/*
**	Command & Conquer Generals Zero Hour(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/>.
*/

/* $Header: /Commando/Code/ww3d2/hanim.cpp 3     12/13/01 7:01p Patrick $ */
/*********************************************************************************************** 
 ***                            Confidential - Westwood Studios                              *** 
 *********************************************************************************************** 
 *                                                                                             * 
 *                 Project Name : Commando / G 3D Library                                      * 
 *                                                                                             * 
 *                     $Archive:: /Commando/Code/ww3d2/hanim.cpp                              $* 
 *                                                                                             * 
 *                       Author:: Greg_h                                                       * 
 *                                                                                             * 
 *                     $Modtime:: 12/13/01 6:54p                                              $* 
 *                                                                                             * 
 *                    $Revision:: 3                                                           $* 
 *                                                                                             * 
 *---------------------------------------------------------------------------------------------* 
 * Functions:                                                                                  * 
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */


#include "hanim.h"
#include "assetmgr.h"
#include "htree.h"
#include "motchan.h"
#include "chunkio.h"
#include "w3d_file.h"
#include "wwdebug.h"
#include <string.h>
#include <nstrdup.h>



/*
**
**	HAnimComboClass
**
**
*/


NamedPivotMapClass::~NamedPivotMapClass(void)
{
}

NamedPivotMapClass::WeightInfoStruct & NamedPivotMapClass::WeightInfoStruct::operator = (WeightInfoStruct const &that)
{	
	if(Name) delete [] Name;
	assert(that.Name != 0);
	Name = nstrdup(that.Name); 
	Weight = that.Weight; 
	return *this; 
}

// add a name & weight to the arrays
void NamedPivotMapClass::Add(const char *Name, float Weight)
{
	WeightInfoStruct info;
	info.Name = (char *) Name;
	info.Weight = Weight;
	WeightInfo.Add(info);	
	info.Name = 0;
}

// configure the base pivot map using the specified tree
void NamedPivotMapClass::Update_Pivot_Map(const HTreeClass *Tree)
{
	// first, resize the base pivot map to fit the tree
	int numPivots = Tree->Num_Pivots();

	Resize(numPivots);
	ActiveCount = numPivots;

	// first, reset all weights to 1 (the default)
	while(numPivots--) {
		(*this)[numPivots] = 1.0f;
	}

	// for each named pivot, find the correct index and set the weight if the indicated bone is present
	// note: there is no check for redundant names
	int count = WeightInfo.Count();
	while(count--) {
		int actualPivot = Tree->Get_Bone_Index(WeightInfo[count].Name);
		if(actualPivot != -1) {
			(*this)[actualPivot] = WeightInfo[count].Weight;
		} 
	}
}

/*
**
*/
DEFINE_AUTO_POOL(HAnimComboDataClass,256);

HAnimComboDataClass::HAnimComboDataClass(bool shared) 
: Shared(shared), HAnim(0), PivotMap(0), Frame(0), PrevFrame(0), Weight(1) 
{}


HAnimComboDataClass::HAnimComboDataClass(const HAnimComboDataClass &src)
:	PivotMap(src.Get_Pivot_Map()),
	HAnim(src.Get_HAnim())
{
	Shared = src.Is_Shared();
	Frame = src.Get_Frame();
	PrevFrame = src.Get_Prev_Frame();
	Weight = src.Get_Weight();
}

void HAnimComboDataClass::Copy(const HAnimComboDataClass *src)
{
	if(src) {
		HAnim = src->Get_HAnim();
		PivotMap = src->Get_Pivot_Map();
		Frame = src->Get_Frame();
		PrevFrame = src->Get_Prev_Frame();
		Weight = src->Get_Weight();
	} else {
		HAnim = 0;
		PivotMap = 0;
		Frame = 0;
		PrevFrame = 0;
		Weight = 1;
	}
}

HAnimComboDataClass::~HAnimComboDataClass(void)
{
	if(HAnim) 
		HAnim->Release_Ref();
	if(PivotMap)
		PivotMap->Release_Ref();
}

void HAnimComboDataClass::Clear(void) 
{
	if ( HAnim != NULL ) {
		HAnim->Release_Ref();
		HAnim = NULL;
	}

	// not sure if the pivot map should be deleted or just have everything set to one.
	// removing it effectively sets it to one, so that's what I'm doing for now.
	if(PivotMap) {
		PivotMap->Release_Ref();
		PivotMap = NULL;
	}

	Frame = 0.0f;
	PrevFrame = 0.0f;
	Weight = 1.0;
	PivotMap = NULL;
}

