/*
								+----------------------------------+
								|                                  |
								|  ***  Texcoords generators  ***  |
								|                                  |
								|   Copyright © -tHE SWINe- 2007   |
								|                                  |
								|          TexCoords.cpp           |
								|                                  |
								+----------------------------------+
*/

/**
 *	@file dev/TexCoords.cpp
 *	@date 2007
 *	@author -tHE SWINe-
 *	@brief texcoords generators
 *
 *	@date 2008-06-24
 *
 *	renamed CPolygon2::r_t_Vertex() and CPolyMesh::r_t_Vertex() to
 *	CPolygon2::r_Vertex() and CPolyMesh::r_Vertex() respectively
 *
 *	@date 2008-08-21
 *
 *	unified gizmo semantics, unit gizmo matrix should transform mesh bounding
 *	box to axis-aligned unit cube with one of corners at O and some other one at (1, 1, 1)
 *
 *	removed map_Cylinder_Caps cap texcoord scaling
 *
 *	added CTexCoordGen::t_Gizmo() for calculating unit gizmo matrix
 *
 *	@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_
 *
 */

#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 <vector>
#include <math.h>
#include "../Vector.h"
#include "../BitArray.h"
#include "PolyMesh.h"
#include "TexCoords.h"
#include "../StlUtils.h"

/*
 *								=== CTexCoordGen ===
 */

/*
 *	static Matrix4f CTexCoordGen::t_Gizmo(CPolyMesh &r_t_mesh)
 *		- creates (unit) textcoord mapping gizmo for mesh r_t_mesh
 *		- returns matrix for transforming vertex positions to positions inside mapping gizmo
 */
Matrix4f CTexCoordGen::t_Gizmo(CPolyMesh &r_t_mesh)
{
	Vector3f v_min(0, 0, 0), v_max(1, 1, 1);
	r_t_mesh.BoundingBox(v_min, v_max);
	return t_Gizmo(v_min, v_max);
}

/*
 *	static Matrix4f CTexCoordGen::t_Gizmo(Vector3f v_min, Vector3f v_max)
 *		- creates (unit) textcoord mapping gizmo for mesh with bounding
 *		  box given by it's two corners (v_min, v_max)
 *		- returns matrix for transforming vertex positions to positions inside mapping gizmo
 */
Matrix4f CTexCoordGen::t_Gizmo(Vector3f v_min, Vector3f v_max)
{
	Matrix4f t_gizmo;
	t_gizmo.Identity();
	t_gizmo.Scale(
		(v_max.x != v_min.x)? 1 / (v_max.x - v_min.x) : 1,
		(v_max.y != v_min.y)? 1 / (v_max.y - v_min.y) : 1,
		(v_max.z != v_min.z)? 1 / (v_max.z - v_min.z) : 1);
	t_gizmo.Translate(-v_min);

	//_ASSERTE((t_gizmo.v_Transform_Pos(v_min) - Vector3f(0, 0, 0)).f_Length() < 1e-3f);
	//_ASSERTE((t_gizmo.v_Transform_Pos(v_max) - Vector3f(1, 1, 1)).f_Length() < 1e-3f);
	// debug (doesn't apply when mesh is flat in one direction)

	return t_gizmo;
}

static inline Vector2f SelectXZ(const Vector3f &r_v_vec)
{
	return r_v_vec.v_xz();
}

static inline Vector2f CalcElev(const Vector3f &r_v_vec)
{
	return Vector2f(r_v_vec.y, r_v_vec.v_xz().f_Length());
}

/*
 *	static bool CTexCoordGen::CalcTexcoords(CPolyMesh &r_t_mesh, Matrix4f t_gizmo_transform,
 *		int n_map_type, int n_channel, int n_coord_s = 0, int n_coord_t = 1)
 *		- calculates 2D texcoords for mesh r_t_mesh
 *		- t_gizmo_transform is matrix for transforming vertex positions to position
 *		  inside mapping gizmo (such transformation that vertex coordinates are transformed
 *		  to unit cube with center at (.5, .5, .5))
 *		- n_map_type is one of map_Plane, map_Box, map_Sphere,
 *		  map_Cylinder or map_Cylinder_Caps (note all but map_Plane explodes vertices)
 *		- note map_Box and map_Cylinder_Caps require correct polygon normals
 *		- n_channel is output texture channel (currently 0 - 3)
 *		- n_coord_s is index of vector component where s coordinate is to be stored (0 - 3)
 *		- n_coord_t is index of vector component where t coordinate is to be stored (0 - 3)
 *		- returns true on success, false on failure (needs to create some tables)
 */
