ts-useful
Version:
Functions for animation, color transitions, ecliptic, bezier, decasteljau, curves, three dimensional curves, smooth scrolling, random range, randomItem, mobius index, vectors, physics vectors, and easing.
121 lines • 6.66 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Boids = void 0;
const randomrange_1 = require("./randomrange");
class Boids {
constructor(options) {
this.attractors = [];
this.boids = [];
this.defAttractor = { position: { x: Infinity, y: Infinity }, radius: 10, force: .25 };
this.deadPoint = { x: 0, y: 0 };
// double-dog-leg hypothenuse approximation
// http://forums.parallax.com/discussion/147522/dog-leg-hypotenuse-approximation
this.hypot = (a, b) => {
[a, b] = [Math.abs(a), Math.abs(b)];
const [n, x] = [Math.min(a, b), Math.max(a, b)];
return x + 3 * n / 32 + Math.max(0, 2 * n - x) / 8 + Math.max(0, 4 * n - x) / 16;
};
this.hypotCoord = (coord) => this.hypot(coord.x, coord.y);
this.sqr = (n) => n * n;
this.dSqr = (p) => this.sqr(p.x) + this.sqr(p.y);
this.ap = (p1, p2) => { return { x: p1.x + p2.x, y: p1.y + p2.y }; };
this.sp = (p1, p2) => { return { x: p1.x - p2.x, y: p1.y - p2.y }; };
this.mp = (p, m) => { return { x: p.x * m, y: p.y * m }; };
this.af = (m, p, fp) => {
const h = this.hypotCoord(fp);
p.x += (m * (fp.x / h)) || 0;
p.y += (m * (fp.y / h)) || 0;
return p;
};
this.sf = (m, p, fp) => {
const h = this.hypotCoord(fp);
p.x -= (m * (fp.x / h)) || 0;
p.y -= (m * (fp.y / h)) || 0;
return p;
};
// Update the attractors to the target position.
this.TargetPositions = (targets) => {
const defaults = { speed: this.speedLimit, distance: this.separationDistance };
this.attractors = targets.map((t) => {
return { ...this.defAttractor, ...t };
});
};
this.Update = async () => {
const { accelerationLimitRoot, accelerationLimit, alignmentDistance, alignmentForce, attractors, boids, cohesionDistance, cohesionForce, separationDistance, separationForce, speedLimitRoot, speedLimit } = this;
let size = boids.length;
let current = size;
let [target, attractorCount, ratio] = [0, attractors.length, 0];
while (current--) {
let [sforce, cforce, aforce, xforce, attractor] = [this.deadPoint, this.deadPoint, this.deadPoint, this.deadPoint, {}];
// Attractors
target = attractorCount;
while (target--) {
attractor = attractors[target];
xforce = this.sp(boids[current].position, attractor.position);
if (this.dSqr(xforce) < Math.sqrt(attractor.radius)) {
boids[current].speed = this.sf(attractor.force, boids[current].speed, xforce);
}
}
target = size;
while (target--) {
if (target === current)
continue;
xforce = this.sp(boids[current].position, boids[target].position);
const ds = this.dSqr(xforce);
if (ds < separationDistance) {
sforce = this.ap(sforce, xforce);
}
else {
if (ds < cohesionDistance) {
cforce = this.ap(cforce, xforce);
}
if (ds < alignmentDistance) {
aforce = this.ap(aforce, boids[target].speed);
}
}
}
boids[current].acceleration = this.af(separationForce, boids[current].acceleration, sforce); // Separation
boids[current].acceleration = this.sf(cohesionForce, boids[current].acceleration, cforce); // Cohesion
boids[current].acceleration = this.sf(alignmentForce, boids[current].acceleration, aforce); // Alignment
}
current = size;
// Apply speed/acceleration for this tick.
while (current--) {
if (accelerationLimit && this.dSqr(boids[current].acceleration) > accelerationLimit) {
ratio = accelerationLimitRoot / this.hypotCoord(boids[current].acceleration);
boids[current].acceleration = this.mp(boids[current].acceleration, ratio);
}
boids[current].speed = this.ap(boids[current].speed, boids[current].acceleration);
if (speedLimit && this.dSqr(boids[current].speed) > speedLimit) {
ratio = speedLimitRoot / this.hypotCoord(boids[current].speed);
boids[current].speed = this.mp(boids[current].speed, ratio);
}
boids[current].position = this.ap(boids[current].position, boids[current].speed);
}
return;
};
this.blankBoids = (count, position) => {
const rPos = () => (0, randomrange_1.RandomRange)(0, this.separationDistance * 2) - this.separationDistance;
const [defSpeed, defAcceleration] = [{ x: this.speedLimit, y: this.speedLimit }, { x: this.accelerationLimit, y: this.accelerationLimit }];
return Array.apply(null, new Array(count)).map(() => {
return { position: position ?? { x: rPos(), y: rPos() }, speed: defSpeed, acceleration: defAcceleration };
});
};
let { speedLimit, accelerationLimit, separationDistance, alignmentDistance, cohesionDistance, separationForce, cohesionForce, alignmentForce, attractors, alignment, boids } = (options ?? {});
boids ?? (boids = 50);
this.speedLimitRoot = (speedLimit || 0);
this.accelerationLimitRoot = (accelerationLimit || 1);
this.speedLimit = Math.pow(this.speedLimitRoot, 2);
this.accelerationLimit = Math.pow(this.accelerationLimitRoot, 2);
this.separationDistance = Math.pow(separationDistance || 60, 2);
this.alignmentDistance = Math.pow(alignmentDistance || 180, 2);
this.cohesionDistance = Math.pow(cohesionDistance || 180, 2);
this.separationForce = separationForce || .15;
this.cohesionForce = cohesionForce || .1;
this.alignmentForce = alignmentForce || alignment || .25;
this.attractors = attractors || [];
this.boids = this.blankBoids(boids);
}
}
exports.Boids = Boids;
//# sourceMappingURL=boids.js.map