/// <summary> /// コンストラクタ /// </summary> /// <param name="bone">ボーン</param> /// <param name="boneFrames">ボーンのモーションデータ</param> public ボーンモーションforVME(PMXボーン bone, List <BoneFrame> boneFrames) { this.bone = bone; foreach (var boneFrame in boneFrames) { frameManager.フレームデータを追加する(boneFrame); } if (!frameManager.フレームは昇順に並んでいる) { throw new Exception("VMEデータがソートされていません"); } }
private void _IKボーンを更新する(PMXボーン IKbone) { // 現在のループ回数のリセット foreach (var link in IKbone.IKリンクリスト) { link.現在のループ回数 = 0; } // 決められた回数、IK演算を反復する。 for (int it = 0; it < IKbone.IK演算のLoop回数; it++) { _一回のIK演算を実行する(IKbone); } }
public PMXスケルトン(PMXモデル model) { // ボーンの数だけ初期化 ボーン配列 = new PMXボーン[model.ボーンリスト.Count]; ボーンのモデルポーズ配列 = new Matrix[model.ボーンリスト.Count]; ボーンのローカル位置 = new Vector3[model.ボーンリスト.Count]; ボーンの回転 = new Vector4[model.ボーンリスト.Count]; IKボーンリスト = new List <PMXボーン>(); // ボーンを読み込む _LoadBones(model); // ボーンマップ作製 ボーンマップ = new Dictionary <string, PMXボーン>(); foreach (var bone in ボーン配列) { if (ボーンマップ.ContainsKey(bone.ボーン名)) { int i = 0; do { i++; } while(ボーンマップ.ContainsKey(bone.ボーン名 + i.ToString())); ボーンマップ.Add(bone.ボーン名 + i.ToString(), bone); Debug.WriteLine("ボーン名{0}は重複しています。自動的にボーン名{1}と読み替えられました。", bone.ボーン名, bone.ボーン名 + i); } else { ボーンマップ.Add(bone.ボーン名, bone); } } // 変形更新プロバイダリスト作成 形更新リスト = new List <形更新>(); 形更新リスト.Add(new CCDによるIK変形更新(new WeakReference <List <PMXボーン> >(IKボーンリスト))); 形更新リスト.Add(new 親付与によるFK変形更新(ボーン配列)); if (ボーン配列.Length > 768) { throw new InvalidOperationException("MMFでは現在768以上のボーンを持つモデルについてサポートしていません。\nただし、Resource\\Shader\\DefaultShader.fx内のボーン変形行列の配列float4x4 BoneTrans[512]:BONETRANS;の要素数を拡張しこの部分をコメントアウトすれば暫定的に利用することができるかもしれません。"); } }
private bool _一回のIK演算を実行する(PMXボーン IKbone) { var エフェクタ = IKbone.IKターゲットボーン; var ターゲット = IKbone; // すべての IKリンク について…… foreach (var ikLink in IKbone.IKリンクリスト) { var IKリンクのローカル座標へ変換する行列 = Matrix.Invert(ikLink.IKリンクボーン.モデルポーズ行列); var IKリンクローカル座標でのエフェクタ位置 = Vector3.TransformCoordinate(エフェクタ.ローカル位置, エフェクタ.モデルポーズ行列 * IKリンクのローカル座標へ変換する行列); var IKリンクローカル座標でのターゲット位置 = Vector3.TransformCoordinate(ターゲット.ローカル位置, ターゲット.モデルポーズ行列 * IKリンクのローカル座標へ変換する行列); var V1 = Vector3.Normalize(IKリンクローカル座標でのエフェクタ位置 - ikLink.IKリンクボーン.ローカル位置); var V2 = Vector3.Normalize(IKリンクローカル座標でのターゲット位置 - ikLink.IKリンクボーン.ローカル位置); // IKリンクを更新する ikLink.現在のループ回数++; // 回転軸 Vaxis = v1×v2 を計算する。 var Vaxis = Vector3.Cross(V1, V2); // 回転軸周りの回転角 θa = Cos-1(V1・V2) を計算する。 var 内積 = (double)Vector3.Dot(V1, V2); 内積 = Math.Max(Math.Min(内積, 1.0), -1.0); // 誤差丸め double θa = Math.Acos(内積); // 内積: -1 → 1 のとき、θa: π → 0 θa = Math.Min(θa, IKbone.IK単位角); // θa は単位角を超えないこと if (θa <= 0.00001) { continue; // θa が小さすぎたら無視 } // Vaxis と θa から、回転クォータニオンを計算する。 var 回転クォータニオン = Quaternion.RotationAxis(Vaxis, (float)θa); 回転クォータニオン.Normalize(); // IKリンクに回転クォータニオンを適用する。 ikLink.IKリンクボーン.回転 *= 回転クォータニオン; // 回転量制限があれば適用する。 if (ikLink.回転制限がある) { #region " 回転量制限 " //---------------- float X軸回転角, Y軸回転角, Z軸回転角; // 回転にはクォータニオンを使っているが、PMXのボーンの最小回転量・最大回転量は、クォータニオンではなくオイラー角(X,Y,Z)で指定される。 // そのため、回転数制限の手順は // (1) クォータニオンをいったんオイラー角(X,Y,Z)に変換 // (2) X,Y,Z に対して回転量制限チェック // (3) オイラー角を再びクォータニオンに戻す // となる。 // // また、オイラー角で回転を表す場合、各軸の回転の順番が重要となる。 // ここでは、 // (A) X → Y → Z の順 // (B) Y → Z → X の順 // (C) Z → X → Y の順 // の3通りを調べて、ジンバルロックが発生しない最初の分解値を採用する。 if (CGHelper.クォータニオンをXYZ回転に分解する(ikLink.IKリンクボーン.回転, out X軸回転角, out Y軸回転角, out Z軸回転角)) // ジンバルロックが発生しなければ true { // (A) XYZ 回転 var clamped = Vector3.Clamp( new Vector3(X軸回転角, Y軸回転角, Z軸回転角).オイラー角の値域を正規化する(), ikLink.最小回転量, ikLink.最大回転量); X軸回転角 = clamped.X; Y軸回転角 = clamped.Y; Z軸回転角 = clamped.Z; ikLink.IKリンクボーン.回転 = Quaternion.RotationMatrix(Matrix.RotationX(X軸回転角) * Matrix.RotationY(Y軸回転角) * Matrix.RotationZ(Z軸回転角)); // X, Y, Z の順 } else if (CGHelper.クォータニオンをYZX回転に分解する(ikLink.IKリンクボーン.回転, out Y軸回転角, out Z軸回転角, out X軸回転角)) // ジンバルロックが発生しなければ true { // (B) YZX 回転 var clamped = Vector3.Clamp( new Vector3(X軸回転角, Y軸回転角, Z軸回転角).オイラー角の値域を正規化する(), ikLink.最小回転量, ikLink.最大回転量); X軸回転角 = clamped.X; Y軸回転角 = clamped.Y; Z軸回転角 = clamped.Z; ikLink.IKリンクボーン.回転 = Quaternion.RotationMatrix(Matrix.RotationY(Y軸回転角) * Matrix.RotationZ(Z軸回転角) * Matrix.RotationX(X軸回転角)); // Y, Z, X の順 } else if (CGHelper.クォータニオンをZXY回転に分解する(ikLink.IKリンクボーン.回転, out Z軸回転角, out X軸回転角, out Y軸回転角)) // ジンバルロックが発生しなければ true { // (C) ZXY 回転 var clamped = Vector3.Clamp( new Vector3(X軸回転角, Y軸回転角, Z軸回転角).オイラー角の値域を正規化する(), ikLink.最小回転量, ikLink.最大回転量); X軸回転角 = clamped.X; Y軸回転角 = clamped.Y; Z軸回転角 = clamped.Z; //ikLink.ikLinkBone.回転行列 = Quaternion.RotationYawPitchRoll( Y軸回転角, X軸回転角, Z軸回転角 ); ikLink.IKリンクボーン.回転 = Quaternion.RotationMatrix(Matrix.RotationZ(Z軸回転角) * Matrix.RotationX(X軸回転角) * Matrix.RotationY(Y軸回転角)); // Z, X, Y の順 } else { // その他はエラー continue; } //---------------- #endregion } // IKリンクの新しい回転行列を反映。 ikLink.IKリンクボーン.モデルポーズを更新する(); } return(true); }
public PMXボーン(List <MMDFileParser.PMXModelParser.ボーン> bones, int index, int layer, スキニング skinning) { var me = bones[index]; //このボーン _スキニング = skinning; _スキニング.ボーン配列[index] = this; ボーンインデックス = index; ローカル位置 = me.位置; ボーン名 = me.ボーン名; ローカル軸あり = me.ローカル軸あり; 形階層 = layer; 形階層の物理前後 = me.物理後変形である ? 形階層の物理前後指定種別.物理演算後 : 形階層の物理前後指定種別.物理演算前; if (ローカル軸あり) { DefaultLocalX = me.ローカル軸のX軸の方向ベクトル; DefaultLocalY = Vector3.Cross(me.ローカル軸のZ軸の方向ベクトル, DefaultLocalX); DefaultLocalZ = Vector3.Cross(DefaultLocalX, DefaultLocalY); } if (me.IKボーンである) { skinning.IKボーンリスト.Add(this); // IK関連の情報をボーン(me)から取得 IKボーンである = true; IK単位角 = me.IK単位角rad; _IKターゲットボーンインデックス = me.IKターゲットボーンインデックス; IK演算のLoop回数 = me.IKループ回数; foreach (MMDFileParser.PMXModelParser.ボーン.IKリンク ikLink in me.IKリンクリスト) { this.IKリンクリスト.Add(new IKリンク(new WeakReference <スキニング>(skinning), ikLink)); } } 回転付与される = me.回転付与される; 移動付与される = me.移動付与される; if (me.付与親ボーンインデックス == -1) { 回転付与される = false; 移動付与される = false; } if (移動付与される || 回転付与される) { 付与親ボーンインデックス = me.付与親ボーンインデックス; 付与率 = me.付与率; } else { 付与親ボーンインデックス = -1; } for (int i = 0; i < bones.Count; i++) { MMDFileParser.PMXModelParser.ボーン bone = bones[i]; if (bone.親ボーンのインデックス == index) { var child = new PMXボーン(bones, i, layer + 1, skinning); 子ボーンを追加する(child); } } }
// その他 public void 子ボーンを追加する(PMXボーン child) { 子ボーンリスト.Add(child); child.親ボーン = this; }
public ボーンモーション(PMXボーン bone) { this._ボーン = bone; }