@multisetai/vps 2.1.1 → 2.2.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # MultiSet VPS WebXR
2
2
 
3
- TypeScript SDK for integrating MultiSet's Visual Positioning System (VPS) into WebXR applications. Provides 6-DOF localization by matching camera frames against cloud-hosted maps.
3
+ TypeScript SDK for integrating MultiSet's Visual Positioning System (VPS) into WebXR applications. Provides 6-DOF localization by matching camera frames against cloud-hosted maps, and object tracking by matching camera frames against registered 3D objects.
4
4
 
5
5
  ## Architecture
6
6
 
@@ -45,7 +45,7 @@ Without this, the browser will block every API request with a CORS error.
45
45
 
46
46
  ## Quick Start
47
47
 
48
- ### With Three.js
48
+ ### VPS Localization — Three.js
49
49
 
50
50
  ```typescript
51
51
  import * as THREE from 'three';
@@ -81,7 +81,6 @@ const session = new XRSessionManager(renderer.getContext() as WebGL2RenderingCon
81
81
  confidenceThreshold: 0.5,
82
82
  onSessionStart: () => {
83
83
  // Hide the canvas during AR — the XR compositor owns the display.
84
- // Leaving it visible shows the stale preview frame on top of the AR scene.
85
84
  renderer.domElement.style.display = 'none';
86
85
  },
87
86
  onSessionEnd: () => {
@@ -102,7 +101,9 @@ scene.add(new THREE.Mesh(
102
101
  ));
103
102
  ```
104
103
 
105
- ### Without Three.js (vanilla WebXR)
104
+ ### VPS Localization — Without Three.js (WebGL2 / Vanilla)
105
+
106
+ In this mode the SDK manages the session lifecycle, camera capture, and localization. You are responsible for all rendering: each XR frame, draw your scene into `event.baseLayer.framebuffer` using the provided `gl` context. This approach works with any WebGL2-based renderer — Babylon.js, raw WebGL, or your own engine.
106
107
 
107
108
  ```typescript
108
109
  import { MultisetClient, XRSessionManager } from '@multisetai/vps/core';
@@ -115,6 +116,7 @@ if (!supported) {
115
116
  const client = new MultisetClient({ clientId: '...', clientSecret: '...', code: '...', mapType: 'map' });
116
117
  await client.authorize();
117
118
 
119
+ // A WebGL2 context is required — WebXR renders into a GL framebuffer, not a 2D canvas.
118
120
  const gl = document.querySelector('canvas')!.getContext('webgl2')!;
119
121
 
120
122
  const session = new XRSessionManager(gl, {
@@ -125,15 +127,70 @@ const session = new XRSessionManager(gl, {
125
127
  onError: (err) => console.error(err),
126
128
  });
127
129
 
128
- // Wire your own render loop before creating the button
130
+ // Wire your render loop called every XR frame with the current pose and framebuffer.
129
131
  session.setXRFrameHandler((event) => {
130
- // event: { time, frame, pose, view, viewport, baseLayer, referenceSpace, deltaSeconds }
131
- // render here using event.baseLayer.framebuffer
132
+ // Bind event.baseLayer.framebuffer and render your scene using event.view for camera matrices.
132
133
  });
133
134
 
134
135
  document.body.appendChild(session.createButton());
135
136
  ```
136
137
 
138
+ ### Object Tracking — Three.js
139
+
140
+ Object tracking detects and poses registered 3D objects by matching a captured camera frame against the MultiSet cloud.
141
+
142
+ ```typescript
143
+ import * as THREE from 'three';
144
+ import { MultisetClient, XRSessionManager } from '@multisetai/vps/core';
145
+ import { ThreeAdapter } from '@multisetai/vps/three';
146
+
147
+ const client = new MultisetClient({
148
+ clientId: 'CLIENT_ID',
149
+ clientSecret: 'CLIENT_SECRET',
150
+ code: '', // unused for object tracking — pass an empty string
151
+ mapType: 'map',
152
+ isRightHanded: true,
153
+ });
154
+
155
+ await client.authorize();
156
+
157
+ const renderer = new THREE.WebGLRenderer({ antialias: true });
158
+ document.body.appendChild(renderer.domElement);
159
+ const scene = new THREE.Scene();
160
+ const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 1000);
161
+
162
+ const session = new XRSessionManager(renderer.getContext() as WebGL2RenderingContext, {
163
+ client,
164
+ overlayRoot: document.body,
165
+ objectCodes: ['YOUR_OBJECT_CODE'],
166
+ autoTracking: true, // detect once on session start
167
+ confidenceCheck: true,
168
+ confidenceThreshold: 0.5,
169
+ onSessionStart: () => { renderer.domElement.style.display = 'none'; },
170
+ onSessionEnd: () => { renderer.domElement.style.display = 'block'; },
171
+ onObjectTrackingSuccess: (result) => {
172
+ console.log('Detected', result.objectCodes, 'at', result.position);
173
+ },
174
+ onObjectTrackingFailure: (reason) => console.warn('Tracking failed:', reason),
175
+ onError: (err) => console.error(err),
176
+ });
177
+
178
+ const adapter = new ThreeAdapter({
179
+ session,
180
+ renderer,
181
+ scene,
182
+ camera,
183
+ showObjectMeshes: true, // load and display the 3D outline mesh
184
+ onObjectMeshLoaded: (code) => console.log('Mesh loaded for', code),
185
+ });
186
+ adapter.initialize();
187
+
188
+ // Trigger tracking manually from a button
189
+ trackButton.addEventListener('click', () => {
190
+ void adapter.trackObjects();
191
+ });
192
+ ```
193
+
137
194
  ---
138
195
 
139
196
  ## Canvas Visibility During AR
@@ -153,7 +210,7 @@ onSessionEnd: () => { renderer.domElement.style.display = 'block'; },
153
210
 
154
211
  ### `MultisetClient`
155
212
 
156
- Pure HTTP client for auth and localization. No WebXR or rendering concerns.
213
+ Pure HTTP client for auth, localization, and object tracking. No WebXR or rendering concerns.
157
214
 
158
215
  ```typescript
159
216
  new MultisetClient(config: IMultisetClientConfig)
@@ -165,7 +222,7 @@ new MultisetClient(config: IMultisetClientConfig)
165
222
  |---|---|---|
166
223
  | `clientId` | `string` | Your MultiSet client ID |
167
224
  | `clientSecret` | `string` | Your MultiSet client secret |
168
- | `code` | `string` | Map or map-set code |
225
+ | `code` | `string` | Map or map-set code. Pass an empty string when using object tracking only. |
169
226
  | `mapType` | `'map' \| 'map-set'` | Whether `code` is a single map or a map set |
170
227
  | `endpoints?` | `Partial<IMultisetSdkEndpoints>` | Override default API endpoints |
171
228
  | `isRightHanded?` | `boolean` | Handedness sent to the API. Default `true` |
@@ -181,14 +238,16 @@ new MultisetClient(config: IMultisetClientConfig)
181
238
  | Method | Returns | Description |
182
239
  |---|---|---|
183
240
  | `authorize()` | `Promise<string>` | Authenticate and cache an access token. Call before any other method. |
184
- | `localizeWithFrame(frame, intrinsics)` | `Promise<ILocalizeAndMapDetails \| null>` | Submit a captured frame for localization. |
241
+ | `localizeWithFrame(frame, intrinsics)` | `Promise<ILocalizeAndMapDetails \| null>` | Submit a captured frame for VPS localization. |
242
+ | `trackObject(frame, intrinsics, objectCodes)` | `Promise<IObjectTrackingResponse \| null>` | Submit a captured frame for object detection. Returns `null` when no object is detected. |
243
+ | `downloadObjectMesh(objectCode)` | `Promise<string \| null>` | Fetch a signed download URL for the 3D mesh of an object. |
185
244
  | `fetchMapDetails(mapCode)` | `Promise<IGetMapsDetailsResponse \| null>` | Fetch map metadata by code (result is cached). |
186
245
 
187
246
  ---
188
247
 
189
248
  ### `XRSessionManager`
190
249
 
191
- Owns the WebXR session lifecycle — frame loop, camera capture, localization, and tracking-loss recovery. Zero Three.js dependency.
250
+ Owns the WebXR session lifecycle — frame loop, camera capture, localization, object tracking, and tracking-loss recovery. Zero Three.js dependency.
192
251
 
193
252
  ```typescript
194
253
  import { XRSessionManager } from '@multisetai/vps/core';
@@ -203,20 +262,30 @@ new XRSessionManager(gl: WebGL2RenderingContext, options: IXRSessionOptions)
203
262
  | `client` | `MultisetClient` | Required. |
204
263
  | `overlayRoot?` | `HTMLElement` | Root element for the WebXR DOM overlay. |
205
264
  | `autoLocalize?` | `boolean` | Run one localization automatically when the session starts. |
206
- | `relocalization?` | `boolean` | Re-localize whenever tracking is recovered after loss. |
207
- | `confidenceCheck?` | `boolean` | Only accept results with `confidence >= confidenceThreshold`. |
265
+ | `relocalization?` | `boolean` | Re-localize whenever tracking is lost and then recovered. |
266
+ | `confidenceCheck?` | `boolean` | Only accept results with `confidence >= confidenceThreshold`. Applies to both VPS and object tracking. |
208
267
  | `confidenceThreshold?` | `number` | Minimum confidence (0.2–0.8). Default `0.5`. |
209
268
  | `localizationTrackingTimeoutMs?` | `number` | Max ms to wait for a viewer pose before failing. Default `10000`. |
269
+ | `backgroundLocalization?` | `boolean` | Periodically send localization/tracking requests in the background while the session is active. |
270
+ | `bgLocalizationDuration?` | `number` | Interval in seconds between background attempts. Clamped to 10–180 s. Default `30` for VPS modes, `10` for object tracking. |
271
+ | `objectCodes?` | `string[]` | Object codes to detect. Required when calling `trackObjects()`. |
272
+ | `autoTracking?` | `boolean` | Call `trackObjects()` once automatically when the session starts. |
273
+ | `restartTracking?` | `boolean` | Re-attempt tracking whenever XR tracking is lost and then recovered. |
274
+ | `trackingCaptureDelayMs?` | `number` | Milliseconds to wait before capturing a frame when `trackObjects()` is called. Useful for camera stabilisation. Default `0`. |
210
275
  | `referenceSpaceType?` | `XRReferenceSpaceType` | XR reference space type. Default `'local'`. Use `'local-floor'` for floor-relative tracking if the device supports it. |
211
276
  | `framebufferScaleFactor?` | `number` | XR framebuffer scale relative to native resolution. Values `< 1` reduce GPU load; values `> 1` supersample. |
212
277
  | `onSessionStart?` | `() => void` | Called when the AR session starts. |
213
278
  | `onSessionEnd?` | `() => void` | Called when the AR session ends. |
214
- | `onLocalizationInit?` | `() => void` | Called at the start of a localization run. |
215
- | `onLocalizationSuccess?` | `(result: ILocalizeAndMapDetails) => void` | Called when localization succeeds (and passes the confidence check, if enabled). |
216
- | `onLocalizationFailure?` | `(reason?: string) => void` | Called when localization fails or falls below the confidence threshold. |
279
+ | `onLocalizationInit?` | `() => void` | Called at the start of a VPS localization run. |
280
+ | `onLocalizationSuccess?` | `(result: ILocalizeAndMapDetails) => void` | Called when VPS localization succeeds (and passes the confidence check, if enabled). |
281
+ | `onLocalizationFailure?` | `(reason?: string) => void` | Called when VPS localization fails or falls below the confidence threshold. |
217
282
  | `onFrameCaptured?` | `(frame: IFrameCaptureEvent) => void` | Called when a camera frame is captured for localization. |
218
283
  | `onCameraIntrinsics?` | `(intrinsics: ICameraIntrinsicsEvent) => void` | Called with camera intrinsic parameters for the captured frame. |
219
284
  | `onPoseResult?` | `(pose: IPoseResultEvent) => void` | Called with the raw pose result from the VPS backend. |
285
+ | `onObjectTrackingInit?` | `() => void` | Called at the start of an object tracking run. |
286
+ | `onObjectTrackingRequested?` | `(frame: IFrameCaptureEvent, intrinsics: ICameraIntrinsicsEvent) => void` | Called just before the tracking request is sent, with the captured frame and intrinsics. |
287
+ | `onObjectTrackingSuccess?` | `(result: IObjectTrackingResponse) => void` | Called when object tracking succeeds and passes the confidence check (if enabled). |
288
+ | `onObjectTrackingFailure?` | `(reason?: string) => void` | Called when object tracking fails or falls below the confidence threshold. |
220
289
  | `onError?` | `(error: unknown) => void` | Called when any error occurs. |
221
290
  | `onContextLost?` | `() => void` | Called when the WebGL context is lost. The active session is ended automatically. |
222
291
  | `onContextRestored?` | `() => void` | Called when the WebGL context is restored. The user may restart the session. |
@@ -234,11 +303,12 @@ new XRSessionManager(gl: WebGL2RenderingContext, options: IXRSessionOptions)
234
303
  | `createButton()` | `HTMLButtonElement` | Create the built-in styled AR button. Shows **START AR** / **STOP AR** and toggles the session on click. |
235
304
  | `startSession()` | `Promise<void>` | Start an AR session programmatically. **Must be called from within a user gesture handler** (click/tap). |
236
305
  | `stopSession()` | `void` | Stop the active AR session. No-op if no session is running. |
237
- | `localizeFrame()` | `Promise<ILocalizeAndMapDetails \| null>` | Capture and localize one frame. Requires an active session. |
306
+ | `localizeFrame()` | `Promise<ILocalizeAndMapDetails \| null>` | Capture and localize one frame against the configured map. Requires an active session. |
307
+ | `trackObjects()` | `Promise<IObjectTrackingResponse \| null>` | Capture one frame and run object detection against `objectCodes`. Requires an active session and `objectCodes` to be set. |
238
308
  | `isActive()` | `boolean` | Whether an XR session is currently running. |
239
- | `isLocalizing` | `boolean` | Whether a localization run is currently in progress. |
309
+ | `isLocalizing` | `boolean` | Whether a localization or tracking run is currently in progress. |
240
310
  | `getClient()` | `MultisetClient` | Access the underlying `MultisetClient`. |
241
- | `dispose()` | `void` | End the session, remove context loss listeners, and release all resources. |
311
+ | `dispose()` | `void` | End the session, clear background timers, remove context loss listeners, and release all resources. |
242
312
 
243
313
  #### Adapter hooks
244
314
 
@@ -247,14 +317,15 @@ Used internally by `ThreeAdapter`. Only call these when building a custom render
247
317
  | Method | Description |
248
318
  |---|---|
249
319
  | `setXRFrameHandler(fn)` | Called every XR frame with pose, view, viewport, and framebuffer info. |
250
- | `setAdapterResultHandler(fn)` | Called after a successful localization with the result and tracker-space matrix. |
320
+ | `setAdapterResultHandler(fn)` | Called after a successful VPS localization with the result and tracker-space matrix. |
321
+ | `setAdapterObjectTrackingHandler(fn)` | Called after a successful object tracking result with the result and tracker-space matrix. |
251
322
  | `setAdapterSessionHandlers(onStart, onEnd)` | Called on session start/end, before user callbacks. |
252
323
 
253
324
  ---
254
325
 
255
326
  ### `ThreeAdapter`
256
327
 
257
- Wires `XRSessionManager` to a Three.js renderer. Handles XR framebuffer binding, camera matrix sync, preview loop, resize, and optional map mesh / gizmo display.
328
+ Wires `XRSessionManager` to a Three.js renderer. Handles XR framebuffer binding, camera matrix sync, preview loop, resize, and optional map mesh / gizmo / object mesh display.
258
329
 
259
330
  ```typescript
260
331
  import { ThreeAdapter } from '@multisetai/vps/three';
@@ -272,11 +343,13 @@ new ThreeAdapter(options: IThreeAdapterOptions)
272
343
  | `camera` | `THREE.PerspectiveCamera` | Required. |
273
344
  | `showMesh?` | `boolean` | Show the VPS map mesh after localization. Default `false`. |
274
345
  | `showGizmo?` | `boolean` | Show a transform gizmo after localization. Default `true`. |
346
+ | `showObjectMeshes?` | `boolean` | Load and display a 3D outline mesh for each detected object. Default `false`. |
275
347
  | `useDefaultButton?` | `boolean` | Mount the built-in START AR / STOP AR button. Default `true`. Set to `false` to drive the session via `startSession()` / `stopSession()`. |
276
348
  | `buttonContainer?` | `HTMLElement` | Where to append the built-in button. Defaults to `overlayRoot` or `document.body`. |
277
349
  | `onButtonCreated?` | `(button: HTMLButtonElement) => void` | Called after the built-in button is created. |
278
350
  | `onXRFrame?` | `(event: IXRFrameEvent) => void` | Called every XR frame after camera matrices are synced, before the scene is rendered. Use this to update scene objects each frame. |
279
- | `onLocalizationSuccess?` | `(result: ILocalizeAndMapDetails, worldFromMap: THREE.Matrix4) => void` | Called after a successful localization. `worldFromMap` transforms any point from VPS map space into Three.js world space — use it to place scene objects at known physical locations in the scanned map. |
351
+ | `onLocalizationSuccess?` | `(result: ILocalizeAndMapDetails, worldFromMap: THREE.Matrix4) => void` | Called immediately after a successful VPS localization. `worldFromMap` transforms map-space coordinates to Three.js world space — use it to place content at known map coordinates. |
352
+ | `onObjectMeshLoaded?` | `(objectCode: string) => void` | Called when a detected object's 3D mesh has been loaded and placed in the scene. Only fires when `showObjectMeshes: true`. |
280
353
 
281
354
  #### Static methods
282
355
 
@@ -290,10 +363,12 @@ new ThreeAdapter(options: IThreeAdapterOptions)
290
363
  |---|---|---|
291
364
  | `initialize(buttonContainer?)` | `HTMLButtonElement \| null` | Start the preview render loop, attach resize handler, and mount the built-in button. Returns `null` when `useDefaultButton: false`. |
292
365
  | `isActive()` | `boolean` | Whether an XR session is currently running. |
293
- | `isLocalizing` | `boolean` | Whether a localization run is currently in progress. |
366
+ | `isLocalizing` | `boolean` | Whether a localization or tracking run is currently in progress. |
294
367
  | `startSession()` | `Promise<void>` | Start an AR session. **Must be called from within a user gesture handler.** |
295
368
  | `stopSession()` | `void` | Stop the active AR session. No-op if no session is running. |
296
369
  | `localizeFrame()` | `Promise<ILocalizeAndMapDetails \| null>` | Capture and localize one frame. |
370
+ | `trackObjects()` | `Promise<IObjectTrackingResponse \| null>` | Capture one frame and run object detection. Requires `objectCodes` to be set in the session options. |
371
+ | `clearObjectMeshes()` | `void` | Remove all object meshes from the scene that were placed by `showObjectMeshes`. |
297
372
  | `dispose()` | `void` | Stop loops, remove listeners, dispose Three.js resources, and end the session. |
298
373
 
299
374
  ---
@@ -390,6 +465,7 @@ import type {
390
465
  ICameraIntrinsicsEvent,
391
466
  IPoseResultEvent,
392
467
  ILocalizeAndMapDetails,
468
+ IObjectTrackingResponse,
393
469
  IGetMapsDetailsResponse,
394
470
  MapType,
395
471
  } from '@multisetai/vps/core';
@@ -397,6 +473,18 @@ import type {
397
473
  import type { IThreeAdapterOptions } from '@multisetai/vps/three';
398
474
  ```
399
475
 
476
+ ### `IObjectTrackingResponse`
477
+
478
+ Returned by `trackObjects()` and `MultisetClient.trackObject()` when an object is detected.
479
+
480
+ | Field | Type | Description |
481
+ |---|---|---|
482
+ | `poseFound` | `boolean` | Whether the API found a valid pose for the object. |
483
+ | `position` | `IPosition` | Position of the detected object in tracker space `{ x, y, z }`. |
484
+ | `rotation` | `IRotation` | Orientation of the detected object as a quaternion `{ x, y, z, w }`. |
485
+ | `confidence` | `number` | Detection confidence score (0–1). |
486
+ | `objectCodes` | `string[]` | The object code(s) that were matched. |
487
+
400
488
  ## License
401
489
 
402
490
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
@@ -125,6 +125,13 @@ interface ILocalizeAndMapDetails {
125
125
  localizeData: ILocalizeResponse;
126
126
  mapDetails?: IGetMapsDetailsResponse;
127
127
  }
128
+ interface IObjectTrackingResponse {
129
+ poseFound: boolean;
130
+ position: IPosition;
131
+ rotation: IRotation;
132
+ confidence: number;
133
+ objectCodes: string[];
134
+ }
128
135
 
