@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@combeenation/3d-viewer",
3
- "version": "11.0.0",
3
+ "version": "12.0.0-alpha2",
4
4
  "description": "Combeenation 3D Viewer",
5
5
  "homepage": "https://github.com/Combeenation/3d-viewer#readme",
6
6
  "bugs": {
@@ -1,58 +1,31 @@
1
- import { bakeGeometryOfAllMeshes } from '..//util/geometryHelper';
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 { Engine } from '@babylonjs/core/Engines/engine';
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 { SceneSerializer } from '@babylonjs/core/Misc/sceneSerializer';
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 _CLONED_FROM_MAT_METADATA_PROPERTY = 'clonedFrom';
20
- public static readonly NAME_BEFORE_EXPORT_METADATA_PROPERTY = 'nameBeforeExport';
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
- * Exports selected nodes to a file.
36
- * @param filename optional name of the exported .GLB file.
37
- * @param exportOptions export options to be merged with default options.\
38
- * `exportOptions.exchangeRefractionMaterials` and `exportOptions.limitTextureSize`
39
- * default to `true` if not given.
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
- * Exports selected nodes to GLTF. This may result in more than one file, since textures are exported separately.
72
- * @param filename name of the main (text-based) .GLTF file referring to separate texture files.
73
- * @param exportOptions export options to be merged with default options.\
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);
48
+
49
+ this._exportPostProcess();
93
50
  }
94
51
 
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));
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
- * Stuff to be done before exporting to GLTF
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
- }
156
-
157
- // copy the scene for the GLB export improvements, so that the original viewer scene remains untouched
158
- const copiedScene = await this.copyViewerScene(limitTextureSize);
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
- if (exchangeRefractionMaterials) {
161
- this.exchangeRefractionMaterials(copiedScene);
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
- if (optimizeForAR) {
165
- this.setUniqueMeshNames(copiedScene);
166
- // get rid of negative scalings and morph targets, since iOS can't handle it in the QuickLook app
167
- bakeGeometryOfAllMeshes(copiedScene);
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
- return { scene: copiedScene, sceneCopied: true };
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
- * Create a copy of the viewer scene, which can be further processed and optimized for the GLB export.
181
- * There is no "scene.clone" function, therefore the scene is serialized and loaded again to achieve this result.
182
- */
183
- protected async copyViewerScene(limitTextureSize: boolean): Promise<Scene> {
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();
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
- // overwritting _LastCreatedScene to fix issue: https://combeenation.youtrack.cloud/issue/CB-8972
210
- // In short: dummy scene erroneously interacts with original viewer scene
211
- // setting viewer scene as fallback again (`_LastCreatedScene`) resolves that issue
212
- const curLastScene = EngineStore._LastCreatedScene;
213
- const newScene = new Scene(engine);
214
- EngineStore._LastCreatedScene = curLastScene;
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
- SceneLoader.ShowLoadingScreen = false;
217
- await SceneLoader.AppendAsync('', 'data:' + JSON.stringify(serializedScene), newScene, undefined, '.babylon');
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
- return newScene;
140
+ // handle children
141
+ const childs = transformNode.getChildTransformNodes(true);
142
+ childs.forEach(child => this._prepareNodeForExport(child, clonedNode, excluded));
220
143
  }
221
144
 
222
- /**
223
- * Stuff to be done after the GLTF export
224
- */
225
- protected exportPostProcess(scene: Scene, disposeScene: boolean) {
226
- if (disposeScene) {
227
- // copied scene for GLB export is not needed anymore, therefore it can be removed
228
- scene.getEngine().dispose();
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
- * Materials with refraction set are not exported properly.
257
- * Exchange all such (relevant) materials with a more export-friendly version
258
- */
259
- protected exchangeRefractionMaterials(scene: Scene) {
260
- for (const n of scene.getNodes()) {
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 createRefractionMaterialReplacement(mat: PBRMaterial): PBRMaterial {
276
- // if we're dealing with a clone already, return it instead of cloning
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 = mat.clone(mat.name); // clone material. clone uses same name
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
- return clonedMaterial;
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 { Geometry } from '@babylonjs/core/Meshes';
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 (
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(`${instancedMesh.name}`, instancedMesh.parent);
43
- // apply the transformation data
44
- newMesh.position = instancedMesh.position;
45
- newMesh.rotation = instancedMesh.rotation;
46
- newMesh.rotationQuaternion = instancedMesh.rotationQuaternion;
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
- // re-assign children of the original mesh, since that one won't be used anymore
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
- bakeMorphTargetManagerIntoVertices(VertexBuffer.PositionKind, morphTargetManager, geometry);
73
- bakeMorphTargetManagerIntoVertices(VertexBuffer.NormalKind, morphTargetManager, geometry);
74
- bakeMorphTargetManagerIntoVertices(VertexBuffer.TangentKind, morphTargetManager, geometry);
75
- bakeMorphTargetManagerIntoVertices(VertexBuffer.UVKind, morphTargetManager, geometry);
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 bakeMorphTargetManagerIntoVertices = function (
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 = getVerticesDataFromMorphTarget(kind, target);
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 getVerticesDataFromMorphTarget = function (kind: string, morphTarget: MorphTarget): Nullable<FloatArray> {
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 { bakeGeometryOfAllMeshes };
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
- // check name instead of unique id, since the unique id changes when copying the scene, which is the case in the
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 nodeName = nodeToCheck.metadata?.[GltfExportManager.NAME_BEFORE_EXPORT_METADATA_PROPERTY] ?? nodeToCheck.name;
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
  };