if (!_cloudsprites.size()) return;
osg::State& state = *renderInfo.getState();
- unsigned int contextID = state.getContextID();
- SortData& sortData = _sortData[contextID];
+
int frameNumber = state.getFrameStamp()->getFrameNumber();
-
- if (!sortData.spriteIdx)
- sortData.spriteIdx = new SortData::SortItemList;
- if (sortData.spriteIdx->size() < _cloudsprites.size()) {
- for (unsigned i = sortData.spriteIdx->size(); i < (unsigned)_cloudsprites.size(); ++i)
- sortData.spriteIdx->push_back(SortData::SortItem(i, 0.0f));
- sortData.frameSorted = frameNumber - (sortData.skip_limit + 1);
- }
+ unsigned int contextID = state.getContextID();
+ SortData& sortData = _sortData[contextID];
+ Geometry* g = _geometry->asGeometry();
+
// If the cloud is already sorted, then it is likely to still be sorted.
// Therefore we can avoid re-sorting it for a period. If it is still
// sorted after that period, then we can wait for a longer period before
// checking again. In this way, only clouds that are changing regularly
- // are sorted.
- if (frameNumber - sortData.skip_limit >= sortData.frameSorted) {
+ // are sorted.
+ osg::Vec3Array* v = dynamic_cast<osg::Vec3Array*>(g->getVertexArray());
+ if ((v->size() > 4) &&
+ (frameNumber - sortData.skip_limit >= sortData.frameSorted)) {
Matrix mvp = state.getModelViewMatrix() * state.getProjectionMatrix();
- for (SortData::SortItemList::iterator itr = sortData.spriteIdx->begin(),
- end = sortData.spriteIdx->end();
- itr != end;
- ++itr) {
- Vec4f projPos
- = Vec4f(toOsg(_cloudsprites[itr->idx].position), 1.0f) * mvp;
- itr->depth = projPos.z() / projPos.w();
+
+ osg::Vec4Array* c = dynamic_cast<osg::Vec4Array*>(g->getColorArray());
+ osg::Vec2Array* t = dynamic_cast<osg::Vec2Array*>(g->getTexCoordArray(0));
+ Vec3f av[4];
+ Vec4f ac[4];
+ Vec2f at[4];
+
+ // Perform a single pass bubble sort of the array,
+ // keeping track of whether we've had to make any changes
+ bool sorted = true;
+ for (unsigned int i = 4; i < v->size(); i = i + 4) {
+ // The position of the sprite is stored in the colour
+ // array, with the exception of the w() coordinate
+ // which is the z-scaling parameter.
+ Vec4f a = (*c)[i-4];
+ Vec4f aPos = Vec4f(a.x(), a.y(), a.z(), 1.0f) * mvp;
+ Vec4f b = (*c)[i];
+ Vec4f bPos = Vec4f(b.x(), b.y(), b.z(), 1.0f) * mvp;
+
+ if ((aPos.z()/aPos.w()) < (bPos.z()/bPos.w() - 0.0001)) {
+ // a is non-trivially closer than b, so should be rendered
+ // later. Swap them around
+ for (int j = 0; j < 4; j++) {
+ av[j] = (*v)[i+j-4];
+ ac[j] = (*c)[i+j-4];
+ at[j] = (*t)[i+j-4];
+
+ (*v)[i+j -4] = (*v)[i+j];
+ (*c)[i+j -4] = (*c)[i+j];
+ (*t)[i+j -4] = (*t)[i+j];
+
+ (*v)[i+j] = av[j];
+ (*c)[i+j] = ac[j];
+ (*t)[i+j] = at[j];
+ }
+
+ // Indicate that the arrays were not sorted
+ // so we should check them next iteration
+ sorted = false;
+ }
}
- // Already sorted?
- if (std::adjacent_find(sortData.spriteIdx->rbegin(),
- sortData.spriteIdx->rend(), SpriteComp())
- == sortData.spriteIdx->rend()) {
+
+ if (sorted) {
// This cloud is sorted, so no need to re-sort.
+
sortData.skip_limit = sortData.skip_limit * 2;
if (sortData.skip_limit > 30) {
// Jitter the skip frames to avoid synchronized sorts
// which will cause periodic frame-rate drops
sortData.skip_limit += sg_random() * 10;
}
- if (sortData.skip_limit > 128) {
- // Maximum of every 128 frames (2 - 4 seconds)
- sortData.skip_limit = 128 + sg_random() * 10;
+ if (sortData.skip_limit > 500) {
+ // Maximum of every 500 frames (10-20 seconds)
+ sortData.skip_limit = 500 + sg_random() * 10;
}
-
} else {
- std::sort(sortData.spriteIdx->begin(), sortData.spriteIdx->end(),
- SpriteComp());
sortData.skip_limit = 1;
}
+
sortData.frameSorted = frameNumber;
}
-
+
const Extensions* extensions = getExtensions(state.getContextID(),true);
-
- for(SortData::SortItemList::const_iterator itr = sortData.spriteIdx->begin(),
- end = sortData.spriteIdx->end();
- itr != end;
- ++itr) {
- const CloudSprite& t = _cloudsprites[itr->idx];
- GLfloat ua1[3] = { (GLfloat) t.texture_index_x/varieties_x,
- (GLfloat) t.texture_index_y/varieties_y,
- (GLfloat) t.width };
- GLfloat ua2[3] = { (GLfloat) t.height,
- (GLfloat) shade_factor,
- (GLfloat) cloud_height };
- GLfloat ua3[3] = { (GLfloat) bottom_factor,
- (GLfloat) middle_factor,
- (GLfloat) top_factor };
-
- extensions->glVertexAttrib3fv(USR_ATTR_1, ua1 );
- extensions->glVertexAttrib3fv(USR_ATTR_2, ua2 );
- extensions->glVertexAttrib3fv(USR_ATTR_3, ua3 );
- glColor4f(t.position.x(), t.position.y(), t.position.z(), zscale);
- _geometry->draw(renderInfo);
- }
+ GLfloat ua1[3] = { (GLfloat) 1.0f,
+ (GLfloat) shade_factor,
+ (GLfloat) cloud_height };
+ GLfloat ua2[3] = { (GLfloat) bottom_factor,
+ (GLfloat) middle_factor,
+ (GLfloat) top_factor };
+
+ extensions->glVertexAttrib3fv(USR_ATTR_1, ua1 );
+ extensions->glVertexAttrib3fv(USR_ATTR_2, ua2 );
+ _geometry->draw(renderInfo);
}
void CloudShaderGeometry::addSprite(const SGVec3f& p, int tx, int ty,
_cloudsprites.push_back(CloudSprite(p, tx, ty, w, h));
}
+void CloudShaderGeometry::generateGeometry()
+{
+ // Generate a set of geometries as a QuadStrip based on the list of sprites
+ int numsprites = _cloudsprites.size();
+
+ // Create front and back polygons so we don't need to screw around
+ // with two-sided lighting in the shader.
+ osg::Vec3Array& v = *(new osg::Vec3Array(4 * numsprites));
+ osg::Vec4Array& c = *(new osg::Vec4Array(4 * numsprites));
+ osg::Vec2Array& t = *(new osg::Vec2Array(4 * numsprites));
+
+ int idx = 0;
+
+ for (CloudShaderGeometry::CloudSpriteList::iterator iter = _cloudsprites.begin();
+ iter != _cloudsprites.end();
+ ++iter)
+ {
+
+ float cw = 0.5f * iter->width;
+ float ch = 0.5f * iter->height;
+
+ // Create the vertices
+ v[4*idx ].set(0.0f, -cw, -ch);
+ v[4*idx+1].set(0.0f, cw, -ch);
+ v[4*idx+2].set(0.0f, cw, ch);
+ v[4*idx+3].set(0.0f, -cw, ch);
+
+ // Set the texture coords for each vertex
+ // from the texture index, and the number
+ // of textures in the image
+ int x = iter->texture_index_x;
+ int y = iter->texture_index_y;
+
+ t[4*idx ].set( (float) x / varieties_x, (float) y / varieties_y);
+ t[4*idx+1].set( (float) (x + 1) / varieties_x, (float) y / varieties_y);
+ t[4*idx+2].set( (float) (x + 1) / varieties_x, (float) (y + 1) / varieties_y);
+ t[4*idx+3].set( (float) x / varieties_x, (float) (y + 1) / varieties_y);
+
+ // The color isn't actually use in lighting, but instead to indicate the center of rotation
+ c[4*idx ].set(iter->position.x(), iter->position.y(), iter->position.z(), zscale);
+ c[4*idx+1].set(iter->position.x(), iter->position.y(), iter->position.z(), zscale);
+ c[4*idx+2].set(iter->position.x(), iter->position.y(), iter->position.z(), zscale);
+ c[4*idx+3].set(iter->position.x(), iter->position.y(), iter->position.z(), zscale);
+
+ idx++;
+ }
+
+ //Quads now created, add it to the geometry.
+ osg::Geometry* geom = new osg::Geometry;
+ geom->setVertexArray(&v);
+ geom->setTexCoordArray(0, &t);
+
+ // The normal isn't actually use in lighting, so we simply bind overall.
+ osg::Vec3Array& n = *(new osg::Vec3Array(4));
+ n[0].set(1.0f, -1.0f, -1.0f);
+ n[1].set(1.0f, 1.0f, -1.0f);
+ n[2].set(1.0f, 1.0f, 1.0f);
+ n[3].set(1.0f, -1.0f, 1.0f);
+
+ geom->setNormalArray(&n);
+ geom->setNormalBinding(Geometry::BIND_OVERALL);
+ geom->setColorArray(&c);
+ geom->setColorBinding(Geometry::BIND_PER_VERTEX);
+ geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,numsprites*4));
+ _geometry = geom;
+}
+
bool CloudShaderGeometry_readLocalData(Object& obj, Input& fr)
{
bool iteratorAdvanced = false;
++fr;
}
}
+ geom.generateGeometry();
}
return iteratorAdvanced;
}
return _bbox;
}
- void setGeometry(osg::Drawable* geometry)
- {
- _geometry = geometry;
- }
-
void addSprite(const SGVec3f& p, int tx, int ty, float w, float h, float cull);
+ void generateGeometry();
+ void rebuildGeometry();
osg::ref_ptr<osg::Drawable> _geometry;
#include <osg/Texture2D>
#include <osg/PositionAttitudeTransform>
#include <osg/Vec4f>
+#include <osgSim/Impostor>
#include <simgear/compiler.h>
double SGCloudField::timer_dt = 0.0;
float SGCloudField::view_distance = 20000.0f;
bool SGCloudField::wrap = true;
-float SGCloudField::RADIUS_LEVEL_1 = 5000.0f;
-float SGCloudField::RADIUS_LEVEL_2 = 2000.0f;
float SGCloudField::MAX_CLOUD_DEPTH = 2000.0f;
+bool SGCloudField::use_impostors = true;
+float SGCloudField::lod1_range = 10000.0f;
+float SGCloudField::lod2_range = 5000.0f;
+float SGCloudField::impostor_distance = 10000.0f;
+
+int impostorcount = 0;
+int lodcount = 0;
+int cloudcount = 0;
SGVec3f SGCloudField::view_vec, SGCloudField::view_X, SGCloudField::view_Y;
field_transform->addChild(altitude_transform.get());
placed_root = new osg::Group();
altitude_transform->addChild(placed_root);
+ impostorcount = 0;
+ lodcount = 0;
+ cloudcount = 0;
}
SGCloudField::~SGCloudField() {
cloud_hash.clear();
}
-void SGCloudField::applyVisRange(void)
+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, view_distance + RADIUS_LEVEL_1 + RADIUS_LEVEL_2 + MAX_CLOUD_DEPTH);
+ lodnode1->setRange(j, 0.0f, lod1_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);
{
osg::ref_ptr<osg::Group> lodnode = transform->getParent(0);
lodnode->removeChild(transform);
+ cloudcount--;
- // Clean up the LOD nodes if required
if (lodnode->getNumChildren() == 0) {
osg::ref_ptr<osg::Group> lodnode1 = lodnode->getParent(0);
-
+ osg::ref_ptr<osgSim::Impostor> impostornode = (osgSim::Impostor*) lodnode1->getParent(0);
+
lodnode1->removeChild(lodnode);
-
- if (lodnode1->getNumChildren() == 0) {
- placed_root->removeChild(lodnode1);
+ lodcount--;
+
+ if (lodnode1->getNumChildren() == 0) {
+ impostornode->removeChild(lodnode1);
+ placed_root->removeChild(impostornode);
+ impostorcount--;
}
}
}
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() < RADIUS_LEVEL_1*RADIUS_LEVEL_1) {
- // New cloud is within RADIUS_LEVEL_1 of the center of the LOD node.
- found = true;
- }
+ 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) {
- lodnode1 = new osg::LOD();
- placed_root->addChild(lodnode1.get());
+ 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++;
}
// 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++) {
+
+ for (unsigned int j = 0; (!found) && (j < lodnode1->getNumChildren()); j++) {
lodnode = (osg::LOD*) lodnode1->getChild(j);
- if ((lodnode->getCenter() - pos).length2() < RADIUS_LEVEL_2*RADIUS_LEVEL_2) {
+ if ((lodnode->getCenter() - pos).length2() < lod2_range*lod2_range) {
// We've found the right leaf LOD node
found = true;
}
}
if (!found) {
- // No suitable leave node was found, so we need to add one.
+ // No suitable leaf node was found, so we need to add one.
lodnode = new osg::LOD();
- lodnode1->addChild(lodnode, 0.0f, view_distance + RADIUS_LEVEL_1 + RADIUS_LEVEL_2 + MAX_CLOUD_DEPTH);
- }
-
+ lodnode1->addChild(lodnode, 0.0f, lod1_range + view_distance + MAX_CLOUD_DEPTH);
+ lodcount++;
+ }
+
transform->setPosition(pos);
- lodnode->addChild(transform.get(), 0.0f, view_distance + MAX_CLOUD_DEPTH);
+ lodnode->addChild(transform.get(), 0.0f, view_distance);
+ cloudcount++;
+ SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "Impostors: " << impostorcount <<
+ " LoD: " << lodcount <<
+ " Clouds: " << cloudcount);
lodnode->dirtyBound();
lodnode1->dirtyBound();
float Rnd(float);
- // Radius of the LoD nodes for the dynamic quadtrees.
- static float RADIUS_LEVEL_1;
- static float RADIUS_LEVEL_2;
-
// Theoretical maximum cloud depth, used for fudging the LoD
// ranges to ensure that clouds become visible at maximum range
static float MAX_CLOUD_DEPTH;
- // this is a relative position only, with that we can move all clouds at once
- SGVec3f relative_position;
+ // this is a relative position only, with that we can move all clouds at once
+ SGVec3f relative_position;
osg::ref_ptr<osg::Group> field_root;
osg::ref_ptr<osg::Group> placed_root;
static SGVec3f view_vec, view_X, view_Y;
static float view_distance;
+ static float impostor_distance;
+ static float lod1_range;
+ static float lod2_range;
+ static bool use_impostors;
static double timer_dt;
static float fieldSize;
static bool wrap;
static bool getWrap(void) { return wrap; }
static void setWrap(bool w) { wrap = w; }
+ static float getImpostorDistance(void) { return impostor_distance; }
+ static void setImpostorDistance(float d) { impostor_distance = d; }
+ static float getLoD1Range(void) { return lod1_range; }
+ static void setLoD1Range(int d) { lod1_range = d; }
+ static float getLoD2Range(void) { return lod2_range; }
+ static void setLoD2Range(int d) { lod2_range = d; }
+ static void setUseImpostors(bool b) { use_impostors = b; }
+ static bool getUseImpostors(void) { return use_impostors; }
+
static float getVisRange(void) { return view_distance; }
static void setVisRange(float d) { view_distance = d; }
- void applyVisRange(void);
+ void applyVisAndLoDRange(void);
static osg::Fog* getFog()
{
} else {
effect = iter->second.get();
}
- quad = createOrthQuad(min_sprite_width, min_sprite_height,
- num_textures_x, num_textures_y);
}
SGNewCloud::~SGNewCloud() {
}
-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.
- osg::Vec3Array& v = *(new osg::Vec3Array(4));
- osg::Vec3Array& n = *(new osg::Vec3Array(4));
- osg::Vec2Array& t = *(new osg::Vec2Array(4));
-
- float cw = w*0.5f;
- float ch = h*0.5f;
-
- v[0].set(0.0f, -cw, -ch);
- v[1].set(0.0f, cw, -ch);
- v[2].set(0.0f, cw, ch);
- v[3].set(0.0f, -cw, ch);
-
- // The texture coordinate range is not the
- // entire coordinate space - as the texture
- // has a number of different clouds on it.
- float tx = 1.0f/varieties_x;
- float ty = 1.0f/varieties_y;
-
- t[0].set(0.0f, 0.0f);
- t[1].set( tx, 0.0f);
- t[2].set( tx, ty);
- t[3].set(0.0f, ty);
-
- // The normal isn't actually use in lighting.
- n[0].set(1.0f, -1.0f, -1.0f);
- n[1].set(1.0f, 1.0f, -1.0f);
- n[2].set(1.0f, 1.0f, 1.0f);
- n[3].set(1.0f, -1.0f, 1.0f);
-
- osg::Geometry *geom = new osg::Geometry;
-
- geom->setVertexArray(&v);
- geom->setTexCoordArray(0, &t);
- geom->setNormalArray(&n);
- geom->setNormalBinding(Geometry::BIND_PER_VERTEX);
- // No color for now; that's used to pass the position.
- geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,4));
-
- return geom;
-}
-
#if 0
// return a random number between -n/2 and n/2, tending to 0
static float Rnd(float n) {
z = height * cos(elev) * 0.5f + height * 0.5f;
}
- // 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;
- float sprite_height = 1.0f + sg_random() * (max_sprite_height - min_sprite_height) / min_sprite_height;
+ // Determine the height and width
+ float sprite_width = min_sprite_width + sg_random() * (max_sprite_width - min_sprite_width);
+ float sprite_height = min_sprite_height + sg_random() * (max_sprite_height - min_sprite_height);
// Sprites are never taller than square.
- if (sprite_height * min_sprite_height > sprite_width * min_sprite_width)
+ if (sprite_height > sprite_width )
{
- sprite_height = sprite_width * min_sprite_width / min_sprite_height;
+ sprite_height = sprite_width;
}
if (i == 0) {
// The center sprite is always maximum size to fill up any holes.
- sprite_width = 1.0f + (max_sprite_width - min_sprite_width) / min_sprite_width;
- sprite_height = 1.0f + (max_sprite_height - min_sprite_height) / min_sprite_height;
+ sprite_width = max_sprite_width;
+ sprite_height = max_sprite_height;
}
// If the center of the sprite is less than half the sprite heightthe sprite will extend
// below the bottom of the cloud and must be shifted upwards. This is particularly important
// for cumulus clouds which have a very well defined base.
- if (z < 0.5f * sprite_height * min_sprite_height)
+ if (z < 0.5f * sprite_height)
{
- z = 0.5f * sprite_height * min_sprite_height;
+ z = 0.5f * sprite_height;
}
// Determine the sprite texture indexes.
cull_distance_squared);
}
- sg->setGeometry(quad);
+ sg->generateGeometry();
geode->addDrawable(sg);
geode->setName("3D cloud");
geode->setEffect(effect.get());
{
SGCloudField::setVisRange(vis);
for ( int i = 0; i < (int)cloud_layers.size(); ++i ) {
- cloud_layers[i]->get_layer3D()->applyVisRange();
+ cloud_layers[i]->get_layer3D()->applyVisAndLoDRange();
+ }
+}
+
+float SGSky::get_3dCloudImpostorDistance() const {
+ return SGCloudField::getImpostorDistance();
+}
+
+void SGSky::set_3dCloudImpostorDistance(float vis)
+{
+ SGCloudField::setImpostorDistance(vis);
+ for ( int i = 0; i < (int)cloud_layers.size(); ++i ) {
+ cloud_layers[i]->get_layer3D()->applyVisAndLoDRange();
+ }
+}
+
+float SGSky::get_3dCloudLoD1Range() const {
+ return SGCloudField::getLoD1Range();
+}
+
+void SGSky::set_3dCloudLoD1Range(float vis)
+{
+ SGCloudField::setLoD1Range(vis);
+ for ( int i = 0; i < (int)cloud_layers.size(); ++i ) {
+ cloud_layers[i]->get_layer3D()->applyVisAndLoDRange();
+ }
+}
+
+float SGSky::get_3dCloudLoD2Range() const {
+ return SGCloudField::getLoD2Range();
+}
+
+void SGSky::set_3dCloudLoD2Range(float vis)
+{
+ SGCloudField::setLoD2Range(vis);
+ for ( int i = 0; i < (int)cloud_layers.size(); ++i ) {
+ cloud_layers[i]->get_layer3D()->applyVisAndLoDRange();
}
}
SGCloudField::setWrap(wrap);
}
+bool SGSky::get_3dCloudUseImpostors() const {
+ return SGCloudField::getUseImpostors();
+}
+
+void SGSky::set_3dCloudUseImpostors(bool imp)
+{
+ SGCloudField::setUseImpostors(imp);
+}
+
void SGSky::texture_path( const string& path ) {
tex_path = SGPath( path );
*/
virtual void set_3dCloudVisRange(float vis);
+ /** Get 3D cloud impostor distance*/
+ virtual float get_3dCloudImpostorDistance() const;
+
+ /** Set 3D cloud impostor distance
+ * @param density 3D cloud impostor distance
+ */
+ virtual void set_3dCloudImpostorDistance(float vis);
+
+ /** Get 3D cloud LoD1 Range*/
+ virtual float get_3dCloudLoD1Range() const;
+
+ /** Set 3D cloud LoD1 Range
+ * @param vis LoD1 Range
+ */
+ virtual void set_3dCloudLoD1Range(float vis);
+
+ /** Get 3D cloud LoD2 Range*/
+ virtual float get_3dCloudLoD2Range() const;
+
+ /** Set 3D cloud LoD2 Range
+ * @param vis LoD2 Range
+ */
+ virtual void set_3dCloudLoD2Range(float vis);
+
+ /** Get 3D cloud impostor usage */
+ virtual bool get_3dCloudUseImpostors() const;
+
+ /** Set 3D cloud impostor usage
+ * @param wrap whether use impostors for 3D clouds
+ */
+ virtual void set_3dCloudUseImpostors(bool imp);
+
/** Get 3D cloud wrapping */
virtual bool get_3dCloudWrap() const;