forestBrushTool.cpp
Engine/source/forest/editor/forestBrushTool.cpp
Public Variables
Public Functions
ConsoleDocClass(ForestBrushTool , "@brief Defines the brush properties when painting trees in <a href="/coding/class/classforest/">Forest</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Editor\n\n</a>" "Editor use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n\n</a>" "@internal" )
DefineEngineMethod(ForestBrushTool , collectElements , void , () , "" )
bool
ImplementEnumType(ForestBrushMode , "Active brush <a href="/coding/file/zipobject_8cpp/#zipobject_8cpp_1ac6c3dfb4c3a68f849f32cbfb21da4e77">mode</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">type.\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">internal\n\n</a>" )
mCircleArea(F32 radius)
Detailed Description
Public Variables
EndImplementEnumType
Public Functions
ConsoleDocClass(ForestBrushTool , "@brief Defines the brush properties when painting trees in <a href="/coding/class/classforest/">Forest</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Editor\n\n</a>" "Editor use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n\n</a>" "@internal" )
DefineEngineMethod(ForestBrushTool , collectElements , void , () , "" )
findSelectedElements(ForestBrushElement * obj)
IMPLEMENT_CONOBJECT(ForestBrushTool )
ImplementEnumType(ForestBrushMode , "Active brush <a href="/coding/file/zipobject_8cpp/#zipobject_8cpp_1ac6c3dfb4c3a68f849f32cbfb21da4e77">mode</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">type.\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">internal\n\n</a>" )
mCircleArea(F32 radius)
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 "forest/editor/forestBrushTool.h" 26 27#include "forest/forest.h" 28#include "forest/editor/forestUndo.h" 29#include "forest/editor/forestBrushElement.h" 30#include "forest/editor/forestEditorCtrl.h" 31 32#include "gui/worldEditor/editTSCtrl.h" 33#include "console/consoleTypes.h" 34#include "console/engineAPI.h" 35#include "core/util/tVector.h" 36#include "gfx/gfxDrawUtil.h" 37#include "gui/core/guiCanvas.h" 38#include "gfx/primBuilder.h" 39#include "gui/controls/guiTreeViewCtrl.h" 40#include "core/strings/stringUnit.h" 41#include "math/mRandomDeck.h" 42#include "math/mRandomSet.h" 43 44 45bool ForestBrushTool::protectedSetSize( void *object, const char *index, const char *data ) 46{ 47 ForestBrushTool *tool = static_cast<ForestBrushTool*>( object ); 48 F32 val = dAtof(data); 49 tool->setSize( val ); 50 51 return false; 52} 53 54bool ForestBrushTool::protectedSetPressure( void *object, const char *index, const char *data ) 55{ 56 ForestBrushTool *tool = static_cast<ForestBrushTool*>( object ); 57 F32 val = dAtof(data); 58 tool->setPressure( val ); 59 60 return false; 61} 62 63bool ForestBrushTool::protectedSetHardness( void *object, const char *index, const char *data ) 64{ 65 ForestBrushTool *tool = static_cast<ForestBrushTool*>( object ); 66 F32 val = dAtof(data); 67 tool->setHardness( val ); 68 69 return false; 70} 71 72ImplementEnumType( ForestBrushMode, 73 "Active brush mode type.\n" 74 "@internal\n\n") 75 { ForestBrushTool::Paint, "Paint", "Creates Items based on the Elements you have selected.\n" }, 76 { ForestBrushTool::Erase, "Erase", "Erases Items of any Mesh type.\n" }, 77 { ForestBrushTool::EraseSelected, "EraseSelected", "Erases items of a specific type.\n" }, 78EndImplementEnumType; 79 80 81ForestBrushTool::ForestBrushTool() 82 : mRandom( Platform::getRealMilliseconds() + 1 ), 83 mSize( 5.0f ), 84 mPressure( 0.1f ), 85 mHardness( 1.0f ), 86 mMode( Paint ), 87 mColor( ColorI::WHITE ), 88 mBrushDown( false ), 89 mDrawBrush( false ), 90 mStrokeEvent( 0 ), 91 mCurrAction( NULL ) 92{ 93} 94 95ForestBrushTool::~ForestBrushTool() 96{ 97} 98 99IMPLEMENT_CONOBJECT( ForestBrushTool ); 100 101ConsoleDocClass( ForestBrushTool, 102 "@brief Defines the brush properties when painting trees in Forest Editor\n\n" 103 "Editor use only.\n\n" 104 "@internal" 105); 106 107void ForestBrushTool::initPersistFields() 108{ 109 addGroup( "ForestBrushTool" ); 110 111 addField( "mode", TYPEID< BrushMode >(), Offset( mMode, ForestBrushTool) ); 112 113 addProtectedField( "size", TypeF32, Offset( mSize, ForestBrushTool ), 114 &protectedSetSize, &defaultProtectedGetFn, "Brush Size" ); 115 116 addProtectedField( "pressure", TypeF32, Offset( mPressure, ForestBrushTool ), 117 &protectedSetPressure, &defaultProtectedGetFn, "Brush Pressure" ); 118 119 addProtectedField( "hardness", TypeF32, Offset( mHardness, ForestBrushTool ), 120 &protectedSetHardness, &defaultProtectedGetFn, "Brush Hardness" ); 121 122 endGroup( "ForestBrushTool" ); 123 124 Parent::initPersistFields(); 125} 126 127bool ForestBrushTool::onAdd() 128{ 129 if ( !Parent::onAdd() ) 130 return false; 131 132 return true; 133} 134 135void ForestBrushTool::onRemove() 136{ 137 Parent::onRemove(); 138} 139 140void ForestBrushTool::on3DMouseDown( const Gui3DMouseEvent &evt ) 141{ 142 Con::executef( this, "onMouseDown" ); 143 144 if ( !_updateBrushPoint( evt ) || !mForest ) 145 return; 146 147 mBrushDown = true; 148 149 mEditor->getRoot()->showCursor( false ); 150 151 _collectElements(); 152 _onStroke(); 153 154 return; 155} 156 157void ForestBrushTool::on3DMouseUp( const Gui3DMouseEvent &evt ) 158{ 159 _updateBrushPoint( evt ); 160 Sim::cancelEvent( mStrokeEvent ); 161 mBrushDown = false; 162 163 mEditor->getRoot()->showCursor( true ); 164 165 if ( mCurrAction ) 166 { 167 _submitUndo( mCurrAction ); 168 mCurrAction = NULL; 169 } 170 171 //mElements.clear(); 172} 173 174void ForestBrushTool::on3DMouseMove( const Gui3DMouseEvent &evt ) 175{ 176 _updateBrushPoint( evt ); 177} 178 179void ForestBrushTool::on3DMouseDragged( const Gui3DMouseEvent &evt ) 180{ 181 _updateBrushPoint( evt ); 182 183 if ( mBrushDown && !Sim::isEventPending( mStrokeEvent ) ) 184 mStrokeEvent = Sim::postEvent( this, new ForestBrushToolEvent(), Sim::getCurrentTime() + 250 ); 185} 186 187bool ForestBrushTool::onMouseWheel( const GuiEvent &evt ) 188{ 189 if ( evt.modifier & SI_PRIMARY_CTRL ) 190 setPressure( mPressure + evt.fval * ( 0.05f / 120.0f ) ); 191 else if ( evt.modifier & SI_SHIFT ) 192 setHardness( mHardness + evt.fval * ( 0.05f / 120.0f ) ); 193 else 194 setSize( mSize + evt.fval * ( 1.0f / 120.0f ) ); 195 196 return true; 197} 198 199void ForestBrushTool::onRender3D( ) 200{ 201} 202 203void ForestBrushTool::onRender2D() 204{ 205 if ( !mDrawBrush ) 206 return; 207 208 if ( !mEditor->getRoot()->isCursorON() && !mBrushDown ) 209 return; 210 211 RayInfo ri; 212 Point3F start( 0, 0, mLastBrushPoint.z + mSize ); 213 Point3F end( 0, 0, mLastBrushPoint.z - mSize ); 214 215 Vector<Point3F> pointList; 216 217 const U32 steps = 32; 218 219 if ( mForest ) 220 mForest->disableCollision(); 221 222 for ( S32 i = 0; i < steps; i++ ) 223 { 224 F32 radians = (F32)i / (F32)(steps-1) * M_2PI_F; 225 VectorF vec(0,1,0); 226 MathUtils::vectorRotateZAxis( vec, radians ); 227 228 end.x = start.x = mLastBrushPoint.x + vec.x * mSize; 229 end.y = start.y = mLastBrushPoint.y + vec.y * mSize; 230 231 bool hit = gServerContainer.castRay( start, end, TerrainObjectType | StaticShapeObjectType, &ri ); 232 233 if ( hit ) 234 pointList.push_back( ri.point ); 235 } 236 237 if ( mForest ) 238 mForest->enableCollision(); 239 240 if ( pointList.empty() ) 241 return; 242 243 ColorI brushColor( ColorI::WHITE ); 244 245 if ( mMode == Paint ) 246 brushColor = ColorI::BLUE; 247 else if ( mMode == Erase ) 248 brushColor = ColorI::RED; 249 else if ( mMode == EraseSelected ) 250 brushColor.set( 150, 0, 0 ); 251 252 if ( mMode == Paint || mMode == EraseSelected ) 253 { 254 if ( mElements.empty() ) 255 { 256 brushColor.set( 140, 140, 140 ); 257 } 258 } 259 260 mEditor->drawLineList( pointList, brushColor, 1.5f ); 261} 262 263void ForestBrushTool::onActivated( const Gui3DMouseEvent &lastEvent ) 264{ 265 _updateBrushPoint( lastEvent ); 266 267 Con::executef( this, "onActivated" ); 268} 269 270void ForestBrushTool::onDeactivated() 271{ 272 Con::executef( this, "onDeactivated" ); 273} 274 275bool ForestBrushTool::updateGuiInfo() 276{ 277 GuiTextCtrl *statusbar; 278 Sim::findObject( "EWorldEditorStatusBarInfo", statusbar ); 279 280 GuiTextCtrl *selectionBar; 281 Sim::findObject( "EWorldEditorStatusBarSelection", selectionBar ); 282 283 String text; 284 285 if ( mMode == Paint ) 286 text = "Forest Editor ( Paint Tool ) - This brush creates Items based on the Elements you have selected."; 287 else if ( mMode == Erase ) 288 text = "Forest Editor ( Erase Tool ) - This brush erases Items of any Mesh type."; 289 else if ( mMode == EraseSelected ) 290 text = "Forest Editor ( Erase Selected ) - This brush erases Items based on the Elements you have selected."; 291 292 if ( statusbar ) 293 statusbar->setText( text ); 294 295 if ( mMode == Paint || mMode == EraseSelected ) 296 text = String::ToString( "%i elements selected", mElements.size() ); 297 else 298 text = ""; 299 300 if ( selectionBar ) 301 selectionBar->setText( text ); 302 303 return true; 304} 305 306void ForestBrushTool::setSize( F32 val ) 307{ 308 mSize = mClampF( val, 0.0f, 150.0f ); 309 Con::executef( this, "syncBrushToolbar" ); 310} 311 312void ForestBrushTool::setPressure( F32 val ) 313{ 314 mPressure = mClampF( val, 0.0f, 1.0f ); 315 Con::executef( this, "syncBrushToolbar" ); 316} 317 318void ForestBrushTool::setHardness( F32 val ) 319{ 320 mHardness = mClampF( val, 0.0f, 1.0f ); 321 Con::executef( this, "syncBrushToolbar" ); 322} 323 324void ForestBrushTool::_onStroke() 325{ 326 if ( !mForest || !mBrushDown ) 327 return; 328 329 _action( mLastBrushPoint ); 330} 331 332void ForestBrushTool::_action( const Point3F &point ) 333{ 334 if ( mMode == Paint ) 335 _paint( point ); 336 else if ( mMode == Erase || mMode == EraseSelected ) 337 _erase( point ); 338} 339 340inline F32 mCircleArea( F32 radius ) 341{ 342 return radius * radius * M_PI_F; 343} 344 345void ForestBrushTool::_paint( const Point3F &point ) 346{ 347 AssertFatal( mForest, "ForestBrushTool::_paint() - Can't paint without a Forest!" ); 348 349 if ( mElements.empty() ) 350 return; 351 352 // Iterators, pointers, and temporaries. 353 ForestBrushElement *pElement; 354 ForestItemData *pData; 355 F32 radius, area; 356 357 // How much area do we have to fill with trees ( within the brush ). 358 F32 fillArea = mCircleArea( mSize ); 359 360 // Scale that down by pressure, this is how much area we want to fill. 361 fillArea *= mPressure; 362 363 // Create an MRandomSet we can get items we are painting out of with 364 // the desired distribution. 365 // Also grab the smallest and largest radius elements while we are looping. 366 367 ForestBrushElement *smallestElement, *largestElement; 368 smallestElement = largestElement = mElements[0]; 369 370 MRandomSet<ForestBrushElement*> randElementSet(&mRandom); 371 372 for ( S32 i = 0; i < mElements.size(); i++ ) 373 { 374 pElement = mElements[i]; 375 376 if ( pElement->mData->mRadius > largestElement->mData->mRadius ) 377 largestElement = pElement; 378 if ( pElement->mData->mRadius < smallestElement->mData->mRadius ) 379 smallestElement = pElement; 380 381 randElementSet.add( pElement, pElement->mProbability ); 382 } 383 384 // Pull elements from the random set until we would theoretically fill 385 // the desired area. 386 387 F32 areaLeft = fillArea; 388 F32 scaleFactor, sink, randRot, worldCoordZ, slope; 389 Point2F worldCoords; 390 Point3F normalVector, right; 391 Point2F temp; 392 393 const S32 MaxTries = 5; 394 395 396 while ( areaLeft > 0.0f ) 397 { 398 pElement = randElementSet.get(); 399 pData = pElement->mData; 400 401 scaleFactor = mLerp( pElement->mScaleMin, pElement->mScaleMax, mClampF( mPow( mRandom.randF(), pElement->mScaleExponent ), 0.0f, 1.0f ) ); 402 radius = getMax( pData->mRadius * scaleFactor, 0.1f ); 403 area = mCircleArea( radius ); 404 405 areaLeft -= area * 5.0f; // fudge value 406 407 // No room left we are done. 408 //if ( areaLeft < 0.0f ) 409 // break; 410 411 // We have area left to fill... 412 413 const F32 rotRange = mDegToRad( pElement->mRotationRange ); 414 415 // Get a random sink value. 416 sink = mRandom.randF( pElement->mSinkMin, pElement->mSinkMax ) * scaleFactor; 417 418 // Get a random rotation. 419 randRot = mRandom.randF( 0.0f, rotRange ); 420 421 // Look for a place within the brush area to place this item. 422 // We may have to try several times or give and go onto the next. 423 424 S32 i = 0; 425 for ( ; i < MaxTries; i++ ) 426 { 427 // Pick some randoms for placement. 428 worldCoords = MathUtils::randomPointInCircle( mSize ) + point.asPoint2F(); 429 430 // Look for the ground at this position. 431 if ( !getGroundAt( Point3F( worldCoords.x, worldCoords.y , point.z ), 432 &worldCoordZ, 433 &normalVector ) ) 434 { 435 continue; 436 } 437 438 // Does this pass our slope and elevation limits. 439 right.set( normalVector.x, normalVector.y, 0 ); 440 right.normalizeSafe(); 441 slope = mRadToDeg( mDot( right, normalVector ) ); 442 443 if ( worldCoordZ < pElement->mElevationMin || 444 worldCoordZ > pElement->mElevationMax || 445 slope < pElement->mSlopeMin || 446 slope > pElement->mSlopeMax ) 447 { 448 continue; 449 } 450 451 // Are we up against another tree? 452 if ( mForest->getData()->getItems( worldCoords, radius, NULL ) > 0 ) 453 continue; 454 455 // If the trunk radius is set then we need to sink 456 // the tree into the ground a bit to hide the bottom. 457 if ( pElement->mSinkRadius > 0.0f ) 458 { 459 // Items that are not aligned to the ground surface 460 // get sunken down to hide the bottom of their trunks. 461 462 // sunk down a bit to hide their bottoms on slopes. 463 normalVector.z = 0; 464 normalVector.normalizeSafe(); 465 normalVector *= sink; 466 temp = worldCoords + normalVector.asPoint2F(); 467 getGroundAt( Point3F( temp.x, temp.y, point.z ), 468 &worldCoordZ, 469 NULL ); 470 } 471 472 worldCoordZ -= sink; 473 474 // Create a new undo action if this is a new stroke. 475 ForestCreateUndoAction *action = dynamic_cast<ForestCreateUndoAction*>( mCurrAction ); 476 if ( !action ) 477 { 478 action = new ForestCreateUndoAction( mForest->getData(), mEditor ); 479 mCurrAction = action; 480 } 481 482 //Con::printf( "worldCoords = %g, %g, %g", worldCoords.x, worldCoords.y, worldCoordZ ); 483 484 // Let the action manage adding it to the forest. 485 action->addItem( pData, 486 Point3F( worldCoords.x, worldCoords.y, worldCoordZ ), 487 randRot, 488 scaleFactor ); 489 break; 490 } 491 } 492} 493 494void ForestBrushTool::_erase( const Point3F &point ) 495{ 496 AssertFatal( mForest, "ForestBrushTool::_erase() - Can't erase without a Forest!" ); 497 498 // First grab all the forest items around the point. 499 ForestItemVector trees; 500 if ( mForest->getData()->getItems( point.asPoint2F(), mSize, &trees ) == 0 ) 501 return; 502 503 if ( mMode == EraseSelected ) 504 { 505 for ( U32 i = 0; i < trees.size(); i++ ) 506 { 507 const ForestItem &tree = trees[i]; 508 509 if ( !mDatablocks.contains( tree.getData() ) ) 510 { 511 trees.erase_fast( i ); 512 i--; 513 } 514 } 515 } 516 517 if ( trees.empty() ) 518 return; 519 520 // Number of trees to erase depending on pressure. 521 S32 eraseCount = getMax( (S32)mCeil( (F32)trees.size() * mPressure ), 0 ); 522 523 // Initialize an MRandomDeck with trees under the brush. 524 MRandomDeck<ForestItem> deck(&mRandom); 525 deck.addToPile( trees ); 526 deck.shuffle(); 527 528 ForestItem currentTree; 529 530 // Draw eraseCount number of trees from MRandomDeck, adding them to our erase action. 531 for ( U32 i = 0; i < eraseCount; i++ ) 532 { 533 deck.draw(¤tTree); 534 535 // Create a new undo action if this is a new stroke. 536 ForestDeleteUndoAction *action = dynamic_cast<ForestDeleteUndoAction*>( mCurrAction ); 537 if ( !action ) 538 { 539 action = new ForestDeleteUndoAction( mForest->getData(), mEditor ); 540 mCurrAction = action; 541 } 542 543 action->removeItem( currentTree ); 544 } 545} 546 547bool ForestBrushTool::_updateBrushPoint( const Gui3DMouseEvent &event_ ) 548{ 549 // Do a raycast for terrain... thats the placement center. 550 const U32 mask = TerrainObjectType | StaticShapeObjectType; // TODO: Make an option! 551 552 Point3F start( event_.pos ); 553 Point3F end( event_.pos + ( event_.vec * 10000.0f ) ); 554 555 if ( mForest ) 556 mForest->disableCollision(); 557 558 RayInfo rinfo; 559 mDrawBrush = gServerContainer.castRay( start, end, mask, &rinfo ); 560 561 if ( mForest ) 562 mForest->enableCollision(); 563 564 if ( mDrawBrush ) 565 { 566 mLastBrushPoint = rinfo.point; 567 mLastBrushNormal = rinfo.normal; 568 } 569 570 return mDrawBrush; 571} 572 573bool findSelectedElements( ForestBrushElement *obj ) 574{ 575 if ( obj->isSelectedRecursive() ) 576 return true; 577 578 return false; 579} 580 581void ForestBrushTool::_collectElements() 582{ 583 mElements.clear(); 584 585 // Get the selected objects from the tree view. 586 // These can be a combination of ForestBrush(s) and ForestBrushElement(s). 587 588 GuiTreeViewCtrl *brushTree; 589 if ( !Sim::findObject( "ForestEditBrushTree", brushTree ) ) 590 return; 591 592 const char* objectIdList = Con::executef( brushTree, "getSelectedObjectList" ); 593 594 // Collect those objects in a vector and mark them as selected. 595 596 Vector<SimObject*> objectList; 597 SimObject *simobj; 598 599 S32 wordCount = StringUnit::getUnitCount( objectIdList, " " ); 600 601 for ( S32 i = 0; i < wordCount; i++ ) 602 { 603 const char* word = StringUnit::getUnit( objectIdList, i, " " ); 604 605 if ( Sim::findObject( word, simobj ) ) 606 { 607 objectList.push_back( simobj ); 608 simobj->setSelected(true); 609 } 610 } 611 612 // Find all ForestBrushElements that are directly or indirectly selected. 613 614 SimGroup *brushGroup = ForestBrush::getGroup(); 615 brushGroup->findObjectByCallback( findSelectedElements, mElements ); 616 617 // We just needed to flag these objects as selected for the benefit of our 618 // findSelectedElements callback, we can now mark them un-selected again. 619 620 for ( S32 i = 0; i < objectList.size(); i++ ) 621 objectList[i]->setSelected(false); 622 623 // If we are in Paint or EraseSelected mode we filter out elements with 624 // a non-positive probability. 625 626 if ( mMode == Paint || mMode == EraseSelected ) 627 { 628 for ( S32 i = 0; i < mElements.size(); i++ ) 629 { 630 if ( mElements[i]->mProbability <= 0.0f ) 631 { 632 mElements.erase_fast(i); 633 i--; 634 } 635 } 636 } 637 638 // Filter out elements with NULL datablocks and collect all unique datablocks 639 // in a vector. 640 641 mDatablocks.clear(); 642 for ( S32 i = 0; i < mElements.size(); i++ ) 643 { 644 if ( mElements[i]->mData == NULL ) 645 { 646 mElements.erase_fast(i); 647 i--; 648 continue; 649 } 650 651 mDatablocks.push_back_unique( mElements[i]->mData ); 652 } 653} 654 655bool ForestBrushTool::getGroundAt( const Point3F &worldPt, F32 *zValueOut, VectorF *normalOut ) 656{ 657 const U32 mask = TerrainObjectType | StaticShapeObjectType; 658 659 Point3F start( worldPt.x, worldPt.y, worldPt.z + mSize ); 660 Point3F end( worldPt.x, worldPt.y, worldPt.z - mSize ); 661 662 if ( mForest ) 663 mForest->disableCollision(); 664 665 // Do a cast ray at this point from the top to 666 // the bottom of our brush radius. 667 668 RayInfo rinfo; 669 bool hit = gServerContainer.castRay( start, end, mask, &rinfo ); 670 671 if ( mForest ) 672 mForest->enableCollision(); 673 674 if ( !hit ) 675 return false; 676 677 if (zValueOut) 678 *zValueOut = rinfo.point.z; 679 680 if (normalOut) 681 *normalOut = rinfo.normal; 682 683 return true; 684} 685 686DefineEngineMethod( ForestBrushTool, collectElements, void, (), , "" ) 687{ 688 object->collectElements(); 689} 690