/*
								+----------------------------------+
								|                                  |
								|  ***  Polygonal mesh class  ***  |
								|                                  |
								|   Copyright © -tHE SWINe- 2008   |
								|                                  |
								|            PolyMesh.h            |
								|                                  |
								+----------------------------------+
*/

#pragma once
#ifndef __POLYGON_MESH_INCLUDED
#define __POLYGON_MESH_INCLUDED

/**
 *	@file lml/PolyMesh.h
 *	@date 2008
 *	@author -tHE SWINe-
 *	@brief polygonal mesh class
 *
 *	@date 2008-06-04
 *
 *	fixed error in CPolyMesh::BoundingBox() (it returned minimal corner coordinates in maximum)
 *
 *	conducted series of experiments to find out what is the most efficient vertex hash
 *	cell size. it was found that for unit sphere with 655362 vertices there is ratio
 *	2:3 between required vertices per cell and real number of vertices / cell. this ratio
 *	is however independent of vertex cell size. timing of vertex hash / vertex merge table
 *	operations is independent as well, aparts from CVertexMergeTable constructor which
 *	runs in linear time proportional to number of vertices / cell. memory trashing
 *	is logarithmic, the bend is approximately at 11 so this value has been chosen as default.
 *
 *	@date 2008-06-24
 *
 *	renamed CPolygon2::r_t_Vertex() and CPolyMesh::r_t_Vertex() to
 *	CPolygon2::r_Vertex() and CPolyMesh::r_Vertex() respectively
 *
 *	added vertex color comparison to CPolyMesh::CVertexMergeTable() and dependent functions
 *	(CPolyMesh::OptimizeVertices(), CPolyMesh::CalcVertexNormals_Thresh())
 *
 *	@date 2008-08-08
 *
 *	added \#ifdef for windows 64, added \#define for GL_GLEXT_LEGACY (for linux builds)
 *
 *	@date 2009-05-23
 *
 *	removed all instances of std::vector::reserve and replaced them by stl_ut::Reserve_*
 *
 *	@date 2009-10-20
 *
 *	fixed some warnings when compiling under VC 2005, implemented "Security
 *	Enhancements in the CRT" for VC 2008. compare against MyProjects_2009-10-19_
 *
 *	@date 2012-06-19
 *
 *	Moved multiple inclusion guard before file documentation comment.
 *
 *	@date 2013-02-23
 *
 *	Started complete overhaul to make it more efficient, less confusing and easier to use
 *	while maintaining or improving performance. Converting documentation to doxygen format.
 *	Storing polygons in a pool as well, saves calling new() many times.
 *
 */

#include "MyVertex.h"
#include "VertexPool.h"
#include "Polygon.h"
#include "../Integer.h"

/*
 *								=== CPolyMesh ===
 */

/**
 *	@brief a simple polygonal mesh class with shared vertex pool
 */
class CPolyMesh {
public:
	typedef uint32_t TSurfaceFlags; /**< @brief surface flags type */
	typedef int TMaterialId; /**< @brief material id type */
	typedef TMyVertex _TyVertex; /**< @brief vertex type */
	typedef TRefVertex2<_TyVertex> _TyRefVertex; /**< @brief reference vertex type */
	typedef CPolygon2<_TyRefVertex, TSurfaceFlags, TMaterialId> _TyPolygon; /**< @brief specialized polygon type */
	typedef CForwardAllocatedPool<_TyPolygon> _TyPolygonPool; /**< @brief specialized polygon pool type */
	typedef CForwardAllocatedPool<_TyVertex> _TyVertexPool; /**< @brief specialized vertex pool type */

	/**
	 *	@brief utility class for transforming (the whole) mesh using CVertexPool::ForEach
	 */
	class CTransform {
	protected:
		Matrix4f m_t_transform; /**< @brief transformation matrix */
		Matrix3f m_t_normal; /**< @brief normal matrix */

	public:
		inline CTransform(const Matrix4f &r_t_transform)
			:m_t_transform(r_t_transform)
		{
			r_t_transform.t_RotationPart().InvertTranspose_To(m_t_normal);
			// normal matrix is inverse-transpose of the upper-left 3x3 submatrix
		}

