2018-10-17 03:06:45 +00:00
|
|
|
#include "fbxdocument.h"
|
|
|
|
#include "fbxutil.h"
|
|
|
|
|
|
|
|
using std::string;
|
|
|
|
using std::cout;
|
|
|
|
using std::endl;
|
|
|
|
using std::ifstream;
|
|
|
|
using std::ofstream;
|
|
|
|
using std::uint32_t;
|
|
|
|
using std::uint8_t;
|
|
|
|
|
|
|
|
namespace fbx {
|
|
|
|
FBXDocument::FBXDocument()
|
|
|
|
{
|
|
|
|
version = 7400;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FBXDocument::read(string fname)
|
|
|
|
{
|
|
|
|
ifstream file;
|
|
|
|
|
|
|
|
// buffer
|
2018-10-17 03:59:21 +00:00
|
|
|
constexpr int bufferSize = 1 << 16;
|
2018-10-17 03:06:45 +00:00
|
|
|
char buffer[bufferSize];
|
|
|
|
file.rdbuf()->pubsetbuf(buffer, bufferSize);
|
|
|
|
|
|
|
|
file.open(fname, std::ios::in | std::ios::binary);
|
|
|
|
if (file.is_open()) {
|
|
|
|
read(file);
|
|
|
|
} else {
|
|
|
|
throw std::string("Cannot read from file: \"" + fname + "\"");
|
|
|
|
}
|
|
|
|
file.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FBXDocument::write(string fname)
|
|
|
|
{
|
|
|
|
ofstream file;
|
|
|
|
|
|
|
|
// buffer
|
2018-10-17 03:59:21 +00:00
|
|
|
constexpr int bufferSize = 1 << 16;
|
2018-10-17 03:06:45 +00:00
|
|
|
char buffer[bufferSize];
|
|
|
|
file.rdbuf()->pubsetbuf(buffer, bufferSize);
|
|
|
|
|
|
|
|
file.open(fname, std::ios::out | std::ios::binary);
|
|
|
|
if (file.is_open()) {
|
|
|
|
write(file);
|
|
|
|
} else {
|
|
|
|
throw std::string("Cannot write to file: \"" + fname + "\"");
|
|
|
|
}
|
|
|
|
file.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool checkMagic(Reader &reader)
|
|
|
|
{
|
|
|
|
string magic("Kaydara FBX Binary ");
|
|
|
|
for(char c : magic) {
|
|
|
|
if(reader.readUint8() != c) return false;
|
|
|
|
}
|
|
|
|
if(reader.readUint8() != 0x00) return false;
|
|
|
|
if(reader.readUint8() != 0x1A) return false;
|
|
|
|
if(reader.readUint8() != 0x00) return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FBXDocument::read(std::ifstream &input)
|
|
|
|
{
|
|
|
|
Reader reader(&input);
|
|
|
|
input >> std::noskipws;
|
|
|
|
if(!checkMagic(reader)) throw std::string("Not a FBX file");
|
|
|
|
|
|
|
|
uint32_t version = reader.readUint32();
|
|
|
|
|
|
|
|
uint32_t maxVersion = 7400;
|
|
|
|
if(version > maxVersion) throw "Unsupported FBX version "+std::to_string(version)
|
|
|
|
+ " latest supported version is "+std::to_string(maxVersion);
|
|
|
|
|
|
|
|
uint32_t start_offset = 27; // magic: 21+2, version: 4
|
|
|
|
do{
|
|
|
|
FBXNode node;
|
|
|
|
start_offset += node.read(input, start_offset);
|
|
|
|
if(node.isNull()) break;
|
|
|
|
nodes.push_back(node);
|
|
|
|
} while(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
void writerFooter(Writer &writer) {
|
|
|
|
uint8_t footer[] = {
|
|
|
|
0xfa, 0xbc, 0xab, 0x09,
|
|
|
|
0xd0, 0xc8, 0xd4, 0x66, 0xb1, 0x76, 0xfb, 0x83, 0x1c, 0xf7, 0x26, 0x7e, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0xe8, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x5a, 0x8c, 0x6a,
|
|
|
|
0xde, 0xf5, 0xd9, 0x7e, 0xec, 0xe9, 0x0c, 0xe3, 0x75, 0x8f, 0x29, 0x0b
|
|
|
|
};
|
|
|
|
for(unsigned int i = 0; i < sizeof(footer); i++) {
|
|
|
|
writer.write(footer[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void FBXDocument::write(std::ofstream &output)
|
|
|
|
{
|
|
|
|
Writer writer(&output);
|
|
|
|
writer.write("Kaydara FBX Binary ");
|
|
|
|
writer.write((uint8_t) 0);
|
|
|
|
writer.write((uint8_t) 0x1A);
|
|
|
|
writer.write((uint8_t) 0);
|
|
|
|
writer.write(version);
|
|
|
|
|
|
|
|
uint32_t offset = 27; // magic: 21+2, version: 4
|
|
|
|
for(FBXNode node : nodes) {
|
|
|
|
offset += node.write(output, offset);
|
|
|
|
}
|
|
|
|
FBXNode nullNode;
|
|
|
|
offset += nullNode.write(output, offset);
|
|
|
|
writerFooter(writer);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FBXDocument::createBasicStructure()
|
|
|
|
{
|
|
|
|
FBXNode headerExtension("FBXHeaderExtension");
|
|
|
|
headerExtension.addPropertyNode("FBXHeaderVersion", (int32_t) 1003);
|
|
|
|
headerExtension.addPropertyNode("FBXVersion", (int32_t) getVersion());
|
|
|
|
headerExtension.addPropertyNode("EncryptionType", (int32_t) 0);
|
|
|
|
{
|
|
|
|
FBXNode creationTimeStamp("CreationTimeStamp");
|
|
|
|
creationTimeStamp.addPropertyNode("Version", (int32_t) 1000);
|
|
|
|
creationTimeStamp.addPropertyNode("Year", (int32_t) 2017);
|
|
|
|
creationTimeStamp.addPropertyNode("Month", (int32_t) 5);
|
|
|
|
creationTimeStamp.addPropertyNode("Day", (int32_t) 2);
|
|
|
|
creationTimeStamp.addPropertyNode("Hour", (int32_t) 14);
|
|
|
|
creationTimeStamp.addPropertyNode("Minute", (int32_t) 11);
|
|
|
|
creationTimeStamp.addPropertyNode("Second", (int32_t) 46);
|
|
|
|
creationTimeStamp.addPropertyNode("Millisecond", (int32_t) 917);
|
|
|
|
headerExtension.addChild(creationTimeStamp);
|
|
|
|
}
|
|
|
|
headerExtension.addPropertyNode("Creator", "Blender (stable FBX IO) - 2.78 (sub 0) - 3.7.7");
|
|
|
|
{
|
|
|
|
FBXNode sceneInfo("SceneInfo");
|
|
|
|
sceneInfo.addProperty(std::vector<uint8_t>({'G','l','o','b','a','l','I','n','f','o',0,1,'S','c','e','n','e','I','n','f','o'}), 'S');
|
|
|
|
sceneInfo.addProperty("UserData");
|
|
|
|
sceneInfo.addPropertyNode("Type", "UserData");
|
|
|
|
sceneInfo.addPropertyNode("Version", 100);
|
|
|
|
{
|
|
|
|
FBXNode metadata("MetaData");
|
|
|
|
metadata.addPropertyNode("Version", 100);
|
|
|
|
metadata.addPropertyNode("Title", "");
|
|
|
|
metadata.addPropertyNode("Subject", "");
|
|
|
|
metadata.addPropertyNode("Author", "");
|
|
|
|
metadata.addPropertyNode("Keywords", "");
|
|
|
|
metadata.addPropertyNode("Revision", "");
|
|
|
|
metadata.addPropertyNode("Comment", "");
|
|
|
|
sceneInfo.addChild(metadata);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
FBXNode properties("Properties70");
|
|
|
|
{
|
|
|
|
FBXNode p("P");
|
|
|
|
p.addProperty("DocumentUrl");
|
|
|
|
p.addProperty("KString");
|
|
|
|
p.addProperty("Url");
|
|
|
|
p.addProperty("");
|
|
|
|
p.addProperty("/foobar.fbx");
|
|
|
|
properties.addChild(p);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
FBXNode p("P");
|
|
|
|
p.addProperty("SrcDocumentUrl");
|
|
|
|
p.addProperty("KString");
|
|
|
|
p.addProperty("Url");
|
|
|
|
p.addProperty("");
|
|
|
|
p.addProperty("/foobar.fbx");
|
|
|
|
properties.addChild(p);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
FBXNode p("P");
|
|
|
|
p.addProperty("Original");
|
|
|
|
p.addProperty("Compound");
|
|
|
|
p.addProperty("");
|
|
|
|
p.addProperty("");
|
|
|
|
properties.addChild(p);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
FBXNode p("P");
|
|
|
|
p.addProperty("Original|ApplicationVendor");
|
|
|
|
p.addProperty("KString");
|
|
|
|
p.addProperty("");
|
|
|
|
p.addProperty("");
|
|
|
|
p.addProperty("Blender Foundation");
|
|
|
|
properties.addChild(p);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
FBXNode p("P");
|
|
|
|
p.addProperty("Original|ApplicationName");
|
|
|
|
p.addProperty("KString");
|
|
|
|
p.addProperty("");
|
|
|
|
p.addProperty("");
|
|
|
|
p.addProperty("Blender (stable FBX IO)");
|
|
|
|
properties.addChild(p);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
FBXNode p("P");
|
|
|
|
p.addProperty("Original|ApplicationVersion");
|
|
|
|
p.addProperty("KString");
|
|
|
|
p.addProperty("");
|
|
|
|
p.addProperty("");
|
|
|
|
p.addProperty("2.78 (sub 0)");
|
|
|
|
properties.addChild(p);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
FBXNode p("P");
|
|
|
|
p.addProperty("Original|DateTime_GMT");
|
|
|
|
p.addProperty("DateTime");
|
|
|
|
p.addProperty("");
|
|
|
|
p.addProperty("");
|
|
|
|
p.addProperty("01/01/1970 00:00:00.000");
|
|
|
|
properties.addChild(p);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
FBXNode p("P");
|
|
|
|
p.addProperty("Original|FileName");
|
|
|
|
p.addProperty("KString");
|
|
|
|
p.addProperty("");
|
|
|
|
p.addProperty("");
|
|
|
|
p.addProperty("/foobar.fbx");
|
|
|
|
properties.addChild(p);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
FBXNode p("P");
|
|
|
|
p.addProperty("LastSaved");
|
|
|
|
p.addProperty("Compound");
|
|
|
|
p.addProperty("");
|
|
|
|
p.addProperty("");
|
|
|
|
properties.addChild(p);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
FBXNode p("P");
|
|
|
|
p.addProperty("LastSaved|ApplicationVendor");
|
|
|
|
p.addProperty("KString");
|
|
|
|
p.addProperty("");
|
|
|
|
p.addProperty("");
|
|
|
|
p.addProperty("Blender Foundation");
|
|
|
|
properties.addChild(p);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
FBXNode p("P");
|
|
|
|
p.addProperty("LastSaved|ApplicationName");
|
|
|
|
p.addProperty("KString");
|
|
|
|
p.addProperty("");
|
|
|
|
p.addProperty("");
|
|
|
|
p.addProperty("Blender (stable FBX IO)");
|
|
|
|
properties.addChild(p);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
FBXNode p("P");
|
|
|
|
p.addProperty("LastSaved|DateTime_GMT");
|
|
|
|
p.addProperty("DateTime");
|
|
|
|
p.addProperty("");
|
|
|
|
p.addProperty("");
|
|
|
|
p.addProperty("01/01/1970 00:00:00.000");
|
|
|
|
properties.addChild(p);
|
|
|
|
}
|
|
|
|
sceneInfo.addChild(properties);
|
|
|
|
}
|
|
|
|
headerExtension.addChild(sceneInfo);
|
|
|
|
}
|
|
|
|
nodes.push_back(headerExtension);
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
std::uint32_t FBXDocument::getVersion()
|
|
|
|
{
|
|
|
|
return version;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FBXDocument::print()
|
|
|
|
{
|
|
|
|
cout << "{\n";
|
|
|
|
cout << " \"version\": " << getVersion() << ",\n";
|
|
|
|
cout << " \"children\": [\n";
|
|
|
|
bool hasPrev = false;
|
|
|
|
for(auto node : nodes) {
|
|
|
|
if(hasPrev) cout << ",\n";
|
|
|
|
node.print(" ");
|
|
|
|
hasPrev = true;
|
|
|
|
}
|
|
|
|
cout << "\n ]\n}" << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace fbx
|