6 useful snippets

I was inspired by the blog post 6 Useful Snippets – Naming Things and implemented in it Codea.


--# Main
-- balls - inspired by https://blog.bruce-hill.com/6-useful-snippets
function setup()
    viewer.mode = FULLSCREEN_NO_BUTTONS
    W, H = WIDTH, HEIGHT
    MIN_RADIUS = math.min(W, H)/40
    MAX_RADIUS = math.min(W, H)/12
    collision_scale = MIN_RADIUS
    balls = {}
    dragging = false
    GRAVITY = vec2(0, -2000)
    K = 0.5
    
    for i = 0, 40 do
        local ball = {
            id=i,
            pos=vec2(math.random()*W, math.random()*H),
            radius=mix(MIN_RADIUS, MAX_RADIUS, math.random()),
            color=hsl(360*((i*GOLDEN_RATIO) % 1), .5, .7, 1.)
        }
        ball.prevpos = ball.pos
        ball.mass = ball.radius*ball.radius
        table.insert(balls, ball)
    end
    
    -- rotating ball
    stirrer = balls[1]
    stirrer.color = color(255, 0, 225)
    stir_angle = 0
    ellipseMode(CENTER)
end

function draw()
    background(40, 40, 50)
    strokeWidth(1)
    
    dt = DeltaTime
    
    for i, b in ipairs(balls) do
        local nextpos = ((b.pos * 2) - b.prevpos) + GRAVITY * dt*dt
        b.prevpos = b.pos
        b.pos = nextpos
    end
    
    stir_angle = stir_angle + dt*6.28/4
    -- solve contraints
    for i = 1, 5 do
        if dragging then
            dragging.pos = mix(dragging.pos, CurrentTouch.pos, .35)
        end
        
        if stirrer then
            stirrer.pos = mix(stirrer.pos, vec2(W/2+H/3*math.cos(stir_angle), H/2+H/3*math.sin(stir_angle)), .35)
        end
        
        local collisions = collisions_between(balls)
        for j, cs in ipairs(collisions) do
            local a = cs[1];
            local b = cs[2];
            if a.pos:dist(b.pos) < a.radius + b.radius then
                local a2b = (b.pos -a.pos):normalize()
                local needed_dist = (a.radius + b.radius) - a.pos:dist(b.pos)
                a.pos = a.pos - (a2b * (K*needed_dist*(b.mass/(a.mass+b.mass))))
                b.pos = b.pos + (a2b * (K*needed_dist*(a.mass/(a.mass+b.mass))))
            end
        end
        
        for i, b in ipairs(balls) do
            local clamped = vec2(clamp(b.pos.x, b.radius, W-b.radius), 
            clamp(b.pos.y, b.radius, H-b.radius))
            if clamped.x ~= b.pos.x or clamped.y ~= b.pos.y then
                b.pos = mix(b.pos, clamped, K)
                -- Damping:
                b.prevpos = mix(b.prevpos, b.pos, .001)
            end
        end
    end
    
    for i, ball in ipairs(balls) do
        fill(ball.color)
        if ball == dragging then
            strokeWidth(8)
        else
            strokeWidth(1)
        end
        ellipse(ball.pos.x, ball.pos.y, ball.radius*2, ball.radius*2)
    end
end

function touched(touch)
    if touch.state == BEGAN then
        dragging = ball_at(touch.pos)
    elseif touch.state == ENDED then
        dragging = false        
    end
end

--# Utils
GOLDEN_RATIO = (math.sqrt(5) - 1)/2

function hsl(h, s, v, a)
    local r, g, b
    
    local i = math.floor(h * 6);
    local f = h * 6 - i;
    local p = v * (1 - s);
    local q = v * (1 - f * s);
    local t = v * (1 - (1 - f) * s);
    
    i = i % 6
    
    if i == 0 then r, g, b = v, t, p
    elseif i == 1 then r, g, b = q, v, p
    elseif i == 2 then r, g, b = p, v, t
    elseif i == 3 then r, g, b = p, q, v
    elseif i == 4 then r, g, b = t, p, v
    elseif i == 5 then r, g, b = v, p, q
    end
    
    return color(r * 255, g * 255, b * 255, a * 255)
end

function mix(a, b, amount) 
    return (1-amount)*a + amount*b
end

function clamp(x, min, max)
    if x < min then
        return min
    end
    if x > max then 
        return max
    end
    return x
end

function collisions_between(things) 
    local S = collision_scale;
    local buckets = {}
    local collisions = {}
    for i, t in ipairs(things) do
        local collided = {}
        for x = math.floor((t.pos.x-t.radius)/S), math.floor((t.pos.x+t.radius)/S) do
            for y = math.floor((t.pos.y-t.radius)/S), math.floor((t.pos.y+t.radius)/S) do
                local key = x .. "," .. y;
                if buckets[key] == nil then
                    buckets[key] = {}
                end
                for j, other in ipairs(buckets[key]) do
                    table.insert(collisions, {other, t})
                    collided[other.id] = true
                end
                table.insert(buckets[key], t)
            end
        end
    end
    return collisions
end

function ball_at(pos) 
    for j, b in ipairs(balls) do
        if b.pos:dist(pos) <= b.radius then
            return b 
        end
    end
end

@tnlogy - thanks for the post - some intriguing ideas and implementations there.

Spot on little demo too.

Thanks again

1 Like