Coverage Report

Created: 2026-05-04 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/glTF/glTFExporter.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/glTF/glTFExporter.h"
45
#include "AssetLib/glTF/glTFAsset.h"
46
#include "AssetLib/glTF/glTFAssetWriter.h"
47
#include "PostProcessing/SplitLargeMeshes.h"
48
49
#include <assimp/commonMetaData.h>
50
#include <assimp/Exceptional.h>
51
#include <assimp/StringComparison.h>
52
#include <assimp/ByteSwapper.h>
53
#include <assimp/SceneCombiner.h>
54
#include <assimp/version.h>
55
#include <assimp/IOSystem.hpp>
56
#include <assimp/Exporter.hpp>
57
#include <assimp/material.h>
58
#include <assimp/scene.h>
59
#include <assimp/config.h>
60
61
// Header files, standard library.
62
#include <memory>
63
#include <limits>
64
#include <inttypes.h>
65
66
#include <rapidjson/rapidjson.h>
67
#include <rapidjson/document.h>
68
#include <rapidjson/error/en.h>
69
70
#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC
71
  // Header files, Open3DGC.
72
# include <Open3DGC/o3dgcSC3DMCEncoder.h>
73
#endif
74
75
using namespace rapidjson;
76
77
using namespace Assimp;
78
using namespace glTF;
79
80
namespace Assimp {
81
82
    // ------------------------------------------------------------------------------------------------
83
    // Worker function for exporting a scene to GLTF. Prototyped and registered in Exporter.cpp
84
    void ExportSceneGLTF(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties)
85
0
    {
86
        // invoke the exporter
87
0
        glTFExporter exporter(pFile, pIOSystem, pScene, pProperties, false);
88
0
    }
89
90
    // ------------------------------------------------------------------------------------------------
91
    // Worker function for exporting a scene to GLB. Prototyped and registered in Exporter.cpp
92
    void ExportSceneGLB(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties)
93
0
    {
94
        // invoke the exporter
95
0
        glTFExporter exporter(pFile, pIOSystem, pScene, pProperties, true);
96
0
    }
97
98
} // end of namespace Assimp
99
100
glTFExporter::glTFExporter(const char* filename, IOSystem* pIOSystem, const aiScene* pScene,
101
                           const ExportProperties* pProperties, bool isBinary)
102
0
    : mFilename(filename)
103
0
    , mIOSystem(pIOSystem)
104
0
    , mProperties(pProperties)
105
0
{
106
0
    aiScene* sceneCopy_tmp;
107
0
    SceneCombiner::CopyScene(&sceneCopy_tmp, pScene);
108
109
0
    SplitLargeMeshesProcess_Triangle tri_splitter;
110
0
    tri_splitter.SetLimit(0xffff);
111
0
    tri_splitter.Execute(sceneCopy_tmp);
112
113
0
    SplitLargeMeshesProcess_Vertex vert_splitter;
114
0
    vert_splitter.SetLimit(0xffff);
115
0
    vert_splitter.Execute(sceneCopy_tmp);
116
117
0
    mScene.reset(sceneCopy_tmp);
118
119
0
    mAsset = std::make_shared<glTF::Asset>(pIOSystem);
120
121
0
    configEpsilon = mProperties->GetPropertyFloat(
122
0
        AI_CONFIG_CHECK_IDENTITY_MATRIX_EPSILON,
123
0
                (ai_real)AI_CONFIG_CHECK_IDENTITY_MATRIX_EPSILON_DEFAULT);
124
125
0
    if (isBinary) {
126
0
        mAsset->SetAsBinary();
127
0
    }
128
129
0
    ExportMetadata();
130
131
    //for (unsigned int i = 0; i < pScene->mNumCameras; ++i) {}
132
133
    //for (unsigned int i = 0; i < pScene->mNumLights; ++i) {}
134
135
0
    ExportMaterials();
136
137
0
    if (mScene->mRootNode) {
138
0
        ExportNodeHierarchy(mScene->mRootNode);
139
0
    }
140
141
0
    ExportMeshes();
142
143
    //for (unsigned int i = 0; i < pScene->mNumTextures; ++i) {}
144
145
0
    ExportScene();
146
147
0
    ExportAnimations();
148
149
0
    glTF::AssetWriter writer(*mAsset);
150
151
0
    if (isBinary) {
152
0
        writer.WriteGLBFile(filename);
153
0
    } else {
154
0
        writer.WriteFile(filename);
155
0
    }
156
0
}
157
158
/*
159
 * Copy a 4x4 matrix from struct aiMatrix to typedef mat4.
160
 * Also converts from row-major to column-major storage.
161
 */
162
static void CopyValue(const aiMatrix4x4& v, glTF::mat4& o)
163
0
{
164
0
    o[ 0] = v.a1; o[ 1] = v.b1; o[ 2] = v.c1; o[ 3] = v.d1;
165
0
    o[ 4] = v.a2; o[ 5] = v.b2; o[ 6] = v.c2; o[ 7] = v.d2;
166
0
    o[ 8] = v.a3; o[ 9] = v.b3; o[10] = v.c3; o[11] = v.d3;
167
0
    o[12] = v.a4; o[13] = v.b4; o[14] = v.c4; o[15] = v.d4;
168
0
}
169
170
static void CopyValue(const aiMatrix4x4& v, aiMatrix4x4& o)
171
0
{
172
0
    memcpy(&o, &v, sizeof(aiMatrix4x4));
173
0
}
174
175
static void IdentityMatrix4(glTF::mat4& o)
176
0
{
177
0
    o[ 0] = 1; o[ 1] = 0; o[ 2] = 0; o[ 3] = 0;
178
0
    o[ 4] = 0; o[ 5] = 1; o[ 6] = 0; o[ 7] = 0;
179
0
    o[ 8] = 0; o[ 9] = 0; o[10] = 1; o[11] = 0;
180
0
    o[12] = 0; o[13] = 0; o[14] = 0; o[15] = 1;
181
0
}
182
183
template<typename T>
184
void SetAccessorRange(Ref<Accessor> acc, void* data, unsigned int count,
185
  unsigned int numCompsIn, unsigned int numCompsOut)
