#region File Description
//-----------------------------------------------------------------------------
// QuatTransform.cs
//-----------------------------------------------------------------------------
#endregion
#region Using ステートメント

using System;
using Microsoft.Xna.Framework;

#endregion

namespace MikuMikuDance.XNA.Model.ModelData
{
    /// <summary>
    /// クォータニオンを使った回転と平行移動を表すことのできる頂点変換用構造体
    /// 通常の行列の半分以下のメモリ使用量になる
    /// </summary>
    public struct QuatTransform
    {
        #region フィールド

        /// <summary>
        /// 回転
        /// </summary>
        public Quaternion   Rotation;

        /// <summary>
        /// 平行移動
        /// </summary>
        public Vector3      Translation;

        #endregion
        /// <summary>
        /// 恒等QuatTransformを返します
        /// </summary>
        public static QuatTransform Identity { get { return new QuatTransform(Quaternion.Identity, Vector3.Zero); } }
        #region 初期化

        /// <summary>
        /// クォータニオンと平行移動を指定して生成する
        /// </summary>
        /// <param name="rotation"></param>
        /// <param name="translation"></param>
        public QuatTransform(Quaternion rotation, Vector3 translation)
        {
            Rotation = rotation;
            Translation = translation;
        }

        /// <summary>
        /// 指定された行列から生成する
        /// </summary>
        /// <param name="matrix"></param>
        /// <returns></returns>
        public static QuatTransform FromMatrix( Matrix matrix )
        {
            // 行列の分解
            Quaternion rotation;
            Vector3 translation;
            Vector3 scale;
            matrix.Decompose( out scale, out rotation, out translation );

            // 一意のスケールか？
            if ( !CloseEnough( scale.X, scale.Y ) || !CloseEnough( scale.X, scale.Z ) )
            {
                throw new InvalidOperationException(
                    "一意のスケール(X,Y,Zが同じスケール値)ではありません" );
            }

            if ( !CloseEnough( scale.X, 1.0f ) )
                throw new InvalidOperationException( "スケール値が1以外です" );

            return new QuatTransform( rotation, translation );
        }

        static bool CloseEnough( float a, float b )
        {
            return ( Math.Abs( a - b ) < 1e-4f );
        }

        #endregion

        /// <summary>
        /// QuatTransformの結合
        /// </summary>
        public static void Multiply(ref QuatTransform value1, ref QuatTransform value2, out QuatTransform result)
        {
            // 平行移動の算出
            Vector3 newTranslation;
            value2.Rotation.Normalize();
            Vector3.Transform(ref value1.Translation, ref value2.Rotation,
                                out newTranslation);

            newTranslation.X += value2.Translation.X;
            newTranslation.Y += value2.Translation.Y;
            newTranslation.Z += value2.Translation.Z;

            // 回転部分の結合
            Quaternion.Concatenate(ref value1.Rotation, ref value2.Rotation,
                                        out result.Rotation);
            
            result.Translation = newTranslation;
            //正規化
            result.Rotation.Normalize();

        }
        /// <summary>
        /// QuatTransformの結合
        /// </summary>
        public static QuatTransform operator *(QuatTransform value1, QuatTransform value2)
        {
            QuatTransform result;
            QuatTransform.Multiply(ref value1, ref value2, out result);
            return result;
        }
        internal Matrix CreateMatrix()
        {
            Matrix move = Matrix.CreateTranslation(Translation);
            Matrix rot = Matrix.CreateFromQuaternion(Rotation);
            Matrix result;
            Matrix.Multiply(ref rot, ref move, out result);
            return result;//回転・移動の順にかけ合わせる
        }
        /// <summary>
        /// 移動QuatTransformを返します
        /// </summary>
        /// <param name="x">X移動</param>
        /// <param name="y">Y移動</param>
        /// <param name="z">Z移動</param>
        /// <returns>移動QuatTransform</returns>
        public static QuatTransform CreateTranslation(float x, float y, float z)
        {
            return new QuatTransform(Quaternion.Identity, new Vector3(x, y, z));
        }
        /// <summary>
        /// 移動QuatTransformを返します
        /// </summary>
        /// <param name="x">X移動</param>
        /// <param name="y">Y移動</param>
        /// <param name="z">Z移動</param>
        /// <param name="result">移動QuatTransform</param>
        public static void CreateTranslation(float x, float y, float z, out QuatTransform result)
        {
            result = new QuatTransform(Quaternion.Identity, new Vector3(x, y, z));
        }

        /// <summary>
        /// X軸回転QuatTransformを返します
        /// </summary>
        /// <param name="rot">X回転</param>
        /// <returns>X軸回転QuatTransform</returns>
        public static QuatTransform CreateRotationX(float rot)
        {
            return new QuatTransform(Quaternion.CreateFromYawPitchRoll(0, rot, 0), Vector3.Zero);
        }
        /// <summary>
        /// X軸回転QuatTransformを返します
        /// </summary>
        /// <param name="rot">X回転</param>
        /// <param name="result">X軸回転QuatTransform</param>
        public static void CreateRotationX(float rot, out QuatTransform result)
        {
            result = new QuatTransform();
            Quaternion.CreateFromYawPitchRoll(0, rot, 0, out result.Rotation);
        }
        /// <summary>
        /// Y軸回転QuatTransformを返します
        /// </summary>
        /// <param name="rot">Y回転</param>
        /// <returns>Y軸回転QuatTransform</returns>
        public static QuatTransform CreateRotationY(float rot)
        {
            return new QuatTransform(Quaternion.CreateFromYawPitchRoll(rot, 0, 0), Vector3.Zero);
        }
        /// <summary>
        /// Y軸回転QuatTransformを返します
        /// </summary>
        /// <param name="rot">Y回転</param>
        /// <param name="result">Y軸回転QuatTransform</param>
        public static void CreateRotationY(float rot, out QuatTransform result)
        {
            result = new QuatTransform();
            Quaternion.CreateFromYawPitchRoll(rot, 0, 0, out result.Rotation);
        }
        /// <summary>
        /// Z軸回転QuatTransformを返します
        /// </summary>
        /// <param name="rot">Z回転</param>
        /// <returns>Z軸回転QuatTransform</returns>
        public static QuatTransform CreateRotationZ(float rot)
        {
            return new QuatTransform(Quaternion.CreateFromYawPitchRoll(0, 0, rot), Vector3.Zero);
        }
        /// <summary>
        /// Z軸回転QuatTransformを返します
        /// </summary>
        /// <param name="rot">Z回転</param>
        /// <param name="result">Z軸回転QuatTransform</param>
        public static void CreateRotationZ(float rot, out QuatTransform result)
        {
            result = new QuatTransform();
            Quaternion.CreateFromYawPitchRoll(0, 0, rot, out result.Rotation);
        }
    }
}
