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