osgEarth 2.1.1
Classes | Public Member Functions | Protected Member Functions | Private Types | Private Member Functions | Private Attributes

SinglePassTerrainTechnique Class Reference

Inheritance diagram for SinglePassTerrainTechnique:
Collaboration diagram for SinglePassTerrainTechnique:

List of all members.

Classes

struct  ImageLayerUpdate

Public Member Functions

 SinglePassTerrainTechnique (TextureCompositor *compositor=0L)
 SinglePassTerrainTechnique (const SinglePassTerrainTechnique &, const osg::CopyOp &copyop=osg::CopyOp::SHALLOW_COPY)
 META_Object (osgEarth, SinglePassTerrainTechnique)
virtual void init ()
void setParentTile (Tile *tile)
void compile (const TileUpdate &updateSpec, ProgressCallback *progress)
bool applyTileUpdates ()
virtual void traverse (osg::NodeVisitor &nv)
void setVerticalScaleOverride (float value)
float getVerticalScaleOverride () const
void setOptimizeTriangleOrientation (bool optimizeTriangleOrientation)
bool getOptimizeTriangleOrientation () const
virtual void releaseGLObjects (osg::State *=0) const
osg::StateSet * getActiveStateSet () const

Protected Member Functions

void calculateSampling (unsigned int &out_rows, unsigned int &out_cols, double &out_i, double &out_j)

Private Types

typedef std::queue
< ImageLayerUpdate
ImageLayerUpdates
typedef std::map< UID, int > LayerUIDtoIndexMap

Private Member Functions

virtual ~SinglePassTerrainTechnique ()
osg::Vec3d computeCenterModel ()
bool createGeoImage (const CustomColorLayer &layer, GeoImage &image) const
osg::Geode * createGeometry (const TileFrame &tilef)
osg::StateSet * createStateSet (const TileFrame &tilef)
void prepareImageLayerUpdate (int layerIndex, const TileFrame &tilef)
osg::Geode * getFrontGeode () const
osg::StateSet * getParentStateSet () const
int getIndexOfColorLayerWithUID (UID uid) const

Private Attributes

bool _debug
OpenThreads::Mutex _compileMutex
osg::ref_ptr
< osg::MatrixTransform > 
_transform
osg::ref_ptr< osg::Geode > _backGeode
osg::ref_ptr< osg::Uniform > _imageLayerStampUniform
osg::Vec3d _centerModel
float _verticalScaleOverride
osg::ref_ptr< GeoLocator_masterLocator
int _initCount
bool _pendingFullUpdate
bool _pendingGeometryUpdate
ImageLayerUpdates _pendingImageLayerUpdates
LayerUIDtoIndexMap _layerUIDtoIndexMap
GeoExtent _tileExtent
TileKey _tileKey
bool _optimizeTriangleOrientation
osg::ref_ptr< const
TextureCompositor
_texCompositor
bool _frontGeodeInstalled
osg::observer_ptr< Tile_parentTile

Detailed Description

A terrain technique that uses a single texture unit by compositing image layer textures on the GPU.

This technique works by creating a single "mosaic" texture and copying each image layer's texture into that mosaic. It then creates a uniform array that conveys the relative offset and scale information of each sub-texture to a shader. The shader then composites the approprate mosaic texels on the GPU.

Limitations:

This technique is limited by the maximum texture size your GPU will support, since it creates a single mosaic texture. For example, if your GPU's max texture size is 2048, this technique can support 64 256-pixel layers.

Definition at line 63 of file SinglePassTerrainTechnique.


Member Typedef Documentation

Definition at line 150 of file SinglePassTerrainTechnique.

typedef std::map< UID, int > SinglePassTerrainTechnique::LayerUIDtoIndexMap [private]

Definition at line 154 of file SinglePassTerrainTechnique.


Constructor & Destructor Documentation

SinglePassTerrainTechnique::SinglePassTerrainTechnique ( TextureCompositor compositor = 0L)

Definition at line 68 of file SinglePassTerrainTechnique.cpp.

                                                                                      :
CustomTerrainTechnique(),
_verticalScaleOverride(1.0f),
_initCount(0),
_pendingFullUpdate( false ),
_pendingGeometryUpdate(false),
_optimizeTriangleOrientation(true),
_texCompositor( compositor ),
_frontGeodeInstalled( false ),
_debug( false )
{
    this->setThreadSafeRefUnref(true);
}
SinglePassTerrainTechnique::SinglePassTerrainTechnique ( const SinglePassTerrainTechnique rhs,
const osg::CopyOp &  copyop = osg::CopyOp::SHALLOW_COPY 
)
SinglePassTerrainTechnique::~SinglePassTerrainTechnique ( ) [private, virtual]

Definition at line 97 of file SinglePassTerrainTechnique.cpp.

{
    //nop
}

Member Function Documentation

bool SinglePassTerrainTechnique::applyTileUpdates ( ) [virtual]

Implements CustomTerrainTechnique.

Definition at line 253 of file SinglePassTerrainTechnique.cpp.

{
    bool applied = false;

    // serialize access to the compilation mechanism.
    OpenThreads::ScopedLock<Mutex> exclusiveLock( _compileMutex );

    // process a pending buffer swap:
    if ( _pendingFullUpdate )
    {
        if ( _backGeode->getStateSet() == 0L )
            OE_WARN << LC << "ILLEGAL: backGeode has no stateset" << std::endl;

        _transform->setChild( 0, _backGeode.get() );
        _frontGeodeInstalled = true;
        _backGeode = 0L;
        _pendingFullUpdate = false;
        _pendingGeometryUpdate = false;
        applied = true;
    }

    else
    {
        // process any pending LIVE geometry updates:
        if ( _pendingGeometryUpdate )
        {
            osg::Geode* frontGeode = getFrontGeode();

            if (frontGeode)
            {

                if ( _texCompositor->requiresUnitTextureSpace() )
                {
                    // in "unit-texture-space" mode, we can take the shortcut of just updating
                    // the geometry VBOs. The texture coordinates never change.
                    for( unsigned int i=0; i<_backGeode->getNumDrawables(); ++i )
                    {
                        osg::Geometry* backGeom = static_cast<osg::Geometry*>( _backGeode->getDrawable(i) );
                        osg::Vec3Array* backVerts = static_cast<osg::Vec3Array*>( backGeom->getVertexArray() );

                        osg::Geometry* frontGeom = static_cast<osg::Geometry*>( frontGeode->getDrawable(i) );
                        osg::Vec3Array* frontVerts = static_cast<osg::Vec3Array*>( frontGeom->getVertexArray() );

                        if ( backVerts->size() == frontVerts->size() )
                        {
                            // simple VBO update:
                            std::copy( backVerts->begin(), backVerts->end(), frontVerts->begin() );
                            frontVerts->dirty();

                            osg::Vec3Array* backNormals = static_cast<osg::Vec3Array*>( backGeom->getNormalArray() );
                            if ( backNormals )
                            {
                                osg::Vec3Array* frontNormals = static_cast<osg::Vec3Array*>( frontGeom->getNormalArray() );
                                std::copy( backNormals->begin(), backNormals->end(), frontNormals->begin() );
                                frontNormals->dirty();
                            }

                            osg::Vec2Array* backTexCoords = static_cast<osg::Vec2Array*>( backGeom->getTexCoordArray(0) );
                            if ( backTexCoords )
                            {
                                osg::Vec2Array* frontTexCoords = static_cast<osg::Vec2Array*>( frontGeom->getTexCoordArray(0) );
                                std::copy( backTexCoords->begin(), backTexCoords->end(), frontTexCoords->begin() );
                                frontTexCoords->dirty();
                            }
                        }
                        else
                        {
                            frontGeom->setVertexArray( backVerts );
                            frontGeom->setTexCoordArray( 0, backGeom->getTexCoordArray( 0 ) ); // TODO: un-hard-code
                            if ( backGeom->getNormalArray() )
                                frontGeom->setNormalArray( backGeom->getNormalArray() );
                        }
                    }
                }
                else
                {
                    // copy the drawables from the back buffer to the front buffer. By doing this,
                    // we don't touch the front geode's stateset (which contains the textures) and
                    // therefore they don't get re-applied.
                    for( unsigned int i=0; i<_backGeode->getNumDrawables(); ++i )
                    {
                        frontGeode->setDrawable( i, _backGeode->getDrawable( i ) );
                    }
                }
            }

            _pendingGeometryUpdate = false;
            _backGeode = 0L;
            applied = true;
        }

        // process any pending LIVE per-layer updates:
        osg::StateSet* parentStateSet = 0;

        if ( !_pendingImageLayerUpdates.empty() )
        {
            parentStateSet = getParentStateSet();
        }

        while( _pendingImageLayerUpdates.size() > 0 )
        {
            const ImageLayerUpdate& update = _pendingImageLayerUpdates.front();

            osg::ref_ptr< osg::Geode > frontGeode = getFrontGeode();
            if (frontGeode.valid())
            {
                _texCompositor->applyLayerUpdate(
                    frontGeode->getStateSet(),
                    update._layerUID,
                    update._image,
                    _tileKey,
                    update._isRealData ? parentStateSet : 0L );
            }

            _pendingImageLayerUpdates.pop();
            applied = true;

        }
    }

    if ( _debug )
    {
        OE_NOTICE << "applyTileUpdates()" << std::endl;
    }

    return applied;
}