bool CTexCoordGen::CalcTexcoords(CPolyMesh &r_t_mesh, Matrix4f t_gizmo_transform,
	int n_map_type, int n_channel, int n_coord_s, int n_coord_t)
{
	std::vector<Vector3f> pos_list;
	std::vector<Vector2f> texcoord_list;

	if(n_map_type == map_Box || n_map_type == map_Sphere ||
	   n_map_type == map_Cylinder || n_map_type == map_Cylinder_Caps) {
		if(!r_t_mesh.ExplodeVertices())
			return false;
		// explode vertices

		/*if(n_map_type == map_Sphere || n_map_type == map_Cylinder ||
		   n_map_type == map_Cylinder_Caps) {
			t_gizmo_transform.Scale(2);
			t_gizmo_transform.Offset(t_gizmo_transform.v_Offset() - .5f);
		}*/
		// these mappings need gizmo to transform coordinates in [-1, 1] range

		for(size_t i = 0, m = r_t_mesh.n_Poly_Num(); i < m; ++ i) {
			CPolyMesh::CPolygon &r_polygon = r_t_mesh.r_Polygon(i);
			size_t n_vertex_num = r_polygon.n_Vertex_Num();

			pos_list.clear();
			texcoord_list.clear();
			if(!stl_ut::Reserve_N(pos_list, n_vertex_num) ||
			   !stl_ut::Reserve_N(texcoord_list, n_vertex_num))
				return false;
			// prepare lists for transformed vertex positions and texcoords

			for(size_t n = 0; n < n_vertex_num; ++ n) {
				pos_list.push_back(t_gizmo_transform.v_Transform_Pos(r_polygon.t_Vertex(n)));
				if(n_map_type == map_Sphere || n_map_type == map_Cylinder ||
					n_map_type == map_Cylinder_Caps) {
					pos_list[n] *= 2;
					pos_list[n] -= 1;
				}
				texcoord_list.push_back(Vector2f(0, 0));
			}
			// get vertex positions

			switch(n_map_type) {
			case map_Box:
				{
					Vector3f v_normal = r_polygon.t_Normal().v_normal;
					for(int n = 0; n < 3; ++ n)
						v_normal[n] = (float)fabs(v_normal[n]);
					int n_coord = (v_normal.x > v_normal.y)? ((v_normal.z > v_normal.x)? 2 : 0) :
						((v_normal.z > v_normal.y)? 2 : 1);
					// find max absolute component of face normal vector

					int n_normal_sign = (r_polygon.t_Normal().v_normal[n_coord] > 0)? 1 : -1;

					if(n_coord == 2) { // fix mapping a bit so textures on the walls aren't upside down / flipped
						for(int n = 0; n < n_vertex_num; ++ n) {
							texcoord_list[n].x = n_normal_sign * pos_list[n][(n_coord + 1) % 3];
							texcoord_list[n].y = -pos_list[n][(n_coord + 2) % 3];
						}
					} else {
						for(int n = 0; n < n_vertex_num; ++ n) {
							texcoord_list[n].x = -n_normal_sign * pos_list[n][(n_coord + 2) % 3];
							texcoord_list[n].y = -pos_list[n][(n_coord + 1) % 3];
						}
					}
					// texture mapping plane is then on the other two coordinates
				}
				break;
			case map_Cylinder_Caps:
				{
					Vector3f v_normal = r_polygon.t_Normal().v_normal;
					for(int n = 0; n < 3; ++ n)
						v_normal[n] = (float)fabs(v_normal[n]);
					if(v_normal.y * v_normal.y > v_normal.x * v_normal.x + v_normal.z * v_normal.z) {
						for(size_t n = 0; n < n_vertex_num; ++ n)
							texcoord_list[n] = pos_list[n].v_xz() * .5f + .5f;
						break; // don't forget to jump out of the case statement
					}
					// in case y is the largest coordinate, use planar mapping (for cap faces)
				}
				// this case intentionally falls trough
			case map_Sphere:
			case map_Cylinder:
				{
					std::vector<float> azimuth_list;
					if(!stl_ut::Reserve_N(azimuth_list, n_vertex_num))
						return false;
					CalcAzimuth(pos_list, azimuth_list, SelectXZ);
					_ASSERTE(azimuth_list.size() == n_vertex_num);
					// calc azimuth angles

					if(n_map_type == map_Cylinder || n_map_type == map_Cylinder_Caps) {
						for(size_t n = 0; n < n_vertex_num; ++ n) {
							texcoord_list[n].x = azimuth_list[n] / (2 * f_pi) * 3;
							texcoord_list[n].y = -(pos_list[n].y * .5f + .5f);
						}
						// cylindrical gizmo takes azimuth as s and y as t
					} else /*if(n_map_type == map_Sphere)*/ {
						std::vector<float> elevation_list;
						if(!stl_ut::Reserve_N(elevation_list, n_vertex_num))
							return false;
						CalcAzimuth(pos_list, elevation_list, CalcElev);
						_ASSERTE(elevation_list.size() == n_vertex_num);
						// calc elevation angles

						for(size_t n = 0; n < n_vertex_num; ++ n) {
							texcoord_list[n].x = azimuth_list[n] / (2 * f_pi) * 3;
							texcoord_list[n].y = elevation_list[n] / f_pi;
						}
						// spherical gizmo takes x-z azimuth as s and y azimuth as t
					}
				}
				break;
			default:
				return false;
			};

			for(size_t n = 0; n < n_vertex_num; ++ n) {
				r_polygon.r_Vertex(n).m_p_ref->p_texture_coord[n_channel][n_coord_s] =
					texcoord_list[n].x;
				r_polygon.r_Vertex(n).m_p_ref->p_texture_coord[n_channel][n_coord_t] =
					texcoord_list[n].y;
			}
			// store result in proper texture channel
		}
		// texcoords are not dependent on position only or the mapping is discontinuous
	} else {
		for(size_t i = 0, n = r_t_mesh.n_Vertex_Num(); i < n; ++ i) {
			Vector3f v_pos = t_gizmo_transform.v_Transform_Pos(r_t_mesh.p_Vertex(i)->v_position);
			// get vertex position

			Vector2f v_texcoord;
			// result is written here

			switch(n_map_type) {
			case map_Plane:
				v_texcoord = v_pos.v_xz();
				break;
			default:
				return false;
			}

			r_t_mesh.r_Vertex(i).p_texture_coord[n_channel][n_coord_s] = v_texcoord.x;
			r_t_mesh.r_Vertex(i).p_texture_coord[n_channel][n_coord_t] = v_texcoord.y;
			// store result in proper texture channel
		}
		// texcoords are dependent on point position only
	}

	return true;
}

