/****************************************************************************************

		SphereCamera.cpp

		Copyright (c)1990 - 1999 MetaCreations. All rights reserved.

		Author:	Guillaume Chermette
		Date:	11/12/99

		Be sure to only use the Ray Tracing renderer, and not the Hybrid one, when rendering 
		scene. Due to its very properties, the Hybrid one won't correctly render the scene
		(for example, it won't deform bounding boxes).

		Revision History

		when		who			what & why
		==========	==========	======================================================

*****************************************************************************************/

#include "SphereCamera.h"


const MCGUID CLSID_SphereCamera(R_CLSID_SphereCamera);


//--------------------------------------------------------
void Extension3DInit(IMCUnknown* utilities)
{
	// Perform your dll initialisation there
}


//--------------------------------------------------------
void Extension3DCleanup()
{
	// Perform any nec clean-up here
}


//--------------------------------------------------------
TBasicUnknown* MakeCOMObject(const MCCLSID& classId)	// This method instanciate
{														// the object Camera
	TBasicUnknown* res = NULL;

	if (classId == CLSID_SphereCamera) res = new SphereCamera;

	return res;
}


// Constructor
//--------------------------------------------------------
SphereCamera::SphereCamera()
{
	fData.fZoomCoef = 50.0;
	ExtensionDataChanged();
}


//--------------------------------------------------------
void* SphereCamera::GetExtensionDataBuffer()
{
	return &fData; // The Shell uses this pointer to set the values of the camera's parameters
}
  

//--------------------------------------------------------
MCCOMErr SphereCamera::ExtensionDataChanged()
{


	SetTransform(fTransform);

	return MC_S_OK;
}


//--------------------------------------------------------
int16 SphereCamera::GetResID()
{
	return 130; // This is the view ID in the resource file
}


//--------------------------------------------------------
MCCOMErr SphereCamera::SetTransform( const TTransform3D& transform )
{
	fTransform = transform; // Copy the data of transform and not the pointer.

	fCameraOrigin = fTransform.TransformPoint(TVector3::kZero);

	return MC_S_OK;
}


// Ray Creation :
//--------------------------------------------------------
boolean  SphereCamera::CreateRay(const TVector2& screenPosition,
								 TVector3& resultOrigin,
								 TVector3& resultDirection) const
{
	real32 theta, phi;
	real32 coef;

	coef = kRealTwoThirds;

	resultOrigin = fCameraOrigin;

	theta = screenPosition[0]*kRealPI;
	phi	= screenPosition[1]*coef*kRealPI;

	resultDirection[0] = RealSin(theta)*RealCos(phi);
	resultDirection[1] = RealSin(phi);
	resultDirection[2] = -RealCos(theta)*RealCos(phi);

	return true;
}


//--------------------------------------------------------
boolean SphereCamera::CreateRay(const TVector2& screenPosition,
								const TVector2& screenDerivate,
								Ray3D &ray)  const
{
	real32 theta, phi;
	static real32 coef = 4.0f/3.0f;

	// -Pi < Theta < Pi
	theta = screenPosition[0]*kRealPI;
	// -Pi/2 < Phi < Pi/2
	phi	= screenPosition[1]*coef*kRealHalfPI;

	if (theta > kRealPI)
		theta = kRealPI;
	else if (theta < -kRealPI)
		theta = -kRealPI;
	if (phi > kRealHalfPI)
		phi = kRealHalfPI;
	else if (phi < -kRealHalfPI)
		phi = -kRealHalfPI;

	ray.fOrigin = fCameraOrigin;

	(ray.fDirection).x = RealSin(theta)*RealCos(phi);
	(ray.fDirection).y = RealSin(phi);
	(ray.fDirection).z = -RealCos(theta)*RealCos(phi);

	//norm of ray.fDirection is equal to 1

	(ray.fDx).x = RealCos(theta)*screenDerivate[0]*kRealPI*RealCos(phi);
	(ray.fDx).y = 0;
	(ray.fDx).z = RealSin(theta)*screenDerivate[0]*kRealPI*RealCos(phi);
	(ray.fDx).Normalize();

	(ray.fDy).x = -RealSin(theta)*RealSin(phi)*screenDerivate[1]*kRealPI*coef;
	(ray.fDy).y = RealCos(phi)*screenDerivate[1]*kRealPI*coef;
	(ray.fDy).z = RealCos(theta)*RealSin(phi)*screenDerivate[1]*kRealPI*coef;
	(ray.fDy).Normalize();

	ray.fOx = TVector3::kZero;
	ray.fOy = TVector3::kZero;

	return true;
}


//--------------------------------------------------------
boolean SphereCamera::PreProject3DTo2D(const TVector3& cameraPosition, TVector4& projectedPosition) const
{
	real32 r,theta,phi,invPI;

	invPI = 1.0f/kRealPI;
		
	r = RealSqrt(cameraPosition[0]*cameraPosition[0]+cameraPosition[2]*cameraPosition[2]);
	theta = RealAtan2(cameraPosition[0],-cameraPosition[2]);
	phi = RealAtan2(cameraPosition[1],r);
	
	if (theta > kRealPI)
		theta -= kRealTwoPI;
	else if (theta < -kRealPI)
		theta += kRealTwoPI;
	if (phi > kRealHalfPI)
		phi = kRealPI-phi;
	else if (phi < -kRealHalfPI)
		phi += kRealPI;

	projectedPosition.x = theta*invPI;
	projectedPosition.y = phi*invPI*2.0f*0.75f;
	projectedPosition.z = -cameraPosition.z;
	projectedPosition.w = -cameraPosition.z;

	return (-cameraPosition.z > 0);
}


//--------------------------------------------------------
boolean SphereCamera::Project3DTo2D	(const TVector3& cameraPosition, TVector3& screenPosition) const
{
	real32 r,theta,phi,invPI;

	invPI = 1.0f/kRealPI;
		
	r = RealSqrt(cameraPosition[0]*cameraPosition[0]+cameraPosition[2]*cameraPosition[2]);
	theta = RealAtan2(cameraPosition[0],-cameraPosition[2]);
	phi = RealAtan2(cameraPosition[1],r);

	if (theta > kRealPI)
		theta -= kRealTwoPI;
	else if (theta < -kRealPI)
		theta += kRealTwoPI;
	if (phi > kRealHalfPI)
		phi = kRealPI-phi;
	else if (phi < -kRealHalfPI)
		phi += kRealPI;

	screenPosition.x = theta*invPI;
	screenPosition.y = phi*invPI*2.0f*0.75f;
	screenPosition.z = cameraPosition.GetNorm();
	
	return true;
}


//--------------------------------------------------------
uint32 SphereCamera::GetPrimitiveID() const
{
	return IDTYPE('c','a','m','e');
}


//--------------------------------------------------------
boolean SphereCamera::IsProjectionAffine() const
{
	return false;
}


// GetStandardCameraInfo is used by the Interactive Renderer
//--------------------------------------------------------
void SphereCamera::GetStandardCameraInfo(TStandardCameraInfo& cameraInfo) const
{
	// EProjectionType: can only be ortho/isometric or persp./conical
	cameraInfo.fProjectionType 		= kProjectionType_kPerspective;
	// real	: fCameraOriginr ortho/perspective cameras
	cameraInfo.fHalfWidthAtOneUnit	= 21.677 / fData.fZoomCoef;
	// boolean	: whether standard camera matches true camera
	cameraInfo.fIsMatching = true;	

}