//
// SPDX-License-Identifier: BSD-3-Clause
// Copyright Contributors to the OpenEXR Project.
//

//
// A 3D plane class template
//

#ifndef INCLUDED_IMATHPLANE_H
#define INCLUDED_IMATHPLANE_H

#include "ImathExport.h"
#include "ImathNamespace.h"

#include "ImathLine.h"
#include "ImathVec.h"

IMATH_INTERNAL_NAMESPACE_HEADER_ENTER

///
/// The `Plane3` class represents a half space in 3D, so the normal
/// may point either towards or away from origin.  The plane `P` can
/// be represented by Plane3 as either `p` or `-p` corresponding to
/// the two half-spaces on either side of the plane. Any function
/// which computes a distance will return either negative or positive
/// values for the distance indicating which half-space the point is
/// in. Note that reflection, and intersection functions will operate
/// as expected.

template <class T> class IMATH_EXPORT_TEMPLATE_TYPE Plane3
{
  public:

    /// @{
    /// @name Direct access to member fields
    
    /// The normal to the plane
    Vec3<T> normal;
    
    /// The distance from the origin to the plane
    T distance;

    /// @}

    /// @{
    ///	@name Constructors

    /// Uninitialized by default
    IMATH_HOSTDEVICE Plane3() IMATH_NOEXCEPT {}

    /// Initialize with a normal and distance
    IMATH_HOSTDEVICE IMATH_CONSTEXPR14 Plane3 (const Vec3<T>& normal, T distance) IMATH_NOEXCEPT;

    /// Initialize with a point and a normal
    IMATH_HOSTDEVICE IMATH_CONSTEXPR14 Plane3 (const Vec3<T>& point, const Vec3<T>& normal) IMATH_NOEXCEPT;
    
    /// Initialize with three points
    IMATH_HOSTDEVICE IMATH_CONSTEXPR14 Plane3 (const Vec3<T>& point1,
                                               const Vec3<T>& point2,
                                               const Vec3<T>& point3) IMATH_NOEXCEPT;

    /// @}
    
    /// @{
    /// @name Manipulation
    
    /// Set via a given normal and distance
    IMATH_HOSTDEVICE void set (const Vec3<T>& normal, T distance) IMATH_NOEXCEPT;

    /// Set via a given point and normal
    IMATH_HOSTDEVICE void set (const Vec3<T>& point, const Vec3<T>& normal) IMATH_NOEXCEPT;

    /// Set via three points
    IMATH_HOSTDEVICE void set (const Vec3<T>& point1, const Vec3<T>& point2, const Vec3<T>& point3) IMATH_NOEXCEPT;

    /// @}
    
    /// @{
    /// @name Utility Methods
    
    /// Determine if a line intersects the plane.
    /// @param line The line
    /// @param[out] intersection The point of intersection
    /// @return True if the line intersects the plane.
    IMATH_HOSTDEVICE IMATH_CONSTEXPR14 bool
    intersect (const Line3<T>& line, Vec3<T>& intersection) const IMATH_NOEXCEPT;

    /// Determine if a line intersects the plane.
    /// @param line The line
    /// @param[out] parameter The parametric value of the point of intersection
    /// @return True if the line intersects the plane.
    IMATH_HOSTDEVICE IMATH_CONSTEXPR14 bool intersectT (const Line3<T>& line, T& parameter) const IMATH_NOEXCEPT;

    /// Return the distance from a point to the plane.
    IMATH_HOSTDEVICE constexpr T distanceTo (const Vec3<T>& point) const IMATH_NOEXCEPT;

    /// Reflect the given point around the plane.
    IMATH_HOSTDEVICE constexpr Vec3<T> reflectPoint (const Vec3<T>& point) const IMATH_NOEXCEPT;

    /// Reflect the direction vector around the plane
    IMATH_HOSTDEVICE constexpr Vec3<T> reflectVector (const Vec3<T>& vec) const IMATH_NOEXCEPT;

    /// @}
};

/// Plane of type float
typedef Plane3<float> Plane3f;

/// Plane of type double
typedef Plane3<double> Plane3d;

//---------------
// Implementation
//---------------

template <class T>
IMATH_HOSTDEVICE IMATH_CONSTEXPR14 inline Plane3<T>::Plane3 (const Vec3<T>& p0, const Vec3<T>& p1, const Vec3<T>& p2) IMATH_NOEXCEPT
{
    set (p0, p1, p2);
}

