@shopware-ag/dive 1.16.26-beta.2 → 1.17.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 +8 -0
- package/build/dive.cjs +133 -111
- package/build/dive.cjs.map +1 -1
- package/build/dive.d.cts +16 -1
- package/build/dive.d.ts +16 -1
- package/build/dive.js +134 -110
- package/build/dive.js.map +1 -1
- package/package.json +3 -2
- package/src/__test__/DIVE.test.ts +2 -2
- package/src/ar/AR.ts +51 -125
- package/src/ar/__test__/AR.test.ts +187 -0
- package/src/ar/arquicklook/ARQuickLook.ts +29 -7
- package/src/ar/arquicklook/__test__/ARQuickLook.test.ts +268 -0
- package/src/ar/sceneviewer/SceneViewer.ts +74 -0
- package/src/ar/sceneviewer/__test__/SceneViewer.test.ts +245 -0
- package/src/ar/webxr/controller/WebXRController.ts +17 -11
- package/src/ar/webxr/origin/WebXROrigin.ts +1 -0
- package/src/com/Communication.ts +4 -3
- package/src/com/__test__/Communication.test.ts +47 -2
- package/src/com/actions/index.ts +2 -2
- package/src/com/actions/scene/launchar.ts +7 -0
- package/src/dive.ts +0 -16
- package/src/exporters/usdz/USDZExporter.ts +21 -0
- package/src/group/__test__/Group.test.ts +2 -0
- package/src/scene/__test__/Scene.test.ts +10 -0
- package/src/toolbox/transform/__test__/TransformTool.test.ts +4 -1
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { Box3, Color, Euler, Mesh, Object3D, Vector3 } from 'three';
|
|
2
|
+
import { DIVEScene } from '../../../scene/Scene';
|
|
3
|
+
import { DIVEAROptions } from '../../AR';
|
|
4
|
+
import { DIVEARQuickLook } from '../ARQuickLook';
|
|
5
|
+
|
|
6
|
+
jest.mock('three', () => {
|
|
7
|
+
return {
|
|
8
|
+
Vector3: jest.fn(function (
|
|
9
|
+
x: number = 0,
|
|
10
|
+
y: number = 0,
|
|
11
|
+
z: number = 0,
|
|
12
|
+
) {
|
|
13
|
+
this.x = x;
|
|
14
|
+
this.y = y;
|
|
15
|
+
this.z = z;
|
|
16
|
+
this.copy = (vec3: Vector3) => {
|
|
17
|
+
this.x = vec3.x;
|
|
18
|
+
this.y = vec3.y;
|
|
19
|
+
this.z = vec3.z;
|
|
20
|
+
return this;
|
|
21
|
+
};
|
|
22
|
+
this.set = (x: number, y: number, z: number) => {
|
|
23
|
+
this.x = x;
|
|
24
|
+
this.y = y;
|
|
25
|
+
this.z = z;
|
|
26
|
+
return this;
|
|
27
|
+
};
|
|
28
|
+
this.multiply = (vec3: Vector3) => {
|
|
29
|
+
this.x *= vec3.x;
|
|
30
|
+
this.y *= vec3.y;
|
|
31
|
+
this.z *= vec3.z;
|
|
32
|
+
return this;
|
|
33
|
+
};
|
|
34
|
+
this.clone = () => {
|
|
35
|
+
return new Vector3(this.x, this.y, this.z);
|
|
36
|
+
};
|
|
37
|
+
this.setY = (y: number) => {
|
|
38
|
+
this.y = y;
|
|
39
|
+
return this;
|
|
40
|
+
};
|
|
41
|
+
this.add = (vec3: Vector3) => {
|
|
42
|
+
this.x += vec3.x;
|
|
43
|
+
this.y += vec3.y;
|
|
44
|
+
this.z += vec3.z;
|
|
45
|
+
return this;
|
|
46
|
+
};
|
|
47
|
+
this.sub = (vec3: Vector3) => {
|
|
48
|
+
this.x -= vec3.x;
|
|
49
|
+
this.y -= vec3.y;
|
|
50
|
+
this.z -= vec3.z;
|
|
51
|
+
return this;
|
|
52
|
+
};
|
|
53
|
+
return this;
|
|
54
|
+
}),
|
|
55
|
+
Euler: jest.fn(function () {
|
|
56
|
+
this.set = jest.fn();
|
|
57
|
+
return this;
|
|
58
|
+
}),
|
|
59
|
+
Object3D: jest.fn(function () {
|
|
60
|
+
this.clear = jest.fn();
|
|
61
|
+
this.color = {};
|
|
62
|
+
this.intensity = 0;
|
|
63
|
+
this.layers = {
|
|
64
|
+
mask: 0,
|
|
65
|
+
};
|
|
66
|
+
this.shadow = {
|
|
67
|
+
radius: 0,
|
|
68
|
+
mapSize: { width: 0, height: 0 },
|
|
69
|
+
bias: 0,
|
|
70
|
+
camera: {
|
|
71
|
+
near: 0,
|
|
72
|
+
far: 0,
|
|
73
|
+
fov: 0,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
this.add = jest.fn();
|
|
77
|
+
this.sub = jest.fn();
|
|
78
|
+
this.children = [
|
|
79
|
+
{
|
|
80
|
+
visible: true,
|
|
81
|
+
material: {
|
|
82
|
+
color: {},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
this.userData = {};
|
|
87
|
+
this.position = new Vector3();
|
|
88
|
+
this.rotation = new Euler();
|
|
89
|
+
this.scale = new Vector3(1, 1, 1);
|
|
90
|
+
this.localToWorld = (vec3: Vector3) => {
|
|
91
|
+
return vec3;
|
|
92
|
+
};
|
|
93
|
+
this.mesh = new Mesh();
|
|
94
|
+
this.traverse = jest.fn((callback) => {
|
|
95
|
+
callback(this.children[0]);
|
|
96
|
+
});
|
|
97
|
+
this.getWorldPosition = jest.fn(() => {
|
|
98
|
+
return new Vector3();
|
|
99
|
+
});
|
|
100
|
+
return this;
|
|
101
|
+
}),
|
|
102
|
+
Box3: jest.fn(function () {
|
|
103
|
+
this.min = new Vector3(Infinity, Infinity, Infinity);
|
|
104
|
+
this.max = new Vector3(-Infinity, -Infinity, -Infinity);
|
|
105
|
+
this.getCenter = jest.fn(() => {
|
|
106
|
+
return new Vector3(0, 0, 0);
|
|
107
|
+
});
|
|
108
|
+
this.expandByObject = jest.fn();
|
|
109
|
+
|
|
110
|
+
return this;
|
|
111
|
+
}),
|
|
112
|
+
Mesh: jest.fn(function () {
|
|
113
|
+
this.geometry = {
|
|
114
|
+
computeBoundingBox: jest.fn(),
|
|
115
|
+
boundingBox: new Box3(),
|
|
116
|
+
};
|
|
117
|
+
this.material = {};
|
|
118
|
+
this.castShadow = true;
|
|
119
|
+
this.receiveShadow = true;
|
|
120
|
+
this.layers = {
|
|
121
|
+
mask: 0,
|
|
122
|
+
};
|
|
123
|
+
this.updateWorldMatrix = jest.fn();
|
|
124
|
+
this.traverse = jest.fn();
|
|
125
|
+
this.removeFromParent = jest.fn();
|
|
126
|
+
this.localToWorld = (vec3: Vector3) => {
|
|
127
|
+
return vec3;
|
|
128
|
+
};
|
|
129
|
+
return this;
|
|
130
|
+
}),
|
|
131
|
+
MeshStandardMaterial: jest.fn(function () {
|
|
132
|
+
this.color = new Color();
|
|
133
|
+
this.roughness = 1;
|
|
134
|
+
this.roughnessMap = undefined;
|
|
135
|
+
this.metalness = 0;
|
|
136
|
+
this.metalnessMap = undefined;
|
|
137
|
+
return this;
|
|
138
|
+
}),
|
|
139
|
+
Color: jest.fn(function () {
|
|
140
|
+
this.set = jest.fn();
|
|
141
|
+
return this;
|
|
142
|
+
}),
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
jest.mock('../../../exporters/usdz/USDZExporter', () => {
|
|
147
|
+
return {
|
|
148
|
+
DIVEUSDZExporter: jest.fn().mockImplementation(() => {
|
|
149
|
+
return {
|
|
150
|
+
parse: jest.fn(() => {
|
|
151
|
+
return Promise.resolve(new Uint8Array());
|
|
152
|
+
}),
|
|
153
|
+
};
|
|
154
|
+
}),
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
URL.createObjectURL = jest.fn(() => 'blob:http://localhost:8080/');
|
|
159
|
+
|
|
160
|
+
describe('DIVEARQuickLook', () => {
|
|
161
|
+
let mockScene: DIVEScene;
|
|
162
|
+
let mockOptions: DIVEAROptions;
|
|
163
|
+
let mockModels: Object3D[];
|
|
164
|
+
|
|
165
|
+
beforeEach(() => {
|
|
166
|
+
mockModels = [
|
|
167
|
+
new Object3D(),
|
|
168
|
+
new Object3D(),
|
|
169
|
+
];
|
|
170
|
+
mockScene = {
|
|
171
|
+
Root: new Object3D(),
|
|
172
|
+
} as DIVEScene;
|
|
173
|
+
mockOptions = {
|
|
174
|
+
arPlacement: 'horizontal',
|
|
175
|
+
arScale: 'auto',
|
|
176
|
+
} as DIVEAROptions;
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('Launch', () => {
|
|
180
|
+
it('should be a function', () => {
|
|
181
|
+
expect(DIVEARQuickLook.Launch).toBeInstanceOf(Function);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should return a promise', () => {
|
|
185
|
+
expect(
|
|
186
|
+
DIVEARQuickLook.Launch(mockScene, mockOptions),
|
|
187
|
+
).toBeInstanceOf(Promise);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should not throw when called without options', () => {
|
|
191
|
+
const usdzParseSpy = jest.spyOn(
|
|
192
|
+
DIVEARQuickLook['_usdzExporter'],
|
|
193
|
+
'parse',
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
expect(
|
|
197
|
+
async () => await DIVEARQuickLook.Launch(mockScene),
|
|
198
|
+
).not.toThrow();
|
|
199
|
+
|
|
200
|
+
expect(usdzParseSpy).toHaveBeenCalled();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should not throw when called with empty scene', () => {
|
|
204
|
+
const usdzParseSpy = jest.spyOn(
|
|
205
|
+
DIVEARQuickLook['_usdzExporter'],
|
|
206
|
+
'parse',
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
expect(
|
|
210
|
+
async () =>
|
|
211
|
+
await DIVEARQuickLook.Launch(mockScene, mockOptions),
|
|
212
|
+
).not.toThrow();
|
|
213
|
+
|
|
214
|
+
expect(usdzParseSpy).toHaveBeenCalled();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should not throw when called with filled scene', () => {
|
|
218
|
+
const usdzParseSpy = jest.spyOn(
|
|
219
|
+
DIVEARQuickLook['_usdzExporter'],
|
|
220
|
+
'parse',
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
mockScene.Root.children = mockModels;
|
|
224
|
+
|
|
225
|
+
expect(
|
|
226
|
+
async () =>
|
|
227
|
+
await DIVEARQuickLook.Launch(mockScene, mockOptions),
|
|
228
|
+
).not.toThrow();
|
|
229
|
+
|
|
230
|
+
expect(usdzParseSpy).toHaveBeenCalled();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should pass options to exporter', async () => {
|
|
234
|
+
const usdzParseSpy = jest.spyOn(
|
|
235
|
+
DIVEARQuickLook['_usdzExporter'],
|
|
236
|
+
'parse',
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
mockOptions.arPlacement = 'vertical';
|
|
240
|
+
mockOptions.arScale = 'fixed';
|
|
241
|
+
|
|
242
|
+
await DIVEARQuickLook.Launch(mockScene, mockOptions);
|
|
243
|
+
|
|
244
|
+
expect(usdzParseSpy).toHaveBeenCalledWith(expect.any(Object3D), {
|
|
245
|
+
quickLookCompatible: true,
|
|
246
|
+
ar: {
|
|
247
|
+
anchoring: { type: 'plane' },
|
|
248
|
+
planeAnchoring: {
|
|
249
|
+
alignment: 'vertical',
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should reject when USDZExporter fails', async () => {
|
|
256
|
+
const usdzParseSpy = jest.spyOn(
|
|
257
|
+
DIVEARQuickLook['_usdzExporter'],
|
|
258
|
+
'parse',
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
usdzParseSpy.mockReturnValueOnce(Promise.reject());
|
|
262
|
+
|
|
263
|
+
await expect(
|
|
264
|
+
DIVEARQuickLook.Launch(mockScene, mockOptions),
|
|
265
|
+
).rejects.toBeUndefined();
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type DIVEScene } from '../../scene/Scene';
|
|
2
|
+
import { type DIVEAROptions } from '../AR';
|
|
3
|
+
|
|
4
|
+
export class DIVESceneViewer {
|
|
5
|
+
public static Launch(scene: DIVEScene, options?: DIVEAROptions): void {
|
|
6
|
+
// find url in scene (first object found that has a set uri)
|
|
7
|
+
const url = this.findSceneViewerSrc(scene);
|
|
8
|
+
|
|
9
|
+
// launch SceneViewer
|
|
10
|
+
this.launchSceneViewer(url, options);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
private static launchSceneViewer(
|
|
14
|
+
url: string,
|
|
15
|
+
options?: DIVEAROptions,
|
|
16
|
+
): void {
|
|
17
|
+
const anchor = document.createElement('a');
|
|
18
|
+
const noArViewerSigil = '#model-viewer-no-ar-fallback';
|
|
19
|
+
|
|
20
|
+
const location = self.location.toString();
|
|
21
|
+
const locationUrl = new URL(location);
|
|
22
|
+
const modelUrl = new URL(url, location);
|
|
23
|
+
const params = new URLSearchParams(modelUrl.search);
|
|
24
|
+
|
|
25
|
+
locationUrl.hash = noArViewerSigil;
|
|
26
|
+
|
|
27
|
+
// modelUrl can contain title/link/sound etc.
|
|
28
|
+
params.set('mode', 'ar_only');
|
|
29
|
+
|
|
30
|
+
if (options?.arScale === 'fixed') {
|
|
31
|
+
params.set('resizable', 'false');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (options?.arPlacement === 'vertical') {
|
|
35
|
+
params.set('enable_vertical_placement', 'true');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// will be added later if needed
|
|
39
|
+
// if (params.has('sound')) {
|
|
40
|
+
// const soundUrl = new URL(params.get('sound')!, location);
|
|
41
|
+
// params.set('sound', soundUrl.toString());
|
|
42
|
+
// }
|
|
43
|
+
// if (params.has('link')) {
|
|
44
|
+
// const linkUrl = new URL(params.get('link')!, location);
|
|
45
|
+
// params.set('link', linkUrl.toString());
|
|
46
|
+
// }
|
|
47
|
+
|
|
48
|
+
const intent = `intent://arvr.google.com/scene-viewer/1.2?${
|
|
49
|
+
params.toString() + '&file=' + modelUrl.toString()
|
|
50
|
+
}#Intent;scheme=https;package=com.google.android.googlequicksearchbox;action=android.intent.action.VIEW;S.browser_fallback_url=${encodeURIComponent(
|
|
51
|
+
locationUrl.toString(),
|
|
52
|
+
)};end;`;
|
|
53
|
+
|
|
54
|
+
anchor.setAttribute('href', intent);
|
|
55
|
+
anchor.click();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private static findSceneViewerSrc(scene: DIVEScene): string {
|
|
59
|
+
let uri: string | null = null;
|
|
60
|
+
|
|
61
|
+
scene.traverse((object) => {
|
|
62
|
+
if (uri) return;
|
|
63
|
+
if (object.userData.uri) {
|
|
64
|
+
uri = object.userData.uri;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (!uri) {
|
|
69
|
+
throw new Error('No model found in scene');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return uri;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { Box3, Color, Euler, Mesh, Object3D, Vector3 } from 'three';
|
|
2
|
+
import { DIVEScene } from '../../../scene/Scene';
|
|
3
|
+
import { DIVEAROptions } from '../../AR';
|
|
4
|
+
import { DIVESceneViewer } from '../SceneViewer';
|
|
5
|
+
|
|
6
|
+
jest.mock('../../../scene/Scene', () => {
|
|
7
|
+
return {
|
|
8
|
+
DIVEScene: jest.fn(function () {
|
|
9
|
+
this.add = jest.fn();
|
|
10
|
+
this.children = [];
|
|
11
|
+
this.Root = {
|
|
12
|
+
children: [],
|
|
13
|
+
};
|
|
14
|
+
this.traverse = jest.fn((callback) => {
|
|
15
|
+
this.Root.children.forEach((child: Object3D) => {
|
|
16
|
+
callback(child);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
return this;
|
|
20
|
+
}),
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
jest.mock('three', () => {
|
|
25
|
+
return {
|
|
26
|
+
Vector3: jest.fn(function (
|
|
27
|
+
x: number = 0,
|
|
28
|
+
y: number = 0,
|
|
29
|
+
z: number = 0,
|
|
30
|
+
) {
|
|
31
|
+
this.x = x;
|
|
32
|
+
this.y = y;
|
|
33
|
+
this.z = z;
|
|
34
|
+
this.copy = (vec3: Vector3) => {
|
|
35
|
+
this.x = vec3.x;
|
|
36
|
+
this.y = vec3.y;
|
|
37
|
+
this.z = vec3.z;
|
|
38
|
+
return this;
|
|
39
|
+
};
|
|
40
|
+
this.set = (x: number, y: number, z: number) => {
|
|
41
|
+
this.x = x;
|
|
42
|
+
this.y = y;
|
|
43
|
+
this.z = z;
|
|
44
|
+
return this;
|
|
45
|
+
};
|
|
46
|
+
this.multiply = (vec3: Vector3) => {
|
|
47
|
+
this.x *= vec3.x;
|
|
48
|
+
this.y *= vec3.y;
|
|
49
|
+
this.z *= vec3.z;
|
|
50
|
+
return this;
|
|
51
|
+
};
|
|
52
|
+
this.clone = () => {
|
|
53
|
+
return new Vector3(this.x, this.y, this.z);
|
|
54
|
+
};
|
|
55
|
+
this.setY = (y: number) => {
|
|
56
|
+
this.y = y;
|
|
57
|
+
return this;
|
|
58
|
+
};
|
|
59
|
+
this.add = (vec3: Vector3) => {
|
|
60
|
+
this.x += vec3.x;
|
|
61
|
+
this.y += vec3.y;
|
|
62
|
+
this.z += vec3.z;
|
|
63
|
+
return this;
|
|
64
|
+
};
|
|
65
|
+
this.sub = (vec3: Vector3) => {
|
|
66
|
+
this.x -= vec3.x;
|
|
67
|
+
this.y -= vec3.y;
|
|
68
|
+
this.z -= vec3.z;
|
|
69
|
+
return this;
|
|
70
|
+
};
|
|
71
|
+
return this;
|
|
72
|
+
}),
|
|
73
|
+
Euler: jest.fn(function () {
|
|
74
|
+
this.set = jest.fn();
|
|
75
|
+
return this;
|
|
76
|
+
}),
|
|
77
|
+
Object3D: jest.fn(function () {
|
|
78
|
+
this.clear = jest.fn();
|
|
79
|
+
this.color = {};
|
|
80
|
+
this.intensity = 0;
|
|
81
|
+
this.layers = {
|
|
82
|
+
mask: 0,
|
|
83
|
+
};
|
|
84
|
+
this.shadow = {
|
|
85
|
+
radius: 0,
|
|
86
|
+
mapSize: { width: 0, height: 0 },
|
|
87
|
+
bias: 0,
|
|
88
|
+
camera: {
|
|
89
|
+
near: 0,
|
|
90
|
+
far: 0,
|
|
91
|
+
fov: 0,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
this.add = jest.fn();
|
|
95
|
+
this.sub = jest.fn();
|
|
96
|
+
this.children = [
|
|
97
|
+
{
|
|
98
|
+
visible: true,
|
|
99
|
+
material: {
|
|
100
|
+
color: {},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
this.userData = {};
|
|
105
|
+
this.position = new Vector3();
|
|
106
|
+
this.rotation = new Euler();
|
|
107
|
+
this.scale = new Vector3(1, 1, 1);
|
|
108
|
+
this.localToWorld = (vec3: Vector3) => {
|
|
109
|
+
return vec3;
|
|
110
|
+
};
|
|
111
|
+
this.mesh = new Mesh();
|
|
112
|
+
this.traverse = jest.fn((callback) => {
|
|
113
|
+
callback(this.children[0]);
|
|
114
|
+
});
|
|
115
|
+
this.getWorldPosition = jest.fn(() => {
|
|
116
|
+
return new Vector3();
|
|
117
|
+
});
|
|
118
|
+
return this;
|
|
119
|
+
}),
|
|
120
|
+
Box3: jest.fn(function () {
|
|
121
|
+
this.min = new Vector3(Infinity, Infinity, Infinity);
|
|
122
|
+
this.max = new Vector3(-Infinity, -Infinity, -Infinity);
|
|
123
|
+
this.getCenter = jest.fn(() => {
|
|
124
|
+
return new Vector3(0, 0, 0);
|
|
125
|
+
});
|
|
126
|
+
this.expandByObject = jest.fn();
|
|
127
|
+
|
|
128
|
+
return this;
|
|
129
|
+
}),
|
|
130
|
+
Mesh: jest.fn(function () {
|
|
131
|
+
this.geometry = {
|
|
132
|
+
computeBoundingBox: jest.fn(),
|
|
133
|
+
boundingBox: new Box3(),
|
|
134
|
+
};
|
|
135
|
+
this.material = {};
|
|
136
|
+
this.castShadow = true;
|
|
137
|
+
this.receiveShadow = true;
|
|
138
|
+
this.layers = {
|
|
139
|
+
mask: 0,
|
|
140
|
+
};
|
|
141
|
+
this.updateWorldMatrix = jest.fn();
|
|
142
|
+
this.traverse = jest.fn();
|
|
143
|
+
this.removeFromParent = jest.fn();
|
|
144
|
+
this.localToWorld = (vec3: Vector3) => {
|
|
145
|
+
return vec3;
|
|
146
|
+
};
|
|
147
|
+
return this;
|
|
148
|
+
}),
|
|
149
|
+
MeshStandardMaterial: jest.fn(function () {
|
|
150
|
+
this.color = new Color();
|
|
151
|
+
this.roughness = 1;
|
|
152
|
+
this.roughnessMap = undefined;
|
|
153
|
+
this.metalness = 0;
|
|
154
|
+
this.metalnessMap = undefined;
|
|
155
|
+
return this;
|
|
156
|
+
}),
|
|
157
|
+
Color: jest.fn(function () {
|
|
158
|
+
this.set = jest.fn();
|
|
159
|
+
return this;
|
|
160
|
+
}),
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
jest.mock('../../../exporters/usdz/USDZExporter', () => {
|
|
165
|
+
return {
|
|
166
|
+
DIVEUSDZExporter: jest.fn().mockImplementation(() => {
|
|
167
|
+
return {
|
|
168
|
+
parse: jest.fn(() => {
|
|
169
|
+
return Promise.resolve(new Uint8Array());
|
|
170
|
+
}),
|
|
171
|
+
};
|
|
172
|
+
}),
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
URL.createObjectURL = jest.fn(() => 'blob:http://localhost:8080/');
|
|
177
|
+
|
|
178
|
+
describe('DIVESceneViewer', () => {
|
|
179
|
+
let mockScene: DIVEScene;
|
|
180
|
+
let mockOptions: DIVEAROptions;
|
|
181
|
+
let mockModels: Object3D[];
|
|
182
|
+
|
|
183
|
+
beforeEach(() => {
|
|
184
|
+
mockModels = [
|
|
185
|
+
new Object3D(),
|
|
186
|
+
new Object3D(),
|
|
187
|
+
new Object3D(),
|
|
188
|
+
];
|
|
189
|
+
mockModels[1].userData = {
|
|
190
|
+
uri: 'https://example.com',
|
|
191
|
+
};
|
|
192
|
+
mockScene = new DIVEScene();
|
|
193
|
+
mockOptions = {
|
|
194
|
+
arPlacement: 'horizontal',
|
|
195
|
+
arScale: 'auto',
|
|
196
|
+
} as DIVEAROptions;
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('Launch', () => {
|
|
200
|
+
it('should be a function', () => {
|
|
201
|
+
expect(DIVESceneViewer.Launch).toBeInstanceOf(Function);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should not throw without options', () => {
|
|
205
|
+
mockScene.Root.children = mockModels;
|
|
206
|
+
|
|
207
|
+
expect(() => {
|
|
208
|
+
DIVESceneViewer.Launch(mockScene);
|
|
209
|
+
}).not.toThrow();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should not throw with options', () => {
|
|
213
|
+
mockScene.Root.children = mockModels;
|
|
214
|
+
|
|
215
|
+
expect(() => {
|
|
216
|
+
DIVESceneViewer.Launch(mockScene, mockOptions);
|
|
217
|
+
}).not.toThrow();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should not throw with alternated options', () => {
|
|
221
|
+
mockScene.Root.children = mockModels;
|
|
222
|
+
|
|
223
|
+
mockOptions = {
|
|
224
|
+
arPlacement: 'vertical',
|
|
225
|
+
arScale: 'fixed',
|
|
226
|
+
} as DIVEAROptions;
|
|
227
|
+
|
|
228
|
+
expect(() => {
|
|
229
|
+
DIVESceneViewer.Launch(mockScene, mockOptions);
|
|
230
|
+
}).not.toThrow();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should throw if no url is found', () => {
|
|
234
|
+
mockScene.Root.children = [
|
|
235
|
+
new Object3D(),
|
|
236
|
+
new Object3D(),
|
|
237
|
+
new Object3D(),
|
|
238
|
+
];
|
|
239
|
+
|
|
240
|
+
expect(() => {
|
|
241
|
+
DIVESceneViewer.Launch(mockScene, mockOptions);
|
|
242
|
+
}).toThrow();
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
@@ -133,15 +133,7 @@ export class DIVEWebXRController extends Object3D {
|
|
|
133
133
|
this._frameBuffer = frame;
|
|
134
134
|
|
|
135
135
|
if (!this._placed) {
|
|
136
|
-
this.
|
|
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
|
-
);
|
|
136
|
+
this.updateHandNode();
|
|
145
137
|
|
|
146
138
|
if (this._origin) {
|
|
147
139
|
this._origin.Update(frame);
|
|
@@ -149,6 +141,18 @@ export class DIVEWebXRController extends Object3D {
|
|
|
149
141
|
}
|
|
150
142
|
}
|
|
151
143
|
|
|
144
|
+
private updateHandNode(): void {
|
|
145
|
+
this._xrCamera.updateMatrixWorld();
|
|
146
|
+
this._scene.XRRoot.XRHandNode.position.copy(
|
|
147
|
+
this._handNodeInitialPosition
|
|
148
|
+
.clone()
|
|
149
|
+
.applyMatrix4(this._xrCamera.matrixWorld),
|
|
150
|
+
);
|
|
151
|
+
this._scene.XRRoot.XRHandNode.quaternion.setFromRotationMatrix(
|
|
152
|
+
this._xrCamera.matrixWorld,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
152
156
|
// placement
|
|
153
157
|
private async initOrigin(): Promise<void> {
|
|
154
158
|
// initialize origin
|
|
@@ -162,6 +166,8 @@ export class DIVEWebXRController extends Object3D {
|
|
|
162
166
|
|
|
163
167
|
private placeObjects(matrix: Matrix4): void {
|
|
164
168
|
this._scene.XRRoot.XRModelRoot.matrix.copy(matrix);
|
|
169
|
+
|
|
170
|
+
// we are copying children to a new array to keep the original array intact
|
|
165
171
|
[...this._scene.XRRoot.XRHandNode.children].forEach((child) => {
|
|
166
172
|
this._scene.XRRoot.XRModelRoot.add(child);
|
|
167
173
|
});
|
|
@@ -290,7 +296,7 @@ export class DIVEWebXRController extends Object3D {
|
|
|
290
296
|
// initialize crosshair
|
|
291
297
|
this._scene.add(this._crosshair);
|
|
292
298
|
|
|
293
|
-
// hang current scene children to
|
|
299
|
+
// hang current scene children to hand node
|
|
294
300
|
const children: Object3D[] = [];
|
|
295
301
|
this._scene.Root.children.forEach((child) => {
|
|
296
302
|
const clone = child.clone();
|
|
@@ -310,7 +316,7 @@ export class DIVEWebXRController extends Object3D {
|
|
|
310
316
|
private restoreScene(): void {
|
|
311
317
|
this._scene.remove(this._crosshair);
|
|
312
318
|
|
|
313
|
-
// clear
|
|
319
|
+
// clear hand node and remove attached models
|
|
314
320
|
this._scene.XRRoot.XRHandNode.clear();
|
|
315
321
|
this._scene.XRRoot.XRModelRoot.clear();
|
|
316
322
|
|
package/src/com/Communication.ts
CHANGED
|
@@ -305,8 +305,9 @@ export class DIVECommunication {
|
|
|
305
305
|
break;
|
|
306
306
|
}
|
|
307
307
|
case 'LAUNCH_AR': {
|
|
308
|
-
this.ar.Launch(
|
|
309
|
-
|
|
308
|
+
returnValue = this.ar.Launch(
|
|
309
|
+
payload as Actions['LAUNCH_AR']['PAYLOAD'],
|
|
310
|
+
);
|
|
310
311
|
break;
|
|
311
312
|
}
|
|
312
313
|
default: {
|
|
@@ -461,7 +462,7 @@ export class DIVECommunication {
|
|
|
461
462
|
|
|
462
463
|
this.registered.delete(payload.id);
|
|
463
464
|
|
|
464
|
-
// detach from parent
|
|
465
|
+
// detach all children from parent if we delete a group
|
|
465
466
|
Array.from(this.registered.values()).forEach((object) => {
|
|
466
467
|
if (!object.parentId) return;
|
|
467
468
|
if (object.parentId !== payload.id) return;
|