/*
Intersect.cpp
Written by Matthew Fisher

Code for computing geometric object intersections.
*/

bool Math::RayIntersectRectangle(const Ray3D &Ray, const Rectangle3f &Rect)
{
    for(UINT PlaneIndex = 0; PlaneIndex < 6; PlaneIndex++)
    {
        Vec3f Pt;
        UINT Axis;
        if(PlaneIndex < 3)
        {
            Pt = Rect.Min;
            Axis = PlaneIndex;
        }
        else
        {
            Pt = Rect.Max;
            Axis = PlaneIndex - 3;
        }
        const float t = (Pt[Axis] - Ray.P0[Axis]) / Ray.D[Axis];
        const Vec3f IntersectPt = Ray.P0 + Ray.D * t;

        if(Rect.PtWithinDistance(IntersectPt, 1e-4f))
        {
            return true;
        }
    }
    return false;
}

//
// Code from http://geometryalgorithms.com/Archive/algorithm_0105/algorithm_0105.htm
//
bool Math::RayIntersectTriangle(const Vec3f &RayPt, const Vec3f &RayDir, const Vec3f &T0, const Vec3f &T1, const Vec3f &T2, Vec3f &IntersectPt)
{
    Vec3f u, v, n;             // triangle vectors
    Vec3f w0, w;               // ray vectors
    float r, a, b;             // params to calc ray-plane intersect

    // get triangle edge vectors and plane normal
    u = T1 - T0;
    v = T2 - T0;
    n = Vec3f::Cross(u, v);
    
    w0 = RayPt - T0;
    a = -Vec3f::Dot(n, w0);
    b = Vec3f::Dot(n, RayDir);
    if(b == 0.0f)
    {
        return false;
    }
    /*if (fabsf(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.0f)                  // ray goes away from triangle
    {
        return false;              // => no intersect
    }
    // for a segment, also test if (r > 1.0) => no intersect

    IntersectPt = RayPt + r * RayDir; // intersect point of ray and plane

    // is I inside T?
    float uu, uv, vv, wu, wv, D;
    uu = Vec3f::Dot(u, u);
    uv = Vec3f::Dot(u, v);
    vv = Vec3f::Dot(v, v);
    w = IntersectPt - T0;
    wu = Vec3f::Dot(w, u);
    wv = Vec3f::Dot(w, v);
    D = 1.0f / (uv * uv - uu * vv);

    // get and test parametric coords
    float s, t;
    s = (uv * wv - vv * wu) * D;
    if (s < 0.0f || s > 1.0f)        // I is outside T
    {
        return false;
    }
    t = (uv * wu - uu * wv) * D;
    if (t < 0.0f || (s + t) > 1.0f)  // I is outside T
    {
        return false;
    }
    return true;
}

//
// Code from http://geometryalgorithms.com/Archive/algorithm_0105/algorithm_0105.htm
//
bool Math::LineIntersectTriangle(const Vec3f &LinePt, const Vec3f &LineDir, const Vec3f &T0, const Vec3f &T1, const Vec3f &T2, Vec3f &IntersectPt)
{
    Vec3f u, v, n;             // triangle vectors
    Vec3f w0, w;               // ray vectors
    float r, a, b;             // params to calc ray-plane intersect

    // get triangle edge vectors and plane normal
    u = T1 - T0;
    v = T2 - T0;
    n = Vec3f::Cross(u, v);    // cross product
    
    w0 = LinePt - T0;
    a = -Vec3f::Dot(n, w0);
    b = Vec3f::Dot(n, LineDir);
    if(b == 0.0f)
    {
        return false;
    }
    /*if (fabsf(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;
    // for a segment, also test if (r > 1.0) => no intersect

    IntersectPt = LinePt + r * LineDir; // intersect point of ray and plane

    // is I inside T?
    float uu, uv, vv, wu, wv, D;
    uu = Vec3f::Dot(u, u);
    uv = Vec3f::Dot(u, v);
    vv = Vec3f::Dot(v, v);
    w = IntersectPt - T0;
    wu = Vec3f::Dot(w, u);
    wv = Vec3f::Dot(w, v);
    D = uv * uv - uu * vv;

    // get and test parametric coords
    float s, t;
    s = (uv * wv - vv * wu) / D;
    if (s < 0.0f || s > 1.0f)        // I is outside T
    {
        return false;
    }
    t = (uv * wu - uu * wv) / D;
    if (t < 0.0f || (s + t) > 1.0f)  // I is outside T
    {
        return false;
    }
    return true;
}

bool Math::SegmentIntersectTriangle(const Vec3f &P0, const Vec3f &P1, const Vec3f &T0, const Vec3f &T1, const Vec3f &T2, Vec3f &IntersectPt)
{
    bool Result = LineIntersectTriangle(P0, P1 - P0, T0, T1, T2, IntersectPt);
    if(Result)
    {
        return Vec3f::WithinRect(IntersectPt, Rectangle3f::ConstructFromTwoPoints(P0, P1));
    }
    else
    {
        return false;
    }
}

bool Rectangle3f::ParametricRayIntersect(const Vec3f &RayOrigin, const Vec3f &RayDir, float &TMin, float &TMax) const
{
    TMin = 0.0f;
    TMax = 1e10f;
    for (UINT AxisIndex = 0; AxisIndex < 3; AxisIndex++)
    {
        float invRayDir = 1.0f / RayDir[AxisIndex];
        float TNear = (Min[AxisIndex] - RayOrigin[AxisIndex]) * invRayDir;
        float TFar  = (Max[AxisIndex] - RayOrigin[AxisIndex]) * invRayDir;
        
        if (TNear > TFar)
        {
            swap(TNear, TFar);
        }
        TMin = TNear > TMin ? TNear : TMin;
        TMax = TFar  < TMax ? TFar  : TMax;
        if (TMin > TMax)
        {
            return false;
        }
    }
    return true;
}