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/WWMath/curve.cpp

591 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;
}