/////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) Statoil ASA
//  Copyright (C) Ceetron Solutions AS
//
//  ResInsight 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.
//
//  ResInsight 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 "cvfGeometryTools.h"

namespace external {
namespace cvf
{
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::Vec3d
    GeometryTools::computeFaceCenter( const cvf::Vec3d& v0, const cvf::Vec3d& v1, const cvf::Vec3d& v2, const cvf::Vec3d& v3 )
{
    cvf::Vec3d centerCoord = v0;
    centerCoord += v1;
    centerCoord += v2;
    centerCoord += v3;
    centerCoord *= 0.25;

    return centerCoord;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::Vec3d GeometryTools::computeTriangleCenter( const cvf::Vec3d& v0, const cvf::Vec3d& v1, const cvf::Vec3d& v2 )
{
    cvf::Vec3d centerCoord = v0;
    centerCoord += v1;
    centerCoord += v2;
    centerCoord /= 3.0;

    return centerCoord;
}

//--------------------------------------------------------------------------------------------------
/// Ez = Plane normal, Ex = in XY plane (horizontal), Ey = semi vertical upwards
//--------------------------------------------------------------------------------------------------
cvf::Mat3f GeometryTools::computePlaneHorizontalRotationMx( const cvf::Vec3f& inPlaneVec0, const cvf::Vec3f& inPlaneVec1 )
{
    cvf::Vec3f Ez = inPlaneVec0 ^ inPlaneVec1;

    if ( !Ez.normalize() ) return cvf::Mat3f::IDENTITY;

    cvf::Vec3f Ex = Ez ^ cvf::Vec3f::Z_AXIS;

    if ( !Ex.normalize() ) return cvf::Mat3f::IDENTITY;

    cvf::Vec3f Ey = Ez ^ Ex;

    if ( Ey[2] < 0.0f ) // Semi vertical is down
    {
        return cvf::Mat3f( -Ex[0], -Ex[1], -Ex[2], -Ey[0], -Ey[1], -Ey[2], Ez[0], Ez[1], Ez[2] );
    }
    else
    {
        return cvf::Mat3f( Ex[0], Ex[1], Ex[2], Ey[0], Ey[1], Ey[2], Ez[0], Ez[1], Ez[2] );
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------

int GeometryTools::findClosestAxis( const cvf::Vec3d& vec )
{
    int    closestAxis  = 0;
    double maxComponent = fabs( vec.x() );

    if ( fabs( vec.y() ) > maxComponent )
    {
        maxComponent = (float)fabs( vec.y() );
        closestAxis  = 1;
    }

    if ( fabs( vec.z() ) > maxComponent )
    {
        closestAxis = 2;
    }

    return closestAxis;
}

//--------------------------------------------------------------------------------------------------
/// Return angle between vectors if v1 x v2 is same way as normal
/// else return 2PI - angle
/// This means if the angle is slightly "negative", using the right hand rule, this method will return
/// nearly 2*PI
//--------------------------------------------------------------------------------------------------

double const MY_PI = 4 * atan( 1.0 );

double GeometryTools::getAngle( const cvf::Vec3d& positiveNormalAxis, const cvf::Vec3d& v1, const cvf::Vec3d& v2 )
{
    bool       isOk = false;
    cvf::Vec3d v1N  = v1.getNormalized( &isOk );
    if ( !isOk ) return 0;
    cvf::Vec3d v2N = v2.getNormalized( &isOk );
    if ( !isOk ) return 0;

    double cosAng = v1N * v2N;
    // Guard acos against out-of-domain input
    if ( cosAng <= -1.0 )
    {
        cosAng = -1.0;
    }
    else if ( cosAng >= 1.0 )
    {
        cosAng = 1.0;
    }
    double angle = acos( cosAng );

    cvf::Vec3d crossProd = v1N ^ v2N;
    double     sign      = positiveNormalAxis * crossProd;
    if ( sign < 0 )
    {
        angle = 2 * MY_PI - angle;
    }
    return angle;
}

//--------------------------------------------------------------------------------------------------
/// Return angle in radians between vectors [0, Pi]
/// If v1 or v2 is zero, the method will return 0.
//--------------------------------------------------------------------------------------------------

double GeometryTools::getAngle( const cvf::Vec3d& v1, const cvf::Vec3d& v2 )
{
    bool isOk = false;

    cvf::Vec3d v1N = v1.getNormalized( &isOk );
    if ( !isOk ) return 0;

    cvf::Vec3d v2N = v2.getNormalized( &isOk );
    if ( !isOk ) return 0;

    double cosAng = v1N * v2N;
    // Guard acos against out-of-domain input
    if ( cosAng <= -1.0 )
    {
        cosAng = -1.0;
    }
    else if ( cosAng >= 1.0 )
    {
        cosAng = 1.0;
    }
    double angle = acos( cosAng );

    return angle;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double GeometryTools::signedAreaPlanarPolygon( const cvf::Vec3d& planeNormal, const std::vector<cvf::Vec3d>& polygon )
{
    int Z = findClosestAxis( planeNormal );
    int X = ( Z + 1 ) % 3;
    int Y = ( Z + 2 ) % 3;

    // Use Shoelace formula to calculate signed area.
    // https://en.wikipedia.org/wiki/Shoelace_formula
    double signedArea = 0.0;
    for ( size_t i = 0; i < polygon.size(); ++i )
    {
        signedArea += ( polygon[( i + 1 ) % polygon.size()][X] - polygon[i][X] ) *
                      ( polygon[( i + 1 ) % polygon.size()][Y] + polygon[i][Y] );
    }
    return signedArea;
}

//--------------------------------------------------------------------------------------------------
/// This method below is more correct than the one above, both in naming and behaviour.
/// Should be replaced, but is not done now to avoid possible sideeffects
/// The difference is the sign of the area. The one below retuns correct sign according to the plane normal
/// provided
//--------------------------------------------------------------------------------------------------
template <typename Vec3Type>
double closestAxisSignedAreaPlanarPolygon( const cvf::Vec3d& planeNormal, const std::vector<Vec3Type>& polygon )
{
    int Z = cvf::GeometryTools::findClosestAxis( planeNormal );
    int X = ( Z + 1 ) % 3;
    int Y = ( Z + 2 ) % 3;

    // Use Shoelace formula to calculate signed area.
    // https://en.wikipedia.org/wiki/Shoelace_formula
    double signedArea = 0.0;
    for ( size_t i = 0; i < polygon.size(); ++i )
    {
        signedArea += ( polygon[( i + 1 ) % polygon.size()][X] + polygon[i][X] ) *
                      ( polygon[( i + 1 ) % polygon.size()][Y] - polygon[i][Y] );
    }

    return ( planeNormal[Z] > 0 ) ? signedArea : -signedArea;
}

/*
   Determine the intersection point of two line segments
   From Paul Bourke, but modified to really handle coincident lines
   and lines with touching vertexes.
   Returns an intersection status telling what kind of intersection it is (if any)
   */

GeometryTools::IntersectionStatus inPlaneLineIntersect( double  x1,
                                                        double  y1,
                                                        double  x2,
                                                        double  y2,
                                                        double  x3,
                                                        double  y3,
                                                        double  x4,
                                                        double  y4,
                                                        double  l1NormalizedTolerance,
                                                        double  l2NormalizedTolerance,
                                                        double* x,
                                                        double* y,
                                                        double* fractionAlongLine1,
                                                        double* fractionAlongLine2 )
{
    double mua, mub;
    double denom, numera, numerb;

    denom  = ( y4 - y3 ) * ( x2 - x1 ) - ( x4 - x3 ) * ( y2 - y1 );
    numera = ( x4 - x3 ) * ( y1 - y3 ) - ( y4 - y3 ) * ( x1 - x3 );
    numerb = ( x2 - x1 ) * ( y1 - y3 ) - ( y2 - y1 ) * ( x1 - x3 );

    double EPS = 1e-40;

    // Are the line coincident?
    if ( fabs( numera ) < EPS && fabs( numerb ) < EPS && fabs( denom ) < EPS )
    {
#if 0
       *x = 0;
       *y = 0;
       *fractionAlongLine1 = 0;
       *fractionAlongLine2 = 0;
       return GeometryTools::LINES_OVERLAP;
#else
        cvf::Vec3d p12( x2 - x1, y2 - y1, 0 );
        cvf::Vec3d p13( x3 - x1, y3 - y1, 0 );
        cvf::Vec3d p34( x4 - x3, y4 - y3, 0 );

        double length12 = p12.length();
        double length34 = p34.length();

        // Check if the p1 p2 line is a point

        if ( length12 < EPS )
        {
            *x                  = x1;
            *y                  = y1;
            *fractionAlongLine1 = 1;
            *fractionAlongLine2 = p13.length() / p34.length();
            return GeometryTools::LINES_OVERLAP;
        }

        cvf::Vec3d p14( x4 - x1, y4 - y1, 0 );
        cvf::Vec3d p32( x2 - x3, y2 - y3, 0 );

        cvf::Vec3d e12        = p12.getNormalized();
        double     normDist13 = e12 * p13 / length12;
        double     normDist14 = e12 * p14 / length12;

        // Check if both points on the p3 p4 line is outside line p1 p2.
        if ( ( normDist13 < 0 - l1NormalizedTolerance && normDist14 < 0 - l1NormalizedTolerance ) ||
             ( normDist13 > 1 + l1NormalizedTolerance && normDist14 > 1 + l1NormalizedTolerance ) )
        {
            *x                  = 0;
            *y                  = 0;
            *fractionAlongLine1 = 0;
            *fractionAlongLine2 = 0;
            return GeometryTools::NO_INTERSECTION;
        }

        // double normDist31 = -e12*p13 / length34;

        // Set up fractions along lines to the edge2 vertex actually touching edge 1.
        /// if two, select the one furthest from the start
        bool pt3IsInside = false;
        bool pt4IsInside = false;
        if ( ( 0.0 - l1NormalizedTolerance ) <= normDist13 && normDist13 <= ( 1.0 + l1NormalizedTolerance ) )
            pt3IsInside = true;
        if ( ( 0.0 - l1NormalizedTolerance ) <= normDist14 && normDist14 <= ( 1.0 + l1NormalizedTolerance ) )
            pt4IsInside = true;

        if ( pt3IsInside && !pt4IsInside )
        {
            *fractionAlongLine1 = normDist13;
            *fractionAlongLine2 = 0.0;
            *x                  = x3;
            *y                  = y3;
        }
        else if ( pt4IsInside && !pt3IsInside )
        {
            *fractionAlongLine1 = normDist14;
            *fractionAlongLine2 = 1.0;
            *x                  = x4;
            *y                  = y4;
        }
        else if ( pt3IsInside && pt4IsInside )
        {
            // Return edge 2 vertex furthest along edge 1
            if ( normDist13 <= normDist14 )
            {
                *fractionAlongLine1 = normDist14;
                *fractionAlongLine2 = 1.0;
                *x                  = x4;
                *y                  = y4;
            }
            else
            {
                *fractionAlongLine1 = normDist13;
                *fractionAlongLine2 = 0.0;
                *x                  = x3;
                *y                  = y3;
            }
        }
        else // both outside on each side
        {
            double normDist32 = e12 * p32 / length34;
            // Return End of edge 1
            *fractionAlongLine1 = 1.0;
            *fractionAlongLine2 = normDist32;
            *x                  = x2;
            *y                  = y2;
        }

        return GeometryTools::LINES_OVERLAP;
#endif
    }

    /* Are the line parallel */
    if ( fabs( denom ) < EPS )
    {
        *x                  = 0;
        *y                  = 0;
        *fractionAlongLine1 = 0;
        *fractionAlongLine2 = 0;

        return GeometryTools::NO_INTERSECTION;
    }

    /* Is the intersection along the the segments */
    mua = numera / denom;
    mub = numerb / denom;

    *x                  = x1 + mua * ( x2 - x1 );
    *y                  = y1 + mua * ( y2 - y1 );
    *fractionAlongLine1 = mua;
    *fractionAlongLine2 = mub;

    if ( mua < 0 - l1NormalizedTolerance || 1 + l1NormalizedTolerance < mua || mub < 0 - l2NormalizedTolerance ||
         1 + l2NormalizedTolerance < mub )
    {
        return GeometryTools::LINES_INTERSECT_OUTSIDE;
    }
    else if ( fabs( mua ) < l1NormalizedTolerance || fabs( 1 - mua ) < l1NormalizedTolerance ||
              fabs( mub ) < l2NormalizedTolerance || fabs( 1 - mub ) < l2NormalizedTolerance )
    {
        if ( fabs( mua ) < l1NormalizedTolerance ) *fractionAlongLine1 = 0;
        if ( fabs( 1 - mua ) < l1NormalizedTolerance ) *fractionAlongLine1 = 1;
        if ( fabs( mub ) < l2NormalizedTolerance ) *fractionAlongLine2 = 0;
        if ( fabs( 1 - mub ) < l2NormalizedTolerance ) *fractionAlongLine2 = 1;
        return GeometryTools::LINES_TOUCH;
    }
    else
    {
        return GeometryTools::LINES_CROSSES;
    }
}
//----------------------------------------------------------------------------------------------------------
/// Supposed to find the intersection point if lines intersect
/// It returns the intersection status telling if the lines only touch or are overlapping
//----------------------------------------------------------------------------------------------------------

GeometryTools::IntersectionStatus GeometryTools::inPlaneLineIntersect3D( const cvf::Vec3d& planeNormal,
                                                                         const cvf::Vec3d& p1,
                                                                         const cvf::Vec3d& p2,
                                                                         const cvf::Vec3d& p3,
                                                                         const cvf::Vec3d& p4,
                                                                         cvf::Vec3d*       intersectionPoint,
                                                                         double*           fractionAlongLine1,
                                                                         double*           fractionAlongLine2,
                                                                         double            tolerance )
{
    CVF_ASSERT( intersectionPoint != nullptr );

    int    Z = findClosestAxis( planeNormal );
    int    X = ( Z + 1 ) % 3;
    int    Y = ( Z + 2 ) % 3;
    double x, y;

    // Todo: handle zero length edges
    double l1NormTol = tolerance / ( p2 - p1 ).length();
    double l2NormTol = tolerance / ( p4 - p3 ).length();

    IntersectionStatus intersectionStatus = inPlaneLineIntersect( p1[X],
                                                                  p1[Y],
                                                                  p2[X],
                                                                  p2[Y],
                                                                  p3[X],
                                                                  p3[Y],
                                                                  p4[X],
                                                                  p4[Y],
                                                                  l1NormTol,
                                                                  l2NormTol,
                                                                  &x,
                                                                  &y,
                                                                  fractionAlongLine1,
                                                                  fractionAlongLine2 );

    // Check if we have a valid intersection point
    if ( intersectionStatus == NO_INTERSECTION || intersectionStatus == LINES_OVERLAP )
    {
        intersectionPoint->setZero();
    }
    else
    {
        *intersectionPoint = p1 + ( *fractionAlongLine1 ) * ( p2 - p1 );
    }

    return intersectionStatus;
}

//--------------------------------------------------------------------------------------------------
/// Compute projection of point p3 on the line p1 - p2
//  If projection is out side the line segment, the end of line is returned
//--------------------------------------------------------------------------------------------------
cvf::Vec3d GeometryTools::projectPointOnLine( const cvf::Vec3d& p1,
                                              const cvf::Vec3d& p2,
                                              const cvf::Vec3d& p3,
                                              double*           normalizedIntersection )
{
    cvf::Vec3d v31 = p3 - p1;
    cvf::Vec3d v21 = p2 - p1;

    double     u = ( v31 * v21 ) / ( v21 * v21 );
    cvf::Vec3d projectedPoint( 0, 0, 0 );
    if ( 0 < u && u < 1 )
        projectedPoint = p1 + u * v21;
    else if ( u <= 0 )
        projectedPoint = p1;
    else
        projectedPoint = p2;

    if ( normalizedIntersection )
    {
        *normalizedIntersection = u;
    }

    return projectedPoint;
}

//--------------------------------------------------------------------------------------------------
/// TODO: Use GeometryTools::projectPointOnLine
//--------------------------------------------------------------------------------------------------
double GeometryTools::linePointSquareDist( const cvf::Vec3d& p1, const cvf::Vec3d& p2, const cvf::Vec3d& p3 )
{
    cvf::Vec3d v31 = p3 - p1;
    cvf::Vec3d v21 = p2 - p1;

    double geomTolerance = 1e-24;
    if ( v21.lengthSquared() < geomTolerance )
    {
        // P2 and P1 coincide, use distance from P3 to P1
        return v31.lengthSquared();
    }

    double     u = ( v31 * v21 ) / ( v21 * v21 );
    cvf::Vec3d pOnLine( 0, 0, 0 );
    if ( 0 < u && u < 1 )
        pOnLine = p1 + u * v21;
    else if ( u <= 0 )
        pOnLine = p1;
    else
        pOnLine = p2;

    return ( p3 - pOnLine ).lengthSquared();
}

//--------------------------------------------------------------------------------------------------
// Copyright 2001, softSurfer (www.softsurfer.com)
// This code may be freely used and modified for any purpose
// providing that this copyright notice is included with it.
// SoftSurfer makes no warranty for this code, and cannot be held
// liable for any real or imagined damage resulting from its use.
// Users of this code must verify correctness for their application.
// http://www.softsurfer.com/Archive/algorithm_0105/algorithm_0105.htm
//
/// Intersect a line segment with a 3D triangle
///    Input:  A line segment p0, p1. A triangle t0, t1, t2.
///    Output: *intersectionPoint = intersection point (when it exists)
///    Return: -1 = triangle is degenerate (a segment or point)
///             0 = disjoint (no intersect)
///             1 = intersect in unique point I1
///             2 = are in the same plane
//--------------------------------------------------------------------------------------------------

#define SMALL_NUM 0.00000001 // anything that avoids division overflow
// dot product (3D) which allows vector operations in arguments
#define dot( u, v ) ( ( u ).x() * ( v ).x() + ( u ).y() * ( v ).y() + ( u ).z() * ( v ).z() )

int GeometryTools::intersectLineSegmentTriangle( const cvf::Vec3d& p0,
                                                 const cvf::Vec3d& p1,
                                                 const cvf::Vec3d& t0,
                                                 const cvf::Vec3d& t1,
                                                 const cvf::Vec3d& t2,
                                                 cvf::Vec3d*       intersectionPoint,
                                                 bool*             isLineDirDotNormalNegative )
{
    CVF_TIGHT_ASSERT( intersectionPoint != nullptr );
    CVF_TIGHT_ASSERT( isLineDirDotNormalNegative != nullptr );

    cvf::Vec3d u, v, n; // triangle vectors
    cvf::Vec3d dir, w0, w; // ray vectors
    double     r, a, b; // params to calc ray-plane intersect

    // get triangle edge vectors and plane normal
    u = t1 - t0;
    v = t2 - t0;
    n = u ^ v; // cross product
    if ( n == cvf::Vec3d::ZERO ) // triangle is degenerate
        return -1; // do not deal with this case

    dir = p1 - p0; // ray direction vector
    w0  = p0 - t0;
    a   = -dot( n, w0 );
    b   = dot( n, dir );

    ( *isLineDirDotNormalNegative ) = ( b < 0.0 );

    if ( fabs( b ) < SMALL_NUM )
    { // ray is parallel to triangle plane
        if ( a == 0 ) // ray lies in triangle plane
            return 2;
        else
            return 0; // ray disjoint from plane
    }

    // get intersect point of ray with triangle plane
    r = a / b;
    if ( r < 0.0 ) // ray goes away from triangle
        return 0; // => no intersect

    if ( r > 1.0 ) // Line segment does not reach triangle
        return 0;

    *intersectionPoint = p0 + r * dir; // intersect point of ray and plane

    // is I inside T?
    double uu, uv, vv, wu, wv, D;
    uu = dot( u, u );
    uv = dot( u, v );
    vv = dot( v, v );
    w  = *intersectionPoint - t0;
    wu = dot( w, u );
    wv = dot( w, v );
    D  = uv * uv - uu * vv;

    // get and test parametric coords
    double s, t;
    s = ( uv * wv - vv * wu ) / D;
    if ( s < 0.0 || s > 1.0 ) // I is outside T
        return 0;

    t = ( uv * wu - uu * wv ) / D;
    if ( t < 0.0 || ( s + t ) > 1.0 ) // I is outside T
        return 0;

    return 1; // I is in T
}

/*
//    t0 = (x0, y0, z0)
//    t1 = (x1, y1, z1)
//    t2 = (x2, y2, z2)
//
//    p = (xp, yp, zp)

cvf::Vec3d barycentricCoordsExperiment(const cvf::Vec3d& t0, const cvf::Vec3d& t1, const cvf::Vec3d& t2, const
cvf::Vec3d& p)
{
    det = x0(y1*z2 - y2*z1) + x1(y2*z0 - z2*y0) + x2(y0*z1 - y1*z0);

    b0 = ((x1 * y2 - x2*y1)*zp + xp*(y1*z2-y2*z1) + yp*(x2*z1-x1*z2)) / det;
    b1 = ((x2 * y0 - x0*y2)*zp + xp*(y2*z0-y0*z2) + yp*(x0*z2-x2*z0)) / det;
    b2 = ((x0 * y1 - x1*y0)*zp + xp*(y0*z1-y1*z0) + yp*(x1*z0-x0*z1)) / det;
}

*/

inline double TriArea2D( double x1, double y1, double x2, double y2, double x3, double y3 )
{
    return ( x1 - x2 ) * ( y2 - y3 ) - ( x2 - x3 ) * ( y1 - y2 );
}

//--------------------------------------------------------------------------------------------------
// Compute barycentric coordinates (area coordinates) (u, v, w) for
// point p with respect to triangle (t0, t1, t2)
// These can be used as weights for interpolating scalar values across the triangle
// Based on section 3.4 in "Real Time collision detection" by Christer Ericson
//--------------------------------------------------------------------------------------------------
cvf::Vec3d
    GeometryTools::barycentricCoords( const cvf::Vec3d& t0, const cvf::Vec3d& t1, const cvf::Vec3d& t2, const cvf::Vec3d& p )
{
    // Unnormalized triangle normal
    cvf::Vec3d m = ( ( t1 - t0 ) ^ ( t2 - t0 ) );

    // Absolute components for determining projection plane
    int X = 0, Y = 1;
    int Z = findClosestAxis( m );
    switch ( Z )
    {
        case 0:
            X = 1;
            Y = 2;
            break; // x is largest, project to the yz plane
        case 1:
            X = 0;
            Y = 2;
            break; // y is largest, project to the xz plane
        case 2:
            X = 0;
            Y = 1;
            break; // z is largest, project to the xy plane
    }

    // Compute areas in plane of largest projection
    // Nominators and one-over-denominator for u and v ratios
    double nu, nv, ood;
    nu  = TriArea2D( p[X], p[Y], t1[X], t1[Y], t2[X], t2[Y] ); // Area of PBC in yz plane
    nv  = TriArea2D( p[X], p[Y], t2[X], t2[Y], t0[X], t0[Y] ); // Area of PCA in yz plane
    ood = 1.0f / m[Z]; // 1/(2*area of ABC in yz plane)

    if ( Z == 1 ) ood = -ood; // For some reason not explained

    // Normalize

    m[0] = nu * ood;
    m[1] = nv * ood;
    m[2] = 1.0f - m[0] - m[1];

    return m;
}

inline double triArea3D( const cvf::Vec3d& v0, const cvf::Vec3d& v1, const cvf::Vec3d& v2 )
{
    return 0.5 * ( ( v1 - v0 ) ^ ( v2 - v0 ) ).length();
}

//--------------------------------------------------------------------------------------------------
/// Barycentric coordinates of a Quad
/// See http://geometry.caltech.edu/pubs/MHBD02.pdf for details Eqn. 6.
/// W_i = a_i / Sum(a_0 ... a_3)
/// a_i = Area(v_(i-1), v_i, v_(i+1))*Area(p, v_(i-2), v_(i-1))*Area(p, v_(i+1), v_(i+2))

//--------------------------------------------------------------------------------------------------
cvf::Vec4d GeometryTools::barycentricCoords( const cvf::Vec3d& v0,
                                             const cvf::Vec3d& v1,
                                             const cvf::Vec3d& v2,
                                             const cvf::Vec3d& v3,
                                             const cvf::Vec3d& p )
{
    cvf::Vec4d w;
    cvf::Vec4d a;

    a[0] = triArea3D( v3, v0, v1 ) * triArea3D( p, v2, v3 ) * triArea3D( p, v1, v2 );
    a[1] = triArea3D( v0, v1, v2 ) * triArea3D( p, v3, v0 ) * triArea3D( p, v2, v3 );
    a[2] = triArea3D( v1, v2, v3 ) * triArea3D( p, v0, v1 ) * triArea3D( p, v3, v0 );
    a[3] = triArea3D( v2, v3, v0 ) * triArea3D( p, v1, v2 ) * triArea3D( p, v0, v1 );

    double sum_a = a[0] + a[1] + a[2] + a[3];

    w[0] = a[0] / sum_a;
    w[1] = a[1] / sum_a;
    w[2] = a[2] / sum_a;
    w[3] = a[3] / sum_a;

    return w;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void GeometryTools::addMidEdgeNodes( std::list<std::pair<cvf::uint, bool>>* polygon,
                                     const cvf::Vec3dArray&                 nodes,
                                     EdgeSplitStorage&                      edgeSplitStorage,
                                     std::vector<cvf::Vec3d>*               createdVertexes )
{
    size_t                                          newVertexIndex = nodes.size() + createdVertexes->size();
    std::list<std::pair<cvf::uint, bool>>::iterator it;
    std::list<std::pair<cvf::uint, bool>>::iterator it2;

    cvf::Vec3d midEdgeCoord( 0, 0, 0 );
    size_t     midPointIndex = cvf::UNDEFINED_UINT;

    for ( it = polygon->begin(); it != polygon->end(); ++it )
    {
        it2 = it;
        ++it2;
        if ( it2 == polygon->end() ) it2 = polygon->begin();

        // Find or Create and add a mid-edge node

        if ( !edgeSplitStorage.findSplitPoint( it->first, it2->first, &midPointIndex ) )
        {
            midEdgeCoord.setZero();
            midEdgeCoord += ( it->first < nodes.size() ) ? nodes[it->first]
                                                         : ( *createdVertexes )[it->first - nodes.size()];
            midEdgeCoord += ( it2->first < nodes.size() ) ? nodes[it2->first]
                                                          : ( *createdVertexes )[it2->first - nodes.size()];
            midEdgeCoord *= 0.5;

            midPointIndex = newVertexIndex;
            createdVertexes->push_back( midEdgeCoord );
            ++newVertexIndex;

            edgeSplitStorage.addSplitPoint( it->first, it2->first, midPointIndex );
        }

        if ( it2 != polygon->begin() )
            polygon->insert( it2, std::make_pair( (cvf::uint)midPointIndex, true ) );
        else
            polygon->insert( polygon->end(), std::make_pair( (cvf::uint)midPointIndex, true ) );

        ++it;

        if ( it == polygon->end() ) break;
    }
}

//--------------------------------------------------------------------------------------------------
/// Based on http://geomalgorithms.com/a01-_area.html
/// This method returns the polygon normal with length equal to the polygon area.
/// The components of the normal is thus the size of projected area into each of the main axis planes
//--------------------------------------------------------------------------------------------------
cvf::Vec3d GeometryTools::polygonAreaNormal3D( const std::vector<cvf::Vec3d>& polygon )
{
    size_t pSize = polygon.size();
    switch ( pSize )
    {
        case 0:
        case 1:
        case 2:
        {
            return cvf::Vec3d::ZERO;
        }
        break;
        case 3:
        {
            return 0.5 * ( ( polygon[1] - polygon[0] ) ^ ( polygon[2] - polygon[0] ) );
        }
        break;
        case 4:
        {
            // Cross product of diagonal = 2*A
            return 0.5 * ( ( polygon[2] - polygon[0] ) ^ ( polygon[3] - polygon[1] ) );
        }
        break;
        default:
        {
            /// JJS:
            // This is possibly not the fastest approach with large polygons, where a scaled projections approach would
            // be better, but I suspect this (simpler) approach is faster for small polygons, as long as we do not have
            // the polygon normal up front.
            //
            cvf::Vec3d areaNormal( cvf::Vec3d::ZERO );
            size_t     h = ( pSize - 1 ) / 2;
            size_t     k = ( pSize % 2 ) ? 0 : pSize - 1;

            // First quads
            for ( size_t i = 1; i < h; ++i )
            {
                areaNormal += ( ( polygon[2 * i] - polygon[0] ) ^ ( polygon[2 * i + 1] - polygon[2 * i - 1] ) );
            }

            // Last triangle or quad
            areaNormal += ( ( polygon[2 * h] - polygon[0] ) ^ ( polygon[k] - polygon[2 * h - 1] ) );

            areaNormal *= 0.5;

            return areaNormal;
        }
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::Vec3f GeometryTools::polygonAreaNormal3D( const std::vector<cvf::Vec3f>& polygon )
{
    size_t pSize = polygon.size();
    switch ( pSize )
    {
        case 0:
        case 1:
        case 2:
        {
            return cvf::Vec3f::ZERO;
        }
        break;
        case 3:
        {
            return 0.5f * ( ( polygon[1] - polygon[0] ) ^ ( polygon[2] - polygon[0] ) );
        }
        break;
        case 4:
        {
            // Cross product of diagonal = 2*A
            return 0.5f * ( ( polygon[2] - polygon[0] ) ^ ( polygon[3] - polygon[1] ) );
        }
        break;
        default:
        {
            /// JJS:
            // This is possibly not the fastest approach with large polygons, where a scaled projections approach would
            // be better, but I suspect this (simpler) approach is faster for small polygons, as long as we do not have
            // the polygon normal up front.
            //
            cvf::Vec3f areaNormal( cvf::Vec3f::ZERO );
            size_t     h = ( pSize - 1 ) / 2;
            size_t     k = ( pSize % 2 ) ? 0 : pSize - 1;

            // First quads
            for ( size_t i = 1; i < h; ++i )
            {
                areaNormal += ( ( polygon[2 * i] - polygon[0] ) ^ ( polygon[2 * i + 1] - polygon[2 * i - 1] ) );
            }

            // Last triangle or quad
            areaNormal += ( ( polygon[2 * h] - polygon[0] ) ^ ( polygon[k] - polygon[2 * h - 1] ) );

            areaNormal *= 0.5;

            return areaNormal;
        }
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
float GeometryTools::polygonArea( const std::vector<cvf::Vec3f>& polygon )
{
    auto areaNormal3D = polygonAreaNormal3D( polygon );

    return areaNormal3D.length();
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double GeometryTools::polygonArea( const std::vector<cvf::Vec3d>& polygon )
{
    auto areaNormal3D = polygonAreaNormal3D( polygon );

    return areaNormal3D.length();
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void EdgeSplitStorage::setVertexCount( size_t size )
{
    m_edgeSplitMap.resize( size );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool EdgeSplitStorage::findSplitPoint( size_t edgeP1Index, size_t edgeP2Index, size_t* splitPointIndex )
{
    canonizeAddress( edgeP1Index, edgeP2Index );
    CVF_ASSERT( edgeP1Index < m_edgeSplitMap.size() );

    std::map<size_t, size_t>::iterator it;

    it = m_edgeSplitMap[edgeP1Index].find( edgeP2Index );
    if ( it == m_edgeSplitMap[edgeP1Index].end() ) return false;

    *splitPointIndex = it->second;
    return true;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void EdgeSplitStorage::addSplitPoint( size_t edgeP1Index, size_t edgeP2Index, size_t splitPointIndex )
{
    canonizeAddress( edgeP1Index, edgeP2Index );
    CVF_ASSERT( edgeP1Index < m_edgeSplitMap.size() );
    m_edgeSplitMap[edgeP1Index][edgeP2Index] = splitPointIndex;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void EdgeSplitStorage::canonizeAddress( size_t& edgeP1Index, size_t& edgeP2Index )
{
    if ( edgeP1Index > edgeP2Index )
    {
        size_t tmp;
        tmp         = edgeP1Index;
        edgeP1Index = edgeP2Index;
        edgeP2Index = tmp;
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
EarClipTesselator::EarClipTesselator()
    : m_nodeCoords( nullptr )
    , m_X( -1 )
    , m_Y( -1 )
    , m_areaTolerance( 1e-12 )
{
}

//--------------------------------------------------------------------------------------------------
/// \brief      Do the main processing/actual triangulation
/// \param      triangleIndices Array that will receive the indices of the triangles resulting from the triangulation
/// \return        true when a tesselation was successully created
//--------------------------------------------------------------------------------------------------

bool EarClipTesselator::calculateTriangles( std::vector<size_t>* triangleIndices )
{
    CVF_ASSERT( m_nodeCoords != nullptr );
    CVF_ASSERT( m_X > -1 && m_Y > -1 );

    size_t numVertices = m_polygonIndices.size();

    if ( numVertices < 3 ) return false;

    // We want m_polygonIndices to be a counter-clockwise polygon to make the validation test work

    if ( calculateProjectedPolygonArea() < 0 )
    {
        m_polygonIndices.reverse();
    }

    std::list<size_t>::iterator u, v, w;

    // If we loop two times around polygon without clipping a single triangle we are toast.
    size_t count = 2 * numVertices; // error detection

    v = m_polygonIndices.end(); // nv - 1;
    --v;

    while ( numVertices > 2 )
    {
        // if we loop, it is probably a non-simple polygon
        if ( count == 0 )
        {
            // Triangulate: ERROR - probable bad polygon!
            return false;
        }
        --count;

        // Three consecutive vertices in current polygon, <u,v,w>
        // previous
        u = v;
        if ( u == m_polygonIndices.end() ) u = m_polygonIndices.begin(); // if (nv <= u) u = 0;

        // new v
        v = u;
        ++v; // u + 1;
        if ( v == m_polygonIndices.end() ) v = m_polygonIndices.begin(); // if (nv <= v) v = 0;

        // next
        w = v;
        ++w; // v + 1;
        if ( w == m_polygonIndices.end() ) w = m_polygonIndices.begin(); // if (nv <= w) w = 0;

        EarClipTesselator::TriangleStatus triStatus = calculateTriangleStatus( u, v, w );

        if ( triStatus != INVALID_TRIANGLE )
        {
            // Indices of the vertices
            if ( triStatus == VALID_TRIANGLE )
            {
                triangleIndices->push_back( *u );
                triangleIndices->push_back( *v );
                triangleIndices->push_back( *w );
            }

            // Remove v from remaining polygon
            m_polygonIndices.erase( v );
            v = w;
            numVertices--;

            // Resets error detection counter
            count = 2 * numVertices;
        }
    }

    return true;
}

//--------------------------------------------------------------------------------------------------
/// Is this a valid triangle ? ( No points inside, and points not on a line. )
//--------------------------------------------------------------------------------------------------

EarClipTesselator::TriangleStatus EarClipTesselator::calculateTriangleStatus( std::list<size_t>::const_iterator u,
                                                                              std::list<size_t>::const_iterator v,
                                                                              std::list<size_t>::const_iterator w ) const
{
    CVF_ASSERT( m_X > -1 && m_Y > -1 );

    cvf::Vec3d A = ( *m_nodeCoords )[*u];
    cvf::Vec3d B = ( *m_nodeCoords )[*v];
    cvf::Vec3d C = ( *m_nodeCoords )[*w];

    double mainAxisProjectedArea = ( ( B[m_X] - A[m_X] ) * ( C[m_Y] - A[m_Y] ) ) -
                                   ( ( B[m_Y] - A[m_Y] ) * ( C[m_X] - A[m_X] ) );
    if ( -m_areaTolerance > mainAxisProjectedArea )
    {
        // Definite negative triangle
        return INVALID_TRIANGLE;
    }
    else if ( fabs( mainAxisProjectedArea ) < m_areaTolerance )
    {
        return NEAR_ZERO_AREA_TRIANGLE;
    }

    std::list<size_t>::const_iterator c;
    std::list<size_t>::const_iterator outside;
    for ( c = m_polygonIndices.begin(); c != m_polygonIndices.end(); ++c )
    {
        // The polygon points that actually make up the triangle candidate does not count
        // (but the same points on different positions in the polygon does!
        // Except those one off the triangle, that references the start or end of the triangle)

        if ( ( c == u ) || ( c == v ) || ( c == w ) ) continue;

        // Originally the below tests was not included which resulted in missing triangles sometimes

        outside = w;
        ++outside;
        if ( outside == m_polygonIndices.end() ) outside = m_polygonIndices.begin();
        if ( c == outside && *c == *u )
        {
            continue;
        }

        outside = u;
        if ( outside == m_polygonIndices.begin() ) outside = m_polygonIndices.end();
        --outside;
        if ( c == outside && *c == *w )
        {
            continue;
        }

        cvf::Vec3d P = ( *m_nodeCoords )[*c];

        if ( isPointInsideTriangle( A, B, C, P ) ) return INVALID_TRIANGLE;
    }

    return VALID_TRIANGLE;
}

//--------------------------------------------------------------------------------------------------
/// Decides if a point P is inside of the triangle defined by A, B, C.
/// By calculating the "double area" (cross product) of Corner to corner x Corner to point vectors
/// If cross product is negative, the point is outside
//--------------------------------------------------------------------------------------------------

bool EarClipTesselator::isPointInsideTriangle( const cvf::Vec3d& A,
                                               const cvf::Vec3d& B,
                                               const cvf::Vec3d& C,
                                               const cvf::Vec3d& P ) const
{
    CVF_ASSERT( m_X > -1 && m_Y > -1 );

    double ax = C[m_X] - B[m_X];
    double ay = C[m_Y] - B[m_Y];
    double bx = A[m_X] - C[m_X];
    double by = A[m_Y] - C[m_Y];
    double cx = B[m_X] - A[m_X];
    double cy = B[m_Y] - A[m_Y];

    double apx = P[m_X] - A[m_X];
    double apy = P[m_Y] - A[m_Y];
    double bpx = P[m_X] - B[m_X];
    double bpy = P[m_Y] - B[m_Y];
    double cpx = P[m_X] - C[m_X];
    double cpy = P[m_Y] - C[m_Y];

    double aCROSSbp = ax * bpy - ay * bpx;
    double cCROSSap = cx * apy - cy * apx;
    double bCROSScp = bx * cpy - by * cpx;
    double tol      = -m_areaTolerance;

    return ( ( aCROSSbp >= tol ) && ( bCROSScp >= tol ) && ( cCROSSap >= tol ) );
};

//--------------------------------------------------------------------------------------------------
/// Computes area of the currently stored 2D polygon/contour
//--------------------------------------------------------------------------------------------------

double EarClipTesselator::calculateProjectedPolygonArea() const
{
    CVF_ASSERT( m_X > -1 && m_Y > -1 );

    double A = 0;

    std::list<size_t>::const_iterator p = m_polygonIndices.end();
    --p;

    std::list<size_t>::const_iterator q = m_polygonIndices.begin();
    while ( q != m_polygonIndices.end() )
    {
        A += ( *m_nodeCoords )[*p][m_X] * ( *m_nodeCoords )[*q][m_Y] -
             ( *m_nodeCoords )[*q][m_X] * ( *m_nodeCoords )[*p][m_Y];

        p = q;
        ++q;
    }

    return A * 0.5;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void EarClipTesselator::setNormal( const cvf::Vec3d& polygonNormal )
{
    int Z           = GeometryTools::findClosestAxis( polygonNormal );
    m_X             = ( Z + 1 ) % 3;
    m_Y             = ( Z + 2 ) % 3;
    m_polygonNormal = polygonNormal;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void EarClipTesselator::setPolygonIndices( const std::list<size_t>& polygon )
{
    m_polygonIndices = polygon;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void EarClipTesselator::setPolygonIndices( const std::vector<size_t>& polygon )
{
    size_t i;
    for ( i = 0; i < polygon.size(); ++i )
    {
        m_polygonIndices.push_back( polygon[i] );
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void EarClipTesselator::setMinTriangleArea( double areaTolerance )
{
    m_areaTolerance = 2 * areaTolerance; // Convert to trapesoidal area
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void EarClipTesselator::setGlobalNodeArray( const cvf::Vec3dArray& nodeCoords )
{
    m_nodeCoords = &nodeCoords;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
FanEarClipTesselator::FanEarClipTesselator()
    : m_centerNodeIndex( std::numeric_limits<size_t>::max() )
{
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool FanEarClipTesselator::calculateTriangles( std::vector<size_t>* triangles )
{
    CVF_ASSERT( m_centerNodeIndex != std::numeric_limits<size_t>::max() );
    CVF_ASSERT( m_nodeCoords != nullptr );
    CVF_ASSERT( m_X > -1 && m_Y > -1 );

    size_t nv = m_polygonIndices.size();

    if ( nv < 3 ) return false;

    // We want m_polygonIndices to be a counter-clockwise polygon to make the validation test work

    if ( calculateProjectedPolygonArea() < 0 )
    {
        m_polygonIndices.reverse();
    }

    std::list<size_t>::const_iterator it1;
    std::list<size_t>::const_iterator it2;

    std::list<std::list<size_t>> restPolygons;
    bool                         wasPreviousTriangleValid = true;

    for ( it1 = m_polygonIndices.begin(); it1 != m_polygonIndices.end(); ++it1 )
    {
        it2 = it1;
        ++it2;

        if ( it2 == m_polygonIndices.end() ) it2 = m_polygonIndices.begin();

        if ( isTriangleValid( *it1, *it2, m_centerNodeIndex ) )
        {
            triangles->push_back( *it1 );
            triangles->push_back( *it2 );
            triangles->push_back( m_centerNodeIndex );
            wasPreviousTriangleValid = true;
        }
        else
        {
            if ( wasPreviousTriangleValid )
            {
                // Create new rest polygon.
                restPolygons.push_back( std::list<size_t>() );
                restPolygons.back().push_back( m_centerNodeIndex );
                restPolygons.back().push_back( *it1 );
                restPolygons.back().push_back( *it2 );
            }
            else
            {
                restPolygons.back().push_back( *it2 );
            }
        }
    }

    EarClipTesselator triMaker;
    triMaker.setNormal( m_polygonNormal );
    triMaker.setMinTriangleArea( m_areaTolerance );
    triMaker.setGlobalNodeArray( *m_nodeCoords );
    std::list<std::list<size_t>>::iterator rpIt;

    for ( rpIt = restPolygons.begin(); rpIt != restPolygons.end(); ++rpIt )
    {
        triMaker.setPolygonIndices( *rpIt );
        triMaker.calculateTriangles( triangles );
    }

    return true;
}

//--------------------------------------------------------------------------------------------------
/// This needs to be rewritten because we need to test for crossing edges, not only point inside.
/// In addition the test for polygon
//--------------------------------------------------------------------------------------------------
bool FanEarClipTesselator::isTriangleValid( size_t u, size_t v, size_t w )
{
    CVF_ASSERT( m_X > -1 && m_Y > -1 );

    cvf::Vec3d A = ( *m_nodeCoords )[u];
    cvf::Vec3d B = ( *m_nodeCoords )[v];
    cvf::Vec3d C = ( *m_nodeCoords )[w];

    if ( m_areaTolerance > ( ( ( B[m_X] - A[m_X] ) * ( C[m_Y] - A[m_Y] ) ) - ( ( B[m_Y] - A[m_Y] ) * ( C[m_X] - A[m_X] ) ) ) )
        return false;

    std::list<size_t>::const_iterator c;
    for ( c = m_polygonIndices.begin(); c != m_polygonIndices.end(); ++c )
    {
        // The polygon points that actually make up the triangle candidate does not count
        // (but the same points on different positions in the polygon does! )
        // Todo so this test below is to accepting !! Bug !!
        if ( ( *c == u ) || ( *c == v ) || ( *c == w ) ) continue;

        cvf::Vec3d P = ( *m_nodeCoords )[*c];

        if ( isPointInsideTriangle( A, B, C, P ) ) return false;
    }

    return true;
}

} // namespace cvf
} //namespace external
