/*****************************************************************************
The Dark Mod GPL Source Code

This file is part of the The Dark Mod Source Code, originally based
on the Doom 3 GPL Source Code as published in 2011.

The Dark Mod Source Code 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. For details, see LICENSE.TXT.

Project: The Dark Mod (http://www.thedarkmod.com/)

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

#ifndef __ANIM_H__
#define __ANIM_H__

//
// animation channels
// these can be changed by modmakers and licensees to be whatever they need.
const int ANIM_NumAnimChannels		= 5;
const int ANIM_MaxAnimsPerChannel	= 3;
const int ANIM_MaxSyncedAnims		= 3;

//
// animation channels.  make sure to change script/doom_defs.script if you add any channels, or change their order
//
const int ANIMCHANNEL_ALL			= 0;
const int ANIMCHANNEL_TORSO			= 1;
const int ANIMCHANNEL_LEGS			= 2;
const int ANIMCHANNEL_HEAD			= 3;
const int ANIMCHANNEL_EYELIDS		= 4;

// for converting from 24 frames per second to milliseconds
ID_INLINE int FRAME2MS( int framenum ) {
	return ( framenum * 1000 ) / 24;
}

class idRenderModel;
class idAnimator;
class idAnimBlend;
class function_t;
class idEntity;
class idSaveGame;
class idRestoreGame;

typedef struct {
	int		cycleCount;	// how many times the anim has wrapped to the begining (0 for clamped anims)
	int		frame1;
	int		frame2;
	float	frontlerp;
	float	backlerp;
} frameBlend_t;

typedef struct {
	int						nameIndex;
	int						parentNum;
	int						animBits;
	int						firstComponent;
} jointAnimInfo_t;

typedef struct {
	jointHandle_t			num;
	jointHandle_t			parentNum;
	int						channel;
} jointInfo_t;

//
// joint modifier modes.  make sure to change script/doom_defs.script if you add any, or change their order.
//
typedef enum {
	JOINTMOD_NONE,				// no modification
	JOINTMOD_LOCAL,				// modifies the joint's position or orientation in joint local space
	JOINTMOD_LOCAL_OVERRIDE,	// sets the joint's position or orientation in joint local space
	JOINTMOD_WORLD,				// modifies joint's position or orientation in model space
	JOINTMOD_WORLD_OVERRIDE		// sets the joint's position or orientation in model space
} jointModTransform_t;

typedef struct {
	jointHandle_t			jointnum;
	idMat3					mat;
	idVec3					pos;
	jointModTransform_t		transform_pos;
	jointModTransform_t		transform_axis;
} jointMod_t;

#define	ANIM_TX				BIT( 0 )
#define	ANIM_TY				BIT( 1 )
#define	ANIM_TZ				BIT( 2 )
#define	ANIM_QX				BIT( 3 )
#define	ANIM_QY				BIT( 4 )
#define	ANIM_QZ				BIT( 5 )

typedef enum {
	FC_SCRIPTFUNCTION,
	FC_SCRIPTFUNCTIONOBJECT,
	FC_EVENTFUNCTION,
	FC_SOUND,
	FC_SOUND_VOICE,
	FC_SOUND_VOICE2,
	FC_SOUND_BODY,
	FC_SOUND_BODY2,
	FC_SOUND_BODY3,
	FC_SOUND_WEAPON,
	FC_SOUND_ITEM,
	FC_SOUND_GLOBAL,
	FC_SOUND_CHATTER,
	FC_SKIN,
	FC_TRIGGER,
	FC_TRIGGER_SMOKE_PARTICLE,
	FC_MELEE,
	FC_DIRECTDAMAGE,
	FC_BEGINATTACK,
	FC_ENDATTACK,
	FC_MUZZLEFLASH,
	FC_CREATEMISSILE,
	FC_LAUNCHMISSILE,
	FC_FIREMISSILEATTARGET,
	FC_FOOTSTEP,
	FC_LEFTFOOT,
	FC_RIGHTFOOT,
	FC_ENABLE_EYE_FOCUS,
	FC_DISABLE_EYE_FOCUS,
	FC_FX,
	FC_DISABLE_GRAVITY,
	FC_ENABLE_GRAVITY,
	FC_JUMP,
	FC_ENABLE_CLIP,
	FC_DISABLE_CLIP,
	FC_ENABLE_WALK_IK,
	FC_DISABLE_WALK_IK,
	FC_ENABLE_LEG_IK,
	FC_DISABLE_LEG_IK,
	FC_RECORDDEMO,
	FC_AVIGAME,

	/**
	* DarkMod:
	* FC_SETRATE sets the anim rate, used for speeding up/slowing down walking
	* and crouchwalking animations to get correct footstep sounds.
	**/
	FC_SETRATE,
	/**
	* Move an attachment to a different position
	**/
	FC_REATTACH,
	/**
	* Tels: Spawn an item (the item contains where to attach it)
	**/
	FC_ATTACH,
	/**
	* Tels: Detach and destroy the named attachement
	**/
	FC_DESTROY,
	/**
	* Tels: Detach and drop the named attachement
	**/
	FC_DROP,
	/**
	* Tels: Detach and put down the named attachement (e.g. restore origin and angles to "before pickup")
	**/
	FC_PUTDOWN,
	/**
	* Tels: Pickup an object (either the direct entity name, or the AIUSE class)
	**/
	FC_PICKUP,
	/**
	* Tels: Activate the attached entity at the named joint
	**/
	FC_ACTIVATE_AT_JOINT,
	/**
	* Tels: Activate the entity (either direct name or AIUSE class)
	**/
	FC_ACTIVATE_NEAR,
	/**
	* Pause the animation at its current frame, wait for unpause from somewhere else
	**/
	FC_PAUSE,
	/**
	* Holds a melee attack at a given point in the animation
	* (e.g., at the back swing in attacks, at the parry position in parries)
	* Similar to pause but also updates actor's melee status
	**/
	FC_MELEE_HOLD,
	FC_MELEE_ATTACK_START,
	FC_MELEE_ATTACK_STOP,
	FC_MELEE_PARRY_START,
	FC_MELEE_PARRY_STOP,
	FC_SET_ATTACK_FLAG,   // greebo: enables a certain attack type
	FC_CLEAR_ATTACK_FLAG, // greebo: disables a certain attack type
	FC_CREATEMISSILE_FROM_DEF,	// greebo: create a specific projectile def for ranged attack
	FC_MELEE_KNOCKOUT	// Obsttorte: alternative knockouts
} frameCommandType_t;

