@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
@@ -3,9 +3,11 @@ import { PickingInfo } from '@babylonjs/core/Collisions/pickingInfo';
3
3
  import { BoundingInfo } from '@babylonjs/core/Culling/boundingInfo';
4
4
  import { Engine } from '@babylonjs/core/Engines/engine';
5
5
  import { HighlightLayer } from '@babylonjs/core/Layers/highlightLayer';
6
+ import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial';
7
+ import { DynamicTexture } from '@babylonjs/core/Materials/Textures/dynamicTexture';
8
+ import { Color3 } from '@babylonjs/core/Maths/math.color';
6
9
  import { Vector3 } from '@babylonjs/core/Maths/math.vector';
7
10
  import { Mesh } from '@babylonjs/core/Meshes/mesh';
8
- import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
9
11
  import { ScreenshotTools } from '@babylonjs/core/Misc/screenshotTools';
10
12
  import { Scene } from '@babylonjs/core/scene';
11
13
  import { isString } from 'lodash-es';
@@ -15,7 +17,7 @@ import { AnimationManager } from '../manager/animationManager';
15
17
  import { SceneManager } from '../manager/sceneManager';
16
18
  import { VariantInstanceManager } from '../manager/variantInstanceManager';
17
19
  import { SpecStorage } from '../store/specStorage';
18
- import { debounce, loadJson } from '../util/resourceHelper';
20
+ import { debounce, loadJson, sleep } from '../util/resourceHelper';
19
21
  import { Event } from './event';
20
22
  import { EventBroadcaster } from './eventBroadcaster';
21
23
  import { Parameter } from './parameter';
@@ -145,8 +147,14 @@ export class Viewer extends EventBroadcaster {
145
147
  }
146
148
  // resize handler
147
149
  window.addEventListener( 'resize', debounce( this.resize.bind( this ), 100 ) );
150
+ // wait until scene is completely ready
151
+ await this.scene.whenReadyAsync();
148
152
  // event broadcasting
149
153
  this.broadcastEvent( Event.BOOTSTRAP_END, this );
154
+ // render loop
155
+ this.engine.runRenderLoop( () => {
156
+ this.scene.render();
157
+ } );
150
158
  return this;
151
159
  }
152
160
 
