Implement ascii FBX export.

It's available under the 'fbxa' format id.
This commit is contained in:
Tommy
2018-03-28 21:40:26 +02:00
parent e549705af0
commit 826243f289
7 changed files with 747 additions and 164 deletions

View File

@@ -66,7 +66,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <vector>
#include <array>
#include <unordered_set>
#include <iostream> // 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<uint8_t> 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,16 +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.force_has_children = true;
n.Dump(outfile);
n.Dump(outfile, binary, 0);
}
@@ -469,9 +518,6 @@ size_t count_images(const aiScene* scene) {
}
}
}
//for (auto &s : images) {
// std::cout << "found image: " << s << std::endl;
//}
return images.size();
}
@@ -511,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;
@@ -890,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);
}
@@ -936,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];
@@ -955,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<double> flattened_vertices;
@@ -981,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.
@@ -997,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", 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);
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<double> normal_data;
normal_data.reserve(3 * polygon_data.size());
@@ -1033,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
@@ -1057,18 +1127,26 @@ void FBXExporter::WriteObjects ()
DefaultLogger::get()->warn(err.str());
}
FBX::Node uv("LayerElementUV", int32_t(uvi));
uv.Begin(outstream);
uv.DumpProperties(outstream);
uv.EndProperties(outstream);
FBX::Node::WritePropertyNode("Version", int32_t(101), outstream);
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<double> uv_data;
@@ -1093,9 +1171,14 @@ 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,
@@ -1108,7 +1191,7 @@ void FBXExporter::WriteObjects ()
mat.AddChild("ReferenceInformationType", "IndexToDirect");
std::vector<int32_t> 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.
@@ -1127,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
@@ -1274,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,
@@ -1322,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
@@ -1441,7 +1525,7 @@ void FBXExporter::WriteObjects ()
tnode.AddChild(
"Cropping", int32_t(0), int32_t(0), int32_t(0), int32_t(0)
);
tnode.Dump(outstream);
tnode.Dump(outstream, binary, indent);
}
}
@@ -1594,7 +1678,7 @@ 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
connections.emplace_back("C", "OO", deformer_uid, mesh_uids[mi]);
@@ -1738,7 +1822,7 @@ 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
connections.emplace_back(
@@ -1856,7 +1940,7 @@ void FBXExporter::WriteObjects ()
}
// now write it
bpnode.Dump(outstream);
bpnode.Dump(outstream, binary, indent);
}*/
// TODO: cameras, lights
@@ -1916,7 +2000,7 @@ void FBXExporter::WriteObjects ()
// this node absurdly always pretends it has children
// (in this case it does, but just in case...)
asnode.force_has_children = true;
asnode.Dump(outstream);
asnode.Dump(outstream, binary, indent);
// note: animation stacks are not connected to anything
}
@@ -1931,7 +2015,7 @@ void FBXExporter::WriteObjects ()
// this node absurdly always pretends it has children
alnode.force_has_children = true;
alnode.Dump(outstream);
alnode.Dump(outstream, binary, indent);
// connect to the relevant animstack
connections.emplace_back(
@@ -2048,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,
@@ -2074,13 +2159,14 @@ const std::map<std::string,std::pair<std::string,char>> 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<std::pair<std::string,aiVector3D>>& transform_chain,
TransformInheritance inherit_type=TransformInheritance_RSrs
TransformInheritance inherit_type
){
const aiVector3D zero = {0, 0, 0};
const aiVector3D one = {1, 1, 1};
@@ -2148,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
@@ -2249,9 +2335,13 @@ void FBXExporter::WriteModelNodes(
node_uid
);
// 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");
@@ -2259,12 +2349,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
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
@@ -2296,7 +2388,7 @@ void FBXExporter::WriteModelNodes(
FBX::Node p("Properties70");
p.AddP70enum("InheritType", 1);
m.AddChild(p);
m.Dump(outstream);
m.Dump(outstream, binary, 1);
}
}
@@ -2325,7 +2417,7 @@ 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
this->connections.emplace_back("C", "OO", uid, layer_uid);
// connect to bone
@@ -2356,7 +2448,7 @@ void FBXExporter::WriteAnimationCurve(
"KeyAttrRefCount",
std::vector<int32_t>{static_cast<int32_t>(times.size())}
);
n.Dump(outstream);
n.Dump(outstream, binary, 1);
this->connections.emplace_back(
"C", "OP", curve_uid, curvenode_uid, property_link
);
@@ -2367,13 +2459,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();
}