Created
September 29, 2021 22:06
-
-
Save antonhornquist/f5f6a30178b33141f876298f028b8e1f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- flocking. | |
-- | |
engine.name = 'PolyPerc' | |
local function to_hz(note) | |
local exp = (note - 21) / 12 | |
return 27.5 * 2^exp | |
end | |
-- http://processingjs.org/learning/topic/flocking/ | |
-- All Examples Written by Casey Reas and Ben Fry | |
-- unless otherwise stated. | |
local WIDTH = 128*2 | |
local HEIGHT = 64*2 | |
local Vector3D = {} | |
Vector3D.__index = Vector3D | |
function Vector3D.new(x, y, z) | |
local v = setmetatable({}, Vector3D) | |
v.x = x or 0 | |
v.y = y or 0 | |
v.z = z or 0 | |
return v | |
end | |
function Vector3D:set_x(x) | |
self.x = x | |
end | |
function Vector3D:set_y(y) | |
self.y = y | |
end | |
function Vector3D:set_z(z) | |
self.z = z | |
end | |
function Vector3D:set_xy(x, y) | |
self.x = x | |
self.y = y | |
end | |
function Vector3D:set_xyz(x, y, z) | |
self.x = x | |
self.y = y | |
self.z = z | |
end | |
function Vector3D:magnitude() | |
return math.sqrt(self.x*self.x + self.y*self.y + self.z*self.z) | |
end | |
function Vector3D:copy() | |
return Vector3D.new(self.x, self.y, self.z) | |
end | |
function Vector3D:add(v) | |
self.x = self.x + v.x | |
self.y = self.y + v.y | |
self.z = self.z + v.z | |
end | |
--[[ | |
function Vector3D:sub(v) | |
self.x = self.x - v.x | |
self.y = self.y - v.y | |
self.z = self.z - v.z | |
end | |
]] | |
function Vector3D.sub(v1, v2) | |
return Vector3D.new(v1.x-v2.x, v1.y-v2.y, v1.z-v2.z) | |
end | |
function Vector3D:mult(n) | |
self.x = self.x * n | |
self.y = self.y * n | |
self.z = self.z * n | |
end | |
function Vector3D:div(n) | |
self.x = self.x / n | |
self.y = self.y / n | |
self.z = self.z / n | |
end | |
function Vector3D:normalize() | |
local m = self:magnitude() | |
if m > 0 then | |
self:div(m) | |
end | |
end | |
function Vector3D:limit(max) | |
if self:magnitude() > max then | |
self:normalize() | |
self:mult(max) | |
end | |
end | |
function Vector3D:heading2D() | |
local angle = math.atan2(-self.y, self.x) | |
return -1*angle | |
end | |
function Vector3D.distance(v1, v2) | |
local dx = v1.x - v2.x | |
local dy = v1.y - v2.y | |
local dz = v1.z - v2.z | |
return math.sqrt(dx*dx + dy*dy + dz*dz) | |
end | |
local Boid = {} | |
Boid.__index = Boid | |
function Boid.new(l, ms, mf) | |
local b = setmetatable({}, Boid) | |
b.acc = Vector3D.new(0, 0) | |
local velx = math.random()*2-1 | |
local vely = math.random()*2-1 | |
-- print("x:" .. velx .. "y:" .. vely) | |
b.vel = Vector3D.new(velx, vely) | |
b.loc = l:copy() | |
b.r = 2.0 | |
b.maxspeed = ms -- Maximum speed | |
b.maxforce = mf -- Maximum steering force | |
b.left_edge = function (b) end | |
b.right_edge = function (b) end | |
b.top_edge = function (b) end | |
b.bottom_edge = function (b) end | |
return b | |
end | |
function Boid:run(boids) | |
self:flock(boids) | |
self:update() | |
self:borders() | |
end | |
-- We accumulate a new acceleration each time based on three rules | |
function Boid:flock(boids) | |
local sep = self:separate(boids) | |
local ali = self:align(boids) | |
local coh = self:cohesion(boids) | |
-- Arbitrarily weight these forces | |
--sep:mult(2) | |
--ali:mult(1) | |
--coh:mult(1) | |
sep:mult(params:get("sep")) | |
ali:mult(params:get("ali")) | |
coh:mult(params:get("coh")) | |
-- Add the force vectors to acceleration | |
self.acc:add(sep) | |
self.acc:add(ali) | |
self.acc:add(coh) | |
end | |
-- Method to update location | |
function Boid:update() | |
-- Update velocity | |
self.vel:add(self.acc) | |
-- Limit speed | |
self.vel:limit(self.maxspeed) | |
self.loc:add(self.vel) | |
-- Reset accelertion to 0 each cycle | |
self.acc:set_xyz(0, 0, 0) | |
end | |
function Boid:seek(target) | |
self.acc:add(self:steer(target, false)) | |
end | |
function Boid:arrive(target) | |
self.acc:add(self:steer(target, true)) | |
end | |
-- A method that calculates a steering vector towards a target | |
-- Takes a second argument, if true, it slows down as it approaches the target | |
function Boid:steer(target, slowdown) | |
local steer | |
local desired = Vector3D.sub(target, self.loc) -- A vector pointing from the location to the target | |
local d = desired:magnitude() -- Distance from the target is the magnitude of the vector | |
-- If the distance is greater than 0, calc steering (otherwise return zero vector) | |
if d > 0 then | |
-- Normalize desired | |
desired:normalize() | |
-- Two options for desired vector magnitude (1 -- based on distance, 2 -- maxspeed) | |
if slowdown and (d < 100) then | |
desired:mult(self.maxspeed*(d/100)) -- This damping is somewhat arbitrary | |
else | |
desired:mult(self.maxspeed) | |
end | |
-- Steering = Desired minus Velocity | |
steer = Vector3D.sub(desired, self.vel) | |
steer:limit(self.maxforce) -- Limit to maximum steering force | |
else | |
steer = Vector3D.new(0, 0) | |
end | |
return steer | |
end | |
-- Wraparound | |
function Boid:borders() | |
local loc = self.loc | |
local r = self.r | |
if loc.x < -r then | |
loc.x = WIDTH+r | |
self.left_edge(self) | |
end | |
if loc.y < -r then | |
loc.y = HEIGHT+r | |
self.top_edge(self) | |
end | |
if loc.x > WIDTH+r then | |
loc.x = -r | |
self.right_edge(self) | |
end | |
if loc.y > HEIGHT+r then | |
loc.y = -r | |
self.bottom_edge(self) | |
end | |
end | |
-- Separation | |
-- Method checks for nearby boids and steers away | |
function Boid:separate(boids) | |
local desiredseparation = 25.0 | |
local sum = Vector3D.new(0, 0, 0) | |
local count = 0 | |
-- For every boid in the system, check if it's too close | |
for i, b in ipairs(boids) do | |
local other = b | |
local d = Vector3D.distance(self.loc, other.loc) | |
-- If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself) | |
if d > 0 and d < desiredseparation then | |
-- Calculate vector pointing away from neighbor | |
local diff = Vector3D.sub(self.loc, other.loc) | |
diff:normalize() | |
diff:div(d) --Weight by distance | |
sum:add(diff) | |
count = count + 1 -- Keep track of how many | |
end | |
end | |
-- Average -- divide by how many | |
if count > 0 then | |
sum:div(count) | |
end | |
return sum | |
end | |
-- Alignment | |
-- For every nearby boid in the system, calculate the average velocity | |
function Boid:align(boids) | |
local neighbordist = 50.0 | |
local sum = Vector3D.new(0, 0, 0) | |
local count = 0 | |
for i, b in ipairs(boids) do | |
local other = b | |
local d = Vector3D.distance(self.loc, other.loc) | |
if d > 0 and d < neighbordist then | |
sum:add(other.vel) | |
count = count + 1 | |
end | |
end | |
if count > 0 then | |
sum:div(count) | |
sum:limit(self.maxforce) | |
end | |
return sum | |
end | |
-- Cohesion | |
-- For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location | |
function Boid:cohesion(boids) | |
local neighbordist = 50.0 | |
local sum = Vector3D.new(0, 0, 0) -- Start with empty vector to accumulate all locations | |
local count = 0 | |
for i, b in ipairs(boids) do | |
local other = b | |
local d = Vector3D.distance(self.loc, other.loc) | |
if d > 0 and d < neighbordist then | |
sum:add(other.loc) -- Add location | |
count = count + 1 | |
end | |
end | |
if count > 0 then | |
sum:div(count) | |
return self:steer(sum, false) -- Steer towards the location | |
end | |
return sum | |
end | |
local Flock = {} | |
Flock.__index = Flock | |
function Flock.new() | |
local f = setmetatable({}, Flock) | |
f.boids = {} -- Initialize the arraylist | |
return f | |
end | |
function Flock:run() | |
for i, b in ipairs(self.boids) do | |
b:run(self.boids) -- Passing the entire list of boids to each boid individually | |
end | |
end | |
function Flock:add_boid(b) | |
table.insert(self.boids, b) | |
end | |
local flock | |
local function respawn() | |
flock = Flock.new() | |
-- Add an initial set of boids into the system | |
for i=1,params:get("boids") do | |
local boid = Boid.new(Vector3D.new(WIDTH/2, HEIGHT/2), 1.0, 0.05) | |
local notes = {60, 62, 63, 65, 67, 69} | |
local rand = math.random(1, 6) | |
boid.left_edge = function(b) | |
engine.hz(to_hz(notes[rand])) | |
end | |
boid.right_edge = function(b) | |
engine.hz(to_hz(notes[rand]+12)) | |
end | |
boid.bottom_edge = function(b) | |
engine.hz(to_hz(notes[rand]-12)) | |
end | |
boid.top_edge = function(b) | |
engine.hz(to_hz(notes[rand]+24)) | |
end | |
flock:add_boid(boid) | |
end | |
end | |
function init() | |
timer = metro.init() | |
timer.event = function () | |
flock:run() | |
redraw() | |
end | |
params:add_number("boids", "boids", 5, 25, 15) | |
params:add_number("sep", "sep", 1, 10, 2) | |
params:add_number("ali", "ali", 1, 10, 1) | |
params:add_number("coh", "coh", 1, 10, 1) | |
params:add_option("run", "run", {"yes", "no"}) | |
params:set_action("run", function (value) | |
if value == 1 then | |
timer:start() | |
else | |
timer:stop() | |
end | |
end) | |
params:add_number("fps", "fps", 1, 120, 60) | |
params:set_action("fps", function (value) | |
timer.time = 1/value | |
end) | |
params:bang() | |
respawn() | |
-- params:read("jah/flocking.pset") | |
screen.line_width(1.0) | |
end | |
function redraw() | |
screen.clear() | |
screen.aa(1) | |
screen.font_size(8) | |
screen.level(15) | |
screen.move(0, 8) | |
--screen.text("flocking") | |
for i, b in ipairs(flock.boids) do | |
local x = b.loc.x/2 | |
local y = b.loc.y/2 | |
screen.rect(x, y, 1, 1) | |
screen.fill() | |
screen.move(x, y) | |
screen.line_rel(b.vel.x*1.25, b.vel.y*1.25) | |
screen.stroke() | |
end | |
screen.update() | |
end | |
function key(n, z) | |
if n == 3 and z == 1 then | |
respawn() | |
end | |
end | |
function enc(n, delta) | |
if n == 1 then | |
mix:delta("output", delta) | |
elseif n == 2 then | |
params:delta("sep", delta) | |
redraw() | |
elseif n == 3 then | |
params:delta("coh", delta) | |
redraw() | |
end | |
end | |
function cleanup() | |
-- params:write("jah/flocking.pset") | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment