Skip to content

Instantly share code, notes, and snippets.

@timetocode
Created February 28, 2017 07:13
Show Gist options
  • Save timetocode/fe3a20b64bf7f600c209f946c30acddd to your computer and use it in GitHub Desktop.
Save timetocode/fe3a20b64bf7f600c209f946c30acddd to your computer and use it in GitHub Desktop.
A rendition of https://github.com/hughsk/boids used in http://sharkz.io/ (~v0.1.3 of sharkz)
function hypot(a, b) {
a = Math.abs(a)
b = Math.abs(b)
var lo = Math.min(a, b)
var hi = Math.max(a, b)
return hi + 3 * lo / 32 + Math.max(0, 2 * lo - hi) / 8 + Math.max(0, 4 * lo - hi) / 16
}
function BoidSys(opts) {
this.boids = []
this.attractors = []
this.boundary = opts.boundary
this.grid = null
this.velocityLimitRoot = opts.velocityLimit || 0
this.accelerationLimitRoot = opts.accelerationLimit || 1
this.velocityLimit = Math.pow(this.velocityLimitRoot, 2)
this.accelerationLimit = Math.pow(this.accelerationLimitRoot, 2)
this.separationDistance = Math.pow(opts.separationDistance || 60, 2)
this.alignmentDistance = Math.pow(opts.alignmentDistance || 580, 2)
this.cohesionDistance = Math.pow(opts.cohesionDistance || 580, 2)
this.separationForce = opts.separationForce || 0.15
this.cohesionForce = opts.cohesionForce || 0.15
this.alignmentForce = opts.alignmentForce || opts.alignment || 0.25
}
BoidSys.prototype.add = function(entity) {
this.boids.push(entity)
}
BoidSys.prototype.remove = function(entity) {
this.boids.splice(this.boids.indexOf(entity), 1)
}
BoidSys.prototype.addAttractor = function(attractor) {
this.attractors.push(attractor)
}
BoidSys.prototype.removeAttractor = function(attractor) {
this.attractors.splice(this.attractors.indexOf(attractor), 1)
}
BoidSys.prototype.update = function() {
//console.log('b', this.boids.length, 'a', this.attractors.length)
//this.boids.forEach(boidA => {
for (var i = 0; i < this.boids.length; i++) {
var boidA = this.boids[i]
var sForceX = 0
var sForceY = 0
var cForceX = 0
var cForceY = 0
var aForceX = 0
var aForceY = 0
var distSquared = 0
var length = 0
var ratio = 0
//this.attractors.forEach(attractor => {
if (this.grid) {
var nearby = this.grid.queryArea({
x: boidA.x,
y: boidA.y,
halfWidth: 2000,
halfHeight: 2000
})
//console.log('boids', nearby.length, this.alignmentDistance*0.5)
for (var m = 0; m < nearby.length; m++) {
var attractor = nearby[m]
if (attractor.creatureType === 'Shark' && attractor.visibility > 20) {
var dx = boidA.x - attractor.x
var dy = boidA.y - attractor.y
distSquared = dx*dx + dy*dy
if (distSquared < attractor.radius * attractor.radius) {
length = hypot(dx, dy)
boidA.vx -= (attractor.force * dx / length) || 0
boidA.vy -= (attractor.force * dy / length) || 0
}
}
}
}
/*
for (var j = 0; j < this.attractors.length; j++) {
var attractor = this.attractors[j]
var dx = boidA.x - attractor.x
var dy = boidA.y - attractor.y
distSquared = dx*dx + dy*dy
if (distSquared < attractor.radius * attractor.radius) {
length = hypot(dx, dy)
boidA.vx -= (attractor.force * dx / length) || 0
boidA.vy -= (attractor.force * dy / length) || 0
// some hacky logic that lets the squid get scared and shoot ink
//if (attractor.isShark) {
// if (typeof boidA.scare === 'function') {
// boidA.scare()
// }
//}
}
}*/
// boids are scared of the edges of the map (can change this and let them
// wrap around the map instead with code in a comment farther down)
if (boidA.x < 250) {
boidA.vx += 6.25
}
if (boidA.x > this.boundary.width - 250) {
boidA.vx -= 6.25
}
if (boidA.y < 250) {
boidA.vy += 6.25
}
if (boidA.y > this.boundary.height - 250) {
boidA.vy -= 6.25
}
if (this.grid) {
var nearby = this.grid.queryArea({
x: boidA.x,
y: boidA.y,
halfWidth: 2000,
halfHeight: 2000
})
//console.log('boids', nearby.length, this.alignmentDistance*0.5)
for (var m = 0; m < nearby.length; m++) {
var boidB = nearby[m]
if (boidB.isFish) {
if (boidA !== boidB) {
var dx = boidA.x - boidB.x
var dy = boidA.y - boidB.y
distSquared = dx*dx + dy*dy
if (distSquared < this.separationDistance) {
sForceX += dx
sForceY += dy
} else {
if (distSquared < this.cohesionDistance) {
cForceX += dx
cForceY += dy
}
if (distSquared < this.alignmentDistance) {
aForceX += boidB.vx
aForceY += boidB.vy
}
}
}
}
}
}
/*
this.boids.forEach(boidB => {
if (boidA !== boidB) {
var dx = boidA.x - boidB.x
var dy = boidA.y - boidB.y
distSquared = dx*dx + dy*dy
if (distSquared < this.separationDistance) {
sForceX += dx
sForceY += dy
} else {
if (distSquared < this.cohesionDistance) {
cForceX += dx
cForceY += dy
}
if (distSquared < this.alignmentDistance) {
aForceX += boidB.vx
aForceY += boidB.vy
}
}
}
})
*/
//this.boids.forEach(boidB => {
/*
for (var j = 0; j < this.boids.length; j++) {
console.log('boids', this.boids.length)
var boidB = this.boids[j]
if (boidA !== boidB) {
var dx = boidA.x - boidB.x
var dy = boidA.y - boidB.y
distSquared = dx*dx + dy*dy
if (distSquared < this.separationDistance) {
sForceX += dx
sForceY += dy
} else {
if (distSquared < this.cohesionDistance) {
cForceX += dx
cForceY += dy
}
if (distSquared < this.alignmentDistance) {
aForceX += boidB.vx
aForceY += boidB.vy
}
}
}
}
*/
length = hypot(sForceX, sForceY)
boidA.ax += (this.separationForce * sForceX / length) || 0
boidA.ay += (this.separationForce * sForceY / length) || 0
length = hypot(cForceX, cForceY)
boidA.ax -= (this.cohesionForce * cForceX / length) || 0
boidA.ay -= (this.cohesionForce * cForceY / length) || 0
length = hypot(aForceX, aForceY)
boidA.ax -= (this.alignmentForce * aForceX / length) || 0
boidA.ay -= (this.alignmentForce * aForceY / length) || 0
// limit acceleration
if (this.accelerationLimit) {
distSquared = boidA.ax*boidA.ax + boidA.ay*boidA.ay
if (distSquared > this.accelerationLimit) {
ratio = this.accelerationLimitRoot / hypot(boidA.ax, boidA.ay)
boidA.ax *= ratio
boidA.ay *= ratio
}
}
boidA.vx += boidA.ax
boidA.vy += boidA.ay
// limit velocity
if (this.velocityLimit) {
distSquared = boidA.vx*boidA.vx + boidA.vy*boidA.vy
if (distSquared > this.velocityLimit) {
ratio = this.velocityLimitRoot / hypot(boidA.vx, boidA.vy)
boidA.vx *= ratio
boidA.vy *= ratio
}
}
boidA.x += boidA.vx
boidA.y += boidA.vy
boidA.rotation = Math.atan2(boidA.vy, boidA.vx)
/*
// boids wrap around boundary edges
if (boidA.x < 0) {
boidA.x = this.boundary.width + boidA.x
}
if (boidA.x > this.boundary.width) {
boidA.x = boidA.x - this.boundary.width
}
if (boidA.y < 0) {
boidA.y = this.boundary.height + boidA.y
}
if (boidA.y > this.boundary.height) {
boidA.y = boidA.y - this.boundary.height
}
*/
// boids cannot travel through boundary edges
if (boidA.x < 0) {
boidA.x = 0
}
if (boidA.x > this.boundary.width) {
boidA.x = this.boundary.width
}
if (boidA.y < 0) {
boidA.y = 0
}
if (boidA.y > this.boundary.height) {
boidA.y = this.boundary.height
}
}
}
module.exports = BoidSys
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment