using UnityEngine;

namespace Game.Plugins.JNGame.Sync.Frame.AStar.Util
{
    /// <summary>  
    /// 在世界空间和二维移动平面之间进行转换。  
    /// 保证转换只包含旋转  
    /// 因此不使用缩放或偏移。此接口主要用于  
    /// 方便编写能够处理XZ平面和XY平面上的移动的脚本。  
    ///  
    /// 参见：<see cref="Pathfinding.Util.GraphTransform"/>  
    /// </summary>  
    public interface IMovementPlane {  
        /// <summary>  
        /// 将三维向量转换到二维平面上。  
        /// </summary>  
        /// <param name="p">要转换的三维向量。</param>  
        /// <returns>转换后的二维向量。</returns>  
        Vector2 ToPlane(Vector3 p);  
  
        /// <summary>  
        /// 将三维向量转换到二维平面上，并返回其高度信息。  
        /// </summary>  
        /// <param name="p">要转换的三维向量。</param>  
        /// <param name="elevation">转换后的二维向量的高度信息。</param>  
        /// <returns>转换后的二维向量。</returns>  
        Vector2 ToPlane(Vector3 p, out float elevation);  
  
        /// <summary>  
        /// 将二维向量和高度信息转换回三维世界空间。  
        /// </summary>  
        /// <param name="p">要转换的二维向量。</param>  
        /// <param name="elevation">二维向量的高度信息。</param>  
        /// <param name="elevation">默认值为0。</param>  
        /// <returns>转换后的三维向量。</returns>  
        Vector3 ToWorld(Vector2 p, float elevation = 0);  
    }
    
    /// <summary>  
    /// 通用的三维坐标变换  
    /// </summary>  
    public interface ITransform {  
        // 将一个三维坐标位置进行变换  
        Vector3 Transform(Vector3 position);  
      
        // 对一个已经变换过的三维坐标位置进行逆变换，恢复到原始坐标空间  
        Vector3 InverseTransform(Vector3 position);  
    }
    
    /// <summary>  
    /// 定义从图空间到世界空间的变换。  
    /// 这本质上只是一个矩阵的简单封装，但它提供了几个有用的实用程序。  
    /// </summary>  
    public class GraphTransform : IMovementPlane, ITransform {  
  
        /// <summary>  
        /// 如果这个变换是恒等变换（即它不执行任何操作），则为真  
        /// </summary>  
        public readonly bool identity;  
  
        /// <summary>  
        /// 如果这个变换是一个纯平移变换，没有任何缩放或旋转，则为真  
        /// </summary>  
        public readonly bool onlyTranslational;  
        
        // 只读布尔值，表示变换是否仅涉及X和Y轴  
        readonly bool isXY;  
        // 只读布尔值，表示变换是否仅涉及X和Z轴  
        readonly bool isXZ;  
        // 4x4变换矩阵，用于从图空间到世界空间的变换  
        readonly Matrix4x4 matrix;  
        // 变换矩阵的逆矩阵，用于从世界空间到图空间的变换  
        readonly Matrix4x4 inverseMatrix;  
        // 表示变换后的“上”方向的向量，通常与旋转部分相关  
        readonly Vector3 up;  
        // 变换的平移部分，表示在X、Y、Z轴上的移动量  
        readonly Vector3 translation;  
        // 变换的平移部分，表示为整数类型，可能用于某些优化或特定的计算场景  
        readonly Int3 i3translation;  
        // 变换的旋转部分，使用四元数表示  
        readonly Quaternion rotation;  
        // 旋转部分的逆四元数，用于反向旋转  
        readonly Quaternion inverseRotation;  

        // 恒等变换的实例，即不改变任何点位置的变换  
        public static readonly GraphTransform identityTransform = new GraphTransform(Matrix4x4.identity);
        
