Coverage Report

Created: 2025-08-28 06:38

/src/assimp/code/AssetLib/glTF2/glTF2Exporter.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
Open Asset Import Library (assimp)
3
----------------------------------------------------------------------
4
5
Copyright (c) 2006-2025, assimp team
6
7
All rights reserved.
8
9
Redistribution and use of this software in source and binary forms,
10
with or without modification, are permitted provided that the
11
following conditions are met:
12
13
* Redistributions of source code must retain the above
14
copyright notice, this list of conditions and the
15
following disclaimer.
16
17
* Redistributions in binary form must reproduce the above
18
copyright notice, this list of conditions and the
19
following disclaimer in the documentation and/or other
20
materials provided with the distribution.
21
22
* Neither the name of the assimp team, nor the names of its
23
contributors may be used to endorse or promote products
24
derived from this software without specific prior
25
written permission of the assimp team.
26
27
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
39
----------------------------------------------------------------------
40
*/
41
#ifndef ASSIMP_BUILD_NO_EXPORT
42
#ifndef ASSIMP_BUILD_NO_GLTF_EXPORTER
43
44
#include "AssetLib/glTF2/glTF2Exporter.h"
45
#include "AssetLib/glTF2/glTF2AssetWriter.h"
46
#include "PostProcessing/SplitLargeMeshes.h"
47
48
#include <assimp/ByteSwapper.h>
49
#include <assimp/Exceptional.h>
50
#include <assimp/SceneCombiner.h>
51
#include <assimp/StringComparison.h>
52
#include <assimp/commonMetaData.h>
53
#include <assimp/material.h>
54
#include <assimp/scene.h>
55
#include <assimp/version.h>
56
#include <assimp/Exporter.hpp>
57
#include <assimp/IOSystem.hpp>
58
#include <assimp/config.h>
59
60
// Header files, standard library.
61
#include <cinttypes>
62
#include <limits>
63
#include <memory>
64
#include <iostream>
65
66
using namespace rapidjson;
67
68
using namespace Assimp;
69
using namespace glTF2;
70
71
namespace Assimp {
72
73
// ------------------------------------------------------------------------------------------------
74
// Worker function for exporting a scene to GLTF. Prototyped and registered in Exporter.cpp
75
0
void ExportSceneGLTF2(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties *pProperties) {
76
    // invoke the exporter
77
0
    glTF2Exporter exporter(pFile, pIOSystem, pScene, pProperties, false);
78
0
}
79
80
// ------------------------------------------------------------------------------------------------
81
// Worker function for exporting a scene to GLB. Prototyped and registered in Exporter.cpp
82
0
void ExportSceneGLB2(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties *pProperties) {
83
    // invoke the exporter
84
0
    glTF2Exporter exporter(pFile, pIOSystem, pScene, pProperties, true);
85
0
}
86
87
} // end of namespace Assimp
88
89
glTF2Exporter::glTF2Exporter(const char *filename, IOSystem *pIOSystem, const aiScene *pScene,
90
        const ExportProperties *pProperties, bool isBinary) :
91
0
        mFilename(filename), mIOSystem(pIOSystem), mScene(pScene), mProperties(pProperties), mAsset(new Asset(pIOSystem)) {
92
    // Always on as our triangulation process is aware of this type of encoding
93
0
    mAsset->extensionsUsed.FB_ngon_encoding = true;
94
95
0
    configEpsilon = mProperties->GetPropertyFloat(
96
0
            AI_CONFIG_CHECK_IDENTITY_MATRIX_EPSILON,
97
0
                    (ai_real)AI_CONFIG_CHECK_IDENTITY_MATRIX_EPSILON_DEFAULT);
98
99
0
    if (isBinary) {
100
0
        mAsset->SetAsBinary();
101
0
    }
102
103
0
    ExportMetadata();
104
105
0
    ExportMaterials();
106
107
0
    if (mScene->mRootNode) {
108
0
        ExportNodeHierarchy(mScene->mRootNode);
109
0
    }
110
111
0
    ExportMeshes();
112
0
    MergeMeshes();
113
114
0
    ExportScene();
115
116
0
    ExportAnimations();
117
118
    // export extras
119
0
    if (mProperties->HasPropertyCallback("extras")) {
120
0
        std::function<void *(void *)> ExportExtras = mProperties->GetPropertyCallback("extras");
121
0
        mAsset->extras = (rapidjson::Value *)ExportExtras(0);
122
0
    }
123
124
0
    AssetWriter writer(*mAsset);
125
126
0
    if (isBinary) {
127
0
        writer.WriteGLBFile(filename);
128
0
    } else {
129
0
        writer.WriteFile(filename);
130
0
    }
131
0
}
132
133
0
glTF2Exporter::~glTF2Exporter() = default;
134
135
/*
136
 * Copy a 4x4 matrix from struct aiMatrix to typedef mat4.
137
 * Also converts from row-major to column-major storage.
138
 */
