|
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
|