/*
								+--------------------------------+
								|                                |
								| *** OpenGL transform utils *** |
								|                                |
								|  Copyright © -tHE SWINe- 2008  |
								|                                |
								|         Transform.cpp          |
								|                                |
								+--------------------------------+
*/

/**
 *	@file gl2/Transform.cpp
 *	@date 2008
 *	@author -tHE SWINe-
 *	@brief OpenGL transform utils
 *
 *	@date 2008-08-08
 *
 *	added \#ifdef for windows 64, added \#define for GL_GLEXT_LEGACY (for linux builds)
 *
 *	@date 2009-05-03
 *
 *	added CGLTransform::PerspectiveTile() and CGLTransform::OrthoTile functions
 *	(tiled rendering support)
 *
 */

#include "../NewFix.h"

#include "../CallStack.h"
#include "OpenGL20.h"
#include <math.h>
#include "../Vector.h"
#include "Transform.h"

/*
 *								=== CGLTransform ===
 */

/*
 *	static void CGLTransform::Perspective(float f_fov, float f_aspect, float f_near, float f_far)
 *		- alias for gluPerspective
 *		- f_fov is field-of-view in degrees, f_aspect is aspect (viewport height / width),
 *		  f_near and f_far are depths of near and far clipping planes respectively
 *		- note this requires glMatrixMode(GL_PROJECTION);
 */
void CGLTransform::Perspective(float f_fov, float f_aspect, float f_near, float f_far)
{
	float f_w, f_h;

	f_h = float(tan(f_fov * f_pi / 180 * .5f)) * f_near;
	f_w = f_h * f_aspect;
	// calc half width of frustum in x-y plane at z = f_near

	glFrustum(-f_w, f_w, -f_h, f_h, f_near, f_far);
	// set frustum
}

/*
 *	static void CGLTransform::PerspectiveTile(int n_total_width, int n_total_height,
 *		int n_tile_x, int n_tile_y, int n_tile_width, int n_tile_height,
 *		float f_fov, float f_aspect, float f_near, float f_far)
 *		- alias for gluPerspective, except this adds support for tile rendering
 *		  (rendering of rectangular window of perspective viewport in
 *		  purpose of rendering images, larger than maximal viewport size)
 *		- n_total_width, n_total_height is target image size (for example 4000, 3000)
 *		- n_tile_x, n_tile_y is current tile position inside the image
 *		- n_tile_width, n_tile_height is tile size (for example 512, 512;
 *		  gl viewport should match this size)
 *		- note tiles don't have to be square or power-of-two. tiles can overlap
 *		  (that is actually required for correct rendering of points and lines,
 *		  and can be useful to blend image seams when using reflection maps)
 *		  and individual tiles don't even have to be equal size
 *		- f_fov is field-of-view in degrees, f_aspect is aspect (viewport height / width),
 *		  f_near and f_far are depths of near and far clipping planes respectively
 *		- note this requires glMatrixMode(GL_PROJECTION);
 */
void CGLTransform::PerspectiveTile(int n_total_width, int n_total_height,
	int n_tile_x, int n_tile_y, int n_tile_width, int n_tile_height,
	float f_fov, float f_aspect, float f_near, float f_far)
{
	float f_half_w, f_half_h;
	f_half_h = float(tan(f_fov * f_pi / 180 * .5f)) * f_near;
	f_half_w = f_half_h * f_aspect;
	// calc half width of frustum in x-y plane at z = f_near

	float f_left = -f_half_w;
	float f_right = f_half_w;
	float f_width = f_half_w + f_half_w;
	float f_bottom = -f_half_h;
	float f_top = f_half_h;
	float f_height = f_half_h + f_half_h;
	// regular frustum

	float f_tile_left = f_left + f_width * n_tile_x / n_total_width;
	float f_tile_right = f_tile_left + f_width * n_tile_width / n_total_width;
	float f_tile_bottom = f_bottom + f_height * n_tile_y / n_total_height;
	float f_tile_top = f_tile_bottom + f_height * n_tile_height / n_total_height;
	// calculate tile frustum

	glFrustum(f_tile_left, f_tile_right, f_tile_bottom, f_tile_top, f_near, f_far);
	// set frustum
}

