//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
-// Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
//
# include <simgear_config.h>
#endif
+#include <osg/Fog>
+#include <osg/Texture2D>
+#include <osg/PositionAttitudeTransform>
+#include <osg/Vec4f>
+
#include <simgear/compiler.h>
-#include <plib/sg.h>
-#include <plib/ssg.h>
+#include <simgear/math/sg_random.h>
#include <simgear/math/sg_geodesy.hxx>
-#include <simgear/math/polar3d.hxx>
-#include STL_ALGORITHM
+#include <algorithm>
#include <vector>
-SG_USING_STD(vector);
+using std::vector;
#include <simgear/environment/visual_enviro.hxx>
+#include <simgear/scene/util/RenderConstants.hxx>
+#include <simgear/scene/util/SGUpdateVisitor.hxx>
+#include "sky.hxx"
#include "newcloud.hxx"
#include "cloudfield.hxx"
-static list_of_culledCloud inViewClouds;
+#if defined(__MINGW32__)
+#define isnan(x) _isnan(x)
+#endif
-// visibility distance for clouds in meters
-float SGCloudField::CloudVis = 25000.0f;
-bool SGCloudField::enable3D = true;
-// fieldSize must be > CloudVis or we can destroy the impostor cache
-// a cloud must only be seen once or the impostor will be generated for each of his positions
-double SGCloudField::fieldSize = 27000.0;
-float SGCloudField::density = 100.0;
-static int last_cache_size = 1*1024;
-static int cacheResolution = 64;
+#if defined (__FreeBSD__)
+# if __FreeBSD_version < 500000
+ extern "C" {
+ inline int isnan(double r) { return !(r <= 0 || r >= 0); }
+ }
+# endif
+#endif
-int SGCloudField::get_CacheResolution(void) {
- return cacheResolution;
-}
+using namespace simgear;
-void SGCloudField::set_CacheResolution(int resolutionPixels) {
- if(cacheResolution == resolutionPixels)
- return;
- cacheResolution = resolutionPixels;
- if(enable3D) {
- int count = last_cache_size * 1024 / (cacheResolution * cacheResolution * 4);
- if(count == 0)
- count = 1;
- SGNewCloud::cldCache->setCacheSize(count, cacheResolution);
- }
-}
-int SGCloudField::get_CacheSize(void) {
- return SGNewCloud::cldCache->queryCacheSize();
-}
-
-void SGCloudField::set_CacheSize(int sizeKb) {
- // apply in rendering option dialog
- if(last_cache_size == sizeKb)
- return;
- if(sizeKb == 0)
- return;
- if(sizeKb)
- last_cache_size = sizeKb;
- if(enable3D) {
- int count = last_cache_size * 1024 / (cacheResolution * cacheResolution * 4);
- if(count == 0)
- count = 1;
-// SGNewCloud::cldCache->setCacheSize(sizeKb);
- SGNewCloud::cldCache->setCacheSize(count, cacheResolution);
- }
-}
-void SGCloudField::set_CloudVis(float distance) {
- if( distance <= fieldSize )
- SGCloudField::CloudVis = distance;
-}
-void SGCloudField::set_density(float density) {
- SGCloudField::density = density;
-}
-void SGCloudField::set_enable3dClouds(bool enable) {
- if(enable3D == enable)
- return;
- enable3D = enable;
- if(enable) {
- SGNewCloud::cldCache->setCacheSize(last_cache_size);
- } else {
- SGNewCloud::cldCache->setCacheSize(0);
- }
-}
+float SGCloudField::fieldSize = 50000.0f;
+double SGCloudField::timer_dt = 0.0;
+float SGCloudField::view_distance = 20000.0f;
+SGVec3f SGCloudField::view_vec, SGCloudField::view_X, SGCloudField::view_Y;
// reposition the cloud layer at the specified origin and orientation
-void SGCloudField::reposition( sgVec3 p, sgVec3 up, double lon, double lat, double alt, double dt) {
- sgMat4 T1, LON, LAT;
- sgVec3 axis;
-
- sgMakeTransMat4( T1, p );
-
- sgSetVec3( axis, 0.0, 0.0, 1.0 );
- sgMakeRotMat4( LON, lon * SGD_RADIANS_TO_DEGREES, axis );
-
- sgSetVec3( axis, 0.0, 1.0, 0.0 );
- sgMakeRotMat4( LAT, 90.0 - lat * SGD_RADIANS_TO_DEGREES, axis );
-
- sgMat4 TRANSFORM;
-
- sgCopyMat4( TRANSFORM, T1 );
- sgPreMultMat4( TRANSFORM, LON );
- sgPreMultMat4( TRANSFORM, LAT );
-
- sgCoord layerpos;
- sgSetCoord( &layerpos, TRANSFORM );
-
- sgMakeCoordMat4( transform, &layerpos );
-
-
- this->alt = alt;
-
- // simulate clouds movement from wind
- double speed = 10.0f;
- double direction = 45.0;
- double sp_dist = speed*dt;
- if (sp_dist > 0) {
- double bx = cos((180.0-direction) * SGD_DEGREES_TO_RADIANS) * sp_dist;
- double by = sin((180.0-direction) * SGD_DEGREES_TO_RADIANS) * sp_dist;
- relative_position[SG_X] += bx;
- relative_position[SG_Y] += by;
- }
-
- if ( lon != last_lon || lat != last_lat || sp_dist != 0 ) {
- Point3D start( last_lon, last_lat, 0.0 );
- Point3D dest( lon, lat, 0.0 );
- double course = 0.0, dist = 0.0;
-
- calc_gc_course_dist( dest, start, &course, &dist );
- // if start and dest are too close together,
- // calc_gc_course_dist() can return a course of "nan". If
- // this happens, lets just use the last known good course.
- // This is a hack, and it would probably be better to make
- // calc_gc_course_dist() more robust.
- if ( isnan(course) ) {
- course = last_course;
- } else {
- last_course = course;
+bool SGCloudField::reposition( const SGVec3f& p, const SGVec3f& up, double lon, double lat,
+ double dt, int asl )
+{
+ osg::Matrix T, LON, LAT;
+
+ // Always update the altitude transform, as this allows
+ // the clouds to rise and fall smoothly depending on environment updates.
+ altitude_transform->setPosition(osg::Vec3d(0.0, 0.0, (double) asl));
+
+ // Calculating the reposition information is expensive.
+ // Only perform the reposition every 60 frames.
+ reposition_count = (reposition_count + 1) % 60;
+ if ((reposition_count != 0) || !defined3D) return false;
+
+ SGGeoc pos = SGGeoc::fromGeod(SGGeod::fromRad(lon, lat));
+
+ double dist = SGGeodesy::distanceM(cld_pos, pos);
+
+ if (dist > (fieldSize * 2)) {
+ // First time or very large distance
+ SGVec3<double> cart;
+ SGGeodesy::SGGeodToCart(SGGeod::fromRad(lon, lat), cart);
+ T.makeTranslate(toOsg(cart));
+
+ LON.makeRotate(lon, osg::Vec3(0, 0, 1));
+ LAT.makeRotate(90.0 * SGD_DEGREES_TO_RADIANS - lat, osg::Vec3(0, 1, 0));
+
+ field_transform->setMatrix( LAT*LON*T );
+ cld_pos = SGGeoc::fromGeod(SGGeod::fromRad(lon, lat));
+ } else if (dist > fieldSize) {
+ // Distance requires repositioning of cloud field.
+ // We can easily work out the direction to reposition
+ // from the course between the cloud position and the
+ // camera position.
+ SGGeoc pos = SGGeoc::fromGeod(SGGeod::fromRad(lon, lat));
+
+ float crs = SGGeoc::courseDeg(cld_pos, pos);
+ if ((crs < 45.0) || (crs > 315.0)) {
+ SGGeodesy::advanceRadM(cld_pos, 0.0, fieldSize, cld_pos);
}
-
- // calculate cloud movement due to external forces
- double ax = 0.0, ay = 0.0;
-
- if (dist > 0.0) {
- ax = cos(course) * dist;
- ay = sin(course) * dist;
+
+ if ((crs > 45.0) && (crs < 135.0)) {
+ SGGeodesy::advanceRadM(cld_pos, SGD_PI_2, fieldSize, cld_pos);
}
- deltax += ax;
- deltay += ay;
-
- last_lon = lon;
- last_lat = lat;
+ if ((crs > 135.0) && (crs < 225.0)) {
+ SGGeodesy::advanceRadM(cld_pos, SGD_PI, fieldSize, cld_pos);
+ }
+
+ if ((crs > 225.0) && (crs < 315.0)) {
+ SGGeodesy::advanceRadM(cld_pos, SGD_PI + SGD_PI_2, fieldSize, cld_pos);
+ }
+
+ SGVec3<double> cart;
+ SGGeodesy::SGGeodToCart(SGGeod::fromRad(cld_pos.getLongitudeRad(), cld_pos.getLatitudeRad()), cart);
+ T.makeTranslate(toOsg(cart));
+
+ LON.makeRotate(cld_pos.getLongitudeRad(), osg::Vec3(0, 0, 1));
+ LAT.makeRotate(90.0 * SGD_DEGREES_TO_RADIANS - cld_pos.getLatitudeRad(), osg::Vec3(0, 1, 0));
+
+ field_transform->setMatrix( LAT*LON*T );
}
+
+ // Render the clouds in order from farthest away layer to nearest one.
+ field_root->getStateSet()->setRenderBinDetails(CLOUDS_BIN, "DepthSortedBin");
-
- // correct the frustum with the right far plane
- ssgContext *context = ssgGetCurrentContext();
- frustum = *context->getFrustum();
- frustum.setFOV(55.0,0);
- frustum.setNearFar(1.0, CloudVis);
+ return true;
}
SGCloudField::SGCloudField() :
- last_density(0.0),
+ field_root(new osg::Group),
+ field_transform(new osg::MatrixTransform),
+ altitude_transform(new osg::PositionAttitudeTransform),
deltax(0.0),
deltay(0.0),
- last_course(0.0)
+ last_course(0.0),
+ last_coverage(0.0),
+ coverage(0.0),
+ reposition_count(0),
+ defined3D(false)
{
- sgSetVec3( relative_position, 0,0,0);
- theField.reserve(200);
- inViewClouds.reserve(200);
+ cld_pos = SGGeoc();
+ field_root->addChild(field_transform.get());
+ field_root->setName("3D Cloud field root");
+ osg::StateSet *rootSet = field_root->getOrCreateStateSet();
+ rootSet->setRenderBinDetails(CLOUDS_BIN, "DepthSortedBin");
+ rootSet->setAttributeAndModes(getFog());
+
+ osg::ref_ptr<osg::Group> quad_root = new osg::Group();
+
+ for (int i = 0; i < BRANCH_SIZE; i++) {
+ for (int j = 0; j < BRANCH_SIZE; j++) {
+ quad[i][j] = new osg::LOD();
+ quad[i][j]->setName("Quad");
+ quad_root->addChild(quad[i][j].get());
+ }
+ }
+
+ int leafs = QUADTREE_SIZE / BRANCH_SIZE;
+
+ for (int x = 0; x < QUADTREE_SIZE; x++) {
+ for (int y = 0; y < QUADTREE_SIZE; y++) {
+ field_group[x][y]= new osg::Switch;
+ field_group[x][y]->setName("3D cloud group");
+
+ // Work out where to put this node in the quad tree
+ int i = x / leafs;
+ int j = y / leafs;
+ quad[i][j]->addChild(field_group[x][y].get(), 0.0f, view_distance);
+ }
+ }
+
+ field_transform->addChild(altitude_transform.get());
+
+ // We duplicate the defined field group in a 3x3 array. This way,
+ // we can simply shift entire groups around.
+ // TODO: "Bend" the edge groups so when shifted they line up.
+ // Currently the clouds "jump down" when we reposition them.
+ for(int x = -1 ; x <= 1 ; x++) {
+ for(int y = -1 ; y <= 1 ; y++ ) {
+ osg::ref_ptr<osg::PositionAttitudeTransform> transform =
+ new osg::PositionAttitudeTransform;
+ transform->addChild(quad_root.get());
+ transform->setPosition(osg::Vec3(x*fieldSize, y * fieldSize, 0.0));
+
+ altitude_transform->addChild(transform.get());
+ }
+ }
}
SGCloudField::~SGCloudField() {
- list_of_Cloud::iterator iCloud;
- for( iCloud = theField.begin() ; iCloud != theField.end() ; iCloud++ ) {
- delete iCloud->aCloud;
- }
- theField.clear();
}
+void SGCloudField::clear(void) {
+ for (int x = 0; x < QUADTREE_SIZE; x++) {
+ for (int y = 0; y < QUADTREE_SIZE; y++) {
+ int num_children = field_group[x][y]->getNumChildren();
+ field_group[x][y]->removeChildren(0, num_children);
+ }
+ }
+
+ SGCloudField::defined3D = false;
+}
+
// use a table or else we see poping when moving the slider...
static int densTable[][10] = {
{0,0,0,0,0,0,0,0,0,0},
{1,1,1,1,1,1,1,1,1,1}
};
-// set the visible flag depending on density
-void SGCloudField::applyDensity(void) {
- int row = (int) (density / 10.0);
- int col = 0;
- list_of_Cloud::iterator iCloud;
- for( iCloud = theField.begin() ; iCloud != theField.end() ; iCloud++ ) {
- if(++col > 9)
- col = 0;
- if( densTable[row][col] ) {
- iCloud->visible = true;
- } else
- iCloud->visible = false;
- }
- last_density = density;
-}
+void SGCloudField::applyCoverage(void) {
+
+ int row = (int) (coverage * 10.0);
+ if (row > 9) row = 9;
+ int col = 0;
+
+ if (coverage != last_coverage) {
+ for (int x = 0; x < QUADTREE_SIZE; x++) {
+ for (int y = 0; y < QUADTREE_SIZE; y++) {
+ // Switch on/off the children depending on the required coverage.
+ int num_children = field_group[x][y]->getNumChildren();
+ for (int i = 0; i < num_children; i++) {
+ if (++col > 9) col = 0;
+ if ( densTable[row][col] ) {
+ field_group[x][y]->setValue(i, true);
+ } else {
+ field_group[x][y]->setValue(i, false);
+ }
+ }
+ }
+ }
+ }
-// add one cloud, data is not copied, ownership given
-void SGCloudField::addCloud( sgVec3 pos, SGNewCloud *cloud) {
- Cloud cl;
- sgCopyVec3( cl.pos, pos );
- cl.aCloud = cloud;
- cl.visible = true;
- cloud->SetPos( pos );
- theField.push_back( cl );
+ last_coverage = coverage;
}
-
-static float Rnd(float n) {
- return n * (-0.5f + rand() / (float) RAND_MAX);
+void SGCloudField::addCloud( SGVec3f& pos, SGNewCloud *cloud) {
+ defined3D = true;
+ osg::ref_ptr<osg::Geode> geode = cloud->genCloud();
+
+ // Determine which quadtree to put it in.
+ int x = (int) floor((pos.x() + fieldSize/2.0) * QUADTREE_SIZE / fieldSize);
+ if (x >= QUADTREE_SIZE) x = (QUADTREE_SIZE - 1);
+ if (x < 0) x = 0;
+
+ int y = (int) floor((pos.y() + fieldSize/2.0) * QUADTREE_SIZE / fieldSize);
+ if (y >= QUADTREE_SIZE) y = (QUADTREE_SIZE - 1);
+ if (y < 0) y = 0;
+
+ osg::ref_ptr<osg::PositionAttitudeTransform> transform = new osg::PositionAttitudeTransform;
+
+ transform->setPosition(toOsg(pos));
+ transform->addChild(geode.get());
+
+ field_group[x][y]->addChild(transform.get(), true);
}
-// for debug only
-// build a field of cloud of size 25x25 km, its a grid of 11x11 clouds
-void SGCloudField::buildTestLayer(void) {
-
- const float s = 2250.0f;
-
- for( int z = -5 ; z <= 5 ; z++) {
- for( int x = -5 ; x <= 5 ; x++ ) {
- SGNewCloud *cloud = new SGNewCloud;
- cloud->new_cu();
- sgVec3 pos = {(x+Rnd(0.7)) * s, 750.0f, (z+Rnd(0.7)) * s};
- addCloud(pos, cloud);
- }
- }
- applyDensity();
+void SGCloudField::applyVisRange(void) {
+
+ for (int x = 0; x < BRANCH_SIZE; x++) {
+ for (int y = 0; y < BRANCH_SIZE; y++) {
+ int num_children = quad[x][y]->getNumChildren();
+ for (int i = 0; i < num_children; i++) {
+ quad[x][y]->setRange(i, 0.0f, view_distance);
+ }
+ }
+ }
}
-// cull all clouds of a tiled field
-void SGCloudField::cullClouds(sgVec3 eyePos, sgMat4 mat) {
- list_of_Cloud::iterator iCloud;
-
- // TODO:cull the field before culling the clouds in the field (should eliminate 3 fields)
- for( iCloud = theField.begin() ; iCloud != theField.end() ; iCloud++ ) {
- sgVec3 dist;
- sgSphere sphere;
- if( ! iCloud->visible )
- continue;
- sgSubVec3( dist, iCloud->pos, eyePos );
- sphere.setCenter(dist[0], dist[2], dist[1]);
- float radius = iCloud->aCloud->getRadius();
- sphere.setRadius(radius);
- sphere.orthoXform(mat);
- if( frustum.contains( & sphere ) != SG_OUTSIDE ) {
- float squareDist = dist[0]*dist[0] + dist[1]*dist[1] + dist[2]*dist[2];
- culledCloud tmp;
- tmp.aCloud = iCloud->aCloud;
- sgCopyVec3( tmp.eyePos, eyePos );
- // save distance for later sort, opposite distance because we want back to front
- tmp.dist = - squareDist;
- inViewClouds.push_back(tmp);
- if( squareDist - radius*radius < 100*100 )
- sgEnviro.set_view_in_cloud(true);
- }
- }
-
+SGCloudField::CloudFog::CloudFog()
+{
+ fog = new osg::Fog;
+ fog->setMode(osg::Fog::EXP2);
+ fog->setDataVariance(osg::Object::DYNAMIC);
}
-
-// Render a cloud field
-// because no field can have an infinite size (and we don't want to reach his border)
-// we draw this field and adjacent fields.
-// adjacent fields are not real, its the same field displaced by some offset
-void SGCloudField::Render(void) {
- sgVec3 eyePos;
- double relx, rely;
-
- if( ! enable3D )
- return;
-
- if( last_density != density ) {
- last_density = density;
- applyDensity();
- }
-
- // ask the impostor cache to do some cleanup
- // TODO:don't do that for every field
- SGNewCloud::cldCache->startNewFrame();
-
- inViewClouds.clear();
-
-
- glPushMatrix();
-
- sgMat4 modelview, tmp, invtrans;
-
- // try to find the sun position
- sgTransposeNegateMat4( invtrans, transform );
- sgVec3 lightVec;
- ssgGetLight( 0 )->getPosition( lightVec );
- sgXformVec3( lightVec, invtrans );
- sgCopyVec3( SGNewCloud::modelSunDir, lightVec );
- sgSetVec3( SGNewCloud::modelSunDir, lightVec[0], lightVec[2], lightVec[1]);
- // try to find the lighting data (buggy)
- sgVec4 diffuse, ambient;
- ssgGetLight( 0 )->getColour( GL_DIFFUSE, diffuse );
- ssgGetLight( 0 )->getColour( GL_AMBIENT, ambient );
- sgScaleVec3 ( SGNewCloud::sunlight, diffuse , 0.70f);
- sgScaleVec3 ( SGNewCloud::ambLight, ambient , 0.60f);
-
- // voodoo things on the matrix stack
- ssgGetModelviewMatrix( modelview );
- sgCopyMat4( tmp, transform );
- sgPostMultMat4( tmp, modelview );
-
- // cloud fields are tiled on the flat earth
- // compute the position in the tile
- relx = -fmod( deltax + relative_position[SG_X] + tmp[3][0], fieldSize );
- rely = -fmod( deltay + relative_position[SG_Y] + tmp[3][1], fieldSize );
-
- sgSetVec3( eyePos, -relx, alt, -rely);
-
- tmp[3][2] = 0;
- tmp[3][0] = 0;
- tmp[3][1] = 0;
- ssgLoadModelviewMatrix( tmp );
-
-/* flat earth
-
- ^
- |
- | FFF
- | FoF
- | FFF
- |
- O----------->
- o = we are here
- F = adjacent fields
-*/
-
- for(int x = -1 ; x <= 1 ; x++)
- for(int y = -1 ; y <= 1 ; y++ ) {
- sgVec3 fieldPos;
- // pretend we are not where we are
- sgSetVec3(fieldPos, eyePos[0] + x*fieldSize, eyePos[1], eyePos[2] + y*fieldSize);
- cullClouds(fieldPos, tmp);
- }
- // sort all visible clouds back to front (because of transparency)
- std::sort( inViewClouds.begin(), inViewClouds.end() );
-
- // TODO:push states
- glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
- glEnable(GL_ALPHA_TEST);
- glAlphaFunc(GL_GREATER, 0.0f);
- glDisable(GL_CULL_FACE);
- glEnable(GL_DEPTH_TEST);
- glEnable(GL_SMOOTH);
- glEnable(GL_BLEND);
- glBlendFunc( GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
- glEnable( GL_TEXTURE_2D );
- glDisable( GL_FOG );
- glDisable(GL_LIGHTING);
-
- // test data: field = 11x11 cloud, 9 fields
- // depending on position and view direction, perhaps 40 to 60 clouds not culled
- list_of_culledCloud::iterator iCloud;
- for( iCloud = inViewClouds.begin() ; iCloud != inViewClouds.end() ; iCloud++ ) {
-// iCloud->aCloud->drawContainers();
- iCloud->aCloud->Render(iCloud->eyePos);
- }
-
- glBindTexture(GL_TEXTURE_2D, 0);
- glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ) ;
- glEnable( GL_FOG );
- glEnable(GL_CULL_FACE);
- glEnable(GL_DEPTH_TEST);
-
- ssgLoadModelviewMatrix( modelview );
-
- glPopMatrix();
-
+void SGCloudField::updateFog(double visibility, const osg::Vec4f& color)
+{
+ const double sqrt_m_log01 = sqrt(-log(0.01));
+ osg::Fog* fog = CloudFog::instance()->fog.get();
+ fog->setColor(color);
+ fog->setDensity(sqrt_m_log01 / visibility);
}
-