@multisetai/vps 2.1.0 → 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 +111 -23
- package/dist/core/index.d.ts +42 -1
- package/dist/core/index.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +44 -3
- package/dist/three/index.d.ts +7 -1
- package/dist/three/index.js +44 -3
- package/package.json +1 -4
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
|
-
###
|
|
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 (
|
|
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
|
|
130
|
+
// Wire your render loop — called every XR frame with the current pose and framebuffer.
|
|
129
131
|
session.setXRFrameHandler((event) => {
|
|
130
|
-
// event
|
|
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
|
|
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
|
|
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
|
|
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)
|
package/dist/core/index.d.ts
CHANGED
|
@@ -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 };
|
package/dist/core/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import E from'axios';var F={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={...F,...i.endpoints};}get token(){return this.accessToken}async authorize(){var n,i,a;let e=await E.post(this.endpoints.authUrl,{},{auth:{username:this.credentials.clientId,password:this.credentials.clientSecret}}),t=(a=(n=e.data)==null?void 0:n.token)!=null?a:(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((s,p)=>{navigator.geolocation.getCurrentPosition(s,p,{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 d,u,m;let i=new FormData;this.config.mapType==="map"?i.append("mapCode",this.config.code):i.append("mapSetCode",this.config.code);let a=(d=this.config.isRightHanded)!=null?d: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&&((u=this.config.hintMapCodes)!=null&&u.length)&&this.config.hintMapCodes.forEach(r=>{i.append("hintMapCodes",r);}),this.config.hintPosition&&i.append("hintPosition",this.config.hintPosition);let s;if(this.config.passGeoPose){let r=await this.getGeoPoseComponents();if(r){let{latitude:f,longitude:g,altitude:v}=r;s=`${f},${g},${v}`;}}if(s&&i.append("geoHint",s),this.config.hintRadius!==void 0){let r=Number(this.config.hintRadius);if(Number.isNaN(r)||r<1||r>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",`${r}`);}"use2DFiltering"in this.config&&this.config.use2DFiltering!==void 0&&i.append("use2DFiltering",`${this.config.use2DFiltering}`);let o=(await E.post(this.endpoints.queryUrl,i,{headers:{Authorization:`Bearer ${this.accessToken}`}})).data;if(!o.poseFound)return null;let c={localizeData:o};if(n&&((m=o.mapCodes)!=null&&m.length)){let r=await this.fetchMapDetails(o.mapCodes[0]);r&&(c.mapDetails=r);}return c}};async function _(l,e,t,n=.8,i=e,a=t){let s=document.createElement("canvas");s.width=e,s.height=t;let p=s.getContext("2d");if(!p)return new Blob;p.putImageData(new ImageData(new Uint8ClampedArray(l),e,t),0,0);let o=document.createElement("canvas");o.width=i,o.height=a;let c=o.getContext("2d");return c?(c.drawImage(s,0,0,i,a),new Promise(d=>{o.toBlob(u=>d(u!=null?u:new Blob),"image/jpeg",n);})):new Blob}async function X(l,e,t,n){let i=l.createFramebuffer();if(!i)return null;let a;try{l.bindFramebuffer(l.FRAMEBUFFER,i),l.framebufferTexture2D(l.FRAMEBUFFER,l.COLOR_ATTACHMENT0,l.TEXTURE_2D,e,0),a=new Uint8Array(t*n*4),l.readPixels(0,0,t,n,l.RGBA,l.UNSIGNED_BYTE,a);}finally{l.bindFramebuffer(l.FRAMEBUFFER,null),l.deleteFramebuffer(i);}let s=new Uint8ClampedArray(a.length);for(let u=0;u<n;u+=1){let m=u*t*4,r=(n-u-1)*t*4;s.set(a.subarray(m,m+t*4),r);}let p=Math.min(1,1280/Math.max(t,n)),o=Math.round(t*p),c=Math.round(n*p),d=await _(s.buffer,t,n,.7,o,c);return d.size?{blob:d,width:o,height:c}:null}function B(l,e){let t=l,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 G(l){return new Float32Array(l)}var U=.2,N=.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 s,p,o,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((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,u)=>this.xrLoop(d,u)),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=(o=this.options).onSessionStart)==null||c.call(o),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((o,c)=>this.xrLoop(o,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 a=i.views[0],s=this.baseLayer.getViewport(a);(p=this.xrFrameHandler)==null||p.call(this,{time:e,frame:t,pose:i,view:a,viewport:s,baseLayer:this.baseLayer,referenceSpace:this.referenceSpace,deltaSeconds:n});}async localizeFrame(){var p,o,c,d,u,m,r,f,g,v,C,I,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(U,Math.min((o=this.options.confidenceThreshold)!=null?o:.5,N));(d=(c=this.options).onLocalizationInit)==null||d.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=(u=this.options).onError)==null||m.call(u,y),null}finally{this._isLocalizing=false;}if(n&&(!e||((r=n.localizeData.confidence)!=null?r:0)>=t)&&n)return (g=(f=this.options).onLocalizationSuccess)==null||g.call(f,n),this.trackerSpace&&((v=this.adapterResultHandler)==null||v.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 (b=(I=this.options).onLocalizationFailure)==null||b.call(I,s),null}async captureFrame(){var i;let e=this.session,t=this.referenceSpace,n=(i=this.options.localizationTrackingTimeoutMs)!=null?i:O;return new Promise((a,s)=>{let p=Date.now(),o=(c,d)=>{e.requestAnimationFrame(async(u,m)=>{var r,f,g,v,C,I,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);a({result:null,failureReason:`Tracking unavailable: no viewer pose within ${R}s. Try moving the device or ensuring good lighting.`});}else o(c+1,d);return}let H=A.views;for(let R of H){let x=R.camera;if(!x)continue;let z=(g=(f=(r=this.xrBinding)==null?void 0:r.getCameraImage)==null?void 0:f.call(r,x))!=null?g:null;if(!z)continue;let{width:k,height:P}=x;if(!k||!P)continue;let L=await X(this.gl,z,k,P);if(!L)continue;let M=B(R.projectionMatrix,{width:L.width,height:L.height,x:0,y:0});if(M){(C=(v=this.options).onFrameCaptured)==null||C.call(v,L),(b=(I=this.options).onCameraIntrinsics)==null||b.call(I,M),this.trackerSpace=G(R.transform.matrix);let h=await this.options.client.localizeWithFrame(L,M);h!=null&&h.localizeData&&((D=(y=this.options).onPoseResult)==null||D.call(y,{poseFound:h.localizeData.poseFound,position:h.localizeData.position,rotation:h.localizeData.rotation,mapIds:h.localizeData.mapIds,confidence:h.localizeData.confidence})),a({result:h});return}}d+1>=q?a({result:null,failureReason:"Camera image not available. The device or browser may not support camera access in AR."}):o(c+1,d+1);}catch(S){s(S);}});};o(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{
|
|
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 B={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={...B,...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(d=>{o.append("hintMapCodes",d);}),this.config.hintPosition&&o.append("hintPosition",this.config.hintPosition);let n;if(this.config.passGeoPose){let d=await this.getGeoPoseComponents();if(d){let{latitude:h,longitude:f,altitude:w}=d;n=`${h},${f},${w}`;}}if(n&&o.append("geoHint",n),this.config.hintRadius!==void 0){let d=Number(this.config.hintRadius);if(Number.isNaN(d)||d<1||d>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",`${d}`);}"use2DFiltering"in this.config&&this.config.use2DFiltering!==void 0&&o.append("use2DFiltering",`${this.config.use2DFiltering}`);let a=(await D.post(this.endpoints.queryUrl,o,{headers:{Authorization:`Bearer ${this.accessToken}`}})).data;if(!a.poseFound)return null;let c={localizeData:a};if(i&&((m=a.mapCodes)!=null&&m.length)){let d=await this.fetchMapDetails(a.mapCodes[0]);d&&(c.mapDetails=d);}return c}};async function q(s,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(s),e,t),0,0);let a=document.createElement("canvas");a.width=o,a.height=r;let c=a.getContext("2d");return c?(c.drawImage(n,0,0,o,r),new Promise(u=>{a.toBlob(p=>u(p!=null?p:new Blob),"image/jpeg",i);})):new Blob}async function V(s,e,t,i){let o=s.createFramebuffer();if(!o)return null;let r;try{s.bindFramebuffer(s.FRAMEBUFFER,o),s.framebufferTexture2D(s.FRAMEBUFFER,s.COLOR_ATTACHMENT0,s.TEXTURE_2D,e,0),r=new Uint8Array(t*i*4),s.readPixels(0,0,t,i,s.RGBA,s.UNSIGNED_BYTE,r);}finally{s.bindFramebuffer(s.FRAMEBUFFER,null),s.deleteFramebuffer(o);}let n=new Uint8ClampedArray(r.length);for(let p=0;p<i;p+=1){let m=p*t*4,d=(i-p-1)*t*4;n.set(r.subarray(m,m+t*4),d);}let l=Math.min(1,1280/Math.max(t,i)),a=Math.round(t*l),c=Math.round(i*l),u=await q(n.buffer,t,i,.7,a,c);return u.size?{blob:u,width:a,height:c}:null}function _(s,e){let t=s,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(s){return new Float32Array(s)}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,a,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 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),(c=(a=this.options).onSessionStart)==null||c.call(a),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((a,c)=>this.xrLoop(a,c));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,a,c,u,p,m,d,h,f,w,C,M,b;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((a=this.options.confidenceThreshold)!=null?a:.5,K));(u=(c=this.options).onLocalizationInit)==null||u.call(c),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||((d=i.localizeData.confidence)!=null?d: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 (b=(M=this.options).onLocalizationFailure)==null||b.call(M,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(),a=(c,u)=>{e.requestAnimationFrame(async(p,m)=>{var d,h,f,w,C,M,b,R,z;try{let T=Date.now()-l,I=m.getViewerPose(t);if(!I){if(T>=i||c+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 a(c+1,u);return}let A=I.views;for(let L of A){let x=L.camera;if(!x)continue;let F=(f=(h=(d=this.xrBinding)==null?void 0:d.getCameraImage)==null?void 0:h.call(d,x))!=null?f:null;if(!F)continue;let{width:S,height:U}=x;if(!S||!U)continue;let H=await V(this.gl,F,S,U);if(!H)continue;let k=_(L.projectionMatrix,{width:H.width,height:H.height,x:0,y:0});if(k){(C=(w=this.options).onFrameCaptured)==null||C.call(w,H),(b=(M=this.options).onCameraIntrinsics)==null||b.call(M,k),this.trackerSpace=O(L.transform.matrix);let y=await this.options.client.localizeWithFrame(H,k);y!=null&&y.localizeData&&((z=(R=this.options).onPoseResult)==null||z.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."}):a(c+1,u+1);}catch(T){n(T);}});};a(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
|
-
`,
|
|
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
|
|
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};
|
package/dist/three/index.d.ts
CHANGED
|
@@ -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;
|
package/dist/three/index.js
CHANGED
|
@@ -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(a,e,t,i=.8,r=e,o=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=r,s.height=o;let c=s.getContext("2d");return c?(c.drawImage(n,0,0,r,o),new Promise(d=>{s.toBlob(u=>d(u!=null?u:new Blob),"image/jpeg",i);})):new Blob}async function N(a,e,t,i){let r=a.createFramebuffer();if(!r)return null;let o;try{a.bindFramebuffer(a.FRAMEBUFFER,r),a.framebufferTexture2D(a.FRAMEBUFFER,a.COLOR_ATTACHMENT0,a.TEXTURE_2D,e,0),o=new Uint8Array(t*i*4),a.readPixels(0,0,t,i,a.RGBA,a.UNSIGNED_BYTE,o);}finally{a.bindFramebuffer(a.FRAMEBUFFER,null),a.deleteFramebuffer(r);}let n=new Uint8ClampedArray(o.length);for(let u=0;u<i;u+=1){let h=u*t*4,m=(i-u-1)*t*4;n.set(o.subarray(h,h+t*4),m);}let l=Math.min(1,1280/Math.max(t,i)),s=Math.round(t*l),c=Math.round(i*l),d=await O(n.buffer,t,i,.7,s,c);return d.size?{blob:d,width:s,height:c}:null}function V(a,e){let t=a,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 k(a){return new Float32Array(a)}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 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")),(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 o=r.views[0],n=this.baseLayer.getViewport(o);(l=this.xrFrameHandler)==null||l.call(this,{time:e,frame:t,pose:r,view:o,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 w=await this.captureFrame();i=w.result,r=w.failureReason;}catch(w){return (h=(u=this.options).onError)==null||h.call(u,w),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((o,n)=>{let l=Date.now(),s=(c,d)=>{e.requestAnimationFrame(async(u,h)=>{var m,p,E,L,y,T,C,w,z;try{let x=Date.now()-l,F=h.getViewerPose(t);if(!F){if(x>=i||c+1>=$){let b=(i/1e3).toFixed(1);o({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:B}=M;if(!H||!B)continue;let G=await N(this.gl,S,H,B);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 R=await this.options.client.localizeWithFrame(G,P);R!=null&&R.localizeData&&((z=(w=this.options).onPoseResult)==null||z.call(w,{poseFound:R.localizeData.poseFound,position:R.localizeData.position,rotation:R.localizeData.rotation,mapIds:R.localizeData.mapIds,confidence:R.localizeData.confidence})),o({result:R});return}}d+1>=Q?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(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
|
-
`,
|
|
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
|
|
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.
|
|
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
|
}
|