Torque3D Documentation / _generateds / groundCover.cpp

groundCover.cpp

Engine/source/T3D/fx/groundCover.cpp

More...

Classes:

class

This defines one grid cell.

Public Functions

ConsoleDocClass(GroundCover , "@brief Covers the ground in <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> field of objects (IE: Grass, Flowers, etc)." "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Foliage\n</a>" )

This is used for rendering ground cover billboards.

Detailed Description

Public Functions

ConsoleDocClass(GroundCover , "@brief Covers the ground in <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> field of objects (IE: Grass, Flowers, etc)." "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Foliage\n</a>" )

GFXImplementVertexFormat(GCVertex )

This is used for rendering ground cover billboards.

IMPLEMENT_CO_NETOBJECT_V1(GroundCover )

   1
   2//-----------------------------------------------------------------------------
   3// Copyright (c) 2012 GarageGames, LLC
   4//
   5// Permission is hereby granted, free of charge, to any person obtaining a copy
   6// of this software and associated documentation files (the "Software"), to
   7// deal in the Software without restriction, including without limitation the
   8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
   9// sell copies of the Software, and to permit persons to whom the Software is
  10// furnished to do so, subject to the following conditions:
  11//
  12// The above copyright notice and this permission notice shall be included in
  13// all copies or substantial portions of the Software.
  14//
  15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  21// IN THE SOFTWARE.
  22//-----------------------------------------------------------------------------
  23
  24#include "platform/platform.h"
  25#include "T3D/fx/groundCover.h"
  26
  27#include "core/resourceManager.h"
  28#include "core/stream/bitStream.h"
  29#include "console/consoleTypes.h"
  30#include "scene/sceneRenderState.h"
  31#include "terrain/terrData.h"
  32#include "renderInstance/renderPassManager.h"
  33#include "gfx/gfxDrawUtil.h"
  34#include "gfx/primBuilder.h"
  35#include "T3D/gameBase/gameConnection.h"
  36#include "gfx/gfxVertexBuffer.h"
  37#include "gfx/gfxStructs.h"
  38#include "ts/tsShapeInstance.h"
  39#include "lighting/lightManager.h"
  40#include "lighting/lightInfo.h"
  41#include "materials/shaderData.h"
  42#include "gfx/gfxTransformSaver.h"
  43#include "shaderGen/shaderGenVars.h"
  44#include "materials/matTextureTarget.h"
  45#include "gfx/util/screenspace.h"
  46#include "materials/materialDefinition.h"
  47#include "materials/materialManager.h"
  48#include "materials/sceneData.h"
  49#include "materials/materialFeatureTypes.h"
  50#include "materials/matInstance.h"
  51#include "renderInstance/renderDeferredMgr.h"
  52#include "console/engineAPI.h"
  53
  54/// This is used for rendering ground cover billboards.
  55GFXImplementVertexFormat( GCVertex )
  56{
  57   addElement( "POSITION", GFXDeclType_Float3 );
  58   addElement( "NORMAL", GFXDeclType_Float3 );
  59   addElement( "COLOR", GFXDeclType_Color );
  60   addElement( "TEXCOORD", GFXDeclType_Float4, 0 );
  61};
  62
  63GroundCoverShaderConstHandles::GroundCoverShaderConstHandles()
  64 : mGroundCover( NULL ),
  65   mTypeRectsSC( NULL ),
  66   mFadeSC( NULL ),
  67   mWindDirSC( NULL ),
  68   mGustInfoSC( NULL ),
  69   mTurbInfoSC( NULL ),
  70   mCamRightSC( NULL ),
  71   mCamUpSC( NULL )
  72{
  73}
  74
  75void GroundCoverShaderConstHandles::init( GFXShader *shader )
  76{   
  77   mTypeRectsSC = shader->getShaderConstHandle( "$gc_typeRects" );      
  78   mFadeSC = shader->getShaderConstHandle( "$gc_fadeParams" );
  79   mWindDirSC = shader->getShaderConstHandle( "$gc_windDir" );
  80   mGustInfoSC = shader->getShaderConstHandle( "$gc_gustInfo" );
  81   mTurbInfoSC = shader->getShaderConstHandle( "$gc_turbInfo" );
  82   mCamRightSC = shader->getShaderConstHandle( "$gc_camRight" );
  83   mCamUpSC = shader->getShaderConstHandle( "$gc_camUp" );
  84}
  85
  86void GroundCoverShaderConstHandles::setConsts( SceneRenderState *state, const SceneData &sgData, GFXShaderConstBuffer *buffer )
  87{         
  88   AlignedArray<Point4F> rectData( MAX_COVERTYPES, sizeof( Point4F ), (U8*)(mGroundCover->mBillboardRects), false );          
  89   buffer->setSafe( mTypeRectsSC, rectData );
  90   
  91   const GroundCoverShaderConstData &data = mGroundCover->getShaderConstData();
  92      
  93   buffer->setSafe( mFadeSC, data.fadeInfo );   
  94   buffer->setSafe( mWindDirSC, mGroundCover->mWindDirection );
  95   buffer->setSafe( mGustInfoSC, data.gustInfo );
  96   buffer->setSafe( mTurbInfoSC, data.turbInfo );    
  97   buffer->setSafe( mCamRightSC, data.camRight );
  98   buffer->setSafe( mCamUpSC, data.camUp );
  99}
 100
 101/// This defines one grid cell.
 102class GroundCoverCell
 103{
 104protected:
 105
 106   friend class GroundCover;
 107
 108   struct Placement
 109   {
 110      Point3F     point;
 111      Point3F     normal;
 112      Point3F     size;
 113      F32         rotation;
 114      U32         type;
 115      F32         windAmplitude;
 116      Box3F       worldBox;
 117      LinearColorF      lmColor;
 118   };
 119
 120   /// This is the x,y index for this cell.
 121   Point2I mIndex;
 122
 123   /// The worldspace bounding box this cell.
 124   Box3F mBounds;
 125
 126   /// The worldspace bounding box of the renderable
 127   /// content within this cell.
 128   Box3F mRenderBounds;
 129
 130   /// The instances of billboard cover elements in this cell.
 131   Vector<Placement> mBillboards;
 132
 133   /// The instances of shape cover elements in this cell.
 134   Vector<Placement> mShapes;
 135
 136   typedef GFXVertexBufferHandle<GCVertex> VBHandle;
 137   typedef Vector< VBHandle> VBHandleVector;
 138
 139   /// The vertex buffers that hold all the 
 140   /// prepared billboards for this cell.
 141   VBHandleVector mVBs;
 142
 143   /// Used to mark the cell dirty and in need
 144   /// of a rebuild.
 145   bool mDirty;
 146
 147   /// Repacks the billboards into the vertex buffer.
 148   void _rebuildVB();
 149
 150public:
 151
 152   GroundCoverCell() : mDirty(false) {}
 153
 154   ~GroundCoverCell() 
 155   {
 156      mVBs.clear();
 157   }
 158
 159   const Point2I& shiftIndex( const Point2I& shift ) { return mIndex += shift; }
 160   
 161   /// The worldspace bounding box this cell.
 162   const Box3F& getBounds() const { return mBounds; }
 163
 164   /// The worldspace bounding box of the renderable
 165   /// content within this cell.
 166   const Box3F& getRenderBounds() const { return mRenderBounds; }
 167
 168   Point3F getCenter() const { return mBounds.getCenter(); }
 169
 170   VectorF getSize() const { return VectorF( mBounds.len_x() / 2.0f,
 171                                             mBounds.len_y() / 2.0f,
 172                                             mBounds.len_z() / 2.0f ); }
 173      
 174   void renderBillboards( SceneRenderState *state, BaseMatInstance *mat, GFXPrimitiveBufferHandle *pb );
 175
 176   U32 renderShapes(    const TSRenderState &rdata, 
 177                        Frustum *culler, 
 178                        TSShapeInstance** shapes );
 179};
 180
 181void GroundCoverCell::_rebuildVB()
 182{
 183   if ( mBillboards.empty() )
 184      return;
 185
 186   PROFILE_SCOPE(GroundCover_RebuildVB);
 187
 188   // The maximum verts we can put in one vertex buffer batch.
 189   const U32 MAX_BILLBOARDS = 0xFFFF / 4;
 190
 191   // How many batches will we need in total?
 192   const U32 batches = mCeil( (F32)mBillboards.size() / (F32)MAX_BILLBOARDS );
 193
 194   // So... how many billboards do we need in
 195   // each batch? We're trying to evenly divide
 196   // the amount across all the VBs.
 197   const U32 batchBB = mBillboards.size() / batches;
 198
 199   // Init the vertex buffer list to the right size.  Any
 200   // VBs already in there will remain unless we're truncating
 201   // the list... those are freed.
 202   mVBs.setSize( batches ); 
 203
 204   // Get the iter to the first billboard.
 205   Vector<Placement>::const_iterator iter = mBillboards.begin();
 206
 207   // Prepare each batch.
 208   U32 bb, remaining = mBillboards.size();
 209   for ( U32 b = 0; b < batches; b++ )
 210   {
 211      // Grab a reference to the vb.
 212      VBHandle &vb = mVBs[b];
 213
 214      // How many billboards in this batch?
 215      bb = getMin( batchBB, remaining );
 216      remaining -= bb;
 217
 218      // Ok... now how many verts is that?
 219      const U32 verts = bb * 4;
 220
 221      // Create the VB hasn't been created or if its
 222      // too small then resize it.
 223      if ( vb.isNull() || vb->mNumVerts < verts )
 224      {
 225         PROFILE_START(GroundCover_CreateVB);
 226         vb.set( GFX, verts, GFXBufferTypeStatic );
 227         PROFILE_END();
 228      }
 229
 230      // Fill this puppy!
 231      GCVertex* vertPtr = vb.lock( 0, verts );
 232      
 233      GFXVertexColor color;
 234
 235      Vector<Placement>::const_iterator last = iter + bb;
 236      for ( ; iter != last; iter++ )
 237      {
 238         const Point3F &position = (*iter).point;
 239         const Point3F &normal = (*iter).normal;
 240         const S32 &type = (*iter).type;
 241         const Point3F &size = (*iter).size;
 242         const F32 &windAmplitude = (*iter).windAmplitude;
 243         color = LinearColorF((*iter).lmColor).toColorI();
 244         U8 *col = (U8 *)const_cast<U32 *>( (const U32 *)color );
 245
 246         vertPtr->point = position;
 247         vertPtr->normal = normal;
 248         vertPtr->params.x = size.x;
 249         vertPtr->params.y = size.y;
 250         vertPtr->params.z = type;
 251         vertPtr->params.w = 0;
 252         col[3] = 0;
 253         vertPtr->ambient = color;
 254         ++vertPtr;
 255
 256         vertPtr->point = position;
 257         vertPtr->normal = normal;
 258         vertPtr->params.x = size.x;
 259         vertPtr->params.y = size.y;
 260         vertPtr->params.z = type;
 261         vertPtr->params.w = 0;
 262         col[3] = 1;
 263         vertPtr->ambient = color;
 264         ++vertPtr;
 265
 266         vertPtr->point = position;
 267         vertPtr->normal = normal;
 268         vertPtr->params.x = size.x;
 269         vertPtr->params.y = size.y;
 270         vertPtr->params.z = type;
 271         vertPtr->params.w = windAmplitude;
 272         col[3] = 2;
 273         vertPtr->ambient = color;
 274         ++vertPtr;
 275
 276         vertPtr->point = position;
 277         vertPtr->normal = normal;
 278         vertPtr->params.x = size.x;
 279         vertPtr->params.y = size.y;
 280         vertPtr->params.z = type;
 281         vertPtr->params.w = windAmplitude;
 282         col[3] = 3;
 283         vertPtr->ambient = color;
 284         ++vertPtr;
 285      }
 286
 287      vb.unlock();
 288   }
 289}
 290
 291U32 GroundCoverCell::renderShapes(  const TSRenderState &rdata,
 292                                    Frustum *culler, 
 293                                    TSShapeInstance** shapes )
 294{
 295   MatrixF worldMat;
 296   TSShapeInstance* shape;
 297   Point3F camVector;
 298   F32 dist;
 299   F32 invScale;
 300
 301   const SceneRenderState *state = rdata.getSceneState();
 302
 303   U32 totalRendered = 0;
 304
 305   Vector<Placement>::const_iterator iter = mShapes.begin();
 306   for ( ; iter != mShapes.end(); iter++ )
 307   {
 308      // Grab a reference here once.
 309      const Placement& inst = (*iter);
 310
 311      // If we were pass a culler then us it to test the shape world box.
 312      if ( culler && culler->isCulled( inst.worldBox ) )
 313         continue;
 314
 315      shape = shapes[ inst.type ];
 316
 317      camVector = inst.point - state->getDiffuseCameraPosition();
 318      dist = getMax( camVector.len(), 0.01f );
 319
 320      worldMat.set( EulerF(inst.normal.x, inst.normal.y, inst.rotation), inst.point );
 321
 322      // TSShapeInstance::render() uses the 
 323      // world matrix for the RenderInst.
 324      worldMat.scale( inst.size );
 325      GFX->setWorldMatrix( worldMat );
 326
 327      // Obey the normal screen space lod metrics.  The shapes should
 328      // be tuned to lod out quickly for ground cover.
 329      //
 330      // Note: The profile doesn't indicate that lod selection is
 331      // very expensive... in fact its less than 1/10th of the cost 
 332      // of the render() call below.
 333      PROFILE_START(GroundCover_RenderShapes_SelectDetail);
 334
 335         invScale = (1.0f/getMax(getMax(inst.size.x,inst.size.y),inst.size.z));
 336         shape->setDetailFromDistance( state, dist * invScale );
 337
 338      PROFILE_END(); // GroundCover_RenderShapes_SelectDetail
 339  
 340      // Note: This is the most expensive call of this loop.  We 
 341      // need to rework the render call completely to optimize it.
 342      PROFILE_START(GroundCover_RenderShapes_Render);
 343
 344         shape->render( rdata );
 345
 346      PROFILE_END(); // GroundCover_RenderShapes_Render
 347
 348      totalRendered++;
 349   }
 350
 351   return totalRendered;
 352}
 353
 354void GroundCoverCell::renderBillboards( SceneRenderState *state, BaseMatInstance *mat, GFXPrimitiveBufferHandle *pb  )
 355{
 356   if ( mDirty )
 357   {
 358      _rebuildVB();
 359      mDirty = false;
 360   }
 361
 362   // Do we have anything to render?
 363   if ( mBillboards.size() == 0 || mVBs.empty() || !mat )
 364      return;
 365
 366   // TODO: Maybe add support for non-facing billboards
 367   // with random rotations and optional crosses.  We could
 368   // stick them into the buffer after the normal billboards,
 369   // then change shader consts.
 370
 371   RenderPassManager *pass = state->getRenderPass();
 372      
 373   // Draw each batch.
 374   U32 remaining = mBillboards.size();
 375   const U32 batches = mVBs.size();
 376   const U32 batchBB = remaining / batches;
 377
 378   for ( U32 b = 0; b < batches; b++ )
 379   {
 380      // Grab a reference to the vb.
 381      VBHandle &vb = mVBs[b];
 382
 383      // How many billboards in this batch?
 384      U32 bb = getMin( batchBB, remaining );
 385      remaining -= bb;
 386
 387      MeshRenderInst *ri = pass->allocInst<MeshRenderInst>();
 388      ri->type = RenderPassManager::RIT_Mesh;
 389      ri->matInst = mat;
 390      ri->vertBuff = &vb;
 391      ri->primBuff = pb;
 392      ri->objectToWorld = &MatrixF::Identity;
 393      ri->worldToCamera = pass->allocSharedXform(RenderPassManager::View);
 394      ri->projection = pass->allocSharedXform(RenderPassManager::Projection);
 395      ri->defaultKey = mat->getStateHint();
 396      ri->prim = pass->allocPrim();
 397      ri->prim->numPrimitives = bb * 2;
 398      ri->prim->numVertices = bb * 4;
 399      ri->prim->startIndex = 0;
 400      ri->prim->startVertex = 0;
 401      ri->prim->minIndex = 0;
 402      ri->prim->type = GFXTriangleList;
 403
 404      // If we need lights then set them up.
 405      if ( mat->isForwardLit() )
 406      {
 407         LightQuery query;
 408         query.init( mBounds );
 409         query.getLights( ri->lights, 8 );
 410      }
 411
 412      pass->addInst( ri );
 413
 414      GroundCover::smStatRenderedBatches++;
 415      GroundCover::smStatRenderedBillboards += bb;
 416   }
 417
 418   GroundCover::smStatRenderedCells++;
 419}
 420
 421
 422U32 GroundCover::smStatRenderedCells = 0;
 423U32 GroundCover::smStatRenderedBillboards = 0;
 424U32 GroundCover::smStatRenderedBatches = 0;
 425U32 GroundCover::smStatRenderedShapes = 0;
 426F32 GroundCover::smDensityScale = 1.0f;
 427F32 GroundCover::smFadeScale = 1.0f;
 428
 429ConsoleDocClass( GroundCover,
 430   "@brief Covers the ground in a field of objects (IE: Grass, Flowers, etc)."
 431   "@ingroup Foliage\n"
 432);
 433
 434GroundCover::GroundCover()
 435{
 436   mTypeMask |= StaticObjectType | StaticShapeObjectType;
 437   mNetFlags.set( Ghostable | ScopeAlways );
 438
 439   mRadius = 200.0f;
 440   mZOffset = 0.0f;
 441   mFadeRadius = 50.0f;
 442   mShapeCullRadius = 75.0f;
 443   mShapesCastShadows = true;
 444   mReflectRadiusScale = 0.25f;
 445
 446   mGridSize = 7;
 447
 448   // By initializing this to a big value we
 449   // ensure we warp on first render.
 450   mGridIndex.set( S32_MAX, S32_MAX );
 451
 452   mMaxPlacement = 1000;
 453   mLastPlacementCount = 0;
 454
 455   mDebugRenderCells = false;
 456   mDebugNoBillboards = false;
 457   mDebugNoShapes = false;
 458   mDebugLockFrustum = false;
 459
 460   mRandomSeed = 1;
 461
 462   initMaterialAsset(Material);
 463
 464   mMatInst = NULL;
 465   mMatParams = NULL;
 466   mTypeRectsParam = NULL;
 467   mFadeParams = NULL;
 468   mWindDirParam = NULL;
 469   mGustInfoParam = NULL;
 470   mTurbInfoParam = NULL;
 471   mCamRightParam = NULL;
 472   mCamUpParam = NULL;
 473
 474   mMaxBillboardTiltAngle = 90.0f;
 475
 476   // TODO: This really doesn't belong here... we need a
 477   // real wind system for Torque scenes.  This data
 478   // would be part of a global scene wind or area wind
 479   // emitter.
 480   //
 481   // Tom Spilman - 10/16/2007
 482
 483   mWindGustLength = 20.0f;
 484   mWindGustFrequency = 0.5f;
 485   mWindGustStrength = 0.5f;
 486   mWindDirection.set( 1.0f, 0.0f );
 487   mWindTurbulenceFrequency = 1.2f;
 488   mWindTurbulenceStrength = 0.125f;
 489
 490   for ( S32 i=0; i < MAX_COVERTYPES; i++ )
 491   {
 492      mProbability[i] = 0.0f;
 493
 494      mSizeMin[i] = 1.0f;
 495      mSizeMax[i] = 1.0f;
 496      mSizeExponent[i] = 1.0f;
 497
 498      mWindScale[i] = 1.0f;
 499
 500     mMinSlope[i] = 0.0f;
 501      mMaxSlope[i] = 0.0f;
 502
 503     mConformToNormal[i] = false;
 504     mMinRotX[i] = 0.0f;
 505     mMaxRotX[i] = 0.0f;
 506     mMinRotY[i] = 0.0f;
 507     mMaxRotY[i] = 0.0f;
 508
 509      mMinElevation[i] = -99999.0f;
 510      mMaxElevation[i] = 99999.0f;
 511
 512      mLayer[i] = StringTable->EmptyString();
 513      mInvertLayer[i] = false;
 514
 515      mMinClumpCount[i] = 1;
 516      mMaxClumpCount[i] = 1;
 517      mClumpCountExponent[i] = 1.0f;
 518      mClumpRadius[i] = 1.0f;
 519
 520      mBillboardRects[i].point.set( 0.0f, 0.0f );
 521      mBillboardRects[i].extent.set( 1.0f, 1.0f );
 522
 523      mShapeFilenames[i] = NULL;
 524      mShapeInstances[i] = NULL;
 525
 526      mBillboardAspectScales[i] = 1.0f;
 527
 528      mNormalizedProbability[i] = 0.0f;
 529   }
 530}
 531
 532GroundCover::~GroundCover()
 533{
 534   SAFE_DELETE( mMatInst );
 535}
 536
 537IMPLEMENT_CO_NETOBJECT_V1(GroundCover);
 538
 539void GroundCover::initPersistFields()
 540{
 541   addGroup( "GroundCover General" );
 542
 543      scriptBindMaterialAsset(Material, GroundCover, "Material used by all GroundCover segments.");
 544
 545      addField( "radius",        TypeF32,          Offset( mRadius, GroundCover ),              "Outer generation radius from the current camera position." );
 546      addField( "dissolveRadius",TypeF32,          Offset( mFadeRadius, GroundCover ),          "This is less than or equal to radius and defines when fading of cover elements begins." );
 547      addField( "reflectScale",  TypeF32,          Offset( mReflectRadiusScale, GroundCover ),  "Scales the various culling radii when rendering a reflection. Typically for water." );
 548
 549      addField( "gridSize",      TypeS32,          Offset( mGridSize, GroundCover ),            "The number of cells per axis in the grid." );
 550      addField( "zOffset",       TypeF32,          Offset( mZOffset, GroundCover ),             "Offset along the Z axis to render the ground cover." );
 551
 552      addField( "seed",          TypeS32,          Offset( mRandomSeed, GroundCover ),          "This RNG seed is saved and sent to clients for generating the same cover." );
 553      addField( "maxElements",   TypeS32,          Offset( mMaxPlacement, GroundCover ),        "The maximum amount of cover elements to include in the grid at any one time." );
 554
 555      addField( "maxBillboardTiltAngle", TypeF32,  Offset( mMaxBillboardTiltAngle, GroundCover ),"The maximum amout of degrees the billboard will tilt down to match the camera." );
 556      addField( "shapeCullRadius", TypeF32,        Offset( mShapeCullRadius, GroundCover ),     "This is the distance at which DTS elements are  completely culled out." );      
 557      addField( "shapesCastShadows", TypeBool,     Offset( mShapesCastShadows, GroundCover ),   "Whether DTS elements should cast shadows or not." );
 558
 559      addArray( "Types", MAX_COVERTYPES );
 560
 561         addField( "billboardUVs",  TypeRectUV,    Offset( mBillboardRects, GroundCover ), MAX_COVERTYPES,  "Subset material UV coordinates for this cover billboard." );
 562
 563         addField( "shapeFilename", TypeFilename,  Offset( mShapeFilenames, GroundCover ), MAX_COVERTYPES,  "The cover shape filename. [Optional]" );
 564
 565         addField( "layer",         TypeTerrainMaterialName, Offset( mLayer, GroundCover ), MAX_COVERTYPES, "Terrain material name to limit coverage to, or blank to not limit." );
 566
 567         addField( "invertLayer",   TypeBool,      Offset( mInvertLayer, GroundCover ), MAX_COVERTYPES,     "Indicates that the terrain material index given in 'layer' is an exclusion mask." );
 568
 569         addField( "probability",   TypeF32,       Offset( mProbability, GroundCover ), MAX_COVERTYPES,     "The probability of one cover type verses another (relative to all cover types)." );
 570
 571         addField( "sizeMin",       TypeF32,       Offset( mSizeMin, GroundCover ), MAX_COVERTYPES,         "The minimum random size for each cover type." );
 572
 573         addField( "sizeMax",       TypeF32,       Offset( mSizeMax, GroundCover ), MAX_COVERTYPES,         "The maximum random size of this cover type." );
 574
 575         addField( "sizeExponent",  TypeF32,       Offset( mSizeExponent, GroundCover ), MAX_COVERTYPES,    "An exponent used to bias between the minimum and maximum random sizes." );
 576
 577         addField( "windScale",     TypeF32,       Offset( mWindScale, GroundCover ), MAX_COVERTYPES,       "The wind effect scale." );
 578
 579       addField( "minSlope",      TypeF32,       Offset(mMinSlope, GroundCover), MAX_COVERTYPES,          "The minimum slope angle in degrees for placement.");
 580
 581         addField( "maxSlope",      TypeF32,       Offset( mMaxSlope, GroundCover ), MAX_COVERTYPES,        "The maximum slope angle in degrees for placement." );
 582
 583       addField("conformToNormal",TypeBool,      Offset(mConformToNormal, GroundCover), MAX_COVERTYPES,   "Use the terrain's slope for angle");
 584       addField("minRotX",        TypeF32,       Offset(mMinRotX, GroundCover), MAX_COVERTYPES,           "minumum amount of rotation along the X axis to add");
 585       addField("maxRotX",        TypeF32,       Offset(mMaxRotX, GroundCover), MAX_COVERTYPES,           "maximum amount of rotation along the X axis to add");
 586       addField("minRotY",        TypeF32,       Offset(mMinRotY, GroundCover), MAX_COVERTYPES,           "minumum amount of rotation along the Y axis to add");
 587       addField("maxRotY",        TypeF32,       Offset(mMaxRotY, GroundCover), MAX_COVERTYPES,           "maximum amount of rotation along the Y axis to add");
 588
 589         addField( "minElevation",  TypeF32,       Offset( mMinElevation, GroundCover ), MAX_COVERTYPES,    "The minimum world space elevation for placement." );
 590
 591         addField( "maxElevation",  TypeF32,       Offset( mMaxElevation, GroundCover ), MAX_COVERTYPES,    "The maximum world space elevation for placement." );
 592
 593         addField( "minClumpCount", TypeS32,       Offset( mMinClumpCount, GroundCover ), MAX_COVERTYPES,   "The minimum amount of elements in a clump." );
 594      
 595         addField( "maxClumpCount", TypeS32,       Offset( mMaxClumpCount, GroundCover ), MAX_COVERTYPES,   "The maximum amount of elements in a clump." );
 596
 597         addField( "clumpExponent", TypeF32,       Offset( mClumpCountExponent, GroundCover ), MAX_COVERTYPES, "An exponent used to bias between the minimum and maximum clump counts for a particular clump." );
 598
 599         addField( "clumpRadius",   TypeF32,       Offset( mClumpRadius, GroundCover ), MAX_COVERTYPES,     "The maximum clump radius." );
 600
 601      endArray( "Types" );
 602
 603   endGroup( "GroundCover General" );
 604
 605   addGroup( "GroundCover Wind" );
 606
 607      addField( "windDirection",    TypePoint2F,   Offset( mWindDirection, GroundCover ),             "The direction of the wind." );
 608
 609      addField( "windGustLength",   TypeF32,       Offset( mWindGustLength, GroundCover ),            "The length in meters between peaks in the wind gust." );
 610      addField( "windGustFrequency",TypeF32,       Offset( mWindGustFrequency, GroundCover ),         "Controls how often the wind gust peaks per second." );
 611      addField( "windGustStrength", TypeF32,       Offset( mWindGustStrength, GroundCover ),          "The maximum distance in meters that the peak wind  gust will displace an element." );
 612
 613      addField( "windTurbulenceFrequency",   TypeF32, Offset( mWindTurbulenceFrequency, GroundCover ),"Controls the overall rapidity of the wind turbulence." );
 614      addField( "windTurbulenceStrength",    TypeF32, Offset( mWindTurbulenceStrength, GroundCover ), "The maximum distance in meters that the turbulence can displace a ground cover element." );
 615
 616   endGroup( "GroundCover Wind" );
 617
 618   addGroup( "GroundCover Debug" );
 619
 620      addField( "lockFrustum",      TypeBool,      Offset( mDebugLockFrustum, GroundCover ),          "Debug parameter for locking the culling frustum which will freeze the cover generation." );
 621      addField( "renderCells",      TypeBool,      Offset( mDebugRenderCells, GroundCover ),          "Debug parameter for displaying the grid cells." );
 622      addField( "noBillboards",     TypeBool,      Offset( mDebugNoBillboards, GroundCover ),         "Debug parameter for turning off billboard rendering." );
 623      addField( "noShapes",         TypeBool,      Offset( mDebugNoShapes, GroundCover ),             "Debug parameter for turning off shape rendering." );
 624
 625   endGroup( "GroundCover Debug" );  
 626
 627   Parent::initPersistFields();
 628}
 629
 630void GroundCover::consoleInit()
 631{     
 632   Con::addVariable( "$pref::GroundCover::densityScale", TypeF32, &smDensityScale, "A global LOD scalar which can reduce the overall density of placed GroundCover.\n" 
 633      "@ingroup Foliage\n");
 634   Con::addVariable("$pref::GroundCover::fadeScale", TypeF32, &smFadeScale, "A global fade scalar which can reduce the overall rendered distance of placed GroundCover.\n"
 635      "@ingroup Foliage\n");
 636
 637   Con::addVariable( "$GroundCover::renderedCells", TypeS32, &smStatRenderedCells, "Stat for number of rendered cells.\n"
 638      "@ingroup Foliage\n");
 639   Con::addVariable( "$GroundCover::renderedBillboards", TypeS32, &smStatRenderedBillboards, "Stat for number of rendered billboards.\n"
 640      "@ingroup Foliage\n");
 641   Con::addVariable( "$GroundCover::renderedBatches", TypeS32, &smStatRenderedBatches, "Stat for number of rendered billboard batches.\n"
 642      "@ingroup Foliage\n");
 643   Con::addVariable( "$GroundCover::renderedShapes", TypeS32, &smStatRenderedShapes, "Stat for number of rendered shapes.\n"
 644      "@ingroup Foliage\n");
 645
 646   Parent::consoleInit();
 647}
 648
 649bool GroundCover::onAdd()
 650{
 651   if (!Parent::onAdd())
 652      return false;
 653
 654   // We don't use any bounds.
 655   setGlobalBounds();
 656
 657   resetWorldBox();
 658
 659   // Prepare some client side things.
 660   if ( isClientObject() )
 661   {            
 662      _initMaterial();
 663
 664      _initShapes();
 665
 666      // Hook ourselves up to get terrain change notifications.
 667      TerrainBlock::smUpdateSignal.notify( this, &GroundCover::onTerrainUpdated );
 668   }
 669
 670   addToScene();
 671
 672   return true;
 673}
 674
 675void GroundCover::onRemove()
 676{
 677   Parent::onRemove();
 678
 679   _deleteCells();
 680   _deleteShapes();
 681   
 682   if ( isClientObject() )
 683   {
 684      TerrainBlock::smUpdateSignal.remove( this, &GroundCover::onTerrainUpdated );      
 685   }
 686
 687   removeFromScene();
 688}
 689
 690void GroundCover::inspectPostApply()
 691{
 692   Parent::inspectPostApply();
 693
 694   // We flag all the parameters as changed because
 695   // we're feeling lazy and there is not a good way
 696   // to track what parameters changed.
 697   //
 698   // TODO: Add a mask bit option to addField() and/or
 699   // addGroup() which is passed to inspectPostApply
 700   // for detection of changed elements.
 701   //
 702   setMaskBits(U32(-1) );
 703}
 704
 705U32 GroundCover::packUpdate( NetConnection *connection, U32 mask, BitStream *stream )
 706{
 707   U32 retMask = Parent::packUpdate( connection, mask, stream );
 708
 709   if (stream->writeFlag(mask & InitialUpdateMask))
 710   {
 711      // TODO: We could probably optimize a few of these
 712      // based on reasonable units at some point.
 713
 714      packMaterialAsset(connection, Material);
 715
 716      stream->write( mRadius );
 717      stream->write( mZOffset );
 718      stream->write( mFadeRadius );
 719      stream->write( mShapeCullRadius );
 720      stream->writeFlag( mShapesCastShadows );
 721      stream->write( mReflectRadiusScale );
 722      stream->write( mGridSize );
 723      stream->write( mRandomSeed );
 724      stream->write( mMaxPlacement );
 725      stream->write( mMaxBillboardTiltAngle );
 726
 727      stream->write( mWindDirection.x );
 728      stream->write( mWindDirection.y );
 729      stream->write( mWindGustLength );
 730      stream->write( mWindGustFrequency );
 731      stream->write( mWindGustStrength );
 732      stream->write( mWindTurbulenceFrequency );
 733      stream->write( mWindTurbulenceStrength );
 734
 735      for ( S32 i=0; i < MAX_COVERTYPES; i++ )
 736      {
 737         stream->write( mProbability[i] );
 738         stream->write( mSizeMin[i] );
 739         stream->write( mSizeMax[i] );
 740         stream->write( mSizeExponent[i] );
 741         stream->write( mWindScale[i] );
 742         
 743         stream->write( mMinSlope[i] );
 744         stream->write( mMaxSlope[i] );
 745       stream->writeFlag(mConformToNormal[i]);
 746       stream->write(mMinRotX[i]);
 747       stream->write(mMaxRotX[i]);
 748       stream->write(mMinRotY[i]);
 749       stream->write(mMaxRotY[i]);
 750         
 751         stream->write( mMinElevation[i] );
 752         stream->write( mMaxElevation[i] );     
 753
 754         stream->writeString( mLayer[i] );
 755         stream->writeFlag( mInvertLayer[i] );      
 756
 757         stream->write( mMinClumpCount[i] );
 758         stream->write( mMaxClumpCount[i] );
 759         stream->write( mClumpCountExponent[i] );
 760         stream->write( mClumpRadius[i] );
 761
 762         stream->write( mBillboardRects[i].point.x );
 763         stream->write( mBillboardRects[i].point.y );
 764         stream->write( mBillboardRects[i].extent.x );
 765         stream->write( mBillboardRects[i].extent.y );
 766
 767         stream->writeString( mShapeFilenames[i] );
 768      }
 769
 770      stream->writeFlag( mDebugRenderCells );
 771      stream->writeFlag( mDebugNoBillboards );
 772      stream->writeFlag( mDebugNoShapes );
 773      stream->writeFlag( mDebugLockFrustum );
 774   }
 775
 776   return retMask;  
 777}
 778
 779void GroundCover::unpackUpdate( NetConnection *connection, BitStream *stream )
 780{
 781   Parent::unpackUpdate( connection, stream );
 782
 783   if (stream->readFlag())
 784   {
 785      unpackMaterialAsset(connection, Material);
 786
 787      stream->read( &mRadius );
 788      stream->read( &mZOffset );
 789      stream->read( &mFadeRadius );
 790      stream->read( &mShapeCullRadius );
 791      mShapesCastShadows = stream->readFlag();
 792      stream->read( &mReflectRadiusScale );   
 793      stream->read( &mGridSize );
 794      stream->read( &mRandomSeed );
 795      stream->read( &mMaxPlacement );
 796      stream->read( &mMaxBillboardTiltAngle );
 797
 798      stream->read( &mWindDirection.x );
 799      stream->read( &mWindDirection.y );
 800      stream->read( &mWindGustLength );
 801      stream->read( &mWindGustFrequency );
 802      stream->read( &mWindGustStrength );
 803      stream->read( &mWindTurbulenceFrequency );
 804      stream->read( &mWindTurbulenceStrength );
 805
 806      for ( S32 i=0; i < MAX_COVERTYPES; i++ )
 807      {
 808         stream->read( &mProbability[i] );
 809         stream->read( &mSizeMin[i] );
 810         stream->read( &mSizeMax[i] );
 811         stream->read( &mSizeExponent[i] );
 812         stream->read( &mWindScale[i] );
 813
 814         stream->read( &mMinSlope[i] );
 815         stream->read( &mMaxSlope[i] );
 816       mConformToNormal[i] = stream->readFlag();
 817       stream->read(&mMinRotX[i]);
 818       stream->read(&mMaxRotX[i]);
 819       stream->read(&mMinRotY[i]);
 820       stream->read(&mMaxRotY[i]);
 821
 822         stream->read( &mMinElevation[i] );
 823         stream->read( &mMaxElevation[i] );     
 824
 825         mLayer[i] = stream->readSTString();
 826         mInvertLayer[i] = stream->readFlag();
 827
 828         stream->read( &mMinClumpCount[i] );
 829         stream->read( &mMaxClumpCount[i] );
 830         stream->read( &mClumpCountExponent[i] );
 831         stream->read( &mClumpRadius[i] );
 832
 833         stream->read( &mBillboardRects[i].point.x );
 834         stream->read( &mBillboardRects[i].point.y );
 835         stream->read( &mBillboardRects[i].extent.x );
 836         stream->read( &mBillboardRects[i].extent.y );
 837
 838         mShapeFilenames[i] = stream->readSTString();
 839      }
 840
 841      mDebugRenderCells    = stream->readFlag();
 842      mDebugNoBillboards   = stream->readFlag();
 843      mDebugNoShapes       = stream->readFlag();
 844      mDebugLockFrustum    = stream->readFlag();
 845
 846      // We have no way to easily know what changed, so by clearing
 847      // the cells we force a reinit and regeneration of the cells.
 848      // It's sloppy, but it works for now.
 849      _freeCells();
 850
 851      if ( isProperlyAdded() )
 852         _initMaterial();
 853   }
 854}
 855
 856void GroundCover::_initMaterial()
 857{
 858   if (mMaterialAsset.notNull())
 859   {
 860      if (mMatInst && String(mMaterialAsset->getMaterialDefinitionName()).equal(mMatInst->getMaterial()->getName(), String::NoCase))
 861         return;
 862
 863      SAFE_DELETE(mMatInst);
 864
 865      if (!Sim::findObject(mMaterialAsset->getMaterialDefinitionName(), mMaterial))
 866         Con::errorf("GroundCover::_initMaterial - Material %s was not found.", mMaterialAsset->getMaterialDefinitionName());
 867
 868      if (mMaterial)
 869         mMatInst = mMaterial->createMatInstance();
 870      else
 871         mMatInst = MATMGR->createMatInstance("WarningMaterial");
 872
 873      if (!mMatInst)
 874         Con::errorf("GroundCover::_initMaterial - no Material called '%s'", mMaterialAsset->getMaterialDefinitionName());
 875   }
 876   else
 877   {
 878      return;
 879   }
 880   
 881   // Add our special feature that makes it all work...
 882   FeatureSet features = MATMGR->getDefaultFeatures();
 883   features.addFeature( MFT_Foliage );
 884   
 885   // Our feature requires a pointer back to this object
 886   // to properly setup its shader consts.
 887   mMatInst->setUserObject( this );
 888
 889   // DO IT!
 890   mMatInst->init( features, getGFXVertexFormat<GCVertex>() );
 891}
 892
 893void GroundCover::_initShapes()
 894{
 895   _deleteShapes();
 896
 897   for ( S32 i=0; i < MAX_COVERTYPES; i++ )
 898   {
 899      if ( !mShapeFilenames[i] || !mShapeFilenames[i][0] )
 900         continue;
 901
 902      // Load the shape.
 903      Resource<TSShape> shape = ResourceManager::get().load(mShapeFilenames[i]);
 904      if ( !(bool)shape )
 905      {
 906         Con::warnf( "GroundCover::_initShapes() unable to load shape: %s", mShapeFilenames[i] );
 907         continue;
 908      }
 909
 910      if ( isClientObject() && !shape->preloadMaterialList(shape.getPath()) && NetConnection::filesWereDownloaded() )
 911      {
 912         Con::warnf( "GroundCover::_initShapes() material preload failed for shape: %s", mShapeFilenames[i] );
 913         continue;
 914      }
 915
 916      // Create the shape instance.
 917      mShapeInstances[i] = new TSShapeInstance( shape, isClientObject() );
 918   }
 919}
 920
 921void GroundCover::_deleteShapes()
 922{
 923   for ( S32 i=0; i < MAX_COVERTYPES; i++ )
 924   {
 925      delete mShapeInstances[i];
 926      mShapeInstances[i] = NULL;
 927   }
 928}
 929
 930void GroundCover::_deleteCells()
 931{
 932   // Delete the allocation list.
 933   for ( S32 i=0; i < mAllocCellList.size(); i++ )
 934      delete mAllocCellList[i];
 935   mAllocCellList.clear();
 936
 937   // Zero out the rest of the stuff.
 938   _freeCells();
 939}
 940
 941void GroundCover::_freeCells()
 942{
 943   // Zero the grid and scratch space.
 944   mCellGrid.clear();
 945   mScratchGrid.clear();
 946
 947   // Compact things... remove excess allocated cells.
 948   const U32 maxCells = mGridSize * mGridSize;
 949   if ( mAllocCellList.size() > maxCells )
 950   {
 951      for ( S32 i=maxCells; i < mAllocCellList.size(); i++ )
 952        delete mAllocCellList[i];
 953      mAllocCellList.setSize( maxCells );
 954   }
 955
 956   // Move all the alloced cells into the free list.
 957   mFreeCellList.clear();
 958   mFreeCellList.merge( mAllocCellList );
 959
 960   // Release the primitive buffer.
 961   mPrimBuffer = NULL;
 962}
 963
 964void GroundCover::_recycleCell( GroundCoverCell* cell )
 965{
 966   mFreeCellList.push_back( cell );
 967}
 968
 969void GroundCover::_initialize( U32 cellCount, U32 cellPlacementCount )
 970{
 971   // Cleanup everything... we're starting over.
 972   _freeCells();
 973   _deleteShapes();
 974
 975   // Nothing to do without a count!
 976   if ( cellPlacementCount == 0 )
 977      return;
 978
 979   // Reset the grid sizes.
 980   mCellGrid.setSize( cellCount );
 981   dMemset( mCellGrid.address(), 0, mCellGrid.memSize() );
 982   mScratchGrid.setSize( cellCount );
 983
 984   // Rebuild the texture aspect scales for each type.
 985   F32 textureAspect = 1.0f;
 986   if( mMatInst && mMatInst->isValid())
 987   {
 988      Material* mat = dynamic_cast<Material*>(mMatInst->getMaterial());
 989      if(mat)
 990      {
 991         GFXTexHandle tex;
 992         if (!mat->mDiffuseMapFilename[0].isEmpty())
 993            tex = GFXTexHandle(mat->mDiffuseMapFilename[0], &GFXStaticTextureSRGBProfile, "GroundCover texture aspect ratio check");
 994         else if (!mat->mDiffuseMapAsset[0].isNull())
 995            tex = mat->mDiffuseMapAsset[0]->getImage(GFXStaticTextureSRGBProfile);
 996
 997         if(tex.isValid())
 998         {
 999            U32 w = tex.getWidth();
1000            U32 h = tex.getHeight();
1001            if(h > 0)
1002               textureAspect = F32(w) / F32(h);
1003         }
1004      }
1005   }
1006   for ( S32 i=0; i < MAX_COVERTYPES; i++ )
1007   {  
1008      if ( mBillboardRects[i].extent.y > 0.0f )
1009      {
1010         mBillboardAspectScales[i] = textureAspect * mBillboardRects[i].extent.x / mBillboardRects[i].extent.y;
1011      }
1012      else
1013         mBillboardAspectScales[i] = 0.0f;
1014   }
1015
1016   // Load the shapes again.
1017   _initShapes();
1018
1019   // Set the primitive buffer up for the maximum placement in a cell.
1020   mPrimBuffer.set( GFX, cellPlacementCount * 6, 0, GFXBufferTypeStatic );
1021   U16 *idxBuff;
1022   mPrimBuffer.lock(&idxBuff);
1023   for ( U32 i=0; i < cellPlacementCount; i++ )
1024   {
1025      //
1026      // The vertex pattern in the VB for each 
1027      // billboard is as follows...
1028      //
1029      //     0----1
1030      //     |\   |
1031      //     | \  |
1032      //     |  \ |
1033      //     |   \|
1034      //     3----2
1035      //
1036      // We setup the index order below to ensure
1037      // sequential, cache friendly, access.
1038      //
1039      U32 offset = i * 4;
1040      idxBuff[i*6+0] = 0 + offset;
1041      idxBuff[i*6+1] = 1 + offset;
1042      idxBuff[i*6+2] = 2 + offset;
1043      idxBuff[i*6+3] = 2 + offset;
1044      idxBuff[i*6+4] = 3 + offset;
1045      idxBuff[i*6+5] = 0 + offset;
1046   }   
1047   mPrimBuffer.unlock();
1048
1049   // Generate the normalized probability.
1050   F32 total = 0.0f;
1051   for ( S32 i=0; i < MAX_COVERTYPES; i++ )
1052   {
1053      // If the element isn't gonna render... then
1054      // set the probability to zero.
1055      if ( mShapeInstances[i] == NULL && mBillboardAspectScales[i] <= 0.0001f )
1056      {
1057         mNormalizedProbability[i] = 0.0f;
1058      }
1059      else
1060      {
1061         mNormalizedProbability[i] = mProbability[i];
1062
1063         total += mProbability[i];
1064      }
1065   }
1066   if ( total > 0.0f )
1067   {
1068      for ( S32 i=0; i < MAX_COVERTYPES; i++ )
1069         mNormalizedProbability[i] /= total;
1070   }
1071}
1072
1073GroundCoverCell* GroundCover::_generateCell( const Point2I& index, 
1074                                             const Box3F& bounds, 
1075                                             U32 placementCount,
1076                                             S32 randSeed )
1077{
1078   PROFILE_SCOPE(GroundCover_GenerateCell);
1079
1080   const Vector<SceneObject*> terrainBlocks = getContainer()->getTerrains();
1081   if ( terrainBlocks.empty() )
1082      return NULL;
1083
1084   // Grab a free cell or allocate a new one.
1085   GroundCoverCell* cell;
1086   if ( mFreeCellList.empty() )
1087   {
1088      cell = new GroundCoverCell();
1089      mAllocCellList.push_back( cell );
1090   }
1091   else
1092   {
1093      cell = mFreeCellList.last();
1094      mFreeCellList.pop_back();
1095   }
1096
1097   cell->mDirty = true;
1098   cell->mIndex = index;
1099   cell->mBounds = bounds;
1100
1101   Point3F pos( 0, 0, 0 );
1102
1103   Box3F renderBounds = bounds;
1104   Point3F point;
1105   Point3F normal;
1106   F32 h;
1107   Point2F cp, uv;
1108   bool hit;
1109   GroundCoverCell::Placement p;
1110   F32 rotation;
1111   F32 size;
1112   F32 sizeExponent;
1113   Point2I lpos;
1114   //F32 value;
1115   VectorF right;
1116   StringTableEntry matName = StringTable->EmptyString();
1117   bool firstElem = true;
1118
1119   TerrainBlock *terrainBlock = NULL;
1120
1121   cell->mBillboards.clear();
1122   cell->mBillboards.reserve( placementCount );
1123   cell->mShapes.clear();
1124   cell->mShapes.reserve( placementCount );
1125
1126   F32   terrainSquareSize, 
1127         oneOverTerrainLength, 
1128         oneOverTerrainSquareSize;
1129   const GBitmap* terrainLM = NULL;
1130
1131   // The RNG that we'll use in generation.
1132   MRandom rand( 0 );
1133
1134   // We process one type at a time.
1135   for ( U32 type=0; type < MAX_COVERTYPES; type++ )
1136   {
1137      // How many cover elements do we need to generate for this type?
1138      const S32 typeCount = mNormalizedProbability[type] * (F32)placementCount;
1139      if ( typeCount <= 0 )
1140         continue;
1141
1142      // Grab the terrain layer for this type.
1143      /*
1144      const TerrainDataLayer* dataLayer = NULL;
1145      const bool typeInvertLayer = mInvertLayer[type];
1146      if ( mLayer[type] > -1 )
1147      {
1148         dataLayer = mTerrainBlock->getDataLayer( mLayer[type] );
1149         if ( dataLayer )
1150         {
1151            // Do an initial check to see if we can place any place anything
1152            // at all...  if the layer area for this element is empty then 
1153            // there is nothing more to do.
1154
1155            RectI area( (S32)mFloor( ( bounds.minExtents.x - pos.x ) * oneOverTerrainSquareSize ),
1156                        (S32)mFloor( ( bounds.minExtents.y - pos.y ) * oneOverTerrainSquareSize ),
1157                        (S32)mCeil( ( bounds.maxExtents.x - pos.x ) * oneOverTerrainSquareSize ),
1158                        (S32)mCeil( ( bounds.maxExtents.y - pos.y ) * oneOverTerrainSquareSize ) );
1159            area.extent -= area.point;
1160
1161            if ( dataLayer->testFill( area, typeInvertLayer ? 255 : 0 ) )
1162               continue;
1163         }
1164      }
1165
1166      // If the layer is not inverted and we have no data 
1167      // then we have nothing to draw.
1168      if ( !typeInvertLayer && !dataLayer )
1169         continue;
1170      */
1171
1172      // We set the seed we were passed which is based on this grids position
1173      // in the world and add the type value.  This keeps changes to one type
1174      // from effecting the outcome of the others.
1175      rand.setSeed( randSeed + type );
1176
1177      // Setup for doing clumps.
1178      S32 clumps = 0;
1179      Point2F clumpCenter(0.0f, 0.0f);
1180      const S32 clumpMin = getMax( 1, (S32)mMinClumpCount[type] );
1181      F32 clumpExponent;   
1182
1183      // We mult this by -1 each billboard we make then use
1184      // it to scale the billboard x axis to flip them.  This
1185      // essentially gives us twice the variation for free.
1186      F32 flipBB = -1.0f;
1187
1188      // Precompute a few other type specific values.
1189     const bool typeConformToNormal = mConformToNormal[type];
1190     const F32 typeMinRotX = (mMaxRotX[type] > mMinRotX[type]) ? mMinRotX[type] : mMaxRotX[type];
1191     const F32 typeMaxRotX = (mMaxRotX[type] > mMinRotX[type]) ? mMaxRotX[type] : mMinRotX[type];
1192     const F32 typeMinRotY = (mMaxRotY[type] > mMinRotY[type]) ? mMinRotY[type] : mMaxRotY[type];
1193     const F32 typeMaxRotY = (mMaxRotY[type] > mMinRotY[type]) ? mMaxRotY[type] : mMinRotY[type];
1194     
1195      const F32 typeSizeRange = mSizeMax[type] - mSizeMin[type];
1196     const F32 typeMinSlope = mMinSlope[type];
1197      const F32 typeMaxSlope = mMaxSlope[type];
1198      const F32 typeMaxElevation = mMaxElevation[type];
1199      const F32 typeMinElevation = mMinElevation[type];
1200      const bool typeIsShape = mShapeInstances[ type ] != NULL;
1201      const Box3F typeShapeBounds = typeIsShape ? mShapeInstances[ type ]->getShape()->mBounds : Box3F();
1202      const F32 typeWindScale = mWindScale[type];
1203      StringTableEntry typeLayer = mLayer[type];
1204      const bool typeInvertLayer = mInvertLayer[type];
1205
1206      // We can set this once here... all the placements for this are the same.
1207      p.type = type;
1208      p.windAmplitude = typeWindScale;
1209      p.lmColor.set(1.0f,1.0f,1.0f);
1210
1211      // Generate all the cover elements for this type.
1212      for ( S32 i=0; i < typeCount; i++ )
1213      {
1214         // Do all the other random things here first as to not 
1215         // disturb the random sequence if the terrain geometry
1216         // or cover layers change.
1217
1218         // Get the random position.      
1219         cp.set( rand.randF(), rand.randF() );
1220
1221         // Prepare the clump info.
1222         clumpExponent = mClampF( mPow( rand.randF(), mClumpCountExponent[type] ), 0.0f, 1.0f );
1223         if ( clumps <= 0 )
1224         {
1225            // We're starting a new clump.
1226            clumps = ( clumpMin + mFloor( ( mMaxClumpCount[type] - clumpMin ) * clumpExponent ) ) - 1;
1227            cp.set(  bounds.minExtents.x + cp.x * bounds.len_x(),
1228                     bounds.minExtents.y + cp.y * bounds.len_y() );
1229            clumpCenter = cp;
1230         }
1231         else
1232         {
1233            clumps--;
1234            cp.set( clumpCenter.x - ( ( cp.x - 0.5f ) * mClumpRadius[type] ),
1235                    clumpCenter.y - ( ( cp.y - 0.5f ) * mClumpRadius[type] ) );
1236         }
1237
1238         // Which terrain do I place on?
1239         if ( terrainBlocks.size() == 1 )
1240            terrainBlock = dynamic_cast< TerrainBlock* >( terrainBlocks.first() );
1241         else
1242         {
1243            for ( U32 blockIDx = 0; blockIDx < terrainBlocks.size(); blockIDx++ )
1244            {
1245               TerrainBlock *terrain = dynamic_cast< TerrainBlock* >( terrainBlocks[ blockIDx ] );
1246               if( !terrain )
1247                  continue;
1248
1249               const Box3F &terrBounds = terrain->getWorldBox();
1250
1251               if (  cp.x < terrBounds.minExtents.x || cp.x > terrBounds.maxExtents.x ||
1252                     cp.y < terrBounds.minExtents.y || cp.y > terrBounds.maxExtents.y )
1253                  continue;
1254
1255               terrainBlock = terrain;
1256               break;
1257            }
1258         }
1259
1260         // This should only happen if the generation went off
1261         // the edge of the terrain blocks.
1262         if ( !terrainBlock )
1263            continue;
1264
1265         terrainLM = terrainBlock->getLightMap();
1266         pos = terrainBlock->getPosition();
1267
1268         terrainSquareSize = (F32)terrainBlock->getSquareSize();
1269         oneOverTerrainLength = 1.0f / terrainBlock->getWorldBlockSize();
1270         oneOverTerrainSquareSize = 1.0f / terrainSquareSize;
1271
1272         // The size is calculated using an exponent to control 
1273         // the frequency between min and max sizes.
1274         sizeExponent = mClampF( mPow( rand.randF(), mSizeExponent[type] ), 0.0f, 1.0f );
1275         size = mSizeMin[type] + ( typeSizeRange * sizeExponent );
1276
1277         // Generate a random z rotation.
1278         rotation = rand.randF() * M_2PI_F;
1279
1280         // Flip the billboard now for the next generation.
1281         flipBB *= -1.0f;
1282
1283         PROFILE_START( GroundCover_TerrainRayCast );
1284         // Transform billboard point into terrain's frame of reference.
1285         Point3F pp = Point3F(cp.x, cp.y, 0);
1286         terrainBlock->getWorldTransform().mulP(pp);
1287         hit = terrainBlock->getNormalHeightMaterial( Point2F ( pp.x, pp.y ),
1288                                                      &normal, &h, matName );
1289         PROFILE_END(); // GroundCover_TerrainRayCast
1290         
1291         // TODO: When did we loose the world space elevation when
1292         // getting the terrain height?
1293         h += pos.z + mZOffset;
1294
1295         if ( !hit || h > typeMaxElevation || h < typeMinElevation || 
1296              ( typeLayer[0] && !typeInvertLayer && matName != typeLayer ) ||
1297              ( typeLayer[0] && typeInvertLayer && matName == typeLayer ) )
1298            continue;
1299
1300         // Do we need to check slope?
1301         if ( !mIsZero( typeMaxSlope ) )
1302         {
1303            if (mAcos(normal.z) > mDegToRad(typeMaxSlope))
1304               continue;
1305         }
1306
1307       if (!mIsZero(typeMinSlope))
1308       {
1309          if (mAcos(normal.z) < mDegToRad(typeMinSlope))
1310             continue;
1311       }
1312         point.set( cp.x, cp.y, h );
1313         p.point = point;
1314         p.rotation = rotation;
1315         p.normal = normal;
1316       if (!typeConformToNormal)
1317       {
1318          p.normal.y = 0;
1319          p.normal.x = 0;
1320       }
1321       p.normal.x += rand.randF(typeMinRotX, typeMaxRotX);
1322       p.normal.y += rand.randF(typeMinRotY, typeMaxRotY);
1323         // Grab the terrain lightmap color at this position.
1324         //
1325         // TODO: Can't we remove this test?  The terrain 
1326         // lightmap should never be null... NEVER!
1327         //
1328         if ( terrainLM )
1329         {
1330            // TODO: We could probably call terrainLM->getBits()
1331            // once outside the loop then pre-calculate the scalar
1332            // for converting a world position into a lexel...
1333            // avoiding the extra protections inside of sampleTexel().
1334
1335            uv.x = (point.x + pos.x) * oneOverTerrainLength;
1336            uv.y = (point.y + pos.y) * oneOverTerrainLength;
1337            uv.x -= mFloor(uv.x);
1338            uv.y -= mFloor(uv.y);
1339            p.lmColor = terrainLM->sampleTexel(uv.x,uv.y);
1340         }
1341
1342         // Put it into the right list by type.
1343         //
1344         // TODO: Could we break up the generation into
1345         // two separate loops for shapes and billboards
1346         // and gain performance?
1347         //
1348         if ( typeIsShape )
1349         {
1350            // TODO: Convert the size into a real size... not scale!
1351
1352            // TODO: We could probably cache the shape bounds
1353            // into a primitive array and avoid the double pointer
1354            // dereference per placement.
1355
1356            p.size.set( size, size, size );
1357            p.worldBox = typeShapeBounds;
1358            p.worldBox.minExtents *= size;
1359            p.worldBox.maxExtents *= size;
1360            p.worldBox.minExtents += point;
1361            p.worldBox.maxExtents += point;
1362
1363            cell->mShapes.push_back( p );
1364         }
1365         else
1366         {
1367            p.size.y = size;
1368            p.size.x = size * flipBB * mBillboardAspectScales[type]; 
1369            p.worldBox.maxExtents = p.worldBox.minExtents = point;
1370
1371            cell->mBillboards.push_back( p );
1372         }
1373
1374         // Update the render bounds.
1375         if ( firstElem )
1376         {
1377            renderBounds = p.worldBox;
1378            firstElem = false;
1379         }
1380         else
1381         {
1382            renderBounds.extend( p.worldBox.minExtents );
1383            renderBounds.extend( p.worldBox.maxExtents );
1384         }
1385
1386      } // for ( S32 i=0; i < typeCount; i++ )
1387
1388   } // for ( U32 type=0; type < NumCoverTypes; type++ )
1389      
1390
1391   cell->mRenderBounds = renderBounds;
1392   cell->mBounds.minExtents.z = renderBounds.minExtents.z;
1393   cell->mBounds.maxExtents.z = renderBounds.maxExtents.z;
1394
1395   return cell;
1396}
1397
1398void GroundCover::onTerrainUpdated( U32 flags, TerrainBlock *tblock, const Point2I& min, const Point2I& max )
1399{
1400   if ( isServerObject() ) 
1401      return;
1402
1403   // Free all the cells if we've gotten a lightmap update.
1404   if ( flags & TerrainBlock::LightmapUpdate )
1405   {
1406      _freeCells();
1407      return;
1408   }
1409
1410   // TODO: EmptyUpdate doesn't work yet... fix editor/terrain.
1411
1412   // If this is a height or opacity update only clear
1413   // the cells that have changed.
1414   if (  flags & TerrainBlock::HeightmapUpdate || 
1415         flags & TerrainBlock::LayersUpdate ||
1416         flags & TerrainBlock::EmptyUpdate )
1417   {
1418      // Convert the min and max into world space.
1419      const F32 size = tblock->getSquareSize();
1420      const Point3F pos = tblock->getPosition();
1421
1422      // TODO: I don't think this works right with tiling!
1423      Box3F dirty(   F32( min.x * size ) + pos.x, F32( min.y * size ) + pos.y, 0.0f,
1424                     F32( max.x * size ) + pos.x, F32( max.y * size ) + pos.y, 0.0f );
1425      
1426      // Now free any cells that overlap it!
1427      for ( S32 i = 0; i < mCellGrid.size(); i++ )
1428      {
1429         GroundCoverCell* cell = mCellGrid[ i ];
1430         if ( !cell )
1431            continue;
1432
1433         const Box3F& bounds = cell->getBounds();
1434         dirty.minExtents.z = bounds.minExtents.z;
1435         dirty.maxExtents.z = bounds.maxExtents.z;
1436         if ( bounds.isOverlapped( dirty ) )
1437         {
1438            mCellGrid[ i ] = NULL;
1439            _recycleCell( cell );
1440         }
1441      }
1442   }
1443}
1444
1445void GroundCover::_updateCoverGrid( const Frustum &culler )
1446{
1447   PROFILE_SCOPE( GroundCover_UpdateCoverGrid );
1448   
1449   mGridSize = getMax( mGridSize, (U32)2 );
1450
1451   // How many cells in the grid?
1452   const U32 cells = mGridSize * mGridSize;
1453
1454   // Whats the max placement count for each cell considering 
1455   // the grid size and quality scale LOD value.
1456   const S32 placementCount = getMax( ( (F32)mMaxPlacement * smDensityScale ) / F32( mGridSize * mGridSize ), 0.0f );
1457
1458   // If the cell grid isn't sized or the placement count
1459   // changed (most likely because of quality lod) then we
1460   // need to initialize the system again.
1461   if ( mCellGrid.empty() || placementCount != mLastPlacementCount )
1462   {
1463      _initialize( cells, placementCount );
1464      mLastPlacementCount = placementCount;
1465   }
1466
1467   // Without a count... we don't function at all.
1468   if ( placementCount == 0 )
1469      return;
1470
1471   // Clear the scratch grid.
1472   dMemset( mScratchGrid.address(), 0, mScratchGrid.memSize() );
1473
1474   // Calculate the normal cell size here.
1475   const F32 cellSize = ( mRadius * 2.0f ) / (F32)(mGridSize - 1);
1476
1477   // Figure out the root index of the new grid based on the camera position.
1478   Point2I index( (S32)mFloor( ( culler.getPosition().x - mRadius ) / cellSize  ),
1479                  (S32)mFloor( ( culler.getPosition().y - mRadius ) / cellSize ) );
1480
1481   // Figure out the cell shift between the old and new grid positions.
1482   Point2I shift = mGridIndex - index;
1483
1484   // If we've shifted more than one in either axis then we've warped.
1485   bool didWarp = shift.x > 1 || shift.x < -1 || 
1486                  shift.y > 1 || shift.y < -1 ? true : false;
1487
1488   // Go thru the grid shifting each cell we find and
1489   // placing them in the scratch grid.
1490   for ( S32 i = 0; i < mCellGrid.size(); i++ )
1491   {
1492      GroundCoverCell* cell = mCellGrid[ i ];
1493      if ( !cell )
1494         continue;
1495
1496      // Whats our new index?
1497      Point2I newIndex = cell->shiftIndex( shift );
1498
1499      // Is this cell outside of the new grid?
1500      if (  newIndex.x < 0 || newIndex.x >= mGridSize ||
1501            newIndex.y < 0 || newIndex.y >= mGridSize )
1502      {
1503         _recycleCell( cell );
1504         continue;
1505      }
1506
1507      // Place the cell in the scratch grid.
1508      mScratchGrid[ ( newIndex.y * mGridSize ) + newIndex.x ] = cell;
1509   }
1510
1511   // Get the terrain elevation range for setting the default cell bounds.
1512   F32   terrainMinHeight = -5000.0f, 
1513         terrainMaxHeight = 5000.0f;
1514
1515   // Go thru the scratch grid copying each cell back to the
1516   // cell grid and creating new cells as needed.
1517   //
1518   // By limiting ourselves to only one new cell generation per
1519   // update we're lowering the performance hiccup during movement
1520   // without getting into the complexity of threading.  The delay
1521   // in generation is rarely noticeable in normal play.
1522   //
1523   // The only caveat is that we need to generate the entire visible
1524   // grid when we warp.
1525   U32 cellsGenerated = 0;
1526   for ( S32 i = 0; i < mScratchGrid.size(); i++ )
1527   {
1528      GroundCoverCell* cell = mScratchGrid[ i ];
1529      if ( !cell && ( cellsGenerated == 0 || didWarp ) )
1530      {
1531         // Get the index point of this new cell.
1532         S32 y = i / mGridSize;
1533         S32 x = i - ( y * mGridSize );
1534         Point2I newIndex = index + Point2I( x, y );
1535
1536         // What will be the world placement bounds for this cell.
1537         Box3F bounds;
1538         bounds.minExtents.set( newIndex.x * cellSize, newIndex.y * cellSize, terrainMinHeight );
1539         bounds.maxExtents.set( bounds.minExtents.x + cellSize, bounds.minExtents.y + cellSize, terrainMaxHeight );
1540
1541         if ( mCuller.isCulled( bounds ) )
1542         {
1543            mCellGrid[ i ] = NULL;
1544            continue;
1545         }
1546
1547         // We need to allocate a new cell.
1548         //
1549         // TODO: This is the expensive call and where we should optimize. In
1550         // particular the next best optimization would be to take advantage of
1551         // multiple cores so that we can generate all the cells in one update.
1552         //
1553         // Instead of generating the cell here we would allocate a cell and stick
1554         // it into a thread safe queue (maybe lockless) as well as the mCellGrid.
1555         // Once all were allocated we would do something like this...
1556         //
1557         // TorqueParallelProcess( cellsToGenerateQueue, _generateCell );
1558         //
1559         // Internally this function would pass the queue to some global pre-allocated
1560         // worker threads which are locked to a particular core.  While the main 
1561         // thread waits for the worker threads to finish it will process cells itself.
1562         // 
1563
1564         cell = _generateCell(   newIndex - index, 
1565                                 bounds, 
1566                                 placementCount, 
1567                                 mRandomSeed + mAbs( newIndex.x ) + mAbs( newIndex.y ) );
1568
1569         // Increment our generation count.
1570         if ( cell )
1571            ++cellsGenerated;
1572      }
1573
1574      mCellGrid[ i ] = cell;
1575   }
1576
1577   // Store the new grid index.
1578   mGridIndex = index;
1579}
1580
1581void GroundCover::prepRenderImage( SceneRenderState *state )
1582{
1583   // Reset stats each time we hit the diffuse pass.
1584   if (mMatInst == nullptr)
1585      return;
1586
1587   if( state->isDiffusePass() )
1588   {
1589      smStatRenderedCells = 0;
1590      smStatRenderedBillboards = 0;
1591      smStatRenderedBatches = 0;
1592      smStatRenderedShapes = 0;
1593   }
1594
1595   // TODO: Make sure that the ground cover stops rendering
1596   // if you're inside a zoned interior.
1597
1598   if ( state->isReflectPass() && mReflectRadiusScale <= 0.0f )
1599      return;
1600
1601   // Nothing to do if this is a shadow pass and shapes in this GoundCover
1602   // should not be casting shadows.
1603
1604   if( state->isShadowPass() && !mShapesCastShadows )
1605      return;
1606
1607   GFXTransformSaver saver;
1608
1609   // Setup the frustum culler.
1610   if ( ( mCuller.getPosition().isZero() || !mDebugLockFrustum ) && !state->isShadowPass() )
1611      mCuller = state->getCullingFrustum();
1612
1613   // Update the cells, but only during the diffuse pass. 
1614   // We don't want cell generation to thrash when the reflection camera 
1615   // position doesn't match the diffuse camera!
1616   if ( state->isDiffusePass() )
1617      _updateCoverGrid( mCuller );
1618
1619   // Render billboards but not into shadow passes.
1620
1621   if ( !state->isShadowPass() && mMatInst->isValid() && !mDebugNoBillboards )
1622   {
1623      PROFILE_SCOPE( GroundCover_RenderBillboards );
1624
1625      // Take zoom into account.
1626      F32 screenScale = state->getWorldToScreenScale().y / state->getViewport().extent.y;
1627
1628      // Set the far distance for billboards.
1629     F32 radius = mRadius * smFadeScale;
1630      mCuller.setFarDist(radius * screenScale );
1631
1632      F32 cullScale = 1.0f;
1633      if ( state->isReflectPass() )
1634         cullScale = mReflectRadiusScale;
1635      
1636      // Setup our shader const data.
1637      // Must be done prior to submitting our render instance.
1638
1639      mShaderConstData.fadeInfo.set( mFadeRadius * smFadeScale * cullScale * screenScale, radius * cullScale * screenScale );
1640
1641      const F32 simTime = Sim::getCurrentTime() * 0.001f;
1642
1643      mShaderConstData.gustInfo.set( mWindGustLength, mWindGustFrequency * simTime, mWindGustStrength );
1644      mShaderConstData.turbInfo.set( mWindTurbulenceFrequency * simTime, mWindTurbulenceStrength );      
1645
1646      // Use the camera's forward vector to calculate the camera's right
1647      // and up vectors.  This removes any camera banking from affecting
1648      // the ground cover.
1649      const MatrixF &camMat = state->getDiffuseCameraTransform();
1650      Point3F camDir, camUp, camRight;
1651      camMat.getColumn( 1, &camDir );
1652      mCross( camDir, Point3F::UnitZ, &camRight );
1653      if ( camRight.magnitudeSafe() == 0.0f )
1654      {
1655         camRight.set( 0.0f, -1.0f, 0.0f );
1656      }
1657      camRight.normalizeSafe();
1658      mCross( camRight, camDir, &camUp );
1659
1660      // Limit the camera up vector to keep the billboards 
1661      // from leaning too far down into the terrain.
1662      VectorF lookDir( camDir.x, camDir.y, 0.0f );
1663      F32 angle;
1664      if ( !lookDir.isZero() )
1665      {
1666         lookDir.normalize();
1667         angle = mAcos( mDot( camUp, lookDir ) );
1668      }
1669      else
1670      {
1671         angle = camDir.z < 0.0f ? 0.0f : ( M_PI_F / 2.0f );
1672      }
1673
1674      const F32 maxBillboardTiltRads = mDegToRad( mMaxBillboardTiltAngle );
1675      if ( angle < (M_PI_F / 2.0f) - maxBillboardTiltRads )
1676      {
1677         QuatF quat( AngAxisF( camRight, maxBillboardTiltRads ) );
1678         quat.mulP( VectorF( 0.0f, 0.0f, 1.0f ), &camUp );
1679      }
1680
1681      mShaderConstData.camRight = camRight;
1682      mShaderConstData.camUp = camUp;      
1683
1684
1685      // Cull and submit render instances for cells.
1686
1687      for ( S32 i = 0; i < mCellGrid.size(); i++ )
1688      {
1689         GroundCoverCell* cell = mCellGrid[ i ];
1690         if ( !cell )
1691            continue;
1692
1693         if ( mCuller.isCulled( cell->getRenderBounds() ) )
1694            continue;
1695
1696         cell->renderBillboards( state, mMatInst, &mPrimBuffer );
1697      }     
1698   }
1699
1700   // Render TS shapes.
1701
1702   if ( !mDebugNoShapes )
1703   {
1704      // Prepare to render the grid shapes.
1705      PROFILE_SCOPE(GroundCover_RenderShapes);
1706
1707      // Set up our TS render state.
1708      TSRenderState rdata;
1709      rdata.setSceneState( state );
1710
1711      // We might have some forward lit materials
1712      // so pass down a query to gather lights.
1713      LightQuery query;
1714      rdata.setLightQuery( &query );
1715
1716      // TODO: Add a special fade out for DTS?
1717      mCuller.setFarDist( mShapeCullRadius*smFadeScale);
1718
1719      for ( S32 i = 0; i < mCellGrid.size(); i++ )
1720      {
1721         GroundCoverCell* cell = mCellGrid[ i ];
1722         if ( !cell || mDebugNoShapes )
1723            continue;
1724
1725         const Box3F &renderBounds = cell->getRenderBounds();
1726         U32 clipMask = mCuller.testPlanes( renderBounds, Frustum::PlaneMaskAll );
1727         if ( clipMask == -1 )
1728            continue;
1729
1730         smStatRenderedCells++;
1731
1732         // Use the cell bounds as the light query volume.
1733         //
1734         // This means all forward lit items in this cell will 
1735         // get the same lights, but it performs much better.
1736         query.init( renderBounds );
1737
1738         // Render the shapes in this cell... only pass the culler if the
1739         // cell wasn't fully within the frustum.
1740         smStatRenderedShapes += cell->renderShapes(  
1741                                    rdata, 
1742                                    clipMask != 0 ? &mCuller : NULL, 
1743                                    mShapeInstances );
1744      }
1745   }
1746
1747   if ( mDebugRenderCells )
1748   {
1749      RenderPassManager *pass = state->getRenderPass();
1750
1751      ObjectRenderInst *ri = pass->allocInst<ObjectRenderInst>();
1752      ri->type = RenderPassManager::RIT_Editor;
1753      ri->renderDelegate.bind( this, &GroundCover::_debugRender );
1754      pass->addInst( ri );
1755   }    
1756}
1757
1758void GroundCover::_debugRender( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat )
1759{   
1760   GFXDrawUtil* drawer = GFX->getDrawUtil();
1761 
1762   GFXStateBlockDesc desc;
1763   desc.setZReadWrite( true, false );
1764   desc.setBlend( true );
1765   desc.fillMode = GFXFillWireframe;
1766
1767   for ( S32 i = 0; i < mCellGrid.size(); i++ )
1768   {
1769      GroundCoverCell* cell = mCellGrid[ i ];
1770      if ( !cell || ( cell->mBillboards.size() + cell->mShapes.size() ) == 0 )
1771         continue;
1772
1773      if ( mCuller.isCulled( cell->getRenderBounds() ) )
1774         continue;
1775
1776      drawer->drawCube( desc, cell->getRenderBounds().getExtents(), cell->getRenderBounds().getCenter(), ColorI( 0, 255, 0 ) );
1777   }
1778}
1779