/*
								+----------------------------------+
								|                                  |
								|  *** Simple mesh bending op ***  |
								|                                  |
								|   Copyright © -tHE SWINe- 2014   |
								|                                  |
								|              Bend.h              |
								|                                  |
								+----------------------------------+
*/

#pragma once
#ifndef __MESH_BEND_INCLUDED
#define __MESH_BEND_INCLUDED

/**
 *	@file lml/Bend.h
 *	@date 2007
 *	@author -tHE SWINe-
 *	@brief simple geometric bending of polygonal meshes
 *
 *	@todo - doc, make .cpp
 *	@todo - this has problems with direction other than 0 or +- pi / 2, test it on some boxes and see what happens
 */

#include "PolyMesh.h"

/**
 *	@brief simple geometric bending operation on polygonal meshes
 */
class CBendModifier {
public:
	typedef CPolyMesh::_TyPolygon _TyPolygon; /**< @brief polygon type */
	typedef CPolyMesh::_TyVertex _TyVertex; /**< @brief vertex type */

public:
	/**
	 *	@brief bends a polygonal object by given angle arround the given axis
	 *
	 *	If applied to a flat object, it will be shaped by a surface of a cylinder.
	 *
	 *	@param[in,out] r_mesh is the mesh to be bent
	 *	@param[in] f_angle is the angle to bend by
	 *	@param[in] t_gizmo_transform is a matrix for transforming vertex positions to positions
	 *		inside the mapping gizmo (such transformation that vertex coordinates are transformed
	 *		to unit cube with center at (.5, .5, .5))
	 *	@param[in] n_arg_axis is zero-based index of axis of coordinates inside the gizmo
	 *		to be used as the bend argument (default x axis)
	 *	@param[in] f_bend_direction is bend direction, relative to bend axis,
	 *		in radians (default 0, meaning the z axis)
	 *	@param[in] f_offset is offset in gizmo coordinates where the zero angle bend
	 *		is located (default .5)
	 *	@param[in] b_update_vertex_normals is vertex normal recalculation flag
	 *		(if set, the vertex normals are also bent, default is true)
	 *	@param[in] b_update_face_normals is face normal recalculation flag
	 *		(if set, the face normals are also bent, default is true)
	 *
	 *	@note The mesh needs to be sufficiently tesselated.
	 *	@note The normals are only modified, not recalculated. If the normals were not
	 *		previously calculated, updating them will not produce meaningful results.
	 *	@note The face normal transformation is only approximate and if the polygons are
	 *		large, it can be expected to be less precise.
	 */
	static void Bend(CPolyMesh &r_mesh, float f_angle, Matrix4f t_gizmo_transform,
		int n_arg_axis = 0, float f_bend_direction = 0, float f_offset = .5f,
		bool b_update_vertex_normals = true, bool b_update_face_normals = true)
	{
		Vector3f v_bend_arg_axis_gizmo(0, 0, 0);
		v_bend_arg_axis_gizmo[n_arg_axis] = 1;
		Vector3f v_bend_axis_gizmo(0, 0, 0);
		v_bend_axis_gizmo[(n_arg_axis + 3 - 1) % 3] = float(cos(f_bend_direction));
		v_bend_axis_gizmo[(n_arg_axis + 1) % 3] = float(sin(f_bend_direction));
		Vector3f v_bend_offset_gizmo;
		v_bend_offset_gizmo = v_bend_arg_axis_gizmo.v_Cross(v_bend_axis_gizmo);
		_ASSERTE(fabs(v_bend_offset_gizmo.f_Length() - 1) < 1e-3f); // should be normalized
		Vector3f p_bend_extreme_gizmo[2] = {Vector3f(.5f, .5f, .5f), Vector3f(.5f, .5f, .5f)};
		p_bend_extreme_gizmo[0][n_arg_axis] = 0;
		p_bend_extreme_gizmo[1][n_arg_axis] = 1;
		Vector3f v_bend_center_gizmo(.5f, .5f, .5f);
		v_bend_center_gizmo[n_arg_axis] = f_offset;
		// get bend axis, bend center and left / right extreme in gizmo coordinates

		Matrix4f t_gizmo_inv;
		t_gizmo_transform.Invert_To(t_gizmo_inv);
		Vector3f v_bend_center_world = t_gizmo_inv.v_Transform_Pos(v_bend_center_gizmo);
		Vector3f v_bend_axis_world = t_gizmo_inv.v_Transform_Dir(v_bend_axis_gizmo);
		v_bend_axis_world.Normalize();
		Vector3f p_bend_extreme_world[2] = {t_gizmo_inv.v_Transform_Pos(p_bend_extreme_gizmo[0]),
			t_gizmo_inv.v_Transform_Pos(p_bend_extreme_gizmo[1])};
		Vector3f v_bend_offset_world = t_gizmo_inv.v_Transform_Dir(v_bend_offset_gizmo);
		v_bend_offset_world.Normalize();
		float f_world_circumference = (p_bend_extreme_world[0] - p_bend_extreme_world[1]).f_Length();
		f_world_circumference = f_world_circumference / f_angle /** 2 * f_pi*/;
		if(fabs(f_world_circumference) > 1e30f) {
			return;
			// the angle is too low, the bend would be too small to observe, but too numerically unstable to calculate
			// note that there could be a better model for these situations
		}
		float f_world_rolled_radius = f_world_circumference /*/ (2 * f_pi)*/; // multiplication and division by two pi evens out

		v_bend_center_world += v_bend_offset_world * f_world_rolled_radius;
		// offset by radius

		Bend(r_mesh, v_bend_axis_world, v_bend_center_world, f_angle, t_gizmo_transform,
			n_arg_axis, f_offset, b_update_vertex_normals, b_update_vertex_normals);
		// use the complicated bend function to perform the bend
	}