/*
 *	static void CGLTransform::OrthoTile(int n_total_width, int n_total_height,
 *		int n_tile_x, int n_tile_y, int n_tile_width, int n_tile_height,
 *		float f_left, float f_right, float f_bottom, float f_top,
 *		float f_near, float f_far)
 *		- alias for gluOrtho, except this adds support for tile rendering
 *		  (rendering of rectangular window of perspective viewport in
 *		  purpose of rendering images, larger than maximal viewport size)
 *		- note this is intended for 2D / GUI rendering in tiled images
 *		- n_total_width, n_total_height is target image size (for example 4000, 3000)
 *		- n_tile_x, n_tile_y is current tile position inside the image
 *		- n_tile_width, n_tile_height is tile size (for example 512, 512;
 *		  do not set this to current tile size which may be smaller in case tile
 *		  is close to image border)
 *		- note tiles don't have to be square or power-of-two. tiles can overlap
 *		  (that is actually required for correct rendering of points and lines,
 *		  and can be useful to blend image seams when using reflection maps)
 *		  and individual tiles don't even have to be equal size
 *		- f_left, f_right, f_bottom and f_top are ortographic projection parameters.
 *		  f_near and f_far are depths of near and far clipping planes respectively
 *		- note this requires glMatrixMode(GL_PROJECTION);
 */
void CGLTransform::OrthoTile(int n_total_width, int n_total_height,
	int n_tile_x, int n_tile_y, int n_tile_width, int n_tile_height,
	float f_left, float f_right, float f_bottom, float f_top, float f_near, float f_far)
{
	float f_width = f_right - f_left;
	float f_height = f_top - f_bottom;
	// regular frustum given by caller

	float f_tile_left = f_left + f_width * n_tile_x / n_total_width;
	float f_tile_right = f_tile_left + f_width * n_tile_width / n_total_width;
	float f_tile_bottom = f_bottom + f_height * n_tile_y / n_total_height;
	float f_tile_top = f_tile_bottom + f_height * n_tile_height / n_total_height;
	// calculate tile frustum

	glOrtho(f_tile_left, f_tile_right, f_tile_bottom, f_tile_top, f_near, f_far);
	// set frustum
}

/*
 *	static void CGLTransform::JitterPerspective(float f_fov, float f_aspect, float f_near,
 *		float f_far, float f_jitter_x, float f_jitter_y, int n_width, int n_height)
 *		- alias for gluPerspective, with additional parameters to support jittering
 *		- f_fov is field-of-view in degrees, f_aspect is aspect (viewport height / width),
 *		  f_near and f_far are depths of near and far clipping planes respectively
 *		- f_jitter_x and f_jitter_y are sub-pixel positions (in scale [0, 1]) and
 *		  n_width, n_height is viewport size in pixels
 *		- note this requires glMatrixMode(GL_PROJECTION);
 */
void CGLTransform::JitterPerspective(float f_fov, float f_aspect, float f_near, float f_far,
	float f_jitter_x, float f_jitter_y, int n_width, int n_height)
{
	float f_w, f_h;

	f_h = float(tan(f_fov * f_pi / 180 * .5f)) * f_near;
	f_w = f_h * f_aspect;
	// calc half width of frustum in x-y plane at z = f_near

	float f_dx = f_jitter_x * 2 * f_h / n_width;
	float f_dy = f_jitter_y * 2 * f_w / n_height;

	glFrustum(-f_w + f_dx, f_w + f_dx, -f_h + f_dy, f_h + f_dy, f_near, f_far);
	// set frustum
}

/*
 *	static void CGLTransform::LookAt(Vector3f v_eye, Vector3f v_target, Vector3f v_up)
 *		- alias for gluLookAt
 *		- v_eye is desired eye position, v_target is center of focus and v_up
 *		  is upside direction (vectors aren't required to be normalized)
 *		- note this requires glMatrixMode(GL_MODELVIEW);
 */
void CGLTransform::LookAt(Vector3f v_eye, Vector3f v_target, Vector3f v_up)
{
	Vector3f v_dir(v_target - v_eye);
	v_dir.Normalize();
	Vector3f v_right(v_dir.v_Cross(v_up));
	v_right.Normalize();
	v_up = v_right.v_Cross(v_dir);
	// calculate complete perpendicular coord frame

	float p_matrix[4][4];
	for(int i = 0; i < 3; ++ i)
		p_matrix[i][0] = v_right[i];
	for(int i = 0; i < 3; ++ i)
		p_matrix[i][1] = v_up[i];
	for(int i = 0; i < 3; ++ i)
		p_matrix[i][2] = -v_dir[i];
	for(int i = 0; i < 3; ++ i) {
		p_matrix[i][3] = 0;
		p_matrix[3][i] = 0;
	}
	p_matrix[3][3] = 1;
	// copy it to matrix

	glMultMatrixf(&p_matrix[0][0]);
	glTranslatef(-v_eye.x, -v_eye.y, -v_eye.z);
	// apply transformation
}

