/* Copyright (C) John W. Ratcliff, 2001. 
* All rights reserved worldwide.
*
* This software is provided "as is" without express or implied
* warranties. You may freely copy and compile this source into
* applications you distribute provided that the copyright text
* below is included in the resulting source code, for example:
* "Portions Copyright (C) John W. Ratcliff, 2001"
*/
#pragma once

/***********************************************************************/
/** SPHEREPACK.H: Contains all of the support code for Sphere Trees.   */
/**                                                                    */
/**               Written by John W. Ratcliff jratcliff@att.net        */
/***********************************************************************/


#include <assert.h>

#include "vector.h"     // need 3d vector support.
#include "pool.h"       // need memory pool template class.
#include "sphere.h"     // Need sphere base class.
#include "frustum.h"    // Need to know what a frustum is.

#define DEMO 0// true if running in windows test app

enum SpherePackFlag
{
	
	SPF_SUPERSPHERE =(1<<0), // this is a supersphere, allocated and deleted by us
		SPF_ROOT_TREE   =(1<<1), // member of the root tree
		SPF_LEAF_TREE   =(1<<2), // member of the leaf node tree
		SPF_ROOTNODE    =(1<<3), // this is the root node
		SPF_RECOMPUTE   =(1<<4), // needs recomputed bounding sphere
		SPF_INTEGRATE   =(1<<5), // needs to be reintegrated into tree
		// Frame-to-frame view frustum status.  Only does callbacks when a
		// state change occurs.
		SPF_HIDDEN      =(1<<6), // outside of view frustum
		SPF_PARTIAL     =(1<<7), // partially inside view frustum
		SPF_INSIDE      =(1<<8)  // completely inside view frustum
};

class SpherePackFactory; // forward reference the factory.
class SpherePack;        // forward reference the sphere package

#pragma warning(push)
#pragma warning(disable:4100)

// Virtual base class, used to implement callbacks for RayTracing,
// range testing, and frustum culling.
class SpherePackCallback
{
public:
	SpherePackCallback() {}
	virtual ~SpherePackCallback() {}

	virtual void VisibilityCallback(const Frustum &f,  // frustum clipped against
		SpherePack *sphere, // leaf node sphere in question
		ViewState state) // new state it is in.
	{};
	
	virtual void RayTraceCallback(const Vector3d &p1,   // source pos of ray
		const Vector3d &dir,  // direction of ray
		float distance,              // distance of ray
		const Vector3d &sect, // intersection location
		SpherePack *sphere)          // sphere ray hit
	{};
	
	virtual void RangeTestCallback(const Vector3d &searchpos, // position we are performing range test against.
		float distance,                     // squared distance we are range searching against.
		SpherePack *sphere,
		ViewState state) // sphere within range, VS_PARTIAL if sphere straddles range test
	{};

	virtual void PointTest2dCallback(const Vector3d &searchpos, // position we are performing range test against.
		SpherePack *sphere,
		ViewState state) // sphere within range, VS_PARTIAL if sphere straddles range test
	{};
	
private:
};

#pragma warning(pop)

class SpherePack : public Sphere
{
public:
	SpherePack(void)
	{		
		mUserData         = 0; // default user data is null
		mFactory          = 0; // factory we are a member of
		mNext             = 0; // linked list pointers
		mPrevious         = 0;
		mParent           = 0;
		mNextSibling      = 0; // our brothers and sisters at this level.
		mPrevSibling      = 0;
		mChildren         = 0; // our children.
		mChildCount       = 0; // number of children we have.
		mFifo1            = 0; // our FIFO1 location if we have one.
		mFifo2            = 0; // our FIFO2 location if we have one.
		SetRadius(0);          // default radius
		mCenter.Set(0,0,0);    // default center position.
		IS_SPHERE	= false;
	};
	virtual ~SpherePack()
	{
	}
	
	void Init(SpherePackFactory *factory, // factory we belong to
		const Vector3d &pos, // center of sphere
		float radius,               // radius of sphere
		void *userdata, bool isSphere)             // user data
	{
		IS_SPHERE			= isSphere;
		mUserData         = userdata;
		mParent           = 0;
		mNextSibling      = 0;
		mPrevSibling      = 0;
		mFlags            = 0;
		mFifo1            = 0;
		mFifo2            = 0;
		mFactory          = factory;
		mCenter           = pos;
		SetRadius(radius);
	};
	
	// Access to SpherePack bit flags.
	void SetSpherePackFlag(SpherePackFlag flag) { mFlags|=flag; };
	void ClearSpherePackFlag(SpherePackFlag flag) { mFlags&=~flag; };
	bool HasSpherePackFlag(SpherePackFlag flag) const
	{
		if ( mFlags & flag ) return true;
		return false;
	};
	
	
	void SetParent(SpherePack *pack) { mParent = pack; };
	SpherePack * GetParent(void) const { return mParent; };
	
	// Sphere has a new position.
	inline void NewPos(const Vector3d &pos);
	// Sphere has a new position and radius
	inline void NewPosRadius(const Vector3d &pos,float radius);
	
