Log In  


Cart #tline3drot_example-0 | 2024-04-13 | Embed ▽ | License: CC4-BY-NC-SA
18

This cartridge is an example of the tline3d rotation algorithm!

I adapted this code from @freds72 's PICO-8 sprite rotation algorithm with tline(). They also helped me with some small edits via the Picotron Discord! You can check out freds' cart here: https://www.lexaloffle.com/bbs/?tid=37561

Here's the uncommented version to add to your project files (I'd recommend making a new file, such as rspr.lua, then including that file in your project to keep your own code clean):

-- rspr.lua
function rspr(sprite,cx,cy,sx,sy,rot)
	sx = sx and sx or 1
	sy = sy and sy or 1
	rot = rot and rot or 0
	local tex = get_spr(sprite)
	local dx,dy = tex:width()*sx,tex:height()*sy
	local quad = {
		{x=0, y=0, u=0, v=0},
		{x=dx, y=0, u=tex:width()-0.001, v=0},
		{x=dx, y=dy, u=tex:width()-0.001, v=tex:height()-0.001},
		{x=0, y=dy, u=0, v=tex:height()-0.001},
	}
	local c,s = cos(rot),-sin(rot)
	local w,h = (dx-1)/2, (dy-1)/2
	for _,v in pairs(quad) do
		local x,y = v.x-w,v.y-h
		v.x = c*x-s*y
		v.y = s*x+c*y	
	end
	tquad(quad, tex, cx, cy)
end

