//========= Copyright © 1996-2007, Valve Corporation, All rights reserved. ============//
//
// Purpose: Data types used inside constraints for the purpose of playing sounds
//			during movement.
//
//=============================================================================//

#ifndef PHYSCONSTRAINT_SOUNDS_H
#define PHYSCONSTRAINT_SOUNDS_H
#ifdef _WIN32
#pragma once
#endif


#include <mathlib/ssemath.h>
#include "soundenvelope.h"


/** \brief Class to store a sampled history of velocity for an object -- used for certain sound calculations

Although this contains only one sample for now, it exists as an interface 
so as to make simpler the possibility of moving to a ring buffer 
implementation in the future. 

The "sample rate" variable is not nominal: it should be used to specify 
the ClientThink() interval. 

Be sure to use the beginSampling() function for the first sample, and 
addSample() thereafter: this will be relevant and necessary for a ring
buffer implementation (which will have to perform certain initialization).
*/
class VelocitySampler
{	
public:
	/*
	enum
	{
	HISTORY_DEPTH_LOG	= 3, // < log-base-2 of the sampler's array depth
	HISTORY_DEPTH		= (1 << VELOCITY_SAMPLER_HISTORY_DEPTH_LOG),
	};
	*/

	/// Return the internally stored sample rate.
	inline float getSampleRate() 
	{
		return m_fIdealSampleRate;
	}


	/// Store off the first recorded sample for the given object.
	inline void BeginSampling(const Vector &relativeVelocity);

	/// Record a sample. Do this LAST, after calling hasReversed() et al.
	inline void AddSample(const Vector &relativeVelocity);

	/// Using the sample history, determine if the object has reversed direction
	/// with at least the given acceleration (in units/sec^2).
	int HasReversed(const Vector &relativeVelocity, const float thresholdAcceleration[], const unsigned short numThresholds);

	/// Call this in spawn(). (Not a constructor because those are difficult to use in entities.)
	void Initialize(float samplerate);


	/// A convenience function for extracting the linear velocity of one object relative to another.
	inline static Vector GetRelativeVelocity(IPhysicsObject *pObj,	IPhysicsObject *pReferenceFrame);

	/// A convenience function for extracting the angular velocity of one object relative to another.
	inline static Vector GetRelativeAngularVelocity(IPhysicsObject *pObj,	IPhysicsObject *pReferenceFrame);


protected:
	Vector m_prevSample;
	float m_fPrevSampleTime;

	float m_fIdealSampleRate;

};

struct SimpleConstraintSoundProfile
{
	// define the indices of the sound points:
	enum
	{
		kMIN_THRESHOLD, ///< below this no sound is played
		kMIN_FULL,      ///< at this velocity sound is at its loudest

		kHIGHWATER,	///< high water mark for this enum
	} eKeypoints;

	float m_keyPoints[kHIGHWATER];

	/// Number of entries in the reversal sound array 
	enum { kREVERSAL_SOUND_ARRAY_SIZE = 3 };

	/// Acceleration threshold for playing the hard-reverse sound. Divided into sections.
	/// Below the 0th threshold no sound will play.
	float m_reversalSoundThresholds[kREVERSAL_SOUND_ARRAY_SIZE]; 

	/// Get volume for given velocity [0..1]
	float GetVolume(float inVel);
};

float SimpleConstraintSoundProfile::GetVolume(float inVel)
{
	// clamped lerp on 0-1
	if (inVel <= m_keyPoints[kMIN_THRESHOLD])
	{
		return 0;
	}
	else if (inVel >= m_keyPoints[kMIN_FULL])
	{
		return 1;
	}
	else	// lerp...
	{
		return (inVel - m_keyPoints[kMIN_THRESHOLD])/(m_keyPoints[kMIN_FULL] - m_keyPoints[kMIN_THRESHOLD]);
	}
}

class CPhysConstraint;
/** This class encapsulates the data and behavior necessary for a constraint to play sounds.

	For the moment I have no easy means of populating this from an entity's datadesc.
	You should explicitly fill out the fields with eg

	DEFINE_KEYFIELD( m_soundInfo.m_soundProfile.m_keyPoints[SimpleConstraintSoundProfile::kMIN_THRESHOLD] , FIELD_FLOAT, "minSoundThreshold" ),
	DEFINE_KEYFIELD( m_soundInfo.m_soundProfile.m_keyPoints[SimpleConstraintSoundProfile::kMIN_FULL] , FIELD_FLOAT, "maxSoundThreshold" ),
	DEFINE_KEYFIELD( m_soundInfo.m_iszTravelSoundFwd, FIELD_SOUNDNAME, "slidesoundfwd" ),
	DEFINE_KEYFIELD( m_soundInfo.m_iszTravelSoundBack, FIELD_SOUNDNAME, "slidesoundback" ),
	DEFINE_KEYFIELD( m_soundInfo.m_iszReversalSound, FIELD_SOUNDNAME, "reversalsound" ),
	DEFINE_KEYFIELD( m_soundInfo.m_soundProfile.m_reversalSoundThreshold , FIELD_FLOAT, "reversalsoundthreshold" ),

 */
class ConstraintSoundInfo
{
public:
	// no ctor.
	// dtor
	~ConstraintSoundInfo();

	/// Call from the constraint's Activate()
	void OnActivate( CPhysConstraint *pOuter );

