@combeenation/3d-viewer 19.1.0 → 20.0.0-alpha1
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/index.d.ts +1 -1
- package/dist/lib-cjs/index.js +1 -1
- package/dist/lib-cjs/index.js.map +1 -1
- package/dist/lib-cjs/internal/animation-helper.d.ts +2 -0
- package/dist/lib-cjs/internal/animation-helper.js +16 -0
- package/dist/lib-cjs/internal/animation-helper.js.map +1 -0
- package/dist/lib-cjs/internal/cloning-helper.js +4 -2
- package/dist/lib-cjs/internal/cloning-helper.js.map +1 -1
- package/dist/lib-cjs/internal/export-helper.js +1 -1
- package/dist/lib-cjs/internal/export-helper.js.map +1 -1
- package/dist/lib-cjs/internal/math-helper.d.ts +9 -0
- package/dist/lib-cjs/internal/math-helper.js +43 -0
- package/dist/lib-cjs/internal/math-helper.js.map +1 -0
- package/dist/lib-cjs/internal/{paintable-helper.d.ts → parameter/paintable-parameter-helper.d.ts} +3 -3
- package/dist/lib-cjs/internal/{paintable-helper.js → parameter/paintable-parameter-helper.js} +26 -26
- package/dist/lib-cjs/internal/parameter/paintable-parameter-helper.js.map +1 -0
- package/dist/lib-cjs/internal/parameter/parameter-helper.d.ts +2 -0
- package/dist/lib-cjs/internal/parameter/parameter-helper.js +214 -0
- package/dist/lib-cjs/internal/parameter/parameter-helper.js.map +1 -0
- package/dist/lib-cjs/internal/{texture-parameter-helper.d.ts → parameter/texture-parameter-helper.d.ts} +1 -2
- package/dist/lib-cjs/internal/{texture-parameter-helper.js → parameter/texture-parameter-helper.js} +15 -16
- package/dist/lib-cjs/internal/parameter/texture-parameter-helper.js.map +1 -0
- package/dist/lib-cjs/manager/camera-manager.js +2 -9
- package/dist/lib-cjs/manager/camera-manager.js.map +1 -1
- package/dist/lib-cjs/manager/debug-manager.d.ts +5 -1
- package/dist/lib-cjs/manager/debug-manager.js +21 -5
- package/dist/lib-cjs/manager/debug-manager.js.map +1 -1
- package/dist/lib-cjs/manager/model-manager.d.ts +35 -4
- package/dist/lib-cjs/manager/model-manager.js +50 -8
- package/dist/lib-cjs/manager/model-manager.js.map +1 -1
- package/dist/lib-cjs/manager/parameter-manager.d.ts +37 -20
- package/dist/lib-cjs/manager/parameter-manager.js +109 -204
- package/dist/lib-cjs/manager/parameter-manager.js.map +1 -1
- package/dist/lib-cjs/utils/viewer-utils.d.ts +35 -0
- package/dist/lib-cjs/utils/viewer-utils.js +112 -0
- package/dist/lib-cjs/utils/viewer-utils.js.map +1 -0
- package/dist/lib-cjs/viewer.d.ts +13 -1
- package/dist/lib-cjs/viewer.js +24 -0
- package/dist/lib-cjs/viewer.js.map +1 -1
- package/package.json +13 -12
- package/src/index.ts +1 -1
- package/src/internal/animation-helper.ts +18 -0
- package/src/internal/cloning-helper.ts +4 -2
- package/src/internal/export-helper.ts +1 -2
- package/src/internal/math-helper.ts +46 -0
- package/src/internal/{paintable-helper.ts → parameter/paintable-parameter-helper.ts} +35 -35
- package/src/internal/parameter/parameter-helper.ts +263 -0
- package/src/internal/{texture-parameter-helper.ts → parameter/texture-parameter-helper.ts} +13 -3
- package/src/manager/camera-manager.ts +2 -10
- package/src/manager/debug-manager.ts +35 -6
- package/src/manager/model-manager.ts +81 -13
- package/src/manager/parameter-manager.ts +138 -232
- package/src/utils/viewer-utils.ts +149 -0
- package/src/viewer.ts +29 -1
- package/dist/lib-cjs/internal/paintable-helper.js.map +0 -1
- package/dist/lib-cjs/internal/texture-parameter-helper.js.map +0 -1
- package/dist/lib-cjs/utils/node-utils.d.ts +0 -17
- package/dist/lib-cjs/utils/node-utils.js +0 -92
- package/dist/lib-cjs/utils/node-utils.js.map +0 -1
- package/src/utils/node-utils.ts +0 -112
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbstractMesh,
|
|
3
|
+
Animation,
|
|
4
|
+
AnimationGroup,
|
|
5
|
+
BuiltInParameter,
|
|
6
|
+
Color3,
|
|
7
|
+
MaterialManager,
|
|
8
|
+
PBRMaterial,
|
|
9
|
+
ParameterManager,
|
|
10
|
+
StandardMaterial,
|
|
11
|
+
Vector3,
|
|
12
|
+
ViewerError,
|
|
13
|
+
ViewerErrorIds,
|
|
14
|
+
} from '../..';
|
|
15
|
+
import { playAnimationGroupOnce } from '../animation-helper';
|
|
16
|
+
import { getInternalMetadataValue, setInternalMetadataValue } from '../metadata-helper';
|
|
17
|
+
|
|
18
|
+
type AnimatableProperties = 'albedoColor' | 'position' | 'rotation' | 'scaling' | 'influence';
|
|
19
|
+
|
|
20
|
+
export function createBuiltInParameter(parameterManager: ParameterManager, materialManager: MaterialManager): void {
|
|
21
|
+
parameterManager.setParameterObserver(BuiltInParameter.Visible, async ({ nodes, newValue }) => {
|
|
22
|
+
const visible = ParameterManager.parseBoolean(newValue);
|
|
23
|
+
|
|
24
|
+
const observerProms = nodes.map(async node => {
|
|
25
|
+
const visibleNested = parameterManager.getNestedVisibilityParameterValueOfNode(node);
|
|
26
|
+
if (visibleNested) {
|
|
27
|
+
// if a mesh gets visible by this operation we have to activate the assigned material which is stored in the
|
|
28
|
+
// internal metadata
|
|
29
|
+
// => consider child meshes as well (CB-10143)
|
|
30
|
+
const activatedNodes = [node, ...node.getChildMeshes(false)];
|
|
31
|
+
const setMaterialProms = activatedNodes.map(async an => {
|
|
32
|
+
const anVisibleNested = parameterManager.getNestedVisibilityParameterValueOfNode(an);
|
|
33
|
+
const anDeferredMaterial = getInternalMetadataValue(an, 'deferredMaterial');
|
|
34
|
+
const anMaterialParamValue = parameterManager.getParameterValueOfNode(an, BuiltInParameter.Material) as
|
|
35
|
+
| string
|
|
36
|
+
| undefined;
|
|
37
|
+
// get latest requested material, either from (new) parameter value or from deferred material
|
|
38
|
+
const anRequestedMaterial = anMaterialParamValue ?? anDeferredMaterial;
|
|
39
|
+
// material observer might have been executed before visibility observer
|
|
40
|
+
// => skip the material application in this case, as if it would be done twice
|
|
41
|
+
const anMaterialToBeSet = getInternalMetadataValue(an, 'materialToBeSet');
|
|
42
|
+
if (anVisibleNested && anRequestedMaterial && !anMaterialToBeSet) {
|
|
43
|
+
await materialManager.setMaterialOnMesh(
|
|
44
|
+
anRequestedMaterial,
|
|
45
|
+
// this cast is fine, as deferred material can only be set on meshes
|
|
46
|
+
an as AbstractMesh
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
await Promise.all(setMaterialProms);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// set enabled state anyway
|
|
54
|
+
node.setEnabled(visible);
|
|
55
|
+
});
|
|
56
|
+
await Promise.all(observerProms);
|
|
57
|
+
});
|
|
58
|
+
parameterManager.setParameterObserver(BuiltInParameter.Material, async ({ nodes, newValue }) => {
|
|
59
|
+
const material = ParameterManager.parseString(newValue);
|
|
60
|
+
|
|
61
|
+
const observerProms = nodes.map(async node => {
|
|
62
|
+
if (!(node instanceof AbstractMesh)) {
|
|
63
|
+
throw new ViewerError({
|
|
64
|
+
id: ViewerErrorIds.InvalidParameterSubject,
|
|
65
|
+
message: `Material can't be set, as the target node "${node.name}" is not a mesh`,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// only set material if the mesh is visible, or gets visible by the parameter update
|
|
70
|
+
const visible = parameterManager.getNestedVisibilityParameterValueOfNode(node);
|
|
71
|
+
if (visible) {
|
|
72
|
+
// visibility observer might have been executed before material observer
|
|
73
|
+
// => skip the material application in this case, as if it would be done twice
|
|
74
|
+
const anMaterialToBeSet = getInternalMetadataValue(node, 'materialToBeSet');
|
|
75
|
+
if (!anMaterialToBeSet) {
|
|
76
|
+
await materialManager.setMaterialOnMesh(material, node);
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
setInternalMetadataValue(node, 'deferredMaterial', material);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
await Promise.all(observerProms);
|
|
83
|
+
});
|
|
84
|
+
parameterManager.setParameterObserver(BuiltInParameter.Position, async ({ nodes, newValue, options }) => {
|
|
85
|
+
const position = ParameterManager.parseVector(newValue);
|
|
86
|
+
|
|
87
|
+
for (const node of nodes) {
|
|
88
|
+
if (options?.animationTimeMs) {
|
|
89
|
+
_animateParameter(
|
|
90
|
+
node,
|
|
91
|
+
'position',
|
|
92
|
+
position,
|
|
93
|
+
`${BuiltInParameter.Position}.${node.name}`,
|
|
94
|
+
options.animationTimeMs
|
|
95
|
+
);
|
|
96
|
+
} else {
|
|
97
|
+
node.position = position;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
parameterManager.setParameterObserver(BuiltInParameter.Rotation, async ({ nodes, newValue, options }) => {
|
|
102
|
+
const rotation = ParameterManager.parseRotation(newValue);
|
|
103
|
+
|
|
104
|
+
for (const node of nodes) {
|
|
105
|
+
if (options?.animationTimeMs) {
|
|
106
|
+
if (node.rotationQuaternion) {
|
|
107
|
+
// set initial rotation value, if rotation is defined via quaternion
|
|
108
|
+
// otherwise the animation would just jump to the end value, as there is no start value to interpolate
|
|
109
|
+
node.rotation = node.rotationQuaternion.toEulerAngles();
|
|
110
|
+
}
|
|
111
|
+
_animateParameter(
|
|
112
|
+
node,
|
|
113
|
+
'rotation',
|
|
114
|
+
rotation,
|
|
115
|
+
`${BuiltInParameter.Rotation}.${node.name}`,
|
|
116
|
+
options.animationTimeMs
|
|
117
|
+
);
|
|
118
|
+
} else {
|
|
119
|
+
node.rotation = rotation;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
parameterManager.setParameterObserver(BuiltInParameter.Scaling, async ({ nodes, newValue, options }) => {
|
|
124
|
+
const scaling = ParameterManager.parseVector(newValue);
|
|
125
|
+
|
|
126
|
+
for (const node of nodes) {
|
|
127
|
+
if (options?.animationTimeMs) {
|
|
128
|
+
_animateParameter(
|
|
129
|
+
node,
|
|
130
|
+
'scaling',
|
|
131
|
+
scaling,
|
|
132
|
+
`${BuiltInParameter.Scaling}.${node.name}`,
|
|
133
|
+
options.animationTimeMs
|
|
134
|
+
);
|
|
135
|
+
} else {
|
|
136
|
+
node.scaling = scaling;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
parameterManager.setParameterObserver(BuiltInParameter.Color, async ({ materials, newValue, options }) => {
|
|
141
|
+
const color = ParameterManager.parseColor(newValue);
|
|
142
|
+
|
|
143
|
+
for (const material of materials) {
|
|
144
|
+
const materialCls = material.getClassName();
|
|
145
|
+
switch (materialCls) {
|
|
146
|
+
case 'PBRMaterial':
|
|
147
|
+
if (options?.animationTimeMs) {
|
|
148
|
+
_animateParameter(
|
|
149
|
+
material as PBRMaterial,
|
|
150
|
+
'albedoColor',
|
|
151
|
+
color.toLinearSpace(),
|
|
152
|
+
`${BuiltInParameter.Color}.${material.id}`,
|
|
153
|
+
options.animationTimeMs
|
|
154
|
+
);
|
|
155
|
+
} else {
|
|
156
|
+
(material as PBRMaterial).albedoColor = color.toLinearSpace();
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
case 'StandardMaterial':
|
|
160
|
+
(material as StandardMaterial).diffuseColor = color;
|
|
161
|
+
break;
|
|
162
|
+
default:
|
|
163
|
+
throw new ViewerError({
|
|
164
|
+
id: ViewerErrorIds.InvalidParameterSubject,
|
|
165
|
+
message: `Setting color for material of instance "${materialCls}" not implemented`,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
parameterManager.setParameterObserver(BuiltInParameter.Roughness, async ({ materials, newValue }) => {
|
|
171
|
+
const roughness = ParameterManager.parseNumber(newValue);
|
|
172
|
+
|
|
173
|
+
for (const material of materials) {
|
|
174
|
+
const materialCls = material.getClassName();
|
|
175
|
+
switch (materialCls) {
|
|
176
|
+
case 'PBRMaterial':
|
|
177
|
+
(material as PBRMaterial).roughness = roughness;
|
|
178
|
+
break;
|
|
179
|
+
case 'StandardMaterial':
|
|
180
|
+
(material as StandardMaterial).roughness = roughness;
|
|
181
|
+
break;
|
|
182
|
+
default:
|
|
183
|
+
throw new ViewerError({
|
|
184
|
+
id: ViewerErrorIds.InvalidParameterSubject,
|
|
185
|
+
message: `Setting rougness for material of instance "${materialCls}" not implemented`,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
parameterManager.setParameterObserver(BuiltInParameter.Metallic, async ({ materials, newValue }) => {
|
|
191
|
+
const metallic = ParameterManager.parseNumber(newValue);
|
|
192
|
+
|
|
193
|
+
for (const material of materials) {
|
|
194
|
+
const materialCls = material.getClassName();
|
|
195
|
+
switch (materialCls) {
|
|
196
|
+
case 'PBRMaterial':
|
|
197
|
+
(material as PBRMaterial).metallic = metallic;
|
|
198
|
+
break;
|
|
199
|
+
default:
|
|
200
|
+
throw new ViewerError({
|
|
201
|
+
id: ViewerErrorIds.InvalidParameterSubject,
|
|
202
|
+
message: `Setting metallic for material of instance "${materialCls}" not implemented`,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
parameterManager.setParameterObserver(BuiltInParameter.Influence, async ({ nodes, newValue, options }) => {
|
|
208
|
+
const morphTargetValues = ParameterManager.parseMorphTargets(newValue);
|
|
209
|
+
|
|
210
|
+
for (const morphTargetValue of morphTargetValues) {
|
|
211
|
+
for (const node of nodes) {
|
|
212
|
+
const target = node instanceof AbstractMesh && node.morphTargetManager?.getTargetByName(morphTargetValue.name);
|
|
213
|
+
|
|
214
|
+
if (target) {
|
|
215
|
+
if (options?.animationTimeMs) {
|
|
216
|
+
_animateParameter(
|
|
217
|
+
target,
|
|
218
|
+
'influence',
|
|
219
|
+
morphTargetValue.value,
|
|
220
|
+
`${BuiltInParameter.Influence}.${node.name}.${target.name}`,
|
|
221
|
+
options.animationTimeMs
|
|
222
|
+
);
|
|
223
|
+
} else {
|
|
224
|
+
target.influence = morphTargetValue.value;
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
throw new ViewerError({
|
|
228
|
+
id: ViewerErrorIds.InvalidParameterSubject,
|
|
229
|
+
message: `Morph target couldn't be found, node: ${node.name} / morph target: ${morphTargetValue.name}`,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function _animateParameter<T, K extends Extract<keyof T, AnimatableProperties>>(
|
|
238
|
+
target: T,
|
|
239
|
+
property: K,
|
|
240
|
+
value: T[K],
|
|
241
|
+
animationNameSuffix: string,
|
|
242
|
+
animationTimeMs: number
|
|
243
|
+
): Promise<void> {
|
|
244
|
+
const framesPerSec = 100;
|
|
245
|
+
// extend if further `AnimatableProperties` with different types are added
|
|
246
|
+
const dataType =
|
|
247
|
+
value instanceof Vector3
|
|
248
|
+
? Animation.ANIMATIONTYPE_VECTOR3
|
|
249
|
+
: value instanceof Color3
|
|
250
|
+
? Animation.ANIMATIONTYPE_COLOR3
|
|
251
|
+
: Animation.ANIMATIONTYPE_FLOAT;
|
|
252
|
+
|
|
253
|
+
const animationGroup = new AnimationGroup(`${ParameterManager.PARAMETER_ANIMATION_NAME}.${animationNameSuffix}`);
|
|
254
|
+
|
|
255
|
+
const animation = new Animation(property, property, framesPerSec, dataType);
|
|
256
|
+
animation.setKeys([
|
|
257
|
+
{ frame: 0, value: target[property] },
|
|
258
|
+
{ frame: framesPerSec, value: value },
|
|
259
|
+
]);
|
|
260
|
+
animationGroup.addTargetedAnimation(animation, target);
|
|
261
|
+
|
|
262
|
+
await playAnimationGroupOnce(animationGroup, framesPerSec, Math.max(animationTimeMs, 0));
|
|
263
|
+
}
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
BaseTexture,
|
|
3
|
+
BuiltInParameter,
|
|
4
|
+
Material,
|
|
5
|
+
PBRMaterial,
|
|
6
|
+
ParameterManager,
|
|
7
|
+
Scene,
|
|
8
|
+
Texture,
|
|
9
|
+
TextureManager,
|
|
10
|
+
ViewerError,
|
|
11
|
+
ViewerErrorIds,
|
|
12
|
+
} from '../..';
|
|
13
|
+
import { embedAssets } from '../svg-helper';
|
|
4
14
|
import isSvg from 'is-svg';
|
|
5
15
|
|
|
6
16
|
/**
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
ViewerErrorIds,
|
|
12
12
|
ViewerEvent,
|
|
13
13
|
} from '../index';
|
|
14
|
+
import { playAnimationGroupOnce } from '../internal/animation-helper';
|
|
14
15
|
import { downloadFile } from '../internal/export-helper';
|
|
15
16
|
import { nodeMatchesAnyCriteria } from '../internal/node-helper';
|
|
16
17
|
import { createScreenshotCanvas, trimCanvas } from '../internal/screenshot-helper';
|
|
@@ -282,16 +283,7 @@ export class CameraManager {
|
|
|
282
283
|
);
|
|
283
284
|
}
|
|
284
285
|
|
|
285
|
-
|
|
286
|
-
const speedRatio = 1000 / durationMs;
|
|
287
|
-
|
|
288
|
-
return new Promise<void>(resolve => {
|
|
289
|
-
animationGroup.onAnimationGroupEndObservable.addOnce(() => {
|
|
290
|
-
animationGroup.dispose();
|
|
291
|
-
resolve();
|
|
292
|
-
});
|
|
293
|
-
animationGroup.start(false, speedRatio, 0, 100);
|
|
294
|
-
});
|
|
286
|
+
await playAnimationGroupOnce(animationGroup, framesPerSec, durationMs);
|
|
295
287
|
}
|
|
296
288
|
|
|
297
289
|
/**
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
Viewer,
|
|
16
16
|
ViewerEvent,
|
|
17
17
|
} from '../index';
|
|
18
|
+
import { nodeHasLeftHandedScaling } from '../internal/math-helper';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Manager for debugging functionalities
|
|
@@ -39,7 +40,7 @@ export class DebugManager {
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
/**
|
|
42
|
-
* Enables the Babylon.js [Inspector](https://doc.babylonjs.com/
|
|
43
|
+
* Enables the Babylon.js [Inspector](https://doc.babylonjs.com/legacy/inspector/).\
|
|
43
44
|
* Due to the additional size of the inspector, this function is only available in "development" builds!
|
|
44
45
|
*
|
|
45
46
|
* @returns Signalizes if inspector could be loaded
|
|
@@ -109,7 +110,12 @@ The inspector can only be used in development builds.`);
|
|
|
109
110
|
const factor = DebugManager._getWorldCoordinatesAxesUnifyFactor();
|
|
110
111
|
this._axesViewer = new AxesViewer(utilityLayerScene, size * factor);
|
|
111
112
|
|
|
113
|
+
let isLeftHanded = true;
|
|
112
114
|
if (node) {
|
|
115
|
+
// required for correct rotation when calling right after creating the node (see CB-11240)
|
|
116
|
+
// force=true might not be needed, but it's safer
|
|
117
|
+
node.computeWorldMatrix(true);
|
|
118
|
+
|
|
113
119
|
// get the world rotation of the mesh and extract the x, y, z axes from that
|
|
114
120
|
// NOTE: `node.getDirection` can't be used because it considers the scaling as well, resulting in distorted axes
|
|
115
121
|
const worldRotMatrix = new Matrix();
|
|
@@ -129,13 +135,15 @@ The inspector can only be used in development builds.`);
|
|
|
129
135
|
if (node.absoluteScaling.z < 0) {
|
|
130
136
|
zAxis.scaleInPlace(-1);
|
|
131
137
|
}
|
|
132
|
-
|
|
133
138
|
this._axesViewer.update(node.absolutePosition, xAxis, yAxis, zAxis);
|
|
139
|
+
|
|
140
|
+
isLeftHanded = nodeHasLeftHandedScaling(node);
|
|
134
141
|
}
|
|
135
142
|
|
|
136
143
|
DebugManager._prepareAxisVisual('X', this._axesViewer.xAxis);
|
|
137
144
|
DebugManager._prepareAxisVisual('Y', this._axesViewer.yAxis);
|
|
138
145
|
DebugManager._prepareAxisVisual('Z', this._axesViewer.zAxis);
|
|
146
|
+
DebugManager._drawLabel(isLeftHanded ? 'LH' : 'RH', -0.05, '#DD0060', this._axesViewer.xAxis, 2);
|
|
139
147
|
}
|
|
140
148
|
|
|
141
149
|
public hideCoordinateSystem(): void {
|
|
@@ -181,7 +189,6 @@ The inspector can only be used in development builds.`);
|
|
|
181
189
|
protected static _prepareAxisVisual(labelText: string, axisNode: TransformNode): void {
|
|
182
190
|
// fixed values used for label size and distance
|
|
183
191
|
const colorScale = 2; // used to multiply the original color scale of 0.5 - result is 1
|
|
184
|
-
const labelSize = 0.05;
|
|
185
192
|
const labelDistance = 0.4;
|
|
186
193
|
|
|
187
194
|
// inherit the color from the arrow and adjust brightness scaling to be 1
|
|
@@ -191,8 +198,30 @@ The inspector can only be used in development builds.`);
|
|
|
191
198
|
|
|
192
199
|
axisMaterial.emissiveColor = color;
|
|
193
200
|
|
|
201
|
+
DebugManager._drawLabel(labelText, labelDistance, color.toHexString(), axisNode);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* More generic function for drawing a letter on a axis arrow, e.g. for drawing coordinate system letters
|
|
206
|
+
*/
|
|
207
|
+
protected static _drawLabel(
|
|
208
|
+
labelText: string,
|
|
209
|
+
labelDistance: number,
|
|
210
|
+
color: string,
|
|
211
|
+
axisNode: TransformNode,
|
|
212
|
+
xScale = 1
|
|
213
|
+
): void {
|
|
214
|
+
const labelHeight = 0.05;
|
|
215
|
+
const labelWidth = labelHeight * xScale;
|
|
216
|
+
const textureHeight = 1024;
|
|
217
|
+
const textureWidth = textureHeight * xScale;
|
|
218
|
+
|
|
194
219
|
const utilityLayerScene = UtilityLayerRenderer.DefaultUtilityLayer.utilityLayerScene;
|
|
195
|
-
const labelPlane = MeshBuilder.CreatePlane(
|
|
220
|
+
const labelPlane = MeshBuilder.CreatePlane(
|
|
221
|
+
`${labelText}_label`,
|
|
222
|
+
{ width: labelWidth, height: labelHeight },
|
|
223
|
+
utilityLayerScene
|
|
224
|
+
);
|
|
196
225
|
labelPlane.parent = axisNode;
|
|
197
226
|
labelPlane.position = new Vector3(0, 0, labelDistance);
|
|
198
227
|
// keep orientation of mesh, so that it is always visible, independent of the camera position
|
|
@@ -201,12 +230,12 @@ The inspector can only be used in development builds.`);
|
|
|
201
230
|
// create GUI element
|
|
202
231
|
const textElement = new TextBlock();
|
|
203
232
|
textElement.text = labelText;
|
|
204
|
-
textElement.color = color
|
|
233
|
+
textElement.color = color;
|
|
205
234
|
textElement.fontSize = '100%';
|
|
206
235
|
textElement.fontFamily = 'Arial';
|
|
207
236
|
textElement.fontWeight = 'bold';
|
|
208
237
|
|
|
209
|
-
const labelTexture = AdvancedDynamicTexture.CreateForMesh(labelPlane);
|
|
238
|
+
const labelTexture = AdvancedDynamicTexture.CreateForMesh(labelPlane, textureWidth, textureHeight);
|
|
210
239
|
labelTexture.addControl(textElement);
|
|
211
240
|
}
|
|
212
241
|
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from '../index';
|
|
11
11
|
import { BaseAsset, loadAsset, prepareAssetForScene } from '../internal/asset-helper';
|
|
12
12
|
import { cloneModelAssetContainer } from '../internal/cloning-helper';
|
|
13
|
+
import { flipHandednessOfNode, nodeHasLeftHandedScaling } from '../internal/math-helper';
|
|
13
14
|
import { isArray } from 'lodash-es';
|
|
14
15
|
|
|
15
16
|
export type ParsedDecalConfiguration = DecalConfiguration & { materialId?: string; tags?: string };
|
|
@@ -24,6 +25,11 @@ export type ModelVisibilityEntry = {
|
|
|
24
25
|
visible: boolean;
|
|
25
26
|
};
|
|
26
27
|
|
|
28
|
+
export type ModelData = {
|
|
29
|
+
assetContainer: AssetContainer;
|
|
30
|
+
rootNode?: TransformNode;
|
|
31
|
+
};
|
|
32
|
+
|
|
27
33
|
/**
|
|
28
34
|
* Callback for renaming nodes when cloning nodes in a model
|
|
29
35
|
*/
|
|
@@ -34,6 +40,12 @@ export type NodeNamingStrategy = (node: TransformNode, newModelName: string) =>
|
|
|
34
40
|
export type TagNamingStrategy = (tag: string, newModelName: string) => string;
|
|
35
41
|
|
|
36
42
|
export type ModelCloneOptions = {
|
|
43
|
+
/**
|
|
44
|
+
* Indicates if cloned model should be shown immediately
|
|
45
|
+
*
|
|
46
|
+
* Default: true
|
|
47
|
+
*/
|
|
48
|
+
show?: boolean;
|
|
37
49
|
/**
|
|
38
50
|
* `true:` Creates "[InstancedMeshes](https://doc.babylonjs.com/typedoc/classes/BABYLON.InstancedMesh)" from meshes of
|
|
39
51
|
* the base model instead of "independent" meshes. Most of the data, especially the geometry and material assignment
|
|
@@ -42,6 +54,21 @@ export type ModelCloneOptions = {
|
|
|
42
54
|
* clones.
|
|
43
55
|
*/
|
|
44
56
|
createInstance?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Optionally set a parent node for the cloned model directly.\
|
|
59
|
+
* Coordinate system handedness of the cloned model root node will be adapted to fit the handedness of this parent
|
|
60
|
+
* node.
|
|
61
|
+
* - parentNode left handed (Babylon.js default): leave imported model root node as is
|
|
62
|
+
* - parentNode right handed (e.g. when adding to existing structure): flip handedness, resetting rotation to 0/0/0
|
|
63
|
+
* and scaling to 1/1/1
|
|
64
|
+
*/
|
|
65
|
+
parentNode?: TransformNode;
|
|
66
|
+
/**
|
|
67
|
+
* Optinally overwrite name of root node.\
|
|
68
|
+
* This is especially useful for improving the readability of the node structure via the inspector.\
|
|
69
|
+
* This setting has priority over the `nodeNamingStrategy`.
|
|
70
|
+
*/
|
|
71
|
+
rootNodeName?: string;
|
|
45
72
|
nodeNamingStrategy?: NodeNamingStrategy;
|
|
46
73
|
tagNamingStrategy?: TagNamingStrategy;
|
|
47
74
|
};
|
|
@@ -252,12 +279,9 @@ export class ModelManager {
|
|
|
252
279
|
* @param options additional options for the cloning procedure, like renaming algorithms
|
|
253
280
|
* @returns asset container for further processing of the clone
|
|
254
281
|
*/
|
|
255
|
-
public async cloneModel(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
show: boolean = true,
|
|
259
|
-
options?: ModelCloneOptions
|
|
260
|
-
): Promise<AssetContainer> {
|
|
282
|
+
public async cloneModel(name: string, newModelName: string, options?: ModelCloneOptions): Promise<ModelData> {
|
|
283
|
+
const { show, createInstance, parentNode, rootNodeName } = options ?? {};
|
|
284
|
+
|
|
261
285
|
const sourceModel = this._getModel(name);
|
|
262
286
|
if (!sourceModel) {
|
|
263
287
|
throw new ViewerError({
|
|
@@ -286,15 +310,38 @@ export class ModelManager {
|
|
|
286
310
|
state: 'loaded',
|
|
287
311
|
assetContainer: cloneModelAssetContainer(sourceModel.assetContainer, newModelName, this.viewer.scene, options),
|
|
288
312
|
isClone: true,
|
|
289
|
-
sourceModelName:
|
|
313
|
+
sourceModelName: createInstance ? sourceModel.name : undefined,
|
|
290
314
|
};
|
|
291
315
|
this._modelAssets[newModelName] = clonedModel;
|
|
292
316
|
|
|
293
|
-
|
|
317
|
+
const rootNode = this._getRootNodeOfModel(clonedModel.assetContainer);
|
|
318
|
+
if (rootNode && rootNodeName) {
|
|
319
|
+
rootNode.name = rootNodeName;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (show !== false) {
|
|
294
323
|
await this._showModel(clonedModel);
|
|
324
|
+
|
|
325
|
+
if (parentNode && rootNode) {
|
|
326
|
+
const parentNodeIsLeftHanded = nodeHasLeftHandedScaling(parentNode);
|
|
327
|
+
|
|
328
|
+
// Freshly loaded (and cloned) models are left handed by default.
|
|
329
|
+
// If the parent node is right handed, we know that a conversion has already been done and the root node
|
|
330
|
+
// conversion node of the new model has to be reset.
|
|
331
|
+
//
|
|
332
|
+
// We expect a rotation of 0/0/0 and scaling 1/1/1 when following our default workflow, which is uploading GLBs
|
|
333
|
+
// on the platform.
|
|
334
|
+
// Still it's a generic solution and so we actually flip the current world matrix instead of applying these
|
|
335
|
+
// transformation values in a hardcoded way.
|
|
336
|
+
if (!parentNodeIsLeftHanded) {
|
|
337
|
+
flipHandednessOfNode(rootNode);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
rootNode.parent = parentNode;
|
|
341
|
+
}
|
|
295
342
|
}
|
|
296
343
|
|
|
297
|
-
return clonedModel.assetContainer;
|
|
344
|
+
return { assetContainer: clonedModel.assetContainer, rootNode };
|
|
298
345
|
}
|
|
299
346
|
|
|
300
347
|
/**
|
|
@@ -335,11 +382,11 @@ export class ModelManager {
|
|
|
335
382
|
}
|
|
336
383
|
|
|
337
384
|
/**
|
|
338
|
-
* Loads and returns the asset container of
|
|
385
|
+
* Loads the model and returns the asset container and root node of that model.\
|
|
339
386
|
* This can be used to access the models content without having to add it to the scene.\
|
|
340
|
-
* A typical use case is to clone or instantiate a node from a "library" model.
|
|
387
|
+
* A typical use case is to clone or instantiate a model or single node from a "library" model.
|
|
341
388
|
*/
|
|
342
|
-
public async
|
|
389
|
+
public async getModelData(name: string): Promise<ModelData> {
|
|
343
390
|
const model = this._getModel(name);
|
|
344
391
|
if (!model) {
|
|
345
392
|
throw new ViewerError({
|
|
@@ -358,7 +405,9 @@ export class ModelManager {
|
|
|
358
405
|
await this._prepareModelForScene(model);
|
|
359
406
|
}
|
|
360
407
|
|
|
361
|
-
|
|
408
|
+
const rootNode = this._getRootNodeOfModel(model.assetContainer);
|
|
409
|
+
|
|
410
|
+
return { assetContainer: model.assetContainer, rootNode };
|
|
362
411
|
}
|
|
363
412
|
|
|
364
413
|
/**
|
|
@@ -465,4 +514,23 @@ export class ModelManager {
|
|
|
465
514
|
model.assetContainer.removeFromScene();
|
|
466
515
|
model.state = 'loaded';
|
|
467
516
|
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Returns the root node of a models asset container.\
|
|
520
|
+
* This can be useful to reposition an entire (cloned) model.\
|
|
521
|
+
* Function expects only one root `TransformNode` and will return `undefined` otherwise.
|
|
522
|
+
*/
|
|
523
|
+
protected _getRootNodeOfModel(assetContainer: AssetContainer): TransformNode | undefined {
|
|
524
|
+
// `assetContainer.rootNodes` can also contain other `Node` types like lights or cameras, which we don't want here
|
|
525
|
+
const rootNodes = assetContainer.rootNodes.filter(node => node instanceof TransformNode) as TransformNode[];
|
|
526
|
+
if (rootNodes.length === 0) {
|
|
527
|
+
console.warn(`No root node found for associated model`);
|
|
528
|
+
return undefined;
|
|
529
|
+
} else if (rootNodes.length > 1) {
|
|
530
|
+
console.warn(`Mutltiple root nodes found for associated model`);
|
|
531
|
+
return undefined;
|
|
532
|
+
} else {
|
|
533
|
+
return rootNodes[0];
|
|
534
|
+
}
|
|
535
|
+
}
|
|
468
536
|
}
|