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

/**
 *	@file lml/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 ===
 */

Matrix4f CTexCoordGen::t_Gizmo(const CPolyMesh &r_mesh)
{
	Vector3f v_min(0, 0, 0), v_max(1, 1, 1);
	r_mesh.BoundingBox(v_min, v_max);
	return t_Gizmo(v_min, v_max);
}

Matrix4f CTexCoordGen::t_Gizmo(Vector3f v_min, Vector3f v_max)
{
	Matrix4f t_gizmo;
	t_gizmo.Scaling(
		(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());
}

bool CTexCoordGen::CalcTexcoords(CPolyMesh &r_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_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_mesh.n_Polygon_Num(); i < m; ++ i) {
			CPolyMesh::_TyPolygon &r_polygon = r_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(size_t 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(size_t 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 = float(-azimuth_list[n] / (2 * f_pi) /** 3*/); // 0 to 1
							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 = float(azimuth_list[n] / (2 * f_pi) /** 3*/); // 0 to 1
							texcoord_list[n].y = float(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.t_Vertex(n).m_p_ref->p_texture_coord[n_channel][n_coord_s] =
					texcoord_list[n].x;
				r_polygon.t_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_mesh.n_Vertex_Num(); i < n; ++ i) {
			Vector3f v_pos = t_gizmo_transform.v_Transform_Pos(r_mesh.r_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_mesh.r_Vertex(i).p_texture_coord[n_channel][n_coord_s] = v_texcoord.x;
			r_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;
}

bool CTexCoordGen::CalcTangents(CPolyMesh &r_mesh, int n_channel_in,
	int n_coord_s, int n_coord_t, int n_channel_out, float f_angle_thresh,
	CPolyMesh::TSurfaceFlags n_surface_flags_mask, CPolyMesh::TMaterialId n_face_material_mask)
{
	_ASSERTE(n_channel_in >= 0 && n_channel_in < CPolyMesh::_TyVertex::n_Texcoord_Num);
	_ASSERTE(n_channel_out >= 0 && n_channel_out < CPolyMesh::_TyVertex::n_Texcoord_Num);

	_ASSERTE(f_angle_thresh >= 0); // negative is just confusing
	f_angle_thresh = (f_angle_thresh >= f_pi)? -1 : (float)cos(f_angle_thresh); // clamp at pi, otherwise higher angles go periodic and cause confusing behavior
	// will be using dot product

	CVertexHash::CVertexGroupTable merge_table(r_mesh,
		CVertexHash::CUniformGrid<>(r_mesh, 100, f_epsilon), false, false, false);
	if(!merge_table.b_Status())
		return false;
	// create vertex merge table

	const std::vector<size_t> &r_group_index = merge_table.r_GroupIndexTable();
	// get vertex group indices (as much as vertices in this object)

	std::vector<std::vector<std::pair<size_t, size_t> > > vertex_ref_table;
	if(!merge_table.Build_VertexReference_Table(vertex_ref_table, r_mesh))
		return false;
	// create a table of polygon references

	std::vector<Vector3f> tangent_bitangent_list;
	if(!stl_ut::Resize_To_N(tangent_bitangent_list, r_mesh.n_Polygon_Num() * 2))
		return false;
	Vector3f *p_tangent = &tangent_bitangent_list[0];
	Vector3f *p_bitangent = p_tangent + r_mesh.n_Polygon_Num();
	// alloc aux array for bi/tangents (do not delete those!)

	CBitArray face_polarity(r_mesh.n_Polygon_Num());
	if(face_polarity.n_Length() != r_mesh.n_Polygon_Num())
		return false;
	// alloc aux array for face polarities

	for(size_t i = 0, n = r_mesh.n_Polygon_Num(); i < n; ++ i) {
		CPolyMesh::_TyPolygon &r_poly = r_mesh.r_Polygon(i);
		if(r_poly.n_Vertex_Num() < 3) {
			p_tangent[i] = Vector3f(0, 0, 0);
			p_bitangent[i] = Vector3f(0, 0, 0);
			face_polarity[i] = false;
			continue;
		}
		// handle tiny polygons with not enough vertices

		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);*/
		Vector3f v_s_dir = (v_edge0 * v_tex1.y - v_edge1 * v_tex0.y) * f_r;
		Vector3f v_t_dir = (v_edge0 * v_tex0.x - v_edge1 * v_tex1.x) * f_r; // can use vector expressions (untested)
		// 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_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 = r_group_index[i];
		// determine vertex group

		const CPolyMesh::_TyVertex *p_cur_vertex = &r_mesh.r_Vertex(i);
		// get vertex

		const std::vector<std::pair<size_t, size_t> > &r_referencing_polygons =
			vertex_ref_table[n_group];
		// get indices of polygons

		const CPolyMesh::_TyPolygon *p_owner = 0;
		size_t n_owner_index = -1;
		for(size_t j = 0, m = r_referencing_polygons.size(); j < m; ++ j) {
			size_t n_polygon = r_referencing_polygons[j].first;
			size_t n_vertex_of_polygon = r_referencing_polygons[j].second;
			const CPolyMesh::_TyPolygon &r_cur_poly = r_mesh.r_Polygon(n_polygon);
			if(r_cur_poly.t_Vertex(n_vertex_of_polygon).m_p_ref == p_cur_vertex) {
				p_owner = &r_cur_poly;
				n_owner_index = n_polygon;
			} else {
				for(size_t k = 0, n_poly_verts = r_cur_poly.n_Vertex_Num(); k < n_poly_verts; ++ k) {
					if(r_cur_poly.t_Vertex(k).m_p_ref == p_cur_vertex) {
						p_owner = &r_cur_poly;
						n_owner_index = n_polygon;
						break;
					}
				}
				// the first test can fail, if the polygon has a tiny edge there might be
				// two or more vertices in the same group and n_vertex_of_polygon is not helpful
			}
		}
		// find face owning this vertex (not one at the same position, this one)

		if(p_owner) {
			for(size_t j = 0, m = r_referencing_polygons.size(); j < m; ++ j) {
				size_t n_cur_index = r_referencing_polygons[j].first;
				const CPolyMesh::_TyPolygon &r_cur_poly = r_mesh.r_Polygon(n_cur_index);
				if(((r_cur_poly.n_SurfaceFlags() ^ p_owner->n_SurfaceFlags()) & n_surface_flags_mask) ||
				   ((r_cur_poly.n_MaterialId() ^ p_owner->n_MaterialId()) & 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 {
			_ASSERTE(r_referencing_polygons.empty());
			// this branch should not be necessary, the reference table should be empty
			/*for(size_t j = 0, m = r_referencing_polygons.size(); j < m; ++ j) {
				size_t n_cur_index = r_referencing_polygons[j].first;
				const CPolyMesh::_TyPolygon &r_cur_poly = r_mesh.r_Polygon(n_cur_index);
				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_mesh.r_Vertex(i).v_normal;
		// get vertex normal

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

		r_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

	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] += float(2 * f_pi);
		}
	}
	// in case we've got vertices in both quadrants, add 2pi to angles in first quadrant
}

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