Here is the call graph for this function:

Here is the caller graph for this function:

void SinglePassTerrainTechnique::calculateSampling ( unsigned int &  out_rows,
unsigned int &  out_cols,
double &  out_i,
double &  out_j 
) [protected]

Definition at line 492 of file SinglePassTerrainTechnique.cpp.

{            
    osgTerrain::Layer* elevationLayer = _tile->getElevationLayer();

    out_rows = elevationLayer->getNumRows();
    out_cols = elevationLayer->getNumColumns();
    out_i = 1.0;
    out_j = 1.0;

    float sampleRatio = _tile->getTerrain() ? _tile->getTerrain()->getSampleRatio() : 1.0f;
    if ( sampleRatio != 1.0f )
    {
        unsigned int originalNumColumns = out_cols;
        unsigned int originalNumRows = out_rows;

        out_cols = osg::maximum((unsigned int) (float(originalNumColumns)*sqrtf(sampleRatio)), 4u);
        out_rows = osg::maximum((unsigned int) (float(originalNumRows)*sqrtf(sampleRatio)),4u);

        out_i = double(originalNumColumns-1)/double(out_cols-1);
        out_j = double(originalNumRows-1)/double(out_rows-1);
    }
}

Here is the call graph for this function:

Here is the caller graph for this function:

void SinglePassTerrainTechnique::compile ( const TileUpdate updateSpec,
ProgressCallback progress 
) [virtual]

Implements CustomTerrainTechnique.

Definition at line 134 of file SinglePassTerrainTechnique.cpp.

{
    // safety check
    if ( !_tile ) 
    {
        OE_WARN << LC << "Illegal; terrain tile is null" << std::endl;
        return;
    }

    //if ( _debug )
    //{
    //    OE_NOTICE << LC << "compile() " << std::endl;
    //}

    // serialize access to the compilation procedure.
    OpenThreads::ScopedLock<Mutex> exclusiveLock( _compileMutex );
    
    // make a frame to use during compilation.
    TileFrame tilef( _tile );

    // establish the master tile locator if this is the first compilation:
    if ( !_masterLocator.valid() || !_transform.valid() )
    {
        _masterLocator = static_cast<GeoLocator*>( tilef._locator.get() );
        _masterLocator->convertLocalToModel( osg::Vec3(.5,.5,0), _centerModel );

        _transform = new osg::MatrixTransform( osg::Matrix::translate(_centerModel) );
        // this is a placeholder so that we can always just call setChild(0) later.
        _transform->addChild( new osg::Group );
    }

    // see whether a full update is required.
    bool partialUpdateOK = _texCompositor->supportsLayerUpdate() && _frontGeodeInstalled;

    // handle image layer addition or update:
    if (partialUpdateOK && 
        ( update.getAction() == TileUpdate::ADD_IMAGE_LAYER || update.getAction() == TileUpdate::UPDATE_IMAGE_LAYER ))
    {
        prepareImageLayerUpdate( update.getLayerUID(), tilef );

        // conditionally regenerate the texture coordinates for this layer.
        // TODO: optimize this with a method that ONLY regenerates the texture coordinates.
        if ( !_texCompositor->requiresUnitTextureSpace() )
        {
            osg::ref_ptr<osg::StateSet> stateSet = _backGeode.valid() ? _backGeode->getStateSet() : 0L;
            _backGeode = createGeometry( tilef );
            _backGeode->setStateSet( stateSet.get() );

            _pendingGeometryUpdate = true;
        }
    }

    else if (partialUpdateOK && update.getAction() == TileUpdate::MOVE_IMAGE_LAYER )
    {
        //nop - layer re-ordering happens entirely in the texture compositor.
    }

    //TODO: we should not need to check supportsLayerUpdate here, but it is not working properly in
    // multitexture mode (white tiles show up). Need to investigate and fix.
    else if ( partialUpdateOK && update.getAction() == TileUpdate::UPDATE_ELEVATION )
    {
        osg::ref_ptr<osg::StateSet> stateSet = _backGeode.valid() ? _backGeode->getStateSet() : 0L;
        _backGeode = createGeometry( tilef );
        _backGeode->setStateSet( stateSet.get() );

        _pendingGeometryUpdate = true;
    }

    else // all other update types
    {
        // give the engine a chance to bail out before generating geometry
        if ( progress && progress->isCanceled() )
        {
            _backGeode = 0L;
            return;
        }
    
        // create the geometry and texture coordinates for this tile in a new buffer
        _backGeode = createGeometry( tilef );
        if ( !_backGeode.valid() )
        {
            OE_WARN << LC << "createGeometry returned NULL" << std::endl;
            return;
        }

        // give the engine a chance to bail out before building the texture stateset:
        if ( progress && progress->isCanceled() )
        {
            _backGeode = 0L;
            return;
        }

        // create the stateset for this tile, which contains all the texture information.
        osg::StateSet* stateSet = createStateSet( tilef );
        if ( stateSet )
        {
            _backGeode->setStateSet( stateSet );
        }

        // give the engine a chance to bail out before swapping buffers
        if ( progress && progress->isCanceled() )
        {
            _backGeode = 0L;
            return;
        }
       
        _initCount++;
        if ( _initCount > 1 )
            OE_WARN << LC << "Tile was fully build " << _initCount << " times" << std::endl;

        if ( _backGeode.valid() && !_backGeode->getStateSet() )
            OE_WARN << LC << "ILLEGAL! no stateset in BackGeode!!" << std::endl;

        _pendingFullUpdate = true;
    }
}

Here is the call graph for this function:

Here is the caller graph for this function:

osg::Vec3d SinglePassTerrainTechnique::computeCenterModel ( ) [private]
bool SinglePassTerrainTechnique::createGeoImage ( const CustomColorLayer layer,
GeoImage image 
) const [private]

Definition at line 405 of file SinglePassTerrainTechnique.cpp.

{
    osg::ref_ptr<const GeoLocator> layerLocator = dynamic_cast<const GeoLocator*>( colorLayer.getLocator() );
    if ( layerLocator.valid() )
    {
        if ( layerLocator->getCoordinateSystemType() == osgTerrain::Locator::GEOCENTRIC )
            layerLocator = layerLocator->getGeographicFromGeocentric();

        const GeoExtent& imageExtent = layerLocator->getDataExtent();
        image = GeoImage( colorLayer.getImage(), imageExtent ); //const_cast<osg::Image*>(colorLayer.getImage()), imageExtent );
        return true;
    }
    return false;
}

Here is the call graph for this function:

Here is the caller graph for this function:

osg::Geode * SinglePassTerrainTechnique::createGeometry ( const TileFrame tilef) [private]

Definition at line 552 of file SinglePassTerrainTechnique.cpp.