        /// <summary>  
        /// GraphTransform 类的构造函数，用于通过提供的 4x4 矩阵初始化变换。  
        /// </summary>  
        /// <param name="matrix">用于初始化变换的 4x4 矩阵。</param>  
        public GraphTransform(Matrix4x4 matrix) {  
            // 将传入的矩阵赋值给类的成员变量 matrix  
            this.matrix = matrix;  
      
            // 计算并赋值逆矩阵  
            inverseMatrix = matrix.inverse;  
      
            // 判断矩阵是否为恒等矩阵，并赋值给成员变量 identity  
            identity = matrix.isIdentity;  
      
            // 判断矩阵是否仅包含平移变换，并赋值给成员变量 onlyTranslational  
            onlyTranslational = MatrixIsTranslational(matrix);  
      
            // 计算变换后的“上”方向向量，并对其进行单位化  
            up = matrix.MultiplyVector(Vector3.up).normalized;  
      
            // 计算平移部分，通过矩阵乘以一个原点向量得到  
            translation = matrix.MultiplyPoint3x4(Vector3.zero);  
      
            // 将平移部分转换为整数类型并赋值给成员变量 i3translation  
            i3translation = (Int3)translation;  
      
            // 从矩阵中提取旋转部分。这只有在矩阵没有倾斜时才正确，但我们只想在移动平面上使用它，  
            // 只要 Up 轴与 Forward 轴垂直，一切就应该没问题。在项目中，只有当使用六边形或等距网格图时，  
            // 三个轴才不全部垂直，但在这两种情况下，只有 X 轴和 Z 轴不垂直。  
            // 使用 LookRotation 方法从两个向量（Forward 和 Up）中计算四元数表示的旋转  
            rotation = Quaternion.LookRotation(TransformVector(Vector3.forward), TransformVector(Vector3.up));  
      
            // 计算旋转部分的逆四元数  
            inverseRotation = Quaternion.Inverse(rotation);  
      
            // 一些用于移动平面计算的短路代码  
            // 检查旋转是否等于绕 Y 轴旋转 -90 度的四元数，如果是，则 isXY 为真  
            isXY = rotation == Quaternion.Euler(-90, 0, 0);  
      
            // 检查旋转是否等于恒等四元数，如果是，则 isXZ 为真  
            isXZ = rotation == Quaternion.Euler(0, 0, 0);  
        }  
  
        /// <summary>  
        /// 一个辅助方法，用于判断一个矩阵是否仅包含平移变换。  
        /// </summary>  
        /// <param name="matrix">要检查的 4x4 矩阵。</param>  
        /// <returns>如果矩阵仅包含平移变换，则返回 true；否则返回 false。</returns>  
        static bool MatrixIsTranslational (Matrix4x4 matrix) {
            return matrix.GetColumn(0) == new Vector4(1, 0, 0, 0) && matrix.GetColumn(1) == new Vector4(0, 1, 0, 0) && matrix.GetColumn(2) == new Vector4(0, 0, 1, 0) && matrix.m33 == 1;
        }
  
        /// <summary>  
        /// 一个辅助方法，用于应用矩阵变换到一个向量上。  
        /// </summary>  
        /// <param name="vector">要变换的向量。</param>  
        /// <returns>变换后的向量。</returns>  
        private Vector3 TransformVector(Vector3 point) {  
            if (onlyTranslational) return point;
            return matrix.MultiplyVector(point);
        }
        
        /// <summary>  
        /// 将从世界空间转换到图形的'地面'平面上。  
        /// 转换仅是一个旋转，不使用缩放或偏移。  
        ///  
        /// 对于使用(-90, 0, 0)旋转的图形，这将把坐标(x,y,z)转换为(x,y)。  
        /// 对于使用(0,0,0)旋转的图形，这将把坐标(x,y,z)转换为(x,z)。  
        /// 更一般地说，对于使用四元数旋转R的图形，这将把向量V转换为R * V（即使用旋转R旋转向量V）。  
        /// </summary>  
        Vector2 IMovementPlane.ToPlane(Vector3 point) {  
            // 这些特殊情况涵盖了实践中使用的大多数图形方向。  
            // 将它们放在这里可以在不影响通用情况的情况下提高这些情况下的性能2.5倍。  
            if (isXY) return new Vector2(point.x, point.y);  
   
            // 如果不是isXZ，则使用逆旋转将点进行旋转  
            if (!isXZ) point = inverseRotation * point;  
   
            // 返回旋转后的点的x和z分量  
            return new Vector2(point.x, point.z);  
        }
        
