using UnityEngine; using System.Collections; using System.Collections.Generic; public class CCDIKSolver : MonoBehaviour { /* MEMO * -------------------- * IKボーンにこのスクリプトを適用して、Solveを呼び出す。 * target = ターゲット(IKボーンを目指すボーン) * chains = IK影響下ボーン * -------------------- */ // Target // IKボーンを目指すボーン public Transform target; // Loop count // ushortだとInspectorに出ない public int iterations; // rad limit // "単位角" public float controll_weight; // IK影響下ボーン public Transform[] chains; // レイの描画(デバッグ用) public bool drawRay = false; // 計算 public void Solve() { // 有効化されてなかった場合は実行しない if (!this.enabled) return; // ループ回数で計算していく for (int tries = 0, _mt = iterations; tries < _mt; tries++) { // IK影響下ボーンごとに for (int _i = 0, _m = chains.Length; _i < _m; _i++) { var bone = chains[_i]; var bonePos = bone.position; // エフェクタ設定 var effectorPos = target.position; var effectorDirection = (effectorPos - bonePos); // ターゲット設定 var targetDirection = (transform.position - bonePos); // 線を引いといてみる if (drawRay) { Debug.DrawRay(bonePos, effectorDirection, Color.green); Debug.DrawRay(bonePos, targetDirection, Color.red); } // 内積 effectorDirection = effectorDirection.normalized; targetDirection = targetDirection.normalized; float rotateDot = Vector3.Dot(effectorDirection, targetDirection); // 角度算出。 // controll_weightによる一回の計算での制限 float rotateAngle = Mathf.Acos(rotateDot); if (float.IsNaN(rotateAngle)) continue; var limit = 4 * controll_weight * (_i + 1); if (rotateAngle > limit) rotateAngle = limit; if (rotateAngle < -limit) rotateAngle = -limit; rotateAngle *= Mathf.Rad2Deg; // 外積で回転軸算出 // lockDirectionを見て、回転制限 => 一旦なし var rotateAxis = Vector3.Cross(effectorDirection, targetDirection).normalized; // 大丈夫そうなら if (float.IsNaN(rotateAxis.x) || float.IsNaN(rotateAxis.y) || float.IsNaN(rotateAxis.z)) continue; // 回す var rotate = Quaternion.AngleAxis(rotateAngle, rotateAxis); bone.rotation = rotate * bone.rotation; // 角度制限 limitter(bone); } } } // 角度制限 void limitter(Transform bone) { // 足首のZ回転 if (bone.name.Contains("足首")) { var vv = bone.localEulerAngles; vv.z = 0; bone.localRotation = Quaternion.Euler(vv); return; } // 本来なら設定値に基づいてやるけど、とりあえず膝限定 if (!bone.name.Contains("ひざ")) return; // オイラー角を取得 var v = bone.localEulerAngles; // y,z回転を無効化 if (adjust_rot(v.y) == adjust_rot(v.z)) { v.y = adjust_rot(v.y); v.z = adjust_rot(v.z); } // 逆に曲がらないように、制限してあげる if (v.x < 90 && v.x > 2 && ((v.y == 0 && v.z == 0) || (v.y == 180 && v.z == 180))) v.x = 360 - v.x * 0.99f; bone.localRotation = Quaternion.Euler(v); } // 0か180か近い方に固定 int adjust_rot(float n) { if (Mathf.Abs(n) > Mathf.Abs(180 - n) && Mathf.Abs(360 - n) > Mathf.Abs(180 - n)) return 180; else return 0; } // MMDEngine.cs の方でIKボーン全体も保持してるので、 // そっちからSolve()を呼び出している。 //-------------------- //void LateUpdate() //{ // Solve(); //} }