/*
**	Command & Conquer Generals(tm)
**	Copyright 2025 Electronic Arts 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/>.
*/

////////////////////////////////////////////////////////////////////////////////
//																																						//
//  (c) 2001-2003 Electronic Arts Inc.																				//
//																																						//
////////////////////////////////////////////////////////////////////////////////

// FILE: Thing.cpp ////////////////////////////////////////////////////////////
// Created:   Colin Day, May 2001
//
// Desc:      Things are the base class for objects and drawables, objects
//						are logic side representations while drawables are client
//						side.  Common data will be held in the Thing defined here
//						and systems that need to work with both of them will work with
//						"Things"
//
//-----------------------------------------------------------------------------
#include "PreRTS.h"	// This must go first in EVERY cpp file int the GameEngine

#include "Common/PerfTimer.h"
#include "Common/Thing.h"
#include "Common/ThingTemplate.h"
#include "Common/ThingFactory.h"
#include "Common/GlobalData.h"
#include "Common/NameKeyGenerator.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/Team.h"
#include "Lib/Trig.h"
#include "GameLogic/TerrainLogic.h"

#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif

//=============================================================================
/** Constructor */
//=============================================================================
Thing::Thing( const ThingTemplate *thingTemplate ) 
{
	// sanity
	if( thingTemplate == NULL )
	{
	
		// cannot create thing without template
		DEBUG_CRASH(( "no template" ));
		return;

	}  // end if
		
	m_template = thingTemplate;
#if defined(_DEBUG) || defined(_INTERNAL)
	m_templateName = thingTemplate->getName();
#endif
	m_transform.Make_Identity();
	m_cachedPos.zero();
	m_cachedAngle = 0.0f;
	m_cachedDirVector.zero();
	m_cachedAltitudeAboveTerrain = 0;
	m_cachedAltitudeAboveTerrainOrWater = 0;
	m_cacheFlags = 0;

}

//=============================================================================
/** Destructor */
//=============================================================================
Thing::~Thing()
{
}

//DECLARE_PERF_TIMER(ThingMatrixStuff)

//=============================================================================
const ThingTemplate *Thing::getTemplate() const
{
	return m_template;
}

//=============================================================================
const Coord3D* Thing::getUnitDirectionVector2D() const
{
	//USE_PERF_TIMER(ThingMatrixStuff)
	if (!(m_cacheFlags & VALID_DIRVECTOR))
	{
		Real angle = getOrientation();
		m_cachedDirVector.x = Cos( angle );
		m_cachedDirVector.y = Sin( angle );
		m_cachedDirVector.z = 0;
		m_cacheFlags |= VALID_DIRVECTOR;
	}

	return &m_cachedDirVector;
}

//=============================================================================
void Thing::getUnitDirectionVector2D(Coord3D& dir) const
{
	dir = *getUnitDirectionVector2D();
}

//=============================================================================
void Thing::getUnitDirectionVector3D(Coord3D& dir) const
{
	Vector3 vdir = m_transform.Get_X_Vector();
	vdir.Normalize();
	dir.x = vdir.X;
	dir.y = vdir.Y;
	dir.z = vdir.Z;
}

//=============================================================================
// the nice thing about this is that we don't have to recalc out cached terrain stuff.
void Thing::setPositionZ( Real z )
{
	//USE_PERF_TIMER(ThingMatrixStuff)
	if( !m_template->isKindOf( KINDOF_STICK_TO_TERRAIN_SLOPE) )
	{
		Real oldAngle = m_cachedAngle;
		Coord3D oldPos = m_cachedPos;
		Matrix3D oldMtx = m_transform;

		m_transform.Set_Z_Translation( z );
		m_cachedPos.z = z;

		if (m_cacheFlags & VALID_ALTITUDE_TERRAIN)
		{
			m_cachedAltitudeAboveTerrain += (z - oldPos.z);
		}
		if (m_cacheFlags & VALID_ALTITUDE_SEALEVEL)
		{
			m_cachedAltitudeAboveTerrainOrWater += (z - oldPos.z);
		}

		reactToTransformChange(&oldMtx, &oldPos, oldAngle);
	}
	else
	{
		Matrix3D mtx;
		const Bool stickToGround = true;	// yes, set the "z" pos		
		Coord3D pos = m_cachedPos;
		pos.z = z;
		TheTerrainLogic->alignOnTerrain(getOrientation(), pos, stickToGround, mtx );
		setTransformMatrix(&mtx);
	}
	DEBUG_ASSERTCRASH(!(_isnan(getPosition()->x) || _isnan(getPosition()->y) || _isnan(getPosition()->z)), ("Drawable/Object position NAN! '%s'\n", m_template->getName().str() ));
}

