604 lines
15 KiB
C++
604 lines
15 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 : WWPhys *
|
|
* *
|
|
* $Archive:: /Commando/Code/wwphys/vistable.cpp $*
|
|
* *
|
|
* Author:: Greg Hjelstrom *
|
|
* *
|
|
* $Modtime:: 6/11/01 6:06p $*
|
|
* *
|
|
* $Revision:: 24 $*
|
|
* *
|
|
*---------------------------------------------------------------------------------------------*
|
|
* Functions: *
|
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
|
|
#include "vistable.h"
|
|
#include "wwphysids.h"
|
|
#include "pscene.h"
|
|
#include "chunkio.h"
|
|
#include "wwdebug.h"
|
|
#include "lzo.h"
|
|
#include "lzo1x.h"
|
|
#include "phys.h"
|
|
#include "wwmemlog.h"
|
|
#include <windows.h>
|
|
|
|
/*
|
|
** Chunk ID's used by a visibility table to save itself
|
|
*/
|
|
enum {
|
|
VISTABLE_CHUNK_BYTECOUNT = 0x00000001, // number of bytes in this pvs
|
|
VISTABLE_CHUNK_BYTES, // pvs bytes, compressed with lzhl (OBSOLETE!)
|
|
VISTABLE_CHUNK_LZOBYTES // pvs bytes, compressed with lzo
|
|
};
|
|
|
|
|
|
/**
|
|
** BitCounterClass
|
|
** This class is used to accelerate some of the bit-vector comparing and counting that
|
|
** goes on in VisTableClass.
|
|
*/
|
|
class BitCounterClass
|
|
{
|
|
public:
|
|
BitCounterClass(void);
|
|
|
|
int Count_True_Bits(uint8 byte) { return TrueBits[byte]; }
|
|
|
|
protected:
|
|
|
|
uint8 TrueBits[256];
|
|
};
|
|
|
|
BitCounterClass::BitCounterClass(void)
|
|
{
|
|
/*
|
|
** Initialize the table of how many "on" bits there are
|
|
** in each number between 0 and 256
|
|
*/
|
|
for (int i=0; i<256; i++) {
|
|
TrueBits[i] = 0;
|
|
for (int bit=0; bit<8; bit++) {
|
|
if (i & (1<<bit)) {
|
|
TrueBits[i]++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static BitCounterClass _TheBitCounter;
|
|
|
|
|
|
/****************************************************************************************************
|
|
**
|
|
** VisTableClass Implementation
|
|
**
|
|
****************************************************************************************************/
|
|
|
|
VisTableClass::VisTableClass(unsigned bitcount,int id) :
|
|
BitCount(bitcount),
|
|
Buffer(NULL),
|
|
VisSectorID(id),
|
|
Timestamp(0)
|
|
{
|
|
Alloc_Buffer(bitcount);
|
|
}
|
|
|
|
VisTableClass::VisTableClass(CompressedVisTableClass * ctable,int bitcount,int id) :
|
|
BitCount(bitcount),
|
|
Buffer(NULL),
|
|
VisSectorID(id),
|
|
Timestamp(0)
|
|
{
|
|
WWASSERT(ctable != NULL);
|
|
Alloc_Buffer(bitcount);
|
|
ctable->Decompress(Get_Bytes(),Get_Byte_Count());
|
|
}
|
|
|
|
VisTableClass::VisTableClass(const VisTableClass & that) :
|
|
BitCount(0),
|
|
Buffer(NULL),
|
|
VisSectorID(0),
|
|
Timestamp(0)
|
|
{
|
|
*this = that;
|
|
}
|
|
|
|
VisTableClass & VisTableClass::operator = (const VisTableClass & that)
|
|
{
|
|
Alloc_Buffer(that.BitCount);
|
|
memcpy(Buffer,that.Buffer,Get_Byte_Count());
|
|
BitCount = that.BitCount;
|
|
VisSectorID = that.VisSectorID;
|
|
Timestamp = that.Timestamp;
|
|
return *this;
|
|
}
|
|
|
|
|
|
VisTableClass::~VisTableClass(void)
|
|
{
|
|
if (Buffer != NULL) {
|
|
delete[] Buffer;
|
|
Buffer = NULL;
|
|
}
|
|
BitCount = 0;
|
|
}
|
|
|
|
void VisTableClass::Alloc_Buffer(int bitcount)
|
|
{
|
|
WWMEMLOG(MEM_VIS);
|
|
if (Buffer != NULL) {
|
|
delete[] Buffer;
|
|
Buffer = NULL;
|
|
}
|
|
BitCount = bitcount;
|
|
int count = ((bitcount + 31) / 32);
|
|
Buffer = new uint32[count];
|
|
memset(Buffer,0,count * sizeof(uint32));
|
|
}
|
|
|
|
uint8 * VisTableClass::Get_Bytes(void) const
|
|
{
|
|
return (uint8*)Buffer;
|
|
}
|
|
|
|
int VisTableClass::Get_Byte_Count(void) const
|
|
{
|
|
/*
|
|
** In order to avoid "endian-ness" problems in the routines that
|
|
** loop over the bytes in the buffer, I'm returning the byte count
|
|
** up to the last longword. The extra bits should always be zero
|
|
** so they should not screw up anything.
|
|
*/
|
|
return Get_Long_Count() * 4; //(BitCount + 7) / 8;
|
|
}
|
|
|
|
uint32 * VisTableClass::Get_Longs(void) const
|
|
{
|
|
return Buffer;
|
|
}
|
|
|
|
int VisTableClass::Get_Long_Count(void) const
|
|
{
|
|
return (BitCount + 31) / 32;
|
|
}
|
|
|
|
void VisTableClass::Reset_All(void)
|
|
{
|
|
if (Buffer != NULL) {
|
|
memset(Buffer,0x00,Get_Byte_Count());
|
|
}
|
|
}
|
|
|
|
void VisTableClass::Set_All(void)
|
|
{
|
|
/*
|
|
** Set the buffer to FF's
|
|
*/
|
|
if (Buffer != NULL) {
|
|
memset((uint8*)Buffer,0xFF,Get_Byte_Count());
|
|
}
|
|
|
|
/*
|
|
** Make sure the trailing bits are zero'd (just for sanity...)
|
|
*/
|
|
for (int i=BitCount; i<32*Get_Long_Count(); i++) {
|
|
Buffer[i>>5] &= ~(0x80000000u >> (i & 0x01F));
|
|
}
|
|
}
|
|
|
|
void VisTableClass::Delete_Bit(int delete_index)
|
|
{
|
|
int i;
|
|
|
|
/*
|
|
** first handle the long that the deleted bit is in
|
|
*/
|
|
int first_long = delete_index >> 5;
|
|
int start_bits = WWMath::Min((first_long + 1)<<5,BitCount-1);
|
|
|
|
for (i=delete_index; i<start_bits; i++) {
|
|
Set_Bit(i,(Get_Bit(i+1) != 0));
|
|
}
|
|
|
|
/*
|
|
** handle the reset of the buffer. There are two cases here, if the deleted
|
|
** bit was in the last long of the buffer, we simply have to clear the trailing
|
|
** bit. Otherwise we need to shift the rest of the longs in the buffer right
|
|
** by one bit and or in the MSB from the following long.
|
|
*/
|
|
int long_count = Get_Long_Count();
|
|
if (first_long == long_count-1) {
|
|
Set_Bit(BitCount-1,false);
|
|
} else {
|
|
for (i=first_long+1; i<long_count-1; i++) {
|
|
Buffer[i] = (Buffer[i]<<1) | ((Buffer[i+1] & 0x80000000) >> 31);
|
|
}
|
|
Buffer[long_count-1] = Buffer[long_count-1]<<1;
|
|
}
|
|
|
|
/*
|
|
** Finally, record that a bit was removed.
|
|
*/
|
|
BitCount--;
|
|
}
|
|
|
|
void VisTableClass::Merge(const VisTableClass & that)
|
|
{
|
|
if (that.BitCount != BitCount) {
|
|
return;
|
|
}
|
|
for (int i=0; i<Get_Long_Count(); i++) {
|
|
Buffer[i] = Buffer[i] | that.Buffer[i];
|
|
}
|
|
}
|
|
|
|
void VisTableClass::Invert(void)
|
|
{
|
|
for (int i=0; i<Get_Long_Count(); i++) {
|
|
Buffer[i] = ~Buffer[i];
|
|
}
|
|
}
|
|
|
|
bool VisTableClass::Is_Equal_To(const VisTableClass & that)
|
|
{
|
|
if (BitCount != that.BitCount) {
|
|
return false;
|
|
}
|
|
if (memcmp((void*)Buffer,(void*)that.Buffer,Get_Byte_Count()) != 0) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int VisTableClass::Count_Differences(const VisTableClass & that)
|
|
{
|
|
if (BitCount != that.BitCount) {
|
|
return BitCount;
|
|
}
|
|
|
|
int counter = 0;
|
|
int byte_count = Get_Byte_Count();
|
|
uint8 * my_bytes = Get_Bytes();
|
|
uint8 * his_bytes = that.Get_Bytes();
|
|
|
|
for (int i=0; i<byte_count; i++) {
|
|
counter += _TheBitCounter.Count_True_Bits(my_bytes[i] ^ his_bytes[i]);
|
|
}
|
|
return counter;
|
|
}
|
|
|
|
int VisTableClass::Count_True_Bits(void)
|
|
{
|
|
int counter = 0;
|
|
int byte_count = Get_Byte_Count();
|
|
uint8 * my_bytes = Get_Bytes();
|
|
|
|
for (int i=0; i<byte_count; i++) {
|
|
counter += _TheBitCounter.Count_True_Bits(my_bytes[i]);
|
|
}
|
|
return counter;
|
|
}
|
|
|
|
float VisTableClass::Match_Fraction(const VisTableClass & that)
|
|
{
|
|
if (BitCount != that.BitCount) {
|
|
return 0.0f;
|
|
}
|
|
|
|
/*
|
|
** or_counter will be the number of bits that were on in at least one of the vectors
|
|
** xor_counter will be the count of bits that were different (1 in one and zero in the other)
|
|
*/
|
|
int xor_counter = 0;
|
|
int or_counter = 0;
|
|
|
|
int byte_count = Get_Byte_Count();
|
|
uint8 * my_bytes = Get_Bytes();
|
|
uint8 * his_bytes = that.Get_Bytes();
|
|
|
|
for (int i=0; i<byte_count; i++) {
|
|
xor_counter += _TheBitCounter.Count_True_Bits(my_bytes[i] ^ his_bytes[i]);
|
|
or_counter += _TheBitCounter.Count_True_Bits(my_bytes[i] | his_bytes[i]);
|
|
}
|
|
|
|
/*
|
|
** match fraction is 1 - (different_bits / on_bits)
|
|
*/
|
|
if (or_counter == 0) {
|
|
return 1.0f;
|
|
} else {
|
|
return 1.0f - (float)xor_counter / (float)or_counter;
|
|
}
|
|
}
|
|
|
|
|
|
/****************************************************************************************************
|
|
**
|
|
** CompressedVisTableClass Implementation
|
|
**
|
|
****************************************************************************************************/
|
|
|
|
|
|
CompressedVisTableClass::CompressedVisTableClass(void) :
|
|
BufferSize(0),
|
|
Buffer(NULL)
|
|
{
|
|
}
|
|
|
|
CompressedVisTableClass::CompressedVisTableClass(VisTableClass * bits) :
|
|
BufferSize(0),
|
|
Buffer(NULL)
|
|
{
|
|
WWMEMLOG(MEM_VIS);
|
|
WWASSERT(bits != NULL);
|
|
Compress(bits->Get_Bytes(),bits->Get_Byte_Count());
|
|
}
|
|
|
|
CompressedVisTableClass::CompressedVisTableClass(const CompressedVisTableClass &that) :
|
|
BufferSize(0),
|
|
Buffer(NULL)
|
|
{
|
|
(*this) = that;
|
|
}
|
|
|
|
CompressedVisTableClass::~CompressedVisTableClass(void)
|
|
{
|
|
if (Buffer != NULL) {
|
|
delete[] Buffer;
|
|
}
|
|
}
|
|
|
|
const CompressedVisTableClass &CompressedVisTableClass::operator= (const CompressedVisTableClass &that)
|
|
{
|
|
WWMEMLOG(MEM_VIS);
|
|
if (Buffer != NULL) {
|
|
delete [] Buffer;
|
|
Buffer = NULL;
|
|
}
|
|
|
|
BufferSize = that.BufferSize;
|
|
Buffer = new uint8[BufferSize];
|
|
::memcpy (Buffer, that.Buffer, sizeof(uint8)*BufferSize);
|
|
return *this;
|
|
}
|
|
|
|
uint8 * CompressedVisTableClass::Get_Bytes(void)
|
|
{
|
|
return Buffer;
|
|
}
|
|
|
|
int CompressedVisTableClass::Get_Byte_Count(void) const
|
|
{
|
|
return BufferSize;
|
|
}
|
|
|
|
void CompressedVisTableClass::Load(ChunkLoadClass & cload)
|
|
{
|
|
WWMEMLOG(MEM_VIS);
|
|
VisTableClass * old_table = NULL;
|
|
if (Buffer != NULL) {
|
|
old_table = NEW_REF(VisTableClass,(this,PhysicsSceneClass::Get_Instance()->Get_Vis_Table_Size(),0));
|
|
delete[] Buffer;
|
|
}
|
|
|
|
cload.Open_Chunk();
|
|
if (cload.Cur_Chunk_ID() != VISTABLE_CHUNK_BYTECOUNT) {
|
|
cload.Close_Chunk();
|
|
return;
|
|
}
|
|
cload.Read(&BufferSize,sizeof(BufferSize));
|
|
cload.Close_Chunk();
|
|
|
|
Buffer = new uint8[BufferSize];
|
|
|
|
/*
|
|
** Load the compressed visibility bits. At one point in the past,
|
|
** we were using the lzhl compression scheme, if we encounter that chunk,
|
|
** just reset vis because that compressor had bugs and the data is probably
|
|
** invalid
|
|
*/
|
|
bool load_error = false;
|
|
while (cload.Open_Chunk()) {
|
|
switch (cload.Cur_Chunk_ID()) {
|
|
case VISTABLE_CHUNK_BYTES:
|
|
cload.Read(Buffer,BufferSize);
|
|
PhysicsSceneClass::Get_Instance()->Reset_Vis();
|
|
load_error = true;
|
|
break;
|
|
case VISTABLE_CHUNK_LZOBYTES:
|
|
WWASSERT(cload.Cur_Chunk_Length() == (uint32)BufferSize);
|
|
cload.Read(Buffer,BufferSize);
|
|
break;
|
|
default:
|
|
WWDEBUG_SAY(("Unhandled chunk ID: %d in vistable.cpp\r\n",cload.Cur_Chunk_ID()));
|
|
load_error = true;
|
|
break;
|
|
}
|
|
cload.Close_Chunk();
|
|
}
|
|
|
|
/*
|
|
** if we loaded a valid vis table and we had a previous valid table, merge
|
|
** the two together
|
|
*/
|
|
if ((load_error == false) && (old_table != NULL)) {
|
|
VisTableClass * new_table = NEW_REF(VisTableClass,(this,PhysicsSceneClass::Get_Instance()->Get_Vis_Table_Size(),0));
|
|
new_table->Merge(*old_table);
|
|
Compress(new_table->Get_Bytes(),new_table->Get_Byte_Count());
|
|
REF_PTR_RELEASE(new_table);
|
|
}
|
|
REF_PTR_RELEASE(old_table);
|
|
}
|
|
|
|
void CompressedVisTableClass::Save(ChunkSaveClass & csave)
|
|
{
|
|
uint32 bytecount = BufferSize;
|
|
csave.Begin_Chunk(VISTABLE_CHUNK_BYTECOUNT);
|
|
csave.Write(&bytecount,sizeof(bytecount));
|
|
csave.End_Chunk();
|
|
|
|
csave.Begin_Chunk(VISTABLE_CHUNK_LZOBYTES);
|
|
csave.Write(Buffer,bytecount);
|
|
csave.End_Chunk();
|
|
}
|
|
|
|
void CompressedVisTableClass::Load (void* hfile)
|
|
{
|
|
WWMEMLOG(MEM_VIS);
|
|
/*
|
|
** Free the buffer
|
|
*/
|
|
if (Buffer != NULL) {
|
|
delete [] Buffer;
|
|
Buffer = NULL;
|
|
BufferSize = 0L;
|
|
}
|
|
|
|
if ((HANDLE)hfile != INVALID_HANDLE_VALUE) {
|
|
|
|
/*
|
|
** Read the buffer size
|
|
*/
|
|
uint32 dwbytes_read = 0L;
|
|
::ReadFile ((HANDLE)hfile, &BufferSize, sizeof (BufferSize), &dwbytes_read, NULL);
|
|
|
|
/*
|
|
** Read the buffer
|
|
*/
|
|
Buffer = new uint8[BufferSize];
|
|
::ReadFile ((HANDLE)hfile, Buffer, sizeof (uint8) * BufferSize, &dwbytes_read, NULL);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void CompressedVisTableClass::Save (void* hfile)
|
|
{
|
|
if ((HANDLE)hfile != INVALID_HANDLE_VALUE) {
|
|
|
|
/*
|
|
** Write the buffer size
|
|
*/
|
|
uint32 dwbytes_written = 0L;
|
|
::WriteFile ((HANDLE)hfile, &BufferSize, sizeof (BufferSize), &dwbytes_written, NULL);
|
|
|
|
/*
|
|
** Write the buffer
|
|
*/
|
|
::WriteFile ((HANDLE)hfile, Buffer, sizeof (uint8) * BufferSize, &dwbytes_written, NULL);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void CompressedVisTableClass::Compress(uint8 * src_buffer,int src_size)
|
|
{
|
|
WWMEMLOG(MEM_VIS);
|
|
if (Buffer != NULL) {
|
|
delete[] Buffer;
|
|
Buffer = NULL;
|
|
}
|
|
|
|
uint8 * comp_buffer = new uint8[LZO_BUFFER_SIZE(src_size)];
|
|
lzo_uint comp_size;
|
|
int lzocode = LZOCompressor::Compress(src_buffer,src_size,comp_buffer,&comp_size);
|
|
WWASSERT(lzocode == LZO_E_OK);
|
|
|
|
BufferSize = comp_size;
|
|
Buffer = new uint8[BufferSize];
|
|
memcpy(Buffer,comp_buffer,BufferSize);
|
|
|
|
#ifdef WWDEBUG
|
|
lzo_uint decomp_size;
|
|
LZOCompressor::Decompress(Buffer,BufferSize,comp_buffer,&decomp_size);
|
|
WWASSERT(decomp_size == (lzo_uint)src_size);
|
|
WWASSERT(src_size % 4 == 0);
|
|
#endif
|
|
|
|
delete[] comp_buffer;
|
|
}
|
|
|
|
void CompressedVisTableClass::Decompress(uint8 * decomp_buffer,int decomp_size)
|
|
{
|
|
WWMEMLOG(MEM_VIS);
|
|
lzo_uint size;
|
|
LZOCompressor::Decompress(Buffer,BufferSize,decomp_buffer, &size);
|
|
WWASSERT((int)size == decomp_size);
|
|
}
|
|
|
|
#if 0
|
|
void CompressedVisTableClass::Compare_Compression(void)
|
|
{
|
|
#ifdef WWDEBUG
|
|
static int num_compressions = 0;
|
|
static int total_size = 0;
|
|
static int lcw_size = 0;
|
|
static int lzo_size = 0;
|
|
static int lcw_failures = 0;
|
|
static int lzo_failures = 0;
|
|
|
|
int test_size = tmp_size;
|
|
uint8 * test_buf = tmp_buffer;
|
|
|
|
num_compressions++;
|
|
total_size += test_size;
|
|
|
|
// Testing LCW
|
|
uint8 * lcw_comp_buf = new uint8[test_size * 2];
|
|
int lcw_comp_size = LCW_Comp(test_buf, lcw_comp_buf, test_size);
|
|
|
|
uint8 * lcw_decomp_buf = new uint8[test_size * 2];
|
|
int lcw_decomp_size = LCW_Uncomp(lcw_comp_buf, lcw_decomp_buf);
|
|
|
|
lcw_size+=lcw_comp_size;
|
|
if ((memcmp(test_buf,lcw_decomp_buf,test_size) != 0) || (lcw_decomp_size != test_size)) {
|
|
lcw_failures++;
|
|
}
|
|
|
|
// Testing LZO
|
|
uint8 * lzo_comp_buf = new uint8[test_size * 2]; //LZO_BUFFER_SIZE(test_size)];
|
|
lzo_uint lzo_comp_size;
|
|
LZOCompressor::Compress(test_buf,test_size,lzo_comp_buf,&lzo_comp_size);
|
|
|
|
uint8 * lzo_decomp_buf = new uint8[test_size];
|
|
lzo_uint lzo_decomp_size;
|
|
LZOCompressor::Decompress(lzo_comp_buf,lzo_comp_size,lzo_decomp_buf,&lzo_decomp_size);
|
|
|
|
lzo_size+=lzo_comp_size;
|
|
if ((memcmp(test_buf,lzo_decomp_buf,test_size) != 0) || ((int)lzo_decomp_size != test_size)) {
|
|
lzo_failures++;
|
|
}
|
|
|
|
delete[] lcw_comp_buf;
|
|
delete[] lcw_decomp_buf;
|
|
delete[] lzo_comp_buf;
|
|
delete[] lzo_decomp_buf;
|
|
#endif
|
|
delete[] tmp_buffer;
|
|
}
|
|
|
|
#endif
|