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/wwphys/vistable.cpp

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