@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,180 +1,171 @@
1
-
1
+ import { Camera } from '@babylonjs/core/Cameras/camera';
2
2
  import { Engine } from '@babylonjs/core/Engines/engine';
3
- import '@babylonjs/core/Materials/Textures/Loaders/ddsTextureLoader';
4
3
  import '@babylonjs/core/Helpers/sceneHelpers';
5
- import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial';
6
- import { HDRCubeTexture } from '@babylonjs/core/Materials/Textures/hdrCubeTexture';
7
- import { Texture } from '@babylonjs/core/Materials/Textures/texture';
8
- import { Color3, Color4 } from '@babylonjs/core/Maths/math.color';
4
+ import '@babylonjs/core/Materials/Textures/Loaders/ddsTextureLoader';
5
+ import { Color4 } from '@babylonjs/core/Maths/math.color';
9
6
  import { Vector3 } from '@babylonjs/core/Maths/math.vector';
7
+ import { GroundMesh } from '@babylonjs/core/Meshes/groundMesh';
10
8
  import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder';
11
9
  import { DefaultRenderingPipeline } from '@babylonjs/core/PostProcesses/RenderPipeline/Pipelines/defaultRenderingPipeline';
12
10
  import { Scene } from '@babylonjs/core/scene';
13
- import type { HemisphericLight } from '@babylonjs/core/Lights/hemisphericLight';
14
- import { Camera } from '@babylonjs/core/Cameras/camera';
15
- import { Light } from '@babylonjs/core/Lights/light';
11
+ import { get, isEmpty, set } from 'lodash-es';
16
12
  import { Parameter } from '../classes/parameter';
17
13
 
