@relu-interactives/spatial-ecs 0.4.4 → 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.
@@ -7464,6 +7464,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
7464
7464
  const rig = world.getComponent(rigEntityId, XRRigComponent);
7465
7465
  const rigTransform = world.getComponent(rigEntityId, TransformComponent);
7466
7466
  if (!rig || !rigTransform) return;
7467
+ if (world.isXrPassthrough()) return;
7467
7468
  const inputs = this.inputSystem.getInputs();
7468
7469
  if (inputs.length === 0) return;
7469
7470
  if (rig.snapTurn) {
@@ -7726,6 +7727,32 @@ var _XRSessionSystem = class _XRSessionSystem {
7726
7727
  }
7727
7728
  if (presenting) {
7728
7729
  this.dispatchInputEdges(world);
7730
+ this.muteDepthOcclusionColor(world);
7731
+ }
7732
+ }
7733
+ /**
7734
+ * Stop three's depth-occlusion pass from writing COLOR.
7735
+ *
7736
+ * With depth sensing granted, three draws a full-screen, head-locked mesh
7737
+ * every frame whose fragment shader assigns ONLY `gl_FragDepth` — that is
7738
+ * the whole occlusion mechanism — but its material never sets
7739
+ * `colorWrite: false`, so the colour output is UNDEFINED per GLSL. Desktop
7740
+ * drivers happen to zero it, which is why this ships unnoticed; Quest's
7741
+ * mobile GPU frequently leaves the last-computed temporary in the register —
7742
+ * the sampled DEPTH value — so the pass paints the depth camera's view over
7743
+ * the screen as faint head-locked contour bands. On device that reads as "a
7744
+ * large wireframe dome that follows my head".
7745
+ *
7746
+ * The mesh is created lazily inside three's render, so this is re-checked
7747
+ * every frame rather than hooked once; the write is idempotent and the
7748
+ * check is two property reads. Depth-only is the mesh's entire purpose —
7749
+ * occlusion is unaffected.
7750
+ */
7751
+ muteDepthOcclusionColor(world) {
7752
+ const xr = world.getRenderer().xr;
7753
+ const material = xr.getDepthSensingMesh?.()?.material;
7754
+ if (material && material.colorWrite !== false) {
7755
+ material.colorWrite = false;
7729
7756
  }
7730
7757
  }
