|
Boids - Source CodeCraig Reynolds' Boid flocking simulation |
|
REBOL [ Title: "Boids" File: %boids.r Author: "Nathan Brown" Home: http://www.croftware.com Date: 29-Mar-2004 Purpose: { Bird flocking simulation based on Craig Reynolds' Boids. The boids are controlled purely by local interactions with no explicit global control. A flocking dynamic emerges over a short period of time. } ] boid-num: 20 max-boid: 199 radius: 50 max-radius: 199 crowded: 10 max-crowded: 39 change-rate: 30 max-change: 180 species: 1 boids: make block! 0 random/seed now/time movement: [[5.0 0.0] [4.8 1.3] [4.3 2.5] [3.5 3.5] [2.5 4.3] [1.3 4.8] [0.0 5.0] [-1.3 4.8] [-2.5 4.3] [-3.5 3.5] [-4.3 2.5] [-4.8 1.3] [-5.0 0.0] [-4.8 -1.3] [-4.3 -2.5] [-3.5 -3.5] [-2.5 -4.3] [-1.3 -4.8] [0.0 -5.0] [1.3 -4.8] [2.5 -4.3] [3.5 -3.5] [4.3 -2.5] [4.8 -1.3] [5.0 0.0] ] boid-face: make face [ id: 0 theta: 0 act-loc: 0x0 ; last updated co-ords co-ords: none ; boid co-ords at next time-step aim: 0x0 too-close: false feathers: red edge: make edge [size: 0x0] size: 10x10 ;image: load %sphere.png effect: [key 0.0.0 fit] refresh: does [ act-loc/x: co-ords/x act-loc/y: co-ords/y offset/x: co-ords/x - (size/x / 2) offset/y: co-ords/y - (size/y / 2) ] move: func [ {Moves the boid one time-step in direction adjusted for its neighbouring flock-mates} heading [integer!] "General heading of this boid's neighbouring flock" /local ch diff ][ ch: 0 diff: theta - heading either too-close [0] [ ch: ch + either theta > heading [ either (abs diff) < (abs (diff + 360)) [- change-rate] [change-rate] ][ either (abs diff) < (abs (diff + 360)) [change-rate] [- change-rate] ] ] theta: theta + ch + 360 // 360 co-ords/x: co-ords/x + to-integer (first (pick movement (theta / 15 + 1))) co-ords/y: co-ords/y + - to-integer (second (pick movement (theta / 15 + 1))) co-ords/x: co-ords/x + world/size/x // world/size/x co-ords/y: co-ords/y + world/size/y // world/size/y aim/x: co-ords/x + to-integer (20 * cosine/radians (theta * (pi / 180))) aim/y: co-ords/y + to-integer (-20 * sine/radians (theta * (pi / 180))) too-close: false ] distance: func [ {Euclidean distance between this boids and the specified co-ordinates} loc [pair! object!] "co-ordinates" /local x y ][ x: loc/x - co-ords/x y: loc/y - co-ords/y return to-integer square-root (x * x + (y * y)) ] general-heading: func [ {Calculates the general heading of this boid's neighbouring flock-mates including itself} boids [block!] "Boids flock block" /local heading num goal-theta ][ heading: make object! [x: 0.0 y: 0.0] num: 0 foreach boid boids [ either all [(distance boid/act-loc) < crowded not id = boid/id] [ too-close: true ][ if (distance boid/act-loc) < radius [ either feathers = boid/feathers [ heading/x: heading/x + boid/aim/x heading/y: heading/y + boid/aim/y ][ heading/x: either aim/x < boid/aim/x [0] [1000] heading/y: either aim/y < boid/aim/y [0] [1000] num: 0 break ] num: num + 1 ] ] ] if not num = 0 [ heading/x: heading/x / num heading/y: heading/y / num ] goal-theta: either (heading/x - co-ords/x) = 0 [0] [ (co-ords/y - heading/y) / (heading/x - co-ords/x) ] goal-theta: to-integer ((arctangent/radians goal-theta) * 180 / pi) if heading/x < co-ords/x [goal-theta: goal-theta + 180] if goal-theta < 0 [goal-theta: goal-theta + 360] return to-integer (goal-theta / 15) * 15 ] ] update-flock: func [ {Updates the entire flock by first finding out where each boid is going to move to, then moving the flock en masse.} boids [block!] "boid flock block" ][ foreach boid boids [boid/move boid/general-heading boids] foreach boid boids [boid/refresh] show world ] create-boids: func [ {Given a boids flock block, the function appends or removes boids until the number of boids is equal to num} boids [block!] "Global boids flock block" num [integer!] "New number of boids in flock block" range [object!] "Permitted co-ordinates range" /init "Initialises the Boids flock block" /local clr ][ if init [remove/part boids (length? boids)] either num > (length? boids) [ for i ((length? boids) + 1) num 1 [ clr: pick [255.0.0 0.255.0] (random species) append boids make boid-face compose/deep [ id: i co-ords: make object! [x: (random range/x) y: (random range/y)] theta: (random 24) * 15 aim: make object! [x: co-ords/x y: co-ords/y] feathers: clr either pos: find effect 'colorize [ change next pos clr ][ append effect [colorize (clr)] ] ] ] ] [remove/part boids ((length? boids) - num)] foreach boid boids [boid/refresh] world/pane: boids show world ] view-boids: func [ /local world-pair world-size boids-layout ][ world-pair: 400x300 world-size: make object! [x: world-pair/x y: world-pair/y] boids-layout: layout [ ;backdrop effect compose [gradient 1x1 (aqua) (sky)] across origin 4x4 space 4x4 style lbl label 100 right style sld slider 200x20 style btn btn 90 style chc choice 200x20 style rot rotary 200x20 world: box world-pair black rate 0 feel [engage: func [face act evt] [update-flock boids]] return across lbl "boids:" num-sld: sld [ boid-num: num-lbl/text: to-integer (num-sld/data * max-boid + 1) show num-lbl create-boids boids boid-num world-size ] num-lbl: label 36 center to-string boid-num return lbl "radius:" rds-sld: sld [ radius: rds-lbl/text: to-integer (rds-sld/data * max-radius + 1) show rds-lbl ] rds-lbl: label 36 center to-string radius return lbl "crowded:" crd-sld: sld [ crowded: crd-lbl/text: to-integer (crd-sld/data * max-crowded + 1) show crd-lbl ] crd-lbl: label 36 center to-string crowded return lbl "change:" chg-sld: sld [ change-rate: chg-lbl/text: to-integer (chg-sld/data * max-change) show chg-lbl ] chg-lbl: label 36 center to-string change-rate return lbl "species:" spc-rot: rot "one" "two" [ species: switch (first spc-rot/data) ["one" [1] "two" [2]] create-boids/init boids boid-num world-size ] return below pad 20 across pad 310 btn #"r" "randomise" [create-boids/init boids boid-num world-size] ] create-boids boids boid-num world-size num-sld/data: boid-num / max-boid rds-sld/data: radius / max-radius crd-sld/data: crowded / max-crowded chg-sld/data: change-rate / max-change view center-face boids-layout ] view-boids |