Log In  


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


(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

38


This is really good!


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 = trivs
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```


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)


exciting!


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



[Please log in to post a comment]