        /// <summary>  
        /// 将从世界空间中的点转换到图形的'地面'平面上，并输出该点的海拔高度。  
        /// 转换仅是一个旋转，不使用缩放或偏移。  
        /// </summary>  
        /// <param name="point">世界空间中的点</param>  
        /// <param name="elevation">输出参数，表示点的海拔高度</param>  
        /// <returns>返回转换后的二维坐标</returns>  
        Vector2 IMovementPlane.ToPlane(Vector3 point, out float elevation) {  
            // 如果不是isXZ，则应用逆旋转来转换点  
            if (!isXZ) point = inverseRotation * point;  
      
            // 获取转换后的点的y分量作为海拔高度  
            elevation = point.y;  
      
            // 返回转换后的点的x和z分量作为二维向量  
            return new Vector2(point.x, point.z);  
        }
        
        /// <summary>  
        /// 将图形'地面'平面上的点转换回世界空间中的三维坐标。  
        /// 转换仅是一个旋转，不使用缩放或偏移。  
        /// </summary>  
        /// <param name="point">图形'地面'平面上的二维点</param>  
        /// <param name="elevation">点的海拔高度</param>  
        /// <returns>返回转换后的世界空间中的三维坐标</returns>  
        Vector3 IMovementPlane.ToWorld(Vector2 point, float elevation) {  
            // 使用旋转矩阵或四元数将二维点和海拔高度转换为世界空间中的三维坐标  
            return rotation * new Vector3(point.x, elevation, point.y);  
        }
        
        // 对Int3数组进行变换  
        public void Transform(Int3[] arr) {  
            // 如果只进行平移变换  
            if (onlyTranslational) {  
                // 从数组的最后一个元素开始向前遍历  
                for (int i = arr.Length - 1; i >= 0; i--) {  
                    // 将当前元素与平移向量i3translation相加，并将结果存回原位置  
                    arr[i] += i3translation;  
                }  
            } else {  
                // 否则，使用矩阵对数组中的每个元素进行变换  
                for (int i = arr.Length - 1; i >= 0; i--) {  
                    // 将数组中的当前元素转换为Vector3，使用矩阵进行变换，然后再将结果转换回Int3，并存回原位置  
                    arr[i] = (Int3)matrix.MultiplyPoint3x4((Vector3)arr[i]);  
                }  
            }  
        }  
  
        // 对Vector3数组进行变换  
        public void Transform(Vector3[] arr) {  
            // 如果只进行平移变换  
            if (onlyTranslational) {  
                // 从数组的最后一个元素开始向前遍历  
                for (int i = arr.Length - 1; i >= 0; i--) {  
                    // 将当前元素与平移向量translation相加，并将结果存回原位置  
                    arr[i] += translation;  
                }  
            } else {  
                // 否则，使用矩阵对数组中的每个元素进行变换  
                for (int i = arr.Length - 1; i >= 0; i--) {  
                    // 使用矩阵对当前元素进行变换，并将结果存回原位置  
                    arr[i] = matrix.MultiplyPoint3x4(arr[i]);  
                }  
            }  
        }  
  
        // 对一个Vector3点进行逆变换  
        public Vector3 Transform (Vector3 point) {
            if (onlyTranslational) return point + translation;
            return matrix.MultiplyPoint3x4(point);
        }

        public Vector3 InverseTransform(Vector3 point) {  
            // 如果只进行平移变换  
            if (onlyTranslational) {  
                // 从点中减去平移向量translation，得到逆变换后的结果  
                return point - translation;  
            } else {  
                // 否则，使用逆矩阵对点进行逆变换  
                return inverseMatrix.MultiplyPoint3x4(point);  
            }  
        }  
  
        // 对一个Vector3方向向量进行逆变换  
        public Vector3 InverseTransformVector(Vector3 dir) {  
            // 如果只进行平移变换，方向向量不受影响，直接返回原向量  
            if (onlyTranslational) {  
                return dir;  
            } else {  
                // 否则，使用逆矩阵对方向向量进行逆变换  
                return inverseMatrix.MultiplyVector(dir);  
            }  
        }
    }
    
}