@combeenation/3d-viewer 11.0.0 → 12.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/api/manager/gltfExportManager.d.ts +17 -93
- package/dist/lib-cjs/api/manager/gltfExportManager.js +110 -249
- package/dist/lib-cjs/api/manager/gltfExportManager.js.map +1 -1
- package/dist/lib-cjs/api/util/geometryHelper.d.ts +7 -7
- package/dist/lib-cjs/api/util/geometryHelper.js +14 -44
- package/dist/lib-cjs/api/util/geometryHelper.js.map +1 -1
- package/dist/lib-cjs/api/util/structureHelper.js +2 -9
- package/dist/lib-cjs/api/util/structureHelper.js.map +1 -1
- package/dist/lib-cjs/buildinfo.json +1 -1
- package/dist/lib-cjs/commonjs.tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/api/manager/gltfExportManager.ts +123 -243
- package/src/api/util/geometryHelper.ts +13 -45
- package/src/api/util/structureHelper.ts +2 -7
package/package.json
CHANGED
|
@@ -1,58 +1,31 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Node as BjsNode, Color3, InstancedMesh, PBRMaterial } from '../../index';
|
|
2
2
|
import { Viewer } from '../classes/viewer';
|
|
3
3
|
import { injectMetadata } from '../util/babylonHelper';
|
|
4
|
+
import { bakeGeometryOfMesh, createMeshFromInstancedMesh, resetTransformation } from '../util/geometryHelper';
|
|
4
5
|
import { isNodeIncludedInExclusionList } from '../util/structureHelper';
|
|
5
|
-
import {
|
|
6
|
-
import { EngineStore } from '@babylonjs/core/Engines/engineStore';
|
|
7
|
-
import { SceneLoader } from '@babylonjs/core/Loading/sceneLoader';
|
|
8
|
-
import { PBRMaterial } from '@babylonjs/core/Materials/PBR/pbrMaterial';
|
|
9
|
-
import { Color3 } from '@babylonjs/core/Maths/math.color';
|
|
10
|
-
import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';
|
|
6
|
+
import { Mesh } from '@babylonjs/core/Meshes/mesh';
|
|
11
7
|
import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
|
|
12
|
-
import {
|
|
13
|
-
import { Scene } from '@babylonjs/core/scene';
|
|
14
|
-
import { GLTF2Export, IExportOptions } from '@babylonjs/serializers/glTF/2.0';
|
|
15
|
-
import { has, merge } from 'lodash-es';
|
|
8
|
+
import { GLTF2Export } from '@babylonjs/serializers/glTF/2.0/glTFSerializer';
|
|
16
9
|
|
|
17
|
-
type MetadataMap = { [key: string]: any };
|
|
18
10
|
export class GltfExportManager {
|
|
19
|
-
protected static readonly
|
|
20
|
-
|
|
11
|
+
protected static readonly _METADATA_PROPS = {
|
|
12
|
+
exportNode: 'exportNode',
|
|
13
|
+
deleteAfterExport: 'deleteAfterExport',
|
|
14
|
+
exchangeMaterialWith: 'exchangeMaterialWith',
|
|
15
|
+
};
|
|
21
16
|
|
|
22
|
-
/**
|
|
23
|
-
* Constructor.
|
|
24
|
-
*/
|
|
25
17
|
protected constructor(protected viewer: Viewer) {}
|
|
26
18
|
|
|
27
|
-
/**
|
|
28
|
-
* Creates an {@link GltfExportManager}.
|
|
29
|
-
*/
|
|
30
19
|
public static async create(viewer: Viewer): Promise<GltfExportManager> {
|
|
31
20
|
return new GltfExportManager(viewer);
|
|
32
21
|
}
|
|
33
22
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
* @param optimizeForAR adjusts the exported GLB so that known AR issues get fixed automatically
|
|
41
|
-
* @param excluded optional list of geometry (meshes, elements, variants, variantInstances) to be excluded from export
|
|
42
|
-
*/
|
|
43
|
-
public async exportGlb(
|
|
44
|
-
filename = 'glb-export.glb',
|
|
45
|
-
{ exchangeRefractionMaterials = true, limitTextureSize = true, ...exportOptions }: IExportOptionsExtended = {},
|
|
46
|
-
optimizeForAR: boolean = false,
|
|
47
|
-
excluded?: ExcludedGeometryList
|
|
48
|
-
): Promise<File | undefined> {
|
|
49
|
-
const { scene, sceneCopied } = await this.exportPreProcess(
|
|
50
|
-
exchangeRefractionMaterials,
|
|
51
|
-
limitTextureSize,
|
|
52
|
-
optimizeForAR
|
|
53
|
-
);
|
|
54
|
-
const glbData = await GLTF2Export.GLBAsync(scene, 'dummy', this.gltfExportOptions(exportOptions, excluded));
|
|
55
|
-
this.exportPostProcess(scene, sceneCopied);
|
|
23
|
+
public async exportGlb(filename = 'glb-export.glb', excluded?: ExcludedGeometryList) {
|
|
24
|
+
await this._exportPreProcess(excluded);
|
|
25
|
+
|
|
26
|
+
const glbData = await GLTF2Export.GLBAsync(this.viewer.scene, 'dummy', this._gltfExportOptions());
|
|
27
|
+
|
|
28
|
+
await this._exportPostProcess();
|
|
56
29
|
|
|
57
30
|
const resBlob = glbData.glTFFiles['dummy.glb'];
|
|
58
31
|
// check if result is valid, according to the typings this could also be a string
|
|
@@ -67,218 +40,142 @@ export class GltfExportManager {
|
|
|
67
40
|
}
|
|
68
41
|
}
|
|
69
42
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
* `exportOptions.exchangeRefractionMaterials` and `exportOptions.limitTextureSize`
|
|
75
|
-
* default to `true` if not given.
|
|
76
|
-
* @param optimizeForAR adjusts the exported GLB so that known AR issues get fixed automatically
|
|
77
|
-
* @param excluded optional list of geometry (meshes, elements, variants, variantInstances) to be excluded from export
|
|
78
|
-
*/
|
|
79
|
-
public async exportGltfToFile(
|
|
80
|
-
filename: string,
|
|
81
|
-
{ exchangeRefractionMaterials = true, limitTextureSize = true, ...exportOptions }: IExportOptionsExtended = {},
|
|
82
|
-
optimizeForAR: boolean = false,
|
|
83
|
-
excluded?: ExcludedGeometryList
|
|
84
|
-
) {
|
|
85
|
-
const { scene, sceneCopied } = await this.exportPreProcess(
|
|
86
|
-
exchangeRefractionMaterials,
|
|
87
|
-
limitTextureSize,
|
|
88
|
-
optimizeForAR
|
|
89
|
-
);
|
|
90
|
-
const gltf = await GLTF2Export.GLTFAsync(scene, filename, this.gltfExportOptions(exportOptions, excluded));
|
|
43
|
+
public async exportGltfToFile(filename: string, excluded?: ExcludedGeometryList) {
|
|
44
|
+
await this._exportPreProcess(excluded);
|
|
45
|
+
|
|
46
|
+
const gltf = await GLTF2Export.GLTFAsync(this.viewer.scene, filename, this._gltfExportOptions());
|
|
91
47
|
gltf.downloadFiles();
|
|
92
|
-
this.exportPostProcess(scene, sceneCopied);
|
|
93
|
-
}
|
|
94
48
|
|
|
95
|
-
|
|
96
|
-
* Exports selected nodes to GLB. This results in one binary file.
|
|
97
|
-
* @param filename name of the .GLB file.
|
|
98
|
-
* @param exportOptions export options to be merged with default options.\
|
|
99
|
-
* `exportOptions.exchangeRefractionMaterials` and `exportOptions.limitTextureSize`
|
|
100
|
-
* default to `true` if not given.
|
|
101
|
-
* @param optimizeForAR adjusts the exported GLB so that known AR issues get fixed automatically
|
|
102
|
-
* @param excluded optional list of geometry (meshes, elements, variants, variantInstances) to be excluded from export
|
|
103
|
-
*/
|
|
104
|
-
public async exportGlbToFile(
|
|
105
|
-
filename: string,
|
|
106
|
-
{ exchangeRefractionMaterials = true, limitTextureSize = true, ...exportOptions }: IExportOptionsExtended = {},
|
|
107
|
-
optimizeForAR: boolean = false,
|
|
108
|
-
excluded?: ExcludedGeometryList
|
|
109
|
-
) {
|
|
110
|
-
const { scene, sceneCopied } = await this.exportPreProcess(
|
|
111
|
-
exchangeRefractionMaterials,
|
|
112
|
-
limitTextureSize,
|
|
113
|
-
optimizeForAR
|
|
114
|
-
);
|
|
115
|
-
const glb = await GLTF2Export.GLBAsync(scene, filename, this.gltfExportOptions(exportOptions, excluded));
|
|
116
|
-
glb.downloadFiles();
|
|
117
|
-
this.exportPostProcess(scene, sceneCopied);
|
|
49
|
+
this._exportPostProcess();
|
|
118
50
|
}
|
|
119
51
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
*/
|
|
123
|
-
protected gltfExportOptions(mergeWithOptions: IExportOptions = {}, excluded?: ExcludedGeometryList): IExportOptions {
|
|
124
|
-
const defaultOptions: IExportOptions = {
|
|
125
|
-
shouldExportNode: function (node: any) {
|
|
126
|
-
if (!node.isEnabled()) {
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
if ((node.name as string).startsWith(Viewer.BOUNDING_BOX_NAME)) {
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
if (excluded && node instanceof TransformNode && isNodeIncludedInExclusionList(node, excluded)) {
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
return true;
|
|
136
|
-
},
|
|
137
|
-
};
|
|
138
|
-
return merge({}, defaultOptions, mergeWithOptions);
|
|
139
|
-
}
|
|
52
|
+
public async exportGlbToFile(filename: string, excluded?: ExcludedGeometryList) {
|
|
53
|
+
await this._exportPreProcess(excluded);
|
|
140
54
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
*
|
|
144
|
-
* @returns Either the original scene if no adjustments have been made or a copied scene (which can be disposed after
|
|
145
|
-
* the export) if adjustments have been made
|
|
146
|
-
*/
|
|
147
|
-
protected async exportPreProcess(
|
|
148
|
-
exchangeRefractionMaterials: boolean,
|
|
149
|
-
limitTextureSize: boolean,
|
|
150
|
-
optimizeForAR: boolean
|
|
151
|
-
): Promise<{ scene: Scene; sceneCopied: boolean }> {
|
|
152
|
-
if (!exchangeRefractionMaterials && !optimizeForAR && !limitTextureSize) {
|
|
153
|
-
// no need to copy the scene if no adjustments have to be made
|
|
154
|
-
return { scene: this.viewer.scene, sceneCopied: false };
|
|
155
|
-
}
|
|
55
|
+
const glb = await GLTF2Export.GLBAsync(this.viewer.scene, filename, this._gltfExportOptions());
|
|
56
|
+
glb.downloadFiles();
|
|
156
57
|
|
|
157
|
-
|
|
158
|
-
|
|
58
|
+
await this._exportPostProcess();
|
|
59
|
+
}
|
|
159
60
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
61
|
+
protected async _exportPreProcess(excluded?: ExcludedGeometryList): Promise<void> {
|
|
62
|
+
// handle materials
|
|
63
|
+
this.viewer.scene.materials
|
|
64
|
+
.filter(material => this._shouldReplaceMaterial(material))
|
|
65
|
+
.forEach(material => this._createRefractionMaterialReplacement(material as PBRMaterial));
|
|
163
66
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
// certain devices can't load models with skeletons, just remove them since animations are not supported in the AR
|
|
169
|
-
// scene anyway
|
|
170
|
-
while (copiedScene.skeletons.length) {
|
|
171
|
-
const skeleton = copiedScene.skeletons.pop();
|
|
172
|
-
skeleton!.dispose();
|
|
173
|
-
}
|
|
174
|
-
}
|
|
67
|
+
// handle nodes
|
|
68
|
+
this.viewer.scene.rootNodes.forEach(rootNode =>
|
|
69
|
+
this._prepareNodeForExport(rootNode as TransformNode, null, excluded)
|
|
70
|
+
);
|
|
175
71
|
|
|
176
|
-
|
|
72
|
+
// reset transformation of all "TransformNodes", which couldn't be handled by the geometry baking algorithm
|
|
73
|
+
// it's important that this is done AFTER all geometries have been baked
|
|
74
|
+
this.viewer.scene
|
|
75
|
+
.getNodes()
|
|
76
|
+
.filter(node => !!node.metadata?.[GltfExportManager._METADATA_PROPS.exportNode])
|
|
77
|
+
.forEach(node => resetTransformation(node as TransformNode));
|
|
177
78
|
}
|
|
178
79
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
// required by the scene serializer
|
|
185
|
-
await import(/* webpackChunkName: "physicsenginecomponent" */ '@babylonjs/core/Physics/physicsEngineComponent');
|
|
186
|
-
|
|
187
|
-
// copy scene function uses scene serializer, which can't handle "complex" data like classes, function or recursive
|
|
188
|
-
// data structures in metadata
|
|
189
|
-
// it basically does a `JSON.stringify` on the metadata
|
|
190
|
-
// therefore the metadata has to be removed from the nodes before the copy process
|
|
191
|
-
const metadataArr = this.extractMetadataOfAllNodes();
|
|
80
|
+
protected async _exportPostProcess(): Promise<void> {
|
|
81
|
+
// dispose all nodes and material that have only been created for the export
|
|
82
|
+
this.viewer.scene.rootNodes
|
|
83
|
+
.filter(rootNode => rootNode.metadata?.[GltfExportManager._METADATA_PROPS.deleteAfterExport])
|
|
84
|
+
.forEach(rootNode => rootNode.dispose());
|
|
192
85
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
this.restoreMetadataOfAllNodes(metadataArr);
|
|
86
|
+
this.viewer.scene.materials
|
|
87
|
+
.filter(mat => !!mat.metadata?.[GltfExportManager._METADATA_PROPS.deleteAfterExport])
|
|
88
|
+
.forEach(material => material.dispose(false, false));
|
|
89
|
+
}
|
|
198
90
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
91
|
+
protected _gltfExportOptions(): IExportOptions {
|
|
92
|
+
return {
|
|
93
|
+
shouldExportNode: function (node: BjsNode) {
|
|
94
|
+
return !!node.metadata?.[GltfExportManager._METADATA_PROPS.exportNode];
|
|
95
|
+
},
|
|
96
|
+
// keep root node(s)
|
|
97
|
+
// this seems to be important due to the geometry baking and transformation resetting stuff
|
|
98
|
+
// => root node has no transformation anymore, which seems to be problematic for the import
|
|
99
|
+
removeNoopRootNodes: false,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
202
102
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
103
|
+
protected _prepareNodeForExport(
|
|
104
|
+
node: TransformNode,
|
|
105
|
+
clonedParent: Nullable<TransformNode>,
|
|
106
|
+
excluded?: ExcludedGeometryList
|
|
107
|
+
) {
|
|
108
|
+
if (!this._shouldExportNode(node, excluded)) {
|
|
109
|
+
return;
|
|
207
110
|
}
|
|
208
111
|
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
112
|
+
// clone original node and create unique name (via uniqueId)
|
|
113
|
+
const newNodeName = `${node.name}_${node.uniqueId}`;
|
|
114
|
+
const clonedNode =
|
|
115
|
+
node instanceof InstancedMesh
|
|
116
|
+
? createMeshFromInstancedMesh(node, newNodeName)
|
|
117
|
+
: node.clone(newNodeName, clonedParent, true)!;
|
|
118
|
+
|
|
119
|
+
// bake transformation, so that we won't get negative values anymore
|
|
120
|
+
// also exchange material
|
|
121
|
+
if (clonedNode instanceof Mesh) {
|
|
122
|
+
bakeGeometryOfMesh(clonedNode);
|
|
123
|
+
const exchangeWithMaterial =
|
|
124
|
+
clonedNode.material?.metadata?.[GltfExportManager._METADATA_PROPS.exchangeMaterialWith];
|
|
125
|
+
if (exchangeWithMaterial) {
|
|
126
|
+
clonedNode.material = this.viewer.scene.getMaterialByName(exchangeWithMaterial);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
215
129
|
|
|
216
|
-
|
|
217
|
-
|
|
130
|
+
// signalize that this is a cloned node
|
|
131
|
+
injectMetadata(clonedNode!, {
|
|
132
|
+
[GltfExportManager._METADATA_PROPS.exportNode]: true,
|
|
133
|
+
[GltfExportManager._METADATA_PROPS.deleteAfterExport]: true,
|
|
134
|
+
});
|
|
218
135
|
|
|
219
|
-
|
|
136
|
+
// recourse into children
|
|
137
|
+
const childs = node.getChildTransformNodes(true);
|
|
138
|
+
childs.forEach(child => this._prepareNodeForExport(child, clonedNode, excluded));
|
|
220
139
|
}
|
|
221
140
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (
|
|
227
|
-
|
|
228
|
-
|
|
141
|
+
protected _shouldExportNode(node: BjsNode, excluded?: ExcludedGeometryList): boolean {
|
|
142
|
+
if (!(node instanceof TransformNode)) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
if (node.name === Viewer.BOUNDING_BOX_NAME) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
if (!node.isEnabled()) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
if (excluded && isNodeIncludedInExclusionList(node, excluded)) {
|
|
152
|
+
return false;
|
|
229
153
|
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Remove all node metadata and return them in a map
|
|
234
|
-
*/
|
|
235
|
-
protected extractMetadataOfAllNodes(): MetadataMap {
|
|
236
|
-
return this.viewer.scene.getNodes().reduce((accMetadataObj, curNode) => {
|
|
237
|
-
const metadata = curNode.metadata;
|
|
238
|
-
delete curNode.metadata;
|
|
239
|
-
return { ...accMetadataObj, [curNode.uniqueId.toString()]: metadata };
|
|
240
|
-
}, {});
|
|
241
|
-
}
|
|
242
154
|
|
|
243
|
-
|
|
244
|
-
* Restore node metadata from previously cropped metadata map
|
|
245
|
-
*/
|
|
246
|
-
protected restoreMetadataOfAllNodes(metadataMap: MetadataMap) {
|
|
247
|
-
this.viewer.scene.getNodes().forEach(node => {
|
|
248
|
-
const metadata = metadataMap[node.uniqueId.toString()];
|
|
249
|
-
if (metadata) {
|
|
250
|
-
node.metadata = metadata;
|
|
251
|
-
}
|
|
252
|
-
});
|
|
155
|
+
return true;
|
|
253
156
|
}
|
|
254
157
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
if ((n.material as PBRMaterial).transparencyMode !== PBRMaterial.PBRMATERIAL_OPAQUE) continue;
|
|
265
|
-
// if we're here, we have a node holding a material with set refraction whose transparencyMode is set to
|
|
266
|
-
// PBRMATERIAL_OPAQUE
|
|
267
|
-
n.material = this.createRefractionMaterialReplacement(n.material);
|
|
158
|
+
protected _shouldReplaceMaterial(material: Material): boolean {
|
|
159
|
+
if (!(material instanceof PBRMaterial)) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
if (!material.subSurface.isRefractionEnabled) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
if (material.transparencyMode !== PBRMaterial.PBRMATERIAL_OPAQUE) {
|
|
166
|
+
return false;
|
|
268
167
|
}
|
|
168
|
+
|
|
169
|
+
return true;
|
|
269
170
|
}
|
|
270
171
|
|
|
271
172
|
/**
|
|
272
173
|
* Create an export-friendly replacement material for a material using refraction.
|
|
273
|
-
* @param mat Material to be replaced
|
|
274
174
|
*/
|
|
275
|
-
protected
|
|
276
|
-
|
|
277
|
-
if (this.isMaterialClonedForExport(mat)) return mat;
|
|
278
|
-
|
|
175
|
+
protected _createRefractionMaterialReplacement(material: PBRMaterial): PBRMaterial {
|
|
176
|
+
const newName = `${material.name}_clone`;
|
|
279
177
|
// change material according to https://www.notion.so/combeenation/Glas-materials-don-t-look-glasy-after-export-d5fda2c6515e4420a8772744d3e6b460
|
|
280
|
-
const clonedMaterial =
|
|
281
|
-
clonedMaterial.metadata = { ...mat.metadata, [GltfExportManager._CLONED_FROM_MAT_METADATA_PROPERTY]: mat.uniqueId }; // create shallow copy of metadata on clone. see https://forum.babylonjs.com/t/the-metadata-of-the-mesh-cloned-by-the-instantiatemodelstoscene-method-is-a-shallow-copy/21563
|
|
178
|
+
const clonedMaterial = material.clone(newName); // clone material
|
|
282
179
|
clonedMaterial.refractionTexture = null;
|
|
283
180
|
clonedMaterial.metallicReflectanceTexture = null; // is this the correct one for metallic roughness?
|
|
284
181
|
clonedMaterial.alpha = 0.7;
|
|
@@ -287,26 +184,9 @@ export class GltfExportManager {
|
|
|
287
184
|
clonedMaterial.metallic = 0.65;
|
|
288
185
|
clonedMaterial.roughness = 0.15;
|
|
289
186
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Inspect if a material was temporarily cloned for GLB export
|
|
295
|
-
* @param mat Material to be inspected
|
|
296
|
-
*/
|
|
297
|
-
protected isMaterialClonedForExport(mat: PBRMaterial): boolean {
|
|
298
|
-
return has(mat.metadata, GltfExportManager._CLONED_FROM_MAT_METADATA_PROPERTY);
|
|
299
|
-
}
|
|
187
|
+
injectMetadata(material, { [GltfExportManager._METADATA_PROPS.exchangeMaterialWith]: newName });
|
|
188
|
+
injectMetadata(clonedMaterial, { [GltfExportManager._METADATA_PROPS.deleteAfterExport]: true });
|
|
300
189
|
|
|
301
|
-
|
|
302
|
-
* Set unique mesh names for GLB export.
|
|
303
|
-
* Duplicate names lead to problems in AR on iOS devices.
|
|
304
|
-
*/
|
|
305
|
-
protected setUniqueMeshNames(scene: Scene): void {
|
|
306
|
-
const allNodes = scene.getNodes().filter(node => node instanceof TransformNode) as TransformNode[];
|
|
307
|
-
allNodes.forEach(currMesh => {
|
|
308
|
-
injectMetadata(currMesh, { [GltfExportManager.NAME_BEFORE_EXPORT_METADATA_PROPERTY]: currMesh.name }, false);
|
|
309
|
-
currMesh.name = `${currMesh.name}_${currMesh.uniqueId}`;
|
|
310
|
-
});
|
|
190
|
+
return clonedMaterial;
|
|
311
191
|
}
|
|
312
192
|
}
|
|
@@ -1,45 +1,16 @@
|
|
|
1
|
-
import { MorphTargetManager } from '@babylonjs/core';
|
|
2
1
|
import { VertexBuffer } from '@babylonjs/core/Buffers/buffer';
|
|
3
2
|
import { Vector3 } from '@babylonjs/core/Maths/math.vector';
|
|
4
|
-
import type
|
|
3
|
+
import { type Geometry } from '@babylonjs/core/Meshes';
|
|
5
4
|
import { InstancedMesh } from '@babylonjs/core/Meshes/instancedMesh';
|
|
6
5
|
import { Mesh } from '@babylonjs/core/Meshes/mesh';
|
|
7
6
|
import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
|
|
8
7
|
import type { MorphTarget } from '@babylonjs/core/Morph';
|
|
8
|
+
import { MorphTargetManager } from '@babylonjs/core/Morph/morphTargetManager';
|
|
9
9
|
import type { FloatArray } from '@babylonjs/core/types';
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
* Removes morph targets and sets transformation values back to their default values.
|
|
13
|
-
* The effects from morph targets and tranformation values will be "baked" in the geometry, so that the final appearance
|
|
14
|
-
* of the meshes stay the same.
|
|
15
|
-
*/
|
|
16
|
-
const bakeGeometryOfAllMeshes = function (scene: Scene) {
|
|
17
|
-
// instanced meshes have to be converted, since they share the geometry of the source mesh, which would lead to issues
|
|
18
|
-
// when baking the transformation values
|
|
19
|
-
scene.meshes.forEach(mesh => {
|
|
20
|
-
if (mesh instanceof InstancedMesh) {
|
|
21
|
-
convertInstancedMeshToMesh(mesh);
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// do the geometry baking for all meshes in the scene
|
|
26
|
-
scene.meshes.forEach(mesh => {
|
|
27
|
-
if (mesh instanceof Mesh) {
|
|
28
|
-
bakeGeometryOfMesh(mesh);
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
// some nodes (mostly transform nodes), are not affected by the baking algorithm since they have no geometry
|
|
33
|
-
// => reset their transformation manually
|
|
34
|
-
const allNodes = scene.getNodes().filter(node => node instanceof TransformNode) as TransformNode[];
|
|
35
|
-
allNodes.forEach(node => {
|
|
36
|
-
resetTransformation(node);
|
|
37
|
-
});
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const convertInstancedMeshToMesh = function (instancedMesh: InstancedMesh) {
|
|
11
|
+
const createMeshFromInstancedMesh = function (instancedMesh: InstancedMesh, newName: string) {
|
|
41
12
|
// first create a clone of the source mesh
|
|
42
|
-
const newMesh = instancedMesh.sourceMesh.clone(
|
|
13
|
+
const newMesh = instancedMesh.sourceMesh.clone(newName, instancedMesh.parent);
|
|
43
14
|
// apply the transformation data
|
|
44
15
|
newMesh.position = instancedMesh.position;
|
|
45
16
|
newMesh.rotation = instancedMesh.rotation;
|
|
@@ -48,10 +19,7 @@ const convertInstancedMeshToMesh = function (instancedMesh: InstancedMesh) {
|
|
|
48
19
|
// also sync the enabled state from the original instanced mesh
|
|
49
20
|
newMesh.setEnabled(instancedMesh.isEnabled(false));
|
|
50
21
|
|
|
51
|
-
|
|
52
|
-
instancedMesh.getChildren(undefined).forEach(childNode => (childNode.parent = newMesh));
|
|
53
|
-
// finally delete the original instanced mesh
|
|
54
|
-
instancedMesh.dispose();
|
|
22
|
+
return newMesh;
|
|
55
23
|
};
|
|
56
24
|
|
|
57
25
|
const bakeGeometryOfMesh = function (mesh: Mesh) {
|
|
@@ -69,10 +37,10 @@ const bakeGeometryOfMesh = function (mesh: Mesh) {
|
|
|
69
37
|
if (morphTargetManager?.numTargets) {
|
|
70
38
|
// apply morph target vertices data to mesh geometry
|
|
71
39
|
// mostly only the "PositionKind" is implemented
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
40
|
+
_bakeMorphTargetManagerIntoVertices(VertexBuffer.PositionKind, morphTargetManager, geometry);
|
|
41
|
+
_bakeMorphTargetManagerIntoVertices(VertexBuffer.NormalKind, morphTargetManager, geometry);
|
|
42
|
+
_bakeMorphTargetManagerIntoVertices(VertexBuffer.TangentKind, morphTargetManager, geometry);
|
|
43
|
+
_bakeMorphTargetManagerIntoVertices(VertexBuffer.UVKind, morphTargetManager, geometry);
|
|
76
44
|
|
|
77
45
|
// remove morph target manager with all it's morph targets
|
|
78
46
|
mesh.morphTargetManager = null;
|
|
@@ -86,7 +54,7 @@ const bakeGeometryOfMesh = function (mesh: Mesh) {
|
|
|
86
54
|
* @param kind morph targets can affect various vertices kinds, whereas "position" is the most common one
|
|
87
55
|
* still other kinds (like normals or tangents) can be affected as well and can be provided on this input
|
|
88
56
|
*/
|
|
89
|
-
const
|
|
57
|
+
const _bakeMorphTargetManagerIntoVertices = function (
|
|
90
58
|
kind: string,
|
|
91
59
|
morphTargetManager: MorphTargetManager,
|
|
92
60
|
geometry: Geometry
|
|
@@ -100,7 +68,7 @@ const bakeMorphTargetManagerIntoVertices = function (
|
|
|
100
68
|
let verticesData = [...origVerticesData];
|
|
101
69
|
for (let i = 0; i < morphTargetManager.numTargets; i++) {
|
|
102
70
|
const target = morphTargetManager.getTarget(i);
|
|
103
|
-
const targetVerticesData =
|
|
71
|
+
const targetVerticesData = _getVerticesDataFromMorphTarget(kind, target);
|
|
104
72
|
if (targetVerticesData) {
|
|
105
73
|
// vertices data of this kind are implemented on the morph target
|
|
106
74
|
// add the influence of this morph target to the vertices data
|
|
@@ -115,7 +83,7 @@ const bakeMorphTargetManagerIntoVertices = function (
|
|
|
115
83
|
geometry.setVerticesData(kind, verticesData);
|
|
116
84
|
};
|
|
117
85
|
|
|
118
|
-
const
|
|
86
|
+
const _getVerticesDataFromMorphTarget = function (kind: string, morphTarget: MorphTarget): Nullable<FloatArray> {
|
|
119
87
|
switch (kind) {
|
|
120
88
|
case VertexBuffer.PositionKind:
|
|
121
89
|
return morphTarget.getPositions();
|
|
@@ -137,4 +105,4 @@ const resetTransformation = function (node: TransformNode) {
|
|
|
137
105
|
node.scaling = new Vector3(1, 1, 1);
|
|
138
106
|
};
|
|
139
107
|
|
|
140
|
-
export {
|
|
108
|
+
export { createMeshFromInstancedMesh, bakeGeometryOfMesh, resetTransformation };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Element } from '../classes/element';
|
|
2
2
|
import { Variant } from '../classes/variant';
|
|
3
3
|
import { VariantInstance } from '../classes/variantInstance';
|
|
4
|
-
import { GltfExportManager } from '../manager/gltfExportManager';
|
|
5
4
|
import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
|
|
6
5
|
import { Tags } from '@babylonjs/core/Misc/tags';
|
|
7
6
|
|
|
@@ -13,10 +12,7 @@ import { Tags } from '@babylonjs/core/Misc/tags';
|
|
|
13
12
|
*/
|
|
14
13
|
const isNodeIncludedInExclusionList = function (node: TransformNode, list: ExcludedGeometryList): boolean {
|
|
15
14
|
const checkNode = (inputNode: TransformNode, nodeToCheck: TransformNode) => {
|
|
16
|
-
|
|
17
|
-
// GLB export
|
|
18
|
-
const nodeName = nodeToCheck.metadata?.[GltfExportManager.NAME_BEFORE_EXPORT_METADATA_PROPERTY] ?? nodeToCheck.name;
|
|
19
|
-
return inputNode.name === nodeName;
|
|
15
|
+
return inputNode.name === nodeToCheck.name;
|
|
20
16
|
};
|
|
21
17
|
const checkElement = (inputEl: Element, nodeToCheck: TransformNode) => {
|
|
22
18
|
return inputEl.nodesFlat.some(m => checkNode(m, nodeToCheck));
|
|
@@ -28,8 +24,7 @@ const isNodeIncludedInExclusionList = function (node: TransformNode, list: Exclu
|
|
|
28
24
|
return inputVarInst.variant.elements.some(el => checkElement(el, nodeToCheck));
|
|
29
25
|
};
|
|
30
26
|
const checkTagManagerSubject = (inputSubject: TagManagerSubject, nodeToCheck: TransformNode) => {
|
|
31
|
-
const
|
|
32
|
-
const nameMatches = inputSubject.nodeName && inputSubject.nodeName === nodeName;
|
|
27
|
+
const nameMatches = inputSubject.nodeName && inputSubject.nodeName === nodeToCheck.name;
|
|
33
28
|
const tagMatches = inputSubject.tagName && Tags.MatchesQuery(nodeToCheck, inputSubject.tagName);
|
|
34
29
|
return nameMatches || tagMatches;
|
|
35
30
|
};
|