typedef struct {
	int						num;
	int						firstCommand;
} frameLookup_t;

typedef struct {
	frameCommandType_t		type;
	idStr					*string;

	union {
		const idSoundShader	*soundShader;
		const function_t	*function;
		const idDeclSkin	*skin;
		int					index;
	};
} frameCommand_t;

typedef struct {
	bool					prevent_idle_override		: 1;
	bool					random_cycle_start			: 1;
	bool					ai_no_turn					: 1;
	bool					anim_turn					: 1;
	bool					no_random_headturning		: 1;
	bool					has_voice_fc				: 1; // grayman #3182
} animFlags_t;


/*
==============================================================================================

	idModelExport

==============================================================================================
*/

class idModelExport {
private:
	void					Reset( void );
	bool					ParseOptions( idLexer &lex );
	int						ParseExportSection( idParser &parser );

	static bool				CheckMayaInstall( void );
	static void				LoadMayaDll( void );

	bool					ConvertMayaToMD5( void );
	static bool				initialized;

public:
	idStr					commandLine;
	idStr					src;
	idStr					dest;
	bool					force;

							idModelExport();

	static void				Shutdown( void );

	int						ExportDefFile( const char *filename );
	bool					ExportModel( const char *model );
	bool					ExportAnim( const char *anim );
	int						ExportModels( const char *pathname, const char *extension );
};

/*
==============================================================================================

	idMD5Anim

==============================================================================================
*/

