@combeenation/3d-viewer 7.1.1 → 7.1.2

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 (115) 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 +153 -153
  7. package/dist/lib-cjs/api/classes/element.js +672 -670
  8. package/dist/lib-cjs/api/classes/element.js.map +1 -1
  9. package/dist/lib-cjs/api/classes/event.d.ts +401 -396
  10. package/dist/lib-cjs/api/classes/event.js +424 -419
  11. package/dist/lib-cjs/api/classes/event.js.map +1 -1
  12. package/dist/lib-cjs/api/classes/eventBroadcaster.d.ts +26 -26
  13. package/dist/lib-cjs/api/classes/eventBroadcaster.js +49 -49
  14. package/dist/lib-cjs/api/classes/fuzzyMap.d.ts +7 -7
  15. package/dist/lib-cjs/api/classes/fuzzyMap.js +21 -21
  16. package/dist/lib-cjs/api/classes/parameter.d.ts +351 -351
  17. package/dist/lib-cjs/api/classes/parameter.js +524 -517
  18. package/dist/lib-cjs/api/classes/parameter.js.map +1 -1
  19. package/dist/lib-cjs/api/classes/parameterObservable.d.ts +36 -36
  20. package/dist/lib-cjs/api/classes/parameterObservable.js +72 -72
  21. package/dist/lib-cjs/api/classes/parameterizable.d.ts +15 -15
  22. package/dist/lib-cjs/api/classes/parameterizable.js +102 -102
  23. package/dist/lib-cjs/api/classes/placementAnimation.d.ts +45 -45
  24. package/dist/lib-cjs/api/classes/placementAnimation.js +176 -176
  25. package/dist/lib-cjs/api/classes/variant.d.ts +253 -253
  26. package/dist/lib-cjs/api/classes/variant.js +858 -843
  27. package/dist/lib-cjs/api/classes/variant.js.map +1 -1
  28. package/dist/lib-cjs/api/classes/variantInstance.d.ts +53 -53
  29. package/dist/lib-cjs/api/classes/variantInstance.js +125 -125
  30. package/dist/lib-cjs/api/classes/variantParameterizable.d.ts +17 -17
  31. package/dist/lib-cjs/api/classes/variantParameterizable.js +86 -88
  32. package/dist/lib-cjs/api/classes/variantParameterizable.js.map +1 -1
  33. package/dist/lib-cjs/api/classes/viewer.d.ts +204 -200
  34. package/dist/lib-cjs/api/classes/viewer.js +682 -670
  35. package/dist/lib-cjs/api/classes/viewer.js.map +1 -1
  36. package/dist/lib-cjs/api/classes/viewerError.d.ts +43 -0
  37. package/dist/lib-cjs/api/classes/viewerError.js +56 -0
  38. package/dist/lib-cjs/api/classes/viewerError.js.map +1 -0
  39. package/dist/lib-cjs/api/classes/viewerLight.d.ts +66 -66
  40. package/dist/lib-cjs/api/classes/viewerLight.js +348 -348
  41. package/dist/lib-cjs/api/internal/lensRendering.d.ts +8 -8
  42. package/dist/lib-cjs/api/internal/lensRendering.js +11 -11
  43. package/dist/lib-cjs/api/internal/sceneSetup.d.ts +13 -13
  44. package/dist/lib-cjs/api/internal/sceneSetup.js +226 -226
  45. package/dist/lib-cjs/api/manager/animationManager.d.ts +30 -30
  46. package/dist/lib-cjs/api/manager/animationManager.js +126 -126
  47. package/dist/lib-cjs/api/manager/gltfExportManager.d.ts +79 -79
  48. package/dist/lib-cjs/api/manager/gltfExportManager.js +242 -242
  49. package/dist/lib-cjs/api/manager/sceneManager.d.ts +33 -33
  50. package/dist/lib-cjs/api/manager/sceneManager.js +128 -130
  51. package/dist/lib-cjs/api/manager/sceneManager.js.map +1 -1
  52. package/dist/lib-cjs/api/manager/tagManager.d.ts +116 -109
  53. package/dist/lib-cjs/api/manager/tagManager.js +444 -425
  54. package/dist/lib-cjs/api/manager/tagManager.js.map +1 -1
  55. package/dist/lib-cjs/api/manager/textureLoadManager.d.ts +22 -22
  56. package/dist/lib-cjs/api/manager/textureLoadManager.js +97 -97
  57. package/dist/lib-cjs/api/manager/variantInstanceManager.d.ts +106 -106
  58. package/dist/lib-cjs/api/manager/variantInstanceManager.js +292 -292
  59. package/dist/lib-cjs/api/store/specStorage.d.ts +32 -32
  60. package/dist/lib-cjs/api/store/specStorage.js +65 -65
  61. package/dist/lib-cjs/api/util/babylonHelper.d.ts +235 -235
  62. package/dist/lib-cjs/api/util/babylonHelper.js +753 -753
  63. package/dist/lib-cjs/api/util/globalTypes.d.ts +441 -437
  64. package/dist/lib-cjs/api/util/globalTypes.js +1 -1
  65. package/dist/lib-cjs/api/util/resourceHelper.d.ts +58 -58
  66. package/dist/lib-cjs/api/util/resourceHelper.js +203 -203
  67. package/dist/lib-cjs/api/util/sceneLoaderHelper.d.ts +44 -44
  68. package/dist/lib-cjs/api/util/sceneLoaderHelper.js +175 -175
  69. package/dist/lib-cjs/api/util/stringHelper.d.ts +13 -13
  70. package/dist/lib-cjs/api/util/stringHelper.js +32 -32
  71. package/dist/lib-cjs/api/util/structureHelper.d.ts +9 -9
  72. package/dist/lib-cjs/api/util/structureHelper.js +57 -48
  73. package/dist/lib-cjs/api/util/structureHelper.js.map +1 -1
  74. package/dist/lib-cjs/buildinfo.json +3 -3
  75. package/dist/lib-cjs/commonjs.tsconfig.tsbuildinfo +1 -1
  76. package/dist/lib-cjs/index.d.ts +54 -53
  77. package/dist/lib-cjs/index.js +117 -114
  78. package/dist/lib-cjs/index.js.map +1 -1
  79. package/package.json +81 -81
  80. package/src/api/classes/animationInterface.ts +10 -10
  81. package/src/api/classes/dottedPath.ts +181 -181
  82. package/src/api/classes/element.ts +733 -731
  83. package/src/api/classes/event.ts +457 -452
  84. package/src/api/classes/eventBroadcaster.ts +52 -52
  85. package/src/api/classes/fuzzyMap.ts +21 -21
  86. package/src/api/classes/parameter.ts +561 -554
  87. package/src/api/classes/parameterObservable.ts +73 -73
  88. package/src/api/classes/parameterizable.ts +87 -87
  89. package/src/api/classes/placementAnimation.ts +162 -162
  90. package/src/api/classes/variant.ts +949 -933
  91. package/src/api/classes/variantInstance.ts +123 -123
  92. package/src/api/classes/variantParameterizable.ts +83 -85
  93. package/src/api/classes/viewer.ts +760 -744
  94. package/src/api/classes/viewerError.ts +63 -0
  95. package/src/api/classes/viewerLight.ts +339 -339
  96. package/src/api/internal/debugViewer.ts +90 -90
  97. package/src/api/internal/lensRendering.ts +9 -9
  98. package/src/api/internal/sceneSetup.ts +205 -205
  99. package/src/api/manager/animationManager.ts +143 -143
  100. package/src/api/manager/gltfExportManager.ts +237 -237
  101. package/src/api/manager/sceneManager.ts +134 -136
  102. package/src/api/manager/tagManager.ts +477 -457
  103. package/src/api/manager/textureLoadManager.ts +95 -95
  104. package/src/api/manager/variantInstanceManager.ts +309 -309
  105. package/src/api/store/specStorage.ts +68 -68
  106. package/src/api/util/babylonHelper.ts +823 -823
  107. package/src/api/util/globalTypes.ts +508 -504
  108. package/src/api/util/resourceHelper.ts +191 -191
  109. package/src/api/util/sceneLoaderHelper.ts +170 -170
  110. package/src/api/util/stringHelper.ts +30 -30
  111. package/src/api/util/structureHelper.ts +58 -49
  112. package/src/buildinfo.json +3 -3
  113. package/src/dev.ts +62 -62
  114. package/src/index.ts +103 -100
  115. package/src/types.d.ts +38 -38
