Log In  

Cart #vgfx_demo-2 | 2024-04-16 | Embed ▽ | License: CC4-BY-NC-SA
62


(click for different demos)

I want to make a game using vector animations so I got started on a way for drawing that.

Main tips for speedy drawing:

  • Scaling/rotating is slower
  • More vector sprites is slower
  • Bigger vector sprites is slower
  • More triangles is MUCH slower

code here:

--[[ vgfx.lua
vector graphics drawing

=== todo: ===
-more triangulation functions
-tween/interpolate between two vector sprites/polygons
-animation playback
-compression for vector data
-maybe look into userdata for optimisations
-maybe try writing directly to graphics memory if it's faster

also:
-make a vector sprite editor/animator app

=== docs: ===
"vector sprite": an array of polygons
"polygon": an array with 3 elements: color 1, color 2, vertices
"vertices": array of numbers (x0,y0, y1,y2, ...etc)

vertices list should be triangulated, sprv() will NOT triangulate arbitrary polygons

tri() and triraster() are decent triangle drawing functions, for lightweight
vector drawing, skip the vector sprite stuff and just use these

]]

vgfx_wire=false--enable for debug view of polygon triangles
vgfx_precise=false--enable if you have issues with edge seams

function make_polygon(col1,col2, verts)
    return {col1,col2,verts}
end

--vector sprite expectations:
-- array of polygons
-- each polygon is an array with:
--  color1, color2, vertices
--vertices expectations:
-- array of numbers (x0,y0, y1,y2, ...etc)
-- should be triangulated already - each three vertices will be drawn as a tri

--draw a vector sprite
function sprv(v, x,y, rot,scale, col)
    local s, cs = 0,0
    if rot and rot!=0 then
        s = sin(rot)
        cs = cos(rot)
    end

    if (col) color(col)
    for p=1,#v do--loop through each polygon in the vector sprite

        --prep to draw this polygon's colours
        if not col then
            fillp(0b1010010110100101)
            color((v[p][1]<<8)+v[p][2])
        end

        --third item in each polygon is a list of vertices (triangulated!)
        local t = v[p][3] 

        --draw each tri now
        local b=0
        local l=#t/6
        local v0 = {}
        local v1 = {}
        local v2 = {}
        for i=1, l do
            if rot and rot!=0 then
                v0 = rotvert(t[1+b],t[2+b],s,cs)
                v1 = rotvert(t[3+b],t[4+b],s,cs)
                v2 =rotvert(t[5+b],t[6+b],s,cs)
            else
                v0 = {x=t[1+b],y=t[2+b]}
                v1 = {x=t[3+b],y=t[4+b]}
                v2 = {x=t[5+b],y=t[6+b]}
            end

            if scale and scale!=1 then
                tri(x+v0.x*scale,y+v0.y*scale, x+v1.x*scale,y+v1.y*scale, x+v2.x*scale,y+v2.y*scale)
            else
                tri(x+v0.x,y+v0.y, x+v1.x,y+v1.y, x+v2.x,y+v2.y)
            end
            b+=6
        end
    end
    return tris
end

function rotvert(x,y,s,c)
    local v={}
    v.x=x*c-y*s
    v.y=x*s+y*c
    return v
end

function fan_triangulate(points)
    local v,i={},3
    while i<=#points do
        add(v,points[i].x)
        add(v,points[i].y)
        add(v,points[i-1].x)
        add(v,points[i-1].y)
        add(v,points[1].x)
        add(v,points[1].y)
        i+=1
    end
    return v
end

