xrblocks 0.4.0 → 0.5.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.
@@ -21,6 +21,7 @@ import { XREffects } from './components/XREffects';
21
21
  import { XRTransition } from './components/XRTransition';
22
22
  import { Options } from './Options';
23
23
  import { User } from './User';
24
+ import { PermissionsManager } from './components/PermissionsManager';
24
25
  /**
25
26
  * Core is the central engine of the XR Blocks framework, acting as a
26
27
  * singleton manager for all XR subsystems. Its primary goal is to abstract
@@ -84,6 +85,7 @@ export declare class Core {
84
85
  scriptsManager: ScriptsManager;
85
86
  renderSceneOverride?: (renderer: THREE.WebGLRenderer, scene: THREE.Scene, camera: THREE.Camera) => void;
86
87
  webXRSessionManager?: WebXRSessionManager;
88
+ permissionsManager: PermissionsManager;
87
89
  /**
88
90
  * Core is a singleton manager that manages all XR "blocks".
89
91
  * It initializes core components and abstractions like the scene, camera,
@@ -108,6 +108,14 @@ export declare class Options {
108
108
  showEnterSimulatorButton: boolean;
109
109
  alwaysAutostartSimulator: boolean;
110
110
  };
111
+ /**
112
+ * Which permissions to request before entering the XR session.
113
+ */
114
+ permissions: {
115
+ geolocation: boolean;
116
+ camera: boolean;
117
+ microphone: boolean;
118
+ };
111
119
  /**
112
120
  * Constructs the Options object by merging default values with provided
113
121
  * custom options.
@@ -161,11 +169,6 @@ export declare class Options {
161
169
  * @returns The instance for chaining.
162
170
  */
163
171
  enableHandRays(): this;
164
- /**
165
- * Enables the Gemini Live feature.
166
- * @returns The instance for chaining.
167
- */
168
- enableGeminiLive(): this;
169
172
  /**
170
173
  * Enables a standard set of AI features, including Gemini Live.
171
174
  * @returns The instance for chaining.
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Interface representing the result of a permission request.
3
+ */
4
+ export interface PermissionResult {
5
+ granted: boolean;
6
+ status: PermissionState | 'unknown' | 'error';
7
+ error?: string;
8
+ }
9
+ /**
10
+ * A utility class to manage and request browser permissions for
11
+ * Location, Camera, and Microphone.
12
+ */
13
+ export declare class PermissionsManager {
14
+ /**
15
+ * Requests permission to access the user's geolocation.
16
+ * Note: This actually attempts to fetch the position to trigger the prompt.
17
+ */
18
+ requestLocationPermission(): Promise<PermissionResult>;
19
+ /**
20
+ * Requests permission to access the microphone.
21
+ * Opens a stream to trigger the prompt, then immediately closes it.
22
+ */
23
+ requestMicrophonePermission(): Promise<PermissionResult>;
24
+ /**
25
+ * Requests permission to access the camera.
26
+ * Opens a stream to trigger the prompt, then immediately closes it.
27
+ */
28
+ requestCameraPermission(): Promise<PermissionResult>;
29
+ /**
30
+ * Requests permission for both camera and microphone simultaneously.
31
+ */
32
+ requestAVPermission(): Promise<PermissionResult>;
33
+ /**
34
+ * Internal helper to handle getUserMedia requests.
35
+ * Crucially, this stops the tracks immediately after permission is granted
36
+ * so the hardware doesn't remain active.
37
+ */
38
+ private requestMediaPermission;
39
+ /**
40
+ * Requests multiple permissions sequentially.
41
+ * Returns a single result: granted is true only if ALL requested permissions are granted.
42
+ */
43
+ checkAndRequestPermissions({ geolocation, camera, microphone, }: {
44
+ geolocation?: boolean;
45
+ camera?: boolean;
46
+ microphone?: boolean;
47
+ }): Promise<PermissionResult>;
48
+ /**
49
+ * Checks the current status of a permission without triggering a prompt.
50
+ * Useful for UI state (e.g., disabling buttons if already denied).
51
+ * * @param permissionName - 'geolocation', 'camera', or 'microphone'
52
+ */
53
+ checkPermissionStatus(permissionName: 'geolocation' | 'camera' | 'microphone'): Promise<PermissionState | 'unknown'>;
54
+ }
@@ -1,15 +1,22 @@
1
+ import { PermissionsManager } from './PermissionsManager';
1
2
  import { WebXRSessionManager } from './WebXRSessionManager';
