/*
								+----------------------------------+
								|                                  |
								|  ***  Platonic primitives  ***   |
								|                                  |
								|   Copyright © -tHE SWINe- 2007   |
								|                                  |
								|           PlatPrim.cpp           |
								|                                  |
								+----------------------------------+
*/

/**
 *	@file lml/PlatPrim.cpp
 *	@date 2007
 *	@author -tHE SWINe-
 *	@brief platonic primitives
 *
 *	@date 2008-06-24
 *
 *	renamed CPolygon2::r_t_Vertex() and CVertexHash::r_t_Vertex() to
 *	CPolygon2::r_Vertex() and CVertexHash::r_Vertex() respectively
 *
 *	@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_
 *
 */

#if defined(_MSC_VER) && !defined(__MWERKS__)
#pragma warning(disable:4786)
#endif
// disable warning C4786 "identifier was truncated to '255' characters in the debug information"

#include "../NewFix.h"

#include "../CallStack.h"
#include <algorithm>
#include <math.h>
#include "../Vector.h"
#include "PolyMesh.h"
#include "PlatPrim.h"

/*
 *								=== CPlatonicPrimitives ===
 */

bool CPlatonicPrimitives::MakeSphere(CPolyMesh &r_mesh, size_t n_tess_axis,
	size_t n_tess_radius, CPolyMesh::TMaterialId n_material)
{
	_ASSERTE(n_tess_radius < SIZE_MAX); // if it equals SIZE_MAX, will loop indefinitely (and will not fit in memory)
	if(n_tess_axis < 2)
		n_tess_axis = 2;
	if(n_tess_radius < 3)
		n_tess_radius = 3;
	// min tesselation check

	size_t n_coat_vertex_num = (n_tess_radius + 1) * (n_tess_axis - 1);
	size_t n_coat_face_num = (n_tess_axis - 2) * n_tess_radius;
	size_t n_cap_face_num = n_tess_radius;
	if(!r_mesh.Alloc(n_coat_vertex_num + 2, n_coat_face_num + n_cap_face_num * 2))
		return false;
	// alloc vertices and faces

	float f_r_step = float(2 * f_pi / n_tess_radius);
	float f_h_step = float(f_pi / n_tess_axis);
	float f_angle = f_h_step;
	size_t n_out_vertex = 0;
	for(size_t j = 1; j < n_tess_axis; ++ j, f_angle += f_h_step) {
		float f_sin_angle = (float)sin(f_angle);
		float f_cos_angle = (float)cos(f_angle);
		float f_angle_r = 0;
		for(size_t i = 0; i <= n_tess_radius; ++ i, f_angle_r += f_r_step, ++ n_out_vertex) {
			CPolyMesh::_TyVertex t_tmp((float)sin(f_angle_r) * f_sin_angle, f_cos_angle,
				(float)cos(f_angle_r) * f_sin_angle);
			t_tmp.v_normal = t_tmp.v_position;
#ifdef __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
			t_tmp.p_texture_coord[0] = Vector4f(float(f_angle_r / (2 * f_pi) * 3), float(f_angle / f_pi), 0, 1);
#endif // __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
			r_mesh.r_Vertex(n_out_vertex) = t_tmp;
			// generate vertex
		}
	}
	// generate grid of (n_tess_radius + 1) * (n_tess_axis - 1) vertices

	r_mesh.r_Vertex(n_out_vertex) = CPolyMesh::_TyVertex(0, 1, 0);
	r_mesh.r_Vertex(n_out_vertex).v_normal = Vector3f(0, 1, 0);
#ifdef __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
	r_mesh.r_Vertex(n_out_vertex).p_texture_coord[0] = Vector4f(0, 0, 0, 1);
#endif // __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
	r_mesh.r_Vertex(n_out_vertex + 1) = CPolyMesh::_TyVertex(0, -1, 0);
	r_mesh.r_Vertex(n_out_vertex + 1).v_normal = Vector3f(0, -1, 0);
#ifdef __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
	r_mesh.r_Vertex(n_out_vertex + 1).p_texture_coord[0] = Vector4f(0, 1, 0, 1);
#endif // __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
	// generate top-most and bottom-most vertices

	_ASSERTE(n_out_vertex + 2 == r_mesh.n_Vertex_Num());

	if(!WindGrid(r_mesh, 0, 0, n_tess_radius, n_tess_axis - 2, false, n_material))
		return false;
	// wind grid of quads (sphere coat)

	if(!WindFan(r_mesh, n_coat_face_num, n_out_vertex,
	   0, n_tess_radius + 1, false, true, n_material) ||
	   !WindFan(r_mesh, n_coat_face_num + n_cap_face_num,
	   n_out_vertex + 1, n_coat_vertex_num - n_tess_radius - 1,
	   n_tess_radius + 1, false, false, n_material))
		return false;
	// wind triangle fans (sphere caps)

	return true;
}