{
    osg::ref_ptr<GeoLocator> masterTextureLocator = _masterLocator.get();
    //GeoLocator* geoMasterLocator = dynamic_cast<GeoLocator*>(_masterLocator.get());

        bool isCube = dynamic_cast<CubeFaceLocator*>(_masterLocator.get()) != NULL;

    // If we have a geocentric locator, get a geographic version of it to avoid converting
    // to/from geocentric when computing texture coordinats
    if (!isCube && /*geoMasterLocator && */ _masterLocator->getCoordinateSystemType() == osgTerrain::Locator::GEOCENTRIC)
    {
        masterTextureLocator = masterTextureLocator->getGeographicFromGeocentric();
    }
    
    osgTerrain::Layer* elevationLayer = _tile->getElevationLayer();

    // fire up a brand new geode.
    osg::Geode* geode = new osg::Geode();
    geode->setThreadSafeRefUnref(true);
    
    // setting the geometry to DYNAMIC means its draw will not overlap the next frame's update/cull
    // traversal - which could access the buffer without a mutex

    osg::Geometry* surface = new osg::Geometry();
    surface->setThreadSafeRefUnref(true); // TODO: probably unnecessary.
    surface->setDataVariance( osg::Object::DYNAMIC );
    surface->setUseDisplayList(false);
    surface->setUseVertexBufferObjects(true);
    geode->addDrawable( surface );

    osg::Geometry* skirt = new osg::Geometry();
    skirt->setThreadSafeRefUnref(true); // TODO: probably unnecessary.
    skirt->setDataVariance( osg::Object::DYNAMIC );
    skirt->setUseDisplayList(false);
    skirt->setUseVertexBufferObjects(true);
    geode->addDrawable( skirt );

        osg::ref_ptr<GeoLocator> geoLocator = _masterLocator;
        // Avoid coordinates conversion when GEOCENTRIC, so get a GEOGRAPHIC version of Locator 
        if (_masterLocator->getCoordinateSystemType() == osgTerrain::Locator::GEOCENTRIC) {
                geoLocator = _masterLocator->getGeographicFromGeocentric();
        }

        float scaleHeight = 
                _verticalScaleOverride != 1.0? _verticalScaleOverride :
                _tile->getTerrain() ? _tile->getTerrain()->getVerticalScale() :
                1.0f;

    MaskRecordVector masks;
    for (MaskLayerVector::const_iterator it = tilef._masks.begin(); it != tilef._masks.end(); ++it)
    {
          // When displaying Plate Carre, Heights have to be converted from meters to degrees.
          // This is also true for mask feature
          // TODO: adjust this calculation based on the actual EllipsoidModel.
          float scale = scaleHeight;
          if (_masterLocator->getCoordinateSystemType() == osgEarth::GeoLocator::GEOGRAPHIC)
                scale = scaleHeight / 111319.0f;

          // TODO: Get the map SRS if possible instead of masterLocator's one
          osg::Vec3dArray* boundary = (*it)->getOrCreateBoundary(scale, _masterLocator->getDataExtent().getSRS());

      if ( boundary )
      {
          osg::Vec3d min, max;
          min = max = boundary->front();

          for (osg::Vec3dArray::iterator it = boundary->begin(); it != boundary->end(); ++it)
          {
            if (it->x() < min.x())
              min.x() = it->x();

            if (it->y() < min.y())
              min.y() = it->y();

            if (it->x() > max.x())
              max.x() = it->x();

            if (it->y() > max.y())
              max.y() = it->y();
          }

          osg::Vec3d min_ndc, max_ndc;
          geoLocator->convertModelToLocal(min, min_ndc);
          geoLocator->convertModelToLocal(max, max_ndc);

          bool x_match = ((min_ndc.x() >= 0.0 && max_ndc.x() <= 1.0) ||
                          (min_ndc.x() <= 0.0 && max_ndc.x() > 0.0) ||
                          (min_ndc.x() < 1.0 && max_ndc.x() >= 1.0));

          bool y_match = ((min_ndc.y() >= 0.0 && max_ndc.y() <= 1.0) ||
                          (min_ndc.y() <= 0.0 && max_ndc.y() > 0.0) ||
                          (min_ndc.y() < 1.0 && max_ndc.y() >= 1.0));

          if (x_match && y_match)
          {
            osg::Geometry* mask_geom = new osg::Geometry();
            mask_geom->setThreadSafeRefUnref(true);
            mask_geom->setDataVariance( osg::Object::DYNAMIC );
            mask_geom->setUseDisplayList(false);
            mask_geom->setUseVertexBufferObjects(true);
            //mask_geom->getOrCreateStateSet()->setAttribute(new osg::Point( 5.0f ), osg::StateAttribute::ON);
            geode->addDrawable(mask_geom);

            masks.push_back(MaskRecord(boundary, min_ndc, max_ndc, mask_geom));
          }
       }
    }

    osg::Geometry* stitching_skirts = 0L;
    osg::Vec3Array* ss_verts = 0L;
    if (masks.size() > 0)
    {
      stitching_skirts = new osg::Geometry();
      stitching_skirts->setThreadSafeRefUnref(true);
      stitching_skirts->setDataVariance( osg::Object::DYNAMIC );
      stitching_skirts->setUseDisplayList(false);
      stitching_skirts->setUseVertexBufferObjects(true);
      //stitching_skirts->getOrCreateStateSet()->setAttribute(new osg::Point( 5.0f ), osg::StateAttribute::ON);
      geode->addDrawable( stitching_skirts);

      ss_verts = new osg::Vec3Array();
      stitching_skirts->setVertexArray(ss_verts);
    }


    unsigned int numRows = 20;
    unsigned int numColumns = 20;
    
    if (elevationLayer)
    {
        numColumns = elevationLayer->getNumColumns();
        numRows = elevationLayer->getNumRows();
    }
    
    double i_sampleFactor, j_sampleFactor;
    calculateSampling( numColumns, numRows, i_sampleFactor, j_sampleFactor );
    
    float skirtHeight = 0.0f;
    osgTerrain::HeightFieldLayer* hfl = dynamic_cast<osgTerrain::HeightFieldLayer*>(elevationLayer);
    if (hfl && hfl->getHeightField()) 
    {
        skirtHeight = hfl->getHeightField()->getSkirtHeight();
    }
    
    bool createSkirt = skirtHeight != 0.0f;
  
    unsigned int numVerticesInSurface = numColumns*numRows;
    unsigned int numVerticesInSkirt = createSkirt ? (2 * (numColumns*2 + numRows*2 - 4)) : 0;
    //unsigned int numVertices = numVerticesInBody+numVerticesInSkirt;

    // allocate and assign vertices
    osg::ref_ptr<osg::Vec3Array> surfaceVerts = new osg::Vec3Array;
    surfaceVerts->reserve( numVerticesInSurface );
    surface->setVertexArray( surfaceVerts.get() );

    // allocate and assign normals
    osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array();
    normals->reserve(numVerticesInSurface);
    surface->setNormalArray(normals.get());
    surface->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);

    // skirt texture coordinates, if applicable:
    osg::Vec2Array* unifiedSkirtTexCoords = 0L;

    // stitching skirt texture coordinates, if applicable:
    osg::Vec2Array* unifiedStitchSkirtTexCoords = 0L;

    // allocate and assign texture coordinates
    osg::Vec2Array* unifiedSurfaceTexCoords = 0L;

    //int numColorLayers = _tile->getNumColorLayers();
    RenderLayerVector renderLayers;

    if ( _texCompositor->requiresUnitTextureSpace() )
    {
        // for a unified unit texture space, just make a single texture coordinate array.
        unifiedSurfaceTexCoords = new osg::Vec2Array();
        unifiedSurfaceTexCoords->reserve( numVerticesInSurface );
        surface->setTexCoordArray( 0, unifiedSurfaceTexCoords );
        if (createSkirt)
        {
            unifiedSkirtTexCoords = new osg::Vec2Array();
            unifiedSkirtTexCoords->reserve( numVerticesInSkirt );        
            skirt->setTexCoordArray( 0, unifiedSkirtTexCoords );
        }

        if (masks.size() > 0)
        {
            unifiedStitchSkirtTexCoords = new osg::Vec2Array();
            //unifiedStitchSkirtTexCoords->reserve( ? );        
            stitching_skirts->setTexCoordArray( 0, unifiedStitchSkirtTexCoords );
        }
    }

    else // if ( !_texCompositor->requiresUnitTextureSpace() )
    {
        LocatorToTexCoordTable locatorToTexCoordTable;
        renderLayers.reserve( tilef._colorLayers.size() );

        // build a list of "render layers", in slot order, sharing texture coordinate
        // arrays wherever possible.
        for( ColorLayersByUID::const_iterator i = tilef._colorLayers.begin(); i != tilef._colorLayers.end(); ++i )
        {
            const CustomColorLayer& colorLayer = i->second;
            RenderLayer r;
            r._layer = colorLayer;

            const GeoLocator* locator = dynamic_cast<const GeoLocator*>( r._layer.getLocator() );
            if ( locator )
            {
                r._texCoords = locatorToTexCoordTable.find( locator );
                if ( !r._texCoords.valid() )
                {
                    r._texCoords = new osg::Vec2Array();
                    r._texCoords->reserve( numVerticesInSurface );
                    r._ownsTexCoords = true;
                    locatorToTexCoordTable.push_back( LocatorTexCoordPair(locator, r._texCoords.get()) );
                }

                r._skirtTexCoords = new osg::Vec2Array();
                r._skirtTexCoords->reserve( numVerticesInSkirt );

                if ( masks.size() > 0 )
                {
                    r._stitchTexCoords = new osg::Vec2Array();
                    r._stitchSkirtTexCoords = new osg::Vec2Array();
                }

                r._locator = locator;
                if ( locator->getCoordinateSystemType() == osgTerrain::Locator::GEOCENTRIC )
                {
                    const GeoLocator* geo = dynamic_cast<const GeoLocator*>(locator);
                    if ( geo )
                        r._locator = geo->getGeographicFromGeocentric();
                }

                _texCompositor->assignTexCoordArray( surface, colorLayer.getUID(), r._texCoords.get() );
                _texCompositor->assignTexCoordArray( skirt, colorLayer.getUID(), r._skirtTexCoords.get() );

                for (MaskRecordVector::iterator mr = masks.begin(); mr != masks.end(); ++mr)
                    _texCompositor->assignTexCoordArray( (*mr)._geom, colorLayer.getUID(), r._stitchTexCoords.get() );

                if (stitching_skirts)
                    _texCompositor->assignTexCoordArray( stitching_skirts, colorLayer.getUID(), r._stitchSkirtTexCoords.get() );

                //surface->setTexCoordArray( renderLayers.size(), r._texCoords );
                renderLayers.push_back( r );
            }
            else
            {
                OE_WARN << LC << "Found a Locator, but it wasn't a GeoLocator." << std::endl;
            }
        }
    }

    osg::ref_ptr<osg::FloatArray> elevations = new osg::FloatArray;
    if (elevations.valid()) elevations->reserve(numVerticesInSurface);        

    // allocate and assign color
    osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array(1);
    (*colors)[0].set(1.0f,1.0f,1.0f,1.0f);
    
    surface->setColorArray(colors.get());
    surface->setColorBinding(osg::Geometry::BIND_OVERALL);

    typedef std::vector<int> Indices;
    Indices indices(numVerticesInSurface, -1);    

    // populate vertex and tex coord arrays    
    unsigned int i, j; //, k=0;
    for(j=0; j<numRows; ++j)
    {
        for(i=0; i<numColumns; ++i) // ++k)
        {
            unsigned int iv = j*numColumns + i;
            osg::Vec3d ndc( ((double)i)/(double)(numColumns-1), ((double)j)/(double)(numRows-1), 0.0);
     
            bool validValue = true;
            
            unsigned int i_equiv = i_sampleFactor==1.0 ? i : (unsigned int) (double(i)*i_sampleFactor);

            unsigned int j_equiv = j_sampleFactor==1.0 ? j : (unsigned int) (double(j)*j_sampleFactor);

            if (elevationLayer)
            {
                float value = 0.0f;
                validValue = elevationLayer->getValidValue(i_equiv,j_equiv, value);
                ndc.z() = value*scaleHeight;
            }

            //Invalidate if point falls within mask bounding box
            if (validValue && masks.size() > 0)
            {
              for (MaskRecordVector::iterator mr = masks.begin(); mr != masks.end(); ++mr)
              {
                if(ndc.x() >= (*mr)._ndcMin.x() && ndc.x() <= (*mr)._ndcMax.x() &&
                   ndc.y() >= (*mr)._ndcMin.y() && ndc.y() <= (*mr)._ndcMax.y())
                {
                  validValue = false;
                  indices[iv] = -2;
                  break;
                }
              }
            }
            
            if (validValue)
            {
                //indices[iv] = k;
                indices[iv] = surfaceVerts->size();
            
                osg::Vec3d model;
                _masterLocator->convertLocalToModel(ndc, model);

                //(*surfaceVerts)[k] = model - centerModel;
                (*surfaceVerts).push_back(model - _centerModel);

                if ( _texCompositor->requiresUnitTextureSpace() )
                {
                    // the unified unit texture space requires a single, untransformed unit coord [0..1]
                    (*unifiedSurfaceTexCoords).push_back( osg::Vec2( ndc.x(), ndc.y() ) );
                }
                else
                {
                    // the separate texture space requires separate transformed texcoords for each layer.
                    for( RenderLayerVector::const_iterator r = renderLayers.begin(); r != renderLayers.end(); ++r )
                    {
                        if ( r->_ownsTexCoords )
                        {
                            //if ( r->_locator.get() != _masterLocator.get() )
                            //if ( r->_locator->isEr->_locator != masterTextureLocator _masterLocator.get() )
                            if ( !r->_locator->isEquivalentTo( *masterTextureLocator.get() ) )
                            {
                                osg::Vec3d color_ndc;
                                osgTerrain::Locator::convertLocalCoordBetween( *masterTextureLocator.get(), ndc, *r->_locator.get(), color_ndc );
                                r->_texCoords->push_back( osg::Vec2( color_ndc.x(), color_ndc.y() ) );
                            }
                            else
                            {
                                r->_texCoords->push_back( osg::Vec2( ndc.x(), ndc.y() ) );
                            }
                        }
                    }
                }

                if (elevations.valid())
                {
                    (*elevations).push_back(ndc.z());
                }

                // compute the local normal
                osg::Vec3d ndc_one = ndc; ndc_one.z() += 1.0;
                osg::Vec3d model_one;
                _masterLocator->convertLocalToModel(ndc_one, model_one);
                model_one = model_one - model;
                model_one.normalize();    

                //(*normals)[k] = model_one;
                (*normals).push_back(model_one);
            }
        }
    }


    for (MaskRecordVector::iterator mr = masks.begin(); mr != masks.end(); ++mr)
    {
      int min_i = (int)floor((*mr)._ndcMin.x() * (double)(numColumns-1));
      if (min_i < 0) min_i = 0;
      if (min_i >= numColumns) min_i = numColumns - 1;

      int min_j = (int)floor((*mr)._ndcMin.y() * (double)(numRows-1));
      if (min_j < 0) min_j = 0;
      if (min_j >= numColumns) min_j = numColumns - 1;

      int max_i = (int)ceil((*mr)._ndcMax.x() * (double)(numColumns-1));
      if (max_i < 0) max_i = 0;
      if (max_i >= numColumns) max_i = numColumns - 1;

      int max_j = (int)ceil((*mr)._ndcMax.y() * (double)(numRows-1));
      if (max_j < 0) max_j = 0;
      if (max_j >= numColumns) max_j = numColumns - 1;

      if (min_i >= 0 && max_i >= 0 && min_j >= 0 && max_j >= 0)
      {
        int num_i = max_i - min_i + 1;
        int num_j = max_j - min_j + 1;

        osg::ref_ptr<osgEarth::Symbology::Polygon> maskSkirtPoly = new osgEarth::Symbology::Polygon();
        maskSkirtPoly->resize(num_i * 2 + num_j * 2 - 4);
        for (int i = 0; i < num_i; i++)
        {
          //int index = indices[min_j*numColumns + i + min_i];
          {
            osg::Vec3d ndc( ((double)(i + min_i))/(double)(numColumns-1), ((double)min_j)/(double)(numRows-1), 0.0);

            if (elevationLayer)
            {
              unsigned int i_equiv = i_sampleFactor==1.0 ? i + min_i : (unsigned int) (double(i + min_i)*i_sampleFactor);
              unsigned int j_equiv = j_sampleFactor==1.0 ? min_j : (unsigned int) (double(min_j)*j_sampleFactor);

              float value = 0.0f;
              if (elevationLayer->getValidValue(i_equiv,j_equiv, value))
                ndc.z() = value*scaleHeight;
            }

            (*maskSkirtPoly)[i] = ndc;
          }

          //index = indices[max_j*numColumns + i + min_i];
          {
            osg::Vec3d ndc( ((double)(i + min_i))/(double)(numColumns-1), ((double)max_j)/(double)(numRows-1), 0.0);

            if (elevationLayer)
            {
              unsigned int i_equiv = i_sampleFactor==1.0 ? i + min_i : (unsigned int) (double(i + min_i)*i_sampleFactor);
              unsigned int j_equiv = j_sampleFactor==1.0 ? max_j : (unsigned int) (double(max_j)*j_sampleFactor);

              float value = 0.0f;
              if (elevationLayer->getValidValue(i_equiv,j_equiv, value))
                ndc.z() = value*scaleHeight;
            }

            (*maskSkirtPoly)[i + (2 * num_i + num_j - 3) - 2 * i] = ndc;
          }
        }
        for (int j = 0; j < num_j - 2; j++)
        {
          //int index = indices[(min_j + j + 1)*numColumns + max_i];
          {
            osg::Vec3d ndc( ((double)max_i)/(double)(numColumns-1), ((double)(min_j + j + 1))/(double)(numRows-1), 0.0);

            if (elevationLayer)
            {
              unsigned int i_equiv = i_sampleFactor==1.0 ? max_i : (unsigned int) (double(max_i)*i_sampleFactor);
              unsigned int j_equiv = j_sampleFactor==1.0 ? min_j + j + 1 : (unsigned int) (double(min_j + j + 1)*j_sampleFactor);

              float value = 0.0f;
              if (elevationLayer->getValidValue(i_equiv,j_equiv, value))
                ndc.z() = value*scaleHeight;
            }

            (*maskSkirtPoly)[j + num_i] = ndc;
          }

          //index = indices[(min_j + j + 1)*numColumns + min_i];
          {
            osg::Vec3d ndc( ((double)min_i)/(double)(numColumns-1), ((double)(min_j + j + 1))/(double)(numRows-1), 0.0);

            if (elevationLayer)
            {
              unsigned int i_equiv = i_sampleFactor==1.0 ? min_i : (unsigned int) (double(min_i)*i_sampleFactor);
              unsigned int j_equiv = j_sampleFactor==1.0 ? min_j + j + 1 : (unsigned int) (double(min_j + j + 1)*j_sampleFactor);

              float value = 0.0f;
              if (elevationLayer->getValidValue(i_equiv,j_equiv, value))
                ndc.z() = value*scaleHeight;
            }

            (*maskSkirtPoly)[j + (2 * num_i + 2 * num_j - 5) - 2 * j] = ndc;
          }
        }

        //Create local polygon representing mask
        osg::ref_ptr<osgEarth::Symbology::Polygon> maskPoly = new osgEarth::Symbology::Polygon();
        for (osg::Vec3dArray::iterator it = (*mr)._boundary->begin(); it != (*mr)._boundary->end(); ++it)
        {
          osg::Vec3d local;
          geoLocator->convertModelToLocal(*it, local);
          maskPoly->push_back(local);
        }


//Change the following two #if statements to see mask skirt polygons
//before clipping and adjusting
#if 1
        //Do a diff on the polygons to get the actual mask skirt
        osg::ref_ptr<osgEarth::Symbology::Geometry> outPoly;
        maskSkirtPoly->difference(maskPoly.get(), outPoly);
#else
        osg::ref_ptr<osgEarth::Symbology::Geometry> outPoly = maskSkirtPoly;
#endif

        osg::Vec3Array* outVerts = new osg::Vec3Array();

        osg::Geometry* stitch_geom = (*mr)._geom;
        stitch_geom->setVertexArray(outVerts);

        bool multiParent = false;
        if (outPoly.valid())
          multiParent = outPoly->getType() == osgEarth::Symbology::Geometry::TYPE_MULTI;
        
        std::vector<int> skirtIndices;

        osgEarth::Symbology::GeometryIterator i( outPoly.get(), false );
        while( i.hasMore() )
        {
          osgEarth::Symbology::Geometry* part = i.next();
          if (!part)
            continue;

          if (part->getType() == osgEarth::Symbology::Geometry::TYPE_POLYGON)
          {
            osg::Vec3Array* partVerts = part->toVec3Array();
            outVerts->insert(outVerts->end(), partVerts->begin(), partVerts->end());
            stitch_geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::POLYGON, outVerts->size() - partVerts->size(), partVerts->size()));
            skirtIndices.push_back(outVerts->size());

            if (!multiParent)
            {
              osg::ref_ptr<osgEarth::Symbology::Polygon> holePoly = static_cast<osgEarth::Symbology::Polygon*>(outPoly.get());
              if (holePoly)
              {
                osgEarth::Symbology::RingCollection holes = holePoly->getHoles();
                
                for (osgEarth::Symbology::RingCollection::iterator hit = holes.begin(); hit != holes.end(); ++hit)
                {
                  (*hit)->rewind(osgEarth::Symbology::Ring::ORIENTATION_CCW);
                  outVerts->insert(outVerts->end(), (*hit)->begin(), (*hit)->end());
                  stitch_geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::POLYGON, outVerts->size() - (*hit)->size(), (*hit)->size()));
                  skirtIndices.push_back(outVerts->size());
                }
              }
            }
          }
        }

        if (stitch_geom->getNumPrimitiveSets() > 0)
        {
#if 1
          // Tessellate mask skirt
          osg::ref_ptr<osgUtil::Tessellator> tscx=new osgUtil::Tessellator;
          tscx->setTessellationType(osgUtil::Tessellator::TESS_TYPE_GEOMETRY);
          tscx->setBoundaryOnly(false);
          tscx->setWindingType(osgUtil::Tessellator::TESS_WINDING_ODD);
          tscx->retessellatePolygons(*stitch_geom);

          // Assign normals to the stitching polygon: -gw
          osg::Vec3Array* sgVerts = dynamic_cast<osg::Vec3Array*>(stitch_geom->getVertexArray());
          osg::Vec3Array* sgNormals = new osg::Vec3Array(sgVerts->size());
          stitch_geom->setNormalArray( sgNormals );
          stitch_geom->setNormalBinding( osg::Geometry::BIND_PER_VERTEX );
          
          // calculate the normal and convert to model space.
          for( unsigned v=0; v<sgVerts->size(); ++v )
          {
              const osg::Vec3& vert = (*sgVerts)[v];
              osg::Vec3d local_one(vert);
              osg::Vec3d model;
              _masterLocator->convertLocalToModel( local_one, model );
              local_one.z() += 1.0;
              osg::Vec3d model_one;
              _masterLocator->convertLocalToModel( local_one, model_one );
              model_one = model_one - model;
              model_one.normalize();
              (*sgNormals)[v] = model_one;
          }

          //Initialize tex coords
          osg::Vec2Array* unifiedStitchTexCoords = 0L;
          if (_texCompositor->requiresUnitTextureSpace())
          {
            unifiedStitchTexCoords = new osg::Vec2Array();
            unifiedStitchTexCoords->reserve(outVerts->size());
            stitch_geom->setTexCoordArray(0, unifiedStitchTexCoords);
          }
          else if ( renderLayers.size() > 0 )
          {
            for (unsigned int i = 0; i < renderLayers.size(); ++i)
            {
              renderLayers[i]._stitchTexCoords->reserve(outVerts->size());
            }
          }


          //Retrieve z values for mask skirt verts
          std::vector<int> isZSet;
          for (osg::Vec3Array::iterator it = outVerts->begin(); it != outVerts->end(); ++it)
          {
            int zSet = 0;

            //Look for verts that belong to the original mask skirt polygon
            for (osgEarth::Symbology::Polygon::iterator mit = maskSkirtPoly->begin(); mit != maskSkirtPoly->end(); ++mit)
            {
              if (osg::absolute((*mit).x() - (*it).x()) < MATCH_TOLERANCE && osg::absolute((*mit).y() - (*it).y()) < MATCH_TOLERANCE)
              {
                (*it).z() = (*mit).z();
                zSet += 1;
                break;
              }
            }

            //Look for verts that belong to the mask polygon
            for (osgEarth::Symbology::Polygon::iterator mit = maskPoly->begin(); mit != maskPoly->end(); ++mit)
            {
              if (osg::absolute((*mit).x() - (*it).x()) < MATCH_TOLERANCE && osg::absolute((*mit).y() - (*it).y()) < MATCH_TOLERANCE)
              {
                (*it).z() = (*mit).z();
                zSet += 2;
                break;
              }
            }

            isZSet.push_back(zSet);
          }

          //Any mask skirt verts that are still unset are newly created verts where the skirt
          //meets the mask. Find the mask segment the point lies along and calculate the
          //appropriate z value for the point.
          //
          //Now that all the z values are set, convert each vert into model coords.
          //
          //Also, while we are iterating through the verts, set up tex coords.
          int count = 0;
          for (osg::Vec3Array::iterator it = outVerts->begin(); it != outVerts->end(); ++it)
          {
            //If the z-value was set from a mask vertex there is no need to change it.  If
            //it was set from a vertex from the stitching polygon it may need overriden if
            //the vertex lies along a mask edge.  Or if it is unset, it will need to be set.
            if (isZSet[count] < 2)
            {
              osg::Vec3d p2 = *it;
              double closestZ = 0.0;
              double closestRatio = DBL_MAX;
              for (osgEarth::Symbology::Polygon::iterator mit = maskPoly->begin(); mit != maskPoly->end(); ++mit)
              {
                osg::Vec3d p1 = *mit;
                osg::Vec3d p3 = mit == --maskPoly->end() ? maskPoly->front() : (*(mit + 1));

                //Truncated vales to compensate for accuracy issues
                double p1x = ((int)(p1.x() * 1000000)) / 1000000.0L;
                double p3x = ((int)(p3.x() * 1000000)) / 1000000.0L;
                double p2x = ((int)(p2.x() * 1000000)) / 1000000.0L;

                double p1y = ((int)(p1.y() * 1000000)) / 1000000.0L;
                double p3y = ((int)(p3.y() * 1000000)) / 1000000.0L;
                double p2y = ((int)(p2.y() * 1000000)) / 1000000.0L;

                if ((p1x < p3x ? p2x >= p1x && p2x <= p3x : p2x >= p3x && p2x <= p1x) &&
                    (p1y < p3y ? p2y >= p1y && p2y <= p3y : p2y >= p3y && p2y <= p1y))
                {
                  double l1 =(osg::Vec2d(p2.x(), p2.y()) - osg::Vec2d(p1.x(), p1.y())).length();
                  double lt = (osg::Vec2d(p3.x(), p3.y()) - osg::Vec2d(p1.x(), p1.y())).length();
                  double zmag = p3.z() - p1.z();

                  double foundZ = (l1 / lt) * zmag + p1.z();

                  double mRatio = 1.0;
                  if (osg::absolute(p1x - p3x) < MATCH_TOLERANCE)
                  {
                    if (osg::absolute(p1x-p2x) < MATCH_TOLERANCE)
                      mRatio = 0.0;
                  }
                  else
                  {
                    double m1 = p1x == p2x ? 0.0 : (p2y - p1y) / (p2x - p1x);
                    double m2 = p1x == p3x ? 0.0 : (p3y - p1y) / (p3x - p1x);
                    mRatio = m2 == 0.0 ? m1 : osg::absolute(1.0L - m1 / m2);
                  }

                  if (mRatio < 0.01)
                  {
                    (*it).z() = foundZ;
                    isZSet[count] = 2;
                    break;
                  }
                  else if (mRatio < closestRatio)
                  {
                    closestRatio = mRatio;
                    closestZ = foundZ;
                  }
                }
              }

              if (!isZSet[count] && closestRatio < DBL_MAX)
              {
                (*it).z() = closestZ;
                isZSet[count] = 2;
              }
            }

            if (!isZSet[count])
              OE_WARN << LC << "Z-value not set for stitching polygon vertex" << std::endl;

            count++;

            //Convert to model coords
            osg::Vec3d model;
            _masterLocator->convertLocalToModel(*it, model);
            model = model - _centerModel;
            (*it).set(model.x(), model.y(), model.z());

            //Setup tex coords
            osg::Vec3d ndc;
            _masterLocator->convertModelToLocal(*it + _centerModel, ndc);

            if (_texCompositor->requiresUnitTextureSpace())
            {
              unifiedStitchTexCoords->push_back(osg::Vec2(ndc.x(), ndc.y()));
            }
            else if (renderLayers.size() > 0)
            {
              for (unsigned int i = 0; i < renderLayers.size(); ++i)
              {
                if (!renderLayers[i]._locator->isEquivalentTo(*masterTextureLocator.get()))
                {
                  osg::Vec3d color_ndc;
                  osgTerrain::Locator::convertLocalCoordBetween(*masterTextureLocator.get(), ndc, *renderLayers[i]._locator.get(), color_ndc);
                  renderLayers[i]._stitchTexCoords->push_back(osg::Vec2(color_ndc.x(), color_ndc.y()));
                }
                else
                {
                  renderLayers[i]._stitchTexCoords->push_back(osg::Vec2(ndc.x(), ndc.y()));
                }
              }
            }
          }
#else
          for (osg::Vec3Array::iterator it = outVerts->begin(); it != outVerts->end(); ++it)
          {
            //Convert to model coords
            osg::Vec3d model;
            _masterLocator->convertLocalToModel(*it, model);
            model = model - _centerModel;
            (*it).set(model.x(), model.y(), model.z());
          }
#endif
      
          //Create stitching skirts
          if (createSkirt && skirtIndices.size() > 0)
          {
            ss_verts->reserve(ss_verts->size() + outVerts->size() * 4 + skirtIndices.size() * 2);

            //Add a primative set for each continuous skirt strip
            for (int p=0; p < skirtIndices.size(); p++)
            {
              int cursor = ss_verts->size();

              int outStart = p == 0 ? 0 : skirtIndices[p-1];
              for (int i=outStart; i < skirtIndices[p]; i++)
              {
                ss_verts->push_back((*outVerts)[i]);
                ss_verts->push_back((*outVerts)[i] - (*sgNormals)[i] * skirtHeight);

                if ( _texCompositor->requiresUnitTextureSpace() )
                {
                    unifiedStitchSkirtTexCoords->push_back( (*unifiedStitchTexCoords)[i] );
                    unifiedStitchSkirtTexCoords->push_back( (*unifiedStitchTexCoords)[i] );
                }
                else if ( renderLayers.size() > 0 )
                {
                    for (unsigned int r = 0; r < renderLayers.size(); ++r)
                    {
                        const osg::Vec2& tc = (*renderLayers[r]._stitchTexCoords.get())[i];
                        renderLayers[r]._stitchSkirtTexCoords->push_back( tc );
                        renderLayers[r]._stitchSkirtTexCoords->push_back( tc );
                    }
                }
              }

              //Add the first vert again to complete the loop
              ss_verts->push_back((*outVerts)[outStart]);
              ss_verts->push_back((*outVerts)[outStart] - (*sgNormals)[outStart] * skirtHeight);

              if ( _texCompositor->requiresUnitTextureSpace() )
              {
                  unifiedStitchSkirtTexCoords->push_back( (*unifiedStitchTexCoords)[outStart] );
                  unifiedStitchSkirtTexCoords->push_back( (*unifiedStitchTexCoords)[outStart] );
              }
              else if ( renderLayers.size() > 0 )
              {
                  for (unsigned int r = 0; r < renderLayers.size(); ++r)
                  {
                      const osg::Vec2& tc = (*renderLayers[r]._stitchTexCoords.get())[outStart];
                      renderLayers[r]._stitchSkirtTexCoords->push_back( tc );
                      renderLayers[r]._stitchSkirtTexCoords->push_back( tc );
                  }
              }

              //Now go back the opposite direction to create a skirt facing the other direction
              for (int i=skirtIndices[p] - 1; i >= outStart; i--)
              {
                ss_verts->push_back((*outVerts)[i]);
                ss_verts->push_back((*outVerts)[i] - (*sgNormals)[i] * skirtHeight);

                if ( _texCompositor->requiresUnitTextureSpace() )
                {
                    unifiedStitchSkirtTexCoords->push_back( (*unifiedStitchTexCoords)[i] );
                    unifiedStitchSkirtTexCoords->push_back( (*unifiedStitchTexCoords)[i] );
                }
                else if ( renderLayers.size() > 0 )
                {
                    for (unsigned int r = 0; r < renderLayers.size(); ++r)
                    {
                        const osg::Vec2& tc = (*renderLayers[r]._stitchTexCoords.get())[i];
                        renderLayers[r]._stitchSkirtTexCoords->push_back( tc );
                        renderLayers[r]._stitchSkirtTexCoords->push_back( tc );
                    }
                }
              }

              stitching_skirts->addPrimitiveSet(new osg::DrawArrays( GL_TRIANGLE_STRIP, cursor, ss_verts->size() - cursor));
            }
          }
        }
      }
    }

    // populate primitive sets
    bool swapOrientation = !(_masterLocator->orientationOpenGL());

    osg::ref_ptr<osg::DrawElementsUInt> elements = new osg::DrawElementsUInt(GL_TRIANGLES);
    elements->reserve((numRows-1) * (numColumns-1) * 6);

    surface->addPrimitiveSet(elements.get());
    
    osg::ref_ptr<osg::Vec3Array> skirtVectors = new osg::Vec3Array( *normals );

          if (!normals)
        createSkirt = false;
    
    // New separated skirts.
    if ( createSkirt )
    {        
        // build the verts first:
        osg::Vec3Array* skirtVerts = new osg::Vec3Array();
        osg::Vec3Array* skirtNormals = new osg::Vec3Array();

        skirtVerts->reserve( numVerticesInSkirt );
        skirtNormals->reserve( numVerticesInSkirt );
        
        Indices skirtBreaks;
        skirtBreaks.push_back(0);

        // bottom:
        for( unsigned int c=0; c<numColumns-1; ++c )
        {
            int orig_i = indices[c];

            //int offset = 0;
            //while (orig_i < 0 && offset < numRows - 1)
            //  orig_i = indices[c + ++offset * numColumns];

            if (orig_i < 0)
            {
              if (skirtBreaks.back() != skirtVerts->size())
                skirtBreaks.push_back(skirtVerts->size());
            }
            else
            {
              skirtVerts->push_back( (*surfaceVerts)[orig_i] );
              skirtVerts->push_back( (*surfaceVerts)[orig_i] - ((*skirtVectors)[orig_i])*skirtHeight );
              skirtNormals->push_back( (*normals)[orig_i] );             
              skirtNormals->push_back( (*normals)[orig_i] );             


              if ( _texCompositor->requiresUnitTextureSpace() )
              {
                  unifiedSkirtTexCoords->push_back( (*unifiedSurfaceTexCoords)[orig_i] );
                  unifiedSkirtTexCoords->push_back( (*unifiedSurfaceTexCoords)[orig_i] );
              }
              else if ( renderLayers.size() > 0 )
              {
                  for (unsigned int i = 0; i < renderLayers.size(); ++i)
                  {
                      const osg::Vec2& tc = (*renderLayers[i]._texCoords.get())[orig_i];
                      renderLayers[i]._skirtTexCoords->push_back( tc );
                      renderLayers[i]._skirtTexCoords->push_back( tc );
                  }
              }
            }
        }

        // right:
        for( unsigned int r=0; r<numRows-1; ++r )
        {
            int orig_i = indices[r*numColumns+(numColumns-1)];
            if (orig_i < 0)
            {
              if (skirtBreaks.back() != skirtVerts->size())
                skirtBreaks.push_back(skirtVerts->size());
            }
            else
            {
              skirtVerts->push_back( (*surfaceVerts)[orig_i] );
              skirtVerts->push_back( (*surfaceVerts)[orig_i] - ((*skirtVectors)[orig_i])*skirtHeight );
              skirtNormals->push_back( (*normals)[orig_i] );             
              skirtNormals->push_back( (*normals)[orig_i] );             

              if ( _texCompositor->requiresUnitTextureSpace() )
              {
                  unifiedSkirtTexCoords->push_back( (*unifiedSurfaceTexCoords)[orig_i] );
                  unifiedSkirtTexCoords->push_back( (*unifiedSurfaceTexCoords)[orig_i] );
              }
              else if ( renderLayers.size() > 0 )
              {
                  for (unsigned int i = 0; i < renderLayers.size(); ++i)
                  {
                      const osg::Vec2& tc = (*renderLayers[i]._texCoords.get())[orig_i];
                      renderLayers[i]._skirtTexCoords->push_back( tc );
                      renderLayers[i]._skirtTexCoords->push_back( tc );
                  }
              }
            }
        }

        // top:
        for( int c=numColumns-1; c>0; --c )
        {
            int orig_i = indices[(numRows-1)*numColumns+c];
            if (orig_i < 0)
            {
              if (skirtBreaks.back() != skirtVerts->size())
                skirtBreaks.push_back(skirtVerts->size());
            }
            else
            {
              skirtVerts->push_back( (*surfaceVerts)[orig_i] );
              skirtVerts->push_back( (*surfaceVerts)[orig_i] - ((*skirtVectors)[orig_i])*skirtHeight );
              skirtNormals->push_back( (*normals)[orig_i] );             
              skirtNormals->push_back( (*normals)[orig_i] );             

              if ( _texCompositor->requiresUnitTextureSpace() )
              {
                  unifiedSkirtTexCoords->push_back( (*unifiedSurfaceTexCoords)[orig_i] );
                  unifiedSkirtTexCoords->push_back( (*unifiedSurfaceTexCoords)[orig_i] );
              }
              else if ( renderLayers.size() > 0 )
              {
                  for (unsigned int i = 0; i < renderLayers.size(); ++i)
                  {
                      const osg::Vec2& tc = (*renderLayers[i]._texCoords.get())[orig_i];
                      renderLayers[i]._skirtTexCoords->push_back( tc );
                      renderLayers[i]._skirtTexCoords->push_back( tc );
                  }
              }
            }
        }

        // left:
        for( int r=numRows-1; r>=0; --r )
        {
            int orig_i = indices[r*numColumns];
            if (orig_i < 0)
            {
              if (skirtBreaks.back() != skirtVerts->size())
                skirtBreaks.push_back(skirtVerts->size());
            }
            else
            {
              skirtVerts->push_back( (*surfaceVerts)[orig_i] );
              skirtVerts->push_back( (*surfaceVerts)[orig_i] - ((*skirtVectors)[orig_i])*skirtHeight );              
              skirtNormals->push_back( (*normals)[orig_i] );             
              skirtNormals->push_back( (*normals)[orig_i] );             

              if ( _texCompositor->requiresUnitTextureSpace() )
              {
                  unifiedSkirtTexCoords->push_back( (*unifiedSurfaceTexCoords)[orig_i] );
                  unifiedSkirtTexCoords->push_back( (*unifiedSurfaceTexCoords)[orig_i] );
              }
              else if ( renderLayers.size() > 0 )
              {
                  for (unsigned int i = 0; i < renderLayers.size(); ++i)
                  {
                      const osg::Vec2& tc = (*renderLayers[i]._texCoords.get())[orig_i];
                      renderLayers[i]._skirtTexCoords->push_back( tc );
                      renderLayers[i]._skirtTexCoords->push_back( tc );
                  }
              }
            }
        }

        skirt->setVertexArray( skirtVerts );
        skirt->setNormalArray( skirtNormals );
        skirt->setNormalBinding( osg::Geometry::BIND_PER_VERTEX );

        //Add a primative set for each continuous skirt strip
        skirtBreaks.push_back(skirtVerts->size());
        for (int p=1; p < skirtBreaks.size(); p++)
          skirt->addPrimitiveSet( new osg::DrawArrays( GL_TRIANGLE_STRIP, skirtBreaks[p-1], skirtBreaks[p] - skirtBreaks[p-1] ) );
    }
    
    bool recalcNormals = elevationLayer != NULL;

    //Clear out the normals
    if (recalcNormals)
    {
        osg::Vec3Array::iterator nitr;
        for(nitr = normals->begin();
            nitr!=normals->end();
            ++nitr)
        {
            nitr->set(0.0f,0.0f,0.0f);
        }
    }
    
    for(j=0; j<numRows-1; ++j)
    {
        for(i=0; i<numColumns-1; ++i)
        {
            int i00;
            int i01;
            if (swapOrientation)
            {
                i01 = j*numColumns + i;
                i00 = i01+numColumns;
            }
            else
            {
                i00 = j*numColumns + i;
                i01 = i00+numColumns;
            }

            int i10 = i00+1;
            int i11 = i01+1;

            // remap indices to final vertex positions
            i00 = indices[i00];
            i01 = indices[i01];
            i10 = indices[i10];
            i11 = indices[i11];
            
            unsigned int numValid = 0;
            if (i00>=0) ++numValid;
            if (i01>=0) ++numValid;
            if (i10>=0) ++numValid;
            if (i11>=0) ++numValid;
            
            if (numValid==4)
            {

                                bool VALID = true;
                                for (MaskRecordVector::iterator mr = masks.begin(); mr != masks.end(); ++mr) {
                                        float min_i = (*mr)._ndcMin.x() * (double)(numColumns-1);
                                        float min_j = (*mr)._ndcMin.y() * (double)(numRows-1);
                                        float max_i = (*mr)._ndcMax.x() * (double)(numColumns-1);
                                        float max_j = (*mr)._ndcMax.y() * (double)(numRows-1);

                                        // We test if mask is completely in square
                                        if(i+1 >= min_i && i <= max_i && j+1 >= min_j && j <= max_j) {
                                                VALID = false;
                                                break;
                                        }
                                }

                                if (VALID) {
                                        float e00 = (*elevations)[i00];
                                        float e10 = (*elevations)[i10];
                                        float e01 = (*elevations)[i01];
                                        float e11 = (*elevations)[i11];

                osg::Vec3f &v00 = (*surfaceVerts)[i00];
                osg::Vec3f &v10 = (*surfaceVerts)[i10];
                osg::Vec3f &v01 = (*surfaceVerts)[i01];
                osg::Vec3f &v11 = (*surfaceVerts)[i11];

                if (!_optimizeTriangleOrientation || (e00-e11)<fabsf(e01-e10))
                {
                    elements->push_back(i01);
                    elements->push_back(i00);
                    elements->push_back(i11);

                    elements->push_back(i00);
                    elements->push_back(i10);
                    elements->push_back(i11);

                    if (recalcNormals)
                    {                        
                        osg::Vec3 normal1 = (v00-v01) ^ (v11-v01);
                        (*normals)[i01] += normal1;
                        (*normals)[i00] += normal1;
                        (*normals)[i11] += normal1;

                        osg::Vec3 normal2 = (v10-v00)^(v11-v00);
                        (*normals)[i00] += normal2;
                        (*normals)[i10] += normal2;
                        (*normals)[i11] += normal2;
                    }
                }
                else
                {
                    elements->push_back(i01);
                    elements->push_back(i00);
                    elements->push_back(i10);

                    elements->push_back(i01);
                    elements->push_back(i10);
                    elements->push_back(i11);

                    if (recalcNormals)
                    {                       
                        osg::Vec3 normal1 = (v00-v01) ^ (v10-v01);
                        (*normals)[i01] += normal1;
                        (*normals)[i00] += normal1;
                        (*normals)[i10] += normal1;

                        osg::Vec3 normal2 = (v10-v01)^(v11-v01);
                        (*normals)[i01] += normal2;
                        (*normals)[i10] += normal2;
                        (*normals)[i11] += normal2;
                    }
                }
                                }
            }
            // As skirtPoly is filling the mask bbox, we don't need to create isolated triangle
                        /*else if (numValid==3)
            {
                int validIndices[3];
                int indexPtr = 0;
                if (i00>=0)
                {
                    elements->push_back(i00);
                    validIndices[indexPtr++] = i00;
                }

                if (i01>=0)
                {
                    elements->push_back(i01);
                    validIndices[indexPtr++] = i01;
                }

                if (i11>=0)
                {
                    elements->push_back(i11);
                    validIndices[indexPtr++] = i11;
                }

                if (i10>=0)
                {
                    elements->push_back(i10);
                    validIndices[indexPtr++] = i10;
                }

                if (recalcNormals)
                {
                    osg::Vec3f &v1 = (*surfaceVerts)[validIndices[0]];
                    osg::Vec3f &v2 = (*surfaceVerts)[validIndices[1]];
                    osg::Vec3f &v3 = (*surfaceVerts)[validIndices[2]];
                    osg::Vec3f normal = (v2 - v1) ^ (v3 - v1);
                    (*normals)[validIndices[0]] += normal;
                    (*normals)[validIndices[1]] += normal;
                    (*normals)[validIndices[2]] += normal;
                }
            } */
        }
    }

    //Normalize the normals
    if (recalcNormals)
    {
        osg::Vec3Array::iterator nitr;
        for(nitr = normals->begin();
            nitr!=normals->end();
            ++nitr)
        {
            nitr->normalize();
        }
    }

  

    MeshConsolidator::run( *surface );

    if ( skirt )
        MeshConsolidator::run( *skirt );

    for (MaskRecordVector::iterator mr = masks.begin(); mr != masks.end(); ++mr)
        MeshConsolidator::run( *((*mr)._geom) );
    
   
    if (osgDB::Registry::instance()->getBuildKdTreesHint()==osgDB::ReaderWriter::Options::BUILD_KDTREES &&
        osgDB::Registry::instance()->getKdTreeBuilder())
    {            
        osg::ref_ptr<osg::KdTreeBuilder> builder = osgDB::Registry::instance()->getKdTreeBuilder()->clone();
        geode->accept(*builder);
    }

    return geode;
}

