592 lines
14 KiB
C++
592 lines
14 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 : WWMath *
|
||
|
* *
|
||
|
* $Archive:: /VSS_Sync/wwmath/curve.cpp $*
|
||
|
* *
|
||
|
* Original Author:: Greg Hjelstrom *
|
||
|
* *
|
||
|
* $Author:: Vss_sync $*
|
||
|
* *
|
||
|
* $Modtime:: 6/13/01 2:18p $*
|
||
|
* *
|
||
|
* $Revision:: 9 $*
|
||
|
* *
|
||
|
*---------------------------------------------------------------------------------------------*
|
||
|
* Functions: *
|
||
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||
|
|
||
|
|
||
|
#include "curve.h"
|
||
|
#include "wwdebug.h"
|
||
|
#include "persistfactory.h"
|
||
|
#include "wwmathids.h"
|
||
|
#include "wwhack.h"
|
||
|
|
||
|
/*
|
||
|
** Force-Link this module because the linker can't detect that we actually need it...
|
||
|
*/
|
||
|
DECLARE_FORCE_LINK(curve);
|
||
|
|
||
|
/*
|
||
|
** Persist factories and chunk-id's used to save and load.
|
||
|
*/
|
||
|
SimplePersistFactoryClass<LinearCurve3DClass,WWMATH_CHUNKID_LINEARCURVE3D> _LinearCurve3DFactory;
|
||
|
SimplePersistFactoryClass<LinearCurve1DClass,WWMATH_CHUNKID_LINEARCURVE1D> _LinearCurve1DFactory;
|
||
|
|
||
|
enum
|
||
|
{
|
||
|
// ID's used by Curve3D
|
||
|
CURVE3D_CHUNK_VARIABLES = 0x00020651,
|
||
|
CURVE3D_CHUNK_KEYS,
|
||
|
|
||
|
CURVE3D_VARIABLE_ISLOOPING = 0x00,
|
||
|
CURVE3D_VARIABLE_KEYCOUNT,
|
||
|
|
||
|
// ID's used by LinearCurve3D
|
||
|
LINEARCURVE3D_CHUNK_CURVE3D = 0x00020653,
|
||
|
|
||
|
// ID's used by Curve1D
|
||
|
CURVE1D_CHUNK_VARIABLES = 0x00020655,
|
||
|
CURVE1D_CHUNK_KEYS,
|
||
|
|
||
|
CURVE1D_VARIABLE_ISLOOPING = 0x00,
|
||
|
CURVE1D_VARIABLE_KEYCOUNT,
|
||
|
|
||
|
// ID's used by LinearCurve1D
|
||
|
LINEARCURVE1D_CHUNK_CURVE1D = 0x00020657,
|
||
|
|
||
|
};
|
||
|
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
**
|
||
|
** Curve3DCLass Implementation
|
||
|
**
|
||
|
***********************************************************************************************/
|
||
|
Curve3DClass::Curve3DClass(void) :
|
||
|
IsLooping(false)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
Curve3DClass::Curve3DClass(const Curve3DClass & that)
|
||
|
{
|
||
|
*this = that;
|
||
|
}
|
||
|
|
||
|
Curve3DClass::~Curve3DClass(void)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
Curve3DClass & Curve3DClass::operator = (const Curve3DClass & that)
|
||
|
{
|
||
|
IsLooping = that.IsLooping;
|
||
|
Keys = that.Keys;
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
bool Curve3DClass::Is_Looping(void)
|
||
|
{
|
||
|
return IsLooping;
|
||
|
}
|
||
|
|
||
|
void Curve3DClass::Set_Looping(bool onoff)
|
||
|
{
|
||
|
IsLooping = onoff;
|
||
|
}
|
||
|
|
||
|
float Curve3DClass::Get_Start_Time(void)
|
||
|
{
|
||
|
if (Keys.Count() > 0) {
|
||
|
return Keys[0].Time;
|
||
|
} else {
|
||
|
return 0.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
float Curve3DClass::Get_End_Time(void)
|
||
|
{
|
||
|
if (Keys.Count() > 0) {
|
||
|
return Keys[Keys.Count() - 1].Time;
|
||
|
} else {
|
||
|
return 0.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int Curve3DClass::Key_Count(void)
|
||
|
{
|
||
|
return Keys.Count();
|
||
|
}
|
||
|
|
||
|
void Curve3DClass::Get_Key(int i,Vector3 * set_point,float * set_t)
|
||
|
{
|
||
|
assert(i >= 0);
|
||
|
assert(i < Keys.Count());
|
||
|
if (set_point != NULL) {
|
||
|
*set_point = Keys[i].Point;
|
||
|
}
|
||
|
if (set_t != NULL) {
|
||
|
*set_t = Keys[i].Time;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Curve3DClass::Set_Key(int i,const Vector3 & point)
|
||
|
{
|
||
|
assert(i >= 0);
|
||
|
assert(i < Keys.Count());
|
||
|
Keys[i].Point = point;
|
||
|
}
|
||
|
|
||
|
|
||
|
int Curve3DClass::Add_Key(const Vector3 & point,float t)
|
||
|
{
|
||
|
int idx = 0;
|
||
|
while (idx < Keys.Count() && Keys[idx].Time < t) {
|
||
|
idx++;
|
||
|
}
|
||
|
|
||
|
KeyClass newkey;
|
||
|
newkey.Point = point;
|
||
|
newkey.Time = t;
|
||
|
|
||
|
Keys.Insert(idx,newkey);
|
||
|
return idx;
|
||
|
}
|
||
|
|
||
|
void Curve3DClass::Remove_Key(int i)
|
||
|
{
|
||
|
assert(i >= 0);
|
||
|
assert(i < Keys.Count());
|
||
|
Keys.Delete(i);
|
||
|
}
|
||
|
|
||
|
void Curve3DClass::Clear_Keys(void)
|
||
|
{
|
||
|
Keys.Clear();
|
||
|
}
|
||
|
|
||
|
void Curve3DClass::Find_Interval(float time,int * i0,int * i1,float * t)
|
||
|
{
|
||
|
WWASSERT(time >= Keys[0].Time);
|
||
|
WWASSERT(time <= Keys[Keys.Count()-1].Time);
|
||
|
|
||
|
int i=0;
|
||
|
while (time > Keys[i+1].Time) {
|
||
|
i++;
|
||
|
}
|
||
|
*i0 = i;
|
||
|
*i1 = i+1;
|
||
|
*t = (time - Keys[i].Time) / (Keys[i+1].Time - Keys[i].Time);
|
||
|
}
|
||
|
|
||
|
bool Curve3DClass::Save(ChunkSaveClass & csave)
|
||
|
{
|
||
|
int keycount = Keys.Count();
|
||
|
csave.Begin_Chunk(CURVE3D_CHUNK_VARIABLES);
|
||
|
WRITE_MICRO_CHUNK(csave,CURVE3D_VARIABLE_ISLOOPING,IsLooping);
|
||
|
WRITE_MICRO_CHUNK(csave,CURVE3D_VARIABLE_KEYCOUNT,keycount);
|
||
|
csave.End_Chunk();
|
||
|
|
||
|
// Saving the keys, Note that if the format of a key changes we'll
|
||
|
// need a new chunk. (I didn't wrap each variable in its own chunk)
|
||
|
csave.Begin_Chunk(CURVE3D_CHUNK_KEYS);
|
||
|
for (int i=0; i<keycount; i++) {
|
||
|
csave.Write(&(Keys[i].Point),sizeof(Keys[i].Point));
|
||
|
csave.Write(&(Keys[i].Time),sizeof(Keys[i].Time));
|
||
|
}
|
||
|
csave.End_Chunk();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool Curve3DClass::Load(ChunkLoadClass & cload)
|
||
|
{
|
||
|
int i;
|
||
|
int keycount = 0;
|
||
|
KeyClass newkey;
|
||
|
|
||
|
// reset the curve
|
||
|
Keys.Delete_All();
|
||
|
|
||
|
// read in the chunks
|
||
|
while (cload.Open_Chunk()) {
|
||
|
|
||
|
switch(cload.Cur_Chunk_ID())
|
||
|
{
|
||
|
case CURVE3D_CHUNK_VARIABLES:
|
||
|
|
||
|
while (cload.Open_Micro_Chunk()) {
|
||
|
switch(cload.Cur_Micro_Chunk_ID()) {
|
||
|
READ_MICRO_CHUNK(cload,CURVE3D_VARIABLE_ISLOOPING,IsLooping);
|
||
|
READ_MICRO_CHUNK(cload,CURVE3D_VARIABLE_KEYCOUNT,keycount);
|
||
|
}
|
||
|
cload.Close_Micro_Chunk();
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case CURVE3D_CHUNK_KEYS:
|
||
|
for (i=0; i<keycount; i++) {
|
||
|
cload.Read(&(newkey.Point),sizeof(newkey.Point));
|
||
|
cload.Read(&(newkey.Time),sizeof(newkey.Time));
|
||
|
Keys.Add(newkey);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
WWDEBUG_SAY(("Unhandled Chunk: 0x%X File: %s Line: %d\r\n",__FILE__,__LINE__));
|
||
|
break;
|
||
|
}
|
||
|
cload.Close_Chunk();
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
**
|
||
|
** LinearCurve3DClass Implementation
|
||
|
** Linear curve, linearly interpolates the keys
|
||
|
**
|
||
|
***********************************************************************************************/
|
||
|
void LinearCurve3DClass::Evaluate(float time,Vector3 * set_val)
|
||
|
{
|
||
|
if (time < Keys[0].Time) {
|
||
|
*set_val = Keys[0].Point;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (time >= Keys[Keys.Count() - 1].Time) {
|
||
|
*set_val = Keys[Keys.Count() - 1].Point;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
int i0,i1;
|
||
|
float t;
|
||
|
Find_Interval(time,&i0,&i1,&t);
|
||
|
|
||
|
*set_val = Keys[i0].Point + t * (Keys[i1].Point - Keys[i0].Point);
|
||
|
}
|
||
|
|
||
|
const PersistFactoryClass & LinearCurve3DClass::Get_Factory(void) const
|
||
|
{
|
||
|
return _LinearCurve3DFactory;
|
||
|
}
|
||
|
|
||
|
bool LinearCurve3DClass::Save(ChunkSaveClass & csave)
|
||
|
{
|
||
|
csave.Begin_Chunk(LINEARCURVE3D_CHUNK_CURVE3D);
|
||
|
Curve3DClass::Save(csave);
|
||
|
csave.End_Chunk();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool LinearCurve3DClass::Load(ChunkLoadClass & cload)
|
||
|
{
|
||
|
while (cload.Open_Chunk()) {
|
||
|
|
||
|
switch(cload.Cur_Chunk_ID())
|
||
|
{
|
||
|
case LINEARCURVE3D_CHUNK_CURVE3D:
|
||
|
Curve3DClass::Load(cload);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
WWDEBUG_SAY(("Unhandled Chunk: 0x%X File: %s Line: %d\r\n",__FILE__,__LINE__));
|
||
|
break;
|
||
|
}
|
||
|
cload.Close_Chunk();
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
**
|
||
|
** Curve1DClass
|
||
|
**
|
||
|
***********************************************************************************************/
|
||
|
Curve1DClass::Curve1DClass(void) :
|
||
|
IsLooping(false)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
Curve1DClass::Curve1DClass(const Curve1DClass & that)
|
||
|
{
|
||
|
*this = that;
|
||
|
}
|
||
|
|
||
|
Curve1DClass::~Curve1DClass(void)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
Curve1DClass & Curve1DClass::operator = (const Curve1DClass & that)
|
||
|
{
|
||
|
IsLooping = that.IsLooping;
|
||
|
Keys = that.Keys;
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
bool Curve1DClass::Is_Looping(void)
|
||
|
{
|
||
|
return IsLooping;
|
||
|
}
|
||
|
|
||
|
void Curve1DClass::Set_Looping(bool onoff)
|
||
|
{
|
||
|
IsLooping = onoff;
|
||
|
}
|
||
|
|
||
|
float Curve1DClass::Get_Start_Time(void)
|
||
|
{
|
||
|
if (Keys.Count() > 0) {
|
||
|
return Keys[0].Time;
|
||
|
} else {
|
||
|
return 0.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
float Curve1DClass::Get_End_Time(void)
|
||
|
{
|
||
|
if (Keys.Count() > 0) {
|
||
|
return Keys[Keys.Count() - 1].Time;
|
||
|
} else {
|
||
|
return 0.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int Curve1DClass::Key_Count(void)
|
||
|
{
|
||
|
return Keys.Count();
|
||
|
}
|
||
|
|
||
|
void Curve1DClass::Get_Key(int i,float * set_point,float * set_t,unsigned int * extra)
|
||
|
{
|
||
|
assert(i >= 0);
|
||
|
assert(i < Keys.Count());
|
||
|
if (set_point != NULL) {
|
||
|
*set_point = Keys[i].Point;
|
||
|
}
|
||
|
if (set_t != NULL) {
|
||
|
*set_t = Keys[i].Time;
|
||
|
}
|
||
|
if (extra != NULL) {
|
||
|
*extra = Keys[i].Extra;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Curve1DClass::Set_Key(int i,float point,unsigned int extra)
|
||
|
{
|
||
|
assert(i >= 0);
|
||
|
assert(i < Keys.Count());
|
||
|
Keys[i].Point = point;
|
||
|
Keys[i].Extra = extra;
|
||
|
}
|
||
|
|
||
|
|
||
|
int Curve1DClass::Add_Key(float point,float t,unsigned int extra)
|
||
|
{
|
||
|
int idx = 0;
|
||
|
while (idx < Keys.Count() && Keys[idx].Time < t) {
|
||
|
idx++;
|
||
|
}
|
||
|
|
||
|
KeyClass newkey;
|
||
|
newkey.Point = point;
|
||
|
newkey.Time = t;
|
||
|
newkey.Extra = extra;
|
||
|
|
||
|
Keys.Insert(idx,newkey);
|
||
|
return idx;
|
||
|
}
|
||
|
|
||
|
void Curve1DClass::Remove_Key(int i)
|
||
|
{
|
||
|
assert(i >= 0);
|
||
|
assert(i < Keys.Count());
|
||
|
Keys.Delete(i);
|
||
|
}
|
||
|
|
||
|
void Curve1DClass::Clear_Keys(void)
|
||
|
{
|
||
|
Keys.Clear();
|
||
|
}
|
||
|
|
||
|
void Curve1DClass::Find_Interval(float time,int * i0,int * i1,float * t)
|
||
|
{
|
||
|
if (IsLooping) {
|
||
|
if (time < Keys[0].Time) {
|
||
|
*i0 = Keys.Count() - 1;
|
||
|
*i1 = 0;
|
||
|
float interval = 1.0f - Keys[*i0].Time + Keys[*i1].Time;
|
||
|
*t = (1.0f - Keys[*i0].Time + time) / interval;
|
||
|
return;
|
||
|
}
|
||
|
else if (time > Keys[Keys.Count() - 1].Time) {
|
||
|
*i0 = Keys.Count() - 1;
|
||
|
*i1 = 0;
|
||
|
float interval = 1.0f - Keys[*i0].Time + Keys[*i1].Time;
|
||
|
*t = (time - Keys[*i0].Time) / interval;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
WWASSERT(time >= Keys[0].Time);
|
||
|
WWASSERT(time <= Keys[Keys.Count()-1].Time);
|
||
|
}
|
||
|
|
||
|
int i=0;
|
||
|
while (time > Keys[i+1].Time) {
|
||
|
i++;
|
||
|
}
|
||
|
*i0 = i;
|
||
|
*i1 = i+1;
|
||
|
*t = (time - Keys[i].Time) / (Keys[i+1].Time - Keys[i].Time);
|
||
|
}
|
||
|
|
||
|
bool Curve1DClass::Save(ChunkSaveClass & csave)
|
||
|
{
|
||
|
int keycount = Keys.Count();
|
||
|
csave.Begin_Chunk(CURVE1D_CHUNK_VARIABLES);
|
||
|
WRITE_MICRO_CHUNK(csave,CURVE1D_VARIABLE_ISLOOPING,IsLooping);
|
||
|
WRITE_MICRO_CHUNK(csave,CURVE1D_VARIABLE_KEYCOUNT,keycount);
|
||
|
csave.End_Chunk();
|
||
|
|
||
|
// Saving the keys, Note that if the format of a key changes we'll
|
||
|
// need a new chunk. (I didn't wrap each variable in its own chunk)
|
||
|
csave.Begin_Chunk(CURVE1D_CHUNK_KEYS);
|
||
|
for (int i=0; i<keycount; i++) {
|
||
|
csave.Write(&(Keys[i].Point),sizeof(Keys[i].Point));
|
||
|
csave.Write(&(Keys[i].Time),sizeof(Keys[i].Time));
|
||
|
csave.Write(&(Keys[i].Extra),sizeof(Keys[i].Extra));
|
||
|
}
|
||
|
csave.End_Chunk();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool Curve1DClass::Load(ChunkLoadClass & cload)
|
||
|
{
|
||
|
int i;
|
||
|
int keycount = 0;
|
||
|
KeyClass newkey;
|
||
|
|
||
|
// reset the curve
|
||
|
Keys.Delete_All();
|
||
|
|
||
|
// read in the chunks
|
||
|
while (cload.Open_Chunk()) {
|
||
|
|
||
|
switch(cload.Cur_Chunk_ID())
|
||
|
{
|
||
|
case CURVE1D_CHUNK_VARIABLES:
|
||
|
|
||
|
while (cload.Open_Micro_Chunk()) {
|
||
|
switch(cload.Cur_Micro_Chunk_ID()) {
|
||
|
READ_MICRO_CHUNK(cload,CURVE1D_VARIABLE_ISLOOPING,IsLooping);
|
||
|
READ_MICRO_CHUNK(cload,CURVE1D_VARIABLE_KEYCOUNT,keycount);
|
||
|
}
|
||
|
cload.Close_Micro_Chunk();
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case CURVE1D_CHUNK_KEYS:
|
||
|
for (i=0; i<keycount; i++) {
|
||
|
cload.Read(&(newkey.Point),sizeof(newkey.Point));
|
||
|
cload.Read(&(newkey.Time),sizeof(newkey.Time));
|
||
|
cload.Read(&(newkey.Extra),sizeof(newkey.Extra));
|
||
|
Keys.Add(newkey);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
WWDEBUG_SAY(("Unhandled Chunk: 0x%X File: %s Line: %d\r\n",__FILE__,__LINE__));
|
||
|
break;
|
||
|
}
|
||
|
cload.Close_Chunk();
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
**
|
||
|
** LinearCurve1DClass, linearly interpolates the keys
|
||
|
**
|
||
|
***********************************************************************************************/
|
||
|
void LinearCurve1DClass::Evaluate(float time,float * set_val)
|
||
|
{
|
||
|
if (!IsLooping) {
|
||
|
if (time < Keys[0].Time) {
|
||
|
*set_val = Keys[0].Point;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (time >= Keys[Keys.Count() - 1].Time) {
|
||
|
*set_val = Keys[Keys.Count() - 1].Point;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int i0,i1;
|
||
|
float t;
|
||
|
Find_Interval(time,&i0,&i1,&t);
|
||
|
|
||
|
*set_val = Keys[i0].Point + t * (Keys[i1].Point - Keys[i0].Point);
|
||
|
}
|
||
|
|
||
|
const PersistFactoryClass & LinearCurve1DClass::Get_Factory(void) const
|
||
|
{
|
||
|
return _LinearCurve1DFactory;
|
||
|
}
|
||
|
|
||
|
bool LinearCurve1DClass::Save(ChunkSaveClass & csave)
|
||
|
{
|
||
|
csave.Begin_Chunk(LINEARCURVE1D_CHUNK_CURVE1D);
|
||
|
Curve1DClass::Save(csave);
|
||
|
csave.End_Chunk();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool LinearCurve1DClass::Load(ChunkLoadClass & cload)
|
||
|
{
|
||
|
while (cload.Open_Chunk()) {
|
||
|
|
||
|
switch(cload.Cur_Chunk_ID())
|
||
|
{
|
||
|
case LINEARCURVE1D_CHUNK_CURVE1D:
|
||
|
Curve1DClass::Load(cload);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
WWDEBUG_SAY(("Unhandled Chunk: 0x%X File: %s Line: %d\r\n",__FILE__,__LINE__));
|
||
|
break;
|
||
|
}
|
||
|
cload.Close_Chunk();
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|