/*==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/>.

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 plAnimPath_inc
#define plAnimPath_inc

#include "hsTemplates.h"
#include "hsGeometry3.h"
#include "hsMatrix44.h"
#include "../plTransform/hsAffineParts.h"
#include "../pnFactory/plCreatable.h"

class plCompoundController;

class plAnimPath : public plCreatable
{
public:
	enum Flags
	{
		kNone			= 0x0,
		kFavorFwdSearch	= 0x1,		// only move fwd on the curve when searching
		kFavorBwdSearch = 0x2,		// only move bwd on the curve when searching
		kCalcPosOnly	= 0x4,		// only compute pos when calling SetCurTime()
		kFarthest		= 0x8,
		kWrap			= 0x10,
		kIncrement		= 0x20,		// find the nearest / farthest point, but increment toward it
	};
protected:
	// The final product info
	hsMatrix44					fXform;
	hsPoint3					fPos;
	hsVector3					fVel;
	hsVector3					fAccel;
	hsScalar					fTime; // presumably seconds

	// The paramters (and options) for this curve.
	UInt32						fAnimPathFlags;		// currently set at runtime only
	hsScalar					fMinDistSq;
	hsScalar					fLength; // presumably seconds

	// Controller stuff only works in local space.
	hsMatrix44					fLocalToWorld;
	hsMatrix44					fWorldToLocal;

	// Bounding sphere available for ignoring out of range
	hsPoint3					fCenter;
	hsScalar					fRadius;

	plCompoundController*		fController;

	hsAffineParts				fParts;

	// These are temps during a search. They're here to avoid recalc.
	mutable hsScalar					fLastTime;
	mutable hsScalar					fLastDistSq;
	mutable hsScalar					fThisTime;
	mutable hsScalar					fThisDistSq;
	mutable hsScalar					fNextTime;
	mutable hsScalar					fNextDistSq;
	mutable hsScalar					fDelTime;
	mutable hsPoint3					fPrevPos, fCurPos;

	void						ICalcBounds();
	hsScalar					ICalcTotalLength();
	hsScalar					IShiftFore(hsPoint3 &pt) const;
	hsScalar					IShiftBack(hsPoint3 &pt) const;
	hsScalar					ISubDivFore(hsPoint3 &pt) const;
	hsScalar					ISubDivBack(hsPoint3 &pt) const;
	void						IInitInterval(hsScalar time, hsScalar delTime, hsPoint3 &pt) const;
	hsScalar					ICheckInterval(hsPoint3 &pt) const;
	hsScalar					IBestTime() const { return fLastDistSq < fThisDistSq 
														? (fLastDistSq < fNextDistSq 
															? fLastTime
															: fNextTime)
														: (fThisDistSq < fNextDistSq
															? fThisTime
															: fNextTime); }

	// Visualization helper
	void IMakeSegment(hsTArray<UInt16>& idx, hsTArray<hsPoint3>& pos,
								  hsPoint3& p1, hsPoint3& p2);
	
	// For computing arclen
	struct ArcLenDeltaInfo
	{
		hsScalar	fT;
		hsScalar	fArcLenDelta;	// arc len distance from prev sample point (array entry)
		ArcLenDeltaInfo(hsScalar t, hsScalar del) : fT(t),fArcLenDelta(del) {}
		ArcLenDeltaInfo() : fT(0),fArcLenDelta(0) {}
	};
	hsTArray<ArcLenDeltaInfo>	fArcLenDeltas;
public:
	plAnimPath();
	virtual ~plAnimPath();

	CLASSNAME_REGISTER( plAnimPath );
	GETINTERFACE_ANY( plAnimPath, plCreatable );
	
    void Reset();

	void SetTransform(const hsMatrix44& l2w, const hsMatrix44& w2l);
	const hsMatrix44& GetLocalToWorld() const { return fLocalToWorld; }
	const hsMatrix44& GetWorldToLocal() const { return fWorldToLocal; }

	// Visualization helper
	void MakeDrawList(hsTArray<UInt16>& idx, hsTArray<hsPoint3>& pos);

	void SetAnimPathFlags(UInt32 f) { fAnimPathFlags=f; }
	UInt32 GetAnimPathFlags() const { return fAnimPathFlags; }

	void SetWrap(hsBool on) { if(on)fAnimPathFlags |= kWrap; else fAnimPathFlags &= ~kWrap; }
	hsBool GetWrap() const { return 0 != (fAnimPathFlags & kWrap); }

	void SetFarthest(hsBool on) { if(on)fAnimPathFlags |= kFarthest; else fAnimPathFlags &= ~kFarthest; }
	hsBool GetFarthest() const { return 0 != (fAnimPathFlags & kFarthest); }

	void SetCurTime(hsScalar t, UInt32 calcFlags=0);
	hsScalar GetCurTime() const { return fTime; }

	void SetController(plCompoundController* tmc);
	plCompoundController* GetController() const { return fController; }
	hsScalar GetLength() const { return fLength; } // seconds

	void SetMinDistance(hsScalar d) { fMinDistSq = d*d; }
	hsScalar GetMinDistance() const { return hsSquareRoot(fMinDistSq); }

	hsMatrix44* GetMatrix44(hsMatrix44* xOut) const { *xOut = fXform; return xOut; }
	hsPoint3*	GetPosition(hsPoint3* pOut) const { *pOut = fPos; return pOut; }
	hsVector3*	GetVelocity(hsVector3* vOut) const { *vOut = fVel; return vOut; }
	hsVector3*	GetDirection(hsVector3* dOut) const { dOut->Set(fXform.fMap[0][2], fXform.fMap[1][2], fXform.fMap[2][2]); return dOut; }
	hsVector3*	GetUp(hsVector3* uOut) const { uOut->Set(fXform.fMap[0][1], fXform.fMap[1][1], fXform.fMap[2][1]); return uOut; }
	hsVector3*	GetAcceleration(hsVector3* aOut) const { *aOut = fAccel; return aOut; }
	
	hsBool OutOfRange(hsPoint3 &pt, hsScalar range) const;
	const hsAffineParts* Parts() const { return &fParts; }
	void InitParts(const hsAffineParts& p) { fParts = p; }

	hsScalar GetExtremePoint(hsPoint3 &worldPt) const; // Exhaustive search
	hsScalar GetExtremePoint(hsScalar lastTime, hsScalar delTime, hsPoint3 &worldPt) const; // Incremental search

	// for arclen usage
	void ComputeArcLenDeltas(Int32 numSamples=256);
	hsScalar GetLookAheadTime(hsScalar startTime, hsScalar arcLength, hsBool bwd, Int32* startSrchIdx);

	virtual void Read(hsStream* s, hsResMgr* mgr);
	virtual void Write(hsStream* s, hsResMgr* mgr);
};

#endif plAnimPath_inc