Coverage Report

Created: 2026-04-29 07:04

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