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

		Sphere.cpp

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

		Definitions shared by the C++ files and the resource definition files (.r)


		Author:	Jerome Bignon

		Date:	04/30/99

		Revision History

		when		who			what & why
		==========	==========	======================================================
		08/20/99	cph			Put in Extension3DInit() and Extension3DCleanup() methods
		05/07/99	Jerome		Solved problem with the getUVSpace methods
		04/30/99	Jerome		Creation

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

#include "math.h"
#include "Sphere.h"

// Constante values
real kDefaultSphereRadius = 3.0f;
real kUmax                = 360.0f;
real kVmax                = 180.0f;

const MCGUID CLSID_Sphere(R_CLSID_Sphere);

// Functions to create the Patches of the Sphere         
// -- Inverse a Patch
void InversePatch(TPatch3D *aPatch)
{
	TVector3 swapPoint;

	for (short uu=0;uu<=1;uu++)
	{
		for (short vv=0;vv<4;vv++)
		{
			for (int i=0; i<3; i++)
			{
				swapPoint[i]=aPatch->fVertices[uu][vv][i];
				aPatch->fVertices[uu][vv][i]=aPatch->fVertices[3-uu][vv][i];
				aPatch->fVertices[3-uu][vv][i]=swapPoint[i];
			}
		}
	}
}

// Create Default Patches of the Sphere
void MakeSpherePatch(TPatch3D *aPatch,real di,real dj,real dk) 
{
	real magicFactor=0.552284749830793398f; 
	real swapUV;

	aPatch->fVertices[0][3][0]=0.0f;
	aPatch->fVertices[0][3][1]=0.0f;
	aPatch->fVertices[0][3][2]=dk;

	aPatch->fVertices[0][2][0]=magicFactor*di;
	aPatch->fVertices[0][2][1]=0.0f;
	aPatch->fVertices[0][2][2]=dk;

	aPatch->fVertices[0][1][0]=di;
	aPatch->fVertices[0][1][1]=0.0f;
	aPatch->fVertices[0][1][2]=magicFactor*dk;

	aPatch->fVertices[0][0][0]=di;
	aPatch->fVertices[0][0][1]=0.0f;
	aPatch->fVertices[0][0][2]=0.0f;

	aPatch->fVertices[1][0][0]=di;
	aPatch->fVertices[1][0][1]=magicFactor*dj;
	aPatch->fVertices[1][0][2]=0.0f;

	aPatch->fVertices[2][0][0]=magicFactor*di;
	aPatch->fVertices[2][0][1]=dj;
	aPatch->fVertices[2][0][2]=0.0f;

	aPatch->fVertices[3][0][0]=0.0f;
	aPatch->fVertices[3][0][1]=dj;
	aPatch->fVertices[3][0][2]=0.0f;

	aPatch->fVertices[3][1][0]=0.0f;
	aPatch->fVertices[3][1][1]=dj;
	aPatch->fVertices[3][1][2]=magicFactor*dk;

	aPatch->fVertices[3][2][0]=0.0f;
	aPatch->fVertices[3][2][1]=magicFactor*dj;
	aPatch->fVertices[3][2][2]=dk;

	aPatch->fVertices[3][3][0]=0.0f;
	aPatch->fVertices[3][3][1]=0.0f;
	aPatch->fVertices[3][3][2]=dk;

	aPatch->fVertices[2][3][0]=0.0f;
	aPatch->fVertices[2][3][1]=0.0f;
	aPatch->fVertices[2][3][2]=dk;

	aPatch->fVertices[1][3][0]=0.0f;
	aPatch->fVertices[1][3][1]=0.0f;
	aPatch->fVertices[1][3][2]=dk;

	//middle of the Patch (control points)
	aPatch->fVertices[1][2][0]=magicFactor*di;
	aPatch->fVertices[1][2][1]=magicFactor*magicFactor*dj;
	aPatch->fVertices[1][2][2]=dk;

	aPatch->fVertices[2][2][0]=magicFactor*magicFactor*di;
	aPatch->fVertices[2][2][1]=magicFactor*dj;
	aPatch->fVertices[2][2][2]=dk;


	aPatch->fVertices[1][1][0]=di;
	aPatch->fVertices[1][1][1]=magicFactor*dj;
	aPatch->fVertices[1][1][2]=magicFactor*dk;

	aPatch->fVertices[2][1][0]=magicFactor*di;
	aPatch->fVertices[2][1][1]=dj;
	aPatch->fVertices[2][1][2]=magicFactor*dk;

	// Patch's uv-space information
	if (di>0.0f) 
	{
		if (dj>0.0f) 
		{
			aPatch->fu[0]=0.0f;		// 0

			aPatch->fu[1]=kUmax/4;	// to 90°
			aPatch->fu[1]/=kUmax;	// normalize to 0..1
		}
		else
		{
			aPatch->fu[0]=(kUmax+kUmax+kUmax)/4;	// 270°
			aPatch->fu[0]/=kUmax;					// normalize to 0..1

			aPatch->fu[1]=kUmax;					// to 360°
			aPatch->fu[1]/=kUmax;					// normalize to 0..1
		}
	}
	else 
	{
		if (dj>0.0f) 
		{
			aPatch->fu[0]=kUmax/4;	// 90°
			aPatch->fu[0]/=kUmax;	// normalize to 0..1

			aPatch->fu[1]=kUmax/2;	// to 180°
			aPatch->fu[1]/=kUmax;	// normalize to 0..1
		}
		else 
		{
			aPatch->fu[0]=kUmax/2;					// 180°
			aPatch->fu[0]/=kUmax;					// normalize to 0..1

			aPatch->fu[1]=(kUmax+kUmax+kUmax)/4;	// to 270°
			aPatch->fu[1]/=kUmax;					// normalize to 0..1
		}
	}

	if (dk>0.0f) 
	{
		aPatch->fv[0]=kVmax/2;	// 90°
		aPatch->fv[0]/=kVmax;	// normalize to 0..1

		aPatch->fv[1]=kVmax;	// to 180°
		aPatch->fv[1]/=kVmax;	// normalize to 0..1
	}
	else 
	{
		swapUV=aPatch->fu[0];
		aPatch->fu[0]=aPatch->fu[1];
		aPatch->fu[1]=swapUV;
  
		aPatch->fv[0]=kVmax/2;
		aPatch->fv[0]/=kVmax;	// normalize to 0..1
		aPatch->fv[1]=0.0f;
	}
  
	int signi=(di>0.0?1:-1); 
	int signj=(dj>0.0?1:-1); 
	int signk=(dk>0.0?1:-1); 
	if (signi*signj*signk<0) 
	{
		InversePatch(aPatch);
	}

	aPatch->fUVSpace=0;
	aPatch->fReserved=0;
}


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

