]> git.mxchange.org Git - simgear.git/blobdiff - simgear/scene/sky/cloudfield.cxx
Work around apparent OSG 3.2.0 normal binding bug.
[simgear.git] / simgear / scene / sky / cloudfield.cxx
index 0d225a0a3ae05dd1d857f420c1aacf966deb4316..45ae15f83c4b4cd9feb71f7d376b28e5578b0d0d 100644 (file)
 #  include <simgear_config.h>
 #endif
 
+#include <osg/Fog>
 #include <osg/Texture2D>
 #include <osg/PositionAttitudeTransform>
+#include <osg/Vec4f>
+#include <osgSim/Impostor>
 
 #include <simgear/compiler.h>
 
-#include <plib/sg.h>
 #include <simgear/math/sg_random.h>
 #include <simgear/math/sg_geodesy.hxx>
-#include <simgear/math/polar3d.hxx>
+#include <simgear/scene/util/SGSceneUserData.hxx>
 
 #include <algorithm>
 #include <vector>
+#include <iostream>
+
+using namespace std;
 
 using std::vector;
 
-#include <simgear/environment/visual_enviro.hxx>
+//#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"
@@ -56,210 +63,351 @@ using std::vector;
 #  endif
 #endif
 
-
-#if defined (__CYGWIN__)
-#include <ieeefp.h>
-#endif
+using namespace simgear;
 
 float SGCloudField::fieldSize = 50000.0f;
-float SGCloudField::density = 100.0f;
 double SGCloudField::timer_dt = 0.0;
-int reposition_count = 0;
-sgVec3 SGCloudField::view_vec, SGCloudField::view_X, SGCloudField::view_Y;
+float SGCloudField::view_distance = 20000.0f;
+bool SGCloudField::wrap = true;
+float SGCloudField::MAX_CLOUD_DEPTH = 2000.0f;
+bool SGCloudField::use_impostors = false;
+float SGCloudField::lod1_range = 8000.0f;
+float SGCloudField::lod2_range = 4000.0f;
+float SGCloudField::impostor_distance = 15000.0f;
+
+int impostorcount = 0;
+int lodcount = 0;
+int cloudcount = 0;
+
+SGVec3f SGCloudField::view_vec, SGCloudField::view_X, SGCloudField::view_Y;
 
-void SGCloudField::set_density(float density) {
-       SGCloudField::density = density;
-}
 
-// reposition the cloud layer at the specified origin and orientation
+// Reposition the cloud layer at the specified origin and orientation
 bool SGCloudField::reposition( const SGVec3f& p, const SGVec3f& up, double lon, double lat,
-                              double dt )
-{
-    osg::Matrix T, LON, LAT;
-    
-    // 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;
+                              double dt, int asl, float speed, float direction ) {
+    // Determine any movement of the placed clouds
+    if (placed_root->getNumChildren() == 0) return false;
+
+    SGVec3<double> cart;
+    SGGeod new_pos = SGGeod::fromRadFt(lon, lat, 0.0f);
+                            
+    SGGeodesy::SGGeodToCart(new_pos, cart);
+    osg::Vec3f osg_pos = toOsg(cart);
+    osg::Quat orient = toOsg(SGQuatd::fromLonLatRad(lon, lat) * SGQuatd::fromRealImag(0, SGVec3d(0, 1, 0)));
     
-    SGGeoc pos = SGGeoc::fromGeod(SGGeod::fromRad(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::Vec3f(0.0f, 0.0f, (float) asl));
     
-    double dist = SGGeodesy::distanceM(cld_pos, pos);
+    // Similarly, always determine the effects of the wind
+    osg::Vec3f wind = osg::Vec3f(-cos((direction + 180)* SGD_DEGREES_TO_RADIANS) * speed * dt,
+                                 sin((direction + 180)* SGD_DEGREES_TO_RADIANS) * speed * dt,
+                                 0.0f);
+    osg::Vec3f windosg = field_transform->getAttitude() * wind;
+    field_transform->setPosition(field_transform->getPosition() + windosg);
     
-    if (dist > (fieldSize * 2)) {
-        // First time or very large distance
-        SGVec3<double> cart;
-        SGGeodesy::SGGeodToCart(SGGeod::fromRad(lon, lat), cart);
-        T.makeTranslate(cart.osg());
-        
-        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);
-        }
-        
-        if ((crs > 45.0) && (crs < 135.0)) {
-            SGGeodesy::advanceRadM(cld_pos, SGD_PI_2, fieldSize, cld_pos);
-        }
-
-        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);
-        }
+    if (!wrap) {
+        // If we're not wrapping the cloudfield, then we make no effort to reposition
+        return false;
+    }
         
