Created
March 16, 2015 19:04
-
-
Save jasonbeverage/3abd5860f7a4b6a25fe5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* -*-c++-*- */ | |
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph | |
* Copyright 2008-2014 Pelican Mapping | |
* http://osgearth.org | |
* | |
* osgEarth is free software; you can redistribute it and/or modify | |
* it under the terms of the GNU Lesser General Public License as published by | |
* the Free Software Foundation; either version 2 of the License, or | |
* (at your option) any later version. | |
* | |
* This program 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. See the | |
* GNU Lesser General Public License for more details. | |
* | |
* You should have received a copy of the GNU Lesser General Public License | |
* along with this program. If not, see <http://www.gnu.org/licenses/> | |
*/ | |
#include "TileModelCompiler" | |
#include "MPGeometry" | |
#include <osgEarth/Locators> | |
#include <osgEarth/Registry> | |
#include <osgEarth/Capabilities> | |
#include <osgEarth/MapFrame> | |
#include <osgEarth/HeightFieldUtils> | |
#include <osgEarth/ImageUtils> | |
#include <osgEarth/Utils> | |
#include <osgEarth/ECEF> | |
#include <osgEarthSymbology/Geometry> | |
#include <osgEarthSymbology/MeshConsolidator> | |
#include <osg/Geode> | |
#include <osg/Geometry> | |
#include <osg/MatrixTransform> | |
#include <osg/GL2Extensions> | |
#include <osg/ComputeBoundsVisitor> | |
#include <osgUtil/DelaunayTriangulator> | |
#include <osgUtil/Optimizer> | |
#include <osgUtil/MeshOptimizers> | |
#include <osgText/Text> | |
using namespace osgEarth::Drivers::MPTerrainEngine; | |
using namespace osgEarth; | |
using namespace osgEarth::Drivers; | |
using namespace osgEarth::Symbology; | |
#define LC "[TileModelCompiler] " | |
//#define USE_TEXCOORD_CACHE 1 | |
//------------------------------------------------------------------------ | |
osg::ref_ptr<osg::Vec2Array>& | |
CompilerCache::TexCoordArrayCache::get(const osg::Vec4d& mat, | |
unsigned cols, | |
unsigned rows) | |
{ | |
for( iterator i = begin(); i != end(); ++i ) | |
{ | |
CompilerCache::TexCoordTableKey& key = i->first; | |
if ( key._mat == mat && key._cols == cols && key._rows == rows ) | |
{ | |
return i->second; | |
} | |
} | |
CompilerCache::TexCoordTableKey newKey; | |
newKey._mat = mat; | |
newKey._cols = cols; | |
newKey._rows = rows; | |
this->push_back( std::make_pair(newKey, (osg::Vec2Array*)0L) ); | |
return this->back().second; | |
} | |
namespace | |
{ | |
struct AllocateBufferObjectsVisitor : public osg::NodeVisitor | |
{ | |
public: | |
AllocateBufferObjectsVisitor(): | |
osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) | |
{ | |
} | |
void apply(osg::Geode& geode) | |
{ | |
for(unsigned i=0; i<geode.getNumDrawables(); ++i) | |
{ | |
osg::Geometry* geom = geode.getDrawable(i)->asGeometry(); | |
if ( geom ) | |
{ | |
// We disable vbo's and then re-enable them to enable sharing of all the arrays. | |
geom->setUseVertexBufferObjects( false ); | |
geom->setUseVertexBufferObjects( true ); | |
} | |
} | |
} | |
}; | |
} | |
//------------------------------------------------------------------------ | |
#define MATCH_TOLERANCE 0.000001 | |
namespace | |
{ | |
// Data for a single renderable color layer | |
struct RenderLayer | |
{ | |
TileModel::ColorData _layer; | |
TileModel::ColorData _layerParent; | |
osg::ref_ptr<const GeoLocator> _locator; | |
osg::ref_ptr<osg::Vec2Array> _texCoords; | |
osg::ref_ptr<osg::Vec2Array> _stitchTexCoords; | |
bool _ownsTexCoords; | |
RenderLayer() : | |
_ownsTexCoords( false ) { } | |
}; | |
typedef std::vector< RenderLayer > RenderLayerVector; | |
// Record that stores the data for a single masking region. | |
struct MaskRecord | |
{ | |
osg::ref_ptr<osg::Vec3dArray> _boundary; | |
osg::Vec3d _ndcMin, _ndcMax; | |
osg::ref_ptr<MPGeometry> _geom; | |
osg::ref_ptr<osg::Vec3Array> _internal; | |
MaskRecord(osg::Vec3dArray* boundary, osg::Vec3d& ndcMin, osg::Vec3d& ndcMax, MPGeometry* geom) | |
: _boundary(boundary), _ndcMin(ndcMin), _ndcMax(ndcMax), _geom(geom), _internal(new osg::Vec3Array()) { } | |
}; | |
typedef std::vector<MaskRecord> MaskRecordVector; | |
typedef std::vector<int> Indices; | |
struct Data | |
{ | |
Data(const TileModel* in_model, | |
const MapFrame& in_frame, | |
const MaskLayerVector& in_maskLayers, | |
const ModelLayerVector& in_modelLayers) | |
: model ( in_model ), | |
frame ( in_frame ), | |
maskLayers( in_maskLayers ), | |
modelLayers( in_modelLayers ) | |
{ | |
surfaceGeode = 0L; | |
surface = 0L; | |
heightScale = 1.0f; | |
heightOffset = 0.0f; | |
createSkirt = false; | |
i_sampleFactor = 1.0f; | |
j_sampleFactor = 1.0f; | |
useVBOs = true; | |
textureImageUnit = 0; | |
renderTileCoords = 0L; | |
ownsTileCoords = false; | |
stitchTileCoords = 0L; | |
//stitchGeom = 0L; | |
installParentData = false; | |
} | |
osg::Matrixd local2world, world2local; | |
const MapFrame& frame; | |
bool useVBOs; | |
int textureImageUnit; | |
const TileModel* model; // the tile's data model | |
osg::ref_ptr<const TileModel> parentModel; // parent model reference | |
bool installParentData; // whether to install parent colors/normals for blending | |
const MaskLayerVector& maskLayers; // map-global masking layer set | |
const ModelLayerVector& modelLayers; // model layers with masks set | |
osg::ref_ptr<GeoLocator> geoLocator; // tile locator adjusted to geographic | |
osg::Vec3d centerModel; // tile center in model (world) coords | |
RenderLayerVector renderLayers; | |
osg::ref_ptr<osg::Vec2Array> renderTileCoords; | |
bool ownsTileCoords; | |
// tile coords for masked areas; always owned (never shared) | |
osg::ref_ptr<osg::Vec2Array> stitchTileCoords; | |
// surface data: | |
osg::Geode* surfaceGeode; | |
MPGeometry* surface; | |
osg::Vec3Array* surfaceVerts; | |
osg::Vec3Array* normals; | |
osg::Vec4Array* surfaceAttribs; | |
osg::Vec4Array* surfaceAttribs2; | |
unsigned numVerticesInSurface; | |
osg::ref_ptr<osg::FloatArray> elevations; | |
Indices indices; | |
osg::BoundingSphere surfaceBound; | |
// skirt data: | |
unsigned numVerticesInSkirt; | |
bool createSkirt; | |
// sampling grid parameters: | |
unsigned numRows; | |
unsigned numCols; | |
double i_sampleFactor; | |
double j_sampleFactor; | |
double heightScale; | |
double heightOffset; | |
unsigned originalNumRows; | |
unsigned originalNumCols; | |
// for masking/stitching: | |
MaskRecordVector maskRecords; | |
//MPGeometry* stitchGeom; | |
bool useUInt; | |
osg::DrawElements* newDrawElements(GLenum mode) { | |
osg::DrawElements* de = 0L; | |
if ( useUInt ) | |
de = new osg::DrawElementsUInt(mode); | |
else | |
de = new osg::DrawElementsUShort(mode); | |
de->setName("TMC"); | |
return de; | |
} | |
}; | |
/** | |
* Set up an single masking geometry. Called by setupMaskRecords | |
*/ | |
void setupMaskRecord(Data& d, osg::Vec3dArray* boundary) | |
{ | |
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; | |
d.geoLocator->modelToUnit(min, min_ndc); | |
d.geoLocator->modelToUnit(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) | |
{ | |
MPGeometry* stitchGeom = new MPGeometry( d.model->_tileKey, d.frame, d.textureImageUnit ); | |
stitchGeom->setName("stitchGeom"); | |
d.maskRecords.push_back( MaskRecord(boundary, min_ndc, max_ndc, stitchGeom) ); | |
} | |
} | |
} | |
/** | |
* Set up the masking records for this build. Here we check all the map's mask layer | |
* boundary geometries and find any that intersect the current tile. For an intersection | |
* we create a MaskRecord that we'll use later in the process. | |
*/ | |
void setupMaskRecords(Data& d) | |
{ | |
// 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 = d.heightScale; | |
if (d.model->_tileLocator->getCoordinateSystemType() == osgEarth::GeoLocator::GEOGRAPHIC) | |
{ | |
scale = d.heightScale / 111319.0f; | |
} | |
for(MaskLayerVector::const_iterator it = d.maskLayers.begin(); | |
it != d.maskLayers.end(); | |
++it) | |
{ | |
MaskLayer* layer = it->get(); | |
if ( layer->getMinLevel() <= d.model->_tileKey.getLevelOfDetail() ) | |
{ | |
setupMaskRecord( d, layer->getOrCreateMaskBoundary( | |
scale, | |
d.model->_tileLocator->getDataExtent().getSRS(), | |
(ProgressCallback*)0L ) ); | |
} | |
} | |
for(ModelLayerVector::const_iterator it = d.modelLayers.begin(); | |
it != d.modelLayers.end(); | |
++it) | |
{ | |
ModelLayer* layer = it->get(); | |
if (layer->getMaskSource() && | |
layer->getMaskMinLevel() <= d.model->_tileKey.getLevelOfDetail() ) | |
{ | |
setupMaskRecord( d, layer->getOrCreateMaskBoundary( | |
scale, | |
d.model->_tileLocator->getDataExtent().getSRS(), | |
(ProgressCallback*)0L ) ); | |
} | |
} | |
} | |
/** | |
* Calculates the sample rate and allocates all the vertex, normal, and color | |
* arrays for the tile. | |
*/ | |
void setupGeometryAttributes( Data& d, int tileSize ) | |
{ | |
d.numRows = 17; | |
d.numCols = 17; | |
d.originalNumRows = 17; | |
d.originalNumCols = 17; | |
// read the row/column count and skirt size from the model: | |
osg::HeightField* hf = d.model->_elevationData.getHeightField(); | |
if ( hf ) | |
{ | |
d.numCols = hf->getNumColumns(); | |
d.numRows = hf->getNumRows(); | |
d.originalNumCols = d.numCols; | |
d.originalNumRows = d.numRows; | |
} | |
// calculate the elevation sampling factors that we'll use to step though | |
// the tile's NDC space. | |
d.i_sampleFactor = 1.0f; | |
d.j_sampleFactor = 1.0f; | |
if ( tileSize > 0 ) | |
{ | |
d.numCols = tileSize; | |
d.numRows = tileSize; | |
d.i_sampleFactor = double(d.originalNumCols-1)/double(d.numCols-1); | |
d.j_sampleFactor = double(d.originalNumRows-1)/double(d.numRows-1); | |
} | |
// calculate the total number of verts: | |
d.numVerticesInSkirt = d.createSkirt ? (2 * (d.numCols*2 + d.numRows*2 - 4)) : 0; | |
d.numVerticesInSurface = d.numCols * d.numRows + d.numVerticesInSkirt; | |
// allocate and assign vertices | |
d.surfaceVerts = new osg::Vec3Array(); | |
d.surfaceVerts->reserve( d.numVerticesInSurface ); | |
d.surface->setVertexArray( d.surfaceVerts ); | |
// allocate and assign normals | |
d.normals = new osg::Vec3Array(); | |
d.normals->reserve( d.numVerticesInSurface ); | |
d.surface->setNormalArray( d.normals ); | |
d.surface->setNormalBinding( osg::Geometry::BIND_PER_VERTEX ); | |
// vertex attribution | |
// for each vertex, a vec4 containing a unit extrusion vector in [0..2] and the raw elevation in [3] | |
d.surfaceAttribs = new osg::Vec4Array(); | |
d.surfaceAttribs->reserve( d.numVerticesInSurface ); | |
d.surface->setVertexAttribArray( osg::Drawable::ATTRIBUTE_6, d.surfaceAttribs ); | |
d.surface->setVertexAttribBinding( osg::Drawable::ATTRIBUTE_6, osg::Geometry::BIND_PER_VERTEX ); | |
d.surface->setVertexAttribNormalize( osg::Drawable::ATTRIBUTE_6, false ); | |
// for each vertex, index 0 holds the interpolated elevation from the lower lod (for morphing) | |
d.surfaceAttribs2 = new osg::Vec4Array(); | |
d.surfaceAttribs2->reserve( d.numVerticesInSurface ); | |
d.surface->setVertexAttribArray( osg::Drawable::ATTRIBUTE_7, d.surfaceAttribs2 ); | |
d.surface->setVertexAttribBinding( osg::Drawable::ATTRIBUTE_7, osg::Geometry::BIND_PER_VERTEX ); | |
d.surface->setVertexAttribNormalize( osg::Drawable::ATTRIBUTE_7, false ); | |
// temporary data structures for triangulation support | |
d.elevations = new osg::FloatArray(); | |
d.elevations->reserve( d.numVerticesInSurface ); | |
d.indices.resize( d.numVerticesInSurface, -1 ); | |
// Uint required? | |
d.useUInt = d.numVerticesInSurface > 0xFFFF; | |
} | |
/** | |
* Generates the texture coordinate arrays for each layer. | |
*/ | |
void setupTextureAttributes( Data& d, CompilerCache& cache ) | |
{ | |
// Any color entries that have the same Locator will share a texcoord | |
// array, saving on memory. | |
d.renderLayers.reserve( d.model->_colorData.size() ); | |
if ( d.maskRecords.size() > 0 ) | |
{ | |
if ( !d.stitchTileCoords.valid() ) | |
{ | |
d.stitchTileCoords = new osg::Vec2Array(); | |
} | |
} | |
#ifdef USE_TEXCOORD_CACHE | |
// unit tile coords - [0..1] always across the tile. | |
osg::Vec4d idmat; | |
idmat[0] = 0.0; | |
idmat[1] = 0.0; | |
idmat[2] = 1.0; | |
idmat[3] = 1.0; | |
osg::ref_ptr<osg::Vec2Array>& tileCoords = cache._surfaceTexCoordArrays.get( idmat, d.numCols, d.numRows ); | |
if ( !tileCoords.valid() ) | |
{ | |
// Note: anything in the cache must have its own VBO. No sharing! | |
tileCoords = new osg::Vec2Array(); | |
tileCoords->setVertexBufferObject( new osg::VertexBufferObject() ); | |
tileCoords->reserve( d.numVerticesInSurface ); | |
d.ownsTileCoords = true; | |
} | |
d.renderTileCoords = tileCoords.get(); | |
#else // not USE_TEXCOORD_CACHE | |
d.renderTileCoords = new osg::Vec2Array(); | |
d.renderTileCoords->reserve( d.numVerticesInSurface ); | |
d.ownsTileCoords = true; | |
#endif | |
// build a list of "render layers", in rendering order, sharing texture coordinate | |
// arrays wherever possible. | |
for( TileModel::ColorDataByUID::const_iterator i = d.model->_colorData.begin(); i != d.model->_colorData.end(); ++i ) | |
{ | |
const TileModel::ColorData& colorLayer = i->second; | |
RenderLayer r; | |
r._layer = colorLayer; | |
const GeoLocator* locator = r._layer.getLocator(); | |
if ( locator ) | |
{ | |
// if we have no mask records, we can use the texture coordinate array cache. | |
if ( d.maskLayers.size() == 0 && locator->isLinear() ) | |
{ | |
const GeoExtent& locex = locator->getDataExtent(); | |
const GeoExtent& keyex = d.model->_tileKey.getExtent(); | |
osg::Vec4d mat; | |
mat[0] = (keyex.xMin() - locex.xMin())/locex.width(); | |
mat[1] = (keyex.yMin() - locex.yMin())/locex.height(); | |
mat[2] = (keyex.width() / locex.width()); | |
mat[3] = (keyex.height() / locex.height()); | |
//OE_DEBUG << "key=" << d.model->_tileKey.str() << ": off=[" <<mat[0]<< ", " <<mat[1] << "] scale=[" | |
// << mat[2]<< ", " << mat[3] << "]" << std::endl; | |
#ifdef USE_TEXCOORD_CACHE | |
osg::ref_ptr<osg::Vec2Array>& surfaceTexCoords = cache._surfaceTexCoordArrays.get( mat, d.numCols, d.numRows ); | |
if ( !surfaceTexCoords.valid() ) | |
{ | |
// Note: anything in the cache must have its own VBO. No sharing! | |
surfaceTexCoords = new osg::Vec2Array(); | |
surfaceTexCoords->setVertexBufferObject( new osg::VertexBufferObject() ); | |
surfaceTexCoords->reserve( d.numVerticesInSurface ); | |
r._ownsTexCoords = true; | |
} | |
r._texCoords = surfaceTexCoords.get(); | |
#else // not USE_TEXCOORD_CACHE | |
r._texCoords = new osg::Vec2Array(); | |
r._texCoords->reserve( d.numVerticesInSurface ); | |
r._ownsTexCoords = true; | |
#endif | |
} | |
else | |
{ | |
// cannot use the tex coord array cache if there are masking records. | |
r._texCoords = new osg::Vec2Array(); | |
r._texCoords->reserve( d.numVerticesInSurface ); | |
r._ownsTexCoords = true; | |
if ( d.maskRecords.size() > 0 ) | |
{ | |
r._stitchTexCoords = new osg::Vec2Array(); | |
} | |
} | |
// install the locator: | |
r._locator = locator; | |
if ( locator->getCoordinateSystemType() == osgTerrain::Locator::GEOCENTRIC ) | |
{ | |
r._locator = locator->getGeographicFromGeocentric(); | |
} | |
// install the parent color data layer if necessary. | |
if ( d.installParentData ) | |
{ | |
if ( d.parentModel.valid() ) | |
{ | |
if (!d.parentModel->getColorData( r._layer.getUID(), r._layerParent )) | |
{ | |
// If we can't get the color data from the parent that means it doesn't exist, perhaps b/c of a min level setting | |
// So we create a false layer parent with a transparent image so it will fade into the real data. | |
r._layerParent = r._layer; | |
r._layerParent._texture = new osg::Texture2D(ImageUtils::createEmptyImage()); | |
r._layerParent._hasAlpha = true; | |
} | |
} | |
else | |
{ | |
r._layerParent = r._layer; | |
} | |
} | |
d.renderLayers.push_back( r ); | |
// Note that we don't actually assign the tex coord arrays to the geometry yet. | |
// That must wait until the end. See the comments in assignTextureArrays() | |
// to understand why. | |
} | |
else | |
{ | |
OE_WARN << LC << "Found a Locator, but it wasn't a GeoLocator." << std::endl; | |
} | |
} | |
} | |
/** | |
* Iterate over the sampling grid and calculate the vertex positions and normals | |
* for each sampling point. | |
*/ | |
void createSurfaceGeometry( Data& d ) | |
{ | |
d.surfaceBound.init(); | |
//osgTerrain::HeightFieldLayer* elevationLayer = d.model->_elevationData.getHFLayer(); | |
osg::HeightField* hf = d.model->_elevationData.getHeightField(); | |
GeoLocator* hfLocator = d.model->_elevationData.getLocator(); | |
// populate vertex and tex coord arrays | |
for(unsigned j=0; j < d.numRows; ++j) | |
{ | |
for(unsigned i=0; i < d.numCols; ++i) | |
{ | |
unsigned int iv = j*d.numCols + i; | |
osg::Vec3d ndc( ((double)i)/(double)(d.numCols-1), ((double)j)/(double)(d.numRows-1), 0.0); | |
// raw height: | |
float heightValue = 0.0f; | |
bool validValue = true; | |
if ( hf ) | |
{ | |
validValue = d.model->_elevationData.getHeight( ndc, d.model->_tileLocator, heightValue, INTERP_TRIANGULATE ); | |
} | |
ndc.z() = heightValue * d.heightScale + d.heightOffset; | |
if ( !validValue ) | |
{ | |
d.indices[iv] = -1; | |
} | |
// First check whether the sampling point falls within a mask's bounding box. | |
// If so, skip the sampling and mark it as a mask location | |
if ( validValue && d.maskRecords.size() > 0 ) | |
{ | |
double minndcx = d.maskRecords[0]._ndcMin.x(); | |
double minndcy = d.maskRecords[0]._ndcMin.y(); | |
double maxndcx = d.maskRecords[0]._ndcMax.x(); | |
double maxndcy = d.maskRecords[0]._ndcMax.y(); | |
for (int mrs = 1; mrs < d.maskRecords.size(); ++mrs) | |
{ | |
if ( d.maskRecords[mrs]._ndcMin.x()< minndcx) | |
{ | |
minndcx = d.maskRecords[mrs]._ndcMin.x(); | |
} | |
if ( d.maskRecords[mrs]._ndcMin.y()< minndcy) | |
{ | |
minndcy = d.maskRecords[mrs]._ndcMin.y(); | |
} | |
if ( d.maskRecords[mrs]._ndcMax.x()> maxndcx) | |
{ | |
maxndcx = d.maskRecords[mrs]._ndcMax.x(); | |
} | |
if ( d.maskRecords[mrs]._ndcMax.y()> maxndcy) | |
{ | |
maxndcy = d.maskRecords[mrs]._ndcMax.y(); | |
} | |
} | |
if(ndc.x() >= (minndcx) && ndc.x() <= (maxndcx) && | |
ndc.y() >= (minndcy) && ndc.y() <= (maxndcy)) | |
{ | |
validValue = false; | |
d.indices[iv] = -2; | |
} | |
} | |
if ( validValue ) | |
{ | |
d.indices[iv] = d.surfaceVerts->size(); | |
osg::Vec3d model; | |
d.model->_tileLocator->unitToModel( ndc, model ); | |
osg::Vec3d modelLTP = model * d.world2local; | |
(*d.surfaceVerts).push_back(modelLTP); | |
// grow the bounding sphere: | |
d.surfaceBound.expandBy( (*d.surfaceVerts).back() ); | |
// the separate texture space requires separate transformed texcoords for each layer. | |
for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r ) | |
{ | |
if ( r->_ownsTexCoords ) | |
{ | |
if ( !r->_locator->isEquivalentTo( *d.geoLocator.get() ) ) | |
{ | |
osg::Vec3d color_ndc; | |
osgTerrain::Locator::convertLocalCoordBetween( *d.geoLocator.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 ( d.ownsTileCoords ) | |
{ | |
d.renderTileCoords->push_back( osg::Vec2(ndc.x(), ndc.y()) ); | |
} | |
// record the raw elevation value in our float array for later | |
(*d.elevations).push_back(ndc.z()); | |
// compute the local normal (up vector) | |
osg::Vec3d ndc_plus_one(ndc.x(), ndc.y(), ndc.z() + 1.0); | |
osg::Vec3d model_up; | |
d.model->_tileLocator->unitToModel(ndc_plus_one, model_up); | |
model_up = (model_up*d.world2local) - modelLTP; | |
model_up.normalize(); | |
(*d.normals).push_back(model_up); | |
// Calculate and store the "old height", i.e the height value from | |
// the parent LOD. | |
float oldHeightValue = heightValue; | |
osg::Vec3 oldNormal; | |
// This only works if the tile size is an odd number in both directions. | |
if (d.model->_tileKey.getLOD() > 0 && (d.numCols&1) && (d.numRows&1) && d.parentModel.valid()) | |
{ | |
d.parentModel->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), oldHeightValue, INTERP_TRIANGULATE ); | |
d.parentModel->_elevationData.getNormal( ndc, d.model->_tileLocator.get(), oldNormal, INTERP_TRIANGULATE ); | |
} | |
else | |
{ | |
d.model->_elevationData.getNormal(ndc, d.model->_tileLocator.get(), oldNormal, INTERP_TRIANGULATE ); | |
} | |
// first attribute set has the unit extrusion vector and the | |
// raw height value. | |
(*d.surfaceAttribs).push_back( osg::Vec4f( | |
model_up.x(), | |
model_up.y(), | |
model_up.z(), | |
heightValue) ); | |
// second attribute set has the old height value in "w" | |
(*d.surfaceAttribs2).push_back( osg::Vec4f( | |
oldNormal.x(), | |
oldNormal.y(), | |
oldNormal.z(), | |
oldHeightValue ) ); | |
} | |
} | |
} | |
//if ( d.renderLayers[0]._texCoords->size() < d.surfaceVerts->size() ) | |
//{ | |
// OE_WARN << LC << "not good. mask error." << std::endl; | |
//} | |
} | |
/** | |
* If there are masking records, calculate the vertices to bound the masked area | |
* and the internal verticies to populate it. Then build a triangulation of the | |
* area inside the masking bounding box and add this to the surface geode. | |
*/ | |
void createMaskGeometry( Data& d ) | |
{ | |
bool hasElev = d.model->hasElevation(); | |
osg::ref_ptr<osgUtil::DelaunayTriangulator> trig=new osgUtil::DelaunayTriangulator(); | |
std::vector<osg::ref_ptr<osgUtil::DelaunayConstraint> > alldcs; | |
osg::ref_ptr<osg::Vec3Array> coordsArray = new osg::Vec3Array; | |
double minndcx = d.maskRecords[0]._ndcMin.x(); | |
double minndcy = d.maskRecords[0]._ndcMin.y(); | |
double maxndcx = d.maskRecords[0]._ndcMax.x(); | |
double maxndcy = d.maskRecords[0]._ndcMax.y(); | |
for (int mrs = 1; mrs < d.maskRecords.size(); ++mrs) | |
{ | |
if ( d.maskRecords[mrs]._ndcMin.x()< minndcx) | |
{ | |
minndcx = d.maskRecords[mrs]._ndcMin.x(); | |
} | |
if ( d.maskRecords[mrs]._ndcMin.y()< minndcy) | |
{ | |
minndcy = d.maskRecords[mrs]._ndcMin.y(); | |
} | |
if ( d.maskRecords[mrs]._ndcMax.x()> maxndcx) | |
{ | |
maxndcx = d.maskRecords[mrs]._ndcMax.x(); | |
} | |
if ( d.maskRecords[mrs]._ndcMax.y()> maxndcy) | |
{ | |
maxndcy = d.maskRecords[mrs]._ndcMax.y(); | |
} | |
} | |
int min_i = (int)floor(minndcx * (double)(d.numCols-1)); | |
if (min_i < 0) min_i = 0; | |
if (min_i >= (int)d.numCols) min_i = d.numCols - 1; | |
int min_j = (int)floor(minndcy * (double)(d.numRows-1)); | |
if (min_j < 0) min_j = 0; | |
if (min_j >= (int)d.numRows) min_j = d.numRows - 1; | |
int max_i = (int)ceil(maxndcx * (double)(d.numCols-1)); | |
if (max_i < 0) max_i = 0; | |
if (max_i >= (int)d.numCols) max_i = d.numCols - 1; | |
int max_j = (int)ceil(maxndcy * (double)(d.numRows-1)); | |
if (max_j < 0) max_j = 0; | |
if (max_j >= (int)d.numRows) max_j = d.numRows - 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<Polygon> maskSkirtPoly = new 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)(d.numCols-1), ((double)min_j)/(double)(d.numRows-1), 0.0); | |
//if (elevationLayer) | |
if ( hasElev ) | |
{ | |
float value = 0.0f; | |
if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) ) | |
ndc.z() = value * d.heightScale + d.heightOffset; | |
} | |
(*maskSkirtPoly)[i] = ndc; | |
} | |
//index = indices[max_j*numColumns + i + min_i]; | |
{ | |
osg::Vec3d ndc( ((double)(i + min_i))/(double)(d.numCols-1), ((double)max_j)/(double)(d.numRows-1), 0.0); | |
if ( hasElev ) | |
{ | |
float value = 0.0f; | |
if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) ) | |
ndc.z() = value * d.heightScale + d.heightOffset; | |
} | |
(*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)(d.numCols-1), ((double)(min_j + j + 1))/(double)(d.numRows-1), 0.0); | |
if ( hasElev ) | |
{ | |
float value = 0.0f; | |
if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) ) | |
ndc.z() = value * d.heightScale + d.heightOffset; | |
} | |
(*maskSkirtPoly)[j + num_i] = ndc; | |
} | |
//index = indices[(min_j + j + 1)*numColumns + min_i]; | |
{ | |
osg::Vec3d ndc( ((double)min_i)/(double)(d.numCols-1), ((double)(min_j + j + 1))/(double)(d.numRows-1), 0.0); | |
if ( hasElev ) | |
{ | |
float value = 0.0f; | |
if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) ) | |
ndc.z() = value * d.heightScale + d.heightOffset; | |
} | |
(*maskSkirtPoly)[j + (2 * num_i + 2 * num_j - 5) - 2 * j] = ndc; | |
} | |
} | |
for (int j = 0; j < num_j; j++) | |
{ | |
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)(d.numCols-1), ((double)(j+min_j))/(double)(d.numRows-1), 0.0); | |
//if (elevationLayer) | |
if ( hasElev ) | |
{ | |
float value = 0.0f; | |
if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) ) | |
ndc.z() = value * d.heightScale + d.heightOffset; | |
} | |
coordsArray->push_back(ndc) ; | |
} | |
} | |
} | |
// Use delaunay triangulation for stitching: | |
for (MaskRecordVector::iterator mr = d.maskRecords.begin();mr != d.maskRecords.end();mr++) | |
{ | |
// Add the outter stitching bounds to the collection of vertices to be used for triangulation | |
// coordsArray->insert(coordsArray->end(), (*mr)._internal->begin(), (*mr)._internal->end()); | |
//Create local polygon representing mask | |
osg::ref_ptr<Polygon> maskPoly = new Polygon(); | |
for (osg::Vec3dArray::iterator it = (*mr)._boundary->begin(); it != (*mr)._boundary->end(); ++it) | |
{ | |
osg::Vec3d local; | |
d.geoLocator->convertModelToLocal(*it, local); | |
maskPoly->push_back(local); | |
} | |
// Add mask bounds as a triangulation constraint | |
osg::ref_ptr<osgUtil::DelaunayConstraint> newdc=new osgUtil::DelaunayConstraint; | |
osg::Vec3Array* maskConstraint = new osg::Vec3Array(); | |
newdc->setVertexArray(maskConstraint); | |
//Crop the mask to the stitching poly (for case where mask crosses tile edge) | |
osg::ref_ptr<Geometry> maskCrop; | |
maskPoly->crop(maskSkirtPoly.get(), maskCrop); | |
GeometryIterator i( maskCrop.get(), false ); | |
while( i.hasMore() ) | |
{ | |
Geometry* part = i.next(); | |
if (!part) | |
continue; | |
if (part->getType() == Geometry::TYPE_POLYGON) | |
{ | |
osg::Vec3Array* partVerts = part->toVec3Array(); | |
maskConstraint->insert(maskConstraint->end(), partVerts->begin(), partVerts->end()); | |
newdc->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_LOOP, maskConstraint->size() - partVerts->size(), partVerts->size())); | |
} | |
} | |
// Cropping strips z-values so need reassign | |
std::vector<int> isZSet; | |
for (osg::Vec3Array::iterator it = maskConstraint->begin(); it != maskConstraint->end(); ++it) | |
{ | |
int zSet = 0; | |
//Look for verts that belong to the original mask skirt polygon | |
for (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; | |
// Remove duplicate point from coordsArray to avoid duplicate point warnings | |
osg::Vec3Array::iterator caIt; | |
for (caIt = coordsArray->begin(); caIt != coordsArray->end(); ++caIt) | |
{ | |
if (osg::absolute((*caIt).x() - (*it).x()) < MATCH_TOLERANCE && osg::absolute((*caIt).y() - (*it).y()) < MATCH_TOLERANCE) | |
break; | |
} | |
if (caIt != coordsArray->end()) | |
coordsArray->erase(caIt); | |
break; | |
} | |
} | |
//Look for verts that belong to the mask polygon | |
for (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. | |
int count = 0; | |
for (osg::Vec3Array::iterator it = maskConstraint->begin(); it != maskConstraint->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) | |
if (!isZSet[count]) | |
{ | |
osg::Vec3d p2 = *it; | |
double closestZ = 0.0; | |
double closestRatio = DBL_MAX; | |
for (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 mask constraint vertex" << std::endl; | |
count++; | |
} | |
alldcs.push_back(newdc); | |
} | |
//coordsArray->insert(coordsArray->end(),maskSkirtPoly->begin(),maskSkirtPoly->end()); | |
trig->setInputPointArray(coordsArray.get()); | |
for (int dcnum =0; dcnum < alldcs.size();dcnum++) | |
{ | |
trig->addInputConstraint(alldcs[dcnum].get()); | |
} | |
// Create array to hold vertex normals | |
osg::Vec3Array *norms=new osg::Vec3Array; | |
trig->setOutputNormalArray(norms); | |
// Triangulate vertices and remove triangles that lie within the contraint loop | |
trig->triangulate(); | |
for (int dcnum =0; dcnum < alldcs.size();dcnum++) | |
{ | |
trig->removeInternalTriangles(alldcs[dcnum].get()); | |
} | |
MaskRecordVector::iterator mr = d.maskRecords.begin(); | |
// Set up new arrays to hold final vertices and normals | |
osg::Geometry* stitch_geom = (*mr)._geom; | |
osg::Vec3Array* stitch_verts = new osg::Vec3Array(); | |
stitch_verts->reserve(trig->getInputPointArray()->size()); | |
stitch_geom->setVertexArray(stitch_verts); | |
osg::Vec3Array* stitch_norms = new osg::Vec3Array(trig->getInputPointArray()->size()); | |
stitch_geom->setNormalArray( stitch_norms ); | |
stitch_geom->setNormalBinding( osg::Geometry::BIND_PER_VERTEX ); | |
//Initialize tex coords | |
if ( d.renderLayers.size() > 0 ) | |
{ | |
for (unsigned int i = 0; i < d.renderLayers.size(); ++i) | |
{ | |
d.renderLayers[i]._stitchTexCoords->reserve(trig->getInputPointArray()->size()); | |
} | |
} | |
d.stitchTileCoords->reserve(trig->getInputPointArray()->size()); | |
// Iterate through point to convert to model coords, calculate normals, and set up tex coords | |
int norm_i = -1; | |
for (osg::Vec3Array::iterator it = trig->getInputPointArray()->begin(); it != trig->getInputPointArray()->end(); ++it) | |
{ | |
// get model coords | |
osg::Vec3d model; | |
d.model->_tileLocator->convertLocalToModel(*it, model); | |
model = model * d.world2local; | |
stitch_verts->push_back(model); | |
// calc normals | |
osg::Vec3d local_one(*it); | |
local_one.z() += 1.0; | |
osg::Vec3d model_one; | |
d.model->_tileLocator->convertLocalToModel( local_one, model_one ); | |
model_one = (model_one*d.world2local) - model; | |
model_one.normalize(); | |
(*stitch_norms)[++norm_i] = model_one; | |
// set up text coords | |
if (d.renderLayers.size() > 0) | |
{ | |
for (unsigned int i = 0; i < d.renderLayers.size(); ++i) | |
{ | |
if (!d.renderLayers[i]._locator->isEquivalentTo( *d.geoLocator.get() )) //*masterTextureLocator.get())) | |
{ | |
osg::Vec3d color_ndc; | |
osgTerrain::Locator::convertLocalCoordBetween(*d.geoLocator.get(), (*it), *d.renderLayers[i]._locator.get(), color_ndc); | |
d.renderLayers[i]._stitchTexCoords->push_back(osg::Vec2(color_ndc.x(), color_ndc.y())); | |
} | |
else | |
{ | |
d.renderLayers[i]._stitchTexCoords->push_back(osg::Vec2((*it).x(), (*it).y())); | |
} | |
} | |
} | |
d.stitchTileCoords->push_back(osg::Vec2((*it).x(), (*it).y())); | |
} | |
// Get triangles from triangulator and add as primative set to the geometry | |
osg::DrawElementsUInt* tris = trig->getTriangles(); | |
if ( tris && tris->getNumIndices() >= 3 ) | |
{ | |
stitch_geom->addPrimitiveSet(tris); | |
} | |
// Finally, add it to the geode. | |
if (stitch_geom->getVertexArray() && | |
stitch_geom->getVertexArray()->getNumElements() > 0 ) | |
{ | |
d.surfaceGeode->addDrawable(stitch_geom); | |
} | |
} | |
} | |
/** | |
* Build the geometry for the tile "skirts" -- this the vertical geometry around the | |
* tile edges that hides the gap effect caused when you render two adjacent tiles at | |
* different LODs. | |
*/ | |
void createSkirtGeometry( Data& d, double skirtRatio ) | |
{ | |
// surface normals will double as our skirt extrusion vectors | |
osg::Vec3Array* skirtVectors = d.normals; | |
// find the skirt height | |
double skirtHeight = d.surfaceBound.radius() * skirtRatio; | |
// build the verts first: | |
osg::Vec3Array* skirtVerts = static_cast<osg::Vec3Array*>(d.surface->getVertexArray()); | |
osg::Vec3Array* skirtNormals = static_cast<osg::Vec3Array*>(d.surface->getNormalArray()); | |
osg::Vec4Array* skirtAttribs = static_cast<osg::Vec4Array*>(d.surface->getVertexAttribArray(osg::Drawable::ATTRIBUTE_6)); //new osg::Vec4Array(); | |
osg::Vec4Array* skirtAttribs2 = static_cast<osg::Vec4Array*>(d.surface->getVertexAttribArray(osg::Drawable::ATTRIBUTE_7)); //new osg::Vec4Array(); | |
osg::ref_ptr<osg::DrawElements> elements = d.newDrawElements(GL_TRIANGLE_STRIP); | |
// bottom: | |
for( unsigned int c=0; c<d.numCols-1; ++c ) | |
{ | |
int orig_i = d.indices[c]; | |
if (orig_i < 0) | |
{ | |
if ( elements->getNumIndices() > 0 ) | |
{ | |
d.surface->addPrimitiveSet( elements.get() ); | |
elements = d.newDrawElements(GL_TRIANGLE_STRIP); | |
} | |
} | |
else | |
{ | |
const osg::Vec3f& surfaceVert = (*d.surfaceVerts)[orig_i]; | |
skirtVerts->push_back( surfaceVert - ((*skirtVectors)[orig_i])*skirtHeight ); | |
const osg::Vec3f& surfaceNormal = (*d.normals)[orig_i]; | |
skirtNormals->push_back( surfaceNormal ); | |
const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i]; | |
skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) ); | |
const osg::Vec4f& surfaceAttribs2 = (*d.surfaceAttribs2)[orig_i]; | |
skirtAttribs2->push_back( surfaceAttribs2 - osg::Vec4f(0,0,0,skirtHeight) ); | |
if ( d.renderLayers.size() > 0 ) | |
{ | |
for (unsigned int i = 0; i < d.renderLayers.size(); ++i) | |
{ | |
if ( d.renderLayers[i]._ownsTexCoords ) | |
{ | |
const osg::Vec2& tc = (*d.renderLayers[i]._texCoords.get())[orig_i]; | |
d.renderLayers[i]._texCoords->push_back( tc ); | |
} | |
} | |
} | |
const osg::Vec2& tilec = (*d.renderTileCoords.get())[orig_i]; | |
d.renderTileCoords->push_back( tilec ); | |
elements->addElement(orig_i); | |
elements->addElement(skirtVerts->size()-1); | |
} | |
} | |
// right: | |
for( unsigned int r=0; r<d.numRows-1; ++r ) | |
{ | |
int orig_i = d.indices[r*d.numCols+(d.numCols-1)]; | |
if (orig_i < 0) | |
{ | |
if ( elements->getNumIndices() > 0 ) | |
{ | |
d.surface->addPrimitiveSet( elements.get() ); | |
elements = d.newDrawElements(GL_TRIANGLE_STRIP); | |
} | |
} | |
else | |
{ | |
const osg::Vec3f& surfaceVert = (*d.surfaceVerts)[orig_i]; | |
skirtVerts->push_back( surfaceVert - ((*skirtVectors)[orig_i])*skirtHeight ); | |
const osg::Vec3f& surfaceNormal = (*d.normals)[orig_i]; | |
skirtNormals->push_back( surfaceNormal ); | |
const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i]; | |
skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) ); | |
const osg::Vec4f& surfaceAttribs2 = (*d.surfaceAttribs2)[orig_i]; | |
skirtAttribs2->push_back( surfaceAttribs2 - osg::Vec4f(0,0,0,skirtHeight) ); | |
if ( d.renderLayers.size() > 0 ) | |
{ | |
for (unsigned int i = 0; i < d.renderLayers.size(); ++i) | |
{ | |
if ( d.renderLayers[i]._ownsTexCoords ) | |
{ | |
const osg::Vec2& tc = (*d.renderLayers[i]._texCoords.get())[orig_i]; | |
d.renderLayers[i]._texCoords->push_back( tc ); | |
} | |
} | |
} | |
const osg::Vec2& tilec = (*d.renderTileCoords.get())[orig_i]; | |
d.renderTileCoords->push_back( tilec ); | |
elements->addElement(orig_i); | |
elements->addElement(skirtVerts->size()-1); | |
} | |
} | |
// top: | |
for( int c=d.numCols-1; c>0; --c ) | |
{ | |
int orig_i = d.indices[(d.numRows-1)*d.numCols+c]; | |
if (orig_i < 0) | |
{ | |
if ( elements->getNumIndices() > 0 ) | |
{ | |
d.surface->addPrimitiveSet( elements.get() ); | |
elements = d.newDrawElements(GL_TRIANGLE_STRIP); | |
} | |
} | |
else | |
{ | |
const osg::Vec3f& surfaceVert = (*d.surfaceVerts)[orig_i]; | |
skirtVerts->push_back( surfaceVert - ((*skirtVectors)[orig_i])*skirtHeight ); | |
const osg::Vec3f& surfaceNormal = (*d.normals)[orig_i]; | |
skirtNormals->push_back( surfaceNormal ); | |
const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i]; | |
skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) ); | |
const osg::Vec4f& surfaceAttribs2 = (*d.surfaceAttribs2)[orig_i]; | |
skirtAttribs2->push_back( surfaceAttribs2 - osg::Vec4f(0,0,0,skirtHeight) ); | |
if ( d.renderLayers.size() > 0 ) | |
{ | |
for (unsigned int i = 0; i < d.renderLayers.size(); ++i) | |
{ | |
if ( d.renderLayers[i]._ownsTexCoords ) | |
{ | |
const osg::Vec2& tc = (*d.renderLayers[i]._texCoords.get())[orig_i]; | |
d.renderLayers[i]._texCoords->push_back( tc ); | |
} | |
} | |
} | |
const osg::Vec2& tilec = (*d.renderTileCoords.get())[orig_i]; | |
d.renderTileCoords->push_back( tilec ); | |
elements->addElement(orig_i); | |
elements->addElement(skirtVerts->size()-1); | |
} | |
} | |
// left: | |
for( int r=d.numRows-1; r>=0; --r ) | |
{ | |
int orig_i = d.indices[r*d.numCols]; | |
if (orig_i < 0) | |
{ | |
if ( elements->getNumIndices() > 0 ) | |
{ | |
d.surface->addPrimitiveSet( elements.get() ); | |
elements = d.newDrawElements(GL_TRIANGLE_STRIP); | |
} | |
} | |
else | |
{ | |
const osg::Vec3f& surfaceVert = (*d.surfaceVerts)[orig_i]; | |
skirtVerts->push_back( surfaceVert - ((*skirtVectors)[orig_i])*skirtHeight ); | |
const osg::Vec3f& surfaceNormal = (*d.normals)[orig_i]; | |
skirtNormals->push_back( surfaceNormal ); | |
const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i]; | |
skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) ); | |
const osg::Vec4f& surfaceAttribs2 = (*d.surfaceAttribs2)[orig_i]; | |
skirtAttribs2->push_back( surfaceAttribs2 - osg::Vec4f(0,0,0,skirtHeight) ); | |
if ( d.renderLayers.size() > 0 ) | |
{ | |
for (unsigned int i = 0; i < d.renderLayers.size(); ++i) | |
{ | |
if ( d.renderLayers[i]._ownsTexCoords ) | |
{ | |
const osg::Vec2& tc = (*d.renderLayers[i]._texCoords.get())[orig_i]; | |
d.renderLayers[i]._texCoords->push_back( tc ); | |
} | |
} | |
} | |
const osg::Vec2& tilec = (*d.renderTileCoords.get())[orig_i]; | |
d.renderTileCoords->push_back( tilec ); | |
elements->addElement(orig_i); | |
elements->addElement(skirtVerts->size()-1); | |
} | |
} | |
// add the final prim set. | |
if ( elements->getNumIndices() > 0 ) | |
{ | |
d.surface->addPrimitiveSet( elements.get() ); | |
} | |
} | |
/** | |
* Builds triangles for the surface geometry, and recalculates the surface normals | |
* to be optimized for slope. | |
*/ | |
void tessellateSurfaceGeometry( Data& d, bool optimizeTriangleOrientation, bool normalizeEdges ) | |
{ | |
bool swapOrientation = !(d.model->_tileLocator->orientationOpenGL()); | |
bool recalcNormals = | |
d.model->hasElevation() && | |
!d.model->hasNormalMap(); | |
unsigned numSurfaceNormals = d.numRows * d.numCols; | |
osg::DrawElements* elements = d.newDrawElements(GL_TRIANGLES); | |
elements->reserveElements((d.numRows-1) * (d.numCols-1) * 6); | |
if ( recalcNormals ) | |
{ | |
// first clear out all the normals on the surface (but not the skirts) | |
// TODO: someday go back and re-apply the skirt normals to match the | |
// corresponding recalculated surface normals. | |
for(unsigned n=0; n<numSurfaceNormals && n<d.normals->size(); ++n) | |
{ | |
(*d.normals)[n].set( 0.0f, 0.0f, 0.0f ); | |
} | |
} | |
for(unsigned j=0; j<d.numRows-1; ++j) | |
{ | |
for(unsigned i=0; i<d.numCols-1; ++i) | |
{ | |
int i00; | |
int i01; | |
if (swapOrientation) | |
{ | |
i01 = j*d.numCols + i; | |
i00 = i01+d.numCols; | |
} | |
else | |
{ | |
i00 = j*d.numCols + i; | |
i01 = i00+d.numCols; | |
} | |
int i10 = i00+1; | |
int i11 = i01+1; | |
// remap indices to final vertex positions | |
i00 = d.indices[i00]; | |
i01 = d.indices[i01]; | |
i10 = d.indices[i10]; | |
i11 = d.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 = d.maskRecords.begin(); mr != d.maskRecords.end(); ++mr) | |
{ | |
float min_i = (*mr)._ndcMin.x() * (double)(d.numCols-1); | |
float min_j = (*mr)._ndcMin.y() * (double)(d.numRows-1); | |
float max_i = (*mr)._ndcMax.x() * (double)(d.numCols-1); | |
float max_j = (*mr)._ndcMax.y() * (double)(d.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 = (*d.elevations)[i00]; | |
float e10 = (*d.elevations)[i10]; | |
float e01 = (*d.elevations)[i01]; | |
float e11 = (*d.elevations)[i11]; | |
osg::Vec3f& v00 = (*d.surfaceVerts)[i00]; | |
osg::Vec3f& v10 = (*d.surfaceVerts)[i10]; | |
osg::Vec3f& v01 = (*d.surfaceVerts)[i01]; | |
osg::Vec3f& v11 = (*d.surfaceVerts)[i11]; | |
if (!optimizeTriangleOrientation || fabsf(e00-e11)<fabsf(e01-e10)) | |
{ | |
elements->addElement(i01); | |
elements->addElement(i00); | |
elements->addElement(i11); | |
elements->addElement(i00); | |
elements->addElement(i10); | |
elements->addElement(i11); | |
if (recalcNormals) | |
{ | |
osg::Vec3 normal1 = (v00-v01) ^ (v11-v01); | |
(*d.normals)[i01] += normal1; | |
(*d.normals)[i00] += normal1; | |
(*d.normals)[i11] += normal1; | |
osg::Vec3 normal2 = (v10-v00) ^ (v11-v00); | |
(*d.normals)[i00] += normal2; | |
(*d.normals)[i10] += normal2; | |
(*d.normals)[i11] += normal2; | |
} | |
} | |
else | |
{ | |
elements->addElement(i01); | |
elements->addElement(i00); | |
elements->addElement(i10); | |
elements->addElement(i01); | |
elements->addElement(i10); | |
elements->addElement(i11); | |
if (recalcNormals) | |
{ | |
osg::Vec3 normal1 = (v00-v01) ^ (v10-v01); | |
(*d.normals)[i01] += normal1; | |
(*d.normals)[i00] += normal1; | |
(*d.normals)[i10] += normal1; | |
osg::Vec3 normal2 = (v10-v01) ^ (v11-v01); | |
(*d.normals)[i01] += normal2; | |
(*d.normals)[i10] += normal2; | |
(*d.normals)[i11] += normal2; | |
} | |
} | |
} | |
} | |
} | |
} | |
if (recalcNormals && normalizeEdges) | |
{ | |
//OE_DEBUG << LC << "Normalizing edges" << std::endl; | |
//Compute the edge normals if we have neighbor data | |
//Get all the neighbors | |
osg::HeightField* w_neighbor = d.model->_elevationData.getNeighbor( -1, 0 ); | |
osg::HeightField* e_neighbor = d.model->_elevationData.getNeighbor( 1, 0 ); | |
osg::HeightField* s_neighbor = d.model->_elevationData.getNeighbor( 0, 1 ); | |
osg::HeightField* n_neighbor = d.model->_elevationData.getNeighbor( 0, -1 ); | |
// Utility arrays: | |
std::vector<osg::Vec3> boundaryVerts; | |
boundaryVerts.reserve( 2 * std::max(d.numRows, d.numCols) ); | |
std::vector< float > boundaryElevations; | |
boundaryElevations.reserve( 2 * std::max(d.numRows, d.numCols) ); | |
//Recalculate the west side | |
if (w_neighbor && w_neighbor->getNumColumns() == d.originalNumCols && w_neighbor->getNumRows() == d.originalNumRows) | |
{ | |
boundaryVerts.clear(); | |
boundaryElevations.clear(); | |
//Compute the verts for the west side | |
for (int j = 0; j < (int)d.numRows; j++) | |
{ | |
for (int i = (int)d.numCols-2; i <= (int)d.numCols-1; i++) | |
{ | |
osg::Vec3d ndc( (double)(i - static_cast<int>(d.numCols-1))/(double)(d.numCols-1), ((double)j)/(double)(d.numRows-1), 0.0); | |
// use the sampling factor to determine the lookup index: | |
unsigned i_equiv = d.i_sampleFactor==1.0 ? i : (unsigned) (double(i)*d.i_sampleFactor); | |
unsigned j_equiv = d.j_sampleFactor==1.0 ? j : (unsigned) (double(j)*d.j_sampleFactor); | |
//TODO: Should probably use an interpolated method here | |
float heightValue = w_neighbor->getHeight( i_equiv, j_equiv ); | |
ndc.z() = heightValue; | |
osg::Vec3d model; | |
d.model->_tileLocator->unitToModel( ndc, model ); | |
osg::Vec3d v = model * d.world2local; //model - d.centerModel; | |
boundaryVerts.push_back( v ); | |
boundaryElevations.push_back( heightValue ); | |
} | |
} | |
//The boundary verts are now populated, so go through and triangulate them add add the normals to the existing normal array | |
for (int j = 0; j < (int)d.numRows-1; j++) | |
{ | |
int i00; | |
int i01; | |
int i = 0; | |
if (swapOrientation) | |
{ | |
i01 = j*d.numCols + i; | |
i00 = i01+d.numCols; | |
} | |
else | |
{ | |
i00 = j*d.numCols + i; | |
i01 = i00+d.numCols; | |
} | |
//remap indices to final vertex position | |
i00 = d.indices[i00]; | |
i01 = d.indices[i01]; | |
if ( i00 >= 0 && i01 >= 0 ) | |
{ | |
int baseIndex = 2 * j; | |
osg::Vec3f& v00 = boundaryVerts[baseIndex ]; | |
osg::Vec3f& v10 = boundaryVerts[baseIndex + 1]; | |
osg::Vec3f& v01 = boundaryVerts[baseIndex + 2]; | |
osg::Vec3f& v11 = boundaryVerts[baseIndex + 3]; | |
float e00 = boundaryElevations[baseIndex]; | |
float e10 = boundaryElevations[baseIndex + 1]; | |
float e01 = boundaryElevations[baseIndex + 2]; | |
float e11 = boundaryElevations[baseIndex + 3]; | |
if (!optimizeTriangleOrientation || fabsf(e00-e11)<fabsf(e01-e10)) | |
{ | |
osg::Vec3 normal1 = (v00-v01) ^ (v11-v01); | |
(*d.normals)[i01] += normal1; | |
osg::Vec3 normal2 = (v10-v00) ^ (v11-v00); | |
(*d.normals)[i00] += normal2; | |
(*d.normals)[i01] += normal2; | |
} | |
else | |
{ | |
osg::Vec3 normal1 = (v00-v01) ^ (v10-v01); | |
(*d.normals)[i00] += normal1; | |
osg::Vec3 normal2 = (v10-v01) ^ (v11-v01); | |
(*d.normals)[i00] += normal2; | |
(*d.normals)[i01] += normal2; | |
} | |
} | |
} | |
} | |
//Recalculate the east side | |
if (e_neighbor && e_neighbor->getNumColumns() == d.originalNumCols && e_neighbor->getNumRows() == d.originalNumRows) | |
{ | |
boundaryVerts.clear(); | |
boundaryElevations.clear(); | |
//Compute the verts for the east side | |
for (int j = 0; j < (int)d.numRows; j++) | |
{ | |
for (int i = 0; i <= 1; i++) | |
{ | |
osg::Vec3d ndc( ((double)(d.numCols -1 + i))/(double)(d.numCols-1), ((double)j)/(double)(d.numRows-1), 0.0); | |
unsigned i_equiv = d.i_sampleFactor==1.0 ? i : (unsigned) (double(i)*d.i_sampleFactor); | |
unsigned j_equiv = d.j_sampleFactor==1.0 ? j : (unsigned) (double(j)*d.j_sampleFactor); | |
//TODO: Should probably use an interpolated method here | |
float heightValue = e_neighbor->getHeight( i_equiv, j_equiv ); | |
ndc.z() = heightValue; | |
osg::Vec3d model; | |
d.model->_tileLocator->unitToModel( ndc, model ); | |
osg::Vec3d v = model * d.world2local; //model - d.centerModel; | |
boundaryVerts.push_back( v ); | |
boundaryElevations.push_back( heightValue ); | |
} | |
} | |
//The boundary verts are now populated, so go through and triangulate them add add the normals to the existing normal array | |
for (int j = 0; j < (int)d.numRows-1; j++) | |
{ | |
int i00; | |
int i01; | |
int i = d.numCols-1; | |
if (swapOrientation) | |
{ | |
i01 = j*d.numCols + i; | |
i00 = i01+d.numCols; | |
} | |
else | |
{ | |
i00 = j*d.numCols + i; | |
i01 = i00+d.numCols; | |
} | |
//remap indices to final vertex position | |
i00 = d.indices[i00]; | |
i01 = d.indices[i01]; | |
if ( i00 >= 0 && i01 >= 0 ) | |
{ | |
int baseIndex = 2 * j; | |
osg::Vec3f& v00 = boundaryVerts[baseIndex ]; | |
osg::Vec3f& v10 = boundaryVerts[baseIndex + 1]; | |
osg::Vec3f& v01 = boundaryVerts[baseIndex + 2]; | |
osg::Vec3f& v11 = boundaryVerts[baseIndex + 3]; | |
float e00 = boundaryElevations[baseIndex]; | |
float e10 = boundaryElevations[baseIndex + 1]; | |
float e01 = boundaryElevations[baseIndex + 2]; | |
float e11 = boundaryElevations[baseIndex + 3]; | |
if (!optimizeTriangleOrientation || fabsf(e00-e11)<fabsf(e01-e10)) | |
{ | |
osg::Vec3 normal1 = (v00-v01) ^ (v11-v01); | |
(*d.normals)[i00] += normal1; | |
(*d.normals)[i01] += normal1; | |
osg::Vec3 normal2 = (v10-v00) ^ (v11-v00); | |
(*d.normals)[i00] += normal2; | |
} | |
else | |
{ | |
osg::Vec3 normal1 = (v00-v01) ^ (v10-v01); | |
(*d.normals)[i00] += normal1; | |
(*d.normals)[i01] += normal1; | |
osg::Vec3 normal2 = (v10-v01) ^ (v11-v01); | |
(*d.normals)[i01] += normal2; | |
} | |
} | |
} | |
} | |
//Recalculate the north side | |
if (n_neighbor && n_neighbor->getNumColumns() == d.originalNumCols && n_neighbor->getNumRows() == d.originalNumRows) | |
{ | |
boundaryVerts.clear(); | |
boundaryElevations.clear(); | |
//Compute the verts for the north side | |
for (int j = 0; j <= 1; j++) | |
{ | |
for (int i = 0; i < (int)d.numCols; i++) | |
{ | |
osg::Vec3d ndc( (double)(i)/(double)(d.numCols-1), (double)(d.numRows -1 + j)/(double)(d.numRows-1), 0.0); | |
unsigned i_equiv = d.i_sampleFactor==1.0 ? i : (unsigned) (double(i)*d.i_sampleFactor); | |
unsigned j_equiv = d.j_sampleFactor==1.0 ? j : (unsigned) (double(j)*d.j_sampleFactor); | |
//TODO: Should probably use an interpolated method here | |
float heightValue = n_neighbor->getHeight( i_equiv, j_equiv ); | |
ndc.z() = heightValue; | |
osg::Vec3d model; | |
d.model->_tileLocator->unitToModel( ndc, model ); | |
osg::Vec3d v = model * d.world2local; //model - d.centerModel; | |
boundaryVerts.push_back( v ); | |
boundaryElevations.push_back( heightValue ); | |
} | |
} | |
//The boundary verts are now populated, so go through and triangulate them add add the normals to the existing normal array | |
for (int i = 0; i < (int)d.numCols-1; i++) | |
{ | |
int i00; | |
int j = d.numRows-1; | |
if (swapOrientation) | |
{ | |
int i01 = j * d.numCols + i; | |
i00 = i01+d.numCols; | |
} | |
else | |
{ | |
i00 = j*d.numCols + i; | |
} | |
int i10 = i00+1; | |
//remap indices to final vertex position | |
i00 = d.indices[i00]; | |
i10 = d.indices[i10]; | |
if ( i00 >= 0 && i10 >= 0 ) | |
{ | |
int baseIndex = i; | |
osg::Vec3f& v00 = boundaryVerts[baseIndex ]; | |
osg::Vec3f& v10 = boundaryVerts[baseIndex + 1]; | |
osg::Vec3f& v01 = boundaryVerts[baseIndex + d.numCols]; | |
osg::Vec3f& v11 = boundaryVerts[baseIndex + d.numCols + 1]; | |
float e00 = boundaryElevations[baseIndex]; | |
float e10 = boundaryElevations[baseIndex + 1]; | |
float e01 = boundaryElevations[baseIndex + d.numCols]; | |
float e11 = boundaryElevations[baseIndex + d.numCols + 1]; | |
if (!optimizeTriangleOrientation || fabsf(e00-e11)<fabsf(e01-e10)) | |
{ | |
osg::Vec3 normal1 = (v00-v01) ^ (v11-v01); | |
(*d.normals)[i00] += normal1; | |
(*d.normals)[i10] += normal1; | |
osg::Vec3 normal2 = (v10-v00) ^ (v11-v00); | |
(*d.normals)[i10] += normal2; | |
} | |
else | |
{ | |
osg::Vec3 normal1 = (v00-v01) ^ (v10-v01); | |
(*d.normals)[i00] += normal1; | |
osg::Vec3 normal2 = (v10-v01) ^ (v11-v01); | |
(*d.normals)[i00] += normal2; | |
(*d.normals)[i10] += normal2; | |
} | |
} | |
} | |
} | |
//Recalculate the south side | |
if (s_neighbor && s_neighbor->getNumColumns() == d.originalNumCols && s_neighbor->getNumRows() == d.originalNumRows) | |
{ | |
boundaryVerts.clear(); | |
boundaryElevations.clear(); | |
//Compute the verts for the south side | |
for (int j = (int)d.numRows-2; j <= (int)d.numRows-1; j++) | |
{ | |
for (int i = 0; i < (int)d.numCols; i++) | |
{ | |
osg::Vec3d ndc( (double)(i)/(double)(d.numCols-1), (double)(j - static_cast<int>(d.numRows-1))/(double)(d.numRows-1), 0.0); | |
unsigned i_equiv = d.i_sampleFactor==1.0 ? i : (unsigned) (double(i)*d.i_sampleFactor); | |
unsigned j_equiv = d.j_sampleFactor==1.0 ? j : (unsigned) (double(j)*d.j_sampleFactor); | |
//TODO: Should probably use an interpolated method here | |
float heightValue = s_neighbor->getHeight( i_equiv, j_equiv ); | |
ndc.z() = heightValue; | |
osg::Vec3d model; | |
d.model->_tileLocator->unitToModel( ndc, model ); | |
osg::Vec3d v = model * d.world2local; //model - d.centerModel; | |
boundaryVerts.push_back( v ); | |
boundaryElevations.push_back( heightValue ); | |
} | |
} | |
//The boundary verts are now populated, so go through and triangulate them add add the normals to the existing normal array | |
for (int i = 0; i < (int)d.numCols-1; i++) | |
{ | |
int i00; | |
int j = 0; | |
if (swapOrientation) | |
{ | |
int i01 = j*d.numCols + i; | |
i00 = i01+d.numCols; | |
} | |
else | |
{ | |
i00 = j*d.numCols + i; | |
} | |
int i10 = i00+1; | |
//remap indices to final vertex position | |
i00 = d.indices[i00]; | |
i10 = d.indices[i10]; | |
if ( i00 >= 0 && i10 >= 0 ) | |
{ | |
int baseIndex = i; | |
osg::Vec3f& v00 = boundaryVerts[baseIndex ]; | |
osg::Vec3f& v10 = boundaryVerts[baseIndex + 1]; | |
osg::Vec3f& v01 = boundaryVerts[baseIndex + d.numCols]; | |
osg::Vec3f& v11 = boundaryVerts[baseIndex + d.numCols + 1]; | |
float e00 = boundaryElevations[baseIndex]; | |
float e10 = boundaryElevations[baseIndex + 1]; | |
float e01 = boundaryElevations[baseIndex + d.numCols]; | |
float e11 = boundaryElevations[baseIndex + d.numCols + 1]; | |
if (!optimizeTriangleOrientation || fabsf(e00-e11)<fabsf(e01-e10)) | |
{ | |
osg::Vec3 normal1 = (v00-v01) ^ (v11-v01); | |
(*d.normals)[i00] += normal1; | |
osg::Vec3 normal2 = (v10-v00) ^ (v11-v00); | |
(*d.normals)[i00] += normal2; | |
(*d.normals)[i10] += normal2; | |
} | |
else | |
{ | |
osg::Vec3 normal1 = (v00-v01) ^ (v10-v01); | |
(*d.normals)[i00] += normal1; | |
(*d.normals)[i10] += normal1; | |
osg::Vec3 normal2 = (v10-v01) ^ (v11-v01); | |
(*d.normals)[i10] += normal2; | |
} | |
} | |
} | |
} | |
} | |
if (recalcNormals) | |
{ | |
for( osg::Vec3Array::iterator nitr = d.normals->begin(); nitr != d.normals->end(); ++nitr ) | |
{ | |
nitr->normalize(); | |
} | |
} | |
// in the case of full-masking, this will be empty | |
if ( elements->getNumIndices() > 0 ) | |
{ | |
d.surface->insertPrimitiveSet(0, elements); // because we always want this first. | |
} | |
} | |
void installRenderData( Data& d ) | |
{ | |
// pre-size all vectors: | |
unsigned size = d.renderLayers.size(); | |
d.surface->_layers.resize( size ); | |
for ( MaskRecordVector::iterator mr = d.maskRecords.begin(); mr != d.maskRecords.end(); ++mr ) | |
mr->_geom->_layers.resize( size ); | |
if ( d.renderTileCoords.valid() ) | |
d.surface->_tileCoords = d.renderTileCoords; | |
if ( d.stitchTileCoords.valid() ) | |
d.surface->_tileCoords = d.stitchTileCoords.get(); | |
// install the render data for each layer: | |
for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r ) | |
{ | |
unsigned order = r->_layer.getOrder(); | |
MPGeometry::Layer layer; | |
layer._layerID = r->_layer.getUID(); | |
layer._imageLayer = r->_layer.getMapLayer(); | |
layer._tex = r->_layer.getTexture(); | |
layer._texParent = r->_layerParent.getTexture(); | |
// cache stock opacity. Disable if a color filter is installed, since | |
// it can modify the alpha. | |
layer._opaque = | |
(r->_layer.getMapLayer()->getColorFilters().size() == 0 ) && | |
(layer._tex.valid() && !r->_layer.hasAlpha()) && | |
(!layer._texParent.valid() || !r->_layerParent.hasAlpha()) && | |
(layer._imageLayer.valid() && layer._imageLayer->getMinVisibleRange() == 0.0f) && | |
(layer._imageLayer.valid() && layer._imageLayer->getMaxVisibleRange() == FLT_MAX); | |
// texture matrix: scale/bias matrix of the texture. Currently we don't use | |
// this for rendering because the scale/bias is already baked into the | |
// texture coordinates. BUT we still need it for sampling shared rasters etc. | |
if ( r->_layer._locator.valid() ) | |
{ | |
osg::Matrixd sbmatrix; | |
r->_layer._locator->createScaleBiasMatrix( | |
d.model->_tileLocator->getDataExtent(), | |
sbmatrix ); | |
layer._texMat = sbmatrix; | |
// a shared layer needs access to a static uniform name. | |
if ( layer._imageLayer->isShared() ) | |
{ | |
layer._texMatUniformID = osg::Uniform::getNameID( layer._imageLayer->shareTexMatUniformName().get() ); | |
} | |
} | |
// parent texture matrix: it's a scale/bias matrix encoding the difference | |
// between the two locators. | |
if ( r->_layerParent.getLocator() ) | |
{ | |
osg::Matrixd sbmatrix; | |
r->_layerParent.getLocator()->createScaleBiasMatrix( | |
r->_layer.getLocator()->getDataExtent(), | |
sbmatrix ); | |
layer._texMatParent = sbmatrix; | |
} | |
// the surface: | |
layer._texCoords = r->_texCoords; | |
d.surface->_layers[order] = layer; | |
// the mask geometries: | |
for ( MaskRecordVector::iterator mr = d.maskRecords.begin(); mr != d.maskRecords.end(); ++mr ) | |
{ | |
layer._texCoords = r->_stitchTexCoords.get(); | |
mr->_geom->_layers[order] = layer; | |
} | |
} | |
// elevation texture. | |
d.surface->_elevTex = d.model->_elevationTexture.get(); | |
} | |
// Optimize the data. Convert all modes to GL_TRIANGLES and run the | |
// critical vertex cache optimizations. | |
void optimize( Data& d, bool runMeshOptimizers, ProgressCallback* progress ) | |
{ | |
// For vertex cache optimization to work, all the arrays must be in | |
// the geometry. MP doesn't store texture/tile coords in the geometry | |
// so we need to temporarily add them. | |
// | |
// Note: the MeshOptimizer will duplicate shared arrays if it finds any. | |
// We don't want that. And since we already have pointers to our texture | |
// arrays elsewhere, it mistakingly thinks there are shared (sine they | |
// have refcount>1). So we need to UNREF them, optimize, and then RE-REF | |
// them afterwards. :/ -gw | |
// We also need to add the tex coords list to the main array so that all of the vertex buffer objects are shared. | |
#if OSG_MIN_VERSION_REQUIRED(3, 1, 8) // after osg::Geometry API changes | |
osg::Geometry::ArrayList* surface_tdl = &d.surface->getTexCoordArrayList(); | |
int u=0; | |
for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r ) | |
{ | |
if ( r->_texCoords.valid() && r->_ownsTexCoords ) | |
{ | |
r->_texCoords->setBinding( osg::Array::BIND_PER_VERTEX ); | |
surface_tdl->push_back( r->_texCoords.get() ); | |
r->_texCoords->unref_nodelete(); | |
} | |
} | |
if ( d.renderTileCoords.valid() && d.ownsTileCoords ) | |
{ | |
d.renderTileCoords->setBinding( osg::Array::BIND_PER_VERTEX ); | |
surface_tdl->push_back( d.renderTileCoords.get() ); | |
d.renderTileCoords->unref_nodelete(); | |
} | |
#else // OSG version < 3.1.8 (before osg::Geometry API changes) | |
osg::Geometry::ArrayDataList* surface_tdl = &d.surface->getTexCoordArrayList(); | |
int u=0; | |
for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r ) | |
{ | |
if ( r->_ownsTexCoords && r->_texCoords.valid() ) | |
{ | |
surface_tdl->push_back( osg::Geometry::ArrayData(r->_texCoords.get(), osg::Geometry::BIND_PER_VERTEX) ); | |
r->_texCoords->unref_nodelete(); | |
} | |
} | |
if ( d.renderTileCoords.valid() && d.ownsTileCoords ) | |
{ | |
surface_tdl->push_back( osg::Geometry::ArrayData(d.renderTileCoords.get(), osg::Geometry::BIND_PER_VERTEX) ); | |
d.renderTileCoords->unref_nodelete(); | |
} | |
#endif | |
// Run the index mesh optimizer. | |
if (runMeshOptimizers && d.maskRecords.size() < 1) | |
{ | |
OE_START_TIMER(index_mesh_time); | |
osgUtil::Optimizer o; | |
o.optimize( d.surfaceGeode, osgUtil::Optimizer::INDEX_MESH ); | |
if (progress) | |
progress->stats()["index_mesh_time"] += OE_STOP_TIMER(index_mesh_time); | |
} | |
// re-ref all the things we un-ref'd earlier. | |
for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r ) | |
{ | |
if ( r->_texCoords.valid() && r->_ownsTexCoords ) | |
{ | |
r->_texCoords->ref(); | |
} | |
} | |
if ( d.renderTileCoords.valid() && d.ownsTileCoords ) | |
{ | |
d.renderTileCoords->ref(); | |
} | |
// clear the data out of the actual geometry now that we're done optimizing. | |
surface_tdl->clear(); | |
} | |
struct CullByTraversalMask : public osg::Drawable::CullCallback | |
{ | |
CullByTraversalMask( unsigned mask ) : _mask(mask) { } | |
unsigned _mask; | |
bool cull(osg::NodeVisitor* nv, osg::Drawable* drawable, osg::RenderInfo* renderInfo) const | |
{ | |
return ((unsigned)nv->getTraversalMask() & ((unsigned)nv->getNodeMaskOverride() | _mask)) == 0; | |
} | |
}; | |
osg::Geode* makeBBox(const Data& d) | |
{ | |
osg::Geode* geode = new osg::Geode(); | |
std::string sizeStr = "(empty)"; | |
float zpos = 0.0f; | |
osg::ComputeBoundsVisitor cbv; | |
d.surfaceGeode->accept( cbv ); | |
const osg::BoundingBox& bbox = cbv.getBoundingBox(); | |
if ( bbox.valid() ) | |
{ | |
osg::Geometry* geom = new osg::Geometry(); | |
geom->setName("bbox"); | |
osg::Vec3Array* v = new osg::Vec3Array(); | |
for(int i=0; i<8; ++i) | |
v->push_back(bbox.corner(i)); | |
geom->setVertexArray(v); | |
osg::DrawElementsUByte* de = new osg::DrawElementsUByte(GL_LINES); | |
de->push_back(0); de->push_back(1); | |
de->push_back(1); de->push_back(3); | |
de->push_back(3); de->push_back(2); | |
de->push_back(2); de->push_back(0); | |
de->push_back(4); de->push_back(5); | |
de->push_back(5); de->push_back(7); | |
de->push_back(7); de->push_back(6); | |
de->push_back(6); de->push_back(4); | |
de->push_back(0); de->push_back(4); | |
de->push_back(1); de->push_back(5); | |
de->push_back(3); de->push_back(7); | |
de->push_back(2); de->push_back(6); | |
geom->addPrimitiveSet(de); | |
osg::Vec4Array* c= new osg::Vec4Array(); | |
c->push_back(osg::Vec4(0,1,1,1)); | |
geom->setColorArray(c); | |
geom->setColorBinding(geom->BIND_OVERALL); | |
geode->addDrawable(geom); | |
sizeStr = Stringify() << bbox.xMax()-bbox.xMin(); | |
sizeStr = Stringify() << "min="<<bbox.zMin()<<"\nmax="<<bbox.zMax(); | |
zpos = bbox.zMax(); | |
} | |
osgText::Text* t = new osgText::Text(); | |
t->setText( Stringify() << d.model->_tileKey.str() << "\n" << sizeStr ); | |
t->setFont( osgEarth::Registry::instance()->getDefaultFont() ); | |
t->setCharacterSizeMode(t->SCREEN_COORDS); | |
t->setCharacterSize(36.0f); | |
t->setAlignment(t->CENTER_CENTER); | |
t->setColor(osg::Vec4(1,1,1,1)); | |
t->setBackdropColor(osg::Vec4(0,0,0,1)); | |
t->setBackdropType(t->OUTLINE); | |
t->setPosition(osg::Vec3(0,0,zpos)); | |
geode->addDrawable(t); | |
geode->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(),0); | |
geode->getOrCreateStateSet()->setMode(GL_LIGHTING,0); | |
return geode; | |
} | |
} | |
//------------------------------------------------------------------------ | |
TileModelCompiler::TileModelCompiler(const MaskLayerVector& maskLayers, | |
const ModelLayerVector& modelLayers, | |
int texImageUnit, | |
bool optimizeTriOrientation, | |
const MPTerrainEngineOptions& options) : | |
_maskLayers ( maskLayers ), | |
_modelLayers ( modelLayers ), | |
_optimizeTriOrientation( optimizeTriOrientation ), | |
_options ( options ), | |
_textureImageUnit ( texImageUnit ) | |
{ | |
_cullByTraversalMask = new CullByTraversalMask(*options.secondaryTraversalMask()); | |
_debug = | |
_options.debug() == true || | |
::getenv("OSGEARTH_MP_DEBUG") != 0L; | |
} | |
TileNode* | |
TileModelCompiler::compile(TileModel* model, | |
const MapFrame& frame, | |
ProgressCallback* progress) | |
{ | |
// Working data for the build. | |
Data d(model, frame, _maskLayers, _modelLayers); | |
d.textureImageUnit = _textureImageUnit; | |
GeoPoint centroid; | |
model->_tileKey.getExtent().getCentroid(centroid); | |
centroid.toWorld(d.centerModel); | |
centroid.createLocalToWorld(d.local2world); | |
d.world2local.invert(d.local2world); | |
TileNode* tile = new TileNode( model->_tileKey, model, d.local2world ); | |
d.installParentData = model->useParentData(); | |
d.parentModel = model->getParentTileModel(); | |
d.heightScale = *_options.verticalScale(); | |
d.heightOffset = *_options.verticalOffset(); | |
// A Geode/Geometry for the surface: | |
d.surface = new MPGeometry( d.model->_tileKey, d.frame, _textureImageUnit ); | |
d.surface->setName( "surface" ); | |
d.surfaceGeode = new osg::Geode(); | |
d.surfaceGeode->setNodeMask( *_options.primaryTraversalMask() ); | |
tile->addChild( d.surfaceGeode ); | |
// A Geode/Geometry for the skirt. This is good for traversal masking (e.g. shadows) | |
// but bad since we're not combining the entire tile into a single geometry. | |
// TODO: make this optional? | |
d.createSkirt = (_options.heightFieldSkirtRatio().value() > 0.0); | |
// adjust the tile locator for geocentric mode: | |
d.geoLocator = model->_tileLocator->getCoordinateSystemType() == GeoLocator::GEOCENTRIC ? | |
model->_tileLocator->getGeographicFromGeocentric() : | |
model->_tileLocator.get(); | |
// Set up any geometry-cutting masks: | |
if ( d.maskLayers.size() > 0 || d.modelLayers.size() > 0 ) | |
setupMaskRecords( d ); | |
// allocate all the vertex, normal, and color arrays. | |
setupGeometryAttributes( d, _options.tileSize().get() ); | |
// set up the list of layers to render and their shared arrays. | |
setupTextureAttributes( d, _cache ); | |
// calculate the vertex and normals for the surface geometry. | |
createSurfaceGeometry( d ); | |
// build geometry for the masked areas, if applicable | |
if ( d.maskRecords.size() > 0 ) | |
createMaskGeometry( d ); | |
// build the skirts. | |
if ( d.createSkirt ) | |
createSkirtGeometry( d, *_options.heightFieldSkirtRatio() ); | |
// at this point, make sure we actually built any surface geometry. | |
if (d.surface->getVertexArray() && | |
d.surface->getVertexArray()->getNumElements() > 0 ) | |
{ | |
d.surfaceGeode->addDrawable( d.surface ); | |
// tesselate the surface verts into triangles. | |
tessellateSurfaceGeometry( d, _optimizeTriOrientation, *_options.normalizeEdges() ); | |
} | |
// performance optimizations. | |
optimize( d, _options.optimizeTiles() == true, progress ); | |
// installs the per-layer rendering data into the Geometry objects. | |
installRenderData( d ); | |
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(); | |
tile->accept(*builder); | |
} | |
// allocate shared buffer objects. | |
AllocateBufferObjectsVisitor boAllocator; | |
tile->accept( boAllocator ); | |
// Temporary solution to the OverlayDecorator techniques' inappropriate setting of | |
// uniform values during the CULL traversal, which causes corruption of the RTT | |
// camera matricies when DRAW overlaps the next frame's CULL. Please see my comments | |
// in DrapingTechnique.cpp for more information. | |
// NOTE: cannot set this until optimizations (above) are complete | |
SetDataVarianceVisitor sdv( osg::Object::DYNAMIC ); | |
tile->accept( sdv ); | |
osg::ComputeBoundsVisitor cbv; | |
d.surfaceGeode->accept(cbv); | |
tile->setTerrainBoundingBox( cbv.getBoundingBox() ); | |
// debugging tools. | |
if (_debug) | |
{ | |
//test: run the geometry validator to make sure geometry it legal | |
osgEarth::GeometryValidator validator; | |
tile->accept(validator); | |
//test: show the tile bounding boxes | |
tile->addChild( makeBBox(d) ); | |
} | |
return tile; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment