/**
 \file G3D/Triangle.h
  
 \maintainer Morgan McGuire, http://graphics.cs.williams.edu
 
 \created 2003-04-05
 \edited  2011-06-20

 \cite Random point method by  Greg Turk, Generating random points in triangles.  In A. S. Glassner, ed., Graphics Gems, pp. 24-28. Academic Press, 1990

 Copyright 2000-2012, Morgan McGuire.
 All rights reserved.
 */

#ifndef G3D_Triangle_h
#define G3D_Triangle_h

#include "G3D/platform.h"
#include "G3D/g3dmath.h"
#include "G3D/Vector3.h"
#include "G3D/Plane.h"
#include "G3D/BoundsTrait.h"
#include "G3D/debugAssert.h"
#include <string>

namespace G3D {

/**
  A generic triangle representation.  This should not be used
  as the underlying triangle for creating models; it is intended
  for providing fast property queries but requires a lot of
  storage and is mostly immutable.
  */
class Triangle {
private:
    friend class CollisionDetection;
    friend class Ray;

    Vector3                     _vertex[3];

    /** edgeDirection[i] is the normalized vector v[i+1] - v[i] */
    Vector3                     edgeDirection[3];
    float                       edgeMagnitude[3];
    Plane                       _plane;
    Vector3::Axis               _primaryAxis;

    /** vertex[1] - vertex[0] */
    Vector3                     _edge01;

    /** vertex[2] - vertex[0] */
    Vector3                     _edge02;

    float                       _area;

    void init(const Vector3& v0, const Vector3& v1, const Vector3& v2);

public:
    
    Triangle(class BinaryInput& b);
    void serialize(class BinaryOutput& b);
    void deserialize(class BinaryInput& b);

    Triangle();
    
    Triangle(const Point3& v0, const Point3& v1, const Point3& v2);
    
    ~Triangle();

    /** 0, 1, or 2 */
    inline const Point3& vertex(int n) const {
        debugAssert((n >= 0) && (n < 3));
        return _vertex[n];
    }

    /** vertex[1] - vertex[0] */
    inline const Vector3& edge01() const {
        return _edge01;
    }

    /** vertex[2] - vertex[0] */
    inline const Vector3& edge02() const {
        return _edge02;
    }

    float area() const;

    Vector3::Axis primaryAxis() const {
        return _primaryAxis;
    }

    const Vector3& normal() const;

    /** Barycenter */
    Point3 center() const;

    const Plane& plane() const;

    /** Returns a random point in the triangle. */
    Point3 randomPoint() const;

    inline void getRandomSurfacePoint
    (Point3& P, 
     Vector3& N = Vector3::ignore()) const {
        P = randomPoint();
        N = normal();
    }

    /**
     For two triangles to be equal they must have
     the same vertices <I>in the same order</I>.
     That is, vertex[0] == vertex[0], etc.
     */
    inline bool operator==(const Triangle& other) const {
        for (int i = 0; i < 3; ++i) {
            if (_vertex[i] != other._vertex[i]) {
                return false;
            }
        }

        return true;
    }

    inline size_t hashCode() const {
        return
            _vertex[0].hashCode() +
            (_vertex[1].hashCode() >> 2) +
            (_vertex[2].hashCode() >> 3);
    }

    void getBounds(class AABox&) const;

    /**
       @brief Intersect the ray at distance less than @a distance.

       @param distance Set to the maximum distance (can be G3D::inf())
       to search for an intersection.  On return, this is the smaller
       of the distance to the intersection, if one exists, and the original
       value.
       
       @param baryCoord  If a triangle is hit before @a distance, a
       the barycentric coordinates of the hit location on the triangle.
       Otherwise, unmodified.

       @return True if there was an intersection before the original distance.
     */
    bool intersect(const class Ray& ray, float& distance, float baryCoord[3]) const;
};

} // namespace G3D

template <> struct HashTrait<G3D::Triangle> {
    static size_t hashCode(const G3D::Triangle& key) { return key.hashCode(); }
};


template<> struct BoundsTrait<class G3D::Triangle> {
    static void getBounds(const G3D::Triangle& t, G3D::AABox& out) { t.getBounds(out); }
};

#endif