void HAnimComboDataClass::Set_HAnim(HAnimClass *motion)
{
	if ( motion != NULL ) {
		motion->Add_Ref();
	}
	if ( HAnim != NULL ) {
		HAnim->Release_Ref();
	}
	HAnim = motion;
}


void HAnimComboDataClass::Set_Pivot_Map(PivotMapClass *map)
{
	if ( map != NULL ) {
		map->Add_Ref();
	}
	if ( PivotMap != NULL ) {
		PivotMap->Release_Ref();
	}
	PivotMap = map;
}

/*
**	This function will replace the current pivot map (if any) with another pivot map that is
** set to 1 for only those pivot indices that actually have data.
*/
void HAnimComboDataClass::Build_Active_Pivot_Map(void) 
{
	if ( PivotMap != NULL ) {
		PivotMap->Release_Ref();
	}
	if(HAnim == NULL) {
		PivotMap = 0;
		return;
	}

	int numpivots = HAnim->Get_Num_Pivots();
	PivotMap = NEW_REF( PivotMapClass, ());
	PivotMap->Resize(numpivots);

	int count = 0;
	while(count < numpivots) {
		if(HAnim->Is_Node_Motion_Present(count)) {
			PivotMap->Add(1);

		} else {
			PivotMap->Add(0);
		}
		count++;
	}
}

/*	
**	HAnimComboClass
**
**
*/

HAnimComboClass::HAnimComboClass(void)
{}

HAnimComboClass::HAnimComboClass( int num_animations )
{
	HAnimComboData.Resize(num_animations);

	while(num_animations--) {
		HAnimComboData.Add(new HAnimComboDataClass());
	}
}


HAnimComboClass::~HAnimComboClass(void)
{
	Reset();
}


void	HAnimComboClass::Clear( void )
{
	int numAnimations = HAnimComboData.Count();
	while ( numAnimations-- ) {
		HAnimComboDataClass *data = HAnimComboData[numAnimations];
		if(data && (! data->Is_Shared()))
			data->Clear();
	}
}

void	HAnimComboClass::Reset( void )
{
	int numAnimations = HAnimComboData.Count();
	while ( numAnimations-- ) {
		HAnimComboDataClass *data = HAnimComboData[numAnimations];
		if(data && (! data->Is_Shared())) {
			delete data; 
		}
	}
	HAnimComboData.Reset_Active();
}

bool	HAnimComboClass::Normalize_Weights(void)
{
	// NOTE: This can only work if either no anims have pivot weight maps (in which case we will
	// adjust the anim weights to ensure normalization), or else if all do (in which case we will
	// adjust the pivot maps). Otherwise we do nothing and return false.
	int anim_count = Get_Num_Anims();
	if (!anim_count) return true;	// Trivially succeeded
	
	// Loop over all anims. Check if all or none have pivot maps, and also calculate the minimum
	// number of pivots.
	int anim_idx = 0;
	bool all_pivot_maps = true;
	bool none_pivot_maps = true;
	int num_anim_pivots = 100000;
	for (anim_idx = 0; anim_idx < anim_count; anim_idx++ ) {
		num_anim_pivots = MIN(num_anim_pivots, Peek_Motion(anim_idx)->Get_Num_Pivots());
		bool has_pivot_map = Peek_Pivot_Weight_Map(anim_idx) != NULL;
		all_pivot_maps &= has_pivot_map;
		none_pivot_maps &= !has_pivot_map;
	}
	if ( num_anim_pivots == 100000 ) {
		num_anim_pivots = 0;
	}

	if (none_pivot_maps) {

		// Calculate total weight of all active anims, ensure it is very close to 1.
		float weight_total = 0.0f;
		for (anim_idx = 0; anim_idx < anim_count; anim_idx++ ) {
			if (Peek_Motion(anim_idx) != NULL ) {
				float	weight = Get_Weight(anim_idx);
				weight_total += weight;
			}
		}

		// weight_total should be very close to 1. If not, normalize this pivot's weights
		if (weight_total != 0.0 && WWMath::Fabs( weight_total - 1.0 ) > WWMATH_EPSILON) {
			float oo_total = 1.0f / weight_total;
			for (anim_idx = 0; anim_idx < anim_count; anim_idx++ ) {
				if (Peek_Motion(anim_idx) != NULL ) {
					Set_Weight(anim_idx, Get_Weight(anim_idx) * oo_total);
				}
			}
		}

	} else {

		if (all_pivot_maps) {

			// For each pivot, calculate total weight of all active anims, ensure close to 1.
			for (int piv_idx = 1; piv_idx < num_anim_pivots; piv_idx++) {

				float weight_total = 0.0f;
				for (anim_idx = 0; anim_idx < anim_count; anim_idx++ ) {
					if (Peek_Motion(anim_idx) != NULL ) {
						float	weight = Get_Weight(anim_idx) * (*Peek_Pivot_Weight_Map(anim_idx))[piv_idx];
						weight_total += weight;
					}
				}

				// weight_total should be very close to 1. If not, normalize this pivot's weights
				if (weight_total != 0.0 && WWMath::Fabs( weight_total - 1.0 ) > WWMATH_EPSILON) {
					float oo_total = 1.0f / weight_total;
					for (anim_idx = 0; anim_idx < anim_count; anim_idx++ ) {
						if (Peek_Motion(anim_idx) != NULL ) {
							PivotMapClass *pivot_map = Get_Pivot_Weight_Map(anim_idx);
							float new_weight = (*pivot_map)[piv_idx] * oo_total;
							(*pivot_map)[piv_idx] = new_weight;
							pivot_map->Release_Ref();
						}
					}
				}

			}

		} else {
			return false;
		}
	}

	return true;

}

