@combeenation/3d-viewer 3.1.1-alpha7 → 3.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 (94) 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 +1 -1
  4. package/dist/lib-cjs/api/classes/dottedPath.d.ts +79 -79
  5. package/dist/lib-cjs/api/classes/dottedPath.js +190 -190
  6. package/dist/lib-cjs/api/classes/element.d.ts +125 -126
  7. package/dist/lib-cjs/api/classes/element.js +678 -674
  8. package/dist/lib-cjs/api/classes/element.js.map +1 -1
  9. package/dist/lib-cjs/api/classes/elementParameterizable.d.ts +14 -14
  10. package/dist/lib-cjs/api/classes/elementParameterizable.js +134 -134
  11. package/dist/lib-cjs/api/classes/event.d.ts +312 -312
  12. package/dist/lib-cjs/api/classes/event.js +357 -357
  13. package/dist/lib-cjs/api/classes/eventBroadcaster.d.ts +26 -26
  14. package/dist/lib-cjs/api/classes/eventBroadcaster.js +53 -53
  15. package/dist/lib-cjs/api/classes/parameter.d.ts +165 -165
  16. package/dist/lib-cjs/api/classes/parameter.js +267 -267
  17. package/dist/lib-cjs/api/classes/parameterObservable.d.ts +36 -36
  18. package/dist/lib-cjs/api/classes/parameterObservable.js +126 -126
  19. package/dist/lib-cjs/api/classes/parameterizable.d.ts +15 -15
  20. package/dist/lib-cjs/api/classes/parameterizable.js +149 -149
  21. package/dist/lib-cjs/api/classes/placementAnimation.d.ts +38 -38
  22. package/dist/lib-cjs/api/classes/placementAnimation.js +138 -138
  23. package/dist/lib-cjs/api/classes/variant.d.ts +180 -190
  24. package/dist/lib-cjs/api/classes/variant.js +863 -873
  25. package/dist/lib-cjs/api/classes/variant.js.map +1 -1
  26. package/dist/lib-cjs/api/classes/variantInstance.d.ts +41 -41
  27. package/dist/lib-cjs/api/classes/variantInstance.js +98 -98
  28. package/dist/lib-cjs/api/classes/variantParameterizable.d.ts +17 -17
  29. package/dist/lib-cjs/api/classes/variantParameterizable.js +92 -92
  30. package/dist/lib-cjs/api/classes/viewer.d.ts +128 -128
  31. package/dist/lib-cjs/api/classes/viewer.js +486 -486
  32. package/dist/lib-cjs/api/classes/viewerLight.d.ts +65 -66
  33. package/dist/lib-cjs/api/classes/viewerLight.js +322 -389
  34. package/dist/lib-cjs/api/classes/viewerLight.js.map +1 -1
  35. package/dist/lib-cjs/api/internal/debugViewer.d.ts +13 -13
  36. package/dist/lib-cjs/api/internal/debugViewer.js +87 -87
  37. package/dist/lib-cjs/api/internal/lensRendering.d.ts +8 -8
  38. package/dist/lib-cjs/api/internal/lensRendering.js +11 -11
  39. package/dist/lib-cjs/api/internal/sceneSetup.d.ts +6 -6
  40. package/dist/lib-cjs/api/internal/sceneSetup.js +227 -227
  41. package/dist/lib-cjs/api/manager/animationManager.d.ts +29 -29
  42. package/dist/lib-cjs/api/manager/animationManager.js +121 -121
  43. package/dist/lib-cjs/api/manager/sceneManager.d.ts +32 -32
  44. package/dist/lib-cjs/api/manager/sceneManager.js +132 -132
  45. package/dist/lib-cjs/api/manager/variantInstanceManager.d.ts +90 -90
  46. package/dist/lib-cjs/api/manager/variantInstanceManager.js +321 -321
  47. package/dist/lib-cjs/api/store/specStorage.d.ts +24 -24
  48. package/dist/lib-cjs/api/store/specStorage.js +51 -51
  49. package/dist/lib-cjs/api/util/babylonHelper.d.ts +125 -125
  50. package/dist/lib-cjs/api/util/babylonHelper.js +368 -368
  51. package/dist/lib-cjs/api/util/globalTypes.d.ts +275 -275
  52. package/dist/lib-cjs/api/util/globalTypes.js +1 -1
  53. package/dist/lib-cjs/api/util/resourceHelper.d.ts +30 -30
  54. package/dist/lib-cjs/api/util/resourceHelper.js +247 -203
  55. package/dist/lib-cjs/api/util/resourceHelper.js.map +1 -1
  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/buildinfo.json +3 -3
  59. package/dist/lib-cjs/index.d.ts +46 -46
  60. package/dist/lib-cjs/index.js +82 -82
  61. package/dist/webpack-stats.json +0 -0
  62. package/package.json +83 -83
  63. package/src/api/classes/animationInterface.ts +11 -11
  64. package/src/api/classes/dottedPath.ts +189 -189
  65. package/src/api/classes/element.ts +608 -606
  66. package/src/api/classes/elementParameterizable.ts +78 -78
  67. package/src/api/classes/event.ts +355 -355
  68. package/src/api/classes/eventBroadcaster.ts +54 -54
  69. package/src/api/classes/parameter.ts +277 -277
  70. package/src/api/classes/parameterObservable.ts +121 -121
  71. package/src/api/classes/placementAnimation.ts +133 -133
  72. package/src/api/classes/variant.ts +659 -670
  73. package/src/api/classes/variantInstance.ts +81 -81
  74. package/src/api/classes/viewer.ts +421 -421
  75. package/src/api/internal/debugViewer.ts +81 -81
  76. package/src/api/internal/lensRendering.ts +10 -10
  77. package/src/api/internal/sceneSetup.ts +203 -203
  78. package/src/api/manager/animationManager.ts +116 -116
  79. package/src/api/manager/sceneManager.ts +105 -105
  80. package/src/api/manager/variantInstanceManager.ts +236 -236
  81. package/src/api/store/specStorage.ts +53 -53
  82. package/src/api/util/babylonHelper.ts +392 -392
  83. package/src/api/util/globalTypes.ts +314 -314
  84. package/src/api/util/resourceHelper.ts +168 -155
  85. package/src/buildinfo.json +2 -2
  86. package/src/commonjs.tsconfig.json +13 -13
  87. package/src/declaration.tsconfig.json +10 -10
  88. package/src/dev.ts +44 -46
  89. package/src/es6.tsconfig.json +13 -13
  90. package/src/index.ts +87 -87
  91. package/src/pagesconfig.json +47 -47
  92. package/src/tsconfig.json +43 -43
  93. package/src/tsconfig.types.json +9 -9
  94. package/src/types.d.ts +4 -4
