Browse Source

completed fbx support + sample

ncannasse 13 years ago
parent
commit
65f87f602a
10 changed files with 839 additions and 62 deletions
  1. 3 1
      .gitignore
  2. 1 1
      h3d/fbx/Mesh.hx
  3. 181 58
      h3d/fbx/Skin.hx
  4. 12 2
      h3d/prim/Skin.hx
  5. 159 0
      samples/Anim.hx
  6. 93 0
      samples/FbxTree.hx
  7. 7 0
      samples/anim.hxml
  8. 55 0
      samples/anim.hxproj
  9. 328 0
      samples/res/model.fbx
  10. BIN
      samples/res/texture.gif

+ 3 - 1
.gitignore

@@ -1,2 +1,4 @@
 
-/engine.swf
+/engine.swf
+/samples/*.swf
+*.n

+ 1 - 1
h3d/fbx/Mesh.hx

@@ -34,7 +34,7 @@ class Mesh {
 		var skin = null;
 		for( v in lib.getSubs(root) )
 			if( v.getType() == "Skin" )
-				return new Skin(lib, v, Std.int(getVertices().length/3), bonesPerVertex);
+				return new Skin(lib, v, root, Std.int(getVertices().length/3), bonesPerVertex);
 		return null;
 	}
 	

+ 181 - 58
h3d/fbx/Skin.hx

@@ -3,15 +3,15 @@ using h3d.fbx.Data;
 
 class Joint extends h3d.prim.Skin.Joint {
 	
-	public var cluster : FbxNode;
 	public var model : FbxNode;
 	public var modelId : Int;
 	
-	public var linkPos : h3d.Matrix; // absolute pose matrix
+	public var defTrans : h3d.Point;
+	public var defScale : h3d.Point;
+	public var defRot : h3d.Point;
 	
-	public function new(n, m) {
+	public function new(m) {
 		super();
-		this.cluster = n;
 		this.model = m;
 	}
 
@@ -21,66 +21,63 @@ class Skin extends h3d.prim.Skin {
 	
 	var root : FbxNode;
 	var lib : Library;
-	var hJoints : IntHash<Joint>;
-	public var allJoints : Array<Joint>;
-	public var rootJoints : Array<Joint>;
+	var allJoints : Array<Joint>;
+	var rootJoints : Array<Joint>;
 	
-	public function new(lib, root, vertexCount, bonesPerVertex) {
+	public function new(lib, root, meshGeom : FbxNode, vertexCount, bonesPerVertex) {
 		super(vertexCount, bonesPerVertex);
 		
 		this.lib = lib;
 		this.root = root;
-		// init joints
-		allJoints = [];
-		for( v in lib.getSubs(root) )
-			if( v.getType() == "Cluster" ) {
-				var model = null;
-				for( s in lib.getSubs(v) )
-					if( s.getType() == "LimbNode" ) {
-						model = s;
-						break;
-					}
-				if( model == null )
-					throw "Missing LimbNode for " + v.getName();
-				allJoints.push(new Joint(v,model));
-			}
-		// do we have a root with no skinning ?
-		var root = lib.getParents(allJoints[0].model,"Model")[0];
-		if( root != null )
-			allJoints.unshift(new Joint(null, root));
-			
-		rootJoints = allJoints.copy();
 		
-		// init joint LimbNode
+		// list joints from clusters (bones with envelop)
 		var envelop = [];
-		hJoints = new IntHash();
-		for( j in allJoints ) {
-			j.modelId = j.model.getId();
+		var hJoints = new IntHash();
+		allJoints = [];
+		for( v in lib.getSubs(root) ) {
+			if( v.getType() != "Cluster" )
+				continue;
+			var model = null;
+			for( s in lib.getSubs(v) )
+				if( s.getType() == "LimbNode" ) {
+					model = s;
+					break;
+				}
+			if( model == null )
+				throw "Missing LimbNode for " + v.getName();
+			
+			var j = new Joint(model);
+			j.modelId = model.getId();
+			j.transPos = h3d.Matrix.L(v.get("Transform").getFloats());
+			var m = getMatrixes(model);
+			if( m.t != null )
+				j.defTrans = m.t;
+			if( m.r != null )
+				j.defRot = m.r;
+			if( m.s != null )
+				j.defScale = m.s;
+			allJoints.push(j);
 			hJoints.set(j.modelId, j);
 			
-			if( j.cluster == null ) {
-				j.transPos = h3d.Matrix.I();
-				j.linkPos = h3d.Matrix.I();
-			} else {
-				j.transPos = h3d.Matrix.L(j.cluster.get("Transform").getFloats());
-				j.linkPos = h3d.Matrix.L(j.cluster.get("TransformLink").getFloats());
-				var weights = j.cluster.getAll("Weights");
-				if( weights.length > 0 ) {
-					var weights = weights[0].getFloats();
-					var vertex = j.cluster.get("Indexes").getInts();
-					for( i in 0...vertex.length ) {
-						var w = weights[i];
-						if( w < 0.01 )
-							continue;
-						addInfluence(vertex[i], j, w);
-					}
+			// init envelop
+			var weights = v.getAll("Weights");
+			if( weights.length > 0 ) {
+				var weights = weights[0].getFloats();
+				var vertex = v.get("Indexes").getInts();
+				for( i in 0...vertex.length ) {
+					var w = weights[i];
+					if( w < 0.01 )
+						continue;
+					addInfluence(vertex[i], j, w);
 				}
 			}
 		}
-		
+	
+		// finalize envelop
 		initWeights();
 		
 		// init tree
+		rootJoints = allJoints.copy();
 		for( j in hJoints )
 			for( s in lib.getSubs(j.model,"Model") )
 				if( s.getType() == "LimbNode" ) {
@@ -93,15 +90,132 @@ class Skin extends h3d.prim.Skin {
 					j.subs.push(sub);
 					rootJoints.remove(sub);
 				}
+				
+		// if we have skinned bones having parents which are not skinned, add them to the list (recursively)
+		var found = true;
+		while( found ) {
+			found = false;
+			for( jsub in rootJoints )
+				for( p in lib.getParents(jsub.model, "Model") )
+					if( p.getType() == "LimbNode" ) {
+						var pid = p.getId();
+						var j = hJoints.get(pid);
+						if( j == null ) {
+							j = new Joint(p);
+							j.modelId = pid;
+							hJoints.set(pid, j);
+							allJoints.push(j);
+							rootJoints.push(j);
+						}
+						jsub.parent = j;
+						j.subs.push(jsub);
+						rootJoints.remove(jsub);
+						found = true;
+						break;
+					}
+		}
+		
+		// init transforms
+		
+		/*
+			From the FBX SDK, we have :
+				
+				VTM = (RGCP'-1 . CGCP) . (CGIP'-1 . RGIP)
+				
+				We have calculated that :
+				- RGCP = MeshPosition
+				- CGCP = (RootMeshPosition ... BipPosition) . (ParentFrame .... CurrentFrame)
+					We concat all position (either the Lcl one or the KeyFrame one if there is an AnimCurve)
+				- CGIP = TransformLink
+				- RGIP = TransformLink . Transform . MeshGeometryTransform
+				
+				As a result, we have :
+					
+				VTM = (MeshPosition.-1 . RootMeshPosition....BipPosition) . (ParentFrame .... CurrentFrame) . Transform . MeshGeometryTransform
+				
+				   Mesh : the geometry mesh
+				   Root : the mesh and skin/bip common ancestor ?
+				
+				/!\ FDK SDK and our implementation perform matrix multiplication is reverse order.
+				The order of this documentation matches the SDK one.
+				
+		*/
+		
+		var meshObj = lib.getParents(meshGeom,"Model")[0];
+
+		// assume that the mesh in on the stage (common ancestor = stage)
+		if( lib.getParents(meshObj).length > 0 )
+			throw "Mesh has parents";
+
+		// get the skin root
+		var skinRoot = lib.getParents(this.rootJoints[0].model, "Model")[0];
+
+		preTransform.identity();
+		while( skinRoot != null ) {
+			preTransform.multiply(preTransform, getMatrix(skinRoot));
+			skinRoot = lib.getParents(skinRoot)[0];
+		}
+			
+		var meshPos = getMatrix(meshObj);
+		meshPos.invert();
+		preTransform.multiply(preTransform, meshPos);
+			
+		// apply geometric translation as a post transform matrix
+		var geomTrans = null;
+		for( p in meshObj.getAll("Properties70.P") )
+			switch( p.props[0].toString() ) {
+			case "GeometricTranslation":
+				geomTrans = new h3d.Point(p.props[4].toFloat(), p.props[5].toFloat(), p.props[6].toFloat());
+			default:
+			}
+		if( geomTrans != null ) {
+			var geomTransform = makeMatrix(null, null, null, null, geomTrans);
+			for( j in allJoints )
+				if( j.transPos != null )
+					j.transPos.multiply(geomTransform, j.transPos);
+		}
 	}
 	
