@combeenation/3d-viewer 20.0.0-alpha2 → 20.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/dist/lib-cjs/buildinfo.json +1 -1
- package/dist/lib-cjs/commonjs.tsconfig.tsbuildinfo +1 -1
- package/dist/lib-cjs/manager/camera-manager.d.ts +31 -1
- package/dist/lib-cjs/manager/camera-manager.js +33 -20
- package/dist/lib-cjs/manager/camera-manager.js.map +1 -1
- package/dist/lib-cjs/manager/scene-manager.d.ts +10 -1
- package/dist/lib-cjs/manager/scene-manager.js +4 -3
- package/dist/lib-cjs/manager/scene-manager.js.map +1 -1
- package/dist/lib-cjs/utils/viewer-utils.d.ts +36 -5
- package/dist/lib-cjs/utils/viewer-utils.js +83 -35
- package/dist/lib-cjs/utils/viewer-utils.js.map +1 -1
- package/package.json +1 -1
- package/src/manager/camera-manager.ts +77 -21
- package/src/manager/scene-manager.ts +15 -3
- package/src/utils/viewer-utils.ts +124 -40
|
@@ -24,17 +24,47 @@ export type CameraPosition = {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export type AutofocusSettings = {
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Can be used to customize the margins shown around the 3d model.\
|
|
29
|
+
* Default: 1
|
|
30
|
+
*/
|
|
28
31
|
radiusFactor?: number;
|
|
32
|
+
/** Default: true */
|
|
33
|
+
adjustLowerRadiusLimit?: boolean;
|
|
34
|
+
/** Default: true */
|
|
35
|
+
adjustMinZ?: boolean;
|
|
36
|
+
/** Default: true */
|
|
29
37
|
adjustWheelPrecision?: boolean;
|
|
38
|
+
/** Default: true */
|
|
30
39
|
adjustPanningSensibility?: boolean;
|
|
40
|
+
/** Default: true */
|
|
31
41
|
adjustPinchPrecision?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* `true:` uses `alpha` from input or default value (45°), `false:` keeps current alpha from camera\
|
|
44
|
+
* Default: true
|
|
45
|
+
*/
|
|
46
|
+
adjustAlpha?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* `true:` uses `beta` from input or default value (75°), `false:` keeps current beta from camera\
|
|
49
|
+
* Default: true
|
|
50
|
+
*/
|
|
51
|
+
adjustBeta?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* `true:` uses calculated radius to fit scene bounding sphere, `false:` keeps current radius from camera\
|
|
54
|
+
* Default: true
|
|
55
|
+
*/
|
|
56
|
+
adjustRadius?: boolean;
|
|
32
57
|
/** Desired horizontal camera angle, this won't be overwritten by the autofocus function */
|
|
33
58
|
alpha?: number;
|
|
34
59
|
/** Desired vertical camera angle, this won't be overwritten by the autofocus function */
|
|
35
60
|
beta?: number;
|
|
36
61
|
/** Optional list of nodes to be excluded from camera distance calculation for autofocusing */
|
|
37
62
|
excludeNodes?: NodeDescription[];
|
|
63
|
+
/**
|
|
64
|
+
* Defines if HTML anchors should be considered for the autofocus camera distance calculation.\
|
|
65
|
+
* Default: `true`
|
|
66
|
+
*/
|
|
67
|
+
excludeHtmlAnchors?: boolean;
|
|
38
68
|
durationMs?: number;
|
|
39
69
|
};
|
|
40
70
|
|
|
@@ -152,6 +182,20 @@ export class CameraManager {
|
|
|
152
182
|
* Focuses the camera to see every visible mesh in scene and tries to optimize wheel precision and panning
|
|
153
183
|
*/
|
|
154
184
|
public async autofocusActiveCamera(settings?: AutofocusSettings): Promise<void> {
|
|
185
|
+
const {
|
|
186
|
+
adjustLowerRadiusLimit,
|
|
187
|
+
adjustMinZ,
|
|
188
|
+
adjustWheelPrecision,
|
|
189
|
+
adjustPanningSensibility,
|
|
190
|
+
adjustPinchPrecision,
|
|
191
|
+
adjustAlpha,
|
|
192
|
+
adjustBeta,
|
|
193
|
+
adjustRadius,
|
|
194
|
+
excludeNodes,
|
|
195
|
+
excludeHtmlAnchors,
|
|
196
|
+
durationMs,
|
|
197
|
+
} = settings ?? {};
|
|
198
|
+
|
|
155
199
|
const activeCamera = this.viewer.scene.activeCamera;
|
|
156
200
|
|
|
157
201
|
if (!(activeCamera instanceof ArcRotateCamera)) {
|
|
@@ -162,42 +206,49 @@ export class CameraManager {
|
|
|
162
206
|
}
|
|
163
207
|
|
|
164
208
|
// get bounding box of all visible meshes, this is the base for the autofocus algorithm
|
|
165
|
-
const boundingInfo = this.viewer.sceneManager.calculateBoundingInfo(
|
|
209
|
+
const boundingInfo = this.viewer.sceneManager.calculateBoundingInfo({ excludeNodes, excludeHtmlAnchors });
|
|
166
210
|
// optionally show bounding sphere for debugging purpose
|
|
167
211
|
this.viewer.eventManager.fireEvent(ViewerEvent.AutofocusStart, boundingInfo.boundingSphere);
|
|
168
212
|
|
|
169
213
|
const distance = this._getAutofocusZoomingDistance(boundingInfo.boundingSphere, activeCamera);
|
|
170
|
-
const
|
|
171
|
-
const
|
|
214
|
+
const bBoxRadius = boundingInfo.boundingSphere.radius;
|
|
215
|
+
const bBoxCenter = boundingInfo.boundingSphere.center;
|
|
172
216
|
|
|
173
217
|
// set lower radius limit on edge of bounding sphere to make sure that we can't dive into the meshes
|
|
174
|
-
|
|
218
|
+
if (adjustLowerRadiusLimit !== false) {
|
|
219
|
+
activeCamera.lowerRadiusLimit = bBoxRadius;
|
|
220
|
+
}
|
|
175
221
|
|
|
176
222
|
// additional settings
|
|
177
223
|
// constants for division are taken directly from Babylon.js repository
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
activeCamera.wheelPrecision = CameraManager._AUTOFOCUS_CONSTANTS.wheelPrecision / radius;
|
|
224
|
+
if (adjustMinZ !== false) {
|
|
225
|
+
activeCamera.minZ = Math.min(bBoxRadius / CameraManager._AUTOFOCUS_CONSTANTS.minZ, 1);
|
|
181
226
|
}
|
|
182
|
-
if (
|
|
183
|
-
activeCamera.
|
|
227
|
+
if (adjustWheelPrecision !== false) {
|
|
228
|
+
activeCamera.wheelPrecision = CameraManager._AUTOFOCUS_CONSTANTS.wheelPrecision / bBoxRadius;
|
|
184
229
|
}
|
|
185
|
-
if (
|
|
186
|
-
activeCamera.
|
|
230
|
+
if (adjustPanningSensibility !== false) {
|
|
231
|
+
activeCamera.panningSensibility = CameraManager._AUTOFOCUS_CONSTANTS.panningSensibility / bBoxRadius;
|
|
232
|
+
}
|
|
233
|
+
if (adjustPinchPrecision !== false) {
|
|
234
|
+
activeCamera.pinchPrecision = CameraManager._AUTOFOCUS_CONSTANTS.pinchPrecision / bBoxRadius;
|
|
187
235
|
}
|
|
188
236
|
|
|
189
|
-
const
|
|
190
|
-
const alpha =
|
|
191
|
-
const
|
|
237
|
+
const adjustedAlpha = settings?.alpha ?? CameraManager.DEFAULT_CAMERA_POSITION.alpha;
|
|
238
|
+
const alpha = adjustAlpha !== false ? adjustedAlpha : activeCamera.alpha;
|
|
239
|
+
const adjustedBeta = settings?.beta ?? CameraManager.DEFAULT_CAMERA_POSITION.beta;
|
|
240
|
+
const beta = adjustBeta !== false ? adjustedBeta : activeCamera.beta;
|
|
241
|
+
const adjustedRadiusFactor = settings?.radiusFactor ?? CameraManager._DEFAULT_AUTOFOCUS_RADIUS_FACTOR;
|
|
242
|
+
const radius = adjustRadius !== false ? distance * adjustedRadiusFactor : activeCamera.radius;
|
|
192
243
|
|
|
193
244
|
const newCameraPosition: CameraPosition = {
|
|
194
|
-
alpha
|
|
195
|
-
beta
|
|
196
|
-
radius
|
|
197
|
-
target:
|
|
245
|
+
alpha,
|
|
246
|
+
beta,
|
|
247
|
+
radius,
|
|
248
|
+
target: bBoxCenter,
|
|
198
249
|
};
|
|
199
250
|
|
|
200
|
-
await this.moveActiveCameraTo(newCameraPosition,
|
|
251
|
+
await this.moveActiveCameraTo(newCameraPosition, durationMs);
|
|
201
252
|
}
|
|
202
253
|
|
|
203
254
|
/**
|
|
@@ -314,7 +365,12 @@ export class CameraManager {
|
|
|
314
365
|
const screenshotCam = this.viewer.scene.activeCamera?.clone(
|
|
315
366
|
CameraManager._SCREENSHOT_CAMERA_NAME
|
|
316
367
|
) as ArcRotateCamera;
|
|
317
|
-
const boundingInfo = autofocusScene
|
|
368
|
+
const boundingInfo = autofocusScene
|
|
369
|
+
? this.viewer.sceneManager.calculateBoundingInfo({
|
|
370
|
+
excludeNodes,
|
|
371
|
+
excludeHtmlAnchors: excludeHtmlAnchorGroups === true,
|
|
372
|
+
})
|
|
373
|
+
: undefined;
|
|
318
374
|
|
|
319
375
|
if (alpha !== undefined) {
|
|
320
376
|
screenshotCam.alpha = alpha;
|
|
@@ -83,6 +83,16 @@ export type UpdateShadowsSettings = {
|
|
|
83
83
|
excludeReceiverNodes?: NodeDescription[];
|
|
84
84
|
};
|
|
85
85
|
|
|
86
|
+
export type CalculateBoundingInfoSettings = {
|
|
87
|
+
/** Optional list of nodes to be excluded from bounding info calculation */
|
|
88
|
+
excludeNodes?: NodeDescription[];
|
|
89
|
+
/**
|
|
90
|
+
* Defines if HTML anchors should be considered for the bounding info calculation.\
|
|
91
|
+
* Default: `true`
|
|
92
|
+
*/
|
|
93
|
+
excludeHtmlAnchors?: boolean;
|
|
94
|
+
};
|
|
95
|
+
|
|
86
96
|
/**
|
|
87
97
|
* Additional scene settings, which are not available in a basic `AssetContainer`.
|
|
88
98
|
* !!! IMPORTANT when adding a new property !!!
|
|
@@ -282,7 +292,9 @@ export class SceneManager {
|
|
|
282
292
|
* - do not have a material of type "BackgroundMaterial"
|
|
283
293
|
* - do not have an infinite distance (like sky boxes)
|
|
284
294
|
*/
|
|
285
|
-
public calculateBoundingInfo(
|
|
295
|
+
public calculateBoundingInfo(settings?: CalculateBoundingInfoSettings): BoundingInfo {
|
|
296
|
+
const { excludeNodes, excludeHtmlAnchors } = settings ?? {};
|
|
297
|
+
|
|
286
298
|
// CB-6062: workaround for BoundingBox not respecting render loop
|
|
287
299
|
this.viewer.scene.render();
|
|
288
300
|
|
|
@@ -295,7 +307,7 @@ export class SceneManager {
|
|
|
295
307
|
hasInvalidBoundingInfo: true,
|
|
296
308
|
hasInfiniteDistance: true,
|
|
297
309
|
isGeneratedBackgroundMesh: true,
|
|
298
|
-
isHtmlAnchorMesh:
|
|
310
|
+
isHtmlAnchorMesh: excludeHtmlAnchors !== false,
|
|
299
311
|
});
|
|
300
312
|
|
|
301
313
|
return !isExcluded;
|
|
@@ -323,7 +335,7 @@ export class SceneManager {
|
|
|
323
335
|
* plane mesh can be used as well.
|
|
324
336
|
*/
|
|
325
337
|
public updateGround(settings?: UpdateGroundSettings): void {
|
|
326
|
-
const bboxInfo = this.calculateBoundingInfo(settings?.excludeNodes);
|
|
338
|
+
const bboxInfo = this.calculateBoundingInfo({ excludeNodes: settings?.excludeNodes });
|
|
327
339
|
|
|
328
340
|
const groundMesh = settings?.groundMesh ?? this.viewer.scene.getMeshByName(SceneManager._DEFAULT_GROUND_PLANE_NAME);
|
|
329
341
|
if (!groundMesh) {
|
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
Mesh,
|
|
5
5
|
MorphTarget,
|
|
6
6
|
MorphTargetManager,
|
|
7
|
+
Node,
|
|
8
|
+
Tags,
|
|
7
9
|
TransformNode,
|
|
8
10
|
Vector3,
|
|
9
11
|
VertexBuffer,
|
|
@@ -13,6 +15,7 @@ import {
|
|
|
13
15
|
export type BakeGeometrySettings = {
|
|
14
16
|
keepTransformation?: boolean;
|
|
15
17
|
};
|
|
18
|
+
|
|
16
19
|
export type CreateTransformNodeSettings = {
|
|
17
20
|
parentNode?: TransformNode;
|
|
18
21
|
position?: Vector3;
|
|
@@ -20,6 +23,34 @@ export type CreateTransformNodeSettings = {
|
|
|
20
23
|
scaling?: Vector3;
|
|
21
24
|
};
|
|
22
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Expected metadata structure for find node functions {@link ViewerUtils.findChildNodes} and
|
|
28
|
+
* {@link ViewerUtils.findParentNode}
|
|
29
|
+
*/
|
|
30
|
+
export type MetadataObject = Record<string, unknown>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Contains all constructor that derive from `Node`, e.g. `TransformNode`, `Mesh`
|
|
34
|
+
*/
|
|
35
|
+
export type DerivedNodeType<T extends Node> = new (...args: any[]) => T;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Criteria object for find node functions {@link ViewerUtils.findChildNodes} and {@link ViewerUtils.findParentNode}.\
|
|
39
|
+
* All given properties must match for the criteria to pass.
|
|
40
|
+
*
|
|
41
|
+
* @param nodeName has to match exactly
|
|
42
|
+
* @param tagName uses algorithm of `Tags.MatchesQuery`, in most cases this is just an exact string match check
|
|
43
|
+
* @param metadataObject has to be an object, filter passes if all property values of the given input object match the
|
|
44
|
+
* checked node
|
|
45
|
+
* @param type nodes type has to match (e.g. Mesh)
|
|
46
|
+
*/
|
|
47
|
+
export type FindNodeCriteria<T extends Node> = {
|
|
48
|
+
nodeName?: string;
|
|
49
|
+
tagName?: string;
|
|
50
|
+
metadataObject?: MetadataObject;
|
|
51
|
+
type?: DerivedNodeType<T>;
|
|
52
|
+
};
|
|
53
|
+
|
|
23
54
|
export class ViewerUtils {
|
|
24
55
|
/** @internal */
|
|
25
56
|
public constructor(protected viewer: Viewer) {}
|
|
@@ -63,10 +94,10 @@ export class ViewerUtils {
|
|
|
63
94
|
if (morphTargetManager?.numTargets) {
|
|
64
95
|
// apply morph target vertices data to mesh geometry
|
|
65
96
|
// mostly only the "PositionKind" is implemented
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
97
|
+
_bakeMorphTargetManagerIntoVertices(VertexBuffer.PositionKind, morphTargetManager, geometry);
|
|
98
|
+
_bakeMorphTargetManagerIntoVertices(VertexBuffer.NormalKind, morphTargetManager, geometry);
|
|
99
|
+
_bakeMorphTargetManagerIntoVertices(VertexBuffer.TangentKind, morphTargetManager, geometry);
|
|
100
|
+
_bakeMorphTargetManagerIntoVertices(VertexBuffer.UVKind, morphTargetManager, geometry);
|
|
70
101
|
|
|
71
102
|
// remove morph target manager with all it's morph targets
|
|
72
103
|
mesh.morphTargetManager = null;
|
|
@@ -100,50 +131,103 @@ export class ViewerUtils {
|
|
|
100
131
|
}
|
|
101
132
|
|
|
102
133
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
134
|
+
* Finds all child nodes (TransformNodes, Meshes, but also Lights, Cameras, etc...) that match a given criteria.
|
|
135
|
+
*
|
|
136
|
+
* @param node If left empty, all nodes of the scene are considered
|
|
137
|
+
* @param criteria If left empty, all child nodes are returned
|
|
105
138
|
*/
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
139
|
+
public findChildNodes<T extends Node>(node?: Node, criteria?: FindNodeCriteria<T>): T[] {
|
|
140
|
+
const allNodes = node ? node.getChildren(undefined, false) : [...this.viewer.scene.getNodes()];
|
|
141
|
+
|
|
142
|
+
if (!criteria) return allNodes as T[];
|
|
143
|
+
|
|
144
|
+
const foundNodes = allNodes.filter(node => _matchesFindNodeCriteria(node, criteria));
|
|
145
|
+
return foundNodes as T[];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Finds nearest node that matches a given criteria.\
|
|
150
|
+
* Returns `undefined` if no node could be found.
|
|
151
|
+
*/
|
|
152
|
+
public findParentNode<T extends Node>(node: Node, criteria?: FindNodeCriteria<T>): T | undefined {
|
|
153
|
+
const parent = node.parent;
|
|
154
|
+
if (!parent) {
|
|
155
|
+
return undefined;
|
|
156
|
+
} else if (!criteria) {
|
|
157
|
+
return parent as T;
|
|
115
158
|
}
|
|
116
159
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
// vertices data of this kind are implemented on the morph target
|
|
123
|
-
// add the influence of this morph target to the vertices data
|
|
124
|
-
// formula is taken from: https://doc.babylonjs.com/features/featuresDeepDive/mesh/morphTargets#basics
|
|
125
|
-
verticesData = verticesData.map(
|
|
126
|
-
(oldVal, idx) => oldVal + (targetVerticesData[idx] - origVerticesData[idx]) * target.influence
|
|
127
|
-
);
|
|
128
|
-
}
|
|
160
|
+
const matchesCriteria = _matchesFindNodeCriteria(parent, criteria);
|
|
161
|
+
if (matchesCriteria) {
|
|
162
|
+
return parent as T;
|
|
163
|
+
} else {
|
|
164
|
+
return this.findParentNode(parent, criteria);
|
|
129
165
|
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
130
168
|
|
|
131
|
-
|
|
132
|
-
|
|
169
|
+
/**
|
|
170
|
+
* @param kind morph targets can affect various vertices kinds, whereas "position" is the most common one
|
|
171
|
+
* still other kinds (like normals or tangents) can be affected as well and can be provided on this input
|
|
172
|
+
*/
|
|
173
|
+
function _bakeMorphTargetManagerIntoVertices(
|
|
174
|
+
kind: string,
|
|
175
|
+
morphTargetManager: MorphTargetManager,
|
|
176
|
+
geometry: Geometry
|
|
177
|
+
): void {
|
|
178
|
+
const origVerticesData = geometry.getVerticesData(kind);
|
|
179
|
+
if (!origVerticesData) {
|
|
180
|
+
// no vertices data for this kind availabe on the mesh geometry
|
|
181
|
+
return;
|
|
133
182
|
}
|
|
134
183
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
184
|
+
let verticesData = [...origVerticesData];
|
|
185
|
+
for (let i = 0; i < morphTargetManager.numTargets; i++) {
|
|
186
|
+
const target = morphTargetManager.getTarget(i);
|
|
187
|
+
const targetVerticesData = _getVerticesDataFromMorphTarget(kind, target);
|
|
188
|
+
if (targetVerticesData) {
|
|
189
|
+
// vertices data of this kind are implemented on the morph target
|
|
190
|
+
// add the influence of this morph target to the vertices data
|
|
191
|
+
// formula is taken from: https://doc.babylonjs.com/features/featuresDeepDive/mesh/morphTargets#basics
|
|
192
|
+
verticesData = verticesData.map(
|
|
193
|
+
(oldVal, idx) => oldVal + (targetVerticesData[idx] - origVerticesData[idx]) * target.influence
|
|
194
|
+
);
|
|
145
195
|
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// apply the updated vertices data
|
|
199
|
+
geometry.setVerticesData(kind, verticesData);
|
|
200
|
+
}
|
|
146
201
|
|
|
147
|
-
|
|
202
|
+
function _getVerticesDataFromMorphTarget(kind: string, morphTarget: MorphTarget): FloatArray | null {
|
|
203
|
+
switch (kind) {
|
|
204
|
+
case VertexBuffer.PositionKind:
|
|
205
|
+
return morphTarget.getPositions();
|
|
206
|
+
case VertexBuffer.NormalKind:
|
|
207
|
+
return morphTarget.getNormals();
|
|
208
|
+
case VertexBuffer.TangentKind:
|
|
209
|
+
return morphTarget.getTangents();
|
|
210
|
+
case VertexBuffer.UVKind:
|
|
211
|
+
return morphTarget.getUVs();
|
|
148
212
|
}
|
|
213
|
+
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function _matchesFindNodeCriteria<T extends Node>(node: Node, criteria?: FindNodeCriteria<T>): boolean {
|
|
218
|
+
if (!criteria) return true;
|
|
219
|
+
|
|
220
|
+
const { nodeName, tagName, metadataObject, type } = criteria;
|
|
221
|
+
const nodeNameFits = !nodeName || node.name === nodeName;
|
|
222
|
+
const tagNameFits = !tagName || Tags.MatchesQuery(node, tagName);
|
|
223
|
+
const metadataFits = !metadataObject || _matchesMetadata(node.metadata, metadataObject);
|
|
224
|
+
const typeFits = !type || node instanceof type;
|
|
225
|
+
|
|
226
|
+
return nodeNameFits && tagNameFits && metadataFits && typeFits;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function _matchesMetadata(metadata: unknown, filter: MetadataObject): boolean {
|
|
230
|
+
if (!metadata || typeof metadata !== 'object') return false;
|
|
231
|
+
|
|
232
|
+
return Object.entries(filter).every(([key, value]) => (metadata as MetadataObject)[key] === value);
|
|
149
233
|
}
|