Skip to content

Commit 44cec90

Browse files
committed
tonic!
0 parents  commit 44cec90

16 files changed

+4487
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
web_modules

.npmignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
index.html
2+
demo.js
3+
web_modules
4+
screenshot.gif

Boid.js

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { vec3 } from "gl-matrix";
2+
import { limit } from "./utils.js";
3+
4+
export default class Boid {
5+
constructor(behaviors) {
6+
this.position = vec3.create();
7+
this.velocity = vec3.create();
8+
this.acceleration = vec3.create();
9+
this.target = vec3.create();
10+
11+
this.behaviors = behaviors || [];
12+
}
13+
14+
applyForce(force) {
15+
vec3.add(this.acceleration, this.acceleration, force);
16+
}
17+
18+
applyBehaviors({ boids, maxSpeed, maxForce, center, bounds }) {
19+
for (let i = 0; i < this.behaviors.length; i++) {
20+
const behavior = this.behaviors[i];
21+
if (behavior.enabled === false) continue;
22+
23+
const force = behavior.fn({
24+
boids,
25+
maxSpeed,
26+
maxForce: this.maxForce || maxForce,
27+
center,
28+
bounds,
29+
position: this.position,
30+
velocity: this.velocity,
31+
target: this.target,
32+
...behavior.options
33+
});
34+
35+
if (force) {
36+
limit(force, force, this.maxForce || maxForce);
37+
if (behavior.scale) vec3.scale(force, force, behavior.scale || 1);
38+
39+
this.applyForce(force);
40+
}
41+
}
42+
}
43+
44+
update(dt, maxSpeed) {
45+
vec3.scaleAndAdd(this.velocity, this.velocity, this.acceleration, dt);
46+
limit(this.velocity, this.velocity, maxSpeed);
47+
vec3.scaleAndAdd(this.position, this.position, this.velocity, dt);
48+
vec3.set(this.acceleration, 0, 0, 0);
49+
}
50+
}

LICENSE.md

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (C) 2020 the internet and Damien Seguin
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in
11+
all copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

Obstacle.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default class Obstacle {
2+
constructor(position, radius) {
3+
this.position = position;
4+
this.radius = radius;
5+
}
6+
}

Path.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default class Path {
2+
constructor(points, radius) {
3+
this.points = points;
4+
this.radius = radius;
5+
}
6+
}

README.md

