Codea is a perfect match for this...

who wants to try this first?
https://youtu.be/makaJpLvbow
Dave?

@Jmv38 Nice little demo. I’ll watch the whole thing when I have more time.

@dave1707 the video is not from me. And i have not written the code, i just noticed it is very easy to implement with Codea.

@Jmv38 After watching more of the video, I realized that it was a video by someone else. I’m kind of working on this, but I’m not sure what some the variable are in the formula do/dt=a+BNtsign(Rt-Lt). a is the angle, Nt is the number of objects within a radius of an object. sign(Rt-Lt) is the sign (+1 or -1) of the number of items to the right or left of an object depending on its orientation. I’m assuming do/dt is the angle change that gets added to the angle a. I’m not sure what B is. So far I have a bunch of circles moving around the screen based on their angle and I’m counting the number of circles within a distance of each circle. Next step is to determine how many objects are on the right or left of the circles orientation.

great!
I thought of using sensor and collisions to have fast built-in acces to close objects.

B is a constant that characterize the physics of this world. At the end of the video they show which couples of A,B values generate interesting worlds.

@Jmv38 Here’s what I have so far. It doesn’t look like the video, but it does eventually start to clump. Apparently I’m not doing something right, but I not sure where and I’m tired of working on it for now. I don’t know if I need to do all the calculations before I change the positions of each point. Sort of like the game of life code. I’ll try that eventually.

PS. I modified the original code to make it faster, starts from random points, and color based on it distance to a nrighbor.

displayMode(FULLSCREEN)

function setup()
    A1,B1=1,1
    cnt=0
    rad=40  -- radius
    tab={}
    for z=1,400 do
        table.insert(tab,{x=math.random(80,WIDTH-80),y=math.random(80,HEIGHT-80),
        ang=math.random(360),lft=0,rgt=0,col=color(0,0,0)} )
    end
end

function draw()
    background(40, 40, 50)
    for a,b in pairs(tab) do
        fill(b.col)        
        b.x=b.x+math.cos(math.rad(b.ang))
        b.y=b.y+math.sin(math.rad(b.ang))
        n=b.lft+b.rgt
        s=sign(b.lft,b.rgt)
        b.ang=(b.ang+(A1+B1*n*s)%360)%360
        ellipse(b.x,b.y,6)
    end
    dist()
    cnt=cnt+1
    fill(255)
    text(cnt,WIDTH/2,HEIGHT-25)
end

-- determine if the sign is positive of negative 
function sign(l,r)
    if r>l then return -1 end
    if l>r then return 1 end
    return 0
end

-- determine if a point is within the radius of another point
function dist()
    for a,b in pairs(tab) do
        b.lft,b.rgt=0,0
        b.col=color(0,255,0)
    end
    for z=1,#tab do
        v=vec2(tab[z].x,tab[z].y)
        for y=z+1,#tab do
            d=v:dist(vec2(tab[y].x,tab[y].y))
            if d<rad then
                pos(tab[z],tab[y],z,y)
                pos(tab[y],tab[z],z,y)
                setColor(d,z,y)
            end
        end        
    end
end

function setColor(d,z,y)
    if d>rad*.6 then
        tab[z].col=color(0,0,255)
        tab[y].col=color(0,0,255)
    elseif d>rad*.4 then
        tab[z].col=color(255,255,0)
        tab[y].col=color(255,255,0)
    elseif d>rad*.2 then
        tab[z].col=color(255,0,255)
        tab[y].col=color(255,0,255)
    else
        tab[z].col=color(255,0,0)
        tab[y].col=color(255,0,0)
    end
end

-- determine how many points are on the right or left side of the point axis
function pos(orig,next)
    ang1=math.deg(math.atan(next.y-orig.y,next.x-orig.x))
    if ang1<0 then ang1=360+ang1 end    
    if orig.ang>=180 then
        if ang1<orig.ang and ang1>orig.ang-180 then
            orig.rgt=orig.rgt+1
        else
            orig.lft=orig.lft+1
        end
    else
        if ang1>=orig.ang and ang1<orig.ang+180 then
            orig.lft=orig.lft+1
        else
            orig.rgt=orig.rgt+1
        end
    end    
end

That’s pretty cool, you end up with little neighbourhoods around step 250

@Simeon I modified the above code. It’s a little faster, starts with random points, and color based on it distance to a neighbor.

@dave1707 i’ve restarted from your project, here is my version.


--# Grid
Grid = class()
-- this is just a tool to find neighbours faster (improves fps)

local rad, halfRad
local w,h,imax,jmax
local grid = {}
local floor = math.floor