2
3
  export declare class XRButton {
3
4
  private sessionManager;
5
+ private permissionsManager;
4
6
  private startText;
5
7
  private endText;
6
8
  private invalidText;
7
9
  private startSimulatorText;
8
10
  startSimulator: () => void;
11
+ private permissions;
9
12
  domElement: HTMLDivElement;
10
13
  simulatorButtonElement: HTMLButtonElement;
11
14
  xrButtonElement: HTMLButtonElement;
12
- constructor(sessionManager: WebXRSessionManager, startText?: string, endText?: string, invalidText?: string, startSimulatorText?: string, showEnterSimulatorButton?: boolean, startSimulator?: () => void);
15
+ constructor(sessionManager: WebXRSessionManager, permissionsManager: PermissionsManager, startText?: string, endText?: string, invalidText?: string, startSimulatorText?: string, showEnterSimulatorButton?: boolean, startSimulator?: () => void, permissions?: {
16
+ geolocation: boolean;
17
+ camera: boolean;
18
+ microphone: boolean;
19
+ });
13
20
  private createSimulatorButton;
14
21
  private createXRButtonElement;
15
22
  private onSessionReady;
@@ -28,6 +28,8 @@ export declare class DepthOptions {
28
28
  enabled: boolean;
29
29
  };
30
30
  useFloat32: boolean;
31
+ depthTypeRequest: XRDepthType[];
32
+ matchDepthView: boolean;
31
33
  constructor(options?: DeepReadonly<DeepPartial<DepthOptions>>);
32
34
  }
33
35
  export declare const xrDepthMeshOptions: {
@@ -58,6 +60,8 @@ export declare const xrDepthMeshOptions: {
58
60
  readonly enabled: boolean;
59
61
  };
60
62
  readonly useFloat32: boolean;
63
+ readonly depthTypeRequest: readonly XRDepthType[];
64
+ readonly matchDepthView: boolean;
61
65
  };
62
66
  export declare const xrDepthMeshVisualizationOptions: {
63
67
  readonly debugging: boolean;
@@ -87,6 +91,8 @@ export declare const xrDepthMeshVisualizationOptions: {
87
91
  readonly enabled: boolean;
88
92
  };
89
93
  readonly useFloat32: boolean;
94
+ readonly depthTypeRequest: readonly XRDepthType[];
95
+ readonly matchDepthView: boolean;
90
96
  };
91
97
  export declare const xrDepthMeshPhysicsOptions: {
92
98
  readonly debugging: boolean;
@@ -116,4 +122,6 @@ export declare const xrDepthMeshPhysicsOptions: {
116
122
  readonly enabled: boolean;
117
123
  };
118
124
  readonly useFloat32: boolean;
125
+ readonly depthTypeRequest: readonly XRDepthType[];
126
+ readonly matchDepthView: boolean;
119
127
  };
