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

Reply via email to