/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#ifndef plViewTransform_inc
#define plViewTransform_inc

#include "hsMatrix44.h"
#include "hsGeometry3.h"
#include "hsPoint2.h"

class hsBounds3;
class hsStream;

// There's a lot here, but there's a lot one might want to do with view transforms.
// It's easiest to grab the structure thinking of it in terms of the different
// spaces you might want a point in. The ones supported here are:
//		Screen - this is actual pixel values
//		NDC - Normalized Device Coordinates, these are post W divide, so the
//			valid range is x = [-1..1], y = [-1..1], z = [0..1]
//		Camera - relative to the camera, with (0,0,-1) directly in front of the camera,
//			and (0, 1, 0) directly above the camera.
//		World - Universal world space.
//		Map - arbitrary mapping of NDC. Like from [(-1,-1,0)..(1,1,1)] => [(0,0,0)..(1,1,1)] (default).
//	Note that there is no object space here. There could be, but I wanted something more constant, more
//		world independent, so the ViewTransform remains constant unless the view changes. Whatever.
//
//	So we're broken into functional sections:
//	1) Queries on the state of this view transform, properties, matrix values, whatever. Note that you
//		generally shouldn't be reading a value (e.g. matrix) out of the ViewTransform, but let the
//		ViewTransform perform the operation you would with the matrix.
//	2) Setting state, properties, matrix values, whatever. There's a couple of really bizarre variants
//		(like the union and intersection of view frustums). Full support is available for perspective
//		or orthogonal views. An additional capability (not necessary) is offset transforms, useful
//		for rendering textures. If you don't what good they are, they probably aren't any good to you.
//	3) Conversions of points from one space to another. You may notice that there are a whole lot of them.
//		There is a conversion from each of the spaces above to each of the other spaces. That's 12
//		transformations right there. But Points and Vectors actually transform differently, so there
//		are different versions for those. Where they could be treated the same, there is an hsScalarTriple
//		version that does the actual work, and then casting versions to come and go from the right type.
//	4) Read and write (note these are NOT virtual).
//
//	More Notes:
//		This class has no virtual functions.
//		You must set the width and height for Screen queries to work (duh!).
//		ViewPort defaults to cover the entire width and height. Viewport only affects mapping, not clipping
//			(i.e. reducing the viewport width will still render the same stuff, just skinnier).
//		The actual data here is very small, this is mostly a collection of functions, so where possible,
//			just keep one of these to pass around, (e.g. rather than keeping track of FOV etc and passing
//			those around).
//
class plViewTransform
{
public:
	plViewTransform();
	~plViewTransform() {}

	void				Reset(); // resets to default state

	// Queries
	hsBool				GetOrthogonal() const { return IHasFlag(kOrthogonal); }
	hsBool				GetPerspective() const { return !GetOrthogonal(); }
	hsBool				GetViewPortRelative() const { return IHasFlag(kViewPortRelative); }

	// Next, all our matrices.
	const hsMatrix44&	GetCameraToWorld() const { return fCameraToWorld; }
	const hsMatrix44&	GetWorldToCamera() const { return fWorldToCamera; }
	const hsMatrix44&	GetCameraToNDC() const { return ICheckCameraToNDC(); }
	const hsMatrix44&	GetWorldToNDC() const { return ICheckWorldToNDC(); }

	hsPoint3			GetPosition() const { return GetCameraToWorld().GetTranslate(); }
	hsVector3			GetDirection() const { return *((hsVector3 *)&GetWorldToCamera().fMap[2]); }
	hsVector3			GetUp() const { return *((hsVector3*)&GetWorldToCamera().fMap[1]); }
	hsVector3			GetAcross() const { return *((hsVector3*)&GetWorldToCamera().fMap[0]); }

	UInt16				GetScreenWidth() const { return fWidth; }
	UInt16				GetScreenHeight() const { return fHeight; }

