@iwsdk/core 0.5.3 → 1.0.0-rc.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.
Files changed (233) hide show
  1. package/README.md +22 -1
  2. package/dist/asset/asset-manager.d.ts.map +1 -1
  3. package/dist/asset/asset-manager.js +3 -2
  4. package/dist/asset/asset-manager.js.map +1 -1
  5. package/dist/asset/cache-manager.js.map +1 -1
  6. package/dist/asset/loaders/audio-loader.js.map +1 -1
  7. package/dist/asset/loaders/cached-asset-load.js.map +1 -1
  8. package/dist/asset/loaders/gltf-loader.js.map +1 -1
  9. package/dist/asset/loaders/hdr-texture-loader.js.map +1 -1
  10. package/dist/asset/loaders/texture-loader.js.map +1 -1
  11. package/dist/asset/loaders/uikitml-loader.js.map +1 -1
  12. package/dist/audio/audio-pool.js.map +1 -1
  13. package/dist/audio/audio-system.js.map +1 -1
  14. package/dist/audio/audio-utils.js.map +1 -1
  15. package/dist/audio/audio.js.map +1 -1
  16. package/dist/camera/camera-source.js.map +1 -1
  17. package/dist/camera/camera-system.d.ts +7 -3
  18. package/dist/camera/camera-system.d.ts.map +1 -1
  19. package/dist/camera/camera-system.js +81 -50
  20. package/dist/camera/camera-system.js.map +1 -1
  21. package/dist/camera/camera-utils.d.ts +5 -0
  22. package/dist/camera/camera-utils.d.ts.map +1 -1
  23. package/dist/camera/camera-utils.js +8 -1
  24. package/dist/camera/camera-utils.js.map +1 -1
  25. package/dist/camera/types.js.map +1 -1
  26. package/dist/depth/depth-occludable.js.map +1 -1
  27. package/dist/depth/depth-sensing-system.d.ts +3 -0
  28. package/dist/depth/depth-sensing-system.d.ts.map +1 -1
  29. package/dist/depth/depth-sensing-system.js +55 -1
  30. package/dist/depth/depth-sensing-system.js.map +1 -1
  31. package/dist/depth/depth-textures.js.map +1 -1
  32. package/dist/depth/occlusion/preprocessing-pass.js.map +1 -1
  33. package/dist/depth/occlusion/preprocessing-shader.glsl.js.map +1 -1
  34. package/dist/ecs/builtin-components.js.map +1 -1
  35. package/dist/ecs/component-editor-metadata.js.map +1 -1
  36. package/dist/ecs/component-manifest.js.map +1 -1
  37. package/dist/ecs/entity.js.map +1 -1
  38. package/dist/ecs/system.js.map +1 -1
  39. package/dist/ecs/world.d.ts +2 -0
  40. package/dist/ecs/world.d.ts.map +1 -1
  41. package/dist/ecs/world.js +4 -0
  42. package/dist/ecs/world.js.map +1 -1
  43. package/dist/environment/dome-gradient.js.map +1 -1
  44. package/dist/environment/dome-texture.js.map +1 -1
  45. package/dist/environment/environment-system.js.map +1 -1
  46. package/dist/environment/gradient-environment.js.map +1 -1
  47. package/dist/environment/ibl-gradient.js.map +1 -1
  48. package/dist/environment/ibl-texture.js.map +1 -1
  49. package/dist/environment/scene-ar-environment.js.map +1 -1
  50. package/dist/environment-raycast/environment-raycast.js.map +1 -1
  51. package/dist/environment-raycast/raycast-target.js.map +1 -1
  52. package/dist/grab/distance-grabbable.d.ts +5 -0
  53. package/dist/grab/distance-grabbable.d.ts.map +1 -1
  54. package/dist/grab/distance-grabbable.js +5 -0
  55. package/dist/grab/distance-grabbable.js.map +1 -1
  56. package/dist/grab/grab-helpers.js.map +1 -1
  57. package/dist/grab/grab-system.js.map +1 -1
  58. package/dist/grab/grab-warnings.js.map +1 -1
  59. package/dist/grab/grabbed.js.map +1 -1
  60. package/dist/grab/handles.d.ts +6 -0
  61. package/dist/grab/handles.d.ts.map +1 -1
  62. package/dist/grab/handles.js +46 -12
  63. package/dist/grab/handles.js.map +1 -1
  64. package/dist/grab/index.js.map +1 -1
  65. package/dist/grab/one-hand-grabbable.d.ts +5 -0
  66. package/dist/grab/one-hand-grabbable.d.ts.map +1 -1
  67. package/dist/grab/one-hand-grabbable.js +5 -0
  68. package/dist/grab/one-hand-grabbable.js.map +1 -1
  69. package/dist/grab/pointer-events-patch.js.map +1 -1
  70. package/dist/grab/two-hands-grabbable.d.ts +5 -0
  71. package/dist/grab/two-hands-grabbable.d.ts.map +1 -1
  72. package/dist/grab/two-hands-grabbable.js +5 -0
  73. package/dist/grab/two-hands-grabbable.js.map +1 -1
  74. package/dist/index.js +4 -3
  75. package/dist/index.js.map +1 -1
  76. package/dist/init/browser-camera.js.map +1 -1
  77. package/dist/init/index.d.ts +2 -2
  78. package/dist/init/index.d.ts.map +1 -1
  79. package/dist/init/player-camera.js.map +1 -1
  80. package/dist/init/session-grant.d.ts +34 -0
  81. package/dist/init/session-grant.d.ts.map +1 -0
  82. package/dist/init/session-grant.js +89 -0
  83. package/dist/init/session-grant.js.map +1 -0
  84. package/dist/init/world-initializer.d.ts +87 -4
  85. package/dist/init/world-initializer.d.ts.map +1 -1
  86. package/dist/init/world-initializer.js +45 -41
  87. package/dist/init/world-initializer.js.map +1 -1
  88. package/dist/init/xr.d.ts +19 -0
  89. package/dist/init/xr.d.ts.map +1 -1
  90. package/dist/init/xr.js +136 -41
  91. package/dist/init/xr.js.map +1 -1
  92. package/dist/input/canvas-pointer-system.d.ts +2 -0
  93. package/dist/input/canvas-pointer-system.d.ts.map +1 -1
  94. package/dist/input/canvas-pointer-system.js +33 -13
  95. package/dist/input/canvas-pointer-system.js.map +1 -1
  96. package/dist/input/input-actions.js.map +1 -1
  97. package/dist/input/input-manager.js.map +1 -1
  98. package/dist/input/input-system.d.ts.map +1 -1
  99. package/dist/input/input-system.js +5 -2
  100. package/dist/input/input-system.js.map +1 -1
  101. package/dist/input/state-tags.js.map +1 -1
  102. package/dist/input/stateful-browser-gamepad.js.map +1 -1
  103. package/dist/input/stateful-keyboard.js.map +1 -1
  104. package/dist/layers/xr-cylinder-layer.d.ts +2 -1
  105. package/dist/layers/xr-cylinder-layer.d.ts.map +1 -1
  106. package/dist/layers/xr-cylinder-layer.js +2 -1
  107. package/dist/layers/xr-cylinder-layer.js.map +1 -1
  108. package/dist/layers/xr-layer-init.d.ts +19 -0
  109. package/dist/layers/xr-layer-init.d.ts.map +1 -0
  110. package/dist/layers/xr-layer-init.js +20 -0
  111. package/dist/layers/xr-layer-init.js.map +1 -0
  112. package/dist/layers/xr-layer-state.js.map +1 -1
  113. package/dist/layers/xr-layer-system.d.ts +41 -1
  114. package/dist/layers/xr-layer-system.d.ts.map +1 -1
  115. package/dist/layers/xr-layer-system.js +506 -132
  116. package/dist/layers/xr-layer-system.js.map +1 -1
  117. package/dist/layers/xr-layer-transform.d.ts +18 -0
  118. package/dist/layers/xr-layer-transform.d.ts.map +1 -0
  119. package/dist/layers/xr-layer-transform.js +115 -0
  120. package/dist/layers/xr-layer-transform.js.map +1 -0
  121. package/dist/layers/xr-quad-layer.d.ts +2 -1
  122. package/dist/layers/xr-quad-layer.d.ts.map +1 -1
  123. package/dist/layers/xr-quad-layer.js +2 -1
  124. package/dist/layers/xr-quad-layer.js.map +1 -1
  125. package/dist/level/level-component-applier.d.ts +2 -0
  126. package/dist/level/level-component-applier.d.ts.map +1 -1
  127. package/dist/level/level-component-applier.js +24 -5
  128. package/dist/level/level-component-applier.js.map +1 -1
  129. package/dist/level/level-player-rig.js.map +1 -1
  130. package/dist/level/level-root.js.map +1 -1
  131. package/dist/level/level-scene-environment.js.map +1 -1
  132. package/dist/level/level-scene-json-importer.js.map +1 -1
  133. package/dist/level/level-scene-material.js.map +1 -1
  134. package/dist/level/level-scene-object.js.map +1 -1
  135. package/dist/level/level-scene-primitive.js.map +1 -1
  136. package/dist/level/level-system.js.map +1 -1
  137. package/dist/level/level-tag.js.map +1 -1
  138. package/dist/lighting/light-binding.js.map +1 -1
  139. package/dist/lighting/light-components.js.map +1 -1
  140. package/dist/lighting/light-system.js.map +1 -1
  141. package/dist/locomotion/geometries/beveled-cylinder.js.map +1 -1
  142. package/dist/locomotion/locomotion-environment.js.map +1 -1
  143. package/dist/locomotion/locomotion-input-provider.js.map +1 -1
  144. package/dist/locomotion/locomotion.js.map +1 -1
  145. package/dist/locomotion/materials/line.js.map +1 -1
  146. package/dist/locomotion/slide.js.map +1 -1
  147. package/dist/locomotion/teleport.js.map +1 -1
  148. package/dist/locomotion/turn.js.map +1 -1
  149. package/dist/mcp/ecs-debug-tools.d.ts +4 -1
  150. package/dist/mcp/ecs-debug-tools.d.ts.map +1 -1
  151. package/dist/mcp/ecs-debug-tools.js +113 -10
  152. package/dist/mcp/ecs-debug-tools.js.map +1 -1
  153. package/dist/mcp/index.d.ts +1 -0
  154. package/dist/mcp/index.d.ts.map +1 -1
  155. package/dist/mcp/mcp-runtime.d.ts +2 -0
  156. package/dist/mcp/mcp-runtime.d.ts.map +1 -1
  157. package/dist/mcp/mcp-runtime.js +41 -0
  158. package/dist/mcp/mcp-runtime.js.map +1 -1
  159. package/dist/mcp/scene-tools.js.map +1 -1
  160. package/dist/mcp/ui-debug-tools.d.ts +56 -0
  161. package/dist/mcp/ui-debug-tools.d.ts.map +1 -0
  162. package/dist/mcp/ui-debug-tools.js +412 -0
  163. package/dist/mcp/ui-debug-tools.js.map +1 -0
  164. package/dist/physics/index.js +1 -1
  165. package/dist/physics/physics-runtime.d.ts +22 -0
  166. package/dist/physics/physics-runtime.d.ts.map +1 -0
  167. package/dist/physics/physics-runtime.js +423 -0
  168. package/dist/physics/physics-runtime.js.map +1 -0
  169. package/dist/physics/physics-system.d.ts +84 -108
  170. package/dist/physics/physics-system.d.ts.map +1 -1
  171. package/dist/physics/physics-system.js +686 -555
  172. package/dist/physics/physics-system.js.map +1 -1
  173. package/dist/physics/physics-transport.d.ts +20 -0
  174. package/dist/physics/physics-transport.d.ts.map +1 -0
  175. package/dist/physics/physics-transport.js +111 -0
  176. package/dist/physics/physics-transport.js.map +1 -0
  177. package/dist/physics/physics-worker-protocol.d.ts +144 -0
  178. package/dist/physics/physics-worker-protocol.d.ts.map +1 -0
  179. package/dist/physics/physics-worker-protocol.js +41 -0
  180. package/dist/physics/physics-worker-protocol.js.map +1 -0
  181. package/dist/physics/physics-worker-utils.d.ts +8 -0
  182. package/dist/physics/physics-worker-utils.d.ts.map +1 -0
  183. package/dist/physics/physics-worker-utils.js +12 -0
  184. package/dist/physics/physics-worker-utils.js.map +1 -0
  185. package/dist/physics/physics-worker.d.ts +8 -0
  186. package/dist/physics/physics-worker.d.ts.map +1 -0
  187. package/dist/physics/physics-worker.js +66 -0
  188. package/dist/physics/physics-worker.js.map +1 -0
  189. package/dist/physics/physicsBody.js.map +1 -1
  190. package/dist/physics/physicsManipulation.js.map +1 -1
  191. package/dist/physics/physicsShape.js.map +1 -1
  192. package/dist/physics/utils.js.map +1 -1
  193. package/dist/project/normalize.js +7 -1
  194. package/dist/project/normalize.js.map +1 -1
  195. package/dist/project/paths.js.map +1 -1
  196. package/dist/project/types.d.ts +7 -1
  197. package/dist/project/types.d.ts.map +1 -1
  198. package/dist/project/types.js.map +1 -1
  199. package/dist/project/validation.js +36 -2
  200. package/dist/project/validation.js.map +1 -1
  201. package/dist/runtime/three.js.map +1 -1
  202. package/dist/scene-understanding/anchor.js.map +1 -1
  203. package/dist/scene-understanding/mesh.js.map +1 -1
  204. package/dist/scene-understanding/plane.js.map +1 -1
  205. package/dist/scene-understanding/scene-understanding-system.d.ts +42 -6
  206. package/dist/scene-understanding/scene-understanding-system.d.ts.map +1 -1
  207. package/dist/scene-understanding/scene-understanding-system.js +391 -106
  208. package/dist/scene-understanding/scene-understanding-system.js.map +1 -1
  209. package/dist/schemas/iwsdk-project.v1.schema.json +20 -1
  210. package/dist/transform/synced-euler.js.map +1 -1
  211. package/dist/transform/synced-quaternion.js.map +1 -1
  212. package/dist/transform/synced-vector3.js.map +1 -1
  213. package/dist/transform/transform-component.js.map +1 -1
  214. package/dist/transform/transform-utils.js.map +1 -1
  215. package/dist/transform/transform.js.map +1 -1
  216. package/dist/ui/document.js.map +1 -1
  217. package/dist/ui/follow-component.js.map +1 -1
  218. package/dist/ui/follow.d.ts.map +1 -1
  219. package/dist/ui/follow.js +0 -1
  220. package/dist/ui/follow.js.map +1 -1
  221. package/dist/ui/panel-components.js.map +1 -1
  222. package/dist/ui/screenspace-component.js.map +1 -1
  223. package/dist/ui/screenspace.js.map +1 -1
  224. package/dist/ui/ui.js.map +1 -1
  225. package/dist/ui/uikitml-asset.js.map +1 -1
  226. package/dist/ui/uikitml.js.map +1 -1
  227. package/dist/version.d.ts +1 -1
  228. package/dist/version.d.ts.map +1 -1
  229. package/dist/version.js +1 -1
  230. package/dist/version.js.map +1 -1
  231. package/dist/visibility/visibility-component.js.map +1 -1
  232. package/dist/visibility/visibility.js.map +1 -1
  233. package/package.json +5 -5