bool CPlatonicPrimitives::MakeCube(CPolyMesh &r_mesh, size_t n_tess_x,
	size_t n_tess_y, size_t n_tess_z, CPolyMesh::TMaterialId n_material)
{
	_ASSERTE(n_tess_x < SIZE_MAX); // if it equals SIZE_MAX, will loop indefinitely (and will not fit in memory)
	_ASSERTE(n_tess_y < SIZE_MAX); // if it equals SIZE_MAX, will loop indefinitely (and will not fit in memory)
	_ASSERTE(n_tess_z < SIZE_MAX); // if it equals SIZE_MAX, will loop indefinitely (and will not fit in memory)
	size_t p_tess[3] = {(n_tess_x >= 1)? n_tess_x : 1,
		(n_tess_y >= 1)? n_tess_y : 1, (n_tess_z >= 1)? n_tess_z : 1};
	// min tesselation check

	size_t n_vertex_num = 0;
	size_t n_face_num = 0;
	for(size_t n_coord = 0; n_coord < 3; ++ n_coord) {
		size_t n_coord1 = (n_coord + 1) % 3;
		size_t n_tess_s = p_tess[n_coord];
		size_t n_tess_t = p_tess[n_coord1];
		n_vertex_num += (n_tess_s + 1) * (n_tess_t + 1) * 2;
		n_face_num += n_tess_s * n_tess_t * 2;
	}
	// determine number of faces (loop should optimize away)

	if(!r_mesh.Alloc(n_vertex_num, n_face_num))
		return false;
	// alloc vertices and faces

	size_t n_out_vertex = 0;
	size_t n_in_vertex = 0;
	size_t n_out_face = 0;
	for(size_t n_coord = 0; n_coord < 3; ++ n_coord) {
		size_t n_coord1 = (n_coord + 1) % 3;
		size_t n_coord2 = (n_coord + 2) % 3;
		// determine index of other coordinates

		size_t n_tess_s = p_tess[n_coord];
		size_t n_tess_t = p_tess[n_coord1];
		float f_step_s = 2.0f / n_tess_s;
		float f_step_t = 2.0f / n_tess_t;
		// determine tesselations and stepping

		for(float n_side = -1; n_side < 2; n_side += 2) {
			float f_t = -1;
			for(size_t y = 0; y <= n_tess_t; ++ y, f_t += f_step_t) {
				float f_s = -1;
				for(size_t x = 0; x <= n_tess_s; ++ x, f_s += f_step_s, ++ n_out_vertex) {
					CPolyMesh::_TyVertex t_tmp(0, 0, 0);
					t_tmp.v_position[n_coord] = f_s;
					t_tmp.v_position[n_coord1] = f_t;
					t_tmp.v_position[n_coord2] = n_side;
					t_tmp.v_normal = Vector3f(0, 0, 0);
					t_tmp.v_normal[n_coord2] = n_side;
#ifdef __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
					if(n_coord2 == 2) {
						t_tmp.p_texture_coord[0] = Vector4f(
							n_side * (t_tmp.v_position[n_coord] + 1) * .5f,
							-(t_tmp.v_position[n_coord1] + 1) * .5f, 0, 1);
					} else {
						t_tmp.p_texture_coord[0] = Vector4f(
							-n_side * (t_tmp.v_position[n_coord1] + 1) * .5f,
							-(t_tmp.v_position[n_coord] + 1) * .5f, 0, 1);
					}
					// produces results, identical to box mapping implemented in TexCoords.h
#endif // __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
					r_mesh.r_Vertex(n_out_vertex) = t_tmp;
					// generate vertex
				}
			}
		}
		// generate vertices on n_coord - n_coord1 plane (both cube sides)

		for(size_t n_side = 0; n_side < 2; ++ n_side) {
			if(!WindGrid(r_mesh, n_out_face, n_in_vertex,
			   n_tess_s, n_tess_t, (n_side)? true : false, n_material))
				return false;
			n_out_face += n_tess_s * n_tess_t;
			n_in_vertex += (n_tess_s + 1) * (n_tess_t + 1);
		}
		// wind polygons
	}
	_ASSERTE(n_out_vertex == r_mesh.n_Vertex_Num());
	_ASSERTE(n_out_face == r_mesh.n_Polygon_Num());

	return true;
}

