[Git][debian-gis-team/osgearth][experimental] 5 commits: New upstream version 2.9~rc3+dfsg

Sebastiaan Couwenberg gitlab at salsa.debian.org
Fri Feb 2 09:28:46 UTC 2018


Sebastiaan Couwenberg pushed to branch experimental at Debian GIS Project / osgearth


Commits:
0d45333a by Bas Couwenberg at 2018-02-02T07:21:28+01:00
New upstream version 2.9~rc3+dfsg
- - - - -
12ecb125 by Bas Couwenberg at 2018-02-02T07:22:32+01:00
Merge tag 'upstream/2.9_rc3+dfsg' into experimental

Upstream version 2.9~rc3+dfsg

- - - - -
57078423 by Bas Couwenberg at 2018-02-02T07:22:55+01:00
New upstream release candidate.

- - - - -
74f162ea by Bas Couwenberg at 2018-02-02T08:59:24+01:00
Update symbols for amd64.

- - - - -
6b46af98 by Bas Couwenberg at 2018-02-02T08:59:24+01:00
Set distribution to experimental.

- - - - -


28 changed files:

- debian/changelog
- debian/libosgearth5.symbols
- debian/libosgearthutil5.symbols
- src/osgEarth/ImageUtils
- src/osgEarth/ImageUtils.cpp
- src/osgEarth/MapNode
- src/osgEarth/MapNode.cpp
- src/osgEarth/ModelLayer.cpp
- src/osgEarth/TerrainLayer
- src/osgEarth/Version
- src/osgEarth/VirtualProgram
- src/osgEarth/VirtualProgram.cpp
- src/osgEarthDrivers/cache_leveldb/LevelDBCacheBin.cpp
- src/osgEarthDrivers/cache_rocksdb/RocksDBCacheBin.cpp
- src/osgEarthDrivers/engine_mp/MPGeometry
- src/osgEarthDrivers/engine_mp/MPGeometry.cpp
- src/osgEarthDrivers/engine_rex/GeometryPool
- src/osgEarthDrivers/engine_rex/GeometryPool.cpp
- src/osgEarthDrivers/engine_rex/MaskGenerator
- src/osgEarthDrivers/engine_rex/MaskGenerator.cpp
- src/osgEarthDrivers/engine_rex/RexEngine.elevation.glsl
- src/osgEarthDrivers/engine_rex/RexTerrainEngineNode
- src/osgEarthDrivers/engine_rex/RexTerrainEngineNode.cpp
- src/osgEarthDrivers/mbtiles/MBTilesTileSource.cpp
- src/osgEarthSymbology/Geometry.cpp
- src/osgEarthUtil/Controls.cpp
- src/osgEarthUtil/TopologyGraph
- src/osgEarthUtil/TopologyGraph.cpp


Changes:

=====================================
debian/changelog
=====================================
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+osgearth (2.9~rc3+dfsg-1~exp1) experimental; urgency=medium
+
+  * New upstream release candidate.
+  * Update symbols for amd64.
+
+ -- Bas Couwenberg <sebastic at debian.org>  Fri, 02 Feb 2018 07:23:55 +0100
+
 osgearth (2.9~rc2+dfsg-1~exp1) experimental; urgency=medium
 
   * New upstream release candidate.


=====================================
debian/libosgearth5.symbols
=====================================
--- a/debian/libosgearth5.symbols
+++ b/debian/libosgearth5.symbols
@@ -1,4 +1,4 @@
-# SymbolsHelper-Confirmed: 2.9~rc2 amd64
+# SymbolsHelper-Confirmed: 2.9~rc3 amd64
 libosgEarth.so.5 #PACKAGE# #MINVER#
  _Z10TiXmlFOpenPKcS0_ at Base 2.4.0
  _ZGVZN13DeclutterSort18sortImplementationEPN7osgUtil9RenderBinEE8s_zero_w at Base 2.5.0
@@ -448,6 +448,7 @@ libosgEarth.so.5 #PACKAGE# #MINVER#
  _ZN8osgEarth10ImageLayerD2Ev at Base 2.4.0
  _ZN8osgEarth10ImageUtils10canConvertEPKN3osg5ImageEjj at Base 2.4.0
  _ZN8osgEarth10ImageUtils10cloneImageEPKN3osg5ImageE at Base 2.4.0
+ _ZN8osgEarth10ImageUtils10readStreamERSiPKN5osgDB7OptionsE at Base 2.9~rc3
  _ZN8osgEarth10ImageUtils10sameFormatEPKN3osg5ImageES4_ at Base 2.6.0
  _ZN8osgEarth10ImageUtils10upSampleNNEPKN3osg5ImageEi at Base 2.7.0
  _ZN8osgEarth10ImageUtils11PixelReader8setImageEPKN3osg5ImageE at Base 2.9~rc1
@@ -483,6 +484,7 @@ libosgEarth.so.5 #PACKAGE# #MINVER#
  _ZN8osgEarth10ImageUtils20createSharpenedImageEPKN3osg5ImageE at Base 2.5.0
  _ZN8osgEarth10ImageUtils22textureArrayCompatibleEPKN3osg5ImageES4_ at Base 2.7.0
  _ZN8osgEarth10ImageUtils24createMipmapBlendedImageEPKN3osg5ImageES4_ at Base 2.4.0
+ _ZN8osgEarth10ImageUtils24getReaderWriterForStreamERSi at Base 2.9~rc3
  _ZN8osgEarth10ImageUtils27buildNearestNeighborMipmapsEPKN3osg5ImageE at Base 2.7.0
  _ZN8osgEarth10ImageUtils27convertToPremultipliedAlphaEPN3osg5ImageE at Base 2.4.0
  _ZN8osgEarth10ImageUtils29computeTextureCompressionModeEPKN3osg5ImageERNS1_7Texture18InternalFormatModeE at Base 2.6.0
@@ -2868,6 +2870,7 @@ libosgEarth.so.5 #PACKAGE# #MINVER#
  _ZN8osgEarth7MapNode15removeExtensionEPNS_9ExtensionE at Base 2.7.0
  _ZN8osgEarth7MapNode17getDrapingManagerEv at Base 2.9~rc1
  _ZN8osgEarth7MapNode18getClampingManagerEv at Base 2.9~rc1
+ _ZN8osgEarth7MapNode21resizeGLObjectBuffersEj at Base 2.9~rc3
  _ZN8osgEarth7MapNode4initEv at Base 2.4.0
  _ZN8osgEarth7MapNode4loadERN3osg14ArgumentParserE at Base 2.4.0
  _ZN8osgEarth7MapNode4loadERN3osg14ArgumentParserERKNS_14MapNodeOptionsE at Base 2.8~rc1
@@ -3810,6 +3813,7 @@ libosgEarth.so.5 #PACKAGE# #MINVER#
  _ZNK8osgEarth10PatchLayer5cloneERKN3osg6CopyOpE at Base 2.9~rc1
  _ZNK8osgEarth10PatchLayer9classNameEv at Base 2.9~rc1
  _ZNK8osgEarth10PatchLayer9cloneTypeEv at Base 2.9~rc1
+ _ZNK8osgEarth10PolyShader16releaseGLObjectsEPN3osg5StateE at Base 2.9~rc3
  _ZNK8osgEarth10PolyShader9getShaderEj at Base 2.8~rc1
  _ZNK8osgEarth10Revisioned10inSyncWithERKNS_8RevisionE at Base 2.4.0
  _ZNK8osgEarth10Revisioned4syncERNS_8RevisionE at Base 2.4.0
@@ -4495,6 +4499,7 @@ libosgEarth.so.5 #PACKAGE# #MINVER#
  _ZNK8osgEarth7MapNode12getLayerNodeEPNS_5LayerE at Base 2.9~rc1
  _ZNK8osgEarth7MapNode12isGeocentricEv at Base 2.4.0
  _ZNK8osgEarth7MapNode16getTerrainEngineEv at Base 2.4.0
+ _ZNK8osgEarth7MapNode16releaseGLObjectsEPN3osg5StateE at Base 2.9~rc3
  _ZNK8osgEarth7MapNode17getLayerNodeGroupEv at Base 2.9~rc1
  _ZNK8osgEarth7MapNode17getMapNodeOptionsEv at Base 2.4.0
  _ZNK8osgEarth7MapNode19getResourceReleaserEv at Base 2.9~rc1


=====================================
debian/libosgearthutil5.symbols
=====================================
--- a/debian/libosgearthutil5.symbols
+++ b/debian/libosgearthutil5.symbols
@@ -1,4 +1,4 @@
-# SymbolsHelper-Confirmed: 2.9~rc2 amd64
+# SymbolsHelper-Confirmed: 2.9~rc3 amd64
 libosgEarthUtil.so.5 #PACKAGE# #MINVER#
  _Z10intersectsRKdS0_S0_S0_S0_S0_S0_S0_ at Base 2.4.0
  _Z17getHorizSRSStringB5cxx11PKN8osgEarth16SpatialReferenceE at Base 2.7.0
@@ -559,9 +559,12 @@ libosgEarthUtil.so.5 #PACKAGE# #MINVER#
  _ZN8osgEarth4Util13TMSBackFiller9writeTileERKNS_7TileKeyEPN3osg5ImageE at Base 2.4.0
  _ZN8osgEarth4Util13TMSBackFillerC1Ev at Base 2.4.0
  _ZN8osgEarth4Util13TMSBackFillerC2Ev at Base 2.4.0
- _ZN8osgEarth4Util13TopologyGraph12dumpBoundaryERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE at Base 2.9~rc1
+ _ZN8osgEarth4Util13TopologyGraph12dumpBoundaryERKSt6vectorISt23_Rb_tree_const_iteratorINS1_6VertexEESaIS5_EERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE at Base 2.9~rc3
  _ZN8osgEarth4Util13TopologyGraphC1Ev at Base 2.9~rc1
  _ZN8osgEarth4Util13TopologyGraphC2Ev at Base 2.9~rc1
+ _ZN8osgEarth4Util13TopologyGraphD0Ev at Base 2.9~rc3
+ _ZN8osgEarth4Util13TopologyGraphD1Ev at Base 2.9~rc3
+ _ZN8osgEarth4Util13TopologyGraphD2Ev at Base 2.9~rc3
  _ZN8osgEarth4Util13VerticalScale11mergeConfigERKNS_6ConfigE at Base 2.5.0
  _ZN8osgEarth4Util13VerticalScale11onUninstallEPNS_17TerrainEngineNodeE at Base 2.5.0
  _ZN8osgEarth4Util13VerticalScale4initEv at Base 2.5.0
@@ -651,7 +654,9 @@ libosgEarthUtil.so.5 #PACKAGE# #MINVER#
  _ZN8osgEarth4Util15TFSReaderWriter4readERSiRNS0_8TFSLayerE at Base 2.4.0
  _ZN8osgEarth4Util15TFSReaderWriter5writeERKNS0_8TFSLayerERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE at Base 2.7.0
  _ZN8osgEarth4Util15TFSReaderWriter5writeERKNS0_8TFSLayerERSo at Base 2.4.0
+ _ZN8osgEarth4Util15TopologyBuilder18assignAndPropagateERSt23_Rb_tree_const_iteratorINS0_13TopologyGraph6VertexEEj at Base 2.9~rc3
  _ZN8osgEarth4Util15TopologyBuilder3addEj at Base 2.9~rc1
+ _ZN8osgEarth4Util15TopologyBuilder6createEPKN3osg13TemplateArrayINS2_5Vec3fELNS2_5Array4TypeE28ELi3ELi5126EEEPKNS2_12PrimitiveSetERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE at Base 2.9~rc3
  _ZN8osgEarth4Util15TopologyBuilderC1Ev at Base 2.9~rc1
  _ZN8osgEarth4Util15TopologyBuilderC2Ev at Base 2.9~rc1
  _ZN8osgEarth4Util15TopologyBuilderclEjjj at Base 2.9~rc1
@@ -2079,7 +2084,13 @@ libosgEarthUtil.so.5 #PACKAGE# #MINVER#
  _ZNK8osgEarth4Util13MapNodeHelper5parseEPNS_7MapNodeERN3osg14ArgumentParserEPN9osgViewer4ViewEPNS4_5GroupEPNS0_8Controls12LabelControlE at Base 2.7.0
  _ZNK8osgEarth4Util13MapNodeHelper5parseEPNS_7MapNodeERN3osg14ArgumentParserEPN9osgViewer4ViewEPNS4_5GroupEPNS0_8Controls9ContainerE at Base 2.7.0
  _ZNK8osgEarth4Util13MapNodeHelper5usageB5cxx11Ev at Base 2.7.0
- _ZNK8osgEarth4Util13TopologyGraph14createBoundaryERSt6vectorISt23_Rb_tree_const_iteratorINS1_6VertexEESaIS5_EE at Base 2.9~rc1
+ _ZNK8osgEarth4Util13TopologyGraph11libraryNameEv at Base 2.9~rc3
+ _ZNK8osgEarth4Util13TopologyGraph12isSameKindAsEPKN3osg6ObjectE at Base 2.9~rc3
+ _ZNK8osgEarth4Util13TopologyGraph14createBoundaryEjRSt6vectorISt23_Rb_tree_const_iteratorINS1_6VertexEESaIS5_EE at Base 2.9~rc3
+ _ZNK8osgEarth4Util13TopologyGraph16getNumBoundariesEv at Base 2.9~rc3
+ _ZNK8osgEarth4Util13TopologyGraph5cloneERKN3osg6CopyOpE at Base 2.9~rc3
+ _ZNK8osgEarth4Util13TopologyGraph9classNameEv at Base 2.9~rc3
+ _ZNK8osgEarth4Util13TopologyGraph9cloneTypeEv at Base 2.9~rc3
  _ZNK8osgEarth4Util13VerticalScale9getConfigEv at Base 2.5.0
  _ZNK8osgEarth4Util14HSLColorFilter12getHSLOffsetEv at Base 2.4.0
  _ZNK8osgEarth4Util14HSLColorFilter25getEntryPointFunctionNameB5cxx11Ev at Base 2.7.0
@@ -2410,7 +2421,6 @@ libosgEarthUtil.so.5 #PACKAGE# #MINVER#
  (optional=templinst)_ZNSt8_Rb_treeIN3osg12observer_ptrIN5osgGA15GUIEventHandlerEEESt4pairIKS4_NS1_IN9osgViewer4ViewEEEESt10_Select1stISA_ESt4lessIS4_ESaISA_EE8_M_eraseEPSt13_Rb_tree_nodeISA_E at Base 2.4.0
  (optional=templinst)_ZNSt8_Rb_treeIN7osgUtil22LineSegmentIntersector12IntersectionES2_St9_IdentityIS2_ESt4lessIS2_ESaIS2_EE8_M_eraseEPSt13_Rb_tree_nodeIS2_E at Base 2.4.0
  (optional=templinst)_ZNSt8_Rb_treeIN8osgEarth20PrimitiveIntersector12IntersectionES2_St9_IdentityIS2_ESt4lessIS2_ESaIS2_EE8_M_eraseEPSt13_Rb_tree_nodeIS2_E at Base 2.5.0
- (optional=templinst)_ZNSt8_Rb_treeIN8osgEarth4Util13TopologyGraph6VertexES3_St9_IdentityIS3_ESt4lessIS3_ESaIS3_EE16_M_insert_uniqueIRKS3_EESt4pairISt17_Rb_tree_iteratorIS3_EbEOT_ at Base 2.9~rc1
  (optional=templinst)_ZNSt8_Rb_treeIN8osgEarth4Util13TopologyGraph6VertexES3_St9_IdentityIS3_ESt4lessIS3_ESaIS3_EE8_M_eraseEPSt13_Rb_tree_nodeIS3_E at Base 2.9~rc1
  (optional=templinst)_ZNSt8_Rb_treeIN8osgEarth4Util16EarthManipulator9InputSpecESt4pairIKS3_NS2_6ActionEESt10_Select1stIS7_ESt4lessIS3_ESaIS7_EE24_M_get_insert_unique_posERS5_ at Base 2.4.0
  (optional=templinst)_ZNSt8_Rb_treeIN8osgEarth4Util16EarthManipulator9InputSpecESt4pairIKS3_NS2_6ActionEESt10_Select1stIS7_ESt4lessIS3_ESaIS7_EE29_M_get_insert_hint_unique_posESt23_Rb_tree_const_iteratorIS7_ERS5_ at Base 2.4.0
@@ -2473,7 +2483,6 @@ libosgEarthUtil.so.5 #PACKAGE# #MINVER#
  (optional=templinst)_ZNSt8_Rb_treeIfSt4pairIKfN3osg7ref_ptrIN8osgEarth4Util8Controls11ControlNodeEEEESt10_Select1stIS9_ESt4lessIfESaIS9_EE8_M_eraseEPSt13_Rb_tree_nodeIS9_E at Base 2.4.0
  (optional=templinst|arch=hurd-i386 i386 kfreebsd-i386)_ZNSt8_Rb_treeIfSt4pairIKfN3osg7ref_ptrIN8osgEarth4Util9GeoObjectEEEESt10_Select1stIS8_ESt4lessIfESaIS8_EE15_M_insert_equalIS0_IfPS6_EEESt17_Rb_tree_iteratorIS8_EOT_ at Base 2.8~rc2
  (optional=templinst)_ZNSt8_Rb_treeIfSt4pairIKfN3osg7ref_ptrIN8osgEarth4Util9GeoObjectEEEESt10_Select1stIS8_ESt4lessIfESaIS8_EE8_M_eraseEPSt13_Rb_tree_nodeIS8_E at Base 2.4.0
- (optional=templinst)_ZNSt8_Rb_treeIjSt4pairIKjSt23_Rb_tree_const_iteratorIN8osgEarth4Util13TopologyGraph6VertexEEESt10_Select1stIS8_ESt4lessIjESaIS8_EE29_M_get_insert_hint_unique_posES2_IS8_ERS1_ at Base 2.9~rc1
  (optional=templinst)_ZNSt8_Rb_treeIjSt4pairIKjSt23_Rb_tree_const_iteratorIN8osgEarth4Util13TopologyGraph6VertexEEESt10_Select1stIS8_ESt4lessIjESaIS8_EE8_M_eraseEPSt13_Rb_tree_nodeIS8_E at Base 2.9~rc1
  (optional=templinst|arch=ia64)_ZSt22__uninitialized_move_aIPN8osgEarth4Util3TMS7TileSetES4_SaIS3_EET0_T_S7_S6_RT1_ at Base 2.4.0
  (optional=templinst)_ZSt4copyIN3osg7ref_ptrIN8osgEarth4Util8Controls7ControlEEEESt15_Deque_iteratorIT_RS8_PS8_ES7_IS8_RKS8_PSC_ESF_SB_ at Base 2.9~rc1
@@ -2565,6 +2574,7 @@ libosgEarthUtil.so.5 #PACKAGE# #MINVER#
  _ZTIN8osgEarth4Util13GARSGraticuleE at Base 2.9~rc1
  _ZTIN8osgEarth4Util13MGRSFormatterE at Base 2.4.0
  _ZTIN8osgEarth4Util13MGRSGraticuleE at Base 2.4.0
+ _ZTIN8osgEarth4Util13TopologyGraphE at Base 2.9~rc3
  _ZTIN8osgEarth4Util13VerticalScaleE at Base 2.5.0
  _ZTIN8osgEarth4Util14HSLColorFilterE at Base 2.4.0
  _ZTIN8osgEarth4Util14RGBColorFilterE at Base 2.4.0
@@ -2794,6 +2804,7 @@ libosgEarthUtil.so.5 #PACKAGE# #MINVER#
  _ZTSN8osgEarth4Util13GARSGraticuleE at Base 2.9~rc1
  _ZTSN8osgEarth4Util13MGRSFormatterE at Base 2.4.0
  _ZTSN8osgEarth4Util13MGRSGraticuleE at Base 2.4.0
+ _ZTSN8osgEarth4Util13TopologyGraphE at Base 2.9~rc3
  _ZTSN8osgEarth4Util13VerticalScaleE at Base 2.5.0
  _ZTSN8osgEarth4Util14HSLColorFilterE at Base 2.4.0
  _ZTSN8osgEarth4Util14RGBColorFilterE at Base 2.4.0
@@ -3028,6 +3039,7 @@ libosgEarthUtil.so.5 #PACKAGE# #MINVER#
  _ZTVN8osgEarth4Util13GARSGraticuleE at Base 2.9~rc1
  _ZTVN8osgEarth4Util13MGRSFormatterE at Base 2.4.0
  _ZTVN8osgEarth4Util13MGRSGraticuleE at Base 2.4.0
+ _ZTVN8osgEarth4Util13TopologyGraphE at Base 2.9~rc3
  _ZTVN8osgEarth4Util13VerticalScaleE at Base 2.5.0
  _ZTVN8osgEarth4Util14HSLColorFilterE at Base 2.4.0
  _ZTVN8osgEarth4Util14RGBColorFilterE at Base 2.4.0


=====================================
src/osgEarth/ImageUtils
=====================================
--- a/src/osgEarth/ImageUtils
+++ b/src/osgEarth/ImageUtils
@@ -26,6 +26,7 @@
 #include <osg/Texture>
 #include <osg/GL>
 #include <osg/NodeVisitor>
+#include <osgDB/ReaderWriter>
 #include <vector>
 
 //These formats were not added to OSG until after 2.8.3 so we need to define them to use them.
@@ -347,6 +348,18 @@ namespace osgEarth
         static void activateMipMaps(osg::Texture* texture);
 
         /**
+         * Gets an osgDB::ReaderWriter for the given input stream.
+         * Returns NULL if no ReaderWriter can be found.
+         */
+        static osgDB::ReaderWriter* getReaderWriterForStream(std::istream& stream);
+
+        /**
+         * Reads an osg::Image from the given input stream.         
+         * Returns NULL if the image could not be read.
+         */
+        static osg::Image* readStream(std::istream& stream, const osgDB::Options* options);
+
+        /**
          * Reads color data out of an image, regardles of its internal pixel format.
          */
         class OSGEARTH_EXPORT PixelReader


=====================================
src/osgEarth/ImageUtils.cpp
=====================================
--- a/src/osgEarth/ImageUtils.cpp
+++ b/src/osgEarth/ImageUtils.cpp
@@ -687,6 +687,82 @@ ImageUtils::createMipmapBlendedImage( const osg::Image* primary, const osg::Imag
     return result.release();
 }
 
+osgDB::ReaderWriter*
+ImageUtils::getReaderWriterForStream(std::istream& stream) {
+    // Modified from https://oroboro.com/image-format-magic-bytes/
+
+    // Get the length of the stream
+    stream.seekg(0, std::ios::end);
+    unsigned int len = stream.tellg();
+    stream.seekg(0, std::ios::beg);
+
+    if (len < 16) return 0;
+
+    //const char* data = input.c_str();
+    // Read a 16 byte header
+    char data[16];
+    stream.read(data, 16);
+    // Reset reading
+    stream.seekg(0, std::ios::beg);
+
+    // .jpg:  FF D8 FF
+    // .png:  89 50 4E 47 0D 0A 1A 0A
+    // .gif:  GIF87a      
+    //        GIF89a
+    // .tiff: 49 49 2A 00
+    //        4D 4D 00 2A
+    // .bmp:  BM 
+    // .webp: RIFF ???? WEBP 
+    // .ico   00 00 01 00
+    //        00 00 02 00 ( cursor files )
+    switch (data[0])
+    {
+    case '\xFF':
+        return (!strncmp((const char*)data, "\xFF\xD8\xFF", 3)) ?
+            osgDB::Registry::instance()->getReaderWriterForExtension("jpg") : 0;
+
+    case '\x89':
+        return (!strncmp((const char*)data,
+            "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8)) ?
+            osgDB::Registry::instance()->getReaderWriterForExtension("png") : 0;
+
+    case 'G':
+        return (!strncmp((const char*)data, "GIF87a", 6) ||
+            !strncmp((const char*)data, "GIF89a", 6)) ?
+            osgDB::Registry::instance()->getReaderWriterForExtension("gif") : 0;
+
+    case 'I':
+        return (!strncmp((const char*)data, "\x49\x49\x2A\x00", 4)) ?
+            osgDB::Registry::instance()->getReaderWriterForExtension("tif") : 0;
+
+    case 'M':
+        return (!strncmp((const char*)data, "\x4D\x4D\x00\x2A", 4)) ?
+            osgDB::Registry::instance()->getReaderWriterForExtension("tif") : 0;
+
+    case 'B':
+        return ((data[1] == 'M')) ?
+            osgDB::Registry::instance()->getReaderWriterForExtension("bmp") : 0;
+
+    default:
+        return 0;
+    }
+}
+
+osg::Image*
+ImageUtils::readStream(std::istream& stream, const osgDB::Options* options) {
+
+    osgDB::ReaderWriter* rw = getReaderWriterForStream(stream);
+    if (!rw) {
+        return 0;
+    }
+
+    osgDB::ReaderWriter::ReadResult rr = rw->readImage(stream, options);
+    if (rr.validImage()) {
+        return rr.takeImage();
+    }
+    return 0;
+}
+
 namespace
 {
     struct MixImage


=====================================
src/osgEarth/MapNode
=====================================
--- a/src/osgEarth/MapNode
+++ b/src/osgEarth/MapNode
@@ -233,6 +233,10 @@ namespace osgEarth
 
         virtual void traverse( class osg::NodeVisitor& nv );
 
+        virtual void resizeGLObjectBuffers(unsigned maxSize);
+
+        virtual void releaseGLObjects(osg::State* state) const;
+
     protected:    
 
         virtual ~MapNode();


=====================================
src/osgEarth/MapNode.cpp
=====================================
--- a/src/osgEarth/MapNode.cpp
+++ b/src/osgEarth/MapNode.cpp
@@ -803,6 +803,32 @@ MapNode::traverse( osg::NodeVisitor& nv )
     }
 }
 
+void
+MapNode::resizeGLObjectBuffers(unsigned maxSize)
+{
+    LayerVector layers;
+    getMap()->getLayers(layers);
+    for (LayerVector::const_iterator i = layers.begin(); i != layers.end(); ++i)
+    {
+        if ((*i)->getStateSet()) {
+            (*i)->getStateSet()->resizeGLObjectBuffers(maxSize);
+        }
+    }
+}
+
+void
+MapNode::releaseGLObjects(osg::State* state) const
+{
+    LayerVector layers;
+    getMap()->getLayers(layers);
+    for (LayerVector::const_iterator i = layers.begin(); i != layers.end(); ++i)
+    {
+        if ((*i)->getStateSet()) {
+            (*i)->getStateSet()->releaseGLObjects(state);
+        }
+    }
+}
+
 DrapingManager*
 MapNode::getDrapingManager()
 {


=====================================
src/osgEarth/ModelLayer.cpp
=====================================
--- a/src/osgEarth/ModelLayer.cpp
+++ b/src/osgEarth/ModelLayer.cpp
@@ -391,10 +391,6 @@ ModelLayer::getOrCreateSceneGraph(const Map*        map,
               
                 ss->setRenderBinDetails( 99999, "RenderBin" ); //TODO: configure this bin ...
             }
-            else
-            {
-                groupSS->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
-            }
 
             // save it.
             _graphs[map->getUID()] = node;


=====================================
src/osgEarth/TerrainLayer
=====================================
--- a/src/osgEarth/TerrainLayer
+++ b/src/osgEarth/TerrainLayer
@@ -262,6 +262,9 @@ namespace osgEarth
          * Given a TileKey, returns a TileKey representing the best known available.
          * For example, if the input TileKey exceeds the layer's max LOD, the return
          * value will be an ancestor key at that max LOD.
+         *
+         * If a setting that effects the visible range of this layer is set (minLevel, maxLevel, minResolution or maxResolution)
+         * then any key passed in that falls outside of the valid range for the layer will return TileKey::INVALID.
          */
         virtual TileKey getBestAvailableTileKey(const TileKey& key) const;
 


=====================================
src/osgEarth/Version
=====================================
--- a/src/osgEarth/Version
+++ b/src/osgEarth/Version
@@ -31,7 +31,7 @@ extern "C" {
 #define OSGEARTH_MINOR_VERSION    9
 #define OSGEARTH_PATCH_VERSION    0
 #define OSGEARTH_SOVERSION        0
-#define OSGEARTH_RC_VERSION       2
+#define OSGEARTH_RC_VERSION       3
 #define OSGEARTH_DEVEL_VERSION    0     // 0 = release; >0 = interim devel version
 
 /* Convenience macro that can be used to decide whether a feature is present or not i.e.


=====================================
src/osgEarth/VirtualProgram
=====================================
--- a/src/osgEarth/VirtualProgram
+++ b/src/osgEarth/VirtualProgram
@@ -178,9 +178,12 @@ namespace osgEarth
         /** Generates the shaders. */
         void prepare();
 
+    public:
         /** Called from the draw context to resize shader buffers as necessary (OSG) */
-        virtual void resizeGLObjectBuffers(unsigned maxSize);
+        void resizeGLObjectBuffers(unsigned maxSize);
 
+        /** Called by OSG to release GPu memory associated with the object */
+        void releaseGLObjects(osg::State* state) const;
 
     protected:
         virtual ~PolyShader() { }
@@ -199,11 +202,6 @@ namespace osgEarth
         osg::ref_ptr<osg::Shader>    _geomShader;
         osg::ref_ptr<osg::Shader>    _tessevalShader;
 
-        // shader source before running thru the preprocessor. Keep this around so that
-        // someone can call VirtualProgram::getShaders and have access to code that has
-        // not been set up for ShaderFactory.
-        //std::string                  _originalSource;
-
         bool                         _dirty;
     };
 


=====================================
src/osgEarth/VirtualProgram.cpp
=====================================
--- a/src/osgEarth/VirtualProgram.cpp
+++ b/src/osgEarth/VirtualProgram.cpp
@@ -926,11 +926,6 @@ VirtualProgram::resizeGLObjectBuffers(unsigned maxSize)
         }
     }
 
-    // Resize the buffered_object
-    //_apply.resize(maxSize);
-
-    //_vpStackMemory._item.resize(maxSize);
-
     _programCacheMutex.unlock();
 }
 
@@ -941,8 +936,15 @@ VirtualProgram::releaseGLObjects(osg::State* state) const
 
     for (ProgramMap::const_iterator i = _programCache.begin(); i != _programCache.end(); ++i)
     {
-        //if ( i->second->referenceCount() == 1 )
-            i->second._program->releaseGLObjects(state);
+        i->second._program->releaseGLObjects(state);
+    }
+
+    for (ShaderMap::const_iterator i = _shaderMap.begin(); i != _shaderMap.end(); ++i)
+    {
+        if (i->data()._shader.valid())
+        {
+            i->data()._shader->releaseGLObjects(state);
+        }
     }
 
     _programCache.clear();
@@ -1949,6 +1951,24 @@ void PolyShader::resizeGLObjectBuffers(unsigned maxSize)
     }
 }
 
+void PolyShader::releaseGLObjects(osg::State* state) const
+{
+    if (_nominalShader.valid())
+    {
+        _nominalShader->releaseGLObjects(state);
+    }
+
+    if (_geomShader.valid())
+    {
+        _geomShader->releaseGLObjects(state);
+    }
+
+    if (_tessevalShader.valid())
+    {
+        _tessevalShader->releaseGLObjects(state);
+    }
+}
+
 //.......................................................................
 // SERIALIZERS for VIRTUALPROGRAM
 


=====================================
src/osgEarthDrivers/cache_leveldb/LevelDBCacheBin.cpp
=====================================
--- a/src/osgEarthDrivers/cache_leveldb/LevelDBCacheBin.cpp
+++ b/src/osgEarthDrivers/cache_leveldb/LevelDBCacheBin.cpp
@@ -59,7 +59,7 @@ namespace
         for(unsigned i=0; i<paddedSize/4; ++i, ++ptr)
             (*ptr) ^= prng.next(INT_MAX);
         data = std::string(buf, data.size());
-        delete buf;
+        delete [] buf;
     }
 
     void unblend(std::string& data, unsigned seed)


=====================================
src/osgEarthDrivers/cache_rocksdb/RocksDBCacheBin.cpp
=====================================
--- a/src/osgEarthDrivers/cache_rocksdb/RocksDBCacheBin.cpp
+++ b/src/osgEarthDrivers/cache_rocksdb/RocksDBCacheBin.cpp
@@ -59,7 +59,7 @@ namespace
         for(unsigned i=0; i<paddedSize/4; ++i, ++ptr)
             (*ptr) ^= prng.next(INT_MAX);
         data = std::string(buf, data.size());
-        delete buf;
+        delete [] buf;
     }
 
     void unblend(std::string& data, unsigned seed)


=====================================
src/osgEarthDrivers/engine_mp/MPGeometry
=====================================
--- a/src/osgEarthDrivers/engine_mp/MPGeometry
+++ b/src/osgEarthDrivers/engine_mp/MPGeometry
@@ -153,7 +153,9 @@ namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 #endif
 
     protected:
-#if OSG_MIN_VERSION_REQUIRED(3,5,6)
+#if OSG_MIN_VERSION_REQUIRED(3,5,9)
+        virtual osg::VertexArrayState* createVertexArrayStateImplementation(osg::RenderInfo& renderInfo) const;
+#elif OSG_MIN_VERSION_REQUIRED(3,5,6)
         virtual osg::VertexArrayState* createVertexArrayState(osg::RenderInfo& renderInfo) const;
 #endif
 


=====================================
src/osgEarthDrivers/engine_mp/MPGeometry.cpp
=====================================
--- a/src/osgEarthDrivers/engine_mp/MPGeometry.cpp
+++ b/src/osgEarthDrivers/engine_mp/MPGeometry.cpp
@@ -565,11 +565,17 @@ MPGeometry::compileGLObjects( osg::RenderInfo& renderInfo ) const
 }
 
 #if OSG_MIN_VERSION_REQUIRED(3,5,6)
+
 osg::VertexArrayState*
+#if OSG_MIN_VERSION_REQUIRED(3,5,9)
+MPGeometry::createVertexArrayStateImplementation(osg::RenderInfo& renderInfo) const
+{
+    osg::VertexArrayState* vas = osg::Geometry::createVertexArrayStateImplementation(renderInfo);
+#else
 MPGeometry::createVertexArrayState(osg::RenderInfo& renderInfo) const
 {
     osg::VertexArrayState* vas = osg::Geometry::createVertexArrayState(renderInfo);
-    
+#endif
     // make sure we have array dispatchers for the multipass coords
     vas->assignTexCoordArrayDispatcher(_texCoordList.size() + 2);
 


=====================================
src/osgEarthDrivers/engine_rex/GeometryPool
=====================================
--- a/src/osgEarthDrivers/engine_rex/GeometryPool
+++ b/src/osgEarthDrivers/engine_rex/GeometryPool
@@ -71,7 +71,11 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
         const osg::DrawElements* getMaskElements() const { return _maskElements.get(); }
 
 #ifdef SUPPORTS_VAO
+    #if OSG_MIN_VERSION_REQUIRED(3,5,9)
+        osg::VertexArrayState* createVertexArrayStateImplementation(osg::RenderInfo& renderInfo) const;
+    #else
         osg::VertexArrayState* createVertexArrayState(osg::RenderInfo& renderInfo) const;
+    #endif
 #endif
 
         void compileGLObjects(osg::RenderInfo& renderInfo) const;


=====================================
src/osgEarthDrivers/engine_rex/GeometryPool.cpp
=====================================
--- a/src/osgEarthDrivers/engine_rex/GeometryPool.cpp
+++ b/src/osgEarthDrivers/engine_rex/GeometryPool.cpp
@@ -148,6 +148,26 @@ namespace
         if ( (col & 0x1)==1 )                   return 2;
         return 1;            
     }
+
+    struct Sort_by_X {
+        osg::Vec3Array& _verts;
+        Sort_by_X(osg::Vec3Array* verts) : _verts(*verts) { }
+        bool operator()(unsigned lhs, unsigned rhs) const {
+            if (_verts[lhs].x() < _verts[rhs].x()) return true;
+            if (_verts[lhs].x() > _verts[rhs].x()) return false;
+            return _verts[lhs].y() < _verts[rhs].y();
+        }
+    };
+
+    struct Sort_by_Y {
+        osg::Vec3Array& _verts;
+        Sort_by_Y(osg::Vec3Array* verts) : _verts(*verts) { }
+        bool operator()(unsigned lhs, unsigned rhs) const {
+            if (_verts[lhs].y() < _verts[rhs].y()) return true;
+            if (_verts[lhs].y() > _verts[rhs].y()) return false;
+            return _verts[lhs].x() < _verts[rhs].x();
+        }
+    };
 }
 
 #define addSkirtDataForIndex(INDEX, HEIGHT) \