186
0
{
187
0
  ai_assert(numCompsOut <= numCompsIn);
188
189
  // Allocate and initialize with large values.
190
0
  for (unsigned int i = 0 ; i < numCompsOut ; i++) {
191
0
    acc->min.push_back( std::numeric_limits<double>::max());
192
0
    acc->max.push_back(-std::numeric_limits<double>::max());
193
0
  }
194
195
0
  size_t totalComps = count * numCompsIn;
196
0
  T* buffer_ptr = static_cast<T*>(data);
197
0
  T* buffer_end = buffer_ptr + totalComps;
198
199
  // Search and set extreme values.
200
0
  for (; buffer_ptr < buffer_end ; buffer_ptr += numCompsIn) {
201
0
    for (unsigned int j = 0 ; j < numCompsOut ; j++) {
202
0
      double valueTmp = buffer_ptr[j];
203
204
0
      if (valueTmp < acc->min[j]) {
205
0
        acc->min[j] = valueTmp;
206
0
      }
207
0
      if (valueTmp > acc->max[j]) {
208
0
        acc->max[j] = valueTmp;
209
0
      }
210
0
    }
211
0
  }
212
0
}
Unexecuted instantiation: void SetAccessorRange<short>(glTFCommon::Ref<glTF::Accessor>, void*, unsigned int, unsigned int, unsigned int)
Unexecuted instantiation: void SetAccessorRange<unsigned short>(glTFCommon::Ref<glTF::Accessor>, void*, unsigned int, unsigned int, unsigned int)
Unexecuted instantiation: void SetAccessorRange<unsigned int>(glTFCommon::Ref<glTF::Accessor>, void*, unsigned int, unsigned int, unsigned int)
Unexecuted instantiation: void SetAccessorRange<float>(glTFCommon::Ref<glTF::Accessor>, void*, unsigned int, unsigned int, unsigned int)
Unexecuted instantiation: void SetAccessorRange<signed char>(glTFCommon::Ref<glTF::Accessor>, void*, unsigned int, unsigned int, unsigned int)
Unexecuted instantiation: void SetAccessorRange<unsigned char>(glTFCommon::Ref<glTF::Accessor>, void*, unsigned int, unsigned int, unsigned int)
213
214
inline void SetAccessorRange(ComponentType compType, Ref<Accessor> acc, void* data,
215
    unsigned int count, unsigned int numCompsIn, unsigned int numCompsOut)
216
0
{
217
0
  switch (compType) {
218
0
    case ComponentType_SHORT:
219
0
      SetAccessorRange<short>(acc, data, count, numCompsIn, numCompsOut);
220
0
      return;
221
0
    case ComponentType_UNSIGNED_SHORT:
222
0
      SetAccessorRange<unsigned short>(acc, data, count, numCompsIn, numCompsOut);
223
0
      return;
224
0
    case ComponentType_UNSIGNED_INT:
225
0
      SetAccessorRange<unsigned int>(acc, data, count, numCompsIn, numCompsOut);
226
0
      return;
227
0
    case ComponentType_FLOAT:
228
0
      SetAccessorRange<float>(acc, data, count, numCompsIn, numCompsOut);
229
0
      return;
230
0
    case ComponentType_BYTE:
231
0
      SetAccessorRange<int8_t>(acc, data, count, numCompsIn, numCompsOut);
232
0
      return;
233
0
    case ComponentType_UNSIGNED_BYTE:
234
0
      SetAccessorRange<uint8_t>(acc, data, count, numCompsIn, numCompsOut);
235
0
      return;
236
0
  }
237
0
}
238
239
inline Ref<Accessor> ExportData(Asset &a, std::string &meshName, Ref<Buffer> &buffer,
240
0
        unsigned int count, void *data, AttribType::Value typeIn, AttribType::Value typeOut, ComponentType compType, BufferViewTarget target = BufferViewTarget_NONE) {
241
0
    if (!count || !data) return Ref<Accessor>();
242
243
0
    unsigned int numCompsIn = AttribType::GetNumComponents(typeIn);
244
0
    unsigned int numCompsOut = AttribType::GetNumComponents(typeOut);
245
0
    unsigned int bytesPerComp = ComponentTypeSize(compType);
246
247
0
    size_t offset = buffer->byteLength;
248
    // make sure offset is correctly byte-aligned, as required by spec
249
0
    size_t padding = offset % bytesPerComp;
250
0
    offset += padding;
251
0
    size_t length = count * numCompsOut * bytesPerComp;
252
0
    buffer->Grow(length + padding);
253
254
    // bufferView
255
0
    Ref<BufferView> bv = a.bufferViews.Create(a.FindUniqueID(meshName, "view"));
256
0
    bv->buffer = buffer;
257
0
    bv->byteOffset = unsigned(offset);
258
0
    bv->byteLength = length; //! The target that the WebGL buffer should be bound to.
259
0
    bv->target = target;
260
261
    // accessor
262
0
    Ref<Accessor> acc = a.accessors.Create(a.FindUniqueID(meshName, "accessor"));
263
0
    acc->bufferView = bv;
264
0
    acc->byteOffset = 0;
265
0
    acc->byteStride = 0;
266
0
    acc->componentType = compType;
267
0
    acc->count = count;
268
0
    acc->type = typeOut;
269
270
    // calculate min and max values
271
0
  SetAccessorRange(compType, acc, data, count, numCompsIn, numCompsOut);
272
273
    // copy the data
274
0
    acc->WriteData(count, data, numCompsIn*bytesPerComp);
275
276
0
    return acc;
277
0
}
278
279
namespace {
280
0
    void GetMatScalar(const aiMaterial* mat, float& val, const char* propName, int type, int idx) {
281
0
        ai_assert( nullptr != mat );
282
0
        if ( nullptr != mat ) {
283
0
            mat->Get(propName, type, idx, val);
284
0
        }
285
0
    }
286
}
287
288
void glTFExporter::GetTexSampler(const aiMaterial* mat, glTF::TexProperty& prop)
289
0
{
290
0
    std::string samplerId = mAsset->FindUniqueID("", "sampler");
291
0
    prop.texture->sampler = mAsset->samplers.Create(samplerId);
292
293
0
    aiTextureMapMode mapU, mapV;
294
0
    aiGetMaterialInteger(mat,AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0),(int*)&mapU);
295
0
    aiGetMaterialInteger(mat,AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0),(int*)&mapV);
296
297
0
    switch (mapU) {
298
0
        case aiTextureMapMode_Wrap:
299
0
            prop.texture->sampler->wrapS = SamplerWrap_Repeat;
300
0
            break;
301
0
        case aiTextureMapMode_Clamp:
302
0
            prop.texture->sampler->wrapS = SamplerWrap_Clamp_To_Edge;
303
0
            break;
304
0
        case aiTextureMapMode_Mirror:
305
0
            prop.texture->sampler->wrapS = SamplerWrap_Mirrored_Repeat;
306
0
            break;
307
0
        case aiTextureMapMode_Decal:
308
0
        default:
309
0
            prop.texture->sampler->wrapS = SamplerWrap_Repeat;
310
0
            break;
311
0
    };