bool CPlatonicPrimitives::MakePlane(CPolyMesh &r_mesh,
	size_t n_tess_x, size_t n_tess_y, CPolyMesh::TMaterialId n_material)
{
	_ASSERTE(n_tess_x < SIZE_MAX); // if it equals SIZE_MAX, will loop indefinitely (and will not fit in memory)
	_ASSERTE(n_tess_y < SIZE_MAX); // if it equals SIZE_MAX, will loop indefinitely (and will not fit in memory)
	if(n_tess_x < 1)
		n_tess_x = 1;
	if(n_tess_y < 1)
		n_tess_y = 1;
	// min tesselation check

	if(!r_mesh.Alloc((n_tess_x + 1) * (n_tess_y + 1), n_tess_x * n_tess_y))
		return false;
	// alloc vertices and faces

	size_t n_out_vertex = 0;
	float f_step_x = 2.0f / n_tess_x;
	float f_step_y = 2.0f / n_tess_y;
	float f_t = -1;
	for(size_t y = 0; y <= n_tess_y; ++ y, f_t += f_step_y) {
		float f_s = -1;
		for(size_t x = 0; x <= n_tess_x; ++ x, f_s += f_step_x, ++ n_out_vertex) {
			CPolyMesh::_TyVertex t_tmp(f_s, 0, f_t);
			t_tmp.v_normal = Vector3f(0, 1, 0);
#ifdef __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
			t_tmp.p_texture_coord[0] = Vector4f((f_s + 1) * .5f, (f_t + 1) * .5f, 0, 1);
#endif // __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
			r_mesh.r_Vertex(n_out_vertex) = t_tmp;
			// generate vertex
		}
	}
	_ASSERTE(n_out_vertex == r_mesh.n_Vertex_Num());
	// generate vertices

	if(!WindGrid(r_mesh, 0, 0, n_tess_x, n_tess_y, false, n_material))
		return false;
	// wind polygons

	return true;
}

bool CPlatonicPrimitives::MakeCylinder(CPolyMesh &r_mesh,
	size_t n_tess_axis, size_t n_tess_radius, size_t n_cap_tess,
	CPolyMesh::TMaterialId n_material_coat,
	CPolyMesh::TMaterialId n_material_caps)
{
	_ASSERTE(n_tess_axis < SIZE_MAX); // if it equals SIZE_MAX, will loop indefinitely (and will not fit in memory)
	_ASSERTE(n_tess_radius < SIZE_MAX); // if it equals SIZE_MAX, will loop indefinitely (and will not fit in memory)
	if(n_tess_axis < 1)
		n_tess_axis = 1;
	if(n_tess_radius < 3)
		n_tess_radius = 3;
	if(n_cap_tess < 0)
		n_cap_tess = 0;
	// min tesselation check

	size_t n_coat_face_num = n_tess_axis * n_tess_radius;
	size_t n_coat_vertex_num = (n_tess_axis + 1) * (n_tess_radius + 1);
	size_t n_cap_face_num = n_tess_radius * n_cap_tess;
	size_t n_cap_vertex_num = (n_cap_tess)? n_tess_radius * n_cap_tess + 1 : 0;
	if(!r_mesh.Alloc(n_coat_vertex_num + 2 * n_cap_vertex_num,
	   n_coat_face_num + 2 * n_cap_face_num))
		return false;
	// alloc vertices and faces

	size_t n_out_vertex = 0;
	float f_r_step = float(2 * f_pi / n_tess_radius);
	float f_y_step = 2.0f / n_tess_axis;
	float f_y = -1;
	for(size_t j = 0; j <= n_tess_axis; ++ j, f_y += f_y_step) {
		float f_angle = 0;
		for(size_t i = 0; i <= n_tess_radius; ++ i, f_angle += f_r_step, ++ n_out_vertex) {
			CPolyMesh::_TyVertex t_tmp((float)sin(f_angle), f_y, (float)cos(f_angle));
			t_tmp.v_normal = t_tmp.v_position;
			t_tmp.v_normal.y = 0;
#ifdef __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
			t_tmp.p_texture_coord[0] = Vector4f(float(f_angle / (2 * f_pi) * 3), (1 - f_y) * .5f, 0, 1);
#endif // __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
			r_mesh.r_Vertex(n_out_vertex) = t_tmp;
			// generate vertex
		}
	}
	// generate grid of (n_tess_radius + 1) * (n_tess_axis + 1) coat vertices

	if(!WindGrid(r_mesh, 0, 0, n_tess_radius, n_tess_axis, true, n_material_coat))
		return false;
	// wind coat polygons

	if(n_cap_tess) {
		_ASSERTE(n_out_vertex == n_coat_vertex_num);
		//size_t n_out_vertex = n_coat_vertex_num; // do not create a new variable, will break the assertion below
		size_t n_in_vertex = n_coat_vertex_num;
		size_t n_out_face = n_coat_face_num;
		for(int y = -1; y < 2; y += 2) {
			float f_radius_step = -1.0f / n_cap_tess;
			float f_radius = 1.0f;
			for(size_t i = 0; i < n_cap_tess; ++ i, f_radius += f_radius_step) {
				float f_angle = 0;
				for(size_t i = 0; i < n_tess_radius; ++ i, f_angle += f_r_step, ++ n_out_vertex) {
					CPolyMesh::_TyVertex t_tmp((float)sin(f_angle), float(y), (float)cos(f_angle));
					t_tmp.v_normal = Vector3f(0, float(y), 0);
					t_tmp.v_position.x *= f_radius;
					t_tmp.v_position.z *= f_radius;
#ifdef __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
					t_tmp.p_texture_coord[0] = Vector4f((t_tmp.v_position.x + 1) * .5f,
						(t_tmp.v_position.z + 1) * y * .5f, 0, 1);
#endif // __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
					r_mesh.r_Vertex(n_out_vertex) = t_tmp;
					// generate vertex
				}
			}
			// extra cap vertices (in case cap is over-tesselated)

			CPolyMesh::_TyVertex t_tmp(0, float(y), 0);
			t_tmp.v_normal = t_tmp.v_position;
#ifdef __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
			t_tmp.p_texture_coord[0] = Vector4f(.5f, .5f * y, 0, 1);
#endif // __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
			r_mesh.r_Vertex(n_out_vertex) = t_tmp;
			// cap center vertex

			for(size_t i = 1; i < n_cap_tess; ++ i) {
				if(!WindSection(r_mesh, n_out_face, n_in_vertex + n_tess_radius * (i - 1),
				   n_in_vertex + n_tess_radius * i, n_tess_radius, true, y > 0, n_material_caps))
					return false;
				n_out_face += n_tess_radius;
			}
			// wind extra cap sections (in case cap is over-tesselated)

			if(!WindFan(r_mesh, n_out_face, n_out_vertex, n_in_vertex +
				n_tess_radius * (n_cap_tess - 1), n_tess_radius, true, y > 0, n_material_caps))
				return false;
			n_out_face += n_tess_radius;
			n_in_vertex += n_cap_vertex_num;
			// wind fan in center of cap

			++ n_out_vertex;
			// skip past center vertex
		}
	}
	// make caps

	_ASSERTE(n_out_vertex == r_mesh.n_Vertex_Num());

	return true;
}

