Beziers!

This was inspired by @mpilgrem’s post on Bézier curves with his petals, and here is my code

function setup()
    points = {}
    for i = 1, math.random(3, 7) do
        table.insert(points, vec2(math.random(WIDTH), math.random(HEIGHT)))
    end
    t = 0 -- time
    q = 100 -- quality
    P = {} -- points of the bezier
    parameter("speed", .1, 2, .75)
end

function draw()
    background(0, 0, 0, 255)
    stroke(127, 127, 127, 255)
    t = t + speed
    if t > q then
        t = 0
        P = {}
    end
    table.insert(P, drawBezier(points, t, q))
    stroke(255, 255, 255, 255)
    for i = 1, #P - 1 do
        line(P[i], P[i + 1])
    end
end

function drawBezier(p)
    local len = #p
    local lines = {}
    if len > 2 then
        strokeWidth(len)
        for i = 1, #p - 1 do
            table.insert(lines, p[i + 1] - p[i])
            line(p[i], p[i + 1])
        end
        local newP = {}
        for i, l in ipairs(lines) do
            table.insert(newP, p[i] + (l / q) * t)
        end
        return drawBezier(newP)
    end
    line(p[1], p[2])
    return p[1] + (p[2] - p[1]) / q * t
end

local l = line
line = { f = l }
function line:__call(x, y, a, b)
    if type(y) == "userdata" then
        self.f(x.x, x.y, y.x, y.y)
    elseif type(x) == "userdata" then
        self.f(x.x, x.y, y, a)
    elseif type(a) == "userdata" then
        self.f(x, y, a.x, a.y)
    else
        self.f(x, y, a, b)
    end
end
setmetatable(line, line)

Nothing happens when I launch the program, Is there any thing missing ?
Thanks in advance.

Oh, my bad! I was using a modification to the line function i wrote as a dependency, here is the code (and I edited the original post)

local l = line
line = { f = l }
function line:__call(x, y, a, b)
    if type(y) == "userdata" then
        self.f(x.x, x.y, y.x, y.y)
    elseif type(x) == "userdata" then
        self.f(x.x, x.y, y, a)
    elseif type(a) == "userdata" then
        self.f(x, y, a.x, a.y)
    else
        self.f(x, y, a, b)
    end
end
setmetatable(line, line)

Thanks for the rapid reply.

Hello @Jordan. I see that you (1) make use of the recursive definition of Bezier curves with recursive calls to drawBezier() and (2) extend Codea’s line() function to take one or more vec2 arguments.

If you have not seen it, you may be interested in @Codeslinger’s Bezier Animation Demo, which also makes use of the recursive definition, by counting down through the chain of control points.

Thanks @mpilgrem, I never saw that section of the wikipedia article, but I have seen @codeslinger’s demo, but a LONG time ago. That line extension has been applied to all of Codea’s drawing operations in my library, as I have been working on a general upgrade to codea’s functions and types as I have not yet found an idea for my next project.

The code below is a non-recursive approach:


function setup()
    p = {}  -- Control points of the Bezier curve
    for i = 1, math.random(3, 4) do
        p[i] = vec2(math.random(WIDTH - 40) + 20, math.random(HEIGHT - 40) + 20)
    end
    b = B(p)
    t = 0   -- time
    q = 100 -- quality
    P = {b:point(0)} -- points of the Bezier curve
    parameter("speed", .1, 2, .75)
    font("Inconsolata")
    fontSize(20)
    fill(255, 0, 0)
end

function draw()
    background(0)
    strokeWidth(5)
    t = t + speed
    if t > q then
        t = 0
        P = {b:point(0)}
    end
    table.insert(P, b:point(1/q * t))
    stroke(255)
    for i = 1, #P - 1 do
        line(P[i].x, P[i].y, P[i + 1].x, P[i + 1].y)
    end
    -- Overlay control points
    noStroke()
    for i = 1, #p do
        ellipse(p[i].x, p[i].y, 10)
        text(i, p[i].x + 10, p[i].y + 10)
    end
end

B = class()

