瀏覽代碼

DynamicBones: add base implementation of dynamic bones

lviguier 7 月之前
父節點
當前提交
e501eb131b
共有 5 個文件被更改,包括 363 次插入158 次删除
  1. 93 88
      h3d/anim/BufferAnimation.hx
  2. 23 0
      h3d/anim/Skin.hx
  3. 134 66
      h3d/prim/ModelDatabase.hx
  4. 101 1
      h3d/scene/Skin.hx
  5. 12 3
      hxd/fmt/hmd/Library.hx

+ 93 - 88
h3d/anim/BufferAnimation.hx

@@ -165,46 +165,98 @@ class BufferAnimation extends Animation {
 
 			var m = o.matrix;
 			if( m != null ) {
+				var dynJoint = Std.downcast(o.targetSkin.getSkinData().allJoints[o.targetJoint], h3d.anim.Skin.DynamicJoint);
+				if (dynJoint == null) {
+					if( layout.has(Position) ) {
+						m._41 = lerpValue();
+						m._42 = lerpValue();
+						m._43 = lerpValue();
+					} else {
+						m._41 = m._14;
+						m._42 = m._24;
+						m._43 = m._34;
+					}
 
-				if( layout.has(Position) ) {
-					m._41 = lerpValue();
-					m._42 = lerpValue();
-					m._43 = lerpValue();
-				} else {
-					m._41 = m._14;
-					m._42 = m._24;
-					m._43 = m._34;
-				}
+					if( layout.has(Rotation) ) {
+						var q1x : Float32 = data[offset1++];
+						var q1y : Float32 = data[offset1++];
+						var q1z : Float32 = data[offset1++];
+						var q1w : Float32 = Math.sqrt(hxd.Math.abs(1 - (q1x*q1x+q1y*q1y+q1z*q1z)));
+						var q2x : Float32 = data[offset2++];
+						var q2y : Float32 = data[offset2++];
+						var q2z : Float32 = data[offset2++];
+						var q2w : Float32 = Math.sqrt(hxd.Math.abs(1 - (q2x*q2x+q2y*q2y+q2z*q2z)));
+
+						// qlerp nearest
+						var dot = q1x * q2x + q1y * q2y + q1z * q2z + q1w * q2w;
+						var q2 = dot < 0 ? -k2 : k2;
+						var qx = q1x * k1 + q2x * q2;
+						var qy = q1y * k1 + q2y * q2;
+						var qz = q1z * k1 + q2z * q2;
+						var qw = q1w * k1 + q2w * q2;
+						// make sure the resulting quaternion is normalized
+						var ql = 1 / Math.sqrt(qx * qx + qy * qy + qz * qz + qw * qw);
+						qx *= ql;
+						qy *= ql;
+						qz *= ql;
+						qw *= ql;
+
+						if( decompose ) {
+							m._12 = qx;
+							m._13 = qy;
+							m._21 = qz;
+							m._23 = qw;
+							if( layout.has(Scale) ) {
+								m._11 = lerpValue();
+								m._22 = lerpValue();
+								m._33 = lerpValue();
+							} else {
+								m._11 = 1;
+								m._22 = 1;
+								m._33 = 1;
+							}
+						} else {
+							// quaternion to matrix
+							var xx = qx * qx;
+							var xy = qx * qy;
+							var xz = qx * qz;
+							var xw = qx * qw;
+							var yy = qy * qy;
+							var yz = qy * qz;
+							var yw = qy * qw;
+							var zz = qz * qz;
+							var zw = qz * qw;
+							m._11 = 1 - 2 * ( yy + zz );
+							m._12 = 2 * ( xy + zw );
+							m._13 = 2 * ( xz - yw );
+							m._21 = 2 * ( xy - zw );
+							m._22 = 1 - 2 * ( xx + zz );
+							m._23 = 2 * ( yz + xw );
+							m._31 = 2 * ( xz + yw );
+							m._32 = 2 * ( yz - xw );
+							m._33 = 1 - 2 * ( xx + yy );
+							if( layout.has(Scale) ) {
+								var sx = lerpValue();
+								var sy = lerpValue();
+								var sz = lerpValue();
+								m._11 *= sx;
+								m._12 *= sx;
+								m._13 *= sx;
+								m._21 *= sy;
+								m._22 *= sy;
+								m._23 *= sy;
+								m._31 *= sz;
+								m._32 *= sz;
+								m._33 *= sz;
+							}
+						}
+
+					} else {
+						m._12 = 0;
+						m._13 = 0;
+						m._21 = 0;
+						m._23 = decompose ? 1 : 0;
 
-				if( layout.has(Rotation) ) {
-					var q1x : Float32 = data[offset1++];
-					var q1y : Float32 = data[offset1++];
-					var q1z : Float32 = data[offset1++];
-					var q1w : Float32 = Math.sqrt(hxd.Math.abs(1 - (q1x*q1x+q1y*q1y+q1z*q1z)));
-					var q2x : Float32 = data[offset2++];
-					var q2y : Float32 = data[offset2++];
-					var q2z : Float32 = data[offset2++];
-					var q2w : Float32 = Math.sqrt(hxd.Math.abs(1 - (q2x*q2x+q2y*q2y+q2z*q2z)));
-
-					// qlerp nearest
-					var dot = q1x * q2x + q1y * q2y + q1z * q2z + q1w * q2w;
-					var q2 = dot < 0 ? -k2 : k2;
-					var qx = q1x * k1 + q2x * q2;
-					var qy = q1y * k1 + q2y * q2;
-					var qz = q1z * k1 + q2z * q2;
-					var qw = q1w * k1 + q2w * q2;
-					// make sure the resulting quaternion is normalized
-					var ql = 1 / Math.sqrt(qx * qx + qy * qy + qz * qz + qw * qw);
-					qx *= ql;
-					qy *= ql;
-					qz *= ql;
-					qw *= ql;
-
-					if( decompose ) {
-						m._12 = qx;
-						m._13 = qy;
-						m._21 = qz;
-						m._23 = qw;
 						if( layout.has(Scale) ) {
 							m._11 = lerpValue();
 							m._22 = lerpValue();
@@ -214,58 +266,11 @@ class BufferAnimation extends Animation {
 							m._22 = 1;
 							m._33 = 1;
 						}
-					} else {
-						// quaternion to matrix
-						var xx = qx * qx;
-						var xy = qx * qy;
-						var xz = qx * qz;
-						var xw = qx * qw;
-						var yy = qy * qy;
-						var yz = qy * qz;
-						var yw = qy * qw;
-						var zz = qz * qz;
-						var zw = qz * qw;
-						m._11 = 1 - 2 * ( yy + zz );
-						m._12 = 2 * ( xy + zw );
-						m._13 = 2 * ( xz - yw );
-						m._21 = 2 * ( xy - zw );
-						m._22 = 1 - 2 * ( xx + zz );
-						m._23 = 2 * ( yz + xw );
-						m._31 = 2 * ( xz + yw );
-						m._32 = 2 * ( yz - xw );
-						m._33 = 1 - 2 * ( xx + yy );
-						if( layout.has(Scale) ) {
-							var sx = lerpValue();
-							var sy = lerpValue();
-							var sz = lerpValue();
-							m._11 *= sx;
-							m._12 *= sx;
-							m._13 *= sx;
-							m._21 *= sy;
-							m._22 *= sy;
-							m._23 *= sy;
-							m._31 *= sz;
-							m._32 *= sz;
-							m._33 *= sz;
-						}
-					}
-
-				} else {
-					m._12 = 0;
-					m._13 = 0;
-					m._21 = 0;
-					m._23 = decompose ? 1 : 0;
-
-					if( layout.has(Scale) ) {
-						m._11 = lerpValue();
-						m._22 = lerpValue();
-						m._33 = lerpValue();
-					} else {
-						m._11 = 1;
-						m._22 = 1;
-						m._33 = 1;
 					}
 				}
+				else {
+					m = dynJoint.relPos;
+				}
 
 				if( o.targetSkin != null ) {
 					o.targetSkin.currentRelPose[o.targetJoint] = m;

+ 23 - 0
h3d/anim/Skin.hx

@@ -26,7 +26,30 @@ class Joint {
 		splitIndex = -1;
 		subs = [];
 	}
+}
+
+class DynamicJoint extends Joint {
+	public static var SLEEP_THRESHOLD : Float = 0.0001;
+
+	public var absPos : h3d.Matrix; // Abs pos of the joint computed each frame
+	public var relPos : h3d.Matrix; // Initial relative pos before joint get moved by animation / dynamic system
+
+	public var speed : h3d.Vector;
 
+	// Global parameters
+	public var globalForce : Vector = new Vector(0.0, 0.0, 0.0);
+
+	// Parameters
+	public var radius : Float = 0.0;
+	public var damping : Float = 0.4;
+	public var stiffness : Float = 0.5;
+	public var resistance : Float = 0.9;
+	public var slackness : Float = 0.1;
+
+	public function new() {
+		super();
+		speed = new h3d.Vector(0.0, 0.0, 0.0);
+	}
 }
 
 private class Permut {

+ 134 - 66
h3d/prim/ModelDatabase.hx

@@ -1,5 +1,13 @@
 package h3d.prim;
 
+typedef ModelDataInput = {
+	var resourceDirectory : String;
+	var resourceName : String;
+	var objectName : String;
+	var hmd : HMDModel;
+	var skin : h3d.scene.Skin;
+}
+
 class ModelDatabase {
 
 	public static var db : Map<String, Dynamic> = new Map();
@@ -22,7 +30,6 @@ class ModelDatabase {
 		return directory == null || directory == "" ? FILE_NAME : directory + "/" + FILE_NAME;
 	}
 
-
 	function getRootData( directory : String ) {
 		if( directory == null )
 			return null;
@@ -35,16 +42,18 @@ class ModelDatabase {
 		return value;
 	}
 
-	function getModelData( directory : String, key : String ) {
+	function getModelData( directory : String, resourceName : String, modelName : String ) {
+		var key = resourceName + "/" + modelName;
 		var rootData = getRootData(directory);
 		cleanOldModelData(rootData, key);
 		return Reflect.field(rootData, key);
 	}
 
-	function saveModelData( directory : String, key : String, data : Dynamic ) {
+	function saveModelData( directory : String, resourceName : String, modelName : String, data : Dynamic ) {
 		var file = getFilePath(directory);
 
 		var rootData = getRootData(directory);
+		var key = resourceName + "/" + modelName;
 		if (data == null || Reflect.fields(data).length == 0)
 			Reflect.deleteField(rootData, key);
 		else
@@ -63,48 +72,112 @@ class ModelDatabase {
 		#end
 	}
 
+	function getDefaultLodConfig( dir : String ) : Array<Float> {
+		var fs = Std.downcast(hxd.res.Loader.currentInstance.fs, hxd.fs.LocalFileSystem);
+		if (fs == null)
+			return baseLodConfig;
+
+		#if (sys || nodejs)
+			var c = @:privateAccess fs.convert.getConfig(defaultLodConfigs, baseLodConfig, dir, function(fullObj) {
+				if (Reflect.hasField(fullObj, "lods.screenRatio"))
+					return Reflect.field(fullObj, "lods.screenRatio");
 
-	public function loadModelProps( objectName : String, hmd : HMDModel ) {
-		var key = @:privateAccess hmd.lib.resource.name + "/" + objectName;
-		var modelData : Dynamic = getModelData(@:privateAccess hmd.lib.resource.entry.directory, key);
+				return baseLodConfig;
+			});
+			return c;
+		#else
+			return baseLodConfig;
+		#end
 
-		loadLodConfig(modelData, hmd);
-		loadDynamicBonesConfig(modelData, hmd);
 	}
 
-	public function saveModelProps( objectName : String, hmd : HMDModel ) {
-		var key = @:privateAccess hmd.lib.resource.name + "/" + objectName;
-		var data : Dynamic = getModelData(@:privateAccess hmd.lib.resource.entry.directory, key);
-		if( data == null )
-			data = {};
+	// Used to clean previous version of modelDatabase, should be removed after some time
+	function cleanOldModelData( rootData : Dynamic, key : String) {
+		var oldLodConfig = Reflect.field(rootData, LOD_CONFIG);
+		if (oldLodConfig != null) {
+			for (f in Reflect.fields(oldLodConfig)) {
+				if (key.indexOf(f) < 0)
+					continue;
 
-		saveLodConfig(data, hmd);
-		saveDynamicBonesConfig(data, hmd);
+				var c = Reflect.field(oldLodConfig, f);
 
-		saveModelData(@:privateAccess hmd.lib.resource.entry.directory, @:privateAccess hmd.lib.resource.name + "/" + objectName, data);
-	}
+				var newData = { "lodConfig" : c };
+				Reflect.setField(rootData, key, newData);
 
+				// Remove old entry
+				Reflect.deleteField(oldLodConfig, f);
+				Reflect.setField(rootData, LOD_CONFIG, oldLodConfig);
+			}
 
-	function loadLodConfig( modelData : Dynamic, hmd : HMDModel ) {
-		var lodConfigs = Reflect.field(modelData, LOD_CONFIG);
-		@:privateAccess hmd.lodConfig = lodConfigs;
+			if (oldLodConfig == null || Reflect.fields(oldLodConfig).length == 0)
+				Reflect.deleteField(rootData, LOD_CONFIG);
+		}
 	}
 
-	function loadDynamicBonesConfig( modelData : Dynamic, hmd : HMDModel ) {
-		// TODO
+
+	function loadLodConfig( input : ModelDataInput, data : Dynamic ) {
+		var c = Reflect.field(data, LOD_CONFIG);
+		if (c == null || input.hmd == null)
+			return;
+		@:privateAccess input.hmd.lodConfig = c;
 	}
 
+	function loadDynamicBonesConfig( input : ModelDataInput, data : Dynamic) {
+		var c : Array<Dynamic> = Reflect.field(data, DYN_BONES_CONFIG);
+		if (c == null || input.skin == null)
+			return;
 
-	function saveLodConfig( data : Dynamic, hmd : HMDModel ) {
+		function getJointConf(j: h3d.anim.Skin.Joint) {
+			for (jConf in c)
+				if (jConf.name == j.name)
+					return jConf;
+
+			return null;
+		}
+
+		var skinData = input.skin.getSkinData();
+		for (j in skinData.allJoints) {
+			var jConf = getJointConf(j);
+			if (jConf == null)
+				continue;
+
+			var newJ = new h3d.anim.Skin.DynamicJoint();
+			newJ.index = j.index;
+			newJ.name = j.name;
+			newJ.bindIndex = j.bindIndex;
+			newJ.splitIndex = j.splitIndex;
+			newJ.defMat = j.defMat;
+			newJ.transPos = j.transPos;
+			newJ.parent = j.parent;
+			newJ.follow = j.follow;
+			newJ.subs = j.subs;
+			newJ.offsets = j.offsets;
+			newJ.offsetRay = j.offsetRay;
+			newJ.retargetAnim = j.retargetAnim;
+			newJ.damping = jConf.damping;
+			newJ.resistance = jConf.resistance;
+			newJ.slackness = jConf.slackness;
+			newJ.stiffness = jConf.stiffness;
+			newJ.globalForce = jConf.globalForce;
+			skinData.allJoints[j.index] = newJ;
+
+			j.parent?.subs.remove(j);
+			j.parent?.subs.push(newJ);
+		}
+
+		input.skin.setSkinData(skinData);
+	}
+
+	function saveLodConfig( input : ModelDataInput, data : Dynamic ) @:privateAccess {
 		var isDefaultConfig = true;
-		var defaultConfig = getDefaultLodConfig(@:privateAccess hmd.lib.resource.entry.directory);
+		var defaultConfig = getDefaultLodConfig(input.resourceDirectory);
 
-		if (@:privateAccess hmd.lodConfig != null) {
-			if (defaultConfig.length != @:privateAccess hmd.lodConfig.length)
+		if (input.hmd.lodConfig != null) {
+			if (defaultConfig.length != input.hmd.lodConfig.length)
 				isDefaultConfig = false;
 
-			for (idx in 0...@:privateAccess hmd.lodConfig.length) {
-				if (defaultConfig[idx] != @:privateAccess hmd.lodConfig[idx]) {
+			for (idx in 0...input.hmd.lodConfig.length) {
+				if (defaultConfig[idx] != input.hmd.lodConfig[idx]) {
 					isDefaultConfig = false;
 					break;
 				}
@@ -113,11 +186,11 @@ class ModelDatabase {
 
 		if (!isDefaultConfig) {
 			var c = [];
-			for (idx in 0...hmd.lodCount()) @:privateAccess {
-				if (idx >= hmd.lodConfig.length)
+			for (idx in 0...input.hmd.lodCount()) {
+				if (idx >= input.hmd.lodConfig.length)
 					c[idx] = 0.;
 				else
-					c[idx] = hmd.lodConfig[idx];
+					c[idx] = input.hmd.lodConfig[idx];
 			}
 			Reflect.setField(data, LOD_CONFIG, c);
 		}
@@ -125,51 +198,46 @@ class ModelDatabase {
 			Reflect.deleteField(data, LOD_CONFIG);
 	}
 
-	function saveDynamicBonesConfig( data : Dynamic, hmd : HMDModel ) {
-		// TODO
-	}
+	function saveDynamicBonesConfig( input : ModelDataInput, data : Dynamic ) {
+		if (input.skin == null)
+			return;
 
+		var dynamicJoints = [];
+		for (j in input.skin.getSkinData().allJoints) {
+			var dynJ = Std.downcast(j, h3d.anim.Skin.DynamicJoint);
+			if (dynJ == null)
+				continue;
 
-	function getDefaultLodConfig( dir : String ) : Array<Float> {
-		var fs = Std.downcast(hxd.res.Loader.currentInstance.fs, hxd.fs.LocalFileSystem);
-		if (fs == null)
-			return baseLodConfig;
-
-		#if (sys || nodejs)
-			var c = @:privateAccess fs.convert.getConfig(defaultLodConfigs, baseLodConfig, dir, function(fullObj) {
-				if (Reflect.hasField(fullObj, "lods.screenRatio"))
-					return Reflect.field(fullObj, "lods.screenRatio");
+			dynamicJoints.push({ name: dynJ.name, slackness: dynJ.slackness, stiffness: dynJ.stiffness, resistance: dynJ.resistance, damping: dynJ.damping, globalForce: dynJ.globalForce });
+		}
 
-				return baseLodConfig;
-			});
-			return c;
-		#else
-			return baseLodConfig;
-		#end
+		if (dynamicJoints.length == 0) {
+			Reflect.deleteField(data, DYN_BONES_CONFIG);
+			return;
+		}
 
+		Reflect.setField(data, DYN_BONES_CONFIG, dynamicJoints);
 	}
 
-	// Used to clean previous version of modelDatabase, should be removed after some time
-	function cleanOldModelData( rootData : Dynamic, key : String) {
-		var oldLodConfig = Reflect.field(rootData, LOD_CONFIG);
-		if (oldLodConfig != null) {
-			for (f in Reflect.fields(oldLodConfig)) {
-				if (key.indexOf(f) < 0)
-					continue;
 
-				var c = Reflect.field(oldLodConfig, f);
+	public function loadModelProps( input : ModelDataInput ) {
+		var data : Dynamic = getModelData(input.resourceDirectory, input.resourceName, input.objectName);
+		if (data == null)
+			return;
 
-				var newData = { "lodConfig" : c };
-				Reflect.setField(rootData, key, newData);
+		loadLodConfig(input, data);
+		loadDynamicBonesConfig(input, data);
+	}
 
-				// Remove old entry
-				Reflect.deleteField(oldLodConfig, f);
-				Reflect.setField(rootData, LOD_CONFIG, oldLodConfig);
-			}
+	public function saveModelProps( input : ModelDataInput ) {
+		var data : Dynamic = getModelData(input.resourceDirectory, input.resourceName, input.objectName);
+		if( data == null )
+			data = {};
 
-			if (oldLodConfig == null || Reflect.fields(oldLodConfig).length == 0)
-				Reflect.deleteField(rootData, LOD_CONFIG);
-		}
+		saveLodConfig(input, data);
+		saveDynamicBonesConfig(input, data);
+
+		saveModelData(input.resourceDirectory, input.resourceName, input.objectName, data);
 	}
 
 	public static var current = new ModelDatabase();

+ 101 - 1
h3d/scene/Skin.hx

@@ -1,5 +1,8 @@
 package h3d.scene;
 
+import haxe.Timer;
+import h3d.anim.Skin.DynamicJoint;
+
 class Joint extends Object {
 	public var skin : Skin;
 	public var index : Int;
@@ -245,6 +248,7 @@ class Skin extends MultiMaterial {
 		if( !ctx.visibleFlag && !alwaysSyncAnimation )
 			return;
 		syncJoints();
+		syncDynamicJoints();
 	}
 
 	static var TMP_MAT = new h3d.Matrix();
@@ -259,7 +263,18 @@ class Skin extends MultiMaterial {
 			var m = currentAbsPose[id];
 			var r = currentRelPose[id];
 			var bid = j.bindIndex;
-			if( r == null ) r = j.defMat else if( j.retargetAnim && enableRetargeting ) { tmpMat.load(r); r = tmpMat; r._41 = j.defMat._41; r._42 = j.defMat._42; r._43 = j.defMat._43; }
+			if( r == null )
+				r = j.defMat
+			else if( j.retargetAnim && enableRetargeting ) {
+				tmpMat.load(r);
+				r = tmpMat;
+				r._41 = j.defMat._41;
+				r._42 = j.defMat._42;
+				r._43 = j.defMat._43;
+			}
+			var dyn = Std.downcast(j, DynamicJoint);
+			if (dyn != null && dyn.relPos == null)
+				dyn.relPos = r;
 			if( j.parent == null )
 				m.multiply3x4inline(r, absPos);
 			else
@@ -271,14 +286,99 @@ class Skin extends MultiMaterial {
 			if( bid >= 0 )
 				currentPalette[bid].multiply3x4inline(j.transPos, m);
 		}
+
 		skinShader.bonesMatrixes = currentPalette;
 		jointsUpdated = false;
 		prevEnableRetargeting = enableRetargeting;
 	}
 
+	function syncDynamicJoints() {
+		// Update dynamic joints
+		for( j in skinData.allJoints ) {
+			if ( j.follow != null ) continue;
+			var dynJoint = Std.downcast(j, h3d.anim.Skin.DynamicJoint);
+			if (dynJoint == null) continue;
+
+			var absPos = dynJoint.absPos == null ? currentAbsPose[dynJoint.index] : dynJoint.absPos;
+			var newWorldPos = absPos.getPosition().clone();
+			var expectedPos = absPos.getPosition().clone();
+
+			// Resistance (force resistance)
+			var globalForce = dynJoint.globalForce;
+			dynJoint.speed += globalForce * (1.0 - dynJoint.resistance);
+
+			// Damping (inertia attenuation)
+			dynJoint.speed *= 1.0 - dynJoint.damping;
+			if (dynJoint.speed.lengthSq() > DynamicJoint.SLEEP_THRESHOLD)
+				newWorldPos += dynJoint.speed * hxd.Timer.dt;
+
+			// Stiffness (shape keeper)
+			var parentMovement = currentAbsPose[j.parent.index].getPosition() - currentAbsPose[dynJoint.parent.index].getPosition();
+            expectedPos = dynJoint.relPos.multiplied(currentAbsPose[dynJoint.parent.index]).getPosition() + parentMovement;
+            newWorldPos.lerp(newWorldPos, expectedPos, dynJoint.stiffness);
+
+			// Slackness (length keeper)
+			var dirToParent = (newWorldPos - currentAbsPose[j.parent.index].getPosition()).normalized();
+            var lengthToParent = dynJoint.relPos.getPosition().length();
+            expectedPos = currentAbsPose[j.parent.index].getPosition() + dirToParent * lengthToParent;
+			newWorldPos.lerp(expectedPos, newWorldPos, dynJoint.slackness);
+
+			// Apply computed position to joint
+			dynJoint.speed = (dynJoint.speed + (newWorldPos - absPos.getPosition()) * (1.0 / hxd.Timer.dt)) * 0.5;
+			currentAbsPose[j.index].setPosition(newWorldPos);
+			dynJoint.absPos = currentAbsPose[dynJoint.index].clone();
+		}
+
+
+		function recomputeAbsPos(dynJoint : DynamicJoint) {
+			var m = currentAbsPose[dynJoint.index];
+			m.multiply3x4inline(dynJoint.relPos, currentAbsPose[dynJoint.parent.index]);
+
+			if (dynJoint.subs == null)
+				return;
+
+			for (s in dynJoint.subs)
+				recomputeAbsPos(cast s);
+		}
+
+		for( j in skinData.allJoints ) {
+			if ( j.follow != null ) continue;
+
+			var dynJoint = Std.downcast(j, h3d.anim.Skin.DynamicJoint);
+			if (dynJoint == null) continue;
+			if (dynJoint.subs.length == 1) {
+				var child = Std.downcast(dynJoint.subs[0], DynamicJoint);
+
+				var q = new Quat();
+				var offset = child.absPos.getPosition() - dynJoint.absPos.getPosition();
+				offset.transform3x3(currentAbsPose[dynJoint.index].getInverse());
+				q.initMoveTo(child.relPos.getPosition().normalized(), offset.normalized());
+
+				currentAbsPose[dynJoint.index].multiply(q.toMatrix(), currentAbsPose[dynJoint.index]);
+
+				if (dynJoint.subs != null) {
+					for (s in dynJoint.subs)
+						recomputeAbsPos(cast s);
+				}
+
+				if( dynJoint.bindIndex >= 0 )
+					currentPalette[dynJoint.bindIndex].multiply3x4inline(j.transPos, currentAbsPose[dynJoint.index]);
+
+				currentAbsPose[dynJoint.index].load(dynJoint.absPos);
+			}
+			else {
+				if( dynJoint.bindIndex >= 0 )
+					currentPalette[dynJoint.bindIndex].multiply3x4inline(j.transPos, currentAbsPose[dynJoint.index]);
+			}
+		}
+
+		skinShader.bonesMatrixes = currentPalette;
+	}
+
 	override function emit( ctx : RenderContext ) {
 		calcScreenRatio(ctx);
 		syncJoints(); // In case sync was not called because of culling (eg fixedPosition)
+		syncDynamicJoints();
 		if( splitPalette == null )
 			super.emit(ctx);
 		else {

+ 12 - 3
hxd/fmt/hmd/Library.hx

@@ -300,9 +300,6 @@ class Library {
 		p.incref(); // Prevent from auto-disposing
 		cachedPrimitives[id] = p;
 
-		if ( hasLod )
-			h3d.prim.ModelDatabase.current.loadModelProps(model.name, p);
-
 		return p;
 	}
 
@@ -352,6 +349,7 @@ class Library {
 		s.allJoints = [];
 		s.boundJoints = [];
 		s.rootJoints = [];
+
 		for( joint in skin.joints ) {
 			var j = new h3d.anim.Skin.Joint();
 			j.name = joint.name;
@@ -493,7 +491,18 @@ class Library {
 			objs.push(obj);
 			var p = objs[m.parent];
 			if( p != null ) p.addChild(obj);
+
+			var modelData : h3d.prim.ModelDatabase.ModelDataInput = {
+				resourceDirectory : resource.entry.directory,
+				resourceName : resource.name,
+				objectName : obj.name,
+				hmd : Std.downcast(Std.downcast(obj, h3d.scene.Mesh)?.primitive, h3d.prim.HMDModel),
+				skin : Std.downcast(obj, h3d.scene.Skin)
+			}
+
+			h3d.prim.ModelDatabase.current.loadModelProps(modelData);
 		}
+
 		var o = objs[0];
 		if( o != null ) o.modelRoot = true;
 		return o;