bool CPlatonicPrimitives::MakeCone(CPolyMesh &r_mesh, size_t n_tess_axis,
	size_t n_tess_radius, size_t n_cap_tess, CPolyMesh::TMaterialId n_material_coat,
	CPolyMesh::TMaterialId n_material_cap)
{
	_ASSERTE(n_tess_radius < SIZE_MAX); // if it equals SIZE_MAX, will loop indefinitely (and will not fit in memory)
	if(n_tess_axis < 1)
		n_tess_axis = 1;
	if(n_tess_radius < 3)
		n_tess_radius = 3;
	if(n_cap_tess < 0)
		n_cap_tess = 0;
	// min tesselation check

	size_t n_coat_face_num = n_tess_axis * n_tess_radius;
	size_t n_coat_vertex_num = n_tess_axis * (n_tess_radius + 1) + 1;
	size_t n_cap_face_num = n_tess_radius * n_cap_tess;
	size_t n_cap_vertex_num = (n_cap_tess)? n_tess_radius * n_cap_tess + 1 : 0;
	if(!r_mesh.Alloc(n_coat_vertex_num + n_cap_vertex_num,
	   n_coat_face_num + n_cap_face_num))
		return false;
	// alloc vertices and faces

	size_t n_out_vertex = 0;
	float f_r_step = float(2 * f_pi / n_tess_radius);
	float f_y_step = 2.0f / n_tess_axis;
	float f_y = -1;
	float f_radius = 1;
	float f_radius_step = -1.0f / n_tess_axis;
	for(size_t j = 0; j < n_tess_axis; ++ j, f_y += f_y_step, f_radius += f_radius_step) {
		float f_angle = 0;
		for(size_t i = 0; i <= n_tess_radius; ++ i, f_angle += f_r_step, ++ n_out_vertex) {
			CPolyMesh::_TyVertex t_tmp((float)sin(f_angle), f_y, (float)cos(f_angle));
			t_tmp.v_normal = t_tmp.v_position;
			t_tmp.v_normal.y = 0;
			t_tmp.v_position.x *= f_radius;
			t_tmp.v_position.z *= f_radius;
#ifdef __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
			t_tmp.p_texture_coord[0] = Vector4f(float(f_angle / (2 * f_pi) * 3), (1 - f_y) * .5f, 0, 1);
#endif // __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
			r_mesh.r_Vertex(n_out_vertex) = t_tmp;
			// generate vertex
		}
	}
	// generate grid of (n_tess_radius + 1) * n_tess_axis coat vertices

	CPolyMesh::_TyVertex t_tmp(0, 1, 0);
	t_tmp.v_normal = Vector3f(0, 1, 0);
#ifdef __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
	t_tmp.p_texture_coord[0] = Vector4f(0, 0, 0, 1);
#endif // __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
	r_mesh.r_Vertex(n_out_vertex) = t_tmp;
	// generate top vertex

	if(!WindGrid(r_mesh, 0, 0, n_tess_radius, n_tess_axis - 1, true, n_material_coat) ||
	   !WindFan(r_mesh, n_tess_radius * (n_tess_axis - 1),
	   n_out_vertex, (n_tess_radius + 1) * (n_tess_axis - 1),
	   n_tess_radius + 1, false, true, n_material_coat))
		return false;
	// generate coat winding

	if(n_cap_tess) {
		size_t n_out_vertex = n_coat_vertex_num;
		size_t n_in_vertex = n_coat_vertex_num;
		size_t n_out_face = n_coat_face_num;
		float f_radius_step = -1.0f / n_cap_tess;
		float f_radius = 1.0f;
		for(size_t i = 0; i < n_cap_tess; ++ i, f_radius += f_radius_step) {
			float f_angle = 0;
			for(size_t i = 0; i < n_tess_radius; ++ i, f_angle += f_r_step, ++ n_out_vertex) {
				CPolyMesh::_TyVertex t_tmp((float)sin(f_angle), -1, (float)cos(f_angle));
				t_tmp.v_normal = Vector3f(0, -1, 0);
				t_tmp.v_position.x *= f_radius;
				t_tmp.v_position.z *= f_radius;
#ifdef __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
				t_tmp.p_texture_coord[0] = Vector4f((t_tmp.v_position.x + 1) * .5f,
					(1 - t_tmp.v_position.z) * .5f, 0, 1);
#endif // __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
				r_mesh.r_Vertex(n_out_vertex) = t_tmp;
				// generate vertex
			}
		}
		// extra cap vertices (in case cap is over-tesselated)

		CPolyMesh::_TyVertex t_tmp(0, -1, 0);
		t_tmp.v_normal = Vector3f(0, -1, 0);
#ifdef __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
		t_tmp.p_texture_coord[0] = Vector4f(.5f, .5f, 0, 1);
#endif // __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
		r_mesh.r_Vertex(n_out_vertex) = t_tmp;
		// cap center vertex

		for(size_t i = 1; i < n_cap_tess; ++ i) {
			if(!WindSection(r_mesh, n_out_face, n_in_vertex + n_tess_radius * (i - 1),
			   n_in_vertex + n_tess_radius * i, n_tess_radius, true, false, n_material_cap))
				return false;
			n_out_face += n_tess_radius;
		}
		// wind extra cap sections (in case cap is over-tesselated)

		if(!WindFan(r_mesh, n_out_face, n_out_vertex, n_in_vertex +
		   n_tess_radius * (n_cap_tess - 1), n_tess_radius, true, false, n_material_cap))
			return false;
		// wind fan in center of cap
	}
	// make cap

	_ASSERTE(n_out_vertex == r_mesh.n_Vertex_Num());

	return true;
}

