using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Unity.IL2CPP.CompilerServices;
namespace Unity.Mathematics.Geometry
{
    /// 
    /// A plane represented by a normal vector and a distance along the normal from the origin.
    /// 
    /// 
    /// A plane splits the 3D space in half.  The normal vector points to the positive half and the other half is
    /// considered negative.
    /// 
    [DebuggerDisplay("{Normal}, {Distance}")]
    [Serializable]
    [Il2CppEagerStaticClassConstruction]
    public struct Plane
    {
        /// 
        /// A plane in the form Ax + By + Cz + Dw = 0.
        /// 
        /// 
        /// Stores the plane coefficients A, B, C, D where (A, B, C) is a unit normal vector and D is the distance
        /// from the origin.  A plane stored with a unit normal vector is called a normalized plane.
        /// 
        public float4 NormalAndDistance;
        /// 
        /// Constructs a Plane from arbitrary coefficients A, B, C, D of the plane equation Ax + By + Cz + Dw = 0.
        /// 
        /// 
        /// The constructed plane will be the normalized form of the plane specified by the given coefficients.
        /// 
        /// Coefficient A from plane equation.
        /// Coefficient B from plane equation.
        /// Coefficient C from plane equation.
        /// Coefficient D from plane equation.
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Plane(float coefficientA, float coefficientB, float coefficientC, float coefficientD)
        {
            NormalAndDistance = Normalize(new float4(coefficientA, coefficientB, coefficientC, coefficientD));
        }
        /// 
        /// Constructs a plane with a normal vector and distance from the origin.
        /// 
        /// 
        /// The constructed plane will be the normalized form of the plane specified by the inputs.
        /// 
        /// A non-zero vector that is perpendicular to the plane.  It may be any length.
        /// Distance from the origin along the normal.  A negative value moves the plane in the
        /// same direction as the normal while a positive value moves it in the opposite direction.
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Plane(float3 normal, float distance)
        {
            NormalAndDistance = Normalize(new float4(normal, distance));
        }
        /// 
        /// Constructs a plane with a normal vector and a point that lies in the plane.
        /// 
        /// 
        /// The constructed plane will be the normalized form of the plane specified by the inputs.
        /// 
        /// A non-zero vector that is perpendicular to the plane.  It may be any length.
        /// A point that lies in the plane.
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Plane(float3 normal, float3 pointInPlane)
        : this(normal, -math.dot(normal, pointInPlane))
        {
        }
        /// 
        /// Constructs a plane with two vectors and a point that all lie in the plane.
        /// 
        /// 
        /// The constructed plane will be the normalized form of the plane specified by the inputs.
        /// 
        /// A non-zero vector that lies in the plane.  It may be any length.
        /// A non-zero vector that lies in the plane.  It may be any length and must not be a scalar multiple of .
        /// A point that lies in the plane.
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Plane(float3 vector1InPlane, float3 vector2InPlane, float3 pointInPlane)
        : this(math.cross(vector1InPlane, vector2InPlane), pointInPlane)
        {
        }
        /// 
        /// Creates a normalized Plane directly without normalization cost.
        /// 
        /// 
        /// If you have a unit length normal vector, you can create a Plane faster than using one of its constructors
        /// by calling this function.
        /// 
        /// A non-zero vector that is perpendicular to the plane.  It must be unit length.
        /// Distance from the origin along the normal.  A negative value moves the plane in the
        /// same direction as the normal while a positive value moves it in the opposite direction.
        /// Normalized Plane constructed from given inputs.
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Plane CreateFromUnitNormalAndDistance(float3 unitNormal, float distance)
        {
            return new Plane { NormalAndDistance = new float4(unitNormal, distance) };
        }
        /// 
        /// Creates a normalized Plane without normalization cost.
        /// 
        /// 
        /// If you have a unit length normal vector, you can create a Plane faster than using one of its constructors
        /// by calling this function.
        /// 
        /// A non-zero vector that is perpendicular to the plane.  It must be unit length.
        /// A point that lies in the plane.
        /// Normalized Plane constructed from given inputs.
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Plane CreateFromUnitNormalAndPointInPlane(float3 unitNormal, float3 pointInPlane)
        {
            return new Plane { NormalAndDistance = new float4(unitNormal, -math.dot(unitNormal, pointInPlane)) };
        }
        /// 
        /// Get/set the normal vector of the plane.
        /// 
        /// 
        /// It is assumed that the normal is unit length.  If you set a new plane such that Ax + By + Cz + Dw = 0 but
        /// (A, B, C) is not unit length, then you must normalize the plane by calling .
        /// 
        public float3 Normal
        {
            get => NormalAndDistance.xyz;
            set => NormalAndDistance.xyz = value;
        }
        /// 
        /// Get/set the distance of the plane from the origin.  May be a negative value.
        /// 
        /// 
        /// It is assumed that the normal is unit length.  If you set a new plane such that Ax + By + Cz + Dw = 0 but
        /// (A, B, C) is not unit length, then you must normalize the plane by calling .
        /// 
        public float Distance
        {
            get => NormalAndDistance.w;
            set => NormalAndDistance.w = value;
        }
        /// 
        /// Normalizes the given Plane.
        /// 
        /// Plane to normalize.
        /// Normalized Plane.
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Plane Normalize(Plane plane)
        {
            return new Plane { NormalAndDistance = Normalize(plane.NormalAndDistance) };
        }
        /// 
        /// Normalizes the plane represented by the given plane coefficients.
        /// 
        /// 
        /// The plane coefficients are A, B, C, D and stored in that order in the .
        /// 
        /// Plane coefficients A, B, C, D stored in x, y, z, w (respectively).
        /// Normalized plane coefficients.
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static float4 Normalize(float4 planeCoefficients)
        {
            float recipLength = math.rsqrt(math.lengthsq(planeCoefficients.xyz));
            return new Plane { NormalAndDistance = planeCoefficients * recipLength };
        }
        /// 
        /// Get the signed distance from the point to the plane.
        /// 
        /// 
        /// Plane must be normalized prior to calling this function.  Distance is positive if point is on side of the
        /// plane the normal points to, negative if on the opposite side and zero if the point lies in the plane.
        /// Avoid comparing equality with 0.0f when testing if a point lies exactly in the plane and use an approximate
        /// comparison instead.
        /// 
        /// Point to find the signed distance with.
        /// Signed distance of the point from the plane.
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public float SignedDistanceToPoint(float3 point)
        {
            CheckPlaneIsNormalized();
            return math.dot(NormalAndDistance, new float4(point, 1.0f));
        }
        /// 
        /// Projects the given point onto the plane.
        /// 
        /// 
        /// Plane must be normalized prior to calling this function.  The result is the position closest to the point
        /// that still lies in the plane.
        /// 
        /// Point to project onto the plane.
        /// Projected point that's inside the plane.
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public float3 Projection(float3 point)
        {
            CheckPlaneIsNormalized();
            return point - Normal * SignedDistanceToPoint(point);
        }
        /// 
        /// Flips the plane so the normal points in the opposite direction.
        /// 
        public Plane Flipped => new Plane { NormalAndDistance = -NormalAndDistance };
        /// 
        /// Implicitly converts a  to .
        /// 
        /// Plane to convert.
        /// A  representing the plane.
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static implicit operator float4(Plane plane) => plane.NormalAndDistance;
        [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
        void CheckPlaneIsNormalized()
        {
            float ll = math.lengthsq(Normal.xyz);
            const float lowerBound = 0.999f * 0.999f;
            const float upperBound = 1.001f * 1.001f;
            if (ll < lowerBound || ll > upperBound)
            {
                throw new System.ArgumentException("Plane must be normalized. Call Plane.Normalize() to normalize plane.");
            }
        }
    }
}