/*
 *	static void CTexCoordGen::CalcTangents(CPolyMesh &r_t_mesh,
 *		int n_channel_in, int n_channel_out)
 *		- calculates tangents of texture, mapped to coordinates n_coord_s and
 *		  n_coord_t of channel n_channel_in
 *		- writes binormal direction and texture polarity to n_channel_out
 *		  (takes up the whole channel)
 *		- note texture polarity is either 1 or -1, face and vertex normals are
 *		  required to calculate it
 *		- f_angle_thresh is maximal angle between tangents of faces for their vertices to break
 *		- returns true on success, false on failure (needs to create some tables)
 */
bool CTexCoordGen::CalcTangents(CPolyMesh &r_t_mesh, int n_channel_in,
	int n_coord_s, int n_coord_t, int n_channel_out, float f_angle_thresh, uint32_t n_face_flags_mask, int n_face_material_mask)
{
	_ASSERTE(n_channel_in >= 0 && n_channel_in < CPolyMesh::TVertex::n_Texcoord_Num);
	_ASSERTE(n_channel_out >= 0 && n_channel_out < CPolyMesh::TVertex::n_Texcoord_Num);

	f_angle_thresh = (float)cos(f_angle_thresh);
	// will be using dot product

	size_t *p_vertex_index;
	std::vector<size_t> *p_ref_table;

	{
		CPolyMesh::CVertexHash vertex_hash(r_t_mesh);
		if(!vertex_hash.b_Status())
			return false;
		CPolyMesh::CVertexMergeTable merge_table(r_t_mesh, vertex_hash, false, false, false);
		if(!merge_table.b_Status())
			return false;
		// create vertex merge table

		if(!(p_vertex_index = merge_table.p_VertexIndexTable()))
			return false;
		// create vertex indices (as much as vertices in this object)

		if(!(p_ref_table = r_t_mesh.p_VertexReference_Table(p_vertex_index, merge_table))) {
			delete[] p_vertex_index;
			return false;
		}
		// create table of polygon references
	}
	// make sure vertex hash and merge table is deleted as soon as tables are created

	Vector3f *p_tangent;
	Vector3f *p_bitangent;
	if(!(p_tangent = new(std::nothrow) Vector3f[r_t_mesh.n_Poly_Num() * 2])) {
		delete[] p_vertex_index;
		delete[] p_ref_table;
		return false;
	}
	p_bitangent = p_tangent + r_t_mesh.n_Poly_Num();
	// alloc aux array for bi/tangents

	CBitArray face_polarity(r_t_mesh.n_Poly_Num());
	if(face_polarity.n_Length() != r_t_mesh.n_Poly_Num()) {
		delete[] p_vertex_index;
		delete[] p_ref_table;
		return false;
	}
	// alloc aux array for face polarities

	for(size_t i = 0, n = r_t_mesh.n_Poly_Num(); i < n; ++ i) {
		CPolyMesh::CPolygon &r_poly = r_t_mesh.r_Polygon(i);

		Vector3f v_edge0 = (Vector3f)r_poly.t_Vertex(1) - (Vector3f)r_poly.t_Vertex(0);
		Vector3f v_edge1 = (Vector3f)r_poly.t_Vertex(2) - (Vector3f)r_poly.t_Vertex(0);
		// worldspace edge vectors

		Vector2f v_tex0(r_poly.t_Vertex(1).m_p_ref->p_texture_coord[n_channel_in][n_coord_s] -
			r_poly.t_Vertex(0).m_p_ref->p_texture_coord[n_channel_in][n_coord_s],
			r_poly.t_Vertex(1).m_p_ref->p_texture_coord[n_channel_in][n_coord_t] -
			r_poly.t_Vertex(0).m_p_ref->p_texture_coord[n_channel_in][n_coord_t]);
		Vector2f v_tex1(r_poly.t_Vertex(2).m_p_ref->p_texture_coord[n_channel_in][n_coord_s] -
			r_poly.t_Vertex(0).m_p_ref->p_texture_coord[n_channel_in][n_coord_s],
			r_poly.t_Vertex(2).m_p_ref->p_texture_coord[n_channel_in][n_coord_t] -
			r_poly.t_Vertex(0).m_p_ref->p_texture_coord[n_channel_in][n_coord_t]);
		// texturespace edge vectors
    
		float f_r = 1.0f / (v_tex0.x * v_tex1.y - v_tex1.x * v_tex0.y);
   		Vector3f v_s_dir((v_tex1.y * v_edge0.x - v_tex0.y * v_edge1.x) * f_r,
						 (v_tex1.y * v_edge0.y - v_tex0.y * v_edge1.y) * f_r,
						 (v_tex1.y * v_edge0.z - v_tex0.y * v_edge1.z) * f_r);
		Vector3f v_t_dir((v_tex0.x * v_edge1.x - v_tex1.x * v_edge0.x) * f_r,
						 (v_tex0.x * v_edge1.y - v_tex1.x * v_edge0.y) * f_r,
						 (v_tex0.x * v_edge1.z - v_tex1.x * v_edge0.z) * f_r);
		// calculate direction of s and t texcoords in worldspace

		v_s_dir.Normalize();
		v_t_dir.Normalize();
		// we will be averaging them. it would be nice to have them normalized

		p_tangent[i] = v_s_dir;
		p_bitangent[i] = v_t_dir;
		// store the vectors

		v_s_dir.Orthogonalize(r_poly.t_Normal().v_normal);
		face_polarity[i] = v_s_dir.v_Cross(r_poly.t_Normal().v_normal).f_Dot(v_t_dir) > 0;
		// store face polarity; shouldn't average tangents / bitangents
		// of vertices of faces with different polarities
	}
	// calculate tangent / bitangent for each face

	for(size_t i = 0, n = r_t_mesh.n_Vertex_Num(); i < n; ++ i) {
		Vector3f v_tangent(0, 0, 0);
		Vector3f v_bitangent(0, 0, 0);
		// tangent and bitangent vectors of vertex

		size_t n_group = p_vertex_index[i];
		// determine vertex group

		const CPolyMesh::TVertex *p_cur_vertex = r_t_mesh.p_Vertex(i);

		const CPolyMesh::CPolygon *p_owner = 0;
		size_t n_owner_index = -1;
		for(size_t j = 0, m = p_ref_table[n_group].size(); j < m; ++ j) {
			size_t n_cur_index = p_ref_table[n_group][j];
			const CPolyMesh::CPolygon *p_cur_poly = r_t_mesh.p_Polygon(n_cur_index);
			for(size_t k = 0, n_poly_verts = p_cur_poly->n_Vertex_Num(); k < n_poly_verts; ++ k) {
				if(p_cur_poly->t_Vertex(k).m_p_ref == p_cur_vertex) {
					p_owner = p_cur_poly;
					n_owner_index = n_cur_index;
					break;
				}
			}
		}
		// find face owning this vertex

		if(p_owner) {
			for(size_t j = 0, m = p_ref_table[n_group].size(); j < m; ++ j) {
				size_t n_cur_index = p_ref_table[n_group][j];
				const CPolyMesh::CPolygon *p_cur_poly = r_t_mesh.p_Polygon(n_cur_index);
				if(((p_cur_poly->n_Flags() ^ p_owner->n_Flags()) & n_face_flags_mask) ||
				   ((p_cur_poly->n_Material() ^ p_owner->n_Material()) & n_face_material_mask) ||
				   face_polarity[n_owner_index] != face_polarity[n_cur_index] ||
				   p_tangent[n_cur_index].f_Dot(p_tangent[n_owner_index]) < f_angle_thresh)
					continue;
				// check if face has the same material and polarity as owner face
				// in case tangents have too different angle, discard it as well

				v_tangent += p_tangent[n_cur_index];
				v_bitangent += p_bitangent[n_cur_index];
			}
			// account tangents / bitangents of faces with the same texture as owner
		} /*else { // should not be necessary, most likely reference table will be empty
			for(size_t j = 0, m = p_ref_table[n_group].size(); j < m; ++ j) {
				CPolyMesh::CPolygon *p_cur_poly = p_ref_table[n_group][j];
				size_t n_cur_index = p_cur_poly - r_t_mesh.p_face;
				v_tangent += p_tangent[n_cur_index];
				v_bitangent += p_bitangent[n_cur_index];
			}
			// account tangents / bitangents of any faces referencing vertices on this position
		}*/
		// average tangents / bitangents of faces with vertices on the same positions

		Vector3f v_normal = r_t_mesh.t_Vertex(i).v_normal;
		// get vertex normal

		v_tangent.Orthogonalize(v_normal);
		// orthogonalize (normalizes as well)

		r_t_mesh.r_Vertex(i).p_texture_coord[n_channel_out] = Vector4f(v_tangent,
			(v_normal.v_Cross(v_tangent).f_Dot(v_bitangent) < 0)? -1.0f : 1.0f);
		// set tangent and calculate texture polarity
	}
	// calculate normalized and orthogonal tangent and determine texture polarity
	// as handedness of t/b/n coordinate system

	delete[] p_vertex_index;
	delete[] p_ref_table;
	delete[] p_tangent;

	return true;
}

