/*
								+----------------------------------+
								|                                  |
								| ***  Simple spline surfaces  *** |
								|                                  |
								|   Copyright © -tHE SWINe- 2008   |
								|                                  |
								|           SplineSurf.h           |
								|                                  |
								+----------------------------------+
*/

#pragma once
#ifndef __SPLINE_SURFACE_INCLUDED
#define __SPLINE_SURFACE_INCLUDED

/**
 *	@file lml/SplineSurf.h
 *	@date 2008
 *	@author -tHE SWINe-
 *	@brief simple spline surfaces
 *
 *	@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 2014-04-02
 *
 *	Added MakeRuledSurf2() and set default material id to zero.
 *
 */

#include "PolyMesh.h"
#include "PlatPrim.h"
#include "Spline.h"

/**
 *	@def __SPLINE_SURF_GENERATE_TEXCOORDS
 *	@brief if defined, 2D texture coordinates are automatically generated
 *		in channel 0 when creating mesh, unless specified otherwise
 */
#define __SPLINE_SURF_GENERATE_TEXCOORDS

/**
 *	@brief basic spline surface triangulator
 *	@note This doesn't triangulate spline patches, use ffd instead or write a specialized function.
 */
class CSplineSurf {
public:
	/**
	 *	@brief simple skewed grid subdivision
	 *
	 *	This class is used to calculate numbers of vertices and triangles
	 *	at different grid lines and in total and to create grid winding.
	 *
	 *	@note Skewed grid in this context means that the number of rows is varying
	 *		linearly between the bottom row and the top row.
	 */
	class CSkewGridSubdivision {
	protected:
		size_t m_n_height, m_n_bottom_width, m_n_top_width;
		size_t m_n_vertex_num, m_n_triangle_num;

	public:
		/**
		 *	@brief default constructor; calculates total vertex and triangle numbers
		 *
		 *	@param[in] n_height is number of grid lines
		 *	@param[in] n_bottom_width is number of columns in the first line
		 *	@param[in] n_top_width is number of columns in the last line
		 */
		CSkewGridSubdivision(size_t n_height, size_t n_bottom_width, size_t n_top_width);

		/**
		 *	@brief gets number of grid columns at a grid line
		 *	@param[in] n_row is zero-based index of a grid line
		 *	@return Returns number of grid columns at the specified line
		 *		(equals to number of vertices - 1 in that line).
		 *	@note This is just linear interpolation.
		 */
		size_t n_Width(size_t n_row) const;

		/**
		 *	@brief gets the total number of vertices
		 *	@return Returns the total number of vertices required for grid mesh.
		 */
		inline size_t n_Vertex_Num() const
		{
			return m_n_vertex_num;
		}

		/**
		 *	@brief gets the total number of triangles
		 *	@return Returns the total number of triangles required for grid mesh.
		 */
		inline size_t n_Triangle_Num() const
		{
			return m_n_triangle_num;
		}

		/**
		 *	@brief gets grid height
		 *	@return Returns grid height (number of lines).
		 */
		inline size_t n_Height() const
		{
			return m_n_height;
		}

		/**
		 *	@brief gets grid width at the bottom
		 *	@return Returns grid width at the bottom (number of columns in the first line).
		 */
		inline size_t n_Bottom_Width() const
		{
			return m_n_bottom_width;
		}

		/**
		 *	@brief gets grid width at the top
		 *	@return Returns grid width at the top (number of columns in the last line).
		 */
		inline size_t n_Top_Width() const
		{
			return m_n_top_width;
		}