function Grid:init(R)
    rad = R
    halfRad = R/2
    w,h = WIDTH, HEIGHT
    imax,jmax = self:get_ij(w,h)
    
    for i=1,imax do
        local col = {}
        grid[i] = col
        for j=1,jmax do
            col[j] = {}
        end
    end
end

function Grid:update(a)
    local i0,j0 = a.i,a.j
    local i,j = Grid:get_ij(a.x,a.y)
    if (i~=i0) or (j~=j0) then
        self:reset(i0-1,j0-1,a)
        self:reset(i0-1,j0  ,a)
        self:reset(i0-1,j0+1,a)
        self:reset(i0  ,j0-1,a)
        self:reset(i0  ,j0  ,a)
        self:reset(i0  ,j0+1,a)
        self:reset(i0+1,j0-1,a)
        self:reset(i0+1,j0  ,a)
        self:reset(i0+1,j0+1,a)
        
        self:set(i-1,j-1,a)
        self:set(i-1,j  ,a)
        self:set(i-1,j+1,a)
        self:set(i  ,j-1,a)
        self:set(i  ,j  ,a)
        self:set(i  ,j+1,a)
        self:set(i+1,j-1,a)
        self:set(i+1,j  ,a)
        self:set(i+1,j+1,a)
        
        a.i,a.j = i,j
    end
end
function Grid:neighbours(a)
    local i,j = a.i, a.j
    i = (i-1)%imax + 1
    j = (j-1)%jmax + 1
    return grid[i][j]
end
function Grid:set(i,j,a)
    i = (i-1)%imax + 1
    j = (j-1)%jmax + 1
    grid[i][j][a] = a
end
function Grid:reset(i,j,a)
    i = (i-1)%imax + 1
    j = (j-1)%jmax + 1
    grid[i][j][a] = nil
end

function Grid:get_ij(x,y)
    local i,j = floor( (x + halfRad)/rad )+1, floor( (y + halfRad)/rad )+1
    return i,j
end

--# Object
Object = class()
-- these values completely defines the objects
local A1, B1, R, S = 180, 17, 30, 3
local A1, B1, R, S = 180, 17, 30, 2
local A1, B1, R, S = 180, 17, 30, 3
-- the number of objects / size of the initial ground are important too
nbrObjects = 500
size = 380

local grid = false
function Object:init(w,h)
    if not grid then -- init the grid just once
        grid = Grid(R)
    end
    -- random position and angle within specified bounds
    self.x=math.random((WIDTH-w)/2,(WIDTH+w)/2)
    self.y=math.random((HEIGHT-h)/2,(HEIGHT+h)/2)
    self.i = 1
    self.j = 1
    self.ang=math.random(360)
    -- init speed
    self.speed = S
    self.dx = math.cos(math.rad(self.ang))*self.speed
    self.dy = math.sin(math.rad(self.ang))*self.speed
    -- init sensing radius
    self.dotRadius = 6
    self.colorRadius = 10
    self.sensingRadius = R
    
    self.col=color(0,255,0)
end
local mod = math.fmod
local min = math.min

function Object:update()
    -- move according current direction and speed
    local x,y
    x = self.x + self.dx
    y = self.y + self.dy
    -- make the world a torus
    x = mod(x +WIDTH , WIDTH )
    y = mod(y +HEIGHT, HEIGHT)
    -- now update positions
    self.x = x
    self.y = y
    grid:update(self)
    -- manage neighbour impact
    local A = vec2(self.x,self.y)
    local nbClose = 0
    local side = 0
    local d,n,lft,rgt = 0,0,0,0
    local B = vec2(0,0)
    for b,b in pairs( grid:neighbours(self) ) do
        if b~=self then
            B.x, B.y = b.x, b.y
            d = A:dist(B)
            if d<self.sensingRadius then
                if d < self.colorRadius then nbClose = nbClose + 1 end
                side = -self.dy*(b.x-self.x) + self.dx*(b.y-self.y)
                if side > 0 then
                    lft = lft + 1
                else
                    rgt = rgt + 1
                end
            end
        end
    end
    -- turn according to neighbours
    n = lft + rgt
    local sgn = 0
    if lft < rgt then sgn = -1 else sgn = 1 end
    self.ang=(self.ang+(A1+B1*n*sgn))%360
    self.dx = math.cos(math.rad(self.ang))*self.speed
    self.dy = math.sin(math.rad(self.ang))*self.speed
    -- define color according to nb of close neighbours
    local col
    if     nbClose < 1   then col = color(0,255,0)
    elseif nbClose < 2 then col = color(0,92,255)
    elseif nbClose < 4 then col = color(255,255,0)
    elseif nbClose < 8 then col = color(255,0,255)
    else col=color(255,0,0)
    end
    self.col=col
