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
|