	void				GetViewPort(hsPoint2& mins, hsPoint2& maxs) const;
	void				GetViewPort(int& loX, int& loY, int& hiX, int& hiY) const;
	int					GetViewPortWidth() const { return GetViewPortRight() - GetViewPortLeft(); }
	int					GetViewPortHeight() const { return GetViewPortBottom() - GetViewPortTop(); }
	int					GetViewPortLeft() const { return int(GetViewPortLoX()); }
	int					GetViewPortTop() const { return int(GetViewPortLoY()); }
	int					GetViewPortRight() const { return int(GetViewPortHiX()); }
	int					GetViewPortBottom() const { return int(GetViewPortHiY()); }
	float				GetViewPortLoX() const { return GetViewPortRelative() ? fViewPortX.fX * fWidth : fViewPortX.fX; }
	float				GetViewPortLoY() const { return GetViewPortRelative() ? fViewPortY.fX * fHeight : fViewPortY.fX; }
	float				GetViewPortHiX() const { return GetViewPortRelative() ? fViewPortX.fY * fWidth : fViewPortX.fY; }
	float				GetViewPortHiY() const { return GetViewPortRelative() ? fViewPortY.fY * fHeight : fViewPortY.fY; }

	hsPoint3			GetMapMin() const { return fMapMin; }
	hsPoint3			GetMapMax() const { return fMapMax; }
	void				GetMapping(hsPoint3& mapMin, hsPoint3& mapMax) const { mapMin = fMapMin; mapMax = fMapMax; }

	hsScalar			GetFovX() const;
	hsScalar			GetFovY() const;
	hsScalar			GetFovXDeg() const { return hsScalarRadToDeg(GetFovX()); }
	hsScalar			GetFovYDeg() const { return hsScalarRadToDeg(GetFovY()); }
	hsScalar			GetOrthoWidth() const { return fMax.fX - fMin.fX; }
	hsScalar			GetOrthoHeight() const { return fMax.fY - fMin.fY; }
	hsScalar			GetHither() const { return fMin.fZ; }
	hsScalar			GetYon() const { return fMax.fZ; }
	void				GetDepth(hsScalar& hither, hsScalar& yon) const { hither = GetHither(); yon = GetYon(); }

	// Setup.
	// First, our world to camera and back again.
	void				SetCameraTransform(const hsMatrix44& w2c, const hsMatrix44& c2w) { fWorldToCamera = w2c; fCameraToWorld = c2w; ISetFlag(kWorldToNDCSet, false); }

	// Next, what kind of projection.
	void				SetOrthogonal(hsBool on) { ISetFlag(kOrthogonal, on); InvalidateTransforms(); }
	void				SetPerspective(hsBool on) { SetOrthogonal(!on); }

	// Next, setting the scree/window/rendertarget size
	void				SetWidth(UInt16 w) { fWidth = w; }
	void				SetHeight(UInt16 h) { fHeight = h; }
	void				SetScreenSize(UInt16 w, UInt16 h) { SetWidth(w); SetHeight(h); }

	// Next, setting the viewport. You only need to do this if you want to use the screen functions above.
	// If you're passing in and getting out normalized device coordinates, skip this. If you don't set viewport,
	// Defaults to 0,0,width,height (i.e. the whole screen).
	void				SetViewPort(const hsPoint2& mins, const hsPoint2& maxs, hsBool relative=true);
	void				SetViewPort(float loX, float loY, float hiX, float hiY, hsBool relative=true) { SetViewPort(hsPoint2().Set(loX, loY), hsPoint2().Set(hiX, hiY), relative); }
	void				SetViewPort(UInt16 left, UInt16 top, UInt16 right, UInt16 bottom) { SetViewPort(hsScalar(left), hsScalar(top), hsScalar(right), hsScalar(bottom), false); }

	void				SetMapping(const hsPoint3& mins, const hsPoint3& maxs) { SetMapMin(mins); SetMapMax(maxs); }
	void				SetMapMin(const hsPoint3& mins) { fMapMin = mins; }
	void				SetMapMax(const hsPoint3& maxs) { fMapMax = maxs; }

