@combeenation/3d-viewer 6.1.0 → 6.2.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.
Files changed (97) hide show
  1. package/README.md +111 -111
  2. package/dist/lib-cjs/api/classes/animationInterface.d.ts +8 -8
  3. package/dist/lib-cjs/api/classes/animationInterface.js +2 -2
  4. package/dist/lib-cjs/api/classes/dottedPath.d.ts +79 -79
  5. package/dist/lib-cjs/api/classes/dottedPath.js +166 -166
  6. package/dist/lib-cjs/api/classes/element.d.ts +149 -149
  7. package/dist/lib-cjs/api/classes/element.js +669 -669
  8. package/dist/lib-cjs/api/classes/event.d.ts +342 -342
  9. package/dist/lib-cjs/api/classes/event.js +365 -365
  10. package/dist/lib-cjs/api/classes/eventBroadcaster.d.ts +26 -26
  11. package/dist/lib-cjs/api/classes/eventBroadcaster.js +49 -49
  12. package/dist/lib-cjs/api/classes/parameter.d.ts +339 -339
  13. package/dist/lib-cjs/api/classes/parameter.js +464 -464
  14. package/dist/lib-cjs/api/classes/parameterObservable.d.ts +36 -36
  15. package/dist/lib-cjs/api/classes/parameterObservable.js +97 -97
  16. package/dist/lib-cjs/api/classes/parameterizable.d.ts +15 -15
  17. package/dist/lib-cjs/api/classes/parameterizable.js +102 -102
  18. package/dist/lib-cjs/api/classes/placementAnimation.d.ts +45 -45
  19. package/dist/lib-cjs/api/classes/placementAnimation.js +176 -176
  20. package/dist/lib-cjs/api/classes/variant.d.ts +238 -234
  21. package/dist/lib-cjs/api/classes/variant.js +841 -828
  22. package/dist/lib-cjs/api/classes/variant.js.map +1 -1
  23. package/dist/lib-cjs/api/classes/variantInstance.d.ts +44 -44
  24. package/dist/lib-cjs/api/classes/variantInstance.js +105 -105
  25. package/dist/lib-cjs/api/classes/variantParameterizable.d.ts +17 -17
  26. package/dist/lib-cjs/api/classes/variantParameterizable.js +88 -88
  27. package/dist/lib-cjs/api/classes/viewer.d.ts +192 -187
  28. package/dist/lib-cjs/api/classes/viewer.js +639 -594
  29. package/dist/lib-cjs/api/classes/viewer.js.map +1 -1
  30. package/dist/lib-cjs/api/classes/viewerLight.d.ts +66 -66
  31. package/dist/lib-cjs/api/classes/viewerLight.js +348 -348
  32. package/dist/lib-cjs/api/internal/lensRendering.d.ts +8 -8
  33. package/dist/lib-cjs/api/internal/lensRendering.js +11 -11
  34. package/dist/lib-cjs/api/internal/sceneSetup.d.ts +13 -13
  35. package/dist/lib-cjs/api/internal/sceneSetup.js +226 -226
  36. package/dist/lib-cjs/api/manager/animationManager.d.ts +30 -30
  37. package/dist/lib-cjs/api/manager/animationManager.js +126 -126
  38. package/dist/lib-cjs/api/manager/gltfExportManager.d.ts +78 -78
  39. package/dist/lib-cjs/api/manager/gltfExportManager.js +241 -241
  40. package/dist/lib-cjs/api/manager/sceneManager.d.ts +33 -33
  41. package/dist/lib-cjs/api/manager/sceneManager.js +130 -130
  42. package/dist/lib-cjs/api/manager/textureLoadManager.d.ts +22 -22
  43. package/dist/lib-cjs/api/manager/textureLoadManager.js +97 -97
  44. package/dist/lib-cjs/api/manager/variantInstanceManager.d.ts +92 -92
  45. package/dist/lib-cjs/api/manager/variantInstanceManager.js +260 -260
  46. package/dist/lib-cjs/api/store/specStorage.d.ts +24 -24
  47. package/dist/lib-cjs/api/store/specStorage.js +50 -50
  48. package/dist/lib-cjs/api/util/babylonHelper.d.ts +187 -187
  49. package/dist/lib-cjs/api/util/babylonHelper.js +596 -596
  50. package/dist/lib-cjs/api/util/globalTypes.d.ts +387 -383
  51. package/dist/lib-cjs/api/util/globalTypes.js +1 -1
  52. package/dist/lib-cjs/api/util/resourceHelper.d.ts +58 -58
  53. package/dist/lib-cjs/api/util/resourceHelper.js +203 -203
  54. package/dist/lib-cjs/api/util/sceneLoaderHelper.d.ts +42 -42
  55. package/dist/lib-cjs/api/util/sceneLoaderHelper.js +139 -139
  56. package/dist/lib-cjs/api/util/stringHelper.d.ts +9 -9
  57. package/dist/lib-cjs/api/util/stringHelper.js +25 -25
  58. package/dist/lib-cjs/api/util/structureHelper.d.ts +9 -9
  59. package/dist/lib-cjs/api/util/structureHelper.js +48 -48
  60. package/dist/lib-cjs/buildinfo.json +3 -3
  61. package/dist/lib-cjs/commonjs.tsconfig.tsbuildinfo +1 -1
  62. package/dist/lib-cjs/index.d.ts +51 -51
  63. package/dist/lib-cjs/index.js +110 -110
  64. package/package.json +81 -81
  65. package/src/api/classes/animationInterface.ts +10 -10
  66. package/src/api/classes/dottedPath.ts +181 -181
  67. package/src/api/classes/element.ts +717 -717
  68. package/src/api/classes/event.ts +385 -385
  69. package/src/api/classes/eventBroadcaster.ts +52 -52
  70. package/src/api/classes/parameter.ts +497 -497
  71. package/src/api/classes/parameterObservable.ts +100 -100
  72. package/src/api/classes/parameterizable.ts +87 -87
  73. package/src/api/classes/placementAnimation.ts +162 -162
  74. package/src/api/classes/variant.ts +910 -896
  75. package/src/api/classes/variantInstance.ts +97 -97
  76. package/src/api/classes/variantParameterizable.ts +85 -85
  77. package/src/api/classes/viewer.ts +720 -672
  78. package/src/api/classes/viewerLight.ts +339 -339
  79. package/src/api/internal/debugViewer.ts +90 -90
  80. package/src/api/internal/lensRendering.ts +9 -9
  81. package/src/api/internal/sceneSetup.ts +205 -205
  82. package/src/api/manager/animationManager.ts +143 -143
  83. package/src/api/manager/gltfExportManager.ts +236 -236
  84. package/src/api/manager/sceneManager.ts +132 -132
  85. package/src/api/manager/textureLoadManager.ts +95 -95
  86. package/src/api/manager/variantInstanceManager.ts +265 -265
  87. package/src/api/store/specStorage.ts +51 -51
  88. package/src/api/util/babylonHelper.ts +663 -663
  89. package/src/api/util/globalTypes.ts +437 -432
  90. package/src/api/util/resourceHelper.ts +191 -191
  91. package/src/api/util/sceneLoaderHelper.ts +137 -137
  92. package/src/api/util/stringHelper.ts +23 -23
  93. package/src/api/util/structureHelper.ts +49 -49
  94. package/src/buildinfo.json +3 -3
  95. package/src/dev.ts +61 -61
  96. package/src/index.ts +96 -96
  97. package/src/types.d.ts +28 -28