		inline void operator ()(_TyVertex &r_vertex) const
		{
			r_vertex.v_position = m_t_transform.v_Transform_Pos(r_vertex.v_position);
			r_vertex.v_normal = m_t_normal * r_vertex.v_normal;
			r_vertex.v_normal.Normalize(); // !!
			// transform both positions and normals
		}
	};

protected:
	_TyPolygonPool m_poly_list; /**< @brief list of polygons */
	_TyVertexPool m_vertex_list; /**< @brief vertex pool */

public:
	/**
	 *	@brief default constructor, creates empty mesh
	 */
	CPolyMesh();

	/**
	 *	@brief constructor; allocates clear mesh with a given number of vertices and polygons
	 *
	 *	@param[in] n_vertex_num is required number of vertices
	 *	@param[in] n_polygon_num is required number of poylgons
	 *
	 *	@note If it fails, it's guaranteed there are no vertices nor polygons.
	 *	@note This function throws std::bad_alloc.
	 */
	CPolyMesh(size_t n_vertex_num, size_t n_polygon_num); // throw(std::bad_alloc)

	/**
	 *	@brief copy-constructor
	 *
	 *	@param[in] r_mesh is a mesh to copy from
	 *
	 *	@note If it fails, it's guaranteed there are no vertices nor polygons.
	 *	@note This function throws std::bad_alloc.
	 */
	CPolyMesh(const CPolyMesh &r_mesh); // throw(std::bad_alloc)

	/**
	 *	@brief destructor
	 */
	~CPolyMesh();

	/**
	 *	@brief copy-operator
	 *
	 *	@return Returns reference to this mesh.
	 *
	 *	@note This just calls Copy(); if it fails, it's guaranteed that the mesh is empty.
	 *	@note This function throws std::bad_alloc.
	 */
	inline CPolyMesh &operator =(const CPolyMesh &r_poly_mesh) // throw(std::bad_alloc)
	{
		if(&r_poly_mesh == this)
			return *this; // handle self-assignment
		if(!Copy(r_poly_mesh))
			throw std::bad_alloc(); // rethrow
		return *this;
	}

	/**
	 *	@brief swap contents of this mesh with another one
	 *	@param[in] r_poly_mesh is the other mesh to swap with
	 */
	void Swap(CPolyMesh &r_poly_mesh);

	/**
	 *	@brief allocates clear mesh with a given number of vertices and polygons
	 *
	 *	@param[in] n_vertex_num is desired number of vertices
	 *	@param[in] n_polygon_num is desired number of polygons
	 *
	 *	@return Returns true on success, false on failure (if it fails, mesh is always empty).
	 *
	 *	@note The mesh contents will be deleted by this operation.
	 */
	bool Alloc(size_t n_vertex_num, size_t n_polygon_num);

	/**
	 *	@brief copies contents of r_poly_mesh
	 *	@return Returns true on success, false on failure (if it fails, mesh is always empty).
	 */
	bool Copy(const CPolyMesh &r_poly_mesh);

	/**
	 *	@brief adds contents of another mesh to this mesh
	 *	@param[in] r_poly_mesh is the mesh to be merged with this
	 *	@return Returns true on success, false on failure (if it fails, mesh is always empty).
	 */
	bool Merge(const CPolyMesh &r_poly_mesh);

	/**
	 *	@brief adds contents of another mesh to this mesh, with transformation
	 *
	 *	@param[in] r_poly_mesh is the mesh to be merged with this
	 *	@param[in] t_transform is the transformation matrix for the other mesh
	 *
	 *	@return Returns true on success, false on failure (if it fails, mesh is always empty).
	 *
	 *	@note Note that face normals are not transformed, only vertex normals.
	 */
	bool Merge(const CPolyMesh &r_poly_mesh, Matrix4f t_transform);

	/**
	 *	@brief applies transformation to the positions of the vertices of this mesh
	 *	@param[in] t_transform is the transformation matrix
	 *	@note Note that face normals are not transformed, only vertex normals.
	 */
	inline void Transform(Matrix4f t_transform)
	{
		m_vertex_list.ForEach(0, size_t(-1), CTransform(t_transform));
	}

	/**
	 *	@brief finds mesh bounding box
	 *
	 *	@param[out] r_v_min is minimum bounding box coordinate
	 *	@param[out] r_v_max is maximum bounding box coordinate
	 *
	 *	@return Returns true on success, false on failure (if there are no vertices).
	 *
	 *	@note In case there are no vertices, both r_v_min and r_v_max are set to (0, 0, 0).
	 */
	bool BoundingBox(Vector3f &r_v_min, Vector3f &r_v_max) const;

