瀏覽代碼

[WIP] Implement binary FBX support (#403)

Pavel Alexandrov 7 年之前
父節點
當前提交
f9d43dc045
共有 4 個文件被更改,包括 224 次插入5 次删除
  1. 2 1
      hxd/fmt/fbx/BaseLibrary.hx
  2. 220 2
      hxd/fmt/fbx/Parser.hx
  3. 1 1
      hxd/fmt/hmd/Dump.hx
  4. 1 1
      hxd/fs/Convert.hx

+ 2 - 1
hxd/fmt/fbx/BaseLibrary.hx

@@ -1,4 +1,5 @@
 package hxd.fmt.fbx;
+import haxe.io.Bytes;
 using hxd.fmt.fbx.Data;
 import h3d.col.Point;
 
@@ -148,7 +149,7 @@ class BaseLibrary {
 		defaultModelMatrixes = new Map();
 	}
 
-	public function loadTextFile( data : String ) {
+	public function loadTextFile( data : Bytes ) {
 		load(Parser.parse(data));
 	}
 

+ 220 - 2
hxd/fmt/fbx/Parser.hx

@@ -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());
 	}
 
 }

+ 1 - 1
hxd/fmt/hmd/Dump.hx

@@ -227,7 +227,7 @@ class Dump {
 		var bytes;
 		if( file.split(".").pop().toLowerCase() == "fbx" ) {
 			var l = new hxd.fmt.fbx.HMDOut(file);
-			l.loadTextFile(sys.io.File.getContent(file));
+			l.loadTextFile(sys.io.File.getBytes(file));
 			var hmd = l.toHMD(null, !StringTools.startsWith(file.split("\\").join("/").split("/").pop(), "Anim_"));
 			var out = new haxe.io.BytesOutput();
 			new Writer(out).write(hmd);

+ 1 - 1
hxd/fs/Convert.hx

@@ -44,7 +44,7 @@ class ConvertFBX2HMD extends Convert {
 	}
 
 	override function convert() {
-		var fbx = try hxd.fmt.fbx.Parser.parse(srcBytes.toString()) catch( e : Dynamic ) throw Std.string(e) + " in " + srcPath;
+		var fbx = try hxd.fmt.fbx.Parser.parse(srcBytes) catch( e : Dynamic ) throw Std.string(e) + " in " + srcPath;
 		var hmdout = new hxd.fmt.fbx.HMDOut(srcPath);
 		hmdout.load(fbx);
 		var isAnim = StringTools.startsWith(srcFilename, "Anim_") || srcFilename.toLowerCase().indexOf("_anim_") > 0;