function B:init(p)
    self.p = p
end

function B:point(t)
    local n = #self.p
    local nm1 = n - 1
    local t1 = 1 - t
    local p = vec2()
    for i = 0, nm1 do
       p = p + math.choose(nm1, i) * t1 ^ (nm1 - i) * t ^ i * self.p[i + 1]
    end
    return p
end

-- helper function
function math.choose(n, k)
    local choose = 1
    for i = 1, k do
        choose = choose * (n - (k - i)) / i
    end
    return choose
end

Jordan: check your scoping. You pass t and q to drawBezier but in that function you don’t use the passed variables, you use the global ones (which happen to be the same).

Secondly, I wouldn’t use lines for this. Make a mesh, it’ll look cleaner.

Thirdly, you are stepping along the bezier at even steps in the parameter space. Try to do it so that your steps are evenly spaced along the length of the curve (there’s code for this buried in the roller coaster).

Here’s a screenshot from an old not-finished project of mine with beziers.

http://pic.twitter.com/HLXPuEpU

Thanks @Andrew_Stacey, my answers are:

  1. At first I passed the time and quality variables to the function, but I got an error (AHA, just realized that when I called the recursive function, I never passed t and q which gave me an error)
  2. I am not sure how I would use a mesh for this, I have seen you do it in harmony, but I never got around to seeing how that project worked… Can you give me a quick explanation? I am thinking flat triangles, but those would be invisible right? So making a thin rectangle?
  3. Emphasis on the word buried. I am not the best at quickly locating code in foreign projects, and I have put the roller coaster example as higher than the capacity of my personal computing power.

Thanks for commenting
Jordan

Hi @Jordan.

Sorry! My comments were a bit curt - a feature of posting from the iPad, I’m afraid.

  1. Ah, I see you figured that one out. I was going to say that. By the way, I recommend @mpilgrem’s method. He passes (effectively) t/q.
  2. I said that before I’d seen your code running so didn’t see what you were going to do. It would be instructive to build a mesh and colour it sequentially rather than create/destroy it each cycle. But having seen it in action, I’d rather just recommend using a better lineCapMode.
  3. That’s something you should learn to do: it’s a great way to learn. But I forgot your age - the roller coaster probably isn’t the easiest code to learn from. Though of my code it does have the advantage of being self-contained.

YAY! Another response! I feel famous!

  1. But then wouldn’t I encounter the binary to decimal shenanigans again?
  2. Maybe I should check out PROJECT because SQUARE doesn’t connect them properly. I want to try and get this to work with a mesh, as I think it would give a much better appearance.
  3. I can try and understand the code, but any pointers as to where I will find the section to spread out the code (I am thinking this would be messy, as some parts of the curve will have super sharp edges… unless you made the spacing tiny…)

P.S. HOW DO YOU DO A LINE BREAK!!!
Thanks for the response
Jordan

  1. What shenanigans are those?
  2. Yes, ROUNDED (or is it ROUND) is the one to avoid. It looks awful.
  3. I think that (of my code) your best place to look is the function addLine in the Harmony code. That adds a line segment to a mesh and there’s an option for angling the ends so that neighbouring segments match up.

As for line breaks, search for Markdown. In brief, lists are done using:

1. First item
2. Second item

To get a literal line break (as in <br/> or <br> - I forget if it’s HTML or XHTML on this forum), put three spaces at the end of the line (take a look at the end of your post - I just edited it for formatting).

  1. The ones where certain numbers are impossible to represent in binary (e.g. 0.2), I have a post about this (something like Modulo headache)
  2. I agree, But it has a graph-like look to it which fascinates me.
  3. I will check Harmony out again.
    Thanks for the tip.
    Jordan

Yes, but in this case you are never trying to recreate the original number and you always divide t by q when you use it so the inaccuracy doesn’t matter. In the end you only care about accuracy to 3 significant figures - pixels on the screen. The only time you need more accuracy is in tests and you don’t have them.

Exercise for you (worth doing): how accurate do you need to specify t to know the point on the curve to within a pixel?