blTerrainSystem.cpp
Engine/source/lighting/basic/blTerrainSystem.cpp
Classes:
Public Variables
Detailed Description
Public Variables
SceneLighting * gLighting
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 "lighting/basic/blTerrainSystem.h" 26 27#include "core/bitVector.h" 28#include "lighting/common/shadowVolumeBSP.h" 29#include "lighting/lightingInterfaces.h" 30#include "terrain/terrData.h" 31#include "lighting/basic/basicLightManager.h" 32#include "lighting/common/sceneLighting.h" 33#include "gfx/bitmap/gBitmap.h" 34#include "collision/collision.h" 35 36extern SceneLighting* gLighting; 37 38 39struct blTerrainChunk : public PersistInfo::PersistChunk 40{ 41 typedef PersistInfo::PersistChunk Parent; 42 43 blTerrainChunk(); 44 ~blTerrainChunk(); 45 46 GBitmap *mLightmap; 47 48 bool read(Stream &); 49 bool write(Stream &); 50}; 51 52//------------------------------------------------------------------------------ 53// Class SceneLighting::TerrainChunk 54//------------------------------------------------------------------------------ 55blTerrainChunk::blTerrainChunk() 56{ 57 mChunkType = PersistChunk::TerrainChunkType; 58 mLightmap = NULL; 59} 60 61blTerrainChunk::~blTerrainChunk() 62{ 63 if(mLightmap) 64 delete mLightmap; 65} 66 67//------------------------------------------------------------------------------ 68 69bool blTerrainChunk::read(Stream & stream) 70{ 71 if(!Parent::read(stream)) 72 return(false); 73 74 mLightmap = new GBitmap(); 75 return mLightmap->readBitmap("png",stream); 76} 77 78bool blTerrainChunk::write(Stream & stream) 79{ 80 if(!Parent::write(stream)) 81 return(false); 82 83 if(!mLightmap) 84 return(false); 85 86 if(!mLightmap->writeBitmap("png",stream)) 87 return(false); 88 89 return(true); 90} 91 92class blTerrainProxy : public SceneLighting::ObjectProxy 93{ 94protected: 95 96 typedef ObjectProxy Parent; 97 98 BitVector mShadowMask; 99 ShadowVolumeBSP * mShadowVolume; 100 LinearColorF * mLightmap; 101 102 /// The dimension of the lightmap in pixels. 103 const U32 mLightMapSize; 104 105 /// The dimension of the terrain height map sample array. 106 const U32 mTerrainBlockSize; 107 108 LinearColorF *sgBakedLightmap; 109 Vector<LightInfo*> sgLights; 110 bool sgMarkStaticShadow(void *terrainproxy, SceneObject *sceneobject, LightInfo *light); 111 //void postLight(bool lastLight); 112 113 void lightVector(LightInfo *); 114 115 struct SquareStackNode 116 { 117 U8 mLevel; 118 U16 mClipFlags; 119 Point2I mPos; 120 }; 121 122 S32 testSquare(const Point3F &, const Point3F &, S32, F32, const Vector<PlaneF> &); 123 bool markObjectShadow(ObjectProxy *); 124 bool sgIsCorrectStaticObjectType(SceneObject *obj); 125 126 inline LinearColorF _getValue( S32 row, S32 column ); 127 128public: 129 130 blTerrainProxy(SceneObject * obj); 131 ~blTerrainProxy(); 132 TerrainBlock * operator->() {return(static_cast<TerrainBlock*>(static_cast<SceneObject*>(mObj)));} 133 TerrainBlock * getObject() {return(static_cast<TerrainBlock*>(static_cast<SceneObject*>(mObj)));} 134 135 bool getShadowedSquares(const Vector<PlaneF> &, Vector<U16> &); 136 137 // lighting 138 void init(); 139 bool preLight(LightInfo *); 140 void light(LightInfo *); 141 142 // persist 143 U32 getResourceCRC(); 144 bool setPersistInfo(PersistInfo::PersistChunk *); 145 bool getPersistInfo(PersistInfo::PersistChunk *); 146 147 virtual bool supportsShadowVolume(); 148 virtual void getClipPlanes(Vector<PlaneF>& planes); 149 virtual void addToShadowVolume(ShadowVolumeBSP * shadowVolume, LightInfo * light, S32 level); 150 151 // events 152 //virtual void processTGELightProcessEvent(U32 curr, U32 max, LightInfo* currlight); 153 //virtual void processSGObjectProcessEvent(LightInfo* currLight); 154}; 155 156//------------------------------------------------------------------------------- 157// Class SceneLighting::TerrainProxy: 158//------------------------------------------------------------------------------- 159blTerrainProxy::blTerrainProxy( SceneObject *obj ) : 160 Parent( obj ), 161 mLightMapSize( getObject()->getLightMapSize() ), 162 mShadowVolume( NULL ), 163 mTerrainBlockSize( getObject()->getBlockSize() ), 164 mLightmap( NULL ), 165 sgBakedLightmap( NULL ) 166{ 167} 168 169blTerrainProxy::~blTerrainProxy() 170{ 171 delete [] mLightmap; 172} 173 174//------------------------------------------------------------------------------- 175void blTerrainProxy::init() 176{ 177 mLightmap = new LinearColorF[ mLightMapSize * mLightMapSize ]; 178 dMemset(mLightmap, 0, mLightMapSize * mLightMapSize * sizeof(LinearColorF)); 179 mShadowMask.setSize( mTerrainBlockSize * mTerrainBlockSize ); 180} 181 182bool blTerrainProxy::preLight(LightInfo * light) 183{ 184 if(!bool(mObj)) 185 return(false); 186 187 if(light->getType() != LightInfo::Vector) 188 return(false); 189 190 mShadowMask.clear(); 191 return(true); 192} 193 194inline LinearColorF blTerrainProxy::_getValue( S32 row, S32 column ) 195{ 196 while( row < 0 ) 197 row += mLightMapSize; 198 row = row % mLightMapSize; 199 200 while( column < 0 ) 201 column += mLightMapSize; 202 column = column % mLightMapSize; 203 204 U32 offset = row * mLightMapSize + column; 205 206 return mLightmap[offset]; 207} 208 209bool blTerrainProxy::markObjectShadow(ObjectProxy * proxy) 210{ 211 if (!proxy->supportsShadowVolume()) 212 return false; 213 214 // setup the clip planes 215 Vector<PlaneF> clipPlanes; 216 proxy->getClipPlanes(clipPlanes); 217 218 Vector<U16> shadowList; 219 if(!getShadowedSquares(clipPlanes, shadowList)) 220 return(false); 221 222 // set the correct bit 223 for(U32 i = 0; i < shadowList.size(); i++) 224 mShadowMask.set(shadowList[i]); 225 226 return(true); 227} 228 229void blTerrainProxy::light(LightInfo * light) 230{ 231 // If we don't have terrain or its not a directional 232 // light then skip processing. 233 TerrainBlock * terrain = getObject(); 234 if ( !terrain || light->getType() != LightInfo::Vector ) 235 return; 236 237 S32 time = Platform::getRealMilliseconds(); 238 239 // reset 240 mShadowVolume = new ShadowVolumeBSP; 241 242 // build interior shadow volume 243 for(ObjectProxy ** itr = gLighting->mLitObjects.begin(); itr != gLighting->mLitObjects.end(); itr++) 244 { 245 ObjectProxy* objproxy = *itr; 246 if (markObjectShadow(objproxy)) 247 objproxy->addToShadowVolume(mShadowVolume, light, SceneLighting::SHADOW_DETAIL); 248 } 249 250 lightVector(light); 251 252 // set the lightmap... 253 terrain->clearLightMap(); 254 255 // Blur... 256 F32 kernel[3][3] = { {1, 2, 1}, 257 {2, 3, 2}, 258 {1, 2, 1} }; 259 260 F32 modifier = 1; 261 F32 divisor = 0; 262 263 264 for( U32 i=0; i<3; i++ ) 265 { 266 for( U32 j=0; j<3; j++ ) 267 { 268 if( i==1 && j==1 ) 269 { 270 kernel[i][j] = 1 + kernel[i][j] * modifier; 271 } 272 else 273 { 274 kernel[i][j] = kernel[i][j] * modifier; 275 } 276 277 divisor += kernel[i][j]; 278 } 279 } 280 281 for( U32 i=0; i < mLightMapSize; i++ ) 282 { 283 for( U32 j=0; j < mLightMapSize; j++ ) 284 { 285 286 LinearColorF val; 287 val = _getValue( i-1, j-1 ) * kernel[0][0]; 288 val += _getValue( i-1, j ) * kernel[0][1]; 289 val += _getValue( i-1, j+1 ) * kernel[0][2]; 290 val += _getValue( i, j-1 ) * kernel[1][0]; 291 val += _getValue( i, j ) * kernel[1][1]; 292 val += _getValue( i, j+1 ) * kernel[1][2]; 293 val += _getValue( i+1, j-1 ) * kernel[2][0]; 294 val += _getValue( i+1, j ) * kernel[2][1]; 295 val += _getValue( i+1, j+1 ) * kernel[2][2]; 296 297 U32 edge = 0; 298 299 if( j == 0 || j == mLightMapSize - 1 ) 300 edge++; 301 302 if( i == 0 || i == mLightMapSize - 1 ) 303 edge++; 304 305 if( !edge ) 306 val = val / divisor; 307 else 308 val = mLightmap[ i * mLightMapSize + j ]; 309 310 // clamp values 311 mLightmap[ i * mLightMapSize + j ]= val; 312 } 313 } 314 315 // And stuff it into the texture... 316 GBitmap *terrLightMap = terrain->getLightMap(); 317 for(U32 y = 0; y < mLightMapSize; y++) 318 { 319 for(U32 x = 0; x < mLightMapSize; x++) 320 { 321 ColorI color(255, 255, 255, 255); 322 323 color.red = mLightmap[x + y * mLightMapSize].red * 255; 324 color.green = mLightmap[x + y * mLightMapSize].green * 255; 325 color.blue = mLightmap[x + y * mLightMapSize].blue * 255; 326 327 terrLightMap->setColor(x, y, color); 328 } 329 } 330 331 /* 332 // This handles matching up the outer edges of the terrain 333 // lightmap when it has neighbors 334 if (!terrain->isTiling()) 335 { 336 for (S32 y = 0; y < terrLightMap->getHeight(); y++) 337 { 338 ColorI c; 339 if (terrain->getFile()->mEdgeTerrainFiles[0]) 340 { 341 terrLightMap->getColor(terrLightMap->getWidth()-1,y,c); 342 terrLightMap->setColor(0,y,c); 343 terrLightMap->setColor(1,y,c); 344 } 345 else 346 { 347 terrLightMap->getColor(0,y,c); 348 terrLightMap->setColor(terrLightMap->getWidth()-1,y,c); 349 terrLightMap->setColor(terrLightMap->getWidth()-2,y,c); 350 } 351 } 352 353 for (S32 x = 0; x < terrLightMap->getHeight(); x++) 354 { 355 ColorI c; 356 if (terrain->getFile()->mEdgeTerrainFiles[1]) 357 { 358 terrLightMap->getColor(x,terrLightMap->getHeight()-1,c); 359 terrLightMap->setColor(x,0,c); 360 terrLightMap->setColor(x,1,c); 361 } 362 else 363 { 364 terrLightMap->getColor(x,0,c); 365 terrLightMap->setColor(x,terrLightMap->getHeight()-1,c); 366 terrLightMap->setColor(x,terrLightMap->getHeight()-2,c); 367 } 368 } 369 } 370 */ 371 372 delete mShadowVolume; 373 374 Con::printf(" = terrain lit in %3.3f seconds", (Platform::getRealMilliseconds()-time)/1000.f); 375} 376 377//------------------------------------------------------------------------------ 378S32 blTerrainProxy::testSquare(const Point3F & min, const Point3F & max, S32 mask, F32 expand, const Vector<PlaneF> & clipPlanes) 379{ 380 expand = 0; 381 S32 retMask = 0; 382 Point3F minPoint, maxPoint; 383 for(S32 i = 0; i < clipPlanes.size(); i++) 384 { 385 if(mask & (1 << i)) 386 { 387 if(clipPlanes[i].x > 0) 388 { 389 maxPoint.x = max.x; 390 minPoint.x = min.x; 391 } 392 else 393 { 394 maxPoint.x = min.x; 395 minPoint.x = max.x; 396 } 397 if(clipPlanes[i].y > 0) 398 { 399 maxPoint.y = max.y; 400 minPoint.y = min.y; 401 } 402 else 403 { 404 maxPoint.y = min.y; 405 minPoint.y = max.y; 406 } 407 if(clipPlanes[i].z > 0) 408 { 409 maxPoint.z = max.z; 410 minPoint.z = min.z; 411 } 412 else 413 { 414 maxPoint.z = min.z; 415 minPoint.z = max.z; 416 } 417 F32 maxDot = mDot(maxPoint, clipPlanes[i]); 418 F32 minDot = mDot(minPoint, clipPlanes[i]); 419 F32 planeD = clipPlanes[i].d; 420 if(maxDot <= -(planeD + expand)) 421 return(U16(-1)); 422 if(minDot <= -planeD) 423 retMask |= (1 << i); 424 } 425 } 426 return(retMask); 427} 428 429bool blTerrainProxy::getShadowedSquares(const Vector<PlaneF> & clipPlanes, Vector<U16> & shadowList) 430{ 431 TerrainBlock *terrain = getObject(); 432 if ( !terrain ) 433 return false; 434 435 // TODO: Fix me for variable terrain sizes! 436 return true; 437 438 /* 439 SquareStackNode stack[TerrainBlock::BlockShift * 4]; 440 441 stack[0].mLevel = TerrainBlock::BlockShift; 442 stack[0].mClipFlags = 0xff; 443 stack[0].mPos.set(0,0); 444 445 U32 stackSize = 1; 446 447 Point3F blockPos; 448 terrain->getTransform().getColumn(3, &blockPos); 449 S32 squareSize = terrain->getSquareSize(); 450 F32 floatSquareSize = (F32)squareSize; 451 452 bool marked = false; 453 454 // push through all the levels of the quadtree 455 while(stackSize) 456 { 457 SquareStackNode * node = &stack[stackSize - 1]; 458 459 S32 clipFlags = node->mClipFlags; 460 Point2I pos = node->mPos; 461 GridSquare * sq = terrain->findSquare(node->mLevel, pos); 462 463 Point3F minPoint, maxPoint; 464 minPoint.set(squareSize * pos.x + blockPos.x, 465 squareSize * pos.y + blockPos.y, 466 fixedToFloat(sq->minHeight)); 467 maxPoint.set(minPoint.x + (squareSize << node->mLevel), 468 minPoint.y + (squareSize << node->mLevel), 469 fixedToFloat(sq->maxHeight)); 470 471 // test the square against the current level 472 if(clipFlags) 473 { 474 clipFlags = testSquare(minPoint, maxPoint, clipFlags, floatSquareSize, clipPlanes); 475 if(clipFlags == U16(-1)) 476 { 477 stackSize--; 478 continue; 479 } 480 } 481 482 // shadowed? 483 if(node->mLevel == 0) 484 { 485 marked = true; 486 shadowList.push_back(pos.x + (pos.y << TerrainBlock::BlockShift)); 487 stackSize--; 488 continue; 489 } 490 491 // setup the next level of squares 492 U8 nextLevel = node->mLevel - 1; 493 S32 squareHalfSize = 1 << nextLevel; 494 495 for(U32 i = 0; i < 4; i++) 496 { 497 node[i].mLevel = nextLevel; 498 node[i].mClipFlags = clipFlags; 499 } 500 501 node[3].mPos = pos; 502 node[2].mPos.set(pos.x + squareHalfSize, pos.y); 503 node[1].mPos.set(pos.x, pos.y + squareHalfSize); 504 node[0].mPos.set(pos.x + squareHalfSize, pos.y + squareHalfSize); 505 506 stackSize += 3; 507 } 508 509 return marked; 510 */ 511} 512 513void blTerrainProxy::lightVector(LightInfo * light) 514{ 515 // Grab our terrain object 516 TerrainBlock* terrain = getObject(); 517 if (!terrain) 518 return; 519 520 // Get the direction to the light (the inverse of the direction 521 // the light is pointing) 522 Point3F lightDir = -light->getDirection(); 523 lightDir.normalize(); 524 525 // Get the ratio between the light map pixel and world space (used below) 526 F32 lmTerrRatio = (F32)mTerrainBlockSize / (F32) mLightMapSize; 527 lmTerrRatio *= terrain->getSquareSize(); 528 529 U32 i = 0; 530 for (U32 y = 0; y < mLightMapSize; y++) 531 { 532 for (U32 x = 0; x < mLightMapSize; x++) 533 { 534 // Get the relative pixel position and scale it 535 // by the ratio between lightmap and world space 536 Point2F pixelPos(x, y); 537 pixelPos *= lmTerrRatio; 538 539 // Start with a default normal of straight up 540 Point3F normal(0.0f, 0.0f, 1.0f); 541 542 // Try to get the actual normal from the terrain. 543 // Note: this won't change the default normal if 544 // it can't find a normal. 545 terrain->getNormal(pixelPos, &normal); 546 547 // The terrain lightmap only contains shadows. 548 F32 shadowed = 0.0f; 549 550 // Get the height at the lightmap pixel's position 551 F32 height = 0.0f; 552 terrain->getHeight(pixelPos, &height); 553 554 // Calculate the 3D position of the pixel 555 Point3F pixelPos3F(pixelPos.x, pixelPos.y, height); 556 557 // Translate that position by the terrain's transform 558 terrain->getTransform().mulP(pixelPos3F); 559 560 // Offset slighting along the normal so that we don't 561 // raycast into ourself 562 pixelPos3F += (normal * 0.1f); 563 564 // Calculate the light's position. 565 // If it is a vector light like the sun (no position 566 // just direction) then translate along that direction 567 // a reasonable distance to get a point sufficiently 568 // far away 569 Point3F lightPos = light->getPosition(); 570 if(light->getType() == LightInfo::Vector) 571 { 572 lightPos = 1000.f * lightDir; 573 lightPos = pixelPos3F + lightPos; 574 } 575 576 // Cast a ray from the world space position of the lightmap pixel to the light source. 577 // If we hit something then we are in shadow. This allows us to be shadowed by anything 578 // that supports a castRay operation. 579 RayInfo info; 580 if(terrain->getContainer()->castRay(pixelPos3F, lightPos, STATIC_COLLISION_TYPEMASK, &info)) 581 { 582 // Shadow the pixel. 583 shadowed = 1.0f; 584 } 585 586 // Set the final lightmap color. 587 mLightmap[i++] += LinearColorF::WHITE * mClampF( 1.0f - shadowed, 0.0f, 1.0f ); 588 } 589 } 590} 591 592//-------------------------------------------------------------------------- 593U32 blTerrainProxy::getResourceCRC() 594{ 595 TerrainBlock * terrain = getObject(); 596 if(!terrain) 597 return(0); 598 return(terrain->getCRC()); 599} 600 601//-------------------------------------------------------------------------- 602bool blTerrainProxy::setPersistInfo(PersistInfo::PersistChunk * info) 603{ 604 if(!Parent::setPersistInfo(info)) 605 return(false); 606 607 blTerrainChunk * chunk = dynamic_cast<blTerrainChunk*>(info); 608 AssertFatal(chunk, "blTerrainProxy::setPersistInfo: invalid info chunk!"); 609 610 TerrainBlock * terrain = getObject(); 611 if(!terrain || !terrain->getLightMap()) 612 return(false); 613 614 terrain->setLightMap( new GBitmap( *chunk->mLightmap) ); 615 616 return(true); 617} 618 619bool blTerrainProxy::getPersistInfo(PersistInfo::PersistChunk * info) 620{ 621 if(!Parent::getPersistInfo(info)) 622 return(false); 623 624 blTerrainChunk * chunk = dynamic_cast<blTerrainChunk*>(info); 625 AssertFatal(chunk, "blTerrainProxy::getPersistInfo: invalid info chunk!"); 626 627 TerrainBlock * terrain = getObject(); 628 if(!terrain || !terrain->getLightMap()) 629 return(false); 630 631 if(chunk->mLightmap) delete chunk->mLightmap; 632 633 chunk->mLightmap = new GBitmap(*terrain->getLightMap()); 634 635 return(true); 636} 637 638bool blTerrainProxy::supportsShadowVolume() 639{ 640 return false; 641} 642 643void blTerrainProxy::getClipPlanes(Vector<PlaneF>& planes) 644{ 645 646} 647 648void blTerrainProxy::addToShadowVolume(ShadowVolumeBSP * shadowVolume, LightInfo * light, S32 level) 649{ 650 651} 652 653 654 655void blTerrainSystem::init() 656{ 657} 658 659U32 blTerrainSystem::addObjectType() 660{ 661 return TerrainObjectType; 662} 663 664SceneLighting::ObjectProxy* blTerrainSystem::createObjectProxy(SceneObject* obj, SceneLighting::ObjectProxyList* sceneObjects) 665{ 666 if ((obj->getTypeMask() & TerrainObjectType) != 0) 667 return new blTerrainProxy(obj); 668 else 669 return NULL; 670} 671 672PersistInfo::PersistChunk* blTerrainSystem::createPersistChunk(const U32 chunkType) 673{ 674 if (chunkType == PersistInfo::PersistChunk::TerrainChunkType) 675 return new blTerrainChunk(); 676 else 677 return NULL; 678} 679 680bool blTerrainSystem::createPersistChunkFromProxy(SceneLighting::ObjectProxy* objproxy, PersistInfo::PersistChunk **ret) 681{ 682 if (dynamic_cast<blTerrainProxy*>(objproxy) != NULL) 683 { 684 *ret = new blTerrainChunk(); 685 return true; 686 } else { 687 return NULL; 688 } 689} 690 691// Given a ray, this will return the color from the lightmap of this object, return true if handled 692bool blTerrainSystem::getColorFromRayInfo(const RayInfo & collision, LinearColorF& result) const 693{ 694 TerrainBlock *terrain = dynamic_cast<TerrainBlock *>(collision.object); 695 if (!terrain) 696 return false; 697 698 Point2F uv; 699 F32 terrainlength = (F32)terrain->getBlockSize(); 700 Point3F pos = terrain->getPosition(); 701 uv.x = (collision.point.x - pos.x) / terrainlength; 702 uv.y = (collision.point.y - pos.y) / terrainlength; 703 704 // similar to x = x & width... 705 uv.x = uv.x - F32(U32(uv.x)); 706 uv.y = uv.y - F32(U32(uv.y)); 707 const GBitmap* lightmap = terrain->getLightMap(); 708 if (!lightmap) 709 return false; 710 711 result = lightmap->sampleTexel(uv.x, uv.y); 712 // terrain lighting is dim - look into this (same thing done in shaders)... 713 result *= 2.0f; 714 return true; 715} 716