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