class idMD5Anim {
private:
	int						numFrames;
	int						frameRate;
	int						animLength;
	int						numJoints;
	int						numAnimatedComponents;
	idList<idBounds>		bounds;
	idList<jointAnimInfo_t>	jointInfo;
	idList<idJointQuat>		baseFrame;
	idList<float>			componentFrames;
	idStr					name;
	idVec3					totaldelta;
	mutable int				ref_count;

public:
							idMD5Anim();
							~idMD5Anim();

	void					Free( void );
	bool					Reload( void );
	size_t					Allocated( void ) const;
	size_t					Size( void ) const { return sizeof( *this ) + Allocated(); };
	bool					LoadAnim( const char *filename );

	void					IncreaseRefs( void ) const;
	void					DecreaseRefs( void ) const;
	int						NumRefs( void ) const;
	
	void					CheckModelHierarchy( const idRenderModel *model ) const;
	void					GetInterpolatedFrame( frameBlend_t &frame, idJointQuat *joints, const int *index, int numIndexes ) const;
	void					GetSingleFrame( int framenum, idJointQuat *joints, const int *index, int numIndexes ) const;
	int						Length( void ) const;
	int						NumFrames( void ) const;
	int						NumJoints( void ) const;
	const idVec3			&TotalMovementDelta( void ) const;
	const char				*Name( void ) const;

	void					GetFrameBlend( int framenum, frameBlend_t &frame ) const;	// frame 1 is first frame
	void					ConvertTimeToFrame( int time, int cyclecount, frameBlend_t &frame ) const;

	void					GetOrigin( idVec3 &offset, int currentTime, int cyclecount ) const;
	void					GetOriginRotation( idQuat &rotation, int time, int cyclecount ) const;
	void					GetBounds( idBounds &bounds, int currentTime, int cyclecount ) const;

	/**
	* DarkMod: Set the framerate to something different from what's in the file.
	**/
	void					SetFrameRate( int frRate );
};

/*
==============================================================================================

	idAnim

==============================================================================================
*/

class idAnim {
private:
	const class idDeclModelDef	*modelDef;
	const idMD5Anim				*anims[ ANIM_MaxSyncedAnims ];
	int							numAnims;
	idStr						name;
	idStr						realname;
	idList<frameLookup_t>		frameLookup;
	idList<frameCommand_t>		frameCommands;
	animFlags_t					flags;

public:
								idAnim();
								idAnim( const idDeclModelDef *modelDef, const idAnim *anim );
								~idAnim();

	void						SetAnim( const idDeclModelDef *modelDef, const char *sourcename, const char *animname, int num, const idMD5Anim *md5anims[ ANIM_MaxSyncedAnims ] );
	const char					*Name( void ) const;
	const char					*FullName( void ) const;
	const idMD5Anim				*MD5Anim( int num ) const;
	const idDeclModelDef		*ModelDef( void ) const;
	int							Length( void ) const;
	int							NumFrames( void ) const;
	int							NumAnims( void ) const;
	const idVec3				&TotalMovementDelta( void ) const;
	bool						GetOrigin( idVec3 &offset, int animNum, int time, int cyclecount ) const;
	bool						GetOriginRotation( idQuat &rotation, int animNum, int currentTime, int cyclecount ) const;
	bool						GetBounds( idBounds &bounds, int animNum, int time, int cyclecount ) const;
	const char					*AddFrameCommand( const class idDeclModelDef *modelDef, int framenum, idLexer &src, const idDict *def, animFlags_t *flags ); // grayman #3182
	void						CallFrameCommands( idEntity *ent, int from, int to, idAnimBlend *caller );
	bool						HasFrameCommands( void ) const;

								// returns first frame (zero based) that command occurs.  returns -1 if not found.
	int							FindFrameForFrameCommand( frameCommandType_t framecommand, const frameCommand_t **command ) const;
	void						SetAnimFlags( const animFlags_t &animflags );
	const animFlags_t			&GetAnimFlags( void ) const;
};

/*
==============================================================================================

	idDeclModelDef

==============================================================================================
*/

class idDeclModelDef : public idDecl {
public:
								idDeclModelDef();
	virtual						~idDeclModelDef() override;

