/*********************************************************************//**
*	\brief 3D Mass for simulation.
*	Definice zakladnich objektu pro funkcnost spring systemu. Zde je
*	definovana Castice, ktera je zakladnim hmotnym objektem v systemu
*	a pak pruzina, ktera definuje silove vztahy mezi casticemi. Posledni
*	trida SpringSystemBase definuje zakladni funkce pro vypocet
*	simulace a vytvoreni vsech potrebnych dat. Vyhodou systemu je, ze
*	pomoci uzivatelskych dat lze v odvozenych tridach reagovat a
*	zpracovavat udalosti z venci.
*
*	\author Michal Jirous
*	\date 16.12.2008
*	\file spring_def.h
**********************************************************************/


#ifndef SPRING_DEF_H
#define SPRING_DEF_H

#include "algebraic.h"
#include "physics.h"

const float PIXELS_PER_METER = POINTS_PER_METER;

/** @brief Jedna castice systemu. */
struct Mass
{
	void *user_data;		/*!< @brief Dodatecne informace. */
	Point *m_pPosition;		/*!< @brief Pozice castice. */
	Vector m_vecVelocity;	/*!< @brief Vektor rychlosti castice. */
	Vector *m_vecForce;		/*!< @brief Sila pusobici na castici. */
	float m_fWeight;		/*!< @brief Hmotnost castice. */

	/** @brief Zakladni inifializacni konstruktor. */
	Mass()
	{
		memset( this, 0, sizeof( *this ) );
	}

	/** @brief Konstruktor nastavuje vsechny potrebne parametry. 
	*	@param position_store_3f Ukazatel na pole tri souradnic pozice.
	*	@param force_store_3f Ukazatel na pole tri souradnic sily.
	*	@param weight Hmotnost castice.
	*/
	Mass( float *position_store_3f, float *force_store_3f, float weight )
	{
		user_data = NULL;
		m_pPosition = (Point*)position_store_3f;
		m_vecForce = (Vector*)force_store_3f;
		m_fWeight = weight;
	}

	/** @brief Funkce pricte vector k sile.
	*	@param force Vektor sily, ktery se ma pricist.
	*/
	void addForce( Vector &force )
	{
		*m_vecForce += force;
	}

	/** @brief Funkce provede simulaci, dle uplynuleho casu, takze castice zmeni svou pozici v zavislosti na case a vysledne sile.
	*	@param dt Uplynuly cas.
	*/
	void simulate( float dt )
	{
		m_vecVelocity += ( *m_vecForce / m_fWeight) * dt;	// Zmena rychlosti je prictena k aktualni rychlosti
		*m_pPosition += m_vecVelocity * dt * PIXELS_PER_METER;					// Zmena polohy je prictena k aktualni poloze
	}
};


/** @brief Pruzina. */
struct Spring
{
	Mass *m_pMassA, *m_pMassB;
	
	float m_fSpringConsistency;		/*!< @brief Tuhost pruziny. */
	float m_fSpringBalanceLength;	/*!< @brief Klidova delka pruziny. */
	float m_fSpringFriction;		/*!< @brief Vnitrni treni. */

	/** @brief Konstruktor s nastavenim parametru.
	*	@param A Castice na zacatku pruziny.
	*	@param B Castice na konci pruziny.
	*	@param consistency Tuhost pruziny.
	*	@param balance_length Klidova delka pruziny.
	*	@param friction Vnitrni treni.
	*/
	Spring( Mass *A, Mass *B, float consistency, float balance_length, float friction )
	{
		m_pMassA = A;
		m_pMassB = B;
		m_fSpringConsistency = consistency;
		m_fSpringBalanceLength = balance_length;
		m_fSpringFriction = friction;

	}

	/** @brief Aktualizace sil pusobicich na castice. */
	void update()
	{
		Vector springVector = (*(m_pMassA->m_pPosition)) - (*(m_pMassB->m_pPosition)) ;
	
		float r = springVector.absolute();

		Vector force;
		
		if( compareFloats( r, 0.0f, 0.0f ) != 0 )
		{
			force += (springVector / r) * (r - m_fSpringBalanceLength) * (-m_fSpringConsistency) / PIXELS_PER_METER;// Výpočet síly
		}

		force += -(m_pMassA->m_vecVelocity - m_pMassB->m_vecVelocity) * m_fSpringFriction;// Zmenšení síly o tření

		m_pMassA->addForce( force );
		m_pMassB->addForce( -force );
	}
};

#define Abstract


