1 /* -*-c++-*- */
2 /* osgEarth - Geospatial SDK for OpenSceneGraph
3 * Copyright 2019 Pelican Mapping
4 * http://osgearth.org
5 *
6 * osgEarth is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
16 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
17 * IN THE SOFTWARE.
18 *
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program.  If not, see <http://www.gnu.org/licenses/>
21 */
22 #include <osgEarth/DrapingTechnique>
23 #include <osgEarth/Capabilities>
24 #include <osgEarth/Registry>
25 #include <osgEarth/Shaders>
26 #include <osgEarth/Lighting>
27 
28 #include <osg/BlendFunc>
29 #include <osg/Texture2D>
30 
31 #define LC "[DrapingTechnique] "
32 
33 //#define OE_TEST if (_dumpRequested) OE_INFO << std::setprecision(9)
34 #define OE_TEST OE_NULL
35 
36 using namespace osgEarth;
37 
38 //---------------------------------------------------------------------------
39 
40 #undef  LC
41 #define LC "[DrapingCamera] "
42 
43 namespace
44 {
45     struct RenderBinSortingVisitor
46     {
apply__anon411a13980111::RenderBinSortingVisitor47         void apply(osgUtil::RenderBin* bin)
48         {
49             bin->setSortMode(osgUtil::RenderBin::TRAVERSAL_ORDER);
50 
51             for(osgUtil::RenderBin::RenderBinList::iterator i = bin->getRenderBinList().begin();
52                 i != bin->getRenderBinList().end();
53                 ++i)
54             {
55                 osgUtil::RenderBin* child = i->second.get();
56                 apply(child);
57             }
58         }
59     };
60 
61     /**
62      * A camera that will traverse the per-thread DrapingCullSet instead of
63      * its own children.
64      */
65     class DrapingCamera : public osg::Camera
66     {
67     public:
DrapingCamera(DrapingManager & dm)68         DrapingCamera(DrapingManager& dm) : osg::Camera(), _dm(dm), _camera(0L)
69         {
70             setCullingActive( false );
71             osg::StateSet* ss = getOrCreateStateSet();
72             ss->setMode(GL_DEPTH_TEST, 0);
73             ss->setRenderBinDetails(1, "TraversalOrderBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS);
74         }
75 
76     public: // osg::Node
77 
accept(osg::NodeVisitor & nv,const osg::Camera * camera)78         void accept(osg::NodeVisitor& nv, const osg::Camera* camera)
79         {
80             _camera = camera;
81             osg::Camera::accept( nv );
82         }
83 
traverse(osg::NodeVisitor & nv)84         void traverse(osg::NodeVisitor& nv)
85         {
86             DrapingCullSet& cullSet = _dm.get(_camera);
87             cullSet.accept( nv );
88 
89             // manhandle the render bin sorting, since OSG ignores the override
90             // in the render bin details above
91             osgUtil::CullVisitor* cv = dynamic_cast<osgUtil::CullVisitor*>(&nv);
92             if (cv)
93             {
94                 applyTraversalOrderSorting(cv->getCurrentRenderBin());
95             }
96         }
97 
applyTraversalOrderSorting(osgUtil::RenderBin * bin)98         void applyTraversalOrderSorting(osgUtil::RenderBin* bin)
99         {
100             bin->setSortMode(osgUtil::RenderBin::TRAVERSAL_ORDER);
101 
102             for (osgUtil::RenderBin::RenderBinList::iterator i = bin->getRenderBinList().begin();
103                 i != bin->getRenderBinList().end();
104                 ++i)
105             {
106                 osgUtil::RenderBin* child = i->second.get();
107                 applyTraversalOrderSorting(child);
108             }
109         }
110 
111     protected:
~DrapingCamera()112         virtual ~DrapingCamera() { }
113         DrapingManager& _dm;
114         const osg::Camera* _camera;
115     };
116 
117 
118     // Additional per-view data stored by the draping technique.
119     struct LocalPerViewData : public osg::Object
120     {
121         META_Object(osgEarth, LocalPerViewData);
122 
123         osg::ref_ptr<osg::Uniform> _texGenUniform;
124 
resizeGLObjectBuffers__anon411a13980111::LocalPerViewData125         void resizeGLObjectBuffers(unsigned maxSize) {
126             if (_texGenUniform.valid())
127                 _texGenUniform->resizeGLObjectBuffers(maxSize);
128         }
releaseGLObjects__anon411a13980111::LocalPerViewData129         void releaseGLObjects(osg::State* state) const {
130             if (_texGenUniform.valid())
131                 _texGenUniform->releaseGLObjects(state);
132         }
133 
LocalPerViewData__anon411a13980111::LocalPerViewData134         LocalPerViewData() { }
LocalPerViewData__anon411a13980111::LocalPerViewData135         LocalPerViewData(const LocalPerViewData& rhs, const osg::CopyOp& co) { }
136     };
137 }
138 
139 //---------------------------------------------------------------------------
140 
141 namespace
142 {
143     struct Line2d
144     {
intersectRaysXY__anon411a13980211::Line2d145         bool intersectRaysXY(
146             const osg::Vec3d& p0, const osg::Vec3d& d0,
147             const osg::Vec3d& p1, const osg::Vec3d& d1,
148             osg::Vec3d& out_p,
149             double&     out_u,
150             double&     out_v) const
151         {
152             static const double epsilon = 0.001;
153 
154             double det = d0.y()*d1.x() - d0.x()*d1.y();
155             if ( osg::equivalent(det, 0.0, epsilon) )
156                 return false; // parallel
157 
158             out_u = (d1.x()*(p1.y()-p0.y())+d1.y()*(p0.x()-p1.x()))/det;
159             out_v = (d0.x()*(p1.y()-p0.y())+d0.y()*(p0.x()-p1.x()))/det;
160             out_p = p0 + d0*out_u;
161             return true;
162         }
163 
164         osg::Vec3d _a, _b;
165 
Line2d__anon411a13980211::Line2d166         Line2d(const osg::Vec3d& p0, const osg::Vec3d& p1) : _a(p0), _b(p1) { }
167 
Line2d__anon411a13980211::Line2d168         Line2d(const osg::Vec4d& p0, const osg::Vec4d& p1)
169             : _a(p0.x()/p0.w(), p0.y()/p0.w(), p0.x()/p0.w()), _b(p1.x()/p1.w(), p1.y()/p1.w(), p1.z()/p1.w()) { }
170 
intersect__anon411a13980211::Line2d171         bool intersect(const Line2d& rhs, osg::Vec4d& out) const {
172             double u, v;
173             osg::Vec3d temp;
174             bool ok = intersectRaysXY(_a, (_b-_a), rhs._a, (rhs._b-rhs._a), temp, u, v);
175             out.set( temp.x(), temp.y(), temp.z(), 1.0 );
176             return ok;
177         }
intersect__anon411a13980211::Line2d178         bool intersect(const Line2d& rhs, osg::Vec3d& out) const {
179             double u, v;
180             return intersectRaysXY(_a, (_b-_a), rhs._a, (rhs._b-rhs._a), out, u, v);
181         }
182     };
183 
184     // Experimental.
optimizeProjectionMatrix(OverlayDecorator::TechRTTParams & params,double maxFarNearRatio)185     void optimizeProjectionMatrix(OverlayDecorator::TechRTTParams& params, double maxFarNearRatio)
186     {
187         // t0,t1,t2,t3 will form a polygon that tightly fits the
188         // main camera's frustum. Texture near the camera will get
189         // more resolution then texture far away.
190         //
191         // The line segment t0t1 represents the near clip plane and is
192         // along the y=-1 line of the clip volume. t2t3 represents the
193         // far plane and lies long y=+1. This code calculates the optimal
194         // width of those 2 line segments off-center. Since the view
195         // frustum is symmertical (as calculated in OverlayDecorator)
196         // we only need find the half-width of each.
197         //
198         // NOTE: this algorithm only works with the top-down RTT camera
199         // created by the OverlayDecorator, AND assumes a "level" view
200         // camera (no roll) with respect to the RTT camera.
201         osg::Vec4d t0, t1, t2, t3;
202         {
203             // cap the width of the far line w.r.t to y-axis of the clip
204             // space. (derived empirically)
205             const double maxApsectRatio   = 1.0;
206 
207             // this matrix xforms the verts from model to clip space.
208             osg::Matrix rttMVP = params._rttViewMatrix * params._rttProjMatrix;
209 
210             // if the eyepoint lies within the RTT clip space, don't bother to
211             // optimize because the camera is looking downish and the existing
212             // rectangular volume is sufficient.
213             osg::Vec3d eyeClip = params._eyeWorld * rttMVP;
214             if ( eyeClip.y() >= -1.0 && eyeClip.y() <= 1.0 )
215                 return;
216 
217             // sanity check. 6 faces requires since we need near and far
218             if ( params._visibleFrustumPH._faces.size() < 6 )
219                 return;
220 
221             // discover the max near-plane width.
222             double halfWidthNear = 0.0;
223             osgShadow::ConvexPolyhedron::Faces::iterator f = params._visibleFrustumPH._faces.begin();
224             f++; f++; f++; f++; // the near plane Face
225             // f->vertices.size() should always be 4, I would think.. but it's not..
226             for(unsigned i=0; i<f->vertices.size(); ++i)
227             {
228                 osg::Vec3d p = f->vertices[i] * rttMVP;
229                 if ( fabs(p.x()) > halfWidthNear )
230                     halfWidthNear = fabs(p.x());
231             }
232 
233             double aspectRatio  = DBL_MAX;
234             double farNearRatio = DBL_MAX;
235             double halfWidthFar = DBL_MAX;
236 
237             // Next, find the point in the camera frustum that forms the largest angle
238             // with the center line (line of sight). This is simply the minimum dot
239             // product of LOS vector and the vector from (0,-1,0) to the point.
240             osg::Vec3d look(0,1,0);
241             double     min_dp   = 1.0;
242             osg::Vec3d rightmost_p;
243             f++; // the Far plane face
244             for(unsigned i=0; i<f->vertices.size(); ++i)
245             {
246                 osg::Vec3d p = f->vertices[i] * rttMVP;
247                 // only check points on the right (since it's symmetrical)
248                 if ( p.x() > 0 )
249                 {
250                     osg::Vec3d pv(p.x(), p.y()+1.0, 0); pv.normalize();
251                     double dp = look * pv;
252                     if ( dp < min_dp )
253                     {
254                         min_dp = dp;
255                         rightmost_p = p;
256                     }
257                 }
258             }
259 
260             // Now calculate the far extent. This is an iterative process;
261             // If either the aspectRatio or far/near-ratio limits are exceeded
262             // by the value we calculate, reset the near width to accomodate
263             // and try again. Worst case this should be no more than 3 iterations.
264             double minHalfWidthNear = halfWidthNear;
265 
266             Line2d farLine( osg::Vec3d(-1,1,0), osg::Vec3d(1,1,0) );
267 
268             int iterations = 0;
269             while(
270                 (aspectRatio > maxApsectRatio || farNearRatio > maxFarNearRatio) &&
271                 (halfWidthFar > halfWidthNear) &&
272                 (iterations++ < 10) )
273             {
274                 // make sure all the far-clip verts are inside our polygon.
275                 // stretch out the far line to accomodate them.
276                 osg::Vec3d NR( halfWidthNear, -1, 0);
277 
278                 osg::Vec3d i;
279                 Line2d( NR, rightmost_p ).intersect( farLine, i );
280                 halfWidthFar = i.x();
281 
282                 aspectRatio  = (halfWidthFar-halfWidthNear)/2.0;
283                 if ( aspectRatio > maxApsectRatio )
284                 {
285                     halfWidthNear = halfWidthFar - 2.0*maxApsectRatio;
286                 }
287 
288                 farNearRatio = halfWidthFar/halfWidthNear;
289                 if ( farNearRatio > maxFarNearRatio )
290                 {
291                     halfWidthNear = halfWidthFar / maxFarNearRatio;
292                     //break;
293                 }
294 
295                 halfWidthNear = osg::maximum(halfWidthNear, minHalfWidthNear);
296             }
297 
298             // if the far plane is narrower than the near plane, bail out and
299             // fall back on a simple rectangular clip camera.
300             if ( halfWidthFar <= halfWidthNear )
301                 return;
302 
303             //OE_NOTICE  << "\n"
304             //    << "HN = " << halfWidthNear << "\n"
305             //    << "HF = " << halfWidthFar << "\n"
306             //    << "AR = " << aspectRatio << "\n"
307             //    << "FNR= " << farNearRatio << "\n"
308             //    << std::endl;
309 
310             // construct the polygon.
311             t0.set(  halfWidthFar,   1.0, 0.0, 1.0 );
312             t1.set( -halfWidthFar,   1.0, 0.0, 1.0 );
313             t2.set( -halfWidthNear, -1.0, 0.0, 1.0 );
314             t3.set(  halfWidthNear, -1.0, 0.0, 1.0 );
315         }
316 
317         // OK now warp our polygon t0,t1,t2,t3 into a clip-space square
318         // through a series of matrix operations.
319         osg::Vec4d  u, v;
320         osg::Matrix M;
321 
322         // translate the center of the near plane to the origin
323         u = (t2 + t3) / 2.0;
324         osg::Matrix T1;
325         T1.makeTranslate(-u.x(), -u.y(), 0.0);
326         M = T1;
327 
328         // find the intersection of the side lines t0,t3 and t1,t2
329         // and translate that point is at the origin:
330         osg::Vec4d i;
331         Line2d(t0, t3).intersect( Line2d(t1, t2), i );
332         u = i*M;
333         osg::Matrix T2;
334         T2.makeTranslate( -u.x(), -u.y(), 0.0 );
335         M = T2*M;
336 
337         // scale the near corners to [-1,1] and [1,1] respectively:
338         u = t3*M; // ...not t2.
339         osg::Matrix S1;
340         S1.makeScale( 1/u.x(), 1/u.y(), 1.0 );
341         M = M*S1;
342 
343         // project onto the Y plane and translate the whole thing
344         // back down to the origin at the same time.
345         osg::Matrix N(
346             1,  0, 0, 0,
347             0,  1, 0, 1,
348             0,  0, 1, 0,
349             0, -1, 0, 0);
350         M = M*N;
351 
352         // scale it back to unit size:
353         u = t0*M;
354         v = t3*M;
355         osg::Matrix S3;
356         S3.makeScale( 1.0, 2.0/(u.y()/u.w() - v.y()/v.w()), 1.0 );
357         M = M*S3;
358 
359         // finally, translate it to it lines up with the clip space boundaries.
360         osg::Matrix T4;
361         T4.makeTranslate( 0.0, -1.0, 0.0 );
362         M = M*T4;
363 
364         // apply the result to the projection matrix.
365         params._rttProjMatrix.postMult( M );
366     }
367 }
368 
369 //---------------------------------------------------------------------------
370 
371 #undef  LC
372 #define LC "[DrapingTechnique] "
373 
DrapingTechnique()374 DrapingTechnique::DrapingTechnique() :
375 _textureUnit     ( 1 ),
376 _textureSize     ( 1024 ),
377 _mipmapping      ( false ),
378 _rttBlending     ( true ),
379 _attachStencil   ( false ),
380 _maxFarNearRatio ( 5.0 )
381 {
382     _supported = Registry::capabilities().supportsGLSL();
383 
384     // try newer version
385     const char* nfr2 = ::getenv("OSGEARTH_OVERLAY_RESOLUTION_RATIO");
386     if ( nfr2 )
387         _maxFarNearRatio = as<double>(nfr2, 0.0);
388 }
389 
390 
391 bool
hasData(OverlayDecorator::TechRTTParams & params) const392 DrapingTechnique::hasData(OverlayDecorator::TechRTTParams& params) const
393 {
394     return getBound(params).valid();
395 }
396 
397 namespace
398 {
399     // Customized texture class will disable texture filtering when rendering under a pick camera.
400     class DrapingTexture : public osg::Texture2D
401     {
402     public:
apply(osg::State & state) const403         virtual void apply(osg::State& state) const
404         {
405             const osg::StateSet::DefineList& defines = state.getDefineMap().currentDefines;
406             if (defines.find("OE_IS_PICK_CAMERA") != defines.end())
407             {
408                 FilterMode minFilter = _min_filter;
409                 FilterMode magFilter = _mag_filter;
410                 DrapingTexture* ncThis = const_cast<DrapingTexture*>(this);
411                 ncThis->_min_filter = NEAREST;
412                 ncThis->_mag_filter = NEAREST;
413                 ncThis->dirtyTextureParameters();
414                 osg::Texture2D::apply(state);
415                 ncThis->_min_filter = minFilter;
416                 ncThis->_mag_filter = magFilter;
417                 ncThis->dirtyTextureParameters();
418             }
419             else
420             {
421                 osg::Texture2D::apply(state);
422             }
423         }
424     };
425 }
426 
427 void
setUpCamera(OverlayDecorator::TechRTTParams & params)428 DrapingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
429 {
430     OE_INFO << LC << "Using texture size = " << _textureSize.get() << std::endl;
431 
432     // create the projected texture:
433     osg::Texture2D* projTexture = new DrapingTexture();
434 
435     projTexture->setTextureSize( *_textureSize, *_textureSize );
436     projTexture->setInternalFormat( GL_RGBA8 );  //use GL_RGBA8 for compatibility with osg's glTexStorage extension
437     projTexture->setSourceFormat( GL_RGBA );
438     projTexture->setSourceType( GL_UNSIGNED_BYTE );
439     projTexture->setFilter( osg::Texture::MIN_FILTER, _mipmapping? osg::Texture::LINEAR_MIPMAP_LINEAR: osg::Texture::LINEAR );
440     projTexture->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
441     projTexture->setWrap( osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_BORDER );
442     projTexture->setWrap( osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_BORDER );
443     //projTexture->setWrap( osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE );
444     projTexture->setBorderColor( osg::Vec4(0,0,0,0) );
445 
446     // set up the RTT camera:
447     params._rttCamera = new DrapingCamera(_drapingManager);
448     params._rttCamera->setClearColor( osg::Vec4f(0,0,0,0) );
449     // this ref frame causes the RTT to inherit its viewpoint from above (in order to properly
450     // process PagedLOD's etc. -- it doesn't affect the perspective of the RTT camera though)
451     params._rttCamera->setReferenceFrame( osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT );
452     params._rttCamera->setViewport( 0, 0, *_textureSize, *_textureSize );
453     params._rttCamera->setComputeNearFarMode( osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR );
454     params._rttCamera->setRenderOrder( osg::Camera::PRE_RENDER );
455     params._rttCamera->setRenderTargetImplementation( osg::Camera::FRAME_BUFFER_OBJECT );
456     params._rttCamera->setImplicitBufferAttachmentMask(0, 0);
457     params._rttCamera->attach( osg::Camera::COLOR_BUFFER0, projTexture, 0, 0, _mipmapping );
458 
459     if ( _attachStencil )
460     {
461         OE_INFO << LC << "Attaching a stencil buffer to the RTT camera" << std::endl;
462 
463         // try a depth-packed buffer. failing that, try a normal one.. if the FBO doesn't support
464         // that (which is doesn't on some GPUs like Intel), it will automatically fall back on
465         // a PBUFFER_RTT impl
466         if ( Registry::capabilities().supportsDepthPackedStencilBuffer() )
467         {
468 #if defined(OSG_GLES2_AVAILABLE) || defined(OSG_GLES3_AVAILABLE)
469             params._rttCamera->attach( osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, GL_DEPTH24_STENCIL8_EXT );
470 #else
471             params._rttCamera->attach( osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, GL_DEPTH_STENCIL_EXT );
472 #endif
473         }
474         else
475         {
476             params._rttCamera->attach( osg::Camera::STENCIL_BUFFER, GL_STENCIL_INDEX );
477         }
478 
479         params._rttCamera->setClearStencil( 0 );
480         params._rttCamera->setClearMask( GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
481     }
482     else
483     {
484         params._rttCamera->setClearMask( GL_COLOR_BUFFER_BIT );
485     }
486 
487 
488     // set up a StateSet for the RTT camera.
489     osg::StateSet* rttStateSet = params._rttCamera->getOrCreateStateSet();
490 
491     osg::StateAttribute::OverrideValue forceOff =
492         osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE;
493 
494     rttStateSet->setDefine(OE_LIGHTING_DEFINE, forceOff);
495     //rttStateSet->addUniform( Registry::shaderFactory()->createUniformForGLMode(GL_LIGHTING, forceOff) );
496     rttStateSet->setMode( GL_LIGHTING, forceOff );
497 
498     // activate blending within the RTT camera's FBO
499     if ( _rttBlending )
500     {
501         //Setup a separate blend function for the alpha components and the RGB components.
502         //Because the destination alpha is initialized to 0 instead of 1
503         osg::BlendFunc* blendFunc = 0;
504         if (Registry::capabilities().supportsGLSL(140u))
505         {
506             //Blend Func Separate is only available on OpenGL 1.4 and above
507             blendFunc = new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
508         }
509         else
510         {
511             blendFunc = new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
512         }
513 
514         rttStateSet->setAttributeAndModes(blendFunc, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
515     }
516     else
517     {
518         rttStateSet->setMode(GL_BLEND, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
519     }
520 
521     // attach the overlay group to the camera.
522     // TODO: we should probably lock this since other cull traversals might be accessing the group
523     //       while we are changing its children.
524     params._rttCamera->addChild( params._group );
525 
526     // add to the terrain stateset, i.e. the stateset that the OverlayDecorator will
527     // apply to the terrain before cull-traversing it. This will activate the projective
528     // texturing on the terrain.
529     params._terrainStateSet->setTextureAttributeAndModes( *_textureUnit, projTexture, osg::StateAttribute::ON );
530 
531     // fire up the local per-view data:
532     LocalPerViewData* local = new LocalPerViewData();
533     params._techniqueData = local;
534 
535     if ( _maxFarNearRatio > 1.0 )
536     {
537         // Custom clipper that accounts for the projection matrix warping.
538         // Without this, you will get geometry beyond the original ortho far plane.
539         // (i.e. geometry from beyond the horizon will show through the earth)
540         // When the projection matrix has been warped, verts with Z=1.0 are no longer
541         // always on the far plane (only when y=-1) because of the perspective divide (w).
542         // So we need to test the Z directly (without w) and clip. NOTE: this seems to work
543         // fine, although I think it would be proper to clip at the fragment level with
544         // alpha. We shall see.
545         const char* warpClip =
546             "#version " GLSL_VERSION_STR "\n"
547             GLSL_DEFAULT_PRECISION_FLOAT "\n"
548             "void oe_overlay_warpClip(inout vec4 vclip) { \n"
549             "    if (vclip.z > 1.0) vclip.z = vclip.w+1.0; \n"
550             "} \n";
551         VirtualProgram* rtt_vp = VirtualProgram::getOrCreate(rttStateSet);
552         rtt_vp->setName("Draping RTT");
553         rtt_vp->setFunction( "oe_overlay_warpClip", warpClip, ShaderComp::LOCATION_VERTEX_CLIP );
554     }
555 
556     // Assemble the terrain shaders that will apply projective texturing.
557     VirtualProgram* terrain_vp = VirtualProgram::getOrCreate(params._terrainStateSet);
558     terrain_vp->setName( "Draping apply");
559 
560     // sampler for projected texture:
561     params._terrainStateSet->getOrCreateUniform(
562         "oe_overlay_tex", osg::Uniform::SAMPLER_2D )->set( *_textureUnit );
563 
564     // the texture projection matrix uniform.
565     local->_texGenUniform = params._terrainStateSet->getOrCreateUniform(
566         "oe_overlay_texmatrix", osg::Uniform::FLOAT_MAT4 );
567 
568     // shaders
569     Shaders pkg;
570     pkg.load( terrain_vp, pkg.DrapingVertex );
571     pkg.load( terrain_vp, pkg.DrapingFragment );
572 }
573 
574 
575 void
preCullTerrain(OverlayDecorator::TechRTTParams & params,osgUtil::CullVisitor * cv)576 DrapingTechnique::preCullTerrain(OverlayDecorator::TechRTTParams& params,
577                                  osgUtil::CullVisitor*             cv )
578 {
579     // allocate a texture image unit the first time through.
580     if ( !_textureUnit.isSet() )
581     {
582         static Threading::Mutex m;
583         m.lock();
584         if ( !_textureUnit.isSet() )
585         {
586             // apply the user-request texture unit, if applicable:
587             if ( _explicitTextureUnit.isSet() )
588             {
589                 if ( !_textureUnit.isSet() || *_textureUnit != *_explicitTextureUnit )
590                 {
591                     _textureUnit = *_explicitTextureUnit;
592                 }
593             }
594 
595             // otherwise, automatically allocate a texture unit if necessary:
596             else if ( !_textureUnit.isSet() )
597             {
598                 int texUnit;
599                 if ( params._terrainResources->reserveTextureImageUnit(texUnit, "Draping") )
600                 {
601                     _textureUnit = texUnit;
602                     OE_INFO << LC << "Reserved texture image unit " << *_textureUnit << std::endl;
603                 }
604                 else
605                 {
606                     OE_WARN << LC << "No texture image units available." << std::endl;
607                 }
608             }
609         }
610         m.unlock();
611     }
612 
613     if ( !params._rttCamera.valid() && _textureUnit.isSet() )
614     {
615         setUpCamera( params );
616 
617         // We do this so we can detect the RTT's camera's parent for
618         // things like auto-scaling, picking, and so on.
619         params._rttCamera->setView(cv->getCurrentCamera()->getView());
620     }
621 }
622 
623 const osg::BoundingSphere&
getBound(OverlayDecorator::TechRTTParams & params) const624 DrapingTechnique::getBound(OverlayDecorator::TechRTTParams& params) const
625 {
626     return _drapingManager.get(params._mainCamera).getBound();
627 }
628 
629 void
cullOverlayGroup(OverlayDecorator::TechRTTParams & params,osgUtil::CullVisitor * cv)630 DrapingTechnique::cullOverlayGroup(OverlayDecorator::TechRTTParams& params,
631                                    osgUtil::CullVisitor*            cv )
632 {
633     if ( params._rttCamera.valid() )
634     {
635         LocalPerViewData& local = *static_cast<LocalPerViewData*>(params._techniqueData.get());
636 
637         // this xforms from clip [-1..1] to texture [0..1] space
638         static osg::Matrix s_scaleBiasMat =
639             osg::Matrix::translate(1.0,1.0,1.0) *
640             osg::Matrix::scale(0.5,0.5,0.5);
641 
642         // resolution weighting based on camera distance.
643         if ( _maxFarNearRatio > 1.0 )
644         {
645             optimizeProjectionMatrix( params, _maxFarNearRatio );
646         }
647 
648         params._rttCamera->setViewMatrix      ( params._rttViewMatrix );
649         params._rttCamera->setProjectionMatrix( params._rttProjMatrix );
650 
651         osg::Matrix VPT = params._rttViewMatrix * params._rttProjMatrix * s_scaleBiasMat;
652 
653         if ( local._texGenUniform.valid() )
654         {
655             // premultiply the inv view matrix so we don't have precision problems in the shader
656             // (and it's faster too)
657 
658             // TODO:
659             // This only works properly if the terrain tiles have a DYNAMIC data variance.
660             // That is because we are setting a Uniform value during the CULL traversal, and
661             // it's possible that the stateset from the previous frame has not yet been
662             // dispatched to render. So we need to come up with a way to address this.
663             // In the meantime, I patched the MP engine to set a DYNAMIC data variance on
664             // terrain tiles to work around the problem.
665             //
666             // Note that we require the InverseViewMatrix, but it is OK to invert the ModelView matrix as the model matrix is identity here.
667             osg::Matrix vm;
668             vm.invert( *cv->getModelViewMatrix() );
669             local._texGenUniform->set( vm * VPT );
670         }
671 
672         // traverse the overlay group (via the RTT camera).
673         static_cast<DrapingCamera*>(params._rttCamera.get())->accept( *cv, cv->getCurrentCamera() );
674     }
675 }
676 
677 
678 void
setTextureSize(int texSize)679 DrapingTechnique::setTextureSize( int texSize )
680 {
681     _textureSize = texSize;
682 }
683 
684 void
setTextureUnit(int texUnit)685 DrapingTechnique::setTextureUnit( int texUnit )
686 {
687     if ( !_explicitTextureUnit.isSet() || texUnit != _explicitTextureUnit.value() )
688     {
689         _explicitTextureUnit = texUnit;
690     }
691 }
692 
693 void
setMipMapping(bool value)694 DrapingTechnique::setMipMapping( bool value )
695 {
696     if ( value != _mipmapping )
697     {
698         _mipmapping = value;
699 
700         if ( _mipmapping )
701             OE_INFO << LC << "Overlay mipmapping " << (value?"enabled":"disabled") << std::endl;
702     }
703 }
704 
705 void
setOverlayBlending(bool value)706 DrapingTechnique::setOverlayBlending( bool value )
707 {
708     if ( value != _rttBlending )
709     {
710         _rttBlending = value;
711 
712         if ( _rttBlending )
713             OE_INFO << LC << "Overlay blending " << (value?"enabled":"disabled")<< std::endl;
714     }
715 }
716 
717 bool
getAttachStencil() const718 DrapingTechnique::getAttachStencil() const
719 {
720     return _attachStencil;
721 }
722 
723 void
setAttachStencil(bool value)724 DrapingTechnique::setAttachStencil( bool value )
725 {
726     _attachStencil = value;
727 }
728 
729 void
setResolutionRatio(float value)730 DrapingTechnique::setResolutionRatio(float value)
731 {
732     // not a typo. "near/far resolution" is equivalent to "far/near clip plane extent"
733     // with respect to the overlay projection frustum.
734     _maxFarNearRatio = (double)osg::clampAbove(value, 1.0f);
735 }
736 
737 float
getResolutionRatio() const738 DrapingTechnique::getResolutionRatio() const
739 {
740     // not a typo. "near/far resolution" is equivalent to "far/near clip plane extent"
741     // with respect to the overlay projection frustum.
742     return (float)_maxFarNearRatio;
743 }
744 
745 void
onInstall(TerrainEngineNode * engine)746 DrapingTechnique::onInstall( TerrainEngineNode* engine )
747 {
748     if ( !_textureSize.isSet() )
749     {
750         unsigned maxSize = Registry::capabilities().getMaxFastTextureSize();
751         _textureSize.init( osg::minimum( 2048u, maxSize ) );
752     }
753 }
754 
755 void
onUninstall(TerrainEngineNode * engine)756 DrapingTechnique::onUninstall( TerrainEngineNode* engine )
757 {
758     if ( !_explicitTextureUnit.isSet() && _textureUnit.isSet() )
759     {
760         engine->getResources()->releaseTextureImageUnit( *_textureUnit );
761         _textureUnit.unset();
762     }
763 }
764