@shopware-ag/dive 1.16.25 → 1.16.26-beta.1

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.
Files changed (44) hide show
  1. package/build/dive.cjs +1656 -203
  2. package/build/dive.cjs.map +1 -1
  3. package/build/dive.d.cts +50 -8
  4. package/build/dive.d.ts +50 -8
  5. package/build/dive.js +1623 -159
  6. package/build/dive.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/ar/AR.ts +164 -0
  9. package/src/ar/arquicklook/ARQuickLook.ts +42 -0
  10. package/src/ar/webxr/WebXR.ts +176 -0
  11. package/src/ar/webxr/controller/WebXRController.ts +334 -0
  12. package/src/ar/webxr/crosshair/WebXRCrosshair.ts +35 -0
  13. package/src/ar/webxr/origin/WebXROrigin.ts +191 -0
  14. package/src/ar/webxr/overlay/Overlay.ts +50 -0
  15. package/src/ar/webxr/raycaster/WebXRRaycaster.ts +131 -0
  16. package/src/ar/webxr/raycaster/ar/WebXRRaycasterAR.ts +102 -0
  17. package/src/ar/webxr/raycaster/three/WebXRRaycasterTHREE.ts +49 -0
  18. package/src/ar/webxr/touchscreencontrols/WebXRTouchscreenControls.ts +356 -0
  19. package/src/axiscamera/AxisCamera.ts +4 -4
  20. package/src/axiscamera/__test__/AxisCamera.test.ts +4 -0
  21. package/src/com/Communication.ts +17 -0
  22. package/src/com/__test__/Communication.test.ts +1 -1
  23. package/src/com/actions/index.ts +2 -0
  24. package/src/dive.ts +55 -9
  25. package/src/events/EventExecutor.ts +35 -0
  26. package/src/helper/findSceneRecursive/findSceneRecursive.ts +2 -2
  27. package/src/info/Info.ts +37 -1
  28. package/src/info/__test__/Info.test.ts +45 -5
  29. package/src/mediacreator/MediaCreator.ts +4 -4
  30. package/src/mediacreator/__test__/MediaCreator.test.ts +7 -2
  31. package/src/renderer/Renderer.ts +21 -11
  32. package/src/renderer/__test__/Renderer.test.ts +19 -1
  33. package/src/scene/Scene.ts +35 -12
  34. package/src/scene/__test__/Scene.test.ts +39 -5
  35. package/src/scene/root/Root.ts +1 -0
  36. package/src/scene/xrroot/XRRoot.ts +56 -0
  37. package/src/scene/xrroot/xrlightroot/XRLightRoot.ts +80 -0
  38. package/src/toolbox/BaseTool.ts +9 -3
  39. package/src/toolbox/Toolbox.ts +1 -1
  40. package/src/toolbox/__test__/Toolbox.test.ts +1 -1
  41. package/src/toolbox/select/SelectTool.ts +1 -1
  42. package/src/toolbox/select/__test__/SelectTool.test.ts +1 -1
  43. package/src/toolbox/transform/TransformTool.ts +4 -4
  44. package/src/toolbox/transform/__test__/TransformTool.test.ts +2 -4
