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