-	function makeMatrix( trans : h3d.Point, rot : h3d.Point, scale : h3d.Point, ?preRot : h3d.Point ) {
+	function getMatrixes( model : FbxNode ) {
+		var preRot = null, trans = null, rot = null, scale = null, geomTrans = null;
+		var F = Math.PI / 180;
+		for( p in model.getAll("Properties70.P") )
+			switch( p.props[0].toString() ) {
+			case "GeometricTranslation":
+				geomTrans = new h3d.Point(p.props[4].toFloat(), p.props[5].toFloat(), p.props[6].toFloat());
+			case "PreRotation":
+				preRot = new h3d.Point(p.props[4].toFloat() * F, p.props[5].toFloat() * F, p.props[6].toFloat() * F);
+			case "Lcl Rotation":
+				rot = new h3d.Point(p.props[4].toFloat() * F, p.props[5].toFloat() * F, p.props[6].toFloat() * F);
+			case "Lcl Translation":
+				trans = new h3d.Point(p.props[4].toFloat(), p.props[5].toFloat(), p.props[6].toFloat());
+			case "Lcl Scaling":
+				scale = new h3d.Point(p.props[4].toFloat(), p.props[5].toFloat(), p.props[6].toFloat());
+			case "RotationActive", "InheritType", "ScalingMin", "MaxHandle", "DefaultAttributeIndex", "Show", "UDP3DSMAX":
+			case "RotationMinX","RotationMinY","RotationMinZ","RotationMaxX","RotationMaxY","RotationMaxZ":
+			default:
+				#if debug
+				trace(p.props[0].toString());
+				#end
+			}
+		return { t : trans, r : rot, s : scale, preRot : preRot, geomTrans : geomTrans };
+	}
+	
+	function getMatrix(model) {
+		var m = getMatrixes(model);
+		return makeMatrix(m.t, m.r, m.s, m.preRot, m.geomTrans);
+	}
+
+	function makeMatrix( trans : h3d.Point, rot : h3d.Point, scale : h3d.Point, ?preRot : h3d.Point, ?geoTrans : h3d.Point ) {
 		var m = new h3d.Matrix();
 		
-		if( scale != null )
-			m.initScale(scale.x, scale.y, scale.z);
+		if( geoTrans != null )
+			m.initTranslate(geoTrans.x, geoTrans.y, geoTrans.z);
 		else
 			m.identity();
+		
+		if( scale != null )
+			m.scale(scale.x, scale.y, scale.z);
 			
 		if( rot != null ) {
 			var q = new h3d.Quat();
@@ -137,7 +251,7 @@ class Skin extends h3d.prim.Skin {
 			}
 		if( node == null )
 			throw "Anim " + name + " not found";
-		var anim = new h3d.prim.Skin.Animation(name);
+		var anim = new h3d.prim.Skin.Animation(this, name);
 		var layers = lib.getSubs(node,"AnimationLayer");
 		//if( layers.length != 1 )
 		//	throw "Anim " + name + " has " + layers.length + " layers";
@@ -173,11 +287,20 @@ class Skin extends h3d.prim.Skin {
 				}
 			
 			var frames = new Array();
-			if( anim.frameCount == 0 && ftrans != null )
-				anim.frameCount = ftrans[0].length;
-			var t = ftrans == null ? null : new h3d.Point();
-			var r = frot == null ? null : new h3d.Point();
-			var s = fscale == null ? null : new h3d.Point();
+			if( anim.frameCount == 0 ) {
+				if( ftrans != null )
+					anim.frameCount = ftrans[0].length;
+				else if( frot != null )
+					anim.frameCount = frot[0].length;
+				else if( fscale != null )
+					anim.frameCount = fscale[0].length;
+			}
+			
+			// for defaults, use the bone Lcl infos, if any
+			var t = ftrans == null ? j.defTrans : new h3d.Point();
+			var r = frot == null ? j.defRot : new h3d.Point();
+			var s = fscale == null ? j.defScale : new h3d.Point();
+			
 			for( i in 0...anim.frameCount ) {
 				if( ftrans != null ) {
 					t.x = ftrans[0][i];

+ 12 - 2
h3d/prim/Skin.hx

@@ -35,12 +35,14 @@ class AnimCurve {
 
 class Animation {
 	
+	public var skin : Skin;
 	public var name : String;
 	public var curves : Array<AnimCurve>;
 	public var hcurves : IntHash<AnimCurve>;
 	public var frameCount : Int;
 	
-	public function new(n) {
+	public function new(sk, n) {
+		this.skin = sk;
 		this.name = n;
 		curves = [];
 		hcurves = new IntHash();
@@ -56,8 +58,14 @@ class Animation {
 		if( c.absolute )
 			return;
 		c.absolute = true;
-		if( c.parent == null )
+		if( c.parent == null ) {
+			for( i in 0...frameCount ) {
+				var m = c.frames[i];
+				if( m == null ) break;
+				m.multiply3x4(m, skin.preTransform);
+			}
 			return;
+		}
 		computeAnimFrames(c.parent);
 		for( i in 0...frameCount ) {
 			var m = c.frames[i];
@@ -98,12 +106,14 @@ class Skin {
 	public var vertexJoints : Table<Int>;
 	public var vertexWeights : Table<Float>;
 	public var boundJoints : Array<Joint>;
+	public var preTransform : h3d.Matrix;
 	
 	var envelop : Array<Array<Influence>>;
 	
 	public function new( vertexCount, bonesPerVertex ) {
 		this.vertexCount = vertexCount;
 		this.bonesPerVertex = bonesPerVertex;
+		preTransform = h3d.Matrix.I();
 		vertexJoints = new Table(#if flash vertexCount * bonesPerVertex #end);
 		vertexWeights = new Table(#if flash vertexCount * bonesPerVertex #end);
 		envelop = [];

+ 159 - 0
samples/Anim.hx

@@ -0,0 +1,159 @@
+typedef K = flash.ui.Keyboard;
+
+@:bitmap("res/texture.gif")
+class Tex extends flash.display.BitmapData {
+}
+
+@:file("res/model.fbx")
+class Model extends flash.utils.ByteArray {
+	
+}
+
+class LightShader extends h3d.Shader {
+	static var SRC = {
+		var input : {
+			pos : Float3,
+			norm : Float3,
+			uv : Float2,
+			weights : Float3,
+			index : Int,
+		};
+		var shade : Float;
+		var tuv : Float2;
+
+		function vertex( mpos : Matrix, mproj : Matrix, light : Float3, bones : M34<39> ) {
+			var p : Float4;
+			p.xyz = pos.xyzw * weights.x * bones[index.x * (255 * 3)] + pos.xyzw * weights.y * bones[index.y * (255 * 3)] + pos.xyzw * weights.z * bones[index.z * (255 * 3)];
+			p.w = 1;
+			out = (p * mpos) * mproj;
+			shade = (norm.xyzw * mpos).xyz.dot(light).sat() * 0.8 + 0.6;
+			tuv = uv;
+		}
+		
+		function fragment( tex : Texture ) {
+			var color = tex.get(tuv,nearest);
+			kill(color.a - 0.001);
+			color.rgb *= shade;
+			out = color;
+		}
+	}
+}
+
+class Anim {
+
+	var engine : h3d.Engine;
+	var obj : h3d.CustomObject<LightShader>;
+	var model : h3d.prim.FBXModel;
+	var anim : h3d.prim.Skin.Animation;
+	var time : Float;
+	var palette : Array<h3d.Matrix>;
+	
+	var flag : Bool;
+	var view : Int;
+
+	function new() {
+		time = 0;
+		view = 3;
+		engine = new h3d.Engine();
+		engine.backgroundColor = 0xFF808080;
+		engine.onReady = onReady;
+		engine.init();
+	}
+	
+	function onReady() {
+		flash.Lib.current.addEventListener(flash.events.Event.ENTER_FRAME, function(_) onUpdate());
+		flash.Lib.current.stage.addEventListener(flash.events.KeyboardEvent.KEY_DOWN, function(k:flash.events.KeyboardEvent ) {
+			switch( k.keyCode ) {
+			case K.NUMPAD_ADD:
+				view++;
+			case K.NUMPAD_SUBTRACT:
+				view--;
+			case K.SPACE:
+				flag = !flag;
+			default:
+			}
+		});
+
+		var shader = new LightShader();
+		var name = "", aname = "Take 001";
+		
+		var file = new Model();
+		var lib = new h3d.fbx.Library();
+		lib.loadTextFile(file.readUTFBytes(file.length));
+		model = new h3d.prim.FBXModel(lib.getMesh(name));
+		
+		try {
+			anim = model.getAnimation(aname);
+		} catch( d:Dynamic ) {
+			throw "no such anim " + aname + " in list : " + Std.string( [model.listAnimations()] );
+		}
+		anim.computeAbsoluteFrames();
+		palette = anim.allocPalette();
+		
+		obj = new h3d.CustomObject(model, shader);
+		obj.material.culling = None;
+		obj.material.blend(SrcAlpha, OneMinusSrcAlpha);
+		obj.shader.tex = engine.mem.makeTexture(new Tex(0,0));
+	}
+		
+	function onUpdate() {
+		if( !engine.begin() )
+			return;
+			
+		var dist = 50., height = 10.;
+		
+		switch( view ) {
+		case 0:
+			engine.camera.pos.set(0, height, dist);
+			engine.camera.up.set(0, -1, 0);
+			engine.camera.target.set(0, height, 0);
+			engine.camera.update();
+		case 1:
+			engine.camera.pos.set(0, dist, 0);
+			engine.camera.up.set(0, 1, 0);
+			engine.camera.target.set(0, 0, 0);
+			engine.camera.update();
+		case 2:
+			var K = Math.sqrt(2);
+			engine.camera.pos.set(dist, height, 0);
+			engine.camera.up.set(1, 0, 0);
+			engine.camera.target.set(0, height, 0);
+			engine.camera.update();
+		case 3:
+			var speed = 0.02;
+			engine.camera.pos.set(Math.cos(time * speed) * dist, Math.sin(time * speed) * dist, height);
+			engine.camera.up.set(0, 0, -1);
+			engine.camera.target.set(0, 0, height);
+			engine.camera.update();
+		default:
+			view = 0;
+		}
+		
+		time += 1;
+		
+		anim.updateJoints(Std.int(time), palette);
+		if( flag )
+			for( p in palette )
+				p.identity();
+		
+		var lspeed = 0.03;
+		var light = new h3d.Vector( -Math.cos(time * lspeed), -Math.sin(time * lspeed), 3 );
+		light.normalize();
+		obj.shader.light = light;
+		obj.shader.mproj = engine.camera.m;
+		obj.shader.mpos = h3d.Matrix.I();
+		obj.shader.bones = palette;
+		obj.render(engine);
+		
+		engine.line(0, 0, 0, 50, 0, 0, 0xFFFF0000);
+		engine.line(0, 0, 0, 0, 50, 0, 0xFF00FF00);
+		engine.line(0, 0, 0, 0, 0, 50, 0xFF0000FF);
+
+		engine.end();
+	}
+	
+	static function main() {
+		new Anim();
+	}
+
+}

+ 93 - 0
samples/FbxTree.hx

@@ -0,0 +1,93 @@
+using h3d.fbx.Data;
+
+class FbxTree {
+
+	public static function main() {
+		var path = Sys.args()[0];
+		if( path == null ) {
+			Sys.println("Please enter file path");
+			return;
+		}
+		var file = sys.io.File.getContent(path);
+		var root : FbxNode = {
+			name : "root",
+			props : [PInt(0),PString("ROOT"),PString("")],
+			childs : h3d.fbx.Parser.parse(file),
+		};
+		
+		var parents = new IntHash();
+		var childs = new IntHash();
+
+		var rootObjects = [];
+		var objects = new IntHash();
+		
+		objects.set(0, root);
+		rootObjects.push(root);
+		
+		for( o in root.get("Objects").childs ) {
+			objects.set(o.props[0].toInt(), o);
+			rootObjects.push(o);
+		}
+		
+		for( c in root.getAll("Connections.C") ) {
+			var cid = c.props[1].toInt();
+			var pid = c.props[2].toInt();
+			
+			rootObjects.remove(objects.get(cid));
+			
+			var pl = parents.get(cid);
+			if( pl == null ) {
+				pl = [];
+				parents.set(cid, pl);
+			}
+			pl.push(pid);
+
+			var cl = childs.get(pid);
+			if( cl == null ) {
+				cl = [];
+				childs.set(pid, cl);
+			}
+			cl.push(cid);
+		}
+		
+		function highestDepth( id : Int, stack : IntHash<Bool> ) {
+			if( stack.get(id) )
+				return 0;
+			var pl = parents.get(id);
+			if( pl == null )
+				return 0;
+			stack.set(id, true);
+			var max = 0;
+			for( p in pl ) {
+				var d = highestDepth(p, stack);
+				if( d > max )
+					max = d;
+			}
+			return 1 + max;
+		}
+		
+		var marked = new IntHash();
+		function genRec( o : FbxNode, tabs : String ) {
+			var id = o.props[0].toInt();
+			var m = marked.get(id);
+			Sys.println(tabs + id + " " + o.props[1].toString() + "(" + o.props[2].toString() + ")" + (m?" [REF]":""));
+			if( m ) return;
+			tabs += "\t";
+			marked.set(id, true);
+			var childs = childs.get(id);
+			if( childs == null )
+				return;
+			var oc = [];
+			for( cid in childs ) {
+				var o = objects.get(cid);
+				oc.push( { o : o, d : highestDepth(cid,new IntHash()) } );
+			}
+			//oc.sort(function(o1, o2) return o1.d - o2.d);
+			for( o in oc )
+				genRec(o.o, tabs);
+		}
+		for( r in rootObjects )
+			genRec(r, "");
+	}
+	
+}

+ 7 - 0
samples/anim.hxml

@@ -0,0 +1,7 @@
+-cp ..
+-swf anim.swf
+-swf-header 800:600:40:FFFFFF
+--flash-strict
+-swf-version 11.3
+-main Anim
+-lib format

+ 55 - 0
samples/anim.hxproj

@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<project version="2">
+  <!-- Output SWF options -->
+  <output>
+    <movie outputType="Application" />
+    <movie input="" />
+    <movie path="anim.swf" />
+    <movie fps="40" />
+    <movie width="800" />
+    <movie height="600" />
+    <movie version="11" />
+    <movie minorVersion="3" />
+    <movie platform="Flash Player" />
+    <movie background="#FFFFFF" />
+  </output>
+  <!-- Other classes to be compiled into your SWF -->
+  <classpaths>
+    <class path=".." />
+  </classpaths>
+  <!-- Build options -->
+  <build>
+    <option directives="" />
+    <option flashStrict="True" />
+    <option mainClass="Anim" />
+    <option enabledebug="False" />
+    <option additional="-lib format" />
+  </build>
+  <!-- haxelib libraries -->
+  <haxelib>
+    <!-- example: <library name="..." /> -->
+  </haxelib>
+  <!-- Class files to compile (other referenced classes will automatically be included) -->
+  <compileTargets>
+    <!-- example: <compile path="..." /> -->
+  </compileTargets>
+  <!-- Assets to embed into the output SWF -->
+  <library>
+    <!-- example: <asset path="..." id="..." update="..." glyphs="..." mode="..." place="..." sharepoint="..." /> -->
+  </library>
+  <!-- Paths to exclude from the Project Explorer tree -->
+  <hiddenPaths>
+    <hidden path="anim.hxml" />
+  </hiddenPaths>
+  <!-- Executed before build -->
+  <preBuildCommand />
+  <!-- Executed after build -->
+  <postBuildCommand alwaysRun="False" />
+  <!-- Other project options -->
+  <options>
+    <option showHiddenPaths="False" />
+    <option testMovie="Default" />
+  </options>
+  <!-- Plugin storage -->
+  <storage />
+</project>

File diff suppressed because it is too large
+ 328 - 0
samples/res/model.fbx


BIN
samples/res/texture.gif


Some files were not shown because too many files changed in this diff