Porting my love2d project that relies on a custom spline editor. Use a mesh?

Hi,

I have a love2d project where that relies heavily on a spline editor. To do this I generate a closed catmull-rom spline with some initial control points (can add up to 15 points and remove such that there’s a minimum o9f 3 points) - I was wondering how I might port this project to defold - I’ve found some information on meshes in the forums but they seem a little out of date. I can import my custom geometry library as a lua module to create all the vector math I need but rendering the spline (I also fill in its area - currently by triangulation then filling in the area of the triangles). is meshes the correct way to do this and is there an example of meshes being used somewhere? I’d hoped the 3d example might have some mesh components but they just have models (although currently my project only has the math for 2d irregular shapes anyway - so I’d probably just set the z vector to 0 for now for all the vertices) - this needs to allow me to drag control points and regenerate the spline and fill its area at runtime but I’m unsure about how I work with meshes in defold (especially editing them at runtime) as I can’t quite find an example. Am I even approaching this correctly?

My Lua spline geometry can be found here if it’s helpful in answering the question (or if anyone just wants to have a look! luaSplineGeometry/spline_geometry.lua at main · SeanTyson/luaSplineGeometry · GitHub

Some notes:

I need to be able to save the resulting triangles so I can store different “territories“
eventually my ideal would that these meshes of different “territories“ would be shaded parts of the world space - which would (my initial idea is) also be a mesh of “terrain“

Thanks in advance to anyone willing to give me some pointers :slight_smile:

1 Like

Hello and welcome to the community! :waving_hand: :blush:

Yes, I think for your use case the best is to use Defold Mesh component + runtime buffers. You could generate your Catmull-Rom spline with your Lua lib in Defold, then triangulate it to be put on triangles, then push the vertices (and optional UVs/colors) into a buffer and set it on a mesh. Whenever the user drags a control point - rebuild the buffer.

There is a great talk by @mozokevgen about meshes in Defold, recorded and uploaded here, I would recommend to start with it, it’s great to learn about meshes:

Then checkout the other resources:

If you are satisfied with lines or something, there is also this extension, perhaps it could be of any help:

2 Likes

Thanks so much for replying and so quickly :slight_smile: (also as a quick aside, I’ve watched a lot of your videos while learning the engine basics so thanks so much for making them!) I’ll checkout the video, I haven’t seen this one yet, then try come up with some implementation. Thanks again!

1 Like

It’s a tough topic and there are not so many resources regarding this, so don’t hesitate to write with anything you struggle with, we’ll try to help :wink: As soon as you grasp the idea of buffers and meshes, I believe this will be very possible to be done, a lot of math, but looking at your repo, I believe you will be able to do it honestly :smiley:

1 Like

I would be curious to know how to draw a convex/concave polygon outline via meshes, for debugging purposes.
I’m doing it right now by using the draw line message to the render script, but that is not ideal and doesn not have control over line thickness for example.

2 Likes

I’m still working on porting but I’ve got convex shapes working. Concave hopefully might work but im finishing off movable control points to test that. (Drag them to make a concave shape) I’ll send you some sample code I can rip out when I’m at the pc

1 Like

@gianmichele I’ve tested my code with vertices for a concave shape. it’s not quite the shape I expected but it is concave haha. I’m still working on getting it to work with control_points that can be moved then it can be regenerated on the fly but with fixed vertices I have this

local spline_geom = require(“game.spline_geometry”)

function regenerate_mesh(self)
– Generate spline from current control points
local spline = spline_geom:generateSpline(self.control_points)
-- Triangulate the spline polygon
local triangles = spline_geom.triangulatePolygon(spline)

-- Flatten triangles into vertices array (x,y,z)
local triangle_vertices = {}
for _, tri in ipairs(triangles) do
	for i = 1, 6, 2 do
		table.insert(triangle_vertices, tri[i])     -- x
		table.insert(triangle_vertices, tri[i+1])   -- y
		table.insert(triangle_vertices, 0)          -- z
	end
end

-- Create buffer and upload data to the mesh
local buf = buffer.create(#triangle_vertices / 3, {
	{ name = hash("position"), type = buffer.VALUE_TYPE_FLOAT32, count = 3 }
})

local positions = buffer.get_stream(buf, "position")
for i = 1, #triangle_vertices do
	positions[i] = triangle_vertices[i]
end

local res = go.get("#mesh", "vertices")
resource.set_buffer(res, buf)

