Torque3D Documentation / _generateds / terrainActions.cpp

terrainActions.cpp

Engine/source/gui/worldEditor/terrainActions.cpp

More...

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