Initial commit

This commit is contained in:
Mattéo Delabre 2020-05-04 17:17:55 +02:00
commit 11ffc81848
Signed by: matteo
GPG Key ID: AE3FBD02DC583ABB
5 changed files with 351 additions and 0 deletions

79
boids.mjs Normal file
View File

@ -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));
}
};

101
draw.mjs Normal file
View File

@ -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 boids 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 boids 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 boids 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();
};

28
index.html Normal file
View File

@ -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>

66
index.mjs Normal file
View File

@ -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);

77
vector.mjs Normal file
View File

@ -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;