@combeenation/3d-viewer 11.0.0 → 12.0.0-alpha2
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 +114 -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 +23 -49
- 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 +129 -245
- package/src/api/util/geometryHelper.ts +28 -50
- 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,146 @@ 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
|
-
|
|
48
|
+
|
|
49
|
+
this._exportPostProcess();
|
|
93
50
|
}
|
|
94
51
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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));
|
|
52
|
+
public async exportGlbToFile(filename: string, excluded?: ExcludedGeometryList) {
|
|
53
|
+
await this._exportPreProcess(excluded);
|
|
54
|
+
|
|
55
|
+
const glb = await GLTF2Export.GLBAsync(this.viewer.scene, filename, this._gltfExportOptions());
|
|
116
56
|
glb.downloadFiles();
|
|
117
|
-
this.exportPostProcess(scene, sceneCopied);
|
|
118
|
-
}
|
|
119
57
|
|
|
120
|
-
|
|
121
|
-
* Gets predefined {@link IExportOptions } merged with given ones.
|
|
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);
|
|
58
|
+
await this._exportPostProcess();
|
|
139
59
|
}
|
|
140
60
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
//
|
|
158
|
-
|
|
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));
|
|
66
|
+
|
|
67
|
+
// handle nodes
|
|
68
|
+
this.viewer.scene.rootNodes.forEach(rootNode => this._prepareNodeForExport(rootNode, null, excluded));
|
|
69
|
+
|
|
70
|
+
// bake transformation of all meshes, so that no negative scalings are left
|
|
71
|
+
// it's important that this is done AFTER instanced meshes have been converted (_prepareNodeForExport)
|
|
72
|
+
this.viewer.scene.meshes
|
|
73
|
+
.filter(mesh => !!mesh.metadata?.[GltfExportManager._METADATA_PROPS.exportNode])
|
|
74
|
+
.forEach(mesh => bakeGeometryOfMesh(mesh as Mesh));
|
|
75
|
+
|
|
76
|
+
// reset transformation of all "TransformNodes", which couldn't be handled by the geometry baking algorithm
|
|
77
|
+
// it's important that this is done AFTER all geometries have been baked
|
|
78
|
+
this.viewer.scene.transformNodes
|
|
79
|
+
.filter(node => !!node.metadata?.[GltfExportManager._METADATA_PROPS.exportNode])
|
|
80
|
+
.forEach(node => resetTransformation(node as TransformNode));
|
|
81
|
+
}
|
|
159
82
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
83
|
+
protected async _exportPostProcess(): Promise<void> {
|
|
84
|
+
// dispose all nodes and materials that have only been created for the export
|
|
85
|
+
this.viewer.scene.rootNodes
|
|
86
|
+
.filter(rootNode => rootNode.metadata?.[GltfExportManager._METADATA_PROPS.deleteAfterExport])
|
|
87
|
+
.forEach(rootNode => rootNode.dispose());
|
|
163
88
|
|
|
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
|
-
}
|
|
89
|
+
this.viewer.scene.materials
|
|
90
|
+
.filter(mat => !!mat.metadata?.[GltfExportManager._METADATA_PROPS.deleteAfterExport])
|
|
91
|
+
.forEach(material => material.dispose(false, false));
|
|
92
|
+
}
|
|
175
93
|
|
|
176
|
-
|
|
94
|
+
protected _gltfExportOptions(): IExportOptions {
|
|
95
|
+
return {
|
|
96
|
+
shouldExportNode: function (node: BjsNode) {
|
|
97
|
+
return !!node.metadata?.[GltfExportManager._METADATA_PROPS.exportNode];
|
|
98
|
+
},
|
|
99
|
+
// keep root node(s)
|
|
100
|
+
// this seems to be important due to the geometry baking and transformation resetting stuff
|
|
101
|
+
// => root node would have no transformation anymore, which seems to be problematic for the import
|
|
102
|
+
removeNoopRootNodes: false,
|
|
103
|
+
};
|
|
177
104
|
}
|
|
178
105
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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();
|
|
192
|
-
|
|
193
|
-
// serialize the scene
|
|
194
|
-
const serializedScene = await SceneSerializer.SerializeAsync(this.viewer.scene);
|
|
195
|
-
|
|
196
|
-
// set the cropped metadata back to the viewer scene nodes
|
|
197
|
-
this.restoreMetadataOfAllNodes(metadataArr);
|
|
198
|
-
|
|
199
|
-
// load the scene into an invisible "dummy" canvas
|
|
200
|
-
const dummyCanvas = document.createElement('canvas');
|
|
201
|
-
const engine = new Engine(dummyCanvas);
|
|
202
|
-
|
|
203
|
-
if (limitTextureSize) {
|
|
204
|
-
// according to some tests iOS devices break when exporting a scene with textures above 1024
|
|
205
|
-
// this only affects the scene clone for the export
|
|
206
|
-
engine.getCaps().maxTextureSize = 1024;
|
|
106
|
+
protected _prepareNodeForExport(
|
|
107
|
+
node: BjsNode,
|
|
108
|
+
clonedParent: Nullable<TransformNode>,
|
|
109
|
+
excluded?: ExcludedGeometryList
|
|
110
|
+
) {
|
|
111
|
+
if (!this._shouldExportNode(node, excluded)) {
|
|
112
|
+
return;
|
|
207
113
|
}
|
|
208
114
|
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
115
|
+
// from here on we only have TransformNodes
|
|
116
|
+
const transformNode = node as TransformNode;
|
|
117
|
+
|
|
118
|
+
// clone original node and create unique name (via uniqueId)
|
|
119
|
+
const newNodeName = `${transformNode.name}_${transformNode.uniqueId}`;
|
|
120
|
+
const clonedNode =
|
|
121
|
+
transformNode instanceof InstancedMesh
|
|
122
|
+
? createMeshFromInstancedMesh(transformNode, newNodeName, clonedParent)
|
|
123
|
+
: transformNode.clone(newNodeName, clonedParent, true)!;
|
|
124
|
+
|
|
125
|
+
// exchange material
|
|
126
|
+
if (clonedNode instanceof Mesh) {
|
|
127
|
+
const exchangeWithMaterial =
|
|
128
|
+
clonedNode.material?.metadata?.[GltfExportManager._METADATA_PROPS.exchangeMaterialWith];
|
|
129
|
+
if (exchangeWithMaterial) {
|
|
130
|
+
clonedNode.material = this.viewer.scene.getMaterialByName(exchangeWithMaterial);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
215
133
|
|
|
216
|
-
|
|
217
|
-
|
|
134
|
+
// signalize that this is a cloned node
|
|
135
|
+
injectMetadata(clonedNode!, {
|
|
136
|
+
[GltfExportManager._METADATA_PROPS.exportNode]: true,
|
|
137
|
+
[GltfExportManager._METADATA_PROPS.deleteAfterExport]: true,
|
|
138
|
+
});
|
|
218
139
|
|
|
219
|
-
|
|
140
|
+
// handle children
|
|
141
|
+
const childs = transformNode.getChildTransformNodes(true);
|
|
142
|
+
childs.forEach(child => this._prepareNodeForExport(child, clonedNode, excluded));
|
|
220
143
|
}
|
|
221
144
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (
|
|
227
|
-
|
|
228
|
-
|
|
145
|
+
protected _shouldExportNode(node: BjsNode, excluded?: ExcludedGeometryList): boolean {
|
|
146
|
+
if (!(node instanceof TransformNode)) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
if (node.name === Viewer.BOUNDING_BOX_NAME) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
if (!node.isEnabled()) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
if (excluded && isNodeIncludedInExclusionList(node, excluded)) {
|
|
156
|
+
return false;
|
|
229
157
|
}
|
|
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
158
|
|
|
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
|
-
});
|
|
159
|
+
return true;
|
|
253
160
|
}
|
|
254
161
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (!(n instanceof AbstractMesh)) continue;
|
|
262
|
-
if (!(n.material instanceof PBRMaterial)) continue;
|
|
263
|
-
if (!(n.material as PBRMaterial).subSurface.isRefractionEnabled) continue;
|
|
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);
|
|
162
|
+
protected _shouldReplaceMaterial(material: Material): boolean {
|
|
163
|
+
if (!(material instanceof PBRMaterial)) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
if (!material.subSurface.isRefractionEnabled) {
|
|
167
|
+
return false;
|
|
268
168
|
}
|
|
169
|
+
if (material.transparencyMode !== PBRMaterial.PBRMATERIAL_OPAQUE) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return true;
|
|
269
174
|
}
|
|
270
175
|
|
|
271
176
|
/**
|
|
272
177
|
* Create an export-friendly replacement material for a material using refraction.
|
|
273
|
-
* @param mat Material to be replaced
|
|
274
178
|
*/
|
|
275
|
-
protected
|
|
276
|
-
|
|
277
|
-
if (this.isMaterialClonedForExport(mat)) return mat;
|
|
278
|
-
|
|
179
|
+
protected _createRefractionMaterialReplacement(material: PBRMaterial): PBRMaterial {
|
|
180
|
+
const newName = `${material.name}_clone`;
|
|
279
181
|
// 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
|
|
182
|
+
const clonedMaterial = material.clone(newName); // clone material
|
|
282
183
|
clonedMaterial.refractionTexture = null;
|
|
283
184
|
clonedMaterial.metallicReflectanceTexture = null; // is this the correct one for metallic roughness?
|
|
284
185
|
clonedMaterial.alpha = 0.7;
|
|
@@ -287,26 +188,9 @@ export class GltfExportManager {
|
|
|
287
188
|
clonedMaterial.metallic = 0.65;
|
|
288
189
|
clonedMaterial.roughness = 0.15;
|
|
289
190
|
|
|
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
|
-
}
|
|
191
|
+
injectMetadata(material, { [GltfExportManager._METADATA_PROPS.exchangeMaterialWith]: newName });
|
|
192
|
+
injectMetadata(clonedMaterial, { [GltfExportManager._METADATA_PROPS.deleteAfterExport]: true });
|
|
300
193
|
|
|
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
|
-
});
|
|
194
|
+
return clonedMaterial;
|
|
311
195
|
}
|
|
312
196
|
}
|
|
@@ -1,57 +1,35 @@
|
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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 (
|
|
12
|
+
instancedMesh: InstancedMesh,
|
|
13
|
+
newName: string,
|
|
14
|
+
clonedParent: Nullable<TransformNode>
|
|
15
|
+
) {
|
|
41
16
|
// first create a clone of the source mesh
|
|
42
|
-
const newMesh = instancedMesh.sourceMesh.clone(
|
|
43
|
-
// apply the transformation data
|
|
44
|
-
|
|
45
|
-
newMesh.
|
|
46
|
-
newMesh.
|
|
47
|
-
newMesh.scaling = instancedMesh.scaling;
|
|
17
|
+
const newMesh = instancedMesh.sourceMesh.clone(newName, clonedParent, true);
|
|
18
|
+
// apply the transformation data, it's important to create clones of the transformations to not touch the original
|
|
19
|
+
// transformation when applying changes (eg: geometry baking)
|
|
20
|
+
newMesh.position = instancedMesh.position.clone();
|
|
21
|
+
newMesh.rotation = instancedMesh.rotation.clone();
|
|
22
|
+
newMesh.scaling = instancedMesh.scaling.clone();
|
|
23
|
+
|
|
24
|
+
// rotation quaternion is optional
|
|
25
|
+
if (instancedMesh.rotationQuaternion) {
|
|
26
|
+
newMesh.rotationQuaternion = instancedMesh.rotationQuaternion.clone();
|
|
27
|
+
}
|
|
28
|
+
|
|
48
29
|
// also sync the enabled state from the original instanced mesh
|
|
49
30
|
newMesh.setEnabled(instancedMesh.isEnabled(false));
|
|
50
31
|
|
|
51
|
-
|
|
52
|
-
instancedMesh.getChildren(undefined).forEach(childNode => (childNode.parent = newMesh));
|
|
53
|
-
// finally delete the original instanced mesh
|
|
54
|
-
instancedMesh.dispose();
|
|
32
|
+
return newMesh;
|
|
55
33
|
};
|
|
56
34
|
|
|
57
35
|
const bakeGeometryOfMesh = function (mesh: Mesh) {
|
|
@@ -69,10 +47,10 @@ const bakeGeometryOfMesh = function (mesh: Mesh) {
|
|
|
69
47
|
if (morphTargetManager?.numTargets) {
|
|
70
48
|
// apply morph target vertices data to mesh geometry
|
|
71
49
|
// mostly only the "PositionKind" is implemented
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
50
|
+
_bakeMorphTargetManagerIntoVertices(VertexBuffer.PositionKind, morphTargetManager, geometry);
|
|
51
|
+
_bakeMorphTargetManagerIntoVertices(VertexBuffer.NormalKind, morphTargetManager, geometry);
|
|
52
|
+
_bakeMorphTargetManagerIntoVertices(VertexBuffer.TangentKind, morphTargetManager, geometry);
|
|
53
|
+
_bakeMorphTargetManagerIntoVertices(VertexBuffer.UVKind, morphTargetManager, geometry);
|
|
76
54
|
|
|
77
55
|
// remove morph target manager with all it's morph targets
|
|
78
56
|
mesh.morphTargetManager = null;
|
|
@@ -86,7 +64,7 @@ const bakeGeometryOfMesh = function (mesh: Mesh) {
|
|
|
86
64
|
* @param kind morph targets can affect various vertices kinds, whereas "position" is the most common one
|
|
87
65
|
* still other kinds (like normals or tangents) can be affected as well and can be provided on this input
|
|
88
66
|
*/
|
|
89
|
-
const
|
|
67
|
+
const _bakeMorphTargetManagerIntoVertices = function (
|
|
90
68
|
kind: string,
|
|
91
69
|
morphTargetManager: MorphTargetManager,
|
|
92
70
|
geometry: Geometry
|
|
@@ -100,7 +78,7 @@ const bakeMorphTargetManagerIntoVertices = function (
|
|
|
100
78
|
let verticesData = [...origVerticesData];
|
|
101
79
|
for (let i = 0; i < morphTargetManager.numTargets; i++) {
|
|
102
80
|
const target = morphTargetManager.getTarget(i);
|
|
103
|
-
const targetVerticesData =
|
|
81
|
+
const targetVerticesData = _getVerticesDataFromMorphTarget(kind, target);
|
|
104
82
|
if (targetVerticesData) {
|
|
105
83
|
// vertices data of this kind are implemented on the morph target
|
|
106
84
|
// add the influence of this morph target to the vertices data
|
|
@@ -115,7 +93,7 @@ const bakeMorphTargetManagerIntoVertices = function (
|
|
|
115
93
|
geometry.setVerticesData(kind, verticesData);
|
|
116
94
|
};
|
|
117
95
|
|
|
118
|
-
const
|
|
96
|
+
const _getVerticesDataFromMorphTarget = function (kind: string, morphTarget: MorphTarget): Nullable<FloatArray> {
|
|
119
97
|
switch (kind) {
|
|
120
98
|
case VertexBuffer.PositionKind:
|
|
121
99
|
return morphTarget.getPositions();
|
|
@@ -137,4 +115,4 @@ const resetTransformation = function (node: TransformNode) {
|
|
|
137
115
|
node.scaling = new Vector3(1, 1, 1);
|
|
138
116
|
};
|
|
139
117
|
|
|
140
|
-
export {
|
|
118
|
+
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
|
};
|