@onerjs/serializers 8.28.7 → 8.28.8
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.
|
@@ -0,0 +1,1198 @@
|
|
|
1
|
+
import { TmpVectors, Quaternion } from "@onerjs/core/Maths/math.vector.js";
|
|
2
|
+
import { Tools } from "@onerjs/core/Misc/tools.js";
|
|
3
|
+
import { VertexBuffer } from "@onerjs/core/Buffers/buffer.js";
|
|
4
|
+
import { TransformNode } from "@onerjs/core/Meshes/transformNode.js";
|
|
5
|
+
import { AbstractMesh } from "@onerjs/core/Meshes/abstractMesh.js";
|
|
6
|
+
import { InstancedMesh } from "@onerjs/core/Meshes/instancedMesh.js";
|
|
7
|
+
import { Material } from "@onerjs/core/Materials/material.js";
|
|
8
|
+
import { Engine } from "@onerjs/core/Engines/engine.js";
|
|
9
|
+
import { EngineStore } from "@onerjs/core/Engines/engineStore.js";
|
|
10
|
+
import { GLTFMaterialExporter } from "./glTFMaterialExporter.js";
|
|
11
|
+
import { GLTFData } from "./glTFData.js";
|
|
12
|
+
import { ConvertToRightHandedPosition, ConvertToRightHandedRotation, DataArrayToUint8Array, GetAccessorType, GetAttributeType, GetMinMax, GetPrimitiveMode, IsTriangleFillMode, IsChildCollapsible, FloatsNeed16BitInteger, IsStandardVertexAttribute, IndicesArrayToTypedSubarray, GetVertexBufferInfo, CollapseChildIntoParent, Rotate180Y, DefaultTranslation, DefaultScale, DefaultRotation, ConvertToRightHandedTransformMatrix, } from "./glTFUtilities.js";
|
|
13
|
+
import { IsNoopNode } from "../../exportUtils.js";
|
|
14
|
+
import { BufferManager } from "./bufferManager.js";
|
|
15
|
+
import { Camera } from "@onerjs/core/Cameras/camera.js";
|
|
16
|
+
import { MultiMaterial } from "@onerjs/core/Materials/multiMaterial.js";
|
|
17
|
+
import { PBRBaseMaterial } from "@onerjs/core/Materials/PBR/pbrBaseMaterial.js";
|
|
18
|
+
import { StandardMaterial } from "@onerjs/core/Materials/standardMaterial.js";
|
|
19
|
+
import { Logger } from "@onerjs/core/Misc/logger.js";
|
|
20
|
+
import { EnumerateFloatValues, AreIndices32Bits } from "@onerjs/core/Buffers/bufferUtils.js";
|
|
21
|
+
import { _GLTFAnimation } from "./glTFAnimation.js";
|
|
22
|
+
import { BuildMorphTargetBuffers } from "./glTFMorphTargetsUtilities.js";
|
|
23
|
+
import { LinesMesh } from "@onerjs/core/Meshes/linesMesh.js";
|
|
24
|
+
import { GreasedLineBaseMesh } from "@onerjs/core/Meshes/GreasedLine/greasedLineBaseMesh.js";
|
|
25
|
+
import { Color3, Color4 } from "@onerjs/core/Maths/math.color.js";
|
|
26
|
+
import { TargetCamera } from "@onerjs/core/Cameras/targetCamera.js";
|
|
27
|
+
import { Epsilon } from "@onerjs/core/Maths/math.constants.js";
|
|
28
|
+
import { DataWriter } from "./dataWriter.js";
|
|
29
|
+
import { OpenPBRMaterial } from "@onerjs/core/Materials/PBR/openpbrMaterial.js";
|
|
30
|
+
class ExporterState {
|
|
31
|
+
constructor(convertToRightHanded, wasAddedByNoopNode) {
|
|
32
|
+
// Babylon indices array, start, count, offset, flip -> glTF accessor index
|
|
33
|
+
this._indicesAccessorMap = new Map();
|
|
34
|
+
// Babylon buffer -> glTF buffer view
|
|
35
|
+
this._vertexBufferViewMap = new Map();
|
|
36
|
+
// Babylon vertex buffer, start, count -> glTF accessor index
|
|
37
|
+
this._vertexAccessorMap = new Map();
|
|
38
|
+
this._remappedBufferView = new Map();
|
|
39
|
+
this._meshMorphTargetMap = new Map();
|
|
40
|
+
this._vertexMapColorAlpha = new Map();
|
|
41
|
+
this._exportedNodes = new Set();
|
|
42
|
+
// Babylon mesh -> glTF mesh index
|
|
43
|
+
this._meshMap = new Map();
|
|
44
|
+
// Only used when convertToRightHanded is true.
|
|
45
|
+
this.convertedToRightHandedBuffers = new Map();
|
|
46
|
+
this.convertToRightHanded = convertToRightHanded;
|
|
47
|
+
this.wasAddedByNoopNode = wasAddedByNoopNode;
|
|
48
|
+
}
|
|
49
|
+
getIndicesAccessor(indices, start, count, offset, flip) {
|
|
50
|
+
return this._indicesAccessorMap.get(indices)?.get(start)?.get(count)?.get(offset)?.get(flip);
|
|
51
|
+
}
|
|
52
|
+
setIndicesAccessor(indices, start, count, offset, flip, accessorIndex) {
|
|
53
|
+
let map1 = this._indicesAccessorMap.get(indices);
|
|
54
|
+
if (!map1) {
|
|
55
|
+
map1 = new Map();
|
|
56
|
+
this._indicesAccessorMap.set(indices, map1);
|
|
57
|
+
}
|
|
58
|
+
let map2 = map1.get(start);
|
|
59
|
+
if (!map2) {
|
|
60
|
+
map2 = new Map();
|
|
61
|
+
map1.set(start, map2);
|
|
62
|
+
}
|
|
63
|
+
let map3 = map2.get(count);
|
|
64
|
+
if (!map3) {
|
|
65
|
+
map3 = new Map();
|
|
66
|
+
map2.set(count, map3);
|
|
67
|
+
}
|
|
68
|
+
let map4 = map3.get(offset);
|
|
69
|
+
if (!map4) {
|
|
70
|
+
map4 = new Map();
|
|
71
|
+
map3.set(offset, map4);
|
|
72
|
+
}
|
|
73
|
+
map4.set(flip, accessorIndex);
|
|
74
|
+
}
|
|
75
|
+
pushExportedNode(node) {
|
|
76
|
+
if (!this._exportedNodes.has(node)) {
|
|
77
|
+
this._exportedNodes.add(node);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
getNodesSet() {
|
|
81
|
+
return this._exportedNodes;
|
|
82
|
+
}
|
|
83
|
+
getVertexBufferView(buffer) {
|
|
84
|
+
return this._vertexBufferViewMap.get(buffer);
|
|
85
|
+
}
|
|
86
|
+
setVertexBufferView(buffer, bufferView) {
|
|
87
|
+
this._vertexBufferViewMap.set(buffer, bufferView);
|
|
88
|
+
}
|
|
89
|
+
setRemappedBufferView(buffer, vertexBuffer, bufferView) {
|
|
90
|
+
this._remappedBufferView.set(buffer, new Map());
|
|
91
|
+
this._remappedBufferView.get(buffer).set(vertexBuffer, bufferView);
|
|
92
|
+
}
|
|
93
|
+
getRemappedBufferView(buffer, vertexBuffer) {
|
|
94
|
+
return this._remappedBufferView.get(buffer)?.get(vertexBuffer);
|
|
95
|
+
}
|
|
96
|
+
getVertexAccessor(vertexBuffer, start, count) {
|
|
97
|
+
return this._vertexAccessorMap.get(vertexBuffer)?.get(start)?.get(count);
|
|
98
|
+
}
|
|
99
|
+
setVertexAccessor(vertexBuffer, start, count, accessorIndex) {
|
|
100
|
+
let map1 = this._vertexAccessorMap.get(vertexBuffer);
|
|
101
|
+
if (!map1) {
|
|
102
|
+
map1 = new Map();
|
|
103
|
+
this._vertexAccessorMap.set(vertexBuffer, map1);
|
|
104
|
+
}
|
|
105
|
+
let map2 = map1.get(start);
|
|
106
|
+
if (!map2) {
|
|
107
|
+
map2 = new Map();
|
|
108
|
+
map1.set(start, map2);
|
|
109
|
+
}
|
|
110
|
+
map2.set(count, accessorIndex);
|
|
111
|
+
}
|
|
112
|
+
hasVertexColorAlpha(vertexBuffer) {
|
|
113
|
+
return this._vertexMapColorAlpha.get(vertexBuffer) || false;
|
|
114
|
+
}
|
|
115
|
+
setHasVertexColorAlpha(vertexBuffer, hasAlpha) {
|
|
116
|
+
return this._vertexMapColorAlpha.set(vertexBuffer, hasAlpha);
|
|
117
|
+
}
|
|
118
|
+
getMesh(mesh) {
|
|
119
|
+
return this._meshMap.get(mesh);
|
|
120
|
+
}
|
|
121
|
+
setMesh(mesh, meshIndex) {
|
|
122
|
+
this._meshMap.set(mesh, meshIndex);
|
|
123
|
+
}
|
|
124
|
+
bindMorphDataToMesh(mesh, morphData) {
|
|
125
|
+
const morphTargets = this._meshMorphTargetMap.get(mesh) || [];
|
|
126
|
+
this._meshMorphTargetMap.set(mesh, morphTargets);
|
|
127
|
+
if (morphTargets.indexOf(morphData) === -1) {
|
|
128
|
+
morphTargets.push(morphData);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
getMorphTargetsFromMesh(mesh) {
|
|
132
|
+
return this._meshMorphTargetMap.get(mesh);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** @internal */
|
|
136
|
+
export class GLTFExporter {
|
|
137
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/promise-function-async
|
|
138
|
+
_ApplyExtension(node, extensions, index, actionAsync) {
|
|
139
|
+
if (index >= extensions.length) {
|
|
140
|
+
return Promise.resolve(node);
|
|
141
|
+
}
|
|
142
|
+
const currentPromise = actionAsync(extensions[index], node);
|
|
143
|
+
if (!currentPromise) {
|
|
144
|
+
return this._ApplyExtension(node, extensions, index + 1, actionAsync);
|
|
145
|
+
}
|
|
146
|
+
// eslint-disable-next-line github/no-then
|
|
147
|
+
return currentPromise.then(async (newNode) => (newNode ? await this._ApplyExtension(newNode, extensions, index + 1, actionAsync) : null));
|
|
148
|
+
}
|
|
149
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/promise-function-async
|
|
150
|
+
_ApplyExtensions(node, actionAsync) {
|
|
151
|
+
const extensions = [];
|
|
152
|
+
for (const name of GLTFExporter._ExtensionNames) {
|
|
153
|
+
extensions.push(this._extensions[name]);
|
|
154
|
+
}
|
|
155
|
+
return this._ApplyExtension(node, extensions, 0, actionAsync);
|
|
156
|
+
}
|
|
157
|
+
// eslint-disable-next-line no-restricted-syntax, @typescript-eslint/promise-function-async
|
|
158
|
+
_extensionsPostExportNodeAsync(context, node, babylonNode, nodeMap, convertToRightHanded) {
|
|
159
|
+
return this._ApplyExtensions(node,
|
|
160
|
+
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
161
|
+
(extension, node) => extension.postExportNodeAsync && extension.postExportNodeAsync(context, node, babylonNode, nodeMap, convertToRightHanded, this._bufferManager));
|
|
162
|
+
}
|
|
163
|
+
// eslint-disable-next-line no-restricted-syntax, @typescript-eslint/promise-function-async
|
|
164
|
+
_extensionsPostExportMaterialAsync(context, material, babylonMaterial) {
|
|
165
|
+
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
166
|
+
return this._ApplyExtensions(material, (extension, node) => extension.postExportMaterialAsync && extension.postExportMaterialAsync(context, node, babylonMaterial));
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get additional textures for a material
|
|
170
|
+
* @param context The context when loading the asset
|
|
171
|
+
* @param material The glTF material
|
|
172
|
+
* @param babylonMaterial The Babylon.js material
|
|
173
|
+
* @returns List of additional textures
|
|
174
|
+
*/
|
|
175
|
+
async _extensionsPostExportMaterialAdditionalTexturesAsync(context, material, babylonMaterial) {
|
|
176
|
+
const output = [];
|
|
177
|
+
await Promise.all(GLTFExporter._ExtensionNames.map(async (name) => {
|
|
178
|
+
const extension = this._extensions[name];
|
|
179
|
+
if (extension.postExportMaterialAdditionalTexturesAsync) {
|
|
180
|
+
const textures = await extension.postExportMaterialAdditionalTexturesAsync(context, material, babylonMaterial);
|
|
181
|
+
output.push(...textures);
|
|
182
|
+
}
|
|
183
|
+
}));
|
|
184
|
+
return output;
|
|
185
|
+
}
|
|
186
|
+
_extensionsPostExportTextures(context, textureInfo, babylonTexture) {
|
|
187
|
+
for (const name of GLTFExporter._ExtensionNames) {
|
|
188
|
+
const extension = this._extensions[name];
|
|
189
|
+
if (extension.postExportTexture) {
|
|
190
|
+
extension.postExportTexture(context, textureInfo, babylonTexture);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
_extensionsPostExportMeshPrimitive(primitive) {
|
|
195
|
+
for (const name of GLTFExporter._ExtensionNames) {
|
|
196
|
+
const extension = this._extensions[name];
|
|
197
|
+
if (extension.postExportMeshPrimitive) {
|
|
198
|
+
extension.postExportMeshPrimitive(primitive, this._bufferManager, this._accessors);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
async _extensionsPreGenerateBinaryAsync() {
|
|
203
|
+
for (const name of GLTFExporter._ExtensionNames) {
|
|
204
|
+
const extension = this._extensions[name];
|
|
205
|
+
if (extension.preGenerateBinaryAsync) {
|
|
206
|
+
// eslint-disable-next-line no-await-in-loop
|
|
207
|
+
await extension.preGenerateBinaryAsync(this._bufferManager);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
_forEachExtensions(action) {
|
|
212
|
+
for (const name of GLTFExporter._ExtensionNames) {
|
|
213
|
+
const extension = this._extensions[name];
|
|
214
|
+
if (extension.enabled) {
|
|
215
|
+
action(extension);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
_extensionsOnExporting() {
|
|
220
|
+
this._forEachExtensions((extension) => {
|
|
221
|
+
var _a, _b, _c;
|
|
222
|
+
if (extension.wasUsed) {
|
|
223
|
+
(_a = this._glTF).extensionsUsed || (_a.extensionsUsed = []);
|
|
224
|
+
if (this._glTF.extensionsUsed.indexOf(extension.name) === -1) {
|
|
225
|
+
this._glTF.extensionsUsed.push(extension.name);
|
|
226
|
+
}
|
|
227
|
+
if (extension.required) {
|
|
228
|
+
(_b = this._glTF).extensionsRequired || (_b.extensionsRequired = []);
|
|
229
|
+
if (this._glTF.extensionsRequired.indexOf(extension.name) === -1) {
|
|
230
|
+
this._glTF.extensionsRequired.push(extension.name);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
(_c = this._glTF).extensions || (_c.extensions = {});
|
|
234
|
+
if (extension.onExporting) {
|
|
235
|
+
extension.onExporting();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
_loadExtensions() {
|
|
241
|
+
for (const name of GLTFExporter._ExtensionNames) {
|
|
242
|
+
const extension = GLTFExporter._ExtensionFactories[name](this);
|
|
243
|
+
this._extensions[name] = extension;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
constructor(babylonScene = EngineStore.LastCreatedScene, options) {
|
|
247
|
+
this._glTF = {
|
|
248
|
+
asset: { generator: `Babylon.js v${Engine.Version}`, version: "2.0" },
|
|
249
|
+
};
|
|
250
|
+
this._animations = [];
|
|
251
|
+
this._accessors = [];
|
|
252
|
+
this._bufferViews = [];
|
|
253
|
+
this._cameras = [];
|
|
254
|
+
this._images = [];
|
|
255
|
+
this._materials = [];
|
|
256
|
+
this._meshes = [];
|
|
257
|
+
this._nodes = [];
|
|
258
|
+
this._samplers = [];
|
|
259
|
+
this._scenes = [];
|
|
260
|
+
this._skins = [];
|
|
261
|
+
this._textures = [];
|
|
262
|
+
this._imageData = {};
|
|
263
|
+
this._shouldUseGlb = false;
|
|
264
|
+
this._materialExporter = new GLTFMaterialExporter(this);
|
|
265
|
+
this._extensions = {};
|
|
266
|
+
this._bufferManager = new BufferManager();
|
|
267
|
+
this._shouldExportNodeMap = new Map();
|
|
268
|
+
// Babylon node -> glTF node index
|
|
269
|
+
this._nodeMap = new Map();
|
|
270
|
+
// Babylon material -> glTF material index
|
|
271
|
+
this._materialMap = new Map();
|
|
272
|
+
this._camerasMap = new Map();
|
|
273
|
+
this._nodesCameraMap = new Map();
|
|
274
|
+
this._skinMap = new Map();
|
|
275
|
+
this._nodesSkinMap = new Map();
|
|
276
|
+
// A material in this set requires UVs
|
|
277
|
+
this._materialNeedsUVsSet = new Set();
|
|
278
|
+
if (!babylonScene) {
|
|
279
|
+
throw new Error("No scene available to export");
|
|
280
|
+
}
|
|
281
|
+
this._babylonScene = babylonScene;
|
|
282
|
+
this._options = {
|
|
283
|
+
shouldExportNode: () => true,
|
|
284
|
+
shouldExportAnimation: () => true,
|
|
285
|
+
metadataSelector: (metadata) => metadata?.gltf?.extras,
|
|
286
|
+
animationSampleRate: 1 / 60,
|
|
287
|
+
exportWithoutWaitingForScene: false,
|
|
288
|
+
exportUnusedUVs: false,
|
|
289
|
+
removeNoopRootNodes: true,
|
|
290
|
+
includeCoordinateSystemConversionNodes: false,
|
|
291
|
+
meshCompressionMethod: "None",
|
|
292
|
+
...options,
|
|
293
|
+
};
|
|
294
|
+
this._loadExtensions();
|
|
295
|
+
}
|
|
296
|
+
dispose() {
|
|
297
|
+
for (const key in this._extensions) {
|
|
298
|
+
const extension = this._extensions[key];
|
|
299
|
+
extension.dispose();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
get options() {
|
|
303
|
+
return this._options;
|
|
304
|
+
}
|
|
305
|
+
static RegisterExtension(name, factory, order = 100) {
|
|
306
|
+
if (GLTFExporter.UnregisterExtension(name)) {
|
|
307
|
+
Tools.Warn(`Extension with the name ${name} already exists`);
|
|
308
|
+
}
|
|
309
|
+
GLTFExporter._ExtensionFactories[name] = factory;
|
|
310
|
+
const extensionOrder = order ?? 0; // Use provided order or default to 0
|
|
311
|
+
GLTFExporter._ExtensionOrders[name] = extensionOrder;
|
|
312
|
+
// Find the correct position to insert the extension based on order
|
|
313
|
+
let insertIndex = GLTFExporter._ExtensionNames.length;
|
|
314
|
+
for (let i = 0; i < GLTFExporter._ExtensionNames.length; i++) {
|
|
315
|
+
const existingName = GLTFExporter._ExtensionNames[i];
|
|
316
|
+
const existingOrder = GLTFExporter._ExtensionOrders[existingName];
|
|
317
|
+
// If the order is less, insert before.
|
|
318
|
+
if (extensionOrder < existingOrder) {
|
|
319
|
+
insertIndex = i;
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
GLTFExporter._ExtensionNames.splice(insertIndex, 0, name);
|
|
324
|
+
}
|
|
325
|
+
static UnregisterExtension(name) {
|
|
326
|
+
if (!GLTFExporter._ExtensionFactories[name]) {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
delete GLTFExporter._ExtensionFactories[name];
|
|
330
|
+
delete GLTFExporter._ExtensionOrders[name];
|
|
331
|
+
const index = GLTFExporter._ExtensionNames.indexOf(name);
|
|
332
|
+
if (index !== -1) {
|
|
333
|
+
GLTFExporter._ExtensionNames.splice(index, 1);
|
|
334
|
+
}
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
_generateJSON(bufferByteLength, fileName, prettyPrint) {
|
|
338
|
+
const buffer = { byteLength: bufferByteLength };
|
|
339
|
+
if (buffer.byteLength) {
|
|
340
|
+
this._glTF.buffers = [buffer];
|
|
341
|
+
}
|
|
342
|
+
if (this._nodes && this._nodes.length) {
|
|
343
|
+
this._glTF.nodes = this._nodes;
|
|
344
|
+
}
|
|
345
|
+
if (this._meshes && this._meshes.length) {
|
|
346
|
+
this._glTF.meshes = this._meshes;
|
|
347
|
+
}
|
|
348
|
+
if (this._scenes && this._scenes.length) {
|
|
349
|
+
this._glTF.scenes = this._scenes;
|
|
350
|
+
this._glTF.scene = 0;
|
|
351
|
+
}
|
|
352
|
+
if (this._cameras && this._cameras.length) {
|
|
353
|
+
this._glTF.cameras = this._cameras;
|
|
354
|
+
}
|
|
355
|
+
if (this._bufferViews && this._bufferViews.length) {
|
|
356
|
+
this._glTF.bufferViews = this._bufferViews;
|
|
357
|
+
}
|
|
358
|
+
if (this._accessors && this._accessors.length) {
|
|
359
|
+
this._glTF.accessors = this._accessors;
|
|
360
|
+
}
|
|
361
|
+
if (this._animations && this._animations.length) {
|
|
362
|
+
this._glTF.animations = this._animations;
|
|
363
|
+
}
|
|
364
|
+
if (this._materials && this._materials.length) {
|
|
365
|
+
this._glTF.materials = this._materials;
|
|
366
|
+
}
|
|
367
|
+
if (this._textures && this._textures.length) {
|
|
368
|
+
this._glTF.textures = this._textures;
|
|
369
|
+
}
|
|
370
|
+
if (this._samplers && this._samplers.length) {
|
|
371
|
+
this._glTF.samplers = this._samplers;
|
|
372
|
+
}
|
|
373
|
+
if (this._skins && this._skins.length) {
|
|
374
|
+
this._glTF.skins = this._skins;
|
|
375
|
+
}
|
|
376
|
+
if (this._images && this._images.length) {
|
|
377
|
+
this._glTF.images = this._images;
|
|
378
|
+
}
|
|
379
|
+
if (!this._shouldUseGlb) {
|
|
380
|
+
buffer.uri = fileName + ".bin";
|
|
381
|
+
}
|
|
382
|
+
return prettyPrint ? JSON.stringify(this._glTF, null, 2) : JSON.stringify(this._glTF);
|
|
383
|
+
}
|
|
384
|
+
async generateGLTFAsync(glTFPrefix) {
|
|
385
|
+
const binaryBuffer = await this._generateBinaryAsync();
|
|
386
|
+
this._extensionsOnExporting();
|
|
387
|
+
const jsonText = this._generateJSON(binaryBuffer.byteLength, glTFPrefix, true);
|
|
388
|
+
const bin = new Blob([binaryBuffer], { type: "application/octet-stream" });
|
|
389
|
+
const glTFFileName = glTFPrefix + ".gltf";
|
|
390
|
+
const glTFBinFile = glTFPrefix + ".bin";
|
|
391
|
+
const container = new GLTFData();
|
|
392
|
+
container.files[glTFFileName] = jsonText;
|
|
393
|
+
container.files[glTFBinFile] = bin;
|
|
394
|
+
if (this._imageData) {
|
|
395
|
+
for (const image in this._imageData) {
|
|
396
|
+
container.files[image] = new Blob([this._imageData[image].data], { type: this._imageData[image].mimeType });
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return container;
|
|
400
|
+
}
|
|
401
|
+
async _generateBinaryAsync() {
|
|
402
|
+
await this._exportSceneAsync();
|
|
403
|
+
await this._extensionsPreGenerateBinaryAsync();
|
|
404
|
+
return this._bufferManager.generateBinary(this._bufferViews);
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Pads the number to a multiple of 4
|
|
408
|
+
* @param num number to pad
|
|
409
|
+
* @returns padded number
|
|
410
|
+
*/
|
|
411
|
+
_getPadding(num) {
|
|
412
|
+
const remainder = num % 4;
|
|
413
|
+
const padding = remainder === 0 ? remainder : 4 - remainder;
|
|
414
|
+
return padding;
|
|
415
|
+
}
|
|
416
|
+
async generateGLBAsync(glTFPrefix) {
|
|
417
|
+
this._shouldUseGlb = true;
|
|
418
|
+
const binaryBuffer = await this._generateBinaryAsync();
|
|
419
|
+
this._extensionsOnExporting();
|
|
420
|
+
const jsonText = this._generateJSON(binaryBuffer.byteLength);
|
|
421
|
+
const glbFileName = glTFPrefix + ".glb";
|
|
422
|
+
const headerLength = 12;
|
|
423
|
+
const chunkLengthPrefix = 8;
|
|
424
|
+
let jsonLength = jsonText.length;
|
|
425
|
+
let encodedJsonText;
|
|
426
|
+
// Make use of TextEncoder when available
|
|
427
|
+
if (typeof TextEncoder !== "undefined") {
|
|
428
|
+
const encoder = new TextEncoder();
|
|
429
|
+
encodedJsonText = encoder.encode(jsonText);
|
|
430
|
+
jsonLength = encodedJsonText.length;
|
|
431
|
+
}
|
|
432
|
+
const jsonPadding = this._getPadding(jsonLength);
|
|
433
|
+
const binPadding = this._getPadding(binaryBuffer.byteLength);
|
|
434
|
+
const byteLength = headerLength + 2 * chunkLengthPrefix + jsonLength + jsonPadding + binaryBuffer.byteLength + binPadding;
|
|
435
|
+
const dataWriter = new DataWriter(byteLength);
|
|
436
|
+
// Header
|
|
437
|
+
dataWriter.writeUInt32(0x46546c67); // "glTF"
|
|
438
|
+
dataWriter.writeUInt32(2); // Version
|
|
439
|
+
dataWriter.writeUInt32(byteLength); // Total bytes in file
|
|
440
|
+
// JSON chunk length prefix
|
|
441
|
+
dataWriter.writeUInt32(jsonLength + jsonPadding);
|
|
442
|
+
dataWriter.writeUInt32(0x4e4f534a); // "JSON"
|
|
443
|
+
// JSON chunk bytes
|
|
444
|
+
if (encodedJsonText) {
|
|
445
|
+
// If TextEncoder was available, we can simply copy the encoded array
|
|
446
|
+
dataWriter.writeTypedArray(encodedJsonText);
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
const blankCharCode = "_".charCodeAt(0);
|
|
450
|
+
for (let i = 0; i < jsonLength; ++i) {
|
|
451
|
+
const charCode = jsonText.charCodeAt(i);
|
|
452
|
+
// If the character doesn't fit into a single UTF-16 code unit, just put a blank character
|
|
453
|
+
if (charCode != jsonText.codePointAt(i)) {
|
|
454
|
+
dataWriter.writeUInt8(blankCharCode);
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
dataWriter.writeUInt8(charCode);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// JSON padding
|
|
462
|
+
for (let i = 0; i < jsonPadding; ++i) {
|
|
463
|
+
dataWriter.writeUInt8(0x20);
|
|
464
|
+
}
|
|
465
|
+
// Binary chunk length prefix
|
|
466
|
+
dataWriter.writeUInt32(binaryBuffer.byteLength + binPadding);
|
|
467
|
+
dataWriter.writeUInt32(0x004e4942); // "BIN"
|
|
468
|
+
// Binary chunk bytes
|
|
469
|
+
dataWriter.writeTypedArray(binaryBuffer);
|
|
470
|
+
// Binary padding
|
|
471
|
+
for (let i = 0; i < binPadding; ++i) {
|
|
472
|
+
dataWriter.writeUInt8(0);
|
|
473
|
+
}
|
|
474
|
+
const container = new GLTFData();
|
|
475
|
+
container.files[glbFileName] = new Blob([dataWriter.getOutputData()], { type: "application/octet-stream" });
|
|
476
|
+
return container;
|
|
477
|
+
}
|
|
478
|
+
_setNodeTransformation(node, babylonTransformNode, convertToRightHanded) {
|
|
479
|
+
if (!babylonTransformNode.getPivotPoint().equalsWithEpsilon(DefaultTranslation, Epsilon)) {
|
|
480
|
+
Tools.Warn("Pivot points are not supported in the glTF serializer");
|
|
481
|
+
}
|
|
482
|
+
if (!babylonTransformNode.position.equalsWithEpsilon(DefaultTranslation, Epsilon)) {
|
|
483
|
+
const translation = TmpVectors.Vector3[0].copyFrom(babylonTransformNode.position);
|
|
484
|
+
if (convertToRightHanded) {
|
|
485
|
+
ConvertToRightHandedPosition(translation);
|
|
486
|
+
}
|
|
487
|
+
node.translation = translation.asArray();
|
|
488
|
+
}
|
|
489
|
+
if (!babylonTransformNode.scaling.equalsWithEpsilon(DefaultScale, Epsilon)) {
|
|
490
|
+
node.scale = babylonTransformNode.scaling.asArray();
|
|
491
|
+
}
|
|
492
|
+
const rotationQuaternion = babylonTransformNode.rotationQuaternion?.clone() ||
|
|
493
|
+
Quaternion.FromEulerAngles(babylonTransformNode.rotation.x, babylonTransformNode.rotation.y, babylonTransformNode.rotation.z);
|
|
494
|
+
if (!rotationQuaternion.equalsWithEpsilon(DefaultRotation, Epsilon)) {
|
|
495
|
+
if (convertToRightHanded) {
|
|
496
|
+
ConvertToRightHandedRotation(rotationQuaternion);
|
|
497
|
+
}
|
|
498
|
+
node.rotation = rotationQuaternion.normalize().asArray();
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
_setCameraTransformation(node, babylonCamera, convertToRightHanded) {
|
|
502
|
+
// Camera types store rotation differently (e.g., ArcRotateCamera uses alpha/beta, others use rotationQuaternion).
|
|
503
|
+
// Extract the transform from the world matrix instead of handling each case separately.
|
|
504
|
+
const translation = TmpVectors.Vector3[0];
|
|
505
|
+
const rotationQuaternion = TmpVectors.Quaternion[0];
|
|
506
|
+
const cameraWorldMatrix = babylonCamera.getWorldMatrix();
|
|
507
|
+
if (babylonCamera.parent) {
|
|
508
|
+
// Camera.getWorldMatrix returns global coordinates. GLTF node must use local coordinates. If camera has parent we need to use local translation/rotation.
|
|
509
|
+
const parentInvWorldMatrix = babylonCamera.parent.getWorldMatrix().invertToRef(TmpVectors.Matrix[0]);
|
|
510
|
+
const cameraLocal = cameraWorldMatrix.multiplyToRef(parentInvWorldMatrix, TmpVectors.Matrix[1]);
|
|
511
|
+
cameraLocal.decompose(undefined, rotationQuaternion, translation);
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
cameraWorldMatrix.decompose(undefined, rotationQuaternion, translation);
|
|
515
|
+
}
|
|
516
|
+
if (!translation.equalsWithEpsilon(DefaultTranslation, Epsilon)) {
|
|
517
|
+
if (convertToRightHanded) {
|
|
518
|
+
ConvertToRightHandedPosition(translation);
|
|
519
|
+
}
|
|
520
|
+
node.translation = translation.asArray();
|
|
521
|
+
}
|
|
522
|
+
if (convertToRightHanded) {
|
|
523
|
+
ConvertToRightHandedRotation(rotationQuaternion);
|
|
524
|
+
}
|
|
525
|
+
// Left-handed scenes have cameras that always face Z+ (opposite of glTF's Z-).
|
|
526
|
+
// Use scene coordinate system rather than convertToRightHanded, since some
|
|
527
|
+
// cameras may not need convertToRightHanded but still need correction to face Z-.
|
|
528
|
+
if (!this._babylonScene.useRightHandedSystem) {
|
|
529
|
+
Rotate180Y(rotationQuaternion);
|
|
530
|
+
}
|
|
531
|
+
if (!rotationQuaternion.equalsWithEpsilon(DefaultRotation, Epsilon)) {
|
|
532
|
+
node.rotation = rotationQuaternion.asArray();
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
// Export babylon cameras to glTF cameras
|
|
536
|
+
_listAvailableCameras() {
|
|
537
|
+
for (const camera of this._babylonScene.cameras) {
|
|
538
|
+
const glTFCamera = {
|
|
539
|
+
type: camera.mode === Camera.PERSPECTIVE_CAMERA ? "perspective" /* CameraType.PERSPECTIVE */ : "orthographic" /* CameraType.ORTHOGRAPHIC */,
|
|
540
|
+
};
|
|
541
|
+
if (camera.name) {
|
|
542
|
+
glTFCamera.name = camera.name;
|
|
543
|
+
}
|
|
544
|
+
if (glTFCamera.type === "perspective" /* CameraType.PERSPECTIVE */) {
|
|
545
|
+
glTFCamera.perspective = {
|
|
546
|
+
aspectRatio: camera.getEngine().getAspectRatio(camera),
|
|
547
|
+
yfov: camera.fovMode === Camera.FOVMODE_VERTICAL_FIXED ? camera.fov : camera.fov * camera.getEngine().getAspectRatio(camera),
|
|
548
|
+
znear: camera.minZ,
|
|
549
|
+
zfar: camera.maxZ,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
else if (glTFCamera.type === "orthographic" /* CameraType.ORTHOGRAPHIC */) {
|
|
553
|
+
const halfWidth = camera.orthoLeft && camera.orthoRight ? 0.5 * (camera.orthoRight - camera.orthoLeft) : camera.getEngine().getRenderWidth() * 0.5;
|
|
554
|
+
const halfHeight = camera.orthoBottom && camera.orthoTop ? 0.5 * (camera.orthoTop - camera.orthoBottom) : camera.getEngine().getRenderHeight() * 0.5;
|
|
555
|
+
glTFCamera.orthographic = {
|
|
556
|
+
xmag: halfWidth,
|
|
557
|
+
ymag: halfHeight,
|
|
558
|
+
znear: camera.minZ,
|
|
559
|
+
zfar: camera.maxZ,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
this._camerasMap.set(camera, glTFCamera);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
// Cleanup unused cameras and assign index to nodes.
|
|
566
|
+
_exportAndAssignCameras() {
|
|
567
|
+
const gltfCameras = Array.from(this._camerasMap.values());
|
|
568
|
+
for (const gltfCamera of gltfCameras) {
|
|
569
|
+
const usedNodes = this._nodesCameraMap.get(gltfCamera);
|
|
570
|
+
if (usedNodes !== undefined) {
|
|
571
|
+
this._cameras.push(gltfCamera);
|
|
572
|
+
for (const node of usedNodes) {
|
|
573
|
+
node.camera = this._cameras.length - 1;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
// Collects all skins in a skins map so nodes can reference it during node parsing.
|
|
579
|
+
_listAvailableSkeletons() {
|
|
580
|
+
for (const skeleton of this._babylonScene.skeletons) {
|
|
581
|
+
if (skeleton.bones.length <= 0) {
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
const skin = { joints: [] };
|
|
585
|
+
this._skinMap.set(skeleton, skin);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
_exportAndAssignSkeletons(leftHandNodes) {
|
|
589
|
+
for (const skeleton of this._babylonScene.skeletons) {
|
|
590
|
+
if (skeleton.bones.length <= 0) {
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
const skin = this._skinMap.get(skeleton);
|
|
594
|
+
if (skin == undefined) {
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
// The bones (joints) of a skeleton (skin) must be exported in the same order as they appear in vertex attributes,
|
|
598
|
+
// which is indicated by getIndex and may not match a bone's index in skeleton.bones
|
|
599
|
+
const boneIndexMap = {};
|
|
600
|
+
let maxBoneIndex = -1;
|
|
601
|
+
for (let i = 0; i < skeleton.bones.length; ++i) {
|
|
602
|
+
const bone = skeleton.bones[i];
|
|
603
|
+
const boneIndex = bone.getIndex() ?? i;
|
|
604
|
+
if (boneIndex !== -1) {
|
|
605
|
+
boneIndexMap[boneIndex] = bone;
|
|
606
|
+
if (boneIndex > maxBoneIndex) {
|
|
607
|
+
maxBoneIndex = boneIndex;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
// Set joints indices to scene nodes.
|
|
612
|
+
const inverseBindMatrices = [];
|
|
613
|
+
for (let boneIndex = 0; boneIndex <= maxBoneIndex; ++boneIndex) {
|
|
614
|
+
const bone = boneIndexMap[boneIndex]; // Assumes no gaps in bone indices
|
|
615
|
+
const transformNode = bone.getTransformNode();
|
|
616
|
+
const nodeIndex = transformNode ? this._nodeMap.get(transformNode) : undefined;
|
|
617
|
+
if (nodeIndex === undefined) {
|
|
618
|
+
Tools.Warn("Exporting a bone without a linked transform node is currently unsupported.");
|
|
619
|
+
continue; // The indices may be out-of-sync after this and break the skinning.
|
|
620
|
+
}
|
|
621
|
+
skin.joints.push(nodeIndex);
|
|
622
|
+
const boneMatrix = bone.getAbsoluteInverseBindMatrix().clone();
|
|
623
|
+
if (leftHandNodes.has(transformNode)) {
|
|
624
|
+
ConvertToRightHandedTransformMatrix(boneMatrix);
|
|
625
|
+
}
|
|
626
|
+
inverseBindMatrices.push(boneMatrix);
|
|
627
|
+
}
|
|
628
|
+
// Nodes that use this skin.
|
|
629
|
+
const skinnedNodes = this._nodesSkinMap.get(skin);
|
|
630
|
+
// Only export the skin if it has at least one joint and is used by a mesh.
|
|
631
|
+
if (skin.joints.length > 0 && skinnedNodes !== undefined) {
|
|
632
|
+
const inverseBindMatricesData = new Float32Array(inverseBindMatrices.length * 16); // Always a 4 x 4 matrix of 32 bit float
|
|
633
|
+
inverseBindMatrices.forEach((mat, index) => {
|
|
634
|
+
inverseBindMatricesData.set(mat.m, index * 16);
|
|
635
|
+
});
|
|
636
|
+
const bufferView = this._bufferManager.createBufferView(inverseBindMatricesData);
|
|
637
|
+
this._accessors.push(this._bufferManager.createAccessor(bufferView, "MAT4" /* AccessorType.MAT4 */, 5126 /* AccessorComponentType.FLOAT */, inverseBindMatrices.length));
|
|
638
|
+
skin.inverseBindMatrices = this._accessors.length - 1;
|
|
639
|
+
this._skins.push(skin);
|
|
640
|
+
const skinIndex = this._skins.length - 1;
|
|
641
|
+
for (const skinnedNode of skinnedNodes) {
|
|
642
|
+
skinnedNode.skin = skinIndex;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
async _exportSceneAsync() {
|
|
648
|
+
const scene = { nodes: [] };
|
|
649
|
+
// Scene metadata
|
|
650
|
+
if (this._babylonScene.metadata) {
|
|
651
|
+
const extras = this._options.metadataSelector(this._babylonScene.metadata);
|
|
652
|
+
if (extras) {
|
|
653
|
+
scene.extras = extras;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
// TODO:
|
|
657
|
+
// deal with this from the loader:
|
|
658
|
+
// babylonMaterial.invertNormalMapX = !this._babylonScene.useRightHandedSystem;
|
|
659
|
+
// babylonMaterial.invertNormalMapY = this._babylonScene.useRightHandedSystem;
|
|
660
|
+
const rootNodesRH = new Array();
|
|
661
|
+
const rootNodesLH = new Array();
|
|
662
|
+
const rootNoopNodesRH = new Array();
|
|
663
|
+
for (const rootNode of this._babylonScene.rootNodes) {
|
|
664
|
+
if (this._options.removeNoopRootNodes && !this._options.includeCoordinateSystemConversionNodes && IsNoopNode(rootNode, this._babylonScene.useRightHandedSystem)) {
|
|
665
|
+
rootNoopNodesRH.push(...rootNode.getChildren());
|
|
666
|
+
}
|
|
667
|
+
else if (this._babylonScene.useRightHandedSystem) {
|
|
668
|
+
rootNodesRH.push(rootNode);
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
rootNodesLH.push(rootNode);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
this._listAvailableCameras();
|
|
675
|
+
this._listAvailableSkeletons();
|
|
676
|
+
const stateLH = new ExporterState(true, false);
|
|
677
|
+
scene.nodes.push(...(await this._exportNodesAsync(rootNodesLH, stateLH)));
|
|
678
|
+
const stateRH = new ExporterState(false, false);
|
|
679
|
+
scene.nodes.push(...(await this._exportNodesAsync(rootNodesRH, stateRH)));
|
|
680
|
+
const noopRH = new ExporterState(false, true);
|
|
681
|
+
scene.nodes.push(...(await this._exportNodesAsync(rootNoopNodesRH, noopRH)));
|
|
682
|
+
if (scene.nodes.length) {
|
|
683
|
+
this._scenes.push(scene);
|
|
684
|
+
}
|
|
685
|
+
this._exportAndAssignCameras();
|
|
686
|
+
this._exportAndAssignSkeletons(stateLH.getNodesSet());
|
|
687
|
+
if (this._babylonScene.animationGroups.length) {
|
|
688
|
+
_GLTFAnimation._CreateNodeAndMorphAnimationFromAnimationGroups(this._babylonScene, this._animations, this._nodeMap, this._bufferManager, this._bufferViews, this._accessors, this._animationSampleRate, stateLH.getNodesSet(), this._options.shouldExportAnimation);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
_shouldExportNode(babylonNode) {
|
|
692
|
+
let result = this._shouldExportNodeMap.get(babylonNode);
|
|
693
|
+
if (result === undefined) {
|
|
694
|
+
result = this._options.shouldExportNode(babylonNode);
|
|
695
|
+
this._shouldExportNodeMap.set(babylonNode, result);
|
|
696
|
+
}
|
|
697
|
+
return result;
|
|
698
|
+
}
|
|
699
|
+
async _exportNodesAsync(babylonRootNodes, state) {
|
|
700
|
+
const nodes = new Array();
|
|
701
|
+
this._exportBuffers(babylonRootNodes, state);
|
|
702
|
+
for (const babylonNode of babylonRootNodes) {
|
|
703
|
+
// eslint-disable-next-line no-await-in-loop
|
|
704
|
+
await this._exportNodeAsync(babylonNode, nodes, state);
|
|
705
|
+
}
|
|
706
|
+
return nodes;
|
|
707
|
+
}
|
|
708
|
+
_collectBuffers(babylonNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap, morphTargetsToMeshesMap, state) {
|
|
709
|
+
if (this._shouldExportNode(babylonNode) && babylonNode instanceof AbstractMesh && babylonNode.geometry) {
|
|
710
|
+
const vertexBuffers = babylonNode.geometry.getVertexBuffers();
|
|
711
|
+
if (vertexBuffers) {
|
|
712
|
+
for (const kind in vertexBuffers) {
|
|
713
|
+
if (!IsStandardVertexAttribute(kind)) {
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
const vertexBuffer = vertexBuffers[kind];
|
|
717
|
+
state.setHasVertexColorAlpha(vertexBuffer, babylonNode.hasVertexAlpha);
|
|
718
|
+
const buffer = vertexBuffer._buffer;
|
|
719
|
+
const vertexBufferArray = bufferToVertexBuffersMap.get(buffer) || [];
|
|
720
|
+
bufferToVertexBuffersMap.set(buffer, vertexBufferArray);
|
|
721
|
+
if (vertexBufferArray.indexOf(vertexBuffer) === -1) {
|
|
722
|
+
vertexBufferArray.push(vertexBuffer);
|
|
723
|
+
}
|
|
724
|
+
const meshes = vertexBufferToMeshesMap.get(vertexBuffer) || [];
|
|
725
|
+
vertexBufferToMeshesMap.set(vertexBuffer, meshes);
|
|
726
|
+
if (meshes.indexOf(babylonNode) === -1) {
|
|
727
|
+
meshes.push(babylonNode);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
const morphTargetManager = babylonNode.morphTargetManager;
|
|
732
|
+
if (morphTargetManager) {
|
|
733
|
+
for (let morphIndex = 0; morphIndex < morphTargetManager.numTargets; morphIndex++) {
|
|
734
|
+
const morphTarget = morphTargetManager.getTarget(morphIndex);
|
|
735
|
+
const meshes = morphTargetsToMeshesMap.get(morphTarget) || [];
|
|
736
|
+
morphTargetsToMeshesMap.set(morphTarget, meshes);
|
|
737
|
+
if (meshes.indexOf(babylonNode) === -1) {
|
|
738
|
+
meshes.push(babylonNode);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
for (const babylonChildNode of babylonNode.getChildren()) {
|
|
744
|
+
this._collectBuffers(babylonChildNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap, morphTargetsToMeshesMap, state);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
_exportBuffers(babylonRootNodes, state) {
|
|
748
|
+
const bufferToVertexBuffersMap = new Map();
|
|
749
|
+
const vertexBufferToMeshesMap = new Map();
|
|
750
|
+
const morphTargetsMeshesMap = new Map();
|
|
751
|
+
for (const babylonNode of babylonRootNodes) {
|
|
752
|
+
this._collectBuffers(babylonNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap, morphTargetsMeshesMap, state);
|
|
753
|
+
}
|
|
754
|
+
const buffers = Array.from(bufferToVertexBuffersMap.keys());
|
|
755
|
+
for (const buffer of buffers) {
|
|
756
|
+
const data = buffer.getData();
|
|
757
|
+
if (!data) {
|
|
758
|
+
throw new Error("Buffer data is not available");
|
|
759
|
+
}
|
|
760
|
+
const vertexBuffers = bufferToVertexBuffersMap.get(buffer);
|
|
761
|
+
if (!vertexBuffers) {
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
const byteStride = vertexBuffers[0].byteStride;
|
|
765
|
+
if (vertexBuffers.some((vertexBuffer) => vertexBuffer.byteStride !== byteStride)) {
|
|
766
|
+
throw new Error("Vertex buffers pointing to the same buffer must have the same byte stride");
|
|
767
|
+
}
|
|
768
|
+
const bytes = DataArrayToUint8Array(data).slice();
|
|
769
|
+
// Apply normalizations and color corrections to buffer data in-place.
|
|
770
|
+
for (const vertexBuffer of vertexBuffers) {
|
|
771
|
+
const meshes = vertexBufferToMeshesMap.get(vertexBuffer);
|
|
772
|
+
const { byteOffset, byteStride, componentCount, type, count, normalized, kind } = GetVertexBufferInfo(vertexBuffer, meshes);
|
|
773
|
+
switch (kind) {
|
|
774
|
+
// Normalize normals and tangents.
|
|
775
|
+
case VertexBuffer.NormalKind:
|
|
776
|
+
case VertexBuffer.TangentKind: {
|
|
777
|
+
EnumerateFloatValues(bytes, byteOffset, byteStride, componentCount, type, count, normalized, (values) => {
|
|
778
|
+
const length = Math.sqrt(values[0] * values[0] + values[1] * values[1] + values[2] * values[2]);
|
|
779
|
+
if (length > 0) {
|
|
780
|
+
const invLength = 1 / length;
|
|
781
|
+
values[0] *= invLength;
|
|
782
|
+
values[1] *= invLength;
|
|
783
|
+
values[2] *= invLength;
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
break;
|
|
787
|
+
}
|
|
788
|
+
// Convert StandardMaterial vertex colors from gamma to linear space.
|
|
789
|
+
case VertexBuffer.ColorKind: {
|
|
790
|
+
const stdMaterialCount = meshes.filter((mesh) => mesh.material instanceof StandardMaterial || mesh.material == null).length;
|
|
791
|
+
if (stdMaterialCount == 0) {
|
|
792
|
+
break; // Buffer not used by StandardMaterials, so no conversion needed.
|
|
793
|
+
}
|
|
794
|
+
// TODO: Implement this case.
|
|
795
|
+
if (stdMaterialCount != meshes.length) {
|
|
796
|
+
Logger.Warn("Not converting vertex color space, as buffer is shared by StandardMaterials and other material types. Results may look incorrect.");
|
|
797
|
+
break;
|
|
798
|
+
}
|
|
799
|
+
if (type == VertexBuffer.UNSIGNED_BYTE) {
|
|
800
|
+
Logger.Warn("Converting uint8 vertex colors to linear space. Results may look incorrect.");
|
|
801
|
+
}
|
|
802
|
+
const vertexData3 = new Color3();
|
|
803
|
+
const vertexData4 = new Color4();
|
|
804
|
+
const useExactSrgbConversions = this._babylonScene.getEngine().useExactSrgbConversions;
|
|
805
|
+
EnumerateFloatValues(bytes, byteOffset, byteStride, componentCount, type, count, normalized, (values) => {
|
|
806
|
+
// Using separate Color3 and Color4 objects to ensure the right functions are called.
|
|
807
|
+
if (values.length === 3) {
|
|
808
|
+
vertexData3.fromArray(values, 0);
|
|
809
|
+
vertexData3.toLinearSpaceToRef(vertexData3, useExactSrgbConversions);
|
|
810
|
+
vertexData3.toArray(values, 0);
|
|
811
|
+
}
|
|
812
|
+
else {
|
|
813
|
+
vertexData4.fromArray(values, 0);
|
|
814
|
+
vertexData4.toLinearSpaceToRef(vertexData4, useExactSrgbConversions);
|
|
815
|
+
vertexData4.toArray(values, 0);
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
// Perform coordinate conversions, if needed, to buffer data in-place (only for positions, normals and tangents).
|
|
822
|
+
if (state.convertToRightHanded) {
|
|
823
|
+
for (const vertexBuffer of vertexBuffers) {
|
|
824
|
+
const meshes = vertexBufferToMeshesMap.get(vertexBuffer);
|
|
825
|
+
const { byteOffset, byteStride, componentCount, type, count, normalized, kind } = GetVertexBufferInfo(vertexBuffer, meshes);
|
|
826
|
+
switch (kind) {
|
|
827
|
+
case VertexBuffer.PositionKind:
|
|
828
|
+
case VertexBuffer.NormalKind:
|
|
829
|
+
case VertexBuffer.TangentKind: {
|
|
830
|
+
EnumerateFloatValues(bytes, byteOffset, byteStride, componentCount, type, count, normalized, (values) => {
|
|
831
|
+
values[0] = -values[0];
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
// Save converted bytes for min/max computation.
|
|
837
|
+
state.convertedToRightHandedBuffers.set(buffer, bytes);
|
|
838
|
+
}
|
|
839
|
+
// Create buffer view, but defer accessor creation for later. Instead, track it via ExporterState.
|
|
840
|
+
const bufferView = this._bufferManager.createBufferView(bytes, byteStride);
|
|
841
|
+
state.setVertexBufferView(buffer, bufferView);
|
|
842
|
+
const floatMatricesIndices = new Map();
|
|
843
|
+
// If buffers are of type MatricesIndicesKind and have float values, we need to create a new buffer instead.
|
|
844
|
+
for (const vertexBuffer of vertexBuffers) {
|
|
845
|
+
const meshes = vertexBufferToMeshesMap.get(vertexBuffer);
|
|
846
|
+
const { kind, totalVertices } = GetVertexBufferInfo(vertexBuffer, meshes);
|
|
847
|
+
switch (kind) {
|
|
848
|
+
case VertexBuffer.MatricesIndicesKind:
|
|
849
|
+
case VertexBuffer.MatricesIndicesExtraKind: {
|
|
850
|
+
if (vertexBuffer.type == VertexBuffer.FLOAT) {
|
|
851
|
+
const floatData = vertexBuffer.getFloatData(totalVertices);
|
|
852
|
+
if (floatData !== null) {
|
|
853
|
+
floatMatricesIndices.set(vertexBuffer, floatData);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (floatMatricesIndices.size !== 0) {
|
|
860
|
+
Logger.Warn(`Joint indices conversion needed: some joint indices are stored as floats in Babylon but GLTF requires UNSIGNED BYTES. We will perform the conversion but this might lead to unused data in the buffer.`);
|
|
861
|
+
}
|
|
862
|
+
const floatArrayVertexBuffers = Array.from(floatMatricesIndices.keys());
|
|
863
|
+
for (const vertexBuffer of floatArrayVertexBuffers) {
|
|
864
|
+
const array = floatMatricesIndices.get(vertexBuffer);
|
|
865
|
+
if (!array) {
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
868
|
+
const is16Bit = FloatsNeed16BitInteger(array);
|
|
869
|
+
const newArray = new (is16Bit ? Uint16Array : Uint8Array)(array.length);
|
|
870
|
+
for (let index = 0; index < array.length; index++) {
|
|
871
|
+
newArray[index] = array[index];
|
|
872
|
+
}
|
|
873
|
+
const bufferView = this._bufferManager.createBufferView(newArray, 4 * (is16Bit ? 2 : 1));
|
|
874
|
+
state.setRemappedBufferView(buffer, vertexBuffer, bufferView);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
// Build morph targets buffers
|
|
878
|
+
const morphTargets = Array.from(morphTargetsMeshesMap.keys());
|
|
879
|
+
for (const morphTarget of morphTargets) {
|
|
880
|
+
const meshes = morphTargetsMeshesMap.get(morphTarget);
|
|
881
|
+
if (!meshes) {
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
const glTFMorphTarget = BuildMorphTargetBuffers(morphTarget, meshes[0], this._bufferManager, this._bufferViews, this._accessors, state.convertToRightHanded);
|
|
885
|
+
for (const mesh of meshes) {
|
|
886
|
+
state.bindMorphDataToMesh(mesh, glTFMorphTarget);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Processes a node to be exported to the glTF file
|
|
892
|
+
* @returns A promise that resolves once the node has been exported
|
|
893
|
+
* @internal
|
|
894
|
+
*/
|
|
895
|
+
async _exportNodeAsync(babylonNode, parentNodeChildren, state) {
|
|
896
|
+
let nodeIndex = this._nodeMap.get(babylonNode);
|
|
897
|
+
if (nodeIndex !== undefined) {
|
|
898
|
+
if (!parentNodeChildren.includes(nodeIndex)) {
|
|
899
|
+
parentNodeChildren.push(nodeIndex);
|
|
900
|
+
}
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
const node = await this._createNodeAsync(babylonNode, state);
|
|
904
|
+
if (node) {
|
|
905
|
+
nodeIndex = this._nodes.length;
|
|
906
|
+
this._nodes.push(node);
|
|
907
|
+
this._nodeMap.set(babylonNode, nodeIndex);
|
|
908
|
+
state.pushExportedNode(babylonNode);
|
|
909
|
+
parentNodeChildren.push(nodeIndex);
|
|
910
|
+
// Process node's animations once the node has been added to nodeMap (TODO: This should be refactored)
|
|
911
|
+
const runtimeGLTFAnimation = {
|
|
912
|
+
name: "runtime animations",
|
|
913
|
+
channels: [],
|
|
914
|
+
samplers: [],
|
|
915
|
+
};
|
|
916
|
+
const idleGLTFAnimations = [];
|
|
917
|
+
if (!this._babylonScene.animationGroups.length) {
|
|
918
|
+
_GLTFAnimation._CreateMorphTargetAnimationFromMorphTargetAnimations(babylonNode, runtimeGLTFAnimation, idleGLTFAnimations, this._nodeMap, this._nodes, this._bufferManager, this._bufferViews, this._accessors, this._animationSampleRate, state.convertToRightHanded, this._options.shouldExportAnimation);
|
|
919
|
+
if (babylonNode.animations.length) {
|
|
920
|
+
_GLTFAnimation._CreateNodeAnimationFromNodeAnimations(babylonNode, runtimeGLTFAnimation, idleGLTFAnimations, this._nodeMap, this._nodes, this._bufferManager, this._bufferViews, this._accessors, this._animationSampleRate, state.convertToRightHanded, this._options.shouldExportAnimation);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
if (runtimeGLTFAnimation.channels.length && runtimeGLTFAnimation.samplers.length) {
|
|
924
|
+
this._animations.push(runtimeGLTFAnimation);
|
|
925
|
+
}
|
|
926
|
+
idleGLTFAnimations.forEach((idleGLTFAnimation) => {
|
|
927
|
+
if (idleGLTFAnimation.channels.length && idleGLTFAnimation.samplers.length) {
|
|
928
|
+
this._animations.push(idleGLTFAnimation);
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
// Begin processing child nodes once parent has been added to the node list
|
|
933
|
+
const children = node ? [] : parentNodeChildren;
|
|
934
|
+
for (const babylonChildNode of babylonNode.getChildren()) {
|
|
935
|
+
// eslint-disable-next-line no-await-in-loop
|
|
936
|
+
await this._exportNodeAsync(babylonChildNode, children, state);
|
|
937
|
+
}
|
|
938
|
+
if (node && children.length) {
|
|
939
|
+
node.children = children;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Creates a glTF node from a Babylon.js node. If skipped, returns null.
|
|
944
|
+
* @internal
|
|
945
|
+
*/
|
|
946
|
+
async _createNodeAsync(babylonNode, state) {
|
|
947
|
+
if (!this._shouldExportNode(babylonNode)) {
|
|
948
|
+
return null;
|
|
949
|
+
}
|
|
950
|
+
const node = {};
|
|
951
|
+
if (babylonNode.name) {
|
|
952
|
+
node.name = babylonNode.name;
|
|
953
|
+
}
|
|
954
|
+
// Node metadata
|
|
955
|
+
if (babylonNode.metadata) {
|
|
956
|
+
const extras = this._options.metadataSelector(babylonNode.metadata);
|
|
957
|
+
if (extras) {
|
|
958
|
+
node.extras = extras;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
if (babylonNode instanceof TransformNode) {
|
|
962
|
+
this._setNodeTransformation(node, babylonNode, state.convertToRightHanded);
|
|
963
|
+
if (babylonNode instanceof AbstractMesh) {
|
|
964
|
+
const babylonMesh = babylonNode instanceof InstancedMesh ? babylonNode.sourceMesh : babylonNode;
|
|
965
|
+
if (babylonMesh.subMeshes && babylonMesh.subMeshes.length > 0) {
|
|
966
|
+
node.mesh = await this._exportMeshAsync(babylonMesh, state);
|
|
967
|
+
}
|
|
968
|
+
if (babylonNode.skeleton) {
|
|
969
|
+
const skin = this._skinMap.get(babylonNode.skeleton);
|
|
970
|
+
if (skin !== undefined) {
|
|
971
|
+
if (this._nodesSkinMap.get(skin) === undefined) {
|
|
972
|
+
this._nodesSkinMap.set(skin, []);
|
|
973
|
+
}
|
|
974
|
+
this._nodesSkinMap.get(skin)?.push(node);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
if (babylonNode instanceof TargetCamera) {
|
|
980
|
+
const gltfCamera = this._camerasMap.get(babylonNode);
|
|
981
|
+
if (gltfCamera) {
|
|
982
|
+
if (this._nodesCameraMap.get(gltfCamera) === undefined) {
|
|
983
|
+
this._nodesCameraMap.set(gltfCamera, []);
|
|
984
|
+
}
|
|
985
|
+
this._setCameraTransformation(node, babylonNode, state.convertToRightHanded);
|
|
986
|
+
// If a parent node exists and can be collapsed, merge their transformations and mark the parent as the camera-containing node.
|
|
987
|
+
const parentBabylonNode = babylonNode.parent;
|
|
988
|
+
if (parentBabylonNode !== null && IsChildCollapsible(babylonNode, parentBabylonNode)) {
|
|
989
|
+
const parentNodeIndex = this._nodeMap.get(parentBabylonNode);
|
|
990
|
+
if (parentNodeIndex !== undefined) {
|
|
991
|
+
const parentNode = this._nodes[parentNodeIndex];
|
|
992
|
+
CollapseChildIntoParent(node, parentNode);
|
|
993
|
+
this._nodesCameraMap.get(gltfCamera)?.push(parentNode);
|
|
994
|
+
return null; // Skip exporting the original child node
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
this._nodesCameraMap.get(gltfCamera)?.push(node);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
// Apply extensions to the node. If this resolves to null, it means we should skip exporting this node
|
|
1001
|
+
const processedNode = await this._extensionsPostExportNodeAsync("exportNodeAsync", node, babylonNode, this._nodeMap, state.convertToRightHanded);
|
|
1002
|
+
if (!processedNode) {
|
|
1003
|
+
Logger.Warn(`Not exporting node ${babylonNode.name}`);
|
|
1004
|
+
return null;
|
|
1005
|
+
}
|
|
1006
|
+
return node;
|
|
1007
|
+
}
|
|
1008
|
+
_exportIndices(indices, is32Bits, start, count, offset, fillMode, sideOrientation, state, primitive) {
|
|
1009
|
+
let indicesToExport = indices;
|
|
1010
|
+
primitive.mode = GetPrimitiveMode(fillMode);
|
|
1011
|
+
// Flip indices if triangle winding order is not CCW, as glTF is always CCW.
|
|
1012
|
+
const flip = sideOrientation !== Material.CounterClockWiseSideOrientation && IsTriangleFillMode(fillMode);
|
|
1013
|
+
if (flip) {
|
|
1014
|
+
if (fillMode === Material.TriangleStripDrawMode || fillMode === Material.TriangleFanDrawMode) {
|
|
1015
|
+
throw new Error("Triangle strip/fan fill mode is not implemented");
|
|
1016
|
+
}
|
|
1017
|
+
primitive.mode = GetPrimitiveMode(fillMode);
|
|
1018
|
+
const newIndices = is32Bits ? new Uint32Array(count) : new Uint16Array(count);
|
|
1019
|
+
if (indices) {
|
|
1020
|
+
for (let i = 0; i + 2 < count; i += 3) {
|
|
1021
|
+
newIndices[i] = indices[start + i] + offset;
|
|
1022
|
+
newIndices[i + 1] = indices[start + i + 2] + offset;
|
|
1023
|
+
newIndices[i + 2] = indices[start + i + 1] + offset;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
else {
|
|
1027
|
+
for (let i = 0; i + 2 < count; i += 3) {
|
|
1028
|
+
newIndices[i] = i;
|
|
1029
|
+
newIndices[i + 1] = i + 2;
|
|
1030
|
+
newIndices[i + 2] = i + 1;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
indicesToExport = newIndices;
|
|
1034
|
+
}
|
|
1035
|
+
else if (indices && offset !== 0) {
|
|
1036
|
+
const newIndices = is32Bits ? new Uint32Array(count) : new Uint16Array(count);
|
|
1037
|
+
for (let i = 0; i < count; i++) {
|
|
1038
|
+
newIndices[i] = indices[start + i] + offset;
|
|
1039
|
+
}
|
|
1040
|
+
indicesToExport = newIndices;
|
|
1041
|
+
}
|
|
1042
|
+
if (indicesToExport) {
|
|
1043
|
+
let accessorIndex = state.getIndicesAccessor(indices, start, count, offset, flip);
|
|
1044
|
+
if (accessorIndex === undefined) {
|
|
1045
|
+
const bytes = IndicesArrayToTypedSubarray(indicesToExport, start, count, is32Bits);
|
|
1046
|
+
const bufferView = this._bufferManager.createBufferView(bytes);
|
|
1047
|
+
const componentType = is32Bits ? 5125 /* AccessorComponentType.UNSIGNED_INT */ : 5123 /* AccessorComponentType.UNSIGNED_SHORT */;
|
|
1048
|
+
this._accessors.push(this._bufferManager.createAccessor(bufferView, "SCALAR" /* AccessorType.SCALAR */, componentType, count, 0));
|
|
1049
|
+
accessorIndex = this._accessors.length - 1;
|
|
1050
|
+
state.setIndicesAccessor(indices, start, count, offset, flip, accessorIndex);
|
|
1051
|
+
}
|
|
1052
|
+
primitive.indices = accessorIndex;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
_exportVertexBuffer(vertexBuffer, babylonMaterial, start, count, state, primitive) {
|
|
1056
|
+
const kind = vertexBuffer.getKind();
|
|
1057
|
+
if (!IsStandardVertexAttribute(kind)) {
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
if (kind.startsWith("uv") && !this._options.exportUnusedUVs) {
|
|
1061
|
+
if (!babylonMaterial || !this._materialNeedsUVsSet.has(babylonMaterial)) {
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
let accessorIndex = state.getVertexAccessor(vertexBuffer, start, count);
|
|
1066
|
+
if (accessorIndex === undefined) {
|
|
1067
|
+
// Get min/max from converted or original data.
|
|
1068
|
+
const data = state.convertedToRightHandedBuffers.get(vertexBuffer._buffer) || vertexBuffer._buffer.getData();
|
|
1069
|
+
const minMax = kind === VertexBuffer.PositionKind ? GetMinMax(data, vertexBuffer, start, count) : undefined;
|
|
1070
|
+
// For the remapped buffer views we created for float matrices indices, make sure to use their updated information.
|
|
1071
|
+
const isFloatMatricesIndices = (kind === VertexBuffer.MatricesIndicesKind || kind === VertexBuffer.MatricesIndicesExtraKind) && vertexBuffer.type === VertexBuffer.FLOAT;
|
|
1072
|
+
const vertexBufferType = isFloatMatricesIndices ? VertexBuffer.UNSIGNED_BYTE : vertexBuffer.type;
|
|
1073
|
+
const vertexBufferNormalized = isFloatMatricesIndices ? undefined : vertexBuffer.normalized;
|
|
1074
|
+
const bufferView = isFloatMatricesIndices ? state.getRemappedBufferView(vertexBuffer._buffer, vertexBuffer) : state.getVertexBufferView(vertexBuffer._buffer);
|
|
1075
|
+
const byteOffset = vertexBuffer.byteOffset + start * vertexBuffer.byteStride;
|
|
1076
|
+
this._accessors.push(this._bufferManager.createAccessor(bufferView, GetAccessorType(kind, state.hasVertexColorAlpha(vertexBuffer)), vertexBufferType, count, byteOffset, minMax, vertexBufferNormalized // TODO: Find other places where this is needed.
|
|
1077
|
+
));
|
|
1078
|
+
accessorIndex = this._accessors.length - 1;
|
|
1079
|
+
state.setVertexAccessor(vertexBuffer, start, count, accessorIndex);
|
|
1080
|
+
}
|
|
1081
|
+
primitive.attributes[GetAttributeType(kind)] = accessorIndex;
|
|
1082
|
+
}
|
|
1083
|
+
async _exportMaterialAsync(babylonMaterial, vertexBuffers, subMesh, primitive) {
|
|
1084
|
+
let materialIndex = this._materialMap.get(babylonMaterial);
|
|
1085
|
+
if (materialIndex === undefined) {
|
|
1086
|
+
const hasUVs = vertexBuffers && Object.keys(vertexBuffers).some((kind) => kind.startsWith("uv"));
|
|
1087
|
+
babylonMaterial = babylonMaterial instanceof MultiMaterial ? babylonMaterial.subMaterials[subMesh.materialIndex] : babylonMaterial;
|
|
1088
|
+
if (babylonMaterial instanceof PBRBaseMaterial) {
|
|
1089
|
+
materialIndex = await this._materialExporter.exportPBRMaterialAsync(babylonMaterial, hasUVs);
|
|
1090
|
+
}
|
|
1091
|
+
else if (babylonMaterial instanceof StandardMaterial) {
|
|
1092
|
+
materialIndex = await this._materialExporter.exportStandardMaterialAsync(babylonMaterial, hasUVs);
|
|
1093
|
+
}
|
|
1094
|
+
else if (babylonMaterial instanceof OpenPBRMaterial) {
|
|
1095
|
+
materialIndex = await this._materialExporter.exportOpenPBRMaterialAsync(babylonMaterial, hasUVs);
|
|
1096
|
+
}
|
|
1097
|
+
else {
|
|
1098
|
+
Logger.Warn(`Unsupported material '${babylonMaterial.name}' with type ${babylonMaterial.getClassName()}`);
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
this._materialMap.set(babylonMaterial, materialIndex);
|
|
1102
|
+
}
|
|
1103
|
+
primitive.material = materialIndex;
|
|
1104
|
+
}
|
|
1105
|
+
async _exportMeshAsync(babylonMesh, state) {
|
|
1106
|
+
let meshIndex = state.getMesh(babylonMesh);
|
|
1107
|
+
if (meshIndex !== undefined) {
|
|
1108
|
+
return meshIndex;
|
|
1109
|
+
}
|
|
1110
|
+
const mesh = { primitives: [] };
|
|
1111
|
+
meshIndex = this._meshes.length;
|
|
1112
|
+
this._meshes.push(mesh);
|
|
1113
|
+
state.setMesh(babylonMesh, meshIndex);
|
|
1114
|
+
const indices = babylonMesh.isUnIndexed ? null : babylonMesh.getIndices();
|
|
1115
|
+
const vertexBuffers = babylonMesh.geometry?.getVertexBuffers();
|
|
1116
|
+
const morphTargets = state.getMorphTargetsFromMesh(babylonMesh);
|
|
1117
|
+
const isLinesMesh = babylonMesh instanceof LinesMesh;
|
|
1118
|
+
const isGreasedLineMesh = babylonMesh instanceof GreasedLineBaseMesh;
|
|
1119
|
+
const subMeshes = babylonMesh.subMeshes;
|
|
1120
|
+
if (vertexBuffers && subMeshes && subMeshes.length > 0) {
|
|
1121
|
+
for (const subMesh of subMeshes) {
|
|
1122
|
+
const primitive = { attributes: {} };
|
|
1123
|
+
const babylonMaterial = subMesh.getMaterial() || this._babylonScene.defaultMaterial;
|
|
1124
|
+
if (isGreasedLineMesh) {
|
|
1125
|
+
const material = {
|
|
1126
|
+
name: babylonMaterial.name,
|
|
1127
|
+
};
|
|
1128
|
+
const babylonLinesMesh = babylonMesh;
|
|
1129
|
+
const colorWhite = Color3.White();
|
|
1130
|
+
const alpha = babylonLinesMesh.material?.alpha ?? 1;
|
|
1131
|
+
const color = babylonLinesMesh.greasedLineMaterial?.color ?? colorWhite;
|
|
1132
|
+
if (!color.equalsWithEpsilon(colorWhite, Epsilon) || alpha < 1) {
|
|
1133
|
+
material.pbrMetallicRoughness = {
|
|
1134
|
+
baseColorFactor: [...color.asArray(), alpha],
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
this._materials.push(material);
|
|
1138
|
+
primitive.material = this._materials.length - 1;
|
|
1139
|
+
}
|
|
1140
|
+
else if (isLinesMesh) {
|
|
1141
|
+
// Special case for LinesMesh
|
|
1142
|
+
const material = {
|
|
1143
|
+
name: babylonMaterial.name,
|
|
1144
|
+
};
|
|
1145
|
+
const babylonLinesMesh = babylonMesh;
|
|
1146
|
+
if (!babylonLinesMesh.color.equalsWithEpsilon(Color3.White(), Epsilon) || babylonLinesMesh.alpha < 1) {
|
|
1147
|
+
material.pbrMetallicRoughness = {
|
|
1148
|
+
baseColorFactor: [...babylonLinesMesh.color.asArray(), babylonLinesMesh.alpha],
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
this._materials.push(material);
|
|
1152
|
+
primitive.material = this._materials.length - 1;
|
|
1153
|
+
}
|
|
1154
|
+
else {
|
|
1155
|
+
// Material
|
|
1156
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1157
|
+
await this._exportMaterialAsync(babylonMaterial, vertexBuffers, subMesh, primitive);
|
|
1158
|
+
}
|
|
1159
|
+
// Index buffer
|
|
1160
|
+
const fillMode = isLinesMesh || isGreasedLineMesh ? Material.LineListDrawMode : (babylonMesh.overrideRenderingFillMode ?? babylonMaterial.fillMode);
|
|
1161
|
+
let sideOrientation = babylonMaterial._getEffectiveOrientation(babylonMesh);
|
|
1162
|
+
if (state.wasAddedByNoopNode && !babylonMesh.getScene().useRightHandedSystem) {
|
|
1163
|
+
// To properly remove a conversion node, we must also cancel out the implicit flip in its children's side orientations.
|
|
1164
|
+
sideOrientation = sideOrientation === Material.ClockWiseSideOrientation ? Material.CounterClockWiseSideOrientation : Material.ClockWiseSideOrientation;
|
|
1165
|
+
}
|
|
1166
|
+
this._exportIndices(indices, indices ? AreIndices32Bits(indices, subMesh.indexCount, subMesh.indexStart, subMesh.verticesStart) : subMesh.verticesCount > 65535, indices ? subMesh.indexStart : subMesh.verticesStart, indices ? subMesh.indexCount : subMesh.verticesCount, -subMesh.verticesStart, fillMode, sideOrientation, state, primitive);
|
|
1167
|
+
// Vertex buffers
|
|
1168
|
+
for (const vertexBuffer of Object.values(vertexBuffers)) {
|
|
1169
|
+
this._exportVertexBuffer(vertexBuffer, babylonMaterial, subMesh.verticesStart, subMesh.verticesCount, state, primitive);
|
|
1170
|
+
}
|
|
1171
|
+
if (morphTargets) {
|
|
1172
|
+
primitive.targets = [];
|
|
1173
|
+
for (const gltfMorphTarget of morphTargets) {
|
|
1174
|
+
primitive.targets.push(gltfMorphTarget.attributes);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
mesh.primitives.push(primitive);
|
|
1178
|
+
this._extensionsPostExportMeshPrimitive(primitive);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
if (morphTargets) {
|
|
1182
|
+
mesh.weights = [];
|
|
1183
|
+
if (!mesh.extras) {
|
|
1184
|
+
mesh.extras = {};
|
|
1185
|
+
}
|
|
1186
|
+
mesh.extras.targetNames = [];
|
|
1187
|
+
for (const gltfMorphTarget of morphTargets) {
|
|
1188
|
+
mesh.weights.push(gltfMorphTarget.influence);
|
|
1189
|
+
mesh.extras.targetNames.push(gltfMorphTarget.name);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
return meshIndex;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
GLTFExporter._ExtensionNames = new Array();
|
|
1196
|
+
GLTFExporter._ExtensionFactories = {};
|
|
1197
|
+
GLTFExporter._ExtensionOrders = {};
|
|
1198
|
+
//# sourceMappingURL=glTFExporter2.js.map
|