	// Next, variants on setting up our projection matrix.
	// Depth is pretty uniform.
	void				SetDepth(hsScalar hither, hsScalar yon) { fMin.fZ = hither; fMax.fZ = yon; InvalidateTransforms(); }
	void				SetDepth(const hsPoint2& d) { SetDepth(d.fX, d.fY); }
	void				SetHither(hsScalar hither) { fMin.fZ = hither; InvalidateTransforms(); }
	void				SetYon(hsScalar yon) { fMax.fZ = yon; InvalidateTransforms(); }

	// Garden variety symmetric fov uses either of this first batch. Unless you're doing some funky projection, you don't even
	// need to look through the rest.
	// Degrees - all are full angles, < 180 degrees
	void				SetFovDeg(const hsPoint2& deg) { SetFovDeg(deg.fX, deg.fY); }
	void				SetFovDeg(hsScalar degX, hsScalar degY) { SetFovXDeg(degX); SetFovYDeg(degY); }
	void				SetFovXDeg(hsScalar deg) { SetFovX(hsScalarDegToRad(deg)); }
	void				SetFovYDeg(hsScalar deg) { SetFovY(hsScalarDegToRad(deg)); }

	// Radians - all are full angles, < PI
	void				SetFov(const hsPoint2& rad) { SetFov(rad.fX, rad.fY); }
	void				SetFov(hsScalar radX, hsScalar radY) { SetFovX(radX); SetFovY(radY); }
	void				SetFovX(hsScalar rad) { SetHalfWidth(hsTan(rad * 0.5f)); }
	void				SetFovY(hsScalar rad) { SetHalfHeight(hsTan(rad * 0.5f)); }

	// For orthogonal projection, don't call SetWidth(hsTan(fovRads)), because hsTan(f)/2 != hsTan(f/2)
	// For non-centered, call SetWidths/Heights() directly.
	void				SetWidth(hsScalar w) { SetHalfWidth(w * 0.5f); }
	void				SetHeight(hsScalar h) { SetHalfHeight(h * 0.5f); }

	// The rest do no interpretation, just stuff the values passed in.
	void				SetHalfWidth(hsScalar hw) { SetWidths(-hw, hw); }
	void				SetHalfHeight(hsScalar hh) { SetHeights(-hh, hh); }
	void				SetWidths(hsScalar minW, hsScalar maxW) { fMin.fX = minW; fMax.fX = maxW; InvalidateTransforms(); }
	void				SetHeights(hsScalar minH, hsScalar maxH) { fMin.fY = minH; fMax.fY = maxH; InvalidateTransforms(); }
	void				SetWidths(const hsPoint2& w) { SetWidths(w.fX, w.fY); }
	void				SetHeights(const hsPoint2& h) { SetHeights(h.fX, h.fY); }
	void				SetView(const hsPoint3& mins, const hsPoint3& maxs) { fMax = maxs; fMin = mins; InvalidateTransforms(); }

	// Take a CAMERA SPACE bounding box and sets up the projection to just surround it.
	// Note this doesn't swivel the camera around to see the box, it offsets the projection.
	// Return false if there isn't a projection that will capture any of the bnd. This
	// can be from the bnd being behind the camera.
	hsBool				SetProjection(const hsBounds3& cBnd);
	hsBool				SetProjectionWorld(const hsBounds3& wBnd);

	// This lets you create insane projection matrices. Note that it won't change the answer on anything like
	// GetFov().
	void				PreMultCameraToNDC(const hsMatrix44& m) { fCameraToNDC = m * GetCameraToNDC(); }
	void				PostMultCameraToNDC(const hsMatrix44& m) { fCameraToNDC = GetCameraToNDC() * m; }
	void				Recalc() { InvalidateTransforms(); }

	// These do the obvious, constructing a single view that encompasses either the intersection or union
	// of what the two views will see. The boolean is performed in axis aligned camera space, which lines
	// up nicely with screen space. Note that this only makes sense for two ViewTransforms with identical
	// CameraToWorld's (which isn't checked).
	hsBool				Intersect(const plViewTransform& view);
	hsBool				Union(const plViewTransform& view);

