@relu-interactives/spatial-ecs 0.5.0 → 0.5.1

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.
@@ -736,6 +736,10 @@ declare class XREnvironmentSystem implements EcsSystem {
736
736
  private roomCaptureRequested;
737
737
  /** This frame's inverse viewer matrix, or null when unavailable. */
738
738
  private viewerInverse;
739
+ /** This frame's viewer position in reference space, for room election. */
740
+ private viewerPos;
741
+ /** The ONE global room mesh currently allowed to spawn. */
742
+ private electedGlobalMesh;
739
743
  private readonly tmpViewerMatrix;
740
744
  private readonly tmpViewPos;
741
745
  update(world: World, deltaTime?: number): void;
@@ -786,6 +790,13 @@ declare class XREnvironmentSystem implements EcsSystem {
786
790
  private findRig;
787
791
  private syncPlanes;
788
792
  private syncMeshes;
793
+ /**
794
+ * Pick THE room's global mesh: the one whose scan origin is nearest the
795
+ * viewer, sticky to the current choice within a margin so origin jitter
796
+ * can't flap the room. Without a viewer pose (or with a single candidate)
797
+ * the first candidate stands; ties are impossible to break anyway.
798
+ */
799
+ private electGlobalMesh;
789
800
  /**
790
801
  * Admission control for a NOT-yet-spawned surface. With a viewer pose
791
802
  * available, the surface must behave world-anchored for a few frames before
@@ -736,6 +736,10 @@ declare class XREnvironmentSystem implements EcsSystem {
736
736
  private roomCaptureRequested;
737
737
  /** This frame's inverse viewer matrix, or null when unavailable. */
738
738
  private viewerInverse;
739
+ /** This frame's viewer position in reference space, for room election. */
740
+ private viewerPos;
741
+ /** The ONE global room mesh currently allowed to spawn. */
742
+ private electedGlobalMesh;
739
743
  private readonly tmpViewerMatrix;
740
744
  private readonly tmpViewPos;
741
745
  update(world: World, deltaTime?: number): void;
@@ -786,6 +790,13 @@ declare class XREnvironmentSystem implements EcsSystem {
786
790
  private findRig;
787
791
  private syncPlanes;
788
792
  private syncMeshes;
793
+ /**
794
+ * Pick THE room's global mesh: the one whose scan origin is nearest the
795
+ * viewer, sticky to the current choice within a margin so origin jitter
796
+ * can't flap the room. Without a viewer pose (or with a single candidate)
797
+ * the first candidate stands; ties are impossible to break anyway.
798
+ */
799
+ private electGlobalMesh;
789
800
  /**
790
801
  * Admission control for a NOT-yet-spawned surface. With a viewer pose
791
802
  * available, the surface must behave world-anchored for a few frames before
package/dist/index.cjs CHANGED
@@ -12978,6 +12978,11 @@ function debugColorFor(kind, semanticLabel) {
12978
12978
  }
12979
12979
  __name(debugColorFor, "debugColorFor");
12980
12980
  var GLOBAL_MESH_DEBUG_OPACITY = 0.35;
12981
+ var GLOBAL_MESH_SWITCH_MARGIN = 1.5;
12982
+ function isGlobalMeshSurface(mesh) {
12983
+ return (mesh.semanticLabel ?? null) === "global mesh";
12984
+ }
12985
+ __name(isGlobalMeshSurface, "isGlobalMeshSurface");
12981
12986
  var DEBUG_RENDER_ORDER = 900;
12982
12987
  var ROOM_CAPTURE_DELAY_SECONDS = 3;
12983
12988
  var SYNTHETIC_FLOOR_EXTENT = 50;
@@ -13016,6 +13021,10 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
13016
13021
  this.roomCaptureRequested = false;
13017
13022
  /** This frame's inverse viewer matrix, or null when unavailable. */
13018
13023
  this.viewerInverse = null;
13024
+ /** This frame's viewer position in reference space, for room election. */
13025
+ this.viewerPos = null;
13026
+ /** The ONE global room mesh currently allowed to spawn. */
13027
+ this.electedGlobalMesh = null;
13019
13028
  this.tmpViewerMatrix = new THREE56__namespace.Matrix4();
13020
13029
  this.tmpViewPos = new THREE56__namespace.Vector3();
13021
13030
  }
@@ -13056,7 +13065,14 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
13056
13065
  const useMesh = (mode === "mesh" || mode === "auto") && meshesAvailable;
13057
13066
  const usePlanes = mode === "planes" || mode === "auto" && !useMesh;
13058
13067
  const viewer = frame.getViewerPose?.(referenceSpace);
13059
- this.viewerInverse = viewer ? this.tmpViewerMatrix.fromArray(viewer.transform.matrix).invert() : null;
13068
+ if (viewer) {
13069
+ const vm = viewer.transform.matrix;
13070
+ this.viewerPos = { x: vm[12], y: vm[13], z: vm[14] };
13071
+ this.viewerInverse = this.tmpViewerMatrix.fromArray(vm).invert();
13072
+ } else {
13073
+ this.viewerPos = null;
13074
+ this.viewerInverse = null;
13075
+ }
13060
13076
  if (usePlanes) {
13061
13077
  this.syncPlanes(world, frame, referenceSpace, deltaTime);
13062
13078
  } else if (this.planes.size > 0) {
@@ -13211,7 +13227,17 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
13211
13227
  for (const mesh of this.pendingMeshes.keys()) {
13212
13228
  if (!detected.has(mesh)) this.pendingMeshes.delete(mesh);
13213
13229
  }
13230
+ const electedGlobal = this.electGlobalMesh(frame, detected, referenceSpace);
13214
13231
  for (const mesh of detected) {
13232
+ if (isGlobalMeshSurface(mesh) && mesh !== electedGlobal) {
13233
+ const stale = this.meshes.get(mesh);
13234
+ if (stale) {
13235
+ this.destroyEntry(world, stale);
13236
+ this.meshes.delete(mesh);
13237
+ }
13238
+ this.pendingMeshes.delete(mesh);
13239
+ continue;
13240
+ }
13215
13241
  const pose = frame.getPose(mesh.meshSpace, referenceSpace);
13216
13242
  if (!pose) continue;
13217
13243
  const emulated = pose.emulatedPosition === true;
@@ -13235,6 +13261,45 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
13235
13261
  if (allowed) this.applyPose(world, entry.entityId, pose);
13236
13262
  }
13237
13263
  }
13264
+ /**
13265
+ * Pick THE room's global mesh: the one whose scan origin is nearest the
13266
+ * viewer, sticky to the current choice within a margin so origin jitter
13267
+ * can't flap the room. Without a viewer pose (or with a single candidate)
13268
+ * the first candidate stands; ties are impossible to break anyway.
13269
+ */
13270
+ electGlobalMesh(frame, detected, referenceSpace) {
13271
+ let nearest = null;
13272
+ let nearestDist = Number.POSITIVE_INFINITY;
13273
+ let currentDist = Number.POSITIVE_INFINITY;
13274
+ let currentStillDetected = false;
13275
+ for (const mesh of detected) {
13276
+ if (!isGlobalMeshSurface(mesh)) continue;
13277
+ if (!this.viewerPos) {
13278
+ return this.electedGlobalMesh && detected.has(this.electedGlobalMesh) ? this.electedGlobalMesh : mesh;
13279
+ }
13280
+ const pose = frame.getPose(mesh.meshSpace, referenceSpace);
13281
+ if (!pose) continue;
13282
+ const m = pose.transform.matrix;
13283
+ const d = Math.hypot(
13284
+ m[12] - this.viewerPos.x,
13285
+ m[13] - this.viewerPos.y,
13286
+ m[14] - this.viewerPos.z
13287
+ );
13288
+ if (mesh === this.electedGlobalMesh) {
13289
+ currentDist = d;
13290
+ currentStillDetected = true;
13291
+ }
13292
+ if (d < nearestDist) {
13293
+ nearestDist = d;
13294
+ nearest = mesh;
13295
+ }
13296
+ }
13297
+ if (currentStillDetected && currentDist < nearestDist + GLOBAL_MESH_SWITCH_MARGIN) {
13298
+ return this.electedGlobalMesh;
13299
+ }
13300
+ this.electedGlobalMesh = nearest;
13301
+ return nearest;
13302
+ }
13238
13303
  /**
13239
13304
  * Admission control for a NOT-yet-spawned surface. With a viewer pose
13240
13305
  * available, the surface must behave world-anchored for a few frames before
@@ -13446,6 +13511,7 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
13446
13511
  this.clearMap(world, this.meshes);
13447
13512
  this.pendingPlanes.clear();
13448
13513
  this.pendingMeshes.clear();
13514
+ this.electedGlobalMesh = null;
13449
13515
  }
13450
13516
  dispose(world) {
13451
13517
  this.clearAll(world);