@@ -229,20 +249,20 @@ GeometryPool::createGeometry(const TileKey& tileKey,
     geom->setDrawElements(primSet);
 
     // the vertex locations:
-    osg::Vec3Array* verts = new osg::Vec3Array();
+    osg::ref_ptr<osg::Vec3Array> verts = new osg::Vec3Array();
     verts->setVertexBufferObject(vbo.get());
     verts->reserve( numVerts );
     verts->setBinding(verts->BIND_PER_VERTEX);
-    geom->setVertexArray( verts );
+    geom->setVertexArray( verts.get() );
 
     // the surface normals (i.e. extrusion vectors)
-    osg::Vec3Array* normals = new osg::Vec3Array();
+    osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array();
     normals->setVertexBufferObject(vbo.get());
     normals->reserve( numVerts );
     normals->setBinding(normals->BIND_PER_VERTEX);
-    geom->setNormalArray( normals );
+    geom->setNormalArray( normals.get() );
     
-    osg::Vec3Array* neighbors = 0L;
+    osg::ref_ptr<osg::Vec3Array> neighbors = 0L;
     if ( _options.morphTerrain() == true )
     {
         // neighbor positions (for morphing)
@@ -250,8 +270,7 @@ GeometryPool::createGeometry(const TileKey& tileKey,
         neighbors->setBinding(neighbors->BIND_PER_VERTEX);
         neighbors->setVertexBufferObject(vbo.get());
         neighbors->reserve( numVerts );
-        geom->setNeighborArray(neighbors);
-        //geom->setTexCoordArray( 1, neighbors );
+        geom->setNeighborArray(neighbors.get());
     }
 
     // tex coord is [0..1] across the tile. The 3rd dimension tracks whether the
@@ -267,13 +286,13 @@ GeometryPool::createGeometry(const TileKey& tileKey,
     osg::Vec3Array* texCoords = _sharedTexCoords.get();
 #else
     bool populateTexCoords = true;
-    osg::Vec3Array* texCoords = new osg::Vec3Array();
+    osg::ref_ptr<osg::Vec3Array> texCoords = new osg::Vec3Array();
     texCoords->setBinding(texCoords->BIND_PER_VERTEX);
     texCoords->setVertexBufferObject(vbo.get());
     texCoords->reserve( numVerts );
 #endif
 
-    geom->setTexCoordArray(texCoords);
+    geom->setTexCoordArray(texCoords.get());
     
     float delta = 1.0/(tileSize-1);
     osg::Vec3d tdelta(delta,0,0);
@@ -317,148 +336,164 @@ GeometryPool::createGeometry(const TileKey& tileKey,
         }
     }
 
-    // Now tessellate the surface.
-    
-    // TODO: do we really need this??
-    bool swapOrientation = !locator->orientationOpenGL();
-
-    for(unsigned j=0; j<tileSize-1; ++j)
+    // By default we tessellate the surface, but if there's a masking set
+    // it might replace some or all of our surface geometry.
+    bool tessellateSurface = true;
+                    
+    if (maskSet)
     {
-        for(unsigned i=0; i<tileSize-1; ++i)
+        // The mask generator adds to the passed-in arrays as necessary,
+        // and then returns a new primtive set containing all the new triangles.
+        osg::ref_ptr<osg::DrawElementsUInt> maskElements;
+
+        MaskGenerator::Result r = maskSet->createMaskPrimitives(
+            mapInfo,
+            verts.get(), texCoords.get(), normals.get(), neighbors.get(),
+            maskElements);
+
+        if (r == MaskGenerator::R_BOUNDARY_INTERSECTS_TILE && 
+            maskElements.valid() && 
+            maskElements->size() > 0)
         {
-            int i00;
-            int i01;
-            if (swapOrientation)
-            {
-                i01 = j*tileSize + i;
-                i00 = i01+tileSize;
-            }
-            else
-            {
-                i00 = j*tileSize + i;
-                i01 = i00+tileSize;
-            }
+            // Share the same EBO as the surface geometry
+            maskElements->setElementBufferObject(primSet->getElementBufferObject());
+            geom->setMaskElements(maskElements.get());
 
-            int i10 = i00+1;
-            int i11 = i01+1;
+            // Build a skirt for the mask geometry?
+            if (createSkirt)
+            {
+                // calculate the skirt extrusion height
+                double height = tileBound.radius() * _options.heightFieldSkirtRatio().get();
 
-            // skip any triangles that have a discarded vertex:
-            bool discard = maskSet && (
-                maskSet->isMasked( (*texCoords)[i00] ) ||
-                maskSet->isMasked( (*texCoords)[i11] )
-            );
+                // Construct a node+edge graph out of the masking geometry:
+                osg::ref_ptr<TopologyGraph> graph = TopologyBuilder::create(verts.get(), maskElements.get(), tileKey.str());
 
-            if ( !discard )
-            {
-                discard = maskSet && maskSet->isMasked( (*texCoords)[i01] );
-                if ( !discard )
+                // Extract the boundaries (if the topology is discontinuous,
+                // there will be more than one)
+                for (unsigned i = 0; i<graph->getNumBoundaries(); ++i)
                 {
-                    primSet->addElement(i01);
-                    primSet->addElement(i00);
-                    primSet->addElement(i11);
-                }
-            
-                discard = maskSet && maskSet->isMasked( (*texCoords)[i10] );
-                if ( !discard )
-                {
-                    primSet->addElement(i00);
-                    primSet->addElement(i10);
-                    primSet->addElement(i11);
+                    TopologyGraph::IndexVector boundary;
+                    graph->createBoundary(i, boundary);
+
+                    if (boundary.size() >= 3)
+                    {
+                        unsigned skirtIndex = verts->size();
+
+                        for (TopologyGraph::IndexVector::const_iterator i = boundary.begin(); i != boundary.end(); ++i)
+                        {
+                            addSkirtDataForIndex((*i)->index(), height);
+                        }
+
+                        // then create the elements:
+                        int i;
+                        for (i = skirtIndex; i < (int)verts->size() - 2; i += 2)
+                            addSkirtTriangles(i, i + 2);
+
+                        addSkirtTriangles(i, skirtIndex);
+                    }
                 }
             }
         }
-    }
 
-    // create mask geometry
-    bool skirtCreated = false;
+        // If the boundary doesn't intersect the tile, draw the entire tile
+        // as we normally would. Need to reset the masking marker.
+        else if (r == MaskGenerator::R_BOUNDARY_DOES_NOT_INTERSECT_TILE)
+        {
+            maskSet = 0L;
+            for (osg::Vec3Array::iterator i = texCoords->begin(); i != texCoords->end(); ++i)
+                i->z() = MASK_MARKER_NORMAL;
+        }
+
+        // If the boundary contains the entire tile, draw nothing!
+        else // if (r == MaskGenerator::R_BOUNDARY_CONTAINS_ENTIRE_TILE)
+        {
+            tessellateSurface = false;
+        }
+    }
 
-    if (maskSet)
+    // Now tessellate the (unmasked) surface.
+    
+    if (tessellateSurface)
     {
-        int s = verts->size();
-        osg::ref_ptr<osg::DrawElementsUInt> maskPrim = maskSet->createMaskPrimitives(mapInfo, verts, texCoords, normals, neighbors);
-        if (maskPrim && maskPrim->size() > 0)
-        {
-            maskPrim->setElementBufferObject(primSet->getElementBufferObject());
-            geom->setMaskElements(maskPrim.get());
+        // TODO: do we really need this??
+        bool swapOrientation = !locator->orientationOpenGL();
 
-            if (createSkirt)
+        for(unsigned j=0; j<tileSize-1; ++j)
+        {
+            for(unsigned i=0; i<tileSize-1; ++i)
             {
-                // Skirts for masking geometries are complicated. There are two parts.
-                // The first part is the "perimeter" of the tile, i.e the outer edge of the 
-                // tessellation. This code will detect that outer boundary and create skrits
-                // for it.
-                // The second part (NYI) detects the actual inner boundary ("patch geometry")
-                // that patches the tile tessellation to the masking boundary. TDB.
-                TopologyGraph topo;
-                BuildTopologyVisitor visitor(topo);
-                visitor.apply(geom.get(), verts); 
-
-                if (topo._verts.empty() == false)
+                int i00;
+                int i01;
+                if (swapOrientation)
                 {
-                    TopologyGraph::IndexVector boundary;
-                    topo.createBoundary(boundary);
-                
-                    double height = tileBound.radius() * _options.heightFieldSkirtRatio().get();
-                    unsigned skirtIndex = verts->size();
+                    i01 = j*tileSize + i;
+                    i00 = i01+tileSize;
+                }
+                else
+                {
+                    i00 = j*tileSize + i;
+                    i01 = i00+tileSize;
+                }
+
+                int i10 = i00+1;
+                int i11 = i01+1;
 
-                    unsigned matches = 0;
-                    for (TopologyGraph::IndexVector::const_iterator i = boundary.begin(); i != boundary.end(); ++i)
+                // skip any triangles that have a discarded vertex:
+                bool discard = maskSet && (
+                    maskSet->isMasked( (*texCoords)[i00] ) ||
+                    maskSet->isMasked( (*texCoords)[i11] )
+                );
+
+                if ( !discard )
+                {
+                    discard = maskSet && maskSet->isMasked( (*texCoords)[i01] );
+                    if ( !discard )
                     {
-                        int k;
-                        for (k = 0; k<skirtIndex; ++k)
-                        {
-                            if ((*verts)[k].x() == (*i)->x() && (*verts)[k].y() == (*i)->y())
-                            {
-                                addSkirtDataForIndex(k, height);
-                                matches++;
-                                break;
-                            }
-                        }
+                        primSet->addElement(i01);
+                        primSet->addElement(i00);
+                        primSet->addElement(i11);
                     }
-
-                    if (matches != boundary.size()) {
-                        OE_WARN << LC << "matches != boundary size" << std::endl;
+            
+                    discard = maskSet && maskSet->isMasked( (*texCoords)[i10] );
+                    if ( !discard )
+                    {
+                        primSet->addElement(i00);
+                        primSet->addElement(i10);
+                        primSet->addElement(i11);
                     }
-
-                    int n;
-                    for (n = skirtIndex; n<(int)verts->size()-2; n+=2)
-                        addMaskSkirtTriangles(n, n+2);
-
-                    addMaskSkirtTriangles(n, skirtIndex);
-
-                    skirtCreated = true;
                 }
             }
         }
-    }
 
-    if ( createSkirt && !skirtCreated )
-    {
-        // SKIRTS:
-        // calculate the skirt extrusion height
-        double height = tileBound.radius() * _options.heightFieldSkirtRatio().get();
+        // Build skirts for the tile geometry
+        if ( createSkirt )
+        {
+            // SKIRTS:
+            // calculate the skirt extrusion height
+            double height = tileBound.radius() * _options.heightFieldSkirtRatio().get();
         
-        unsigned skirtIndex = verts->size();
+            unsigned skirtIndex = verts->size();
 
-        // first, create all the skirt verts, normals, and texcoords.
-        for(int c=0; c<(int)tileSize-1; ++c)
-            addSkirtDataForIndex( c, height ); //top
+            // first, create all the skirt verts, normals, and texcoords.
+            for(int c=0; c<(int)tileSize-1; ++c)
+                addSkirtDataForIndex( c, height ); //top
 
-        for(int r=0; r<(int)tileSize-1; ++r)
-            addSkirtDataForIndex( r*tileSize+(tileSize-1), height ); //right
+            for(int r=0; r<(int)tileSize-1; ++r)
+                addSkirtDataForIndex( r*tileSize+(tileSize-1), height ); //right
     
-        for(int c=tileSize-1; c>=0; --c)
-            addSkirtDataForIndex( (tileSize-1)*tileSize+c, height ); //bottom
+            for(int c=tileSize-1; c>=0; --c)
+                addSkirtDataForIndex( (tileSize-1)*tileSize+c, height ); //bottom
 
-        for(int r=tileSize-1; r>=0; --r)
-            addSkirtDataForIndex( r*tileSize, height ); //left
+            for(int r=tileSize-1; r>=0; --r)
+                addSkirtDataForIndex( r*tileSize, height ); //left
     
-        // then create the elements indices:
-        int i;
-        for(i=skirtIndex; i<(int)verts->size()-2; i+=2)
-            addSkirtTriangles( i, i+2 );
+            // then create the elements indices:
+            int i;
+            for(i=skirtIndex; i<(int)verts->size()-2; i+=2)
+                addSkirtTriangles( i, i+2 );
 
-        addSkirtTriangles( i, skirtIndex );
+            addSkirtTriangles( i, skirtIndex );
+        }
     }
 
     return geom.release();
@@ -592,7 +627,11 @@ SharedGeometry::empty() const
 
 
 #ifdef SUPPORTS_VAO
+#if OSG_MIN_VERSION_REQUIRED(3,5,9)
+osg::VertexArrayState* SharedGeometry::createVertexArrayStateImplementation(osg::RenderInfo& renderInfo) const
+#else
 osg::VertexArrayState* SharedGeometry::createVertexArrayState(osg::RenderInfo& renderInfo) const
+#endif
 {
     osg::State& state = *renderInfo.getState();
 


=====================================
src/osgEarthDrivers/engine_rex/MaskGenerator
=====================================
--- a/src/osgEarthDrivers/engine_rex/MaskGenerator
+++ b/src/osgEarthDrivers/engine_rex/MaskGenerator
@@ -26,7 +26,7 @@
 
 #define MASK_MARKER_DISCARD  0.0f    // do not draw
 #define MASK_MARKER_NORMAL   1.0f    // normal vertex
-#define MASK_MARKER_SKIRT    2.0f    // not subject to morphing
+#define MASK_MARKER_PATCH    2.0f    // not subject to morphing
 #define MASK_MARKER_BOUNDARY 3.0f    // not subject to elevation texture
 
 namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
@@ -52,47 +52,58 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
 
     /**
      * Creates geometry for the part of a tile containing mask data.
+     * Used internally by GeometryPool.
      */
     class MaskGenerator : public osg::Referenced
     {
     public:
-        MaskGenerator(const TileKey& key, unsigned tileSize, const Map* map);
+        enum Result {
+            R_BOUNDARY_DOES_NOT_INTERSECT_TILE,
+            R_BOUNDARY_CONTAINS_ENTIRE_TILE,
+            R_BOUNDARY_INTERSECTS_TILE
+        };
 
+    public:
+        MaskGenerator(const TileKey& key, unsigned tileSize, const Map* map);
 
+        //! True if this tile has masking data at all
         bool hasMasks() const
         {
             return _maskRecords.size() > 0;
         }
 
-        /** whether a texcoord indicates that the corresponding vert is masked. */
+        //! whether a texcoord indicates that the corresponding vert is masked.
         bool isMasked(const osg::Vec3f& texCoord) const
         {
             return texCoord.z() == MASK_MARKER_DISCARD;
         }
 
-        /** whether the masking geometry contains a unit location. */
-        /*  0.0 - contains                                         */
-        /*  1.0 - does not contain                                 */
-        /*  2.0 - does not contain but is a tile vert on the outer */
-        /*        masking skirt boundary                           */
-        float getMarker(float nx, float ny) const;
-
-        bool containedByQuadAtColRow(int col, int row, int tileSize) const
+        //! True if the texcoord indicates a bounary vertex
+        bool isBoundary(const osg::Vec3f& texCoord) const
         {
-            // Placeholder for now.
-            return false;
+            return texCoord.z() == MASK_MARKER_BOUNDARY;
         }
 
+        //! returns once of the MASK_MARKER_* defines for the given NDC location
+        float getMarker(float nx, float ny) const;
+
         //! Gets the LL and UR corners of the "patch rectangle" in NDC space
         void getMinMax(osg::Vec3d& min, osg::Vec3d& max);
 
-        osg::DrawElementsUInt* createMaskPrimitives(const MapInfo& mapInfo, osg::Vec3Array* verts, osg::Vec3Array* texCoords,  osg::Vec3Array* normals, osg::Vec3Array* neighbors);
+        //! Generates all the masking geometry and appened it to the passed-in arrays.
+        Result createMaskPrimitives(
+            const MapInfo&  mapInfo, 
+            osg::Vec3Array* verts, 
+            osg::Vec3Array* texCoords,
+            osg::Vec3Array* normals,
+            osg::Vec3Array* neighbors,
+            osg::ref_ptr<osg::DrawElementsUInt>& out_elements);
 
     protected:
         void setupMaskRecord(const MapInfo& mapInfo, osg::Vec3dArray* boundary);
 
     protected:
-        const TileKey    _key;
+        const TileKey _key;
         unsigned _tileSize;
         MaskRecordVector _maskRecords;
         osg::Vec3d _ndcMin, _ndcMax;


=====================================
src/osgEarthDrivers/engine_rex/MaskGenerator.cpp
=====================================
--- a/src/osgEarthDrivers/engine_rex/MaskGenerator.cpp
+++ b/src/osgEarthDrivers/engine_rex/MaskGenerator.cpp
@@ -33,6 +33,120 @@ using namespace osgEarth::Symbology;
 
 #define MATCH_TOLERANCE 0.000001
 
+#define EQUIVALENT(X,Y) (osg::equivalent((double)(X), (double)(Y), MATCH_TOLERANCE))
+
+#define EQUIVALENT_2D(A, B) (EQUIVALENT(A->x(), B->x()) && EQUIVALENT(A->y(), B->y()))
+
+namespace
+{
+    void resample(Geometry* geom, double maxLen)
+    {
+        GeometryIterator i(geom);
+        while (i.hasMore())
+        {
+            Geometry* part = i.next();
+            if (part->size() < 2) continue;
+
+            std::vector<osg::Vec3d> newGeom;
+            ConstSegmentIterator csi(part, true);
+            while(csi.hasMore())
+            {
+                Segment seg = csi.next();
+                newGeom.push_back(seg.first);
+                osg::Vec3d vec3d = seg.second - seg.first;
+                osg::Vec2d vec2d(vec3d.x(), vec3d.y());
+                double len2d = vec2d.length();
+
+                if (len2d > maxLen)
+                {
+                    double numNewPoints = ::floor(len2d/maxLen);
+                    double interval = len2d/(numNewPoints+1.0);
+                    for (double d=interval; d<len2d; d+=interval)
+                    {
+                        double t = d/len2d;
+                            
+                        osg::Vec3d newPoint(
+                            seg.first.x() + vec2d.x()*t,
+                            seg.first.y() + vec2d.y()*t,
+                            seg.first.z() + vec3d.z()*t);
+
+                        if (newGeom.empty() || newPoint != newGeom.back())
+                        {
+                            newGeom.push_back(newPoint);
+                        }
+
+                    }
+                }
+            }
+
+            if (newGeom.empty() == false)
+            {
+                part->swap(newGeom);
+            }
+        }
+
+        geom->removeDuplicates();
+    }
+
+    //! Compares two 3D points ignoring the Z value.
+    struct less_2d
+    {
+        bool operator()(const osg::Vec3& lhs, const osg::Vec3& rhs) const
+        {
+            if (lhs[0] < rhs[0]) return true;
+            else if (lhs[0] > rhs[0]) return false;
+            else return lhs[1] < rhs[1];
+        }
+    };
+
+    //! Removes all duplicate points in a vertex array.
+    void removeDupes(osg::Vec3Array* verts)
+    {
+        unsigned finalSize = verts->size();
+        std::set<osg::Vec3, less_2d> unique;
+        for (unsigned i = 0; i<finalSize; ++i)
+        {
+            if (unique.find( (*verts)[i] ) == unique.end())
+            {
+                unique.insert((*verts)[i]);
+            }
+            else
+            {
+                (*verts)[i] = verts->back();
+                --finalSize;
+            }
+        }
+
+        if (finalSize != verts->size())
+        {
+            verts->resize(finalSize);
+        }
+    }
+
+    //! Removes verts from the vec array that appear in the unique set.
+    void removeDupes(osg::Vec3Array* verts, const std::set<osg::Vec3, less_2d>& unique)
+    {
+        unsigned finalSize = verts->size();
+        
+        for (unsigned i = 0; i<finalSize; ++i)
+        {
+            if (unique.find( (*verts)[i] ) != unique.end())
+            {
+                (*verts)[i] = verts->back();
+                --finalSize;
+            }
+        }
+
+        if (finalSize != verts->size())
+        {
+            verts->resize(finalSize);
+            //OE_INFO << LC << "Removed " << verts->size() - finalSize << " duplicates.\n";
+        }
+    }
+}
+
+
+
 
 MaskGenerator::MaskGenerator(const TileKey& key, unsigned tileSize, const Map* map) :
 _key( key ), _tileSize(tileSize)
@@ -55,12 +169,15 @@ _key( key ), _tileSize(tileSize)
 void
 MaskGenerator::setupMaskRecord(const MapInfo& mapInfo, osg::Vec3dArray* boundary)
 {
+    // Make a "locator" for this key so we can do coordinate conversion:
     osg::ref_ptr<osgEarth::GeoLocator> geoLocator = GeoLocator::createForKey(_key, mapInfo);
+
     if (geoLocator->getCoordinateSystemType() == GeoLocator::GEOCENTRIC)
         geoLocator = geoLocator->getGeographicFromGeocentric();
 
     if ( boundary )
     {
+        // Calculate the axis-aligned bounding box of the boundary polygon:
         osg::Vec3d min, max;
         min = max = boundary->front();
 
@@ -79,20 +196,25 @@ MaskGenerator::setupMaskRecord(const MapInfo& mapInfo, osg::Vec3dArray* boundary
             max.y() = it->y();
         }
 
+        // convert that bounding box to "unit" space (0..1 across the tile)
         osg::Vec3d min_ndc, max_ndc;
         geoLocator->modelToUnit(min, min_ndc);
         geoLocator->modelToUnit(max, max_ndc);
 
+        // true if boundary overlaps tile in X dimension:
         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));
 
+        // true if boundary overlaps tile in Y dimension:
         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)
         {
+            // yes, boundary overlaps tile, so expand the global NDC bounding
+            // box to include the new mask:
             if (_maskRecords.size() == 0)
             {
                 _ndcMin = min_ndc;
@@ -106,34 +228,40 @@ MaskGenerator::setupMaskRecord(const MapInfo& mapInfo, osg::Vec3dArray* boundary
                 if (max_ndc.y() > _ndcMax.y()) _ndcMax.y() = max_ndc.y();
             }
 
+            // and add this mask to the list.
             _maskRecords.push_back( MaskRecord(boundary, min_ndc, max_ndc, 0L) );
         }
     }
 }
 
-osg::DrawElementsUInt*
-MaskGenerator::createMaskPrimitives(const MapInfo& mapInfo, osg::Vec3Array* verts, osg::Vec3Array* texCoords, osg::Vec3Array* normals, osg::Vec3Array* neighbors)
+MaskGenerator::Result
+MaskGenerator::createMaskPrimitives(const MapInfo& mapInfo, 
+                                    osg::Vec3Array* verts, osg::Vec3Array* texCoords, 
+                                    osg::Vec3Array* normals, osg::Vec3Array* neighbors,
+                                    osg::ref_ptr<osg::DrawElementsUInt>& out_elements)
 {
     if (_maskRecords.size() <= 0)
-      return 0L;
+    {
+        return R_BOUNDARY_DOES_NOT_INTERSECT_TILE;
+    }
 
     osg::ref_ptr<osgEarth::GeoLocator> geoLocator = GeoLocator::createForKey(_key, mapInfo);
     if (geoLocator->getCoordinateSystemType() == GeoLocator::GEOCENTRIC)
         geoLocator = geoLocator->getGeographicFromGeocentric();
 
+    // Configure up a local tangent plane at the centroid of the tile:
     GeoPoint centroid;
     _key.getExtent().getCentroid( centroid );
-
     osg::Matrix world2local, local2world;
     centroid.createWorldToLocal( world2local );
     local2world.invert( world2local );
 
-    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;
+    // This array holds the NDC grid of points inside the patch polygon
+    // (built later in this method)
+    osg::ref_ptr<osg::Vec3Array> coordsArray = new osg::Vec3Array();
 
+    // Calculate the combined axis-aligned NDC bounding box for all masks:
+    // (gw: didn't we already do this in setupMaskRecord?)
     double minndcx = _maskRecords[0]._ndcMin.x();
     double minndcy = _maskRecords[0]._ndcMin.y();
     double maxndcx = _maskRecords[0]._ndcMax.x();
@@ -158,6 +286,8 @@ MaskGenerator::createMaskPrimitives(const MapInfo& mapInfo, osg::Vec3Array* vert
         }			
     }
 
+    // Figure out the indices representing the area we need to "cut out"
+    // of the tile to accommadate the mask:
     int min_i = (int)floor(minndcx * (double)(_tileSize-1));
     if (min_i < 0) min_i = 0;
     if (min_i >= (int)_tileSize) min_i = _tileSize - 1;
@@ -174,340 +304,374 @@ MaskGenerator::createMaskPrimitives(const MapInfo& mapInfo, osg::Vec3Array* vert
     if (max_j < 0) max_j = 0;
     if (max_j >= (int)_tileSize) max_j = _tileSize - 1;
 
-    if (min_i >= 0 && max_i >= 0 && min_j >= 0 && max_j >= 0)
+    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;
+        return R_BOUNDARY_DOES_NOT_INTERSECT_TILE;
+    }
 
-        // The "patch polygon" is the region that stitches the normal tile geometry to the mask boundary.
-        osg::ref_ptr<Polygon> patchPoly = new Polygon();
-        patchPoly->resize(num_i * 2 + num_j * 2 - 4);
+    
+    // The "patch polygon" is the region that stitches the normal tile geometry to the mask boundary.
+    // The patch will be in NDC coordinates:
 
-        for (int i = 0; i < num_i; i++)
+    // Number of verts wide (i) and height(i) of the patch polygon:
+    int num_i = max_i - min_i + 1;
+    int num_j = max_j - min_j + 1;
+
+    osg::ref_ptr<Polygon> patchPoly = new Polygon();
+    patchPoly->resize(num_i * 2 + num_j * 2 - 4);
+
+    // top and bottom verts:
+    for (int i = 0; i < num_i; i++)
+    {
         {
-            {
-                osg::Vec3d ndc( ((double)(i + min_i))/(double)(_tileSize-1), ((double)min_j)/(double)(_tileSize-1), 0.0);
-                (*patchPoly)[i] = ndc;
-            }
+            osg::Vec3d ndc( ((double)(i + min_i))/(double)(_tileSize-1), ((double)min_j)/(double)(_tileSize-1), 0.0);
+            (*patchPoly)[i] = ndc;
+        }
 
-            {
-                osg::Vec3d ndc( ((double)(i + min_i))/(double)(_tileSize-1), ((double)max_j)/(double)(_tileSize-1), 0.0);
-                (*patchPoly)[i + (2 * num_i + num_j - 3) - 2 * i] = ndc;
-            }
+        {
+            // bottom:
+            osg::Vec3d ndc( ((double)(i + min_i))/(double)(_tileSize-1), ((double)max_j)/(double)(_tileSize-1), 0.0);
+            (*patchPoly)[i + (2 * num_i + num_j - 3) - 2 * i] = ndc;
         }
-        for (int j = 0; j < num_j - 2; j++)
+    }
+
+    // left and right verts:
+    for (int j = 0; j < num_j - 2; j++)
+    {
         {
-            {
-                osg::Vec3d ndc( ((double)max_i)/(double)(_tileSize-1), ((double)(min_j + j + 1))/(double)(_tileSize-1), 0.0);
-                (*patchPoly)[j + num_i] = ndc;
-            }
+            // right:
+            osg::Vec3d ndc( ((double)max_i)/(double)(_tileSize-1), ((double)(min_j + j + 1))/(double)(_tileSize-1), 0.0);
+            (*patchPoly)[j + num_i] = ndc;
+        }
 
-            {
-                osg::Vec3d ndc( ((double)min_i)/(double)(_tileSize-1), ((double)(min_j + j + 1))/(double)(_tileSize-1), 0.0);
-                (*patchPoly)[j + (2 * num_i + 2 * num_j - 5) - 2 * j] = ndc;
-            }
+        {
+            osg::Vec3d ndc( ((double)min_i)/(double)(_tileSize-1), ((double)(min_j + j + 1))/(double)(_tileSize-1), 0.0);
+            (*patchPoly)[j + (2 * num_i + 2 * num_j - 5) - 2 * j] = ndc;
         }
+    }
 
-        for (int j = 0; j < num_j; j++)
+    // create a grid of points making up the inside of the patch polygon.
+    for (int j = 0; j < num_j; j++)
+    {
+        for (int i = 0; i < num_i; i++)
         {
-            for (int i = 0; i < num_i; i++)
             {
-                {
-                    osg::Vec3d ndc( ((double)(i + min_i))/(double)(_tileSize-1), ((double)(j+min_j))/(double)(_tileSize-1), 0.0);
-                    coordsArray->push_back(ndc) ;
-                }						
-            }
+                osg::Vec3d ndc( ((double)(i + min_i))/(double)(_tileSize-1), ((double)(j+min_j))/(double)(_tileSize-1), 0.0);
+                coordsArray->push_back(ndc) ;
+            }						
         }
+    }
 
+    double patchArea = patchPoly->getSignedArea2D();
 
-        //
-        osg::ref_ptr<osg::Vec3dArray> boundaryVerts = new osg::Vec3dArray;
+    std::set<osg::Vec3d, less_2d> boundaryVerts;
 
-        // Use delaunay triangulation for stitching:
-        for (MaskRecordVector::iterator mr = _maskRecords.begin();mr != _maskRecords.end();mr++)
+    osg::ref_ptr<osgUtil::DelaunayConstraint> dc = new osgUtil::DelaunayConstraint();
+    osg::Vec3Array* constraintVerts = new osg::Vec3Array();
+    dc->setVertexArray(constraintVerts);
+
+    // Use delaunay triangulation for stitching:
+    for (MaskRecordVector::iterator mr = _maskRecords.begin();mr != _maskRecords.end();mr++)
+    {
+        //Create local polygon representing mask
+        osg::ref_ptr<Polygon> boundaryPoly = new Polygon();
+        boundaryPoly->reserve(mr->_boundary->size());
+        for (osg::Vec3dArray::iterator it = (*mr)._boundary->begin(); it != (*mr)._boundary->end(); ++it)
         {
-            //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;
-                geoLocator->convertModelToLocal(*it, local);
-                maskPoly->push_back(local);
-            }
+            osg::Vec3d local;
+            geoLocator->convertModelToLocal(*it, local);
+            boundaryPoly->push_back(local);
+        }
+            
+        // Resample the masking polygon to closely match the resolution of the 
+        // current tile grid, which will result in a better tessellation.
+        // Ideally we would do this after cropping, but that is causing some
+        // triangulation errors. TODO -gw
+        if (!boundaryPoly->empty())
+        {
+            const double interval = 1.0 / double(_tileSize-1);
+            resample(boundaryPoly.get(), interval);
+        }
 
-            // 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 boundary to the patch polygon (i.e. the bounding box)
+        // for case where mask crosses tile edges
+        osg::ref_ptr<Geometry> boundaryPolyCroppedToTile;
+        boundaryPoly->crop(patchPoly.get(), boundaryPolyCroppedToTile);
 
-            //Crop the mask to the stitching poly (for case where mask crosses tile edge)
-            osg::ref_ptr<Geometry> maskCrop;
-            maskPoly->crop(patchPoly.get(), maskCrop);
+        // See the comment for the call to resample above. -gw
+        //if (boundaryPolyCroppedToTile.valid() && !boundaryPolyCroppedToTile->empty())
+        //{
+        //    const double interval = 1.0 / double(_tileSize-1);
+        //    resample(boundaryPolyCroppedToTile.get(), interval);
+        //}
 
-            GeometryIterator i( maskCrop.get(), false );
-            while( i.hasMore() )
-            {
-                Geometry* part = i.next();
-                if (!part)
-                    continue;
+        // Add the cropped boundary geometry as a Triangulation Constraint.
+        unsigned start = constraintVerts->size();
 
-                if (part->getType() == Geometry::TYPE_POLYGON)
-                {
-                    osg::ref_ptr<osg::Vec3Array> partVerts = part->createVec3Array();
-                    maskConstraint->insert(maskConstraint->end(), partVerts->begin(), partVerts->end());
-                    newdc->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_LOOP, maskConstraint->size() - partVerts->size(), partVerts->size()));
-                }
-            }
+        GeometryIterator i( boundaryPolyCroppedToTile.get(), false );
+        while( i.hasMore() )
+        {
+            Geometry* part = i.next();
+            if (!part)
+                continue;
 
-            // Cropping strips z-values so need reassign
-            std::vector<int> isZSet;
-            for (osg::Vec3Array::iterator it = maskConstraint->begin(); it != maskConstraint->end(); ++it)
+            if (part->getType() == Geometry::TYPE_POLYGON)
             {
-                int zSet = 0;
+                osg::ref_ptr<osg::Vec3Array> partVerts = part->createVec3Array();
+                int offset = constraintVerts->size();
+                constraintVerts->reserve(constraintVerts->size() + partVerts->size());
+                constraintVerts->insert(constraintVerts->end(), partVerts->begin(), partVerts->end());
+                dc->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_LOOP, offset, partVerts->size()));
+            }
+        }
 
