1 // dome.cxx -- model sky with an upside down "bowl"
3 // Written by Curtis Olson, started December 1997.
4 // SSG-ified by Curtis Olson, February 2000.
6 // Copyright (C) 1997-2000 Curtis L. Olson - http://www.flightgear.org/~curt
8 // This library is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU Library General Public
10 // License as published by the Free Software Foundation; either
11 // version 2 of the License, or (at your option) any later version.
13 // This library is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // Library General Public License for more details.
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 # include <simgear_config.h>
29 #include <simgear/compiler.h>
33 #include <osg/Geometry>
36 #include <osg/MatrixTransform>
37 #include <osg/Material>
38 #include <osg/ShadeModel>
39 #include <osg/PrimitiveSet>
40 #include <osg/CullFace>
42 #include <simgear/debug/logstream.hxx>
43 #include <simgear/math/Math.hxx>
44 #include <simgear/scene/util/VectorArrayAdapter.hxx>
45 #include <simgear/scene/material/Effect.hxx>
46 #include <simgear/scene/material/EffectGeode.hxx>
51 using namespace simgear;
53 // proportions of max dimensions fed to the build() routine
54 static const float center_elev = 1.0;
62 } domeParams[] = {{.5, .8660}, // 60deg from horizon
63 {.8660, .5}, // 30deg from horizon
64 // Original dome horizon vertices
70 const int numRings = 64; //sizeof(domeParams) / sizeof(domeParams[0]);
71 const int numBands = 64; // 12
72 const int halfBands = numBands / 2;
74 // Make dome a bit over half sphere
75 const float domeAngle = 120.0;
77 const float bandDelta = 360.0 / numBands;
78 const float ringDelta = domeAngle / (numRings+1);
80 // Which band is at horizon
81 const int halfRings = numRings * (90.0 / domeAngle);
82 const int upperRings = numRings * (60.0 / domeAngle); // top half
83 const int middleRings = numRings * (15.0 / domeAngle);
87 static const float upper_radius = 0.9701; // (.6, 0.15)
88 static const float upper_elev = 0.2425;
90 static const float middle_radius = 0.9960; // (.9, .08)
91 static const float middle_elev = 0.0885;
93 static const float lower_radius = 1.0;
94 static const float lower_elev = 0.0;
96 static const float bottom_radius = 0.9922; // (.8, -.1)
97 static const float bottom_elev = -0.1240;
101 SGSkyDome::SGSkyDome( void ) {
107 SGSkyDome::~SGSkyDome( void ) {
110 // Generate indices for a dome mesh. Assume a center vertex at 0, then
111 // rings of vertices. Each ring's vertices are stored together. An
112 // even number of longitudinal bands are assumed.
116 // Calculate the index of a vertex in the grid by using its address in
117 // the array that holds its location.
120 VectorArrayAdapter<Vec3Array> gridAdapter;
122 GridIndex(Vec3Array& array, int rowStride, int baseOffset) :
123 gridAdapter(array, rowStride, baseOffset), grid(array)
126 unsigned short operator() (int ring, int band)
128 return (unsigned short)(&gridAdapter(ring, band) - &grid[0]);
132 void SGSkyDome::makeDome(int rings, int bands, DrawElementsUShort& elements)
134 std::back_insert_iterator<DrawElementsUShort> pusher
135 = std::back_inserter(elements);
136 GridIndex grid(*dome_vl, numBands, 1);
137 for (int i = 0; i < bands; i++) {
138 *pusher = 0; *pusher = grid(0, i+1); *pusher = grid(0, i);
140 for (int j = 0; j < rings - 1; ++j) {
141 *pusher = grid(j, i); *pusher = grid(j, (i + 1)%bands);
142 *pusher = grid(j + 1, (i + 1)%bands);
143 *pusher = grid(j, i); *pusher = grid(j + 1, (i + 1)%bands);
144 *pusher = grid(j + 1, i);
146 // and up the next one
147 /* for (int j = rings - 1; j > 0; --j) {
148 *pusher = grid(j, i + 1); *pusher = grid(j - 1, i + 1);
149 *pusher = grid(j, (i + 2) % bands);
150 *pusher = grid(j, (i + 2) % bands); *pusher = grid(j - 1, i + 1);
151 *pusher = grid(j - 1, (i + 2) % bands);
153 *pusher = grid(0, i + 1); *pusher = 0;
154 *pusher = grid(0, (i + 2) % bands);*/
158 // initialize the sky object and connect it into our scene graph
160 SGSkyDome::build( double hscale, double vscale ) {
162 EffectGeode* geode = new EffectGeode;
163 // Geode* geode = new Geode;
164 geode->setName("Skydome");
165 geode->setCullingActive(false); // Prevent skydome from being culled away
167 Effect *effect = makeEffect("Effects/skydome", true);
169 geode->setEffect(effect);
172 osg::StateSet* stateSet = geode->getOrCreateStateSet();
173 stateSet->setRenderBinDetails(-10, "RenderBin");
175 osg::ShadeModel* shadeModel = new osg::ShadeModel;
176 shadeModel->setMode(osg::ShadeModel::SMOOTH);
177 stateSet->setAttributeAndModes(shadeModel);
178 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
179 stateSet->setMode(GL_FOG, osg::StateAttribute::OFF);
180 stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
181 stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::ON);
182 stateSet->setMode(GL_BLEND, osg::StateAttribute::OFF);
183 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF);
185 stateSet->setAttribute(new osg::CullFace(osg::CullFace::BACK));
187 osg::Material* material = new osg::Material;
188 stateSet->setAttribute(material);
190 dome_vl = new osg::Vec3Array(1 + numRings * numBands);
191 dome_cl = new osg::Vec3Array(1 + numRings * numBands);
192 // generate the raw vertex data
194 (*dome_vl)[0].set(0.0, 0.0, center_elev * vscale);
195 simgear::VectorArrayAdapter<Vec3Array> vertices(*dome_vl, numBands, 1);
197 for ( int i = 0; i < numBands; ++i ) {
198 double theta = (i * bandDelta) * SGD_DEGREES_TO_RADIANS;
199 double sTheta = hscale*sin(theta);
200 double cTheta = hscale*cos(theta);
201 for (int j = 0; j < numRings; ++j) {
202 vertices(j, i).set(cTheta * sin((j+1)*ringDelta*SGD_DEGREES_TO_RADIANS), //domeParams[j].radius,
203 sTheta * sin((j+1)*ringDelta*SGD_DEGREES_TO_RADIANS),// domeParams[j].radius,
204 vscale * cos((j+1)*ringDelta*SGD_DEGREES_TO_RADIANS)); //domeParams[j].elev * vscale);
208 DrawElementsUShort* domeElements
209 = new osg::DrawElementsUShort(GL_TRIANGLES);
210 makeDome(numRings, numBands, *domeElements);
211 osg::Geometry* geom = new Geometry;
212 geom->setName("Dome Elements");
213 geom->setUseDisplayList(false);
214 geom->setVertexArray(dome_vl.get());
215 geom->setColorArray(dome_cl.get());
216 geom->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
217 geom->setNormalBinding(osg::Geometry::BIND_OFF);
218 geom->addPrimitiveSet(domeElements);
219 geode->addDrawable(geom);
220 // force a repaint of the sky colors with ugly defaults
221 repaint(SGVec3f(1, 1, 1), SGVec3f(1, 1, 1), SGVec3f(1, 1, 1), 0.0, 5000.0 );
222 dome_transform = new osg::MatrixTransform;
223 dome_transform->addChild(geode);
225 return dome_transform.get();
228 static void fade_to_black(osg::Vec3 sky_color[], float asl, int count) {
229 const float ref_asl = 10000.0f;
230 const float d = exp( - asl / ref_asl );
231 for(int i = 0; i < count ; i++)
235 inline void clampColor(osg::Vec3& color)
237 color.x() = osg::clampTo(color.x(), 0.0f, 1.0f);
238 color.y() = osg::clampTo(color.y(), 0.0f, 1.0f);
239 color.z() = osg::clampTo(color.z(), 0.0f, 1.0f);
242 // repaint the sky colors based on current value of sun_angle, sky,
243 // and fog colors. This updates the color arrays for ssgVtxTable.
244 // sun angle in degrees relative to verticle
245 // 0 degrees = high noon
246 // 90 degrees = sun rise/set
247 // 180 degrees = darkest midnight
249 SGSkyDome::repaint( const SGVec3f& sun_color, const SGVec3f& sky_color,
250 const SGVec3f& fog_color, double sun_angle, double vis )
252 SGVec3f outer_param, outer_diff;
253 SGVec3f middle_param, middle_diff;
255 // Check for sunrise/sunset condition
256 if (sun_angle > 80) {
258 double sunAngleFactor = 10.0 - fabs(90.0 - sun_angle);
259 static const SGVec3f outerConstant(1.0 / 20.0, 1.0 / 40.0, -1.0 / 30.0);
260 static const SGVec3f middleConstant(1.0 / 40.0, 1.0 / 80.0, 0.0);
261 outer_param = sunAngleFactor * outerConstant;
262 middle_param = sunAngleFactor * middleConstant;
263 outer_diff = (1.0 / numRings) * outer_param;
264 middle_diff = (1.0 / numRings) * middle_param;
266 outer_param = SGVec3f(0, 0, 0);
267 middle_param = SGVec3f(0, 0, 0);
268 outer_diff = SGVec3f(0, 0, 0);
269 middle_diff = SGVec3f(0, 0, 0);
271 // printf(" outer_red_param = %.2f outer_red_diff = %.2f\n",
272 // outer_red_param, outer_red_diff);
274 // calculate transition colors between sky and fog
275 SGVec3f outer_amt = outer_param;
276 SGVec3f middle_amt = middle_param;
279 // First, recalulate the basic colors
282 // Magic factors for coloring the sky according visibility and
284 const double cvf = osg::clampBelow(vis, 45000.0);
285 const double vis_factor = osg::clampTo((vis - 1000.0) / 2000.0, 0.0, 1.0);
286 const float upperVisFactor = 1.0 - vis_factor * (0.7 + 0.3 * cvf/45000);
287 const float middleVisFactor = 1.0 - vis_factor * (0.1 + 0.85 * cvf/45000);
289 // Dome top is always sky_color
290 (*dome_cl)[0] = toOsg(sky_color);
291 simgear::VectorArrayAdapter<Vec3Array> colors(*dome_cl, numBands, 1);
292 const double saif = sun_angle/SG_PI;
293 static const SGVec3f blueShift(0.8, 1.0, 1.2);
294 const SGVec3f skyFogDelta = sky_color - fog_color;
295 const SGVec3f sunSkyDelta = sun_color - sky_color;
297 // For now the colors of the upper two rings are linearly
298 // interpolated between the zenith color and the first horizon
299 // ring color. Means angles from top to 30 degrees
301 for (int i = 0; i < halfBands+1; i++) {
302 SGVec3f diff = mult(skyFogDelta, blueShift);
303 diff *= (0.8 + saif - ((halfBands-i)/(float)(numBands-2)));
305 // Color the ~60 deg ring
306 colors(upperRings, i) = toOsg(sky_color - upperVisFactor * diff);
309 // Color top half by linear interpolation (90...60 degrees)
310 for (; j < upperRings; j++)
311 colors(j, i) = simgear::math::lerp(toOsg(sky_color), colors(upperRings, i), j / (float)upperRings);
313 j++; // Skip the 60 deg ring
314 // From 60 to ~85 degrees
315 for (int l = 0; j < upperRings + middleRings + 1; j++, l++)
316 colors(j, i) = simgear::math::lerp(colors(upperRings, i),
317 toOsg(sky_color - middleVisFactor * diff + middle_amt), l / (float)middleRings);
320 for (int l = 0; j < halfRings; j++, l++)
321 colors(j, i) = simgear::math::lerp(colors(upperRings + middleRings, i), toOsg(fog_color + outer_amt),
322 l / (float)(halfRings - upperRings - middleRings));
325 //colors(2, i) = toOsg(sky_color - upperVisFactor * diff);
326 //colors(3, i) = toOsg(sky_color - middleVisFactor * diff + middle_amt);
327 //colors(4, i) = toOsg(fog_color + outer_amt);
328 //colors(0, i) = simgear::math::lerp(toOsg(sky_color), colors(2, i), .3942);
329 //colors(1, i) = simgear::math::lerp(toOsg(sky_color), colors(2, i), .7885);
331 for (int j = 0; j < numRings - 1; ++j)
332 clampColor(colors(j, i));
334 outer_amt -= outer_diff;
335 middle_amt -= middle_diff;
338 // Other side of dome is mirror of the other
339 for (int i = halfBands+1; i < numBands; ++i)
340 for (int j = 0; j < numRings-1; ++j)
341 colors(j, i) = colors(j, numBands - i);
343 // Fade colors to black when going to space
344 // Center of dome is blackest and then fade decreases towards horizon
345 fade_to_black(&(*dome_cl)[0], asl * center_elev, 1);
346 for (int i = 0; i < numRings - 1; ++i) {
347 float fadeValue = (asl+0.05f) * cos(i*ringDelta*SGD_DEGREES_TO_RADIANS);
348 if(fadeValue < 0.0) fadeValue = 0.0; // Prevent brightening up if dome is over 90 degrees
349 fade_to_black(&colors(i, 0), fadeValue, //domeParams[i].elev,
353 // All rings below horizon are fog color
354 for ( int i = halfRings; i < numRings; i++)
355 for ( int j = 0; j < numBands; j++ )
356 colors(i, j) = toOsg(fog_color);
363 // reposition the sky at the specified origin and orientation
364 // lon specifies a rotation about the Z axis
365 // lat specifies a rotation about the new Y axis
366 // spin specifies a rotation about the new Z axis (and orients the
367 // sunrise/set effects
369 SGSkyDome::reposition( const SGVec3f& p, double _asl,
370 double lon, double lat, double spin ) {
373 osg::Matrix T, LON, LAT, SPIN;
375 // Translate to view position
376 // Point3D zero_elev = current_view.get_cur_zero_elev();
377 // xglTranslatef( zero_elev.x(), zero_elev.y(), zero_elev.z() );
378 T.makeTranslate( toOsg(p) );
380 // printf(" Translated to %.2f %.2f %.2f\n",
381 // zero_elev.x, zero_elev.y, zero_elev.z );
383 // Rotate to proper orientation
384 // printf(" lon = %.2f lat = %.2f\n",
385 // lon * SGD_RADIANS_TO_DEGREES,
386 // lat * SGD_RADIANS_TO_DEGREES);
387 // xglRotatef( lon * SGD_RADIANS_TO_DEGREES, 0.0, 0.0, 1.0 );
388 LON.makeRotate(lon, osg::Vec3(0, 0, 1));
390 // xglRotatef( 90.0 - f->get_Latitude() * SGD_RADIANS_TO_DEGREES,
392 LAT.makeRotate(90.0 * SGD_DEGREES_TO_RADIANS - lat, osg::Vec3(0, 1, 0));
394 // xglRotatef( l->sun_rotation * SGD_RADIANS_TO_DEGREES, 0.0, 0.0, 1.0 );
395 SPIN.makeRotate(spin, osg::Vec3(0, 0, 1));
397 dome_transform->setMatrix( SPIN*LAT*LON*T );