Bloom: a first experiment with procedural flowers

The code below (2017 update: updated for the introduction of integers in Lua 5.3) builds on my recent experiments with pastel colours and the golden angle to generate procedural flowers. The petals are drawn with a mesh using quadratic Bézier curves. The flowers, once initialised, draw themselves as a sprite. (Update) An example of its output is below:


--
-- Bloom
--

supportedOrientations(LANDSCAPE_ANY)
function setup()
    w2, h2 = WIDTH / 2, HEIGHT / 2
    bImg = image(WIDTH, HEIGHT)
    setContext(bImg)
    background(100, 175, 60)
    rectMode(CENTER)
    for i = 1, 1000 do
        resetMatrix()
        rotate(math.random(-10, 10))
        fill(math.random(90, 110),
        math.random(150, 200),
        math.random(50, 70))
        rect(math.random(WIDTH), math.random(HEIGHT),
            math.random(5, 10), 100)
    end
    setContext()
    f = {}
    print("Touch, hold and release viewer to grow a bloom.")
end

function draw()
    sprite(bImg, w2, h2)
    for i = 1, #f do
        f[i]:draw()
    end
end

function touched(touch)
    local tx, ty, state = touch.x, touch.y, touch.state
    if state == BEGAN and not id then
        touchTime = ElapsedTime
        id = touch.id
    elseif state == ENDED and touch.id == id then
        local size = math.min(50 + (ElapsedTime - touchTime) * 50, 100)
        table.insert(f, Flower(size, 21, tx, ty, math.random()))
        id = nil
    end
end

-- A function to generate a spectrum of pastel colours
-- h is [0, 1); strength is [0, 1]
function pastelH2RGB(h, strength)
    local s = strength / 2 + 0.25
    local r, g, b = 1, 1, 1
    local i = h * 3
    local x = (i % 1) * (1 - s)
    if i < 1 then r, g = 1 - x, s + x
    elseif i < 2 then r, b = s + x, 1 - x
    else g, b = 1 - x, s + x end
    return color(255 * r, 255 * g, 255 * b)
end

B2 = class()

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

function B2:point(t)
    local p = self.p
    local t1 = 1 - t
    return t1 * t1 * p[1] + 2 * t * t1 * p[2] + t * t * p[3]
end

Petal = class()

function Petal:init(size, a, b)
    local as, bs = a * size, b * size
    local n = 20
    self.mesh = mesh()
    local b1 = B2({vec2(0, 0), vec2(-as , bs), vec2(0, size)})
    local b2 = B2({vec2(0, 0), vec2(as, bs), vec2(0, size)})
    local v = {
        b1:point(0), b2:point(1/n), b1:point(1/n),
        b1:point(1), b1:point(1 - 1/n), b2:point(1 - 1/n)}
    for i = 2, n - 1 do
        local t1 = (i - 1) / n
        local t2 = i / n
        table.append(v,
            triangulate({b1:point(t1), b1:point(t2),
            b2:point(t2), b2:point(t1)}))
    end
    self.mesh.vertices = v
end

function Petal:setColor(col)
    self.color = col
end

function Petal:draw()
    pushMatrix()
    translate(self.x - 2, self.y - 2)
    rotate(self.angle)
    self.mesh:setColors(0, 0, 0, 16)
    self.mesh:draw()
    popMatrix()
    pushMatrix()
    translate(self.x, self.y)
    rotate(self.angle)
    self.mesh:setColors(self.color)
    self.mesh:draw()
    popMatrix()   
end

-- Helper function
function table.append(t1, t2)
    local n = #t1
    for i = 1, #t2 do
        t1[n + i] = t2[i]
    end
end

Flower = class()

