Game Development Community

GroundCover Atlas Changes

by J.C. Smith · in Torque Game Engine Advanced · 03/19/2008 (8:40 am) · 31 replies

DISCLAIMER: This is unoptimized code and a lot of areas are just hacked in where they should really get their own settings which can be read in. But I just did this port quickly today while porting the 1.7 changes into the Repopulation client. I had to disable the lightmap stuff also unfortunately due to differences between Atlas and legacy terrain, though I may add a method for that later this week if I have time. But this will get the groundcover changes working for you on Atlas... at the cost of removing the ability to use it on Legacy. I'm tired and lazy so just posted it in here in its entirety.

groundcover.h code:

//-----------------------------------------------------------------------------
// Torque Ground Cover
// Copyright (C) Sickhead Games, LLC
//-----------------------------------------------------------------------------

#ifndef _GROUNDCOVER_H_
#define _GROUNDCOVER_H_

#ifndef _SCENEOBJECT_H_
   #include "sceneGraph/sceneObject.h"
#endif
#ifndef _FRUSTRUMCULLER_H_
   #include "util/frustrumCuller.h"
#endif
#ifndef _GFXTEXTUREHANDLE_H_
   #include "gfx/gfxTextureHandle.h"
#endif
#ifndef _GFX_GFXPRIMITIVEBUFFER_H_
   #include "gfx/gfxPrimitiveBuffer.h"
#endif
//jc
#ifndef _ATLASINSTANCE2_H_
   #include "atlas/runtime/atlasinstance2.h"
#endif

//class TerrainBlock; //jc
class GroundCoverCell;
class ShaderData;
class TSShapeInstance;
class LightAllocator;


class GroundCover : public SceneObject
{
public:
   GroundCover();

   DECLARE_CONOBJECT(GroundCover);

   static void consoleInit();
   static void initPersistFields();

   bool onAdd();
   void onRemove();
   void inspectPostApply();

   // Network
   U32 packUpdate( NetConnection *, U32 mask, BitStream *stream );
   void unpackUpdate( NetConnection *, BitStream *stream );

   // Rendering
   bool prepRenderImage( SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseState );
   void renderObject( SceneState *state, RenderInst *ri );

   // Editor
   void onTerrainUpdated( U32 flags, const Point2I& min, const Point2I& max ); //jc

   /// Sets the global ground cover LOD scalar which controls
   /// the percentage of the maximum designed cover to put down.
   /// It scales both rendering cost and placement CPU performance.
   /// Returns the actual value set.
   static F32 setQualityScale( F32 scale ) { return smQualityScale = mClampF( scale, 0.0f, 1.0f ); }

   /// Returns the current quality scale... see above.
   static F32 getQualityScale() { return smQualityScale; }

protected:
   friend class GroundCoverCell;

   typedef SceneObject Parent;

   enum
   {
      /// This is the max number of ground cover types allowed.
      NumCoverTypes = 6,      
   };

   /// This RNG seed is saved and sent to clients
   /// for generating the same cover.
   S32 mRandomSeed;

   /// The name of the terrain object which we're
   /// generating ground cover for.
   StringTableEntry mTerrainName;

   /// The ghost id of the terrain object on the client side.
   U32 mTerrainGhostId;

   /// The terrain object we're generating ground cover on.
   SimObjectPtr<AtlasInstance> mTerrainBlock;

   /// This is the outer generation radius from
   /// the current camera position.
   F32 mRadius;

   // Offset along the Z axis to render the ground cover.
   F32 mZOffset;

   /// This is less than or equal to mRadius and
   /// defines when fading of cover elements begins.
   F32 mFadeRadius;
Page «Previous 1 2
#1
03/19/2008 (8:41 am)
/// This is the distance at which DTS elements are 
   /// completely culled out.
   F32 mShapeCullRadius;

   /// This is used to scale the various culling radii 
   /// when rendering a reflection... typically for water.
   F32 mReflectRadiusScale;

   /// This is the number of cells per axis in the grid.
   U32 mGridSize;

   typedef Vector<GroundCoverCell*> CellVector;

   /// This is the allocator for GridCell chunks.
   CellVector mAllocCellList;
   CellVector mFreeCellList;

   /// This is the grid of active cells.
   CellVector mCellGrid;

   /// This is a scratch grid used while updating
   /// the cell grid.
   CellVector mScratchGrid;

   /// This is the index to the first grid cell.
   Point2I mGridIndex;

   /// The maximum amount of cover elements to include in
   /// the grid at any one time.  The actual amount may be
   /// less than this based on randomization.
   S32 mMaxPlacement;

   /// Used to detect changes in cell placement count from 
   /// the global quality scale so we can regen the cells.
   S32 mLastPlacementCount;

   /// Used for culling cells to update and render.
   FrustrumCuller mCuller;

   /// The shader used to render the ground cover billboards.
   ShaderData *mBBShader;

   /// Debug parameter for displaying the grid cells.
   bool mDebugRenderCells;

   /// Debug parameter for turning off billboard rendering.
   bool mDebugNoBillboards;

   /// Debug parameter for turning off shape rendering.
   bool mDebugNoShapes;

   /// Debug parameter for locking the culling frustum which
   /// will freeze the cover generation.
   bool mDebugLockFrustum;

   /// Stat for number of rendered cells.
   static U32 smStatRenderedCells;

   /// Stat for number of rendered billboards.
   static U32 smStatRenderedBillboards;

   /// Stat for number of rendered billboard batches.
   static U32 smStatRenderedBatches;

   /// Stat for number of rendered shapes.
   static U32 smStatRenderedShapes;

   /// Used to detect when to reset the stats.
   static U32 smLastState;

   /// The global ground cover LOD scalar which controls
   /// the percentage of the maximum amount of cover to put
   /// down.  It scales both rendering cost and placement
   /// CPU performance.
   static F32 smQualityScale;

   /// The name of the texture atlas of the cover billboards.
   StringTableEntry mTextureName;

   /// The texture atlas of the cover billboards.
   GFXTexHandle mTexture;

   /// This is the maximum amout of degrees the billboard will
   /// tilt down to match the camera.
   F32 mMaxBillboardTiltAngle;

   /// The probability of one cover type verses another.
   F32 mProbability[NumCoverTypes];

   /// The minimum random size for each cover type.
   F32 mSizeMin[NumCoverTypes];

   /// The maximum random size of this cover type.
   F32 mSizeMax[NumCoverTypes];

   /// An exponent used to bias between the minimum
   /// and maximum random sizes.
   F32 mSizeExponent[NumCoverTypes];

   /// The wind effect scale.
   F32 mWindScale[NumCoverTypes];

   /// The maximum slope angle in degrees for placement.
   F32 mMaxSlope[NumCoverTypes];

   /// The minimum world space elevation for placement.
   F32 mMinElevation[NumCoverTypes];

   /// The maximum world space elevation for placement.
   F32 mMaxElevation[NumCoverTypes];

   /// The integer terrain data layer or -1 to not
   /// test placement against one.
   S32 mLayer[NumCoverTypes];

   /// Inverts the data layer test making the 
   /// layer an exclusion mask.
   bool mInvertLayer[NumCoverTypes];

   /// The minimum amount of elements in a clump.
   S32 mMinClumpCount[NumCoverTypes];

   /// The maximum amount of elements in a clump.
   S32 mMaxClumpCount[NumCoverTypes];

   /// An exponent used to bias between the minimum
   /// and maximum clump counts for a particular clump.
   F32 mClumpCountExponent[NumCoverTypes];

   /// The maximum clump radius.
   F32 mClumpRadius[NumCoverTypes];

   /// The billboard atlas texture uvs.
   RectF mBillboardRects[NumCoverTypes];

   /// This is a cached array of billboard aspect scales
   /// used to avoid some calculations when generating cells.
   F32 mBillboardAspectScales[NumCoverTypes];

   /// The cover shape filenames.
   StringTableEntry mShapeFilenames[NumCoverTypes];

   /// The cover shape instances.
   TSShapeInstance* mShapeInstances[NumCoverTypes];

   /// This is the same as mProbability, but normalized for use
   /// during the cover placement process. 
   F32 mNormalizedProbability[NumCoverTypes];

   /// A shared primitive buffer setup for drawing the maximum amount
   /// of billboards you could possibly have in a single cell.
   GFXPrimitiveBufferHandle mPrimBuffer;

   /// The lighting system requires that you have an allocated
   /// light per rendered shape and that it persist until the
   /// render instance manager finishes with it.  So this deals
   /// with tracking all the light allocations per render call.
   LightAllocator* mLightAllocator;

   /// The length in meters between peaks in the wind gust.
   F32 mWindGustLength;

   /// Controls how often the wind gust peaks per second.
   F32 mWindGustFrequency;

   /// The maximum distance in meters that the peak wind 
   /// gust will displace an element.
   F32 mWindGustStrength;

   /// The direction of the wind.
   Point2F mWindDirection;

   /// Controls the overall rapidity of the wind turbulence.
   F32 mWindTurbulenceFrequency;

   /// The maximum distance in meters that the turbulence can
   /// displace a ground cover element.
   F32 mWindTurbulenceStrength;

   void _initShapes();

   void _deleteShapes();

   /// Called when GroundCover parameters are changed and
   /// things need to be reinitialized to continue.
   void _initialize( U32 cellCount, U32 cellPlacementCount );

   /// Updates the cover grid by removing cells that
   /// have fallen outside of mRadius and adding new 
   /// ones that have come into view.
   void _updateCoverGrid( const FrustrumCuller& culler );

   /// Clears the cell grid, moves all the allocated cells to
   /// the free list, and deletes excess free cells.
   void _freeCells();

   /// Clears the cell grid and deletes all the free cells.
   void _deleteCells();

   /// Returns a cell to the free list.
   void _recycleCell( GroundCoverCell* cell );

   /// Generates a new cell using the recycle list when possible.
   GroundCoverCell* _generateCell(  const Point2I& index,
                                    const Box3F& bounds, 
                                    U32 placementCount,
                                    S32 randSeed );
};

#endif // _GROUNDCOVER_H_
#2
03/19/2008 (8:43 am)
Groundcover.cpp code:

//-----------------------------------------------------------------------------
// Torque Ground Cover
// Copyright (C) Sickhead Games, LLC
//-----------------------------------------------------------------------------

#include "platform/platform.h"
#include "T3D/fx/groundCover.h"

