diff --git a/code/Exporter.cpp b/code/Exporter.cpp index 49523e658..b7478c0be 100644 --- a/code/Exporter.cpp +++ b/code/Exporter.cpp @@ -98,7 +98,7 @@ void ExportSceneAssbin(const char*, IOSystem*, const aiScene*, const ExportPrope void ExportSceneAssxml(const char*, IOSystem*, const aiScene*, const ExportProperties*); void ExportSceneX3D(const char*, IOSystem*, const aiScene*, const ExportProperties*); void ExportSceneFBX(const char*, IOSystem*, const aiScene*, const ExportProperties*); -//void ExportSceneFBXA(const char*, IOSystem*, const aiScene*, const ExportProperties*); +void ExportSceneFBXA(const char*, IOSystem*, const aiScene*, const ExportProperties*); void ExportScene3MF( const char*, IOSystem*, const aiScene*, const ExportProperties* ); // ------------------------------------------------------------------------------------------------ @@ -173,7 +173,7 @@ Exporter::ExportFormatEntry gExporters[] = #ifndef ASSIMP_BUILD_NO_FBX_EXPORTER Exporter::ExportFormatEntry( "fbx", "Autodesk FBX (binary)", "fbx", &ExportSceneFBX, 0 ), - //Exporter::ExportFormatEntry( "fbxa", "Autodesk FBX (ascii)", "fbx", &ExportSceneFBXA, 0 ), + Exporter::ExportFormatEntry( "fbxa", "Autodesk FBX (ascii)", "fbx", &ExportSceneFBXA, 0 ), #endif #ifndef ASSIMP_BUILD_NO_3MF_EXPORTER diff --git a/code/FBXExportNode.cpp b/code/FBXExportNode.cpp index 596901989..a1171eca8 100644 --- a/code/FBXExportNode.cpp +++ b/code/FBXExportNode.cpp @@ -45,9 +45,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "FBXCommon.h" #include // StreamWriterLE +#include // DeadlyExportError #include +#include // ai_snprintf #include +#include +#include // ostringstream #include // shared_ptr // AddP70 helpers... there's no usable pattern here, @@ -145,33 +149,174 @@ void FBX::Node::AddP70time( } +// public member functions for writing nodes to stream + +void FBX::Node::Dump( + std::shared_ptr outfile, + bool binary, int indent +) { + if (binary) { + Assimp::StreamWriterLE outstream(outfile); + DumpBinary(outstream); + } else { + std::ostringstream ss; + DumpAscii(ss, indent); + std::string s = ss.str(); + outfile->Write(s.c_str(), s.size(), 1); + } +} + +void FBX::Node::Dump( + Assimp::StreamWriterLE &outstream, + bool binary, int indent +) { + if (binary) { + DumpBinary(outstream); + } else { + std::ostringstream ss; + DumpAscii(ss, indent); + outstream.PutString(ss.str()); + } +} + + +// public member functions for low-level writing + +void FBX::Node::Begin( + Assimp::StreamWriterLE &s, + bool binary, int indent +) { + if (binary) { + BeginBinary(s); + } else { + // assume we're at the correct place to start already + (void)indent; + std::ostringstream ss; + BeginAscii(ss, indent); + s.PutString(ss.str()); + } +} + +void FBX::Node::DumpProperties( + Assimp::StreamWriterLE& s, + bool binary, int indent +) { + if (binary) { + DumpPropertiesBinary(s); + } else { + std::ostringstream ss; + DumpPropertiesAscii(ss, indent); + s.PutString(ss.str()); + } +} + +void FBX::Node::EndProperties( + Assimp::StreamWriterLE &s, + bool binary, int indent +) { + EndProperties(s, binary, indent, properties.size()); +} + +void FBX::Node::EndProperties( + Assimp::StreamWriterLE &s, + bool binary, int indent, + size_t num_properties +) { + if (binary) { + EndPropertiesBinary(s, num_properties); + } else { + // nothing to do + (void)indent; + } +} + +void FBX::Node::BeginChildren( + Assimp::StreamWriterLE &s, + bool binary, int indent +) { + if (binary) { + // nothing to do + } else { + std::ostringstream ss; + BeginChildrenAscii(ss, indent); + s.PutString(ss.str()); + } +} + +void FBX::Node::DumpChildren( + Assimp::StreamWriterLE& s, + bool binary, int indent +) { + if (binary) { + DumpChildrenBinary(s); + } else { + std::ostringstream ss; + DumpChildrenAscii(ss, indent); + s.PutString(ss.str()); + } +} + +void FBX::Node::End( + Assimp::StreamWriterLE &s, + bool binary, int indent, + bool has_children +) { + if (binary) { + EndBinary(s, has_children); + } else { + std::ostringstream ss; + EndAscii(ss, indent, has_children); + s.PutString(ss.str()); + } +} + + // public member functions for writing to binary fbx -void FBX::Node::Dump(std::shared_ptr outfile) -{ - Assimp::StreamWriterLE outstream(outfile); - Dump(outstream); -} - -void FBX::Node::Dump(Assimp::StreamWriterLE &s) +void FBX::Node::DumpBinary(Assimp::StreamWriterLE &s) { // write header section (with placeholders for some things) - Begin(s); + BeginBinary(s); // write properties - DumpProperties(s); + DumpPropertiesBinary(s); // go back and fill in property related placeholders - EndProperties(s, properties.size()); + EndPropertiesBinary(s, properties.size()); // write children - DumpChildren(s); + DumpChildrenBinary(s); // finish, filling in end offset placeholder - End(s, !children.empty()); + EndBinary(s, force_has_children || !children.empty()); } -void FBX::Node::Begin(Assimp::StreamWriterLE &s) + +// public member functions for writing to ascii fbx + +void FBX::Node::DumpAscii(std::ostream &s, int indent) +{ + // write name + BeginAscii(s, indent); + + // write properties + DumpPropertiesAscii(s, indent); + + if (force_has_children || !children.empty()) { + // begin children (with a '{') + BeginChildrenAscii(s, indent + 1); + // write children + DumpChildrenAscii(s, indent + 1); + } + + // finish (also closing the children bracket '}') + EndAscii(s, indent, force_has_children || !children.empty()); +} + + +// private member functions for low-level writing to fbx + +void FBX::Node::BeginBinary(Assimp::StreamWriterLE &s) { // remember start pos so we can come back and write the end pos this->start_pos = s.Tell(); @@ -189,26 +334,14 @@ void FBX::Node::Begin(Assimp::StreamWriterLE &s) this->property_start = s.Tell(); } -void FBX::Node::DumpProperties(Assimp::StreamWriterLE& s) +void FBX::Node::DumpPropertiesBinary(Assimp::StreamWriterLE& s) { for (auto &p : properties) { - p.Dump(s); + p.DumpBinary(s); } } -void FBX::Node::DumpChildren(Assimp::StreamWriterLE& s) -{ - for (FBX::Node& child : children) { - child.Dump(s); - } -} - -void FBX::Node::EndProperties(Assimp::StreamWriterLE &s) -{ - EndProperties(s, properties.size()); -} - -void FBX::Node::EndProperties( +void FBX::Node::EndPropertiesBinary( Assimp::StreamWriterLE &s, size_t num_properties ) { @@ -222,7 +355,14 @@ void FBX::Node::EndProperties( s.Seek(pos); } -void FBX::Node::End( +void FBX::Node::DumpChildrenBinary(Assimp::StreamWriterLE& s) +{ + for (FBX::Node& child : children) { + child.DumpBinary(s); + } +} + +void FBX::Node::EndBinary( Assimp::StreamWriterLE &s, bool has_children ) { @@ -237,48 +377,192 @@ void FBX::Node::End( } -// static member functions +void FBX::Node::BeginAscii(std::ostream& s, int indent) +{ + s << '\n'; + for (int i = 0; i < indent; ++i) { s << '\t'; } + s << name << ": "; +} -// convenience function to create and write a property node, -// holding a single property which is an array of values. -// does not copy the data, so is efficient for large arrays. +void FBX::Node::DumpPropertiesAscii(std::ostream &s, int indent) +{ + for (size_t i = 0; i < properties.size(); ++i) { + if (i > 0) { s << ", "; } + properties[i].DumpAscii(s, indent); + } +} + +void FBX::Node::BeginChildrenAscii(std::ostream& s, int indent) +{ + // only call this if there are actually children + s << " {"; + (void)indent; +} + +void FBX::Node::DumpChildrenAscii(std::ostream& s, int indent) +{ + // children will need a lot of padding and corralling + if (children.size() || force_has_children) { + for (size_t i = 0; i < children.size(); ++i) { + // no compression in ascii files, so skip this node if it exists + if (children[i].name == "EncryptionType") { continue; } + // the child can dump itself + children[i].DumpAscii(s, indent); + } + } +} + +void FBX::Node::EndAscii(std::ostream& s, int indent, bool has_children) +{ + if (!has_children) { return; } // nothing to do + s << '\n'; + for (int i = 0; i < indent; ++i) { s << '\t'; } + s << "}"; +} + +// private helpers for static member functions + +// ascii property node from vector of doubles +void FBX::Node::WritePropertyNodeAscii( + const std::string& name, + const std::vector& v, + Assimp::StreamWriterLE& s, + int indent +){ + char buffer[32]; + FBX::Node node(name); + node.Begin(s, false, indent); + std::string vsize = std::to_string(v.size()); + // * { + s.PutChar('*'); s.PutString(vsize); s.PutString(" {\n"); + // indent + 1 + for (int i = 0; i < indent + 1; ++i) { s.PutChar('\t'); } + // a: value,value,value,... + s.PutString("a: "); + int count = 0; + for (size_t i = 0; i < v.size(); ++i) { + if (i > 0) { s.PutChar(','); } + int len = ai_snprintf(buffer, sizeof(buffer), "%f", v[i]); + count += len; + if (count > 2048) { s.PutChar('\n'); count = 0; } + if (len < 0 || len > 31) { + // this should never happen + throw DeadlyExportError("failed to convert double to string"); + } + for (int j = 0; j < len; ++j) { s.PutChar(buffer[j]); } + } + // } + s.PutChar('\n'); + for (int i = 0; i < indent; ++i) { s.PutChar('\t'); } + s.PutChar('}'); s.PutChar(' '); + node.End(s, false, indent, false); +} + +// ascii property node from vector of int32_t +void FBX::Node::WritePropertyNodeAscii( + const std::string& name, + const std::vector& v, + Assimp::StreamWriterLE& s, + int indent +){ + char buffer[32]; + FBX::Node node(name); + node.Begin(s, false, indent); + std::string vsize = std::to_string(v.size()); + // * { + s.PutChar('*'); s.PutString(vsize); s.PutString(" {\n"); + // indent + 1 + for (int i = 0; i < indent + 1; ++i) { s.PutChar('\t'); } + // a: value,value,value,... + s.PutString("a: "); + int count = 0; + for (size_t i = 0; i < v.size(); ++i) { + if (i > 0) { s.PutChar(','); } + int len = ai_snprintf(buffer, sizeof(buffer), "%d", v[i]); + count += len; + if (count > 2048) { s.PutChar('\n'); count = 0; } + if (len < 0 || len > 31) { + // this should never happen + throw DeadlyExportError("failed to convert double to string"); + } + for (int j = 0; j < len; ++j) { s.PutChar(buffer[j]); } + } + // } + s.PutChar('\n'); + for (int i = 0; i < indent; ++i) { s.PutChar('\t'); } + s.PutChar('}'); s.PutChar(' '); + node.End(s, false, indent, false); +} + +// binary property node from vector of doubles // TODO: optional zip compression! -void FBX::Node::WritePropertyNode( +void FBX::Node::WritePropertyNodeBinary( const std::string& name, const std::vector& v, Assimp::StreamWriterLE& s ){ - Node node(name); - node.Begin(s); + FBX::Node node(name); + node.BeginBinary(s); s.PutU1('d'); s.PutU4(uint32_t(v.size())); // number of elements s.PutU4(0); // no encoding (1 would be zip-compressed) s.PutU4(uint32_t(v.size()) * 8); // data size for (auto it = v.begin(); it != v.end(); ++it) { s.PutF8(*it); } - node.EndProperties(s, 1); - node.End(s, false); + node.EndPropertiesBinary(s, 1); + node.EndBinary(s, false); } -// convenience function to create and write a property node, -// holding a single property which is an array of values. -// does not copy the data, so is efficient for large arrays. +// binary property node from vector of int32_t // TODO: optional zip compression! -void FBX::Node::WritePropertyNode( +void FBX::Node::WritePropertyNodeBinary( const std::string& name, const std::vector& v, Assimp::StreamWriterLE& s ){ - Node node(name); - node.Begin(s); + FBX::Node node(name); + node.BeginBinary(s); s.PutU1('i'); s.PutU4(uint32_t(v.size())); // number of elements s.PutU4(0); // no encoding (1 would be zip-compressed) s.PutU4(uint32_t(v.size()) * 4); // data size for (auto it = v.begin(); it != v.end(); ++it) { s.PutI4(*it); } - node.EndProperties(s, 1); - node.End(s, false); + node.EndPropertiesBinary(s, 1); + node.EndBinary(s, false); } +// public static member functions + +// convenience function to create and write a property node, +// holding a single property which is an array of values. +// does not copy the data, so is efficient for large arrays. +void FBX::Node::WritePropertyNode( + const std::string& name, + const std::vector& v, + Assimp::StreamWriterLE& s, + bool binary, int indent +){ + if (binary) { + FBX::Node::WritePropertyNodeBinary(name, v, s); + } else { + FBX::Node::WritePropertyNodeAscii(name, v, s, indent); + } +} + +// convenience function to create and write a property node, +// holding a single property which is an array of values. +// does not copy the data, so is efficient for large arrays. +void FBX::Node::WritePropertyNode( + const std::string& name, + const std::vector& v, + Assimp::StreamWriterLE& s, + bool binary, int indent +){ + if (binary) { + FBX::Node::WritePropertyNodeBinary(name, v, s); + } else { + FBX::Node::WritePropertyNodeAscii(name, v, s, indent); + } +} #endif // ASSIMP_BUILD_NO_FBX_EXPORTER #endif // ASSIMP_BUILD_NO_EXPORT diff --git a/code/FBXExportNode.h b/code/FBXExportNode.h index edce8f700..5ddd8c77b 100644 --- a/code/FBXExportNode.h +++ b/code/FBXExportNode.h @@ -66,14 +66,18 @@ public: // public data members std::vector properties; // node properties std::vector children; // child nodes + // some nodes always pretend they have children... + bool force_has_children = false; + public: // constructors Node() = default; Node(const std::string& n) : name(n) {} - Node(const std::string& n, const FBX::Property &p) + + // convenience template to construct with properties directly + template + Node(const std::string& n, const More... more) : name(n) - { properties.push_back(p); } - Node(const std::string& n, const std::vector &pv) - : name(n), properties(pv) {} + { AddProperties(more...); } public: // functions to add properties or children // add a single property to the node @@ -138,19 +142,48 @@ public: // support specifically for dealing with Properties70 nodes public: // member functions for writing data to a file or stream - // write the full node as binary data to the given file or stream - void Dump(std::shared_ptr outfile); - void Dump(Assimp::StreamWriterLE &s); + // write the full node to the given file or stream + void Dump( + std::shared_ptr outfile, + bool binary, int indent + ); + void Dump(Assimp::StreamWriterLE &s, bool binary, int indent); // these other functions are for writing data piece by piece. // they must be used carefully. // for usage examples see FBXExporter.cpp. - void Begin(Assimp::StreamWriterLE &s); - void DumpProperties(Assimp::StreamWriterLE& s); - void EndProperties(Assimp::StreamWriterLE &s); - void EndProperties(Assimp::StreamWriterLE &s, size_t num_properties); - void DumpChildren(Assimp::StreamWriterLE& s); - void End(Assimp::StreamWriterLE &s, bool has_children); + void Begin(Assimp::StreamWriterLE &s, bool binary, int indent); + void DumpProperties(Assimp::StreamWriterLE& s, bool binary, int indent); + void EndProperties(Assimp::StreamWriterLE &s, bool binary, int indent); + void EndProperties( + Assimp::StreamWriterLE &s, bool binary, int indent, + size_t num_properties + ); + void BeginChildren(Assimp::StreamWriterLE &s, bool binary, int indent); + void DumpChildren(Assimp::StreamWriterLE& s, bool binary, int indent); + void End( + Assimp::StreamWriterLE &s, bool binary, int indent, + bool has_children + ); + +private: // internal functions used for writing + + void DumpBinary(Assimp::StreamWriterLE &s); + void DumpAscii(Assimp::StreamWriterLE &s, int indent); + void DumpAscii(std::ostream &s, int indent); + + void BeginBinary(Assimp::StreamWriterLE &s); + void DumpPropertiesBinary(Assimp::StreamWriterLE& s); + void EndPropertiesBinary(Assimp::StreamWriterLE &s); + void EndPropertiesBinary(Assimp::StreamWriterLE &s, size_t num_properties); + void DumpChildrenBinary(Assimp::StreamWriterLE& s); + void EndBinary(Assimp::StreamWriterLE &s, bool has_children); + + void BeginAscii(std::ostream &s, int indent); + void DumpPropertiesAscii(std::ostream &s, int indent); + void BeginChildrenAscii(std::ostream &s, int indent); + void DumpChildrenAscii(std::ostream &s, int indent); + void EndAscii(std::ostream &s, int indent, bool has_children); private: // data used for binary dumps size_t start_pos; // starting position in stream @@ -165,11 +198,12 @@ public: // static member functions static void WritePropertyNode( const std::string& name, const T value, - Assimp::StreamWriterLE& s + Assimp::StreamWriterLE& s, + bool binary, int indent ) { FBX::Property p(value); FBX::Node node(name, p); - node.Dump(s); + node.Dump(s, binary, indent); } // convenience function to create and write a property node, @@ -178,7 +212,8 @@ public: // static member functions static void WritePropertyNode( const std::string& name, const std::vector& v, - Assimp::StreamWriterLE& s + Assimp::StreamWriterLE& s, + bool binary, int indent ); // convenience function to create and write a property node, @@ -187,8 +222,34 @@ public: // static member functions static void WritePropertyNode( const std::string& name, const std::vector& v, + Assimp::StreamWriterLE& s, + bool binary, int indent + ); + +private: // static helper functions + static void WritePropertyNodeAscii( + const std::string& name, + const std::vector& v, + Assimp::StreamWriterLE& s, + int indent + ); + static void WritePropertyNodeAscii( + const std::string& name, + const std::vector& v, + Assimp::StreamWriterLE& s, + int indent + ); + static void WritePropertyNodeBinary( + const std::string& name, + const std::vector& v, Assimp::StreamWriterLE& s ); + static void WritePropertyNodeBinary( + const std::string& name, + const std::vector& v, + Assimp::StreamWriterLE& s + ); + }; diff --git a/code/FBXExportProperty.cpp b/code/FBXExportProperty.cpp index e139bb95a..431750274 100644 --- a/code/FBXExportProperty.cpp +++ b/code/FBXExportProperty.cpp @@ -48,7 +48,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include // stringstream +#include +#include +#include // ostringstream // constructors for single element properties @@ -164,18 +166,18 @@ size_t FBX::Property::size() } } -void FBX::Property::Dump(Assimp::StreamWriterLE &s) +void FBX::Property::DumpBinary(Assimp::StreamWriterLE &s) { s.PutU1(type); - uint8_t* d; + uint8_t* d = data.data(); size_t N; switch (type) { - case 'C': s.PutU1(*(reinterpret_cast(data.data()))); return; - case 'Y': s.PutI2(*(reinterpret_cast(data.data()))); return; - case 'I': s.PutI4(*(reinterpret_cast(data.data()))); return; - case 'F': s.PutF4(*(reinterpret_cast(data.data()))); return; - case 'D': s.PutF8(*(reinterpret_cast(data.data()))); return; - case 'L': s.PutI8(*(reinterpret_cast(data.data()))); return; + case 'C': s.PutU1(*(reinterpret_cast(d))); return; + case 'Y': s.PutI2(*(reinterpret_cast(d))); return; + case 'I': s.PutI4(*(reinterpret_cast(d))); return; + case 'F': s.PutF4(*(reinterpret_cast(d))); return; + case 'D': s.PutF8(*(reinterpret_cast(d))); return; + case 'L': s.PutI8(*(reinterpret_cast(d))); return; case 'S': case 'R': s.PutU4(uint32_t(data.size())); @@ -187,7 +189,6 @@ void FBX::Property::Dump(Assimp::StreamWriterLE &s) s.PutU4(0); // no encoding (1 would be zip-compressed) // TODO: compress if large? s.PutU4(uint32_t(data.size())); // data size - d = data.data(); for (size_t i = 0; i < N; ++i) { s.PutI4((reinterpret_cast(d))[i]); } @@ -198,7 +199,6 @@ void FBX::Property::Dump(Assimp::StreamWriterLE &s) s.PutU4(0); // no encoding (1 would be zip-compressed) // TODO: compress if large? s.PutU4(uint32_t(data.size())); // data size - d = data.data(); for (size_t i = 0; i < N; ++i) { s.PutI8((reinterpret_cast(d))[i]); } @@ -209,7 +209,6 @@ void FBX::Property::Dump(Assimp::StreamWriterLE &s) s.PutU4(0); // no encoding (1 would be zip-compressed) // TODO: compress if large? s.PutU4(uint32_t(data.size())); // data size - d = data.data(); for (size_t i = 0; i < N; ++i) { s.PutF4((reinterpret_cast(d))[i]); } @@ -220,18 +219,146 @@ void FBX::Property::Dump(Assimp::StreamWriterLE &s) s.PutU4(0); // no encoding (1 would be zip-compressed) // TODO: compress if large? s.PutU4(uint32_t(data.size())); // data size - d = data.data(); for (size_t i = 0; i < N; ++i) { s.PutF8((reinterpret_cast(d))[i]); } return; default: - std::stringstream err; + std::ostringstream err; err << "Tried to dump property with invalid type '"; err << type << "'!"; throw DeadlyExportError(err.str()); } } +void FBX::Property::DumpAscii(Assimp::StreamWriterLE &outstream, int indent) +{ + std::ostringstream ss; + ss.imbue(std::locale::classic()); + ss.precision(15); // this seems to match official FBX SDK exports + DumpAscii(ss, indent); + outstream.PutString(ss.str()); +} + +void FBX::Property::DumpAscii(std::ostream& s, int indent) +{ + // no writing type... or anything. just shove it into the stream. + uint8_t* d = data.data(); + size_t N; + size_t swap = data.size(); + size_t count = 0; + switch (type) { + case 'C': + if (*(reinterpret_cast(d))) { s << 'T'; } + else { s << 'F'; } + return; + case 'Y': s << *(reinterpret_cast(d)); return; + case 'I': s << *(reinterpret_cast(d)); return; + case 'F': s << *(reinterpret_cast(d)); return; + case 'D': s << *(reinterpret_cast(d)); return; + case 'L': s << *(reinterpret_cast(d)); return; + case 'S': + // first search to see if it has "\x00\x01" in it - + // which separates fields which are reversed in the ascii version. + // yeah. + // FBX, yeah. + for (size_t i = 0; i < data.size(); ++i) { + if (data[i] == '\0') { + swap = i; + break; + } + } + case 'R': + s << '"'; + // we might as well check this now, + // probably it will never happen + for (size_t i = 0; i < data.size(); ++i) { + char c = data[i]; + if (c == '"') { + throw runtime_error("can't handle quotes in property string"); + } + } + // first write the SWAPPED member (if any) + for (size_t i = swap + 2; i < data.size(); ++i) { + char c = data[i]; + s << c; + } + // then a separator + if (swap != data.size()) { + s << "::"; + } + // then the initial member + for (size_t i = 0; i < swap; ++i) { + char c = data[i]; + s << c; + } + s << '"'; + return; + case 'i': + N = data.size() / 4; // number of elements + s << '*' << N << " {\n"; + for (int i = 0; i < indent + 1; ++i) { s << '\t'; } + s << "a: "; + for (size_t i = 0; i < N; ++i) { + if (i > 0) { s << ','; } + if (count++ > 120) { s << '\n'; count = 0; } + s << (reinterpret_cast(d))[i]; + } + s << '\n'; + for (int i = 0; i < indent; ++i) { s << '\t'; } + s << "} "; + return; + case 'l': + N = data.size() / 8; + s << '*' << N << " {\n"; + for (int i = 0; i < indent + 1; ++i) { s << '\t'; } + s << "a: "; + for (size_t i = 0; i < N; ++i) { + if (i > 0) { s << ','; } + if (count++ > 120) { s << '\n'; count = 0; } + s << (reinterpret_cast(d))[i]; + } + s << '\n'; + for (int i = 0; i < indent; ++i) { s << '\t'; } + s << "} "; + return; + case 'f': + N = data.size() / 4; + s << '*' << N << " {\n"; + for (int i = 0; i < indent + 1; ++i) { s << '\t'; } + s << "a: "; + for (size_t i = 0; i < N; ++i) { + if (i > 0) { s << ','; } + if (count++ > 120) { s << '\n'; count = 0; } + s << (reinterpret_cast(d))[i]; + } + s << '\n'; + for (int i = 0; i < indent; ++i) { s << '\t'; } + s << "} "; + return; + case 'd': + N = data.size() / 8; + s << '*' << N << " {\n"; + for (int i = 0; i < indent + 1; ++i) { s << '\t'; } + s << "a: "; + // set precision to something that can handle doubles + s.precision(15); + for (size_t i = 0; i < N; ++i) { + if (i > 0) { s << ','; } + if (count++ > 120) { s << '\n'; count = 0; } + s << (reinterpret_cast(d))[i]; + } + s << '\n'; + for (int i = 0; i < indent; ++i) { s << '\t'; } + s << "} "; + return; + default: + std::ostringstream err; + err << "Tried to dump property with invalid type '"; + err << type << "'!"; + throw runtime_error(err.str()); + } +} + #endif // ASSIMP_BUILD_NO_FBX_EXPORTER #endif // ASSIMP_BUILD_NO_EXPORT diff --git a/code/FBXExportProperty.h b/code/FBXExportProperty.h index 40a020688..cb3b0113f 100644 --- a/code/FBXExportProperty.h +++ b/code/FBXExportProperty.h @@ -53,6 +53,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include // is_void namespace FBX { @@ -113,7 +114,10 @@ public: size_t size(); // write this property node as binary data to the given stream - void Dump(Assimp::StreamWriterLE &s); + void DumpBinary(Assimp::StreamWriterLE &s); + void DumpAscii(Assimp::StreamWriterLE &s, int indent=0); + void DumpAscii(std::ostream &s, int indent=0); + // note: make sure the ostream is in classic "C" locale private: char type; diff --git a/code/FBXExporter.cpp b/code/FBXExporter.cpp index 7dcf752da..c524e3911 100644 --- a/code/FBXExporter.cpp +++ b/code/FBXExporter.cpp @@ -66,7 +66,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include // endl // RESOURCES: // https://code.blender.org/2013/08/fbx-binary-file-format-specification/ @@ -89,6 +88,8 @@ namespace FBX { "\xfa\xbc\xab\x09\xd0\xc8\xd4\x66\xb1\x76\xfb\x83\x1c\xf7\x26\x7e"; const std::string FOOT_MAGIC = "\xf8\x5a\x8c\x6a\xde\xf5\xd9\x7e\xec\xe9\x0c\xe3\x75\x8f\x29\x0b"; + const std::string COMMENT_UNDERLINE = + ";------------------------------------------------------------------"; } using namespace Assimp; @@ -115,7 +116,7 @@ namespace Assimp { // --------------------------------------------------------------------- // Worker function for exporting a scene to ASCII FBX. // Prototyped and registered in Exporter.cpp - /*void ExportSceneFBXA ( + void ExportSceneFBXA ( const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, @@ -126,7 +127,7 @@ namespace Assimp { // perform ascii export exporter.ExportAscii(pFile, pIOSystem); - }*/ // TODO + } } // end of namespace Assimp @@ -194,27 +195,43 @@ void FBXExporter::ExportAscii ( ); } - // this isn't really necessary, - // but the Autodesk FBX SDK puts a similar comment at the top of the file. - // Theirs declares that the file copyright is owned by Autodesk... - std::stringstream head; - using std::endl; - head << "; FBX " << EXPORT_VERSION_STR << " project file" << endl; - head << "; Created by the Open Asset Import Library (Assimp)" << endl; - head << "; http://assimp.org" << endl; - head << "; -------------------------------------------------" << endl; - head << endl; - const std::string ascii_header = head.str(); - outfile->Write(ascii_header.c_str(), ascii_header.size(), 1); + // write the ascii header + WriteAsciiHeader(); // write all the sections WriteAllNodes(); + // make sure the file ends with a newline. + // note: if the file is opened in text mode, + // this should do the right cross-platform thing. + outfile->Write("\n", 1, 1); + // explicitly release file pointer, // so we don't have to rely on class destruction. outfile.reset(); } +void FBXExporter::WriteAsciiHeader() +{ + // basically just a comment at the top of the file + std::stringstream head; + head << "; FBX " << EXPORT_VERSION_STR << " project file\n"; + head << "; Created by the Open Asset Import Library (Assimp)\n"; + head << "; http://assimp.org\n"; + head << "; -------------------------------------------------\n"; + const std::string ascii_header = head.str(); + outfile->Write(ascii_header.c_str(), ascii_header.size(), 1); +} + +void FBXExporter::WriteAsciiSectionHeader(const std::string& title) +{ + StreamWriterLE outstream(outfile); + std::stringstream s; + s << "\n\n; " << title << '\n'; + s << FBX::COMMENT_UNDERLINE << "\n"; + outstream.PutString(s.str()); +} + void FBXExporter::WriteBinaryHeader() { // first a specific sequence of 23 bytes, always the same @@ -294,28 +311,39 @@ void FBXExporter::WriteAllNodes () //FBXHeaderExtension top-level node void FBXExporter::WriteHeaderExtension () { + if (!binary) { + // no title, follows directly from the top comment + } FBX::Node n("FBXHeaderExtension"); StreamWriterLE outstream(outfile); + int indent = 0; // begin node - n.Begin(outstream); + n.Begin(outstream, binary, indent); // write properties // (none) // finish properties - n.EndProperties(outstream, 0); + n.EndProperties(outstream, binary, indent, 0); + + // begin children + n.BeginChildren(outstream, binary, indent); + + indent = 1; // write child nodes FBX::Node::WritePropertyNode( - "FBXHeaderVersion", int32_t(1003), outstream + "FBXHeaderVersion", int32_t(1003), outstream, binary, indent ); FBX::Node::WritePropertyNode( - "FBXVersion", int32_t(EXPORT_VERSION_INT), outstream - ); - FBX::Node::WritePropertyNode( - "EncryptionType", int32_t(0), outstream + "FBXVersion", int32_t(EXPORT_VERSION_INT), outstream, binary, indent ); + if (binary) { + FBX::Node::WritePropertyNode( + "EncryptionType", int32_t(0), outstream, binary, indent + ); + } FBX::Node CreationTimeStamp("CreationTimeStamp"); time_t rawtime; @@ -329,36 +357,50 @@ void FBXExporter::WriteHeaderExtension () CreationTimeStamp.AddChild("Minute", int32_t(now->tm_min)); CreationTimeStamp.AddChild("Second", int32_t(now->tm_sec)); CreationTimeStamp.AddChild("Millisecond", int32_t(0)); - CreationTimeStamp.Dump(outstream); + CreationTimeStamp.Dump(outstream, binary, indent); std::stringstream creator; creator << "Open Asset Import Library (Assimp) " << aiGetVersionMajor() << "." << aiGetVersionMinor() << "." << aiGetVersionRevision(); - FBX::Node::WritePropertyNode("Creator", creator.str(), outstream); + FBX::Node::WritePropertyNode( + "Creator", creator.str(), outstream, binary, indent + ); - FBX::Node sceneinfo("SceneInfo"); + //FBX::Node sceneinfo("SceneInfo"); //sceneinfo.AddProperty("GlobalInfo" + FBX::SEPARATOR + "SceneInfo"); // not sure if any of this is actually needed, // so just write an empty node for now. - sceneinfo.Dump(outstream); + //sceneinfo.Dump(outstream, binary, indent); + + indent = 0; // finish node - n.End(outstream, true); + n.End(outstream, binary, indent, true); // that's it for FBXHeaderExtension... + if (!binary) { return; } // but binary files also need top-level FileID, CreationTime, Creator: std::vector raw(GENERIC_FILEID.size()); for (size_t i = 0; i < GENERIC_FILEID.size(); ++i) { raw[i] = uint8_t(GENERIC_FILEID[i]); } - FBX::Node::WritePropertyNode("FileId", raw, outstream); - FBX::Node::WritePropertyNode("CreationTime", GENERIC_CTIME, outstream); - FBX::Node::WritePropertyNode("Creator", creator.str(), outstream); + FBX::Node::WritePropertyNode( + "FileId", raw, outstream, binary, indent + ); + FBX::Node::WritePropertyNode( + "CreationTime", GENERIC_CTIME, outstream, binary, indent + ); + FBX::Node::WritePropertyNode( + "Creator", creator.str(), outstream, binary, indent + ); } void FBXExporter::WriteGlobalSettings () { + if (!binary) { + // no title, follows directly from the header extension + } FBX::Node gs("GlobalSettings"); gs.AddChild("Version", int32_t(1000)); @@ -385,11 +427,15 @@ void FBXExporter::WriteGlobalSettings () p.AddP70int("CurrentTimeMarker", -1); gs.AddChild(p); - gs.Dump(outfile); + gs.Dump(outfile, binary, 0); } void FBXExporter::WriteDocuments () { + if (!binary) { + WriteAsciiSectionHeader("Documents Description"); + } + // not sure what the use of multiple documents would be, // or whether any end-application supports it FBX::Node docs("Documents"); @@ -411,15 +457,19 @@ void FBXExporter::WriteDocuments () doc.AddChild("RootNode", int64_t(0)); docs.AddChild(doc); - docs.Dump(outfile); + docs.Dump(outfile, binary, 0); } void FBXExporter::WriteReferences () { + if (!binary) { + WriteAsciiSectionHeader("Document References"); + } // always empty for now. // not really sure what this is for. FBX::Node n("References"); - n.Dump(outfile); + n.force_has_children = true; + n.Dump(outfile, binary, 0); } @@ -468,9 +518,6 @@ size_t count_images(const aiScene* scene) { } } } - //for (auto &s : images) { - // std::cout << "found image: " << s << std::endl; - //} return images.size(); } @@ -510,6 +557,11 @@ void FBXExporter::WriteDefinitions () // determining how many of each type of object there are // and specifying the base properties to use when otherwise unspecified. + // ascii section header + if (!binary) { + WriteAsciiSectionHeader("Object definitions"); + } + // we need to count the objects int32_t count; int32_t total_count = 0; @@ -520,7 +572,7 @@ void FBXExporter::WriteDefinitions () // GlobalSettings // this seems to always be here in Maya exports - n = FBX::Node("ObjectType", Property("GlobalSettings")); + n = FBX::Node("ObjectType", "GlobalSettings"); count = 1; n.AddChild("Count", count); object_nodes.push_back(n); @@ -531,9 +583,9 @@ void FBXExporter::WriteDefinitions () // but no harm seems to come of leaving it out. count = mScene->mNumAnimations; if (count) { - n = FBX::Node("ObjectType", Property("AnimationStack")); + n = FBX::Node("ObjectType", "AnimationStack"); n.AddChild("Count", count); - pt = FBX::Node("PropertyTemplate", Property("FbxAnimStack")); + pt = FBX::Node("PropertyTemplate", "FbxAnimStack"); p = FBX::Node("Properties70"); p.AddP70string("Description", ""); p.AddP70time("LocalStart", 0); @@ -553,9 +605,9 @@ void FBXExporter::WriteDefinitions () // so there will be one per aiAnimation count = mScene->mNumAnimations; if (count) { - n = FBX::Node("ObjectType", Property("AnimationLayer")); + n = FBX::Node("ObjectType", "AnimationLayer"); n.AddChild("Count", count); - pt = FBX::Node("PropertyTemplate", Property("FBXAnimLayer")); + pt = FBX::Node("PropertyTemplate", "FBXAnimLayer"); p = FBX::Node("Properties70"); p.AddP70("Weight", "Number", "", "A", double(100)); p.AddP70bool("Mute", 0); @@ -583,9 +635,9 @@ void FBXExporter::WriteDefinitions () count = 1; // TODO: select properly if (count) { // FbxSkeleton - n = FBX::Node("ObjectType", Property("NodeAttribute")); + n = FBX::Node("ObjectType", "NodeAttribute"); n.AddChild("Count", count); - pt = FBX::Node("PropertyTemplate", Property("FbxSkeleton")); + pt = FBX::Node("PropertyTemplate", "FbxSkeleton"); p = FBX::Node("Properties70"); p.AddP70color("Color", 0.8, 0.8, 0.8); p.AddP70double("Size", 33.333333333333); @@ -601,9 +653,9 @@ void FBXExporter::WriteDefinitions () // <~~ node heirarchy count = int32_t(count_nodes(mScene->mRootNode)) - 1; // (not counting root node) if (count) { - n = FBX::Node("ObjectType", Property("Model")); + n = FBX::Node("ObjectType", "Model"); n.AddChild("Count", count); - pt = FBX::Node("PropertyTemplate", Property("FbxNode")); + pt = FBX::Node("PropertyTemplate", "FbxNode"); p = FBX::Node("Properties70"); p.AddP70enum("QuaternionInterpolate", 0); p.AddP70vector("RotationOffset", 0.0, 0.0, 0.0); @@ -698,9 +750,9 @@ void FBXExporter::WriteDefinitions () // <~~ aiMesh count = mScene->mNumMeshes; if (count) { - n = FBX::Node("ObjectType", Property("Geometry")); + n = FBX::Node("ObjectType", "Geometry"); n.AddChild("Count", count); - pt = FBX::Node("PropertyTemplate", Property("FbxMesh")); + pt = FBX::Node("PropertyTemplate", "FbxMesh"); p = FBX::Node("Properties70"); p.AddP70color("Color", 0, 0, 0); p.AddP70vector("BBoxMin", 0, 0, 0); @@ -724,7 +776,7 @@ void FBXExporter::WriteDefinitions () count = mScene->mNumMaterials; if (count) { bool has_phong = has_phong_mat(mScene); - n = FBX::Node("ObjectType", Property("Material")); + n = FBX::Node("ObjectType", "Material"); n.AddChild("Count", count); pt = FBX::Node("PropertyTemplate"); if (has_phong) { @@ -771,9 +823,9 @@ void FBXExporter::WriteDefinitions () // one for each image file. count = int32_t(count_images(mScene)); if (count) { - n = FBX::Node("ObjectType", Property("Video")); + n = FBX::Node("ObjectType", "Video"); n.AddChild("Count", count); - pt = FBX::Node("PropertyTemplate", Property("FbxVideo")); + pt = FBX::Node("PropertyTemplate", "FbxVideo"); p = FBX::Node("Properties70"); p.AddP70bool("ImageSequence", 0); p.AddP70int("ImageSequenceOffset", 0); @@ -800,9 +852,9 @@ void FBXExporter::WriteDefinitions () // <~~ aiTexture count = int32_t(count_textures(mScene)); if (count) { - n = FBX::Node("ObjectType", Property("Texture")); + n = FBX::Node("ObjectType", "Texture"); n.AddChild("Count", count); - pt = FBX::Node("PropertyTemplate", Property("FbxFileTexture")); + pt = FBX::Node("PropertyTemplate", "FbxFileTexture"); p = FBX::Node("Properties70"); p.AddP70enum("TextureTypeUse", 0); p.AddP70numberA("Texture alpha", 1.0); @@ -829,9 +881,9 @@ void FBXExporter::WriteDefinitions () // AnimationCurveNode / FbxAnimCurveNode count = mScene->mNumAnimations * 3; if (count) { - n = FBX::Node("ObjectType", Property("AnimationCurveNode")); + n = FBX::Node("ObjectType", "AnimationCurveNode"); n.AddChild("Count", count); - pt = FBX::Node("PropertyTemplate", Property("FbxAnimCurveNode")); + pt = FBX::Node("PropertyTemplate", "FbxAnimCurveNode"); p = FBX::Node("Properties70"); p.AddP70("d", "Compound", "", ""); pt.AddChild(p); @@ -843,7 +895,7 @@ void FBXExporter::WriteDefinitions () // AnimationCurve / FbxAnimCurve count = mScene->mNumAnimations * 9; if (count) { - n = FBX::Node("ObjectType", Property("AnimationCurve")); + n = FBX::Node("ObjectType", "AnimationCurve"); n.AddChild("Count", count); object_nodes.push_back(n); total_count += count; @@ -856,7 +908,7 @@ void FBXExporter::WriteDefinitions () if (mesh->HasBones()) { ++count; } } if (count) { - n = FBX::Node("ObjectType", Property("Pose")); + n = FBX::Node("ObjectType", "Pose"); n.AddChild("Count", count); object_nodes.push_back(n); total_count += count; @@ -865,7 +917,7 @@ void FBXExporter::WriteDefinitions () // Deformer count = int32_t(count_deformers(mScene)); if (count) { - n = FBX::Node("ObjectType", Property("Deformer")); + n = FBX::Node("ObjectType", "Deformer"); n.AddChild("Count", count); object_nodes.push_back(n); total_count += count; @@ -874,9 +926,9 @@ void FBXExporter::WriteDefinitions () // (template) count = 0; if (count) { - n = FBX::Node("ObjectType", Property("")); + n = FBX::Node("ObjectType", ""); n.AddChild("Count", count); - pt = FBX::Node("PropertyTemplate", Property("")); + pt = FBX::Node("PropertyTemplate", ""); p = FBX::Node("Properties70"); pt.AddChild(p); n.AddChild(pt); @@ -889,7 +941,7 @@ void FBXExporter::WriteDefinitions () defs.AddChild("Version", int32_t(100)); defs.AddChild("Count", int32_t(total_count)); for (auto &n : object_nodes) { defs.AddChild(n); } - defs.Dump(outfile); + defs.Dump(outfile, binary, 0); } @@ -935,14 +987,20 @@ int64_t to_ktime(double ticks, const aiAnimation* anim) { void FBXExporter::WriteObjects () { + if (!binary) { + WriteAsciiSectionHeader("Object properties"); + } // numbers should match those given in definitions! make sure to check StreamWriterLE outstream(outfile); FBX::Node object_node("Objects"); - object_node.Begin(outstream); - object_node.EndProperties(outstream); + int indent = 0; + object_node.Begin(outstream, binary, indent); + object_node.EndProperties(outstream, binary, indent); + object_node.BeginChildren(outstream, binary, indent); // geometry (aiMesh) mesh_uids.clear(); + indent = 1; for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) { // it's all about this mesh aiMesh* m = mScene->mMeshes[mi]; @@ -954,9 +1012,11 @@ void FBXExporter::WriteObjects () n.AddProperty(uid); n.AddProperty(FBX::SEPARATOR + "Geometry"); n.AddProperty("Mesh"); - n.Begin(outstream); - n.DumpProperties(outstream); - n.EndProperties(outstream); + n.Begin(outstream, binary, indent); + n.DumpProperties(outstream, binary, indent); + n.EndProperties(outstream, binary, indent); + n.BeginChildren(outstream, binary, indent); + indent = 2; // output vertex data - each vertex should be unique (probably) std::vector flattened_vertices; @@ -980,7 +1040,7 @@ void FBXExporter::WriteObjects () } } FBX::Node::WritePropertyNode( - "Vertices", flattened_vertices, outstream + "Vertices", flattened_vertices, outstream, binary, indent ); // output polygon data as a flattened array of vertex indices. @@ -996,30 +1056,38 @@ void FBXExporter::WriteObjects () ); } FBX::Node::WritePropertyNode( - "PolygonVertexIndex", polygon_data, outstream + "PolygonVertexIndex", polygon_data, outstream, binary, indent ); // here could be edges but they're insane. // it's optional anyway, so let's ignore it. FBX::Node::WritePropertyNode( - "GeometryVersion", int32_t(124), outstream + "GeometryVersion", int32_t(124), outstream, binary, indent ); // normals, if any if (m->HasNormals()) { - FBX::Node normals("LayerElementNormal", Property(int32_t(0))); - normals.Begin(outstream); - normals.DumpProperties(outstream); - normals.EndProperties(outstream); - FBX::Node::WritePropertyNode("Version", int32_t(101), outstream); - FBX::Node::WritePropertyNode("Name", "", outstream); + FBX::Node normals("LayerElementNormal", int32_t(0)); + normals.Begin(outstream, binary, indent); + normals.DumpProperties(outstream, binary, indent); + normals.EndProperties(outstream, binary, indent); + normals.BeginChildren(outstream, binary, indent); + indent = 3; FBX::Node::WritePropertyNode( - "MappingInformationType", "ByPolygonVertex", outstream + "Version", int32_t(101), outstream, binary, indent + ); + FBX::Node::WritePropertyNode( + "Name", "", outstream, binary, indent + ); + FBX::Node::WritePropertyNode( + "MappingInformationType", "ByPolygonVertex", + outstream, binary, indent ); // TODO: vertex-normals or indexed normals when appropriate FBX::Node::WritePropertyNode( - "ReferenceInformationType", "Direct", outstream + "ReferenceInformationType", "Direct", + outstream, binary, indent ); std::vector normal_data; normal_data.reserve(3 * polygon_data.size()); @@ -1032,10 +1100,13 @@ void FBXExporter::WriteObjects () normal_data.push_back(n.z); } } - FBX::Node::WritePropertyNode("Normals", normal_data, outstream); + FBX::Node::WritePropertyNode( + "Normals", normal_data, outstream, binary, indent + ); // note: version 102 has a NormalsW also... not sure what it is, // so we can stick with version 101 for now. - normals.End(outstream, true); + indent = 2; + normals.End(outstream, binary, indent, true); } // uvs, if any @@ -1055,19 +1126,27 @@ void FBXExporter::WriteObjects () err << " but may be incorrectly interpreted on load."; DefaultLogger::get()->warn(err.str()); } - FBX::Node uv("LayerElementUV", Property(int32_t(uvi))); - uv.Begin(outstream); - uv.DumpProperties(outstream); - uv.EndProperties(outstream); - FBX::Node::WritePropertyNode("Version", int32_t(101), outstream); + FBX::Node uv("LayerElementUV", int32_t(uvi)); + uv.Begin(outstream, binary, indent); + uv.DumpProperties(outstream, binary, indent); + uv.EndProperties(outstream, binary, indent); + uv.BeginChildren(outstream, binary, indent); + indent = 3; + FBX::Node::WritePropertyNode( + "Version", int32_t(101), outstream, binary, indent + ); // it doesn't seem like assimp keeps the uv map name, // so just leave it blank. - FBX::Node::WritePropertyNode("Name", "", outstream); FBX::Node::WritePropertyNode( - "MappingInformationType", "ByPolygonVertex", outstream + "Name", "", outstream, binary, indent ); FBX::Node::WritePropertyNode( - "ReferenceInformationType", "IndexToDirect", outstream + "MappingInformationType", "ByPolygonVertex", + outstream, binary, indent + ); + FBX::Node::WritePropertyNode( + "ReferenceInformationType", "IndexToDirect", + outstream, binary, indent ); std::vector uv_data; @@ -1092,27 +1171,32 @@ void FBXExporter::WriteObjects () } } } - FBX::Node::WritePropertyNode("UV", uv_data, outstream); - FBX::Node::WritePropertyNode("UVIndex", uv_indices, outstream); - uv.End(outstream, true); + FBX::Node::WritePropertyNode( + "UV", uv_data, outstream, binary, indent + ); + FBX::Node::WritePropertyNode( + "UVIndex", uv_indices, outstream, binary, indent + ); + indent = 2; + uv.End(outstream, binary, indent, true); } // i'm not really sure why this material section exists, // as the material is linked via "Connections". // it seems to always have the same "0" value. - FBX::Node mat("LayerElementMaterial", Property(int32_t(0))); + FBX::Node mat("LayerElementMaterial", int32_t(0)); mat.AddChild("Version", int32_t(101)); mat.AddChild("Name", ""); mat.AddChild("MappingInformationType", "AllSame"); mat.AddChild("ReferenceInformationType", "IndexToDirect"); std::vector mat_indices = {0}; mat.AddChild("Materials", mat_indices); - mat.Dump(outstream); + mat.Dump(outstream, binary, indent); // finally we have the layer specifications, // which select the normals / UV set / etc to use. // TODO: handle multiple uv sets correctly? - FBX::Node layer("Layer", Property(int32_t(0))); + FBX::Node layer("Layer", int32_t(0)); layer.AddChild("Version", int32_t(100)); FBX::Node le("LayerElement"); le.AddChild("Type", "LayerElementNormal"); @@ -1126,10 +1210,11 @@ void FBXExporter::WriteObjects () le.AddChild("Type", "LayerElementUV"); le.AddChild("TypedIndex", int32_t(0)); layer.AddChild(le); - layer.Dump(outstream); + layer.Dump(outstream, binary, indent); // finish the node record - n.End(outstream, true); + indent = 1; + n.End(outstream, binary, indent, true); } // aiMaterial @@ -1273,7 +1358,7 @@ void FBXExporter::WriteObjects () n.AddChild(p); - n.Dump(outstream); + n.Dump(outstream, binary, indent); } // we need to look up all the images we're using, @@ -1321,7 +1406,7 @@ void FBXExporter::WriteObjects () n.AddChild("UseMipMap", int32_t(0)); n.AddChild("Filename", path); n.AddChild("RelativeFilename", path); - n.Dump(outstream); + n.Dump(outstream, binary, indent); } // Textures @@ -1408,14 +1493,12 @@ void FBXExporter::WriteObjects () const int64_t texture_uid = generate_uid(); // link the texture to the material - FBX::Node c("C"); - c.AddProperties("OP", texture_uid, material_uid, prop_name); - connections.push_back(c); + connections.emplace_back( + "C", "OP", texture_uid, material_uid, prop_name + ); // link the image data to the texture - c = FBX::Node("C"); - c.AddProperties("OO", image_uid, texture_uid); - connections.push_back(c); + connections.emplace_back("C", "OO", image_uid, texture_uid); // now write the actual texture node FBX::Node tnode("Texture"); @@ -1438,11 +1521,11 @@ void FBXExporter::WriteObjects () tnode.AddChild("RelativeFilename", texture_path); tnode.AddChild("ModelUVTranslation", double(0.0), double(0.0)); tnode.AddChild("ModelUVScaling", double(1.0), double(1.0)); - tnode.AddChild("Texture_Alpha_Soutce", "None"); + tnode.AddChild("Texture_Alpha_Source", "None"); tnode.AddChild( "Cropping", int32_t(0), int32_t(0), int32_t(0), int32_t(0) ); - tnode.Dump(outstream); + tnode.Dump(outstream, binary, indent); } } @@ -1595,12 +1678,10 @@ void FBXExporter::WriteObjects () // "acuracy"... this is not a typo.... dnode.AddChild("Link_DeformAcuracy", double(50)); dnode.AddChild("SkinningType", "Linear"); // TODO: other modes? - dnode.Dump(outstream); + dnode.Dump(outstream, binary, indent); // connect it - FBX::Node c("C"); - c.AddProperties("OO", deformer_uid, mesh_uids[mi]); - connections.push_back(c); // TODO: emplace_back + connections.emplace_back("C", "OO", deformer_uid, mesh_uids[mi]); // we will be indexing by vertex... // but there might be a different number of "vertices" @@ -1703,7 +1784,7 @@ void FBXExporter::WriteObjects () // this should be the same as the bone's mOffsetMatrix. // if it's not the same, the skeleton isn't in the bind pose. - const float epsilon = 1e-5f; // some error is to be expected + const float epsilon = 1e-4f; // some error is to be expected bool bone_xform_okay = true; if (b && ! tr.Equal(b->mOffsetMatrix, epsilon)) { not_in_bind_pose.insert(b); @@ -1741,17 +1822,17 @@ void FBXExporter::WriteObjects () // there's not really any way around this at the moment. // done - sdnode.Dump(outstream); + sdnode.Dump(outstream, binary, indent); // lastly, connect to the parent deformer - c = FBX::Node("C"); - c.AddProperties("OO", subdeformer_uid, deformer_uid); - connections.push_back(c); // TODO: emplace_back + connections.emplace_back( + "C", "OO", subdeformer_uid, deformer_uid + ); // we also need to connect the limb node to the subdeformer. - c = FBX::Node("C"); - c.AddProperties("OO", node_uids[bone_node], subdeformer_uid); - connections.push_back(c); // TODO: emplace_back + connections.emplace_back( + "C", "OO", node_uids[bone_node], subdeformer_uid + ); } // if we cannot create a valid FBX file, simply die. @@ -1859,7 +1940,7 @@ void FBXExporter::WriteObjects () } // now write it - bpnode.Dump(outstream); + bpnode.Dump(outstream, binary, indent); }*/ // TODO: cameras, lights @@ -1918,11 +1999,8 @@ void FBXExporter::WriteObjects () // this node absurdly always pretends it has children // (in this case it does, but just in case...) - asnode.Begin(outstream); - asnode.DumpProperties(outstream); - asnode.EndProperties(outstream); - asnode.DumpChildren(outstream); - asnode.End(outstream, true); + asnode.force_has_children = true; + asnode.Dump(outstream, binary, indent); // note: animation stacks are not connected to anything } @@ -1936,16 +2014,13 @@ void FBXExporter::WriteObjects () alnode.AddProperties(animlayer_uid, FBX::SEPARATOR + "AnimLayer", ""); // this node absurdly always pretends it has children - alnode.Begin(outstream); - alnode.DumpProperties(outstream); - alnode.EndProperties(outstream); - alnode.DumpChildren(outstream); - alnode.End(outstream, true); + alnode.force_has_children = true; + alnode.Dump(outstream, binary, indent); // connect to the relevant animstack - FBX::Node c("C"); - c.AddProperties("OO", animlayer_uid, animation_stack_uids[ai]); - connections.push_back(c); // TODO: emplace_back + connections.emplace_back( + "C", "OO", animlayer_uid, animation_stack_uids[ai] + ); } // AnimCurveNode - three per aiNodeAnim @@ -2057,7 +2132,8 @@ void FBXExporter::WriteObjects () } } - object_node.End(outstream, true); + indent = 0; + object_node.End(outstream, binary, indent, true); } // convenience map of magic node name strings to FBX properties, @@ -2083,13 +2159,14 @@ const std::map> transform_types = { }; // write a single model node to the stream -void WriteModelNode( +void FBXExporter::WriteModelNode( StreamWriterLE& outstream, + bool binary, const aiNode* node, int64_t node_uid, const std::string& type, const std::vector>& transform_chain, - TransformInheritance inherit_type=TransformInheritance_RSrs + TransformInheritance inherit_type ){ const aiVector3D zero = {0, 0, 0}; const aiVector3D one = {1, 1, 1}; @@ -2157,7 +2234,7 @@ void WriteModelNode( m.AddChild("Shading", Property(true)); m.AddChild("Culling", Property("CullingOff")); - m.Dump(outstream); + m.Dump(outstream, binary, 1); } // wrapper for WriteModelNodes to create and pass a blank transform chain @@ -2181,15 +2258,6 @@ void FBXExporter::WriteModelNodes( // first collapse any expanded transformation chains created by FBX import. std::string node_name(node->mName.C_Str()); if (node_name.find(MAGIC_NODE_TAG) != std::string::npos) { - if (node->mNumChildren != 1) { - // this should never happen - std::stringstream err; - err << "FBX transformation node should have exactly 1 child,"; - err << " but " << node->mNumChildren << " found"; - err << " on node \"" << node_name << "\"!"; - throw DeadlyExportError(err.str()); - } - aiNode* next_node = node->mChildren[0]; auto pos = node_name.find(MAGIC_NODE_TAG) + MAGIC_NODE_TAG.size() + 1; std::string type_name = node_name.substr(pos); auto elem = transform_types.find(type_name); @@ -2223,10 +2291,16 @@ void FBXExporter::WriteModelNodes( err << elem->second.second; throw DeadlyExportError(err.str()); } - // now just continue to the next node - WriteModelNodes( - outstream, next_node, parent_uid, limbnodes, transform_chain - ); + // now continue on to any child nodes + for (unsigned i = 0; i < node->mNumChildren; ++i) { + WriteModelNodes( + outstream, + node->mChildren[i], + parent_uid, + limbnodes, + transform_chain + ); + } return; } @@ -2240,9 +2314,7 @@ void FBXExporter::WriteModelNodes( node_uid = generate_uid(); node_uids[node] = node_uid; } - FBX::Node c("C"); - c.AddProperties("OO", node_uid, parent_uid); - connections.push_back(c); + connections.emplace_back("C", "OO", node_uid, parent_uid); } // what type of node is this? @@ -2250,21 +2322,23 @@ void FBXExporter::WriteModelNodes( // handled later } else if (node->mNumMeshes == 1) { // connect to child mesh, which should have been written previously - FBX::Node c("C"); - c.AddProperties("OO", mesh_uids[node->mMeshes[0]], node_uid); - connections.push_back(c); + connections.emplace_back( + "C", "OO", mesh_uids[node->mMeshes[0]], node_uid + ); // also connect to the material for the child mesh - c = FBX::Node("C"); - c.AddProperties( - "OO", + connections.emplace_back( + "C", "OO", material_uids[mScene->mMeshes[node->mMeshes[0]]->mMaterialIndex], node_uid ); - connections.push_back(c); // write model node - WriteModelNode(outstream, node, node_uid, "Mesh", transform_chain); + WriteModelNode( + outstream, binary, node, node_uid, "Mesh", transform_chain + ); } else if (limbnodes.count(node)) { - WriteModelNode(outstream, node, node_uid, "LimbNode", transform_chain); + WriteModelNode( + outstream, binary, node, node_uid, "LimbNode", transform_chain + ); // we also need to write a nodeattribute to mark it as a skeleton int64_t node_attribute_uid = generate_uid(); FBX::Node na("NodeAttribute"); @@ -2272,14 +2346,14 @@ void FBXExporter::WriteModelNodes( node_attribute_uid, FBX::SEPARATOR + "NodeAttribute", "LimbNode" ); na.AddChild("TypeFlags", Property("Skeleton")); - na.Dump(outstream); + na.Dump(outstream, binary, 1); // and connect them - FBX::Node c("C"); - c.AddProperties("OO", node_attribute_uid, node_uid); - connections.push_back(c); + connections.emplace_back("C", "OO", node_attribute_uid, node_uid); } else { // generate a null node so we can add children to it - WriteModelNode(outstream, node, node_uid, "Null", transform_chain); + WriteModelNode( + outstream, binary, node, node_uid, "Null", transform_chain + ); } // if more than one child mesh, make nodes for each mesh @@ -2288,23 +2362,19 @@ void FBXExporter::WriteModelNodes( // make a new model node int64_t new_node_uid = generate_uid(); // connect to parent node - FBX::Node c("C"); - c.AddProperties("OO", new_node_uid, node_uid); - connections.push_back(c); + connections.emplace_back("C", "OO", new_node_uid, node_uid); // connect to child mesh, which should have been written previously - c = FBX::Node("C"); - c.AddProperties("OO", mesh_uids[node->mMeshes[i]], new_node_uid); - connections.push_back(c); + connections.emplace_back( + "C", "OO", mesh_uids[node->mMeshes[i]], new_node_uid + ); // also connect to the material for the child mesh - c = FBX::Node("C"); - c.AddProperties( - "OO", + connections.emplace_back( + "C", "OO", material_uids[ mScene->mMeshes[node->mMeshes[i]]->mMaterialIndex ], new_node_uid ); - connections.push_back(c); // write model node FBX::Node m("Model"); // take name from mesh name, if it exists @@ -2315,7 +2385,7 @@ void FBXExporter::WriteModelNodes( FBX::Node p("Properties70"); p.AddP70enum("InheritType", 1); m.AddChild(p); - m.Dump(outstream); + m.Dump(outstream, binary, 1); } } @@ -2344,15 +2414,11 @@ void FBXExporter::WriteAnimationCurveNode( p.AddP70numberA("d|Y", default_value.y); p.AddP70numberA("d|Z", default_value.z); n.AddChild(p); - n.Dump(outstream); + n.Dump(outstream, binary, 1); // connect to layer - FBX::Node cl("C"); - cl.AddProperties("OO", uid, layer_uid); - this->connections.push_back(cl); // TODO: emplace_back + this->connections.emplace_back("C", "OO", uid, layer_uid); // connect to bone - FBX::Node cb("C"); - cb.AddProperties("OP", uid, node_uid, property_name); - this->connections.push_back(cb); // TODO: emplace_back + this->connections.emplace_back("C", "OP", uid, node_uid, property_name); } @@ -2379,10 +2445,10 @@ void FBXExporter::WriteAnimationCurve( "KeyAttrRefCount", std::vector{static_cast(times.size())} ); - n.Dump(outstream); - FBX::Node c("C"); - c.AddProperties("OP", curve_uid, curvenode_uid, property_link); - this->connections.push_back(c); // TODO: emplace_back + n.Dump(outstream, binary, 1); + this->connections.emplace_back( + "C", "OP", curve_uid, curvenode_uid, property_link + ); } @@ -2390,13 +2456,18 @@ void FBXExporter::WriteConnections () { // we should have completed the connection graph already, // so basically just dump it here + if (!binary) { + WriteAsciiSectionHeader("Object connections"); + } + // TODO: comments with names in the ascii version FBX::Node conn("Connections"); StreamWriterLE outstream(outfile); - conn.Begin(outstream); + conn.Begin(outstream, binary, 0); + conn.BeginChildren(outstream, binary, 0); for (auto &n : connections) { - n.Dump(outstream); + n.Dump(outstream, binary, 1); } - conn.End(outstream, !connections.empty()); + conn.End(outstream, binary, 0, !connections.empty()); connections.clear(); } diff --git a/code/FBXExporter.h b/code/FBXExporter.h index 553cf60fe..3b9de8acb 100644 --- a/code/FBXExporter.h +++ b/code/FBXExporter.h @@ -48,6 +48,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_FBX_EXPORTER #include "FBXExportNode.h" // FBX::Node +#include "FBXCommon.h" // FBX::TransformInheritance #include //#include @@ -104,6 +105,9 @@ namespace Assimp void WriteBinaryHeader(); void WriteBinaryFooter(); + // ascii files have a comment at the top + void WriteAsciiHeader(); + // WriteAllNodes does the actual export. // It just calls all the Write
methods below in order. void WriteAllNodes(); @@ -126,6 +130,7 @@ namespace Assimp // WriteTakes(); // deprecated since at least 2015 (fbx 7.4) // helpers + void WriteAsciiSectionHeader(const std::string& title); void WriteModelNodes( Assimp::StreamWriterLE& s, const aiNode* node, @@ -139,6 +144,15 @@ namespace Assimp const std::unordered_set& limbnodes, std::vector>& transform_chain ); + void WriteModelNode( // nor this + StreamWriterLE& s, + bool binary, + const aiNode* node, + int64_t node_uid, + const std::string& type, + const std::vector>& xfm_chain, + FBX::TransformInheritance ti_type=FBX::TransformInheritance_RSrs + ); void WriteAnimationCurveNode( StreamWriterLE& outstream, int64_t uid, diff --git a/include/assimp/StreamWriter.h b/include/assimp/StreamWriter.h index 6f492df90..5ce3b172b 100644 --- a/include/assimp/StreamWriter.h +++ b/include/assimp/StreamWriter.h @@ -203,6 +203,12 @@ public: Put(n); } + // --------------------------------------------------------------------- + /** Write a single character to the stream */ + void PutChar(char c) { + Put(c); + } + // --------------------------------------------------------------------- /** Write an aiString to the stream */ void PutString(const aiString& s)