Here is the call graph for this function:

Here is the caller graph for this function:

osg::StateSet * SinglePassTerrainTechnique::createStateSet ( const TileFrame tilef) [private]

Definition at line 449 of file SinglePassTerrainTechnique.cpp.

{
    // establish the tile extent. we will calculate texture coordinate offset/scale based on this
    if ( !_tileExtent.isValid() )
    {
        osg::ref_ptr<GeoLocator> tileLocator = dynamic_cast<GeoLocator*>( tilef._locator.get() ); // _terrainTile->getLocator() );
        if ( tileLocator.valid() )
        {
            if ( tileLocator->getCoordinateSystemType() == osgTerrain::Locator::GEOCENTRIC )
                tileLocator = tileLocator->getGeographicFromGeocentric();

            _tileExtent = tileLocator->getDataExtent();
        }
        _tileKey = tilef._tileKey;
    }

    osg::StateSet* stateSet = new osg::StateSet();
    osg::StateSet* parentStateSet = getParentStateSet();

    for( ColorLayersByUID::const_iterator i = tilef._colorLayers.begin(); i != tilef._colorLayers.end(); ++i )
    {
        const CustomColorLayer& colorLayer = i->second;

        bool isRealData = !colorLayer.isFallbackData();

        GeoImage image;
        if ( createGeoImage( colorLayer, image ) )
        {
            image = _texCompositor->prepareImage( image, _tileExtent );

            _texCompositor->applyLayerUpdate( 
                stateSet, 
                colorLayer.getUID(), 
                image, 
                _tileKey, 
                isRealData ? parentStateSet : 0L );
        }
    }

    return stateSet;
}

