Canvas flocking cellular automaton

Share your advanced PureBasic knowledge/code with the community.
User avatar
fiver
User
User
Posts: 36
Joined: Wed May 05, 2004 8:21 pm
Location: An outer spiral arm of the Milky Way

Canvas flocking cellular automaton

Post by fiver »

This is code for a flocking cellular automaton ported to PB from html5 canvas/javascript code posted on rectangleworld:
http://rectangleworld.com/blog/archives/587
Lot's of other cool stuff there :wink:
It is based on bird flocking behaviour and cellular automata and particularly boid's algorithm which models flocks of birds using the behavioural principles separation, alignment and cohesion.

It looks fairly blocky in the screen-shot below but there are lots of tweaks you can make, it looks really good with a 1px x 1px grid but don't go too big on the window size or your CPU will melt :twisted: Interestingly, a fairly naive analysis (Task manger :D ) indicates that PB's canvas runs about 3 times faster than the one in firefox size for size.

Image

It should work on Mac but only tested on xubuntu 12.10 and Windows Vista.

Code: Select all

;-- separation: if a cell’s color is too close to that of one of its neighbors, move away from that color.
;-- alignment: adjust the rate of change of a cell’s color towards the average rate of change of its neighbors.
;-- cohesion: move the color of a cell towards the average color of its neighbors.

;-- 1000mS / 25 = 40mS
#mSRedraw = 40
#minDist = 8
#minDistSquare = #minDist * #minDist
#sepNormMag = 4
#Easing = 0.67

;-- Smaller cells and a bigger canvas will eat your CPU!
CanvWidth.w = 600
CanvHeight.w = 400
Cellwidth.w= CanvWidth / 20
CellHeight.w = CanvHeight / 30

ArrayX = CanvWidth/Cellwidth
ArrayY = CanvHeight/CellHeight

Structure CELL
  X.w
  Y.w
  ;-- Colours
  R.f
  G.f
  B.f
  bufferR.f
  bufferG.f
  bufferB.f
  ;-- Velocities
  RVel.f
  GVel.f
  BVel.f
  bufferRVel.f
  bufferGVel.f
  bufferBVel.f
  List *neighbours()
  neighbourCnt.w
EndStructure

Dim Cells.CELL(ArrayX, ArrayY)


Procedure InitialiseCells(Canvas)
  Shared Cells(), ArrayX, ArrayY, CellWidth, CellHeight

  If StartDrawing(CanvasOutput(Canvas))

    For i = 0 To ArrayX
      For j = 0 To ArrayY
        Cells(i,j)\X = i * Cellwidth
        Cells(i,j)\Y = j * CellHeight

        Cells(i,j)\bufferRVel = 0
        Cells(i,j)\bufferGVel = 0
        Cells(i,j)\bufferBVel = 0

        Cells(i,j)\RVel = 0
        Cells(i,j)\GVel = 0
        Cells(i,j)\BVel = 0

        Cells(i,j)\R = Random(255)
        Cells(i,j)\G = Random(255)
        Cells(i,j)\B = Random(255)

        Cells(i,j)\bufferR = Cells(i,j)\R
        Cells(i,j)\bufferG = Cells(i,j)\G
        Cells(i,j)\bufferB = Cells(i,j)\B

        Box(Cells(i,j)\X, Cells(i,j)\Y, Cellwidth, CellHeight, RGB(Cells(i,j)\R, Cells(i,j)\G, Cells(i,j)\B))
      Next
    Next
    StopDrawing()
  EndIf
EndProcedure


Procedure InitialiseNeighbours()
  Shared Cells(), ArrayX, ArrayY

  For i = 0 To ArrayX
    For j = 0 To ArrayY

      If i > 0
        AddElement(Cells(i, j)\neighbours())
        Cells(i, j)\neighbours() = @Cells(i - 1, j)
        AddElement(Cells(i - 1, j)\neighbours())
        Cells(i - 1, j)\neighbours() = @Cells(i, j)
      EndIf

      If j > 0
        AddElement(Cells(i, j)\neighbours())
        Cells(i, j)\neighbours() = @Cells(i, j - 1)
        AddElement(Cells(i, j - 1)\neighbours())
        Cells(i, j - 1)\neighbours() = @Cells(i, j)
      EndIf
      neighbourCnt = ListSize(Cells(i,j)\neighbours())
    Next
  Next
  ;-- Go through again and get the number of neighbours
  For i = 0 To ArrayX
    For j = 0 To ArrayY
      Cells(i,j)\neighbourCnt = ListSize(Cells(i,j)\neighbours())
    Next
  Next
EndProcedure


