1 /*
2 Open Asset Import Library (assimp)
3 ----------------------------------------------------------------------
4
5 Copyright (c) 2006-2019, 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_FBX_EXPORTER
43
44 #include "FBXExporter.h"
45 #include "FBXExportNode.h"
46 #include "FBXExportProperty.h"
47 #include "FBXCommon.h"
48 #include "FBXUtil.h"
49
50 #include <assimp/version.h> // aiGetVersion
51 #include <assimp/IOSystem.hpp>
52 #include <assimp/Exporter.hpp>
53 #include <assimp/DefaultLogger.hpp>
54 #include <assimp/StreamWriter.h> // StreamWriterLE
55 #include <assimp/Exceptional.h> // DeadlyExportError
56 #include <assimp/material.h> // aiTextureType
57 #include <assimp/scene.h>
58 #include <assimp/mesh.h>
59
60 // Header files, standard library.
61 #include <memory> // shared_ptr
62 #include <string>
63 #include <sstream> // stringstream
64 #include <ctime> // localtime, tm_*
65 #include <map>
66 #include <set>
67 #include <vector>
68 #include <array>
69 #include <unordered_set>
70 #include <numeric>
71
72 // RESOURCES:
73 // https://code.blender.org/2013/08/fbx-binary-file-format-specification/
74 // https://wiki.blender.org/index.php/User:Mont29/Foundation/FBX_File_Structure
75
76 const ai_real DEG = ai_real( 57.29577951308232087679815481 ); // degrees per radian
77
78 using namespace Assimp;
79 using namespace Assimp::FBX;
80
81 // some constants that we'll use for writing metadata
82 namespace Assimp {
83 namespace FBX {
84 const std::string EXPORT_VERSION_STR = "7.4.0";
85 const uint32_t EXPORT_VERSION_INT = 7400; // 7.4 == 2014/2015
86 // FBX files have some hashed values that depend on the creation time field,
87 // but for now we don't actually know how to generate these.
88 // what we can do is set them to a known-working version.
89 // this is the data that Blender uses in their FBX export process.
90 const std::string GENERIC_CTIME = "1970-01-01 10:00:00:000";
91 const std::string GENERIC_FILEID =
92 "\x28\xb3\x2a\xeb\xb6\x24\xcc\xc2\xbf\xc8\xb0\x2a\xa9\x2b\xfc\xf1";
93 const std::string GENERIC_FOOTID =
94 "\xfa\xbc\xab\x09\xd0\xc8\xd4\x66\xb1\x76\xfb\x83\x1c\xf7\x26\x7e";
95 const std::string FOOT_MAGIC =
96 "\xf8\x5a\x8c\x6a\xde\xf5\xd9\x7e\xec\xe9\x0c\xe3\x75\x8f\x29\x0b";
97 const std::string COMMENT_UNDERLINE =
98 ";------------------------------------------------------------------";
99 }
100
101 // ---------------------------------------------------------------------
102 // Worker function for exporting a scene to binary FBX.
103 // Prototyped and registered in Exporter.cpp
ExportSceneFBX(const char * pFile,IOSystem * pIOSystem,const aiScene * pScene,const ExportProperties * pProperties)104 void ExportSceneFBX (
105 const char* pFile,
106 IOSystem* pIOSystem,
107 const aiScene* pScene,
108 const ExportProperties* pProperties
109 ){
110 // initialize the exporter
111 FBXExporter exporter(pScene, pProperties);
112
113 // perform binary export
114 exporter.ExportBinary(pFile, pIOSystem);
115 }
116
117 // ---------------------------------------------------------------------
118 // Worker function for exporting a scene to ASCII FBX.
119 // Prototyped and registered in Exporter.cpp
ExportSceneFBXA(const char * pFile,IOSystem * pIOSystem,const aiScene * pScene,const ExportProperties * pProperties)120 void ExportSceneFBXA (
121 const char* pFile,
122 IOSystem* pIOSystem,
123 const aiScene* pScene,
124 const ExportProperties* pProperties
125
126 ){
127 // initialize the exporter
128 FBXExporter exporter(pScene, pProperties);
129
130 // perform ascii export
131 exporter.ExportAscii(pFile, pIOSystem);
132 }
133
134 } // end of namespace Assimp
135
FBXExporter(const aiScene * pScene,const ExportProperties * pProperties)136 FBXExporter::FBXExporter ( const aiScene* pScene, const ExportProperties* pProperties )
137 : binary(false)
138 , mScene(pScene)
139 , mProperties(pProperties)
140 , outfile()
141 , connections()
142 , mesh_uids()
143 , material_uids()
144 , node_uids() {
145 // will probably need to determine UIDs, connections, etc here.
146 // basically anything that needs to be known
147 // before we start writing sections to the stream.
148 }
149
ExportBinary(const char * pFile,IOSystem * pIOSystem)150 void FBXExporter::ExportBinary (
151 const char* pFile,
152 IOSystem* pIOSystem
153 ){
154 // remember that we're exporting in binary mode
155 binary = true;
156
157 // we're not currently using these preferences,
158 // but clang will cry about it if we never touch it.
159 // TODO: some of these might be relevant to export
160 (void)mProperties;
161
162 // open the indicated file for writing (in binary mode)
163 outfile.reset(pIOSystem->Open(pFile,"wb"));
164 if (!outfile) {
165 throw DeadlyExportError(
166 "could not open output .fbx file: " + std::string(pFile)
167 );
168 }
169
170 // first a binary-specific file header
171 WriteBinaryHeader();
172
173 // the rest of the file is in node entries.
174 // we have to serialize each entry before we write to the output,
175 // as the first thing we write is the byte offset of the _next_ entry.
176 // Either that or we can skip back to write the offset when we finish.
177 WriteAllNodes();
178
179 // finally we have a binary footer to the file
180 WriteBinaryFooter();
181
182 // explicitly release file pointer,
183 // so we don't have to rely on class destruction.
184 outfile.reset();
185 }
186
ExportAscii(const char * pFile,IOSystem * pIOSystem)187 void FBXExporter::ExportAscii (
188 const char* pFile,
189 IOSystem* pIOSystem
190 ){
191 // remember that we're exporting in ascii mode
192 binary = false;
193
194 // open the indicated file for writing in text mode
195 outfile.reset(pIOSystem->Open(pFile,"wt"));
196 if (!outfile) {
197 throw DeadlyExportError(
198 "could not open output .fbx file: " + std::string(pFile)
199 );
200 }
201
202 // write the ascii header
203 WriteAsciiHeader();
204
205 // write all the sections
206 WriteAllNodes();
207
208 // make sure the file ends with a newline.
209 // note: if the file is opened in text mode,
210 // this should do the right cross-platform thing.
211 outfile->Write("\n", 1, 1);
212
213 // explicitly release file pointer,
214 // so we don't have to rely on class destruction.
215 outfile.reset();
216 }
217
WriteAsciiHeader()218 void FBXExporter::WriteAsciiHeader()
219 {
220 // basically just a comment at the top of the file
221 std::stringstream head;
222 head << "; FBX " << EXPORT_VERSION_STR << " project file\n";
223 head << "; Created by the Open Asset Import Library (Assimp)\n";
224 head << "; http://assimp.org\n";
225 head << "; -------------------------------------------------\n";
226 const std::string ascii_header = head.str();
227 outfile->Write(ascii_header.c_str(), ascii_header.size(), 1);
228 }
229
WriteAsciiSectionHeader(const std::string & title)230 void FBXExporter::WriteAsciiSectionHeader(const std::string& title)
231 {
232 StreamWriterLE outstream(outfile);
233 std::stringstream s;
234 s << "\n\n; " << title << '\n';
235 s << FBX::COMMENT_UNDERLINE << "\n";
236 outstream.PutString(s.str());
237 }
238
WriteBinaryHeader()239 void FBXExporter::WriteBinaryHeader()
240 {
241 // first a specific sequence of 23 bytes, always the same
242 const char binary_header[24] = "Kaydara FBX Binary\x20\x20\x00\x1a\x00";
243 outfile->Write(binary_header, 1, 23);
244
245 // then FBX version number, "multiplied" by 1000, as little-endian uint32.
246 // so 7.3 becomes 7300 == 0x841C0000, 7.4 becomes 7400 == 0xE81C0000, etc
247 {
248 StreamWriterLE outstream(outfile);
249 outstream.PutU4(EXPORT_VERSION_INT);
250 } // StreamWriter destructor writes the data to the file
251
252 // after this the node data starts immediately
253 // (probably with the FBXHEaderExtension node)
254 }
255
WriteBinaryFooter()256 void FBXExporter::WriteBinaryFooter()
257 {
258 outfile->Write(NULL_RECORD.c_str(), NULL_RECORD.size(), 1);
259
260 outfile->Write(GENERIC_FOOTID.c_str(), GENERIC_FOOTID.size(), 1);
261
262 // here some padding is added for alignment to 16 bytes.
263 // if already aligned, the full 16 bytes is added.
264 size_t pos = outfile->Tell();
265 size_t pad = 16 - (pos % 16);
266 for (size_t i = 0; i < pad; ++i) {
267 outfile->Write("\x00", 1, 1);
268 }
269
270 // not sure what this is, but it seems to always be 0 in modern files
271 for (size_t i = 0; i < 4; ++i) {
272 outfile->Write("\x00", 1, 1);
273 }
274
275 // now the file version again
276 {
277 StreamWriterLE outstream(outfile);
278 outstream.PutU4(EXPORT_VERSION_INT);
279 } // StreamWriter destructor writes the data to the file
280
281 // and finally some binary footer added to all files
282 for (size_t i = 0; i < 120; ++i) {
283 outfile->Write("\x00", 1, 1);
284 }
285 outfile->Write(FOOT_MAGIC.c_str(), FOOT_MAGIC.size(), 1);
286 }
287
WriteAllNodes()288 void FBXExporter::WriteAllNodes ()
289 {
290 // header
291 // (and fileid, creation time, creator, if binary)
292 WriteHeaderExtension();
293
294 // global settings
295 WriteGlobalSettings();
296
297 // documents
298 WriteDocuments();
299
300 // references
301 WriteReferences();
302
303 // definitions
304 WriteDefinitions();
305
306 // objects
307 WriteObjects();
308
309 // connections
310 WriteConnections();
311
312 // WriteTakes? (deprecated since at least 2015 (fbx 7.4))
313 }
314
315 //FBXHeaderExtension top-level node
WriteHeaderExtension()316 void FBXExporter::WriteHeaderExtension ()
317 {
318 if (!binary) {
319 // no title, follows directly from the top comment
320 }
321 FBX::Node n("FBXHeaderExtension");
322 StreamWriterLE outstream(outfile);
323 int indent = 0;
324
325 // begin node
326 n.Begin(outstream, binary, indent);
327
328 // write properties
329 // (none)
330
331 // finish properties
332 n.EndProperties(outstream, binary, indent, 0);
333
334 // begin children
335 n.BeginChildren(outstream, binary, indent);
336
337 indent = 1;
338
339 // write child nodes
340 FBX::Node::WritePropertyNode(
341 "FBXHeaderVersion", int32_t(1003), outstream, binary, indent
342 );
343 FBX::Node::WritePropertyNode(
344 "FBXVersion", int32_t(EXPORT_VERSION_INT), outstream, binary, indent
345 );
346 if (binary) {
347 FBX::Node::WritePropertyNode(
348 "EncryptionType", int32_t(0), outstream, binary, indent
349 );
350 }
351
352 FBX::Node CreationTimeStamp("CreationTimeStamp");
353 time_t rawtime;
354 time(&rawtime);
355 struct tm * now = localtime(&rawtime);
356 CreationTimeStamp.AddChild("Version", int32_t(1000));
357 CreationTimeStamp.AddChild("Year", int32_t(now->tm_year + 1900));
358 CreationTimeStamp.AddChild("Month", int32_t(now->tm_mon + 1));
359 CreationTimeStamp.AddChild("Day", int32_t(now->tm_mday));
360 CreationTimeStamp.AddChild("Hour", int32_t(now->tm_hour));
361 CreationTimeStamp.AddChild("Minute", int32_t(now->tm_min));
362 CreationTimeStamp.AddChild("Second", int32_t(now->tm_sec));
363 CreationTimeStamp.AddChild("Millisecond", int32_t(0));
364 CreationTimeStamp.Dump(outstream, binary, indent);
365
366 std::stringstream creator;
367 creator << "Open Asset Import Library (Assimp) " << aiGetVersionMajor()
368 << "." << aiGetVersionMinor() << "." << aiGetVersionRevision();
369 FBX::Node::WritePropertyNode(
370 "Creator", creator.str(), outstream, binary, indent
371 );
372
373 //FBX::Node sceneinfo("SceneInfo");
374 //sceneinfo.AddProperty("GlobalInfo" + FBX::SEPARATOR + "SceneInfo");
375 // not sure if any of this is actually needed,
376 // so just write an empty node for now.
377 //sceneinfo.Dump(outstream, binary, indent);
378
379 indent = 0;
380
381 // finish node
382 n.End(outstream, binary, indent, true);
383
384 // that's it for FBXHeaderExtension...
385 if (!binary) { return; }
386
387 // but binary files also need top-level FileID, CreationTime, Creator:
388 std::vector<uint8_t> raw(GENERIC_FILEID.size());
389 for (size_t i = 0; i < GENERIC_FILEID.size(); ++i) {
390 raw[i] = uint8_t(GENERIC_FILEID[i]);
391 }
392 FBX::Node::WritePropertyNode(
393 "FileId", raw, outstream, binary, indent
394 );
395 FBX::Node::WritePropertyNode(
396 "CreationTime", GENERIC_CTIME, outstream, binary, indent
397 );
398 FBX::Node::WritePropertyNode(
399 "Creator", creator.str(), outstream, binary, indent
400 );
401 }
402
WriteGlobalSettings()403 void FBXExporter::WriteGlobalSettings ()
404 {
405 if (!binary) {
406 // no title, follows directly from the header extension
407 }
408 FBX::Node gs("GlobalSettings");
409 gs.AddChild("Version", int32_t(1000));
410
411 FBX::Node p("Properties70");
412 p.AddP70int("UpAxis", 1);
413 p.AddP70int("UpAxisSign", 1);
414 p.AddP70int("FrontAxis", 2);
415 p.AddP70int("FrontAxisSign", 1);
416 p.AddP70int("CoordAxis", 0);
417 p.AddP70int("CoordAxisSign", 1);
418 p.AddP70int("OriginalUpAxis", 1);
419 p.AddP70int("OriginalUpAxisSign", 1);
420 p.AddP70double("UnitScaleFactor", 1.0);
421 p.AddP70double("OriginalUnitScaleFactor", 1.0);
422 p.AddP70color("AmbientColor", 0.0, 0.0, 0.0);
423 p.AddP70string("DefaultCamera", "Producer Perspective");
424 p.AddP70enum("TimeMode", 11);
425 p.AddP70enum("TimeProtocol", 2);
426 p.AddP70enum("SnapOnFrameMode", 0);
427 p.AddP70time("TimeSpanStart", 0); // TODO: animation support
428 p.AddP70time("TimeSpanStop", FBX::SECOND); // TODO: animation support
429 p.AddP70double("CustomFrameRate", -1.0);
430 p.AddP70("TimeMarker", "Compound", "", ""); // not sure what this is
431 p.AddP70int("CurrentTimeMarker", -1);
432 gs.AddChild(p);
433
434 gs.Dump(outfile, binary, 0);
435 }
436
WriteDocuments()437 void FBXExporter::WriteDocuments ()
438 {
439 if (!binary) {
440 WriteAsciiSectionHeader("Documents Description");
441 }
442
443 // not sure what the use of multiple documents would be,
444 // or whether any end-application supports it
445 FBX::Node docs("Documents");
446 docs.AddChild("Count", int32_t(1));
447 FBX::Node doc("Document");
448
449 // generate uid
450 int64_t uid = generate_uid();
451 doc.AddProperties(uid, "", "Scene");
452 FBX::Node p("Properties70");
453 p.AddP70("SourceObject", "object", "", ""); // what is this even for?
454 p.AddP70string("ActiveAnimStackName", ""); // should do this properly?
455 doc.AddChild(p);
456
457 // UID for root node in scene hierarchy.
458 // always set to 0 in the case of a single document.
459 // not sure what happens if more than one document exists,
460 // but that won't matter to us as we're exporting a single scene.
461 doc.AddChild("RootNode", int64_t(0));
462
463 docs.AddChild(doc);
464 docs.Dump(outfile, binary, 0);
465 }
466
WriteReferences()467 void FBXExporter::WriteReferences ()
468 {
469 if (!binary) {
470 WriteAsciiSectionHeader("Document References");
471 }
472 // always empty for now.
473 // not really sure what this is for.
474 FBX::Node n("References");
475 n.force_has_children = true;
476 n.Dump(outfile, binary, 0);
477 }
478
479
480 // ---------------------------------------------------------------
481 // some internal helper functions used for writing the definitions
482 // (before any actual data is written)
483 // ---------------------------------------------------------------
484
count_nodes(const aiNode * n)485 size_t count_nodes(const aiNode* n) {
486 size_t count = 1;
487 for (size_t i = 0; i < n->mNumChildren; ++i) {
488 count += count_nodes(n->mChildren[i]);
489 }
490 return count;
491 }
492
has_phong_mat(const aiScene * scene)493 bool has_phong_mat(const aiScene* scene)
494 {
495 // just search for any material with a shininess exponent
496 for (size_t i = 0; i < scene->mNumMaterials; ++i) {
497 aiMaterial* mat = scene->mMaterials[i];
498 float shininess = 0;
499 mat->Get(AI_MATKEY_SHININESS, shininess);
500 if (shininess > 0) {
501 return true;
502 }
503 }
504 return false;
505 }
506
count_images(const aiScene * scene)507 size_t count_images(const aiScene* scene) {
508 std::unordered_set<std::string> images;
509 aiString texpath;
510 for (size_t i = 0; i < scene->mNumMaterials; ++i) {
511 aiMaterial* mat = scene->mMaterials[i];
512 for (
513 size_t tt = aiTextureType_DIFFUSE;
514 tt < aiTextureType_UNKNOWN;
515 ++tt
516 ){
517 const aiTextureType textype = static_cast<aiTextureType>(tt);
518 const size_t texcount = mat->GetTextureCount(textype);
519 for (unsigned int j = 0; j < texcount; ++j) {
520 mat->GetTexture(textype, j, &texpath);
521 images.insert(std::string(texpath.C_Str()));
522 }
523 }
524 }
525 return images.size();
526 }
527
count_textures(const aiScene * scene)528 size_t count_textures(const aiScene* scene) {
529 size_t count = 0;
530 for (size_t i = 0; i < scene->mNumMaterials; ++i) {
531 aiMaterial* mat = scene->mMaterials[i];
532 for (
533 size_t tt = aiTextureType_DIFFUSE;
534 tt < aiTextureType_UNKNOWN;
535 ++tt
536 ){
537 // TODO: handle layered textures
538 if (mat->GetTextureCount(static_cast<aiTextureType>(tt)) > 0) {
539 count += 1;
540 }
541 }
542 }
543 return count;
544 }
545
count_deformers(const aiScene * scene)546 size_t count_deformers(const aiScene* scene) {
547 size_t count = 0;
548 for (size_t i = 0; i < scene->mNumMeshes; ++i) {
549 const size_t n = scene->mMeshes[i]->mNumBones;
550 if (n) {
551 // 1 main deformer, 1 subdeformer per bone
552 count += n + 1;
553 }
554 }
555 return count;
556 }
557
WriteDefinitions()558 void FBXExporter::WriteDefinitions ()
559 {
560 // basically this is just bookkeeping:
561 // determining how many of each type of object there are
562 // and specifying the base properties to use when otherwise unspecified.
563
564 // ascii section header
565 if (!binary) {
566 WriteAsciiSectionHeader("Object definitions");
567 }
568
569 // we need to count the objects
570 int32_t count;
571 int32_t total_count = 0;
572
573 // and store them
574 std::vector<FBX::Node> object_nodes;
575 FBX::Node n, pt, p;
576
577 // GlobalSettings
578 // this seems to always be here in Maya exports
579 n = FBX::Node("ObjectType", "GlobalSettings");
580 count = 1;
581 n.AddChild("Count", count);
582 object_nodes.push_back(n);
583 total_count += count;
584
585 // AnimationStack / FbxAnimStack
586 // this seems to always be here in Maya exports,
587 // but no harm seems to come of leaving it out.
588 count = mScene->mNumAnimations;
589 if (count) {
590 n = FBX::Node("ObjectType", "AnimationStack");
591 n.AddChild("Count", count);
592 pt = FBX::Node("PropertyTemplate", "FbxAnimStack");
593 p = FBX::Node("Properties70");
594 p.AddP70string("Description", "");
595 p.AddP70time("LocalStart", 0);
596 p.AddP70time("LocalStop", 0);
597 p.AddP70time("ReferenceStart", 0);
598 p.AddP70time("ReferenceStop", 0);
599 pt.AddChild(p);
600 n.AddChild(pt);
601 object_nodes.push_back(n);
602 total_count += count;
603 }
604
605 // AnimationLayer / FbxAnimLayer
606 // this seems to always be here in Maya exports,
607 // but no harm seems to come of leaving it out.
608 // Assimp doesn't support animation layers,
609 // so there will be one per aiAnimation
610 count = mScene->mNumAnimations;
611 if (count) {
612 n = FBX::Node("ObjectType", "AnimationLayer");
613 n.AddChild("Count", count);
614 pt = FBX::Node("PropertyTemplate", "FBXAnimLayer");
615 p = FBX::Node("Properties70");
616 p.AddP70("Weight", "Number", "", "A", double(100));
617 p.AddP70bool("Mute", 0);
618 p.AddP70bool("Solo", 0);
619 p.AddP70bool("Lock", 0);
620 p.AddP70color("Color", 0.8, 0.8, 0.8);
621 p.AddP70("BlendMode", "enum", "", "", int32_t(0));
622 p.AddP70("RotationAccumulationMode", "enum", "", "", int32_t(0));
623 p.AddP70("ScaleAccumulationMode", "enum", "", "", int32_t(0));
624 p.AddP70("BlendModeBypass", "ULongLong", "", "", int64_t(0));
625 pt.AddChild(p);
626 n.AddChild(pt);
627 object_nodes.push_back(n);
628 total_count += count;
629 }
630
631 // NodeAttribute
632 // this is completely absurd.
633 // there can only be one "NodeAttribute" template,
634 // but FbxSkeleton, FbxCamera, FbxLight all are "NodeAttributes".
635 // so if only one exists we should set the template for that,
636 // otherwise... we just pick one :/.
637 // the others have to set all their properties every instance,
638 // because there's no template.
639 count = 1; // TODO: select properly
640 if (count) {
641 // FbxSkeleton
642 n = FBX::Node("ObjectType", "NodeAttribute");
643 n.AddChild("Count", count);
644 pt = FBX::Node("PropertyTemplate", "FbxSkeleton");
645 p = FBX::Node("Properties70");
646 p.AddP70color("Color", 0.8, 0.8, 0.8);
647 p.AddP70double("Size", 33.333333333333);
648 p.AddP70("LimbLength", "double", "Number", "H", double(1));
649 // note: not sure what the "H" flag is for - hidden?
650 pt.AddChild(p);
651 n.AddChild(pt);
652 object_nodes.push_back(n);
653 total_count += count;
654 }
655
656 // Model / FbxNode
657 // <~~ node hierarchy
658 count = int32_t(count_nodes(mScene->mRootNode)) - 1; // (not counting root node)
659 if (count) {
660 n = FBX::Node("ObjectType", "Model");
661 n.AddChild("Count", count);
662 pt = FBX::Node("PropertyTemplate", "FbxNode");
663 p = FBX::Node("Properties70");
664 p.AddP70enum("QuaternionInterpolate", 0);
665 p.AddP70vector("RotationOffset", 0.0, 0.0, 0.0);
666 p.AddP70vector("RotationPivot", 0.0, 0.0, 0.0);
667 p.AddP70vector("ScalingOffset", 0.0, 0.0, 0.0);
668 p.AddP70vector("ScalingPivot", 0.0, 0.0, 0.0);
669 p.AddP70bool("TranslationActive", 0);
670 p.AddP70vector("TranslationMin", 0.0, 0.0, 0.0);
671 p.AddP70vector("TranslationMax", 0.0, 0.0, 0.0);
672 p.AddP70bool("TranslationMinX", 0);
673 p.AddP70bool("TranslationMinY", 0);
674 p.AddP70bool("TranslationMinZ", 0);
675 p.AddP70bool("TranslationMaxX", 0);
676 p.AddP70bool("TranslationMaxY", 0);
677 p.AddP70bool("TranslationMaxZ", 0);
678 p.AddP70enum("RotationOrder", 0);
679 p.AddP70bool("RotationSpaceForLimitOnly", 0);
680 p.AddP70double("RotationStiffnessX", 0.0);
681 p.AddP70double("RotationStiffnessY", 0.0);
682 p.AddP70double("RotationStiffnessZ", 0.0);
683 p.AddP70double("AxisLen", 10.0);
684 p.AddP70vector("PreRotation", 0.0, 0.0, 0.0);
685 p.AddP70vector("PostRotation", 0.0, 0.0, 0.0);
686 p.AddP70bool("RotationActive", 0);
687 p.AddP70vector("RotationMin", 0.0, 0.0, 0.0);
688 p.AddP70vector("RotationMax", 0.0, 0.0, 0.0);
689 p.AddP70bool("RotationMinX", 0);
690 p.AddP70bool("RotationMinY", 0);
691 p.AddP70bool("RotationMinZ", 0);
692 p.AddP70bool("RotationMaxX", 0);
693 p.AddP70bool("RotationMaxY", 0);
694 p.AddP70bool("RotationMaxZ", 0);
695 p.AddP70enum("InheritType", 0);
696 p.AddP70bool("ScalingActive", 0);
697 p.AddP70vector("ScalingMin", 0.0, 0.0, 0.0);
698 p.AddP70vector("ScalingMax", 1.0, 1.0, 1.0);
699 p.AddP70bool("ScalingMinX", 0);
700 p.AddP70bool("ScalingMinY", 0);
701 p.AddP70bool("ScalingMinZ", 0);
702 p.AddP70bool("ScalingMaxX", 0);
703 p.AddP70bool("ScalingMaxY", 0);
704 p.AddP70bool("ScalingMaxZ", 0);
705 p.AddP70vector("GeometricTranslation", 0.0, 0.0, 0.0);
706 p.AddP70vector("GeometricRotation", 0.0, 0.0, 0.0);
707 p.AddP70vector("GeometricScaling", 1.0, 1.0, 1.0);
708 p.AddP70double("MinDampRangeX", 0.0);
709 p.AddP70double("MinDampRangeY", 0.0);
710 p.AddP70double("MinDampRangeZ", 0.0);
711 p.AddP70double("MaxDampRangeX", 0.0);
712 p.AddP70double("MaxDampRangeY", 0.0);
713 p.AddP70double("MaxDampRangeZ", 0.0);
714 p.AddP70double("MinDampStrengthX", 0.0);
715 p.AddP70double("MinDampStrengthY", 0.0);
716 p.AddP70double("MinDampStrengthZ", 0.0);
717 p.AddP70double("MaxDampStrengthX", 0.0);
718 p.AddP70double("MaxDampStrengthY", 0.0);
719 p.AddP70double("MaxDampStrengthZ", 0.0);
720 p.AddP70double("PreferedAngleX", 0.0);
721 p.AddP70double("PreferedAngleY", 0.0);
722 p.AddP70double("PreferedAngleZ", 0.0);
723 p.AddP70("LookAtProperty", "object", "", "");
724 p.AddP70("UpVectorProperty", "object", "", "");
725 p.AddP70bool("Show", 1);
726 p.AddP70bool("NegativePercentShapeSupport", 1);
727 p.AddP70int("DefaultAttributeIndex", -1);
728 p.AddP70bool("Freeze", 0);
729 p.AddP70bool("LODBox", 0);
730 p.AddP70(
731 "Lcl Translation", "Lcl Translation", "", "A",
732 double(0), double(0), double(0)
733 );
734 p.AddP70(
735 "Lcl Rotation", "Lcl Rotation", "", "A",
736 double(0), double(0), double(0)
737 );
738 p.AddP70(
739 "Lcl Scaling", "Lcl Scaling", "", "A",
740 double(1), double(1), double(1)
741 );
742 p.AddP70("Visibility", "Visibility", "", "A", double(1));
743 p.AddP70(
744 "Visibility Inheritance", "Visibility Inheritance", "", "",
745 int32_t(1)
746 );
747 pt.AddChild(p);
748 n.AddChild(pt);
749 object_nodes.push_back(n);
750 total_count += count;
751 }
752
753 // Geometry / FbxMesh
754 // <~~ aiMesh
755 count = mScene->mNumMeshes;
756 if (count) {
757 n = FBX::Node("ObjectType", "Geometry");
758 n.AddChild("Count", count);
759 pt = FBX::Node("PropertyTemplate", "FbxMesh");
760 p = FBX::Node("Properties70");
761 p.AddP70color("Color", 0, 0, 0);
762 p.AddP70vector("BBoxMin", 0, 0, 0);
763 p.AddP70vector("BBoxMax", 0, 0, 0);
764 p.AddP70bool("Primary Visibility", 1);
765 p.AddP70bool("Casts Shadows", 1);
766 p.AddP70bool("Receive Shadows", 1);
767 pt.AddChild(p);
768 n.AddChild(pt);
769 object_nodes.push_back(n);
770 total_count += count;
771 }
772
773 // Material / FbxSurfacePhong, FbxSurfaceLambert, FbxSurfaceMaterial
774 // <~~ aiMaterial
775 // basically if there's any phong material this is defined as phong,
776 // and otherwise lambert.
777 // More complex materials cause a bare-bones FbxSurfaceMaterial definition
778 // and are treated specially, as they're not really supported by FBX.
779 // TODO: support Maya's Stingray PBS material
780 count = mScene->mNumMaterials;
781 if (count) {
782 bool has_phong = has_phong_mat(mScene);
783 n = FBX::Node("ObjectType", "Material");
784 n.AddChild("Count", count);
785 pt = FBX::Node("PropertyTemplate");
786 if (has_phong) {
787 pt.AddProperty("FbxSurfacePhong");
788 } else {
789 pt.AddProperty("FbxSurfaceLambert");
790 }
791 p = FBX::Node("Properties70");
792 if (has_phong) {
793 p.AddP70string("ShadingModel", "Phong");
794 } else {
795 p.AddP70string("ShadingModel", "Lambert");
796 }
797 p.AddP70bool("MultiLayer", 0);
798 p.AddP70colorA("EmissiveColor", 0.0, 0.0, 0.0);
799 p.AddP70numberA("EmissiveFactor", 1.0);
800 p.AddP70colorA("AmbientColor", 0.2, 0.2, 0.2);
801 p.AddP70numberA("AmbientFactor", 1.0);
802 p.AddP70colorA("DiffuseColor", 0.8, 0.8, 0.8);
803 p.AddP70numberA("DiffuseFactor", 1.0);
804 p.AddP70vector("Bump", 0.0, 0.0, 0.0);
805 p.AddP70vector("NormalMap", 0.0, 0.0, 0.0);
806 p.AddP70double("BumpFactor", 1.0);
807 p.AddP70colorA("TransparentColor", 0.0, 0.0, 0.0);
808 p.AddP70numberA("TransparencyFactor", 0.0);
809 p.AddP70color("DisplacementColor", 0.0, 0.0, 0.0);
810 p.AddP70double("DisplacementFactor", 1.0);
811 p.AddP70color("VectorDisplacementColor", 0.0, 0.0, 0.0);
812 p.AddP70double("VectorDisplacementFactor", 1.0);
813 if (has_phong) {
814 p.AddP70colorA("SpecularColor", 0.2, 0.2, 0.2);
815 p.AddP70numberA("SpecularFactor", 1.0);
816 p.AddP70numberA("ShininessExponent", 20.0);
817 p.AddP70colorA("ReflectionColor", 0.0, 0.0, 0.0);
818 p.AddP70numberA("ReflectionFactor", 1.0);
819 }
820 pt.AddChild(p);
821 n.AddChild(pt);
822 object_nodes.push_back(n);
823 total_count += count;
824 }
825
826 // Video / FbxVideo
827 // one for each image file.
828 count = int32_t(count_images(mScene));
829 if (count) {
830 n = FBX::Node("ObjectType", "Video");
831 n.AddChild("Count", count);
832 pt = FBX::Node("PropertyTemplate", "FbxVideo");
833 p = FBX::Node("Properties70");
834 p.AddP70bool("ImageSequence", 0);
835 p.AddP70int("ImageSequenceOffset", 0);
836 p.AddP70double("FrameRate", 0.0);
837 p.AddP70int("LastFrame", 0);
838 p.AddP70int("Width", 0);
839 p.AddP70int("Height", 0);
840 p.AddP70("Path", "KString", "XRefUrl", "", "");
841 p.AddP70int("StartFrame", 0);
842 p.AddP70int("StopFrame", 0);
843 p.AddP70double("PlaySpeed", 0.0);
844 p.AddP70time("Offset", 0);
845 p.AddP70enum("InterlaceMode", 0);
846 p.AddP70bool("FreeRunning", 0);
847 p.AddP70bool("Loop", 0);
848 p.AddP70enum("AccessMode", 0);
849 pt.AddChild(p);
850 n.AddChild(pt);
851 object_nodes.push_back(n);
852 total_count += count;
853 }
854
855 // Texture / FbxFileTexture
856 // <~~ aiTexture
857 count = int32_t(count_textures(mScene));
858 if (count) {
859 n = FBX::Node("ObjectType", "Texture");
860 n.AddChild("Count", count);
861 pt = FBX::Node("PropertyTemplate", "FbxFileTexture");
862 p = FBX::Node("Properties70");
863 p.AddP70enum("TextureTypeUse", 0);
864 p.AddP70numberA("Texture alpha", 1.0);
865 p.AddP70enum("CurrentMappingType", 0);
866 p.AddP70enum("WrapModeU", 0);
867 p.AddP70enum("WrapModeV", 0);
868 p.AddP70bool("UVSwap", 0);
869 p.AddP70bool("PremultiplyAlpha", 1);
870 p.AddP70vectorA("Translation", 0.0, 0.0, 0.0);
871 p.AddP70vectorA("Rotation", 0.0, 0.0, 0.0);
872 p.AddP70vectorA("Scaling", 1.0, 1.0, 1.0);
873 p.AddP70vector("TextureRotationPivot", 0.0, 0.0, 0.0);
874 p.AddP70vector("TextureScalingPivot", 0.0, 0.0, 0.0);
875 p.AddP70enum("CurrentTextureBlendMode", 1);
876 p.AddP70string("UVSet", "default");
877 p.AddP70bool("UseMaterial", 0);
878 p.AddP70bool("UseMipMap", 0);
879 pt.AddChild(p);
880 n.AddChild(pt);
881 object_nodes.push_back(n);
882 total_count += count;
883 }
884
885 // AnimationCurveNode / FbxAnimCurveNode
886 count = mScene->mNumAnimations * 3;
887 if (count) {
888 n = FBX::Node("ObjectType", "AnimationCurveNode");
889 n.AddChild("Count", count);
890 pt = FBX::Node("PropertyTemplate", "FbxAnimCurveNode");
891 p = FBX::Node("Properties70");
892 p.AddP70("d", "Compound", "", "");
893 pt.AddChild(p);
894 n.AddChild(pt);
895 object_nodes.push_back(n);
896 total_count += count;
897 }
898
899 // AnimationCurve / FbxAnimCurve
900 count = mScene->mNumAnimations * 9;
901 if (count) {
902 n = FBX::Node("ObjectType", "AnimationCurve");
903 n.AddChild("Count", count);
904 object_nodes.push_back(n);
905 total_count += count;
906 }
907
908 // Pose
909 count = 0;
910 for (size_t i = 0; i < mScene->mNumMeshes; ++i) {
911 aiMesh* mesh = mScene->mMeshes[i];
912 if (mesh->HasBones()) { ++count; }
913 }
914 if (count) {
915 n = FBX::Node("ObjectType", "Pose");
916 n.AddChild("Count", count);
917 object_nodes.push_back(n);
918 total_count += count;
919 }
920
921 // Deformer
922 count = int32_t(count_deformers(mScene));
923 if (count) {
924 n = FBX::Node("ObjectType", "Deformer");
925 n.AddChild("Count", count);
926 object_nodes.push_back(n);
927 total_count += count;
928 }
929
930 // (template)
931 count = 0;
932 if (count) {
933 n = FBX::Node("ObjectType", "");
934 n.AddChild("Count", count);
935 pt = FBX::Node("PropertyTemplate", "");
936 p = FBX::Node("Properties70");
937 pt.AddChild(p);
938 n.AddChild(pt);
939 object_nodes.push_back(n);
940 total_count += count;
941 }
942
943 // now write it all
944 FBX::Node defs("Definitions");
945 defs.AddChild("Version", int32_t(100));
946 defs.AddChild("Count", int32_t(total_count));
947 for (auto &n : object_nodes) { defs.AddChild(n); }
948 defs.Dump(outfile, binary, 0);
949 }
950
951
952 // -------------------------------------------------------------------
953 // some internal helper functions used for writing the objects section
954 // (which holds the actual data)
955 // -------------------------------------------------------------------
956
get_node_for_mesh(unsigned int meshIndex,aiNode * node)957 aiNode* get_node_for_mesh(unsigned int meshIndex, aiNode* node)
958 {
959 for (size_t i = 0; i < node->mNumMeshes; ++i) {
960 if (node->mMeshes[i] == meshIndex) {
961 return node;
962 }
963 }
964 for (size_t i = 0; i < node->mNumChildren; ++i) {
965 aiNode* ret = get_node_for_mesh(meshIndex, node->mChildren[i]);
966 if (ret) { return ret; }
967 }
968 return nullptr;
969 }
970
get_world_transform(const aiNode * node,const aiScene * scene)971 aiMatrix4x4 get_world_transform(const aiNode* node, const aiScene* scene)
972 {
973 std::vector<const aiNode*> node_chain;
974 while (node != scene->mRootNode) {
975 node_chain.push_back(node);
976 node = node->mParent;
977 }
978 aiMatrix4x4 transform;
979 for (auto n = node_chain.rbegin(); n != node_chain.rend(); ++n) {
980 transform *= (*n)->mTransformation;
981 }
982 return transform;
983 }
984
to_ktime(double ticks,const aiAnimation * anim)985 int64_t to_ktime(double ticks, const aiAnimation* anim) {
986 if (anim->mTicksPerSecond <= 0) {
987 return static_cast<int64_t>(ticks) * FBX::SECOND;
988 }
989 return (static_cast<int64_t>(ticks) / static_cast<int64_t>(anim->mTicksPerSecond)) * FBX::SECOND;
990 }
991
to_ktime(double time)992 int64_t to_ktime(double time) {
993 return (static_cast<int64_t>(time * FBX::SECOND));
994 }
995
WriteObjects()996 void FBXExporter::WriteObjects ()
997 {
998 if (!binary) {
999 WriteAsciiSectionHeader("Object properties");
1000 }
1001 // numbers should match those given in definitions! make sure to check
1002 StreamWriterLE outstream(outfile);
1003 FBX::Node object_node("Objects");
1004 int indent = 0;
1005 object_node.Begin(outstream, binary, indent);
1006 object_node.EndProperties(outstream, binary, indent);
1007 object_node.BeginChildren(outstream, binary, indent);
1008
1009 bool bJoinIdenticalVertices = mProperties->GetPropertyBool("bJoinIdenticalVertices", true);
1010 std::vector<std::vector<int32_t>> vVertexIndice;//save vertex_indices as it is needed later
1011
1012 // geometry (aiMesh)
1013 mesh_uids.clear();
1014 indent = 1;
1015 for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
1016 // it's all about this mesh
1017 aiMesh* m = mScene->mMeshes[mi];
1018
1019 // start the node record
1020 FBX::Node n("Geometry");
1021 int64_t uid = generate_uid();
1022 mesh_uids.push_back(uid);
1023 n.AddProperty(uid);
1024 n.AddProperty(FBX::SEPARATOR + "Geometry");
1025 n.AddProperty("Mesh");
1026 n.Begin(outstream, binary, indent);
1027 n.DumpProperties(outstream, binary, indent);
1028 n.EndProperties(outstream, binary, indent);
1029 n.BeginChildren(outstream, binary, indent);
1030 indent = 2;
1031
1032 // output vertex data - each vertex should be unique (probably)
1033 std::vector<double> flattened_vertices;
1034 // index of original vertex in vertex data vector
1035 std::vector<int32_t> vertex_indices;
1036 // map of vertex value to its index in the data vector
1037 std::map<aiVector3D,size_t> index_by_vertex_value;
1038 if(bJoinIdenticalVertices){
1039 int32_t index = 0;
1040 for (size_t vi = 0; vi < m->mNumVertices; ++vi) {
1041 aiVector3D vtx = m->mVertices[vi];
1042 auto elem = index_by_vertex_value.find(vtx);
1043 if (elem == index_by_vertex_value.end()) {
1044 vertex_indices.push_back(index);
1045 index_by_vertex_value[vtx] = index;
1046 flattened_vertices.push_back(vtx[0]);
1047 flattened_vertices.push_back(vtx[1]);
1048 flattened_vertices.push_back(vtx[2]);
1049 ++index;
1050 } else {
1051 vertex_indices.push_back(int32_t(elem->second));
1052 }
1053 }
1054 }
1055 else { // do not join vertex, respect the export flag
1056 vertex_indices.resize(m->mNumVertices);
1057 std::iota(vertex_indices.begin(), vertex_indices.end(), 0);
1058 for(unsigned int v = 0; v < m->mNumVertices; ++ v) {
1059 aiVector3D vtx = m->mVertices[v];
1060 flattened_vertices.push_back(vtx.x);
1061 flattened_vertices.push_back(vtx.y);
1062 flattened_vertices.push_back(vtx.z);
1063 }
1064 }
1065 vVertexIndice.push_back(vertex_indices);
1066
1067 FBX::Node::WritePropertyNode(
1068 "Vertices", flattened_vertices, outstream, binary, indent
1069 );
1070
1071 // output polygon data as a flattened array of vertex indices.
1072 // the last vertex index of each polygon is negated and - 1
1073 std::vector<int32_t> polygon_data;
1074 for (size_t fi = 0; fi < m->mNumFaces; ++fi) {
1075 const aiFace &f = m->mFaces[fi];
1076 for (size_t pvi = 0; pvi < f.mNumIndices - 1; ++pvi) {
1077 polygon_data.push_back(vertex_indices[f.mIndices[pvi]]);
1078 }
1079 polygon_data.push_back(
1080 -1 - vertex_indices[f.mIndices[f.mNumIndices-1]]
1081 );
1082 }
1083 FBX::Node::WritePropertyNode(
1084 "PolygonVertexIndex", polygon_data, outstream, binary, indent
1085 );
1086
1087 // here could be edges but they're insane.
1088 // it's optional anyway, so let's ignore it.
1089
1090 FBX::Node::WritePropertyNode(
1091 "GeometryVersion", int32_t(124), outstream, binary, indent
1092 );
1093
1094 // normals, if any
1095 if (m->HasNormals()) {
1096 FBX::Node normals("LayerElementNormal", int32_t(0));
1097 normals.Begin(outstream, binary, indent);
1098 normals.DumpProperties(outstream, binary, indent);
1099 normals.EndProperties(outstream, binary, indent);
1100 normals.BeginChildren(outstream, binary, indent);
1101 indent = 3;
1102 FBX::Node::WritePropertyNode(
1103 "Version", int32_t(101), outstream, binary, indent
1104 );
1105 FBX::Node::WritePropertyNode(
1106 "Name", "", outstream, binary, indent
1107 );
1108 FBX::Node::WritePropertyNode(
1109 "MappingInformationType", "ByPolygonVertex",
1110 outstream, binary, indent
1111 );
1112 // TODO: vertex-normals or indexed normals when appropriate
1113 FBX::Node::WritePropertyNode(
1114 "ReferenceInformationType", "Direct",
1115 outstream, binary, indent
1116 );
1117 std::vector<double> normal_data;
1118 normal_data.reserve(3 * polygon_data.size());
1119 for (size_t fi = 0; fi < m->mNumFaces; ++fi) {
1120 const aiFace &f = m->mFaces[fi];
1121 for (size_t pvi = 0; pvi < f.mNumIndices; ++pvi) {
1122 const aiVector3D &n = m->mNormals[f.mIndices[pvi]];
1123 normal_data.push_back(n.x);
1124 normal_data.push_back(n.y);
1125 normal_data.push_back(n.z);
1126 }
1127 }
1128 FBX::Node::WritePropertyNode(
1129 "Normals", normal_data, outstream, binary, indent
1130 );
1131 // note: version 102 has a NormalsW also... not sure what it is,
1132 // so we can stick with version 101 for now.
1133 indent = 2;
1134 normals.End(outstream, binary, indent, true);
1135 }
1136
1137 // colors, if any
1138 // TODO only one color channel currently
1139 const int32_t colorChannelIndex = 0;
1140 if (m->HasVertexColors(colorChannelIndex)) {
1141 FBX::Node vertexcolors("LayerElementColor", int32_t(colorChannelIndex));
1142 vertexcolors.Begin(outstream, binary, indent);
1143 vertexcolors.DumpProperties(outstream, binary, indent);
1144 vertexcolors.EndProperties(outstream, binary, indent);
1145 vertexcolors.BeginChildren(outstream, binary, indent);
1146 indent = 3;
1147 FBX::Node::WritePropertyNode(
1148 "Version", int32_t(101), outstream, binary, indent
1149 );
1150 char layerName[8];
1151 sprintf(layerName, "COLOR_%d", colorChannelIndex);
1152 FBX::Node::WritePropertyNode(
1153 "Name", (const char*)layerName, outstream, binary, indent
1154 );
1155 FBX::Node::WritePropertyNode(
1156 "MappingInformationType", "ByPolygonVertex",
1157 outstream, binary, indent
1158 );
1159 FBX::Node::WritePropertyNode(
1160 "ReferenceInformationType", "Direct",
1161 outstream, binary, indent
1162 );
1163 std::vector<double> color_data;
1164 color_data.reserve(4 * polygon_data.size());
1165 for (size_t fi = 0; fi < m->mNumFaces; ++fi) {
1166 const aiFace &f = m->mFaces[fi];
1167 for (size_t pvi = 0; pvi < f.mNumIndices; ++pvi) {
1168 const aiColor4D &c = m->mColors[colorChannelIndex][f.mIndices[pvi]];
1169 color_data.push_back(c.r);
1170 color_data.push_back(c.g);
1171 color_data.push_back(c.b);
1172 color_data.push_back(c.a);
1173 }
1174 }
1175 FBX::Node::WritePropertyNode(
1176 "Colors", color_data, outstream, binary, indent
1177 );
1178 indent = 2;
1179 vertexcolors.End(outstream, binary, indent, true);
1180 }
1181
1182 // uvs, if any
1183 for (size_t uvi = 0; uvi < m->GetNumUVChannels(); ++uvi) {
1184 if (m->mNumUVComponents[uvi] > 2) {
1185 // FBX only supports 2-channel UV maps...
1186 // or at least i'm not sure how to indicate a different number
1187 std::stringstream err;
1188 err << "Only 2-channel UV maps supported by FBX,";
1189 err << " but mesh " << mi;
1190 if (m->mName.length) {
1191 err << " (" << m->mName.C_Str() << ")";
1192 }
1193 err << " UV map " << uvi;
1194 err << " has " << m->mNumUVComponents[uvi];
1195 err << " components! Data will be preserved,";
1196 err << " but may be incorrectly interpreted on load.";
1197 ASSIMP_LOG_WARN(err.str());
1198 }
1199 FBX::Node uv("LayerElementUV", int32_t(uvi));
1200 uv.Begin(outstream, binary, indent);
1201 uv.DumpProperties(outstream, binary, indent);
1202 uv.EndProperties(outstream, binary, indent);
1203 uv.BeginChildren(outstream, binary, indent);
1204 indent = 3;
1205 FBX::Node::WritePropertyNode(
1206 "Version", int32_t(101), outstream, binary, indent
1207 );
1208 // it doesn't seem like assimp keeps the uv map name,
1209 // so just leave it blank.
1210 FBX::Node::WritePropertyNode(
1211 "Name", "", outstream, binary, indent
1212 );
1213 FBX::Node::WritePropertyNode(
1214 "MappingInformationType", "ByPolygonVertex",
1215 outstream, binary, indent
1216 );
1217 FBX::Node::WritePropertyNode(
1218 "ReferenceInformationType", "IndexToDirect",
1219 outstream, binary, indent
1220 );
1221
1222 std::vector<double> uv_data;
1223 std::vector<int32_t> uv_indices;
1224 std::map<aiVector3D,int32_t> index_by_uv;
1225 int32_t index = 0;
1226 for (size_t fi = 0; fi < m->mNumFaces; ++fi) {
1227 const aiFace &f = m->mFaces[fi];
1228 for (size_t pvi = 0; pvi < f.mNumIndices; ++pvi) {
1229 const aiVector3D &uv =
1230 m->mTextureCoords[uvi][f.mIndices[pvi]];
1231 auto elem = index_by_uv.find(uv);
1232 if (elem == index_by_uv.end()) {
1233 index_by_uv[uv] = index;
1234 uv_indices.push_back(index);
1235 for (unsigned int x = 0; x < m->mNumUVComponents[uvi]; ++x) {
1236 uv_data.push_back(uv[x]);
1237 }
1238 ++index;
1239 } else {
1240 uv_indices.push_back(elem->second);
1241 }
1242 }
1243 }
1244 FBX::Node::WritePropertyNode(
1245 "UV", uv_data, outstream, binary, indent
1246 );
1247 FBX::Node::WritePropertyNode(
1248 "UVIndex", uv_indices, outstream, binary, indent
1249 );
1250 indent = 2;
1251 uv.End(outstream, binary, indent, true);
1252 }
1253
1254 // i'm not really sure why this material section exists,
1255 // as the material is linked via "Connections".
1256 // it seems to always have the same "0" value.
1257 FBX::Node mat("LayerElementMaterial", int32_t(0));
1258 mat.AddChild("Version", int32_t(101));
1259 mat.AddChild("Name", "");
1260 mat.AddChild("MappingInformationType", "AllSame");
1261 mat.AddChild("ReferenceInformationType", "IndexToDirect");
1262 std::vector<int32_t> mat_indices = {0};
1263 mat.AddChild("Materials", mat_indices);
1264 mat.Dump(outstream, binary, indent);
1265
1266 // finally we have the layer specifications,
1267 // which select the normals / UV set / etc to use.
1268 // TODO: handle multiple uv sets correctly?
1269 FBX::Node layer("Layer", int32_t(0));
1270 layer.AddChild("Version", int32_t(100));
1271 FBX::Node le("LayerElement");
1272 le.AddChild("Type", "LayerElementNormal");
1273 le.AddChild("TypedIndex", int32_t(0));
1274 layer.AddChild(le);
1275 // TODO only 1 color channel currently
1276 le = FBX::Node("LayerElement");
1277 le.AddChild("Type", "LayerElementColor");
1278 le.AddChild("TypedIndex", int32_t(0));
1279 layer.AddChild(le);
1280 le = FBX::Node("LayerElement");
1281 le.AddChild("Type", "LayerElementMaterial");
1282 le.AddChild("TypedIndex", int32_t(0));
1283 layer.AddChild(le);
1284 le = FBX::Node("LayerElement");
1285 le.AddChild("Type", "LayerElementUV");
1286 le.AddChild("TypedIndex", int32_t(0));
1287 layer.AddChild(le);
1288 layer.Dump(outstream, binary, indent);
1289
1290 for(unsigned int lr = 1; lr < m->GetNumUVChannels(); ++ lr)
1291 {
1292 FBX::Node layerExtra("Layer", int32_t(lr));
1293 layerExtra.AddChild("Version", int32_t(100));
1294 FBX::Node leExtra("LayerElement");
1295 leExtra.AddChild("Type", "LayerElementUV");
1296 leExtra.AddChild("TypedIndex", int32_t(lr));
1297 layerExtra.AddChild(leExtra);
1298 layerExtra.Dump(outstream, binary, indent);
1299 }
1300 // finish the node record
1301 indent = 1;
1302 n.End(outstream, binary, indent, true);
1303 }
1304
1305 // aiMaterial
1306 material_uids.clear();
1307 for (size_t i = 0; i < mScene->mNumMaterials; ++i) {
1308 // it's all about this material
1309 aiMaterial* m = mScene->mMaterials[i];
1310
1311 // these are used to receive material data
1312 float f; aiColor3D c;
1313
1314 // start the node record
1315 FBX::Node n("Material");
1316
1317 int64_t uid = generate_uid();
1318 material_uids.push_back(uid);
1319 n.AddProperty(uid);
1320
1321 aiString name;
1322 m->Get(AI_MATKEY_NAME, name);
1323 n.AddProperty(name.C_Str() + FBX::SEPARATOR + "Material");
1324
1325 n.AddProperty("");
1326
1327 n.AddChild("Version", int32_t(102));
1328 f = 0;
1329 m->Get(AI_MATKEY_SHININESS, f);
1330 bool phong = (f > 0);
1331 if (phong) {
1332 n.AddChild("ShadingModel", "phong");
1333 } else {
1334 n.AddChild("ShadingModel", "lambert");
1335 }
1336 n.AddChild("MultiLayer", int32_t(0));
1337
1338 FBX::Node p("Properties70");
1339
1340 // materials exported using the FBX SDK have two sets of fields.
1341 // there are the properties specified in the PropertyTemplate,
1342 // which are those supported by the modernFBX SDK,
1343 // and an extra set of properties with simpler names.
1344 // The extra properties are a legacy material system from pre-2009.
1345 //
1346 // In the modern system, each property has "color" and "factor".
1347 // Generally the interpretation of these seems to be
1348 // that the colour is multiplied by the factor before use,
1349 // but this is not always clear-cut.
1350 //
1351 // Usually assimp only stores the colour,
1352 // so we can just leave the factors at the default "1.0".
1353
1354 // first we can export the "standard" properties
1355 if (m->Get(AI_MATKEY_COLOR_AMBIENT, c) == aiReturn_SUCCESS) {
1356 p.AddP70colorA("AmbientColor", c.r, c.g, c.b);
1357 //p.AddP70numberA("AmbientFactor", 1.0);
1358 }
1359 if (m->Get(AI_MATKEY_COLOR_DIFFUSE, c) == aiReturn_SUCCESS) {
1360 p.AddP70colorA("DiffuseColor", c.r, c.g, c.b);
1361 //p.AddP70numberA("DiffuseFactor", 1.0);
1362 }
1363 if (m->Get(AI_MATKEY_COLOR_TRANSPARENT, c) == aiReturn_SUCCESS) {
1364 // "TransparentColor" / "TransparencyFactor"...
1365 // thanks FBX, for your insightful interpretation of consistency
1366 p.AddP70colorA("TransparentColor", c.r, c.g, c.b);
1367 // TransparencyFactor defaults to 0.0, so set it to 1.0.
1368 // note: Maya always sets this to 1.0,
1369 // so we can't use it sensibly as "Opacity".
1370 // In stead we rely on the legacy "Opacity" value, below.
1371 // Blender also relies on "Opacity" not "TransparencyFactor",
1372 // probably for a similar reason.
1373 p.AddP70numberA("TransparencyFactor", 1.0);
1374 }
1375 if (m->Get(AI_MATKEY_COLOR_REFLECTIVE, c) == aiReturn_SUCCESS) {
1376 p.AddP70colorA("ReflectionColor", c.r, c.g, c.b);
1377 }
1378 if (m->Get(AI_MATKEY_REFLECTIVITY, f) == aiReturn_SUCCESS) {
1379 p.AddP70numberA("ReflectionFactor", f);
1380 }
1381 if (phong) {
1382 if (m->Get(AI_MATKEY_COLOR_SPECULAR, c) == aiReturn_SUCCESS) {
1383 p.AddP70colorA("SpecularColor", c.r, c.g, c.b);
1384 }
1385 if (m->Get(AI_MATKEY_SHININESS_STRENGTH, f) == aiReturn_SUCCESS) {
1386 p.AddP70numberA("ShininessFactor", f);
1387 }
1388 if (m->Get(AI_MATKEY_SHININESS, f) == aiReturn_SUCCESS) {
1389 p.AddP70numberA("ShininessExponent", f);
1390 }
1391 if (m->Get(AI_MATKEY_REFLECTIVITY, f) == aiReturn_SUCCESS) {
1392 p.AddP70numberA("ReflectionFactor", f);
1393 }
1394 }
1395
1396 // Now the legacy system.
1397 // For safety let's include it.
1398 // thrse values don't exist in the property template,
1399 // and usually are completely ignored when loading.
1400 // One notable exception is the "Opacity" property,
1401 // which Blender uses as (1.0 - alpha).
1402 c.r = 0.0f; c.g = 0.0f; c.b = 0.0f;
1403 m->Get(AI_MATKEY_COLOR_EMISSIVE, c);
1404 p.AddP70vector("Emissive", c.r, c.g, c.b);
1405 c.r = 0.2f; c.g = 0.2f; c.b = 0.2f;
1406 m->Get(AI_MATKEY_COLOR_AMBIENT, c);
1407 p.AddP70vector("Ambient", c.r, c.g, c.b);
1408 c.r = 0.8f; c.g = 0.8f; c.b = 0.8f;
1409 m->Get(AI_MATKEY_COLOR_DIFFUSE, c);
1410 p.AddP70vector("Diffuse", c.r, c.g, c.b);
1411 // The FBX SDK determines "Opacity" from transparency colour (RGB)
1412 // and factor (F) as: O = (1.0 - F * ((R + G + B) / 3)).
1413 // However we actually have an opacity value,
1414 // so we should take it from AI_MATKEY_OPACITY if possible.
1415 // It might make more sense to use TransparencyFactor,
1416 // but Blender actually loads "Opacity" correctly, so let's use it.
1417 f = 1.0f;
1418 if (m->Get(AI_MATKEY_COLOR_TRANSPARENT, c) == aiReturn_SUCCESS) {
1419 f = 1.0f - ((c.r + c.g + c.b) / 3.0f);
1420 }
1421 m->Get(AI_MATKEY_OPACITY, f);
1422 p.AddP70double("Opacity", f);
1423 if (phong) {
1424 // specular color is multiplied by shininess_strength
1425 c.r = 0.2f; c.g = 0.2f; c.b = 0.2f;
1426 m->Get(AI_MATKEY_COLOR_SPECULAR, c);
1427 f = 1.0f;
1428 m->Get(AI_MATKEY_SHININESS_STRENGTH, f);
1429 p.AddP70vector("Specular", f*c.r, f*c.g, f*c.b);
1430 f = 20.0f;
1431 m->Get(AI_MATKEY_SHININESS, f);
1432 p.AddP70double("Shininess", f);
1433 // Legacy "Reflectivity" is F*F*((R+G+B)/3),
1434 // where F is the proportion of light reflected (AKA reflectivity),
1435 // and RGB is the reflective colour of the material.
1436 // No idea why, but we might as well set it the same way.
1437 f = 0.0f;
1438 m->Get(AI_MATKEY_REFLECTIVITY, f);
1439 c.r = 1.0f, c.g = 1.0f, c.b = 1.0f;
1440 m->Get(AI_MATKEY_COLOR_REFLECTIVE, c);
1441 p.AddP70double("Reflectivity", f*f*((c.r+c.g+c.b)/3.0));
1442 }
1443
1444 n.AddChild(p);
1445
1446 n.Dump(outstream, binary, indent);
1447 }
1448
1449 // we need to look up all the images we're using,
1450 // so we can generate uids, and eliminate duplicates.
1451 std::map<std::string, int64_t> uid_by_image;
1452 for (size_t i = 0; i < mScene->mNumMaterials; ++i) {
1453 aiString texpath;
1454 aiMaterial* mat = mScene->mMaterials[i];
1455 for (
1456 size_t tt = aiTextureType_DIFFUSE;
1457 tt < aiTextureType_UNKNOWN;
1458 ++tt
1459 ){
1460 const aiTextureType textype = static_cast<aiTextureType>(tt);
1461 const size_t texcount = mat->GetTextureCount(textype);
1462 for (size_t j = 0; j < texcount; ++j) {
1463 mat->GetTexture(textype, (unsigned int)j, &texpath);
1464 const std::string texstring = texpath.C_Str();
1465 auto elem = uid_by_image.find(texstring);
1466 if (elem == uid_by_image.end()) {
1467 uid_by_image[texstring] = generate_uid();
1468 }
1469 }
1470 }
1471 }
1472
1473 // FbxVideo - stores images used by textures.
1474 for (const auto &it : uid_by_image) {
1475 FBX::Node n("Video");
1476 const int64_t& uid = it.second;
1477 const std::string name = ""; // TODO: ... name???
1478 n.AddProperties(uid, name + FBX::SEPARATOR + "Video", "Clip");
1479 n.AddChild("Type", "Clip");
1480 FBX::Node p("Properties70");
1481 // TODO: get full path... relative path... etc... ugh...
1482 // for now just use the same path for everything,
1483 // and hopefully one of them will work out.
1484 std::string path = it.first;
1485 // try get embedded texture
1486 const aiTexture* embedded_texture = mScene->GetEmbeddedTexture(it.first.c_str());
1487 if (embedded_texture != nullptr) {
1488 // change the path (use original filename, if available. If name is empty, concatenate texture index with file extension)
1489 std::stringstream newPath;
1490 if (embedded_texture->mFilename.length > 0) {
1491 newPath << embedded_texture->mFilename.C_Str();
1492 } else if (embedded_texture->achFormatHint[0]) {
1493 int texture_index = std::stoi(path.substr(1, path.size() - 1));
1494 newPath << texture_index << "." << embedded_texture->achFormatHint;
1495 }
1496 path = newPath.str();
1497 // embed the texture
1498 size_t texture_size = static_cast<size_t>(embedded_texture->mWidth * std::max(embedded_texture->mHeight, 1u));
1499 if (binary) {
1500 // embed texture as binary data
1501 std::vector<uint8_t> tex_data;
1502 tex_data.resize(texture_size);
1503 memcpy(&tex_data[0], (char*)embedded_texture->pcData, texture_size);
1504 n.AddChild("Content", tex_data);
1505 } else {
1506 // embed texture in base64 encoding
1507 std::string encoded_texture = FBX::Util::EncodeBase64((char*)embedded_texture->pcData, texture_size);
1508 n.AddChild("Content", encoded_texture);
1509 }
1510 }
1511 p.AddP70("Path", "KString", "XRefUrl", "", path);
1512 n.AddChild(p);
1513 n.AddChild("UseMipMap", int32_t(0));
1514 n.AddChild("Filename", path);
1515 n.AddChild("RelativeFilename", path);
1516 n.Dump(outstream, binary, indent);
1517 }
1518
1519 // Textures
1520 // referenced by material_index/texture_type pairs.
1521 std::map<std::pair<size_t,size_t>,int64_t> texture_uids;
1522 const std::map<aiTextureType,std::string> prop_name_by_tt = {
1523 {aiTextureType_DIFFUSE, "DiffuseColor"},
1524 {aiTextureType_SPECULAR, "SpecularColor"},
1525 {aiTextureType_AMBIENT, "AmbientColor"},
1526 {aiTextureType_EMISSIVE, "EmissiveColor"},
1527 {aiTextureType_HEIGHT, "Bump"},
1528 {aiTextureType_NORMALS, "NormalMap"},
1529 {aiTextureType_SHININESS, "ShininessExponent"},
1530 {aiTextureType_OPACITY, "TransparentColor"},
1531 {aiTextureType_DISPLACEMENT, "DisplacementColor"},
1532 //{aiTextureType_LIGHTMAP, "???"},
1533 {aiTextureType_REFLECTION, "ReflectionColor"}
1534 //{aiTextureType_UNKNOWN, ""}
1535 };
1536 for (size_t i = 0; i < mScene->mNumMaterials; ++i) {
1537 // textures are attached to materials
1538 aiMaterial* mat = mScene->mMaterials[i];
1539 int64_t material_uid = material_uids[i];
1540
1541 for (
1542 size_t j = aiTextureType_DIFFUSE;
1543 j < aiTextureType_UNKNOWN;
1544 ++j
1545 ) {
1546 const aiTextureType tt = static_cast<aiTextureType>(j);
1547 size_t n = mat->GetTextureCount(tt);
1548
1549 if (n < 1) { // no texture of this type
1550 continue;
1551 }
1552
1553 if (n > 1) {
1554 // TODO: multilayer textures
1555 std::stringstream err;
1556 err << "Multilayer textures not supported (for now),";
1557 err << " skipping texture type " << j;
1558 err << " of material " << i;
1559 ASSIMP_LOG_WARN(err.str());
1560 }
1561
1562 // get image path for this (single-image) texture
1563 aiString tpath;
1564 if (mat->GetTexture(tt, 0, &tpath) != aiReturn_SUCCESS) {
1565 std::stringstream err;
1566 err << "Failed to get texture 0 for texture of type " << tt;
1567 err << " on material " << i;
1568 err << ", however GetTextureCount returned 1.";
1569 throw DeadlyExportError(err.str());
1570 }
1571 const std::string texture_path(tpath.C_Str());
1572
1573 // get connected image uid
1574 auto elem = uid_by_image.find(texture_path);
1575 if (elem == uid_by_image.end()) {
1576 // this should never happen
1577 std::stringstream err;
1578 err << "Failed to find video element for texture with path";
1579 err << " \"" << texture_path << "\"";
1580 err << ", type " << j << ", material " << i;
1581 throw DeadlyExportError(err.str());
1582 }
1583 const int64_t image_uid = elem->second;
1584
1585 // get the name of the material property to connect to
1586 auto elem2 = prop_name_by_tt.find(tt);
1587 if (elem2 == prop_name_by_tt.end()) {
1588 // don't know how to handle this type of texture,
1589 // so skip it.
1590 std::stringstream err;
1591 err << "Not sure how to handle texture of type " << j;
1592 err << " on material " << i;
1593 err << ", skipping...";
1594 ASSIMP_LOG_WARN(err.str());
1595 continue;
1596 }
1597 const std::string& prop_name = elem2->second;
1598
1599 // generate a uid for this texture
1600 const int64_t texture_uid = generate_uid();
1601
1602 // link the texture to the material
1603 connections.emplace_back(
1604 "C", "OP", texture_uid, material_uid, prop_name
1605 );
1606
1607 // link the image data to the texture
1608 connections.emplace_back("C", "OO", image_uid, texture_uid);
1609
1610 // now write the actual texture node
1611 FBX::Node tnode("Texture");
1612 // TODO: some way to determine texture name?
1613 const std::string texture_name = "" + FBX::SEPARATOR + "Texture";
1614 tnode.AddProperties(texture_uid, texture_name, "");
1615 // there really doesn't seem to be a better type than this:
1616 tnode.AddChild("Type", "TextureVideoClip");
1617 tnode.AddChild("Version", int32_t(202));
1618 tnode.AddChild("TextureName", texture_name);
1619 FBX::Node p("Properties70");
1620 p.AddP70enum("CurrentTextureBlendMode", 0); // TODO: verify
1621 //p.AddP70string("UVSet", ""); // TODO: how should this work?
1622 p.AddP70bool("UseMaterial", 1);
1623 tnode.AddChild(p);
1624 // can't easily detrmine which texture path will be correct,
1625 // so just store what we have in every field.
1626 // these being incorrect is a common problem with FBX anyway.
1627 tnode.AddChild("FileName", texture_path);
1628 tnode.AddChild("RelativeFilename", texture_path);
1629 tnode.AddChild("ModelUVTranslation", double(0.0), double(0.0));
1630 tnode.AddChild("ModelUVScaling", double(1.0), double(1.0));
1631 tnode.AddChild("Texture_Alpha_Source", "None");
1632 tnode.AddChild(
1633 "Cropping", int32_t(0), int32_t(0), int32_t(0), int32_t(0)
1634 );
1635 tnode.Dump(outstream, binary, indent);
1636 }
1637 }
1638
1639 // bones.
1640 //
1641 // output structure:
1642 // subset of node hierarchy that are "skeleton",
1643 // i.e. do not have meshes but only bones.
1644 // but.. i'm not sure how anyone could guarantee that...
1645 //
1646 // input...
1647 // well, for each mesh it has "bones",
1648 // and the bone names correspond to nodes.
1649 // of course we also need the parent nodes,
1650 // as they give some of the transform........
1651 //
1652 // well. we can assume a sane input, i suppose.
1653 //
1654 // so input is the bone node hierarchy,
1655 // with an extra thing for the transformation of the MESH in BONE space.
1656 //
1657 // output is a set of bone nodes,
1658 // a "bindpose" which indicates the default local transform of all bones,
1659 // and a set of "deformers".
1660 // each deformer is parented to a mesh geometry,
1661 // and has one or more "subdeformer"s as children.
1662 // each subdeformer has one bone node as a child,
1663 // and represents the influence of that bone on the grandparent mesh.
1664 // the subdeformer has a list of indices, and weights,
1665 // with indices specifying vertex indices,
1666 // and weights specifying the corresponding influence of this bone.
1667 // it also has Transform and TransformLink elements,
1668 // specifying the transform of the MESH in BONE space,
1669 // and the transformation of the BONE in WORLD space,
1670 // likely in the bindpose.
1671 //
1672 // the input bone structure is different but similar,
1673 // storing the number of weights for this bone,
1674 // and an array of (vertex index, weight) pairs.
1675 //
1676 // one sticky point is that the number of vertices may not match,
1677 // because assimp splits vertices by normal, uv, etc.
1678
1679 // functor for aiNode sorting
1680 struct SortNodeByName
1681 {
1682 bool operator()(const aiNode *lhs, const aiNode *rhs) const
1683 {
1684 return strcmp(lhs->mName.C_Str(), rhs->mName.C_Str()) < 0;
1685 }
1686 };
1687
1688 // first we should mark the skeleton for each mesh.
1689 // the skeleton must include not only the aiBones,
1690 // but also all their parent nodes.
1691 // anything that affects the position of any bone node must be included.
1692 // Use SorNodeByName to make sure the exported result will be the same across all systems
1693 // Otherwise the aiNodes of the skeleton would be sorted based on the pointer address, which isn't consistent
1694 std::vector<std::set<const aiNode*, SortNodeByName>> skeleton_by_mesh(mScene->mNumMeshes);
1695 // at the same time we can build a list of all the skeleton nodes,
1696 // which will be used later to mark them as type "limbNode".
1697 std::unordered_set<const aiNode*> limbnodes;
1698
1699 //actual bone nodes in fbx, without parenting-up
1700 std::unordered_set<std::string> setAllBoneNamesInScene;
1701 for(unsigned int m = 0; m < mScene->mNumMeshes; ++ m)
1702 {
1703 aiMesh* pMesh = mScene->mMeshes[m];
1704 for(unsigned int b = 0; b < pMesh->mNumBones; ++ b)
1705 setAllBoneNamesInScene.insert(pMesh->mBones[b]->mName.data);
1706 }
1707 aiMatrix4x4 mxTransIdentity;
1708
1709 // and a map of nodes by bone name, as finding them is annoying.
1710 std::map<std::string,aiNode*> node_by_bone;
1711 for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
1712 const aiMesh* m = mScene->mMeshes[mi];
1713 std::set<const aiNode*, SortNodeByName> skeleton;
1714 for (size_t bi =0; bi < m->mNumBones; ++bi) {
1715 const aiBone* b = m->mBones[bi];
1716 const std::string name(b->mName.C_Str());
1717 auto elem = node_by_bone.find(name);
1718 aiNode* n;
1719 if (elem != node_by_bone.end()) {
1720 n = elem->second;
1721 } else {
1722 n = mScene->mRootNode->FindNode(b->mName);
1723 if (!n) {
1724 // this should never happen
1725 std::stringstream err;
1726 err << "Failed to find node for bone: \"" << name << "\"";
1727 throw DeadlyExportError(err.str());
1728 }
1729 node_by_bone[name] = n;
1730 limbnodes.insert(n);
1731 }
1732 skeleton.insert(n);
1733 // mark all parent nodes as skeleton as well,
1734 // up until we find the root node,
1735 // or else the node containing the mesh,
1736 // or else the parent of a node containig the mesh.
1737 for (
1738 const aiNode* parent = n->mParent;
1739 parent && parent != mScene->mRootNode;
1740 parent = parent->mParent
1741 ) {
1742 // if we've already done this node we can skip it all
1743 if (skeleton.count(parent)) {
1744 break;
1745 }
1746 // ignore fbx transform nodes as these will be collapsed later
1747 // TODO: cache this by aiNode*
1748 const std::string node_name(parent->mName.C_Str());
1749 if (node_name.find(MAGIC_NODE_TAG) != std::string::npos) {
1750 continue;
1751 }
1752 //not a bone in scene && no effect in transform
1753 if(setAllBoneNamesInScene.find(node_name)==setAllBoneNamesInScene.end()
1754 && parent->mTransformation == mxTransIdentity) {
1755 continue;
1756 }
1757 // otherwise check if this is the root of the skeleton
1758 bool end = false;
1759 // is the mesh part of this node?
1760 for (size_t i = 0; i < parent->mNumMeshes; ++i) {
1761 if (parent->mMeshes[i] == mi) {
1762 end = true;
1763 break;
1764 }
1765 }
1766 // is the mesh in one of the children of this node?
1767 for (size_t j = 0; j < parent->mNumChildren; ++j) {
1768 aiNode* child = parent->mChildren[j];
1769 for (size_t i = 0; i < child->mNumMeshes; ++i) {
1770 if (child->mMeshes[i] == mi) {
1771 end = true;
1772 break;
1773 }
1774 }
1775 if (end) { break; }
1776 }
1777
1778 // if it was the skeleton root we can finish here
1779 if (end) { break; }
1780 }
1781 }
1782 skeleton_by_mesh[mi] = skeleton;
1783 }
1784
1785 // we'll need the uids for the bone nodes, so generate them now
1786 for (size_t i = 0; i < mScene->mNumMeshes; ++i) {
1787 auto &s = skeleton_by_mesh[i];
1788 for (const aiNode* n : s) {
1789 auto elem = node_uids.find(n);
1790 if (elem == node_uids.end()) {
1791 node_uids[n] = generate_uid();
1792 }
1793 }
1794 }
1795
1796 // now, for each aiMesh, we need to export a deformer,
1797 // and for each aiBone a subdeformer,
1798 // which should have all the skinning info.
1799 // these will need to be connected properly to the mesh,
1800 // and we can do that all now.
1801 for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
1802 const aiMesh* m = mScene->mMeshes[mi];
1803 if (!m->HasBones()) {
1804 continue;
1805 }
1806 // make a deformer for this mesh
1807 int64_t deformer_uid = generate_uid();
1808 FBX::Node dnode("Deformer");
1809 dnode.AddProperties(deformer_uid, FBX::SEPARATOR + "Deformer", "Skin");
1810 dnode.AddChild("Version", int32_t(101));
1811 // "acuracy"... this is not a typo....
1812 dnode.AddChild("Link_DeformAcuracy", double(50));
1813 dnode.AddChild("SkinningType", "Linear"); // TODO: other modes?
1814 dnode.Dump(outstream, binary, indent);
1815
1816 // connect it
1817 connections.emplace_back("C", "OO", deformer_uid, mesh_uids[mi]);
1818
1819 //computed before
1820 std::vector<int32_t>& vertex_indices = vVertexIndice[mi];
1821
1822 // TODO, FIXME: this won't work if anything is not in the bind pose.
1823 // for now if such a situation is detected, we throw an exception.
1824 std::set<const aiBone*> not_in_bind_pose;
1825 std::set<const aiNode*> no_offset_matrix;
1826
1827 // first get this mesh's position in world space,
1828 // as we'll need it for each subdeformer.
1829 //
1830 // ...of course taking the position of the MESH doesn't make sense,
1831 // as it can be instanced to many nodes.
1832 // All we can do is assume no instancing,
1833 // and take the first node we find that contains the mesh.
1834 aiNode* mesh_node = get_node_for_mesh((unsigned int)mi, mScene->mRootNode);
1835 aiMatrix4x4 mesh_xform = get_world_transform(mesh_node, mScene);
1836
1837 // now make a subdeformer for each bone in the skeleton
1838 const std::set<const aiNode*, SortNodeByName> skeleton= skeleton_by_mesh[mi];
1839 for (const aiNode* bone_node : skeleton) {
1840 // if there's a bone for this node, find it
1841 const aiBone* b = nullptr;
1842 for (size_t bi = 0; bi < m->mNumBones; ++bi) {
1843 // TODO: this probably should index by something else
1844 const std::string name(m->mBones[bi]->mName.C_Str());
1845 if (node_by_bone[name] == bone_node) {
1846 b = m->mBones[bi];
1847 break;
1848 }
1849 }
1850 if (!b) {
1851 no_offset_matrix.insert(bone_node);
1852 }
1853
1854 // start the subdeformer node
1855 const int64_t subdeformer_uid = generate_uid();
1856 FBX::Node sdnode("Deformer");
1857 sdnode.AddProperties(
1858 subdeformer_uid, FBX::SEPARATOR + "SubDeformer", "Cluster"
1859 );
1860 sdnode.AddChild("Version", int32_t(100));
1861 sdnode.AddChild("UserData", "", "");
1862
1863 // add indices and weights, if any
1864 if (b) {
1865 std::vector<int32_t> subdef_indices;
1866 std::vector<double> subdef_weights;
1867 int32_t last_index = -1;
1868 for (size_t wi = 0; wi < b->mNumWeights; ++wi) {
1869 int32_t vi = vertex_indices[b->mWeights[wi].mVertexId];
1870 if (vi == last_index) {
1871 // only for vertices we exported to fbx
1872 // TODO, FIXME: this assumes identically-located vertices
1873 // will always deform in the same way.
1874 // as assimp doesn't store a separate list of "positions",
1875 // there's not much that can be done about this
1876 // other than assuming that identical position means
1877 // identical vertex.
1878 continue;
1879 }
1880 subdef_indices.push_back(vi);
1881 subdef_weights.push_back(b->mWeights[wi].mWeight);
1882 last_index = vi;
1883 }
1884 // yes, "indexes"
1885 sdnode.AddChild("Indexes", subdef_indices);
1886 sdnode.AddChild("Weights", subdef_weights);
1887 }
1888
1889 // transform is the transform of the mesh, but in bone space.
1890 // if the skeleton is in the bind pose,
1891 // we can take the inverse of the world-space bone transform
1892 // and multiply by the world-space transform of the mesh.
1893 aiMatrix4x4 bone_xform = get_world_transform(bone_node, mScene);
1894 aiMatrix4x4 inverse_bone_xform = bone_xform;
1895 inverse_bone_xform.Inverse();
1896 aiMatrix4x4 tr = inverse_bone_xform * mesh_xform;
1897
1898 sdnode.AddChild("Transform", tr);
1899
1900
1901 sdnode.AddChild("TransformLink", bone_xform);
1902 // note: this means we ALWAYS rely on the mesh node transform
1903 // being unchanged from the time the skeleton was bound.
1904 // there's not really any way around this at the moment.
1905
1906 // done
1907 sdnode.Dump(outstream, binary, indent);
1908
1909 // lastly, connect to the parent deformer
1910 connections.emplace_back(
1911 "C", "OO", subdeformer_uid, deformer_uid
1912 );
1913
1914 // we also need to connect the limb node to the subdeformer.
1915 connections.emplace_back(
1916 "C", "OO", node_uids[bone_node], subdeformer_uid
1917 );
1918 }
1919
1920 // if we cannot create a valid FBX file, simply die.
1921 // this will both prevent unnecessary bug reports,
1922 // and tell the user what they can do to fix the situation
1923 // (i.e. export their model in the bind pose).
1924 if (no_offset_matrix.size() && not_in_bind_pose.size()) {
1925 std::stringstream err;
1926 err << "Not enough information to construct bind pose";
1927 err << " for mesh " << mi << "!";
1928 err << " Transform matrix for bone \"";
1929 err << (*not_in_bind_pose.begin())->mName.C_Str() << "\"";
1930 if (not_in_bind_pose.size() > 1) {
1931 err << " (and " << not_in_bind_pose.size() - 1 << " more)";
1932 }
1933 err << " does not match mOffsetMatrix,";
1934 err << " and node \"";
1935 err << (*no_offset_matrix.begin())->mName.C_Str() << "\"";
1936 if (no_offset_matrix.size() > 1) {
1937 err << " (and " << no_offset_matrix.size() - 1 << " more)";
1938 }
1939 err << " has no offset matrix to rely on.";
1940 err << " Please ensure bones are in the bind pose to export.";
1941 throw DeadlyExportError(err.str());
1942 }
1943
1944 }
1945
1946 // BindPose
1947 //
1948 // This is a legacy system, which should be unnecessary.
1949 //
1950 // Somehow including it slows file loading by the official FBX SDK,
1951 // and as it can reconstruct it from the deformers anyway,
1952 // this is not currently included.
1953 //
1954 // The code is kept here in case it's useful in the future,
1955 // but it's pretty much a hack anyway,
1956 // as assimp doesn't store bindpose information for full skeletons.
1957 //
1958 /*for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
1959 aiMesh* mesh = mScene->mMeshes[mi];
1960 if (! mesh->HasBones()) { continue; }
1961 int64_t bindpose_uid = generate_uid();
1962 FBX::Node bpnode("Pose");
1963 bpnode.AddProperty(bindpose_uid);
1964 // note: this uid is never linked or connected to anything.
1965 bpnode.AddProperty(FBX::SEPARATOR + "Pose"); // blank name
1966 bpnode.AddProperty("BindPose");
1967
1968 bpnode.AddChild("Type", "BindPose");
1969 bpnode.AddChild("Version", int32_t(100));
1970
1971 aiNode* mesh_node = get_node_for_mesh(mi, mScene->mRootNode);
1972
1973 // next get the whole skeleton for this mesh.
1974 // we need it all to define the bindpose section.
1975 // the FBX SDK will complain if it's missing,
1976 // and also if parents of used bones don't have a subdeformer.
1977 // order shouldn't matter.
1978 std::set<aiNode*> skeleton;
1979 for (size_t bi = 0; bi < mesh->mNumBones; ++bi) {
1980 // bone node should have already been indexed
1981 const aiBone* b = mesh->mBones[bi];
1982 const std::string bone_name(b->mName.C_Str());
1983 aiNode* parent = node_by_bone[bone_name];
1984 // insert all nodes down to the root or mesh node
1985 while (
1986 parent
1987 && parent != mScene->mRootNode
1988 && parent != mesh_node
1989 ) {
1990 skeleton.insert(parent);
1991 parent = parent->mParent;
1992 }
1993 }
1994
1995 // number of pose nodes. includes one for the mesh itself.
1996 bpnode.AddChild("NbPoseNodes", int32_t(1 + skeleton.size()));
1997
1998 // the first pose node is always the mesh itself
1999 FBX::Node pose("PoseNode");
2000 pose.AddChild("Node", mesh_uids[mi]);
2001 aiMatrix4x4 mesh_node_xform = get_world_transform(mesh_node, mScene);
2002 pose.AddChild("Matrix", mesh_node_xform);
2003 bpnode.AddChild(pose);
2004
2005 for (aiNode* bonenode : skeleton) {
2006 // does this node have a uid yet?
2007 int64_t node_uid;
2008 auto node_uid_iter = node_uids.find(bonenode);
2009 if (node_uid_iter != node_uids.end()) {
2010 node_uid = node_uid_iter->second;
2011 } else {
2012 node_uid = generate_uid();
2013 node_uids[bonenode] = node_uid;
2014 }
2015
2016 // make a pose thingy
2017 pose = FBX::Node("PoseNode");
2018 pose.AddChild("Node", node_uid);
2019 aiMatrix4x4 node_xform = get_world_transform(bonenode, mScene);
2020 pose.AddChild("Matrix", node_xform);
2021 bpnode.AddChild(pose);
2022 }
2023
2024 // now write it
2025 bpnode.Dump(outstream, binary, indent);
2026 }*/
2027
2028 // TODO: cameras, lights
2029
2030 // write nodes (i.e. model hierarchy)
2031 // start at root node
2032 WriteModelNodes(
2033 outstream, mScene->mRootNode, 0, limbnodes
2034 );
2035
2036 // animations
2037 //
2038 // in FBX there are:
2039 // * AnimationStack - corresponds to an aiAnimation
2040 // * AnimationLayer - a combinable animation component
2041 // * AnimationCurveNode - links the property to be animated
2042 // * AnimationCurve - defines animation data for a single property value
2043 //
2044 // the CurveNode also provides the default value for a property,
2045 // such as the X, Y, Z coordinates for animatable translation.
2046 //
2047 // the Curve only specifies values for one component of the property,
2048 // so there will be a separate AnimationCurve for X, Y, and Z.
2049 //
2050 // Assimp has:
2051 // * aiAnimation - basically corresponds to an AnimationStack
2052 // * aiNodeAnim - defines all animation for one aiNode
2053 // * aiVectorKey/aiQuatKey - define the keyframe data for T/R/S
2054 //
2055 // assimp has no equivalent for AnimationLayer,
2056 // and these are flattened on FBX import.
2057 // we can assume there will be one per AnimationStack.
2058 //
2059 // the aiNodeAnim contains all animation data for a single aiNode,
2060 // which will correspond to three AnimationCurveNode's:
2061 // one each for translation, rotation and scale.
2062 // The data for each of these will be put in 9 AnimationCurve's,
2063 // T.X, T.Y, T.Z, R.X, R.Y, R.Z, etc.
2064
2065 // AnimationStack / aiAnimation
2066 std::vector<int64_t> animation_stack_uids(mScene->mNumAnimations);
2067 for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) {
2068 int64_t animstack_uid = generate_uid();
2069 animation_stack_uids[ai] = animstack_uid;
2070 const aiAnimation* anim = mScene->mAnimations[ai];
2071
2072 FBX::Node asnode("AnimationStack");
2073 std::string name = anim->mName.C_Str() + FBX::SEPARATOR + "AnimStack";
2074 asnode.AddProperties(animstack_uid, name, "");
2075 FBX::Node p("Properties70");
2076 p.AddP70time("LocalStart", 0); // assimp doesn't store this
2077 p.AddP70time("LocalStop", to_ktime(anim->mDuration, anim));
2078 p.AddP70time("ReferenceStart", 0);
2079 p.AddP70time("ReferenceStop", to_ktime(anim->mDuration, anim));
2080 asnode.AddChild(p);
2081
2082 // this node absurdly always pretends it has children
2083 // (in this case it does, but just in case...)
2084 asnode.force_has_children = true;
2085 asnode.Dump(outstream, binary, indent);
2086
2087 // note: animation stacks are not connected to anything
2088 }
2089
2090 // AnimationLayer - one per aiAnimation
2091 std::vector<int64_t> animation_layer_uids(mScene->mNumAnimations);
2092 for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) {
2093 int64_t animlayer_uid = generate_uid();
2094 animation_layer_uids[ai] = animlayer_uid;
2095 FBX::Node alnode("AnimationLayer");
2096 alnode.AddProperties(animlayer_uid, FBX::SEPARATOR + "AnimLayer", "");
2097
2098 // this node absurdly always pretends it has children
2099 alnode.force_has_children = true;
2100 alnode.Dump(outstream, binary, indent);
2101
2102 // connect to the relevant animstack
2103 connections.emplace_back(
2104 "C", "OO", animlayer_uid, animation_stack_uids[ai]
2105 );
2106 }
2107
2108 // AnimCurveNode - three per aiNodeAnim
2109 std::vector<std::vector<std::array<int64_t,3>>> curve_node_uids;
2110 for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) {
2111 const aiAnimation* anim = mScene->mAnimations[ai];
2112 const int64_t layer_uid = animation_layer_uids[ai];
2113 std::vector<std::array<int64_t,3>> nodeanim_uids;
2114 for (size_t nai = 0; nai < anim->mNumChannels; ++nai) {
2115 const aiNodeAnim* na = anim->mChannels[nai];
2116 // get the corresponding aiNode
2117 const aiNode* node = mScene->mRootNode->FindNode(na->mNodeName);
2118 // and its transform
2119 const aiMatrix4x4 node_xfm = get_world_transform(node, mScene);
2120 aiVector3D T, R, S;
2121 node_xfm.Decompose(S, R, T);
2122
2123 // AnimationCurveNode uids
2124 std::array<int64_t,3> ids;
2125 ids[0] = generate_uid(); // T
2126 ids[1] = generate_uid(); // R
2127 ids[2] = generate_uid(); // S
2128
2129 // translation
2130 WriteAnimationCurveNode(outstream,
2131 ids[0], "T", T, "Lcl Translation",
2132 layer_uid, node_uids[node]
2133 );
2134
2135 // rotation
2136 WriteAnimationCurveNode(outstream,
2137 ids[1], "R", R, "Lcl Rotation",
2138 layer_uid, node_uids[node]
2139 );
2140
2141 // scale
2142 WriteAnimationCurveNode(outstream,
2143 ids[2], "S", S, "Lcl Scale",
2144 layer_uid, node_uids[node]
2145 );
2146
2147 // store the uids for later use
2148 nodeanim_uids.push_back(ids);
2149 }
2150 curve_node_uids.push_back(nodeanim_uids);
2151 }
2152
2153 // AnimCurve - defines actual keyframe data.
2154 // there's a separate curve for every component of every vector,
2155 // for example a transform curvenode will have separate X/Y/Z AnimCurve's
2156 for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) {
2157 const aiAnimation* anim = mScene->mAnimations[ai];
2158 for (size_t nai = 0; nai < anim->mNumChannels; ++nai) {
2159 const aiNodeAnim* na = anim->mChannels[nai];
2160 // get the corresponding aiNode
2161 const aiNode* node = mScene->mRootNode->FindNode(na->mNodeName);
2162 // and its transform
2163 const aiMatrix4x4 node_xfm = get_world_transform(node, mScene);
2164 aiVector3D T, R, S;
2165 node_xfm.Decompose(S, R, T);
2166 const std::array<int64_t,3>& ids = curve_node_uids[ai][nai];
2167
2168 std::vector<int64_t> times;
2169 std::vector<float> xval, yval, zval;
2170
2171 // position/translation
2172 for (size_t ki = 0; ki < na->mNumPositionKeys; ++ki) {
2173 const aiVectorKey& k = na->mPositionKeys[ki];
2174 times.push_back(to_ktime(k.mTime));
2175 xval.push_back(k.mValue.x);
2176 yval.push_back(k.mValue.y);
2177 zval.push_back(k.mValue.z);
2178 }
2179 // one curve each for X, Y, Z
2180 WriteAnimationCurve(outstream, T.x, times, xval, ids[0], "d|X");
2181 WriteAnimationCurve(outstream, T.y, times, yval, ids[0], "d|Y");
2182 WriteAnimationCurve(outstream, T.z, times, zval, ids[0], "d|Z");
2183
2184 // rotation
2185 times.clear(); xval.clear(); yval.clear(); zval.clear();
2186 for (size_t ki = 0; ki < na->mNumRotationKeys; ++ki) {
2187 const aiQuatKey& k = na->mRotationKeys[ki];
2188 times.push_back(to_ktime(k.mTime));
2189 // TODO: aiQuaternion method to convert to Euler...
2190 aiMatrix4x4 m(k.mValue.GetMatrix());
2191 aiVector3D qs, qr, qt;
2192 m.Decompose(qs, qr, qt);
2193 qr *= DEG;
2194 xval.push_back(qr.x);
2195 yval.push_back(qr.y);
2196 zval.push_back(qr.z);
2197 }
2198 WriteAnimationCurve(outstream, R.x, times, xval, ids[1], "d|X");
2199 WriteAnimationCurve(outstream, R.y, times, yval, ids[1], "d|Y");
2200 WriteAnimationCurve(outstream, R.z, times, zval, ids[1], "d|Z");
2201
2202 // scaling/scale
2203 times.clear(); xval.clear(); yval.clear(); zval.clear();
2204 for (size_t ki = 0; ki < na->mNumScalingKeys; ++ki) {
2205 const aiVectorKey& k = na->mScalingKeys[ki];
2206 times.push_back(to_ktime(k.mTime));
2207 xval.push_back(k.mValue.x);
2208 yval.push_back(k.mValue.y);
2209 zval.push_back(k.mValue.z);
2210 }
2211 WriteAnimationCurve(outstream, S.x, times, xval, ids[2], "d|X");
2212 WriteAnimationCurve(outstream, S.y, times, yval, ids[2], "d|Y");
2213 WriteAnimationCurve(outstream, S.z, times, zval, ids[2], "d|Z");
2214 }
2215 }
2216
2217 indent = 0;
2218 object_node.End(outstream, binary, indent, true);
2219 }
2220
2221 // convenience map of magic node name strings to FBX properties,
2222 // including the expected type of transform.
2223 const std::map<std::string,std::pair<std::string,char>> transform_types = {
2224 {"Translation", {"Lcl Translation", 't'}},
2225 {"RotationOffset", {"RotationOffset", 't'}},
2226 {"RotationPivot", {"RotationPivot", 't'}},
2227 {"PreRotation", {"PreRotation", 'r'}},
2228 {"Rotation", {"Lcl Rotation", 'r'}},
2229 {"PostRotation", {"PostRotation", 'r'}},
2230 {"RotationPivotInverse", {"RotationPivotInverse", 'i'}},
2231 {"ScalingOffset", {"ScalingOffset", 't'}},
2232 {"ScalingPivot", {"ScalingPivot", 't'}},
2233 {"Scaling", {"Lcl Scaling", 's'}},
2234 {"ScalingPivotInverse", {"ScalingPivotInverse", 'i'}},
2235 {"GeometricScaling", {"GeometricScaling", 's'}},
2236 {"GeometricRotation", {"GeometricRotation", 'r'}},
2237 {"GeometricTranslation", {"GeometricTranslation", 't'}},
2238 {"GeometricTranslationInverse", {"GeometricTranslationInverse", 'i'}},
2239 {"GeometricRotationInverse", {"GeometricRotationInverse", 'i'}},
2240 {"GeometricScalingInverse", {"GeometricScalingInverse", 'i'}}
2241 };
2242
2243 // write a single model node to the stream
WriteModelNode(StreamWriterLE & outstream,bool binary,const aiNode * node,int64_t node_uid,const std::string & type,const std::vector<std::pair<std::string,aiVector3D>> & transform_chain,TransformInheritance inherit_type)2244 void FBXExporter::WriteModelNode(
2245 StreamWriterLE& outstream,
2246 bool binary,
2247 const aiNode* node,
2248 int64_t node_uid,
2249 const std::string& type,
2250 const std::vector<std::pair<std::string,aiVector3D>>& transform_chain,
2251 TransformInheritance inherit_type
2252 ){
2253 const aiVector3D zero = {0, 0, 0};
2254 const aiVector3D one = {1, 1, 1};
2255 FBX::Node m("Model");
2256 std::string name = node->mName.C_Str() + FBX::SEPARATOR + "Model";
2257 m.AddProperties(node_uid, name, type);
2258 m.AddChild("Version", int32_t(232));
2259 FBX::Node p("Properties70");
2260 p.AddP70bool("RotationActive", 1);
2261 p.AddP70int("DefaultAttributeIndex", 0);
2262 p.AddP70enum("InheritType", inherit_type);
2263 if (transform_chain.empty()) {
2264 // decompose 4x4 transform matrix into TRS
2265 aiVector3D t, r, s;
2266 node->mTransformation.Decompose(s, r, t);
2267 if (t != zero) {
2268 p.AddP70(
2269 "Lcl Translation", "Lcl Translation", "", "A",
2270 double(t.x), double(t.y), double(t.z)
2271 );
2272 }
2273 if (r != zero) {
2274 p.AddP70(
2275 "Lcl Rotation", "Lcl Rotation", "", "A",
2276 double(DEG*r.x), double(DEG*r.y), double(DEG*r.z)
2277 );
2278 }
2279 if (s != one) {
2280 p.AddP70(
2281 "Lcl Scaling", "Lcl Scaling", "", "A",
2282 double(s.x), double(s.y), double(s.z)
2283 );
2284 }
2285 } else {
2286 // apply the transformation chain.
2287 // these transformation elements are created when importing FBX,
2288 // which has a complex transformation hierarchy for each node.
2289 // as such we can bake the hierarchy back into the node on export.
2290 for (auto &item : transform_chain) {
2291 auto elem = transform_types.find(item.first);
2292 if (elem == transform_types.end()) {
2293 // then this is a bug
2294 std::stringstream err;
2295 err << "unrecognized FBX transformation type: ";
2296 err << item.first;
2297 throw DeadlyExportError(err.str());
2298 }
2299 const std::string &name = elem->second.first;
2300 const aiVector3D &v = item.second;
2301 if (name.compare(0, 4, "Lcl ") == 0) {
2302 // special handling for animatable properties
2303 p.AddP70(
2304 name, name, "", "A",
2305 double(v.x), double(v.y), double(v.z)
2306 );
2307 } else {
2308 p.AddP70vector(name, v.x, v.y, v.z);
2309 }
2310 }
2311 }
2312 m.AddChild(p);
2313
2314 // not sure what these are for,
2315 // but they seem to be omnipresent
2316 m.AddChild("Shading", FBXExportProperty(true));
2317 m.AddChild("Culling", FBXExportProperty("CullingOff"));
2318
2319 m.Dump(outstream, binary, 1);
2320 }
2321
2322 // wrapper for WriteModelNodes to create and pass a blank transform chain
WriteModelNodes(StreamWriterLE & s,const aiNode * node,int64_t parent_uid,const std::unordered_set<const aiNode * > & limbnodes)2323 void FBXExporter::WriteModelNodes(
2324 StreamWriterLE& s,
2325 const aiNode* node,
2326 int64_t parent_uid,
2327 const std::unordered_set<const aiNode*>& limbnodes
2328 ) {
2329 std::vector<std::pair<std::string,aiVector3D>> chain;
2330 WriteModelNodes(s, node, parent_uid, limbnodes, chain);
2331 }
2332
WriteModelNodes(StreamWriterLE & outstream,const aiNode * node,int64_t parent_uid,const std::unordered_set<const aiNode * > & limbnodes,std::vector<std::pair<std::string,aiVector3D>> & transform_chain)2333 void FBXExporter::WriteModelNodes(
2334 StreamWriterLE& outstream,
2335 const aiNode* node,
2336 int64_t parent_uid,
2337 const std::unordered_set<const aiNode*>& limbnodes,
2338 std::vector<std::pair<std::string,aiVector3D>>& transform_chain
2339 ) {
2340 // first collapse any expanded transformation chains created by FBX import.
2341 std::string node_name(node->mName.C_Str());
2342 if (node_name.find(MAGIC_NODE_TAG) != std::string::npos) {
2343 auto pos = node_name.find(MAGIC_NODE_TAG) + MAGIC_NODE_TAG.size() + 1;
2344 std::string type_name = node_name.substr(pos);
2345 auto elem = transform_types.find(type_name);
2346 if (elem == transform_types.end()) {
2347 // then this is a bug and should be fixed
2348 std::stringstream err;
2349 err << "unrecognized FBX transformation node";
2350 err << " of type " << type_name << " in node " << node_name;
2351 throw DeadlyExportError(err.str());
2352 }
2353 aiVector3D t, r, s;
2354 node->mTransformation.Decompose(s, r, t);
2355 switch (elem->second.second) {
2356 case 'i': // inverse
2357 // we don't need to worry about the inverse matrices
2358 break;
2359 case 't': // translation
2360 transform_chain.emplace_back(elem->first, t);
2361 break;
2362 case 'r': // rotation
2363 r *= float(DEG);
2364 transform_chain.emplace_back(elem->first, r);
2365 break;
2366 case 's': // scale
2367 transform_chain.emplace_back(elem->first, s);
2368 break;
2369 default:
2370 // this should never happen
2371 std::stringstream err;
2372 err << "unrecognized FBX transformation type code: ";
2373 err << elem->second.second;
2374 throw DeadlyExportError(err.str());
2375 }
2376 // now continue on to any child nodes
2377 for (unsigned i = 0; i < node->mNumChildren; ++i) {
2378 WriteModelNodes(
2379 outstream,
2380 node->mChildren[i],
2381 parent_uid,
2382 limbnodes,
2383 transform_chain
2384 );
2385 }
2386 return;
2387 }
2388
2389 int64_t node_uid = 0;
2390 // generate uid and connect to parent, if not the root node,
2391 if (node != mScene->mRootNode) {
2392 auto elem = node_uids.find(node);
2393 if (elem != node_uids.end()) {
2394 node_uid = elem->second;
2395 } else {
2396 node_uid = generate_uid();
2397 node_uids[node] = node_uid;
2398 }
2399 connections.emplace_back("C", "OO", node_uid, parent_uid);
2400 }
2401
2402 // what type of node is this?
2403 if (node == mScene->mRootNode) {
2404 // handled later
2405 } else if (node->mNumMeshes == 1) {
2406 // connect to child mesh, which should have been written previously
2407 connections.emplace_back(
2408 "C", "OO", mesh_uids[node->mMeshes[0]], node_uid
2409 );
2410 // also connect to the material for the child mesh
2411 connections.emplace_back(
2412 "C", "OO",
2413 material_uids[mScene->mMeshes[node->mMeshes[0]]->mMaterialIndex],
2414 node_uid
2415 );
2416 // write model node
2417 WriteModelNode(
2418 outstream, binary, node, node_uid, "Mesh", transform_chain
2419 );
2420 } else if (limbnodes.count(node)) {
2421 WriteModelNode(
2422 outstream, binary, node, node_uid, "LimbNode", transform_chain
2423 );
2424 // we also need to write a nodeattribute to mark it as a skeleton
2425 int64_t node_attribute_uid = generate_uid();
2426 FBX::Node na("NodeAttribute");
2427 na.AddProperties(
2428 node_attribute_uid, FBX::SEPARATOR + "NodeAttribute", "LimbNode"
2429 );
2430 na.AddChild("TypeFlags", FBXExportProperty("Skeleton"));
2431 na.Dump(outstream, binary, 1);
2432 // and connect them
2433 connections.emplace_back("C", "OO", node_attribute_uid, node_uid);
2434 } else {
2435 // generate a null node so we can add children to it
2436 WriteModelNode(
2437 outstream, binary, node, node_uid, "Null", transform_chain
2438 );
2439 }
2440
2441 // if more than one child mesh, make nodes for each mesh
2442 if (node->mNumMeshes > 1 || node == mScene->mRootNode) {
2443 for (size_t i = 0; i < node->mNumMeshes; ++i) {
2444 // make a new model node
2445 int64_t new_node_uid = generate_uid();
2446 // connect to parent node
2447 connections.emplace_back("C", "OO", new_node_uid, node_uid);
2448 // connect to child mesh, which should have been written previously
2449 connections.emplace_back(
2450 "C", "OO", mesh_uids[node->mMeshes[i]], new_node_uid
2451 );
2452 // also connect to the material for the child mesh
2453 connections.emplace_back(
2454 "C", "OO",
2455 material_uids[
2456 mScene->mMeshes[node->mMeshes[i]]->mMaterialIndex
2457 ],
2458 new_node_uid
2459 );
2460 // write model node
2461 FBX::Node m("Model");
2462 // take name from mesh name, if it exists
2463 std::string name = mScene->mMeshes[node->mMeshes[i]]->mName.C_Str();
2464 name += FBX::SEPARATOR + "Model";
2465 m.AddProperties(new_node_uid, name, "Mesh");
2466 m.AddChild("Version", int32_t(232));
2467 FBX::Node p("Properties70");
2468 p.AddP70enum("InheritType", 1);
2469 m.AddChild(p);
2470 m.Dump(outstream, binary, 1);
2471 }
2472 }
2473
2474 // now recurse into children
2475 for (size_t i = 0; i < node->mNumChildren; ++i) {
2476 WriteModelNodes(
2477 outstream, node->mChildren[i], node_uid, limbnodes
2478 );
2479 }
2480 }
2481
2482
WriteAnimationCurveNode(StreamWriterLE & outstream,int64_t uid,const std::string & name,aiVector3D default_value,std::string property_name,int64_t layer_uid,int64_t node_uid)2483 void FBXExporter::WriteAnimationCurveNode(
2484 StreamWriterLE& outstream,
2485 int64_t uid,
2486 const std::string& name, // "T", "R", or "S"
2487 aiVector3D default_value,
2488 std::string property_name, // "Lcl Translation" etc
2489 int64_t layer_uid,
2490 int64_t node_uid
2491 ) {
2492 FBX::Node n("AnimationCurveNode");
2493 n.AddProperties(uid, name + FBX::SEPARATOR + "AnimCurveNode", "");
2494 FBX::Node p("Properties70");
2495 p.AddP70numberA("d|X", default_value.x);
2496 p.AddP70numberA("d|Y", default_value.y);
2497 p.AddP70numberA("d|Z", default_value.z);
2498 n.AddChild(p);
2499 n.Dump(outstream, binary, 1);
2500 // connect to layer
2501 this->connections.emplace_back("C", "OO", uid, layer_uid);
2502 // connect to bone
2503 this->connections.emplace_back("C", "OP", uid, node_uid, property_name);
2504 }
2505
2506
WriteAnimationCurve(StreamWriterLE & outstream,double default_value,const std::vector<int64_t> & times,const std::vector<float> & values,int64_t curvenode_uid,const std::string & property_link)2507 void FBXExporter::WriteAnimationCurve(
2508 StreamWriterLE& outstream,
2509 double default_value,
2510 const std::vector<int64_t>& times,
2511 const std::vector<float>& values,
2512 int64_t curvenode_uid,
2513 const std::string& property_link // "d|X", "d|Y", etc
2514 ) {
2515 FBX::Node n("AnimationCurve");
2516 int64_t curve_uid = generate_uid();
2517 n.AddProperties(curve_uid, FBX::SEPARATOR + "AnimCurve", "");
2518 n.AddChild("Default", default_value);
2519 n.AddChild("KeyVer", int32_t(4009));
2520 n.AddChild("KeyTime", times);
2521 n.AddChild("KeyValueFloat", values);
2522 // TODO: keyattr flags and data (STUB for now)
2523 n.AddChild("KeyAttrFlags", std::vector<int32_t>{0});
2524 n.AddChild("KeyAttrDataFloat", std::vector<float>{0,0,0,0});
2525 n.AddChild(
2526 "KeyAttrRefCount",
2527 std::vector<int32_t>{static_cast<int32_t>(times.size())}
2528 );
2529 n.Dump(outstream, binary, 1);
2530 this->connections.emplace_back(
2531 "C", "OP", curve_uid, curvenode_uid, property_link
2532 );
2533 }
2534
2535
WriteConnections()2536 void FBXExporter::WriteConnections ()
2537 {
2538 // we should have completed the connection graph already,
2539 // so basically just dump it here
2540 if (!binary) {
2541 WriteAsciiSectionHeader("Object connections");
2542 }
2543 // TODO: comments with names in the ascii version
2544 FBX::Node conn("Connections");
2545 StreamWriterLE outstream(outfile);
2546 conn.Begin(outstream, binary, 0);
2547 conn.BeginChildren(outstream, binary, 0);
2548 for (auto &n : connections) {
2549 n.Dump(outstream, binary, 1);
2550 }
2551 conn.End(outstream, binary, 0, !connections.empty());
2552 connections.clear();
2553 }
2554
2555 #endif // ASSIMP_BUILD_NO_FBX_EXPORTER
2556 #endif // ASSIMP_BUILD_NO_EXPORT
2557