forestBrushTool.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "platform/platform.h"
  23. #include "forest/editor/forestBrushTool.h"
  24. #include "forest/forest.h"
  25. #include "forest/editor/forestUndo.h"
  26. #include "forest/editor/forestBrushElement.h"
  27. #include "forest/editor/forestEditorCtrl.h"
  28. #include "gui/worldEditor/editTSCtrl.h"
  29. #include "console/consoleTypes.h"
  30. #include "console/engineAPI.h"
  31. #include "core/util/tVector.h"
  32. #include "gfx/gfxDrawUtil.h"
  33. #include "gui/core/guiCanvas.h"
  34. #include "gfx/primBuilder.h"
  35. #include "gui/controls/guiTreeViewCtrl.h"
  36. #include "core/strings/stringUnit.h"
  37. #include "math/mRandomDeck.h"
  38. #include "math/mRandomSet.h"
  39. #include "scene/sceneContainer.h"
  40. #include "console/typeValidators.h"
  41. bool ForestBrushTool::protectedSetSize( void *object, const char *index, const char *data )
  42. {
  43. ForestBrushTool *tool = static_cast<ForestBrushTool*>( object );
  44. F32 val = dAtof(data);
  45. tool->setSize( val );
  46. return false;
  47. }
  48. bool ForestBrushTool::protectedSetPressure( void *object, const char *index, const char *data )
  49. {
  50. ForestBrushTool *tool = static_cast<ForestBrushTool*>( object );
  51. F32 val = dAtof(data);
  52. tool->setPressure( val );
  53. return false;
  54. }
  55. bool ForestBrushTool::protectedSetHardness( void *object, const char *index, const char *data )
  56. {
  57. ForestBrushTool *tool = static_cast<ForestBrushTool*>( object );
  58. F32 val = dAtof(data);
  59. tool->setHardness( val );
  60. return false;
  61. }
  62. ImplementEnumType( ForestBrushMode,
  63. "Active brush mode type.\n"
  64. "@internal\n\n")
  65. { ForestBrushTool::Paint, "Paint", "Creates Items based on the Elements you have selected.\n" },
  66. { ForestBrushTool::Erase, "Erase", "Erases Items of any Mesh type.\n" },
  67. { ForestBrushTool::EraseSelected, "EraseSelected", "Erases items of a specific type.\n" },
  68. EndImplementEnumType;
  69. ForestBrushTool::ForestBrushTool()
  70. : mRandom( Platform::getRealMilliseconds() + 1 ),
  71. mSize( 5.0f ),
  72. mPressure( 0.1f ),
  73. mHardness( 1.0f ),
  74. mMode( Paint ),
  75. mColor( ColorI::WHITE ),
  76. mBrushDown( false ),
  77. mDrawBrush( false ),
  78. mStrokeEvent( 0 ),
  79. mCurrAction( NULL )
  80. {
  81. }
  82. ForestBrushTool::~ForestBrushTool()
  83. {
  84. }
  85. IMPLEMENT_CONOBJECT( ForestBrushTool );
  86. ConsoleDocClass( ForestBrushTool,
  87. "@brief Defines the brush properties when painting trees in Forest Editor\n\n"
  88. "Editor use only.\n\n"
  89. "@internal"
  90. );
  91. IMPLEMENT_CALLBACK(ForestBrushTool, onAction, void, (U32 mode, Point3F point), (mode, point),
  92. "Called when the editor performs a brush action\n"
  93. "@param mode the Int/Enum value of the mode of the action\n"
  94. "@param point the position the action was performed at\n");
  95. FRangeValidator fBrushRange(0.0f, 150.0f);
  96. void ForestBrushTool::initPersistFields()
  97. {
  98. docsURL;
  99. addGroup( "ForestBrushTool" );
  100. addField( "mode", TYPEID< BrushMode >(), Offset( mMode, ForestBrushTool) );
  101. addProtectedFieldV( "size", TypeRangedF32, Offset( mSize, ForestBrushTool ),
  102. &protectedSetSize, &defaultProtectedGetFn, &fBrushRange, "Brush Size" );
  103. addProtectedFieldV( "pressure", TypeRangedF32, Offset( mPressure, ForestBrushTool ),
  104. &protectedSetPressure, &defaultProtectedGetFn, &CommonValidators::NormalizedFloat, "Brush Pressure" );
  105. addProtectedFieldV( "hardness", TypeRangedF32, Offset( mHardness, ForestBrushTool ),
  106. &protectedSetHardness, &defaultProtectedGetFn, &CommonValidators::NormalizedFloat, "Brush Hardness" );
  107. endGroup( "ForestBrushTool" );
  108. Parent::initPersistFields();
  109. }
  110. bool ForestBrushTool::onAdd()
  111. {
  112. if ( !Parent::onAdd() )
  113. return false;
  114. return true;
  115. }
  116. void ForestBrushTool::onRemove()
  117. {
  118. Parent::onRemove();
  119. }
  120. void ForestBrushTool::on3DMouseDown( const Gui3DMouseEvent &evt )
  121. {
  122. Con::executef( this, "onMouseDown" );
  123. if ( !_updateBrushPoint( evt ) || !mForest )
  124. return;
  125. mBrushDown = true;
  126. mEditor->getRoot()->showCursor( false );
  127. _collectElements();
  128. _onStroke();
  129. return;
  130. }
  131. void ForestBrushTool::on3DMouseUp( const Gui3DMouseEvent &evt )
  132. {
  133. _updateBrushPoint( evt );
  134. Sim::cancelEvent( mStrokeEvent );
  135. mBrushDown = false;
  136. mEditor->getRoot()->showCursor( true );
  137. if ( mCurrAction )
  138. {
  139. _submitUndo( mCurrAction );
  140. mCurrAction = NULL;
  141. }
  142. //mElements.clear();
  143. }
  144. void ForestBrushTool::on3DMouseMove( const Gui3DMouseEvent &evt )
  145. {
  146. _updateBrushPoint( evt );
  147. }
  148. void ForestBrushTool::on3DMouseDragged( const Gui3DMouseEvent &evt )
  149. {
  150. _updateBrushPoint( evt );
  151. if ( mBrushDown && !Sim::isEventPending( mStrokeEvent ) )
  152. mStrokeEvent = Sim::postEvent( this, new ForestBrushToolEvent(), Sim::getCurrentTime() + 250 );
  153. }
  154. bool ForestBrushTool::onMouseWheel( const GuiEvent &evt )
  155. {
  156. if ( evt.modifier & SI_PRIMARY_CTRL )
  157. setPressure( mPressure + evt.fval * ( 0.05f / 120.0f ) );
  158. else if ( evt.modifier & SI_SHIFT )
  159. setHardness( mHardness + evt.fval * ( 0.05f / 120.0f ) );
  160. else
  161. setSize( mSize + evt.fval * ( 1.0f / 120.0f ) );
  162. return true;
  163. }
  164. void ForestBrushTool::onRender3D( )
  165. {
  166. }
  167. void ForestBrushTool::onRender2D()
  168. {
  169. if ( !mDrawBrush )
  170. return;
  171. if ( !mEditor->getRoot()->isCursorON() && !mBrushDown )
  172. return;
  173. RayInfo ri;
  174. Point3F start( 0, 0, mLastBrushPoint.z + mSize );
  175. Point3F end( 0, 0, mLastBrushPoint.z - mSize );
  176. Vector<Point3F> pointList;
  177. const U32 steps = 32;
  178. if ( mForest )
  179. mForest->disableCollision();
  180. for ( S32 i = 0; i < steps; i++ )
  181. {
  182. F32 radians = (F32)i / (F32)(steps-1) * M_2PI_F;
  183. VectorF vec(0,1,0);
  184. MathUtils::vectorRotateZAxis( vec, radians );
  185. end.x = start.x = mLastBrushPoint.x + vec.x * mSize;
  186. end.y = start.y = mLastBrushPoint.y + vec.y * mSize;
  187. bool hit = gServerContainer.castRay( start, end, TerrainObjectType | StaticShapeObjectType, &ri );
  188. if ( hit )
  189. pointList.push_back( ri.point );
  190. }
  191. if ( mForest )
  192. mForest->enableCollision();
  193. if ( pointList.empty() )
  194. return;
  195. ColorI brushColor( ColorI::WHITE );
  196. if ( mMode == Paint )
  197. brushColor = ColorI::BLUE;
  198. else if ( mMode == Erase )
  199. brushColor = ColorI::RED;
  200. else if ( mMode == EraseSelected )
  201. brushColor.set( 150, 0, 0 );
  202. if ( mMode == Paint || mMode == EraseSelected )
  203. {
  204. if ( mElements.empty() )
  205. {
  206. brushColor.set( 140, 140, 140 );
  207. }
  208. }
  209. mEditor->drawLineList( pointList, brushColor, 1.5f );
  210. }
  211. void ForestBrushTool::onActivated( const Gui3DMouseEvent &lastEvent )
  212. {
  213. _updateBrushPoint( lastEvent );
  214. Con::executef( this, "onActivated" );
  215. }
  216. void ForestBrushTool::onDeactivated()
  217. {
  218. Con::executef( this, "onDeactivated" );
  219. }
  220. bool ForestBrushTool::updateGuiInfo()
  221. {
  222. GuiTextCtrl *statusbar;
  223. Sim::findObject( "EWorldEditorStatusBarInfo", statusbar );
  224. GuiTextCtrl *selectionBar;
  225. Sim::findObject( "EWorldEditorStatusBarSelection", selectionBar );
  226. String text;
  227. if ( mMode == Paint )
  228. text = "Forest Editor ( Paint Tool ) - This brush creates Items based on the Elements you have selected.";
  229. else if ( mMode == Erase )
  230. text = "Forest Editor ( Erase Tool ) - This brush erases Items of any Mesh type.";
  231. else if ( mMode == EraseSelected )
  232. text = "Forest Editor ( Erase Selected ) - This brush erases Items based on the Elements you have selected.";
  233. if ( statusbar )
  234. statusbar->setText( text );
  235. if ( mMode == Paint || mMode == EraseSelected )
  236. text = String::ToString( "%i elements selected", mElements.size() );
  237. else
  238. text = "";
  239. if ( selectionBar )
  240. selectionBar->setText( text );
  241. return true;
  242. }
  243. void ForestBrushTool::setSize( F32 val )
  244. {
  245. mSize = mClampF( val, 0.0f, 150.0f );
  246. Con::executef( this, "syncBrushToolbar" );
  247. }
  248. void ForestBrushTool::setPressure( F32 val )
  249. {
  250. mPressure = mClampF( val, 0.0f, 1.0f );
  251. Con::executef( this, "syncBrushToolbar" );
  252. }
  253. void ForestBrushTool::setHardness( F32 val )
  254. {
  255. mHardness = mClampF( val, 0.0f, 1.0f );
  256. Con::executef( this, "syncBrushToolbar" );
  257. }
  258. void ForestBrushTool::_onStroke()
  259. {
  260. if ( !mForest || !mBrushDown )
  261. return;
  262. _action( mLastBrushPoint );
  263. }
  264. void ForestBrushTool::_action( const Point3F &point )
  265. {
  266. if ( mMode == Paint )
  267. _paint( point );
  268. else if ( mMode == Erase || mMode == EraseSelected )
  269. _erase( point );
  270. onAction_callback(mMode, point);
  271. }
  272. inline F32 mCircleArea( F32 radius )
  273. {
  274. return radius * radius * M_PI_F;
  275. }
  276. void ForestBrushTool::_paint( const Point3F &point )
  277. {
  278. AssertFatal( mForest, "ForestBrushTool::_paint() - Can't paint without a Forest!" );
  279. if ( mElements.empty() )
  280. return;
  281. // Iterators, pointers, and temporaries.
  282. ForestBrushElement *pElement;
  283. ForestItemData *pData;
  284. F32 radius, area;
  285. // How much area do we have to fill with trees ( within the brush ).
  286. F32 fillArea = mCircleArea( mSize );
  287. // Scale that down by pressure, this is how much area we want to fill.
  288. fillArea *= mPressure;
  289. // Create an MRandomSet we can get items we are painting out of with
  290. // the desired distribution.
  291. // Also grab the smallest and largest radius elements while we are looping.
  292. ForestBrushElement *smallestElement, *largestElement;
  293. smallestElement = largestElement = mElements[0];
  294. MRandomSet<ForestBrushElement*> randElementSet(&mRandom);
  295. for ( S32 i = 0; i < mElements.size(); i++ )
  296. {
  297. pElement = mElements[i];
  298. if ( pElement->mData->mRadius > largestElement->mData->mRadius )
  299. largestElement = pElement;
  300. if ( pElement->mData->mRadius < smallestElement->mData->mRadius )
  301. smallestElement = pElement;
  302. randElementSet.add( pElement, pElement->mProbability );
  303. }
  304. // Pull elements from the random set until we would theoretically fill
  305. // the desired area.
  306. F32 areaLeft = fillArea;
  307. F32 scaleFactor, sink, randRot, worldCoordZ, slope;
  308. Point2F worldCoords;
  309. Point3F normalVector, right;
  310. Point2F temp;
  311. const S32 MaxTries = 5;
  312. while ( areaLeft > 0.0f )
  313. {
  314. pElement = randElementSet.get();
  315. pData = pElement->mData;
  316. scaleFactor = mLerp( pElement->mScaleMin, pElement->mScaleMax, mClampF( mPow( mRandom.randF(), pElement->mScaleExponent ), 0.0f, 1.0f ) );
  317. radius = getMax( pData->mRadius * scaleFactor, 0.1f );
  318. area = mCircleArea( radius );
  319. areaLeft -= area * 5.0f; // fudge value
  320. // No room left we are done.
  321. //if ( areaLeft < 0.0f )
  322. // break;
  323. // We have area left to fill...
  324. const F32 rotRange = mDegToRad( pElement->mRotationRange );
  325. // Get a random sink value.
  326. sink = mRandom.randF( pElement->mSinkMin, pElement->mSinkMax ) * scaleFactor;
  327. // Get a random rotation.
  328. randRot = mRandom.randF( 0.0f, rotRange );
  329. // Look for a place within the brush area to place this item.
  330. // We may have to try several times or give and go onto the next.
  331. S32 i = 0;
  332. for ( ; i < MaxTries; i++ )
  333. {
  334. // Pick some randoms for placement.
  335. worldCoords = MathUtils::randomPointInCircle( mSize ) + point.asPoint2F();
  336. // Look for the ground at this position.
  337. if ( !getGroundAt( Point3F( worldCoords.x, worldCoords.y , point.z ),
  338. &worldCoordZ,
  339. &normalVector ) )
  340. {
  341. continue;
  342. }
  343. // Does this pass our slope and elevation limits.
  344. right.set( normalVector.x, normalVector.y, 0 );
  345. right.normalizeSafe();
  346. slope = mRadToDeg( mDot( right, normalVector ) );
  347. if ( worldCoordZ < pElement->mElevationMin ||
  348. worldCoordZ > pElement->mElevationMax ||
  349. slope < pElement->mSlopeMin ||
  350. slope > pElement->mSlopeMax )
  351. {
  352. continue;
  353. }
  354. // Are we up against another tree?
  355. if ( mForest->getData()->getItems( worldCoords, radius, NULL ) > 0 )
  356. continue;
  357. // If the trunk radius is set then we need to sink
  358. // the tree into the ground a bit to hide the bottom.
  359. if ( pElement->mSinkRadius > 0.0f )
  360. {
  361. // Items that are not aligned to the ground surface
  362. // get sunken down to hide the bottom of their trunks.
  363. // sunk down a bit to hide their bottoms on slopes.
  364. normalVector.z = 0;
  365. normalVector.normalizeSafe();
  366. normalVector *= sink;
  367. temp = worldCoords + normalVector.asPoint2F();
  368. getGroundAt( Point3F( temp.x, temp.y, point.z ),
  369. &worldCoordZ,
  370. NULL );
  371. }
  372. worldCoordZ -= sink;
  373. // Create a new undo action if this is a new stroke.
  374. ForestCreateUndoAction *action = dynamic_cast<ForestCreateUndoAction*>( mCurrAction );
  375. if ( !action )
  376. {
  377. action = new ForestCreateUndoAction( mForest->getData(), mEditor );
  378. mCurrAction = action;
  379. }
  380. //Con::printf( "worldCoords = %g, %g, %g", worldCoords.x, worldCoords.y, worldCoordZ );
  381. // Let the action manage adding it to the forest.
  382. action->addItem( pData,
  383. Point3F( worldCoords.x, worldCoords.y, worldCoordZ ),
  384. randRot,
  385. scaleFactor );
  386. break;
  387. }
  388. }
  389. }
  390. void ForestBrushTool::_erase( const Point3F &point )
  391. {
  392. AssertFatal( mForest, "ForestBrushTool::_erase() - Can't erase without a Forest!" );
  393. // First grab all the forest items around the point.
  394. ForestItemVector trees;
  395. if ( mForest->getData()->getItems( point.asPoint2F(), mSize, &trees ) == 0 )
  396. return;
  397. if ( mMode == EraseSelected )
  398. {
  399. for ( U32 i = 0; i < trees.size(); i++ )
  400. {
  401. const ForestItem &tree = trees[i];
  402. if ( !mDatablocks.contains( tree.getData() ) )
  403. {
  404. trees.erase_fast( i );
  405. i--;
  406. }
  407. }
  408. }
  409. if ( trees.empty() )
  410. return;
  411. // Number of trees to erase depending on pressure.
  412. S32 eraseCount = getMax( (S32)mCeil( (F32)trees.size() * mPressure ), 0 );
  413. // Initialize an MRandomDeck with trees under the brush.
  414. MRandomDeck<ForestItem> deck(&mRandom);
  415. deck.addToPile( trees );
  416. deck.shuffle();
  417. ForestItem currentTree;
  418. // Draw eraseCount number of trees from MRandomDeck, adding them to our erase action.
  419. for ( U32 i = 0; i < eraseCount; i++ )
  420. {
  421. deck.draw(&currentTree);
  422. // Create a new undo action if this is a new stroke.
  423. ForestDeleteUndoAction *action = dynamic_cast<ForestDeleteUndoAction*>( mCurrAction );
  424. if ( !action )
  425. {
  426. action = new ForestDeleteUndoAction( mForest->getData(), mEditor );
  427. mCurrAction = action;
  428. }
  429. action->removeItem( currentTree );
  430. }
  431. }
  432. bool ForestBrushTool::_updateBrushPoint( const Gui3DMouseEvent &event_ )
  433. {
  434. // Do a raycast for terrain... thats the placement center.
  435. const U32 mask = TerrainObjectType | StaticShapeObjectType; // TODO: Make an option!
  436. Point3F start( event_.pos );
  437. Point3F end( event_.pos + ( event_.vec * 10000.0f ) );
  438. if ( mForest )
  439. mForest->disableCollision();
  440. RayInfo rinfo;
  441. mDrawBrush = gServerContainer.castRay( start, end, mask, &rinfo );
  442. if ( mForest )
  443. mForest->enableCollision();
  444. if ( mDrawBrush )
  445. {
  446. mLastBrushPoint = rinfo.point;
  447. mLastBrushNormal = rinfo.normal;
  448. }
  449. return mDrawBrush;
  450. }
  451. bool findSelectedElements( ForestBrushElement *obj )
  452. {
  453. if ( obj->isSelectedRecursive() )
  454. return true;
  455. return false;
  456. }
  457. void ForestBrushTool::_collectElements()
  458. {
  459. mElements.clear();
  460. // Get the selected objects from the tree view.
  461. // These can be a combination of ForestBrush(s) and ForestBrushElement(s).
  462. GuiTreeViewCtrl *brushTree;
  463. if ( !Sim::findObject( "ForestEditBrushTree", brushTree ) )
  464. return;
  465. ConsoleValue cValue = Con::executef( brushTree, "getSelectedObjectList" );
  466. const char* objectIdList = cValue.getString();
  467. // Collect those objects in a vector and mark them as selected.
  468. Vector<SimObject*> objectList;
  469. SimObject *simobj;
  470. S32 wordCount = StringUnit::getUnitCount( objectIdList, " " );
  471. for ( S32 i = 0; i < wordCount; i++ )
  472. {
  473. const char* word = StringUnit::getUnit( objectIdList, i, " " );
  474. if ( Sim::findObject( word, simobj ) )
  475. {
  476. objectList.push_back( simobj );
  477. simobj->setSelected(true);
  478. }
  479. }
  480. // Find all ForestBrushElements that are directly or indirectly selected.
  481. SimSet* brushSet;
  482. if (!Sim::findObject("ForestBrushSet", brushSet))
  483. {
  484. Con::errorf("ForestBrushTool::_collectElements() - could not find ForestBrushSet!");
  485. return;
  486. }
  487. brushSet->findObjectByCallback( findSelectedElements, mElements );
  488. // We just needed to flag these objects as selected for the benefit of our
  489. // findSelectedElements callback, we can now mark them un-selected again.
  490. for ( S32 i = 0; i < objectList.size(); i++ )
  491. objectList[i]->setSelected(false);
  492. // If we are in Paint or EraseSelected mode we filter out elements with
  493. // a non-positive probability.
  494. if ( mMode == Paint || mMode == EraseSelected )
  495. {
  496. for ( S32 i = 0; i < mElements.size(); i++ )
  497. {
  498. if ( mElements[i]->mProbability <= 0.0f )
  499. {
  500. mElements.erase_fast(i);
  501. i--;
  502. }
  503. }
  504. }
  505. // Filter out elements with NULL datablocks and collect all unique datablocks
  506. // in a vector.
  507. mDatablocks.clear();
  508. for ( S32 i = 0; i < mElements.size(); i++ )
  509. {
  510. if ( mElements[i]->mData == NULL )
  511. {
  512. mElements.erase_fast(i);
  513. i--;
  514. continue;
  515. }
  516. mDatablocks.push_back_unique( mElements[i]->mData );
  517. }
  518. }
  519. bool ForestBrushTool::getGroundAt( const Point3F &worldPt, F32 *zValueOut, VectorF *normalOut )
  520. {
  521. const U32 mask = TerrainObjectType | StaticShapeObjectType;
  522. Point3F start( worldPt.x, worldPt.y, worldPt.z + mSize );
  523. Point3F end( worldPt.x, worldPt.y, worldPt.z - mSize );
  524. if ( mForest )
  525. mForest->disableCollision();
  526. // Do a cast ray at this point from the top to
  527. // the bottom of our brush radius.
  528. RayInfo rinfo;
  529. bool hit = gServerContainer.castRay( start, end, mask, &rinfo );
  530. if ( mForest )
  531. mForest->enableCollision();
  532. if ( !hit )
  533. return false;
  534. if (zValueOut)
  535. *zValueOut = rinfo.point.z;
  536. if (normalOut)
  537. *normalOut = rinfo.normal;
  538. return true;
  539. }
  540. DefineEngineMethod( ForestBrushTool, collectElements, void, (), , "" )
  541. {
  542. object->collectElements();
  543. }