void Extension3DInit(IMCUnknown* utilities)
{
	// Perform your dll initialization here
}

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

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

	if (classId == CLSID_Sphere) res = new Sphere;

	return res;
}


// Constructor of the C++ Object
Sphere::Sphere()
{
}
 

MCCOMErr Sphere::QueryInterface(const MCIID& riid, void** ppvObj)
{
	if (riid==IID_IRaytracablePrimitive) 
	{
		TMCCountedGetHelper<IRaytracablePrimitive> result(ppvObj);
		result = this;
		return MC_S_OK;
	}
	else
		return TBasicPrimitive::QueryInterface(riid, ppvObj);
}

uint32 Sphere::AddRef()
{
	return TBasicPrimitive::AddRef();
}


// I3DExGeometricPrimitive methods
// -- Geometry calls
MCCOMErr Sphere::EnumPatches( EnumPatchesCallback callback, void* privData, boolean& closed)
{
	TPatch3D aPatch;
	if (callback == NULL) return MC_S_OK;

	MakeSpherePatch(&aPatch,kDefaultSphereRadius,kDefaultSphereRadius,kDefaultSphereRadius);
	callback(&aPatch,privData);
	MakeSpherePatch(&aPatch,-kDefaultSphereRadius,kDefaultSphereRadius,kDefaultSphereRadius);
	callback(&aPatch,privData);
	MakeSpherePatch(&aPatch,kDefaultSphereRadius,-kDefaultSphereRadius,kDefaultSphereRadius);
	callback(&aPatch,privData);
	MakeSpherePatch(&aPatch,-kDefaultSphereRadius,-kDefaultSphereRadius,kDefaultSphereRadius);
	callback(&aPatch,privData);
	MakeSpherePatch(&aPatch,kDefaultSphereRadius,kDefaultSphereRadius,-kDefaultSphereRadius);
	callback(&aPatch,privData);
	MakeSpherePatch(&aPatch,-kDefaultSphereRadius,kDefaultSphereRadius,-kDefaultSphereRadius);
	callback(&aPatch,privData);
	MakeSpherePatch(&aPatch,kDefaultSphereRadius,-kDefaultSphereRadius,-kDefaultSphereRadius);
	callback(&aPatch,privData);
	MakeSpherePatch(&aPatch,-kDefaultSphereRadius,-kDefaultSphereRadius,-kDefaultSphereRadius);
	callback(&aPatch,privData);

	closed = true;

	return MC_S_OK;
}