129
136
  interface IMultisetClientConfigBase {
130
137
  clientId: string;
@@ -169,6 +176,8 @@ interface IMultisetSdkEndpoints {
169
176
  mapDetailsUrl: string;
170
177
  fileDownloadUrl: string;
171
178
  mapSetDetailsUrl: string;
179
+ objectQueryUrl: string;
180
+ objectDetailsUrl: string;
172
181
  }
173
182
  interface IFrameCaptureEvent {
174
183
  blob: Blob;
@@ -219,7 +228,11 @@ declare class MultisetClient {
219
228
  localizeWithFrame(frame: IFrameCaptureEvent, intrinsics: ICameraIntrinsicsEvent, options?: {
220
229
  fetchMapDetails?: boolean;
221
230
  }): Promise<ILocalizeAndMapDetails | null>;
231
+ trackObject(frame: IFrameCaptureEvent, intrinsics: ICameraIntrinsicsEvent, objectCodes: string[]): Promise<IObjectTrackingResponse | null>;
232
+ downloadObjectMesh(objectCode: string): Promise<string | null>;
233
+ private fetchObjectMeshKey;
222
234
  fetchMapDetails(mapCode: string): Promise<IGetMapsDetailsResponse | null>;
235
+ private apiFetch;
223
236
  private getGeoPoseComponents;
224
237
  private queryLocalization;
225
238
  }
@@ -244,12 +257,30 @@ interface IXRSessionOptions {
244
257
  autoLocalize?: boolean;
245
258
  /** Re-localize whenever tracking is lost and then recovered. */
246
259
  relocalization?: boolean;
260
+ /**
261
+ * Periodically send localization requests in the background to maintain or
262
+ * improve the device's pose over time. Only active during an AR session.
263
+ */
264
+ backgroundLocalization?: boolean;
265
+ /**
266
+ * Interval in seconds between background localization attempts.
267
+ * Clamped to 15–180 s. Default 30 s.
268
+ */
269
+ bgLocalizationDuration?: number;
247
270
  /** Only accept a result if confidence >= confidenceThreshold. */
248
271
  confidenceCheck?: boolean;
249
272
  /** Minimum confidence (0.2–0.8). Default 0.5. */
250
273
  confidenceThreshold?: number;
251
274
  /** Max ms to wait for a valid viewer pose before failing. Default 10000. */
252
275
  localizationTrackingTimeoutMs?: number;
276
+ /** Object codes to detect and track. Required for trackObjects(). */
277
+ objectCodes?: string[];
278
+ /** Automatically call trackObjects() once when the AR session starts. */
279
+ autoTracking?: boolean;
280
+ /** Re-attempt tracking whenever XR tracking is lost and then recovered. */
281
+ restartTracking?: boolean;
282
+ /** Milliseconds to wait before capturing a frame when trackObjects() is called. Useful for camera stabilisation. Default 0. */
283
+ trackingCaptureDelayMs?: number;
253
284
  /**
254
285
  * XR reference space type. Default 'local' (always supported for immersive-ar).
255
286
  * Use 'local-floor' for floor-relative tracking if the device supports it.
@@ -269,6 +300,10 @@ interface IXRSessionOptions {
269
300
  onFrameCaptured?: (frame: IFrameCaptureEvent) => void;
270
301
  onCameraIntrinsics?: (intrinsics: ICameraIntrinsicsEvent) => void;
271
302
  onPoseResult?: (pose: IPoseResultEvent) => void;
303
+ onObjectTrackingInit?: () => void;
304
+ onObjectTrackingRequested?: (frame: IFrameCaptureEvent, intrinsics: ICameraIntrinsicsEvent) => void;
305
+ onObjectTrackingSuccess?: (result: IObjectTrackingResponse) => void;
306
+ onObjectTrackingFailure?: (reason?: string) => void;
272
307
  onError?: (error: unknown) => void;
273
308
  /** Called when the WebGL context is lost. The active session is ended automatically. */
274
309
  onContextLost?: () => void;
@@ -287,9 +322,11 @@ declare class XRSessionManager {
287
322
  private hadTrackingLoss;
288
323
  private _isLocalizing;
289
324
  private trackerSpace;
325
+ private bgLocalizationTimer;
290
326
  private arButton;
291
327
  private xrFrameHandler;
292
328
  private adapterResultHandler;
329
+ private adapterObjectTrackingHandler;
293
330
  private adapterSessionStartHandler;
294
331
  private adapterSessionEndHandler;
295
332
  constructor(gl: WebGL2RenderingContext, options: IXRSessionOptions);
@@ -297,6 +334,7 @@ declare class XRSessionManager {
297
334
  static isSupported(): Promise<boolean>;
298
335
  setXRFrameHandler(handler: (event: IXRFrameEvent) => void): void;
299
336
  setAdapterResultHandler(handler: (result: ILocalizeAndMapDetails, trackerSpace: Mat4) => void): void;
337
+ setAdapterObjectTrackingHandler(handler: (result: IObjectTrackingResponse, trackerSpace: Mat4) => void): void;
300
338
  setAdapterSessionHandlers(onStart: () => void, onEnd: () => void): void;
301
339
  getClient(): MultisetClient;
302
340
  getOverlayRoot(): HTMLElement | undefined;
@@ -307,13 +345,16 @@ declare class XRSessionManager {
307
345
  createButton(): HTMLButtonElement;
308
346
  startSession(): Promise<void>;
309
347
  private initSession;
348
+ private startBackgroundLocalization;
310
349
  private handleSessionEnd;
311
350
  private readonly handleContextLost;
312
351
  private readonly handleContextRestored;
313
352
  private xrLoop;
314
353
  localizeFrame(): Promise<ILocalizeAndMapDetails | null>;
315
354
  private captureFrame;
355
+ trackObjects(): Promise<IObjectTrackingResponse | null>;
356
+ private captureFrameAndTrack;
316
357
  dispose(): void;
317
358
  }
318
359
 
319
- export { DEFAULT_ENDPOINTS, type ICameraIntrinsicsEvent, type IFrameCaptureEvent, type IGetMapsDetailsResponse, type ILocalizeAndMapDetails, type ILocalizeResponse, type ILocalizeResultEvent, type IMapSetMapsResponse, type IMultisetClientConfig, type IMultisetSdkEndpoints, type IPoseResultEvent, type IXRFrameEvent, type IXRSessionOptions, type MapType, MultisetClient, XRSessionManager };
360
+ export { DEFAULT_ENDPOINTS, type ICameraIntrinsicsEvent, type IFrameCaptureEvent, type IGetMapsDetailsResponse, type ILocalizeAndMapDetails, type ILocalizeResponse, type ILocalizeResultEvent, type IMapSetMapsResponse, type IMultisetClientConfig, type IMultisetSdkEndpoints, type IObjectTrackingResponse, type IPoseResultEvent, type IXRFrameEvent, type IXRSessionOptions, type MapType, MultisetClient, XRSessionManager };
@@ -1,2 +1,2 @@
1
- import E from'axios';var x={authUrl:"https://api.multiset.ai/v1/m2m/token",queryUrl:"https://api.multiset.ai/v1/vps/map/query-form",mapDetailsUrl:"https://api.multiset.ai/v1/vps/map/",mapSetDetailsUrl:"https://api.multiset.ai/v1/vps/map-set/",fileDownloadUrl:"https://api.multiset.ai/v1/file"};var w=class{constructor(e){this.accessToken=null;this.mapDetailsCache={};let{clientId:t,clientSecret:n,...i}=e;this.credentials={clientId:t,clientSecret:n},this.config=i,this.endpoints={...x,...i.endpoints};}get token(){return this.accessToken}async authorize(){var n,i,r;let e=await E.post(this.endpoints.authUrl,{},{auth:{username:this.credentials.clientId,password:this.credentials.clientSecret}}),t=(r=(n=e.data)==null?void 0:n.token)!=null?r:(i=e.data)==null?void 0:i.access_token;if(!t)throw new Error("Authorization succeeded but no token was returned.");return this.accessToken=t,t}async downloadFile(e){if(!this.accessToken||!e)return "";try{let t=await E.get(`${this.endpoints.fileDownloadUrl}?key=${encodeURIComponent(e)}`,{headers:{Authorization:`Bearer ${this.accessToken}`}});return t.status===200?t.data.url:""}catch{return ""}}async localizeWithFrame(e,t,n){var i;if(!this.accessToken)throw new Error("Access token is missing. Call authorize() first.");return this.queryLocalization(e,t,(i=n==null?void 0:n.fetchMapDetails)!=null?i:false)}async fetchMapDetails(e){if(!this.accessToken)return null;let t=this.mapDetailsCache[e];if(t)return t;try{let n=await E.get(`${this.endpoints.mapDetailsUrl}${e}`,{headers:{Authorization:`Bearer ${this.accessToken}`}});return this.mapDetailsCache[e]=n.data,n.data}catch{return null}}async getGeoPoseComponents(){if(typeof navigator=="undefined"||!("geolocation"in navigator)||!navigator.geolocation)return null;try{let e=await new Promise((a,p)=>{navigator.geolocation.getCurrentPosition(a,p,{enableHighAccuracy:!0,timeout:1e4,maximumAge:0});}),{latitude:t,longitude:n,altitude:i}=e.coords,r=typeof i=="number"&&!Number.isNaN(i)?i:0;return {latitude:t,longitude:n,altitude:r}}catch{return null}}async queryLocalization(e,t,n){var u,d,m;let i=new FormData;this.config.mapType==="map"?i.append("mapCode",this.config.code):i.append("mapSetCode",this.config.code);let r=(u=this.config.isRightHanded)!=null?u:true;i.append("isRightHanded",`${r}`),i.append("fx",`${t.fx}`),i.append("fy",`${t.fy}`),i.append("px",`${t.px}`),i.append("py",`${t.py}`),i.append("width",`${e.width}`),i.append("height",`${e.height}`),i.append("queryImage",e.blob),this.config.convertToGeoCoordinates!==void 0&&i.append("convertToGeoCoordinates",`${this.config.convertToGeoCoordinates}`),"hintMapCodes"in this.config&&((d=this.config.hintMapCodes)!=null&&d.length)&&this.config.hintMapCodes.forEach(l=>{i.append("hintMapCodes",l);}),this.config.hintPosition&&i.append("hintPosition",this.config.hintPosition);let a;if(this.config.passGeoPose){let l=await this.getGeoPoseComponents();if(l){let{latitude:h,longitude:g,altitude:v}=l;a=`${h},${g},${v}`;}}if(a&&i.append("geoHint",a),this.config.hintRadius!==void 0){let l=Number(this.config.hintRadius);if(Number.isNaN(l)||l<1||l>100)throw new Error("hintRadius must be a number between 1 and 100.");if(!this.config.hintPosition&&!a)throw new Error("hintRadius requires hintPosition, or passGeoPose with available geolocation to generate geoHint.");i.append("hintRadius",`${l}`);}"use2DFiltering"in this.config&&this.config.use2DFiltering!==void 0&&i.append("use2DFiltering",`${this.config.use2DFiltering}`);let s=(await E.post(this.endpoints.queryUrl,i,{headers:{Authorization:`Bearer ${this.accessToken}`}})).data;if(!s.poseFound)return null;let c={localizeData:s};if(n&&((m=s.mapCodes)!=null&&m.length)){let l=await this.fetchMapDetails(s.mapCodes[0]);l&&(c.mapDetails=l);}return c}};async function _(o,e,t,n=.8,i=e,r=t){let a=document.createElement("canvas");a.width=e,a.height=t;let p=a.getContext("2d");if(!p)return new Blob;p.putImageData(new ImageData(new Uint8ClampedArray(o),e,t),0,0);let s=document.createElement("canvas");s.width=i,s.height=r;let c=s.getContext("2d");return c?(c.drawImage(a,0,0,i,r),new Promise(u=>{s.toBlob(d=>u(d!=null?d:new Blob),"image/jpeg",n);})):new Blob}async function B(o,e,t,n){let i=o.createFramebuffer();if(!i)return null;let r=o.getParameter(o.FRAMEBUFFER_BINDING),a;try{o.bindFramebuffer(o.FRAMEBUFFER,i),o.framebufferTexture2D(o.FRAMEBUFFER,o.COLOR_ATTACHMENT0,o.TEXTURE_2D,e,0),a=new Uint8Array(t*n*4),o.readPixels(0,0,t,n,o.RGBA,o.UNSIGNED_BYTE,a);}finally{o.bindFramebuffer(o.FRAMEBUFFER,r),o.deleteFramebuffer(i);}let p=new Uint8ClampedArray(a.length);for(let m=0;m<n;m+=1){let l=m*t*4,h=(n-m-1)*t*4;p.set(a.subarray(l,l+t*4),h);}let s=Math.min(1,1280/Math.max(t,n)),c=Math.round(t*s),u=Math.round(n*s),d=await _(p.buffer,t,n,.7,c,u);return d.size?{blob:d,width:c,height:u}:null}function X(o,e){let t=o,n=(1-t[8])*e.width/2+e.x,i=(1-t[9])*e.height/2+e.y,r=e.width/2*t[0],a=e.height/2*t[5];return {fx:r,fy:a,px:n,py:i,width:e.width,height:e.height}}function G(o){return new Float32Array(o)}var N=.2,U=.8,$=60,O=1e4,W=300,q=10,T=class{constructor(e,t){this.gl=e;this.options=t;this.session=null;this.referenceSpace=null;this.baseLayer=null;this.xrBinding=null;this.lastFrameTime=0;this.trackingLossFrames=0;this.hadTrackingLoss=false;this._isLocalizing=false;this.trackerSpace=null;this.arButton=null;this.xrFrameHandler=null;this.adapterResultHandler=null;this.adapterSessionStartHandler=null;this.adapterSessionEndHandler=null;this.handleContextLost=e=>{var t,n;if(e.preventDefault(),this.session)try{this.session.end();}catch{}(n=(t=this.options).onContextLost)==null||n.call(t);};this.handleContextRestored=()=>{var e,t;(t=(e=this.options).onContextRestored)==null||t.call(e);};let n=e.canvas;n instanceof HTMLCanvasElement&&(n.addEventListener("webglcontextlost",this.handleContextLost),n.addEventListener("webglcontextrestored",this.handleContextRestored));}static async isSupported(){if(!navigator.xr)return false;try{return await navigator.xr.isSessionSupported("immersive-ar")}catch{return false}}setXRFrameHandler(e){this.xrFrameHandler=e;}setAdapterResultHandler(e){this.adapterResultHandler=e;}setAdapterSessionHandlers(e,t){this.adapterSessionStartHandler=e,this.adapterSessionEndHandler=t;}getClient(){return this.options.client}getOverlayRoot(){return this.options.overlayRoot}isActive(){return this.session!==null}get isLocalizing(){return this._isLocalizing}stopSession(){this.session&&this.session.end();}getBaseLayer(){return this.baseLayer}createButton(){let e=document.createElement("button");return e.textContent="START AR",e.className="multiset-ar-button multiset-ar-inactive",e.style.cssText=["position:fixed","bottom:20px","left:50%","transform:translateX(-50%)","padding:12px 24px","border:1px solid #fff","border-radius:4px","background:rgba(0,0,0,0.1)","color:#fff","font-size:13px","cursor:pointer","z-index:999"].join(";"),e.addEventListener("click",()=>{this.session?this.session.end():this.startSession();}),this.arButton=e,e}async startSession(){var e,t,n;try{if(!navigator.xr)throw new Error("WebXR not supported on this device.");let i=await navigator.xr.requestSession("immersive-ar",{requiredFeatures:["local"],optionalFeatures:["camera-access","dom-overlay"],domOverlay:{root:(e=this.options.overlayRoot)!=null?e:document.body}});await this.initSession(i);}catch(i){(n=(t=this.options).onError)==null||n.call(t,i);}}async initSession(e){var a,p,s,c;await this.gl.makeXRCompatible();let t={};this.options.framebufferScaleFactor!==void 0&&(t.framebufferScaleFactor=this.options.framebufferScaleFactor);let n=new XRWebGLLayer(e,this.gl,t);await e.updateRenderState({baseLayer:n});let i=await e.requestReferenceSpace((a=this.options.referenceSpaceType)!=null?a:"local");this.session=e,this.baseLayer=n,this.referenceSpace=i;let r=XRWebGLBinding;this.xrBinding=new r(e,this.gl),e.addEventListener("end",()=>this.handleSessionEnd()),e.requestAnimationFrame((u,d)=>this.xrLoop(u,d)),this.arButton&&(this.arButton.textContent="STOP AR",this.arButton.classList.replace("multiset-ar-inactive","multiset-ar-active")),(p=this.adapterSessionStartHandler)==null||p.call(this),(c=(s=this.options).onSessionStart)==null||c.call(s),this.options.autoLocalize&&e.requestAnimationFrame(()=>{this.localizeFrame();});}handleSessionEnd(){var e,t,n;this.arButton&&(this.arButton.textContent="START AR",this.arButton.classList.replace("multiset-ar-active","multiset-ar-inactive")),this.session=null,this.baseLayer=null,this.referenceSpace=null,this.xrBinding=null,this.lastFrameTime=0,this.trackingLossFrames=0,this.hadTrackingLoss=false,this._isLocalizing=false,this.trackerSpace=null,(e=this.adapterSessionEndHandler)==null||e.call(this),(n=(t=this.options).onSessionEnd)==null||n.call(t);}xrLoop(e,t){var p;this.session.requestAnimationFrame((s,c)=>this.xrLoop(s,c));let n=this.lastFrameTime===0?0:(e-this.lastFrameTime)/1e3;this.lastFrameTime=e;let i=t.getViewerPose(this.referenceSpace);if(!i){this.options.relocalization&&(this.trackingLossFrames+=1,this.trackingLossFrames>=$&&(this.hadTrackingLoss=true));return}this.hadTrackingLoss&&!this._isLocalizing&&this.options.relocalization?(this.hadTrackingLoss=false,this.trackingLossFrames=0,this.localizeFrame()):this.trackingLossFrames=0;let r=i.views[0],a=this.baseLayer.getViewport(r);(p=this.xrFrameHandler)==null||p.call(this,{time:e,frame:t,pose:i,view:r,viewport:a,baseLayer:this.baseLayer,referenceSpace:this.referenceSpace,deltaSeconds:n});}async localizeFrame(){var p,s,c,u,d,m,l,h,g,v,I,C,b;if(!this.session)throw new Error("No active XR session. Start AR before calling localizeFrame().");let e=(p=this.options.confidenceCheck)!=null?p:false,t=Math.max(N,Math.min((s=this.options.confidenceThreshold)!=null?s:.5,U));(u=(c=this.options).onLocalizationInit)==null||u.call(c),this._isLocalizing=true;let n=null,i;try{let y=await this.captureFrame();n=y.result,i=y.failureReason;}catch(y){return (m=(d=this.options).onError)==null||m.call(d,y),null}finally{this._isLocalizing=false;}if(n&&(!e||((l=n.localizeData.confidence)!=null?l:0)>=t)&&n)return (g=(h=this.options).onLocalizationSuccess)==null||g.call(h,n),this.trackerSpace&&((v=this.adapterResultHandler)==null||v.call(this,n,this.trackerSpace)),n;let a=i!=null?i:n?e?`Best confidence ${(I=n.localizeData.confidence)!=null?I:0} below threshold ${t}.`:void 0:"All attempts failed to produce a pose.";return (b=(C=this.options).onLocalizationFailure)==null||b.call(C,a),null}async captureFrame(){var i;let e=this.session,t=this.referenceSpace,n=(i=this.options.localizationTrackingTimeoutMs)!=null?i:O;return new Promise((r,a)=>{let p=Date.now(),s=(c,u)=>{e.requestAnimationFrame(async(d,m)=>{var l,h,g,v,I,C,b,y,D;try{let S=Date.now()-p,A=m.getViewerPose(t);if(!A){if(S>=n||c+1>=W){let R=(n/1e3).toFixed(1);r({result:null,failureReason:`Tracking unavailable: no viewer pose within ${R}s. Try moving the device or ensuring good lighting.`});}else s(c+1,u);return}let H=A.views;for(let R of H){let F=R.camera;if(!F)continue;let z=(g=(h=(l=this.xrBinding)==null?void 0:l.getCameraImage)==null?void 0:h.call(l,F))!=null?g:null;if(!z)continue;let{width:k,height:P}=F;if(!k||!P)continue;let L=await B(this.gl,z,k,P);if(!L)continue;let M=X(R.projectionMatrix,{width:L.width,height:L.height,x:0,y:0});if(M){(I=(v=this.options).onFrameCaptured)==null||I.call(v,L),(b=(C=this.options).onCameraIntrinsics)==null||b.call(C,M),this.trackerSpace=G(R.transform.matrix);let f=await this.options.client.localizeWithFrame(L,M);f!=null&&f.localizeData&&((D=(y=this.options).onPoseResult)==null||D.call(y,{poseFound:f.localizeData.poseFound,position:f.localizeData.position,rotation:f.localizeData.rotation,mapIds:f.localizeData.mapIds,confidence:f.localizeData.confidence})),r({result:f});return}}u+1>=q?r({result:null,failureReason:"Camera image not available. The device or browser may not support camera access in AR."}):s(c+1,u+1);}catch(S){a(S);}});};s(0,0);})}dispose(){if(this.session)try{this.session.end();}catch{}let e=this.gl.canvas;e instanceof HTMLCanvasElement&&(e.removeEventListener("webglcontextlost",this.handleContextLost),e.removeEventListener("webglcontextrestored",this.handleContextRestored)),this.arButton=null;}};
2
- export{x as DEFAULT_ENDPOINTS,w as MultisetClient,T as XRSessionManager};
1
+ var z={authUrl:"https://api.multiset.ai/v1/m2m/token",queryUrl:"https://api.multiset.ai/v1/vps/map/query-form",mapDetailsUrl:"https://api.multiset.ai/v1/vps/map/",mapSetDetailsUrl:"https://api.multiset.ai/v1/vps/map-set/",fileDownloadUrl:"https://api.multiset.ai/v1/file",objectQueryUrl:"https://api.multiset.ai/v1/vps/object/query",objectDetailsUrl:"https://api.multiset.ai/v1/vps/object/"};var O=class{constructor(e){this.accessToken=null;this.mapDetailsCache={};let{clientId:t,clientSecret:n,...i}=e;this.credentials={clientId:t,clientSecret:n},this.config=i,this.endpoints={...z,...i.endpoints};}get token(){return this.accessToken}async authorize(){var i;let e=btoa(`${this.credentials.clientId}:${this.credentials.clientSecret}`),t=await this.apiFetch(this.endpoints.authUrl,{method:"POST",headers:{Authorization:`Basic ${e}`,"Content-Type":"application/json"},body:"{}"}),n=(i=t.token)!=null?i:t.access_token;if(!n)throw new Error("Authorization succeeded but no token was returned.");return this.accessToken=n,n}async downloadFile(e){var t;if(!this.accessToken||!e)return "";try{return (t=(await this.apiFetch(`${this.endpoints.fileDownloadUrl}?key=${encodeURIComponent(e)}`)).url)!=null?t:""}catch{return ""}}async localizeWithFrame(e,t,n){var i;if(!this.accessToken)throw new Error("Access token is missing. Call authorize() first.");return this.queryLocalization(e,t,(i=n==null?void 0:n.fetchMapDetails)!=null?i:false)}async trackObject(e,t,n){var s;if(!this.accessToken)throw new Error("Access token is missing. Call authorize() first.");let i=new FormData;i.append("queryImage",e.blob),i.append("isRightHanded",`${(s=this.config.isRightHanded)!=null?s:true}`),i.append("fx",`${t.fx}`),i.append("fy",`${t.fy}`),i.append("px",`${t.px}`),i.append("py",`${t.py}`),i.append("width",`${e.width}`),i.append("height",`${e.height}`),n.forEach(r=>i.append("objectCode",r));let a=await this.apiFetch(this.endpoints.objectQueryUrl,{method:"POST",body:i});return a.poseFound?a:null}async downloadObjectMesh(e){let t=await this.fetchObjectMeshKey(e);return t&&await this.downloadFile(t)||null}async fetchObjectMeshKey(e){var t,n,i;if(!this.accessToken)return null;try{let a=await this.apiFetch(`${this.endpoints.objectDetailsUrl}${e}`);return (i=(n=(t=a.objectMesh)==null?void 0:t.meshLink)!=null?n:a.meshLink)!=null?i:null}catch{return null}}async fetchMapDetails(e){if(!this.accessToken)return null;let t=this.mapDetailsCache[e];if(t)return t;try{let n=await this.apiFetch(`${this.endpoints.mapDetailsUrl}${e}`);return this.mapDetailsCache[e]=n,n}catch{return null}}async apiFetch(e,t={}){var a;let n={};this.accessToken&&(n.Authorization=`Bearer ${this.accessToken}`);let i=await fetch(e,{...t,headers:{...n,...t.headers}});if(!i.ok){let s=`HTTP ${i.status} ${i.statusText}`;try{let r=await i.json(),o=(a=r.error)!=null?a:r.message;o&&(s+=`: ${o}`);}catch{}throw new Error(s)}return i.json()}async getGeoPoseComponents(){if(typeof navigator=="undefined"||!("geolocation"in navigator)||!navigator.geolocation)return null;try{let e=await new Promise((s,r)=>{navigator.geolocation.getCurrentPosition(s,r,{enableHighAccuracy:!0,timeout:1e4,maximumAge:0});}),{latitude:t,longitude:n,altitude:i}=e.coords,a=typeof i=="number"&&!Number.isNaN(i)?i:0;return {latitude:t,longitude:n,altitude:a}}catch{return null}}async queryLocalization(e,t,n){var l,d,p;let i=new FormData;this.config.mapType==="map"?i.append("mapCode",this.config.code):i.append("mapSetCode",this.config.code);let a=(l=this.config.isRightHanded)!=null?l:true;i.append("isRightHanded",`${a}`),i.append("fx",`${t.fx}`),i.append("fy",`${t.fy}`),i.append("px",`${t.px}`),i.append("py",`${t.py}`),i.append("width",`${e.width}`),i.append("height",`${e.height}`),i.append("queryImage",e.blob),this.config.convertToGeoCoordinates!==void 0&&i.append("convertToGeoCoordinates",`${this.config.convertToGeoCoordinates}`),"hintMapCodes"in this.config&&((d=this.config.hintMapCodes)!=null&&d.length)&&this.config.hintMapCodes.forEach(c=>{i.append("hintMapCodes",c);}),this.config.hintPosition&&i.append("hintPosition",this.config.hintPosition);let s;if(this.config.passGeoPose){let c=await this.getGeoPoseComponents();if(c){let{latitude:h,longitude:g,altitude:v}=c;s=`${h},${g},${v}`;}}if(s&&i.append("geoHint",s),this.config.hintRadius!==void 0){let c=Number(this.config.hintRadius);if(Number.isNaN(c)||c<1||c>100)throw new Error("hintRadius must be a number between 1 and 100.");if(!this.config.hintPosition&&!s)throw new Error("hintRadius requires hintPosition, or passGeoPose with available geolocation to generate geoHint.");i.append("hintRadius",`${c}`);}"use2DFiltering"in this.config&&this.config.use2DFiltering!==void 0&&i.append("use2DFiltering",`${this.config.use2DFiltering}`);let r=await this.apiFetch(this.endpoints.queryUrl,{method:"POST",body:i});if(!r.poseFound)return null;let o={localizeData:r};if(n&&((p=r.mapCodes)!=null&&p.length)){let c=await this.fetchMapDetails(r.mapCodes[0]);c&&(o.mapDetails=c);}return o}};async function $(u,e,t,n=.8,i=e,a=t){let s=document.createElement("canvas");s.width=e,s.height=t;let r=s.getContext("2d");if(!r)return new Blob;r.putImageData(new ImageData(new Uint8ClampedArray(u),e,t),0,0);let o=document.createElement("canvas");o.width=i,o.height=a;let l=o.getContext("2d");return l?(l.drawImage(s,0,0,i,a),new Promise(d=>{o.toBlob(p=>d(p!=null?p:new Blob),"image/jpeg",n);})):new Blob}async function P(u,e,t,n){let i=u.createFramebuffer();if(!i)return null;let a=u.getParameter(u.FRAMEBUFFER_BINDING),s;try{u.bindFramebuffer(u.FRAMEBUFFER,i),u.framebufferTexture2D(u.FRAMEBUFFER,u.COLOR_ATTACHMENT0,u.TEXTURE_2D,e,0),s=new Uint8Array(t*n*4),u.readPixels(0,0,t,n,u.RGBA,u.UNSIGNED_BYTE,s);}finally{u.bindFramebuffer(u.FRAMEBUFFER,a),u.deleteFramebuffer(i);}let r=new Uint8ClampedArray(s.length);for(let c=0;c<n;c+=1){let h=c*t*4,g=(n-c-1)*t*4;r.set(s.subarray(h,h+t*4),g);}let o=Math.min(1,1280/Math.max(t,n)),l=Math.round(t*o),d=Math.round(n*o),p=await $(r.buffer,t,n,.7,l,d);return p.size?{blob:p,width:l,height:d}:null}function j(u,e){let t=u,n=(1-t[8])*e.width/2+e.x,i=(1-t[9])*e.height/2+e.y,a=e.width/2*t[0],s=e.height/2*t[5];return {fx:a,fy:s,px:n,py:i,width:e.width,height:e.height}}function _(u){return new Float32Array(u)}var X=.2,G=.8,q=60,H=1e4,N=300,U=10,W=10,V=180,K=30,Z=10,B=class{constructor(e,t){this.gl=e;this.options=t;this.session=null;this.referenceSpace=null;this.baseLayer=null;this.xrBinding=null;this.lastFrameTime=0;this.trackingLossFrames=0;this.hadTrackingLoss=false;this._isLocalizing=false;this.trackerSpace=null;this.bgLocalizationTimer=null;this.arButton=null;this.xrFrameHandler=null;this.adapterResultHandler=null;this.adapterObjectTrackingHandler=null;this.adapterSessionStartHandler=null;this.adapterSessionEndHandler=null;this.handleContextLost=e=>{var t,n;if(e.preventDefault(),this.session)try{this.session.end();}catch{}(n=(t=this.options).onContextLost)==null||n.call(t);};this.handleContextRestored=()=>{var e,t;(t=(e=this.options).onContextRestored)==null||t.call(e);};let n=e.canvas;n instanceof HTMLCanvasElement&&(n.addEventListener("webglcontextlost",this.handleContextLost),n.addEventListener("webglcontextrestored",this.handleContextRestored));}static async isSupported(){if(!navigator.xr)return false;try{return await navigator.xr.isSessionSupported("immersive-ar")}catch{return false}}setXRFrameHandler(e){this.xrFrameHandler=e;}setAdapterResultHandler(e){this.adapterResultHandler=e;}setAdapterObjectTrackingHandler(e){this.adapterObjectTrackingHandler=e;}setAdapterSessionHandlers(e,t){this.adapterSessionStartHandler=e,this.adapterSessionEndHandler=t;}getClient(){return this.options.client}getOverlayRoot(){return this.options.overlayRoot}isActive(){return this.session!==null}get isLocalizing(){return this._isLocalizing}stopSession(){this.session&&this.session.end();}getBaseLayer(){return this.baseLayer}createButton(){let e=document.createElement("button");return e.textContent="START AR",e.className="multiset-ar-button multiset-ar-inactive",e.style.cssText=["position:fixed","bottom:20px","left:50%","transform:translateX(-50%)","padding:12px 24px","border:1px solid #fff","border-radius:4px","background:rgba(0,0,0,0.1)","color:#fff","font-size:13px","cursor:pointer","z-index:999"].join(";"),e.addEventListener("click",()=>{this.session?this.session.end():this.startSession();}),this.arButton=e,e}async startSession(){var e,t,n;try{if(!navigator.xr)throw new Error("WebXR not supported on this device.");let i=await navigator.xr.requestSession("immersive-ar",{requiredFeatures:["local"],optionalFeatures:["camera-access","dom-overlay"],domOverlay:{root:(e=this.options.overlayRoot)!=null?e:document.body}});await this.initSession(i);}catch(i){(n=(t=this.options).onError)==null||n.call(t,i);}}async initSession(e){var s,r,o,l;await this.gl.makeXRCompatible();let t={};this.options.framebufferScaleFactor!==void 0&&(t.framebufferScaleFactor=this.options.framebufferScaleFactor);let n=new XRWebGLLayer(e,this.gl,t);await e.updateRenderState({baseLayer:n});let i=await e.requestReferenceSpace((s=this.options.referenceSpaceType)!=null?s:"local");this.session=e,this.baseLayer=n,this.referenceSpace=i;let a=XRWebGLBinding;this.xrBinding=new a(e,this.gl),e.addEventListener("end",()=>this.handleSessionEnd()),e.requestAnimationFrame((d,p)=>this.xrLoop(d,p)),this.arButton&&(this.arButton.textContent="STOP AR",this.arButton.classList.replace("multiset-ar-inactive","multiset-ar-active")),(r=this.adapterSessionStartHandler)==null||r.call(this),(l=(o=this.options).onSessionStart)==null||l.call(o),this.options.autoLocalize&&e.requestAnimationFrame(()=>{this.localizeFrame();}),this.options.autoTracking&&e.requestAnimationFrame(()=>{this.trackObjects();}),this.options.backgroundLocalization&&this.startBackgroundLocalization();}startBackgroundLocalization(){var i,a,s;let e=((a=(i=this.options.objectCodes)==null?void 0:i.length)!=null?a:0)>0,t=e?Z:K,n=Math.max(W,Math.min((s=this.options.bgLocalizationDuration)!=null?s:t,V))*1e3;this.bgLocalizationTimer=setInterval(()=>{this.session&&!this._isLocalizing&&(e?this.trackObjects():this.localizeFrame());},n);}handleSessionEnd(){var e,t,n;this.arButton&&(this.arButton.textContent="START AR",this.arButton.classList.replace("multiset-ar-active","multiset-ar-inactive")),this.bgLocalizationTimer!==null&&(clearInterval(this.bgLocalizationTimer),this.bgLocalizationTimer=null),this.session=null,this.baseLayer=null,this.referenceSpace=null,this.xrBinding=null,this.lastFrameTime=0,this.trackingLossFrames=0,this.hadTrackingLoss=false,this._isLocalizing=false,this.trackerSpace=null,(e=this.adapterSessionEndHandler)==null||e.call(this),(n=(t=this.options).onSessionEnd)==null||n.call(t);}xrLoop(e,t){var r;this.session.requestAnimationFrame((o,l)=>this.xrLoop(o,l));let n=this.lastFrameTime===0?0:(e-this.lastFrameTime)/1e3;this.lastFrameTime=e;let i=t.getViewerPose(this.referenceSpace);if(!i){(this.options.relocalization||this.options.restartTracking)&&(this.trackingLossFrames+=1,this.trackingLossFrames>=q&&(this.hadTrackingLoss=true));return}this.hadTrackingLoss&&!this._isLocalizing&&(this.options.relocalization||this.options.restartTracking)?(this.hadTrackingLoss=false,this.trackingLossFrames=0,this.options.restartTracking?this.trackObjects():this.options.relocalization&&this.localizeFrame()):this.trackingLossFrames=0;let a=i.views[0],s=this.baseLayer.getViewport(a);(r=this.xrFrameHandler)==null||r.call(this,{time:e,frame:t,pose:i,view:a,viewport:s,baseLayer:this.baseLayer,referenceSpace:this.referenceSpace,deltaSeconds:n});}async localizeFrame(){var r,o,l,d,p,c,h,g,v,b,I,T,C,R,y;if(!this.session)throw new Error("No active XR session. Start AR before calling localizeFrame().");let e=(r=this.options.confidenceCheck)!=null?r:false,t=Math.max(X,Math.min((o=this.options.confidenceThreshold)!=null?o:.5,G));(d=(l=this.options).onLocalizationInit)==null||d.call(l),this._isLocalizing=true;let n=null,i;try{let m=await this.captureFrame();n=m.result,i=m.failureReason;}catch(m){let f=m instanceof Error?m.message:String(m);return (c=(p=this.options).onLocalizationFailure)==null||c.call(p,f),(g=(h=this.options).onError)==null||g.call(h,m),null}finally{this._isLocalizing=false;}if(n&&(!e||((v=n.localizeData.confidence)!=null?v:0)>=t)&&n)return (I=(b=this.options).onLocalizationSuccess)==null||I.call(b,n),this.trackerSpace&&((T=this.adapterResultHandler)==null||T.call(this,n,this.trackerSpace)),n;let s=i!=null?i:n?e?`Best confidence ${(C=n.localizeData.confidence)!=null?C:0} below threshold ${t}.`:void 0:"All attempts failed to produce a pose.";return (y=(R=this.options).onLocalizationFailure)==null||y.call(R,s),null}async captureFrame(){var i;let e=this.session,t=this.referenceSpace,n=(i=this.options.localizationTrackingTimeoutMs)!=null?i:H;return new Promise((a,s)=>{let r=Date.now(),o=(l,d)=>{e.requestAnimationFrame(async(p,c)=>{var h,g,v,b,I,T,C,R,y;try{let m=Date.now()-r,f=c.getViewerPose(t);if(!f){if(m>=n||l+1>=N){let w=(n/1e3).toFixed(1);a({result:null,failureReason:`Tracking unavailable: no viewer pose within ${w}s. Try moving the device or ensuring good lighting.`});}else o(l+1,d);return}let L=f.views;for(let w of L){let E=w.camera;if(!E)continue;let x=(v=(g=(h=this.xrBinding)==null?void 0:h.getCameraImage)==null?void 0:g.call(h,E))!=null?v:null;if(!x)continue;let{width:D,height:A}=E;if(!D||!A)continue;let k=_(w.transform.matrix),S=await P(this.gl,x,D,A);if(!S)continue;let M=j(w.projectionMatrix,{width:S.width,height:S.height,x:0,y:0});if(M){(I=(b=this.options).onFrameCaptured)==null||I.call(b,S),(C=(T=this.options).onCameraIntrinsics)==null||C.call(T,M),this.trackerSpace=k;let F=await this.options.client.localizeWithFrame(S,M);F!=null&&F.localizeData&&((y=(R=this.options).onPoseResult)==null||y.call(R,{poseFound:F.localizeData.poseFound,position:F.localizeData.position,rotation:F.localizeData.rotation,mapIds:F.localizeData.mapIds,confidence:F.localizeData.confidence})),a({result:F});return}}d+1>=U?a({result:null,failureReason:"Camera image not available. The device or browser may not support camera access in AR."}):o(l+1,d+1);}catch(m){s(m);}});};o(0,0);})}async trackObjects(){var s,r,o,l,d,p,c,h,g,v,b,I,T,C,R,y,m;if(!this.session)throw new Error("No active XR session. Start AR before calling trackObjects().");let e=this.options.objectCodes;if(!(e!=null&&e.length))return (r=(s=this.options).onError)==null||r.call(s,new Error("No objectCodes configured. Set objectCodes in the session options.")),null;if(this._isLocalizing)return null;(l=(o=this.options).onObjectTrackingInit)==null||l.call(o),this._isLocalizing=true;let t=null,n;try{let f=await this.captureFrameAndTrack(e);t=f.outcome,n=f.failureReason;}catch(f){let L=f instanceof Error?f.message:String(f);return (p=(d=this.options).onObjectTrackingFailure)==null||p.call(d,L),(h=(c=this.options).onError)==null||h.call(c,f),null}finally{this._isLocalizing=false;}if(!t)return (v=(g=this.options).onObjectTrackingFailure)==null||v.call(g,n!=null?n:"No object detected."),null;let i=(b=this.options.confidenceCheck)!=null?b:false,a=Math.max(X,Math.min((I=this.options.confidenceThreshold)!=null?I:.5,G));return i&&t.result.confidence<a?((C=(T=this.options).onObjectTrackingFailure)==null||C.call(T,`Confidence ${t.result.confidence.toFixed(3)} below threshold ${a}.`),null):((y=(R=this.options).onObjectTrackingSuccess)==null||y.call(R,t.result),(m=this.adapterObjectTrackingHandler)==null||m.call(this,t.result,t.trackerSpace),t.result)}async captureFrameAndTrack(e){var s,r;let t=(s=this.options.trackingCaptureDelayMs)!=null?s:0;if(t>0&&(await new Promise(o=>setTimeout(o,t)),!this.session))return {outcome:null,failureReason:"Session ended during capture delay."};let n=this.session,i=this.referenceSpace,a=(r=this.options.localizationTrackingTimeoutMs)!=null?r:H;return new Promise((o,l)=>{let d=Date.now(),p=(c,h)=>{n.requestAnimationFrame(async(g,v)=>{var b,I,T,C,R;try{let y=Date.now()-d,m=v.getViewerPose(i);if(!m){if(y>=a||c+1>=N){let L=(a/1e3).toFixed(1);o({outcome:null,failureReason:`Tracking unavailable: no viewer pose within ${L}s. Try moving the device or ensuring good lighting.`});}else p(c+1,h);return}let f=m.views;for(let L of f){let w=L.camera;if(!w)continue;let E=(T=(I=(b=this.xrBinding)==null?void 0:b.getCameraImage)==null?void 0:I.call(b,w))!=null?T:null;if(!E)continue;let{width:x,height:D}=w;if(!x||!D)continue;let A=_(L.transform.matrix),k=await P(this.gl,E,x,D);if(!k)continue;let S=j(L.projectionMatrix,{width:k.width,height:k.height,x:0,y:0});if(S){(R=(C=this.options).onObjectTrackingRequested)==null||R.call(C,k,S);let M=await this.options.client.trackObject(k,S,e);o(M?{outcome:{result:M,trackerSpace:A}}:{outcome:null,failureReason:"No object detected."});return}}h+1>=U?o({outcome:null,failureReason:"Camera image not available. The device or browser may not support camera access in AR."}):p(c+1,h+1);}catch(y){l(y);}});};p(0,0);})}dispose(){if(this.bgLocalizationTimer!==null&&(clearInterval(this.bgLocalizationTimer),this.bgLocalizationTimer=null),this.session)try{this.session.end();}catch{}let e=this.gl.canvas;e instanceof HTMLCanvasElement&&(e.removeEventListener("webglcontextlost",this.handleContextLost),e.removeEventListener("webglcontextrestored",this.handleContextRestored)),this.arButton=null;}};
2
+ export{z as DEFAULT_ENDPOINTS,O as MultisetClient,B as XRSessionManager};
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { DEFAULT_ENDPOINTS, ICameraIntrinsicsEvent, IFrameCaptureEvent, IGetMapsDetailsResponse, ILocalizeAndMapDetails, ILocalizeResponse, ILocalizeResultEvent, IMapSetMapsResponse, IMultisetClientConfig, IMultisetSdkEndpoints, IPoseResultEvent, IXRFrameEvent, IXRSessionOptions, MapType, MultisetClient, XRSessionManager } from './core/index.js';
1
+ export { DEFAULT_ENDPOINTS, ICameraIntrinsicsEvent, IFrameCaptureEvent, IGetMapsDetailsResponse, ILocalizeAndMapDetails, ILocalizeResponse, ILocalizeResultEvent, IMapSetMapsResponse, IMultisetClientConfig, IMultisetSdkEndpoints, IObjectTrackingResponse, IPoseResultEvent, IXRFrameEvent, IXRSessionOptions, MapType, MultisetClient, XRSessionManager } from './core/index.js';
2
2
  export { IThreeAdapterOptions, ThreeAdapter } from './three/index.js';
3
3
  import 'three';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import D from'axios';import*as g from'three';import {DRACOLoader}from'three/examples/jsm/loaders/DRACOLoader.js';import {GLTFLoader}from'three/examples/jsm/loaders/GLTFLoader.js';import {TransformControls}from'three/examples/jsm/controls/TransformControls.js';var k={authUrl:"https://api.multiset.ai/v1/m2m/token",queryUrl:"https://api.multiset.ai/v1/vps/map/query-form",mapDetailsUrl:"https://api.multiset.ai/v1/vps/map/",mapSetDetailsUrl:"https://api.multiset.ai/v1/vps/map-set/",fileDownloadUrl:"https://api.multiset.ai/v1/file"};var X=class{constructor(e){this.accessToken=null;this.mapDetailsCache={};let{clientId:t,clientSecret:i,...o}=e;this.credentials={clientId:t,clientSecret:i},this.config=o,this.endpoints={...k,...o.endpoints};}get token(){return this.accessToken}async authorize(){var i,o,r;let e=await D.post(this.endpoints.authUrl,{},{auth:{username:this.credentials.clientId,password:this.credentials.clientSecret}}),t=(r=(i=e.data)==null?void 0:i.token)!=null?r:(o=e.data)==null?void 0:o.access_token;if(!t)throw new Error("Authorization succeeded but no token was returned.");return this.accessToken=t,t}async downloadFile(e){if(!this.accessToken||!e)return "";try{let t=await D.get(`${this.endpoints.fileDownloadUrl}?key=${encodeURIComponent(e)}`,{headers:{Authorization:`Bearer ${this.accessToken}`}});return t.status===200?t.data.url:""}catch{return ""}}async localizeWithFrame(e,t,i){var o;if(!this.accessToken)throw new Error("Access token is missing. Call authorize() first.");return this.queryLocalization(e,t,(o=i==null?void 0:i.fetchMapDetails)!=null?o:false)}async fetchMapDetails(e){if(!this.accessToken)return null;let t=this.mapDetailsCache[e];if(t)return t;try{let i=await D.get(`${this.endpoints.mapDetailsUrl}${e}`,{headers:{Authorization:`Bearer ${this.accessToken}`}});return this.mapDetailsCache[e]=i.data,i.data}catch{return null}}async getGeoPoseComponents(){if(typeof navigator=="undefined"||!("geolocation"in navigator)||!navigator.geolocation)return null;try{let e=await new Promise((n,l)=>{navigator.geolocation.getCurrentPosition(n,l,{enableHighAccuracy:!0,timeout:1e4,maximumAge:0});}),{latitude:t,longitude:i,altitude:o}=e.coords,r=typeof o=="number"&&!Number.isNaN(o)?o:0;return {latitude:t,longitude:i,altitude:r}}catch{return null}}async queryLocalization(e,t,i){var u,p,m;let o=new FormData;this.config.mapType==="map"?o.append("mapCode",this.config.code):o.append("mapSetCode",this.config.code);let r=(u=this.config.isRightHanded)!=null?u:true;o.append("isRightHanded",`${r}`),o.append("fx",`${t.fx}`),o.append("fy",`${t.fy}`),o.append("px",`${t.px}`),o.append("py",`${t.py}`),o.append("width",`${e.width}`),o.append("height",`${e.height}`),o.append("queryImage",e.blob),this.config.convertToGeoCoordinates!==void 0&&o.append("convertToGeoCoordinates",`${this.config.convertToGeoCoordinates}`),"hintMapCodes"in this.config&&((p=this.config.hintMapCodes)!=null&&p.length)&&this.config.hintMapCodes.forEach(c=>{o.append("hintMapCodes",c);}),this.config.hintPosition&&o.append("hintPosition",this.config.hintPosition);let n;if(this.config.passGeoPose){let c=await this.getGeoPoseComponents();if(c){let{latitude:h,longitude:f,altitude:w}=c;n=`${h},${f},${w}`;}}if(n&&o.append("geoHint",n),this.config.hintRadius!==void 0){let c=Number(this.config.hintRadius);if(Number.isNaN(c)||c<1||c>100)throw new Error("hintRadius must be a number between 1 and 100.");if(!this.config.hintPosition&&!n)throw new Error("hintRadius requires hintPosition, or passGeoPose with available geolocation to generate geoHint.");o.append("hintRadius",`${c}`);}"use2DFiltering"in this.config&&this.config.use2DFiltering!==void 0&&o.append("use2DFiltering",`${this.config.use2DFiltering}`);let s=(await D.post(this.endpoints.queryUrl,o,{headers:{Authorization:`Bearer ${this.accessToken}`}})).data;if(!s.poseFound)return null;let d={localizeData:s};if(i&&((m=s.mapCodes)!=null&&m.length)){let c=await this.fetchMapDetails(s.mapCodes[0]);c&&(d.mapDetails=c);}return d}};async function q(a,e,t,i=.8,o=e,r=t){let n=document.createElement("canvas");n.width=e,n.height=t;let l=n.getContext("2d");if(!l)return new Blob;l.putImageData(new ImageData(new Uint8ClampedArray(a),e,t),0,0);let s=document.createElement("canvas");s.width=o,s.height=r;let d=s.getContext("2d");return d?(d.drawImage(n,0,0,o,r),new Promise(u=>{s.toBlob(p=>u(p!=null?p:new Blob),"image/jpeg",i);})):new Blob}async function _(a,e,t,i){let o=a.createFramebuffer();if(!o)return null;let r=a.getParameter(a.FRAMEBUFFER_BINDING),n;try{a.bindFramebuffer(a.FRAMEBUFFER,o),a.framebufferTexture2D(a.FRAMEBUFFER,a.COLOR_ATTACHMENT0,a.TEXTURE_2D,e,0),n=new Uint8Array(t*i*4),a.readPixels(0,0,t,i,a.RGBA,a.UNSIGNED_BYTE,n);}finally{a.bindFramebuffer(a.FRAMEBUFFER,r),a.deleteFramebuffer(o);}let l=new Uint8ClampedArray(n.length);for(let m=0;m<i;m+=1){let c=m*t*4,h=(i-m-1)*t*4;l.set(n.subarray(c,c+t*4),h);}let s=Math.min(1,1280/Math.max(t,i)),d=Math.round(t*s),u=Math.round(i*s),p=await q(l.buffer,t,i,.7,d,u);return p.size?{blob:p,width:d,height:u}:null}function V(a,e){let t=a,i=(1-t[8])*e.width/2+e.x,o=(1-t[9])*e.height/2+e.y,r=e.width/2*t[0],n=e.height/2*t[5];return {fx:r,fy:n,px:i,py:o,width:e.width,height:e.height}}function O(a){return new Float32Array(a)}var j=.2,K=.8,Q=60,J=1e4,Y=300,Z=10,G=class{constructor(e,t){this.gl=e;this.options=t;this.session=null;this.referenceSpace=null;this.baseLayer=null;this.xrBinding=null;this.lastFrameTime=0;this.trackingLossFrames=0;this.hadTrackingLoss=false;this._isLocalizing=false;this.trackerSpace=null;this.arButton=null;this.xrFrameHandler=null;this.adapterResultHandler=null;this.adapterSessionStartHandler=null;this.adapterSessionEndHandler=null;this.handleContextLost=e=>{var t,i;if(e.preventDefault(),this.session)try{this.session.end();}catch{}(i=(t=this.options).onContextLost)==null||i.call(t);};this.handleContextRestored=()=>{var e,t;(t=(e=this.options).onContextRestored)==null||t.call(e);};let i=e.canvas;i instanceof HTMLCanvasElement&&(i.addEventListener("webglcontextlost",this.handleContextLost),i.addEventListener("webglcontextrestored",this.handleContextRestored));}static async isSupported(){if(!navigator.xr)return false;try{return await navigator.xr.isSessionSupported("immersive-ar")}catch{return false}}setXRFrameHandler(e){this.xrFrameHandler=e;}setAdapterResultHandler(e){this.adapterResultHandler=e;}setAdapterSessionHandlers(e,t){this.adapterSessionStartHandler=e,this.adapterSessionEndHandler=t;}getClient(){return this.options.client}getOverlayRoot(){return this.options.overlayRoot}isActive(){return this.session!==null}get isLocalizing(){return this._isLocalizing}stopSession(){this.session&&this.session.end();}getBaseLayer(){return this.baseLayer}createButton(){let e=document.createElement("button");return e.textContent="START AR",e.className="multiset-ar-button multiset-ar-inactive",e.style.cssText=["position:fixed","bottom:20px","left:50%","transform:translateX(-50%)","padding:12px 24px","border:1px solid #fff","border-radius:4px","background:rgba(0,0,0,0.1)","color:#fff","font-size:13px","cursor:pointer","z-index:999"].join(";"),e.addEventListener("click",()=>{this.session?this.session.end():this.startSession();}),this.arButton=e,e}async startSession(){var e,t,i;try{if(!navigator.xr)throw new Error("WebXR not supported on this device.");let o=await navigator.xr.requestSession("immersive-ar",{requiredFeatures:["local"],optionalFeatures:["camera-access","dom-overlay"],domOverlay:{root:(e=this.options.overlayRoot)!=null?e:document.body}});await this.initSession(o);}catch(o){(i=(t=this.options).onError)==null||i.call(t,o);}}async initSession(e){var n,l,s,d;await this.gl.makeXRCompatible();let t={};this.options.framebufferScaleFactor!==void 0&&(t.framebufferScaleFactor=this.options.framebufferScaleFactor);let i=new XRWebGLLayer(e,this.gl,t);await e.updateRenderState({baseLayer:i});let o=await e.requestReferenceSpace((n=this.options.referenceSpaceType)!=null?n:"local");this.session=e,this.baseLayer=i,this.referenceSpace=o;let r=XRWebGLBinding;this.xrBinding=new r(e,this.gl),e.addEventListener("end",()=>this.handleSessionEnd()),e.requestAnimationFrame((u,p)=>this.xrLoop(u,p)),this.arButton&&(this.arButton.textContent="STOP AR",this.arButton.classList.replace("multiset-ar-inactive","multiset-ar-active")),(l=this.adapterSessionStartHandler)==null||l.call(this),(d=(s=this.options).onSessionStart)==null||d.call(s),this.options.autoLocalize&&e.requestAnimationFrame(()=>{this.localizeFrame();});}handleSessionEnd(){var e,t,i;this.arButton&&(this.arButton.textContent="START AR",this.arButton.classList.replace("multiset-ar-active","multiset-ar-inactive")),this.session=null,this.baseLayer=null,this.referenceSpace=null,this.xrBinding=null,this.lastFrameTime=0,this.trackingLossFrames=0,this.hadTrackingLoss=false,this._isLocalizing=false,this.trackerSpace=null,(e=this.adapterSessionEndHandler)==null||e.call(this),(i=(t=this.options).onSessionEnd)==null||i.call(t);}xrLoop(e,t){var l;this.session.requestAnimationFrame((s,d)=>this.xrLoop(s,d));let i=this.lastFrameTime===0?0:(e-this.lastFrameTime)/1e3;this.lastFrameTime=e;let o=t.getViewerPose(this.referenceSpace);if(!o){this.options.relocalization&&(this.trackingLossFrames+=1,this.trackingLossFrames>=Q&&(this.hadTrackingLoss=true));return}this.hadTrackingLoss&&!this._isLocalizing&&this.options.relocalization?(this.hadTrackingLoss=false,this.trackingLossFrames=0,this.localizeFrame()):this.trackingLossFrames=0;let r=o.views[0],n=this.baseLayer.getViewport(r);(l=this.xrFrameHandler)==null||l.call(this,{time:e,frame:t,pose:o,view:r,viewport:n,baseLayer:this.baseLayer,referenceSpace:this.referenceSpace,deltaSeconds:i});}async localizeFrame(){var l,s,d,u,p,m,c,h,f,w,C,b,M;if(!this.session)throw new Error("No active XR session. Start AR before calling localizeFrame().");let e=(l=this.options.confidenceCheck)!=null?l:false,t=Math.max(j,Math.min((s=this.options.confidenceThreshold)!=null?s:.5,K));(u=(d=this.options).onLocalizationInit)==null||u.call(d),this._isLocalizing=true;let i=null,o;try{let R=await this.captureFrame();i=R.result,o=R.failureReason;}catch(R){return (m=(p=this.options).onError)==null||m.call(p,R),null}finally{this._isLocalizing=false;}if(i&&(!e||((c=i.localizeData.confidence)!=null?c:0)>=t)&&i)return (f=(h=this.options).onLocalizationSuccess)==null||f.call(h,i),this.trackerSpace&&((w=this.adapterResultHandler)==null||w.call(this,i,this.trackerSpace)),i;let n=o!=null?o:i?e?`Best confidence ${(C=i.localizeData.confidence)!=null?C:0} below threshold ${t}.`:void 0:"All attempts failed to produce a pose.";return (M=(b=this.options).onLocalizationFailure)==null||M.call(b,n),null}async captureFrame(){var o;let e=this.session,t=this.referenceSpace,i=(o=this.options.localizationTrackingTimeoutMs)!=null?o:J;return new Promise((r,n)=>{let l=Date.now(),s=(d,u)=>{e.requestAnimationFrame(async(p,m)=>{var c,h,f,w,C,b,M,R,I;try{let T=Date.now()-l,z=m.getViewerPose(t);if(!z){if(T>=i||d+1>=Y){let L=(i/1e3).toFixed(1);r({result:null,failureReason:`Tracking unavailable: no viewer pose within ${L}s. Try moving the device or ensuring good lighting.`});}else s(d+1,u);return}let A=z.views;for(let L of A){let x=L.camera;if(!x)continue;let F=(f=(h=(c=this.xrBinding)==null?void 0:c.getCameraImage)==null?void 0:h.call(c,x))!=null?f:null;if(!F)continue;let{width:S,height:U}=x;if(!S||!U)continue;let H=await _(this.gl,F,S,U);if(!H)continue;let B=V(L.projectionMatrix,{width:H.width,height:H.height,x:0,y:0});if(B){(C=(w=this.options).onFrameCaptured)==null||C.call(w,H),(M=(b=this.options).onCameraIntrinsics)==null||M.call(b,B),this.trackerSpace=O(L.transform.matrix);let y=await this.options.client.localizeWithFrame(H,B);y!=null&&y.localizeData&&((I=(R=this.options).onPoseResult)==null||I.call(R,{poseFound:y.localizeData.poseFound,position:y.localizeData.position,rotation:y.localizeData.rotation,mapIds:y.localizeData.mapIds,confidence:y.localizeData.confidence})),r({result:y});return}}u+1>=Z?r({result:null,failureReason:"Camera image not available. The device or browser may not support camera access in AR."}):s(d+1,u+1);}catch(T){n(T);}});};s(0,0);})}dispose(){if(this.session)try{this.session.end();}catch{}let e=this.gl.canvas;e instanceof HTMLCanvasElement&&(e.removeEventListener("webglcontextlost",this.handleContextLost),e.removeEventListener("webglcontextrestored",this.handleContextRestored)),this.arButton=null;}};var ee=`
1
+ import*as g from'three';import {DRACOLoader}from'three/examples/jsm/loaders/DRACOLoader.js';import {GLTFLoader}from'three/examples/jsm/loaders/GLTFLoader.js';var V={authUrl:"https://api.multiset.ai/v1/m2m/token",queryUrl:"https://api.multiset.ai/v1/vps/map/query-form",mapDetailsUrl:"https://api.multiset.ai/v1/vps/map/",mapSetDetailsUrl:"https://api.multiset.ai/v1/vps/map-set/",fileDownloadUrl:"https://api.multiset.ai/v1/file",objectQueryUrl:"https://api.multiset.ai/v1/vps/object/query",objectDetailsUrl:"https://api.multiset.ai/v1/vps/object/"};var X=class{constructor(e){this.accessToken=null;this.mapDetailsCache={};let{clientId:t,clientSecret:i,...r}=e;this.credentials={clientId:t,clientSecret:i},this.config=r,this.endpoints={...V,...r.endpoints};}get token(){return this.accessToken}async authorize(){var r;let e=btoa(`${this.credentials.clientId}:${this.credentials.clientSecret}`),t=await this.apiFetch(this.endpoints.authUrl,{method:"POST",headers:{Authorization:`Basic ${e}`,"Content-Type":"application/json"},body:"{}"}),i=(r=t.token)!=null?r:t.access_token;if(!i)throw new Error("Authorization succeeded but no token was returned.");return this.accessToken=i,i}async downloadFile(e){var t;if(!this.accessToken||!e)return "";try{return (t=(await this.apiFetch(`${this.endpoints.fileDownloadUrl}?key=${encodeURIComponent(e)}`)).url)!=null?t:""}catch{return ""}}async localizeWithFrame(e,t,i){var r;if(!this.accessToken)throw new Error("Access token is missing. Call authorize() first.");return this.queryLocalization(e,t,(r=i==null?void 0:i.fetchMapDetails)!=null?r:false)}async trackObject(e,t,i){var n;if(!this.accessToken)throw new Error("Access token is missing. Call authorize() first.");let r=new FormData;r.append("queryImage",e.blob),r.append("isRightHanded",`${(n=this.config.isRightHanded)!=null?n:true}`),r.append("fx",`${t.fx}`),r.append("fy",`${t.fy}`),r.append("px",`${t.px}`),r.append("py",`${t.py}`),r.append("width",`${e.width}`),r.append("height",`${e.height}`),i.forEach(a=>r.append("objectCode",a));let o=await this.apiFetch(this.endpoints.objectQueryUrl,{method:"POST",body:r});return o.poseFound?o:null}async downloadObjectMesh(e){let t=await this.fetchObjectMeshKey(e);return t&&await this.downloadFile(t)||null}async fetchObjectMeshKey(e){var t,i,r;if(!this.accessToken)return null;try{let o=await this.apiFetch(`${this.endpoints.objectDetailsUrl}${e}`);return (r=(i=(t=o.objectMesh)==null?void 0:t.meshLink)!=null?i:o.meshLink)!=null?r:null}catch{return null}}async fetchMapDetails(e){if(!this.accessToken)return null;let t=this.mapDetailsCache[e];if(t)return t;try{let i=await this.apiFetch(`${this.endpoints.mapDetailsUrl}${e}`);return this.mapDetailsCache[e]=i,i}catch{return null}}async apiFetch(e,t={}){var o;let i={};this.accessToken&&(i.Authorization=`Bearer ${this.accessToken}`);let r=await fetch(e,{...t,headers:{...i,...t.headers}});if(!r.ok){let n=`HTTP ${r.status} ${r.statusText}`;try{let a=await r.json(),s=(o=a.error)!=null?o:a.message;s&&(n+=`: ${s}`);}catch{}throw new Error(n)}return r.json()}async getGeoPoseComponents(){if(typeof navigator=="undefined"||!("geolocation"in navigator)||!navigator.geolocation)return null;try{let e=await new Promise((n,a)=>{navigator.geolocation.getCurrentPosition(n,a,{enableHighAccuracy:!0,timeout:1e4,maximumAge:0});}),{latitude:t,longitude:i,altitude:r}=e.coords,o=typeof r=="number"&&!Number.isNaN(r)?r:0;return {latitude:t,longitude:i,altitude:o}}catch{return null}}async queryLocalization(e,t,i){var c,d,p;let r=new FormData;this.config.mapType==="map"?r.append("mapCode",this.config.code):r.append("mapSetCode",this.config.code);let o=(c=this.config.isRightHanded)!=null?c:true;r.append("isRightHanded",`${o}`),r.append("fx",`${t.fx}`),r.append("fy",`${t.fy}`),r.append("px",`${t.px}`),r.append("py",`${t.py}`),r.append("width",`${e.width}`),r.append("height",`${e.height}`),r.append("queryImage",e.blob),this.config.convertToGeoCoordinates!==void 0&&r.append("convertToGeoCoordinates",`${this.config.convertToGeoCoordinates}`),"hintMapCodes"in this.config&&((d=this.config.hintMapCodes)!=null&&d.length)&&this.config.hintMapCodes.forEach(u=>{r.append("hintMapCodes",u);}),this.config.hintPosition&&r.append("hintPosition",this.config.hintPosition);let n;if(this.config.passGeoPose){let u=await this.getGeoPoseComponents();if(u){let{latitude:m,longitude:v,altitude:y}=u;n=`${m},${v},${y}`;}}if(n&&r.append("geoHint",n),this.config.hintRadius!==void 0){let u=Number(this.config.hintRadius);if(Number.isNaN(u)||u<1||u>100)throw new Error("hintRadius must be a number between 1 and 100.");if(!this.config.hintPosition&&!n)throw new Error("hintRadius requires hintPosition, or passGeoPose with available geolocation to generate geoHint.");r.append("hintRadius",`${u}`);}"use2DFiltering"in this.config&&this.config.use2DFiltering!==void 0&&r.append("use2DFiltering",`${this.config.use2DFiltering}`);let a=await this.apiFetch(this.endpoints.queryUrl,{method:"POST",body:r});if(!a.poseFound)return null;let s={localizeData:a};if(i&&((p=a.mapCodes)!=null&&p.length)){let u=await this.fetchMapDetails(a.mapCodes[0]);u&&(s.mapDetails=u);}return s}};async function se(l,e,t,i=.8,r=e,o=t){let n=document.createElement("canvas");n.width=e,n.height=t;let a=n.getContext("2d");if(!a)return new Blob;a.putImageData(new ImageData(new Uint8ClampedArray(l),e,t),0,0);let s=document.createElement("canvas");s.width=r,s.height=o;let c=s.getContext("2d");return c?(c.drawImage(n,0,0,r,o),new Promise(d=>{s.toBlob(p=>d(p!=null?p:new Blob),"image/jpeg",i);})):new Blob}async function U(l,e,t,i){let r=l.createFramebuffer();if(!r)return null;let o=l.getParameter(l.FRAMEBUFFER_BINDING),n;try{l.bindFramebuffer(l.FRAMEBUFFER,r),l.framebufferTexture2D(l.FRAMEBUFFER,l.COLOR_ATTACHMENT0,l.TEXTURE_2D,e,0),n=new Uint8Array(t*i*4),l.readPixels(0,0,t,i,l.RGBA,l.UNSIGNED_BYTE,n);}finally{l.bindFramebuffer(l.FRAMEBUFFER,o),l.deleteFramebuffer(r);}let a=new Uint8ClampedArray(n.length);for(let u=0;u<i;u+=1){let m=u*t*4,v=(i-u-1)*t*4;a.set(n.subarray(m,m+t*4),v);}let s=Math.min(1,1280/Math.max(t,i)),c=Math.round(t*s),d=Math.round(i*s),p=await se(a.buffer,t,i,.7,c,d);return p.size?{blob:p,width:c,height:d}:null}function $(l,e){let t=l,i=(1-t[8])*e.width/2+e.x,r=(1-t[9])*e.height/2+e.y,o=e.width/2*t[0],n=e.height/2*t[5];return {fx:o,fy:n,px:i,py:r,width:e.width,height:e.height}}function q(l){return new Float32Array(l)}var Z=.2,Y=.8,le=60,J=1e4,ee=300,te=10,ce=10,de=180,ue=30,pe=10,P=class{constructor(e,t){this.gl=e;this.options=t;this.session=null;this.referenceSpace=null;this.baseLayer=null;this.xrBinding=null;this.lastFrameTime=0;this.trackingLossFrames=0;this.hadTrackingLoss=false;this._isLocalizing=false;this.trackerSpace=null;this.bgLocalizationTimer=null;this.arButton=null;this.xrFrameHandler=null;this.adapterResultHandler=null;this.adapterObjectTrackingHandler=null;this.adapterSessionStartHandler=null;this.adapterSessionEndHandler=null;this.handleContextLost=e=>{var t,i;if(e.preventDefault(),this.session)try{this.session.end();}catch{}(i=(t=this.options).onContextLost)==null||i.call(t);};this.handleContextRestored=()=>{var e,t;(t=(e=this.options).onContextRestored)==null||t.call(e);};let i=e.canvas;i instanceof HTMLCanvasElement&&(i.addEventListener("webglcontextlost",this.handleContextLost),i.addEventListener("webglcontextrestored",this.handleContextRestored));}static async isSupported(){if(!navigator.xr)return false;try{return await navigator.xr.isSessionSupported("immersive-ar")}catch{return false}}setXRFrameHandler(e){this.xrFrameHandler=e;}setAdapterResultHandler(e){this.adapterResultHandler=e;}setAdapterObjectTrackingHandler(e){this.adapterObjectTrackingHandler=e;}setAdapterSessionHandlers(e,t){this.adapterSessionStartHandler=e,this.adapterSessionEndHandler=t;}getClient(){return this.options.client}getOverlayRoot(){return this.options.overlayRoot}isActive(){return this.session!==null}get isLocalizing(){return this._isLocalizing}stopSession(){this.session&&this.session.end();}getBaseLayer(){return this.baseLayer}createButton(){let e=document.createElement("button");return e.textContent="START AR",e.className="multiset-ar-button multiset-ar-inactive",e.style.cssText=["position:fixed","bottom:20px","left:50%","transform:translateX(-50%)","padding:12px 24px","border:1px solid #fff","border-radius:4px","background:rgba(0,0,0,0.1)","color:#fff","font-size:13px","cursor:pointer","z-index:999"].join(";"),e.addEventListener("click",()=>{this.session?this.session.end():this.startSession();}),this.arButton=e,e}async startSession(){var e,t,i;try{if(!navigator.xr)throw new Error("WebXR not supported on this device.");let r=await navigator.xr.requestSession("immersive-ar",{requiredFeatures:["local"],optionalFeatures:["camera-access","dom-overlay"],domOverlay:{root:(e=this.options.overlayRoot)!=null?e:document.body}});await this.initSession(r);}catch(r){(i=(t=this.options).onError)==null||i.call(t,r);}}async initSession(e){var n,a,s,c;await this.gl.makeXRCompatible();let t={};this.options.framebufferScaleFactor!==void 0&&(t.framebufferScaleFactor=this.options.framebufferScaleFactor);let i=new XRWebGLLayer(e,this.gl,t);await e.updateRenderState({baseLayer:i});let r=await e.requestReferenceSpace((n=this.options.referenceSpaceType)!=null?n:"local");this.session=e,this.baseLayer=i,this.referenceSpace=r;let o=XRWebGLBinding;this.xrBinding=new o(e,this.gl),e.addEventListener("end",()=>this.handleSessionEnd()),e.requestAnimationFrame((d,p)=>this.xrLoop(d,p)),this.arButton&&(this.arButton.textContent="STOP AR",this.arButton.classList.replace("multiset-ar-inactive","multiset-ar-active")),(a=this.adapterSessionStartHandler)==null||a.call(this),(c=(s=this.options).onSessionStart)==null||c.call(s),this.options.autoLocalize&&e.requestAnimationFrame(()=>{this.localizeFrame();}),this.options.autoTracking&&e.requestAnimationFrame(()=>{this.trackObjects();}),this.options.backgroundLocalization&&this.startBackgroundLocalization();}startBackgroundLocalization(){var r,o,n;let e=((o=(r=this.options.objectCodes)==null?void 0:r.length)!=null?o:0)>0,t=e?pe:ue,i=Math.max(ce,Math.min((n=this.options.bgLocalizationDuration)!=null?n:t,de))*1e3;this.bgLocalizationTimer=setInterval(()=>{this.session&&!this._isLocalizing&&(e?this.trackObjects():this.localizeFrame());},i);}handleSessionEnd(){var e,t,i;this.arButton&&(this.arButton.textContent="START AR",this.arButton.classList.replace("multiset-ar-active","multiset-ar-inactive")),this.bgLocalizationTimer!==null&&(clearInterval(this.bgLocalizationTimer),this.bgLocalizationTimer=null),this.session=null,this.baseLayer=null,this.referenceSpace=null,this.xrBinding=null,this.lastFrameTime=0,this.trackingLossFrames=0,this.hadTrackingLoss=false,this._isLocalizing=false,this.trackerSpace=null,(e=this.adapterSessionEndHandler)==null||e.call(this),(i=(t=this.options).onSessionEnd)==null||i.call(t);}xrLoop(e,t){var a;this.session.requestAnimationFrame((s,c)=>this.xrLoop(s,c));let i=this.lastFrameTime===0?0:(e-this.lastFrameTime)/1e3;this.lastFrameTime=e;let r=t.getViewerPose(this.referenceSpace);if(!r){(this.options.relocalization||this.options.restartTracking)&&(this.trackingLossFrames+=1,this.trackingLossFrames>=le&&(this.hadTrackingLoss=true));return}this.hadTrackingLoss&&!this._isLocalizing&&(this.options.relocalization||this.options.restartTracking)?(this.hadTrackingLoss=false,this.trackingLossFrames=0,this.options.restartTracking?this.trackObjects():this.options.relocalization&&this.localizeFrame()):this.trackingLossFrames=0;let o=r.views[0],n=this.baseLayer.getViewport(o);(a=this.xrFrameHandler)==null||a.call(this,{time:e,frame:t,pose:r,view:o,viewport:n,baseLayer:this.baseLayer,referenceSpace:this.referenceSpace,deltaSeconds:i});}async localizeFrame(){var a,s,c,d,p,u,m,v,y,b,f,E,w,R,L;if(!this.session)throw new Error("No active XR session. Start AR before calling localizeFrame().");let e=(a=this.options.confidenceCheck)!=null?a:false,t=Math.max(Z,Math.min((s=this.options.confidenceThreshold)!=null?s:.5,Y));(d=(c=this.options).onLocalizationInit)==null||d.call(c),this._isLocalizing=true;let i=null,r;try{let T=await this.captureFrame();i=T.result,r=T.failureReason;}catch(T){let C=T instanceof Error?T.message:String(T);return (u=(p=this.options).onLocalizationFailure)==null||u.call(p,C),(v=(m=this.options).onError)==null||v.call(m,T),null}finally{this._isLocalizing=false;}if(i&&(!e||((y=i.localizeData.confidence)!=null?y:0)>=t)&&i)return (f=(b=this.options).onLocalizationSuccess)==null||f.call(b,i),this.trackerSpace&&((E=this.adapterResultHandler)==null||E.call(this,i,this.trackerSpace)),i;let n=r!=null?r:i?e?`Best confidence ${(w=i.localizeData.confidence)!=null?w:0} below threshold ${t}.`:void 0:"All attempts failed to produce a pose.";return (L=(R=this.options).onLocalizationFailure)==null||L.call(R,n),null}async captureFrame(){var r;let e=this.session,t=this.referenceSpace,i=(r=this.options.localizationTrackingTimeoutMs)!=null?r:J;return new Promise((o,n)=>{let a=Date.now(),s=(c,d)=>{e.requestAnimationFrame(async(p,u)=>{var m,v,y,b,f,E,w,R,L;try{let T=Date.now()-a,C=u.getViewerPose(t);if(!C){if(T>=i||c+1>=ee){let S=(i/1e3).toFixed(1);o({result:null,failureReason:`Tracking unavailable: no viewer pose within ${S}s. Try moving the device or ensuring good lighting.`});}else s(c+1,d);return}let I=C.views;for(let S of I){let F=S.camera;if(!F)continue;let G=(y=(v=(m=this.xrBinding)==null?void 0:m.getCameraImage)==null?void 0:v.call(m,F))!=null?y:null;if(!G)continue;let{width:k,height:j}=F;if(!k||!j)continue;let O=q(S.transform.matrix),z=await U(this.gl,G,k,j);if(!z)continue;let D=$(S.projectionMatrix,{width:z.width,height:z.height,x:0,y:0});if(D){(f=(b=this.options).onFrameCaptured)==null||f.call(b,z),(w=(E=this.options).onCameraIntrinsics)==null||w.call(E,D),this.trackerSpace=O;let A=await this.options.client.localizeWithFrame(z,D);A!=null&&A.localizeData&&((L=(R=this.options).onPoseResult)==null||L.call(R,{poseFound:A.localizeData.poseFound,position:A.localizeData.position,rotation:A.localizeData.rotation,mapIds:A.localizeData.mapIds,confidence:A.localizeData.confidence})),o({result:A});return}}d+1>=te?o({result:null,failureReason:"Camera image not available. The device or browser may not support camera access in AR."}):s(c+1,d+1);}catch(T){n(T);}});};s(0,0);})}async trackObjects(){var n,a,s,c,d,p,u,m,v,y,b,f,E,w,R,L,T;if(!this.session)throw new Error("No active XR session. Start AR before calling trackObjects().");let e=this.options.objectCodes;if(!(e!=null&&e.length))return (a=(n=this.options).onError)==null||a.call(n,new Error("No objectCodes configured. Set objectCodes in the session options.")),null;if(this._isLocalizing)return null;(c=(s=this.options).onObjectTrackingInit)==null||c.call(s),this._isLocalizing=true;let t=null,i;try{let C=await this.captureFrameAndTrack(e);t=C.outcome,i=C.failureReason;}catch(C){let I=C instanceof Error?C.message:String(C);return (p=(d=this.options).onObjectTrackingFailure)==null||p.call(d,I),(m=(u=this.options).onError)==null||m.call(u,C),null}finally{this._isLocalizing=false;}if(!t)return (y=(v=this.options).onObjectTrackingFailure)==null||y.call(v,i!=null?i:"No object detected."),null;let r=(b=this.options.confidenceCheck)!=null?b:false,o=Math.max(Z,Math.min((f=this.options.confidenceThreshold)!=null?f:.5,Y));return r&&t.result.confidence<o?((w=(E=this.options).onObjectTrackingFailure)==null||w.call(E,`Confidence ${t.result.confidence.toFixed(3)} below threshold ${o}.`),null):((L=(R=this.options).onObjectTrackingSuccess)==null||L.call(R,t.result),(T=this.adapterObjectTrackingHandler)==null||T.call(this,t.result,t.trackerSpace),t.result)}async captureFrameAndTrack(e){var n,a;let t=(n=this.options.trackingCaptureDelayMs)!=null?n:0;if(t>0&&(await new Promise(s=>setTimeout(s,t)),!this.session))return {outcome:null,failureReason:"Session ended during capture delay."};let i=this.session,r=this.referenceSpace,o=(a=this.options.localizationTrackingTimeoutMs)!=null?a:J;return new Promise((s,c)=>{let d=Date.now(),p=(u,m)=>{i.requestAnimationFrame(async(v,y)=>{var b,f,E,w,R;try{let L=Date.now()-d,T=y.getViewerPose(r);if(!T){if(L>=o||u+1>=ee){let I=(o/1e3).toFixed(1);s({outcome:null,failureReason:`Tracking unavailable: no viewer pose within ${I}s. Try moving the device or ensuring good lighting.`});}else p(u+1,m);return}let C=T.views;for(let I of C){let S=I.camera;if(!S)continue;let F=(E=(f=(b=this.xrBinding)==null?void 0:b.getCameraImage)==null?void 0:f.call(b,S))!=null?E:null;if(!F)continue;let{width:G,height:k}=S;if(!G||!k)continue;let j=q(I.transform.matrix),O=await U(this.gl,F,G,k);if(!O)continue;let z=$(I.projectionMatrix,{width:O.width,height:O.height,x:0,y:0});if(z){(R=(w=this.options).onObjectTrackingRequested)==null||R.call(w,O,z);let D=await this.options.client.trackObject(O,z,e);s(D?{outcome:{result:D,trackerSpace:j}}:{outcome:null,failureReason:"No object detected."});return}}m+1>=te?s({outcome:null,failureReason:"Camera image not available. The device or browser may not support camera access in AR."}):p(u+1,m+1);}catch(L){c(L);}});};p(0,0);})}dispose(){if(this.bgLocalizationTimer!==null&&(clearInterval(this.bgLocalizationTimer),this.bgLocalizationTimer=null),this.session)try{this.session.end();}catch{}let e=this.gl.canvas;e instanceof HTMLCanvasElement&&(e.removeEventListener("webglcontextlost",this.handleContextLost),e.removeEventListener("webglcontextrestored",this.handleContextRestored)),this.arButton=null;}};var he=`
2
2
  varying vec3 vWorldPosition;
3
3
  varying vec3 vWorldNormal;
4
4
 
@@ -9,7 +9,7 @@ void main() {
9
9
 
10
10
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
11
11
  }
12
- `,te=`
12
+ `,me=`
13
13
  uniform vec3 uColor;
14
14
  uniform float uOpacity;
15
15
 
@@ -90,4 +90,45 @@ void main() {
90
90
 
91
91
  gl_FragColor = vec4(finalColor, alphaWithGlow);
92
92
  }
93
- `;function $(a={}){var f,w,C,b,M,R,I,T,z,A,L,x,F,S;let e=(f=a.color)!=null?f:"#7B2CBF",t=(w=a.opacity)!=null?w:150/255,i=(C=a.gridColor)!=null?C:"#ffeb3b",o=(b=a.gridScale)!=null?b:2,r=(M=a.gridLineWidth)!=null?M:.02,n=(R=a.showGrid)!=null?R:1,l=(I=a.center)!=null?I:new g.Vector3(0,0,0),s=(T=a.radius)!=null?T:1,d=(z=a.progress)!=null?z:1,u=(A=a.glowColor)!=null?A:"#FF3A00",p=(L=a.glowWidth)!=null?L:.05,m=(x=a.glowIntensity)!=null?x:2,c=(F=a.lightColor)!=null?F:"#ffffff",h=(S=a.lightDirection)!=null?S:new g.Vector3(.3,1,.3).normalize();return new g.ShaderMaterial({vertexShader:ee,fragmentShader:te,transparent:true,side:g.DoubleSide,uniforms:{uColor:{value:typeof e=="string"?new g.Color(e):e},uOpacity:{value:t},uGridColor:{value:typeof i=="string"?new g.Color(i):i},uGridSize:{value:o},uGridLineWidth:{value:r},uShowGrid:{value:n},uCenter:{value:l.clone()},uRadius:{value:s},uProgress:{value:d},uGlowColor:{value:typeof u=="string"?new g.Color(u):u},uGlowWidth:{value:p},uGlowIntensity:{value:m},uLightColor:{value:typeof c=="string"?new g.Color(c):c},uLightDirection:{value:h.clone()}}})}var P=class{constructor(e,t,i,o){this.scene=e;this.client=t;this.renderer=i;this.camera=o;this.gizmoControl=null;this.gizmoHelper=null;this.meshMaterial=null;this.localCenter=new g.Vector3;this.localRadius=0;this.radialDuration=4;this.meshGroup=new g.Group,this.meshGroup.visible=false,this.scene.add(this.meshGroup),this.dracoLoader=new DRACOLoader,this.dracoLoader.setDecoderPath("/draco/"),this.gltfLoader=new GLTFLoader,this.gltfLoader.setDRACOLoader(this.dracoLoader);}getMeshGroup(){return this.meshGroup}async ensureGizmoLoaded(){if(!this.gizmoControl){let e=new TransformControls(this.camera,this.renderer.domElement);e.setMode("translate"),e.setSpace("local"),e.enabled=false;let t=e.getHelper();this.scene.add(t),e.attach(this.meshGroup),this.gizmoControl=e,this.gizmoHelper=t;}this.gizmoHelper.visible=true;}async ensureMeshLoaded(e){var o,r;if(this.scene.getObjectByName(e._id))return;let t=(r=(o=e.mapMesh)==null?void 0:o.rawMesh)==null?void 0:r.meshLink;if(!t)return;let i=await this.client.downloadFile(t);i&&await new Promise((n,l)=>{this.gltfLoader.load(i,s=>{let d=new g.Box3().setFromObject(s.scene),u=d.getSize(new g.Vector3),p=d.getCenter(new g.Vector3);this.localCenter.copy(p),this.localRadius=Math.max(u.length()/2*1.1,.001);let m=$({color:"#7B2CBF",opacity:.58,gridColor:"#ffeb3b",gridScale:2,gridLineWidth:.02,showGrid:1,center:this.localCenter.clone(),radius:this.localRadius,progress:0,glowColor:"#FF3A00",glowWidth:.05,glowIntensity:2});this.meshMaterial=m,s.scene.traverse(c=>{let h=c;h.isMesh&&(h.material=m,h.frustumCulled=false);}),s.scene.name=e._id,this.meshGroup.add(s.scene),n();},void 0,s=>{l(s instanceof Error?s:new Error(String(s)));});});}applyMeshTransform(e){let t=new g.Vector3,i=new g.Quaternion,o=new g.Vector3;e.decompose(t,i,o),this.meshGroup.position.copy(t),this.meshGroup.quaternion.copy(i),this.meshGroup.scale.set(1,1,1),this.meshGroup.visible=true,this.meshGroup.updateMatrixWorld(true),this.meshMaterial&&(this.meshMaterial.uniforms.uProgress.value=0,this.meshMaterial.uniforms.uCenter.value.copy(this.localCenter).applyMatrix4(this.meshGroup.matrixWorld));}update(e,t){if(!this.meshMaterial)return;let i=this.meshMaterial.uniforms.uProgress;i&&(i.value>=1||(i.value=Math.min(i.value+e/this.radialDuration,1)));}hideUntilNextLocalization(){this.meshGroup.visible=false,this.gizmoHelper&&(this.gizmoHelper.visible=false),this.meshMaterial&&(this.meshMaterial.uniforms.uProgress.value=0);}dispose(){this.meshMaterial=null,this.meshGroup.traverse(e=>{let t=e;t.isMesh&&(t.geometry.dispose(),Array.isArray(t.material)?t.material.forEach(i=>i.dispose()):t.material.dispose());}),this.scene.remove(this.meshGroup),this.dracoLoader.dispose(),this.gizmoHelper&&this.scene.remove(this.gizmoHelper),this.gizmoControl&&this.gizmoControl.dispose();}};var W=class{constructor(e,t,i,o){this.meshVisualizer=new P(e,t,i,o);}async ensureGizmoLoaded(){await this.meshVisualizer.ensureGizmoLoaded();}async ensureMeshLoaded(e){await this.meshVisualizer.ensureMeshLoaded(e);}applyMeshTransform(e){this.meshVisualizer.applyMeshTransform(e);}update(e,t){this.meshVisualizer.update(e,t);}hideUntilNextLocalization(){this.meshVisualizer.hideUntilNextLocalization();}dispose(){this.meshVisualizer.dispose();}};var N=class{constructor(e){this.options=e;this.world=null;this.previewRAFHandle=null;this.resizeHandler=null;this.xrRenderTarget=null;let{session:t,renderer:i,camera:o}=e;i.xr.enabled=false,o.matrixAutoUpdate=false,t.setXRFrameHandler(r=>this.onXRFrame(r)),t.setAdapterResultHandler((r,n)=>{this.handleLocalizationResult(r,n);}),t.setAdapterSessionHandlers(()=>{o.matrixAutoUpdate=false;let r=t.getBaseLayer();r&&(this.xrRenderTarget=new g.WebGLRenderTarget(r.framebufferWidth,r.framebufferHeight)),this.stopPreviewLoop();},()=>{var r,n;i.setRenderTarget(null),this.xrRenderTarget&&(i.setRenderTargetFramebuffer(this.xrRenderTarget,null),this.xrRenderTarget.dispose(),this.xrRenderTarget=null),(r=this.world)==null||r.hideUntilNextLocalization(),o.matrixAutoUpdate=true,o.position.set(0,0,0),o.quaternion.identity(),(n=this.resizeHandler)==null||n.call(this),this.startPreviewLoop();}),(e.showMesh||e.showGizmo!==false)&&(this.world=new W(e.scene,t.getClient(),i,o));}static isSupported(){return G.isSupported()}initialize(e){var l,s;let{options:t}=this,{session:i}=t,o=null;if(t.useDefaultButton!==false){o=i.createButton();let d=e instanceof HTMLElement?e:t.buttonContainer instanceof HTMLElement?t.buttonContainer:(l=i.getOverlayRoot())!=null?l:document.body;d.contains(o)||d.appendChild(o),(s=t.onButtonCreated)==null||s.call(t,o);}let{renderer:r,camera:n}=t;return this.resizeHandler=()=>{n.aspect=window.innerWidth/window.innerHeight,n.updateProjectionMatrix(),r.setSize(window.innerWidth,window.innerHeight);},window.addEventListener("resize",this.resizeHandler),this.resizeHandler(),this.startPreviewLoop(),o}isActive(){return this.options.session.isActive()}get isLocalizing(){return this.options.session.isLocalizing}startSession(){return this.options.session.startSession()}stopSession(){this.options.session.stopSession();}async localizeFrame(){return this.options.session.localizeFrame()}onXRFrame(e){var r,n;let{renderer:t,scene:i,camera:o}=this.options;if(this.xrRenderTarget&&(t.setRenderTargetFramebuffer(this.xrRenderTarget,e.baseLayer.framebuffer),t.setRenderTarget(this.xrRenderTarget)),this.syncCameraMatrices(e.view),this.world&&e.deltaSeconds>0){let l=e.pose.transform.position;this.world.update(e.deltaSeconds,new g.Vector3(l.x,l.y,l.z));}(n=(r=this.options).onXRFrame)==null||n.call(r,e),t.render(i,o);}syncCameraMatrices(e){let{camera:t}=this.options;t.projectionMatrix.fromArray(e.projectionMatrix),t.projectionMatrixInverse.copy(t.projectionMatrix).invert(),t.matrixWorld.fromArray(e.transform.matrix),t.matrixWorldInverse.fromArray(e.transform.inverse.matrix),t.matrix.copy(t.matrixWorld);}startPreviewLoop(){let{renderer:e,scene:t,camera:i}=this.options,o=()=>{this.previewRAFHandle=window.requestAnimationFrame(o),e.render(t,i);};this.previewRAFHandle=window.requestAnimationFrame(o);}stopPreviewLoop(){this.previewRAFHandle!==null&&(cancelAnimationFrame(this.previewRAFHandle),this.previewRAFHandle=null);}async handleLocalizationResult(e,t){var m,c,h;let{position:i,rotation:o}=e.localizeData,r=new g.Matrix4().compose(new g.Vector3(i.x,i.y,i.z),new g.Quaternion(o.x,o.y,o.z,o.w),new g.Vector3(1,1,1)),n=new g.Matrix4().multiplyMatrices(new g.Matrix4().fromArray(t),r.invert());if((c=(m=this.options).onLocalizationSuccess)==null||c.call(m,e,n),!this.world)return;let{showMesh:l,showGizmo:s,session:d}=this.options,u=s!==false;if(l&&((h=e.localizeData.mapCodes)!=null&&h[0]))try{let f=await d.getClient().fetchMapDetails(e.localizeData.mapCodes[0]);f&&(e.mapDetails=f);}catch{}let p=l&&!!e.mapDetails;if(!(!p&&!u))try{if(u&&await this.world.ensureGizmoLoaded(),p&&await this.world.ensureMeshLoaded(e.mapDetails),!d.isActive())return;this.world.applyMeshTransform(n);}catch{}}dispose(){var e;this.resizeHandler&&(window.removeEventListener("resize",this.resizeHandler),this.resizeHandler=null),this.stopPreviewLoop(),(e=this.world)==null||e.dispose(),this.world=null,this.options.session.dispose();}};export{k as DEFAULT_ENDPOINTS,X as MultisetClient,N as ThreeAdapter,G as XRSessionManager};
93
+ `;function ie(l={}){var y,b,f,E,w,R,L,T,C,I,S,F,G,k;let e=(y=l.color)!=null?y:"#7B2CBF",t=(b=l.opacity)!=null?b:150/255,i=(f=l.gridColor)!=null?f:"#ffeb3b",r=(E=l.gridScale)!=null?E:2,o=(w=l.gridLineWidth)!=null?w:.02,n=(R=l.showGrid)!=null?R:1,a=(L=l.center)!=null?L:new g.Vector3(0,0,0),s=(T=l.radius)!=null?T:1,c=(C=l.progress)!=null?C:1,d=(I=l.glowColor)!=null?I:"#FF3A00",p=(S=l.glowWidth)!=null?S:.05,u=(F=l.glowIntensity)!=null?F:2,m=(G=l.lightColor)!=null?G:"#ffffff",v=(k=l.lightDirection)!=null?k:new g.Vector3(.3,1,.3).normalize();return new g.ShaderMaterial({vertexShader:he,fragmentShader:me,transparent:true,side:g.DoubleSide,uniforms:{uColor:{value:typeof e=="string"?new g.Color(e):e},uOpacity:{value:t},uGridColor:{value:typeof i=="string"?new g.Color(i):i},uGridSize:{value:r},uGridLineWidth:{value:o},uShowGrid:{value:n},uCenter:{value:a.clone()},uRadius:{value:s},uProgress:{value:c},uGlowColor:{value:typeof d=="string"?new g.Color(d):d},uGlowWidth:{value:p},uGlowIntensity:{value:u},uLightColor:{value:typeof m=="string"?new g.Color(m):m},uLightDirection:{value:v.clone()}}})}var K=.18,re=.01,ve=.025,oe=.06,Ee=.018,Re=[["#ff2060",new g.Vector3(1,0,0)],["#20df80",new g.Vector3(0,1,0)],["#2080ff",new g.Vector3(0,0,1)]],B=new g.Vector3(0,1,0);function ye(){let l=new g.Group,e=new g.Mesh(new g.SphereGeometry(Ee,16,16),new g.MeshBasicMaterial({color:16777215,depthTest:false,transparent:true}));e.renderOrder=999,l.add(e);for(let[t,i]of Re){let r=new g.MeshBasicMaterial({color:t,depthTest:false,transparent:true}),o=new g.Mesh(new g.CylinderGeometry(re,re,K,12),r);o.position.copy(i.clone().multiplyScalar(K/2)),i.equals(B)||o.quaternion.setFromUnitVectors(B,i),o.renderOrder=998,l.add(o);let n=new g.Mesh(new g.ConeGeometry(ve,oe,16,1),r);n.position.copy(i.clone().multiplyScalar(K+oe/2)),i.equals(B)||n.quaternion.setFromUnitVectors(B,i),n.renderOrder=999,l.add(n);}return l}var W=class{constructor(e,t,i,r){this.scene=e;this.client=t;this.renderer=i;this.camera=r;this.gizmoHelper=null;this.meshMaterial=null;this.localCenter=new g.Vector3;this.localRadius=0;this.radialDuration=4;this.meshGroup=new g.Group,this.meshGroup.visible=false,this.scene.add(this.meshGroup),this.dracoLoader=new DRACOLoader,this.dracoLoader.setDecoderPath("/draco/"),this.gltfLoader=new GLTFLoader,this.gltfLoader.setDRACOLoader(this.dracoLoader);}getMeshGroup(){return this.meshGroup}ensureGizmoLoaded(){if(!this.gizmoHelper){let e=ye();this.meshGroup.add(e),this.gizmoHelper=e;}}async ensureMeshLoaded(e){var r,o;if(this.scene.getObjectByName(e._id))return;let t=(o=(r=e.mapMesh)==null?void 0:r.rawMesh)==null?void 0:o.meshLink;if(!t)return;let i=await this.client.downloadFile(t);i&&await new Promise((n,a)=>{this.gltfLoader.load(i,s=>{let c=new g.Box3().setFromObject(s.scene),d=c.getSize(new g.Vector3),p=c.getCenter(new g.Vector3);this.localCenter.copy(p),this.localRadius=Math.max(d.length()/2*1.1,.001);let u=ie({color:"#7B2CBF",opacity:.58,gridColor:"#ffeb3b",gridScale:.25,gridLineWidth:.015,showGrid:1,center:this.localCenter.clone(),radius:this.localRadius,progress:0,glowColor:"#FF3A00",glowWidth:.05,glowIntensity:2});this.meshMaterial=u,s.scene.traverse(m=>{let v=m;v.isMesh&&(v.material=u,v.frustumCulled=false);}),s.scene.name=e._id,this.meshGroup.add(s.scene),n();},void 0,s=>{a(s instanceof Error?s:new Error(String(s)));});});}applyMeshTransform(e){let t=new g.Vector3,i=new g.Quaternion,r=new g.Vector3;e.decompose(t,i,r),this.meshGroup.position.copy(t),this.meshGroup.quaternion.copy(i),this.meshGroup.scale.set(1,1,1),this.meshGroup.visible=true,this.meshGroup.updateMatrixWorld(true),this.meshMaterial&&(this.meshMaterial.uniforms.uProgress.value=0,this.meshMaterial.uniforms.uCenter.value.copy(this.localCenter).applyMatrix4(this.meshGroup.matrixWorld));}update(e,t){if(!this.meshMaterial)return;let i=this.meshMaterial.uniforms.uProgress;i&&(i.value>=1||(i.value=Math.min(i.value+e/this.radialDuration,1)));}hideUntilNextLocalization(){this.meshGroup.visible=false,this.meshMaterial&&(this.meshMaterial.uniforms.uProgress.value=0);}dispose(){var e;(e=this.meshMaterial)==null||e.dispose(),this.meshMaterial=null,this.meshGroup.traverse(t=>{let i=t;i.isMesh&&(i.geometry.dispose(),Array.isArray(i.material)?i.material.forEach(r=>r.dispose()):i.material.dispose());}),this.scene.remove(this.meshGroup),this.dracoLoader.dispose(),this.gizmoHelper=null;}};var Te=`
94
+ attribute vec3 aStart;
95
+ attribute vec3 aEnd;
96
+ attribute float aSide;
97
+ attribute float aAlong;
98
+
99
+ uniform float uTime;
100
+ uniform float uLineWidth;
101
+ uniform vec2 uResolution;
102
+
103
+ varying float vIntensity;
104
+
105
+ void main() {
106
+ vec4 clipA = projectionMatrix * modelViewMatrix * vec4(aStart, 1.0);
107
+ vec4 clipB = projectionMatrix * modelViewMatrix * vec4(aEnd, 1.0);
108
+
109
+ vec2 dirScreen = (clipB.xy / clipB.w - clipA.xy / clipA.w) * uResolution;
110
+ vec2 tangent = normalize(dirScreen + vec2(0.0001, 0.0));
111
+ vec2 normal = vec2(-tangent.y, tangent.x);
112
+
113
+ vec4 clipPos = mix(clipA, clipB, aAlong);
114
+
115
+ vec2 offset = normal * aSide * (uLineWidth * 0.5);
116
+ clipPos.xy += (offset / uResolution) * clipPos.w;
117
+
118
+ gl_Position = clipPos;
119
+
120
+ vec3 midpoint = mix(aStart, aEnd, 0.5);
121
+ float phase = dot(midpoint, vec3(3.1415, 2.7183, 4.3301));
122
+ vIntensity = 0.6 + 0.4 * sin(phase + uTime * 2.5);
123
+ }
124
+ `,be=`
125
+ uniform vec3 uColor;
126
+ uniform float uOpacity;
127
+ uniform float uGlowIntensity;
128
+
129
+ varying float vIntensity;
130
+
131
+ void main() {
132
+ gl_FragColor = vec4(uColor * uGlowIntensity * vIntensity, uOpacity * vIntensity);
133
+ }
134
+ `;function ne(l){let e=l.attributes.position.array,t=e.length/6,i=new Float32Array(t*4*3),r=new Float32Array(t*4*3),o=new Float32Array(t*4),n=new Float32Array(t*4),a=new Uint32Array(t*6);for(let c=0;c<t;c++){let d=c*6,p=e[d],u=e[d+1],m=e[d+2],v=e[d+3],y=e[d+4],b=e[d+5],f=c*4;for(let w=0;w<4;w++){let R=(f+w)*3;i[R]=p,i[R+1]=u,i[R+2]=m,r[R]=v,r[R+1]=y,r[R+2]=b;}o[f]=-1,o[f+1]=1,o[f+2]=-1,o[f+3]=1,n[f]=0,n[f+1]=0,n[f+2]=1,n[f+3]=1;let E=c*6;a[E]=f,a[E+1]=f+2,a[E+2]=f+1,a[E+3]=f+1,a[E+4]=f+2,a[E+5]=f+3;}let s=new g.BufferGeometry;return s.setAttribute("aStart",new g.BufferAttribute(i,3)),s.setAttribute("aEnd",new g.BufferAttribute(r,3)),s.setAttribute("aSide",new g.BufferAttribute(o,1)),s.setAttribute("aAlong",new g.BufferAttribute(n,1)),s.setIndex(new g.BufferAttribute(a,1)),s}function ae(l={}){var o,n,a,s;let e=(o=l.color)!=null?o:"#9055FF",t=(n=l.lineWidth)!=null?n:3,i=(a=l.opacity)!=null?a:.9,r=(s=l.glowIntensity)!=null?s:2;return new g.ShaderMaterial({vertexShader:Te,fragmentShader:be,transparent:true,blending:g.AdditiveBlending,depthWrite:false,depthTest:true,side:g.DoubleSide,uniforms:{uColor:{value:typeof e=="string"?new g.Color(e):e.clone()},uLineWidth:{value:t},uResolution:{value:new g.Vector2(1080,1920)},uOpacity:{value:i},uGlowIntensity:{value:r},uTime:{value:0}}})}async function Me(l,e){let t=await e.loadAsync(l),i=new g.Group,r=[],o=t.scene.clone(true);o.traverse(a=>{a instanceof g.Mesh&&(a.material=new g.MeshBasicMaterial({colorWrite:false,side:g.FrontSide}),a.renderOrder=0);}),i.add(o),t.scene.updateWorldMatrix(true,true);let n=new g.Group;return t.scene.traverse(a=>{if(!(a instanceof g.Mesh))return;let s=new g.EdgesGeometry(a.geometry,15),c=ne(s);s.dispose();let d=ae();r.push(d);let p=new g.Mesh(c,d);p.applyMatrix4(a.matrixWorld),p.renderOrder=1,p.frustumCulled=false,n.add(p);}),i.add(n),{group:i,materials:r}}var _=class{constructor(e,t,i,r){this.scene=e;this.client=t;this.renderer=i;this.onObjectMeshLoaded=r;this.tracked=new Map;this.meshCache=new Map;this.activeMaterials=new Set;this.loader=new GLTFLoader;this.elapsed=0;this._renderSize=new g.Vector2;}async placeOrUpdateAnchor(e,t,i){let r=new g.Matrix4().compose(new g.Vector3(i.position.x,i.position.y,i.position.z),new g.Quaternion(i.rotation.x,i.rotation.y,i.rotation.z,i.rotation.w),new g.Vector3(1,1,1)),o=new g.Matrix4().multiplyMatrices(t,r.clone().invert()),n=new g.Vector3,a=new g.Quaternion,s=new g.Vector3;if(o.decompose(n,a,s),this.tracked.has(e)){let d=this.tracked.get(e);d.anchor.position.copy(n),d.anchor.quaternion.copy(a),d.anchor.updateMatrixWorld(true);return}let c=new g.Object3D;c.position.copy(n),c.quaternion.copy(a),this.scene.add(c),this.tracked.set(e,{anchor:c,mesh:null}),this.loadAndAttachMesh(e,c);}update(e){this.elapsed+=e,this.renderer.getSize(this._renderSize);for(let t of this.activeMaterials)t.uniforms.uTime.value=this.elapsed,t.uniforms.uResolution.value.copy(this._renderSize);}async loadAndAttachMesh(e,t){var n;let i=await this.getOrLoadMesh(e),r=this.tracked.has(e),o=t.parent!==null;if(i&&r&&o){let a=i.clone();t.add(a),this.tracked.get(e).mesh=a,a.traverse(s=>{s instanceof g.Mesh&&s.material instanceof g.ShaderMaterial&&this.activeMaterials.add(s.material);}),(n=this.onObjectMeshLoaded)==null||n.call(this,e);}}async getOrLoadMesh(e){if(this.meshCache.has(e))return this.meshCache.get(e);let t=await this.client.downloadObjectMesh(e);if(!t)return null;try{let{group:i,materials:r}=await Me(t,this.loader);this.meshCache.set(e,i);for(let o of r)this.activeMaterials.add(o);return i}catch{return null}}clearAll(){this.tracked.forEach(({anchor:e})=>this.scene.remove(e)),this.tracked.clear(),this.activeMaterials.clear();}dispose(){this.clearAll(),this.meshCache.forEach(e=>{e.traverse(t=>{t instanceof g.Mesh&&(t.geometry.dispose(),Array.isArray(t.material)?t.material.forEach(i=>i.dispose()):t.material.dispose());});}),this.meshCache.clear(),this.activeMaterials.clear();}get trackedCodes(){return Array.from(this.tracked.keys())}};var N=class{constructor(e,t,i,r,o){this.scene=e;this.client=t;this.renderer=i;this.onObjectMeshLoaded=o;this.objectVisualizer=null;this.meshVisualizer=new W(e,t,i,r);}ensureGizmoLoaded(){this.meshVisualizer.ensureGizmoLoaded();}async ensureMeshLoaded(e){await this.meshVisualizer.ensureMeshLoaded(e);}applyMeshTransform(e){this.meshVisualizer.applyMeshTransform(e);}async placeOrUpdateObjectAnchor(e,t,i){return this.objectVisualizer||(this.objectVisualizer=new _(this.scene,this.client,this.renderer,this.onObjectMeshLoaded)),this.objectVisualizer.placeOrUpdateAnchor(e,t,i)}clearObjectMeshes(){var e;(e=this.objectVisualizer)==null||e.clearAll();}update(e,t){var i;this.meshVisualizer.update(e,t),(i=this.objectVisualizer)==null||i.update(e);}hideUntilNextLocalization(){this.meshVisualizer.hideUntilNextLocalization();}dispose(){var e;this.meshVisualizer.dispose(),(e=this.objectVisualizer)==null||e.dispose(),this.objectVisualizer=null;}};var Q=class{constructor(e){this.options=e;this.world=null;this.previewRAFHandle=null;this.resizeHandler=null;this.xrRenderTarget=null;let{session:t,renderer:i,camera:r}=e;i.xr.enabled=false,r.matrixAutoUpdate=false,t.setXRFrameHandler(o=>this.onXRFrame(o)),t.setAdapterResultHandler((o,n)=>{this.handleLocalizationResult(o,n);}),t.setAdapterSessionHandlers(()=>{r.matrixAutoUpdate=false;let o=t.getBaseLayer();o&&(this.xrRenderTarget=new g.WebGLRenderTarget(o.framebufferWidth,o.framebufferHeight)),this.stopPreviewLoop();},()=>{var o,n;i.setRenderTarget(null),this.xrRenderTarget&&(i.setRenderTargetFramebuffer(this.xrRenderTarget,null),this.xrRenderTarget.dispose(),this.xrRenderTarget=null),(o=this.world)==null||o.hideUntilNextLocalization(),r.matrixAutoUpdate=true,r.position.set(0,0,0),r.quaternion.identity(),(n=this.resizeHandler)==null||n.call(this),this.startPreviewLoop();}),(e.showMesh||e.showGizmo!==false||e.showObjectMeshes)&&(this.world=new N(e.scene,t.getClient(),i,r,e.onObjectMeshLoaded)),t.setAdapterObjectTrackingHandler((o,n)=>{e.showObjectMeshes&&this.world&&o.objectCodes.length>0&&this.world.placeOrUpdateObjectAnchor(o.objectCodes[0],new g.Matrix4().fromArray(n),o);});}static isSupported(){return P.isSupported()}initialize(e){var a,s;let{options:t}=this,{session:i}=t,r=null;if(t.useDefaultButton!==false){r=i.createButton();let c=e instanceof HTMLElement?e:t.buttonContainer instanceof HTMLElement?t.buttonContainer:(a=i.getOverlayRoot())!=null?a:document.body;c.contains(r)||c.appendChild(r),(s=t.onButtonCreated)==null||s.call(t,r);}let{renderer:o,camera:n}=t;return this.resizeHandler=()=>{n.aspect=window.innerWidth/window.innerHeight,n.updateProjectionMatrix(),o.setSize(window.innerWidth,window.innerHeight);},window.addEventListener("resize",this.resizeHandler),this.resizeHandler(),this.startPreviewLoop(),r}isActive(){return this.options.session.isActive()}get isLocalizing(){return this.options.session.isLocalizing}startSession(){return this.options.session.startSession()}stopSession(){this.options.session.stopSession();}async localizeFrame(){return this.options.session.localizeFrame()}async trackObjects(){return this.options.session.trackObjects()}clearObjectMeshes(){var e;(e=this.world)==null||e.clearObjectMeshes();}onXRFrame(e){var o,n;let{renderer:t,scene:i,camera:r}=this.options;if(this.xrRenderTarget&&(t.setRenderTargetFramebuffer(this.xrRenderTarget,e.baseLayer.framebuffer),t.setRenderTarget(this.xrRenderTarget)),this.syncCameraMatrices(e.view),this.world&&e.deltaSeconds>0){let a=e.pose.transform.position;this.world.update(e.deltaSeconds,new g.Vector3(a.x,a.y,a.z));}(n=(o=this.options).onXRFrame)==null||n.call(o,e),t.render(i,r);}syncCameraMatrices(e){let{camera:t}=this.options;t.projectionMatrix.fromArray(e.projectionMatrix),t.projectionMatrixInverse.copy(t.projectionMatrix).invert(),t.matrixWorld.fromArray(e.transform.matrix),t.matrixWorldInverse.fromArray(e.transform.inverse.matrix),t.matrix.copy(t.matrixWorld);}startPreviewLoop(){let{renderer:e,scene:t,camera:i}=this.options,r=()=>{this.previewRAFHandle=window.requestAnimationFrame(r),e.render(t,i);};this.previewRAFHandle=window.requestAnimationFrame(r);}stopPreviewLoop(){this.previewRAFHandle!==null&&(cancelAnimationFrame(this.previewRAFHandle),this.previewRAFHandle=null);}async handleLocalizationResult(e,t){var u,m,v;let{position:i,rotation:r}=e.localizeData,o=new g.Matrix4().compose(new g.Vector3(i.x,i.y,i.z),new g.Quaternion(r.x,r.y,r.z,r.w),new g.Vector3(1,1,1)),n=new g.Matrix4().multiplyMatrices(new g.Matrix4().fromArray(t),o.invert());if((m=(u=this.options).onLocalizationSuccess)==null||m.call(u,e,n),!this.world)return;let{showMesh:a,showGizmo:s,session:c}=this.options,d=s!==false;if(a&&((v=e.localizeData.mapCodes)!=null&&v[0]))try{let y=await c.getClient().fetchMapDetails(e.localizeData.mapCodes[0]);y&&(e.mapDetails=y);}catch{}let p=a&&!!e.mapDetails;if(!(!p&&!d))try{if(d&&this.world.ensureGizmoLoaded(),p&&await this.world.ensureMeshLoaded(e.mapDetails),!c.isActive())return;this.world.applyMeshTransform(n);}catch{}}dispose(){var e;this.resizeHandler&&(window.removeEventListener("resize",this.resizeHandler),this.resizeHandler=null),this.stopPreviewLoop(),(e=this.world)==null||e.dispose(),this.world=null,this.options.session.dispose();}};export{V as DEFAULT_ENDPOINTS,X as MultisetClient,Q as ThreeAdapter,P as XRSessionManager};
@@ -1,4 +1,4 @@
1
- import { XRSessionManager, IXRFrameEvent, ILocalizeAndMapDetails } from '../core/index.js';
1
+ import { XRSessionManager, IXRFrameEvent, ILocalizeAndMapDetails, IObjectTrackingResponse } from '../core/index.js';
2
2
  import * as THREE from 'three';
3
3
 
4
4
  interface IThreeAdapterOptions {
@@ -43,6 +43,10 @@ interface IThreeAdapterOptions {
43
43
  * ```
44
44
  */
45
45
  onLocalizationSuccess?: (result: ILocalizeAndMapDetails, worldFromMap: THREE.Matrix4) => void;
46
+ /** Load and display a 3D outline mesh for each detected object. Default false. */
47
+ showObjectMeshes?: boolean;
48
+ /** Called when a detected object's 3D mesh has been loaded and placed in the scene. */
49
+ onObjectMeshLoaded?: (objectCode: string) => void;
46
50
  }
47
51
  declare class ThreeAdapter {
48
52
  private readonly options;
@@ -59,6 +63,8 @@ declare class ThreeAdapter {
59
63
  startSession(): Promise<void>;
60
64
  stopSession(): void;
61
65
  localizeFrame(): Promise<ILocalizeAndMapDetails | null>;
66
+ trackObjects(): Promise<IObjectTrackingResponse | null>;
67
+ clearObjectMeshes(): void;
62
68
  private onXRFrame;
63
69
  private syncCameraMatrices;
64
70
  private startPreviewLoop;
@@ -1,4 +1,4 @@
1
- import*as f from'three';import {DRACOLoader}from'three/examples/jsm/loaders/DRACOLoader.js';import {GLTFLoader}from'three/examples/jsm/loaders/GLTFLoader.js';import {TransformControls}from'three/examples/jsm/controls/TransformControls.js';async function O(o,e,t,i=.8,r=e,a=t){let n=document.createElement("canvas");n.width=e,n.height=t;let l=n.getContext("2d");if(!l)return new Blob;l.putImageData(new ImageData(new Uint8ClampedArray(o),e,t),0,0);let s=document.createElement("canvas");s.width=r,s.height=a;let c=s.getContext("2d");return c?(c.drawImage(n,0,0,r,a),new Promise(d=>{s.toBlob(u=>d(u!=null?u:new Blob),"image/jpeg",i);})):new Blob}async function N(o,e,t,i){let r=o.createFramebuffer();if(!r)return null;let a=o.getParameter(o.FRAMEBUFFER_BINDING),n;try{o.bindFramebuffer(o.FRAMEBUFFER,r),o.framebufferTexture2D(o.FRAMEBUFFER,o.COLOR_ATTACHMENT0,o.TEXTURE_2D,e,0),n=new Uint8Array(t*i*4),o.readPixels(0,0,t,i,o.RGBA,o.UNSIGNED_BYTE,n);}finally{o.bindFramebuffer(o.FRAMEBUFFER,a),o.deleteFramebuffer(r);}let l=new Uint8ClampedArray(n.length);for(let h=0;h<i;h+=1){let m=h*t*4,p=(i-h-1)*t*4;l.set(n.subarray(m,m+t*4),p);}let s=Math.min(1,1280/Math.max(t,i)),c=Math.round(t*s),d=Math.round(i*s),u=await O(l.buffer,t,i,.7,c,d);return u.size?{blob:u,width:c,height:d}:null}function V(o,e){let t=o,i=(1-t[8])*e.width/2+e.x,r=(1-t[9])*e.height/2+e.y,a=e.width/2*t[0],n=e.height/2*t[5];return {fx:a,fy:n,px:i,py:r,width:e.width,height:e.height}}function k(o){return new Float32Array(o)}var U=.2,j=.8,q=60,K=1e4,$=300,Q=10,I=class{constructor(e,t){this.gl=e;this.options=t;this.session=null;this.referenceSpace=null;this.baseLayer=null;this.xrBinding=null;this.lastFrameTime=0;this.trackingLossFrames=0;this.hadTrackingLoss=false;this._isLocalizing=false;this.trackerSpace=null;this.arButton=null;this.xrFrameHandler=null;this.adapterResultHandler=null;this.adapterSessionStartHandler=null;this.adapterSessionEndHandler=null;this.handleContextLost=e=>{var t,i;if(e.preventDefault(),this.session)try{this.session.end();}catch{}(i=(t=this.options).onContextLost)==null||i.call(t);};this.handleContextRestored=()=>{var e,t;(t=(e=this.options).onContextRestored)==null||t.call(e);};let i=e.canvas;i instanceof HTMLCanvasElement&&(i.addEventListener("webglcontextlost",this.handleContextLost),i.addEventListener("webglcontextrestored",this.handleContextRestored));}static async isSupported(){if(!navigator.xr)return false;try{return await navigator.xr.isSessionSupported("immersive-ar")}catch{return false}}setXRFrameHandler(e){this.xrFrameHandler=e;}setAdapterResultHandler(e){this.adapterResultHandler=e;}setAdapterSessionHandlers(e,t){this.adapterSessionStartHandler=e,this.adapterSessionEndHandler=t;}getClient(){return this.options.client}getOverlayRoot(){return this.options.overlayRoot}isActive(){return this.session!==null}get isLocalizing(){return this._isLocalizing}stopSession(){this.session&&this.session.end();}getBaseLayer(){return this.baseLayer}createButton(){let e=document.createElement("button");return e.textContent="START AR",e.className="multiset-ar-button multiset-ar-inactive",e.style.cssText=["position:fixed","bottom:20px","left:50%","transform:translateX(-50%)","padding:12px 24px","border:1px solid #fff","border-radius:4px","background:rgba(0,0,0,0.1)","color:#fff","font-size:13px","cursor:pointer","z-index:999"].join(";"),e.addEventListener("click",()=>{this.session?this.session.end():this.startSession();}),this.arButton=e,e}async startSession(){var e,t,i;try{if(!navigator.xr)throw new Error("WebXR not supported on this device.");let r=await navigator.xr.requestSession("immersive-ar",{requiredFeatures:["local"],optionalFeatures:["camera-access","dom-overlay"],domOverlay:{root:(e=this.options.overlayRoot)!=null?e:document.body}});await this.initSession(r);}catch(r){(i=(t=this.options).onError)==null||i.call(t,r);}}async initSession(e){var n,l,s,c;await this.gl.makeXRCompatible();let t={};this.options.framebufferScaleFactor!==void 0&&(t.framebufferScaleFactor=this.options.framebufferScaleFactor);let i=new XRWebGLLayer(e,this.gl,t);await e.updateRenderState({baseLayer:i});let r=await e.requestReferenceSpace((n=this.options.referenceSpaceType)!=null?n:"local");this.session=e,this.baseLayer=i,this.referenceSpace=r;let a=XRWebGLBinding;this.xrBinding=new a(e,this.gl),e.addEventListener("end",()=>this.handleSessionEnd()),e.requestAnimationFrame((d,u)=>this.xrLoop(d,u)),this.arButton&&(this.arButton.textContent="STOP AR",this.arButton.classList.replace("multiset-ar-inactive","multiset-ar-active")),(l=this.adapterSessionStartHandler)==null||l.call(this),(c=(s=this.options).onSessionStart)==null||c.call(s),this.options.autoLocalize&&e.requestAnimationFrame(()=>{this.localizeFrame();});}handleSessionEnd(){var e,t,i;this.arButton&&(this.arButton.textContent="START AR",this.arButton.classList.replace("multiset-ar-active","multiset-ar-inactive")),this.session=null,this.baseLayer=null,this.referenceSpace=null,this.xrBinding=null,this.lastFrameTime=0,this.trackingLossFrames=0,this.hadTrackingLoss=false,this._isLocalizing=false,this.trackerSpace=null,(e=this.adapterSessionEndHandler)==null||e.call(this),(i=(t=this.options).onSessionEnd)==null||i.call(t);}xrLoop(e,t){var l;this.session.requestAnimationFrame((s,c)=>this.xrLoop(s,c));let i=this.lastFrameTime===0?0:(e-this.lastFrameTime)/1e3;this.lastFrameTime=e;let r=t.getViewerPose(this.referenceSpace);if(!r){this.options.relocalization&&(this.trackingLossFrames+=1,this.trackingLossFrames>=q&&(this.hadTrackingLoss=true));return}this.hadTrackingLoss&&!this._isLocalizing&&this.options.relocalization?(this.hadTrackingLoss=false,this.trackingLossFrames=0,this.localizeFrame()):this.trackingLossFrames=0;let a=r.views[0],n=this.baseLayer.getViewport(a);(l=this.xrFrameHandler)==null||l.call(this,{time:e,frame:t,pose:r,view:a,viewport:n,baseLayer:this.baseLayer,referenceSpace:this.referenceSpace,deltaSeconds:i});}async localizeFrame(){var l,s,c,d,u,h,m,p,E,L,y,T,C;if(!this.session)throw new Error("No active XR session. Start AR before calling localizeFrame().");let e=(l=this.options.confidenceCheck)!=null?l:false,t=Math.max(U,Math.min((s=this.options.confidenceThreshold)!=null?s:.5,j));(d=(c=this.options).onLocalizationInit)==null||d.call(c),this._isLocalizing=true;let i=null,r;try{let R=await this.captureFrame();i=R.result,r=R.failureReason;}catch(R){return (h=(u=this.options).onError)==null||h.call(u,R),null}finally{this._isLocalizing=false;}if(i&&(!e||((m=i.localizeData.confidence)!=null?m:0)>=t)&&i)return (E=(p=this.options).onLocalizationSuccess)==null||E.call(p,i),this.trackerSpace&&((L=this.adapterResultHandler)==null||L.call(this,i,this.trackerSpace)),i;let n=r!=null?r:i?e?`Best confidence ${(y=i.localizeData.confidence)!=null?y:0} below threshold ${t}.`:void 0:"All attempts failed to produce a pose.";return (C=(T=this.options).onLocalizationFailure)==null||C.call(T,n),null}async captureFrame(){var r;let e=this.session,t=this.referenceSpace,i=(r=this.options.localizationTrackingTimeoutMs)!=null?r:K;return new Promise((a,n)=>{let l=Date.now(),s=(c,d)=>{e.requestAnimationFrame(async(u,h)=>{var m,p,E,L,y,T,C,R,z;try{let x=Date.now()-l,F=h.getViewerPose(t);if(!F){if(x>=i||c+1>=$){let b=(i/1e3).toFixed(1);a({result:null,failureReason:`Tracking unavailable: no viewer pose within ${b}s. Try moving the device or ensuring good lighting.`});}else s(c+1,d);return}let A=F.views;for(let b of A){let M=b.camera;if(!M)continue;let S=(E=(p=(m=this.xrBinding)==null?void 0:m.getCameraImage)==null?void 0:p.call(m,M))!=null?E:null;if(!S)continue;let{width:H,height:X}=M;if(!H||!X)continue;let G=await N(this.gl,S,H,X);if(!G)continue;let P=V(b.projectionMatrix,{width:G.width,height:G.height,x:0,y:0});if(P){(y=(L=this.options).onFrameCaptured)==null||y.call(L,G),(C=(T=this.options).onCameraIntrinsics)==null||C.call(T,P),this.trackerSpace=k(b.transform.matrix);let w=await this.options.client.localizeWithFrame(G,P);w!=null&&w.localizeData&&((z=(R=this.options).onPoseResult)==null||z.call(R,{poseFound:w.localizeData.poseFound,position:w.localizeData.position,rotation:w.localizeData.rotation,mapIds:w.localizeData.mapIds,confidence:w.localizeData.confidence})),a({result:w});return}}d+1>=Q?a({result:null,failureReason:"Camera image not available. The device or browser may not support camera access in AR."}):s(c+1,d+1);}catch(x){n(x);}});};s(0,0);})}dispose(){if(this.session)try{this.session.end();}catch{}let e=this.gl.canvas;e instanceof HTMLCanvasElement&&(e.removeEventListener("webglcontextlost",this.handleContextLost),e.removeEventListener("webglcontextrestored",this.handleContextRestored)),this.arButton=null;}};var J=`
1
+ import*as p from'three';import {DRACOLoader}from'three/examples/jsm/loaders/DRACOLoader.js';import {GLTFLoader}from'three/examples/jsm/loaders/GLTFLoader.js';async function ae(l,e,t,r=.8,i=e,o=t){let a=document.createElement("canvas");a.width=e,a.height=t;let s=a.getContext("2d");if(!s)return new Blob;s.putImageData(new ImageData(new Uint8ClampedArray(l),e,t),0,0);let n=document.createElement("canvas");n.width=i,n.height=o;let c=n.getContext("2d");return c?(c.drawImage(a,0,0,i,o),new Promise(d=>{n.toBlob(u=>d(u!=null?u:new Blob),"image/jpeg",r);})):new Blob}async function X(l,e,t,r){let i=l.createFramebuffer();if(!i)return null;let o=l.getParameter(l.FRAMEBUFFER_BINDING),a;try{l.bindFramebuffer(l.FRAMEBUFFER,i),l.framebufferTexture2D(l.FRAMEBUFFER,l.COLOR_ATTACHMENT0,l.TEXTURE_2D,e,0),a=new Uint8Array(t*r*4),l.readPixels(0,0,t,r,l.RGBA,l.UNSIGNED_BYTE,a);}finally{l.bindFramebuffer(l.FRAMEBUFFER,o),l.deleteFramebuffer(i);}let s=new Uint8ClampedArray(a.length);for(let v=0;v<r;v+=1){let f=v*t*4,E=(r-v-1)*t*4;s.set(a.subarray(f,f+t*4),E);}let n=Math.min(1,1280/Math.max(t,r)),c=Math.round(t*n),d=Math.round(r*n),u=await ae(s.buffer,t,r,.7,c,d);return u.size?{blob:u,width:c,height:d}:null}function N(l,e){let t=l,r=(1-t[8])*e.width/2+e.x,i=(1-t[9])*e.height/2+e.y,o=e.width/2*t[0],a=e.height/2*t[5];return {fx:o,fy:a,px:r,py:i,width:e.width,height:e.height}}function U(l){return new Float32Array(l)}var K=.2,Q=.8,ne=60,Z=1e4,Y=300,J=10,se=10,le=180,ce=30,de=10,B=class{constructor(e,t){this.gl=e;this.options=t;this.session=null;this.referenceSpace=null;this.baseLayer=null;this.xrBinding=null;this.lastFrameTime=0;this.trackingLossFrames=0;this.hadTrackingLoss=false;this._isLocalizing=false;this.trackerSpace=null;this.bgLocalizationTimer=null;this.arButton=null;this.xrFrameHandler=null;this.adapterResultHandler=null;this.adapterObjectTrackingHandler=null;this.adapterSessionStartHandler=null;this.adapterSessionEndHandler=null;this.handleContextLost=e=>{var t,r;if(e.preventDefault(),this.session)try{this.session.end();}catch{}(r=(t=this.options).onContextLost)==null||r.call(t);};this.handleContextRestored=()=>{var e,t;(t=(e=this.options).onContextRestored)==null||t.call(e);};let r=e.canvas;r instanceof HTMLCanvasElement&&(r.addEventListener("webglcontextlost",this.handleContextLost),r.addEventListener("webglcontextrestored",this.handleContextRestored));}static async isSupported(){if(!navigator.xr)return false;try{return await navigator.xr.isSessionSupported("immersive-ar")}catch{return false}}setXRFrameHandler(e){this.xrFrameHandler=e;}setAdapterResultHandler(e){this.adapterResultHandler=e;}setAdapterObjectTrackingHandler(e){this.adapterObjectTrackingHandler=e;}setAdapterSessionHandlers(e,t){this.adapterSessionStartHandler=e,this.adapterSessionEndHandler=t;}getClient(){return this.options.client}getOverlayRoot(){return this.options.overlayRoot}isActive(){return this.session!==null}get isLocalizing(){return this._isLocalizing}stopSession(){this.session&&this.session.end();}getBaseLayer(){return this.baseLayer}createButton(){let e=document.createElement("button");return e.textContent="START AR",e.className="multiset-ar-button multiset-ar-inactive",e.style.cssText=["position:fixed","bottom:20px","left:50%","transform:translateX(-50%)","padding:12px 24px","border:1px solid #fff","border-radius:4px","background:rgba(0,0,0,0.1)","color:#fff","font-size:13px","cursor:pointer","z-index:999"].join(";"),e.addEventListener("click",()=>{this.session?this.session.end():this.startSession();}),this.arButton=e,e}async startSession(){var e,t,r;try{if(!navigator.xr)throw new Error("WebXR not supported on this device.");let i=await navigator.xr.requestSession("immersive-ar",{requiredFeatures:["local"],optionalFeatures:["camera-access","dom-overlay"],domOverlay:{root:(e=this.options.overlayRoot)!=null?e:document.body}});await this.initSession(i);}catch(i){(r=(t=this.options).onError)==null||r.call(t,i);}}async initSession(e){var a,s,n,c;await this.gl.makeXRCompatible();let t={};this.options.framebufferScaleFactor!==void 0&&(t.framebufferScaleFactor=this.options.framebufferScaleFactor);let r=new XRWebGLLayer(e,this.gl,t);await e.updateRenderState({baseLayer:r});let i=await e.requestReferenceSpace((a=this.options.referenceSpaceType)!=null?a:"local");this.session=e,this.baseLayer=r,this.referenceSpace=i;let o=XRWebGLBinding;this.xrBinding=new o(e,this.gl),e.addEventListener("end",()=>this.handleSessionEnd()),e.requestAnimationFrame((d,u)=>this.xrLoop(d,u)),this.arButton&&(this.arButton.textContent="STOP AR",this.arButton.classList.replace("multiset-ar-inactive","multiset-ar-active")),(s=this.adapterSessionStartHandler)==null||s.call(this),(c=(n=this.options).onSessionStart)==null||c.call(n),this.options.autoLocalize&&e.requestAnimationFrame(()=>{this.localizeFrame();}),this.options.autoTracking&&e.requestAnimationFrame(()=>{this.trackObjects();}),this.options.backgroundLocalization&&this.startBackgroundLocalization();}startBackgroundLocalization(){var i,o,a;let e=((o=(i=this.options.objectCodes)==null?void 0:i.length)!=null?o:0)>0,t=e?de:ce,r=Math.max(se,Math.min((a=this.options.bgLocalizationDuration)!=null?a:t,le))*1e3;this.bgLocalizationTimer=setInterval(()=>{this.session&&!this._isLocalizing&&(e?this.trackObjects():this.localizeFrame());},r);}handleSessionEnd(){var e,t,r;this.arButton&&(this.arButton.textContent="START AR",this.arButton.classList.replace("multiset-ar-active","multiset-ar-inactive")),this.bgLocalizationTimer!==null&&(clearInterval(this.bgLocalizationTimer),this.bgLocalizationTimer=null),this.session=null,this.baseLayer=null,this.referenceSpace=null,this.xrBinding=null,this.lastFrameTime=0,this.trackingLossFrames=0,this.hadTrackingLoss=false,this._isLocalizing=false,this.trackerSpace=null,(e=this.adapterSessionEndHandler)==null||e.call(this),(r=(t=this.options).onSessionEnd)==null||r.call(t);}xrLoop(e,t){var s;this.session.requestAnimationFrame((n,c)=>this.xrLoop(n,c));let r=this.lastFrameTime===0?0:(e-this.lastFrameTime)/1e3;this.lastFrameTime=e;let i=t.getViewerPose(this.referenceSpace);if(!i){(this.options.relocalization||this.options.restartTracking)&&(this.trackingLossFrames+=1,this.trackingLossFrames>=ne&&(this.hadTrackingLoss=true));return}this.hadTrackingLoss&&!this._isLocalizing&&(this.options.relocalization||this.options.restartTracking)?(this.hadTrackingLoss=false,this.trackingLossFrames=0,this.options.restartTracking?this.trackObjects():this.options.relocalization&&this.localizeFrame()):this.trackingLossFrames=0;let o=i.views[0],a=this.baseLayer.getViewport(o);(s=this.xrFrameHandler)==null||s.call(this,{time:e,frame:t,pose:i,view:o,viewport:a,baseLayer:this.baseLayer,referenceSpace:this.referenceSpace,deltaSeconds:r});}async localizeFrame(){var s,n,c,d,u,v,f,E,w,b,m,g,y,R,x;if(!this.session)throw new Error("No active XR session. Start AR before calling localizeFrame().");let e=(s=this.options.confidenceCheck)!=null?s:false,t=Math.max(K,Math.min((n=this.options.confidenceThreshold)!=null?n:.5,Q));(d=(c=this.options).onLocalizationInit)==null||d.call(c),this._isLocalizing=true;let r=null,i;try{let T=await this.captureFrame();r=T.result,i=T.failureReason;}catch(T){let L=T instanceof Error?T.message:String(T);return (v=(u=this.options).onLocalizationFailure)==null||v.call(u,L),(E=(f=this.options).onError)==null||E.call(f,T),null}finally{this._isLocalizing=false;}if(r&&(!e||((w=r.localizeData.confidence)!=null?w:0)>=t)&&r)return (m=(b=this.options).onLocalizationSuccess)==null||m.call(b,r),this.trackerSpace&&((g=this.adapterResultHandler)==null||g.call(this,r,this.trackerSpace)),r;let a=i!=null?i:r?e?`Best confidence ${(y=r.localizeData.confidence)!=null?y:0} below threshold ${t}.`:void 0:"All attempts failed to produce a pose.";return (x=(R=this.options).onLocalizationFailure)==null||x.call(R,a),null}async captureFrame(){var i;let e=this.session,t=this.referenceSpace,r=(i=this.options.localizationTrackingTimeoutMs)!=null?i:Z;return new Promise((o,a)=>{let s=Date.now(),n=(c,d)=>{e.requestAnimationFrame(async(u,v)=>{var f,E,w,b,m,g,y,R,x;try{let T=Date.now()-s,L=v.getViewerPose(t);if(!L){if(T>=r||c+1>=Y){let S=(r/1e3).toFixed(1);o({result:null,failureReason:`Tracking unavailable: no viewer pose within ${S}s. Try moving the device or ensuring good lighting.`});}else n(c+1,d);return}let C=L.views;for(let S of C){let F=S.camera;if(!F)continue;let G=(w=(E=(f=this.xrBinding)==null?void 0:f.getCameraImage)==null?void 0:E.call(f,F))!=null?w:null;if(!G)continue;let{width:O,height:j}=F;if(!O||!j)continue;let k=U(S.transform.matrix),A=await X(this.gl,G,O,j);if(!A)continue;let D=N(S.projectionMatrix,{width:A.width,height:A.height,x:0,y:0});if(D){(m=(b=this.options).onFrameCaptured)==null||m.call(b,A),(y=(g=this.options).onCameraIntrinsics)==null||y.call(g,D),this.trackerSpace=k;let I=await this.options.client.localizeWithFrame(A,D);I!=null&&I.localizeData&&((x=(R=this.options).onPoseResult)==null||x.call(R,{poseFound:I.localizeData.poseFound,position:I.localizeData.position,rotation:I.localizeData.rotation,mapIds:I.localizeData.mapIds,confidence:I.localizeData.confidence})),o({result:I});return}}d+1>=J?o({result:null,failureReason:"Camera image not available. The device or browser may not support camera access in AR."}):n(c+1,d+1);}catch(T){a(T);}});};n(0,0);})}async trackObjects(){var a,s,n,c,d,u,v,f,E,w,b,m,g,y,R,x,T;if(!this.session)throw new Error("No active XR session. Start AR before calling trackObjects().");let e=this.options.objectCodes;if(!(e!=null&&e.length))return (s=(a=this.options).onError)==null||s.call(a,new Error("No objectCodes configured. Set objectCodes in the session options.")),null;if(this._isLocalizing)return null;(c=(n=this.options).onObjectTrackingInit)==null||c.call(n),this._isLocalizing=true;let t=null,r;try{let L=await this.captureFrameAndTrack(e);t=L.outcome,r=L.failureReason;}catch(L){let C=L instanceof Error?L.message:String(L);return (u=(d=this.options).onObjectTrackingFailure)==null||u.call(d,C),(f=(v=this.options).onError)==null||f.call(v,L),null}finally{this._isLocalizing=false;}if(!t)return (w=(E=this.options).onObjectTrackingFailure)==null||w.call(E,r!=null?r:"No object detected."),null;let i=(b=this.options.confidenceCheck)!=null?b:false,o=Math.max(K,Math.min((m=this.options.confidenceThreshold)!=null?m:.5,Q));return i&&t.result.confidence<o?((y=(g=this.options).onObjectTrackingFailure)==null||y.call(g,`Confidence ${t.result.confidence.toFixed(3)} below threshold ${o}.`),null):((x=(R=this.options).onObjectTrackingSuccess)==null||x.call(R,t.result),(T=this.adapterObjectTrackingHandler)==null||T.call(this,t.result,t.trackerSpace),t.result)}async captureFrameAndTrack(e){var a,s;let t=(a=this.options.trackingCaptureDelayMs)!=null?a:0;if(t>0&&(await new Promise(n=>setTimeout(n,t)),!this.session))return {outcome:null,failureReason:"Session ended during capture delay."};let r=this.session,i=this.referenceSpace,o=(s=this.options.localizationTrackingTimeoutMs)!=null?s:Z;return new Promise((n,c)=>{let d=Date.now(),u=(v,f)=>{r.requestAnimationFrame(async(E,w)=>{var b,m,g,y,R;try{let x=Date.now()-d,T=w.getViewerPose(i);if(!T){if(x>=o||v+1>=Y){let C=(o/1e3).toFixed(1);n({outcome:null,failureReason:`Tracking unavailable: no viewer pose within ${C}s. Try moving the device or ensuring good lighting.`});}else u(v+1,f);return}let L=T.views;for(let C of L){let S=C.camera;if(!S)continue;let F=(g=(m=(b=this.xrBinding)==null?void 0:b.getCameraImage)==null?void 0:m.call(b,S))!=null?g:null;if(!F)continue;let{width:G,height:O}=S;if(!G||!O)continue;let j=U(C.transform.matrix),k=await X(this.gl,F,G,O);if(!k)continue;let A=N(C.projectionMatrix,{width:k.width,height:k.height,x:0,y:0});if(A){(R=(y=this.options).onObjectTrackingRequested)==null||R.call(y,k,A);let D=await this.options.client.trackObject(k,A,e);n(D?{outcome:{result:D,trackerSpace:j}}:{outcome:null,failureReason:"No object detected."});return}}f+1>=J?n({outcome:null,failureReason:"Camera image not available. The device or browser may not support camera access in AR."}):u(v+1,f+1);}catch(x){c(x);}});};u(0,0);})}dispose(){if(this.bgLocalizationTimer!==null&&(clearInterval(this.bgLocalizationTimer),this.bgLocalizationTimer=null),this.session)try{this.session.end();}catch{}let e=this.gl.canvas;e instanceof HTMLCanvasElement&&(e.removeEventListener("webglcontextlost",this.handleContextLost),e.removeEventListener("webglcontextrestored",this.handleContextRestored)),this.arButton=null;}};var ue=`
2
2
  varying vec3 vWorldPosition;
3
3
  varying vec3 vWorldNormal;
4
4
 
@@ -9,7 +9,7 @@ void main() {
9
9
 
10
10
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
11
11
  }
12
- `,Y=`
12
+ `,he=`
13
13
  uniform vec3 uColor;
14
14
  uniform float uOpacity;
15
15
 
@@ -90,4 +90,45 @@ void main() {
90
90
 
91
91
  gl_FragColor = vec4(finalColor, alphaWithGlow);
92
92
  }
93
- `;function _(o={}){var E,L,y,T,C,R,z,x,F,A,b,M,S,H;let e=(E=o.color)!=null?E:"#7B2CBF",t=(L=o.opacity)!=null?L:150/255,i=(y=o.gridColor)!=null?y:"#ffeb3b",r=(T=o.gridScale)!=null?T:2,a=(C=o.gridLineWidth)!=null?C:.02,n=(R=o.showGrid)!=null?R:1,l=(z=o.center)!=null?z:new f.Vector3(0,0,0),s=(x=o.radius)!=null?x:1,c=(F=o.progress)!=null?F:1,d=(A=o.glowColor)!=null?A:"#FF3A00",u=(b=o.glowWidth)!=null?b:.05,h=(M=o.glowIntensity)!=null?M:2,m=(S=o.lightColor)!=null?S:"#ffffff",p=(H=o.lightDirection)!=null?H:new f.Vector3(.3,1,.3).normalize();return new f.ShaderMaterial({vertexShader:J,fragmentShader:Y,transparent:true,side:f.DoubleSide,uniforms:{uColor:{value:typeof e=="string"?new f.Color(e):e},uOpacity:{value:t},uGridColor:{value:typeof i=="string"?new f.Color(i):i},uGridSize:{value:r},uGridLineWidth:{value:a},uShowGrid:{value:n},uCenter:{value:l.clone()},uRadius:{value:s},uProgress:{value:c},uGlowColor:{value:typeof d=="string"?new f.Color(d):d},uGlowWidth:{value:u},uGlowIntensity:{value:h},uLightColor:{value:typeof m=="string"?new f.Color(m):m},uLightDirection:{value:p.clone()}}})}var D=class{constructor(e,t,i,r){this.scene=e;this.client=t;this.renderer=i;this.camera=r;this.gizmoControl=null;this.gizmoHelper=null;this.meshMaterial=null;this.localCenter=new f.Vector3;this.localRadius=0;this.radialDuration=4;this.meshGroup=new f.Group,this.meshGroup.visible=false,this.scene.add(this.meshGroup),this.dracoLoader=new DRACOLoader,this.dracoLoader.setDecoderPath("/draco/"),this.gltfLoader=new GLTFLoader,this.gltfLoader.setDRACOLoader(this.dracoLoader);}getMeshGroup(){return this.meshGroup}async ensureGizmoLoaded(){if(!this.gizmoControl){let e=new TransformControls(this.camera,this.renderer.domElement);e.setMode("translate"),e.setSpace("local"),e.enabled=false;let t=e.getHelper();this.scene.add(t),e.attach(this.meshGroup),this.gizmoControl=e,this.gizmoHelper=t;}this.gizmoHelper.visible=true;}async ensureMeshLoaded(e){var r,a;if(this.scene.getObjectByName(e._id))return;let t=(a=(r=e.mapMesh)==null?void 0:r.rawMesh)==null?void 0:a.meshLink;if(!t)return;let i=await this.client.downloadFile(t);i&&await new Promise((n,l)=>{this.gltfLoader.load(i,s=>{let c=new f.Box3().setFromObject(s.scene),d=c.getSize(new f.Vector3),u=c.getCenter(new f.Vector3);this.localCenter.copy(u),this.localRadius=Math.max(d.length()/2*1.1,.001);let h=_({color:"#7B2CBF",opacity:.58,gridColor:"#ffeb3b",gridScale:2,gridLineWidth:.02,showGrid:1,center:this.localCenter.clone(),radius:this.localRadius,progress:0,glowColor:"#FF3A00",glowWidth:.05,glowIntensity:2});this.meshMaterial=h,s.scene.traverse(m=>{let p=m;p.isMesh&&(p.material=h,p.frustumCulled=false);}),s.scene.name=e._id,this.meshGroup.add(s.scene),n();},void 0,s=>{l(s instanceof Error?s:new Error(String(s)));});});}applyMeshTransform(e){let t=new f.Vector3,i=new f.Quaternion,r=new f.Vector3;e.decompose(t,i,r),this.meshGroup.position.copy(t),this.meshGroup.quaternion.copy(i),this.meshGroup.scale.set(1,1,1),this.meshGroup.visible=true,this.meshGroup.updateMatrixWorld(true),this.meshMaterial&&(this.meshMaterial.uniforms.uProgress.value=0,this.meshMaterial.uniforms.uCenter.value.copy(this.localCenter).applyMatrix4(this.meshGroup.matrixWorld));}update(e,t){if(!this.meshMaterial)return;let i=this.meshMaterial.uniforms.uProgress;i&&(i.value>=1||(i.value=Math.min(i.value+e/this.radialDuration,1)));}hideUntilNextLocalization(){this.meshGroup.visible=false,this.gizmoHelper&&(this.gizmoHelper.visible=false),this.meshMaterial&&(this.meshMaterial.uniforms.uProgress.value=0);}dispose(){this.meshMaterial=null,this.meshGroup.traverse(e=>{let t=e;t.isMesh&&(t.geometry.dispose(),Array.isArray(t.material)?t.material.forEach(i=>i.dispose()):t.material.dispose());}),this.scene.remove(this.meshGroup),this.dracoLoader.dispose(),this.gizmoHelper&&this.scene.remove(this.gizmoHelper),this.gizmoControl&&this.gizmoControl.dispose();}};var W=class{constructor(e,t,i,r){this.meshVisualizer=new D(e,t,i,r);}async ensureGizmoLoaded(){await this.meshVisualizer.ensureGizmoLoaded();}async ensureMeshLoaded(e){await this.meshVisualizer.ensureMeshLoaded(e);}applyMeshTransform(e){this.meshVisualizer.applyMeshTransform(e);}update(e,t){this.meshVisualizer.update(e,t);}hideUntilNextLocalization(){this.meshVisualizer.hideUntilNextLocalization();}dispose(){this.meshVisualizer.dispose();}};var B=class{constructor(e){this.options=e;this.world=null;this.previewRAFHandle=null;this.resizeHandler=null;this.xrRenderTarget=null;let{session:t,renderer:i,camera:r}=e;i.xr.enabled=false,r.matrixAutoUpdate=false,t.setXRFrameHandler(a=>this.onXRFrame(a)),t.setAdapterResultHandler((a,n)=>{this.handleLocalizationResult(a,n);}),t.setAdapterSessionHandlers(()=>{r.matrixAutoUpdate=false;let a=t.getBaseLayer();a&&(this.xrRenderTarget=new f.WebGLRenderTarget(a.framebufferWidth,a.framebufferHeight)),this.stopPreviewLoop();},()=>{var a,n;i.setRenderTarget(null),this.xrRenderTarget&&(i.setRenderTargetFramebuffer(this.xrRenderTarget,null),this.xrRenderTarget.dispose(),this.xrRenderTarget=null),(a=this.world)==null||a.hideUntilNextLocalization(),r.matrixAutoUpdate=true,r.position.set(0,0,0),r.quaternion.identity(),(n=this.resizeHandler)==null||n.call(this),this.startPreviewLoop();}),(e.showMesh||e.showGizmo!==false)&&(this.world=new W(e.scene,t.getClient(),i,r));}static isSupported(){return I.isSupported()}initialize(e){var l,s;let{options:t}=this,{session:i}=t,r=null;if(t.useDefaultButton!==false){r=i.createButton();let c=e instanceof HTMLElement?e:t.buttonContainer instanceof HTMLElement?t.buttonContainer:(l=i.getOverlayRoot())!=null?l:document.body;c.contains(r)||c.appendChild(r),(s=t.onButtonCreated)==null||s.call(t,r);}let{renderer:a,camera:n}=t;return this.resizeHandler=()=>{n.aspect=window.innerWidth/window.innerHeight,n.updateProjectionMatrix(),a.setSize(window.innerWidth,window.innerHeight);},window.addEventListener("resize",this.resizeHandler),this.resizeHandler(),this.startPreviewLoop(),r}isActive(){return this.options.session.isActive()}get isLocalizing(){return this.options.session.isLocalizing}startSession(){return this.options.session.startSession()}stopSession(){this.options.session.stopSession();}async localizeFrame(){return this.options.session.localizeFrame()}onXRFrame(e){var a,n;let{renderer:t,scene:i,camera:r}=this.options;if(this.xrRenderTarget&&(t.setRenderTargetFramebuffer(this.xrRenderTarget,e.baseLayer.framebuffer),t.setRenderTarget(this.xrRenderTarget)),this.syncCameraMatrices(e.view),this.world&&e.deltaSeconds>0){let l=e.pose.transform.position;this.world.update(e.deltaSeconds,new f.Vector3(l.x,l.y,l.z));}(n=(a=this.options).onXRFrame)==null||n.call(a,e),t.render(i,r);}syncCameraMatrices(e){let{camera:t}=this.options;t.projectionMatrix.fromArray(e.projectionMatrix),t.projectionMatrixInverse.copy(t.projectionMatrix).invert(),t.matrixWorld.fromArray(e.transform.matrix),t.matrixWorldInverse.fromArray(e.transform.inverse.matrix),t.matrix.copy(t.matrixWorld);}startPreviewLoop(){let{renderer:e,scene:t,camera:i}=this.options,r=()=>{this.previewRAFHandle=window.requestAnimationFrame(r),e.render(t,i);};this.previewRAFHandle=window.requestAnimationFrame(r);}stopPreviewLoop(){this.previewRAFHandle!==null&&(cancelAnimationFrame(this.previewRAFHandle),this.previewRAFHandle=null);}async handleLocalizationResult(e,t){var h,m,p;let{position:i,rotation:r}=e.localizeData,a=new f.Matrix4().compose(new f.Vector3(i.x,i.y,i.z),new f.Quaternion(r.x,r.y,r.z,r.w),new f.Vector3(1,1,1)),n=new f.Matrix4().multiplyMatrices(new f.Matrix4().fromArray(t),a.invert());if((m=(h=this.options).onLocalizationSuccess)==null||m.call(h,e,n),!this.world)return;let{showMesh:l,showGizmo:s,session:c}=this.options,d=s!==false;if(l&&((p=e.localizeData.mapCodes)!=null&&p[0]))try{let E=await c.getClient().fetchMapDetails(e.localizeData.mapCodes[0]);E&&(e.mapDetails=E);}catch{}let u=l&&!!e.mapDetails;if(!(!u&&!d))try{if(d&&await this.world.ensureGizmoLoaded(),u&&await this.world.ensureMeshLoaded(e.mapDetails),!c.isActive())return;this.world.applyMeshTransform(n);}catch{}}dispose(){var e;this.resizeHandler&&(window.removeEventListener("resize",this.resizeHandler),this.resizeHandler=null),this.stopPreviewLoop(),(e=this.world)==null||e.dispose(),this.world=null,this.options.session.dispose();}};export{B as ThreeAdapter};
93
+ `;function ee(l={}){var w,b,m,g,y,R,x,T,L,C,S,F,G,O;let e=(w=l.color)!=null?w:"#7B2CBF",t=(b=l.opacity)!=null?b:150/255,r=(m=l.gridColor)!=null?m:"#ffeb3b",i=(g=l.gridScale)!=null?g:2,o=(y=l.gridLineWidth)!=null?y:.02,a=(R=l.showGrid)!=null?R:1,s=(x=l.center)!=null?x:new p.Vector3(0,0,0),n=(T=l.radius)!=null?T:1,c=(L=l.progress)!=null?L:1,d=(C=l.glowColor)!=null?C:"#FF3A00",u=(S=l.glowWidth)!=null?S:.05,v=(F=l.glowIntensity)!=null?F:2,f=(G=l.lightColor)!=null?G:"#ffffff",E=(O=l.lightDirection)!=null?O:new p.Vector3(.3,1,.3).normalize();return new p.ShaderMaterial({vertexShader:ue,fragmentShader:he,transparent:true,side:p.DoubleSide,uniforms:{uColor:{value:typeof e=="string"?new p.Color(e):e},uOpacity:{value:t},uGridColor:{value:typeof r=="string"?new p.Color(r):r},uGridSize:{value:i},uGridLineWidth:{value:o},uShowGrid:{value:a},uCenter:{value:s.clone()},uRadius:{value:n},uProgress:{value:c},uGlowColor:{value:typeof d=="string"?new p.Color(d):d},uGlowWidth:{value:u},uGlowIntensity:{value:v},uLightColor:{value:typeof f=="string"?new p.Color(f):f},uLightDirection:{value:E.clone()}}})}var q=.18,te=.01,fe=.025,re=.06,ve=.018,Ee=[["#ff2060",new p.Vector3(1,0,0)],["#20df80",new p.Vector3(0,1,0)],["#2080ff",new p.Vector3(0,0,1)]],P=new p.Vector3(0,1,0);function ge(){let l=new p.Group,e=new p.Mesh(new p.SphereGeometry(ve,16,16),new p.MeshBasicMaterial({color:16777215,depthTest:false,transparent:true}));e.renderOrder=999,l.add(e);for(let[t,r]of Ee){let i=new p.MeshBasicMaterial({color:t,depthTest:false,transparent:true}),o=new p.Mesh(new p.CylinderGeometry(te,te,q,12),i);o.position.copy(r.clone().multiplyScalar(q/2)),r.equals(P)||o.quaternion.setFromUnitVectors(P,r),o.renderOrder=998,l.add(o);let a=new p.Mesh(new p.ConeGeometry(fe,re,16,1),i);a.position.copy(r.clone().multiplyScalar(q+re/2)),r.equals(P)||a.quaternion.setFromUnitVectors(P,r),a.renderOrder=999,l.add(a);}return l}var W=class{constructor(e,t,r,i){this.scene=e;this.client=t;this.renderer=r;this.camera=i;this.gizmoHelper=null;this.meshMaterial=null;this.localCenter=new p.Vector3;this.localRadius=0;this.radialDuration=4;this.meshGroup=new p.Group,this.meshGroup.visible=false,this.scene.add(this.meshGroup),this.dracoLoader=new DRACOLoader,this.dracoLoader.setDecoderPath("/draco/"),this.gltfLoader=new GLTFLoader,this.gltfLoader.setDRACOLoader(this.dracoLoader);}getMeshGroup(){return this.meshGroup}ensureGizmoLoaded(){if(!this.gizmoHelper){let e=ge();this.meshGroup.add(e),this.gizmoHelper=e;}}async ensureMeshLoaded(e){var i,o;if(this.scene.getObjectByName(e._id))return;let t=(o=(i=e.mapMesh)==null?void 0:i.rawMesh)==null?void 0:o.meshLink;if(!t)return;let r=await this.client.downloadFile(t);r&&await new Promise((a,s)=>{this.gltfLoader.load(r,n=>{let c=new p.Box3().setFromObject(n.scene),d=c.getSize(new p.Vector3),u=c.getCenter(new p.Vector3);this.localCenter.copy(u),this.localRadius=Math.max(d.length()/2*1.1,.001);let v=ee({color:"#7B2CBF",opacity:.58,gridColor:"#ffeb3b",gridScale:.25,gridLineWidth:.015,showGrid:1,center:this.localCenter.clone(),radius:this.localRadius,progress:0,glowColor:"#FF3A00",glowWidth:.05,glowIntensity:2});this.meshMaterial=v,n.scene.traverse(f=>{let E=f;E.isMesh&&(E.material=v,E.frustumCulled=false);}),n.scene.name=e._id,this.meshGroup.add(n.scene),a();},void 0,n=>{s(n instanceof Error?n:new Error(String(n)));});});}applyMeshTransform(e){let t=new p.Vector3,r=new p.Quaternion,i=new p.Vector3;e.decompose(t,r,i),this.meshGroup.position.copy(t),this.meshGroup.quaternion.copy(r),this.meshGroup.scale.set(1,1,1),this.meshGroup.visible=true,this.meshGroup.updateMatrixWorld(true),this.meshMaterial&&(this.meshMaterial.uniforms.uProgress.value=0,this.meshMaterial.uniforms.uCenter.value.copy(this.localCenter).applyMatrix4(this.meshGroup.matrixWorld));}update(e,t){if(!this.meshMaterial)return;let r=this.meshMaterial.uniforms.uProgress;r&&(r.value>=1||(r.value=Math.min(r.value+e/this.radialDuration,1)));}hideUntilNextLocalization(){this.meshGroup.visible=false,this.meshMaterial&&(this.meshMaterial.uniforms.uProgress.value=0);}dispose(){var e;(e=this.meshMaterial)==null||e.dispose(),this.meshMaterial=null,this.meshGroup.traverse(t=>{let r=t;r.isMesh&&(r.geometry.dispose(),Array.isArray(r.material)?r.material.forEach(i=>i.dispose()):r.material.dispose());}),this.scene.remove(this.meshGroup),this.dracoLoader.dispose(),this.gizmoHelper=null;}};var Re=`
94
+ attribute vec3 aStart;
95
+ attribute vec3 aEnd;
96
+ attribute float aSide;
97
+ attribute float aAlong;
98
+
99
+ uniform float uTime;
100
+ uniform float uLineWidth;
101
+ uniform vec2 uResolution;
102
+
103
+ varying float vIntensity;
104
+
105
+ void main() {
106
+ vec4 clipA = projectionMatrix * modelViewMatrix * vec4(aStart, 1.0);
107
+ vec4 clipB = projectionMatrix * modelViewMatrix * vec4(aEnd, 1.0);
108
+
109
+ vec2 dirScreen = (clipB.xy / clipB.w - clipA.xy / clipA.w) * uResolution;
110
+ vec2 tangent = normalize(dirScreen + vec2(0.0001, 0.0));
111
+ vec2 normal = vec2(-tangent.y, tangent.x);
112
+
113
+ vec4 clipPos = mix(clipA, clipB, aAlong);
114
+
115
+ vec2 offset = normal * aSide * (uLineWidth * 0.5);
116
+ clipPos.xy += (offset / uResolution) * clipPos.w;
117
+
118
+ gl_Position = clipPos;
119
+
120
+ vec3 midpoint = mix(aStart, aEnd, 0.5);
121
+ float phase = dot(midpoint, vec3(3.1415, 2.7183, 4.3301));
122
+ vIntensity = 0.6 + 0.4 * sin(phase + uTime * 2.5);
123
+ }
124
+ `,Te=`
125
+ uniform vec3 uColor;
126
+ uniform float uOpacity;
127
+ uniform float uGlowIntensity;
128
+
129
+ varying float vIntensity;
130
+
131
+ void main() {
132
+ gl_FragColor = vec4(uColor * uGlowIntensity * vIntensity, uOpacity * vIntensity);
133
+ }
134
+ `;function ie(l){let e=l.attributes.position.array,t=e.length/6,r=new Float32Array(t*4*3),i=new Float32Array(t*4*3),o=new Float32Array(t*4),a=new Float32Array(t*4),s=new Uint32Array(t*6);for(let c=0;c<t;c++){let d=c*6,u=e[d],v=e[d+1],f=e[d+2],E=e[d+3],w=e[d+4],b=e[d+5],m=c*4;for(let y=0;y<4;y++){let R=(m+y)*3;r[R]=u,r[R+1]=v,r[R+2]=f,i[R]=E,i[R+1]=w,i[R+2]=b;}o[m]=-1,o[m+1]=1,o[m+2]=-1,o[m+3]=1,a[m]=0,a[m+1]=0,a[m+2]=1,a[m+3]=1;let g=c*6;s[g]=m,s[g+1]=m+2,s[g+2]=m+1,s[g+3]=m+1,s[g+4]=m+2,s[g+5]=m+3;}let n=new p.BufferGeometry;return n.setAttribute("aStart",new p.BufferAttribute(r,3)),n.setAttribute("aEnd",new p.BufferAttribute(i,3)),n.setAttribute("aSide",new p.BufferAttribute(o,1)),n.setAttribute("aAlong",new p.BufferAttribute(a,1)),n.setIndex(new p.BufferAttribute(s,1)),n}function oe(l={}){var o,a,s,n;let e=(o=l.color)!=null?o:"#9055FF",t=(a=l.lineWidth)!=null?a:3,r=(s=l.opacity)!=null?s:.9,i=(n=l.glowIntensity)!=null?n:2;return new p.ShaderMaterial({vertexShader:Re,fragmentShader:Te,transparent:true,blending:p.AdditiveBlending,depthWrite:false,depthTest:true,side:p.DoubleSide,uniforms:{uColor:{value:typeof e=="string"?new p.Color(e):e.clone()},uLineWidth:{value:t},uResolution:{value:new p.Vector2(1080,1920)},uOpacity:{value:r},uGlowIntensity:{value:i},uTime:{value:0}}})}async function be(l,e){let t=await e.loadAsync(l),r=new p.Group,i=[],o=t.scene.clone(true);o.traverse(s=>{s instanceof p.Mesh&&(s.material=new p.MeshBasicMaterial({colorWrite:false,side:p.FrontSide}),s.renderOrder=0);}),r.add(o),t.scene.updateWorldMatrix(true,true);let a=new p.Group;return t.scene.traverse(s=>{if(!(s instanceof p.Mesh))return;let n=new p.EdgesGeometry(s.geometry,15),c=ie(n);n.dispose();let d=oe();i.push(d);let u=new p.Mesh(c,d);u.applyMatrix4(s.matrixWorld),u.renderOrder=1,u.frustumCulled=false,a.add(u);}),r.add(a),{group:r,materials:i}}var V=class{constructor(e,t,r,i){this.scene=e;this.client=t;this.renderer=r;this.onObjectMeshLoaded=i;this.tracked=new Map;this.meshCache=new Map;this.activeMaterials=new Set;this.loader=new GLTFLoader;this.elapsed=0;this._renderSize=new p.Vector2;}async placeOrUpdateAnchor(e,t,r){let i=new p.Matrix4().compose(new p.Vector3(r.position.x,r.position.y,r.position.z),new p.Quaternion(r.rotation.x,r.rotation.y,r.rotation.z,r.rotation.w),new p.Vector3(1,1,1)),o=new p.Matrix4().multiplyMatrices(t,i.clone().invert()),a=new p.Vector3,s=new p.Quaternion,n=new p.Vector3;if(o.decompose(a,s,n),this.tracked.has(e)){let d=this.tracked.get(e);d.anchor.position.copy(a),d.anchor.quaternion.copy(s),d.anchor.updateMatrixWorld(true);return}let c=new p.Object3D;c.position.copy(a),c.quaternion.copy(s),this.scene.add(c),this.tracked.set(e,{anchor:c,mesh:null}),this.loadAndAttachMesh(e,c);}update(e){this.elapsed+=e,this.renderer.getSize(this._renderSize);for(let t of this.activeMaterials)t.uniforms.uTime.value=this.elapsed,t.uniforms.uResolution.value.copy(this._renderSize);}async loadAndAttachMesh(e,t){var a;let r=await this.getOrLoadMesh(e),i=this.tracked.has(e),o=t.parent!==null;if(r&&i&&o){let s=r.clone();t.add(s),this.tracked.get(e).mesh=s,s.traverse(n=>{n instanceof p.Mesh&&n.material instanceof p.ShaderMaterial&&this.activeMaterials.add(n.material);}),(a=this.onObjectMeshLoaded)==null||a.call(this,e);}}async getOrLoadMesh(e){if(this.meshCache.has(e))return this.meshCache.get(e);let t=await this.client.downloadObjectMesh(e);if(!t)return null;try{let{group:r,materials:i}=await be(t,this.loader);this.meshCache.set(e,r);for(let o of i)this.activeMaterials.add(o);return r}catch{return null}}clearAll(){this.tracked.forEach(({anchor:e})=>this.scene.remove(e)),this.tracked.clear(),this.activeMaterials.clear();}dispose(){this.clearAll(),this.meshCache.forEach(e=>{e.traverse(t=>{t instanceof p.Mesh&&(t.geometry.dispose(),Array.isArray(t.material)?t.material.forEach(r=>r.dispose()):t.material.dispose());});}),this.meshCache.clear(),this.activeMaterials.clear();}get trackedCodes(){return Array.from(this.tracked.keys())}};var _=class{constructor(e,t,r,i,o){this.scene=e;this.client=t;this.renderer=r;this.onObjectMeshLoaded=o;this.objectVisualizer=null;this.meshVisualizer=new W(e,t,r,i);}ensureGizmoLoaded(){this.meshVisualizer.ensureGizmoLoaded();}async ensureMeshLoaded(e){await this.meshVisualizer.ensureMeshLoaded(e);}applyMeshTransform(e){this.meshVisualizer.applyMeshTransform(e);}async placeOrUpdateObjectAnchor(e,t,r){return this.objectVisualizer||(this.objectVisualizer=new V(this.scene,this.client,this.renderer,this.onObjectMeshLoaded)),this.objectVisualizer.placeOrUpdateAnchor(e,t,r)}clearObjectMeshes(){var e;(e=this.objectVisualizer)==null||e.clearAll();}update(e,t){var r;this.meshVisualizer.update(e,t),(r=this.objectVisualizer)==null||r.update(e);}hideUntilNextLocalization(){this.meshVisualizer.hideUntilNextLocalization();}dispose(){var e;this.meshVisualizer.dispose(),(e=this.objectVisualizer)==null||e.dispose(),this.objectVisualizer=null;}};var $=class{constructor(e){this.options=e;this.world=null;this.previewRAFHandle=null;this.resizeHandler=null;this.xrRenderTarget=null;let{session:t,renderer:r,camera:i}=e;r.xr.enabled=false,i.matrixAutoUpdate=false,t.setXRFrameHandler(o=>this.onXRFrame(o)),t.setAdapterResultHandler((o,a)=>{this.handleLocalizationResult(o,a);}),t.setAdapterSessionHandlers(()=>{i.matrixAutoUpdate=false;let o=t.getBaseLayer();o&&(this.xrRenderTarget=new p.WebGLRenderTarget(o.framebufferWidth,o.framebufferHeight)),this.stopPreviewLoop();},()=>{var o,a;r.setRenderTarget(null),this.xrRenderTarget&&(r.setRenderTargetFramebuffer(this.xrRenderTarget,null),this.xrRenderTarget.dispose(),this.xrRenderTarget=null),(o=this.world)==null||o.hideUntilNextLocalization(),i.matrixAutoUpdate=true,i.position.set(0,0,0),i.quaternion.identity(),(a=this.resizeHandler)==null||a.call(this),this.startPreviewLoop();}),(e.showMesh||e.showGizmo!==false||e.showObjectMeshes)&&(this.world=new _(e.scene,t.getClient(),r,i,e.onObjectMeshLoaded)),t.setAdapterObjectTrackingHandler((o,a)=>{e.showObjectMeshes&&this.world&&o.objectCodes.length>0&&this.world.placeOrUpdateObjectAnchor(o.objectCodes[0],new p.Matrix4().fromArray(a),o);});}static isSupported(){return B.isSupported()}initialize(e){var s,n;let{options:t}=this,{session:r}=t,i=null;if(t.useDefaultButton!==false){i=r.createButton();let c=e instanceof HTMLElement?e:t.buttonContainer instanceof HTMLElement?t.buttonContainer:(s=r.getOverlayRoot())!=null?s:document.body;c.contains(i)||c.appendChild(i),(n=t.onButtonCreated)==null||n.call(t,i);}let{renderer:o,camera:a}=t;return this.resizeHandler=()=>{a.aspect=window.innerWidth/window.innerHeight,a.updateProjectionMatrix(),o.setSize(window.innerWidth,window.innerHeight);},window.addEventListener("resize",this.resizeHandler),this.resizeHandler(),this.startPreviewLoop(),i}isActive(){return this.options.session.isActive()}get isLocalizing(){return this.options.session.isLocalizing}startSession(){return this.options.session.startSession()}stopSession(){this.options.session.stopSession();}async localizeFrame(){return this.options.session.localizeFrame()}async trackObjects(){return this.options.session.trackObjects()}clearObjectMeshes(){var e;(e=this.world)==null||e.clearObjectMeshes();}onXRFrame(e){var o,a;let{renderer:t,scene:r,camera:i}=this.options;if(this.xrRenderTarget&&(t.setRenderTargetFramebuffer(this.xrRenderTarget,e.baseLayer.framebuffer),t.setRenderTarget(this.xrRenderTarget)),this.syncCameraMatrices(e.view),this.world&&e.deltaSeconds>0){let s=e.pose.transform.position;this.world.update(e.deltaSeconds,new p.Vector3(s.x,s.y,s.z));}(a=(o=this.options).onXRFrame)==null||a.call(o,e),t.render(r,i);}syncCameraMatrices(e){let{camera:t}=this.options;t.projectionMatrix.fromArray(e.projectionMatrix),t.projectionMatrixInverse.copy(t.projectionMatrix).invert(),t.matrixWorld.fromArray(e.transform.matrix),t.matrixWorldInverse.fromArray(e.transform.inverse.matrix),t.matrix.copy(t.matrixWorld);}startPreviewLoop(){let{renderer:e,scene:t,camera:r}=this.options,i=()=>{this.previewRAFHandle=window.requestAnimationFrame(i),e.render(t,r);};this.previewRAFHandle=window.requestAnimationFrame(i);}stopPreviewLoop(){this.previewRAFHandle!==null&&(cancelAnimationFrame(this.previewRAFHandle),this.previewRAFHandle=null);}async handleLocalizationResult(e,t){var v,f,E;let{position:r,rotation:i}=e.localizeData,o=new p.Matrix4().compose(new p.Vector3(r.x,r.y,r.z),new p.Quaternion(i.x,i.y,i.z,i.w),new p.Vector3(1,1,1)),a=new p.Matrix4().multiplyMatrices(new p.Matrix4().fromArray(t),o.invert());if((f=(v=this.options).onLocalizationSuccess)==null||f.call(v,e,a),!this.world)return;let{showMesh:s,showGizmo:n,session:c}=this.options,d=n!==false;if(s&&((E=e.localizeData.mapCodes)!=null&&E[0]))try{let w=await c.getClient().fetchMapDetails(e.localizeData.mapCodes[0]);w&&(e.mapDetails=w);}catch{}let u=s&&!!e.mapDetails;if(!(!u&&!d))try{if(d&&this.world.ensureGizmoLoaded(),u&&await this.world.ensureMeshLoaded(e.mapDetails),!c.isActive())return;this.world.applyMeshTransform(a);}catch{}}dispose(){var e;this.resizeHandler&&(window.removeEventListener("resize",this.resizeHandler),this.resizeHandler=null),this.stopPreviewLoop(),(e=this.world)==null||e.dispose(),this.world=null,this.options.session.dispose();}};export{$ as ThreeAdapter};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multisetai/vps",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "Multiset VPS WebXR SDK - Core client and WebXR controller.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -64,8 +64,5 @@
64
64
  "rimraf": "^6.0.1",
65
65
  "tsup": "^8.1.0",
66
66
  "typescript": "~5.8.3"
67
- },
68
- "dependencies": {
69
- "axios": "1.10.0"
70
67
  }
71
68
  }