312
313
0
    switch (mapV) {
314
0
        case aiTextureMapMode_Wrap:
315
0
            prop.texture->sampler->wrapT = SamplerWrap_Repeat;
316
0
            break;
317
0
        case aiTextureMapMode_Clamp:
318
0
            prop.texture->sampler->wrapT = SamplerWrap_Clamp_To_Edge;
319
0
            break;
320
0
        case aiTextureMapMode_Mirror:
321
0
            prop.texture->sampler->wrapT = SamplerWrap_Mirrored_Repeat;
322
0
            break;
323
0
        case aiTextureMapMode_Decal:
324
0
        default:
325
0
            prop.texture->sampler->wrapT = SamplerWrap_Repeat;
326
0
            break;
327
0
    };
328
329
    // Hard coded Texture filtering options because I do not know where to find them in the aiMaterial.
330
0
    prop.texture->sampler->magFilter = SamplerMagFilter_Linear;
331
0
    prop.texture->sampler->minFilter = SamplerMinFilter_Linear;
332
0
}
333
334
void glTFExporter::GetMatColorOrTex(const aiMaterial* mat, glTF::TexProperty& prop,
335
0
        const char* propName, int type, int idx, aiTextureType tt) {
336
0
    aiString tex;
337
0
    aiColor4D col;
338
0
    if (mat->GetTextureCount(tt) > 0) {
339
0
        if (mat->Get(AI_MATKEY_TEXTURE(tt, 0), tex) == AI_SUCCESS) {
340
0
            std::string path = tex.C_Str();
341
342
0
            if (path.size() > 0) {
343
0
                if (path[0] != '*') {
344
0
                    std::map<std::string, unsigned int>::iterator it = mTexturesByPath.find(path);
345
0
                    if (it != mTexturesByPath.end()) {
346
0
                        prop.texture = mAsset->textures.Get(it->second);
347
0
                    }
348
0
                }
349
350
0
                if (!prop.texture) {
351
0
                    std::string texId = mAsset->FindUniqueID("", "texture");
352
0
                    prop.texture = mAsset->textures.Create(texId);
353
0
                    mTexturesByPath[path] = prop.texture.GetIndex();
354
355
0
                    std::string imgId = mAsset->FindUniqueID("", "image");
356
0
                    prop.texture->source = mAsset->images.Create(imgId);
357
358
0
                    if (path[0] == '*') { // embedded
359
0
                        aiTexture* curTex = mScene->mTextures[atoi(&path[1])];
360
361
0
                        prop.texture->source->name = curTex->mFilename.C_Str();
362
363
0
                        uint8_t *data = reinterpret_cast<uint8_t *>(curTex->pcData);
364
0
                        prop.texture->source->SetData(data, curTex->mWidth, *mAsset);
365
366
0
                        if (curTex->achFormatHint[0]) {
367
0
                            std::string mimeType = "image/";
368
0
                            mimeType += (memcmp(curTex->achFormatHint, "jpg", 3) == 0) ? "jpeg" : curTex->achFormatHint;
369
0
                            prop.texture->source->mimeType = mimeType;
370
0
                        }
371
0
                    } else {
372
0
                        prop.texture->source->uri = path;
373
0
                    }
374
375
0
                    GetTexSampler(mat, prop);
376
0
                }
377
0
            }
378
0
        }
379
0
    }
380
381
0
    if (mat->Get(propName, type, idx, col) == AI_SUCCESS) {
382
0
        prop.color[0] = col.r;
383
0
        prop.color[1] = col.g;
384
0
        prop.color[2] = col.b;
385
0
        prop.color[3] = col.a;
386
0
    }
387
0
}
388
389
390
void glTFExporter::ExportMaterials()
391
0
{
392
0
    aiString aiName;
393
0
    for (unsigned int i = 0; i < mScene->mNumMaterials; ++i) {
394
0
        const aiMaterial* mat = mScene->mMaterials[i];
395
396
397
0
        std::string name;
398
0
        if (mat->Get(AI_MATKEY_NAME, aiName) == AI_SUCCESS) {
399
0
            name = aiName.C_Str();
400
0
        }
401
0
        name = mAsset->FindUniqueID(name, "material");
402
403
0
        Ref<Material> m = mAsset->materials.Create(name);
404
405
0
        GetMatColorOrTex(mat, m->ambient, AI_MATKEY_COLOR_AMBIENT, aiTextureType_AMBIENT);
406
0
        GetMatColorOrTex(mat, m->diffuse, AI_MATKEY_COLOR_DIFFUSE, aiTextureType_DIFFUSE);
407
0
        GetMatColorOrTex(mat, m->specular, AI_MATKEY_COLOR_SPECULAR, aiTextureType_SPECULAR);
408
0
        GetMatColorOrTex(mat, m->emission, AI_MATKEY_COLOR_EMISSIVE, aiTextureType_EMISSIVE);
409
410
0
        m->transparent = mat->Get(AI_MATKEY_OPACITY, m->transparency) == aiReturn_SUCCESS && m->transparency != 1.0;
411
412
0
        GetMatScalar(mat, m->shininess, AI_MATKEY_SHININESS);
413
0
    }
414
0
}
415
416
/*
417
 * Search through node hierarchy and find the node containing the given meshID.
418
 * Returns true on success, and false otherwise.
419
 */
420
0
bool FindMeshNode(Ref<Node> &nodeIn, Ref<Node> &meshNode, const std::string &meshID) {
421
0
    for (unsigned int i = 0; i < nodeIn->meshes.size(); ++i) {
422
0
        if (meshID.compare(nodeIn->meshes[i]->id) == 0) {
423
0
          meshNode = nodeIn;
424
0
          return true;
425
0
        }
426
0
    }
427
428
0
    for (unsigned int i = 0; i < nodeIn->children.size(); ++i) {
429
0
        if(FindMeshNode(nodeIn->children[i], meshNode, meshID)) {
430
0
          return true;
431
0
        }
432
0
    }
433
434
0
    return false;
435
0
}
436
437
/*
438
 * Find the root joint of the skeleton.
439
 * Starts will any joint node and traces up the tree,
440
 * until a parent is found that does not have a jointName.
441
 * Returns the first parent Ref<Node> found that does not have a jointName.
442
 */