139
0
static void CopyValue(const aiMatrix4x4 &v, mat4 &o) {
140
0
    o[0] = v.a1;
141
0
    o[1] = v.b1;
142
0
    o[2] = v.c1;
143
0
    o[3] = v.d1;
144
0
    o[4] = v.a2;
145
0
    o[5] = v.b2;
146
0
    o[6] = v.c2;
147
0
    o[7] = v.d2;
148
0
    o[8] = v.a3;
149
0
    o[9] = v.b3;
150
0
    o[10] = v.c3;
151
0
    o[11] = v.d3;
152
0
    o[12] = v.a4;
153
0
    o[13] = v.b4;
154
0
    o[14] = v.c4;
155
0
    o[15] = v.d4;
156
0
}
157
158
0
static void CopyValue(const aiMatrix4x4 &v, aiMatrix4x4 &o) {
159
0
    memcpy(&o, &v, sizeof(aiMatrix4x4));
160
0
}
161
162
0
static void IdentityMatrix4(mat4 &o) {
163
0
    o[0] = 1;
164
0
    o[1] = 0;
165
0
    o[2] = 0;
166
0
    o[3] = 0;
167
0
    o[4] = 0;
168
0
    o[5] = 1;
169
0
    o[6] = 0;
170
0
    o[7] = 0;
171
0
    o[8] = 0;
172
0
    o[9] = 0;
173
0
    o[10] = 1;
174
0
    o[11] = 0;
175
0
    o[12] = 0;
176
0
    o[13] = 0;
177
0
    o[14] = 0;
178
0
    o[15] = 1;
179
0
}
180
181
template <typename T>
182
void SetAccessorRange(Ref<Accessor> acc, void *data, size_t count,
183
0
        unsigned int numCompsIn, unsigned int numCompsOut) {
184
0
    ai_assert(numCompsOut <= numCompsIn);
185
186
    // Allocate and initialize with large values.
187
0
    for (unsigned int i = 0; i < numCompsOut; i++) {
188
0
        acc->min.push_back(std::numeric_limits<double>::min());
189
0
        acc->max.push_back(-std::numeric_limits<double>::max());
190
0
    }
191
192
0
    size_t totalComps = count * numCompsIn;
193
0
    T *buffer_ptr = static_cast<T *>(data);
194
0
    T *buffer_end = buffer_ptr + totalComps;
195
196
    // Search and set extreme values.
197
0
    for (; buffer_ptr < buffer_end; buffer_ptr += numCompsIn) {
198
0
        for (unsigned int j = 0; j < numCompsOut; j++) {
199
0
            double valueTmp = buffer_ptr[j];
200
201
            // Gracefully tolerate rogue NaN's in buffer data
202
            // Any NaNs/Infs introduced in accessor bounds will end up in
203
            // document and prevent rapidjson from writing out valid JSON
204
0
            if (!std::isfinite(valueTmp)) {
205
0
                continue;
206
0
            }
207
208
0
            if (valueTmp < acc->min[j]) {
209
0
                acc->min[j] = valueTmp;
210
0
            }
211
0
            if (valueTmp > acc->max[j]) {
212
0
                acc->max[j] = valueTmp;
213
0
            }
214
0
        }
215
0
    }
216
0
}
Unexecuted instantiation: void SetAccessorRange<short>(glTFCommon::Ref<glTF2::Accessor>, void*, unsigned long, unsigned int, unsigned int)
Unexecuted instantiation: void SetAccessorRange<unsigned short>(glTFCommon::Ref<glTF2::Accessor>, void*, unsigned long, unsigned int, unsigned int)
Unexecuted instantiation: void SetAccessorRange<unsigned int>(glTFCommon::Ref<glTF2::Accessor>, void*, unsigned long, unsigned int, unsigned int)
Unexecuted instantiation: void SetAccessorRange<float>(glTFCommon::Ref<glTF2::Accessor>, void*, unsigned long, unsigned int, unsigned int)
Unexecuted instantiation: void SetAccessorRange<signed char>(glTFCommon::Ref<glTF2::Accessor>, void*, unsigned long, unsigned int, unsigned int)
Unexecuted instantiation: void SetAccessorRange<unsigned char>(glTFCommon::Ref<glTF2::Accessor>, void*, unsigned long, unsigned int, unsigned int)
217
218
inline void SetAccessorRange(ComponentType compType, Ref<Accessor> acc, void *data,
219
0
        size_t count, unsigned int numCompsIn, unsigned int numCompsOut) {
220
0
    switch (compType) {
221
0
    case ComponentType_SHORT:
222
0
        SetAccessorRange<short>(acc, data, count, numCompsIn, numCompsOut);
223
0
        return;
224
0
    case ComponentType_UNSIGNED_SHORT:
225
0
        SetAccessorRange<unsigned short>(acc, data, count, numCompsIn, numCompsOut);
226
0
        return;
227
0
    case ComponentType_UNSIGNED_INT:
228
0
        SetAccessorRange<unsigned int>(acc, data, count, numCompsIn, numCompsOut);
229
0
        return;
230
0
    case ComponentType_FLOAT:
231
0
        SetAccessorRange<float>(acc, data, count, numCompsIn, numCompsOut);
232
0
        return;
233
0
    case ComponentType_BYTE:
234
0
        SetAccessorRange<int8_t>(acc, data, count, numCompsIn, numCompsOut);
235
0
        return;
236
0
    case ComponentType_UNSIGNED_BYTE:
237
0
        SetAccessorRange<uint8_t>(acc, data, count, numCompsIn, numCompsOut);
238
0
        return;
239
0
    }
240
0
}
241
242
// compute the (data-dataBase), store the non-zero data items
243
template <typename T>
244
0
size_t NZDiff(void *data, void *dataBase, size_t count, unsigned int numCompsIn, unsigned int numCompsOut, void *&outputNZDiff, void *&outputNZIdx) {
245
0
    std::vector<T> vNZDiff;
246
0
    std::vector<unsigned short> vNZIdx;
247
0
    size_t totalComps = count * numCompsIn;
248
0
    T *bufferData_ptr = static_cast<T *>(data);
249
0
    T *bufferData_end = bufferData_ptr + totalComps;
250
0
    T *bufferBase_ptr = static_cast<T *>(dataBase);
251
252
    // Search and set extreme values.
253
0
    for (short idx = 0; bufferData_ptr < bufferData_end; idx += 1, bufferData_ptr += numCompsIn) {
254
0
        bool bNonZero = false;
255
256
        // for the data, check any component Non Zero
257
0
        for (unsigned int j = 0; j < numCompsOut; j++) {
258
0
            double valueData = bufferData_ptr[j];
259
0
            double valueBase = bufferBase_ptr ? bufferBase_ptr[j] : 0;
260
0
            if ((valueData - valueBase) != 0) {
261
0
                bNonZero = true;
262
0
                break;
263
0
            }
264
0
        }
265
266
        // all zeros, continue
267
0
        if (!bNonZero)
268
0
            continue;
269
270
        // non zero, store the data
271
0
        for (unsigned int j = 0; j < numCompsOut; j++) {
272
0
            T valueData = bufferData_ptr[j];
273
0
            T valueBase = bufferBase_ptr ? bufferBase_ptr[j] : 0;
274
0
            vNZDiff.push_back(valueData - valueBase);
275
0
        }
276
0
        vNZIdx.push_back(idx);
277
0
    }
278
279
    // avoid all-0, put 1 item
280
0
    if (vNZDiff.size() == 0) {
281
0
        for (unsigned int j = 0; j < numCompsOut; j++)
282
0
            vNZDiff.push_back(0);
283
0
        vNZIdx.push_back(0);
284
0
    }
285
286
    // process data
287
0
    outputNZDiff = new T[vNZDiff.size()];
288
0
    memcpy(outputNZDiff, vNZDiff.data(), vNZDiff.size() * sizeof(T));
289
290
0
    outputNZIdx = new unsigned short[vNZIdx.size()];
291
0
    memcpy(outputNZIdx, vNZIdx.data(), vNZIdx.size() * sizeof(unsigned short));
292
0
    return vNZIdx.size();
293
0
}
Unexecuted instantiation: unsigned long NZDiff<short>(void*, void*, unsigned long, unsigned int, unsigned int, void*&, void*&)
Unexecuted instantiation: unsigned long NZDiff<unsigned short>(void*, void*, unsigned long, unsigned int, unsigned int, void*&, void*&)
Unexecuted instantiation: unsigned long NZDiff<unsigned int>(void*, void*, unsigned long, unsigned int, unsigned int, void*&, void*&)
Unexecuted instantiation: unsigned long NZDiff<float>(void*, void*, unsigned long, unsigned int, unsigned int, void*&, void*&)
Unexecuted instantiation: unsigned long NZDiff<signed char>(void*, void*, unsigned long, unsigned int, unsigned int, void*&, void*&)
Unexecuted instantiation: unsigned long NZDiff<unsigned char>(void*, void*, unsigned long, unsigned int, unsigned int, void*&, void*&)
294
295
0
inline size_t NZDiff(ComponentType compType, void *data, void *dataBase, size_t count, unsigned int numCompsIn, unsigned int numCompsOut, void *&nzDiff, void *&nzIdx) {
296
0
    switch (compType) {
297
0
    case ComponentType_SHORT:
298
0
        return NZDiff<short>(data, dataBase, count, numCompsIn, numCompsOut, nzDiff, nzIdx);
299
0
    case ComponentType_UNSIGNED_SHORT:
300
0
        return NZDiff<unsigned short>(data, dataBase, count, numCompsIn, numCompsOut, nzDiff, nzIdx);
301
0
    case ComponentType_UNSIGNED_INT:
302
0
        return NZDiff<unsigned int>(data, dataBase, count, numCompsIn, numCompsOut, nzDiff, nzIdx);
303
0
    case ComponentType_FLOAT:
304
0
        return NZDiff<float>(data, dataBase, count, numCompsIn, numCompsOut, nzDiff, nzIdx);
305
0
    case ComponentType_BYTE:
306
0
        return NZDiff<int8_t>(data, dataBase, count, numCompsIn, numCompsOut, nzDiff, nzIdx);
307
0
    case ComponentType_UNSIGNED_BYTE:
308
0
        return NZDiff<uint8_t>(data, dataBase, count, numCompsIn, numCompsOut, nzDiff, nzIdx);
309
0
    }
310
0
    return 0;
311
0
}
312
313
inline Ref<Accessor> ExportDataSparse(Asset &a, std::string &meshName, Ref<Buffer> &buffer,
314
0
        size_t count, void *data, AttribType::Value typeIn, AttribType::Value typeOut, ComponentType compType, BufferViewTarget target = BufferViewTarget_NONE, void *dataBase = nullptr) {
315
0
    if (!count || !data) {
316
0
        return Ref<Accessor>();
317
0
    }
318
319
0
    unsigned int numCompsIn = AttribType::GetNumComponents(typeIn);
320
0
    unsigned int numCompsOut = AttribType::GetNumComponents(typeOut);
321
0
    unsigned int bytesPerComp = ComponentTypeSize(compType);
322
323
    // accessor
324
0
    Ref<Accessor> acc = a.accessors.Create(a.FindUniqueID(meshName, "accessor"));
325
326
    // if there is a basic data vector
327
0
    if (dataBase) {
328
0
        size_t base_offset = buffer->byteLength;
329
0
        size_t base_padding = base_offset % bytesPerComp;
330
0
        base_offset += base_padding;
331
0
        size_t base_length = count * numCompsOut * bytesPerComp;
332
0
        buffer->Grow(base_length + base_padding);
333
334
0
        Ref<BufferView> bv = a.bufferViews.Create(a.FindUniqueID(meshName, "view"));
335
0
        bv->buffer = buffer;
336
0
        bv->byteOffset = base_offset;
337
0
        bv->byteLength = base_length; //! The target that the WebGL buffer should be bound to.
338
0
        bv->byteStride = 0;
339
0
        bv->target = target;
340
0
        acc->bufferView = bv;
341
0
        acc->WriteData(count, dataBase, numCompsIn * bytesPerComp);
342
0
    }
343
0
    acc->byteOffset = 0;
344
0
    acc->componentType = compType;
345
0
    acc->count = count;
346
0
    acc->type = typeOut;
347
348
0
    if (data) {
349
0
        void *nzDiff = nullptr, *nzIdx = nullptr;
350
0
        size_t nzCount = NZDiff(compType, data, dataBase, count, numCompsIn, numCompsOut, nzDiff, nzIdx);
351
0
        acc->sparse.reset(new Accessor::Sparse);
352
0
        acc->sparse->count = nzCount;
353
354
        // indices
355
0
        unsigned int bytesPerIdx = sizeof(unsigned short);
356
0
        size_t indices_offset = buffer->byteLength;
357
0
        size_t indices_padding = indices_offset % bytesPerIdx;
358
0
        indices_offset += indices_padding;
359
0
        size_t indices_length = nzCount * 1 * bytesPerIdx;
360
0
        buffer->Grow(indices_length + indices_padding);
361
362
0
        Ref<BufferView> indicesBV = a.bufferViews.Create(a.FindUniqueID(meshName, "view"));
363
0
        indicesBV->buffer = buffer;
364
0
        indicesBV->byteOffset = indices_offset;
365
0
        indicesBV->byteLength = indices_length;
366
0
        indicesBV->byteStride = 0;
367
0
        acc->sparse->indices = indicesBV;
368
0
        acc->sparse->indicesType = ComponentType_UNSIGNED_SHORT;
369
0
        acc->sparse->indicesByteOffset = 0;
370
0
        acc->WriteSparseIndices(nzCount, nzIdx, 1 * bytesPerIdx);
371
372
        // values
373
0
        size_t values_offset = buffer->byteLength;
374
0
        size_t values_padding = values_offset % bytesPerComp;
375
0
        values_offset += values_padding;
376
0
        size_t values_length = nzCount * numCompsOut * bytesPerComp;
377
0
        buffer->Grow(values_length + values_padding);
378
379
0
        Ref<BufferView> valuesBV = a.bufferViews.Create(a.FindUniqueID(meshName, "view"));
380
0
        valuesBV->buffer = buffer;
381
0
        valuesBV->byteOffset = values_offset;
382
0
        valuesBV->byteLength = values_length;
383
0
        valuesBV->byteStride = 0;
384
0
        acc->sparse->values = valuesBV;
385
0
        acc->sparse->valuesByteOffset = 0;
386
0
        acc->WriteSparseValues(nzCount, nzDiff, numCompsIn * bytesPerComp);
387
388
        // clear
389
0
        delete[] (char *)nzDiff;
390
0
        delete[] (char *)nzIdx;
391
0
    }
392
0
    return acc;
393
0
}
394
inline Ref<Accessor> ExportData(Asset &a, std::string &meshName, Ref<Buffer> &buffer,
395
0
        size_t count, void *data, AttribType::Value typeIn, AttribType::Value typeOut, ComponentType compType, BufferViewTarget target = BufferViewTarget_NONE) {
396
0
    if (!count || !data) {
397
0
        return Ref<Accessor>();
398
0
    }
399
400
0
    unsigned int numCompsIn = AttribType::GetNumComponents(typeIn);
401
0
    unsigned int numCompsOut = AttribType::GetNumComponents(typeOut);
402
0
    unsigned int bytesPerComp = ComponentTypeSize(compType);
403
404
0
    size_t offset = buffer->byteLength;
405
    // make sure offset is correctly byte-aligned, as required by spec
406
0
    size_t padding = offset % bytesPerComp;
407
0
    offset += padding;
408
0
    size_t length = count * numCompsOut * bytesPerComp;
409
0
    buffer->Grow(length + padding);
410
411
    // bufferView
412
0
    Ref<BufferView> bv = a.bufferViews.Create(a.FindUniqueID(meshName, "view"));
413
0
    bv->buffer = buffer;
414
0
    bv->byteOffset = offset;
415
0
    bv->byteLength = length; //! The target that the WebGL buffer should be bound to.
416
0
    bv->byteStride = 0;
417
0
    bv->target = target;
418
419
    // accessor
420
0
    Ref<Accessor> acc = a.accessors.Create(a.FindUniqueID(meshName, "accessor"));
421
0
    acc->bufferView = bv;
422
0
    acc->byteOffset = 0;
423
0
    acc->componentType = compType;
424
0
    acc->count = count;
425
0
    acc->type = typeOut;
426
427
    // calculate min and max values
428
0
    SetAccessorRange(compType, acc, data, count, numCompsIn, numCompsOut);
429
430
    // copy the data
431
0
    acc->WriteData(count, data, numCompsIn * bytesPerComp);
432
433
0
    return acc;
434
0
}
435
436
0
inline void ExportNodeExtras(const aiMetadataEntry &metadataEntry, aiString name, CustomExtension &value) {
437
438
0
    value.name = name.C_Str();
439
0
    switch (metadataEntry.mType) {
440
0
    case AI_BOOL:
441
0
        value.mBoolValue.value = *static_cast<bool *>(metadataEntry.mData);
442
0
        value.mBoolValue.isPresent = true;
443
0
        break;
444
0
    case AI_INT32:
445
0
        value.mInt64Value.value = *static_cast<int32_t *>(metadataEntry.mData);
446
0
        value.mInt64Value.isPresent = true;
447
0
        break;
448
0
    case AI_UINT64:
449
0
        value.mUint64Value.value = *static_cast<uint64_t *>(metadataEntry.mData);
450
0
        value.mUint64Value.isPresent = true;
451
0
        break;
452
0
    case AI_FLOAT:
453
0
        value.mDoubleValue.value = *static_cast<float *>(metadataEntry.mData);
454
0
        value.mDoubleValue.isPresent = true;
455
0
        break;
456
0
    case AI_DOUBLE:
457
0
        value.mDoubleValue.value = *static_cast<double *>(metadataEntry.mData);
458
0
        value.mDoubleValue.isPresent = true;
459
0
        break;
460
0
    case AI_AISTRING:
461
0
        value.mStringValue.value = static_cast<aiString *>(metadataEntry.mData)->C_Str();
462
0
        value.mStringValue.isPresent = true;
463
0
        break;
464
0
    case AI_AIMETADATA: {
465
0
        const aiMetadata *subMetadata = static_cast<aiMetadata *>(metadataEntry.mData);
466
0
        value.mValues.value.resize(subMetadata->mNumProperties);
467
0
        value.mValues.isPresent = true;
468
469
0
        for (unsigned i = 0; i < subMetadata->mNumProperties; ++i) {
470
0
            ExportNodeExtras(subMetadata->mValues[i], subMetadata->mKeys[i], value.mValues.value.at(i));
471
0
        }
472
0
        break;
473
0
    }
474
0
    default:
475
        // AI_AIVECTOR3D not handled
476
0
        break;
477
0
    }
478
0
}
479
480
0
inline void ExportNodeExtras(const aiMetadata *metadata, Extras &extras) {
481
0
    if (metadata == nullptr || metadata->mNumProperties == 0) {
482
0
        return;
483
0
    }
484
485
0
    extras.mValues.resize(metadata->mNumProperties);
486
0
    for (unsigned int i = 0; i < metadata->mNumProperties; ++i) {
487
0
        ExportNodeExtras(metadata->mValues[i], metadata->mKeys[i], extras.mValues.at(i));
488
0
    }
489
0
}
490
491
0
inline void SetSamplerWrap(SamplerWrap &wrap, aiTextureMapMode map) {
492
0
    switch (map) {
493
0
    case aiTextureMapMode_Clamp:
494
0
        wrap = SamplerWrap::Clamp_To_Edge;
495
0
        break;
496
0
    case aiTextureMapMode_Mirror:
497
0
        wrap = SamplerWrap::Mirrored_Repeat;
498
0
        break;
499
0
    case aiTextureMapMode_Wrap:
500
0
    case aiTextureMapMode_Decal:
501
0
    default:
502
0
        wrap = SamplerWrap::Repeat;
503
0
        break;
504
0
    };
505
0
}
506
507
0
void glTF2Exporter::GetTexSampler(const aiMaterial &mat, Ref<Texture> texture, aiTextureType tt, unsigned int slot) {
508
0
    aiString aId;
509
0
    std::string id;
510
0
    if (aiGetMaterialString(&mat, AI_MATKEY_GLTF_MAPPINGID(tt, slot), &aId) == AI_SUCCESS) {
511
0
        id = aId.C_Str();
512
0
    }
513
514
0
    if (Ref<Sampler> ref = mAsset->samplers.Get(id.c_str())) {
515
0
        texture->sampler = ref;
516
0
    } else {
517
0
        id = mAsset->FindUniqueID(id, "sampler");
518
519
0
        texture->sampler = mAsset->samplers.Create(id.c_str());
520
521
0
        aiTextureMapMode mapU, mapV;
522
0
        SamplerMagFilter filterMag;
523
0
        SamplerMinFilter filterMin;
524
525
0
        if (aiGetMaterialInteger(&mat, AI_MATKEY_MAPPINGMODE_U(tt, slot), (int *)&mapU) == AI_SUCCESS) {
526
0
            SetSamplerWrap(texture->sampler->wrapS, mapU);
527
0
        }
528
529
0
        if (aiGetMaterialInteger(&mat, AI_MATKEY_MAPPINGMODE_V(tt, slot), (int *)&mapV) == AI_SUCCESS) {
530
0
            SetSamplerWrap(texture->sampler->wrapT, mapV);
531
0
        }
532
533
0
        if (aiGetMaterialInteger(&mat, AI_MATKEY_GLTF_MAPPINGFILTER_MAG(tt, slot), (int *)&filterMag) == AI_SUCCESS) {
534
0
            texture->sampler->magFilter = filterMag;
535
0
        }
536
537
0
        if (aiGetMaterialInteger(&mat, AI_MATKEY_GLTF_MAPPINGFILTER_MIN(tt, slot), (int *)&filterMin) == AI_SUCCESS) {
538
0
            texture->sampler->minFilter = filterMin;
539
0
        }
540
541
0
        aiString name;
542
0
        if (aiGetMaterialString(&mat, AI_MATKEY_GLTF_MAPPINGNAME(tt, slot), &name) == AI_SUCCESS) {
543
0
            texture->sampler->name = name.C_Str();
544
0
        }
545
0
    }
546
0
}
547
548
0
void glTF2Exporter::GetMatTexProp(const aiMaterial &mat, unsigned int &prop, const char *propName, aiTextureType tt, unsigned int slot) {
549
0
    std::string textureKey = std::string(_AI_MATKEY_TEXTURE_BASE) + "." + propName;
550
551
0
    mat.Get(textureKey.c_str(), tt, slot, prop);
552
0
}
553
554
0
void glTF2Exporter::GetMatTexProp(const aiMaterial &mat, float &prop, const char *propName, aiTextureType tt, unsigned int slot) {
555
0
    std::string textureKey = std::string(_AI_MATKEY_TEXTURE_BASE) + "." + propName;
556
557
0
    mat.Get(textureKey.c_str(), tt, slot, prop);
558
0
}
559
560
0
void glTF2Exporter::GetMatTex(const aiMaterial &mat, Ref<Texture> &texture, unsigned int &texCoord, aiTextureType tt, unsigned int slot = 0) {
561
0
    if (mat.GetTextureCount(tt) == 0) {
562
0
        return;
563
0
    }
564
565
0
    aiString tex;
566
567
    // Read texcoord (UV map index)
568
    // Note: must be an int to be successful.
569
0
    int tmp = 0;
570
0
    const auto ok = mat.Get(AI_MATKEY_UVWSRC(tt, slot), tmp);
571
0
    if (ok == aiReturn_SUCCESS) texCoord = tmp;
572
573
574
0
    if (mat.Get(AI_MATKEY_TEXTURE(tt, slot), tex) == AI_SUCCESS) {
575
0
        std::string path = tex.C_Str();
576
577
0
        if (path.size() > 0) {
578
0
            std::map<std::string, unsigned int>::iterator it = mTexturesByPath.find(path);
579
0
            if (it != mTexturesByPath.end()) {
580
0
                texture = mAsset->textures.Get(it->second);
581
0
            }
582
583
0
            bool useBasisUniversal = false;
584
0
            if (!texture) {
585
0
                std::string texId = mAsset->FindUniqueID("", "texture");
586
0
                texture = mAsset->textures.Create(texId);
587
0
                mTexturesByPath[path] = texture.GetIndex();
588
589
0
                std::string imgId = mAsset->FindUniqueID("", "image");
590
0
                texture->source = mAsset->images.Create(imgId);
591
592
0
                const aiTexture *curTex = mScene->GetEmbeddedTexture(path.c_str());
593
0
                if (curTex != nullptr) { // embedded
594
0
                    texture->source->name = curTex->mFilename.C_Str();
595
596
                    // basisu: embedded ktx2, bu
597
0
                    if (curTex->achFormatHint[0]) {
598
0
                        std::string mimeType = "image/";
599
0
                        if (memcmp(curTex->achFormatHint, "jpg", 3) == 0)
600
0
                            mimeType += "jpeg";
601
0
                        else if (memcmp(curTex->achFormatHint, "ktx", 3) == 0) {
602
0
                            useBasisUniversal = true;
603
0
                            mimeType += "ktx";
604
0
                        } else if (memcmp(curTex->achFormatHint, "kx2", 3) == 0) {
605
0
                            useBasisUniversal = true;
606
0
                            mimeType += "ktx2";
607
0
                        } else if (memcmp(curTex->achFormatHint, "bu", 2) == 0) {
608
0
                            useBasisUniversal = true;
609
0
                            mimeType += "basis";
610
0
                        } else
611
0
                            mimeType += curTex->achFormatHint;
612
0
                        texture->source->mimeType = mimeType;
613
0
                    }
614
615
                    // The asset has its own buffer, see Image::SetData
616
                    // basisu: "image/ktx2", "image/basis" as is
617
0
                    texture->source->SetData(reinterpret_cast<uint8_t *>(curTex->pcData), curTex->mWidth, *mAsset);
618
0
                } else {
619
0
                    texture->source->uri = path;
620
0
                    if (texture->source->uri.find(".ktx") != std::string::npos ||
621
0
                            texture->source->uri.find(".basis") != std::string::npos) {
622
0
                        useBasisUniversal = true;
623
0
                    }
624
0
                }
625
626
                // basisu
627
0
                if (useBasisUniversal) {
628
0
                    mAsset->extensionsUsed.KHR_texture_basisu = true;
629
0
                    mAsset->extensionsRequired.KHR_texture_basisu = true;
630
0
                }
631
632
0
                GetTexSampler(mat, texture, tt, slot);
633
0
            }
634
0
        }
635
0
    }
636
0
}
637
638
0
void glTF2Exporter::GetMatTex(const aiMaterial &mat, TextureInfo &prop, aiTextureType tt, unsigned int slot = 0) {
639
0
    Ref<Texture> &texture = prop.texture;
640
0
    GetMatTex(mat, texture, prop.texCoord, tt, slot);
641
0
}
642
643
0
void glTF2Exporter::GetMatTex(const aiMaterial &mat, NormalTextureInfo &prop, aiTextureType tt, unsigned int slot = 0) {
644
0
    Ref<Texture> &texture = prop.texture;
645
646
0
    GetMatTex(mat, texture, prop.texCoord, tt, slot);
647
648
0
    if (texture) {
649
        // GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot);
650
0
        GetMatTexProp(mat, prop.scale, "scale", tt, slot);
651
0
    }
652
0
}
653
654
0
void glTF2Exporter::GetMatTex(const aiMaterial &mat, OcclusionTextureInfo &prop, aiTextureType tt, unsigned int slot = 0) {
655
0
    Ref<Texture> &texture = prop.texture;
656
657
0
    GetMatTex(mat, texture, prop.texCoord, tt, slot);
658
659
0
    if (texture) {
660
        // GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot);
661
0
        GetMatTexProp(mat, prop.strength, "strength", tt, slot);
662
0
    }
663
0
}
664
665
0
aiReturn glTF2Exporter::GetMatColor(const aiMaterial &mat, vec4 &prop, const char *propName, int type, int idx) const {
666
0
    aiColor4D col;
667
0
    aiReturn result = mat.Get(propName, type, idx, col);
668
669
0
    if (result == AI_SUCCESS) {
670
0
        prop[0] = col.r;
671
0
        prop[1] = col.g;
672
0
        prop[2] = col.b;
673
0
        prop[3] = col.a;
674
0
    }
675
676
0
    return result;
677
0
}
678
679
0
aiReturn glTF2Exporter::GetMatColor(const aiMaterial &mat, vec3 &prop, const char *propName, int type, int idx) const {
680
0
    aiColor3D col;
681
0
    aiReturn result = mat.Get(propName, type, idx, col);
682
683
0
    if (result == AI_SUCCESS) {
684
0
        prop[0] = col.r;
685
0
        prop[1] = col.g;
686
0
        prop[2] = col.b;
687
0
    }
688
689
0
    return result;
690
0
}
691
692
// This extension has been deprecated, only export with the specific flag enabled, defaults to false. Uses KHR_material_specular default.
693
0
bool glTF2Exporter::GetMatSpecGloss(const aiMaterial &mat, glTF2::PbrSpecularGlossiness &pbrSG) {
694
0
    bool result = false;
695
    // If has Glossiness, a Specular Color or Specular Texture, use the KHR_materials_pbrSpecularGlossiness extension
696
0
    if (mat.Get(AI_MATKEY_GLOSSINESS_FACTOR, pbrSG.glossinessFactor) == AI_SUCCESS) {
697
0
        result = true;
698
0
    } else {
699
        // Don't have explicit glossiness, convert from pbr roughness or legacy shininess
700
0
        float shininess;
701
0
        if (mat.Get(AI_MATKEY_ROUGHNESS_FACTOR, shininess) == AI_SUCCESS) {
702
0
            pbrSG.glossinessFactor = 1.0f - shininess; // Extension defines this way
703
0
        } else if (mat.Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS) {
704
0
            pbrSG.glossinessFactor = shininess / 1000;
705
0
        }
706
0
    }
707
708
0
    if (GetMatColor(mat, pbrSG.specularFactor, AI_MATKEY_COLOR_SPECULAR) == AI_SUCCESS) {
709
0
        result = true;
710
0
    }
711
    // Add any appropriate textures
712
0
    GetMatTex(mat, pbrSG.specularGlossinessTexture, aiTextureType_SPECULAR);
713
714
0
    result = result || pbrSG.specularGlossinessTexture.texture;
715
716
0
    if (result) {
717
        // Likely to always have diffuse
718
0
        GetMatTex(mat, pbrSG.diffuseTexture, aiTextureType_DIFFUSE);
719
0
        GetMatColor(mat, pbrSG.diffuseFactor, AI_MATKEY_COLOR_DIFFUSE);
720
0
    }
721
722
0
    return result;
723
0
}
724
725
0
bool glTF2Exporter::GetMatSpecular(const aiMaterial &mat, glTF2::MaterialSpecular &specular) {
726
    // Specular requires either/or, default factors of zero disables specular, so do not export
727
0
    if (GetMatColor(mat, specular.specularColorFactor, AI_MATKEY_COLOR_SPECULAR) != AI_SUCCESS && mat.Get(AI_MATKEY_SPECULAR_FACTOR, specular.specularFactor) != AI_SUCCESS) {
728
0
        return false;
729
0
    }
730
    // The spec states that the default is 1.0 and [1.0, 1.0, 1.0]. We if both are 0, which should disable specular. Otherwise, if one is 0, set to 1.0
731
0
    const bool colorFactorIsZero = specular.specularColorFactor[0] == defaultSpecularColorFactor[0] && specular.specularColorFactor[1] == defaultSpecularColorFactor[1] && specular.specularColorFactor[2] == defaultSpecularColorFactor[2];
732
0
    if (specular.specularFactor == 0.0f && colorFactorIsZero) {
733
0
        return false;
734
0
    } else if (specular.specularFactor == 0.0f) {
735
0
        specular.specularFactor = 1.0f;
736
0
    } else if (colorFactorIsZero) {
737
0
        specular.specularColorFactor[0] = specular.specularColorFactor[1] = specular.specularColorFactor[2] = 1.0f;
738
0
    }
739
0
    GetMatTex(mat, specular.specularTexture, aiTextureType_SPECULAR, 0);
740
0
    GetMatTex(mat, specular.specularColorTexture, aiTextureType_SPECULAR, 1);
741
0
    return true;
742
0
}
743
744
0
bool glTF2Exporter::GetMatSheen(const aiMaterial &mat, glTF2::MaterialSheen &sheen) {
745
    // Return true if got any valid Sheen properties or textures
746
0
    if (GetMatColor(mat, sheen.sheenColorFactor, AI_MATKEY_SHEEN_COLOR_FACTOR) != aiReturn_SUCCESS) {
747
0
        return false;
748
0
    }
749
750
    // Default Sheen color factor {0,0,0} disables Sheen, so do not export
751
0
    if (sheen.sheenColorFactor[0] == defaultSheenFactor[0] && sheen.sheenColorFactor[1] == defaultSheenFactor[1] && sheen.sheenColorFactor[2] == defaultSheenFactor[2]) {
752
0
        return false;
753
0
    }
754
755
0
    mat.Get(AI_MATKEY_SHEEN_ROUGHNESS_FACTOR, sheen.sheenRoughnessFactor);
756
757
0
    GetMatTex(mat, sheen.sheenColorTexture, AI_MATKEY_SHEEN_COLOR_TEXTURE);
758
0
    GetMatTex(mat, sheen.sheenRoughnessTexture, AI_MATKEY_SHEEN_ROUGHNESS_TEXTURE);
759
760
0
    return true;
761
0
}
762
763
0
bool glTF2Exporter::GetMatClearcoat(const aiMaterial &mat, glTF2::MaterialClearcoat &clearcoat) {
764
0
    if (mat.Get(AI_MATKEY_CLEARCOAT_FACTOR, clearcoat.clearcoatFactor) != aiReturn_SUCCESS) {
765
0
        return false;
766
0
    }
767
768
    // Clearcoat factor of zero disables Clearcoat, so do not export
769
0
    if (clearcoat.clearcoatFactor == 0.0f)
770
0
        return false;
771
772
0
    mat.Get(AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR, clearcoat.clearcoatRoughnessFactor);
773
774
0
    GetMatTex(mat, clearcoat.clearcoatTexture, AI_MATKEY_CLEARCOAT_TEXTURE);
775
0
    GetMatTex(mat, clearcoat.clearcoatRoughnessTexture, AI_MATKEY_CLEARCOAT_ROUGHNESS_TEXTURE);
776
0
    GetMatTex(mat, clearcoat.clearcoatNormalTexture, AI_MATKEY_CLEARCOAT_NORMAL_TEXTURE);
777
778
0
    return true;
779
0
}
780
781
0
bool glTF2Exporter::GetMatTransmission(const aiMaterial &mat, glTF2::MaterialTransmission &transmission) {
782
0
    bool result = mat.Get(AI_MATKEY_TRANSMISSION_FACTOR, transmission.transmissionFactor) == aiReturn_SUCCESS;
783
0
    GetMatTex(mat, transmission.transmissionTexture, AI_MATKEY_TRANSMISSION_TEXTURE);
784
0
    return result || transmission.transmissionTexture.texture;
785
0
}
786
787
0
bool glTF2Exporter::GetMatVolume(const aiMaterial &mat, glTF2::MaterialVolume &volume) {
788
0
    bool result = mat.Get(AI_MATKEY_VOLUME_THICKNESS_FACTOR, volume.thicknessFactor) != aiReturn_SUCCESS;
789
790
0
    GetMatTex(mat, volume.thicknessTexture, AI_MATKEY_VOLUME_THICKNESS_TEXTURE);
791
792
0
    result = result || mat.Get(AI_MATKEY_VOLUME_ATTENUATION_DISTANCE, volume.attenuationDistance);
793
0
    result = result || GetMatColor(mat, volume.attenuationColor, AI_MATKEY_VOLUME_ATTENUATION_COLOR) != aiReturn_SUCCESS;
794
795
    // Valid if any of these properties are available
796
0
    return result || volume.thicknessTexture.texture;
797
0
}
798
799
0
bool glTF2Exporter::GetMatIOR(const aiMaterial &mat, glTF2::MaterialIOR &ior) {
800
0
    return mat.Get(AI_MATKEY_REFRACTI, ior.ior) == aiReturn_SUCCESS;
801
0
}
802
803
0
bool glTF2Exporter::GetMatEmissiveStrength(const aiMaterial &mat, glTF2::MaterialEmissiveStrength &emissiveStrength) {
804
0
    return mat.Get(AI_MATKEY_EMISSIVE_INTENSITY, emissiveStrength.emissiveStrength) == aiReturn_SUCCESS;
805
0
}
806
807
0
bool glTF2Exporter::GetMatAnisotropy(const aiMaterial &mat, glTF2::MaterialAnisotropy &anisotropy) {
808
0
    if (mat.Get(AI_MATKEY_ANISOTROPY_FACTOR, anisotropy.anisotropyStrength) != aiReturn_SUCCESS) {
809
0
        return false;
810
0
    }
811
812
    // do not export anisotropy when strength is zero
813
0
    if (anisotropy.anisotropyStrength == 0.0f) {
814
0
        return false;
815
0
    }
816
817
0
    mat.Get(AI_MATKEY_ANISOTROPY_ROTATION, anisotropy.anisotropyRotation);
818
0
    GetMatTex(mat, anisotropy.anisotropyTexture, AI_MATKEY_ANISOTROPY_TEXTURE);
819
820
0
    return true;
821
0
}
822
823
0
void glTF2Exporter::ExportMaterials() {
824
0
    aiString aiName;
825
0
    for (unsigned int i = 0; i < mScene->mNumMaterials; ++i) {
826
0
        ai_assert(mScene->mMaterials[i] != nullptr);
827
828
0
        const aiMaterial &mat = *(mScene->mMaterials[i]);
829
830
0
        std::string id = "material_" + ai_to_string(i);
831
832
0
        Ref<Material> m = mAsset->materials.Create(id);
833
834
0
        std::string name;
835
0
        if (mat.Get(AI_MATKEY_NAME, aiName) == AI_SUCCESS) {
836
0
            name = aiName.C_Str();
837
0
        }
838
0
        name = mAsset->FindUniqueID(name, "material");
839
840
0
        m->name = name;
841
842
0
        GetMatTex(mat, m->pbrMetallicRoughness.baseColorTexture, aiTextureType_BASE_COLOR);
843
844
0
        if (!m->pbrMetallicRoughness.baseColorTexture.texture) {
845
            // if there wasn't a baseColorTexture defined in the source, fallback to any diffuse texture
846
0
            GetMatTex(mat, m->pbrMetallicRoughness.baseColorTexture, aiTextureType_DIFFUSE);
847
0
        }
848
849
0
        GetMatTex(mat, m->pbrMetallicRoughness.metallicRoughnessTexture, aiTextureType_DIFFUSE_ROUGHNESS);
850
851
0
        if (!m->pbrMetallicRoughness.metallicRoughnessTexture.texture) {
852
            // if there wasn't a aiTextureType_DIFFUSE_ROUGHNESS defined in the source, fallback to aiTextureType_METALNESS
853
0
            GetMatTex(mat, m->pbrMetallicRoughness.metallicRoughnessTexture, aiTextureType_METALNESS);
854
0
        }
855
856
0
        if (!m->pbrMetallicRoughness.metallicRoughnessTexture.texture) {
857
            // if there still wasn't a aiTextureType_METALNESS defined in the source, fallback to AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE
858
0
            GetMatTex(mat, m->pbrMetallicRoughness.metallicRoughnessTexture, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE);
859
0
        }
860
861
0
        if (GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_BASE_COLOR) != AI_SUCCESS) {
862
            // if baseColorFactor wasn't defined, then the source is likely not a metallic roughness material.
863
            // a fallback to any diffuse color should be used instead
864
0
            GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_COLOR_DIFFUSE);
865
0
        }