@@ -1,933 +1,949 @@
1
- import {
2
- deactivateTransformNode,
3
- getDottedPathForNode,
4
- injectNodeMetadata,
5
- intersectingNodeNames,
6
- reportDuplicateNodeNames,
7
- } from '../util/babylonHelper';
8
- import { loadJson, mergeMaps } from '../util/resourceHelper';
9
- import { DottedPath } from './dottedPath';
10
- import { Element } from './element';
11
- import { Event } from './event';
12
- import { Parameter } from './parameter';
13
- import { ParameterObservable } from './parameterObservable';
14
- import { Parameterizable } from './parameterizable';
15
- import { VariantParameterizable } from './variantParameterizable';
16
- import { Viewer } from './viewer';
17
- import { ViewerLight } from './viewerLight';
18
- import { Light } from '@babylonjs/core/Lights/light';
19
- import '@babylonjs/core/Loading/Plugins/babylonFileLoader';
20
- import { SceneLoader } from '@babylonjs/core/Loading/sceneLoader';
21
- import { Material } from '@babylonjs/core/Materials/material';
22
- import { Mesh } from '@babylonjs/core/Meshes/mesh';
23
- import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
24
- import { AssetContainer } from '@babylonjs/core/assetContainer';
25
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression';
26
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_clearcoat';
27
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_ior';
28
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness';
29
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_sheen';
30
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_specular';
31
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_translucency';
32
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_transmission';
33
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_unlit';
34
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_variants';
35
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_texture_basisu';
36
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_texture_transform';
37
- import '@babylonjs/loaders/glTF/2.0/glTFLoader';
38
- import { cloneDeep, concat, get, isEmpty, isEqual, isString, merge, set } from 'lodash-es';
39
-
40
- /**
41
- * A concrete "Variant". Most of these are handled by either the {@link Viewer} or {@link VariantInstance}.
42
- */
43
- export class Variant extends Parameterizable {
44
- public assetContainer: AssetContainer;
45
-
46
- public readonly elements: Element[] = [];
47
-
48
- public readonly viewerLights: ViewerLight[] = [];
49
-
50
- public structureJson: StructureJson;
51
-
52
- protected _dottedNodes: Map<DottedPath, TransformNode> | undefined;
53
-
54
- protected readonly _children: Map<string, Variant> = new Map();
55
-
56
- protected readonly _parameterObservers: Map<string, ParameterObserver[]> = new Map();
57
-
58
- /**
59
- * @internal
60
- */
61
- private parametersInitialized: boolean = false;
62
-
63
- /**
64
- * Constructor.
65
- */
66
- protected constructor(
67
- protected readonly _variantInstance: VariantInstance | null,
68
- public readonly name: string,
69
- protected readonly _structureJson: StructureJson,
70
- public readonly viewer: Viewer,
71
- public readonly parent?: Variant
72
- ) {
73
- super(cloneDeep(_structureJson.parameterDeclaration), cloneDeep(_structureJson.parameters));
74
- for (const parameter in _structureJson.parameterDeclaration) {
75
- if (!(parameter in (_structureJson.parameters || []))) {
76
- throw new Error(`No default value for parameter "${parameter}" defined.`);
77
- }
78
- }
79
- this.assetContainer = new AssetContainer(viewer.scene);
80
- this.structureJson = cloneDeep(_structureJson);
81
- }
82
-
83
- /**
84
- * Creates the root {@link Variant}.
85
- */
86
- public static async createRoot(structureJson: StructureJson, viewer: Viewer): Promise<Variant> {
87
- return Variant.create(null, '_', structureJson, viewer);
88
- }
89
-
90
- /**
91
- * Creates a {@link Variant} based on given parameters.
92
- *
93
- * @throws Error if "gltf" property is provided without a filename
94
- */
95
- public static async create(
96
- variantInstance: VariantInstance | null,
97
- name: string,
98
- structureJson: StructureJson,
99
- viewer: Viewer,
100
- parent?: Variant
101
- ): Promise<Variant> {
102
- const variant = new Variant(variantInstance, name, structureJson, viewer, parent);
103
- await variant.loadAssets();
104
- return variant;
105
- }
106
-
107
- /**
108
- * The ancestor {@link Variant}s ordered from top to bottom in the built tree.
109
- */
110
- get ancestors(): Variant[] {
111
- const ancestors = [];
112
- let variant: Variant = this;
113
- while (variant.parent) {
114
- ancestors.unshift(variant.parent);
115
- variant = variant.parent;
116
- }
117
- return ancestors;
118
- }
119
-
120
- /**
121
- * The root {@link Variant}.
122
- */
123
- get root(): Variant {
124
- return this.ancestors[0] ?? this;
125
- }
126
-
127
- /**
128
- * The {@link DottedPath} in the built tree of {@link Variant}s.
129
- * E.g. "_.top-1.sub-2.sub-sub-3"
130
- */
131
- get dottedPath(): DottedPath {
132
- const parentIds = this.ancestors.map(ancestor => {
133
- return ancestor.name;
134
- });
135
- return DottedPath.createFromParts(parentIds).addPart(this.name);
136
- }
137
-
138
- /**
139
- * Gets the {@link VariantInstance} this variant was created for. There are variants without an instance (the "ghost"
140
- * ones used for bootstrapping instances). The usage of {@link Variant}s without an instance is an absolute edge-case
141
- * when deeply using the viewer api and working abroad best practices.
142
- */
143
- get variantInstance(): VariantInstance | null {
144
- return this._variantInstance;
145
- }
146
-
147
- /**
148
- * The id representing a {@link DottedPath}.
149
- */
150
- get id(): string {
151
- const dottedPath = DottedPath.create(this.dottedPath);
152
- dottedPath.shiftPart(); // remove root
153
- return dottedPath.path || '_';
154
- }
155
-
156
- /**
157
- * The defined glTF Asset.
158
- */
159
- get glTF(): Asset | undefined {
160
- if (this.structureJson.glTF) {
161
- if (isString(this.structureJson.glTF)) {
162
- return {
163
- rootUrl: this.structureJson.glTF,
164
- fileName: undefined,
165
- };
166
- }
167
- if (isEmpty(this.structureJson.glTF.rootUrl)) {
168
- throw new Error(`No "rootUrl" defined in "glTF" definition for variant "${this.id}".`);
169
- }
170
- return this.structureJson.glTF;
171
- }
172
- }
173
-
174
- /**
175
- * The defined glTF URI.
176
- */
177
- get glTFUri(): string | undefined {
178
- if (this.glTF) {
179
- return [this.glTF.rootUrl, this.glTF.fileName].join('');
180
- }
181
- }
182
-
183
- /**
184
- * The inherited defined glTF URI.
185
- */
186
- get inheritedGlTFUri(): string | undefined {
187
- if (!this.glTFUri && this.parent) {
188
- return this.parent.inheritedGlTFUri;
189
- }
190
- return this.glTFUri;
191
- }
192
-
193
- /**
194
- * The TransformNodes of the {@link Variant}.
195
- */
196
- get nodes(): TransformNode[] {
197
- const rootNodes = this.assetContainer
198
- .getNodes()
199
- .filter(n => n instanceof TransformNode && !n.parent) as TransformNode[];
200
- return rootNodes;
201
- }
202
-
203
- /**
204
- * The {@link ViewerLight}s of the {@link Variant}.
205
- */
206
- get lights(): Light[] {
207
- return this.assetContainer.lights;
208
- }
209
-
210
- /**
211
- * All TransformNodes of the {@link Variant} mapped flat with a {@link DottedPath}.
212
- */
213
- get dottedNodes(): Map<DottedPath, TransformNode> {
214
- if (!this._dottedNodes) {
215
- const nodes = this.assetContainer.getNodes().filter(n => n instanceof TransformNode);
216
- const dottedNodes = new Map();
217
- nodes.forEach(node => {
218
- dottedNodes.set(node.metadata.dottedPath, node);
219
- });
220
- this._dottedNodes = dottedNodes;
221
- }
222
- return this._dottedNodes;
223
- }
224
-
225
- /**
226
- * The Materials of the {@link Variant}.
227
- */
228
- get materials(): Material[] {
229
- return this.assetContainer.materials;
230
- }
231
-
232
- /**
233
- * The cloned TransformNodes of all {@link Element}s created for this {@link Variant}.
234
- */
235
- get elementNodes(): TransformNode[] {
236
- return this.elements.reduce((a, c) => a.concat(c.nodes), [] as TransformNode[]);
237
- }
238
-
239
- /**
240
- * The cloned TransformNodes of all {@link Element}s created for this {@link Variant} flattened.
241
- */
242
- get elementNodesFlat(): TransformNode[] {
243
- return this.elements.reduce((a, c) => [...a, ...c.nodesFlat], [] as TransformNode[]);
244
- }
245
-
246
- /**
247
- * All {@link Element}s from this {@link Variant}'s parents.
248
- */
249
- get inheritedElements(): Element[] {
250
- let elements: Element[] = [];
251
- this.ancestors.forEach(ancestor => {
252
- elements = concat(elements, ancestor.elements);
253
- });
254
- return concat(elements, this.elements);
255
- }
256
-
257
- /**
258
- * All {@link ViewerLight}s inherited from this {@link Variant}'s parents.
259
- */
260
- get inheritedViewerLights(): ViewerLight[] {
261
- let viewerLights: ViewerLight[] = [];
262
- this.ancestors.forEach(ancestor => {
263
- viewerLights = concat(viewerLights, ancestor.viewerLights);
264
- });
265
- return concat(viewerLights, this.viewerLights);
266
- }
267
-
268
- /**
269
- * All TransformNodes inherited from this {@link Variant}'s parents.
270
- */
271
- get inheritedNodes(): TransformNode[] {
272
- let nodes: TransformNode[] = [];
273
- this.ancestors.forEach(ancestor => {
274
- nodes = concat(nodes, ancestor.nodes);
275
- });
276
- return concat(nodes, this.nodes);
277
- }
278
-
279
- /**
280
- * All TransformNodes inherited from this {@link Variant}'s parents mapped flat with a {@link DottedPath}.
281
- */
282
- get inheritedDottedNodes(): Map<DottedPath, TransformNode> {
283
- let dottedNodes = this.dottedNodes;
284
- this.ancestors.forEach(ancestor => {
285
- dottedNodes = mergeMaps(dottedNodes, ancestor.dottedNodes);
286
- });
287
- return dottedNodes;
288
- }
289
-
290
- /**
291
- * All Lights inherited from this {@link Variant}'s parents.
292
- */
293
- get inheritedLights(): Light[] {
294
- let lights: Light[] = [];
295
- this.ancestors.forEach(ancestor => {
296
- lights = concat(lights, ancestor.lights);
297
- });
298
- return concat(lights, this.lights);
299
- }
300
-
301
- /**
302
- * The {@link ParameterDeclarations} inherited from this {@link Variant}'s parents.
303
- */
304
- get inheritedParameterDeclaration(): ParameterDeclarations {
305
- const declaration = {};
306
- this.ancestors.forEach(ancestor => {
307
- merge(declaration, ancestor.parameterDeclaration);
308
- });
309
- return merge(declaration, this.parameterDeclaration);
310
- }
311
-
312
- /**
313
- * The {@link ParameterBag} inherited from this {@link Variant}'s parents.
314
- */
315
- get inheritedParameters(): ParameterBag {
316
- const parameters = {};
317
- this.ancestors.forEach(ancestor => {
318
- merge(parameters, ancestor.parameters);
319
- });
320
- return merge(parameters, this.parameters);
321
- }
322
-
323
- /**
324
- * All Materials from this {@link Variant}'s parents.
325
- */
326
- get inheritedMaterials(): Material[] {
327
- let materials: Material[] = [];
328
- this.ancestors.forEach(ancestor => {
329
- materials = concat(materials, ancestor.materials);
330
- });
331
- return concat(materials, this.materials);
332
- }
333
-
334
- /**
335
- * Gets the direct children of the current {@link Variant}.
336
- */
337
- public async getChildren(): Promise<Variant[]> {
338
- const children: Variant[] = [];
339
- for (const name in this.structureJson.variants) {
340
- children.push(await this.getDescendant(name));
341
- }
342
- return children;
343
- }
344
-
345
- /**
346
- * Gets a descendant {@link Variant} of the current {@link Variant} relative to its {@link DottedPath}.
347
- * If you have the dotted path `_.product_x.variant_blue.with_yellow_highlight` in a tree and you operate on the
348
- * `product_x`, you can call `this.getDescendant('variant_blue.with_yellow_highlight')` to get the lowermost
349
- * {@link Variant}.
350
- */
351
- public async getDescendant(dottedPath: DottedPathArgument): Promise<Variant> {
352
- const _dottedPath = DottedPath.create(dottedPath);
353
- const [name, ...descendantParts] = _dottedPath.parts;
354
- let variant;
355
- if (this._children.has(name)) {
356
- variant = this._children.get(name);
357
- } else {
358
- if (!this.structureJson.variants) {
359
- throw new Error(`Missing key "variants" in JSON structure for variant "${this.id}".`);
360
- }
361
- if (!this.structureJson.variants[name]) {
362
- throw new Error(`Variant "${_dottedPath.path}" not defined in JSON structure for variant "${this.id}".`);
363
- }
364
- if (this.structureJson.variants[name].file) {
365
- const file = this.structureJson.variants[name].file as string;
366
- variant = await Variant.create(
367
- this.variantInstance,
368
- name,
369
- await loadJson<StructureJson>(file),
370
- this.viewer,
371
- this
372
- );
373
- } else {
374
- variant = await Variant.create(
375
- this.variantInstance,
376
- name,
377
- this.structureJson.variants[name],
378
- this.viewer,
379
- this
380
- );
381
- }
382
- this._children.set(name, variant);
383
- }
384
- if (!variant) {
385
- throw new Error(`Variant "${_dottedPath.path}" was not created.`);
386
- }
387
- if (descendantParts.length > 0) {
388
- return await variant.getDescendant(DottedPath.createFromParts(descendantParts));
389
- }
390
- return variant;
391
- }
392
-
393
- /**
394
- * Gets the desired {@link Element} of the current {@link Variant} relative to its {@link DottedPath}.
395
- * Uses the mechanism of {@link getDescendant} to resolve the appropriate variant in tree.
396
- */
397
- public async getElement(dottedPath: DottedPathArgument): Promise<Element> {
398
- const _dottedPath = DottedPath.create(dottedPath);
399
- const elementName = _dottedPath.popPart();
400
- let variant: Variant = this;
401
- if (_dottedPath.parts.length > 0) {
402
- variant = await this.getDescendant(_dottedPath);
403
- }
404
- if (variant.inheritedElements.length === 0) {
405
- throw new Error(
406
- `No elements for variant "${variant.id}" found. ` +
407
- `Either none are defined or they are not initialized (are you operating on the appropriate living?).`
408
- );
409
- }
410
- let element;
411
- variant.inheritedElements.forEach(_element => {
412
- if (_element.name === elementName) {
413
- element = _element;
414
- }
415
- });
416
- if (!element) {
417
- throw new Error(`Element with name "${elementName}" does not exist for variant "${variant.id}".`);
418
- }
419
- return element;
420
- }
421
-
422
- /**
423
- * Gets the desired {@link ViewerLight} of the current {@link Variant} relative to its {@link DottedPath}.
424
- * Uses the mechanism of {@link getDescendant} to resolve the appropriate variant in tree.
425
- */
426
- public async getViewerLight(dottedPath: DottedPathArgument): Promise<ViewerLight> {
427
- const _dottedPath = DottedPath.create(dottedPath);
428
- const viewerLightName = _dottedPath.popPart();
429
- let variant: Variant = this;
430
- if (_dottedPath.parts.length > 0) {
431
- variant = await this.getDescendant(_dottedPath);
432
- }
433
- if (variant.inheritedViewerLights.length === 0) {
434
- throw new Error(
435
- `No viewerLights for variant "${variant.id}" found. ` +
436
- `Either none are defined or they are not initialized (are you operating on the appropriate living?).`
437
- );
438
- }
439
- let viewerLight;
440
- variant.inheritedViewerLights.forEach(_viewerLight => {
441
- if (_viewerLight.name === viewerLightName) {
442
- viewerLight = _viewerLight;
443
- }
444
- });
445
- if (!viewerLight) {
446
- throw new Error(`ViewerLight with name "${viewerLightName}" does not exist for variant "${variant.id}".`);
447
- }
448
- return viewerLight;
449
- }
450
-
451
- /**
452
- * A proxy for directly getting a Node from an {@link Element} by its {@link DottedPath}s.
453
- */
454
- public async getNode(
455
- elementDottedPath: DottedPathArgument,
456
- nodeDottedPath: DottedPathArgument
457
- ): Promise<TransformNode> {
458
- const element = await this.getElement(elementDottedPath);
459
- return element.getNode(nodeDottedPath);
460
- }
461
-
462
- /**
463
- * A proxy for directly getting a Mesh from an {@link Element} by its {@link DottedPath}s.
464
- */
465
- public async getMesh(
466
- elementDottedPath: DottedPathArgument,
467
- meshDottedPath: DottedPathArgument
468
- ): Promise<Mesh | null> {
469
- const element = await this.getElement(elementDottedPath);
470
- return element.getMesh(meshDottedPath);
471
- }
472
-
473
- /**
474
- * Creates a living clone of this {@link Variant}. Will clone all parent {@link Variant}s in tree.
475
- *
476
- * @emit {@link Event.VARIANT_CREATED}
477
- * @ignore
478
- */
479
- public async createLiving(variantInstance: VariantInstance, parameters?: ParameterBag): Promise<Variant> {
480
- const parent = await this.parent?.createLiving(variantInstance);
481
- const variant = new Variant(variantInstance, this.name, this._structureJson, this.viewer, parent);
482
- parent?._children.set(variant.name, variant);
483
- variant.assetContainer = this.assetContainer;
484
- variant.parameterObservers = cloneDeep(this.parameterObservers);
485
- await variant.createElements();
486
- await variant.createViewerLights();
487
- variant.addParameterObservers();
488
- await variant.bootstrapParameters(parameters);
489
- this.viewer.broadcastEvent(Event.VARIANT_CREATED, variant);
490
- return variant;
491
- }
492
-
493
- /**
494
- * Destroys this {@link Variant}, all parents and destroy the {@link Element}s.
495
- */
496
- public destroy(): Variant {
497
- this.elements.forEach(element => element.destroy());
498
- if (this.parent) {
499
- this.parent.destroy();
500
- }
501
- this.broadcastEvent(Event.VARIANT_DESTROYED, this);
502
- return this;
503
- }
504
-
505
- /**
506
- * Places the given {@link ParameterBag} in the {@link Variant}'s parameters, replaces all patterns in the
507
- * {@link StructureJson}, broadcasts all {@link ParameterObserver}s and delegates them to its {@link Element}s.
508
- *
509
- * @emit {@link Event.VARIANT_PARAMETER_COMMITTED}
510
- */
511
- public async commitParameters(parameters?: ParameterBag): Promise<Variant> {
512
- parameters = merge({}, parameters);
513
-
514
- // remember old parameter values for later comparison
515
- const oldParameters = cloneDeep(this.inheritedParameters);
516
-
517
- // replace patterns in given parameters
518
- let _parameters = JSON.stringify(parameters);
519
- for (const parameter in this.inheritedParameters) {
520
- const value = this.inheritedParameters[parameter];
521
- const search = new RegExp(`\\$\\{${parameter}\\}`, 'g');
522
- _parameters = _parameters.replace(search, value.toString());
523
- }
524
- merge(parameters, JSON.parse(_parameters));
525
-
526
- // merge inherited parameters and replaced given parameters
527
- const mergedParameters = merge({}, this.inheritedParameters, parameters);
528
-
529
- // replace patterns in structure parameters
530
- const structureParameters = this._structureJson.parameters || {};
531
- let _structureParameters = JSON.stringify(structureParameters);
532
- for (const parameter in mergedParameters) {
533
- const value = mergedParameters[parameter];
534
- const search = new RegExp(`\\$\\{${parameter}\\}`, 'g');
535
- _structureParameters = _structureParameters.replace(search, value.toString());
536
- }
537
- const replacedStructureParameters = JSON.parse(_structureParameters);
538
-
539
- // calculate which replaced structure parameters have changed and should overload given parameters
540
- const differentStructureParameters: ParameterBag = {};
541
- for (const parameter in replacedStructureParameters) {
542
- if (!isEqual(structureParameters[parameter], replacedStructureParameters[parameter])) {
543
- differentStructureParameters[parameter] = replacedStructureParameters[parameter];
544
- }
545
- }
546
-
547
- // merge replaced structure parameters and given inherited parameters to structure parameters
548
- merge(this.parameters, mergedParameters, differentStructureParameters);
549
-
550
- // inherited parameters are now the new parameters to process
551
- const newParameters = this.inheritedParameters;
552
-
553
- // replace all parameter patterns in structure json
554
- let structure = JSON.stringify(this._structureJson);
555
- for (const parameter in newParameters) {
556
- const value = newParameters[parameter];
557
- const search = new RegExp(`\\$\\{${parameter}\\}`, 'g');
558
- structure = structure.replace(search, value.toString());
559
- }
560
- this.structureJson = JSON.parse(structure);
561
-
562
- // handle parameter observers
563
- let observerPromises: Promise<void | ParameterObserver>[] = [];
564
- for (const parameter in newParameters) {
565
- const oldParameterValue = oldParameters[parameter];
566
- const newParameterValue = newParameters[parameter];
567
- this.assertParameter(this.inheritedParameterDeclaration, parameter, newParameterValue);
568
- if (oldParameterValue === newParameterValue && this.parametersInitialized) {
569
- continue;
570
- }
571
- // parameter changed
572
- const parameterObservers = mergeMaps(this._parameterObservers, this.parameterObservers);
573
- if (parameterObservers.has(parameter)) {
574
- const observers = parameterObservers.get(parameter)!;
575
- observerPromises = concat(
576
- observerPromises,
577
- observers.map(observer => {
578
- const observerResult = observer(this, oldParameterValue, newParameterValue);
579
- return Promise.resolve(observerResult).then(() => {
580
- if (this.parametersInitialized) {
581
- this.broadcastEvent(
582
- Event.VARIANT_PARAMETER_COMMITTED,
583
- this,
584
- parameter,
585
- oldParameterValue,
586
- newParameterValue
587
- );
588
- }
589
- });
590
- })
591
- );
592
- }
593
- }
594
- await Promise.all(observerPromises);
595
-
596
- // broadcast that bag has been committed
597
- this.broadcastEvent(Event.VARIANT_PARAMETER_BAG_COMMITTED, this, oldParameters, newParameters);
598
-
599
- // commit parameters to elements
600
- await this.commitParametersToElements(newParameters);
601
-
602
- // commit parameters to lights
603
- await this.commitParametersToViewerLights(newParameters);
604
-
605
- // propagate parameters to parent
606
- if (this.parent) {
607
- await this.parent.commitParameters(this.parameters);
608
- }
609
-
610
- return this;
611
- }
612
-
613
- /**
614
- * Adds an observer function for camera matrix changes for given `dottedPath` representing the {@link Element}
615
- * and the `traceable`. The `observer` gets 2 parameters: the `AbstractMesh` and a `ClientRect` object.
616
- */
617
- public async addTraceableObserver(
618
- dottedPath: DottedPathArgument,
619
- observer: CallableFunction,
620
- payload?: any
621
- ): Promise<Element> {
622
- const _dottedPath = DottedPath.create(dottedPath);
623
- const traceableName = _dottedPath.popPart();
624
- if (!traceableName) {
625
- throw new Error(
626
- `The dottedPath must consist of the element and the name of the defined corresponding ` +
627
- `traceable ("${_dottedPath.path}" given).`
628
- );
629
- }
630
- const element = await this.getElement(_dottedPath);
631
- return element.addTraceableObserver(traceableName, observer, payload);
632
- }
633
-
634
- /**
635
- * Loads {@link glTFUri} with assets, adds them to the {@link Variant}'s `assetContainer` and deactivates the meshes.
636
- * (for further processing).
637
- * @emits {@link Event.ASSET_LOADING_START}
638
- * @emits {@link Event.ASSET_LOADING_END}
639
- */
640
- protected async loadAssets(): Promise<Variant> {
641
- this.broadcastEvent(Event.ASSET_LOADING_START, this);
642
- return new Promise<Variant>(resolve => {
643
- if (!this.structureJson) {
644
- this.broadcastEvent(Event.ASSET_LOADING_END, this);
645
- return resolve(this);
646
- }
647
- if (!this.glTF) {
648
- this.broadcastEvent(Event.ASSET_LOADING_END, this);
649
- return resolve(this);
650
- }
651
- SceneLoader.LoadAssetContainer(
652
- this.glTF.rootUrl,
653
- this.glTF.fileName,
654
- this.viewer.scene,
655
- // on success
656
- container => {
657
- this.assetContainer = container;
658
- const nodes = this.assetContainer.getNodes().filter(n => n instanceof TransformNode) as TransformNode[];
659
- reportDuplicateNodeNames(
660
- intersectingNodeNames(nodes, this.viewer.scene.getNodes(), n => n instanceof TransformNode)
661
- );
662
- nodes.forEach(node => {
663
- deactivateTransformNode(node, false);
664
- injectNodeMetadata(node, { dottedPath: getDottedPathForNode(node), originalName: node.name }, false);
665
- });
666
- this.assetContainer.lights.forEach(light => {
667
- light.setEnabled(false);
668
- injectNodeMetadata(light, { dottedPath: getDottedPathForNode(light), originalName: light.name }, false);
669
- this.viewer.scene.addLight(light);
670
- });
671
- this.assetContainer.cameras.forEach(camera => {
672
- camera.setEnabled(false);
673
- injectNodeMetadata(camera, { dottedPath: getDottedPathForNode(camera), originalName: camera.name }, false);
674
- this.viewer.scene.addCamera(camera);
675
- });
676
- this.assetContainer.materials.forEach(material => this.viewer.scene.materials.push(material));
677
- this.broadcastEvent(Event.ASSET_LOADING_END, this);
678
- resolve(this);
679
- },
680
- // on progress
681
- undefined,
682
- // on error
683
- reason => {
684
- this.broadcastEvent(Event.ASSET_LOADING_END, this);
685
- throw new Error(`Error loading assets for variant "${this.id}": ${reason}.`);
686
- }
687
- );
688
- });
689
- }
690
-
691
- /**
692
- * Commits given parameters to all {@link Element}s.
693
- */
694
- protected async commitParametersToElements(parameters: ParameterBag) {
695
- await Promise.all(
696
- this.elements.map(element => this.commitParametersToVariantParameterizable(parameters, element, 'elements'))
697
- );
698
- }
699
-
700
- /**
701
- * Commits given parameters to all {@link ViewerLight}s.
702
- */
703
- protected async commitParametersToViewerLights(parameters: ParameterBag) {
704
- await Promise.all(
705
- this.viewerLights.map(viewerLight =>
706
- this.commitParametersToVariantParameterizable(parameters, viewerLight, 'lights')
707
- )
708
- );
709
- }
710
-
711
- /**
712
- * Commits given parameters to a {@link VariantParameterizable} and updates the according definition with given
713
- * key in the {@link StructureJson}. The `definitionKey` "elements" for example will update the definition in
714
- * `this.structureJson.elements`.
715
- */
716
- protected async commitParametersToVariantParameterizable(
717
- parameters: ParameterBag,
718
- parameterizable: VariantParameterizable,
719
- definitionKey: string
720
- ): Promise<ParameterObservable> {
721
- const initialDefinition = get(this._structureJson, definitionKey)[parameterizable.name];
722
- let initialDefinitionStr = JSON.stringify(initialDefinition);
723
- const _parameters: ParameterBag = {};
724
- for (const parameter in parameters) {
725
- const dpp = DottedPath.create(parameter);
726
- if (dpp.shiftPart() !== parameterizable.name) {
727
- continue;
728
- }
729
- // we got a parameterizable ("element") parameter
730
- let parameterValue = parameters[parameter];
731
- const parameterizableParameter = dpp.path;
732
- // If the variant is explicitly hidden, we must not override the visibility with element parameters. We need
733
- // an exception for visibility to avoid overloading already applied element parameters with element parameters
734
- // defined in the variant spec ("dotted parameters").
735
- // @see https://github.com/Combeenation/3d-viewer/issues/44
736
- if (parameterizableParameter === Parameter.VISIBLE && parameters[Parameter.VISIBLE] === false) {
737
- parameterValue = false;
738
- }
739
- _parameters[parameterizableParameter] = parameterValue;
740
- const search = new RegExp(`\\$\\{${parameterizableParameter}\\}`, 'g');
741
- initialDefinitionStr = initialDefinitionStr.replace(search, parameterValue.toString());
742
- }
743
- const definition = get(this.structureJson, definitionKey);
744
- definition[this.name] = JSON.parse(initialDefinitionStr);
745
- set(this.structureJson, definitionKey, definition);
746
- return await parameterizable.commitParameters(_parameters);
747
- }
748
-
749
- /**
750
- * Commits given {@link Parameter} to the {@link Variant}'s {@link Element}s.
751
- */
752
- protected async commitParameterToElements(parameter: string, value: ParameterValue): Promise<Variant> {
753
- const promises = [];
754
- for (const element of this.elements) {
755
- const paramShowsVariant = Parameter.VISIBLE === parameter && value;
756
-
757
- // Fixes https://combeenation.youtrack.cloud/issue/CB-7773
758
- // Don't enable/show the variants element if it is explicitly hidden via its element parameters.
759
- // E.g. via spec:
760
- // ```
761
- // variants: {
762
- // theVariant: {
763
- // elements: {
764
- // Main: ['root.main'],
765
- // Secondary: ['root.secondary'],
766
- // },
767
- // parameters: {
768
- // // !!! The element `Secondary` should **not** be enabled when its containing variant is enabled !!!
769
- // 'Secondary.visible': false,
770
- // }
771
- // }
772
- // }
773
- // ```
774
- const elVisibleParamPath = DottedPath.create([element.name, Parameter.VISIBLE]).toString();
775
- const elVisibleParamValue = this.inheritedParameters[elVisibleParamPath];
776
- const elVisibleParamValueParsed = elVisibleParamValue && Parameter.parseBoolean(elVisibleParamValue);
777
- const elIsHiddenViaParams = elVisibleParamValueParsed === false;
778
- if (paramShowsVariant && elIsHiddenViaParams) continue;
779
-
780
- // Fixes https://combeenation.youtrack.cloud/issue/CB-7809
781
- // Apply element material before showing the element to prevent loading of the elements "original" material which
782
- // is never shown when "overwritten" by elements material param.
783
- const elMaterialParamPath = DottedPath.create([element.name, Parameter.MATERIAL]).toString();
784
- const elMaterialParamValue = this.inheritedParameters[elMaterialParamPath];
785
- if (paramShowsVariant && elMaterialParamValue) {
786
- await element.commitParameter(Parameter.MATERIAL, elMaterialParamValue);
787
- }
788
-
789
- promises.push(element.commitParameter(parameter, value));
790
- }
791
- await Promise.all(promises);
792
- return this;
793
- }
794
-
795
- /**
796
- * Commits given {@link Parameter} to the {@link Variant}'s {@link Element}s.
797
- */
798
- protected async commitParameterToViewerLights(parameter: string, value: ParameterValue): Promise<Variant> {
799
- const promises = [];
800
- for (const viewerLight of this.viewerLights) {
801
- promises.push(viewerLight.commitParameter(parameter, value));
802
- }
803
- await Promise.all(promises);
804
- return this;
805
- }
806
-
807
- /**
808
- * Adds the default {@link ParameterObserver}s which are called every time {@link commitParameters} is called.
809
- */
810
- protected addParameterObservers(): Variant {
811
- this._parameterObservers.set(Parameter.VISIBLE, [
812
- async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
813
- await variant.commitParameterToElements(Parameter.VISIBLE, newValue);
814
- await variant.commitParameterToViewerLights(Parameter.VISIBLE, newValue);
815
- },
816
- ]);
817
- this._parameterObservers.set(Parameter.SCALING, [
818
- async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
819
- await variant.commitParameterToElements(Parameter.SCALING, newValue);
820
- await variant.commitParameterToViewerLights(Parameter.SCALING, newValue);
821
- },
822
- ]);
823
- this._parameterObservers.set(Parameter.MATERIAL, [
824
- async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
825
- await variant.commitParameterToElements(Parameter.MATERIAL, newValue);
826
- },
827
- ]);
828
- this._parameterObservers.set(Parameter.MATERIAL_COLOR, [
829
- async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
830
- await variant.commitParameterToElements(Parameter.MATERIAL_COLOR, newValue);
831
- },
832
- ]);
833
- this._parameterObservers.set(Parameter.MATERIAL_METALLNESS, [
834
- async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
835
- await variant.commitParameterToElements(Parameter.MATERIAL_METALLNESS, newValue);
836
- },
837
- ]);
838
- this._parameterObservers.set(Parameter.MATERIAL_ROUGHNESS, [
839
- async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
840
- await variant.commitParameterToElements(Parameter.MATERIAL_ROUGHNESS, newValue);
841
- },
842
- ]);
843
- this._parameterObservers.set(Parameter.HIGHLIGHT_COLOR, [
844
- async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
845
- await variant.commitParameterToElements(Parameter.HIGHLIGHT_COLOR, newValue);
846
- },
847
- ]);
848
- this._parameterObservers.set(Parameter.HIGHLIGHTED, [
849
- async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
850
- await variant.commitParameterToElements(Parameter.HIGHLIGHTED, newValue);
851
- },
852
- ]);
853
- this._parameterObservers.set(Parameter.POSITION, [
854
- async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
855
- await variant.commitParameterToElements(Parameter.POSITION, newValue);
856
- await variant.commitParameterToViewerLights(Parameter.POSITION, newValue);
857
- },
858
- ]);
859
- this._parameterObservers.set(Parameter.ROTATION, [
860
- async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
861
- await variant.commitParameterToElements(Parameter.ROTATION, newValue);
862
- await variant.commitParameterToViewerLights(Parameter.ROTATION, newValue);
863
- },
864
- ]);
865
- this._parameterObservers.set(Parameter.CAST_SHADOW, [
866
- async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
867
- await variant.commitParameterToElements(Parameter.CAST_SHADOW, newValue);
868
- },
869
- ]);
870
- this._parameterObservers.set(Parameter.CAST_SHADOW_FROM_LIGHTS, [
871
- async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
872
- await variant.commitParameterToElements(Parameter.CAST_SHADOW_FROM_LIGHTS, newValue);
873
- },
874
- ]);
875
- this._parameterObservers.set(Parameter.RECEIVE_SHADOWS, [
876
- async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
877
- await variant.commitParameterToElements(Parameter.RECEIVE_SHADOWS, newValue);
878
- },
879
- ]);
880
- return this;
881
- }
882
-
883
- /**
884
- * Creates {@link Element}s and clones nodes into them.
885
- */
886
- protected async createElements(forInstance?: string): Promise<Variant> {
887
- this.createElementDefinitionsIfNotExist();
888
- for (const name in this.structureJson.elements || {}) {
889
- this.elements.push(await Element.create(this, name));
890
- }
891
- // inject node meta to all inherited elements
892
- // we do this to inject the deepest and most concrete variant information to all cloned nodes in the tree
893
- this.inheritedElements.forEach(element =>
894
- element.nodes.forEach(node => injectNodeMetadata(node, { variant: this, variantParameterizable: element }))
895
- );
896
- return this;
897
- }
898
-
899
- /**
900
- * Creates {@link ViewerLight}s.
901
- */
902
- protected async createViewerLights(): Promise<Variant> {
903
- for (const name in this.structureJson.lights || {}) {
904
- this.viewerLights.push(await ViewerLight.create(this, name));
905
- }
906
- this.inheritedViewerLights.forEach(viewerLight => {
907
- injectNodeMetadata(viewerLight.light, { variant: this, variantParameterizable: viewerLight });
908
- });
909
- return this;
910
- }
911
-
912
- /**
913
- * Bootstrapping for parameters. It sets the `parametersInitialized` to true for all ancestors.
914
- */
915
- protected async bootstrapParameters(parameters?: ParameterBag): Promise<Variant> {
916
- await this.commitParameters(merge(cloneDeep(this.parameters), parameters));
917
- concat(this.ancestors, this).forEach(ancestor => (ancestor.parametersInitialized = true));
918
- return this;
919
- }
920
-
921
- /**
922
- * Ensures there is at least one "Main" {@link Element} for the {@link Variant} with all "root nodes" defined in path.
923
- */
924
- protected createElementDefinitionsIfNotExist() {
925
- if (this._structureJson.elements || this.inheritedNodes.length === 0) {
926
- return;
927
- }
928
- this._structureJson.elements = {
929
- Main: { paths: { include: this.inheritedNodes.map(node => node.metadata.dottedPath.path) } },
930
- };
931
- this.structureJson.elements = cloneDeep(this._structureJson.elements);
932
- }
933
- }
1
+ import {
2
+ deactivateTransformNode,
3
+ getDottedPathForNode,
4
+ injectNodeMetadata,
5
+ intersectingNodeNames,
6
+ reportDuplicateNodeNames,
7
+ } from '../util/babylonHelper';
8
+ import { loadJson, mergeMaps } from '../util/resourceHelper';
9
+ import { DottedPath } from './dottedPath';
10
+ import { Element } from './element';
11
+ import { Event } from './event';
12
+ import { Parameter } from './parameter';
13
+ import { ParameterObservable } from './parameterObservable';
14
+ import { Parameterizable } from './parameterizable';
15
+ import { VariantParameterizable } from './variantParameterizable';
16
+ import { Viewer } from './viewer';
17
+ import { ViewerError, ViewerErrorIds } from './viewerError';
18
+ import { ViewerLight } from './viewerLight';
19
+ import { Light } from '@babylonjs/core/Lights/light';
20
+ import '@babylonjs/core/Loading/Plugins/babylonFileLoader';
21
+ import { SceneLoader } from '@babylonjs/core/Loading/sceneLoader';
22
+ import { Material } from '@babylonjs/core/Materials/material';
23
+ import { Mesh } from '@babylonjs/core/Meshes/mesh';
24
+ import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
25
+ import { AssetContainer } from '@babylonjs/core/assetContainer';
26
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression';
27
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_clearcoat';
28
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_ior';
29
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness';
30
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_sheen';
31
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_specular';
32
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_translucency';
33
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_transmission';
34
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_unlit';
35
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_variants';
36
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_texture_basisu';
37
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_texture_transform';
38
+ import '@babylonjs/loaders/glTF/2.0/glTFLoader';
39
+ import { cloneDeep, concat, get, isEmpty, isEqual, isString, merge, set } from 'lodash-es';
40
+
41
+ /**
42
+ * A concrete "Variant". Most of these are handled by either the {@link Viewer} or {@link VariantInstance}.
43
+ */
44
+ export class Variant extends Parameterizable {
45
+ public assetContainer: AssetContainer;
46
+
47
+ public readonly elements: Element[] = [];
48
+
49
+ public readonly viewerLights: ViewerLight[] = [];
50
+
51
+ public structureJson: StructureJson;
52
+
53
+ protected _dottedNodes: Map<DottedPath, TransformNode> | undefined;
54
+
55
+ protected readonly _children: Map<string, Variant> = new Map();
56
+
57
+ protected readonly _parameterObservers: Map<string, ParameterObserver[]> = new Map();
58
+
59
+ /**
60
+ * @internal
61
+ */
62
+ private parametersInitialized: boolean = false;
63
+
64
+ /**
65
+ * Constructor.
66
+ */
67
+ protected constructor(
68
+ protected readonly _variantInstance: VariantInstance | null,
69
+ public readonly name: string,
70
+ protected readonly _structureJson: StructureJson,
71
+ public readonly viewer: Viewer,
72
+ public readonly parent?: Variant
73
+ ) {
74
+ super(cloneDeep(_structureJson.parameterDeclaration), cloneDeep(_structureJson.parameters));
75
+ for (const parameter in _structureJson.parameterDeclaration) {
76
+ if (!(parameter in (_structureJson.parameters || []))) {
77
+ throw new Error(`No default value for parameter "${parameter}" defined.`);
78
+ }
79
+ }
80
+ this.assetContainer = new AssetContainer(viewer.scene);
81
+ this.structureJson = cloneDeep(_structureJson);
82
+ }
83
+
84
+ /**
85
+ * Creates the root {@link Variant}.
86
+ */
87
+ public static async createRoot(structureJson: StructureJson, viewer: Viewer): Promise<Variant> {
88
+ return Variant.create(null, '_', structureJson, viewer);
89
+ }
90
+
91
+ /**
92
+ * Creates a {@link Variant} based on given parameters.
93
+ *
94
+ * @throws Error if "gltf" property is provided without a filename
95
+ */
96
+ public static async create(
97
+ variantInstance: VariantInstance | null,
98
+ name: string,
99
+ structureJson: StructureJson,
100
+ viewer: Viewer,
101
+ parent?: Variant
102
+ ): Promise<Variant> {
103
+ const variant = new Variant(variantInstance, name, structureJson, viewer, parent);
104
+ await variant.loadAssets();
105
+ return variant;
106
+ }
107
+
108
+ /**
109
+ * The ancestor {@link Variant}s ordered from top to bottom in the built tree.
110
+ */
111
+ get ancestors(): Variant[] {
112
+ const ancestors = [];
113
+ let variant: Variant = this;
114
+ while (variant.parent) {
115
+ ancestors.unshift(variant.parent);
116
+ variant = variant.parent;
117
+ }
118
+ return ancestors;
119
+ }
120
+
121
+ /**
122
+ * The root {@link Variant}.
123
+ */
124
+ get root(): Variant {
125
+ return this.ancestors[0] ?? this;
126
+ }
127
+
128
+ /**
129
+ * The {@link DottedPath} in the built tree of {@link Variant}s.
130
+ * E.g. "_.top-1.sub-2.sub-sub-3"
131
+ */
132
+ get dottedPath(): DottedPath {
133
+ const parentIds = this.ancestors.map(ancestor => {
134
+ return ancestor.name;
135
+ });
136
+ return DottedPath.createFromParts(parentIds).addPart(this.name);
137
+ }
138
+
139
+ /**
140
+ * Gets the {@link VariantInstance} this variant was created for. There are variants without an instance (the "ghost"
141
+ * ones used for bootstrapping instances). The usage of {@link Variant}s without an instance is an absolute edge-case
142
+ * when deeply using the viewer api and working abroad best practices.
143
+ */
144
+ get variantInstance(): VariantInstance | null {
145
+ return this._variantInstance;
146
+ }
147
+
148
+ /**
149
+ * The id representing a {@link DottedPath}.
150
+ */
151
+ get id(): string {
152
+ const dottedPath = DottedPath.create(this.dottedPath);
153
+ dottedPath.shiftPart(); // remove root
154
+ return dottedPath.path || '_';
155
+ }
156
+
157
+ /**
158
+ * The defined glTF Asset.
159
+ */
160
+ get glTF(): Asset | undefined {
161
+ if (this.structureJson.glTF) {
162
+ if (isString(this.structureJson.glTF)) {
163
+ return {
164
+ rootUrl: this.structureJson.glTF,
165
+ fileName: undefined,
166
+ };
167
+ }
168
+ if (isEmpty(this.structureJson.glTF.rootUrl)) {
169
+ throw new Error(`No "rootUrl" defined in "glTF" definition for variant "${this.id}".`);
170
+ }
171
+ return this.structureJson.glTF;
172
+ }
173
+ }
174
+
175
+ /**
176
+ * The defined glTF URI.
177
+ */
178
+ get glTFUri(): string | undefined {
179
+ if (this.glTF) {
180
+ return [this.glTF.rootUrl, this.glTF.fileName].join('');
181
+ }
182
+ }
183
+
184
+ /**
185
+ * The inherited defined glTF URI.
186
+ */
187
+ get inheritedGlTFUri(): string | undefined {
188
+ if (!this.glTFUri && this.parent) {
189
+ return this.parent.inheritedGlTFUri;
190
+ }
191
+ return this.glTFUri;
192
+ }
193
+
194
+ /**
195
+ * The TransformNodes of the {@link Variant}.
196
+ */
197
+ get nodes(): TransformNode[] {
198
+ const rootNodes = this.assetContainer
199
+ .getNodes()
200
+ .filter(n => n instanceof TransformNode && !n.parent) as TransformNode[];
201
+ return rootNodes;
202
+ }
203
+
204
+ /**
205
+ * The {@link ViewerLight}s of the {@link Variant}.
206
+ */
207
+ get lights(): Light[] {
208
+ return this.assetContainer.lights;
209
+ }
210
+
211
+ /**
212
+ * All TransformNodes of the {@link Variant} mapped flat with a {@link DottedPath}.
213
+ */
214
+ get dottedNodes(): Map<DottedPath, TransformNode> {
215
+ if (!this._dottedNodes) {
216
+ const nodes = this.assetContainer.getNodes().filter(n => n instanceof TransformNode);
217
+ const dottedNodes = new Map();
218
+ nodes.forEach(node => {
219
+ dottedNodes.set(node.metadata.dottedPath, node);
220
+ });
221
+ this._dottedNodes = dottedNodes;
222
+ }
223
+ return this._dottedNodes;
224
+ }
225
+
226
+ /**
227
+ * The Materials of the {@link Variant}.
228
+ */
229
+ get materials(): Material[] {
230
+ return this.assetContainer.materials;
231
+ }
232
+
233
+ /**
234
+ * The cloned TransformNodes of all {@link Element}s created for this {@link Variant}.
235
+ */
236
+ get elementNodes(): TransformNode[] {
237
+ return this.elements.reduce((a, c) => a.concat(c.nodes), [] as TransformNode[]);
238
+ }
239
+
240
+ /**
241
+ * The cloned TransformNodes of all {@link Element}s created for this {@link Variant} flattened.
242
+ */
243
+ get elementNodesFlat(): TransformNode[] {
244
+ return this.elements.reduce((a, c) => [...a, ...c.nodesFlat], [] as TransformNode[]);
245
+ }
246
+
247
+ /**
248
+ * All {@link Element}s from this {@link Variant}'s parents.
249
+ */
250
+ get inheritedElements(): Element[] {
251
+ let elements: Element[] = [];
252
+ this.ancestors.forEach(ancestor => {
253
+ elements = concat(elements, ancestor.elements);
254
+ });
255
+ return concat(elements, this.elements);
256
+ }
257
+
258
+ /**
259
+ * All {@link ViewerLight}s inherited from this {@link Variant}'s parents.
260
+ */
261
+ get inheritedViewerLights(): ViewerLight[] {
262
+ let viewerLights: ViewerLight[] = [];
263
+ this.ancestors.forEach(ancestor => {
264
+ viewerLights = concat(viewerLights, ancestor.viewerLights);
265
+ });
266
+ return concat(viewerLights, this.viewerLights);
267
+ }
268
+
269
+ /**
270
+ * All TransformNodes inherited from this {@link Variant}'s parents.
271
+ */
272
+ get inheritedNodes(): TransformNode[] {
273
+ let nodes: TransformNode[] = [];
274
+ this.ancestors.forEach(ancestor => {
275
+ nodes = concat(nodes, ancestor.nodes);
276
+ });
277
+ return concat(nodes, this.nodes);
278
+ }
279
+
280
+ /**
281
+ * All TransformNodes inherited from this {@link Variant}'s parents mapped flat with a {@link DottedPath}.
282
+ */
283
+ get inheritedDottedNodes(): Map<DottedPath, TransformNode> {
284
+ let dottedNodes = this.dottedNodes;
285
+ this.ancestors.forEach(ancestor => {
286
+ dottedNodes = mergeMaps(dottedNodes, ancestor.dottedNodes);
287
+ });
288
+ return dottedNodes;
289
+ }
290
+
291
+ /**
292
+ * All Lights inherited from this {@link Variant}'s parents.
293
+ */
294
+ get inheritedLights(): Light[] {
295
+ let lights: Light[] = [];
296
+ this.ancestors.forEach(ancestor => {
297
+ lights = concat(lights, ancestor.lights);
298
+ });
299
+ return concat(lights, this.lights);
300
+ }
301
+
302
+ /**
303
+ * The {@link ParameterDeclarations} inherited from this {@link Variant}'s parents.
304
+ */
305
+ get inheritedParameterDeclaration(): ParameterDeclarations {
306
+ const declaration = {};
307
+ this.ancestors.forEach(ancestor => {
308
+ merge(declaration, ancestor.parameterDeclaration);
309
+ });
310
+ return merge(declaration, this.parameterDeclaration);
311
+ }
312
+
313
+ /**
314
+ * The {@link ParameterBag} inherited from this {@link Variant}'s parents.
315
+ */
316
+ get inheritedParameters(): ParameterBag {
317
+ const parameters = {};
318
+ this.ancestors.forEach(ancestor => {
319
+ merge(parameters, ancestor.parameters);
320
+ });
321
+ return merge(parameters, this.parameters);
322
+ }
323
+
324
+ /**
325
+ * All Materials from this {@link Variant}'s parents.
326
+ */
327
+ get inheritedMaterials(): Material[] {
328
+ let materials: Material[] = [];
329
+ this.ancestors.forEach(ancestor => {
330
+ materials = concat(materials, ancestor.materials);
331
+ });
332
+ return concat(materials, this.materials);
333
+ }
334
+
335
+ /**
336
+ * Gets the direct children of the current {@link Variant}.
337
+ */
338
+ public async getChildren(): Promise<Variant[]> {
339
+ const children: Variant[] = [];
340
+ for (const name in this.structureJson.variants) {
341
+ children.push(await this.getDescendant(name));
342
+ }
343
+ return children;
344
+ }
345
+
346
+ /**
347
+ * Gets a descendant {@link Variant} of the current {@link Variant} relative to its {@link DottedPath}.
348
+ * If you have the dotted path `_.product_x.variant_blue.with_yellow_highlight` in a tree and you operate on the
349
+ * `product_x`, you can call `this.getDescendant('variant_blue.with_yellow_highlight')` to get the lowermost
350
+ * {@link Variant}.
351
+ */
352
+ public async getDescendant(dottedPath: DottedPathArgument): Promise<Variant> {
353
+ const _dottedPath = DottedPath.create(dottedPath);
354
+ const [name, ...descendantParts] = _dottedPath.parts;
355
+ let variant;
356
+ if (this._children.has(name)) {
357
+ variant = this._children.get(name);
358
+ } else {
359
+ if (!this.structureJson.variants) {
360
+ throw new Error(`Missing key "variants" in JSON structure for variant "${this.id}".`);
361
+ }
362
+ if (!this.structureJson.variants[name]) {
363
+ throw new Error(`Variant "${_dottedPath.path}" not defined in JSON structure for variant "${this.id}".`);
364
+ }
365
+ if (this.structureJson.variants[name].file) {
366
+ const file = this.structureJson.variants[name].file as string;
367
+ variant = await Variant.create(
368
+ this.variantInstance,
369
+ name,
370
+ await loadJson<StructureJson>(file),
371
+ this.viewer,
372
+ this
373
+ );
374
+ } else {
375
+ variant = await Variant.create(
376
+ this.variantInstance,
377
+ name,
378
+ this.structureJson.variants[name],
379
+ this.viewer,
380
+ this
381
+ );
382
+ }
383
+ this._children.set(name, variant);
384
+ }
385
+ if (!variant) {
386
+ throw new Error(`Variant "${_dottedPath.path}" was not created.`);
387
+ }
388
+ if (descendantParts.length > 0) {
389
+ return await variant.getDescendant(DottedPath.createFromParts(descendantParts));
390
+ }
391
+ return variant;
392
+ }
393
+
394
+ /**
395
+ * Gets the desired {@link Element} of the current {@link Variant} relative to its {@link DottedPath}.
396
+ * Uses the mechanism of {@link getDescendant} to resolve the appropriate variant in tree.
397
+ */
398
+ public async getElement(dottedPath: DottedPathArgument): Promise<Element> {
399
+ const _dottedPath = DottedPath.create(dottedPath);
400
+ const elementName = _dottedPath.popPart();
401
+ let variant: Variant = this;
402
+ if (_dottedPath.parts.length > 0) {
403
+ variant = await this.getDescendant(_dottedPath);
404
+ }
405
+ if (variant.inheritedElements.length === 0) {
406
+ throw new Error(
407
+ `No elements for variant "${variant.id}" found. ` +
408
+ `Either none are defined or they are not initialized (are you operating on the appropriate living?).`
409
+ );
410
+ }
411
+ let element;
412
+ variant.inheritedElements.forEach(_element => {
413
+ if (_element.name === elementName) {
414
+ element = _element;
415
+ }
416
+ });
417
+ if (!element) {
418
+ throw new Error(`Element with name "${elementName}" does not exist for variant "${variant.id}".`);
419
+ }
420
+ return element;
421
+ }
422
+
423
+ /**
424
+ * Gets the desired {@link ViewerLight} of the current {@link Variant} relative to its {@link DottedPath}.
425
+ * Uses the mechanism of {@link getDescendant} to resolve the appropriate variant in tree.
426
+ */
427
+ public async getViewerLight(dottedPath: DottedPathArgument): Promise<ViewerLight> {
428
+ const _dottedPath = DottedPath.create(dottedPath);
429
+ const viewerLightName = _dottedPath.popPart();
430
+ let variant: Variant = this;
431
+ if (_dottedPath.parts.length > 0) {
432
+ variant = await this.getDescendant(_dottedPath);
433
+ }
434
+ if (variant.inheritedViewerLights.length === 0) {
435
+ throw new Error(
436
+ `No viewerLights for variant "${variant.id}" found. ` +
437
+ `Either none are defined or they are not initialized (are you operating on the appropriate living?).`
438
+ );
439
+ }
440
+ let viewerLight;
441
+ variant.inheritedViewerLights.forEach(_viewerLight => {
442
+ if (_viewerLight.name === viewerLightName) {
443
+ viewerLight = _viewerLight;
444
+ }
445
+ });
446
+ if (!viewerLight) {
447
+ throw new Error(`ViewerLight with name "${viewerLightName}" does not exist for variant "${variant.id}".`);
448
+ }
449
+ return viewerLight;
450
+ }
451
+
452
+ /**
453
+ * A proxy for directly getting a Node from an {@link Element} by its {@link DottedPath}s.
454
+ */
455
+ public async getNode(
456
+ elementDottedPath: DottedPathArgument,
457
+ nodeDottedPath: DottedPathArgument
458
+ ): Promise<TransformNode> {
459
+ const element = await this.getElement(elementDottedPath);
460
+ return element.getNode(nodeDottedPath);
461
+ }
462
+
463
+ /**
464
+ * A proxy for directly getting a Mesh from an {@link Element} by its {@link DottedPath}s.
465
+ */
466
+ public async getMesh(
467
+ elementDottedPath: DottedPathArgument,
468
+ meshDottedPath: DottedPathArgument
469
+ ): Promise<Mesh | null> {
470
+ const element = await this.getElement(elementDottedPath);
471
+ return element.getMesh(meshDottedPath);
472
+ }
473
+
474
+ /**
475
+ * Creates a living clone of this {@link Variant}. Will clone all parent {@link Variant}s in tree.
476
+ *
477
+ * @emit {@link Event.VARIANT_CREATED}
478
+ * @ignore
479
+ */
480
+ public async createLiving(variantInstance: VariantInstance, parameters?: ParameterBag): Promise<Variant> {
481
+ const parent = await this.parent?.createLiving(variantInstance);
482
+ const variant = new Variant(variantInstance, this.name, this._structureJson, this.viewer, parent);
483
+ parent?._children.set(variant.name, variant);
484
+ variant.assetContainer = this.assetContainer;
485
+ variant.parameterObservers = cloneDeep(this.parameterObservers);
486
+ await variant.createElements();
487
+ await variant.createViewerLights();
488
+ variant.addParameterObservers();
489
+ await variant.bootstrapParameters(parameters);
490
+ this.viewer.broadcastEvent(Event.VARIANT_CREATED, variant);
491
+ return variant;
492
+ }
493
+
494
+ /**
495
+ * Destroys this {@link Variant}, all parents and destroy the {@link Element}s.
496
+ */
497
+ public destroy(): Variant {
498
+ this.elements.forEach(element => element.destroy());
499
+ if (this.parent) {
500
+ this.parent.destroy();
501
+ }
502
+ this.broadcastEvent(Event.VARIANT_DESTROYED, this);
503
+ return this;
504
+ }
505
+
506
+ /**
507
+ * Places the given {@link ParameterBag} in the {@link Variant}'s parameters, replaces all patterns in the
508
+ * {@link StructureJson}, broadcasts all {@link ParameterObserver}s and delegates them to its {@link Element}s.
509
+ *
510
+ * @emit {@link Event.VARIANT_PARAMETER_COMMITTED}
511
+ */
512
+ public async commitParameters(parameters?: ParameterBag): Promise<Variant> {
513
+ parameters = merge({}, parameters);
514
+
515
+ // remember old parameter values for later comparison
516
+ const oldParameters = cloneDeep(this.inheritedParameters);
517
+
518
+ // replace patterns in given parameters
519
+ let _parameters = JSON.stringify(parameters);
520
+ for (const parameter in this.inheritedParameters) {
521
+ const value = this.inheritedParameters[parameter];
522
+ const search = new RegExp(`\\$\\{${parameter}\\}`, 'g');
523
+ _parameters = _parameters.replace(search, value.toString());
524
+ }
525
+ merge(parameters, JSON.parse(_parameters));
526
+
527
+ // merge inherited parameters and replaced given parameters
528
+ const mergedParameters = merge({}, this.inheritedParameters, parameters);
529
+
530
+ // replace patterns in structure parameters
531
+ const structureParameters = this._structureJson.parameters || {};
532
+ let _structureParameters = JSON.stringify(structureParameters);
533
+ for (const parameter in mergedParameters) {
534
+ const value = mergedParameters[parameter];
535
+ const search = new RegExp(`\\$\\{${parameter}\\}`, 'g');
536
+ _structureParameters = _structureParameters.replace(search, value.toString());
537
+ }
538
+ const replacedStructureParameters = JSON.parse(_structureParameters);
539
+
540
+ // calculate which replaced structure parameters have changed and should overload given parameters
541
+ const differentStructureParameters: ParameterBag = {};
542
+ for (const parameter in replacedStructureParameters) {
543
+ if (!isEqual(structureParameters[parameter], replacedStructureParameters[parameter])) {
544
+ differentStructureParameters[parameter] = replacedStructureParameters[parameter];
545
+ }
546
+ }
547
+
548
+ // merge replaced structure parameters and given inherited parameters to structure parameters
549
+ merge(this.parameters, mergedParameters, differentStructureParameters);
550
+
551
+ // inherited parameters are now the new parameters to process
552
+ const newParameters = this.inheritedParameters;
553
+
554
+ // replace all parameter patterns in structure json
555
+ let structure = JSON.stringify(this._structureJson);
556
+ for (const parameter in newParameters) {
557
+ const value = newParameters[parameter];
558
+ const search = new RegExp(`\\$\\{${parameter}\\}`, 'g');
559
+ structure = structure.replace(search, value.toString());
560
+ }
561
+ this.structureJson = JSON.parse(structure);
562
+
563
+ // handle parameter observers
564
+ let observerPromises: Promise<void | ParameterObserver>[] = [];
565
+ for (const parameter in newParameters) {
566
+ const oldParameterValue = oldParameters[parameter];
567
+ const newParameterValue = newParameters[parameter];
568
+ this.assertParameter(this.inheritedParameterDeclaration, parameter, newParameterValue);
569
+ if (oldParameterValue === newParameterValue && this.parametersInitialized) {
570
+ continue;
571
+ }
572
+ // parameter changed
573
+ const parameterObservers = mergeMaps(this._parameterObservers, this.parameterObservers);
574
+ if (parameterObservers.has(parameter)) {
575
+ const observers = parameterObservers.get(parameter)!;
576
+ observerPromises = concat(
577
+ observerPromises,
578
+ observers.map(observer => {
579
+ const observerResult = observer(this, oldParameterValue, newParameterValue);
580
+ return Promise.resolve(observerResult).then(() => {
581
+ if (this.parametersInitialized) {
582
+ this.broadcastEvent(
583
+ Event.VARIANT_PARAMETER_COMMITTED,
584
+ this,
585
+ parameter,
586
+ oldParameterValue,
587
+ newParameterValue
588
+ );
589
+ }
590
+ });
591
+ })
592
+ );
593
+ }
594
+ }
595
+ await Promise.all(observerPromises);
596
+
597
+ // broadcast that bag has been committed
598
+ this.broadcastEvent(Event.VARIANT_PARAMETER_BAG_COMMITTED, this, oldParameters, newParameters);
599
+
600
+ // commit parameters to elements
601
+ await this.commitParametersToElements(newParameters);
602
+
603
+ // commit parameters to lights
604
+ await this.commitParametersToViewerLights(newParameters);
605
+
606
+ // propagate parameters to parent
607
+ if (this.parent) {
608
+ await this.parent.commitParameters(this.parameters);
609
+ }
610
+
611
+ return this;
612
+ }
613
+
614
+ /**
615
+ * Adds an observer function for camera matrix changes for given `dottedPath` representing the {@link Element}
616
+ * and the `traceable`. The `observer` gets 2 parameters: the `AbstractMesh` and a `ClientRect` object.
617
+ */
618
+ public async addTraceableObserver(
619
+ dottedPath: DottedPathArgument,
620
+ observer: CallableFunction,
621
+ payload?: any
622
+ ): Promise<Element> {
623
+ const _dottedPath = DottedPath.create(dottedPath);
624
+ const traceableName = _dottedPath.popPart();
625
+ if (!traceableName) {
626
+ throw new Error(
627
+ `The dottedPath must consist of the element and the name of the defined corresponding ` +
628
+ `traceable ("${_dottedPath.path}" given).`
629
+ );
630
+ }
631
+ const element = await this.getElement(_dottedPath);
632
+ return element.addTraceableObserver(traceableName, observer, payload);
633
+ }
634
+
635
+ /**
636
+ * Loads {@link glTFUri} with assets, adds them to the {@link Variant}'s `assetContainer` and deactivates the meshes.
637
+ * (for further processing).
638
+ * @emits {@link Event.ASSET_LOADING_START}
639
+ * @emits {@link Event.ASSET_LOADING_END}
640
+ */
641
+ protected async loadAssets(): Promise<Variant> {
642
+ this.broadcastEvent(Event.ASSET_LOADING_START, this);
643
+ return new Promise<Variant>((resolve, reject) => {
644
+ if (!this.structureJson) {
645
+ this.broadcastEvent(Event.ASSET_LOADING_END, this);
646
+ return resolve(this);
647
+ }
648
+ if (!this.glTF) {
649
+ this.broadcastEvent(Event.ASSET_LOADING_END, this);
650
+ return resolve(this);
651
+ }
652
+ SceneLoader.LoadAssetContainer(
653
+ this.glTF.rootUrl,
654
+ this.glTF.fileName,
655
+ this.viewer.scene,
656
+ // on success
657
+ container => {
658
+ this.assetContainer = container;
659
+ const nodes = this.assetContainer.getNodes().filter(n => n instanceof TransformNode) as TransformNode[];
660
+ reportDuplicateNodeNames(
661
+ intersectingNodeNames(nodes, this.viewer.scene.getNodes(), n => n instanceof TransformNode)
662
+ );
663
+ nodes.forEach(node => {
664
+ deactivateTransformNode(node, false);
665
+ injectNodeMetadata(node, { dottedPath: getDottedPathForNode(node), originalName: node.name }, false);
666
+ });
667
+ this.assetContainer.lights.forEach(light => {
668
+ light.setEnabled(false);
669
+ injectNodeMetadata(light, { dottedPath: getDottedPathForNode(light), originalName: light.name }, false);
670
+ this.viewer.scene.addLight(light);
671
+ });
672
+ this.assetContainer.cameras.forEach(camera => {
673
+ camera.setEnabled(false);
674
+ injectNodeMetadata(camera, { dottedPath: getDottedPathForNode(camera), originalName: camera.name }, false);
675
+ this.viewer.scene.addCamera(camera);
676
+ });
677
+ this.assetContainer.materials.forEach(material => this.viewer.scene.materials.push(material));
678
+ this.broadcastEvent(Event.ASSET_LOADING_END, this);
679
+ resolve(this);
680
+ },
681
+ // on progress
682
+ undefined,
683
+ // on error
684
+ (scene, msg, exc) => {
685
+ this.broadcastEvent(Event.ASSET_LOADING_END, this);
686
+
687
+ /**
688
+ * @preserve (4 webpack terser plugin)
689
+ * If you're here and the shown error doesn't seem to make any sense:
690
+ * BJS "swallows" all exceptions within our success callback above and reports some generic error like
691
+ * "Unable to load from https://some/url.babylon" etc.
692
+ * -> The real reason for the error **could** be that some of our code within the above success callback has
693
+ * thrown.
694
+ */
695
+ const error = new ViewerError({
696
+ id: ViewerErrorIds.AssetLoadingFailed,
697
+ message: `Error loading assets for variant "${this.id}": ${msg}.`,
698
+ });
699
+ reject(error);
700
+ }
701
+ );
702
+ });
703
+ }
704
+
705
+ /**
706
+ * Commits given parameters to all {@link Element}s.
707
+ */
708
+ protected async commitParametersToElements(parameters: ParameterBag) {
709
+ await Promise.all(
710
+ this.elements.map(element => this.commitParametersToVariantParameterizable(parameters, element, 'elements'))
711
+ );
712
+ }
713
+
714
+ /**
715
+ * Commits given parameters to all {@link ViewerLight}s.
716
+ */
717
+ protected async commitParametersToViewerLights(parameters: ParameterBag) {
718
+ await Promise.all(
719
+ this.viewerLights.map(viewerLight =>
720
+ this.commitParametersToVariantParameterizable(parameters, viewerLight, 'lights')
721
+ )
722
+ );
723
+ }
724
+
725
+ /**
726
+ * Commits given parameters to a {@link VariantParameterizable} and updates the according definition with given
727
+ * key in the {@link StructureJson}. The `definitionKey` "elements" for example will update the definition in
728
+ * `this.structureJson.elements`.
729
+ */
730
+ protected async commitParametersToVariantParameterizable(
731
+ parameters: ParameterBag,
732
+ parameterizable: VariantParameterizable,
733
+ definitionKey: string
734
+ ): Promise<ParameterObservable> {
735
+ const initialDefinition = get(this._structureJson, definitionKey)[parameterizable.name];
736
+ let initialDefinitionStr = JSON.stringify(initialDefinition);
737
+ const _parameters: ParameterBag = {};
738
+ for (const parameter in parameters) {
739
+ const dpp = DottedPath.create(parameter);
740
+ if (dpp.shiftPart() !== parameterizable.name) {
741
+ continue;
742
+ }
743
+ // we got a parameterizable ("element") parameter
744
+ let parameterValue = parameters[parameter];
745
+ const parameterizableParameter = dpp.path;
746
+ // If the variant is explicitly hidden, we must not override the visibility with element parameters. We need
747
+ // an exception for visibility to avoid overloading already applied element parameters with element parameters
748
+ // defined in the variant spec ("dotted parameters").
749
+ // @see https://github.com/Combeenation/3d-viewer/issues/44
750
+ const visibleParamValue = parameters[Parameter.VISIBLE];
751
+ const isVisible = undefined === visibleParamValue || Parameter.parseBoolean(visibleParamValue);
752
+ if (parameterizableParameter === Parameter.VISIBLE && !isVisible) {
753
+ parameterValue = false;
754
+ }
755
+ _parameters[parameterizableParameter] = parameterValue;
756
+ const search = new RegExp(`\\$\\{${parameterizableParameter}\\}`, 'g');
757
+ initialDefinitionStr = initialDefinitionStr.replace(search, parameterValue.toString());
758
+ }
759
+ const definition = get(this.structureJson, definitionKey);
760
+ definition[this.name] = JSON.parse(initialDefinitionStr);
761
+ set(this.structureJson, definitionKey, definition);
762
+ return await parameterizable.commitParameters(_parameters);
763
+ }
764
+
765
+ /**
766
+ * Commits given {@link Parameter} to the {@link Variant}'s {@link Element}s.
767
+ */
768
+ protected async commitParameterToElements(parameter: string, value: ParameterValue): Promise<Variant> {
769
+ const promises = [];
770
+ for (const element of this.elements) {
771
+ const paramShowsVariant = Parameter.VISIBLE === parameter && value;
772
+
773
+ // Fixes https://combeenation.youtrack.cloud/issue/CB-7773
774
+ // Don't enable/show the variants element if it is explicitly hidden via its element parameters.
775
+ // E.g. via spec:
776
+ // ```
777
+ // variants: {
778
+ // theVariant: {
779
+ // elements: {
780
+ // Main: ['root.main'],
781
+ // Secondary: ['root.secondary'],
782
+ // },
783
+ // parameters: {
784
+ // // !!! The element `Secondary` should **not** be enabled when its containing variant is enabled !!!
785
+ // 'Secondary.visible': false,
786
+ // }
787
+ // }
788
+ // }
789
+ // ```
790
+ const elVisibleParamPath = DottedPath.create([element.name, Parameter.VISIBLE]).toString();
791
+ const elVisibleParamValue = this.inheritedParameters[elVisibleParamPath];
792
+ const elVisibleParamValueParsed = elVisibleParamValue && Parameter.parseBoolean(elVisibleParamValue);
793
+ const elIsHiddenViaParams = elVisibleParamValueParsed === false;
794
+ if (paramShowsVariant && elIsHiddenViaParams) continue;
795
+
796
+ // Fixes https://combeenation.youtrack.cloud/issue/CB-7809
797
+ // Apply element material before showing the element to prevent loading of the elements "original" material which
798
+ // is never shown when "overwritten" by elements material param.
799
+ const elMaterialParamPath = DottedPath.create([element.name, Parameter.MATERIAL]).toString();
800
+ const elMaterialParamValue = this.inheritedParameters[elMaterialParamPath];
801
+ if (paramShowsVariant && elMaterialParamValue) {
802
+ await element.commitParameter(Parameter.MATERIAL, elMaterialParamValue);
803
+ }
804
+
805
+ promises.push(element.commitParameter(parameter, value));
806
+ }
807
+ await Promise.all(promises);
808
+ return this;
809
+ }
810
+
811
+ /**
812
+ * Commits given {@link Parameter} to the {@link Variant}'s {@link Element}s.
813
+ */
814
+ protected async commitParameterToViewerLights(parameter: string, value: ParameterValue): Promise<Variant> {
815
+ const promises = [];
816
+ for (const viewerLight of this.viewerLights) {
817
+ promises.push(viewerLight.commitParameter(parameter, value));
818
+ }
819
+ await Promise.all(promises);
820
+ return this;
821
+ }
822
+
823
+ /**
824
+ * Adds the default {@link ParameterObserver}s which are called every time {@link commitParameters} is called.
825
+ */
826
+ protected addParameterObservers(): Variant {
827
+ this._parameterObservers.set(Parameter.VISIBLE, [
828
+ async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
829
+ await variant.commitParameterToElements(Parameter.VISIBLE, newValue);
830
+ await variant.commitParameterToViewerLights(Parameter.VISIBLE, newValue);
831
+ },
832
+ ]);
833
+ this._parameterObservers.set(Parameter.SCALING, [
834
+ async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
835
+ await variant.commitParameterToElements(Parameter.SCALING, newValue);
836
+ await variant.commitParameterToViewerLights(Parameter.SCALING, newValue);
837
+ },
838
+ ]);
839
+ this._parameterObservers.set(Parameter.MATERIAL, [
840
+ async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
841
+ await variant.commitParameterToElements(Parameter.MATERIAL, newValue);
842
+ },
843
+ ]);
844
+ this._parameterObservers.set(Parameter.MATERIAL_COLOR, [
845
+ async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
846
+ await variant.commitParameterToElements(Parameter.MATERIAL_COLOR, newValue);
847
+ },
848
+ ]);
849
+ this._parameterObservers.set(Parameter.MATERIAL_METALLNESS, [
850
+ async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
851
+ await variant.commitParameterToElements(Parameter.MATERIAL_METALLNESS, newValue);
852
+ },
853
+ ]);
854
+ this._parameterObservers.set(Parameter.MATERIAL_ROUGHNESS, [
855
+ async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
856
+ await variant.commitParameterToElements(Parameter.MATERIAL_ROUGHNESS, newValue);
857
+ },
858
+ ]);
859
+ this._parameterObservers.set(Parameter.HIGHLIGHT_COLOR, [
860
+ async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
861
+ await variant.commitParameterToElements(Parameter.HIGHLIGHT_COLOR, newValue);
862
+ },
863
+ ]);
864
+ this._parameterObservers.set(Parameter.HIGHLIGHTED, [
865
+ async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
866
+ await variant.commitParameterToElements(Parameter.HIGHLIGHTED, newValue);
867
+ },
868
+ ]);
869
+ this._parameterObservers.set(Parameter.POSITION, [
870
+ async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
871
+ await variant.commitParameterToElements(Parameter.POSITION, newValue);
872
+ await variant.commitParameterToViewerLights(Parameter.POSITION, newValue);
873
+ },
874
+ ]);
875
+ this._parameterObservers.set(Parameter.ROTATION, [
876
+ async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
877
+ await variant.commitParameterToElements(Parameter.ROTATION, newValue);
878
+ await variant.commitParameterToViewerLights(Parameter.ROTATION, newValue);
879
+ },
880
+ ]);
881
+ this._parameterObservers.set(Parameter.CAST_SHADOW, [
882
+ async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
883
+ await variant.commitParameterToElements(Parameter.CAST_SHADOW, newValue);
884
+ },
885
+ ]);
886
+ this._parameterObservers.set(Parameter.CAST_SHADOW_FROM_LIGHTS, [
887
+ async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
888
+ await variant.commitParameterToElements(Parameter.CAST_SHADOW_FROM_LIGHTS, newValue);
889
+ },
890
+ ]);
891
+ this._parameterObservers.set(Parameter.RECEIVE_SHADOWS, [
892
+ async (variant: Variant, oldValue: Undefinable<ParameterValue>, newValue: ParameterValue) => {
893
+ await variant.commitParameterToElements(Parameter.RECEIVE_SHADOWS, newValue);
894
+ },
895
+ ]);
896
+ return this;
897
+ }
898
+
899
+ /**
900
+ * Creates {@link Element}s and clones nodes into them.
901
+ */
902
+ protected async createElements(forInstance?: string): Promise<Variant> {
903
+ this.createElementDefinitionsIfNotExist();
904
+ for (const name in this.structureJson.elements || {}) {
905
+ this.elements.push(await Element.create(this, name));
906
+ }
907
+ // inject node meta to all inherited elements
908
+ // we do this to inject the deepest and most concrete variant information to all cloned nodes in the tree
909
+ this.inheritedElements.forEach(element =>
910
+ element.nodes.forEach(node => injectNodeMetadata(node, { variant: this, variantParameterizable: element }))
911
+ );
912
+ return this;
913
+ }
914
+
915
+ /**
916
+ * Creates {@link ViewerLight}s.
917
+ */
918
+ protected async createViewerLights(): Promise<Variant> {
919
+ for (const name in this.structureJson.lights || {}) {
920
+ this.viewerLights.push(await ViewerLight.create(this, name));
921
+ }
922
+ this.inheritedViewerLights.forEach(viewerLight => {
923
+ injectNodeMetadata(viewerLight.light, { variant: this, variantParameterizable: viewerLight });
924
+ });
925
+ return this;
926
+ }
927
+
928
+ /**
929
+ * Bootstrapping for parameters. It sets the `parametersInitialized` to true for all ancestors.
930
+ */
931
+ protected async bootstrapParameters(parameters?: ParameterBag): Promise<Variant> {
932
+ await this.commitParameters(merge(cloneDeep(this.parameters), parameters));
933
+ concat(this.ancestors, this).forEach(ancestor => (ancestor.parametersInitialized = true));
934
+ return this;
935
+ }
936
+
937
+ /**
938
+ * Ensures there is at least one "Main" {@link Element} for the {@link Variant} with all "root nodes" defined in path.
939
+ */
940
+ protected createElementDefinitionsIfNotExist() {
941
+ if (this._structureJson.elements || this.inheritedNodes.length === 0) {
942
+ return;
943
+ }
944
+ this._structureJson.elements = {
945
+ Main: { paths: { include: this.inheritedNodes.map(node => node.metadata.dottedPath.path) } },
946
+ };
947
+ this.structureJson.elements = cloneDeep(this._structureJson.elements);
948
+ }
949
+ }