@shopware-ag/dive 1.18.5 → 1.19.1-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/README.md +8 -0
- package/build/dive.cjs +220 -215
- package/build/dive.cjs.map +1 -1
- package/build/dive.js +233 -21102
- package/build/dive.js.map +1 -1
- package/build/dive.mjs +26111 -0
- package/build/dive.mjs.map +1 -0
- package/build/src/ar/AR.d.ts +37 -14
- package/build/src/ar/arquicklook/ARQuickLook.d.ts +5 -6
- package/build/src/ar/sceneviewer/SceneViewer.d.ts +42 -6
- package/build/src/com/actions/index.d.ts +2 -0
- package/build/src/com/actions/renderer/startrender.d.ts +5 -0
- package/build/src/com/actions/scene/launchar.d.ts +5 -2
- package/build/src/converter/Converter.d.ts +21 -0
- package/build/src/dive.d.ts +3 -2
- package/build/src/exporter/Exporter.d.ts +11 -0
- package/build/src/helper/applyMixins/applyMixins.d.ts +22 -6
- package/build/src/info/Info.d.ts +37 -13
- package/build/src/interface/Movable.d.ts +5 -5
- package/build/src/interface/Selectable.d.ts +4 -4
- package/build/src/loader/Loader.d.ts +11 -0
- package/build/src/model/Model.d.ts +2 -2
- package/build/src/node/Node.d.ts +3 -3
- package/build/src/scene/root/Root.d.ts +1 -1
- package/build/src/types/ExporterOptions.d.ts +15 -0
- package/build/src/types/FileTypes.d.ts +27 -0
- package/build/src/types/index.d.ts +4 -0
- package/build/src/types/info/index.d.ts +66 -0
- package/package.json +16 -1
- package/src/ar/AR.ts +72 -69
- package/src/ar/__test__/AR.test.ts +194 -105
- package/src/ar/arquicklook/ARQuickLook.ts +32 -72
- package/src/ar/arquicklook/__test__/ARQuickLook.test.ts +89 -38
- package/src/ar/sceneviewer/SceneViewer.ts +96 -51
- package/src/ar/sceneviewer/__test__/SceneViewer.test.ts +144 -47
- package/src/ar/webxr/WebXR.ts +5 -4
- package/src/com/Communication.ts +10 -7
- package/src/com/__test__/Communication.test.ts +16 -3
- package/src/com/actions/index.ts +2 -0
- package/src/com/actions/renderer/startrender.ts +5 -0
- package/src/com/actions/scene/launchar.ts +2 -2
- package/src/converter/Converter.ts +117 -0
- package/src/dive.ts +10 -6
- package/src/exporter/Exporter.ts +75 -0
- package/src/helper/applyMixins/applyMixins.ts +59 -7
- package/src/info/Info.ts +99 -75
- package/src/info/__test__/Info.test.ts +162 -154
- package/src/interface/Movable.ts +5 -5
- package/src/interface/Selectable.ts +4 -4
- package/src/loader/Loader.ts +48 -0
- package/src/model/Model.ts +10 -5
- package/src/model/__test__/Model.test.ts +4 -11
- package/src/node/Node.ts +7 -5
- package/src/scene/root/Root.ts +4 -4
- package/src/scene/root/__test__/Root.test.ts +4 -4
- package/src/toolbox/Toolbox.ts +1 -3
- package/src/types/ExporterOptions.ts +14 -0
- package/src/types/FileTypes.ts +37 -0
- package/src/types/index.ts +26 -0
- package/src/types/info/index.ts +76 -0
- package/build/src/exporters/usdz/USDZExporter.d.ts +0 -15
- package/build/src/loadingmanager/LoadingManager.d.ts +0 -14
- package/src/exporters/usdz/USDZExporter.ts +0 -21
- package/src/exporters/usdz/__test__/USDZExporter.test.ts +0 -57
- package/src/loadingmanager/LoadingManager.ts +0 -50
- package/src/loadingmanager/__test__/LoadingManager.test.ts +0 -27
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
type COMPov,
|
|
35
35
|
} from '../types';
|
|
36
36
|
import { type DIVESceneObject } from '../../types';
|
|
37
|
+
import { type ARSystemOptions } from '../../ar/AR';
|
|
37
38
|
|
|
38
39
|
const mockModule: Record<string, any> = {
|
|
39
40
|
get: jest.fn().mockReturnValue(Promise.resolve({})),
|
|
@@ -92,6 +93,7 @@ jest.mock('../../toolbox/select/SelectTool', () => {
|
|
|
92
93
|
const mockRenderer = {
|
|
93
94
|
render: jest.fn(),
|
|
94
95
|
OnResize: jest.fn(),
|
|
96
|
+
StartRenderer: jest.fn(),
|
|
95
97
|
} as unknown as DIVERenderer;
|
|
96
98
|
|
|
97
99
|
const mockScene = {
|
|
@@ -288,6 +290,12 @@ describe('dive/communication/DIVECommunication', () => {
|
|
|
288
290
|
).not.toThrow();
|
|
289
291
|
});
|
|
290
292
|
|
|
293
|
+
it('should perform action START_RENDER', () => {
|
|
294
|
+
const success = testCom.PerformAction('START_RENDER');
|
|
295
|
+
expect(mockRenderer.StartRenderer).toHaveBeenCalledTimes(1);
|
|
296
|
+
expect(success).toBe(true);
|
|
297
|
+
});
|
|
298
|
+
|
|
291
299
|
it('should perform action ADD_OBJECT', () => {
|
|
292
300
|
const payload = {
|
|
293
301
|
entityType: 'light',
|
|
@@ -998,14 +1006,19 @@ describe('dive/communication/DIVECommunication', () => {
|
|
|
998
1006
|
|
|
999
1007
|
it('should perform action LAUNCH_AR', async () => {
|
|
1000
1008
|
jest.spyOn(mockModule, 'get').mockResolvedValue({
|
|
1001
|
-
|
|
1009
|
+
launch: jest.fn(),
|
|
1002
1010
|
});
|
|
1003
1011
|
const arModule = await testCom['_ar'].get();
|
|
1004
1012
|
const arLaunchSpy = jest
|
|
1005
|
-
.spyOn(arModule, '
|
|
1013
|
+
.spyOn(arModule, 'launch')
|
|
1006
1014
|
.mockResolvedValueOnce();
|
|
1007
1015
|
|
|
1008
|
-
const result = await testCom.PerformAction('LAUNCH_AR',
|
|
1016
|
+
const result = await testCom.PerformAction('LAUNCH_AR', {
|
|
1017
|
+
uri: 'https://example.com',
|
|
1018
|
+
options: {
|
|
1019
|
+
arPlacement: 'horizontal',
|
|
1020
|
+
} as ARSystemOptions,
|
|
1021
|
+
});
|
|
1009
1022
|
expect(arLaunchSpy).toHaveBeenCalledTimes(1);
|
|
1010
1023
|
});
|
|
1011
1024
|
|
package/src/com/actions/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import START_RENDER from './renderer/startrender.ts';
|
|
1
2
|
import SET_BACKGROUND from './scene/setbackground.ts';
|
|
2
3
|
import RESET_CAMERA from './camera/resetcamera.ts';
|
|
3
4
|
import SET_CAMERA_LAYER from './camera/setcameralayer.ts';
|
|
@@ -28,6 +29,7 @@ import EXPORT_SCENE from './scene/exportscene.ts';
|
|
|
28
29
|
import LAUNCH_AR from './scene/launchar.ts';
|
|
29
30
|
|
|
30
31
|
export interface Actions {
|
|
32
|
+
START_RENDER: START_RENDER;
|
|
31
33
|
GET_ALL_SCENE_DATA: GET_ALL_SCENE_DATA;
|
|
32
34
|
GET_ALL_OBJECTS: GET_ALL_OBJECTS;
|
|
33
35
|
GET_OBJECTS: GET_OBJECTS;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ARSystemOptions } from '../../../ar/AR';
|
|
2
2
|
|
|
3
3
|
export default interface LAUNCH_AR {
|
|
4
4
|
DESCRIPTION: 'Launches AR mode in native capabilities. (iOS: AR Quick Look, Android: Google Scene Viewer)';
|
|
5
|
-
PAYLOAD?:
|
|
5
|
+
PAYLOAD: { uri: string; options?: ARSystemOptions };
|
|
6
6
|
RETURN: Promise<void>;
|
|
7
7
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Loader } from '../loader/Loader';
|
|
2
|
+
import { Exporter } from '../exporter/Exporter';
|
|
3
|
+
import {
|
|
4
|
+
type FileType,
|
|
5
|
+
SUPPORTED_FILE_TYPES,
|
|
6
|
+
type ExportOptions,
|
|
7
|
+
} from '../types';
|
|
8
|
+
|
|
9
|
+
export class ConversionError extends Error {
|
|
10
|
+
constructor(
|
|
11
|
+
message: string,
|
|
12
|
+
public readonly cause?: unknown,
|
|
13
|
+
) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'ConversionError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class FileTypeError extends ConversionError {
|
|
20
|
+
constructor(extension: string) {
|
|
21
|
+
super(
|
|
22
|
+
`Unsupported file type: ${extension}. Supported types are: ${SUPPORTED_FILE_TYPES.join(', ')}`,
|
|
23
|
+
);
|
|
24
|
+
this.name = 'FileTypeError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class NetworkError extends ConversionError {
|
|
29
|
+
constructor(uri: string, cause?: unknown) {
|
|
30
|
+
super(`Failed to fetch file from ${uri}`, cause);
|
|
31
|
+
this.name = 'NetworkError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class Converter {
|
|
36
|
+
private _loader: Loader;
|
|
37
|
+
private _exporter: Exporter;
|
|
38
|
+
|
|
39
|
+
constructor(private readonly _uri: string) {
|
|
40
|
+
this._loader = new Loader();
|
|
41
|
+
this._exporter = new Exporter();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public static convert(uri: string): Converter {
|
|
45
|
+
return new Converter(uri);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public async to<T extends FileType>(
|
|
49
|
+
type: T,
|
|
50
|
+
options?: ExportOptions<T>,
|
|
51
|
+
): Promise<ArrayBuffer> {
|
|
52
|
+
try {
|
|
53
|
+
const sourceType = this._getFileTypeFromUri();
|
|
54
|
+
|
|
55
|
+
// If source and target types match, just return the file content
|
|
56
|
+
if (sourceType === type) {
|
|
57
|
+
return await this._loadFile();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Otherwise, convert through Object3D
|
|
61
|
+
const object3D = await this._loader.load(this._uri);
|
|
62
|
+
return await this._exporter.export(object3D, type, options);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (error instanceof ConversionError) {
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
throw new ConversionError('Failed to convert file', error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private _getFileTypeFromUri(): FileType {
|
|
72
|
+
const extension = this._uri.split('.').pop()?.toLowerCase();
|
|
73
|
+
if (!extension) {
|
|
74
|
+
throw new FileTypeError('no extension');
|
|
75
|
+
}
|
|
76
|
+
if (!SUPPORTED_FILE_TYPES.includes(extension as FileType)) {
|
|
77
|
+
throw new FileTypeError(extension);
|
|
78
|
+
}
|
|
79
|
+
return extension as FileType;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private async _loadFile(): Promise<ArrayBuffer> {
|
|
83
|
+
try {
|
|
84
|
+
const response = await fetch(this._uri);
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
throw new NetworkError(
|
|
87
|
+
this._uri,
|
|
88
|
+
`HTTP error! status: ${response.status}`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return response.arrayBuffer();
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error instanceof NetworkError) {
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
throw new NetworkError(this._uri, error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Example usage:
|
|
102
|
+
// Converter.convert('https://example.com/model.glb').to('usdz', {
|
|
103
|
+
// usdz: {
|
|
104
|
+
// ar: {
|
|
105
|
+
// anchoring: { type: 'plane' },
|
|
106
|
+
// planeAnchoring: { alignment: 'horizontal' }
|
|
107
|
+
// }
|
|
108
|
+
// }
|
|
109
|
+
// });
|
|
110
|
+
//
|
|
111
|
+
// Converter.convert('https://example.com/model.usdz').to('gltf', {
|
|
112
|
+
// gltf: {
|
|
113
|
+
// onlyVisible: true,
|
|
114
|
+
// maxTextureSize: 2048,
|
|
115
|
+
// includeCustomExtensions: true
|
|
116
|
+
// }
|
|
117
|
+
// });
|
package/src/dive.ts
CHANGED
|
@@ -18,11 +18,12 @@ import { DIVEAnimationSystem } from './animation/AnimationSystem.ts';
|
|
|
18
18
|
import DIVEAxisCamera from './axiscamera/AxisCamera.ts';
|
|
19
19
|
import { getObjectDelta } from './helper/getObjectDelta/getObjectDelta.ts';
|
|
20
20
|
import { MathUtils } from 'three';
|
|
21
|
-
import {
|
|
21
|
+
import { SystemInfo } from './info/Info.ts';
|
|
22
22
|
import pkgjson from '../package.json';
|
|
23
23
|
|
|
24
24
|
export type DIVESettings = {
|
|
25
25
|
autoResize: boolean;
|
|
26
|
+
autoStart: boolean;
|
|
26
27
|
displayAxes: boolean;
|
|
27
28
|
renderer: Partial<DIVERendererSettings>;
|
|
28
29
|
perspectiveCamera: Partial<DIVEPerspectiveCameraSettings>;
|
|
@@ -31,6 +32,7 @@ export type DIVESettings = {
|
|
|
31
32
|
|
|
32
33
|
export const DIVEDefaultSettings: DIVESettings = {
|
|
33
34
|
autoResize: true,
|
|
35
|
+
autoStart: true,
|
|
34
36
|
displayAxes: false,
|
|
35
37
|
renderer: DIVERendererDefaultSettings,
|
|
36
38
|
perspectiveCamera: DIVEPerspectiveCameraDefaultSettings,
|
|
@@ -156,8 +158,8 @@ export default class DIVE {
|
|
|
156
158
|
return this.renderer.domElement;
|
|
157
159
|
}
|
|
158
160
|
|
|
159
|
-
public get Info():
|
|
160
|
-
return
|
|
161
|
+
public get Info(): SystemInfo {
|
|
162
|
+
return SystemInfo;
|
|
161
163
|
}
|
|
162
164
|
|
|
163
165
|
// setters
|
|
@@ -266,9 +268,6 @@ export default class DIVE {
|
|
|
266
268
|
this.addResizeObserver();
|
|
267
269
|
}
|
|
268
270
|
|
|
269
|
-
// whene everything is done, start the renderer
|
|
270
|
-
this.renderer.StartRenderer(this.scene, this.perspectiveCamera);
|
|
271
|
-
|
|
272
271
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
273
272
|
(window as any).DIVE = {
|
|
274
273
|
PrintScene: () => {
|
|
@@ -307,6 +306,11 @@ export default class DIVE {
|
|
|
307
306
|
@@@@@@@ @@@@@@
|
|
308
307
|
|
|
309
308
|
`);
|
|
309
|
+
|
|
310
|
+
if (this._settings.autoStart) {
|
|
311
|
+
// when everything is done, start the renderer
|
|
312
|
+
this.renderer.StartRenderer(this.scene, this.perspectiveCamera);
|
|
313
|
+
}
|
|
310
314
|
}
|
|
311
315
|
|
|
312
316
|
public Dispose(): void {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Object3D } from 'three';
|
|
2
|
+
import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter';
|
|
3
|
+
import { USDZExporter } from 'three/examples/jsm/exporters/USDZExporter';
|
|
4
|
+
import {
|
|
5
|
+
type FileType,
|
|
6
|
+
type GLTFExporterOptions,
|
|
7
|
+
type USDZExporterOptions,
|
|
8
|
+
type ExportOptions,
|
|
9
|
+
} from '../types';
|
|
10
|
+
|
|
11
|
+
export class Exporter {
|
|
12
|
+
private _gltfExporter: GLTFExporter;
|
|
13
|
+
private _usdzExporter: USDZExporter;
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
this._gltfExporter = new GLTFExporter();
|
|
17
|
+
this._usdzExporter = new USDZExporter();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public async export<T extends FileType>(
|
|
21
|
+
object: Object3D,
|
|
22
|
+
type: T,
|
|
23
|
+
options?: ExportOptions<T>,
|
|
24
|
+
): Promise<ArrayBuffer> {
|
|
25
|
+
switch (type) {
|
|
26
|
+
case 'glb': {
|
|
27
|
+
return this._exportGlb(object, options);
|
|
28
|
+
}
|
|
29
|
+
case 'gltf': {
|
|
30
|
+
return this._exportGltf(object, options);
|
|
31
|
+
}
|
|
32
|
+
case 'usdz': {
|
|
33
|
+
return this._exportUsdz(object, options);
|
|
34
|
+
}
|
|
35
|
+
default:
|
|
36
|
+
throw new Error(`Unsupported file type: ${type}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private async _exportGlb(
|
|
41
|
+
object: Object3D,
|
|
42
|
+
options?: GLTFExporterOptions,
|
|
43
|
+
): Promise<ArrayBuffer> {
|
|
44
|
+
const result = await this._gltfExporter.parseAsync(object, {
|
|
45
|
+
...options,
|
|
46
|
+
binary: true,
|
|
47
|
+
});
|
|
48
|
+
if (result instanceof ArrayBuffer) {
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
throw new Error('Failed to export GLB: expected ArrayBuffer');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private async _exportGltf(
|
|
55
|
+
object: Object3D,
|
|
56
|
+
options?: GLTFExporterOptions,
|
|
57
|
+
): Promise<ArrayBuffer> {
|
|
58
|
+
const json = await this._gltfExporter.parseAsync(object, {
|
|
59
|
+
...options,
|
|
60
|
+
binary: false,
|
|
61
|
+
});
|
|
62
|
+
const text = JSON.stringify(json);
|
|
63
|
+
const encoder = new TextEncoder();
|
|
64
|
+
const bytes = encoder.encode(text);
|
|
65
|
+
return bytes.buffer as ArrayBuffer;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private async _exportUsdz(
|
|
69
|
+
object: Object3D,
|
|
70
|
+
options?: USDZExporterOptions,
|
|
71
|
+
): Promise<ArrayBuffer> {
|
|
72
|
+
const result = await this._usdzExporter.parse(object, options);
|
|
73
|
+
return result.buffer as ArrayBuffer;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -1,11 +1,62 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
3
|
+
// A generic constructor type.
|
|
4
|
+
type Constructor<T = {}> = new (...args: any[]) => T;
|
|
5
|
+
|
|
6
|
+
// Converts a union of types to an intersection of types.
|
|
7
|
+
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
|
|
8
|
+
k: infer I,
|
|
9
|
+
) => void
|
|
10
|
+
? I
|
|
11
|
+
: never;
|
|
12
|
+
|
|
13
|
+
// Merges the instance types of the base class and all mixin classes.
|
|
14
|
+
type MixedInstance<
|
|
15
|
+
T extends Constructor,
|
|
16
|
+
K extends readonly Constructor[],
|
|
17
|
+
> = InstanceType<T> & UnionToIntersection<InstanceType<K[number]>>;
|
|
18
|
+
|
|
19
|
+
// Recursively flatten the constructor parameter lists for a tuple of constructors.
|
|
20
|
+
type FlattenConstructorParams<T extends readonly Constructor[]> =
|
|
21
|
+
T extends readonly [infer First, ...infer Rest]
|
|
22
|
+
? First extends Constructor
|
|
23
|
+
? Rest extends readonly Constructor[]
|
|
24
|
+
? [
|
|
25
|
+
...ConstructorParameters<First>,
|
|
26
|
+
...FlattenConstructorParams<Rest>,
|
|
27
|
+
]
|
|
28
|
+
: ConstructorParameters<First>
|
|
29
|
+
: []
|
|
30
|
+
: [];
|
|
31
|
+
|
|
32
|
+
// Constructs the mixed constructor type.
|
|
33
|
+
// It accepts the parameters of T followed by all the parameters of K and
|
|
34
|
+
// produces an instance that is the intersection of the instance types.
|
|
35
|
+
type MixedConstructor<
|
|
36
|
+
T extends Constructor,
|
|
37
|
+
K extends readonly Constructor[],
|
|
38
|
+
> = new (
|
|
39
|
+
...args: [...ConstructorParameters<T>, ...FlattenConstructorParams<K>]
|
|
40
|
+
) => MixedInstance<T, K>;
|
|
41
|
+
|
|
1
42
|
/**
|
|
2
|
-
*
|
|
43
|
+
* Applies mixins to a base class.
|
|
44
|
+
*
|
|
45
|
+
* @param derivedCtor Base class constructor
|
|
46
|
+
* @param constructors Additional constructors that get mixed into the base class
|
|
47
|
+
* @returns A mixed constructor with the instance type of the base class and all mixin classes
|
|
48
|
+
* @example
|
|
49
|
+
* ```
|
|
50
|
+
* const SelectableMovableObject3D = applyMixins(Object3D, [DIVESelectable, DIVEMovable]);
|
|
51
|
+
* const instance = new SelectableMovableObject3D();
|
|
52
|
+
* instance.onMove();
|
|
53
|
+
* instance.onSelect();
|
|
54
|
+
* ```
|
|
3
55
|
*/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
): void => {
|
|
56
|
+
export function applyMixins<
|
|
57
|
+
T extends Constructor,
|
|
58
|
+
K extends readonly Constructor[],
|
|
59
|
+
>(derivedCtor: T, constructors: K): MixedConstructor<T, K> {
|
|
9
60
|
constructors.forEach((baseCtor) => {
|
|
10
61
|
Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
|
|
11
62
|
Object.defineProperty(
|
|
@@ -15,4 +66,5 @@ export const applyMixins = (
|
|
|
15
66
|
);
|
|
16
67
|
});
|
|
17
68
|
});
|
|
18
|
-
|
|
69
|
+
return derivedCtor as unknown as MixedConstructor<T, K>;
|
|
70
|
+
}
|
package/src/info/Info.ts
CHANGED
|
@@ -1,137 +1,161 @@
|
|
|
1
|
-
|
|
2
|
-
'UNKNWON_ERROR' = 0,
|
|
3
|
-
'NO_HTTPS' = 1,
|
|
4
|
-
'IMMERSIVE_AR_NOT_SUPPORTED_BY_DEVICE' = 2,
|
|
5
|
-
'AR_SESSION_NOT_ALLOWED' = 3,
|
|
6
|
-
}
|
|
1
|
+
import { ESystem, EWebXRUnsupportedReason } from '../types/info';
|
|
7
2
|
|
|
8
|
-
export class
|
|
9
|
-
private static _supportsWebXR: boolean
|
|
10
|
-
private static _webXRUnsupportedReason:
|
|
3
|
+
export class SystemInfo {
|
|
4
|
+
private static _supportsWebXR: boolean = false;
|
|
5
|
+
private static _webXRUnsupportedReason: EWebXRUnsupportedReason | null =
|
|
11
6
|
null;
|
|
12
7
|
|
|
13
8
|
/**
|
|
14
|
-
*
|
|
15
|
-
* @returns The system
|
|
9
|
+
* Gets the current system (iOS, Android, Windows, etc.)
|
|
10
|
+
* @returns DIVESystem The current system
|
|
16
11
|
*/
|
|
17
|
-
public static GetSystem():
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return 'Linux';
|
|
29
|
-
} else {
|
|
30
|
-
return 'Unknown';
|
|
12
|
+
public static GetSystem(): ESystem {
|
|
13
|
+
if (typeof window === 'undefined' || !window.navigator) {
|
|
14
|
+
return ESystem.UNKNOWN;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const userAgent = window.navigator.userAgent.toLowerCase();
|
|
18
|
+
if (userAgent.includes('iphone') || userAgent.includes('ipad')) {
|
|
19
|
+
return ESystem.IOS;
|
|
20
|
+
}
|
|
21
|
+
if (userAgent.includes('android')) {
|
|
22
|
+
return ESystem.ANDROID;
|
|
31
23
|
}
|
|
24
|
+
if (userAgent.includes('windows')) {
|
|
25
|
+
return ESystem.WINDOWS;
|
|
26
|
+
}
|
|
27
|
+
if (userAgent.includes('macintosh')) {
|
|
28
|
+
return ESystem.MACOS;
|
|
29
|
+
}
|
|
30
|
+
if (userAgent.includes('linux')) {
|
|
31
|
+
return ESystem.LINUX;
|
|
32
|
+
}
|
|
33
|
+
return ESystem.UNKNOWN;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
37
|
* @returns A promise that resolves to a boolean indicating whether the user's device supports WebXR.
|
|
36
38
|
*/
|
|
37
39
|
public static async GetSupportsWebXR(): Promise<boolean> {
|
|
38
|
-
if (this._supportsWebXR !==
|
|
40
|
+
if (this._supportsWebXR !== false) {
|
|
39
41
|
return this._supportsWebXR;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
//
|
|
43
|
-
if (!
|
|
44
|
+
// Check if we're in a secure context (HTTPS)
|
|
45
|
+
if (!window.isSecureContext) {
|
|
44
46
|
this._supportsWebXR = false;
|
|
47
|
+
this._webXRUnsupportedReason = EWebXRUnsupportedReason.NO_HTTPS;
|
|
48
|
+
return this._supportsWebXR;
|
|
49
|
+
}
|
|
45
50
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
WebXRUnsupportedReason.UNKNWON_ERROR;
|
|
51
|
-
}
|
|
52
|
-
|
|
51
|
+
// Check if XRSystem is available
|
|
52
|
+
if (!navigator.xr) {
|
|
53
|
+
this._supportsWebXR = false;
|
|
54
|
+
this._webXRUnsupportedReason = EWebXRUnsupportedReason.NO_WEBXR_API;
|
|
53
55
|
return this._supportsWebXR;
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
// Check if immersive-vr session mode is supported
|
|
57
58
|
try {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
// Check specifically for immersive-ar support
|
|
60
|
+
const arSupported =
|
|
61
|
+
await navigator.xr.isSessionSupported('immersive-ar');
|
|
62
|
+
this._supportsWebXR = arSupported;
|
|
63
|
+
|
|
64
|
+
if (!this._supportsWebXR) {
|
|
61
65
|
this._webXRUnsupportedReason =
|
|
62
|
-
|
|
66
|
+
EWebXRUnsupportedReason.IMMERSIVE_AR_NOT_SUPPORTED_BY_DEVICE;
|
|
63
67
|
}
|
|
64
|
-
this._supportsWebXR = supported;
|
|
65
68
|
} catch (error) {
|
|
66
69
|
this._supportsWebXR = false;
|
|
67
70
|
this._webXRUnsupportedReason =
|
|
68
|
-
|
|
71
|
+
EWebXRUnsupportedReason.AR_PERMISSION_DENIED;
|
|
69
72
|
}
|
|
73
|
+
|
|
70
74
|
return this._supportsWebXR;
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
/**
|
|
74
|
-
* @returns The reason why WebXR is not supported on the user's device. Returns null if WebXR is supported
|
|
78
|
+
* @returns The reason why WebXR is not supported on the user's device. Returns null if WebXR is supported.
|
|
75
79
|
*/
|
|
76
|
-
public static GetWebXRUnsupportedReason():
|
|
77
|
-
if (this._supportsWebXR
|
|
78
|
-
console.log('WebXR
|
|
80
|
+
public static GetWebXRUnsupportedReason(): EWebXRUnsupportedReason | null {
|
|
81
|
+
if (this._supportsWebXR) {
|
|
82
|
+
console.log('WebXR is supported.');
|
|
79
83
|
return null;
|
|
80
84
|
}
|
|
81
85
|
return this._webXRUnsupportedReason;
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
/**
|
|
85
|
-
*
|
|
89
|
+
* Checks if ARQuickLook is supported on the current device
|
|
90
|
+
* This checks for:
|
|
91
|
+
* 1. AR support via relList
|
|
92
|
+
*
|
|
93
|
+
* Requirements:
|
|
94
|
+
* - iOS 13.0 or later
|
|
95
|
+
* - Safari browser (ARQuickLook is only supported in Safari)
|
|
96
|
+
* - Device with AR capabilities (iPhone/iPad with LiDAR scanner or ARKit support)
|
|
97
|
+
*
|
|
98
|
+
* Note: ARQuickLook is only available in Safari on iOS. Other browsers
|
|
99
|
+
* (Chrome, Firefox, etc.) do not support ARQuickLook, even on iOS.
|
|
100
|
+
*
|
|
101
|
+
* @returns boolean indicating if ARQuickLook is supported
|
|
86
102
|
*/
|
|
87
103
|
public static GetSupportsARQuickLook(): boolean {
|
|
88
104
|
const a = document.createElement('a');
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// fallback check
|
|
94
|
-
const userAgent = navigator.userAgent;
|
|
105
|
+
return a.relList.supports('ar');
|
|
106
|
+
}
|
|
95
107
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Checks if SceneViewer is supported on the current device
|
|
110
|
+
* This checks for:
|
|
111
|
+
* 1. Android device
|
|
112
|
+
* 2. Chrome browser (version 89 or later)
|
|
113
|
+
*
|
|
114
|
+
* Requirements:
|
|
115
|
+
* - Android 7.0 (API level 24) or later
|
|
116
|
+
* - Chrome for Android 89 or later
|
|
117
|
+
*
|
|
118
|
+
* Note: According to Google's documentation, if these requirements are met,
|
|
119
|
+
* SceneViewer will be available. If ARCore is not installed, SceneViewer will
|
|
120
|
+
* fall back to showing the model in 3D.
|
|
121
|
+
*
|
|
122
|
+
* @returns boolean indicating if SceneViewer is supported
|
|
123
|
+
*/
|
|
124
|
+
public static GetSupportsSceneViewer(): boolean {
|
|
125
|
+
// Check if we're in a browser environment
|
|
126
|
+
if (typeof window === 'undefined' || !window.navigator) {
|
|
101
127
|
return false;
|
|
102
128
|
}
|
|
103
129
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
130
|
+
const userAgent = window.navigator.userAgent.toLowerCase();
|
|
131
|
+
|
|
132
|
+
// Check if we're on Android
|
|
133
|
+
if (!userAgent.includes('android')) {
|
|
107
134
|
return false;
|
|
108
135
|
}
|
|
109
|
-
const iOSVersion = parseInt(match[1], 10);
|
|
110
|
-
|
|
111
|
-
// Minimum iOS version for QuickLook support
|
|
112
|
-
const minQuickLookVersion = 12;
|
|
113
136
|
|
|
114
|
-
// Check if
|
|
115
|
-
if (
|
|
137
|
+
// Check if we're using Chrome
|
|
138
|
+
if (!userAgent.includes('chrome')) {
|
|
116
139
|
return false;
|
|
117
140
|
}
|
|
118
141
|
|
|
119
|
-
// Check
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return true;
|
|
142
|
+
// Check Chrome version (89 or later)
|
|
143
|
+
const chromeVersion = userAgent.match(/chrome\/(\d+)/);
|
|
144
|
+
if (!chromeVersion || parseInt(chromeVersion[1]) < 89) {
|
|
145
|
+
return false;
|
|
124
146
|
}
|
|
125
147
|
|
|
126
|
-
|
|
127
|
-
return false;
|
|
148
|
+
return true;
|
|
128
149
|
}
|
|
129
150
|
|
|
130
151
|
/**
|
|
131
152
|
* @returns A boolean indicating whether the user's device is a mobile device.
|
|
132
153
|
*/
|
|
133
154
|
public static get isMobile(): boolean {
|
|
134
|
-
return
|
|
155
|
+
return (
|
|
156
|
+
this.GetSystem() === ESystem.ANDROID ||
|
|
157
|
+
this.GetSystem() === ESystem.IOS
|
|
158
|
+
);
|
|
135
159
|
}
|
|
136
160
|
|
|
137
161
|
/**
|