	virtual size_t				Size( void ) const override;
	virtual const char *		DefaultDefinition( void ) const override;
	virtual bool				Parse( const char *text, const int textLength ) override;
	virtual void				FreeData( void ) override;

	void						Touch( void ) const;

	const idDeclSkin *			GetDefaultSkin( void ) const;
	const idJointQuat *			GetDefaultPose( void ) const;
	void						SetupJoints( int *numJoints, idJointMat **jointList, idBounds &frameBounds, bool removeOriginOffset ) const;
	idRenderModel *				ModelHandle( void ) const;
	void						GetJointList( const char *jointnames, idList<jointHandle_t> &jointList ) const;
	const jointInfo_t *			FindJoint( const char *name ) const;

	int							NumAnims( void ) const;
	const idAnim *				GetAnim( int index ) const;
	int							GetSpecificAnim( const char *name ) const;
	int							GetAnim( const char *name ) const;
	bool						HasAnim( const char *name ) const;
	const idDeclSkin *			GetSkin( void ) const;
	const char *				GetModelName( void ) const;
	const idList<jointInfo_t> &	Joints( void ) const;
	const int *					JointParents( void ) const;
	int							NumJoints( void ) const;
	const jointInfo_t *			GetJoint( int jointHandle ) const;
	const char *				GetJointName( int jointHandle ) const;
	int							NumJointsOnChannel( int channel ) const;
	const int *					GetChannelJoints( int channel ) const;

	const idVec3 &				GetVisualOffset( void ) const;

private:
	void						CopyDecl( const idDeclModelDef *decl );
	bool						ParseAnim( idLexer &src, int numDefaultAnims );

private:
	idVec3						offset;
	idList<jointInfo_t>			joints;
	idList<int>					jointParents;
	idList<int>					channelJoints[ ANIM_NumAnimChannels ];
	idRenderModel *				modelHandle;
	idList<idAnim *>			anims;
	const idDeclSkin *			skin;
};

/*
==============================================================================================

	idAnimBlend

==============================================================================================
*/

class idAnimBlend {
private:
	const class idDeclModelDef	*modelDef;
	int							starttime;
	int							endtime;
	int							timeOffset;
	float						rate;

	int							blendStartTime;
	int							blendDuration;
	float						blendStartValue;
	float						blendEndValue;

	float						animWeights[ ANIM_MaxSyncedAnims ];
	short						cycle;
	short						frame;
	short						animNum;
	bool						allowMove;
	bool						allowFrameCommands;
	/**
	* TDM: This animation is paused at the current frame
	**/
	bool						m_bPaused;
	/**
	* endtime and cycle before we paused
	* The following are used for proper re-entry when unpausing
	**/
	int							m_PausedEndtime;
	short						m_PausedCycle;
	/**
	* Time at which we paused
	**/
	int							m_PausedTime;

	friend class				idAnimator;

