@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.
- package/README.md +3 -1
- package/dist/lib-cjs/api/classes/element.d.ts +14 -9
- package/dist/lib-cjs/api/classes/element.js +148 -87
- package/dist/lib-cjs/api/classes/element.js.map +1 -1
- package/dist/lib-cjs/api/classes/event.d.ts +15 -1
- package/dist/lib-cjs/api/classes/event.js +15 -1
- package/dist/lib-cjs/api/classes/event.js.map +1 -1
- package/dist/lib-cjs/api/classes/parameter.d.ts +101 -7
- package/dist/lib-cjs/api/classes/parameter.js +141 -21
- package/dist/lib-cjs/api/classes/parameter.js.map +1 -1
- package/dist/lib-cjs/api/classes/parameterObservable.js +11 -36
- package/dist/lib-cjs/api/classes/parameterObservable.js.map +1 -1
- package/dist/lib-cjs/api/classes/placementAnimation.d.ts +2 -2
- package/dist/lib-cjs/api/classes/placementAnimation.js +11 -0
- package/dist/lib-cjs/api/classes/placementAnimation.js.map +1 -1
- package/dist/lib-cjs/api/classes/variant.d.ts +48 -4
- package/dist/lib-cjs/api/classes/variant.js +320 -46
- package/dist/lib-cjs/api/classes/variant.js.map +1 -1
- package/dist/lib-cjs/api/classes/variantInstance.d.ts +5 -1
- package/dist/lib-cjs/api/classes/variantInstance.js +10 -0
- package/dist/lib-cjs/api/classes/variantInstance.js.map +1 -1
- package/dist/lib-cjs/api/classes/viewer.d.ts +6 -3
- package/dist/lib-cjs/api/classes/viewer.js +140 -59
- package/dist/lib-cjs/api/classes/viewer.js.map +1 -1
- package/dist/lib-cjs/api/internal/sceneSetup.d.ts +5 -1
- package/dist/lib-cjs/api/internal/sceneSetup.js +75 -71
- package/dist/lib-cjs/api/internal/sceneSetup.js.map +1 -1
- package/dist/lib-cjs/api/util/babylonHelper.d.ts +54 -4
- package/dist/lib-cjs/api/util/babylonHelper.js +160 -8
- package/dist/lib-cjs/api/util/babylonHelper.js.map +1 -1
- package/dist/lib-cjs/api/util/globalTypes.d.ts +62 -8
- package/dist/lib-cjs/api/util/resourceHelper.d.ts +13 -8
- package/dist/lib-cjs/api/util/resourceHelper.js +14 -14
- package/dist/lib-cjs/api/util/resourceHelper.js.map +1 -1
- package/dist/lib-cjs/index.d.ts +24 -22
- package/dist/lib-cjs/index.js +42 -38
- package/dist/lib-cjs/index.js.map +1 -1
- package/package.json +5 -5
- package/src/api/classes/element.ts +118 -91
- package/src/api/classes/event.ts +16 -1
- package/src/api/classes/parameter.ts +153 -22
- package/src/api/classes/parameterObservable.ts +9 -31
- package/src/api/classes/{elementParameterizable.ts → parameterizable.ts} +12 -1
- package/src/api/classes/placementAnimation.ts +10 -0
- package/src/api/classes/variant.ts +187 -40
- package/src/api/classes/variantInstance.ts +8 -1
- package/src/api/classes/variantParameterizable.ts +73 -0
- package/src/api/classes/viewer.ts +83 -17
- package/src/api/classes/viewerLight.ts +330 -0
- package/src/api/internal/sceneSetup.ts +99 -109
- package/src/api/util/babylonHelper.ts +173 -10
- package/src/api/util/globalTypes.ts +71 -10
- package/src/api/util/resourceHelper.ts +16 -16
- package/src/api/util/stringHelper.ts +26 -0
- package/src/dev.ts +3 -7
- package/src/index.ts +27 -23
- 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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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(
|
|
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
|
+
}
|