-        SGVec3<double> cart;
-        SGGeodesy::SGGeodToCart(SGGeod::fromRad(cld_pos.getLongitudeRad(), cld_pos.getLatitudeRad()), cart);
-        T.makeTranslate(cart.osg());
+    if ((old_pos - osg_pos).length() > fieldSize*2) {
+        // Big movement - reposition centered to current location.
+        field_transform->setPosition(osg_pos);
+        field_transform->setAttitude(orient);
+        old_pos = osg_pos;
+    } else {        
+        osg::Quat fta =  field_transform->getAttitude();
+        osg::Quat ftainv = field_transform->getAttitude().inverse();
         
-        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 );
+        // delta is the vector from the old position to the new position in cloud-coords
+        // osg::Vec3f delta = ftainv * (osg_pos - old_pos);
+        old_pos = osg_pos;
+                
+        // Check if any of the clouds should be moved.
+        for(CloudHash::const_iterator itr = cloud_hash.begin(), end = cloud_hash.end();
+            itr != end;
+            ++itr) {
+              
+             osg::ref_ptr<osg::PositionAttitudeTransform> pat = itr->second;
+            
+             if (pat == 0) {
+                continue;
+             }
+             
+             osg::Vec3f currpos = field_transform->getPosition() + fta * pat->getPosition();
+                                      
+             // Determine the vector from the new position to the cloud in cloud-space.
+             osg::Vec3f w =  ftainv * (currpos - toOsg(cart));               
+              
+             // Determine a course if required. Note that this involves some axis translation.
+             float x = 0.0;
+             float y = 0.0;
+             if (w.x() >  0.6*fieldSize) { y =  fieldSize; }
+             if (w.x() < -0.6*fieldSize) { y = -fieldSize; }
+             if (w.y() >  0.6*fieldSize) { x = -fieldSize; }
+             if (w.y() < -0.6*fieldSize) { x =  fieldSize; }
+              
+             if ((x != 0.0) || (y != 0.0)) {
+                 removeCloudFromTree(pat);
+                 SGGeod p = SGGeod::fromCart(toSG(field_transform->getPosition() + 
+                                                  fta * pat->getPosition()));
+                 addCloudToTree(pat, p, x, y);                
+            }
+        }        
     }
+    
+    // Render the clouds in order from farthest away layer to nearest one.
+    field_root->getStateSet()->setRenderBinDetails(CLOUDS_BIN, "DepthSortedBin");
     return true;
 }
 
 SGCloudField::SGCloudField() :
         field_root(new osg::Group),
-        field_transform(new osg::MatrixTransform),
-       deltax(0.0),
-       deltay(0.0),
-       last_course(0.0),
-       last_density(0.0),
-        defined3D(false)               
+        field_transform(new osg::PositionAttitudeTransform),
+        altitude_transform(new osg::PositionAttitudeTransform)
 {
-    cld_pos = SGGeoc();
-    field_root->addChild(field_transform.get());         
+    old_pos = osg::Vec3f(0.0f, 0.0f, 0.0f);
+    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();
-    osg::ref_ptr<osg::LOD> quad[BRANCH_SIZE][BRANCH_SIZE];
+    field_transform->addChild(altitude_transform.get());
+    placed_root = new osg::Group();
+    altitude_transform->addChild(placed_root);
+    impostorcount = 0;
+    lodcount = 0;
+    cloudcount = 0;
+}
     
