@relu-interactives/spatial-ecs 0.5.0 → 0.5.2

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
@@ -6042,7 +6042,12 @@ var HYDRATABLE_ENTITY_KINDS = /* @__PURE__ */ new Set([
6042
6042
  "scriptObject",
6043
6043
  "xrRig",
6044
6044
  "xrInteractor",
6045
- "teleportArea"
6045
+ "teleportArea",
6046
+ "uiPanel",
6047
+ "uiText",
6048
+ "uiButton",
6049
+ "uiToggle",
6050
+ "uiSlider"
6046
6051
  ]);
6047
6052
 
6048
6053
  // core/hydration/hydrateWorld.ts
@@ -12978,6 +12983,11 @@ function debugColorFor(kind, semanticLabel) {
12978
12983
  }
12979
12984
  __name(debugColorFor, "debugColorFor");
12980
12985
  var GLOBAL_MESH_DEBUG_OPACITY = 0.35;
12986
+ var GLOBAL_MESH_SWITCH_MARGIN = 1.5;
12987
+ function isGlobalMeshSurface(mesh) {
12988
+ return (mesh.semanticLabel ?? null) === "global mesh";
12989
+ }
12990
+ __name(isGlobalMeshSurface, "isGlobalMeshSurface");
12981
12991
  var DEBUG_RENDER_ORDER = 900;
12982
12992
  var ROOM_CAPTURE_DELAY_SECONDS = 3;
12983
12993
  var SYNTHETIC_FLOOR_EXTENT = 50;
@@ -13016,6 +13026,10 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
13016
13026
  this.roomCaptureRequested = false;
13017
13027
  /** This frame's inverse viewer matrix, or null when unavailable. */
13018
13028
  this.viewerInverse = null;
13029
+ /** This frame's viewer position in reference space, for room election. */
13030
+ this.viewerPos = null;
13031
+ /** The ONE global room mesh currently allowed to spawn. */
13032
+ this.electedGlobalMesh = null;
13019
13033
  this.tmpViewerMatrix = new THREE56__namespace.Matrix4();
13020
13034
  this.tmpViewPos = new THREE56__namespace.Vector3();
13021
13035
  }
@@ -13056,7 +13070,14 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
13056
13070
  const useMesh = (mode === "mesh" || mode === "auto") && meshesAvailable;
13057
13071
  const usePlanes = mode === "planes" || mode === "auto" && !useMesh;
13058
13072
  const viewer = frame.getViewerPose?.(referenceSpace);
13059
- this.viewerInverse = viewer ? this.tmpViewerMatrix.fromArray(viewer.transform.matrix).invert() : null;
13073
+ if (viewer) {
13074
+ const vm = viewer.transform.matrix;
13075
+ this.viewerPos = { x: vm[12], y: vm[13], z: vm[14] };
13076
+ this.viewerInverse = this.tmpViewerMatrix.fromArray(vm).invert();
13077
+ } else {
13078
+ this.viewerPos = null;
13079
+ this.viewerInverse = null;
13080
+ }
13060
13081
  if (usePlanes) {
13061
13082
  this.syncPlanes(world, frame, referenceSpace, deltaTime);
13062
13083
  } else if (this.planes.size > 0) {
@@ -13211,7 +13232,17 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
13211
13232
  for (const mesh of this.pendingMeshes.keys()) {
13212
13233
  if (!detected.has(mesh)) this.pendingMeshes.delete(mesh);
13213
13234
  }
13235
+ const electedGlobal = this.electGlobalMesh(frame, detected, referenceSpace);
13214
13236
  for (const mesh of detected) {
13237
+ if (isGlobalMeshSurface(mesh) && mesh !== electedGlobal) {
13238
+ const stale = this.meshes.get(mesh);
13239
+ if (stale) {
13240
+ this.destroyEntry(world, stale);
13241
+ this.meshes.delete(mesh);
13242
+ }
13243
+ this.pendingMeshes.delete(mesh);
13244
+ continue;
13245
+ }
13215
13246
  const pose = frame.getPose(mesh.meshSpace, referenceSpace);
13216
13247
  if (!pose) continue;
13217
13248
  const emulated = pose.emulatedPosition === true;
@@ -13235,6 +13266,45 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
13235
13266
  if (allowed) this.applyPose(world, entry.entityId, pose);
13236
13267
  }
13237
13268
  }
13269
+ /**
13270
+ * Pick THE room's global mesh: the one whose scan origin is nearest the
13271
+ * viewer, sticky to the current choice within a margin so origin jitter
13272
+ * can't flap the room. Without a viewer pose (or with a single candidate)
13273
+ * the first candidate stands; ties are impossible to break anyway.
13274
+ */
13275
+ electGlobalMesh(frame, detected, referenceSpace) {
13276
+ let nearest = null;
13277
+ let nearestDist = Number.POSITIVE_INFINITY;
13278
+ let currentDist = Number.POSITIVE_INFINITY;
13279
+ let currentStillDetected = false;
13280
+ for (const mesh of detected) {
13281
+ if (!isGlobalMeshSurface(mesh)) continue;
13282
+ if (!this.viewerPos) {
13283
+ return this.electedGlobalMesh && detected.has(this.electedGlobalMesh) ? this.electedGlobalMesh : mesh;
13284
+ }
13285
+ const pose = frame.getPose(mesh.meshSpace, referenceSpace);
13286
+ if (!pose) continue;
13287
+ const m = pose.transform.matrix;
13288
+ const d = Math.hypot(
13289
+ m[12] - this.viewerPos.x,
13290
+ m[13] - this.viewerPos.y,
13291
+ m[14] - this.viewerPos.z
13292
+ );
13293
+ if (mesh === this.electedGlobalMesh) {
13294
+ currentDist = d;
13295
+ currentStillDetected = true;
13296
+ }
13297
+ if (d < nearestDist) {
13298
+ nearestDist = d;
13299
+ nearest = mesh;
13300
+ }
13301
+ }
13302
+ if (currentStillDetected && currentDist < nearestDist + GLOBAL_MESH_SWITCH_MARGIN) {
13303
+ return this.electedGlobalMesh;
13304
+ }
13305
+ this.electedGlobalMesh = nearest;
13306
+ return nearest;
13307
+ }
13238
13308
  /**
13239
13309
  * Admission control for a NOT-yet-spawned surface. With a viewer pose
13240
13310
  * available, the surface must behave world-anchored for a few frames before
@@ -13446,6 +13516,7 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
13446
13516
  this.clearMap(world, this.meshes);
13447
13517
  this.pendingPlanes.clear();
13448
13518
  this.pendingMeshes.clear();
13519
+ this.electedGlobalMesh = null;
13449
13520
  }
13450
13521
  dispose(world) {
13451
13522
  this.clearAll(world);