/***************************************************************************
**
**  This file is part of QGpCoreMath.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file is distributed in the hope that it will be useful, but WITHOUT
**  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
**  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2010-06-07
**  Copyright: 2010-2019
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "Plane.h"

namespace QGpCoreMath {

/*!
  \class Plane Plane.h
  \brief A geometrical plane

  A geometrical plane optimized to return the interpolated z at any position.
*/

Plane::Plane(const Point& normal, const Point& reference)
{
  TRACE;
  _normalVector=normal;
  _normalVector/=_normalVector.length();
  _references[0]=reference;
  _d=_normalVector.scalarProduct(_references[0]);
}

Plane::Plane(const Plane& o)
{
  TRACE;
  _references[0]=o._references[0];
  _references[1]=o._references[1];
  _references[2]=o._references[2];
  _directionalVector[0]=o._directionalVector[0];
  _directionalVector[1]=o._directionalVector[1];
  _normalVector=o._normalVector;
  _d=o._d;
}

/*!
  Calculates internal normal vector from 3 references, valid only after altering
  z components from references points.
*/
void Plane::setNormalVectorXY()
{
  TRACE;
  _directionalVector[0].setZ(_references[0].z()-_references[1].z());
  _directionalVector[1].setZ(_references[0].z()-_references[2].z());
  _normalVector.setX(_directionalVector[0].y()*_directionalVector[1].z()-
                     _directionalVector[0].z()*_directionalVector[1].y());
  _normalVector.setY(_directionalVector[1].x()*_directionalVector[0].z()-
                     _directionalVector[1].z()*_directionalVector[0].x());
}

/*!
  Calculates internal normal vector from 3 references
*/
bool Plane::setNormalVector()
{
  TRACE;
  _directionalVector[0].setX(_references[0].x()-_references[1].x());
  _directionalVector[1].setX(_references[0].x()-_references[2].x());
  _directionalVector[0].setY(_references[0].y()-_references[1].y());
  _directionalVector[1].setY(_references[0].y()-_references[2].y());
  _normalVector.setZ(_directionalVector[0].x()*_directionalVector[1].y()-
                     _directionalVector[0].y()*_directionalVector[1].x());
  setNormalVectorXY();
  double n=_normalVector.length();
  if(n>0.0) {
    _normalVector/=n;
    _d=_normalVector.scalarProduct(_references[0]);
    return true;
  } else {
    return false;
  }
}

/*!
  \a p must have at least 3 points. They must all belong to the same plane.
  It searches for the first combination of three non co-linear points.
*/
bool Plane::setReferences(const QVector<Point>& p)
{
  TRACE;
  ASSERT(p.count()>=3);
  int n=p.count();
  for(int i=0; i<n; i++) {
    setReference(0, p.at(i));
    for(int j=i+1; j<n; j++) {
      setReference(1, p.at(j));
      for(int k=j+1; k<n; k++) {
        setReference(2, p.at(k));
        if(setNormalVector()) {
          return true;
        }
      }
    }
  }
  return false;
}

/*!
  \fn void Plane::setZ(int index, double z)
  \a index ranges from 0 to 2. setNormalVectorXY() must be called
  whenever z is changed.
*/

/*!
  \fn double Plane::z(const Point& at)
  Returns the z value at \a at, only (x, y) are considered in \a at.
*/

bool Plane::includes(const Point& p) const
{
  return fabs(_d-_normalVector.scalarProduct(p))<1e-5;
}

} // namespace QGpCoreMath