bool CPlatonicPrimitives::MakeTube(CPolyMesh &r_mesh, size_t n_tess_axis,
	size_t n_tess_radius, float f_inner_radius,
	CPolyMesh::TMaterialId n_material_inner_coat,
	CPolyMesh::TMaterialId n_material_outer_coat,
	CPolyMesh::TMaterialId n_material_caps)
{
	_ASSERTE(n_tess_axis < SIZE_MAX); // if it equals SIZE_MAX, will loop indefinitely (and will not fit in memory)
	_ASSERTE(n_tess_radius < SIZE_MAX); // if it equals SIZE_MAX, will loop indefinitely (and will not fit in memory)
	if(n_tess_axis < 1)
		n_tess_axis = 1;
	if(n_tess_radius < 3)
		n_tess_radius = 3;
	// min tesselation check

	size_t n_coat_face_num = n_tess_axis * n_tess_radius;
	size_t n_coat_vertex_num = (n_tess_axis + 1) * (n_tess_radius + 1);
	size_t n_cap_face_num = n_tess_radius;
	size_t n_cap_vertex_num = 2 * n_tess_radius;
	if(!r_mesh.Alloc(2 * n_coat_vertex_num + 2 * n_cap_vertex_num,
	   2 * n_coat_face_num + 2 * n_cap_face_num))
		return false;
	// alloc vertices and faces

	size_t n_out_vertex = 0;
	float f_r_step = float(2 * f_pi / n_tess_radius);
	float f_y_step = 2.0f / n_tess_axis;
	float f_r = 1;
	for(int n_pass = -1; n_pass < 2; n_pass += 2) {
		float f_y = -1;
		for(size_t j = 0; j <= n_tess_axis; ++ j, f_y += f_y_step) {
			float f_angle = 0;
			for(size_t i = 0; i <= n_tess_radius; ++ i, f_angle += f_r_step, ++ n_out_vertex) {
				CPolyMesh::_TyVertex t_tmp((float)sin(f_angle), f_y, (float)cos(f_angle));
				t_tmp.v_normal = t_tmp.v_position * float(-n_pass);
				t_tmp.v_normal.y = 0;
				t_tmp.v_position.x *= f_r;
				t_tmp.v_position.z *= f_r;
#ifdef __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
				t_tmp.p_texture_coord[0] =
					Vector4f(float(f_angle / (2 * f_pi) * 3) * -n_pass, (1 - f_y) * .5f, 0, 1);
#endif // __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
				r_mesh.r_Vertex(n_out_vertex) = t_tmp;
				// generate vertex
			}
		}
		f_r = f_inner_radius;
	}
	// generate two grids of (n_tess_radius + 1) * (n_tess_axis + 1) coat vertices

	if(!WindGrid(r_mesh, 0, 0, n_tess_radius, n_tess_axis, true, n_material_inner_coat) ||
	   !WindGrid(r_mesh, n_coat_face_num, n_coat_vertex_num, n_tess_radius,
	   n_tess_axis, false, n_material_outer_coat))
		return false;
	// wind coat polygons

	{float f_r = 1;
	for(int n_pass = -1; n_pass < 2; n_pass += 2) {
		for(int n_y = -1; n_y < 2; n_y += 2) {
			float f_angle = 0;
			for(size_t i = 0; i < n_tess_radius; ++ i, f_angle += f_r_step, ++ n_out_vertex) {
				CPolyMesh::_TyVertex t_tmp((float)sin(f_angle), float(n_y), (float)cos(f_angle));
				t_tmp.v_normal = Vector3f(0, float(n_y), 0);
				t_tmp.v_position.x *= f_r;
				t_tmp.v_position.z *= f_r;
#ifdef __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
				t_tmp.p_texture_coord[0] = Vector4f((t_tmp.v_position.x + 1) * .5f,
					(t_tmp.v_position.z + 1) * .5f * n_y, 0, 1);
#endif // __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
				r_mesh.r_Vertex(n_out_vertex) = t_tmp;
				// generate vertex
			}
		}
		f_r = f_inner_radius;
	}}
	// generate two grids of (n_tess_radius + 1) * 4 coat vertices

	_ASSERTE(n_out_vertex == r_mesh.n_Vertex_Num());

	if(!WindSection(r_mesh, 2 * n_coat_face_num, 2 * n_coat_vertex_num,
	   2 * n_coat_vertex_num + 2 * n_tess_radius, n_tess_radius, true, false, n_material_caps) ||
	   !WindSection(r_mesh, 2 * n_coat_face_num + n_cap_face_num,
	   2 * n_coat_vertex_num + n_tess_radius,
	   2 * n_coat_vertex_num + 3 * n_tess_radius, n_tess_radius, true, true, n_material_caps))
		return false;
	// wind cap polygons

	return true;
}