@@ -264,36 +272,47 @@ export class Viewer extends EventBroadcaster {
264
272
  /**
265
273
  * Calculates the bounding box from all visible meshes on the scene.
266
274
  */
267
- public calculateBoundingBox(): Mesh {
275
+ public async calculateBoundingBox(): Promise<Mesh> {
268
276
  if( this.scene.meshes.length === 0 ) {
269
277
  throw new Error( 'There are currently no meshes on the scene.' );
270
278
  }
271
- let max = new Vector3( 0, 0, 0 );
272
- let min = new Vector3( 0, 0, 0 );
273
- this.scene.meshes.filter( mesh => mesh.isVisible ).forEach( mesh => {
274
- max = Vector3.Maximize( max, mesh.getBoundingInfo().boundingBox.maximumWorld );
275
- min = Vector3.Minimize( min, mesh.getBoundingInfo().boundingBox.minimumWorld );
276
- } );
277
- let boundingBox = this.scene.getMeshByName( '__bounding_box__' ) as Mesh;
279
+ this.scene.render(); // XXX: workaround for BoundingBox not respecting render loop
280
+ const bbName = '__bounding_box__';
281
+
282
+ const { max, min } = this.scene.meshes.filter( mesh => {
283
+ const isEnabled = mesh.isEnabled();
284
+ // ignore the existing bounding box mesh for calculating the current one
285
+ const isNotBBoxMesh = bbName !== mesh.id;
286
+ // ignore meshes with invalid bounding infos
287
+ const hasValidBBoxInfo = mesh.getBoundingInfo().boundingSphere.radius > 0;
288
+ return isEnabled && isNotBBoxMesh && hasValidBBoxInfo;
289
+ } ).reduce( ( accBBoxMinMax, curMesh, idx ) => {
290
+ const bBox = curMesh.getBoundingInfo().boundingBox;
291
+ // use the first entry in the array as default value and get the resulting maximum/minimum values
292
+ const max = ( idx === 0 ) ? bBox.maximumWorld : Vector3.Maximize( accBBoxMinMax.max, bBox.maximumWorld )
293
+ const min = ( idx === 0 ) ? bBox.minimumWorld : Vector3.Minimize( accBBoxMinMax.min, bBox.minimumWorld )
294
+ return { max, min }
295
+ }, { max: new Vector3(), min: new Vector3() } );
296
+
297
+ let boundingBox = this.scene.getMeshByName( bbName ) as Mesh;
278
298
  if( !boundingBox ) {
279
- boundingBox = new Mesh( '__bounding_box__', this.scene );
299
+ boundingBox = new Mesh( bbName, this.scene );
280
300
  }
281
301
  boundingBox.setBoundingInfo( new BoundingInfo( min, max ) );
282
- //boundingBox.showBoundingBox = true;
283
302
  return boundingBox;
284
303
  }
285
304
 
286
305
  /**
287
306
  * Focuses the camera to see every visible mesh in scene and tries to optimize wheel precision and panning.
288
307
  */
289
- public autofocusActiveCamera( settings?: AutofocusSettings ) {
308
+ public async autofocusActiveCamera( settings?: AutofocusSettings ) {
290
309
  const activeCamera = this.scene.activeCamera;
291
310
  if( !activeCamera ) {
292
311
  throw new Error( 'No active camera found when using autofocus feature.' );
293
312
  }
294
313
  if( activeCamera instanceof ArcRotateCamera ) {
295
314
  // calculate some values
296
- const boundingBox = this.calculateBoundingBox();
315
+ const boundingBox = await this.calculateBoundingBox();
297
316
  const size = boundingBox.getBoundingInfo().maximum.subtract( boundingBox.getBoundingInfo().minimum );
298
317
  let radius = size.length() * (settings?.radiusFactor ?? 1.5);
299
318
  if( !isFinite( radius ) ) {
@@ -358,6 +377,56 @@ export class Viewer extends EventBroadcaster {
358
377
  return this;
359
378
  }
360
379
 
380
+ /**
381
+ * Show coordinate system with given dimension (for debugging purpose).
382
+ */
383
+ public showWorldCoordinates( dimension: number ) {
384
+ const scene = this.scene;
385
+ const makeTextPlane = function( text: string, color: string, size: number ) {
386
+ const dynamicTexture = new DynamicTexture( 'DynamicTexture', 50, scene, true );
387
+ dynamicTexture.hasAlpha = true;
388
+ dynamicTexture.drawText( text, 5, 40, 'bold 36px Arial', color, 'transparent', true );
389
+ const plane = Mesh.CreatePlane( 'TextPlane', size, scene, true );
390
+ plane.material = new StandardMaterial( 'TextPlaneMaterial', scene );
391
+ plane.material.backFaceCulling = false;
392
+ // @ts-ignore
393
+ plane.material.specularColor = new Color3( 0, 0, 0 );
394
+ // @ts-ignore
395
+ plane.material.diffuseTexture = dynamicTexture;
396
+ return plane;
397
+ };
398
+ const axisX = Mesh.CreateLines( 'axisX', [
399
+ Vector3.Zero(),
400
+ new Vector3( dimension, 0, 0 ),
401
+ new Vector3( dimension * 0.95, 0.05 * dimension, 0 ),
402
+ new Vector3( dimension, 0, 0 ),
403
+ new Vector3( dimension * 0.95, -0.05 * dimension, 0 )
404
+ ], scene );
405
+ axisX.color = new Color3( 1, 0, 0 );
406
+ const xChar = makeTextPlane( 'X', 'red', dimension / 10 );
407
+ xChar.position = new Vector3( 0.9 * dimension, -0.05 * dimension, 0 );
408
+ const axisY = Mesh.CreateLines( 'axisY', [
409
+ Vector3.Zero(),
410
+ new Vector3( 0, dimension, 0 ),
411
+ new Vector3( -0.05 * dimension, dimension * 0.95, 0 ),
412
+ new Vector3( 0, dimension, 0 ),
413
+ new Vector3( 0.05 * dimension, dimension * 0.95, 0 )
414
+ ], scene );
415
+ axisY.color = new Color3( 0, 1, 0 );
416
+ const yChar = makeTextPlane( 'Y', 'green', dimension / 10 );
417
+ yChar.position = new Vector3( 0, 0.9 * dimension, -0.05 * dimension );
418
+ const axisZ = Mesh.CreateLines( 'axisZ', [
419
+ Vector3.Zero(),
420
+ new Vector3( 0, 0, dimension ),
421
+ new Vector3( 0, -0.05 * dimension, dimension * 0.95 ),
422
+ new Vector3( 0, 0, dimension ),
423
+ new Vector3( 0, 0.05 * dimension, dimension * 0.95 )
424
+ ], scene );
425
+ axisZ.color = new Color3( 0, 0, 1 );
426
+ const zChar = makeTextPlane( 'Z', 'blue', dimension / 10 );
427
+ zChar.position = new Vector3( 0, 0.05 * dimension, 0.9 * dimension );
428
+ }
429
+
361
430
  /**
362
431
  * @emits {@link Event.SCENE_PROCESSING_START}
363
432
  * @emits {@link Event.SCENE_PROCESSING_END}
@@ -393,9 +462,6 @@ export class Viewer extends EventBroadcaster {
393
462
  this._sceneManager = await SceneManager.create( scene );
394
463
  this._animationManager = await AnimationManager.create( scene );
395
464
  this.broadcastEvent( Event.SCENE_PROCESSING_END, scene );
396
- engine.runRenderLoop( () => {
397
- scene.render();
398
- } );
399
465
  return scene;
400
466
  }
401
467
 
@@ -0,0 +1,330 @@
1
+ import { PointLight } from '@babylonjs/core';
2
+ import { HemisphericLight } from '@babylonjs/core/Lights/hemisphericLight';
3
+ import { Light } from '@babylonjs/core/Lights/light';
4
+ import { ShadowLight } from '@babylonjs/core/Lights/shadowLight';
5
+ import { ShadowGenerator } from '@babylonjs/core/Lights/Shadows/shadowGenerator';
6
+ import '@babylonjs/core/Lights/Shadows/shadowGeneratorSceneComponent';
7
+ import { Vector3 } from '@babylonjs/core/Maths/math.vector';
8
+ import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
9
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_lights_punctual';
10
+ import { get, isEmpty, isString, set } from 'lodash-es';
11
+ import {
12
+ cloneNodeWithParents,
13
+ disableNodeWithParents,
14
+ enableNodeWithParents,
15
+ getRootNode,
16
+ injectNodeMetadata,
17
+ moveTransformNode,
18
+ rotateTransformNode
19
+ } from './../util/babylonHelper';
20
+ import { DottedPath } from './dottedPath';
21
+ import { Parameter } from './parameter';
22
+ import { Variant } from './variant';
23
+ import { VariantParameterizable } from './variantParameterizable';
24
+
25
+ /**
26
+ * A {@link ViewerLight} of a {@link Variant}. Acts as a container for BabylonJS lights. Lives only in the context of a
27
+ * {@link Variant}.
28
+ */
29
+ export class ViewerLight extends VariantParameterizable {
30
+
31
+ protected _light: Light | undefined;
32
+
33
+ /**
34
+ * Constructor.
35
+ */
36
+ protected constructor( public readonly variant: Variant,
37
+ public readonly name: string ) {
38
+ super( variant, name );
39
+ this.addParameterObservers();
40
+ }
41
+
42
+ /**
43
+ * Creates a {@link ViewerLight} with given name.
44
+ */
45
+ public static async create( variant: Variant, name: string ): Promise<ViewerLight> {
46
+ const viewerLight = new ViewerLight( variant, name );
47
+ viewerLight._light = await viewerLight.createBabylonLightFromDefinition( viewerLight.definition );
48
+ return viewerLight;
49
+ }
50
+
51
+ /**
52
+ * The wrapped Light.
53
+ */
54
+ get light(): Light {
55
+ if( !this._light ) {
56
+ throw new Error( `Light for ViewerLight "${this.name}" has not been properly initialized.` );
57
+ }
58
+ return this._light!;
59
+ }
60
+
61
+ /**
62
+ * The {@link DottedPath} in the built tree of {@link ViewerLight}s.
63
+ * E.g. "_.top-1.sub-2.sub-sub-3.el-1"
64
+ */
65
+ get dottedPath(): DottedPath {
66
+ return DottedPath.create( this.variant.dottedPath ).addPart( this.name );
67
+ }
68
+
69
+ /**
70
+ * The id representing a {@link DottedPath}.
71
+ */
72
+ get id(): string {
73
+ const dottedPath = DottedPath.create( this.dottedPath );
74
+ dottedPath.shiftPart(); // remove root
75
+ return dottedPath.path;
76
+ }
77
+
78
+ /**
79
+ * The {@link LightDefinition} of the {@link ViewerLight}.
80
+ */
81
+ get definition(): LightDefinition {
82
+ const definition = this.variant.structureJson.lights![this.name];
83
+ if( isString( definition ) ) {
84
+ return {
85
+ type: 'baked',
86
+ path: definition
87
+ } as LightDefinition;
88
+ }
89
+ return definition as LightDefinition;
90
+ }
91
+
92
+ /**
93
+ * The type of the {@link ViewerLight}'s light.
94
+ */
95
+ get type(): string {
96
+ return this.light.constructor.name.replace( /light/i, '' ).toLowerCase();
97
+ }
98
+
99
+ /**
100
+ * @see {@link VariantParameterizable.commitParameters}
101
+ * @emit {@link Event.VIEWER_LIGHT_PARAMETER_COMMITTED}
102
+ */
103
+ public async commitParameters( parameters?: ParameterBag ): Promise<VariantParameterizable> {
104
+ return super.commitParameters( parameters );
105
+ }
106
+
107
+ /**
108
+ * Adds the default {@link ParameterObserver}s which are called every time {@link commitParameters} is called.
109
+ */
110
+ protected addParameterObservers(): ViewerLight {
111
+ this._parameterObservers.set( Parameter.VISIBLE, [
112
+ async ( light: ViewerLight, oldValue: ParameterValue, newValue: ParameterValue ) => {
113
+ let visible;
114
+ try {
115
+ visible = Parameter.parseBoolean( newValue );
116
+ } catch( e ) {
117
+ return;
118
+ }
119
+ if( visible === true ) {
120
+ enableNodeWithParents( light.light );
121
+ } else if( visible === false ) {
122
+ disableNodeWithParents( light.light );
123
+ }
124
+ }
125
+ ] );
126
+ this._parameterObservers.set( Parameter.INTENSITY, [
127
+ async ( light: ViewerLight, oldValue: ParameterValue, newValue: ParameterValue ) => {
128
+ set( light.light, 'intensity', Parameter.parseNumber( newValue ) );
129
+ }
130
+ ] );
131
+ this._parameterObservers.set( Parameter.POSITION, [
132
+ async ( light: ViewerLight, oldValue: ParameterValue, newValue: ParameterValue ) => {
133
+ // we have to deal just with root nodes here due to relative impacts in a node tree
134
+ const rootNode = getRootNode( light.light );
135
+ if( rootNode instanceof TransformNode ) {
136
+ moveTransformNode( rootNode, Parameter.parseVector( newValue ) );
137
+ }
138
+ }
139
+ ] );
140
+ this._parameterObservers.set( Parameter.ROTATION, [
141
+ async ( light: ViewerLight, oldValue: ParameterValue, newValue: ParameterValue ) => {
142
+ // The current implementation (rotating around coordinates 0,0,0) implicitly mutates the position of a node.
143
+ // Since a user expects the rotation after the positioning, we have to manually fire the position observers.
144
+ // Without calling these observers, the pivot and the position of a node is initially the same before rotating,
145
+ // so there is no rotation happening at all.
146
+ if( Parameter.POSITION in light.inheritedParameters ) {
147
+ await light.commitParameter( Parameter.POSITION, light.inheritedParameters[Parameter.POSITION] );
148
+ }
149
+ // we have to deal just with root nodes here due to relative impacts in a node tree
150
+ const rootNode = getRootNode( light.light );
151
+ if( rootNode instanceof TransformNode ) {
152
+ rotateTransformNode( rootNode, Parameter.parseRotation( newValue ) );
153
+ }
154
+ }
155
+ ] );
156
+ this._parameterObservers.set( Parameter.SCALING, [
157
+ async ( light: ViewerLight, oldValue: ParameterValue, newValue: ParameterValue ) => {
158
+ // we have to deal just with root nodes here due to relative impacts in a node tree
159
+ const rootNode = getRootNode( light.light );
160
+ if( rootNode instanceof TransformNode ) {
161
+ rootNode.scaling = Parameter.parseScaling( newValue );
162
+ }
163
+ }
164
+ ] );
165
+ this._parameterObservers.set( Parameter.DIRECTION, [
166
+ async ( light: ViewerLight, oldValue: ParameterValue, newValue: ParameterValue ) => {
167
+ if(
168
+ (light.light instanceof ShadowLight && !(light.light instanceof PointLight))
169
+ || light.light instanceof HemisphericLight
170
+ ) {
171
+ set( light.light, 'direction', Parameter.parseVector( newValue ) );
172
+ }
173
+ }
174
+ ] );
175
+ this._parameterObservers.set( Parameter.ANGLE, [
176
+ async ( light: ViewerLight, oldValue: ParameterValue, newValue: ParameterValue ) => {
177
+ if( light.light.getClassName() === 'SpotLight' ) {
178
+ set( light.light, 'angle', Parameter.parseNumber( newValue ) );
179
+ }
180
+ }
181
+ ] );
182
+ this._parameterObservers.set( Parameter.EXPONENT, [
183
+ async ( light: ViewerLight, oldValue: ParameterValue, newValue: ParameterValue ) => {
184
+ if( light.light.getClassName() === 'SpotLight' ) {
185
+ set( light.light, 'exponent', Parameter.parseNumber( newValue ) );
186
+ }
187
+ }
188
+ ] );
189
+ this._parameterObservers.set( Parameter.DIFFUSE, [
190
+ async ( light: ViewerLight, oldValue: ParameterValue, newValue: ParameterValue ) => {
191
+ set( light.light, 'diffuse', Parameter.parseColor( newValue ) );
192
+ }
193
+ ] );
194
+ this._parameterObservers.set( Parameter.SPECULAR, [
195
+ async ( light: ViewerLight, oldValue: ParameterValue, newValue: ParameterValue ) => {
196
+ set( light.light, 'specular', Parameter.parseColor( newValue ) );
197
+ }
198
+ ] );
199
+ return this;
200
+ }
201
+
202
+ /**
203
+ * @param definition
204
+ * @protected
205
+ */
206
+ protected async createBabylonLightFromDefinition( definition: LightDefinition ): Promise<Light> {
207
+ const parameters = Parameter.parseFromDeclarations( Parameter.declarations, this.inheritedParameters );
208
+ const scene = this.variant.viewer.scene;
209
+ let lightId = this.id;
210
+ let babylonLight;
211
+ switch( definition.type ) {
212
+ case 'baked':
213
+ if( !definition['path'] ) {
214
+ throw new Error( `The light "${lightId}" of type "${definition.type}" needs a "path".` );
215
+ }
216
+ const bakedLight = this.variant.inheritedLights.find( l => l.metadata.dottedPath.path === definition['path'] );
217
+ if( bakedLight ) {
218
+ lightId = bakedLight.metadata.dottedPath.clone().unshiftPart( this.id ).path;
219
+ babylonLight = cloneNodeWithParents( bakedLight ) as Light;
220
+ babylonLight!.name = lightId;
221
+ babylonLight!.id = lightId;
222
+ } else {
223
+ throw new Error( `No light found for path "${definition['path']}" in ViewerLight "${lightId}".` );
224
+ }
225
+ break;
226
+ case 'hemispheric':
227
+ if( !parameters['direction'] ) {
228
+ throw new Error( `The ViewerLight "${lightId}" of type "${definition.type}" needs a "direction".` );
229
+ }
230
+ // @ts-ignore
231
+ const hemisphericLightModule = await import(/* webpackChunkName: "hemispheric-light" */ '@babylonjs/core/Lights/hemisphericLight');
232
+ babylonLight = new hemisphericLightModule.HemisphericLight(
233
+ lightId,
234
+ parameters['direction'],
235
+ scene
236
+ );
237
+ break;
238
+ case 'point':
239
+ // @ts-ignore
240
+ const pointLightModule = await import(/* webpackChunkName: "point-light" */ '@babylonjs/core/Lights/pointLight');
241
+ babylonLight = new pointLightModule.PointLight(
242
+ lightId,
243
+ Vector3.Zero(), // position is set via parent TransformNode
244
+ scene
245
+ );
246
+ break;
247
+ case 'directional':
248
+ if( !parameters['direction'] ) {
249
+ throw new Error( `The ViewerLight "${lightId}" of type "${definition.type}" needs a "direction".` );
250
+ }
251
+ // @ts-ignore
252
+ const directionalLightModule = await import(/* webpackChunkName: "directional-light" */ '@babylonjs/core/Lights/directionalLight');
253
+ babylonLight = new directionalLightModule.DirectionalLight(
254
+ lightId,
255
+ parameters['direction'],
256
+ scene
257
+ );
258
+ break;
259
+ case 'spot':
260
+ if( !parameters['direction'] ) {
261
+ throw new Error( `The ViewerLight "${lightId}" of type "${definition.type}" needs a "direction".` );
262
+ }
263
+ if( !parameters['angle'] ) {
264
+ throw new Error( `A ViewerLight of type "${definition.type}" needs an "angle".` );
265
+ }
266
+ if( !parameters['exponent'] ) {
267
+ throw new Error( `The ViewerLight "${lightId}" of type "${definition.type}" needs an "exponent".` );
268
+ }
269
+ // @ts-ignore
270
+ const spotLightModule = await import(/* webpackChunkName: "spot-light" */ '@babylonjs/core/Lights/spotLight');
271
+ babylonLight = new spotLightModule.SpotLight(
272
+ lightId,
273
+ Vector3.Zero(), // position is set via parent TransformNode
274
+ parameters['direction'],
275
+ parameters['angle'],
276
+ parameters['exponent'],
277
+ scene
278
+ );
279
+ break;
280
+ default:
281
+ throw new Error( `The type "${definition.type}" for ViewerLight "${lightId}" is not implemented (yet).` );
282
+ }
283
+ if( !babylonLight ) {
284
+ throw new Error( `The Light for ViewerLight "${lightId}" of type "${definition.type}" could no be created ` +
285
+ `or found.` );
286
+ }
287
+ if( !babylonLight.parent ) {
288
+ // Create pseudo parent since lights do not implement mutations like "rotation" by itself.
289
+ babylonLight.parent = new TransformNode( '__light__', this.variant.viewer.scene, true );
290
+ }
291
+ injectNodeMetadata( babylonLight.parent, {
292
+ variant: this.variant,
293
+ variantParameterizable: this
294
+ } );
295
+ // disable/hide by default
296
+ disableNodeWithParents( babylonLight );
297
+ // process shadow generator
298
+ const shadowGeneratorDefinition = get( definition, 'shadowGenerator' ) as ShadowGeneratorDefinition;
299
+ if( !isEmpty( shadowGeneratorDefinition ) ) {
300
+ if( !(babylonLight instanceof ShadowLight) ) {
301
+ throw new Error( `Using a ShadowGenerator with light type "${definition.type}" is not supported for ` +
302
+ `"${lightId}". Use lights deriving from ShadowLight.` );
303
+ }
304
+ await this.processShadowGenerator( babylonLight, shadowGeneratorDefinition );
305
+ }
306
+ return babylonLight;
307
+ };
308
+
309
+ /**
310
+ * @param babylonLight
311
+ * @param definition
312
+ * @protected
313
+ */
314
+ protected async processShadowGenerator( babylonLight: ShadowLight,
315
+ definition: ShadowGeneratorDefinition ): Promise<ShadowGenerator> {
316
+ const parameterDeclarations: ParameterDeclarations = {};
317
+ // define declarations here if needed in future
318
+ const parameterBag = definition as {} as ParameterBag;
319
+ const parameters = Parameter.parseFromDeclarations( parameterDeclarations, parameterBag );
320
+ const shadowGenerator = new ShadowGenerator( parameters['mapSize'], babylonLight );
321
+ for( const parameter in parameters ) {
322
+ if( parameter === 'mapSize' ) {
323
+ continue;
324
+ }
325
+ set( shadowGenerator, parameter, get( parameters, parameter ) );
326
+ }
327
+ return shadowGenerator;
328
+ };
329
+
330
+ }