groundCover.cpp
Engine/source/T3D/fx/groundCover.cpp
Classes:
class
This defines one grid cell.
Public Functions
ConsoleDocClass(GroundCover , "@brief Covers the ground in 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.
Detailed Description
Public Functions
ConsoleDocClass(GroundCover , "@brief Covers the ground in 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/renderPrePassMgr.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 ColorF 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 GFXVertexColor color = (ColorI)(*iter).lmColor; 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(0, 0, 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; 427 428ConsoleDocClass( GroundCover, 429 "@brief Covers the ground in a field of objects (IE: Grass, Flowers, etc)." 430 "@ingroup Foliage\n" 431); 432 433GroundCover::GroundCover() 434{ 435 mTypeMask |= StaticObjectType | StaticShapeObjectType; 436 mNetFlags.set( Ghostable | ScopeAlways ); 437 438 mRadius = 200.0f; 439 mZOffset = 0.0f; 440 mFadeRadius = 50.0f; 441 mShapeCullRadius = 75.0f; 442 mShapesCastShadows = true; 443 mReflectRadiusScale = 0.25f; 444 445 mGridSize = 7; 446 447 // By initializing this to a big value we 448 // ensure we warp on first render. 449 mGridIndex.set( S32_MAX, S32_MAX ); 450 451 mMaxPlacement = 1000; 452 mLastPlacementCount = 0; 453 454 mDebugRenderCells = false; 455 mDebugNoBillboards = false; 456 mDebugNoShapes = false; 457 mDebugLockFrustum = false; 458 459 mRandomSeed = 1; 460 461 mMaterial = NULL; 462 mMatInst = NULL; 463 mMatParams = NULL; 464 mTypeRectsParam = NULL; 465 mFadeParams = NULL; 466 mWindDirParam = NULL; 467 mGustInfoParam = NULL; 468 mTurbInfoParam = NULL; 469 mCamRightParam = NULL; 470 mCamUpParam = NULL; 471 472 mMaxBillboardTiltAngle = 90.0f; 473 474 // TODO: This really doesn't belong here... we need a 475 // real wind system for Torque scenes. This data 476 // would be part of a global scene wind or area wind 477 // emitter. 478 // 479 // Tom Spilman - 10/16/2007 480 481 mWindGustLength = 20.0f; 482 mWindGustFrequency = 0.5f; 483 mWindGustStrength = 0.5f; 484 mWindDirection.set( 1.0f, 0.0f ); 485 mWindTurbulenceFrequency = 1.2f; 486 mWindTurbulenceStrength = 0.125f; 487 488 for ( S32 i=0; i < MAX_COVERTYPES; i++ ) 489 { 490 mProbability[i] = 0.0f; 491 492 mSizeMin[i] = 1.0f; 493 mSizeMax[i] = 1.0f; 494 mSizeExponent[i] = 1.0f; 495 496 mWindScale[i] = 1.0f; 497 498 mMaxSlope[i] = 0.0f; 499 500 mMinElevation[i] = -99999.0f; 501 mMaxElevation[i] = 99999.0f; 502 503 mLayer[i] = StringTable->EmptyString(); 504 mInvertLayer[i] = false; 505 506 mMinClumpCount[i] = 1; 507 mMaxClumpCount[i] = 1; 508 mClumpCountExponent[i] = 1.0f; 509 mClumpRadius[i] = 1.0f; 510 511 mBillboardRects[i].point.set( 0.0f, 0.0f ); 512 mBillboardRects[i].extent.set( 1.0f, 1.0f ); 513 514 mShapeFilenames[i] = NULL; 515 mShapeInstances[i] = NULL; 516 517 mBillboardAspectScales[i] = 1.0f; 518 519 mNormalizedProbability[i] = 0.0f; 520 } 521} 522 523GroundCover::~GroundCover() 524{ 525 SAFE_DELETE( mMatInst ); 526} 527 528IMPLEMENT_CO_NETOBJECT_V1(GroundCover); 529 530void GroundCover::initPersistFields() 531{ 532 addGroup( "GroundCover General" ); 533 534 addField( "material", TypeMaterialName, Offset( mMaterialName, GroundCover ), "Material used by all GroundCover segments." ); 535 536 addField( "radius", TypeF32, Offset( mRadius, GroundCover ), "Outer generation radius from the current camera position." ); 537 addField( "dissolveRadius",TypeF32, Offset( mFadeRadius, GroundCover ), "This is less than or equal to radius and defines when fading of cover elements begins." ); 538 addField( "reflectScale", TypeF32, Offset( mReflectRadiusScale, GroundCover ), "Scales the various culling radii when rendering a reflection. Typically for water." ); 539 540 addField( "gridSize", TypeS32, Offset( mGridSize, GroundCover ), "The number of cells per axis in the grid." ); 541 addField( "zOffset", TypeF32, Offset( mZOffset, GroundCover ), "Offset along the Z axis to render the ground cover." ); 542 543 addField( "seed", TypeS32, Offset( mRandomSeed, GroundCover ), "This RNG seed is saved and sent to clients for generating the same cover." ); 544 addField( "maxElements", TypeS32, Offset( mMaxPlacement, GroundCover ), "The maximum amount of cover elements to include in the grid at any one time." ); 545 546 addField( "maxBillboardTiltAngle", TypeF32, Offset( mMaxBillboardTiltAngle, GroundCover ),"The maximum amout of degrees the billboard will tilt down to match the camera." ); 547 addField( "shapeCullRadius", TypeF32, Offset( mShapeCullRadius, GroundCover ), "This is the distance at which DTS elements are completely culled out." ); 548 addField( "shapesCastShadows", TypeBool, Offset( mShapesCastShadows, GroundCover ), "Whether DTS elements should cast shadows or not." ); 549 550 addArray( "Types", MAX_COVERTYPES ); 551 552 addField( "billboardUVs", TypeRectUV, Offset( mBillboardRects, GroundCover ), MAX_COVERTYPES, "Subset material UV coordinates for this cover billboard." ); 553 554 addField( "shapeFilename", TypeFilename, Offset( mShapeFilenames, GroundCover ), MAX_COVERTYPES, "The cover shape filename. [Optional]" ); 555 556 addField( "layer", TypeTerrainMaterialName, Offset( mLayer, GroundCover ), MAX_COVERTYPES, "Terrain material name to limit coverage to, or blank to not limit." ); 557 558 addField( "invertLayer", TypeBool, Offset( mInvertLayer, GroundCover ), MAX_COVERTYPES, "Indicates that the terrain material index given in 'layer' is an exclusion mask." ); 559 560 addField( "probability", TypeF32, Offset( mProbability, GroundCover ), MAX_COVERTYPES, "The probability of one cover type verses another (relative to all cover types)." ); 561 562 addField( "sizeMin", TypeF32, Offset( mSizeMin, GroundCover ), MAX_COVERTYPES, "The minimum random size for each cover type." ); 563 564 addField( "sizeMax", TypeF32, Offset( mSizeMax, GroundCover ), MAX_COVERTYPES, "The maximum random size of this cover type." ); 565 566 addField( "sizeExponent", TypeF32, Offset( mSizeExponent, GroundCover ), MAX_COVERTYPES, "An exponent used to bias between the minimum and maximum random sizes." ); 567 568 addField( "windScale", TypeF32, Offset( mWindScale, GroundCover ), MAX_COVERTYPES, "The wind effect scale." ); 569 570 addField( "maxSlope", TypeF32, Offset( mMaxSlope, GroundCover ), MAX_COVERTYPES, "The maximum slope angle in degrees for placement." ); 571 572 addField( "minElevation", TypeF32, Offset( mMinElevation, GroundCover ), MAX_COVERTYPES, "The minimum world space elevation for placement." ); 573 574 addField( "maxElevation", TypeF32, Offset( mMaxElevation, GroundCover ), MAX_COVERTYPES, "The maximum world space elevation for placement." ); 575 576 addField( "minClumpCount", TypeS32, Offset( mMinClumpCount, GroundCover ), MAX_COVERTYPES, "The minimum amount of elements in a clump." ); 577 578 addField( "maxClumpCount", TypeS32, Offset( mMaxClumpCount, GroundCover ), MAX_COVERTYPES, "The maximum amount of elements in a clump." ); 579 580 addField( "clumpExponent", TypeF32, Offset( mClumpCountExponent, GroundCover ), MAX_COVERTYPES, "An exponent used to bias between the minimum and maximum clump counts for a particular clump." ); 581 582 addField( "clumpRadius", TypeF32, Offset( mClumpRadius, GroundCover ), MAX_COVERTYPES, "The maximum clump radius." ); 583 584 endArray( "Types" ); 585 586 endGroup( "GroundCover General" ); 587 588 addGroup( "GroundCover Wind" ); 589 590 addField( "windDirection", TypePoint2F, Offset( mWindDirection, GroundCover ), "The direction of the wind." ); 591 592 addField( "windGustLength", TypeF32, Offset( mWindGustLength, GroundCover ), "The length in meters between peaks in the wind gust." ); 593 addField( "windGustFrequency",TypeF32, Offset( mWindGustFrequency, GroundCover ), "Controls how often the wind gust peaks per second." ); 594 addField( "windGustStrength", TypeF32, Offset( mWindGustStrength, GroundCover ), "The maximum distance in meters that the peak wind gust will displace an element." ); 595 596 addField( "windTurbulenceFrequency", TypeF32, Offset( mWindTurbulenceFrequency, GroundCover ),"Controls the overall rapidity of the wind turbulence." ); 597 addField( "windTurbulenceStrength", TypeF32, Offset( mWindTurbulenceStrength, GroundCover ), "The maximum distance in meters that the turbulence can displace a ground cover element." ); 598 599 endGroup( "GroundCover Wind" ); 600 601 addGroup( "GroundCover Debug" ); 602 603 addField( "lockFrustum", TypeBool, Offset( mDebugLockFrustum, GroundCover ), "Debug parameter for locking the culling frustum which will freeze the cover generation." ); 604 addField( "renderCells", TypeBool, Offset( mDebugRenderCells, GroundCover ), "Debug parameter for displaying the grid cells." ); 605 addField( "noBillboards", TypeBool, Offset( mDebugNoBillboards, GroundCover ), "Debug parameter for turning off billboard rendering." ); 606 addField( "noShapes", TypeBool, Offset( mDebugNoShapes, GroundCover ), "Debug parameter for turning off shape rendering." ); 607 608 endGroup( "GroundCover Debug" ); 609 610 Parent::initPersistFields(); 611} 612 613void GroundCover::consoleInit() 614{ 615 Con::addVariable( "$pref::GroundCover::densityScale", TypeF32, &smDensityScale, "A global LOD scalar which can reduce the overall density of placed GroundCover.\n" 616 "@ingroup Foliage\n"); 617 618 Con::addVariable( "$GroundCover::renderedCells", TypeS32, &smStatRenderedCells, "Stat for number of rendered cells.\n" 619 "@ingroup Foliage\n"); 620 Con::addVariable( "$GroundCover::renderedBillboards", TypeS32, &smStatRenderedBillboards, "Stat for number of rendered billboards.\n" 621 "@ingroup Foliage\n"); 622 Con::addVariable( "$GroundCover::renderedBatches", TypeS32, &smStatRenderedBatches, "Stat for number of rendered billboard batches.\n" 623 "@ingroup Foliage\n"); 624 Con::addVariable( "$GroundCover::renderedShapes", TypeS32, &smStatRenderedShapes, "Stat for number of rendered shapes.\n" 625 "@ingroup Foliage\n"); 626 627 Parent::consoleInit(); 628} 629 630bool GroundCover::onAdd() 631{ 632 if (!Parent::onAdd()) 633 return false; 634 635 // We don't use any bounds. 636 setGlobalBounds(); 637 638 resetWorldBox(); 639 640 // Prepare some client side things. 641 if ( isClientObject() ) 642 { 643 _initMaterial(); 644 645 _initShapes(); 646 647 // Hook ourselves up to get terrain change notifications. 648 TerrainBlock::smUpdateSignal.notify( this, &GroundCover::onTerrainUpdated ); 649 } 650 651 addToScene(); 652 653 return true; 654} 655 656void GroundCover::onRemove() 657{ 658 Parent::onRemove(); 659 660 _deleteCells(); 661 _deleteShapes(); 662 663 if ( isClientObject() ) 664 { 665 TerrainBlock::smUpdateSignal.remove( this, &GroundCover::onTerrainUpdated ); 666 } 667 668 removeFromScene(); 669} 670 671void GroundCover::inspectPostApply() 672{ 673 Parent::inspectPostApply(); 674 675 // We flag all the parameters as changed because 676 // we're feeling lazy and there is not a good way 677 // to track what parameters changed. 678 // 679 // TODO: Add a mask bit option to addField() and/or 680 // addGroup() which is passed to inspectPostApply 681 // for detection of changed elements. 682 // 683 setMaskBits(U32(-1) ); 684} 685 686U32 GroundCover::packUpdate( NetConnection *connection, U32 mask, BitStream *stream ) 687{ 688 U32 retMask = Parent::packUpdate( connection, mask, stream ); 689 690 if (stream->writeFlag(mask & InitialUpdateMask)) 691 { 692 // TODO: We could probably optimize a few of these 693 // based on reasonable units at some point. 694 695 stream->write( mMaterialName ); 696 697 stream->write( mRadius ); 698 stream->write( mZOffset ); 699 stream->write( mFadeRadius ); 700 stream->write( mShapeCullRadius ); 701 stream->writeFlag( mShapesCastShadows ); 702 stream->write( mReflectRadiusScale ); 703 stream->write( mGridSize ); 704 stream->write( mRandomSeed ); 705 stream->write( mMaxPlacement ); 706 stream->write( mMaxBillboardTiltAngle ); 707 708 stream->write( mWindDirection.x ); 709 stream->write( mWindDirection.y ); 710 stream->write( mWindGustLength ); 711 stream->write( mWindGustFrequency ); 712 stream->write( mWindGustStrength ); 713 stream->write( mWindTurbulenceFrequency ); 714 stream->write( mWindTurbulenceStrength ); 715 716 for ( S32 i=0; i < MAX_COVERTYPES; i++ ) 717 { 718 stream->write( mProbability[i] ); 719 stream->write( mSizeMin[i] ); 720 stream->write( mSizeMax[i] ); 721 stream->write( mSizeExponent[i] ); 722 stream->write( mWindScale[i] ); 723 724 stream->write( mMaxSlope[i] ); 725 726 stream->write( mMinElevation[i] ); 727 stream->write( mMaxElevation[i] ); 728 729 stream->writeString( mLayer[i] ); 730 stream->writeFlag( mInvertLayer[i] ); 731 732 stream->write( mMinClumpCount[i] ); 733 stream->write( mMaxClumpCount[i] ); 734 stream->write( mClumpCountExponent[i] ); 735 stream->write( mClumpRadius[i] ); 736 737 stream->write( mBillboardRects[i].point.x ); 738 stream->write( mBillboardRects[i].point.y ); 739 stream->write( mBillboardRects[i].extent.x ); 740 stream->write( mBillboardRects[i].extent.y ); 741 742 stream->writeString( mShapeFilenames[i] ); 743 } 744 745 stream->writeFlag( mDebugRenderCells ); 746 stream->writeFlag( mDebugNoBillboards ); 747 stream->writeFlag( mDebugNoShapes ); 748 stream->writeFlag( mDebugLockFrustum ); 749 } 750 751 return retMask; 752} 753 754void GroundCover::unpackUpdate( NetConnection *connection, BitStream *stream ) 755{ 756 Parent::unpackUpdate( connection, stream ); 757 758 if (stream->readFlag()) 759 { 760 stream->read( &mMaterialName ); 761 762 stream->read( &mRadius ); 763 stream->read( &mZOffset ); 764 stream->read( &mFadeRadius ); 765 stream->read( &mShapeCullRadius ); 766 mShapesCastShadows = stream->readFlag(); 767 stream->read( &mReflectRadiusScale ); 768 stream->read( &mGridSize ); 769 stream->read( &mRandomSeed ); 770 stream->read( &mMaxPlacement ); 771 stream->read( &mMaxBillboardTiltAngle ); 772 773 stream->read( &mWindDirection.x ); 774 stream->read( &mWindDirection.y ); 775 stream->read( &mWindGustLength ); 776 stream->read( &mWindGustFrequency ); 777 stream->read( &mWindGustStrength ); 778 stream->read( &mWindTurbulenceFrequency ); 779 stream->read( &mWindTurbulenceStrength ); 780 781 for ( S32 i=0; i < MAX_COVERTYPES; i++ ) 782 { 783 stream->read( &mProbability[i] ); 784 stream->read( &mSizeMin[i] ); 785 stream->read( &mSizeMax[i] ); 786 stream->read( &mSizeExponent[i] ); 787 stream->read( &mWindScale[i] ); 788 789 stream->read( &mMaxSlope[i] ); 790 791 stream->read( &mMinElevation[i] ); 792 stream->read( &mMaxElevation[i] ); 793 794 mLayer[i] = stream->readSTString(); 795 mInvertLayer[i] = stream->readFlag(); 796 797 stream->read( &mMinClumpCount[i] ); 798 stream->read( &mMaxClumpCount[i] ); 799 stream->read( &mClumpCountExponent[i] ); 800 stream->read( &mClumpRadius[i] ); 801 802 stream->read( &mBillboardRects[i].point.x ); 803 stream->read( &mBillboardRects[i].point.y ); 804 stream->read( &mBillboardRects[i].extent.x ); 805 stream->read( &mBillboardRects[i].extent.y ); 806 807 mShapeFilenames[i] = stream->readSTString(); 808 } 809 810 mDebugRenderCells = stream->readFlag(); 811 mDebugNoBillboards = stream->readFlag(); 812 mDebugNoShapes = stream->readFlag(); 813 mDebugLockFrustum = stream->readFlag(); 814 815 // We have no way to easily know what changed, so by clearing 816 // the cells we force a reinit and regeneration of the cells. 817 // It's sloppy, but it works for now. 818 _freeCells(); 819 820 if ( isProperlyAdded() ) 821 _initMaterial(); 822 } 823} 824 825void GroundCover::_initMaterial() 826{ 827 SAFE_DELETE( mMatInst ); 828 829 if ( mMaterialName.isNotEmpty() ) 830 if ( !Sim::findObject( mMaterialName, mMaterial ) ) 831 Con::errorf( "GroundCover::_initMaterial - Material %s was not found.", mMaterialName.c_str() ); 832 833 if ( mMaterial ) 834 mMatInst = mMaterial->createMatInstance(); 835 else 836 mMatInst = MATMGR->createMatInstance( "WarningMaterial" ); 837 838 // Add our special feature that makes it all work... 839 FeatureSet features = MATMGR->getDefaultFeatures(); 840 features.addFeature( MFT_Foliage ); 841 842 // Our feature requires a pointer back to this object 843 // to properly setup its shader consts. 844 mMatInst->setUserObject( this ); 845 846 // DO IT! 847 mMatInst->init( features, getGFXVertexFormat<GCVertex>() ); 848} 849 850void GroundCover::_initShapes() 851{ 852 _deleteShapes(); 853 854 for ( S32 i=0; i < MAX_COVERTYPES; i++ ) 855 { 856 if ( !mShapeFilenames[i] || !mShapeFilenames[i][0] ) 857 continue; 858 859 // Load the shape. 860 Resource<TSShape> shape = ResourceManager::get().load(mShapeFilenames[i]); 861 if ( !(bool)shape ) 862 { 863 Con::warnf( "GroundCover::_initShapes() unable to load shape: %s", mShapeFilenames[i] ); 864 continue; 865 } 866 867 if ( isClientObject() && !shape->preloadMaterialList(shape.getPath()) && NetConnection::filesWereDownloaded() ) 868 { 869 Con::warnf( "GroundCover::_initShapes() material preload failed for shape: %s", mShapeFilenames[i] ); 870 continue; 871 } 872 873 // Create the shape instance. 874 mShapeInstances[i] = new TSShapeInstance( shape, isClientObject() ); 875 } 876} 877 878void GroundCover::_deleteShapes() 879{ 880 for ( S32 i=0; i < MAX_COVERTYPES; i++ ) 881 { 882 delete mShapeInstances[i]; 883 mShapeInstances[i] = NULL; 884 } 885} 886 887void GroundCover::_deleteCells() 888{ 889 // Delete the allocation list. 890 for ( S32 i=0; i < mAllocCellList.size(); i++ ) 891 delete mAllocCellList[i]; 892 mAllocCellList.clear(); 893 894 // Zero out the rest of the stuff. 895 _freeCells(); 896} 897 898void GroundCover::_freeCells() 899{ 900 // Zero the grid and scratch space. 901 mCellGrid.clear(); 902 mScratchGrid.clear(); 903 904 // Compact things... remove excess allocated cells. 905 const U32 maxCells = mGridSize * mGridSize; 906 if ( mAllocCellList.size() > maxCells ) 907 { 908 for ( S32 i=maxCells; i < mAllocCellList.size(); i++ ) 909 delete mAllocCellList[i]; 910 mAllocCellList.setSize( maxCells ); 911 } 912 913 // Move all the alloced cells into the free list. 914 mFreeCellList.clear(); 915 mFreeCellList.merge( mAllocCellList ); 916 917 // Release the primitive buffer. 918 mPrimBuffer = NULL; 919} 920 921void GroundCover::_recycleCell( GroundCoverCell* cell ) 922{ 923 mFreeCellList.push_back( cell ); 924} 925 926void GroundCover::_initialize( U32 cellCount, U32 cellPlacementCount ) 927{ 928 // Cleanup everything... we're starting over. 929 _freeCells(); 930 _deleteShapes(); 931 932 // Nothing to do without a count! 933 if ( cellPlacementCount == 0 ) 934 return; 935 936 // Reset the grid sizes. 937 mCellGrid.setSize( cellCount ); 938 dMemset( mCellGrid.address(), 0, mCellGrid.memSize() ); 939 mScratchGrid.setSize( cellCount ); 940 941 // Rebuild the texture aspect scales for each type. 942 F32 textureAspect = 1.0f; 943 if( mMatInst && mMatInst->isValid()) 944 { 945 Material* mat = dynamic_cast<Material*>(mMatInst->getMaterial()); 946 if(mat) 947 { 948 GFXTexHandle tex(mat->mDiffuseMapFilename[0], &GFXDefaultStaticDiffuseProfile, "GroundCover texture aspect ratio check" ); 949 if(tex.isValid()) 950 { 951 U32 w = tex.getWidth(); 952 U32 h = tex.getHeight(); 953 if(h > 0) 954 textureAspect = F32(w) / F32(h); 955 } 956 } 957 } 958 for ( S32 i=0; i < MAX_COVERTYPES; i++ ) 959 { 960 if ( mBillboardRects[i].extent.y > 0.0f ) 961 { 962 mBillboardAspectScales[i] = textureAspect * mBillboardRects[i].extent.x / mBillboardRects[i].extent.y; 963 } 964 else 965 mBillboardAspectScales[i] = 0.0f; 966 } 967 968 // Load the shapes again. 969 _initShapes(); 970 971 // Set the primitive buffer up for the maximum placement in a cell. 972 mPrimBuffer.set( GFX, cellPlacementCount * 6, 0, GFXBufferTypeStatic ); 973 U16 *idxBuff; 974 mPrimBuffer.lock(&idxBuff); 975 for ( U32 i=0; i < cellPlacementCount; i++ ) 976 { 977 // 978 // The vertex pattern in the VB for each 979 // billboard is as follows... 980 // 981 // 0----1 982 // |\ | 983 // | \ | 984 // | \ | 985 // | \| 986 // 3----2 987 // 988 // We setup the index order below to ensure 989 // sequential, cache friendly, access. 990 // 991 U32 offset = i * 4; 992 idxBuff[i*6+0] = 0 + offset; 993 idxBuff[i*6+1] = 1 + offset; 994 idxBuff[i*6+2] = 2 + offset; 995 idxBuff[i*6+3] = 2 + offset; 996 idxBuff[i*6+4] = 3 + offset; 997 idxBuff[i*6+5] = 0 + offset; 998 } 999 mPrimBuffer.unlock(); 1000 1001 // Generate the normalized probability. 1002 F32 total = 0.0f; 1003 for ( S32 i=0; i < MAX_COVERTYPES; i++ ) 1004 { 1005 // If the element isn't gonna render... then 1006 // set the probability to zero. 1007 if ( mShapeInstances[i] == NULL && mBillboardAspectScales[i] <= 0.0001f ) 1008 { 1009 mNormalizedProbability[i] = 0.0f; 1010 } 1011 else 1012 { 1013 mNormalizedProbability[i] = mProbability[i]; 1014 1015 total += mProbability[i]; 1016 } 1017 } 1018 if ( total > 0.0f ) 1019 { 1020 for ( S32 i=0; i < MAX_COVERTYPES; i++ ) 1021 mNormalizedProbability[i] /= total; 1022 } 1023} 1024 1025GroundCoverCell* GroundCover::_generateCell( const Point2I& index, 1026 const Box3F& bounds, 1027 U32 placementCount, 1028 S32 randSeed ) 1029{ 1030 PROFILE_SCOPE(GroundCover_GenerateCell); 1031 1032 const Vector<SceneObject*> terrainBlocks = getContainer()->getTerrains(); 1033 if ( terrainBlocks.empty() ) 1034 return NULL; 1035 1036 // Grab a free cell or allocate a new one. 1037 GroundCoverCell* cell; 1038 if ( mFreeCellList.empty() ) 1039 { 1040 cell = new GroundCoverCell(); 1041 mAllocCellList.push_back( cell ); 1042 } 1043 else 1044 { 1045 cell = mFreeCellList.last(); 1046 mFreeCellList.pop_back(); 1047 } 1048 1049 cell->mDirty = true; 1050 cell->mIndex = index; 1051 cell->mBounds = bounds; 1052 1053 Point3F pos( 0, 0, 0 ); 1054 1055 Box3F renderBounds = bounds; 1056 Point3F point; 1057 Point3F normal; 1058 F32 h; 1059 Point2F cp, uv; 1060 bool hit; 1061 GroundCoverCell::Placement p; 1062 F32 rotation; 1063 F32 size; 1064 F32 sizeExponent; 1065 Point2I lpos; 1066 //F32 value; 1067 VectorF right; 1068 StringTableEntry matName = StringTable->EmptyString(); 1069 bool firstElem = true; 1070 1071 TerrainBlock *terrainBlock = NULL; 1072 1073 cell->mBillboards.clear(); 1074 cell->mBillboards.reserve( placementCount ); 1075 cell->mShapes.clear(); 1076 cell->mShapes.reserve( placementCount ); 1077 1078 F32 terrainSquareSize, 1079 oneOverTerrainLength, 1080 oneOverTerrainSquareSize; 1081 const GBitmap* terrainLM = NULL; 1082 1083 // The RNG that we'll use in generation. 1084 MRandom rand( 0 ); 1085 1086 // We process one type at a time. 1087 for ( U32 type=0; type < MAX_COVERTYPES; type++ ) 1088 { 1089 // How many cover elements do we need to generate for this type? 1090 const S32 typeCount = mNormalizedProbability[type] * (F32)placementCount; 1091 if ( typeCount <= 0 ) 1092 continue; 1093 1094 // Grab the terrain layer for this type. 1095 /* 1096 const TerrainDataLayer* dataLayer = NULL; 1097 const bool typeInvertLayer = mInvertLayer[type]; 1098 if ( mLayer[type] > -1 ) 1099 { 1100 dataLayer = mTerrainBlock->getDataLayer( mLayer[type] ); 1101 if ( dataLayer ) 1102 { 1103 // Do an initial check to see if we can place any place anything 1104 // at all... if the layer area for this element is empty then 1105 // there is nothing more to do. 1106 1107 RectI area( (S32)mFloor( ( bounds.minExtents.x - pos.x ) * oneOverTerrainSquareSize ), 1108 (S32)mFloor( ( bounds.minExtents.y - pos.y ) * oneOverTerrainSquareSize ), 1109 (S32)mCeil( ( bounds.maxExtents.x - pos.x ) * oneOverTerrainSquareSize ), 1110 (S32)mCeil( ( bounds.maxExtents.y - pos.y ) * oneOverTerrainSquareSize ) ); 1111 area.extent -= area.point; 1112 1113 if ( dataLayer->testFill( area, typeInvertLayer ? 255 : 0 ) ) 1114 continue; 1115 } 1116 } 1117 1118 // If the layer is not inverted and we have no data 1119 // then we have nothing to draw. 1120 if ( !typeInvertLayer && !dataLayer ) 1121 continue; 1122 */ 1123 1124 // We set the seed we were passed which is based on this grids position 1125 // in the world and add the type value. This keeps changes to one type 1126 // from effecting the outcome of the others. 1127 rand.setSeed( randSeed + type ); 1128 1129 // Setup for doing clumps. 1130 S32 clumps = 0; 1131 Point2F clumpCenter(0.0f, 0.0f); 1132 const S32 clumpMin = getMax( 1, (S32)mMinClumpCount[type] ); 1133 F32 clumpExponent; 1134 1135 // We mult this by -1 each billboard we make then use 1136 // it to scale the billboard x axis to flip them. This 1137 // essentially gives us twice the variation for free. 1138 F32 flipBB = -1.0f; 1139 1140 // Precompute a few other type specific values. 1141 const F32 typeSizeRange = mSizeMax[type] - mSizeMin[type]; 1142 const F32 typeMaxSlope = mMaxSlope[type]; 1143 const F32 typeMaxElevation = mMaxElevation[type]; 1144 const F32 typeMinElevation = mMinElevation[type]; 1145 const bool typeIsShape = mShapeInstances[ type ] != NULL; 1146 const Box3F typeShapeBounds = typeIsShape ? mShapeInstances[ type ]->getShape()->bounds : Box3F(); 1147 const F32 typeWindScale = mWindScale[type]; 1148 StringTableEntry typeLayer = mLayer[type]; 1149 const bool typeInvertLayer = mInvertLayer[type]; 1150 1151 // We can set this once here... all the placements for this are the same. 1152 p.type = type; 1153 p.windAmplitude = typeWindScale; 1154 p.lmColor.set(1.0f,1.0f,1.0f); 1155 1156 // Generate all the cover elements for this type. 1157 for ( S32 i=0; i < typeCount; i++ ) 1158 { 1159 // Do all the other random things here first as to not 1160 // disturb the random sequence if the terrain geometry 1161 // or cover layers change. 1162 1163 // Get the random position. 1164 cp.set( rand.randF(), rand.randF() ); 1165 1166 // Prepare the clump info. 1167 clumpExponent = mClampF( mPow( rand.randF(), mClumpCountExponent[type] ), 0.0f, 1.0f ); 1168 if ( clumps <= 0 ) 1169 { 1170 // We're starting a new clump. 1171 clumps = ( clumpMin + mFloor( ( mMaxClumpCount[type] - clumpMin ) * clumpExponent ) ) - 1; 1172 cp.set( bounds.minExtents.x + cp.x * bounds.len_x(), 1173 bounds.minExtents.y + cp.y * bounds.len_y() ); 1174 clumpCenter = cp; 1175 } 1176 else 1177 { 1178 clumps--; 1179 cp.set( clumpCenter.x - ( ( cp.x - 0.5f ) * mClumpRadius[type] ), 1180 clumpCenter.y - ( ( cp.y - 0.5f ) * mClumpRadius[type] ) ); 1181 } 1182 1183 // Which terrain do I place on? 1184 if ( terrainBlocks.size() == 1 ) 1185 terrainBlock = dynamic_cast< TerrainBlock* >( terrainBlocks.first() ); 1186 else 1187 { 1188 for ( U32 i = 0; i < terrainBlocks.size(); i++ ) 1189 { 1190 TerrainBlock *terrain = dynamic_cast< TerrainBlock* >( terrainBlocks[ i ] ); 1191 if( !terrain ) 1192 continue; 1193 1194 const Box3F &terrBounds = terrain->getWorldBox(); 1195 1196 if ( cp.x < terrBounds.minExtents.x || cp.x > terrBounds.maxExtents.x || 1197 cp.y < terrBounds.minExtents.y || cp.y > terrBounds.maxExtents.y ) 1198 continue; 1199 1200 terrainBlock = terrain; 1201 break; 1202 } 1203 } 1204 1205 // This should only happen if the generation went off 1206 // the edge of the terrain blocks. 1207 if ( !terrainBlock ) 1208 continue; 1209 1210 terrainLM = terrainBlock->getLightMap(); 1211 pos = terrainBlock->getPosition(); 1212 1213 terrainSquareSize = (F32)terrainBlock->getSquareSize(); 1214 oneOverTerrainLength = 1.0f / terrainBlock->getWorldBlockSize(); 1215 oneOverTerrainSquareSize = 1.0f / terrainSquareSize; 1216 1217 // The size is calculated using an exponent to control 1218 // the frequency between min and max sizes. 1219 sizeExponent = mClampF( mPow( rand.randF(), mSizeExponent[type] ), 0.0f, 1.0f ); 1220 size = mSizeMin[type] + ( typeSizeRange * sizeExponent ); 1221 1222 // Generate a random z rotation. 1223 rotation = rand.randF() * M_2PI_F; 1224 1225 // Flip the billboard now for the next generation. 1226 flipBB *= -1.0f; 1227 1228 PROFILE_START( GroundCover_TerrainRayCast ); 1229 // Transform billboard point into terrain's frame of reference. 1230 Point3F pp = Point3F(cp.x, cp.y, 0); 1231 terrainBlock->getWorldTransform().mulP(pp); 1232 hit = terrainBlock->getNormalHeightMaterial( Point2F ( pp.x, pp.y ), 1233 &normal, &h, matName ); 1234 PROFILE_END(); // GroundCover_TerrainRayCast 1235 1236 // TODO: When did we loose the world space elevation when 1237 // getting the terrain height? 1238 h += pos.z + mZOffset; 1239 1240 if ( !hit || h > typeMaxElevation || h < typeMinElevation || 1241 ( typeLayer[0] && !typeInvertLayer && matName != typeLayer ) || 1242 ( typeLayer[0] && typeInvertLayer && matName == typeLayer ) ) 1243 continue; 1244 1245 // Do we need to check slope? 1246 if ( !mIsZero( typeMaxSlope ) ) 1247 { 1248 if (mAcos(normal.z) > mDegToRad(typeMaxSlope)) 1249 continue; 1250 } 1251 1252 point.set( cp.x, cp.y, h ); 1253 p.point = point; 1254 p.rotation = rotation; 1255 p.normal = normal; 1256 1257 // Grab the terrain lightmap color at this position. 1258 // 1259 // TODO: Can't we remove this test? The terrain 1260 // lightmap should never be null... NEVER! 1261 // 1262 if ( terrainLM ) 1263 { 1264 // TODO: We could probably call terrainLM->getBits() 1265 // once outside the loop then pre-calculate the scalar 1266 // for converting a world position into a lexel... 1267 // avoiding the extra protections inside of sampleTexel(). 1268 1269 uv.x = (point.x + pos.x) * oneOverTerrainLength; 1270 uv.y = (point.y + pos.y) * oneOverTerrainLength; 1271 uv.x -= mFloor(uv.x); 1272 uv.y -= mFloor(uv.y); 1273 p.lmColor = terrainLM->sampleTexel(uv.x,uv.y); 1274 } 1275 1276 // Put it into the right list by type. 1277 // 1278 // TODO: Could we break up the generation into 1279 // two separate loops for shapes and billboards 1280 // and gain performance? 1281 // 1282 if ( typeIsShape ) 1283 { 1284 // TODO: Convert the size into a real size... not scale! 1285 1286 // TODO: We could probably cache the shape bounds 1287 // into a primitive array and avoid the double pointer 1288 // dereference per placement. 1289 1290 p.size.set( size, size, size ); 1291 p.worldBox = typeShapeBounds; 1292 p.worldBox.minExtents *= size; 1293 p.worldBox.maxExtents *= size; 1294 p.worldBox.minExtents += point; 1295 p.worldBox.maxExtents += point; 1296 1297 cell->mShapes.push_back( p ); 1298 } 1299 else 1300 { 1301 p.size.y = size; 1302 p.size.x = size * flipBB * mBillboardAspectScales[type]; 1303 p.worldBox.maxExtents = p.worldBox.minExtents = point; 1304 1305 cell->mBillboards.push_back( p ); 1306 } 1307 1308 // Update the render bounds. 1309 if ( firstElem ) 1310 { 1311 renderBounds = p.worldBox; 1312 firstElem = false; 1313 } 1314 else 1315 { 1316 renderBounds.extend( p.worldBox.minExtents ); 1317 renderBounds.extend( p.worldBox.maxExtents ); 1318 } 1319 1320 } // for ( S32 i=0; i < typeCount; i++ ) 1321 1322 } // for ( U32 type=0; type < NumCoverTypes; type++ ) 1323 1324 1325 cell->mRenderBounds = renderBounds; 1326 cell->mBounds.minExtents.z = renderBounds.minExtents.z; 1327 cell->mBounds.maxExtents.z = renderBounds.maxExtents.z; 1328 1329 return cell; 1330} 1331 1332void GroundCover::onTerrainUpdated( U32 flags, TerrainBlock *tblock, const Point2I& min, const Point2I& max ) 1333{ 1334 if ( isServerObject() ) 1335 return; 1336 1337 // Free all the cells if we've gotten a lightmap update. 1338 if ( flags & TerrainBlock::LightmapUpdate ) 1339 { 1340 _freeCells(); 1341 return; 1342 } 1343 1344 // TODO: EmptyUpdate doesn't work yet... fix editor/terrain. 1345 1346 // If this is a height or opacity update only clear 1347 // the cells that have changed. 1348 if ( flags & TerrainBlock::HeightmapUpdate || 1349 flags & TerrainBlock::LayersUpdate || 1350 flags & TerrainBlock::EmptyUpdate ) 1351 { 1352 // Convert the min and max into world space. 1353 const F32 size = tblock->getSquareSize(); 1354 const Point3F pos = tblock->getPosition(); 1355 1356 // TODO: I don't think this works right with tiling! 1357 Box3F dirty( F32( min.x * size ) + pos.x, F32( min.y * size ) + pos.y, 0.0f, 1358 F32( max.x * size ) + pos.x, F32( max.y * size ) + pos.y, 0.0f ); 1359 1360 // Now free any cells that overlap it! 1361 for ( S32 i = 0; i < mCellGrid.size(); i++ ) 1362 { 1363 GroundCoverCell* cell = mCellGrid[ i ]; 1364 if ( !cell ) 1365 continue; 1366 1367 const Box3F& bounds = cell->getBounds(); 1368 dirty.minExtents.z = bounds.minExtents.z; 1369 dirty.maxExtents.z = bounds.maxExtents.z; 1370 if ( bounds.isOverlapped( dirty ) ) 1371 { 1372 mCellGrid[ i ] = NULL; 1373 _recycleCell( cell ); 1374 } 1375 } 1376 } 1377} 1378 1379void GroundCover::_updateCoverGrid( const Frustum &culler ) 1380{ 1381 PROFILE_SCOPE( GroundCover_UpdateCoverGrid ); 1382 1383 mGridSize = getMax( mGridSize, (U32)2 ); 1384 1385 // How many cells in the grid? 1386 const U32 cells = mGridSize * mGridSize; 1387 1388 // Whats the max placement count for each cell considering 1389 // the grid size and quality scale LOD value. 1390 const S32 placementCount = getMax( ( (F32)mMaxPlacement * smDensityScale ) / F32( mGridSize * mGridSize ), 0.0f ); 1391 1392 // If the cell grid isn't sized or the placement count 1393 // changed (most likely because of quality lod) then we 1394 // need to initialize the system again. 1395 if ( mCellGrid.empty() || placementCount != mLastPlacementCount ) 1396 { 1397 _initialize( cells, placementCount ); 1398 mLastPlacementCount = placementCount; 1399 } 1400 1401 // Without a count... we don't function at all. 1402 if ( placementCount == 0 ) 1403 return; 1404 1405 // Clear the scratch grid. 1406 dMemset( mScratchGrid.address(), 0, mScratchGrid.memSize() ); 1407 1408 // Calculate the normal cell size here. 1409 const F32 cellSize = ( mRadius * 2.0f ) / (F32)(mGridSize - 1); 1410 1411 // Figure out the root index of the new grid based on the camera position. 1412 Point2I index( (S32)mFloor( ( culler.getPosition().x - mRadius ) / cellSize ), 1413 (S32)mFloor( ( culler.getPosition().y - mRadius ) / cellSize ) ); 1414 1415 // Figure out the cell shift between the old and new grid positions. 1416 Point2I shift = mGridIndex - index; 1417 1418 // If we've shifted more than one in either axis then we've warped. 1419 bool didWarp = shift.x > 1 || shift.x < -1 || 1420 shift.y > 1 || shift.y < -1 ? true : false; 1421 1422 // Go thru the grid shifting each cell we find and 1423 // placing them in the scratch grid. 1424 for ( S32 i = 0; i < mCellGrid.size(); i++ ) 1425 { 1426 GroundCoverCell* cell = mCellGrid[ i ]; 1427 if ( !cell ) 1428 continue; 1429 1430 // Whats our new index? 1431 Point2I newIndex = cell->shiftIndex( shift ); 1432 1433 // Is this cell outside of the new grid? 1434 if ( newIndex.x < 0 || newIndex.x >= mGridSize || 1435 newIndex.y < 0 || newIndex.y >= mGridSize ) 1436 { 1437 _recycleCell( cell ); 1438 continue; 1439 } 1440 1441 // Place the cell in the scratch grid. 1442 mScratchGrid[ ( newIndex.y * mGridSize ) + newIndex.x ] = cell; 1443 } 1444 1445 // Get the terrain elevation range for setting the default cell bounds. 1446 F32 terrainMinHeight = -5000.0f, 1447 terrainMaxHeight = 5000.0f; 1448 1449 // Go thru the scratch grid copying each cell back to the 1450 // cell grid and creating new cells as needed. 1451 // 1452 // By limiting ourselves to only one new cell generation per 1453 // update we're lowering the performance hiccup during movement 1454 // without getting into the complexity of threading. The delay 1455 // in generation is rarely noticeable in normal play. 1456 // 1457 // The only caveat is that we need to generate the entire visible 1458 // grid when we warp. 1459 U32 cellsGenerated = 0; 1460 for ( S32 i = 0; i < mScratchGrid.size(); i++ ) 1461 { 1462 GroundCoverCell* cell = mScratchGrid[ i ]; 1463 if ( !cell && ( cellsGenerated == 0 || didWarp ) ) 1464 { 1465 // Get the index point of this new cell. 1466 S32 y = i / mGridSize; 1467 S32 x = i - ( y * mGridSize ); 1468 Point2I newIndex = index + Point2I( x, y ); 1469 1470 // What will be the world placement bounds for this cell. 1471 Box3F bounds; 1472 bounds.minExtents.set( newIndex.x * cellSize, newIndex.y * cellSize, terrainMinHeight ); 1473 bounds.maxExtents.set( bounds.minExtents.x + cellSize, bounds.minExtents.y + cellSize, terrainMaxHeight ); 1474 1475 if ( mCuller.isCulled( bounds ) ) 1476 { 1477 mCellGrid[ i ] = NULL; 1478 continue; 1479 } 1480 1481 // We need to allocate a new cell. 1482 // 1483 // TODO: This is the expensive call and where we should optimize. In 1484 // particular the next best optimization would be to take advantage of 1485 // multiple cores so that we can generate all the cells in one update. 1486 // 1487 // Instead of generating the cell here we would allocate a cell and stick 1488 // it into a thread safe queue (maybe lockless) as well as the mCellGrid. 1489 // Once all were allocated we would do something like this... 1490 // 1491 // TorqueParallelProcess( cellsToGenerateQueue, _generateCell ); 1492 // 1493 // Internally this function would pass the queue to some global pre-allocated 1494 // worker threads which are locked to a particular core. While the main 1495 // thread waits for the worker threads to finish it will process cells itself. 1496 // 1497 1498 cell = _generateCell( newIndex - index, 1499 bounds, 1500 placementCount, 1501 mRandomSeed + mAbs( newIndex.x ) + mAbs( newIndex.y ) ); 1502 1503 // Increment our generation count. 1504 if ( cell ) 1505 ++cellsGenerated; 1506 } 1507 1508 mCellGrid[ i ] = cell; 1509 } 1510 1511 // Store the new grid index. 1512 mGridIndex = index; 1513} 1514 1515void GroundCover::prepRenderImage( SceneRenderState *state ) 1516{ 1517 // Reset stats each time we hit the diffuse pass. 1518 1519 if( state->isDiffusePass() ) 1520 { 1521 smStatRenderedCells = 0; 1522 smStatRenderedBillboards = 0; 1523 smStatRenderedBatches = 0; 1524 smStatRenderedShapes = 0; 1525 } 1526 1527 // TODO: Make sure that the ground cover stops rendering 1528 // if you're inside a zoned interior. 1529 1530 if ( state->isReflectPass() && mReflectRadiusScale <= 0.0f ) 1531 return; 1532 1533 // Nothing to do if this is a shadow pass and shapes in this GoundCover 1534 // should not be casting shadows. 1535 1536 if( state->isShadowPass() && !mShapesCastShadows ) 1537 return; 1538 1539 GFXTransformSaver saver; 1540 1541 // Setup the frustum culler. 1542 if ( ( mCuller.getPosition().isZero() || !mDebugLockFrustum ) && !state->isShadowPass() ) 1543 mCuller = state->getCullingFrustum(); 1544 1545 // Update the cells, but only during the diffuse pass. 1546 // We don't want cell generation to thrash when the reflection camera 1547 // position doesn't match the diffuse camera! 1548 if ( state->isDiffusePass() ) 1549 _updateCoverGrid( mCuller ); 1550 1551 // Render billboards but not into shadow passes. 1552 1553 if ( !state->isShadowPass() && mMatInst->isValid() && !mDebugNoBillboards ) 1554 { 1555 PROFILE_SCOPE( GroundCover_RenderBillboards ); 1556 1557 // Take zoom into account. 1558 F32 screenScale = state->getWorldToScreenScale().y / state->getViewport().extent.y; 1559 1560 // Set the far distance for billboards. 1561 mCuller.setFarDist( mRadius * screenScale ); 1562 1563 F32 cullScale = 1.0f; 1564 if ( state->isReflectPass() ) 1565 cullScale = mReflectRadiusScale; 1566 1567 // Setup our shader const data. 1568 // Must be done prior to submitting our render instance. 1569 1570 mShaderConstData.fadeInfo.set( mFadeRadius * cullScale * screenScale, mRadius * cullScale * screenScale ); 1571 1572 const F32 simTime = Sim::getCurrentTime() * 0.001f; 1573 1574 mShaderConstData.gustInfo.set( mWindGustLength, mWindGustFrequency * simTime, mWindGustStrength ); 1575 mShaderConstData.turbInfo.set( mWindTurbulenceFrequency * simTime, mWindTurbulenceStrength ); 1576 1577 // Use the camera's forward vector to calculate the camera's right 1578 // and up vectors. This removes any camera banking from affecting 1579 // the ground cover. 1580 const MatrixF &camMat = state->getDiffuseCameraTransform(); 1581 Point3F camDir, camUp, camRight; 1582 camMat.getColumn( 1, &camDir ); 1583 mCross( camDir, Point3F::UnitZ, &camRight ); 1584 if ( camRight.magnitudeSafe() == 0.0f ) 1585 { 1586 camRight.set( 0.0f, -1.0f, 0.0f ); 1587 } 1588 camRight.normalizeSafe(); 1589 mCross( camRight, camDir, &camUp ); 1590 1591 // Limit the camera up vector to keep the billboards 1592 // from leaning too far down into the terrain. 1593 VectorF lookDir( camDir.x, camDir.y, 0.0f ); 1594 F32 angle; 1595 if ( !lookDir.isZero() ) 1596 { 1597 lookDir.normalize(); 1598 angle = mAcos( mDot( camUp, lookDir ) ); 1599 } 1600 else 1601 { 1602 angle = camDir.z < 0.0f ? 0.0f : ( M_PI_F / 2.0f ); 1603 } 1604 1605 const F32 maxBillboardTiltRads = mDegToRad( mMaxBillboardTiltAngle ); 1606 if ( angle < (M_PI_F / 2.0f) - maxBillboardTiltRads ) 1607 { 1608 QuatF quat( AngAxisF( camRight, maxBillboardTiltRads ) ); 1609 quat.mulP( VectorF( 0.0f, 0.0f, 1.0f ), &camUp ); 1610 } 1611 1612 mShaderConstData.camRight = camRight; 1613 mShaderConstData.camUp = camUp; 1614 1615 1616 // Cull and submit render instances for cells. 1617 1618 for ( S32 i = 0; i < mCellGrid.size(); i++ ) 1619 { 1620 GroundCoverCell* cell = mCellGrid[ i ]; 1621 if ( !cell ) 1622 continue; 1623 1624 if ( mCuller.isCulled( cell->getRenderBounds() ) ) 1625 continue; 1626 1627 cell->renderBillboards( state, mMatInst, &mPrimBuffer ); 1628 } 1629 } 1630 1631 // Render TS shapes. 1632 1633 if ( !mDebugNoShapes ) 1634 { 1635 // Prepare to render the grid shapes. 1636 PROFILE_SCOPE(GroundCover_RenderShapes); 1637 1638 // Set up our TS render state. 1639 TSRenderState rdata; 1640 rdata.setSceneState( state ); 1641 1642 // We might have some forward lit materials 1643 // so pass down a query to gather lights. 1644 LightQuery query; 1645 rdata.setLightQuery( &query ); 1646 1647 // TODO: Add a special fade out for DTS? 1648 mCuller.setFarDist( mShapeCullRadius ); 1649 1650 for ( S32 i = 0; i < mCellGrid.size(); i++ ) 1651 { 1652 GroundCoverCell* cell = mCellGrid[ i ]; 1653 if ( !cell || mDebugNoShapes ) 1654 continue; 1655 1656 const Box3F &renderBounds = cell->getRenderBounds(); 1657 U32 clipMask = mCuller.testPlanes( renderBounds, Frustum::PlaneMaskAll ); 1658 if ( clipMask == -1 ) 1659 continue; 1660 1661 smStatRenderedCells++; 1662 1663 // Use the cell bounds as the light query volume. 1664 // 1665 // This means all forward lit items in this cell will 1666 // get the same lights, but it performs much better. 1667 query.init( renderBounds ); 1668 1669 // Render the shapes in this cell... only pass the culler if the 1670 // cell wasn't fully within the frustum. 1671 smStatRenderedShapes += cell->renderShapes( 1672 rdata, 1673 clipMask != 0 ? &mCuller : NULL, 1674 mShapeInstances ); 1675 } 1676 } 1677 1678 if ( mDebugRenderCells ) 1679 { 1680 RenderPassManager *pass = state->getRenderPass(); 1681 1682 ObjectRenderInst *ri = pass->allocInst<ObjectRenderInst>(); 1683 ri->type = RenderPassManager::RIT_Editor; 1684 ri->renderDelegate.bind( this, &GroundCover::_debugRender ); 1685 pass->addInst( ri ); 1686 } 1687} 1688 1689void GroundCover::_debugRender( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat ) 1690{ 1691 GFXDrawUtil* drawer = GFX->getDrawUtil(); 1692 1693 GFXStateBlockDesc desc; 1694 desc.setZReadWrite( true, false ); 1695 desc.setBlend( true ); 1696 desc.fillMode = GFXFillWireframe; 1697 1698 for ( S32 i = 0; i < mCellGrid.size(); i++ ) 1699 { 1700 GroundCoverCell* cell = mCellGrid[ i ]; 1701 if ( !cell || ( cell->mBillboards.size() + cell->mShapes.size() ) == 0 ) 1702 continue; 1703 1704 if ( mCuller.isCulled( cell->getRenderBounds() ) ) 1705 continue; 1706 1707 drawer->drawCube( desc, cell->getRenderBounds().getExtents(), cell->getRenderBounds().getCenter(), ColorI( 0, 255, 0 ) ); 1708 } 1709} 1710
