@combeenation/3d-viewer 4.0.0-beta3 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +3 -1
  2. package/dist/lib-cjs/api/classes/element.d.ts +14 -9
  3. package/dist/lib-cjs/api/classes/element.js +148 -87
  4. package/dist/lib-cjs/api/classes/element.js.map +1 -1
  5. package/dist/lib-cjs/api/classes/event.d.ts +15 -1
  6. package/dist/lib-cjs/api/classes/event.js +15 -1
  7. package/dist/lib-cjs/api/classes/event.js.map +1 -1
  8. package/dist/lib-cjs/api/classes/parameter.d.ts +101 -7
  9. package/dist/lib-cjs/api/classes/parameter.js +141 -21
  10. package/dist/lib-cjs/api/classes/parameter.js.map +1 -1
  11. package/dist/lib-cjs/api/classes/parameterObservable.js +11 -36
  12. package/dist/lib-cjs/api/classes/parameterObservable.js.map +1 -1
  13. package/dist/lib-cjs/api/classes/placementAnimation.d.ts +2 -2
  14. package/dist/lib-cjs/api/classes/placementAnimation.js +11 -0
  15. package/dist/lib-cjs/api/classes/placementAnimation.js.map +1 -1
  16. package/dist/lib-cjs/api/classes/variant.d.ts +48 -4
  17. package/dist/lib-cjs/api/classes/variant.js +320 -46
  18. package/dist/lib-cjs/api/classes/variant.js.map +1 -1
  19. package/dist/lib-cjs/api/classes/variantInstance.d.ts +5 -1
  20. package/dist/lib-cjs/api/classes/variantInstance.js +10 -0
  21. package/dist/lib-cjs/api/classes/variantInstance.js.map +1 -1
  22. package/dist/lib-cjs/api/classes/viewer.d.ts +6 -3
  23. package/dist/lib-cjs/api/classes/viewer.js +140 -59
  24. package/dist/lib-cjs/api/classes/viewer.js.map +1 -1
  25. package/dist/lib-cjs/api/internal/sceneSetup.d.ts +5 -1
  26. package/dist/lib-cjs/api/internal/sceneSetup.js +75 -71
  27. package/dist/lib-cjs/api/internal/sceneSetup.js.map +1 -1
  28. package/dist/lib-cjs/api/util/babylonHelper.d.ts +54 -4
  29. package/dist/lib-cjs/api/util/babylonHelper.js +160 -8
  30. package/dist/lib-cjs/api/util/babylonHelper.js.map +1 -1
  31. package/dist/lib-cjs/api/util/globalTypes.d.ts +62 -8
  32. package/dist/lib-cjs/api/util/resourceHelper.d.ts +13 -8
  33. package/dist/lib-cjs/api/util/resourceHelper.js +14 -14
  34. package/dist/lib-cjs/api/util/resourceHelper.js.map +1 -1
  35. package/dist/lib-cjs/index.d.ts +24 -22
  36. package/dist/lib-cjs/index.js +42 -38
  37. package/dist/lib-cjs/index.js.map +1 -1
  38. package/package.json +5 -5
  39. package/src/api/classes/element.ts +118 -91
  40. package/src/api/classes/event.ts +16 -1
  41. package/src/api/classes/parameter.ts +153 -22
  42. package/src/api/classes/parameterObservable.ts +9 -31
  43. package/src/api/classes/{elementParameterizable.ts → parameterizable.ts} +12 -1
  44. package/src/api/classes/placementAnimation.ts +10 -0
  45. package/src/api/classes/variant.ts +187 -40
  46. package/src/api/classes/variantInstance.ts +8 -1
  47. package/src/api/classes/variantParameterizable.ts +73 -0
  48. package/src/api/classes/viewer.ts +83 -17
  49. package/src/api/classes/viewerLight.ts +330 -0
  50. package/src/api/internal/sceneSetup.ts +99 -109
  51. package/src/api/util/babylonHelper.ts +173 -10
  52. package/src/api/util/globalTypes.ts +71 -10
  53. package/src/api/util/resourceHelper.ts +16 -16
  54. package/src/api/util/stringHelper.ts +26 -0
  55. package/src/dev.ts +3 -7
  56. package/src/index.ts +27 -23
  57. package/src/pagesconfig.json +4 -0