end

function Object:draw()
    fill(self.col)        
    ellipse(self.x, self.y, self.dotRadius)
end

--# Main
displayMode(FULLSCREEN)

function setup()
    cnt=0
    tab={}
    for z=1,nbrObjects do
        table.insert(tab, Object(size,size) )
    end
end

function draw()
    background(40, 40, 50)
    for a,b in pairs(tab) do
        b:draw()
        b:update()
    end
    cnt=cnt+1
    fill(255)
    text(cnt,WIDTH/2,HEIGHT-25)
end

@Jmv38 You’re code is working the way it should. I’ll have to look thru it to see what I’m doing wrong in my code. Apparently I don’t understand what’s really happening.

i dont think you are doing anything wrong.
Starting from your code was simpler for me, thanks!
I just introduced a faster way to locate the neighbours via the ‘grid’.

a more interactive version


--# Object
Object = class()
-- these values completely defines the objects
local A1, B1, R, S = 180, 17, 30, 3
local A1, B1, R, S = 180, 17, 30, 2
local A1, B1, R, S = 180, 17, 35, 4
-- the number of objects / size of the initial ground are important too
nbrObjects = 700
size = 500

local grid = false
function Object:init(w,h,x,y)
    if not grid then -- init the grid just once
        grid = Grid(R)
    end
    -- random position and angle within specified bounds
    if x == nil then
        self.x=math.random((WIDTH-w)/2,(WIDTH+w)/2)
        self.y=math.random((HEIGHT-h)/2,(HEIGHT+h)/2)
    else
        self.x = x
        self.y = y
    end
    
    self.i = 1
    self.j = 1
    self.ang=math.random(360)
    -- init speed
    self.speed = S
    self.dx = math.cos(math.rad(self.ang))*self.speed
    self.dy = math.sin(math.rad(self.ang))*self.speed
    -- init sensing radius
    self.dotRadius = 6
    self.colorRadius = 10
    self.sensingRadius = R
    
    self.col=color(0,255,0)
end
local mod = math.fmod
local min = math.min

function Object:update()
    -- move according current direction and speed
    local x,y
    x = self.x + self.dx
    y = self.y + self.dy
    -- make the world a torus
    x = mod(x +WIDTH , WIDTH )
    y = mod(y +HEIGHT, HEIGHT)
    -- now update positions
    self.x = x
    self.y = y
    grid:update(self)
    -- manage neighbour impact
    local A = vec2(self.x,self.y)
    local nbClose = 0
    local side = 0
    local d,n,lft,rgt = 0,0,0,0
    local B = vec2(0,0)
    for b,b in pairs( grid:neighbours(self) ) do
        if b~=self then
            B.x, B.y = b.x, b.y
            d = A:dist(B)
            if d<self.sensingRadius then
                if d < self.colorRadius then nbClose = nbClose + 1 end
                side = -self.dy*(b.x-self.x) + self.dx*(b.y-self.y)
                if side > 0 then
                    lft = lft + 1
                else
                    rgt = rgt + 1
                end
            end
        end
    end
    -- turn according to neighbours
    n = lft + rgt
    local sgn = 0
    if lft < rgt then sgn = -1 else sgn = 1 end
    self.ang=(self.ang+(A1+B1*n*sgn))%360
    self.dx = math.cos(math.rad(self.ang))*self.speed
    self.dy = math.sin(math.rad(self.ang))*self.speed
    -- define color according to nb of close neighbours
    local col
    if     nbClose < 1   then col = color(0,255,0)
    elseif nbClose < 8 then col = color(0,92,255)
    elseif nbClose < 12 then col = color(255,255,0)
    elseif nbClose < 16 then col = color(255,0,255)
    else col=color(255,0,0)
    end
    self.col=col
end

function Object:draw()
    fill(self.col)        
    ellipse(self.x, self.y, self.dotRadius)
end

--# Grid
Grid = class()
-- this is just a tool to find neighbours faster (improves fps)

local rad, halfRad
local w,h,imax,jmax
local grid = {}
local floor = math.floor

function Grid:init(R)
    rad = R
    halfRad = R/2
    w,h = WIDTH, HEIGHT
    imax,jmax = self:get_ij(w,h)
    
    for i=1,imax do
        local col = {}
        grid[i] = col
        for j=1,jmax do
            col[j] = {}
        end
    end
end