	// Convenience to move from one space to another.
	// Screen means pixels - Default is mapping NDC -> [0..1]. Z value of pixel is NDC Z.
	// NDC is the ([-1..1],[-1..1],[0..1]) Normalized device coordinates.
	// Camera is camera space.
	// World is world space.
	// Past that, you're on your own.
	hsScalarTriple		ScreenToNDC(const hsScalarTriple& scrP) const;
	hsScalarTriple		ScreenToCamera(const hsScalarTriple& scrP) const { return NDCToCamera(ScreenToNDC(scrP)); }

	hsPoint3			ScreenToNDC(const hsPoint3& scrP) const { return hsPoint3(ScreenToNDC(hsScalarTriple(scrP))); }
	hsPoint3			ScreenToCamera(const hsPoint3& scrP) const { return hsPoint3(ScreenToCamera(hsScalarTriple(scrP))); }
	hsPoint3			ScreenToWorld(const hsPoint3& scrP) const { return CameraToWorld(ScreenToCamera(scrP)); }

	hsVector3			ScreenToNDC(const hsVector3& scrV) const { return hsVector3(ScreenToNDC(hsScalarTriple(scrV))); }
	hsVector3			ScreenToCamera(const hsVector3& scrV) const { return hsVector3(ScreenToCamera(hsScalarTriple(scrV))); }
	hsVector3			ScreenToWorld(const hsVector3& scrV) const { return CameraToWorld(ScreenToCamera(scrV)); }

	hsScalarTriple		NDCToScreen(const hsScalarTriple& ndc) const;
	hsScalarTriple		NDCToCamera(const hsScalarTriple& ndc) const;

	hsPoint3			NDCToScreen(const hsPoint3& ndc) const { return hsPoint3(NDCToScreen(hsScalarTriple(ndc))); }
	hsPoint3			NDCToCamera(const hsPoint3& ndc) const { return hsPoint3(NDCToCamera(hsScalarTriple(ndc))); }
	hsPoint3			NDCToWorld(const hsPoint3& ndc) const { return CameraToWorld(NDCToCamera(ndc)); }

	hsVector3			NDCToScreen(const hsVector3& ndc) const { return hsVector3(NDCToScreen(hsScalarTriple(ndc))); }
	hsVector3			NDCToCamera(const hsVector3& ndc) const { return hsVector3(NDCToCamera(hsScalarTriple(ndc))); }
	hsVector3			NDCToWorld(const hsVector3& ndc) const { return CameraToWorld(NDCToCamera(ndc)); }

	hsScalarTriple		CameraToScreen(const hsScalarTriple& camP) const { return NDCToScreen(CameraToNDC(camP)); }
	hsScalarTriple		CameraToNDC(const hsScalarTriple& camP) const;

	hsPoint3			CameraToScreen(const hsPoint3& camP) const { return hsPoint3(CameraToScreen(hsScalarTriple(camP))); }
	hsPoint3			CameraToNDC(const hsPoint3& camP) const { return hsPoint3(CameraToNDC(hsScalarTriple(camP))); }
	hsPoint3			CameraToWorld(const hsPoint3& camP) const { return GetCameraToWorld() * camP; }

	hsVector3			CameraToScreen(const hsVector3& camP) const { return hsVector3(CameraToScreen(hsScalarTriple(camP))); }
	hsVector3			CameraToNDC(const hsVector3& camP) const { return hsVector3(CameraToNDC(hsScalarTriple(camP))); }
	hsVector3			CameraToWorld(const hsVector3& camV) const { return GetCameraToWorld() * camV; }

	hsPoint3			WorldToScreen(const hsPoint3& worldP) const { return (hsPoint3)CameraToScreen(WorldToCamera(worldP)); }
	hsPoint3			WorldToNDC(const hsPoint3& worldP) const { return CameraToNDC(WorldToCamera(worldP)); }
	hsPoint3			WorldToCamera(const hsPoint3& worldP) const { return GetWorldToCamera() * worldP; }

	hsVector3			WorldToScreen(const hsVector3& worldV) const { return (hsVector3)CameraToScreen(WorldToCamera(worldV)); }
	hsVector3			WorldToNDC(const hsVector3& worldP) const { return CameraToNDC(WorldToCamera(worldP)); }
	hsVector3			WorldToCamera(const hsVector3& worldV) const { return GetWorldToCamera() * worldV; }