/** @brief Zakladni predpis tridy pro casticovy system pruzin. */
Abstract class SpringSystemBase
{
protected:
	float *m_fPoints;		/*!< @brief Pole bodu. */
	float *m_fTexCoords;	/*!< @brief Pole texturovacich souradnic. */
	float *m_fForces;		/*!< @brief Pole sil castic Mass. */
	Mass **m_pMasses;		/*!< @brief Pole castic Mass. */
	Spring **m_pSprings;	/*!< @brief Pole pruzin mezi casticemi. */
	unsigned int	m_uiNumMass,	/*!< @brief Pocet castic. */
					m_uiNumSprings;	/*!< @brief Pocet pruzin. */
	float			m_fGravity,		/*!< @brief Velikost gravitacniho zrychleni. */
					m_fWeight;		/*!< @brief Pocatecni hmotnost castic. */
	Vector m_vecOutsideForce;		/*!< @brief Vektor pusobici z venci. */


	/** @brief Uzivatelska funkce, ktera se vola, pokud castice Mass obsahuje uzivatelska data v promenne user_data. 
	*	@param pMass Castice, ktera funkci vyvolala.
	*	@param dt Cas uplynuly od posledni aktualizace.
	*	@return False, pokud se ma nasledne provest standardni vypocet nove pozice, True, pokud se funkce o vypocet postarala sama.
	*/
	virtual bool mass_user_update( Mass *pMass, float dt ){ return false; }	
public:
	/** @brief Inicializacni konstruktor. */
	SpringSystemBase()
	{
		memset( this, 0, sizeof( *this ) );
	}

	 

	/** @brief Funkce provede aktualizace pozic vsech castic Mass. 
	*	@param df Cas, ktery ubehl od posledni aktualizace.
	*/
	virtual void update( float dt )
	{
		memset( m_fForces, 0, m_uiNumMass * 3 * sizeof( float ) );	//reset vsech sil

		Spring **tmpSpring = m_pSprings;
		while( *tmpSpring )
		{
			(*tmpSpring)->update();
			tmpSpring++;
		}

		Mass **tmpMass = m_pMasses;
		while( *tmpMass )
		{
			//kdyz ma Mass nejaky user data, tak se vola uzivatelska update funkce, kde se muze neco zmenit
			if( !(*tmpMass)->user_data || !mass_user_update( (*tmpMass), dt ) )
			{
				(*tmpMass)->addForce( Vector( 0,0, -m_fGravity * (*tmpMass)->m_fWeight) + m_vecOutsideForce );
				(*tmpMass)->simulate( dt );
			}
			tmpMass++;
		}
	}

	/** @brief Zakladni destruktor, ktery smaze vsechna data. */
	virtual ~SpringSystemBase()
	{
		delete [] m_fPoints;
		delete [] m_fTexCoords;
		delete [] m_fForces;

		Spring *tmpSpring = *m_pSprings;
		while( tmpSpring )
		{
			delete tmpSpring;
			tmpSpring++;
		}

		Mass *tmpMass = *m_pMasses;
		while( tmpMass )
		{
			delete tmpMass;
			tmpMass++;
		}

		delete [] m_pMasses;
		delete [] m_pSprings;
	}

	/** @brief Funkce pripravy potrebna uloziste dat.
	*	@param uiNumMass Pocet castic v systemu.
	*	@param uiNumSprings Pocet pruzin v systemu.
	*/
	void prepare( const unsigned int uiNumMass, const unsigned int uiNumSprings )
	{
		m_uiNumMass = uiNumMass;
		m_uiNumSprings = uiNumSprings;

		m_fPoints = new float[uiNumMass*3];
		m_fTexCoords = new float[uiNumMass*2];
		m_fForces = new float[uiNumMass*3];
		m_pMasses = new Mass*[uiNumMass + 1];
		for( unsigned int i = 0; i < uiNumMass; i++ )
			m_pMasses[i] = new Mass( &m_fPoints[i*3], &m_fForces[i*3], m_fWeight );
		m_pMasses[uiNumMass] = NULL;

		m_pSprings = new Spring*[uiNumSprings + 1];
		m_pSprings[uiNumSprings] = NULL;

		memset( m_fPoints, 0, uiNumMass*3*sizeof( float ) );
		memset( m_fTexCoords, 0, uiNumMass*2*sizeof( float ) );
		memset( m_fForces, 0, uiNumMass*3*sizeof( float ) );
	}


	/** @brief Nastavi velikost vektoru gravitace.
	*	@param g Velikost vektoru gravitace.
	*/
	void setGravity( float g )
	{
		m_fGravity = g;
	}
	
	/** @brief Nastavi pocatecni hmotnost castic.
	*	@param w Hmotnost.
	*/
	void setWeight( float w )
	{
		m_fWeight = w;
	}

	/** @brief Nastavi silu z venci, ktera se bude pripocitavat k vysledne sile pucobici na Mass.
	*	@param f Sila pusobici na body.
	*/
	void setOutsideForce( Vector &f )
	{
		m_vecOutsideForce = f;
	}

	virtual void draw() = 0;	/*!< @brief Funkce vykreslovani. */
};

#endif

