@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,35 +1,40 @@
1
1
  import { HighlightLayer } from '@babylonjs/core/Layers/highlightLayer';
2
+ import { Light } from '@babylonjs/core/Lights/light';
3
+ import { ShadowGenerator } from '@babylonjs/core/Lights/Shadows/shadowGenerator';
2
4
  import { Material } from '@babylonjs/core/Materials/material';
3
5
  import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial';
4
6
  import { DynamicTexture } from '@babylonjs/core/Materials/Textures/dynamicTexture';
5
- import { Axis, Space } from '@babylonjs/core/Maths/math.axis';
6
7
  import { Color3 } from '@babylonjs/core/Maths/math.color';
7
8
  import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';
8
9
  import { InstancedMesh } from '@babylonjs/core/Meshes/instancedMesh';
9
10
  import { Mesh } from '@babylonjs/core/Meshes/mesh';
10
11
  import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
11
- import { cloneDeep, concat, get, isArray, isNumber, isPlainObject, merge, union } from 'lodash-es';
12
+ import { has, isArray, isNumber, isPlainObject, merge, union } from 'lodash-es';
12
13
  import {
13
14
  activateTransformNode as activate,
14
15
  addToHighlightLayer,
16
+ addToShadowGenerator,
15
17
  assertTransformNode,
16
18
  cloneTransformNode,
17
19
  cloneTransformNodeMaterial,
18
20
  deactivateTransformNode as deactivate,
19
21
  getClientRectFromMesh,
20
- injectTransformNodeMetadata,
22
+ injectNodeMetadata,
21
23
  mapToDottedNodes,
24
+ moveTransformNode,
22
25
  removeFromHighlightLayer,
26
+ removeFromShadowGenerator,
27
+ rotateTransformNode,
23
28
  setMaterial,
24
29
  setMaterialColor,
25
30
  setMaterialMetallness,
26
31
  setMaterialRoughness,
27
- setMaterialTexture
32
+ setMaterialTexture,
33
+ setReceiveShadows
28
34
  } from '../util/babylonHelper';
29
- import { createImageFromSvg, createImageFromImgSrc, mergeMaps } from '../util/resourceHelper';
35
+ import { createImageFromImgSrc, createImageFromSvg } from '../util/resourceHelper';
36
+ import { VariantParameterizable } from './../classes/variantParameterizable';
30
37
  import { DottedPath } from './dottedPath';
31
- import { ElementParameterizable } from './elementParameterizable';
32
- import { Event } from './event';
33
38
  import { Parameter } from './parameter';
34
39
  import { Variant } from './variant';
35
40
 
@@ -40,22 +45,20 @@ import { Variant } from './variant';
40
45
  * When used in typings, refer to via its alias {@link VariantElement} to prevent name clashes with the web APIs
41
46
  * [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) class
42
47
  */