/*
 *	static void CGLTransform::Get_Projection(Matrix4f &r_dest)
 *		- copies projection matrix to r_dest
 */
void CGLTransform::Get_Projection(Matrix4f &r_dest)
{
	glGetFloatv(GL_PROJECTION_MATRIX, &r_dest[0][0]);
}

/*
 *	static void CGLTransform::Get_Modelview(Matrix4f &r_dest)
 *		- copies modelview matrix to r_dest
 */
void CGLTransform::Get_Modelview(Matrix4f &r_dest)
{
	glGetFloatv(GL_MODELVIEW_MATRIX, &r_dest[0][0]);
}

/*
 *	static void CGLTransform::Calc_ViewRay(
 *		const Matrix4f &r_modelview_projection_inverse_transpose,
 *		Vector2f v_point, Vector3f &r_v_org, Vector3f &r_v_dir)
 *		- calculates worldspace ray under position v_point (in normalized [-1, 1]
 *		  OpenGL screen-space coordinates), useful for object selection using raytracing
 *		- r_modelview_projection_inverse_transpose is modelview projection
 *		  inverse transpose matrix (use (t_modelview * t_projection).t_FullInverse())
 *		- r_v_org and r_v_dir are ray origin and direction respectively (function outputs)
 *		- note ray direction doesn't come out normalized
 */
void CGLTransform::Calc_ViewRay(const Matrix4f &r_modelview_projection_inverse_transpose,
	Vector2f v_screen_point, Vector3f &r_v_org, Vector3f &r_v_dir)
{
	Vector4f v_far4 = r_modelview_projection_inverse_transpose *
		Vector4f(v_screen_point.x, v_screen_point.y, 1, 1);
	Vector4f v_near4 = r_modelview_projection_inverse_transpose *
		Vector4f(v_screen_point.x, v_screen_point.y, -1, 1);
	// z needs to be normalized to [-1, 1] range, just as x and y are

	Vector3f v_far = v_far4.v_xyz() / v_far4.w;
	Vector3f v_near = v_near4.v_xyz() / v_near4.w;
	// dehomogenize

	r_v_org = v_near;
	r_v_dir = v_far - v_near;
	// output
}

static inline float f_sign(float f_x)
{
	if(f_x > 0)
		return 1;
	else if(f_x < 0)
		return -1;
	return 0;
}

/*
 *	static void CGLTransform::ObliqueClipping(const Plane3f &r_t_plane,
 *		const Matrix4f &r_t_modelview, const Matrix4f &r_t_projection)
 *		- modifies projection matrix for oblique clipping by plane r_t_plane
 *		  (this is advantageous because it's for free, while enabling user
 *		  clip-plane employs entire texturing unit on older cards)
 *		- r_t_modelview and r_t_projection are modelview and projection matrices
 *		- note this code is combination of code from NVIDIA OpenGL SDK 9.5
 *		  oblique clipping example and code from Game Programming Gems 5.
 *		  NVIDIA code is somewhat more complicated (slower) and tends to use
 *		  narrow depth range (therefore lower depth-test precission).
 *		  gems code is fast and uses wide depth range, but it can be used
 *		  to shear front clipping plane only and therefore works for cases
 *		  when camera is on the negative side of clipping plane only.
 *		- note this requires glMatrixMode(GL_PROJECTION);
 *		- note oblique clipping changes fragment depth values so it can't
 *		  be enabled and disabled trough drawing the scene because clipped
 *		  geometry would be offset in depth from the rest of the scene
 *		  yielding faulty depth-test results. but it can be used for
 *		  rendering reflection / refraction textures, etc.
 */