bool CPlatonicPrimitives::MakeTorus(CPolyMesh &r_mesh, size_t n_tess_major,
	size_t n_tess_minor, float f_minor_radius, CPolyMesh::TMaterialId n_material)
{
	_ASSERTE(n_tess_minor < SIZE_MAX); // if it equals SIZE_MAX, will loop indefinitely (and will not fit in memory)
	_ASSERTE(n_tess_major < SIZE_MAX); // if it equals SIZE_MAX, will loop indefinitely (and will not fit in memory)
	if(n_tess_major < 3)
		n_tess_major = 3;
	if(n_tess_minor < 3)
		n_tess_minor = 3;
	// min tesselation check

	size_t n_coat_vertex_num = (n_tess_minor + 1) * (n_tess_major + 1);
	size_t n_coat_face_num = n_tess_minor * n_tess_major;
	if(!r_mesh.Alloc(n_coat_vertex_num, n_coat_face_num))
		return false;
	// alloc vertices and faces

	size_t n_out_vertex = 0;
	float f_r_step = float(2 * f_pi / n_tess_major);
	float f_sub_r_step = float(2 * f_pi / n_tess_minor);
	float f_angle_r = 0;
	for(size_t i = 0; i <= n_tess_major; ++ i, f_angle_r += f_r_step) {
		Vector3f v_local_org(sinf(f_angle_r), 0, cosf(f_angle_r));
		Vector3f v_local_x = v_local_org * f_minor_radius;
		Vector3f v_local_y(0, f_minor_radius, 0);
		float f_angle = 0;
		for(size_t j = 0; j <= n_tess_minor; ++ j, f_angle += f_sub_r_step, ++ n_out_vertex) {
			CPolyMesh::_TyVertex t_tmp((v_local_x * sinf(f_angle) +
				v_local_y * cosf(f_angle)) + v_local_org);
			t_tmp.v_normal = t_tmp.v_position - v_local_org;
			t_tmp.v_normal.Normalize();
#ifdef __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
			t_tmp.p_texture_coord[0] =
				Vector4f(float(-f_angle / (2 * f_pi)), float(f_angle_r / (2 * f_pi)), 0, 1);
#endif // __PLATONIC_PRIMITIVES_GENERATE_TEXCOORDS
			r_mesh.r_Vertex(n_out_vertex) = t_tmp;
		}
	}
	_ASSERTE(n_out_vertex == r_mesh.n_Vertex_Num());
	// calc vertices

	if(!WindGrid(r_mesh, 0, 0, n_tess_minor, n_tess_major, true, n_material))
		return false;
	// wind grid of quads (torus coat)

	return true;
}

