@combeenation/3d-viewer 4.0.1-alpha1 → 4.2.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/dist/lib-cjs/api/classes/element.js +31 -28
- package/dist/lib-cjs/api/classes/element.js.map +1 -1
- package/dist/lib-cjs/api/classes/parameter.js +2 -2
- package/dist/lib-cjs/api/classes/parameter.js.map +1 -1
- package/dist/lib-cjs/api/classes/variantParameterizable.js +8 -1
- package/dist/lib-cjs/api/classes/variantParameterizable.js.map +1 -1
- package/dist/lib-cjs/api/classes/viewer.d.ts +8 -3
- package/dist/lib-cjs/api/classes/viewer.js +83 -42
- package/dist/lib-cjs/api/classes/viewer.js.map +1 -1
- package/dist/lib-cjs/api/classes/viewerLight.js +21 -18
- package/dist/lib-cjs/api/classes/viewerLight.js.map +1 -1
- package/dist/lib-cjs/api/manager/gltfExportManager.d.ts +35 -0
- package/dist/lib-cjs/api/manager/gltfExportManager.js +108 -0
- package/dist/lib-cjs/api/manager/gltfExportManager.js.map +1 -0
- package/dist/lib-cjs/api/util/babylonHelper.d.ts +4 -13
- package/dist/lib-cjs/api/util/babylonHelper.js +23 -43
- package/dist/lib-cjs/api/util/babylonHelper.js.map +1 -1
- package/dist/lib-cjs/api/util/globalTypes.d.ts +11 -0
- package/package.json +1 -1
- package/src/api/classes/element.ts +27 -23
- package/src/api/classes/parameter.ts +2 -2
- package/src/api/classes/variantParameterizable.ts +8 -1
- package/src/api/classes/viewer.ts +83 -46
- package/src/api/classes/viewerLight.ts +19 -15
- package/src/api/util/babylonHelper.ts +22 -43
- package/src/api/util/globalTypes.ts +13 -1
- package/src/pagesconfig.json +5 -1
|
@@ -276,7 +276,7 @@ export class Viewer extends EventBroadcaster {
|
|
|
276
276
|
if( this.scene.meshes.length === 0 ) {
|
|
277
277
|
throw new Error( 'There are currently no meshes on the scene.' );
|
|
278
278
|
}
|
|
279
|
-
this.scene.render(); //
|
|
279
|
+
this.scene.render(); // CB-6062: workaround for BoundingBox not respecting render loop
|
|
280
280
|
const bbName = '__bounding_box__';
|
|
281
281
|
|
|
282
282
|
const { max, min } = this.scene.meshes.filter( mesh => {
|
|
@@ -302,57 +302,26 @@ export class Viewer extends EventBroadcaster {
|
|
|
302
302
|
return boundingBox;
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
-
/**
|
|
306
|
-
* Focuses the camera to see every visible mesh in scene and tries to optimize wheel precision and panning.
|
|
307
|
-
*/
|
|
308
305
|
public async autofocusActiveCamera( settings?: AutofocusSettings ) {
|
|
306
|
+
// first check some preconditions
|
|
309
307
|
const activeCamera = this.scene.activeCamera;
|
|
310
|
-
if( !activeCamera ) {
|
|
311
|
-
throw new Error(
|
|
308
|
+
if ( !activeCamera ) {
|
|
309
|
+
throw new Error("No active camera found when using autofocus feature.");
|
|
312
310
|
}
|
|
313
|
-
if( activeCamera instanceof ArcRotateCamera ) {
|
|
314
|
-
// calculate some values
|
|
315
|
-
const boundingBox = await this.calculateBoundingBox();
|
|
316
|
-
const size = boundingBox.getBoundingInfo().maximum.subtract( boundingBox.getBoundingInfo().minimum );
|
|
317
|
-
let radius = size.length() * (settings?.radiusFactor ?? 1.5);
|
|
318
|
-
if( !isFinite( radius ) ) {
|
|
319
|
-
radius = 1;
|
|
320
|
-
}
|
|
321
|
-
// use helper camera to get some default values
|
|
322
|
-
const helperCamera = new ArcRotateCamera(
|
|
323
|
-
'__helper_camera__',
|
|
324
|
-
Math.PI / -2,
|
|
325
|
-
Math.PI / 2,
|
|
326
|
-
radius,
|
|
327
|
-
Vector3.Zero(),
|
|
328
|
-
this.scene
|
|
329
|
-
);
|
|
330
|
-
helperCamera.useFramingBehavior = true;
|
|
331
|
-
helperCamera.setTarget( boundingBox );
|
|
332
|
-
// translate values from helper to active camera
|
|
333
|
-
activeCamera.setTarget( Mesh.Center( [boundingBox] ) );
|
|
334
|
-
activeCamera.alpha = helperCamera.alpha;
|
|
335
|
-
activeCamera.beta = helperCamera.beta;
|
|
336
|
-
activeCamera.minZ = helperCamera.minZ;
|
|
337
|
-
activeCamera.maxZ = helperCamera.maxZ;
|
|
338
|
-
activeCamera.radius = helperCamera.radius;
|
|
339
|
-
activeCamera.lowerRadiusLimit = helperCamera.lowerRadiusLimit;
|
|
340
|
-
activeCamera.upperRadiusLimit = helperCamera.upperRadiusLimit;
|
|
341
|
-
if( settings?.adjustWheelPrecision !== false ) {
|
|
342
|
-
activeCamera.wheelPrecision = helperCamera.wheelPrecision;
|
|
343
|
-
}
|
|
344
|
-
if( settings?.adjustPanningSensibility !== false ) {
|
|
345
|
-
activeCamera.panningSensibility = helperCamera.panningSensibility;
|
|
346
|
-
}
|
|
347
|
-
if( settings?.adjustPinchPrecision !== false ) {
|
|
348
|
-
activeCamera.pinchPrecision = helperCamera.pinchPrecision;
|
|
349
|
-
}
|
|
350
|
-
// remove the helper camera
|
|
351
|
-
helperCamera.dispose();
|
|
352
|
-
} else {
|
|
311
|
+
if ( !( activeCamera instanceof ArcRotateCamera ) ) {
|
|
353
312
|
const cameraClsName = activeCamera.getClassName();
|
|
354
313
|
throw new Error( `Camera of type "${cameraClsName}" is not implemented yet to use autofocus feature.` );
|
|
355
314
|
}
|
|
315
|
+
|
|
316
|
+
// get bounding box of all visible meshes, this is the base for the autofocus algorithm
|
|
317
|
+
const boundingBox = await this.calculateBoundingBox();
|
|
318
|
+
|
|
319
|
+
// focus the helper camera and set the calculated camera data to the real camera
|
|
320
|
+
const helperCamera = this.getFocusedHelperCamera( boundingBox, settings )
|
|
321
|
+
await this.applyFocusedHelperCameraData( activeCamera, helperCamera, settings )
|
|
322
|
+
|
|
323
|
+
// remove the helper camera
|
|
324
|
+
helperCamera.dispose();
|
|
356
325
|
}
|
|
357
326
|
|
|
358
327
|
/**
|
|
@@ -485,4 +454,72 @@ export class Viewer extends EventBroadcaster {
|
|
|
485
454
|
return instances;
|
|
486
455
|
}
|
|
487
456
|
|
|
457
|
+
/**
|
|
458
|
+
* Help function for focusing a helper camera exactly onto the given bounding box
|
|
459
|
+
*/
|
|
460
|
+
private getFocusedHelperCamera(boundingBox: Mesh, settings?: AutofocusSettings): ArcRotateCamera {
|
|
461
|
+
// use helper camera to get some default values and set the values of the real camera accordingly
|
|
462
|
+
const helperCamera = new ArcRotateCamera(
|
|
463
|
+
"__helper_camera__",
|
|
464
|
+
0, // camera angles will be overwritten after the target has been set
|
|
465
|
+
0,
|
|
466
|
+
0, // radius will be calculated, so we can set to 0 here
|
|
467
|
+
Vector3.Zero(),
|
|
468
|
+
this.scene
|
|
469
|
+
);
|
|
470
|
+
// this is required for automatically calculating the `lowerRadiusLimit`, so that we don't "dive" into meshes
|
|
471
|
+
// see https://doc.babylonjs.com/divingDeeper/behaviors/cameraBehaviors#framing-behavior
|
|
472
|
+
helperCamera.useFramingBehavior = true;
|
|
473
|
+
|
|
474
|
+
// `minZ` is the camera distance beyond which the mesh will be clipped
|
|
475
|
+
// this should be very low, but can't be zero
|
|
476
|
+
// a good value seems to be 1% of the bounding box size (= radius), whereas the value shouldn't go above 1, which is also the default value
|
|
477
|
+
const radius = boundingBox.getBoundingInfo().boundingSphere.radius;
|
|
478
|
+
helperCamera.minZ = Math.min(radius/100, 1);
|
|
479
|
+
|
|
480
|
+
// set desired camera data, these won't be changed by the autofocus function!
|
|
481
|
+
// default values should focus the element exactly from the front (= XY Plane)
|
|
482
|
+
helperCamera.setTarget(boundingBox, true);
|
|
483
|
+
helperCamera.alpha = ( settings?.alpha ?? - 90 ) * ( Math.PI / 180 );
|
|
484
|
+
helperCamera.beta = ( settings?.beta ?? 90 ) * ( Math.PI / 180 );
|
|
485
|
+
|
|
486
|
+
// finally zoom to the bounding box
|
|
487
|
+
// also apply a zoom factor, this adjusts the borders around the model in the viewport
|
|
488
|
+
helperCamera.zoomOnFactor = settings?.radiusFactor || 1;
|
|
489
|
+
helperCamera.zoomOn( [boundingBox], true );
|
|
490
|
+
|
|
491
|
+
return helperCamera;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Help function for applying the relevant data of the focused helper camera to the real camera
|
|
496
|
+
*/
|
|
497
|
+
private async applyFocusedHelperCameraData(activeCamera: ArcRotateCamera, helperCamera: ArcRotateCamera, settings?: AutofocusSettings) {
|
|
498
|
+
// limits
|
|
499
|
+
activeCamera.minZ = helperCamera.minZ;
|
|
500
|
+
activeCamera.maxZ = helperCamera.maxZ;
|
|
501
|
+
activeCamera.lowerRadiusLimit = helperCamera.lowerRadiusLimit;
|
|
502
|
+
activeCamera.upperRadiusLimit = helperCamera.upperRadiusLimit;
|
|
503
|
+
|
|
504
|
+
// additional settings
|
|
505
|
+
if( settings?.adjustWheelPrecision !== false ) {
|
|
506
|
+
activeCamera.wheelPrecision = helperCamera.wheelPrecision;
|
|
507
|
+
}
|
|
508
|
+
if( settings?.adjustPanningSensibility !== false ) {
|
|
509
|
+
activeCamera.panningSensibility = helperCamera.panningSensibility;
|
|
510
|
+
}
|
|
511
|
+
if( settings?.adjustPinchPrecision !== false ) {
|
|
512
|
+
activeCamera.pinchPrecision = helperCamera.pinchPrecision;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// finally move the camera
|
|
516
|
+
// do this at last, so that all camera settings are already considered
|
|
517
|
+
const newCameraPosition: PlacementDefinition = {
|
|
518
|
+
alpha: helperCamera.alpha,
|
|
519
|
+
beta: helperCamera.beta,
|
|
520
|
+
radius: helperCamera.radius,
|
|
521
|
+
target: helperCamera.target
|
|
522
|
+
}
|
|
523
|
+
await this.animationManager.animateToPlacement(activeCamera, newCameraPosition, settings?.animation)
|
|
524
|
+
}
|
|
488
525
|
}
|
|
@@ -14,8 +14,7 @@ import {
|
|
|
14
14
|
enableNodeWithParents,
|
|
15
15
|
getRootNode,
|
|
16
16
|
injectNodeMetadata,
|
|
17
|
-
|
|
18
|
-
rotateTransformNode
|
|
17
|
+
transformTransformNode
|
|
19
18
|
} from './../util/babylonHelper';
|
|
20
19
|
import { DottedPath } from './dottedPath';
|
|
21
20
|
import { Parameter } from './parameter';
|
|
@@ -128,37 +127,42 @@ export class ViewerLight extends VariantParameterizable {
|
|
|
128
127
|
set( light.light, 'intensity', Parameter.parseNumber( newValue ) );
|
|
129
128
|
}
|
|
130
129
|
] );
|
|
131
|
-
this._parameterObservers.set( Parameter.
|
|
130
|
+
this._parameterObservers.set( Parameter.SCALING, [
|
|
132
131
|
async ( light: ViewerLight, oldValue: ParameterValue, newValue: ParameterValue ) => {
|
|
133
132
|
// we have to deal just with root nodes here due to relative impacts in a node tree
|
|
134
133
|
const rootNode = getRootNode( light.light );
|
|
135
134
|
if( rootNode instanceof TransformNode ) {
|
|
136
|
-
|
|
135
|
+
transformTransformNode( rootNode, {
|
|
136
|
+
scaling: Parameter.parseScaling( newValue ),
|
|
137
|
+
position: Parameter.parseVector( light.inheritedParameters[Parameter.POSITION] || '(0, 0, 0)' ),
|
|
138
|
+
rotation: Parameter.parseRotation( light.inheritedParameters[Parameter.ROTATION] || '(0, 0, 0)' )
|
|
139
|
+
} );
|
|
137
140
|
}
|
|
138
141
|
}
|
|
139
142
|
] );
|
|
140
|
-
this._parameterObservers.set( Parameter.
|
|
143
|
+
this._parameterObservers.set( Parameter.POSITION, [
|
|
141
144
|
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
145
|
// we have to deal just with root nodes here due to relative impacts in a node tree
|
|
150
146
|
const rootNode = getRootNode( light.light );
|
|
151
147
|
if( rootNode instanceof TransformNode ) {
|
|
152
|
-
|
|
148
|
+
transformTransformNode( rootNode, {
|
|
149
|
+
scaling: Parameter.parseVector( light.inheritedParameters[Parameter.SCALING] || '(1, 1, 1)' ),
|
|
150
|
+
position: Parameter.parseVector( newValue ),
|
|
151
|
+
rotation: Parameter.parseRotation( light.inheritedParameters[Parameter.ROTATION] || '(0, 0, 0)' )
|
|
152
|
+
} );
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
] );
|
|
156
|
-
this._parameterObservers.set( Parameter.
|
|
156
|
+
this._parameterObservers.set( Parameter.ROTATION, [
|
|
157
157
|
async ( light: ViewerLight, oldValue: ParameterValue, newValue: ParameterValue ) => {
|
|
158
158
|
// we have to deal just with root nodes here due to relative impacts in a node tree
|
|
159
159
|
const rootNode = getRootNode( light.light );
|
|
160
160
|
if( rootNode instanceof TransformNode ) {
|
|
161
|
-
|
|
161
|
+
transformTransformNode( rootNode, {
|
|
162
|
+
scaling: Parameter.parseVector( light.inheritedParameters[Parameter.SCALING] || '(1, 1, 1)' ),
|
|
163
|
+
position: Parameter.parseVector( light.inheritedParameters[Parameter.POSITION] || '(0, 0, 0)' ),
|
|
164
|
+
rotation: Parameter.parseRotation( newValue )
|
|
165
|
+
} );
|
|
162
166
|
}
|
|
163
167
|
}
|
|
164
168
|
] );
|
|
@@ -216,13 +216,24 @@ const disableNodeWithParents = function( node: Node ) {
|
|
|
216
216
|
};
|
|
217
217
|
|
|
218
218
|
/**
|
|
219
|
-
*
|
|
220
|
-
* functions moving nodes around.
|
|
219
|
+
* Applies a {@link TransformationDefinition} consecutively to ensure dependencies in positioning etc.
|
|
221
220
|
* @param node
|
|
222
|
-
* @param
|
|
221
|
+
* @param transformation
|
|
223
222
|
*/
|
|
224
|
-
const
|
|
225
|
-
//
|
|
223
|
+
const transformTransformNode = function( node: TransformNode, transformation: TransformationDefinition ) {
|
|
224
|
+
// scaling
|
|
225
|
+
if( !has( node.metadata, 'scaling.initial' ) ) {
|
|
226
|
+
injectNodeMetadata( node, { 'scaling.initial': node.scaling }, false );
|
|
227
|
+
}
|
|
228
|
+
const initialScaling = get( node.metadata, 'scaling.initial' ) as Vector3;
|
|
229
|
+
node.scaling = initialScaling.multiply( transformation.scaling );
|
|
230
|
+
// position
|
|
231
|
+
if( !has( node.metadata, 'position.initial' ) ) {
|
|
232
|
+
injectNodeMetadata( node, { 'position.initial': node.absolutePosition.clone() }, false );
|
|
233
|
+
}
|
|
234
|
+
const initialPosition = get( node.metadata, 'position.initial' ) as Vector3;
|
|
235
|
+
node.setAbsolutePosition( initialPosition.add( transformation.position ).multiply( transformation.scaling ) );
|
|
236
|
+
// rotation
|
|
226
237
|
if( !has( node.metadata, 'rotation.initial' ) ) {
|
|
227
238
|
let rotationQuaternion = node.rotationQuaternion;
|
|
228
239
|
if( !rotationQuaternion ) {
|
|
@@ -230,43 +241,12 @@ const rotateTransformNode = function( node: TransformNode, rotation: Vector3 ):
|
|
|
230
241
|
}
|
|
231
242
|
injectNodeMetadata( node, { 'rotation.initial': rotationQuaternion.asArray() }, false );
|
|
232
243
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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 );
|
|
244
|
+
const initialRotationQuaternion = Quaternion.FromArray( get( node.metadata, 'rotation.initial' ) as [] );
|
|
245
|
+
node.rotationQuaternion = initialRotationQuaternion;
|
|
246
|
+
node.rotateAround( Vector3.Zero(), Axis.X, transformation.rotation.x );
|
|
247
|
+
node.rotateAround( Vector3.Zero(), Axis.Y, transformation.rotation.y );
|
|
248
|
+
node.rotateAround( Vector3.Zero(), Axis.Z, transformation.rotation.z );
|
|
246
249
|
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
250
|
};
|
|
271
251
|
|
|
272
252
|
/**
|
|
@@ -538,8 +518,7 @@ export {
|
|
|
538
518
|
deactivateTransformNode,
|
|
539
519
|
enableNodeWithParents,
|
|
540
520
|
disableNodeWithParents,
|
|
541
|
-
|
|
542
|
-
moveTransformNode,
|
|
521
|
+
transformTransformNode,
|
|
543
522
|
setMaterial,
|
|
544
523
|
setSourceNodeMaterial,
|
|
545
524
|
setMaterialColor,
|
|
@@ -79,6 +79,12 @@ type ElementDefinition = {
|
|
|
79
79
|
paintables?: PaintableDefinitions
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
+
type TransformationDefinition = {
|
|
83
|
+
scaling: Vector3,
|
|
84
|
+
position: Vector3,
|
|
85
|
+
rotation: Vector3
|
|
86
|
+
};
|
|
87
|
+
|
|
82
88
|
type StructureJson = {
|
|
83
89
|
/**
|
|
84
90
|
* `scene` describes the visualisation of the Babylon `scene` such as the incidence of light and camera position. If a
|
|
@@ -227,7 +233,13 @@ type AutofocusSettings = {
|
|
|
227
233
|
radiusFactor?: number,
|
|
228
234
|
adjustWheelPrecision?: boolean,
|
|
229
235
|
adjustPanningSensibility?: boolean,
|
|
230
|
-
adjustPinchPrecision?: boolean
|
|
236
|
+
adjustPinchPrecision?: boolean,
|
|
237
|
+
/** Desired horizontal camera angle, this won't be overwritten by the autofocus function */
|
|
238
|
+
alpha?: number;
|
|
239
|
+
/** Desired vertical camera angle, this won't be overwritten by the autofocus function */
|
|
240
|
+
beta?: number;
|
|
241
|
+
/** Optional animation for the focusing camera movement */
|
|
242
|
+
animation?: string | AnimationDefinition
|
|
231
243
|
};
|
|
232
244
|
|
|
233
245
|
type LightDefinitions = {
|
package/src/pagesconfig.json
CHANGED
|
@@ -40,11 +40,15 @@
|
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
"title": "ViewerLights",
|
|
43
|
-
"source": "./doc/ViewerLights.md"
|
|
43
|
+
"source": "./doc/documentation/ViewerLights.md"
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
"title": "Paintables",
|
|
47
47
|
"source": "./doc/documentation/Paintables.md"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"title": "Transformation Parameters",
|
|
51
|
+
"source": "./doc/documentation/Transformation-Parameters.md"
|
|
48
52
|
}
|
|
49
53
|
]
|
|
50
54
|
},
|