Here is the call graph for this function:

Here is the caller graph for this function:

osg::StateSet * SinglePassTerrainTechnique::getActiveStateSet ( ) const

Definition at line 422 of file SinglePassTerrainTechnique.cpp.

{
    OpenThreads::ScopedLock<Mutex> exclusiveLock( const_cast<SinglePassTerrainTechnique*>(this)->_compileMutex );

    osg::StateSet* result = 0L;
    osg::Geode* front = getFrontGeode();
    if ( front ) 
        result = front->getStateSet();
    if ( !result && _backGeode.valid() )
        result = _backGeode->getStateSet();

    return result;
}

Here is the call graph for this function:

Here is the caller graph for this function:

osg::Geode* SinglePassTerrainTechnique::getFrontGeode ( ) const [inline, private]

Definition at line 174 of file SinglePassTerrainTechnique.

                                           {
        if (_transform.valid() && _transform->getNumChildren() > 0)
            return static_cast<osg::Geode*>( _transform->getChild(0) ); 
        return NULL;
    }

Here is the caller graph for this function:

int SinglePassTerrainTechnique::getIndexOfColorLayerWithUID ( UID  uid) const [private]
bool SinglePassTerrainTechnique::getOptimizeTriangleOrientation ( ) const [virtual]

Implements CustomTerrainTechnique.

