#ifndef COLLISION_BODY_MESH_HPP
#define COLLISION_BODY_MESH_HPP

#include "../../../eshared.hpp"
#include "types.hpp"
#include "grid3d.hpp"

class Collision_body;

/// structure used to initialise the mesh
struct Collision_triangle
{
  Collision_triangle() {}
  Collision_triangle(const Position & v0, const Position & v1, const Position & v2) 
    : v0(v0), v1(v1), v2(v2) {}
  Position v0, v1, v2;
};

/// interface used when populating the mesh
class Collision_mesh_object
{
public:
  virtual ~Collision_mesh_object() {}

  /// if the second version of initialise is used the derived class should implement this
  /// to return the relevant info. It should return true (to indicate the info has been set).
  virtual bool get_mesh_info(const Position & pos, 
                             bool & inside,
                             Vector3 & vector_to_surface) const = 0;
};

//==============================================================
// Collision_body_mesh
//==============================================================
class Collision_body_mesh
{
public:
  Collision_body_mesh();

  /// initialises the mesh. extra_frac indicates hw much bigger the mesh should be
  /// than the bounding volume. The triangle are passed in in the body coordinates.
  /// If body is zero, the triangle mesh MUST be closed, as it is used to populate 
  /// the mesh.
  /// If body is non-zero it will be used to populate the mesh by calls to 
  /// get_mesh_info
  void construct_mesh(int nx, int ny, int nz, 
                      Scalar extra_frac, 
                      const std::vector<Collision_triangle> & triangles,
                      const Collision_mesh_object * body);

  /// clones the mesh passed in
  void construct_mesh(const Collision_body_mesh & mesh);

  /// Returns the info at the specified position (in body coords). Direction (normalised) and
  /// distance to body surface are returned by reference. dist will be -ve if outside. retval indicates
  /// if the position is inside the body.
  /// if you want an accurate distance when outside, specify the flag
  bool get_point_info(const Position & pos,
                      Vector3 & dir,
                      Scalar & dist,
                      bool accurate_outside_dist);

  inline Scalar get_sqr_distance_to_bounding_box(const Position & pos) const;

  /// Returns the bounding radius - i.e. the max of the mesh limits
  Scalar get_bounding_radius() const {return m_bounding_radius;}

  /// displays the mesh in terms of the vectors
  void display_object();

  /// specifies if mesh lookup should interpolate, rather than use closest point.
  void static set_interpolate_mesh(bool interp) {m_interpolate_mesh = interp;}

private:
  /// helper fn to walk through the triangles and populate our mesh
  void populate_mesh(const std::vector<Collision_triangle> & triangles);

  void populate_mesh(const Collision_mesh_object & body);

  /// m_nx etc are the number of _points_ in each direction, so 
  /// m_max_x = m_min_x + (m_nx - 1) * dx
  int m_nx, m_ny, m_nz;
  Scalar m_min_x, m_min_y, m_min_z;
  Scalar m_max_x, m_max_y, m_max_z;
  Scalar m_dx, m_dy, m_dz;
  Scalar m_bounding_radius;
  struct Datum
  {
    Vector3 dir;  // normalised direction pointing away from the body centre.
    Scalar  dist; // distance to the inside - +ve if inside
  };

  Grid3D<Datum> m_data;

  static bool m_interpolate_mesh;
};

// ================= inline functions ===================
inline Scalar Collision_body_mesh::get_sqr_distance_to_bounding_box(const Position & pos) const
{
  // closest point on the box
  Position box_pos;
  if (pos[0] > m_max_x)
    box_pos[0] = m_max_x;
  else if (pos[0] < m_min_x)
    box_pos[0] = m_min_x;
  else
    box_pos[0] = pos[0];

  if (pos[1] > m_max_y)
    box_pos[1] = m_max_y;
  else if (pos[1] < m_min_y)
    box_pos[1] = m_min_y;
  else
    box_pos[1] = pos[1];

  if (pos[2] > m_max_z)
    box_pos[2] = m_max_z;
  else if (pos[2] < m_min_z)
    box_pos[2] = m_min_z;
  else
    box_pos[2] = pos[2];

  return (pos - box_pos).mag2();
}


#endif