	void Unlink(void)
	{
		if ( mFifo1 ) // if we belong to fifo1, null us out
		{
			*mFifo1 = 0;
			mFifo1 = 0;
		}
		
		if ( mFifo2 ) // if we belong to fifo2, null us out
		{
			*mFifo2 = 0;
			mFifo2 = 0;
		}
		
		if ( mParent ) mParent->LostChild(this);
		
		assert( !mChildren ); // can't unlink guys with children!
		
		mParent = 0; // got no father anymore
	}
	
	
	void AddChild(SpherePack *pack)
	{
		
		SpherePack *my_child = mChildren;
		mChildren = pack; // new head of list
		
		pack->SetNextSibling(my_child); // his next is my old next
		pack->SetPrevSibling(0); // at head of list, no previous
		pack->SetParent(this);
		
		if ( my_child ) my_child->SetPrevSibling(pack); // previous now this..
		
		mChildCount++;

#if defined(_DEBUG)
		float dist = DistanceSquared(pack);
		float radius = sqrtf(dist) + pack->GetRadius();
		
		assert( radius <= GetRadius()+0.0001f );
#endif
	}
	
	void SetNextSibling(SpherePack *child) { mNextSibling = child; }
	void SetPrevSibling(SpherePack *child) { mPrevSibling = child; }
	
	SpherePack * _GetNextSibling(void) const 
	{ 
		return mNextSibling; 
	}
	SpherePack * _GetPrevSibling(void) const 
	{ 
		return mPrevSibling; 
	}
	SpherePack * GetChildren(void)    const { return mChildren; }
	
	SpherePack * GetNext(void)     const { return mNext; };
	SpherePack * GetPrevious(void) const { return mPrevious; };
	
	void SetNext(SpherePack *pack) { mNext = pack; };
	void SetPrevious(SpherePack *pack) { mPrevious = pack; };
	
	void * GetUserData(void) const { return mUserData; };
	void   SetUserData(void *data, bool isSphere) { mUserData = data; IS_SPHERE=isSphere;};
	
	float DistanceSquared(const SpherePack *pack) const { return mCenter.DistanceSq( pack->mCenter );  };
	
	void LostChild(SpherePack *pack);
	
	const Vector3d& GetPos(void) const { return mCenter; };
	
	inline void Render(unsigned int color);
	
	inline bool Recompute(float gravy);
	
	int GetChildCount(void) const { return mChildCount; };
	
#if DEMO
	void SetColor(unsigned int color) { mColor = color; };
	unsigned int GetColor(void) const { return mColor; };
#endif
	
	void SetFifo1(SpherePack **fifo)
	{
		mFifo1 = fifo;
	};
	
	void SetFifo2(SpherePack **fifo)
	{
		mFifo2 = fifo;
	};
	
	void ComputeBindingDistance(SpherePack *parent)
	{
		mBindingDistance = parent->GetRadius() - GetRadius();
		if ( mBindingDistance <= 0 )
			mBindingDistance = 0;
		else
			mBindingDistance*=mBindingDistance;
	}
	
	void VisibilityTest(const Frustum &f,
		SpherePackCallback *callback,
		ViewState state);
	
	void RayTrace(const Vector3d &p1,           // origin of Ray
		const Vector3d &dir,          // direction of Ray
		float distance,                      // length of ray.
		SpherePackCallback *callback);
	
	
	void RangeTest(const Vector3d &p,
		float distance,
		SpherePackCallback *callback,
		ViewState state);
	
	void PointTest2d(const Vector3d &p,
		SpherePackCallback *callback,
		ViewState state);

	void Reset(void);
	
private:
	SpherePack       *mNext;
	SpherePack       *mPrevious; // used by pool memory management linked list code
	
	SpherePack       *mParent;
	SpherePack       *mChildren;  // *my* children
	
	SpherePack       *mNextSibling; // doubly linked list of my brothers
	SpherePack       *mPrevSibling; // and sisters
	
	SpherePack      **mFifo1; // address of location inside of fifo1
	SpherePack      **mFifo2; // address of location inside of fifo2
	
	long               mFlags; // my bit flags.
	long               mChildCount; // number of children
	
	float             mBindingDistance;
	
	void             *mUserData;
	
	SpherePackFactory *mFactory; // the factory we are a member of.
#if DEMO
	unsigned long      mColor;
#endif

public:
	bool				IS_SPHERE;
};

class SpherePackFifo
{
public:
	SpherePackFifo(int fifosize)
	{
		mCount = 0;
		mSP = 0;
		mBottom = 0;
		mFifoSize = fifosize;
		mFifo = new SpherePack *[mFifoSize];
	};
	
	virtual ~SpherePackFifo(void)
	{
		delete [] mFifo;
	};
	
	SpherePack ** Push(SpherePack *sphere)
	{
		mCount++;
		SpherePack **ret = &mFifo[mSP];
		mFifo[mSP] = sphere;
		mSP++;
		if ( mSP == mFifoSize ) mSP = 0;
		return ret;
	};
	
