Just figured this out today. To draw a bezier, call cubicBezier
. The first and last arguments are start and end points of the curve, while middle arguments are control points.
Unfortunately, I don’t understand derivatives and cannot make smooth meshes. Reducing the alpha of curveColor
will demonstrate the messy issue I have encountered by using lines with round line caps.
--# Main
-- Bézier Curves
function setup()
-- Tests
tests = {Test1(),Test2()}
updateTest(1)
parameter.watch("Current_Test_Name")
parameter.integer("Test_Number",1,#tests,1,updateTest)
-- Colors
parameter.color("curveColor",color(242, 32, 32, 255))
touchAreaColor = color(255,60)
touchAreaColorOnTouch = color(255,100)
end
function draw()
-- This sets a dark background color
background(40, 40, 50)
currentTest:draw()
end
function touched(touch)
currentTest:touched(touch)
end
function updateTest(n)
currentTest = tests[n]
_G["Current_Test_Name"] = currentTest.name
end
--# Test1
Test1 = class()
-- Test 1: Quadratic Béziers
function Test1:init()
-- Test name
self.name = "Quadratic Béziers"
-- Points
self.p0 = vec2( 100, 100 )
self.p1 = vec2( WIDTH / 2, 300 )
self.p2 = vec2( WIDTH - 100, 100)
-- Dragging handles
self.h0 = handle(self.p0)
self.h1 = handle(self.p1)
self.h2 = handle(self.p2)
end
function Test1:draw()
-- Draw curve
fill(curveColor)
stroke(curveColor)
strokeWidth(10)
quadBezier(self.p0,self.p1,self.p2)
-- Draw touch areas
noStroke()
self.h0:draw()
self.h1:draw()
self.h2:draw()
end
function Test1:touched(touch)
if self.h0:touched(touch) then self.h0:move("p0",self) return end
if self.h1:touched(touch) then self.h1:move("p1",self) return end
if self.h2:touched(touch) then self.h2:move("p2",self) return end
end
--# Test2
Test2 = class()
-- Test 2: Cubic Béziers
function Test2:init()
-- Test name
self.name = "Cubic Béziers"
-- Points
self.p0 = vec2( 100, 100 )
self.p1 = vec2( WIDTH * 1/3, 300 )
self.p2 = vec2( WIDTH * 2/3, 300 )
self.p3 = vec2( WIDTH - 100, 100 )
-- Dragging handles
self.h0 = handle(self.p0)
self.h1 = handle(self.p1)
self.h2 = handle(self.p2)
self.h3 = handle(self.p3)
end
function Test2:draw()
-- Draw curve
fill(curveColor)
stroke(curveColor)
strokeWidth(10)
cubicBezier(self.p0,self.p1,self.p2,self.p3)
-- Draw touch areas
noStroke()
self.h0:draw()
self.h1:draw()
self.h2:draw()
self.h3:draw()
end
function Test2:touched(touch)
if self.h0:touched(touch) then self.h0:move("p0",self) return end
if self.h1:touched(touch) then self.h1:move("p1",self) return end
if self.h2:touched(touch) then self.h2:move("p2",self) return end
if self.h3:touched(touch) then self.h3:move("p3",self) return end
end
--# cubicCurves
function cubicCurveAt(t,p0,p1,p2,p3)
local vec = (1-t)*(1-t)*(1-t)*p0 + 3*(1-t)*(1-t)*t*p1 + 3*(1-t)*t*t*p2 + t*t*t*p3
return vec
end
function cubicBezier(p0,p1,p2,p3)
local len = squareDist(p0,p1)+squareDist(p1,p2)+squareDist(p2,p3) -- get approximate length
local divisions = math.floor(math.sqrt(len+6400)/16) -- calculate hyperbola
local increment = 1/divisions
for i = 1, divisions do
local t = increment * i
local c = cubicCurveAt(t,p0,p1,p2,p3)
local n = cubicCurveAt(t-increment,p0,p1,p2,p3)
line(c.x,c.y,n.x,n.y)
end
end
--# quadCurves
function quadCurveAt(t,p0,p1,p2)
local vec = (1 - t) * (1 - t) * p0 + 2 * (1 - t) * t * p1 + t * t * p2
return vec
end
function quadBezier(p0,p1,p2)
local len = squareDist(p0,p1)+squareDist(p1,p2) -- get approximate length
local divisions = math.floor(math.sqrt(len+6400)/16) -- calculate hyperbola
local increment = 1/divisions
for i = 1, divisions do
local t = increment * i
local c = quadCurveAt(t,p0,p1,p2)
local n = quadCurveAt(t-increment,p0,p1,p2)
line(c.x,c.y,n.x,n.y)
end
end
--# Misc
function squareDist(a,b)local c=a.x-b.x;local d=a.y-b.y;return c*c+d*d end;
handle=class()function handle:init(e)self.p=e;self.state="initial"self.handler=function()end;self.handlerArgs={}end
function handle:draw()if self.state=="touching"or self.state=="dragging"then fill(touchAreaColorOnTouch)else fill(touchAreaColor)end;ellipse(self.p.x,self.p.y,60)ellipse(self.p.x,self.p.y,30)end
function handle:touched(f)if f.state==ENDED or f.state==CANCELLED then self.state="initial"end;if squareDist(f,self.p)<225 then if f.state==BEGAN and self.state=="initial"then self.state="touching"return true end end;if f.state==MOVING and self.state=="touching"or self.state=="dragging"then self.state="dragging"self.p=vec2(f.x,f.y)return true end end;function handle:move(e,g)(g or _G)[e]=self.p end