@relu-interactives/spatial-ecs 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-6UZC6M7V.js → chunk-QIIWFP3V.js} +244 -31
- package/dist/chunk-QIIWFP3V.js.map +1 -0
- package/dist/docs/guide/mixed-reality.md +160 -3
- package/dist/docs/guide/vr-and-hand-poses.md +29 -0
- package/dist/docs/guide/world-api.md +1 -1
- package/dist/{index-n7SeH119.d.ts → index-BphKHaV-.d.ts} +65 -1
- package/dist/{index-CBdnIyI8.d.cts → index-CH7qMsQT.d.cts} +65 -1
- package/dist/index.cjs +242 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/systems/index.cjs +242 -29
- package/dist/systems/index.cjs.map +1 -1
- package/dist/systems/index.d.cts +1 -1
- package/dist/systems/index.d.ts +1 -1
- package/dist/systems/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-6UZC6M7V.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -12059,6 +12059,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
|
|
|
12059
12059
|
const rig = world.getComponent(rigEntityId, XRRigComponent);
|
|
12060
12060
|
const rigTransform = world.getComponent(rigEntityId, TransformComponent);
|
|
12061
12061
|
if (!rig || !rigTransform) return;
|
|
12062
|
+
if (world.isXrPassthrough()) return;
|
|
12062
12063
|
const inputs = this.inputSystem.getInputs();
|
|
12063
12064
|
if (inputs.length === 0) return;
|
|
12064
12065
|
if (rig.snapTurn) {
|
|
@@ -12305,6 +12306,8 @@ var _XRSessionSystem = class _XRSessionSystem {
|
|
|
12305
12306
|
this.capabilities = NO_XR_CAPABILITIES;
|
|
12306
12307
|
/** Y compensation applied when a floor-relative space was requested but not granted. */
|
|
12307
12308
|
this.appliedOriginOffset = 0;
|
|
12309
|
+
/** The live session, so an async floor probe can tell if it has been replaced. */
|
|
12310
|
+
this.session = null;
|
|
12308
12311
|
this.inputSystem = inputSystem ?? null;
|
|
12309
12312
|
}
|
|
12310
12313
|
update(world) {
|
|
@@ -12319,10 +12322,37 @@ var _XRSessionSystem = class _XRSessionSystem {
|
|
|
12319
12322
|
}
|
|
12320
12323
|
if (presenting) {
|
|
12321
12324
|
this.dispatchInputEdges(world);
|
|
12325
|
+
this.muteDepthOcclusionColor(world);
|
|
12326
|
+
}
|
|
12327
|
+
}
|
|
12328
|
+
/**
|
|
12329
|
+
* Stop three's depth-occlusion pass from writing COLOR.
|
|
12330
|
+
*
|
|
12331
|
+
* With depth sensing granted, three draws a full-screen, head-locked mesh
|
|
12332
|
+
* every frame whose fragment shader assigns ONLY `gl_FragDepth` — that is
|
|
12333
|
+
* the whole occlusion mechanism — but its material never sets
|
|
12334
|
+
* `colorWrite: false`, so the colour output is UNDEFINED per GLSL. Desktop
|
|
12335
|
+
* drivers happen to zero it, which is why this ships unnoticed; Quest's
|
|
12336
|
+
* mobile GPU frequently leaves the last-computed temporary in the register —
|
|
12337
|
+
* the sampled DEPTH value — so the pass paints the depth camera's view over
|
|
12338
|
+
* the screen as faint head-locked contour bands. On device that reads as "a
|
|
12339
|
+
* large wireframe dome that follows my head".
|
|
12340
|
+
*
|
|
12341
|
+
* The mesh is created lazily inside three's render, so this is re-checked
|
|
12342
|
+
* every frame rather than hooked once; the write is idempotent and the
|
|
12343
|
+
* check is two property reads. Depth-only is the mesh's entire purpose —
|
|
12344
|
+
* occlusion is unaffected.
|
|
12345
|
+
*/
|
|
12346
|
+
muteDepthOcclusionColor(world) {
|
|
12347
|
+
const xr = world.getRenderer().xr;
|
|
12348
|
+
const material = xr.getDepthSensingMesh?.()?.material;
|
|
12349
|
+
if (material && material.colorWrite !== false) {
|
|
12350
|
+
material.colorWrite = false;
|
|
12322
12351
|
}
|
|
12323
12352
|
}
|
|
12324
12353
|
handleSessionStart(world) {
|
|
12325
12354
|
const session = world.getRenderer().xr.getSession();
|
|
12355
|
+
this.session = session ?? null;
|
|
12326
12356
|
const mode = session && session.environmentBlendMode !== "opaque" ? "immersive-ar" : "immersive-vr";
|
|
12327
12357
|
this.capabilities = readXrCapabilities(session ?? null, mode);
|
|
12328
12358
|
if (session && this.inputSystem) {
|
|
@@ -12361,16 +12391,16 @@ var _XRSessionSystem = class _XRSessionSystem {
|
|
|
12361
12391
|
renderer.xr.setReferenceSpaceType(wantsFloor ? "local-floor" : "local");
|
|
12362
12392
|
} catch {
|
|
12363
12393
|
}
|
|
12364
|
-
|
|
12365
|
-
|
|
12366
|
-
|
|
12367
|
-
|
|
12368
|
-
|
|
12369
|
-
|
|
12370
|
-
|
|
12371
|
-
|
|
12394
|
+
this.appliedOriginOffset = 0;
|
|
12395
|
+
if (!wantsFloor || !session) return;
|
|
12396
|
+
const sessionAtRequest = session;
|
|
12397
|
+
session.requestReferenceSpace("local-floor").then(() => {
|
|
12398
|
+
}).catch(() => {
|
|
12399
|
+
if (this.session !== sessionAtRequest) return;
|
|
12400
|
+
if (this.appliedOriginOffset !== 0) return;
|
|
12401
|
+
this.appliedOriginOffset = rig.fallbackEyeHeight;
|
|
12372
12402
|
transform.position.y -= this.appliedOriginOffset;
|
|
12373
|
-
}
|
|
12403
|
+
});
|
|
12374
12404
|
}
|
|
12375
12405
|
handleSessionEnd(world) {
|
|
12376
12406
|
if (this.appliedOriginOffset !== 0) {
|
|
@@ -12381,6 +12411,7 @@ var _XRSessionSystem = class _XRSessionSystem {
|
|
|
12381
12411
|
}
|
|
12382
12412
|
this.appliedOriginOffset = 0;
|
|
12383
12413
|
}
|
|
12414
|
+
this.session = null;
|
|
12384
12415
|
this.capabilities = NO_XR_CAPABILITIES;
|
|
12385
12416
|
this.inputSystem?.detachSession();
|
|
12386
12417
|
this.forEachBehaviour(world, (behaviour) => {
|
|
@@ -12470,8 +12501,12 @@ function planeColliderFromPolygon(polygon) {
|
|
|
12470
12501
|
__name(planeColliderFromPolygon, "planeColliderFromPolygon");
|
|
12471
12502
|
function writeXrMeshGeometry(vertices, indices, existing) {
|
|
12472
12503
|
const geometry = existing ?? new THREE51__namespace.BufferGeometry();
|
|
12473
|
-
geometry.setAttribute(
|
|
12474
|
-
|
|
12504
|
+
geometry.setAttribute(
|
|
12505
|
+
"position",
|
|
12506
|
+
new THREE51__namespace.BufferAttribute(vertices.slice(), 3)
|
|
12507
|
+
);
|
|
12508
|
+
geometry.setIndex(new THREE51__namespace.BufferAttribute(indices.slice(), 1));
|
|
12509
|
+
geometry.deleteAttribute("normal");
|
|
12475
12510
|
geometry.computeBoundingSphere();
|
|
12476
12511
|
geometry.computeVertexNormals();
|
|
12477
12512
|
return geometry;
|
|
@@ -12497,6 +12532,42 @@ __name(decomposeXrPose, "decomposeXrPose");
|
|
|
12497
12532
|
|
|
12498
12533
|
// core/systems/XREnvironmentSystem.ts
|
|
12499
12534
|
var DEBUG_COLOR = 5088255;
|
|
12535
|
+
function debugColorFor(kind, semanticLabel) {
|
|
12536
|
+
switch (semanticLabel) {
|
|
12537
|
+
case "floor":
|
|
12538
|
+
return 5100666;
|
|
12539
|
+
// green — the surface everything should rest on
|
|
12540
|
+
case "ceiling":
|
|
12541
|
+
return 11693055;
|
|
12542
|
+
// purple
|
|
12543
|
+
case "wall":
|
|
12544
|
+
case "wall face":
|
|
12545
|
+
return DEBUG_COLOR;
|
|
12546
|
+
// blue, the family's base colour
|
|
12547
|
+
case "global mesh":
|
|
12548
|
+
return 9079442;
|
|
12549
|
+
// dim gray — raw scan, not an object
|
|
12550
|
+
default:
|
|
12551
|
+
return semanticLabel ? 16747594 : DEBUG_COLOR;
|
|
12552
|
+
}
|
|
12553
|
+
}
|
|
12554
|
+
__name(debugColorFor, "debugColorFor");
|
|
12555
|
+
var GLOBAL_MESH_DEBUG_OPACITY = 0.35;
|
|
12556
|
+
var DEBUG_RENDER_ORDER = 900;
|
|
12557
|
+
var ROOM_CAPTURE_DELAY_SECONDS = 3;
|
|
12558
|
+
var SYNTHETIC_FLOOR_EXTENT = 50;
|
|
12559
|
+
var SYNTHETIC_FLOOR_PLANE = {
|
|
12560
|
+
planeSpace: {},
|
|
12561
|
+
polygon: [
|
|
12562
|
+
{ x: -SYNTHETIC_FLOOR_EXTENT / 2, y: 0, z: -SYNTHETIC_FLOOR_EXTENT / 2 },
|
|
12563
|
+
{ x: SYNTHETIC_FLOOR_EXTENT / 2, y: 0, z: -SYNTHETIC_FLOOR_EXTENT / 2 },
|
|
12564
|
+
{ x: SYNTHETIC_FLOOR_EXTENT / 2, y: 0, z: SYNTHETIC_FLOOR_EXTENT / 2 },
|
|
12565
|
+
{ x: -SYNTHETIC_FLOOR_EXTENT / 2, y: 0, z: SYNTHETIC_FLOOR_EXTENT / 2 }
|
|
12566
|
+
],
|
|
12567
|
+
orientation: "horizontal",
|
|
12568
|
+
semanticLabel: "floor",
|
|
12569
|
+
lastChangedTime: 0
|
|
12570
|
+
};
|
|
12500
12571
|
var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
12501
12572
|
constructor() {
|
|
12502
12573
|
/** Keyed by the XRPlane/XRMesh object itself — the runtime's own identity. */
|
|
@@ -12504,17 +12575,24 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
12504
12575
|
this.meshes = /* @__PURE__ */ new Map();
|
|
12505
12576
|
this.presenting = false;
|
|
12506
12577
|
this.debugVisible = false;
|
|
12578
|
+
/** Seconds presenting, used to decide the room has genuinely nothing in it. */
|
|
12579
|
+
this.elapsed = 0;
|
|
12580
|
+
/** `initiateRoomCapture` may be called only ONCE per session. */
|
|
12581
|
+
this.roomCaptureRequested = false;
|
|
12507
12582
|
}
|
|
12508
|
-
update(world) {
|
|
12583
|
+
update(world, deltaTime = 0) {
|
|
12509
12584
|
const presenting = world.isXrPresenting();
|
|
12510
12585
|
if (!presenting) {
|
|
12511
12586
|
if (this.presenting) {
|
|
12512
12587
|
this.presenting = false;
|
|
12588
|
+
this.elapsed = 0;
|
|
12589
|
+
this.roomCaptureRequested = false;
|
|
12513
12590
|
this.clearAll(world);
|
|
12514
12591
|
}
|
|
12515
12592
|
return;
|
|
12516
12593
|
}
|
|
12517
12594
|
this.presenting = true;
|
|
12595
|
+
this.elapsed += deltaTime;
|
|
12518
12596
|
const rig = this.findRig(world);
|
|
12519
12597
|
const mode = rig?.environmentPhysics ?? "none";
|
|
12520
12598
|
if (mode === "none") {
|
|
@@ -12530,6 +12608,11 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
12530
12608
|
const frame = xr.getFrame?.();
|
|
12531
12609
|
const referenceSpace = xr.getReferenceSpace();
|
|
12532
12610
|
if (!frame || !referenceSpace) return;
|
|
12611
|
+
if (frame.detectedPlanes === void 0 && frame.detectedMeshes === void 0) {
|
|
12612
|
+
this.ensureSyntheticFloor(world);
|
|
12613
|
+
return;
|
|
12614
|
+
}
|
|
12615
|
+
this.removeSyntheticFloor(world);
|
|
12533
12616
|
const meshesAvailable = Boolean(frame.detectedMeshes);
|
|
12534
12617
|
const useMesh = (mode === "mesh" || mode === "auto") && meshesAvailable;
|
|
12535
12618
|
const usePlanes = mode === "planes" || mode === "auto" && !useMesh;
|
|
@@ -12543,11 +12626,84 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
12543
12626
|
} else if (this.meshes.size > 0) {
|
|
12544
12627
|
this.clearMap(world, this.meshes);
|
|
12545
12628
|
}
|
|
12629
|
+
this.maybeCaptureRoom(world, frame);
|
|
12630
|
+
}
|
|
12631
|
+
/**
|
|
12632
|
+
* Ask the runtime to scan the room when it turns out we have no room.
|
|
12633
|
+
*
|
|
12634
|
+
* Plane and mesh detection do NOT scan anything — they replay a scan the user
|
|
12635
|
+
* captured earlier through the system's own room setup. Most people have
|
|
12636
|
+
* never run it, and those who have may have run it in a DIFFERENT room, so a
|
|
12637
|
+
* mixed-reality experience opens against either nothing at all or someone
|
|
12638
|
+
* else's furniture, with no prompt and no error.
|
|
12639
|
+
*
|
|
12640
|
+
* `XRSession.initiateRoomCapture()` (Meta's extension) launches that setup
|
|
12641
|
+
* from inside the session, and exists precisely for this case. Contract, from
|
|
12642
|
+
* Meta's docs: callable ONCE per session, and only worth calling once we are
|
|
12643
|
+
* confident the room really is empty — the arrays populate a beat after the
|
|
12644
|
+
* session starts, so a check on frame one would fire for everyone.
|
|
12645
|
+
*/
|
|
12646
|
+
maybeCaptureRoom(world, frame) {
|
|
12647
|
+
if (this.roomCaptureRequested) return;
|
|
12648
|
+
if (this.elapsed < ROOM_CAPTURE_DELAY_SECONDS) return;
|
|
12649
|
+
if (this.planes.size > 0 || this.meshes.size > 0) return;
|
|
12650
|
+
if ((frame.detectedPlanes?.size ?? 0) > 0) return;
|
|
12651
|
+
if ((frame.detectedMeshes?.size ?? 0) > 0) return;
|
|
12652
|
+
const session = world.getRenderer().xr.getSession?.();
|
|
12653
|
+
if (typeof session?.initiateRoomCapture !== "function") return;
|
|
12654
|
+
this.roomCaptureRequested = true;
|
|
12655
|
+
session.initiateRoomCapture()?.catch(() => {
|
|
12656
|
+
});
|
|
12546
12657
|
}
|
|
12547
12658
|
/** How many pieces of real geometry are currently colliding. Script-facing. */
|
|
12548
12659
|
getDetectedCount() {
|
|
12549
12660
|
return { planes: this.planes.size, meshes: this.meshes.size };
|
|
12550
12661
|
}
|
|
12662
|
+
/**
|
|
12663
|
+
* A census of every surface currently spawned: what it is, where it sits and
|
|
12664
|
+
* roughly how big it is. Built for the template's post-session readout, so a
|
|
12665
|
+
* mystery shape on the headset can be looked up by its label instead of
|
|
12666
|
+
* guessed at — a small-radius entry at head height IS the mystery shape, and
|
|
12667
|
+
* a clean list means whatever was seen did not come from this system.
|
|
12668
|
+
*/
|
|
12669
|
+
getDebugSnapshot(world) {
|
|
12670
|
+
const snapshot = [];
|
|
12671
|
+
for (const [plane, entry] of this.planes) {
|
|
12672
|
+
const transform = world.getComponent(entry.entityId, TransformComponent);
|
|
12673
|
+
const collider = planeColliderFromPolygon(plane.polygon);
|
|
12674
|
+
snapshot.push({
|
|
12675
|
+
kind: "plane",
|
|
12676
|
+
label: plane.semanticLabel ?? null,
|
|
12677
|
+
position: transform ? { ...transform.position } : { x: 0, y: 0, z: 0 },
|
|
12678
|
+
radius: collider ? Math.hypot(collider.size.x, collider.size.z) / 2 : 0,
|
|
12679
|
+
emulated: entry.emulated
|
|
12680
|
+
});
|
|
12681
|
+
}
|
|
12682
|
+
for (const [mesh, entry] of this.meshes) {
|
|
12683
|
+
const transform = world.getComponent(entry.entityId, TransformComponent);
|
|
12684
|
+
snapshot.push({
|
|
12685
|
+
kind: "mesh",
|
|
12686
|
+
label: mesh.semanticLabel ?? null,
|
|
12687
|
+
position: transform ? { ...transform.position } : { x: 0, y: 0, z: 0 },
|
|
12688
|
+
radius: entry.geometry?.boundingSphere?.radius ?? 0,
|
|
12689
|
+
emulated: entry.emulated
|
|
12690
|
+
});
|
|
12691
|
+
}
|
|
12692
|
+
return snapshot;
|
|
12693
|
+
}
|
|
12694
|
+
/** Create the no-scan floor once; it lives in the ordinary plane map. */
|
|
12695
|
+
ensureSyntheticFloor(world) {
|
|
12696
|
+
if (this.planes.has(SYNTHETIC_FLOOR_PLANE)) return;
|
|
12697
|
+
const created = this.createPlaneEntity(world, SYNTHETIC_FLOOR_PLANE);
|
|
12698
|
+
if (created) this.planes.set(SYNTHETIC_FLOOR_PLANE, created);
|
|
12699
|
+
}
|
|
12700
|
+
/** Real scene data exists (or arrived) — the stand-in must go. */
|
|
12701
|
+
removeSyntheticFloor(world) {
|
|
12702
|
+
const entry = this.planes.get(SYNTHETIC_FLOOR_PLANE);
|
|
12703
|
+
if (!entry) return;
|
|
12704
|
+
this.destroyEntry(world, entry);
|
|
12705
|
+
this.planes.delete(SYNTHETIC_FLOOR_PLANE);
|
|
12706
|
+
}
|
|
12551
12707
|
findRig(world) {
|
|
12552
12708
|
const ids = world.query(XRRigComponent);
|
|
12553
12709
|
if (ids.length === 0) return null;
|
|
@@ -12565,9 +12721,11 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
12565
12721
|
for (const plane of detected) {
|
|
12566
12722
|
const pose = frame.getPose(plane.planeSpace, referenceSpace);
|
|
12567
12723
|
if (!pose) continue;
|
|
12724
|
+
const emulated = pose.emulatedPosition === true;
|
|
12568
12725
|
let entry = this.planes.get(plane);
|
|
12569
12726
|
const changed = plane.lastChangedTime ?? 0;
|
|
12570
12727
|
if (!entry) {
|
|
12728
|
+
if (emulated) continue;
|
|
12571
12729
|
const created = this.createPlaneEntity(world, plane);
|
|
12572
12730
|
if (!created) continue;
|
|
12573
12731
|
entry = created;
|
|
@@ -12580,11 +12738,17 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
12580
12738
|
if (collider) {
|
|
12581
12739
|
entry.geometry?.dispose();
|
|
12582
12740
|
entry.geometry = null;
|
|
12583
|
-
this.attachPlaneDebug(
|
|
12741
|
+
this.attachPlaneDebug(
|
|
12742
|
+
world,
|
|
12743
|
+
entry,
|
|
12744
|
+
collider,
|
|
12745
|
+
plane.semanticLabel ?? null
|
|
12746
|
+
);
|
|
12584
12747
|
}
|
|
12585
12748
|
}
|
|
12586
12749
|
}
|
|
12587
|
-
|
|
12750
|
+
entry.emulated = emulated;
|
|
12751
|
+
if (!emulated) this.applyPose(world, entry.entityId, pose);
|
|
12588
12752
|
}
|
|
12589
12753
|
}
|
|
12590
12754
|
syncMeshes(world, frame, referenceSpace) {
|
|
@@ -12599,9 +12763,11 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
12599
12763
|
for (const mesh of detected) {
|
|
12600
12764
|
const pose = frame.getPose(mesh.meshSpace, referenceSpace);
|
|
12601
12765
|
if (!pose) continue;
|
|
12766
|
+
const emulated = pose.emulatedPosition === true;
|
|
12602
12767
|
let entry = this.meshes.get(mesh);
|
|
12603
12768
|
const changed = mesh.lastChangedTime ?? 0;
|
|
12604
12769
|
if (!entry) {
|
|
12770
|
+
if (emulated) continue;
|
|
12605
12771
|
const created = this.createMeshEntity(world, mesh);
|
|
12606
12772
|
if (!created) continue;
|
|
12607
12773
|
entry = created;
|
|
@@ -12610,7 +12776,8 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
12610
12776
|
entry.lastChangedTime = changed;
|
|
12611
12777
|
writeXrMeshGeometry(mesh.vertices, mesh.indices, entry.geometry);
|
|
12612
12778
|
}
|
|
12613
|
-
|
|
12779
|
+
entry.emulated = emulated;
|
|
12780
|
+
if (!emulated) this.applyPose(world, entry.entityId, pose);
|
|
12614
12781
|
}
|
|
12615
12782
|
}
|
|
12616
12783
|
createPlaneEntity(world, plane) {
|
|
@@ -12630,9 +12797,12 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
12630
12797
|
entityId,
|
|
12631
12798
|
lastChangedTime: plane.lastChangedTime ?? 0,
|
|
12632
12799
|
geometry: null,
|
|
12633
|
-
material: null
|
|
12800
|
+
material: null,
|
|
12801
|
+
emulated: false
|
|
12634
12802
|
};
|
|
12635
|
-
if (this.debugVisible)
|
|
12803
|
+
if (this.debugVisible) {
|
|
12804
|
+
this.attachPlaneDebug(world, entry, collider, plane.semanticLabel ?? null);
|
|
12805
|
+
}
|
|
12636
12806
|
return entry;
|
|
12637
12807
|
}
|
|
12638
12808
|
applyPlaneCollider(world, entityId, plane) {
|
|
@@ -12657,15 +12827,36 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
12657
12827
|
createMeshEntity(world, mesh) {
|
|
12658
12828
|
if (!mesh.vertices?.length || !mesh.indices?.length) return null;
|
|
12659
12829
|
const geometry = writeXrMeshGeometry(mesh.vertices, mesh.indices);
|
|
12830
|
+
const label = mesh.semanticLabel ?? null;
|
|
12831
|
+
const isGlobalMesh = label === "global mesh";
|
|
12660
12832
|
const material = new THREE51__namespace.MeshBasicMaterial({
|
|
12661
|
-
color:
|
|
12662
|
-
wireframe: true
|
|
12833
|
+
color: debugColorFor("mesh", label),
|
|
12834
|
+
wireframe: true,
|
|
12835
|
+
// The global scan is a coarse triangle soup of the whole room; at full
|
|
12836
|
+
// strength it reads as phantom objects rather than as context.
|
|
12837
|
+
transparent: isGlobalMesh,
|
|
12838
|
+
opacity: isGlobalMesh ? GLOBAL_MESH_DEBUG_OPACITY : 1,
|
|
12839
|
+
// Depth-TESTED, deliberately. The overlay was X-ray for one release —
|
|
12840
|
+
// introduced on the theory that depth occlusion was carving it into a
|
|
12841
|
+
// head-locked dome — but that theory was falsified (the dome was the
|
|
12842
|
+
// depth mesh's unset colour output, plus emulated poses; both fixed).
|
|
12843
|
+
// X-ray had its own on-device failure: the global scan is DENSE, and a
|
|
12844
|
+
// dense wireframe at 0.35 opacity fuses into a translucent sheet — with
|
|
12845
|
+
// no depth test the walls' sheets drew through everything, stacked in
|
|
12846
|
+
// front of the user's eyes ("planes covering my face"). With the depth
|
|
12847
|
+
// buffer now holding CORRECT real-world depth, normal testing gives the
|
|
12848
|
+
// right picture: the near wall hides the far one. depthWrite stays off
|
|
12849
|
+
// so the overlay never occludes actual content.
|
|
12850
|
+
depthTest: true,
|
|
12851
|
+
depthWrite: false
|
|
12663
12852
|
});
|
|
12664
12853
|
const root = new THREE51__namespace.Group();
|
|
12665
12854
|
const child = new THREE51__namespace.Mesh(geometry, material);
|
|
12666
12855
|
child.visible = this.debugVisible;
|
|
12856
|
+
child.renderOrder = DEBUG_RENDER_ORDER;
|
|
12667
12857
|
root.add(child);
|
|
12668
12858
|
const entityId = world.createEntity();
|
|
12859
|
+
root.userData.entityId = entityId;
|
|
12669
12860
|
world.addComponents(entityId, [
|
|
12670
12861
|
new TransformComponent(),
|
|
12671
12862
|
new Object3DRef(root),
|
|
@@ -12679,29 +12870,37 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
12679
12870
|
entityId,
|
|
12680
12871
|
lastChangedTime: mesh.lastChangedTime ?? 0,
|
|
12681
12872
|
geometry,
|
|
12682
|
-
material
|
|
12873
|
+
material,
|
|
12874
|
+
emulated: false
|
|
12683
12875
|
};
|
|
12684
12876
|
}
|
|
12685
12877
|
/** A thin box so a detected plane can be SEEN, not just collided with. */
|
|
12686
|
-
attachPlaneDebug(world, entry, collider) {
|
|
12687
|
-
const
|
|
12878
|
+
attachPlaneDebug(world, entry, collider, semanticLabel) {
|
|
12879
|
+
const box = new THREE51__namespace.BoxGeometry(
|
|
12688
12880
|
collider.size.x,
|
|
12689
12881
|
collider.size.y,
|
|
12690
12882
|
collider.size.z
|
|
12691
12883
|
);
|
|
12692
|
-
const
|
|
12693
|
-
|
|
12694
|
-
|
|
12884
|
+
const geometry = new THREE51__namespace.EdgesGeometry(box);
|
|
12885
|
+
box.dispose();
|
|
12886
|
+
const material = new THREE51__namespace.LineBasicMaterial({
|
|
12887
|
+
color: debugColorFor("plane", semanticLabel),
|
|
12888
|
+
// Same depth policy as the mesh overlay — see createMeshEntity.
|
|
12889
|
+
depthTest: true,
|
|
12890
|
+
depthWrite: false
|
|
12695
12891
|
});
|
|
12696
12892
|
const root = new THREE51__namespace.Group();
|
|
12697
|
-
const child = new THREE51__namespace.
|
|
12893
|
+
const child = new THREE51__namespace.LineSegments(geometry, material);
|
|
12894
|
+
child.renderOrder = DEBUG_RENDER_ORDER;
|
|
12698
12895
|
child.position.set(collider.offset.x, collider.offset.y, collider.offset.z);
|
|
12699
12896
|
child.visible = this.debugVisible;
|
|
12700
12897
|
root.add(child);
|
|
12701
12898
|
entry.geometry = geometry;
|
|
12702
12899
|
entry.material = material;
|
|
12900
|
+
root.userData.entityId = entry.entityId;
|
|
12703
12901
|
const existing = world.getComponent(entry.entityId, Object3DRef);
|
|
12704
12902
|
if (existing) {
|
|
12903
|
+
existing.object.removeFromParent();
|
|
12705
12904
|
existing.object = root;
|
|
12706
12905
|
} else {
|
|
12707
12906
|
world.addComponent(entry.entityId, new Object3DRef(root));
|
|
@@ -12717,19 +12916,31 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
12717
12916
|
for (const [plane, entry] of this.planes) {
|
|
12718
12917
|
if (entry.geometry) continue;
|
|
12719
12918
|
const collider = planeColliderFromPolygon(plane.polygon);
|
|
12720
|
-
if (collider)
|
|
12919
|
+
if (collider) {
|
|
12920
|
+
this.attachPlaneDebug(
|
|
12921
|
+
world,
|
|
12922
|
+
entry,
|
|
12923
|
+
collider,
|
|
12924
|
+
plane.semanticLabel ?? null
|
|
12925
|
+
);
|
|
12926
|
+
}
|
|
12721
12927
|
}
|
|
12722
12928
|
}
|
|
12723
12929
|
for (const map of [this.planes, this.meshes]) {
|
|
12724
12930
|
for (const entry of map.values()) {
|
|
12725
12931
|
const ref = world.getComponent(entry.entityId, Object3DRef);
|
|
12726
12932
|
ref?.object.traverse((child) => {
|
|
12727
|
-
|
|
12933
|
+
const drawable = child;
|
|
12934
|
+
if (drawable.isMesh || drawable.isLine) {
|
|
12935
|
+
child.visible = this.debugVisible;
|
|
12936
|
+
}
|
|
12728
12937
|
});
|
|
12729
12938
|
}
|
|
12730
12939
|
}
|
|
12731
12940
|
}
|
|
12732
12941
|
destroyEntry(world, entry) {
|
|
12942
|
+
const ref = world.getComponent(entry.entityId, Object3DRef);
|
|
12943
|
+
ref?.object.removeFromParent();
|
|
12733
12944
|
entry.geometry?.dispose();
|
|
12734
12945
|
entry.material?.dispose();
|
|
12735
12946
|
world.destroyEntity(entry.entityId);
|
|
@@ -12906,7 +13117,9 @@ var _XRAnchorSystem = class _XRAnchorSystem {
|
|
|
12906
13117
|
continue;
|
|
12907
13118
|
}
|
|
12908
13119
|
const pose = frame.getPose(anchor.anchorSpace, referenceSpace);
|
|
12909
|
-
if (pose
|
|
13120
|
+
if (pose && pose.emulatedPosition !== true) {
|
|
13121
|
+
decomposeXrPose(pose.transform.matrix, transform);
|
|
13122
|
+
}
|
|
12910
13123
|
}
|
|
12911
13124
|
}
|
|
12912
13125
|
/** Whether this entity's pose is currently being driven by an anchor. */
|