#include "core/bitStream.h"
#include "console/consoleTypes.h"
#include "sceneGraph/sceneState.h"
#include "terrain/terrData.h"
#include "renderInstance/renderInstMgr.h"
#include "gfx/gfxDrawUtil.h"
#include "gfx/primBuilder.h"
#include "T3D/gameConnection.h"
#include "gfx/gfxVertexBuffer.h"
#include "gfx/gfxStructs.h"
#include "ts/tsShapeInstance.h"
#include "sceneGraph/lightManager.h"
#include "sceneGraph/lightInfo.h"
#include "sceneGraph/lightAllocator.h"
#include "T3D/fx/windEmitter.h"
#include "atlas/runtime/atlasinstance2.h" //jc


//#define FXFOLIAGEREPLICATOR_COLLISION_MASK   (   AtlasObjectType       |   \
//                                                 InteriorObjectType    |   \
//                                                 StaticObjectType      |   \
//                                                 WaterObjectType      )

//Fix this path
#define GROUND_COVER_SHADER_CONSTANT_INCLUDE_FILE "../../../../GameExamples/T3D/game/shaders/legacyTerrain/groundCover.h"
#include GROUND_COVER_SHADER_CONSTANT_INCLUDE_FILE


// This class acts as a dummy SceneObject which will
// pass rendering state down for TSMesh rendering.
class TSSceneObjectHelper : public SceneObject
{
public:
   TSSceneObjectHelper() 
   {
   }

   void setState( const Box3F& objBox, const MatrixF& worldMat, const VectorF& scale )
   {
      mObjBox = objBox;
      mRenderObjToWorld = worldMat;
      mRenderWorldToObj = worldMat;
      mRenderWorldToObj.affineInverse();
      mObjScale = scale;
   }
};


/// This is used for rendering ground cover billboards.
DEFINE_VERT( GCVertex, 
   GFXVertexFlagXYZ | GFXVertexFlagDiffuse |
   GFXVertexFlagTextureCount1 | GFXVertexFlagUVWQ0 )
{
   Point3F point;

   // .rgb = ambient
   // .a = corner index
   GFXVertexColor ambient;

   // .x = size x
   // .y = size y
   // .z = type
   // .w = wind amplitude
   Point4F params;
};

/// This defines one grid cell.
class GroundCoverCell
{
protected:

   friend class GroundCover;

   struct Placement
   {
      Point3F     point;
      Point3F     size;
      F32         rotation;
      U32         type;
      F32         windAmplitude;
      Box3F       worldBox;
      ColorF      lmColor;
   };

   /// This is the x,y index for this cell.
   Point2I mIndex;

   /// The worldspace bounding box this cell.
   Box3F mBounds;

   /// The worldspace bounding box of the renderable
   /// content within this cell.
   Box3F mRenderBounds;

   /// The instances of billboard cover elements in this cell.
   Vector<Placement> mBillboards;

   /// The instances of shape cover elements in this cell.
   Vector<Placement> mShapes;

   typedef GFXVertexBufferHandle<GCVertex> VBHandle;
   typedef Vector< VBHandle > VBHandleVector;

   /// The vertex buffers that hold all the 
   /// prepared billboards for this cell.
   VBHandleVector mVBs;

   /// Used to mark the cell dirty and in need
   /// of a rebuild.
   bool mDirty;

   /// Repacks the billboards into the vertex buffer.
   void _rebuildVB();
#3
03/19/2008 (8:44 am)
public:

   GroundCoverCell() {}

   ~GroundCoverCell() 
   {
      mVBs.clear();
   }

   const Point2I& shiftIndex( const Point2I& shift ) { return mIndex += shift; }
   
   /// The worldspace bounding box this cell.
   const Box3F& getBounds() const { return mBounds; }

   /// The worldspace bounding box of the renderable
   /// content within this cell.
   const Box3F& getRenderBounds() const { return mRenderBounds; }

   Point3F getCenter() const { return ( mBounds.min + mBounds.max ) / 2.0f; }

   VectorF getSize() const { return VectorF( mBounds.len_x() / 2.0f,
                                             mBounds.len_y() / 2.0f,
                                             mBounds.len_z() / 2.0f ); }
   
   static void initRender( GFXShader* shader, 
                           const MatrixF& camMat,
                           F32 startFade, 
                           F32 endFade,
                           F32 maxBillboardTiltRads,
                           GFXTexHandle& texture,
                           const RectF coverRect[6] );

   /// Renders all the billboard batches returning the 
   /// total billboards rendered.
   U32 renderBillboards(   const LightInfoList& lights,
                           const WindEmitterList& emitters,
                           GFXPrimitiveBufferHandle& primBuffer );

   U32 renderShapes(    SceneState *state, 
                        FrustrumCuller* culler, 
                        TSSceneObjectHelper& helper,
                        LightAllocator* lightAllocator,
                        TSShapeInstance** shapes );
};

void GroundCoverCell::_rebuildVB()
{
   if ( mBillboards.empty() )
   {
      return;
   }

   PROFILE_SCOPE(GroundCover_RebuildVB);

   // The maximum verts we can put in one vertex buffer batch.
   const U32 MAX_BILLBOARDS = 0xFFFF / 4;

   // How many batches will we need in total?
   const U32 batches = mCeil( (F32)mBillboards.size() / (F32)MAX_BILLBOARDS );

   // So... how many billboards do we need in
   // each batch? We're trying to evenly divide
   // the amount across all the VBs.
   const U32 batchBB = mBillboards.size() / batches;

   // Init the vertex buffer list to the right size.  Any
   // VBs already in there will remain unless we're truncating
   // the list... those are freed.
   mVBs.setSize( batches ); 

   // Get the iter to the first billboard.
   Vector<Placement>::const_iterator iter = mBillboards.begin();

   // Prepare each batch.
   U32 bb, remaining = mBillboards.size();
   for ( U32 b = 0; b < batches; b++ )
   {
      // Grab a reference to the vb.
      VBHandle &vb = mVBs[b];

      // How many billboards in this batch?
      bb = getMin( batchBB, remaining );
      remaining -= bb;

      // Ok... now how many verts is that?
      const U32 verts = bb * 4;

      // Create the VB hasn't been created or if its
      // too small then resize it.
      if ( vb.isNull() || vb->mNumVerts < verts )
      {
         PROFILE_START(GroundCover_CreateVB);
         vb.set( GFX, verts, GFXBufferTypeStatic );
         PROFILE_END();
      }

      // Fill this puppy!
      GCVertex* vertPtr = vb.lock( 0, verts );
      
      GFXVertexColor color;

      Vector<Placement>::const_iterator last = iter + bb;
      for ( ; iter != last; iter++ )
      {
         const Point3F &position = (*iter).point;
         const S32 &type = (*iter).type;
         const Point3F &size = (*iter).size;
         const F32 &windAmplitude = (*iter).windAmplitude;
         GFXVertexColor color = (ColorI)(*iter).lmColor;
         U8 *col = (U8 *)const_cast<U32 *>( (const U32 *)color );

         vertPtr->point = position;
         vertPtr->params.x = size.x;
         vertPtr->params.y = size.y;
         vertPtr->params.z = type;
         vertPtr->params.w = 0;
         col[3] = 0;
         vertPtr->ambient = color;
         ++vertPtr;

         vertPtr->point = position;
         vertPtr->params.x = size.x;
         vertPtr->params.y = size.y;
         vertPtr->params.z = type;
         vertPtr->params.w = 0;
         col[3] = 1;
         vertPtr->ambient = color;
         ++vertPtr;

         vertPtr->point = position;
         vertPtr->params.x = size.x;
         vertPtr->params.y = size.y;
         vertPtr->params.z = type;
         vertPtr->params.w = windAmplitude;
         col[3] = 2;
         vertPtr->ambient = color;
         ++vertPtr;

         vertPtr->point = position;
         vertPtr->params.x = size.x;
         vertPtr->params.y = size.y;
         vertPtr->params.z = type;
         vertPtr->params.w = windAmplitude;
         col[3] = 3;
         vertPtr->ambient = color;
         ++vertPtr;
      }

      vb.unlock();
   }
}


void GroundCoverCell::initRender(   GFXShader* shader, 
                                    const MatrixF& camMat, 
                                    F32 startFade, 
                                    F32 endFade,
                                    F32 maxBillboardTiltRads,
                                    GFXTexHandle& texture,
                                    const RectF coverRect[6] )
{
   PROFILE_SCOPE(GroundCoverCell_initRender);

   GFX->setTextureStageAddressModeU( 0, GFXAddressClamp );
   GFX->setTextureStageAddressModeV( 0, GFXAddressClamp );
   GFX->setTextureStageAddressModeU( 1, GFXAddressClamp );
   GFX->setTextureStageAddressModeV( 1, GFXAddressClamp );

   GFX->setStencilEnable( false );

   GFX->setZEnable( true );
   
   GFX->setAlphaBlendEnable( false );
   GFX->setAlphaTestEnable( true );
   GFX->setAlphaRef( 84 );
   GFX->setAlphaFunc( GFXCmpGreater );
 
   GFX->setCullMode( GFXCullNone );

   // Setup the shader.
   GFX->setShader( shader );


   // Get the data we need from the camera matrix.
   MatrixF camMatTrans = camMat;
   camMat.transposeTo( camMatTrans );

   const VectorF *camRight = (VectorF *)&camMatTrans[0];
   const VectorF *camDir = (VectorF *)&camMatTrans[4];
   VectorF *camUp = (VectorF *)&camMatTrans[8];

   // Limit the camera up vector to keep the billboards 
   // from leaning too far down into the terrain.
   VectorF lookDir( camDir->x, camDir->y, 0.0f );
   F32 angle;
   if ( !lookDir.isZero() )
   {
      lookDir.normalize();
      angle = mAcos( mDot( *camUp, lookDir ) );
   }
   else
   {
      angle = camDir->z < 0.0f ? 0.0f : ( M_PI_F / 2.0f );
   }

   if ( angle < (M_PI_F / 2.0f) - maxBillboardTiltRads )
   {
      PROFILE_SCOPE(GroundCoverCell_QuatF_STUFF);
      QuatF quat( AngAxisF( *camRight, maxBillboardTiltRads ) );
      quat.mulP( VectorF( 0.0f, 0.0f, 1.0f ), camUp );
   }

   // Setup the shader consts for the camera.
   GFX->setVertexShaderConstF( 6, camMatTrans, 4 );

   // Setup the fade parameters.
   Point4F fadeParams( startFade, endFade, 0.0f, 0.0f );
   GFX->setVertexShaderConstF( 10, (F32*)&fadeParams, 1 );

   // Setup the texture.
   GFX->setTexture( 0, texture );

   // Pass the cover rects.
   GFX->setVertexShaderConstF( 60, (F32*)coverRect, 6 );
}
#4
03/19/2008 (8:45 am)
U32 GroundCoverCell::renderShapes(  SceneState *state, 
                                    FrustrumCuller* culler, 
                                    TSSceneObjectHelper& helper, 
                                    LightAllocator* lightAllocator,
                                    TSShapeInstance** shapes )
{
   MatrixF worldMat;
   TSShapeInstance* shape;
   Point3F camVector;
   F32 dist;
   F32 invScale;

   const S32 cullerMasks = FrustrumCuller::ClipPlaneMask | FrustrumCuller::FarSphereMask;

   U32 totalRendered = 0;

   Vector<Placement>::const_iterator iter = mShapes.begin();
   for ( ; iter != mShapes.end(); iter++ )
   {
      // Grab a reference here once.
      const Placement& inst = (*iter);

      // If we were pass a culler then us it to test the shape world box.
      if ( culler && culler->testBoxVisibility( inst.worldBox, cullerMasks, 0 ) == -1 )
         continue;

      shape = shapes[ inst.type ];

      camVector = inst.point - state->getCameraPosition();
      dist = getMax( camVector.len(), 0.01f );

      worldMat.set( EulerF(0, 0, inst.rotation), inst.point );
      helper.setState( shape->getShape()->bounds, worldMat, inst.size );

      // TSShapeInstance::render() uses the 
      // world matrix for the RenderInst.
      worldMat.scale( inst.size );
      GFX->setWorldMatrix( worldMat );

      // Obey the normal screen space lod metrics.  The shapes should
      // be tuned to lod out quickly for ground cover.
      //
      // Note: The profile doesn't indicate that lod selection is
      // very expensive... in fact its less than 1/10th of the cost 
      // of the render() call below.
      PROFILE_START(GroundCover_RenderShapes_SelectDetail);

         invScale = (1.0f/getMax(getMax(inst.size.x,inst.size.y),inst.size.z));
         shape->selectCurrentDetail2( dist * invScale );

      PROFILE_END(); // GroundCover_RenderShapes_SelectDetail
   
      // Note: This is the most expensive call of this loop.  We 
      // need to rework the render call completely to optimize it.
      PROFILE_START(GroundCover_RenderShapes_Render);

         shape->render();

      PROFILE_END(); // GroundCover_RenderShapes_Render

      totalRendered++;
   }

   return totalRendered;
}