866
867
0
        if (mat.Get(AI_MATKEY_METALLIC_FACTOR, m->pbrMetallicRoughness.metallicFactor) != AI_SUCCESS) {
868
            // if metallicFactor wasn't defined, then the source is likely not a PBR file, and the metallicFactor should be 0
869
0
            m->pbrMetallicRoughness.metallicFactor = 0;
870
0
        }
871
872
        // get roughness if source is gltf2 file
873
0
        if (mat.Get(AI_MATKEY_ROUGHNESS_FACTOR, m->pbrMetallicRoughness.roughnessFactor) != AI_SUCCESS) {
874
            // otherwise, try to derive and convert from specular + shininess values
875
0
            aiColor4D specularColor;
876
0
            ai_real shininess;
877
878
0
            if (mat.Get(AI_MATKEY_COLOR_SPECULAR, specularColor) == AI_SUCCESS && mat.Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS) {
879
                // convert specular color to luminance
880
0
                float specularIntensity = specularColor[0] * 0.2125f + specularColor[1] * 0.7154f + specularColor[2] * 0.0721f;
881
                // normalize shininess (assuming max is 1000) with an inverse exponentional curve
882
0
                float normalizedShininess = std::sqrt(shininess / 1000);
883
884
                // clamp the shininess value between 0 and 1
885
0
                normalizedShininess = std::min(std::max(normalizedShininess, 0.0f), 1.0f);
886
                // low specular intensity values should produce a rough material even if shininess is high.
887
0
                normalizedShininess = normalizedShininess * specularIntensity;
888
889
0
                m->pbrMetallicRoughness.roughnessFactor = 1 - normalizedShininess;
890
0
            }
891
0
        }