@@ -1,6 +1,5 @@
1
1
  import { merge } from 'lodash-es';
2
- import { Parameter } from '../classes/parameter';
3
- import { uuidv4 } from '../util/resourceHelper';
2
+ import { uuidv4 } from '../util/stringHelper';
4
3
  import { EventBroadcaster } from './eventBroadcaster';
5
4
 
6
5
  export abstract class ParameterObservable extends EventBroadcaster {
@@ -75,37 +74,16 @@ export abstract class ParameterObservable extends EventBroadcaster {
75
74
  return;
76
75
  }
77
76
  const declaration = parameterDeclaration[parameter];
78
- const genericError = `"${value}" is not a valid value for parameter "${parameter}" of type ` +
77
+ const genericError = `"${value}" is not a valid value for parameter "${parameter}" of type. ` +
79
78
  `"${declaration.type}" for ${this.constructor.name} "${this.id}".`;
79
+ if( declaration.parser ) {
80
+ try {
81
+ declaration.parser( value );
82
+ } catch( e ) {
83
+ throw Error( genericError + e.message );
84
+ }
85
+ }
80
86
  switch( declaration.type ) {
81
- case 'boolean':
82
- try {
83
- Parameter.parseBoolean( value );
84
- } catch( e ) {
85
- throw Error( genericError );
86
- }
87
- break;
88
- case 'number':
89
- try {
90
- Parameter.parseNumber( value );
91
- } catch( e ) {
92
- throw Error( genericError );
93
- }
94
- break;
95
- case 'color':
96
- try {
97
- Parameter.parseColor( value );
98
- } catch( e ) {
99
- throw Error( genericError + ` It must be hex with leading "#" or of format "(r,g,b)".` );
100
- }
101
- break;
102
- case 'vector':
103
- try {
104
- Parameter.parseVector( value );
105
- } catch( e ) {
106
- throw Error( genericError + ` It must be of format "(x,y,z)".` );
107
- }
108
- break;
109
87
  case 'select':
110
88
  if( !declaration.options ) {
111
89
  throw Error( `No options defined for parameter declaration "${parameter}"` +
@@ -1,7 +1,7 @@
1
1
  import { Parameter } from './parameter';
2
2
  import { ParameterObservable } from './parameterObservable';
3
3
 
4
- export abstract class ElementParameterizable extends ParameterObservable {
4
+ export abstract class Parameterizable extends ParameterObservable {
5
5
 
6
6
  protected parameterDeclaration: ParameterDeclarations = Parameter.declarations;
7
7
 
@@ -55,6 +55,17 @@ export abstract class ElementParameterizable extends ParameterObservable {
55
55
  return this.parameters[Parameter.ROTATION].toString();
56
56
  }
57
57
 
58
+ get castShadow(): boolean | undefined {
59
+ if( !(Parameter.CAST_SHADOW in this.parameters) ) {
60
+ return undefined;
61
+ }
62
+ try {
63
+ return Parameter.parseBoolean( this.parameters[Parameter.CAST_SHADOW] );
64
+ } catch( e ) {
65
+ return undefined;
66
+ }
67
+ }
68
+
58
69
  public async show(): Promise<ParameterObservable> {
59
70
  await this.commitParameter( Parameter.VISIBLE, true );
60
71
  return this;
@@ -88,6 +88,11 @@ export class PlacementAnimation implements AnimationInterface {
88
88
  position = Parameter.parseVector( position );
89
89
  this._placementDefinition.position = position;
90
90
  }
91
+ let shortestWay = true;
92
+ if( this._animationDefinition.shortestWay !== undefined ) {
93
+ shortestWay = Parameter.parseBoolean( this._animationDefinition.shortestWay );
94
+ this._animationDefinition.shortestWay = shortestWay;
95
+ }
91
96
  if( this.mutable instanceof ArcRotateCamera ) {
92
97
  // parse the target
93
98
  if( isString( this._placementDefinition.target ) ) {
@@ -105,6 +110,11 @@ export class PlacementAnimation implements AnimationInterface {
105
110
  } );
106
111
  delete this._placementDefinition.position;
107
112
  ghostCam.dispose();
113
+ } else if( shortestWay && this._placementDefinition.alpha ) {
114
+ // transform the target's alpha value into the same turn as the current camera position to avoid movements > 360 degrees
115
+ const alphaGap = this.mutable.alpha - this._placementDefinition.alpha;
116
+ const cntTurns = Math.round(alphaGap / (2 * Math.PI));
117
+ this._placementDefinition.alpha += (2 * Math.PI * cntTurns);
108
118
  }
109
119
  }
110
120
  }
@@ -1,4 +1,5 @@
1
1
  import { AssetContainer } from '@babylonjs/core/assetContainer';
2
+ import { Light } from '@babylonjs/core/Lights/light';
2
3
  import '@babylonjs/core/Loading/Plugins/babylonFileLoader';
3
4
  import { SceneLoader } from '@babylonjs/core/Loading/sceneLoader';
4
5
  import { Material } from '@babylonjs/core/Materials/material';
@@ -7,19 +8,18 @@ import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
7
8
  import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression';
8
9
  import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_texture_transform';
9
10
  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';
11
+ import { cloneDeep, concat, get, isEmpty, isEqual, isString, merge, set } from 'lodash-es';
12
+ import { deactivateTransformNode, getDottedPathForNode, injectNodeMetadata } from '../util/babylonHelper';
16
13
  import { loadJson, mergeMaps } from '../util/resourceHelper';
17
14
  import { DottedPath } from './dottedPath';
18
15
  import { Element } from './element';
19
- import { ElementParameterizable } from './elementParameterizable';
20
16
  import { Event } from './event';
21
17
  import { Parameter } from './parameter';
18
+ import { Parameterizable } from './parameterizable';
19
+ import { ParameterObservable } from './parameterObservable';
20
+ import { VariantParameterizable } from './variantParameterizable';
22
21
  import { Viewer } from './viewer';
22
+ import { ViewerLight } from './viewerLight';
23
23
 
24
24
  import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_texture_basisu';
25
25
  import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_clearcoat';
@@ -35,12 +35,14 @@ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_variants';
35
35
  /**
36
36
  * A concrete "Variant". Most of these are handled by either the {@link Viewer} or {@link VariantInstance}.
37
37
  */
38
- export class Variant extends ElementParameterizable {
38
+ export class Variant extends Parameterizable {
39
39
 
40
40
  public assetContainer: AssetContainer;
41
41
 
42
42
  public readonly elements: Element[] = [];
43
43
 
44
+ public readonly viewerLights: ViewerLight[] = [];
45
+
44
46
  public structureJson: StructureJson;
45
47
 
46
48
  protected _dottedNodes: Map<DottedPath, TransformNode> | undefined;
@@ -175,6 +177,13 @@ export class Variant extends ElementParameterizable {
175
177
  return rootNodes;
176
178
  }
177
179
 
180
+ /**
181
+ * The {@link ViewerLight}s of the {@link Variant}.
182
+ */
183
+ get lights(): Light[] {
184
+ return this.assetContainer.lights;
185
+ }
186
+
178
187
  /**
179
188
  * All TransformNodes of the {@link Variant} mapped flat with a {@link DottedPath}.
180
189
  */
@@ -208,6 +217,17 @@ export class Variant extends ElementParameterizable {
208
217
  return concat( elements, this.elements );
209
218
  }
210
219
 
220
+ /**
221
+ * All {@link ViewerLight}s inherited from this {@link Variant}'s parents.
222
+ */
223
+ get inheritedViewerLights(): ViewerLight[] {
224
+ let viewerLights: ViewerLight[] = [];
225
+ this.ancestors.forEach( ancestor => {
226
+ viewerLights = concat( viewerLights, ancestor.viewerLights );
227
+ } );
228
+ return concat( viewerLights, this.viewerLights );
229
+ }
230
+
211
231
  /**
212
232
  * All TransformNodes inherited from this {@link Variant}'s parents.
213
233
  */
@@ -230,6 +250,17 @@ export class Variant extends ElementParameterizable {
230
250
  return dottedNodes;
231
251
  }
232
252
 
253
+ /**
254
+ * All Lights inherited from this {@link Variant}'s parents.
255
+ */
256
+ get inheritedLights(): Light[] {
257
+ let lights: Light[] = [];
258
+ this.ancestors.forEach( ancestor => {
259
+ lights = concat( lights, ancestor.lights );
260
+ } );
261
+ return concat( lights, this.lights );
262
+ }
263
+
233
264
  /**
234
265
  * The {@link ParameterDeclarations} inherited from this {@link Variant}'s parents.
235
266
  */
@@ -337,6 +368,33 @@ export class Variant extends ElementParameterizable {
337
368
  return element;
338
369
  }
339
370
 
371
+ /**
372
+ * Gets the desired {@link ViewerLight} of the current {@link Variant} relative to its {@link DottedPath}.
373
+ * Uses the mechanism of {@link getDescendant} to resolve the appropriate variant in tree.
374
+ */
375
+ public async getViewerLight( dottedPath: DottedPathArgument ): Promise<ViewerLight> {
376
+ const _dottedPath = DottedPath.create( dottedPath );
377
+ const viewerLightName = _dottedPath.popPart();
378
+ let variant: Variant = this;
379
+ if( _dottedPath.parts.length > 0 ) {
380
+ variant = await this.getDescendant( _dottedPath );
381
+ }
382
+ if( variant.inheritedViewerLights.length === 0 ) {
383
+ throw new Error( `No viewerLights for variant "${variant.id}" found. ` +
384
+ `Either none are defined or they are not initialized (are you operating on the appropriate living?).` );
385
+ }
386
+ let viewerLight;
387
+ variant.inheritedViewerLights.forEach( _viewerLight => {
388
+ if( _viewerLight.name === viewerLightName ) {
389
+ viewerLight = _viewerLight;
390
+ }
391
+ } );
392
+ if( !viewerLight ) {
393
+ throw new Error( `ViewerLight with name "${viewerLightName}" does not exist for variant "${variant.id}".` );
394
+ }
395
+ return viewerLight;
396
+ }
397
+
340
398
  /**
341
399
  * A proxy for directly getting a Node from an {@link Element} by its {@link DottedPath}s.
342
400
  */
@@ -383,7 +441,8 @@ export class Variant extends ElementParameterizable {
383
441
  parent?._children.set( variant.name, variant );
384
442
  variant.assetContainer = this.assetContainer;
385
443
  variant.parameterObservers = cloneDeep( this.parameterObservers );
386
- variant.createElements();
444
+ await variant.createElements();
445
+ await variant.createViewerLights();
387
446
  variant.addParameterObservers();
388
447
  await variant.bootstrapParameters( parameters );
389
448
  this.broadcastEvent( Event.VARIANT_CREATED, variant );
@@ -489,31 +548,10 @@ export class Variant extends ElementParameterizable {
489
548
  this.broadcastEvent(Event.VARIANT_PARAMETER_BAG_COMMITTED, this, oldParameters, newParameters);
490
549
 
491
550
  // 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 );
551
+ await this.commitParametersToElements( newParameters );
552
+
553
+ // commit parameters to lights
554
+ await this.commitParametersToViewerLights( newParameters );
517
555
 
518
556
  // propagate parameters to parent
519
557
  if( this.parent ) {
@@ -562,7 +600,17 @@ export class Variant extends ElementParameterizable {
562
600
  const nodes = this.assetContainer.getNodes().filter( n => n instanceof TransformNode ) as TransformNode[];
563
601
  nodes.forEach( node => {
564
602
  deactivateTransformNode( node, false );
565
- injectTransformNodeMetadata( node, { dottedPath: getDottedPathForTransformNode( node ) }, false );
603
+ injectNodeMetadata( node, { dottedPath: getDottedPathForNode( node ) }, false );
604
+ } );
605
+ this.assetContainer.lights.forEach( light => {
606
+ light.setEnabled( false );
607
+ injectNodeMetadata( light, { dottedPath: getDottedPathForNode( light ) }, false );
608
+ this.viewer.scene.addLight( light );
609
+ } );
610
+ this.assetContainer.cameras.forEach( camera => {
611
+ camera.setEnabled( false );
612
+ injectNodeMetadata( camera, { dottedPath: getDottedPathForNode( camera ) }, false );
613
+ this.viewer.scene.addCamera( camera );
566
614
  } );
567
615
  this.broadcastEvent( Event.ASSET_LOADING_END, this );
568
616
  resolve( this );
@@ -573,15 +621,82 @@ export class Variant extends ElementParameterizable {
573
621
  } );
574
622
  }
575
623
 
624
+ /**
625
+ * Commits given parameters to all {@link Element}s.
626
+ */
627
+ protected async commitParametersToElements( parameters: ParameterBag ) {
628
+ await Promise.all( this.elements.map(
629
+ element => this.commitParametersToVariantParameterizable(parameters, element, 'elements')
630
+ ) );
631
+ }
632
+
633
+ /**
634
+ * Commits given parameters to all {@link ViewerLight}s.
635
+ */
636
+ protected async commitParametersToViewerLights( parameters: ParameterBag ) {
637
+ await Promise.all( this.viewerLights.map(
638
+ viewerLight => this.commitParametersToVariantParameterizable(parameters, viewerLight, 'lights')
639
+ ) );
640
+ }
641
+
642
+ /**
643
+ * Commits given parameters to a {@link VariantParameterizable} and updates the according definition with given
644
+ * key in the {@link StructureJson}. The `definitionKey` "elements" for example will update the definition in
645
+ * `this.structureJson.elements`.
646
+ */
647
+ protected async commitParametersToVariantParameterizable( parameters: ParameterBag,
648
+ parameterizable: VariantParameterizable,
649
+ definitionKey: string ): Promise<ParameterObservable> {
650
+ const initialDefinition = get( this._structureJson, definitionKey )[parameterizable.name];
651
+ let initialDefinitionStr = JSON.stringify( initialDefinition );
652
+ const _parameters: ParameterBag = {};
653
+ for( const parameter in parameters ) {
654
+ const dpp = DottedPath.create( parameter );
655
+ if( dpp.shiftPart() !== parameterizable.name ) {
656
+ continue;
657
+ }
658
+ // we got a parameterizable ("element") parameter
659
+ let parameterValue = parameters[parameter];
660
+ const parameterizableParameter = dpp.path;
661
+ // If the variant is explicitly hidden, we must not override the visibility with element parameters. We need
662
+ // an exception for visibility to avoid overloading already applied element parameters with element parameters
663
+ // defined in the variant spec ("dotted parameters").
664
+ // @see https://github.com/Combeenation/3d-viewer/issues/44
665
+ if( parameterizableParameter === Parameter.VISIBLE && parameters[Parameter.VISIBLE] === false ) {
666
+ parameterValue = false;
667
+ }
668
+ _parameters[parameterizableParameter] = parameterValue;
669
+ const search = new RegExp( `\\$\\{${parameterizableParameter}\\}`, 'g' );
670
+ initialDefinitionStr = initialDefinitionStr.replace( search, parameterValue.toString() );
671
+ }
672
+ const definition = get( this.structureJson, definitionKey );
673
+ definition[this.name] = JSON.parse( initialDefinitionStr );
674
+ set( this.structureJson, definitionKey, definition );
675
+ return await parameterizable.commitParameters( _parameters );
676
+ }
677
+
576
678
  /**
577
679
  * Commits given {@link Parameter} to the {@link Variant}'s {@link Element}s.
578
680
  */
579
- protected async commitParameterToElements( parameter: string, value: ParameterValue ) {
681
+ protected async commitParameterToElements( parameter: string, value: ParameterValue ): Promise<Variant> {
580
682
  const promises = [];
581
683
  for( const element of this.elements ) {
582
684
  promises.push( element.commitParameter( parameter, value ) );
583
685
  }
584
686
  await Promise.all( promises );
687
+ return this;
688
+ }
689
+
690
+ /**
691
+ * Commits given {@link Parameter} to the {@link Variant}'s {@link Element}s.
692
+ */
693
+ protected async commitParameterToViewerLights( parameter: string, value: ParameterValue ): Promise<Variant> {
694
+ const promises = [];
695
+ for( const viewerLight of this.viewerLights ) {
696
+ promises.push( viewerLight.commitParameter( parameter, value ) );
697
+ }
698
+ await Promise.all( promises );
699
+ return this;
585
700
  }
586
701
 
587
702
  /**
@@ -591,11 +706,13 @@ export class Variant extends ElementParameterizable {
591
706
  this._parameterObservers.set( Parameter.VISIBLE, [
592
707
  async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
593
708
  await variant.commitParameterToElements( Parameter.VISIBLE, newValue );
709
+ await variant.commitParameterToViewerLights( Parameter.VISIBLE, newValue );
594
710
  }
595
711
  ] );
596
712
  this._parameterObservers.set( Parameter.SCALING, [
597
713
  async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
598
714
  await variant.commitParameterToElements( Parameter.SCALING, newValue );
715
+ await variant.commitParameterToViewerLights( Parameter.SCALING, newValue );
599
716
  }
600
717
  ] );
601
718
  this._parameterObservers.set( Parameter.MATERIAL, [
@@ -631,11 +748,28 @@ export class Variant extends ElementParameterizable {
631
748
  this._parameterObservers.set( Parameter.POSITION, [
632
749
  async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
633
750
  await variant.commitParameterToElements( Parameter.POSITION, newValue );
751
+ await variant.commitParameterToViewerLights( Parameter.POSITION, newValue );
634
752
  }
635
753
  ] );
636
754
  this._parameterObservers.set( Parameter.ROTATION, [
637
755
  async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
638
756
  await variant.commitParameterToElements( Parameter.ROTATION, newValue );
757
+ await variant.commitParameterToViewerLights( Parameter.ROTATION, newValue );
758
+ }
759
+ ] );
760
+ this._parameterObservers.set( Parameter.CAST_SHADOW, [
761
+ async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
762
+ await variant.commitParameterToElements( Parameter.CAST_SHADOW, newValue );
763
+ }
764
+ ] );
765
+ this._parameterObservers.set( Parameter.CAST_SHADOW_FROM_LIGHTS, [
766
+ async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
767
+ await variant.commitParameterToElements( Parameter.CAST_SHADOW_FROM_LIGHTS, newValue );
768
+ }
769
+ ] );
770
+ this._parameterObservers.set( Parameter.RECEIVE_SHADOWS, [
771
+ async ( variant: Variant, oldValue: ParameterValue, newValue: ParameterValue ) => {
772
+ await variant.commitParameterToElements( Parameter.RECEIVE_SHADOWS, newValue );
639
773
  }
640
774
  ] );
641
775
  return this;
@@ -644,20 +778,33 @@ export class Variant extends ElementParameterizable {
644
778
  /**
645
779
  * Creates {@link Element}s and clones nodes into them.
646
780
  */
647
- protected createElements(): Variant {
648
- for( const elementName in this.structureJson.elements || {} ) {
649
- this.elements.push( new Element( this, elementName ) );
781
+ protected async createElements(): Promise<Variant> {
782
+ for( const name in this.structureJson.elements || {} ) {
783
+ this.elements.push( await Element.create( this, name ) );
650
784
  }
651
785
  // inject node meta to all inherited elements
652
786
  // we do this to inject the deepest and most concrete variant information to all cloned nodes in the tree
653
787
  this.inheritedElements.forEach( element => {
654
788
  element.nodes.forEach( node => {
655
- injectTransformNodeMetadata( node, { variant: this, element: element } );
789
+ injectNodeMetadata( node, { variant: this, variantParameterizable: element } );
656
790
  } );
657
791
  } );
658
792
  return this;
659
793
  }
660
794
 
795
+ /**
796
+ * Creates {@link ViewerLight}s.
797
+ */
798
+ protected async createViewerLights(): Promise<Variant> {
799
+ for( const name in this.structureJson.lights || {} ) {
800
+ this.viewerLights.push( await ViewerLight.create( this, name ) );
801
+ }
802
+ this.inheritedViewerLights.forEach( viewerLight => {
803
+ injectNodeMetadata( viewerLight.light, { variant: this, variantParameterizable: viewerLight } );
804
+ } );
805
+ return this;
806
+ }
807
+
661
808
  /**
662
809
  * Bootstrapping for parameters. It sets the `parametersInitialized` to true for all ancestors.
663
810
  */
@@ -1,6 +1,6 @@
1
1
  import { Mesh } from '@babylonjs/core/Meshes/mesh';
2
- import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
3
2
  import { Element } from './element';
3
+ import { ViewerLight } from './viewerLight';
4
4
  import { EventBroadcaster } from './eventBroadcaster';
5
5
  import { ParameterObservable } from './parameterObservable';
6
6
  import { Variant } from './variant';
@@ -51,6 +51,13 @@ export class VariantInstance extends EventBroadcaster {
51
51
  return this.variant.getElement( dottedPath );
52
52
  }
53
53
 
54
+ /**
55
+ * A proxy for {@link Variant.getViewerLight}.
56
+ */
57
+ public async getViewerLight( dottedPath: DottedPathArgument ): Promise<ViewerLight> {
58
+ return this.variant.getViewerLight( dottedPath );
59
+ }
60
+
54
61
  /**
55
62
  * A proxy for {@link Variant.getNode}.
56
63
  */
@@ -0,0 +1,73 @@
1
+ import { DottedPath } from './../classes/dottedPath';
2
+ import { Event } from './../classes/event';
3
+ import { Parameterizable } from './../classes/parameterizable';
4
+ import { Variant } from './../classes/variant';
5
+ import { mergeMaps } from './../util/resourceHelper';
6
+ import { camelToSnakeCase } from './../util/stringHelper';
7
+ import { cloneDeep, concat, get, merge } from 'lodash-es';
8
+
9
+ export abstract class VariantParameterizable extends Parameterizable {
10
+
11
+ protected readonly _parameterObservers: Map<string, ParameterObserver[]> = new Map();
12
+
13
+ protected constructor( public readonly variant: Variant,
14
+ public readonly name: string ) {
15
+ super();
16
+ }
17
+
18
+ /**
19
+ * Gets the inherited parameters from the {@link Variant} with cleaned {@link Parameterizable} prefix.
20
+ */
21
+ get inheritedParameters(): ParameterBag {
22
+ const parameterBag: ParameterBag = {};
23
+ for( const parameter in this.variant.inheritedParameters ) {
24
+ const dpp = DottedPath.create( parameter );
25
+ if( dpp.firstPart !== this.name ) {
26
+ continue;
27
+ }
28
+ dpp.shiftPart();
29
+ parameterBag[dpp.path] = this.variant.inheritedParameters[parameter];
30
+ }
31
+ return parameterBag;
32
+ }
33
+
34
+ /**
35
+ * Places the given {@link ParameterBag} in the {@link VariantParameterizable}'s parameters, replaces all patterns in the
36
+ * {@link StructureJson} and broadcasts all {@link ParameterObserver}s.
37
+ */
38
+ public async commitParameters( parameters?: ParameterBag ): Promise<VariantParameterizable> {
39
+ if( !parameters ) {
40
+ parameters = {};
41
+ }
42
+ const oldParameters = cloneDeep( this.parameters );
43
+ merge( this.parameters, parameters );
44
+ // handle parameter observers
45
+ let observerPromises: Promise<void | ParameterObserver>[] = [];
46
+ for( const parameter in this.parameters ) {
47
+ const oldParameterValue = oldParameters[parameter];
48
+ const newParameterValue = this.parameters[parameter];
49
+ this.variant.assertParameter( this.variant.inheritedParameterDeclaration, parameter, newParameterValue );
50
+ if( oldParameterValue === newParameterValue ) {
51
+ continue;
52
+ }
53
+ // parameter changed
54
+ const parameterObservers = mergeMaps( this._parameterObservers, this.parameterObservers );
55
+ if( parameterObservers.has( parameter ) ) {
56
+ const observers = parameterObservers.get( parameter )!;
57
+ observerPromises = concat( observerPromises, observers.map( observer => {
58
+ const observerResult = observer( this, oldParameterValue, newParameterValue );
59
+ const observerPromise = Promise.resolve( observerResult );
60
+ observerPromise.then( () => {
61
+ const clsName = camelToSnakeCase( this.constructor.name ).toUpperCase();
62
+ this.broadcastEvent( get( Event, clsName + '_PARAMETER_COMMITTED' ),
63
+ this, parameter, oldParameterValue, newParameterValue );
64
+ } );
65
+ return observerPromise;
66
+ } ) );
67
+ }
68
+ }
69
+ await Promise.all( observerPromises );
70
+ return this;
71
+ }
72
+
73
+ }