//=============================================================================
void Thing::setPosition( const Coord3D *pos )
{
	//USE_PERF_TIMER(ThingMatrixStuff)
	if( !m_template->isKindOf( KINDOF_STICK_TO_TERRAIN_SLOPE) )
	{
		Real oldAngle = m_cachedAngle;
		Coord3D oldPos = m_cachedPos;
		Matrix3D oldMtx = m_transform;

		//DEBUG_ASSERTCRASH(!(_isnan(pos->x) || _isnan(pos->y) || _isnan(pos->z)), ("Drawable/Object position NAN! '%s'\n", m_template->getName().str() ));
		m_transform.Set_X_Translation( pos->x );
		m_transform.Set_Y_Translation( pos->y );
		m_transform.Set_Z_Translation( pos->z );
		m_cachedPos = *pos;
		m_cacheFlags &= ~(VALID_ALTITUDE_TERRAIN | VALID_ALTITUDE_SEALEVEL);	// but don't clear the dir flags.

		reactToTransformChange(&oldMtx, &oldPos, oldAngle);
	}
	else
	{
		Matrix3D mtx;
		const Bool stickToGround = true;	// yes, set the "z" pos				
		TheTerrainLogic->alignOnTerrain(getOrientation(), *pos, stickToGround, mtx );
		setTransformMatrix(&mtx);
	}
	DEBUG_ASSERTCRASH(!(_isnan(getPosition()->x) || _isnan(getPosition()->y) || _isnan(getPosition()->z)), ("Drawable/Object position NAN! '%s'\n", m_template->getName().str() ));
}

//=============================================================================
void Thing::setOrientation( Real angle )
{
	//USE_PERF_TIMER(ThingMatrixStuff)
	Coord3D u, x, y, z, pos;

	// setOrientation always forces us straight up in the Z axis,
	// or aligned with the terrain if we have the magic flag set.
	// don't want this? call setTransformMatrix instead.

	Real oldAngle = m_cachedAngle;
	Coord3D oldPos = m_cachedPos;
	Matrix3D oldMtx = m_transform;

	pos.x = m_transform.Get_X_Translation();
	pos.y = m_transform.Get_Y_Translation();
	pos.z = m_transform.Get_Z_Translation();
	if( m_template->isKindOf( KINDOF_STICK_TO_TERRAIN_SLOPE) )
	{
		Matrix3D mtx;
		const Bool stickToGround = true;	// yes, set the "z" pos				
		TheTerrainLogic->alignOnTerrain(angle, pos, stickToGround, m_transform );
	}
	else
	{
		z.x = 0.0f;
		z.y = 0.0f;
		z.z = 1.0f;

		u.x = Cos(angle);
		u.y = Sin(angle);
		u.z = 0.0f;

		y.crossProduct( &z, &u, &y );
		x.crossProduct( &y, &z, &x );

		m_transform.Set(  x.x, y.x, z.x, pos.x,
											x.y, y.y, z.y, pos.y,
											x.z, y.z, z.z, pos.z );
	}

	//DEBUG_ASSERTCRASH(-PI <= angle && angle <= PI, ("Please pass only normalized (-PI..PI) angles to setOrientation (%f).\n", angle));
	m_cachedAngle = normalizeAngle(angle);
	m_cachedPos = pos;
	m_cacheFlags &= ~VALID_DIRVECTOR;	// but don't clear the altitude flags.

	reactToTransformChange(&oldMtx, &oldPos, oldAngle);
	DEBUG_ASSERTCRASH(!(_isnan(getPosition()->x) || _isnan(getPosition()->y) || _isnan(getPosition()->z)), ("Drawable/Object position NAN! '%s'\n", m_template->getName().str() ));
}

//=============================================================================
/** Set the world transformation matrix */
//=============================================================================
void Thing::setTransformMatrix( const Matrix3D *mx )
{
	//USE_PERF_TIMER(ThingMatrixStuff)
	Real oldAngle = m_cachedAngle;
	Coord3D oldPos = m_cachedPos;
	Matrix3D oldMtx = m_transform;

	m_transform = *mx;
	m_cachedPos.x = m_transform.Get_X_Translation();
	m_cachedPos.y = m_transform.Get_Y_Translation();
	m_cachedPos.z = m_transform.Get_Z_Translation();
	m_cachedAngle = m_transform.Get_Z_Rotation();
	m_cacheFlags = 0;

	reactToTransformChange(&oldMtx, &oldPos, oldAngle);
	DEBUG_ASSERTCRASH(!(_isnan(getPosition()->x) || _isnan(getPosition()->y) || _isnan(getPosition()->z)), ("Drawable/Object position NAN! '%s'\n", m_template->getName().str() ));
}

