// Copyright (C) 2010, Guy Barrand. All rights reserved.
// See the file tools.license for terms.

#ifndef tools_plane
#define tools_plane

#include "line"

namespace tools {

template <class VEC3>
class plane {
protected:
  typedef typename VEC3::elem_t T;
public:
  plane(){}

  plane(const VEC3& a_p0,const VEC3& a_p1,const VEC3& a_p2) {
    // Construct a plane given 3 points.
    // Orientation is computed by taking (p1 - p0) x (p2 - p0) and
    // pointing the normal in that direction.

    VEC3 P = a_p1;
    P.subtract(a_p0);
    VEC3 P2 = a_p2;
    P2.subtract(a_p0);
    P.cross(P2,m_normal);
    if(!m_normal.normalize()) {} //throw
    m_distance =
      m_normal.v0() * a_p0.v0() +
      m_normal.v1() * a_p0.v1() +
      m_normal.v2() * a_p0.v2();
  }

  plane(const VEC3& a_normal,const T& a_distance){
    set(a_normal,a_distance);
  }

  plane(const VEC3& a_normal,const VEC3& a_point){
    set(a_normal,a_point);
  }

  virtual ~plane() {}
public:
  plane(const plane& a_from)
  :m_normal(a_from.m_normal)
  ,m_distance(a_from.m_distance)
  {}
  plane& operator=(const plane& a_from) {
    m_normal = a_from.m_normal;
    m_distance = a_from.m_distance;
    return *this;
  }

public:
  bool is_valid() const {return m_normal.length()?true:false;}

  void offset(const T& a_distance){
    // Offset a plane by a given distance.
    m_distance += a_distance;
  }

  bool intersect(const line<VEC3>& a_line,VEC3& a_intersection) const {
    // Intersect line and plane, returning true if there is an intersection
    // false if line is parallel to plane
    const VEC3& pos = a_line.position();
    const VEC3& dir = a_line.direction();
    T d = m_normal.dot(dir);
    if(d==T()) return false;
    T t = (m_distance - m_normal.dot(pos))/d;
    a_intersection = dir;
    a_intersection.multiply(t);
    a_intersection.add(pos);
    //a_intersection = pos + t * dir;
    return true;
  }

  bool is_in_half_space(const VEC3& a_point) const {
    // Returns true if the given point is within the half-space
    // defined by the plane
    //vec pos = m_normal * m_distance;
    VEC3 pos = m_normal;
    pos.multiply(-m_distance);
    pos.add(a_point);
    return (m_normal.dot(pos) >= T() ? true : false);
  }

  const VEC3& normal() const {return m_normal;}

  T distance_from_origin() const {return m_distance;}

  T distance(const VEC3& a_point) const {
    // Return the distance from point to plane. Positive distance means
    // the point is in the plane's half space.
    return a_point.dot(m_normal) - m_distance;
  }

  void set(const VEC3& a_normal,const T& a_distance){
    m_normal = a_normal;
    if(!m_normal.normalize()) {} //throw
    m_distance = a_distance;
  }

  void set(const VEC3& a_normal,const VEC3& a_point){
    // Construct a plane given normal and a point to pass through
    // Orientation is given by the normal vector n.
    m_normal = a_normal;
    if(!m_normal.normalize()) {} //throw
    m_distance =
      m_normal.v0() * a_point.v0() +
      m_normal.v1() * a_point.v1() +
      m_normal.v2() * a_point.v2();
  }

public: //iv2sg
  const VEC3& getNormal() const {return m_normal;}
protected:
  // equation of the plane is :
  //  norm[0]*x+norm[1]*y+norm[2]*z = dist

  VEC3 m_normal; //normalized.
  T m_distance;
};

}

#endif
