Log In  

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

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
    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.
P#146575 2024-04-13 22:27

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)
    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
P#146576 2024-04-13 22:28

[Please log in to post a comment]