	/**
	 *	@brief bends a polygonal object by given angle arround the given axis
	 *
	 *	@param[in,out] r_mesh is the mesh to be bent
	 *	@param[in] v_bend_axis is bending axis direction
	 *	@param[in] v_center is point on the bending axis (can be e.g. mesh bounding box center)
	 *	@param[in] f_angle is the angle to bend by
	 *	@param[in] t_gizmo_transform is a matrix for transforming vertex positions to positions
	 *		inside the mapping gizmo (such transformation that vertex coordinates are transformed
	 *		to unit cube with center at (.5, .5, .5))
	 *	@param[in] n_arg_axis is zero-based index of axis of coordinates inside the gizmo
	 *		to be used as the bend argument (default x axis)
	 *	@param[in] f_offset is offset in gizmo coordinates where the zero angle bend
	 *		is located (default .5)
	 *	@param[in] b_update_vertex_normals is vertex normal recalculation flag
	 *		(if set, the vertex normals are also bent, default is true)
	 *	@param[in] b_update_face_normals is face normal recalculation flag
	 *		(if set, the face normals are also bent, default is true)
	 *
	 *	@note The mesh needs to be sufficiently tesselated.
	 *	@note The normals are only modified, not recalculated. If the normals were not
	 *		previously calculated, updating them will not produce meaningful results.
	 *	@note The face normal transformation is only approximate and if the polygons are
	 *		large, it can be expected to be less precise.
	 */
	static void Bend(CPolyMesh &r_mesh, Vector3f v_bend_axis, const Vector3f &v_bend_center,
		float f_angle, Matrix4f t_gizmo_transform, int n_arg_axis = 0, float f_offset = .5f,
		bool b_update_vertex_normals = true, bool b_update_face_normals = true)
	{
		_ASSERTE(n_arg_axis >= 0 && n_arg_axis < 3); // make sure correct axis is selected
		v_bend_axis.Normalize(); // make sure the axis is normalized

		Vector3f v_arg_axis(t_gizmo_transform[n_arg_axis][0],
			t_gizmo_transform[n_arg_axis][1], t_gizmo_transform[n_arg_axis][2]);
		v_arg_axis.Orthogonalize(v_bend_axis); // should be orthogonal
		//v_arg_axis.Normalize(); // already normalized after orthogonalization
		// calculate worldspace argument axis

		for(size_t i = 0, n = r_mesh.n_Vertex_Num(); i < n; ++ i) {
			_TyVertex &r_vertex = r_mesh.r_Vertex(i);
			const Vector3f v_pos = r_vertex.v_position; // do not modify this
			Vector3f v_gizmo_pos = t_gizmo_transform.v_Transform_Pos(v_pos);
			float f_gizmo_arg = v_gizmo_pos[n_arg_axis];
			f_gizmo_arg -= f_offset; // add offset
			// determine the relative bending amount

			Vector3f v_pos2 = v_pos + v_arg_axis * v_arg_axis.f_Dot(v_bend_center - v_pos);
			// hack - this makes it work correctly - need to project the vertex onto
			// the plane under the axis of rotation (it lies on the axis of rotation
			// and is perpendicular to the argument axis)

			Quatf rotation(v_bend_axis, f_angle * f_gizmo_arg, quat::from_axis_angle);
			r_vertex.v_position = rotation.v_Transform(v_pos2 - v_bend_center) + v_bend_center; // rotate the positions
			// rotate the points arround the bend center

			if(b_update_vertex_normals)
				r_vertex.v_normal = rotation.v_Transform(r_vertex.v_normal);
			// normals would be transformed using inverse transpose matrix, but
			// this rotation is orthogonal, therefore inverse transpose is a no-op
		}

		if(b_update_face_normals) {
			for(size_t i = 0, n = r_mesh.n_Polygon_Num(); i < n; ++ i) {
				_TyPolygon &r_poly = r_mesh.r_Polygon(i);
				Vector3f v_pos = r_poly.v_Center();
				Vector3f v_gizmo_pos = t_gizmo_transform.v_Transform_Pos(v_pos);
				float f_gizmo_arg = v_gizmo_pos[n_arg_axis];
				f_gizmo_arg -= f_offset; // add offset
				// determine the relative bending amount

				Quatf rotation(v_bend_axis, f_angle * f_gizmo_arg, quat::from_axis_angle);
				const_cast<Plane3f&>(r_poly.t_Normal()) = rotation.t_ToMatrix4().t_Transform_Plane(r_poly.t_Normal());
				// transform the face normals
			}
			// transform face normals by rotating them arround the center
		}
	}
};

#endif // !__MESH_BEND_INCLUDED
