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

#pragma once
#ifndef __OPENGL_TRANSFORM_UTILITIES_INCLUDED
#define __OPENGL_TRANSFORM_UTILITIES_INCLUDED

/**
 *	@file gl2/Transform.h
 *	@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)
 *
 *	@date 2012-06-19
 *
 *	Moved multiple inclusion guard before file documentation comment.
 *
 */

#include "../Vector.h"

/**
 *	@brief implements misc transformation-related utilities for OpenGL
 */
class CGLTransform {
public:
	/**
	 *	@brief alias for gluPerspective
	 *
	 *	Calculates perspective projection matrix.
	 *
	 *	@param[in] f_fov is field-of-view in degrees
	 *	@param[in] f_aspect is aspect (viewport height / width)
	 *	@param[in] f_near is depth of near clip plane
	 *	@param[in] f_far is depth of far clip plane
	 *
	 *	@note This requires <tt>glMatrixMode(GL_PROJECTION);</tt>
	 */
	static void Perspective(float f_fov, float f_aspect, float f_near, float f_far);

	/**
	 *	@brief calculates perspective projection matrix for tiled rendering
	 *
	 *	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).
	 *
	 *		Tiles don't have to be square or power-of-two. and 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).
	 *
	 *		Example use:
	 *<code>		const int n_tile_size = 512;
	 *		const int n_target_width = 4000;
	 *		const int n_target_height = 3000;
	 *
	 *		for(int y = 0; y < n_target_height; y += n_tile_size) {
	 *			for(int x = 0; x < n_target_width; x += n_tile_size) {
	 *				glMatrixMode(GL_PROJECTION);
	 *				glLoadIdentity();
	 *				PerspectiveTile(n_target_width, n_target_height, x, y, n_tile_size, n_tile_size,
	 *					90, float(n_target_width) / n_target_height, .1f, 10000);
	 *				RenderScene();
	 *			}
	 *		}</code>
	 *
	 *	@todo See if example use code fragment works correctly.
	 *
	 *	@param[in] n_total_width is target image width (for example 4000)
	 *	@param[in] n_total_height is target image height (for example 3000)
	 *	@param[in] n_tile_x is horizontal offset of tile in target image
	 *	@param[in] n_tile_y is vertical offset of tile in target image
	 *	@param[in] n_tile_width is tile width (for example 512)
	 *	@param[in] n_tile_height is tile height (for example 512)
	 *	@param[in] f_fov is field-of-view in degrees
	 *	@param[in] f_aspect is aspect (viewport height / width)
	 *	@param[in] f_near is depth of near clip plane
	 *	@param[in] f_far is depth of far clip plane
	 *
	 *	@note n_tile_width, n_tile_height should be constant size, even if
	 *		<tt>n_tile_x + n_tile_width > n_total_width</tt> (or <tt>n_tile_y +
	 *		n_tile_height > n_total_height</tt>, respectively).
	 *	@note This requires <tt>glMatrixMode(GL_PROJECTION);</tt>
	 */
	static void 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);

	/**
	 *	@brief calculates ortoghraphic projection matrix for tiled rendering
	 *
	 *	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).
	 *		This is intended for 2D / GUI rendering in tiled images.
	 *
	 *		Tiles don't have to be square or power-of-two. and 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).
	 *
	 *		Example use:
	 *<code>		const int n_tile_size = 512;
	 *		const int n_target_width = 4000;
	 *		const int n_target_height = 3000;
	 *
	 *		for(int y = 0; y < n_target_height; y += n_tile_size) {
	 *			for(int x = 0; x < n_target_width; x += n_tile_size) {
	 *				glMatrixMode(GL_PROJECTION);
	 *				glLoadIdentity();
	 *				OrthoTile(n_target_width, n_target_height, x, y,
	 *					n_tile_size, n_tile_size, -1, 1, -1, 1, .1f, 10000);
	 *				RenderScene();
	 *			}
	 *		}</code>
	 *
	 *	@todo See if example use code fragment works correctly.
	 *
	 *	@param[in] n_total_width is target image width (for example 4000)
	 *	@param[in] n_total_height is target image height (for example 3000)
	 *	@param[in] n_tile_x is horizontal offset of tile in target image
	 *	@param[in] n_tile_y is vertical offset of tile in target image
	 *	@param[in] n_tile_width is tile width (for example 512)
	 *	@param[in] n_tile_height is tile height (for example 512)
	 *	@param[in] f_left is position of left clip-plane
	 *	@param[in] f_right is position of right clip-plane
	 *	@param[in] f_bottom is position of bottom clip-plane
	 *	@param[in] f_top is position of top clip-plane
	 *	@param[in] f_near is depth of near clip plane
	 *	@param[in] f_far is depth of far clip plane
	 *
	 *	@note n_tile_width, n_tile_height should be constant size, even if
	 *		<tt>n_tile_x + n_tile_width > n_total_width</tt> (or <tt>n_tile_y +
	 *		n_tile_height > n_total_height</tt>, respectively).
	 *	@note This requires <tt>glMatrixMode(GL_PROJECTION);</tt>
	 */
	static void 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);

	/**
	 *	@brief alias for gluPerspective
	 *
	 *	Alias for gluPerspective, with additional parameters to support jittering.
	 *		This, along with accumulation buffer is used to render antialiassed
	 *		images. The image is rendered multiple times, each time with different
	 *		sub-pixel offset and all passes are averaged. Number of passes is
	 *		arbitrary, more means higher quality, but lower speed rendering.
	 *		Maximal number of passes is limited by bit-width of accumulation buffer.
	 *
	 *	@param[in] f_fov is field-of-view in degrees
	 *	@param[in] f_aspect is aspect (viewport height / width)
	 *	@param[in] f_near is depth of near clip plane
	 *	@param[in] f_far is depth of far clip plane
	 *	@param[in] f_jitter_x is sub-pixel samples horizntal offset (0 - 1)
	 *	@param[in] f_jitter_y is sub-pixel samples vertical offset (0 - 1)
	 *	@param[in] n_width is viewport width
	 *	@param[in] n_height is viewport height
	 *
	 *	@note This requires <tt>glMatrixMode(GL_PROJECTION);</tt>
	 *	@note Accumulation buffer is deprecated OpenGL feature, antialiassing
	 *		is implemented more effectevely in hardware.
	 */
	static void 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);

	/**
	 *	@brief alias for gluPerspective
	 *
	 *	Alias for gluPerspective, with additional parameters to support jittering.
	 *		This, along with accumulation buffer is used to render antialiassed
	 *		images. The image is rendered multiple times, each time with different
	 *		sub-pixel offset and all passes are averaged. Number of passes is
	 *		arbitrary, more means higher quality, but lower speed rendering.
	 *		Maximal number of passes is limited by bit-width of accumulation buffer.
	 *
	 *	@param[in] f_fov is field-of-view in degrees
	 *	@param[in] f_aspect is aspect (viewport height / width)
	 *	@param[in] f_near is depth of near clip plane
	 *	@param[in] f_far is depth of far clip plane
	 *	@param[in] f_jitter_x is sub-pixel samples horizntal offset (0 - 1)
	 *	@param[in] f_jitter_y is sub-pixel samples vertical offset (0 - 1)
	 *
	 *	@note This requires <tt>glMatrixMode(GL_PROJECTION);</tt>
	 *	@note Accumulation buffer is deprecated OpenGL feature, antialiassing
	 *		is implemented more effectevely in hardware.
	 *	@note This version calls glGetIntegerv(GL_VIEWPORT, p_wp) to determine viewport size.
	 */
	static inline void JitterPerspective(float f_fov, float f_aspect, float f_near, float f_far,
		float f_jitter_x, float f_jitter_y)
	{
		int p_viewport[4];
		glGetIntegerv(GL_VIEWPORT, p_viewport);
		JitterPerspective(f_fov, f_aspect, f_near, f_far,
			f_jitter_x, f_jitter_y, p_viewport[2], p_viewport[3]);
	}

	/**
	 *	alias for gluLookAt
	 *
	 *	Calculates matrix of camera placed at f_eye_[xyz], looking towards f_target_[xyz],
	 *		while f_up_[xyz] is collinear with (0, 1) when projected using the camera matrix.
	 *		Modelview is multiplied by this matrix.
	 *
	 *	@param[in] f_eye_x is x-position of the eye (camera)
	 *	@param[in] f_eye_y is y-position of the eye (camera)
	 *	@param[in] f_eye_z is z-position of the eye (camera)
	 *	@param[in] f_target_x is x-position of the eye (camera) target
	 *	@param[in] f_target_y is y-position of the eye (camera) target
	 *	@param[in] f_target_z is z-position of the eye (camera) target
	 *	@param[in] f_up_x is x-direction of up-vector
	 *	@param[in] f_up_y is y-direction of up-vector
	 *	@param[in] f_up_z is z-direction of up-vector
	 *
	 *	@note This doesn't work when up-vector is colinear with view vector.
	 *	@note This calls Vector3f version of LookAt().
	 *	@note This requires <tt>glMatrixMode(GL_MODELVIEW);</tt>
	 */
	static inline void LookAt(float f_eye_x, float f_eye_y, float f_eye_z,
		float f_target_x, float f_target_y, float f_target_z,
		float f_up_x, float f_up_y, float f_up_z)
	{
		LookAt(Vector3f(f_eye_x, f_eye_y, f_eye_z),
			   Vector3f(f_target_x, f_target_y, f_target_z),
			   Vector3f(f_up_x, f_up_y, f_up_z));
	}

	/**
	 *	alias for gluLookAt
	 *
	 *	Calculates matrix of camera placed at v_eye, looking towards v_target,
	 *		while v_up is collinear with (0, 1) when projected using the camera matrix.
	 *		Modelview is multiplied by this matrix.
	 *
	 *	@param[in] v_eye is position of the eye (camera)
	 *	@param[in] v_target is position of the eye (camera) target
	 *	@param[in] v_up is direction of up-vector
	 *
	 *	@note This doesn't work when up-vector is colinear with view vector.
	 *	@note This requires <tt>glMatrixMode(GL_MODELVIEW);</tt>
	 */
	static void LookAt(Vector3f v_eye, Vector3f v_target, Vector3f v_up);

	/**
	 *	@brief gets projection matrix
	 *
	 *	Copies projection matrix to r_dest.
	 *
	 *	@param[out] r_dest is destination matrix value of GL_PROJECTION is copied to
	 *
	 *	@note This doesn't affect current matrix mode.
	 */
	static void Get_Projection(Matrix4f &r_dest);

	/**
	 *	@brief gets modelview matrix
	 *
	 *	Copies modelview matrix to r_dest.
	 *
	 *	@param[out] r_dest is destination matrix value of GL_MODELVIEW is copied to
	 *
	 *	@note This doesn't affect current matrix mode.
	 */
	static void Get_Modelview(Matrix4f &r_dest);

	/**
	 *	@brief calculates worldspace ray for given pixel under given projection
	 *
	 *	Calculates worldspace ray under position v_point (in normalized [-1, 1] OpenGL
	 *		screen-space coordinates), useful for object selection using raytracing,
	 *		or for raytracing parts of picture, coherent with rasterized image.
	 *
	 *	@param[in] r_modelview_projection_inverse_transpose is modelview projection
	 *		inverse transpose matrix (use (t_modelview * t_projection).t_FullInverse())
	 *	@param[in] v_screen_point is point in normalized [-1, 1] OpenGL screen-space coordinates
	 *	@param[out] r_v_org is ray origin
	 *	@param[out] r_v_dir is ray direction (not normalized)
	 *
	 *	@note Ray direction doesn't come out normalized.
	 */
	static void Calc_ViewRay(const Matrix4f &r_modelview_projection_inverse_transpose,
		Vector2f v_screen_point, Vector3f &r_v_org, Vector3f &r_v_dir);

	/**
	 *	@brief sets up oblique clipping
	 *
	 *	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).
	 *
	 *		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.
	 *
	 *	param[in] r_t_plane is clipping plane to be set up
	 *	param[in] r_t_modelview is current modelview matrix
	 *	param[in] r_t_projection is current projection matrix
	 *	
	 *	@note This requires <tt>glMatrixMode(GL_PROJECTION);</tt>
	 *	@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 compared to the rest of the scene,
	 *		  yielding faulty depth-test results. But it can be used for
	 *		  rendering reflection / refraction textures, etc.
	 *
	 *	@todo See how is this done in OpenGL 3.0 class hardware.
	 */
	static void ObliqueClipping(const Plane3f &r_t_plane,
		const Matrix4f &r_t_modelview, const Matrix4f &r_t_projection);

	/**
	 *	@brief sets up oblique clipping
	 *
	 *	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).
	 *
	 *		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.
	 *
	 *	param[in] r_t_plane is clipping plane to be set up
	 *
	 *	@note This version reads-back current modelview and projection matrices.
	 *	@note This requires <tt>glMatrixMode(GL_PROJECTION);</tt>
	 *	@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 compared to the rest of the scene,
	 *		  yielding faulty depth-test results. But it can be used for
	 *		  rendering reflection / refraction textures, etc.
	 *
	 *	@todo See how is this done in OpenGL 3.0 class hardware.
	 */
	static inline void ObliqueClipping(const Plane3f &r_t_plane)
	{
		Matrix4f t_modelview, t_projection;
		CGLTransform::Get_Modelview(t_modelview);
		CGLTransform::Get_Projection(t_projection);
		// get modelview, projection

		ObliqueClipping(r_t_plane, t_modelview, t_projection);
	}

	/**
	 *	@brief mirrors camera arround plane
	 *
	 *	Mirrors camera arround r_t_plane (needs to be normalized).
	 *		This is used to render mirrors, it is also required to
	 *		set proper clipping plane, so the geometry stays "behind
	 *		the mirror".
	 *
	 *	param[in] r_t_plane is mirror plane
	 *
	 *	@note This requires <tt>glMatrixMode(GL_MODELVIEW);</tt>
	 */
	static void Mirror(const Plane3f &r_t_plane);

	/**
	 *	@brief calculates screenspace to worldspace transformation
	 *
	 *	@param[in] r_modelview_projection_inverse_transpose is modelview projection
	 *		inverse transpose matrix (use (t_modelview * t_projection).t_FullInverse())
	 *	@param[in] v_screen_point is point in normalized [-1, 1] OpenGL screen-space coordinates
	 *	@param[in] f_z is (normalized) screen point depth (-1 = point at near plane)
	 *
	 *	@return Returns worldspace point, corresponding to given screenspace coordinates.
	 */
	static Vector3f v_UnTransform(const Matrix4f &r_modelview_projection_inverse_transpose,
		Vector2f v_screen_point, float f_z = -1);

	/**
	 *	@brief calculates worldspace to screenspace transformation
	 *
	 *	@param[in] r_modelview_projection is modelview-projection matrix
	 *	@param[in] v_world_point is point in world-space coordinates
	 *
	 *	@return Returns screenspace point, corresponding to worldspace point.
	 */
	static Vector3f v_Transform(const Matrix4f &r_modelview_projection,
		Vector3f v_world_point);
};

#endif // !__OPENGL_TRANSFORM_UTILITIES_INCLUDED
