/**
 @file MeshEntity.cpp

 Triangle mesh for fast ray tracing

 @author Morgan McGuire, morgan@cs.williams.edu
 */
#include "MeshEntity.h"
#include "Hit.h"
#include "BSDF.h"

MeshEntityRef MeshEntity::create(
    const std::string& filename, 
    const BSDF* bsdf, 
    const CoordinateFrame& xform, 
    const Vector3& scale,
    bool smoothNormals) {

    IFSModelRef ifs = IFSModel::fromFile(filename, scale, xform);
    PosedModelRef posed = ifs->pose(CoordinateFrame(), smoothNormals);

    return create(posed, bsdf, smoothNormals);
}


MeshEntityRef MeshEntity::create(
    const PosedModelRef& model,
    const BSDF* bsdf,
    bool smooth) {

    return new MeshEntity(model, bsdf, smooth);
}


MeshEntity::MeshEntity(const PosedModelRef& model, const BSDF* bsdf, bool smooth) : m_bsdf(bsdf), m_smooth(smooth) {

    // Extract the geometry from the object
    const MeshAlg::Geometry& geometry = model->objectSpaceGeometry();
    const Array<Vector3>& v = geometry.vertexArray;
    const Array<Vector3>& n = geometry.normalArray;
    const Array<int>& index = model->triangleIndices();

    if (v.size() > 0) {
        m_bounds = AABox(v[0]);
    }

    if (smooth) {
        // Every three indices is a triangle
        for (int j = 0; j < index.size(); j += 3) {
            const int i0 = index[j];
            const int i1 = index[j + 1];
            const int i2 = index[j + 2];

            Tri tri(v[i0], v[i1], v[i2], n[i0], n[i1], n[i2]);

            m_bounds.merge(v[i0]);
            m_bounds.merge(v[i1]);
            m_bounds.merge(v[i2]);

            m_triSet.insert(tri);
        }
    } else {
        // Every three indices is a triangle
        for (int j = 0; j < index.size(); j += 3) {
            const int i0 = index[j];
            const int i1 = index[j + 1];
            const int i2 = index[j + 2];

            // Compute the unit face normal from the vertices
            Vector3 faceNormal((v[i1] - v[i0]).cross(v[i2] - v[i0]));
            faceNormal.unitize();

            m_bounds.merge(v[i0]);
            m_bounds.merge(v[i1]);
            m_bounds.merge(v[i2]);

            Tri tri(v[i0], v[i1], v[i2], faceNormal);
            m_triSet.insert(tri);
        }
    }

    m_triSet.balance();
}

/**

 Used by MeshEntity::intersect.
*/
class TriIntersect {
private:

    /**
     The hit result for the caller of MeshEntity::intersect
     */
    Hit& hitResult;

public:

    /** Pointer to the closest triangle found, NULL if no intersection was found. */
    const MeshEntity::Tri* closestTri;

    /** The Barycentric coordinates of the intersection in closestTri are (1-u-v)*v0 + u*v1 + v*v2.
        These are stored for later texture mapping and normal smoothing. */
    float closestU;
    float closestV;

    TriIntersect(Hit& hitResult) : hitResult(hitResult), closestTri(NULL) {}
   
    /** 
        Computes the intersection of the ray and triangle.  If an intersection is 
        found that is closer than <i>distance</i>, updates distance and stores the 
        result in <i>hitResult</i>.  

        For use with AABSPTree::intersectRay. 
      */
    void operator()(const Ray& ray, const MeshEntity::Tri& tri, float& distance) {
        static const float EPS = 1e-5f;
        static const float NEG_EPS = -EPS;

        const Vector3& v0 = tri.v0;
        const Vector3& e1 = tri.e1;
        const Vector3& e2 = tri.e2;

        // BEGIN---------------------------------------------------

        // See RTR2 ch. 13.7 for the algorithm.

        float t = 0;

        // END---------------------------------------------------

        if ((t > 0.0f) && (t < distance)) {
            // This is a new hit.  Save away the data about the hit location,
            // but don't bother computing the location or normal until
            // after we've checked against all triangles.
            distance = t;
            closestTri = &tri;
            closestU = u;
            closestV = v;
        }
    }
};


void MeshEntity::intersect(const Ray& worldRay, float& hitDistance, class Hit& hitResult) {

    // Tell the AABSPTree to not bother computing bounding box intersections; our triangle intersector
    // is really fast.
    static const bool TRI_INTERSECT_IS_FASTER_THAN_BOX = true;
    
    TriIntersect intersector(hitResult);

    // Look for intersections
    m_triSet.intersectRay(worldRay, intersector, hitDistance, TRI_INTERSECT_IS_FASTER_THAN_BOX);

    if (intersector.closestTri != NULL) {
        // There was an intersection.  Update the hitResult position and normal.

        hitResult.position = worldRay.origin + hitDistance * worldRay.direction;

        if (m_smooth) {
            const float u = intersector.closestU;
            const float v = intersector.closestV;
            
            const Vector3& n0 = intersector.closestTri->n0;
            const Vector3& n1 = intersector.closestTri->n1;
            const Vector3& n2 = intersector.closestTri->n2;

            // Smooth the normal across the surface
            hitResult.normal = (1.0f - u - v) * n0 + u * n1 + v * n2;
            hitResult.normal.unitize();

        } else {
            // Use the face normal
            hitResult.normal = intersector.closestTri->n0;
        }

        // Our triangles are two sided, so flip the normal if it is facing away from the ray
        if (hitResult.normal.dot(worldRay.direction) > 0) {
            hitResult.normal = -hitResult.normal;
        }

        debugAssert(hitResult.normal.isUnit());

        hitResult.bsdf = m_bsdf;
        hitResult.emissionFunction = NULL;
    }
}


void MeshEntity::getBounds(AABox& box) const {
    box = m_bounds;
}


int MeshEntity::primitiveCount() const {
    return m_triSet.size();
}


////////////////////////////////////////////////////////////

MeshEntity::Tri::Tri() {}


MeshEntity::Tri::Tri(const Vector3& v0, 
         const Vector3& v1, 
         const Vector3& v2, 
         const Vector3& n0, 
         const Vector3& n1, 
         const Vector3& n2) :
    v0(v0), e1(v1 - v0), e2(v2 - v0),
    n0(n0), n1(n1), n2(n2) {
}