		/**
		 *	@brief creates grid winding in a given mesh
		 *
		 *	@param[in,out] r_mesh is is mesh where the grid should be generated (only the polygons are touched)
		 *	@param[in] n_face is zero-based index of the first face of wind
		 *	@param[in] n_left_vertex is zero-based index of the first vertex of the grid
		 *	@param[in] b_flip is flip flag (if set, the faces are wound with reversed vertex order)
		 *	@param[in] n_material is material id to set to the mesh faces
		 *
		 *	@return Returns true on success, false on failure.
		 *
		 *	@note This type of grid operates on triangles.
		 */
		bool CreateWinding(CPolyMesh &r_mesh, size_t n_face, size_t n_vertex,
			bool b_flip, CPolyMesh::TMaterialId n_material);
	};

public:
	/**
	 *	@brief creates a surface by extruding a spline in a given direction
	 *
	 *	@param[out] r_mesh is filled with the surface mesh; any original contents are deleted
	 *	@param[in] r_spline is spline to be extruded
	 *	@param[in] v_extrude is extrude direction
	 *	@param[in] n_tess_spline is number of points on spline
	 *	@param[in] n_tess_extrusion is number of points along extrusion direction (over-tesselation)
	 *	@param[in] n_material is material id to set to the mesh faces
	 *	@param[in] b_flip is flip flag (if set, the faces are wound with reversed vertex order)
	 *
	 *	@return Returns true on success, false on failure.
	 */
	static bool MakeExtrudeSurf(CPolyMesh &r_mesh, const CSplineSampler<Vector3f> &r_spline,
		Vector3f v_extrude, size_t n_tess_spline, size_t n_tess_extrusion,
		CPolyMesh::TMaterialId n_material = 0, bool b_flip = false);

	/**
	 *	@brief creates a surface by moving a line to connect the corresponding
	 *		points at the same position on two splines
	 *
	 *	@param[out] r_mesh is filled with the surface mesh; any original contents are deleted
	 *	@param[in] r_left_spline is the spline on the left
	 *	@param[in] r_right_spline is the spline on the right
	 *	@param[in] n_tess_left_spline is a number of points on the left spline
	 *	@param[in] n_tess_right_spline is a number of points on the right spline
	 *	@param[in] n_tess_cross_section is a number of points along the line between splines (over-tesselation)
	 *	@param[in] n_material is material id to set to the mesh faces
	 *	@param[in] b_flip is flip flag (if set, the faces are wound with reversed vertex order)
	 *
	 *	@return Returns true on success, false on failure.
	 *
	 *	@note This version has uniform tesselation over the cross-section and different tesselation for either spline.
	 */
	static bool MakeRuledSurf(CPolyMesh &r_mesh, const CSplineSampler<Vector3f> &r_left_spline,
		const CSplineSampler<Vector3f> &r_right_spline, size_t n_tess_left_spline,
		size_t n_tess_right_spline, size_t n_tess_cross_section,
		CPolyMesh::TMaterialId n_material = 0, bool b_flip = false);

	/**
	 *	@brief creates a surface by moving a line to connect the corresponding
	 *		points at the same position on two splines
	 *
	 *	@param[out] r_mesh is filled with the surface mesh; any original contents are deleted
	 *	@param[in] r_left_spline is the spline on the left
	 *	@param[in] r_right_spline is the spline on the right
	 *	@param[in] n_tess_splines is a number of points on the left and right splines
	 *	@param[in] n_tess_cross_section_begin is a number of points along the line between splines (over-tesselation)
	 *	@param[in] n_tess_cross_section_end is a number of points along the line between splines (over-tesselation)
	 *	@param[in] n_material is material id to set to the mesh faces
	 *	@param[in] b_flip is flip flag (if set, the faces are wound with reversed vertex order)
	 *
	 *	@return Returns true on success, false on failure.
	 *
	 *	@note This version has uniform tesselation over the splines and varying tesselation of the cross-section.
	 */
	static bool MakeRuledSurf2(CPolyMesh &r_mesh, const CSplineSampler<Vector3f> &r_left_spline,
		const CSplineSampler<Vector3f> &r_right_spline, size_t n_tess_splines,
		size_t n_tess_cross_section_begin, size_t n_tess_cross_section_end,
		CPolyMesh::TMaterialId n_material = 0, bool b_flip = false);