template <class T>
IMATH_HOSTDEVICE IMATH_CONSTEXPR14 inline Plane3<T>::Plane3 (const Vec3<T>& n, T d) IMATH_NOEXCEPT
{
    set (n, d);
}

template <class T>
IMATH_HOSTDEVICE IMATH_CONSTEXPR14 inline Plane3<T>::Plane3 (const Vec3<T>& p, const Vec3<T>& n) IMATH_NOEXCEPT
{
    set (p, n);
}

template <class T>
IMATH_HOSTDEVICE inline void
Plane3<T>::set (const Vec3<T>& point1, const Vec3<T>& point2, const Vec3<T>& point3) IMATH_NOEXCEPT
{
    normal = (point2 - point1) % (point3 - point1);
    normal.normalize();
    distance = normal ^ point1;
}

template <class T>
IMATH_HOSTDEVICE inline void
Plane3<T>::set (const Vec3<T>& point, const Vec3<T>& n) IMATH_NOEXCEPT
{
    normal = n;
    normal.normalize();
    distance = normal ^ point;
}

template <class T>
IMATH_HOSTDEVICE inline void
Plane3<T>::set (const Vec3<T>& n, T d) IMATH_NOEXCEPT
{
    normal = n;
    normal.normalize();
    distance = d;
}

template <class T>
IMATH_HOSTDEVICE constexpr inline T
Plane3<T>::distanceTo (const Vec3<T>& point) const IMATH_NOEXCEPT
{
    return (point ^ normal) - distance;
}

template <class T>
IMATH_HOSTDEVICE constexpr inline Vec3<T>
Plane3<T>::reflectPoint (const Vec3<T>& point) const IMATH_NOEXCEPT
{
    return normal * distanceTo (point) * -2.0 + point;
}

template <class T>
IMATH_HOSTDEVICE constexpr inline Vec3<T>
Plane3<T>::reflectVector (const Vec3<T>& v) const IMATH_NOEXCEPT
{
    return normal * (normal ^ v) * 2.0 - v;
}

template <class T>
IMATH_HOSTDEVICE IMATH_CONSTEXPR14 inline bool
Plane3<T>::intersect (const Line3<T>& line, Vec3<T>& point) const IMATH_NOEXCEPT
{
    T d = normal ^ line.dir;
    if (d == 0.0)
        return false;
    T t   = -((normal ^ line.pos) - distance) / d;
    point = line (t);
    return true;
}

template <class T>
IMATH_HOSTDEVICE IMATH_CONSTEXPR14 inline bool
Plane3<T>::intersectT (const Line3<T>& line, T& t) const IMATH_NOEXCEPT
{
    T d = normal ^ line.dir;
    if (d == 0.0)
        return false;
    t = -((normal ^ line.pos) - distance) / d;
    return true;
}

/// Stream output, as "(normal distance)"
template <class T>
std::ostream&
operator<< (std::ostream& o, const Plane3<T>& plane)
{
    return o << "(" << plane.normal << ", " << plane.distance << ")";
}

/// Transform a plane by a matrix
template <class T>
IMATH_HOSTDEVICE IMATH_CONSTEXPR14 Plane3<T>
operator* (const Plane3<T>& plane, const Matrix44<T>& M) IMATH_NOEXCEPT
{
    //                        T
    //	                    -1
    //	Could also compute M    but that would suck.
    //

    Vec3<T> dir1 = Vec3<T> (1, 0, 0) % plane.normal;
    T dir1Len    = dir1 ^ dir1;

    Vec3<T> tmp = Vec3<T> (0, 1, 0) % plane.normal;
    T tmpLen    = tmp ^ tmp;

    if (tmpLen > dir1Len)
    {
        dir1    = tmp;
        dir1Len = tmpLen;
    }

    tmp    = Vec3<T> (0, 0, 1) % plane.normal;
    tmpLen = tmp ^ tmp;

    if (tmpLen > dir1Len)
    {
        dir1 = tmp;
    }

    Vec3<T> dir2  = dir1 % plane.normal;
    Vec3<T> point = plane.distance * plane.normal;

    return Plane3<T> (point * M, (point + dir2) * M, (point + dir1) * M);
}

/// Reflect the pla
template <class T>
IMATH_HOSTDEVICE constexpr inline Plane3<T>
operator- (const Plane3<T>& plane) IMATH_NOEXCEPT
{
    return Plane3<T> (-plane.normal, -plane.distance);
}

IMATH_INTERNAL_NAMESPACE_HEADER_EXIT

#endif // INCLUDED_IMATHPLANE_H