443
Ref<Node> FindSkeletonRootJoint(Ref<Skin>& skinRef)
444
0
{
445
0
    Ref<Node> startNodeRef;
446
0
    Ref<Node> parentNodeRef;
447
448
    // Arbitrarily use the first joint to start the search.
449
0
    startNodeRef = skinRef->jointNames[0];
450
0
    parentNodeRef = skinRef->jointNames[0];
451
452
0
    do {
453
0
        startNodeRef = parentNodeRef;
454
0
        parentNodeRef = startNodeRef->parent;
455
0
    } while (!parentNodeRef->jointName.empty());
456
457
0
    return parentNodeRef;
458
0
}
459
460
void ExportSkin(Asset& mAsset, const aiMesh* aimesh, Ref<Mesh>& meshRef, Ref<Buffer>& bufferRef, Ref<Skin>& skinRef, std::vector<aiMatrix4x4>& inverseBindMatricesData)
461
0
{
462
0
    if (aimesh->mNumBones < 1) {
463
0
        return;
464
0
    }
465
466
    // Store the vertex joint and weight data.
467
0
    const size_t NumVerts( aimesh->mNumVertices );
468
0
    vec4* vertexJointData = new vec4[ NumVerts ];
469
0
    vec4* vertexWeightData = new vec4[ NumVerts ];
470
0
    int* jointsPerVertex = new int[ NumVerts ];
471
0
    for (size_t i = 0; i < NumVerts; ++i) {
472
0
        jointsPerVertex[i] = 0;
473
0
        for (size_t j = 0; j < 4; ++j) {
474
0
            vertexJointData[i][j] = 0;
475
0
            vertexWeightData[i][j] = 0;
476
0
        }
477
0
    }
478
479
0
    for (unsigned int idx_bone = 0; idx_bone < aimesh->mNumBones; ++idx_bone) {
480
0
        const aiBone* aib = aimesh->mBones[idx_bone];
481
482
        // aib->mName   =====>  skinRef->jointNames
483
        // Find the node with id = mName.
484
0
        Ref<Node> nodeRef = mAsset.nodes.Get(aib->mName.C_Str());
485
0
        nodeRef->jointName = nodeRef->id;
486
487
0
        unsigned int jointNamesIndex = 0;
488
0
        bool addJointToJointNames = true;
489
0
        for ( unsigned int idx_joint = 0; idx_joint < skinRef->jointNames.size(); ++idx_joint) {
490
0
            if (skinRef->jointNames[idx_joint]->jointName.compare(nodeRef->jointName) == 0) {
491
0
                addJointToJointNames = false;
492
0
                jointNamesIndex = idx_joint;
493
0
            }
494
0
        }
495
496
0
        if (addJointToJointNames) {
497
0
            skinRef->jointNames.push_back(nodeRef);
498
499
            // aib->mOffsetMatrix   =====>  skinRef->inverseBindMatrices
500
0
            aiMatrix4x4 tmpMatrix4;
501
0
            CopyValue(aib->mOffsetMatrix, tmpMatrix4);
502
0
            inverseBindMatricesData.push_back(tmpMatrix4);
503
0
            jointNamesIndex = static_cast<unsigned int>(inverseBindMatricesData.size() - 1);
504
0
        }
505
506
        // aib->mWeights   =====>  vertexWeightData
507
0
        for (unsigned int idx_weights = 0; idx_weights < aib->mNumWeights; ++idx_weights) {
508
0
            unsigned int vertexId = aib->mWeights[idx_weights].mVertexId;
509
0
            float vertWeight      = aib->mWeights[idx_weights].mWeight;
510
511
            // A vertex can only have at most four joint weights. Ignore all others.
512
0
            if (jointsPerVertex[vertexId] > 3) {
513
0
                continue;
514
0
            }
515
516
0
            vertexJointData[vertexId][jointsPerVertex[vertexId]] = static_cast<float>(jointNamesIndex);
517
0
            vertexWeightData[vertexId][jointsPerVertex[vertexId]] = vertWeight;
518
519
0
            jointsPerVertex[vertexId] += 1;
520
0
        }
521
522
0
    } // End: for-loop mNumMeshes
523
524
0
    Mesh::Primitive& p = meshRef->primitives.back();
525
0
    Ref<Accessor> vertexJointAccessor = ExportData(mAsset, skinRef->id, bufferRef, aimesh->mNumVertices, vertexJointData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT);
526
0
    if ( vertexJointAccessor ) {
527
0
        p.attributes.joint.push_back( vertexJointAccessor );
528
0
    }
529
530
0
    Ref<Accessor> vertexWeightAccessor = ExportData(mAsset, skinRef->id, bufferRef, aimesh->mNumVertices, vertexWeightData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT);
531
0
    if ( vertexWeightAccessor ) {
532
0
        p.attributes.weight.push_back( vertexWeightAccessor );
533
0
    }
534
0
    delete[] jointsPerVertex;
535
0
    delete[] vertexWeightData;
536
0
    delete[] vertexJointData;
537
0
}
538
539
#if defined(__has_warning)
540
#if __has_warning("-Wunused-but-set-variable")
541
#pragma GCC diagnostic push
542
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
543
#endif
544
#endif
545
546
void glTFExporter::ExportMeshes()
547
0
{
548
    // Not for
549
    //     using IndicesType = decltype(aiFace::mNumIndices);
550
    // But yes for
551
    //     using IndicesType = unsigned short;
552
    // because "ComponentType_UNSIGNED_SHORT" used for indices. And it's a maximal type according to glTF specification.
553
0
    typedef unsigned short IndicesType;
554
555
    // Variables needed for compression. BEGIN.
556
    // Indices, not pointers - because pointer to buffer is changing while writing to it.
557
0
#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC
558
0
    size_t idx_srcdata_begin = 0; // Index of buffer before writing mesh data. Also, index of begin of coordinates array in buffer.
559
0
    size_t idx_srcdata_normal = SIZE_MAX;// Index of begin of normals array in buffer. SIZE_MAX - mean that mesh has no normals.
560
0
    size_t idx_srcdata_ind;// Index of begin of coordinates indices array in buffer.
561
0
#endif
562
0
    std::vector<size_t> idx_srcdata_tc;// Array of indices. Every index point to begin of texture coordinates array in buffer.
563
0
    bool comp_allow;// Point that data of current mesh can be compressed.
564
    // Variables needed for compression. END.
565
566
0
    std::string fname = std::string(mFilename);
567
0
    std::string bufferIdPrefix = fname.substr(0, fname.rfind(".gltf"));
568
0
    std::string bufferId = mAsset->FindUniqueID("", bufferIdPrefix.c_str());
569
570
0
    Ref<Buffer> b = mAsset->GetBodyBuffer();
571
0
    if (!b) {
572
0
       b = mAsset->buffers.Create(bufferId);
573
0
    }
574
575
    //----------------------------------------
576
    // Initialize variables for the skin
577
0
    bool createSkin = false;
578
0
    for (unsigned int idx_mesh = 0; idx_mesh < mScene->mNumMeshes; ++idx_mesh) {
579
0
        const aiMesh* aim = mScene->mMeshes[idx_mesh];
580
0
        if(aim->HasBones()) {
581
0
            createSkin = true;
582
0
            break;
583
0
        }
584
0
    }
585
586
0
    Ref<Skin> skinRef;
587
0
    std::string skinName = mAsset->FindUniqueID("skin", "skin");
588
0
    std::vector<aiMatrix4x4> inverseBindMatricesData;
589
0
    if(createSkin) {
590
0
        skinRef = mAsset->skins.Create(skinName);
591
0
        skinRef->name = skinName;
592
0
    }
593
    //----------------------------------------
594
595
0
  for (unsigned int idx_mesh = 0; idx_mesh < mScene->mNumMeshes; ++idx_mesh) {
596
0
    const aiMesh* aim = mScene->mMeshes[idx_mesh];
597
598
    // Check if compressing requested and mesh can be encoded.
599
0
#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC
600
0
    comp_allow = mProperties->GetPropertyBool("extensions.Open3DGC.use", false);
601
#else
602
    comp_allow = false;
603
#endif
604
605
0
    if(comp_allow && (aim->mPrimitiveTypes == aiPrimitiveType_TRIANGLE) && (aim->mNumVertices > 0) && (aim->mNumFaces > 0))
606
0
    {
607
0
      idx_srcdata_tc.clear();
608
0
      idx_srcdata_tc.reserve(AI_MAX_NUMBER_OF_TEXTURECOORDS);
609
0
    }
610
0
    else
611
0
    {
612
0
      std::string msg;
613
614
0
      if(aim->mPrimitiveTypes != aiPrimitiveType_TRIANGLE)
615
0
        msg = "all primitives of the mesh must be a triangles.";
616
0
      else
617
0
        msg = "mesh must has vertices and faces.";
618
619
0
            ASSIMP_LOG_WARN("GLTF: can not use Open3DGC-compression: ", msg);
620
0
            comp_allow = false;
621
0
    }
622
623
0
        std::string meshId = mAsset->FindUniqueID(aim->mName.C_Str(), "mesh");
624
0
        Ref<Mesh> m = mAsset->meshes.Create(meshId);
625
0
        m->primitives.resize(1);
626
0
        Mesh::Primitive& p = m->primitives.back();
627
628
0
        p.material = mAsset->materials.Get(aim->mMaterialIndex);
629
630
    /******************* Vertices ********************/
631
    // If compression is used then you need parameters of uncompressed region: begin and size. At this step "begin" is stored.
632
0
#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC
633
0
    if(comp_allow) idx_srcdata_begin = b->byteLength;
634
0
#endif
635
636
0
        Ref<Accessor> v = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mVertices, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER);
637
0
    if (v) p.attributes.position.push_back(v);
638
639
    /******************** Normals ********************/
640
0
#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC
641
0
    if(comp_allow && (aim->mNormals != 0)) idx_srcdata_normal = b->byteLength;// Store index of normals array.
642
0
#endif
643
644
0
    Ref<Accessor> n = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mNormals, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER);
