terrainActions.cpp
Engine/source/gui/worldEditor/terrainActions.cpp
Public Functions
ConsoleDocClass(TerrainSmoothAction , "@brief Terrain action used <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> leveling varying terrain heights <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">smoothly.\n\n</a>" "Editor use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n\n</a>" "@internal" )
DefineEngineMethod(TerrainSmoothAction , smooth , void , (TerrainBlock *terrain, F32 factor, U32 steps) , "( TerrainBlock obj, <a href="/coding/file/types_8h/#types_8h_1a841d3674577a1e86afdc2f4845f46c4b">F32</a> factor, <a href="/coding/file/types_8h/#types_8h_1ac3df7cf3c8cb172a588adec881447d68">U32</a> steps )" )
Detailed Description
Public Functions
ConsoleDocClass(TerrainSmoothAction , "@brief Terrain action used <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> leveling varying terrain heights <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">smoothly.\n\n</a>" "Editor use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n\n</a>" "@internal" )
DefineEngineMethod(TerrainSmoothAction , smooth , void , (TerrainBlock *terrain, F32 factor, U32 steps) , "( TerrainBlock obj, <a href="/coding/file/types_8h/#types_8h_1a841d3674577a1e86afdc2f4845f46c4b">F32</a> factor, <a href="/coding/file/types_8h/#types_8h_1ac3df7cf3c8cb172a588adec881447d68">U32</a> steps )" )
IMPLEMENT_CONOBJECT(TerrainSmoothAction )
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 "console/engineAPI.h" 25#include "platform/platform.h" 26#include "gui/worldEditor/terrainActions.h" 27 28#include "gui/core/guiCanvas.h" 29 30//------------------------------------------------------------------------------ 31 32void SelectAction::process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type) 33{ 34 if(sel == mTerrainEditor->getCurrentSel()) 35 return; 36 37 if(type == Process) 38 return; 39 40 if(selChanged) 41 { 42 if(event.modifier & SI_MULTISELECT) 43 { 44 for(U32 i = 0; i < sel->size(); i++) 45 mTerrainEditor->getCurrentSel()->remove((*sel)[i]); 46 } 47 else 48 { 49 for(U32 i = 0; i < sel->size(); i++) 50 { 51 GridInfo gInfo; 52 if(mTerrainEditor->getCurrentSel()->getInfo((*sel)[i].mGridPoint.gridPos, gInfo)) 53 { 54 if(!gInfo.mPrimarySelect) 55 gInfo.mPrimarySelect = (*sel)[i].mPrimarySelect; 56 57 if(gInfo.mWeight < (*sel)[i].mWeight) 58 gInfo.mWeight = (*sel)[i].mWeight; 59 60 mTerrainEditor->getCurrentSel()->setInfo(gInfo); 61 } 62 else 63 mTerrainEditor->getCurrentSel()->add((*sel)[i]); 64 } 65 } 66 } 67} 68 69void DeselectAction::process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type) 70{ 71 if(sel == mTerrainEditor->getCurrentSel()) 72 return; 73 74 if(type == Process) 75 return; 76 77 if(selChanged) 78 { 79 for(U32 i = 0; i < sel->size(); i++) 80 mTerrainEditor->getCurrentSel()->remove((*sel)[i]); 81 } 82} 83 84//------------------------------------------------------------------------------ 85 86void SoftSelectAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type type) 87{ 88 TerrainBlock *terrBlock = mTerrainEditor->getActiveTerrain(); 89 if ( !terrBlock ) 90 return; 91 92 // allow process of current selection 93 Selection tmpSel; 94 if(sel == mTerrainEditor->getCurrentSel()) 95 { 96 tmpSel = *sel; 97 sel = &tmpSel; 98 } 99 100 if(type == Begin || type == Process) 101 mFilter.set(1, &mTerrainEditor->mSoftSelectFilter); 102 103 // 104 if(selChanged) 105 { 106 F32 radius = mTerrainEditor->mSoftSelectRadius; 107 if(radius == 0.f) 108 return; 109 110 S32 squareSize = terrBlock->getSquareSize(); 111 U32 offset = U32(radius / F32(squareSize)) + 1; 112 113 for(U32 i = 0; i < sel->size(); i++) 114 { 115 GridInfo & info = (*sel)[i]; 116 117 info.mPrimarySelect = true; 118 info.mWeight = mFilter.getValue(0); 119 120 if(!mTerrainEditor->getCurrentSel()->add(info)) 121 mTerrainEditor->getCurrentSel()->setInfo(info); 122 123 Point2F infoPos((F32)info.mGridPoint.gridPos.x, (F32)info.mGridPoint.gridPos.y); 124 125 // 126 for(S32 x = info.mGridPoint.gridPos.x - offset; x < info.mGridPoint.gridPos.x + (offset << 1); x++) 127 for(S32 y = info.mGridPoint.gridPos.y - offset; y < info.mGridPoint.gridPos.y + (offset << 1); y++) 128 { 129 // 130 Point2F pos((F32)x, (F32)y); 131 132 F32 dist = Point2F(pos - infoPos).len() * F32(squareSize); 133 134 if(dist > radius) 135 continue; 136 137 F32 weight = mFilter.getValue(dist / radius); 138 139 // 140 GridInfo gInfo; 141 GridPoint gridPoint = info.mGridPoint; 142 gridPoint.gridPos.set(x, y); 143 144 if(mTerrainEditor->getCurrentSel()->getInfo(Point2I(x, y), gInfo)) 145 { 146 if(gInfo.mPrimarySelect) 147 continue; 148 149 if(gInfo.mWeight < weight) 150 { 151 gInfo.mWeight = weight; 152 mTerrainEditor->getCurrentSel()->setInfo(gInfo); 153 } 154 } 155 else 156 { 157 Vector<GridInfo> gInfos; 158 mTerrainEditor->getGridInfos(gridPoint, gInfos); 159 160 for (U32 z = 0; z < gInfos.size(); z++) 161 { 162 gInfos[z].mWeight = weight; 163 gInfos[z].mPrimarySelect = false; 164 mTerrainEditor->getCurrentSel()->add(gInfos[z]); 165 } 166 } 167 } 168 } 169 } 170} 171 172//------------------------------------------------------------------------------ 173 174void OutlineSelectAction::process(Selection * sel, const Gui3DMouseEvent & event, bool, Type type) 175{ 176 TORQUE_UNUSED(sel); TORQUE_UNUSED(event); TORQUE_UNUSED(type); 177 switch(type) 178 { 179 case Begin: 180 if(event.modifier & SI_SHIFT) 181 break; 182 183 mTerrainEditor->getCurrentSel()->reset(); 184 break; 185 186 case End: 187 case Update: 188 189 default: 190 return; 191 } 192 193 mLastEvent = event; 194} 195 196//------------------------------------------------------------------------------ 197 198void PaintMaterialAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) 199{ 200 S32 mat = mTerrainEditor->getPaintMaterialIndex(); 201 if ( !selChanged || mat < 0 ) 202 return; 203 204 const bool slopeLimit = mTerrainEditor->mSlopeMinAngle > 0.0f || mTerrainEditor->mSlopeMaxAngle < 90.0f; 205 const F32 minSlope = mSin( mDegToRad( 90.0f - mTerrainEditor->mSlopeMinAngle ) ); 206 const F32 maxSlope = mSin( mDegToRad( 90.0f - mTerrainEditor->mSlopeMaxAngle ) ); 207 208 const TerrainBlock *terrain = mTerrainEditor->getActiveTerrain(); 209 const F32 squareSize = terrain->getSquareSize(); 210 211 Point2F p; 212 Point3F norm; 213 214 215 for( U32 i = 0; i < sel->size(); i++ ) 216 { 217 GridInfo &inf = (*sel)[i]; 218 219 if ( slopeLimit ) 220 { 221 p.x = inf.mGridPoint.gridPos.x * squareSize; 222 p.y = inf.mGridPoint.gridPos.y * squareSize; 223 if ( !terrain->getNormal( p, &norm, true ) ) 224 continue; 225 226 if ( norm.z > minSlope || 227 norm.z < maxSlope ) 228 continue; 229 } 230 231 // If grid is already set to our material, or it is an 232 // empty grid spot, then skip painting. 233 if ( inf.mMaterial == mat || inf.mMaterial == U8_MAX ) 234 continue; 235 236 if ( mRandF() > mTerrainEditor->getBrushPressure() ) 237 continue; 238 239 inf.mMaterialChanged = true; 240 mTerrainEditor->getUndoSel()->add(inf); 241 242 // Painting is really simple now... set the one mat index. 243 inf.mMaterial = mat; 244 mTerrainEditor->setGridInfo(inf, true); 245 } 246 247 mTerrainEditor->scheduleMaterialUpdate(); 248} 249 250//------------------------------------------------------------------------------ 251 252void ClearMaterialsAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) 253{ 254 if(selChanged) 255 { 256 for(U32 i = 0; i < sel->size(); i++) 257 { 258 GridInfo &inf = (*sel)[i]; 259 260 mTerrainEditor->getUndoSel()->add(inf); 261 inf.mMaterialChanged = true; 262 263 // Reset to the first texture layer. 264 inf.mMaterial = 0; 265 mTerrainEditor->setGridInfo(inf); 266 } 267 mTerrainEditor->scheduleMaterialUpdate(); 268 } 269} 270 271//------------------------------------------------------------------------------ 272 273void RaiseHeightAction::process( Selection *sel, const Gui3DMouseEvent &evt, bool selChanged, Type type ) 274{ 275 // ok the raise height action is our "dirt pour" action 276 // only works on brushes... 277 278 Brush *brush = dynamic_cast<Brush*>(sel); 279 if ( !brush ) 280 return; 281 282 if ( type == End ) 283 return; 284 285 Point2I brushPos = brush->getPosition(); 286 GridPoint brushGridPoint = brush->getGridPoint(); 287 288 Vector<GridInfo> cur; // the height at the brush position 289 mTerrainEditor->getGridInfos(brushGridPoint, cur); 290 291 if ( cur.size() == 0 ) 292 return; 293 294 // we get 30 process actions per second (at least) 295 F32 heightAdjust = mTerrainEditor->mAdjustHeightVal / 30; 296 // nothing can get higher than the current brush pos adjusted height 297 298 F32 maxHeight = cur[0].mHeight + heightAdjust; 299 300 for ( U32 i = 0; i < sel->size(); i++ ) 301 { 302 mTerrainEditor->getUndoSel()->add((*sel)[i]); 303 if ( (*sel)[i].mHeight < maxHeight ) 304 { 305 (*sel)[i].mHeight += heightAdjust * (*sel)[i].mWeight; 306 if ( (*sel)[i].mHeight > maxHeight ) 307 (*sel)[i].mHeight = maxHeight; 308 } 309 mTerrainEditor->setGridInfo((*sel)[i]); 310 } 311 312 mTerrainEditor->scheduleGridUpdate(); 313} 314 315//------------------------------------------------------------------------------ 316 317void LowerHeightAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type type) 318{ 319 // ok the lower height action is our "dirt dig" action 320 // only works on brushes... 321 322 Brush *brush = dynamic_cast<Brush *>(sel); 323 if(!brush) 324 return; 325 326 if ( type == End ) 327 return; 328 329 Point2I brushPos = brush->getPosition(); 330 GridPoint brushGridPoint = brush->getGridPoint(); 331 332 Vector<GridInfo> cur; // the height at the brush position 333 mTerrainEditor->getGridInfos(brushGridPoint, cur); 334 335 if (cur.size() == 0) 336 return; 337 338 // we get 30 process actions per second (at least) 339 F32 heightAdjust = -mTerrainEditor->mAdjustHeightVal / 30; 340 // nothing can get higher than the current brush pos adjusted height 341 342 F32 maxHeight = cur[0].mHeight + heightAdjust; 343 if(maxHeight < 0) 344 maxHeight = 0; 345 346 for(U32 i = 0; i < sel->size(); i++) 347 { 348 mTerrainEditor->getUndoSel()->add((*sel)[i]); 349 if((*sel)[i].mHeight > maxHeight) 350 { 351 (*sel)[i].mHeight += heightAdjust * (*sel)[i].mWeight; 352 if((*sel)[i].mHeight < maxHeight) 353 (*sel)[i].mHeight = maxHeight; 354 } 355 mTerrainEditor->setGridInfo((*sel)[i]); 356 } 357 358 mTerrainEditor->scheduleGridUpdate(); 359} 360 361//------------------------------------------------------------------------------ 362 363void SetHeightAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) 364{ 365 if(selChanged) 366 { 367 for(U32 i = 0; i < sel->size(); i++) 368 { 369 mTerrainEditor->getUndoSel()->add((*sel)[i]); 370 (*sel)[i].mHeight = mTerrainEditor->mSetHeightVal; 371 mTerrainEditor->setGridInfo((*sel)[i]); 372 } 373 mTerrainEditor->scheduleGridUpdate(); 374 } 375} 376 377//------------------------------------------------------------------------------ 378 379void SetEmptyAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) 380{ 381 if ( !selChanged ) 382 return; 383 384 mTerrainEditor->setMissionDirty(); 385 386 for ( U32 i = 0; i < sel->size(); i++ ) 387 { 388 GridInfo &inf = (*sel)[i]; 389 390 // Skip already empty blocks. 391 if ( inf.mMaterial == U8_MAX ) 392 continue; 393 394 // The change flag needs to be set on the undo 395 // so that it knows to restore materials. 396 inf.mMaterialChanged = true; 397 mTerrainEditor->getUndoSel()->add( inf ); 398 399 // Set the material to empty. 400 inf.mMaterial = -1; 401 mTerrainEditor->setGridInfo( inf ); 402 } 403 404 mTerrainEditor->scheduleGridUpdate(); 405} 406 407//------------------------------------------------------------------------------ 408 409void ClearEmptyAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) 410{ 411 if ( !selChanged ) 412 return; 413 414 mTerrainEditor->setMissionDirty(); 415 416 for ( U32 i = 0; i < sel->size(); i++ ) 417 { 418 GridInfo &inf = (*sel)[i]; 419 420 // Skip if not empty. 421 if ( inf.mMaterial != U8_MAX ) 422 continue; 423 424 // The change flag needs to be set on the undo 425 // so that it knows to restore materials. 426 inf.mMaterialChanged = true; 427 mTerrainEditor->getUndoSel()->add( inf ); 428 429 // Set the material 430 inf.mMaterial = 0; 431 mTerrainEditor->setGridInfo( inf ); 432 } 433 434 mTerrainEditor->scheduleGridUpdate(); 435} 436 437//------------------------------------------------------------------------------ 438 439void ScaleHeightAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) 440{ 441 if(selChanged) 442 { 443 for(U32 i = 0; i < sel->size(); i++) 444 { 445 mTerrainEditor->getUndoSel()->add((*sel)[i]); 446 (*sel)[i].mHeight *= mTerrainEditor->mScaleVal; 447 mTerrainEditor->setGridInfo((*sel)[i]); 448 } 449 mTerrainEditor->scheduleGridUpdate(); 450 } 451} 452 453void BrushAdjustHeightAction::process(Selection * sel, const Gui3DMouseEvent & event, bool, Type type) 454{ 455 if(type == Process) 456 return; 457 458 TerrainBlock *terrBlock = mTerrainEditor->getActiveTerrain(); 459 if ( !terrBlock ) 460 return; 461 462 if(type == Begin) 463 { 464 mTerrainEditor->lockSelection(true); 465 mTerrainEditor->getRoot()->mouseLock(mTerrainEditor); 466 467 // the way this works is: 468 // construct a plane that goes through the collision point 469 // with one axis up the terrain Z, and horizontally parallel to the 470 // plane of projection 471 472 // the cross of the camera ffdv and the terrain up vector produces 473 // the cross plane vector. 474 475 // all subsequent mouse actions are collided against the plane and the deltaZ 476 // from the previous position is used to delta the selection up and down. 477 Point3F cameraDir; 478 479 EditTSCtrl::smCamMatrix.getColumn(1, &cameraDir); 480 terrBlock->getTransform().getColumn(2, &mTerrainUpVector); 481 482 // ok, get the cross vector for the plane: 483 Point3F planeCross; 484 mCross(cameraDir, mTerrainUpVector, &planeCross); 485 486 planeCross.normalize(); 487 Point3F planeNormal; 488 489 Point3F intersectPoint; 490 mTerrainEditor->collide(event, intersectPoint); 491 492 mCross(mTerrainUpVector, planeCross, &planeNormal); 493 mIntersectionPlane.set(intersectPoint, planeNormal); 494 495 // ok, we have the intersection point... 496 // project the collision point onto the up vector of the terrain 497 498 mPreviousZ = mDot(mTerrainUpVector, intersectPoint); 499 500 // add to undo 501 // and record the starting heights 502 for(U32 i = 0; i < sel->size(); i++) 503 { 504 mTerrainEditor->getUndoSel()->add((*sel)[i]); 505 (*sel)[i].mStartHeight = (*sel)[i].mHeight; 506 } 507 } 508 else if(type == Update) 509 { 510 // ok, collide the ray from the event with the intersection plane: 511 512 Point3F intersectPoint; 513 Point3F start = event.pos; 514 Point3F end = start + event.vec * 1000; 515 516 F32 t = mIntersectionPlane.intersect(start, end); 517 518 m_point3F_interpolate( start, end, t, intersectPoint); 519 F32 currentZ = mDot(mTerrainUpVector, intersectPoint); 520 521 F32 diff = currentZ - mPreviousZ; 522 523 for(U32 i = 0; i < sel->size(); i++) 524 { 525 (*sel)[i].mHeight = (*sel)[i].mStartHeight + diff * (*sel)[i].mWeight; 526 527 // clamp it 528 if((*sel)[i].mHeight < 0.f) 529 (*sel)[i].mHeight = 0.f; 530 if((*sel)[i].mHeight > 2047.f) 531 (*sel)[i].mHeight = 2047.f; 532 533 mTerrainEditor->setGridInfoHeight((*sel)[i]); 534 } 535 mTerrainEditor->scheduleGridUpdate(); 536 } 537 else if(type == End) 538 { 539 mTerrainEditor->getRoot()->mouseUnlock(mTerrainEditor); 540 } 541} 542 543//------------------------------------------------------------------------------ 544 545AdjustHeightAction::AdjustHeightAction(TerrainEditor * editor) : 546 BrushAdjustHeightAction(editor) 547{ 548 mCursor = 0; 549} 550 551void AdjustHeightAction::process(Selection *sel, const Gui3DMouseEvent & event, bool b, Type type) 552{ 553 Selection * curSel = mTerrainEditor->getCurrentSel(); 554 BrushAdjustHeightAction::process(curSel, event, b, type); 555} 556 557//------------------------------------------------------------------------------ 558// flatten the primary selection then blend in the rest... 559 560void FlattenHeightAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) 561{ 562 if(!sel->size()) 563 return; 564 565 if(selChanged) 566 { 567 F32 average = 0.f; 568 569 // get the average height 570 U32 cPrimary = 0; 571 for(U32 k = 0; k < sel->size(); k++) 572 if((*sel)[k].mPrimarySelect) 573 { 574 cPrimary++; 575 average += (*sel)[k].mHeight; 576 } 577 578 average /= cPrimary; 579 580 // set it 581 for(U32 i = 0; i < sel->size(); i++) 582 { 583 mTerrainEditor->getUndoSel()->add((*sel)[i]); 584 585 // 586 if((*sel)[i].mPrimarySelect) 587 (*sel)[i].mHeight = average; 588 else 589 { 590 F32 h = average - (*sel)[i].mHeight; 591 (*sel)[i].mHeight += (h * (*sel)[i].mWeight); 592 } 593 594 mTerrainEditor->setGridInfo((*sel)[i]); 595 } 596 mTerrainEditor->scheduleGridUpdate(); 597 } 598} 599 600//------------------------------------------------------------------------------ 601 602void SmoothHeightAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) 603{ 604 if(!sel->size()) 605 return; 606 607 if(selChanged) 608 { 609 F32 avgHeight = 0.f; 610 for(U32 k = 0; k < sel->size(); k++) 611 { 612 mTerrainEditor->getUndoSel()->add((*sel)[k]); 613 avgHeight += (*sel)[k].mHeight; 614 } 615 616 avgHeight /= sel->size(); 617 618 // clamp the terrain smooth factor... 619 if(mTerrainEditor->mSmoothFactor < 0.f) 620 mTerrainEditor->mSmoothFactor = 0.f; 621 if(mTerrainEditor->mSmoothFactor > 1.f) 622 mTerrainEditor->mSmoothFactor = 1.f; 623 624 // linear 625 for(U32 i = 0; i < sel->size(); i++) 626 { 627 (*sel)[i].mHeight += (avgHeight - (*sel)[i].mHeight) * mTerrainEditor->mSmoothFactor * (*sel)[i].mWeight; 628 mTerrainEditor->setGridInfo((*sel)[i]); 629 } 630 mTerrainEditor->scheduleGridUpdate(); 631 } 632} 633 634void SmoothSlopeAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) 635{ 636 if(!sel->size()) 637 return; 638 639 if(selChanged) 640 { 641 // Perform simple 2d linear regression on x&z and y&z: 642 // b = (Avg(xz) - Avg(x)Avg(z))/(Avg(x^2) - Avg(x)^2) 643 Point2F prod(0.f, 0.f); // mean of product for covar 644 Point2F avgSqr(0.f, 0.f); // mean sqr of x, y for var 645 Point2F avgPos(0.f, 0.f); 646 F32 avgHeight = 0.f; 647 F32 z; 648 Point2F pos; 649 for(U32 k = 0; k < sel->size(); k++) 650 { 651 mTerrainEditor->getUndoSel()->add((*sel)[k]); 652 pos = Point2F((*sel)[k].mGridPoint.gridPos.x, (*sel)[k].mGridPoint.gridPos.y); 653 z = (*sel)[k].mHeight; 654 655 prod += pos * z; 656 avgSqr += pos * pos; 657 avgPos += pos; 658 avgHeight += z; 659 } 660 661 prod /= sel->size(); 662 avgSqr /= sel->size(); 663 avgPos /= sel->size(); 664 avgHeight /= sel->size(); 665 666 Point2F avgSlope = (prod - avgPos*avgHeight)/(avgSqr - avgPos*avgPos); 667 668 F32 goalHeight; 669 for(U32 i = 0; i < sel->size(); i++) 670 { 671 goalHeight = avgHeight + ((*sel)[i].mGridPoint.gridPos.x - avgPos.x)*avgSlope.x + 672 ((*sel)[i].mGridPoint.gridPos.y - avgPos.y)*avgSlope.y; 673 (*sel)[i].mHeight += (goalHeight - (*sel)[i].mHeight) * (*sel)[i].mWeight; 674 mTerrainEditor->setGridInfo((*sel)[i]); 675 } 676 mTerrainEditor->scheduleGridUpdate(); 677 } 678} 679 680void PaintNoiseAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type type) 681{ 682 // If this is the ending 683 // mouse down event, then 684 // update the noise values. 685 if ( type == Begin ) 686 { 687 mNoise.setSeed( Sim::getCurrentTime() ); 688 mNoise.fBm( &mNoiseData, mNoiseSize, 12, 1.0f, 5.0f ); 689 mNoise.getMinMax( &mNoiseData, &mMinMaxNoise.x, &mMinMaxNoise.y, mNoiseSize ); 690 691 mScale = 1.5f / ( mMinMaxNoise.x - mMinMaxNoise.y); 692 } 693 694 if( selChanged ) 695 { 696 for( U32 i = 0; i < sel->size(); i++ ) 697 { 698 mTerrainEditor->getUndoSel()->add((*sel)[i]); 699 700 const Point2I &gridPos = (*sel)[i].mGridPoint.gridPos; 701 702 const F32 noiseVal = mNoiseData[ ( gridPos.x % mNoiseSize ) + 703 ( ( gridPos.y % mNoiseSize ) * mNoiseSize ) ]; 704 705 (*sel)[i].mHeight += (noiseVal - mMinMaxNoise.y * mScale) * (*sel)[i].mWeight * mTerrainEditor->mNoiseFactor; 706 707 mTerrainEditor->setGridInfo((*sel)[i]); 708 } 709 710 mTerrainEditor->scheduleGridUpdate(); 711 } 712} 713/* 714void ThermalErosionAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) 715{ 716 if( selChanged ) 717 { 718 TerrainBlock *tblock = mTerrainEditor->getActiveTerrain(); 719 if ( !tblock ) 720 return; 721 722 F32 height = 0; 723 F32 maxHeight = 0; 724 U32 shift = getBinLog2( TerrainBlock::BlockSize ); 725 726 for ( U32 x = 0; x < TerrainBlock::BlockSize; x++ ) 727 { 728 for ( U32 y = 0; y < TerrainBlock::BlockSize; y++ ) 729 { 730 height = fixedToFloat( tblock->getHeight( x, y ) ); 731 mTerrainHeights[ x + (y << 8)] = height; 732 733 if ( height > maxHeight ) 734 maxHeight = height; 735 } 736 } 737 738 //mNoise.erodeThermal( &mTerrainHeights, &mNoiseData, 30.0f, 5.0f, 5, TerrainBlock::BlockSize, tblock->getSquareSize(), maxHeight ); 739 740 mNoise.erodeHydraulic( &mTerrainHeights, &mNoiseData, 1, TerrainBlock::BlockSize ); 741 742 F32 heightDiff = 0; 743 744 for( U32 i = 0; i < sel->size(); i++ ) 745 { 746 mTerrainEditor->getUndoSel()->add((*sel)[i]); 747 748 const Point2I &gridPos = (*sel)[i].mGridPoint.gridPos; 749 750 // Need to get the height difference 751 // between the current height and the 752 // erosion height to properly apply the 753 // softness and pressure settings of the brush 754 // for this selection. 755 heightDiff = (*sel)[i].mHeight - mNoiseData[ gridPos.x + (gridPos.y << shift)]; 756 757 (*sel)[i].mHeight -= (heightDiff * (*sel)[i].mWeight); 758 759 mTerrainEditor->setGridInfo((*sel)[i]); 760 } 761 762 mTerrainEditor->gridUpdateComplete(); 763 } 764} 765*/ 766 767 768IMPLEMENT_CONOBJECT( TerrainSmoothAction ); 769 770ConsoleDocClass( TerrainSmoothAction, 771 "@brief Terrain action used for leveling varying terrain heights smoothly.\n\n" 772 "Editor use only.\n\n" 773 "@internal" 774); 775 776TerrainSmoothAction::TerrainSmoothAction() 777 : UndoAction("Terrain Smoothing"), mFactor(1.0), mSteps(1), mTerrainId(NULL) 778{ 779} 780 781void TerrainSmoothAction::initPersistFields() 782{ 783 Parent::initPersistFields(); 784} 785 786void TerrainSmoothAction::smooth( TerrainBlock *terrain, F32 factor, U32 steps ) 787{ 788 AssertFatal( terrain, "TerrainSmoothAction::smooth() - Got null object!" ); 789 790 // Store our input parameters. 791 mTerrainId = terrain->getId(); 792 mSteps = steps; 793 mFactor = factor; 794 795 // The redo can do the rest. 796 redo(); 797} 798 799DefineEngineMethod( TerrainSmoothAction, smooth, void, ( TerrainBlock *terrain, F32 factor, U32 steps ), , "( TerrainBlock obj, F32 factor, U32 steps )") 800{ 801 if (terrain) 802 object->smooth( terrain, factor, mClamp( steps, 1, 13 ) ); 803} 804 805void TerrainSmoothAction::undo() 806{ 807 // First find the terrain from the id. 808 TerrainBlock *terrain; 809 if ( !Sim::findObject( mTerrainId, terrain ) || !terrain ) 810 return; 811 812 // Get the terrain file. 813 TerrainFile *terrFile = terrain->getFile(); 814 815 // Copy our stored heightmap to the file. 816 terrFile->setHeightMap( mUnsmoothedHeights, false ); 817 818 // Tell the terrain to update itself. 819 terrain->updateGrid( Point2I::Zero, Point2I::Max, true ); 820} 821 822void TerrainSmoothAction::redo() 823{ 824 // First find the terrain from the id. 825 TerrainBlock *terrain; 826 if ( !Sim::findObject( mTerrainId, terrain ) || !terrain ) 827 return; 828 829 // Get the terrain file. 830 TerrainFile *terrFile = terrain->getFile(); 831 832 // First copy the heightmap state. 833 mUnsmoothedHeights = terrFile->getHeightMap(); 834 835 // Do the smooth. 836 terrFile->smooth( mFactor, mSteps, false ); 837 838 // Tell the terrain to update itself. 839 terrain->updateGrid( Point2I::Zero, Point2I::Max, true ); 840} 841