Skip to content

Instantly share code, notes, and snippets.

@jeremycflin
Created September 14, 2017 16:54
Show Gist options
  • Save jeremycflin/7a987f309bd2faaccde94d353b2904a0 to your computer and use it in GitHub Desktop.
Save jeremycflin/7a987f309bd2faaccde94d353b2904a0 to your computer and use it in GitHub Desktop.
Boids
license: mit

The Boids model can be used to simulate the flocking behavior of birds.

forked from pmplewa's block: Boids

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
</head>
<body>
<script>
var width = 960,
height = 500;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var n = 500;
var cohesionCoeff = 0.05,
alignmentCoeff = 0.05,
separationCoeff = 0.05,
separationDistance = 30,
neighborDistance = 60,
maxVelocity = 2,
maxAcceleration = 0.02;
var line = d3.line(),
color = d3.scaleSequential(d3.interpolateYlGnBu).domain([0, maxVelocity]);
function Vec(x, y) {
this.x = x || 0;
this.y = y || 0;
return this;
}
Vec.prototype.clone = function() {
return new Vec(this.x, this.y);
};
Vec.prototype.length = function() {
return Math.sqrt(this.x*this.x + this.y*this.y);
};
Vec.prototype.plus = function(v) {
this.x += v.x;
this.y += v.y;
return this;
};
Vec.prototype.minus = function(v) {
this.x -= v.x;
this.y -= v.y;
return this;
};
Vec.prototype.scale = function(x) {
this.x *= x;
this.y *= x;
return this;
};
Vec.prototype.normalize = function(x) {
x = typeof x !== "undefined" ? x : 1;
var length = this.length();
if (length > 0) {
return this.scale(x/length);
}
else {
return this;
}
};
Vec.prototype.truncate = function(x) {
var length = this.length();
if (length > x) {
return this.normalize(x);
}
else {
return this;
}
};
var randomX = d3.randomUniform(0, width),
randomY = d3.randomUniform(0, height),
randomVx = d3.randomUniform(0, maxVelocity),
randomVy = d3.randomNormal(0, maxVelocity/4);
var boids = d3.range(n).map(function() {
return {
pos: new Vec(randomX(), randomY()),
vel: new Vec(randomVx(), randomVy()),
acc: new Vec()
};
});
function tick(t) {
boids = boids.filter(function(b) {
return (b.pos.x > 0) & (b.pos.x < width)
& (b.pos.y > 0) & (b.pos.y < height);
});
boids.push({
pos: new Vec(0, randomY()),
vel: new Vec(randomVx(), randomVy()),
acc: new Vec()
});
boids.forEach(function(b1) {
var cohesionForce = new Vec(),
alignmentForce = new Vec(),
separationForce = new Vec();
boids.forEach(function(b2) {
if (b1 === b2) return;
var separation = b2.pos.clone().minus(b1.pos),
distance = separation.length();
if (distance < separationDistance) {
separationForce.minus(separation);
}
else if (distance < neighborDistance) {
cohesionForce.plus(separation);
alignmentForce.plus(b2.vel.clone().minus(b1.vel));
}
});
cohesionForce.normalize(cohesionCoeff);
alignmentForce.normalize(alignmentCoeff);
separationForce.normalize(separationCoeff);
b1.acc = new Vec();
b1.acc.plus(cohesionForce).plus(alignmentForce).plus(separationForce).truncate(maxAcceleration);
b1.vel.plus(b1.acc).truncate(maxVelocity);
b1.pos.plus(b1.vel);
});
var lines = svg.selectAll(".boid").data(boids);
lines.exit().remove();
lines.enter().append("path")
.attr("class", "boid")
.style("stroke", color(0))
.style("stroke-width", 2)
.merge(lines)
.style("stroke", function(b) { return color(b.vel.length()); })
.attr("d", function(b) {
var v = b.vel.clone().normalize(20);
return line([
[b.pos.x - v.x/2, b.pos.y - v.y/2],
[b.pos.x + v.x/2, b.pos.y + v.y/2]]);
});
}
var timer = d3.interval(tick, 20);
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment