261f4a3b1c
email comments from Andew Sampson, "I've contacted Terrex, and obtained the 2.2 version of their trpage library. The library included a fork of OSG 0.9.8's txp plugin, modified to load 2.1+ txp DBs. I've done the work of incorporating the changes made to OSG's txp plugin since 0.9.8 into Terrex's fork. The forked version is now up-to-date with the changes made to OSG 0.9.9 and 1.0. Terrex made a lot of changes (especially differences in whitespace), so the diff between the forked version and OSG 1.0's txp plugin is yucky. I did my best, but keep in mind that this is the result of a 4-way merge (kinda... terrex-0.9.8, stock-0.9.8, stock-0.9.9, stock-1.0). I really want to see this forked version merged back into the main OSG branch. The new features offered by this version of the plugin (2.1+ support, variable LOD support, bug fixes) are worth the trouble." -- Don Tidrow then took this code and added his work. -- Robert Osfield then fixed all the warnings that abound in the trpage code base.
711 lines
14 KiB
C++
711 lines
14 KiB
C++
/* ************************
|
|
Copyright Terrain Experts Inc.
|
|
Terrain Experts Inc (TERREX) reserves all rights to this source code
|
|
unless otherwise specified in writing by the President of TERREX.
|
|
This copyright may be updated in the future, in which case that version
|
|
supercedes this one.
|
|
-------------------
|
|
Terrex Experts Inc.
|
|
4400 East Broadway #314
|
|
Tucson, AZ 85711
|
|
info@terrex.com
|
|
Tel: (520) 323-7990
|
|
************************
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
/* trpage_readbuf.cpp
|
|
Methods for the trpgReadBuffer and trpgMemReadBuffer classes.
|
|
trpgReadBuffer is a virtual base class with a few utility functions.
|
|
It's used as generic interface for reading data out of.
|
|
trpgMemReadBuffer is a subclass of that which implements methods for
|
|
reading out of a chunk of memory. Data is read off of disk and then
|
|
dumped into a read buffer for parsing.
|
|
If you wanted to read directly from disk, for example, you could
|
|
implement a trpgDiskReadBuffer as a subclass of trpgReadBuffer.
|
|
*/
|
|
|
|
#include <trpage_io.h>
|
|
#include <trpage_swap.h>
|
|
|
|
/* **********************
|
|
Read buffer base class functions
|
|
**********************
|
|
*/
|
|
|
|
// Basic get functions
|
|
bool trpgReadBuffer::Get(int32 &ret)
|
|
{
|
|
int32 val;
|
|
|
|
if (!GetData((char *)&val,sizeof(int32))) return false;
|
|
if (ness != cpuNess)
|
|
ret = trpg_byteswap_int(val);
|
|
else
|
|
ret = val;
|
|
|
|
return true;
|
|
}
|
|
bool trpgReadBuffer::Get(int64 &ret)
|
|
{
|
|
int64 val;
|
|
|
|
if (!GetData((char *)&val,sizeof(int64))) return false;
|
|
// trpg_byteswap_llong is defined to be 64 bit
|
|
if (ness != cpuNess)
|
|
ret = trpg_byteswap_llong(val);
|
|
else
|
|
ret = val;
|
|
|
|
return true;
|
|
}
|
|
bool trpgReadBuffer::Get(char *ret,int retLen)
|
|
{
|
|
int32 len;
|
|
|
|
// Get the length first
|
|
if (!Get(len)) return false;
|
|
|
|
// Read what we can
|
|
int rlen = MIN(len,retLen-1);
|
|
if (!GetData(ret,rlen)) return false;
|
|
ret[rlen] = 0;
|
|
|
|
// Skip the rest
|
|
if (!Skip(len-rlen)) return false;
|
|
|
|
return true;
|
|
}
|
|
bool trpgReadBuffer::Get(std::string &str)
|
|
{
|
|
int32 len;
|
|
|
|
// Get the length first
|
|
if (!Get(len)) return false;
|
|
|
|
if (len < 0)
|
|
return false;
|
|
// Note: Should fix this
|
|
char *tmpStr = new char[len+1];
|
|
|
|
// Read it
|
|
if (!GetData(tmpStr,len)) return false;
|
|
tmpStr[len] = 0;
|
|
|
|
str = tmpStr;
|
|
|
|
return true;
|
|
}
|
|
bool trpgReadBuffer::Get(float32 &ret)
|
|
{
|
|
char cval[4];
|
|
|
|
if (!GetData(cval,sizeof(float32))) return false;
|
|
try {
|
|
if (ness == cpuNess)
|
|
memcpy(&ret,cval,4);
|
|
else
|
|
ret = trpg_byteswap_4bytes_to_float(cval);
|
|
}
|
|
catch (...) {
|
|
}
|
|
|
|
return true;
|
|
}
|
|
bool trpgReadBuffer::Get(float64 &ret)
|
|
{
|
|
char cval[8];
|
|
|
|
if (!GetData(cval,sizeof(float64))) return false;
|
|
try {
|
|
if (ness == cpuNess)
|
|
memcpy(&ret,cval,8);
|
|
else
|
|
ret = trpg_byteswap_8bytes_to_double(cval);
|
|
}
|
|
catch (...) {
|
|
}
|
|
|
|
return true;
|
|
}
|
|
bool trpgReadBuffer::Get(uint8 &ret)
|
|
{
|
|
uint8 val;
|
|
|
|
if (!GetData((char *)&val,sizeof(uint8))) return false;
|
|
// No byte swapping needed
|
|
ret = val;
|
|
|
|
return true;
|
|
}
|
|
|
|
//#if (bool != int32)
|
|
bool trpgReadBuffer::Get(bool &ret)
|
|
{
|
|
uint8 val;
|
|
|
|
if (!GetData((char *)&val,sizeof(uint8))) return false;
|
|
// No byte swapping needed
|
|
ret = (val == 0) ? false : true;
|
|
|
|
return true;
|
|
}
|
|
//#endif
|
|
|
|
#if (trpgDiskRef != int64)
|
|
bool trpgReadBuffer::Get(trpgDiskRef &ret)
|
|
{
|
|
trpgDiskRef val;
|
|
|
|
if (!GetData((char *)&val,sizeof(trpgDiskRef))) return false;
|
|
if (ness == cpuNess)
|
|
ret = val;
|
|
else
|
|
ret = trpg_byteswap_llong(val);
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
bool trpgReadBuffer::Get(trpgToken &ret)
|
|
{
|
|
trpgToken val;
|
|
|
|
if (!GetData((char *)&val,sizeof(trpgToken))) return false;
|
|
if (ness == cpuNess)
|
|
ret = val;
|
|
else
|
|
ret = trpg_byteswap_short(val);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Array Get functions
|
|
bool trpgReadBuffer::GetArray(int len,float32 **arr)
|
|
{
|
|
if (!GetDataRef((char **)arr,sizeof(float32)*len))
|
|
return false;
|
|
// Byteswap in place if necessary
|
|
if (ness != cpuNess) {
|
|
char *ptr;
|
|
int pos;
|
|
for (pos=0,ptr = (char *)*arr;pos<len;pos++,ptr+=4)
|
|
trpg_swap_four(ptr,ptr);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
bool trpgReadBuffer::GetArray(int len,float64 **arr)
|
|
{
|
|
if (!GetDataRef((char **)arr,sizeof(float64)*len))
|
|
return false;
|
|
// Byteswap in place if necessary
|
|
if (ness != cpuNess) {
|
|
char *ptr;
|
|
int pos;
|
|
for (pos=0,ptr = (char *)*arr;pos<len;pos++,ptr+=8)
|
|
trpg_swap_eight(ptr,ptr);
|
|
}
|
|
return true;
|
|
}
|
|
bool trpgReadBuffer::GetArray(int len,int32 **arr)
|
|
{
|
|
if (!GetDataRef((char **)arr,sizeof(int32)*len))
|
|
return false;
|
|
// Byteswap in place if necessary
|
|
if (ness != cpuNess) {
|
|
char *ptr;
|
|
int pos;
|
|
for (pos=0,ptr = (char *)*arr;pos<len;pos++,ptr+=4)
|
|
trpg_swap_four(ptr,ptr);
|
|
}
|
|
return true;
|
|
}
|
|
bool trpgReadBuffer::GetArray(int len,trpgColor **arr)
|
|
{
|
|
if (!GetDataRef((char **)arr,sizeof(trpgColor)*len))
|
|
return false;
|
|
// Byteswap in place if necessary
|
|
if (ness != cpuNess) {
|
|
char *ptr;
|
|
int pos;
|
|
for (pos=0,ptr = (char *)*arr;pos<len;pos++,ptr+=8)
|
|
trpg_swap_four(ptr,ptr);
|
|
}
|
|
return true;
|
|
}
|
|
bool trpgReadBuffer::GetArray(int len,char **arr)
|
|
{
|
|
return GetDataRef((char **)arr,sizeof(char)*len);
|
|
}
|
|
|
|
// Utility Get functions - Just call the others
|
|
bool trpgReadBuffer::Get(trpg2iPoint &pt)
|
|
{
|
|
if (!Get(pt.x) || !Get(pt.y))
|
|
return false;
|
|
return true;
|
|
}
|
|
bool trpgReadBuffer::Get(trpg2dPoint &pt)
|
|
{
|
|
if (!Get(pt.x) || !Get(pt.y))
|
|
return false;
|
|
return true;
|
|
}
|
|
bool trpgReadBuffer::Get(trpg3dPoint &pt)
|
|
{
|
|
if (!Get(pt.x) || !Get(pt.y) || !Get(pt.z))
|
|
return false;
|
|
return true;
|
|
}
|
|
bool trpgReadBuffer::Get(trpgColor &color)
|
|
{
|
|
if (!Get(color.red) || !Get(color.green) || !Get(color.blue))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// Get both a token and it's length, since that's fairly common
|
|
bool trpgReadBuffer::GetToken(trpgToken &tok,int32 &len)
|
|
{
|
|
if (!Get(tok) || !Get(len))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Limit Handling functions
|
|
These impose arbitrary lenght limits on the read buffer.
|
|
This keeps us from reading pased a token group and parsing
|
|
random data within an archive.
|
|
*/
|
|
// Push Limit
|
|
// Add another limit to the top of the stack
|
|
void trpgReadBuffer::PushLimit(int limit)
|
|
{
|
|
limits.push_back(limit);
|
|
}
|
|
|
|
// Pop Limit
|
|
// Remove the current limit from the stack
|
|
void trpgReadBuffer::PopLimit()
|
|
{
|
|
int len = limits.size();
|
|
|
|
if (len > 0)
|
|
limits.resize(len-1);
|
|
}
|
|
|
|
// Skip To Limit
|
|
// Skip to the end of the current limit.
|
|
// This happens when we bag the rest of the current token
|
|
bool trpgReadBuffer::SkipToLimit()
|
|
{
|
|
int len=0;
|
|
|
|
if (limits.size() != 0)
|
|
len = limits[limits.size()-1];
|
|
|
|
if (len > 0)
|
|
return Skip(len);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Test Limit
|
|
// See if the next read is going to blow the limits
|
|
bool trpgReadBuffer::TestLimit(int len)
|
|
{
|
|
for (unsigned int i=0;i<limits.size();i++)
|
|
if (len > limits[i])
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Update Limits
|
|
// We just read a few bytes. Update the limits
|
|
void trpgReadBuffer::UpdateLimits(int len)
|
|
{
|
|
for (unsigned int i=0;i<limits.size();i++)
|
|
limits[i] -= len;
|
|
}
|
|
|
|
/* *************************
|
|
Memory Read Buffer
|
|
*************************
|
|
*/
|
|
trpgMemReadBuffer::trpgMemReadBuffer(trpgEndian in_ness)
|
|
{
|
|
data = NULL;
|
|
len = totLen = pos = 0;
|
|
ness = in_ness;
|
|
cpuNess = trpg_cpu_byte_order();
|
|
}
|
|
trpgMemReadBuffer::~trpgMemReadBuffer()
|
|
{
|
|
if (data)
|
|
delete [] data;
|
|
}
|
|
|
|
// Empty check
|
|
bool trpgMemReadBuffer::isEmpty()
|
|
{
|
|
if (!data) return true;
|
|
|
|
if (pos >= len)
|
|
return true;
|
|
|
|
// Also test the limits
|
|
for (unsigned int i=0;i<limits.size();i++)
|
|
if (limits[i] == 0) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Set Length
|
|
// Allocate the given space
|
|
void trpgMemReadBuffer::SetLength(int newLen)
|
|
{
|
|
if (newLen > totLen) {
|
|
if (data)
|
|
delete [] data;
|
|
data = new char[newLen];
|
|
totLen = newLen;
|
|
}
|
|
len = newLen;
|
|
pos = 0;
|
|
}
|
|
|
|
// Get Data Ptr
|
|
// Return a pointer to our data so it can be written to
|
|
char *trpgMemReadBuffer::GetDataPtr()
|
|
{
|
|
return data;
|
|
}
|
|
|
|
// Get Data
|
|
// Protected method for actually retrieving a piece of data
|
|
bool trpgMemReadBuffer::GetData(char *ret,int rlen)
|
|
{
|
|
if (rlen < 0)
|
|
return false;
|
|
|
|
// Test against limits imposed from without
|
|
if (!TestLimit(rlen)) throw 1;
|
|
|
|
// See if we've actually got the data
|
|
if (pos+rlen > len) throw 1;
|
|
|
|
// Copy into the return buffer
|
|
memcpy(ret,&data[pos],rlen);
|
|
|
|
// Update any limits we might have
|
|
UpdateLimits(rlen);
|
|
|
|
pos += rlen;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Get Reference to Data
|
|
// Protected method that retrieves a reference to the given amoutn of data
|
|
bool trpgMemReadBuffer::GetDataRef(char **ret,int rlen)
|
|
{
|
|
if (rlen < 0) return false;
|
|
|
|
// Test against limits
|
|
if (!TestLimit(rlen)) throw 1;
|
|
if (pos + rlen > len) throw 1;
|
|
|
|
// Set up reference
|
|
*ret = &data[pos];
|
|
|
|
UpdateLimits(rlen);
|
|
pos += rlen;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Skip
|
|
// Same as read except we're not, uh, reading
|
|
bool trpgMemReadBuffer::Skip(int rlen)
|
|
{
|
|
if (rlen == 0)
|
|
return true;
|
|
|
|
if (rlen < 0)
|
|
return false;
|
|
|
|
// Test against limits
|
|
if (!TestLimit(rlen)) return false;
|
|
if (pos + rlen > len) return false;
|
|
|
|
UpdateLimits(rlen);
|
|
|
|
pos += rlen;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Appendable File read class
|
|
*/
|
|
|
|
trpgrAppFile::trpgrAppFile(trpgEndian inNess,const char *fileName)
|
|
{
|
|
Init(inNess,fileName);
|
|
}
|
|
|
|
void trpgrAppFile::Init(trpgEndian inNess,const char *fileName)
|
|
{
|
|
valid = false;
|
|
ness = inNess;
|
|
cpuNess = trpg_cpu_byte_order();
|
|
|
|
if (!(fp = fopen(fileName,"rb")))
|
|
return;
|
|
|
|
valid = true;
|
|
}
|
|
|
|
trpgrAppFile::~trpgrAppFile()
|
|
{
|
|
if (fp)
|
|
fclose(fp);
|
|
|
|
valid = false;
|
|
}
|
|
|
|
bool trpgrAppFile::isValid(void) const
|
|
{
|
|
return valid;
|
|
}
|
|
|
|
// Read a section of data from the given file
|
|
// and dump it into the given buffer
|
|
bool trpgrAppFile::Read(trpgMemReadBuffer *buf,int32 offset)
|
|
{
|
|
if (!valid) return false;
|
|
|
|
// Seek to the right location
|
|
if (fseek(fp,offset,SEEK_SET))
|
|
return false;
|
|
|
|
// Read a length
|
|
int32 len;
|
|
if (fread(&len,sizeof(int32),1,fp) != 1) {
|
|
valid = false;
|
|
return false;
|
|
}
|
|
|
|
// Byteswap if necessary
|
|
if (ness != cpuNess)
|
|
len = trpg_byteswap_int(len);
|
|
|
|
if (len < 0) {
|
|
valid = false;
|
|
return false;
|
|
}
|
|
|
|
buf->SetLength(len);
|
|
char *data = buf->GetDataPtr();
|
|
if (!data) {
|
|
valid = false;
|
|
return false;
|
|
}
|
|
|
|
if (fread(data,sizeof(char),len,fp) != (uint32)len) {
|
|
valid = false;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Read a section of data from the given file
|
|
and dump it into the given memory. Sanity
|
|
check the length against the size of the memory
|
|
passed into dataSize.
|
|
*/
|
|
bool trpgrAppFile::Read(char *data,int32 baseOffset,int32 objOffset,int32 dataSize)
|
|
{
|
|
if (!valid) return false;
|
|
|
|
// Seek to the right place
|
|
int result;
|
|
if ((result = fseek(fp,baseOffset,SEEK_SET))) {
|
|
valid = false;
|
|
return false;
|
|
}
|
|
|
|
// Read the total object length
|
|
int32 len;
|
|
if (fread(&len,sizeof(int32),1,fp) != 1) {
|
|
valid = false;
|
|
return false;
|
|
}
|
|
|
|
// Byteswap if necessary
|
|
if (ness != cpuNess)
|
|
len = trpg_byteswap_int(len);
|
|
|
|
if (len < 0) {
|
|
valid = false;
|
|
return false;
|
|
}
|
|
|
|
// It's all right to read less than the whole data block
|
|
if (objOffset+dataSize > len)
|
|
return false;
|
|
|
|
// Skip to the object offset
|
|
if (fseek(fp,objOffset,SEEK_CUR)) {
|
|
valid = false;
|
|
return false;
|
|
}
|
|
|
|
// Read the raw data
|
|
// Note: What about byte swapping?
|
|
if (fread(data,sizeof(char),dataSize,fp) != (uint32)dataSize) {
|
|
valid = false;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* App File Cache
|
|
This class manages a group of appendable files with
|
|
the same base name and extension. It will keep a certain
|
|
number of them open to facilitate caching.
|
|
*/
|
|
|
|
trpgrAppFileCache::trpgrAppFileCache(const char *inPre,const char *inExt,int noFiles)
|
|
{
|
|
Init(inPre,inExt,noFiles);
|
|
}
|
|
|
|
void trpgrAppFileCache::Init(const char *inPre,const char *inExt,int noFiles)
|
|
{
|
|
strcpy(baseName,inPre);
|
|
strcpy(ext,inExt);
|
|
|
|
files.resize(noFiles);
|
|
timeCount = 0;
|
|
}
|
|
|
|
trpgrAppFileCache::~trpgrAppFileCache()
|
|
{
|
|
unsigned int len = files.size();
|
|
for (unsigned int i=0;i<len;i++) {
|
|
if (files[i].afile) {
|
|
delete files[i].afile;
|
|
files[i].afile = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
trpgrAppFile *trpgrAppFileCache::GetNewRAppFile(trpgEndian ness, const char *fileName)
|
|
{
|
|
return new trpgrAppFile(ness,fileName);
|
|
}
|
|
|
|
trpgrAppFile *trpgrAppFileCache::GetFile(trpgEndian ness,int id)
|
|
{
|
|
return GetFile(ness,id,-1,-1);
|
|
}
|
|
|
|
/*
|
|
*/
|
|
trpgrAppFile *trpgrAppFileCache::GetFile(trpgEndian ness,int id,int col,int row)
|
|
{
|
|
// Look for it already here
|
|
int foundID = -1;
|
|
unsigned int i;
|
|
for (i=0;i<files.size();i++) {
|
|
if ((files[i].id == id)&&
|
|
(files[i].col == col) &&
|
|
(files[i].row == row)) {
|
|
foundID = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Found it in cache, just return
|
|
if (foundID != -1) {
|
|
OpenFile &of = files[foundID];
|
|
|
|
if (of.afile->isValid())
|
|
{
|
|
of.lastUsed = timeCount;
|
|
return of.afile;
|
|
}
|
|
else
|
|
{
|
|
if (of.afile)
|
|
delete of.afile;
|
|
of.afile = NULL;
|
|
}
|
|
}
|
|
|
|
// Didn't find it. Need to reclaim one
|
|
// Look for the oldest used
|
|
int oldTime=-1,oldID=-1;
|
|
for (i=0;i<files.size();i++) {
|
|
OpenFile &of = files[i];
|
|
if (!of.afile || (oldTime == -1) || (of.lastUsed < oldTime)) {
|
|
oldID = i;
|
|
oldTime = of.lastUsed;
|
|
if (!of.afile)
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// Reclaim this one
|
|
OpenFile &of = files[oldID];
|
|
if (of.afile)
|
|
delete of.afile;
|
|
char fileName[1024];
|
|
if(col==-1) {
|
|
sprintf(fileName,"%s_%d.%s",baseName,id,ext);
|
|
}
|
|
else {
|
|
char dir[1024];
|
|
char filebase[1024];
|
|
//this is very ugly, but it avoids radical API changes
|
|
// find the last PATHSEPERATOR in the baseName string
|
|
int len = strlen(baseName);
|
|
while(--len > 0) {
|
|
if(baseName[len]==PATHSEPERATOR[0]) {
|
|
strcpy(filebase,&baseName[len+1]);
|
|
strcpy(dir,baseName);
|
|
dir[len]='\0';
|
|
break;
|
|
}
|
|
}
|
|
sprintf(fileName,"%s" PATHSEPERATOR "%d" PATHSEPERATOR "%d" PATHSEPERATOR "%s_%d.%s",
|
|
dir,col,row,filebase,id,ext);
|
|
}
|
|
of.afile = GetNewRAppFile(ness,fileName);
|
|
of.id = id;
|
|
of.row = row;
|
|
of.col = col;
|
|
of.lastUsed = timeCount;
|
|
|
|
timeCount++;
|
|
|
|
return of.afile;
|
|
}
|
|
|
|
// Constructor for OpenFile class
|
|
trpgrAppFileCache::OpenFile::OpenFile()
|
|
{
|
|
afile = NULL;
|
|
lastUsed = 0;
|
|
id = -1;
|
|
}
|