	void						Reset( const idDeclModelDef *_modelDef );
	void						CallFrameCommands( idEntity *ent, int fromtime, int totime ) const;
	void						SetFrame( const idDeclModelDef *modelDef, int animnum, int frame, int currenttime, int blendtime, const idEntity *ent );
	void						CycleAnim( const idDeclModelDef *modelDef, int animnum, int currenttime, int blendtime, const idEntity *ent );
	void						PlayAnim( const idDeclModelDef *modelDef, int animnum, int currenttime, int blendtime, const idEntity *ent );
	bool						BlendAnim( int currentTime, int channel, int numJoints, idJointQuat *blendFrame, float &blendWeight, bool removeOrigin, bool overrideBlend, bool printInfo ) const;
	void						BlendOrigin( int currentTime, idVec3 &blendPos, float &blendWeight, bool removeOriginOffset ) const;
	void						BlendDelta( int fromtime, int totime, idVec3 &blendDelta, float &blendWeight ) const;
	void						BlendDeltaRotation( int fromtime, int totime, idQuat &blendDelta, float &blendWeight ) const;
	bool						AddBounds( int currentTime, idBounds &bounds, bool removeOriginOffset ) const;

public:
								idAnimBlend();
	void						Save( idSaveGame *savefile ) const;
	void						Restore( idRestoreGame *savefile, const idDeclModelDef *modelDef );
	const char					*AnimName( void ) const;
	const char					*AnimFullName( void ) const;
	float						GetWeight( int currenttime ) const;
	float						GetFinalWeight( void ) const;
	void						SetWeight( float newweight, int currenttime, int blendtime );
	int							NumSyncedAnims( void ) const;
	bool						SetSyncedAnimWeight( int num, float weight );
	void						Clear( int currentTime, int clearTime );
	bool						IsDone( int currentTime ) const;
	bool						FrameHasChanged( int currentTime ) const;
	int							GetCycleCount( void ) const;
	void						SetCycleCount( int count );
	void						SetPlaybackRate( int currentTime, float newRate );
	float						GetPlaybackRate( void ) const;
	// Ishtvan test: Making this public
	/** TDM: UpdatePlaybackRate sets the playback rate to the one given by animnum in ent->m_animRates */
	void						UpdatePlaybackRate(int animnum, const idEntity *ent);
	void						SetStartTime( int startTime );
	int							GetStartTime( void ) const;
	int							GetEndTime( void ) const;
	int							GetFrameNumber( int currenttime ) const;
	int							AnimTime( int currenttime ) const;
	int							NumFrames( void ) const;
	int							Length( void ) const;
	int							PlayLength( void ) const;
	void						AllowMovement( bool allow );
	void						AllowFrameCommands( bool allow );
	bool						FrameCommandsAllowed( void ) const; // SteveL #3800
	const idAnim				*Anim( void ) const;
	int							AnimNum( void ) const;
	/**
	* Pause (true) or unpause (false) the animation at the current frame
	**/
	void						Pause( bool bPause );
	bool						IsPaused( void );
	void						CopyStateData( const idAnimBlend& other ); // for LOD swaps during an animation -- SteveL #3834
};

/*
==============================================================================================

	idAFPoseJointMod

==============================================================================================
*/

typedef enum {
	AF_JOINTMOD_AXIS,
	AF_JOINTMOD_ORIGIN,
	AF_JOINTMOD_BOTH,
	AF_JOINTMOD_NONE // Added for TDM
} AFJointModType_t;

class idAFPoseJointMod {
public:
								idAFPoseJointMod( void );

	AFJointModType_t			mod;
	idMat3						axis;
	idVec3						origin;
};

ID_INLINE idAFPoseJointMod::idAFPoseJointMod( void ) {
	mod = AF_JOINTMOD_AXIS;
	axis.Identity();
	origin.Zero();
}

/*
==============================================================================================

	idAnimator

==============================================================================================
*/

class idAnimator {
public:
								idAnimator();
								~idAnimator();

	size_t						Allocated( void ) const;
	size_t						Size( void ) const;

	void						Save( idSaveGame *savefile ) const;					// archives object for save game file
	void						Restore( idRestoreGame *savefile );					// unarchives object from save game file

	void						SetEntity( idEntity *ent );
	idEntity					*GetEntity( void ) const ;
	void						RemoveOriginOffset( bool remove );
	bool						RemoveOrigin( void ) const;

	void						GetJointList( const char *jointnames, idList<jointHandle_t> &jointList ) const;

	int							NumAnims( void ) const;
	const idAnim				*GetAnim( int index ) const;
	int							GetAnim( const char *name ) const;
	bool						HasAnim( const char *name ) const;

	void						ServiceAnims( int fromtime, int totime );
	bool						IsAnimating( int currentTime ) const;

	void						GetJoints( int *numJoints, idJointMat **jointsPtr );
	int							NumJoints( void ) const;
	jointHandle_t				GetFirstChild( jointHandle_t jointnum ) const;
	jointHandle_t				GetFirstChild( const char *name ) const;

	idRenderModel				*SetModel( const char *modelname );
	idRenderModel				*SwapLODModel( const char *modelname ); // SteveL #3770
	idRenderModel				*ModelHandle( void ) const;
	const idDeclModelDef		*ModelDef( void ) const;

