/////////////////////////////////////////////////////////////
//                                                         //
// Copyright (c) 2007-2017 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.apache.org/licenses/LICENSE-2.0              //
//                                                         //
/////////////////////////////////////////////////////////////

#include "CylinderVol.h"

//--- 
#include <cmath>
#include <cstdlib>

using std::cos;
using std::sin; 
using std::fabs; 

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

using std::make_pair;

CylinderVol::CylinderVol()
{ }

CylinderVol::CylinderVol(const Vector3& c,const Vector3& axis,double l,double r): m_cyl(c,axis,r)
{
  m_bottom=Plane(c,axis);
  m_top=Plane(c+l*axis.unit(),-1.0*axis);
  m_len=l;
  // calculate "in plane" vectors
  m_xd=Vector3(axis.y(),-axis.x(),0);
  if (m_xd.norm()==0) {
    m_xd = Vector3(0,axis.z(),-axis.y()) ;
  }
  if (m_xd.norm()==0) {
    m_xd = Vector3(axis.z(),0,-axis.x()) ;
  }
  m_xd = m_xd.unit();
  m_yd = cross(axis,m_xd);

}

pair<Vector3,Vector3> CylinderVol::getBoundingBox()
{
  Vector3 center=m_cyl.getBasePoint()+0.5*m_len*m_cyl.getAxis();
    Vector3 abs_axis=Vector3(fabs(m_cyl.getAxis().X()),fabs(m_cyl.getAxis().Y()),fabs(m_cyl.getAxis().Z()));
    Vector3 p0=center-0.5*m_len*abs_axis-Vector3(m_cyl.getRadius(),m_cyl.getRadius(),m_cyl.getRadius());
  Vector3 p1=center+0.5*m_len*abs_axis+Vector3(m_cyl.getRadius(),m_cyl.getRadius(),m_cyl.getRadius());

  return make_pair(p0,p1);
}

Vector3 CylinderVol::getAPoint(int) const
{
  double l=m_len*((double)(rand())/(double)(RAND_MAX));
  double r=m_cyl.getRadius()*((double)(rand())/(double)(RAND_MAX));
  double phi=2*M_PI*((double)(rand())/(double)(RAND_MAX));
  
  Vector3 p0=m_cyl.getBasePoint()+l*m_cyl.getAxis();
  double x=cos(phi);
  double y=sin(phi);
  Vector3 dir=x*m_xd+y*m_yd;

  return p0+r*dir;
}

const map<double,const AGeometricObject*> CylinderVol::getClosestObjects(const Vector3& P,int) const
{
  map<double,const AGeometricObject*> res;
  
  res.insert(make_pair(m_cyl.getDist(P),&m_cyl));
  res.insert(make_pair(m_bottom.getDist(P),&m_bottom));
  res.insert(make_pair(m_top.getDist(P),&m_top));

  return res;
}

bool CylinderVol::isIn(const Vector3& P) const
{
  bool in_cyl=(m_cyl.getDirDist(P)>0);
  bool above_bottom=(m_bottom.getNormal()*(P-m_bottom.getOrig())>0);
  bool below_top=(m_top.getNormal()*(P-m_top.getOrig())>0);

  return in_cyl && above_bottom && below_top;
}

bool CylinderVol::isIn(const Sphere& S)
{ 
  bool in_cyl=(m_cyl.getDirDist(S.Center())>S.Radius());
  bool above_bottom=(m_bottom.getNormal()*(S.Center()-m_bottom.getOrig())>S.Radius());
  bool below_top=(m_top.getNormal()*(S.Center()-m_top.getOrig())>S.Radius());
  bool res=in_cyl && above_bottom && below_top;

  return res;
}

/*
  check if a Sphere is completely outside the cylinder

  \param S the sphere
*/
bool CylinderVol::isFullyOutside(const Sphere& S)
{
  bool outside_cyl=(m_cyl.getDirDist(S.Center())<(-1.0*S.Radius()));
  bool below_bottom=(m_bottom.getNormal()*(S.Center()-m_bottom.getOrig())<(-1.0*S.Radius()));
  bool above_top=(m_top.getNormal()*(S.Center()-m_top.getOrig())<(-1.0*S.Radius()));
  bool res=outside_cyl || below_bottom || above_top;

  return res;

}

ostream& operator << (ostream& ost,const CylinderVol& T)
{
   return ost;
}

