/* ************************ 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 #include #include /* 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 #include #include #include // Constructor trpgwArchive::trpgwArchive(trpgEndian inNess,TileMode inTileMode,int inVersion) { version = inVersion; if (version != 1 && version != 2) 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) { maxTileFileLen = -1; version = TRPG_VERSION_MAJOR; fp = NULL; strcpy(dir,inDir); cpuNess = trpg_cpu_byte_order(); tileFile = NULL; tileFileCount = 0; isRegenerate = true; errMess[0] = '\0'; // Open a Read Archive to get the rest of the info we need trpgr_Archive *inArch = new trpgr_Archive(); 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(); // Expand the coverage trpg2dPoint newSW,newNE; trpg2dPoint oldSW,oldNE; inHeader->GetExtents(oldSW,oldNE); 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 { 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; // Update to the new MBR and tile grid sizes header.SetExtents(newSW,newNE); header.GetNumLods(numLod); for (int i=0;iGetTileSize(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 const trpgTileTable *oldTiles = inArch->GetTileTable(); trpgTileTable::TileMode tileTableMode; oldTiles->GetMode(tileTableMode); tileTable.SetMode(tileTableMode); tileTable.SetNumLod(numLod); for (int lod=0;lodGetLodSize(lod,oldTileSize); for (int ix=0;ixGetTile(ix,iy,lod,addr,zmin,zmax); tileTable.SetTile(ix+addOffset.x,iy+addOffset.y,lod,addr,zmin,zmax); } } } // Continue to work in the mode the original database is in tileMode = (tileTableMode == trpgTileTable::Local) ? TileLocal : TileExternal; // That's it for the read archive delete inArch; } // Destructor trpgwArchive::~trpgwArchive() { if (fp) fclose(fp); if (tileFile) { delete tileFile; tileFile = NULL; } } // 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 = 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 (tileMode == TileExternal) { // External tiles are easy tileTable.SetMode(trpgTileTable::External); } else { if (!isRegenerate && firstHeaderWrite) { // Local tiles require more work tileTable.SetMode(trpgTileTable::Local); // Set up the sizes int32 numLod; header.GetNumLods(numLod); tileTable.SetNumLod(numLod); for (int i=0;iisValid()) 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 = new trpgwAppFile(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) { FILE *tfp=NULL; if (!isValid()) return false; // External tiles get their own individual files if (tileMode == TileExternal) { // 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 = 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); } else { // Local tiles get appended to a tile file if (!tileFile) { if (!IncrementTileFile()) return false; } else { // See if we've exceeded the maximum advised size for a tile file if (maxTileFileLen > 0 && tileFile->GetLengthWritten() > maxTileFileLen) if (!IncrementTileFile()) return false; } int32 pos = static_cast(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; tf.tiles.push_back(te); } 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 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;loopGetEndian() != 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 &itex) { v = iv; n = in; tex=itex; valid = true;} optVert(int numMat, int vid, std::vector &iv, std::vector &in, std::vector &itex); trpg3dPoint v; trpg3dPoint n; std::vector 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 &iv, std::vector &in, std::vector &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; triIdFindAddTexture(tex); else texID = texTable->AddTexture(tex); return (texID != -1); } void trpgwImageHelper::SetMaxTexFileLength(int len) { maxTexFileLen = len; } // doing this so people don't have to modify thier code, for both TXP users out there bool trpgwImageHelper::AddLocal(char *name,trpgTexture::ImageType type,int sizeX,int sizeY, bool isMipmap,char *data,int &texID) { int depth; switch (type) { case trpgTexture::trpg_INT8: depth = 1; break; case trpgTexture::trpg_INTA8: depth = 2; break; case trpgTexture::trpg_RGB8: case trpgTexture::trpg_FXT1: case trpgTexture::trpg_DXT1: case trpgTexture::trpg_DXT3: case trpgTexture::trpg_DXT5: depth = 3; break; case trpgTexture::trpg_RGBA8: depth = 4; break; default: depth = -1; break; } return AddLocal(name,type,sizeX,sizeY,depth,trpgTexture::trpg_RGBX_Neither,isMipmap,data,texID); } bool trpgwImageHelper::AddLocal(char *name,trpgTexture::ImageType type,int sizeX,int sizeY, int sizeZ, trpgTexture::ImageOrg org, bool isMipmap,char *data,int &texID) { // Set up the basic texture trpgTexture tex; tex.SetName(name); tex.SetImageMode(trpgTexture::Local); tex.SetImageType(type); tex.SetImageSize(trpg2iPoint(sizeX,sizeY)); tex.SetNumLayer(sizeZ); tex.SetImageOrganization(org); tex.SetIsMipmap(isMipmap); // Write the image out to disk trpgwAppAddress addr; if (!WriteToArchive(tex,data,addr)) return false; // Now add the specifics to the texture table tex.SetImageAddr(addr); texID = texTable->AddTexture(tex); return true; } // doing this so people don't have to modify thier code, for both TXP users out there bool trpgwImageHelper::AddTileLocal(char *name,trpgTexture::ImageType type, int sizeX, int sizeY, bool isMipmap,char *data,int &texID,trpgwAppAddress &addr) { int depth; switch (type) { case trpgTexture::trpg_INT8: depth = 1; break; case trpgTexture::trpg_INTA8: depth = 2; break; case trpgTexture::trpg_RGB8: case trpgTexture::trpg_FXT1: case trpgTexture::trpg_DXT1: case trpgTexture::trpg_DXT3: case trpgTexture::trpg_DXT5: depth = 3; break; case trpgTexture::trpg_RGBA8: depth = 4; break; default: depth = -1; break; } return AddTileLocal(name,type,sizeX,sizeY,depth,trpgTexture::trpg_RGBX_Neither,isMipmap,data,texID,addr); } bool trpgwImageHelper::AddTileLocal(char *name,trpgTexture::ImageType type, int sizeX, int sizeY, int sizeZ, trpgTexture::ImageOrg org, bool isMipmap,char *data,int &texID,trpgwAppAddress &addr) { // Set up the texture template and add to the table trpgTexture tex; tex.SetName(name); tex.SetImageMode(trpgTexture::Template); tex.SetImageType(type); tex.SetImageSize(trpg2iPoint(sizeX,sizeY)); tex.SetNumLayer(sizeZ); tex.SetImageOrganization(org); tex.SetIsMipmap(isMipmap); texID = texTable->FindAddTexture(tex); // Write the specific data out to an archive (return the address) if (!WriteToArchive(tex,data,addr)) return false; return true; } /* Increment Texture File. Close the current texture file (if any) and open the next one. */ bool trpgwImageHelper::IncrementTextureFile() { // Closes the current texture file if (texFile) delete texFile; texFile = NULL; // Open the next one char filename[1024]; sprintf(filename,"%s" PATHSEPERATOR "texFile_%d.txf",dir,texFileIDs.size()); texFile = new trpgwAppFile(ness,filename); if (!texFile->isValid()) return false; texFileIDs.push_back(texFileIDs.size()); return true; } // Flush current texture file (if any) bool trpgwImageHelper::Flush() { if (texFile) texFile->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 = new trpgwAppFile(ness,filename); if (!texFile->isValid()) return false; texFileIDs.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) { trpg2iPoint size; tex.GetImageSize(size); int32 depth; tex.GetImageDepth(depth); // Get a usable texture archive file if (!texFile) { if (!IncrementTextureFile()) return false; } else { // Deal with maximum advised size for texture archive if (maxTexFileLen > 0 && texFile->GetLengthWritten() > maxTexFileLen) { if (!IncrementTextureFile()) return false; } } // Get the current address addr.file = texFileIDs[texFileIDs.size()-1]; addr.offset = static_cast(texFile->Pos()); // Write the data out to the archive. int totSize = tex.CalcTotalSize(); if (!texFile->Append(data,totSize)) return false; return true; }