U32 GroundCoverCell::renderBillboards( const LightInfoList& lights, 
                                       const WindEmitterList& emitters,
                                       GFXPrimitiveBufferHandle& primBuffer )
{
   if ( mDirty )
   {
      _rebuildVB();
      mDirty = false;
   }

   // Do we have anything to render?
   if ( mBillboards.size() == 0 || mVBs.empty() )
   {
      return 0;
   }

   // Setup two lights for the shader to process... 
   ColorF lightColor;
   Point3F lightPosition;
   F32 oneOverLightRadius;
   for ( S32 i=0; i < MAX_POINT_LIGHTS; i++ )
   {
      // Collect the light data.
      LightInfo* light = lights.size() > i ? lights[i] : NULL;
      if ( !light || !( light->mType == LightInfo::Point || light->mType == LightInfo::SGStaticPoint ) || light->mRadius <= 0.001f )
      {
         // We have no light here... set the radius to
         // zero to disable it in the shader.
         oneOverLightRadius = 0;
      }
      else
      {
         lightColor = light->mColor;
         lightPosition = light->mPos;
         oneOverLightRadius = 1.0f / light->mRadius;
      }

      // Setup the shader!
      const U32 startReg = 0;
      GFX->setPixelShaderConstF( startReg + (i*3) + 0, (F32*)&lightColor, 1 );
      GFX->setPixelShaderConstF( startReg + (i*3) + 1, (F32*)&lightPosition, 1 );
      GFX->setPixelShaderConstF( startReg + (i*3) + 2, (F32*)&oneOverLightRadius, 1 );
   }

   // Setup the wind emitters!
   const F32 simTime = Sim::getCurrentTime() * 0.001f;
   for ( S32 i=0; i < MAX_WIND_EMITTERS; i++ )
   {
      // Collect a sphere.
      WindEmitter* sphere = emitters.size() > i ? emitters[i] : NULL;
      Point4F placement( 0.0f, 0.0f, 0.0f, 0.0f );
      Point4F effect( 0.0f, 0.0f, 0.0f, 0.0f );
      if ( sphere && sphere->getRadius() > 0.0f )
      {
         placement = sphere->getCenter();
         placement.w = 1.0f / sphere->getRadius();
         effect.x = sphere->getStrength();
         effect.y = sphere->getTurbulenceFrequency() * simTime;
         effect.z = sphere->getTurbulenceStrength();
      }

      // We pack the wind emitter as follows...
      //
      // .xyz = position
      // .w = one over radius   
      // float4 placement;
      //
      // .x = strength
      // .y = turbulence frequency
      // .z = turbulence strength
      // .w = unused
      // float4 effect;
      const U32 startReg = 40;
      GFX->setVertexShaderConstF( startReg + (i*2) + 0, (F32*)&placement, 1 );
      GFX->setVertexShaderConstF( startReg + (i*2) + 1, (F32*)&effect, 1 );
   }


   // TODO: Maybe add support for non-facing billboards
   // with random rotations and optional crosses.  We could
   // stick them into the buffer after the normal billboards,
   // then change shader consts.
   //

   // Setup the primitive buffer once.
   GFX->setPrimitiveBuffer( primBuffer );

   // Draw each batch.
   U32 remaining = mBillboards.size();
   const U32 batches = mVBs.size();
   const U32 batchBB = remaining / batches;

   for ( U32 b = 0; b < batches; b++ )
   {
      // Grab a reference to the vb.
      VBHandle &vb = mVBs[b];

      // How many billboards in this batch?
      U32 bb = getMin( batchBB, remaining );
      remaining -= bb;

      // Setup and render it!
      GFX->setVertexBuffer( vb );
      GFX->drawIndexedPrimitive( GFXTriangleList,
                                 0, 
                                 bb * 4, 
                                 0, 
                                 bb * 2 );
   }

   return mBillboards.size();
}


U32 GroundCover::smStatRenderedCells = 0;
U32 GroundCover::smStatRenderedBillboards = 0;
U32 GroundCover::smStatRenderedBatches = 0;
U32 GroundCover::smStatRenderedShapes = 0;
U32 GroundCover::smLastState = 0;
F32 GroundCover::smQualityScale = 1.0f;
#5
03/19/2008 (8:45 am)
GroundCover::GroundCover()
{
   mTypeMask |= StaticObjectType;
   mNetFlags.set( Ghostable | ScopeAlways );

   mRadius = 200.0f;
   mZOffset = 0.0f;
   mFadeRadius = 50.0f;
   mShapeCullRadius = 75.0f;
   mReflectRadiusScale = 0.25f;

   mGridSize = 7;

   // By initializing this to a big value we
   // ensure we warp on first render.
   mGridIndex.set( S32_MAX, S32_MAX );

   mMaxPlacement = 1000;
   mLastPlacementCount = 0;

   mDebugRenderCells = false;
   mDebugNoBillboards = false;
   mDebugNoShapes = false;
   mDebugLockFrustum = false;

   mRandomSeed = 1;

   mTerrainName = NULL;
   mTerrainGhostId = 0;

   mTextureName = NULL;

   mBBShader = NULL;
   mLightAllocator = NULL;

   mMaxBillboardTiltAngle = 90.0f;

   // TODO: This really doesn't belong here... we need a
   // real wind system for Torque scenes.  This data
   // would be part of a global scene wind or area wind
   // emitter.
   //
   // Tom Spilman - 10/16/2007

   mWindGustLength = 20.0f;
   mWindGustFrequency = 0.5f;
   mWindGustStrength = 0.5f;
   mWindDirection.set( 1.0f, 0.0f );
   mWindTurbulenceFrequency = 1.2f;
   mWindTurbulenceStrength = 0.125f;

   for ( S32 i=0; i < NumCoverTypes; i++ )
   {
      mProbability[i] = 1.0f;

      mSizeMin[i] = 1.0f;
      mSizeMax[i] = 1.0f;
      mSizeExponent[i] = 1.0f;

      mWindScale[i] = 1.0f;

      mMaxSlope[i] = 0.0f;

      mMinElevation[i] = -99999.0f;
      mMaxElevation[i] = 99999.0f;

      mLayer[i] = -1;
      mInvertLayer[i] = false;

      mMinClumpCount[i] = 1;
      mMaxClumpCount[i] = 1;
      mClumpCountExponent[i] = 1.0f;
      mClumpRadius[i] = 1.0f;

      mBillboardRects[i].point.set( 0.0f, 0.0f );
      mBillboardRects[i].extent.set( 1.0f, 1.0f );

      mShapeFilenames[i] = NULL;
      mShapeInstances[i] = NULL;

      mBillboardAspectScales[i] = 1.0f;

      mNormalizedProbability[i] = 0.0f;
   }
}

IMPLEMENT_CO_NETOBJECT_V1(GroundCover);