@@ -42,6 +42,8 @@ export declare class Simulator extends Script {
42
42
  effects?: XREffects;
43
43
  virtualSceneRenderTarget?: THREE.WebGLRenderTarget;
44
44
  virtualSceneFullScreenQuad?: FullScreenQuad;
45
+ backgroundVideoQuad?: FullScreenQuad;
46
+ videoElement?: HTMLVideoElement;
45
47
  camera?: SimulatorCamera;
46
48
  options: SimulatorOptions;
47
49
  renderer: THREE.WebGLRenderer;
@@ -23,6 +23,7 @@ export declare class SimulatorOptions {
23
23
  z: number;
24
24
  };
25
25
  scenePath: string;
26
+ videoPath?: string;
26
27
  initialScenePosition: {
27
28
  x: number;
28
29
  y: number;
@@ -43,7 +44,10 @@ export declare class SimulatorOptions {
43
44
  enabled: boolean;
44
45
  element: string;
45
46
  };
46
- geminilive: boolean;
47
+ geminiLivePanel: {
48
+ enabled: boolean;
49
+ element: string;
50
+ };
47
51
  stereo: {
48
52
  enabled: boolean;
49
53
  };
package/build/xrblocks.js CHANGED
@@ -14,9 +14,9 @@
14
14
  * limitations under the License.
15
15
  *
16
16
  * @file xrblocks.js
17
- * @version v0.4.0
18
- * @commitid 385db96
19
- * @builddate 2025-11-20T21:08:04.032Z
17
+ * @version v0.5.0
18
+ * @commitid c2f4b09
19
+ * @builddate 2025-12-04T15:14:30.184Z
20
20
  * @description XR Blocks SDK, built from source with the above commit ID.
21
21
  * @agent When using with Gemini to create XR apps, use **Gemini Canvas** mode,
22
22
  * and follow rules below:
@@ -2408,6 +2408,8 @@ class DepthOptions {
2408
2408
  // Occlusion pass.
2409
2409
  this.occlusion = { enabled: false };
2410
2410
  this.useFloat32 = true;
2411
+ this.depthTypeRequest = ['raw'];
2412
+ this.matchDepthView = true;
2411
2413
  deepMerge(this, options);
2412
2414
  }
2413
2415
  }
@@ -4402,13 +4404,19 @@ class WebXRSessionManager extends THREE.EventDispatcher {
4402
4404
  const XRBUTTON_WRAPPER_ID = 'XRButtonWrapper';
4403
4405
  const XRBUTTON_CLASS = 'XRButton';
4404
4406
  class XRButton {
4405
- constructor(sessionManager, startText = 'ENTER XR', endText = 'END XR', invalidText = 'XR NOT SUPPORTED', startSimulatorText = 'START SIMULATOR', showEnterSimulatorButton = false, startSimulator = () => { }) {
4407
+ constructor(sessionManager, permissionsManager, startText = 'ENTER XR', endText = 'END XR', invalidText = 'XR NOT SUPPORTED', startSimulatorText = 'START SIMULATOR', showEnterSimulatorButton = false, startSimulator = () => { }, permissions = {
4408
+ geolocation: false,
4409
+ camera: false,
4410
+ microphone: false,
4411
+ }) {
4406
4412
  this.sessionManager = sessionManager;
4413
+ this.permissionsManager = permissionsManager;
4407
4414
  this.startText = startText;
4408
4415
  this.endText = endText;
4409
4416
  this.invalidText = invalidText;
4410
4417
  this.startSimulatorText = startSimulatorText;
4411
4418
  this.startSimulator = startSimulator;
4419
+ this.permissions = permissions;
4412
4420
  this.domElement = document.createElement('div');
4413
4421
  this.simulatorButtonElement = document.createElement('button');
4414
4422
  this.xrButtonElement = document.createElement('button');
@@ -4443,7 +4451,17 @@ class XRButton {
4443
4451
  button.innerHTML = this.startText;
4444
4452
  button.disabled = false;
4445
4453
  button.onclick = () => {
4446
- this.sessionManager.startSession();
4454
+ this.permissionsManager
4455
+ .checkAndRequestPermissions(this.permissions)
4456
+ .then((result) => {
4457
+ if (result.granted) {
4458
+ this.sessionManager.startSession();
4459
+ }
4460
+ else {
4461
+ this.xrButtonElement.textContent =
4462
+ 'Error:' + result.error + '\nPlease try again.';
4463
+ }
4464
+ });
4447
4465
  };
4448
4466
  }
4449
4467
  showXRNotSupported() {
@@ -7172,6 +7190,7 @@ class SimulatorOptions {
7172
7190
  constructor(options) {
7173
7191
  this.initialCameraPosition = { x: 0, y: 1.5, z: 0 };
7174
7192
  this.scenePath = XR_BLOCKS_ASSETS_PATH + 'simulator/scenes/XREmulatorsceneV5_livingRoom.glb';
7193
+ this.videoPath = undefined;
7175
7194
  this.initialScenePosition = { x: -1.6, y: 0.3, z: 0 };
7176
7195
  this.defaultMode = SimulatorMode.USER;
7177
7196
  this.defaultHand = Handedness.LEFT;
@@ -7188,7 +7207,10 @@ class SimulatorOptions {
7188
7207
  enabled: true,
7189
7208
  element: 'xrblocks-simulator-hand-pose-panel',
7190
7209
  };
7191
- this.geminilive = false;
7210
+ this.geminiLivePanel = {
7211
+ enabled: false,
7212
+ element: 'xrblocks-simulator-geminilive',
7213
+ };
7192
7214
  this.stereo = {
7193
7215
  enabled: false,
7194
7216
  };
@@ -7436,6 +7458,14 @@ class Options {
7436
7458
  // Whether to autostart the simulator even if WebXR is available.
7437
7459
  alwaysAutostartSimulator: false,
7438
7460
  };
7461
+ /**
7462
+ * Which permissions to request before entering the XR session.
7463
+ */
7464
+ this.permissions = {
7465
+ geolocation: false,
7466
+ camera: false,
7467
+ microphone: false,
7468
+ };
7439
7469
  deepMerge(this, options);
7440
7470
  }
7441
7471
  /**
@@ -7476,6 +7506,7 @@ class Options {
7476
7506
  * @returns The instance for chaining.
7477
7507
  */
7478
7508
  enableObjectDetection() {
7509
+ this.permissions.camera = true;
7479
7510
  this.world.enableObjectDetection();
7480
7511
  return this;
7481
7512
  }
@@ -7486,6 +7517,7 @@ class Options {
7486
7517
  * @returns The instance for chaining.
7487
7518
  */
7488
7519
  enableCamera(facingMode = 'environment') {
7520
+ this.permissions.camera = true;
7489
7521
  this.deviceCamera = new DeviceCameraOptions(facingMode === 'environment'
7490
7522
  ? xrDeviceCameraEnvironmentOptions
7491
7523
  : xrDeviceCameraUserOptions);
@@ -7516,14 +7548,6 @@ class Options {
7516
7548
  this.controllers.visualizeRays = true;
7517
7549
  return this;
7518
7550
  }
7519
- /**
7520
- * Enables the Gemini Live feature.
7521
- * @returns The instance for chaining.
7522
- */
7523
- enableGeminiLive() {
7524
- this.simulator.geminilive = true;
7525
- return this;
7526
- }
7527
7551
  /**
7528
7552
  * Enables a standard set of AI features, including Gemini Live.
7529
7553
  * @returns The instance for chaining.
@@ -9785,8 +9809,8 @@ class SimulatorInterface {
9785
9809
  }
9786
9810
  }
9787
9811
  showGeminiLivePanel(simulatorOptions) {
9788
- if (simulatorOptions.geminilive) {
9789
- const element = document.createElement('xrblocks-simulator-geminilive');
9812
+ if (simulatorOptions.geminiLivePanel.enabled) {
9813
+ const element = document.createElement(simulatorOptions.geminiLivePanel.element);
9790
9814
  document.body.appendChild(element);
9791
9815
  this.elements.push(element);
9792
9816
  }
@@ -9830,6 +9854,9 @@ class SimulatorScene extends THREE.Scene {
9830
9854
  }
9831
9855
  async init(simulatorOptions) {
9832
9856
  this.addLights();
9857
+ if (simulatorOptions.videoPath) {
9858
+ return;
9859
+ }
9833
9860
  if (simulatorOptions.scenePath) {
9834
9861
  await this.loadGLTF(simulatorOptions.scenePath, new THREE.Vector3(simulatorOptions.initialScenePosition.x, simulatorOptions.initialScenePosition.y, simulatorOptions.initialScenePosition.z));
9835
9862
  }
@@ -9967,6 +9994,21 @@ class Simulator extends Script {
9967
9994
  if (this.options.stereo.enabled) {
9968
9995
  this.setupStereoCameras(camera);
9969
9996
  }
9997
+ if (this.options.videoPath) {
9998
+ this.videoElement = document.createElement('video');
9999
+ this.videoElement.src = this.options.videoPath;
10000
+ this.videoElement.loop = true;
10001
+ this.videoElement.muted = true;
10002
+ this.videoElement.play().catch((e) => {
10003
+ console.error(`Simulator: Failed to play video at ${this.options.videoPath}`, e);
10004
+ });
10005
+ this.videoElement.addEventListener('error', () => {
10006
+ console.error(`Simulator: Error loading video at ${this.options.videoPath}`, this.videoElement?.error);
10007
+ });
10008
+ const videoTexture = new THREE.VideoTexture(this.videoElement);
10009
+ videoTexture.colorSpace = THREE.SRGBColorSpace;
10010
+ this.backgroundVideoQuad = new FullScreenQuad(new THREE.MeshBasicMaterial({ map: videoTexture }));
10011
+ }
9970
10012
  this.virtualSceneRenderTarget = new THREE.WebGLRenderTarget(renderer.domElement.width, renderer.domElement.height, { stencilBuffer: options.stencil });
9971
10013
  const virtualSceneMaterial = new THREE.MeshBasicMaterial({
9972
10014
  map: this.virtualSceneRenderTarget.texture,
@@ -10076,6 +10118,9 @@ class Simulator extends Script {
10076
10118
  this.sparkRenderer.defaultView.encodeLinear = false;
10077
10119
  }
10078
10120
  this.renderer.setRenderTarget(null);
10121
+ if (this.backgroundVideoQuad) {
10122
+ this.backgroundVideoQuad.render(this.renderer);
10123
+ }
10079
10124
  this.renderer.render(this.simulatorScene, camera);
10080
10125
  this.renderer.clearDepth();
10081
10126
  }
@@ -14397,6 +14442,216 @@ class XRTransition extends MeshScript {
14397
14442
  }
14398
14443
  }
14399
14444
 
14445
+ /**
14446
+ * A utility class to manage and request browser permissions for
14447
+ * Location, Camera, and Microphone.
14448
+ */
14449
+ class PermissionsManager {
14450
+ /**
14451
+ * Requests permission to access the user's geolocation.
14452
+ * Note: This actually attempts to fetch the position to trigger the prompt.
14453
+ */
14454
+ async requestLocationPermission() {
14455
+ if (!('geolocation' in navigator)) {
14456
+ return {
14457
+ granted: false,
14458
+ status: 'error',
14459
+ error: 'Geolocation is not supported by this browser.',
14460
+ };
14461
+ }
14462
+ return new Promise((resolve) => {
14463
+ navigator.geolocation.getCurrentPosition(() => {
14464
+ resolve({ granted: true, status: 'granted' });
14465
+ }, (error) => {
14466
+ let errorMsg = 'Unknown error';
14467
+ switch (error.code) {
14468
+ case error.PERMISSION_DENIED:
14469
+ errorMsg = 'User denied the request.';
14470
+ break;
14471
+ case error.POSITION_UNAVAILABLE:
14472
+ errorMsg = 'Location information is unavailable.';
14473
+ break;
14474
+ case error.TIMEOUT:
14475
+ errorMsg = 'The request to get user location timed out.';
14476
+ break;
14477
+ }
14478
+ resolve({ granted: false, status: 'denied', error: errorMsg });
14479
+ }, { timeout: 10000 } // 10 second timeout
14480
+ );
14481
+ });
14482
+ }
14483
+ /**
14484
+ * Requests permission to access the microphone.
14485
+ * Opens a stream to trigger the prompt, then immediately closes it.
14486
+ */
14487
+ async requestMicrophonePermission() {
14488
+ return this.requestMediaPermission({ audio: true });
14489
+ }
14490
+ /**
14491
+ * Requests permission to access the camera.
14492
+ * Opens a stream to trigger the prompt, then immediately closes it.
14493
+ */
14494
+ async requestCameraPermission() {
14495
+ return this.requestMediaPermission({ video: true });
14496
+ }
14497
+ /**
14498
+ * Requests permission for both camera and microphone simultaneously.
14499
+ */
14500
+ async requestAVPermission() {
14501
+ return this.requestMediaPermission({ video: true, audio: true });
14502
+ }
14503
+ /**
14504
+ * Internal helper to handle getUserMedia requests.
14505
+ * Crucially, this stops the tracks immediately after permission is granted
14506
+ * so the hardware doesn't remain active.
14507
+ */
14508
+ async requestMediaPermission(constraints) {
14509
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
14510
+ return {
14511
+ granted: false,
14512
+ status: 'error',
14513
+ error: 'Media Devices API is not supported by this browser.',
14514
+ };
14515
+ }
14516
+ try {
14517
+ const stream = await navigator.mediaDevices.getUserMedia(constraints);
14518
+ // Permission granted. Now stop the stream to release hardware.
14519
+ stream.getTracks().forEach((track) => track.stop());
14520
+ return { granted: true, status: 'granted' };
14521
+ }
14522
+ catch (err) {
14523
+ // Handle common getUserMedia errors
14524
+ const status = 'denied';
14525
+ let errorMessage = 'Permission denied';
14526
+ if (err instanceof Error) {
14527
+ if (err.name === 'NotFoundError' ||
14528
+ err.name === 'DevicesNotFoundError') {
14529
+ return {
14530
+ granted: false,
14531
+ status: 'error',
14532
+ error: 'Hardware not found.',
14533
+ };
14534
+ }
14535
+ errorMessage = err.message || errorMessage;
14536
+ }
14537
+ return { granted: false, status: status, error: errorMessage };
14538
+ }
14539
+ }
14540
+ /**
14541
+ * Requests multiple permissions sequentially.
14542
+ * Returns a single result: granted is true only if ALL requested permissions are granted.
14543
+ */
14544
+ async checkAndRequestPermissions({ geolocation = false, camera = false, microphone = false, }) {
14545
+ const results = [];
14546
+ // 1. Handle Location
14547
+ if (geolocation) {
14548
+ const status = await this.checkPermissionStatus('geolocation');
14549
+ if (status === 'granted') {
14550
+ results.push({ granted: true, status: 'granted' });
14551
+ }
14552
+ else {
14553
+ results.push(await this.requestLocationPermission());
14554
+ }
14555
+ }
14556
+ // 2. Handle Media (Camera & Mic)
14557
+ // We group these because requestAVPermission can ask for both in one prompt
14558
+ if (camera && microphone) {
14559
+ const camStatus = await this.checkPermissionStatus('camera');
14560
+ const micStatus = await this.checkPermissionStatus('microphone');
14561
+ if (camStatus === 'granted' && micStatus === 'granted') {
14562
+ results.push({ granted: true, status: 'granted' });
14563
+ }
14564
+ else if (camStatus === 'granted') {
14565
+ // Only need mic
14566
+ results.push(await this.requestMicrophonePermission());
14567
+ }
14568
+ else if (micStatus === 'granted') {
14569
+ // Only need camera
14570
+ results.push(await this.requestCameraPermission());
14571
+ }
14572
+ else {
14573
+ // Need both
14574
+ results.push(await this.requestAVPermission());
14575
+ }
14576
+ }
14577
+ else if (camera) {
14578
+ const status = await this.checkPermissionStatus('camera');
14579
+ if (status === 'granted') {
14580
+ results.push({ granted: true, status: 'granted' });
14581
+ }
14582
+ else {
14583
+ results.push(await this.requestCameraPermission());
14584
+ }
14585
+ }
14586
+ else if (microphone) {
14587
+ const status = await this.checkPermissionStatus('microphone');
14588
+ if (status === 'granted') {
14589
+ results.push({ granted: true, status: 'granted' });
14590
+ }
14591
+ else {
14592
+ results.push(await this.requestMicrophonePermission());
14593
+ }
14594
+ }
14595
+ // 3. Aggregate results
14596
+ if (results.length === 0) {
14597
+ return { granted: true, status: 'granted' };
14598
+ }
14599
+ const allGranted = results.every((r) => r.granted);
14600
+ const anyDenied = results.find((r) => r.status === 'denied');
14601
+ const anyError = results.find((r) => r.status === 'error');
14602
+ // Aggregate errors
14603
+ const errors = results
14604
+ .filter((r) => r.error)
14605
+ .map((r) => r.error)
14606
+ .join(' | ');
14607
+ let finalStatus = 'granted';
14608
+ if (anyError)
14609
+ finalStatus = 'error';
14610
+ else if (anyDenied)
14611
+ finalStatus = 'denied';
14612
+ return {
14613
+ granted: allGranted,
14614
+ status: finalStatus,
14615
+ error: errors || undefined,
14616
+ };
14617
+ }
14618
+ /**
14619
+ * Checks the current status of a permission without triggering a prompt.
14620
+ * Useful for UI state (e.g., disabling buttons if already denied).
14621
+ * * @param permissionName - 'geolocation', 'camera', or 'microphone'
14622
+ */
14623
+ async checkPermissionStatus(permissionName) {
14624
+ if (!navigator.permissions || !navigator.permissions.query) {
14625
+ return 'unknown';
14626
+ }
14627
+ try {
14628
+ let queryName;
14629
+ // Map friendly names to API PermissionName types
14630
+ // Note: 'camera' and 'microphone' are part of the newer spec,
14631
+ // but strictly Typed TypeScript might expect specific descriptor objects.
14632
+ if (permissionName === 'geolocation') {
14633
+ queryName = 'geolocation';
14634
+ }
14635
+ else if (permissionName === 'camera' ||
14636
+ permissionName === 'microphone') {
14637
+ const descriptor = { name: permissionName };
14638
+ const result = await navigator.permissions.query(descriptor);
14639
+ return result.state;
14640
+ }
14641
+ else {
14642
+ return 'unknown';
14643
+ }
14644
+ const result = await navigator.permissions.query({ name: queryName });
14645
+ return result.state;
14646
+ }
14647
+ catch (error) {
14648
+ // Firefox and Safari have incomplete Permissions API support
14649
+ console.warn(`Error checking permission status for ${permissionName}`, error);
14650
+ return 'unknown';
14651
+ }
14652
+ }
14653
+ }
14654
+
14400
14655
  /**
14401
14656
  * Core is the central engine of the XR Blocks framework, acting as a
14402
14657
  * singleton manager for all XR subsystems. Its primary goal is to abstract
@@ -14456,6 +14711,7 @@ class Core {
14456
14711
  await script.initPhysics(this.physics);
14457
14712
  }
14458
14713
  });
14714
+ this.permissionsManager = new PermissionsManager();
14459
14715
  if (Core.instance) {
14460
14716
  return Core.instance;
14461
14717
  }
@@ -14564,6 +14820,8 @@ class Core {
14564
14820
  dataFormatPreference: [
14565
14821
  this.options.depth.useFloat32 ? 'float32' : 'luminance-alpha',
14566
14822
  ],
14823
+ depthTypeRequest: options.depth.depthTypeRequest,
14824
+ matchDepthView: options.depth.matchDepthView,
14567
14825
  };
14568
14826
  this.depth.init(this.camera, options.depth, this.renderer, this.registry, this.scene);
14569
14827
  }
@@ -14600,7 +14858,7 @@ class Core {
14600
14858
  // Sets up xrButton.
14601
14859
  let shouldAutostartSimulator = this.options.xrButton.alwaysAutostartSimulator;
14602
14860
  if (!shouldAutostartSimulator && options.xrButton.enabled) {
14603
- this.xrButton = new XRButton(this.webXRSessionManager, options.xrButton?.startText, options.xrButton?.endText, options.xrButton?.invalidText, options.xrButton?.startSimulatorText, options.xrButton?.showEnterSimulatorButton, this.startSimulator.bind(this));
14861
+ this.xrButton = new XRButton(this.webXRSessionManager, this.permissionsManager, options.xrButton?.startText, options.xrButton?.endText, options.xrButton?.invalidText, options.xrButton?.startSimulatorText, options.xrButton?.showEnterSimulatorButton, this.startSimulator.bind(this), options.permissions);
14604
14862
  document.body.appendChild(this.xrButton.domElement);
14605
14863
  }
14606
14864
  this.webXRSessionManager.addEventListener(WebXRSessionEventType.UNSUPPORTED, () => {