void	HAnimComboClass::Set_Motion( int index, HAnimClass *motion )
{
	HAnimComboDataClass *data = HAnimComboData[index];
	WWASSERT(data);

	data->Set_HAnim(motion);
}

HAnimClass *HAnimComboClass::Get_Motion( int index )
{
	HAnimComboDataClass *data = HAnimComboData[index];
	WWASSERT(data);

	HAnimClass *anim = data->Peek_HAnim();

	if ( anim != NULL ) {
		anim->Add_Ref();
	}
	return anim;
}

HAnimClass *HAnimComboClass::Peek_Motion( int index )
{
	HAnimComboDataClass *data = HAnimComboData[index];
	WWASSERT(data);

	HAnimClass *anim = data->Peek_HAnim();
	return anim;
}

void	HAnimComboClass::Set_Frame( int index, float frame )
{
	HAnimComboDataClass *data = HAnimComboData[index];
	WWASSERT(data);

	data->Set_Frame(frame);
}

float	HAnimComboClass::Get_Frame( int index )
{
	HAnimComboDataClass *data = HAnimComboData[index];
	WWASSERT(data);

	return data->Get_Frame();
}

void	HAnimComboClass::Set_Prev_Frame( int index, float frame )
{
	HAnimComboDataClass *data = HAnimComboData[index];
	WWASSERT(data);

	data->Set_Prev_Frame(frame);
}

float	HAnimComboClass::Get_Prev_Frame( int index )
{
	HAnimComboDataClass *data = HAnimComboData[index];
	WWASSERT(data);

	return data->Get_Prev_Frame();
}

void	HAnimComboClass::Set_Weight( int index, float weight )
{
	HAnimComboDataClass *data = HAnimComboData[index];
	WWASSERT(data);

	data->Set_Weight(weight);
}

float	HAnimComboClass::Get_Weight( int index )
{
	HAnimComboDataClass *data = HAnimComboData[index];
	WWASSERT(data);

	return data->Get_Weight();
}

void	HAnimComboClass::Set_Pivot_Weight_Map( int index, PivotMapClass *map )
{
	HAnimComboDataClass *data = HAnimComboData[index];
	WWASSERT(data);

	data->Set_Pivot_Map(map);
}

PivotMapClass	*HAnimComboClass::Get_Pivot_Weight_Map( int index )
{
	HAnimComboDataClass *data = HAnimComboData[index];
	WWASSERT(data);

	return data->Get_Pivot_Map();
}

PivotMapClass	*HAnimComboClass::Peek_Pivot_Weight_Map( int index )
{
	HAnimComboDataClass *data = HAnimComboData[index];
	WWASSERT(data);

	return data->Peek_Pivot_Map();
}

void HAnimComboClass::Append_Anim_Combo_Data(HAnimComboDataClass * Data)
{
	HAnimComboData.Add(Data);
}

void HAnimComboClass::Remove_Anim_Combo_Data(HAnimComboDataClass * Data)
{
	HAnimComboData.Delete(Data);
}