7731
7758
  handleSessionStart(world) {
@@ -7879,8 +7906,12 @@ function planeColliderFromPolygon(polygon) {
7879
7906
  __name(planeColliderFromPolygon, "planeColliderFromPolygon");
7880
7907
  function writeXrMeshGeometry(vertices, indices, existing) {
7881
7908
  const geometry = existing ?? new THREE19.BufferGeometry();
7882
- geometry.setAttribute("position", new THREE19.BufferAttribute(vertices, 3));
7883
- geometry.setIndex(new THREE19.BufferAttribute(indices, 1));
7909
+ geometry.setAttribute(
7910
+ "position",
7911
+ new THREE19.BufferAttribute(vertices.slice(), 3)
7912
+ );
7913
+ geometry.setIndex(new THREE19.BufferAttribute(indices.slice(), 1));
7914
+ geometry.deleteAttribute("normal");
7884
7915
  geometry.computeBoundingSphere();
7885
7916
  geometry.computeVertexNormals();
7886
7917
  return geometry;
@@ -7906,7 +7937,42 @@ __name(decomposeXrPose, "decomposeXrPose");
7906
7937
 
7907
7938
  // core/systems/XREnvironmentSystem.ts
7908
7939
  var DEBUG_COLOR = 5088255;
7940
+ function debugColorFor(kind, semanticLabel) {
7941
+ switch (semanticLabel) {
7942
+ case "floor":
7943
+ return 5100666;
7944
+ // green — the surface everything should rest on
7945
+ case "ceiling":
7946
+ return 11693055;
7947
+ // purple
7948
+ case "wall":
7949
+ case "wall face":
7950
+ return DEBUG_COLOR;
7951
+ // blue, the family's base colour
7952
+ case "global mesh":
7953
+ return 9079442;
7954
+ // dim gray — raw scan, not an object
7955
+ default:
7956
+ return semanticLabel ? 16747594 : DEBUG_COLOR;
7957
+ }
7958
+ }
7959
+ __name(debugColorFor, "debugColorFor");
7960
+ var GLOBAL_MESH_DEBUG_OPACITY = 0.35;
7961
+ var DEBUG_RENDER_ORDER = 900;
7909
7962
  var ROOM_CAPTURE_DELAY_SECONDS = 3;
7963
+ var SYNTHETIC_FLOOR_EXTENT = 50;
7964
+ var SYNTHETIC_FLOOR_PLANE = {
7965
+ planeSpace: {},
7966
+ polygon: [
7967
+ { x: -SYNTHETIC_FLOOR_EXTENT / 2, y: 0, z: -SYNTHETIC_FLOOR_EXTENT / 2 },
7968
+ { x: SYNTHETIC_FLOOR_EXTENT / 2, y: 0, z: -SYNTHETIC_FLOOR_EXTENT / 2 },
7969
+ { x: SYNTHETIC_FLOOR_EXTENT / 2, y: 0, z: SYNTHETIC_FLOOR_EXTENT / 2 },
7970
+ { x: -SYNTHETIC_FLOOR_EXTENT / 2, y: 0, z: SYNTHETIC_FLOOR_EXTENT / 2 }
7971
+ ],
7972
+ orientation: "horizontal",
7973
+ semanticLabel: "floor",
7974
+ lastChangedTime: 0
7975
+ };
7910
7976
  var _XREnvironmentSystem = class _XREnvironmentSystem {
7911
7977
  constructor() {
7912
7978
  /** Keyed by the XRPlane/XRMesh object itself — the runtime's own identity. */
@@ -7947,6 +8013,11 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
7947
8013
  const frame = xr.getFrame?.();
7948
8014
  const referenceSpace = xr.getReferenceSpace();
7949
8015
  if (!frame || !referenceSpace) return;
8016
+ if (frame.detectedPlanes === void 0 && frame.detectedMeshes === void 0) {
8017
+ this.ensureSyntheticFloor(world);
8018
+ return;
8019
+ }
8020
+ this.removeSyntheticFloor(world);
7950
8021
  const meshesAvailable = Boolean(frame.detectedMeshes);
7951
8022
  const useMesh = (mode === "mesh" || mode === "auto") && meshesAvailable;
7952
8023
  const usePlanes = mode === "planes" || mode === "auto" && !useMesh;
@@ -7993,6 +8064,51 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
7993
8064
  getDetectedCount() {
7994
8065
  return { planes: this.planes.size, meshes: this.meshes.size };
7995
8066
  }
8067
+ /**
8068
+ * A census of every surface currently spawned: what it is, where it sits and
8069
+ * roughly how big it is. Built for the template's post-session readout, so a
8070
+ * mystery shape on the headset can be looked up by its label instead of
8071
+ * guessed at — a small-radius entry at head height IS the mystery shape, and
8072
+ * a clean list means whatever was seen did not come from this system.
8073
+ */
8074
+ getDebugSnapshot(world) {
8075
+ const snapshot = [];
8076
+ for (const [plane, entry] of this.planes) {
8077
+ const transform = world.getComponent(entry.entityId, TransformComponent);
8078
+ const collider = planeColliderFromPolygon(plane.polygon);
8079
+ snapshot.push({
8080
+ kind: "plane",
8081
+ label: plane.semanticLabel ?? null,
8082
+ position: transform ? { ...transform.position } : { x: 0, y: 0, z: 0 },
8083
+ radius: collider ? Math.hypot(collider.size.x, collider.size.z) / 2 : 0,
8084
+ emulated: entry.emulated
8085
+ });
8086
+ }
8087
+ for (const [mesh, entry] of this.meshes) {
8088
+ const transform = world.getComponent(entry.entityId, TransformComponent);
8089
+ snapshot.push({
8090
+ kind: "mesh",
8091
+ label: mesh.semanticLabel ?? null,
8092
+ position: transform ? { ...transform.position } : { x: 0, y: 0, z: 0 },
8093
+ radius: entry.geometry?.boundingSphere?.radius ?? 0,
8094
+ emulated: entry.emulated
8095
+ });
8096
+ }
8097
+ return snapshot;
8098
+ }
8099
+ /** Create the no-scan floor once; it lives in the ordinary plane map. */
8100
+ ensureSyntheticFloor(world) {
8101
+ if (this.planes.has(SYNTHETIC_FLOOR_PLANE)) return;
8102
+ const created = this.createPlaneEntity(world, SYNTHETIC_FLOOR_PLANE);
8103
+ if (created) this.planes.set(SYNTHETIC_FLOOR_PLANE, created);
8104
+ }
8105
+ /** Real scene data exists (or arrived) — the stand-in must go. */
8106
+ removeSyntheticFloor(world) {
8107
+ const entry = this.planes.get(SYNTHETIC_FLOOR_PLANE);
8108
+ if (!entry) return;
8109
+ this.destroyEntry(world, entry);
8110
+ this.planes.delete(SYNTHETIC_FLOOR_PLANE);
8111
+ }
7996
8112
  findRig(world) {
7997
8113
  const ids = world.query(XRRigComponent);
7998
8114
  if (ids.length === 0) return null;
@@ -8010,9 +8126,11 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8010
8126
  for (const plane of detected) {
8011
8127
  const pose = frame.getPose(plane.planeSpace, referenceSpace);
8012
8128
  if (!pose) continue;
8129
+ const emulated = pose.emulatedPosition === true;
8013
8130
  let entry = this.planes.get(plane);
8014
8131
  const changed = plane.lastChangedTime ?? 0;
8015
8132
  if (!entry) {
8133
+ if (emulated) continue;
8016
8134
  const created = this.createPlaneEntity(world, plane);
8017
8135
  if (!created) continue;
8018
8136
  entry = created;
@@ -8025,11 +8143,17 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8025
8143
  if (collider) {
8026
8144
  entry.geometry?.dispose();
8027
8145
  entry.geometry = null;
8028
- this.attachPlaneDebug(world, entry, collider);
8146
+ this.attachPlaneDebug(
8147
+ world,
8148
+ entry,
8149
+ collider,
8150
+ plane.semanticLabel ?? null
8151
+ );
8029
8152
  }
8030
8153
  }
8031
8154
  }
8032
- this.applyPose(world, entry.entityId, pose);
8155
+ entry.emulated = emulated;
8156
+ if (!emulated) this.applyPose(world, entry.entityId, pose);
8033
8157
  }
8034
8158
  }
8035
8159
  syncMeshes(world, frame, referenceSpace) {
@@ -8044,9 +8168,11 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8044
8168
  for (const mesh of detected) {
8045
8169
  const pose = frame.getPose(mesh.meshSpace, referenceSpace);
8046
8170
  if (!pose) continue;
8171
+ const emulated = pose.emulatedPosition === true;
8047
8172
  let entry = this.meshes.get(mesh);
8048
8173
  const changed = mesh.lastChangedTime ?? 0;
8049
8174
  if (!entry) {
8175
+ if (emulated) continue;
8050
8176
  const created = this.createMeshEntity(world, mesh);
8051
8177
  if (!created) continue;
8052
8178
  entry = created;
@@ -8055,7 +8181,8 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8055
8181
  entry.lastChangedTime = changed;
8056
8182
  writeXrMeshGeometry(mesh.vertices, mesh.indices, entry.geometry);
8057
8183
  }
8058
- this.applyPose(world, entry.entityId, pose);
8184
+ entry.emulated = emulated;
8185
+ if (!emulated) this.applyPose(world, entry.entityId, pose);
8059
8186
  }
8060
8187
  }
8061
8188
  createPlaneEntity(world, plane) {
@@ -8075,9 +8202,12 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8075
8202
  entityId,
8076
8203
  lastChangedTime: plane.lastChangedTime ?? 0,
8077
8204
  geometry: null,
8078
- material: null
8205
+ material: null,
8206
+ emulated: false
8079
8207
  };
8080
- if (this.debugVisible) this.attachPlaneDebug(world, entry, collider);
8208
+ if (this.debugVisible) {
8209
+ this.attachPlaneDebug(world, entry, collider, plane.semanticLabel ?? null);
8210
+ }
8081
8211
  return entry;
8082
8212
  }
8083
8213
  applyPlaneCollider(world, entityId, plane) {
@@ -8102,15 +8232,36 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8102
8232
  createMeshEntity(world, mesh) {
8103
8233
  if (!mesh.vertices?.length || !mesh.indices?.length) return null;
8104
8234
  const geometry = writeXrMeshGeometry(mesh.vertices, mesh.indices);
8235
+ const label = mesh.semanticLabel ?? null;
8236
+ const isGlobalMesh = label === "global mesh";
8105
8237
  const material = new THREE19.MeshBasicMaterial({
8106
- color: DEBUG_COLOR,
8107
- wireframe: true
8238
+ color: debugColorFor("mesh", label),
8239
+ wireframe: true,
8240
+ // The global scan is a coarse triangle soup of the whole room; at full
8241
+ // strength it reads as phantom objects rather than as context.
8242
+ transparent: isGlobalMesh,
8243
+ opacity: isGlobalMesh ? GLOBAL_MESH_DEBUG_OPACITY : 1,
8244
+ // Depth-TESTED, deliberately. The overlay was X-ray for one release —
8245
+ // introduced on the theory that depth occlusion was carving it into a
8246
+ // head-locked dome — but that theory was falsified (the dome was the
8247
+ // depth mesh's unset colour output, plus emulated poses; both fixed).
8248
+ // X-ray had its own on-device failure: the global scan is DENSE, and a
8249
+ // dense wireframe at 0.35 opacity fuses into a translucent sheet — with
8250
+ // no depth test the walls' sheets drew through everything, stacked in
8251
+ // front of the user's eyes ("planes covering my face"). With the depth
8252
+ // buffer now holding CORRECT real-world depth, normal testing gives the
8253
+ // right picture: the near wall hides the far one. depthWrite stays off
8254
+ // so the overlay never occludes actual content.
8255
+ depthTest: true,
8256
+ depthWrite: false
8108
8257
  });
8109
8258
  const root = new THREE19.Group();
8110
8259
  const child = new THREE19.Mesh(geometry, material);
8111
8260
  child.visible = this.debugVisible;
8261
+ child.renderOrder = DEBUG_RENDER_ORDER;
8112
8262
  root.add(child);
8113
8263
  const entityId = world.createEntity();
8264
+ root.userData.entityId = entityId;
8114
8265
  world.addComponents(entityId, [
8115
8266
  new TransformComponent(),
8116
8267
  new Object3DRef(root),
@@ -8124,29 +8275,37 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8124
8275
  entityId,
8125
8276
  lastChangedTime: mesh.lastChangedTime ?? 0,
8126
8277
  geometry,
8127
- material
8278
+ material,
8279
+ emulated: false
8128
8280
  };
8129
8281
  }
8130
8282
  /** A thin box so a detected plane can be SEEN, not just collided with. */
8131
- attachPlaneDebug(world, entry, collider) {
8132
- const geometry = new THREE19.BoxGeometry(
8283
+ attachPlaneDebug(world, entry, collider, semanticLabel) {
8284
+ const box = new THREE19.BoxGeometry(
8133
8285
  collider.size.x,
8134
8286
  collider.size.y,
8135
8287
  collider.size.z
8136
8288
  );
8137
- const material = new THREE19.MeshBasicMaterial({
8138
- color: DEBUG_COLOR,
8139
- wireframe: true
8289
+ const geometry = new THREE19.EdgesGeometry(box);
8290
+ box.dispose();
8291
+ const material = new THREE19.LineBasicMaterial({
8292
+ color: debugColorFor("plane", semanticLabel),
8293
+ // Same depth policy as the mesh overlay — see createMeshEntity.
8294
+ depthTest: true,
8295
+ depthWrite: false
8140
8296
  });
8141
8297
  const root = new THREE19.Group();
8142
- const child = new THREE19.Mesh(geometry, material);
8298
+ const child = new THREE19.LineSegments(geometry, material);
8299
+ child.renderOrder = DEBUG_RENDER_ORDER;
8143
8300
  child.position.set(collider.offset.x, collider.offset.y, collider.offset.z);
8144
8301
  child.visible = this.debugVisible;
8145
8302
  root.add(child);
8146
8303
  entry.geometry = geometry;
8147
8304
  entry.material = material;
8305
+ root.userData.entityId = entry.entityId;
8148
8306
  const existing = world.getComponent(entry.entityId, Object3DRef);
8149
8307
  if (existing) {
8308
+ existing.object.removeFromParent();
8150
8309
  existing.object = root;
8151
8310
  } else {
8152
8311
  world.addComponent(entry.entityId, new Object3DRef(root));
@@ -8162,19 +8321,31 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8162
8321
  for (const [plane, entry] of this.planes) {
8163
8322
  if (entry.geometry) continue;
8164
8323
  const collider = planeColliderFromPolygon(plane.polygon);
8165
- if (collider) this.attachPlaneDebug(world, entry, collider);
8324
+ if (collider) {
8325
+ this.attachPlaneDebug(
8326
+ world,
8327
+ entry,
8328
+ collider,
8329
+ plane.semanticLabel ?? null
8330
+ );
8331
+ }
8166
8332
  }
8167
8333
  }
8168
8334
  for (const map of [this.planes, this.meshes]) {
8169
8335
  for (const entry of map.values()) {
8170
8336
  const ref = world.getComponent(entry.entityId, Object3DRef);
8171
8337
  ref?.object.traverse((child) => {
8172
- if (child.isMesh) child.visible = this.debugVisible;
8338
+ const drawable = child;
8339
+ if (drawable.isMesh || drawable.isLine) {
8340
+ child.visible = this.debugVisible;
8341
+ }
8173
8342
  });
8174
8343
  }
8175
8344
  }
8176
8345
  }
8177
8346
  destroyEntry(world, entry) {
8347
+ const ref = world.getComponent(entry.entityId, Object3DRef);
8348
+ ref?.object.removeFromParent();
8178
8349
  entry.geometry?.dispose();
8179
8350
  entry.material?.dispose();
8180
8351
  world.destroyEntity(entry.entityId);
@@ -8351,7 +8522,9 @@ var _XRAnchorSystem = class _XRAnchorSystem {
8351
8522
  continue;
8352
8523
  }
8353
8524
  const pose = frame.getPose(anchor.anchorSpace, referenceSpace);
8354
- if (pose) decomposeXrPose(pose.transform.matrix, transform);
8525
+ if (pose && pose.emulatedPosition !== true) {
8526
+ decomposeXrPose(pose.transform.matrix, transform);
8527
+ }
8355
8528
  }
8356
8529
  }
8357
8530
  /** Whether this entity's pose is currently being driven by an anchor. */
@@ -9641,5 +9814,5 @@ __name(_XRHoverVisualSystem, "XRHoverVisualSystem");
9641
9814
  var XRHoverVisualSystem = _XRHoverVisualSystem;
9642
9815
 
9643
9816
  export { ADDED_NODE_MARKER_COMPONENT, ARC_GRAVITY, ARC_MAX_TIME, ARC_SPEED, ARC_STEPS, AnimationSystem, AudioSystem, Behaviour, CURLED_COS, CameraSyncSystem, DEFAULT_DIRECTION_TOLERANCE, DEFAULT_ENTER_CONFIDENCE, DEFAULT_EXIT_CONFIDENCE, DEFAULT_LOST_GRACE_SECONDS, DEFAULT_MIN_HOLD_SECONDS, EnvironmentSyncSystem, FALLBACK_HAND_SCALE, HYDRATABLE_ENTITY_KINDS, HandPoseTracker, InputSystem, InstancedMeshSystem, KeyboardKeys, LightSyncSystem, MATCH_FALLOFF, MAX_PLAUSIBLE_SPEED, MAX_SAMPLE_AGE_SECONDS, MaterialSyncSystem, MeshGeometrySystem, NODE_KEY_SEPARATOR, NO_XR_CAPABILITIES, OVERRIDE_DIFF_EXCLUDED_COMPONENTS, OVERRIDE_ROOT_EXCLUDED_COMPONENTS, ObjectVisibilitySystem, PINCH_FINGERS, PREFAB_FILE_VERSION, PREFAB_FILE_VERSION_NESTED, PhysicsSyncSystem, PostprocessingSyncSystem, RenderSystem, SCRIPT_OBJECT_ROOT_FLAG, STRAIGHT_COS, ScriptBehaviourSystem, ScriptObject, ScriptObjectSystem, TeleportAimGate, ThrowVelocityTracker, TransformSyncSystem, VELOCITY_SAMPLES, VRTeleportAreaSystem, VideoSystem, XRAnchorSystem, XRControllerVisualSystem, XREnvironmentSystem, XRHandPoseSystem, XRHitTestSystem, XRHoverVisualSystem, XRInputSystem, XRInteractionSystem, XRLocomotionSystem, XRSessionSystem, XRTeleportVisualSystem, XR_FINGERS, XR_HOVER_SHELL_FLAG, angularVelocityFrom, applyHandMaterial, arcGroundCrossing, buildComponentValueMaps, buildNodeKeyMapsFromSnapshot, buildNodeMapsFromSnapshot, canonicalizeComponentValue, clearPrefabCache, collectDiffPaths, collectEntitySubtree, collectSnapshotSubtreeIds, computeFingerCurl, computeHandShape, computeInstanceOverrides, computePalmPose, computePalmWidth, computePinch, computeSpread, createHandMaterial, decanonicalizeComponentValue, despawnPrefab, encodeOverride, estimateHandScale, findSnapshotRootEntityIds, findTopLevelNestedRegions, groupOverridePaths, hydrateWorldFromSnapshot, hydrateWorldFromSnapshotDetailed, indexSnapshotByEntity, instantiateSubtree, loadPrefab, matchHandPose, mergeComponentValue, parseOverride, parsePrefabAsset, prefabFileVersionFor, readXrCapabilities, restoreComponentState, restorePostCreateComponents, sampleTeleportArc, setPrefabAssetUrlResolver, spawnPrefab };
9644
- //# sourceMappingURL=chunk-OY2OONRR.js.map
9645
- //# sourceMappingURL=chunk-OY2OONRR.js.map
9817
+ //# sourceMappingURL=chunk-QIIWFP3V.js.map
9818
+ //# sourceMappingURL=chunk-QIIWFP3V.js.map