	SpherePack * Pop(void)
	{
		while ( mSP != mBottom )
		{
			mCount--;
			SpherePack *ret = mFifo[mBottom];
			mBottom++;
			if ( mBottom == mFifoSize ) mBottom = 0;
			if ( ret ) return ret;
		}
		return 0;
	}
	
	bool Flush(SpherePack *pack)
	{
		if ( mSP == mBottom ) return false;
		int i = mBottom;
		while ( i != mSP )
		{
			if ( mFifo[i] == pack )
			{
				mFifo[i] = 0;
				return true;
			}
			i++;
			if ( i == mFifoSize ) i = 0;
		}
		return false;
	};
	
	int GetCount(void) const { return mCount; };
	
private:
	int         mCount;
	int         mSP; // stack pointer
	int         mBottom;
	int         mFifoSize;
	SpherePack **mFifo;
};


class SpherePackFactory : public SpherePackCallback
{
public:
	
	SpherePackFactory(int maxspheres,
		float rootsize,
		float leafsize,
		float gravy);
	
	
	virtual ~SpherePackFactory(void);
	
	void Process(void);
	
	SpherePack *AddSphere_(const Vector3d &pos,
		float radius,
		void *userdata,
		bool isSphere,
		int flags=SPF_LEAF_TREE);
	
	void AddIntegrate(SpherePack *pack);          // add to the integration FIFO
	void AddRecompute(SpherePack *recompute);     // add to the recomputation (balancing) FIFO.
	
	void Integrate(SpherePack *pack,SpherePack *supersphere,float node_size);
	
	void Render(void);
	
	void Remove(SpherePack *pack);
	// see if any other spheres are contained within this one, if so
	// collapse them and inherit their children.
#if DEMO
	unsigned int GetColor(void);
#endif
	
	void FrustumTest(const Frustum &f,SpherePackCallback *callback);
	
	void RayTrace(const Vector3d &p1,           // source
		const Vector3d &p2,           // dest
		SpherePackCallback *callback);
	
	void RangeTest(const Vector3d &center,float radius,SpherePackCallback *callback);
	void PointTest2d(const Vector3d &center, SpherePackCallback *callback);
	
	virtual void RayTraceCallback(const Vector3d &p1,          // source pos of ray
		const Vector3d &dir,          // direction of ray
		float distance,                      // distance of ray
		const Vector3d &sect,          // intersection location
		SpherePack *sphere);
	
	virtual void RangeTestCallback(const Vector3d &p,float distance,SpherePack *sphere,ViewState state);
	virtual void PointTest2dCallback(const Vector3d &p, SpherePack *sphere,ViewState state);
	
	virtual void VisibilityCallback(const Frustum &f,SpherePack *sphere,ViewState state);
	
	
	void Reset(void);
	
private:
	
	
	SpherePack        *mRoot;     // 1024x1024 root node of all active spheres.
	SpherePack        *mLeaf;     // 1024x1024 root node of all active spheres.
	SpherePackCallback *mCallback;
	
	Pool< SpherePack > mSpheres;  // all spheres possibly represented.
	
	SpherePackFifo    *mIntegrate; // integration fifo
	SpherePackFifo    *mRecompute; // recomputation fifo
	
#if DEMO
#define MAXCOLORS 12
	int               mColorCount;
	unsigned int      mColors[MAXCOLORS];
#endif
	
	float             mMaxRootSize;              // maximum size of a root node supersphere
	float             mMaxLeafSize;              // maximum size of the leaf node supersphere
	float             mSuperSphereGravy;         // binding distance gravy.
};

//*** Source code placed in header file so that it will be inlined!
void SpherePack::NewPos(const Vector3d &pos)
{
	mCenter = pos;    // set our new center position.
	
	// is we have a parent (meaning we are a valid leaf node) and we have not already been flagged for re-integration, then.....	
	if (mParent && !HasSpherePackFlag(SPF_INTEGRATE))
	{	
		float dist = DistanceSquared(mParent);  // compute squared distance to our parent.
		
		if (dist >= mBindingDistance) // if that exceeds our binding distance...
		{
			// If our parent, is not already marked to be recomputed (rebalance the sphere), then add him to the recomputation fifo.
			mFactory->AddRecompute(mParent);

			// Unlink ourselves from the parent sphere and place ourselves into the root node.
			Unlink();
			mFactory->AddIntegrate(this); // add ourselves to the re-integration fifo.
		}
	}
	
}

void SpherePack::NewPosRadius(const Vector3d &pos,float radius)
{
	// New position and, possibly, a new radius.
	mCenter = pos;
	
	if (mParent && !HasSpherePackFlag(SPF_INTEGRATE))
	{
		if (radius != GetRadius())
		{
			SetRadius(radius);
			ComputeBindingDistance(mParent);
		}
		
		mFactory->AddRecompute(mParent);

		float dist = DistanceSquared(mParent);
	
		if (dist >= mBindingDistance)
		{
			Unlink();
			mFactory->AddIntegrate(this);
		}
	}
}
