forked from LeenkxTeam/LNXSDK
		
	
		
			
				
	
	
		
			1169 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
			
		
		
	
	
			1169 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
| package iron.object;
 | |
| 
 | |
| import iron.object.Animation.ActionSampler;
 | |
| #if lnx_skin
 | |
| 
 | |
| import kha.FastFloat;
 | |
| import kha.arrays.Float32Array;
 | |
| import iron.math.Vec4;
 | |
| import iron.math.Mat4;
 | |
| import iron.math.Quat;
 | |
| import iron.data.MeshData;
 | |
| import iron.data.SceneFormat;
 | |
| import iron.data.Armature;
 | |
| import iron.data.Data;
 | |
| import iron.math.Ray;
 | |
| 
 | |
| class BoneAnimation extends Animation {
 | |
| 
 | |
| 	public static var skinMaxBones = 128;
 | |
| 
 | |
| 	// Skinning
 | |
| 	public var object: MeshObject;
 | |
| 	public var armatureObject: Object;
 | |
| 	public var data: MeshData;
 | |
| 	public var skinBuffer: Float32Array;
 | |
| 
 | |
| 	var updateAnimation: Array<Mat4>->Void = null;
 | |
| 
 | |
| 	public var skeletonBones(default, null): Array<TObj> = null;
 | |
| 	public var skeletonMats(default, null): Array<Mat4> = null;
 | |
| 	//var skeletonBonesBlend: Array<TObj> = null;
 | |
| 	var absMats: Array<Mat4> = null;
 | |
| 	var applyParent: Array<Bool> = null;
 | |
| 	var matsFast: Array<Mat4> = [];
 | |
| 	var matsFastSort: Array<Int> = [];
 | |
| 	var matsFastBlend: Array<Mat4> = [];
 | |
| 	var matsFastBlendSort: Array<Int> = [];
 | |
| 
 | |
| 	var rootMotionCacheInit: Bool = false;
 | |
| 	public var rootMotion(default, null): TObj = null;
 | |
| 	public var rootMotionVelocity(default, null): Vec4 = null;
 | |
| 	public var rootMotionRotation(default, null): Quat = null;
 | |
| 	var rootMotionIndex: Int = -1;
 | |
| 	var rootMotionLockX: Bool = false;
 | |
| 	var rootMotionLockY: Bool = false;
 | |
| 	var rootMotionLockZ: Bool = false;
 | |
| 
 | |
| 	var delta: FastFloat = 0;
 | |
| 
 | |
| 	var boneChildren: Map<String, Array<Object>> = null; // Parented to bone
 | |
| 
 | |
| 	var constraintTargets: Array<Object> = null;
 | |
| 	var constraintTargetsI: Array<Mat4> = null;
 | |
| 	var constraintMats: Map<TObj, Mat4> = null;
 | |
| 	var relativeBoneConstraints: Bool = false;
 | |
| 
 | |
| 	static var m = Mat4.identity(); // Skinning matrix
 | |
| 	static var m1 = Mat4.identity();
 | |
| 	static var m2 = Mat4.identity();
 | |
| 	static var bm = Mat4.identity(); // Absolute bone matrix
 | |
| 	static var wm = Mat4.identity();
 | |
| 	static var vpos = new Vec4();
 | |
| 	static var vpos2 = new Vec4();
 | |
| 	static var vpos3 = new Vec4();
 | |
| 	static var vscl = new Vec4();
 | |
| 	static var vscl2 = new Vec4();
 | |
| 	static var vscl3 = new Vec4();
 | |
| 	static var q1 = new Quat();
 | |
| 	static var q2 = new Quat();
 | |
| 	static var q3 = new Quat();
 | |
| 	static var q4 = new Quat();
 | |
| 	static var v1 = new Vec4();
 | |
| 	static var v2 = new Vec4();
 | |
| 
 | |
| 	public function new(armatureUid: Int, armatureObject: Object) {
 | |
| 		super();
 | |
| 		this.isSampled = false;
 | |
| 		this.armatureObject = armatureObject;
 | |
| 		for (a in Scene.active.armatures) {
 | |
| 			if (a.uid == armatureUid) {
 | |
| 				this.armature = a;
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public function initMatsEmpty(): Array<Mat4> {
 | |
| 
 | |
| 		var mats = [];
 | |
| 		for(i in 0...skeletonMats.length) mats.push(Mat4.identity());
 | |
| 		return mats;
 | |
| 	}
 | |
| 	
 | |
| 	public inline function getNumBones(): Int {
 | |
| 		if (skeletonBones == null) return 0;
 | |
| 		return skeletonBones.length;
 | |
| 	}
 | |
| 
 | |
| 	public function setSkin(mo: MeshObject) {
 | |
| 		this.object = mo;
 | |
| 		this.data = mo != null ? mo.data : null;
 | |
| 		this.isSkinned = data != null ? data.isSkinned : false;
 | |
| 		if (this.isSkinned) {
 | |
| 			var boneSize = 12; // Dual-quat skinning + scaling
 | |
| 			this.skinBuffer = new Float32Array(skinMaxBones * boneSize);
 | |
| 			for (i in 0...this.skinBuffer.length) this.skinBuffer[i] = 0;
 | |
| 			// Rotation is already applied to skin at export
 | |
| 			object.transform.rot.set(0, 0, 0, 1);
 | |
| 			object.transform.buildMatrix();
 | |
| 
 | |
| 			var refs = mo.parent.raw.bone_actions;
 | |
| 			if (refs != null && refs.length > 0) {
 | |
| 				Data.getSceneRaw(refs[0], function(action: TSceneFormat) { play(action.name); });
 | |
| 			}
 | |
| 		}
 | |
| 		if (armatureObject.raw.relative_bone_constraints) relativeBoneConstraints = true;
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	public function addBoneChild(bone: String, o: Object) {
 | |
| 		if (boneChildren == null) boneChildren = new Map();
 | |
| 		var ar = boneChildren.get(bone);
 | |
| 		if (ar == null) {
 | |
| 			ar = [];
 | |
| 			boneChildren.set(bone, ar);
 | |
| 		}
 | |
| 		ar.push(o);
 | |
| 	}
 | |
| 	
 | |
| 	public function removeBoneChild(bone: String, o: Object) {
 | |
| 		if (boneChildren != null) {
 | |
| 			var ar = boneChildren.get(bone);
 | |
| 			if (ar != null) ar.remove(o);
 | |
| 		}
 | |
| 	}
 | |
|  
 | |
| 	@:access(iron.object.Transform)
 | |
| 	function updateBoneChildren(bone: TObj, bm: Mat4) {
 | |
| 		var ar = boneChildren.get(bone.name);
 | |
| 		if (ar == null) return;
 | |
| 		for (o in ar) {
 | |
| 			var t = o.transform;
 | |
| 			if (t.boneParent == null) t.boneParent = Mat4.identity();
 | |
| 			if (o.raw.parent_bone_tail != null) {
 | |
| 				if (o.raw.parent_bone_connected || isSkinned) {
 | |
| 					var v = o.raw.parent_bone_tail;
 | |
| 					t.boneParent.initTranslate(v[0], v[1], v[2]);
 | |
| 					t.boneParent.multmat(bm);
 | |
| 				}
 | |
| 				else {
 | |
| 					var v = o.raw.parent_bone_tail_pose;
 | |
| 					t.boneParent.setFrom(bm);
 | |
| 					t.boneParent.translate(v[0], v[1], v[2]);
 | |
| 				}
 | |
| 			}
 | |
| 			else t.boneParent.setFrom(bm);
 | |
| 			t.buildMatrix();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	function numParents(b: TObj): Int {
 | |
| 		var i = 0;
 | |
| 		var p = b.parent;
 | |
| 		while (p != null) {
 | |
| 			i++;
 | |
| 			p = p.parent;
 | |
| 		}
 | |
| 		return i;
 | |
| 	}
 | |
| 
 | |
| 	function setMats() {
 | |
| 		while (matsFast.length < skeletonBones.length) {
 | |
| 			matsFast.push(Mat4.identity());
 | |
| 			matsFastSort.push(matsFastSort.length);
 | |
| 		}
 | |
| 		// Calc bones with 0 parents first
 | |
| 		matsFastSort.sort(function(a, b) {
 | |
| 			var i = numParents(skeletonBones[a]);
 | |
| 			var j = numParents(skeletonBones[b]);
 | |
| 			return i < j ? -1 : i > j ? 1 : 0;
 | |
| 		});
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	function setAction(action: String) {
 | |
| 		armature.initMats();
 | |
| 		var a = armature.getAction(action);
 | |
| 		skeletonBones = a.bones;
 | |
| 		skeletonMats = a.mats;
 | |
| 		if(! rootMotionCacheInit) skeletonMats.push(Mat4.identity());
 | |
| 		rootMotionCacheInit = true;
 | |
| 		setMats();
 | |
| 	}
 | |
| 
 | |
| 	function getAction(action: String): Array<TObj> {
 | |
| 		armature.initMats();
 | |
| 		return armature.getAction(action).bones;
 | |
| 	}
 | |
| 
 | |
| 	function multParent(i: Int, fasts: Array<Mat4>, bones: Array<TObj>, mats: Array<Mat4>) {
 | |
| 		var f = fasts[i];
 | |
| 		if (applyParent != null && !applyParent[i]) {
 | |
| 			f.setFrom(mats[i]);
 | |
| 			return;
 | |
| 		}
 | |
| 		var p = bones[i].parent;
 | |
| 		var bi = getBoneIndex(p, bones);
 | |
| 		(p == null || bi == -1) ? f.setFrom(mats[i]) : f.multmats(fasts[bi], mats[i]);
 | |
| 	}
 | |
| 
 | |
| 	inline function multVecs(vec1: Vec4, vec2: Vec4): Vec4 {
 | |
| 		var res = new Vec4().setFrom(vec1);
 | |
| 		res.x *= vec2.x;
 | |
| 		res.y *= vec2.y;
 | |
| 		res.z *= vec2.z;
 | |
| 		res.w *= vec2.w;
 | |
| 
 | |
| 		return res;
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	// Do animation here
 | |
| 	public function setAnimationLoop(f: Array<Mat4>->Void) {
 | |
| 		updateAnimation = f;
 | |
| 	}
 | |
| 
 | |
| 	override public function play(action = "", onComplete: Void->Void = null, blendTime = 0.2, speed = 1.0, loop = true) {
 | |
| 		super.play(action, onComplete, blendTime, speed, loop);
 | |
| 		if (action != "") {
 | |
| 			setAction(action);
 | |
| 			var tempAnimParam = new ActionSampler(action);
 | |
| 			registerAction("tempAction", tempAnimParam);
 | |
| 			updateAnimation = function(mats){
 | |
| 				sampleAction(tempAnimParam, mats);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	override public function update(delta: FastFloat) {
 | |
| 		this.delta = delta;
 | |
| 		if (!isSkinned && skeletonBones == null) setAction(armature.actions[0].name);
 | |
| 		if (object != null && (!object.visible || object.culled)) return;
 | |
| 		if (skeletonBones == null || skeletonBones.length == 0) return;
 | |
| 
 | |
| 		#if lnx_debug
 | |
| 		Animation.beginProfile();
 | |
| 		#end
 | |
| 
 | |
| 		super.update(delta);
 | |
| 		if(updateAnimation != null) {
 | |
| 			
 | |
| 			updateAnimation(skeletonMats);
 | |
| 		}
 | |
| 
 | |
| 		updateConstraints();
 | |
| 		// Do forward kinematics and inverse kinematics here
 | |
| 		if (onUpdates != null) {
 | |
| 			var i = 0;
 | |
| 			var l = onUpdates.length;
 | |
| 			while (i < l) {
 | |
| 				onUpdates[i]();
 | |
| 				l <= onUpdates.length ? i++ : l = onUpdates.length;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Calc absolute bones
 | |
| 		for (i in 0...skeletonBones.length) {
 | |
| 			// Take bones with 0 parents first
 | |
| 			multParent(matsFastSort[i], matsFast, skeletonBones, skeletonMats);
 | |
| 		}
 | |
| 		if (isSkinned) updateSkinGpu();
 | |
| 		else updateBonesOnly();
 | |
| 
 | |
| 		#if lnx_debug
 | |
| 		Animation.endProfile();
 | |
| 		#end
 | |
| 	}
 | |
| 
 | |
| 	public function evaluateRootMotion(actionMats: Array<Mat4>): Vec4{
 | |
| 		if(rootMotionIndex < 0) return new Vec4();
 | |
| 		var scl = armatureObject.transform.scale;
 | |
| 		wm = getRootMotionWorldMat(actionMats, rootMotion);
 | |
| 		wm.decompose(vpos, q1, vscl);
 | |
| 		vpos = multVecs(vpos, scl);
 | |
| 		rootMotionVelocity.setFrom(vpos);
 | |
| 		rootMotionRotation.setFrom(q1);
 | |
| 		return new Vec4().setFrom(rootMotionVelocity);
 | |
| 		
 | |
| 	}
 | |
| 
 | |
| 	public function setRootMotion(bone: TObj, lockX: Bool = false, lockY: Bool = false, lockZ: Bool = false) {
 | |
| 		rootMotion = bone;
 | |
| 		rootMotionIndex = getBoneIndex(rootMotion);
 | |
| 		rootMotionLockX	= lockX;
 | |
| 		rootMotionLockY	= lockY;
 | |
| 		rootMotionLockZ	= lockZ;
 | |
| 		rootMotionVelocity = new Vec4();
 | |
| 		rootMotionRotation = new Quat();
 | |
| 	}
 | |
| 
 | |
| 	function multParents(m: Mat4, i: Int, bones: Array<TObj>, mats: Array<Mat4>) {
 | |
| 		var bone = bones[i];
 | |
| 		var p = bone.parent;
 | |
| 		while (p != null) {
 | |
| 			var i = getBoneIndex(p, bones);
 | |
| 			if (i == -1) continue;
 | |
| 			m.multmat(mats[i]);
 | |
| 			p = p.parent;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	function getConstraintsFromScene(cs: Array<TConstraint>) {
 | |
| 		// Init constraints
 | |
| 		if (constraintTargets == null) {
 | |
| 			constraintTargets = [];
 | |
| 			constraintTargetsI = [];
 | |
| 			for (c in cs) {
 | |
| 				var o = Scene.active.getChild(c.target);
 | |
| 				constraintTargets.push(o);
 | |
| 				var m: Mat4 = null;
 | |
| 				if (o != null) {
 | |
| 					m = Mat4.identity().setFrom(o.transform.world);
 | |
| 					m.getInverse(m);
 | |
| 				}
 | |
| 				constraintTargetsI.push(m);
 | |
| 			}
 | |
| 			constraintMats = new Map();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	function getConstraintsFromParentRelative(cs: Array<TConstraint>) {
 | |
| 		// Init constraints
 | |
| 		if (constraintTargets == null) {
 | |
| 			constraintTargets = [];
 | |
| 			constraintTargetsI = [];
 | |
| 			// MeshObject -> ArmatureObject -> Collection/Empty
 | |
| 			var conParent = armatureObject.parent;
 | |
| 			if (conParent == null) return;
 | |
| 			for (c in cs) {
 | |
| 				var o = conParent.getChild(c.target);
 | |
| 				constraintTargets.push(o);
 | |
| 				var m: Mat4 = null;
 | |
| 				if (o != null) {
 | |
| 					m = Mat4.identity().setFrom(o.transform.world);
 | |
| 					m.getInverse(m);
 | |
| 				}
 | |
| 				constraintTargetsI.push(m);
 | |
| 			}
 | |
| 			constraintMats = new Map();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	function updateConstraints() {
 | |
| 		if (data == null) return;
 | |
| 		var cs = data.raw.skin.constraints;
 | |
| 		if (cs == null) return;
 | |
| 		if (relativeBoneConstraints) {
 | |
| 			getConstraintsFromParentRelative(cs);
 | |
| 		}
 | |
| 		else {
 | |
| 			getConstraintsFromScene(cs);
 | |
| 		}
 | |
| 		// Update matrices
 | |
| 		for (i in 0...cs.length) {
 | |
| 			var c = cs[i];
 | |
| 			var bone = getBone(c.bone);
 | |
| 			if (bone == null) continue;
 | |
| 			var o = constraintTargets[i];
 | |
| 			if (o == null) continue;
 | |
| 			if (c.type == "CHILD_OF") {
 | |
| 				var m = constraintMats.get(bone);
 | |
| 				if (m == null) {
 | |
| 					m = Mat4.identity();
 | |
| 					constraintMats.set(bone, m);
 | |
| 				}
 | |
| 				m.setFrom(armatureObject.transform.world); // Armature transform
 | |
| 				m.multmat(constraintTargetsI[i]); // Roll back initial hitbox transform
 | |
| 				m.multmat(o.transform.world); // Current hitbox transform
 | |
| 				m1.getInverse(armatureObject.transform.world); // Roll back armature transform
 | |
| 				m.multmat(m1);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	var onUpdates: Array<Void->Void> = null;
 | |
| 	public function notifyOnUpdate(f: Void->Void) {
 | |
| 		if (onUpdates == null) onUpdates = [];
 | |
| 		onUpdates.push(f);
 | |
| 	}
 | |
| 
 | |
| 	public function removeUpdate(f: Void->Void) {
 | |
| 		onUpdates.remove(f);
 | |
| 	}
 | |
| 
 | |
| 	override public function updateActionTrack(sampler: ActionSampler) {
 | |
| 		if(sampler.paused) return;
 | |
| 
 | |
| 		if(! sampler.actionDataInit) {
 | |
| 			var bones = getAction(sampler.action);
 | |
| 			sampler.setBoneAction(bones);
 | |
| 		}
 | |
| 		
 | |
| 		var bones = sampler.getBoneAction();
 | |
| 		for(b in bones){
 | |
| 			if (b.anim != null) {
 | |
| 				updateTrack(b.anim, sampler);
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public function sampleAction(sampler: ActionSampler, actionMats: Array<Mat4>) {
 | |
| 
 | |
| 		if(! sampler.actionDataInit) {
 | |
| 			var bones = getAction(sampler.action);
 | |
| 			sampler.setBoneAction(bones);
 | |
| 		}
 | |
| 		
 | |
| 		var bones = sampler.getBoneAction();
 | |
| 		actionMats[skeletonBones.length].setIdentity();
 | |
| 		var rootMotionEnabled = sampler.rootMotionPos || sampler.rootMotionRot;
 | |
| 		for (i in 0...bones.length) {
 | |
| 			if (i == rootMotionIndex && rootMotionEnabled){
 | |
| 				updateAnimSampledRootMotion(bones[i].anim, actionMats[i], actionMats[skeletonBones.length], sampler);
 | |
| 			}
 | |
| 			else {
 | |
| 				updateAnimSampled(bones[i].anim, actionMats[i], sampler);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	function updateAnimSampled(anim: TAnimation, mm: Mat4, sampler: ActionSampler) {
 | |
| 
 | |
| 		if(anim == null) return;
 | |
| 		var track = anim.tracks[0];
 | |
| 		var sign = sampler.speed > 0 ? 1 : -1;
 | |
| 
 | |
| 		var t = sampler.time;
 | |
| 		//t = t < 0 ? 0.1 : t;
 | |
| 
 | |
| 		var ti = sampler.offset;
 | |
| 		//ti = ti < 0 ? 1 : ti;
 | |
| 
 | |
| 		interpolateSample(track, mm, t, ti, sign);
 | |
| 	}
 | |
| 
 | |
| 	function updateAnimSampledRootMotion(anim: TAnimation, mm: Mat4, rm: Mat4, sampler: ActionSampler) {
 | |
| 		if(anim == null) return;
 | |
| 		var track = anim.tracks[0];
 | |
| 		var sign = sampler.speed > 0 ? 1 : -1;
 | |
| 
 | |
| 		var t0 = speed > 0 ? 0 : track.frames.length - 1;
 | |
| 		var t = sampler.time;
 | |
| 		var ti = sampler.offset;
 | |
| 
 | |
| 		if(sampler.trackEnd || !sampler.cacheInit) {
 | |
| 			interpolateSample(track, bm, t, ti, sign);
 | |
| 			//bm.setF32(track.values, t0);
 | |
| 			sampler.setActionCache(bm);
 | |
| 		}
 | |
| 
 | |
| 		// Interpolated action for current frame
 | |
| 		interpolateSample(track, m1, t, ti, sign);
 | |
| 		// Action at first frame
 | |
| 		m.setF32(track.values, t0);
 | |
| 		// Action at previous frame
 | |
| 		sampler.getActionCache(m2);
 | |
| 
 | |
| 		m.decompose(vpos, q1, vscl);
 | |
| 		m1.decompose(vpos2, q2, vscl2);
 | |
| 		m2.decompose(vpos3, q3, vscl3);
 | |
| 
 | |
| 		// Compose
 | |
| 		if(sampler.rootMotionRot) {
 | |
| 			// Bone matrix
 | |
| 			mm.fromQuat(q1);
 | |
| 			mm.scale(vscl2);
 | |
| 			// Root motion matrix
 | |
| 			q2.mult(q4.inverse(q3));
 | |
| 			rm.fromQuat(q2);
 | |
| 		}
 | |
| 		else {
 | |
| 			// Bone matrix
 | |
| 			mm.fromQuat(q2);
 | |
| 			mm.scale(vscl2);
 | |
| 		}
 | |
| 
 | |
| 		if(sampler.rootMotionPos) {
 | |
| 			// Bone matrix
 | |
| 			mm._30 = vpos.x;
 | |
| 			mm._31 = vpos.y;
 | |
| 			mm._32 = vpos.z;
 | |
| 			// Root motion matrix
 | |
| 			rm._30 = vpos2.x - vpos3.x;
 | |
| 			rm._31 = vpos2.y - vpos3.y;
 | |
| 			rm._32 = vpos2.z - vpos3.z;
 | |
| 		}
 | |
| 		else {
 | |
| 			// Bone matrix
 | |
| 			mm._30 = vpos2.x;
 | |
| 			mm._31 = vpos2.y;
 | |
| 			mm._32 = vpos2.z;
 | |
| 		}
 | |
| 
 | |
| 		sampler.setActionCache(m1);
 | |
| 	}
 | |
| 
 | |
| 
 | |
| 	inline function interpolateSample(track: TTrack, m: Mat4, t: FastFloat, ti: Int, sign: Int) {
 | |
| 		var t1 = track.frames[ti] * frameTime;
 | |
| 		var t2 = track.frames[ti + sign] * frameTime;
 | |
| 		var s: FastFloat = (t - t1) / (t2 - t1); // Linear
 | |
| 
 | |
| 		m1.setF32(track.values, ti * 16); // Offset to 4x4 matrix array
 | |
| 		m2.setF32(track.values, (ti + sign) * 16);
 | |
| 
 | |
| 		// Decompose
 | |
| 		m1.decompose(vpos, q1, vscl);
 | |
| 		m2.decompose(vpos2, q2, vscl2);
 | |
| 
 | |
| 		// Lerp
 | |
| 		v1.lerp(vpos, vpos2, s);
 | |
| 		v2.lerp(vscl, vscl2, s);
 | |
| 		q3.lerp(q1, q2, s);
 | |
| 
 | |
| 		// Compose
 | |
| 		m.fromQuat(q3);
 | |
| 		m.scale(v2);
 | |
| 		m._30 = v1.x;
 | |
| 		m._31 = v1.y;
 | |
| 		m._32 = v1.z;
 | |
| 	}
 | |
| 
 | |
| 	function updateBonesOnly() {
 | |
| 		if (boneChildren != null) {
 | |
| 			for (i in 0...skeletonBones.length) {
 | |
| 				var b = skeletonBones[i]; // TODO: blendTime > 0
 | |
| 				m.setFrom(matsFast[i]);
 | |
| 				updateBoneChildren(b, m);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	function updateSkinGpu() {
 | |
| 		var bones = skeletonBones;
 | |
| 
 | |
| 		// Update skin buffer
 | |
| 		for (i in 0...bones.length) {
 | |
| 			if (constraintMats != null) {
 | |
| 				var m = constraintMats.get(bones[i]);
 | |
| 				if (m != null) {
 | |
| 					updateSkinBuffer(m, i);
 | |
| 					continue;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			m.setFrom(matsFast[i]);
 | |
| 
 | |
| 			if (absMats != null && i < absMats.length) absMats[i].setFrom(m);
 | |
| 			if (boneChildren != null) updateBoneChildren(bones[i], m);
 | |
| 
 | |
| 			m.multmats(m, data.geom.skeletonTransformsI[i]);
 | |
| 			updateSkinBuffer(m, i);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	function updateSkinBuffer(m: Mat4, i: Int) {
 | |
| 		// Dual quat skinning
 | |
| 		m.decompose(vpos, q1, vscl);
 | |
| 		q1.normalize();
 | |
| 		q2.set(vpos.x, vpos.y, vpos.z, 0.0);
 | |
| 		q2.multquats(q2, q1);
 | |
| 		skinBuffer[i * 12] = q1.x; // Real
 | |
| 		skinBuffer[i * 12 + 1] = q1.y;
 | |
| 		skinBuffer[i * 12 + 2] = q1.z;
 | |
| 		skinBuffer[i * 12 + 3] = q1.w;
 | |
| 		skinBuffer[i * 12 + 4] = q2.x * 0.5; // Dual
 | |
| 		skinBuffer[i * 12 + 5] = q2.y * 0.5;
 | |
| 		skinBuffer[i * 12 + 6] = q2.z * 0.5;
 | |
| 		skinBuffer[i * 12 + 7] = q2.w * 0.5;
 | |
| 		skinBuffer[i * 12 + 8] = vscl.x;
 | |
| 		skinBuffer[i * 12 + 9] = vscl.y;
 | |
| 		skinBuffer[i * 12 + 10] = vscl.z;
 | |
| 		skinBuffer[i * 12 + 11] = 1.0;
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	public override function getTotalFrames(sampler: ActionSampler): Int {
 | |
| 		var bones = getAction(sampler.action);
 | |
| 		var track = bones[0].anim.tracks[0];
 | |
| 		return Std.int(track.frames[track.frames.length - 1] - track.frames[0]);
 | |
| 	}
 | |
| 
 | |
| 	public function getBone(name: String): TObj {
 | |
| 		if (skeletonBones == null) return null;
 | |
| 		for (b in skeletonBones) if (b.name == name) return b;
 | |
| 		return null;
 | |
| 	}
 | |
| 
 | |
| 	function getBoneIndex(bone: TObj, bones: Array<TObj> = null): Int {
 | |
| 		if (bones == null) bones = skeletonBones;
 | |
| 		if (bones != null) for (i in 0...bones.length) if (bones[i] == bone) return i;
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	public function getBoneMat(actionMats: Array<Mat4>, bone: TObj): Mat4 {
 | |
| 		return actionMats != null ? actionMats[getBoneIndex(bone)] : null;
 | |
| 	}
 | |
| 
 | |
| 	function getWorldMat(actionMats: Array<Mat4>, bone: TObj): Mat4 {
 | |
| 		if (applyParent == null) {
 | |
| 			applyParent = [];
 | |
| 			for (m in actionMats) applyParent.push(true);
 | |
| 		}
 | |
| 		var i = getBoneIndex(bone);
 | |
| 		wm.setFrom(actionMats[i]);
 | |
| 		multParents(wm, i, skeletonBones, actionMats);
 | |
| 		return wm;
 | |
| 	}
 | |
| 
 | |
| 	function getRootMotionWorldMat(actionMats: Array<Mat4>, bone: TObj): Mat4 {
 | |
| 		// Get root motion bone index
 | |
| 		var i = getBoneIndex(bone);
 | |
| 		// Store current bone matrix in temp
 | |
| 		var tempMat = Mat4.identity().setFrom(actionMats[i]);
 | |
| 		// Move root motion cache to bone position
 | |
| 		actionMats[i].setFrom(actionMats[skeletonBones.length]);
 | |
| 		// Calculate world matrix
 | |
| 		wm.setFrom(getWorldMat(actionMats, bone));
 | |
| 		// Revert to old value
 | |
| 		actionMats[i].setFrom(tempMat);
 | |
| 		return wm;
 | |
| 	}
 | |
| 
 | |
| 	// Returns bone matrix in world space
 | |
| 	public function getAbsWorldMat(actionMats: Array<Mat4>, bone: TObj): Mat4 {
 | |
| 		var wm = getWorldMat(actionMats, bone);
 | |
| 		wm.multmat(armatureObject.transform.world);
 | |
| 		return wm;
 | |
| 	}
 | |
| 
 | |
| 	// Returns an array of bone matrices in world space
 | |
| 	public function getWorldMatsFast(actionMats: Array<Mat4>, tip: TObj, chainLength: Int): Array<Mat4> {
 | |
| 		var wmArray: Array<Mat4> = [];
 | |
| 		var root = tip;
 | |
| 		var numP = chainLength;
 | |
| 		for (i in 0...chainLength) {
 | |
| 			var wm = getAbsWorldMat(actionMats, root);
 | |
| 			wmArray[chainLength - 1 - i] = wm.clone();
 | |
| 			root = root.parent;
 | |
| 			numP--;
 | |
| 		}
 | |
| 
 | |
| 		// Root bone at [0]
 | |
| 		return wmArray;
 | |
| 	}
 | |
| 
 | |
| 	// Set bone transforms in world space
 | |
| 	public function setBoneMatFromWorldMat(actionMats: Array<Mat4>, wm: Mat4, bone: TObj) {
 | |
| 		var invMat = Mat4.identity();
 | |
| 		var tempMat = wm.clone();
 | |
| 		invMat.getInverse(armatureObject.transform.world);
 | |
| 		tempMat.multmat(invMat);
 | |
| 		var bones: Array<TObj> = [];
 | |
| 		var pBone = bone;
 | |
| 		while (pBone.parent != null) {
 | |
| 			bones.push(pBone.parent);
 | |
| 			pBone = pBone.parent;
 | |
| 		}
 | |
| 
 | |
| 		for (i in 0...bones.length) {
 | |
| 			var x = bones.length - 1;
 | |
| 			invMat.getInverse(getBoneMat(actionMats, bones[x - i]));
 | |
| 			tempMat.multmat(invMat);
 | |
| 		}
 | |
| 
 | |
| 		getBoneMat(actionMats, bone).setFrom(tempMat);
 | |
| 	}
 | |
| 
 | |
| 	public function blendAction(actionMats1: Array<Mat4>, actionMats2: Array<Mat4>, resultMat: Array<Mat4>, factor: FastFloat = 0.0, layerMask: Int = -1, threshold: FastFloat = 0.1) {
 | |
| 
 | |
| 		if(factor < threshold) {
 | |
| 			for(i in 0...actionMats1.length){
 | |
| 				// Use Action 1
 | |
| 				resultMat[i].setFrom(actionMats1[i]);
 | |
| 			}
 | |
| 		}
 | |
| 		else if(factor > 1.0 - threshold){
 | |
| 			for(i in 0...actionMats2.length){
 | |
| 				// If root motion cache -> use root motion index, else use current index
 | |
| 				var j = i == skeletonBones.length ? rootMotionIndex : i;
 | |
| 				// Skip if root motion is disabled
 | |
| 				if(j < 0) continue;
 | |
| 				// Use Action 2
 | |
| 				if(skeletonBones[j].bone_layers[layerMask] || layerMask < 0){
 | |
| 					resultMat[i].setFrom(actionMats2[i]);
 | |
| 				}
 | |
| 				// Use Action 1 if not in layer
 | |
| 				else {
 | |
| 					resultMat[i].setFrom(actionMats1[i]);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		else {
 | |
| 			for(i in 0...actionMats1.length){
 | |
| 				// If root motion cache -> use root motion index, else use current index
 | |
| 				var j = i == skeletonBones.length ? rootMotionIndex : i;
 | |
| 				// Skip if root motion is disabled
 | |
| 				if(j < 0) continue;
 | |
| 				// Blend
 | |
| 				if(skeletonBones[j].bone_layers[j] || layerMask < 0) {
 | |
| 					// Decompose
 | |
| 					m.setFrom(actionMats1[i]);
 | |
| 					m1.setFrom(actionMats2[i]);
 | |
| 					m.decompose(vpos, q1, vscl);
 | |
| 					m1.decompose(vpos2, q2, vscl2);
 | |
| 					// Lerp
 | |
| 					v1.lerp(vpos, vpos2, factor);
 | |
| 					v2.lerp(vscl, vscl2, factor);
 | |
| 					q3.lerp(q1, q2, factor);
 | |
| 					// Compose
 | |
| 					m2.fromQuat(q3);
 | |
| 					m2.scale(v2);
 | |
| 					m2._30 = v1.x;
 | |
| 					m2._31 = v1.y;
 | |
| 					m2._32 = v1.z;
 | |
| 					// Lock root motion to one action on certain axis to conserve looping
 | |
| 					if(i == skeletonBones.length){
 | |
| 						m2._30 = rootMotionLockX ? vpos2.x : m2._30;
 | |
| 						m2._31 = rootMotionLockY ? vpos2.y : m2._31;
 | |
| 						m2._32 = rootMotionLockZ ? vpos2.z : m2._32;
 | |
| 					}
 | |
| 					// Return Result
 | |
| 					resultMat[i].setFrom(m2);
 | |
| 				}
 | |
| 				// Use Action 1 if not in layer
 | |
| 				else {
 | |
| 					resultMat[i].setFrom(actionMats1[i]);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public function additiveBlendAction(baseActionMats: Array<Mat4>, addActionMats: Array<Mat4>, restPoseMats: Array<Mat4>, resultMat: Array<Mat4>, factor: FastFloat, layerMask: Int = -1, threshold: FastFloat = 0.1) {
 | |
| 
 | |
| 		if(factor < threshold) {
 | |
| 			for(i in 0...baseActionMats.length){
 | |
| 				resultMat[i].setFrom(baseActionMats[i]);
 | |
| 			}
 | |
| 		}
 | |
| 		else{
 | |
| 			for(i in 0...baseActionMats.length){
 | |
| 
 | |
| 				if(skeletonBones[i].bone_layers[layerMask] || layerMask < 0) {
 | |
| 					// Decompose
 | |
| 					m.setFrom(baseActionMats[i]);
 | |
| 					m1.setFrom(addActionMats[i]);
 | |
| 					bm.setFrom(restPoseMats[i]);
 | |
| 
 | |
| 					m.decompose(vpos, q1, vscl);
 | |
| 					m1.decompose(vpos2, q2, vscl2);
 | |
| 					bm.decompose(vpos3, q3, vscl3);
 | |
| 
 | |
| 					// Add Transforms
 | |
| 					v1.setFrom(vpos);
 | |
| 					v2.setFrom(vpos2);
 | |
| 					v2.sub(vpos3);
 | |
| 					v2.mult(factor);
 | |
| 					v1.add(v2);
 | |
| 
 | |
| 					// Add Scales
 | |
| 					vscl2.mult(factor);
 | |
| 					v2.set(1-factor, 1-factor, 1-factor, 1);
 | |
| 					v2.add(vscl2);
 | |
| 					v2.x *= vscl.x;
 | |
| 					v2.y *= vscl.y;
 | |
| 					v2.z *= vscl.z;
 | |
| 					v2.w = 1.0;
 | |
| 
 | |
| 					// Add rotations
 | |
| 					q2.lerp(q3, q2, factor);
 | |
| 					wm.fromQuat(q3);
 | |
| 					wm.getInverse(wm);
 | |
| 					q3.fromMat(wm).normalize();
 | |
| 					q3.multquats(q3, q2);
 | |
| 					q3.multquats(q1, q3);
 | |
| 
 | |
| 					// Compose
 | |
| 					m2.fromQuat(q3);
 | |
| 					m2.scale(v2);
 | |
| 					m2._30 = v1.x;
 | |
| 					m2._31 = v1.y;
 | |
| 					m2._32 = v1.z;
 | |
| 					// Return Result
 | |
| 					resultMat[i].setFrom(m2);
 | |
| 				}
 | |
| 				else{
 | |
| 					resultMat[i].setFrom(baseActionMats[i]);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public function solveIK(actionMats: Array<Mat4>, effector: TObj, goal: Vec4, precision = 0.01, maxIterations = 100, chainLenght = 100, pole: Vec4 = null, rollAngle = 0.0) {
 | |
| 		// Array of bones to solve IK for, effector at 0
 | |
| 		var bones: Array<TObj> = [];
 | |
| 
 | |
| 		// Array of bones lengths, effector length at 0
 | |
| 		var lengths: Array<FastFloat> = [];
 | |
| 
 | |
| 		// Array of bones matrices in world coordinates, effector at 0
 | |
| 		var boneWorldMats: Array<Mat4>;
 | |
| 
 | |
| 		var tempLoc = new Vec4();
 | |
| 		var tempRot = new Quat();
 | |
| 		var tempRot2 = new Quat();
 | |
| 		var tempScl = new Vec4();
 | |
| 		var roll = new Quat().fromEuler(0, rollAngle, 0);
 | |
| 
 | |
| 		// Store all bones and lengths in array
 | |
| 		var tip = effector;
 | |
| 		bones.push(tip);
 | |
| 		var root = tip;
 | |
| 
 | |
| 		while (root.parent != null) {
 | |
| 			if (bones.length > chainLenght - 1) break;
 | |
| 			bones.push(root.parent);
 | |
| 			root = root.parent;
 | |
| 		}
 | |
| 
 | |
| 		// Get all bone mats in world space
 | |
| 		boneWorldMats = getWorldMatsFast(actionMats, effector, bones.length);
 | |
| 
 | |
| 		var tempIndex = 0;
 | |
| 		for(b in bones){
 | |
| 			lengths.push(b.bone_length * boneWorldMats[boneWorldMats.length - 1 - tempIndex].getScale().x);
 | |
| 			tempIndex++;
 | |
| 		}
 | |
| 
 | |
| 		// Root bone
 | |
| 		root = bones[bones.length - 1];
 | |
| 
 | |
| 		// World matrix of root bone
 | |
| 		var rootWorldMat = getAbsWorldMat(actionMats, root).clone();
 | |
| 		// Distance from root to goal
 | |
| 		var dist = Vec4.distance(goal, rootWorldMat.getLoc());
 | |
| 
 | |
| 
 | |
| 		// Total bones length
 | |
| 		var totalLength: FastFloat = 0.0;
 | |
| 		for (l in lengths) totalLength += l;
 | |
| 
 | |
| 		// Unreachable distance
 | |
| 		if (dist > totalLength) {
 | |
| 			// Calculate unit vector from root to goal
 | |
| 			var newLook = goal.clone();
 | |
| 			newLook.sub(rootWorldMat.getLoc());
 | |
| 			newLook.normalize();
 | |
| 
 | |
| 			// Rotate root bone to point at goal
 | |
| 			rootWorldMat.decompose(tempLoc, tempRot, tempScl);
 | |
| 			tempRot2.fromTo(rootWorldMat.look().normalize(), newLook);
 | |
| 			tempRot2.mult(tempRot);
 | |
| 			tempRot2.mult(roll);
 | |
| 			rootWorldMat.compose(tempLoc, tempRot2, tempScl);
 | |
| 
 | |
| 			// Set bone matrix in local space from world space
 | |
| 			setBoneMatFromWorldMat(actionMats, rootWorldMat, root);
 | |
| 
 | |
| 			// Set child bone rotations to zero
 | |
| 			for (i in 0...bones.length - 1) {
 | |
| 				getBoneMat(actionMats, bones[i]).decompose(tempLoc, tempRot, tempScl);
 | |
| 				getBoneMat(actionMats, bones[i]).compose(tempLoc, roll, tempScl);
 | |
| 			}
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		// Array of bone locations in world space, root location at [0]
 | |
| 		var boneWorldLocs: Array<Vec4> = [];
 | |
| 		for (b in boneWorldMats) boneWorldLocs.push(b.getLoc());
 | |
| 
 | |
| 		// Solve FABRIK
 | |
| 		var vec = new Vec4();
 | |
| 		var startLoc = boneWorldLocs[0].clone();
 | |
| 		var l = boneWorldLocs.length;
 | |
| 		var testLength = 0;
 | |
| 
 | |
| 		for (iter in 0...maxIterations) {
 | |
| 			// Backward
 | |
| 			vec.setFrom(goal);
 | |
| 			vec.sub(boneWorldLocs[l - 1]);
 | |
| 			vec.normalize();
 | |
| 			vec.mult(lengths[0]);
 | |
| 			boneWorldLocs[l - 1].setFrom(goal);
 | |
| 			boneWorldLocs[l - 1].sub(vec);
 | |
| 
 | |
| 			for (j in 1...l) {
 | |
| 				vec.setFrom(boneWorldLocs[l - 1 - j]);
 | |
| 				vec.sub(boneWorldLocs[l - j]);
 | |
| 				vec.normalize();
 | |
| 				vec.mult(lengths[j]);
 | |
| 				boneWorldLocs[l - 1 - j].setFrom(boneWorldLocs[l - j]);
 | |
| 				boneWorldLocs[l - 1 - j].add(vec);
 | |
| 			}
 | |
| 
 | |
| 			// Forward
 | |
| 			boneWorldLocs[0].setFrom(startLoc);
 | |
| 			for (j in 1...l) {
 | |
| 				vec.setFrom(boneWorldLocs[j]);
 | |
| 				vec.sub(boneWorldLocs[j - 1]);
 | |
| 				vec.normalize();
 | |
| 				vec.mult(lengths[l - j]);
 | |
| 				boneWorldLocs[j].setFrom(boneWorldLocs[j - 1]);
 | |
| 				boneWorldLocs[j].add(vec);
 | |
| 			}
 | |
| 
 | |
| 			if (Vec4.distance(boneWorldLocs[l - 1], goal) - lengths[0] <= precision) break;
 | |
| 		}
 | |
| 
 | |
| 		// Pole rotation implementation
 | |
| 		if (pole != null) {
 | |
| 			for (i in 1...boneWorldLocs.length - 1) {
 | |
| 				boneWorldLocs[i] = moveTowardPole(boneWorldLocs[i - 1].clone(), boneWorldLocs[i].clone(), boneWorldLocs[i + 1].clone(), pole.clone());
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Correct rotations
 | |
| 		// Applying locations and rotations
 | |
| 		var tempLook = new Vec4();
 | |
| 		var tempLoc2 = new Vec4();
 | |
| 
 | |
| 		for (i in 0...l - 1){
 | |
| 			// Decompose matrix
 | |
| 			boneWorldMats[i].decompose(tempLoc, tempRot, tempScl);
 | |
| 
 | |
| 			// Rotate to point to parent bone
 | |
| 			tempLoc2.setFrom(boneWorldLocs[i + 1]);
 | |
| 			tempLoc2.sub(boneWorldLocs[i]);
 | |
| 			tempLoc2.normalize();
 | |
| 			tempLook.setFrom(boneWorldMats[i].look());
 | |
| 			tempLook.normalize();
 | |
| 			tempRot2.fromTo(tempLook, tempLoc2);
 | |
| 			tempRot2.mult(tempRot);
 | |
| 			tempRot2.mult(roll);
 | |
| 
 | |
| 			// Compose matrix with new rotation and location
 | |
| 			boneWorldMats[i].compose(boneWorldLocs[i], tempRot2, tempScl);
 | |
| 
 | |
| 			// Set bone matrix in local space from world space
 | |
| 			setBoneMatFromWorldMat(actionMats, boneWorldMats[i], bones[bones.length - 1 - i]);
 | |
| 		}
 | |
| 
 | |
| 		// Decompose matrix
 | |
| 		boneWorldMats[l - 1].decompose(tempLoc, tempRot, tempScl);
 | |
| 
 | |
| 		// Rotate to point to goal
 | |
| 		tempLoc2.setFrom(goal);
 | |
| 		tempLoc2.sub(tempLoc);
 | |
| 		tempLoc2.normalize();
 | |
| 		tempLook.setFrom(boneWorldMats[l - 1].look());
 | |
| 		tempLook.normalize();
 | |
| 		tempRot2.fromTo(tempLook, tempLoc2);
 | |
| 		tempRot2.mult(tempRot);
 | |
| 		tempRot2.mult(roll);
 | |
| 
 | |
| 		// Compose matrix with new rotation and location
 | |
| 		boneWorldMats[l - 1].compose(boneWorldLocs[l - 1], tempRot2, tempScl);
 | |
| 
 | |
| 		// Set bone matrix in local space from world space
 | |
| 		setBoneMatFromWorldMat(actionMats, boneWorldMats[l - 1], bones[0]);
 | |
| 	}
 | |
| 
 | |
| 	public function moveTowardPole(bone0Pos: Vec4, bone1Pos: Vec4, bone2Pos: Vec4, polePos: Vec4): Vec4 {
 | |
| 		// Setup projection plane at current bone's parent
 | |
| 		var plane = new Plane();
 | |
| 
 | |
| 		// Plane normal from parent of current bone to child of current bone
 | |
| 		var planeNormal = new Vec4().setFrom(bone2Pos);
 | |
| 		planeNormal.sub(bone0Pos);
 | |
| 		planeNormal.normalize();
 | |
| 		plane.set(planeNormal, bone0Pos);
 | |
| 
 | |
| 		// Create and project ray from current bone to plane
 | |
| 		var rayPos = new Vec4();
 | |
| 		rayPos.setFrom(bone1Pos);
 | |
| 		var rayDir = new Vec4();
 | |
| 		rayDir.sub(planeNormal);
 | |
| 		rayDir.normalize();
 | |
| 		var rayBone = new Ray(rayPos, rayDir);
 | |
| 
 | |
| 		// Projection point of current bone on plane
 | |
| 		// If pole does not project on the plane
 | |
| 		if (!rayBone.intersectsPlane(plane)) {
 | |
| 			rayBone.direction = planeNormal;
 | |
| 		}
 | |
| 
 | |
| 		var bone1Proj = rayBone.intersectPlane(plane);
 | |
| 
 | |
| 		// Create and project ray from pole to plane
 | |
| 		rayPos.setFrom(polePos);
 | |
| 		var rayPole = new Ray(rayPos, rayDir);
 | |
| 
 | |
| 		// If pole does not project on the plane
 | |
| 		if (!rayPole.intersectsPlane(plane)) {
 | |
| 			rayPole.direction = planeNormal;
 | |
| 		}
 | |
| 
 | |
| 		// Projection point of pole on plane
 | |
| 		var poleProj = rayPole.intersectPlane(plane);
 | |
| 
 | |
| 		// Caclulate unit vectors from pole projection to parent bone
 | |
| 		var poleProjNormal = new Vec4();
 | |
| 		poleProjNormal.setFrom(bone0Pos);
 | |
| 		poleProjNormal.sub(poleProj);
 | |
| 		poleProjNormal.normalize();
 | |
| 
 | |
| 		// Calculate unit vector from current bone projection to parent bone
 | |
| 		var bone1ProjNormal = new Vec4();
 | |
| 		bone1ProjNormal.setFrom(bone0Pos);
 | |
| 		bone1ProjNormal.sub(bone1Proj);
 | |
| 		bone1ProjNormal.normalize();
 | |
| 
 | |
| 		// Calculate rotation quaternion
 | |
| 		var rotQuat = new Quat();
 | |
| 		rotQuat.fromTo(bone1ProjNormal, poleProjNormal);
 | |
| 
 | |
| 		// Apply quaternion to current bone location
 | |
| 		var bone1Res = new Vec4().setFrom(bone1Pos);
 | |
| 		bone1Res.sub(bone0Pos);
 | |
| 		bone1Res.applyQuat(rotQuat);
 | |
| 		bone1Res.add(bone0Pos);
 | |
| 
 | |
| 		// Return new location of current bone
 | |
| 		return bone1Res;
 | |
| 	}
 | |
| 
 | |
| 	public function solveTwoBoneIK(actionMats : Array<Mat4>, effector: TObj, goal: Vec4, pole: Vec4 = null, rollAngle = 0.0) {		
 | |
| 		var roll = new Quat().fromEuler(0, rollAngle, 0);
 | |
| 		var root = effector.parent;
 | |
| 
 | |
| 		// Get bone transforms in world space
 | |
| 		var effectorMat = getAbsWorldMat(actionMats, effector).clone();
 | |
| 		var rootMat = getAbsWorldMat(actionMats, root).clone();
 | |
| 
 | |
| 		// Get bone lenghts
 | |
| 		var effectorLen = effector.bone_length * effectorMat.getScale().x;
 | |
| 		var rootLen = root.bone_length * rootMat.getScale().x;
 | |
| 
 | |
| 		// Get distance form root to goal
 | |
| 		var goalLen = Math.abs(Vec4.distance(rootMat.getLoc(), goal));
 | |
| 
 | |
| 		var totalLength = effectorLen + rootLen;
 | |
| 
 | |
| 		// Get tip location of effector bone
 | |
| 		var effectorTipPos = new Vec4().setFrom(effectorMat.look()).normalize();
 | |
| 		effectorTipPos.mult(effectorLen);
 | |
| 		effectorTipPos.add(effectorMat.getLoc());
 | |
| 
 | |
| 		// Get unit vector from root to effector tip
 | |
| 		var vectorRootEffector = new Vec4().setFrom(effectorTipPos).sub(rootMat.getLoc());
 | |
| 		vectorRootEffector.normalize();
 | |
| 
 | |
| 		// Get unit vector from root to goal
 | |
| 		var vectorGoal = new Vec4().setFrom(goal).sub(rootMat.getLoc());
 | |
| 		vectorGoal.normalize();
 | |
| 
 | |
| 		// Get unit vector of root bone
 | |
| 		var vectorRoot = new Vec4().setFrom(rootMat.look()).normalize();
 | |
| 
 | |
| 		// Get unit vector of effector bone
 | |
| 		var vectorEffector = new Vec4().setFrom(effectorMat.look()).normalize();		
 | |
| 		
 | |
| 		// Get dot product of vectors
 | |
| 		var dot = new Vec4().setFrom(vectorRootEffector).dot(vectorRoot);
 | |
| 		// Calmp between -1 and 1
 | |
| 		dot = dot < -1.0 ? -1.0 : dot > 1.0 ? 1.0 : dot;
 | |
| 		// Gat angle A1
 | |
| 		var angleA1 = Math.acos(dot);
 | |
| 
 | |
| 		// Get angle A2
 | |
| 		dot = new Vec4().setFrom(vectorRoot).mult(-1.0).dot(vectorEffector);
 | |
| 		dot = dot < -1.0 ? -1.0 : dot > 1.0 ? 1.0 : dot;
 | |
| 		var angleA2 = Math.acos(dot);
 | |
| 
 | |
| 		// Get angle A3
 | |
| 		dot = new Vec4().setFrom(vectorRootEffector).dot(vectorGoal);
 | |
| 		dot = dot < -1.0 ? -1.0 : dot > 1.0 ? 1.0 : dot;
 | |
| 		var angleA3 = Math.acos(dot);
 | |
| 
 | |
| 		// Get angle B1
 | |
| 		dot = (effectorLen * effectorLen - rootLen * rootLen - goalLen * goalLen) / (-2 * rootLen * goalLen);
 | |
| 		dot = dot < -1.0 ? -1.0 : dot > 1.0 ? 1.0 : dot;
 | |
| 		var angleB1 = Math.acos(dot);
 | |
| 
 | |
| 		// Get angle B2
 | |
| 		dot = (goalLen * goalLen - rootLen * rootLen - effectorLen * effectorLen) / (-2 * rootLen * effectorLen);
 | |
| 		dot = dot < -1.0 ? -1.0 : dot > 1.0 ? 1.0 : dot;
 | |
| 		var angleB2 = Math.acos(dot);
 | |
| 
 | |
| 		// Calculate rotation axes
 | |
| 		var axis0 = new Vec4().setFrom(vectorRootEffector).cross(vectorRoot).normalize();
 | |
| 		var axis1 = new Vec4().setFrom(vectorRootEffector).cross(vectorGoal).normalize();
 | |
| 
 | |
| 		// Apply rotations to effector bone
 | |
| 		vpos.setFrom(effectorMat.getLoc());
 | |
| 		effectorMat.setLoc(new Vec4());
 | |
| 		effectorMat.applyQuat(new Quat().fromAxisAngle(axis0, angleB2 - angleA2));
 | |
| 		effectorMat.setLoc(vpos);
 | |
| 		setBoneMatFromWorldMat(actionMats, effectorMat, effector);
 | |
| 
 | |
| 		// Apply rotations to root bone
 | |
| 		vpos.setFrom(rootMat.getLoc());
 | |
| 		rootMat.setLoc(new Vec4());
 | |
| 		rootMat.applyQuat(new Quat().fromAxisAngle(axis0, angleB1 - angleA1));
 | |
| 		rootMat.applyQuat(new Quat().fromAxisAngle(axis1, angleA3));
 | |
| 		rootMat.setLoc(vpos);
 | |
| 		setBoneMatFromWorldMat(actionMats, rootMat, root);
 | |
| 
 | |
| 		// Recalculate new effector matrix
 | |
| 		effectorMat.setFrom(getAbsWorldMat(actionMats, effector));
 | |
| 
 | |
| 		// Check if pole present
 | |
| 		if((pole != null) && (goalLen < totalLength)) {
 | |
| 		
 | |
| 			// Calculate new effector tip position
 | |
| 			vscl.setFrom(effectorMat.look()).normalize();
 | |
| 			vscl.mult(effectorLen);
 | |
| 			vscl.add(effectorMat.getLoc());
 | |
| 
 | |
| 			// Calculate new effector position from pole
 | |
| 			vpos2 = moveTowardPole(rootMat.getLoc(), effectorMat.getLoc(), vscl, pole);
 | |
| 
 | |
| 			// Orient root bone to new effector position
 | |
| 			vpos.setFrom(rootMat.getLoc());
 | |
| 			rootMat.setLoc(new Vec4());
 | |
| 			vpos3.setFrom(vpos2).sub(vpos).normalize();
 | |
| 			rootMat.applyQuat(new Quat().fromTo(rootMat.look().normalize(), vpos3));
 | |
| 			rootMat.setLoc(vpos);
 | |
| 			
 | |
| 			// Orient effector bone to new position
 | |
| 			vpos.setFrom(effectorMat.getLoc());
 | |
| 			effectorMat.setLoc(new Vec4());
 | |
| 			vpos3.setFrom(vscl).sub(vpos2).normalize();
 | |
| 			effectorMat.applyQuat(new Quat().fromTo(effectorMat.look().normalize(), vpos3));
 | |
| 			effectorMat.setLoc(vpos2);
 | |
| 		}
 | |
| 
 | |
| 		// Apply roll to root bone
 | |
| 		vpos.setFrom(rootMat.getLoc());
 | |
| 		rootMat.setLoc(new Vec4());
 | |
| 		rootMat.applyQuat(new Quat().fromAxisAngle(rootMat.look().normalize(), rollAngle));
 | |
| 		rootMat.setLoc(vpos);
 | |
| 
 | |
| 		// Apply roll to effector bone
 | |
| 		vpos.setFrom(effectorMat.getLoc());
 | |
| 		effectorMat.setLoc(new Vec4());
 | |
| 		effectorMat.applyQuat(new Quat().fromAxisAngle(effectorMat.look().normalize(), rollAngle));
 | |
| 		effectorMat.setLoc(vpos);
 | |
| 
 | |
| 		// Finally set root and effector matrices in local space
 | |
| 		setBoneMatFromWorldMat(actionMats, rootMat, root);
 | |
| 		setBoneMatFromWorldMat(actionMats, effectorMat, effector);
 | |
| 
 | |
| 	}
 | |
| 
 | |
| }
 | |
| #end
 |