terrFile.cpp
Engine/source/terrain/terrFile.cpp
Public Functions
Detailed Description
Public Functions
calcDev(const PlaneF & pl, const Point3F & pt)
checkSquare(TerrainSquare * parent, const TerrainSquare * child)
getMinMax(U16 & inMin, U16 & inMax, U16 height)
getMostSignificantBit(U32 v)
Umax(U16 u1, U16 u2)
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 "terrain/terrFile.h" 26 27#include "core/stream/fileStream.h" 28#include "core/resourceManager.h" 29#include "terrain/terrMaterial.h" 30#include "gfx/gfxTextureHandle.h" 31#include "gfx/bitmap/gBitmap.h" 32#include "platform/profiler.h" 33#include "math/mPlane.h" 34 35 36template<> 37void* Resource<TerrainFile>::create( const Torque::Path &path ) 38{ 39 return TerrainFile::load( path ); 40} 41 42template<> ResourceBase::Signature Resource<TerrainFile>::signature() 43{ 44 return MakeFourCC('t','e','r','d'); 45} 46 47 48TerrainFile::TerrainFile() 49 : mSize( 256 ), 50 mGridLevels(0), 51 mFileVersion( FILE_VERSION ), 52 mNeedsResaving( false ) 53{ 54 mLayerMap.setSize( mSize * mSize ); 55 dMemset( mLayerMap.address(), 0, mLayerMap.memSize() ); 56 57 mHeightMap.setSize( mSize * mSize ); 58 dMemset( mHeightMap.address(), 0, mHeightMap.memSize() ); 59} 60 61TerrainFile::~TerrainFile() 62{ 63} 64 65static U16 calcDev( const PlaneF &pl, const Point3F &pt ) 66{ 67 F32 z = (pl.d + pl.x * pt.x + pl.y * pt.y) / -pl.z; 68 F32 diff = z - pt.z; 69 if(diff < 0.0f) 70 diff = -diff; 71 72 if(diff > 0xFFFF) 73 return 0xFFFF; 74 else 75 return U16(diff); 76} 77 78static U16 Umax( U16 u1, U16 u2 ) 79{ 80 return u1 > u2 ? u1 : u2; 81} 82 83 84inline U32 getMostSignificantBit( U32 v ) 85{ 86 U32 bit = 0; 87 88 while ( v >>= 1 ) 89 bit++; 90 91 return bit; 92} 93 94void TerrainFile::_buildGridMap() 95{ 96 // The grid level count is the same as the 97 // most significant bit of the size. While 98 // we loop we take the time to calculate the 99 // grid memory pool size. 100 mGridLevels = 0; 101 U32 size = mSize; 102 U32 poolSize = size * size; 103 while ( size >>= 1 ) 104 { 105 poolSize += size * size; 106 mGridLevels++; 107 } 108 109 mGridMapPool.setSize( poolSize ); 110 mGridMapPool.compact(); 111 mGridMap.setSize( mGridLevels + 1 ); 112 mGridMap.compact(); 113 114 // Assign memory from the pool to each grid level. 115 TerrainSquare *grid = mGridMapPool.address(); 116 for ( S32 i = mGridLevels; i >= 0; i-- ) 117 { 118 mGridMap[i] = grid; 119 grid += 1 << ( 2 * ( mGridLevels - i ) ); 120 } 121 122 for( S32 i = mGridLevels; i >= 0; i-- ) 123 { 124 S32 squareCount = 1 << ( mGridLevels - i ); 125 S32 squareSize = mSize / squareCount; 126 127 for ( S32 squareX = 0; squareX < squareCount; squareX++ ) 128 { 129 for ( S32 squareY = 0; squareY < squareCount; squareY++ ) 130 { 131 U16 min = 0xFFFF; 132 U16 max = 0; 133 U16 mindev45 = 0; 134 U16 mindev135 = 0; 135 136 // determine max error for both possible splits. 137 138 const Point3F p1(0, 0, getHeight(squareX * squareSize, squareY * squareSize)); 139 const Point3F p2(0, (F32)squareSize, getHeight(squareX * squareSize, squareY * squareSize + squareSize)); 140 const Point3F p3((F32)squareSize, (F32)squareSize, getHeight(squareX * squareSize + squareSize, squareY * squareSize + squareSize)); 141 const Point3F p4((F32)squareSize, 0, getHeight(squareX * squareSize + squareSize, squareY * squareSize)); 142 143 // pl1, pl2 = split45, pl3, pl4 = split135 144 const PlaneF pl1(p1, p2, p3); 145 const PlaneF pl2(p1, p3, p4); 146 const PlaneF pl3(p1, p2, p4); 147 const PlaneF pl4(p2, p3, p4); 148 149 bool parentSplit45 = false; 150 TerrainSquare *parent = NULL; 151 if ( i < mGridLevels ) 152 { 153 parent = findSquare( i+1, squareX * squareSize, squareY * squareSize ); 154 parentSplit45 = parent->flags & TerrainSquare::Split45; 155 } 156 157 bool empty = true; 158 bool hasEmpty = false; 159 160 for ( S32 sizeX = 0; sizeX <= squareSize; sizeX++ ) 161 { 162 for ( S32 sizeY = 0; sizeY <= squareSize; sizeY++ ) 163 { 164 S32 x = squareX * squareSize + sizeX; 165 S32 y = squareY * squareSize + sizeY; 166 167 if(sizeX != squareSize && sizeY != squareSize) 168 { 169 if ( !isEmptyAt( x, y ) ) 170 empty = false; 171 else 172 hasEmpty = true; 173 } 174 175 U16 ht = getHeight( x, y ); 176 if ( ht < min ) 177 min = ht; 178 if( ht > max ) 179 max = ht; 180 181 Point3F pt( (F32)sizeX, (F32)sizeY, (F32)ht ); 182 U16 dev; 183 184 if(sizeX < sizeY) 185 dev = calcDev(pl1, pt); 186 else if(sizeX > sizeY) 187 dev = calcDev(pl2, pt); 188 else 189 dev = Umax(calcDev(pl1, pt), calcDev(pl2, pt)); 190 191 if(dev > mindev45) 192 mindev45 = dev; 193 194 if(sizeX + sizeY < squareSize) 195 dev = calcDev(pl3, pt); 196 else if(sizeX + sizeY > squareSize) 197 dev = calcDev(pl4, pt); 198 else 199 dev = Umax(calcDev(pl3, pt), calcDev(pl4, pt)); 200 201 if(dev > mindev135) 202 mindev135 = dev; 203 } 204 } 205 206 TerrainSquare *sq = findSquare( i, squareX * squareSize, squareY * squareSize ); 207 sq->minHeight = min; 208 sq->maxHeight = max; 209 210 sq->flags = empty ? TerrainSquare::Empty : 0; 211 if ( hasEmpty ) 212 sq->flags |= TerrainSquare::HasEmpty; 213 214 bool shouldSplit45 = ((squareX ^ squareY) & 1) == 0; 215 bool split45; 216 217 //split45 = shouldSplit45; 218 if ( i == 0 ) 219 split45 = shouldSplit45; 220 else if( i < 4 && shouldSplit45 == parentSplit45 ) 221 split45 = shouldSplit45; 222 else 223 split45 = mindev45 < mindev135; 224 225 //split45 = shouldSplit45; 226 if(split45) 227 { 228 sq->flags |= TerrainSquare::Split45; 229 sq->heightDeviance = mindev45; 230 } 231 else 232 sq->heightDeviance = mindev135; 233 234 if( parent ) 235 if ( parent->heightDeviance < sq->heightDeviance ) 236 parent->heightDeviance = sq->heightDeviance; 237 } 238 } 239 } 240 241 /* 242 for ( S32 y = 0; y < mSize; y += 2 ) 243 { 244 for ( S32 x=0; x < mSize; x += 2 ) 245 { 246 GridSquare *sq = findSquare(1, Point2I(x, y)); 247 GridSquare *s1 = findSquare(0, Point2I(x, y)); 248 GridSquare *s2 = findSquare(0, Point2I(x+1, y)); 249 GridSquare *s3 = findSquare(0, Point2I(x, y+1)); 250 GridSquare *s4 = findSquare(0, Point2I(x+1, y+1)); 251 sq->flags |= (s1->flags | s2->flags | s3->flags | s4->flags) & ~(GridSquare::MaterialStart -1); 252 } 253 } 254 */ 255} 256 257void TerrainFile::_initMaterialInstMapping() 258{ 259 mMaterialInstMapping.clearMatInstList(); 260 261 for( U32 i = 0; i < mMaterials.size(); ++ i ) 262 { 263 Torque::Path path( mMaterials[ i ]->getDiffuseMap() ); 264 mMaterialInstMapping.push_back( path.getFileName() ); 265 } 266 267 mMaterialInstMapping.mapMaterials(); 268} 269 270bool TerrainFile::save( const char *filename ) 271{ 272 FileStream stream; 273 stream.open( filename, Torque::FS::File::Write ); 274 if ( stream.getStatus() != Stream::Ok ) 275 return false; 276 277 stream.write( (U8)FILE_VERSION ); 278 279 stream.write( mSize ); 280 281 // Write out the height map. 282 for ( U32 i=0; i < mHeightMap.size(); i++) 283 stream.write( mHeightMap[i] ); 284 285 // Write out the layer map. 286 for ( U32 i=0; i < mLayerMap.size(); i++) 287 stream.write( mLayerMap[i] ); 288 289 // Write out the material names. 290 stream.write( (U32)mMaterials.size() ); 291 for ( U32 i=0; i < mMaterials.size(); i++ ) 292 stream.write( String( mMaterials[i]->getInternalName() ) ); 293 294 return stream.getStatus() == FileStream::Ok; 295} 296 297TerrainFile* TerrainFile::load( const Torque::Path &path ) 298{ 299 FileStream stream; 300 301 stream.open( path.getFullPath(), Torque::FS::File::Read ); 302 if ( stream.getStatus() != Stream::Ok ) 303 { 304 Con::errorf( "Resource<TerrainFile>::create - could not open '%s'", path.getFullPath().c_str() ); 305 return NULL; 306 } 307 308 U8 version; 309 stream.read(&version); 310 if (version > TerrainFile::FILE_VERSION) 311 { 312 Con::errorf( "Resource<TerrainFile>::create - file version '%i' is newer than engine version '%i'", version, TerrainFile::FILE_VERSION ); 313 return NULL; 314 } 315 316 TerrainFile *ret = new TerrainFile; 317 ret->mFileVersion = version; 318 ret->mFilePath = path; 319 320 if ( version >= 7 ) 321 ret->_load( stream ); 322 else 323 ret->_loadLegacy( stream ); 324 325 // Update the collision structures. 326 ret->_buildGridMap(); 327 328 // Do the material mapping. 329 ret->_initMaterialInstMapping(); 330 331 return ret; 332} 333 334void TerrainFile::_load( FileStream &stream ) 335{ 336 // NOTE: We read using a loop instad of in one large chunk 337 // because the stream will do endian conversions for us when 338 // reading one type at a time. 339 340 stream.read( &mSize ); 341 342 // Load the heightmap. 343 mHeightMap.setSize( mSize * mSize ); 344 for ( U32 i=0; i < mHeightMap.size(); i++ ) 345 stream.read( &mHeightMap[i] ); 346 347 // Load the layer index map. 348 mLayerMap.setSize( mSize * mSize ); 349 for ( U32 i=0; i < mLayerMap.size(); i++ ) 350 stream.read( &mLayerMap[i] ); 351 352 // Get the material name count. 353 U32 materialCount; 354 stream.read( &materialCount ); 355 Vector<String> materials; 356 materials.setSize( materialCount ); 357 358 // Load the material names. 359 for ( U32 i=0; i < materialCount; i++ ) 360 stream.read( &materials[i] ); 361 362 // Resolve the TerrainMaterial objects from the names. 363 _resolveMaterials( materials ); 364} 365 366void TerrainFile::_loadLegacy( FileStream &stream ) 367{ 368 // Some legacy constants. 369 enum 370 { 371 MaterialGroups = 8, 372 BlockSquareWidth = 256, 373 }; 374 375 const U32 sampleCount = BlockSquareWidth * BlockSquareWidth; 376 mSize = BlockSquareWidth; 377 378 // Load the heightmap. 379 mHeightMap.setSize( sampleCount ); 380 for ( U32 i=0; i < mHeightMap.size(); i++ ) 381 stream.read( &mHeightMap[i] ); 382 383 // Prior to version 7 we stored this weird material struct. 384 const U32 MATERIAL_GROUP_MASK = 0x7; 385 struct Material 386 { 387 enum Flags 388 { 389 Plain = 0, 390 Rotate = 1, 391 FlipX = 2, 392 FlipXRotate = 3, 393 FlipY = 4, 394 FlipYRotate = 5, 395 FlipXY = 6, 396 FlipXYRotate = 7, 397 RotateMask = 7, 398 Empty = 8, 399 Modified = BIT(7), 400 401 // must not clobber TerrainFile::MATERIAL_GROUP_MASK bits! 402 PersistMask = BIT(7) 403 }; 404 405 U8 flags; 406 U8 index; 407 }; 408 409 // Temp locals for loading before we convert to the new 410 // version 7+ format. 411 U8 baseMaterialMap[sampleCount] = { 0 }; 412 U8 *materialAlphaMap[MaterialGroups] = { 0 }; 413 Material materialMap[BlockSquareWidth * BlockSquareWidth]; 414 415 // read the material group map and flags... 416 dMemset(materialMap, 0, sizeof(materialMap)); 417 418 AssertFatal(!(Material::PersistMask & MATERIAL_GROUP_MASK), 419 "Doh! We have flag clobberage..."); 420 421 for (S32 j=0; j < sampleCount; j++) 422 { 423 U8 val; 424 stream.read(&val); 425 426 // 427 baseMaterialMap[j] = val & MATERIAL_GROUP_MASK; 428 materialMap[j].flags = val & Material::PersistMask; 429 } 430 431 // Load the material names. 432 Vector<String> materials; 433 for ( U32 i=0; i < MaterialGroups; i++ ) 434 { 435 String matName; 436 stream.read( &matName ); 437 if ( matName.isEmpty() ) 438 continue; 439 440 if ( mFileVersion > 3 && mFileVersion < 6 ) 441 { 442 // Between version 3 and 5 we store the texture file names 443 // relative to the terrain file. We restore the full path 444 // here so that we can create a TerrainMaterial from it. 445 materials.push_back( Torque::Path::CompressPath( mFilePath.getRoot() + mFilePath.getPath() + '/' + matName ) ); 446 } 447 else 448 materials.push_back( matName ); 449 } 450 451 if ( mFileVersion <= 3 ) 452 { 453 GFXTexHandle terrainMat; 454 Torque::Path matRelPath; 455 456 // Try to automatically fix up our material file names 457 for (U32 i = 0; i < materials.size(); i++) 458 { 459 if ( materials[i].isEmpty() ) 460 continue; 461 462 terrainMat.set( materials[i], &GFXTexturePersistentSRGBProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ) ); 463 if ( terrainMat ) 464 continue; 465 466 matRelPath = materials[i]; 467 468 String path = matRelPath.getPath(); 469 470 String::SizeType n = path.find( '/', 0, String::NoCase ); 471 if ( n != String::NPos ) 472 { 473 matRelPath.setPath( String(Con::getVariable( "$defaultGame" )) + path.substr( n, path.length() - n ) ); 474 475 terrainMat.set( matRelPath, &GFXTexturePersistentSRGBProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ) ); 476 if ( terrainMat ) 477 { 478 materials[i] = matRelPath.getFullPath(); 479 mNeedsResaving = true; 480 } 481 } 482 483 } // for (U32 i = 0; i < TerrainBlock::MaterialGroups; i++) 484 485 } // if ( mFileVersion <= 3 ) 486 487 if ( mFileVersion == 1 ) 488 { 489 for( S32 j = 0; j < sampleCount; j++ ) 490 { 491 if ( materialAlphaMap[baseMaterialMap[j]] == NULL ) 492 { 493 materialAlphaMap[baseMaterialMap[j]] = new U8[sampleCount]; 494 dMemset(materialAlphaMap[baseMaterialMap[j]], 0, sampleCount); 495 } 496 497 materialAlphaMap[baseMaterialMap[j]][j] = 255; 498 } 499 } 500 else 501 { 502 for( S32 k=0; k < materials.size(); k++ ) 503 { 504 AssertFatal(materialAlphaMap[k] == NULL, "Bad assumption. There should be no alpha map at this point..."); 505 materialAlphaMap[k] = new U8[sampleCount]; 506 stream.read(sampleCount, materialAlphaMap[k]); 507 } 508 } 509 510 // Throw away the old texture and heightfield scripts. 511 if ( mFileVersion >= 3 ) 512 { 513 U32 len; 514 stream.read(&len); 515 char *textureScript = (char *)dMalloc(len + 1); 516 stream.read(len, textureScript); 517 dFree( textureScript ); 518 519 stream.read(&len); 520 char *heightfieldScript = (char *)dMalloc(len + 1); 521 stream.read(len, heightfieldScript); 522 dFree( heightfieldScript ); 523 } 524 525 // Load and throw away the old edge terrain paths. 526 if ( mFileVersion >= 5 ) 527 { 528 stream.readSTString(true); 529 stream.readSTString(true); 530 } 531 532 U32 layerCount = materials.size() - 1; 533 534 // Ok... time to convert all this mess to the layer index map! 535 for ( U32 i=0; i < sampleCount; i++ ) 536 { 537 // Find the greatest layer. 538 U32 layer = 0; 539 U32 lastValue = 0; 540 for ( U32 k=0; k < MaterialGroups; k++ ) 541 { 542 if ( materialAlphaMap[k] && materialAlphaMap[k][i] > lastValue ) 543 { 544 layer = k; 545 lastValue = materialAlphaMap[k][i]; 546 } 547 } 548 549 // Set the layer index. 550 mLayerMap[i] = getMin( layer, layerCount ); 551 } 552 553 // Cleanup. 554 for ( U32 i=0; i < MaterialGroups; i++ ) 555 delete [] materialAlphaMap[i]; 556 557 // Force resaving on these old file versions. 558 //mNeedsResaving = false; 559 560 // Resolve the TerrainMaterial objects from the names. 561 _resolveMaterials( materials ); 562} 563 564void TerrainFile::_resolveMaterials( const Vector<String> &materials ) 565{ 566 mMaterials.clear(); 567 568 for ( U32 i=0; i < materials.size(); i++ ) 569 mMaterials.push_back( TerrainMaterial::findOrCreate( materials[i] ) ); 570 571 // If we didn't get any materials then at least 572 // add a warning material so we will render. 573 if ( mMaterials.empty() ) 574 mMaterials.push_back( TerrainMaterial::getWarningMaterial() ); 575} 576 577void TerrainFile::setSize( U32 newSize, bool clear ) 578{ 579 // Make sure the resolution is a power of two. 580 newSize = getNextPow2( newSize ); 581 582 // 583 if ( clear ) 584 { 585 mLayerMap.setSize( newSize * newSize ); 586 mLayerMap.compact(); 587 dMemset( mLayerMap.address(), 0, mLayerMap.memSize() ); 588 589 // Initialize the elevation to something above 590 // zero so that we have room to excavate by default. 591 U16 elev = floatToFixed( 512.0f ); 592 593 mHeightMap.setSize( newSize * newSize ); 594 mHeightMap.compact(); 595 for ( U32 i = 0; i < mHeightMap.size(); i++ ) 596 mHeightMap[i] = elev; 597 } 598 else 599 { 600 // We're resizing here! 601 602 603 604 } 605 606 mSize = newSize; 607 608 _buildGridMap(); 609} 610 611void TerrainFile::smooth( F32 factor, U32 steps, bool updateCollision ) 612{ 613 const U32 blockSize = mSize * mSize; 614 615 // Grab some temp buffers for our smoothing results. 616 Vector<F32> h1, h2; 617 h1.setSize( blockSize ); 618 h2.setSize( blockSize ); 619 620 // Fill the first buffer with the current heights. 621 for ( U32 i=0; i < blockSize; i++ ) 622 h1[i] = (F32)mHeightMap[i]; 623 624 // factor of 0.0 = NO Smoothing 625 // factor of 1.0 = MAX Smoothing 626 const F32 matrixM = 1.0f - getMax(0.0f, getMin(1.0f, factor)); 627 const F32 matrixE = (1.0f-matrixM) * (1.0f/12.0f) * 2.0f; 628 const F32 matrixC = matrixE * 0.5f; 629 630 // Now loop for our interations. 631 F32 *src = h1.address(); 632 F32 *dst = h2.address(); 633 for ( U32 s=0; s < steps; s++ ) 634 { 635 for ( S32 y=0; y < mSize; y++ ) 636 { 637 for ( S32 x=0; x < mSize; x++ ) 638 { 639 F32 samples[9]; 640 641 S32 c = 0; 642 for (S32 i = y-1; i < y+2; i++) 643 for (S32 j = x-1; j < x+2; j++) 644 { 645 if ( i < 0 || j < 0 || i >= mSize || j >= mSize ) 646 samples[c++] = src[ x + ( y * mSize ) ]; 647 else 648 samples[c++] = src[ j + ( i * mSize ) ]; 649 } 650 651 // 0 1 2 652 // 3 x,y 5 653 // 6 7 8 654 655 dst[ x + ( y * mSize ) ] = 656 ((samples[0]+samples[2]+samples[6]+samples[8]) * matrixC) + 657 ((samples[1]+samples[3]+samples[5]+samples[7]) * matrixE) + 658 (samples[4] * matrixM); 659 } 660 } 661 662 // Swap! 663 F32 *tmp = dst; 664 dst = src; 665 src = tmp; 666 } 667 668 // Copy the results back to the height map. 669 for ( U32 i=0; i < blockSize; i++ ) 670 mHeightMap[i] = (U16)mCeil( (F32)src[i] ); 671 672 if ( updateCollision ) 673 _buildGridMap(); 674} 675 676void TerrainFile::setHeightMap( const Vector<U16> &heightmap, bool updateCollision ) 677{ 678 AssertFatal( mHeightMap.size() == heightmap.size(), "TerrainFile::setHeightMap - Incorrect heightmap size!" ); 679 dMemcpy( mHeightMap.address(), heightmap.address(), mHeightMap.size() ); 680 681 if ( updateCollision ) 682 _buildGridMap(); 683} 684 685void TerrainFile::import( const GBitmap &heightMap, 686 F32 heightScale, 687 const Vector<U8> &layerMap, 688 const Vector<String> &materials, 689 bool flipYAxis ) 690{ 691 AssertFatal( heightMap.getWidth() == heightMap.getHeight(), "TerrainFile::import - Height map is not square!" ); 692 AssertFatal( isPow2( heightMap.getWidth() ), "TerrainFile::import - Height map is not power of two!" ); 693 694 const U32 newSize = heightMap.getWidth(); 695 if ( newSize != mSize ) 696 { 697 mHeightMap.setSize( newSize * newSize ); 698 mHeightMap.compact(); 699 mSize = newSize; 700 } 701 702 // Convert the height map to heights. 703 U16 *oBits = mHeightMap.address(); 704 if ( heightMap.getFormat() == GFXFormatL16) 705 { 706 const F32 toFixedPoint = ( 1.0f / (F32)U16_MAX ) * floatToFixed( heightScale ); 707 const U16 *iBits = (const U16*)heightMap.getBits(); 708 if ( flipYAxis ) 709 { 710 for ( U32 i = 0; i < mSize * mSize; i++ ) 711 { 712 U16 height = convertBEndianToHost( *iBits ); 713 *oBits = (U16)mCeil( (F32)height * toFixedPoint ); 714 ++oBits; 715 ++iBits; 716 } 717 } 718 else 719 { 720 for(S32 y = mSize - 1; y >= 0; y--) { 721 for(U32 x = 0; x < mSize; x++) { 722 U16 height = convertBEndianToHost( *iBits ); 723 mHeightMap[x + y * mSize] = (U16)mCeil( (F32)height * toFixedPoint ); 724 ++iBits; 725 } 726 } 727 } 728 } 729 else 730 { 731 const F32 toFixedPoint = ( 1.0f / (F32)U8_MAX ) * floatToFixed( heightScale ); 732 const U8 *iBits = heightMap.getBits(); 733 if ( flipYAxis ) 734 { 735 for ( U32 i = 0; i < mSize * mSize; i++ ) 736 { 737 *oBits = (U16)mCeil( ((F32)*iBits) * toFixedPoint ); 738 ++oBits; 739 iBits += heightMap.getBytesPerPixel(); 740 } 741 } 742 else 743 { 744 for(S32 y = mSize - 1; y >= 0; y--) { 745 for(U32 x = 0; x < mSize; x++) { 746 mHeightMap[x + y * mSize] = (U16)mCeil( ((F32)*iBits) * toFixedPoint ); 747 iBits += heightMap.getBytesPerPixel(); 748 } 749 } 750 } 751 } 752 753 // Copy over the layer map. 754 AssertFatal( layerMap.size() == mHeightMap.size(), "TerrainFile::import - Layer map is the wrong size!" ); 755 mLayerMap = layerMap; 756 mLayerMap.compact(); 757 758 // Resolve the materials. 759 _resolveMaterials( materials ); 760 761 // Rebuild the collision grid map. 762 _buildGridMap(); 763} 764 765 766void TerrainFile::create( String *inOutFilename, 767 U32 newSize, 768 const Vector<String> &materials ) 769{ 770 // Determine the path and basename 771 Torque::Path basePath( *inOutFilename ); 772 if ( !basePath.getExtension().equal("ter") ) 773 { 774 // Use the default path and filename 775 String terrainDirectory( Con::getVariable( "$pref::Directories::Terrain" ) ); 776 if ( terrainDirectory.isEmpty() ) 777 { 778 terrainDirectory = "data/terrains"; 779 } 780 basePath.setPath( terrainDirectory ); 781 basePath.setFileName( "terrain" ); 782 } 783 784 // Construct a default file name 785 (*inOutFilename) = Torque::FS::MakeUniquePath( basePath.getRootAndPath(), basePath.getFileName(), "ter" ); 786 787 // Create the file 788 TerrainFile *file = new TerrainFile; 789 790 for ( U32 i=0; i < materials.size(); i++ ) 791 file->mMaterials.push_back( TerrainMaterial::findOrCreate( materials[i] ) ); 792 793 file->setSize( newSize, true ); 794 file->save( *inOutFilename ); 795 796 delete file; 797} 798 799inline void getMinMax( U16 &inMin, U16 &inMax, U16 height ) 800{ 801 if ( height < inMin ) 802 inMin = height; 803 if ( height > inMax ) 804 inMax = height; 805} 806 807inline void checkSquare( TerrainSquare *parent, const TerrainSquare *child ) 808{ 809 if(parent->minHeight > child->minHeight) 810 parent->minHeight = child->minHeight; 811 if(parent->maxHeight < child->maxHeight) 812 parent->maxHeight = child->maxHeight; 813 814 if ( child->flags & (TerrainSquare::Empty | TerrainSquare::HasEmpty) ) 815 parent->flags |= TerrainSquare::HasEmpty; 816} 817 818void TerrainFile::updateGrid( const Point2I &minPt, const Point2I &maxPt ) 819{ 820 // here's how it works: 821 // for the current terrain renderer we only care about 822 // the minHeight and maxHeight on the GridSquare 823 // so we do one pass through, updating minHeight and maxHeight 824 // on the level 0 squares, then we loop up the grid map from 1 to 825 // the top, expanding the bounding boxes as necessary. 826 // this should end up being way, way, way, way faster for the terrain 827 // editor 828 829 PROFILE_SCOPE( TerrainFile_UpdateGrid ); 830 831 for ( S32 y = minPt.y - 1; y < maxPt.y + 1; y++ ) 832 { 833 for ( S32 x = minPt.x - 1; x < maxPt.x + 1; x++ ) 834 { 835 S32 px = x; 836 S32 py = y; 837 if ( px < 0 ) 838 px += mSize; 839 if ( py < 0 ) 840 py += mSize; 841 842 TerrainSquare *sq = findSquare( 0, px, py ); 843 sq->minHeight = 0xFFFF; 844 sq->maxHeight = 0; 845 846 // Update the empty state. 847 if ( isEmptyAt( x, y ) ) 848 sq->flags |= TerrainSquare::Empty; 849 else 850 sq->flags &= ~<a href="/coding/class/structterrainsquare/">TerrainSquare</a>::Empty; 851 852 getMinMax( sq->minHeight, sq->maxHeight, getHeight( x, y ) ); 853 getMinMax( sq->minHeight, sq->maxHeight, getHeight( x+1, y ) ); 854 getMinMax( sq->minHeight, sq->maxHeight, getHeight( x, y+1 ) ); 855 getMinMax( sq->minHeight, sq->maxHeight, getHeight( x+1, y+1 ) ); 856 } 857 } 858 859 // ok, all the level 0 grid squares are updated: 860 // now update all the parent grid squares that need to be updated: 861 for( S32 level = 1; level <= mGridLevels; level++ ) 862 { 863 S32 size = 1 << level; 864 S32 halfSize = size >> 1; 865 866 for( S32 y = (minPt.y - 1) >> level; y < (maxPt.y + size) >> level; y++ ) 867 { 868 for ( S32 x = (minPt.x - 1) >> level; x < (maxPt.x + size) >> level; x++ ) 869 { 870 S32 px = x << level; 871 S32 py = y << level; 872 873 TerrainSquare *sq = findSquare(level, px, py); 874 sq->minHeight = 0xFFFF; 875 sq->maxHeight = 0; 876 sq->flags &= ~( TerrainSquare::Empty | TerrainSquare::HasEmpty ); 877 878 checkSquare( sq, findSquare( level - 1, px, py ) ); 879 checkSquare( sq, findSquare( level - 1, px + halfSize, py ) ); 880 checkSquare( sq, findSquare( level - 1, px, py + halfSize ) ); 881 checkSquare( sq, findSquare( level - 1, px + halfSize, py + halfSize ) ); 882 } 883 } 884 } 885} 886