void GroundCover::initPersistFields()
{
   Parent::initPersistFields();

   addGroup( "Wind" );

      addField( "windDirection", TypePoint2F, Offset( mWindDirection, GroundCover ) );

      addField( "windGustLength", TypeF32, Offset( mWindGustLength, GroundCover ) );
      addField( "windGustFrequency", TypeF32, Offset( mWindGustFrequency, GroundCover ) );
      addField( "windGustStrength", TypeF32, Offset( mWindGustStrength, GroundCover ) );

      addField( "windTurbulenceFrequency", TypeF32, Offset( mWindTurbulenceFrequency, GroundCover ) );
      addField( "windTurbulenceStrength", TypeF32, Offset( mWindTurbulenceStrength, GroundCover ) );

   endGroup( "Wind" );

   addGroup( "Ground Cover General" );
      
      addField( "radius", TypeF32, Offset( mRadius, GroundCover ) );
      addField( "dissolveRadius", TypeF32, Offset( mFadeRadius, GroundCover ) );
      addField( "reflectScale", TypeF32, Offset( mReflectRadiusScale, GroundCover ) );

      addField( "gridSize", TypeS32, Offset( mGridSize, GroundCover ) );
      addField( "zOffset", TypeF32, Offset( mZOffset, GroundCover ) );

      addField( "seed", TypeS32, Offset( mRandomSeed, GroundCover ) );
      addField( "terrain", TypeString, Offset( mTerrainName, GroundCover ) );
      addField( "maxElements", TypeS32, Offset( mMaxPlacement, GroundCover ) );

   endGroup( "Ground Cover General" );

   addGroup( "Ground Cover Billboard" );

      addField( "billboardTexture", TypeFilename, Offset( mTextureName, GroundCover ) );
      addField( "maxBillboardTiltAngle", TypeF32, Offset( mMaxBillboardTiltAngle, GroundCover ) );

   endGroup( "Ground Cover Billboard" );

   addGroup( "Ground Cover Shape" );

      addField( "shapeCullRadius", TypeF32, Offset( mShapeCullRadius, GroundCover ) );      
   endGroup( "Ground Cover Shape" );


   addGroup( "Ground Cover Debug" );

      addField( "lockFrustum", TypeBool, Offset( mDebugLockFrustum, GroundCover ) );
      addField( "renderCells", TypeBool, Offset( mDebugRenderCells, GroundCover ) );
      addField( "noBillboards", TypeBool, Offset( mDebugNoBillboards, GroundCover ) );
      addField( "noShapes", TypeBool, Offset( mDebugNoShapes, GroundCover ) );

   endGroup( "Ground Cover Debug" );  

   UTF8 groupName[256];
   UTF8 fieldName[256];
   for ( S32 i=0; i < NumCoverTypes; i++ )
   {
      dSprintf( groupName, sizeof( groupName ), "Ground Cover Type %d", i );
      addGroup( groupName );

         dSprintf( fieldName, sizeof( fieldName ), "billboardUVs%d", i );
         addField( fieldName, TypeRectF, Offset( mBillboardRects[i], GroundCover ) );

         dSprintf( fieldName, sizeof( fieldName ), "shapeFilename%d", i );      
         addField( fieldName, TypeFilename, Offset( mShapeFilenames[i], GroundCover ) );

         dSprintf( fieldName, sizeof( fieldName ), "layer%d", i );
         addField( fieldName, TypeS32, Offset( mLayer[i], GroundCover ) );

         dSprintf( fieldName, sizeof( fieldName ), "invertLayer%d", i );
         addField( fieldName, TypeBool, Offset( mInvertLayer[i], GroundCover ) );

         dSprintf( fieldName, sizeof( fieldName ), "probability%d", i );
         addField( fieldName, TypeF32, Offset( mProbability[i], GroundCover ) );

         dSprintf( fieldName, sizeof( fieldName ), "sizeMin%d", i );
         addField( fieldName, TypeF32, Offset( mSizeMin[i], GroundCover ) );

         dSprintf( fieldName, sizeof( fieldName ), "sizeMax%d", i );
         addField( fieldName, TypeF32, Offset( mSizeMax[i], GroundCover ) );

         dSprintf( fieldName, sizeof( fieldName ), "sizeExponent%d", i );
         addField( fieldName, TypeF32, Offset( mSizeExponent[i], GroundCover ) );

         dSprintf( fieldName, sizeof( fieldName ), "windScale%d", i );
         addField( fieldName, TypeF32, Offset( mWindScale[i], GroundCover ) );

         dSprintf( fieldName, sizeof( fieldName ), "maxSlope%d", i );
         addField( fieldName, TypeF32, Offset( mMaxSlope[i], GroundCover ) );

         dSprintf( fieldName, sizeof( fieldName ), "minElevation%d", i );
         addField( fieldName, TypeF32, Offset( mMinElevation[i], GroundCover ) );

         dSprintf( fieldName, sizeof( fieldName ), "maxElevation%d", i );
         addField( fieldName, TypeF32, Offset( mMaxElevation[i], GroundCover ) );

         dSprintf( fieldName, sizeof( fieldName ), "minClumpCount%d", i );
         addField( fieldName, TypeS32, Offset( mMinClumpCount[i], GroundCover ) );
         
         dSprintf( fieldName, sizeof( fieldName ), "maxClumpCount%d", i );
         addField( fieldName, TypeS32, Offset( mMaxClumpCount[i], GroundCover ) );

         dSprintf( fieldName, sizeof( fieldName ), "clumpExponent%d", i );
         addField( fieldName, TypeF32, Offset( mClumpCountExponent[i], GroundCover ) );

         dSprintf( fieldName, sizeof( fieldName ), "clumpRadius%d", i );
         addField( fieldName, TypeF32, Offset( mClumpRadius[i], GroundCover ) );

      endGroup( groupName );
   }
}
#6
03/19/2008 (8:46 am)
void GroundCover::consoleInit()
{  
   Con::addVariable( "$GroundCover::renderedCells", TypeS32, &smStatRenderedCells );
   Con::addVariable( "$GroundCover::renderedBillboards", TypeS32, &smStatRenderedBillboards );
   Con::addVariable( "$GroundCover::renderedBatches", TypeS32, &smStatRenderedBatches );
   Con::addVariable( "$GroundCover::renderedShapes", TypeS32, &smStatRenderedShapes );
}

bool GroundCover::onAdd()
{
   if (!Parent::onAdd())
   {
      return false;
   }
   // We don't use any bounds.
	mObjBox.min.set(-1e5, -1e5, -1e5);
	mObjBox.max.set( 1e5,  1e5,  1e5);
   resetWorldBox();

   // Find the terrain object on the server... the client waits until
   // rendering time to do this to be sure the terrain has ghosted.
   if ( isServerObject() )
   {
      StringTableEntry terrainName = mTerrainName;
//      if ( !terrainName || !terrainName[0] )
         terrainName = StringTable->insert( "AtlasTerrain" );

		mTerrainBlock = dynamic_cast<AtlasInstance*>(Sim::findObject("AtlasTerrain")); //jc
      //mTerrainBlock = dynamic_cast<TerrainBlock*>( Sim::findObject( terrainName ) );
      mTerrainGhostId = 0;
   }
   else
   {
      mTerrainBlock = NULL;
   }

   // Prepare some client side things.
   if ( isClientObject() )
   {
      mBBShader = NULL;
      if ( !Sim::findObject( "GroundCoverShaderData", mBBShader ) )
         Con::warnf("GroundCover - failed to locate billboard shader GroundCoverShaderData!");

      mLightAllocator = new LightAllocator();

      _initShapes();
   }

   addToScene();

   return true;
}

void GroundCover::onRemove()
{
   Parent::onRemove();

   _deleteCells();
   _deleteShapes();

	// jc
   //if ( isClientObject() && mTerrainBlock )
   //   mTerrainBlock->mUpdateSignal.remove( this, &GroundCover::onTerrainUpdated );

   mTerrainBlock = NULL;
   mBBShader = NULL;

   SAFE_DELETE( mLightAllocator );

   removeFromScene();
}

void GroundCover::inspectPostApply()
{
   Parent::inspectPostApply();

   // We flag all the parameters as changed because
   // we're feeling lazy and there is not a good way
   // to track what parameters changed.
   //
   // TODO: Add a mask bit option to addField() and/or
   // addGroup() which is passed to inspectPostApply
   // for detection of changed elements.
   //
   setMaskBits( -1 );
}

U32 GroundCover::packUpdate( NetConnection *connection, U32 mask, BitStream *stream )
{
   Parent::packUpdate( connection, mask, stream );
  
   // TODO: We could probably optimize a few of these
   // based on reasonable units at some point.

   stream->write( mRadius );
   stream->write( mZOffset );
   stream->write( mFadeRadius );
   stream->write( mShapeCullRadius );
   stream->write( mReflectRadiusScale );
   stream->write( mGridSize );
   stream->write( mRandomSeed );
   stream->write( mMaxPlacement );
   stream->write( mMaxBillboardTiltAngle );

   stream->writeString( mTextureName );

   stream->write( mWindDirection.x );
   stream->write( mWindDirection.y );
   stream->write( mWindGustLength );
   stream->write( mWindGustFrequency );
   stream->write( mWindGustStrength );
   stream->write( mWindTurbulenceFrequency );
   stream->write( mWindTurbulenceStrength );

   for ( S32 i=0; i < NumCoverTypes; i++ )
   {
      stream->write( mProbability[i] );
      stream->write( mSizeMin[i] );
      stream->write( mSizeMax[i] );
      stream->write( mSizeExponent[i] );
      stream->write( mWindScale[i] );
      
      stream->write( mMaxSlope[i] );
      
      stream->write( mMinElevation[i] );
      stream->write( mMaxElevation[i] );     

      stream->write( mLayer[i] );
      stream->writeFlag( mInvertLayer[i] );      

      stream->write( mMinClumpCount[i] );
      stream->write( mMaxClumpCount[i] );
      stream->write( mClumpCountExponent[i] );
      stream->write( mClumpRadius[i] );

      stream->write( mBillboardRects[i].point.x );
      stream->write( mBillboardRects[i].point.y );
      stream->write( mBillboardRects[i].extent.x );
      stream->write( mBillboardRects[i].extent.y );

      stream->writeString( mShapeFilenames[i] );
   }
   
   U32 ghostId = -1;

   if ( mTerrainBlock )
      ghostId = connection->getGhostIndex( mTerrainBlock );

   stream->write( ghostId );

   stream->writeFlag( mDebugRenderCells );
   stream->writeFlag( mDebugNoBillboards );
   stream->writeFlag( mDebugNoShapes );
   stream->writeFlag( mDebugLockFrustum );

   return 0;  
}
#7
03/19/2008 (8:46 am)
void GroundCover::unpackUpdate( NetConnection *connection, BitStream *stream )
{
   Parent::unpackUpdate( connection, stream );

   stream->read( &mRadius );
   stream->read( &mZOffset );
   stream->read( &mFadeRadius );
   stream->read( &mShapeCullRadius );
   stream->read( &mReflectRadiusScale );   
   stream->read( &mGridSize );
   stream->read( &mRandomSeed );
   stream->read( &mMaxPlacement );
   stream->read( &mMaxBillboardTiltAngle );

   mTextureName = stream->readSTString();

   stream->read( &mWindDirection.x );
   stream->read( &mWindDirection.y );
   stream->read( &mWindGustLength );
   stream->read( &mWindGustFrequency );
   stream->read( &mWindGustStrength );
   stream->read( &mWindTurbulenceFrequency );
   stream->read( &mWindTurbulenceStrength );

   for ( S32 i=0; i < NumCoverTypes; i++ )
   {
      stream->read( &mProbability[i] );
      stream->read( &mSizeMin[i] );
      stream->read( &mSizeMax[i] );
      stream->read( &mSizeExponent[i] );
      stream->read( &mWindScale[i] );

      stream->read( &mMaxSlope[i] );

      stream->read( &mMinElevation[i] );
      stream->read( &mMaxElevation[i] );     

      stream->read( &mLayer[i] );
      mInvertLayer[i] = stream->readFlag();

      stream->read( &mMinClumpCount[i] );
      stream->read( &mMaxClumpCount[i] );
      stream->read( &mClumpCountExponent[i] );
      stream->read( &mClumpRadius[i] );

      stream->read( &mBillboardRects[i].point.x );
      stream->read( &mBillboardRects[i].point.y );
      stream->read( &mBillboardRects[i].extent.x );
      stream->read( &mBillboardRects[i].extent.y );

      mShapeFilenames[i] = stream->readSTString();
   }

   stream->read( &mTerrainGhostId );

   mDebugRenderCells    = stream->readFlag();
   mDebugNoBillboards   = stream->readFlag();
   mDebugNoShapes       = stream->readFlag();
   mDebugLockFrustum    = stream->readFlag();

   // We have no way to easily know what changed, so by clearing
   // the cells we force a reinit and regeneration of the cells.
   // It's sloppy, but it works for now.
   _freeCells();
}

