Coverage Report

Created: 2026-10-28 07:46

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