#include "gpuCacheIsectUtil.h"
#include <maya/MMatrix.h>
#include <maya/MPointArray.h>
#include <limits>
namespace GPUCache {
    
    
    
    double gpuCacheIsectUtil::getClosestPointOnLine(
const MPoint& queryPoint, 
const MPoint& pt1, 
const MPoint& pt2, 
MPoint& closestPoint){
 
        double t = ( (queryPoint - pt1) * edgeVec ) / (edgeVec * edgeVec);
        if(t<0) t=0;
        if(t>1) t=1;
        closestPoint = (1-t) * pt1 + t * pt2;
        return t;
    }
    
    
    
    
        
        if(firstRayIntersection(bbox.
min(), bbox.
max(), raySource, rayDirection, NULL, &boxIntersectionPt))
 
        {
            
            
            
            snapPoint = boxIntersectionPt;
            return 0.0;
        }
        int edgeIndices[12][2]={{0,1},{1,2},{2,3},{3,0},{4,5},{5,6},{6,7},{7,4},{0,4},{1,5},{3,7},{2,6}};
        double minDistRect = std::numeric_limits<double>::max();
        
        for(int edge=0; edge<12;edge++){
            MPoint vertex1Org = verts[edgeIndices[edge][0]];
 
            MPoint vertex2Org = verts[edgeIndices[edge][1]];
 
            
            double coef_plane = rayDirection * raySource;
            double d = coef_plane - rayDirection * vertex1Org;
            MPoint vertex1 = vertex1Org + rayDirection * d;
 
            d = coef_plane - rayDirection * vertex2Org;
            MPoint vertex2 = vertex2Org + rayDirection * d;
 
            MVector edgeDir = vertex2 - vertex1;
 
            if (edgeDir.
length()<0.0000001){
 
                if (dist < minDistRect) {
                    minDistRect = dist;
                    snapPoint = vertex1Org;
                }
            } else {
                
                double percent = gpuCacheIsectUtil::getClosestPointOnLine(raySource, vertex1, vertex2, edgePt);
                if (dist < minDistRect) {
                    minDistRect = dist;
                    snapPoint =  (vertex1Org + percent * (vertex2Org - vertex1Org));
                }
            }
        }
        