892
893
0
        GetMatTex(mat, m->normalTexture, aiTextureType_NORMALS);
894
0
        GetMatTex(mat, m->occlusionTexture, aiTextureType_LIGHTMAP);
895
0
        GetMatTex(mat, m->emissiveTexture, aiTextureType_EMISSIVE);
896
0
        GetMatColor(mat, m->emissiveFactor, AI_MATKEY_COLOR_EMISSIVE);
897
898
0
        mat.Get(AI_MATKEY_TWOSIDED, m->doubleSided);
899
0
        mat.Get(AI_MATKEY_GLTF_ALPHACUTOFF, m->alphaCutoff);
900
901
0
        float opacity;
902
0
        aiString alphaMode;
903
904
0
        if (mat.Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS) {
905
0
            if (opacity < 1) {
906
0
                m->alphaMode = "BLEND";
907
0
                m->pbrMetallicRoughness.baseColorFactor[3] *= opacity;
908
0
            }
909
0
        }
910
0
        if (mat.Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode) == AI_SUCCESS) {
911
0
            m->alphaMode = alphaMode.C_Str();
912
0
        }
913
914
        // This extension has been deprecated, only export with the specific flag enabled, defaults to false. Uses KHR_material_specular default.
915
0
        if (mProperties->GetPropertyBool(AI_CONFIG_USE_GLTF_PBR_SPECULAR_GLOSSINESS)) {
916
            // KHR_materials_pbrSpecularGlossiness extension
917
0
            PbrSpecularGlossiness pbrSG;
918
0
            if (GetMatSpecGloss(mat, pbrSG)) {
919
0
                mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness = true;
920
0
                m->pbrSpecularGlossiness = Nullable<PbrSpecularGlossiness>(pbrSG);
921
0
            }
922
0
        }
