@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
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
AbstractMesh,
|
|
3
2
|
Color3,
|
|
4
3
|
IAssetContainer,
|
|
5
4
|
Material,
|
|
6
5
|
PBRMaterial,
|
|
7
|
-
StandardMaterial,
|
|
8
6
|
Tags,
|
|
9
7
|
TransformNode,
|
|
10
8
|
Vector3,
|
|
@@ -12,16 +10,16 @@ import {
|
|
|
12
10
|
ViewerError,
|
|
13
11
|
ViewerErrorIds,
|
|
14
12
|
} from '../index';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { getTagsAsStrArr } from '../internal/tags-helper';
|
|
13
|
+
import { createPaintableParameter } from '../internal/parameter/paintable-parameter-helper';
|
|
14
|
+
import { createBuiltInParameter } from '../internal/parameter/parameter-helper';
|
|
18
15
|
import {
|
|
19
16
|
BuiltInTextureParameter,
|
|
20
17
|
BuiltInTextureParameterKeys,
|
|
21
18
|
ParameterTextureChannelsKeys,
|
|
22
19
|
createBuiltInTextureParameter,
|
|
23
|
-
} from '../internal/texture-parameter-helper';
|
|
24
|
-
import {
|
|
20
|
+
} from '../internal/parameter/texture-parameter-helper';
|
|
21
|
+
import { getTagsAsStrArr } from '../internal/tags-helper';
|
|
22
|
+
import { capitalize, isArray, isNumber, isObject, isString } from 'lodash-es';
|
|
25
23
|
|
|
26
24
|
/**
|
|
27
25
|
* Parameters with a built in observer implementation
|
|
@@ -51,10 +49,16 @@ export const LegacyParameter = {
|
|
|
51
49
|
Paintable: 'paintable',
|
|
52
50
|
};
|
|
53
51
|
|
|
52
|
+
export type ParameterName = string;
|
|
53
|
+
|
|
54
54
|
export type MorphTargetParameterValue = { name: string; value: number };
|
|
55
|
+
const isMorphTargetParameterValue = (value: InputParameterValue): value is MorphTargetParameterValue =>
|
|
56
|
+
isObject(value) && 'name' in value && isString(value.name) && 'value' in value && isNumber(value.value);
|
|
57
|
+
const isMorphTargetsParameterValue = (value: ParameterValue): value is MorphTargetParameterValue[] =>
|
|
58
|
+
isArray(value) && value.every(mT => isMorphTargetParameterValue(mT));
|
|
55
59
|
|
|
56
|
-
export type
|
|
57
|
-
export type
|
|
60
|
+
export type ParameterValue = string | number | boolean | Vector3 | Color3 | MorphTargetParameterValue[];
|
|
61
|
+
export type InputParameterValue = Exclude<ParameterValue, MorphTargetParameterValue[]> | MorphTargetParameterValue;
|
|
58
62
|
|
|
59
63
|
export type TagParameterSubject = { tagName: string; nodeName?: never; materialName?: never };
|
|
60
64
|
export type NodeParameterSubject = { tagName?: never; nodeName: string; materialName?: never };
|
|
@@ -87,9 +91,14 @@ const isMaterialParameterSubject = (subject: ParameterSubject): subject is Mater
|
|
|
87
91
|
*/
|
|
88
92
|
export type ParameterValues = (ParameterSubject & {
|
|
89
93
|
parameterName: ParameterName;
|
|
90
|
-
value:
|
|
94
|
+
value: InputParameterValue;
|
|
95
|
+
options?: ParameterOptions;
|
|
91
96
|
})[];
|
|
92
97
|
|
|
98
|
+
export type ParameterOptions = {
|
|
99
|
+
animationTimeMs?: number;
|
|
100
|
+
};
|
|
101
|
+
|
|
93
102
|
/**
|
|
94
103
|
* Definition of callback function for parameter change
|
|
95
104
|
*/
|
|
@@ -105,6 +114,7 @@ export type ParameterObserverPayload = {
|
|
|
105
114
|
materials: Material[];
|
|
106
115
|
newValue: ParameterValue;
|
|
107
116
|
oldValue: ParameterValue | undefined;
|
|
117
|
+
options?: ParameterOptions;
|
|
108
118
|
};
|
|
109
119
|
|
|
110
120
|
// internal type which holds the data of a certain parameter entry
|
|
@@ -113,9 +123,12 @@ type ParameterEntry = {
|
|
|
113
123
|
parameterName: ParameterName;
|
|
114
124
|
value: ParameterValue;
|
|
115
125
|
oldValue: ParameterValue | undefined;
|
|
126
|
+
options?: ParameterOptions;
|
|
116
127
|
};
|
|
117
128
|
|
|
118
129
|
export class ParameterManager {
|
|
130
|
+
public static readonly PARAMETER_ANIMATION_NAME = '$parameterAnimation';
|
|
131
|
+
|
|
119
132
|
/**
|
|
120
133
|
* Parses and converts input to a boolean value, valid values are:
|
|
121
134
|
* - true / false
|
|
@@ -230,7 +243,7 @@ export class ParameterManager {
|
|
|
230
243
|
});
|
|
231
244
|
}
|
|
232
245
|
|
|
233
|
-
public static
|
|
246
|
+
public static parseMorphTargets(value: ParameterValue): MorphTargetParameterValue[] {
|
|
234
247
|
let objValue;
|
|
235
248
|
if (isString(value)) {
|
|
236
249
|
try {
|
|
@@ -245,8 +258,8 @@ export class ParameterManager {
|
|
|
245
258
|
objValue = value;
|
|
246
259
|
}
|
|
247
260
|
|
|
248
|
-
if (
|
|
249
|
-
return { name:
|
|
261
|
+
if (isMorphTargetsParameterValue(objValue)) {
|
|
262
|
+
return objValue.map(mT => ({ name: mT.name, value: mT.value }));
|
|
250
263
|
} else {
|
|
251
264
|
throw new ViewerError({
|
|
252
265
|
id: ViewerErrorIds.InvalidParameterValue,
|
|
@@ -271,9 +284,11 @@ export class ParameterManager {
|
|
|
271
284
|
public async setNodeParameterValue(
|
|
272
285
|
nodeName: string,
|
|
273
286
|
parameterName: ParameterName,
|
|
274
|
-
value:
|
|
287
|
+
value: InputParameterValue,
|
|
288
|
+
options?: ParameterOptions
|
|
275
289
|
): Promise<boolean> {
|
|
276
|
-
const
|
|
290
|
+
const internalValue = this._prepareInputValue(value);
|
|
291
|
+
const valueChanged = this._addParameterValue({ nodeName }, parameterName, internalValue, options);
|
|
277
292
|
if (valueChanged) {
|
|
278
293
|
await this._applyParameterValue({ nodeName }, parameterName);
|
|
279
294
|
}
|
|
@@ -289,9 +304,11 @@ export class ParameterManager {
|
|
|
289
304
|
public async setMaterialParameterValue(
|
|
290
305
|
materialName: string,
|
|
291
306
|
parameterName: ParameterName,
|
|
292
|
-
value:
|
|
307
|
+
value: InputParameterValue,
|
|
308
|
+
options?: ParameterOptions
|
|
293
309
|
): Promise<boolean> {
|
|
294
|
-
const
|
|
310
|
+
const internalValue = this._prepareInputValue(value);
|
|
311
|
+
const valueChanged = this._addParameterValue({ materialName }, parameterName, internalValue, options);
|
|
295
312
|
if (valueChanged) {
|
|
296
313
|
await this._applyParameterValue({ materialName }, parameterName);
|
|
297
314
|
}
|
|
@@ -309,9 +326,11 @@ export class ParameterManager {
|
|
|
309
326
|
public async setTagParameterValue(
|
|
310
327
|
tagName: string,
|
|
311
328
|
parameterName: ParameterName,
|
|
312
|
-
value:
|
|
329
|
+
value: InputParameterValue,
|
|
330
|
+
options?: ParameterOptions
|
|
313
331
|
): Promise<boolean> {
|
|
314
|
-
const
|
|
332
|
+
const internalValue = this._prepareInputValue(value);
|
|
333
|
+
const valueChanged = this._addParameterValue({ tagName }, parameterName, internalValue, options);
|
|
315
334
|
if (valueChanged) {
|
|
316
335
|
await this._applyParameterValue({ tagName }, parameterName);
|
|
317
336
|
}
|
|
@@ -333,19 +352,30 @@ export class ParameterManager {
|
|
|
333
352
|
: isMaterialParameterSubject(valueEntry)
|
|
334
353
|
? { materialName: valueEntry.materialName }
|
|
335
354
|
: { tagName: valueEntry.tagName };
|
|
336
|
-
|
|
337
|
-
|
|
355
|
+
const value = this._prepareInputValue(valueEntry.value);
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
subject,
|
|
359
|
+
parameterName: valueEntry.parameterName,
|
|
360
|
+
value,
|
|
361
|
+
oldValue: undefined,
|
|
362
|
+
options: valueEntry.options,
|
|
363
|
+
};
|
|
338
364
|
});
|
|
339
365
|
|
|
340
|
-
const changedParameterEntries = parameterEntries.filter(
|
|
341
|
-
this._addParameterValue(
|
|
366
|
+
const changedParameterEntries = parameterEntries.filter(({ subject, parameterName, value, options }) =>
|
|
367
|
+
this._addParameterValue(subject, parameterName, value, options)
|
|
342
368
|
);
|
|
343
369
|
|
|
344
370
|
await this._applyParameterValues(changedParameterEntries);
|
|
345
371
|
|
|
346
372
|
// convert back to original typing
|
|
347
373
|
const changedParameterValues: ParameterValues = changedParameterEntries.map(paramEntry => {
|
|
348
|
-
|
|
374
|
+
const value = isMorphTargetsParameterValue(paramEntry.value)
|
|
375
|
+
? // we can be sure that only one morph target was given on the input
|
|
376
|
+
paramEntry.value[0]
|
|
377
|
+
: paramEntry.value;
|
|
378
|
+
return { ...paramEntry.subject, parameterName: paramEntry.parameterName, value };
|
|
349
379
|
});
|
|
350
380
|
|
|
351
381
|
return changedParameterValues;
|
|
@@ -459,6 +489,52 @@ export class ParameterManager {
|
|
|
459
489
|
await this._applyParameterValues(textureParameterEntriesToApply);
|
|
460
490
|
}
|
|
461
491
|
|
|
492
|
+
/**
|
|
493
|
+
* @returns Desired parameter value of a certain node.\
|
|
494
|
+
* Tags are considered as well but have lower priority than node parameters, as these are more specific.
|
|
495
|
+
*
|
|
496
|
+
* @internal
|
|
497
|
+
*/
|
|
498
|
+
public getParameterValueOfNode(node: TransformNode, parameterName: string): ParameterValue | undefined {
|
|
499
|
+
const nodeParamValue = this.getParameterValue({ nodeName: node.name }, parameterName);
|
|
500
|
+
if (nodeParamValue !== undefined) {
|
|
501
|
+
return nodeParamValue;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const tags = getTagsAsStrArr(node);
|
|
505
|
+
const tagParamValue = tags.reduce<ParameterValue | undefined>((accValue, curTag) => {
|
|
506
|
+
// NOTE: it is possible that values are available for multiple tags
|
|
507
|
+
// in this case the resulting parameter value is quite "random" as the last tag in the tag string of the node has
|
|
508
|
+
// priority
|
|
509
|
+
const tagParamValue = this.getParameterValue({ tagName: curTag }, parameterName);
|
|
510
|
+
return accValue ?? tagParamValue;
|
|
511
|
+
}, undefined);
|
|
512
|
+
|
|
513
|
+
return tagParamValue;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* @returns Desired parameter value of a certain material.\
|
|
518
|
+
* Tags are considered as well but have lower priority than material parameters, as these are more specific.\
|
|
519
|
+
* Unused ATM, but added for consistency as counter part for {@link _getParameterValueOfNode}
|
|
520
|
+
*
|
|
521
|
+
* @internal
|
|
522
|
+
*/
|
|
523
|
+
public getParameterValueOfMaterial(material: Material, parameterName: string): ParameterValue | undefined {
|
|
524
|
+
const materialParamValue = this.getParameterValue({ materialName: material.name }, parameterName);
|
|
525
|
+
if (materialParamValue !== undefined) {
|
|
526
|
+
return materialParamValue;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const tags = getTagsAsStrArr(material);
|
|
530
|
+
const tagParamValue = tags.reduce<ParameterValue | undefined>((accValue, curTag) => {
|
|
531
|
+
const tagParamValue = this.getParameterValue({ tagName: curTag }, parameterName);
|
|
532
|
+
return accValue ?? tagParamValue;
|
|
533
|
+
}, undefined);
|
|
534
|
+
|
|
535
|
+
return tagParamValue;
|
|
536
|
+
}
|
|
537
|
+
|
|
462
538
|
/**
|
|
463
539
|
* Retrieves visibility state of node, considering pending parameter values and node hierarchy.\
|
|
464
540
|
* `node.isEnabled()` alone is insufficient here, as visibility observers for the desired node or one of it's parents
|
|
@@ -470,7 +546,7 @@ export class ParameterManager {
|
|
|
470
546
|
let curNode: TransformNode | null = node;
|
|
471
547
|
let visibleByParameter: boolean | undefined = undefined;
|
|
472
548
|
while (curNode && visibleByParameter !== false) {
|
|
473
|
-
const curNodeVisibleByParameter = this.
|
|
549
|
+
const curNodeVisibleByParameter = this.getParameterValueOfNode(curNode, BuiltInParameter.Visible);
|
|
474
550
|
if (curNodeVisibleByParameter !== undefined) {
|
|
475
551
|
visibleByParameter = curNodeVisibleByParameter as boolean;
|
|
476
552
|
}
|
|
@@ -485,172 +561,31 @@ export class ParameterManager {
|
|
|
485
561
|
}
|
|
486
562
|
|
|
487
563
|
/**
|
|
488
|
-
*
|
|
564
|
+
* Converts the input parameter value type to the actual internally used parameter type.\
|
|
565
|
+
* ATM this is required for making morph target parameters of a specific node mergeable.
|
|
489
566
|
*/
|
|
490
|
-
protected
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
const
|
|
496
|
-
if (
|
|
497
|
-
|
|
498
|
-
// internal metadata
|
|
499
|
-
// => consider child meshes as well (CB-10143)
|
|
500
|
-
const activatedNodes = [node, ...node.getChildMeshes(false)];
|
|
501
|
-
const setMaterialProms = activatedNodes.map(async an => {
|
|
502
|
-
const anVisibleNested = this.getNestedVisibilityParameterValueOfNode(an);
|
|
503
|
-
const anDeferredMaterial = getInternalMetadataValue(an, 'deferredMaterial');
|
|
504
|
-
const anMaterialParamValue = this._getParameterValueOfNode(an, BuiltInParameter.Material) as
|
|
505
|
-
| string
|
|
506
|
-
| undefined;
|
|
507
|
-
// get latest requested material, either from (new) parameter value or from deferred material
|
|
508
|
-
const anRequestedMaterial = anMaterialParamValue ?? anDeferredMaterial;
|
|
509
|
-
// material observer might have been executed before visibility observer
|
|
510
|
-
// => skip the material application in this case, as if it would be done twice
|
|
511
|
-
const anMaterialToBeSet = getInternalMetadataValue(an, 'materialToBeSet');
|
|
512
|
-
if (anVisibleNested && anRequestedMaterial && !anMaterialToBeSet) {
|
|
513
|
-
await this.viewer.materialManager.setMaterialOnMesh(
|
|
514
|
-
anRequestedMaterial,
|
|
515
|
-
// this cast is fine, as deferred material can only be set on meshes
|
|
516
|
-
an as AbstractMesh
|
|
517
|
-
);
|
|
518
|
-
}
|
|
519
|
-
});
|
|
520
|
-
await Promise.all(setMaterialProms);
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// set enabled state anyway
|
|
524
|
-
node.setEnabled(visible);
|
|
525
|
-
});
|
|
526
|
-
await Promise.all(observerProms);
|
|
527
|
-
});
|
|
528
|
-
this.setParameterObserver(BuiltInParameter.Material, async ({ nodes, newValue }) => {
|
|
529
|
-
const material = ParameterManager.parseString(newValue);
|
|
530
|
-
|
|
531
|
-
const observerProms = nodes.map(async node => {
|
|
532
|
-
if (!(node instanceof AbstractMesh)) {
|
|
533
|
-
throw new ViewerError({
|
|
534
|
-
id: ViewerErrorIds.InvalidParameterSubject,
|
|
535
|
-
message: `Material can't be set, as the target node "${node.name}" is not a mesh`,
|
|
536
|
-
});
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
// only set material if the mesh is visible, or gets visible by the parameter update
|
|
540
|
-
const visible = this.getNestedVisibilityParameterValueOfNode(node);
|
|
541
|
-
if (visible) {
|
|
542
|
-
// visibility observer might have been executed before material observer
|
|
543
|
-
// => skip the material application in this case, as if it would be done twice
|
|
544
|
-
const anMaterialToBeSet = getInternalMetadataValue(node, 'materialToBeSet');
|
|
545
|
-
if (!anMaterialToBeSet) {
|
|
546
|
-
await this.viewer.materialManager.setMaterialOnMesh(material, node);
|
|
547
|
-
}
|
|
548
|
-
} else {
|
|
549
|
-
setInternalMetadataValue(node, 'deferredMaterial', material);
|
|
567
|
+
protected _prepareInputValue(inputValue: InputParameterValue): ParameterValue {
|
|
568
|
+
if (isMorphTargetParameterValue(inputValue)) {
|
|
569
|
+
return [inputValue];
|
|
570
|
+
} else if (isString(inputValue)) {
|
|
571
|
+
try {
|
|
572
|
+
const objValue = JSON.parse(inputValue);
|
|
573
|
+
if (isMorphTargetParameterValue(objValue)) {
|
|
574
|
+
return [objValue];
|
|
550
575
|
}
|
|
551
|
-
})
|
|
552
|
-
|
|
553
|
-
});
|
|
554
|
-
this.setParameterObserver(BuiltInParameter.Position, async ({ nodes, newValue }) => {
|
|
555
|
-
const position = ParameterManager.parseVector(newValue);
|
|
556
|
-
|
|
557
|
-
for (const node of nodes) {
|
|
558
|
-
node.position = position;
|
|
559
|
-
}
|
|
560
|
-
});
|
|
561
|
-
this.setParameterObserver(BuiltInParameter.Rotation, async ({ nodes, newValue }) => {
|
|
562
|
-
const rotation = ParameterManager.parseRotation(newValue);
|
|
563
|
-
|
|
564
|
-
for (const node of nodes) {
|
|
565
|
-
node.rotation = rotation;
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
this.setParameterObserver(BuiltInParameter.Scaling, async ({ nodes, newValue }) => {
|
|
569
|
-
const scaling = ParameterManager.parseVector(newValue);
|
|
576
|
+
} catch (e) {}
|
|
577
|
+
}
|
|
570
578
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
}
|
|
574
|
-
});
|
|
575
|
-
this.setParameterObserver(BuiltInParameter.Color, async ({ materials, newValue }) => {
|
|
576
|
-
const color = ParameterManager.parseColor(newValue);
|
|
577
|
-
|
|
578
|
-
for (const material of materials) {
|
|
579
|
-
const materialCls = material.getClassName();
|
|
580
|
-
switch (materialCls) {
|
|
581
|
-
case 'PBRMaterial':
|
|
582
|
-
(material as PBRMaterial).albedoColor = color.toLinearSpace();
|
|
583
|
-
break;
|
|
584
|
-
case 'StandardMaterial':
|
|
585
|
-
(material as StandardMaterial).diffuseColor = color;
|
|
586
|
-
break;
|
|
587
|
-
default:
|
|
588
|
-
throw new ViewerError({
|
|
589
|
-
id: ViewerErrorIds.InvalidParameterSubject,
|
|
590
|
-
message: `Setting color for material of instance "${materialCls}" not implemented`,
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
});
|
|
595
|
-
this.setParameterObserver(BuiltInParameter.Roughness, async ({ materials, newValue }) => {
|
|
596
|
-
const roughness = ParameterManager.parseNumber(newValue);
|
|
597
|
-
|
|
598
|
-
for (const material of materials) {
|
|
599
|
-
const materialCls = material.getClassName();
|
|
600
|
-
switch (materialCls) {
|
|
601
|
-
case 'PBRMaterial':
|
|
602
|
-
(material as PBRMaterial).roughness = roughness;
|
|
603
|
-
break;
|
|
604
|
-
case 'StandardMaterial':
|
|
605
|
-
(material as StandardMaterial).roughness = roughness;
|
|
606
|
-
break;
|
|
607
|
-
default:
|
|
608
|
-
throw new ViewerError({
|
|
609
|
-
id: ViewerErrorIds.InvalidParameterSubject,
|
|
610
|
-
message: `Setting rougness for material of instance "${materialCls}" not implemented`,
|
|
611
|
-
});
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
});
|
|
615
|
-
this.setParameterObserver(BuiltInParameter.Metallic, async ({ materials, newValue }) => {
|
|
616
|
-
const metallic = ParameterManager.parseNumber(newValue);
|
|
617
|
-
|
|
618
|
-
for (const material of materials) {
|
|
619
|
-
const materialCls = material.getClassName();
|
|
620
|
-
switch (materialCls) {
|
|
621
|
-
case 'PBRMaterial':
|
|
622
|
-
(material as PBRMaterial).metallic = metallic;
|
|
623
|
-
break;
|
|
624
|
-
default:
|
|
625
|
-
throw new ViewerError({
|
|
626
|
-
id: ViewerErrorIds.InvalidParameterSubject,
|
|
627
|
-
message: `Setting metallic for material of instance "${materialCls}" not implemented`,
|
|
628
|
-
});
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
});
|
|
632
|
-
this.setParameterObserver(BuiltInParameter.Influence, async ({ nodes, newValue }) => {
|
|
633
|
-
const morphTargetValue = ParameterManager.parseMorphTarget(newValue);
|
|
634
|
-
|
|
635
|
-
for (const node of nodes) {
|
|
636
|
-
const target = node instanceof AbstractMesh && node.morphTargetManager?.getTargetByName(morphTargetValue.name);
|
|
637
|
-
|
|
638
|
-
if (target) {
|
|
639
|
-
target.influence = morphTargetValue.value;
|
|
640
|
-
} else {
|
|
641
|
-
throw new ViewerError({
|
|
642
|
-
id: ViewerErrorIds.InvalidParameterSubject,
|
|
643
|
-
message: `Morph target couldn't be found, node: ${node.name} / morph target: ${morphTargetValue.name}`,
|
|
644
|
-
});
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
});
|
|
648
|
-
this.setParameterObserver(LegacyParameter.Paintable, async ({ newValue, materials }) => {
|
|
649
|
-
paintableParameterObserver(newValue, materials, this.viewer.scene);
|
|
650
|
-
});
|
|
579
|
+
return inputValue;
|
|
580
|
+
}
|
|
651
581
|
|
|
652
|
-
|
|
582
|
+
/**
|
|
583
|
+
* Parameter observer implementation of default parameters
|
|
584
|
+
*/
|
|
585
|
+
protected _addBuiltInParameterObservers(): void {
|
|
586
|
+
createBuiltInParameter(this, this.viewer.materialManager);
|
|
653
587
|
createBuiltInTextureParameter(this, this.viewer.scene);
|
|
588
|
+
createPaintableParameter(this, this.viewer.scene);
|
|
654
589
|
}
|
|
655
590
|
|
|
656
591
|
/**
|
|
@@ -661,19 +596,31 @@ export class ParameterManager {
|
|
|
661
596
|
protected _addParameterValue(
|
|
662
597
|
subject: ParameterSubject,
|
|
663
598
|
parameterName: ParameterName,
|
|
664
|
-
value: ParameterValue
|
|
599
|
+
value: ParameterValue,
|
|
600
|
+
options?: ParameterOptions
|
|
665
601
|
): boolean {
|
|
666
602
|
const existingEntry = this._getEntry(subject, parameterName);
|
|
667
603
|
|
|
668
|
-
|
|
604
|
+
let mergedValue = value;
|
|
605
|
+
if (existingEntry && isMorphTargetsParameterValue(existingEntry.value) && isMorphTargetsParameterValue(value)) {
|
|
606
|
+
// morph target values have to be merged instead of completely replaced
|
|
607
|
+
const map = new Map(existingEntry.value.map(e => [e.name, e]));
|
|
608
|
+
for (const item of value) {
|
|
609
|
+
map.set(item.name, item);
|
|
610
|
+
}
|
|
611
|
+
mergedValue = Array.from(map.values());
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
if (JSON.stringify(existingEntry?.value) === JSON.stringify(mergedValue)) {
|
|
669
615
|
return false;
|
|
670
616
|
}
|
|
671
617
|
|
|
672
618
|
if (existingEntry) {
|
|
673
619
|
existingEntry.oldValue = existingEntry.value;
|
|
674
|
-
existingEntry.value =
|
|
620
|
+
existingEntry.value = mergedValue;
|
|
621
|
+
existingEntry.options = options;
|
|
675
622
|
} else {
|
|
676
|
-
this._parameterEntries.push({ subject, parameterName, value, oldValue: undefined });
|
|
623
|
+
this._parameterEntries.push({ subject, parameterName, value: mergedValue, oldValue: undefined, options });
|
|
677
624
|
}
|
|
678
625
|
|
|
679
626
|
return true;
|
|
@@ -730,6 +677,7 @@ export class ParameterManager {
|
|
|
730
677
|
oldValue: parameterEntry.oldValue,
|
|
731
678
|
nodes,
|
|
732
679
|
materials,
|
|
680
|
+
options: parameterEntry.options,
|
|
733
681
|
});
|
|
734
682
|
}
|
|
735
683
|
|
|
@@ -797,46 +745,4 @@ export class ParameterManager {
|
|
|
797
745
|
|
|
798
746
|
return entries;
|
|
799
747
|
}
|
|
800
|
-
|
|
801
|
-
/**
|
|
802
|
-
* @returns Desired parameter value of a certain node.\
|
|
803
|
-
* Tags are considered as well but have lower priority than node parameters, as these are more specific.
|
|
804
|
-
*/
|
|
805
|
-
protected _getParameterValueOfNode(node: TransformNode, parameterName: string): ParameterValue | undefined {
|
|
806
|
-
const nodeParamValue = this.getParameterValue({ nodeName: node.name }, parameterName);
|
|
807
|
-
if (nodeParamValue !== undefined) {
|
|
808
|
-
return nodeParamValue;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
const tags = getTagsAsStrArr(node);
|
|
812
|
-
const tagParamValue = tags.reduce<ParameterValue | undefined>((accValue, curTag) => {
|
|
813
|
-
// NOTE: it is possible that values are available for multiple tags
|
|
814
|
-
// in this case the resulting parameter value is quite "random" as the last tag in the tag string of the node has
|
|
815
|
-
// priority
|
|
816
|
-
const tagParamValue = this.getParameterValue({ tagName: curTag }, parameterName);
|
|
817
|
-
return accValue ?? tagParamValue;
|
|
818
|
-
}, undefined);
|
|
819
|
-
|
|
820
|
-
return tagParamValue;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
/**
|
|
824
|
-
* @returns Desired parameter value of a certain material.\
|
|
825
|
-
* Tags are considered as well but have lower priority than material parameters, as these are more specific.\
|
|
826
|
-
* Unused ATM, but added for consistency as counter part for {@link _getParameterValueOfNode}
|
|
827
|
-
*/
|
|
828
|
-
protected _getParameterValueOfMaterial(material: Material, parameterName: string): ParameterValue | undefined {
|
|
829
|
-
const materialParamValue = this.getParameterValue({ materialName: material.name }, parameterName);
|
|
830
|
-
if (materialParamValue !== undefined) {
|
|
831
|
-
return materialParamValue;
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
const tags = getTagsAsStrArr(material);
|
|
835
|
-
const tagParamValue = tags.reduce<ParameterValue | undefined>((accValue, curTag) => {
|
|
836
|
-
const tagParamValue = this.getParameterValue({ tagName: curTag }, parameterName);
|
|
837
|
-
return accValue ?? tagParamValue;
|
|
838
|
-
}, undefined);
|
|
839
|
-
|
|
840
|
-
return tagParamValue;
|
|
841
|
-
}
|
|
842
748
|
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FloatArray,
|
|
3
|
+
Geometry,
|
|
4
|
+
Mesh,
|
|
5
|
+
MorphTarget,
|
|
6
|
+
MorphTargetManager,
|
|
7
|
+
TransformNode,
|
|
8
|
+
Vector3,
|
|
9
|
+
VertexBuffer,
|
|
10
|
+
Viewer,
|
|
11
|
+
} from '../index';
|
|
12
|
+
|
|
13
|
+
export type BakeGeometrySettings = {
|
|
14
|
+
keepTransformation?: boolean;
|
|
15
|
+
};
|
|
16
|
+
export type CreateTransformNodeSettings = {
|
|
17
|
+
parentNode?: TransformNode;
|
|
18
|
+
position?: Vector3;
|
|
19
|
+
rotation?: Vector3;
|
|
20
|
+
scaling?: Vector3;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export class ViewerUtils {
|
|
24
|
+
/** @internal */
|
|
25
|
+
public constructor(protected viewer: Viewer) {}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Removes transformation data from the mesh and stores it in the geometry, which is called "baking".
|
|
29
|
+
* Also considers the geometry change from morph targets and skeletons.
|
|
30
|
+
*
|
|
31
|
+
* @param settings Optionally avoid baking the transformation, as this only works conveniently if the parent structure
|
|
32
|
+
* is baked as well. Baking morph targets only is a use case when preparing a mesh for CSG operations,
|
|
33
|
+
* which doesn't consider morph targets (yet).
|
|
34
|
+
*/
|
|
35
|
+
public bakeGeometryOfMesh(mesh: Mesh, settings?: BakeGeometrySettings): void {
|
|
36
|
+
const { keepTransformation } = settings ?? {};
|
|
37
|
+
|
|
38
|
+
if (!mesh.geometry) {
|
|
39
|
+
// no geometry available, nothing to do
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// geometries can be shared across multiple meshes, first make them unique to avoid side-effects
|
|
44
|
+
mesh.makeGeometryUnique();
|
|
45
|
+
|
|
46
|
+
// Babylon.js already provides a function for baking the current skeleton changes into the geometry
|
|
47
|
+
if (mesh.skeleton) {
|
|
48
|
+
mesh.applySkeleton(mesh.skeleton);
|
|
49
|
+
mesh.skeleton = null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// NOTE: in difference to skeletons and transformations there is no baking function for morph targets (yet)
|
|
53
|
+
// however another approach could be to re-apply the position and normals data, as there are nice functions for it
|
|
54
|
+
// - `getPositionData(applySkeleton: boolean = false, applyMorph: boolean = false)`
|
|
55
|
+
// - `getNormalsData(applySkeleton: boolean = false, applyMorph: boolean = false)`
|
|
56
|
+
// you can decide if skeletons and morph targets can be added, which is exactly what we want
|
|
57
|
+
// I'm still hesitant to use it because "tangent" and "UV" kinds are not supported, whereas I'm not sure if it's
|
|
58
|
+
// required
|
|
59
|
+
// => try it out when there is enough time for detailed regression tests!
|
|
60
|
+
const morphTargetManager = mesh.morphTargetManager;
|
|
61
|
+
const geometry = mesh.geometry;
|
|
62
|
+
|
|
63
|
+
if (morphTargetManager?.numTargets) {
|
|
64
|
+
// apply morph target vertices data to mesh geometry
|
|
65
|
+
// mostly only the "PositionKind" is implemented
|
|
66
|
+
this._bakeMorphTargetManagerIntoVertices(VertexBuffer.PositionKind, morphTargetManager, geometry);
|
|
67
|
+
this._bakeMorphTargetManagerIntoVertices(VertexBuffer.NormalKind, morphTargetManager, geometry);
|
|
68
|
+
this._bakeMorphTargetManagerIntoVertices(VertexBuffer.TangentKind, morphTargetManager, geometry);
|
|
69
|
+
this._bakeMorphTargetManagerIntoVertices(VertexBuffer.UVKind, morphTargetManager, geometry);
|
|
70
|
+
|
|
71
|
+
// remove morph target manager with all it's morph targets
|
|
72
|
+
mesh.morphTargetManager = null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// bake the transformation data in the mesh geometry, fortunately there is already a help function from Babylon.js
|
|
76
|
+
if (!keepTransformation) {
|
|
77
|
+
mesh.bakeCurrentTransformIntoVertices();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get a `TransformNode` by name or create one if not already available.\
|
|
83
|
+
* Optionally set a parent node for the new `TransformNode` right away.
|
|
84
|
+
*/
|
|
85
|
+
public getOrCreateTransformNode(nodeName: string, settings?: CreateTransformNodeSettings): TransformNode {
|
|
86
|
+
const { parentNode, position, rotation, scaling } = settings ?? {};
|
|
87
|
+
const scene = this.viewer.scene;
|
|
88
|
+
let node = scene.getTransformNodeByName(nodeName);
|
|
89
|
+
|
|
90
|
+
if (!node) {
|
|
91
|
+
node = new TransformNode(nodeName, scene);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
node.parent = parentNode ?? null;
|
|
95
|
+
node.position = position ?? Vector3.Zero();
|
|
96
|
+
node.rotation = rotation ?? Vector3.Zero();
|
|
97
|
+
node.scaling = scaling ?? Vector3.One();
|
|
98
|
+
|
|
99
|
+
return node;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @param kind morph targets can affect various vertices kinds, whereas "position" is the most common one
|
|
104
|
+
* still other kinds (like normals or tangents) can be affected as well and can be provided on this input
|
|
105
|
+
*/
|
|
106
|
+
protected _bakeMorphTargetManagerIntoVertices(
|
|
107
|
+
kind: string,
|
|
108
|
+
morphTargetManager: MorphTargetManager,
|
|
109
|
+
geometry: Geometry
|
|
110
|
+
): void {
|
|
111
|
+
const origVerticesData = geometry.getVerticesData(kind);
|
|
112
|
+
if (!origVerticesData) {
|
|
113
|
+
// no vertices data for this kind availabe on the mesh geometry
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let verticesData = [...origVerticesData];
|
|
118
|
+
for (let i = 0; i < morphTargetManager.numTargets; i++) {
|
|
119
|
+
const target = morphTargetManager.getTarget(i);
|
|
120
|
+
const targetVerticesData = this._getVerticesDataFromMorphTarget(kind, target);
|
|
121
|
+
if (targetVerticesData) {
|
|
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
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// apply the updated vertices data
|
|
132
|
+
geometry.setVerticesData(kind, verticesData);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
protected _getVerticesDataFromMorphTarget(kind: string, morphTarget: MorphTarget): FloatArray | null {
|
|
136
|
+
switch (kind) {
|
|
137
|
+
case VertexBuffer.PositionKind:
|
|
138
|
+
return morphTarget.getPositions();
|
|
139
|
+
case VertexBuffer.NormalKind:
|
|
140
|
+
return morphTarget.getNormals();
|
|
141
|
+
case VertexBuffer.TangentKind:
|
|
142
|
+
return morphTarget.getTangents();
|
|
143
|
+
case VertexBuffer.UVKind:
|
|
144
|
+
return morphTarget.getUVs();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|