        return minDistRect;
    }
    
    
    
    double gpuCacheIsectUtil::getEdgeSnapPointOnTriangle(
const MPoint& raySource, 
const MVector& rayDirection, 
const MPoint& vert1, 
const MPoint& vert2, 
const MPoint& vert3, 
MPoint& snapPoint){
 
        int edgeIndices[3][2]={{0,1},{1,2},{2,0}};
        double minDistTri = std::numeric_limits<double>::max();
        for(int edge=0; edge<3;edge++){
            MPoint vertex1Org = verts[edgeIndices[edge][0]];
 
            MPoint vertex2Org = verts[edgeIndices[edge][1]];
 
            double coef_plane = rayDirection * raySource;
            double d = coef_plane - rayDirection * vertex1Org;
            MPoint vertex1 = vertex1Org + rayDirection * d;
 
            d = coef_plane - rayDirection * vertex2Org;
            MPoint vertex2 = vertex2Org + rayDirection * d;
 
            MVector edgeDir = vertex2 - vertex1;
 
            
            if (edgeDir.
length()<0.0000001){
 
                if (dist < minDistTri) {
                    minDistTri = dist;
                    snapPoint = vertex1Org;
                }
            } else {
                
                double percent = gpuCacheIsectUtil::getClosestPointOnLine(raySource, vertex1, vertex2, edgePt);
                if (dist < minDistTri) {
                    minDistTri = dist;
                    snapPoint =  (vertex1Org + percent * (vertex2Org - vertex1Org));
                }
            }
        }
        return minDistTri;
    }
    void gpuCacheIsectUtil::getClosestPointToRayOnLine(
const MPoint& vertex1, 
const MPoint& vertex2, 
const MPoint& raySource, 
const MVector& rayDirection, 
MPoint& closestPoint, 
double& percent){
 
        MVector edgeDir = vertex2 - vertex1;
 
        double len = edgeDir.
length();
 
        if(len < 0.0000001 )
        {
            percent = 0.0;
            closestPoint = vertex1;
            return;
        }
        
        
        double dotPrd = fabs(edgeDir * rayDirection);
        if(dotPrd > 0.9999){
            percent = 0.0;
            closestPoint = vertex1;
            return;
        }
        
        
        MVector crossProd = edgeDir ^ rayDirection;
 
        
        
        MVector planeNormal = rayDirection ^ crossProd;
 
        
        double t;
        if(intersectPlane(raySource, planeNormal, vertex1,edgeDir,t)){
            clsPoint = vertex1 + t * edgeDir;
            
            
            
            percent = edgeDir * (clsPoint - vertex1) / len;
            
            
            
            if (percent < 0)
            {
                closestPoint = vertex1;
                percent = 0.0;
            }
            else if (percent > 1.0)
            {
                closestPoint = vertex2;
                percent = 1.0;
            }
            else
            {
                closestPoint = clsPoint;
            }
        } else
        {
            closestPoint = vertex1;
            percent = 0.0;
        }
    }
        double sqrDistance = 0.0;
        double delta;
        for (int i = 0; i < 3; ++i)
        {
            closest[i] = diff * axis[i];
            if (closest[i] < -dimensions[i])
            {
                delta = closest[i] + dimensions[i];
                sqrDistance += delta*delta;
                closest[i] = -dimensions[i];
            }
            else if (closest[i] > dimensions[i])
            {
                delta = closest[i] - dimensions[i];  
                sqrDistance += delta*delta;
                closest[i] = dimensions[i];
            }
        }
        for (int i = 0; i < 3; ++i)
        {
            closestPoint += closest[i]*axis[i];
        }
        return sqrt(sqrDistance);
    }
    int gpuCacheIsectUtil::intersectRayWithBox(
        double          isectParams[2]
    )
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    {
        
        
        
        const double isectTol = 1.0e-6;
        
        
        int numFound = 0;
        
        
        double boundsMin[3]={minPoint[0],minPoint[1],minPoint[2]};
        double boundsMax[3]={maxPoint[0],maxPoint[1],maxPoint[2]};
        
        
        
        
        for( int axis = 0; axis < 3; axis++ )
        {
            
            
            if( rayDirection[axis] != 0 )
            {
                
                
                
                
                
                int otherAxis1 = (axis+1)%3;
                int otherAxis2 = (axis+2)%3;
                
                
                
                double sides[2] = { boundsMin[axis], boundsMax[axis] };
                
                
                
                
                
                
                
                
                
                
                for( int side = 0; side < 2; side++ )
                {
                    
                    
                    double tSide = (sides[side]-rayOrigin[axis])/rayDirection[axis];
                    if( tSide > 0.0 )
                    {
                        
                        
                        
                        double newPointOtherAxis1 = rayOrigin[otherAxis1] + 
                            tSide*rayDirection[otherAxis1];
                        
                        
                        
                        
                        if( (newPointOtherAxis1 >= (boundsMin[otherAxis1]-isectTol)) && (newPointOtherAxis1 <= (boundsMax[otherAxis1]+isectTol)) )
                        {
                            
                            
                            
                            double newPointOtherAxis2 = rayOrigin[otherAxis2] + 
                                tSide*rayDirection[otherAxis2];
                            if( (newPointOtherAxis2 >= (boundsMin[otherAxis2]-isectTol)) && (newPointOtherAxis2 <= (boundsMax[otherAxis2]+isectTol)) )
                            {
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                if( numFound == 0 )
                                {
                                    isectParams[0] = tSide;
                                    numFound++;
                                }
                                else if( numFound == 1 )
                                {
                                    
                                    
                                    
                                    if( tSide >= (isectParams[0]+1.0e-10) )
                                    {
                                        isectParams[1] = tSide;
                                        numFound++;
                                    }
                                    else if( tSide <= (isectParams[0]-1.0e-10) )
                                    {
                                        isectParams[1] = isectParams[0];
                                        isectParams[0] = tSide;
                                        numFound++;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return numFound;
    }
    bool gpuCacheIsectUtil::firstRayIntersection ( 
        double*         isectParam,
        ) 
        
        
        
        
        
        
        
        
        
        
    {
        
        
        double allIsectParams[4];
        if( !intersectRayWithBox( bboxMin, bboxMax, rayOrigin, rayDirection, allIsectParams ) )
        {
            return false;
        }
        else
        {
            
            
            
            if( isectParam != NULL )
            {
                *isectParam = allIsectParams[0];
            }
            if( isectPoint != NULL )
            {
                *isectPoint = rayOrigin + allIsectParams[0]*rayDirection;
            }
            return true;
        }
    }
    bool gpuCacheIsectUtil::intersectPlane(
const MPoint &planePoint, 
const MVector &planeNormal, 
const MPoint& rayPoint, 
const MVector &rayDirection, 
double &t)
 
    {
        
        double denom = planeNormal * rayDirection;
        if (denom > 0.0000001) {
            MVector p0l0 = planePoint - rayPoint;
 
            t = (p0l0 * planeNormal) / denom; 
            return (t >= 0);
        }
        return false;
    }
    bool gpuCacheIsectUtil::getClosestPointOnTri(
const MPoint &toThisPoint, 
const MPoint &pt1, 
const MPoint &pt2, 
const MPoint &pt3, 
MPoint &theClosestPoint, 
double &currDist) 
 
    {
        double      sum, a, b, c, len, dist;
        mat[2][0] = mat[2][1] = mat[2][2] = 1.;
        len = norm * norm;
        if (len < 1.175494351e-38F) return false;
        len = ( norm * v ) / len;
        MPoint pnt = toThisPoint - len * norm;
 
        
            return false;
        int i, j;               
        if (fabs(norm[0]) > fabs(norm[1]))
        {
            if (fabs(norm[0]) > fabs(norm[2]))
            {
                i = 1; j = 2;
            }
            else
            {
                i = 0; j = 1;
            }
        }
        else
        {
            if (fabs(norm[1]) > fabs(norm[2]))
            {
                i = 0; j = 2;
                
            }
            else
            {
                i = 0; j = 1;
            }
        }
        mat[0][0] = pt1[i]; mat[0][1] = pt2[i]; mat[0][2] = pt3[i]; 
        mat[1][0] = pt1[j]; mat[1][1] = pt2[j]; mat[1][2] = pt3[j]; 
        MPoint abc(pnt[i], pnt[j], 1, 0);
 
        abc = matInv * abc;
        
        
        if (abc[0]<0) { 
            if (abc[1]<0) { 
                a = b = 0;
                c = 1;
            } else if (abc[2]<0) { 
                a = c = 0;
                b = 1;
            } else {
                a = 0;
                
                c = ( vp * v23 ) / ( v23[0]*v23[0] + v23[1]*v23[1] + v23[2]*v23[2] );
                if (c<0) c = 0; else if (c>1) c = 1;
                b = 1 - c;
            }
        } else if (abc[1]<0) { 
            if (abc[2]<0) { 
                b = c = 0;
                a = 1;
                
                
                
            } else {
                b = 0;
                
                a = ( vp * v31 ) / ( v31[0]*v31[0] + v31[1]*v31[1] +v31[2]*v31[2] );
                if (a<0) a = 0; else if (a>1) a = 1;
                c = 1 - a;
            } 
        } else if (abc[2]<0) { 
            
            
            
            
            
            
            
            c = 0;
            
            
            b = ( vp * v12 ) / ( v12[0]*v12[0] + v12[1]*v12[1] + v12[2]*v12[2] );
            if (b<0) b = 0; else if (b>1) b = 1;
            a = 1 - b;
            
        } else {
            if (abc[0]>0) a = abc[0]; else a = 0;
            if (abc[1]>0) b = abc[1]; else b = 0;
            if (abc[2]>0) c = abc[2]; else c = 0;
        }
        sum = a+b+c;
        a /= sum ; b /= sum ; c /= sum ; 
        pnt = a * pt1 + b * pt2 + c * pt3;
        if ( dist < currDist)
        {           
            
            currDist = dist;
            theClosestPoint = pnt;
            return true;
        }
        return false;
    }
}