/**
 \file Plane.h

 Plane class

 \maintainer Morgan McGuire, http://graphics.cs.williams.edu

 \created 2001-06-02
 \edited  2010-12-04
*/

#ifndef G3D_Plane_h
#define G3D_Plane_h

#include "G3D/platform.h"
#include "G3D/Vector3.h"
#include "G3D/Vector4.h"
#include "G3D/debugAssert.h"

namespace G3D {

/**
 An infinite 2D plane in 3D space.
 */
class Plane {
private:

    /** normal.Dot(x,y,z) = distance */
    Vector3                        _normal;
    float                        _distance;

    /**
     Assumes the normal has unit length.
     */
    Plane(const Vector3& n, float d) : _normal(n), _distance(d) {
    }

public:

    Plane() : _normal(Vector3::unitY()), _distance(0) {
    }

    /** Format is:
        - Plane(normal, point)
    */
    explicit Plane(const class Any& a);

    Any toAny() const;

    /**
     Constructs a plane from three points.
     */
    Plane
     (const Point3&      point0,
      const Point3&      point1,
      const Point3&      point2);

    /**
     Constructs a plane from three points, where at most two are
     at infinity (w = 0, not xyz = inf).
     */
    Plane(
        Vector4         point0,
        Vector4         point1,
        Vector4         point2);

    /**
     The normal will be unitized.
     */
    Plane
     (const Vector3&     normal,
      const Point3&      point);

    static Plane fromEquation(float a, float b, float c, float d);

    Plane(class BinaryInput& b);

    void serialize(class BinaryOutput& b) const;
    void deserialize(class BinaryInput& b);
    
    virtual ~Plane() {}

    /**
     Returns true if point is on the side the normal points to or 
     is in the plane.
     */
    inline bool halfSpaceContains(Point3 point) const {
        // Clamp to a finite range for testing
        point = point.clamp(Vector3::minFinite(), Vector3::maxFinite());

        // We can get away with putting values *at* the limits of the float32 range into
        // a dot product, since the dot product is carried out on float64.
        return _normal.dot(point) >= _distance;
    }

    /**
     Returns true if point is on the side the normal points to or 
     is in the plane.
     */
    inline bool halfSpaceContains(const Vector4& point) const {
        if (point.w == 0) {
            return _normal.dot(point.xyz()) > 0;
        } else {
            return halfSpaceContains(point.xyz() / point.w);
        }
    }

    /**
     Returns true if point is on the side the normal points to or 
     is in the plane.  Only call on finite points.  Faster than halfSpaceContains.
     */
    inline bool halfSpaceContainsFinite(const Point3& point) const {
        debugAssert(point.isFinite());
        return _normal.dot(point) >= _distance;
    }

    /**
     Returns true if the point is nearly in the plane.
     */
    inline bool fuzzyContains(const Point3& point) const {
        return fuzzyEq(point.dot(_normal), _distance);
    }

    inline const Vector3& normal() const {
        return _normal;
    }

    /**
      Returns distance from point to plane. Distance is negative if point is behind (not in plane in direction opposite normal) the plane.
    */
    inline float distance(const Vector3& x) const {
        return (_normal.dot(x) - _distance);
    }

    inline Point3 closestPoint(const Point3& x) const {
        return x + (_normal * (-distance(x)));
    }

    /** Returns normal * distance from origin */
    Vector3 center() const {
        return _normal * _distance;
    }

    /**
     Inverts the facing direction of the plane so the new normal
     is the inverse of the old normal.
     */
    void flip();

    /**
      Returns the equation in the form:

      <CODE>normal.Dot(Vector3(<I>x</I>, <I>y</I>, <I>z</I>)) + d = 0</CODE>
     */
    void getEquation(Vector3& normal, double& d) const;
    void getEquation(Vector3& normal, float& d) const;

    /**
      ax + by + cz + d = 0
     */
    void getEquation(double& a, double& b, double& c, double& d) const;
    void getEquation(float& a, float& b, float& c, float& d) const;

    std::string toString() const;
};

} // namespace

#endif