Definition at line 121 of file SinglePassTerrainTechnique.cpp.

osg::StateSet * SinglePassTerrainTechnique::getParentStateSet ( ) const [private]

Definition at line 437 of file SinglePassTerrainTechnique.cpp.

{
    osg::StateSet* parentStateSet = 0;
    osg::ref_ptr<Tile> parentTile_safe = _parentTile.get();
    if ( parentTile_safe.valid() )
    {
        return static_cast<SinglePassTerrainTechnique*>(_parentTile->getTerrainTechnique())->getActiveStateSet();
    }
    else return 0L;
}

Here is the call graph for this function:

Here is the caller graph for this function:

float SinglePassTerrainTechnique::getVerticalScaleOverride ( ) const

Gets the overriden vertical scale value.

Definition at line 109 of file SinglePassTerrainTechnique.cpp.

void SinglePassTerrainTechnique::init ( ) [virtual]

Implements TerrainTechnique.

Definition at line 127 of file SinglePassTerrainTechnique.cpp.

Here is the call graph for this function:

SinglePassTerrainTechnique::META_Object ( osgEarth  ,
SinglePassTerrainTechnique   
)
void SinglePassTerrainTechnique::prepareImageLayerUpdate ( int  layerIndex,
const TileFrame tilef 
) [private]

