@rpgjs/physic
Version:
A deterministic 2D top-down physics library for RPG, sandbox and MMO games
142 lines (141 loc) • 4.05 kB
JavaScript
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