@@ -0,0 +1,334 @@
1
+ import {
2
+ Matrix4,
3
+ Mesh,
4
+ Object3D,
5
+ Quaternion,
6
+ Vector3,
7
+ WebXRArrayCamera,
8
+ } from 'three';
9
+ import { DIVERenderer } from '../../../renderer/Renderer';
10
+ import { DIVEScene } from '../../../scene/Scene';
11
+ import { DIVEWebXRCrosshair } from '../crosshair/WebXRCrosshair';
12
+ import { DIVEWebXRRaycaster } from '../raycaster/WebXRRaycaster';
13
+ import { DIVEWebXROrigin } from '../origin/WebXROrigin';
14
+ import {
15
+ DIVETouchscreenEvents,
16
+ DIVEWebXRTouchscreenControls,
17
+ } from '../touchscreencontrols/WebXRTouchscreenControls';
18
+ import { type DIVEMovable } from '../.././../interface/Movable';
19
+ import { findInterface } from '../../../helper/findInterface/findInterface';
20
+
21
+ export class DIVEWebXRController extends Object3D {
22
+ // general members
23
+ private _renderer: DIVERenderer;
24
+ private _scene: DIVEScene;
25
+ private _session: XRSession;
26
+
27
+ private _frameBuffer: XRFrame | null = null;
28
+
29
+ // raycaster members
30
+ private _xrRaycaster: DIVEWebXRRaycaster;
31
+ private _origin: DIVEWebXROrigin;
32
+
33
+ // crosshair
34
+ private _crosshair: DIVEWebXRCrosshair;
35
+
36
+ // controller members
37
+ private _touchscreenControls: DIVEWebXRTouchscreenControls;
38
+ private _handNodeInitialPosition = new Vector3();
39
+ private _xrCamera: WebXRArrayCamera;
40
+ private _placed: boolean = false;
41
+
42
+ // grabbing
43
+ private _grabbedObject: Object3D | null = null;
44
+ private _arHitPosition: Vector3 = new Vector3();
45
+ private _arHitQuaternion: Quaternion = new Quaternion();
46
+ private _arHitScale: Vector3 = new Vector3(1, 1, 1);
47
+
48
+ // grabbing position
49
+ private _initialObjectPosition: Vector3 | null = null;
50
+ private _initialRaycastHit: Vector3 | null = null;
51
+ private _deltaRaycastHit: Vector3 = new Vector3();
52
+
53
+ // grabbing rotation
54
+ private _touchQuaterion: Quaternion = new Quaternion();
55
+
56
+ // grabbing scale
57
+ private _touchScale: number = 1;
58
+ private _scaleThreshold: number = 0.1;
59
+
60
+ constructor(session: XRSession, renderer: DIVERenderer, scene: DIVEScene) {
61
+ super();
62
+
63
+ this._renderer = renderer;
64
+ this._scene = scene;
65
+ this._session = session;
66
+
67
+ this._xrRaycaster = new DIVEWebXRRaycaster(session, renderer, scene);
68
+ this._origin = new DIVEWebXROrigin(this._session, this._renderer, [
69
+ 'plane',
70
+ ]);
71
+
72
+ this._crosshair = new DIVEWebXRCrosshair();
73
+ this._crosshair.visible = false;
74
+
75
+ this._xrCamera = this._renderer.xr.getCamera();
76
+
77
+ this._scene.XRRoot.XRHandNode.position.set(0, -0.05, -0.25);
78
+ this._handNodeInitialPosition =
79
+ this._scene.XRRoot.XRHandNode.position.clone();
80
+
81
+ this._touchscreenControls = new DIVEWebXRTouchscreenControls(
82
+ this._session,
83
+ );
84
+
85
+ // translating
86
+ this._touchscreenControls.Subscribe('TOUCH_START', () =>
87
+ this.onTouchStart(),
88
+ );
89
+ this._touchscreenControls.Subscribe('TOUCH_MOVE', () =>
90
+ this.onTouchMove(),
91
+ );
92
+ this._touchscreenControls.Subscribe('TOUCH_END', (p) =>
93
+ this.onTouchEnd(p),
94
+ );
95
+
96
+ // rotating
97
+ this._touchscreenControls.Subscribe('ROTATE_START', () =>
98
+ this.onRotateStart(),
99
+ );
100
+ this._touchscreenControls.Subscribe('ROTATE_MOVE', (p) =>
101
+ this.onRotateMove(p),
102
+ );
103
+
104
+ // scaling
105
+ this._touchscreenControls.Subscribe('PINCH_START', () =>
106
+ this.onPinchStart(),
107
+ );
108
+ this._touchscreenControls.Subscribe('PINCH_MOVE', (p) =>
109
+ this.onPinchMove(p),
110
+ );
111
+ }
112
+
113
+ public async Init(): Promise<this> {
114
+ this.prepareScene();
115
+
116
+ await this.initOrigin();
117
+ await this.initRaycaster();
118
+
119
+ return Promise.resolve(this);
120
+ }
121
+
122
+ public Dispose(): void {
123
+ this.restoreScene();
124
+
125
+ this._origin.Dispose();
126
+ this._xrRaycaster.Dispose();
127
+
128
+ // reset placement members
129
+ this._placed = false;
130
+ }
131
+
132
+ public Update(frame: XRFrame): void {
133
+ this._frameBuffer = frame;
134
+
135
+ if (!this._placed) {
136
+ this._xrCamera.updateMatrixWorld();
137
+ this._scene.XRRoot.XRHandNode.position.copy(
138
+ this._handNodeInitialPosition
139
+ .clone()
140
+ .applyMatrix4(this._xrCamera.matrixWorld),
141
+ );
142
+ this._scene.XRRoot.XRHandNode.quaternion.setFromRotationMatrix(
143
+ this._xrCamera.matrixWorld,
144
+ );
145
+
146
+ if (this._origin) {
147
+ this._origin.Update(frame);
148
+ }
149
+ }
150
+ }
151
+
152
+ // placement
153
+ private async initOrigin(): Promise<void> {
154
+ // initialize origin
155
+ this._origin = await this._origin.Init();
156
+
157
+ // set resolve callback: place objects at origin when it is set
158
+ this._origin.originSet.then(() => {
159
+ this.placeObjects(this._origin.matrix);
160
+ });
161
+ }
162
+
163
+ private placeObjects(matrix: Matrix4): void {
164
+ this._scene.XRRoot.XRModelRoot.matrix.copy(matrix);
165
+ [...this._scene.XRRoot.XRHandNode.children].forEach((child) => {
166
+ this._scene.XRRoot.XRModelRoot.add(child);
167
+ });
168
+ this._placed = true;
169
+ }
170
+
171
+ // grabbing
172
+ private updateObject(): void {
173
+ if (!this._grabbedObject) return;
174
+
175
+ this._grabbedObject.position.copy(this._arHitPosition);
176
+ this._grabbedObject.quaternion.copy(
177
+ this._arHitQuaternion.clone().multiply(this._touchQuaterion),
178
+ );
179
+ this._grabbedObject.scale.copy(
180
+ new Vector3(
181
+ this._touchScale,
182
+ this._touchScale,
183
+ this._touchScale,
184
+ ).multiply(this._arHitScale),
185
+ );
186
+ }
187
+
188
+ private onTouchStart(): void {
189
+ const sceneHits = this._xrRaycaster.GetSceneIntersections();
190
+ console.log('sceneHits', sceneHits);
191
+ if (sceneHits.length === 0) return;
192
+ if (!sceneHits[0].object) return;
193
+
194
+ const moveable = findInterface<DIVEMovable>(
195
+ sceneHits[0].object,
196
+ 'isMovable',
197
+ );
198
+ if (!moveable) return;
199
+
200
+ this._grabbedObject = moveable;
201
+ }
202
+
203
+ private onTouchMove(): void {
204
+ // raycast ar
205
+ if (!this._frameBuffer) return;
206
+ if (!this._grabbedObject) return;
207
+
208
+ const intersections = this._xrRaycaster.GetARIntersections(
209
+ this._frameBuffer,
210
+ );
211
+ if (intersections.length === 0) {
212
+ this._crosshair.visible = false;
213
+ return;
214
+ }
215
+
216
+ const hit = intersections[0];
217
+
218
+ this._crosshair.visible = true;
219
+ this._crosshair.matrix.copy(hit.matrix);
220
+
221
+ if (!this._grabbedObject) return;
222
+
223
+ // if initial values have been reset by TOUCH_END event then set them again
224
+ if (!this._initialObjectPosition || !this._initialRaycastHit) {
225
+ this._initialObjectPosition = this._grabbedObject.position.clone();
226
+ this._initialRaycastHit = hit.point.clone();
227
+ }
228
+
229
+ // decompose hit matrix to apply hit matrix to object
230
+ hit.matrix.decompose(
231
+ this._arHitPosition,
232
+ this._arHitQuaternion,
233
+ this._arHitScale,
234
+ );
235
+
236
+ // calculate raycast hit delta
237
+ this._deltaRaycastHit.copy(
238
+ hit.point.clone().sub(this._initialRaycastHit),
239
+ );
240
+
241
+ // apply moved raycast delta to actual object position
242
+ this._arHitPosition.copy(
243
+ this._initialObjectPosition.clone().add(this._deltaRaycastHit),
244
+ );
245
+
246
+ console.log('arHitPosition', this._arHitPosition);
247
+
248
+ this.updateObject();
249
+ }
250
+
251
+ private onTouchEnd(payload: DIVETouchscreenEvents['TOUCH_END']): void {
252
+ if (payload.touchCount === 0) {
253
+ this._crosshair.visible = false;
254
+
255
+ // reset grab
256
+ this._initialObjectPosition = null;
257
+ this._initialRaycastHit = null;
258
+ this._grabbedObject = null;
259
+ }
260
+ }
261
+
262
+ private _startTouchQuaternion: Quaternion = new Quaternion();
263
+ private onRotateStart(): void {
264
+ this._startTouchQuaternion = this._touchQuaterion.clone();
265
+ }
266
+
267
+ private onRotateMove(payload: DIVETouchscreenEvents['ROTATE_MOVE']): void {
268
+ this._touchQuaterion.setFromAxisAngle(
269
+ new Vector3(0, -1, 0),
270
+ payload.delta * 3,
271
+ );
272
+ this._touchQuaterion.multiply(this._startTouchQuaternion);
273
+ this.updateObject();
274
+ }
275
+
276
+ private _startTouchScale: number = 1;
277
+ private onPinchStart(): void {
278
+ this._startTouchScale = this._touchScale;
279
+ }
280
+
281
+ private onPinchMove(payload: DIVETouchscreenEvents['PINCH_MOVE']): void {
282
+ this._touchScale = this._startTouchScale * payload.current;
283
+ this.updateObject();
284
+ }
285
+
286
+ // prepare & cleanup scene
287
+ private prepareScene(): void {
288
+ this._scene.XRRoot.XRModelRoot.matrixAutoUpdate = false;
289
+
290
+ // initialize crosshair
291
+ this._scene.add(this._crosshair);
292
+
293
+ // hang current scene children to hang node
294
+ const children: Object3D[] = [];
295
+ this._scene.Root.children.forEach((child) => {
296
+ const clone = child.clone();
297
+ clone.layers.enableAll();
298
+ clone.traverse((obj) => {
299
+ obj.layers.enableAll();
300
+ if (obj instanceof Mesh) {
301
+ obj.scale.set(0.1, 0.1, 0.1);
302
+ }
303
+ });
304
+ clone.position.set(0, 0, 0);
305
+ children.push(clone);
306
+ });
307
+ this._scene.XRRoot.XRHandNode.add(...children);
308
+ }
309
+
310
+ private restoreScene(): void {
311
+ this._scene.remove(this._crosshair);
312
+
313
+ // clear hang node and remove attached models
314
+ this._scene.XRRoot.XRHandNode.clear();
315
+ this._scene.XRRoot.XRModelRoot.clear();
316
+
317
+ this._scene.XRRoot.XRModelRoot.matrixAutoUpdate = true;
318
+ }
319
+
320
+ // raycast
321
+ private async initRaycaster(): Promise<void> {
322
+ // initialize raycaster
323
+ await this._xrRaycaster.Init();
324
+
325
+ // check if successful
326
+ if (!this._xrRaycaster) {
327
+ console.error(
328
+ 'Raycaster not initialized successfully. Aborting WebXR...',
329
+ );
330
+ this.Dispose();
331
+ return Promise.reject();
332
+ }
333
+ }
334
+ }
@@ -0,0 +1,35 @@
1
+ import { Mesh, MeshBasicMaterial, Object3D, RingGeometry } from 'three';
2
+
3
+ export class DIVEWebXRCrosshair extends Object3D {
4
+ public set mesh(mesh: Mesh | undefined) {
5
+ this.clear();
6
+
7
+ if (mesh) {
8
+ this.add(mesh);
9
+ }
10
+ }
11
+
12
+ constructor(mesh?: Mesh) {
13
+ super();
14
+
15
+ if (mesh) {
16
+ this.mesh = mesh;
17
+ } else {
18
+ this.UseDefaultMesh();
19
+ }
20
+
21
+ this.matrixAutoUpdate = false;
22
+
23
+ return this;
24
+ }
25
+
26
+ public UseDefaultMesh(): void {
27
+ const geometry = new RingGeometry(0.08, 0.1, 32).rotateX(-Math.PI / 2);
28
+ const material = new MeshBasicMaterial();
29
+ this.mesh = new Mesh(geometry, material);
30
+ }
31
+
32
+ public UpdateFromPose(pose: XRPose): void {
33
+ this.matrix.fromArray(pose.transform.matrix);
34
+ }
35
+ }
@@ -0,0 +1,191 @@
1
+ import { Matrix4, Quaternion, Vector3 } from 'three';
2
+ import { DIVERenderer } from '../../../renderer/Renderer';
3
+
4
+ export class DIVEWebXROrigin {
5
+ private _renderer: DIVERenderer;
6
+ private _session: XRSession;
7
+
8
+ private _requesting: boolean;
9
+ private _initialized: boolean;
10
+
11
+ private _referenceSpaceBuffer: XRReferenceSpace | null;
12
+ private _hitTestSource: XRHitTestSource | null;
13
+
14
+ private _entityTypes: XRHitTestTrackableType[];
15
+
16
+ private _hitTestResultBuffer: XRHitTestResult[];
17
+ private _raycastHitCounter: number = 0;
18
+
19
+ private _originSet: Promise<void>;
20
+ private _originSetResolve: (value: void) => void = () => {};
21
+ public get originSet(): Promise<void> {
22
+ return this._originSet;
23
+ }
24
+
25
+ private _matrix: Matrix4;
26
+ public get matrix(): Matrix4 {
27
+ return this._matrix;
28
+ }
29
+ private set matrix(value: Matrix4) {
30
+ this._matrix = value;
31
+ this._matrix.decompose(this._position, this._quaternion, this._scale);
32
+ }
33
+
34
+ private _position: Vector3;
35
+ public get position(): Vector3 {
36
+ return this._position;
37
+ }
38
+
39
+ private _quaternion: Quaternion;
40
+ public get quaternion(): Quaternion {
41
+ return this._quaternion;
42
+ }
43
+
44
+ private _scale: Vector3;
45
+ public get scale(): Vector3 {
46
+ return this._scale;
47
+ }
48
+
49
+ constructor(
50
+ session: XRSession,
51
+ renderer: DIVERenderer,
52
+ entityTypes?: XRHitTestTrackableType[],
53
+ ) {
54
+ this._renderer = renderer;
55
+ this._session = session;
56
+
57
+ this._originSet = new Promise<void>((resolve) => {
58
+ this._originSetResolve = resolve;
59
+ });
60
+
61
+ this._requesting = false;
62
+ this._initialized = false;
63
+
64
+ this._referenceSpaceBuffer = null;
65
+ this._hitTestSource = null;
66
+
67
+ this._entityTypes = entityTypes || ['plane'];
68
+
69
+ this._hitTestResultBuffer = [];
70
+
71
+ // set up promises and executors
72
+ this._matrix = new Matrix4();
73
+ this._position = new Vector3();
74
+ this._quaternion = new Quaternion();
75
+ this._scale = new Vector3();
76
+
77
+ // when origin is set, decompose matrix into position, quaternion and scale
78
+ this._originSet.then(() => {
79
+ // decompose matrix into position, quaternion and scale
80
+ this._matrix.decompose(
81
+ this._position,
82
+ this._quaternion,
83
+ this._scale,
84
+ );
85
+ });
86
+ }
87
+
88
+ public async Init(): Promise<this> {
89
+ if (this._initialized) {
90
+ return Promise.resolve(this);
91
+ }
92
+
93
+ if (!this._session) {
94
+ console.error(
95
+ 'DIVEWebXROrigin: No session set in Init()! Aborting initialization...',
96
+ );
97
+ return Promise.reject();
98
+ }
99
+
100
+ if (this._requesting) {
101
+ console.error(
102
+ 'DIVEWebXROrigin: Currently initializing! Aborting initialization...',
103
+ );
104
+ return Promise.reject();
105
+ }
106
+
107
+ this._requesting = true;
108
+ const referenceSpace =
109
+ await this._session.requestReferenceSpace('viewer');
110
+ this._hitTestSource =
111
+ (await this._session.requestHitTestSource!({
112
+ space: referenceSpace,
113
+ entityTypes: this._entityTypes,
114
+ })) || null;
115
+ this._requesting = false;
116
+
117
+ if (!this._hitTestSource) {
118
+ return Promise.reject();
119
+ }
120
+
121
+ this._initialized = true;
122
+
123
+ return Promise.resolve(this);
124
+ }
125
+
126
+ public Dispose(): void {
127
+ this._initialized = false;
128
+ this._requesting = false;
129
+
130
+ this._hitTestSource?.cancel();
131
+
132
+ this._hitTestSource = null;
133
+ this._hitTestResultBuffer = [];
134
+
135
+ this._matrix = new Matrix4();
136
+ this._position = new Vector3();
137
+ this._quaternion = new Quaternion();
138
+ this._scale = new Vector3();
139
+ }
140
+
141
+ public Update(frame: XRFrame): void {
142
+ if (!this._initialized) return;
143
+
144
+ if (!this._hitTestSource) {
145
+ throw new Error(
146
+ 'DIVEWebXRRaycaster: Critical Error: HitTestSource not available but WebXROrigin is initialized!',
147
+ );
148
+ }
149
+
150
+ // get hit test results
151
+ this._hitTestResultBuffer = frame.getHitTestResults(
152
+ this._hitTestSource,
153
+ );
154
+ if (this._hitTestResultBuffer.length > 0) {
155
+ // hit found
156
+ this._referenceSpaceBuffer = this._renderer.xr.getReferenceSpace();
157
+
158
+ // if there is no reference space, hit will be counted as lost for this frame
159
+ if (!this._referenceSpaceBuffer) {
160
+ this.onHitLost();
161
+ return;
162
+ }
163
+
164
+ const pose = this._hitTestResultBuffer[0].getPose(
165
+ this._referenceSpaceBuffer,
166
+ );
167
+ if (!pose) {
168
+ this.onHitLost();
169
+ return;
170
+ }
171
+
172
+ this.onHitFound(pose);
173
+ } else {
174
+ this.onHitLost();
175
+ }
176
+ }
177
+
178
+ private onHitFound(pose: XRPose): void {
179
+ this._raycastHitCounter++;
180
+
181
+ this.matrix.fromArray(pose.transform.matrix);
182
+
183
+ if (this._raycastHitCounter > 50) {
184
+ this._originSetResolve();
185
+ }
186
+ }
187
+
188
+ private onHitLost(): void {
189
+ this._raycastHitCounter = 0;
190
+ }
191
+ }
@@ -0,0 +1,50 @@
1
+ export class Overlay {
2
+ private _element: HTMLDivElement;
3
+ public get Element(): HTMLDivElement {
4
+ return this._element;
5
+ }
6
+
7
+ private _closeButton: SVGSVGElement;
8
+ public get CloseButton(): SVGSVGElement {
9
+ return this._closeButton;
10
+ }
11
+
12
+ constructor() {
13
+ // create div
14
+ this._element = document.createElement('div');
15
+
16
+ // create and append close button to overlay
17
+ this._closeButton = this.createCloseButton();
18
+ this._element.appendChild(this._closeButton);
19
+
20
+ // append overlay to body
21
+ document.body.appendChild(this._element);
22
+ }
23
+
24
+ private createCloseButton(): SVGSVGElement {
25
+ // create svg path
26
+ const path = document.createElementNS(
27
+ 'http://www.w3.org/2000/svg',
28
+ 'path',
29
+ );
30
+ path.setAttribute('d', 'M 12,12 L 28,28 M 28,12 12,28');
31
+ path.setAttribute('stroke', '#fff');
32
+ path.setAttribute('stroke-width', '2');
33
+
34
+ // create svg
35
+ const svg = document.createElementNS(
36
+ 'http://www.w3.org/2000/svg',
37
+ 'svg',
38
+ );
39
+ svg.setAttribute('width', '38');
40
+ svg.setAttribute('height', '38');
41
+ svg.style.position = 'absolute';
42
+ svg.style.right = '20px';
43
+ svg.style.top = '20px';
44
+
45
+ // append path to svg
46
+ svg.appendChild(path);
47
+
48
+ return svg;
49
+ }
50
+ }