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