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  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>
18  */
19 #include "TritonContext"
20 #include "TritonDrawable"
21 #include "TritonHeightMap"
22 #include <Version.h>
23 #include <osg/MatrixTransform>
24 #include <osg/FrameBufferObject>
25 #include <osg/Depth>
26 
27 #include <osgEarth/SpatialReference>
28 #include <osgEarth/VirtualProgram>
29 #include <osgEarth/MapNode>
30 #include <osgEarth/TerrainEngineNode>
31 #include <osgEarth/Random>
32 
33 #undef  LC
34 #define LC "[TritonDrawable] "
35 
36 //#define DEBUG_HEIGHTMAP
37 
38 using namespace osgEarth::Triton;
39 
40 
TritonDrawable(TritonContext * TRITON)41 TritonDrawable::TritonDrawable(TritonContext* TRITON) :
42 _TRITON(TRITON)
43 {
44     // call this to ensure draw() gets called every frame.
45     setSupportsDisplayList( false );
46     setUseVertexBufferObjects( false );
47 
48     // dynamic variance prevents update/cull overlap when drawing this
49     setDataVariance( osg::Object::DYNAMIC );
50 }
51 
~TritonDrawable()52 TritonDrawable::~TritonDrawable()
53 {
54     //nop
55 }
56 
57 void
setMaskLayer(const osgEarth::ImageLayer * layer)58 TritonDrawable::setMaskLayer(const osgEarth::ImageLayer* layer)
59 {
60     _maskLayer = layer;
61 }
62 
63 void
setHeightMapGenerator(TritonHeightMap * value)64 TritonDrawable::setHeightMapGenerator(TritonHeightMap* value)
65 {
66     _heightMapGenerator = value;
67 }
68 
69 void
setPlanarReflectionMap(osg::Texture2D * map)70 TritonDrawable::setPlanarReflectionMap(osg::Texture2D* map)
71 {
72     _planarReflectionMap = map;
73 }
74 
75 void
setPlanarReflectionProjection(osg::RefMatrix * proj)76 TritonDrawable::setPlanarReflectionProjection(osg::RefMatrix* proj)
77 {
78     _planarReflectionProjection = proj;
79 }
80 
81 osg::BoundingBox
computeBoundingBox() const82 TritonDrawable::computeBoundingBox() const
83 {
84     return osg::BoundingBox();
85 }
86 
87 namespace {
88 
89 // Wrapper around Ocean->GetShaderObject() to account for API changes from Triton 3.x to 4.x
90 GLint
getOceanShader(::Triton::Ocean * ocean,::Triton::Shaders shaderProgram,void * context,const::Triton::Camera * camera)91 getOceanShader(::Triton::Ocean* ocean, ::Triton::Shaders shaderProgram, void* context, const ::Triton::Camera* camera)
92 {
93 #if (TRITON_MAJOR_VERSION >= 4)
94   return (GLint)ocean->GetShaderObject( shaderProgram, context, camera );
95 #else
96   return (GLint)ocean->GetShaderObject( shaderProgram );
97 #endif
98 }
99 
100 }
101 
102 void
drawImplementation(osg::RenderInfo & renderInfo) const103 TritonDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
104 {
105     osg::State* state = renderInfo.getState();
106 
107     state->disableAllVertexArrays();
108 
109     _TRITON->initialize( renderInfo );
110     if ( !_TRITON->ready() )
111         return;
112 
113     // Configure the height map generator.
114     // If configuration fails, attempt to continue without a heightmap.
115     if (_heightMapGenerator.valid())
116     {
117         bool configOK = _heightMapGenerator->configure(_TRITON->getHeightMapSize(), *state);
118         if (configOK == false)
119         {
120             _heightMapGenerator = 0L;
121             OE_WARN << LC << "Failed to establish a legal FBO configuration; disabling height map generator!" << std::endl;
122         }
123     }
124 
125     ::Triton::Environment* environment = _TRITON->getEnvironment();
126 
127     // Find or create the Triton camera for this OSG camera:
128     CameraLocal& local = _local.get(renderInfo.getCurrentCamera());
129     if (local._tritonCam == 0L)
130     {
131         local._tritonCam = environment->CreateCamera();
132         local._tritonCam->SetName(renderInfo.getCurrentCamera()->getName().c_str());
133     }
134     ::Triton::Camera* tritonCam = local._tritonCam;
135 
136     osgEarth::NativeProgramAdapterCollection& adapters = _adapters[ state->getContextID() ];
137     if ( adapters.empty() )
138     {
139         OE_INFO << LC << "Initializing Triton program adapters" << std::endl;
140         const char* prefix = "oe_"; // because, don't forget osg_*
141         adapters.push_back( new osgEarth::NativeProgramAdapter(state, getOceanShader(_TRITON->getOcean(), ::Triton::WATER_SURFACE, 0L, tritonCam), prefix, "WATER_SURFACE"));
142         adapters.push_back( new osgEarth::NativeProgramAdapter(state, getOceanShader(_TRITON->getOcean(), ::Triton::WATER_SURFACE_PATCH, 0L, tritonCam), prefix, "WATER_SURFACE_PATCH"));
143         adapters.push_back( new osgEarth::NativeProgramAdapter(state, getOceanShader(_TRITON->getOcean(), ::Triton::GOD_RAYS, 0L, tritonCam), prefix, "GOD_RAYS"));
144         adapters.push_back( new osgEarth::NativeProgramAdapter(state, getOceanShader(_TRITON->getOcean(), ::Triton::SPRAY_PARTICLES, 0L, tritonCam), prefix, "SPRAY_PARTICLES"));
145         adapters.push_back( new osgEarth::NativeProgramAdapter(state, getOceanShader(_TRITON->getOcean(), ::Triton::WAKE_SPRAY_PARTICLES, 0L, tritonCam), prefix, "WAKE_SPRAY_PARTICLES"));
146 #if 0
147         // In older Triton (3.91), this line causes problems in Core profile and prevents the ocean from drawing.  In newer Triton (4.01),
148         // this line causes a crash because there is no context passed in to GetShaderObject(), resulting in multiple NULL references.
149         adapters.push_back( new osgEarth::NativeProgramAdapter(state, getOceanShader(_TRITON->getOcean(), ::Triton::WATER_DECALS, 0L, tritonCam), prefix, "WATER_DECALS"));
150 #endif
151     }
152     adapters.apply( state );
153 
154 
155     // Pass the final view and projection matrices into Triton.
156     if ( environment )
157     {
158         tritonCam->SetCameraMatrix(state->getModelViewMatrix().ptr());
159         tritonCam->SetProjectionMatrix(state->getProjectionMatrix().ptr());
160     }
161 
162     if (_heightMapGenerator.valid())
163     {
164         GLint texName;
165         osg::Matrix hMM;
166         if (_heightMapGenerator->getTextureAndMatrix(renderInfo, texName, hMM))
167         {
168             // copy the OSG matrix to a Triton matrix:
169             ::Triton::Matrix4 texMat(
170                 hMM(0, 0), hMM(0, 1), hMM(0, 2), hMM(0, 3),
171                 hMM(1, 0), hMM(1, 1), hMM(1, 2), hMM(1, 3),
172                 hMM(2, 0), hMM(2, 1), hMM(2, 2), hMM(2, 3),
173                 hMM(3, 0), hMM(3, 1), hMM(3, 2), hMM(3, 3));
174 
175             environment->SetHeightMap((::Triton::TextureHandle)texName, texMat, 0L, tritonCam);
176 
177             OE_DEBUG << LC << "Updating height map, FN=" << renderInfo.getState()->getFrameStamp()->getFrameNumber() << std::endl;
178         }
179     }
180 
181     state->dirtyAllVertexArrays();
182 
183     // Now light and draw the ocean:
184     if ( environment )
185     {
186         // User pre-draw callback:
187         if (_TRITON->getCallback())
188         {
189             _TRITON->getCallback()->onDrawOcean(
190                 _TRITON->getEnvironmentWrapper(),
191                 _TRITON->getOceanWrapper());
192         }
193 
194         // The sun position is roughly where it is in our skybox texture:
195 
196         // Since this is a simple example we will just assume that Sun is the light from View light source
197         // TODO: fix this...
198         osg::Light* light = renderInfo.getView() ? renderInfo.getView()->getLight() : NULL;
199 
200         // This is the light attached to View so there are no transformations above..
201         // But in general case you would need to accumulate all transforms above the light into this matrix
202         osg::Matrix lightLocalToWorldMatrix = osg::Matrix::identity();
203 
204         // If you don't know where the sun lightsource is attached and don't know its local to world matrix you may use
205         // following elaborate scheme to grab the light source while drawing Triton ocean:
206         // - Install cull callback to catch CullVisitor and record pointer to its associated RenderStage
207         //   I was hoping RenderStage can be found from renderInfo in drawImplementation but I didn't figure how ...
208         // - When TritonDrawable::drawImplementation is called all lights will be already applied to OpenGL
209         //   then just find proper infinite directional light by scanning renderStage->PositionalStateContainer.
210         // - Note that we canot scan for the lights inside cull because they may not be traversed before Triton drawable
211         // - When you found interesting ligt source that can work as Sun, read its modelview matrix and lighting params
212         //   Multiply light position by ( modelview * inverse camera view ) and pass this to Triton with lighting colors
213 
214         if ( light && light->getPosition().w() == 0 )
215         {
216             osg::Vec4 ambient = light->getAmbient();
217             osg::Vec4 diffuse = light->getDiffuse();
218             osg::Vec4 position = light->getPosition();
219 
220             // Compute light position/direction in the world
221             position = position * lightLocalToWorldMatrix;
222 
223             // Diffuse direction and color
224             environment->SetDirectionalLight(
225                 ::Triton::Vector3( position[0], position[1], position[2] ),
226                 ::Triton::Vector3( diffuse[0],  diffuse[1],  diffuse[2] ) );
227 
228             // Sun-based ambient value:
229             osg::Vec3d up = osg::Vec3d(0,0,0) * renderInfo.getCurrentCamera()->getInverseViewMatrix();
230             up.normalize();
231             osg::Vec3d pos3 = osg::Vec3d(position.x(), position.y(), position.z());
232             pos3.normalize();
233             float dot = osg::clampAbove(up*pos3, 0.0); dot*=dot;
234             float sunAmbient = (float)osg::clampBetween( dot, 0.0f, 0.88f );
235             float fa = osg::maximum(sunAmbient, ambient[0]);
236 
237             // Ambient color based on the zenith color in the cube map
238             environment->SetAmbientLight( ::Triton::Vector3(fa, fa, fa) );
239         }
240 
241         else
242         {
243             environment->SetDirectionalLight( ::Triton::Vector3(0,0,1), ::Triton::Vector3(1,1,1) );
244             environment->SetAmbientLight( ::Triton::Vector3(0.88f, 0.88f, 0.88f) );
245         }
246 
247         if ( _cubeMap.valid() )
248         {
249             // Build transform from our cube map orientation space to native Triton orientation
250             // See worldToCubeMap function used in SkyBox to orient sky texture so that sky is up and earth is down
251             osg::Matrix m = osg::Matrix::rotate( osg::PI_2, osg::X_AXIS ); // = worldToCubeMap
252 
253             ::Triton::Matrix3 transformFromYUpToZUpCubeMapCoords(
254                 m(0,0), m(0,1), m(0,2),
255                 m(1,0), m(1,1), m(1,2),
256                 m(2,0), m(2,1), m(2,2) );
257 
258             // Grab the cube map from our sky box and give it to Triton to use as an _environment map
259             // GLenum texture = renderInfo.getState()->getLastAppliedTextureAttribute( _stage, osg::StateAttribute::TEXTURE );
260             environment->SetEnvironmentMap(
261                 (::Triton::TextureHandle)_cubeMap->getTextureObject( state->getContextID() )->id(),
262                 transformFromYUpToZUpCubeMapCoords );
263 
264             if( _planarReflectionMap.valid() && _planarReflectionProjection.valid() )
265             {
266                 osg::Matrix & p = *_planarReflectionProjection;
267 
268                 ::Triton::Matrix3 planarProjection(
269                     p(0,0), p(0,1), p(0,2),
270                     p(1,0), p(1,1), p(1,2),
271                     p(2,0), p(2,1), p(2,2) );
272 
273                 environment->SetPlanarReflectionMap(
274                     (::Triton::TextureHandle)_planarReflectionMap->getTextureObject( state->getContextID() )->id(),
275                     planarProjection,
276                     0.125 );
277             }
278         }
279 
280         // Draw the ocean for the current time sample
281         if ( _TRITON->getOcean() )
282         {
283             osg::GLExtensions* ext = osg::GLExtensions::Get(state->getContextID(), true);
284 
285             bool writeDepth = true;
286             const osg::Depth* depth = static_cast<const osg::Depth*>(state->getLastAppliedAttribute(osg::StateAttribute::DEPTH));
287             if (depth)
288                 writeDepth = depth->getWriteMask();
289 
290             double simTime = renderInfo.getView()->getFrameStamp()->getSimulationTime();
291             simTime = fmod(simTime, 86400.0);
292 
293             _TRITON->getOcean()->Draw(
294                 simTime,
295                 writeDepth, // depth writes
296                 true, // draw water
297                 true, // draw particles
298                 NULL, // optional context
299                 tritonCam);
300 
301         }
302     }
303 
304     // Put GL back in a state that won't confuse the OSG state tracking:
305     state->dirtyAllVertexArrays();
306     state->dirtyAllAttributes();
307     state->dirtyAllModes();
308 
309 #ifndef OSG_GL_FIXED_FUNCTION_AVAILABLE
310     // Keep OSG from reapplying GL_LIGHTING on next state change after dirtyAllModes().
311     state->setModeValidity(GL_LIGHTING, false);
312 #endif
313 
314     // Keep an eye on this.
315     // I had to remove something similar in another module (Rex engine) because it was causing
316     // positional attributes (like clip planes) to re-apply with an incorrect MVM. -gw
317     state->apply();
318 }
319