void GroundCover::_initShapes()
{
   _deleteShapes();

   for ( S32 i=0; i < NumCoverTypes; i++ )
   {
      if ( !mShapeFilenames[i] || !mShapeFilenames[i][0] )
         continue;

      // Load the shape.
      Resource<TSShape> shape = ResourceManager->load(mShapeFilenames[i]);
      if ( !(bool)shape )
      {
         Con::warnf( "GroundCover::_initShapes() unable to load shape: %s", mShapeFilenames[i] );
         continue;
      }

      if ( isClientObject() && !shape->preloadMaterialList() && NetConnection::filesWereDownloaded() )
      {
         Con::warnf( "GroundCover::_initShapes() material preload failed for shape: %s", mShapeFilenames[i] );
         continue;
      }

      // Create the shape instance.
      mShapeInstances[i] = new TSShapeInstance( shape, isClientObject() );
   }
}

void GroundCover::_deleteShapes()
{
   for ( S32 i=0; i < NumCoverTypes; i++ )
   {
      delete mShapeInstances[i];
      mShapeInstances[i] = NULL;
   }
}

void GroundCover::_deleteCells()
{
   // Delete the allocation list.
   for ( S32 i=0; i < mAllocCellList.size(); i++ )
      delete mAllocCellList[i];
   mAllocCellList.clear();

   // Zero out the rest of the stuff.
   _freeCells();
}
#8
03/19/2008 (8:46 am)
void GroundCover::_freeCells()
{
   // Zero the grid and scratch space.
   mCellGrid.clear();
   mScratchGrid.clear();

   // Compact things... remove excess allocated cells.
   const U32 maxCells = mGridSize * mGridSize;
   if ( mAllocCellList.size() > maxCells )
   {
      for ( S32 i=maxCells; i < mAllocCellList.size(); i++ )
        delete mAllocCellList[i];
      mAllocCellList.setSize( maxCells );
   }

   // Move all the alloced cells into the free list.
   mFreeCellList.clear();
   mFreeCellList.merge( mAllocCellList );

   // Release the primitive buffer.
   mPrimBuffer = NULL;
}

void GroundCover::_recycleCell( GroundCoverCell* cell )
{
   mFreeCellList.push_back( cell );
}

void GroundCover::_initialize( U32 cellCount, U32 cellPlacementCount )
{
   // Cleanup everything... we're starting over.
   _freeCells();
   _deleteShapes();
   mTexture.free();

   // Nothing to do without a count!
   if ( cellPlacementCount == 0 )
   {
      return;

   }

   // Grab the terrain block.
   if ( mTerrainBlock )
   {
     // mTerrainBlock->mUpdateSignal.remove( this, &GroundCover::onTerrainUpdated ); //jc
      mTerrainBlock = NULL;
   }

   // Use the net connection to resolve the ghost id.
   NetConnection* conn = NetConnection::getConnectionToServer();

   // Without a connection or id we cannot continue.
   if ( mTerrainGhostId == -1 || !conn )
   {
      return;
   }

   mTerrainBlock = dynamic_cast<AtlasInstance*>( conn->resolveGhost( mTerrainGhostId ) ); //jc
   if ( !mTerrainBlock || mTerrainBlock->isServerObject() )
   {
      mTerrainBlock = NULL;
      mTerrainGhostId = -1;
      return;
   }

   // Hook ourselves up to get terrain change notifications.
   //mTerrainBlock->mUpdateSignal.notify( this, &GroundCover::onTerrainUpdated ); //jc
   onTerrainUpdated(0,Point2I(-1000,-1000),Point2I(1000,1000));

   // Reset the grid sizes.
   mCellGrid.setSize( cellCount );
   dMemset( mCellGrid.address(), 0, mCellGrid.memSize() );
   mScratchGrid.setSize( cellCount );

   // Reload the texture.
   if ( mTextureName && mTextureName[0] )
      mTexture.set( mTextureName, &GFXDefaultStaticDiffuseProfile );//&GFXMaterialStaticDXT5Profile );

   // Grab the texture aspect ratio.
   F32 texAspect = 1.0f;
   if ( !mTexture.isNull() )
      texAspect = mTexture.getWidth() / (F32)mTexture.getHeight();

   // Rebuild the texture aspect scales for each type.
   for ( S32 i=0; i < NumCoverTypes; i++ )
   {
      if ( mBillboardRects[i].len_y() > 0.0f )
         mBillboardAspectScales[i] = texAspect * ( mBillboardRects[i].len_x() / mBillboardRects[i].len_y() );
      else
         mBillboardAspectScales[i] = 0.0f;
   }

   // Load the shapes again.
   _initShapes();

   // Set the primitive buffer up for the maximum placement in a cell.
   mPrimBuffer.set( GFX, cellPlacementCount * 6, 0, GFXBufferTypeStatic );
   U16 *idxBuff;
   mPrimBuffer.lock(&idxBuff);
   for ( U32 i=0; i < cellPlacementCount; i++ )
   {
      //
      // The vertex pattern in the VB for each 
      // billboard is as follows...
      //
      //     0----1
      //     |\   |
      //     | \  |
      //     |  \ |
      //     |   \|
      //     3----2
      //
      // We setup the index order below to ensure
      // sequential, cache friendly, access.
      //
      U32 offset = i * 4;
      idxBuff[i*6+0] = 0 + offset;
      idxBuff[i*6+1] = 1 + offset;
      idxBuff[i*6+2] = 2 + offset;
      idxBuff[i*6+3] = 2 + offset;
      idxBuff[i*6+4] = 3 + offset;
      idxBuff[i*6+5] = 0 + offset;
   }   
   mPrimBuffer.unlock();

   // Generate the normalised probability.
   F32 total = 0.0f;
   for ( S32 i=0; i < NumCoverTypes; i++ )
   {
      // If the element isn't gonna render... then
      // set the probability to zero.
      if ( mShapeInstances[i] == NULL && mBillboardAspectScales[i] <= 0.0001f )
      {
         mNormalizedProbability[i] = 0.0f;
      }
      else
      {
         mNormalizedProbability[i] = mProbability[i];

         total += mProbability[i];
      }
   }
   if ( total > 0.0f )
   {
      for ( S32 i=0; i < NumCoverTypes; i++ )
         mNormalizedProbability[i] /= total;
   }
}
#9
03/19/2008 (8:49 am)
GroundCoverCell* GroundCover::_generateCell( const Point2I& index, 
                                             const Box3F& bounds, 
                                             U32 placementCount,
                                             S32 randSeed )
{
   PROFILE_SCOPE(GroundCover_GenerateCell);

   // Grab a free cell or allocate a new one.
   GroundCoverCell* cell;
   if ( mFreeCellList.empty() )
   {
      cell = new GroundCoverCell();
      mAllocCellList.push_back( cell );
   }
   else
   {
      cell = mFreeCellList.last();
      mFreeCellList.pop_back();
   }

   cell->mDirty = true;
   cell->mIndex = index;
   cell->mBounds = bounds;

   Point3F pos = mTerrainBlock->getPosition();

   Box3F renderBounds = bounds;
   Point3F point;
   Point3F normal;
//   F32 h;
   Point2F cp, uv;
   bool hit;
   GroundCoverCell::Placement p;
   F32 rotation;
   U8 minValue;
   F32 size;
   F32 sizeExponent;
   Point2I lpos;
   //F32 value;
   VectorF right;
   bool firstElem = true;

   cell->mBillboards.clear();
   cell->mBillboards.reserve( placementCount );
   cell->mShapes.clear();
   cell->mShapes.reserve( placementCount );

//   const F32 terrainSquareSize = 1.5; // jc (F32)mTerrainBlock->getSquareSize();
   const F32 oneOverTerrainLength = 1.0f / 512;

   //Con::errorf("oneOverTerrainLength = %f",oneOverTerrainLength);
//   const F32 oneOverTerrainLength = 1.0f / (F32)(terrainSquareSize * TerrainBlock::BlockSize);
   //const F32 oneOverTerrainSquareSize = 1.0f / terrainSquareSize; /jc this line was already commented
   //const GBitmap* terrainLM = mTerrainBlock->lightMap; // jc not using it for now
   
   // The RNG that we'll use in generation.
   MRandom rand( 0 );

   // We process one type at a time.
   for ( U32 type=0; type < NumCoverTypes; type++ )
   {
      // How many cover elements do we need to generate for this type?
      const S32 typeCount = mNormalizedProbability[type] * (F32)placementCount;
      if ( typeCount <= 0 )
         continue;
	//jc this block was already commented out
      // Grab the terrain layer for this type.
      /*
      const TerrainDataLayer* dataLayer = NULL;
      const bool typeInvertLayer = mInvertLayer[type];
      if ( mLayer[type] > -1 )
      {
         dataLayer = mTerrainBlock->getDataLayer( mLayer[type] );
         if ( dataLayer )
         {
            // Do an initial check to see if we can place any place anything
            // at all...  if the layer area for this element is empty then 
            // there is nothing more to do.

            RectI area( (S32)mFloor( ( bounds.min.x - pos.x ) * oneOverTerrainSquareSize ),
                        (S32)mFloor( ( bounds.min.y - pos.y ) * oneOverTerrainSquareSize ),
                        (S32)mCeil( ( bounds.max.x - pos.x ) * oneOverTerrainSquareSize ),
                        (S32)mCeil( ( bounds.max.y - pos.y ) * oneOverTerrainSquareSize ) );
            area.extent -= area.point;

            if ( dataLayer->testFill( area, typeInvertLayer ? 255 : 0 ) )
               continue;
         }
      }

      // If the layer is not inverted and we have no data 
      // then we have nothing to draw.
      if ( !typeInvertLayer && !dataLayer )
         continue;
      */

      // We set the seed we were passed which is based on this grids position
      // in the world and add the type value.  This keeps changes to one type
      // from effecting the outcome of the others.
      rand.setSeed( randSeed + type );

      // Setup for doing clumps.
      S32 clumps = 0;
      Point2F clumpCenter;
      const S32 clumpMin = getMax( 1, (S32)mMinClumpCount[type] );
      F32 clumpExponent;   

      // We mult this by -1 each billboard we make then use
      // it to scale the billboard x axis to flip them.  This
      // essentially gives us twice the variation for free.
      F32 flipBB = -1.0f;

      // Precompute a few other type specific values.
      const F32 typeSizeRange = mSizeMax[type] - mSizeMin[type];
      const F32 typeMaxSlope = mMaxSlope[type];
//      const F32 typeMaxElevation = mMaxElevation[type];
//      const F32 typeMinElevation = mMinElevation[type];
      const bool typeIsShape = mShapeInstances[ type ] != NULL;
      const Box3F typeShapeBounds = typeIsShape ? mShapeInstances[ type ]->getShape()->bounds : Box3F();
      const F32 typeWindScale = mWindScale[type];

      // We can set this once here... all the placements for this are the same.
      p.type = type;
      p.windAmplitude = typeWindScale;
      p.lmColor.set(1.0f,1.0f,1.0f);

      // Generate all the cover elements for this type.
      for ( S32 i=0; i < typeCount; i++ )
      {
         // Do all the other random things here first as to not 
         // disturb the random sequence if the terrain geometry
         // or cover layers change.

         // Get the random position.      
         cp.set( rand.randF(), rand.randF() );

         // Prepare the clump info.
         clumpExponent = mClampF( mPow( rand.randF(), mClumpCountExponent[type] ), 0.0f, 1.0f );
         if ( clumps <= 0 )
         {
            // We're starting a new clump.
            clumps = ( clumpMin + mFloor( ( mMaxClumpCount[type] - clumpMin ) * clumpExponent ) ) - 1;
            cp.set(  bounds.min.x + cp.x * bounds.len_x(),
                     bounds.min.y + cp.y * bounds.len_y() );
            clumpCenter = cp;
         }
         else
         {
            clumps--;
            cp.set( clumpCenter.x - ( ( cp.x - 0.5f ) * mClumpRadius[type] ),
                    clumpCenter.y - ( ( cp.y - 0.5f ) * mClumpRadius[type] ) );
         }

         // The size is calculated using an exponent to control 
         // the frequency between min and max sizes.
         sizeExponent = mClampF( mPow( rand.randF(), mSizeExponent[type] ), 0.0f, 1.0f );
         size = mSizeMin[type] + ( typeSizeRange * sizeExponent );

         // Generate a random z rotation.
         rotation = rand.randF() * M_2PI_F;

         // Generate a tolerance to test against the cover layer.
         minValue = rand.randI( 1, 255 );

         // Flip the billboard now for the next generation.
         flipBB *= -1.0f;

		 // jc start
 //        PROFILE_START( GroundCover_TerrainRayCast );
//         hit = mTerrainBlock->getNormalAndHeight( Point2F( cp.x - pos.x, cp.y - pos.y ), &normal, &h ); //jc need to replace this
//         PROFILE_END(); // GroundCover_TerrainRayCast
//         if ( !hit || h > typeMaxElevation || h < typeMinElevation )
//            continue;
		RayInfo			RayEvent;
			hit = gClientContainer.castRay(	Point3F(cp.x,cp.y,0), Point3F(cp.x,cp.y,1500), AtlasObjectType, &RayEvent);
			//jc diabled for testing
         if ( !isZero( typeMaxSlope ) )
		 {
			if (RayEvent.normal.z < mSin(mDegToRad(typeMaxSlope))) 
				hit = false;
		 }

		if (!hit) 
		{	
			//Con::errorf("We hit at cp.x = %f, cp.y = %f",cp.x,cp.y);
			continue;
		}

         // Do we need to check slope?
//         if ( !isZero( typeMaxSlope ) )
//         {
//            if (mAcos(normal.z) > mDegToRad(typeMaxSlope))
//               continue;
//         }

//jc end

		//Con::errorf("point.x = %f, point.y = %f, point.z = %f",cp.x,cp.y,RayEvent.point.z-getPosition().z);
         point.set( cp.x, cp.y, RayEvent.point.z );

         /*
         if ( dataLayer )
         {
            lpos.set( (S32)mFloor( ( ( point.x - pos.x ) * oneOverTerrainSquareSize ) + 0.5f ), 
                      (S32)mFloor( ( ( point.y - pos.y ) * oneOverTerrainSquareSize ) + 0.5f ) );

            value = dataLayer->getValue( lpos );

            if (  typeInvertLayer && value > minValue ||
                  !typeInvertLayer && value < minValue )
                  continue;
         }
#10
03/19/2008 (8:50 am)
*/

         p.point = point;
         p.rotation = rotation;

         // Grab the terrain lightmap color at this position.
         //
         // TODO: Can't we remove this test?  The terrain 
         // lightmap should never be null... NEVER!
         //
//         if ( terrainLM ) //jc start
//         {
            // TODO: We could probably call terrainLM->getBits()
            // once outside the loop then pre-calculate the scalar
            // for converting a world position into a lexel...
            // avoiding the extra protections inside of sampleTexel().

            uv.x = (point.x + pos.x) * oneOverTerrainLength;
            uv.y = (point.y + pos.y) * oneOverTerrainLength;
            uv.x -= mFloor(uv.x);
            uv.y -= mFloor(uv.y);
//            p.lmColor = terrainLM->sampleTexel(uv.x,uv.y);
//         } 
//jc end

         // Put it into the right list by type.
         //
         // TODO: Could we break up the generation into
         // two separate loops for shapes and billboards
         // and gain performance?
         //
         if ( typeIsShape )
         {
            // TODO: Convert the size into a real size... not scale!

            // TODO: We could probably cache the shape bounds
            // into a primitive array and avoid the double pointer
            // dereference per placement.

            p.size.set( size, size, size );
            p.worldBox = typeShapeBounds;
            p.worldBox.min *= size;
            p.worldBox.max *= size;
            p.worldBox.min += point;
            p.worldBox.max += point;

            cell->mShapes.push_back( p );
         }
         else
         {
            p.size.y = size;
            p.size.x = size * flipBB * mBillboardAspectScales[type]; 
            p.worldBox.max = p.worldBox.min = point;

            cell->mBillboards.push_back( p );
         }

         // Update the render bounds.
         if ( firstElem )
         {
            renderBounds = p.worldBox;
            firstElem = false;
         }
         else
         {
            renderBounds.extend( p.worldBox.min );
            renderBounds.extend( p.worldBox.max );
         }

      } // for ( S32 i=0; i < typeCount; i++ )

   } // for ( U32 type=0; type < NumCoverTypes; type++ )
      

   cell->mRenderBounds = renderBounds;
   cell->mBounds.min.z = renderBounds.min.z;
   cell->mBounds.max.z = renderBounds.max.z;
   return cell;
}

