who wants to try this first?
https://youtu.be/makaJpLvbow
Dave?
@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!
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.