function tquad(coords,tex,dx,dy)
	local screen_max = get_display():height()-1
	local p0,spans = coords[#coords],{}
	local x0,y0,u0,v0=p0.x+dx,p0.y+dy,p0.u,p0.v
	for i=1,#coords do
		local p1 = coords[i]
		local x1,y1,u1,v1=p1.x+dx,p1.y+dy,p1.u,p1.v
		local _x1,_y1,_u1,_v1=x1,y1,u1,v1
		if(y0>y1) x0,y0,x1,y1,u0,v0,u1,v1=x1,y1,x0,y0,u1,v1,u0,v0
		local dy=y1-y0
		local dx,du,dv=(x1-x0)/dy,(u1-u0)/dy,(v1-v0)/dy
		if(y0<0) x0-=y0*dx u0-=y0*du v0-=y0*dv y0=0
		local cy0=ceil(y0)
		local sy=cy0-y0
		x0+=sy*dx
		u0+=sy*du
		v0+=sy*dv
		for y=cy0,min(ceil(y1)-1,screen_max) do
			local span=spans[y]
			if span then tline3d(tex,span.x,y,x0,y,span.u,span.v,u0,v0)
			else spans[y]={x=x0,u=u0,v=v0} end
			x0+=dx
			u0+=du
			v0+=dv
		end
		x0,y0,u0,v0=_x1,_y1,_u1,_v1
	end
end

You don't need to call tquad yourself, rspr will do that for you. rpsr takes 6 arguments, the last three of which are optional:

  • sprite: the spritesheet index to draw
  • cx: the screen x-coordinate to render the sprite (the center of the sprite)
  • cy: the screen y-coordinate to render the sprite (the center of the sprite)
  • sx: (optional) the scale factor in the x-axis (defaults to 1)
  • sy: (optional) the scale factor in the y-axis (defaults to 1)
  • rot: (optional) the angle at which to rotate your sprite [0-1)

By no means is this code minified or code-golfed, so feel free to hack away at the token count if you wish!
In the comments below I'll drop a commented version that does a better job of explaining what each line does.

Current limitations:

  • tline3d doesn't like bitmaps that don't have dimensions that are a power of 2. Be sure that your sprite has side-lengths that are a power of 2 (i.e. 8, 16, 32, 64, etc). This code can handle rectangles. If your sprite doesn't take up the entire space canvas, just use a transparency color to give it padding.
  • draws from the CENTER of the sprite, not the top-left corner. Keep this in mind when rendering your sprites if you also need to handle collisions or other positional updates.
18


Here's the commented code for education purposes!

-- rspr - draw a rotated sprite
-- param: sprite - the number of the sprite to read from the spritesheet
-- param: cx - the x-coordinate on which to render the sprite (center of the sprite, not top-left)
-- param: cy - the y-coordinate on which to render the sprite (center of the sprite, not top-left)
-- param: sx - scale factor in the x direction
-- param: sy - scale factor in the y direction
-- param: rot - the angle to render the sprite [0-1)
function rspr(sprite,cx,cy,sx,sy,rot)
	-- get default values
	sx = sx and sx or 1
	sy = sy and sy or 1
	rot = rot and rot or 0

	-- get sprite userdata and sprite dimensions
	local tex = get_spr(sprite)
	local dx,dy = tex:width()*sx,tex:height()*sy

	-- create a polygon of 4 points that will represent the corners of the rendered sprite
	-- we subtract an arbitrarily small amount from the texture to prevent artifacting (tline3d bug)
	local quad = {
		{x=0, y=0, u=0, v=0},
		{x=dx, y=0, u=tex:width()-0.001, v=0},
		{x=dx, y=dy, u=tex:width()-0.001, v=tex:height()-0.001},
		{x=0, y=dy, u=0, v=tex:height()-0.001},
	}

	-- calculate cosine and sine, as well as the center point for the sprite
	local c,s = cos(rot),-sin(rot)
	local w,h = (dx-1)/2, (dy-1)/2

	-- rotate each point in the quad
	for _,v in pairs(quad) do
		local x,y = v.x-w,v.y-h
		v.x = c*x-s*y
		v.y = s*x+c*y	
	end

	-- render the quad to the display
	tquad(quad, tex, cx, cy)
end

-- tquad - render a textured quadrilateral. based on @fred72's implementation here: https://www.lexaloffle.com/bbs/?tid=37561
-- param: coords - a table with 4 subtables. each subtable has values for x, y, u, v
-- param: tex - a bitmap userdata that represents the texture to render
-- param: dx, dy - the center of the quad on screen
function tquad(coords,tex,dx,dy)
	-- what is the max y value that will get rendered? use this to cull unnecessary draw ops
	local screen_max = get_display():height()-1
	-- load the first set of points into the p0 table
	local p0,spans = coords[#coords],{}
	-- extract coordinates, offset by the desired position
	local x0,y0,u0,v0=p0.x+dx,p0.y+dy,p0.u,p0.v

	-- loop through the polygon's vertices
	for i=1,#coords do
		-- get the next vertex from the list
		local p1 = coords[i]
		-- extract coordinates, offset by desired position
		local x1,y1,u1,v1=p1.x+dx,p1.y+dy,p1.u,p1.v
		-- save those coordinate values to restore later
		local _x1,_y1,_u1,_v1=x1,y1,u1,v1
		-- swap coordinate pairs if they are out of order (with respect to y)
		if(y0>y1) x0,y0,x1,y1,u0,v0,u1,v1=x1,y1,x0,y0,u1,v1,u0,v0
		-- get the difference between y0 and y1
		local dy=y1-y0
		-- get the change in x,u,v based on the slope of y
		local dx,du,dv=(x1-x0)/dy,(u1-u0)/dy,(v1-v0)/dy
		-- cull our calculations to not go above y=0 to save CPU
		if(y0<0) x0-=y0*dx u0-=y0*du v0-=y0*dv y0=0
		-- snap y0 to a pixel value
		local cy0=ceil(y0)
		-- get the fractional difference between y0 and cy0
		local sy=cy0-y0
		-- adjust x,u,v by the fractional offset
		x0+=sy*dx
		u0+=sy*du
		v0+=sy*dv
		-- loop through all of the horizontal rows that we need to draw to make the rotated shape
		-- we stop at screen_max if the sprite goes off screen to save CPU
		for y=cy0,min(ceil(y1)-1,screen_max) do
			-- a "span" is a row of pixels that we'll draw. attempt to get the span that we saved for this y-value from the list
			local span=spans[y]
			-- if we found a span, then draw a horizontal line across the span, sampling the texture from the sprite provided
			if span then tline3d(tex,span.x,y,x0,y,span.u,span.v,u0,v0)
			-- if we didn't find a span, add one to the list
			else spans[y]={x=x0,u=u0,v=v0} end
			-- add the change-in values to each coordinate based on the slope of y
			x0+=dx
			u0+=du
			v0+=dv
		end
		-- restore coordinates to prepare for the next loop
		x0,y0,u0,v0=_x1,_y1,_u1,_v1
	end
end

if the camera follows the player, how i can make it works?


Since this has already been bumped, did you mean for quad to be a global variable? (line 8)


@edrato, I believe this code will work with the camera as well - wherever the sprite you're drawing is located in the game world (cx, cy), if that location is present within the camera's clipping rect, the sprite will be visible.

@Soupster, great point! No, that should not be a global. Thank you for pointing that out.


1

Another nitpick: You can use for v in all() do instead of for _,v in pairs() do



[Please log in to post a comment]