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.5.0";
85     const uint32_t EXPORT_VERSION_INT = 7500; // 7.5 == 2016+
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             std::set<int32_t> setWeightedVertex;
1864             // add indices and weights, if any
1865             if (b) {
1866                 std::vector<int32_t> subdef_indices;
1867                 std::vector<double> subdef_weights;
1868                 int32_t last_index = -1;
1869                 for (size_t wi = 0; wi < b->mNumWeights; ++wi) {
1870                     int32_t vi = vertex_indices[b->mWeights[wi].mVertexId];
1871                     bool bIsWeightedAlready = (setWeightedVertex.find(vi) != setWeightedVertex.end());
1872                     if (vi == last_index || bIsWeightedAlready) {
1873                         // only for vertices we exported to fbx
1874                         // TODO, FIXME: this assumes identically-located vertices
1875                         // will always deform in the same way.
1876                         // as assimp doesn't store a separate list of "positions",
1877                         // there's not much that can be done about this
1878                         // other than assuming that identical position means
1879                         // identical vertex.
1880                         continue;
1881                     }
1882                     setWeightedVertex.insert(vi);
1883                     subdef_indices.push_back(vi);
1884                     subdef_weights.push_back(b->mWeights[wi].mWeight);
1885                     last_index = vi;
1886                 }
1887                 // yes, "indexes"
1888                 sdnode.AddChild("Indexes", subdef_indices);
1889                 sdnode.AddChild("Weights", subdef_weights);
1890             }
1891 
1892             // transform is the transform of the mesh, but in bone space.
1893             // if the skeleton is in the bind pose,
1894             // we can take the inverse of the world-space bone transform
1895             // and multiply by the world-space transform of the mesh.
1896             aiMatrix4x4 bone_xform = get_world_transform(bone_node, mScene);
1897             aiMatrix4x4 inverse_bone_xform = bone_xform;
1898             inverse_bone_xform.Inverse();
1899             aiMatrix4x4 tr = inverse_bone_xform * mesh_xform;
1900 
1901             sdnode.AddChild("Transform", tr);
1902 
1903 
1904             sdnode.AddChild("TransformLink", bone_xform);
1905             // note: this means we ALWAYS rely on the mesh node transform
1906             // being unchanged from the time the skeleton was bound.
1907             // there's not really any way around this at the moment.
1908 
1909             // done
1910             sdnode.Dump(outstream, binary, indent);
1911 
1912             // lastly, connect to the parent deformer
1913             connections.emplace_back(
1914                 "C", "OO", subdeformer_uid, deformer_uid
1915             );
1916 
1917             // we also need to connect the limb node to the subdeformer.
1918             connections.emplace_back(
1919                 "C", "OO", node_uids[bone_node], subdeformer_uid
1920             );
1921         }
1922 
1923         // if we cannot create a valid FBX file, simply die.
1924         // this will both prevent unnecessary bug reports,
1925         // and tell the user what they can do to fix the situation
1926         // (i.e. export their model in the bind pose).
1927         if (no_offset_matrix.size() && not_in_bind_pose.size()) {
1928             std::stringstream err;
1929             err << "Not enough information to construct bind pose";
1930             err << " for mesh " << mi << "!";
1931             err << " Transform matrix for bone \"";
1932             err << (*not_in_bind_pose.begin())->mName.C_Str() << "\"";
1933             if (not_in_bind_pose.size() > 1) {
1934                 err << " (and " << not_in_bind_pose.size() - 1 << " more)";
1935             }
1936             err << " does not match mOffsetMatrix,";
1937             err << " and node \"";
1938             err << (*no_offset_matrix.begin())->mName.C_Str() << "\"";
1939             if (no_offset_matrix.size() > 1) {
1940                 err << " (and " << no_offset_matrix.size() - 1 << " more)";
1941             }
1942             err << " has no offset matrix to rely on.";
1943             err << " Please ensure bones are in the bind pose to export.";
1944             throw DeadlyExportError(err.str());
1945         }
1946 
1947     }
1948 
1949     // BindPose
1950     //
1951     // This is a legacy system, which should be unnecessary.
1952     //
1953     // Somehow including it slows file loading by the official FBX SDK,
1954     // and as it can reconstruct it from the deformers anyway,
1955     // this is not currently included.
1956     //
1957     // The code is kept here in case it's useful in the future,
1958     // but it's pretty much a hack anyway,
1959     // as assimp doesn't store bindpose information for full skeletons.
1960     //
1961     /*for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
1962         aiMesh* mesh = mScene->mMeshes[mi];
1963         if (! mesh->HasBones()) { continue; }
1964         int64_t bindpose_uid = generate_uid();
1965         FBX::Node bpnode("Pose");
1966         bpnode.AddProperty(bindpose_uid);
1967         // note: this uid is never linked or connected to anything.
1968         bpnode.AddProperty(FBX::SEPARATOR + "Pose"); // blank name
1969         bpnode.AddProperty("BindPose");
1970 
1971         bpnode.AddChild("Type", "BindPose");
1972         bpnode.AddChild("Version", int32_t(100));
1973 
1974         aiNode* mesh_node = get_node_for_mesh(mi, mScene->mRootNode);
1975 
1976         // next get the whole skeleton for this mesh.
1977         // we need it all to define the bindpose section.
1978         // the FBX SDK will complain if it's missing,
1979         // and also if parents of used bones don't have a subdeformer.
1980         // order shouldn't matter.
1981         std::set<aiNode*> skeleton;
1982         for (size_t bi = 0; bi < mesh->mNumBones; ++bi) {
1983             // bone node should have already been indexed
1984             const aiBone* b = mesh->mBones[bi];
1985             const std::string bone_name(b->mName.C_Str());
1986             aiNode* parent = node_by_bone[bone_name];
1987             // insert all nodes down to the root or mesh node
1988             while (
1989                 parent
1990                 && parent != mScene->mRootNode
1991                 && parent != mesh_node
1992             ) {
1993                 skeleton.insert(parent);
1994                 parent = parent->mParent;
1995             }
1996         }
1997 
1998         // number of pose nodes. includes one for the mesh itself.
1999         bpnode.AddChild("NbPoseNodes", int32_t(1 + skeleton.size()));
2000 
2001         // the first pose node is always the mesh itself
2002         FBX::Node pose("PoseNode");
2003         pose.AddChild("Node", mesh_uids[mi]);
2004         aiMatrix4x4 mesh_node_xform = get_world_transform(mesh_node, mScene);
2005         pose.AddChild("Matrix", mesh_node_xform);
2006         bpnode.AddChild(pose);
2007 
2008         for (aiNode* bonenode : skeleton) {
2009             // does this node have a uid yet?
2010             int64_t node_uid;
2011             auto node_uid_iter = node_uids.find(bonenode);
2012             if (node_uid_iter != node_uids.end()) {
2013                 node_uid = node_uid_iter->second;
2014             } else {
2015                 node_uid = generate_uid();
2016                 node_uids[bonenode] = node_uid;
2017             }
2018 
2019             // make a pose thingy
2020             pose = FBX::Node("PoseNode");
2021             pose.AddChild("Node", node_uid);
2022             aiMatrix4x4 node_xform = get_world_transform(bonenode, mScene);
2023             pose.AddChild("Matrix", node_xform);
2024             bpnode.AddChild(pose);
2025         }
2026 
2027         // now write it
2028         bpnode.Dump(outstream, binary, indent);
2029     }*/
2030 
2031     // TODO: cameras, lights
2032 
2033     // write nodes (i.e. model hierarchy)
2034     // start at root node
2035     WriteModelNodes(
2036         outstream, mScene->mRootNode, 0, limbnodes
2037     );
2038 
2039     // animations
2040     //
2041     // in FBX there are:
2042     // * AnimationStack - corresponds to an aiAnimation
2043     // * AnimationLayer - a combinable animation component
2044     // * AnimationCurveNode - links the property to be animated
2045     // * AnimationCurve - defines animation data for a single property value
2046     //
2047     // the CurveNode also provides the default value for a property,
2048     // such as the X, Y, Z coordinates for animatable translation.
2049     //
2050     // the Curve only specifies values for one component of the property,
2051     // so there will be a separate AnimationCurve for X, Y, and Z.
2052     //
2053     // Assimp has:
2054     // * aiAnimation - basically corresponds to an AnimationStack
2055     // * aiNodeAnim - defines all animation for one aiNode
2056     // * aiVectorKey/aiQuatKey - define the keyframe data for T/R/S
2057     //
2058     // assimp has no equivalent for AnimationLayer,
2059     // and these are flattened on FBX import.
2060     // we can assume there will be one per AnimationStack.
2061     //
2062     // the aiNodeAnim contains all animation data for a single aiNode,
2063     // which will correspond to three AnimationCurveNode's:
2064     // one each for translation, rotation and scale.
2065     // The data for each of these will be put in 9 AnimationCurve's,
2066     // T.X, T.Y, T.Z, R.X, R.Y, R.Z, etc.
2067 
2068     // AnimationStack / aiAnimation
2069     std::vector<int64_t> animation_stack_uids(mScene->mNumAnimations);
2070     for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) {
2071         int64_t animstack_uid = generate_uid();
2072         animation_stack_uids[ai] = animstack_uid;
2073         const aiAnimation* anim = mScene->mAnimations[ai];
2074 
2075         FBX::Node asnode("AnimationStack");
2076         std::string name = anim->mName.C_Str() + FBX::SEPARATOR + "AnimStack";
2077         asnode.AddProperties(animstack_uid, name, "");
2078         FBX::Node p("Properties70");
2079         p.AddP70time("LocalStart", 0); // assimp doesn't store this
2080         p.AddP70time("LocalStop", to_ktime(anim->mDuration, anim));
2081         p.AddP70time("ReferenceStart", 0);
2082         p.AddP70time("ReferenceStop", to_ktime(anim->mDuration, anim));
2083         asnode.AddChild(p);
2084 
2085         // this node absurdly always pretends it has children
2086         // (in this case it does, but just in case...)
2087         asnode.force_has_children = true;
2088         asnode.Dump(outstream, binary, indent);
2089 
2090         // note: animation stacks are not connected to anything
2091     }
2092 
2093     // AnimationLayer - one per aiAnimation
2094     std::vector<int64_t> animation_layer_uids(mScene->mNumAnimations);
2095     for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) {
2096         int64_t animlayer_uid = generate_uid();
2097         animation_layer_uids[ai] = animlayer_uid;
2098         FBX::Node alnode("AnimationLayer");
2099         alnode.AddProperties(animlayer_uid, FBX::SEPARATOR + "AnimLayer", "");
2100 
2101         // this node absurdly always pretends it has children
2102         alnode.force_has_children = true;
2103         alnode.Dump(outstream, binary, indent);
2104 
2105         // connect to the relevant animstack
2106         connections.emplace_back(
2107             "C", "OO", animlayer_uid, animation_stack_uids[ai]
2108         );
2109     }
2110 
2111     // AnimCurveNode - three per aiNodeAnim
2112     std::vector<std::vector<std::array<int64_t,3>>> curve_node_uids;
2113     for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) {
2114         const aiAnimation* anim = mScene->mAnimations[ai];
2115         const int64_t layer_uid = animation_layer_uids[ai];
2116         std::vector<std::array<int64_t,3>> nodeanim_uids;
2117         for (size_t nai = 0; nai < anim->mNumChannels; ++nai) {
2118             const aiNodeAnim* na = anim->mChannels[nai];
2119             // get the corresponding aiNode
2120             const aiNode* node = mScene->mRootNode->FindNode(na->mNodeName);
2121             // and its transform
2122             const aiMatrix4x4 node_xfm = get_world_transform(node, mScene);
2123             aiVector3D T, R, S;
2124             node_xfm.Decompose(S, R, T);
2125 
2126             // AnimationCurveNode uids
2127             std::array<int64_t,3> ids;
2128             ids[0] = generate_uid(); // T
2129             ids[1] = generate_uid(); // R
2130             ids[2] = generate_uid(); // S
2131 
2132             // translation
2133             WriteAnimationCurveNode(outstream,
2134                 ids[0], "T", T, "Lcl Translation",
2135                 layer_uid, node_uids[node]
2136             );
2137 
2138             // rotation
2139             WriteAnimationCurveNode(outstream,
2140                 ids[1], "R", R, "Lcl Rotation",
2141                 layer_uid, node_uids[node]
2142             );
2143 
2144             // scale
2145             WriteAnimationCurveNode(outstream,
2146                 ids[2], "S", S, "Lcl Scale",
2147                 layer_uid, node_uids[node]
2148             );
2149 
2150             // store the uids for later use
2151             nodeanim_uids.push_back(ids);
2152         }
2153         curve_node_uids.push_back(nodeanim_uids);
2154     }
2155 
2156     // AnimCurve - defines actual keyframe data.
2157     // there's a separate curve for every component of every vector,
2158     // for example a transform curvenode will have separate X/Y/Z AnimCurve's
2159     for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) {
2160         const aiAnimation* anim = mScene->mAnimations[ai];
2161         for (size_t nai = 0; nai < anim->mNumChannels; ++nai) {
2162             const aiNodeAnim* na = anim->mChannels[nai];
2163             // get the corresponding aiNode
2164             const aiNode* node = mScene->mRootNode->FindNode(na->mNodeName);
2165             // and its transform
2166             const aiMatrix4x4 node_xfm = get_world_transform(node, mScene);
2167             aiVector3D T, R, S;
2168             node_xfm.Decompose(S, R, T);
2169             const std::array<int64_t,3>& ids = curve_node_uids[ai][nai];
2170 
2171             std::vector<int64_t> times;
2172             std::vector<float> xval, yval, zval;
2173 
2174             // position/translation
2175             for (size_t ki = 0; ki < na->mNumPositionKeys; ++ki) {
2176                 const aiVectorKey& k = na->mPositionKeys[ki];
2177                 times.push_back(to_ktime(k.mTime));
2178                 xval.push_back(k.mValue.x);
2179                 yval.push_back(k.mValue.y);
2180                 zval.push_back(k.mValue.z);
2181             }
2182             // one curve each for X, Y, Z
2183             WriteAnimationCurve(outstream, T.x, times, xval, ids[0], "d|X");
2184             WriteAnimationCurve(outstream, T.y, times, yval, ids[0], "d|Y");
2185             WriteAnimationCurve(outstream, T.z, times, zval, ids[0], "d|Z");
2186 
2187             // rotation
2188             times.clear(); xval.clear(); yval.clear(); zval.clear();
2189             for (size_t ki = 0; ki < na->mNumRotationKeys; ++ki) {
2190                 const aiQuatKey& k = na->mRotationKeys[ki];
2191                 times.push_back(to_ktime(k.mTime));
2192                 // TODO: aiQuaternion method to convert to Euler...
2193                 aiMatrix4x4 m(k.mValue.GetMatrix());
2194                 aiVector3D qs, qr, qt;
2195                 m.Decompose(qs, qr, qt);
2196                 qr *= DEG;
2197                 xval.push_back(qr.x);
2198                 yval.push_back(qr.y);
2199                 zval.push_back(qr.z);
2200             }
2201             WriteAnimationCurve(outstream, R.x, times, xval, ids[1], "d|X");
2202             WriteAnimationCurve(outstream, R.y, times, yval, ids[1], "d|Y");
2203             WriteAnimationCurve(outstream, R.z, times, zval, ids[1], "d|Z");
2204 
2205             // scaling/scale
2206             times.clear(); xval.clear(); yval.clear(); zval.clear();
2207             for (size_t ki = 0; ki < na->mNumScalingKeys; ++ki) {
2208                 const aiVectorKey& k = na->mScalingKeys[ki];
2209                 times.push_back(to_ktime(k.mTime));
2210                 xval.push_back(k.mValue.x);
2211                 yval.push_back(k.mValue.y);
2212                 zval.push_back(k.mValue.z);
2213             }
2214             WriteAnimationCurve(outstream, S.x, times, xval, ids[2], "d|X");
2215             WriteAnimationCurve(outstream, S.y, times, yval, ids[2], "d|Y");
2216             WriteAnimationCurve(outstream, S.z, times, zval, ids[2], "d|Z");
2217         }
2218     }
2219 
2220     indent = 0;
2221     object_node.End(outstream, binary, indent, true);
2222 }
2223 
2224 // convenience map of magic node name strings to FBX properties,
2225 // including the expected type of transform.
2226 const std::map<std::string,std::pair<std::string,char>> transform_types = {
2227     {"Translation", {"Lcl Translation", 't'}},
2228     {"RotationOffset", {"RotationOffset", 't'}},
2229     {"RotationPivot", {"RotationPivot", 't'}},
2230     {"PreRotation", {"PreRotation", 'r'}},
2231     {"Rotation", {"Lcl Rotation", 'r'}},
2232     {"PostRotation", {"PostRotation", 'r'}},
2233     {"RotationPivotInverse", {"RotationPivotInverse", 'i'}},
2234     {"ScalingOffset", {"ScalingOffset", 't'}},
2235     {"ScalingPivot", {"ScalingPivot", 't'}},
2236     {"Scaling", {"Lcl Scaling", 's'}},
2237     {"ScalingPivotInverse", {"ScalingPivotInverse", 'i'}},
2238     {"GeometricScaling", {"GeometricScaling", 's'}},
2239     {"GeometricRotation", {"GeometricRotation", 'r'}},
2240     {"GeometricTranslation", {"GeometricTranslation", 't'}},
2241     {"GeometricTranslationInverse", {"GeometricTranslationInverse", 'i'}},
2242     {"GeometricRotationInverse", {"GeometricRotationInverse", 'i'}},
2243     {"GeometricScalingInverse", {"GeometricScalingInverse", 'i'}}
2244 };
2245 
2246 // 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)2247 void FBXExporter::WriteModelNode(
2248     StreamWriterLE& outstream,
2249     bool binary,
2250     const aiNode* node,
2251     int64_t node_uid,
2252     const std::string& type,
2253     const std::vector<std::pair<std::string,aiVector3D>>& transform_chain,
2254     TransformInheritance inherit_type
2255 ){
2256     const aiVector3D zero = {0, 0, 0};
2257     const aiVector3D one = {1, 1, 1};
2258     FBX::Node m("Model");
2259     std::string name = node->mName.C_Str() + FBX::SEPARATOR + "Model";
2260     m.AddProperties(node_uid, name, type);
2261     m.AddChild("Version", int32_t(232));
2262     FBX::Node p("Properties70");
2263     p.AddP70bool("RotationActive", 1);
2264     p.AddP70int("DefaultAttributeIndex", 0);
2265     p.AddP70enum("InheritType", inherit_type);
2266     if (transform_chain.empty()) {
2267         // decompose 4x4 transform matrix into TRS
2268         aiVector3D t, r, s;
2269         node->mTransformation.Decompose(s, r, t);
2270         if (t != zero) {
2271             p.AddP70(
2272                 "Lcl Translation", "Lcl Translation", "", "A",
2273                 double(t.x), double(t.y), double(t.z)
2274             );
2275         }
2276         if (r != zero) {
2277             p.AddP70(
2278                 "Lcl Rotation", "Lcl Rotation", "", "A",
2279                 double(DEG*r.x), double(DEG*r.y), double(DEG*r.z)
2280             );
2281         }
2282         if (s != one) {
2283             p.AddP70(
2284                 "Lcl Scaling", "Lcl Scaling", "", "A",
2285                 double(s.x), double(s.y), double(s.z)
2286             );
2287         }
2288     } else {
2289         // apply the transformation chain.
2290         // these transformation elements are created when importing FBX,
2291         // which has a complex transformation hierarchy for each node.
2292         // as such we can bake the hierarchy back into the node on export.
2293         for (auto &item : transform_chain) {
2294             auto elem = transform_types.find(item.first);
2295             if (elem == transform_types.end()) {
2296                 // then this is a bug
2297                 std::stringstream err;
2298                 err << "unrecognized FBX transformation type: ";
2299                 err << item.first;
2300                 throw DeadlyExportError(err.str());
2301             }
2302             const std::string &name = elem->second.first;
2303             const aiVector3D &v = item.second;
2304             if (name.compare(0, 4, "Lcl ") == 0) {
2305                 // special handling for animatable properties
2306                 p.AddP70(
2307                     name, name, "", "A",
2308                     double(v.x), double(v.y), double(v.z)
2309                 );
2310             } else {
2311                 p.AddP70vector(name, v.x, v.y, v.z);
2312             }
2313         }
2314     }
2315     m.AddChild(p);
2316 
2317     // not sure what these are for,
2318     // but they seem to be omnipresent
2319     m.AddChild("Shading", FBXExportProperty(true));
2320     m.AddChild("Culling", FBXExportProperty("CullingOff"));
2321 
2322     m.Dump(outstream, binary, 1);
2323 }
2324 
2325 // 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)2326 void FBXExporter::WriteModelNodes(
2327     StreamWriterLE& s,
2328     const aiNode* node,
2329     int64_t parent_uid,
2330     const std::unordered_set<const aiNode*>& limbnodes
2331 ) {
2332     std::vector<std::pair<std::string,aiVector3D>> chain;
2333     WriteModelNodes(s, node, parent_uid, limbnodes, chain);
2334 }
2335 
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)2336 void FBXExporter::WriteModelNodes(
2337     StreamWriterLE& outstream,
2338     const aiNode* node,
2339     int64_t parent_uid,
2340     const std::unordered_set<const aiNode*>& limbnodes,
2341     std::vector<std::pair<std::string,aiVector3D>>& transform_chain
2342 ) {
2343     // first collapse any expanded transformation chains created by FBX import.
2344     std::string node_name(node->mName.C_Str());
2345     if (node_name.find(MAGIC_NODE_TAG) != std::string::npos) {
2346         auto pos = node_name.find(MAGIC_NODE_TAG) + MAGIC_NODE_TAG.size() + 1;
2347         std::string type_name = node_name.substr(pos);
2348         auto elem = transform_types.find(type_name);
2349         if (elem == transform_types.end()) {
2350             // then this is a bug and should be fixed
2351             std::stringstream err;
2352             err << "unrecognized FBX transformation node";
2353             err << " of type " << type_name << " in node " << node_name;
2354             throw DeadlyExportError(err.str());
2355         }
2356         aiVector3D t, r, s;
2357         node->mTransformation.Decompose(s, r, t);
2358         switch (elem->second.second) {
2359         case 'i': // inverse
2360             // we don't need to worry about the inverse matrices
2361             break;
2362         case 't': // translation
2363             transform_chain.emplace_back(elem->first, t);
2364             break;
2365         case 'r': // rotation
2366             r *= float(DEG);
2367             transform_chain.emplace_back(elem->first, r);
2368             break;
2369         case 's': // scale
2370             transform_chain.emplace_back(elem->first, s);
2371             break;
2372         default:
2373             // this should never happen
2374             std::stringstream err;
2375             err << "unrecognized FBX transformation type code: ";
2376             err << elem->second.second;
2377             throw DeadlyExportError(err.str());
2378         }
2379         // now continue on to any child nodes
2380         for (unsigned i = 0; i < node->mNumChildren; ++i) {
2381             WriteModelNodes(
2382                 outstream,
2383                 node->mChildren[i],
2384                 parent_uid,
2385                 limbnodes,
2386                 transform_chain
2387             );
2388         }
2389         return;
2390     }
2391 
2392     int64_t node_uid = 0;
2393     // generate uid and connect to parent, if not the root node,
2394     if (node != mScene->mRootNode) {
2395         auto elem = node_uids.find(node);
2396         if (elem != node_uids.end()) {
2397             node_uid = elem->second;
2398         } else {
2399             node_uid = generate_uid();
2400             node_uids[node] = node_uid;
2401         }
2402         connections.emplace_back("C", "OO", node_uid, parent_uid);
2403     }
2404 
2405     // what type of node is this?
2406     if (node == mScene->mRootNode) {
2407         // handled later
2408     } else if (node->mNumMeshes == 1) {
2409         // connect to child mesh, which should have been written previously
2410         connections.emplace_back(
2411             "C", "OO", mesh_uids[node->mMeshes[0]], node_uid
2412         );
2413         // also connect to the material for the child mesh
2414         connections.emplace_back(
2415             "C", "OO",
2416             material_uids[mScene->mMeshes[node->mMeshes[0]]->mMaterialIndex],
2417             node_uid
2418         );
2419         // write model node
2420         WriteModelNode(
2421             outstream, binary, node, node_uid, "Mesh", transform_chain
2422         );
2423     } else if (limbnodes.count(node)) {
2424         WriteModelNode(
2425             outstream, binary, node, node_uid, "LimbNode", transform_chain
2426         );
2427         // we also need to write a nodeattribute to mark it as a skeleton
2428         int64_t node_attribute_uid = generate_uid();
2429         FBX::Node na("NodeAttribute");
2430         na.AddProperties(
2431             node_attribute_uid, FBX::SEPARATOR + "NodeAttribute", "LimbNode"
2432         );
2433         na.AddChild("TypeFlags", FBXExportProperty("Skeleton"));
2434         na.Dump(outstream, binary, 1);
2435         // and connect them
2436         connections.emplace_back("C", "OO", node_attribute_uid, node_uid);
2437     } else {
2438         // generate a null node so we can add children to it
2439         WriteModelNode(
2440             outstream, binary, node, node_uid, "Null", transform_chain
2441         );
2442     }
2443 
2444     // if more than one child mesh, make nodes for each mesh
2445     if (node->mNumMeshes > 1 || node == mScene->mRootNode) {
2446         for (size_t i = 0; i < node->mNumMeshes; ++i) {
2447             // make a new model node
2448             int64_t new_node_uid = generate_uid();
2449             // connect to parent node
2450             connections.emplace_back("C", "OO", new_node_uid, node_uid);
2451             // connect to child mesh, which should have been written previously
2452             connections.emplace_back(
2453                 "C", "OO", mesh_uids[node->mMeshes[i]], new_node_uid
2454             );
2455             // also connect to the material for the child mesh
2456             connections.emplace_back(
2457                 "C", "OO",
2458                 material_uids[
2459                     mScene->mMeshes[node->mMeshes[i]]->mMaterialIndex
2460                 ],
2461                 new_node_uid
2462             );
2463             // write model node
2464             FBX::Node m("Model");
2465             // take name from mesh name, if it exists
2466             std::string name = mScene->mMeshes[node->mMeshes[i]]->mName.C_Str();
2467             name += FBX::SEPARATOR + "Model";
2468             m.AddProperties(new_node_uid, name, "Mesh");
2469             m.AddChild("Version", int32_t(232));
2470             FBX::Node p("Properties70");
2471             p.AddP70enum("InheritType", 1);
2472             m.AddChild(p);
2473             m.Dump(outstream, binary, 1);
2474         }
2475     }
2476 
2477     // now recurse into children
2478     for (size_t i = 0; i < node->mNumChildren; ++i) {
2479         WriteModelNodes(
2480             outstream, node->mChildren[i], node_uid, limbnodes
2481         );
2482     }
2483 }
2484 
2485 
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)2486 void FBXExporter::WriteAnimationCurveNode(
2487     StreamWriterLE& outstream,
2488     int64_t uid,
2489     const std::string& name, // "T", "R", or "S"
2490     aiVector3D default_value,
2491     std::string property_name, // "Lcl Translation" etc
2492     int64_t layer_uid,
2493     int64_t node_uid
2494 ) {
2495     FBX::Node n("AnimationCurveNode");
2496     n.AddProperties(uid, name + FBX::SEPARATOR + "AnimCurveNode", "");
2497     FBX::Node p("Properties70");
2498     p.AddP70numberA("d|X", default_value.x);
2499     p.AddP70numberA("d|Y", default_value.y);
2500     p.AddP70numberA("d|Z", default_value.z);
2501     n.AddChild(p);
2502     n.Dump(outstream, binary, 1);
2503     // connect to layer
2504     this->connections.emplace_back("C", "OO", uid, layer_uid);
2505     // connect to bone
2506     this->connections.emplace_back("C", "OP", uid, node_uid, property_name);
2507 }
2508 
2509 
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)2510 void FBXExporter::WriteAnimationCurve(
2511     StreamWriterLE& outstream,
2512     double default_value,
2513     const std::vector<int64_t>& times,
2514     const std::vector<float>& values,
2515     int64_t curvenode_uid,
2516     const std::string& property_link // "d|X", "d|Y", etc
2517 ) {
2518     FBX::Node n("AnimationCurve");
2519     int64_t curve_uid = generate_uid();
2520     n.AddProperties(curve_uid, FBX::SEPARATOR + "AnimCurve", "");
2521     n.AddChild("Default", default_value);
2522     n.AddChild("KeyVer", int32_t(4009));
2523     n.AddChild("KeyTime", times);
2524     n.AddChild("KeyValueFloat", values);
2525     // TODO: keyattr flags and data (STUB for now)
2526     n.AddChild("KeyAttrFlags", std::vector<int32_t>{0});
2527     n.AddChild("KeyAttrDataFloat", std::vector<float>{0,0,0,0});
2528     n.AddChild(
2529         "KeyAttrRefCount",
2530         std::vector<int32_t>{static_cast<int32_t>(times.size())}
2531     );
2532     n.Dump(outstream, binary, 1);
2533     this->connections.emplace_back(
2534         "C", "OP", curve_uid, curvenode_uid, property_link
2535     );
2536 }
2537 
2538 
WriteConnections()2539 void FBXExporter::WriteConnections ()
2540 {
2541     // we should have completed the connection graph already,
2542     // so basically just dump it here
2543     if (!binary) {
2544         WriteAsciiSectionHeader("Object connections");
2545     }
2546     // TODO: comments with names in the ascii version
2547     FBX::Node conn("Connections");
2548     StreamWriterLE outstream(outfile);
2549     conn.Begin(outstream, binary, 0);
2550     conn.BeginChildren(outstream, binary, 0);
2551     for (auto &n : connections) {
2552         n.Dump(outstream, binary, 1);
2553     }
2554     conn.End(outstream, binary, 0, !connections.empty());
2555     connections.clear();
2556 }
2557 
2558 #endif // ASSIMP_BUILD_NO_FBX_EXPORTER
2559 #endif // ASSIMP_BUILD_NO_EXPORT
2560