Compare commits
2 Commits
32411933cd
...
639c56333e
Author | SHA1 | Date |
---|---|---|
Mattéo Delabre | 639c56333e | |
Mattéo Delabre | cc3a719542 |
153
boids.mjs
153
boids.mjs
|
@ -1,8 +1,52 @@
|
||||||
import Vector from './vector.mjs';
|
import {Vector} from './geometry.mjs';
|
||||||
|
|
||||||
|
// Outline of the shape of a boid when angled at 0 rad
|
||||||
|
const boidShape = [
|
||||||
|
new Vector(1, 0),
|
||||||
|
new Vector(-.5, .5),
|
||||||
|
new Vector(-.5, -.5),
|
||||||
|
new Vector(1, 0),
|
||||||
|
];
|
||||||
|
const boidShapeLength = boidShape.length;
|
||||||
|
|
||||||
|
class Boid
|
||||||
|
{
|
||||||
|
constructor(params, center)
|
||||||
|
{
|
||||||
|
this.params = params;
|
||||||
|
this.pos = center.clone();
|
||||||
|
this.vel = new Vector(Math.random() - 0.5, Math.random() - 0.5);
|
||||||
|
this.register = new Vector(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
draw(ctx)
|
||||||
|
{
|
||||||
|
const transformed = this.register;
|
||||||
|
const angle = this.vel.angle();
|
||||||
|
let isFirst = true;
|
||||||
|
|
||||||
|
for (let j = 0; j < boidShapeLength; ++j)
|
||||||
|
{
|
||||||
|
transformed.x = boidShape[j].x;
|
||||||
|
transformed.y = boidShape[j].y;
|
||||||
|
transformed.rotate(angle).mul(this.params.radius).add(this.pos);
|
||||||
|
|
||||||
|
if (isFirst)
|
||||||
|
{
|
||||||
|
ctx.moveTo(transformed.x, transformed.y);
|
||||||
|
isFirst = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ctx.lineTo(transformed.x, transformed.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Boids
|
class Boids
|
||||||
{
|
{
|
||||||
constructor(canvas, params = {})
|
constructor(canvas, obstacles, params = {})
|
||||||
{
|
{
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
this.ctx = canvas.getContext('2d');
|
this.ctx = canvas.getContext('2d');
|
||||||
|
@ -20,6 +64,7 @@ class Boids
|
||||||
|
|
||||||
radius: 10,
|
radius: 10,
|
||||||
color: 'black',
|
color: 'black',
|
||||||
|
debug: false,
|
||||||
}, params);
|
}, params);
|
||||||
|
|
||||||
// Current width and height of the canvas
|
// Current width and height of the canvas
|
||||||
|
@ -27,13 +72,18 @@ class Boids
|
||||||
this.height = canvas.height;
|
this.height = canvas.height;
|
||||||
|
|
||||||
// Current center point of the canvas
|
// Current center point of the canvas
|
||||||
this.center = new Vector(canvas.width / 2, canvas.height / 2);
|
this.center = null;
|
||||||
|
|
||||||
// Last time where the canvas was repainted
|
// Last time where the canvas was repainted
|
||||||
this.lastTime = null;
|
this.lastTime = null;
|
||||||
|
|
||||||
// List of active simulated boids
|
// List of active simulated boids
|
||||||
this.boids = [];
|
this.boids = [];
|
||||||
|
this.boidsLength = 0;
|
||||||
|
|
||||||
|
// List of obstacles
|
||||||
|
this.obstacles = obstacles;
|
||||||
|
this.obstaclesLength = this.obstacles.length;
|
||||||
|
|
||||||
// Vector registers used for holding temporary values
|
// Vector registers used for holding temporary values
|
||||||
this.registers = [
|
this.registers = [
|
||||||
|
@ -46,14 +96,7 @@ class Boids
|
||||||
// otherwise, null
|
// otherwise, null
|
||||||
this.animationId = null;
|
this.animationId = null;
|
||||||
|
|
||||||
// Outline of the shape of a boid when angled at 0 rad
|
this._afterResize();
|
||||||
this.boidShape = [
|
|
||||||
new Vector(1, 0),
|
|
||||||
new Vector(-.5, .5),
|
|
||||||
new Vector(-.5, -.5),
|
|
||||||
new Vector(1, 0),
|
|
||||||
];
|
|
||||||
this.boidShapeLength = this.boidShape.length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Change the canvas’ dimensions */
|
/** Change the canvas’ dimensions */
|
||||||
|
@ -64,16 +107,22 @@ class Boids
|
||||||
|
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
this.center = new Vector(width / 2, height / 2);
|
|
||||||
|
this._afterResize();
|
||||||
|
}
|
||||||
|
|
||||||
|
_afterResize()
|
||||||
|
{
|
||||||
|
this.center = new Vector(this.width / 2, this.height / 2);
|
||||||
|
this.ctx.resetTransform();
|
||||||
|
this.ctx.translate(this.center.x, this.center.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Introduce a new boid in the simulation. */
|
/** Introduce a new boid in the simulation. */
|
||||||
add(center)
|
add(center)
|
||||||
{
|
{
|
||||||
this.boids.push({
|
this.boids.push(new Boid(this.params, center));
|
||||||
pos: center.clone(),
|
this.boidsLength += 1;
|
||||||
vel: new Vector(Math.random() - 0.5, Math.random() - 0.5),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Start the simulation. */
|
/** Start the simulation. */
|
||||||
|
@ -125,9 +174,9 @@ class Boids
|
||||||
*/
|
*/
|
||||||
_update(delta)
|
_update(delta)
|
||||||
{
|
{
|
||||||
const length = this.boids.length;
|
const boidsLength = this.boidsLength;
|
||||||
|
|
||||||
for (let i = 0; i < length; ++i)
|
for (let i = 0; i < boidsLength; ++i)
|
||||||
{
|
{
|
||||||
const me = this.boids[i];
|
const me = this.boids[i];
|
||||||
|
|
||||||
|
@ -137,7 +186,9 @@ class Boids
|
||||||
|
|
||||||
let visibles = 0;
|
let visibles = 0;
|
||||||
|
|
||||||
for (let j = 0; j < length; ++j)
|
// Compute mean flock position and velocity and compute
|
||||||
|
// the repel force
|
||||||
|
for (let j = 0; j < boidsLength; ++j)
|
||||||
{
|
{
|
||||||
if (i != j)
|
if (i != j)
|
||||||
{
|
{
|
||||||
|
@ -188,6 +239,17 @@ class Boids
|
||||||
.mul(this.params.matchAccel));
|
.mul(this.params.matchAccel));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Avoid obstacles
|
||||||
|
for (let j = 0; j < this.obstaclesLength; ++j)
|
||||||
|
{
|
||||||
|
const obstacle = this.obstacles[j];
|
||||||
|
|
||||||
|
if (obstacle.intersect(me.pos, this.params.radius))
|
||||||
|
{
|
||||||
|
me.vel.sub(obstacle.center).add(me.pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Do not surpass maximum speed
|
// Do not surpass maximum speed
|
||||||
const speed = me.vel.normSquared();
|
const speed = me.vel.normSquared();
|
||||||
|
|
||||||
|
@ -207,42 +269,35 @@ class Boids
|
||||||
*/
|
*/
|
||||||
_draw()
|
_draw()
|
||||||
{
|
{
|
||||||
const length = this.boids.length;
|
const boidsLength = this.boidsLength;
|
||||||
const transformed = this.registers[0];
|
const obstaclesLength = this.obstaclesLength;
|
||||||
|
|
||||||
this.ctx.clearRect(0, 0, this.width, this.height);
|
this.ctx.clearRect(
|
||||||
|
-this.width / 2, -this.height / 2,
|
||||||
|
this.width, this.height
|
||||||
|
);
|
||||||
|
|
||||||
// Draw each boid’s head following the angle of its course
|
// Draw obstacles
|
||||||
|
if (this.params.debug)
|
||||||
|
{
|
||||||
|
this.ctx.fillStyle = '#dddddd';
|
||||||
this.ctx.beginPath();
|
this.ctx.beginPath();
|
||||||
|
|
||||||
|
for (let i = 0; i < obstaclesLength; ++i)
|
||||||
|
{
|
||||||
|
this.obstacles[i].draw(this.ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw boids
|
||||||
this.ctx.fillStyle = this.params.color;
|
this.ctx.fillStyle = this.params.color;
|
||||||
|
this.ctx.beginPath();
|
||||||
|
|
||||||
for (let i = 0; i < length; ++i)
|
for (let i = 0; i < boidsLength; ++i)
|
||||||
{
|
{
|
||||||
const boid = this.boids[i];
|
this.boids[i].draw(this.ctx);
|
||||||
const angle = boid.vel.angle();
|
|
||||||
let isFirst = true;
|
|
||||||
|
|
||||||
for (let j = 0; j < this.boidShapeLength; ++j)
|
|
||||||
{
|
|
||||||
transformed.x = this.boidShape[j].x;
|
|
||||||
transformed.y = this.boidShape[j].y;
|
|
||||||
|
|
||||||
transformed
|
|
||||||
.rotate(angle)
|
|
||||||
.mul(this.params.radius)
|
|
||||||
.add(this.center)
|
|
||||||
.add(boid.pos);
|
|
||||||
|
|
||||||
if (isFirst)
|
|
||||||
{
|
|
||||||
this.ctx.moveTo(transformed.x, transformed.y);
|
|
||||||
isFirst = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.ctx.lineTo(transformed.x, transformed.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ctx.fill();
|
this.ctx.fill();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
class Vector
|
export class Vector
|
||||||
{
|
{
|
||||||
constructor(x = 0, y = 0)
|
constructor(x = 0, y = 0)
|
||||||
{
|
{
|
||||||
|
@ -82,4 +82,52 @@ class Vector
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Vector;
|
export class Rectangle
|
||||||
|
{
|
||||||
|
constructor(x, y, w, h)
|
||||||
|
{
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.w = w;
|
||||||
|
this.h = h;
|
||||||
|
this.center = new Vector(x + w / 2, y + h / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
intersect(point, radius)
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
point.x >= this.x - radius
|
||||||
|
&& point.x < this.x + this.w + radius
|
||||||
|
&& point.y >= this.y - radius
|
||||||
|
&& point.y < this.y + this.h + radius
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
draw(ctx)
|
||||||
|
{
|
||||||
|
ctx.rect(this.x, this.y, this.w, this.h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Circle
|
||||||
|
{
|
||||||
|
constructor(x, y, r)
|
||||||
|
{
|
||||||
|
this.r = r;
|
||||||
|
this.center = new Vector(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
intersect(point, radius)
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
Vector.distSquared(this.center, point)
|
||||||
|
<= (this.r + radius) * (this.r + radius)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
draw(ctx)
|
||||||
|
{
|
||||||
|
ctx.moveTo(this.center.x + this.r, this.center.y);
|
||||||
|
ctx.arc(this.center.x, this.center.y, this.r, 0, 2 * Math.PI, false);
|
||||||
|
}
|
||||||
|
}
|
10
index.html
10
index.html
|
@ -25,17 +25,21 @@
|
||||||
<canvas id="boids-canvas"></canvas>
|
<canvas id="boids-canvas"></canvas>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import Boids from './boids.mjs';
|
import Boids from './boids.mjs';
|
||||||
import Vector from './vector.mjs';
|
import {Circle, Rectangle, Vector} from './geometry.mjs';
|
||||||
|
|
||||||
const canvas = document.querySelector('#boids-canvas');
|
const canvas = document.querySelector('#boids-canvas');
|
||||||
const boids = new Boids(canvas);
|
const boids = new Boids(canvas, [
|
||||||
|
new Rectangle(-400, -200, 200, 150),
|
||||||
|
new Circle(200, 200, 100),
|
||||||
|
new Circle(300, 200, 100),
|
||||||
|
], {debug: true});
|
||||||
|
|
||||||
canvas.onclick = ev =>
|
canvas.onclick = ev =>
|
||||||
{
|
{
|
||||||
const pos = new Vector(ev.offsetX, ev.offsetY);
|
const pos = new Vector(ev.offsetX, ev.offsetY);
|
||||||
pos.sub(boids.center);
|
pos.sub(boids.center);
|
||||||
|
|
||||||
for (let i = 0; i < 100; ++i)
|
for (let i = 0; i < 500; ++i)
|
||||||
{
|
{
|
||||||
boids.add(pos);
|
boids.add(pos);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue