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