UNPKG

@rpgjs/physic

Version:

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

142 lines (141 loc) 4.05 kB
import { AABB } from "./index4.js"; import { createCollider } from "./index18.js"; import { raycastCollider } from "./index20.js"; class BVHNode { constructor(bounds) { this.left = null; this.right = null; this.leaf = null; this.bounds = bounds; } } class BVH { constructor() { this.entities = /* @__PURE__ */ new Map(); this.root = null; } clear() { this.entities.clear(); this.root = null; } insert(entity) { const collider = createCollider(entity); if (!collider) return; const leaf = { entity, bounds: collider.getBounds() }; this.entities.set(entity, leaf); this.rebuild(); } remove(entity) { this.entities.delete(entity); this.rebuild(); } update(entity) { const leaf = this.entities.get(entity); const collider = createCollider(entity); if (!collider) return; const b = collider.getBounds(); if (!leaf || !equalAABB(leaf.bounds, b)) { this.entities.set(entity, { entity, bounds: b }); this.rebuild(); } } query(entity) { const collider = createCollider(entity); if (!collider) return /* @__PURE__ */ new Set(); const bounds = collider.getBounds(); const out = /* @__PURE__ */ new Set(); this.queryAABBInternal(bounds, out); out.delete(entity); return out; } queryAABB(bounds) { const out = /* @__PURE__ */ new Set(); this.queryAABBInternal(bounds, out); return out; } raycast(ray, mask, filter) { const origin = ray.origin; const end = ray.getPoint(ray.length); const bounds = new AABB( Math.min(origin.x, end.x), Math.min(origin.y, end.y), Math.max(origin.x, end.x), Math.max(origin.y, end.y) ); const candidates = this.queryAABB(bounds); let closestHit = null; for (const entity of candidates) { if (mask !== void 0 && (entity.collisionCategory & mask) === 0) continue; if (filter && !filter(entity)) continue; const collider = createCollider(entity); if (collider) { const hit = raycastCollider(collider, ray.origin, ray.direction, ray.length); if (hit) { if (!closestHit || hit.distance < closestHit.distance) { closestHit = hit; } } } } return closestHit; } queryAABBInternal(bounds, out) { if (!this.root) return; const stack = [this.root]; while (stack.length) { const n = stack.pop(); if (!n.bounds.intersects(bounds)) continue; if (n.leaf) { if (n.leaf.bounds.intersects(bounds)) out.add(n.leaf.entity); } else { if (n.left) stack.push(n.left); if (n.right) stack.push(n.right); } } } rebuild() { const leaves = Array.from(this.entities.values()); this.root = buildBVH(leaves); } } function buildBVH(leaves) { if (leaves.length === 0) return null; if (leaves.length === 1) { const leaf = leaves[0]; if (!leaf) return null; const n2 = new BVHNode(leaf.bounds); n2.leaf = leaf; return n2; } const firstLeaf = leaves[0]; if (!firstLeaf) return null; const bounds = leaves.slice(1).reduce((acc, l) => { if (!l) return acc; return acc.union(l.bounds); }, firstLeaf.bounds); const extentX = bounds.maxX - bounds.minX; const extentY = bounds.maxY - bounds.minY; const axis = extentX >= extentY ? 0 : 1; leaves.sort((a, b) => { if (!a || !b) return 0; return centerOf(a.bounds, axis) - centerOf(b.bounds, axis); }); const mid = Math.floor(leaves.length / 2); const left = buildBVH(leaves.slice(0, mid)); const right = buildBVH(leaves.slice(mid)); if (!left || !right) return null; const n = new BVHNode(left.bounds.union(right.bounds)); n.left = left; n.right = right; return n; } function centerOf(b, axis) { return axis === 0 ? (b.minX + b.maxX) * 0.5 : (b.minY + b.maxY) * 0.5; } function equalAABB(a, b) { return a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY; } export { BVH }; //# sourceMappingURL=index16.js.map