function Grid:update(a)
    local i0,j0 = a.i,a.j
    local i,j = Grid:get_ij(a.x,a.y)
    if (i~=i0) or (j~=j0) then
        for di =-1,1 do
            for dj =-1,1 do
                self:reset(i0+di,j0+dj,a)
            end
        end
        for di =-1,1 do
            for dj =-1,1 do
                self:set(i+di,j+dj,a)
            end
        end
        a.i,a.j = i,j
    end
end
function Grid:neighbours(a)
    local i,j = a.i, a.j
    i = (i-1)%imax + 1
    j = (j-1)%jmax + 1
    return grid[i][j]
end
function Grid:set(i,j,a)
    i = (i-1)%imax + 1
    j = (j-1)%jmax + 1
    grid[i][j][a] = a
end
function Grid:reset(i,j,a)
    i = (i-1)%imax + 1
    j = (j-1)%jmax + 1
    grid[i][j][a] = nil
end

function Grid:get_ij(x,y)
    local i,j = floor( (x + halfRad)/rad )+1, floor( (y + halfRad)/rad )+1
    return i,j
end

--# Main
displayMode(FULLSCREEN)
local t
function setup()
    cnt=0
    tab={}
    t=0
    for z=1,nbrObjects do
        -- table.insert(tab, Object(size,size) )
    end
    showText = "swipe the screen to add objects"
end
floor = math.floor
function draw()
    background(40, 40, 50)
    for a,b in pairs(tab) do
        b:draw()
        b:update()
    end
    if false and floor(ElapsedTime) > t then
        t = floor(ElapsedTime) +1
        table.insert(tab, Object(size,size) )
    end
    if showText then
        fill(255)
        text(showText,WIDTH/2,HEIGHT/2)
    else
        cnt=cnt+1
        fill(255)
        text(cnt,WIDTH/2,HEIGHT-25)
    end
    
end
function touched(touch)
    showText = nil
    table.insert(tab, Object(size,size,touch.x,touch.y) )
end

@Jmv38 This new version works great. I made a slight change to it for myself. I added width and height parameters so I could squeeze the size of the working area. I would fill the screen with green dots and then start reducing the width and height. It was like I was increasing the pressure inside the area with more and more dots turning to red. I also found out what I was doing wrong with my code. When I changed A1 to 180 and B1 to 17, it starting acting right. I also wasn’t limiting the size of the working area. So it was just increasing in size off screen and not forcing the dots to interact that much.

Ha! That’s so great! Well done both!

@dave1707 would you be willing to paste in your squeezy version here?

@UberGoober I tried searching for it, but I guess I never saved it or else I deleted it.

Aw rats, it seemed like a neat mod. The original is still darn cool though.

@UberGoober Here’s the above code with changes to force the dots into a smaller area. Move the parameter slider. Depending on how fast you move it, you’ll miss some and have to expand it and redo it. It’s not that great which is probably why I didn’t post or save it.

viewer.mode=STANDARD

local t

function setup()
    parameter.integer("wid",0,500)    
    cnt=0
    tab={}
    t=0
    for z=1,nbrObjects do
        -- table.insert(tab, Object(size,size) )
    end
    showText = "swipe the screen to add objects"
end

floor = math.floor

