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> ×) {
1414 return ExportData(asset, animId, buffer, (unsigned int)times.size(), ×[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