Beziérs

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

@em2 Excellent! Beziér curves have been on my to do list for a long time, so thanks for this!

Whenever lines have transparency and intersect (regardless of the caps mode), you will see an overlay effect, which is normal. If you want to avoid this, you can draw all lines into an image() texture and draw that image at certain opacity.

I reduced the alpha and I like the effect that shows more than a solid line.

Good job.

@se24vad the best solution would be to use a mesh (not with addRect) and manually create vertices by calculating normals at intersections. I only wish I knew how to calculate them, but calculus (which derives normals and tangents) is beyond me.
For now, your suggestion will work, but I will probably have to create a Bézier class.

I did a mesh/shader version of Béziers a while ago. You can find it on github. It implements them both as a drawing command and as a class.