923
924
        // glTFv2 is either PBR or Unlit
925
0
        aiShadingMode shadingMode = aiShadingMode_PBR_BRDF;
926
0
        mat.Get(AI_MATKEY_SHADING_MODEL, shadingMode);
927
0
        if (shadingMode == aiShadingMode_Unlit) {
928
0
            mAsset->extensionsUsed.KHR_materials_unlit = true;
929
0
            m->unlit = true;
930
0
        } else {
931
            // These extensions are not compatible with KHR_materials_unlit or KHR_materials_pbrSpecularGlossiness
932
0
            if (!m->pbrSpecularGlossiness.isPresent) {
933
0
                MaterialSpecular specular;
934
0
                if (GetMatSpecular(mat, specular)) {
935
0
                    mAsset->extensionsUsed.KHR_materials_specular = true;
936
0
                    m->materialSpecular = Nullable<MaterialSpecular>(specular);
937
0
                    GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_COLOR_DIFFUSE);
938
0
                }
939
940
0
                MaterialSheen sheen;
941
0
                if (GetMatSheen(mat, sheen)) {
942
0
                    mAsset->extensionsUsed.KHR_materials_sheen = true;
943
0
                    m->materialSheen = Nullable<MaterialSheen>(sheen);
944
0
                }
945
946
0
                MaterialClearcoat clearcoat;
947
0
                if (GetMatClearcoat(mat, clearcoat)) {
948
0
                    mAsset->extensionsUsed.KHR_materials_clearcoat = true;
949
0
                    m->materialClearcoat = Nullable<MaterialClearcoat>(clearcoat);
950
0
                }
951
952
0
                MaterialTransmission transmission;
953
0
                if (GetMatTransmission(mat, transmission)) {
954
0
                    mAsset->extensionsUsed.KHR_materials_transmission = true;
955
0
                    m->materialTransmission = Nullable<MaterialTransmission>(transmission);
956
0
                }
957
958
0
                MaterialVolume volume;
959
0
                if (GetMatVolume(mat, volume)) {
960
0
                    mAsset->extensionsUsed.KHR_materials_volume = true;
961
0
                    m->materialVolume = Nullable<MaterialVolume>(volume);
962
0
                }
963
964
0
                MaterialIOR ior;
965
0
                if (GetMatIOR(mat, ior)) {
966
0
                    mAsset->extensionsUsed.KHR_materials_ior = true;
967
0
                    m->materialIOR = Nullable<MaterialIOR>(ior);
968
0
                }
969
970
0
                MaterialEmissiveStrength emissiveStrength;
971
0
                if (GetMatEmissiveStrength(mat, emissiveStrength)) {
972
0
                    mAsset->extensionsUsed.KHR_materials_emissive_strength = true;
973
0
                    m->materialEmissiveStrength = Nullable<MaterialEmissiveStrength>(emissiveStrength);
974
0
                }
975
976
0
                MaterialAnisotropy anisotropy;
977
0
                if (GetMatAnisotropy(mat, anisotropy)) {
978
0
                    mAsset->extensionsUsed.KHR_materials_anisotropy = true;
979
0
                    m->materialAnisotropy = Nullable<MaterialAnisotropy>(anisotropy);
980
0
                }
981
0
            }
982
0
        }
983
0
    }
984
0
}
985
986
/*
987
 * Search through node hierarchy and find the node containing the given meshID.
988
 * Returns true on success, and false otherwise.
989
 */
990
0
bool FindMeshNode(Ref<Node> &nodeIn, Ref<Node> &meshNode, const std::string &meshID) {
991
0
    for (unsigned int i = 0; i < nodeIn->meshes.size(); ++i) {
992
0
        if (meshID.compare(nodeIn->meshes[i]->id) == 0) {
993
0
            meshNode = nodeIn;
994
0
            return true;
995
0
        }
996
0
    }
997
998
0
    for (unsigned int i = 0; i < nodeIn->children.size(); ++i) {
999
0
        if (FindMeshNode(nodeIn->children[i], meshNode, meshID)) {
1000
0
            return true;
1001
0
        }
1002
0
    }
1003
1004
0
    return false;
1005
0
}
1006
1007
/*
1008
 * Find the root joint of the skeleton.
1009
 * Starts will any joint node and traces up the tree,
1010
 * until a parent is found that does not have a jointName.
1011
 * Returns the first parent Ref<Node> found that does not have a jointName.
1012
 */
