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

#pragma once
#ifndef __OPENGL_TRANSFORM_UTILITIES_INCLUDED
#define __OPENGL_TRANSFORM_UTILITIES_INCLUDED

/**
 *	@file dev/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 "../CallStack.h"
#include "../Vector.h"

/*
 *	class CGLTransform
 *		- misc transformation-related utilities for OpenGL
 */
class CGLTransform {
public:
	/*
	 *	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);
	 */
	static void Perspective(float f_fov, float f_aspect, float f_near, float f_far);

	/*
	 *	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;
	 *		  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_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);
	 */
	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);

	/*
	 *	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);
	 */
	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);

	/*
	 *	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);
	 */
	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);

	/*
	 *	static inline void CGLTransform::JitterPerspective(float f_fov, float f_aspect,
	 *		float f_near, float f_far, float f_jitter_x, float f_jitter_y)
	 *		- 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])
	 *		- note this requires glMatrixMode(GL_PROJECTION);
	 *		- 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]);
	}
	
	/*
	 *	static inline void CGLTransform::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)
	 *		- alias for gluLookAt
	 *		- f_eye_[xyz] is desired eye position, f_target_[xyz] is center of focus
	 *		  and f_up_[xyz] is upside direction (vectors aren't required to be normalized)
	 *		- note this requires glMatrixMode(GL_MODELVIEW);
	 */
	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));
	}

	/*
	 *	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);
	 */
	static void LookAt(Vector3f v_eye, Vector3f v_target, Vector3f v_up);

	/*
	 *	static void CGLTransform::Get_Projection(Matrix4f &r_dest)
	 *		- copies projection matrix to r_dest
	 */
	static void Get_Projection(Matrix4f &r_dest);

	/*
	 *	static void CGLTransform::Get_Modelview(Matrix4f &r_dest)
	 *		- copies modelview matrix to r_dest
	 */
	static void Get_Modelview(Matrix4f &r_dest);

	/*
	 *	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
	 */
	static void Calc_ViewRay(const Matrix4f &r_modelview_projection_inverse_transpose,
		Vector2f v_screen_point, Vector3f &r_v_org, Vector3f &r_v_dir);

	/*
	 *	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.
	 */
	static void ObliqueClipping(const Plane3f &r_t_plane,
		const Matrix4f &r_t_modelview, const Matrix4f &r_t_projection);

	/*
	 *	static inline void CGLTransform::ObliqueClipping(const Plane3f &r_t_plane)
	 *		- 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)
	 *		- modelview and projection matrices are determined using glGetFloatv()
	 *		- 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.
	 */
	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);
	}

	/*
	 *	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);
	 */
	static void Mirror(const Plane3f &r_t_plane);

	static Vector3f v_Transform(const Matrix4f &r_modelview_projection_inverse_transpose,
		Vector2f v_screen_point, float f_z = -1)
	{
		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;
	}

	static Vector3f v_Untransform(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;
	}
};

#endif // __OPENGL_TRANSFORM_UTILITIES_INCLUDED