-    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());
-        }
+SGCloudField::~SGCloudField() {
+}
+
+
+void SGCloudField::clear(void) {
+
+    for(CloudHash::const_iterator itr = cloud_hash.begin(), end = cloud_hash.end();
+        itr != end;
+        ++itr) {
+        removeCloudFromTree(itr->second);
     }
     
-    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 = (int) (BRANCH_SIZE * ((float) x) / ((float) QUADTREE_SIZE));
-            int j = (int) (BRANCH_SIZE * ((float) y) / ((float) QUADTREE_SIZE));
-            quad[i][j]->addChild(field_group[x][y].get(), 0.0f, 20000.0f);
-        }
-    }
+    cloud_hash.clear();
+}
 
-    // 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));
-
-            field_transform->addChild(transform.get());
+void SGCloudField::applyVisAndLoDRange(void)
+{
+    for (unsigned int i = 0; i < placed_root->getNumChildren(); i++) {
+        osg::ref_ptr<osg::LOD> lodnode1 = (osg::LOD*) placed_root->getChild(i);
+        for (unsigned int j = 0; j < lodnode1->getNumChildren(); j++) {
+            lodnode1->setRange(j, 0.0f, lod1_range + lod2_range + view_distance + MAX_CLOUD_DEPTH);
+            osg::ref_ptr<osg::LOD> lodnode2 = (osg::LOD*) lodnode1->getChild(j);
+            for (unsigned int k = 0; k < lodnode2->getNumChildren(); k++) {
+                lodnode2->setRange(k, 0.0f, view_distance + MAX_CLOUD_DEPTH);
+            }
         }
     }
 }
+            
+bool SGCloudField::addCloud(float lon, float lat, float alt, int index, osg::ref_ptr<EffectGeode> geode) {
+  return addCloud(lon, lat, alt, 0.0f, 0.0f, index, geode);
+}
 
-SGCloudField::~SGCloudField() {
+bool SGCloudField::addCloud(float lon, float lat, float alt, float x, float y, int index, osg::ref_ptr<EffectGeode> geode) {
+    // If this cloud index already exists, don't replace it.
+    if (cloud_hash[index]) return false;
+
+    osg::ref_ptr<osg::PositionAttitudeTransform> transform = new osg::PositionAttitudeTransform;
+
+    transform->addChild(geode.get());
+    addCloudToTree(transform, lon, lat, alt, x, y);
+    cloud_hash[index] = transform;
+    return true;
 }
 
+// Remove a give cloud from inside the tree, without removing it from the cloud hash
+void SGCloudField::removeCloudFromTree(osg::ref_ptr<osg::PositionAttitudeTransform> transform)
+{
+    if (transform == 0)
+    {
+        // Ooops!
+        return;
+    }
+    osg::ref_ptr<osg::Group> lodnode = transform->getParent(0);
+    lodnode->removeChild(transform);
+    cloudcount--;
 
-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();
+    if (lodnode->getNumChildren() == 0) {
+        osg::ref_ptr<osg::Group> lodnode1 = lodnode->getParent(0);
+        osg::ref_ptr<osgSim::Impostor> impostornode = (osgSim::Impostor*) lodnode1->getParent(0);
 
-            for (int i = 0; i < num_children; i++) {
-                field_group[x][y]->removeChild(i);
-            }
+        lodnode1->removeChild(lodnode);
+        lodcount--;
+
+        if (lodnode1->getNumChildren() == 0) {
+          impostornode->removeChild(lodnode1);
+          placed_root->removeChild(impostornode);
+          impostorcount--;
         }
     }
+}
+
+void SGCloudField::addCloudToTree(osg::ref_ptr<osg::PositionAttitudeTransform> transform,
+                                  float lon, float lat, float alt, float x, float y) {
     
-    SGCloudField::defined3D = false;
+    // Get the base position
+    SGGeod loc = SGGeod::fromDegFt(lon, lat, alt);
+    addCloudToTree(transform, loc, x, y);      
 }
 
-// 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,0,0,0,0,0,0,0,0,0},
-       {1,0,0,0,1,0,0,0,0,0},
-       {1,0,0,0,1,0,0,1,0,0}, // 30%
-       {1,0,1,0,1,0,0,1,0,0},
-       {1,0,1,0,1,0,1,1,0,0}, // 50%
-       {1,0,1,0,1,0,1,1,0,1},
-       {1,0,1,1,1,0,1,1,0,1}, // 70%
-       {1,1,1,1,1,0,1,1,0,1},
-       {1,1,1,1,1,0,1,1,1,1}, // 90%
-       {1,1,1,1,1,1,1,1,1,1}
-};
-
-void SGCloudField::applyDensity(void) {
-
-        int row = (int) (density / 10.0);
-        int col = 0;
-
-        if (density != last_density) {
-            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 density.
-                    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);
-                        }
-                    }
-                }
-            }
-        }
 