Procedure UpdateCells(Canvas)
  Shared Cells(), ArrayX, ArrayY, CellWidth, CellHeight
  If StartDrawing(CanvasOutput(Canvas))

    For i = 0 To ArrayX
      For j = 0 To ArrayY
        rAve = 0
        gAve = 0
        bAve = 0
        rVelAve = 0
        gVelAve = 0
        bVelAve = 0
        rSep = 0
        gSep = 0
        bSep = 0

        ForEach Cells(i,j)\neighbours()
          *neighbour.CELL = Cells(i, j)\neighbours()

          rAve + *neighbour\R
          gAve + *neighbour\G
          bAve + *neighbour\B

          rVelAve + *neighbour\RVel
          gVelAve + *neighbour\GVel
          bVelAve + *neighbour\BVel

          dr = Cells(i,j)\R - *neighbour\R
          dg = Cells(i,j)\G - *neighbour\G
          db = Cells(i,j)\B - *neighbour\B

          If dr*dr + dg*dg + db*db < #minDistSquare
            rSep + dr
            gSep + dg
            bSep + db
          EndIf

        Next

        f.f = 1 / Cells(i,j)\neighbourCnt
        rAve = rAve * f
        gAve = gAve * f
        bAve = bAve * f
        rVelAve = rVelAve * f
        gVelAve = gVelAve * f
        bVelAve = bVelAve * f

        ;-- normalize separation vector
        If (rSep <> 0) Or (gSep <> 0) Or (bSep <> 0)
          sepMagRecip = #sepNormMag / Sqr(rSep * rSep + gSep * gSep + bSep * bSep)
          rSep = rSep * sepMagRecip
          gSep = gSep * sepMagRecip
          bSep = bSep * sepMagRecip
        EndIf

        ;-- Update velocity by combining separation, alignment and cohesion effects.
        ;-- Change velocity only by Easing ratio.
        Cells(i,j)\bufferRVel + #Easing * (rSep + rVelAve + rAve - Cells(i,j)\R - Cells(i,j)\bufferRVel)
        Cells(i,j)\bufferGVel + #Easing * (gSep + gVelAve + gAve - Cells(i,j)\G - Cells(i,j)\bufferGVel)
        Cells(i,j)\bufferBVel + #Easing * (bSep + bVelAve + bAve - Cells(i,j)\B - Cells(i,j)\bufferBVel)

        ;-- update colors according to color velocities
        Cells(i,j)\bufferR + Cells(i,j)\bufferRVel
        Cells(i,j)\bufferG + Cells(i,j)\bufferGVel
        Cells(i,j)\bufferB + Cells(i,j)\bufferBVel

        ;-- bounce colors off of color cube boundaries
        If Cells(i,j)\bufferR < 0
          Cells(i,j)\bufferR = 0
          Cells(i,j)\bufferRVel = Cells(i,j)\bufferRVel * -1
        ElseIf Cells(i,j)\bufferR > 255
          Cells(i,j)\bufferR = 255
          Cells(i,j)\bufferRVel = Cells(i,j)\bufferRVel * -1
        EndIf

        If Cells(i,j)\bufferG < 0
          Cells(i,j)\bufferG = 0
          Cells(i,j)\bufferGVel = Cells(i,j)\bufferGVel * -1
        ElseIf Cells(i,j)\bufferG > 255
          Cells(i,j)\bufferG = 255
          Cells(i,j)\bufferGVel = Cells(i,j)\bufferGVel * -1
        EndIf

        If Cells(i,j)\bufferB < 0
          Cells(i,j)\bufferB = 0
          Cells(i,j)\bufferBVel = Cells(i,j)\bufferBVel * -1
        ElseIf Cells(i,j)\bufferB > 255
          Cells(i,j)\bufferB = 255
          Cells(i,j)\bufferBVel = Cells(i,j)\bufferBVel * -1
        EndIf

      Next
    Next
    ;-- now loop through again, copy buffer values and draw
    For i = 0 To ArrayX
      For j = 0 To ArrayY
        Cells(i,j)\R = Cells(i,j)\bufferR
        Cells(i,j)\G = Cells(i,j)\bufferG
        Cells(i,j)\B = Cells(i,j)\bufferB

        Cells(i,j)\RVel = Cells(i,j)\bufferRVel
        Cells(i,j)\GVel = Cells(i,j)\bufferGVel
        Cells(i,j)\BVel = Cells(i,j)\bufferBVel

        Box(Cells(i,j)\X, Cells(i,j)\Y, Cellwidth, CellHeight, RGB(Cells(i,j)\R, Cells(i,j)\G, Cells(i,j)\B))
      Next
    Next

    StopDrawing()
  EndIf
EndProcedure