	hsScalarTriple		NDCToMap(const hsScalarTriple& ndcP) const;
	hsScalarTriple		CameraToMap(const hsScalarTriple& camP) const { return NDCToMap(CameraToNDC(camP)); }

	hsPoint3			NDCToMap(const hsPoint3& ndcP) const { return hsPoint3(NDCToMap(hsScalarTriple(ndcP))); }
	hsPoint3			CameraToMap(const hsPoint3& camP) const { return hsPoint3(CameraToMap(hsScalarTriple(camP))); }
	hsPoint3			WorldToMap(const hsPoint3& worldP) const { return CameraToMap(WorldToCamera(worldP)); }

	hsVector3			NDCToMap(const hsVector3& ndcP) const { return hsVector3(NDCToMap(hsScalarTriple(ndcP))); }
	hsVector3			CameraToMap(const hsVector3& camP) const { return hsVector3(CameraToMap(hsScalarTriple(camP))); }
	hsVector3			WorldToMap(const hsVector3& worldP) const { return CameraToMap(WorldToCamera(worldP)); }

	void				Read(hsStream* s);
	void				Write(hsStream* s);

protected:
	enum
	{
		kNone				= 0x0,
		kOrthogonal			= 0x1,
		kSymmetric			= 0x2,
		kCameraToNDCSet		= 0x4,
		kWorldToNDCSet		= 0x8,
		kSetMask			= kCameraToNDCSet | kWorldToNDCSet,
		kViewPortRelative	= 0x10
	};

	mutable UInt32			fFlags;

	hsMatrix44				fCameraToWorld;
	hsMatrix44				fWorldToCamera;

	hsPoint3				fMin; // minTanX/X, minTanY/Y, hither
	hsPoint3				fMax; // maxTanX/X, maxTanY/Y, yon

	// Screen (or rendertarget) dimensions in pixels.
	UInt16					fWidth; 
	UInt16					fHeight;

	// Viewport can be stored as fraction of screen size, so the view transform's viewport
	// can be set up independent of the size of the window it's applied to. 
	hsPoint3		fViewPortX; // min, max, 1 / (max-min)
	hsPoint3		fViewPortY; // min, max, 1 / (max-min)

	// For arbitrary mapping (unconfined to pixel coords or NDC), just set what you want
	// to map to.
	hsPoint3		fMapMin;
	hsPoint3		fMapMax;

	// Some mutables. These are just the calculated from the above (e.g. fov, depth, perspective, etc).
	mutable hsMatrix44		fCameraToNDC;
	mutable hsMatrix44		fWorldToNDC;

	// Have to set a limit here on the smallest the hither plane can be.
	static const hsScalar	kMinHither;

	void				ISetCameraToNDC() const;
	hsBool				ICameraToNDCSet() const { return IHasFlag(kCameraToNDCSet); }
	const hsMatrix44&	ICheckCameraToNDC() const { if( !ICameraToNDCSet() ) ISetCameraToNDC(); return fCameraToNDC; }

	void				ISetWorldToNDC() const { fWorldToNDC = GetCameraToNDC() * fWorldToCamera; ISetFlag(kWorldToNDCSet); }
	hsBool				IWorldToNDCSet() const { return IHasFlag(kWorldToNDCSet); }
	const hsMatrix44&	ICheckWorldToNDC() const { if( !IWorldToNDCSet() ) ISetWorldToNDC(); return fWorldToNDC; }

	hsBool				IGetMaxMinsFromBnd(const hsBounds3& bnd, hsPoint3& mins, hsPoint3& maxs) const;

	void				InvalidateTransforms() { ISetFlag(kCameraToNDCSet|kWorldToNDCSet, false); }

	// Flags - generic
	hsBool				IHasFlag(UInt32 f) const { return 0 != (fFlags & f); }
	void				ISetFlag(UInt32 f, hsBool on=true) const { if(on) fFlags |= f; else fFlags &= ~f; }

};

#endif // plViewTransform_inc