18
-
19
- const defaultLightning = async function(scene: Scene) {
20
- const defaultSetup: LightDefinition = {
21
- type: 'hemispheric',
22
- };
23
-
24
- const defaultLight = await processLight(scene, 'default_light', defaultSetup) as HemisphericLight;
25
- defaultLight.groundColor = Color3.Black();
26
- defaultLight.diffuse = Color3.White();
27
-
28
- return defaultLight;
29
- };
30
-
31
-
32
- const processLight = async function(scene: Scene, name: string, lightSetup: LightDefinition) {
33
-
34
- let light: Light;
35
-
36
- switch(lightSetup.type ) {
37
- case 'hemispheric':
38
- // @ts-ignore
39
- const hemisphericLightModule = await import(/* webpackChunkName: "hemispheric-light" */ '@babylonjs/core/Lights/hemisphericLight');
40
- light = new hemisphericLightModule.HemisphericLight( name, Vector3.Up(), scene);
41
- break;
42
- case 'point':
43
- // @ts-ignore
44
- const pointLightModule = await import(/* webpackChunkName: "point-light" */ '@babylonjs/core/Lights/pointLight');
45
- light = new pointLightModule.PointLight( name, Vector3.Up(), scene);
46
- break;
47
- }
48
-
49
- return light;
14
+ /**
15
+ * @param scene
16
+ * @param name
17
+ * @param definition
18
+ */
19
+ const processGround = async function( scene: Scene, name: string, definition: GroundDefinition ): Promise<Mesh | GroundMesh> {
20
+ return new Promise( resolve => {
21
+ const _resolve = ( ground: Mesh ) => {
22
+ if( definition.receiveShadows !== undefined ) {
23
+ ground.receiveShadows = definition.receiveShadows;
24
+ }
25
+ resolve( ground );
26
+ };
27
+ switch( definition.type ) {
28
+ case 'baked':
29
+ if( !definition.meshId ) {
30
+ throw new Error( `A baked ground must define a "meshId".` );
31
+ }
32
+ _resolve( scene.getMeshByID( definition.meshId ) as Mesh );
33
+ break;
34
+ case 'ground':
35
+ _resolve( MeshBuilder.CreateGround(
36
+ name,
37
+ definition,
38
+ scene
39
+ ) );
40
+ break;
41
+ case 'heightmap':
42
+ if( !definition.url ) {
43
+ throw new Error( `A heightmap ground must define an "url".` );
44
+ }
45
+ definition.onReady = ( _ground: GroundMesh ) => {
46
+ _resolve( _ground );
47
+ };
48
+ MeshBuilder.CreateGroundFromHeightMap(
49
+ name,
50
+ definition.url,
51
+ definition,
52
+ scene,
53
+ );
54
+ break;
55
+ default:
56
+ throw new Error( `Ground of type "${definition.type}" not implemented (yet).` );
57
+ }
58
+ } );
50
59
  };
51
60
 
52
-
53
- const defaultCamera = async function(scene: Scene) {
54
-
55
- const cameraSetup: CameraDefinition = {
61
+ /**
62
+ * @param scene
63
+ */
64
+ const defaultCamera = async function( scene: Scene ): Promise<Camera> {
65
+ return await processCamera( scene, 'default_camera', {
56
66
  type: 'arc',
57
67
  active: true,
58
- };
59
-
60
- const defaultCamera = await processCamera(scene, 'default_camera', cameraSetup);
61
-
62
- return defaultCamera;
68
+ } );
63
69
  };
64
70
 
65
- const processCamera = async function(scene: Scene, name: string, cameraSetup: CameraDefinition) {
66
-
71
+ /**
72
+ * @param scene
73
+ * @param name
74
+ * @param cameraSetup
75
+ */
76
+ const processCamera = async function( scene: Scene, name: string, cameraSetup: CameraDefinition ): Promise<Camera> {
67
77
  let camera: Camera;
68
-
69
78
  let target = Vector3.Zero();
70
- if(cameraSetup.target) {
71
- target = Parameter.parseVector(cameraSetup.target);
79
+ if( cameraSetup.target ) {
80
+ target = Parameter.parseVector( cameraSetup.target );
72
81
  }
73
-
74
- switch(cameraSetup.type ) {
82
+ switch( cameraSetup.type ) {
75
83
  case 'arc':
76
84
  // @ts-ignore
77
85
  const arcCameraModule = await import(/* webpackChunkName: "arc-rotate-camera" */ '@babylonjs/core/Cameras/arcRotateCamera');
78
- camera = new arcCameraModule.ArcRotateCamera(name, Math.PI / 4, Math.PI / 4, 2, target, scene );
86
+ camera = new arcCameraModule.ArcRotateCamera( name, Math.PI / 4, Math.PI / 4, 2, target, scene );
79
87
  camera.metadata = {
80
88
  alpha: Math.PI / 4,
81
89
  beta: Math.PI / 4,
82
90
  radius: 2
83
91
  };
84
92
  break;
85
-
86
93
  }
87
-
88
- if(cameraSetup.active) {
94
+ if( cameraSetup.active ) {
89
95
  camera.attachControl( scene.getEngine().getRenderingCanvas()!, true );
90
96
  }
91
-
92
97
  if( cameraSetup.fov ) {
93
98
  camera.fov = cameraSetup.fov;
94
99
  }
95
-
96
100
  camera.storeState();
97
101
  return camera;
98
102
  };
99
103
 
100
- const sceneSetup = async function(engine: Engine, sceneJson: SceneJson) {
104
+ /**
105
+ * @param engine
106
+ * @param sceneJson
107
+ */
108
+ const sceneSetup = async function(engine: Engine, sceneJson: SceneJson): Promise<Scene> {
101
109
  const scene = new Scene( engine );
102
110
  scene.clearColor = new Color4(0, 0, 0, 0);
103
-
104
- let cameras: Camera[] = [];
105
- //there is either no "cameras" or "cameras" is empty
106
- if( ! sceneJson.scene.hasOwnProperty('cameras') || (sceneJson.scene.cameras && ! Object.keys(sceneJson.scene.cameras).length ) ) {
107
- const camera = await defaultCamera(scene);
108
-
109
- cameras.push(camera);
110
- } else {
111
- for(let camera in sceneJson.scene.cameras) {
112
- cameras.push(await processCamera(scene, camera, sceneJson.scene.cameras[camera]));
111
+ // cameras
112
+ const cameras: Camera[] = [];
113
+ const cameraDefinitions = get( sceneJson.scene, 'cameras' ) as CameraDefinitions;
114
+ if( !isEmpty( cameraDefinitions ) ) {
115
+ for( let cameraName in cameraDefinitions ) {
116
+ cameras.push( await processCamera( scene, cameraName, cameraDefinitions[cameraName] ) );
113
117
  }
114
- }
115
-
116
-
117
- //there is either no "lights" or "lights" is empty
118
- if( ! sceneJson.scene.hasOwnProperty('lights') || (sceneJson.scene.lights && ! Object.keys(sceneJson.scene.lights).length ) ) {
119
- // await defaultLightning(scene);
120
118
  } else {
121
- for(let light in sceneJson.scene.lights) {
122
- await processLight(scene, light, sceneJson.scene.lights[light]);
119
+ const camera = await defaultCamera( scene );
120
+ cameras.push( camera );
121
+ }
122
+ // grounds
123
+ const groundDefinitions = get( sceneJson.scene, 'grounds' ) as GroundDefinitions;
124
+ if( !isEmpty( groundDefinitions ) ) {
125
+ for( const groundName in groundDefinitions ) {
126
+ await processGround( scene, groundName, groundDefinitions[groundName] );
123
127
  }
124
128
  }
125
-
126
-
127
129
  // TODO: try to split this away from the default rendering pipeline to save module imports
128
130
  // The "hdr" setting had negative effect on rendering in Safari. Everything looked a little "edgy". I'm not completely
129
131
  // sure if disabling it comes with other negative side effects I don't see right now, so we should probably
130
132
  // investigate this a little more in detail at some point.
131
133
  const defaultPipeline = new DefaultRenderingPipeline( 'default-rendering-pipeline', false, scene );
132
-
133
134
  /* DISABLED: causes problems with Internet Explorer
134
135
  defaultPipeline.imageProcessingEnabled = true;
135
136
  */
136
-
137
137
  if( sceneJson.scene.globals.aa ) {
138
138
  defaultPipeline.fxaaEnabled = true; //implicitly does FxaaPostProcess()
139
139
  }
140
-
141
140
  // Set samples regardless of fxaa setting since we don't want to use fxaa but still increase the sample count
142
141
  defaultPipeline.samples = 8;
143
-
144
142
  if( sceneJson.scene.globals['camera-settings'] ) {
145
- if( sceneJson.scene.globals['camera-settings'].sharpen && sceneJson.scene.globals['camera-settings'].sharpen.enabled ) {
143
+ if( sceneJson.scene.globals['camera-settings']!.sharpen && sceneJson.scene.globals['camera-settings']!.sharpen.enabled ) {
146
144
  defaultPipeline.sharpenEnabled = true; //implicitly does SharpenPostProcess()
147
145
  //defaultPipeline.sharpen.colorAmount = 1;
148
146
  //defaultPipeline.sharpen.edgeAmount = 0;
149
147
  }
150
-
151
- if( sceneJson.scene.globals['camera-settings'].bloom && sceneJson.scene.globals['camera-settings'].bloom.enabled ) {
148
+ if( sceneJson.scene.globals['camera-settings']!.bloom && sceneJson.scene.globals['camera-settings']!.bloom.enabled ) {
152
149
  defaultPipeline.bloomEnabled = true;
153
-
154
- if( sceneJson.scene.globals['camera-settings'].bloom.size ) {
155
- defaultPipeline.bloomScale = sceneJson.scene.globals['camera-settings'].bloom.size;
150
+ if( sceneJson.scene.globals['camera-settings']!.bloom.size ) {
151
+ defaultPipeline.bloomScale = sceneJson.scene.globals['camera-settings']!.bloom.size;
156
152
  }
157
-
158
- if( sceneJson.scene.globals['camera-settings'].bloom.threshold ) {
159
- defaultPipeline.bloomThreshold = sceneJson.scene.globals['camera-settings'].bloom.threshold;
153
+ if( sceneJson.scene.globals['camera-settings']!.bloom.threshold ) {
154
+ defaultPipeline.bloomThreshold = sceneJson.scene.globals['camera-settings']!.bloom.threshold;
160
155
  }
161
156
  }
162
-
163
- if( sceneJson.scene.globals['camera-settings'].contrast ) {
164
- scene.imageProcessingConfiguration.contrast = sceneJson.scene.globals['camera-settings'].contrast;
157
+ if( sceneJson.scene.globals['camera-settings']!.contrast ) {
158
+ scene.imageProcessingConfiguration.contrast = sceneJson.scene.globals['camera-settings']!.contrast;
165
159
  }
166
-
167
- if( sceneJson.scene.globals['camera-settings'].exposure ) {
168
- scene.imageProcessingConfiguration.exposure = sceneJson.scene.globals['camera-settings'].exposure;
160
+ if( sceneJson.scene.globals['camera-settings']!.exposure ) {
161
+ scene.imageProcessingConfiguration.exposure = sceneJson.scene.globals['camera-settings']!.exposure;
169
162
  }
170
-
171
- if( sceneJson.scene.globals['camera-settings'].dof && sceneJson.scene.globals['camera-settings'].dof.enabled ) {
163
+ if( sceneJson.scene.globals['camera-settings']!.dof && sceneJson.scene.globals['camera-settings']!.dof.enabled ) {
172
164
  //@ts-ignore
173
165
  const module = await import(/* webpackChunkName: "lens-rendering" */ './lensRendering');
174
- new module.LensRenderingPipeline( 'lens-rendering', sceneJson.scene.globals['camera-settings'].dof!.settings, scene, 1.0, cameras );
166
+ new module.LensRenderingPipeline( 'lens-rendering', sceneJson.scene.globals['camera-settings']!.dof.settings, scene, 1.0, cameras );
175
167
  }
176
168
  }
177
-
178
169
  // TODO: make this dynamic
179
170
  /*
180
171
  const texture = new HDRCubeTexture("/assets/small_cave_1k.hdr", scene, 256);
@@ -195,10 +186,9 @@ const sceneSetup = async function(engine: Engine, sceneJson: SceneJson) {
195
186
  */
196
187
 
197
188
  // TODO: shadows don't work with HemishpericLight
198
-
199
189
  return scene;
200
190
  };
201
191
 
202
192
  export {
203
193
  sceneSetup
204
- }
194
+ };
@@ -1,18 +1,34 @@
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 { PBRMaterial } from '@babylonjs/core/Materials/PBR/pbrMaterial';
4
6
  import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial';
7
+ import { Axis } from '@babylonjs/core/Maths/math.axis';
5
8
  import { Color3 } from '@babylonjs/core/Maths/math.color';
6
- import { Vector3 } from '@babylonjs/core/Maths/math.vector';
9
+ import { Quaternion, Vector3 } from '@babylonjs/core/Maths/math.vector';
7
10
  import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';
8
11
  import { InstancedMesh } from '@babylonjs/core/Meshes/instancedMesh';
9
12
  import { Mesh } from '@babylonjs/core/Meshes/mesh';
10
13
  import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
11
14
  import { Node } from '@babylonjs/core/node';
12
15
  import { Scene } from '@babylonjs/core/scene';
13
- import { cloneDeep, merge } from 'lodash-es';
16
+ import { Nullable } from '@babylonjs/core/types';
17
+ import { cloneDeep, get, has, merge } from 'lodash-es';
14
18
  import { DottedPath } from '../classes/dottedPath';
15
19
 
20
+ /**
21
+ * @param node
22
+ * @return Node
23
+ */
24
+ const getRootNode = function( node: Node ): Node {
25
+ let _node = node;
26
+ while( _node.parent ) {
27
+ _node = _node.parent;
28
+ }
29
+ return _node;
30
+ }
31
+
16
32
  /**
17
33
  * @param nodes
18
34
  * @param predicate
@@ -39,18 +55,17 @@ const mapToDottedNodes = function <T>( nodes: Node[],
39
55
  * @param node
40
56
  * @return DottedPath
41
57
  */
42
- const getDottedPathForTransformNode = function( node: TransformNode ): DottedPath {
58
+ const getDottedPathForNode = function( node: Node ): DottedPath {
43
59
  const dottedPath = DottedPath.create( node.name );
44
60
  let _parent = node;
45
61
  while( _parent.parent ) {
46
- _parent = _parent.parent as TransformNode;
62
+ _parent = _parent.parent;
47
63
  dottedPath.unshiftPart( _parent.name );
48
64
  }
49
65
  return dottedPath;
50
66
  };
51
67
 
52
68
  /**
53
- *
54
69
  * @param node
55
70
  * @param predicate
56
71
  * @param deep
@@ -78,6 +93,21 @@ const cloneTransformNode = function( node: TransformNode,
78
93
  return clone;
79
94
  };
80
95
 
96
+ /**
97
+ * @param node
98
+ */
99
+ const cloneNodeWithParents = function( node: Node | null ): Node | null {
100
+ let clone = null;
101
+ if( node instanceof TransformNode ) {
102
+ clone = node.clone( node.name, cloneNodeWithParents( node.parent ) as Nullable<Node>, true );
103
+ } else if( node instanceof Light ) {
104
+ clone = node.clone( node.name, cloneNodeWithParents( node.parent ) as Nullable<Node> );
105
+ } else if( node ) {
106
+ throw new Error( `Cloning of "${node?.constructor.name}" is not implemented (yet).` );
107
+ }
108
+ return clone;
109
+ };
110
+
81
111
  /**
82
112
  * @param node
83
113
  * @param deep
@@ -106,11 +136,11 @@ const cloneTransformNodeMaterial = function( node: TransformNode,
106
136
  * @param deep
107
137
  * @param metadata
108
138
  */
109
- const injectTransformNodeMetadata = function( node: TransformNode, metadata: {}, deep: boolean = true ) {
139
+ const injectNodeMetadata = function( node: Node, metadata: {}, deep: boolean = true ) {
110
140
  node.metadata = merge( {}, node.metadata, metadata );
111
- if( deep ) {
141
+ if( deep && node instanceof TransformNode ) {
112
142
  const children = node.getChildTransformNodes( true );
113
- children.forEach( child => injectTransformNodeMetadata( child, metadata, deep ) );
143
+ children.forEach( child => injectNodeMetadata( child, metadata, deep ) );
114
144
  }
115
145
  };
116
146
 
@@ -165,6 +195,80 @@ const deactivateTransformNode = function( node: TransformNode, deep: boolean = t
165
195
  }
166
196
  };
167
197
 
198
+ /**
199
+ * @param node
200
+ */
201
+ const enableNodeWithParents = function( node: Node ) {
202
+ node.setEnabled( true );
203
+ if ( node.parent ) {
204
+ enableNodeWithParents( node.parent );
205
+ }
206
+ };
207
+
208
+ /**
209
+ * @param node
210
+ */
211
+ const disableNodeWithParents = function( node: Node ) {
212
+ node.setEnabled( false );
213
+ if ( node.parent ) {
214
+ disableNodeWithParents( node.parent );
215
+ }
216
+ };
217
+
218
+ /**
219
+ * Attention: this function mutates the position of a node. Keep in mind that there are dependencies to other
220
+ * functions moving nodes around.
221
+ * @param node
222
+ * @param rotation
223
+ */
224
+ const rotateTransformNode = function( node: TransformNode, rotation: Vector3 ): TransformNode {
225
+ // remember absolute rotation and reset it before translating
226
+ if( !has( node.metadata, 'rotation.initial' ) ) {
227
+ let rotationQuaternion = node.rotationQuaternion;
228
+ if( !rotationQuaternion ) {
229
+ rotationQuaternion = Quaternion.RotationYawPitchRoll( node.rotation.x, node.rotation.y, node.rotation.z );
230
+ }
231
+ injectNodeMetadata( node, { 'rotation.initial': rotationQuaternion.asArray() }, false );
232
+ }
233
+ if( !has( node.metadata, 'rotation.position' ) || get( node.metadata, 'position.dirty' ) ) {
234
+ let rotationPosition = node.absolutePosition.clone();
235
+ if( has( node.metadata, 'rotation.offset' ) ) {
236
+ rotationPosition = rotationPosition.subtract( get( node.metadata, 'rotation.offset' ) as Vector3 );
237
+ }
238
+ injectNodeMetadata( node, { 'rotation.position': rotationPosition }, false );
239
+ injectNodeMetadata( node, { 'position.dirty': false }, false );
240
+ }
241
+ node.setAbsolutePosition( get( node.metadata, 'rotation.position' ) );
242
+ node.rotationQuaternion = Quaternion.FromArray( get( node.metadata, 'rotation.initial' ) as [] );
243
+ node.rotateAround( Vector3.Zero(), Axis.X, rotation.x );
244
+ node.rotateAround( Vector3.Zero(), Axis.Y, rotation.y );
245
+ node.rotateAround( Vector3.Zero(), Axis.Z, rotation.z );
246
+ node.computeWorldMatrix( true );
247
+ const rotationOffset = node.absolutePosition.subtract( get( node.metadata, 'rotation.position' ) as Vector3 );
248
+ injectNodeMetadata( node, { 'rotation.offset': rotationOffset }, false );
249
+ return node;
250
+ };
251
+
252
+ /**
253
+ * Attention: this function mutates the position of a node. Keep in mind that there are dependencies to other
254
+ * functions moving nodes around.
255
+ * @param node
256
+ * @param distance
257
+ */
258
+ const moveTransformNode = function( node: TransformNode, distance: Vector3 ): TransformNode {
259
+ // remember absolute position and reset it before translating
260
+ if( !has( node.metadata, 'position.initial' ) ) {
261
+ injectNodeMetadata( node, { 'position.initial': node.absolutePosition.clone() }, false );
262
+ }
263
+ let position = get( node.metadata, 'position.initial' ) as Vector3;
264
+ if( has( node.metadata, 'rotation.offset' ) ) {
265
+ position = position.add( get( node.metadata, 'rotation.offset' ) as Vector3 );
266
+ }
267
+ node.setAbsolutePosition( position.add( distance ) );
268
+ injectNodeMetadata( node, { 'position.dirty': true }, false );
269
+ return node;
270
+ };
271
+
168
272
  /**
169
273
  * @param node
170
274
  * @param material
@@ -335,6 +439,56 @@ const removeFromHighlightLayer = function( layer: HighlightLayer, node: Transfor
335
439
  }
336
440
  };
337
441
 
442
+ /**
443
+ * @param node
444
+ * @param receiveShadows
445
+ * @param deep
446
+ */
447
+ const setReceiveShadows = function( node: TransformNode, receiveShadows: boolean, deep: boolean = true ) {
448
+ if( node instanceof AbstractMesh ) {
449
+ node.receiveShadows = receiveShadows;
450
+ }
451
+ if( deep ) {
452
+ node.getChildTransformNodes( true ).forEach(
453
+ child => setReceiveShadows( child, receiveShadows, deep )
454
+ );
455
+ }
456
+ };
457
+
458
+ /**
459
+ * @param node
460
+ * @param generator
461
+ * @param deep
462
+ */
463
+ const addToShadowGenerator = function( generator: ShadowGenerator, node: TransformNode, deep: boolean = true ) {
464
+ if( node instanceof AbstractMesh ) {
465
+ // We have to remove the node because there's no duplicate check in babylon
466
+ generator.removeShadowCaster( node, false );
467
+ generator.addShadowCaster( node, false );
468
+ }
469
+ if( deep ) {
470
+ node.getChildTransformNodes( true ).forEach(
471
+ child => addToShadowGenerator( generator, child, deep )
472
+ );
473
+ }
474
+ };
475
+
476
+ /**
477
+ * @param node
478
+ * @param generator
479
+ * @param deep
480
+ */
481
+ const removeFromShadowGenerator = function( generator: ShadowGenerator, node: TransformNode, deep: boolean = true ) {
482
+ if( node instanceof AbstractMesh ) {
483
+ generator.removeShadowCaster( node, false );
484
+ }
485
+ if( deep ) {
486
+ node.getChildTransformNodes( true ).forEach(
487
+ child => removeFromShadowGenerator( generator, child, deep )
488
+ );
489
+ }
490
+ };
491
+
338
492
  /**
339
493
  * https://forum.babylonjs.com/t/get-mesh-bounding-box-position-and-size-in-2d-screen-coordinates/1058/3
340
494
  * @param mesh
@@ -372,14 +526,20 @@ const getClientRectFromMesh = function( mesh: AbstractMesh, scene: Scene, canvas
372
526
  };
373
527
 
374
528
  export {
529
+ getRootNode,
375
530
  mapToDottedNodes,
376
- getDottedPathForTransformNode,
531
+ getDottedPathForNode,
377
532
  cloneTransformNode,
533
+ cloneNodeWithParents,
378
534
  cloneTransformNodeMaterial,
379
- injectTransformNodeMetadata,
535
+ injectNodeMetadata,
380
536
  assertTransformNode,
381
537
  activateTransformNode,
382
538
  deactivateTransformNode,
539
+ enableNodeWithParents,
540
+ disableNodeWithParents,
541
+ rotateTransformNode,
542
+ moveTransformNode,
383
543
  setMaterial,
384
544
  setSourceNodeMaterial,
385
545
  setMaterialColor,
@@ -388,5 +548,8 @@ export {
388
548
  setMaterialRoughness,
389
549
  addToHighlightLayer,
390
550
  removeFromHighlightLayer,
551
+ setReceiveShadows,
552
+ addToShadowGenerator,
553
+ removeFromShadowGenerator,
391
554
  getClientRectFromMesh
392
555
  };