645
0
    if (n) p.attributes.normal.push_back(n);
646
647
    /************** Texture coordinates **************/
648
0
        for (int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
649
            // Flip UV y coords
650
0
            if (aim -> mNumUVComponents[i] > 1) {
651
0
                for (unsigned int j = 0; j < aim->mNumVertices; ++j) {
652
0
                    aim->mTextureCoords[i][j].y = 1 - aim->mTextureCoords[i][j].y;
653
0
                }
654
0
            }
655
656
0
            if (aim->mNumUVComponents[i] > 0) {
657
0
                AttribType::Value type = (aim->mNumUVComponents[i] == 2) ? AttribType::VEC2 : AttribType::VEC3;
658
659
0
        if(comp_allow) idx_srcdata_tc.push_back(b->byteLength);// Store index of texture coordinates array.
660
661
0
        Ref<Accessor> tc = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mTextureCoords[i], AttribType::VEC3, type, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER);
662
0
        if (tc) p.attributes.texcoord.push_back(tc);
663
0
      }
664
0
    }
665
666
    /*************** Vertices indices ****************/
667
0
#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC
668
0
    idx_srcdata_ind = b->byteLength;// Store index of indices array.
669
0
#endif
670
671
0
    if (aim->mNumFaces > 0) {
672
0
      std::vector<IndicesType> indices;
673
0
      unsigned int nIndicesPerFace = aim->mFaces[0].mNumIndices;
674
0
            indices.resize(aim->mNumFaces * nIndicesPerFace);
675
0
            for (size_t i = 0; i < aim->mNumFaces; ++i) {
676
0
                for (size_t j = 0; j < nIndicesPerFace; ++j) {
677
0
                    indices[i*nIndicesPerFace + j] = uint16_t(aim->mFaces[i].mIndices[j]);
678
0
                }
679
0
            }
680
681
0
      p.indices = ExportData(*mAsset, meshId, b, unsigned(indices.size()), &indices[0], AttribType::SCALAR, AttribType::SCALAR, ComponentType_UNSIGNED_SHORT, BufferViewTarget_ELEMENT_ARRAY_BUFFER);
682
0
    }
683
684
0
        switch (aim->mPrimitiveTypes) {
685
0
            case aiPrimitiveType_POLYGON:
686
0
                p.mode = PrimitiveMode_TRIANGLES; break; // TODO implement this
687
0
            case aiPrimitiveType_LINE:
688
0
                p.mode = PrimitiveMode_LINES; break;
689
0
            case aiPrimitiveType_POINT:
690
0
                p.mode = PrimitiveMode_POINTS; break;
691
0
            default: // aiPrimitiveType_TRIANGLE
692
0
                p.mode = PrimitiveMode_TRIANGLES;
693
0
        }
694
695
    /*************** Skins ****************/
696
0
    if(aim->HasBones()) {
697
0
        ExportSkin(*mAsset, aim, m, b, skinRef, inverseBindMatricesData);
698
0
    }
699
700
    /****************** Compression ******************/
701
    ///TODO: animation: weights, joints.
702
0
    if(comp_allow)