1013
0
Ref<Node> FindSkeletonRootJoint(Ref<Skin> &skinRef) {
1014
0
    Ref<Node> startNodeRef;
1015
0
    Ref<Node> parentNodeRef;
1016
1017
    // Arbitrarily use the first joint to start the search.
1018
0
    startNodeRef = skinRef->jointNames[0];
1019
0
    parentNodeRef = skinRef->jointNames[0];
1020
1021
0
    do {
1022
0
        startNodeRef = parentNodeRef;
1023
0
        parentNodeRef = startNodeRef->parent;
1024
0
    } while (parentNodeRef && !parentNodeRef->jointName.empty());
1025
1026
0
    return parentNodeRef;
1027
0
}
1028
1029
struct boneIndexWeightPair {
1030
    unsigned int indexJoint;
1031
    float weight;
1032
0
    bool operator()(boneIndexWeightPair &a, boneIndexWeightPair &b) {
1033
0
        return a.weight > b.weight;
1034
0
    }
1035
};
1036
1037
void ExportSkin(Asset &mAsset, const aiMesh *aimesh, Ref<Mesh> &meshRef, Ref<Buffer> &bufferRef, Ref<Skin> &skinRef,
1038
0
        std::vector<aiMatrix4x4> &inverseBindMatricesData, bool unlimitedBonesPerVertex) {
1039
0
    if (aimesh->mNumBones < 1) {
1040
0
        return;
1041
0
    }
1042
1043
    // Store the vertex joint and weight data.
1044
0
    const size_t NumVerts(aimesh->mNumVertices);
1045
0
    int *jointsPerVertex = new int[NumVerts];
1046
0
    std::vector<std::vector<boneIndexWeightPair>> allVerticesPairs;
1047
0
    int maxJointsPerVertex = 0;
1048
0
    for (size_t i = 0; i < NumVerts; ++i) {
1049
0
        jointsPerVertex[i] = 0;
1050
0
        std::vector<boneIndexWeightPair> vertexPair;
1051
0
        allVerticesPairs.push_back(vertexPair);
1052
0
    }
1053
1054
0
    for (unsigned int idx_bone = 0; idx_bone < aimesh->mNumBones; ++idx_bone) {
1055
0
        const aiBone *aib = aimesh->mBones[idx_bone];
1056
1057
        // aib->mName   =====>  skinRef->jointNames
1058
        // Find the node with id = mName.
1059
0
        Ref<Node> nodeRef = mAsset.nodes.Get(aib->mName.C_Str());
1060
0
        nodeRef->jointName = nodeRef->name;
1061
1062
0
        unsigned int jointNamesIndex = 0;
1063
0
        bool addJointToJointNames = true;
1064
0
        for (unsigned int idx_joint = 0; idx_joint < skinRef->jointNames.size(); ++idx_joint) {
1065
0
            if (skinRef->jointNames[idx_joint]->jointName.compare(nodeRef->jointName) == 0) {
1066
0
                addJointToJointNames = false;
1067
0
                jointNamesIndex = idx_joint;
1068
0
            }
1069
0
        }
1070
1071
0
        if (addJointToJointNames) {
1072
0
            skinRef->jointNames.push_back(nodeRef);
1073
1074
            // aib->mOffsetMatrix   =====>  skinRef->inverseBindMatrices
1075
0
            aiMatrix4x4 tmpMatrix4;
1076
0
            CopyValue(aib->mOffsetMatrix, tmpMatrix4);
1077
0
            inverseBindMatricesData.push_back(tmpMatrix4);
1078
0
            jointNamesIndex = static_cast<unsigned int>(inverseBindMatricesData.size() - 1);
1079
0
        }
1080
1081
        // aib->mWeights   =====>  temp pairs data
1082
0
        for (unsigned int idx_weights = 0; idx_weights < aib->mNumWeights;
1083
0
              ++idx_weights) {
1084
0
            unsigned int vertexId = aib->mWeights[idx_weights].mVertexId;
1085
0
            float vertWeight = aib->mWeights[idx_weights].mWeight;
1086
0
            allVerticesPairs[vertexId].push_back({jointNamesIndex, vertWeight});
1087
0
            jointsPerVertex[vertexId] += 1;
1088
0
            maxJointsPerVertex =
1089
0
                    std::max(maxJointsPerVertex, jointsPerVertex[vertexId]);
1090
0
        }
1091
0
    } // End: for-loop mNumMeshes
1092
1093
0
    if (!unlimitedBonesPerVertex){
1094
        // skinning limited only for 4 bones per vertex, default
1095
0
        maxJointsPerVertex = 4;
1096
0
    }
1097
1098
    // temp pairs data  =====>  vertexWeightData
1099
0
    size_t numGroups = (maxJointsPerVertex - 1) / 4 + 1;
1100
0
    vec4 *vertexJointData = new vec4[NumVerts * numGroups];
1101
0
    vec4 *vertexWeightData = new vec4[NumVerts * numGroups];
1102
0
    for (size_t indexVertex = 0; indexVertex < NumVerts; ++indexVertex) {
1103
        // order pairs by weight for each vertex
1104
0
        std::sort(allVerticesPairs[indexVertex].begin(),
1105
0
                allVerticesPairs[indexVertex].end(),
1106
0
                boneIndexWeightPair());
1107
0
        for (size_t indexGroup = 0; indexGroup < numGroups; ++indexGroup) {
1108
0
            for (size_t indexJoint = 0; indexJoint < 4; ++indexJoint) {
1109
0
                size_t indexBone = indexGroup * 4 + indexJoint;
1110
0
                size_t indexData = indexVertex + NumVerts * indexGroup;
1111
0
                if (indexBone >= allVerticesPairs[indexVertex].size()) {
1112
0
                    vertexJointData[indexData][indexJoint] = 0.f;
1113
0
                    vertexWeightData[indexData][indexJoint] = 0.f;
1114
0
                } else {
1115
0
                    vertexJointData[indexData][indexJoint] =
1116
0
                    static_cast<float>(
1117
0
                            allVerticesPairs[indexVertex][indexBone].indexJoint);
1118
0
                    vertexWeightData[indexData][indexJoint] =
1119
0
                            allVerticesPairs[indexVertex][indexBone].weight;
1120
0
                }
1121
0
            }
1122
0
        }
1123
0
    }
1124
1125
0
    for (size_t idx_group = 0; idx_group < numGroups; ++idx_group) {
1126
0
        Mesh::Primitive &p = meshRef->primitives.back();
1127
0
        Ref<Accessor> vertexJointAccessor = ExportData(
1128
0
            mAsset, skinRef->id, bufferRef, aimesh->mNumVertices,
1129
0
            vertexJointData + idx_group * NumVerts,
1130
0
            AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT);
1131
0
        if (vertexJointAccessor) {
1132
0
            size_t offset = vertexJointAccessor->bufferView->byteOffset;
1133
0
            size_t bytesLen = vertexJointAccessor->bufferView->byteLength;
1134
0
            unsigned int s_bytesPerComp =
1135
0
                ComponentTypeSize(ComponentType_UNSIGNED_SHORT);
1136
0
            unsigned int bytesPerComp =
1137
0
                ComponentTypeSize(vertexJointAccessor->componentType);
1138
0
            size_t s_bytesLen = bytesLen * s_bytesPerComp / bytesPerComp;
1139
0
            Ref<Buffer> buf = vertexJointAccessor->bufferView->buffer;
1140
0
            uint8_t *arrys = new uint8_t[bytesLen];
1141
0
            unsigned int i = 0;
1142
0
            for (unsigned int j = 0; j < bytesLen; j += bytesPerComp) {
1143
0
                size_t len_p = offset + j;
1144
0
                float f_value = *(float *)&buf->GetPointer()[len_p];
1145
0
                unsigned short c = static_cast<unsigned short>(f_value);
1146
0
                memcpy(&arrys[i * s_bytesPerComp], &c, s_bytesPerComp);
1147
0
                ++i;
1148
0
            }
1149
0
            buf->ReplaceData_joint(offset, bytesLen, arrys, bytesLen);
1150
0
            vertexJointAccessor->componentType = ComponentType_UNSIGNED_SHORT;
1151
0
            vertexJointAccessor->bufferView->byteLength = s_bytesLen;
1152
1153
0
            p.attributes.joint.push_back(vertexJointAccessor);
1154
0
            delete[] arrys;
1155
0
        }
1156
0
        Ref<Accessor> vertexWeightAccessor = ExportData(
1157
0
            mAsset, skinRef->id, bufferRef, aimesh->mNumVertices,
1158
0
            vertexWeightData + idx_group * NumVerts,
1159
0
            AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT);
1160
0
        if (vertexWeightAccessor) {
1161
0
            p.attributes.weight.push_back(vertexWeightAccessor);
1162
0
        }
1163
0
    }
1164
0
    delete[] jointsPerVertex;
1165
0
    delete[] vertexWeightData;
1166
0
    delete[] vertexJointData;
1167
0
}
1168
1169
0
void glTF2Exporter::ExportMeshes() {
1170
0
    typedef decltype(aiFace::mNumIndices) IndicesType;
1171
1172
0
    std::string fname = std::string(mFilename);
1173
0
    std::string bufferIdPrefix = fname.substr(0, fname.rfind(".gltf"));
1174
0
    std::string bufferId = mAsset->FindUniqueID("", bufferIdPrefix.c_str());
1175
1176
0
    Ref<Buffer> b = mAsset->GetBodyBuffer();
1177
0
    if (!b) {
1178
0
        b = mAsset->buffers.Create(bufferId);
1179
0
    }
1180
1181
    //----------------------------------------
1182
    // Initialize variables for the skin
1183
0
    bool createSkin = false;
1184
0
    for (unsigned int idx_mesh = 0; idx_mesh < mScene->mNumMeshes; ++idx_mesh) {
1185
0
        const aiMesh *aim = mScene->mMeshes[idx_mesh];
1186
0
        if (aim->HasBones()) {
1187
0
            createSkin = true;
1188
0
            break;
1189
0
        }
1190
0
    }
1191
1192
0
    Ref<Skin> skinRef;
1193
0
    std::string skinName = mAsset->FindUniqueID("skin", "skin");
1194
0
    std::vector<aiMatrix4x4> inverseBindMatricesData;
1195
0
    if (createSkin) {
1196
0
        skinRef = mAsset->skins.Create(skinName);
1197
0
        skinRef->name = skinName;
1198
0
    }
1199
    //----------------------------------------
1200
1201
0
    for (unsigned int idx_mesh = 0; idx_mesh < mScene->mNumMeshes; ++idx_mesh) {
1202
0
        const aiMesh *aim = mScene->mMeshes[idx_mesh];
1203
0
        if (aim->mNumFaces == 0) {
1204
0
            continue;
1205
0
        }
1206
1207
0
        std::string name = aim->mName.C_Str();
1208
1209
0
        std::string meshId = mAsset->FindUniqueID(name, "mesh");
1210
0
        Ref<Mesh> m = mAsset->meshes.Create(meshId);
1211
0
        m->primitives.resize(1);
1212
0
        Mesh::Primitive &p = m->primitives.back();
1213
1214
0
        m->name = name;
1215
1216
0
        p.material = mAsset->materials.Get(aim->mMaterialIndex);
1217
0
        p.ngonEncoded = (aim->mPrimitiveTypes & aiPrimitiveType_NGONEncodingFlag) != 0;
1218
1219
        /******************* Vertices ********************/
1220
0
        Ref<Accessor> v = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mVertices, AttribType::VEC3,
1221
0
                AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER);
1222
0
        if (v) {
1223
0
            p.attributes.position.push_back(v);
1224
0
        }
1225
1226
        /******************** Normals ********************/
1227
        // Normalize all normals as the validator can emit a warning otherwise
1228
0
        if (nullptr != aim->mNormals) {
1229
0
            for (auto i = 0u; i < aim->mNumVertices; ++i) {
1230
0
                aim->mNormals[i].NormalizeSafe();
1231
0
            }
1232
0
        }
1233
1234
0
        Ref<Accessor> n = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mNormals, AttribType::VEC3,
1235
0
                AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER);
1236
0
        if (n) {
1237
0
            p.attributes.normal.push_back(n);
1238
0
        }
1239
1240
        /******************** Tangents ********************/
1241
0
        if (nullptr != aim->mTangents) {
1242
0
            for (uint32_t i = 0; i < aim->mNumVertices; ++i) {
1243
0
                aim->mTangents[i].NormalizeSafe();
1244
0
            }
1245
0
            Ref<Accessor> t = ExportData(
1246
0
                *mAsset, meshId, b, aim->mNumVertices, aim->mTangents, AttribType::VEC3,
1247
0
                AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER
1248
0
            );
1249
0
            if (t) {
1250
0
                p.attributes.tangent.push_back(t);
1251
0
            }
1252
0
        }
1253
1254
        /************** Texture coordinates **************/
1255
0
        for (int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
1256
0
            if (!aim->HasTextureCoords(i)) {
1257
0
                continue;
1258
0
            }
1259
1260
            // Flip UV y coords
1261
0
            if (aim->mNumUVComponents[i] > 1) {
1262
0
                for (unsigned int j = 0; j < aim->mNumVertices; ++j) {
1263
0
                    aim->mTextureCoords[i][j].y = 1 - aim->mTextureCoords[i][j].y;
1264
0
                }
1265
0
            }
1266
1267
0
            if (aim->mNumUVComponents[i] > 0) {
1268
0
                AttribType::Value type = (aim->mNumUVComponents[i] == 2) ? AttribType::VEC2 : AttribType::VEC3;
1269
1270
0
                Ref<Accessor> tc = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mTextureCoords[i],
1271
0
                        AttribType::VEC3, type, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER);
1272
0
                if (tc) {
1273
0
                    p.attributes.texcoord.push_back(tc);
1274
0
                }
1275
0
            }
1276
0
        }
1277
1278
        /*************** Vertex colors ****************/
1279
0
        for (unsigned int indexColorChannel = 0; indexColorChannel < aim->GetNumColorChannels(); ++indexColorChannel) {
1280
0
            Ref<Accessor> c = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mColors[indexColorChannel],
1281
0
                    AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER);
1282
0
            if (c) {
1283
0
                p.attributes.color.push_back(c);
1284
0
            }
1285
0
        }
1286
1287
        /*************** Vertices indices ****************/
1288
0
        if (aim->mNumFaces > 0) {
1289
0
            std::vector<IndicesType> indices;
1290
0
            unsigned int nIndicesPerFace = aim->mFaces[0].mNumIndices;
1291
0
            indices.resize(aim->mNumFaces * nIndicesPerFace);
1292
0
            for (size_t i = 0; i < aim->mNumFaces; ++i) {
1293
0
                for (size_t j = 0; j < nIndicesPerFace; ++j) {
1294
0
                    indices[i * nIndicesPerFace + j] = IndicesType(aim->mFaces[i].mIndices[j]);
1295
0
                }
1296
0
            }
1297
1298
0
            p.indices = ExportData(*mAsset, meshId, b, indices.size(), &indices[0], AttribType::SCALAR, AttribType::SCALAR,
1299
0
                    ComponentType_UNSIGNED_INT, BufferViewTarget_ELEMENT_ARRAY_BUFFER);
1300
0
        }
1301
1302
0
        switch (aim->mPrimitiveTypes) {
1303
0
        case aiPrimitiveType_POLYGON:
1304
0
            p.mode = PrimitiveMode_TRIANGLES;
1305
0
            break; // TODO implement this
1306
0
        case aiPrimitiveType_LINE:
1307
0
            p.mode = PrimitiveMode_LINES;
1308
0
            break;
1309
0
        case aiPrimitiveType_POINT:
1310
0
            p.mode = PrimitiveMode_POINTS;
1311
0
            break;
1312
0
        default: // aiPrimitiveType_TRIANGLE
1313
0
            p.mode = PrimitiveMode_TRIANGLES;
1314
0
            break;
1315
0
        }
1316
1317
//        /*************** Skins ****************/
1318
//        if (aim->HasBones()) {
1319
//            ExportSkin(*mAsset, aim, m, b, skinRef, inverseBindMatricesData);
1320
//        }
1321
        /*************** Skins ****************/