void GroundCover::onTerrainUpdated( U32 flags, const Point2I& min, const Point2I& max )
{
   AssertFatal( isClientObject(), "GroundCover::onTerrainUpdated() - Got server side terrain update!" );
//jc start
   // Free all the cells if we've gotten a lightmap update.
/*   if ( flags & TerrainBlock::LightmapUpdate )
   {
      _freeCells();
      return;
   }
*/
   // TODO: EmptyUpdate doesn't work yet... fix editor/terrain.

   // If this is a height or opacity update only clear
   // the cells that have changed.
/*   if (  flags & TerrainBlock::HeightmapUpdate || 
         flags & TerrainBlock::OpacityUpdate ||
         flags & TerrainBlock::EmptyUpdate )
   {*/
      // Convert the min and max into world space.
	   const F32 size = 1.5; //jc
//      const F32 size = mTerrainBlock->getSquareSize();
      const Point3F pos = mTerrainBlock->getPosition();

      // TODO: I don't think this works right with tiling!
      Box3F dirty(   F32( min.x * size ) + pos.x, F32( min.y * size ) + pos.y, 0.0f,
                     F32( max.x * size ) + pos.x, F32( max.y * size ) + pos.y, 0.0f );
      
      // Now free any cells that overlap it!
      for ( S32 i = 0; i < mCellGrid.size(); i++ )
      {
         GroundCoverCell* cell = mCellGrid[ i ];
         if ( !cell )
            continue;

         const Box3F& bounds = cell->getBounds();
         dirty.min.z = bounds.min.z;
         dirty.max.z = bounds.max.z;
         if ( bounds.isOverlapped( dirty ) )
         {
            mCellGrid[ i ] = NULL;
            _recycleCell( cell );
         }
      }
   // jc end
}

