Line to nearest neighbor

Here’s another useless program. Each circle draws a fat line to its nearest neighbor. Tap the screen to add 5 more circles. I had nothing better to do.

displayMode(FULLSCREEN)

function setup()
    tab={}
    FPS=60
    create()
end

function create()
    for z=1,5 do
        local sp=2
        local t={}
        t.x=math.random(WIDTH)
        t.y=math.random(HEIGHT)
        t.xv=math.random(-sp,sp)
        t.yv=math.random(-sp,sp)
        t.dist=0    -- nearest neighbor distance
        t.x1=0      -- nearest neighbor x,y
        t.y1=0
        t.col=color(math.random(255),math.random(255),math.random(255))
        table.insert(tab,t)
    end
end

function draw()
    background(80, 80, 80, 54)
    fill(255)
    text("Tap screen for 5 more circles",WIDTH/2,HEIGHT-20)
    text("Circles "..#tab,WIDTH/2,HEIGHT-40)
    text("FPS "..FPS//1,WIDTH/2,HEIGHT-60)
    -- find nearest neighbor
    for a,b in pairs(tab) do
        local v1=vec2(b.x,b.y)
        b.dist=9999 
        for c,d in pairs(tab) do
            local v2=vec2(d.x,d.y)
            local dst=v1:dist(v2)
            if dst>0 and dst<b.dist then
                b.dist=dst
                b.x1=d.x
                b.y1=d.y
            end            
        end
    end
    -- draw lines
    for a,b in pairs(tab) do
        stroke(b.col)
        strokeWidth(40)
        line(b.x,b.y,b.x1,b.y1)
    end
    -- draw circles over lines
    for a,b in pairs(tab) do
        fill(0)
        noStroke()
        ellipse(b.x,b.y,10)
        stroke(0)
        strokeWidth(5)
        line(b.x,b.y,b.x-(b.x-b.x1)/4,b.y-(b.y-b.y1)/4)
    end
    -- change position and reverse direction at edges
    for a,b in pairs(tab) do
        b.x=b.x+b.xv
        b.y=b.y+b.yv
        if b.x<0 or b.x>WIDTH then
            b.xv=-b.xv
        end
        if b.y<0 or b.y>HEIGHT then
            b.yv=-b.yv
        end        
    end
    FPS = FPS * 0.9 + 0.1 / DeltaTime
end

function touched(t)
    if t.state==BEGAN then
        create()
    end
end

@LoopSpace Interesting changes. Kind of reminds me of dodgem cars.

I thought that the shorter lines behaved like springs, so I put in forces and acceleration.

-- NeighbourLines

displayMode(FULLSCREEN)

function setup()
    tab={}
    FPS=60
    create()
end

function create()
    local t,sp
    sp=2
    for z=1,5 do
        t={}
        t.p=vec2(math.random(WIDTH),math.random(HEIGHT))
        t.v=vec2(0,0)
        t.a=vec2(0,0)
        t.col=randomColour()
        table.insert(tab,t)
    end
end

function randomColour()
    local h = math.random()*6
    local x = 255*(1 - math.abs(h%2 - 1))
    local r,g,b
    if h < 1 then
        r,g,b = 255,x,0
    elseif h < 2 then
        r,g,b = x,255,0
    elseif h < 3 then
        r,g,b = 0,255,x
    elseif h < 4 then
        r,g,b = 0,x,255
    elseif h < 5 then
        r,g,b = x,0,255
    else
        r,g,b = 255,0,x
    end
    return color(r,g,b)
end

function draw()
    background(80, 80, 80, 54)
    fill(255)
    text("Tap screen for 5 more circles",WIDTH/2,HEIGHT-20)
    text("Circles "..#tab,WIDTH/2,HEIGHT-40)
    text("FPS "..FPS//1,WIDTH/2,HEIGHT-60)

    local dists = {}
    local d
    -- work out all the distances in one fell swoop
    for k,v in ipairs(tab) do
        dists[k] = {}
        for l=1,k-1 do
            d = v.p:dist(tab[l].p)
            dists[k][l] = {d,tab[l]}
            dists[l][k] = {d,tab[k]}
        end
        -- ensure that the closest point is not itself
        dists[k][k] = {math.max(WIDTH,HEIGHT),v}
    end
    -- find closest neighbour by sorting the rows
    for k,v in ipairs(dists) do
        table.sort(v,function(a,b) return a[1] < b[1] end)
    end
    -- draw lines
    strokeWidth(40)
    local u
    for k,v in ipairs(tab) do
        stroke(v.col)
        u = dists[k][1][2].p/4 + 3*v.p/4
        line(v.p.x,v.p.y,u.x,u.y)
    end
    -- draw circles over lines
    fill(0)
    noStroke()
    for k,v in ipairs(tab) do
        ellipse(v.p.x,v.p.y,10)
    end
    -- change position and reverse direction at edges
    for a,b in pairs(tab) do
        b.p = b.p + DeltaTime * b.v
        b.v = b.v + DeltaTime * b.a
        b.a = (dists[a][1][2].p - b.p)
        b.a = b.a*(b.a:len() - 150)/3000
        if b.p.x<0 then
            b.v.x = math.abs(b.v.x)
            b.p.x = math.abs(b.p.x)
        end
        if b.p.x>WIDTH then
            b.v.x = -math.abs(b.v.x)
            b.p.x = 2*WIDTH - b.p.x
        end
        if b.p.y<0 then
            b.v.y = math.abs(b.v.y)
            b.p.y = math.abs(b.p.y)
        end
        if b.p.y>HEIGHT then
            b.v.y = -math.abs(b.v.y)
            b.p.y = 2*HEIGHT - b.p.y
        end        
    end
    FPS = FPS * 0.9 + 0.1 / DeltaTime
end

function touched(t)
    if t.state==BEGAN then
        create()
    end
end

That’s quite fun.

I took the liberty of vecing it.

-- NeighbourLines

displayMode(FULLSCREEN)

function setup()
    tab={}
    FPS=60
    create()
end

function create()
    local t,sp
    sp=2
    for z=1,5 do
        t={}
        t.p=vec2(math.random(WIDTH),math.random(HEIGHT))
        t.v=vec2(math.random(-sp,sp),math.random(-sp,sp))
        t.col=randomColour()
        table.insert(tab,t)
    end
end

function randomColour()
    local h = math.random()*6
    local x = 255*(1 - math.abs(h%2 - 1))
    local r,g,b
    if h < 1 then
        r,g,b = 255,x,0
    elseif h < 2 then
        r,g,b = x,255,0
    elseif h < 3 then
        r,g,b = 0,255,x
    elseif h < 4 then
        r,g,b = 0,x,255
    elseif h < 5 then
        r,g,b = x,0,255
    else
        r,g,b = 255,0,x
    end
    return color(r,g,b)
end

function draw()
    background(80, 80, 80, 54)
    fill(255)
    text("Tap screen for 5 more circles",WIDTH/2,HEIGHT-20)
    text("Circles "..#tab,WIDTH/2,HEIGHT-40)
    text("FPS "..FPS//1,WIDTH/2,HEIGHT-60)

    local dists = {}
    local d
    -- work out all the distances in one fell swoop
    for k,v in ipairs(tab) do
        dists[k] = {}
        for l=1,k-1 do
            d = v.p:dist(tab[l].p)
            dists[k][l] = {d,tab[l]}
            dists[l][k] = {d,tab[k]}
        end
        -- ensure that the closest point is not itself
        dists[k][k] = {math.max(WIDTH,HEIGHT),v}
    end
    -- find closest neighbour by sorting the rows
    for k,v in ipairs(dists) do
        table.sort(v,function(a,b) return a[1] < b[1] end)
    end
    -- draw lines
    strokeWidth(40)
    local u
    for k,v in ipairs(tab) do
        stroke(v.col)
        u = dists[k][1][2]
        line(v.p.x,v.p.y,u.p.x,u.p.y)
    end
    -- draw circles over lines
    fill(0)
    noStroke()
    for k,v in ipairs(tab) do
        ellipse(v.p.x,v.p.y,10)
    end
    -- change position and reverse direction at edges
    for a,b in pairs(tab) do
        b.p = b.p + b.v
        if b.p.x<0 or b.p.x>WIDTH then
            b.v.x=-b.v.x
        end
        if b.p.y<0 or b.p.y>HEIGHT then
            b.v.y=-b.v.y
        end        
    end
    FPS = FPS * 0.9 + 0.1 / DeltaTime
end

function touched(t)
    if t.state==BEGAN then
        create()
    end
end

Made changes to my code at the top so the circles point to its nearest neighbor. That helps to see who the closest neighbor is. @LoopSpace I’m still trying to figure out why the vectors work in the calcP3 function of the other program. I printed the different values in the calcP3 function, but why they work I haven’t figured out yet.

To make the same change in my code:

u = dists[k][1][2].p/4 + 3* v.p/4
line(v.p.x,v.p.y,u.x,u.y)

I’ll write up an explanation of the code in the calcP3 function in a bit.