	/**
	 *	@brief creates a surface of spline revolution arround the y-axis
	 *
	 *	@param[out] r_mesh is filled with the surface mesh; any original contents are deleted
	 *	@param[in] r_spline is the spline to form the surface
	 *	@param[in] n_tess_spline is a number of points on spline
	 *	@param[in] n_tess_radius is a number of revolution steps
	 *	@param[in] b_bottom_cap is bottom cap flag (cap is a triangle fan arround the y-axis)
	 *	@param[in] b_top_cap is top cap flag (cap is a triangle fan arround the y-axis)
	 *	@param[in] n_material is material id to set to the mesh faces
	 *	@param[in] b_weld_bottom_vertex is bottom vertex identity flag
	 *		(set if the spline touches the axis; can't be used with b_bottom_cap)
	 *	@param[in] b_weld_top_vertex is top vertex identity flag
	 *		(set if the spline touches the axis; can't be used with b_top_cap)
	 *	@param[in] b_flip is flip flag (if set, the faces are wound with reversed vertex order)
	 *
	 *	@return Returns true on success, false on failure.
	 *
	 *	@note Here 'bottom' refers to the starting point of the spline (t = 0)
	 *		and 'top' to the endpoint (t = 1).
	 *
	 *	@todo test and support true 3D revolution surfaces arround
	 *		arbitrary axis (this gets 2d spline as input)
	 */
	static bool MakeRevolutionSurf(CPolyMesh &r_mesh, const CSplineSampler<Vector2f> &r_spline,
		size_t n_tess_spline, size_t n_tess_radius, bool b_bottom_cap, bool b_top_cap,
		CPolyMesh::TMaterialId n_material = 0, bool b_weld_bottom_vertex = false,
		bool b_weld_top_vertex = false, bool b_flip = false);

	/**
	 *	@brief creates a surface by sweeping a spline connecting the corresponding points
	 *		on the left and the right spline (points at the same position on the spline)
	 *
	 *	@param[out] r_mesh is filled with the surface mesh; any original contents are deleted
	 *	@param[in] r_left_rail is the spline on the left
	 *	@param[in] r_right_rail is the spline on the right
	 *	@param[in] r_connect_spline is the spline connecting the rails
	 *		(defines the profile of the surface)
	 *	@param[in] f_connect_spline_position is position along the rail splines where
	 *		the connection spline is attached to them (defines a coordinate frame)
	 *	@param[in] n_tess_rail is number of points on left and right splines (rails),
	 *	@param[in] n_tess_connect_spline_begin is number of points on the connecting
	 *		spline at the beginning of the rails
	 *	@param[in] n_tess_connect_spline_end is number of points on the connecting
	 *		spline at the end of the rails
	 *	@param[in] n_material is material id to set to the mesh faces
	 *	@param[in] b_flip is flip flag (if set, the faces are wound with reversed vertex order)
	 *	@param[in] b_scale is scale flag (if set, scales the connection spline according
	 *		to the distance it connects)
	 *	@param[in] b_unskew is matrix orthogonalization flag (if set, forces orthogonal
	 *		matrices for transforming the connecting spline position, may lead to unexpected
	 *		surface shapes)
	 *
	 *	@return Returns true on success, false on failure.
	 *
	 *	@note This doesn't calculate normals (lot of blending involved, it's cheaper
	 *		to use CPolyMesh::CalcVertexNormals_Simple()).
	 */
	static bool MakeRailSweep(CPolyMesh &r_mesh,
		const CSplineSampler<Vector3f> &r_left_rail,
		const CSplineSampler<Vector3f> &r_right_rail,
		const CSplineSampler<Vector3f> &r_connect_spline,
		float f_connect_spline_position, size_t n_tess_rail, size_t n_tess_connect_spline_begin,
		size_t n_tess_connect_spline_end, CPolyMesh::TMaterialId n_material = 0,
		bool b_flip = false, bool b_scale = true, bool b_unskew = false);

