Synaptic Chaos
Flocking behaviour in neurotransmitters. What more could you ask for?
P5.js
Main class
This is our instance of the main class in the P5.js
library.
The argument is the link between the library and this code, and the special functions we override in the class definition are callbacks for P5.js events.
### Ben Scott # 2015-10-12 # Mania ###
'use strict' # just like JavaScript
myp = new p5 (p) ->
### Input ###
mouse = [p.mouseX,p.mouseY]
lastMouse = [p.pmouseX,p.pmouseY]
### Library ###
rand = p.random
### DOM ###
[container,canvas] = [null,null]
### Assets ###
[bg_img,dope_img] = [null,null]
### Mania ###
manic = false
[max_v,max_f,dope_rate] = [3,0.05,60]
[receptors,transmitters] = [[],[]]
Neurotransmitter
This class represents a generic neurotransmitter, which the dopamine and amphetamine classes extend.
@a
real : acceleration (m^2/ms)@v
real : velocity (m/ms)@m
int : mass (kg)@t
real : lifetime (ms)@pos
<real,real> : current position@tgt
<real,real> : target position
class Neurotransmitter
constructor: (@pos,@tgt,@m=1,@t=1024) ->
@a = p.createVector(0,0)
@v = p.createVector(rand(-1,1), rand(-1,1))
@tgt = p.createVector(p.width/2,p.height/2)
unless (@pos?)
@pos = p.createVector(
p.width/2+p.random(-50,50),-10)
@v.y = p.random(1,2)
run: (group) ->
@flock(group)
@update()
@bounds()
@draw()
applyForce: (list...) ->
@a.add(force)/@m for force in list
flock: (group) ->
@applyForce(
@separate(group).mult(1.5)
@align(group).mult(1)
@cohesion(group).mult(1))
seek: (tgt) ->
@tgt = tgt
len = p5.Vector.sub(tgt,@pos)
len.normalize()
len.mult(max_v)
dir = p5.Vector.sub(len,@v)
dir.limit(0.05)
return dir
bounds: ->
return if (-10<@pos.x<p.width+10)
unless (-10<@pos.y<p.height+10)
transmitters.remove(this)
delete this
update: ->
@v.add(@a)
@v.limit(max_v)
@pos.add(@v)
@a = p.createVector(0,0)
draw: ->
p.push()
polygon(@pos.x,@pos.y,5,6)
p.pop()
Dopamine
This is a class which represents the neurotransmitter Dopamine, which is responsible for pleasure and euphoria.
@a
real : acceleration (m^2/ms)@v
real : velocity (m/ms)@m
int : mass (kg)@t
real : lifetime (ms)@r
int : radius (pixel)@pos
<real,real> : current position@tgt
<real,real> : target position@max_v
real : maximum speed@max_f
real : maximum force
class Dopamine extends Neurotransmitter
flock: (group) ->
super
final_steer = @seek(
p.createVector(p.width/2,p.height*2))
final_steer.mult(0.5)
@applyForce(final_steer)
draw: ->
p.push()
value = p.map(value,0,255,60,120)
p.fill(value,180,180)
p.stroke(180)
p.strokeWeight(2)
if (manic && @pos.y>p.height/3)
p.translate(rand(3),rand(3))
p.line(@pos.x,@pos.y,@pos.x-8,@pos.y-4)
p.line(@pos.x,@pos.y,@pos.x-8,@pos.y+4)
p.line(@pos.x,@pos.y,@pos.x+8,@pos.y-4)
p.line(@pos.x+8,@pos.y-4,@pos.x+12,@pos.y-2)
polygon(@pos.x,@pos.y,5,6)
p.pop()
separate: (group) ->
[tgt,n] = [20,0]
dir = p.createVector(0,0)
for elem in group
d = p5.Vector.dist(@pos,elem.pos)
if (0<d<tgt)
diff = p5.Vector.sub(@pos,elem.pos)
diff.normalize()
diff.div(d)
dir.add(diff)
n++
dir.div(n) if (n>0)
if (0<dir.mag())
dir.normalize()
dir.mult(max_v)
dir.sub(@v)
dir.limit(0.05)
return dir
align: (group) ->
[len,n] = [50.0,0]
sum = p.createVector(0,0)
for elem in group
d = p5.Vector.dist(@pos,elem.pos)
if (0<d<len)
sum.add(elem.v)
n++
if (n>0)
sum.div(n)
sum.normalize()
sum.mult(max_v)
dir = p5.Vector.sub(sum,@v)
dir.limit(0.05)
return dir
else return p.createVector(0,0)
cohesion: (group) ->
[len,n] = [50,0]
dir = p.createVector(0,0)
for elem in group
d = p5.Vector.dist(@pos,elem.pos)
if (0<d<=len)
dir.add(elem.pos)
n++
if (n>0)
dir.div(n)
return @seek(dir)
else return p.createVector(0,0)
Cocaine
This is a class which represents Cocaine, a dopamine reuptake inhibitor (obviously this is what it’s known for).
@a
real : acceleration (m^2/ms)@v
real : velocity (m/ms)@m
int : mass (kg)@t
real : lifetime (ms)@pos
<real,real> : current position@tgt
<real,real> : target position@n_tgt
<real,real> : clearing behaviour
class Cocaine extends Dopamine
@n_tgt = null
constructor: ->
super
@t = 512+p.random(-128,128)
@mass = 3
@pos = p.createVector(p.mouseX,p.mouseY)
@n_tgt = p.createVector(
p.width/2+rand(-128,128)
p.height/2+rand(-50,50))
update: ->
super
@t--
if (@t<0)
@applyForce(@seek(p.createVector(
p.width*2, rand(-30,30))).mult(3))
draw: ->
p.push()
p.fill(255)
p.stroke(200)
p.line(@pos.x,@pos.y,@pos.x,@pos.y-8)
p.line(@pos.x,@pos.y-8,@pos.x+2,@pos.y-12)
p.line(@pos.x,@pos.y,@pos.x+8,@pos.y+4)
p.line(@pos.x+8,@pos.y+4,@pos.x+12,@pos.y+6)
polygon(@pos.x-4,@pos.y,5,6,30)
polygon(@pos.x+4,@pos.y,5,6,30)
p.pop()
flock: (group) ->
@applyForce(
@separate(group)
@seek(@n_tgt))
separate: (group) ->
[tgt,n] = [20,0]
dir = p.createVector(0,0)
for elem in group
d = p5.Vector.dist(@pos,elem.pos)
if (0<d<tgt)
diff = p5.Vector.sub(elem.pos,@pos)
diff.normalize()
diff.div(d)
dir.add(diff)
n++
unless (elem instanceof Cocaine)
elem.applyForce(diff.normalize())
else elem.applyForce(
p.createVector())
dir.div(n) if (n>0)
if (0<dir.mag())
dir.normalize()
dir.mult(-1)
dir.sub(@v)
dir.limit(0.5)
return dir
Amphetamine
The same neuron! Now with amphetamines! Amphetamines act as a releasing agent for dopamine, and are much easier to animate!
@a
real : acceleration (m^2/ms)@v
real : velocity (m/ms)@m
int : mass (kg)@t
real : lifetime (ms)@pos
<real,real> : current position@tgt
<real,real> : target position@n_tgt
<real,real> : clearing behaviour
class Amphetamine extends Dopamine
@n_tgt = null
constructor: (@pos,@tgt,@m=1,@t=1024) ->
super
@pos = p.createVector(p.mouseX,p.mouseY)
@n_tgt = p.createVector(
p.width/2-128+rand(-2,2)
p.height/2-100+rand(-2,2))
if (p.width/3<@pos.x)
@n_tgt = p.createVector(
p.width/2+128+rand(-2,2)
p.height/2-100+rand(-2,2))
update: ->
super
@t--
if (@t<0)
if (rand(2)>1)
@applyForce(@seek(p.createVector(
p.width*2, rand(-30,30))).mult(3))
else @applyForce(@seek(p.createVector(
p.width/2,-p.height).mult(3)))
draw: ->
p.push()
p.fill(255,180,180)
p.stroke(180)
p.strokeWeight(2)
p.line(@pos.x,@pos.y,@pos.x+8,@pos.y-4)
p.line(@pos.x+8,@pos.y-4,@pos.x+12,@pos.y-2)
polygon(@pos.x,@pos.y,5,6)
p.pop()
flock: (group) ->
super
@applyForce(@seek(@n_tgt))
separate: (group) ->
[tgt,n] = [20,0]
dir = p.createVector(0,0)
for elem in group
d = p5.Vector.dist(@pos,elem.pos)
if (0<d<tgt)
diff = p5.Vector.sub(elem.pos,@pos)
diff.normalize()
diff.div(d)
dir.add(diff)
unless (elem instanceof Cocaine)
elem.applyForce(diff.normalize())
n++
dir.div(n) if (n>0)
if (0<dir.mag())
dir.normalize()
dir.mult(3)
dir.sub(@v)
dir.limit(0.1)
return dir
align: (group) -> return p.createVector(0,0)
cohesion: (group) -> return p.createVector(0,0)
Events
These functions are automatic callbacks for P5.js
events:
p.preload
is called once, immediately beforesetup
p.setup
is called once, at the beginning of executionp.draw
is called as frequently asp.framerate
p.keyPressed
is called on every key input eventp.mousePressed
is called on mouse downp.windowResized
keeps window fullp.remove
destroys everything in the sketch
p.preload = ->
bg_img = p.loadImage("/rsc/sketch/synapse.png")
p.setup = ->
setupDOM()
p.noStroke()
p.frameRate(60)
p.draw = ->
drawDOM()
getInput()
manic = (dope_rate<60)
drawReceptors()
dope_rate++ if (dope_rate<60 && p.frameCount%60==0)
if (p.frameCount%dope_rate==0 && transmitters.length<100)
n = if (dope_rate<55) then 20 else 5
for i in [0..rand(2,n)]
transmitters.push(new Dopamine())
for dope in transmitters
dope?.run(transmitters)
p.keyPressed = ->
alt = !alt if (p.keyCode is p.ALT)
p.mouseDragged = ->
mouse = [p.mouseX,p.mouseY]
lastMouse = [p.pmouseX,p.pmouseY]
p.mousePressed = ->
if (p.width/3<p.mouseX<2*p.width/3)
if (p.height/3<p.mouseY<2*p.height/3)
transmitters.unshift(new Cocaine())
else transmitters.unshift(new Amphetamine())
dope_rate-- if (dope_rate>30)
#p.windowResized = ->
# p.resizeCanvas(p.windowWidth, p.windowHeight);
#p.remove = -> p5 = null
Library Functions
These functions I’ve included from other files. They’re the sort of generic utilities that would constitute a library.
- ‘Array::Remove’ takes an element out of a standard array
@e
: element to remove
polygon
draws a regular polygon.@x,@y
<int,int> : center@r
int : radius@n
int : number of points@o
real : offset theta
Array::remove = (e) ->
@[t..t] = [] if (t=@indexOf(e))>-1
polygon = (x,y,r=1,n=3,o=0) ->
theta = p.TWO_PI/n
p.beginShape()
for i in [0..p.TWO_PI] by theta
p.vertex(
x+p.cos(i+o)*r
y+p.sin(i+o)*r)
p.endShape(p.CLOSE)
DOM Functions
These functions initialize and position the DOM objects and the main canvas.
setupDOM
: creates DOM objects & canvasdrawDOM
: draws all DOM objects to the canvasgetInput
: collects input data, processes it, and in the case ofp.mouseIsPressed
, it calls the mouse event callback (otherwise it single-clicks)
setupDOM = ->
canvas = p.createCanvas(756,512)
canvas.parent('CoffeeSketch')
canvas.class("entry")
canvas.style("max-width", "100%")
drawDOM = ->
p.clear()
p.background(bg_img)
getInput = ->
mouse = [p.mouseX,p.mouseY]
lastMouse = [p.pmouseX,p.pmouseY]
Mania Functions
These functions draw the stars, the planets, and carry out the logic of the game / sketch. (what a mess!)
drawReceptors
: draws the reuptake receptors on the axon
drawReceptors = ->
p.push()
p.translate(p.width/2-128,p.height/2-100)
p.rotate(60)
p.ellipse(0,0,30,40)
p.fill(240)
p.ellipse(10,0,10,30)
p.ellipse(-10,0,10,30)
p.pop()
p.push()
p.translate(p.width/2+128,p.height/2-100)
p.rotate(-60)
p.ellipse(0,0,30,40)
p.fill(240)
p.ellipse(10,0,10,30)
p.ellipse(-10,0,10,30)
p.pop()