void CloudShaderGeometry::drawImplementation(RenderInfo& renderInfo) const
{
osg::State& state = *renderInfo.getState();
+ osg::Matrix vm = state.getModelViewMatrix();
+
+ //TODO: It isn't clear whether this is worth the perf hit ATM.
+
+ // Do a single iteration of a bubble sort. We do this in reverse
+ // so that elements closest to the camera bubble to the front than
+ // the elements further away
+ for(int i = (_cloudsprites.size() -2); i >= 0; i--)
+ {
+ osg::Vec4f p = vm * osg::Vec4f(_cloudsprites[i]->position.osg(), 1.0f);
+ osg::Vec4f q = vm * osg::Vec4f(_cloudsprites[i+1]->position.osg(), 1.0f);
+
+ if (p.z() > q.z())
+ {
+ CloudSprite c = *_cloudsprites[i];
+ *_cloudsprites[i] = *_cloudsprites[i+1];
+ *_cloudsprites[i+1] = c;
+ }
+ }
+
const Extensions* extensions = getExtensions(state.getContextID(),true);
for(CloudSpriteList::const_iterator t = _cloudsprites.begin(); t != _cloudsprites.end(); ++t)
{
- extensions->glVertexAttrib1f(TEXTURE_INDEX_X, (GLfloat) t->texture_index_x/varieties_x);
- extensions->glVertexAttrib1f(TEXTURE_INDEX_Y, (GLfloat) t->texture_index_y/varieties_y);
- extensions->glVertexAttrib1f(WIDTH, (GLfloat) t->width);
- extensions->glVertexAttrib1f(HEIGHT, (GLfloat) t->height);
- extensions->glVertexAttrib1f(SHADE, (GLfloat) t->shade);
- glColor4f(t->position.x(), t->position.y(), t->position.z(), 1.0);
+ extensions->glVertexAttrib1f(TEXTURE_INDEX_X, (GLfloat) (*t)->texture_index_x/varieties_x);
+ extensions->glVertexAttrib1f(TEXTURE_INDEX_Y, (GLfloat) (*t)->texture_index_y/varieties_y);
+ extensions->glVertexAttrib1f(WIDTH, (GLfloat) (*t)->width);
+ extensions->glVertexAttrib1f(HEIGHT, (GLfloat) (*t)->height);
+ extensions->glVertexAttrib1f(SHADE, (GLfloat) (*t)->shade);
+ glColor4f((*t)->position.x(), (*t)->position.y(), (*t)->position.z(), 1.0);
_geometry->draw(renderInfo);
}
}
for(CloudSpriteList::const_iterator itr = _cloudsprites.begin();
itr != _cloudsprites.end();
++itr) {
- bb.expandBy(geom_box.corner(0)*itr->width +
- osg::Vec3( itr->position.x(), itr->position.y(), itr->position.z() ));
- bb.expandBy(geom_box.corner(7)*itr->height +
- osg::Vec3( itr->position.x(), itr->position.y(), itr->position.z() ));
+ bb.expandBy(geom_box.corner(0)*(*itr)->width +
+ osg::Vec3( (*itr)->position.x(), (*itr)->position.y(), (*itr)->position.z() ));
+ bb.expandBy(geom_box.corner(7)*(*itr)->height +
+ osg::Vec3( (*itr)->position.x(), (*itr)->position.y(), (*itr)->position.z() ));
}
return bb;
}
fr[4].getFloat(w) && fr[4].getFloat(h)&& fr[4].getFloat(s)) {
fr += 5;
//SGVec3f* v = new SGVec3f(v.x(), v.y(), v.z());
- geom._cloudsprites.push_back(CloudShaderGeometry::CloudSprite(v, tx, ty, w, h,s));
+ geom._cloudsprites.push_back(new CloudShaderGeometry::CloudSprite(v, tx, ty, w, h,s));
} else {
++fr;
}
fw.indent() << "instances " << geom._cloudsprites.size() << std::endl;
fw.indent() << "{" << std::endl;
fw.moveIn();
- for (CloudShaderGeometry::CloudSpriteList::const_iterator iter
+ for (CloudShaderGeometry::CloudSpriteList::const_iterator itr
= geom._cloudsprites.begin();
- iter != geom._cloudsprites.end();
- ++iter) {
- fw.indent() << iter->position.x() << " " << iter->position.y() << " "
- << iter->position.z() << " " << iter->texture_index_x << " "
- << iter->texture_index_y << " "
- << iter->width << " " << iter->height << " " << iter->shade << std::endl;
+ itr != geom._cloudsprites.end();
+ ++itr) {
+ fw.indent() << (*itr)->position.x() << " " << (*itr)->position.y() << " "
+ << (*itr)->position.z() << " " << (*itr)->texture_index_x << " "
+ << (*itr)->texture_index_y << " "
+ << (*itr)->width << " " << (*itr)->height << " " << (*itr)->shade << std::endl;
}
fw.moveOut();
fw.indent() << "}" << std::endl;
META_Object(flightgear, CloudShaderGeometry);
struct CloudSprite {
- CloudSprite(const SGVec3f& p, int tx, int ty, float w, float h, float s) :
+ CloudSprite(SGVec3f& p, int tx, int ty, float w, float h, float s) :
position(p), texture_index_x(tx), texture_index_y(ty), width(w), height(h), shade(s)
{ }
float shade;
};
- typedef std::vector<CloudSprite> CloudSpriteList;
+ typedef std::vector<CloudSprite*> CloudSpriteList;
- void insert(const CloudSprite& t)
+ void insert(CloudSprite* t)
{ _cloudsprites.push_back(t); }
- void insert(const SGVec3f& p, int tx, int ty, float w, float h, float s)
- { insert(CloudSprite(p, tx, ty, w, h, s)); }
+ void insert(SGVec3f& p, int tx, int ty, float w, float h, float s)
+ { insert(new CloudSprite(p, tx, ty, w, h, s)); }
unsigned getNumCloudSprite() const
{ return _cloudsprites.size(); }
- const CloudSprite& getCloudSprite(unsigned i) const
+ CloudSprite* getCloudSprite(unsigned i) const
{ return _cloudsprites[i]; }
CloudSpriteList _cloudsprites;
_geometry = geometry;
}
- void addSprite(const SGVec3f& p, int tx, int ty, float w, float h, float s, float cull)
+ void addSprite(SGVec3f& p, int tx, int ty, float w, float h, float s, float cull)
{
// Only add the sprite if it is further than the cull distance to all other sprites
- for (CloudShaderGeometry::CloudSpriteList::const_iterator iter = _cloudsprites.begin();
+ for (CloudShaderGeometry::CloudSpriteList::iterator iter = _cloudsprites.begin();
iter != _cloudsprites.end();
++iter)
{
- if (distSqr(iter->position, p) < cull)
+ if (distSqr((*iter)->position, p) < cull)
{
// Too close - cull it
return;
}
}
- _cloudsprites.push_back(CloudSprite(p, tx, ty, w, h, s));
+ _cloudsprites.push_back(new CloudSprite(p, tx, ty, w, h, s));
}
-
+
osg::ref_ptr<osg::Drawable> _geometry;
int varieties_x;
int varieties_y;
-
+
protected:
virtual ~CloudShaderGeometry() {}
bool SGCloudField::reposition( const SGVec3f& p, const SGVec3f& up, double lon, double lat,
double dt )
{
- osg::Matrix T, LON, LAT;
-
+ osg::Matrix T, LON, LAT;
+
+ 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(cart.osg());
+
LON.makeRotate(lon, osg::Vec3(0, 0, 1));
LAT.makeRotate(90.0 * SGD_DEGREES_TO_RADIANS - lat, osg::Vec3(0, 1, 0));
- osg::Vec3 u = up.osg();
- u.normalize();
-
- if ((last_lon == 0.0f) || (fabs(last_lon - lon) > 1.0) || (fabs(last_lat - lat) > 1.0))
- {
- // First time, or large delta requires repositioning from scratch.
- // TODO: Make this calculation better - a 0.5 degree shift will take a number
- // of reposition calls to correct at the moment.
-
- // combine p and asl (meters) to get translation offset.
- osg::Vec3 pos = p.osg();
- pos += u;
-
- T.makeTranslate(pos);
-
- field_transform->setMatrix( LAT*LON*T );
- last_lon = lon;
- last_lat = lat;
- last_pos = p.osg();
+ 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);
}
- else
- {
- // Rotation positon back to simple X-Z space.
- osg::Vec3 pos = last_pos;
- pos += u;
-
- T.makeTranslate(pos);
-
- osg::Matrix U = LAT*LON;
- osg::Vec3 x = osg::Vec3f(fieldSize, 0.0, 0.0)*U;
- osg::Vec3 y = osg::Vec3f(0.0, fieldSize, 0.0)*U;
-
- osg::Matrix V;
- V.makeIdentity();
- V.invert(U*T);
-
- osg::Vec3 q = pos*V;
-
- // Shift the field if we've moved away from the centre box.
- if (q.x() > fieldSize) last_pos = last_pos + x;
- if (q.x() < -fieldSize) last_pos = last_pos - x;
- if (q.y() > fieldSize) last_pos = last_pos + y;
- if (q.y() < -fieldSize) last_pos = last_pos - y;
-
- pos = last_pos;
- pos += u;
-
- T.makeTranslate(pos);
- field_transform->setMatrix( LAT*LON*T );
+
+ 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(cart.osg());
+
+ 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 );
+ }
return true;
}
SGCloudField::SGCloudField() :
field_root(new osg::Group),
field_transform(new osg::MatrixTransform),
- field_group(new osg::Switch),
deltax(0.0),
deltay(0.0),
- last_lon(0.0),
- last_lat(0.0),
last_course(0.0),
last_density(0.0),
defined3D(false)
{
- field_root->addChild(field_transform.get());
- field_group->setName("3Dcloud");
-
- // We duplicate the defined field group in a 3x3 array. This way,
- // we can simply shift entire groups around.
- 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(field_group.get());
- transform->setPosition(osg::Vec3(x*fieldSize, y * fieldSize, 0.0));
-
- field_transform->addChild(transform.get());
- }
+ cld_pos = SGGeoc();
+ field_root->addChild(field_transform.get());
+
+ osg::ref_ptr<osg::Group> quad_root = new osg::Group();
+ osg::ref_ptr<osg::LOD> quad[BRANCH_SIZE][BRANCH_SIZE];
+
+ 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());
}
-
+ }
+
+ 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);
+ }
+ }
+
+ // 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());
+ }
+ }
}
SGCloudField::~SGCloudField() {
void SGCloudField::clear(void) {
- int num_children = field_group->getNumChildren();
+ for (int x = 0; x < QUADTREE_SIZE; x++) {
+ for (int y = 0; y < QUADTREE_SIZE; y++) {
+ int num_children = field_group[x][y]->getNumChildren();
- for (int i = 0; i < num_children; i++) {
- field_group->removeChild(i);
+ for (int i = 0; i < num_children; i++) {
+ field_group[x][y]->removeChild(i);
+ }
}
- SGCloudField::defined3D = false;
+ }
+
+ SGCloudField::defined3D = false;
}
// use a table or else we see poping when moving the slider...
int row = (int) (density / 10.0);
int col = 0;
- int num_children = field_group->getNumChildren();
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.
- for (int i = 0; i < num_children; i++) {
+ 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->setValue(i, true);
+ field_group[x][y]->setValue(i, true);
} else {
- field_group->setValue(i, false);
+ field_group[x][y]->setValue(i, false);
}
+ }
}
+ }
}
last_density = density;
void SGCloudField::addCloud( SGVec3f& pos, SGNewCloud *cloud) {
defined3D = true;
- osg::ref_ptr<osg::LOD> lod = cloud->genCloud();
+ 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(pos.osg());
- transform->addChild(lod.get());
-
- field_group->addChild(transform.get());
+ transform->addChild(geode.get());
+
+ field_group[x][y]->addChild(transform.get());
}
};
float Rnd(float);
+
+ // We create a quadtree two levels deep
+ static const int BRANCH_SIZE = 16;
+ static const int QUADTREE_SIZE = 32;
// this is a relative position only, with that we can move all clouds at once
sgVec3 relative_position;
osg::ref_ptr<osg::Group> field_root;
osg::ref_ptr<osg::MatrixTransform> field_transform;
- osg::ref_ptr<osg::Switch> field_group;
+ osg::ref_ptr<osg::Switch> field_group[QUADTREE_SIZE][QUADTREE_SIZE];
+ osg::ref_ptr<osg::LOD> field_lod;
double deltax, deltay, alt;
- double last_lon, last_lat, last_course;
+ double last_course;
sgSphere field_sphere;
float last_density;
- osg::Vec3 last_pos;
+ SGGeoc cld_pos;
public:
"#version 120\n"
"\n"
"varying float fogFactor;\n"
- "varying float alphaBlend;\n"
"attribute float textureIndexX;\n"
"attribute float textureIndexY;\n"
"attribute float wScale;\n"
" vec4 backlight = 0.8 * gl_LightSource[0].ambient + 0.2 * gl_LightSource[0].diffuse;\n"
" gl_FrontColor = mix(backlight, gl_LightSource[0].diffuse, n);\n"
" gl_FrontColor += gl_FrontLightModelProduct.sceneColor;\n"
+// As we get within 100m of the sprite, it is faded out
" gl_FrontColor.a = smoothstep(10.0, 100.0, fogCoord);\n"
" gl_BackColor = gl_FrontColor;\n"
// Fog doesn't affect clouds as much as other objects.
- " fogFactor = exp( -gl_Fog.density * fogCoord);\n"
+ " fogFactor = exp( -gl_Fog.density * fogCoord * 0.5);\n"
" fogFactor = clamp(fogFactor, 0.0, 1.0);\n"
-// As we get within 100m of the sprite, it is faded out
- " alphaBlend = smoothstep(10.0, 100.0, fogCoord);\n"
"}\n";
static char fragmentShaderSource[] =
"uniform sampler2D baseTexture; \n"
"varying float fogFactor;\n"
- "varying float alphaBlend;\n"
"\n"
"void main(void)\n"
"{\n"
" vec4 base = texture2D( baseTexture, gl_TexCoord[0].st);\n"
" vec4 finalColor = base * gl_Color;\n"
-// " finalColor.a = min(alphaBlend, finalColor.a);\n"
" gl_FragColor = mix(gl_Fog.color, finalColor, fogFactor );\n"
"}\n";
} else {
stateSet = iter->second.get();
}
+
+ quad = createOrthQuad(min_sprite_width, min_sprite_height, num_textures_x, num_textures_y);
}
SGNewCloud::~SGNewCloud() {
}
-osg::Geometry* createOrthQuad(float w, float h, int varieties_x, int varieties_y)
+osg::Geometry* SGNewCloud::createOrthQuad(float w, float h, int varieties_x, int varieties_y)
{
// Create front and back polygons so we don't need to screw around
// with two-sided lighting in the shader.
return n * (-0.5f + (sg_random() + sg_random()) / 2.0f);
}
-osg::ref_ptr<LOD> SGNewCloud::genCloud() {
- LOD* result = new LOD;
+osg::ref_ptr<Geode> SGNewCloud::genCloud() {
Geode* geode = new Geode;
CloudShaderGeometry* sg = new CloudShaderGeometry(num_textures_x, num_textures_y);
- Geometry* quad = createOrthQuad(min_sprite_width, min_sprite_height, num_textures_x, num_textures_y);
- // Determine how big this specific cloud instance is.
- float width = min_width + sg_random() * (max_width - min_width);
- float height = min_height + sg_random() * (max_height - min_height);
+ // Determine how big this specific cloud instance is. Note that we subtract
+ // the sprite size because the width/height is used to define the limits of
+ // the center of the sprites, not their edges.
+ float width = min_width + sg_random() * (max_width - min_width) - max_sprite_width;
+ float height = min_height + sg_random() * (max_height - min_height) - max_sprite_height;
// Determine the cull distance. This is used to remove sprites that are too close together.
// The value is squared as we use vector calculations.
for (int i = 0; i < num_sprites; i++)
{
- // Determine the position of the sprite.
- SGVec3f *pos = new SGVec3f(Rnd(width),
- Rnd(width),
- Rnd(height));
+ // Determine the position of the sprite. Rather than being completely random,
+ // sprites are placed on a squashed sphere.
+ double theta = sg_random() * SGD_2PI;
+ float x = width * cos(theta) * 0.5f;
+ float y = width * sin(theta) * 0.5f;
+ float z = height * cos(sg_random() * SGD_2PI) * 0.5f;
+
+ SGVec3f *pos = new SGVec3f(x, y, z);
// Determine the height and width as scaling factors on the minimum size (used to create the quad)
float sprite_width = 1.0f + sg_random() * (max_sprite_width - min_sprite_width) / min_sprite_width;
// The shade varies from bottom_shade to 1.0 non-linearly
float shade;
- if (pos->z() > 0.0f) {
+ if (z > 0.0f) {
shade = 1.0f;
} else {
- shade = ((2 * pos->z() + height) / height) * (1 - bottom_shade) + bottom_shade;
+ shade = ((2 * z + height) / height) * (1 - bottom_shade) + bottom_shade;
}
// Determine the sprite texture indexes;
geode->addDrawable(sg);
geode->setName("3D cloud");
geode->setStateSet(stateSet.get());
- result->addChild(geode, 0, 20000);
- return result;
+ return geode;
}
~SGNewCloud();
// Generate a Cloud
- osg::ref_ptr<osg::LOD> genCloud ();
+ osg::ref_ptr<osg::Geode> genCloud ();
private:
int num_textures_x;
int num_textures_y;
const string texture;
-
+ osg::Geometry* quad;
osg::ref_ptr<osg::StateSet> stateSet;
+ osg::Geometry* createOrthQuad(float w, float h, int varieties_x, int varieties_y);
+
public:
};
return cloud_layers.size();
}
-void SGSky::set_3dClouds(bool enable)
-{
- for ( unsigned i = 0; i < cloud_layers.size(); ++i ) {
- cloud_layers[i]->set_enable3dClouds(enable);
- }
-
- clouds_3d_enabled = enable;
-}
-
-bool SGSky::get_3dClouds() const {
- return clouds_3d_enabled;
-}
-
double SGSky::get_3dCloudDensity() const {
return SGCloudField::get_density();
}
}
if ( cloud_layers[i]->getCoverage() == SGCloudLayer::SG_CLOUD_CLEAR ||
- get_3dClouds()) {
+ cloud_layers[i]->get_layer3D()->defined3D) {
// do nothing, clear layers aren't drawn, don't affect
// visibility andn dont' need to be faded in or out.
} else if ( (cloud_layers[i]->getCoverage() ==
effective_visibility = visibility = (v <= 25.0) ? 25.0 : v;
}
- /** Get whether 3D clouds are enabled */
- virtual bool get_3dClouds() const;
-
- /** Set whether 3D clouds are enabled
- * @param enable Whether to enable 3D clouds
- */
- virtual void set_3dClouds(bool enable);
-
/** Get 3D cloud density */
virtual double get_3dCloudDensity() const;