@rpgjs/physic
Version:
A deterministic 2D top-down physics library for RPG, sandbox and MMO games
98 lines (97 loc) • 3.49 kB
JavaScript
import { AABB } from "./index4.js";
import { Vector2 } from "./index2.js";
const EPSILON = 1e-3;
class SeekAvoid {
/**
* @param engine - Physics engine used for spatial queries
* @param targetProvider - Function returning the target entity (or null)
* @param maxSpeed - Maximum speed in units per second
* @param repulseRadius - Radius in which obstacles apply repulsion
* @param repulseWeight - Strength of the repulsion force
* @param arriveRadius - Distance considered as arrival
*/
constructor(engine, targetProvider, maxSpeed = 2.5, repulseRadius = 2, repulseWeight = 4, arriveRadius = 0.5) {
this.engine = engine;
this.targetProvider = targetProvider;
this.maxSpeed = maxSpeed;
this.repulseRadius = repulseRadius;
this.repulseWeight = repulseWeight;
this.repulseRadiusSq = repulseRadius * repulseRadius;
this.arriveRadiusSq = arriveRadius * arriveRadius;
}
update(body, _dt) {
const entity = body.getEntity?.();
if (!entity) {
throw new Error("SeekAvoid requires a movement body backed by a physics entity.");
}
const target = this.targetProvider();
if (!target) {
body.setVelocity({ x: 0, y: 0 });
return;
}
const toTarget = new Vector2(target.position.x - entity.position.x, target.position.y - entity.position.y);
const distSq = toTarget.lengthSquared();
let arrived = false;
if (distSq <= this.arriveRadiusSq) {
toTarget.set(0, 0);
arrived = true;
} else if (distSq > 0) {
toTarget.divInPlace(Math.sqrt(distSq));
}
const bounds = AABB.fromCenterSize(entity.position.x, entity.position.y, this.repulseRadius * 2, this.repulseRadius * 2);
const neighbors = this.engine.queryAABB(bounds);
const push = new Vector2(0, 0);
if (!arrived) {
for (const other of neighbors) {
if (other === entity || other === target || other.isStatic()) {
continue;
}
const diff = new Vector2(entity.position.x - other.position.x, entity.position.y - other.position.y);
let d2 = diff.lengthSquared();
if (d2 > this.repulseRadiusSq) {
continue;
}
if (d2 < EPSILON) {
d2 = EPSILON;
}
const weight = this.repulseWeight / d2;
push.addInPlace(diff.mul(weight));
}
}
const pushLength = push.length();
const maxPush = this.maxSpeed * 0.5;
if (pushLength > maxPush && pushLength > 0) {
push.mulInPlace(maxPush / pushLength);
}
const desired = toTarget.mul(this.maxSpeed).add(push);
const desiredLength = desired.length();
const maxCombinedSpeed = this.maxSpeed * 1.5;
if (desiredLength > maxCombinedSpeed && desiredLength > 0) {
desired.mulInPlace(maxCombinedSpeed / desiredLength);
}
if (!Number.isFinite(desired.x) || !Number.isFinite(desired.y)) {
body.setVelocity({ x: 0, y: 0 });
return;
}
body.setVelocity(desired);
}
setParameters(maxSpeed, repulseRadius, repulseWeight, arriveRadius) {
if (maxSpeed !== void 0) {
this.maxSpeed = maxSpeed;
}
if (repulseRadius !== void 0) {
this.repulseRadius = repulseRadius;
this.repulseRadiusSq = repulseRadius * repulseRadius;
}
if (repulseWeight !== void 0) {
this.repulseWeight = repulseWeight;
}
if (arriveRadius !== void 0) {
this.arriveRadiusSq = arriveRadius * arriveRadius;
}
}
}
export {
SeekAvoid
};
//# sourceMappingURL=index41.js.map