Bezier curves in Playdate API

The basic ability to draw Bezier curves is lacking. Are there any plans to add curves to Playdate API?

Earlier on I added this as Feature Request, issue 40, on the Playdate gitlab.

Not just BĂ©zier, but Cattmull-Rom (useful for curves that pass through the defining points) and other related types.

1 Like

Great!
Let’s hope so.

I think adding some use cases to the feature request can only help.

My use case is I want to create a Catmull-Rom spline through points that are positioned around the road of a racing game.

I’ll then query distance from this line for various AI and gameplay.

Feel free to add yours to the issue.

tbh this is a pretty low priority in the huge list of things we want to add/fix. If you’re in Lua, have you seen playdate.graphics.draw/fillPolygon? It might be sufficient to approximate the curve with a polygon, and the polygon scan converter is pretty fast. (Though I haven’t checked how well it performs with tiny segments…) It looks like those functions aren’t in the C API yet (I’ll file a bug), though it does a drawLine function.

This is very strange when someone launch a Doom on Playdate and you suggest drawing curves from polygons. :upside_down_face:
I would very much like to be able to import vector graphics (like svg) from the editor (e.g. Illustrator) and always have the best rendering when converting, compared to bitmaps. This would be a very cool development opportunity. :rocket:

It would be cool. Maybe there’s an existing vector rendering library you can port to the Playdate C API

That would be indeed pretty cool to have Bezier Curve and other splines.

I think this is where the community come into play and developing libraries and sharing them would be super helpful for everyone. Panic has been super dedicated in establishing all the foundations for us. As Dave said, the SDK provides the core functionality to draw polygons or lines and what we need to do is simply developing some functions to generate the points.

1 Like

Unfortunately I didn’t deal with C :slightly_frowning_face:, and Lua is a new language for me, but I really like it.

If it’s low on the list then… I accept that.

Catmull-Rom isn’t very complicated, here it is in JavaScript, but my C is very rusty which is the main hurdle for me. But it’s something that I will tackle soon enough.

I would very much like to be able to import vector graphics (like svg) from the editor (e.g. Illustrator) and always have the best rendering when converting, compared to bitmaps.

I have more thoughts about that strategy.
First I think performance could be an issue (depending on what you want to achieve). Rendering would be much more costly than bitmap so even if you cache the result, it would have an impact on loading.
Second, while vector would definitively have always a better rendering than bitmap on high resolutions, when you start to go low resolution you can have rendering imprecision that actually look worse. Working with bitmap you actually have full control on how it look on the screen in the end because a pixel in the bitmap will be a pixel on the screen.

Funny enough I also started to look 2 weeks ago in importing svg and my goal was to have the picture being rendering progressively. So you would see the whole scene being render shape after shape. I really like this esthetics but I evaluated that it would be a rather big project and I didn’t know if I would actually use it in a game.

1 Like

I tried drawing a curve on Lua.
I would love to port js to lua, it’s just a question of performance.

bezier-1

local accuracy = 0.01

function bezier(t, p0, p1, p2, p3)
	
	local cX = 3 * (p1[1] - p0[1])
	local bX = 3 * (p2[1] - p1[1]) - cX
	local aX = p3[1] - p0[1] - cX - bX
		
	local cY = 3 * (p1[2] - p0[2])
	local bY = 3 * (p2[2] - p1[2]) - cY
	local aY = p3[2] - p0[2] - cY - bY
	
	return ((aX * math.pow(t, 3)) + (bX * math.pow(t, 2)) + (cX * t) + p0[1]),
			((aY * math.pow(t, 3)) + (bY * math.pow(t, 2)) + (cY * t) + p0[2])

end
	
function curve(p0, p1, p2, p3)
	local x,y = p0[1], p0[2]
	
  for i = 0, 1, accuracy do
	local pX, pY = bezier(i, p0, p1, p2, p3)
		 gfx.drawLine(x, y, pX, pY)
		 x,y = pX, pY
  end
end

gfx.setLineCapStyle(gfx.kLineCapStyleRound)
curve({10, 10}, {50, 100}, {150, 200}, {380, 230})

3 Likes

!!! Don’t use this method

Changed it to a polygon. All this is awesome as long as the line width is 1 px.

bezier-poly-1

Houston, we have a problem.

bezier-poly-2

local accuracy = 0.005

function bezier(t, p0, p1, p2, p3)
	
	local cX = 3 * (p1[1] - p0[1])
	local bX = 3 * (p2[1] - p1[1]) - cX
	local aX = p3[1] - p0[1] - cX - bX
	
	local cY = 3 * (p1[2] - p0[2])
	local bY = 3 * (p2[2] - p1[2]) - cY
	local aY = p3[2] - p0[2] - cY - bY
	
	return ((aX * math.pow(t, 3)) + (bX * math.pow(t, 2)) + (cX * t) + p0[1]),
	((aY * math.pow(t, 3)) + (bY * math.pow(t, 2)) + (cY * t) + p0[2])
	
end

function curve(p0, p1, p2, p3)

	local lsa = {p0[1], p0[2]}
	
	for i = 0, 1, accuracy do
		local pX, pY = bezier(i, p0, p1, p2, p3)
		table.insert(lsa, pX)
		table.insert(lsa, pY)
	end

	local poly = geo.polygon.new(table.unpack(lsa))
	poly:setClosed(false)
	gfx.drawPolygon(poly)	
end

curve({10, 10}, {50, 100}, {150, 200}, {380, 230})	
1 Like

That’s cool!

You can use playdate.graphics.drawLine(x1, y1, x2, y2) and set its line width beforehand with playdate.graphics.setLineWidth(width)

Yes, I tried, it doesn’t help.
Regardless of the drawing type (line or poly)

But now we can draw springs! :laughing:

bezier-line-2

This is rounding errors? Try different line cap ending?

I tried, it doesn’t help :man_shrugging:

Ah, I just checked and setLineCapStyle only works on lines. So as Nic says polygon is out in favour of line.

1 Like

Hooray! It’s Working!

bezier-line-3

3 Likes

Can you please share your final code? Thank you!