-        last_density = density;
+void SGCloudField::addCloudToTree(osg::ref_ptr<osg::PositionAttitudeTransform> transform,
+                                  SGGeod loc, float x, float y) {
+        
+    float alt = loc.getElevationFt();
+    // Determine any shift by x/y
+    if ((x != 0.0f) || (y != 0.0f)) {
+        double crs = 90.0 - SG_RADIANS_TO_DEGREES * atan2(y, x); 
+        double dst = sqrt(x*x + y*y);
+        double endcrs;
+        
+        SGGeod base_pos = SGGeod::fromGeodFt(loc, 0.0f);            
+        SGGeodesy::direct(base_pos, crs, dst, loc, endcrs);
+    }
+    
+    // The direct call provides the position at 0 alt, so adjust as required.      
+    loc.setElevationFt(alt);    
+    addCloudToTree(transform, loc);    
 }
-
-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;
+void SGCloudField::addCloudToTree(osg::ref_ptr<osg::PositionAttitudeTransform> transform, SGGeod loc) {        
+    // Work out where this cloud should go in OSG coordinates.
+    SGVec3<double> cart;
+    SGGeodesy::SGGeodToCart(loc, cart);
+    osg::Vec3f pos = toOsg(cart);
+
+
+    if (old_pos == osg::Vec3f(0.0f, 0.0f, 0.0f)) {
+        // First setup.
+        SGVec3<double> fieldcenter;
+        SGGeodesy::SGGeodToCart(SGGeod::fromDegFt(loc.getLongitudeDeg(), loc.getLatitudeDeg(), 0.0f), fieldcenter);
+        // Convert to the scenegraph orientation where we just rotate around
+        // the y axis 180 degrees.
+        osg::Quat orient = toOsg(SGQuatd::fromLonLatDeg(loc.getLongitudeDeg(), loc.getLatitudeDeg()) * SGQuatd::fromRealImag(0, SGVec3d(0, 1, 0)));
+        
+        osg::Vec3f osg_pos = toOsg(fieldcenter);            
+        
+        field_transform->setPosition(osg_pos);
+        field_transform->setAttitude(orient);
+        old_pos = osg_pos;
+    }
+    
+    // Convert position to cloud-coordinates
+    pos = pos - field_transform->getPosition();
+    pos = field_transform->getAttitude().inverse() * pos;    
+
+    // We have a two level dynamic quad tree which the cloud will be added
+    // to. If there are no appropriate nodes in the quad tree, they are
+    // created as required.
+    bool found = false;
+    osg::ref_ptr<osg::LOD> lodnode1;
+    osg::ref_ptr<osg::LOD> lodnode;
+    osg::ref_ptr<osgSim::Impostor> impostornode;
+
+    for (unsigned int i = 0; (!found) && (i < placed_root->getNumChildren()); i++) {
+        lodnode1 = (osg::LOD*) placed_root->getChild(i);
+        if ((lodnode1->getCenter() - pos).length2() < lod1_range*lod1_range) {
+          // New cloud is within RADIUS_LEVEL_1 of the center of the LOD node.
+          found = true;
+        }         
+    }
+
+    if (!found) {
+        if (use_impostors) {
+          impostornode = new osgSim::Impostor();
+          impostornode->setImpostorThreshold(impostor_distance);
+          //impostornode->setImpostorThresholdToBound();
+          //impostornode->setCenter(pos);                
+          placed_root->addChild(impostornode.get());
+          lodnode1 = (osg::ref_ptr<osg::LOD>) impostornode;
+        } else {
+          lodnode1 = new osg::LOD();
+          placed_root->addChild(lodnode1.get());
+        }
+        impostorcount++;
+    }
 
-        osg::ref_ptr<osg::PositionAttitudeTransform> transform = new osg::PositionAttitudeTransform;
+    // Now check if there is a second level LOD node at an appropriate distance
+    found = false;
+    
+    for (unsigned int j = 0; (!found) && (j < lodnode1->getNumChildren()); j++) {      
+        lodnode = (osg::LOD*) lodnode1->getChild(j);
+        if ((lodnode->getCenter() - pos).length2() < lod2_range*lod2_range) {
+            // We've found the right leaf LOD node
+            found = true;
+        }
+    }
 
-        transform->setPosition(pos.osg());
-        transform->addChild(geode.get());
+    if (!found) {
+        // No suitable leaf node was found, so we need to add one.
+        lodnode = new osg::LOD();
+        lodnode1->addChild(lodnode, 0.0f, lod1_range + lod2_range + view_distance + MAX_CLOUD_DEPTH);
+        lodcount++;
+    } 
+    
+    transform->setPosition(pos);
+    lodnode->addChild(transform.get(), 0.0f, view_distance + MAX_CLOUD_DEPTH);
+    cloudcount++;
+    SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "Impostors: " << impostorcount <<
+                                     " LoD: " << lodcount << 
+                                     " Clouds: " << cloudcount);
+
+    lodnode->dirtyBound();
+    lodnode1->dirtyBound();
+    field_root->dirtyBound();
+}
+        
+bool SGCloudField::deleteCloud(int identifier) {
+    osg::ref_ptr<osg::PositionAttitudeTransform> transform = cloud_hash[identifier];
+    if (transform == 0) return false;
         
-        field_group[x][y]->addChild(transform.get());
+    removeCloudFromTree(transform);
+    cloud_hash.erase(identifier);
+
+    return true;
+}
+        
+bool SGCloudField::repositionCloud(int identifier, float lon, float lat, float alt) {
+    return repositionCloud(identifier, lon, lat, alt, 0.0f, 0.0f);
+}
+
+bool SGCloudField::repositionCloud(int identifier, float lon, float lat, float alt, float x, float y) {
+    osg::ref_ptr<osg::PositionAttitudeTransform> transform = cloud_hash[identifier];
+    
+    if (transform == NULL) return false;
+
+    removeCloudFromTree(transform);
+    addCloudToTree(transform, lon, lat, alt, x, y);
+    return true;
+    }
+
+bool SGCloudField::isDefined3D(void) {
+    return (! cloud_hash.empty());
 }
+
+SGCloudField::CloudFog::CloudFog() {
+    fog = new osg::Fog;
+    fog->setMode(osg::Fog::EXP2);
+    fog->setDataVariance(osg::Object::DYNAMIC);
+}
+
+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);
+}
+