Compare commits
	
		
			2 Commits
		
	
	
		
			32411933cd
			...
			639c56333e
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 639c56333e | |
|  | 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
 | ||||||
|         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(); | ||||||
|  |  | ||||||
|  | @ -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