// Give the boundary Box
void Sphere::GetBoundingBox(TBBox3D& bbox) 
{
	bbox.fMin[0]=-kDefaultSphereRadius;
	bbox.fMax[0]=kDefaultSphereRadius;
	bbox.fMin[1]=-kDefaultSphereRadius;
	bbox.fMax[1]=kDefaultSphereRadius;
	bbox.fMin[2]=-kDefaultSphereRadius;
	bbox.fMax[2]=kDefaultSphereRadius;
}
  
// -- Shading calls
uint32 Sphere::GetUVSpaceCount() 
{
	return 1; // the Sphere is describe with only 1 UV-Space
}

MCCOMErr Sphere::GetUVSpace( uint32 uvSpaceID, UVSpaceInfo* uvSpaceInfo) 
{
	if (uvSpaceID == 0)
	{     
		uvSpaceInfo->fWraparound[0] = true; 
		uvSpaceInfo->fWraparound[1] = false;
	}
	return MC_S_OK;
}
 
MCCOMErr Sphere::GetUVSpaceRDS5(uint32 uvSpaceID, UVSpaceInfoRDS5* uvSpaceInfo)
// To be implemented only if your primitive existed in RDS 5 or earlier, so that UV spaces can be mapped properly to the new 0..1 boundaries
{
	if (uvSpaceID == 0)
	{ 
		// UV-Space is equivalent to Spherical Coordinates
		uvSpaceInfo->fMin[0] = 0.0;       // u coordinate goes from 0 to 360
		uvSpaceInfo->fMax[0] = kUmax;
		uvSpaceInfo->fMin[1] = 0.0;       // v coordinate goes from 0 to 180
		uvSpaceInfo->fMax[1] = kVmax; 
		uvSpaceInfo->fWraparound[0] = true;  // No Wrap around
		uvSpaceInfo->fWraparound[1] = false;
	}
	return MC_S_OK;
}

// We use the default interpolation method to get all the coordinate of a point in UV Coordinates
MCCOMErr Sphere::UV2XYZ( TVector2* uv, uint32 uvSpaceID, TVector3* resultPosition, boolean* inUVSpace)
{
	real phi,theta;
	real sinphi,cosphi;
	real sintheta,costheta;

	*inUVSpace=true;
	if (((*uv)[0]<0.0f) || ((*uv)[0]>=1.0f)) *inUVSpace=false;
	if (((*uv)[1]<0.0f) || ((*uv)[1]>=1.0f)) *inUVSpace=false;

	if (*inUVSpace == boolean(true))
	{
		phi=((*uv)[0]*kUmax);
		theta=((*uv)[1]*kVmax)-(kVmax/2);
  
		// Spherical Coordinates To XYZ-Coordinates
		sinphi=sin(phi);
		cosphi=cos(phi); //phi.DegreeGetSinCos(sinphi,cosphi);
		sintheta=sin(theta);
		costheta=cos(theta); //theta.DegreeGetSinCos(sintheta,costheta);
  
		(*resultPosition)[0]=cosphi*costheta;
		(*resultPosition)[1]=sinphi*costheta;
		(*resultPosition)[2]=sintheta;
		(*resultPosition)[0] *= kDefaultSphereRadius;
		(*resultPosition)[1] *= kDefaultSphereRadius;
		(*resultPosition)[2] *= kDefaultSphereRadius;
	}  
	return MC_S_OK;
}