function draw()
    background(40, 40, 50)
    for a,b in pairs(tab) do
        b:draw()
        b:update()
    end
    if false and floor(ElapsedTime) > t then
        t = floor(ElapsedTime) +1
        table.insert(tab, Object(size,size) )
    end
    if showText then
        fill(255)
        text(showText,WIDTH/2,HEIGHT/2)
    else
        cnt=cnt+1
        fill(255)
        text(cnt,WIDTH/2,HEIGHT-25)
    end
    
    stroke(255)
    strokeWidth(2)
    line(wid,0,wid,HEIGHT)
    line(WIDTH-wid,0,WIDTH-wid,HEIGHT)
    line(0,wid,WIDTH,wid)
    line(0,HEIGHT-wid,WIDTH,HEIGHT-wid)
    text("FPS  "..1//DeltaTime,WIDTH/2,HEIGHT-50)
    text("Nbr  "..#tab,WIDTH/2,HEIGHT-75)
    
end

function touched(touch)
    showText = nil
    table.insert(tab, Object(size,size,touch.x,touch.y) )
end

--# Object
Object = class()
-- these values completely defines the objects
local A1, B1, R, S = 180, 17, 30, 3
local A1, B1, R, S = 180, 17, 30, 2
local A1, B1, R, S = 180, 17, 35, 4
-- the number of objects / size of the initial ground are important too
nbrObjects = 700
size = 200

local grid = false

function Object:init(w,h,x,y)
    if not grid then -- init the grid just once
        grid = Grid(R)
    end
    -- random position and angle within specified bounds
    if x == nil then
        self.x=math.random((WIDTH-w)/2,(WIDTH+w)/2)
        self.y=math.random((HEIGHT-h)/2,(HEIGHT+h)/2)
    else
        self.x = x
        self.y = y
    end
    
    self.i = 1
    self.j = 1
    self.ang=math.random(360)
    -- init speed
    self.speed = S
    self.dx = math.cos(math.rad(self.ang))*self.speed
    self.dy = math.sin(math.rad(self.ang))*self.speed
    -- init sensing radius
    self.dotRadius = 6
    self.colorRadius = 10
    self.sensingRadius = R
    
    self.col=color(0,255,0)
end

local mod = math.fmod
local min = math.min

function Object:update()
    -- move according current direction and speed
    local x,y
    
    if self.x+self.dx>WIDTH-wid or self.x+self.dx<1+wid then
        x=self.x-self.dx
    else
        x = self.x + self.dx
    end
    if self.y+self.dy>HEIGHT-wid or self.y+self.dy<1+wid then
        y=self.y-self.dy
    else
        y = self.y + self.dy
    end
    -- make the world a torus
    --x = mod(x +WIDTH , WIDTH )
    --y = mod(y +HEIGHT, HEIGHT)
    -- now update positions
    self.x = x
    self.y = y
    grid:update(self)
    -- manage neighbour impact
    local A = vec2(self.x,self.y)
    local nbClose = 0
    local side = 0
    local d,n,lft,rgt = 0,0,0,0
    local B = vec2(0,0)
    for b,b in pairs( grid:neighbours(self) ) do
        if b~=self then
            B.x, B.y = b.x, b.y
            d = A:dist(B)
            if d<self.sensingRadius then
                if d < self.colorRadius then nbClose = nbClose + 1 end
                side = -self.dy*(b.x-self.x) + self.dx*(b.y-self.y)
                if side > 0 then
                    lft = lft + 1
                else
                    rgt = rgt + 1
                end
            end
        end
    end
    -- turn according to neighbours
    n = lft + rgt
    local sgn = 0
    if lft < rgt then sgn = -1 else sgn = 1 end
    self.ang=(self.ang+(A1+B1*n*sgn))%360
    self.dx = math.cos(math.rad(self.ang))*self.speed
    self.dy = math.sin(math.rad(self.ang))*self.speed
    -- define color according to nb of close neighbours
    local col
    if     nbClose < 1   then col = color(0,255,0)
    elseif nbClose < 8 then col = color(0,92,255)
    elseif nbClose < 12 then col = color(255,255,0)
    elseif nbClose < 16 then col = color(255,0,255)
    else col=color(255,0,0)
    end
    self.col=col
end

function Object:draw()
    fill(self.col)
    ellipse(self.x, self.y, self.dotRadius)
end

--# Grid
Grid = class()
-- this is just a tool to find neighbours faster (improves fps)

local rad, halfRad
local w,h,imax,jmax
local grid = {}
local floor = math.floor

function Grid:init(R)
    rad = R
    halfRad = R/2
    w,h = WIDTH, HEIGHT
    imax,jmax = self:get_ij(w,h)
    
    for i=1,imax do
        local col = {}
        grid[i] = col
        for j=1,jmax do
            col[j] = {}
        end
    end
end

function Grid:update(a)
    local i0,j0 = a.i,a.j
    local i,j = Grid:get_ij(a.x,a.y)
    if (i~=i0) or (j~=j0) then
        for di =-1,1 do
            for dj =-1,1 do
                self:reset(i0+di,j0+dj,a)
            end
        end
        for di =-1,1 do
            for dj =-1,1 do
                self:set(i+di,j+dj,a)
            end
        end
        a.i,a.j = i,j
    end
end

function Grid:neighbours(a)
    local i,j = a.i, a.j
    i = (i-1)%imax + 1
    j = (j-1)%jmax + 1
    return grid[i][j]
end

function Grid:set(i,j,a)
    i = (i-1)%imax + 1
    j = (j-1)%jmax + 1
    grid[i][j][a] = a
end

function Grid:reset(i,j,a)
    i = (i-1)%imax + 1
    j = (j-1)%jmax + 1
    grid[i][j][a] = nil
end

function Grid:get_ij(x,y)
    local i,j = floor( (x + halfRad)/rad )+1, floor( (y + halfRad)/rad )+1
    return i,j
end

Actually I quite like that if you move fast it leaves some of the dots outside. It makes for a wider variety of scenarios, like adding stuff back in after taking it out, etc.