-                //Look for verts that belong to the original mask patch polygon
-                for (Polygon::iterator mit = patchPoly->begin(); mit != patchPoly->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;
+        // Cropping strips z-values! so we need reassign them.            
+        osg::Vec3Array::iterator it_start = constraintVerts->begin() + start;
+        std::vector<int> isZSet;
+        for (osg::Vec3Array::iterator it = it_start; it != constraintVerts->end(); ++it)
+        {
+            int zSet = 0;
 
-                        // 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);
+            // Search the patch (bounding box) polygon for matching points:
+            for (Polygon::iterator mit = patchPoly->begin(); mit != patchPoly->end(); ++mit)
+            {
+                if (EQUIVALENT_2D(mit, it))
+                {
+                    //(*it).z() = (*mit).z(); // commented out by Jeff...why?
+                    zSet += 1;
 
-                        break;
+                    // Remove duplicate point from coordsArray to avoid duplicate point warnings
+                    osg::Vec3Array::iterator caIt;
+                    for (caIt = coordsArray->begin(); caIt != coordsArray->end(); ++caIt)
+                    {
+                        if (EQUIVALENT_2D(caIt, it))
+                            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)
+            // Search the original uncropped boundary polygon for matching points,
+            // and build a set of boundary vertices.
+            for (Polygon::iterator mit = boundaryPoly->begin(); mit != boundaryPoly->end(); ++mit)
+            {
+                if (EQUIVALENT_2D(mit, it))
                 {
-                    if (osg::absolute((*mit).x() - (*it).x()) < MATCH_TOLERANCE && osg::absolute((*mit).y() - (*it).y()) < MATCH_TOLERANCE)
-                    {
-                        (*it).z() = (*mit).z();
-                        zSet += 2;
+                    (*it).z() = (*mit).z();
+                    zSet += 2;
 
-                        boundaryVerts->push_back((*it));
-                        break;
-                    }
+                    boundaryVerts.insert( *it );
+                    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)
+            isZSet.push_back(zSet);
+        }
+
+        // Any mask patch verts that are still unset are newly created verts where the patch
+        // meets the mask. (Do you mean, where the boundary crosses the tile edge? -gw) 
+        // 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 = it_start; it != constraintVerts->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 patch polygon it may need to be 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])
             {
-                //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 = boundaryPoly->begin(); mit != boundaryPoly->end(); ++mit)
                 {
-                    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));
+                    osg::Vec3d p1 = *mit;
+                    osg::Vec3d p3 = mit == --boundaryPoly->end() ? boundaryPoly->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;
 
-                        //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;
 
-                        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();
 
-                        if ((p1x < p3x ? p2x >= p1x && p2x <= p3x : p2x >= p3x && p2x <= p1x) &&
-                            (p1y < p3y ? p2y >= p1y && p2y <= p3y : p2y >= p3y && p2y <= p1y))
+                        double mRatio = 1.0;
+                        if (EQUIVALENT(p1x, p3x))
                         {
-                            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;
-
-                                boundaryVerts->push_back((*it));
-                                break;
-                            }
-                            else if (mRatio < closestRatio)
-                            {
-                                closestRatio = mRatio;
-                                closestZ = foundZ;
-                            }
+                            if (EQUIVALENT(p1x, p2x))
+                                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 (!isZSet[count] && closestRatio < DBL_MAX)
-                    {
-                        (*it).z() = closestZ;
-                        isZSet[count] = 2;
+                        if (mRatio < 0.01)
+                        {
+                            (*it).z() = foundZ;
+                            isZSet[count] = 2;
 
-                        boundaryVerts->push_back((*it));
+                            boundaryVerts.insert( *it );
+                            break;
+                        }
+                        else if (mRatio < closestRatio)
+                        {
+                            closestRatio = mRatio;
+                            closestZ = foundZ;
+                        }
                     }
                 }
 
-                if (!isZSet[count])
-                    OE_WARN << LC << "Z-value not set for mask constraint vertex" << std::endl;
-
-                count++;
+                if (!isZSet[count] && closestRatio < DBL_MAX)
+                {
+                    (*it).z() = closestZ;
+                    isZSet[count] = 2;
+                    boundaryVerts.insert( *it );
+                }
             }
 
-            alldcs.push_back(newdc);
+            if (!isZSet[count])
+                OE_WARN << LC << "Z-value not set for mask constraint vertex" << std::endl;
+
+            count++;
         }
+    }
 
-        trig->setInputPointArray(coordsArray.get());
+    // If we collected no constraints, that means the boundary geometry
+    // does not intersect the tile at all. Bail out now.
+    if (constraintVerts->empty())
+    {
+        return R_BOUNDARY_DOES_NOT_INTERSECT_TILE;
+    }
 
-        for (int dcnum =0; dcnum < alldcs.size();dcnum++)
-        {
-            trig->addInputConstraint(alldcs[dcnum].get());
-        }
+    // Set up a triangulator with the patch coordinates:
+    osg::ref_ptr<osgUtil::DelaunayTriangulator> trig = new osgUtil::DelaunayTriangulator();
+    trig->setInputPointArray(coordsArray.get());
+    trig->addInputConstraint(dc.get());
 
-        // Create array to hold vertex normals
-        osg::Vec3Array *norms=new osg::Vec3Array;
-        trig->setOutputNormalArray(norms);
+    // Create array to hold vertex normals
+    //osg::Vec3Array* norms = new osg::Vec3Array();
+    //trig->setOutputNormalArray(norms);
 
+    // Triangulate! 
+    trig->triangulate();
 
-        // 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());
-        }
+    // Remove any triangles interior to the boundaries.
+    // Note: an alternative here would be to flatten them all to a common height -gw
+    trig->removeInternalTriangles(dc.get());
+        
+    // Now build the new geometry.
+    const osg::Vec3Array* trigPoints = trig->getInputPointArray();
 
-        verts->reserve(verts->size() + trig->getInputPointArray()->size());
-        texCoords->reserve(texCoords->size() + trig->getInputPointArray()->size());
-        normals->reserve(normals->size() + trig->getInputPointArray()->size());
-        if ( neighbors )
-            neighbors->reserve(neighbors->size() + trig->getInputPointArray()->size()); 
+    // Reserve space; pre-allocating space is faster
+    verts->reserve(verts->size() + trigPoints->size());
+    texCoords->reserve(texCoords->size() + trigPoints->size());
+    normals->reserve(normals->size() + trigPoints->size());
+    if ( neighbors )
+        neighbors->reserve(neighbors->size() + trigPoints->size()); 
 
-        // Iterate through point to convert to model coords, calculate normals, and set up tex coords
-        osg::ref_ptr<GeoLocator> locator = GeoLocator::createForKey( _key, mapInfo );
+    // Iterate through point to convert to model coords, calculate normals, and set up tex coords
+    osg::ref_ptr<GeoLocator> locator = GeoLocator::createForKey( _key, mapInfo );
 
-        //int norm_i = -1;
-        unsigned vertsOffset = verts->size();
+    unsigned vertsOffset = verts->size();
 
-        for (osg::Vec3Array::iterator it = trig->getInputPointArray()->begin(); it != trig->getInputPointArray()->end(); ++it)
-        {
-            // check to see if point is a part of the original mask boundary
-            bool isBoundary = false;
-            for (osg::Vec3dArray::iterator bit = boundaryVerts->begin(); bit != boundaryVerts->end(); ++bit)
-            {
-                if (osg::absolute((*bit).x() - (*it).x()) < MATCH_TOLERANCE && osg::absolute((*bit).y() - (*it).y()) < MATCH_TOLERANCE)
-                {
-                    isBoundary = true;
-                    break;
-                }
-            }
+    for (osg::Vec3Array::const_iterator it = trigPoints->begin(); it != trigPoints->end(); ++it)
+    {
+        // check to see if point is a part of the original mask boundary
+        bool isBoundary = boundaryVerts.find(*it) != boundaryVerts.end();
 
-            // get model coords
-            osg::Vec3d model;
-            locator->unitToModel(osg::Vec3d(it->x(), it->y(), 0.0f), model);
-            model = model * world2local;
+        // get local coords
+        osg::Vec3d local;
+        locator->unitToModel(osg::Vec3d(it->x(), it->y(), 0.0f), local);
+        local = local * world2local;
 
-            // calc normals
-            osg::Vec3d modelPlusOne;
-            locator->unitToModel(osg::Vec3d(it->x(), it->y(), 1.0f), modelPlusOne);
-            osg::Vec3d normal = (modelPlusOne*world2local)-model;                
-            normal.normalize();
-            normals->push_back( normal );
+        // calc normals
+        osg::Vec3d localPlusOne;
+        locator->unitToModel(osg::Vec3d(it->x(), it->y(), 1.0f), localPlusOne);
+        osg::Vec3d normal = (localPlusOne*world2local)-local;                
+        normal.normalize();
+        normals->push_back( normal );
 
-            // set elevation if this is a point along the mask boundary
-            if (isBoundary)
-                model += normal*it->z();
+        // set elevation if this is a point along the mask boundary
+        if (isBoundary)
+            local += normal*it->z();
 
-            verts->push_back(model);
+        verts->push_back(local);
 
-            // use same vert for neighbor to prevent morphing
-            if ( neighbors )
-                neighbors->push_back( model );  
+        // use same vert for neighbor to prevent morphing
+        if ( neighbors )
+            neighbors->push_back( local );  
 
-            // set up text coords
-            texCoords->push_back( osg::Vec3f(it->x(), it->y(), isBoundary ? MASK_MARKER_BOUNDARY : MASK_MARKER_SKIRT) );
-        }
+        // set up text coords
+        texCoords->push_back( osg::Vec3f(it->x(), it->y(), isBoundary ? MASK_MARKER_BOUNDARY : MASK_MARKER_PATCH) );
+    }
 
-        // Get triangles from triangulator and add as primative set to the geometry
-        osg::DrawElementsUInt* tris = trig->getTriangles();
-        if ( tris && tris->getNumIndices() >= 3 )
-        {
-            osg::ref_ptr<osg::DrawElementsUInt> elems = new osg::DrawElementsUInt(tris->getMode());
-            elems->reserve(tris->size());
+    // Get triangles from triangulator and add as primitive set to the geometry
+    osg::DrawElementsUInt* tris = trig->getTriangles();
 
-            const osg::MixinVector<GLuint> ins = tris->asVector();
-            for (osg::MixinVector<GLuint>::const_iterator it = ins.begin(); it != ins.end(); ++it)
-            {
-                elems->push_back((*it) + vertsOffset);
-            }
+    // If something went wrong, just bail out. This should never happen
+    if (tris == 0L || tris->getNumIndices() < 3)
+    {
+        //OE_INFO << LC << "* Triangulation resulted in no geometry\n";
+        return R_BOUNDARY_CONTAINS_ENTIRE_TILE;
+    }
+
+    // Construct the output triangle set.
+    out_elements = new osg::DrawElementsUInt(tris->getMode());
+    out_elements->reserve(tris->size());
+
+    const osg::MixinVector<GLuint> ins = tris->asVector();
 
-            return elems.release();
+    for (osg::MixinVector<GLuint>::const_iterator it = ins.begin(); it != ins.end(); ++it)
+    {
+        unsigned i0 = vertsOffset + *it++;
+        unsigned i1 = vertsOffset + *it++;
+        unsigned i2 = vertsOffset + *it;
+
+        const osg::Vec3d& v0 = (*verts)[i0];
+        const osg::Vec3d& v1 = (*verts)[i1];
+        const osg::Vec3d& v2 = (*verts)[i2];
+
+        // check the winding order. Triangles don't always come out in the right orientation
+        if (((v0 - v1) ^ (v2 - v1)).z() < 0)
+        {
+            out_elements->push_back(i0);
+            out_elements->push_back(i1);
+            out_elements->push_back(i2);
+        }
+        else
+        {
+            out_elements->push_back(i0);
+            out_elements->push_back(i2);
+            out_elements->push_back(i1);
         }
     }
 
-    return 0L;
+    return R_BOUNDARY_INTERSECTS_TILE;
 }
 
 void
 MaskGenerator::getMinMax(osg::Vec3d& min, osg::Vec3d& max)
 {
-#if 1
     min = _ndcMin;
     max = _ndcMax;
-
-#else
-    if (_maskRecords.size() > 0)
-    {
-        min.x() = _maskRecords[0]._ndcMin.x();
-        min.y() = _maskRecords[0]._ndcMin.y();
-        min.z() = _maskRecords[0]._ndcMin.z();
-
-        max.x() = _maskRecords[0]._ndcMax.x();
-        max.y() = _maskRecords[0]._ndcMax.y();
-        max.z() = _maskRecords[0]._ndcMax.z();
-
-        for (MaskRecordVector::const_iterator it = _maskRecords.begin(); it != _maskRecords.end(); ++it)
-        {
-            if (it->_ndcMin.x() < min.x()) min.x() = it->_ndcMin.x();
-            if (it->_ndcMin.y() < min.y()) min.y() = it->_ndcMin.y();
-            if (it->_ndcMin.z() < min.z()) min.z() = it->_ndcMin.z();
-
-            if (it->_ndcMax.x() > max.x()) max.x() = it->_ndcMax.x();
-            if (it->_ndcMax.y() > max.y()) max.y() = it->_ndcMax.y();
-            if (it->_ndcMax.z() > max.z()) max.z() = it->_ndcMax.z();
-        }
-    }
-#endif
 }
 
 float
@@ -527,14 +691,14 @@ MaskGenerator::getMarker(float nx, float ny) const
 
         if (i > min_i && i < max_i && j > min_j && j < max_j)
         {
-            marker = MASK_MARKER_DISCARD; // contained by mask
+            marker = MASK_MARKER_DISCARD; // contained by boundary
         }
         else if ((i == min_i && j >= min_j && j <= max_j) ||
                  (i == max_i && j >= min_j && j <= max_j) ||
                  (j == min_j && i >= min_i && i <= max_i) ||
                  (j == max_j && i >= min_i && i <= max_i))
         {
-            marker = MASK_MARKER_SKIRT; // tile vert on outer mask skirt boundary
+            marker = MASK_MARKER_PATCH;
         }
     }
 


=====================================
src/osgEarthDrivers/engine_rex/RexEngine.elevation.glsl
=====================================
--- a/src/osgEarthDrivers/engine_rex/RexEngine.elevation.glsl
+++ b/src/osgEarthDrivers/engine_rex/RexEngine.elevation.glsl
@@ -10,7 +10,7 @@
 // Vertex Markers:
 #define MASK_MARKER_DISCARD  0.0
 #define MASK_MARKER_NORMAL   1.0
-#define MASK_MARKER_SKIRT    2.0
+#define MASK_MARKER_PATCH    2.0
 #define MASK_MARKER_BOUNDARY 3.0
 
 // stage


=====================================
src/osgEarthDrivers/engine_rex/RexTerrainEngineNode
=====================================
--- a/src/osgEarthDrivers/engine_rex/RexTerrainEngineNode
+++ b/src/osgEarthDrivers/engine_rex/RexTerrainEngineNode
@@ -93,6 +93,10 @@ namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
 
         osg::BoundingSphere computeBound() const;
 
+        void resizeGLObjectBuffers(unsigned maxSize);
+
+        void releaseGLObjects(osg::State* state) const;
+
     public: // MapCallback adapter functions
 
         void onMapModelChanged( const MapModelChange& change ); // not virtual!


=====================================
src/osgEarthDrivers/engine_rex/RexTerrainEngineNode.cpp
=====================================
--- a/src/osgEarthDrivers/engine_rex/RexTerrainEngineNode.cpp
+++ b/src/osgEarthDrivers/engine_rex/RexTerrainEngineNode.cpp
@@ -190,6 +190,46 @@ RexTerrainEngineNode::~RexTerrainEngineNode()
     OE_DEBUG << LC << "~RexTerrainEngineNode\n";
 }
 