void CGLTransform::ObliqueClipping(const Plane3f &r_t_plane,
	const Matrix4f &r_t_modelview, const Matrix4f &r_t_projection)
{
	Vector4f v_clip_plane = Vector4f(r_t_plane.v_normal.x,
		r_t_plane.v_normal.y, r_t_plane.v_normal.z, r_t_plane.f_dist);
	// convert to Vector4f first

	Vector4f v_eye_clip_plane = /*-*/(r_t_modelview.t_InverseTranspose() * v_clip_plane); // checked, seems to work
	// transform clip plane to eye-space

	// see if camera is in front
	if(v_eye_clip_plane.w > 0) {
		// it is - use NVsdk code

		v_clip_plane /= v_clip_plane.v_xyz().f_Length();
		// normalize clip plane

		Quatf t_rotation;
		t_rotation.Align(Vector3f(0, 0, -1), v_clip_plane.v_xyz());
		Matrix4f t_plane = t_rotation.t_ToMatrix4();
		t_plane.Translate(Vector3f(0, 0, v_clip_plane.w));
		// calculate transformation of near clip plane (0, 0, -1, 0) to desired plane

		Matrix4f t_inverse_mvp(-(r_t_projection *
			(r_t_modelview * t_plane)).t_InverseTranspose()); // checked, seems to work
		// inverse (not transpose) modelview projection matrix
		// (t_FullInverse() calculates inverse-transpose, t_Transpose() transposes back)

		Vector4f v_cplane(t_inverse_mvp * Vector4f(0, 0, 1, 0));
		if(int(v_cplane.z))
			v_cplane /= float(abs(int(v_cplane.z)));
		v_cplane.w -= 1;
		if(v_cplane.z < 0)
			 v_cplane *= -1;
		Matrix4f t_suffix;
		t_suffix.Identity();
		for(int i = 0; i < 4; ++ i)
			t_suffix[i][2] = v_cplane[i];
		// calculate skew transformation for z

		Matrix4f t_new_p(t_suffix * r_t_projection);
		// modify current projection matrix

		glLoadMatrixf(&t_new_p[0][0]);
		// load new projection matrix with skewed front plane
	} else {
		// it is not, use game programming gems code

		Vector4f v_corner;
		v_corner.x = (f_sign(v_eye_clip_plane.x) +
			r_t_projection[2][0]) / r_t_projection[0][0];
		v_corner.y = (f_sign(v_eye_clip_plane.y) +
			r_t_projection[2][1]) / r_t_projection[1][1];
		v_corner.z = -1;
		v_corner.w = (1 + r_t_projection[2][2]) / r_t_projection[3][2];

		Vector4f v_cplane = v_eye_clip_plane * (2 / v_eye_clip_plane.f_Dot(v_corner));
		v_cplane.z += 1;
		// calculate scaled plane vector

		Matrix4f t_new_p(r_t_projection);
		for(int i = 0; i < 4; ++ i)
			t_new_p[i][2] = v_cplane[i];
		// modify current projection matrix

		glLoadMatrixf(&t_new_p[0][0]);
		// load new projection matrix with skewed front plane
	}
}

/*
 *	static void CGLTransform::Mirror(const Plane3f &r_t_plane)
 *		- mirrors camera arround r_t_plane (needs to be normalized)
 *		- note this requires glMatrixMode(GL_MODELVIEW);
 */
void CGLTransform::Mirror(const Plane3f &r_t_plane)
{
	const float nx = r_t_plane.v_normal.x, ny = r_t_plane.v_normal.y,
		nz = r_t_plane.v_normal.z, d = r_t_plane.f_dist;
	/*float p_mirror_matrix[4][4] = {
		{1-2 * nx * nx, -2 * nx * ny, -2 * nx * nz, -2 * nx * d},
		{-2 * ny * nx, 1-2 * ny * ny, -2 * ny * nz, -2 * ny * d},
		{-2 * nz * nx, -2 * nz * ny, 1-2 * nz * nz, -2 * nz * d},
		{           0,            0,            0,           1}
	};*/
	float p_mirror_matrix[4][4] = {
		{1-2 * nx * nx, -2 * ny * nx, -2 * nz * nx, 0},
		{-2 * nx * ny, 1-2 * ny * ny, -2 * nz * ny, 0},
		{-2 * nx * nz, -2 * ny * nz, 1-2 * nz * nz, 0},
		{-2 * nx * d, -2 * ny * d, -2 * nz * d, 1}
	};
	// calculate mirroring matrix

	glMultMatrixf(&p_mirror_matrix[0][0]);
	// apply mirroring matrix
}

Vector3f CGLTransform::v_UnTransform(const Matrix4f &r_modelview_projection_inverse_transpose,
	Vector2f v_screen_point, float f_z)
{
	Vector4f v_pt4 = r_modelview_projection_inverse_transpose *
		Vector4f(v_screen_point.x, v_screen_point.y, f_z, 1);
	return v_pt4.v_xyz() / v_pt4.w;
}

Vector3f CGLTransform::v_Transform(const Matrix4f &r_modelview_projection,
	Vector3f v_world_point)
{
	Vector4f v_pt4 = r_modelview_projection *
		Vector4f(v_world_point.x, v_world_point.y, v_world_point.z, 1);
	return v_pt4.v_xyz() / v_pt4.w;
}

/*
 *								=== ~CGLTransform ===
 */
