@multisetai/vps 1.0.6 → 1.0.7-beta.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.
@@ -1,60 +1,376 @@
1
- import * as THREE from 'three';
1
+ import * as THREE3 from 'three';
2
2
  import { ARButton } from 'three/examples/jsm/webxr/ARButton.js';
3
+ import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
4
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
3
5
 
4
- // src/lib/webxr/index.ts
6
+ // src/lib/webxr/controller.ts
7
+
8
+ // src/lib/webxr/internal/cameraIntrinsics.ts
9
+ function getCameraIntrinsics(projectionMatrix, viewport) {
10
+ const p = projectionMatrix;
11
+ const u0 = (1 - p[8]) * viewport.width / 2 + viewport.x;
12
+ const v0 = (1 - p[9]) * viewport.height / 2 + viewport.y;
13
+ const ax = viewport.width / 2 * p[0];
14
+ const ay = viewport.height / 2 * p[5];
15
+ return {
16
+ fx: ax,
17
+ fy: ay,
18
+ px: u0,
19
+ py: v0,
20
+ width: viewport.width,
21
+ height: viewport.height
22
+ };
23
+ }
24
+
25
+ // src/lib/webxr/internal/frameCapture.ts
26
+ async function compressToJpeg(buffer, width, height, quality = 0.8) {
27
+ const canvas = document.createElement("canvas");
28
+ canvas.width = width;
29
+ canvas.height = height;
30
+ const ctx = canvas.getContext("2d");
31
+ if (!ctx) return new Blob();
32
+ const imageData = new ImageData(new Uint8ClampedArray(buffer), width, height);
33
+ ctx.putImageData(imageData, 0, 0);
34
+ return new Promise((resolve) => {
35
+ canvas.toBlob((blob) => resolve(blob != null ? blob : new Blob()), "image/jpeg", quality);
36
+ });
37
+ }
38
+ async function getCameraTextureAsImage(renderer, webGLTexture, width, height) {
39
+ const gl = renderer.getContext();
40
+ if (!gl) return null;
41
+ const framebuffer = gl.createFramebuffer();
42
+ if (!framebuffer) return null;
43
+ let pixelBuffer;
44
+ try {
45
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
46
+ gl.framebufferTexture2D(
47
+ gl.FRAMEBUFFER,
48
+ gl.COLOR_ATTACHMENT0,
49
+ gl.TEXTURE_2D,
50
+ webGLTexture,
51
+ 0
52
+ );
53
+ pixelBuffer = new Uint8Array(width * height * 4);
54
+ gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixelBuffer);
55
+ } finally {
56
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
57
+ gl.deleteFramebuffer(framebuffer);
58
+ }
59
+ const flippedData = new Uint8ClampedArray(pixelBuffer.length);
60
+ for (let row = 0; row < height; row += 1) {
61
+ const sourceStart = row * width * 4;
62
+ const destStart = (height - row - 1) * width * 4;
63
+ flippedData.set(
64
+ pixelBuffer.subarray(sourceStart, sourceStart + width * 4),
65
+ destStart
66
+ );
67
+ }
68
+ const blob = await compressToJpeg(flippedData.buffer, width, height, 0.7);
69
+ if (!blob.size) {
70
+ return null;
71
+ }
72
+ return {
73
+ blob,
74
+ width,
75
+ height
76
+ };
77
+ }
78
+ var Experience = class {
79
+ constructor(canvas) {
80
+ this.renderer = new THREE3.WebGLRenderer({
81
+ canvas,
82
+ antialias: true,
83
+ alpha: true
84
+ });
85
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
86
+ this.renderer.setPixelRatio(window.devicePixelRatio);
87
+ this.renderer.xr.enabled = true;
88
+ this.camera = new THREE3.PerspectiveCamera(
89
+ 45,
90
+ window.innerWidth / window.innerHeight,
91
+ 0.2,
92
+ 1e4
93
+ );
94
+ this.scene = new THREE3.Scene();
95
+ this.setupLights();
96
+ }
97
+ setupLights() {
98
+ const light = new THREE3.HemisphereLight(16777215, 16314623, 1);
99
+ light.position.set(0.5, 2, 0.25);
100
+ this.scene.add(light);
101
+ const diLight = new THREE3.DirectionalLight("#7B2CBF");
102
+ diLight.position.set(0, 2, 0);
103
+ this.scene.add(diLight);
104
+ }
105
+ getScene() {
106
+ return this.scene;
107
+ }
108
+ getCamera() {
109
+ return this.camera;
110
+ }
111
+ getRenderer() {
112
+ return this.renderer;
113
+ }
114
+ resize() {
115
+ this.camera.aspect = window.innerWidth / window.innerHeight;
116
+ this.camera.updateProjectionMatrix();
117
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
118
+ }
119
+ dispose() {
120
+ this.renderer.dispose();
121
+ this.scene = null;
122
+ this.camera = null;
123
+ this.renderer = null;
124
+ }
125
+ };
126
+ var VERTEX = `
127
+ varying vec3 vWorldPosition;
128
+
129
+ void main() {
130
+ vec4 worldPos = modelMatrix * vec4(position, 1.0);
131
+ vWorldPosition = worldPos.xyz;
132
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
133
+ }
134
+ `;
135
+ var FRAGMENT = `
136
+ uniform vec3 uColor;
137
+ uniform float uOpacity;
138
+ uniform vec3 uGridColor;
139
+ uniform float uGridScale;
140
+
141
+ varying vec3 vWorldPosition;
142
+
143
+ void main() {
144
+ vec2 coord = vWorldPosition.xz * uGridScale;
145
+ vec2 grid = abs(fract(coord - 0.5) - 0.5) / fwidth(coord);
146
+ float line = min(grid.x, grid.y) / 2.0;
147
+ float gridLine = 1.0 - min(line, 1.0);
148
+
149
+ vec3 base = uColor;
150
+ vec3 gridCol = uGridColor;
151
+ float alpha = mix(uOpacity, 1.0, gridLine);
152
+ vec3 color = mix(base, gridCol, gridLine);
153
+ gl_FragColor = vec4(color, alpha);
154
+ }
155
+ `;
156
+ function createGridMaterial(options = {}) {
157
+ var _a, _b, _c, _d;
158
+ const color = (_a = options.color) != null ? _a : "#7B2CBF";
159
+ const opacity = (_b = options.opacity) != null ? _b : 0.6;
160
+ const gridColor = (_c = options.gridColor) != null ? _c : "#ffeb3b";
161
+ const gridScale = (_d = options.gridScale) != null ? _d : 2;
162
+ return new THREE3.ShaderMaterial({
163
+ vertexShader: VERTEX,
164
+ fragmentShader: FRAGMENT,
165
+ transparent: true,
166
+ side: THREE3.DoubleSide,
167
+ uniforms: {
168
+ uColor: { value: typeof color === "string" ? new THREE3.Color(color) : color },
169
+ uOpacity: { value: opacity },
170
+ uGridColor: { value: typeof gridColor === "string" ? new THREE3.Color(gridColor) : gridColor },
171
+ uGridScale: { value: gridScale }
172
+ }
173
+ });
174
+ }
175
+
176
+ // src/lib/webxr/mapMeshVisualizer.ts
177
+ var LARGE_MAP_THRESHOLD = 50;
178
+ var MapMeshVisualizer = class {
179
+ constructor(scene, client) {
180
+ this.scene = scene;
181
+ this.client = client;
182
+ this.meshGroup = new THREE3.Group();
183
+ this.meshGroup.visible = false;
184
+ this.scene.add(this.meshGroup);
185
+ this.dracoLoader = new DRACOLoader();
186
+ this.dracoLoader.setDecoderPath("/draco/");
187
+ this.gltfLoader = new GLTFLoader();
188
+ this.gltfLoader.setDRACOLoader(this.dracoLoader);
189
+ }
190
+ getMeshGroup() {
191
+ return this.meshGroup;
192
+ }
193
+ async ensureMeshLoaded(mapDetails) {
194
+ var _a, _b;
195
+ if (this.scene.getObjectByName(mapDetails._id)) {
196
+ return;
197
+ }
198
+ const meshKey = (_b = (_a = mapDetails.mapMesh) == null ? void 0 : _a.rawMesh) == null ? void 0 : _b.meshLink;
199
+ if (!meshKey) {
200
+ return;
201
+ }
202
+ const url = await this.client.downloadFile(meshKey);
203
+ if (!url) {
204
+ return;
205
+ }
206
+ const meshMaterial = createGridMaterial({
207
+ color: "#7B2CBF",
208
+ opacity: 0.6,
209
+ gridColor: "#ffeb3b",
210
+ gridScale: 2
211
+ });
212
+ await new Promise((resolve, reject) => {
213
+ this.gltfLoader.load(
214
+ url,
215
+ (gltf) => {
216
+ gltf.scene.traverse((child) => {
217
+ if (child.isMesh) {
218
+ child.material = meshMaterial;
219
+ }
220
+ });
221
+ const box = new THREE3.Box3().setFromObject(gltf.scene);
222
+ let size = box.getSize(new THREE3.Vector3()).length();
223
+ box.getCenter(new THREE3.Vector3());
224
+ if (size > LARGE_MAP_THRESHOLD) {
225
+ size = LARGE_MAP_THRESHOLD;
226
+ new THREE3.Vector3();
227
+ }
228
+ gltf.scene.name = mapDetails._id;
229
+ this.meshGroup.add(gltf.scene);
230
+ resolve();
231
+ },
232
+ void 0,
233
+ (error) => {
234
+ reject(error instanceof Error ? error : new Error(String(error)));
235
+ }
236
+ );
237
+ });
238
+ }
239
+ applyMeshTransform(best, trackerSpace) {
240
+ const pose = best.localizeData;
241
+ const resPosition = new THREE3.Vector3(
242
+ pose.position.x,
243
+ pose.position.y,
244
+ pose.position.z
245
+ );
246
+ const resRotation = new THREE3.Quaternion(
247
+ pose.rotation.x,
248
+ pose.rotation.y,
249
+ pose.rotation.z,
250
+ pose.rotation.w
251
+ );
252
+ const responseMatrix = new THREE3.Matrix4();
253
+ responseMatrix.compose(resPosition, resRotation, new THREE3.Vector3(1, 1, 1));
254
+ const inverseResponseMatrix = responseMatrix.clone().invert();
255
+ const resultantMatrix = new THREE3.Matrix4();
256
+ resultantMatrix.multiplyMatrices(trackerSpace, inverseResponseMatrix);
257
+ const position = new THREE3.Vector3();
258
+ const rotation = new THREE3.Quaternion();
259
+ const scale = new THREE3.Vector3();
260
+ resultantMatrix.decompose(position, rotation, scale);
261
+ this.meshGroup.position.copy(position);
262
+ this.meshGroup.quaternion.copy(rotation);
263
+ this.meshGroup.scale.set(1, 1, 1);
264
+ this.meshGroup.visible = true;
265
+ this.meshGroup.updateMatrix();
266
+ }
267
+ dispose() {
268
+ this.meshGroup.traverse((child) => {
269
+ const mesh = child;
270
+ if (mesh.isMesh) {
271
+ mesh.geometry.dispose();
272
+ if (Array.isArray(mesh.material)) {
273
+ mesh.material.forEach((m) => m.dispose());
274
+ } else {
275
+ mesh.material.dispose();
276
+ }
277
+ }
278
+ });
279
+ this.scene.remove(this.meshGroup);
280
+ this.dracoLoader.dispose();
281
+ }
282
+ };
283
+
284
+ // src/lib/webxr/world/World.ts
285
+ var World = class {
286
+ constructor(scene, client) {
287
+ this.meshVisualizer = new MapMeshVisualizer(scene, client);
288
+ }
289
+ async ensureMeshLoaded(mapDetails) {
290
+ await this.meshVisualizer.ensureMeshLoaded(mapDetails);
291
+ }
292
+ applyMeshTransform(best, trackerSpace) {
293
+ this.meshVisualizer.applyMeshTransform(best, trackerSpace);
294
+ }
295
+ dispose() {
296
+ this.meshVisualizer.dispose();
297
+ }
298
+ };
299
+
300
+ // src/lib/webxr/controller.ts
301
+ function clamp(value, min, max) {
302
+ return Math.max(min, Math.min(max, value));
303
+ }
304
+ var REQUEST_ATTEMPTS_MIN = 1;
305
+ var REQUEST_ATTEMPTS_MAX = 5;
306
+ var LOCALIZATION_INTERVAL_MIN = 1;
307
+ var LOCALIZATION_INTERVAL_MAX = 5;
308
+ var CONFIDENCE_THRESHOLD_MIN = 0.2;
309
+ var CONFIDENCE_THRESHOLD_MAX = 0.8;
310
+ var TRACKING_LOSS_FRAME_THRESHOLD = 60;
5
311
  var WebxrController = class {
6
312
  constructor(options) {
7
313
  this.options = options;
8
- this.renderer = null;
9
- this.camera = null;
10
- this.scene = null;
11
- this.animationLoop = null;
314
+ this.experience = null;
315
+ this.world = null;
12
316
  this.arButton = null;
13
317
  this.resizeHandler = null;
14
318
  this.isSessionActive = false;
319
+ this.trackingLossFrames = 0;
320
+ this.isLocalizing = false;
321
+ this.trackerSpace = null;
15
322
  }
16
323
  async initialize(buttonContainer) {
17
324
  var _a, _b, _c, _d;
18
- if (this.renderer) {
325
+ if (this.experience) {
19
326
  return this.arButton;
20
327
  }
21
328
  if (!window.isSecureContext) {
22
329
  throw new Error("WebXR requires a secure context (HTTPS).");
23
330
  }
24
331
  const canvas = (_a = this.options.canvas) != null ? _a : document.createElement("canvas");
25
- const renderer = new THREE.WebGLRenderer({
26
- canvas,
27
- antialias: true,
28
- alpha: true
29
- });
30
- renderer.setSize(window.innerWidth, window.innerHeight);
31
- renderer.setPixelRatio(window.devicePixelRatio);
32
- renderer.xr.enabled = true;
332
+ this.experience = new Experience(canvas);
333
+ this.world = new World(this.experience.getScene(), this.options.client);
334
+ const renderer = this.experience.getRenderer();
335
+ const camera = this.experience.getCamera();
336
+ const scene = this.experience.getScene();
33
337
  renderer.xr.addEventListener("sessionstart", () => {
34
338
  var _a2, _b2;
35
339
  this.isSessionActive = true;
36
340
  (_b2 = (_a2 = this.options).onSessionStart) == null ? void 0 : _b2.call(_a2);
341
+ const cfg = this.options.client.getConfig();
342
+ if (cfg.autoLocalize) {
343
+ void this.localizeFrame();
344
+ }
37
345
  });
38
346
  renderer.xr.addEventListener("sessionend", () => {
39
347
  var _a2, _b2;
40
348
  this.isSessionActive = false;
41
349
  (_b2 = (_a2 = this.options).onSessionEnd) == null ? void 0 : _b2.call(_a2);
42
350
  });
43
- const camera = new THREE.PerspectiveCamera(
44
- 45,
45
- window.innerWidth / window.innerHeight,
46
- 0.2,
47
- 1e4
48
- );
49
- const scene = new THREE.Scene();
50
- const animationLoop = () => {
351
+ const animationLoop = (_time, frame) => {
51
352
  renderer.render(scene, camera);
353
+ const cfg = this.options.client.getConfig();
354
+ if (cfg.relocalization && frame && !this.isLocalizing) {
355
+ const refSpace = renderer.xr.getReferenceSpace();
356
+ if (refSpace) {
357
+ const viewerPose = frame.getViewerPose(refSpace);
358
+ if (!viewerPose) {
359
+ this.trackingLossFrames += 1;
360
+ if (this.trackingLossFrames >= TRACKING_LOSS_FRAME_THRESHOLD) {
361
+ this.trackingLossFrames = 0;
362
+ void this.localizeFrame();
363
+ }
364
+ } else {
365
+ this.trackingLossFrames = 0;
366
+ }
367
+ }
368
+ }
52
369
  };
53
370
  renderer.setAnimationLoop(animationLoop);
54
371
  const resizeHandler = () => {
55
- camera.aspect = window.innerWidth / window.innerHeight;
56
- camera.updateProjectionMatrix();
57
- renderer.setSize(window.innerWidth, window.innerHeight);
372
+ var _a2;
373
+ (_a2 = this.experience) == null ? void 0 : _a2.resize();
58
374
  };
59
375
  window.addEventListener("resize", resizeHandler);
60
376
  const overlayRoot = (_b = this.options.overlayRoot) != null ? _b : document.body;
@@ -66,51 +382,142 @@ var WebxrController = class {
66
382
  if (!buttonParent.contains(arButton)) {
67
383
  buttonParent.appendChild(arButton);
68
384
  }
69
- this.renderer = renderer;
70
- this.camera = camera;
71
- this.scene = scene;
72
- this.animationLoop = animationLoop;
73
385
  this.arButton = arButton;
74
386
  this.resizeHandler = resizeHandler;
75
387
  (_d = (_c = this.options).onARButtonCreated) == null ? void 0 : _d.call(_c, arButton);
76
388
  return arButton;
77
389
  }
78
390
  getScene() {
79
- if (!this.scene) {
391
+ if (!this.experience) {
80
392
  throw new Error("Scene: WebXR controller has not been initialized.");
81
393
  }
82
- return this.scene;
394
+ return this.experience.getScene();
83
395
  }
84
396
  getCamera() {
85
- if (!this.camera) {
397
+ if (!this.experience) {
86
398
  throw new Error("Camera: WebXR controller has not been initialized.");
87
399
  }
88
- return this.camera;
400
+ return this.experience.getCamera();
89
401
  }
90
402
  getRenderer() {
91
- if (!this.renderer) {
403
+ if (!this.experience) {
92
404
  throw new Error("Renderer: WebXR controller has not been initialized.");
93
405
  }
94
- return this.renderer;
406
+ return this.experience.getRenderer();
95
407
  }
96
408
  hasActiveSession() {
97
409
  var _a;
98
- return this.isSessionActive && ((_a = this.renderer) == null ? void 0 : _a.xr.isPresenting) === true;
410
+ return this.isSessionActive && ((_a = this.experience) == null ? void 0 : _a.getRenderer().xr.isPresenting) === true;
99
411
  }
412
+ /**
413
+ * Runs a single-frame localization cycle: multiple attempts (per requestAttempts),
414
+ * picks best result by confidence, optionally validates against confidenceThreshold,
415
+ * and fires onLocalizationInit / onLocalizationSuccess / onLocalizationFailure.
416
+ * Aligns with Unity SingleFrameLocalizationManager.LocalizeFrame().
417
+ */
418
+ async localizeFrame() {
419
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
420
+ if (!this.experience) {
421
+ throw new Error("WebXR: WebXR controller has not been initialized.");
422
+ }
423
+ const renderer = this.experience.getRenderer();
424
+ const session = (_b = (_a = renderer.xr).getSession) == null ? void 0 : _b.call(_a);
425
+ if (!session) {
426
+ throw new Error(
427
+ "WebXR Session: No active WebXR session. Start AR before calling localizeFrame()."
428
+ );
429
+ }
430
+ const cfg = this.options.client.getConfig();
431
+ const requestAttempts = clamp(
432
+ (_c = cfg.requestAttempts) != null ? _c : 1,
433
+ REQUEST_ATTEMPTS_MIN,
434
+ REQUEST_ATTEMPTS_MAX
435
+ );
436
+ const localizationInterval = clamp(
437
+ (_d = cfg.localizationInterval) != null ? _d : 1,
438
+ LOCALIZATION_INTERVAL_MIN,
439
+ LOCALIZATION_INTERVAL_MAX
440
+ );
441
+ const confidenceCheck = (_e = cfg.confidenceCheck) != null ? _e : false;
442
+ const confidenceThreshold = clamp(
443
+ (_f = cfg.confidenceThreshold) != null ? _f : 0.5,
444
+ CONFIDENCE_THRESHOLD_MIN,
445
+ CONFIDENCE_THRESHOLD_MAX
446
+ );
447
+ (_g = cfg.onLocalizationInit) == null ? void 0 : _g.call(cfg);
448
+ this.isLocalizing = true;
449
+ const results = [];
450
+ const MAX_FRAME_ATTEMPTS = requestAttempts * 3;
451
+ try {
452
+ let apiCallsDone = 0;
453
+ let frameAttempts = 0;
454
+ while (apiCallsDone < requestAttempts && frameAttempts < MAX_FRAME_ATTEMPTS) {
455
+ frameAttempts++;
456
+ try {
457
+ const { result, apiCalled } = await this.captureFrame();
458
+ if (apiCalled) {
459
+ apiCallsDone++;
460
+ if ((_h = result == null ? void 0 : result.localizeData) == null ? void 0 : _h.poseFound) {
461
+ results.push(result);
462
+ }
463
+ }
464
+ } catch {
465
+ apiCallsDone++;
466
+ }
467
+ if (apiCallsDone < requestAttempts && frameAttempts < MAX_FRAME_ATTEMPTS) {
468
+ await new Promise(
469
+ (r) => setTimeout(r, localizationInterval * 1e3)
470
+ );
471
+ }
472
+ }
473
+ } finally {
474
+ this.isLocalizing = false;
475
+ }
476
+ const best = results.length ? results.sort(
477
+ (a, b) => {
478
+ var _a2, _b2;
479
+ return ((_a2 = b.localizeData.confidence) != null ? _a2 : 0) - ((_b2 = a.localizeData.confidence) != null ? _b2 : 0);
480
+ }
481
+ )[0] : null;
482
+ const accepted = best && (!confidenceCheck || ((_i = best.localizeData.confidence) != null ? _i : 0) >= confidenceThreshold);
483
+ if (accepted && best) {
484
+ (_j = cfg.onLocalizationSuccess) == null ? void 0 : _j.call(cfg, best);
485
+ if (cfg.showMesh && best.mapDetails && this.world && this.trackerSpace) {
486
+ try {
487
+ await this.world.ensureMeshLoaded(best.mapDetails);
488
+ this.world.applyMeshTransform(best, this.trackerSpace);
489
+ } catch {
490
+ }
491
+ }
492
+ return best;
493
+ }
494
+ const reason = !best ? "All attempts failed to produce a pose." : confidenceCheck ? `Best confidence ${(_k = best.localizeData.confidence) != null ? _k : 0} below threshold ${confidenceThreshold}.` : void 0;
495
+ (_l = cfg.onLocalizationFailure) == null ? void 0 : _l.call(cfg, reason);
496
+ return null;
497
+ }
498
+ /**
499
+ * Internal: captures one frame and calls the localization API. Returns the
500
+ * result and whether the API was actually invoked — used by localizeFrame()
501
+ * to count only real API calls toward requestAttempts (frames with no camera
502
+ * image are skipped).
503
+ */
100
504
  async captureFrame() {
101
505
  var _a, _b;
102
- const renderer = this.renderer;
103
- const camera = this.camera;
104
- if (!renderer || !camera) {
506
+ if (!this.experience) {
105
507
  throw new Error("WebXR: WebXR controller has not been initialized.");
106
508
  }
509
+ const renderer = this.experience.getRenderer();
107
510
  const session = (_b = (_a = renderer.xr).getSession) == null ? void 0 : _b.call(_a);
108
511
  if (!session) {
109
- throw new Error("WebXR Session: No active WebXR session. Start AR before capturing.");
512
+ throw new Error(
513
+ "WebXR Session: No active WebXR session. Start AR before capturing."
514
+ );
110
515
  }
111
516
  const referenceSpace = renderer.xr.getReferenceSpace();
112
517
  if (!referenceSpace) {
113
- throw new Error("WebXR Reference Space: Unable to acquire XR reference space.");
518
+ throw new Error(
519
+ "WebXR Reference Space: Unable to acquire XR reference space."
520
+ );
114
521
  }
115
522
  const gl = renderer.getContext();
116
523
  return new Promise((resolve, reject) => {
@@ -119,7 +526,7 @@ var WebxrController = class {
119
526
  try {
120
527
  const viewerPose = xrFrame.getViewerPose(referenceSpace);
121
528
  if (!viewerPose) {
122
- resolve(null);
529
+ resolve({ result: null, apiCalled: false });
123
530
  return;
124
531
  }
125
532
  for (const view of viewerPose.views) {
@@ -146,15 +553,16 @@ var WebxrController = class {
146
553
  y: 0
147
554
  });
148
555
  if (frameData && intrinsics) {
556
+ this.trackerSpace = new THREE3.Matrix4().fromArray(view.transform.matrix);
149
557
  const result = await this.options.client.localizeWithFrame(
150
558
  frameData,
151
559
  intrinsics
152
560
  );
153
- resolve(result);
561
+ resolve({ result, apiCalled: true });
154
562
  return;
155
563
  }
156
564
  }
157
- resolve(null);
565
+ resolve({ result: null, apiCalled: false });
158
566
  } catch (error) {
159
567
  reject(error);
160
568
  } finally {
@@ -167,85 +575,22 @@ var WebxrController = class {
167
575
  });
168
576
  }
169
577
  dispose() {
170
- var _a;
578
+ var _a, _b, _c, _d;
171
579
  if (this.resizeHandler) {
172
580
  window.removeEventListener("resize", this.resizeHandler);
173
581
  }
174
- if (this.renderer) {
175
- this.renderer.dispose();
176
- this.renderer = null;
177
- }
178
- this.animationLoop = null;
179
- this.camera = null;
180
- this.scene = null;
181
- if ((_a = this.arButton) == null ? void 0 : _a.parentElement) {
582
+ (_a = this.experience) == null ? void 0 : _a.getRenderer().setAnimationLoop(null);
583
+ (_b = this.experience) == null ? void 0 : _b.dispose();
584
+ this.experience = null;
585
+ (_c = this.world) == null ? void 0 : _c.dispose();
586
+ this.world = null;
587
+ this.trackingLossFrames = 0;
588
+ if ((_d = this.arButton) == null ? void 0 : _d.parentElement) {
182
589
  this.arButton.parentElement.removeChild(this.arButton);
183
590
  }
184
591
  this.arButton = null;
185
592
  }
186
593
  };
187
- function getCameraIntrinsics(projectionMatrix, viewport) {
188
- const p = projectionMatrix;
189
- const u0 = (1 - p[8]) * viewport.width / 2 + viewport.x;
190
- const v0 = (1 - p[9]) * viewport.height / 2 + viewport.y;
191
- const ax = viewport.width / 2 * p[0];
192
- const ay = viewport.height / 2 * p[5];
193
- return {
194
- fx: ax,
195
- fy: ay,
196
- px: u0,
197
- py: v0,
198
- width: viewport.width,
199
- height: viewport.height
200
- };
201
- }
202
- async function compressToJpeg(buffer, width, height, quality = 0.8) {
203
- const canvas = document.createElement("canvas");
204
- const ctx = canvas.getContext("2d");
205
- canvas.width = width;
206
- canvas.height = height;
207
- const imageData = new ImageData(new Uint8ClampedArray(buffer), width, height);
208
- ctx == null ? void 0 : ctx.putImageData(imageData, 0, 0);
209
- return new Promise((resolve) => {
210
- canvas.toBlob((blob) => resolve(blob != null ? blob : new Blob()), "image/jpeg", quality);
211
- });
212
- }
213
- async function getCameraTextureAsImage(renderer, webGLTexture, width, height) {
214
- const gl = renderer.getContext();
215
- if (!gl) return null;
216
- const framebuffer = gl.createFramebuffer();
217
- if (!framebuffer) return null;
218
- gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
219
- gl.framebufferTexture2D(
220
- gl.FRAMEBUFFER,
221
- gl.COLOR_ATTACHMENT0,
222
- gl.TEXTURE_2D,
223
- webGLTexture,
224
- 0
225
- );
226
- const pixelBuffer = new Uint8Array(width * height * 4);
227
- gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixelBuffer);
228
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
229
- gl.deleteFramebuffer(framebuffer);
230
- const flippedData = new Uint8ClampedArray(pixelBuffer.length);
231
- for (let row = 0; row < height; row += 1) {
232
- const sourceStart = row * width * 4;
233
- const destStart = (height - row - 1) * width * 4;
234
- flippedData.set(
235
- pixelBuffer.subarray(sourceStart, sourceStart + width * 4),
236
- destStart
237
- );
238
- }
239
- const blob = await compressToJpeg(flippedData.buffer, width, height, 0.7);
240
- if (!blob.size) {
241
- return null;
242
- }
243
- return {
244
- blob,
245
- width,
246
- height
247
- };
248
- }
249
594
 
250
595
  export { WebxrController };
251
596
  //# sourceMappingURL=index.js.map