bool CPlatonicPrimitives::WindGrid(CPolyMesh &r_mesh, size_t n_face, size_t n_vertex,
	size_t n_size_x, size_t n_size_y, bool b_flip, CPolyMesh::TMaterialId n_material)
{
	try {
		_ASSERTE(n_vertex < r_mesh.n_Vertex_Num()); // make sure it is a valid vertex index
		_ASSERTE(n_face < r_mesh.n_Polygon_Num()); // make sure it is a valid face index
		_ASSERTE(n_vertex + (n_size_x + 1) * (n_size_y + 1) <= r_mesh.n_Vertex_Num()); // make sure there is enough vertices
		_ASSERTE(n_face + n_size_x * n_size_y <= r_mesh.n_Polygon_Num()); // make sure there is enough faces

		for(size_t y = 0; y < n_size_y; ++ y) {
			for(size_t x = 0; x < n_size_x; ++ x, ++ n_face) {
				CPolyMesh::_TyRefVertex p_vertex_ptr[4] = {
					r_mesh.t_RefVertex(n_vertex + x + y * (n_size_x + 1)),
					r_mesh.t_RefVertex(n_vertex + x + (y + 1) * (n_size_x + 1)),
					r_mesh.t_RefVertex(n_vertex + (x + 1) + (y + 1) * (n_size_x + 1)),
					r_mesh.t_RefVertex(n_vertex + (x + 1) + y * (n_size_x + 1))};
				// take ref vertices of grid quad

				if(b_flip) {
					CPolyMesh::_TyRefVertex tmp(p_vertex_ptr[3]);
					p_vertex_ptr[3] = p_vertex_ptr[0];
					p_vertex_ptr[0] = tmp;
					CPolyMesh::_TyRefVertex tmp2(p_vertex_ptr[2]);
					p_vertex_ptr[2] = p_vertex_ptr[1];
					p_vertex_ptr[1] = tmp2;
				}
				// flip?

				CPolyMesh::_TyPolygon &r_face = r_mesh.r_Polygon(n_face);
				r_face.Delete();
				r_face.Insert_Vertex(p_vertex_ptr, p_vertex_ptr + 4);
				// creates a new polygon

				r_face.Set_SurfaceFlags(0);
				r_face.Set_MaterialId(n_material);
				// set no material
			}
		}
	} catch(std::bad_alloc&) {
		return false;
	}

	return true;
}

bool CPlatonicPrimitives::WindFan(CPolyMesh &r_mesh, size_t n_face, size_t n_center_vertex,
	size_t n_fan_vertex, size_t n_fan_length, bool b_loop, bool b_flip, CPolyMesh::TMaterialId n_material)
{
	try {
		_ASSERTE((b_loop && n_fan_length >= 0) || n_fan_length > 0); // makes sure it will not underflow
		_ASSERTE(n_center_vertex < r_mesh.n_Vertex_Num()); // make sure it is a valid vertex index
		_ASSERTE(n_fan_vertex < r_mesh.n_Vertex_Num()); // make sure it is a valid vertex index
		_ASSERTE(n_fan_vertex + n_fan_length <= r_mesh.n_Vertex_Num()); // make sure there is enough vertices
		_ASSERTE(n_center_vertex < n_fan_vertex ||
			n_center_vertex >= n_fan_vertex + n_fan_length); // make sure the center vertex is not a part of the fan (would yield degenerate triangles)
		_ASSERTE(n_face < r_mesh.n_Polygon_Num()); // make sure it is a valid face index
		_ASSERTE(n_face + ((b_loop)? n_fan_length : n_fan_length - 1) <= r_mesh.n_Polygon_Num()); // make sure there is enough faces

		int n_flip = int(b_flip);
		int n_not_flip = 1 - n_flip;
		for(size_t i = 1; i < n_fan_length; ++ i, ++ n_face) {
			CPolyMesh::_TyRefVertex p_vertex_ptr[3] = {
				r_mesh.t_RefVertex(n_center_vertex),
				r_mesh.t_RefVertex(n_fan_vertex + i - n_flip),
				r_mesh.t_RefVertex(n_fan_vertex + i - n_not_flip)};
			// take ref vertices of triangle

			CPolyMesh::_TyPolygon &r_face = r_mesh.r_Polygon(n_face);
			r_face.Delete();
			r_face.Insert_Vertex(p_vertex_ptr, p_vertex_ptr + 3);
			// creates a new polygon

			r_face.Set_SurfaceFlags(0);
			r_face.Set_MaterialId(n_material);
			// set no material
		}
		if(b_loop) {
			CPolyMesh::_TyRefVertex p_vertex_ptr[3] = {
				r_mesh.t_RefVertex(n_center_vertex),
				r_mesh.t_RefVertex(n_fan_vertex + n_flip * (n_fan_length - 1)),
				r_mesh.t_RefVertex(n_fan_vertex + n_not_flip * (n_fan_length - 1))};
			// take ref vertices of triangle

			CPolyMesh::_TyPolygon &r_face = r_mesh.r_Polygon(n_face);
			r_face.Delete();
			r_face.Insert_Vertex(p_vertex_ptr, p_vertex_ptr + 3);
			// creates a new polygon

			r_face.Set_SurfaceFlags(0);
			r_face.Set_MaterialId(n_material);
			// set no material
		}
	} catch(std::bad_alloc&) {
		return false;
	}

	return true;
}