template <class CCoordSelector>
void CTexCoordGen::CalcAzimuth(const std::vector<Vector3f> &r_pos_list,
	std::vector<float> &r_azimuth_list, CCoordSelector selector)
{
	_ASSERTE(r_azimuth_list.capacity() >= r_pos_list.size());
	_ASSERTE(r_azimuth_list.empty());

	bool b_have_first_quadrant = false;
	bool b_have_third_quadrant = false;
	for(size_t i = 0, n = r_pos_list.size(); i < n; ++ i) {
		Vector2f v_azimuth = selector(r_pos_list[i]);
		v_azimuth.Normalize();
		if(v_azimuth.y == 0 && v_azimuth.x == 0)
			r_azimuth_list.push_back(0);
		else {
			r_azimuth_list.push_back(float(atan(v_azimuth.y / v_azimuth.x)) +
				((v_azimuth.x < 0)? f_pi : ((v_azimuth.y < 0)? 2 * f_pi : 0)));
			// do not use atan2
		}
		// calculate azimuth

		if(v_azimuth.x >= 0) {
			if(v_azimuth.y >= 0)
				b_have_first_quadrant = true;
			else
				b_have_third_quadrant = true;
		}
		// is polygon spanning across more quadrants?
	}
	// calc azimuth angles

	if(b_have_first_quadrant && b_have_third_quadrant) {
		for(size_t i = 0, n = r_pos_list.size(); i < n; ++ i) {
			Vector2f v_azimuth = selector(r_pos_list[i]);
			if(v_azimuth.x >= 0 && v_azimuth.y >= 0)
				r_azimuth_list[i] += 2 * f_pi;
		}
	}
	// in case we've got vertices in both quadrants, add 2pi to angles in first quadrant
}

/*
 *								=== ~CTexCoordGen ===
 */