void GroundCover::_updateCoverGrid( const FrustrumCuller& culler )
{
   PROFILE_SCOPE( GroundCover_UpdateCoverGrid );
   
   // How many cells in the grid?
   const U32 cells = mGridSize * mGridSize;

   // Whats the max placement count for each cell considering 
   // the grid size and quality scale LOD value.
   const S32 placementCount = ( (F32)mMaxPlacement * smQualityScale ) / F32( mGridSize * mGridSize );

   // If the cell grid isn't sized or the placement count
   // changed (most likely because of quality lod) then we
   // need to initialize the system again.
   if ( mCellGrid.empty() || placementCount != mLastPlacementCount )
   {
      _initialize( cells, placementCount );
      mLastPlacementCount = placementCount;
   }

   // Without terrain or a count... we don't function at all.
   if ( !mTerrainBlock || placementCount == 0 )
   {
      return;
   }

   // Clear the scratch grid.
   dMemset( mScratchGrid.address(), 0, mScratchGrid.memSize() );

   // Calculate the normal cell size here.
   const F32 cellSize = ( mRadius * 2.0f ) / (F32)(mGridSize - 1);

   // Figure out the root index of the new grid based on the camera position.
   Point2I index( (S32)mFloor( ( culler.mCamPos.x - mRadius ) / cellSize  ),
                  (S32)mFloor( ( culler.mCamPos.y - mRadius ) / cellSize ) );

   // Figure out the cell shift between the old and new grid positions.
   Point2I shift = mGridIndex - index;

   // If we've shifted more than one in either axis then we've warped.
   bool didWarp = shift.x > 1 || shift.x < -1 || 
                  shift.y > 1 || shift.y < -1 ? true : false;

   // Go thru the grid shifting each cell we find and
   // placing them in the scratch grid.
   for ( S32 i = 0; i < mCellGrid.size(); i++ )
   {
      GroundCoverCell* cell = mCellGrid[ i ];
      if ( !cell )
         continue;

      // Whats our new index?
      Point2I newIndex = cell->shiftIndex( shift );

      // Is this cell outside of the new grid?
      if (  newIndex.x < 0 || newIndex.x >= mGridSize ||
            newIndex.y < 0 || newIndex.y >= mGridSize )
      {
         _recycleCell( cell );
         continue;
      }

      // Place the cell in the scratch grid.
      mScratchGrid[ ( newIndex.y * mGridSize ) + newIndex.x ] = cell;
   }

   // Get the terrain elevation range for setting the default cell bounds.
   // jc start
   F32 terrainMinHeight = 0.0f; // note hardcoded areas ewww
   F32 terrainMaxHeight = 1200.0f;
//   mTerrainBlock->getMinMaxHeight( &terrainMinHeight, &terrainMaxHeight );
// jc end
#11
03/19/2008 (8:51 am)
// Go thru the scratch grid copying each cell back to the
   // cell grid and creating new cells as needed.
   //
   // By limiting ourselves to only one new cell generation per
   // update we're lowering the performance hiccup during movement
   // without getting into the complexity of threading.  The delay
   // in generation is rarely noticeable in normal play.
   //
   // The only caveat is that we need to generate the entire visible
   // grid when we warp.
   U32 cellsGenerated = 0;
   for ( S32 i = 0; i < mScratchGrid.size(); i++ )
   {
      GroundCoverCell* cell = mScratchGrid[ i ];
      if ( !cell && ( cellsGenerated == 0 || didWarp ) )
      {
         // Get the index point of this new cell.
         S32 y = i / mGridSize;
         S32 x = i - ( y * mGridSize );
         Point2I newIndex = index + Point2I( x, y );

         // What will be the world placement bounds for this cell.
         Box3F bounds;
         bounds.min.set( newIndex.x * cellSize, newIndex.y * cellSize, terrainMinHeight );
         bounds.max.set( bounds.min.x + cellSize, bounds.min.y + cellSize, terrainMaxHeight );

         S32 clipMask = mCuller.testBoxVisibility( bounds, FrustrumCuller::ClipPlaneMask, 0 );
         if ( clipMask == -1 )
         {
            mCellGrid[ i ] = NULL;
            continue;
         }

         // We need to allocate a new cell.
         //
         // TODO: This is the expensive call and where we should optimize. In
         // particular the next best optimization would be to take advantage of
         // multiple cores so that we can generate all the cells in one update.
         //
         // Instead of generating the cell here we would allocate a cell and stick
         // it into a thread safe queue (maybe lockless) as well as the mCellGrid.
         // Once all were allocated we would do something like this...
         //
         // TorqueParallelProcess( cellsToGenerateQueue, _generateCell );
         //
         // Internally this function would pass the queue to some global pre-allocated
         // worker threads which are locked to a particular core.  While the main 
         // thread waits for the worker threads to finish it will process cells itself.
         // 
         cell = _generateCell(   newIndex - index, 
                                 bounds, 
                                 placementCount, 
                                 mRandomSeed + mAbs( newIndex.x ) + mAbs( newIndex.y ) );

         // Increment our generation count.
         ++cellsGenerated;
      }

      mCellGrid[ i ] = cell;
   }

   // Store the new grid index.
   mGridIndex = index;
}

bool GroundCover::prepRenderImage( 
   SceneState *state, 
   const U32 stateKey, 
   const U32 startZone, 
   const bool modifyBaseZoneState )
{
   // TODO: Educate myself... WTF does this accomplish?
   // Is it just a protection against double rendering?
   if ( isLastState( state, stateKey ) )
      return false;

   // Reset the rendering stats on a new scene state!
   //
   // TODO: This doesn't work with multiple ground cover
   // elements in a scene... fix me!
   //
   if ( stateKey != smLastState )
   {
      smStatRenderedCells = 0;
      smStatRenderedBillboards = 0;
      smStatRenderedBatches = 0;
      smStatRenderedShapes = 0;

      smLastState = stateKey;
   }

   setLastState( state, stateKey );

   // Check portal visibility.
   //
   // TODO: Make sure that the ground cover stops rendering
   // if you're inside a zoned interior.
   //
   if ( !state->isObjectRendered( this ) )
      return false;

   // Setup the frustum culler.
   if ( !mCuller.mSceneState || !mDebugLockFrustum )
      mCuller.init( state );

   // Update the cells.
   _updateCoverGrid( mCuller );

   // Prepare to render the grid shapes.
   PROFILE_START(GroundCover_RenderShapes);

   // Move all the allocated lights into the free list
   // to prepare for rendering.
   mLightAllocator->free();

   // We use a dummy helper object for tricking TSMesh
   // into rendering our instances.
   TSSceneObjectHelper helper;
   TSMesh::setObject( &helper );
   TSMesh::setCamTrans( GFX->getWorldMatrix() );
   TSMesh::setSceneState( state );

   GFX->pushState();
   GFX->pushWorldMatrix();

   const S32 cullerMasks = FrustrumCuller::ClipPlaneMask | FrustrumCuller::FarSphereMask;

   // TODO: Add a special fade out for DTS?
   mCuller.mFarDistance = mShapeCullRadius;

   for ( S32 i = 0; i < mCellGrid.size(); i++ )
   {
      GroundCoverCell* cell = mCellGrid[ i ];
      if ( !cell || mDebugNoShapes )
         continue;

      S32 clipMask = mCuller.testBoxVisibility( cell->getRenderBounds(), cullerMasks, 0 );
      if ( clipMask == -1 )
         continue;

      smStatRenderedCells++;

      // Render the shapes in this cell... only pass the culler if the
      // cell wasn't fully within the frustum.
      smStatRenderedShapes += cell->renderShapes(  
                                 state, 
                                 clipMask != 0 ? &mCuller : NULL, 
                                 helper,
                                 mLightAllocator,
                                 mShapeInstances );
   }

   // Cleanup.
   GFX->popWorldMatrix();
   GFX->popState();
   TSMesh::setObject( NULL );

   PROFILE_END(); // GroundCover_RenderShapes

   // Submit ourselves for rendering billboards later.
   RenderInst *ri = gRenderInstManager.allocInst();
   ri->obj = this;
   ri->state = state;
   ri->type = RenderInstManager::RIT_Foliage;
   gRenderInstManager.addInst( ri );

   return true;
}
#12
03/19/2008 (8:52 am)
void GroundCover::renderObject( SceneState *state, RenderInst *ri )
{
   // This sucks... there has got to be a better place
   // to get the camera transform!
   GameConnection* conn = GameConnection::getConnectionToServer();
   if ( !conn )
   {
      return;
   }

   if ( !mTerrainBlock )
   {
      return;
   }

   PROFILE_SCOPE(GroundCover_RenderBillboards);

   // Prepare to render.
   GFX->pushState();

   GFX->pushWorldMatrix();

   // Apply Z offset
   MatrixF world = GFX->getWorldMatrix();

   if(mZOffset != 0)
   {
      const F32 &zOffset = mZOffset;
      
      const F32 *b = world;
      world[8] = b[8] + zOffset * b[12];
      world[9] = b[9] + zOffset * b[13];
      world[10]= b[10]+ zOffset * b[14];
      world[11]= b[11]+ zOffset * b[15];

      GFX->setWorldMatrix( world );
   }

   // Set the projection and world transform info.
   MatrixF proj = GFX->getProjectionMatrix();
   proj.mul( world );
   proj.transpose();
   GFX->setVertexShaderConstF( 0, proj, 4 ); // VC_WORLD_PROJ

   F32 cullScale = 1.0f;
   if ( gClientSceneGraph->isReflectPass() )
      cullScale = mReflectRadiusScale;

   // Prepare to render some batches.
   MatrixF camMat;
   conn->getControlCameraTransform( 0, &camMat );
   GroundCoverCell::initRender(  ( mBBShader && mBBShader->shader ? mBBShader->shader : NULL ), 
                                 camMat, 
                                 mFadeRadius * cullScale, 
                                 mRadius * cullScale, 
                                 mDegToRad( mMaxBillboardTiltAngle ), 
                                 mTexture, 
                                 mBillboardRects );

   // Pass the damage mask from the terrain for hiding damaged billboards.
   GFX->setTexture( 2, NULL);//mTerrainBlock->getDamageMaskTexture() );
   GFX->setTextureStageAddressModeU( 2, GFXAddressWrap );
   GFX->setTextureStageAddressModeV( 2, GFXAddressWrap );
   GFX->setTextureStageMagFilter( 2, GFXTextureFilterPoint );
   GFX->setTextureStageMinFilter( 2, GFXTextureFilterPoint );
   Point3F terrainPos = mTerrainBlock->getPosition();
   // *** DSBOJ
//   Point4F terrainData( terrainPos.x, terrainPos.y, mTerrainBlock->getSquareSize() * TerrainBlock::BlockSize, 0.0f );
      Point4F terrainData( terrainPos.x, terrainPos.y, 1.5f * 512.0f, 0.0f );
   GFX->setVertexShaderConstF( 11, terrainData, 1 );

   // Pass in the sim time in seconds... wrap 
   // around when the time gets out of scale.
   const F32 simTime = Sim::getCurrentTime() * 0.001f;
   //GFX->setVertexShaderConstF( 12, (F32*)&simTime, 1 );

   // Pass the wind parameters.

   // *** DSBOJ

   Point2F windDirection( mWindDirection.x, mWindDirection.y );
   windDirection.normalize();
   GFX->setVertexShaderConstF( 12, windDirection, 1 );
   GFX->setVertexShaderConstF( 13, &mWindGustLength, 1 );
   F32 gustFrequency = mWindGustFrequency * simTime;
   GFX->setVertexShaderConstF( 14, &gustFrequency, 1 );
   GFX->setVertexShaderConstF( 15, &mWindGustStrength, 1 );
   F32 turbFrequency = mWindTurbulenceFrequency * simTime;
   GFX->setVertexShaderConstF( 16, &turbFrequency, 1 );
   GFX->setVertexShaderConstF( 17, &mWindTurbulenceStrength, 1 );

   // Set the far distance for billboards.
   mCuller.mFarDistance = mRadius;
   
   // Setup our culler mask here once for easy changing.
   const S32 cullerMasks = FrustrumCuller::ClipPlaneMask | FrustrumCuller::FarSphereMask;

   // Calculate the cell size plus a fudge factor
   // for searching for light and spheres.
   const F32 cellSearchDist = ( ( ( mRadius * cullScale ) * 2.0f ) / (F32)(mGridSize - 1) ) * 1.25f;

   // Grab the light manager for gathering dynamic lights!
   LightManager* lm = gClientSceneGraph->getLightManager();  
   LightInfoList lights;
   
   // Setup the sun direction and color.
   //LightInfo* sunLight = lm->getSpecialLight( LightManager::slSunLightType );
   //ColorF sunColor( sunLight->mColor );
   //sunColor *= 0.5f;
   //GFX->setVertexShaderConstF( 13, (F32*)&sunColor, 1 );
   //GFX->setVertexShaderConstF( 14, (F32*)&sunLight->mDirection, 1 );

   // We need to view direction for light and sphere selection!
   Point3F camDir;
   camMat.getColumn( 1, &camDir );

   for ( S32 i = 0; i < mCellGrid.size(); i++ )
   {
      GroundCoverCell* cell = mCellGrid[ i ];
      if ( !cell || mDebugNoBillboards )
         continue;

      S32 clipMask = mCuller.testBoxVisibility( cell->getRenderBounds(), cullerMasks, 0 );
      if ( clipMask == -1 )
         continue;

      // Find the best lights for this current view direction.
      //
      // NOTE: We use the grid size with a fudge for the far distance 
      // to help cull the lights better.  We're only looking for influences
      // that would be within this cell.
      //
      lm->setupLights( NULL, camMat.getPosition(), camDir, cellSearchDist, 4 );
      lm->getBestLights( lights );

      // Find the best wind emitters.
      WindEmitterList emitters;
      WindEmitter::findBest( camMat.getPosition(), camDir, cellSearchDist, 4, &emitters );

      // Render!
      smStatRenderedBillboards += cell->renderBillboards( lights, emitters, mPrimBuffer );

      // TODO: Eventually we may allow more than one billboard batch
      // per cell and we need to account for that!
      smStatRenderedBatches++;
   }

   // We must make a separate pass rendering the debug stuff
   // as the draw util will clobber the render state!
   if ( mDebugRenderCells )
   {
      // Used for debug drawing.
      GFXDrawUtil* drawer = GFX->getDrawUtil();
      drawer->clearBitmapModulation();

      for ( S32 i = 0; i < mCellGrid.size(); i++ )
      {
         GroundCoverCell* cell = mCellGrid[ i ];
         if ( !cell || ( cell->mBillboards.size() + cell->mShapes.size() ) == 0 )
            continue;

         S32 clipMask = mCuller.testBoxVisibility( cell->getRenderBounds(), cullerMasks, 0 );
         if ( clipMask == -1 )
            continue;
         
         drawer->drawWireCube( cell->getSize(), cell->getCenter(), ColorI( 0, 255, 0 ) );
      }
   }

   // Cleanup.
   GFX->popWorldMatrix();
   GFX->popState();
}