43
- export class Element extends ElementParameterizable {
48
+ export class Element extends VariantParameterizable {
44
49
 
45
- protected readonly _dottedNodes: Map<DottedPath, TransformNode> = new Map();
50
+ public readonly nodes: TransformNode[] = [];
46
51
 
47
- protected readonly _parameterObservers: Map<string, ParameterObserver[]> = new Map();
52
+ protected readonly _dottedNodes: Map<DottedPath, TransformNode> = new Map();
48
53
 
49
54
  protected _highlightLayer?: HighlightLayer;
50
55
 
51
- public readonly nodes: TransformNode[] = [];
52
-
53
56
  /**
54
57
  * Constructor.
55
58
  */
56
- public constructor( public readonly variant: Variant,
59
+ protected constructor( public readonly variant: Variant,
57
60
  public readonly name: string ) {
58
- super();
61
+ super( variant, name );
59
62
  if ( process.env.NODE_ENV?.toLowerCase().includes('dev')) {
60
63
  this.assertPathDefinitions();
61
64
  }
@@ -70,6 +73,13 @@ export class Element extends ElementParameterizable {
70
73
  this.addParameterObservers();
71
74
  }
72
75
 
76
+ /**
77
+ * Creates an {@link Element} with given name.
78
+ */
79
+ public static async create( variant: Variant, name: string ): Promise<Element> {
80
+ return new Element( variant, name );
81
+ }
82
+
73
83
  /**
74
84
  * The {@link DottedPath} in the built tree of {@link Element}s.
75
85
  * E.g. "_.top-1.sub-2.sub-sub-3.el-1"
@@ -212,41 +222,11 @@ export class Element extends ElementParameterizable {
212
222
  }
213
223
 
214
224
  /**
215
- * Places the given {@link ParameterBag} in the {@link Element}'s parameters, replaces all patterns in the
216
- * {@link StructureJson} and broadcasts all {@link ParameterObserver}s.
217
- *
225
+ * @see {@link VariantParameterizable.commitParameters}
218
226
  * @emit {@link Event.ELEMENT_PARAMETER_COMMITTED}
219
227
  */
220
- public async commitParameters( parameters?: ParameterBag ): Promise<Element> {
221
- if( !parameters ) {
222
- parameters = {};
223
- }
224
- const oldParameters = cloneDeep( this.parameters );
225
- merge( this.parameters, parameters );
226
- // handle parameter observers
227
- let observerPromises: Promise<void | ParameterObserver>[] = [];
228
- for( const parameter in this.parameters ) {
229
- const oldParameterValue = oldParameters[parameter];
230
- const newParameterValue = this.parameters[parameter];
231
- this.variant.assertParameter( this.variant.inheritedParameterDeclaration, parameter, newParameterValue );
232
- if( oldParameterValue === newParameterValue ) {
233
- continue;
234
- }
235
- // parameter changed
236
- const parameterObservers = mergeMaps( this._parameterObservers, this.parameterObservers );
237
- if( parameterObservers.has( parameter ) ) {
238
- const observers = parameterObservers.get( parameter )!;
239
- observerPromises = concat(observerPromises, observers.map( observer => {
240
- const observerResult = observer( this, oldParameterValue, newParameterValue );
241
- return Promise.resolve( observerResult ).then( () => {
242
- this.broadcastEvent( Event.ELEMENT_PARAMETER_COMMITTED,
243
- this, parameter, oldParameterValue, newParameterValue );
244
- } );
245
- } ) );
246
- }
247
- }
248
- await Promise.all( observerPromises );
249
- return this;
228
+ public async commitParameters( parameters?: ParameterBag ): Promise<VariantParameterizable> {
229
+ return super.commitParameters( parameters );
250
230
  }
251
231
 
252
232
  /**
@@ -287,18 +267,18 @@ export class Element extends ElementParameterizable {
287
267
  throw new Error( `The path must be an instance of "AbstractMesh" for paintable "${paintable}" ` +
288
268
  `in element "${this.id}".` );
289
269
  }
290
- if( node.material && !get( node.metadata, 'dirty.material' ) ) {
270
+ if( node.material && !has( node.metadata, 'dirty.material' ) ) {
291
271
  cloneTransformNodeMaterial( node );
292
272
  }
293
273
  if( !node.material ) {
294
274
  node.material = new StandardMaterial( `${this.id}.${paintable}.material`, this.variant.viewer.scene );
295
275
  }
296
276
  node.material.transparencyMode = Material.MATERIAL_ALPHATESTANDBLEND;
297
- if( !get( node.metadata, 'dirty.material.texture' ) ) {
277
+ if( !has( node.metadata, 'dirty.material.texture' ) ) {
298
278
  // inject initial value and mark as dirty
299
- injectTransformNodeMetadata( node, { dirty: { material: { texture: true } } } );
279
+ injectNodeMetadata( node, { dirty: { material: { texture: true } } } );
300
280
  }
301
-
281
+
302
282
  // consider width and height of the paintable
303
283
  const widthAndHeight = {
304
284
  width: imageSource.width,
@@ -328,7 +308,7 @@ export class Element extends ElementParameterizable {
328
308
 
329
309
  // finally apply the texture on the desired node material
330
310
  setMaterialTexture( node, texture, false );
331
-
311
+
332
312
  return this;
333
313
  }
334
314
 
@@ -362,12 +342,12 @@ export class Element extends ElementParameterizable {
362
342
  }
363
343
  if( visible === true ) {
364
344
  element.nodes.forEach( node => {
365
- injectTransformNodeMetadata( node, { visibility: node.isEnabled() } );
345
+ injectNodeMetadata( node, { visibility: node.isEnabled() } );
366
346
  activate( node );
367
347
  } );
368
348
  } else if( visible === false ) {
369
349
  element.nodes.forEach( node => {
370
- injectTransformNodeMetadata( node, { visibility: node.isEnabled() } );
350
+ injectNodeMetadata( node, { visibility: node.isEnabled() } );
371
351
  deactivate( node );
372
352
  } );
373
353
  }
@@ -383,7 +363,7 @@ export class Element extends ElementParameterizable {
383
363
  async ( element: Element, oldValue: ParameterValue, newValue: ParameterValue ) => {
384
364
  const material = element.variant.getMaterial( newValue.toString() );
385
365
  element.nodes.forEach( node => {
386
- assertTransformNode(node, (node: TransformNode) => {
366
+ assertTransformNode(node, (node: AbstractMesh) => {
387
367
  if( node instanceof InstancedMesh ) {
388
368
  throw new Error( `Changing parameter "${Parameter.MATERIAL}" ` +
389
369
  `of an InstancedMesh is not supported. ` +
@@ -398,19 +378,19 @@ export class Element extends ElementParameterizable {
398
378
  async ( element: Element, oldValue: ParameterValue, newValue: ParameterValue ) => {
399
379
  const color = Parameter.parseColor( newValue );
400
380
  element.nodes.forEach( node => {
401
- assertTransformNode(node, (node: TransformNode) => {
381
+ assertTransformNode(node, (node: AbstractMesh) => {
402
382
  if( node instanceof InstancedMesh ) {
403
383
  throw new Error( `Changing parameter "${Parameter.MATERIAL_COLOR}" ` +
404
384
  `of an InstancedMesh is not supported. ` +
405
385
  `Tried to change node "${node.id}" on element "${element.id}".` );
406
386
  }
407
387
  });
408
- if( !get( node.metadata, 'dirty.material' ) ) {
388
+ if( !has( node.metadata, 'dirty.material' ) ) {
409
389
  cloneTransformNodeMaterial( node );
410
390
  }
411
- if( !get( node.metadata, 'dirty.material.color' ) ) {
391
+ if( !has( node.metadata, 'dirty.material.color' ) ) {
412
392
  // inject initial value and mark as dirty
413
- injectTransformNodeMetadata( node, { dirty: { material: { color: oldValue } } } );
393
+ injectNodeMetadata( node, { dirty: { material: { color: oldValue } } } );
414
394
  }
415
395
  setMaterialColor( node, color );
416
396
  } );
@@ -420,19 +400,19 @@ export class Element extends ElementParameterizable {
420
400
  async ( element: Element, oldValue: ParameterValue, newValue: ParameterValue ) => {
421
401
  const metallness = Parameter.parseNumber( newValue );
422
402
  element.nodes.forEach( node => {
423
- assertTransformNode(node, (node: TransformNode) => {
403
+ assertTransformNode(node, (node: AbstractMesh) => {
424
404
  if( node instanceof InstancedMesh ) {
425
405
  throw new Error( `Changing parameter "${Parameter.MATERIAL_METALLNESS}" ` +
426
406
  `of an InstancedMesh is not supported. ` +
427
407
  `Tried to change node "${node.id}" on element "${element.id}".` );
428
408
  }
429
409
  });
430
- if( !get( node.metadata, 'dirty.material' ) ) {
410
+ if( !has( node.metadata, 'dirty.material' ) ) {
431
411
  cloneTransformNodeMaterial( node );
432
412
  }
433
- if( !get( node.metadata, 'dirty.material.metallness' ) ) {
413
+ if( !has( node.metadata, 'dirty.material.metallness' ) ) {
434
414
  // inject initial value and mark as dirty
435
- injectTransformNodeMetadata( node, { dirty: { material: { metallness: oldValue } } } );
415
+ injectNodeMetadata( node, { dirty: { material: { metallness: oldValue } } } );
436
416
  }
437
417
  setMaterialMetallness( node, metallness );
438
418
  } );
@@ -442,19 +422,19 @@ export class Element extends ElementParameterizable {
442
422
  async ( element: Element, oldValue: ParameterValue, newValue: ParameterValue ) => {
443
423
  const roughness = Parameter.parseNumber( newValue );
444
424
  element.nodes.forEach( node => {
445
- assertTransformNode(node, (node: TransformNode) => {
425
+ assertTransformNode(node, (node: AbstractMesh) => {
446
426
  if( node instanceof InstancedMesh ) {
447
427
  throw new Error( `Changing parameter "${Parameter.MATERIAL_ROUGHNESS}" ` +
448
428
  `of an InstancedMesh is not supported. ` +
449
429
  `Tried to change node "${node.id}" on element "${element.id}".` );
450
430
  }
451
431
  });
452
- if( !get( node.metadata, 'dirty.material' ) ) {
432
+ if( !has( node.metadata, 'dirty.material' ) ) {
453
433
  cloneTransformNodeMaterial( node );
454
434
  }
455
- if( !get( node.metadata, 'dirty.material.roughness' ) ) {
435
+ if( !has( node.metadata, 'dirty.material.roughness' ) ) {
456
436
  // inject initial value and mark as dirty
457
- injectTransformNodeMetadata( node, { dirty: { material: { roughness: oldValue } } } );
437
+ injectNodeMetadata( node, { dirty: { material: { roughness: oldValue } } } );
458
438
  }
459
439
  setMaterialRoughness( node, roughness );
460
440
  } );
@@ -510,7 +490,7 @@ export class Element extends ElementParameterizable {
510
490
  // Add/Remove meshes to previously created highlight layers.
511
491
  if( highlighted === true ) {
512
492
  element.nodes.forEach( node => {
513
- assertTransformNode(node, (node: TransformNode) => {
493
+ assertTransformNode(node, (node: AbstractMesh) => {
514
494
  if( node instanceof InstancedMesh ) {
515
495
  throw new Error( `Changing parameter "${Parameter.HIGHLIGHTED}" ` +
516
496
  `of an InstancedMesh is not supported. ` +
@@ -537,35 +517,61 @@ export class Element extends ElementParameterizable {
537
517
  this._parameterObservers.set( Parameter.POSITION, [
538
518
  async ( element: Element, oldValue: ParameterValue, newValue: ParameterValue ) => {
539
519
  // we have to deal just with root nodes here due to relative impacts in a node tree
540
- element.nodes.forEach( node => {
541
- // remember absolute position and reset it before translating
542
- if( !get( node.metadata, 'position' ) ) {
543
- node.metadata.position = node.absolutePosition.clone();
544
- }
545
- node.setAbsolutePosition( node.metadata.position );
546
- // move
547
- const distance = Parameter.parseVector( newValue );
548
- node.translate( Axis.X, distance.x, Space.WORLD );
549
- node.translate( Axis.Y, distance.y, Space.WORLD );
550
- node.translate( Axis.Z, distance.z, Space.WORLD );
551
- } );
520
+ element.nodes.forEach( node => moveTransformNode( node, Parameter.parseVector( newValue ) ) );
552
521
  }
553
522
  ] );
554
523
  this._parameterObservers.set( Parameter.ROTATION, [
555
524
  async ( element: Element, oldValue: ParameterValue, newValue: ParameterValue ) => {
525
+ // The current implementation (rotating around coordinates 0,0,0) implicitly mutates the position of a node.
526
+ // Since a user expects the rotation after the positioning, we have to manually fire the position observers.
527
+ // Without calling these observers, the pivot and the position of a node is initially the same before rotating,
528
+ // so there is no rotation happening at all.
529
+ if( Parameter.POSITION in element.inheritedParameters ) {
530
+ await element.commitParameter( Parameter.POSITION, element.inheritedParameters[Parameter.POSITION] );
531
+ }
556
532
  // we have to deal just with root nodes here due to relative impacts in a node tree
557
- element.nodes.forEach( node => {
558
- // remember absolute rotation and reset it before translating
559
- if(!get( node.metadata, 'rotation' ) ) {
560
- node.metadata.rotation = node.rotation.clone();
533
+ element.nodes.forEach( node => rotateTransformNode( node, Parameter.parseRotation( newValue ) ) );
534
+ }
535
+ ] );
536
+ this._parameterObservers.set( Parameter.CAST_SHADOW, [
537
+ async ( element: Element, oldValue: ParameterValue, newValue: ParameterValue ) => {
538
+ let castShadow;
539
+ try {
540
+ castShadow = Parameter.parseBoolean( newValue );
541
+ } catch( e ) {
542
+ return;
543
+ }
544
+ let lightCsl = element.inheritedParameters[Parameter.CAST_SHADOW_FROM_LIGHTS];
545
+ if( ! lightCsl ) {
546
+ lightCsl = element.variant.inheritedViewerLights.map( l => l.name ).join( ',' );
547
+ }
548
+ if( castShadow === true ) {
549
+ await this.castShadowValueHandler( lightCsl, addToShadowGenerator );
550
+ }
551
+ if( castShadow === false ) {
552
+ await this.castShadowValueHandler( lightCsl, removeFromShadowGenerator );
553
+ }
554
+ }
555
+ ] );
556
+ this._parameterObservers.set( Parameter.CAST_SHADOW_FROM_LIGHTS, [
557
+ async ( element: Element, oldValue: ParameterValue, newValue: ParameterValue ) => {
558
+ // TODO: Possible performance issue in combination with CAST_SHADOW, since both observers are initially called
559
+ // when CAST_SHADOW and CAST_SHADOW_FROM_LIGHTS are defined in the spec.
560
+ const lightCsl = element.variant.inheritedViewerLights.map( l => l.name ).join( ',' );
561
+ // cleanup all shadow generators
562
+ await this.castShadowValueHandler( lightCsl, removeFromShadowGenerator );
563
+ if( element.castShadow === true ) {
564
+ // if newValue is undefined or '' then set newValue to lightCsl (use all lights)
565
+ if( !newValue ) {
566
+ newValue = lightCsl;
561
567
  }
562
- node.rotation = node.metadata.rotation;
563
- // rotate
564
- const rotation = Parameter.parseRotation( newValue );
565
- node.rotate( Axis.X, rotation.x, Space.WORLD );
566
- node.rotate( Axis.Y, rotation.y, Space.WORLD );
567
- node.rotate( Axis.Z, rotation.z, Space.WORLD );
568
- } );
568
+ await this.castShadowValueHandler( newValue, addToShadowGenerator );
569
+ }
570
+ }
571
+ ] );
572
+ this._parameterObservers.set( Parameter.RECEIVE_SHADOWS, [
573
+ async ( element: Element, oldValue: ParameterValue, newValue: ParameterValue ) => {
574
+ element.nodes.forEach( node => setReceiveShadows( node, Parameter.parseBoolean( newValue ) ) );
569
575
  }
570
576
  ] );
571
577
  return this;
@@ -614,4 +620,25 @@ export class Element extends ElementParameterizable {
614
620
  } );
615
621
  }
616
622
 
623
+ /**
624
+ * Handles callback for given light parameter.
625
+ */
626
+ private async castShadowValueHandler( lightCsl: ParameterValue, mutator: CallableFunction ) {
627
+ let lights: Light[] = [];
628
+ for( const lightName of Parameter.parseCommaSeparatedList( lightCsl ) ) {
629
+ const viewerLight = await this.variant.getViewerLight( lightName );
630
+ if( viewerLight ) {
631
+ lights.push( viewerLight.light );
632
+ }
633
+ }
634
+ const shadowGenerators = lights
635
+ .map( light => light?.getShadowGenerator() as ShadowGenerator )
636
+ .filter( Boolean );
637
+ shadowGenerators.forEach( generator => {
638
+ this.nodes.forEach( node => {
639
+ mutator( generator, node );
640
+ } );
641
+ } );
642
+ }
643
+
617
644
  }
@@ -262,13 +262,28 @@ export class Event {
262
262
  * * {@link Element}
263
263
  *
264
264
  * Payload:
265
- * * variant: {@link Element}
265
+ * * element: {@link Element}
266
266
  * * parameter: string
267
267
  * * oldValue: string
268
268
  * * newValue: string
269
269
  */
270
270
  public static readonly ELEMENT_PARAMETER_COMMITTED = 'elementParameterCommitted';
271
271
 
272
+ /**
273
+ * Fired after a parameter on an {@link ViewerLight} has been committed.
274
+ *
275
+ * Scopes:
276
+ * * Global
277
+ * * {@link ViewerLight}
278
+ *
279
+ * Payload:
280
+ * * light: {@link ViewerLight}
281
+ * * parameter: string
282
+ * * oldValue: string
283
+ * * newValue: string
284
+ */
285
+ public static readonly VIEWER_LIGHT_PARAMETER_COMMITTED = 'viewerLightParameterCommitted';
286
+
272
287
  /**
273
288
  * Fired after a parameter on an {@link SceneManager} has been committed.
274
289
  *
@@ -1,6 +1,6 @@
1
1
  import { Color3 } from '@babylonjs/core/Maths/math.color';
2
2
  import { Vector3 } from '@babylonjs/core/Maths/math.vector';
3
- import { isNumber, isString } from 'lodash-es';
3
+ import { get, isEmpty, isNumber, isString } from 'lodash-es';
4
4
 
5
5
 
6
6
  /**
@@ -16,19 +16,20 @@ export class Parameter {
16
16
  private constructor() {}
17
17
 
18
18
  /**
19
- * Mutates the visibility. Helper methods are {@link ElementParameterizable.show} and
20
- * {@link ElementParameterizable.hide}. Getter and setter is {@link ElementParameterizable.visible}.
19
+ * Mutates the visibility. Helper methods are {@link VariantParameterizable.show} and
20
+ * {@link VariantParameterizable.hide}. Getter and setter is {@link VariantParameterizable.visible}.
21
21
  *
22
22
  * Scopes:
23
23
  * * {@link Element}
24
24
  * * {@link Variant}
25
+ * * {@link ViewerLight} Available for all light types
25
26
  *
26
27
  * @parser {@link parseBoolean}
27
28
  */
28
29
  public static readonly VISIBLE = 'visible';
29
30
 
30
31
  /**
31
- * Stretches or compresses some meshes. Getter and setter is {@link ElementParameterizable.scaling}.
32
+ * Stretches or compresses some meshes. Getter and setter is {@link VariantParameterizable.scaling}.
32
33
  *
33
34
  * Scopes:
34
35
  * * {@link Element}
@@ -39,7 +40,7 @@ export class Parameter {
39
40
  public static readonly SCALING = 'scaling';
40
41
 
41
42
  /**
42
- * Mutates the material. Getter and setter is {@link ElementParameterizable.material}. The value of this parameter
43
+ * Mutates the material. Getter and setter is {@link VariantParameterizable.material}. The value of this parameter
43
44
  * represents the ID of a baked-in glTF or custom created material in the {@link Variant} tree.
44
45
  *
45
46
  * Scopes:
@@ -106,7 +107,7 @@ export class Parameter {
106
107
  public static readonly HIGHLIGHT_COLOR = 'highlight.color';
107
108
 
108
109
  /**
109
- * Mutates the highlighted state. Getter and setter is {@link ElementParameterizable.highlighted}.
110
+ * Mutates the highlighted state. Getter and setter is {@link VariantParameterizable.highlighted}.
110
111
  *
111
112
  * Scopes:
112
113
  * * {@link Element}
@@ -117,18 +118,19 @@ export class Parameter {
117
118
  public static readonly HIGHLIGHTED = 'highlighted';
118
119
 
119
120
  /**
120
- * Mutates the position relative to its origin position. Getter and setter is {@link ElementParameterizable.position}.
121
+ * Mutates the position relative to its origin position. Getter and setter is {@link VariantParameterizable.position}.
121
122
  *
122
123
  * Scopes:
123
124
  * * {@link Element}
124
125
  * * {@link Variant}
126
+ * * {@link ViewerLight} Available for [PointLight](https://doc.babylonjs.com/typedoc/classes/babylon.pointlight#position), [SpotLight](https://doc.babylonjs.com/typedoc/classes/babylon.spotlight#position), [DirectionalLight](https://doc.babylonjs.com/typedoc/classes/babylon.directionallight#position)
125
127
  *
126
128
  * @parser {@link parseVector}
127
129
  */
128
130
  public static readonly POSITION = 'position';
129
131
 
130
132
  /**
131
- * Mutates the rotation relative to its origin rotation. Getter and setter is {@link ElementParameterizable.rotation}.
133
+ * Mutates the rotation relative to its origin rotation. Getter and setter is {@link VariantParameterizable.rotation}.
132
134
  *
133
135
  * Scopes:
134
136
  * * {@link Element}
@@ -138,24 +140,126 @@ export class Parameter {
138
140
  */
139
141
  public static readonly ROTATION = 'rotation';
140
142
 
143
+ /**
144
+ * Mutates whether a shadow should be casted or not.
145
+ *
146
+ * Scopes:
147
+ * * {@link Element}
148
+ * * {@link Variant}
149
+ *
150
+ * @parser {@link parseBoolean}
151
+ */
152
+ public static readonly CAST_SHADOW = 'castShadow';
153
+
154
+ /**
155
+ * Mutates from which lights a shadow should be casted.
156
+ *
157
+ * Scopes:
158
+ * * {@link Element}
159
+ * * {@link Variant}
160
+ *
161
+ * @parser {@link parseCommaSeparatedList}
162
+ */
163
+ public static readonly CAST_SHADOW_FROM_LIGHTS = 'castShadow.fromLights';
164
+
165
+ /**
166
+ * Mutates whether to receive shadows or not.
167
+ *
168
+ * Scopes:
169
+ * * {@link Element}
170
+ * * {@link Variant}
171
+ *
172
+ * @parser {@link parseBoolean}
173
+ */
174
+ public static readonly RECEIVE_SHADOWS = 'receiveShadows';
175
+
176
+ /**
177
+ * Mutates the intensity of Lights.
178
+ *
179
+ * Scopes:
180
+ * * {@link ViewerLight} Available for [PointLight](https://doc.babylonjs.com/typedoc/classes/babylon.pointlight#intensity), [SpotLight](https://doc.babylonjs.com/typedoc/classes/babylon.spotlight#intensity), [DirectionalLight](https://doc.babylonjs.com/typedoc/classes/babylon.directionallight#intensity), [HemisphericLight](https://doc.babylonjs.com/typedoc/classes/babylon.hemisphericlight#intensity)
181
+ *
182
+ * @parser {@link parseNumber}
183
+ */
184
+ public static readonly INTENSITY = 'intensity';
185
+
186
+ /**
187
+ * Mutates the direction of Lights.
188
+ *
189
+ * Scopes:
190
+ * * {@link ViewerLight} Available for [PointLight](https://doc.babylonjs.com/typedoc/classes/babylon.pointlight#direction), [SpotLight](https://doc.babylonjs.com/typedoc/classes/babylon.spotlight#direction), [DirectionalLight](https://doc.babylonjs.com/typedoc/classes/babylon.directionallight#direction), [HemisphericLight](https://doc.babylonjs.com/typedoc/classes/babylon.hemisphericlight#direction)
191
+ *
192
+ * @parser {@link parseNumber}
193
+ */
194
+ public static readonly DIRECTION = 'direction';
195
+
196
+ /**
197
+ * Mutates the angle of Lights.
198
+ *
199
+ * Scopes:
200
+ * * {@link ViewerLight} Available for [SpotLight](https://doc.babylonjs.com/typedoc/classes/babylon.spotlight#angle)
201
+ *
202
+ * @parser {@link parseNumber}
203
+ */
204
+ public static readonly ANGLE = 'angle';
205
+
206
+ /**
207
+ * Mutates the exponent of Lights.
208
+ *
209
+ * Scopes:
210
+ * * {@link ViewerLight} Available for [SpotLight](https://doc.babylonjs.com/typedoc/classes/babylon.spotlight#exponent)
211
+ *
212
+ * @parser {@link parseNumber}
213
+ */
214
+ public static readonly EXPONENT = 'exponent';
215
+
216
+ /**
217
+ * Mutates the diffuse color of Lights.
218
+ *
219
+ * Scopes:
220
+ * * {@link ViewerLight} Available for [PointLight](https://doc.babylonjs.com/typedoc/classes/babylon.pointlight#diffuse), [SpotLight](https://doc.babylonjs.com/typedoc/classes/babylon.spotlight#diffuse), [DirectionalLight](https://doc.babylonjs.com/typedoc/classes/babylon.directionallight#diffuse), [HemisphericLight](https://doc.babylonjs.com/typedoc/classes/babylon.hemisphericlight#diffuse)
221
+ *
222
+ * @parser {@link parseNumber}
223
+ */
224
+ public static readonly DIFFUSE = 'diffuse';
225
+
226
+ /**
227
+ * Mutates the specular color of Lights.
228
+ *
229
+ * Scopes:
230
+ * * {@link ViewerLight} Available for [PointLight](https://doc.babylonjs.com/typedoc/classes/babylon.pointlight#specular), [SpotLight](https://doc.babylonjs.com/typedoc/classes/babylon.spotlight#specular), [DirectionalLight](https://doc.babylonjs.com/typedoc/classes/babylon.directionallight#specular), [HemisphericLight](https://doc.babylonjs.com/typedoc/classes/babylon.hemisphericlight#specular)
231
+ *
232
+ * @parser {@link parseNumber}
233
+ */
234
+ public static readonly SPECULAR = 'specular';
235
+
141
236
  /**
142
237
  * The {@link ParameterDeclarations} for all parameters in this class.
143
238
  */
144
239
  public static get declarations(): ParameterDeclarations {
145
- const declaration: ParameterDeclarations = {};
146
- declaration[Parameter.VISIBLE] = { type: 'boolean' };
147
- declaration[Parameter.SCALING] = { type: 'number' };
148
- declaration[Parameter.HIGHLIGHTED] = { type: 'boolean' };
149
- declaration[Parameter.MATERIAL] = { type: 'string' };
150
- declaration[Parameter.MATERIAL_COLOR] = { type: 'color' };
151
- declaration[Parameter.MATERIAL_METALLNESS] = { type: 'number' };
152
- declaration[Parameter.MATERIAL_ROUGHNESS] = { type: 'number' };
153
- declaration[Parameter.HIGHLIGHT_ENABLED] = { type: 'boolean' };
154
- declaration[Parameter.HIGHLIGHT_COLOR] = { type: 'color' };
155
- declaration[Parameter.HIGHLIGHTED] = { type: 'boolean' };
156
- declaration[Parameter.POSITION] = { type: 'vector' };
157
- declaration[Parameter.ROTATION] = { type: 'vector' };
158
- return declaration;
240
+ const declarations: ParameterDeclarations = {};
241
+ declarations[Parameter.VISIBLE] = { type: 'boolean', parser: Parameter.parseBoolean };
242
+ declarations[Parameter.SCALING] = { type: 'number', parser: Parameter.parseNumber };
243
+ declarations[Parameter.HIGHLIGHTED] = { type: 'boolean', parser: Parameter.parseBoolean };
244
+ declarations[Parameter.MATERIAL] = { type: 'string' };
245
+ declarations[Parameter.MATERIAL_COLOR] = { type: 'color', parser: Parameter.parseColor };
246
+ declarations[Parameter.MATERIAL_METALLNESS] = { type: 'number', parser: Parameter.parseNumber };
247
+ declarations[Parameter.MATERIAL_ROUGHNESS] = { type: 'number', parser: Parameter.parseNumber };
248
+ declarations[Parameter.HIGHLIGHT_ENABLED] = { type: 'boolean', parser: Parameter.parseBoolean };
249
+ declarations[Parameter.HIGHLIGHT_COLOR] = { type: 'color', parser: Parameter.parseColor };
250
+ declarations[Parameter.HIGHLIGHTED] = { type: 'boolean', parser: Parameter.parseBoolean };
251
+ declarations[Parameter.POSITION] = { type: 'vector', parser: Parameter.parseVector };
252
+ declarations[Parameter.ROTATION] = { type: 'vector', parser: Parameter.parseVector };
253
+ declarations[Parameter.CAST_SHADOW] = { type: 'boolean', parser: Parameter.parseBoolean };
254
+ declarations[Parameter.CAST_SHADOW_FROM_LIGHTS] = { type: 'csl', parser: Parameter.parseCommaSeparatedList };
255
+ declarations[Parameter.RECEIVE_SHADOWS] = { type: 'boolean', parser: Parameter.parseBoolean };
256
+ declarations[Parameter.INTENSITY] = { type: 'number', parser: Parameter.parseNumber };
257
+ declarations[Parameter.DIRECTION] = { type: 'vector', parser: Parameter.parseVector };
258
+ declarations[Parameter.ANGLE] = { type: 'number', parser: Parameter.parseNumber };
259
+ declarations[Parameter.EXPONENT] = { type: 'number', parser: Parameter.parseNumber };
260
+ declarations[Parameter.DIFFUSE] = { type: 'color', parser: Parameter.parseColor };
261
+ declarations[Parameter.SPECULAR] = { type: 'color', parser: Parameter.parseColor };
262
+ return declarations;
159
263
  }
160
264
 
161
265
  /**
@@ -179,6 +283,24 @@ export class Parameter {
179
283
  return all;
180
284
  }
181
285
 
286
+ /**
287
+ * Parses given {@link ParameterBag} with given {@link ParameterDeclarations}.
288
+ */
289
+ public static parseFromDeclarations( declarations: ParameterDeclarations, parameterBag: ParameterBag ): ParsedParameterBag {
290
+ const parameters: ParsedParameterBag = {};
291
+ for( const parameter in parameterBag ) {
292
+ const value = get( parameterBag, parameter );
293
+ parameters[parameter] = value;
294
+ const declaration: ParameterDeclaration = declarations[parameter];
295
+ if( declaration ) {
296
+ if( !isEmpty( value ) && declaration.parser ) {
297
+ parameters[parameter] = declaration.parser( value );
298
+ }
299
+ }
300
+ }
301
+ return parameters;
302
+ }
303
+
182
304
  /**
183
305
  * Parses a string of format `'(x,y,z)'` to a `Vector3`.
184
306
  */
@@ -274,4 +396,13 @@ export class Parameter {
274
396
  return parseFloat( value.toString() );
275
397
  }
276
398
 
399
+ /**
400
+ * Parses a string with comma separated values to a list of strings.
401
+ */
402
+ public static parseCommaSeparatedList( value: ParameterValue ): string[] {
403
+ return value.toString()
404
+ .split( ',' )
405
+ .map( s => s.trim() );
406
+ }
407
+
277
408
  }