	/**
	 *	@brief gets const reference to vertex list
	 *	@return Returns const reference to vertex list.
	 */
	inline const _TyVertexPool &r_Vertex_Pool() const
	{
		return m_vertex_list;
	}

	/**
	 *	@brief gets reference to vertex list
	 *	@return Returns reference to vertex list.
	 */
	inline _TyVertexPool &r_Vertex_Pool()
	{
		return m_vertex_list;
	}

	/**
	 *	@brief gets const reference to polygon list
	 *	@return Returns const reference to polygon list.
	 */
	inline const _TyPolygonPool &r_Polygon_Pool() const
	{
		return m_poly_list;
	}

	/**
	 *	@brief gets reference to polygon list
	 *	@return Returns reference to polygon list.
	 */
	inline _TyPolygonPool &r_Polygon_Pool()
	{
		return m_poly_list;
	}

	/**
	 *	@brief gets returns number of mesh vertices
	 *	@return Returns returns number of mesh vertices.
	 */
	inline size_t n_Vertex_Num() const
	{
		return m_vertex_list.n_Size();
	}

	/**
	 *	@brief gets reference to vertex with a specified index
	 *	@param[in] n_index is a (zero-based) index of the vertex
	 *	@return Returns reference to vertex with the specified index.
	 *	@note Do not assume the vertices are in linear block of memory, they are not.
	 */
	inline _TyVertex &r_Vertex(size_t n_index)
	{
		return m_vertex_list[n_index];
	}

	/**
	 *	@brief gets const reference to vertex with a specified index
	 *	@param[in] n_index is a (zero-based) index of the vertex
	 *	@return Returns const reference to vertex with the specified index.
	 *	@note Do not assume the vertices are in linear block of memory, they are not.
	 */
	inline const _TyVertex &r_Vertex(size_t n_index) const
	{
		return m_vertex_list[n_index];
	}

	/**
	 *	@brief gets reference vertex to vertex with a specified index
	 *	@param[in] n_index is a (zero-based) index of the vertex
	 *	@return Returns reference vertex to vertex with the specified index.
	 *	@note Do not assume the vertices are in linear block of memory, they are not.
	 */
	inline _TyRefVertex t_RefVertex(size_t n_index)
	{
		return _TyRefVertex(m_vertex_list[n_index], m_vertex_list);
	}

	/**
	 *	@brief gets returns number of mesh polygons
	 *	@return Returns returns number of mesh polygons.
	 */
	inline size_t n_Polygon_Num() const
	{
		return m_poly_list.n_Size();
	}

	/**
	 *	@brief gets reference to polygon with a specified index
	 *	@param[in] n_index is a (zero-based) index of the polygon
	 *	@return Returns reference to polygon with the specified index.
	 *	@note Do not assume the polygons are in linear block of memory, they are not.
	 */
	inline _TyPolygon &r_Polygon(size_t n_index)
	{
		return m_poly_list[n_index];
	}

	/**
	 *	@brief gets const reference to polygon with a specified index
	 *	@param[in] n_index is a (zero-based) index of the polygon
	 *	@return Returns const reference to polygon with the specified index.
	 *	@note Do not assume the polygons are in linear block of memory, they are not.
	 */
	inline const _TyPolygon &r_Polygon(size_t n_index) const
	{
		return m_poly_list[n_index];
	}

	/**
	 *	@brief optimizes vertices (ie. merges all equivalent vertices)
	 *
	 *	@param[in] b_compare_normal is normal comparison flag
	 *	@param[in] b_compare_color is color comparison flag
	 *	@param[in] n_compare_texcoord_num is a number of texcoords to be compared
	 *	@param[in] f_epsilon_ex is epsilon for coordinate comparison
	 *		(used for all vertex coordinates)
	 *
	 *	@return Returns true on success, false on failure.
	 *
	 *	@note all epsilons are equal to f_epsilon from Vector.cpp
	 */
	bool OptimizeVertices(bool b_compare_normal, bool b_compare_color,
		size_t n_compare_texcoord_num, float f_epsilon_ex = f_epsilon);

	/**
	 *	@brief explodes vertices (ie. makes sure every polygon has unique vertices)
	 *	@return Returns true on success, false on failure.
	 */
	bool ExplodeVertices();

	/**
	 *	@brief breaks polygons down to triangles
	 *	@return Returns true on success, false on failure.
	 *	@note This removes degenerate polygons with less than 3 vertices.
	 */
	bool MakeTris();