print("Mesh regenerated with " .. #triangles .. " triangles")
end

function init(self)
msg.post(“.”, “acquire_input_focus”)
–  { {0, 0}, {1, 0}, {1, 1}, {0, 1} } curvy cube
self.control_points = { {-0.5, 1}, {-0.25, 0.5}, {-0.5, -1}, {0.25, 0.5} }
regenerate_mesh(self)
– local pos = go.get_position()
– msg.post(“spawner#spawner”, “create_control_point”, {x = pos.x, y = pos.y})
– msg.post(“spawner#spawner”, “create_control_point”, {x = pos.x+30, y = pos.y+30})

end

and the relevant spline_geometry.lua functions are


– ✳ Generates a smooth curve that passes through p1 and p2.
– ✳ This uses a cubic Hermite polynomial interpolation with 4 points.
– ✳ t ∈ [0, 1] defines the fraction along the segment between p1 and p2.
local function catmullRom(p0, p1, p2, p3, t)
local t2, t3 = t * t, t * t * t – ✳ Precompute powers for efficiency
– ✳ The formula below is derived from the Catmull–Rom spline equation:
– ✳ P(t) = 0.5 * ((2P1) + (-P0 + P2)t + (2P0 - 5P1 + 4P2 - P3)t² + (-P0 + 3P1 - 3P2 + P3)t³)
– ✳ Each coordinate is computed independently.
local x = 0.5 * ((2 * p1[1])
+ (-p0[1] + p2[1]) * t
+ (2p0[1] - 5p1[1] + 4p2[1] - p3[1]) * t2
+ (-p0[1] + 3
p1[1] - 3p2[1] + p3[1]) * t3)
local y = 0.5 * ((2 * p1[2])
+ (-p0[2] + p2[2]) * t
+ (2
p0[2] - 5p1[2] + 4p2[2] - p3[2]) * t2
+ (-p0[2] + 3p1[2] - 3p2[2] + p3[2]) * t3)
return { x, y }
end

function spline_geom:generateSpline(points)
   local spline = {}
   local n = #points
   if n < 3 then return spline end – ✳ Need at least 3 points to define a curve

for i = 1, n do
	-- ✳ Wrap indices (mod n) to form a closed loop.
	local p0 = points[((i-2)%n)+1]
	local p1 = points[i]
	local p2 = points[(i%n)+1]
	local p3 = points[((i+1)%n)+1]
	-- ✳ Sample the curve between p1 and p2 in samplesPerSegment increments.
	for s = 0, self.samplesPerSegment-1 do
		table.insert(spline, catmullRom(p0,p1,p2,p3,s/self.samplesPerSegment))
	end
end
return spline
end

function spline_geom.ensureCounterClockwise(polygon)
– Calculate signed area to determine winding
local area = 0
local n = #polygon
for i = 1, n do
local j = i % n + 1
area = area + (polygon[j][1] - polygon[i][1]) * (polygon[j][2] + polygon[i][2])
end end

-- If area is positive, it's clockwise, so reverse
if area > 0 then
	local reversed = {}
	for i = n, 1, -1 do
		table.insert(reversed, polygon[i])
	end
	return reversed
end
return polygon
function spline_geom.triangulatePolygon(polygon)
polygon = spline_geom.ensureCounterClockwise(polygon)
– polygon: { {x1,y1}, {x2,y2}, … }
local n = #polygon
if n < 3 then return {} end
if n == 3 then
return { { polygon[1][1], polygon[1][2],
polygon[2][1], polygon[2][2],
polygon[3][1], polygon[3][2] } }
end

-- Copy
 vertices and initialize next/prev indices
local vertices = {}
for i, p in ipairs(polygon) do
	vertices[i] = {x = p[1], y = p[2], i = i}
end

local function isCCW(a,b,c) -- counter clockwise
	return ((b.x - a.x)*(c.y - a.y) - (b.y - a.y)*(c.x - a.x)) >= 0
end

local function isEar(a,b,c, others)
	if not isCCW(a,b,c) then return false end
	for _, p in ipairs(others) do
		if p ~= a and p ~= b and p ~= c then
			if pointInTriangle(p.x,p.y, a.x,a.y, b.x,b.y, c.x,c.y) then
				return false
			end
		end
	end
	return true
end

local result = {}
local V = {}
for i = 1, #vertices do
	V[i] = vertices[i]
end

while #V > 3 do
	local earFound = false
	for i=1,#V do
		local prev = V[(i-2)%#V+1]
		local curr = V[i]
		local next = V[i%#V+1]

		if isEar(prev,curr,next,V) then
			table.insert(result, {prev.x,prev.y, curr.x,curr.y, next.x,next.y})
			table.remove(V,i)
			earFound = true
			break
		end
	end
	if not earFound then
		error("Cannot triangulate polygon: possible self-intersection or degenerate polygon")
	end
end

-- Last remaining triangle
table.insert(result, {V[1].x,V[1].y, V[2].x,V[2].y, V[3].x,V[3].y})

return result
end

local function pointInTriangle(px, py, x1, y1, x2, y2, x3, y3)
local d1 = (px - x2)(y1 - y2) - (py - y2)(x1 - x2)
local d2 = (px - x3)(y2 - y3) - (py - y3)(x2 - x3)
local d3 = (px - x1)(y3 - y1) - (py - y1)(x3 - x1)
local has_neg = (d1 < 0) or (d2 < 0) or (d3 < 0)
local has_pos = (d1 > 0) or (d2 > 0) or (d3 > 0)
return not (has_neg and has_pos)
end

that should get you started with creating convex meshes. tbh you probably dont need the triangulation if you’re only looking to draw an outline since defold seems to have a line primitive you can use for the mesh - you might just be able to use the splinepoints from the generatespline function but I haven’t tried that. the control points that generate me a concave mesh are self.control_points = { {-0.5, 1}, {-0.25, 0.5}, {-0.5, -1}, {0.25, 0.5} } the pointInTriangle and triangulation functions are there if you wanna play with em though :slight_smile:

this just requires an initial buffer with a position stream to be set on the mesh.

2 Likes

oh my god the code formatting is broken as all hell. good luck to anyone trying to glue that together

1 Like

Oh wow! Thank you so much for this!

2 Likes

No problem mate :slight_smile: let me know if you have any issues with it. It’ll probably be something I’ve come up against haha

1 Like