Coverage Report

Created: 2025-05-06 06:42

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