	/**
	 *	@brief calculates face normals
	 *	@return Returns true on success, false on failure
	 *		(some polygons failed to calculate their normal (it is set to zero)).
	 *	@note This always atttempts to calculates normal for all the polygons.
	 */
	bool CalcFaceNormals(float f_epsilon_ex = f_epsilon);

	/**
	 *	@brief calculates vertex normals by averaging normals of faces which references the vertex
	 *
	 *	@note This requires face normals to be valid before the fuinction is called.
	 *	@note This does not make any changes to mesh topology.
	 */
	void CalcVertexNormals_Simple();

	/**
	 *	@brief calculates vertex normals, normals of faces holding angle
	 *		below a specified threshold are averaged only
	 *	@param[in] f_angle_thresh is angular threshold, in radians
	 *	@note This explodes vertices (it is advised to call OptimizeVertices() afterwards).
	 */
	bool CalcVertexNormals_Thresh(float f_angle_thresh = 30 * f_pi / 180);

	/**
	 *	@brief deletes all vertices, implies deleting the polygons as well
	 */
	void DeleteVertices();

	/**
	 *	@brief deletes polygons in a specified range
	 *
	 *	@param[in] n_begin is the first polygon to be deleted
	 *	@param[in] n_end is one past the last polygon to be deleted
	 *		(-1 means the end of the list)
	 */
	void DeletePolygons(size_t n_begin = 0, size_t n_end = size_t(-1));

	/**
	 *	@brief sorts the polygons by their material for easier rendering
	 */
	inline void SortByMaterial()
	{
		std::sort(m_poly_list.p_Begin_it(), m_poly_list.p_End_it(), b_HasLower_MaterialId);
	}

	/**
	 *	@brief makes buffers for drawing the mesh as triangles (must not contain polygons)
	 *
	 *	@param[out] r_index_buffer is filled with vertex indices per each triangle
	 *	@param[out] r_vertex_buffer is filled with vertex coordinates per each vertex
	 *		(3D position followed by 3D normal, 4D color and 4D texcoords)
	 *	@param[out] r_material_range_list is filled with pairs of material and
	 *		a pair of first index and number of triangles
	 *	@param[in] b_use_normals is normal drawing flag
	 *		(if cleared, normals are not present in vertex buffer)
	 *	@param[in] b_use_colors is color (RGBA, 4 elements) drawing flag
	 *		(if cleared, colors are not present in vertex buffer)
	 *	@param[in] n_use_texcoord_element_num is number of texcoord elements to draw
	 *		(if zero, texcoords are not present in vertex buffer)
	 *
	 *	@return Returns true on success, false on failure.
	 */
	bool Build_DrawBuffers(std::vector<uint32_t> &r_index_buffer,
		std::vector<float> &r_vertex_buffer, std::vector<std::pair<size_t,
		std::pair<size_t, size_t> > > &r_material_range_list,
		bool b_use_normals = true, bool b_use_colors = false,
		size_t n_use_texcoord_element_num = 4) const;

	/**
	 *	@brief makes buffers for drawing the mesh as triangles (must not contain polygons)
	 *
	 *	@param[out] r_index_buffer is filled with vertex indices per each triangle
	 *	@param[out] r_vertex_buffer is filled with vertex coordinates per each vertex
	 *		(3D position followed by 3D normal, 4D color and 4D texcoords)
	 *	@param[in] b_use_normals is normal drawing flag
	 *		(if cleared, normals are not present in vertex buffer)
	 *	@param[in] b_use_colors is color (RGBA, 4 elements) drawing flag
	 *		(if cleared, colors are not present in vertex buffer)
	 *	@param[in] n_use_texcoord_element_num is number of texcoord elements to draw
	 *		(if zero, texcoords are not present in vertex buffer)
	 *
	 *	@return Returns true on success, false on failure.
	 */
	bool Build_DrawBuffers(std::vector<uint32_t> &r_index_buffer,
		std::vector<float> &r_vertex_buffer, bool b_use_normals = true,
		bool b_use_colors = false, size_t n_use_texcoord_element_num = 4) const;

protected:
	static inline bool b_HasLower_MaterialId(const _TyPolygon &r_a, const _TyPolygon &r_b)
	{
		return r_a.n_MaterialId() < r_b.n_MaterialId();
	}
};

#include "VertexHash.h"

/*
 *								=== ~CPolyMesh ===
 */

#endif // !__POLYGON_MESH_INCLUDED
