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