|
@@ -1,4 +1,5 @@
|
|
|
package hxd.fmt.fbx;
|
|
|
+import haxe.io.Bytes;
|
|
|
using hxd.fmt.fbx.Data;
|
|
|
|
|
|
private enum Token {
|
|
@@ -18,8 +19,10 @@ class Parser {
|
|
|
|
|
|
var line : Int;
|
|
|
var buf : String;
|
|
|
+ var bytes : Bytes;
|
|
|
var pos : Int;
|
|
|
var token : Null<Token>;
|
|
|
+ var binary : Bool;
|
|
|
|
|
|
function new() {
|
|
|
}
|
|
@@ -28,6 +31,7 @@ class Parser {
|
|
|
this.buf = str;
|
|
|
this.pos = 0;
|
|
|
this.line = 1;
|
|
|
+ this.binary = false;
|
|
|
token = null;
|
|
|
return {
|
|
|
name : "Root",
|
|
@@ -36,6 +40,47 @@ class Parser {
|
|
|
};
|
|
|
}
|
|
|
|
|
|
+ function parseBytes( bytes : Bytes ) : FbxNode {
|
|
|
+ this.bytes = bytes;
|
|
|
+ this.pos = 0;
|
|
|
+ this.line = 0;
|
|
|
+ this.binary = (bytes.getString(0, 20) == "Kaydara FBX Binary ") && bytes.get(20) == 0;
|
|
|
+
|
|
|
+ token = null;
|
|
|
+ if (this.binary) {
|
|
|
+ // Skip header, magic [0x1A, 0x00] and version number.
|
|
|
+ this.pos = 21 + 2 + 4;
|
|
|
+ var firstNode = parseBinaryNode(getInt32());
|
|
|
+ if (firstNode.name != "") {
|
|
|
+
|
|
|
+ // Root was omitted, read until all data obtained.
|
|
|
+ var nodes : Array<FbxNode> = [firstNode];
|
|
|
+ var size:Int = getInt32();
|
|
|
+
|
|
|
+ while (size != 0) {
|
|
|
+ nodes.push(parseBinaryNode(size));
|
|
|
+ size = getInt32();
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ name: "Root",
|
|
|
+ props: [PInt(0), PString("Root"), PString("Root")],
|
|
|
+ childs: nodes
|
|
|
+ };
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return firstNode;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ name: "Root",
|
|
|
+ props : [PInt(0),PString("Root"),PString("Root")],
|
|
|
+ childs : parseNodes(),
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
function parseNodes() {
|
|
|
var nodes = [];
|
|
|
while( true ) {
|
|
@@ -124,6 +169,143 @@ class Parser {
|
|
|
if( childs == null ) childs = [];
|
|
|
return { name : name, props : props, childs : childs };
|
|
|
}
|
|
|
+
|
|
|
+ function parseBinaryNodes( output : Array<FbxNode> ) {
|
|
|
+ var size : Int = getInt32();
|
|
|
+ while (size != 0)
|
|
|
+ {
|
|
|
+ output.push(parseBinaryNode(size));
|
|
|
+ size = getInt32();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function parseBinaryNode( nextRecord : Int ) : FbxNode {
|
|
|
+
|
|
|
+ var numProperties : Int = getInt32();
|
|
|
+ var propertyListLength : UInt = getInt32();
|
|
|
+ var nameLen : Int = getByte();
|
|
|
+ var name : String = (nameLen == 0 ? "" : bytes.getString(pos, nameLen));
|
|
|
+ pos += nameLen;
|
|
|
+
|
|
|
+ var props : Array<FbxProp> = new Array();
|
|
|
+ var childs : Array<FbxNode> = new Array();
|
|
|
+
|
|
|
+ var propStart : Int = pos;
|
|
|
+
|
|
|
+ for ( i in 0...numProperties ) {
|
|
|
+ props.push(readBinaryProperty());
|
|
|
+ }
|
|
|
+
|
|
|
+ pos = propStart + propertyListLength;
|
|
|
+
|
|
|
+ if ( pos < nextRecord ) {
|
|
|
+ parseBinaryNodes(childs);
|
|
|
+ }
|
|
|
+ pos = nextRecord;
|
|
|
+
|
|
|
+ return { name: name, props: props, childs: childs };
|
|
|
+ }
|
|
|
+
|
|
|
+ function readBinaryProperty() : FbxProp {
|
|
|
+
|
|
|
+ var arrayLen : Int = 0;
|
|
|
+ var arrayEncoding:Int;
|
|
|
+ var arrayCompressedLen:Int;
|
|
|
+ var arrayBytes:Bytes = null;
|
|
|
+ var arrayBytesPos:Int = 0;
|
|
|
+
|
|
|
+ inline function readArray(entrySize:Int) {
|
|
|
+ arrayLen = getInt32();
|
|
|
+ arrayEncoding = getInt32();
|
|
|
+ arrayCompressedLen = getInt32();
|
|
|
+
|
|
|
+ switch( arrayEncoding ) {
|
|
|
+ case 0:
|
|
|
+ arrayBytes = bytes;
|
|
|
+ arrayBytesPos = pos;
|
|
|
+ pos += arrayLen * entrySize;
|
|
|
+ case 1:
|
|
|
+ arrayBytesPos = 0;
|
|
|
+ arrayBytes = haxe.zip.Uncompress.run(bytes.sub(pos, arrayCompressedLen));
|
|
|
+ pos += arrayCompressedLen;
|
|
|
+ default:
|
|
|
+ error("Unsupported array encoding: " + arrayEncoding);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Limitations:
|
|
|
+ // Int64 records are converted to Floats with top bits being lost.
|
|
|
+ // Raw binary data converted to Strings.
|
|
|
+
|
|
|
+ var type : Int = getByte();
|
|
|
+ switch( type ) {
|
|
|
+ case 'Y'.code:
|
|
|
+ return PInt(getInt16());
|
|
|
+ case 'C'.code:
|
|
|
+ return PInt(getByte());
|
|
|
+ case 'I'.code:
|
|
|
+ return PInt(getInt32());
|
|
|
+ case 'F'.code:
|
|
|
+ return PFloat(getFloat());
|
|
|
+ case 'D'.code:
|
|
|
+ return PFloat(getDouble());
|
|
|
+ case 'L'.code:
|
|
|
+ var i64 : haxe.Int64 = bytes.getInt64(pos);
|
|
|
+ pos += 8;
|
|
|
+ return PFloat(i64ToFloat(i64));
|
|
|
+ case 'f'.code:
|
|
|
+ readArray(4);
|
|
|
+ var floats:Array<Float> = new Array();
|
|
|
+ while ( arrayLen > 0 ) {
|
|
|
+ floats.push(arrayBytes.getFloat(arrayBytesPos));
|
|
|
+ arrayBytesPos += 4;
|
|
|
+ arrayLen--;
|
|
|
+ }
|
|
|
+ return PFloats(floats);
|
|
|
+ case 'd'.code:
|
|
|
+ readArray(8);
|
|
|
+ var doubles:Array<Float> = new Array();
|
|
|
+ while ( arrayLen > 0 ) {
|
|
|
+ doubles.push(arrayBytes.getDouble(arrayBytesPos));
|
|
|
+ arrayBytesPos += 8;
|
|
|
+ arrayLen--;
|
|
|
+ }
|
|
|
+ return PFloats(doubles);
|
|
|
+ case 'l'.code:
|
|
|
+ readArray(8);
|
|
|
+ var i64s:Array<Float> = new Array();
|
|
|
+ while ( arrayLen > 0 ) {
|
|
|
+ i64s.push(i64ToFloat(arrayBytes.getInt64(arrayBytesPos)));
|
|
|
+ arrayBytesPos += 8;
|
|
|
+ arrayLen--;
|
|
|
+ }
|
|
|
+ return PFloats(i64s);
|
|
|
+ case 'i'.code:
|
|
|
+ readArray(4);
|
|
|
+ var ints:Array<Int> = new Array();
|
|
|
+ while ( arrayLen > 0 ) {
|
|
|
+ ints.push(arrayBytes.getInt32(arrayBytesPos));
|
|
|
+ arrayBytesPos += 4;
|
|
|
+ arrayLen--;
|
|
|
+ }
|
|
|
+ return PInts(ints);
|
|
|
+ case 'b'.code:
|
|
|
+ readArray(1);
|
|
|
+ var bools:Array<Int> = new Array();
|
|
|
+ while ( arrayLen > 0 ) {
|
|
|
+ bools.push(arrayBytes.get(arrayBytesPos++));
|
|
|
+ arrayLen--;
|
|
|
+ }
|
|
|
+ return PInts(bools);
|
|
|
+ case 'S'.code, 'R'.code:
|
|
|
+ var len:Int = getInt32();
|
|
|
+ var s:String = bytes.getString(pos, len);
|
|
|
+ pos += len;
|
|
|
+ return PString(s);
|
|
|
+ default:
|
|
|
+ return error("Unknown property type: " + type + "/" + String.fromCharCode(type));
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
function except( except : Token ) {
|
|
|
var t = next();
|
|
@@ -173,6 +355,39 @@ class Parser {
|
|
|
return StringTools.fastCodeAt(buf, pos++);
|
|
|
}
|
|
|
|
|
|
+ inline function getInt32() {
|
|
|
+ var i : Int = bytes.getInt32(pos);
|
|
|
+ pos += 4;
|
|
|
+ return i;
|
|
|
+ }
|
|
|
+
|
|
|
+ inline function getInt16() {
|
|
|
+ var i : Int = bytes.get(pos) | (bytes.get(pos + 1) << 8);
|
|
|
+ pos += 2;
|
|
|
+ return i;
|
|
|
+ }
|
|
|
+
|
|
|
+ inline function getFloat() {
|
|
|
+ var f : Float = bytes.getFloat(pos);
|
|
|
+ pos += 4;
|
|
|
+ return f;
|
|
|
+ }
|
|
|
+
|
|
|
+ inline function getDouble() {
|
|
|
+ var d : Float = bytes.getDouble(pos);
|
|
|
+ pos += 8;
|
|
|
+ return d;
|
|
|
+ }
|
|
|
+
|
|
|
+ inline function i64ToFloat( i64 : haxe.Int64 ) : Float {
|
|
|
+ return (i64.high * 4294967296) +
|
|
|
+ ( (i64.low & 0x80000000) != 0 ? ((i64.low & 0x7fffffff) + 2147483648) : i64.low );
|
|
|
+ }
|
|
|
+
|
|
|
+ inline function getByte() {
|
|
|
+ return bytes.get(pos++);
|
|
|
+ }
|
|
|
+
|
|
|
inline function getBuf( pos : Int, len : Int ) {
|
|
|
return buf.substr(pos, len);
|
|
|
}
|
|
@@ -267,8 +482,11 @@ class Parser {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- public static function parse( text : String ) {
|
|
|
- return new Parser().parseText(text);
|
|
|
+ public static function parse( data : Bytes ) {
|
|
|
+ if (data.length > 20 && data.getString(0, 20) == "Kaydara FBX Binary ") {
|
|
|
+ return new Parser().parseBytes(data);
|
|
|
+ }
|
|
|
+ return new Parser().parseText(data.toString());
|
|
|
}
|
|
|
|
|
|
}
|