function strip_triangulate(points)
    if (#points<=3) return points

    local v,i={},4
    add(v,points[1].x)
    add(v,points[1].y)
    add(v,points[2].x)
    add(v,points[2].y)
    add(v,points[3].x)
    add(v,points[3].y)
    while i<=#points do
        add(v,points[i].x)
        add(v,points[i].y)
        add(v,points[i-1].x)
        add(v,points[i-1].y)
        add(v,points[i-2].x)
        add(v,points[i-2].y)
        i+=1
    end
    return v
end

vgfx_trisdrawn=0
function tri(x0,y0, x1,y1, x2,y2)
    vgfx_trisdrawn += 1

    if (vgfx_precise)   x0,y0,x1,y1,x2,y2 = flr(x0),flr(y0),flr(x1),flr(y1),flr(x2),flr(y2)

    --wireframe
    if vgfx_wire then
        fillp()
        line(x0,y0, x1,y1, 7)
        line(x1,y1, x2,y2, 7)
        line(x2,y2, x0,y0, 7)
        return
    end

    --order the vertices so they are descending from top to bottom
    --we need this since we are drawing it as two triangles:
    --one with a flat base, one with a flat top
    if (y1<y0) x0,x1=x1,x0; y0,y1=y1,y0
    if (y2<y0) x0,x2=x2,x0; y0,y2=y2,y0
    if (y2<y1) x1,x2=x2,x1; y1,y2=y2,y1

    --draw the top half
    local hh=y1-y0--height of the half
    local x3=x0+hh*(x2-x0)/(y2-y0)--slicing the tri in two makes another vertex
    if (y0!=y1) triraster(y0,y1, (x3-x0)/hh,(x1-x0)/hh, x0,x0)

    --draw the bottom half
    hh=y2-y1
    if (y1!=y2) triraster(y1,y2, (x2-x1)/hh,(x2-x3)/hh, x1,x3)
end

--draws a filled triangle line-by-line, top-to-bottom
--args: top, bottom, step left, step right, left pixel, right pixel
function triraster(t,b, sl,sr, pl,pr)
    for y=t,b do
        rectfill(pl,y,pr,y)
        pl+=sl
        pr+=sr
    end
end

P#144124 2024-03-22 11:49 ( Edited 2024-03-26 19:59)

This is really good!

P#144135 2024-03-22 12:20
2

Cart #vgfx_demo-1 | 2024-03-23 | Embed ▽ | License: CC4-BY-NC-SA
2

now it uses userdata to hold/process polygon vertices - the performance increase isn't huge but it's definitely not nothing.
Also I started work on polygon outlines, fixed an issue with the wireframe colours, and I updated the demos to be a little more fancy and show off more things.

new code:

```--[[ vgfx.lua
vector graphics drawing

=== todo: ===
-polygon outlines
-more triangulation functions
-tween/interpolate between two vector sprites/polygons
-animation playback
-compression for vector data
-maybe try writing directly to graphics memory if it's faster

also:
-make a vector sprite editor/animator app

=== docs: ===
"vector sprite": an array of polygons
"polygon": a table with the elements:

  • col (color)
  • col2 (color 2)
  • col3 (outline color)
  • verts (vertices)
  • outline (also vertices)
    "vertices": 1d f66 userdata numbers (x0,y0, y1,y2, ...etc)

vertices list should be triangulated, sprv() will NOT triangulate arbitrary polygons

tri() and triraster() are decent triangle drawing functions, for lightweight
vector drawing, skip the vector sprite stuff and just use these

=== changes: ===

  • polygon vertices now use userdata
  • wire rendering uses same colour rules as filled triangles
    ]]

vgfx_wire=false--enable for debug view of polygon triangles
vgfx_precise=false--enable if you have issues with edge seams

function make_polygon(c1,c2, vertices, outline, c3)
local polygon={col=c1,col2=c2,col3=c3, verts=userdata("f64",#vertices),outline=nil}

for i=1,#vertices do
    set(polygon.verts, i-1, vertices[i])
end
if outline==true then
    --this doesn't work yet!
    local oline = get_outline(vertices)
    polygon.outline = userdata("f64",#oline)
    for i=1,#oline do
        set(polygon.outline, i-1, oline[i])
    end
elseif outline!=nil then
    polygon.outline = userdata("f64",#outline)
    for i=1,#outline do
        set(polygon.outline, i-1, outline[i])
    end
end

return polygon

end

function edgematch(a0x,a0y, a1x,a1y, b0x,b0y, b1x,b1y)
if (a0x==b0x and a0y==b0y) then
return (a1x==b1x and a1y==b1y)
end
if (a1x==b0x and a1y==b0y) then
return (a0x==b1x and a0y==b1y)
end
return false
end
function get_outline(triverts)
--this doesn't work yet.
--even though it should.
--it's misbehaving.
--don't give it any cookies.
local oline={}
for ta=1,#triverts+6,6 do
local add_a = true
local add_b = true
local add_c = true
for tb=ta+6,#triverts+6,6 do
if (edgematch(triverts[ta], triverts[ta+1], triverts[ta+2], triverts[ta+3],
triverts[tb], triverts[tb+1], triverts[tb+2], triverts[tb+3])) then
add_a = false
end
if (edgematch(triverts[ta], triverts[ta+1], triverts[ta+2], triverts[ta+3],
triverts[tb+2], triverts[tb+3], triverts[tb+4], triverts[tb+5])) then
add_a = false
end
if (edgematch(triverts[ta], triverts[ta+1], triverts[ta+2], triverts[ta+3],
triverts[tb+4], triverts[tb+5], triverts[tb], triverts[tb+1])) then
add_a = false
end

        if (edgematch(triverts[ta+2], triverts[ta+3], triverts[ta+4], triverts[ta+5],
        triverts[tb], triverts[tb+1], triverts[tb+2], triverts[tb+3])) then
            add_b = false
        end
        if (edgematch(triverts[ta+2], triverts[ta+3], triverts[ta+4], triverts[ta+5],
        triverts[tb+2], triverts[tb+3], triverts[tb+4], triverts[tb+5])) then
            add_b = false
        end
        if (edgematch(triverts[ta+2], triverts[ta+3], triverts[ta+4], triverts[ta+5],
        triverts[tb+4], triverts[tb+5], triverts[tb], triverts[tb+1])) then
            add_b = false
        end

        if (edgematch(triverts[ta+4], triverts[ta+5], triverts[ta], triverts[ta+1],
        triverts[tb], triverts[tb+1], triverts[tb+2], triverts[tb+3])) then
            add_c = false
        end
        if (edgematch(triverts[ta+4], triverts[ta+5], triverts[ta], triverts[ta+1],
        triverts[tb+2], triverts[tb+3], triverts[tb+4], triverts[tb+5])) then
            add_c = false
        end
        if (edgematch(triverts[ta+4], triverts[ta+5], triverts[ta], triverts[ta+1],
        triverts[tb+4], triverts[tb+5], triverts[tb], triverts[tb+1])) then
            add_c = false
        end
    end

    if add_a then

        add(oline, triverts[ta])
        add(oline, triverts[ta+1])

        add(oline, triverts[ta+2])
        add(oline, triverts[ta+3])
    end
    if add_b then

        add(oline, triverts[ta+2])
        add(oline, triverts[ta+3])

        add(oline, triverts[ta+4])
        add(oline, triverts[ta+5])
    end
    if add_c then

        add(oline, triverts[ta+4])
        add(oline, triverts[ta+5])

        add(oline, triverts[ta])
        add(oline, triverts[ta+1])

    end
end
return oline

end

--draw a vector sprite
function sprv(v, x,y, rot,scale, col)
local s, cs = 0,0
if rot and rot!=0 then
s = sin(rot)
cs = cos(rot)
end
local outline = false
local sinxy = vec(0,0,0,0,0,0)
local cosxy = vec(0,0,0,0,0,0)
local triv = userdata("f64",6)
local tpos = vec(x,y,x,y,x,y)
--local tscale = vec(scale,scale,scale,scale,scale,scale)
if (not scale) scale = 1

if (col) color(col)
for p=1,#v do--loop through each polygon in the vector sprite

    --prep to draw this polygon's colours
    if not col then
        fillp(0b1010010110100101)
        color((v[p].col<<8)+v[p].col2)
    end

    --draw each tri now
    local b=0
    while b<#v[p].verts do
        set(triv, 0, v[p].verts:get(b,6))
        b+=6

        triv *= scale

        if rot and rot!=0 then
            sinxy = triv*s
            cosxy = triv*cs

            set(triv, 0,
            cosxy[0]-sinxy[1], sinxy[0]+cosxy[1],
            cosxy[2]-sinxy[3], sinxy[2]+cosxy[3],
            cosxy[4]-sinxy[5], sinxy[4]+cosxy[5])
        end

        triv += tpos

        tri(triv[0],triv[1],triv[2],triv[3],triv[4],triv[5])

        if (v[p].outline) outline = true
    end
end

if outline then
    for p=1,#v do
        if v[p].outline then
            b=0
            local oline=vec(0,0,0,0)
            if (not col) color((v[p].col3<<8)+v[p].col3)
            while b<#v[p].outline do
                set(oline, 0, v[p].outline:get(b,4))
                b+=4

                --fillp()
                --color()

                oline *= scale
                if rot and rot!=0 then
                    sinxy = oline*s
                    cosxy = oline*cs

                    set(oline, 0,
                    cosxy[0]-sinxy[1], sinxy[0]+cosxy[1],
                    cosxy[2]-sinxy[3], sinxy[2]+cosxy[3])
                end
                oline += tpos
                line(oline[0],oline[1],oline[2],oline[3])

            end
        end
    end
end

end

function fan_triangulate(points)
local v,i={},3
while i<=#points do
add(v,points[i].x)
add(v,points[i].y)
add(v,points[i-1].x)
add(v,points[i-1].y)
add(v,points[1].x)
add(v,points[1].y)
i+=1
end
return v
end

function strip_triangulate(points)
if (#points<=3) return points

local v,i={},4
add(v,points[1].x)
add(v,points[1].y)
add(v,points[2].x)
add(v,points[2].y)
add(v,points[3].x)
add(v,points[3].y)
while i<=#points do
    add(v,points[i].x)
    add(v,points[i].y)
    add(v,points[i-1].x)
    add(v,points[i-1].y)
    add(v,points[i-2].x)
    add(v,points[i-2].y)
    i+=1
end
return v

end

vgfx_trisdrawn=0
function tri(x0,y0, x1,y1, x2,y2)
vgfx_trisdrawn += 1

if (vgfx_precise)   x0,y0,x1,y1,x2,y2 = flr(x0),flr(y0),flr(x1),flr(y1),flr(x2),flr(y2)

--wireframe
if vgfx_wire then
    fillp()
    line(x0,y0, x1,y1)
    line(x1,y1, x2,y2)
    line(x2,y2, x0,y0)
    return
end

--order the vertices so they are descending from top to bottom
--we need this since we are drawing it as two triangles:
--one with a flat base, one with a flat top
if (y1<y0) x0,x1=x1,x0; y0,y1=y1,y0
if (y2<y1) x1,x2=x2,x1; y1,y2=y2,y1
if (y1<y0) x0,x1=x1,x0; y0,y1=y1,y0

--draw the top half
local hh=y1-y0--height of the half
local x3=x0+hh*(x2-x0)/(y2-y0)--slicing the tri in two makes another vertex
if (y0!=y1) triraster(y0,y1, (x3-x0)/hh,(x1-x0)/hh, x0,x0)

--draw the bottom half
hh=y2-y1
if (y1!=y2) triraster(y1,y2, (x2-x1)/hh,(x2-x3)/hh, x1,x3)

end

--draws a filled triangle line-by-line, top-to-bottom
--args: top, bottom, step left, step right, left pixel, right pixel
function triraster(t,b, sl,sr, pl,pr)
for y=t,b do
rectfill(pl,y,pr,y)
pl+=sl
pr+=sr
end
end```

P#144231 2024-03-23 17:00
9

Cart #veditor-0 | 2024-03-26 | Embed ▽ | License: CC4-BY-NC-SA
9


I started work on an editor for making vector sprites. You can't save/export your work yet and a few other aspects are still not yet in place, but it's starting to feel useable (if you're okay being limited to fan-triangulated polygons. I'll get ear clipping working another day)

P#144570 2024-03-26 19:47

exciting!

P#144720 2024-03-27 16:57

could you possibly make a launcher for this with auto updates like Slate?

P#145869 2024-04-06 00:19

[Please log in to post a comment]