703
0
    {
704
0
#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC
705
      // Only one type of compression supported at now - Open3DGC.
706
    //
707
0
      o3dgc::BinaryStream bs;
708
0
      o3dgc::SC3DMCEncoder<IndicesType> encoder;
709
0
      o3dgc::IndexedFaceSet<IndicesType> comp_o3dgc_ifs;
710
0
      o3dgc::SC3DMCEncodeParams comp_o3dgc_params;
711
712
      //
713
      // Fill data for encoder.
714
      //
715
      // Quantization
716
0
      unsigned quant_coord = mProperties->GetPropertyInteger("extensions.Open3DGC.quantization.POSITION", 12);
717
0
      unsigned quant_normal = mProperties->GetPropertyInteger("extensions.Open3DGC.quantization.NORMAL", 10);
718
0
      unsigned quant_texcoord = mProperties->GetPropertyInteger("extensions.Open3DGC.quantization.TEXCOORD", 10);
719
720
      // Prediction
721
0
      o3dgc::O3DGCSC3DMCPredictionMode prediction_position = o3dgc::O3DGC_SC3DMC_PARALLELOGRAM_PREDICTION;
722
0
      o3dgc::O3DGCSC3DMCPredictionMode prediction_normal =  o3dgc::O3DGC_SC3DMC_SURF_NORMALS_PREDICTION;
723
0
      o3dgc::O3DGCSC3DMCPredictionMode prediction_texcoord = o3dgc::O3DGC_SC3DMC_PARALLELOGRAM_PREDICTION;
724
725
      // IndexedFacesSet: "Crease angle", "solid", "convex" are set to default.
726
0
      comp_o3dgc_ifs.SetCCW(true);
727
0
      comp_o3dgc_ifs.SetIsTriangularMesh(true);
728
0
      comp_o3dgc_ifs.SetNumFloatAttributes(0);
729
      // Coordinates
730
0
      comp_o3dgc_params.SetCoordQuantBits(quant_coord);
731
0
      comp_o3dgc_params.SetCoordPredMode(prediction_position);
732
0
      comp_o3dgc_ifs.SetNCoord(aim->mNumVertices);
733
0
      comp_o3dgc_ifs.SetCoord((o3dgc::Real* const)&b->GetPointer()[idx_srcdata_begin]);
734
      // Normals
735
0
      if(idx_srcdata_normal != SIZE_MAX)
736
0
      {
737
0
        comp_o3dgc_params.SetNormalQuantBits(quant_normal);
738
0
        comp_o3dgc_params.SetNormalPredMode(prediction_normal);
739
0
        comp_o3dgc_ifs.SetNNormal(aim->mNumVertices);
740
0
        comp_o3dgc_ifs.SetNormal((o3dgc::Real* const)&b->GetPointer()[idx_srcdata_normal]);
741
0
      }
742
743
      // Texture coordinates
744
0
      for(size_t num_tc = 0; num_tc < idx_srcdata_tc.size(); num_tc++)
745
0
      {
746
0
        size_t num = comp_o3dgc_ifs.GetNumFloatAttributes();
747
748
0
        comp_o3dgc_params.SetFloatAttributeQuantBits(static_cast<unsigned long>(num), quant_texcoord);
749
0
        comp_o3dgc_params.SetFloatAttributePredMode(static_cast<unsigned long>(num), prediction_texcoord);
750
0
        comp_o3dgc_ifs.SetNFloatAttribute(static_cast<unsigned long>(num), aim->mNumVertices);// number of elements.
751
0
        comp_o3dgc_ifs.SetFloatAttributeDim(static_cast<unsigned long>(num), aim->mNumUVComponents[num_tc]);// components per element: aiVector3D => x * float
752
0
        comp_o3dgc_ifs.SetFloatAttributeType(static_cast<unsigned long>(num), o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD);
753
0
        comp_o3dgc_ifs.SetFloatAttribute(static_cast<unsigned long>(num), (o3dgc::Real* const)&b->GetPointer()[idx_srcdata_tc[num_tc]]);
754
0
        comp_o3dgc_ifs.SetNumFloatAttributes(static_cast<unsigned long>(num + 1));
755
0
      }
756
757
      // Coordinates indices
758
0
      comp_o3dgc_ifs.SetNCoordIndex(aim->mNumFaces);
759
0
      comp_o3dgc_ifs.SetCoordIndex((IndicesType* const)&b->GetPointer()[idx_srcdata_ind]);
760
      // Prepare to encoding
761
0
      comp_o3dgc_params.SetNumFloatAttributes(comp_o3dgc_ifs.GetNumFloatAttributes());
762
0
      if(mProperties->GetPropertyBool("extensions.Open3DGC.binary", true))
763
0
        comp_o3dgc_params.SetStreamType(o3dgc::O3DGC_STREAM_TYPE_BINARY);
764
0
      else
765
0
        comp_o3dgc_params.SetStreamType(o3dgc::O3DGC_STREAM_TYPE_ASCII);
766
767
0
      comp_o3dgc_ifs.ComputeMinMax(o3dgc::O3DGC_SC3DMC_MAX_ALL_DIMS);
768
      //
769
      // Encoding
770
      //
771
0
      encoder.Encode(comp_o3dgc_params, comp_o3dgc_ifs, bs);
772
      // Replace data in buffer.
773
0
      b->ReplaceData(idx_srcdata_begin, b->byteLength - idx_srcdata_begin, bs.GetBuffer(), bs.GetSize());
774
      //
775
      // Add information about extension to mesh.
776
      //
777
      // Create extension structure.
778
0
      Mesh::SCompression_Open3DGC* ext = new Mesh::SCompression_Open3DGC;
779
780
      // Fill it.
781
0
      ext->Buffer = b->id;
782
0
      ext->Offset = idx_srcdata_begin;
783
0
      ext->Count = b->byteLength - idx_srcdata_begin;
784
0
      ext->Binary = mProperties->GetPropertyBool("extensions.Open3DGC.binary");
785
0
      ext->IndicesCount = comp_o3dgc_ifs.GetNCoordIndex() * 3;
786
0
      ext->VerticesCount = comp_o3dgc_ifs.GetNCoord();
787
      // And assign to mesh.
788
0
      m->Extension.push_back(ext);
789
0
#endif
790
0
    }// if(comp_allow)
791
0
  }// for (unsigned int i = 0; i < mScene->mNumMeshes; ++i)
792
793
    //----------------------------------------
794
    // Finish the skin
795
    // Create the Accessor for skinRef->inverseBindMatrices
796
0
    if (createSkin) {
797
0
        mat4* invBindMatrixData = new mat4[inverseBindMatricesData.size()];
798
0
        for ( unsigned int idx_joint = 0; idx_joint < inverseBindMatricesData.size(); ++idx_joint) {
799
0
            CopyValue(inverseBindMatricesData[idx_joint], invBindMatrixData[idx_joint]);
800
0
        }
801
802
0
        Ref<Accessor> invBindMatrixAccessor = ExportData(*mAsset, skinName, b, static_cast<unsigned int>(inverseBindMatricesData.size()), invBindMatrixData, AttribType::MAT4, AttribType::MAT4, ComponentType_FLOAT);
803
0
        if (invBindMatrixAccessor) skinRef->inverseBindMatrices = invBindMatrixAccessor;
804
805
        // Identity Matrix   =====>  skinRef->bindShapeMatrix
806
        // Temporary. Hard-coded identity matrix here
807
0
        skinRef->bindShapeMatrix.isPresent = true;
808
0
        IdentityMatrix4(skinRef->bindShapeMatrix.value);
809
810
        // Find node that contains this mesh and add "skeletons" and "skin" attributes to that node.
811
0
        Ref<Node> rootNode = mAsset->nodes.Get(unsigned(0));
812
0
        Ref<Node> meshNode;
813
0
        std::string meshID = mAsset->meshes.Get(unsigned(0))->id;
814
0
        FindMeshNode(rootNode, meshNode, meshID);
815
816
0
        Ref<Node> rootJoint = FindSkeletonRootJoint(skinRef);
817
0
        meshNode->skeletons.push_back(rootJoint);
818
0
        meshNode->skin = skinRef;
819
0
    }