Procedure MaxWindow()
  Shared Cells(), Canvas, CanvWidth, CanvHeight, ArrayX, ArrayY, CellWidth, CellHeight
  ExamineDesktops()
  CanvWidth = DesktopWidth(0)
  CanvHeight = DesktopHeight(0)
  Cellwidth= CanvWidth / 20
  CellHeight = CanvHeight / 30
  ArrayX = CanvWidth/Cellwidth
  ArrayY = CanvHeight/CellHeight
  Dim Cells.CELL(ArrayX, ArrayY)
  InitialiseCells(Canvas)
  InitialiseNeighbours()
  ResizeGadget(Canvas, #PB_Ignore, #PB_Ignore, CanvWidth, CanvHeight)
  CompilerIf #PB_Compiler_OS = #PB_OS_Linux
  gtk_window_fullscreen_(WindowID(0))
  CompilerEndIf
EndProcedure

Procedure MinWindow()
  Shared Cells(), Canvas, CanvWidth, CanvHeight, ArrayX, ArrayY, CellWidth, CellHeight
  CanvWidth = WindowWidth(0)
  Debug CanvWidth
  CanvHeight = WindowHeight(0)
  Cellwidth= CanvWidth / 20
  CellHeight = CanvHeight / 30
  ArrayX = CanvWidth/Cellwidth
  ArrayY = CanvHeight/CellHeight
  Dim Cells.CELL(ArrayX, ArrayY)
  InitialiseCells(Canvas)
  InitialiseNeighbours()
  ResizeGadget(Canvas, #PB_Ignore, #PB_Ignore, CanvWidth, CanvHeight)
EndProcedure

If OpenWindow(0, 0, 0, CanvWidth, CanvHeight, "Flocking Cellular Automaton", #PB_Window_BorderLess | #PB_Window_MaximizeGadget | #PB_Window_ScreenCentered)
  AddWindowTimer(0, 666, #mSRedraw)
  AddKeyboardShortcut(0, #PB_Shortcut_Escape, 1)
  AddKeyboardShortcut(0, #PB_Shortcut_Tab, 2)
  Canvas = CanvasGadget(#PB_Any, 0, 0, CanvWidth, CanvHeight)
  GadgetToolTip(Canvas, "Flocking Cellular Automaton" + #CRLF$ + "Left click to reset" + #CRLF$ + "Tab toggles fullscreen" + #CRLF$ + "Esc to exit")
  InitialiseCells(Canvas)
  InitialiseNeighbours()
  Repeat
    Event = WaitWindowEvent(5)
    Type = EventType()
    If Event = #PB_Event_MaximizeWindow
      MaxWindow()
    ElseIf Event = #PB_Event_RestoreWindow
      MinWindow()
    ElseIf Event = #PB_Event_SizeWindow
      MinWindow()
    ElseIf Event = #PB_Event_Timer And EventTimer() = 666
      UpdateCells(Canvas)
    ElseIf Event = #PB_Event_Gadget
      If Type = #PB_EventType_LeftClick
        InitialiseCells(Canvas)
        UpdateCells(Canvas)
      EndIf
    ElseIf Event = #PB_Event_Menu
      Menu = EventMenu()
      If Menu = 1
        Break
      ElseIf Menu = 2
        State ! 1
        If State = 1
          SetWindowState(0, #PB_Window_Maximize)
        Else
          CompilerIf #PB_Compiler_OS = #PB_OS_Linux
          gtk_window_unfullscreen_(WindowID(0))
          CompilerEndIf
          SetWindowState(0, #PB_Window_Normal)
        EndIf
      EndIf
    EndIf

  Until Event = #PB_Event_CloseWindow
EndIf

User avatar
luis
Addict
Addict
Posts: 3893
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: Canvas flocking cellular automaton

Post by luis »

Nice :P

I've put

Cellwidth.w= 3
CellHeight.w = 3

and looks similar to a colorful plasma effect but smoothly evolving forever

Thanks!
"Have you tried turning it off and on again ?"
A little PureBasic review
davido
Addict
Addict
Posts: 1890
Joined: Fri Nov 09, 2012 11:04 pm
Location: Uttoxeter, UK

Re: Canvas flocking cellular automaton

Post by davido »

Thanks very much looks nice.

Tried suggestion by luis - looks even better!
DE AA EB
User avatar
Mindphazer
Enthusiast
Enthusiast
Posts: 456
Joined: Mon Sep 10, 2012 10:41 am
Location: Savoie

Re: Canvas flocking cellular automaton

Post by Mindphazer »

Works fine on OSX, except : TAB key does not toggle to fullscreen
MacBook Pro 16" M4 Pro - 24 Gb - MacOS 15.4.1 - Iphone 15 Pro Max - iPad at home
...and unfortunately... Windows at work...
User avatar
idle
Always Here
Always Here
Posts: 5836
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Canvas flocking cellular automaton

Post by idle »

very nice
Windows 11, Manjaro, Raspberry Pi OS
Image
User avatar
kernadec
Enthusiast
Enthusiast
Posts: 146
Joined: Tue Jan 05, 2010 10:35 am

Re: Canvas flocking cellular automaton

Post by kernadec »

thank you fiver

very beautiful and without smoking

good day
Fred
Administrator
Administrator
Posts: 18162
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: Canvas flocking cellular automaton

Post by Fred »

yep, very good
Post Reply