#ifndef COLLISION_BODY_HPP
#define COLLISION_BODY_HPP

#include "types.hpp"
#include "distance.hpp"
#include "collision_body_mesh.hpp"

#include <vector>

class Rigid_body;
class Collision_mesh_object;

//==============================================================
// Collision_body_edge
//==============================================================
struct Collision_body_edge
{
  Collision_body_edge() {}
  Collision_body_edge(int i0, int i1, int num_samples) 
    : i0(i0), i1(i1), num_samples(num_samples > 2 ? num_samples : 2) 
    {
    }
  
  int i0, i1, num_samples;
};

/// Holds the data used in testing for collisions
class Collision_body
{
public:
  Collision_body(Rigid_body * rigid_body);

  Rigid_body * rigid_body() const {return m_rigid_body;}

  /// initialise the body. If the flag is set the mesh will be constructed 
  /// based on the triangle list, and will be mesh_extra_frac times bigger 
  /// in each direction. Otherwise the body will be interrogated for each
  // point on the mesh using get_mesh_info(...)
  void initialise(int mesh_nx, int mesh_ny, int mesh_nz, 
                  Scalar mesh_extra_frac, 
                  const std::vector<Collision_triangle> & triangles_for_mesh,
                  const std::vector<Position> & points,
                  const std::vector<Position> & edge_points,
                  const std::vector<Collision_body_edge> & edges,
                  const Collision_mesh_object * mesh_object = 0);

  /// initialises by cloning the mesh passed in
  void initialise(int mesh_nx, int mesh_ny, int mesh_nz, 
                  Scalar mesh_extra_frac, 
                  const std::vector<Collision_triangle> & triangles,
                  const std::vector<Position> & points,
                  const std::vector<Position> & edge_points,
                  const std::vector<Collision_body_edge> & edges,
                  const Collision_body_mesh & mesh);

  /// sets the body position
  void set_position(const Position & position)
    {
      m_position = position;
    }
  
  /// sets the body orientation
  void set_orientation(const Orientation & orientation)
    {
      m_orientation = orientation;
	    m_inv_orientation = transpose(orientation);
    }
  
  /// calculates the world point/edge positions
  void calculate_world_properties();

  /// removes all the point/edge info from this object...
  void remove_all_point_info();

  /// see comments in Collision_body_mesh
  bool get_point_info(const Position & pos,
                      Vector3 & dir,
                      Scalar & dist,
                      bool accurate_outside_dist)
  { return m_body_mesh.get_point_info(pos, dir, dist, accurate_outside_dist); }

  Scalar get_sqr_distance_to_bounding_box(const Position & pos) const
  { return m_body_mesh.get_sqr_distance_to_bounding_box(pos); }

  /// the world points. Assumes calculate_world_properties has been called.
  const std::vector<Position> & world_points() const {return m_world_points;}

  /// the world edge points. Assumes calculate_world_properties has been called.
  const std::vector<Position> & world_edge_points() const {return m_world_edge_points;}

  /// the edges (indexes into world_edge_points)
  const std::vector<Collision_body_edge> & edges() const {return m_edges;}

  /// Intended for internal use by Physics - we get told about the collisions
  /// we're involved with. Used to resolve penetrations.
  /// The value is the index into the list of collisions owned by physics.
  std::vector<int> & collisions() {return m_collisions;}

  /// intended for use by collision detection
  std::vector<Collision_body *> & non_collidables() {return m_non_collidables;}

  // Inherited from Render_object
  inline void get_bounding_sphere(Position & pos, Scalar & radius);
  const Position & get_position() const {return m_position;}
  const Orientation & get_orientation() const {return m_orientation;}
  const Orientation & get_inv_orientation() const {return m_inv_orientation;}

  const Collision_body_mesh & get_collision_mesh() const {return m_body_mesh;}

private:
  void debug_render_mesh();
  void debug_render_points_and_edges();

  // our owner
  Rigid_body * m_rigid_body;

  /// Position
  Position m_position;

  // Orientation
  Orientation m_orientation;
  Orientation m_inv_orientation;

  /// points in this body used for testing against other bodies.
  std::vector<Position> m_body_points;
  
  /// points in this body used by the edges
  std::vector<Position> m_body_edge_points;
  
  /// edges in this body used for testing against other bodies
  std::vector<Collision_body_edge> m_edges;

  /// points converted to world space
  std::vector<Position> m_world_points;

  /// edge points converted to world space
  std::vector<Position> m_world_edge_points;

  /// the volume information used when other bodies test their points etc
  Collision_body_mesh m_body_mesh;

  /// a list of other bodies we don't collide with
  std::vector<Collision_body *> m_non_collidables;

  /// all the collisions we're involved with
  std::vector<int> m_collisions;
};

//==============================================================
// get_bounding_sphere
//==============================================================
inline void Collision_body::get_bounding_sphere(
  Position & pos, Scalar & radius)
{
  pos = m_position;
  radius = m_body_mesh.get_bounding_radius();
}


#endif