ConsoleStaticMethod( GroundCover, setQualityScale, F32, 2, 2, 
   "GroundCover.setQualityScale( F32 scale )\n"
   "Sets the global ground cover LOD scalar which controls "
   "the percentage of the maximum designed cover to put down. "
   "It scales both rendering cost and placement CPU performance. "
   "Returns the actual value set." )
{
   return GroundCover::setQualityScale( dAtof( argv[1] ) ); 
}

ConsoleStaticMethod( GroundCover, getQualityScale, F32, 1, 1, 
   "GroundCover.getQualityScale()\n"
   "Returns the global quality scale.  See GroundCover::setQualityScale()..." )
{
   return GroundCover::getQualityScale(); 
}
#13
03/19/2008 (8:59 am)
The areas that are changed all have //jc areas. Be sure to look over them since things are hardcoded, for example the tile size is hardcoded at 1.5. Hope its useful to someone. I'm going to sleep.

Here's a picture of it in Atlas:

71.18.167.9/foliagepic.jpg
#14
03/19/2008 (9:03 am)
Wow JC... you're all over it.
#15
03/23/2008 (8:41 am)
Thanks a lot JC...It would be probably be helpful to post this as a resource as well...that is after you have some well deserved sleep!
#16
03/23/2008 (7:14 pm)
It could use a little improvement first. I haven't had a chance to get back to it, as I've ran into some issues with getting PyTGE to play friendly with 1.7, which has kept me from getting my client ported over just yet. Once I get Repopulation working properly I'll go back to this, optimize it a little better, and adding in the ability to block it from certain areas/add shadows. Then it should be ready for a resource.
#17
03/24/2008 (5:08 pm)
Yeah, I can understand how you would want to make sure that it is polished before submitting it. I am very impressed that you were able to createthe ground cover for TGEA 1.7 so quickly after it was released though.
#18
03/28/2008 (12:53 am)
I put in some simple colormapping today for atlas. It basically just takes what was in place for legacy terrains, and converts it to atlas, allowing a file to be loaded as the colormap rather than pulling it from the legacy terrains shadowmap. It takes a colormap file specified by the lightmapfile setting in groundcover (which was added). Again, I did this for a personal project so things are kind of hacked into our specs, it assumes that the atlas file is at x and y position 0. It also calculates the distance at the moment just using a hardcoded number, though in the comments it tells you how to specify this for your map. Obviously it would be easy to turn that into a field but since all of our maps are 2048x2048x1.5 tile size, it wasn't necessary for me.

If you exported a lightmap from Grome or L3DT or something, load it up in photoshop or whatever image editing software you use. Rotate the canvas 90 degrees clockwise and then flip it horizontally. That will align the colormap properly. It should also be noted that this is an RGB colormap, which means that it doesn't have to only be used for shadows. You could paint it in hues to make certain areas of foliage darker than other, to simulate patches of dead grass or whatever. Anywho to the code:

In groundcover.cpp:

In GroundCover::Groundcover add this someplace:

mLightmapFile = StringTable->insert("scriptsandassets/data/terrains/lightmap");

Make sure you have a lightmap at this location, even if its just an 8x8 all white file if you don't have one. Otherwise you will crash. Again this is an area that would need cleaning up if this was a resource, but I don't plan on typing one up for a while at least, so just something you have to do on your own or bear with.

Somewhere in InitPersistFields() add this:

addField("lightmapFile", TypeFilename, Offset(mLightmapFile, GroundCover));

In PackUpdate() right after the stream->writeString( mTextureName ); line add this:

stream->writeString(mLightmapFile);

In UnpackUpdate() beneath the mTextureName = stream->readSTString(); line add this:

mLightmapFile = stream->readSTString();

In the initialize sectio after this line:

mTexture.set( mTextureName, &GFXDefaultStaticDiffuseProfile );//&GFXMaterialStaticDXT5Profile );

add in:

if ( mLightmapFile && mLightmapFile[0] )
		mLightmap = GBitmap::load(mLightmapFile); //jc lightmap

Now replace the line that was formerly:

const F32 oneOverTerrainLength = 1.0f / 512;

with:

const F32 oneOverTerrainLength = 1.0f / 3072;  //3072 =  (2048 heightmap * 1.5 height)

Some notes about that line, we came up with the 3072 number because we have a 2048 hieghtmap with a 1.5 tile size. 2048 * 1.5 = 3072. You'd need to adjust that for yoru map sizes, or turn it into a field.

Now there was a block of code that looked something like this before:

// if ( terrainLM ) //jc lightmap start
// {
// TODO: We could probably call terrainLM->getBits()
// once outside the loop then pre-calculate the scalar
// for converting a world position into a lexel...
// avoiding the extra protections inside of sampleTexel().

uv.x = (point.x + pos.x) * oneOverTerrainLength;
uv.y = (point.y + pos.y) * oneOverTerrainLength;
uv.x -= mFloor(uv.x);
uv.y -= mFloor(uv.y);

We want to change that to:

uv.x = (point.x) * oneOverTerrainLength;
            uv.y = (point.y) * oneOverTerrainLength;
            uv.x -= mFloor(uv.x);
            uv.y -= mFloor(uv.y);
	p.lmColor = mLightmap->sampleTexel(uv.x,uv.y);

Finally, be sure to add this to the protected area of your GroundCover.h file:

GBitmap *mLightmap; 
   StringTableEntry mLightmapFile;

Enjoy.
#19
03/28/2008 (4:22 am)
Layering is a feature that was commented out in the groundcover release. It looks like it was partially implemented but removed possibly until they could refine it more. It was legacy terrain specific though. I took a look at what had been done by GG and did a quick implementation for Atlas. Again with the disclaimer though that this was just something I did on a personal project and am releasing this in case it will help someone. It's pretty much project specific in some of the design here though, and is a pretty simplistic implementation.

In this case, I wanted to be able to reuse the same opacity map that I am going to reuse to determine which detail texture channel to use on atlas. In our case the red channel was used for the standard ground, grass or rock, channel 1 was used for cliff faces, channel 2 was used for roads or pavement in which we didn't want any foliage, and channel 4 is used as a map specific second detail texture. In the case of this layer implementation it uses that format and only places groundcover in areas that is free of the green or blue channels. To save video memory we assume that channel 0 is always on, and we use a 24 bit texture rather than a 32 bit texture and simply do our checks on the rgb as layers 1-3, and ignore 0. So basically it only places groundfoliage on areas that would use the first or second detail texture, and keeps them away from roads or cliffs. I did not bother to plug this into the groundcovers layering code, it basically does just as described above, no more or no less.

The first changes to be made are in GBitmap.h. We need to add in a new command to allow us to sample just a single channel. Add this into the public area:

Now let's open up groundcover.h and add the following code to the same area that you added it for the lightmap information, somewhere in protected:

GBitmap *mLayermap; 
   StringTableEntry mLayermapFile;

The next few steps are nearly identical to what we did for the colormap, but instead we're having it read in a layersmap. First in GroundCover::GroundCover():

mLayermapFile = StringTable->insert("scriptsandassets/data/terrains/layers");

Once again make sure you at least have a 32 bit png with all white channels as the layer file, or write some code to check to make sure that there is one being used. It may crash you otherwise. Now in InitPersistFields():

addField("layermapFile", TypeFilename, Offset(mLayermapFile, GroundCover));

In packupdate() underneath where we passed along the lightmapfile add:

stream->writeString(mLayermapFile);

Then in the same place (underneath the lightmapfile) in unpackupdate:

mLayermapFile = stream->readSTString();

Underneath where we initialize the lightmap in initialize():

if ( mLayermapFile && mLayermapFile[0] ) 
		mLayermap = GBitmap::load(mLayermapFile);

The rest of the code happens in generatecell. First underneath the F32 size; add this:

ColorF layertest;

Now a bit further down find the line that reads: rand.setSeed( randSeed + type ); and add this block of code directly ABOVE that code. Note that it is above and not below, as the code that was above that was commented out in my code and I'm not sure if you will still have it or not:

// note the way we are working it is so that only layers 0 and 3 can have foliage. maps must be designed around this
            uv.x = (bounds.min.x) * oneOverTerrainLength;
            uv.y = (bounds.min.y) * oneOverTerrainLength;
            uv.x -= mFloor(uv.x);
            uv.y -= mFloor(uv.y);
			layertest = mLayermap->sampleTexel(uv.x,uv.y); 
			if (layertest.red || layertest.green) 
				continue;

That should be everything. It's not a fancy bells and whistles implementation but it gets the job done.
#20
04/01/2008 (10:55 am)
Here's an image of the colormapping, layers and a mid-construction version of some split detail texture terrain, a bit more recent than the one above:

71.18.167.9/foliagecolorslayersterrain.jpg
Page «Previous 1 2