@@ -73,8 +73,20 @@ class SceneUnderstandingSystem extends createSystem({
73
73
  super(...arguments);
74
74
  /** Tracks whether an anchor creation request is in progress to prevent duplicate requests */
75
75
  this.anchorRequested = false;
76
+ /** Render-clock deadline before another native anchor request may start. */
77
+ this.nextAnchorRequestTime = 0;
78
+ /** Index of the delay assigned to the next failed native anchor request. */
79
+ this.anchorRetryDelayIndex = 0;
80
+ /** Latest elapsed render-clock time, in seconds. */
81
+ this.anchorRetryClockTime = 0;
82
+ /** Invalidates asynchronous anchor work when the active XR session changes. */
83
+ this.anchorRequestGeneration = 0;
84
+ /** Prevents duplicate native-anchor disposal across async teardown paths. */
85
+ this.deletedAnchors = new WeakSet();
76
86
  this.currentPlanes = new Map();
77
87
  this.currentMeshes = new Map();
88
+ this.planeGeometryStates = new Map();
89
+ this.meshGeometryStates = new Map();
78
90
  /** Group that holds all anchored objects, positioned to match the XRAnchor's world pose */
79
91
  this.anchoredGroup = new Group();
80
92
  this.matrixBuffer = new Matrix4();
@@ -93,7 +105,7 @@ class SceneUnderstandingSystem extends createSystem({
93
105
  wireframe: true,
94
106
  opacity: 0.3,
95
107
  });
96
- this.xrManager.addEventListener('sessionstart', async () => {
108
+ const onSessionStart = async () => {
97
109
  this.updateEnabledFeatures(this.xrManager.getSession());
98
110
  // Attempt to restore a persistent anchor from previous sessions
99
111
  // This allows anchored objects to maintain their world position across sessions
@@ -110,35 +122,24 @@ class SceneUnderstandingSystem extends createSystem({
110
122
  // ) {
111
123
  // await this.xrManager.getSession()?.initiateRoomCapture();
112
124
  // }
113
- });
114
- this.xrManager.addEventListener('sessionend', () => {
125
+ };
126
+ const onSessionEnd = () => {
115
127
  // Clean up all plane and mesh entities to prevent stale XRSpace references
116
128
  // XRSpace objects (like planeSpace, meshSpace, anchorSpace) are tied to a specific
117
129
  // XRSession and become invalid when the session ends. Using them with a new session's
118
130
  // XRFrame will cause "XRSpace and XRFrame sessions do not match" errors.
119
- this.queries.planeEntities.entities.forEach((entity) => {
120
- this.disposeEntityGeometry(entity);
121
- entity.destroy();
122
- });
123
- this.queries.meshEntities.entities.forEach((entity) => {
124
- this.disposeEntityGeometry(entity);
125
- entity.destroy();
126
- });
131
+ this.disposeDetectedEntities();
127
132
  // Clear session-specific state to prevent stale references
128
133
  // Note: The persistent anchor UUID in localStorage is preserved for the next session
129
- this.xrAnchor = undefined;
130
- this.anchorRequested = false;
131
- this.planeFeatureEnabled = undefined;
132
- this.meshFeatureEnabled = undefined;
133
- this.anchorFeatureEnabled = undefined;
134
- this.currentPlanes.clear();
135
- this.currentMeshes.clear();
136
- });
137
- this.world.createTransformEntity(this.anchoredGroup, {
134
+ this.resetSessionState();
135
+ };
136
+ this.xrManager.addEventListener('sessionstart', onSessionStart);
137
+ this.xrManager.addEventListener('sessionend', onSessionEnd);
138
+ this.anchoredGroupEntity = this.world.createTransformEntity(this.anchoredGroup, {
138
139
  parent: this.world.sceneEntity,
139
140
  persistent: true,
140
141
  });
141
- this.cleanupFuncs.push(this.config.showWireFrame.subscribe((value) => {
142
+ this.cleanupFuncs.push(() => this.xrManager.removeEventListener('sessionstart', onSessionStart), () => this.xrManager.removeEventListener('sessionend', onSessionEnd), this.config.showWireFrame.subscribe((value) => {
142
143
  this.queries.planeEntities.entities.forEach((planeEntity) => {
143
144
  const planeObject = planeEntity.object3D;
144
145
  if (planeObject instanceof Mesh) {
@@ -151,10 +152,31 @@ class SceneUnderstandingSystem extends createSystem({
151
152
  meshObject.visible = value;
152
153
  }
153
154
  });
154
- }));
155
+ }), () => {
156
+ var _a;
157
+ this.disposeDetectedEntities();
158
+ this.resetSessionState();
159
+ for (const entity of [...this.queries.anchoredEntities.entities]) {
160
+ const object = entity.object3D;
161
+ if ((object === null || object === void 0 ? void 0 : object.parent) === this.anchoredGroup) {
162
+ this.scene.attach(object);
163
+ }
164
+ if (entity.active) {
165
+ entity.setValue(XRAnchor, 'attached', false);
166
+ }
167
+ }
168
+ this.anchoredGroup.removeFromParent();
169
+ if ((_a = this.anchoredGroupEntity) === null || _a === void 0 ? void 0 : _a.active) {
170
+ this.anchoredGroupEntity.destroy();
171
+ }
172
+ this.anchoredGroupEntity = undefined;
173
+ this.planeMaterial.dispose();
174
+ this.meshMaterial.dispose();
175
+ });
155
176
  }
156
- update(_delta, _time) {
177
+ update(_delta, time) {
157
178
  var _a;
179
+ this.anchorRetryClockTime = time;
158
180
  const frame = this.xrFrame;
159
181
  const planes = frame === null || frame === void 0 ? void 0 : frame.detectedPlanes;
160
182
  const meshes = frame === null || frame === void 0 ? void 0 : frame.detectedMeshes;
@@ -167,8 +189,8 @@ class SceneUnderstandingSystem extends createSystem({
167
189
  }
168
190
  if (this.anchorFeatureEnabled &&
169
191
  this.xrAnchor === undefined &&
170
- !this.anchorRequested) {
171
- console.log('[SceneUnderstandingSystem] Anchor needed but not present, triggering creation');
192
+ !this.anchorRequested &&
193
+ time >= this.nextAnchorRequestTime) {
172
194
  this.createAnchor(referenceSpace);
173
195
  }
174
196
  if (this.xrAnchor && referenceSpace) {
@@ -192,6 +214,66 @@ class SceneUnderstandingSystem extends createSystem({
192
214
  object3D.geometry.dispose();
193
215
  }
194
216
  }
217
+ disposeDetectedEntities() {
218
+ const entities = new Set([
219
+ ...this.queries.planeEntities.entities,
220
+ ...this.queries.meshEntities.entities,
221
+ ]);
222
+ for (const entity of entities) {
223
+ this.disposeEntityGeometry(entity);
224
+ if (entity.active) {
225
+ entity.destroy();
226
+ }
227
+ }
228
+ this.currentPlanes.clear();
229
+ this.currentMeshes.clear();
230
+ this.planeGeometryStates.clear();
231
+ this.meshGeometryStates.clear();
232
+ }
233
+ resetSessionState() {
234
+ this.anchorRequestGeneration++;
235
+ this.anchorRequested = false;
236
+ this.resetAnchorRetryState();
237
+ this.clearActiveAnchor();
238
+ this.planeFeatureEnabled = undefined;
239
+ this.meshFeatureEnabled = undefined;
240
+ this.anchorFeatureEnabled = undefined;
241
+ }
242
+ isAnchorRequestCurrent(session, requestGeneration) {
243
+ return (requestGeneration === this.anchorRequestGeneration &&
244
+ session === this.xrManager.getSession());
245
+ }
246
+ deleteAnchor(anchor) {
247
+ if (!anchor || this.deletedAnchors.has(anchor)) {
248
+ return;
249
+ }
250
+ this.deletedAnchors.add(anchor);
251
+ try {
252
+ anchor.delete();
253
+ }
254
+ catch (_error) {
255
+ // The session may already have ended, but the anchor must still be
256
+ // forgotten locally and must never be installed into a later session.
257
+ }
258
+ }
259
+ clearActiveAnchor() {
260
+ const anchor = this.xrAnchor;
261
+ this.xrAnchor = undefined;
262
+ this.deleteAnchor(anchor);
263
+ }
264
+ resetAnchorRetryState() {
265
+ this.nextAnchorRequestTime = 0;
266
+ this.anchorRetryDelayIndex = 0;
267
+ }
268
+ increaseAnchorRetryDelay(session, requestGeneration, requestDelayIndex) {
269
+ if (!this.isAnchorRequestCurrent(session, requestGeneration)) {
270
+ return;
271
+ }
272
+ this.nextAnchorRequestTime =
273
+ this.anchorRetryClockTime +
274
+ SceneUnderstandingSystem.ANCHOR_RETRY_DELAYS_SECONDS[requestDelayIndex];
275
+ this.anchorRetryDelayIndex = Math.min(requestDelayIndex + 1, SceneUnderstandingSystem.ANCHOR_RETRY_DELAYS_SECONDS.length - 1);
276
+ }
195
277
  updatePlanes(planes, referenceSpace) {
196
278
  this.currentPlanes.clear();
197
279
  this.queries.planeEntities.entities.forEach((planeEntity) => {
@@ -199,6 +281,7 @@ class SceneUnderstandingSystem extends createSystem({
199
281
  this.currentPlanes.set(planeEntity.getValue(XRPlane, '_plane'), planeEntity);
200
282
  }
201
283
  else {
284
+ this.planeGeometryStates.delete(planeEntity.getValue(XRPlane, '_plane'));
202
285
  this.disposeEntityGeometry(planeEntity);
203
286
  planeEntity.destroy();
204
287
  }
@@ -215,24 +298,8 @@ class SceneUnderstandingSystem extends createSystem({
215
298
  }
216
299
  this.matrixBuffer.fromArray(pose.transform.matrix);
217
300
  if (this.currentPlanes.has(plane) === false) {
218
- // Only build geometry for newly-seen planes. Doing it every frame
219
- // for every plane allocated a BoxGeometry that was then discarded
220
- // (without dispose) for already-tracked planes — a per-frame GPU
221
- // leak. Existing planes only need their transform refreshed below.
222
301
  const polygon = plane.polygon;
223
- let minX = Number.MAX_SAFE_INTEGER;
224
- let maxX = Number.MIN_SAFE_INTEGER;
225
- let minZ = Number.MAX_SAFE_INTEGER;
226
- let maxZ = Number.MIN_SAFE_INTEGER;
227
- for (const point of polygon) {
228
- minX = Math.min(minX, point.x);
229
- maxX = Math.max(maxX, point.x);
230
- minZ = Math.min(minZ, point.z);
231
- maxZ = Math.max(maxZ, point.z);
232
- }
233
- const width = maxX - minX;
234
- const height = maxZ - minZ;
235
- const geometry = new BoxGeometry(width, 0.001, height);
302
+ const geometry = this.createPlaneGeometry(polygon);
236
303
  const mesh = new Mesh(geometry, this.planeMaterial);
237
304
  mesh.visible = this.config.showWireFrame.value;
238
305
  mesh.position.setFromMatrixPosition(this.matrixBuffer);
@@ -242,9 +309,31 @@ class SceneUnderstandingSystem extends createSystem({
242
309
  planeEntity.addComponent(XRPlane, {
243
310
  _plane: plane,
244
311
  });
312
+ this.planeGeometryStates.set(plane, {
313
+ lastChangedTime: plane.lastChangedTime,
314
+ polygon,
315
+ });
245
316
  }
246
317
  else {
247
318
  const planeObject = (_a = this.currentPlanes.get(plane)) === null || _a === void 0 ? void 0 : _a.object3D;
319
+ const previousState = this.planeGeometryStates.get(plane);
320
+ if (planeObject instanceof Mesh &&
321
+ (previousState === undefined ||
322
+ previousState.lastChangedTime !== plane.lastChangedTime)) {
323
+ const polygon = plane.polygon;
324
+ // Conforming runtimes replace changed topology arrays. Keep the
325
+ // identity guard because some emulators advance the timestamp on
326
+ // every frame even when their topology is unchanged.
327
+ if ((previousState === null || previousState === void 0 ? void 0 : previousState.polygon) !== polygon) {
328
+ const previousGeometry = planeObject.geometry;
329
+ planeObject.geometry = this.createPlaneGeometry(polygon);
330
+ previousGeometry.dispose();
331
+ }
332
+ this.planeGeometryStates.set(plane, {
333
+ lastChangedTime: plane.lastChangedTime,
334
+ polygon,
335
+ });
336
+ }
248
337
  planeObject === null || planeObject === void 0 ? void 0 : planeObject.position.setFromMatrixPosition(this.matrixBuffer);
249
338
  planeObject === null || planeObject === void 0 ? void 0 : planeObject.quaternion.setFromRotationMatrix(this.matrixBuffer);
250
339
  }
@@ -259,13 +348,13 @@ class SceneUnderstandingSystem extends createSystem({
259
348
  this.currentMeshes.set(meshEntity.getValue(XRMesh, '_mesh'), meshEntity);
260
349
  }
261
350
  else {
351
+ this.meshGeometryStates.delete(meshEntity.getValue(XRMesh, '_mesh'));
262
352
  this.disposeEntityGeometry(meshEntity);
263
353
  meshEntity.destroy();
264
354
  }
265
355
  });
266
356
  if (meshes) {
267
357
  meshes.forEach((mesh) => {
268
- var _a;
269
358
  if (referenceSpace != null) {
270
359
  const pose = this.xrManager
271
360
  .getFrame()
@@ -275,43 +364,73 @@ class SceneUnderstandingSystem extends createSystem({
275
364
  }
276
365
  this.matrixBuffer.fromArray(pose.transform.matrix);
277
366
  if (this.currentMeshes.has(mesh) === false) {
278
- // Only build geometry for newly-seen meshes. Building a
279
- // BufferGeometry every frame for every mesh and discarding it
280
- // (without dispose) for already-tracked meshes was a per-frame GPU
281
- // leak. Existing meshes only need their transform refreshed below.
282
- const geometry = new BufferGeometry();
283
- geometry.setAttribute('position', new BufferAttribute(mesh.vertices, 3));
284
- geometry.setIndex(new BufferAttribute(mesh.indices, 1));
367
+ const vertices = mesh.vertices;
368
+ const indices = mesh.indices;
369
+ const metadata = this.tryGetMeshMetadata(vertices, mesh.semanticLabel);
370
+ if (metadata === undefined) {
371
+ return;
372
+ }
373
+ const geometry = this.createMeshGeometry(vertices, indices);
285
374
  const threeMesh = new Mesh(geometry, this.meshMaterial);
286
375
  threeMesh.visible = this.config.showWireFrame.value;
287
376
  this.scene.add(threeMesh);
288
377
  const meshEntity = this.world.createTransformEntity(threeMesh);
289
378
  threeMesh.position.setFromMatrixPosition(this.matrixBuffer);
290
379
  threeMesh.quaternion.setFromRotationMatrix(this.matrixBuffer);
291
- if (mesh.semanticLabel === 'global mesh') {
292
- meshEntity.addComponent(XRMesh, {
293
- _mesh: mesh,
294
- isBounded3D: false,
295
- });
296
- }
297
- else {
298
- const { minEntry, maxEntry } = findExtremeVertices(mesh.vertices);
299
- meshEntity.addComponent(XRMesh, {
300
- _mesh: mesh,
301
- isBounded3D: true,
302
- semanticLabel: mesh.semanticLabel,
303
- min: [minEntry.x, minEntry.y, minEntry.z],
304
- max: [maxEntry.x, maxEntry.y, maxEntry.z],
305
- dimensions: [
306
- maxEntry.x - minEntry.x,
307
- maxEntry.y - minEntry.y,
308
- maxEntry.z - minEntry.z,
309
- ],
310
- });
311
- }
380
+ meshEntity.addComponent(XRMesh, {
381
+ _mesh: mesh,
382
+ ...metadata,
383
+ });
384
+ this.meshGeometryStates.set(mesh, {
385
+ indices,
386
+ lastChangedTime: mesh.lastChangedTime,
387
+ semanticLabel: mesh.semanticLabel,
388
+ vertices,
389
+ });
312
390
  }
313
391
  else {
314
- const meshObject = (_a = this.currentMeshes.get(mesh)) === null || _a === void 0 ? void 0 : _a.object3D;
392
+ const meshEntity = this.currentMeshes.get(mesh);
393
+ const meshObject = meshEntity === null || meshEntity === void 0 ? void 0 : meshEntity.object3D;
394
+ const previousState = this.meshGeometryStates.get(mesh);
395
+ if (meshEntity !== undefined &&
396
+ meshObject instanceof Mesh &&
397
+ (previousState === undefined ||
398
+ previousState.lastChangedTime !== mesh.lastChangedTime)) {
399
+ const vertices = mesh.vertices;
400
+ const indices = mesh.indices;
401
+ const semanticLabel = mesh.semanticLabel;
402
+ // See the plane path above: timestamp changes alone are noisy in
403
+ // some emulators, while conforming topology updates replace the
404
+ // vertex or index array.
405
+ const topologyChanged = (previousState === null || previousState === void 0 ? void 0 : previousState.vertices) !== vertices ||
406
+ (previousState === null || previousState === void 0 ? void 0 : previousState.indices) !== indices;
407
+ const metadataChanged = topologyChanged ||
408
+ (previousState === null || previousState === void 0 ? void 0 : previousState.semanticLabel) !== semanticLabel;
409
+ const metadata = metadataChanged
410
+ ? this.tryGetMeshMetadata(vertices, semanticLabel)
411
+ : undefined;
412
+ if (metadataChanged && metadata === undefined) {
413
+ // Keep the last known-good geometry and component metadata.
414
+ // A later valid topology update can still replace them.
415
+ meshObject.position.setFromMatrixPosition(this.matrixBuffer);
416
+ meshObject.quaternion.setFromRotationMatrix(this.matrixBuffer);
417
+ return;
418
+ }
419
+ if (topologyChanged) {
420
+ const previousGeometry = meshObject.geometry;
421
+ meshObject.geometry = this.createMeshGeometry(vertices, indices);
422
+ previousGeometry.dispose();
423
+ }
424
+ if (metadata !== undefined) {
425
+ this.updateMeshMetadata(meshEntity, metadata);
426
+ }
427
+ this.meshGeometryStates.set(mesh, {
428
+ indices,
429
+ lastChangedTime: mesh.lastChangedTime,
430
+ semanticLabel,
431
+ vertices,
432
+ });
433
+ }
315
434
  meshObject === null || meshObject === void 0 ? void 0 : meshObject.position.setFromMatrixPosition(this.matrixBuffer);
316
435
  meshObject === null || meshObject === void 0 ? void 0 : meshObject.quaternion.setFromRotationMatrix(this.matrixBuffer);
317
436
  }
@@ -319,6 +438,68 @@ class SceneUnderstandingSystem extends createSystem({
319
438
  });
320
439
  }
321
440
  }
441
+ createPlaneGeometry(polygon) {
442
+ let minX = Number.MAX_SAFE_INTEGER;
443
+ let maxX = Number.MIN_SAFE_INTEGER;
444
+ let minZ = Number.MAX_SAFE_INTEGER;
445
+ let maxZ = Number.MIN_SAFE_INTEGER;
446
+ for (const point of polygon) {
447
+ minX = Math.min(minX, point.x);
448
+ maxX = Math.max(maxX, point.x);
449
+ minZ = Math.min(minZ, point.z);
450
+ maxZ = Math.max(maxZ, point.z);
451
+ }
452
+ return new BoxGeometry(maxX - minX, 0.001, maxZ - minZ);
453
+ }
454
+ createMeshGeometry(vertices, indices) {
455
+ const geometry = new BufferGeometry();
456
+ geometry.setAttribute('position', new BufferAttribute(vertices, 3));
457
+ geometry.setIndex(new BufferAttribute(indices, 1));
458
+ return geometry;
459
+ }
460
+ getMeshMetadata(vertices, semanticLabel) {
461
+ if (semanticLabel === 'global mesh') {
462
+ return {
463
+ dimensions: [0, 0, 0],
464
+ isBounded3D: false,
465
+ max: [0, 0, 0],
466
+ min: [0, 0, 0],
467
+ semanticLabel: '',
468
+ };
469
+ }
470
+ const { minEntry, maxEntry } = findAxisAlignedBounds(vertices);
471
+ return {
472
+ dimensions: [
473
+ maxEntry.x - minEntry.x,
474
+ maxEntry.y - minEntry.y,
475
+ maxEntry.z - minEntry.z,
476
+ ],
477
+ isBounded3D: true,
478
+ max: [maxEntry.x, maxEntry.y, maxEntry.z],
479
+ min: [minEntry.x, minEntry.y, minEntry.z],
480
+ semanticLabel: semanticLabel !== null && semanticLabel !== void 0 ? semanticLabel : '',
481
+ };
482
+ }
483
+ tryGetMeshMetadata(vertices, semanticLabel) {
484
+ try {
485
+ return this.getMeshMetadata(vertices, semanticLabel);
486
+ }
487
+ catch (error) {
488
+ console.warn('[SceneUnderstandingSystem] Skipping mesh with invalid vertex data.', error);
489
+ return undefined;
490
+ }
491
+ }
492
+ updateMeshMetadata(entity, metadata) {
493
+ entity.getVectorView(XRMesh, 'min').set(metadata.min);
494
+ entity.getVectorView(XRMesh, 'max').set(metadata.max);
495
+ entity.getVectorView(XRMesh, 'dimensions').set(metadata.dimensions);
496
+ if (entity.getValue(XRMesh, 'semanticLabel') !== metadata.semanticLabel) {
497
+ entity.setValue(XRMesh, 'semanticLabel', metadata.semanticLabel);
498
+ }
499
+ if (entity.getValue(XRMesh, 'isBounded3D') !== metadata.isBounded3D) {
500
+ entity.setValue(XRMesh, 'isBounded3D', metadata.isBounded3D);
501
+ }
502
+ }
322
503
  updateEnabledFeatures(xrSession) {
323
504
  if (!xrSession) {
324
505
  console.log('Warning: xrSession is null when trying to query enabled features. Scene understanding features are disabled.');
@@ -350,33 +531,62 @@ class SceneUnderstandingSystem extends createSystem({
350
531
  * anchor will be created in the update loop.
351
532
  */
352
533
  async tryRestorePersistentAnchor() {
534
+ let requestGeneration;
535
+ let session = null;
353
536
  try {
354
537
  // Load the saved anchor UUID from localStorage
355
538
  const savedUuid = localStorage.getItem(SceneUnderstandingSystem.ANCHOR_UUID_STORAGE_KEY);
356
539
  if (!savedUuid) {
357
540
  return;
358
541
  }
359
- const session = this.xrManager.getSession();
542
+ session = this.xrManager.getSession();
360
543
  if (!session) {
361
544
  return;
362
545
  }
363
- // Attempt to restore the anchor using the saved UUID
364
- this.anchorRequested = true;
365
546
  if (!session.restorePersistentAnchor) {
366
547
  console.warn('XRSession.restorePersistentAnchor not supported');
367
- this.anchorRequested = false;
368
548
  return;
369
549
  }
370
- this.xrAnchor = await session.restorePersistentAnchor(savedUuid);
371
- if (!this.xrAnchor) {
372
- // Restoration returned null, will create new anchor
373
- this.anchorRequested = false;
550
+ // Each async request owns a generation. Session end, system teardown, or
551
+ // a subsequent restore invalidates it so a late result cannot install an
552
+ // XRAnchor whose XRSpace belongs to an obsolete XRSession.
553
+ requestGeneration = ++this.anchorRequestGeneration;
554
+ this.anchorRequested = true;
555
+ const restoredAnchor = (await session.restorePersistentAnchor(savedUuid));
556
+ if (!this.isAnchorRequestCurrent(session, requestGeneration)) {
557
+ this.deleteAnchor(restoredAnchor);
558
+ return;
559
+ }
560
+ if (!restoredAnchor) {
561
+ this.clearActiveAnchor();
562
+ return;
563
+ }
564
+ if (this.xrAnchor !== restoredAnchor) {
565
+ this.clearActiveAnchor();
566
+ this.xrAnchor = restoredAnchor;
374
567
  }
568
+ this.resetAnchorRetryState();
375
569
  }
376
570
  catch (_error) {
377
- // Restoration failed - clear the invalid UUID and allow new anchor creation
378
- localStorage.removeItem(SceneUnderstandingSystem.ANCHOR_UUID_STORAGE_KEY);
379
- this.anchorRequested = false;
571
+ // Only the current session's failure proves that its saved UUID is
572
+ // invalid. A superseded session may reject merely because it ended.
573
+ if (requestGeneration === undefined ||
574
+ (session !== null &&
575
+ this.isAnchorRequestCurrent(session, requestGeneration))) {
576
+ try {
577
+ localStorage.removeItem(SceneUnderstandingSystem.ANCHOR_UUID_STORAGE_KEY);
578
+ }
579
+ catch (_storageError) {
580
+ // Storage access can be unavailable in privacy-restricted contexts.
581
+ }
582
+ }
583
+ }
584
+ finally {
585
+ // A stale request must not clear the in-flight marker owned by the next
586
+ // session's restore.
587
+ if (requestGeneration === this.anchorRequestGeneration) {
588
+ this.anchorRequested = false;
589
+ }
380
590
  }
381
591
  }
382
592
  /**
@@ -388,28 +598,71 @@ class SceneUnderstandingSystem extends createSystem({
388
598
  * will only last for the current session.
389
599
  */
390
600
  async createAnchor(referenceSpace) {
391
- const frame = this.xrManager.getFrame();
392
- if (!frame.createAnchor) {
393
- throw 'XRFrame.createAnchor is undefined';
394
- }
395
- if (!referenceSpace) {
396
- throw 'renderer.xr.getReferenceSpace() returned null';
397
- }
398
- this.anchorRequested = true;
399
- this.xrAnchor = await frame.createAnchor(new XRRigidTransform(), referenceSpace);
400
- if (!this.xrAnchor) {
401
- this.anchorRequested = false;
402
- throw 'XRAnchor creation failed';
403
- }
404
- // Request a persistent handle for the anchor so it can be restored in future sessions
601
+ let requestGeneration;
602
+ let session = null;
603
+ let createdAnchor;
604
+ let requestDelayIndex;
405
605
  try {
406
- if (this.xrAnchor.requestPersistentHandle) {
407
- const uuid = await this.xrAnchor.requestPersistentHandle();
606
+ session = this.xrManager.getSession();
607
+ const frame = this.xrManager.getFrame();
608
+ if (!session || !(frame === null || frame === void 0 ? void 0 : frame.createAnchor) || !referenceSpace) {
609
+ return;
610
+ }
611
+ requestGeneration = ++this.anchorRequestGeneration;
612
+ this.anchorRequested = true;
613
+ requestDelayIndex = this.anchorRetryDelayIndex;
614
+ console.log('[SceneUnderstandingSystem] Anchor needed but not present, triggering creation');
615
+ createdAnchor = (await frame.createAnchor(new XRRigidTransform(), referenceSpace));
616
+ if (!createdAnchor) {
617
+ this.increaseAnchorRetryDelay(session, requestGeneration, requestDelayIndex);
618
+ return;
619
+ }
620
+ if (!this.isAnchorRequestCurrent(session, requestGeneration)) {
621
+ this.deleteAnchor(createdAnchor);
622
+ return;
623
+ }
624
+ if (this.xrAnchor !== createdAnchor) {
625
+ this.clearActiveAnchor();
626
+ this.xrAnchor = createdAnchor;
627
+ }
628
+ this.resetAnchorRetryState();
629
+ if (createdAnchor.requestPersistentHandle) {
630
+ const uuid = await createdAnchor.requestPersistentHandle();
631
+ if (!this.isAnchorRequestCurrent(session, requestGeneration) ||
632
+ this.xrAnchor !== createdAnchor) {
633
+ if (this.xrAnchor === createdAnchor) {
634
+ this.xrAnchor = undefined;
635
+ }
636
+ this.deleteAnchor(createdAnchor);
637
+ return;
638
+ }
408
639
  localStorage.setItem(SceneUnderstandingSystem.ANCHOR_UUID_STORAGE_KEY, uuid);
409
640
  }
410
641
  }
411
642
  catch (_error) {
412
- // Persistence not supported or failed - anchor will work for this session only
643
+ // Anchor creation and persistence are optional. A current created anchor
644
+ // remains usable when only persistence fails; a stale one is discarded.
645
+ if (!createdAnchor &&
646
+ requestGeneration !== undefined &&
647
+ requestDelayIndex !== undefined &&
648
+ session !== null) {
649
+ this.increaseAnchorRetryDelay(session, requestGeneration, requestDelayIndex);
650
+ }
651
+ if (createdAnchor &&
652
+ requestGeneration !== undefined &&
653
+ session !== null &&
654
+ (!this.isAnchorRequestCurrent(session, requestGeneration) ||
655
+ this.xrAnchor !== createdAnchor)) {
656
+ if (this.xrAnchor === createdAnchor) {
657
+ this.xrAnchor = undefined;
658
+ }
659
+ this.deleteAnchor(createdAnchor);
660
+ }
661
+ }
662
+ finally {
663
+ if (requestGeneration === this.anchorRequestGeneration) {
664
+ this.anchorRequested = false;
665
+ }
413
666
  }
414
667
  }
415
668
  updateAnchoredObject() {
@@ -422,15 +675,47 @@ class SceneUnderstandingSystem extends createSystem({
422
675
  });
423
676
  }
424
677
  }
678
+ /** Delays between failed native anchor requests. */
679
+ SceneUnderstandingSystem.ANCHOR_RETRY_DELAYS_SECONDS = [
680
+ 0.25, 0.5, 1, 2, 5,
681
+ ];
425
682
  /** localStorage key for storing the persistent anchor UUID */
426
683
  SceneUnderstandingSystem.ANCHOR_UUID_STORAGE_KEY = 'iwsdk_scene_anchor_uuid';
427
684
  /**
428
- * From a flat `[x,y,z, x,y,z, ...]` vertex buffer, return the vertices with the
429
- * smallest and largest coordinate sum (`x + y + z`).
685
+ * From a flat `[x,y,z, x,y,z, ...]` vertex buffer, return its axis-aligned
686
+ * minimum and maximum coordinates. The extrema can come from different source
687
+ * vertices; treating one smallest-sum and one largest-sum vertex as bounds
688
+ * under-reports skewed meshes.
689
+ */
690
+ function findAxisAlignedBounds(arr) {
691
+ if (!arr || arr.length === 0 || arr.length % 3 !== 0) {
692
+ throw new Error('Array length must be a positive multiple of 3.');
693
+ }
694
+ let minX = arr[0];
695
+ let minY = arr[1];
696
+ let minZ = arr[2];
697
+ let maxX = minX;
698
+ let maxY = minY;
699
+ let maxZ = minZ;
700
+ for (let i = 3; i < arr.length; i += 3) {
701
+ minX = Math.min(minX, arr[i]);
702
+ minY = Math.min(minY, arr[i + 1]);
703
+ minZ = Math.min(minZ, arr[i + 2]);
704
+ maxX = Math.max(maxX, arr[i]);
705
+ maxY = Math.max(maxY, arr[i + 1]);
706
+ maxZ = Math.max(maxZ, arr[i + 2]);
707
+ }
708
+ return {
709
+ minEntry: { x: minX, y: minY, z: minZ },
710
+ maxEntry: { x: maxX, y: maxY, z: maxZ },
711
+ };
712
+ }
713
+ /**
714
+ * From a flat `[x,y,z, x,y,z, ...]` vertex buffer, return the source
715
+ * vertices with the smallest and largest coordinate sums.
430
716
  *
431
- * Equivalent to the previous `flatToVec3Array(...)` -> `findMinMaxEntries(...)`
432
- * pipeline, but it scans the typed array directly and allocates only the two
433
- * result objects instead of one object per vertex (thousands per mesh).
717
+ * @deprecated This legacy helper does not calculate an axis-aligned bounding
718
+ * box. Use {@link findAxisAlignedBounds} for bounds and dimensions.
434
719
  */
435
720
  function findExtremeVertices(arr) {
436
721
  if (!arr || arr.length === 0 || arr.length % 3 !== 0) {
@@ -457,5 +742,5 @@ function findExtremeVertices(arr) {
457
742
  };
458
743
  }
459
744
 
460
- export { SceneUnderstandingSystem, findExtremeVertices };
745
+ export { SceneUnderstandingSystem, findAxisAlignedBounds, findExtremeVertices };
461
746
  //# sourceMappingURL=scene-understanding-system.js.map