This repository has been archived on 2025-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
CnC_Renegade/Code/ww3d2/hmorphanim.cpp

743 lines
19 KiB
C++

/*
** Command & Conquer Renegade(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 O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
***********************************************************************************************
* *
* Project Name : WW3D *
* *
* $Archive:: /Commando/Code/ww3d2/hmorphanim.cpp $*
* *
* Original Author:: Greg Hjelstrom *
* *
* $Author:: Byon_g $*
* *
* $Modtime:: 1/16/02 6:39p $*
* *
* $Revision:: 7 $*
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "hmorphanim.h"
#include "w3d_file.h"
#include "chunkio.h"
#include "assetmgr.h"
#include "htree.h"
#include "wwstring.h"
#include "textfile.h"
#include "simplevec.h"
TimeCodedMorphKeysClass::TimeCodedMorphKeysClass(void)
: CachedIdx (0)
{
}
TimeCodedMorphKeysClass::~TimeCodedMorphKeysClass(void)
{
Free();
}
void TimeCodedMorphKeysClass::Free(void)
{
Keys.Delete_All ();
CachedIdx = 0;
}
bool TimeCodedMorphKeysClass::Load_W3D(ChunkLoadClass & cload)
{
Free();
uint32 key_count = cload.Cur_Chunk_Length() / sizeof(W3dMorphAnimKeyStruct);
W3dMorphAnimKeyStruct w3dkey;
for (uint32 i=0; i<key_count; i++) {
cload.Read(&w3dkey,sizeof(w3dkey));
Keys.Add (MorphKeyStruct (w3dkey.MorphFrame, w3dkey.PoseFrame));
}
CachedIdx = 0;
return true;
}
bool TimeCodedMorphKeysClass::Save_W3D(ChunkSaveClass & csave)
{
W3dMorphAnimKeyStruct w3dkey;
for (int i=0; i<Keys.Count (); i++) {
w3dkey.MorphFrame = Keys[i].MorphFrame;
w3dkey.PoseFrame = Keys[i].PoseFrame;
csave.Write(&w3dkey,sizeof(w3dkey));
}
return true;
}
void TimeCodedMorphKeysClass::Add_Key (uint32 morph_frame, uint32 pose_frame)
{
Keys.Add (MorphKeyStruct (morph_frame, pose_frame));
return ;
}
void TimeCodedMorphKeysClass::Get_Morph_Info(float morph_frame,int * pose_frame0,int * pose_frame1,float * fraction)
{
if (morph_frame < 0.0f) {
*pose_frame0 = *pose_frame1 = Keys[0].PoseFrame;
*fraction = 0.0f;
return;
}
if (morph_frame >= Keys[Keys.Count ()-1].MorphFrame) {
*pose_frame0 = *pose_frame1 = Keys[Keys.Count ()-1].PoseFrame;
*fraction = 0.0f;
return;
}
int key_index = get_index(morph_frame);
*pose_frame0 = Keys[key_index].PoseFrame;
*pose_frame1 = Keys[key_index+1].PoseFrame;
*fraction = (morph_frame - Keys[key_index].MorphFrame) / (Keys[key_index+1].MorphFrame - Keys[key_index].MorphFrame);
}
uint32 TimeCodedMorphKeysClass::get_index(float frame)
{
assert(CachedIdx <= (uint32)Keys.Count ()-1);
float cached_frame = Keys[CachedIdx].MorphFrame;
// check if the requested time is in the cached interval or the following one
if (frame >= cached_frame) {
// special case for end packets
if (CachedIdx == (uint32)Keys.Count ()-1) return(CachedIdx);
// check if the requested time is still in the cached interval
if (frame < Keys[CachedIdx + 1].MorphFrame) return(CachedIdx);
// do one time look-ahead before reverting to a search
CachedIdx++;
// again, special case the end interval
if (CachedIdx == (uint32)Keys.Count ()-1) return(CachedIdx);
// check if requested time is in this interval
if (frame < Keys[CachedIdx + 1].MorphFrame) return(CachedIdx);
}
// nope, fall back to a binary search for the requested interval
CachedIdx = binary_search_index(frame);
return(CachedIdx);
}
uint32 TimeCodedMorphKeysClass::binary_search_index(float req_frame)
{
// special case first and last packet
if (req_frame < Keys[0].MorphFrame) return 0;
if (req_frame >= Keys[Keys.Count ()-1].MorphFrame) return Keys.Count ()-1;
int leftIdx = 0;
int rightIdx = Keys.Count ();
int idx,dx;
// binary search for the desired interval
for (;;) {
// if we've zeroed in on the interval, return the left index
dx = rightIdx - leftIdx;
if (dx == 1) {
WWASSERT(req_frame >= Keys[leftIdx].MorphFrame);
WWASSERT(req_frame < Keys[rightIdx].MorphFrame);
return leftIdx;
}
// otherwise, split our interval in half and descend into one of them
dx>>=1;
idx = leftIdx + dx;
if (req_frame < Keys[idx].MorphFrame) {
rightIdx = idx;
} else {
leftIdx = idx;
}
}
assert(0);
return(0);
}
/*********************************************************************************************
**
** HMorphAnimClass Implementation
**
*********************************************************************************************/
HMorphAnimClass::HMorphAnimClass(void) :
FrameCount(0),
FrameRate(0.0f),
ChannelCount(0),
NumNodes(0),
PoseData(NULL),
MorphKeyData(NULL),
PivotChannel(NULL)
{
memset(Name,0,sizeof(Name));
memset(AnimName,0,sizeof(AnimName));
memset(HierarchyName,0,sizeof(HierarchyName));
}
HMorphAnimClass::~HMorphAnimClass(void)
{
Free();
}
void HMorphAnimClass::Free(void)
{
if (PoseData != NULL) {
for (int i=0; i<ChannelCount; i++) {
REF_PTR_RELEASE(PoseData[i]);
}
delete[] PoseData;
PoseData = NULL;
}
if (MorphKeyData != NULL) {
delete[] MorphKeyData;
MorphKeyData = NULL;
}
if (PivotChannel != NULL) {
delete[] PivotChannel;
PivotChannel = NULL;
}
}
static int Build_List_From_String
(
const char * buffer,
const char * delimiter,
StringClass ** string_list
)
{
int count = 0;
WWASSERT (buffer != NULL);
WWASSERT (delimiter != NULL);
WWASSERT (string_list != NULL);
if ((buffer != NULL) &&
(delimiter != NULL) &&
(string_list != NULL))
{
int delim_len = ::strlen (delimiter);
//
// Determine how many entries there will be in the list
//
for (const char *entry = buffer;
(entry != NULL) && (entry[1] != 0);
entry = ::strstr (entry, delimiter))
{
//
// Move past the current delimiter (if necessary)
//
if ((::strnicmp (entry, delimiter, delim_len) == 0) && (count > 0)) {
entry += delim_len;
}
// Increment the count of entries
count ++;
}
if (count > 0) {
//
// Allocate enough StringClass objects to hold all the strings in the list
//
(*string_list) = new StringClass[count];
//
// Parse the string and pull out its entries.
//
count = 0;
for (entry = buffer;
(entry != NULL) && (entry[1] != 0);
entry = ::strstr (entry, delimiter))
{
//
// Move past the current delimiter (if necessary)
//
if ((::strnicmp (entry, delimiter, delim_len) == 0) && (count > 0)) {
entry += delim_len;
}
//
// Copy this entry into its own string
//
StringClass entry_string = entry;
char *delim_start = ::strstr (entry_string, delimiter);
if (delim_start != NULL) {
delim_start[0] = 0;
}
//
// Add this entry to our list
//
if ((entry_string.Get_Length () > 0) || (count == 0)) {
(*string_list)[count++] = entry_string;
}
}
} else if (delim_len > 0) {
count = 1;
(*string_list) = new StringClass[count];
(*string_list)[0] = buffer;
}
}
//
// Return the number of entries in our list
//
return count;
}
bool Is_Number (const char *str)
{
bool retval = true;
while (retval && str[0] != NULL){
retval = ((str[0] >= '0' && str[0] <= '9') || str[0] == '-' || str[0] == '.');
str ++;
}
return retval;
}
bool HMorphAnimClass::Import(const char *hierarchy_name, TextFileClass &text_desc)
{
bool retval = false;
Free ();
FrameCount = 0;
FrameRate = 30.0F;
//
// Copy the hierarchy name into a class variable
//
::strncpy (HierarchyName, hierarchy_name, W3D_NAME_LEN);
HierarchyName[W3D_NAME_LEN - 1] = 0;
//
// Attempt to load the new base pose
//
HTreeClass * base_pose = WW3DAssetManager::Get_Instance()->Get_HTree(HierarchyName);
WWASSERT (base_pose != NULL);
NumNodes = base_pose->Num_Pivots();
//
// Read the header from the file
//
StringClass header;
bool success = text_desc.Read_Line (header);
if (success) {
//
// Get the list of comma-delimited strings from the header
//
StringClass *column_list = NULL;
int column_count = Build_List_From_String (header, ",", &column_list);
//
// The first column header should be 'Frame#', all other headers
// should be channel animation names.
//
ChannelCount = column_count - 1;
WWASSERT (ChannelCount > 0);
if (ChannelCount > 0) {
//
// Allocate and initialize each animation channel
//
PoseData = new HAnimClass *[ChannelCount];
MorphKeyData = new TimeCodedMorphKeysClass[ChannelCount];
for (int index = 0; index < ChannelCount; index ++) {
StringClass channel_anim_name;
channel_anim_name.Format ("%s.%s", HierarchyName, (const char *)column_list[index + 1]);
PoseData[index] = WW3DAssetManager::Get_Instance()->Get_HAnim (channel_anim_name);
}
//
// Now read the animation data for each frame
//
StringClass frame_desc;
while (text_desc.Read_Line (frame_desc)) {
//
// Get the frame descriptions from this line
//
StringClass *channel_list = NULL;
int list_count = Build_List_From_String (frame_desc, ",", &channel_list);
WWASSERT (list_count > 0);
if (list_count > 0) {
//
// The first column contains the morph frame number
//
int frame = ::atoi (channel_list[0]);
//
// Now read the animation frame number for each channel
//
for (int index = 1; index < list_count; index ++) {
StringClass &channel_frame = channel_list[index];
//
// If this channel contains a valid number, then record
// its animation frame
//
if (::Is_Number (channel_frame)) {
MorphKeyData[index - 1].Add_Key (frame, ::atoi (channel_frame));
}
}
FrameCount = frame + 1;
}
//
// Cleanup
//
if (channel_list != NULL) {
delete [] channel_list;
channel_list = NULL;
}
}
//
// Allocate the pivot channel list
//
PivotChannel = new uint32[NumNodes];
Resolve_Pivot_Channels ();
}
//
// Cleanup
//
if (column_list != NULL) {
delete [] column_list;
column_list = NULL;
}
}
return retval;
}
void HMorphAnimClass::Resolve_Pivot_Channels(void)
{
WWASSERT (PivotChannel != NULL);
//
// Loop over all the pivots in the HTree
//
for (int pivot = 0; pivot < NumNodes; pivot ++) {
PivotChannel[pivot] = 0;
//
// Find out which animation channel affects this pivot
//
for (int channel = 0; channel < ChannelCount; channel ++) {
if (PoseData[channel]->Is_Node_Motion_Present (pivot)) {
PivotChannel[pivot] = channel;
}
}
}
return ;
}
void HMorphAnimClass::Set_Name(const char * name)
{
//
// Copy the full name
//
::strcpy (Name, name);
//
// Try to find the separator (a period)
//
StringClass full_name = name;
char *separator = ::strchr (full_name, '.');
if (separator != NULL) {
//
// Null out the separator and copy the two names
// into our two buffers
//
separator[0] = 0;
::strcpy (AnimName, separator + 1);
::strcpy (HierarchyName, full_name);
}
return ;
}
void HMorphAnimClass::Free_Morph(void)
{
Free();
}
int HMorphAnimClass::Create_New_Morph(const int channels, HAnimClass *anim[])
{
// clean out the previous instance of this class
Free();
// set the number of channels
ChannelCount = channels;
// read in the animation header
if (anim == NULL) {
return LOAD_ERROR;
}
// set up info
// FrameCount = anim[0]->Get_Num_Frames();
// FrameRate = anim[0]->Get_Frame_Rate();
FrameCount = 0;
FrameRate = 30.0f;
NumNodes = anim[0]->Get_Num_Pivots();
// Set up the anim data for all the channels
PoseData = new HAnimClass * [ChannelCount];
for(int i=0;i<ChannelCount;i++)
PoseData[i] = anim[i];
// Create a timecodekey array for each channel and initialize the pivot channels
MorphKeyData = new TimeCodedMorphKeysClass[ChannelCount];
PivotChannel = new uint32[NumNodes];
// Resolve the pivots so that they correspond to the proper morphing channels
memset(PivotChannel,0,NumNodes * sizeof(uint32));
Resolve_Pivot_Channels();
// Signal successful process
return OK;
}
int HMorphAnimClass::Load_W3D(ChunkLoadClass & cload)
{
Free();
// read in the animation header
W3dMorphAnimHeaderStruct header;
cload.Open_Chunk();
WWASSERT(cload.Cur_Chunk_ID() == W3D_CHUNK_MORPHANIM_HEADER);
cload.Read(&header,sizeof(header));
cload.Close_Chunk();
strncpy(AnimName,header.Name,sizeof(AnimName));
strncpy(HierarchyName,header.HierarchyName,sizeof(HierarchyName));
strcpy(Name,HierarchyName);
strcat(Name,".");
strcat(Name,AnimName);
HTreeClass * base_pose = WW3DAssetManager::Get_Instance()->Get_HTree(HierarchyName);
if (base_pose == NULL) {
return LOAD_ERROR;
}
NumNodes = base_pose->Num_Pivots();
FrameCount = header.FrameCount;
FrameRate = header.FrameRate;
ChannelCount = header.ChannelCount;
PoseData = new HAnimClass * [ChannelCount];
MorphKeyData = new TimeCodedMorphKeysClass[ChannelCount];
PivotChannel = new uint32[NumNodes];
memset(PivotChannel,0,NumNodes * sizeof(uint32));
// read in the rest of the chunks
int cur_channel = 0;
while (cload.Open_Chunk()) {
switch(cload.Cur_Chunk_ID())
{
case W3D_CHUNK_MORPHANIM_CHANNEL:
read_channel(cload,cur_channel++);
break;
case W3D_CHUNK_MORPHANIM_PIVOTCHANNELDATA:
cload.Read(PivotChannel,cload.Cur_Chunk_Length());
break;
};
cload.Close_Chunk();
}
return OK;
}
void HMorphAnimClass::read_channel(ChunkLoadClass & cload,int channel)
{
WWASSERT(channel >= 0);
WWASSERT(channel < ChannelCount);
StringClass anim_name;
cload.Open_Chunk();
WWASSERT(cload.Cur_Chunk_ID() == W3D_CHUNK_MORPHANIM_POSENAME);
cload.Read(anim_name.Get_Buffer(cload.Cur_Chunk_Length()),cload.Cur_Chunk_Length());
cload.Close_Chunk();
//StringClass channel_anim_name;
//channel_anim_name.Format ("%s.%s", HierarchyName, anim_name);
PoseData[channel] = WW3DAssetManager::Get_Instance()->Get_HAnim(anim_name);
WWASSERT(PoseData[channel] != NULL);
cload.Open_Chunk();
WWASSERT(cload.Cur_Chunk_ID() == W3D_CHUNK_MORPHANIM_KEYDATA);
MorphKeyData[channel].Load_W3D(cload);
cload.Close_Chunk();
}
int HMorphAnimClass::Save_W3D(ChunkSaveClass & csave)
{
// W3D objects write their own wrapper chunks
csave.Begin_Chunk(W3D_CHUNK_MORPH_ANIMATION);
// init the header data
W3dMorphAnimHeaderStruct header;
memset(&header,0,sizeof(header));
strncpy(header.Name,AnimName,sizeof(header.Name));
strncpy(header.HierarchyName,HierarchyName,sizeof(header.HierarchyName));
header.FrameCount = FrameCount;
header.FrameRate = FrameRate;
header.ChannelCount = ChannelCount;
// write out the animation header
csave.Begin_Chunk(W3D_CHUNK_MORPHANIM_HEADER);
csave.Write(&header,sizeof(header));
csave.End_Chunk();
// write out the morph channels
for (int ci=0; ci<ChannelCount; ci++) {
csave.Begin_Chunk(W3D_CHUNK_MORPHANIM_CHANNEL);
write_channel(csave,ci);
csave.End_Chunk();
}
// write out the pivot attachments
csave.Begin_Chunk(W3D_CHUNK_MORPHANIM_PIVOTCHANNELDATA);
csave.Write(PivotChannel,NumNodes * sizeof(uint32));
csave.End_Chunk();
csave.End_Chunk();
return OK;
}
void HMorphAnimClass::write_channel(ChunkSaveClass & csave,int channel)
{
WWASSERT(PoseData[channel] != NULL);
const char * pose_name = PoseData[channel]->Get_Name();
csave.Begin_Chunk(W3D_CHUNK_MORPHANIM_POSENAME);
csave.Write(pose_name,strlen(pose_name) + 1);
csave.End_Chunk();
csave.Begin_Chunk(W3D_CHUNK_MORPHANIM_KEYDATA);
MorphKeyData[channel].Save_W3D(csave);
csave.End_Chunk();
}
void HMorphAnimClass::Get_Translation(Vector3& trans,int pividx,float frame) const
{
int channel = PivotChannel[pividx];
int pose_frame0,pose_frame1;
float fraction;
MorphKeyData[channel].Get_Morph_Info(frame,&pose_frame0,&pose_frame1,&fraction);
Vector3 t0;
PoseData[channel]->Get_Translation(t0,pividx,pose_frame0);
Vector3 t1;
PoseData[channel]->Get_Translation(t1,pividx,pose_frame1);
Vector3::Lerp(t0,t1,fraction,&trans);
}
void HMorphAnimClass::Get_Orientation(Quaternion& q, int pividx,float frame) const
{
int channel = PivotChannel[pividx];
int pose_frame0,pose_frame1;
float fraction;
MorphKeyData[channel].Get_Morph_Info(frame,&pose_frame0,&pose_frame1,&fraction);
Quaternion q0;
PoseData[channel]->Get_Orientation(q0,pividx,pose_frame0);
Quaternion q1;
PoseData[channel]->Get_Orientation(q1,pividx,pose_frame1);
::Fast_Slerp(q,q0,q1,fraction);
}
void HMorphAnimClass::Get_Transform(Matrix3D& mtx,int pividx,float frame) const
{
int channel = PivotChannel[pividx];
int pose_frame0,pose_frame1;
float fraction;
MorphKeyData[channel].Get_Morph_Info(frame,&pose_frame0,&pose_frame1,&fraction);
Quaternion q0;
PoseData[channel]->Get_Orientation(q0,pividx,pose_frame0);
Quaternion q1;
PoseData[channel]->Get_Orientation(q1,pividx,pose_frame1);
Quaternion q;
::Fast_Slerp(q,q0,q1,fraction);
mtx=::Build_Matrix3D(q);
Vector3 t0;
PoseData[channel]->Get_Translation(t0,pividx,pose_frame0);
Vector3 t1;
PoseData[channel]->Get_Translation(t1,pividx,pose_frame1);
Vector3 trans;
Vector3::Lerp(t0,t1,fraction,&trans);
mtx.Set_Translation(trans);
}
void HMorphAnimClass::Insert_Morph_Key(const int channel, uint32 morph_frame, uint32 pose_frame)
{
assert(channel<ChannelCount);
MorphKeyData[channel].Add_Key(morph_frame,pose_frame);
// update the framecount to reflect the newly added key
FrameCount = WWMath::Max(morph_frame,FrameCount);
}
void HMorphAnimClass::Release_Keys(void)
{
for(int i=0;i<ChannelCount;i++)
MorphKeyData[i].Free();
// update the framecount as 0
FrameCount = 0;
}