+void 
+RexTerrainEngineNode::resizeGLObjectBuffers(unsigned maxSize)
+{
+    getStateSet()->resizeGLObjectBuffers(maxSize);
+
+    _terrain->getStateSet()->resizeGLObjectBuffers(maxSize);
+
+    _imageLayerStateSet.get()->resizeGLObjectBuffers(maxSize);
+
+    // TODO: where should this live? MapNode?
+    LayerVector layers;
+    getMap()->getLayers(layers);
+    for (LayerVector::const_iterator i = layers.begin(); i != layers.end(); ++i)
+    {
+        if ((*i)->getStateSet()) {
+            (*i)->getStateSet()->resizeGLObjectBuffers(maxSize);
+        }
+    }
+}
+
+void
+RexTerrainEngineNode::releaseGLObjects(osg::State* state) const
+{
+    getStateSet()->releaseGLObjects(state);
+
+    _terrain->getStateSet()->releaseGLObjects(state);
+
+    _imageLayerStateSet.get()->releaseGLObjects(state);
+
+    // TODO: where should this live? MapNode?
+    LayerVector layers;
+    getMap()->getLayers(layers);
+    for (LayerVector::const_iterator i = layers.begin(); i != layers.end(); ++i)
+    {
+        if ((*i)->getStateSet()) {
+            (*i)->getStateSet()->releaseGLObjects(state);
+        }
+    }
+}
+
 void
 RexTerrainEngineNode::setMap(const Map* map, const TerrainOptions& options)
 {


=====================================
src/osgEarthDrivers/mbtiles/MBTilesTileSource.cpp
=====================================
--- a/src/osgEarthDrivers/mbtiles/MBTilesTileSource.cpp
+++ b/src/osgEarthDrivers/mbtiles/MBTilesTileSource.cpp
@@ -193,10 +193,6 @@ MBTilesTileSource::initialize(const osgDB::Options* dbOptions)
         if ( _tileFormat.empty() )
             return Status::Error(Status::ConfigurationError, "Required format not in metadata, nor specified in the options.");
 
-        _rw = getReaderWriter( _tileFormat );
-        if ( !_rw.valid() )
-            return Status::Error(Status::ServiceUnavailable, "No plugin to load format \"" + _tileFormat + "\"");
-
         // check for compression.
         std::string compression;
         getMetaData("compression", compression);
@@ -361,7 +357,10 @@ MBTilesTileSource::createImage(const TileKey&    key,
             std::string value;
             if ( !_compressor->decompress(inputStream, value) )
             {
-                OE_WARN << LC << "Decompression failed" << std::endl;
+                if ( _options.filename().isSet() )
+                    OE_WARN << LC << "Decompression failed: " << _options.filename()->base() << std::endl;
+                else
+                    OE_WARN << LC << "Decompression failed" << std::endl;
                 valid = false;
             }
             else
@@ -374,11 +373,7 @@ MBTilesTileSource::createImage(const TileKey&    key,
         if ( valid )
         {
             std::istringstream inputStream(dataBuffer);
-            osgDB::ReaderWriter::ReadResult rr = _rw->readImage( inputStream, _dbOptions.get() );
-            if (rr.validImage())
-            {
-                result = rr.takeImage();
-            }
+            result = ImageUtils::readStream(inputStream, _dbOptions.get());
         }
     }
     else


=====================================
src/osgEarthSymbology/Geometry.cpp
=====================================
--- a/src/osgEarthSymbology/Geometry.cpp
+++ b/src/osgEarthSymbology/Geometry.cpp
@@ -633,6 +633,9 @@ Geometry::getOrientation() const
 double
 Geometry::getLength() const
 {
+    if (empty())
+        return 0.0;
+
     double length = 0;
     for (unsigned int i = 0; i < size()-1; ++i)
     {
@@ -746,6 +749,9 @@ Ring::cloneAs( const Geometry::Type& newType ) const
 double
 Ring::getLength() const
 {
+    if (empty())
+        return 0.0;
+
     double length = Geometry::getLength();
     if ( isOpen() )
     {


=====================================
src/osgEarthUtil/Controls.cpp
=====================================
--- a/src/osgEarthUtil/Controls.cpp
+++ b/src/osgEarthUtil/Controls.cpp
@@ -2741,12 +2741,14 @@ ControlCanvas::init()
     _controlNodeBin = new ControlNodeBin();
     this->addChild( _controlNodeBin->getControlGroup() );
    
+#if 0
 #if defined(OSG_GL_FIXED_FUNCTION_AVAILABLE)
     // don't use shaders unless we have to.
     this->getOrCreateStateSet()->setAttributeAndModes(
         new osg::Program(), 
         osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE);
 #endif
+#endif
 }
 
 ControlCanvas::~ControlCanvas()


=====================================
src/osgEarthUtil/TopologyGraph
=====================================
--- a/src/osgEarthUtil/TopologyGraph
+++ b/src/osgEarthUtil/TopologyGraph
@@ -39,86 +39,134 @@ namespace osgEarth { namespace Util
     //! Stores the noded topology of a model with unique verticies and edge definitions.
     //! The verticies are stored rotated into the XY plane so that we can properly find
     //! the "bottom-most" vert and walk the boundary.
-    class OSGEARTHUTIL_EXPORT TopologyGraph
+    class OSGEARTHUTIL_EXPORT TopologyGraph : public osg::Object
     {
     public:
 
-        struct Vertex {
-            Vertex(const Vertex& rhs) : _drawable(rhs._drawable), _verts(rhs._verts), _index(rhs._index) { }
-            Vertex(osg::Drawable* drawable, osg::Vec3Array* verts, unsigned index) : _drawable(drawable), _verts(verts), _index(index) { }
-            osg::Drawable* _drawable;
-            osg::Vec3Array* _verts;
-            unsigned _index;
+        //! Represents a single vertex in the topology graph
+        struct Vertex
+        {
+            Vertex(const Vertex& rhs) : _verts(rhs._verts), _index(rhs._index), _graphID(0u) { }
+
+            Vertex(const osg::Vec3Array* verts, unsigned index) : _verts(verts), _index(index), _graphID(0u) { }
+
             const osg::Vec3& vertex() const { return (*_verts)[_index]; }
+
+            unsigned index() const { return _index; }
+
             float x() const { return (*_verts)[_index].x(); }
             float y() const { return (*_verts)[_index].y(); }
+
             bool operator < (const Vertex& rhs) const {
-                double dx = x() - rhs.x();
-                if (dx < 0.0) return true;
-                if (dx > 0.0) return false;
-                double dy = y() - rhs.y();
-                return dy < 0.0;
+                //if (_verts < rhs._verts) return true;
+                //if (_verts > rhs._verts) return false;
+                //return _index < rhs._index;
+                if (x() < rhs.x()) return true;
+                if (x() > rhs.x()) return false;
+                return y() < rhs.y();
             }
+
+            const osg::Vec3Array* _verts;   //! vertex array from which this vertex originated
+            unsigned _index;                //! index into the source vertex array
+            mutable unsigned _graphID;      //! which graph does this vertex belong to (in the event of multiple graphs)
         };
 
+        //! One or more Vertex objects
         typedef std::set<Vertex> VertexSet;
 
+        //! Iterator into a VertexSet.
         typedef VertexSet::iterator Index;
 
-        // custom comparator for Index so we can use it at a std::map key
+        //! Custom comparator for Index so we can use it at a std::map key
         struct IndexLess : public std::less<Index> {
             bool operator()(const Index& lhs, const Index& rhs) const {
                 return (*lhs) < (*rhs);
             }
         };
 
+        //! Sortable set of indices
         typedef std::set<Index, IndexLess> IndexSet;
 
-        typedef std::map<Index, IndexSet, IndexLess>  EdgeMap;
+        //! Maps a Vertex Index to a collection of Vertex indices (edges)
+        typedef std::map<Index, IndexSet, IndexLess> EdgeMap;
 
+        //! Vector of Vertex Indexes
         typedef std::vector<Index> IndexVector;
 
     public:
 
+        META_Object(osgEarthUtil, TopologyGraph);
+
+        //! CTOR
         TopologyGraph();
 
-        void createBoundary(IndexVector& output) const;
+        //! How many disconnected graphs are there in this topology?
+        unsigned getNumBoundaries() const;
+
+        //! Creates the boundary of the nth disconnected graph
+        void createBoundary(unsigned boundaryNum, IndexVector& output) const;
+
+    public:
+        void addTriangle(const osg::Vec3Array* verts, unsigned v0, unsigned v1, unsigned v2);
+
+        Index add(const osg::Vec3Array* verts, unsigned v);
+
+        void assignAndPropagate(TopologyGraph::Index& vertex, unsigned graphID);
 
     public:
         unsigned     _totalVerts;  // total number of verts encountered
-        //VertexSet    _vertsWorld;  // 
         VertexSet    _verts;       // set of unique verts in the topology (rotated into XY plane)
         EdgeMap      _edgeMap;     // maps each vert to all the verts with which it shares an edge
-        Index        _minY;        // points to the vert with the minimum Y coordinate (in XY plane)
-        osg::Matrixd _world2plane; // matrix that transforms into a localized XY plane
-        const osgEarth::SpatialReference* _srs;
+        unsigned     _maxGraphID;  // maximum graph id from builder (1=one graph)
 
         friend class TopologyBuilder;
         friend class BuildTopologyVisitor;
 
-        void dumpBoundary(const std::string& filename);
+        void dumpBoundary(const IndexVector& boundary, const std::string& filename);
+
+    protected:
+        // no copy ctor
+        TopologyGraph(const TopologyGraph& rhs, const osg::CopyOp& copy) { }
+
+        virtual ~TopologyGraph() { }
+
+        unsigned recomputeSubgraphs();
     };
 
+    typedef std::vector< osg::ref_ptr<TopologyGraph> > TopologyGraphVector;
 
     // A TriangleIndexFunctor that traverses a stream of triangles and builds a
     // topology graph from their points and edges.
     class OSGEARTHUTIL_EXPORT TopologyBuilder
     {
     public:
+        //! CTOR
         TopologyBuilder();
 
+        //! Creates a new topology graph from a single set of triangles.
+        //! @param[in ] verts Vertex array input
+        //! @param[in ] elems Elements defining triangle set
+        //! @param[in ] name  Optional name for error/warning output
+        //! @return     New TopologyGraph object
+        static TopologyGraph* create(
+            const osg::Vec3Array*    verts,
+            const osg::PrimitiveSet* elems,
+            const std::string&       name = std::string());
+
         typedef std::map<unsigned, TopologyGraph::Index> UniqueMap;
 
-        TopologyGraph*  _topology;     // topology to which to append point and edge data
-        osg::Drawable*  _drawable;     // source geometry
-        osg::Vec3Array* _verts;        // source vertex list
-        osg::Matrix     _local2world;  // transforms source verts into world coordinates
-        UniqueMap       _uniqueMap;    // prevents duplicates
+        TopologyGraph* _graph;           // topology to which to append point and edge data
+        const osg::Drawable* _drawable;  // source geometry
+        const osg::Vec3Array* _verts;    // source vertex list
+        UniqueMap _uniqueMap;            // prevents duplicates
 
+        // entry point for the TriangleIndexFunctor
         void operator()( unsigned v0, unsigned v1, unsigned v2 );
 
         TopologyGraph::Index add( unsigned v );
 
+        void assignAndPropagate(TopologyGraph::Index& vertex, unsigned graphID);
+
         friend class TopologyBuilderVisitor;
     };
 
@@ -136,10 +184,11 @@ namespace osgEarth { namespace Util
         // add the contents of a Geometry to the topology
         void apply( osg::Drawable& drawable );
 
+        // ???
         void apply( osg::Drawable* drawable, osg::Vec3Array* verts );
 
         std::vector<osg::Matrixd> _matrixStack;
-        TopologyGraph&            _topology;
+        TopologyGraph& _graph;
     };
 
 } }


=====================================
src/osgEarthUtil/TopologyGraph.cpp
=====================================
--- a/src/osgEarthUtil/TopologyGraph.cpp
+++ b/src/osgEarthUtil/TopologyGraph.cpp
@@ -36,62 +36,56 @@ using namespace osgEarth::Util;
 #include <vector>
 #include <set>
 
+#define LC "[TopologyGraph] "
+
 
 TopologyGraph::TopologyGraph() :
-_minY(_verts.end()),
-_totalVerts(0),
-_srs(0L)
+_maxGraphID(0u)
 {
     //nop
 }
 
+unsigned
+TopologyGraph::getNumBoundaries() const
+{
+    return _maxGraphID;
+}
 
 void
-TopologyGraph::createBoundary(TopologyGraph::IndexVector& output) const
+TopologyGraph::createBoundary(unsigned graphNum, TopologyGraph::IndexVector& output) const
 {
-    // the normal defines the XY plane in which to search for a boundary
-    osg::Vec3d normal(0,0,1);
-    osg::Vec3d center;
-
-#if 0
-    if ( geocentric )
+    // nothing to do - bail
+    if (_verts.empty() || graphNum+1 > _maxGraphID)
+        return;
+
+    // graph ID is one more than the graph number passed in:
+    unsigned graphID = graphNum + 1u;
+
+    // Find the starting point (vertex with minimum Y) for this graph ID.
+    // By the nature of disconnected graphs, that start point is all we need
+    // to ensure we are walking a single connected mesh.
+    Index vstart = _verts.end();
+    for (VertexSet::const_iterator vert = _verts.begin(); vert != _verts.end(); ++vert)
     {
-        // define the XY plane based on the normal to the center of the dataset:
-        osg::BoundingSphere bs = node->getBound();
-        center = bs.center();
-        normal = center;
-        normal.normalize();
-        OE_DEBUG << "Normal = " << normal.x() << ", " << normal.y() << ", " << normal.z() << std::endl;
-    }
-#endif
-
-
-#if 0
-    // set up a transform that will localize geometry into an XY plane
-    if ( geocentric )
-    {
-        topology._world2plane.makeRotate(normal, osg::Vec3d(0,0,1));
-        topology._world2plane.preMultTranslate(-center);
-
-        // if this is set, use mercator projection instead of a simple geolocation
-        //topology._srs = osgEarth::SpatialReference::get("spherical-mercator");
+        if (vert->_graphID == graphID) 
+        {
+            if (vstart == _verts.end() || vert->y() < vstart->y())
+            {
+                vstart = vert;
+            }
+        }
     }
-#endif
-
-    // build the topology
-    //BuildTopologyVisitor buildTopoVisitor(topology);
-    //node->accept( buildTopoVisitor );
 
-    //OE_DEBUG << "Found " << topology._verts.size() << " unique verts" << std::endl;
-    //dumpPointCloud(topology);
+    // couldn't find a start point - bail (should never happen)
+    if (vstart == _verts.end())
+        return;
 
     // starting with the minimum-Y vertex (which is guaranteed to be in the boundary)
     // traverse the outside of the point set. Do this by sorting all the edges by
-    // their angle relative to the vector to the previous point. The vector with the
-    // smallest angle represents the edge connecting the current point to the next
-    // boundary point. Walk the edge until we return to the beginning.
-    
-    Index vptr      = _minY;
+    // their angle relative to the vector from the previous point. The "leftest" turn
+    // represents the edge connecting the current point to the next boundary point.
+    // Thusly we walk the boundary counterclockwise until we return to the start point.
+    Index vptr = vstart;
     Index vptr_prev = _verts.end();
 
     IndexSet visited;
@@ -105,29 +99,36 @@ TopologyGraph::createBoundary(TopologyGraph::IndexVector& output) const
         osg::Vec2d vert ( vptr->x(), vptr->y() );
 
         // construct the "base" vector that points from the previous 
-        // point to the current point; or to -X in the initial case
+        // point to the current point; or to +X in the initial case
         osg::Vec2d base;
         if ( vptr_prev == _verts.end() )
-            base.set( -1, 0 );
+        {
+            base.set(1, 0);
+        }
         else
+        {
             base = vert - osg::Vec2d( vptr_prev->x(), vptr_prev->y() );
+            base.normalize();
+        }
             
         // pull up the edge set for this vertex:
         EdgeMap::const_iterator ei = _edgeMap.find(vptr);
         if (ei == _edgeMap.end())
             continue; // should be impossible
 
-        const IndexSet& edges = ei->second; //_edgeMap[vptr];
+        const IndexSet& edges = ei->second;
 
         // find the edge with the minimun delta angle to the base vector
-        double bestScore = DBL_MAX;
+        double bestScore = -DBL_MAX;
         Index  bestEdge  = _verts.end();
         
-        //OE_DEBUG << "VERTEX (" << 
-        //    vptr->x() << ", " << vptr->y() << ", " << vptr->z() 
+        //OE_NOTICE << "VERTEX (" << 
+        //    vptr->x() << ", " << vptr->y() << ", "
         //    << ") has " << edges.size() << " edges..."
         //    << std::endl;
 
+        unsigned possibleEdges = 0u;
+
         for( IndexSet::iterator e = edges.begin(); e != edges.end(); ++e )
         {
             // don't go back from whence we just came
@@ -138,28 +139,28 @@ TopologyGraph::createBoundary(TopologyGraph::IndexVector& output) const
             if ( visited.find(*e) != visited.end() )
                 continue;
 
+            ++possibleEdges;
+
             // calculate the angle between the base vector and the current edge:
             osg::Vec2d edgeVert( (*e)->x(), (*e)->y() );
             osg::Vec2d edge = edgeVert - vert;
-
-            base.normalize();
             edge.normalize();
+
             double cross = base.x()*edge.y() - base.y()*edge.x();
             double dot   = base * edge;
-            double score = dot;
 
-            if ( cross < 0.0 )
-            {
-                double diff = 2.0-(score+1.0);
-                score = 1.0 + diff;
-            }
+            double score;
+            if (cross <= 0.0)
+                score = 1.0-dot; // [0..2]
+            else
+                score = dot-1.0; // [-2..0]
 
-            //OE_DEBUG << "   check: " << (*e)->x() << ", " << (*e)->y() << ", " << (*e)->z() << std::endl;
-            //OE_DEBUG << "   base = " << base.x() << ", " << base.y() << std::endl;
-            //OE_DEBUG << "   edge = " << edge.x() << ", " << edge.y() << std::endl;
-            //OE_DEBUG << "   crs = " << cross << ", dot = " << dot << ", score = " << score << std::endl;
+            //OE_NOTICE << "   check: " << (*e)->x() << ", " << (*e)->y() << std::endl;
+            //OE_NOTICE << "   base = " << base.x() << ", " << base.y() << std::endl;
+            //OE_NOTICE << "   edge = " << edge.x() << ", " << edge.y() << std::endl;
+            //OE_NOTICE << "   crs = " << cross << ", dot = " << dot << ", score = " << score << std::endl;
             
-            if ( score < bestScore )
+            if (score > bestScore)
             {
                 bestScore = score;
                 bestEdge = *e;
@@ -168,8 +169,11 @@ TopologyGraph::createBoundary(TopologyGraph::IndexVector& output) const
 
         if ( bestEdge == _verts.end() )
         {
-            // this will probably never happen
-            osg::notify(osg::WARN) << "Illegal state - reached a dead end!" << std::endl;
+            // this should never happen
+            // but sometimes does anyway
+            OE_WARN << LC << getName() << " - Illegal state - reached a dead end during boundary detection. Vertex (" 
+                << vptr->x() << ", " << vptr->y() << ") has " << possibleEdges << " possible edges.\n"
+                << std::endl;
             break;
         }
 
@@ -184,35 +188,16 @@ TopologyGraph::createBoundary(TopologyGraph::IndexVector& output) const
         visited.insert( vptr );
 
         // once we make it all the way around, we're done:
-        if ( vptr == _minY )
+        if ( vptr == vstart )
             break;
     }
-
-#if 0
-    // un-rotate the results from the XY plane back to their original frame:
-    if ( _srs )
-    {
-        const osgEarth::SpatialReference* ecef = _srs->getECEF();
-        topology._srs->transform(_result->asVector(), ecef);
-    }
-    else
-    {
-        osg::Matrix plane2world;
-        plane2world.invert( _world2plane );
-        for( osg::Vec3dArray::iterator i = _result->begin(); i != _result->end(); ++i )
-            (*i) = (*i) * plane2world;
-    }
-
-    return _result.release();
-#endif
 }
 
-
 void
-TopologyGraph::dumpBoundary(const std::string& filename)
+TopologyGraph::dumpBoundary(const IndexVector& boundary, const std::string& filename)
 {
-    IndexVector boundary;
-    createBoundary(boundary);
+    if (boundary.empty())
+        return;
     
     osg::Vec3Array* v = new osg::Vec3Array();
 
@@ -224,13 +209,12 @@ TopologyGraph::dumpBoundary(const std::string& filename)
 
     osg::ref_ptr<osg::Geometry> g = new osg::Geometry();
     g->setVertexArray(v);
-    g->addPrimitiveSet(new osg::DrawArrays(GL_LINE_STRIP, 0, v->size()));
+    g->addPrimitiveSet(new osg::DrawArrays(GL_LINE_LOOP, 0, v->size()));
     g->addPrimitiveSet(new osg::DrawArrays(GL_POINTS, 0, v->size()));
     g->getOrCreateStateSet()->setAttributeAndModes(new osg::Point(3));
     osgDB::writeNodeFile(*(g.get()), filename);
 }
 
-
 TopologyBuilder::TopologyBuilder()
 {
     //nop
@@ -239,17 +223,44 @@ TopologyBuilder::TopologyBuilder()
 void
 TopologyBuilder::operator()(unsigned v0, unsigned v1, unsigned v2)
 {
+    // Add three verts to the graph. Any of them may already exist
+    // in the graph. If so, make sure the existing graph IDs get
+    // properly propagated.
     TopologyGraph::Index i0 = add(v0);
     TopologyGraph::Index i1 = add(v1);
     TopologyGraph::Index i2 = add(v2);
 
+    if (i0->_graphID == 0u && i1->_graphID == 0u && i2->_graphID == 0u)
+    {
+        unsigned newGraphID = ++_graph->_maxGraphID;
+        i0->_graphID = i1->_graphID = i2->_graphID = newGraphID;
+    }
+    else
+    {
+        if (i0->_graphID != 0u)
+        {
+            assignAndPropagate(i1, i0->_graphID);
+            assignAndPropagate(i2, i0->_graphID);
+        }
+        else if (i1->_graphID != 0u)
+        {
+            assignAndPropagate(i0, i1->_graphID);
+            assignAndPropagate(i2, i1->_graphID);
+        }
+        else
+        {
+            assignAndPropagate(i0, i2->_graphID);
+            assignAndPropagate(i1, i2->_graphID);
+        }
+    }
+
     // add to the edge list for each of these verts
-    if (i0 != i1) _topology->_edgeMap[i0].insert(i1);
-    if (i0 != i2) _topology->_edgeMap[i0].insert(i2);
-    if (i1 != i0) _topology->_edgeMap[i1].insert(i0);
-    if (i1 != i2) _topology->_edgeMap[i1].insert(i2);
-    if (i2 != i0) _topology->_edgeMap[i2].insert(i0);
-    if (i2 != i1) _topology->_edgeMap[i2].insert(i1);
+    if (i0 != i1) _graph->_edgeMap[i0].insert(i1);
+    if (i0 != i2) _graph->_edgeMap[i0].insert(i2);
+    if (i1 != i0) _graph->_edgeMap[i1].insert(i0);
+    if (i1 != i2) _graph->_edgeMap[i1].insert(i2);
+    if (i2 != i0) _graph->_edgeMap[i2].insert(i0);
+    if (i2 != i1) _graph->_edgeMap[i2].insert(i1);
 }
 
 TopologyGraph::Index
@@ -259,51 +270,8 @@ TopologyBuilder::add(unsigned v)
     UniqueMap::iterator i = _uniqueMap.find(v);
     if (i == _uniqueMap.end())
     {
-#if 0
-        // no, so transform it into world coordinates, and rotate it into the XY plane
-        osg::Vec3d vert = (*_verts)[v];
-        osg::Vec3d world = vert * _local2world;
-        osg::Vec3d plane = world;
-
-        if (_topology->_srs)
-        {
-            const osgEarth::SpatialReference* ecef = _topology->_srs->getECEF();
-            ecef->transform(world, _topology->_srs, plane);
-        }
-        else
-        {
-            plane = world * _topology->_world2plane;
-        }
-#endif
-
-        TopologyGraph::Vertex vertex(_drawable, _verts, v);
-        std::pair<TopologyGraph::Index, bool> f = _topology->_verts.insert(vertex);
-        if (f.second == true) // insert succeeded
-        {
-            // this is a new location, so check it to see if it is the new "southernmost" point:
-            if (_topology->_minY == _topology->_verts.end() || vertex.y() < _topology->_minY->y())
-            {
-                _topology->_minY = f.first;
-            }
-        }
-
-#if 0
-        // insert it into the unique vert list
-        std::pair<TopologyGraph::VertexSet::iterator, bool> f = _topology->_verts.insert(plane);
-        if (f.second) // insert succedded
-        {
-            // this is a new location, so check it to see if it is the new "southernmost" point:
-            if (_topology->_minY == _topology->_verts.end() || plane.y() < _topology->_minY->y())
-            {
-                _topology->_minY = f.first;
-            }
-        }
-#endif
-
-        // store in the uniqueness map so we don't process the same index again
-        _uniqueMap[v] = f.first;
-
-        // return the index of the vert.
+        TopologyGraph::Vertex vertex(_verts, v);
+        std::pair<TopologyGraph::Index, bool> f = _graph->_verts.insert(vertex);
         return f.first;
     }
     else
@@ -312,12 +280,49 @@ TopologyBuilder::add(unsigned v)
     }
 }
 
+void
+TopologyBuilder::assignAndPropagate(TopologyGraph::Index& vertex, unsigned graphID)
+{
+    // already set, so no need to continue propagating
+    if (vertex->_graphID == graphID)
+        return;
+
+    // assign
+    vertex->_graphID = graphID;
+
+    // propagate along all edges
+    TopologyGraph::EdgeMap::iterator edges = _graph->_edgeMap.find(vertex);
+    if (edges != _graph->_edgeMap.end())
+    {
+        TopologyGraph::IndexSet& endPoints = edges->second;
+        for (TopologyGraph::IndexSet::iterator endPoint = endPoints.begin();
+            endPoint != endPoints.end();
+            ++endPoint)
+        {
+            TopologyGraph::Index vertex = *endPoint;
+            assignAndPropagate(vertex, graphID);
+        }
+    }
+}
+
+TopologyGraph*
+TopologyBuilder::create(const osg::Vec3Array* verts, const osg::PrimitiveSet* elems, const std::string& name)
+{
+    osg::ref_ptr<TopologyGraph> graph = new TopologyGraph();
+    graph->setName(name);
 
+    osg::TriangleIndexFunctor<TopologyBuilder> builder;
+    builder.setVertexArray(verts->getNumElements(), static_cast<const osg::Vec3*>(verts->getDataPointer()));
+    builder._verts = verts;
+    builder._graph = graph.get();
+    elems->accept(builder);
 
+    return graph.release();
+}
 
 BuildTopologyVisitor::BuildTopologyVisitor(TopologyGraph& graph) :
 osg::NodeVisitor(),
-_topology(graph)
+_graph(graph)
 {
     setTraversalMode(TRAVERSE_ALL_CHILDREN);
     setNodeMaskOverride(~0);
@@ -352,629 +357,10 @@ void
 BuildTopologyVisitor::apply(osg::Drawable* drawable, osg::Vec3Array* verts)
 {
     osg::TriangleIndexFunctor<TopologyBuilder> builder;
-    builder._topology = &_topology;
+    builder._graph = &_graph;
     builder._drawable = drawable;
     builder._verts = verts;
-    if (!_matrixStack.empty())
-        builder._local2world = _matrixStack.back();
-    _topology._totalVerts += verts->size();
+    //if (!_matrixStack.empty())
+    //    builder._local2world = _matrixStack.back();
     drawable->accept(builder);
 }
-
-#if 0
-
-/* Comparator used to sort osg::Vec3d's first by x and then by y */
-bool presortCompare (osg::Vec3d i, osg::Vec3d j)
-{
-  if (i.x() == j.x())
-    return i.y() < j.y();
-
-  return i.x() < j.x();
-}
-
-double BoundaryUtil::_tolerance = 0.005;
-
-void
-BoundaryUtil::setTolerance(double value)
-{
-    _tolerance = value;
-}
-
-/* Use the vertices of the given node to calculate a boundary via the
- * findHull() method.
- */
-osg::Vec3dArray* BoundaryUtil::getBoundary(osg::Node* modelNode, bool geocentric, bool convexHull)
-{
-  if (!modelNode)
-    return 0;
-
-  if ( convexHull )
-  {
-    VertexCollectionVisitor v(geocentric);
-    modelNode->accept(v);
-
-    osg::ref_ptr<osg::Vec3dArray> verts = v.getVertices();
-    verts = findHull(*verts);
-
-    osg::EllipsoidModel em;
-    for( osg::Vec3dArray::iterator i = verts->begin(); i != verts->end(); ++i )
-    {
-      em.convertLatLongHeightToXYZ( osg::DegreesToRadians(i->y()), osg::DegreesToRadians(i->x()), i->z(),
-        i->x(), i->y(), i->z() );      
-    }
-
-    return verts.release();
-  }
-  else
-  {
-    return findMeshBoundary( modelNode, geocentric );
-  }
-}
-
-/* Finds the convex hull for the given points using the Andrew's monotone
- * chian algorithm. Returns an ordered set of points defining the hull
- *
- * Implementation based on chainHull_2D() method from 
- * softSurfer (www.softsurfer.com)
- */
-osg::Vec3dArray* BoundaryUtil::findHull(osg::Vec3dArray& points)
-{
-  if (points.size() == 0)
-    return 0;
-
-  // the output array hull will be used as the stack
-  osg::Vec3dArray* hull = new osg::Vec3dArray(points.size());
-
-  // Presort the points as required by the algorithm
-  osg::ref_ptr<osg::Vec3dArray> sorted = hullPresortPoints(points);
-  
-  int bot=0, top=(-1);  // indices for bottom and top of the stack
-  int i;                // array scan index
-  int n = sorted->size();
-
-  // Get the indices of points with min x-coord and min|max y-coord
-  int minmin = 0, minmax;
-  double xmin = (*sorted)[0].x();
-  for (i=1; i<n; i++)
-    if ((*sorted)[i].x() != xmin) break;
-  minmax = i-1;
-
-  //if the points at minmin and minmax have the same value, shift minmin
-  //to ignore the duplicate points
-  if ((*sorted)[minmin] == (*sorted)[minmax])
-    minmin = minmax;
-
-  // degenerate case: all x-coords == xmin
-  if (minmax == n-1)
-  {       
-    (*hull)[++top] = (*sorted)[minmin];
-    if ((*sorted)[minmax].y() != (*sorted)[minmin].y()) // a nontrivial segment
-      (*hull)[++top] = (*sorted)[minmax];
-    (*hull)[++top] = (*sorted)[minmin];           // add polygon endpoint
-
-    hull->resize(top + 1);
-    return hull;
-  }
-
-  // Get the indices of points with max x-coord and min|max y-coord
-  int maxmin, maxmax = n-1;
-  double xmax = (*sorted)[n-1].x();
-  for (i=n-2; i>=0; i--)
-    if ((*sorted)[i].x() != xmax) break;
-  maxmin = i+1;
-
-  // Compute the lower hull on the stack
-  (*hull)[++top] = (*sorted)[minmin];  // push minmin point onto stack
-  i = minmax;
-  while (++i <= maxmin)
-  {
-    // the lower line joins (*sorted)[minmin] with (*sorted)[maxmin]
-
-    // if multiple points have the same x/y values, go with the lowest z value
-    if ((*hull)[top].x() == (*sorted)[i].x() && (*hull)[top].y() == (*sorted)[i].y())
-    {
-      if ((*sorted)[i].z() < (*hull)[top].z())
-        (*hull)[top].z() = (*sorted)[i].z();
-
-      continue;
-    }
-
-    if (isLeft((*sorted)[minmin], (*sorted)[maxmin], (*sorted)[i]) > 0 && i < maxmin)
-      continue;  // ignore (*sorted)[i] above the lower line.  NOTE: Differs from original CH algorithm in that it keeps collinear points
-
-    while (top > 0)  // there are at least 2 points on the stack
-    {
-      // test if (*sorted)[i] is left of or on the line at the stack top. NOTE: Differs from original CH algorithm in that it keeps collinear points
-      if (isLeft((*hull)[top-1], (*hull)[top], (*sorted)[i]) >= 0)
-        break;  // (*sorted)[i] is a new hull vertex
-      else
-        top--;  // pop top point off stack
-    }
-    (*hull)[++top] = (*sorted)[i];  // push (*sorted)[i] onto stack
-  }
-
-  if (maxmax != maxmin)  // if distinct xmax points
-  {
-    // Push all points between maxmin and maxmax onto stack. NOTE: Differs from original CH algorithm in that it keeps collinear points
-    while (i <= maxmax)
-    {
-      if ((*hull)[top].x() == (*sorted)[i].x() && (*hull)[top].y() == (*hull)[top].y())
-      {
-        if ((*sorted)[i].z() < (*hull)[top].z())
-          (*hull)[top].z() = (*sorted)[i].z();
-      }
-      else
-      {
-        (*hull)[++top] = (*sorted)[i];
-      }
-
-      i++;
-    }
-  }
-
-  // Next, compute the upper hull on the stack H above the bottom hull
-
-  bot = top;  // the bottom point of the upper hull stack
-  i = maxmin;
-  while (--i >= minmax)
-  {
-    // the upper line joins (*sorted)[maxmax] with (*sorted)[minmax]
-
-    // if multiple points have the same x/y values, go with the lowest z value
-    if ((*hull)[top].x() == (*sorted)[i].x() && (*hull)[top].y() == (*sorted)[i].y())
-    {
-      if ((*sorted)[i].z() < (*hull)[top].z())
-        (*hull)[top].z() = (*sorted)[i].z();
-
-      continue;
-    }
-
-    if (isLeft((*sorted)[maxmax], (*sorted)[minmax], (*sorted)[i]) > 0 && i > minmax)
-      continue;  // ignore (*sorted)[i] below the upper line. NOTE: Differs from original CH algorithm in that it keeps collinear points
-
-    while (top > bot)  // at least 2 points on the upper stack
-    {
-      // test if (*sorted)[i] is left of or on the line at the stack top. NOTE: Differs from original CH algorithm in that it keeps collinear points
-      if (isLeft((*hull)[top-1], (*hull)[top], (*sorted)[i]) >= 0)
-        break;   // (*sorted)[i] is a new hull vertex
-      else
-        top--;   // pop top point off stack
-    }
-    (*hull)[++top] = (*sorted)[i];  // push (*sorted)[i] onto stack
-  }
-
-  // If minmax and minmin are the same, remove the duplicate point
-  if (minmax == minmin)
-  {
-    top--;
-  }
-  else
-  {
-    // Push all points between minmax and minmin onto stack. NOTE: Differs from original CH algorithm in that it keeps collinear points
-    while (i > minmin)
-    {
-      if ((*hull)[top].x() == (*sorted)[i].x() && (*hull)[top].y() == (*hull)[top].y())
-      {
-        if ((*sorted)[i].z() < (*hull)[top].z())
-          (*hull)[top].z() = (*sorted)[i].z();
-      }
-      else
-      {
-        (*hull)[++top] = (*sorted)[i];
-      }
-
-      i--;
-    }
-  }
-
-  hull->resize(top + 1);
-  return hull;
-}
-
-/* Returns an array containing the points sorted first by x and then by y */
-osg::Vec3dArray* BoundaryUtil::hullPresortPoints(osg::Vec3dArray& points)
-{
-  osg::Vec3dArray* sorted = new osg::Vec3dArray(points.begin(), points.end());
-  std::sort(sorted->begin(), sorted->end(), presortCompare);
-
-  return sorted;
-}
-
-//---------------------------------------------------------------------------
-
-namespace
-{
-    // custom comparator for VertexSet.
-    struct VertexLess
-    {
-        bool operator()(const osg::Vec3d& lhs, const osg::Vec3d& rhs) const
-        {
-            double dx = lhs.x() - rhs.x();
-            if ( dx < 0.0 && dx < -BoundaryUtil::getTolerance() ) return true;
-            if ( dx > 0.0 && dx >  BoundaryUtil::getTolerance() ) return false;
-
-            double dy = lhs.y() - rhs.y();
-            return (dy < 0.0 && dy < -BoundaryUtil::getTolerance());
-        }
-    };
-
-    typedef std::set<osg::Vec3d, VertexLess> VertexSet;
-    typedef VertexSet::iterator Index;
-
-    // custom comparator for Index so we can use it at a std::map key
-    struct IndexLess : public std::less<Index> {
-      bool operator()(const Index& lhs, const Index& rhs ) const {
-        return (*lhs) < (*rhs);
-      }
-    };
-    
-    typedef std::set<Index, IndexLess>            IndexSet;
-    typedef std::map<Index, IndexSet, IndexLess>  EdgeMap;
-
-    // Stores the noded topology of a model with unique verticies and edge definitions.
-    // The verticies are stored rotated into the XY plane so that we can properly find
-    // the "bottom-most" vert and walk the boundary.
-    struct TopologyGraph
-    {
-        TopologyGraph()
-          : _minY( _verts.end() ), _totalVerts(0), _srs(0L) { }
-
-        unsigned     _totalVerts;  // total number of verts encountered
-        VertexSet    _vertsWorld;  // 
-        VertexSet    _verts;       // set of unique verts in the topology (rotated into XY plane)
-        EdgeMap      _edgeMap;     // maps each vert to all the verts with which it shares an edge
-        Index        _minY;        // points to the vert with the minimum Y coordinate (in XY plane)
-        osg::Matrixd _world2plane; // matrix that transforms into a localized XY plane
-        const osgEarth::SpatialReference* _srs;
-    };
-
-    typedef std::map<unsigned,Index> UniqueMap;
-
-    // A TriangleIndexFunctor that traverses a stream of triangles and builds a
-    // topology graph from their points and edges.
-    struct TopologyBuilder
-    {
-        TopologyGraph*  _topology;     // topology to which to append point and edge data
-        osg::Vec3Array* _vertexList;   // source vertex list
-        osg::Matrixd    _local2world;  // transforms source verts into world coordinates
-        UniqueMap       _uniqueMap;    // prevents duplicates
-
-        void operator()( unsigned v0, unsigned v1, unsigned v2 )
-        {
-            Index i0 = add( v0 );
-            Index i1 = add( v1 );
-            Index i2 = add( v2 );
-
-            // add to the edge list for each of these verts
-            if ( i0 != i1 ) _topology->_edgeMap[i0].insert( i1 );
-            if ( i0 != i2 ) _topology->_edgeMap[i0].insert( i2 );
-            if ( i1 != i0 ) _topology->_edgeMap[i1].insert( i0 );
-            if ( i1 != i2 ) _topology->_edgeMap[i1].insert( i2 );
-            if ( i2 != i0 ) _topology->_edgeMap[i2].insert( i0 );
-            if ( i2 != i1 ) _topology->_edgeMap[i2].insert( i1 );
-        }
-
-        Index add( unsigned v )
-        {
-            // first see if we already added the vert at this index.
-            UniqueMap::iterator i = _uniqueMap.find( v );
-            if ( i == _uniqueMap.end() )
-            {
-                // no, so transform it into world coordinates, and rotate it into the XY plane
-                osg::Vec3d vert = (*_vertexList)[v];
-                osg::Vec3d world = vert * _local2world;
-                osg::Vec3d plane = world;
-
-                if ( _topology->_srs )
-                {
-                    const osgEarth::SpatialReference* ecef = _topology->_srs->getECEF();
-                    ecef->transform(world, _topology->_srs, plane);
-                }
-                else
-                {
-                    plane = world * _topology->_world2plane;
-                }
-
-                // insert it into the unique vert list
-                std::pair<VertexSet::iterator,bool> f = _topology->_verts.insert( plane );
-                if ( f.second ) // insert succedded
-                {
-                    // this is a new location, so check it to see if it is the new "southernmost" point:
-                    if ( _topology->_minY == _topology->_verts.end() || plane.y() < _topology->_minY->y() )
-                    {
-                        _topology->_minY = f.first;
-                    }
-                }
-
-                // store in the uniqueness map so we don't process the same index again
-                _uniqueMap[ v ] = f.first;
-
-                // return the index of the vert.
-                return f.first;
-            }
-            else
-            {
-                return i->second;
-            }
-        }
-    };
-
-    // Visits a scene graph and builds a topology graph from the verts and edges
-    // found within.
-    struct BuildTopologyVisitor : public osg::NodeVisitor
-    {
-        BuildTopologyVisitor( TopologyGraph& topology )
-            : osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ),
-              _topology( topology )
-        {
-            //nop
-        }
-
-        // track local transforms so we can build a topology in world coords
-        void apply( osg::Transform& xform )
-        {
-            osg::Matrix matrix;
-            if ( !_matrixStack.empty() ) matrix = _matrixStack.back();
-            xform.computeLocalToWorldMatrix( matrix, this );
-            _matrixStack.push_back( matrix );
-            traverse( xform );
-            _matrixStack.pop_back();
-        }
-
-        // add the contents of a geode to the topology
-        void apply( osg::Geode& geode )
-        {
-            for( unsigned i=0; i<geode.getNumDrawables(); ++i )
-            {
-                osg::Drawable* drawable = geode.getDrawable( i );
-                if ( drawable->asGeometry() )
-                {
-                    apply( drawable->asGeometry() );
-                }
-            }
-        }
-
-        // add the contents of a Geometry to the topology
-        void apply( osg::Geometry* geometry )
-        {
-            osg::Vec3Array* vertexList = dynamic_cast<osg::Vec3Array*>(geometry->getVertexArray());
-            if ( vertexList )
-            {
-                osg::TriangleIndexFunctor<TopologyBuilder> builder;
-                builder._topology = &_topology;
-                builder._vertexList = vertexList;
-                if ( !_matrixStack.empty() )
-                    builder._local2world = _matrixStack.back();
-                _topology._totalVerts += vertexList->size();
-                geometry->accept( builder );
-            }
-        }
-
-        std::vector<osg::Matrixd> _matrixStack;
-        TopologyGraph&            _topology;
-    };
-
-    void dumpPointCloud(TopologyGraph& t)
-    {
-        osg::Vec3Array* v = new osg::Vec3Array();
-        osg::DrawElementsUInt* lines = new osg::DrawElementsUInt(GL_LINES);
-        unsigned index = 0;
-        unsigned minyindex = 0;
-        std::map<Index,unsigned, IndexLess> order;
-        for(Index i = t._verts.begin(); i != t._verts.end(); ++i, ++index)
-        {
-            v->push_back( *i );
-            if ( i == t._minY )
-                minyindex = index;
-            order[i] = index;
-        }
-        index = 0;
-        for(Index i = t._verts.begin(); i != t._verts.end(); ++i, ++index)
-        {
-            IndexSet& edges = t._edgeMap[i];
-            for(IndexSet::iterator j=edges.begin(); j!=edges.end(); ++j)
-            {
-                lines->push_back(order[i]);
-                lines->push_back(order[*j]);
-            }
-        }
-        osg::Geometry* g = new osg::Geometry();
-        g->setVertexArray(v);
-        g->addPrimitiveSet(new osg::DrawArrays(GL_POINTS, 0, v->size()));
-        g->addPrimitiveSet(lines);
-        g->getOrCreateStateSet()->setAttributeAndModes(new osg::Point(3));
-        osg::Geode* n = new osg::Geode();
-        n->addDrawable(g);
-        osg::Geometry* g2 = new osg::Geometry();
-        g2->setVertexArray(v);
-        g2->addPrimitiveSet(new osg::DrawArrays(GL_POINTS, minyindex, 1));
-        g2->getOrCreateStateSet()->setAttributeAndModes(new osg::Point(10));
-        n->addDrawable(g2);
-        osgDB::writeNodeFile(*n, "mesh.osg");            
-        n->unref();
-    }
-}
-
-//------------------------------------------------------------------------
-
-osg::Vec3dArray*
-BoundaryUtil::findMeshBoundary( osg::Node* node, bool geocentric )
-{
-    // the normal defines the XY plane in which to search for a boundary
-    osg::Vec3d normal(0,0,1);
-    osg::Vec3d center;
-
-    if ( geocentric )
-    {
-        // define the XY plane based on the normal to the center of the dataset:
-        osg::BoundingSphere bs = node->getBound();
-        center = bs.center();
-        normal = center;
-        normal.normalize();
-        OE_DEBUG << "Normal = " << normal.x() << ", " << normal.y() << ", " << normal.z() << std::endl;
-    }
-
-    osg::ref_ptr<osg::Vec3dArray> _result = new osg::Vec3dArray();
-
-    // first build a topology graph from the node.
-    TopologyGraph topology;
-
-    // set up a transform that will localize geometry into an XY plane
-    if ( geocentric )
-    {
-        topology._world2plane.makeRotate(normal, osg::Vec3d(0,0,1));
-        topology._world2plane.preMultTranslate(-center);
-
-        // if this is set, use mercator projection instead of a simple geolocation
-        //topology._srs = osgEarth::SpatialReference::get("spherical-mercator");
-    }
-
-    // build the topology
-    BuildTopologyVisitor buildTopoVisitor(topology);
-    node->accept( buildTopoVisitor );
-
-    OE_DEBUG << "Found " << topology._verts.size() << " unique verts" << std::endl;
-    //dumpPointCloud(topology);
-
-    // starting with the minimum-Y vertex (which is guaranteed to be in the boundary)
-    // traverse the outside of the point set. Do this by sorting all the edges by
-    // their angle relative to the vector to the previous point. The vector with the
-    // smallest angle represents the edge connecting the current point to the next
-    // boundary point. Walk the edge until we return to the beginning.
-    
-    Index vptr      = topology._minY;
-    Index vptr_prev = topology._verts.end();
-
-    IndexSet visited;
-
-    while( true )
-    {
-        // store this vertex in the result set:
-        _result->push_back( *vptr );
-
-        if ( _result->size() == 56 )
-        {
-            int asd=0;
-        }
-
-        // pull up the next 2D vertex (XY plane):
-        osg::Vec2d vert ( vptr->x(), vptr->y() );
-
-        // construct the "base" vector that points from the previous 
-        // point to the current point; or to -X in the initial case
-        osg::Vec2d base;
-        if ( vptr_prev == topology._verts.end() )
-            base.set( -1, 0 );
-        else
-            base = vert - osg::Vec2d( vptr_prev->x(), vptr_prev->y() );
-            
-        // pull up the edge set for this vertex:
-        IndexSet& edges = topology._edgeMap[vptr];
-
-        // find the edge with the minimun delta angle to the base vector
-        double bestScore = DBL_MAX;
-        Index  bestEdge  = topology._verts.end();
-        
-        OE_DEBUG << "VERTEX (" << 
-            vptr->x() << ", " << vptr->y() << ", " << vptr->z() 
-            << ") has " << edges.size() << " edges..."
-            << std::endl;
-
-        for( IndexSet::iterator e = edges.begin(); e != edges.end(); ++e )
-        {
-            // don't go back from whence we just came
-            if ( *e == vptr_prev )
-                continue;
-
-            // never return to a vert we've already visited
-            if ( visited.find(*e) != visited.end() )
-                continue;
-
-            // calculate the angle between the base vector and the current edge:
-            osg::Vec2d edgeVert( (*e)->x(), (*e)->y() );
-            osg::Vec2d edge = edgeVert - vert;
-
-            base.normalize();
-            edge.normalize();
-            double cross = base.x()*edge.y() - base.y()*edge.x();
-            double dot   = base * edge;
-            double score = dot;
-
-            if ( cross < 0.0 )
-            {
-                double diff = 2.0-(score+1.0);
-                score = 1.0 + diff;
-            }
-
-            OE_DEBUG << "   check: " << (*e)->x() << ", " << (*e)->y() << ", " << (*e)->z() << std::endl;
-            OE_DEBUG << "   base = " << base.x() << ", " << base.y() << std::endl;
-            OE_DEBUG << "   edge = " << edge.x() << ", " << edge.y() << std::endl;
-            OE_DEBUG << "   crs = " << cross << ", dot = " << dot << ", score = " << score << std::endl;
-            
-            if ( score < bestScore )
-            {
-                bestScore = score;
-                bestEdge = *e;
-            }
-        }
-
-        if ( bestEdge == topology._verts.end() )
-        {
-            // this will probably never happen
-            osg::notify(osg::WARN) << "Illegal state - reached a dead end!" << std::endl;
-            break;
-        }
-
-        // store the previous:
-        vptr_prev = vptr;
-
-        // follow the chosen edge around the outside of the geometry:
-        OE_DEBUG << "   BEST SCORE = " << bestScore << std::endl;
-        vptr = bestEdge;
-
-        // record this vert so we don't visit it again.
-        visited.insert( vptr );
-
-        // once we make it all the way around, we're done:
-        if ( vptr == topology._minY )
-            break;
-    }
-
-    // un-rotate the results from the XY plane back to their original frame:
-    if ( topology._srs )
-    {
-        const osgEarth::SpatialReference* ecef = topology._srs->getECEF();
-        topology._srs->transform(_result->asVector(), ecef);
-    }
-    else
-    {
-        osg::Matrix plane2world;
-        plane2world.invert( topology._world2plane );
-        for( osg::Vec3dArray::iterator i = _result->begin(); i != _result->end(); ++i )
-            (*i) = (*i) * plane2world;
-    }
-
-    return _result.release();
-}
-
-//------------------------------------------------------------------------
-
-bool
-BoundaryUtil::simpleBoundaryTest(const osg::Vec3dArray& boundary)
-{
-  osg::ref_ptr<osgEarth::Symbology::Polygon> boundsPoly = new osgEarth::Symbology::Polygon();
-  for (int i=0; i < (int)boundary.size(); i++)
-    boundsPoly->push_back(boundary[i]);
-
-  osgEarth::Bounds boundsBounds = boundsPoly->getBounds();
-
-  osg::ref_ptr<osgEarth::Symbology::Polygon> outterPoly = new osgEarth::Symbology::Polygon();
-  outterPoly->push_back(osg::Vec3d(boundsBounds.xMin() - 10.0, boundsBounds.yMin() - 10.0, boundsBounds.zMin()));
-  outterPoly->push_back(osg::Vec3d(boundsBounds.xMax() + 10.0, boundsBounds.yMin() - 10.0, boundsBounds.zMin()));
-  outterPoly->push_back(osg::Vec3d(boundsBounds.xMax() + 10.0, boundsBounds.yMax() + 10.0, boundsBounds.zMin()));
-  outterPoly->push_back(osg::Vec3d(boundsBounds.xMin() - 10.0, boundsBounds.yMax() + 10.0, boundsBounds.zMin()));
-
-  osg::ref_ptr<osgEarth::Symbology::Geometry> outPoly;
-  return outterPoly->difference(boundsPoly, outPoly);
-}
-#endif



View it on GitLab: https://salsa.debian.org/debian-gis-team/osgearth/compare/e0c952b2697f03805c19a784386a3613383a3aba...6b46af98a1ad0c9c1f3ad4d5559a0faade53cf1b

---
View it on GitLab: https://salsa.debian.org/debian-gis-team/osgearth/compare/e0c952b2697f03805c19a784386a3613383a3aba...6b46af98a1ad0c9c1f3ad4d5559a0faade53cf1b
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.alioth.debian.org/pipermail/pkg-grass-devel/attachments/20180202/abdcea9b/attachment-0001.html>


More information about the Pkg-grass-devel mailing list