//-------------------------------------------------------------------------------------------------
Bool Thing::isKindOf(KindOfType t) const 
{ 
	return getTemplate()->isKindOf(t); 
}

//-------------------------------------------------------------------------------------------------
Bool Thing::isKindOfMulti(const KindOfMaskType& mustBeSet, const KindOfMaskType& mustBeClear) const 
{ 
	return getTemplate()->isKindOfMulti(mustBeSet, mustBeClear); 
}

// ------------------------------------------------------------------------------------------------
Bool Thing::isAnyKindOf( const KindOfMaskType& anyKindOf ) const
{
	return getTemplate()->isAnyKindOf( anyKindOf );
}

// ------------------------------------------------------------------------------------------------
Real Thing::calculateHeightAboveTerrain() const 
{
	//USE_PERF_TIMER(ThingMatrixStuff)
	const Coord3D* pos = getPosition();
	Real terrainZ = TheTerrainLogic->getGroundHeight( pos->x, pos->y );
	Real myZ = pos->z;
	return myZ - terrainZ;
}

//-------------------------------------------------------------------------------------------------
Real Thing::getHeightAboveTerrain() const
{
	if (!(m_cacheFlags & VALID_ALTITUDE_TERRAIN))
	{
		m_cachedAltitudeAboveTerrain = calculateHeightAboveTerrain();
		m_cacheFlags |= VALID_ALTITUDE_TERRAIN;
	}
	return m_cachedAltitudeAboveTerrain;
}

//-------------------------------------------------------------------------------------------------
Real Thing::getHeightAboveTerrainOrWater() const
{
	//USE_PERF_TIMER(ThingMatrixStuff)
	if (!(m_cacheFlags & VALID_ALTITUDE_SEALEVEL))
	{
		const Coord3D* pos = getPosition();
		Real waterZ;
		if (TheTerrainLogic->isUnderwater(pos->x, pos->y, &waterZ)) 
		{
			m_cachedAltitudeAboveTerrainOrWater = pos->z - waterZ;
		} 
		else
		{
			m_cachedAltitudeAboveTerrainOrWater = getHeightAboveTerrain();
		}
		m_cacheFlags |= VALID_ALTITUDE_SEALEVEL;
	}
	return m_cachedAltitudeAboveTerrainOrWater;
}

//=============================================================================
/** If we treat this as airborne, then they slide down slopes.  This checks whether
they are high enough that we should let them act like they're flying. jba. */
//=============================================================================
Bool Thing::isSignificantlyAboveTerrain() const 
{
	// If it's high enough that it will take more than 3 frames to return to the ground,
	// then it's significantly airborne.  jba
	return (getHeightAboveTerrain() > -(3*3)*TheGlobalData->m_gravity);
}


//-------------------------------------------------------------------------------------------------
void Thing::convertBonePosToWorldPos(const Coord3D* bonePos, const Matrix3D* boneTransform, Coord3D* worldPos, Matrix3D* worldTransform) const
{
	if (worldTransform)
	{
#ifdef ALLOW_TEMPORARIES
		*worldTransform = m_transform * (*boneTransform);
#else
		worldTransform->mul(m_transform, *boneTransform);
#endif
	}
	if (worldPos)
	{
		Vector3 vector;
		vector.X = bonePos->x;
		vector.Y = bonePos->y;
		vector.Z = bonePos->z;
		m_transform.Transform_Vector(m_transform, vector, &vector);
		worldPos->x = vector.X;
		worldPos->y = vector.Y;
		worldPos->z = vector.Z;
	}
}

// ------------------------------------------------------------------------------------------------
/** Push the 'in' parameter through our transformation matrix and store in 'out' */
// ------------------------------------------------------------------------------------------------
void Thing::transformPoint( const Coord3D *in, Coord3D *out )
{

	// santiy
	if( in == NULL || out == NULL )
		return;

	// for conversion
	Vector3 vectorIn;
	Vector3 vectorOut;

	///@ todo this is dumb and we should not have to convert types
	// convert to Vector3 datatypes
	vectorIn.X = in->x;
	vectorIn.Y = in->y;
	vectorIn.Z = in->z;

	// do the transform
	m_transform.Transform_Vector( m_transform, vectorIn, &vectorOut );

	// store converted vector in 'out'
	out->x = vectorOut.X;
	out->y = vectorOut.Y;
	out->z = vectorOut.Z;

}  // end transformPoint
