@shopware-ag/dive 1.6.5 → 1.8.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 +6 -8
- package/build/dive.cjs +130 -17
- package/build/dive.cjs.map +1 -1
- package/build/dive.d.cts +38 -7
- package/build/dive.d.ts +38 -7
- package/build/dive.js +129 -16
- package/build/dive.js.map +1 -1
- package/package.json +1 -1
- package/src/__test__/DIVE.test.ts +7 -1
- package/src/camera/PerspectiveCamera.ts +7 -2
- package/src/camera/__test__/PerspectiveCamera.test.ts +11 -2
- package/src/controls/OrbitControls.ts +9 -4
- package/src/controls/__test__/OrbitControls.test.ts +15 -5
- package/src/dive.ts +15 -8
- package/src/info/Info.ts +114 -0
- package/src/info/__test__/Info.test.ts +267 -0
- package/src/renderer/Renderer.ts +10 -7
- package/src/renderer/__test__/Renderer.test.ts +11 -0
package/package.json
CHANGED
|
@@ -183,6 +183,8 @@ jest.mock('../axiscamera/AxisCamera.ts', () => {
|
|
|
183
183
|
});
|
|
184
184
|
});
|
|
185
185
|
|
|
186
|
+
console.log = jest.fn();
|
|
187
|
+
|
|
186
188
|
describe('dive/DIVE', () => {
|
|
187
189
|
it('should QuickView', () => {
|
|
188
190
|
const dive = DIVE.QuickView('test_uri');
|
|
@@ -193,7 +195,6 @@ describe('dive/DIVE', () => {
|
|
|
193
195
|
const dive = new DIVE();
|
|
194
196
|
expect(dive).toBeDefined();
|
|
195
197
|
expect((window as any).DIVE.PrintScene).toBeDefined();
|
|
196
|
-
console.log = jest.fn();
|
|
197
198
|
expect(() => (window as any).DIVE.PrintScene()).not.toThrow();
|
|
198
199
|
});
|
|
199
200
|
|
|
@@ -244,6 +245,11 @@ describe('dive/DIVE', () => {
|
|
|
244
245
|
expect(dive.Communication).toBeDefined();
|
|
245
246
|
});
|
|
246
247
|
|
|
248
|
+
it('should have Info', () => {
|
|
249
|
+
const dive = new DIVE();
|
|
250
|
+
expect(dive.Info).toBeDefined();
|
|
251
|
+
});
|
|
252
|
+
|
|
247
253
|
it('should resize', () => {
|
|
248
254
|
const dive = new DIVE();
|
|
249
255
|
expect(() => dive.OnResize(800, 600)).not.toThrow();
|
|
@@ -25,8 +25,13 @@ export default class DIVEPerspectiveCamera extends PerspectiveCamera {
|
|
|
25
25
|
|
|
26
26
|
public onSetCameraLayer: (mask: number) => void = () => { };
|
|
27
27
|
|
|
28
|
-
constructor(settings: DIVEPerspectiveCameraSettings = DIVEPerspectiveCameraDefaultSettings) {
|
|
29
|
-
super(
|
|
28
|
+
constructor(settings: Partial<DIVEPerspectiveCameraSettings> = DIVEPerspectiveCameraDefaultSettings) {
|
|
29
|
+
super(
|
|
30
|
+
settings.fov || DIVEPerspectiveCameraDefaultSettings.fov,
|
|
31
|
+
1,
|
|
32
|
+
settings.near || DIVEPerspectiveCameraDefaultSettings.near,
|
|
33
|
+
settings.far || DIVEPerspectiveCameraDefaultSettings.far
|
|
34
|
+
);
|
|
30
35
|
|
|
31
36
|
this.layers.mask = DIVEPerspectiveCamera.EDITOR_VIEW_LAYER_MASK;
|
|
32
37
|
}
|
|
@@ -5,11 +5,20 @@ let cam: DIVEPerspectiveCamera;
|
|
|
5
5
|
describe('dive/camera/DIVEPerspectiveCamera', () => {
|
|
6
6
|
beforeEach(() => {
|
|
7
7
|
jest.clearAllMocks();
|
|
8
|
-
cam = new DIVEPerspectiveCamera(
|
|
8
|
+
cam = new DIVEPerspectiveCamera();
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
it('should instantiate', () => {
|
|
12
|
-
cam = new DIVEPerspectiveCamera();
|
|
12
|
+
cam = new DIVEPerspectiveCamera({});
|
|
13
|
+
expect(cam).toBeDefined();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should instantiate with settings', () => {
|
|
17
|
+
cam = new DIVEPerspectiveCamera({
|
|
18
|
+
fov: 45,
|
|
19
|
+
near: 0.1,
|
|
20
|
+
far: 100,
|
|
21
|
+
});
|
|
13
22
|
expect(cam).toBeDefined();
|
|
14
23
|
});
|
|
15
24
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { OrbitControls } from "three/examples/jsm/
|
|
1
|
+
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
|
2
2
|
import DIVEPerspectiveCamera from "../camera/PerspectiveCamera.ts";
|
|
3
3
|
import { DIVERenderer } from "../renderer/Renderer.ts";
|
|
4
4
|
import { type Box3, MathUtils, Vector3, Vector3Like } from "three";
|
|
@@ -39,7 +39,7 @@ export default class DIVEOrbitControls extends OrbitControls {
|
|
|
39
39
|
|
|
40
40
|
private _removePreRenderCallback: () => void = () => { };
|
|
41
41
|
|
|
42
|
-
constructor(camera: DIVEPerspectiveCamera, renderer: DIVERenderer, animationSystem: DIVEAnimationSystem, settings: DIVEOrbitControlsSettings = DIVEOrbitControlsDefaultSettings) {
|
|
42
|
+
constructor(camera: DIVEPerspectiveCamera, renderer: DIVERenderer, animationSystem: DIVEAnimationSystem, settings: Partial<DIVEOrbitControlsSettings> = DIVEOrbitControlsDefaultSettings) {
|
|
43
43
|
super(camera, renderer.domElement);
|
|
44
44
|
|
|
45
45
|
this._animationSystem = animationSystem;
|
|
@@ -56,8 +56,13 @@ export default class DIVEOrbitControls extends OrbitControls {
|
|
|
56
56
|
renderer.RemovePreRenderCallback(id);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
this.enableDamping = settings.enableDamping;
|
|
60
|
-
this.dampingFactor = settings.dampingFactor;
|
|
59
|
+
this.enableDamping = settings.enableDamping || DIVEOrbitControlsDefaultSettings.enableDamping;
|
|
60
|
+
this.dampingFactor = settings.dampingFactor || DIVEOrbitControlsDefaultSettings.dampingFactor;
|
|
61
|
+
|
|
62
|
+
// initialize camera transformation
|
|
63
|
+
this.object.position.set(0, 2, 2);
|
|
64
|
+
this.target.copy({ x: 0, y: 0.5, z: 0 });
|
|
65
|
+
this.update();
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
public Dispose(): void {
|
|
@@ -12,7 +12,7 @@ jest.mock('@tweenjs/tween.js', () => {
|
|
|
12
12
|
}
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
jest.mock('three/examples/jsm/
|
|
15
|
+
jest.mock('three/examples/jsm/controls/OrbitControls', () => {
|
|
16
16
|
return {
|
|
17
17
|
OrbitControls: jest.fn(function () {
|
|
18
18
|
this.enableDamping = true;
|
|
@@ -42,14 +42,13 @@ jest.mock('three/examples/jsm/Addons.js', () => {
|
|
|
42
42
|
MIDDLE: 1,
|
|
43
43
|
RIGHT: 2,
|
|
44
44
|
};
|
|
45
|
-
this.target = {
|
|
46
|
-
set: jest.fn(),
|
|
47
|
-
};
|
|
48
45
|
this.update = jest.fn();
|
|
49
46
|
this.dispose = jest.fn();
|
|
50
47
|
this.getDistance = jest.fn();
|
|
51
48
|
this.target = {
|
|
52
49
|
clone: jest.fn(),
|
|
50
|
+
set: jest.fn(),
|
|
51
|
+
copy: jest.fn(),
|
|
53
52
|
};
|
|
54
53
|
return this;
|
|
55
54
|
}),
|
|
@@ -122,6 +121,9 @@ const mockCamera = {
|
|
|
122
121
|
multiplyScalar: jest.fn(() => {
|
|
123
122
|
return mockCamera.position;
|
|
124
123
|
}),
|
|
124
|
+
set: jest.fn(() => {
|
|
125
|
+
return mockCamera.position;
|
|
126
|
+
}),
|
|
125
127
|
},
|
|
126
128
|
lookAt: jest.fn(),
|
|
127
129
|
} as unknown as DIVEPerspectiveCamera;
|
|
@@ -148,7 +150,15 @@ describe('dive/controls/DIVEOrbitControls', () => {
|
|
|
148
150
|
});
|
|
149
151
|
|
|
150
152
|
it('should instantiate', () => {
|
|
151
|
-
const controller = new DIVEOrbitControls(mockCamera, mockRenderer, mockAnimSystem);
|
|
153
|
+
const controller = new DIVEOrbitControls(mockCamera, mockRenderer, mockAnimSystem, {});
|
|
154
|
+
expect(controller).toBeDefined();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should instantiate with settings', () => {
|
|
158
|
+
const controller = new DIVEOrbitControls(mockCamera, mockRenderer, mockAnimSystem, {
|
|
159
|
+
enableDamping: false,
|
|
160
|
+
dampingFactor: 0.5,
|
|
161
|
+
});
|
|
152
162
|
expect(controller).toBeDefined();
|
|
153
163
|
});
|
|
154
164
|
|
package/src/dive.ts
CHANGED
|
@@ -12,13 +12,14 @@ import type { Actions } from './com/actions/index.ts';
|
|
|
12
12
|
import type { COMPov, COMLight, COMModel, COMEntity } from './com/types.ts';
|
|
13
13
|
import { DIVEMath } from './math/index.ts';
|
|
14
14
|
import { generateUUID } from "three/src/math/MathUtils";
|
|
15
|
+
import { DIVEInfo } from "./info/Info.ts";
|
|
15
16
|
|
|
16
17
|
export type DIVESettings = {
|
|
17
18
|
autoResize: boolean;
|
|
18
19
|
displayAxes: boolean;
|
|
19
|
-
renderer: DIVERendererSettings
|
|
20
|
-
perspectiveCamera: DIVEPerspectiveCameraSettings
|
|
21
|
-
orbitControls: DIVEOrbitControlsSettings
|
|
20
|
+
renderer: Partial<DIVERendererSettings>;
|
|
21
|
+
perspectiveCamera: Partial<DIVEPerspectiveCameraSettings>;
|
|
22
|
+
orbitControls: Partial<DIVEOrbitControlsSettings>;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export const DIVEDefaultSettings: DIVESettings = {
|
|
@@ -145,6 +146,10 @@ export default class DIVE {
|
|
|
145
146
|
return this.renderer.domElement;
|
|
146
147
|
}
|
|
147
148
|
|
|
149
|
+
public get Info(): DIVEInfo {
|
|
150
|
+
return DIVEInfo;
|
|
151
|
+
}
|
|
152
|
+
|
|
148
153
|
// setters
|
|
149
154
|
public set Settings(settings: Partial<DIVESettings>) {
|
|
150
155
|
const settingsDelta = getObjectDelta(this._settings, settings);
|
|
@@ -154,15 +159,15 @@ export default class DIVE {
|
|
|
154
159
|
|
|
155
160
|
// apply perspective camera settings
|
|
156
161
|
if (settingsDelta.perspectiveCamera) {
|
|
157
|
-
this.perspectiveCamera.fov = settingsDelta.perspectiveCamera.fov;
|
|
158
|
-
this.perspectiveCamera.near = settingsDelta.perspectiveCamera.near;
|
|
159
|
-
this.perspectiveCamera.far = settingsDelta.perspectiveCamera.far;
|
|
162
|
+
if (settingsDelta.perspectiveCamera.fov !== undefined) this.perspectiveCamera.fov = settingsDelta.perspectiveCamera.fov;
|
|
163
|
+
if (settingsDelta.perspectiveCamera.near !== undefined) this.perspectiveCamera.near = settingsDelta.perspectiveCamera.near;
|
|
164
|
+
if (settingsDelta.perspectiveCamera.far !== undefined) this.perspectiveCamera.far = settingsDelta.perspectiveCamera.far;
|
|
160
165
|
this.perspectiveCamera.OnResize(this.renderer.domElement.width, this.renderer.domElement.height);
|
|
161
166
|
}
|
|
162
167
|
// apply orbit controls settings
|
|
163
168
|
if (settingsDelta.orbitControls) {
|
|
164
|
-
this.orbitControls.enableDamping = settingsDelta.orbitControls.enableDamping;
|
|
165
|
-
this.orbitControls.dampingFactor = settingsDelta.orbitControls.dampingFactor;
|
|
169
|
+
if (settingsDelta.orbitControls.enableDamping !== undefined) this.orbitControls.enableDamping = settingsDelta.orbitControls.enableDamping;
|
|
170
|
+
if (settingsDelta.orbitControls.dampingFactor !== undefined) this.orbitControls.dampingFactor = settingsDelta.orbitControls.dampingFactor;
|
|
166
171
|
}
|
|
167
172
|
|
|
168
173
|
if (settingsDelta.autoResize !== this._settings.autoResize) {
|
|
@@ -224,6 +229,8 @@ export default class DIVE {
|
|
|
224
229
|
console.log(this.scene);
|
|
225
230
|
},
|
|
226
231
|
}
|
|
232
|
+
|
|
233
|
+
console.log('DIVE initialized');
|
|
227
234
|
}
|
|
228
235
|
|
|
229
236
|
public Dispose(): void {
|
package/src/info/Info.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export class DIVEInfo {
|
|
2
|
+
private static _supportsWebXR: boolean | null = null;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @returns The system the user is using. Possible values are "Android", "iOS", "Windows", "MacOS", "Linux" or "Unknown".
|
|
7
|
+
*/
|
|
8
|
+
public static GetSystem(): string {
|
|
9
|
+
const platform = navigator.platform;
|
|
10
|
+
if (/Android/.test(navigator.userAgent)) {
|
|
11
|
+
return "Android";
|
|
12
|
+
} else if (/iPhone|iPad|iPod/.test(navigator.userAgent)) {
|
|
13
|
+
return "iOS";
|
|
14
|
+
} else if (platform.startsWith("Win")) {
|
|
15
|
+
return "Windows";
|
|
16
|
+
} else if (platform.startsWith("Mac")) {
|
|
17
|
+
return "MacOS";
|
|
18
|
+
} else if (platform.startsWith("Linux")) {
|
|
19
|
+
return "Linux";
|
|
20
|
+
} else {
|
|
21
|
+
return "Unknown";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @returns A promise that resolves to a boolean indicating whether the user's device supports WebXR.
|
|
27
|
+
*/
|
|
28
|
+
public static async GetSupportsWebXR(): Promise<boolean> {
|
|
29
|
+
if (this._supportsWebXR !== null) {
|
|
30
|
+
return this._supportsWebXR;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!navigator.xr) {
|
|
34
|
+
this._supportsWebXR = false;
|
|
35
|
+
return this._supportsWebXR;
|
|
36
|
+
}
|
|
37
|
+
// Check if immersive-vr session mode is supported
|
|
38
|
+
try {
|
|
39
|
+
const supported = await navigator.xr.isSessionSupported('immersive-ar');
|
|
40
|
+
this._supportsWebXR = supported;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
this._supportsWebXR = false;
|
|
43
|
+
}
|
|
44
|
+
return this._supportsWebXR;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @returns A boolean indicating whether the user's device supports AR Quick Look.
|
|
49
|
+
*/
|
|
50
|
+
public static GetSupportsARQuickLook(): boolean {
|
|
51
|
+
const a = document.createElement("a");
|
|
52
|
+
if (a.relList.supports("ar")) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// fallback check
|
|
57
|
+
const userAgent = navigator.userAgent;
|
|
58
|
+
|
|
59
|
+
// Check if the device is running iOS
|
|
60
|
+
const isIOS = /iPad|iPhone|iPod/.test(userAgent) && !(window as unknown as Window & { MSStream?: string }).MSStream;
|
|
61
|
+
if (!isIOS) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Extract iOS version
|
|
66
|
+
const match = userAgent.match(/OS (\d+)_/);
|
|
67
|
+
if (!match || match.length < 2) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
const iOSVersion = parseInt(match[1], 10);
|
|
71
|
+
|
|
72
|
+
// Minimum iOS version for QuickLook support
|
|
73
|
+
const minQuickLookVersion = 12;
|
|
74
|
+
|
|
75
|
+
// Check if the iOS version is supported
|
|
76
|
+
if (iOSVersion < minQuickLookVersion) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check for supported browser
|
|
81
|
+
const isSupportedBrowser = /^((?!chrome|android).)*safari|CriOS|FxiOS/i.test(userAgent);
|
|
82
|
+
if (isSupportedBrowser) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Default to false if none of the conditions are met
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @returns A boolean indicating whether the user's device is a mobile device.
|
|
92
|
+
*/
|
|
93
|
+
public static get isMobile(): boolean {
|
|
94
|
+
return this.GetSystem() === "Android" || this.GetSystem() === "iOS";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @returns A boolean indicating whether the user's device is a desktop device.
|
|
99
|
+
*/
|
|
100
|
+
public static get isDesktop(): boolean {
|
|
101
|
+
return !this.isMobile;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @returns A promise that resolves to a boolean indicating whether the user's device is capable of AR.
|
|
106
|
+
*/
|
|
107
|
+
public static async GetIsARCapable(): Promise<boolean> {
|
|
108
|
+
if (this.GetSupportsARQuickLook()) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return await this.GetSupportsWebXR();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { DIVEInfo } from '../Info';
|
|
2
|
+
|
|
3
|
+
const mockNavigator = (navigator: any) => {
|
|
4
|
+
Object.defineProperty(global, 'navigator', {
|
|
5
|
+
value: navigator,
|
|
6
|
+
writable: true
|
|
7
|
+
});
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
describe('dive/info/DIVEInfo', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
DIVEInfo['_supportsWebXR'] = null;
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should get system: Windows', () => {
|
|
17
|
+
mockNavigator({
|
|
18
|
+
platform: 'Win64'
|
|
19
|
+
});
|
|
20
|
+
expect(DIVEInfo.GetSystem()).toBe('Windows');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should get system: MacOS', () => {
|
|
24
|
+
mockNavigator({
|
|
25
|
+
platform: 'MacIntel'
|
|
26
|
+
});
|
|
27
|
+
expect(DIVEInfo.GetSystem()).toBe('MacOS');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should get system: Linux', () => {
|
|
31
|
+
mockNavigator({
|
|
32
|
+
platform: 'Linux'
|
|
33
|
+
});
|
|
34
|
+
expect(DIVEInfo.GetSystem()).toBe('Linux');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should get system: Android', () => {
|
|
38
|
+
mockNavigator({
|
|
39
|
+
userAgent: 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3',
|
|
40
|
+
platform: 'Linux'
|
|
41
|
+
});
|
|
42
|
+
expect(DIVEInfo.GetSystem()).toBe('Android');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should get system: iOS', () => {
|
|
46
|
+
mockNavigator({
|
|
47
|
+
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0_1 like Mac OS X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3',
|
|
48
|
+
platform: 'iPhone'
|
|
49
|
+
});
|
|
50
|
+
expect(DIVEInfo.GetSystem()).toBe('iOS');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should get system: Unknown', () => {
|
|
54
|
+
mockNavigator({
|
|
55
|
+
platform: 'Unknown'
|
|
56
|
+
});
|
|
57
|
+
expect(DIVEInfo.GetSystem()).toBe('Unknown');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should support webXR', async () => {
|
|
61
|
+
DIVEInfo['_supportsWebXR'] = null;
|
|
62
|
+
mockNavigator({
|
|
63
|
+
xr: {
|
|
64
|
+
isSessionSupported: jest.fn().mockResolvedValue(true),
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
const supports = await DIVEInfo.GetSupportsWebXR();
|
|
68
|
+
expect(supports).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should not support webXR (xr undefined)', async () => {
|
|
72
|
+
DIVEInfo['_supportsWebXR'] = null;
|
|
73
|
+
mockNavigator({
|
|
74
|
+
xr: undefined
|
|
75
|
+
});
|
|
76
|
+
const supports = await DIVEInfo.GetSupportsWebXR();
|
|
77
|
+
expect(supports).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should not support webXR', async () => {
|
|
81
|
+
DIVEInfo['_supportsWebXR'] = null;
|
|
82
|
+
mockNavigator({
|
|
83
|
+
xr: {
|
|
84
|
+
isSessionSupported: jest.fn().mockResolvedValue(false),
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
const supports = await DIVEInfo.GetSupportsWebXR();
|
|
88
|
+
expect(supports).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should not support webXR on error', async () => {
|
|
92
|
+
DIVEInfo['_supportsWebXR'] = null;
|
|
93
|
+
mockNavigator({
|
|
94
|
+
xr: {
|
|
95
|
+
isSessionSupported: jest.fn().mockRejectedValue('error'),
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
const supports = await DIVEInfo.GetSupportsWebXR();
|
|
99
|
+
expect(supports).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should return cached value', async () => {
|
|
103
|
+
DIVEInfo['_supportsWebXR'] = true;
|
|
104
|
+
mockNavigator({
|
|
105
|
+
xr: {
|
|
106
|
+
isSessionSupported: jest.fn().mockRejectedValue('error'),
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
const supports = await DIVEInfo.GetSupportsWebXR();
|
|
110
|
+
expect(supports).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should return cached value (false)', async () => {
|
|
114
|
+
DIVEInfo['_supportsWebXR'] = false;
|
|
115
|
+
mockNavigator({
|
|
116
|
+
xr: {
|
|
117
|
+
isSessionSupported: jest.fn().mockRejectedValue('error'),
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
const supports = await DIVEInfo.GetSupportsWebXR();
|
|
121
|
+
expect(supports).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should support ARQuickLook with feature detection', () => {
|
|
125
|
+
mockNavigator({
|
|
126
|
+
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/604.'
|
|
127
|
+
});
|
|
128
|
+
jest.spyOn(document, 'createElement').mockReturnValue({ relList: { supports: () => true } } as unknown as HTMLAnchorElement);
|
|
129
|
+
const supports = DIVEInfo.GetSupportsARQuickLook();
|
|
130
|
+
expect(supports).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should support ARQuickLook (iPhone, iOS 15, Safari)', () => {
|
|
134
|
+
mockNavigator({
|
|
135
|
+
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/604.'
|
|
136
|
+
});
|
|
137
|
+
jest.spyOn(document, 'createElement').mockReturnValue({ relList: { supports: () => false } } as unknown as HTMLAnchorElement);
|
|
138
|
+
const supports = DIVEInfo.GetSupportsARQuickLook();
|
|
139
|
+
expect(supports).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should support ARQuickLook (iPhone, iOS 17, Google)', () => {
|
|
143
|
+
mockNavigator({
|
|
144
|
+
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) GSA/277.0.555192628 Mobile/15E148 Safari/604.'
|
|
145
|
+
});
|
|
146
|
+
jest.spyOn(document, 'createElement').mockReturnValue({ relList: { supports: () => false } } as unknown as HTMLAnchorElement);
|
|
147
|
+
const supports = DIVEInfo.GetSupportsARQuickLook();
|
|
148
|
+
expect(supports).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should support ARQuickLook (iPhone, iOS 17, Chrome)', () => {
|
|
152
|
+
mockNavigator({
|
|
153
|
+
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/126.0.6478.153 Mobile/15E148 Safari/604.'
|
|
154
|
+
});
|
|
155
|
+
jest.spyOn(document, 'createElement').mockReturnValue({ relList: { supports: () => false } } as unknown as HTMLAnchorElement);
|
|
156
|
+
const supports = DIVEInfo.GetSupportsARQuickLook();
|
|
157
|
+
expect(supports).toBe(true);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should support ARQuickLook (iPhone, iOS 17, Chrome)', () => {
|
|
161
|
+
mockNavigator({
|
|
162
|
+
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X) AppleWebKit/605.1.11 (KHTML, like Gecko) Version/11.1 Mobile/11E148 CriOS/604.'
|
|
163
|
+
});
|
|
164
|
+
jest.spyOn(document, 'createElement').mockReturnValue({ relList: { supports: () => false } } as unknown as HTMLAnchorElement);
|
|
165
|
+
const supports = DIVEInfo.GetSupportsARQuickLook();
|
|
166
|
+
expect(supports).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should support ARQuickLook (iPhone, iOS 17, Firefox)', () => {
|
|
170
|
+
mockNavigator({
|
|
171
|
+
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X) AppleWebKit/605.1.11 (KHTML, like Gecko) Version/11.1 Mobile/11E148 FxiOS/604.'
|
|
172
|
+
});
|
|
173
|
+
jest.spyOn(document, 'createElement').mockReturnValue({ relList: { supports: () => false } } as unknown as HTMLAnchorElement);
|
|
174
|
+
const supports = DIVEInfo.GetSupportsARQuickLook();
|
|
175
|
+
expect(supports).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should not support ARQuickLook (Android)', () => {
|
|
179
|
+
mockNavigator({
|
|
180
|
+
userAgent: 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Mobile Safari/537.3'
|
|
181
|
+
});
|
|
182
|
+
jest.spyOn(document, 'createElement').mockReturnValue({ relList: { supports: () => false } } as unknown as HTMLAnchorElement);
|
|
183
|
+
const supports = DIVEInfo.GetSupportsARQuickLook();
|
|
184
|
+
expect(supports).toBe(false);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should not support ARQuickLook (iPhone, no iOS version)', () => {
|
|
188
|
+
mockNavigator({
|
|
189
|
+
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS like Mac OS X) AppleWebKit/605.1.11 (KHTML, like Gecko) Version/11.1 Mobile/11E148 Safari/604.'
|
|
190
|
+
});
|
|
191
|
+
jest.spyOn(document, 'createElement').mockReturnValue({ relList: { supports: () => false } } as unknown as HTMLAnchorElement);
|
|
192
|
+
const supports = DIVEInfo.GetSupportsARQuickLook();
|
|
193
|
+
expect(supports).toBe(false);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should not support ARQuickLook (iPhone, iOS 17, no browser)', () => {
|
|
197
|
+
mockNavigator({
|
|
198
|
+
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X)'
|
|
199
|
+
});
|
|
200
|
+
jest.spyOn(document, 'createElement').mockReturnValue({ relList: { supports: () => false } } as unknown as HTMLAnchorElement);
|
|
201
|
+
const supports = DIVEInfo.GetSupportsARQuickLook();
|
|
202
|
+
expect(supports).toBe(false);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should not support ARQuickLook (iPhone, iOS <12, Safari)', () => {
|
|
206
|
+
mockNavigator({
|
|
207
|
+
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_1 like Mac OS X) AppleWebKit/605.1.11 (KHTML, like Gecko) Version/11.1 Mobile/11E148 Safari/604.'
|
|
208
|
+
});
|
|
209
|
+
jest.spyOn(document, 'createElement').mockReturnValue({ relList: { supports: () => false } } as unknown as HTMLAnchorElement);
|
|
210
|
+
const supports = DIVEInfo.GetSupportsARQuickLook();
|
|
211
|
+
expect(supports).toBe(false);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should be mobile (iOS)', () => {
|
|
215
|
+
jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('iOS');
|
|
216
|
+
expect(DIVEInfo.isMobile).toBe(true);
|
|
217
|
+
expect(DIVEInfo.isDesktop).toBe(false);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should be mobile (Android)', () => {
|
|
221
|
+
jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('Android');
|
|
222
|
+
expect(DIVEInfo.isMobile).toBe(true);
|
|
223
|
+
expect(DIVEInfo.isDesktop).toBe(false);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should be desktop (Windows)', () => {
|
|
227
|
+
jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('Windows');
|
|
228
|
+
expect(DIVEInfo.isMobile).toBe(false);
|
|
229
|
+
expect(DIVEInfo.isDesktop).toBe(true);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('should be desktop (MacOS)', () => {
|
|
233
|
+
jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('MacOS');
|
|
234
|
+
expect(DIVEInfo.isMobile).toBe(false);
|
|
235
|
+
expect(DIVEInfo.isDesktop).toBe(true);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should be desktop (Linux)', () => {
|
|
239
|
+
jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('Linux');
|
|
240
|
+
expect(DIVEInfo.isMobile).toBe(false);
|
|
241
|
+
expect(DIVEInfo.isDesktop).toBe(true);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should be desktop (Unknown)', () => {
|
|
245
|
+
jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('Unknown');
|
|
246
|
+
expect(DIVEInfo.isMobile).toBe(false);
|
|
247
|
+
expect(DIVEInfo.isDesktop).toBe(true);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('should be capable of AR (ARQuickLook)', async () => {
|
|
251
|
+
jest.spyOn(DIVEInfo, 'GetSupportsARQuickLook').mockReturnValue(true);
|
|
252
|
+
jest.spyOn(DIVEInfo, 'GetSupportsWebXR').mockResolvedValue(false);
|
|
253
|
+
expect(await DIVEInfo.GetIsARCapable()).toBe(true);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should be capable of AR (WebXR)', async () => {
|
|
257
|
+
jest.spyOn(DIVEInfo, 'GetSupportsARQuickLook').mockReturnValue(false);
|
|
258
|
+
jest.spyOn(DIVEInfo, 'GetSupportsWebXR').mockResolvedValue(true);
|
|
259
|
+
expect(await DIVEInfo.GetIsARCapable()).toBe(true);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should not be capable of AR', async () => {
|
|
263
|
+
jest.spyOn(DIVEInfo, 'GetSupportsARQuickLook').mockReturnValue(false);
|
|
264
|
+
jest.spyOn(DIVEInfo, 'GetSupportsWebXR').mockResolvedValue(false);
|
|
265
|
+
expect(await DIVEInfo.GetIsARCapable()).toBe(false);
|
|
266
|
+
});
|
|
267
|
+
});
|
package/src/renderer/Renderer.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type DIVERendererSettings = {
|
|
|
7
7
|
shadowMapEnabled: boolean;
|
|
8
8
|
shadowMapType: ShadowMapType;
|
|
9
9
|
toneMapping: ToneMapping;
|
|
10
|
+
canvas?: HTMLCanvasElement;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export const DIVERendererDefaultSettings: DIVERendererSettings = {
|
|
@@ -16,6 +17,7 @@ export const DIVERendererDefaultSettings: DIVERendererSettings = {
|
|
|
16
17
|
shadowMapEnabled: true,
|
|
17
18
|
shadowMapType: PCFSoftShadowMap,
|
|
18
19
|
toneMapping: NoToneMapping,
|
|
20
|
+
canvas: undefined,
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
/**
|
|
@@ -36,18 +38,19 @@ export class DIVERenderer extends WebGLRenderer {
|
|
|
36
38
|
private preRenderCallbacks: Map<string, () => void> = new Map<string, () => void>();
|
|
37
39
|
private postRenderCallbacks: Map<string, () => void> = new Map<string, () => void>();
|
|
38
40
|
|
|
39
|
-
constructor(rendererSettings: DIVERendererSettings = DIVERendererDefaultSettings) {
|
|
41
|
+
constructor(rendererSettings: Partial<DIVERendererSettings> = DIVERendererDefaultSettings) {
|
|
40
42
|
super({
|
|
41
|
-
antialias: rendererSettings.antialias,
|
|
42
|
-
alpha: rendererSettings.alpha,
|
|
43
|
-
preserveDrawingBuffer: true
|
|
43
|
+
antialias: rendererSettings.antialias || DIVERendererDefaultSettings.antialias,
|
|
44
|
+
alpha: rendererSettings.alpha || DIVERendererDefaultSettings.alpha,
|
|
45
|
+
preserveDrawingBuffer: true,
|
|
46
|
+
canvas: rendererSettings.canvas,
|
|
44
47
|
});
|
|
45
48
|
this.setPixelRatio(window.devicePixelRatio);
|
|
46
49
|
|
|
47
|
-
this.shadowMap.enabled = rendererSettings.shadowMapEnabled;
|
|
48
|
-
this.shadowMap.type = rendererSettings.shadowMapType;
|
|
50
|
+
this.shadowMap.enabled = rendererSettings.shadowMapEnabled || DIVERendererDefaultSettings.shadowMapEnabled;
|
|
51
|
+
this.shadowMap.type = rendererSettings.shadowMapType || DIVERendererDefaultSettings.shadowMapType;
|
|
49
52
|
|
|
50
|
-
this.toneMapping = rendererSettings.toneMapping;
|
|
53
|
+
this.toneMapping = rendererSettings.toneMapping || DIVERendererDefaultSettings.toneMapping;
|
|
51
54
|
|
|
52
55
|
this.debug.checkShaderErrors = false;
|
|
53
56
|
}
|
|
@@ -48,9 +48,20 @@ describe('dive/renderer/DIVERenderer', () => {
|
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
it('should instantiate', () => {
|
|
51
|
+
renderer = new DIVERenderer({});
|
|
51
52
|
expect(renderer).toBeDefined();
|
|
52
53
|
});
|
|
53
54
|
|
|
55
|
+
it('should instantiate', () => {
|
|
56
|
+
renderer = new DIVERenderer({
|
|
57
|
+
alpha: true,
|
|
58
|
+
shadowMapEnabled: true,
|
|
59
|
+
shadowMapType: 1,
|
|
60
|
+
toneMapping: 1,
|
|
61
|
+
antialias: true,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
54
65
|
it('should instantiate with settings parameter', () => {
|
|
55
66
|
renderer = new DIVERenderer(DIVERendererDefaultSettings);
|
|
56
67
|
expect(renderer).toBeDefined();
|