+203
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# bird-oid [![stable](http://badges.github.io/stability-badges/dist/stable.svg)](http://github.com/badges/stability-badges)
2+
3+
[![npm version](https://badge.fury.io/js/bird-oid.svg)](https://www.npmjs.com/package/bird-oid)
4+
[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
5+
6+
A 3D boid system with accompanying emergent behaviors. Implementation mostly based on Craig Reynolds paper Steering Behaviors For Autonomous Characters.
7+
8+
![](https://raw.githubusercontent.com/dmnsgn/bird-oid/master/screenshot.gif)
9+
10+
## Installation
11+
12+
```bash
13+
npm install bird-oid
14+
```
15+
16+
[![NPM](https://nodei.co/npm/bird-oid.png)](https://nodei.co/npm/bird-oid/)
17+
18+
## Usage
19+
20+
See [demo](https://dmnsgn.github.io/bird-oid/).
21+
22+
```js
23+
import { System as BoidSystem, behaviors } from "bird-oid";
24+
25+
const BOIDS_COUNT = 100;
26+
const WANDER_SPEED = 2;
27+
28+
const system = new BoidSystem({
29+
maxSpeed: 0.3,
30+
maxForce: 0.4
31+
});
32+
33+
const boidBehaviors = [
34+
{
35+
fn: behaviors.wander,
36+
options: {
37+
distance: system.scale * 0.04,
38+
radius: system.scale * 0.3,
39+
theta: 0,
40+
phi: 0.3
41+
}
42+
},
43+
{
44+
enabled: true,
45+
fn: behaviors.boundsConstrain,
46+
scale: 2
47+
},
48+
{
49+
enabled: true,
50+
fn: behaviors.boundsWrapConstrain
51+
}
52+
];
53+
54+
for (let i = 0; i < BOIDS_COUNT; i++) {
55+
const boid = new Boid(boidBehaviors);
56+
57+
// Position in the bounds
58+
boid.position = system.getRandomPosition();
59+
60+
// Add initial velocity
61+
boid.velocity = [
62+
Math.random() * 0.5 * system.maxSpeed,
63+
Math.random() * 0.5 * system.maxSpeed,
64+
Math.random() * 0.5 * system.maxSpeed
65+
];
66+
system.addBoid(boid);
67+
}
68+
69+
const frame = () => {
70+
const dt = Math.min(0.1, myClock.getDelta());
71+
72+
// Wander requires angle update
73+
boidBehaviors[0].options.theta +=
74+
Math.random() * WANDER_SPEED - WANDER_SPEED * 0.5;
75+
boidBehaviors[0].options.phi +=
76+
Math.random() * WANDER_SPEED - WANDER_SPEED * 0.5;
77+
78+
system.update(dt);
79+
80+
requestAnimationFrame(frame);
81+
};
82+
83+
requestAnimationFrame(() => {
84+
frame();
85+
});
86+
```
87+
88+
## API
89+
90+
### `new System({ scale: number, maxSpeed: number, maxForce: number, center: [number, number, number], bounds: [number, number, number] })`
91+
92+
Handle common boids update and global state.
93+
94+
| Option | Default | Description |
95+
| -------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
96+
| **options.scale** | 1 | A global scale for the system. |
97+
| **options.maxSpeed** | scale | A maximum speed for the boids in the system. Can be tweaked individually via `boid.maxSpeed`. |
98+
| **options.maxForce** | scale | A maximum force for each behavior of a boid. Can be tweaked individually via `boid.maxForce` or via `behaviors.options.maxForce`. |
99+
| **options.center** | [0, 0, 0] | A center point for the system. |
100+
| **options.bounds** | [scale, scale, scale] | Positives bounds x/y/z for the system expanding from the center point. |
101+
102+
#### `system.addBoid(boid: Boid): void`
103+
104+
Push a new boid to the system.
105+
106+
#### `system.getRandomPosition(): [number, number, number]`
107+
108+
Get a position within the system's bounds.
109+
110+
### `new Boid(behaviors)`
111+
112+
```ts
113+
type BehaviorObject = {
114+
fn: Function;
115+
enabled: Boolean;
116+
scale: number;
117+
options: Object;
118+
};
119+
```
120+
121+
| Option | Default | Description |
122+
| ------------- | ---------------- | ------------------------------------------- |
123+
| **behaviors** | BehaviorObject[] | An array of behaviors to apply to the boid. |
124+
125+
The following method are usually handled by the System.
126+
127+
#### `boid.applyForce(force: [number, number, number]): void`
128+
129+
Add a force to the boid's acceleration vector. If you need mass, you can either use behaviors.scale or override.
130+
131+
#### `boid.applyBehaviors({ boids: Boid[], maxSpeed: number, maxForce: number, center: [number, number, number], bounds: [number, number, number] }): void`
132+
133+
Compute all the behaviors specified in `boid.behaviors` and apply them via applyForce. Arguments usually come from the system and can be overridden via `behavior.options`.
134+
135+
#### `boid.update(dt: number, maxSpeed: number): void`
136+
137+
Update a boid's position according to its current acceleration/velocity and reset acceleration. Usually called consecutively to `boid.applyBehaviors`.
138+
139+
### `new Path(points: Array<[number, number, number]>, radius: number)`
140+
141+
Used for path following behavior.
142+
143+
| Option | Default | Description |
144+
| ---------- | ------- | ----------------------------------------------------------------- |
145+
| **points** | | An array of 3d points. |
146+
| **radius** | | The radius of a path (see it as transforming the path to a tube). |
147+
148+
### `new Obstacle(points: Array<[number, number, number]>, radius: number)`
149+
150+
Used for obstacle avoidance behavior.
151+
152+
| Option | Default | Description |
153+
| ------------ | ------- | --------------------------- |
154+
| **position** | | The center of the obstacle. |
155+
| **radius** | | The radius of the sphere. |
156+
157+
### `behaviors`
158+
159+
Use these function as `fn` property of the `BehaviorObject` passed to a `Boid`.
160+
161+
#### `seek({ position, target, velocity, maxSpeed })`
162+
163+
#### `flee({ position, target, velocity, maxSpeed })`
164+
165+
#### `pursue({position, target, velocity, targetVelocity, maxSpeed })`
166+
167+
#### `evade({position, target, velocity, targetVelocity, maxSpeed })`
168+
169+
#### `arrive({ position, target, velocity, maxSpeed, radius })`
170+
171+
#### `avoidObstacles({ position, velocity, obstacles, maxSpeed, maxAvoidForce, fixedDistance = false })`
172+
173+
#### `wander({ velocity, distance, radius, theta, phi })`
174+
175+
#### `followPath({ path, position, velocity, maxSpeed, fixedDistance = false })`
176+
177+
#### `followFlowFieldSimple({ flowField, position, velocity, maxSpeed })`
178+
179+
#### `followFlowField({ flowField, position, velocity, maxSpeed, fixedDistance = false })`
180+
181+
#### `followLeaderSimple({ leader, position, velocity, maxSpeed, distance, radius })`
182+
183+
#### `followLeader({ leader, position, velocity, maxSpeed, distance, radius, evadeScale = 1, arriveScale = 1 })`
184+
185+
#### `separate({ boids, maxDistance, position, velocity, maxSpeed })`
186+
187+
#### `cohere({ boids, maxDistance, position, velocity, maxSpeed })`
188+
189+
#### `align({ boids, maxDistance, position, velocity, maxSpeed })`
190+
191+
#### `flock({ boids, maxDistance, position, velocity, maxSpeed })`
192+
193+
#### `boundsConstrain({ center, bounds, position, velocity, maxSpeed, maxForce, fixedDistance = false })`
194+
195+
#### `sphereConstrain({ center, radius, position, velocity, maxSpeed, maxForce, fixedDistance = false })`
196+
197+
#### `boundsWrapConstrain({ position, center, bounds })`
198+
199+
#### `sphereWrapConstrain({ position, center, radius })`
200+
201+
## License
202+
203+
MIT. See [license file](https://github.com/dmnsgn/bird-oid/blob/master/LICENSE.md).

System.js

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
export default class System {
2+
constructor(options) {
3+
this.boids = [];
4+
this.scale = 1;
5+
6+
this.maxSpeed = this.scale;
7+
this.maxForce = this.scale;
8+
9+
this.center = [0, 0, 0];
10+
this.bounds = [this.scale, this.scale, this.scale];
11+
12+
Object.assign(this, options);
13+
}
14+
15+
getRandomPosition() {
16+
return [
17+
(2 * Math.random() - 1) * this.bounds[0] * 0.5 + this.center[0],
18+
(2 * Math.random() - 1) * this.bounds[1] * 0.5 + this.center[1],
19+
(2 * Math.random() - 1) * this.bounds[2] * 0.5 + this.center[2]
20+
];
21+
}
22+
23+
addBoid(boid) {
24+
this.boids.push(boid);
25+
}
26+
27+
update(dt) {
28+
for (let i = 0; i < this.boids.length; i++) {
29+
const boid = this.boids[i];
30+
boid.applyBehaviors({
31+
boids: this.boids,
32+
maxSpeed: this.maxSpeed,
33+
maxForce: this.maxForce,
34+
center: this.center,
35+
bounds: this.bounds
36+
});
37+
boid.update(dt, boid.maxSpeed || this.maxSpeed);
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)