Initial commit
This commit is contained in:
commit
11ffc81848
|
@ -0,0 +1,79 @@
|
||||||
|
import Vector from './vector.mjs';
|
||||||
|
|
||||||
|
const findNeighbors = (boids, boid, radius) =>
|
||||||
|
{
|
||||||
|
const neighbors = [];
|
||||||
|
|
||||||
|
for (let otherBoid of boids)
|
||||||
|
{
|
||||||
|
if (boid != otherBoid)
|
||||||
|
{
|
||||||
|
const dist = boid.pos.sub(otherBoid.pos).normSquared();
|
||||||
|
|
||||||
|
if (dist < radius * radius)
|
||||||
|
{
|
||||||
|
neighbors.push(otherBoid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return neighbors;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const update = (boids, params, time) =>
|
||||||
|
{
|
||||||
|
for (let boid of boids)
|
||||||
|
{
|
||||||
|
const visible = findNeighbors(boids, boid, params.visibleDist);
|
||||||
|
const close = findNeighbors(boids, boid, params.closeDist);
|
||||||
|
|
||||||
|
// Attract towards center of visible flock
|
||||||
|
const center = new Vector(0, 0);
|
||||||
|
|
||||||
|
for (let otherBoid of visible)
|
||||||
|
{
|
||||||
|
center.addMut(otherBoid.pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visible.length >= 1)
|
||||||
|
{
|
||||||
|
center.divMut(visible.length);
|
||||||
|
boid.vel.addMut(center.sub(boid.pos).mul(params.centerAccel));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attract toward center of screen
|
||||||
|
boid.vel.addMut(boid.pos.mul(-0.01));
|
||||||
|
|
||||||
|
// Repel away from close boids
|
||||||
|
for (let otherBoid of close)
|
||||||
|
{
|
||||||
|
boid.vel.addMut(boid.pos
|
||||||
|
.sub(otherBoid.pos)
|
||||||
|
.mul(params.repelAccel));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match other boids’ velocity
|
||||||
|
const velocity = new Vector(0, 0);
|
||||||
|
|
||||||
|
for (let otherBoid of visible)
|
||||||
|
{
|
||||||
|
velocity.addMut(otherBoid.vel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visible.length >= 1)
|
||||||
|
{
|
||||||
|
velocity.divMut(visible.length);
|
||||||
|
boid.vel.addMut(velocity.sub(boid.vel).mul(params.matchAccel));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not surpass maximum speed
|
||||||
|
const speed = boid.vel.normSquared();
|
||||||
|
|
||||||
|
if (speed > params.maxSpeed * params.maxSpeed)
|
||||||
|
{
|
||||||
|
boid.vel.divMut(Math.sqrt(speed)).mulMut(params.maxSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
boid.pos.addMut(boid.vel.mul(time));
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,101 @@
|
||||||
|
import Vector from './vector.mjs';
|
||||||
|
|
||||||
|
const boidShape = [
|
||||||
|
new Vector(2, 0),
|
||||||
|
new Vector(-1, 1),
|
||||||
|
new Vector(-1, -1),
|
||||||
|
new Vector(2, 0),
|
||||||
|
];
|
||||||
|
|
||||||
|
export const boids = (activeBoids, params, ctx, width, height, center) =>
|
||||||
|
{
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
// // Draw each boid’s visibility and proximity zones
|
||||||
|
// ctx.beginPath();
|
||||||
|
// ctx.fillStyle = '#BBEE94';
|
||||||
|
|
||||||
|
// for (let boid of activeBoids)
|
||||||
|
// {
|
||||||
|
// const trans = boid.pos.add(center);
|
||||||
|
|
||||||
|
// ctx.moveTo(
|
||||||
|
// trans.x + params.visibleDist,
|
||||||
|
// trans.y
|
||||||
|
// );
|
||||||
|
|
||||||
|
// ctx.arc(
|
||||||
|
// trans.x, trans.y,
|
||||||
|
// params.visibleDist,
|
||||||
|
// 0, 2 * Math.PI
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ctx.fill();
|
||||||
|
// ctx.stroke();
|
||||||
|
|
||||||
|
// ctx.beginPath();
|
||||||
|
// ctx.fillStyle = '#EE9495';
|
||||||
|
|
||||||
|
// for (let boid of activeBoids)
|
||||||
|
// {
|
||||||
|
// const trans = boid.pos.add(center);
|
||||||
|
|
||||||
|
// ctx.moveTo(
|
||||||
|
// trans.x + params.closeDist,
|
||||||
|
// trans.y
|
||||||
|
// );
|
||||||
|
|
||||||
|
// ctx.arc(
|
||||||
|
// trans.x, trans.y,
|
||||||
|
// params.closeDist,
|
||||||
|
// 0, 2 * Math.PI
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ctx.fill();
|
||||||
|
// ctx.stroke();
|
||||||
|
|
||||||
|
// // Draw each boid’s velocity vector
|
||||||
|
// ctx.beginPath();
|
||||||
|
// ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)';
|
||||||
|
|
||||||
|
// for (let boid of activeBoids)
|
||||||
|
// {
|
||||||
|
// const start = boid.pos.add(center);
|
||||||
|
// const end = start.add(boid.vel.mul(0.25));
|
||||||
|
|
||||||
|
// ctx.moveTo(start.x, start.y);
|
||||||
|
// ctx.lineTo(end.x, end.y);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ctx.stroke();
|
||||||
|
|
||||||
|
// Draw each boid’s head following the angle of its course
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.fillStyle = 'black';
|
||||||
|
|
||||||
|
for (let boid of activeBoids)
|
||||||
|
{
|
||||||
|
const angle = boid.vel.angle();
|
||||||
|
let isFirst = true;
|
||||||
|
|
||||||
|
for (let point of boidShape)
|
||||||
|
{
|
||||||
|
const trans = boid.pos.add(center)
|
||||||
|
.add(point.rotate(angle).mul(params.radius))
|
||||||
|
|
||||||
|
if (isFirst)
|
||||||
|
{
|
||||||
|
ctx.moveTo(trans.x, trans.y);
|
||||||
|
isFirst = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ctx.lineTo(trans.x, trans.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.fill();
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Boids</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body, html
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#boids-canvas
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="boids-canvas"></canvas>
|
||||||
|
<script src="index.mjs" type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,66 @@
|
||||||
|
import Vector from './vector.mjs';
|
||||||
|
import * as boids from './boids.mjs';
|
||||||
|
import * as draw from './draw.mjs';
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
centerAccel: 0.01,
|
||||||
|
repelAccel: 1,
|
||||||
|
matchAccel: 0.1,
|
||||||
|
|
||||||
|
maxSpeed: 300,
|
||||||
|
|
||||||
|
closeDist: 20,
|
||||||
|
visibleDist: 80,
|
||||||
|
|
||||||
|
radius: 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
const boidsCanvas = document.querySelector('#boids-canvas');
|
||||||
|
const boidsCtx = boidsCanvas.getContext('2d');
|
||||||
|
|
||||||
|
let width = null;
|
||||||
|
let height = null;
|
||||||
|
let center = null;
|
||||||
|
|
||||||
|
const updateSize = () =>
|
||||||
|
{
|
||||||
|
width = window.innerWidth;
|
||||||
|
height = window.innerHeight;
|
||||||
|
center = new Vector(width / 2, height / 2);
|
||||||
|
|
||||||
|
boidsCanvas.width = width;
|
||||||
|
boidsCanvas.height = height;
|
||||||
|
};
|
||||||
|
|
||||||
|
updateSize();
|
||||||
|
window.onresize = updateSize;
|
||||||
|
|
||||||
|
const activeBoids = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < 300; ++i)
|
||||||
|
{
|
||||||
|
activeBoids.push({
|
||||||
|
pos: new Vector(200 + (i % 2) * (-400), 0),
|
||||||
|
vel: new Vector(Math.random() * 100, Math.random() * 100),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastTime = null;
|
||||||
|
|
||||||
|
const loop = time =>
|
||||||
|
{
|
||||||
|
if (!lastTime)
|
||||||
|
{
|
||||||
|
lastTime = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
const delta = (time - lastTime) / 1000;
|
||||||
|
lastTime = time;
|
||||||
|
|
||||||
|
boids.update(activeBoids, params, delta);
|
||||||
|
draw.boids(activeBoids, params, boidsCtx, width, height, center);
|
||||||
|
|
||||||
|
requestAnimationFrame(loop);
|
||||||
|
};
|
||||||
|
|
||||||
|
requestAnimationFrame(loop);
|
|
@ -0,0 +1,77 @@
|
||||||
|
class Vector
|
||||||
|
{
|
||||||
|
constructor(x = 0, y = 0)
|
||||||
|
{
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
add(vector)
|
||||||
|
{
|
||||||
|
return new Vector(this.x + vector.x, this.y + vector.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
addMut(vector)
|
||||||
|
{
|
||||||
|
this.x += vector.x;
|
||||||
|
this.y += vector.y;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub(vector)
|
||||||
|
{
|
||||||
|
return new Vector(this.x - vector.x, this.y - vector.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
subMut(vector)
|
||||||
|
{
|
||||||
|
this.x -= vector.x;
|
||||||
|
this.y -= vector.y;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
mul(scalar)
|
||||||
|
{
|
||||||
|
return new Vector(this.x * scalar, this.y * scalar);
|
||||||
|
}
|
||||||
|
|
||||||
|
mulMut(scalar)
|
||||||
|
{
|
||||||
|
this.x *= scalar;
|
||||||
|
this.y *= scalar;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
div(scalar)
|
||||||
|
{
|
||||||
|
return this.mul(1 / scalar);
|
||||||
|
}
|
||||||
|
|
||||||
|
divMut(scalar)
|
||||||
|
{
|
||||||
|
return this.mulMut(1 / scalar);
|
||||||
|
}
|
||||||
|
|
||||||
|
rotate(angle)
|
||||||
|
{
|
||||||
|
const cos = Math.cos(angle);
|
||||||
|
const sin = Math.sin(angle);
|
||||||
|
|
||||||
|
return new Vector(
|
||||||
|
cos * this.x - sin * this.y,
|
||||||
|
cos * this.y + sin * this.x,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
angle()
|
||||||
|
{
|
||||||
|
return Math.PI / 2 - Math.atan2(this.x, this.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
normSquared()
|
||||||
|
{
|
||||||
|
return this.x * this.x + this.y * this.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Vector;
|
Loading…
Reference in New Issue