Files
OpenSceneGraph/src/osgPlugins/txp/trpage_warchive.cpp

1610 lines
44 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 <osgDB/FileUtils>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/* trpage_warchive.cpp
This source file contains the implementations of trpgwArchive and trpgwGeomHelper.
The Write Archive is used to write TerraPage archives. All its important methods
are virtual, so you shouldn't need to modify any of this code. Simply subclass
and override.
The Geometry Helper is a class that's used to sort out polygons and build
trpgGeometry objects, containing triangle strips and fans out of them. The one
contained here is fairly simple, but all its important methods are virtual. So
again, subclass and override if you need to change them.
*/
#include <trpage_geom.h>
#include <trpage_write.h>
#include <trpage_compat.h>
#include <trpage_read.h>
// Constructor
trpgwArchive::trpgwArchive(trpgEndian inNess,trpgwArchive::TileMode inTileMode,int majorVer, int minorVer)
{
Init(inNess,inTileMode,majorVer, minorVer);
}
void trpgwArchive::Init(trpgEndian inNess, trpgwArchive::TileMode inTileMode,int majorVer, int minorVer)
{
minorVersion = minorVer;
majorVersion = majorVer;
if (majorVersion < 1 || majorVersion > TRPG_VERSION_MAJOR)
throw 1;
if(majorVersion == TRPG_VERSION_MAJOR)
{
if(minorVersion < 0 || minorVersion > TRPG_VERSION_MINOR)
throw 1;
}
fp = NULL;
strcpy(dir,".");
ness = inNess;
tileMode = inTileMode;
cpuNess = trpg_cpu_byte_order();
tileFile = NULL;
tileFileCount = 0;
isRegenerate = false;
maxTileFileLen = -1;
firstHeaderWrite = true;
}
// Constructor for regenerate
trpgwArchive::trpgwArchive(char *inDir,char *inFile,trpg2dPoint &sw, trpg2dPoint &ne, int majorVer, int minorVer)
{
Init(inDir,inFile,sw,ne, majorVer, minorVer);
}
void trpgwArchive::Init(char *inDir,char *inFile,trpg2dPoint &sw, trpg2dPoint &ne, int majorVer, int minorVer)
{
maxTileFileLen = -1;
majorVersion = majorVer;
minorVersion = minorVer;
fp = NULL;
strcpy(dir,inDir);
cpuNess = trpg_cpu_byte_order();
tileFile = NULL;
tileFileCount = 0;
isRegenerate = true;
errMess[0] = '\0';
firstHeaderWrite = true;
// TODO: have a "setup from file" method for trpgwArchive
// Open a Read Archive to get the rest of the info we need
trpgr_Archive *inArch = this->GetArchiveReader();
inArch->SetDirectory(inDir);
if (!inArch->OpenFile(inFile))
{
delete inArch;
throw 1;
}
// Get the header (this is what we need)
if (!inArch->ReadHeader())
{
delete inArch;
throw 1;
}
ness = inArch->GetEndian();
const trpgHeader *inHeader = inArch->GetHeader();
// use the version in the archive instead.
inHeader->GetVersion(majorVersion,minorVersion);
// Expand the coverage
trpg2dPoint newSW,newNE;
trpg2dPoint oldSW,oldNE;
// Or not. Have to add in something to avoid recalculation
// when merging geocentric databases. We don't really support
// them, and everything goes to hell. So: hack is:
// if sw=ne, don't change anything.
// This will also help a little with MMB TXP merge speed.
bool extentsUnchanged=false;
inHeader->GetExtents(oldSW,oldNE);
// just checking for equality right now. Bad?
if ((sw==ne) || ((oldSW==sw) && (oldNE==ne)))
{
extentsUnchanged = true;
// set up passed-in SW and NE as well.
sw=newSW=oldSW;
ne=newNE=oldNE;
}
else
{
newSW.x = MIN(sw.x,oldSW.x);
newSW.y = MIN(sw.y,oldSW.y);
newNE.x = MAX(ne.x,oldNE.x);
newNE.y = MAX(ne.y,oldNE.y);
}
// Decide what the offset should be for new tiles
if (!extentsUnchanged)
{
trpg2dPoint blockSize;
inHeader->GetTileSize(0,blockSize);
double dx = (oldSW.x - newSW.x)/blockSize.x + 10e-10;
double dy = (oldSW.y - newSW.y)/blockSize.y + 10e-10;
addOffset.x = (int)dx;
addOffset.y = (int)dy;
if (dx - addOffset.x > 10e-4 ||
dy - addOffset.y > 10e-4) {
delete inArch;
throw 1;
}
}
// Header can mostly stay the same
header = *inHeader;
header.SetExtents(newSW,newNE);
header.GetNumLods(numLod);
// Update to the new MBR and tile grid sizes
if (!extentsUnchanged) {
for (int i=0;i<numLod;i++) {
// Figure out the tile grid size
trpg2dPoint tileSize;
inHeader->GetTileSize(i,tileSize);
trpg2iPoint newTileExt;
newTileExt.x = int((newNE.x - newSW.x)/tileSize.x + 10e-5);
newTileExt.y = int((newNE.y - newSW.y)/tileSize.y + 10e-15);
header.SetLodSize(i,newTileExt);
}
}
// These tables we can copy straight over
matTable = *inArch->GetMaterialTable();
texTable = *inArch->GetTexTable();
modelTable = *inArch->GetModelTable();
lightTable = *inArch->GetLightTable();
rangeTable = *inArch->GetRangeTable();
textStyleTable = *inArch->GetTextStyleTable();
supportStyleTable = *inArch->GetSupportStyleTable();
labelPropertyTable = *inArch->GetLabelPropertyTable();
// Need to resize the tile table (maybe)
// NOTE: Starting with version 2.1, the tile tables will contain only
// the lod 0 tiles
trpgTileTable::TileMode tileTableMode;
if (!extentsUnchanged) {
const trpgTileTable *oldTiles = inArch->GetTileTable();
oldTiles->GetMode(tileTableMode);
tileTable.SetMode(tileTableMode);
if(majorVersion == 2 && minorVersion >=1)
{
// Version 2.1. we store only lod 0, all other lod tiles are
// stored in the parent tile
tileTable.SetNumLod(0);
// Size the output tile table
trpg2iPoint tileSize;
header.GetLodSize(0,tileSize);
tileTable.SetNumTiles(tileSize.x, tileSize.y, 0);
// Copy over individual tiles
trpg2iPoint levelOffset;
levelOffset.x = addOffset.x;
levelOffset.y = addOffset.y;
trpg2iPoint oldTileSize;
inHeader->GetLodSize(0, oldTileSize);
for (int ix=0;ix<oldTileSize.x;ix++) {
for (int iy=0;iy<oldTileSize.y;iy++) {
trpgwAppAddress addr;
float zmin,zmax;
oldTiles->GetTile(ix, iy, 0,addr,zmin,zmax);
tileTable.SetTile(ix+addOffset.x, iy+addOffset.y ,0, addr, zmin, zmax);
}
}
}
else
{
tileTable.SetNumLod(numLod);
for (int lod=0;lod<numLod;lod++) {
// Size the output tile table
trpg2iPoint tileSize;
header.GetLodSize(lod,tileSize);
tileTable.SetNumTiles(tileSize.x,tileSize.y,lod);
// Copy over individual tiles
trpg2iPoint levelOffset;
levelOffset.x = addOffset.x*(lod+1);
levelOffset.y = addOffset.y*(lod+1);
trpg2iPoint oldTileSize;
inHeader->GetLodSize(lod,oldTileSize);
for (int ix=0;ix<oldTileSize.x;ix++) {
for (int iy=0;iy<oldTileSize.y;iy++) {
trpgwAppAddress addr;
float zmin,zmax;
oldTiles->GetTile(ix,iy,lod,addr,zmin,zmax);
tileTable.SetTile(ix+addOffset.x,iy+addOffset.y,lod,addr,zmin,zmax);
}
}
}
}
} else {
tileTable = *inArch->GetTileTable();
tileTable.GetMode(tileTableMode);
}
// Continue to work in the mode the original database is in
switch(tileTableMode)
{
case trpgTileTable::Local:
tileMode = TileLocal;
break;
case trpgTileTable::External:
tileMode = TileExternal;
break;
case trpgTileTable::ExternalSaved:
tileMode = TileExternalSaved;
break;
}
// That's it for the read archive
delete inArch;
}
// Destructor
trpgwArchive::~trpgwArchive()
{
if (fp)
fclose(fp);
if (tileFile) {
delete tileFile;
tileFile = NULL;
}
}
// WriteHeaderData
int32 trpgwArchive::WriteHeaderData(const char *dataPtr, int length, FILE* /*filehandle*/)
{
return fwrite(dataPtr,1,length,fp);
}
// IsValid()
// Verifies that our file is open
bool trpgwArchive::isValid() const
{
if (!fp)
{
strcpy(errMess, "File object do not exist");
return false;
}
return true;
}
const char *trpgwArchive::getErrMess() const
{
if(errMess[0] == '\0')
return 0;
else
return &errMess[0];
}
// Set the maximum advised size for a tile file
void trpgwArchive::SetMaxTileFileLength(int max)
{
maxTileFileLen = max;
}
/* Set Functions
These just copy tables and the header from the input.
If these aren't set, then empty ones are written.
*/
bool trpgwArchive::SetHeader(const trpgHeader &head)
{
header = head;
return true;
}
bool trpgwArchive::SetMaterialTable(const trpgMatTable &mat)
{
matTable = mat;
return true;
}
bool trpgwArchive::SetTextureTable(const trpgTexTable &tex)
{
texTable = tex;
return true;
}
bool trpgwArchive::SetModelTable(const trpgModelTable &models)
{
modelTable = models;
return true;
}
bool trpgwArchive::SetLightTable(const trpgLightTable &lights)
{
lightTable = lights;
return true;
}
bool trpgwArchive::SetRangeTable(const trpgRangeTable &ranges)
{
rangeTable = ranges;
return true;
}
bool trpgwArchive::SetTextStyleTable(const trpgTextStyleTable &styles)
{
textStyleTable = styles;
return true;
}
bool trpgwArchive::SetLabelPropertyTable(const trpgLabelPropertyTable &properties)
{
labelPropertyTable = properties;
return true;
}
bool trpgwArchive::SetSupportStyleTable(const trpgSupportStyleTable &styles)
{
supportStyleTable = styles;
return true;
}
/* Get Methods
Used in regenerate.
*/
trpgHeader *trpgwArchive::GetHeader()
{
return &header;
}
trpgMatTable *trpgwArchive::GetMatTable()
{
return &matTable;
}
trpgTexTable *trpgwArchive::GetTextureTable()
{
return &texTable;
}
trpgModelTable *trpgwArchive::GetModelTable()
{
return &modelTable;
}
trpgLightTable *trpgwArchive::GetLightTable()
{
return &lightTable;
}
trpgRangeTable *trpgwArchive::GetRangeTable()
{
return &rangeTable;
}
trpgTextStyleTable *trpgwArchive::GetTextStyleTable()
{
return &textStyleTable;
}
trpgSupportStyleTable *trpgwArchive::GetSupportStyleTable()
{
return &supportStyleTable;
}
trpgLabelPropertyTable *trpgwArchive::GetLabelPropertyTable()
{
return &labelPropertyTable;
}
// OpenFile
// Same as above, only gets a basename as well
bool trpgwArchive::OpenFile(const char *in_dir,const char *name)
{
char filename[1024];
strncpy(dir,in_dir,1023);
sprintf(filename,"%s" PATHSEPERATOR "%s",dir,name);
if (!(fp = osgDB::fopen(filename,"wb")))
return false;
return true;
}
// CloseFile
// Close the open file
void trpgwArchive::CloseFile()
{
if (fp)
fclose(fp);
fp = NULL;
}
/* Write Header
Flush out the header (checkpoint) and return.
*/
bool trpgwArchive::WriteHeader()
{
bool ret = CheckpointHeader();
if (tileFile) {
delete tileFile;
tileFile=NULL;
}
return ret;
}
/* CheckpointHeader
The header lives in its own file, so we can write it at any point we
have a valid archive.
This includes all the tables, as well as basic header info.
*/
bool trpgwArchive::CheckpointHeader()
{
trpgMemWriteBuffer buf(ness);
if (!isValid())
return false;
if (!header.isValid())
{
if(header.getErrMess())
strcpy(errMess, header.getErrMess());
return false;
}
// This will close the appendable files
if (tileFile) {
tileFile->Flush();
}
/* Build a Tile Table
We need to build one from scratch here. However,
we have all the relevant information collected during
the WriteTile calls.
*/
if(header.GetIsLocal()) {
int row = 0;
int col = 0;
header.GetBlocks(row,col);
tileTable.SetCurrentBlock(row,col,true);
}
if (tileMode == TileExternal) {
// External tiles are easy
tileTable.SetMode(trpgTileTable::External);
} else if( tileMode == TileExternalSaved) {
if(!isRegenerate && firstHeaderWrite)
{
// Set up the sizes
tileTable.SetMode(trpgTileTable::ExternalSaved);
tileTable.SetNumLod(1);
trpg2iPoint lodSize;
header.GetLodSize(0,lodSize);
tileTable.SetNumTiles(lodSize.x, lodSize.y, 0);
firstHeaderWrite = false;
}
// Now set the individual tile locations
for (unsigned int i=0;i<externalTiles.size();i++) {
TileFileEntry &te = externalTiles[i];
trpgwAppAddress addr;
addr.file = -1;
addr.offset = -1;
tileTable.SetTile(te.x,te.y,te.lod,addr,te.zmin,te.zmax);
}
externalTiles.clear();
} else {
if (!isRegenerate && firstHeaderWrite) {
// Local tiles require more work
tileTable.SetMode(trpgTileTable::Local);
if(majorVersion == 2 && minorVersion >= 1)
{
// Version 2.1, we store only lod 0 in the tile table
// Set up the sizes
tileTable.SetNumLod(1);
trpg2iPoint lodSize;
header.GetLodSize(0,lodSize);
tileTable.SetNumTiles(lodSize.x, lodSize.y, 0);
}
else
{
// Set up the sizes
int32 numLods;
header.GetNumLods(numLods);
tileTable.SetNumLod(numLods);
for (int i=0;i<numLods;i++) {
trpg2iPoint lodSize;
header.GetLodSize(i,lodSize);
tileTable.SetNumTiles(lodSize.x,lodSize.y,i);
}
}
firstHeaderWrite = false;
}
// Now set the individual tile locations
// Nothing special need to be done with version 2.1 since
// only tile with lod 0 will be found in the tileFiles container
for (unsigned int i=0;i<tileFiles.size();i++) {
TileFile &tf = tileFiles[i];
for (unsigned int j=0;j<tf.tiles.size();j++) {
TileFileEntry &te = tf.tiles[j];
trpgwAppAddress addr;
addr.file = tf.id;
addr.offset = te.offset;
tileTable.SetTile(te.x,te.y,te.lod,addr,te.zmin,te.zmax);
}
tf.tiles.clear();
}
}
// Write all the headers into a buffer
if (!header.Write(buf))
return false;
// Do the mat table and texture table
// These can be different depending on the version
switch (majorVersion) {
case 1:
{
trpgMatTable1_0 matTable1_0(matTable);
trpgTexTable1_0 texTable1_0(texTable);
trpgTileTable1_0 tileTable1_0(tileTable);
if (!matTable1_0.Write(buf) ||
!texTable1_0.Write(buf) ||
!modelTable.Write(buf) ||
!tileTable1_0.Write(buf) ||
!lightTable.Write(buf) ||
!rangeTable.Write(buf))
return false;
}
break;
case 2:
if(!header.GetIsMaster()||texTable.isValid()) {
if(!texTable.Write(buf))
{
strcpy(errMess, "Error writing texture table");
if(texTable.getErrMess())
{
strcat(errMess, ": ");
strcat(errMess, texTable.getErrMess());
return false;
}
}
}
//These tables will not be populated if this is the master table.
if(!header.GetIsMaster()) {
if (!matTable.Write(buf))
{
strcpy(errMess, "Error writing material table");
if(matTable.getErrMess())
{
strcat(errMess, ": ");
strcat(errMess, matTable.getErrMess());
return false;
}
}
if(!modelTable.Write(buf) )
{
strcpy(errMess, "Error writing model table");
if(modelTable.getErrMess())
{
strcat(errMess, ": ");
strcat(errMess, modelTable.getErrMess());
return false;
}
}
}
//always write the tile table, even if we are a master
if(!tileTable.Write(buf))
{
strcpy(errMess, "Error writing tile table");
if(tileTable.getErrMess())
{
strcat(errMess, ": ");
strcat(errMess, tileTable.getErrMess());
return false;
}
}
if(!header.GetIsMaster()) {
if(!lightTable.Write(buf))
{
strcpy(errMess, "Error writing light table");
if(lightTable.getErrMess())
{
strcat(errMess, ": ");
strcat(errMess, lightTable.getErrMess());
return false;
}
}
if(!rangeTable.Write(buf))
{
strcpy(errMess, "Error writing range table");
if(rangeTable.getErrMess())
{
strcat(errMess, ": ");
strcat(errMess, rangeTable.getErrMess());
return false;
}
}
if (!textStyleTable.Write(buf))
{
strcpy(errMess,"Error writing text style table");
if (textStyleTable.getErrMess())
{
strcat(errMess, ": ");
strcat(errMess, textStyleTable.getErrMess());
return false;
}
}
if (!supportStyleTable.Write(buf))
{
strcpy(errMess,"Error writing support style table");
if (supportStyleTable.getErrMess())
{
strcat(errMess, ": ");
strcat(errMess, supportStyleTable.getErrMess());
return false;
}
}
if (!labelPropertyTable.Write(buf))
{
strcpy(errMess,"Error writing label property table");
if (labelPropertyTable.getErrMess())
{
strcat(errMess, ": ");
strcat(errMess, labelPropertyTable.getErrMess());
return false;
}
}
}
break;
}
// Write the disk header
int32 magic = GetMagicNumber();
if (ness != cpuNess)
magic = trpg_byteswap_int(magic);
if (fwrite(&magic,sizeof(int32),1,fp) != 1)
{
strcpy(errMess, "Could not write the magic number");
return false;
}
// Write the header length
int32 headerSize = buf.length();
int headLen = headerSize;
if (ness != cpuNess)
headerSize = trpg_byteswap_int(headerSize);
if (fwrite(&headerSize,1,sizeof(int32),fp) != sizeof(int32))
{
strcpy(errMess, "Could not write the header size");
return false;
}
// Write the buffer
const char *data = buf.getData();
if (WriteHeaderData(data,headLen,fp) != headLen)
{
strcpy(errMess, "Could not write the buffer");
return false;
}
// Note: Not sure what this is
char space[40];
if (fwrite(space,1,4,fp) != 4)
return false;
// Flush output
fflush(fp);
// Head back to the start of the file
rewind(fp);
return true;
}
/* Get a new appendable file.
*/
trpgwAppFile *trpgwArchive::GetNewWAppFile(trpgEndian inNess,const char *fileName,bool reuse)
{
return new trpgwAppFile(inNess,fileName,reuse);
}
/* Get a new write image helper
*/
trpgwImageHelper *trpgwArchive::GetNewWImageHelper(trpgEndian endian,char *imageDir,trpgTexTable &inTexTable)
{
bool separateGeo = false;
int majorVer,minorVer;
GetHeader()->GetVersion(majorVer,minorVer);
if((majorVer >= TRPG_NOMERGE_VERSION_MAJOR) && (minorVer>=TRPG_NOMERGE_VERSION_MINOR)) {
separateGeo = true;
}
return new trpgwImageHelper(endian,imageDir,inTexTable,separateGeo);
}
/* Increment Tile File.
Close the current tile file (if any) and open the next one.
Also update the records we're keeping of which tiles went in
which files.
*/
bool trpgwArchive::IncrementTileFile()
{
if (tileMode != TileLocal)
return false;
// Closes the current tile file
if (tileFile) {
delete tileFile;
tileFile=NULL;
}
// Open the next one
char filename[1024];
sprintf(filename,"%s" PATHSEPERATOR "tileFile_%d.tpf",dir,tileFileCount++);
tileFile = GetNewWAppFile(ness,filename,true);
if (!tileFile->isValid())
return false;
// Add another TileFiles entry
tileFiles.resize(tileFiles.size()+1);
tileFiles[tileFiles.size()-1].id = tileFiles.size()-1;
return true;
}
/* Designate Tile File
Close the current tile file (if any) and open one with the
given base name. This is used for regenerate.
*/
bool trpgwArchive::DesignateTileFile(int id)
{
if (tileMode != TileLocal)
return false;
// Close the current tile file
if (tileFile) {
delete tileFile;
tileFile=NULL;
}
// Open a named on
char filename[1024];
sprintf(filename,"%s" PATHSEPERATOR "tileFile_%d.tpf",dir,id);
tileFile = GetNewWAppFile(ness,filename);
if (!tileFile->isValid())
return false;
// Add another TileFiles entry
tileFiles.resize(tileFiles.size()+1);
tileFiles[tileFiles.size()-1].id = id;
return true;
}
/* WriteTile.
Write the given tile (x,y,lod) in the appropriate mode (Local or External).
The tile header is given separately from the rest of the tile, but they are
appended together to the file.
*/
bool trpgwArchive::WriteTile(unsigned int x,unsigned int y,unsigned int lod, float zmin, float zmax,
const trpgMemWriteBuffer *head,const trpgMemWriteBuffer *buf, int32& fileId, int32& fileOffset)
{
FILE *tfp=NULL;
if (!isValid())
return false;
fileId = -1;
fileOffset = -1;
// External tiles get their own individual files
if (tileMode == TileExternal || tileMode == TileExternalSaved) {
// Make a new filename
char filename[1024];
// Note: Windows specific
sprintf(filename,"%s" PATHSEPERATOR "tile_%d_%d_%d.tpt",dir,x,y,lod);
if (!(tfp = osgDB::fopen(filename,"wb")))
return false;
// Write the header first
unsigned int len;
const char *data;
if (head) {
data = head->getData();
len = head->length();
if (fwrite(data,sizeof(char),len,tfp) != len) {
fclose(tfp);
return false;
}
}
// Write the buffer out
data = buf->getData();
len = buf->length();
if (fwrite(data,sizeof(char),len,tfp) != len) {
fclose(tfp);
return false;
}
fclose(tfp);
// In version 2.1 and over we still add an entry to the tile table
// to save the zmin and zmax value.
if(tileMode == TileExternalSaved && lod == 0)
{
externalTiles.push_back(TileFileEntry());
TileFileEntry& tf = externalTiles.back();
tf.x = x;
tf.y = y;
tf.lod = lod;
tf.offset = -1;
tf.zmax = zmax;
tf.zmin = zmin;
}
} else {
// Local tiles get appended to a tile file
if (!tileFile) {
if (!IncrementTileFile())
return false;
}
// See if we've exceeded the maximum advised size for a tile file
while (maxTileFileLen > 0 && tileFile->GetLengthWritten() > maxTileFileLen) {
if (!IncrementTileFile())
return false;
}
int32 pos = static_cast<int32>(tileFile->Pos());
if (!tileFile->Append(head,buf))
return false;
// Keep track of the fact that this went here
TileFile &tf = tileFiles[tileFiles.size()-1];
TileFileEntry te;
te.x = x; te.y = y; te.lod = lod;
te.zmin = zmin; te.zmax = zmax; te.offset = pos;
if(majorVersion == 2 && minorVersion >=1)
{
// Version 2.1, we need to keep track of lod 0 only
if(lod == 0)
tf.tiles.push_back(te);
}
else
tf.tiles.push_back(te);
fileOffset = pos;
fileId = tileFiles[tileFiles.size()-1].id;
}
return true;
}
/* ****************
Geometry Stats
Used by the Geometry Helper
****************
*/
trpgwGeomStats::trpgwGeomStats()
{
totalTri = totalStripTri = totalFanTri = totalBagTri = 0;
for (int i=0;i<15;i++) {
stripStat[i] = fanStat[i] = 0;
}
stripGeom = fanGeom = bagGeom = 0;
stateChanges = 0;
numStrip = numFan = 0;
totalQuad = 0;
}
trpgwGeomStats::~trpgwGeomStats()
{
}
/* ****************
Geometry Helper
Here, since it's used with a write archive.
****************
*/
trpgwGeomHelper::trpgwGeomHelper()
{
buf = NULL;
mode = trpgGeometry::Triangles;
}
trpgwGeomHelper::~trpgwGeomHelper()
{
}
void trpgwGeomHelper::SetMode(int m)
{
if (m == trpgGeometry::Triangles || m == trpgGeometry::Quads)
mode = m;
}
trpgwGeomHelper::trpgwGeomHelper(trpgWriteBuffer *ibuf, int dtype)
{
init(ibuf,dtype);
}
void trpgwGeomHelper::init(trpgWriteBuffer *ibuf,int dtype)
{
buf = ibuf;
dataType = dtype;
zmin = 1e12;
zmax = -1e12;
}
// Reset back to a clean state (except for the buffer)
void trpgwGeomHelper::Reset()
{
ResetTri();
ResetPolygon();
zmin = 1e12;
zmax = -1e12;
}
// Reset triangle arrays (usually after a flush)
void trpgwGeomHelper::ResetTri()
{
strips.Reset();
fans.Reset();
bags.Reset();
tex.resize(0);
norm.resize(0);
vert.resize(0);
}
// Start a polygon definition
void trpgwGeomHelper::StartPolygon()
{
ResetPolygon();
}
// Finish a polygon definition
void trpgwGeomHelper::EndPolygon()
{
// See if we can add it to the current triangle arrays
if (vert.size() && (matTri != matPoly)) {
// Couldn't flush geometry and move on
FlushGeom();
}
// Turn the polygon into triangles
// Note: Only dealing with convex here
matTri = matPoly;
unsigned int numMats=matTri.size();
switch (mode) {
case trpgGeometry::Triangles:
{
int num = polyVert.size() - 2;
int id1,id2;
for (int i=0;i<num;i++) {
// Note: Handle color
/* Swap 1 and 2 positions
This lets the Optimizer pick up on triangle fans
If you're not using our optimizer this will do very weird things
Probably it will screw up backface culling.
*/
// Note: turned this off because it was broken. Put it back
#if 0
id1 = i+1;
id2 = i+2;
if (num > 1) {
id1 = i+2; id2 = i+1;
}
#else
id1 = i+1;
id2 = i+2;
#endif
// Define the triangle
vert.push_back(polyVert[0]);
vert.push_back(polyVert[id1]);
vert.push_back(polyVert[id2]);
norm.push_back(polyNorm[0]);
norm.push_back(polyNorm[id1]);
norm.push_back(polyNorm[id2]);
// multiple textures
unsigned int loop;
for (loop=0;loop<numMats;loop++) tex.push_back(polyTex[loop]);
for (loop=0;loop<numMats;loop++) tex.push_back(polyTex[numMats*id1+loop]);
for (loop=0;loop<numMats;loop++) tex.push_back(polyTex[numMats*id2+loop]);
}
}
break;
case trpgGeometry::Quads:
{
int num = polyVert.size();
if (polyVert.size() == 4) {
for (int i=0;i<num;i++) {
vert.push_back(polyVert[i]);
norm.push_back(polyNorm[i]);
// multiple textures
for (unsigned int loop=0;loop<numMats;loop++) tex.push_back(polyTex[numMats*i+loop]);
}
}
}
break;
}
ResetPolygon();
}
// Clean out the polygon arrays
void trpgwGeomHelper::ResetPolygon()
{
tmpTex.resize(0);
matPoly.resize(0);
polyTex.resize(0);
polyNorm.resize(0);
polyVert.resize(0);
}
// Set the current color
// Note: Required
void trpgwGeomHelper::SetColor(trpgColor& /*col*/)
{
// tmpColor = col;
}
// Set the current texture coord
// Note: Required
void trpgwGeomHelper::SetTexCoord(trpg2dPoint &pt)
{
tmpTex.resize(0);
tmpTex.push_back(pt);
}
void trpgwGeomHelper::AddTexCoord(trpg2dPoint &pt)
{
tmpTex.push_back(pt);
}
// Set the current normal
// Note: required
void trpgwGeomHelper::SetNormal(trpg3dPoint &pt)
{
tmpNorm = pt;
}
// Set the current material
// Note: required
void trpgwGeomHelper::SetMaterial(int32 imat)
{
matPoly.resize(0);
matPoly.push_back(imat);
}
void trpgwGeomHelper::AddMaterial(int32 imat)
{
matPoly.push_back(imat);
}
// Get the Z min/max we've collected so far
void trpgwGeomHelper::GetZMinMax(double &outZmin,double &outZmax)
{
outZmin = zmin;
outZmax = zmax;
}
// Collect the current vertex data and add a new whole vertex
// Note: Deal with color
void trpgwGeomHelper::AddVertex(trpg3dPoint &pt)
{
polyTex.insert(polyTex.end(),tmpTex.begin(),tmpTex.end());
polyNorm.push_back(tmpNorm);
// Note: Turn this back on. It's not right currently, though
#if 0
if (buf->GetEndian() != trpg_cpu_byte_order())
{
trpg3dPoint tmpVert;
tmpVert.x = trpg_byteswap_8bytes_to_double ((char *)&pt.x);
tmpVert.y = trpg_byteswap_8bytes_to_double ((char *)&pt.y);
tmpVert.z = trpg_byteswap_8bytes_to_double ((char *)&pt.z);
polyVert.push_back(tmpVert);
}
else
#endif
polyVert.push_back(pt);
// Update min/max
zmin = MIN(pt.z,zmin);
zmax = MAX(pt.z,zmax);
}
// Flush the current set of geometry and move on
void trpgwGeomHelper::FlushGeom()
{
bool hadGeom = false;
switch (mode) {
case trpgGeometry::Triangles:
{
Optimize();
// Write only if we've got something
int numPrim;
if (strips.GetNumPrims(numPrim) && numPrim) {
strips.Write(*buf);
stats.stripGeom++;
hadGeom = true;
}
if (fans.GetNumPrims(numPrim) && numPrim) {
fans.Write(*buf);
stats.fanGeom++;
hadGeom = true;
}
if (bags.GetNumPrims(numPrim) && numPrim) {
bags.Write(*buf);
stats.bagGeom++;
hadGeom = true;
}
}
break;
case trpgGeometry::Quads:
{
unsigned int numVert = vert.size();
unsigned int numMat = matTri.size();
unsigned int loop;
// Make sure we've got quads
if (numVert % 4 == 0) {
int dtype = (dataType == UseDouble ? trpgGeometry::DoubleData : trpgGeometry::FloatData);
// Just dump the quads into single geometry node
trpgGeometry quads;
quads.SetPrimType(trpgGeometry::Quads);
for (loop=0;loop<numMat;loop++) quads.AddTexCoords(trpgGeometry::PerVertex);
for (unsigned int i=0;i<numVert;i++) {
quads.AddVertex((trpgGeometry::DataType)dtype,vert[i]);
quads.AddNormal((trpgGeometry::DataType)dtype,norm[i]);
for (loop=0;loop<numMat;loop++) quads.AddTexCoord((trpgGeometry::DataType)dtype,tex[i*numMat+loop],loop);
}
quads.SetNumPrims(numVert/4);
for (loop=0;loop<numMat;loop++) quads.AddMaterial(matTri[loop]);
quads.Write(*buf);
stats.totalQuad++;
hadGeom = true;
}
}
break;
}
if (hadGeom)
stats.stateChanges++;
ResetTri();
}
/* Optimize
Form triangle strips and fans and dump the rest into a "bag"
of triangles.
This works for TERREX, but won't do a whole lot for anyone else.
It will produce valid output, but not particularly optimized.
*/
#define ADDVERT(dest,vt) { \
dest.AddVertex((trpgGeometry::DataType)dtype,vt.v); \
dest.AddNormal((trpgGeometry::DataType)dtype,vt.n); \
dest.AddTexCoord((trpgGeometry::DataType)dtype,vt.tex); \
}
class optVert {
public:
optVert() { valid = false; }
optVert(trpg3dPoint &iv,trpg3dPoint &in,trpg2dPoint &itex) { v = iv; n = in; tex.resize(0); tex.push_back(itex); valid = true;}
optVert(trpg3dPoint &iv,trpg3dPoint &in,std::vector<trpg2dPoint> &itex) { v = iv; n = in; tex=itex; valid = true;}
optVert(int numMat, int vid, std::vector<trpg3dPoint> &iv, std::vector<trpg3dPoint> &in, std::vector<trpg2dPoint> &itex);
trpg3dPoint v;
trpg3dPoint n;
std::vector<trpg2dPoint> tex;
bool valid;
int operator == (const optVert &in) const { return (v == in.v && n == in.n && tex == in.tex); }
};
optVert::optVert(int numMat, int vid, std::vector<trpg3dPoint> &iv, std::vector<trpg3dPoint> &in, std::vector<trpg2dPoint> &itex)
{
v=iv[vid];
n=in[vid];
tex.resize(0);
for (unsigned int loop=0; loop < (unsigned int)numMat; loop++) tex.push_back(itex[vid*numMat+loop]);
}
void trpgwGeomHelper::Optimize()
{
int dtype = (dataType == UseDouble ? trpgGeometry::DoubleData : trpgGeometry::FloatData);
// Potentially writing to all of these
strips.SetPrimType(trpgGeometry::TriStrips);
fans.SetPrimType(trpgGeometry::TriFans);
bags.SetPrimType(trpgGeometry::Triangles);
unsigned int numMat = matTri.size();
for (unsigned int loop =0; loop < numMat; loop++ ) {
strips.AddMaterial(matTri[loop]);
strips.AddTexCoords(trpgGeometry::PerVertex);
fans.AddMaterial(matTri[loop]);
fans.AddTexCoords(trpgGeometry::PerVertex);
bags.AddMaterial(matTri[loop]);
bags.AddTexCoords(trpgGeometry::PerVertex);
}
int numTri = vert.size()/3;
if (numTri == 0)
return;
// Iterate through the triangles
enum {Strip,Fan,Bag};
int type,triId;
optVert a[3],b[3],c[3];
for (triId = 0; triId<numTri; ) {
// Triangle A
int vid = 3*triId;
a[0] = optVert(numMat,vid,vert,norm,tex);
a[1] = optVert(numMat,vid+1,vert,norm,tex);
a[2] = optVert(numMat,vid+2,vert,norm,tex);
// If we've got two or more triangles to go, try to form something
if (triId + 1 <numTri) {
// Triangle B
b[0] = optVert(numMat,vid+3,vert,norm,tex);
b[1] = optVert(numMat,vid+4,vert,norm,tex);
b[2] = optVert(numMat,vid+5,vert,norm,tex);
// Is it a triangle strip?
if (a[1] == b[1] && a[2] == b[0])
type = Strip;
else {
// Might be a Fan
if (a[0] == b[0] && a[1] == b[2])
type = Fan;
else
type = Bag;
}
} else
type = Bag;
switch (type) {
case Bag:
ADDVERT(bags,a[0]);
ADDVERT(bags,a[1]);
ADDVERT(bags,a[2]);
bags.AddPrim();
triId++;
stats.AddBagStat(1);
break;
case Strip:
{
bool isStrip=true, flip=true;
int primLen = 0;
// Dump A into the strip
ADDVERT(strips,a[0]);
ADDVERT(strips,a[1]);
ADDVERT(strips,a[2]);
triId++;
primLen = 3;
do {
// Already checked that B was good on last go-round
ADDVERT(strips,b[2]); primLen++;
triId++;
vid = 3*triId;
if (triId < numTri) {
// B is the new primary, check it against the next
c[0] = optVert(numMat,vid,vert,norm,tex);
c[1] = optVert(numMat,vid+1,vert,norm,tex);
c[2] = optVert(numMat,vid+2,vert,norm,tex);
if (flip)
isStrip = (c[0] == b[0] && c[1] == b[2]);
else
isStrip = (c[0] == b[2] && c[1] == b[1]);
b[0] = c[0]; b[1] = c[1]; b[2] = c[2];
}
flip = !flip;
} while (triId < numTri && isStrip);
strips.AddPrimLength(primLen);
stats.AddStripStat(primLen);
}
break;
case Fan:
{
bool isFan = true;
int primLen = 0;
// Dump A into the Fan
ADDVERT(fans,a[0]);
ADDVERT(fans,a[2]);
ADDVERT(fans,a[1]);
triId++;
primLen = 3;
do {
// Already know that B is good, add that
ADDVERT(fans,b[1]); primLen++;
triId++;
vid = 3*triId;
if (triId < numTri) {
// B is the new primary, check it against the next
c[0] = optVert(numMat,vid,vert,norm,tex);
c[1] = optVert(numMat,vid+1,vert,norm,tex);
c[2] = optVert(numMat,vid+2,vert,norm,tex);
isFan = (c[0] == b[0] && c[2] == b[1]);
b[0] = c[0]; b[1] = c[1]; b[2] = c[2];
}
} while (triId < numTri && isFan);
fans.AddPrimLength(primLen);
stats.AddFanStat(primLen);
}
break;
}
}
}
/* *************************
Image Write Helper class
*************************
*/
trpgwImageHelper::trpgwImageHelper(trpgEndian inNess,char *inDir,trpgTexTable &inTable,bool sepGeoTypical)
{
Init(inNess,inDir,inTable,sepGeoTypical);
}
void trpgwImageHelper::Init(trpgEndian inNess,char *inDir,trpgTexTable &inTable,bool sepGeoTypical)
{
ness = inNess;
strcpy(dir,inDir);
texTable = &inTable;
texFile = NULL;
geotypFile = NULL;
separateGeoTypical = sepGeoTypical;
maxTexFileLen = -1;
}
trpgwImageHelper::~trpgwImageHelper()
{
if (texFile)
delete texFile;
if (geotypFile)
delete geotypFile;
}
bool trpgwImageHelper::AddExternal(char *name,int &texID,bool lookForExisting)
{
trpgTexture tex;
tex.SetImageMode(trpgTexture::External);
tex.SetName(name);
if (lookForExisting)
texID = texTable->FindAddTexture(tex);
else
texID = texTable->AddTexture(tex);
return (texID != -1);
}
void trpgwImageHelper::SetMaxTexFileLength(int len)
{
maxTexFileLen = len;
}
bool trpgwImageHelper::AddLocal(char *name,trpgTexture::ImageType type,int sizeX,int sizeY,
bool isMipmap,char *data,int &texID,bool deferWrite)
{
// Set up the basic texture
trpgTexture tex;
if(texID!=-1)
tex.SetHandle(texID);
tex.SetName(name);
tex.SetImageMode(trpgTexture::Local);
tex.SetImageType(type);
int depth;
tex.GetImageDepth(depth);
tex.SetNumLayer(depth);
tex.SetImageSize(trpg2iPoint(sizeX,sizeY));
tex.SetIsMipmap(isMipmap);
// Write the image out to disk
trpgwAppAddress addr;
if(!deferWrite)
if (!WriteToArchive(tex,data,addr,true))
return false;
// Now add the specifics to the texture table
tex.SetImageAddr(addr);
texID = texTable->AddTexture(tex);
return true;
}
bool trpgwImageHelper::ReplaceLocal(char *data,int &texID)
{
const trpgTexture *texRef=texTable->GetTextureRef(texID);
if (!texRef) return false;
// Write the image out to disk
trpgwAppAddress addr;
if (!WriteToArchive(*texRef,data,addr,true))
return false;
// Now add the specifics to the texture table
const_cast<trpgTexture *>(texRef)->SetImageAddr(addr);
return true;
}
bool trpgwImageHelper::AddTileLocal(char *name,trpgTexture::ImageType type, int sizeX, int sizeY,
bool isMipmap,char *data,int &texID,trpgwAppAddress &addr)
{
// Set up the texture template and add to the table
trpgTexture tex;
if(texID!=-1)
tex.SetHandle(texID);
tex.SetName(name);
tex.SetImageMode(trpgTexture::Template);
tex.SetImageType(type);
int depth;
tex.GetImageDepth(depth);
tex.SetNumLayer(depth);
tex.SetImageSize(trpg2iPoint(sizeX,sizeY));
tex.SetIsMipmap(isMipmap);
texID = texTable->FindAddTexture(tex);
// Write the specific data out to an archive (return the address)
if (!WriteToArchive(tex,data,addr,false))
return false;
return true;
}
/* Get new appendable file.
*/
trpgwAppFile* trpgwImageHelper::GetNewWAppFile(trpgEndian inNess,const char *fileName,bool reuse) {
return new trpgwAppFile(inNess,fileName,reuse);
}
/* Increment Texture File.
Close the current texture file (if any) and open the next one.
*/
trpgwAppFile * trpgwImageHelper::IncrementTextureFile(bool geotyp)
{
char filename[1024];
trpgwAppFile *thefile = texFile;
if(geotyp && separateGeoTypical) {
thefile = geotypFile;
sprintf(filename,"%s" PATHSEPERATOR "geotypFile_%d.txf",dir,static_cast<int>(geotypFileIDs.size()));
}
else {
sprintf(filename,"%s" PATHSEPERATOR "texFile_%d.txf",dir,static_cast<int>(texFileIDs.size()));
}
// Closes the current texture file
if (thefile) delete thefile;
thefile = NULL;
// Open the next one
thefile = GetNewWAppFile(ness,filename,true);
if (!thefile->isValid())
return NULL;
if(geotyp && separateGeoTypical) {
geotypFileIDs.push_back(geotypFileIDs.size());
geotypFile = thefile;
}
else {
texFileIDs.push_back(texFileIDs.size());
texFile = thefile;
}
return thefile;
}
// Flush current texture file (if any)
bool trpgwImageHelper::Flush()
{
if (texFile)
texFile->Flush();
if (geotypFile)
geotypFile->Flush();
return true;
}
/* Designate Texture File
Close the curren texture file (if any) and open one with the given
base name.
*/
bool trpgwImageHelper::DesignateTextureFile(int id)
{
// Close the current texture file
if (texFile) delete texFile;
texFile = NULL;
// Open one with the given base name
char filename[1024];
sprintf(filename,"%s" PATHSEPERATOR "texFile_%d.txf",dir,id);
texFile = GetNewWAppFile(ness,filename);
if (!texFile->isValid())
return false;
texFileIDs.push_back(id);
sprintf(filename,"%s" PATHSEPERATOR "geotypFile_%d.txf",dir,id);
geotypFile = GetNewWAppFile(ness,filename);
if (!geotypFile->isValid())
return false;
geotypFileIDs.push_back(id);
return true;
}
/* Write To Archive.
Write the given image data out to an appropriate archive and
return the address. This is used for Local and Tile Local textures.
*/
bool trpgwImageHelper::WriteToArchive(const trpgTexture &tex,char *data,trpgwAppAddress &addr,bool geotyp)
{
trpg2iPoint size;
tex.GetImageSize(size);
int32 depth;
tex.GetImageDepth(depth);
trpgwAppFile *thefile = texFile;
if(geotyp && separateGeoTypical) {
thefile = geotypFile;
}
// Get a usable texture archive file
if (!thefile) {
if (! (thefile=IncrementTextureFile(geotyp && separateGeoTypical)))
return false;
}
while (maxTexFileLen > 0 && thefile->GetLengthWritten() > maxTexFileLen) {
if (!(thefile=IncrementTextureFile(geotyp && separateGeoTypical)))
return false;
}
// Get the current address
if(geotyp && separateGeoTypical) {
addr.file = geotypFileIDs[geotypFileIDs.size()-1];
}
else {
addr.file = texFileIDs[texFileIDs.size()-1];
}
addr.offset = (int32)thefile->Pos();
// Write the data out to the archive.
int totSize = tex.CalcTotalSize();
if (!thefile->Append(data,totSize))
return false;
return true;
}