@combeenation/3d-viewer 12.4.1 → 13.0.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/dist/lib-cjs/buildinfo.json +1 -1
- package/dist/lib-cjs/commonjs.tsconfig.tsbuildinfo +1 -1
- package/dist/lib-cjs/index.d.ts +63 -63
- package/dist/lib-cjs/index.js +81 -114
- package/dist/lib-cjs/index.js.map +1 -1
- package/dist/lib-cjs/internal/cbnCustomBabylonLoaderPlugin.d.ts +10 -0
- package/dist/lib-cjs/internal/cbnCustomBabylonLoaderPlugin.js +124 -0
- package/dist/lib-cjs/internal/cbnCustomBabylonLoaderPlugin.js.map +1 -0
- package/dist/lib-cjs/internal/cloningHelper.d.ts +19 -0
- package/dist/lib-cjs/internal/cloningHelper.js +165 -0
- package/dist/lib-cjs/internal/cloningHelper.js.map +1 -0
- package/dist/lib-cjs/internal/deviceHelper.d.ts +9 -0
- package/dist/lib-cjs/{api/util → internal}/deviceHelper.js +6 -10
- package/dist/lib-cjs/internal/deviceHelper.js.map +1 -0
- package/dist/lib-cjs/internal/geometryHelper.d.ts +21 -0
- package/dist/lib-cjs/{api/util → internal}/geometryHelper.js +57 -24
- package/dist/lib-cjs/internal/geometryHelper.js.map +1 -0
- package/dist/lib-cjs/internal/metadataHelper.d.ts +26 -0
- package/dist/lib-cjs/internal/metadataHelper.js +51 -0
- package/dist/lib-cjs/internal/metadataHelper.js.map +1 -0
- package/dist/lib-cjs/internal/paintableHelper.d.ts +40 -0
- package/dist/lib-cjs/internal/paintableHelper.js +287 -0
- package/dist/lib-cjs/internal/paintableHelper.js.map +1 -0
- package/dist/lib-cjs/internal/tagsHelper.d.ts +12 -0
- package/dist/lib-cjs/internal/tagsHelper.js +38 -0
- package/dist/lib-cjs/internal/tagsHelper.js.map +1 -0
- package/dist/lib-cjs/manager/cameraManager.d.ts +51 -0
- package/dist/lib-cjs/manager/cameraManager.js +154 -0
- package/dist/lib-cjs/manager/cameraManager.js.map +1 -0
- package/dist/lib-cjs/manager/debugManager.d.ts +60 -0
- package/dist/lib-cjs/manager/debugManager.js +218 -0
- package/dist/lib-cjs/manager/debugManager.js.map +1 -0
- package/dist/lib-cjs/manager/eventManager.d.ts +52 -0
- package/dist/lib-cjs/manager/eventManager.js +72 -0
- package/dist/lib-cjs/manager/eventManager.js.map +1 -0
- package/dist/lib-cjs/{api/manager → manager}/gltfExportManager.d.ts +29 -34
- package/dist/lib-cjs/{api/manager → manager}/gltfExportManager.js +99 -120
- package/dist/lib-cjs/manager/gltfExportManager.js.map +1 -0
- package/dist/lib-cjs/manager/materialManager.d.ts +35 -0
- package/dist/lib-cjs/manager/materialManager.js +126 -0
- package/dist/lib-cjs/manager/materialManager.js.map +1 -0
- package/dist/lib-cjs/manager/modelManager.d.ts +145 -0
- package/dist/lib-cjs/manager/modelManager.js +381 -0
- package/dist/lib-cjs/manager/modelManager.js.map +1 -0
- package/dist/lib-cjs/manager/parameterManager.d.ts +210 -0
- package/dist/lib-cjs/manager/parameterManager.js +515 -0
- package/dist/lib-cjs/manager/parameterManager.js.map +1 -0
- package/dist/lib-cjs/manager/sceneManager.d.ts +45 -0
- package/dist/lib-cjs/manager/sceneManager.js +65 -0
- package/dist/lib-cjs/manager/sceneManager.js.map +1 -0
- package/dist/lib-cjs/manager/screenshotManager.d.ts +36 -0
- package/dist/lib-cjs/manager/screenshotManager.js +40 -0
- package/dist/lib-cjs/manager/screenshotManager.js.map +1 -0
- package/dist/lib-cjs/manager/textureManager.d.ts +12 -0
- package/dist/lib-cjs/manager/textureManager.js +44 -0
- package/dist/lib-cjs/manager/textureManager.js.map +1 -0
- package/dist/lib-cjs/viewer.d.ts +117 -0
- package/dist/lib-cjs/viewer.js +222 -0
- package/dist/lib-cjs/viewer.js.map +1 -0
- package/dist/lib-cjs/{api/classes/viewerError.d.ts → viewerError.d.ts} +6 -1
- package/dist/lib-cjs/{api/classes/viewerError.js → viewerError.js} +6 -1
- package/dist/lib-cjs/viewerError.js.map +1 -0
- package/package.json +10 -11
- package/src/dev.ts +14 -37
- package/src/{types.d.ts → globalTypes.d.ts} +8 -18
- package/src/index.ts +79 -113
- package/src/internal/cbnCustomBabylonLoaderPlugin.ts +149 -0
- package/src/internal/cloningHelper.ts +225 -0
- package/src/internal/deviceHelper.ts +25 -0
- package/src/{api/util → internal}/geometryHelper.ts +63 -24
- package/src/internal/metadataHelper.ts +63 -0
- package/src/internal/paintableHelper.ts +310 -0
- package/src/internal/tagsHelper.ts +41 -0
- package/src/manager/cameraManager.ts +236 -0
- package/src/manager/debugManager.ts +245 -0
- package/src/manager/eventManager.ts +72 -0
- package/src/{api/manager → manager}/gltfExportManager.ts +132 -125
- package/src/manager/materialManager.ts +135 -0
- package/src/manager/modelManager.ts +456 -0
- package/src/manager/parameterManager.ts +652 -0
- package/src/manager/sceneManager.ts +101 -0
- package/src/manager/screenshotManager.ts +59 -0
- package/src/manager/textureManager.ts +32 -0
- package/src/viewer.ts +296 -0
- package/src/{api/classes/viewerError.ts → viewerError.ts} +6 -1
- package/dist/lib-cjs/api/classes/animationInterface.d.ts +0 -8
- package/dist/lib-cjs/api/classes/animationInterface.js +0 -3
- package/dist/lib-cjs/api/classes/animationInterface.js.map +0 -1
- package/dist/lib-cjs/api/classes/dottedPath.d.ts +0 -79
- package/dist/lib-cjs/api/classes/dottedPath.js +0 -167
- package/dist/lib-cjs/api/classes/dottedPath.js.map +0 -1
- package/dist/lib-cjs/api/classes/element.d.ts +0 -153
- package/dist/lib-cjs/api/classes/element.js +0 -703
- package/dist/lib-cjs/api/classes/element.js.map +0 -1
- package/dist/lib-cjs/api/classes/event.d.ts +0 -401
- package/dist/lib-cjs/api/classes/event.js +0 -425
- package/dist/lib-cjs/api/classes/event.js.map +0 -1
- package/dist/lib-cjs/api/classes/eventBroadcaster.d.ts +0 -26
- package/dist/lib-cjs/api/classes/eventBroadcaster.js +0 -50
- package/dist/lib-cjs/api/classes/eventBroadcaster.js.map +0 -1
- package/dist/lib-cjs/api/classes/fuzzyMap.d.ts +0 -7
- package/dist/lib-cjs/api/classes/fuzzyMap.js +0 -22
- package/dist/lib-cjs/api/classes/fuzzyMap.js.map +0 -1
- package/dist/lib-cjs/api/classes/parameter.d.ts +0 -410
- package/dist/lib-cjs/api/classes/parameter.js +0 -643
- package/dist/lib-cjs/api/classes/parameter.js.map +0 -1
- package/dist/lib-cjs/api/classes/parameterObservable.d.ts +0 -36
- package/dist/lib-cjs/api/classes/parameterObservable.js +0 -73
- package/dist/lib-cjs/api/classes/parameterObservable.js.map +0 -1
- package/dist/lib-cjs/api/classes/parameterizable.d.ts +0 -15
- package/dist/lib-cjs/api/classes/parameterizable.js +0 -103
- package/dist/lib-cjs/api/classes/parameterizable.js.map +0 -1
- package/dist/lib-cjs/api/classes/placementAnimation.d.ts +0 -45
- package/dist/lib-cjs/api/classes/placementAnimation.js +0 -177
- package/dist/lib-cjs/api/classes/placementAnimation.js.map +0 -1
- package/dist/lib-cjs/api/classes/variant.d.ts +0 -261
- package/dist/lib-cjs/api/classes/variant.js +0 -873
- package/dist/lib-cjs/api/classes/variant.js.map +0 -1
- package/dist/lib-cjs/api/classes/variantInstance.d.ts +0 -53
- package/dist/lib-cjs/api/classes/variantInstance.js +0 -126
- package/dist/lib-cjs/api/classes/variantInstance.js.map +0 -1
- package/dist/lib-cjs/api/classes/variantParameterizable.d.ts +0 -17
- package/dist/lib-cjs/api/classes/variantParameterizable.js +0 -87
- package/dist/lib-cjs/api/classes/variantParameterizable.js.map +0 -1
- package/dist/lib-cjs/api/classes/viewer.d.ts +0 -215
- package/dist/lib-cjs/api/classes/viewer.js +0 -709
- package/dist/lib-cjs/api/classes/viewer.js.map +0 -1
- package/dist/lib-cjs/api/classes/viewerError.js.map +0 -1
- package/dist/lib-cjs/api/classes/viewerLight.d.ts +0 -66
- package/dist/lib-cjs/api/classes/viewerLight.js +0 -345
- package/dist/lib-cjs/api/classes/viewerLight.js.map +0 -1
- package/dist/lib-cjs/api/internal/lensRendering.d.ts +0 -8
- package/dist/lib-cjs/api/internal/lensRendering.js +0 -12
- package/dist/lib-cjs/api/internal/lensRendering.js.map +0 -1
- package/dist/lib-cjs/api/internal/sceneSetup.d.ts +0 -13
- package/dist/lib-cjs/api/internal/sceneSetup.js +0 -228
- package/dist/lib-cjs/api/internal/sceneSetup.js.map +0 -1
- package/dist/lib-cjs/api/manager/animationManager.d.ts +0 -30
- package/dist/lib-cjs/api/manager/animationManager.js +0 -127
- package/dist/lib-cjs/api/manager/animationManager.js.map +0 -1
- package/dist/lib-cjs/api/manager/gltfExportManager.js.map +0 -1
- package/dist/lib-cjs/api/manager/sceneManager.d.ts +0 -33
- package/dist/lib-cjs/api/manager/sceneManager.js +0 -129
- package/dist/lib-cjs/api/manager/sceneManager.js.map +0 -1
- package/dist/lib-cjs/api/manager/tagManager.d.ts +0 -118
- package/dist/lib-cjs/api/manager/tagManager.js +0 -531
- package/dist/lib-cjs/api/manager/tagManager.js.map +0 -1
- package/dist/lib-cjs/api/manager/textureLoadManager.d.ts +0 -22
- package/dist/lib-cjs/api/manager/textureLoadManager.js +0 -108
- package/dist/lib-cjs/api/manager/textureLoadManager.js.map +0 -1
- package/dist/lib-cjs/api/manager/variantInstanceManager.d.ts +0 -106
- package/dist/lib-cjs/api/manager/variantInstanceManager.js +0 -291
- package/dist/lib-cjs/api/manager/variantInstanceManager.js.map +0 -1
- package/dist/lib-cjs/api/store/specStorage.d.ts +0 -32
- package/dist/lib-cjs/api/store/specStorage.js +0 -66
- package/dist/lib-cjs/api/store/specStorage.js.map +0 -1
- package/dist/lib-cjs/api/util/babylonHelper.d.ts +0 -238
- package/dist/lib-cjs/api/util/babylonHelper.js +0 -826
- package/dist/lib-cjs/api/util/babylonHelper.js.map +0 -1
- package/dist/lib-cjs/api/util/debugHelper.d.ts +0 -9
- package/dist/lib-cjs/api/util/debugHelper.js +0 -94
- package/dist/lib-cjs/api/util/debugHelper.js.map +0 -1
- package/dist/lib-cjs/api/util/deviceHelper.d.ts +0 -9
- package/dist/lib-cjs/api/util/deviceHelper.js.map +0 -1
- package/dist/lib-cjs/api/util/geometryHelper.d.ts +0 -17
- package/dist/lib-cjs/api/util/geometryHelper.js.map +0 -1
- package/dist/lib-cjs/api/util/globalTypes.d.ts +0 -490
- package/dist/lib-cjs/api/util/globalTypes.js +0 -2
- package/dist/lib-cjs/api/util/globalTypes.js.map +0 -1
- package/dist/lib-cjs/api/util/resourceHelper.d.ts +0 -58
- package/dist/lib-cjs/api/util/resourceHelper.js +0 -215
- package/dist/lib-cjs/api/util/resourceHelper.js.map +0 -1
- package/dist/lib-cjs/api/util/sceneLoaderHelper.d.ts +0 -58
- package/dist/lib-cjs/api/util/sceneLoaderHelper.js +0 -229
- package/dist/lib-cjs/api/util/sceneLoaderHelper.js.map +0 -1
- package/dist/lib-cjs/api/util/stringHelper.d.ts +0 -13
- package/dist/lib-cjs/api/util/stringHelper.js +0 -33
- package/dist/lib-cjs/api/util/stringHelper.js.map +0 -1
- package/dist/lib-cjs/api/util/structureHelper.d.ts +0 -9
- package/dist/lib-cjs/api/util/structureHelper.js +0 -58
- package/dist/lib-cjs/api/util/structureHelper.js.map +0 -1
- package/src/api/classes/animationInterface.ts +0 -10
- package/src/api/classes/dottedPath.ts +0 -181
- package/src/api/classes/element.ts +0 -766
- package/src/api/classes/event.ts +0 -457
- package/src/api/classes/eventBroadcaster.ts +0 -52
- package/src/api/classes/fuzzyMap.ts +0 -21
- package/src/api/classes/parameter.ts +0 -686
- package/src/api/classes/parameterObservable.ts +0 -73
- package/src/api/classes/parameterizable.ts +0 -87
- package/src/api/classes/placementAnimation.ts +0 -162
- package/src/api/classes/variant.ts +0 -965
- package/src/api/classes/variantInstance.ts +0 -123
- package/src/api/classes/variantParameterizable.ts +0 -83
- package/src/api/classes/viewer.ts +0 -751
- package/src/api/classes/viewerLight.ts +0 -335
- package/src/api/internal/debugViewer.ts +0 -90
- package/src/api/internal/lensRendering.ts +0 -9
- package/src/api/internal/sceneSetup.ts +0 -208
- package/src/api/manager/animationManager.ts +0 -143
- package/src/api/manager/sceneManager.ts +0 -134
- package/src/api/manager/tagManager.ts +0 -572
- package/src/api/manager/textureLoadManager.ts +0 -107
- package/src/api/manager/variantInstanceManager.ts +0 -306
- package/src/api/store/specStorage.ts +0 -68
- package/src/api/util/babylonHelper.ts +0 -915
- package/src/api/util/debugHelper.ts +0 -121
- package/src/api/util/deviceHelper.ts +0 -31
- package/src/api/util/globalTypes.ts +0 -566
- package/src/api/util/resourceHelper.ts +0 -201
- package/src/api/util/sceneLoaderHelper.ts +0 -247
- package/src/api/util/stringHelper.ts +0 -30
- package/src/api/util/structureHelper.ts +0 -62
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DynamicTexture,
|
|
3
|
+
Material,
|
|
4
|
+
PBRMaterial,
|
|
5
|
+
ParameterValue,
|
|
6
|
+
Scene,
|
|
7
|
+
StandardMaterial,
|
|
8
|
+
Texture,
|
|
9
|
+
ViewerError,
|
|
10
|
+
ViewerErrorIds,
|
|
11
|
+
} from '../index';
|
|
12
|
+
import isSvg from 'is-svg';
|
|
13
|
+
import { isString } from 'lodash-es';
|
|
14
|
+
|
|
15
|
+
type PaintableValue = {
|
|
16
|
+
src: string;
|
|
17
|
+
options?: PaintableOptions;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type PaintableOptions = {
|
|
21
|
+
uScale?: number;
|
|
22
|
+
uOffset?: number;
|
|
23
|
+
vScale?: number;
|
|
24
|
+
vOffset?: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Observer implementation for "paintable" parameter.
|
|
29
|
+
* Basically creates a dynamic texture from the source image and assigns it as albedo (or diffuse) texture.
|
|
30
|
+
*/
|
|
31
|
+
export async function paintableParameterObserver(
|
|
32
|
+
newValue: ParameterValue,
|
|
33
|
+
materials: Material[],
|
|
34
|
+
scene: Scene
|
|
35
|
+
): Promise<void> {
|
|
36
|
+
const paintable = parsePaintable(newValue);
|
|
37
|
+
|
|
38
|
+
// check if value is svg or image source, do the conversion accordingly
|
|
39
|
+
const srcIsSvg = isSvg(paintable.src);
|
|
40
|
+
if (!srcIsSvg && paintable.src.includes('<svg') && paintable.src.includes('</svg>')) {
|
|
41
|
+
// seems like the user tried to use a SVG string, as <svg> tags are used
|
|
42
|
+
// inform the user that this is not a valid SVG string
|
|
43
|
+
throw new ViewerError({
|
|
44
|
+
id: ViewerErrorIds.InvalidParameterValue,
|
|
45
|
+
message: `Invalid value for parameter "paintable" given:\nsource string is no valid SVG string\nGiven value: ${paintable.src}`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let imageSource: CanvasImageSource;
|
|
50
|
+
try {
|
|
51
|
+
imageSource = srcIsSvg ? await _createImageFromSvg(paintable.src) : await _createImageFromImgSrc(paintable.src);
|
|
52
|
+
} catch {
|
|
53
|
+
// SVG might be invalid, even if it passes `isSvg` check
|
|
54
|
+
// in this case the image can't be created and will throw an error, which should be handled by the viewer and
|
|
55
|
+
// Combeenation viewer control
|
|
56
|
+
throw new ViewerError({
|
|
57
|
+
id: ViewerErrorIds.InvalidParameterValue,
|
|
58
|
+
message: `Invalid value for parameter "paintable" given:\nimage can't be created from source string\nGiven value: ${paintable.src}`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// apply image source on desired material(s)
|
|
63
|
+
for (const material of materials) {
|
|
64
|
+
_drawPaintableOnMaterial(material, imageSource, scene, paintable.options);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Parser for paintable value.
|
|
70
|
+
* ATM this is only used internally, but it could theoretically be used to create ones own paintable implementation.
|
|
71
|
+
*
|
|
72
|
+
* @param value The value to parse. Examples:
|
|
73
|
+
* ```ts
|
|
74
|
+
* // Default definition as JSON object:
|
|
75
|
+
* '{ "src": "https://path.to/image.jpg", "uScale": 0.5 }'
|
|
76
|
+
*
|
|
77
|
+
* // Short hand definition, only contains source string:
|
|
78
|
+
* 'https://path.to/image.jpg'
|
|
79
|
+
*
|
|
80
|
+
* // Full content, paintable texture is flipped in both directions
|
|
81
|
+
* '{ "src": "https://path.to/image.jpg", "uScale": -1, "vScale": -1, "uOffset": 0, "vOffset": 0 }'
|
|
82
|
+
*
|
|
83
|
+
* // SVG content can be used directly:
|
|
84
|
+
* '<svg>...</svg>'
|
|
85
|
+
*
|
|
86
|
+
* // SVG in src property works as well:
|
|
87
|
+
* '{ "src": "<svg>...</svg>", "uScale": 0.5 }'
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export function parsePaintable(value: ParameterValue): PaintableValue {
|
|
91
|
+
if (!isString(value)) {
|
|
92
|
+
throw new ViewerError({
|
|
93
|
+
id: ViewerErrorIds.InvalidParameterValue,
|
|
94
|
+
message: `Unable to parse paintable value: not a string\nGiven value: ${value}`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const paintableValue: PaintableValue = { src: '' };
|
|
99
|
+
let valObj: { [key: string]: any } | null = null;
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
valObj = JSON.parse(value);
|
|
103
|
+
} catch {
|
|
104
|
+
// use string directly
|
|
105
|
+
paintableValue.src = value;
|
|
106
|
+
|
|
107
|
+
if (value.startsWith('{')) {
|
|
108
|
+
// seems like the user tried to use a JSON string, as the input starts with {
|
|
109
|
+
throw new ViewerError({
|
|
110
|
+
id: ViewerErrorIds.InvalidParameterValue,
|
|
111
|
+
message: `Unable to parse paintable value: not a valid JSON string\nGiven value: ${value}`,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (valObj) {
|
|
117
|
+
// input string is JSON, src attribute is required
|
|
118
|
+
if (!valObj.src) {
|
|
119
|
+
throw new ViewerError({
|
|
120
|
+
id: ViewerErrorIds.InvalidParameterValue,
|
|
121
|
+
message: `Unable to parse paintable value: property "src" is missing\nGiven value: ${value}`,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!isString(valObj.src)) {
|
|
126
|
+
throw new ViewerError({
|
|
127
|
+
id: ViewerErrorIds.InvalidParameterValue,
|
|
128
|
+
message: `Unable to parse paintable value: property "src" is not a string\nGiven value: ${value}`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// split src and options
|
|
133
|
+
const { src, ...options } = valObj;
|
|
134
|
+
paintableValue.src = src;
|
|
135
|
+
|
|
136
|
+
// only forward valid paintable options
|
|
137
|
+
const validOptionKeys = ['uScale', 'vScale', 'uOffset', 'vOffset'];
|
|
138
|
+
const { validOptions, invalidKeys } = Object.entries(options).reduce(
|
|
139
|
+
(accRes, [curKey, curValue]) => {
|
|
140
|
+
const isValidKey = validOptionKeys.includes(curKey);
|
|
141
|
+
if (isValidKey) {
|
|
142
|
+
accRes.validOptions[curKey] = curValue;
|
|
143
|
+
} else {
|
|
144
|
+
accRes.invalidKeys.push(curKey);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return accRes;
|
|
148
|
+
},
|
|
149
|
+
{ validOptions: {} as { [key: string]: any }, invalidKeys: [] as string[] }
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
if (invalidKeys.length) {
|
|
153
|
+
console.warn('Invalid paintable options provided: ' + invalidKeys.toString());
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
paintableValue.options = validOptions;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return paintableValue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function _drawPaintableOnMaterial(
|
|
163
|
+
material: Material,
|
|
164
|
+
imageSource: HTMLImageElement,
|
|
165
|
+
scene: Scene,
|
|
166
|
+
options?: PaintableOptions
|
|
167
|
+
): void {
|
|
168
|
+
// always take width and height from image source, scaling is done with uvScale properties
|
|
169
|
+
const widthAndHeight = {
|
|
170
|
+
width: imageSource.width,
|
|
171
|
+
height: imageSource.height,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const texture = new DynamicTexture(`${material.id}.paintable_texture`, widthAndHeight, scene);
|
|
175
|
+
|
|
176
|
+
// draw image on texture
|
|
177
|
+
const ctx = texture.getContext();
|
|
178
|
+
ctx.drawImage(imageSource, 0, 0);
|
|
179
|
+
texture.update();
|
|
180
|
+
|
|
181
|
+
// apply settings from paintable options to tweak position and scaling of image on the texture
|
|
182
|
+
texture.uScale = options?.uScale ?? texture.uScale;
|
|
183
|
+
texture.vScale = options?.vScale ?? texture.vScale;
|
|
184
|
+
texture.uOffset = options?.uOffset ?? texture.uOffset;
|
|
185
|
+
texture.vOffset = options?.vOffset ?? texture.vOffset;
|
|
186
|
+
|
|
187
|
+
// wrap mode is preferred, as it will always show the texture, no matter which position offset is currently chosen
|
|
188
|
+
// clamp mode requires more knowledge (and patience) when adjusting the uv scale and offset values
|
|
189
|
+
texture.wrapU = Texture.WRAP_ADDRESSMODE;
|
|
190
|
+
texture.wrapV = Texture.WRAP_ADDRESSMODE;
|
|
191
|
+
|
|
192
|
+
// apply the paintable texture on the dedicated material type
|
|
193
|
+
const materialCls = material.getClassName();
|
|
194
|
+
switch (materialCls) {
|
|
195
|
+
case 'PBRMaterial':
|
|
196
|
+
(material as PBRMaterial).albedoTexture = texture;
|
|
197
|
+
break;
|
|
198
|
+
case 'StandardMaterial':
|
|
199
|
+
(material as StandardMaterial).diffuseTexture = texture;
|
|
200
|
+
break;
|
|
201
|
+
default:
|
|
202
|
+
throw new Error(`Setting paintable texture for material of instance "${materialCls}" not implemented (yet)`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Creates a HTML image element based on a SVG string, whereas all the embedded assets in the SVG (eg: fonts, images)
|
|
208
|
+
* are already loaded and exchanged by their base64 representation.\
|
|
209
|
+
* There the output image can exist as "standalone" image and may be used for example as a paintable.
|
|
210
|
+
*
|
|
211
|
+
* !!CAUTION!!: The used functions within this code section are very well evaluated since most alternatives
|
|
212
|
+
* somehow don't work in Safari, as mentioned in the following BJS forum entries:
|
|
213
|
+
* - https://forum.babylonjs.com/t/drawing-svg-content-text-into-dynamictexture-doesnt-work-in-safari-v15/25048
|
|
214
|
+
* - https://forum.babylonjs.com/t/texture-createfrombase64string-doesnt-seem-to-work-for-ios-devices-initially/25502
|
|
215
|
+
*/
|
|
216
|
+
async function _createImageFromSvg(svgSrc: string): Promise<HTMLImageElement> {
|
|
217
|
+
// replace assets with their base64 versions in svg source code
|
|
218
|
+
const svgWithAssetsEmbedded = await _embedAssets(svgSrc);
|
|
219
|
+
|
|
220
|
+
// create data string which can be used as an image source
|
|
221
|
+
const svgEncoded = 'data:image/svg+xml,' + encodeURIComponent(svgWithAssetsEmbedded);
|
|
222
|
+
|
|
223
|
+
return _createImageFromImgSrc(svgEncoded);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Creates an HTML image element from a dedicated image source.\
|
|
228
|
+
* Also waits until the image has loaded.
|
|
229
|
+
*
|
|
230
|
+
* !!CAUTION!!: The `setTimeout` after loading is finished is required due to a Safari bug:
|
|
231
|
+
* - https://bugs.webkit.org/show_bug.cgi?id=39059
|
|
232
|
+
* - https://bugs.webkit.org/show_bug.cgi?id=219770
|
|
233
|
+
*
|
|
234
|
+
* It's not 100% ensured that the timeout solves the issue in every case, but there is no other way unfortunately.\
|
|
235
|
+
* => Keep an eye on it in future projects
|
|
236
|
+
*
|
|
237
|
+
* @param imgSrc Theoretically every source is valid which is also supported by
|
|
238
|
+
* [HTMLImageElement.src](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/src).\
|
|
239
|
+
* Known exceptions are SVGs with embedded assets that are provided as object URL. See comments in
|
|
240
|
+
* {@link createImageFromSvg} for further details.
|
|
241
|
+
*/
|
|
242
|
+
async function _createImageFromImgSrc(imgSrc: string): Promise<HTMLImageElement> {
|
|
243
|
+
const img = new Image();
|
|
244
|
+
|
|
245
|
+
await new Promise((resolve, reject) => {
|
|
246
|
+
img.onload = (): void => {
|
|
247
|
+
setTimeout(resolve, 0);
|
|
248
|
+
};
|
|
249
|
+
img.onerror = (): void => {
|
|
250
|
+
reject();
|
|
251
|
+
};
|
|
252
|
+
img.crossOrigin = 'anonymous';
|
|
253
|
+
img.src = imgSrc;
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
return img;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Replaces all supported image & font URLs in the given SVG with their base64 representation.
|
|
261
|
+
*/
|
|
262
|
+
async function _embedAssets(svgSrc: string): Promise<string> {
|
|
263
|
+
const _imageExtensions = ['png', 'gif', 'jpg', 'jpeg', 'svg', 'bmp'];
|
|
264
|
+
const _fontExtensions = ['woff2', 'woff', 'ttf', 'otf'];
|
|
265
|
+
const _assetExtensions = [..._imageExtensions, ..._fontExtensions];
|
|
266
|
+
// Regex copied from https://stackoverflow.com/a/8943487/1273551, not "stress tested"...
|
|
267
|
+
const urlRegex = /(\bhttps?:\/\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/gi;
|
|
268
|
+
const allUrls = svgSrc.match(urlRegex) as string[];
|
|
269
|
+
|
|
270
|
+
const assetUrls = allUrls.filter(url => {
|
|
271
|
+
const indexParam = url.indexOf('?');
|
|
272
|
+
// remove url parameter to recognize extension
|
|
273
|
+
if (indexParam > -1) {
|
|
274
|
+
url = url.substring(0, indexParam);
|
|
275
|
+
}
|
|
276
|
+
return _assetExtensions.some(extension => url.toLowerCase().endsWith(`.${extension}`));
|
|
277
|
+
});
|
|
278
|
+
const assetBase64Fetcher = assetUrls.map(_fetchBase64AssetUrl);
|
|
279
|
+
const assetFetcherResults = await Promise.all(assetBase64Fetcher);
|
|
280
|
+
return assetFetcherResults.reduce((svgSrc, x) => svgSrc.replace(x.url, x.base64), svgSrc);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Fetch asset (image or font) and convert it to base64 string representation.
|
|
285
|
+
*/
|
|
286
|
+
async function _fetchBase64AssetUrl(assetUrl: string): Promise<{ url: string; base64: string }> {
|
|
287
|
+
// TODO WTT: Cache known base64 representation and only fetch/convert when not already known.
|
|
288
|
+
// Usually the fetch shouldn't hit the network but the browser cache since the SVG was already drawn..
|
|
289
|
+
const resp = await fetch(assetUrl);
|
|
290
|
+
const blob = await resp.blob();
|
|
291
|
+
|
|
292
|
+
return new Promise((resolve, reject) => {
|
|
293
|
+
const reader = new FileReader();
|
|
294
|
+
reader.onloadend = (event): void => {
|
|
295
|
+
const target = event.target;
|
|
296
|
+
if (!target) {
|
|
297
|
+
return reject(`Asset with URL "${assetUrl}" could not be loaded.`);
|
|
298
|
+
}
|
|
299
|
+
const result = target.result;
|
|
300
|
+
if (!result) {
|
|
301
|
+
return reject(`Asset with URL "${assetUrl}" returned an empty result.`);
|
|
302
|
+
}
|
|
303
|
+
resolve({
|
|
304
|
+
url: assetUrl,
|
|
305
|
+
base64: result.toString() as string,
|
|
306
|
+
});
|
|
307
|
+
};
|
|
308
|
+
reader.readAsDataURL(blob);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Material, Node } from '../index';
|
|
2
|
+
import { Tags } from '@babylonjs/core/Misc/tags';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* This is a wrapper around Babylon.js Tags API, as the API is really clunky to use
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
type TagTarget = Node | Material;
|
|
9
|
+
|
|
10
|
+
export function hasTag(object: TagTarget, tag: string): boolean {
|
|
11
|
+
return Tags.MatchesQuery(object, tag);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getTags(object: TagTarget): string[] {
|
|
15
|
+
const tagsObject = Tags.GetTags(object, false) ?? {};
|
|
16
|
+
const tags = Object.keys(tagsObject);
|
|
17
|
+
|
|
18
|
+
return tags;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function setTags(object: TagTarget, tags: string[]): void {
|
|
22
|
+
const tagsString = tags.join(' ');
|
|
23
|
+
Tags.AddTagsTo(object, tagsString);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function deleteAllTags(object: TagTarget): void {
|
|
27
|
+
const curTags = Tags.GetTags(object);
|
|
28
|
+
if (curTags) {
|
|
29
|
+
Tags.RemoveTagsFrom(object, curTags);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function setTagsAsString(object: TagTarget, tagsString: string): void {
|
|
34
|
+
Tags.AddTagsTo(object, tagsString);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function cloneTags(sourceObject: TagTarget, destinationObject: TagTarget): void {
|
|
38
|
+
if (Tags.HasTags(sourceObject)) {
|
|
39
|
+
Tags.AddTagsTo(destinationObject, Tags.GetTags(sourceObject, true));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Animation,
|
|
3
|
+
AnimationGroup,
|
|
4
|
+
ArcRotateCamera,
|
|
5
|
+
ExcludedGeometryList,
|
|
6
|
+
Vector3,
|
|
7
|
+
Viewer,
|
|
8
|
+
ViewerEvent,
|
|
9
|
+
} from '../index';
|
|
10
|
+
|
|
11
|
+
export type CameraPosition = {
|
|
12
|
+
alpha: number;
|
|
13
|
+
beta: number;
|
|
14
|
+
radius: number;
|
|
15
|
+
target: Vector3;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type AutofocusSettings = {
|
|
19
|
+
/** Can be used to customize the margins shown around the 3d model. Defaults to 1. */
|
|
20
|
+
radiusFactor?: number;
|
|
21
|
+
adjustWheelPrecision?: boolean;
|
|
22
|
+
adjustPanningSensibility?: boolean;
|
|
23
|
+
adjustPinchPrecision?: boolean;
|
|
24
|
+
/** Desired horizontal camera angle, this won't be overwritten by the autofocus function */
|
|
25
|
+
alpha?: number;
|
|
26
|
+
/** Desired vertical camera angle, this won't be overwritten by the autofocus function */
|
|
27
|
+
beta?: number;
|
|
28
|
+
/** Optional list of geometry to be excluded from consideration */
|
|
29
|
+
exclude?: ExcludedGeometryList;
|
|
30
|
+
durationMs?: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Manager for camera related tasks
|
|
35
|
+
*/
|
|
36
|
+
export class CameraManager {
|
|
37
|
+
public static readonly CAMERA_ANIMATION_NAME = '__cameraAnimation__';
|
|
38
|
+
|
|
39
|
+
/** @internal */
|
|
40
|
+
public static readonly DEFAULT_CAMERA_POSITION: CameraPosition = {
|
|
41
|
+
alpha: (45 * Math.PI) / 180,
|
|
42
|
+
beta: (75 * Math.PI) / 180,
|
|
43
|
+
radius: 1,
|
|
44
|
+
target: new Vector3(0, 0, 0),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
protected static readonly _AUTOFOCUS_CONSTANTS = {
|
|
48
|
+
minZ: 100,
|
|
49
|
+
wheelPrecision: 100,
|
|
50
|
+
panningSensibility: 2500,
|
|
51
|
+
pinchPrecision: 200,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
protected static readonly _DEFAULT_AUTOFOCUS_RADIUS_FACTOR = 1;
|
|
55
|
+
protected static readonly _DEFAULT_CAM_SPEED_MS = 250;
|
|
56
|
+
|
|
57
|
+
/** @internal */
|
|
58
|
+
public constructor(protected viewer: Viewer) {}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Focuses the camera to see every visible mesh in scene and tries to optimize wheel precision and panning
|
|
62
|
+
*/
|
|
63
|
+
public async autofocusActiveCamera(settings?: AutofocusSettings): Promise<void> {
|
|
64
|
+
const activeCamera = this.viewer.scene.activeCamera;
|
|
65
|
+
|
|
66
|
+
if (!(activeCamera instanceof ArcRotateCamera)) {
|
|
67
|
+
throw new Error(`Camera type "${activeCamera?.getClassName()}" is not implemented for "autofocusActiveCamera".`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// get bounding box of all visible meshes, this is the base for the autofocus algorithm
|
|
71
|
+
const boundingInfo = this.viewer.calculateBoundingInfo(settings?.exclude);
|
|
72
|
+
// optionally show bounding sphere for debugging purpose
|
|
73
|
+
this.viewer.eventManager.fireEvent(ViewerEvent.AutofocusStart, boundingInfo.boundingSphere);
|
|
74
|
+
|
|
75
|
+
const radius = boundingInfo.boundingSphere.radius;
|
|
76
|
+
const center = boundingInfo.boundingSphere.center;
|
|
77
|
+
|
|
78
|
+
// set lower radius limit on edge of bounding sphere to make sure that we can't dive into the meshes
|
|
79
|
+
activeCamera.lowerRadiusLimit = radius;
|
|
80
|
+
|
|
81
|
+
// additional settings
|
|
82
|
+
// constants for division are taken directly from Babylon.js repository
|
|
83
|
+
activeCamera.minZ = Math.min(radius / CameraManager._AUTOFOCUS_CONSTANTS.minZ, 1);
|
|
84
|
+
if (settings?.adjustWheelPrecision !== false) {
|
|
85
|
+
activeCamera.wheelPrecision = CameraManager._AUTOFOCUS_CONSTANTS.wheelPrecision / radius;
|
|
86
|
+
}
|
|
87
|
+
if (settings?.adjustPanningSensibility !== false) {
|
|
88
|
+
activeCamera.panningSensibility = CameraManager._AUTOFOCUS_CONSTANTS.panningSensibility / radius;
|
|
89
|
+
}
|
|
90
|
+
if (settings?.adjustPinchPrecision !== false) {
|
|
91
|
+
activeCamera.pinchPrecision = CameraManager._AUTOFOCUS_CONSTANTS.pinchPrecision / radius;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const radiusFactor = settings?.radiusFactor ?? CameraManager._DEFAULT_AUTOFOCUS_RADIUS_FACTOR;
|
|
95
|
+
const alpha = settings?.alpha ?? CameraManager.DEFAULT_CAMERA_POSITION.alpha;
|
|
96
|
+
const beta = settings?.beta ?? CameraManager.DEFAULT_CAMERA_POSITION.beta;
|
|
97
|
+
|
|
98
|
+
// calculation is taken from Babylon.js repository, which on their side took it from this SO post:
|
|
99
|
+
// http://stackoverflow.com/questions/2866350/move-camera-to-fit-3d-scene
|
|
100
|
+
// there's a good sketch which explains the calculation quite a bit
|
|
101
|
+
const aspectRatio = this.viewer.engine.getAspectRatio(activeCamera);
|
|
102
|
+
const frustumSlopeY = Math.tan(activeCamera.fov / 2);
|
|
103
|
+
const frustumSlopeX = frustumSlopeY * aspectRatio;
|
|
104
|
+
|
|
105
|
+
const distanceForHorizontalFrustum = radius * Math.sqrt(1 + 1 / (frustumSlopeX * frustumSlopeX));
|
|
106
|
+
const distanceForVerticalFrustum = radius * Math.sqrt(1 + 1 / (frustumSlopeY * frustumSlopeY));
|
|
107
|
+
const distance = Math.max(distanceForHorizontalFrustum, distanceForVerticalFrustum);
|
|
108
|
+
|
|
109
|
+
const newCameraPosition: CameraPosition = {
|
|
110
|
+
alpha: alpha,
|
|
111
|
+
beta: beta,
|
|
112
|
+
radius: distance * radiusFactor,
|
|
113
|
+
target: center,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
await this.moveActiveCameraTo(newCameraPosition, settings?.durationMs);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Moves the active camera smoothly to certain position
|
|
121
|
+
*
|
|
122
|
+
* @param durationMs defaults to 250ms if unset, use 0 for instant movement
|
|
123
|
+
*/
|
|
124
|
+
public async moveActiveCameraTo(
|
|
125
|
+
position: CameraPosition,
|
|
126
|
+
durationMs = CameraManager._DEFAULT_CAM_SPEED_MS
|
|
127
|
+
): Promise<void> {
|
|
128
|
+
const activeCamera = this.viewer.scene.activeCamera;
|
|
129
|
+
|
|
130
|
+
if (!(activeCamera instanceof ArcRotateCamera)) {
|
|
131
|
+
throw new Error(`Camera type "${activeCamera?.getClassName()}" is not implemented yet for "moveActiveCameraTo".`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (durationMs === 0) {
|
|
135
|
+
// instant movement without animation
|
|
136
|
+
activeCamera.alpha = position.alpha ?? activeCamera.alpha;
|
|
137
|
+
activeCamera.beta = position.beta ?? activeCamera.beta;
|
|
138
|
+
activeCamera.radius = position.radius ?? activeCamera.radius;
|
|
139
|
+
activeCamera.target = position.target ?? activeCamera.target;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const framesPerSec = 100;
|
|
143
|
+
const animationGroup = new AnimationGroup(CameraManager.CAMERA_ANIMATION_NAME, this.viewer.scene);
|
|
144
|
+
|
|
145
|
+
if (position.alpha !== undefined) {
|
|
146
|
+
// transform the target's alpha value into the same turn as the current camera position to avoid movements
|
|
147
|
+
// > 360 degrees
|
|
148
|
+
const alphaGap = activeCamera.alpha - position.alpha;
|
|
149
|
+
const cntTurns = Math.round(alphaGap / (2 * Math.PI));
|
|
150
|
+
const targetAlpha = position.alpha + 2 * Math.PI * cntTurns;
|
|
151
|
+
|
|
152
|
+
CameraManager._addCameraAnimationToGroup(
|
|
153
|
+
'alpha',
|
|
154
|
+
activeCamera.alpha,
|
|
155
|
+
targetAlpha,
|
|
156
|
+
framesPerSec,
|
|
157
|
+
Animation.ANIMATIONTYPE_FLOAT,
|
|
158
|
+
animationGroup,
|
|
159
|
+
activeCamera
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (position.beta !== undefined) {
|
|
163
|
+
CameraManager._addCameraAnimationToGroup(
|
|
164
|
+
'beta',
|
|
165
|
+
activeCamera.beta,
|
|
166
|
+
position.beta,
|
|
167
|
+
framesPerSec,
|
|
168
|
+
Animation.ANIMATIONTYPE_FLOAT,
|
|
169
|
+
animationGroup,
|
|
170
|
+
activeCamera
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
if (position.radius !== undefined) {
|
|
174
|
+
CameraManager._addCameraAnimationToGroup(
|
|
175
|
+
'radius',
|
|
176
|
+
activeCamera.radius,
|
|
177
|
+
position.radius,
|
|
178
|
+
framesPerSec,
|
|
179
|
+
Animation.ANIMATIONTYPE_FLOAT,
|
|
180
|
+
animationGroup,
|
|
181
|
+
activeCamera
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
if (position.target !== undefined) {
|
|
185
|
+
CameraManager._addCameraAnimationToGroup(
|
|
186
|
+
'target',
|
|
187
|
+
activeCamera.target,
|
|
188
|
+
position.target,
|
|
189
|
+
framesPerSec,
|
|
190
|
+
Animation.ANIMATIONTYPE_VECTOR3,
|
|
191
|
+
animationGroup,
|
|
192
|
+
activeCamera
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// animation is created for 1 second (100 frames), adjust speed ratio according to desired movement time
|
|
197
|
+
const speedRatio = 1000 / durationMs;
|
|
198
|
+
|
|
199
|
+
return new Promise<void>(resolve => {
|
|
200
|
+
animationGroup.onAnimationGroupEndObservable.addOnce(() => {
|
|
201
|
+
animationGroup.dispose();
|
|
202
|
+
resolve();
|
|
203
|
+
});
|
|
204
|
+
animationGroup.start(false, speedRatio, 0, 100);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
protected static _addCameraAnimationToGroup(
|
|
209
|
+
targetProperty: string,
|
|
210
|
+
from: number | Vector3,
|
|
211
|
+
to: number | Vector3,
|
|
212
|
+
framesPerSec: number,
|
|
213
|
+
dataType: number,
|
|
214
|
+
animationGroup: AnimationGroup,
|
|
215
|
+
activeCamera: ArcRotateCamera
|
|
216
|
+
): void {
|
|
217
|
+
const alphaAnimation = new Animation(
|
|
218
|
+
targetProperty,
|
|
219
|
+
targetProperty,
|
|
220
|
+
framesPerSec,
|
|
221
|
+
dataType,
|
|
222
|
+
Animation.ANIMATIONLOOPMODE_CONSTANT
|
|
223
|
+
);
|
|
224
|
+
alphaAnimation.setKeys([
|
|
225
|
+
{
|
|
226
|
+
frame: 0,
|
|
227
|
+
value: from,
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
frame: 100,
|
|
231
|
+
value: to,
|
|
232
|
+
},
|
|
233
|
+
]);
|
|
234
|
+
animationGroup.addTargetedAnimation(alphaAnimation, activeCamera);
|
|
235
|
+
}
|
|
236
|
+
}
|