Compare commits

...

2 Commits

Author SHA1 Message Date
Mattéo Delabre 639c56333e
Guard obstacle drawing behind flag 2020-05-08 18:00:56 +02:00
Mattéo Delabre cc3a719542
Add obstacles 2020-05-08 17:42:15 +02:00
3 changed files with 161 additions and 54 deletions

153
boids.mjs
View File

@ -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 boids head following the angle of its course // Draw obstacles
this.ctx.beginPath(); if (this.params.debug)
this.ctx.fillStyle = this.params.color;
for (let i = 0; i < length; ++i)
{ {
const boid = this.boids[i]; this.ctx.fillStyle = '#dddddd';
const angle = boid.vel.angle(); this.ctx.beginPath();
let isFirst = true;
for (let j = 0; j < this.boidShapeLength; ++j) for (let i = 0; i < obstaclesLength; ++i)
{ {
transformed.x = this.boidShape[j].x; this.obstacles[i].draw(this.ctx);
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();
}
// Draw boids
this.ctx.fillStyle = this.params.color;
this.ctx.beginPath();
for (let i = 0; i < boidsLength; ++i)
{
this.boids[i].draw(this.ctx);
} }
this.ctx.fill(); this.ctx.fill();

View File

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

View File

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