@shopware-ag/dive 1.16.14 → 1.16.15-beta.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/package.json +1 -1
- package/src/ar/AR.ts +164 -0
- package/src/ar/arquicklook/ARQuickLook.ts +42 -0
- package/src/ar/webxr/WebXR.ts +176 -0
- package/src/ar/webxr/controller/WebXRController.ts +334 -0
- package/src/ar/webxr/crosshair/WebXRCrosshair.ts +35 -0
- package/src/ar/webxr/origin/WebXROrigin.ts +191 -0
- package/src/ar/webxr/overlay/Overlay.ts +50 -0
- package/src/ar/webxr/raycaster/WebXRRaycaster.ts +131 -0
- package/src/ar/webxr/raycaster/ar/WebXRRaycasterAR.ts +102 -0
- package/src/ar/webxr/raycaster/three/WebXRRaycasterTHREE.ts +49 -0
- package/src/ar/webxr/touchscreencontrols/WebXRTouchscreenControls.ts +356 -0
- package/src/axiscamera/AxisCamera.ts +4 -4
- package/src/axiscamera/__test__/AxisCamera.test.ts +4 -0
- package/src/com/Communication.ts +17 -0
- package/src/com/__test__/Communication.test.ts +1 -1
- package/src/com/actions/index.ts +2 -0
- package/src/dive.ts +51 -9
- package/src/events/EventExecutor.ts +35 -0
- package/src/helper/findSceneRecursive/findSceneRecursive.ts +2 -2
- package/src/info/Info.ts +37 -1
- package/src/info/__test__/Info.test.ts +45 -5
- package/src/mediacreator/MediaCreator.ts +4 -4
- package/src/mediacreator/__test__/MediaCreator.test.ts +7 -2
- package/src/renderer/Renderer.ts +21 -11
- package/src/renderer/__test__/Renderer.test.ts +19 -1
- package/src/scene/Scene.ts +35 -12
- package/src/scene/__test__/Scene.test.ts +39 -5
- package/src/scene/root/Root.ts +1 -0
- package/src/scene/xrroot/XRRoot.ts +56 -0
- package/src/scene/xrroot/xrlightroot/XRLightRoot.ts +80 -0
- package/src/toolbox/BaseTool.ts +9 -3
- package/src/toolbox/Toolbox.ts +1 -1
- package/src/toolbox/__test__/Toolbox.test.ts +1 -1
- package/src/toolbox/select/SelectTool.ts +1 -1
- package/src/toolbox/select/__test__/SelectTool.test.ts +1 -1
- package/src/toolbox/transform/TransformTool.ts +4 -4
- package/src/toolbox/transform/__test__/TransformTool.test.ts +2 -4
- package/build/dive.cjs +0 -3280
- package/build/dive.cjs.map +0 -1
- package/build/dive.d.cts +0 -1083
- package/build/dive.d.ts +0 -1083
- package/build/dive.js +0 -3298
- package/build/dive.js.map +0 -1
package/package.json
CHANGED
package/src/ar/AR.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { DIVEInfo, WebXRUnsupportedReason } from '../info/Info';
|
|
2
|
+
import { DIVEARQuickLook } from './arquicklook/ARQuickLook';
|
|
3
|
+
import { DIVEWebXR } from './webxr/WebXR';
|
|
4
|
+
import { type DIVEScene } from '../scene/Scene';
|
|
5
|
+
import { type DIVERenderer } from '../renderer/Renderer';
|
|
6
|
+
import DIVEOrbitControls from '../controls/OrbitControls';
|
|
7
|
+
|
|
8
|
+
export class DIVEAR {
|
|
9
|
+
private _renderer: DIVERenderer;
|
|
10
|
+
private _scene: DIVEScene;
|
|
11
|
+
private _controller: DIVEOrbitControls;
|
|
12
|
+
|
|
13
|
+
private arPlacement: string = 'floor';
|
|
14
|
+
private arScale: string = 'auto';
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
renderer: DIVERenderer,
|
|
18
|
+
scene: DIVEScene,
|
|
19
|
+
controller: DIVEOrbitControls,
|
|
20
|
+
) {
|
|
21
|
+
this._renderer = renderer;
|
|
22
|
+
this._scene = scene;
|
|
23
|
+
this._controller = controller;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public async Launch(): Promise<void> {
|
|
27
|
+
const system = DIVEInfo.GetSystem();
|
|
28
|
+
|
|
29
|
+
if (system === 'iOS') {
|
|
30
|
+
const support = DIVEInfo.GetSupportsARQuickLook();
|
|
31
|
+
if (!support) {
|
|
32
|
+
console.log('ARQuickLook not supported');
|
|
33
|
+
return Promise.reject();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log('Launching AR on iOS');
|
|
37
|
+
|
|
38
|
+
// Launch ARQuickLook
|
|
39
|
+
await DIVEARQuickLook.Launch(this._scene);
|
|
40
|
+
return Promise.resolve();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (system === 'Android') {
|
|
44
|
+
this.openSceneViewer();
|
|
45
|
+
return;
|
|
46
|
+
|
|
47
|
+
const support = await DIVEInfo.GetSupportsWebXR();
|
|
48
|
+
if (!support) {
|
|
49
|
+
console.log(
|
|
50
|
+
'WebXR not supported. Reason: ' +
|
|
51
|
+
WebXRUnsupportedReason[
|
|
52
|
+
DIVEInfo.GetWebXRUnsupportedReason()!
|
|
53
|
+
],
|
|
54
|
+
);
|
|
55
|
+
return Promise.reject();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log('Launching AR on Android');
|
|
59
|
+
// Launch WebXR
|
|
60
|
+
await DIVEWebXR.Launch(
|
|
61
|
+
this._renderer,
|
|
62
|
+
this._scene,
|
|
63
|
+
this._controller,
|
|
64
|
+
);
|
|
65
|
+
return Promise.resolve();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log(
|
|
69
|
+
'AR not supported. Not a mobile system. (System is ' + system + ')',
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private openSceneViewer(): void {
|
|
74
|
+
const src = this.createSceneViewerSrc();
|
|
75
|
+
const anchor = document.createElement('a');
|
|
76
|
+
const noArViewerSigil = '#model-viewer-no-ar-fallback';
|
|
77
|
+
// let isSceneViewerBlocked = false;
|
|
78
|
+
|
|
79
|
+
const location = self.location.toString();
|
|
80
|
+
const locationUrl = new URL(location);
|
|
81
|
+
const modelUrl = new URL(src, location);
|
|
82
|
+
if (modelUrl.hash) modelUrl.hash = '';
|
|
83
|
+
const params = new URLSearchParams(modelUrl.search);
|
|
84
|
+
|
|
85
|
+
locationUrl.hash = noArViewerSigil;
|
|
86
|
+
|
|
87
|
+
// modelUrl can contain title/link/sound etc.
|
|
88
|
+
params.set('mode', 'ar_preferred');
|
|
89
|
+
if (!params.has('disable_occlusion')) {
|
|
90
|
+
params.set('disable_occlusion', 'true');
|
|
91
|
+
}
|
|
92
|
+
if (this.arScale === 'fixed') {
|
|
93
|
+
params.set('resizable', 'false');
|
|
94
|
+
}
|
|
95
|
+
if (this.arPlacement === 'wall') {
|
|
96
|
+
params.set('enable_vertical_placement', 'true');
|
|
97
|
+
}
|
|
98
|
+
if (params.has('sound')) {
|
|
99
|
+
const soundUrl = new URL(params.get('sound')!, location);
|
|
100
|
+
params.set('sound', soundUrl.toString());
|
|
101
|
+
}
|
|
102
|
+
if (params.has('link')) {
|
|
103
|
+
const linkUrl = new URL(params.get('link')!, location);
|
|
104
|
+
params.set('link', linkUrl.toString());
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
console.log('modelUrl.toString()', modelUrl.toString());
|
|
108
|
+
console.log(
|
|
109
|
+
'encodeURIComponent(modelUrl.toString())',
|
|
110
|
+
encodeURIComponent(modelUrl.toString()),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const intent = `intent://arvr.google.com/scene-viewer/1.2?${
|
|
114
|
+
params.toString() + '&file=' + modelUrl.toString()
|
|
115
|
+
}#Intent;scheme=https;package=com.google.android.googlequicksearchbox;action=android.intent.action.VIEW;S.browser_fallback_url=${encodeURIComponent(
|
|
116
|
+
locationUrl.toString(),
|
|
117
|
+
)};end;`;
|
|
118
|
+
// intent =
|
|
119
|
+
// 'intent://arvr.google.com/scene-viewer/1.0?file=https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/Avocado/glTF/Avocado.gltf#Intent;scheme=https;package=com.google.android.googlequicksearchbox;action=android.intent.action.VIEW;S.browser_fallback_url=https://developers.google.com/ar;end;';
|
|
120
|
+
console.log({ intent });
|
|
121
|
+
|
|
122
|
+
const undoHashChange = (): void => {
|
|
123
|
+
if (self.location.hash === noArViewerSigil) {
|
|
124
|
+
// isSceneViewerBlocked = true;
|
|
125
|
+
// The new history will be the current URL with a new hash.
|
|
126
|
+
// Go back one step so that we reset to the expected URL.
|
|
127
|
+
// NOTE(cdata): this should not invoke any browser-level navigation
|
|
128
|
+
// because hash-only changes modify the URL in-place without
|
|
129
|
+
// navigating:
|
|
130
|
+
self.history.back();
|
|
131
|
+
console.warn(
|
|
132
|
+
'Error while trying to present in AR with Scene Viewer',
|
|
133
|
+
);
|
|
134
|
+
console.warn('Falling back to next ar-mode');
|
|
135
|
+
// this[$selectARMode]();
|
|
136
|
+
// Would be nice to activateAR() here, but webXR fails due to not
|
|
137
|
+
// seeing a user activation.
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
self.addEventListener('hashchange', undoHashChange, { once: true });
|
|
142
|
+
|
|
143
|
+
anchor.setAttribute('href', intent);
|
|
144
|
+
console.log('Attempting to present in AR with Scene Viewer...');
|
|
145
|
+
anchor.click();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private createSceneViewerSrc(): string {
|
|
149
|
+
let uri: string | null = null;
|
|
150
|
+
|
|
151
|
+
this._scene.traverse((object) => {
|
|
152
|
+
if (uri) return;
|
|
153
|
+
if (object.userData.uri) {
|
|
154
|
+
uri = object.userData.uri;
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
if (!uri) {
|
|
159
|
+
throw new Error('No model found in scene');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return uri;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Object3D } from 'three';
|
|
2
|
+
import { type DIVEScene } from '../../scene/Scene';
|
|
3
|
+
import { USDZExporter } from 'three/examples/jsm/exporters/USDZExporter';
|
|
4
|
+
|
|
5
|
+
export class DIVEARQuickLook {
|
|
6
|
+
private static _usdzExporter: USDZExporter = new USDZExporter();
|
|
7
|
+
|
|
8
|
+
public static Launch(scene: DIVEScene): Promise<void> {
|
|
9
|
+
// create node to build usdz from
|
|
10
|
+
const quickLookScene = new Object3D();
|
|
11
|
+
|
|
12
|
+
// extract models from scene
|
|
13
|
+
quickLookScene.add(...this.extractModels(scene));
|
|
14
|
+
|
|
15
|
+
// launch ARQuickLook
|
|
16
|
+
return this.launchARFromNode(quickLookScene);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private static extractModels(scene: DIVEScene): Object3D[] {
|
|
20
|
+
// extract models
|
|
21
|
+
return scene.Root.children;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private static launchARFromNode(node: Object3D): Promise<void> {
|
|
25
|
+
// bundle USDZ
|
|
26
|
+
return this._usdzExporter
|
|
27
|
+
.parse(node, { quickLookCompatible: true })
|
|
28
|
+
.then((usdz: Uint8Array) => {
|
|
29
|
+
// create blob
|
|
30
|
+
const blob = new Blob([usdz], { type: 'model/vnd.usdz+zip' });
|
|
31
|
+
const url = URL.createObjectURL(blob);
|
|
32
|
+
|
|
33
|
+
// launch ARQuickLook
|
|
34
|
+
const a = document.createElement('a');
|
|
35
|
+
a.innerHTML = '<picture></picture>'; // This is actually needed so the viewer opens instantly
|
|
36
|
+
a.rel = 'ar';
|
|
37
|
+
a.href = url;
|
|
38
|
+
a.download = 'scene.usdz';
|
|
39
|
+
a.click();
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { Vector3 } from 'three';
|
|
2
|
+
import DIVEOrbitControls from '../../controls/OrbitControls';
|
|
3
|
+
import { type DIVERenderer } from '../../renderer/Renderer';
|
|
4
|
+
import { type DIVEScene } from '../../scene/Scene';
|
|
5
|
+
import { Overlay } from './overlay/Overlay';
|
|
6
|
+
import { DIVEWebXRController } from './controller/WebXRController';
|
|
7
|
+
|
|
8
|
+
export class DIVEWebXR {
|
|
9
|
+
// general members
|
|
10
|
+
private static _renderer: DIVERenderer;
|
|
11
|
+
private static _scene: DIVEScene;
|
|
12
|
+
private static _controller: DIVEOrbitControls;
|
|
13
|
+
|
|
14
|
+
// camera reset members
|
|
15
|
+
private static _cameraPosition: Vector3;
|
|
16
|
+
private static _cameraTarget: Vector3;
|
|
17
|
+
|
|
18
|
+
// render loop members
|
|
19
|
+
private static _renderCallbackId: string | null = null;
|
|
20
|
+
|
|
21
|
+
// setup members
|
|
22
|
+
private static _session: XRSession | null = null;
|
|
23
|
+
private static _referenceSpaceType: XRReferenceSpaceType = 'local';
|
|
24
|
+
private static _overlay: Overlay | null = null;
|
|
25
|
+
private static _options = {
|
|
26
|
+
requiredFeatures: [
|
|
27
|
+
'local',
|
|
28
|
+
'hit-test',
|
|
29
|
+
],
|
|
30
|
+
optionalFeatures: [
|
|
31
|
+
'light-estimation',
|
|
32
|
+
'local-floor',
|
|
33
|
+
'dom-overlay',
|
|
34
|
+
'depth-sensing',
|
|
35
|
+
],
|
|
36
|
+
depthSensing: {
|
|
37
|
+
usagePreference: ['gpu-optimized'],
|
|
38
|
+
dataFormatPreference: [],
|
|
39
|
+
},
|
|
40
|
+
domOverlay: { root: {} as HTMLElement },
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
private static _xrController: DIVEWebXRController | null = null;
|
|
44
|
+
|
|
45
|
+
public static async Launch(
|
|
46
|
+
renderer: DIVERenderer,
|
|
47
|
+
scene: DIVEScene,
|
|
48
|
+
controller: DIVEOrbitControls,
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
this._renderer = renderer;
|
|
51
|
+
this._scene = scene;
|
|
52
|
+
this._controller = controller;
|
|
53
|
+
|
|
54
|
+
// setting camera reset values
|
|
55
|
+
this._cameraPosition = this._controller.object.position.clone();
|
|
56
|
+
this._cameraTarget = this._controller.target.clone();
|
|
57
|
+
|
|
58
|
+
if (!navigator.xr) {
|
|
59
|
+
console.error('WebXR not supported');
|
|
60
|
+
return Promise.reject();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// setup current instance
|
|
64
|
+
this._renderer.xr.enabled = true;
|
|
65
|
+
this._scene.InitXR(renderer);
|
|
66
|
+
|
|
67
|
+
// creating overlay
|
|
68
|
+
if (!DIVEWebXR._overlay) {
|
|
69
|
+
const overlay = new Overlay();
|
|
70
|
+
DIVEWebXR._overlay = overlay;
|
|
71
|
+
}
|
|
72
|
+
DIVEWebXR._options.domOverlay = { root: DIVEWebXR._overlay.Element };
|
|
73
|
+
|
|
74
|
+
// request session
|
|
75
|
+
const session = await navigator.xr.requestSession(
|
|
76
|
+
'immersive-ar',
|
|
77
|
+
this._options,
|
|
78
|
+
);
|
|
79
|
+
session.addEventListener('end', () => {
|
|
80
|
+
this._onSessionEnded();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// build up session
|
|
84
|
+
renderer.xr.setReferenceSpaceType(this._referenceSpaceType);
|
|
85
|
+
await renderer.xr.setSession(session);
|
|
86
|
+
DIVEWebXR._overlay.Element.style.display = '';
|
|
87
|
+
this._session = session;
|
|
88
|
+
|
|
89
|
+
// add end session event listener
|
|
90
|
+
DIVEWebXR._overlay.CloseButton.addEventListener('click', () =>
|
|
91
|
+
this.End(),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// start session
|
|
95
|
+
await this._onSessionStarted();
|
|
96
|
+
|
|
97
|
+
return Promise.resolve();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
public static Update(_time: DOMHighResTimeStamp, frame: XRFrame): void {
|
|
101
|
+
if (!this._session) return;
|
|
102
|
+
|
|
103
|
+
if (this._xrController) {
|
|
104
|
+
this._xrController.Update(frame);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public static End(): void {
|
|
109
|
+
if (!this._session) return;
|
|
110
|
+
this._session.end();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private static async _onSessionStarted(): Promise<void> {
|
|
114
|
+
if (!this._session) return;
|
|
115
|
+
|
|
116
|
+
// add update callback to render loop
|
|
117
|
+
this._renderCallbackId = this._renderer.AddPreRenderCallback(
|
|
118
|
+
(time: DOMHighResTimeStamp, frame: XRFrame) => {
|
|
119
|
+
this.Update(time, frame);
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
this._xrController = new DIVEWebXRController(
|
|
124
|
+
this._session,
|
|
125
|
+
this._renderer,
|
|
126
|
+
this._scene,
|
|
127
|
+
);
|
|
128
|
+
await this._xrController.Init().catch(() => {
|
|
129
|
+
this.End();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return Promise.resolve();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private static _onSessionEnded(): void {
|
|
136
|
+
if (!this._session) return;
|
|
137
|
+
|
|
138
|
+
if (this._xrController) {
|
|
139
|
+
this._xrController.Dispose();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// remove Update() callback
|
|
143
|
+
if (this._renderCallbackId) {
|
|
144
|
+
this._renderer.RemovePreRenderCallback(this._renderCallbackId);
|
|
145
|
+
this._renderCallbackId = null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// disable XR on renderer to restore canvas rendering
|
|
149
|
+
this._renderer.xr.enabled = false;
|
|
150
|
+
|
|
151
|
+
// resize renderer
|
|
152
|
+
const canvasWrapper = this._renderer.domElement.parentElement;
|
|
153
|
+
if (canvasWrapper) {
|
|
154
|
+
const { clientWidth, clientHeight } = canvasWrapper;
|
|
155
|
+
this._renderer.OnResize(clientWidth, clientHeight);
|
|
156
|
+
|
|
157
|
+
// resize camera
|
|
158
|
+
this._controller.object.OnResize(clientWidth, clientHeight);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// reset camera
|
|
162
|
+
this._controller.object.position.copy(this._cameraPosition);
|
|
163
|
+
this._controller.target.copy(this._cameraTarget);
|
|
164
|
+
|
|
165
|
+
// reset camera values
|
|
166
|
+
this._cameraPosition.set(0, 0, 0);
|
|
167
|
+
this._cameraTarget.set(0, 0, 0);
|
|
168
|
+
|
|
169
|
+
// dispose xr scene
|
|
170
|
+
this._scene.DisposeXR();
|
|
171
|
+
|
|
172
|
+
this._session.removeEventListener('end', this._onSessionEnded);
|
|
173
|
+
DIVEWebXR._overlay!.Element.style.display = 'none';
|
|
174
|
+
this._session = null;
|
|
175
|
+
}
|
|
176
|
+
}
|