@shopware-ag/dive
Version:
Shopware Spatial Framework
177 lines (144 loc) • 5.36 kB
text/typescript
import { Vector3 } from 'three';
import DIVEOrbitControls from '../../controls/OrbitControls';
import { type DIVERenderer } from '../../renderer/Renderer';
import { type DIVEScene } from '../../scene/Scene';
import { Overlay } from './overlay/Overlay';
import { DIVEWebXRController } from './controller/WebXRController';
export class DIVEWebXR {
// general members
private static _renderer: DIVERenderer;
private static _scene: DIVEScene;
private static _controller: DIVEOrbitControls;
// camera reset members
private static _cameraPosition: Vector3;
private static _cameraTarget: Vector3;
// render loop members
private static _renderCallbackId: string | null = null;
// setup members
private static _session: XRSession | null = null;
private static _referenceSpaceType: XRReferenceSpaceType = 'local';
private static _overlay: Overlay | null = null;
private static _options = {
requiredFeatures: [
'local',
'hit-test',
],
optionalFeatures: [
'light-estimation',
'local-floor',
'dom-overlay',
'depth-sensing',
],
depthSensing: {
usagePreference: ['gpu-optimized'],
dataFormatPreference: [],
},
domOverlay: { root: {} as HTMLElement },
};
private static _xrController: DIVEWebXRController | null = null;
public static async Launch(
renderer: DIVERenderer,
scene: DIVEScene,
controller: DIVEOrbitControls,
): Promise<void> {
this._renderer = renderer;
this._scene = scene;
this._controller = controller;
// setting camera reset values
this._cameraPosition = this._controller.object.position.clone();
this._cameraTarget = this._controller.target.clone();
if (!navigator.xr) {
console.error('WebXR not supported');
return Promise.reject();
}
// setup current instance
this._renderer.xr.enabled = true;
this._scene.InitXR(renderer);
// creating overlay
if (!DIVEWebXR._overlay) {
const overlay = new Overlay();
DIVEWebXR._overlay = overlay;
}
DIVEWebXR._options.domOverlay = { root: DIVEWebXR._overlay.Element };
// request session
const session = await navigator.xr.requestSession(
'immersive-ar',
this._options,
);
session.addEventListener('end', () => {
this._onSessionEnded();
});
// build up session
renderer.xr.setReferenceSpaceType(this._referenceSpaceType);
await renderer.xr.setSession(session);
DIVEWebXR._overlay.Element.style.display = '';
this._session = session;
// add end session event listener
DIVEWebXR._overlay.CloseButton.addEventListener('click', () =>
this.End(),
);
// start session
await this._onSessionStarted();
return Promise.resolve();
}
public static Update(_time: DOMHighResTimeStamp, frame: XRFrame): void {
if (!this._session) return;
if (this._xrController) {
this._xrController.Update(frame);
}
}
public static End(): void {
if (!this._session) return;
this._session.end();
}
private static async _onSessionStarted(): Promise<void> {
if (!this._session) return;
// add update callback to render loop
this._renderCallbackId = this._renderer.AddPreRenderCallback(
(time: DOMHighResTimeStamp, frame: XRFrame) => {
this.Update(time, frame);
},
);
this._xrController = new DIVEWebXRController(
this._session,
this._renderer,
this._scene,
);
await this._xrController.Init().catch(() => {
this.End();
});
return Promise.resolve();
}
private static _onSessionEnded(): void {
if (!this._session) return;
if (this._xrController) {
this._xrController.Dispose();
}
// remove Update() callback
if (this._renderCallbackId) {
this._renderer.RemovePreRenderCallback(this._renderCallbackId);
this._renderCallbackId = null;
}
// disable XR on renderer to restore canvas rendering
this._renderer.xr.enabled = false;
// resize renderer
const canvasWrapper = this._renderer.domElement.parentElement;
if (canvasWrapper) {
const { clientWidth, clientHeight } = canvasWrapper;
this._renderer.OnResize(clientWidth, clientHeight);
// resize camera
this._controller.object.OnResize(clientWidth, clientHeight);
}
// reset camera
this._controller.object.position.copy(this._cameraPosition);
this._controller.target.copy(this._cameraTarget);
// reset camera values
this._cameraPosition.set(0, 0, 0);
this._cameraTarget.set(0, 0, 0);
// dispose xr scene
this._scene.DisposeXR();
this._session.removeEventListener('end', this._onSessionEnded);
DIVEWebXR._overlay!.Element.style.display = 'none';
this._session = null;
}
}