UNPKG

@rpgjs/physic

Version:

A deterministic 2D top-down physics library for RPG, sandbox and MMO games

98 lines (97 loc) 3.49 kB
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