@@ -1,670 +1,659 @@
1
- import { AssetContainer } from '@babylonjs/core/assetContainer';
2
- import '@babylonjs/core/Loading/Plugins/babylonFileLoader';
3
- import { SceneLoader } from '@babylonjs/core/Loading/sceneLoader';
4
- import { Material } from '@babylonjs/core/Materials/material';
5
- import { Mesh } from '@babylonjs/core/Meshes/mesh';
6
- import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
7
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression';
8
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_texture_transform';
9
- import '@babylonjs/loaders/glTF/2.0/glTFLoader';
10
- import { cloneDeep, concat, isEmpty, isEqual, isString, merge } from 'lodash-es';
11
- import {
12
- deactivateTransformNode,
13
- getDottedPathForTransformNode,
14
- injectTransformNodeMetadata
15
- } from '../util/babylonHelper';
16
- import { loadJson, mergeMaps } from '../util/resourceHelper';
17
- import { DottedPath } from './dottedPath';
18
- import { Element } from './element';
19
- import { ElementParameterizable } from './elementParameterizable';
20
- import { Event } from './event';
21
- import { Parameter } from './parameter';
22
- import { Viewer } from './viewer';
23
-
24
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_texture_basisu';
25
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_clearcoat';
26
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_sheen';
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_specular';
30
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_translucency';
31
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_transmission';
32
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_unlit';
33
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_variants';
34
-
35
- /**
36
- * A concrete "Variant". Most of these are handled by either the {@link Viewer} or {@link VariantInstance}.
37
- */
38
- export class Variant extends ElementParameterizable {
39
-
40
- public assetContainer: AssetContainer;
41
-
42
- public readonly elements: Element[] = [];
43
-
44
- public structureJson: StructureJson;
45
-
46
- protected _dottedNodes: Map<DottedPath, TransformNode> | undefined;
47
-
48
- protected readonly _children: Map<string, Variant> = new Map();
49
-
50
- protected readonly _parameterObservers: Map<string, ParameterObserver[]> = new Map();
51
-
52
- /**
53
- * @internal
54
- */
55
- private parametersInitialized: boolean = false;
56
-
57
- /**
58
- * Constructor.
59
- */
60
- protected constructor( public readonly name: string,
61
- protected readonly _structureJson: StructureJson,
62
- public readonly viewer: Viewer,
63
- public readonly parent?: Variant ) {
64
- super(
65
- cloneDeep( _structureJson.parameterDeclaration ),
66
- cloneDeep( _structureJson.parameters )
67
- );
68
- for( const parameter in _structureJson.parameterDeclaration ) {
69
- if( !(parameter in (_structureJson.parameters || [])) ) {
70
- throw new Error( `No default value for parameter "${parameter}" defined.` );
71
- }
72
- }
73
- this.assetContainer = new AssetContainer( viewer.scene );
74
- this.structureJson = cloneDeep( _structureJson );
75
- }
76
-
77
- /**
78
- * Creates a {@link Variant} based on given parameters.
79
- *
80
- * @throws Error if "gltf" property is provided without a filename
81
- */
82
- public static async create( name: string,
83
- structureJson: StructureJson,
84
- viewer: Viewer,
85
- parent?: Variant ): Promise<Variant> {
86
- const variant = new Variant( name, structureJson, viewer, parent );
87
- await variant.loadAssets();
88
- return variant;
89
- }
90
-
91
- /**
92
- * The ancestor {@link Variant}s ordered from top to bottom in the built tree.
93
- */
94
- get ancestors(): Variant[] {
95
- const ancestors = [];
96
- let variant: Variant = this;
97
- while( variant.parent ) {
98
- ancestors.unshift( variant.parent );
99
- variant = variant.parent;
100
- }
101
- return ancestors;
102
- }
103
-
104
- /**
105
- * The root {@link Variant}.
106
- */
107
- get root(): Variant {
108
- return this.ancestors[0] ?? this;
109
- }
110
-
111
- /**
112
- * The {@link DottedPath} in the built tree of {@link Variant}s.
113
- * E.g. "_.top-1.sub-2.sub-sub-3"
114
- */
115
- get dottedPath(): DottedPath {
116
- const parentIds = this.ancestors.map( ancestor => {
117
- return ancestor.name;
118
- } );
119
- return DottedPath.createFromParts( parentIds ).addPart( this.name );
120
- }
121
-
122
- /**
123
- * The id representing a {@link DottedPath}.
124
- */
125
- get id(): string {
126
- const dottedPath = DottedPath.create( this.dottedPath );
127
- dottedPath.shiftPart(); // remove root
128
- return dottedPath.path || '_';
129
- }
130
-
131
- /**
132
- * The defined glTF Asset.
133
- */
134
- get glTF(): Asset | undefined {
135
- if( this.structureJson.glTF ) {
136
- if( isString( this.structureJson.glTF ) ) {
137
- return {
138
- rootUrl: this.structureJson.glTF,
139
- fileName: undefined
140
- };
141
- }
142
- if( isEmpty( this.structureJson.glTF.rootUrl ) ) {
143
- throw new Error( `No "rootUrl" defined in "glTF" definition for variant "${this.id}".` );
144
- }
145
- return this.structureJson.glTF;
146
- }
147
- }
148
-
149
- /**
150
- * The defined glTF URI.
151
- */
152
- get glTFUri(): string | undefined {
153
- if( this.glTF ) {
154
- return [this.glTF.rootUrl, this.glTF.fileName].join( '' );
155
- }
156
- }
157
-
158
- /**
159
- * The inherited defined glTF URI.
160
- */
161
- get inheritedGlTFUri(): string | undefined {
162
- if( !this.glTFUri && this.parent ) {
163
- return this.parent.inheritedGlTFUri;
164
- }
165
- return this.glTFUri;
166
- }
167
-
168
- /**
169
- * The TransformNodes of the {@link Variant}.
170
- */
171
- get nodes(): TransformNode[] {
172
- const rootNodes = this.assetContainer.getNodes().filter(
173
- n => n instanceof TransformNode && !n.parent
174
- ) as TransformNode[];
175
- return rootNodes;
176
- }
177
-
178
- /**
179
- * All TransformNodes of the {@link Variant} mapped flat with a {@link DottedPath}.
180
- */
181
- get dottedNodes(): Map<DottedPath, TransformNode> {
182
- if( ! this._dottedNodes ) {
183
- const nodes = this.assetContainer.getNodes().filter( n => n instanceof TransformNode );
184
- const dottedNodes = new Map();
185
- nodes.forEach( node => {
186
- dottedNodes.set( node.metadata.dottedPath, node );
187
- } );
188
- this._dottedNodes = dottedNodes;
189
- }
190
- return this._dottedNodes;
191
- }
192
-
193
- /**
194
- * The Materials of the {@link Variant}.
195
- */
196
- get materials(): Material[] {
197
- return this.assetContainer.materials;
198
- }
199
-
200
- /**
201
- * All {@link Element}s from this {@link Variant}'s parents.
202
- */
203
- get inheritedElements(): Element[] {
204
- let elements: Element[] = [];
205
- this.ancestors.forEach( ancestor => {
206
- elements = concat( elements, ancestor.elements );
207
- } );
208
- return concat( elements, this.elements );
209
- }
210
-
211
- /**
212
- * All TransformNodes inherited from this {@link Variant}'s parents.
213
- */
214
- get inheritedNodes(): TransformNode[] {
215
- let nodes: TransformNode[] = [];
216
- this.ancestors.forEach( ancestor => {
217
- nodes = concat( nodes, ancestor.nodes );
218
- } );
219
- return concat( nodes, this.nodes );
220
- }
221
-
222
- /**
223
- * All TransformNodes inherited from this {@link Variant}'s parents mapped flat with a {@link DottedPath}.
224
- */
225
- get inheritedDottedNodes(): Map<DottedPath, TransformNode> {
226
- let dottedNodes = this.dottedNodes;
227
- this.ancestors.forEach( ancestor => {
228
- dottedNodes = mergeMaps( dottedNodes, ancestor.dottedNodes );
229
- } );
230
- return dottedNodes;
231
- }
232
-
233
- /**
234
- * The {@link ParameterDeclarations} inherited from this {@link Variant}'s parents.
235
- */
236
- get inheritedParameterDeclaration(): ParameterDeclarations {
237
- let declaration = {};
238
- this.ancestors.forEach( ancestor => {
239
- merge( declaration, ancestor.parameterDeclaration );
240
- } );
241
- return merge( declaration, this.parameterDeclaration );
242
- }
243
-
244
- /**
245
- * The {@link ParameterBag} inherited from this {@link Variant}'s parents.
246
- */
247
- get inheritedParameters(): ParameterBag {
248
- let parameters = {};
249
- this.ancestors.forEach( ancestor => {
250
- merge( parameters, ancestor.parameters );
251
- } );
252
- return merge( parameters, this.parameters );
253
- }
254
-
255
- /**
256
- * All Materials from this {@link Variant}'s parents.
257
- */
258
- get inheritedMaterials(): Material[] {
259
- let materials: Material[] = [];
260
- this.ancestors.forEach( ancestor => {
261
- materials = concat( materials, ancestor.materials );
262
- } );
263
- return concat( materials, this.materials );
264
- }
265
-
266
- /**
267
- * Gets the direct children of the current {@link Variant}.
268
- */
269
- public async getChildren(): Promise<Variant[]> {
270
- const children: Variant[] = [];
271
- for( const name in this.structureJson.variants ) {
272
- children.push( await this.getDescendant( name ) );
273
- }
274
- return children;
275
- }
276
-
277
- /**
278
- * Gets a descendant {@link Variant} of the current {@link Variant} relative to its {@link DottedPath}.
279
- * If you have the dotted path `_.product_x.variant_blue.with_yellow_highlight` in a tree and you operate on the
280
- * `product_x`, you can call `this.getDescendant('variant_blue.with_yellow_highlight')` to get the lowermost
281
- * {@link Variant}.
282
- */
283
- public async getDescendant( dottedPath: DottedPathArgument ): Promise<Variant> {
284
- const _dottedPath = DottedPath.create( dottedPath );
285
- const [name, ...descendantParts] = _dottedPath.parts;
286
- let variant;
287
- if( this._children.has( name ) ) {
288
- variant = this._children.get( name );
289
- } else {
290
- if( !this.structureJson.variants ) {
291
- throw new Error( `Missing key "variants" in JSON structure for variant "${this.id}".` );
292
- }
293
- if( !this.structureJson.variants[name] ) {
294
- throw new Error( `Variant "${_dottedPath.path}" not defined in JSON structure for variant "${this.id}".` );
295
- }
296
- if( this.structureJson.variants[name].file ) {
297
- const file = this.structureJson.variants[name].file as string;
298
- variant = await Variant.create( name, await loadJson<StructureJson>( file ), this.viewer, this );
299
- } else {
300
- variant = await Variant.create( name, this.structureJson.variants[name], this.viewer, this );
301
- }
302
- this._children.set( name, variant );
303
- }
304
- if( !variant ) {
305
- throw new Error( `Variant "${_dottedPath.path}" was not created.` );
306
- }
307
- if( descendantParts.length > 0 ) {
308
- return await variant.getDescendant( DottedPath.createFromParts( descendantParts ) );
309
- }
310
- return variant;
311
- }
312
-
313
- /**
314
- * Gets the desired {@link Element} of the current {@link Variant} relative to its {@link DottedPath}.
315
- * Uses the mechanism of {@link getDescendant} to resolve the appropriate variant in tree.
316
- */
317
- public async getElement( dottedPath: DottedPathArgument ): Promise<Element> {
318
- const _dottedPath = DottedPath.create( dottedPath );
319
- const elementName = _dottedPath.popPart();
320
- let variant: Variant = this;
321
- if( _dottedPath.parts.length > 0 ) {
322
- variant = await this.getDescendant( _dottedPath );
323
- }
324
- if( variant.inheritedElements.length === 0 ) {
325
- throw new Error( `No elements for variant "${variant.id}" found. ` +
326
- `Either none are defined or they are not initialized (are you operating on the appropriate living?).` );
327
- }
328
- let element;
329
- variant.inheritedElements.forEach( _element => {
330
- if( _element.name === elementName ) {
331
- element = _element;
332
- }
333
- } );
334
- if( !element ) {
335
- throw new Error( `Element with name "${elementName}" does not exist for variant "${variant.id}".` );
336
- }
337
- return element;
338
- }
339
-
340
- /**
341
- * A proxy for directly getting a Node from an {@link Element} by its {@link DottedPath}s.
342
- */
343
- public async getNode( elementDottedPath: DottedPathArgument, nodeDottedPath: DottedPathArgument ): Promise<TransformNode> {
344
- const element = await this.getElement( elementDottedPath );
345
- return element.getNode( nodeDottedPath );
346
- }
347
-
348
- /**
349
- * A proxy for directly getting a Mesh from an {@link Element} by its {@link DottedPath}s.
350
- */
351
- public async getMesh( elementDottedPath: DottedPathArgument, meshDottedPath: DottedPathArgument ): Promise<Mesh|null> {
352
- const element = await this.getElement( elementDottedPath );
353
- return element.getMesh( meshDottedPath );
354
- }
355
-
356
- /**
357
- * Gets the Material defined in one of the variants glTFs by its id.
358
- */
359
- public getMaterial( id: string ): Material {
360
- for( const material of this.inheritedMaterials ) {
361
- if( material.id === id ) {
362
- return material;
363
- }
364
- }
365
- // fallback to dynamically created materials on scene
366
- for( const material of this.viewer.scene.materials ) {
367
- if( material.id === id ) {
368
- return material;
369
- }
370
- }
371
- throw new Error( `Material with id "${id}" does not exist for variant "${this.id}".` );
372
- }
373
-
374
- /**
375
- * Creates a living clone of this {@link Variant}. Will clone all parent {@link Variant}s in tree.
376
- *
377
- * @emit {@link Event.VARIANT_CREATED}
378
- * @ignore
379
- */
380
- public async createLiving( parameters?: ParameterBag ): Promise<Variant> {
381
- const parent = await this.parent?.createLiving();
382
- const variant = new Variant( this.name, this._structureJson, this.viewer, parent );
383
- parent?._children.set( variant.name, variant );
384
- variant.assetContainer = this.assetContainer;
385
- variant.parameterObservers = cloneDeep( this.parameterObservers );
386
- variant.createElements();
387
- variant.addParameterObservers();
388
- await variant.bootstrapParameters( parameters );
389
- this.broadcastEvent( Event.VARIANT_CREATED, variant );
390
- return variant;
391
- }
392
-
393
- /**
394
- * Destroys this {@link Variant}, all parents and destroy the {@link Element}s.
395
- */
396
- public destroy(): Variant {
397
- this.elements.forEach( element => element.destroy() );
398
- if( this.parent ) {
399
- this.parent.destroy();
400
- }
401
- this.broadcastEvent( Event.VARIANT_DESTROYED, this );
402
- return this;
403
- }
404
-
405
- /**
406
- * Places the given {@link ParameterBag} in the {@link Variant}'s parameters, replaces all patterns in the
407
- * {@link StructureJson}, broadcasts all {@link ParameterObserver}s and delegates them to its {@link Element}s.
408
- *
409
- * @emit {@link Event.VARIANT_PARAMETER_COMMITTED}
410
- */
411
- public async commitParameters( parameters?: ParameterBag ): Promise<Variant> {
412
- parameters = merge( {}, parameters );
413
-
414
- // remember old parameter values for later comparison
415
- const oldParameters = cloneDeep( this.inheritedParameters );
416
-
417
- // replace patterns in given parameters
418
- let _parameters = JSON.stringify( parameters );
419
- for( const parameter in this.inheritedParameters ) {
420
- const value = this.inheritedParameters[parameter];
421
- const search = new RegExp( `\\$\\{${parameter}\\}`, 'g' );
422
- _parameters = _parameters.replace( search, value.toString() );
423
- }
424
- merge( parameters, JSON.parse( _parameters ) );
425
-
426
- // merge inherited parameters and replaced given parameters
427
- const mergedParameters = merge( {}, this.inheritedParameters, parameters );
428
-
429
- // replace patterns in structure parameters
430
- const structureParameters = this._structureJson.parameters || {};
431
- let _structureParameters = JSON.stringify( structureParameters );
432
- for( const parameter in mergedParameters ) {
433
- const value = mergedParameters[parameter];
434
- const search = new RegExp( `\\$\\{${parameter}\\}`, 'g' );
435
- _structureParameters = _structureParameters.replace( search, value.toString() );
436
- }
437
- const replacedStructureParameters = JSON.parse( _structureParameters );
438
-
439
- // calculate which replaced structure parameters have changed and should overload given parameters
440
- const differentStructureParameters: ParameterBag = {};
441
- for( const parameter in replacedStructureParameters ) {
442
- if( !isEqual(structureParameters[parameter], replacedStructureParameters[parameter]) ) {
443
- differentStructureParameters[parameter] = replacedStructureParameters[parameter];
444
- }
445
- }
446
-
447
- // merge replaced structure parameters and given inherited parameters to structure parameters
448
- merge( this.parameters, mergedParameters, differentStructureParameters );
449
-
450
- // inherited parameters are now the new parameters to process
451
- const newParameters = this.inheritedParameters;
452
-
453
- // replace all parameter patterns in structure json
454
- let structure = JSON.stringify( this._structureJson );
455
- for( const parameter in newParameters ) {
456
- const value = newParameters[parameter];
457
- const search = new RegExp( `\\$\\{${parameter}\\}`, 'g' );
458
- structure = structure.replace( search, value.toString() );
459
- }
460
- this.structureJson = JSON.parse( structure );
461
-
462
- // handle parameter observers
463
- let observerPromises: Promise<void | ParameterObserver>[] = [];
464
- for( const parameter in newParameters ) {
465
- const oldParameterValue = oldParameters[parameter];
466
- const newParameterValue = newParameters[parameter];
467
- this.assertParameter( this.inheritedParameterDeclaration, parameter, newParameterValue );
468
- if( oldParameterValue === newParameterValue && this.parametersInitialized ) {
469
- continue;
470
- }
471
- // parameter changed
472
- const parameterObservers = mergeMaps( this._parameterObservers, this.parameterObservers );
473
- if( parameterObservers.has( parameter ) ) {
474
- const observers = parameterObservers.get( parameter )!;
475
- observerPromises = concat(observerPromises, observers.map( observer => {
476
- const observerResult = observer( this, oldParameterValue, newParameterValue );
477
- return Promise.resolve( observerResult ).then( () => {
478
- if( this.parametersInitialized ) {
479
- this.broadcastEvent( Event.VARIANT_PARAMETER_COMMITTED,
480
- this, parameter, oldParameterValue, newParameterValue );
481
- }
482
- } );
483
- } ) );
484
- }
485
- }
486
- await Promise.all( observerPromises );
487
-
488
- // broadcast that bag has been committed
489
- this.broadcastEvent(Event.VARIANT_PARAMETER_BAG_COMMITTED, this, oldParameters, newParameters);
490
-
491
- // commit parameters to elements
492
- const elementPromises: Promise<Element>[] = this.elements.map( element => {
493
- let _elementDefinition = JSON.stringify( this._structureJson.elements![element.name] );
494
- const elementParameters: ParameterBag = {};
495
- for( const parameter in newParameters ) {
496
- if( DottedPath.create( parameter ).firstPart !== element.name ) {
497
- continue;
498
- }
499
- // we got an element parameter
500
- let newParameterValue = newParameters[parameter];
501
- const elementParameter = parameter.replace( `${element.name}.`, '' );
502
- // If the variant is explicitly hidden, we must not override the visibility with element parameters. We need
503
- // an exception for visibility to avoid overloading already applied element parameters with element parameters
504
- // defined in the variant spec ("dotted parameters").
505
- // @see https://github.com/Combeenation/3d-viewer/issues/44
506
- if( elementParameter === Parameter.VISIBLE && newParameters[Parameter.VISIBLE] === false ) {
507
- newParameterValue = false;
508
- }
509
- elementParameters[elementParameter] = newParameterValue;
510
- const search = new RegExp( `\\$\\{${elementParameter}\\}`, 'g' );
511
- _elementDefinition = _elementDefinition.replace( search, newParameterValue.toString() );
512
- }
513
- this.structureJson.elements![this.name] = JSON.parse( _elementDefinition );
514
- return element.commitParameters( elementParameters );
515
- } );
516
- await Promise.all( elementPromises );
517
-
518
- // propagate parameters to parent
519
- if( this.parent ) {
520
- await this.parent.commitParameters( this.parameters );
521
- }
522
-
523
- return this;
524
- }
525
-
526
- /**
527
- * Adds an observer function for camera matrix changes for given `dottedPath` representing the {@link Element}
528
- * and the `traceable`. The `observer` gets 2 parameters: the `AbstractMesh` and a `ClientRect` object.
529
- */
530
- public async addTraceableObserver( dottedPath: DottedPathArgument,
531
- observer: CallableFunction,
532
- payload?: any ): Promise<Element> {
533
- const _dottedPath = DottedPath.create( dottedPath );
534
- const traceableName = _dottedPath.popPart();
535
- if( !traceableName ) {
536
- throw new Error( `The dottedPath must consist of the element and the name of the defined corresponding ` +
537
- `traceable ("${_dottedPath.path}" given).` );
538
- }
539
- const element = await this.getElement( _dottedPath );
540
- return element.addTraceableObserver( traceableName, observer, payload );
541
- }
542
-
543
- /**
544
- * Loads {@link glTFUri} with assets, adds them to the {@link Variant}'s `assetContainer` and deactivates the meshes.
545
- * (for further processing).
546
- * @emits {@link Event.ASSET_LOADING_START}
547
- * @emits {@link Event.ASSET_LOADING_END}
548
- */
549
- protected async loadAssets(): Promise<Variant> {
550
- this.broadcastEvent( Event.ASSET_LOADING_START, this );
551
- return new Promise( resolve => {
552
- if( !this.structureJson ) {
553
- this.broadcastEvent( Event.ASSET_LOADING_END, this );
554
- return resolve( this );
555
- }
556
- if( !this.glTF ) {
557
- this.broadcastEvent( Event.ASSET_LOADING_END, this );
558
- return resolve( this );
559
- }
560
- SceneLoader.LoadAssetContainerAsync( this.glTF.rootUrl, this.glTF.fileName, this.viewer.scene ).then( container => {
561
- this.assetContainer = container;
562
- const nodes = this.assetContainer.getNodes().filter( n => n instanceof TransformNode ) as TransformNode[];
563
- nodes.forEach( node => {
564
- deactivateTransformNode( node, false );
565
- injectTransformNodeMetadata( node, { dottedPath: getDottedPathForTransformNode( node ) }, false );
566
- } );
567
- this.broadcastEvent( Event.ASSET_LOADING_END, this );
568
- resolve( this );
569
- } ).catch( reason => {
570
- this.broadcastEvent( Event.ASSET_LOADING_END, this );
571
- throw new Error( `Error loading assets for variant "${this.id}": ${reason}.` );
572
- } );
573
- } );
574
- }
575
-
576
- /**
577
- * Commits given {@link Parameter} to the {@link Variant}'s {@link Element}s.
578
- */
579
- protected async commitParameterToElements( parameter: string, value: ParameterValue ) {
580
- const promises = [];
581
- for( const element of this.elements ) {
582
- promises.push( element.commitParameter( parameter, value ) );
583
- }
584
- await Promise.all( promises );
585
- }
586
-
587
- /**
588
- * Adds the default {@link ParameterObserver}s which are called every time {@link commitParameters} is called.
589
- */
590
- protected addParameterObservers(): Variant {
591
- this._parameterObservers.set( Parameter.VISIBLE, [
592
- async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
593
- await variant.commitParameterToElements( Parameter.VISIBLE, newValue );
594
- }
595
- ] );
596
- this._parameterObservers.set( Parameter.SCALING, [
597
- async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
598
- await variant.commitParameterToElements( Parameter.SCALING, newValue );
599
- }
600
- ] );
601
- this._parameterObservers.set( Parameter.MATERIAL, [
602
- async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
603
- await variant.commitParameterToElements( Parameter.MATERIAL, newValue );
604
- }
605
- ] );
606
- this._parameterObservers.set( Parameter.MATERIAL_COLOR, [
607
- async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
608
- await variant.commitParameterToElements( Parameter.MATERIAL_COLOR, newValue );
609
- }
610
- ] );
611
- this._parameterObservers.set( Parameter.MATERIAL_METALLNESS, [
612
- async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
613
- await variant.commitParameterToElements( Parameter.MATERIAL_METALLNESS, newValue );
614
- }
615
- ] );
616
- this._parameterObservers.set( Parameter.MATERIAL_ROUGHNESS, [
617
- async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
618
- await variant.commitParameterToElements( Parameter.MATERIAL_ROUGHNESS, newValue );
619
- }
620
- ] );
621
- this._parameterObservers.set( Parameter.HIGHLIGHT_COLOR, [
622
- async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
623
- await variant.commitParameterToElements( Parameter.HIGHLIGHT_COLOR, newValue );
624
- }
625
- ] );
626
- this._parameterObservers.set( Parameter.HIGHLIGHTED, [
627
- async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
628
- await variant.commitParameterToElements( Parameter.HIGHLIGHTED, newValue );
629
- }
630
- ] );
631
- this._parameterObservers.set( Parameter.POSITION, [
632
- async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
633
- await variant.commitParameterToElements( Parameter.POSITION, newValue );
634
- }
635
- ] );
636
- this._parameterObservers.set( Parameter.ROTATION, [
637
- async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
638
- await variant.commitParameterToElements( Parameter.ROTATION, newValue );
639
- }
640
- ] );
641
- return this;
642
- }
643
-
644
- /**
645
- * Creates {@link Element}s and clones nodes into them.
646
- */
647
- protected createElements(): Variant {
648
- for( const elementName in this.structureJson.elements || {} ) {
649
- this.elements.push( new Element( this, elementName ) );
650
- }
651
- // inject node meta to all inherited elements
652
- // we do this to inject the deepest and most concrete variant information to all cloned nodes in the tree
653
- this.inheritedElements.forEach( element => {
654
- element.nodes.forEach( node => {
655
- injectTransformNodeMetadata( node, { variant: this, element: element } );
656
- } );
657
- } );
658
- return this;
659
- }
660
-
661
- /**
662
- * Bootstrapping for parameters. It sets the `parametersInitialized` to true for all ancestors.
663
- */
664
- protected async bootstrapParameters( parameters?: ParameterBag ): Promise<Variant> {
665
- await this.commitParameters( merge( cloneDeep( this.parameters ), parameters ) );
666
- concat( this.ancestors, this ).forEach( ancestor => ancestor.parametersInitialized = true );
667
- return this;
668
- }
669
-
670
- }
1
+ import { AssetContainer } from '@babylonjs/core/assetContainer';
2
+ import '@babylonjs/core/Loading/Plugins/babylonFileLoader';
3
+ import { SceneLoader } from '@babylonjs/core/Loading/sceneLoader';
4
+ import { Material } from '@babylonjs/core/Materials/material';
5
+ import { Mesh } from '@babylonjs/core/Meshes/mesh';
6
+ import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
7
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression';
8
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_texture_transform';
9
+ import '@babylonjs/loaders/glTF/2.0/glTFLoader';
10
+ import { cloneDeep, concat, isEmpty, isEqual, isString, merge } from 'lodash-es';
11
+ import {
12
+ deactivateTransformNode,
13
+ getDottedPathForTransformNode,
14
+ injectTransformNodeMetadata
15
+ } from '../util/babylonHelper';
16
+ import { loadJson, mergeMaps } from '../util/resourceHelper';
17
+ import { DottedPath } from './dottedPath';
18
+ import { Element } from './element';
19
+ import { ElementParameterizable } from './elementParameterizable';
20
+ import { Event } from './event';
21
+ import { Parameter } from './parameter';
22
+ import { Viewer } from './viewer';
23
+
24
+ /**
25
+ * A concrete "Variant". Most of these are handled by either the {@link Viewer} or {@link VariantInstance}.
26
+ */
27
+ export class Variant extends ElementParameterizable {
28
+
29
+ public assetContainer: AssetContainer;
30
+
31
+ public readonly elements: Element[] = [];
32
+
33
+ public structureJson: StructureJson;
34
+
35
+ protected _dottedNodes: Map<DottedPath, TransformNode> | undefined;
36
+
37
+ protected readonly _children: Map<string, Variant> = new Map();
38
+
39
+ protected readonly _parameterObservers: Map<string, ParameterObserver[]> = new Map();
40
+
41
+ /**
42
+ * @internal
43
+ */
44
+ private parametersInitialized: boolean = false;
45
+
46
+ /**
47
+ * Constructor.
48
+ */
49
+ protected constructor( public readonly name: string,
50
+ protected readonly _structureJson: StructureJson,
51
+ public readonly viewer: Viewer,
52
+ public readonly parent?: Variant ) {
53
+ super(
54
+ cloneDeep( _structureJson.parameterDeclaration ),
55
+ cloneDeep( _structureJson.parameters )
56
+ );
57
+ for( const parameter in _structureJson.parameterDeclaration ) {
58
+ if( !(parameter in (_structureJson.parameters || [])) ) {
59
+ throw new Error( `No default value for parameter "${parameter}" defined.` );
60
+ }
61
+ }
62
+ this.assetContainer = new AssetContainer( viewer.scene );
63
+ this.structureJson = cloneDeep( _structureJson );
64
+ }
65
+
66
+ /**
67
+ * Creates a {@link Variant} based on given parameters.
68
+ *
69
+ * @throws Error if "gltf" property is provided without a filename
70
+ */
71
+ public static async create( name: string,
72
+ structureJson: StructureJson,
73
+ viewer: Viewer,
74
+ parent?: Variant ): Promise<Variant> {
75
+ const variant = new Variant( name, structureJson, viewer, parent );
76
+ await variant.loadAssets();
77
+ return variant;
78
+ }
79
+
80
+ /**
81
+ * The ancestor {@link Variant}s ordered from top to bottom in the built tree.
82
+ */
83
+ get ancestors(): Variant[] {
84
+ const ancestors = [];
85
+ let variant: Variant = this;
86
+ while( variant.parent ) {
87
+ ancestors.unshift( variant.parent );
88
+ variant = variant.parent;
89
+ }
90
+ return ancestors;
91
+ }
92
+
93
+ /**
94
+ * The root {@link Variant}.
95
+ */
96
+ get root(): Variant {
97
+ return this.ancestors[0] ?? this;
98
+ }
99
+
100
+ /**
101
+ * The {@link DottedPath} in the built tree of {@link Variant}s.
102
+ * E.g. "_.top-1.sub-2.sub-sub-3"
103
+ */
104
+ get dottedPath(): DottedPath {
105
+ const parentIds = this.ancestors.map( ancestor => {
106
+ return ancestor.name;
107
+ } );
108
+ return DottedPath.createFromParts( parentIds ).addPart( this.name );
109
+ }
110
+
111
+ /**
112
+ * The id representing a {@link DottedPath}.
113
+ */
114
+ get id(): string {
115
+ const dottedPath = DottedPath.create( this.dottedPath );
116
+ dottedPath.shiftPart(); // remove root
117
+ return dottedPath.path || '_';
118
+ }
119
+
120
+ /**
121
+ * The defined glTF Asset.
122
+ */
123
+ get glTF(): Asset | undefined {
124
+ if( this.structureJson.glTF ) {
125
+ if( isString( this.structureJson.glTF ) ) {
126
+ return {
127
+ rootUrl: this.structureJson.glTF,
128
+ fileName: undefined
129
+ };
130
+ }
131
+ if( isEmpty( this.structureJson.glTF.rootUrl ) ) {
132
+ throw new Error( `No "rootUrl" defined in "glTF" definition for variant "${this.id}".` );
133
+ }
134
+ return this.structureJson.glTF;
135
+ }
136
+ }
137
+
138
+ /**
139
+ * The defined glTF URI.
140
+ */
141
+ get glTFUri(): string | undefined {
142
+ if( this.glTF ) {
143
+ return [this.glTF.rootUrl, this.glTF.fileName].join( '' );
144
+ }
145
+ }
146
+
147
+ /**
148
+ * The inherited defined glTF URI.
149
+ */
150
+ get inheritedGlTFUri(): string | undefined {
151
+ if( !this.glTFUri && this.parent ) {
152
+ return this.parent.inheritedGlTFUri;
153
+ }
154
+ return this.glTFUri;
155
+ }
156
+
157
+ /**
158
+ * The TransformNodes of the {@link Variant}.
159
+ */
160
+ get nodes(): TransformNode[] {
161
+ const rootNodes = this.assetContainer.getNodes().filter(
162
+ n => n instanceof TransformNode && !n.parent
163
+ ) as TransformNode[];
164
+ return rootNodes;
165
+ }
166
+
167
+ /**
168
+ * All TransformNodes of the {@link Variant} mapped flat with a {@link DottedPath}.
169
+ */
170
+ get dottedNodes(): Map<DottedPath, TransformNode> {
171
+ if( ! this._dottedNodes ) {
172
+ const nodes = this.assetContainer.getNodes().filter( n => n instanceof TransformNode );
173
+ const dottedNodes = new Map();
174
+ nodes.forEach( node => {
175
+ dottedNodes.set( node.metadata.dottedPath, node );
176
+ } );
177
+ this._dottedNodes = dottedNodes;
178
+ }
179
+ return this._dottedNodes;
180
+ }
181
+
182
+ /**
183
+ * The Materials of the {@link Variant}.
184
+ */
185
+ get materials(): Material[] {
186
+ return this.assetContainer.materials;
187
+ }
188
+
189
+ /**
190
+ * All {@link Element}s from this {@link Variant}'s parents.
191
+ */
192
+ get inheritedElements(): Element[] {
193
+ let elements: Element[] = [];
194
+ this.ancestors.forEach( ancestor => {
195
+ elements = concat( elements, ancestor.elements );
196
+ } );
197
+ return concat( elements, this.elements );
198
+ }
199
+
200
+ /**
201
+ * All TransformNodes inherited from this {@link Variant}'s parents.
202
+ */
203
+ get inheritedNodes(): TransformNode[] {
204
+ let nodes: TransformNode[] = [];
205
+ this.ancestors.forEach( ancestor => {
206
+ nodes = concat( nodes, ancestor.nodes );
207
+ } );
208
+ return concat( nodes, this.nodes );
209
+ }
210
+
211
+ /**
212
+ * All TransformNodes inherited from this {@link Variant}'s parents mapped flat with a {@link DottedPath}.
213
+ */
214
+ get inheritedDottedNodes(): Map<DottedPath, TransformNode> {
215
+ let dottedNodes = this.dottedNodes;
216
+ this.ancestors.forEach( ancestor => {
217
+ dottedNodes = mergeMaps( dottedNodes, ancestor.dottedNodes );
218
+ } );
219
+ return dottedNodes;
220
+ }
221
+
222
+ /**
223
+ * The {@link ParameterDeclarations} inherited from this {@link Variant}'s parents.
224
+ */
225
+ get inheritedParameterDeclaration(): ParameterDeclarations {
226
+ let declaration = {};
227
+ this.ancestors.forEach( ancestor => {
228
+ merge( declaration, ancestor.parameterDeclaration );
229
+ } );
230
+ return merge( declaration, this.parameterDeclaration );
231
+ }
232
+
233
+ /**
234
+ * The {@link ParameterBag} inherited from this {@link Variant}'s parents.
235
+ */
236
+ get inheritedParameters(): ParameterBag {
237
+ let parameters = {};
238
+ this.ancestors.forEach( ancestor => {
239
+ merge( parameters, ancestor.parameters );
240
+ } );
241
+ return merge( parameters, this.parameters );
242
+ }
243
+
244
+ /**
245
+ * All Materials from this {@link Variant}'s parents.
246
+ */
247
+ get inheritedMaterials(): Material[] {
248
+ let materials: Material[] = [];
249
+ this.ancestors.forEach( ancestor => {
250
+ materials = concat( materials, ancestor.materials );
251
+ } );
252
+ return concat( materials, this.materials );
253
+ }
254
+
255
+ /**
256
+ * Gets the direct children of the current {@link Variant}.
257
+ */
258
+ public async getChildren(): Promise<Variant[]> {
259
+ const children: Variant[] = [];
260
+ for( const name in this.structureJson.variants ) {
261
+ children.push( await this.getDescendant( name ) );
262
+ }
263
+ return children;
264
+ }
265
+
266
+ /**
267
+ * Gets a descendant {@link Variant} of the current {@link Variant} relative to its {@link DottedPath}.
268
+ * If you have the dotted path `_.product_x.variant_blue.with_yellow_highlight` in a tree and you operate on the
269
+ * `product_x`, you can call `this.getDescendant('variant_blue.with_yellow_highlight')` to get the lowermost
270
+ * {@link Variant}.
271
+ */
272
+ public async getDescendant( dottedPath: DottedPathArgument ): Promise<Variant> {
273
+ const _dottedPath = DottedPath.create( dottedPath );
274
+ const [name, ...descendantParts] = _dottedPath.parts;
275
+ let variant;
276
+ if( this._children.has( name ) ) {
277
+ variant = this._children.get( name );
278
+ } else {
279
+ if( !this.structureJson.variants ) {
280
+ throw new Error( `Missing key "variants" in JSON structure for variant "${this.id}".` );
281
+ }
282
+ if( !this.structureJson.variants[name] ) {
283
+ throw new Error( `Variant "${_dottedPath.path}" not defined in JSON structure for variant "${this.id}".` );
284
+ }
285
+ if( this.structureJson.variants[name].file ) {
286
+ const file = this.structureJson.variants[name].file as string;
287
+ variant = await Variant.create( name, await loadJson<StructureJson>( file ), this.viewer, this );
288
+ } else {
289
+ variant = await Variant.create( name, this.structureJson.variants[name], this.viewer, this );
290
+ }
291
+ this._children.set( name, variant );
292
+ }
293
+ if( !variant ) {
294
+ throw new Error( `Variant "${_dottedPath.path}" was not created.` );
295
+ }
296
+ if( descendantParts.length > 0 ) {
297
+ return await variant.getDescendant( DottedPath.createFromParts( descendantParts ) );
298
+ }
299
+ return variant;
300
+ }
301
+
302
+ /**
303
+ * Gets the desired {@link Element} of the current {@link Variant} relative to its {@link DottedPath}.
304
+ * Uses the mechanism of {@link getDescendant} to resolve the appropriate variant in tree.
305
+ */
306
+ public async getElement( dottedPath: DottedPathArgument ): Promise<Element> {
307
+ const _dottedPath = DottedPath.create( dottedPath );
308
+ const elementName = _dottedPath.popPart();
309
+ let variant: Variant = this;
310
+ if( _dottedPath.parts.length > 0 ) {
311
+ variant = await this.getDescendant( _dottedPath );
312
+ }
313
+ if( variant.inheritedElements.length === 0 ) {
314
+ throw new Error( `No elements for variant "${variant.id}" found. ` +
315
+ `Either none are defined or they are not initialized (are you operating on the appropriate living?).` );
316
+ }
317
+ let element;
318
+ variant.inheritedElements.forEach( _element => {
319
+ if( _element.name === elementName ) {
320
+ element = _element;
321
+ }
322
+ } );
323
+ if( !element ) {
324
+ throw new Error( `Element with name "${elementName}" does not exist for variant "${variant.id}".` );
325
+ }
326
+ return element;
327
+ }
328
+
329
+ /**
330
+ * A proxy for directly getting a Node from an {@link Element} by its {@link DottedPath}s.
331
+ */
332
+ public async getNode( elementDottedPath: DottedPathArgument, nodeDottedPath: DottedPathArgument ): Promise<TransformNode> {
333
+ const element = await this.getElement( elementDottedPath );
334
+ return element.getNode( nodeDottedPath );
335
+ }
336
+
337
+ /**
338
+ * A proxy for directly getting a Mesh from an {@link Element} by its {@link DottedPath}s.
339
+ */
340
+ public async getMesh( elementDottedPath: DottedPathArgument, meshDottedPath: DottedPathArgument ): Promise<Mesh|null> {
341
+ const element = await this.getElement( elementDottedPath );
342
+ return element.getMesh( meshDottedPath );
343
+ }
344
+
345
+ /**
346
+ * Gets the Material defined in one of the variants glTFs by its id.
347
+ */
348
+ public getMaterial( id: string ): Material {
349
+ for( const material of this.inheritedMaterials ) {
350
+ if( material.id === id ) {
351
+ return material;
352
+ }
353
+ }
354
+ // fallback to dynamically created materials on scene
355
+ for( const material of this.viewer.scene.materials ) {
356
+ if( material.id === id ) {
357
+ return material;
358
+ }
359
+ }
360
+ throw new Error( `Material with id "${id}" does not exist for variant "${this.id}".` );
361
+ }
362
+
363
+ /**
364
+ * Creates a living clone of this {@link Variant}. Will clone all parent {@link Variant}s in tree.
365
+ *
366
+ * @emit {@link Event.VARIANT_CREATED}
367
+ * @ignore
368
+ */
369
+ public async createLiving( parameters?: ParameterBag ): Promise<Variant> {
370
+ const parent = await this.parent?.createLiving();
371
+ const variant = new Variant( this.name, this._structureJson, this.viewer, parent );
372
+ parent?._children.set( variant.name, variant );
373
+ variant.assetContainer = this.assetContainer;
374
+ variant.parameterObservers = cloneDeep( this.parameterObservers );
375
+ variant.createElements();
376
+ variant.addParameterObservers();
377
+ await variant.bootstrapParameters( parameters );
378
+ this.broadcastEvent( Event.VARIANT_CREATED, variant );
379
+ return variant;
380
+ }
381
+
382
+ /**
383
+ * Destroys this {@link Variant}, all parents and destroy the {@link Element}s.
384
+ */
385
+ public destroy(): Variant {
386
+ this.elements.forEach( element => element.destroy() );
387
+ if( this.parent ) {
388
+ this.parent.destroy();
389
+ }
390
+ this.broadcastEvent( Event.VARIANT_DESTROYED, this );
391
+ return this;
392
+ }
393
+
394
+ /**
395
+ * Places the given {@link ParameterBag} in the {@link Variant}'s parameters, replaces all patterns in the
396
+ * {@link StructureJson}, broadcasts all {@link ParameterObserver}s and delegates them to its {@link Element}s.
397
+ *
398
+ * @emit {@link Event.VARIANT_PARAMETER_COMMITTED}
399
+ */
400
+ public async commitParameters( parameters?: ParameterBag ): Promise<Variant> {
401
+ parameters = merge( {}, parameters );
402
+
403
+ // remember old parameter values for later comparison
404
+ const oldParameters = cloneDeep( this.inheritedParameters );
405
+
406
+ // replace patterns in given parameters
407
+ let _parameters = JSON.stringify( parameters );
408
+ for( const parameter in this.inheritedParameters ) {
409
+ const value = this.inheritedParameters[parameter];
410
+ const search = new RegExp( `\\$\\{${parameter}\\}`, 'g' );
411
+ _parameters = _parameters.replace( search, value.toString() );
412
+ }
413
+ merge( parameters, JSON.parse( _parameters ) );
414
+
415
+ // merge inherited parameters and replaced given parameters
416
+ const mergedParameters = merge( {}, this.inheritedParameters, parameters );
417
+
418
+ // replace patterns in structure parameters
419
+ const structureParameters = this._structureJson.parameters || {};
420
+ let _structureParameters = JSON.stringify( structureParameters );
421
+ for( const parameter in mergedParameters ) {
422
+ const value = mergedParameters[parameter];
423
+ const search = new RegExp( `\\$\\{${parameter}\\}`, 'g' );
424
+ _structureParameters = _structureParameters.replace( search, value.toString() );
425
+ }
426
+ const replacedStructureParameters = JSON.parse( _structureParameters );
427
+
428
+ // calculate which replaced structure parameters have changed and should overload given parameters
429
+ const differentStructureParameters: ParameterBag = {};
430
+ for( const parameter in replacedStructureParameters ) {
431
+ if( !isEqual(structureParameters[parameter], replacedStructureParameters[parameter]) ) {
432
+ differentStructureParameters[parameter] = replacedStructureParameters[parameter];
433
+ }
434
+ }
435
+
436
+ // merge replaced structure parameters and given inherited parameters to structure parameters
437
+ merge( this.parameters, mergedParameters, differentStructureParameters );
438
+
439
+ // inherited parameters are now the new parameters to process
440
+ const newParameters = this.inheritedParameters;
441
+
442
+ // replace all parameter patterns in structure json
443
+ let structure = JSON.stringify( this._structureJson );
444
+ for( const parameter in newParameters ) {
445
+ const value = newParameters[parameter];
446
+ const search = new RegExp( `\\$\\{${parameter}\\}`, 'g' );
447
+ structure = structure.replace( search, value.toString() );
448
+ }
449
+ this.structureJson = JSON.parse( structure );
450
+
451
+ // handle parameter observers
452
+ let observerPromises: Promise<void | ParameterObserver>[] = [];
453
+ for( const parameter in newParameters ) {
454
+ const oldParameterValue = oldParameters[parameter];
455
+ const newParameterValue = newParameters[parameter];
456
+ this.assertParameter( this.inheritedParameterDeclaration, parameter, newParameterValue );
457
+ if( oldParameterValue === newParameterValue && this.parametersInitialized ) {
458
+ continue;
459
+ }
460
+ // parameter changed
461
+ const parameterObservers = mergeMaps( this._parameterObservers, this.parameterObservers );
462
+ if( parameterObservers.has( parameter ) ) {
463
+ const observers = parameterObservers.get( parameter )!;
464
+ observerPromises = concat(observerPromises, observers.map( observer => {
465
+ const observerResult = observer( this, oldParameterValue, newParameterValue );
466
+ return Promise.resolve( observerResult ).then( () => {
467
+ if( this.parametersInitialized ) {
468
+ this.broadcastEvent( Event.VARIANT_PARAMETER_COMMITTED,
469
+ this, parameter, oldParameterValue, newParameterValue );
470
+ }
471
+ } );
472
+ } ) );
473
+ }
474
+ }
475
+ await Promise.all( observerPromises );
476
+
477
+ // broadcast that bag has been committed
478
+ this.broadcastEvent(Event.VARIANT_PARAMETER_BAG_COMMITTED, this, oldParameters, newParameters);
479
+
480
+ // commit parameters to elements
481
+ const elementPromises: Promise<Element>[] = this.elements.map( element => {
482
+ let _elementDefinition = JSON.stringify( this._structureJson.elements![element.name] );
483
+ const elementParameters: ParameterBag = {};
484
+ for( const parameter in newParameters ) {
485
+ if( DottedPath.create( parameter ).firstPart !== element.name ) {
486
+ continue;
487
+ }
488
+ // we got an element parameter
489
+ let newParameterValue = newParameters[parameter];
490
+ const elementParameter = parameter.replace( `${element.name}.`, '' );
491
+ // If the variant is explicitly hidden, we must not override the visibility with element parameters. We need
492
+ // an exception for visibility to avoid overloading already applied element parameters with element parameters
493
+ // defined in the variant spec ("dotted parameters").
494
+ // @see https://github.com/Combeenation/3d-viewer/issues/44
495
+ if( elementParameter === Parameter.VISIBLE && newParameters[Parameter.VISIBLE] === false ) {
496
+ newParameterValue = false;
497
+ }
498
+ elementParameters[elementParameter] = newParameterValue;
499
+ const search = new RegExp( `\\$\\{${elementParameter}\\}`, 'g' );
500
+ _elementDefinition = _elementDefinition.replace( search, newParameterValue.toString() );
501
+ }
502
+ this.structureJson.elements![this.name] = JSON.parse( _elementDefinition );
503
+ return element.commitParameters( elementParameters );
504
+ } );
505
+ await Promise.all( elementPromises );
506
+
507
+ // propagate parameters to parent
508
+ if( this.parent ) {
509
+ await this.parent.commitParameters( this.parameters );
510
+ }
511
+
512
+ return this;
513
+ }
514
+
515
+ /**
516
+ * Adds an observer function for camera matrix changes for given `dottedPath` representing the {@link Element}
517
+ * and the `traceable`. The `observer` gets 2 parameters: the `AbstractMesh` and a `ClientRect` object.
518
+ */
519
+ public async addTraceableObserver( dottedPath: DottedPathArgument,
520
+ observer: CallableFunction,
521
+ payload?: any ): Promise<Element> {
522
+ const _dottedPath = DottedPath.create( dottedPath );
523
+ const traceableName = _dottedPath.popPart();
524
+ if( !traceableName ) {
525
+ throw new Error( `The dottedPath must consist of the element and the name of the defined corresponding ` +
526
+ `traceable ("${_dottedPath.path}" given).` );
527
+ }
528
+ const element = await this.getElement( _dottedPath );
529
+ return element.addTraceableObserver( traceableName, observer, payload );
530
+ }
531
+
532
+ /**
533
+ * Loads {@link glTFUri} with assets, adds them to the {@link Variant}'s `assetContainer` and deactivates the meshes.
534
+ * (for further processing).
535
+ * @emits {@link Event.ASSET_LOADING_START}
536
+ * @emits {@link Event.ASSET_LOADING_END}
537
+ */
538
+ protected async loadAssets(): Promise<Variant> {
539
+ this.broadcastEvent( Event.ASSET_LOADING_START, this );
540
+ return new Promise( resolve => {
541
+ if( !this.structureJson ) {
542
+ this.broadcastEvent( Event.ASSET_LOADING_END, this );
543
+ return resolve( this );
544
+ }
545
+ if( !this.glTF ) {
546
+ this.broadcastEvent( Event.ASSET_LOADING_END, this );
547
+ return resolve( this );
548
+ }
549
+ SceneLoader.LoadAssetContainerAsync( this.glTF.rootUrl, this.glTF.fileName, this.viewer.scene ).then( container => {
550
+ this.assetContainer = container;
551
+ const nodes = this.assetContainer.getNodes().filter( n => n instanceof TransformNode ) as TransformNode[];
552
+ nodes.forEach( node => {
553
+ deactivateTransformNode( node, false );
554
+ injectTransformNodeMetadata( node, { dottedPath: getDottedPathForTransformNode( node ) }, false );
555
+ } );
556
+ this.broadcastEvent( Event.ASSET_LOADING_END, this );
557
+ resolve( this );
558
+ } ).catch( reason => {
559
+ this.broadcastEvent( Event.ASSET_LOADING_END, this );
560
+ throw new Error( `Error loading assets for variant "${this.id}": ${reason}.` );
561
+ } );
562
+ } );
563
+ }
564
+
565
+ /**
566
+ * Commits given {@link Parameter} to the {@link Variant}'s {@link Element}s.
567
+ */
568
+ protected async commitParameterToElements( parameter: string, value: ParameterValue ) {
569
+ const promises = [];
570
+ for( const element of this.elements ) {
571
+ promises.push( element.commitParameter( parameter, value ) );
572
+ }
573
+ await Promise.all( promises );
574
+ }
575
+
576
+ /**
577
+ * Adds the default {@link ParameterObserver}s which are called every time {@link commitParameters} is called.
578
+ */
579
+ protected addParameterObservers(): Variant {
580
+ this._parameterObservers.set( Parameter.VISIBLE, [
581
+ async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
582
+ await variant.commitParameterToElements( Parameter.VISIBLE, newValue );
583
+ }
584
+ ] );
585
+ this._parameterObservers.set( Parameter.SCALING, [
586
+ async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
587
+ await variant.commitParameterToElements( Parameter.SCALING, newValue );
588
+ }
589
+ ] );
590
+ this._parameterObservers.set( Parameter.MATERIAL, [
591
+ async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
592
+ await variant.commitParameterToElements( Parameter.MATERIAL, newValue );
593
+ }
594
+ ] );
595
+ this._parameterObservers.set( Parameter.MATERIAL_COLOR, [
596
+ async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
597
+ await variant.commitParameterToElements( Parameter.MATERIAL_COLOR, newValue );
598
+ }
599
+ ] );
600
+ this._parameterObservers.set( Parameter.MATERIAL_METALLNESS, [
601
+ async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
602
+ await variant.commitParameterToElements( Parameter.MATERIAL_METALLNESS, newValue );
603
+ }
604
+ ] );
605
+ this._parameterObservers.set( Parameter.MATERIAL_ROUGHNESS, [
606
+ async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
607
+ await variant.commitParameterToElements( Parameter.MATERIAL_ROUGHNESS, newValue );
608
+ }
609
+ ] );
610
+ this._parameterObservers.set( Parameter.HIGHLIGHT_COLOR, [
611
+ async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
612
+ await variant.commitParameterToElements( Parameter.HIGHLIGHT_COLOR, newValue );
613
+ }
614
+ ] );
615
+ this._parameterObservers.set( Parameter.HIGHLIGHTED, [
616
+ async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
617
+ await variant.commitParameterToElements( Parameter.HIGHLIGHTED, newValue );
618
+ }
619
+ ] );
620
+ this._parameterObservers.set( Parameter.POSITION, [
621
+ async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
622
+ await variant.commitParameterToElements( Parameter.POSITION, newValue );
623
+ }
624
+ ] );
625
+ this._parameterObservers.set( Parameter.ROTATION, [
626
+ async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
627
+ await variant.commitParameterToElements( Parameter.ROTATION, newValue );
628
+ }
629
+ ] );
630
+ return this;
631
+ }
632
+
633
+ /**
634
+ * Creates {@link Element}s and clones nodes into them.
635
+ */
636
+ protected createElements(): Variant {
637
+ for( const elementName in this.structureJson.elements || {} ) {
638
+ this.elements.push( new Element( this, elementName ) );
639
+ }
640
+ // inject node meta to all inherited elements
641
+ // we do this to inject the deepest and most concrete variant information to all cloned nodes in the tree
642
+ this.inheritedElements.forEach( element => {
643
+ element.nodes.forEach( node => {
644
+ injectTransformNodeMetadata( node, { variant: this, element: element } );
645
+ } );
646
+ } );
647
+ return this;
648
+ }
649
+
650
+ /**
651
+ * Bootstrapping for parameters. It sets the `parametersInitialized` to true for all ancestors.
652
+ */
653
+ protected async bootstrapParameters( parameters?: ParameterBag ): Promise<Variant> {
654
+ await this.commitParameters( merge( cloneDeep( this.parameters ), parameters ) );
655
+ concat( this.ancestors, this ).forEach( ancestor => ancestor.parametersInitialized = true );
656
+ return this;
657
+ }
658
+
659
+ }