This pseudo-loader permits on-the-fly modifications of a PagedLOD database's terrain heightfield's elevation values during load or save operations by simply appending the .modifyterrain suffix to the database filename and setting some ReaderWriter Options like "MODIFYTERRAIN_ELEV_ADD". These can be specified through the command-line osgViewer like:
osgviewer -O "MODIFYTERRAIN_ELEV_ADD 5" terrain.osg.modifyterrain Currently the sample code permits adding a positive or negative constant value, scaling, enforcing a maximum or minimum clamp (ceiling/floor), replacement of a known constant value with another known constant value, as well as replacing all values below a constant with a potentially different constant. The underlying code is equipped to do non-spatially uniform terrain elevation modification (similar to osg::TDS) through the use of generalized visitors and functors offering point-elevation-evaluation callbacks. The callback provides the current spatial point location and elevation and allows user code to return a new elevation value that will replace the heightfield's existing elevation at that point. No spatial-feature-deformation library (for raising/lowering regional features like lakes, building sites, roads, ordinance craters, etc) is included at this point. My client isn't able to release this work yet, but may be able to in the future. This framework is designed so that any API capable of answering "what elevation should be in effect at this X/Y position" can be plugged in by deriving from the ModifyTerrainFunctor class, constructing a ModifyTerrainVisitor with it, and apply()ing it to a scenegraph. A CMakeLists.txt is included. ReaderWriterMODIFYTERRAIN.cpp replies on some of the PagedLOD/ProxyNode suffix enhancements I have already submitted in order to propagate the psuedoloader suffix through child PagedLODs and ProxyNodes so that deeper levels of the scenegraph get the modify treatment as they are loaded. While this code is finished and working, it could use a bit of cut & paste before being checked in as-is. Much of the code within the ReaderWriterMODIFYTERRAIN.cpp file should be (I feel, without arrogance) incorporated into the broader osg/osgTerrain architecture. The three classes/APIs: ModifyTerrainFunctor, ModifyTerrainHeightField and ModifyTerrainVisitor are applicable to in-scenegraph modification of VPB/PagedLOD terrain whether it is done through the .modifyterrain pseudoloader or through user-application-invoked code. Additionally the ModifyDatabaseSuffixVisitor is generally applicable to any application of the pseudoloader technique in a database containing PagedLOD or ProxyNode entities -- this class could be placed into any convenient library/namespace -- osgDB suggests itself to me. The code for each of these is marked in the comments with a <<<>>> token and an explanation of where it might go in the osg Core code if it were broken out of .modifyterrain. This is just the tip of what I'm working on as far as terrain tools, but it's all I can release right now. It has already been tested pretty thoroughly, and can do some great things. The pseudoloader technique means that you can apply these terrain modifications at numerous different stages: During loading of a scenegraph in osgViewer, using -O to pass the modification parameters. While building the scenegraph within OSGDEM/VPB. While converting a scenegraph using osgconv (see my recent recursive-conversion submissions). The pseudoloader also abstracts away any file-format dependency -- you can use the pseudoloader atop any format plugin that can load and save PagedLOD databases made of osgTerrain/heightfield geometry. Currently this is just .osg and .IVE. Moving the ModifyTerrainFunctor, ModifyTerrainHeightField and ModifyTerrainVisitor classes out of .modifyterrain should allow any osg program to perform on-the-fly elevation modifications to the terrain even after it is loaded. I have some additional work in progress that would facilitate selectively requesting PagedLOD nodes to reload their already-loaded-and-displayed children (presumably through the pseudoloader layer) so that runtime dynamic terrain alterations (think, explosion craters) can easily be performed on the database without affecting the pristine on-disk copy. Alternately, this could be used to load permanently-updated copies of the terrain model that had been built by VPB (patching the database to include newly-incorporated but permanent modifications such as data updated from UAVs, etc). I intend to submit as much of this as I can as time and client allows. Essentially, there's an enormous amount of terrain technology I can provide to OSG in the near future, as my client allows me to release it. I'd love to hear feedback from others about where you'd like to see this go. -- Chris 'Xenon' Hanson, omo sanza lettere Xenon AlphaPixel.com PixelSense Landsat processing now available! http://www.alphapixel.com/demos/ "There is no Truth. There is only Perception. To Perceive is to Exist." - Xen
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2009 Robert Osfield * * This application is open source and may be redistributed and/or modified * freely and without restriction, both in commericial and non commericial * applications, as long as this copyright notice is maintained. * * This application is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * */ /* file: src/osgPlugins/modifyterrain/ReaderWriterMODIFYTERRAIN.cpp * author: Chris Hanson http://xenon.arcticus.com/ [email protected] 2009-06-30 (based on ReaderWriterSCALE.cpp) * copyright: (C) 2009 Chris Hanson * license: OpenSceneGraph Public License (OSGPL) */ #include <osg/Notify> #include <osg/Matrix> #include <osg/MatrixTransform> #include <osg/ProxyNode> #include <osgDB/ReaderWriter> #include <osgDB/FileNameUtils> #include <osgDB/Registry> #include <osgDB/ReadFile> #include <osgDB/WriteFile> #include <osgTerrain/TerrainTile> #include <stdio.h> #include <sstream> #include <iomanip> #include <assert.h> #define EXTENSION_NAME "modifyterrain" /////////////////////////////////////////////////////////////////////////// /** * An OSG reader plugin for the ".modifyterrain" pseudo-loader/saver, which * modifies the elevation of heightfields in the scene graph according to * user-specified rules and options. * This pseudo-loader/saver make it possible to deform terrain during the VPB * build-and-save stage, or during the OSG load (or save) stage * * Usage: <modelfile.ext>.modifyterrain * where: * <modelfile.ext> = a model filename. * * Operations and options are passed in the osgDB::ReaderWriter::Options * Usage: * MODIFYTERRAIN_ELEV_ADD +5 Add 5 meters of elevation uniformly to all heightfield vertices * MODIFYTERRAIN_ELEV_ADD -5 Subtract 5 meters of elevation uniformly from all heightfield vertices * MODIFYTERRAIN_ELEV_SCALE 2 Multiply all heightfield vertices elevation values by 2.0 * MODIFYTERRAIN_ELEV_MAX 2000 Adjust any heightfield vertices elevation values that exceed 2000m to be 2000m * MODIFYTERRAIN_ELEV_MIN 0 Adjust any heightfield vertices elevation values that are lower than 0m to be 0m * MODIFYTERRAIN_ELEV_MIN 0:-9999 Adjust any heightfield vertices elevation values that are lower than 0m to be -9999m * MODIFYTERRAIN_ELEV_REPLACE 0:400 Adjust any heightfield vertices elevation values that are 0m to be 400m * * Operators are applied in a fixed order independent of the order they are listed in the options: * Replace, Add, Scale, Min, Max * * There is one common "Replace" variable, so the two-arg versions of Max and Min cannot be employed * simultaneously with each other, or with Find/Replace. * * example: osgviewer -O "MODIFYTERRAIN_ELEV_ADD 5" terrain.osg.modifyterrain */ /** * This functor is used to allow customization of the terrain modification process as driven by * the ModifyTerrainHeightField() and ModifyTerrainVisitor. * * This is an abstract base class and you _must_ override the pure virtual operator () to get anything done. * * <<<>>> Should go in osgTerrain namespace/library. */ class ModifyTerrainFunctor { public: /** * Connects ModifyTerrainVisitor to code that actually calculates elevation changes. * Called by ModifyTerrainVisitor::apply * This is NOT const so that you can track internal state changes and cache data in private member * variables of the ModifyTerrainFunctor-derived subclass. * This IS pure virtual -- you MUST override it in order to do anything. * * @param [in] terrainTileNode The tile currently being modified. * * @return True if any elevation samples were actually altered from their original value within this tile. * * @see ModifyTerrainVisitor */ virtual bool operator() (osgTerrain::TerrainTile &terrainTileNode) = 0; /** * Connects ModifyTerrainHeightField to code that actually calculates elevation changes. * Called by ModifyTerrainHeightField * This is NOT const so that you can track internal state changes and cache data in private member * variables of the ModifyTerrainFunctor-derived subclass. * This IS pure virtual -- you MUST override it in order to do anything. * * @param [in] inLocation Geospatial location where new elevation is requested. * @param [in] locator osgTerrain::Locator with info about how to interpret inLocation * @param [out] outElev Modified elevation from elevation calculations. Will always be written even if no change. * * @return True if successful (outElev was written) false if failed (outElev is undefined). * * @see ModifyTerrainHeightField */ virtual bool modifyTerrainElevation(const osg::Vec3d inLocation, const osg::Vec2d sampleSize, osgTerrain::Locator *locator, double &outElev) = 0; ModifyTerrainFunctor() {} virtual ~ModifyTerrainFunctor() {} }; /** * Terrain modification parameters * * Currently extracts from an osgDB::ReaderWriter::Options object. * This is specific to the modifyterrain pseudoloader. */ struct ModifyTerrainArgs { ModifyTerrainArgs(): _elevDeltaAdd(0.0), _elevScale(1.0), _elevMax(DBL_MAX), _elevMin(DBL_MIN), _elevFindReplaceEnabled(false), _elevFind(DBL_MAX), _elevReplace(DBL_MAX) { } // basic math operations double _elevDeltaAdd; double _elevScale; double _elevMax; double _elevMin; // single-value find/replace bool _elevFindReplaceEnabled; double _elevFind; double _elevReplace; /** * Extract args from an osgDB::ReaderWriter::Options * * @param [in] options osgDB::ReaderWriter::Options normally supplied by readNode or writeNode * * @return True if successful, false if failed. */ bool parseOptions(const osgDB::ReaderWriter::Options* options) { if(options) { std::istringstream iss(options->getOptionString()); std::string opt; while (iss >> opt) { if(opt=="MODIFYTERRAIN_ELEV_ADD") { iss >> _elevDeltaAdd; } // if if(opt=="MODIFYTERRAIN_ELEV_SCALE") { iss >> _elevScale; } // if if(opt=="MODIFYTERRAIN_ELEV_MAX") { std::string parseString; std::istringstream numericParseStream; iss >> parseString; // parse optional second value size_t colonPos; if((colonPos = parseString.find_first_of(':')) != parseString.npos) { // parse _elevMax value numericParseStream.str(parseString.substr(0, colonPos)); // grab up to colon numericParseStream >> _elevMax; // convert to _elevMax // parse _elevReplace value numericParseStream.str(parseString.substr(colonPos + 1, parseString.npos)); // grab all after colon numericParseStream >> _elevReplace; // convert to _elevReplace } // if else { // parse single _elevMax value numericParseStream.str(parseString); numericParseStream >> _elevMax; // convert to _elevMax } // else } // if if(opt=="MODIFYTERRAIN_ELEV_MIN") { std::string parseString; std::istringstream numericParseStream; iss >> parseString; // parse optional second value size_t colonPos; if((colonPos = parseString.find_first_of(':')) != parseString.npos) { // parse _elevMin value numericParseStream.str(parseString.substr(0, colonPos)); // grab up to colon numericParseStream >> _elevMin; // convert to _elevMin // parse _elevReplace value numericParseStream.str(parseString.substr(colonPos + 1, parseString.npos)); // grab all after colon numericParseStream >> _elevReplace; // convert to _elevReplace } // if else { // parse single _elevMin value numericParseStream.str(parseString); numericParseStream >> _elevMin; // convert to _elevMin } // else } // if if(opt=="MODIFYTERRAIN_ELEV_REPLACE") { std::string parseString; std::istringstream numericParseStream; iss >> parseString; // must have two values to proceed with find/replace size_t colonPos; if((colonPos = parseString.find_first_of(':')) != parseString.npos) { _elevFindReplaceEnabled = true; // parse _elevFind value numericParseStream.str(parseString.substr(0, colonPos)); // grab up to colon numericParseStream >> _elevFind; // convert to _elevFind // parse _elevReplace value numericParseStream.str(parseString.substr(colonPos + 1, parseString.npos)); // grab all after colon numericParseStream >> _elevReplace; // convert to _elevReplace } // if } // if } // while } // if options osg::notify(osg::DEBUG_INFO) << " elevDeltaAdd = " << _elevDeltaAdd << std::endl; osg::notify(osg::DEBUG_INFO) << " elevScale = " << _elevScale << std::endl; osg::notify(osg::DEBUG_INFO) << " elevMax = " << _elevMax << std::endl; osg::notify(osg::DEBUG_INFO) << " elevMin = " << _elevMin << std::endl; if(_elevFindReplaceEnabled) { osg::notify(osg::INFO) << " elevFindReplace enabled." << std::endl; osg::notify(osg::INFO) << " elevFind = " << _elevFind << std::endl; osg::notify(osg::INFO) << " elevReplace = " << _elevReplace << std::endl; } // if return true; } // parseOptions }; // ModifyTerrainArgs /** * Basic standalone function to calculate a new elevation given the original elevation and a * ModifyTerrainArgs object with the parameters/rules in effect. * Called by ModifyTerrainHeightField() via ModifyTerrainFunctor::modifyTerrainElevation() * This is specific to the modifyterrain pseudoloader. * * @param [in] inLocation Geospatial location where new elevation is requested. (not yet used by this implementation) * @param [in] locator osgTerrain::Locator with info about how to interpret inLocation (not yet used by this implementation) * @param [out] outElev Modified elevation from elevation calculations. Will always be written even if no change. * @param [in] modifyArgs Parameters to define the elevation modifications. * * @return True if successful (outElev was written) false if failed (outElev is undefined). * * @see ModifyTerrainHeightField() */ bool modifyTerrainElevationUsingArgs(const osg::Vec3d inLocation, const osg::Vec2d sampleSize, osgTerrain::Locator *locator, double &outElev, const ModifyTerrainArgs &modifyArgs) { outElev = inLocation.z(); // start with the original elevation // perform find/replace before all other operations if(modifyArgs._elevFindReplaceEnabled) { if(outElev == modifyArgs._elevFind) outElev = modifyArgs._elevReplace; } // if // perform add outElev += modifyArgs._elevDeltaAdd; // perform scale outElev *= modifyArgs._elevScale; // perform min clip if(outElev < modifyArgs._elevMin) { if(modifyArgs._elevReplace != DBL_MAX) { // replace with alternate value outElev = modifyArgs._elevReplace; } // if replace else { outElev = modifyArgs._elevMin; // simple min processing } // else normal min } // if min clip enabled // perform max clip if(outElev > modifyArgs._elevMax) { if(modifyArgs._elevReplace != DBL_MAX) { // replace with alternate value outElev = modifyArgs._elevReplace; } // if replace else { outElev = modifyArgs._elevMax; // simple max processing } // else normal max } // if max clip enabled return true; } // modifyTerrainElevationUsingArgs /** * Modify a Terrain's heightfield utilizing ModifyTerrainFunctor::modifyTerrainElevation() * Calls modifyTerrainElevation() to do the dirty work. * Called by ModifyTerrainVisitor::apply by way of ModifyTerrainFunctor::operator () * * <<<>>> This should be moved to osgTerrain library/namespace as it it useful for generalized * scenegraph terrain modification outside of just the .modifyterrain pseudoloader * * @param [in] terrainTileNode The osgTerrain::TerrainTile heightfield tile to modify. * @param [in] modifyFunctor The ModifyTerrainFunctor-derived functor object used to * invoke the actual elevation change calculation. * * @return Number of elevation samples actually altered from their original value within this tile. * * @see ModifyTerrainVisitor * @see ModifyTerrainFunctor * @see modifyTerrainElevation */ unsigned int ModifyTerrainHeightField(osgTerrain::TerrainTile &terrainTileNode, ModifyTerrainFunctor *modifyFunctor) { unsigned int numSamplesAltered = 0; osg::notify(osg::DEBUG_INFO) <<"ModifyTerrainHeightField"<<std::endl; if(osgTerrain::HeightFieldLayer *hfLayer = dynamic_cast<osgTerrain::HeightFieldLayer*>(terrainTileNode.getElevationLayer())) { if(hfLayer->getNumColumns() > 0 && hfLayer->getNumRows() > 0) { if(osg::HeightField *heightField = hfLayer->getHeightField()) { // Calculate X & Y coordinates of the vertices for spatially-variant elevation changes osg::notify(osg::DEBUG_INFO) << std::fixed << std::setprecision(8) <<" XInterval:"<<heightField->getXInterval()<<" YInterval:"<<heightField->getYInterval()<<std::endl; osg::notify(osg::DEBUG_INFO) << std::fixed << std::setprecision(8) <<" hfOriginX:"<<heightField->getOrigin().x()<<" hfOriginY:"<<heightField->getOrigin().y()<<std::endl; for(unsigned int yLoop = 0; yLoop < heightField->getNumRows(); yLoop++) { for(unsigned int xLoop = 0; xLoop < heightField->getNumColumns(); xLoop++) { double inElev, outElev = 0.0; // destLocation must be Vec3d (not Vec3f) to preserve geospatial precision osg::Vec3d destLocation; osg::Vec2d sampleSize(heightField->getXInterval(), heightField->getYInterval()); destLocation = heightField->getVertex(xLoop, yLoop); // calculate full local coords including origin inElev = destLocation.z(); // invoke the modifyFunctor's modifyTerrainElevation virtual method to calculate elevation // change for this individual vertex modifyFunctor->modifyTerrainElevation(destLocation, sampleSize, terrainTileNode.getLocator(), outElev); if(inElev != outElev) { numSamplesAltered++; osg::notify(osg::DEBUG_INFO) <<" IN:"<<inElev<<" OUT:"<<outElev<<std::endl; // DEBUG_INFO } // if Elev changed heightField->setHeight(xLoop, yLoop, (float)outElev); } // for xLoop } // for yLoop if(numSamplesAltered != 0) hfLayer->dirty(); } // if heightField } // if Rows and Columns } // if hfLayer osg::notify(osg::DEBUG_INFO) <<" Number of Samples Modified "<<numSamplesAltered<<std::endl; return(numSamplesAltered); } // ModifyTerrainHeightField /** * Implementation of ModifyTerrainFunctor to utilize ModifyTerrainArgs object and modifyTerrainElevationUsingArgs(). * * This should stay in ReaderWriterMODIFYTERRAIN.cpp * * @see ModifyTerrainVisitor * @see modifyTerrainElevationUsingArgs * @see ModifyTerrainArgs */ struct ModifyTerrainUsingArgsFunctor : public ModifyTerrainFunctor { ModifyTerrainUsingArgsFunctor(ModifyTerrainArgs *modifyArgs) : _modifyArgs(modifyArgs) {} /** * Connects ModifyTerrainVisitor to ModifyTerrainHeightField. * Calls ModifyTerrainHeightField() to do the dirty work. * Called by ModifyTerrainVisitor::apply * Passes 'this' (a class derived from ModifyTerrainFunctor) to callee to supply necessary parameters * * @param [in] terrainTileNode The tile currently being modified. * * @return True if any elevation samples were actually altered from their original value within this tile. * * @see ModifyTerrainVisitor * @see modifyTerrainElevationUsingArgs */ bool operator() (osgTerrain::TerrainTile &terrainTileNode) {return(ModifyTerrainHeightField(terrainTileNode, this) > 0);} /** * Connects ModifyTerrainHeightField to modifyTerrainElevationUsingArgs() that actually calculates elevation changes. * Called by ModifyTerrainHeightField * * @param [in] inLocation Geospatial location where new elevation is requested. * @param [in] locator osgTerrain::Locator with info about how to interpret inLocation * @param [out] outElev Modified elevation from elevation calculations. Will always be written even if no change. * * @return True if successful (outElev was written) false if failed (outElev is undefined). * * @see ModifyTerrainHeightField * @see modifyTerrainElevationUsingArgs */ bool modifyTerrainElevation(const osg::Vec3d inLocation, const osg::Vec2d sampleSize, osgTerrain::Locator *locator, double &outElev) {return(modifyTerrainElevationUsingArgs(inLocation, sampleSize, locator, outElev, *_modifyArgs));} ~ModifyTerrainUsingArgsFunctor() {} // nothing needed -- no dynamic members to clean up ModifyTerrainArgs *_modifyArgs; }; /** * This visitor takes a ModifyTerrainFunctor object and traverses all terrains in * a subgraph, performing the desired modifications to each. * Counts how many Terrain Tiles report they had any elevation samples altered. * Uses ModifyTerrainFunctor to do the dirty work. * Called from ReaderWriterMODIFYTERRAIN::readNode and ReaderWriterMODIFYTERRAIN::writeNode * * <<<>>> This should be moved to osgTerrain library/namespace as it it useful for generalized * terrain modification outside of .modifyterrain. * * @see ModifyTerrainFunctor * @see ReaderWriterMODIFYTERRAIN::readNode() * @see ReaderWriterMODIFYTERRAIN::writeNode() */ class ModifyTerrainVisitor : public osg::NodeVisitor { public: ModifyTerrainVisitor(ModifyTerrainFunctor &action): osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN), _numTerrainsModified(0), _action(action) { osg::notify(osg::DEBUG_INFO) <<"Running ModifyTerrainVisitor..."<<std::endl; } ~ModifyTerrainVisitor() { osg::notify(osg::DEBUG_INFO) <<" Number of Terrains Modified "<<_numTerrainsModified<<std::endl; } /** * Traverses all terrains in a subgraph, performing the desired modifications to each. * Counts how many Terrain Tiles report they had any elevation samples altered. * Uses ModifyTerrainFunctor to do the dirty work. * Called from ReaderWriterMODIFYTERRAIN::readNode and ReaderWriterMODIFYTERRAIN::writeNode * * @param [in] node The subgraph to begin traversing. * * @see ModifyTerrainFunctor * @see ReaderWriterMODIFYTERRAIN::readNode() * @see ReaderWriterMODIFYTERRAIN::writeNode() */ void apply(osg::Node& node) { osgTerrain::TerrainTile *terrainTileNode = dynamic_cast<osgTerrain::TerrainTile*>(&node); if(terrainTileNode) { if(_action.operator ()(*terrainTileNode)) { terrainTileNode->dirtyBound(); // dirty bounds in case Z value changed bound sphere terrainTileNode->setDirty(); // dirty geometry _numTerrainsModified++; } // if } // if terrainTileNode traverse(node); } // apply unsigned int _numTerrainsModified; ModifyTerrainFunctor &_action; }; // ModifyTerrainVisitor /** * This visitor traverses all Nodes in a subgraph, altering any PagedLOD/ProxyNode * nodes to incorporate the suffix in their filenames. * Counts how many PagedLOD or ProxyNodes had their suffix modified. * * <<<>>> This should be moved into a more generalied location within OSG itself * as it is not specific to either .modifyterrain or terrain databases in general. */ class ModifyDatabaseSuffixVisitor : public osg::NodeVisitor { public: ModifyDatabaseSuffixVisitor(std::string databaseSuffix): osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN), _numNodesModified(0), _databaseSuffix(databaseSuffix) { osg::notify(osg::DEBUG_INFO) <<"Running ModifyDatabaseSuffixVisitor..."<<std::endl; } ~ModifyDatabaseSuffixVisitor() { osg::notify(osg::DEBUG_INFO) <<" Number of Nodes Modified "<<_numNodesModified<<std::endl; } /** * Alters any PagedLOD nodes to incorporate the database * suffix in their filenames. * Counts how many PagedLOD nodes had their suffix modified. * * @param [in] node The PagedLOD node to alter. */ void apply(osg::PagedLOD& node) { osg::notify(osg::DEBUG_INFO) <<"ModifyDatabaseSuffixVisitor::apply PagedLOD" << std::endl; // set the PagedLOD's suffix node.setDatabaseSuffix(_databaseSuffix); _numNodesModified++; traverse(node); } // apply /** * Alters any ProxyNode nodes to incorporate the database * suffix in their filenames. * Counts how many ProxyNode nodes had their suffix modified. * * @param [in] node The ProxyNode node to alter. */ void apply(osg::ProxyNode& node) { osg::notify(osg::DEBUG_INFO) <<"ModifyDatabaseSuffixVisitor::apply ProxyNode" << std::endl; // set the ProxyNode's suffix node.setDatabaseSuffix(_databaseSuffix); _numNodesModified++; traverse(node); } // apply unsigned int _numNodesModified; std::string _databaseSuffix; }; // ModifyDatabaseSuffixVisitor /** * Actual (pseudo)loader/saver for ModifyTerrain * Uses ModifyTerrainVisitor to traverse scene graph to do the work. * Uses ModifyTerrainArgs to parse and store the parameters for the operation. * @sideeffect writeNode modifies the data being written, contrary to what the const-ness of it would suggest. * * @see ModifyTerrainVisitor * @see ModifyTerrainArgs */ class ReaderWriterMODIFYTERRAIN : public osgDB::ReaderWriter { public: ReaderWriterMODIFYTERRAIN() { supportsExtension(EXTENSION_NAME,"ModifyTerrain Pseudo loader/saver"); } virtual const char* className() const { return "terrain-modifying pseudo-loader/saver"; } virtual bool acceptsExtension(const std::string& extension) const { return osgDB::equalCaseInsensitive( extension, EXTENSION_NAME ); } virtual ReadResult readNode(const std::string& fileName, const osgDB::ReaderWriter::Options* options) const { std::string ext = osgDB::getLowerCaseFileExtension(fileName); if( !acceptsExtension(ext) ) return ReadResult::FILE_NOT_HANDLED; osg::notify(osg::DEBUG_INFO) << "ReaderWriterMODIFYTERRAIN readNode( \"" << fileName << "\" )" << std::endl; // strip the pseudo-loader extension std::string subFileName = osgDB::getNameLessExtension( fileName ); if( subFileName.empty()) { osg::notify(osg::WARN) << "Missing subfilename for " EXTENSION_NAME " pseudo-loader" << std::endl; return ReadResult::FILE_NOT_HANDLED; } ModifyTerrainArgs modifyArgs; if(options) { modifyArgs.parseOptions(options); } // if options osg::notify(osg::INFO) << " readNodeFile subFileName = \"" << subFileName << "\"" << std::endl; // recursively load the subfile. osg::Node *node = osgDB::readNodeFile( subFileName, options ); if( !node ) { // propagate the read failure upwards osg::notify(osg::WARN) << "ModifyTerrain Subfile \"" << subFileName << "\" could not be loaded" << std::endl; return ReadResult::FILE_NOT_HANDLED; } // visit the subgraph, altering each heightfield's vertices ModifyTerrainUsingArgsFunctor modifyFunctor(&modifyArgs); // apply the terrain modifications via visitor ModifyTerrainVisitor modTerrain(modifyFunctor); node->accept(modTerrain); // apply the .modifyterrain suffix to all PagedLODs that might have just been loaded ModifyDatabaseSuffixVisitor pseudoLoaderFunctor(".modifyterrain"); node->accept(pseudoLoaderFunctor); osg::notify(osg::DEBUG_INFO) << "ReaderWriterMODIFYTERRAIN readNode exiting." << std::endl; return node; } // readNode /** * Modify the terrain tiles as desired and write the scene graph. * * @sideeffect In this ReaderWriter plugin, writeNode modifies the data being written, * which is in direct contradiction of the const-ness of the writeNode prototype * for node, so we must forcibly cast away the const-ness of the osg::Node argument. * There appears to be no other way to accomplish this, short of making a temporary full * in-memory copy of the scenegraph being saved, and modifying that, and that is likely * to be prohibitive for most applications. In any case, VPB typically discards the saved * scenegraph after saving and won't mind. If your usage disagrees, you'll need to make * a scartch copy of the scenegraph before calling the modifyterrain pseudosaver. * Caveat Emptor. */ virtual WriteResult writeNode(const osg::Node& node,const std::string& fileName,const Options* options =NULL) const { osg::Node& nonconstnode = const_cast<osg::Node&>(node); std::string ext = osgDB::getLowerCaseFileExtension(fileName); if( !acceptsExtension(ext)) return WriteResult::FILE_NOT_HANDLED; osg::notify(osg::INFO) << "ReaderWriterMODIFYTERRAIN writeNode( \"" << fileName << "\" )" << std::endl; // strip the pseudo-saver extension std::string subFileName = osgDB::getNameLessExtension( fileName ); if( subFileName.empty()) { osg::notify(osg::WARN) << "Missing subfilename for " EXTENSION_NAME " pseudo-saver" << std::endl; return WriteResult::FILE_NOT_HANDLED; } ModifyTerrainArgs modifyArgs; if(options) { modifyArgs.parseOptions(options); } // if options // visit the subgraph, altering each heightfield's vertices ModifyTerrainUsingArgsFunctor modifyFunctor(&modifyArgs); ModifyTerrainVisitor modTerrain(modifyFunctor); nonconstnode.accept(modTerrain); osg::notify(osg::INFO) << " readNode subFileName = \"" << subFileName << "\"" << std::endl; // recursively save the subfile. if(!osgDB::writeNodeFile(nonconstnode, subFileName, options )) { // log the failure osg::notify(osg::WARN) << "ModifyTerrain Subfile \"" << subFileName << "\" could not be saved" << std::endl; return WriteResult::FILE_NOT_HANDLED; } // return success return WriteResult::FILE_SAVED; } // writeNode }; // Add ourself to the Registry to instantiate the reader/writer. REGISTER_OSGPLUGIN(modifyterrain, ReaderWriterMODIFYTERRAIN) /*EOF*/
SET(TARGET_SRC ReaderWriterMODIFYTERRAIN.cpp ) SET(TARGET_ADDED_LIBRARIES osgTerrain ) #### end var setup ### SETUP_PLUGIN(modifyterrain)
_______________________________________________ osg-submissions mailing list [email protected] http://lists.openscenegraph.org/listinfo.cgi/osg-submissions-openscenegraph.org