Definition at line 382 of file SinglePassTerrainTechnique.cpp.

{
    CustomColorLayer layer;
    if ( tilef.getCustomColorLayer( layerUID, layer ) )
    {
        GeoImage geoImage, secondaryImage;

        if ( createGeoImage( layer, geoImage ) )
        {
            ImageLayerUpdate update;
            
            update._image = _texCompositor->prepareImage( geoImage, _tileExtent );
            update._layerUID = layerUID;
            update._isRealData = !layer.isFallbackData();

            if ( update._image.valid() )
                _pendingImageLayerUpdates.push( update );
        }

    }
}

Here is the call graph for this function:

Here is the caller graph for this function:

void SinglePassTerrainTechnique::releaseGLObjects ( osg::State *  state = 0) const [virtual]

If State is non-zero, this function releases any associated OpenGL objects for the specified graphics context. Otherwise, releases OpenGL objects for all graphics contexts.

Implements TerrainTechnique.

Definition at line 1743 of file SinglePassTerrainTechnique.cpp.

{
    SinglePassTerrainTechnique* ncThis = const_cast<SinglePassTerrainTechnique*>(this);

    Threading::ScopedWriteLock lock( static_cast<Tile*>(ncThis->_tile)->getTileLayersMutex() );

    if ( _transform.valid() )
    {
        _transform->releaseGLObjects( state );
    }

    if ( _backGeode.valid() )
    {
        _backGeode->releaseGLObjects(state);
        ncThis->_backGeode = 0L;
    }
}
void SinglePassTerrainTechnique::setOptimizeTriangleOrientation ( bool  optimizeTriangleOrientation) [virtual]

