@iwsdk/core 0.5.3 → 1.0.0-rc.0
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/README.md +22 -1
- package/dist/asset/asset-manager.d.ts.map +1 -1
- package/dist/asset/asset-manager.js +3 -2
- package/dist/asset/asset-manager.js.map +1 -1
- package/dist/camera/camera-system.d.ts +7 -3
- package/dist/camera/camera-system.d.ts.map +1 -1
- package/dist/camera/camera-system.js +81 -50
- package/dist/camera/camera-system.js.map +1 -1
- package/dist/camera/camera-utils.d.ts +5 -0
- package/dist/camera/camera-utils.d.ts.map +1 -1
- package/dist/camera/camera-utils.js +8 -1
- package/dist/camera/camera-utils.js.map +1 -1
- package/dist/depth/depth-sensing-system.d.ts +3 -0
- package/dist/depth/depth-sensing-system.d.ts.map +1 -1
- package/dist/depth/depth-sensing-system.js +55 -1
- package/dist/depth/depth-sensing-system.js.map +1 -1
- package/dist/ecs/world.d.ts +2 -0
- package/dist/ecs/world.d.ts.map +1 -1
- package/dist/ecs/world.js +4 -0
- package/dist/ecs/world.js.map +1 -1
- package/dist/grab/distance-grabbable.d.ts +5 -0
- package/dist/grab/distance-grabbable.d.ts.map +1 -1
- package/dist/grab/distance-grabbable.js +5 -0
- package/dist/grab/distance-grabbable.js.map +1 -1
- package/dist/grab/handles.d.ts +6 -0
- package/dist/grab/handles.d.ts.map +1 -1
- package/dist/grab/handles.js +46 -12
- package/dist/grab/handles.js.map +1 -1
- package/dist/grab/one-hand-grabbable.d.ts +5 -0
- package/dist/grab/one-hand-grabbable.d.ts.map +1 -1
- package/dist/grab/one-hand-grabbable.js +5 -0
- package/dist/grab/one-hand-grabbable.js.map +1 -1
- package/dist/grab/two-hands-grabbable.d.ts +5 -0
- package/dist/grab/two-hands-grabbable.d.ts.map +1 -1
- package/dist/grab/two-hands-grabbable.js +5 -0
- package/dist/grab/two-hands-grabbable.js.map +1 -1
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/init/index.d.ts +2 -2
- package/dist/init/index.d.ts.map +1 -1
- package/dist/init/session-grant.d.ts +34 -0
- package/dist/init/session-grant.d.ts.map +1 -0
- package/dist/init/session-grant.js +89 -0
- package/dist/init/session-grant.js.map +1 -0
- package/dist/init/world-initializer.d.ts +87 -4
- package/dist/init/world-initializer.d.ts.map +1 -1
- package/dist/init/world-initializer.js +48 -43
- package/dist/init/world-initializer.js.map +1 -1
- package/dist/init/xr.d.ts +19 -0
- package/dist/init/xr.d.ts.map +1 -1
- package/dist/init/xr.js +136 -41
- package/dist/init/xr.js.map +1 -1
- package/dist/input/canvas-pointer-system.d.ts +2 -0
- package/dist/input/canvas-pointer-system.d.ts.map +1 -1
- package/dist/input/canvas-pointer-system.js +33 -13
- package/dist/input/canvas-pointer-system.js.map +1 -1
- package/dist/input/input-system.d.ts.map +1 -1
- package/dist/input/input-system.js +5 -2
- package/dist/input/input-system.js.map +1 -1
- package/dist/layers/xr-cylinder-layer.d.ts +2 -1
- package/dist/layers/xr-cylinder-layer.d.ts.map +1 -1
- package/dist/layers/xr-cylinder-layer.js +2 -1
- package/dist/layers/xr-cylinder-layer.js.map +1 -1
- package/dist/layers/xr-layer-init.d.ts +19 -0
- package/dist/layers/xr-layer-init.d.ts.map +1 -0
- package/dist/layers/xr-layer-init.js +20 -0
- package/dist/layers/xr-layer-init.js.map +1 -0
- package/dist/layers/xr-layer-system.d.ts +41 -1
- package/dist/layers/xr-layer-system.d.ts.map +1 -1
- package/dist/layers/xr-layer-system.js +506 -132
- package/dist/layers/xr-layer-system.js.map +1 -1
- package/dist/layers/xr-layer-transform.d.ts +18 -0
- package/dist/layers/xr-layer-transform.d.ts.map +1 -0
- package/dist/layers/xr-layer-transform.js +115 -0
- package/dist/layers/xr-layer-transform.js.map +1 -0
- package/dist/layers/xr-quad-layer.d.ts +2 -1
- package/dist/layers/xr-quad-layer.d.ts.map +1 -1
- package/dist/layers/xr-quad-layer.js +2 -1
- package/dist/layers/xr-quad-layer.js.map +1 -1
- package/dist/level/level-component-applier.d.ts +2 -0
- package/dist/level/level-component-applier.d.ts.map +1 -1
- package/dist/level/level-component-applier.js +24 -5
- package/dist/level/level-component-applier.js.map +1 -1
- package/dist/mcp/ecs-debug-tools.d.ts +4 -1
- package/dist/mcp/ecs-debug-tools.d.ts.map +1 -1
- package/dist/mcp/ecs-debug-tools.js +113 -10
- package/dist/mcp/ecs-debug-tools.js.map +1 -1
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/mcp-runtime.d.ts +2 -0
- package/dist/mcp/mcp-runtime.d.ts.map +1 -1
- package/dist/mcp/mcp-runtime.js +41 -0
- package/dist/mcp/mcp-runtime.js.map +1 -1
- package/dist/mcp/ui-debug-tools.d.ts +56 -0
- package/dist/mcp/ui-debug-tools.d.ts.map +1 -0
- package/dist/mcp/ui-debug-tools.js +412 -0
- package/dist/mcp/ui-debug-tools.js.map +1 -0
- package/dist/physics/index.js +1 -1
- package/dist/physics/physics-runtime.d.ts +22 -0
- package/dist/physics/physics-runtime.d.ts.map +1 -0
- package/dist/physics/physics-runtime.js +423 -0
- package/dist/physics/physics-runtime.js.map +1 -0
- package/dist/physics/physics-system.d.ts +84 -108
- package/dist/physics/physics-system.d.ts.map +1 -1
- package/dist/physics/physics-system.js +686 -555
- package/dist/physics/physics-system.js.map +1 -1
- package/dist/physics/physics-transport.d.ts +20 -0
- package/dist/physics/physics-transport.d.ts.map +1 -0
- package/dist/physics/physics-transport.js +111 -0
- package/dist/physics/physics-transport.js.map +1 -0
- package/dist/physics/physics-worker-protocol.d.ts +144 -0
- package/dist/physics/physics-worker-protocol.d.ts.map +1 -0
- package/dist/physics/physics-worker-protocol.js +41 -0
- package/dist/physics/physics-worker-protocol.js.map +1 -0
- package/dist/physics/physics-worker-utils.d.ts +8 -0
- package/dist/physics/physics-worker-utils.d.ts.map +1 -0
- package/dist/physics/physics-worker-utils.js +12 -0
- package/dist/physics/physics-worker-utils.js.map +1 -0
- package/dist/physics/physics-worker.d.ts +8 -0
- package/dist/physics/physics-worker.d.ts.map +1 -0
- package/dist/physics/physics-worker.js +66 -0
- package/dist/physics/physics-worker.js.map +1 -0
- package/dist/project/normalize.js +7 -1
- package/dist/project/normalize.js.map +1 -1
- package/dist/project/types.d.ts +7 -1
- package/dist/project/types.d.ts.map +1 -1
- package/dist/project/validation.js +36 -2
- package/dist/project/validation.js.map +1 -1
- package/dist/scene-understanding/scene-understanding-system.d.ts +42 -6
- package/dist/scene-understanding/scene-understanding-system.d.ts.map +1 -1
- package/dist/scene-understanding/scene-understanding-system.js +391 -106
- package/dist/scene-understanding/scene-understanding-system.js.map +1 -1
- package/dist/schemas/iwsdk-project.v1.schema.json +20 -1
- package/dist/ui/follow.d.ts.map +1 -1
- package/dist/ui/follow.js +0 -1
- package/dist/ui/follow.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- 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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
geometry.
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
//
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
-
|
|
407
|
-
|
|
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
|
-
//
|
|
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
|
|
429
|
-
*
|
|
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
|
-
*
|
|
432
|
-
*
|
|
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
|