	/**
	 *	@brief creates a surface by sweeping a spline connecting the corresponding points
	 *		on the left and the right spline (points at the same position on the spline)
	 *
	 *	@param[out] r_mesh is filled with the surface mesh; any original contents are deleted
	 *	@param[in] r_left_rail is the spline on the left
	 *	@param[in] r_right_rail is the spline on the right
	 *	@param[in] r_conn_spline_0 is the first morph target of the spline connecting the rails
	 *		(defines the profile of the surface)
	 *	@param[in] r_conn_spline_1 is the second morph target of the spline connecting the rails
	 *		(defines the profile of the surface)
	 *	@param[in] f_connect_spline_position is position along the rail splines where
	 *		the connection spline is attached to them (defines a coordinate frame)
	 *	@param[in] n_tess_rail is number of points on left and right splines (rails),
	 *	@param[in] n_tess_connect_spline_begin is number of points on the connecting
	 *		spline at the beginning of the rails
	 *	@param[in] n_tess_connect_spline_end is number of points on the connecting
	 *		spline at the end of the rails
	 *	@param[in] n_material is material id to set to the mesh faces
	 *	@param[in] b_flip is flip flag (if set, the faces are wound with reversed vertex order)
	 *	@param[in] b_scale is scale flag (if set, scales the connection spline according
	 *		to the distance it connects)
	 *	@param[in] b_unskew is matrix orthogonalization flag (if set, forces orthogonal
	 *		matrices for transforming the connecting spline position, may lead to unexpected
	 *		surface shapes)
	 *
	 *	@return Returns true on success, false on failure.
	 *
	 *	@note This doesn't calculate normals (lot of blending involved, it's cheaper
	 *		to use CPolyMesh::CalcVertexNormals_Simple()).
	 */
	static bool MakeRailSweep2(CPolyMesh &r_mesh,
		const CSplineSampler<Vector3f> &r_left_rail,
		const CSplineSampler<Vector3f> &r_right_rail,
		const CSplineSampler<Vector3f> &r_conn_spline_0,
		const CSplineSampler<Vector3f> &r_conn_spline_1, size_t n_tess_rail,
		size_t n_tess_connect_spline_begin, size_t n_tess_connect_spline_end,
		CPolyMesh::TMaterialId n_material = 0, bool b_flip = false,
		bool b_scale = true, bool b_unskew = false);

	/*
	CapSurface (triangulate inside of closed curve)
	*/

	/**
	 *	@brief calculates two basis matrices for point on two splines, on a specified position
	 *
	 *	The bases are aligned with spline tangents and with the line connecting the points on the splines.
	 *
	 *	@param[in] r_left_spline is the spline on the left
	 *	@param[in] r_right_spline is the spline on the right
	 *	@param[in] f_t is a position along the two splines where the bases should be calculated
	 *	@param[in] b_dist_scaling is distance scaling flag
	 *		(if set, scales the matrices with distance between the points)
	 *	@param[in] b_unskew is matrix orthogonalization flag
	 *		(if set, orthogonalizes the coordinate frames)
	 *	@param[out] r_t_left_basis is the left basis matrix
	 *	@param[out] r_t_right_basis is the right basis matrix
	 *
	 *	@note The bases cannot be constructed if the points are identical and
	 *		spline tangents on them are collinear (but that should be rather rare).
	 */
	static void RailBases(const CSplineSampler<Vector3f> &r_left_spline,
		const CSplineSampler<Vector3f> &r_right_spline, float f_t, bool b_dist_scaling,
		bool b_unskew, Matrix4f &r_t_left_basis, Matrix4f &r_t_right_basis);
};

#endif // !__SPLINE_SURFACE_INCLUDED