820
0
}
821
822
#if defined(__has_warning)
823
#if __has_warning("-Wunused-but-set-variable")
824
#pragma GCC diagnostic pop
825
#endif
826
#endif
827
828
/*
829
 * Export the root node of the node hierarchy.
830
 * Calls ExportNode for all children.
831
 */
832
unsigned int glTFExporter::ExportNodeHierarchy(const aiNode* n)
833
0
{
834
0
    Ref<Node> node = mAsset->nodes.Create(mAsset->FindUniqueID(n->mName.C_Str(), "node"));
835
836
0
    if (!n->mTransformation.IsIdentity(configEpsilon)) {
837
0
        node->matrix.isPresent = true;
838
0
        CopyValue(n->mTransformation, node->matrix.value);
839
0
    }
840
841
0
    for (unsigned int i = 0; i < n->mNumMeshes; ++i) {
842
0
        node->meshes.push_back(mAsset->meshes.Get(n->mMeshes[i]));
843
0
    }
844
845
0
    for (unsigned int i = 0; i < n->mNumChildren; ++i) {
846
0
        unsigned int idx = ExportNode(n->mChildren[i], node);
847
0
        node->children.push_back(mAsset->nodes.Get(idx));
848
0
    }
849
850
0
    return node.GetIndex();
851
0
}
852
853
/*
854
 * Export node and recursively calls ExportNode for all children.
855
 * Since these nodes are not the root node, we also export the parent Ref<Node>
856
 */