1322
0
        if (aim->HasBones()) {
1323
0
            bool unlimitedBonesPerVertex =
1324
0
                this->mProperties->HasPropertyBool(
1325
0
                        AI_CONFIG_EXPORT_GLTF_UNLIMITED_SKINNING_BONES_PER_VERTEX) &&
1326
0
                this->mProperties->GetPropertyBool(
1327
0
                        AI_CONFIG_EXPORT_GLTF_UNLIMITED_SKINNING_BONES_PER_VERTEX);
1328
0
            ExportSkin(*mAsset, aim, m, b, skinRef, inverseBindMatricesData,
1329
0
                    unlimitedBonesPerVertex);
1330
0
        }
1331
1332
        /*************** Targets for blendshapes ****************/
1333
0
        if (aim->mNumAnimMeshes > 0) {
1334
0
            bool bUseSparse = this->mProperties->HasPropertyBool("GLTF2_SPARSE_ACCESSOR_EXP") &&
1335
0
                              this->mProperties->GetPropertyBool("GLTF2_SPARSE_ACCESSOR_EXP");
1336
0
            bool bIncludeNormal = this->mProperties->HasPropertyBool("GLTF2_TARGET_NORMAL_EXP") &&
1337
0
                                  this->mProperties->GetPropertyBool("GLTF2_TARGET_NORMAL_EXP");
1338
0
            bool bExportTargetNames = this->mProperties->HasPropertyBool("GLTF2_TARGETNAMES_EXP") &&
1339
0
                                      this->mProperties->GetPropertyBool("GLTF2_TARGETNAMES_EXP");
1340
1341
0
            p.targets.resize(aim->mNumAnimMeshes);
1342
0
            for (unsigned int am = 0; am < aim->mNumAnimMeshes; ++am) {
1343
0
                aiAnimMesh *pAnimMesh = aim->mAnimMeshes[am];
1344
0
                if (bExportTargetNames) {
1345
0
                    m->targetNames.emplace_back(pAnimMesh->mName.data);
1346
0
                }
1347
                // position
1348
0
                if (pAnimMesh->HasPositions()) {
1349
                    // NOTE: in gltf it is the diff stored
1350
0
                    aiVector3D *pPositionDiff = new aiVector3D[pAnimMesh->mNumVertices];
1351
0
                    for (unsigned int vt = 0; vt < pAnimMesh->mNumVertices; ++vt) {
1352
0
                        pPositionDiff[vt] = pAnimMesh->mVertices[vt] - aim->mVertices[vt];
1353
0
                    }
1354
0
                    Ref<Accessor> vec;
1355
0
                    if (bUseSparse) {
1356
0
                        vec = ExportDataSparse(*mAsset, meshId, b,
1357
0
                                pAnimMesh->mNumVertices, pPositionDiff,
1358
0
                                AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT);
1359
0
                    } else {
1360
0
                        vec = ExportData(*mAsset, meshId, b,
1361
0
                                pAnimMesh->mNumVertices, pPositionDiff,
1362
0
                                AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT);
1363
0
                    }
1364
0
                    if (vec) {
1365
0
                        p.targets[am].position.push_back(vec);
1366
0
                    }
1367
0
                    delete[] pPositionDiff;
1368
0
                }
1369
1370
                // normal
1371
0
                if (pAnimMesh->HasNormals() && bIncludeNormal) {
1372
0
                    aiVector3D *pNormalDiff = new aiVector3D[pAnimMesh->mNumVertices];
1373
0
                    for (unsigned int vt = 0; vt < pAnimMesh->mNumVertices; ++vt) {
1374
0
                        pNormalDiff[vt] = pAnimMesh->mNormals[vt] - aim->mNormals[vt];
1375
0
                    }
1376
0
                    Ref<Accessor> vec;
1377
0
                    if (bUseSparse) {
1378
0
                        vec = ExportDataSparse(*mAsset, meshId, b,
1379
0
                                pAnimMesh->mNumVertices, pNormalDiff,
1380
0
                                AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT);
1381
0
                    } else {
1382
0
                        vec = ExportData(*mAsset, meshId, b,
1383
0
                                pAnimMesh->mNumVertices, pNormalDiff,
1384
0
                                AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT);
1385
0
                    }
1386
0
                    if (vec) {
1387
0
                        p.targets[am].normal.push_back(vec);
1388
0
                    }
1389
0
                    delete[] pNormalDiff;
1390
0
                }
1391
1392
                // tangent?
1393
0
            }
1394
0
        }
1395
0
    }
1396
1397
    //----------------------------------------
1398
    // Finish the skin
1399
    // Create the Accessor for skinRef->inverseBindMatrices
1400
0
    bool bAddCustomizedProperty = this->mProperties->HasPropertyBool("GLTF2_CUSTOMIZE_PROPERTY");
1401
0
    if (createSkin) {
1402
0
        mat4 *invBindMatrixData = new mat4[inverseBindMatricesData.size()];
1403
0
        for (unsigned int idx_joint = 0; idx_joint < inverseBindMatricesData.size(); ++idx_joint) {
1404
0
            CopyValue(inverseBindMatricesData[idx_joint], invBindMatrixData[idx_joint]);
1405
0
        }
1406
1407
0
        Ref<Accessor> invBindMatrixAccessor = ExportData(*mAsset, skinName, b,
1408
0
                static_cast<unsigned int>(inverseBindMatricesData.size()),
1409
0
                invBindMatrixData, AttribType::MAT4, AttribType::MAT4, ComponentType_FLOAT);
1410
0
        if (invBindMatrixAccessor) {
1411
0
            skinRef->inverseBindMatrices = invBindMatrixAccessor;
1412
0
        }
1413
1414
        // Identity Matrix   =====>  skinRef->bindShapeMatrix
1415
        // Temporary. Hard-coded identity matrix here
1416
0
        skinRef->bindShapeMatrix.isPresent = bAddCustomizedProperty;
1417
0
        IdentityMatrix4(skinRef->bindShapeMatrix.value);
1418
1419
        // Find nodes that contain a mesh with bones and add "skeletons" and "skin" attributes to those nodes.
1420
0
        Ref<Node> rootNode = mAsset->nodes.Get(unsigned(0));
1421
0
        Ref<Node> meshNode;
1422
0
        for (unsigned int meshIndex = 0; meshIndex < mAsset->meshes.Size(); ++meshIndex) {
1423
0
            Ref<Mesh> mesh = mAsset->meshes.Get(meshIndex);
1424
0
            bool hasBones = false;
1425
0
            for (unsigned int i = 0; i < mesh->primitives.size(); ++i) {
1426
0
                if (!mesh->primitives[i].attributes.weight.empty()) {
1427
0
                    hasBones = true;
1428
0
                    break;
1429
0
                }
1430
0
            }
1431
0
            if (!hasBones) {
1432
0
                continue;
1433
0
            }
1434
0
            std::string meshID = mesh->id;
1435
0
            FindMeshNode(rootNode, meshNode, meshID);
1436
0
            Ref<Node> rootJoint = FindSkeletonRootJoint(skinRef);
1437
0
            if (bAddCustomizedProperty)
1438
0
                meshNode->skeletons.push_back(rootJoint);
1439
0
            meshNode->skin = skinRef;
1440
0
        }
1441
0
        delete[] invBindMatrixData;
1442
0
    }
1443
0
}
1444
1445
// Merges a node's multiple meshes (with one primitive each) into one mesh with multiple primitives
1446
0
void glTF2Exporter::MergeMeshes() {
1447
0
    for (unsigned int n = 0; n < mAsset->nodes.Size(); ++n) {
1448
0
        Ref<Node> node = mAsset->nodes.Get(n);
1449
1450
0
        unsigned int nMeshes = static_cast<unsigned int>(node->meshes.size());
1451
1452
        // skip if it's 1 or less meshes per node
1453
0
        if (nMeshes > 1) {
1454
0
            Ref<Mesh> firstMesh = node->meshes.at(0);
1455
1456
            // loop backwards to allow easy removal of a mesh from a node once it's merged
1457
0
            for (unsigned int m = nMeshes - 1; m >= 1; --m) {
1458
0
                Ref<Mesh> mesh = node->meshes.at(m);
1459
1460
                // append this mesh's primitives to the first mesh's primitives
1461
0
                firstMesh->primitives.insert(
1462
0
                        firstMesh->primitives.end(),
1463
0
                        mesh->primitives.begin(),
1464
0
                        mesh->primitives.end());
1465
1466
                // remove the mesh from the list of meshes
1467
0
                unsigned int removedIndex = mAsset->meshes.Remove(mesh->id.c_str());
1468
1469
                // find the presence of the removed mesh in other nodes
1470
0
                for (unsigned int nn = 0; nn < mAsset->nodes.Size(); ++nn) {
1471
0
                    Ref<Node> curNode = mAsset->nodes.Get(nn);
1472
1473
0
                    for (unsigned int mm = 0; mm < curNode->meshes.size(); ++mm) {
1474
0
                        Ref<Mesh> &meshRef = curNode->meshes.at(mm);
1475
0
                        unsigned int meshIndex = meshRef.GetIndex();
1476
1477
0
                        if (meshIndex == removedIndex) {
1478
0
                            curNode->meshes.erase(curNode->meshes.begin() + mm);
1479
0
                        } else if (meshIndex > removedIndex) {
1480
0
                            Ref<Mesh> newMeshRef = mAsset->meshes.Get(meshIndex - 1);
1481
1482
0
                            meshRef = newMeshRef;
1483
0
                        }
1484
0
                    }
1485
0
                }
1486
0
            }
1487
1488
            // since we were looping backwards, reverse the order of merged primitives to their original order
1489
0
            std::reverse(firstMesh->primitives.begin() + 1, firstMesh->primitives.end());
1490
0
        }
1491
0
    }
1492
0
}
1493
1494
/*
1495
 * Export the root node of the node hierarchy.
1496
 * Calls ExportNode for all children.
1497
 */
1498
0
unsigned int glTF2Exporter::ExportNodeHierarchy(const aiNode *n) {
1499
0
    Ref<Node> node = mAsset->nodes.Create(mAsset->FindUniqueID(n->mName.C_Str(), "node"));
1500
1501
0
    node->name = n->mName.C_Str();
1502
0
    if(n->mNumChildren > 0)
1503
0
        node->children.reserve(n->mNumChildren);
1504
0
    if(n->mNumMeshes > 0)
1505
0
        node->meshes.reserve(n->mNumMeshes);
1506
1507
0
    if (!n->mTransformation.IsIdentity(configEpsilon)) {
1508
0
        node->matrix.isPresent = true;
1509
0
        CopyValue(n->mTransformation, node->matrix.value);
1510
0
    }
1511
1512
0
    for (unsigned int i = 0; i < n->mNumMeshes; ++i) {
1513
0
        node->meshes.emplace_back(mAsset->meshes.Get(n->mMeshes[i]));
1514
0
    }
1515
1516
0
    for (unsigned int i = 0; i < n->mNumChildren; ++i) {
1517
0
        unsigned int idx = ExportNode(n->mChildren[i], node);
1518
0
        node->children.emplace_back(mAsset->nodes.Get(idx));
1519
0
    }
1520
1521
0
    return node.GetIndex();
1522
0
}
1523
1524
/*
1525
 * Export node and recursively calls ExportNode for all children.
1526
 * Since these nodes are not the root node, we also export the parent Ref<Node>
1527
 */