	/// Constraint should have a think function that calls this. It should pass in relative velocity
	/// between child and parent. (This need not be linear velocity; it may be angular.)
	void OnThink( CPhysConstraint *pOuter, const Vector &relativeVelocity );

	/// This is how often the think function should be run:
	inline float getThinkRate() const { return 0.09f; }

	/// Call this before the first call to OnThink()
	void StartThinking( CPhysConstraint *pOuter, const Vector &relativeVelocity, const Vector &forwardVector );

	/// Call this if you intend to stop calling OnThink():
	void StopThinking( CPhysConstraint *pOuter );

	/// Call from owner's Precache().
	void OnPrecache( CPhysConstraint *pOuter );


	VelocitySampler m_vSampler;
	SimpleConstraintSoundProfile m_soundProfile;

	Vector m_forwardAxis; ///< velocity in this direction is forward. The opposite direction is backward.

	string_t m_iszTravelSoundFwd,m_iszTravelSoundBack;			// Path/filename of WAV file to play.
	CSoundPatch		*m_pTravelSound;
	bool			m_bPlayTravelSound;

	string_t m_iszReversalSounds[SimpleConstraintSoundProfile::kREVERSAL_SOUND_ARRAY_SIZE];			// Path/filename of WAV files to play -- one per entry in threshold.
	// CSoundPatch		*m_pReversalSound;
	bool			m_bPlayReversalSound;

protected:
	/// Maintain consistency of internal datastructures on start
	void ValidateInternals( CPhysConstraint *pOuter );

	/// Stop playing any active sounds.
	void DeleteAllSounds();
};


/////////////// INLINE FUNCTIONS
	

/// compute the relative velocity between an object and its parent. Just a convenience.
Vector VelocitySampler::GetRelativeVelocity( IPhysicsObject *pObj, IPhysicsObject *pReferenceFrame )
{
	Vector childVelocity, parentVelocity;
	pObj->GetImplicitVelocity( &childVelocity, NULL );
	pReferenceFrame->GetImplicitVelocity(&parentVelocity, NULL);

	return (childVelocity - parentVelocity);
}


Vector VelocitySampler::GetRelativeAngularVelocity( IPhysicsObject *pObj, IPhysicsObject *pReferenceFrame )
{
	Assert(pObj);

	if ( pReferenceFrame )
	{
		Vector childVelocityLocal, parentVelocityLocal, childVelocityWorld, parentVelocityWorld;
		pObj->GetImplicitVelocity( NULL, &childVelocityLocal );
		pObj->LocalToWorldVector( &childVelocityWorld, childVelocityLocal );
		pReferenceFrame->GetImplicitVelocity( NULL, &parentVelocityLocal );
		pObj->LocalToWorldVector( &parentVelocityWorld, parentVelocityLocal );

		return (childVelocityWorld - parentVelocityWorld);
	}
	else
	{
		Vector childVelocityLocal, childVelocityWorld;
		pObj->GetImplicitVelocity( NULL, &childVelocityLocal );
		pObj->LocalToWorldVector( &childVelocityWorld, childVelocityLocal );

		return (childVelocityWorld);
	}
}

/************************************************************************/
// This function is nominal -- it's here as an interface because in the 
// future there will need to be special initialization for the first entry
// in a ring buffer. (I made a test implementation of this, then reverted it
// later; this is not an arbitrary assumption.)
/************************************************************************/
/// Store off the first recorded sample for the given object.
void VelocitySampler::BeginSampling(const Vector &relativeVelocity)
{
	return AddSample(relativeVelocity);
}

// Record a sample for the given object
void VelocitySampler::AddSample(const Vector &relativeVelocity)
{
	m_prevSample = relativeVelocity;
	m_fPrevSampleTime = gpGlobals->curtime;
}



/* // abandoned -- too complicated, no way to set from keyfields
#pragma warning(push)
#pragma warning( disable:4201 ) // C4201: nonstandard extension used: nameless struct/union
/// Stores information used for playing sounds based on 
/// constraint movement
class ConstraintSoundProfile
{
public:
/// Defines a point in the sound profile: volume and pitch for the sound to play.
/// Implicit crossfading between two sounds. Used to map velocity to a sound profile.
struct SoundInfoTuple
{
float minVelocity;
union {
struct{
float volume1,pitch1; //< volume and pitch of sound 1
float volume2,pitch2; //< volume and pitch of sound 2
};
fltx4 m_as4;
};

inline SoundInfoTuple(float _minVelocity, float _volume1, float _pitch1, float _volume2, float _pitch2) :
minVelocity(_minVelocity), volume1(_volume1), pitch1(_pitch1), volume2(_volume2), pitch2(_pitch2) 
{}
};

ConstraintSoundProfile(const SoundInfoTuple *soundTable, unsigned int tableSize) 
: m_pSoundInfos(soundTable), m_numSoundInfos(tableSize) 
{}


protected:

/// A table of sound info structs
const SoundInfoTuple * const m_pSoundInfos;
/// Size of the table
const unsigned int m_numSoundInfos;
};

static ConstraintSoundProfile::SoundInfoTuple CSDebugProfileTable[] =
{
ConstraintSoundProfile::SoundInfoTuple(12,0,0,0,0),
ConstraintSoundProfile::SoundInfoTuple(24,0,0,0,0),

};
#pragma warning(pop)
*/


#endif