@@ -1,663 +1,663 @@
1
- import { DottedPath } from '../classes/dottedPath';
2
- import { defaultEnvHelperColor, defaultSceneClearColor } from '../internal/sceneSetup';
3
- import { addMissingMaterialObserver, missingMaterialMetadataName } from './sceneLoaderHelper';
4
- import { EnvironmentHelper } from '@babylonjs/core/Helpers/environmentHelper';
5
- import { PhotoDome } from '@babylonjs/core/Helpers/photoDome';
6
- import { HighlightLayer } from '@babylonjs/core/Layers/highlightLayer';
7
- import { ShadowGenerator } from '@babylonjs/core/Lights/Shadows/shadowGenerator';
8
- import { Light } from '@babylonjs/core/Lights/light';
9
- import { PBRMaterial } from '@babylonjs/core/Materials/PBR/pbrMaterial';
10
- import { BaseTexture } from '@babylonjs/core/Materials/Textures/baseTexture';
11
- import { CubeTexture } from '@babylonjs/core/Materials/Textures/cubeTexture';
12
- import { Material } from '@babylonjs/core/Materials/material';
13
- import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial';
14
- import { Axis } from '@babylonjs/core/Maths/math.axis';
15
- import { Color3, Color4 } from '@babylonjs/core/Maths/math.color';
16
- import { Quaternion, Vector3 } from '@babylonjs/core/Maths/math.vector';
17
- import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';
18
- import { InstancedMesh } from '@babylonjs/core/Meshes/instancedMesh';
19
- import { Mesh } from '@babylonjs/core/Meshes/mesh';
20
- import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
21
- import { Observable } from '@babylonjs/core/Misc/observable';
22
- import { Tools } from '@babylonjs/core/Misc/tools';
23
- import { Node } from '@babylonjs/core/node';
24
- import { Scene } from '@babylonjs/core/scene';
25
- import { Nullable } from '@babylonjs/core/types';
26
- import { cloneDeep, get, has, merge } from 'lodash-es';
27
-
28
- const backgroundDomeName = 'BackgroundDome_ViewerGenerated';
29
- const envHelperMetadataName = 'viewerEnvHelper';
30
-
31
- /**
32
- * @param node
33
- * @return Node
34
- */
35
- const getRootNode = function (node: Node): Node {
36
- let _node = node;
37
- while (_node.parent) {
38
- _node = _node.parent;
39
- }
40
- return _node;
41
- };
42
-
43
- /**
44
- * @param nodes
45
- * @param predicate
46
- * @return Map<DottedPath, T>
47
- */
48
- const mapToDottedNodes = function <T>(nodes: Node[], predicate?: (node: Node) => boolean): Map<DottedPath, T> {
49
- const map = new Map<DottedPath, T>();
50
- const addNodes = function (_node: Node) {
51
- if (predicate && predicate(_node)) {
52
- map.set(_node.metadata.dottedPath, _node as any);
53
- }
54
- _node.getChildren().forEach(child => {
55
- addNodes(child);
56
- });
57
- };
58
- nodes.forEach(node => {
59
- addNodes(node);
60
- });
61
- return map;
62
- };
63
-
64
- /**
65
- * @param node
66
- * @return DottedPath
67
- */
68
- const getDottedPathForNode = function (node: Node): DottedPath {
69
- const dottedPath = DottedPath.create(node.name);
70
- let _parent = node;
71
- while (_parent.parent) {
72
- _parent = _parent.parent;
73
- dottedPath.unshiftPart(_parent.name);
74
- }
75
- return dottedPath;
76
- };
77
-
78
- /**
79
- * @param node
80
- * @param predicate
81
- * @param deep
82
- * @return TransformNode | null
83
- */
84
- const cloneTransformNode = function (
85
- node: TransformNode,
86
- predicate?: (node: TransformNode) => boolean,
87
- deep: boolean = true
88
- ): TransformNode | null {
89
- if (predicate && !predicate(node)) {
90
- return null;
91
- }
92
- const clone = node.clone(node.name, node.parent, true);
93
- if (clone) {
94
- clone.metadata = cloneDeep(node.metadata);
95
- // if the cloned node is of type InstancedMesh, due to a bug(?),
96
- // the InstancedMesh.isEnabled state may have changed after cloning.
97
- // in that case, set the clone's enabled state to the original's state
98
- if (node.constructor.name === 'InstancedMesh') {
99
- clone.setEnabled(node.isEnabled(false));
100
- }
101
- }
102
- if (deep) {
103
- const children = node.getChildTransformNodes(true);
104
- children.forEach(child => {
105
- const clonedChild = cloneTransformNode(child, predicate, deep);
106
- if (clonedChild) {
107
- clonedChild.parent = clone;
108
- }
109
- });
110
- }
111
- return clone;
112
- };
113
-
114
- /**
115
- * @param node
116
- */
117
- const cloneNodeWithParents = function (node: Node | null): Node | null {
118
- let clone = null;
119
- if (node instanceof TransformNode) {
120
- clone = node.clone(node.name, cloneNodeWithParents(node.parent) as Nullable<Node>, true);
121
- } else if (node instanceof Light) {
122
- clone = node.clone(node.name, cloneNodeWithParents(node.parent) as Nullable<Node>);
123
- } else if (node) {
124
- throw new Error(`Cloning of "${node?.constructor.name}" is not implemented (yet).`);
125
- }
126
- return clone;
127
- };
128
-
129
- /**
130
- * @param node
131
- * @param deep
132
- * @param prefix
133
- * @return TransformNode
134
- */
135
- const cloneTransformNodeMaterial = function (
136
- node: TransformNode,
137
- prefix: DottedPathArgument = '',
138
- deep: boolean = true
139
- ): TransformNode {
140
- if (node instanceof AbstractMesh && node.material) {
141
- const newMatName = DottedPath.create(prefix).addParts([node.material.name, 'clone', node.uniqueId.toString()]);
142
- node.material = node.material.clone(newMatName.path);
143
- }
144
- if (deep) {
145
- const children = node.getChildTransformNodes(true);
146
- children.forEach(child => cloneTransformNodeMaterial(child, prefix, deep));
147
- }
148
- return node;
149
- };
150
-
151
- /**
152
- * @param node
153
- * @param deep
154
- * @param metadata
155
- */
156
- const injectNodeMetadata = function (node: Node, metadata: {}, deep: boolean = true) {
157
- node.metadata = merge({}, node.metadata, metadata);
158
- if (deep && node instanceof TransformNode) {
159
- const children = node.getChildTransformNodes(true);
160
- children.forEach(child => injectNodeMetadata(child, metadata, deep));
161
- }
162
- };
163
-
164
- /**
165
- * @param node
166
- * @param assertCallable
167
- * @param callableParameters
168
- * @param deep
169
- */
170
- const assertTransformNode = function (
171
- node: TransformNode,
172
- assertCallable: CallableFunction,
173
- callableParameters: any[] = [],
174
- deep: boolean = true
175
- ) {
176
- assertCallable(node, ...callableParameters);
177
- if (deep) {
178
- const children = node.getChildTransformNodes(true);
179
- children.forEach(child => assertTransformNode(child, assertCallable, callableParameters, deep));
180
- }
181
- };
182
-
183
- /**
184
- * @param node
185
- * @param deep
186
- */
187
- const activateTransformNode = function (node: TransformNode, deep: boolean = true) {
188
- node.setEnabled(true);
189
- /*
190
- if( node instanceof AbstractMesh ) {
191
- node.visibility = 1;
192
- node.isPickable = true;
193
- }
194
- */
195
- if (deep) {
196
- node.getChildTransformNodes(true).forEach(child => activateTransformNode(child, deep));
197
- }
198
- };
199
-
200
- /**
201
- * @param node
202
- * @param deep
203
- */
204
- const deactivateTransformNode = function (node: TransformNode, deep: boolean = true) {
205
- node.setEnabled(false);
206
- /*
207
- if( node instanceof AbstractMesh ) {
208
- node.visibility = 0;
209
- node.isPickable = false;
210
- }
211
- */
212
- if (deep) {
213
- node.getChildTransformNodes(true).forEach(child => deactivateTransformNode(child, deep));
214
- }
215
- };
216
-
217
- /**
218
- * @param node
219
- */
220
- const enableNodeWithParents = function (node: Node) {
221
- node.setEnabled(true);
222
- if (node.parent) {
223
- enableNodeWithParents(node.parent);
224
- }
225
- };
226
-
227
- /**
228
- * @param node
229
- */
230
- const disableNodeWithParents = function (node: Node) {
231
- node.setEnabled(false);
232
- if (node.parent) {
233
- disableNodeWithParents(node.parent);
234
- }
235
- };
236
-
237
- /**
238
- * Applies a {@link TransformationDefinition} consecutively to ensure dependencies in positioning etc.
239
- * @param node
240
- * @param transformation
241
- */
242
- const transformTransformNode = function (node: TransformNode, transformation: TransformationDefinition) {
243
- // scaling
244
- if (!has(node.metadata, 'scaling.initial')) {
245
- injectNodeMetadata(node, { 'scaling.initial': node.scaling }, false);
246
- }
247
- const initialScaling = get(node.metadata, 'scaling.initial') as Vector3;
248
- node.scaling = initialScaling.multiply(transformation.scaling);
249
- // position
250
- if (!has(node.metadata, 'position.initial')) {
251
- injectNodeMetadata(node, { 'position.initial': node.absolutePosition.clone() }, false);
252
- }
253
- const initialPosition = get(node.metadata, 'position.initial') as Vector3;
254
- node.setAbsolutePosition(initialPosition.add(transformation.position).multiply(transformation.scaling));
255
- // rotation
256
- if (!has(node.metadata, 'rotation.initial')) {
257
- let rotationQuaternion = node.rotationQuaternion;
258
- if (!rotationQuaternion) {
259
- rotationQuaternion = Quaternion.RotationYawPitchRoll(node.rotation.x, node.rotation.y, node.rotation.z);
260
- }
261
- injectNodeMetadata(node, { 'rotation.initial': rotationQuaternion.asArray() }, false);
262
- }
263
- const initialRotationQuaternion = Quaternion.FromArray(get(node.metadata, 'rotation.initial') as []);
264
- node.rotationQuaternion = initialRotationQuaternion;
265
- node.rotateAround(Vector3.Zero(), Axis.X, transformation.rotation.x);
266
- node.rotateAround(Vector3.Zero(), Axis.Y, transformation.rotation.y);
267
- node.rotateAround(Vector3.Zero(), Axis.Z, transformation.rotation.z);
268
- node.computeWorldMatrix(true);
269
- };
270
-
271
- /**
272
- * Apply changes of environment (background texture, etc.) consecutively in order to avoid dependency related issues.
273
- * @param scene
274
- * @param envDef
275
- */
276
- const changeEnvironment = function (scene: Scene, envDef: EnvironmentDefinition) {
277
- // issue warning if both background texture and usedefault are set
278
- if (envDef.environmentUseDefault && envDef.environmentBackground) {
279
- console.warn(
280
- `!!! Warning !!! Spec parameter 'environment.usedefault' is being overruled by 'environment.background'.`
281
- );
282
- }
283
-
284
- const useDefaultEnv = envDef.environmentUseDefault && !envDef.environmentBackground;
285
-
286
- // 1) set ENVIRONMENT_COLOR
287
- const clearColorBefore = scene.clearColor.toString();
288
- scene.clearColor = envDef.environmentColor
289
- ? Color4.FromColor3(envDef.environmentColor)
290
- : useDefaultEnv
291
- ? defaultEnvHelperColor
292
- : defaultSceneClearColor;
293
- const clearColorChanged = clearColorBefore !== scene.clearColor.toString();
294
-
295
- // 2) dispose existing & set new ENVIRONMENT (==texture)
296
- const curEnvTexture = scene.environmentTexture as Nullable<CubeTexture>;
297
- const envTextureChanged = envDef.environment !== curEnvTexture?.url;
298
-
299
- if (curEnvTexture && (!envDef.environment || envTextureChanged)) {
300
- curEnvTexture.dispose();
301
- }
302
-
303
- const rotation = envDef.environmentRotation !== undefined ? Tools.ToRadians(envDef.environmentRotation) : 0;
304
- if (envDef.environment && envTextureChanged) {
305
- const envTexture = CubeTexture.CreateFromPrefilteredData(envDef.environment, scene);
306
- envTexture.rotationY = rotation;
307
- scene.environmentTexture = envTexture;
308
- } else if (curEnvTexture && curEnvTexture.rotationY !== rotation) {
309
- curEnvTexture.rotationY = rotation;
310
- }
311
-
312
- // 3) dispose existing & set new ENVIRONMENT_BACKGROUND
313
- const curDome = scene.getNodeByName(backgroundDomeName) as undefined | PhotoDome;
314
- const backgroundUrlChanged = envDef.environmentBackground !== curDome?.texture.url;
315
-
316
- if (curDome && (!envDef.environmentBackground || backgroundUrlChanged)) {
317
- curDome.dispose();
318
- }
319
-
320
- const rotationPhotoDome = -1 * rotation;
321
- if (envDef.environmentBackground && backgroundUrlChanged) {
322
- const textureUrl = envDef.environmentBackground;
323
- const dome = new PhotoDome(backgroundDomeName, textureUrl, { resolution: 32, size: 450 }, scene);
324
- // Rotate submesh to get them in line with environment texture
325
- dome.getChildMeshes(true)[0].rotation.y = Tools.ToRadians(90);
326
- dome.rotation.y = rotationPhotoDome;
327
- } else if (curDome && curDome.rotation.y !== rotationPhotoDome) {
328
- curDome.rotation.y = rotationPhotoDome;
329
- }
330
-
331
- // 4) dispose existing & set new ENVIRONMENT_USEDEFAULT (only if no background is set)
332
- const curEnvHelper = scene.metadata?.[envHelperMetadataName] as undefined | EnvironmentHelper;
333
-
334
- if (curEnvHelper && !useDefaultEnv) {
335
- curEnvHelper.dispose();
336
- delete scene.metadata[envHelperMetadataName];
337
- }
338
-
339
- const envHelperMainColor = Color3.FromArray(scene.clearColor.asArray());
340
- if (useDefaultEnv && !curEnvHelper) {
341
- const envHelper = scene.createDefaultEnvironment({ sizeAuto: true });
342
- envHelper?.setMainColor(envHelperMainColor);
343
- if (envHelper) {
344
- scene.metadata = merge({}, scene.metadata, { [envHelperMetadataName]: envHelper });
345
- }
346
- } else if (curEnvHelper && useDefaultEnv && clearColorChanged) {
347
- curEnvHelper.setMainColor(envHelperMainColor);
348
- }
349
-
350
- // 5) set ENVIRONMENT_INTENSITY
351
- if (envDef.environmentIntensity !== undefined) {
352
- scene.environmentIntensity = envDef.environmentIntensity;
353
- }
354
- };
355
-
356
- /**
357
- * @param node
358
- * @param materialName
359
- * @param deep
360
- */
361
- const setMaterial = function (variant: Variant, node: TransformNode, materialName: string, deep: boolean = true) {
362
- if (node instanceof AbstractMesh) {
363
- const materialExists = variant.viewer.scene.getMaterialById(materialName);
364
- const hasMissingMaterial = has(node.metadata, missingMaterialMetadataName);
365
- const deferMaterialCreation = !materialExists && !node.isEnabled();
366
-
367
- if (deferMaterialCreation) {
368
- injectNodeMetadata(node, { [missingMaterialMetadataName]: materialName }, false);
369
-
370
- // If it already had the missing material flag before, there already exists an observer...
371
- if (!hasMissingMaterial) {
372
- addMissingMaterialObserver(node);
373
- }
374
- } else {
375
- node.material = variant.getOrCreateMaterial(materialName);
376
- if (hasMissingMaterial) {
377
- delete node.metadata[missingMaterialMetadataName];
378
- }
379
- }
380
- }
381
- if (deep) {
382
- node.getChildTransformNodes(true).forEach(child => setMaterial(variant, child, materialName, deep));
383
- }
384
- };
385
-
386
- /**
387
- * !!! Warning !!!
388
- * This function is not public API. Whilst it can help solving certain problems, it only works reliably in well defined
389
- * situations and can cause unwanted side effects under some conditions. Use carefully at your own risk!
390
- *
391
- * See https://combeenation.myjetbrains.com/youtrack/issue/CB-5906 for further details regarding this warning.
392
- *
393
- * Set material of an instanced meshes source mesh.
394
- * Changes the material of all instanced meshes which have the same source mesh.
395
- *
396
- * @param node
397
- * @param material
398
- * @param deep
399
- *
400
- * @ignore
401
- */
402
- const setSourceNodeMaterial = function (node: TransformNode, material: Material, deep: boolean = true) {
403
- const warn = ` You're using "setSourceNodeMaterial" which is not public API.
404
- Whilst it can help solving certain problems, it only works reliably in well defined situations and can cause unwanted side effects under some conditions.
405
- Use carefully at your own risk!`;
406
- console.warn(`!!! Warning !!!\n${warn}`);
407
-
408
- if (node instanceof InstancedMesh) {
409
- node.sourceMesh.material = material;
410
- }
411
- if (deep) {
412
- node.getChildTransformNodes(true).forEach(child => setSourceNodeMaterial(child, material, deep));
413
- }
414
- };
415
-
416
- /**
417
- * @param node
418
- * @param color
419
- * @param deep
420
- */
421
- const setMaterialColor = function (node: TransformNode, color: Color3, deep: boolean = true) {
422
- if (node instanceof AbstractMesh && node.material) {
423
- const materialCls = node.material.getClassName();
424
- switch (materialCls) {
425
- case 'PBRMaterial':
426
- (node.material as PBRMaterial).albedoColor = color.toLinearSpace();
427
- break;
428
- case 'StandardMaterial':
429
- (node.material as StandardMaterial).diffuseColor = color;
430
- break;
431
- default:
432
- throw new Error(`Setting color for material of instance "${materialCls}" not implemented (yet).`);
433
- }
434
- }
435
- if (deep) {
436
- node.getChildTransformNodes(true).forEach(child => setMaterialColor(child, color, deep));
437
- }
438
- };
439
-
440
- /**
441
- * @param node
442
- * @param texture
443
- * @param deep
444
- */
445
- const setMaterialTexture = function (node: TransformNode, texture: Texture, deep: boolean = true) {
446
- if (node instanceof AbstractMesh && node.material) {
447
- const materialCls = node.material.getClassName();
448
- switch (materialCls) {
449
- case 'PBRMaterial':
450
- (node.material as PBRMaterial).albedoTexture = texture;
451
- break;
452
- case 'StandardMaterial':
453
- (node.material as StandardMaterial).diffuseTexture = texture;
454
- break;
455
- default:
456
- throw new Error(`Setting texture for material of instance "${materialCls}" not implemented (yet).`);
457
- }
458
- }
459
- if (deep) {
460
- node.getChildTransformNodes(true).forEach(child => setMaterialTexture(child, texture, deep));
461
- }
462
- };
463
-
464
- /**
465
- * @param node
466
- * @param metallness
467
- * @param deep
468
- */
469
- const setMaterialMetallness = function (node: TransformNode, metallness: number, deep: boolean = true) {
470
- if (node instanceof AbstractMesh && node.material) {
471
- const materialCls = node.material.getClassName();
472
- switch (materialCls) {
473
- case 'PBRMaterial':
474
- (node.material as PBRMaterial).metallic = metallness;
475
- break;
476
- default:
477
- throw new Error(`Setting metallness for material of instance "${materialCls}" not implemented (yet).`);
478
- }
479
- }
480
- if (deep) {
481
- node.getChildTransformNodes(true).forEach(child => setMaterialMetallness(child, metallness, deep));
482
- }
483
- };
484
-
485
- /**
486
- * @param node
487
- * @param roughness
488
- * @param deep
489
- */
490
- const setMaterialRoughness = function (node: TransformNode, roughness: number, deep: boolean = true) {
491
- if (node instanceof AbstractMesh && node.material) {
492
- const materialCls = node.material.getClassName();
493
- switch (materialCls) {
494
- case 'PBRMaterial':
495
- (node.material as PBRMaterial).roughness = roughness;
496
- break;
497
- case 'StandardMaterial':
498
- (node.material as StandardMaterial).roughness = roughness;
499
- break;
500
- default:
501
- throw new Error(`Setting roughness for material of instance "${materialCls}" not implemented (yet).`);
502
- }
503
- }
504
- if (deep) {
505
- node.getChildTransformNodes(true).forEach(child => setMaterialRoughness(child, roughness, deep));
506
- }
507
- };
508
-
509
- /**
510
- * @param node
511
- * @param layer
512
- * @param color
513
- * @param deep
514
- */
515
- const addToHighlightLayer = function (layer: HighlightLayer, color: Color3, node: TransformNode, deep: boolean = true) {
516
- if (node instanceof AbstractMesh) {
517
- layer.addMesh(node as Mesh, color);
518
- }
519
- if (deep) {
520
- node.getChildTransformNodes(true).forEach(child => addToHighlightLayer(layer, color, child, deep));
521
- }
522
- };
523
-
524
- /**
525
- * @param node
526
- * @param layer
527
- * @param deep
528
- */
529
- const removeFromHighlightLayer = function (layer: HighlightLayer, node: TransformNode, deep: boolean = true) {
530
- if (node instanceof AbstractMesh) {
531
- layer.removeMesh(node as Mesh);
532
- }
533
- if (deep) {
534
- node.getChildTransformNodes(true).forEach(child => removeFromHighlightLayer(layer, child, deep));
535
- }
536
- };
537
-
538
- /**
539
- * @param node
540
- * @param receiveShadows
541
- * @param deep
542
- */
543
- const setReceiveShadows = function (node: TransformNode, receiveShadows: boolean, deep: boolean = true) {
544
- if (node instanceof AbstractMesh) {
545
- node.receiveShadows = receiveShadows;
546
- }
547
- if (deep) {
548
- node.getChildTransformNodes(true).forEach(child => setReceiveShadows(child, receiveShadows, deep));
549
- }
550
- };
551
-
552
- /**
553
- * @param node
554
- * @param generator
555
- * @param deep
556
- */
557
- const addToShadowGenerator = function (generator: ShadowGenerator, node: TransformNode, deep: boolean = true) {
558
- if (node instanceof AbstractMesh) {
559
- // We have to remove the node because there's no duplicate check in babylon
560
- generator.removeShadowCaster(node, false);
561
- generator.addShadowCaster(node, false);
562
- }
563
- if (deep) {
564
- node.getChildTransformNodes(true).forEach(child => addToShadowGenerator(generator, child, deep));
565
- }
566
- };
567
-
568
- /**
569
- * @param node
570
- * @param generator
571
- * @param deep
572
- */
573
- const removeFromShadowGenerator = function (generator: ShadowGenerator, node: TransformNode, deep: boolean = true) {
574
- if (node instanceof AbstractMesh) {
575
- generator.removeShadowCaster(node, false);
576
- }
577
- if (deep) {
578
- node.getChildTransformNodes(true).forEach(child => removeFromShadowGenerator(generator, child, deep));
579
- }
580
- };
581
-
582
- /**
583
- * https://forum.babylonjs.com/t/get-mesh-bounding-box-position-and-size-in-2d-screen-coordinates/1058/3
584
- * @param mesh
585
- * @param scene
586
- * @param canvas
587
- */
588
- const getClientRectFromMesh = function (mesh: AbstractMesh, scene: Scene, canvas: HTMLCanvasElement): ClientRect {
589
- // get bounding box of the mesh
590
- const meshVectors = mesh.getBoundingInfo().boundingBox.vectors;
591
- // get the matrix and viewport needed to project the vectors onto the screen
592
- const worldMatrix = mesh.getWorldMatrix();
593
- const transformMatrix = scene.getTransformMatrix();
594
- const viewport = scene.activeCamera!.viewport;
595
- // loop though all the vectors and project them against the current camera viewport to get a set of coordinates
596
- const coordinates = meshVectors.map(vector => {
597
- const projection = Vector3.Project(vector, worldMatrix, transformMatrix, viewport);
598
- projection.x = projection.x * canvas.clientWidth;
599
- projection.y = projection.y * canvas.clientHeight;
600
- return projection;
601
- });
602
- // get the min and max for all the coordinates so we can calculate the largest possible screen size
603
- const maxX = Math.max(...coordinates.map(o => o.x));
604
- const minX = Math.min(...coordinates.map(o => o.x));
605
- const maxY = Math.max(...coordinates.map(o => o.y));
606
- const minY = Math.min(...coordinates.map(o => o.y));
607
- // return a ClientRect from this
608
- return {
609
- width: maxX - minX,
610
- height: maxY - minY,
611
- left: minX,
612
- top: minY,
613
- right: maxX,
614
- bottom: maxY,
615
- } as ClientRect;
616
- };
617
-
618
- type BaseTextureWithOnLoadObservable = BaseTexture & {
619
- onLoadObservable: Observable<BaseTexture>;
620
- };
621
-
622
- /**
623
- * This type guard checks whether the given `BaseTextures` is any of its subtypes which comes with an
624
- * `onLoadObservable`.
625
- *
626
- * !!! Timing of when this function is called is important !!!
627
- * See the following for more details: https://forum.babylonjs.com/t/basetexture-whenallready-returns-too-early/34501/6
628
- */
629
- const isTextureWithOnLoadObservable = function (texture: BaseTexture): texture is BaseTextureWithOnLoadObservable {
630
- return !!(texture as BaseTextureWithOnLoadObservable).onLoadObservable;
631
- };
632
-
633
- export {
634
- getRootNode,
635
- isTextureWithOnLoadObservable,
636
- mapToDottedNodes,
637
- getDottedPathForNode,
638
- cloneTransformNode,
639
- cloneNodeWithParents,
640
- cloneTransformNodeMaterial,
641
- injectNodeMetadata,
642
- assertTransformNode,
643
- activateTransformNode,
644
- deactivateTransformNode,
645
- enableNodeWithParents,
646
- disableNodeWithParents,
647
- transformTransformNode,
648
- setMaterial,
649
- setSourceNodeMaterial,
650
- setMaterialColor,
651
- setMaterialTexture,
652
- setMaterialMetallness,
653
- setMaterialRoughness,
654
- addToHighlightLayer,
655
- removeFromHighlightLayer,
656
- setReceiveShadows,
657
- addToShadowGenerator,
658
- removeFromShadowGenerator,
659
- getClientRectFromMesh,
660
- changeEnvironment,
661
- backgroundDomeName,
662
- envHelperMetadataName,
663
- };
1
+ import { DottedPath } from '../classes/dottedPath';
2
+ import { defaultEnvHelperColor, defaultSceneClearColor } from '../internal/sceneSetup';
3
+ import { addMissingMaterialObserver, missingMaterialMetadataName } from './sceneLoaderHelper';
4
+ import { EnvironmentHelper } from '@babylonjs/core/Helpers/environmentHelper';
5
+ import { PhotoDome } from '@babylonjs/core/Helpers/photoDome';
6
+ import { HighlightLayer } from '@babylonjs/core/Layers/highlightLayer';
7
+ import { ShadowGenerator } from '@babylonjs/core/Lights/Shadows/shadowGenerator';
8
+ import { Light } from '@babylonjs/core/Lights/light';
9
+ import { PBRMaterial } from '@babylonjs/core/Materials/PBR/pbrMaterial';
10
+ import { BaseTexture } from '@babylonjs/core/Materials/Textures/baseTexture';
11
+ import { CubeTexture } from '@babylonjs/core/Materials/Textures/cubeTexture';
12
+ import { Material } from '@babylonjs/core/Materials/material';
13
+ import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial';
14
+ import { Axis } from '@babylonjs/core/Maths/math.axis';
15
+ import { Color3, Color4 } from '@babylonjs/core/Maths/math.color';
16
+ import { Quaternion, Vector3 } from '@babylonjs/core/Maths/math.vector';
17
+ import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';
18
+ import { InstancedMesh } from '@babylonjs/core/Meshes/instancedMesh';
19
+ import { Mesh } from '@babylonjs/core/Meshes/mesh';
20
+ import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
21
+ import { Observable } from '@babylonjs/core/Misc/observable';
22
+ import { Tools } from '@babylonjs/core/Misc/tools';
23
+ import { Node } from '@babylonjs/core/node';
24
+ import { Scene } from '@babylonjs/core/scene';
25
+ import { Nullable } from '@babylonjs/core/types';
26
+ import { cloneDeep, get, has, merge } from 'lodash-es';
27
+
28
+ const backgroundDomeName = 'BackgroundDome_ViewerGenerated';
29
+ const envHelperMetadataName = 'viewerEnvHelper';
30
+
31
+ /**
32
+ * @param node
33
+ * @return Node
34
+ */
35
+ const getRootNode = function (node: Node): Node {
36
+ let _node = node;
37
+ while (_node.parent) {
38
+ _node = _node.parent;
39
+ }
40
+ return _node;
41
+ };
42
+
43
+ /**
44
+ * @param nodes
45
+ * @param predicate
46
+ * @return Map<DottedPath, T>
47
+ */
48
+ const mapToDottedNodes = function <T>(nodes: Node[], predicate?: (node: Node) => boolean): Map<DottedPath, T> {
49
+ const map = new Map<DottedPath, T>();
50
+ const addNodes = function (_node: Node) {
51
+ if (predicate && predicate(_node)) {
52
+ map.set(_node.metadata.dottedPath, _node as any);
53
+ }
54
+ _node.getChildren().forEach(child => {
55
+ addNodes(child);
56
+ });
57
+ };
58
+ nodes.forEach(node => {
59
+ addNodes(node);
60
+ });
61
+ return map;
62
+ };
63
+
64
+ /**
65
+ * @param node
66
+ * @return DottedPath
67
+ */
68
+ const getDottedPathForNode = function (node: Node): DottedPath {
69
+ const dottedPath = DottedPath.create(node.name);
70
+ let _parent = node;
71
+ while (_parent.parent) {
72
+ _parent = _parent.parent;
73
+ dottedPath.unshiftPart(_parent.name);
74
+ }
75
+ return dottedPath;
76
+ };
77
+
78
+ /**
79
+ * @param node
80
+ * @param predicate
81
+ * @param deep
82
+ * @return TransformNode | null
83
+ */
84
+ const cloneTransformNode = function (
85
+ node: TransformNode,
86
+ predicate?: (node: TransformNode) => boolean,
87
+ deep: boolean = true
88
+ ): TransformNode | null {
89
+ if (predicate && !predicate(node)) {
90
+ return null;
91
+ }
92
+ const clone = node.clone(node.name, node.parent, true);
93
+ if (clone) {
94
+ clone.metadata = cloneDeep(node.metadata);
95
+ // if the cloned node is of type InstancedMesh, due to a bug(?),
96
+ // the InstancedMesh.isEnabled state may have changed after cloning.
97
+ // in that case, set the clone's enabled state to the original's state
98
+ if (node.constructor.name === 'InstancedMesh') {
99
+ clone.setEnabled(node.isEnabled(false));
100
+ }
101
+ }
102
+ if (deep) {
103
+ const children = node.getChildTransformNodes(true);
104
+ children.forEach(child => {
105
+ const clonedChild = cloneTransformNode(child, predicate, deep);
106
+ if (clonedChild) {
107
+ clonedChild.parent = clone;
108
+ }
109
+ });
110
+ }
111
+ return clone;
112
+ };
113
+
114
+ /**
115
+ * @param node
116
+ */
117
+ const cloneNodeWithParents = function (node: Node | null): Node | null {
118
+ let clone = null;
119
+ if (node instanceof TransformNode) {
120
+ clone = node.clone(node.name, cloneNodeWithParents(node.parent) as Nullable<Node>, true);
121
+ } else if (node instanceof Light) {
122
+ clone = node.clone(node.name, cloneNodeWithParents(node.parent) as Nullable<Node>);
123
+ } else if (node) {
124
+ throw new Error(`Cloning of "${node?.constructor.name}" is not implemented (yet).`);
125
+ }
126
+ return clone;
127
+ };
128
+
129
+ /**
130
+ * @param node
131
+ * @param deep
132
+ * @param prefix
133
+ * @return TransformNode
134
+ */
135
+ const cloneTransformNodeMaterial = function (
136
+ node: TransformNode,
137
+ prefix: DottedPathArgument = '',
138
+ deep: boolean = true
139
+ ): TransformNode {
140
+ if (node instanceof AbstractMesh && node.material) {
141
+ const newMatName = DottedPath.create(prefix).addParts([node.material.name, 'clone', node.uniqueId.toString()]);
142
+ node.material = node.material.clone(newMatName.path);
143
+ }
144
+ if (deep) {
145
+ const children = node.getChildTransformNodes(true);
146
+ children.forEach(child => cloneTransformNodeMaterial(child, prefix, deep));
147
+ }
148
+ return node;
149
+ };
150
+
151
+ /**
152
+ * @param node
153
+ * @param deep
154
+ * @param metadata
155
+ */
156
+ const injectNodeMetadata = function (node: Node, metadata: {}, deep: boolean = true) {
157
+ node.metadata = merge({}, node.metadata, metadata);
158
+ if (deep && node instanceof TransformNode) {
159
+ const children = node.getChildTransformNodes(true);
160
+ children.forEach(child => injectNodeMetadata(child, metadata, deep));
161
+ }
162
+ };
163
+
164
+ /**
165
+ * @param node
166
+ * @param assertCallable
167
+ * @param callableParameters
168
+ * @param deep
169
+ */
170
+ const assertTransformNode = function (
171
+ node: TransformNode,
172
+ assertCallable: CallableFunction,
173
+ callableParameters: any[] = [],
174
+ deep: boolean = true
175
+ ) {
176
+ assertCallable(node, ...callableParameters);
177
+ if (deep) {
178
+ const children = node.getChildTransformNodes(true);
179
+ children.forEach(child => assertTransformNode(child, assertCallable, callableParameters, deep));
180
+ }
181
+ };
182
+
183
+ /**
184
+ * @param node
185
+ * @param deep
186
+ */
187
+ const activateTransformNode = function (node: TransformNode, deep: boolean = true) {
188
+ node.setEnabled(true);
189
+ /*
190
+ if( node instanceof AbstractMesh ) {
191
+ node.visibility = 1;
192
+ node.isPickable = true;
193
+ }
194
+ */
195
+ if (deep) {
196
+ node.getChildTransformNodes(true).forEach(child => activateTransformNode(child, deep));
197
+ }
198
+ };
199
+
200
+ /**
201
+ * @param node
202
+ * @param deep
203
+ */
204
+ const deactivateTransformNode = function (node: TransformNode, deep: boolean = true) {
205
+ node.setEnabled(false);
206
+ /*
207
+ if( node instanceof AbstractMesh ) {
208
+ node.visibility = 0;
209
+ node.isPickable = false;
210
+ }
211
+ */
212
+ if (deep) {
213
+ node.getChildTransformNodes(true).forEach(child => deactivateTransformNode(child, deep));
214
+ }
215
+ };
216
+
217
+ /**
218
+ * @param node
219
+ */
220
+ const enableNodeWithParents = function (node: Node) {
221
+ node.setEnabled(true);
222
+ if (node.parent) {
223
+ enableNodeWithParents(node.parent);
224
+ }
225
+ };
226
+
227
+ /**
228
+ * @param node
229
+ */
230
+ const disableNodeWithParents = function (node: Node) {
231
+ node.setEnabled(false);
232
+ if (node.parent) {
233
+ disableNodeWithParents(node.parent);
234
+ }
235
+ };
236
+
237
+ /**
238
+ * Applies a {@link TransformationDefinition} consecutively to ensure dependencies in positioning etc.
239
+ * @param node
240
+ * @param transformation
241
+ */
242
+ const transformTransformNode = function (node: TransformNode, transformation: TransformationDefinition) {
243
+ // scaling
244
+ if (!has(node.metadata, 'scaling.initial')) {
245
+ injectNodeMetadata(node, { 'scaling.initial': node.scaling }, false);
246
+ }
247
+ const initialScaling = get(node.metadata, 'scaling.initial') as Vector3;
248
+ node.scaling = initialScaling.multiply(transformation.scaling);
249
+ // position
250
+ if (!has(node.metadata, 'position.initial')) {
251
+ injectNodeMetadata(node, { 'position.initial': node.absolutePosition.clone() }, false);
252
+ }
253
+ const initialPosition = get(node.metadata, 'position.initial') as Vector3;
254
+ node.setAbsolutePosition(initialPosition.add(transformation.position).multiply(transformation.scaling));
255
+ // rotation
256
+ if (!has(node.metadata, 'rotation.initial')) {
257
+ let rotationQuaternion = node.rotationQuaternion;
258
+ if (!rotationQuaternion) {
259
+ rotationQuaternion = Quaternion.RotationYawPitchRoll(node.rotation.x, node.rotation.y, node.rotation.z);
260
+ }
261
+ injectNodeMetadata(node, { 'rotation.initial': rotationQuaternion.asArray() }, false);
262
+ }
263
+ const initialRotationQuaternion = Quaternion.FromArray(get(node.metadata, 'rotation.initial') as []);
264
+ node.rotationQuaternion = initialRotationQuaternion;
265
+ node.rotateAround(Vector3.Zero(), Axis.X, transformation.rotation.x);
266
+ node.rotateAround(Vector3.Zero(), Axis.Y, transformation.rotation.y);
267
+ node.rotateAround(Vector3.Zero(), Axis.Z, transformation.rotation.z);
268
+ node.computeWorldMatrix(true);
269
+ };
270
+
271
+ /**
272
+ * Apply changes of environment (background texture, etc.) consecutively in order to avoid dependency related issues.
273
+ * @param scene
274
+ * @param envDef
275
+ */
276
+ const changeEnvironment = function (scene: Scene, envDef: EnvironmentDefinition) {
277
+ // issue warning if both background texture and usedefault are set
278
+ if (envDef.environmentUseDefault && envDef.environmentBackground) {
279
+ console.warn(
280
+ `!!! Warning !!! Spec parameter 'environment.usedefault' is being overruled by 'environment.background'.`
281
+ );
282
+ }
283
+
284
+ const useDefaultEnv = envDef.environmentUseDefault && !envDef.environmentBackground;
285
+
286
+ // 1) set ENVIRONMENT_COLOR
287
+ const clearColorBefore = scene.clearColor.toString();
288
+ scene.clearColor = envDef.environmentColor
289
+ ? Color4.FromColor3(envDef.environmentColor)
290
+ : useDefaultEnv
291
+ ? defaultEnvHelperColor
292
+ : defaultSceneClearColor;
293
+ const clearColorChanged = clearColorBefore !== scene.clearColor.toString();
294
+
295
+ // 2) dispose existing & set new ENVIRONMENT (==texture)
296
+ const curEnvTexture = scene.environmentTexture as Nullable<CubeTexture>;
297
+ const envTextureChanged = envDef.environment !== curEnvTexture?.url;
298
+
299
+ if (curEnvTexture && (!envDef.environment || envTextureChanged)) {
300
+ curEnvTexture.dispose();
301
+ }
302
+
303
+ const rotation = envDef.environmentRotation !== undefined ? Tools.ToRadians(envDef.environmentRotation) : 0;
304
+ if (envDef.environment && envTextureChanged) {
305
+ const envTexture = CubeTexture.CreateFromPrefilteredData(envDef.environment, scene);
306
+ envTexture.rotationY = rotation;
307
+ scene.environmentTexture = envTexture;
308
+ } else if (curEnvTexture && curEnvTexture.rotationY !== rotation) {
309
+ curEnvTexture.rotationY = rotation;
310
+ }
311
+
312
+ // 3) dispose existing & set new ENVIRONMENT_BACKGROUND
313
+ const curDome = scene.getNodeByName(backgroundDomeName) as undefined | PhotoDome;
314
+ const backgroundUrlChanged = envDef.environmentBackground !== curDome?.texture.url;
315
+
316
+ if (curDome && (!envDef.environmentBackground || backgroundUrlChanged)) {
317
+ curDome.dispose();
318
+ }
319
+
320
+ const rotationPhotoDome = -1 * rotation;
321
+ if (envDef.environmentBackground && backgroundUrlChanged) {
322
+ const textureUrl = envDef.environmentBackground;
323
+ const dome = new PhotoDome(backgroundDomeName, textureUrl, { resolution: 32, size: 450 }, scene);
324
+ // Rotate submesh to get them in line with environment texture
325
+ dome.getChildMeshes(true)[0].rotation.y = Tools.ToRadians(90);
326
+ dome.rotation.y = rotationPhotoDome;
327
+ } else if (curDome && curDome.rotation.y !== rotationPhotoDome) {
328
+ curDome.rotation.y = rotationPhotoDome;
329
+ }
330
+
331
+ // 4) dispose existing & set new ENVIRONMENT_USEDEFAULT (only if no background is set)
332
+ const curEnvHelper = scene.metadata?.[envHelperMetadataName] as undefined | EnvironmentHelper;
333
+
334
+ if (curEnvHelper && !useDefaultEnv) {
335
+ curEnvHelper.dispose();
336
+ delete scene.metadata[envHelperMetadataName];
337
+ }
338
+
339
+ const envHelperMainColor = Color3.FromArray(scene.clearColor.asArray());
340
+ if (useDefaultEnv && !curEnvHelper) {
341
+ const envHelper = scene.createDefaultEnvironment({ sizeAuto: true });
342
+ envHelper?.setMainColor(envHelperMainColor);
343
+ if (envHelper) {
344
+ scene.metadata = merge({}, scene.metadata, { [envHelperMetadataName]: envHelper });
345
+ }
346
+ } else if (curEnvHelper && useDefaultEnv && clearColorChanged) {
347
+ curEnvHelper.setMainColor(envHelperMainColor);
348
+ }
349
+
350
+ // 5) set ENVIRONMENT_INTENSITY
351
+ if (envDef.environmentIntensity !== undefined) {
352
+ scene.environmentIntensity = envDef.environmentIntensity;
353
+ }
354
+ };
355
+
356
+ /**
357
+ * @param node
358
+ * @param materialName
359
+ * @param deep
360
+ */
361
+ const setMaterial = function (variant: Variant, node: TransformNode, materialName: string, deep: boolean = true) {
362
+ if (node instanceof AbstractMesh) {
363
+ const materialExists = variant.viewer.scene.getMaterialById(materialName);
364
+ const hasMissingMaterial = has(node.metadata, missingMaterialMetadataName);
365
+ const deferMaterialCreation = !materialExists && !node.isEnabled();
366
+
367
+ if (deferMaterialCreation) {
368
+ injectNodeMetadata(node, { [missingMaterialMetadataName]: materialName }, false);
369
+
370
+ // If it already had the missing material flag before, there already exists an observer...
371
+ if (!hasMissingMaterial) {
372
+ addMissingMaterialObserver(node);
373
+ }
374
+ } else {
375
+ node.material = variant.getOrCreateMaterial(materialName);
376
+ if (hasMissingMaterial) {
377
+ delete node.metadata[missingMaterialMetadataName];
378
+ }
379
+ }
380
+ }
381
+ if (deep) {
382
+ node.getChildTransformNodes(true).forEach(child => setMaterial(variant, child, materialName, deep));
383
+ }
384
+ };
385
+
386
+ /**
387
+ * !!! Warning !!!
388
+ * This function is not public API. Whilst it can help solving certain problems, it only works reliably in well defined
389
+ * situations and can cause unwanted side effects under some conditions. Use carefully at your own risk!
390
+ *
391
+ * See https://combeenation.myjetbrains.com/youtrack/issue/CB-5906 for further details regarding this warning.
392
+ *
393
+ * Set material of an instanced meshes source mesh.
394
+ * Changes the material of all instanced meshes which have the same source mesh.
395
+ *
396
+ * @param node
397
+ * @param material
398
+ * @param deep
399
+ *
400
+ * @ignore
401
+ */
402
+ const setSourceNodeMaterial = function (node: TransformNode, material: Material, deep: boolean = true) {
403
+ const warn = ` You're using "setSourceNodeMaterial" which is not public API.
404
+ Whilst it can help solving certain problems, it only works reliably in well defined situations and can cause unwanted side effects under some conditions.
405
+ Use carefully at your own risk!`;
406
+ console.warn(`!!! Warning !!!\n${warn}`);
407
+
408
+ if (node instanceof InstancedMesh) {
409
+ node.sourceMesh.material = material;
410
+ }
411
+ if (deep) {
412
+ node.getChildTransformNodes(true).forEach(child => setSourceNodeMaterial(child, material, deep));
413
+ }
414
+ };
415
+
416
+ /**
417
+ * @param node
418
+ * @param color
419
+ * @param deep
420
+ */
421
+ const setMaterialColor = function (node: TransformNode, color: Color3, deep: boolean = true) {
422
+ if (node instanceof AbstractMesh && node.material) {
423
+ const materialCls = node.material.getClassName();
424
+ switch (materialCls) {
425
+ case 'PBRMaterial':
426
+ (node.material as PBRMaterial).albedoColor = color.toLinearSpace();
427
+ break;
428
+ case 'StandardMaterial':
429
+ (node.material as StandardMaterial).diffuseColor = color;
430
+ break;
431
+ default:
432
+ throw new Error(`Setting color for material of instance "${materialCls}" not implemented (yet).`);
433
+ }
434
+ }
435
+ if (deep) {
436
+ node.getChildTransformNodes(true).forEach(child => setMaterialColor(child, color, deep));
437
+ }
438
+ };
439
+
440
+ /**
441
+ * @param node
442
+ * @param texture
443
+ * @param deep
444
+ */
445
+ const setMaterialTexture = function (node: TransformNode, texture: Texture, deep: boolean = true) {
446
+ if (node instanceof AbstractMesh && node.material) {
447
+ const materialCls = node.material.getClassName();
448
+ switch (materialCls) {
449
+ case 'PBRMaterial':
450
+ (node.material as PBRMaterial).albedoTexture = texture;
451
+ break;
452
+ case 'StandardMaterial':
453
+ (node.material as StandardMaterial).diffuseTexture = texture;
454
+ break;
455
+ default:
456
+ throw new Error(`Setting texture for material of instance "${materialCls}" not implemented (yet).`);
457
+ }
458
+ }
459
+ if (deep) {
460
+ node.getChildTransformNodes(true).forEach(child => setMaterialTexture(child, texture, deep));
461
+ }
462
+ };
463
+
464
+ /**
465
+ * @param node
466
+ * @param metallness
467
+ * @param deep
468
+ */
469
+ const setMaterialMetallness = function (node: TransformNode, metallness: number, deep: boolean = true) {
470
+ if (node instanceof AbstractMesh && node.material) {
471
+ const materialCls = node.material.getClassName();
472
+ switch (materialCls) {
473
+ case 'PBRMaterial':
474
+ (node.material as PBRMaterial).metallic = metallness;
475
+ break;
476
+ default:
477
+ throw new Error(`Setting metallness for material of instance "${materialCls}" not implemented (yet).`);
478
+ }
479
+ }
480
+ if (deep) {
481
+ node.getChildTransformNodes(true).forEach(child => setMaterialMetallness(child, metallness, deep));
482
+ }
483
+ };
484
+
485
+ /**
486
+ * @param node
487
+ * @param roughness
488
+ * @param deep
489
+ */
490
+ const setMaterialRoughness = function (node: TransformNode, roughness: number, deep: boolean = true) {
491
+ if (node instanceof AbstractMesh && node.material) {
492
+ const materialCls = node.material.getClassName();
493
+ switch (materialCls) {
494
+ case 'PBRMaterial':
495
+ (node.material as PBRMaterial).roughness = roughness;
496
+ break;
497
+ case 'StandardMaterial':
498
+ (node.material as StandardMaterial).roughness = roughness;
499
+ break;
500
+ default:
501
+ throw new Error(`Setting roughness for material of instance "${materialCls}" not implemented (yet).`);
502
+ }
503
+ }
504
+ if (deep) {
505
+ node.getChildTransformNodes(true).forEach(child => setMaterialRoughness(child, roughness, deep));
506
+ }
507
+ };
508
+
509
+ /**
510
+ * @param node
511
+ * @param layer
512
+ * @param color
513
+ * @param deep
514
+ */
515
+ const addToHighlightLayer = function (layer: HighlightLayer, color: Color3, node: TransformNode, deep: boolean = true) {
516
+ if (node instanceof AbstractMesh) {
517
+ layer.addMesh(node as Mesh, color);
518
+ }
519
+ if (deep) {
520
+ node.getChildTransformNodes(true).forEach(child => addToHighlightLayer(layer, color, child, deep));
521
+ }
522
+ };
523
+
524
+ /**
525
+ * @param node
526
+ * @param layer
527
+ * @param deep
528
+ */
529
+ const removeFromHighlightLayer = function (layer: HighlightLayer, node: TransformNode, deep: boolean = true) {
530
+ if (node instanceof AbstractMesh) {
531
+ layer.removeMesh(node as Mesh);
532
+ }
533
+ if (deep) {
534
+ node.getChildTransformNodes(true).forEach(child => removeFromHighlightLayer(layer, child, deep));
535
+ }
536
+ };
537
+
538
+ /**
539
+ * @param node
540
+ * @param receiveShadows
541
+ * @param deep
542
+ */
543
+ const setReceiveShadows = function (node: TransformNode, receiveShadows: boolean, deep: boolean = true) {
544
+ if (node instanceof AbstractMesh) {
545
+ node.receiveShadows = receiveShadows;
546
+ }
547
+ if (deep) {
548
+ node.getChildTransformNodes(true).forEach(child => setReceiveShadows(child, receiveShadows, deep));
549
+ }
550
+ };
551
+
552
+ /**
553
+ * @param node
554
+ * @param generator
555
+ * @param deep
556
+ */
557
+ const addToShadowGenerator = function (generator: ShadowGenerator, node: TransformNode, deep: boolean = true) {
558
+ if (node instanceof AbstractMesh) {
559
+ // We have to remove the node because there's no duplicate check in babylon
560
+ generator.removeShadowCaster(node, false);
561
+ generator.addShadowCaster(node, false);
562
+ }
563
+ if (deep) {
564
+ node.getChildTransformNodes(true).forEach(child => addToShadowGenerator(generator, child, deep));
565
+ }
566
+ };
567
+
568
+ /**
569
+ * @param node
570
+ * @param generator
571
+ * @param deep
572
+ */
573
+ const removeFromShadowGenerator = function (generator: ShadowGenerator, node: TransformNode, deep: boolean = true) {
574
+ if (node instanceof AbstractMesh) {
575
+ generator.removeShadowCaster(node, false);
576
+ }
577
+ if (deep) {
578
+ node.getChildTransformNodes(true).forEach(child => removeFromShadowGenerator(generator, child, deep));
579
+ }
580
+ };
581
+
582
+ /**
583
+ * https://forum.babylonjs.com/t/get-mesh-bounding-box-position-and-size-in-2d-screen-coordinates/1058/3
584
+ * @param mesh
585
+ * @param scene
586
+ * @param canvas
587
+ */
588
+ const getClientRectFromMesh = function (mesh: AbstractMesh, scene: Scene, canvas: HTMLCanvasElement): ClientRect {
589
+ // get bounding box of the mesh
590
+ const meshVectors = mesh.getBoundingInfo().boundingBox.vectors;
591
+ // get the matrix and viewport needed to project the vectors onto the screen
592
+ const worldMatrix = mesh.getWorldMatrix();
593
+ const transformMatrix = scene.getTransformMatrix();
594
+ const viewport = scene.activeCamera!.viewport;
595
+ // loop though all the vectors and project them against the current camera viewport to get a set of coordinates
596
+ const coordinates = meshVectors.map(vector => {
597
+ const projection = Vector3.Project(vector, worldMatrix, transformMatrix, viewport);
598
+ projection.x = projection.x * canvas.clientWidth;
599
+ projection.y = projection.y * canvas.clientHeight;
600
+ return projection;
601
+ });
602
+ // get the min and max for all the coordinates so we can calculate the largest possible screen size
603
+ const maxX = Math.max(...coordinates.map(o => o.x));
604
+ const minX = Math.min(...coordinates.map(o => o.x));
605
+ const maxY = Math.max(...coordinates.map(o => o.y));
606
+ const minY = Math.min(...coordinates.map(o => o.y));
607
+ // return a ClientRect from this
608
+ return {
609
+ width: maxX - minX,
610
+ height: maxY - minY,
611
+ left: minX,
612
+ top: minY,
613
+ right: maxX,
614
+ bottom: maxY,
615
+ } as ClientRect;
616
+ };
617
+
618
+ type BaseTextureWithOnLoadObservable = BaseTexture & {
619
+ onLoadObservable: Observable<BaseTexture>;
620
+ };
621
+
622
+ /**
623
+ * This type guard checks whether the given `BaseTextures` is any of its subtypes which comes with an
624
+ * `onLoadObservable`.
625
+ *
626
+ * !!! Timing of when this function is called is important !!!
627
+ * See the following for more details: https://forum.babylonjs.com/t/basetexture-whenallready-returns-too-early/34501/6
628
+ */
629
+ const isTextureWithOnLoadObservable = function (texture: BaseTexture): texture is BaseTextureWithOnLoadObservable {
630
+ return !!(texture as BaseTextureWithOnLoadObservable).onLoadObservable;
631
+ };
632
+
633
+ export {
634
+ getRootNode,
635
+ isTextureWithOnLoadObservable,
636
+ mapToDottedNodes,
637
+ getDottedPathForNode,
638
+ cloneTransformNode,
639
+ cloneNodeWithParents,
640
+ cloneTransformNodeMaterial,
641
+ injectNodeMetadata,
642
+ assertTransformNode,
643
+ activateTransformNode,
644
+ deactivateTransformNode,
645
+ enableNodeWithParents,
646
+ disableNodeWithParents,
647
+ transformTransformNode,
648
+ setMaterial,
649
+ setSourceNodeMaterial,
650
+ setMaterialColor,
651
+ setMaterialTexture,
652
+ setMaterialMetallness,
653
+ setMaterialRoughness,
654
+ addToHighlightLayer,
655
+ removeFromHighlightLayer,
656
+ setReceiveShadows,
657
+ addToShadowGenerator,
658
+ removeFromShadowGenerator,
659
+ getClientRectFromMesh,
660
+ changeEnvironment,
661
+ backgroundDomeName,
662
+ envHelperMetadataName,
663
+ };