@multisetai/vps 1.1.0 → 2.1.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 +276 -347
- package/dist/core/index.d.ts +106 -46
- package/dist/core/index.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/three/index.d.ts +70 -0
- package/dist/three/index.js +93 -0
- package/package.json +5 -5
- package/dist/webxr/index.d.ts +0 -52
- package/dist/webxr/index.js +0 -93
package/dist/core/index.d.ts
CHANGED
|
@@ -126,52 +126,20 @@ interface ILocalizeAndMapDetails {
|
|
|
126
126
|
mapDetails?: IGetMapsDetailsResponse;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
type IMultisetPublicConfig = Omit<IMultisetSdkConfig, 'clientId' | 'clientSecret'>;
|
|
131
|
-
interface IMultisetSdkConfigBase {
|
|
129
|
+
interface IMultisetClientConfigBase {
|
|
132
130
|
clientId: string;
|
|
133
131
|
clientSecret: string;
|
|
134
132
|
/** Map or map-set code used for localization. */
|
|
135
133
|
code: string;
|
|
136
134
|
endpoints?: Partial<IMultisetSdkEndpoints>;
|
|
137
|
-
/** If true, show the mesh in the AR session. Default is false. */
|
|
138
|
-
showMesh?: boolean;
|
|
139
|
-
/** If true, show the coordinate gizmo when a pose is found. Defaults to true. */
|
|
140
|
-
showGizmo?: boolean;
|
|
141
|
-
/** If true, automatically start a localization run when the AR session starts. */
|
|
142
|
-
autoLocalize?: boolean;
|
|
143
|
-
/** If true, automatically re-localize when tracking is lost and then recovered. */
|
|
144
|
-
relocalization?: boolean;
|
|
145
|
-
/** When enabled, only accept a localization result if confidence >= confidenceThreshold. */
|
|
146
|
-
confidenceCheck?: boolean;
|
|
147
|
-
/** Minimum confidence (0.2–0.8) required when confidenceCheck is enabled. */
|
|
148
|
-
confidenceThreshold?: number;
|
|
149
135
|
/** Convert localization result to geographic coordinates. */
|
|
150
136
|
convertToGeoCoordinates?: boolean;
|
|
151
|
-
/** Override handedness flag sent to
|
|
137
|
+
/** Override handedness flag sent to the query endpoint. Defaults to true. */
|
|
152
138
|
isRightHanded?: boolean;
|
|
153
139
|
/** Optional position hint in local coordinates "x,y,z". */
|
|
154
140
|
hintPosition?: string;
|
|
155
|
-
/** Optional spatial filter radius in
|
|
141
|
+
/** Optional spatial filter radius in metres (1–100). */
|
|
156
142
|
hintRadius?: number | string;
|
|
157
|
-
/** Max time in ms to wait for a valid viewer pose before failing. Default 10000. */
|
|
158
|
-
localizationTrackingTimeoutMs?: number;
|
|
159
|
-
/** Invoked at the start of a localization run. */
|
|
160
|
-
onLocalizationInit?: () => void;
|
|
161
|
-
/** Invoked after a successful localization that meets confidence criteria (if enabled). */
|
|
162
|
-
onLocalizationSuccess?: (result: ILocalizeAndMapDetails) => void;
|
|
163
|
-
/** Invoked when all attempts fail or the best result is below the confidence threshold. */
|
|
164
|
-
onLocalizationFailure?: (reason?: string) => void;
|
|
165
|
-
/** Called after a successful authorization with the access token. */
|
|
166
|
-
onAuthorize?: (token: string) => void;
|
|
167
|
-
/** Called whenever a camera frame is captured and sent for localization. */
|
|
168
|
-
onFrameCaptured?: (payload: IFrameCaptureEvent) => void;
|
|
169
|
-
/** Called with the camera intrinsics used for a localization request. */
|
|
170
|
-
onCameraIntrinsics?: (intrinsics: ICameraIntrinsicsEvent) => void;
|
|
171
|
-
/** Called with the raw pose/localization result returned by the backend. */
|
|
172
|
-
onPoseResult?: (payload: IPoseResultEvent) => void;
|
|
173
|
-
/** Called when any error occurs during authorization or localization. */
|
|
174
|
-
onError?: (error: unknown) => void;
|
|
175
143
|
}
|
|
176
144
|
/**
|
|
177
145
|
* Keeps passGeoPose and use2DFiltering in sync: use2DFiltering is only
|
|
@@ -185,16 +153,16 @@ type IGeoFilterConfig = {
|
|
|
185
153
|
use2DFiltering?: boolean;
|
|
186
154
|
};
|
|
187
155
|
/** Config when targeting a single map. hintMapCodes is not applicable. */
|
|
188
|
-
type
|
|
156
|
+
type IMultisetClientMapConfig = IMultisetClientConfigBase & IGeoFilterConfig & {
|
|
189
157
|
mapType: 'map';
|
|
190
158
|
hintMapCodes?: never;
|
|
191
159
|
};
|
|
192
160
|
/** Config when targeting a map set. hintMapCodes can narrow the candidate maps. */
|
|
193
|
-
type
|
|
161
|
+
type IMultisetClientMapSetConfig = IMultisetClientConfigBase & IGeoFilterConfig & {
|
|
194
162
|
mapType: 'map-set';
|
|
195
163
|
hintMapCodes?: string[];
|
|
196
164
|
};
|
|
197
|
-
type
|
|
165
|
+
type IMultisetClientConfig = IMultisetClientMapConfig | IMultisetClientMapSetConfig;
|
|
198
166
|
interface IMultisetSdkEndpoints {
|
|
199
167
|
authUrl: string;
|
|
200
168
|
queryUrl: string;
|
|
@@ -243,17 +211,109 @@ declare class MultisetClient {
|
|
|
243
211
|
private readonly config;
|
|
244
212
|
private readonly endpoints;
|
|
245
213
|
private accessToken;
|
|
246
|
-
private mapDetailsCache;
|
|
247
|
-
constructor(input:
|
|
214
|
+
private readonly mapDetailsCache;
|
|
215
|
+
constructor(input: IMultisetClientConfig);
|
|
248
216
|
get token(): string | null;
|
|
249
|
-
getConfig(): IMultisetPublicConfig;
|
|
250
|
-
downloadFile(key: string): Promise<string>;
|
|
251
217
|
authorize(): Promise<string>;
|
|
252
|
-
|
|
253
|
-
localizeWithFrame(frame: IFrameCaptureEvent, intrinsics: ICameraIntrinsicsEvent
|
|
218
|
+
downloadFile(key: string): Promise<string>;
|
|
219
|
+
localizeWithFrame(frame: IFrameCaptureEvent, intrinsics: ICameraIntrinsicsEvent, options?: {
|
|
220
|
+
fetchMapDetails?: boolean;
|
|
221
|
+
}): Promise<ILocalizeAndMapDetails | null>;
|
|
222
|
+
fetchMapDetails(mapCode: string): Promise<IGetMapsDetailsResponse | null>;
|
|
254
223
|
private getGeoPoseComponents;
|
|
255
224
|
private queryLocalization;
|
|
256
|
-
private fetchMapDetails;
|
|
257
225
|
}
|
|
258
226
|
|
|
259
|
-
|
|
227
|
+
/** Column-major 4×4 matrix — same memory layout as WebXR and OpenGL. */
|
|
228
|
+
type Mat4 = Float32Array;
|
|
229
|
+
|
|
230
|
+
interface IXRFrameEvent {
|
|
231
|
+
time: number;
|
|
232
|
+
frame: XRFrame;
|
|
233
|
+
pose: XRViewerPose;
|
|
234
|
+
view: XRView;
|
|
235
|
+
viewport: XRViewport;
|
|
236
|
+
baseLayer: XRWebGLLayer;
|
|
237
|
+
referenceSpace: XRReferenceSpace;
|
|
238
|
+
deltaSeconds: number;
|
|
239
|
+
}
|
|
240
|
+
interface IXRSessionOptions {
|
|
241
|
+
client: MultisetClient;
|
|
242
|
+
overlayRoot?: HTMLElement;
|
|
243
|
+
/** Trigger one localization run automatically when the AR session starts. */
|
|
244
|
+
autoLocalize?: boolean;
|
|
245
|
+
/** Re-localize whenever tracking is lost and then recovered. */
|
|
246
|
+
relocalization?: boolean;
|
|
247
|
+
/** Only accept a result if confidence >= confidenceThreshold. */
|
|
248
|
+
confidenceCheck?: boolean;
|
|
249
|
+
/** Minimum confidence (0.2–0.8). Default 0.5. */
|
|
250
|
+
confidenceThreshold?: number;
|
|
251
|
+
/** Max ms to wait for a valid viewer pose before failing. Default 10000. */
|
|
252
|
+
localizationTrackingTimeoutMs?: number;
|
|
253
|
+
/**
|
|
254
|
+
* XR reference space type. Default 'local' (always supported for immersive-ar).
|
|
255
|
+
* Use 'local-floor' for floor-relative tracking if the device supports it.
|
|
256
|
+
*/
|
|
257
|
+
referenceSpaceType?: XRReferenceSpaceType;
|
|
258
|
+
/**
|
|
259
|
+
* Scale factor applied to the XR framebuffer relative to the device's native
|
|
260
|
+
* resolution. Values < 1 reduce GPU load; values > 1 supersample.
|
|
261
|
+
* Passed directly to XRWebGLLayer as framebufferScaleFactor.
|
|
262
|
+
*/
|
|
263
|
+
framebufferScaleFactor?: number;
|
|
264
|
+
onSessionStart?: () => void;
|
|
265
|
+
onSessionEnd?: () => void;
|
|
266
|
+
onLocalizationInit?: () => void;
|
|
267
|
+
onLocalizationSuccess?: (result: ILocalizeAndMapDetails) => void;
|
|
268
|
+
onLocalizationFailure?: (reason?: string) => void;
|
|
269
|
+
onFrameCaptured?: (frame: IFrameCaptureEvent) => void;
|
|
270
|
+
onCameraIntrinsics?: (intrinsics: ICameraIntrinsicsEvent) => void;
|
|
271
|
+
onPoseResult?: (pose: IPoseResultEvent) => void;
|
|
272
|
+
onError?: (error: unknown) => void;
|
|
273
|
+
/** Called when the WebGL context is lost. The active session is ended automatically. */
|
|
274
|
+
onContextLost?: () => void;
|
|
275
|
+
/** Called when the WebGL context is restored. The user may restart the session. */
|
|
276
|
+
onContextRestored?: () => void;
|
|
277
|
+
}
|
|
278
|
+
declare class XRSessionManager {
|
|
279
|
+
private readonly gl;
|
|
280
|
+
private readonly options;
|
|
281
|
+
private session;
|
|
282
|
+
private referenceSpace;
|
|
283
|
+
private baseLayer;
|
|
284
|
+
private xrBinding;
|
|
285
|
+
private lastFrameTime;
|
|
286
|
+
private trackingLossFrames;
|
|
287
|
+
private hadTrackingLoss;
|
|
288
|
+
private _isLocalizing;
|
|
289
|
+
private trackerSpace;
|
|
290
|
+
private arButton;
|
|
291
|
+
private xrFrameHandler;
|
|
292
|
+
private adapterResultHandler;
|
|
293
|
+
private adapterSessionStartHandler;
|
|
294
|
+
private adapterSessionEndHandler;
|
|
295
|
+
constructor(gl: WebGL2RenderingContext, options: IXRSessionOptions);
|
|
296
|
+
/** Returns true if the browser supports immersive-ar WebXR sessions. */
|
|
297
|
+
static isSupported(): Promise<boolean>;
|
|
298
|
+
setXRFrameHandler(handler: (event: IXRFrameEvent) => void): void;
|
|
299
|
+
setAdapterResultHandler(handler: (result: ILocalizeAndMapDetails, trackerSpace: Mat4) => void): void;
|
|
300
|
+
setAdapterSessionHandlers(onStart: () => void, onEnd: () => void): void;
|
|
301
|
+
getClient(): MultisetClient;
|
|
302
|
+
getOverlayRoot(): HTMLElement | undefined;
|
|
303
|
+
isActive(): boolean;
|
|
304
|
+
get isLocalizing(): boolean;
|
|
305
|
+
stopSession(): void;
|
|
306
|
+
getBaseLayer(): XRWebGLLayer | null;
|
|
307
|
+
createButton(): HTMLButtonElement;
|
|
308
|
+
startSession(): Promise<void>;
|
|
309
|
+
private initSession;
|
|
310
|
+
private handleSessionEnd;
|
|
311
|
+
private readonly handleContextLost;
|
|
312
|
+
private readonly handleContextRestored;
|
|
313
|
+
private xrLoop;
|
|
314
|
+
localizeFrame(): Promise<ILocalizeAndMapDetails | null>;
|
|
315
|
+
private captureFrame;
|
|
316
|
+
dispose(): void;
|
|
317
|
+
}
|
|
318
|
+
|
|
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 };
|
package/dist/core/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import d from'axios';var g={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 m=class{constructor(i){this.accessToken=null;this.mapDetailsCache={};let{clientId:t,clientSecret:e,...o}=i;this.credentials={clientId:t,clientSecret:e},this.config=o,this.endpoints={...g,...o.endpoints};}get token(){return this.accessToken}getConfig(){return this.config}async downloadFile(i){if(!this.accessToken||!i)return "";try{let t=await d.get(`${this.endpoints.fileDownloadUrl}?key=${encodeURIComponent(i)}`,{headers:{Authorization:`Bearer ${this.accessToken}`}});return t.status===200?t.data.url:""}catch(t){return this.handleError(t),""}}async authorize(){var i,t,e,o,a;try{let s=await d.post(this.endpoints.authUrl,{},{auth:{username:this.credentials.clientId,password:this.credentials.clientSecret}}),r=(e=(i=s.data)==null?void 0:i.token)!=null?e:(t=s.data)==null?void 0:t.access_token;if(!r)throw new Error("Authorization succeeded but no token was returned.");return this.accessToken=r,(a=(o=this.config).onAuthorize)==null||a.call(o,r),r}catch(s){throw this.handleError(s),s}}handleError(i){var t,e;(e=(t=this.config).onError)==null||e.call(t,i);}async localizeWithFrame(i,t){var o,a,s,r,p,n,l;if(!this.accessToken)throw new Error("Access token is missing. Call authorize() first.");(a=(o=this.config).onFrameCaptured)==null||a.call(o,i),(r=(s=this.config).onCameraIntrinsics)==null||r.call(s,t);let e=await this.queryLocalization(i,t);return (p=e==null?void 0:e.localizeData)!=null&&p.poseFound&&((l=(n=this.config).onPoseResult)==null||l.call(n,e.localizeData)),e}async getGeoPoseComponents(){if(typeof navigator=="undefined"||!("geolocation"in navigator)||!navigator.geolocation)return null;try{let i=await new Promise((s,r)=>{navigator.geolocation.getCurrentPosition(s,r,{enableHighAccuracy:!0,timeout:1e4,maximumAge:0});}),{latitude:t,longitude:e,altitude:o}=i.coords,a=typeof o=="number"&&!Number.isNaN(o)?o:0;return {latitude:t,longitude:e,altitude:a}}catch{return null}}async queryLocalization(i,t){var s,r,p;let e=new FormData;this.config.mapType==="map"?e.append("mapCode",this.config.code):e.append("mapSetCode",this.config.code);let o=(s=this.config.isRightHanded)!=null?s:true;if(e.append("isRightHanded",`${o}`),e.append("fx",`${t.fx}`),e.append("fy",`${t.fy}`),e.append("px",`${t.px}`),e.append("py",`${t.py}`),e.append("width",`${i.width}`),e.append("height",`${i.height}`),e.append("queryImage",i.blob),this.config.convertToGeoCoordinates!==void 0){let n=this.config.convertToGeoCoordinates;e.append("convertToGeoCoordinates",`${n}`);}(r=this.config.hintMapCodes)!=null&&r.length&&this.config.hintMapCodes.forEach(n=>{e.append("hintMapCodes",n);}),this.config.hintPosition&&e.append("hintPosition",this.config.hintPosition);let a;if(this.config.passGeoPose){let n=await this.getGeoPoseComponents();if(n){let{latitude:l,longitude:c,altitude:u}=n;a=`${l},${c},${u}`;}}if(a&&e.append("geoHint",a),this.config.hintRadius!==void 0){let n=Number(this.config.hintRadius);if(Number.isNaN(n)||n<1||n>100)throw new Error("hintRadius must be a number between 1 and 100.");if(!this.config.hintPosition&&!a)throw new Error("hintRadius requires hintPosition, or passGeoPose with available geolocation to generate geoHint.");e.append("hintRadius",`${n}`);}this.config.use2DFiltering!==void 0&&e.append("use2DFiltering",`${this.config.use2DFiltering}`);try{let l=(await d.post(this.endpoints.queryUrl,e,{headers:{Authorization:`Bearer ${this.accessToken}`}})).data;if(!l.poseFound)return null;let c={localizeData:l};if(this.config.showMesh&&((p=l.mapCodes)!=null&&p.length)){let u=l.mapCodes[0],f=this.mapDetailsCache[u];if(f)c.mapDetails=f;else {let h=await this.fetchMapDetails(u);h&&(this.mapDetailsCache[u]=h,c.mapDetails=h);}}return c}catch(n){return this.handleError(n),null}}async fetchMapDetails(i){try{return (await d.get(`${this.endpoints.mapDetailsUrl}${i}`,{headers:{Authorization:`Bearer ${this.accessToken}`}})).data}catch(t){return this.handleError(t),null}}};
|
|
2
|
-
export{
|
|
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{F as DEFAULT_ENDPOINTS,w as MultisetClient,T as XRSessionManager};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { DEFAULT_ENDPOINTS, ICameraIntrinsicsEvent, IFrameCaptureEvent, IGetMapsDetailsResponse, ILocalizeAndMapDetails, ILocalizeResponse, ILocalizeResultEvent, IMapSetMapsResponse,
|
|
2
|
-
export {
|
|
1
|
+
export { DEFAULT_ENDPOINTS, ICameraIntrinsicsEvent, IFrameCaptureEvent, IGetMapsDetailsResponse, ILocalizeAndMapDetails, ILocalizeResponse, ILocalizeResultEvent, IMapSetMapsResponse, IMultisetClientConfig, IMultisetSdkEndpoints, IPoseResultEvent, IXRFrameEvent, IXRSessionOptions, MapType, MultisetClient, XRSessionManager } from './core/index.js';
|
|
2
|
+
export { IThreeAdapterOptions, ThreeAdapter } from './three/index.js';
|
|
3
3
|
import 'three';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import S from'axios';import*as m from'three';import {ARButton}from'three/addons/webxr/ARButton.js';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 N=class{constructor(i){this.accessToken=null;this.mapDetailsCache={};let{clientId:t,clientSecret:e,...r}=i;this.credentials={clientId:t,clientSecret:e},this.config=r,this.endpoints={...B,...r.endpoints};}get token(){return this.accessToken}getConfig(){return this.config}async downloadFile(i){if(!this.accessToken||!i)return "";try{let t=await S.get(`${this.endpoints.fileDownloadUrl}?key=${encodeURIComponent(i)}`,{headers:{Authorization:`Bearer ${this.accessToken}`}});return t.status===200?t.data.url:""}catch(t){return this.handleError(t),""}}async authorize(){var i,t,e,r,s;try{let o=await S.post(this.endpoints.authUrl,{},{auth:{username:this.credentials.clientId,password:this.credentials.clientSecret}}),a=(e=(i=o.data)==null?void 0:i.token)!=null?e:(t=o.data)==null?void 0:t.access_token;if(!a)throw new Error("Authorization succeeded but no token was returned.");return this.accessToken=a,(s=(r=this.config).onAuthorize)==null||s.call(r,a),a}catch(o){throw this.handleError(o),o}}handleError(i){var t,e;(e=(t=this.config).onError)==null||e.call(t,i);}async localizeWithFrame(i,t){var r,s,o,a,l,n,c;if(!this.accessToken)throw new Error("Access token is missing. Call authorize() first.");(s=(r=this.config).onFrameCaptured)==null||s.call(r,i),(a=(o=this.config).onCameraIntrinsics)==null||a.call(o,t);let e=await this.queryLocalization(i,t);return (l=e==null?void 0:e.localizeData)!=null&&l.poseFound&&((c=(n=this.config).onPoseResult)==null||c.call(n,e.localizeData)),e}async getGeoPoseComponents(){if(typeof navigator=="undefined"||!("geolocation"in navigator)||!navigator.geolocation)return null;try{let i=await new Promise((o,a)=>{navigator.geolocation.getCurrentPosition(o,a,{enableHighAccuracy:!0,timeout:1e4,maximumAge:0});}),{latitude:t,longitude:e,altitude:r}=i.coords,s=typeof r=="number"&&!Number.isNaN(r)?r:0;return {latitude:t,longitude:e,altitude:s}}catch{return null}}async queryLocalization(i,t){var o,a,l;let e=new FormData;this.config.mapType==="map"?e.append("mapCode",this.config.code):e.append("mapSetCode",this.config.code);let r=(o=this.config.isRightHanded)!=null?o:true;if(e.append("isRightHanded",`${r}`),e.append("fx",`${t.fx}`),e.append("fy",`${t.fy}`),e.append("px",`${t.px}`),e.append("py",`${t.py}`),e.append("width",`${i.width}`),e.append("height",`${i.height}`),e.append("queryImage",i.blob),this.config.convertToGeoCoordinates!==void 0){let n=this.config.convertToGeoCoordinates;e.append("convertToGeoCoordinates",`${n}`);}(a=this.config.hintMapCodes)!=null&&a.length&&this.config.hintMapCodes.forEach(n=>{e.append("hintMapCodes",n);}),this.config.hintPosition&&e.append("hintPosition",this.config.hintPosition);let s;if(this.config.passGeoPose){let n=await this.getGeoPoseComponents();if(n){let{latitude:c,longitude:u,altitude:g}=n;s=`${c},${u},${g}`;}}if(s&&e.append("geoHint",s),this.config.hintRadius!==void 0){let n=Number(this.config.hintRadius);if(Number.isNaN(n)||n<1||n>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.");e.append("hintRadius",`${n}`);}this.config.use2DFiltering!==void 0&&e.append("use2DFiltering",`${this.config.use2DFiltering}`);try{let c=(await S.post(this.endpoints.queryUrl,e,{headers:{Authorization:`Bearer ${this.accessToken}`}})).data;if(!c.poseFound)return null;let u={localizeData:c};if(this.config.showMesh&&((l=c.mapCodes)!=null&&l.length)){let g=c.mapCodes[0],h=this.mapDetailsCache[g];if(h)u.mapDetails=h;else {let f=await this.fetchMapDetails(g);f&&(this.mapDetailsCache[g]=f,u.mapDetails=f);}}return u}catch(n){return this.handleError(n),null}}async fetchMapDetails(i){try{return (await S.get(`${this.endpoints.mapDetailsUrl}${i}`,{headers:{Authorization:`Bearer ${this.accessToken}`}})).data}catch(t){return this.handleError(t),null}}};function _(d,i){let t=d,e=(1-t[8])*i.width/2+i.x,r=(1-t[9])*i.height/2+i.y,s=i.width/2*t[0],o=i.height/2*t[5];return {fx:s,fy:o,px:e,py:r,width:i.width,height:i.height}}async function Q(d,i,t,e=.8){let r=document.createElement("canvas");r.width=i,r.height=t;let s=r.getContext("2d");if(!s)return new Blob;let o=new ImageData(new Uint8ClampedArray(d),i,t);return s.putImageData(o,0,0),new Promise(a=>{r.toBlob(l=>a(l!=null?l:new Blob),"image/jpeg",e);})}async function $(d,i,t,e){let r=d.getContext();if(!r)return null;let s=r.createFramebuffer();if(!s)return null;let o;try{r.bindFramebuffer(r.FRAMEBUFFER,s),r.framebufferTexture2D(r.FRAMEBUFFER,r.COLOR_ATTACHMENT0,r.TEXTURE_2D,i,0),o=new Uint8Array(t*e*4),r.readPixels(0,0,t,e,r.RGBA,r.UNSIGNED_BYTE,o);}finally{r.bindFramebuffer(r.FRAMEBUFFER,null),r.deleteFramebuffer(s);}let a=new Uint8ClampedArray(o.length);for(let n=0;n<e;n+=1){let c=n*t*4,u=(e-n-1)*t*4;a.set(o.subarray(c,c+t*4),u);}let l=await Q(a.buffer,t,e,.7);return l.size?{blob:l,width:t,height:e}:null}var F=class{constructor(i){this.renderer=new m.WebGLRenderer({canvas:i,antialias:true,alpha:true}),this.renderer.setSize(window.innerWidth,window.innerHeight),this.renderer.setPixelRatio(window.devicePixelRatio),this.renderer.xr.enabled=true,this.camera=new m.PerspectiveCamera(45,window.innerWidth/window.innerHeight,.2,1e4),this.scene=new m.Scene,this.setupLights();}setupLights(){let i=new m.HemisphereLight(16777215,16314623,1);i.position.set(.5,2,.25),this.scene.add(i);let t=new m.DirectionalLight("#7B2CBF");t.position.set(0,2,0),this.scene.add(t);}getScene(){return this.scene}getCamera(){return this.camera}getRenderer(){return this.renderer}resize(){this.camera.aspect=window.innerWidth/window.innerHeight,this.camera.updateProjectionMatrix(),this.renderer.setSize(window.innerWidth,window.innerHeight);}dispose(){this.renderer.dispose(),this.scene=null,this.camera=null,this.renderer=null;}};var J=`
|
|
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=`
|
|
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
|
+
`,te=`
|
|
13
13
|
uniform vec3 uColor;
|
|
14
14
|
uniform float uOpacity;
|
|
15
15
|
|
|
@@ -90,4 +90,4 @@ void main() {
|
|
|
90
90
|
|
|
91
91
|
gl_FragColor = vec4(finalColor, alphaWithGlow);
|
|
92
92
|
}
|
|
93
|
-
`;function q(d={}){var C,p,v,w,E,T,R,x,y,G,H,L,z,I;let i=(C=d.color)!=null?C:"#7B2CBF",t=(p=d.opacity)!=null?p:150/255,e=(v=d.gridColor)!=null?v:"#ffeb3b",r=(w=d.gridScale)!=null?w:2,s=(E=d.gridLineWidth)!=null?E:.02,o=(T=d.showGrid)!=null?T:1,a=(R=d.center)!=null?R:new m.Vector3(0,0,0),l=(x=d.radius)!=null?x:1,n=(y=d.progress)!=null?y:1,c=(G=d.glowColor)!=null?G:"#FF3A00",u=(H=d.glowWidth)!=null?H:.05,g=(L=d.glowIntensity)!=null?L:2,h=(z=d.lightColor)!=null?z:"#ffffff",f=(I=d.lightDirection)!=null?I:new m.Vector3(.3,1,.3).normalize();return new m.ShaderMaterial({vertexShader:J,fragmentShader:Y,transparent:true,side:m.DoubleSide,uniforms:{uColor:{value:typeof i=="string"?new m.Color(i):i},uOpacity:{value:t},uGridColor:{value:typeof e=="string"?new m.Color(e):e},uGridSize:{value:r},uGridLineWidth:{value:s},uShowGrid:{value:o},uCenter:{value:a.clone()},uRadius:{value:l},uProgress:{value:n},uGlowColor:{value:typeof c=="string"?new m.Color(c):c},uGlowWidth:{value:u},uGlowIntensity:{value:g},uLightColor:{value:typeof h=="string"?new m.Color(h):h},uLightDirection:{value:f.clone()}}})}var j=50,D=class{constructor(i,t,e,r){this.scene=i;this.client=t;this.renderer=e;this.camera=r;this.gizmoControl=null;this.gizmoHelper=null;this.meshMaterial=null;this.localCenter=new m.Vector3;this.localRadius=0;this.radialDuration=4;this.radialRepeatInterval=20;this.idleTimeAfterReveal=0;this.lastViewerWorldPosition=new m.Vector3;this.meshGroup=new m.Group,this.meshGroup.visible=false,this.scene.add(this.meshGroup),this.dracoLoader=new DRACOLoader,this.dracoLoader.setDecoderPath("/draco/"),this.gltfLoader=new GLTFLoader,this.gltfLoader.setDRACOLoader(this.dracoLoader);}getMeshGroup(){return this.meshGroup}async ensureGizmoLoaded(){if(this.client.getConfig().showGizmo!==false){if(!this.gizmoControl){let t=new TransformControls(this.camera,this.renderer.domElement);t.setMode("translate"),t.setSpace("local"),t.enabled=false;let e=t.getHelper();this.scene.add(e),t.attach(this.meshGroup),this.gizmoControl=t,this.gizmoHelper=e;}this.gizmoHelper.visible=true;}}async ensureMeshLoaded(i){var r,s;if(this.scene.getObjectByName(i._id))return;let t=(s=(r=i.mapMesh)==null?void 0:r.rawMesh)==null?void 0:s.meshLink;if(!t)return;let e=await this.client.downloadFile(t);e&&await new Promise((o,a)=>{this.gltfLoader.load(e,l=>{let n=new m.Box3().setFromObject(l.scene),u=n.getSize(new m.Vector3).length(),g=n.getCenter(new m.Vector3);u>j&&(u=j),this.localCenter.copy(g),this.localRadius=Math.max(u/2,.001);let h=q({color:"#7B2CBF",opacity:.58,gridColor:"#ffeb3b",gridScale:2,gridLineWidth:.02,showGrid:1,center:this.localCenter.clone(),radius:this.localRadius,progress:0,glowColor:"#FF3A00",glowWidth:.05,glowIntensity:2});this.meshMaterial=h,l.scene.traverse(f=>{f.isMesh&&(f.material=h);}),l.scene.name=i._id,this.meshGroup.add(l.scene),o();},void 0,l=>{a(l instanceof Error?l:new Error(String(l)));});});}applyMeshTransform(i,t){let e=i.localizeData,r=new m.Vector3(e.position.x,e.position.y,e.position.z),s=new m.Quaternion(e.rotation.x,e.rotation.y,e.rotation.z,e.rotation.w),o=new m.Matrix4;o.compose(r,s,new m.Vector3(1,1,1));let a=o.clone().invert(),l=new m.Matrix4;l.multiplyMatrices(t,a);let n=new m.Vector3,c=new m.Quaternion,u=new m.Vector3;l.decompose(n,c,u),this.meshGroup.position.copy(n),this.meshGroup.quaternion.copy(c),this.meshGroup.scale.set(1,1,1),this.meshGroup.visible=true,this.meshGroup.updateMatrix(),this.meshMaterial&&this.meshMaterial.uniforms.uCenter.value.copy(this.localCenter).applyMatrix4(this.meshGroup.matrixWorld);}update(i,t){var o;if(!this.meshMaterial)return;t&&this.lastViewerWorldPosition.copy(t);let e=this.meshMaterial.uniforms;if(!e.uProgress)return;let r=(o=e.uProgress.value)!=null?o:0;if(r===0&&e.uCenter&&this.lastViewerWorldPosition.lengthSq()>0&&e.uCenter.value.copy(this.lastViewerWorldPosition),r>=1){this.idleTimeAfterReveal+=i,this.idleTimeAfterReveal>=this.radialRepeatInterval&&(e.uProgress.value=0,this.idleTimeAfterReveal=0);return}this.idleTimeAfterReveal=0;let s=Math.min(r+i/this.radialDuration,1);e.uProgress.value=s;}dispose(){this.meshMaterial=null,this.meshGroup.traverse(i=>{let t=i;t.isMesh&&(t.geometry.dispose(),Array.isArray(t.material)?t.material.forEach(e=>e.dispose()):t.material.dispose());}),this.scene.remove(this.meshGroup),this.dracoLoader.dispose(),this.gizmoHelper&&this.scene.remove(this.gizmoHelper),this.gizmoControl&&this.gizmoControl.dispose();}};var P=class{constructor(i,t,e,r){this.meshVisualizer=new D(i,t,e,r);}async ensureGizmoLoaded(){await this.meshVisualizer.ensureGizmoLoaded();}async ensureMeshLoaded(i){await this.meshVisualizer.ensureMeshLoaded(i);}applyMeshTransform(i,t){this.meshVisualizer.applyMeshTransform(i,t);}update(i,t){this.meshVisualizer.update(i,t);}dispose(){this.meshVisualizer.dispose();}};var re=.2,oe=.8,ne=60,se=1e4,ae=300,le=10,V=class{constructor(i){this.options=i;this.experience=null;this.world=null;this.arButton=null;this.resizeHandler=null;this.isSessionActive=false;this.trackingLossFrames=0;this.hadTrackingLoss=false;this.isLocalizing=false;this.trackerSpace=null;this.xrWebGLBinding=null;}async initialize(i){var g,h,f,C;if(this.experience)return this.arButton;if(!window.isSecureContext)throw new Error("WebXR requires a secure context (HTTPS).");let t=(g=this.options.canvas)!=null?g:document.createElement("canvas");this.experience=new F(t);let e=this.experience.getRenderer(),r=this.experience.getCamera(),s=this.experience.getScene();this.world=new P(s,this.options.client,e,r),e.xr.addEventListener("sessionstart",()=>{var w,E;this.isSessionActive=true;let p=e.xr.getSession();if(p){let T=e.getContext(),R=XRWebGLBinding;this.xrWebGLBinding=new R(p,T);}(E=(w=this.options).onSessionStart)==null||E.call(w),this.options.client.getConfig().autoLocalize&&(p?p.requestAnimationFrame(()=>{this.localizeFrame();}):this.localizeFrame());}),e.xr.addEventListener("sessionend",()=>{var p,v;this.isSessionActive=false,this.xrWebGLBinding=null,(v=(p=this.options).onSessionEnd)==null||v.call(p);});let o=0,a=(p,v)=>{let w=o===0?0:(p-o)/1e3;o=p;let E=null;if(v){let R=e.xr.getReferenceSpace();if(R){let x=v.getViewerPose(R);if(x){let y=x.transform.position;E=new m.Vector3(y.x,y.y,y.z);}}}if(this.world&&w>0&&this.world.update(w,E),e.render(s,r),this.options.client.getConfig().relocalization&&v&&!this.isLocalizing){let R=e.xr.getReferenceSpace();R&&(v.getViewerPose(R)?this.hadTrackingLoss?(this.hadTrackingLoss=false,this.trackingLossFrames=0,this.localizeFrame()):this.trackingLossFrames=0:(this.trackingLossFrames+=1,this.trackingLossFrames>=ne&&(this.hadTrackingLoss=true)));}};e.setAnimationLoop(a);let l=()=>{var p;(p=this.experience)==null||p.resize();};window.addEventListener("resize",l);let n=(h=this.options.overlayRoot)!=null?h:document.body,c=ARButton.createButton(e,{requiredFeatures:["camera-access","dom-overlay"],domOverlay:{root:n}}),u=i&&i instanceof HTMLElement?i:this.options.buttonContainer&&this.options.buttonContainer instanceof HTMLElement?this.options.buttonContainer:n;return u.contains(c)||u.appendChild(c),this.arButton=c,this.resizeHandler=l,(C=(f=this.options).onARButtonCreated)==null||C.call(f,c),c}getScene(){if(!this.experience)throw new Error("Scene: WebXR controller has not been initialized.");return this.experience.getScene()}getCamera(){if(!this.experience)throw new Error("Camera: WebXR controller has not been initialized.");return this.experience.getCamera()}getRenderer(){if(!this.experience)throw new Error("Renderer: WebXR controller has not been initialized.");return this.experience.getRenderer()}hasActiveSession(){var i;return this.isSessionActive&&((i=this.experience)==null?void 0:i.getRenderer().xr.isPresenting)===true}async localizeFrame(){var c,u,g,h,f,C,p,v,w;if(!this.experience)throw new Error("WebXR: WebXR controller has not been initialized.");if(!((u=(c=this.experience.getRenderer().xr).getSession)==null?void 0:u.call(c)))throw new Error("WebXR Session: No active WebXR session. Start AR before calling localizeFrame().");let e=this.options.client.getConfig(),r=(g=e.confidenceCheck)!=null?g:false,s=Math.max(re,Math.min((h=e.confidenceThreshold)!=null?h:.5,oe));(f=e.onLocalizationInit)==null||f.call(e),this.isLocalizing=true;let o=null,a;try{let E=await this.captureFrame();o=E.result,a=E.failureReason;}finally{this.isLocalizing=false;}if(o&&(!r||((C=o.localizeData.confidence)!=null?C:0)>=s)&&o){if((p=e.onLocalizationSuccess)==null||p.call(e,o),this.world&&this.trackerSpace){let E=e.showMesh&&!!o.mapDetails,T=e.showGizmo!==false;if(E||T)try{await this.world.ensureGizmoLoaded(),E&&await this.world.ensureMeshLoaded(o.mapDetails),this.world.applyMeshTransform(o,this.trackerSpace);}catch{}}return o}let n=a!=null?a:o?r?`Best confidence ${(v=o.localizeData.confidence)!=null?v:0} below threshold ${s}.`:void 0:"All attempts failed to produce a pose.";return (w=e.onLocalizationFailure)==null||w.call(e,n),null}async captureFrame(){var a,l,n;if(!this.experience)throw new Error("WebXR: WebXR controller has not been initialized.");let i=this.experience.getRenderer(),t=(l=(a=i.xr).getSession)==null?void 0:l.call(a);if(!t)throw new Error("WebXR Session: No active WebXR session. Start AR before capturing.");let e=i.xr.getReferenceSpace();if(!e)throw new Error("WebXR Reference Space: Unable to acquire XR reference space.");let r=i.getContext(),o=(n=this.options.client.getConfig().localizationTrackingTimeoutMs)!=null?n:se;return new Promise((c,u)=>{let g=Date.now(),h=(f,C)=>{t.requestAnimationFrame(async(p,v)=>{var w,E,T,R,x;try{let y=Date.now()-g,G=v.getViewerPose(e);if(!G){if(y>=o||f+1>=ae){let L=(o/1e3).toFixed(1);c({result:null,failureReason:`Tracking unavailable: no viewer pose within ${L}s. Try moving the device or ensuring good lighting.`});}else h(f+1,C);return}let H=G.views;for(let L=0;L<H.length;L+=1){let z=H[L],I=z.camera;if(!I)continue;let U=(T=(E=(w=this.xrWebGLBinding)==null?void 0:w.getCameraImage)==null?void 0:E.call(w,I))!=null?T:null;if(!U)continue;let W=I.width,k=I.height;if(!W||!k)continue;let O=await $(i,U,W,k),X=_(z.projectionMatrix,{width:W,height:k,x:0,y:0});if(O&&X){this.trackerSpace=new m.Matrix4().fromArray(z.transform.matrix);let K=await this.options.client.localizeWithFrame(O,X);c({result:K});return}}C+1>=le?c({result:null,failureReason:"Camera image not available. The device or browser may not support camera access in AR."}):h(f+1,C+1);}catch(y){u(y);}finally{r.bindFramebuffer(r.FRAMEBUFFER,(x=(R=t.renderState.baseLayer)==null?void 0:R.framebuffer)!=null?x:null);}});};h(0,0);})}dispose(){var i,t,e,r;this.resizeHandler&&window.removeEventListener("resize",this.resizeHandler),(i=this.experience)==null||i.getRenderer().setAnimationLoop(null),(t=this.experience)==null||t.dispose(),this.experience=null,(e=this.world)==null||e.dispose(),this.world=null,this.xrWebGLBinding=null,this.trackingLossFrames=0,this.hadTrackingLoss=false,(r=this.arButton)!=null&&r.parentElement&&this.arButton.parentElement.removeChild(this.arButton),this.arButton=null;}};export{B as DEFAULT_ENDPOINTS,N as MultisetClient,V as WebxrController};
|
|
93
|
+
`;function $(s={}){var f,w,C,M,b,R,z,T,I,A,L,x,F,S;let e=(f=s.color)!=null?f:"#7B2CBF",t=(w=s.opacity)!=null?w:150/255,i=(C=s.gridColor)!=null?C:"#ffeb3b",o=(M=s.gridScale)!=null?M:2,r=(b=s.gridLineWidth)!=null?b:.02,n=(R=s.showGrid)!=null?R:1,l=(z=s.center)!=null?z:new g.Vector3(0,0,0),a=(T=s.radius)!=null?T:1,c=(I=s.progress)!=null?I:1,u=(A=s.glowColor)!=null?A:"#FF3A00",p=(L=s.glowWidth)!=null?L:.05,m=(x=s.glowIntensity)!=null?x:2,d=(F=s.lightColor)!=null?F:"#ffffff",h=(S=s.lightDirection)!=null?S:new g.Vector3(.3,1,.3).normalize();return new g.ShaderMaterial({vertexShader:ee,fragmentShader:te,transparent:true,side:g.DoubleSide,uniforms:{uColor:{value:typeof e=="string"?new g.Color(e):e},uOpacity:{value:t},uGridColor:{value:typeof i=="string"?new g.Color(i):i},uGridSize:{value:o},uGridLineWidth:{value:r},uShowGrid:{value:n},uCenter:{value:l.clone()},uRadius:{value:a},uProgress:{value:c},uGlowColor:{value:typeof u=="string"?new g.Color(u):u},uGlowWidth:{value:p},uGlowIntensity:{value:m},uLightColor:{value:typeof d=="string"?new g.Color(d):d},uLightDirection:{value:h.clone()}}})}var P=class{constructor(e,t,i,o){this.scene=e;this.client=t;this.renderer=i;this.camera=o;this.gizmoControl=null;this.gizmoHelper=null;this.meshMaterial=null;this.localCenter=new g.Vector3;this.localRadius=0;this.radialDuration=4;this.meshGroup=new g.Group,this.meshGroup.visible=false,this.scene.add(this.meshGroup),this.dracoLoader=new DRACOLoader,this.dracoLoader.setDecoderPath("/draco/"),this.gltfLoader=new GLTFLoader,this.gltfLoader.setDRACOLoader(this.dracoLoader);}getMeshGroup(){return this.meshGroup}async ensureGizmoLoaded(){if(!this.gizmoControl){let e=new TransformControls(this.camera,this.renderer.domElement);e.setMode("translate"),e.setSpace("local"),e.enabled=false;let t=e.getHelper();this.scene.add(t),e.attach(this.meshGroup),this.gizmoControl=e,this.gizmoHelper=t;}this.gizmoHelper.visible=true;}async ensureMeshLoaded(e){var o,r;if(this.scene.getObjectByName(e._id))return;let t=(r=(o=e.mapMesh)==null?void 0:o.rawMesh)==null?void 0:r.meshLink;if(!t)return;let i=await this.client.downloadFile(t);i&&await new Promise((n,l)=>{this.gltfLoader.load(i,a=>{let c=new g.Box3().setFromObject(a.scene),u=c.getSize(new g.Vector3),p=c.getCenter(new g.Vector3);this.localCenter.copy(p),this.localRadius=Math.max(u.length()/2*1.1,.001);let m=$({color:"#7B2CBF",opacity:.58,gridColor:"#ffeb3b",gridScale:2,gridLineWidth:.02,showGrid:1,center:this.localCenter.clone(),radius:this.localRadius,progress:0,glowColor:"#FF3A00",glowWidth:.05,glowIntensity:2});this.meshMaterial=m,a.scene.traverse(d=>{let h=d;h.isMesh&&(h.material=m,h.frustumCulled=false);}),a.scene.name=e._id,this.meshGroup.add(a.scene),n();},void 0,a=>{l(a instanceof Error?a:new Error(String(a)));});});}applyMeshTransform(e){let t=new g.Vector3,i=new g.Quaternion,o=new g.Vector3;e.decompose(t,i,o),this.meshGroup.position.copy(t),this.meshGroup.quaternion.copy(i),this.meshGroup.scale.set(1,1,1),this.meshGroup.visible=true,this.meshGroup.updateMatrixWorld(true),this.meshMaterial&&(this.meshMaterial.uniforms.uProgress.value=0,this.meshMaterial.uniforms.uCenter.value.copy(this.localCenter).applyMatrix4(this.meshGroup.matrixWorld));}update(e,t){if(!this.meshMaterial)return;let i=this.meshMaterial.uniforms.uProgress;i&&(i.value>=1||(i.value=Math.min(i.value+e/this.radialDuration,1)));}hideUntilNextLocalization(){this.meshGroup.visible=false,this.gizmoHelper&&(this.gizmoHelper.visible=false),this.meshMaterial&&(this.meshMaterial.uniforms.uProgress.value=0);}dispose(){this.meshMaterial=null,this.meshGroup.traverse(e=>{let t=e;t.isMesh&&(t.geometry.dispose(),Array.isArray(t.material)?t.material.forEach(i=>i.dispose()):t.material.dispose());}),this.scene.remove(this.meshGroup),this.dracoLoader.dispose(),this.gizmoHelper&&this.scene.remove(this.gizmoHelper),this.gizmoControl&&this.gizmoControl.dispose();}};var W=class{constructor(e,t,i,o){this.meshVisualizer=new P(e,t,i,o);}async ensureGizmoLoaded(){await this.meshVisualizer.ensureGizmoLoaded();}async ensureMeshLoaded(e){await this.meshVisualizer.ensureMeshLoaded(e);}applyMeshTransform(e){this.meshVisualizer.applyMeshTransform(e);}update(e,t){this.meshVisualizer.update(e,t);}hideUntilNextLocalization(){this.meshVisualizer.hideUntilNextLocalization();}dispose(){this.meshVisualizer.dispose();}};var N=class{constructor(e){this.options=e;this.world=null;this.previewRAFHandle=null;this.resizeHandler=null;this.xrRenderTarget=null;let{session:t,renderer:i,camera:o}=e;i.xr.enabled=false,o.matrixAutoUpdate=false,t.setXRFrameHandler(r=>this.onXRFrame(r)),t.setAdapterResultHandler((r,n)=>{this.handleLocalizationResult(r,n);}),t.setAdapterSessionHandlers(()=>{o.matrixAutoUpdate=false;let r=t.getBaseLayer();r&&(this.xrRenderTarget=new g.WebGLRenderTarget(r.framebufferWidth,r.framebufferHeight)),this.stopPreviewLoop();},()=>{var r,n;i.setRenderTarget(null),this.xrRenderTarget&&(i.setRenderTargetFramebuffer(this.xrRenderTarget,null),this.xrRenderTarget.dispose(),this.xrRenderTarget=null),(r=this.world)==null||r.hideUntilNextLocalization(),o.matrixAutoUpdate=true,o.position.set(0,0,0),o.quaternion.identity(),(n=this.resizeHandler)==null||n.call(this),this.startPreviewLoop();}),(e.showMesh||e.showGizmo!==false)&&(this.world=new W(e.scene,t.getClient(),i,o));}static isSupported(){return G.isSupported()}initialize(e){var l,a;let{options:t}=this,{session:i}=t,o=null;if(t.useDefaultButton!==false){o=i.createButton();let c=e instanceof HTMLElement?e:t.buttonContainer instanceof HTMLElement?t.buttonContainer:(l=i.getOverlayRoot())!=null?l:document.body;c.contains(o)||c.appendChild(o),(a=t.onButtonCreated)==null||a.call(t,o);}let{renderer:r,camera:n}=t;return this.resizeHandler=()=>{n.aspect=window.innerWidth/window.innerHeight,n.updateProjectionMatrix(),r.setSize(window.innerWidth,window.innerHeight);},window.addEventListener("resize",this.resizeHandler),this.resizeHandler(),this.startPreviewLoop(),o}isActive(){return this.options.session.isActive()}get isLocalizing(){return this.options.session.isLocalizing}startSession(){return this.options.session.startSession()}stopSession(){this.options.session.stopSession();}async localizeFrame(){return this.options.session.localizeFrame()}onXRFrame(e){var r,n;let{renderer:t,scene:i,camera:o}=this.options;if(this.xrRenderTarget&&(t.setRenderTargetFramebuffer(this.xrRenderTarget,e.baseLayer.framebuffer),t.setRenderTarget(this.xrRenderTarget)),this.syncCameraMatrices(e.view),this.world&&e.deltaSeconds>0){let l=e.pose.transform.position;this.world.update(e.deltaSeconds,new g.Vector3(l.x,l.y,l.z));}(n=(r=this.options).onXRFrame)==null||n.call(r,e),t.render(i,o);}syncCameraMatrices(e){let{camera:t}=this.options;t.projectionMatrix.fromArray(e.projectionMatrix),t.projectionMatrixInverse.copy(t.projectionMatrix).invert(),t.matrixWorld.fromArray(e.transform.matrix),t.matrixWorldInverse.fromArray(e.transform.inverse.matrix),t.matrix.copy(t.matrixWorld);}startPreviewLoop(){let{renderer:e,scene:t,camera:i}=this.options,o=()=>{this.previewRAFHandle=window.requestAnimationFrame(o),e.render(t,i);};this.previewRAFHandle=window.requestAnimationFrame(o);}stopPreviewLoop(){this.previewRAFHandle!==null&&(cancelAnimationFrame(this.previewRAFHandle),this.previewRAFHandle=null);}async handleLocalizationResult(e,t){var m,d,h;let{position:i,rotation:o}=e.localizeData,r=new g.Matrix4().compose(new g.Vector3(i.x,i.y,i.z),new g.Quaternion(o.x,o.y,o.z,o.w),new g.Vector3(1,1,1)),n=new g.Matrix4().multiplyMatrices(new g.Matrix4().fromArray(t),r.invert());if((d=(m=this.options).onLocalizationSuccess)==null||d.call(m,e,n),!this.world)return;let{showMesh:l,showGizmo:a,session:c}=this.options,u=a!==false;if(l&&((h=e.localizeData.mapCodes)!=null&&h[0]))try{let f=await c.getClient().fetchMapDetails(e.localizeData.mapCodes[0]);f&&(e.mapDetails=f);}catch{}let p=l&&!!e.mapDetails;if(!(!p&&!u))try{if(u&&await this.world.ensureGizmoLoaded(),p&&await this.world.ensureMeshLoaded(e.mapDetails),!c.isActive())return;this.world.applyMeshTransform(n);}catch{}}dispose(){var e;this.resizeHandler&&(window.removeEventListener("resize",this.resizeHandler),this.resizeHandler=null),this.stopPreviewLoop(),(e=this.world)==null||e.dispose(),this.world=null,this.options.session.dispose();}};export{B as DEFAULT_ENDPOINTS,X as MultisetClient,N as ThreeAdapter,G as XRSessionManager};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { XRSessionManager, IXRFrameEvent, ILocalizeAndMapDetails } from '../core/index.js';
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
|
|
4
|
+
interface IThreeAdapterOptions {
|
|
5
|
+
session: XRSessionManager;
|
|
6
|
+
renderer: THREE.WebGLRenderer;
|
|
7
|
+
scene: THREE.Scene;
|
|
8
|
+
camera: THREE.PerspectiveCamera;
|
|
9
|
+
/** Show the map mesh after a successful localization. Default false. */
|
|
10
|
+
showMesh?: boolean;
|
|
11
|
+
/** Show the transform gizmo after a successful localization. Default true. */
|
|
12
|
+
showGizmo?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Mount the built-in START AR / STOP AR button. Default true.
|
|
15
|
+
* Set to false to drive the session entirely via startSession() / stopSession().
|
|
16
|
+
* NOTE: startSession() must be called from within a user gesture (click/tap)
|
|
17
|
+
* handler — browser security requires it to obtain a user activation token.
|
|
18
|
+
*/
|
|
19
|
+
useDefaultButton?: boolean;
|
|
20
|
+
onButtonCreated?: (button: HTMLButtonElement) => void;
|
|
21
|
+
buttonContainer?: HTMLElement;
|
|
22
|
+
/**
|
|
23
|
+
* Called every XR frame after camera matrices are synced and the world is
|
|
24
|
+
* updated, but before the scene is rendered. Use this to update scene objects
|
|
25
|
+
* in response to the current device pose.
|
|
26
|
+
*/
|
|
27
|
+
onXRFrame?: (event: IXRFrameEvent) => void;
|
|
28
|
+
/**
|
|
29
|
+
* Called after a successful localization.
|
|
30
|
+
*
|
|
31
|
+
* `worldFromMap` is a THREE.Matrix4 that transforms any point from VPS map
|
|
32
|
+
* space into Three.js world space (the XR reference space). Use it to place
|
|
33
|
+
* scene objects at known physical locations inside the scanned map:
|
|
34
|
+
*
|
|
35
|
+
* ```ts
|
|
36
|
+
* onLocalizationSuccess: (result, worldFromMap) => {
|
|
37
|
+
* const marker = new THREE.Mesh(geometry, material);
|
|
38
|
+
* // mapPoint is a position you know from the scanned map (in metres)
|
|
39
|
+
* const mapPoint = new THREE.Vector3(1.5, 0, -2.0);
|
|
40
|
+
* marker.position.copy(mapPoint.applyMatrix4(worldFromMap));
|
|
41
|
+
* scene.add(marker);
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
onLocalizationSuccess?: (result: ILocalizeAndMapDetails, worldFromMap: THREE.Matrix4) => void;
|
|
46
|
+
}
|
|
47
|
+
declare class ThreeAdapter {
|
|
48
|
+
private readonly options;
|
|
49
|
+
/** Returns true if the browser supports immersive-ar WebXR sessions. */
|
|
50
|
+
static isSupported(): Promise<boolean>;
|
|
51
|
+
private world;
|
|
52
|
+
private previewRAFHandle;
|
|
53
|
+
private resizeHandler;
|
|
54
|
+
private xrRenderTarget;
|
|
55
|
+
constructor(options: IThreeAdapterOptions);
|
|
56
|
+
initialize(buttonContainer?: HTMLElement): HTMLButtonElement | null;
|
|
57
|
+
isActive(): boolean;
|
|
58
|
+
get isLocalizing(): boolean;
|
|
59
|
+
startSession(): Promise<void>;
|
|
60
|
+
stopSession(): void;
|
|
61
|
+
localizeFrame(): Promise<ILocalizeAndMapDetails | null>;
|
|
62
|
+
private onXRFrame;
|
|
63
|
+
private syncCameraMatrices;
|
|
64
|
+
private startPreviewLoop;
|
|
65
|
+
private stopPreviewLoop;
|
|
66
|
+
private handleLocalizationResult;
|
|
67
|
+
dispose(): void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { type IThreeAdapterOptions, ThreeAdapter };
|
|
@@ -0,0 +1,93 @@
|
|
|
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=`
|
|
2
|
+
varying vec3 vWorldPosition;
|
|
3
|
+
varying vec3 vWorldNormal;
|
|
4
|
+
|
|
5
|
+
void main() {
|
|
6
|
+
vec4 worldPos = modelMatrix * vec4(position, 1.0);
|
|
7
|
+
vWorldPosition = worldPos.xyz;
|
|
8
|
+
vWorldNormal = normalize(mat3(modelMatrix) * normal);
|
|
9
|
+
|
|
10
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
11
|
+
}
|
|
12
|
+
`,Y=`
|
|
13
|
+
uniform vec3 uColor;
|
|
14
|
+
uniform float uOpacity;
|
|
15
|
+
|
|
16
|
+
uniform vec3 uGridColor;
|
|
17
|
+
uniform float uGridSize;
|
|
18
|
+
uniform float uGridLineWidth;
|
|
19
|
+
uniform float uShowGrid;
|
|
20
|
+
|
|
21
|
+
uniform vec3 uCenter;
|
|
22
|
+
uniform float uRadius;
|
|
23
|
+
uniform float uProgress;
|
|
24
|
+
|
|
25
|
+
uniform vec3 uGlowColor;
|
|
26
|
+
uniform float uGlowWidth;
|
|
27
|
+
uniform float uGlowIntensity;
|
|
28
|
+
|
|
29
|
+
uniform vec3 uLightColor;
|
|
30
|
+
uniform vec3 uLightDirection;
|
|
31
|
+
|
|
32
|
+
varying vec3 vWorldPosition;
|
|
33
|
+
varying vec3 vWorldNormal;
|
|
34
|
+
|
|
35
|
+
float getGridFactor(vec3 worldPos, vec3 worldNormal, float gridSize, float lineWidth) {
|
|
36
|
+
vec3 absNormal = abs(worldNormal);
|
|
37
|
+
vec2 gridCoord;
|
|
38
|
+
|
|
39
|
+
if (absNormal.y > absNormal.x && absNormal.y > absNormal.z) {
|
|
40
|
+
gridCoord = worldPos.xz / gridSize;
|
|
41
|
+
} else if (absNormal.x > absNormal.z) {
|
|
42
|
+
gridCoord = worldPos.yz / gridSize;
|
|
43
|
+
} else {
|
|
44
|
+
gridCoord = worldPos.xy / gridSize;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
vec2 gridFract = fract(abs(gridCoord));
|
|
48
|
+
vec2 distToLine = min(gridFract, 1.0 - gridFract);
|
|
49
|
+
float minDist = min(distToLine.x, distToLine.y);
|
|
50
|
+
float gridFactor = 1.0 - smoothstep(0.0, lineWidth, minDist);
|
|
51
|
+
|
|
52
|
+
return gridFactor;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
void main() {
|
|
56
|
+
vec3 baseColor = uColor;
|
|
57
|
+
float baseAlpha = uOpacity;
|
|
58
|
+
|
|
59
|
+
float distanceFromCenter = length(vWorldPosition - uCenter);
|
|
60
|
+
float normalizedDistance = distanceFromCenter / max(uRadius, 0.001);
|
|
61
|
+
float threshold = uProgress;
|
|
62
|
+
|
|
63
|
+
if (threshold - normalizedDistance - 0.001 < 0.0) {
|
|
64
|
+
discard;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
float distanceFromEdge = threshold - normalizedDistance;
|
|
68
|
+
|
|
69
|
+
float gridFactor = getGridFactor(vWorldPosition, normalize(vWorldNormal), uGridSize, uGridLineWidth);
|
|
70
|
+
|
|
71
|
+
vec3 colorGridMixed = mix(baseColor, uGridColor, gridFactor * uShowGrid);
|
|
72
|
+
float alphaGridMixed = max(baseAlpha, gridFactor * uShowGrid);
|
|
73
|
+
|
|
74
|
+
float glowFactor = clamp(1.0 - clamp(distanceFromEdge / max(uGlowWidth, 0.001), 0.0, 1.0), 0.0, 1.0);
|
|
75
|
+
glowFactor *= glowFactor;
|
|
76
|
+
|
|
77
|
+
vec3 glowContribution = uGlowColor * glowFactor * uGlowIntensity;
|
|
78
|
+
|
|
79
|
+
float alphaWithGlow = max(alphaGridMixed, glowFactor);
|
|
80
|
+
alphaWithGlow = max(alphaWithGlow, 0.3);
|
|
81
|
+
|
|
82
|
+
vec3 N = normalize(vWorldNormal);
|
|
83
|
+
vec3 L = normalize(uLightDirection);
|
|
84
|
+
float ndotl = max(0.3, dot(N, L));
|
|
85
|
+
vec3 ambient = vec3(0.4);
|
|
86
|
+
vec3 lighting = uLightColor * ndotl + ambient;
|
|
87
|
+
|
|
88
|
+
vec3 litColor = colorGridMixed * lighting;
|
|
89
|
+
vec3 finalColor = litColor + glowContribution * 0.5;
|
|
90
|
+
|
|
91
|
+
gl_FragColor = vec4(finalColor, alphaWithGlow);
|
|
92
|
+
}
|
|
93
|
+
`;function _(a={}){var E,L,y,T,C,w,z,x,F,A,b,M,S,H;let e=(E=a.color)!=null?E:"#7B2CBF",t=(L=a.opacity)!=null?L:150/255,i=(y=a.gridColor)!=null?y:"#ffeb3b",r=(T=a.gridScale)!=null?T:2,o=(C=a.gridLineWidth)!=null?C:.02,n=(w=a.showGrid)!=null?w:1,l=(z=a.center)!=null?z:new f.Vector3(0,0,0),s=(x=a.radius)!=null?x:1,c=(F=a.progress)!=null?F:1,d=(A=a.glowColor)!=null?A:"#FF3A00",u=(b=a.glowWidth)!=null?b:.05,h=(M=a.glowIntensity)!=null?M:2,m=(S=a.lightColor)!=null?S:"#ffffff",p=(H=a.lightDirection)!=null?H:new f.Vector3(.3,1,.3).normalize();return new f.ShaderMaterial({vertexShader:J,fragmentShader:Y,transparent:true,side:f.DoubleSide,uniforms:{uColor:{value:typeof e=="string"?new f.Color(e):e},uOpacity:{value:t},uGridColor:{value:typeof i=="string"?new f.Color(i):i},uGridSize:{value:r},uGridLineWidth:{value:o},uShowGrid:{value:n},uCenter:{value:l.clone()},uRadius:{value:s},uProgress:{value:c},uGlowColor:{value:typeof d=="string"?new f.Color(d):d},uGlowWidth:{value:u},uGlowIntensity:{value:h},uLightColor:{value:typeof m=="string"?new f.Color(m):m},uLightDirection:{value:p.clone()}}})}var D=class{constructor(e,t,i,r){this.scene=e;this.client=t;this.renderer=i;this.camera=r;this.gizmoControl=null;this.gizmoHelper=null;this.meshMaterial=null;this.localCenter=new f.Vector3;this.localRadius=0;this.radialDuration=4;this.meshGroup=new f.Group,this.meshGroup.visible=false,this.scene.add(this.meshGroup),this.dracoLoader=new DRACOLoader,this.dracoLoader.setDecoderPath("/draco/"),this.gltfLoader=new GLTFLoader,this.gltfLoader.setDRACOLoader(this.dracoLoader);}getMeshGroup(){return this.meshGroup}async ensureGizmoLoaded(){if(!this.gizmoControl){let e=new TransformControls(this.camera,this.renderer.domElement);e.setMode("translate"),e.setSpace("local"),e.enabled=false;let t=e.getHelper();this.scene.add(t),e.attach(this.meshGroup),this.gizmoControl=e,this.gizmoHelper=t;}this.gizmoHelper.visible=true;}async ensureMeshLoaded(e){var r,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,l)=>{this.gltfLoader.load(i,s=>{let c=new f.Box3().setFromObject(s.scene),d=c.getSize(new f.Vector3),u=c.getCenter(new f.Vector3);this.localCenter.copy(u),this.localRadius=Math.max(d.length()/2*1.1,.001);let h=_({color:"#7B2CBF",opacity:.58,gridColor:"#ffeb3b",gridScale:2,gridLineWidth:.02,showGrid:1,center:this.localCenter.clone(),radius:this.localRadius,progress:0,glowColor:"#FF3A00",glowWidth:.05,glowIntensity:2});this.meshMaterial=h,s.scene.traverse(m=>{let p=m;p.isMesh&&(p.material=h,p.frustumCulled=false);}),s.scene.name=e._id,this.meshGroup.add(s.scene),n();},void 0,s=>{l(s instanceof Error?s:new Error(String(s)));});});}applyMeshTransform(e){let t=new f.Vector3,i=new f.Quaternion,r=new f.Vector3;e.decompose(t,i,r),this.meshGroup.position.copy(t),this.meshGroup.quaternion.copy(i),this.meshGroup.scale.set(1,1,1),this.meshGroup.visible=true,this.meshGroup.updateMatrixWorld(true),this.meshMaterial&&(this.meshMaterial.uniforms.uProgress.value=0,this.meshMaterial.uniforms.uCenter.value.copy(this.localCenter).applyMatrix4(this.meshGroup.matrixWorld));}update(e,t){if(!this.meshMaterial)return;let i=this.meshMaterial.uniforms.uProgress;i&&(i.value>=1||(i.value=Math.min(i.value+e/this.radialDuration,1)));}hideUntilNextLocalization(){this.meshGroup.visible=false,this.gizmoHelper&&(this.gizmoHelper.visible=false),this.meshMaterial&&(this.meshMaterial.uniforms.uProgress.value=0);}dispose(){this.meshMaterial=null,this.meshGroup.traverse(e=>{let t=e;t.isMesh&&(t.geometry.dispose(),Array.isArray(t.material)?t.material.forEach(i=>i.dispose()):t.material.dispose());}),this.scene.remove(this.meshGroup),this.dracoLoader.dispose(),this.gizmoHelper&&this.scene.remove(this.gizmoHelper),this.gizmoControl&&this.gizmoControl.dispose();}};var W=class{constructor(e,t,i,r){this.meshVisualizer=new D(e,t,i,r);}async ensureGizmoLoaded(){await this.meshVisualizer.ensureGizmoLoaded();}async ensureMeshLoaded(e){await this.meshVisualizer.ensureMeshLoaded(e);}applyMeshTransform(e){this.meshVisualizer.applyMeshTransform(e);}update(e,t){this.meshVisualizer.update(e,t);}hideUntilNextLocalization(){this.meshVisualizer.hideUntilNextLocalization();}dispose(){this.meshVisualizer.dispose();}};var X=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 f.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)&&(this.world=new W(e.scene,t.getClient(),i,r));}static isSupported(){return I.isSupported()}initialize(e){var l,s;let{options:t}=this,{session:i}=t,r=null;if(t.useDefaultButton!==false){r=i.createButton();let c=e instanceof HTMLElement?e:t.buttonContainer instanceof HTMLElement?t.buttonContainer:(l=i.getOverlayRoot())!=null?l:document.body;c.contains(r)||c.appendChild(r),(s=t.onButtonCreated)==null||s.call(t,r);}let{renderer: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()}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 l=e.pose.transform.position;this.world.update(e.deltaSeconds,new f.Vector3(l.x,l.y,l.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 h,m,p;let{position:i,rotation:r}=e.localizeData,o=new f.Matrix4().compose(new f.Vector3(i.x,i.y,i.z),new f.Quaternion(r.x,r.y,r.z,r.w),new f.Vector3(1,1,1)),n=new f.Matrix4().multiplyMatrices(new f.Matrix4().fromArray(t),o.invert());if((m=(h=this.options).onLocalizationSuccess)==null||m.call(h,e,n),!this.world)return;let{showMesh:l,showGizmo:s,session:c}=this.options,d=s!==false;if(l&&((p=e.localizeData.mapCodes)!=null&&p[0]))try{let E=await c.getClient().fetchMapDetails(e.localizeData.mapCodes[0]);E&&(e.mapDetails=E);}catch{}let u=l&&!!e.mapDetails;if(!(!u&&!d))try{if(d&&await this.world.ensureGizmoLoaded(),u&&await this.world.ensureMeshLoaded(e.mapDetails),!c.isActive())return;this.world.applyMeshTransform(n);}catch{}}dispose(){var e;this.resizeHandler&&(window.removeEventListener("resize",this.resizeHandler),this.resizeHandler=null),this.stopPreviewLoop(),(e=this.world)==null||e.dispose(),this.world=null,this.options.session.dispose();}};export{X as ThreeAdapter};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multisetai/vps",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Multiset VPS WebXR SDK - Core client and WebXR controller.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
"import": "./dist/core/index.js",
|
|
22
22
|
"require": "./dist/core/index.js"
|
|
23
23
|
},
|
|
24
|
-
"./
|
|
25
|
-
"types": "./dist/
|
|
26
|
-
"import": "./dist/
|
|
27
|
-
"require": "./dist/
|
|
24
|
+
"./three": {
|
|
25
|
+
"types": "./dist/three/index.d.ts",
|
|
26
|
+
"import": "./dist/three/index.js",
|
|
27
|
+
"require": "./dist/three/index.js"
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
package/dist/webxr/index.d.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { MultisetClient, ILocalizeAndMapDetails } from '../core/index.js';
|
|
2
|
-
import * as THREE from 'three';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Options for WebxrController. Property names align with Unity SingleFrameLocalizationManager
|
|
6
|
-
* where applicable (see https://docs.multiset.ai/unity-sdk/api-reference/singleframelocalizationmanager).
|
|
7
|
-
*/
|
|
8
|
-
interface IWebxrControllerOptions {
|
|
9
|
-
client: MultisetClient;
|
|
10
|
-
canvas?: HTMLCanvasElement;
|
|
11
|
-
overlayRoot?: HTMLElement;
|
|
12
|
-
buttonContainer?: HTMLElement;
|
|
13
|
-
onARButtonCreated?: (button: HTMLButtonElement) => void;
|
|
14
|
-
onSessionStart?: () => void;
|
|
15
|
-
onSessionEnd?: () => void;
|
|
16
|
-
}
|
|
17
|
-
declare class WebxrController {
|
|
18
|
-
private readonly options;
|
|
19
|
-
private experience;
|
|
20
|
-
private world;
|
|
21
|
-
private arButton;
|
|
22
|
-
private resizeHandler;
|
|
23
|
-
private isSessionActive;
|
|
24
|
-
private trackingLossFrames;
|
|
25
|
-
private hadTrackingLoss;
|
|
26
|
-
private isLocalizing;
|
|
27
|
-
private trackerSpace;
|
|
28
|
-
private xrWebGLBinding;
|
|
29
|
-
constructor(options: IWebxrControllerOptions);
|
|
30
|
-
initialize(buttonContainer?: HTMLElement): Promise<HTMLButtonElement>;
|
|
31
|
-
getScene(): THREE.Scene;
|
|
32
|
-
getCamera(): THREE.PerspectiveCamera;
|
|
33
|
-
getRenderer(): THREE.WebGLRenderer;
|
|
34
|
-
hasActiveSession(): boolean;
|
|
35
|
-
/**
|
|
36
|
-
* Runs a single-frame localization: captures one frame, evaluates the result
|
|
37
|
-
* against the optional confidenceThreshold, and fires
|
|
38
|
-
* onLocalizationInit / onLocalizationSuccess / onLocalizationFailure.
|
|
39
|
-
* Aligns with Unity SingleFrameLocalizationManager.LocalizeFrame().
|
|
40
|
-
*/
|
|
41
|
-
localizeFrame(): Promise<ILocalizeAndMapDetails | null>;
|
|
42
|
-
/**
|
|
43
|
-
* Internal: captures one frame and calls the localization API. Waits for a
|
|
44
|
-
* valid viewer pose up to localizationTrackingTimeoutMs (and TRACKING_FRAME_CAP),
|
|
45
|
-
* then tries to get a camera image (up to CAMERA_IMAGE_MAX_ATTEMPTS). Returns
|
|
46
|
-
* result and an optional failureReason for onLocalizationFailure.
|
|
47
|
-
*/
|
|
48
|
-
private captureFrame;
|
|
49
|
-
dispose(): void;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export { type IWebxrControllerOptions, WebxrController };
|