1528
0
unsigned int glTF2Exporter::ExportNode(const aiNode *n, Ref<Node> &parent) {
1529
0
    std::string name = mAsset->FindUniqueID(n->mName.C_Str(), "node");
1530
0
    Ref<Node> node = mAsset->nodes.Create(name);
1531
1532
0
    node->parent = parent;
1533
0
    node->name = name;
1534
0
    if(n->mNumChildren > 0)
1535
0
        node->children.reserve(n->mNumChildren);
1536
0
    if(n->mNumMeshes > 0)
1537
0
        node->meshes.reserve(n->mNumMeshes);
1538
1539
0
    ExportNodeExtras(n->mMetaData, node->extras);
1540
1541
0
    if (!n->mTransformation.IsIdentity(configEpsilon)) {
1542
0
        if (mScene->mNumAnimations > 0 || (mProperties && mProperties->HasPropertyBool("GLTF2_NODE_IN_TRS"))) {
1543
0
            aiQuaternion quaternion;
1544
0
            n->mTransformation.Decompose(*reinterpret_cast<aiVector3D *>(&node->scale.value), quaternion, *reinterpret_cast<aiVector3D *>(&node->translation.value));
1545
1546
0
            aiVector3D vector(static_cast<ai_real>(1.0f), static_cast<ai_real>(1.0f), static_cast<ai_real>(1.0f));
1547
0
            if (!reinterpret_cast<aiVector3D *>(&node->scale.value)->Equal(vector)) {
1548
0
                node->scale.isPresent = true;
1549
0
            }
1550
0
            if (!reinterpret_cast<aiVector3D *>(&node->translation.value)->Equal(vector)) {
1551
0
                node->translation.isPresent = true;
1552
0
            }
1553
0
            node->rotation.isPresent = true;
1554
0
            node->rotation.value[0] = quaternion.x;
1555
0
            node->rotation.value[1] = quaternion.y;
1556
0
            node->rotation.value[2] = quaternion.z;
1557
0
            node->rotation.value[3] = quaternion.w;
1558
0
            node->matrix.isPresent = false;
1559
0
        } else {
1560
0
            node->matrix.isPresent = true;
1561
0
            CopyValue(n->mTransformation, node->matrix.value);
1562
0
        }
1563
0
    }
1564
1565
0
    for (unsigned int i = 0; i < n->mNumMeshes; ++i) {
1566
0
        node->meshes.emplace_back(mAsset->meshes.Get(n->mMeshes[i]));
1567
0
    }
1568
1569
0
    for (unsigned int i = 0; i < n->mNumChildren; ++i) {
1570
0
        unsigned int idx = ExportNode(n->mChildren[i], node);
1571
0
        node->children.emplace_back(mAsset->nodes.Get(idx));
1572
0
    }
1573
1574
0
    return node.GetIndex();
1575
0
}
1576
1577
0
void glTF2Exporter::ExportScene() {
1578
    // Use the name of the scene if specified
1579
0
    const std::string sceneName = (mScene->mName.length > 0) ? mScene->mName.C_Str() : "defaultScene";
1580
1581
    // Ensure unique
1582
0
    Ref<Scene> scene = mAsset->scenes.Create(mAsset->FindUniqueID(sceneName, ""));
1583
1584
    // root node will be the first one exported (idx 0)
1585
0
    if (mAsset->nodes.Size() > 0) {
1586
0
        scene->nodes.emplace_back(mAsset->nodes.Get(0u));
1587
0
    }
1588
1589
    // set as the default scene
1590
0
    mAsset->scene = scene;
1591
0
}
1592
1593
0
void glTF2Exporter::ExportMetadata() {
1594
0
    AssetMetadata &asset = mAsset->asset;
1595
0
    asset.version = "2.0";
1596
1597
0
    char buffer[256];
1598
0
    ai_snprintf(buffer, 256, "Open Asset Import Library (assimp v%d.%d.%x)",
1599
0
            aiGetVersionMajor(), aiGetVersionMinor(), aiGetVersionRevision());
1600
1601
0
    asset.generator = buffer;
1602
1603
    // Copyright
1604
0
    aiString copyright_str;
1605
0
    if (mScene->mMetaData != nullptr && mScene->mMetaData->Get(AI_METADATA_SOURCE_COPYRIGHT, copyright_str)) {
1606
0
        asset.copyright = copyright_str.C_Str();
1607
0
    }
1608
0
}
1609
1610
0
inline Ref<Accessor> GetSamplerInputRef(Asset &asset, std::string &animId, Ref<Buffer> &buffer, std::vector<ai_real> &times) {
1611
0
    return ExportData(asset, animId, buffer, (unsigned int)times.size(), &times[0], AttribType::SCALAR, AttribType::SCALAR, ComponentType_FLOAT);
1612
0
}
1613
1614
0
inline void ExtractTranslationSampler(Asset &asset, std::string &animId, Ref<Buffer> &buffer, const aiNodeAnim *nodeChannel, float ticksPerSecond, Animation::Sampler &sampler) {
1615
0
    const unsigned int numKeyframes = nodeChannel->mNumPositionKeys;
1616
1617
0
    std::vector<ai_real> times(numKeyframes);
1618
0
    std::vector<ai_real> values(numKeyframes * 3);
1619
0
    for (unsigned int i = 0; i < numKeyframes; ++i) {
1620
0
        const aiVectorKey &key = nodeChannel->mPositionKeys[i];
1621
        // mTime is measured in ticks, but GLTF time is measured in seconds, so convert.
1622
0
        times[i] = static_cast<float>(key.mTime / ticksPerSecond);
1623
0
        values[(i * 3) + 0] = (ai_real)key.mValue.x;
1624
0
        values[(i * 3) + 1] = (ai_real)key.mValue.y;
1625
0
        values[(i * 3) + 2] = (ai_real)key.mValue.z;
1626
0
    }
1627
1628
0
    sampler.input = GetSamplerInputRef(asset, animId, buffer, times);
1629
0
    sampler.output = ExportData(asset, animId, buffer, numKeyframes, &values[0], AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT);
1630
0
    sampler.interpolation = Interpolation_LINEAR;
1631
0
}
1632
1633
0
inline void ExtractScaleSampler(Asset &asset, std::string &animId, Ref<Buffer> &buffer, const aiNodeAnim *nodeChannel, float ticksPerSecond, Animation::Sampler &sampler) {
1634
0
    const unsigned int numKeyframes = nodeChannel->mNumScalingKeys;
1635
1636
0
    std::vector<ai_real> times(numKeyframes);
1637
0
    std::vector<ai_real> values(numKeyframes * 3);
1638
0
    for (unsigned int i = 0; i < numKeyframes; ++i) {
1639
0
        const aiVectorKey &key = nodeChannel->mScalingKeys[i];
1640
        // mTime is measured in ticks, but GLTF time is measured in seconds, so convert.
1641
0
        times[i] = static_cast<float>(key.mTime / ticksPerSecond);
1642
0
        values[(i * 3) + 0] = (ai_real)key.mValue.x;
1643
0
        values[(i * 3) + 1] = (ai_real)key.mValue.y;
1644
0
        values[(i * 3) + 2] = (ai_real)key.mValue.z;
1645
0
    }
1646
1647
0
    sampler.input = GetSamplerInputRef(asset, animId, buffer, times);
1648
0
    sampler.output = ExportData(asset, animId, buffer, numKeyframes, &values[0], AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT);
1649
0
    sampler.interpolation = Interpolation_LINEAR;
1650
0
}
1651
1652
0
inline void ExtractRotationSampler(Asset &asset, std::string &animId, Ref<Buffer> &buffer, const aiNodeAnim *nodeChannel, float ticksPerSecond, Animation::Sampler &sampler) {
1653
0
    const unsigned int numKeyframes = nodeChannel->mNumRotationKeys;
1654
1655
0
    std::vector<ai_real> times(numKeyframes);
1656
0
    std::vector<ai_real> values(numKeyframes * 4);
1657
0
    for (unsigned int i = 0; i < numKeyframes; ++i) {
1658
0
        const aiQuatKey &key = nodeChannel->mRotationKeys[i];
1659
        // mTime is measured in ticks, but GLTF time is measured in seconds, so convert.
1660
0
        times[i] = static_cast<float>(key.mTime / ticksPerSecond);
1661
0
        values[(i * 4) + 0] = (ai_real)key.mValue.x;
1662
0
        values[(i * 4) + 1] = (ai_real)key.mValue.y;
1663
0
        values[(i * 4) + 2] = (ai_real)key.mValue.z;
1664
0
        values[(i * 4) + 3] = (ai_real)key.mValue.w;
1665
0
    }
1666
1667
0
    sampler.input = GetSamplerInputRef(asset, animId, buffer, times);
1668
0
    sampler.output = ExportData(asset, animId, buffer, numKeyframes, &values[0], AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT);
1669
0
    sampler.interpolation = Interpolation_LINEAR;
1670
0
}
1671
1672
0
static void AddSampler(Ref<Animation> &animRef, Ref<Node> &nodeRef, Animation::Sampler &sampler, AnimationPath path) {
1673
0
    Animation::Channel channel;
1674
0
    channel.sampler = static_cast<int>(animRef->samplers.size());
1675
0
    channel.target.path = path;
1676
0
    channel.target.node = nodeRef;
1677
0
    animRef->channels.push_back(channel);
1678
0
    animRef->samplers.push_back(sampler);
1679
0
}
1680
1681
0
void glTF2Exporter::ExportAnimations() {
1682
0
    Ref<Buffer> bufferRef = mAsset->buffers.Get(unsigned(0));
1683
1684
0
    for (unsigned int i = 0; i < mScene->mNumAnimations; ++i) {
1685
0
        const aiAnimation *anim = mScene->mAnimations[i];
1686
0
        const float ticksPerSecond = static_cast<float>(anim->mTicksPerSecond);
1687
1688
0
        std::string nameAnim = "anim";
1689
0
        if (anim->mName.length > 0) {
1690
0
            nameAnim = anim->mName.C_Str();
1691
0
        }
1692
0
        Ref<Animation> animRef = mAsset->animations.Create(nameAnim);
1693
0
        animRef->name = nameAnim;
1694
1695
0
        for (unsigned int channelIndex = 0; channelIndex < anim->mNumChannels; ++channelIndex) {
1696
0
            const aiNodeAnim *nodeChannel = anim->mChannels[channelIndex];
1697
1698
0
            std::string name = nameAnim + "_" + ai_to_string(channelIndex);
1699
0
            name = mAsset->FindUniqueID(name, "animation");
1700
1701
0
            Ref<Node> animNode = mAsset->nodes.Get(nodeChannel->mNodeName.C_Str());
1702
1703
0
            if (nodeChannel->mNumPositionKeys > 0) {
1704
0
                Animation::Sampler translationSampler;
1705
0
                ExtractTranslationSampler(*mAsset, name, bufferRef, nodeChannel, ticksPerSecond, translationSampler);
1706
0
                AddSampler(animRef, animNode, translationSampler, AnimationPath_TRANSLATION);
1707
0
            }
1708
1709
0
            if (nodeChannel->mNumRotationKeys > 0) {
1710
0
                Animation::Sampler rotationSampler;
1711
0
                ExtractRotationSampler(*mAsset, name, bufferRef, nodeChannel, ticksPerSecond, rotationSampler);
1712
0
                AddSampler(animRef, animNode, rotationSampler, AnimationPath_ROTATION);
1713
0
            }
1714
1715
0
            if (nodeChannel->mNumScalingKeys > 0) {
1716
0
                Animation::Sampler scaleSampler;
1717
0
                ExtractScaleSampler(*mAsset, name, bufferRef, nodeChannel, ticksPerSecond, scaleSampler);
1718
0
                AddSampler(animRef, animNode, scaleSampler, AnimationPath_SCALE);
1719
0
            }
1720
0
        }
1721
0
    } // End: for-loop mNumAnimations
1722
0
}
1723
1724
#endif // ASSIMP_BUILD_NO_GLTF_EXPORTER
1725
#endif // ASSIMP_BUILD_NO_EXPORT