bool CPlatonicPrimitives::WindSection(CPolyMesh &r_mesh, size_t n_face, size_t n_left_vertex,
	size_t n_right_vertex, size_t n_section_length, bool b_loop, bool b_flip,
	CPolyMesh::TMaterialId n_material)
{
	try {
		_ASSERTE((b_loop && n_section_length >= 0) || n_section_length > 0); // makes sure it will not underflow
		_ASSERTE(n_left_vertex < r_mesh.n_Vertex_Num()); // make sure it is a valid vertex index
		_ASSERTE(n_right_vertex < r_mesh.n_Vertex_Num()); // make sure it is a valid vertex index
		_ASSERTE(n_left_vertex + n_section_length <= r_mesh.n_Vertex_Num()); // make sure there is enough vertices
		_ASSERTE(n_right_vertex + n_section_length <= r_mesh.n_Vertex_Num()); // make sure there is enough vertices
		_ASSERTE(n_face < r_mesh.n_Polygon_Num()); // make sure it is a valid face index
		_ASSERTE(n_face + ((b_loop)? n_section_length : n_section_length - 1) <= r_mesh.n_Polygon_Num()); // make sure there is enough faces
		_ASSERTE(n_left_vertex + n_section_length <= n_right_vertex ||
			n_right_vertex + n_section_length <= n_left_vertex); // makes sure the ranges do not overlap (otherwise degenerate faces occur)

		int n_flip = int(b_flip);
		int n_not_flip = 1 - n_flip;
		for(size_t i = 1; i < n_section_length; ++ i, ++ n_face) {
			CPolyMesh::_TyRefVertex p_vertex_ptr[4] = {
				r_mesh.t_RefVertex(n_left_vertex + i - n_flip),
				r_mesh.t_RefVertex(n_left_vertex + i - n_not_flip),
				r_mesh.t_RefVertex(n_right_vertex + i - n_not_flip),
				r_mesh.t_RefVertex(n_right_vertex + i - n_flip)
			};
			// vertices of a single quad

			CPolyMesh::_TyPolygon &r_face = r_mesh.r_Polygon(n_face);
			r_face.Delete();
			r_face.Insert_Vertex(p_vertex_ptr, p_vertex_ptr + 4);
			// creates a new polygon

			r_face.Set_SurfaceFlags(0);
			r_face.Set_MaterialId(n_material);
			// set no material
		}
		if(b_loop) {
			CPolyMesh::_TyRefVertex p_vertex_ptr[4] = {
				r_mesh.t_RefVertex(n_left_vertex + n_flip * (n_section_length - 1)),
				r_mesh.t_RefVertex(n_left_vertex + n_not_flip * (n_section_length - 1)),
				r_mesh.t_RefVertex(n_right_vertex + n_not_flip * (n_section_length - 1)),
				r_mesh.t_RefVertex(n_right_vertex + n_flip * (n_section_length - 1))
			};
			// vertices of a single quad

			CPolyMesh::_TyPolygon &r_face = r_mesh.r_Polygon(n_face);
			r_face.Delete();
			r_face.Insert_Vertex(p_vertex_ptr, p_vertex_ptr + 4);
			// creates a new polygon

			r_face.Set_SurfaceFlags(0);
			r_face.Set_MaterialId(n_material);
			// set no material
		}
	} catch(std::bad_alloc&) {
		return false;
	}

	return true;
}

/*
 *								=== ~CPlatonicPrimitives ===
 */
