-
-
Notifications
You must be signed in to change notification settings - Fork 663
3D Spatial Queries
Part of Working in 3D.
For "what entities are near me / in front of me / on the camera frustum", melonJS exposes a 3D spatial-query surface through the physics adapter:
const candidates = world.adapter.querySphere?.(sphere) ?? [];new Sphere(x, y, z, radius) is the geometry primitive for sphere queries and arcade 3D collision:
import { Sphere } from "melonjs";
const probe = new Sphere(0, 0, 0, 30);
probe.contains(p); // point-in-sphere
probe.overlaps(other); // sphere-vs-sphere
probe.overlapsAABB(aabb); // sphere-vs-AABB
probe.setShape(x, y, z, r); // reposition + resize in place (no alloc)Returns every renderable within the given sphere. Two call shapes — pick whichever reads better at the call site:
// Packaged form — sphere maintained alongside the entity
this.collider.pos.set(this.pos.x, this.pos.y, this.depth);
const nearby = world.adapter.querySphere?.(this.collider) ?? [];
// Loose form — one-off query
const nearby = world.adapter.querySphere?.(centerVec3, HIT_RADIUS) ?? [];Use it as a broadphase. The result is every renderable whose centre falls inside the sphere — your code does the per-entity narrow phase (kind filter, exact bounding-sphere test, etc.). On 2D adapters (matter, planck) the optional method is absent, so the ?? [] fallback runs.
Gotcha:
SpritedefaultsisKinematic = true, which means the broadphase skips it. Flip tofalseat spawn for any entity you want findable viaquerySphere:bullet.isKinematic = false;
3D ray cast. Returns the nearest RaycastHit3d (with renderable, point: Vector3d, normal: Vector3d, fraction) or null. Each renderable is treated as a bounding sphere using the circumradius of its 2D bounds.
const hit = world.adapter.raycast3d?.(eye.pos, target.pos);
if (hit) {
console.log("hit", hit.renderable, "at fraction", hit.fraction);
}Capability-gated by adapter.capabilities.raycasts3d. Builtin adapter supports it under Camera3d; matter/planck don't (raycasts3d: false).
Bulk frustum cull — returns every renderable whose octant overlaps the current view frustum. Use as a broadphase pass before per-renderable narrow culling on dense 3D scenes (hundreds+ of items):
const visible = camera.queryVisible(app.world);
for (const r of visible) {
if (camera.isVisible(r)) {
// draw / process
}
}Returns [] under a 2D camera — safe to call unconditionally.
Two lower-level types back the queries above; you rarely construct them directly, but they're exported for code that needs to reason about 3D regions:
-
AABB3d— minimal 3D axis-aligned bounding box (min,max,contains,overlaps,overlapsSphere,isFinite, …). TheOctree's bounding primitive; pass one toworld.adapter.queryAABBfor a 3D region query. -
Frustum— the perspective view volume (fov/aspect/near/far+ projection matrix) thatCamera3dbuilds internally andqueryVisibletests octants against. Exposed on the camera ascamera.frustum.