Sets whether to try to optimize the triangle orientation based on the elevation values. If false,

Implements CustomTerrainTechnique.

Definition at line 115 of file SinglePassTerrainTechnique.cpp.

{
    _optimizeTriangleOrientation = optimizeTriangleOrientation;
}
void SinglePassTerrainTechnique::setParentTile ( Tile tile) [inline, virtual]

Implements CustomTerrainTechnique.

Definition at line 84 of file SinglePassTerrainTechnique.

{ _parentTile = tile; }
void SinglePassTerrainTechnique::setVerticalScaleOverride ( float  value)

Sets a factor by which to scale elevation height values. By default, this object will get the vertical scale from the osgTerrain::Terrain with which the tile is associated. Setting this value overrides that (or sets it if there is no terrain).

Definition at line 103 of file SinglePassTerrainTechnique.cpp.

void SinglePassTerrainTechnique::traverse ( osg::NodeVisitor &  nv) [virtual]

Traverse the terrain subgraph.

Implements TerrainTechnique.

Definition at line 1731 of file SinglePassTerrainTechnique.cpp.

{
    if ( !_tile )
        return;

    if ( _transform.valid() )
    {
        _transform->accept( nv );
    }
}

Member Data Documentation

osg::ref_ptr<osg::Geode> SinglePassTerrainTechnique::_backGeode [private]

Definition at line 135 of file SinglePassTerrainTechnique.

Definition at line 137 of file SinglePassTerrainTechnique.

OpenThreads::Mutex SinglePassTerrainTechnique::_compileMutex [private]

Definition at line 132 of file SinglePassTerrainTechnique.

Definition at line 130 of file SinglePassTerrainTechnique.

Definition at line 164 of file SinglePassTerrainTechnique.

osg::ref_ptr<osg::Uniform> SinglePassTerrainTechnique::_imageLayerStampUniform [private]

Definition at line 136 of file SinglePassTerrainTechnique.

Definition at line 141 of file SinglePassTerrainTechnique.

Definition at line 155 of file SinglePassTerrainTechnique.

Definition at line 139 of file SinglePassTerrainTechnique.

Definition at line 161 of file SinglePassTerrainTechnique.

osg::observer_ptr<Tile> SinglePassTerrainTechnique::_parentTile [private]

Definition at line 184 of file SinglePassTerrainTechnique.

Definition at line 142 of file SinglePassTerrainTechnique.

Definition at line 143 of file SinglePassTerrainTechnique.

Definition at line 151 of file SinglePassTerrainTechnique.

Definition at line 163 of file SinglePassTerrainTechnique.

Definition at line 158 of file SinglePassTerrainTechnique.

Definition at line 159 of file SinglePassTerrainTechnique.

osg::ref_ptr<osg::MatrixTransform> SinglePassTerrainTechnique::_transform [private]

Definition at line 134 of file SinglePassTerrainTechnique.

Definition at line 138 of file SinglePassTerrainTechnique.


The documentation for this class was generated from the following files:
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines