/////////////////////////////////////////////////////////////
//                                                         //
// Copyright (c) 2007-2014 by The University of Queensland //
// Centre for Geoscience Computing                         //
// http://earth.uq.edu.au/centre-geoscience-computing      //
//                                                         //
// Primary Business: Brisbane, Queensland, Australia       //
// Licensed under the Open Software License version 3.0    //
// http://www.opensource.org/licenses/osl-3.0.php          //
//                                                         //
/////////////////////////////////////////////////////////////

#include "MeshVolume.h"

// --- Project includes ---
#include "geometry/Triangle3D.h"

//-- system includes --
#include <cstdlib>

using std::rand;

// --- STL includes ---
#include <utility>

// --- I/O includes
#include <iostream>

using std::make_pair;

/*!
  helper function, return random value between min & max
*/
double MeshVolume::m_random(double imin,double imax) const
{
  return imin+((imax-imin)*((double)(rand())/(double)(RAND_MAX)));
}

/*!
  Constructor
*/
MeshVolume::MeshVolume()
{}

/*!
  Constructor

  \param tp the triangle set
*/
MeshVolume::MeshVolume(const TriPatchSet& tp)
{
  m_mesh=tp;
  m_MinPoint=m_mesh.getBoundingBox().first;
  m_MaxPoint=m_mesh.getBoundingBox().second;
  m_DistPoint=m_MinPoint-Vector3(1.0,1.0,1.0);
}

/*!
  get bounding box
*/
pair<Vector3,Vector3> MeshVolume::getBoundingBox()
{
  return m_mesh.getBoundingBox();
}

/*! 
  Get a point inside the volume. The argument is ignored.

  \warning if the mesh is incorrect the function might loop forever
*/
Vector3 MeshVolume::getAPoint(int) const
{
  Vector3 res;
  double px,py,pz;
 
  do{
    px=m_random(m_MinPoint.x(),m_MaxPoint.x());
    py=m_random(m_MinPoint.y(),m_MaxPoint.y());
    pz=m_random(m_MinPoint.z(),m_MaxPoint.z());
    res=Vector3(px,py,pz);
  } while (!isIn(res));

  return res;
}

/*!
  returns a map of the closest mesh facets and their distances to a given point

  \param P the point
  \param nmax the maximum number of objects to be returned
*/ 
const map<double,const AGeometricObject*> MeshVolume::getClosestObjects(const Vector3& P,int nmax) const
{
  map<double,const AGeometricObject*> res;

  for(vector<Triangle3D>::const_iterator iter=m_mesh.triangles_begin();
      iter!=m_mesh.triangles_end();
      iter++){
    double ndist=iter->getDist(P);
    res.insert(make_pair(ndist,&(*iter)));
  }

  return res;  
}

/*!
  Check if a point is inside the volume.  Works by checking if a 
  line between the point and a point outside crosses
  an odd number of mesh facets.

  \param P the point
*/
bool MeshVolume::isIn(const Vector3& P) const
{
  int cross_count=0;
  for(vector<Triangle3D>::const_iterator iter=m_mesh.triangles_begin();
      iter!=m_mesh.triangles_end();
      iter++){
    if(iter->crosses(P,m_DistPoint)) cross_count++;
  }
  return (cross_count%2)==1;
}

/*!
  Check if a sphere is fully inside the volume. Works by testing if center of the 
  sphere is inside and sphere doesn't intersect a mesh facet.

  \param S the sphere
*/
bool MeshVolume::isIn(const Sphere& S)
{
  bool res=isIn(S.Center());
  vector<Triangle3D>::const_iterator iter=m_mesh.triangles_begin();
  while((iter!=m_mesh.triangles_end()) && res){
    res=(iter->getDist(S.Center())>S.Radius());
    iter++;
  }

  return res;
}

/*!
  Check if a sphere is fully outside the volume. Works by testing if center of the 
  sphere is not inside and sphere doesn't intersect a mesh facet.

  \param S the sphere
*/
bool MeshVolume::isFullyOutside(const Sphere& S)
{
  bool res=!isIn(S.Center());

  vector<Triangle3D>::const_iterator iter=m_mesh.triangles_begin();
  while((iter!=m_mesh.triangles_end()) && res){
    res=(iter->getDist(S.Center())>S.Radius());
    iter++;
  }

  return res;
}
