1 | /* |
2 | Open Asset Import Library (assimp) |
3 | ---------------------------------------------------------------------- |
4 | |
5 | Copyright (c) 2006-2017, 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 "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 | |
65 | using namespace rapidjson; |
66 | |
67 | using namespace Assimp; |
68 | using namespace glTF2; |
69 | |
70 | namespace 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 | |
82 | glTF2Exporter::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 | */ |
128 | static 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 | |
136 | static 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 | |
144 | static 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 | |
152 | inline 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 | |
219 | inline 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 | |
236 | void 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 | |
278 | void 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 | |
285 | void 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 | |
292 | void 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 | |
340 | void 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 | |
351 | void 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 | |
363 | void 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 | |
375 | aiReturn 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 | |
387 | aiReturn 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 | |
399 | void 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 | */ |
518 | bool 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 | */ |
542 | Ref<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 | |
559 | void 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 | |
638 | void 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 |
788 | void 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 | */ |
842 | unsigned 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 | */ |
867 | unsigned 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 | |
893 | void 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 | |
907 | void 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 | |
919 | inline void (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 | |
1007 | void 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 | |