function Flower:init(size, n, x, y, hue)
    self.x, self.y = x, y
    self.img = image(size * 2.1, size * 2.1)
    local petals = {}
    local a = math.random(360)
    local pa = 0.3 + 0.3 * math.random()
    local pb = 0.2 + 0.6 * math.random()
    local phi = (1 + math.sqrt(5)) / 2 -- Golden ratio
    local ga = 360 / phi ^ 2           -- Golden angle (radians)
    for i = 1, n do
        local p = Petal(math.random((size * 0.95) // 1, (size * 1.05) // 1), pa, pb)
        p.x, p.y = 0, 0
        p.angle = a
        local col = (hue + math.random() / 10 - 0.05) % 1
        p:setColor(pastelH2RGB(col, 0.5))
        table.insert(petals, p)
        a = a + ga
    end
    setContext(self.img)
    translate(size * 1.05, size * 1.05)
    for i = 1, #petals do
        petals[i]:draw()
    end
    a = 0
    ga = math.rad(ga)
    for i = 1, 20 do
        local r = math.sqrt(i) * size / 20
        local x, y = r * math.cos(a), r * math.sin(a)
        fill(245, 200, 0)
        ellipse(x - 1, y - 1, 7)
        fill(250, 250, 0)
        ellipse(x, y, 7)
        a = a + ga
    end
    setContext()
end

function Flower:draw()
    sprite(self.img, self.x, self.y)
end

Man, that is some beautiful work! Very pretty pastels and ‘shadowing’ effect! Fast, too!

Any thoughts on how to make this into trees? I would love to touch-drag and create different trees by dragging up for heat and seeing growth as it gets taller.

Seems possible, but I don’t yet understand what in the world this code is doing to make the mesh! :slight_smile:

This is a really beautiful demo. Great colour choices.

Thank you. @aciolino, the petal boundary is formed by two quadratic Bézier curves that are mirror images of each other, each curve being defined by its three control points and parameterized by a parameter that runs from 0 to 1. Where the two curves meet forms a triangle, at the top and bottom of the petal. The rest of the petal can be approximated by a series of quads with vertices on the boundary curves. I divide the petal into 20 pieces in this way.

So, what are these classes? How / what does the bezier calculation? I’m really interested in how this code works. :slight_smile:

Hello @aciolino. I hope the following helps.

Flowers (Codea class: Flower) are made up of petals (class: Petal) which are defined by two 2nd-order (that is, quadratic) Bezier curves (class: B2).

Taking a couple of steps back: What, in general, are Bezier curves? What do they look like?

An answer to the first question is provided by this Wikipedia article.

An answer to the second is provided by @Codeslinger’s Interactive Bezier Curve Animation. The default for the animation is four control points (a cubic Bezier curve); you need to delete a control point to see a 2nd-order curve.

Class B2 is simple. An array referred to by field p holds the three control points (as vec2 userdata values) and a function point(t) returns the point parameterised by variable t:


B2 = class()

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

function B2:point(t)
    local p = self.p
    local t1 = 1 - t
    return t1 * t1 * p[1] + 2 * t * t1 * p[2] + t * t * p[3]
end

Class Petal is also simple. A mesh userdata value referred to by field mesh holds the mesh for the petal. Local variables b1 and b2 refer to the two Bezier curves.

The two curves both start at 0, 0 and end at 0, size. The middle control points are placed symmetrically at -as, bs and +as, bs. If b is 0.5, then the petal will be symmetical. If a is large, then the petal will be fat.

Local variable v refers to an array that holds the vertices for the triangles of the mesh. It is initialised with the top and bottom triangles and then helper function table.append(t1, t2) is used to add more triangles.

The Codea API function triangulate is used to triangulate each quad before its triangles are added to v. The top vertices of each quad lie at point t2 on the curves and the bottom vertices of each quad lie at point t1 on the curves.


Petal = class()

function Petal:init(size, a, b)
    -- Scale up a and b for size
    local as, bs = a * size, b * size
    -- Set number of segments for petal
    local n = 20
    -- Establish the mesh
    self.mesh = mesh()
    -- Establish the two Bezier curves
    local b1 = B2({vec2(0, 0), vec2(-as , bs), vec2(0, size)})
    local b2 = B2({vec2(0, 0), vec2(as, bs), vec2(0, size)})
    -- Establish the top and bottom triangles
    local v = {
        b1:point(0), b2:point(1/n), b1:point(1/n),
        b1:point(1), b1:point(1 - 1/n), b2:point(1 - 1/n)}
    -- Work through the remaining quads
    for i = 2, n - 1 do
        local t1 = (i - 1) / n
        local t2 = i / n
        -- Add the triangulated quad
        table.append(v,
            triangulate({b1:point(t1), b1:point(t2),
            b2:point(t2), b2:point(t1)}))
    end
    -- Set the vertices of the mesh
    self.mesh.vertices = v
end

This mesh has a particular location (the origin) and orientation (vertical). The petal also records its location (fields x and y) and orientation (field angle) and the mesh is translated and rotated before it is drawn, accordingly. The petal also records its colour (field color).

Thank you for taking the time to share. It really helps out newbs such as myself.

Great explanation! Nice post, too.

The shadow effect mentioned by @aciolino above (see also the screen shot in the updated original post) dramatically improved the quality of the output. It built on an idea for improved contrast here by @KalimMalik.

Each petal is drawn twice, first in transparent black offset a couple of pixels below and to the left (the shadow) and then in the colour of the petal.


function Petal:draw()
    pushMatrix()
    translate(self.x - 2, self.y - 2) -- offset a couple of pixels
    rotate(self.angle)
    self.mesh:setColors(0, 0, 0, 16)  -- transparent black
    self.mesh:draw()
    popMatrix()
    pushMatrix()
    translate(self.x, self.y)       -- set location of petal origin on the viewer
    rotate(self.angle)              -- rotate the petal about its origin
    self.mesh:setColors(self.color) -- the colour of the petal
    self.mesh:draw()
    popMatrix()   
end

If anyone else is interested in running this, they will find that it stops due to errors when you touch the screen. This is apparently because it was written before Lua had integers, at least that’s my best guess, so math.random doesn’t know what to do with some decimals that get passed to it.

I tried to account for that by mildly adjusting the code in that section, and I think now it runs like it was meant to. I know this is a very old post but it is still kind of cool.

--
-- Bloom
--

supportedOrientations(LANDSCAPE_ANY)
function setup()
    w2, h2 = WIDTH / 2, HEIGHT / 2
    bImg = image(WIDTH, HEIGHT)
    setContext(bImg)
    background(100, 175, 60)
    rectMode(CENTER)
    for i = 1, 1000 do
        resetMatrix()
        rotate(math.random(-10, 10))
        fill(math.random(90, 110),
        math.random(150, 200),
        math.random(50, 70))
        rect(math.random(WIDTH), math.random(HEIGHT),
            math.random(5, 10), 100)
    end
    setContext()
    f = {}
    print("Touch, hold and release viewer to grow a bloom.")
end

function draw()
    sprite(bImg, w2, h2)
    for i = 1, #f do
        f[i]:draw()
    end
end

function touched(touch)
    local tx, ty, state = touch.x, touch.y, touch.state
    if state == BEGAN and not id then
        touchTime = ElapsedTime
        id = touch.id
    elseif state == ENDED and touch.id == id then
        local size = math.min(50 + (ElapsedTime - touchTime) * 50, 100)
        table.insert(f, Flower(size, 21, tx, ty, math.random()))
        id = nil
    end
end

-- A function to generate a spectrum of pastel colours
-- h is [0, 1); strength is [0, 1]
function pastelH2RGB(h, strength)
    local s = strength / 2 + 0.25
    local r, g, b = 1, 1, 1
    local i = h * 3
    local x = (i % 1) * (1 - s)
    if i < 1 then r, g = 1 - x, s + x
    elseif i < 2 then r, b = s + x, 1 - x
    else g, b = 1 - x, s + x end
    return color(255 * r, 255 * g, 255 * b)
end

B2 = class()

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

function B2:point(t)
    local p = self.p
    local t1 = 1 - t
    return t1 * t1 * p[1] + 2 * t * t1 * p[2] + t * t * p[3]
end

Petal = class()

function Petal:init(size, a, b)
    local as, bs = a * size, b * size
    local n = 20
    self.mesh = mesh()
    local b1 = B2({vec2(0, 0), vec2(-as , bs), vec2(0, size)})
    local b2 = B2({vec2(0, 0), vec2(as, bs), vec2(0, size)})
    local v = {
        b1:point(0), b2:point(1/n), b1:point(1/n),
        b1:point(1), b1:point(1 - 1/n), b2:point(1 - 1/n)}
    for i = 2, n - 1 do
        local t1 = (i - 1) / n
        local t2 = i / n
        table.append(v,
            triangulate({b1:point(t1), b1:point(t2),
            b2:point(t2), b2:point(t1)}))
    end
    self.mesh.vertices = v
end

function Petal:setColor(col)
    self.color = col
end

function Petal:draw()
    pushMatrix()
    translate(self.x - 2, self.y - 2)
    rotate(self.angle)
    self.mesh:setColors(0, 0, 0, 16)
    self.mesh:draw()
    popMatrix()
    pushMatrix()
    translate(self.x, self.y)
    rotate(self.angle)
    self.mesh:setColors(self.color)
    self.mesh:draw()
    popMatrix()   
end

-- Helper function
function table.append(t1, t2)
    local n = #t1
    for i = 1, #t2 do
        t1[n + i] = t2[i]
    end
end

Flower = class()

function Flower:init(size, n, x, y, hue)
    self.x, self.y = x, y
    self.img = image(size * 2.1, size * 2.1)
    local petals = {}
    local a = math.random(360)
    local pa = 0.3 + 0.3 * math.random()
    local pb = 0.2 + 0.6 * math.random()
    local phi = (1 + math.sqrt(5)) / 2 -- Golden ratio
    local ga = 360 / phi ^ 2           -- Golden angle (radians)
    for i = 1, n do
        local rParam1 = math.floor(size * 0.95 * 100000)
        local rParam2 = math.floor(size * 1.05 * 100000)
        local random = math.random(rParam1, rParam2) * 0.00001
        local p = Petal(random, pa, pb)
        p.x, p.y = 0, 0
        p.angle = a
        local col = (hue + math.random() / 10 - 0.05) % 1
        p:setColor(pastelH2RGB(col, 0.5))
        table.insert(petals, p)
        a = a + ga
    end
    setContext(self.img)
    translate(size * 1.05, size * 1.05)
    for i = 1, #petals do
        petals[i]:draw()
    end
    a = 0
    ga = math.rad(ga)
    for i = 1, 20 do
        local r = math.sqrt(i) * size / 20
        local x, y = r * math.cos(a), r * math.sin(a)
        fill(245, 200, 0)
        ellipse(x - 1, y - 1, 7)
        fill(250, 250, 0)
        ellipse(x, y, 7)
        a = a + ga
    end
    setContext()
end

function Flower:draw()
    sprite(self.img, self.x, self.y)
end

@UberGoober The line in the original code just needed to be change to what I show.

Original code

 local p = Petal(math.random(size * 0.95, size * 1.05), pa, pb)

Change code

 local p = Petal(math.random((size * 0.95)//1, (size * 1.05)//1), pa, pb)

What does // do?

It rounds the result of division. math.floor(20/11) == 20//11

https://www.lua.org/manual/5.3/manual.html#3.4.1

I’ve restored the example image (Dropbox no longer works) and applied @dave1707’s fix to update the original code for Lua 5.3.