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