//##################################################################################################
//
//   Custom Visualization Core library
//   Copyright (C) 2011-2013 Ceetron AS
//
//   This library may be used under the terms of either the GNU General Public License or
//   the GNU Lesser General Public License as follows:
//
//   GNU General Public License Usage
//   This library is free software: you can redistribute it and/or modify
//   it under the terms of the GNU General Public License as published by
//   the Free Software Foundation, either version 3 of the License, or
//   (at your option) any later version.
//
//   This library is distributed in the hope that it will be useful, but WITHOUT ANY
//   WARRANTY; without even the implied warranty of MERCHANTABILITY or
//   FITNESS FOR A PARTICULAR PURPOSE.
//
//   See the GNU General Public License at <<http://www.gnu.org/licenses/gpl.html>>
//   for more details.
//
//##################################################################################################


#include "../LibCore/cvfBase.h"
#include "../LibGeometry/cvfRay.h"
#include "../LibGeometry/cvfBoundingBox.h"
#include "../LibCore/cvfPlane.h"

namespace external {
namespace cvf {



//==================================================================================================
///
/// \class cvf::Ray
/// \ingroup Geometry
///
/// A ray that can be used for intersection testing.
///
//==================================================================================================

//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
Ray::Ray()
:   m_origin(Vec3d::ZERO),
    m_direction(-Vec3d::Z_AXIS),
    m_minDistance(cvf::UNDEFINED_DOUBLE),
    m_maxDistance(cvf::UNDEFINED_DOUBLE),
    m_minDistanceSquared(cvf::UNDEFINED_DOUBLE),
    m_maxDistanceSquared(cvf::UNDEFINED_DOUBLE),
    m_distanceLimitedRay(false)
{
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
Ray::Ray(const Ray& other)
    : Object{}
    , m_origin(other.origin())
    , m_direction(other.direction())
    , m_minDistance(other.m_minDistance)
    , m_maxDistance(other.m_maxDistance)
    , m_minDistanceSquared(other.m_minDistanceSquared)
    , m_maxDistanceSquared(other.m_maxDistanceSquared)
    , m_distanceLimitedRay(other.m_distanceLimitedRay)
{
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
Ray::~Ray()
{
}


//--------------------------------------------------------------------------------------------------
/// Sets the origin (starting point) of the ray
//--------------------------------------------------------------------------------------------------
void Ray::setOrigin(const Vec3d& orig) 
{ 
    m_origin = orig; 
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
const Vec3d& Ray::origin() const 
{ 
    return m_origin; 
}


//--------------------------------------------------------------------------------------------------
/// Sets the direction of the ray
//--------------------------------------------------------------------------------------------------
void Ray::setDirection(const Vec3d& dir) 
{ 
    m_direction = dir; 
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
const Vec3d& Ray::direction() const 
{ 
    return m_direction; 
}


//--------------------------------------------------------------------------------------------------
/// Transforms the origin and direction with the given transformation matrix
//--------------------------------------------------------------------------------------------------
void Ray::transform(const Mat4d& matrix)
{
    m_origin.transformPoint(matrix);
    m_direction.transformVector(matrix);
}


//--------------------------------------------------------------------------------------------------
/// Returns this ray transformed with the given transformation matrix
//--------------------------------------------------------------------------------------------------
const Ray Ray::getTransformed(const Mat4d& matrix) const
{
    Ray ray(*this);
    ray.transform(matrix);

    return ray;
}


//--------------------------------------------------------------------------------------------------
/// Returns true if the ray intersects the triangle. 
/// 
/// intersectionPoint (if not NULL) will be set to the ray intersection point on the triangle.
//--------------------------------------------------------------------------------------------------
bool Ray::triangleIntersect(const Vec3d& v1, const Vec3d& v2, const Vec3d& v3, Vec3d* intersectionPoint) const
{
    Vec3d v12 = v2 - v1;
    Vec3d v13 = v3 - v1;

    Vec3d n = (v12 ^ v13).getNormalized();
    
    double det = n * direction();
    
    if (det == 0.0f)
    {
        return false;
    }

    double t = n * ((v1 - origin()) / det);
    
    if (t < 0)
    {
        return false;
    }

    Vec3d fp = origin() + direction()*t;
    const Vec3d pts[] = { v1, v2, v3, v1 };
    
    int i;
    for(i = 0; i <  3; i++)
    {
        Vec3d bi_norm = -((pts[i+1]-pts[i]) ^ n).getNormalized();
        
        if (((fp-pts[i]) * bi_norm) < 0)
        {
            return false;
        }
    }

    // Distance filtering
    if (m_distanceLimitedRay)
    {
        double distanceSquared = origin().pointDistanceSquared(fp);

        if (m_minDistanceSquared < cvf::UNDEFINED_DOUBLE_THRESHOLD && distanceSquared < m_minDistanceSquared)
        {
            return false;   
        }

        if (m_maxDistanceSquared < cvf::UNDEFINED_DOUBLE_THRESHOLD && distanceSquared > m_maxDistanceSquared)
        {
            return false;   
        }
    }

    if (intersectionPoint) 
    {
        *intersectionPoint = fp;
    }

    return true;
}


//--------------------------------------------------------------------------------------------------
/// Returns true if the ray intersects the quad. 
/// 
/// intersectionPoint (if not NULL) will be set to the ray intersection point on the quad.
//--------------------------------------------------------------------------------------------------
bool Ray::quadIntersect(const Vec3d& v1, const Vec3d& v2, const Vec3d& v3, const Vec3d& v4, Vec3d* intersectionPoint) const
{
    Vec3d v12 = v2 - v1;
    Vec3d v13 = v3 - v1;

    Vec3d n = (v12 ^ v13).getNormalized();  // Quad Average normal

    double det = n * direction();  // Length of projection of Quad normal to ray direction

    if (det == 0.0f)
    {
        return false;  // Ray parallel to average quad plane. Not quite accurate if origin is close to quad 
    }

    double t = n * ((v1 - origin()) / det);

    if (t < 0)
    {
        return false;
    }

    Vec3d fp = origin() + direction()*t;
    const Vec3d pts[] = { v1, v2, v3, v4, v1 };

    int i;
    for(i = 0; i <  4; i++)
    {
        Vec3d bi_norm = -((pts[i+1] - pts[i]) ^ n).getNormalized(); // - (Edge x Quad normal) : Unit Vector in quad plane from edge into the quad

        if (((fp - pts[i]) * bi_norm) < 0)
        {
            return false;
        }
    }

    // Distance filtering
    if (m_distanceLimitedRay)
    {
        double distanceSquared = origin().pointDistanceSquared(fp);

        if (m_minDistanceSquared < cvf::UNDEFINED_DOUBLE_THRESHOLD && distanceSquared < m_minDistanceSquared)
        {
            return false;   
        }

        if (m_maxDistanceSquared < cvf::UNDEFINED_DOUBLE_THRESHOLD && distanceSquared > m_maxDistanceSquared)
        {
            return false;   
        }
    }

    if (intersectionPoint) 
    {
        *intersectionPoint = fp;
    }

    return true;
}


//--------------------------------------------------------------------------------------------------
/// Returns true if the ray intersects the bounding box. 
/// 
/// intersectionPoint (if not NULL) will be set to the ray intersection point on the bounding box.
//--------------------------------------------------------------------------------------------------
bool Ray::boxIntersect(const BoundingBox& box, Vec3d* intersectionPoint) const
{
    if (!box.isValid()) return false;

    const int RIGHT = 0;
    const int LEFT = 1;
    const int MIDDLE = 2;


    // Find candidate planes; this loop can be avoided if rays cast all from the eye(assume perpsective view)
    bool inside = true;
    char quadrant[3]{};
    double candidatePlane[3] = {0.0, 0.0, 0.0};

    Vec3d min = box.min();
    Vec3d max = box.max();

    int i;
    for (i = 0; i < 3; i++)
    {
        if(m_origin[i] < min[i]) 
        {
            quadrant[i] = LEFT;
            candidatePlane[i] = min[i];
            inside = false;
        }
        else if (m_origin[i] > max[i]) 
        {
            quadrant[i] = RIGHT;
            candidatePlane[i] = max[i];
            inside = false;
        }
        else	
        {
            quadrant[i] = MIDDLE;
        }
    }

    // Ray origin inside bounding box
    if (inside)	
    {
        // Return intersection point
        if (intersectionPoint) *intersectionPoint = m_origin;

        return true;
    }

    // Calculate T distances to candidate planes
    double maxT[3]{-1.0f, -1.0f, -1.0f};
    for (i = 0; i < 3; i++)
    {
        if (quadrant[i] != MIDDLE && m_direction[i] !=0.0f)
        {
            maxT[i] = (candidatePlane[i] - m_origin[i]) / m_direction[i];
        }
    }

    // Get largest of the maxT's for final choice of intersection
    int whichPlane = 0;
    for (i = 1; i < 3; i++)
    {
        if (maxT[whichPlane] < maxT[i]) whichPlane = i;
    }

    // Check final candidate actually inside box
    if (maxT[whichPlane] < 0.0f) return false;

    Vec3d hitPoint;

    for (i = 0; i < 3; i++)
    {
        if (whichPlane != i) 
        {
            hitPoint[i] = m_origin[i] + maxT[whichPlane] * m_direction[i];

            if (hitPoint[i] < min[i] || hitPoint[i] > max[i]) return false;
        } 
        else 
        {
            hitPoint[i] = candidatePlane[i];
        }
    }

    // Distance filtering
    if (m_distanceLimitedRay)
    {
        double distanceSquared = origin().pointDistanceSquared(hitPoint);

        if (m_minDistanceSquared < cvf::UNDEFINED_DOUBLE_THRESHOLD && distanceSquared < m_minDistanceSquared)
        {
            return false;   
        }

        if (m_maxDistanceSquared < cvf::UNDEFINED_DOUBLE_THRESHOLD && distanceSquared > m_maxDistanceSquared)
        {
            return false;   
        }
    }

    if (intersectionPoint)
    {
        *intersectionPoint = hitPoint;
    }

    // Ray hits box
    return true;				
}


//--------------------------------------------------------------------------------------------------
/// Returns true if the ray intersects the ray
//--------------------------------------------------------------------------------------------------
bool Ray::planeIntersect(const Plane& plane, Vec3d* intersectionPoint) const
{
    Vec3d pn = plane.normal();
    double D = plane.D();

    double vd = pn*m_direction;

    // If vd > 0, the plane normal is pointing 'away' from the ray
    // We want the plane normal facing the ray so flip direction of the plane
    if (vd > 0) 
    {
        pn *= -1;
        D *= -1;
        vd *= -1;
    }

    // No intersection ray and plane normal are parallel 
    if (Math::abs(vd) > std::numeric_limits<double>::epsilon())
    {
        double v0 = -(pn*m_origin + D);
        double t = v0/vd;
        if (t >= 0)
        {
            // Distance filtering
            if (m_distanceLimitedRay)
            {
                double distanceSquared = origin().pointDistanceSquared(m_origin + t*m_direction);

                if (m_minDistanceSquared < cvf::UNDEFINED_DOUBLE_THRESHOLD && distanceSquared < m_minDistanceSquared)
                {
                    return false;   
                }

                if (m_maxDistanceSquared < cvf::UNDEFINED_DOUBLE_THRESHOLD && distanceSquared > m_maxDistanceSquared)
                {
                    return false;   
                }
            }

            if (intersectionPoint)
            {
                *intersectionPoint = m_origin + t*m_direction;
            }

            return true;
        }
    }

    return false;
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
String Ray::debugString() const
{
    String str = "Ray: ";
    str += " origin: x=" + String::number(m_origin.x()) + " y=" + String::number(m_origin.y()) + " z=" + String::number(m_origin.z());
    str += " direction: x=" + String::number(m_direction.x()) + " y=" + String::number(m_direction.y()) + " z=" + String::number(m_direction.z());
    str += " minDist: " + String::number(m_minDistance);
    str += " maxDist: " + String::number(m_maxDistance);

    return str;
}


//--------------------------------------------------------------------------------------------------
/// Set the minimum distance (from the origin) that the ray will hit. 
/// 
/// Useful for limiting the ray when picking on models with clipping planes.
/// Set to tsv::UNDEFINED_DOUBLE
//--------------------------------------------------------------------------------------------------
void Ray::setMinimumDistance(double distance)
{
    m_minDistance = distance;

    if (m_minDistance >= 0 && m_minDistance < cvf::UNDEFINED_DOUBLE_THRESHOLD)
    {
        m_minDistanceSquared = m_minDistance*m_minDistance;
    }
    else
    {
        m_minDistanceSquared = cvf::UNDEFINED_DOUBLE;
    }

    m_distanceLimitedRay = (m_minDistanceSquared < cvf::UNDEFINED_DOUBLE_THRESHOLD) || (m_maxDistanceSquared < cvf::UNDEFINED_DOUBLE_THRESHOLD);
}


//--------------------------------------------------------------------------------------------------
/// Set the maximum distance (from the origin) that the ray will hit. 
/// 
/// Useful for limiting the ray when picking on models with clipping planes
//--------------------------------------------------------------------------------------------------
void Ray::setMaximumDistance(double distance)
{
    m_maxDistance = distance;

    if (m_maxDistance >= 0 && m_maxDistance < cvf::UNDEFINED_DOUBLE_THRESHOLD)
    {
        m_maxDistanceSquared = m_maxDistance*m_maxDistance;
    }
    else
    {
        m_maxDistanceSquared = cvf::UNDEFINED_DOUBLE;
    }

    m_distanceLimitedRay = (m_minDistanceSquared < cvf::UNDEFINED_DOUBLE_THRESHOLD) || (m_maxDistanceSquared < cvf::UNDEFINED_DOUBLE_THRESHOLD);
}


//--------------------------------------------------------------------------------------------------
/// Minimum distance (from the origin) that the ray will hit. 
//--------------------------------------------------------------------------------------------------
double Ray::minimumDistance() const
{
    return m_minDistance;
}


//--------------------------------------------------------------------------------------------------
/// Maximum distance (from the origin) that the ray will hit
//--------------------------------------------------------------------------------------------------
double Ray::maximumDistance() const
{
    return m_maxDistance;
}

} // namespace cvf
} //namespace external