	void						ForceUpdate( void );
	void						ClearForceUpdate( void );
	bool						CreateFrame( int animtime, bool force );
	bool						FrameHasChanged( int animtime ) const;
	void						GetDelta( int fromtime, int totime, idVec3 &delta ) const;
	bool						GetDeltaRotation( int fromtime, int totime, idMat3 &delta ) const;
	void						GetOrigin( int currentTime, idVec3 &pos ) const;
	bool						GetBounds( int currentTime, idBounds &bounds );

	idAnimBlend					*CurrentAnim( int channelNum );
	void						Clear( int channelNum, int currentTime, int cleartime );
	void						SetFrame( int channelNum, int animnum, int frame, int currenttime, int blendtime );
	void						CycleAnim( int channelNum, int animnum, int currenttime, int blendtime );
	void						PlayAnim( int channelNum, int animnum, int currenttime, int blendTime );

								// copies the current anim from fromChannelNum to channelNum.
								// the copied anim will have frame commands disabled to avoid executing them twice.
	void						SyncAnimChannels( int channelNum, int fromChannelNum, int currenttime, int blendTime );

	void						SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos );
	void						SetJointAxis( jointHandle_t jointnum, jointModTransform_t transform_type, const idMat3 &mat );
	void						ClearJoint( jointHandle_t jointnum );
	void						ClearAllJoints( void );

	void						InitAFPose( void );
	void						SetAFPoseJointMod( const jointHandle_t jointNum, const AFJointModType_t mod, const idMat3 &axis, const idVec3 &origin );
	void						FinishAFPose( int animnum, const idBounds &bounds, const int time );
	void						SetAFPoseBlendWeight( float blendWeight );
	bool						BlendAFPose( idJointQuat *blendFrame ) const;
	void						ClearAFPose( void );

	void						ClearAllAnims( int currentTime, int cleartime );

	jointHandle_t				GetJointHandle( const char *name ) const;
	const char *				GetJointName( jointHandle_t handle ) const;
	int							GetChannelForJoint( jointHandle_t joint ) const;
	bool						GetJointTransform( jointHandle_t jointHandle, int currenttime, idVec3 &offset, idMat3 &axis );
	bool						GetJointLocalTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis );

	const animFlags_t			GetAnimFlags( int animnum ) const;
	int							NumFrames( int animnum ) const;
	int							NumSyncedAnims( int animnum ) const;
	const char					*AnimName( int animnum ) const;
	const char					*AnimFullName( int animnum ) const;
	int							AnimLength( int animnum ) const;
	const idVec3				&TotalMovementDelta( int animnum ) const;

private:
	void						FreeData( void );
	void						PushAnims( int channel, int currentTime, int blendTime );

private:
	const idDeclModelDef *		modelDef;
	idEntity *					entity;

	idAnimBlend					channels[ ANIM_NumAnimChannels ][ ANIM_MaxAnimsPerChannel ];
	idList<jointMod_t *>		jointMods;
	int							numJoints;
	idJointMat *				joints;

	mutable int					lastTransformTime;		// mutable because the value is updated in CreateFrame
	mutable bool				stoppedAnimatingUpdate;
	bool						removeOriginOffset;
	bool						forceUpdate;

	idBounds					frameBounds;

	float						AFPoseBlendWeight;
	idList<int>					AFPoseJoints;
	idList<idAFPoseJointMod>	AFPoseJointMods;
	idList<idJointQuat>			AFPoseJointFrame;
	idBounds					AFPoseBounds;
	int							AFPoseTime;
};

/*
==============================================================================================

	idAnimManager

==============================================================================================
*/

class idAnimManager {
public:
								idAnimManager();
								~idAnimManager();

	static bool					forceExport;

	void						Shutdown( void );
	idMD5Anim *					GetAnim( const char *name );
	void						ReloadAnims( void );
	void						ListAnims( void ) const;
	int							JointIndex( const char *name );
	const char *				JointName( int index ) const;

	void						ClearAnimsInUse( void );
	void						FlushUnusedAnims( void );

private:
	idHashTable<idMD5Anim *>	animations;
	idStrList					jointnames;
	idHashIndex					jointnamesHash;
};

#endif /* !__ANIM_H__ */