void Sphere::GetDetails(RayHit3D& hit)
{
	real phi,theta,rxy;
	real sinphi,cosphi,sintheta,costheta;

	if (!hit.fShouldSetUV && !hit.fShouldSetIsoUV) return;
  
	phi=atan2(hit.fNormalLoc[1],hit.fNormalLoc[0]); //phi.DegreeSetFromSinCos(hit.fNormal[1],hit.fNormal[0]);
	rxy=hit.fNormalLoc[0]*hit.fNormalLoc[0]+hit.fNormalLoc[1]*hit.fNormalLoc[1];
	rxy=sqrt(rxy); //rxy.GetSquareRoot(rxy);
	theta=atan2(hit.fNormalLoc[2],rxy); //theta.DegreeSetFromSinCos(hit.fNormalLoc[2],rxy);

	phi /= real(PI);
	theta /= real(PI);

	if (hit.fShouldSetUV)
	{
		hit.fUV[0]=phi;
		if (theta>1.0f) // means > kVmax
		{
			theta = theta - 2.0f;
		}
		hit.fUV[1]=theta+0.5f;
	}
	if (hit.fShouldSetIsoUV)
	{
		sinphi=sin(phi);
		cosphi=cos(phi); //phi.DegreeGetSinCos(sinphi,cosphi);
		sintheta=sin(theta);
		costheta=cos(theta); //theta.DegreeGetSinCos(sintheta,costheta);
    
		hit.fIsoU[0]=-sinphi*kDefaultSphereRadius;
		hit.fIsoU[1]=cosphi*kDefaultSphereRadius;
		hit.fIsoV[2]=0.0f;
		hit.fIsoV[0]=cosphi*sintheta*kDefaultSphereRadius;
		hit.fIsoV[1]=sinphi*sintheta*kDefaultSphereRadius;
		hit.fIsoV[2]=costheta*kDefaultSphereRadius;
	}
}

// -- Ray Tracing calls
boolean Sphere::RayHit(RayHitParameters& params)
{ 
	const Ray3D* aR= params.ray;

	TVector3 OriginToCenter;
	real    DirectionNorm2;
	real    dotProduct;
	real    t;              // Sphere Center = Ray Origin + t * Ray Direction
	TVector3 CH;             // position of the center projected on the ray
	real    distCH2;        // square of the distance of the projected point
	real    resT;
	real    delta,delta2;
	real    radius2=kDefaultSphereRadius*kDefaultSphereRadius;

	OriginToCenter=-aR->fOrigin;

	DirectionNorm2 = aR->fDirection.GetMagnitudeSquared();

	dotProduct = aR->fDirection * OriginToCenter;

	t=dotProduct/DirectionNorm2;

	CH = aR->fOrigin + t * aR->fDirection;
	distCH2 = CH.GetMagnitudeSquared();

	if (distCH2 > radius2)
	{ 
		return false;
    }
	else if (distCH2 == radius2)
	{
		resT= t;
    }
	else
	{
		// distCH2<radius2
		delta2 = (radius2-distCH2)/DirectionNorm2;
		delta = sqrt(delta2); //delta2.GetSquareRoot(delta);
		resT = t-delta;
		if (resT<=params.tmin)
		{
			resT = t+delta;
		}
    }
	if (resT<=params.tmin)
	{ 
		return false;
    }

	if (resT>params.tmax)
	{ 
		return false;
    }
   	
	RayHit3D* hit = params.hit;
	hit->fPointLoc = aR->fOrigin + aR->fDirection*resT;
	hit->fNormalLoc = hit->fSurfaceNormalLoc = hit->fPointLoc / kDefaultSphereRadius;
	hit->ft = resT;
	hit->fCalcInfo = GetDetails;

	return true;
}
  
MCCOMErr Sphere::GetRayHitDetails(RayHit3D* hit)
{
	GetDetails(*hit);

	return MC_S_OK;

}


MCCOMErr Sphere::IsInfiniteBB(boolean &isInfinite)
{
	isInfinite = false;
	return MC_S_OK;

}
