Fermat's Spiral: another experiment with large images, meshes and HSV

Another simple, and mildly hypnotic, experiment with movement and colours, along the same lines as Golden Angle. Warning: some of the colour combinations may be hard to look at, and that may affect some people more than others.


--
-- Fermat's Spiral
-- 

displayMode(FULLSCREEN)
supportedOrientations(LANDSCAPE_ANY)
function setup()
    spin = 360
    w2, h2 = WIDTH / 2, HEIGHT / 2
    local len = math.sqrt(w2 * w2 + h2 * h2)
    local scl = 50
    local s = 5    
    local pi2 = math.pi * 2
    local p = {}
    local dim = len / scl
    local ht = math.floor(dim * dim / math.pi)
    for ang = 360, s, -s do
        local theta = math.rad(ang)
        local r = -math.sqrt(theta) * scl
        theta = theta % pi2
        local x = r * math.cos(theta)
        local y = r * math.sin(theta)
        table.insert(p, vec2(x, y))
    end
    for ang = 0, 180, s do
        local theta = math.rad(ang)
        local r = math.sqrt(theta) * scl
        local x = r * math.cos(theta)
        local y = r * math.sin(theta)
        table.insert(p, vec2(x, y))
    end
    local v = triangulate(p)
    local pAO, pBO = p[1], p[#p]
    for a = s, 180 * (ht - 1), s do
        local thetaA = math.rad(360 + a)
        local rA = -math.sqrt(thetaA) * scl
        local xA = rA * math.cos(thetaA)
        local yA = rA * math.sin(thetaA)
        local thetaB = math.rad(180 + a)
        local rB = math.sqrt(thetaB) * scl
        local xB = rB * math.cos(thetaB)
        local yB = rB * math.sin(thetaB)
        local pA, pB = vec2(xA, yA), vec2(xB, yB)
        table.append(v, triangulate({pB, pA, pAO, pBO}))
        pAO, pBO = pA, pB
    end
    m = mesh()
    m.vertices = v
    m:setColors(255, 255, 255)
    img, ox, oy = {}, {}, {}
    for j = 0, 3 do
        img[j] = image(len, len)
        setContext(img[j])
        ox[j] = -(j % 2) * len
        oy[j] = -(1 - math.floor(j / 2)) * len
        resetMatrix()
        translate(-ox[j], -oy[j])
        m:draw()
        setContext()
    end
    spriteMode(CORNER)
end

function draw()
    local c = ElapsedTime / 60
    background(H2RGB(c % 1))
    -- The 0.5 in the line below affects the colour combinations.
    -- A value of 0.4 may reduce the frequency of combinations that jar.
    tint(H2RGB((c + 0.5) % 1))
    translate(w2, h2)
    rotate(-ElapsedTime * spin % 360)
    for i = 0, 3 do sprite(img[i], ox[i], oy[i]) end
end

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

-- Hue ([0, 1)) to RGB; (S = 1, V = 1)
function H2RGB(h)
    local r, g, b = 0, 0, 0
    local i = h * 6
    local x = (1 - math.abs(i % 2 - 1))
    if i < 1 then r, g = 1, x
    elseif i < 2 then r, g = x, 1
    elseif i < 3 then g, b = 1, x
    elseif i < 4 then g, b = x, 1
    elseif i < 5 then r, b = x, 1
    elseif i < 6 then r, b = 1, x end
    return color(255 * r, 255 * g, 255 * b)
end

That’s a really neat effect.

Fermat's Spiral

It looks like you are choosing inverse hues for the two colours? That can sometimes produce some fairly hard-to-look-at colour combinations.

.@mpilgrem, I have decided to stop trying to understand your code, and just look at the pretty colours. You have some of the best (and most confusing) code I have ever seen.

Hello @Simeon. Thank you for adding the screen shot. I’ve lost the ability to do that with Codea 1.4.6(15) on iOS 6.0. (I’ve added the issue to the tracker.) I’ll add a warning about the colour combinations to my original comment; they may affect some people more than others.

Hello @Jordan. I read that you are starting to learn trigonometry. Good luck with that. Some trigonometry underpins this code, as well as polar coordinates and a line known as Fermat’s spiral that is usually expressed as a relationship between its polar coordinates.

At its simplest, this code draws a pattern on a large image. The pattern is drawn in opaque white on a transparent black background. By using tint and sprite onto a coloured background, the two colours can be varied. The image is then rotated about the centre of the viewer.

A complication is that Codea cannot handle large images, so the image is in fact made up of an array of four separate images, arranged like four panes in a square window.

The pattern is drawn and coloured in using a mesh. Unfortunately, the built-in triangulate function cannot handle polygons with more than 256 points. In any event, the time it takes depends on the square of the number of points. So the code builds up the mesh in steps, an initial step for the complex middle and then one quad at a time.

I feel so proud for noticing that triangulate one myself (check the issue tracker ;)). Thanks @mpilgrem, maybe now it will make sense.

.@mpilgrem you should still be able to take screenshots using the system method: press the home button on your iPad while simultaneously pressing the lock/sleep button. This will save a screenshot into your camera roll.