857
unsigned int glTFExporter::ExportNode(const aiNode* n, Ref<Node>& parent)
858
0
{
859
0
    Ref<Node> node = mAsset->nodes.Create(mAsset->FindUniqueID(n->mName.C_Str(), "node"));
860
861
0
    node->parent = parent;
862
863
0
    if (!n->mTransformation.IsIdentity(configEpsilon)) {
864
0
        node->matrix.isPresent = true;
865
0
        CopyValue(n->mTransformation, node->matrix.value);
866
0
    }
867
868
0
    for (unsigned int i = 0; i < n->mNumMeshes; ++i) {
869
0
        node->meshes.push_back(mAsset->meshes.Get(n->mMeshes[i]));
870
0
    }
871
872
0
    for (unsigned int i = 0; i < n->mNumChildren; ++i) {
873
0
        unsigned int idx = ExportNode(n->mChildren[i], node);
874
0
        node->children.push_back(mAsset->nodes.Get(idx));
875
0
    }
876
877
0
    return node.GetIndex();
878
0
}
879
880
881
void glTFExporter::ExportScene()
882
0
{
883
0
    const char* sceneName = "defaultScene";
884
0
    Ref<Scene> scene = mAsset->scenes.Create(sceneName);
885
886
    // root node will be the first one exported (idx 0)
887
0
    if (mAsset->nodes.Size() > 0) {
888
0
        scene->nodes.push_back(mAsset->nodes.Get(0u));
889
0
    }
890
891
    // set as the default scene
892
0
    mAsset->scene = scene;
893
0
}
894
895
void glTFExporter::ExportMetadata()
896
0
{
897
0
    glTF::AssetMetadata& asset = mAsset->asset;
898
0
    asset.version = "1.0";
899
900
0
    char buffer[256];
901
0
    ai_snprintf(buffer, 256, "Open Asset Import Library (assimp v%d.%d.%x)",
902
0
        aiGetVersionMajor(), aiGetVersionMinor(), aiGetVersionRevision());
903
904
0
    asset.generator = buffer;
905
906
  // Copyright
907
0
  aiString copyright_str;
908
0
  if (mScene->mMetaData != nullptr && mScene->mMetaData->Get(AI_METADATA_SOURCE_COPYRIGHT, copyright_str)) {
909
0
    asset.copyright = copyright_str.C_Str();
910
0
  }
911
0
}
912
913
inline void ExtractAnimationData(Asset& mAsset, std::string& animId, Ref<Animation>& animRef, Ref<Buffer>& buffer, const aiNodeAnim* nodeChannel, float ticksPerSecond)
914
0
{
915
    // Loop over the data and check to see if it exactly matches an existing buffer.
916
    //    If yes, then reference the existing corresponding accessor.
917
    //    Otherwise, add to the buffer and create a new accessor.
918
919
0
    size_t counts[3] = {
920
0
        nodeChannel->mNumPositionKeys,
921
0
        nodeChannel->mNumScalingKeys,
922
0
        nodeChannel->mNumRotationKeys,
923
0
    };
924
0
    size_t numKeyframes = 1;
925
0
    for (int i = 0; i < 3; ++i) {
926
0
        if (counts[i] > numKeyframes) {
927
0
            numKeyframes = counts[i];
928
0
        }
929
0
    }
930
931
    //-------------------------------------------------------
932
    // Extract TIME parameter data.
933
    // Check if the timeStamps are the same for mPositionKeys, mRotationKeys, and mScalingKeys.
934
0
    if(nodeChannel->mNumPositionKeys > 0) {
935
0
        typedef float TimeType;
936
0
        std::vector<TimeType> timeData;
937
0
        timeData.resize(numKeyframes);
938
0
        for (size_t i = 0; i < numKeyframes; ++i) {
939
0
            size_t frameIndex = i * nodeChannel->mNumPositionKeys / numKeyframes;
940
            // mTime is measured in ticks, but GLTF time is measured in seconds, so convert.
941
            // Check if we have to cast type here. e.g. uint16_t()
942
0
            timeData[i] = static_cast<float>(nodeChannel->mPositionKeys[frameIndex].mTime / ticksPerSecond);
943
0
        }
944
945
0
        Ref<Accessor> timeAccessor = ExportData(mAsset, animId, buffer, static_cast<unsigned int>(numKeyframes), &timeData[0], AttribType::SCALAR, AttribType::SCALAR, ComponentType_FLOAT);
946
0
        if (timeAccessor) animRef->Parameters.TIME = timeAccessor;
947
0
    }
948
949
    //-------------------------------------------------------
950
    // Extract translation parameter data
951
0
    if(nodeChannel->mNumPositionKeys > 0) {
952
0
        C_STRUCT aiVector3D* translationData = new aiVector3D[numKeyframes];
953
0
        for (size_t i = 0; i < numKeyframes; ++i) {
954
0
            size_t frameIndex = i * nodeChannel->mNumPositionKeys / numKeyframes;
955
0
            translationData[i] = nodeChannel->mPositionKeys[frameIndex].mValue;
956
0
        }
957
958
0
        Ref<Accessor> tranAccessor = ExportData(mAsset, animId, buffer, static_cast<unsigned int>(numKeyframes), translationData, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT);
959
0
        if ( tranAccessor ) {
960
0
            animRef->Parameters.translation = tranAccessor;
961
0
        }
962
0
        delete[] translationData;
963
0
    }
964
965
    //-------------------------------------------------------
966
    // Extract scale parameter data
967
0
    if(nodeChannel->mNumScalingKeys > 0) {
968
0
        C_STRUCT aiVector3D* scaleData = new aiVector3D[numKeyframes];
969
0
        for (size_t i = 0; i < numKeyframes; ++i) {
970
0
            size_t frameIndex = i * nodeChannel->mNumScalingKeys / numKeyframes;
971
0
            scaleData[i] = nodeChannel->mScalingKeys[frameIndex].mValue;
972
0
        }
973
974
0
        Ref<Accessor> scaleAccessor = ExportData(mAsset, animId, buffer, static_cast<unsigned int>(numKeyframes), scaleData, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT);
975
0
        if ( scaleAccessor ) {
976
0
            animRef->Parameters.scale = scaleAccessor;
977
0
        }
978
0
        delete[] scaleData;
979
0
    }
980
981
    //-------------------------------------------------------
982
    // Extract rotation parameter data
983
0
    if(nodeChannel->mNumRotationKeys > 0) {
984
0
        vec4* rotationData = new vec4[numKeyframes];
985
0
        for (size_t i = 0; i < numKeyframes; ++i) {
986
0
            size_t frameIndex = i * nodeChannel->mNumRotationKeys / numKeyframes;
987
0
            rotationData[i][0] = nodeChannel->mRotationKeys[frameIndex].mValue.x;
988
0
            rotationData[i][1] = nodeChannel->mRotationKeys[frameIndex].mValue.y;
989
0
            rotationData[i][2] = nodeChannel->mRotationKeys[frameIndex].mValue.z;
990
0
            rotationData[i][3] = nodeChannel->mRotationKeys[frameIndex].mValue.w;
991
0
        }
992
993
0
        Ref<Accessor> rotAccessor = ExportData(mAsset, animId, buffer, static_cast<unsigned int>(numKeyframes), rotationData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT);
994
0
        if ( rotAccessor ) {
995
0
            animRef->Parameters.rotation = rotAccessor;
996
0
        }
997
0
        delete[] rotationData;
998
0
    }
999
0
}
1000
1001
void glTFExporter::ExportAnimations()
1002
0
{
1003
0
    Ref<Buffer> bufferRef = mAsset->buffers.Get(unsigned (0));
1004
1005
0
    for (unsigned int i = 0; i < mScene->mNumAnimations; ++i) {
1006
0
        const aiAnimation* anim = mScene->mAnimations[i];
1007
1008
0
        std::string nameAnim = "anim";
1009
0
        if (anim->mName.length > 0) {
1010
0
            nameAnim = anim->mName.C_Str();
1011
0
        }
1012
1013
0
        for (unsigned int channelIndex = 0; channelIndex < anim->mNumChannels; ++channelIndex) {
1014
0
            const aiNodeAnim* nodeChannel = anim->mChannels[channelIndex];
1015
1016
            // It appears that assimp stores this type of animation as multiple animations.
1017
            // where each aiNodeAnim in mChannels animates a specific node.
1018
0
            std::string name = nameAnim + "_" + ai_to_string(channelIndex);
1019
0
            name = mAsset->FindUniqueID(name, "animation");
1020
0
            Ref<Animation> animRef = mAsset->animations.Create(name);
1021
1022
            /******************* Parameters ********************/
1023
0
            ExtractAnimationData(*mAsset, name, animRef, bufferRef, nodeChannel, static_cast<float>(anim->mTicksPerSecond));
1024
1025
0
            for (unsigned int j = 0; j < 3; ++j) {
1026
0
                std::string channelType;
1027
0
                int channelSize=0;
1028
0
                switch (j) {
1029
0
                    case 0:
1030
0
                        channelType = "rotation";
1031
0
                        channelSize = nodeChannel->mNumRotationKeys;
1032
0
                        break;
1033
0
                    case 1:
1034
0
                        channelType = "scale";
1035
0
                        channelSize = nodeChannel->mNumScalingKeys;
1036
0
                        break;
1037
0
                    case 2:
1038
0
                        channelType = "translation";
1039
0
                        channelSize = nodeChannel->mNumPositionKeys;
1040
0
                        break;
1041
0
                }
1042
1043
0
                if (channelSize < 1) { continue; }
1044
1045
0
                Animation::AnimChannel tmpAnimChannel;
1046
0
                Animation::AnimSampler tmpAnimSampler;
1047
1048
0
                tmpAnimChannel.sampler = name + "_" + channelType;
1049
0
                tmpAnimChannel.target.path = channelType;
1050
0
                tmpAnimSampler.output = channelType;
1051
0
                tmpAnimSampler.id = name + "_" + channelType;
1052
1053
0
                tmpAnimChannel.target.id = mAsset->nodes.Get(nodeChannel->mNodeName.C_Str());
1054
1055
0
                tmpAnimSampler.input = "TIME";
1056
0
                tmpAnimSampler.interpolation = "LINEAR";
1057
1058
0
                animRef->Channels.push_back(tmpAnimChannel);
1059
0
                animRef->Samplers.push_back(tmpAnimSampler);
1060
0
            }
1061
1062
0
        }
1063
1064
        // Assimp documentation staes this is not used (not implemented)
1065
        // for (unsigned int channelIndex = 0; channelIndex < anim->mNumMeshChannels; ++channelIndex) {
1066
        //     const aiMeshAnim* meshChannel = anim->mMeshChannels[channelIndex];
1067
        // }
1068
1069
0
    } // End: for-loop mNumAnimations
1070
0
}
1071
1072
#endif // ASSIMP_BUILD_NO_GLTF_EXPORTER
1073
#endif // ASSIMP_BUILD_NO_EXPORT