#include #include #include #include #include #include #include #include #include #include #include #include "ReaderWriterTXP.h" #include "TXPNode.h" #include "TXPArchive.h" #include "TXPPagedLOD.h" #include "TXPSeamLOD.h" #include "TileMapper.h" #define ReaderWriterTXPERROR(s) osg::notify(osg::NOTICE) << "txp::ReaderWriterTXP::" << (s) << " error: " namespace { char gbuf[2048]; } using namespace txp; int ReaderWriterTXP::_archiveId = 0; osgDB::ReaderWriter::ReadResult ReaderWriterTXP::local_readNode(const std::string& file, const osgDB::ReaderWriter::Options* options) { std::string name = osgDB::getSimpleFileName(file); // We load archive.txp if (strncmp(name.c_str(),"archive",7)==0) { std::string fileName = osgDB::findDataFile( file, options ); if ( fileName.empty() ) return ReadResult::FILE_NOT_FOUND; osg::ref_ptr txpNode = new TXPNode; txpNode->setArchiveName(fileName); if (options) { txpNode->setOptions(options->getOptionString()); } if (txpNode->loadArchive()) { TXPArchive* archive = txpNode->getArchive(); if (archive) { int id = _archiveId++; archive->setId(id); // txpNode->setArchive(getArchive(id,osgDB::getFilePath(fileName))); getArchive(id,osgDB::getFilePath(fileName)); } return txpNode.get(); } else { return ReadResult::ERROR_IN_READING_FILE; } } // We load tileLOD_XxY_ID.txp else if (strncmp(name.c_str(),"tile",4)==0) { int x,y,lod; unsigned int id; sscanf(name.c_str(),"tile%d_%dx%d_%d",&lod,&x,&y,&id); TXPArchive* archive = getArchive(id,osgDB::getFilePath(file)); // The way this is done a 'tile' should only be created for lod 0 only, // something is wrong if this is no the case if(lod != 0) { ReaderWriterTXPERROR("ReaderWriterTXP::local_readNode()") << "paged 'tile' should be at lod 0" << std::endl; return ReadResult::ERROR_IN_READING_FILE; } trpgEndian endian = archive->GetEndian(); archive->ReadSubArchive( 0, 0, endian); archive->ReadSubArchive( y, x, endian); // std::cout << "Attempted " << x << " " << y << std::endl; TXPArchive::TileInfo info; if (!archive->getTileInfo(x,y,lod,info)) return ReadResult::ERROR_IN_READING_FILE; std::vector childrenLoc; osg::ref_ptr tileContent = getTileContent(info,x,y,lod,archive, childrenLoc); tileContent->setName("TileContent"); bool asChildren = false; std::string childrenInfoStr; int numLods = archive->getNumLODs(); int majorVersion, minorVersion; archive->GetVersion(majorVersion, minorVersion); if(majorVersion ==2 && minorVersion >=1) { // Version 2.1 and over // The tile table only contains lod 0 and the children // info are stored in its parent. SO if we do not want // to be forced to reparse the parent we need to save that // info. For now we just add it to the node name if(childrenLoc.size() > 0) { asChildren = true; createChildrenLocationString(childrenLoc, childrenInfoStr); } } else { if (lod < (numLods-1)) asChildren = true; } if (asChildren) { char pagedLODfile[1024]; sprintf(pagedLODfile,"%s\\subtiles%d_%dx%d_%d", archive->getDir(), lod, x, y, archive->getId()); strcat(pagedLODfile, childrenInfoStr.c_str()); strcat(pagedLODfile, ".txp"); // there are tile sets which do not maintain the z extents in // the tile table. This attempt to address the issue by using // the geometry bounding sphere. The downside is that this is // not coupled to the generation and may result in runtime cracks if (info.center.z() == 0) { osg::BoundingSphere bSphere = tileContent->getBound(); info.center.z() = bSphere.center().z(); info.radius = bSphere.radius(); } osg::ref_ptr pagedLOD = new TXPPagedLOD; // note: use maximum(info.maxRange,1e7) as just maxRange would result in some corner tiles from being culled out. pagedLOD->addChild(tileContent.get(),info.minRange,osg::maximum(info.maxRange,1e7)); pagedLOD->setFileName(1,pagedLODfile); pagedLOD->setRange(1,0,info.minRange); pagedLOD->setCenter(info.center); pagedLOD->setRadius(info.radius); pagedLOD->setPriorityOffset(0,numLods-lod); pagedLOD->setPriorityScale(0,1.0f); pagedLOD->setNumChildrenThatCannotBeExpired(1); pagedLOD->setTileId(x,y,lod); const trpgHeader* header = archive->GetHeader(); trpgHeader::trpgTileType tileType; header->GetTileOriginType(tileType); if(tileType == trpgHeader::TileLocal) { osg::Vec3d sw(info.bbox._min); pagedLOD->setCenter(info.center - sw); } return pagedLOD.get(); } else return tileContent.get(); } // For 2.0 and lower we load subtilesLOD_XxY_ID.txp // For 2.1 and over we load subtilesLOD_XxY_ID_NBCHILD_{X_Y_FID_FOFFSET_ZMIN_ZMAX_X_Y_ADDR ....}.txp else if (strncmp(name.c_str(),"sub",3)==0) { int x,y,lod; unsigned int id; sscanf(name.c_str(),"subtiles%d_%dx%d_%d",&lod,&x,&y,&id); TXPArchive* archive = getArchive(id,osgDB::getFilePath(file)); int majorVersion, minorVersion; archive->GetVersion(majorVersion, minorVersion); std::vector childrenLoc; osg::ref_ptr subtiles = new osg::Group; int numLods = archive->getNumLODs(); if(majorVersion == 2 && minorVersion >= 1) { int nbChild; sscanf(name.c_str(),"subtiles%d_%dx%d_%d_%d",&lod,&x,&y,&id, &nbChild); std::vector locs; bool status = true; status = extractChildrenLocations(name, lod, locs, nbChild); if(majorVersion >= TRPG_NOMERGE_VERSION_MAJOR && minorVersion >=TRPG_NOMERGE_VERSION_MINOR && archive->GetHeader()->GetIsMaster()) { for(int idx=0;idxGetHeader(); trpgHeader::trpgTileType tileType; header->GetTileOriginType(tileType); TXPArchive::TileLocationInfo plInfo; plInfo.x = x; plInfo.y = y; plInfo.lod = lod; TXPArchive::TileInfo parentInfo; archive->getTileInfo(plInfo,parentInfo); for(int idx = 0; idx < nbChild; ++idx) { std::vector childrenChildLoc; TXPArchive::TileLocationInfo& loc = locs[idx]; TXPArchive::TileInfo info; if (!archive->getTileInfo(loc,info)) continue; osg::ref_ptr tileContent = getTileContent(info, loc, archive, childrenChildLoc); tileContent->setName("TileContent"); if(childrenChildLoc.size() > 0) { std::string childInfoStr; createChildrenLocationString(childrenChildLoc, childInfoStr); char pagedLODfile[1024]; sprintf(pagedLODfile,"%s\\subtiles%d_%dx%d_%d%s.txp", archive->getDir(), loc.lod, loc.x, loc.y, archive->getId(), childInfoStr.c_str()); // there are tile sets which do not maintain the z extents in // the tile table. This attempt to address the issue by using // the geometry bounding sphere. The downside is that this is // not coupled to the generation and may result in runtime cracks if (info.center.z() == 0) { osg::BoundingSphere bSphere = tileContent->getBound(); info.center.z() = bSphere.center().z(); info.radius = bSphere.radius(); } osg::ref_ptr pagedLOD = new TXPPagedLOD; // note: use maximum(info.maxRange,1e7) as just maxRange would result in some corner tiles from being culled out. pagedLOD->addChild(tileContent.get(),info.minRange,osg::maximum(info.maxRange,1e7)); pagedLOD->setFileName(1,pagedLODfile); pagedLOD->setRange(1,0,info.minRange); pagedLOD->setCenter(info.center); pagedLOD->setRadius(info.radius); pagedLOD->setPriorityOffset(0,numLods - loc.lod); pagedLOD->setPriorityScale(0,1.0f); pagedLOD->setNumChildrenThatCannotBeExpired(1); pagedLOD->setTileId(loc.x, loc.y, loc.lod); if(tileType == trpgHeader::TileLocal) { osg::Vec3d center(info.center - parentInfo.bbox._min); osg::Vec3d sw(info.bbox._min - parentInfo.bbox._min); sw[2] = 0.0; pagedLOD->setCenter(center - sw); osg::Matrix offset; offset.setTrans(sw); osg::MatrixTransform *tform = new osg::MatrixTransform(offset); tform->addChild(pagedLOD.get()); subtiles->addChild(tform); } else subtiles->addChild(pagedLOD.get()); subtiles->setUserData(new TileIdentifier(loc.x, loc.y, loc.lod)); // is this really needed? } else { subtiles->setUserData(new TileIdentifier(loc.x, loc.y, loc.lod)); if(tileType == trpgHeader::TileLocal) { osg::Vec3d center(info.center - parentInfo.bbox._min); osg::Vec3d sw(info.bbox._min - parentInfo.bbox._min); sw[2] = 0.0; osg::Matrix offset; offset.setTrans(sw); osg::MatrixTransform *tform = new osg::MatrixTransform(offset); tform->addChild(tileContent.get()); subtiles->addChild(tform); } else subtiles->addChild(tileContent.get()); } } } else { int sizeX, sizeY; archive->getLODSize(lod+1,sizeX,sizeY); const trpgHeader* header = archive->GetHeader(); trpgHeader::trpgTileType tileType; header->GetTileOriginType(tileType); TXPArchive::TileInfo parentInfo; archive->getTileInfo(x,y,lod,parentInfo); for (int ix = 0; ix < 2; ix++) { for (int iy = 0; iy < 2; iy++) { int tileX = x*2+ix; int tileY = y*2+iy; int tileLOD = lod+1; TXPArchive::TileInfo info; if (!archive->getTileInfo(tileX,tileY,tileLOD,info)) continue; osg::ref_ptr tileContent = getTileContent(info,tileX,tileY,tileLOD,archive, childrenLoc); tileContent->setName("TileContent"); if (tileLOD < (numLods-1)) { char pagedLODfile[1024]; sprintf(pagedLODfile,"%s\\subtiles%d_%dx%d_%d.txp", archive->getDir(), tileLOD, tileX, tileY, archive->getId()); // there are tile sets which do not maintain the z extents in // the tile table. This attempt to address the issue by using // the geometry bounding sphere. The downside is that this is // not coupled to the generation and may result in runtime cracks if (info.center.z() == 0) { osg::BoundingSphere bSphere = tileContent->getBound(); info.center.z() = bSphere.center().z(); info.radius = bSphere.radius(); } osg::ref_ptr pagedLOD = new TXPPagedLOD; // note: use maximum(info.maxRange,1e7) as just maxRange would result in some corner tiles from being culled out. pagedLOD->addChild(tileContent.get(),info.minRange,osg::maximum(info.maxRange,1e7)); pagedLOD->setFileName(1,pagedLODfile); pagedLOD->setRange(1,0,info.minRange); pagedLOD->setCenter(info.center); pagedLOD->setRadius(info.radius); pagedLOD->setPriorityOffset(0,numLods-lod); pagedLOD->setPriorityScale(0,1.0f); pagedLOD->setNumChildrenThatCannotBeExpired(1); pagedLOD->setTileId(tileX,tileY,tileLOD); if(tileType == trpgHeader::TileLocal) { osg::Vec3d center(info.center - parentInfo.bbox._min); osg::Vec3d sw(info.bbox._min - parentInfo.bbox._min); sw[2] = 0.0; pagedLOD->setCenter(center - sw); osg::Matrix offset; offset.setTrans(sw); osg::MatrixTransform *tform = new osg::MatrixTransform(offset); tform->addChild(pagedLOD.get()); subtiles->addChild(tform); } else subtiles->addChild(pagedLOD.get()); } else { subtiles->setUserData(new TileIdentifier(tileX,tileY,tileLOD)); if(tileType == trpgHeader::TileLocal) { osg::Vec3d center(info.center - parentInfo.bbox._min); osg::Vec3d sw(info.bbox._min - parentInfo.bbox._min); sw[2] = 0.0; osg::Matrix offset; offset.setTrans(sw); osg::MatrixTransform *tform = new osg::MatrixTransform(offset); tform->addChild(tileContent.get()); subtiles->addChild(tform); } else subtiles->addChild(tileContent.get()); } } } } //osg::notify(osg::NOTICE) << "Subtiles for " << x << " " << y << " " << lod << " lodaded" << std::endl; return subtiles.get(); } return ReadResult::ERROR_IN_READING_FILE; } // If you change this then you have to change extractChildrenLocation() void ReaderWriterTXP::createChildrenLocationString(const std::vector& locs, std::string& locString) const { std::stringstream theLoc; if(locs.size() == 0) { theLoc << "_" << locs.size(); } else { theLoc << "_" << locs.size() << "_" << "{" ; for(unsigned int idx = 0; idx < locs.size(); ++idx) { const TXPArchive::TileLocationInfo& loc = locs[idx]; theLoc << loc.x << "_" << loc.y << "_" << loc.addr.file << "_" << loc.addr.offset << "_" << loc.zmin << "_" << loc.zmax; if(idx != locs.size() -1) theLoc << "_"; } } theLoc << "}" << std::ends; locString = theLoc.str(); } bool ReaderWriterTXP::extractChildrenLocations(const std::string& name, int parentLod, std::vector& locs, int nbChild) const { locs.clear(); if(nbChild == 0) return true; locs.resize(nbChild); // We look for '{', which should be the start of the list of {x,y,addr} children data // '}' should end the list. // We expect: X,Y,FID,FOFFSET,ZMIN,ZMAX std::string::size_type startOfList = name.find_last_of('{'); if(startOfList == std::string::npos) return false; std::string::size_type endOfList = name.find_last_of('}'); if(endOfList == std::string::npos) return false; // Extract the data strcpy(gbuf, name.substr(startOfList + 1, endOfList - startOfList - 1).c_str()); char *token = strtok( gbuf, "_" ); int nbTokenRead = 0; for(int idx = 0; idx < nbChild; idx++) { // X if(!token) break; locs[idx].x = atoi(token); nbTokenRead++; // Y token = strtok(0, "_"); if(!token) break; locs[idx].y = atoi(token); nbTokenRead++; // FID token = strtok(0, "_"); if(!token) break; locs[idx].addr.file = atoi(token); nbTokenRead++; // OFFSET token = strtok(0, "_"); if(!token) break; locs[idx].addr.offset = atoi(token); nbTokenRead++; // ZMIN token = strtok(0, "_"); if(!token) break; locs[idx].zmin = (float)atof(token); nbTokenRead++; // ZMAX token = strtok(0, "_"); if(!token) break; locs[idx].zmax= (float)atof(token); nbTokenRead++; locs[idx].lod = parentLod+1; token = strtok(0, "_"); } if(nbTokenRead != nbChild*6) return false; else return true; } TXPArchive *ReaderWriterTXP::getArchive(int id, const std::string& dir) { TXPArchive* archive = NULL; std::map< int,osg::ref_ptr >::iterator iter = _archives.find(id); if (iter != _archives.end()) { archive = iter->second.get(); } if (archive == NULL) { #ifdef _WIN32 const char _PATHD = '\\'; #elif defined(macintosh) const char _PATHD = ':'; #else const char _PATHD = '/'; #endif std::string archiveName = dir+_PATHD+"archive.txp"; archive = new TXPArchive; if (archive->openFile(archiveName) == false) { ReaderWriterTXPERROR("getArchive()") << "failed to load archive: \"" << archiveName << "\"" << std::endl; return NULL; } if (archive->loadMaterials() == false) { ReaderWriterTXPERROR("getArchive()") << "failed to load materials from archive: \"" << archiveName << "\"" << std::endl; return NULL; } if (archive->loadModels() == false) { ReaderWriterTXPERROR("getArchive()") << "failed to load models from archive: \"" << archiveName << "\"" << std::endl; return NULL; } if (archive->loadLightAttributes() == false) { ReaderWriterTXPERROR("getArchive()") << "failed to load light attributes from archive: \"" << archiveName << "\"" << std::endl; return NULL; } if (archive->loadTextStyles() == false) { ReaderWriterTXPERROR("getArchive()") << "failed to load text styles from archive: \"" << archiveName << "\"" << std::endl; return NULL; } archive->setId(id); _archives[id] = archive; } return archive; } class SeamFinder: public osg::NodeVisitor { public: SeamFinder(int x, int y, int lod, const TXPArchive::TileInfo& info, TXPArchive *archive ): osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN), _x(x), _y(y), _lod(lod), _info(info), _archive(archive) {} virtual void apply(osg::Group& group) { for (unsigned int i = 0; i < group.getNumChildren(); i++) { osg::Node* child = group.getChild(i); osg::Node* seam = seamReplacement(child); if (child != seam) { group.replaceChild(child,seam); } else { child->accept(*this); } } } protected: osg::Node* seamReplacement(osg::Node* node); int _x, _y, _lod; const TXPArchive::TileInfo& _info; TXPArchive *_archive; }; osg::Node* SeamFinder::seamReplacement(osg::Node* node) { osg::Group* group = node->asGroup(); if ( group == 0 ) return node; std::vector nonSeamChildren; osg::LOD* hiRes = 0; osg::LOD* loRes = 0; const trpgHeader* header = _archive->GetHeader(); trpgHeader::trpgTileType tileType; header->GetTileOriginType(tileType); for (unsigned int i = 0; i < group->getNumChildren(); i++) { osg::LOD* lod = dynamic_cast(group->getChild(i)); if (lod == 0) { nonSeamChildren.push_back(group->getChild(i)); continue; } bool nonSeamChild = true; // looks like the problem is in here - likely due to seamLOD info // not being adjusted properly in tiled databases // seam center is outside the bounding box of the tile osg::Vec3 lodCenter = lod->getCenter(); if(tileType == trpgHeader::TileLocal) { trpg2dPoint tileExtents; header->GetTileSize(0, tileExtents); osg::BoundingBox bbox; _archive->getExtents(bbox); osg::Vec3 offset(0.0, 0.0, 0.0); int divider = (0x1 << _lod); // calculate which tile model is located in tileExtents.x /= divider; tileExtents.y /= divider; offset[0] = _x*tileExtents.x;// + tileExtents.x*0.5; offset[1] = _y*tileExtents.y;// + tileExtents.y*0.5; lodCenter += offset; } if (!_info.bbox.contains(lodCenter)) { // seams have center as the neighbour tile osg::Vec3 d = _info.center - lodCenter; if (((fabs(d.x())-_info.size.x()) > 0.0001) && ((fabs(d.y())-_info.size.y()) > 0.0001)) { nonSeamChildren.push_back(lod); continue; } // low res seam has min/max ranges of lod+1 range/lod 0 range if ((fabs((float)_info.minRange-lod->getMinRange(0))<0.001)&&(fabs((float)_info.lod0Range-lod->getMaxRange(0))<0.001)) { if (loRes==0) { loRes = lod; nonSeamChild = false; } } // hi res seam has min/max ranges of 0 range/lod+1 range if ((lod->getMinRange(0)==0.0f)&&(fabs(_info.minRange-lod->getMaxRange(0))<0.001)) { if (hiRes==0) { hiRes = lod; nonSeamChild = false; } } } if (nonSeamChild) { nonSeamChildren.push_back(lod); } } if (loRes) { int dx = 0; int dy = 0; int lod = _lod; osg::Vec3 lodCenter = loRes->getCenter(); if(tileType == trpgHeader::TileLocal) { trpg2dPoint tileExtents; header->GetTileSize(0, tileExtents); osg::BoundingBox bbox; _archive->getExtents(bbox); osg::Vec3 offset(0.0, 0.0, 0.0); int divider = (0x1 << _lod); // calculate which tile model is located in tileExtents.x /= divider; tileExtents.y /= divider; offset[0] = _x*tileExtents.x;// + tileExtents.x*0.5; offset[1] = _y*tileExtents.y;// + tileExtents.y*0.5; lodCenter += offset; } osg::Vec3 delta = lodCenter-_info.center; if (fabs(delta.x())>fabs(delta.y())) { if ( delta.x() < 0.0 ) --dx; // west else dx++; // east } else { if ( delta.y() < 0.0 ) --dy; // south else ++dy; // north } TXPSeamLOD* seam = new TXPSeamLOD(_x, _y, lod, dx, dy); seam->setCenter(loRes->getCenter()); seam->addChild(loRes->getChild(0)); // low res if (hiRes) { seam->addChild(hiRes->getChild(0)); // high res } if (nonSeamChildren.empty()) { return seam; } else { osg::Group* newGroup = new osg::Group; newGroup->addChild(seam); for (unsigned int i = 0; i < nonSeamChildren.size(); i++) newGroup->addChild(nonSeamChildren[i]); return newGroup; } } return node; } osg::Node* ReaderWriterTXP::getTileContent(const TXPArchive::TileInfo &info, int x, int y, int lod, TXPArchive* archive, std::vector& childrenLoc) { if ( archive == 0 ) return false; int majorVersion, minorVersion; archive->GetVersion(majorVersion, minorVersion); double realMinRange = info.minRange; double realMaxRange = info.maxRange; double usedMaxRange = osg::maximum(info.maxRange,1e7); osg::Vec3 tileCenter; osg::Group* tileGroup = archive->getTileContent(x,y,lod,realMinRange,realMaxRange,usedMaxRange,tileCenter, childrenLoc); // if group has only one child, then simply use its child. while (tileGroup->getNumChildren()==1 && tileGroup->getChild(0)->asGroup()) { tileGroup = tileGroup->getChild(0)->asGroup(); } bool doSeam = false; if(majorVersion == 2 && minorVersion >= 1) doSeam = (childrenLoc.size() > 0); else doSeam = (lod < (archive->getNumLODs() - 1)); // Handle seams if (doSeam) { SeamFinder sfv(x,y,lod,info,archive); tileGroup->accept(sfv); } return tileGroup; } // this version only gets called if the TXP version is >= than 2.1 osg::Node* ReaderWriterTXP::getTileContent(const TXPArchive::TileInfo &info, const TXPArchive::TileLocationInfo& loc, TXPArchive* archive, std::vector& childrenLoc) { if ( archive == 0 ) return false; // int numLods = archive->getNumLODs(); double realMinRange = info.minRange; double realMaxRange = info.maxRange; double usedMaxRange = osg::maximum(info.maxRange,1e7); osg::Vec3 tileCenter; osg::Group* tileGroup = archive->getTileContent(loc,realMinRange,realMaxRange,usedMaxRange,tileCenter, childrenLoc); // if group has only one child, then simply use its child. while (tileGroup->getNumChildren()==1 && tileGroup->getChild(0)->asGroup()) { tileGroup = tileGroup->getChild(0)->asGroup(); } // Handle seams if (childrenLoc.size() > 0) { SeamFinder sfv(loc.x, loc.y, loc.lod, info, archive); tileGroup->accept(sfv); } return tileGroup; } osgDB::RegisterReaderWriterProxy g_txpReaderWriterProxy;