Torque3D Documentation / _generateds / terrainEditor.cpp

terrainEditor.cpp

Engine/source/gui/worldEditor/terrainEditor.cpp

More...

Classes:

Public Defines

Public Functions

bool
checkTerrainBlock(TerrainEditor * object, const char * funcName)
ConsoleDocClass(TerrainEditor , "@brief The base Terrain Editor <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tool\n\n</a>" "Editor use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n\n</a>" "@internal" )
DefineEngineMethod(TerrainEditor , addMaterial , S32 , (String matName) , "( string matName )\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "Adds <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> material." )
DefineEngineMethod(TerrainEditor , attachTerrain , void , (const char *terrain) , ("") , "(TerrainBlock terrain)" )
DefineEngineMethod(TerrainEditor , autoMaterialLayer , void , (F32 minHeight, F32 maxHeight, F32 minSlope, F32 maxSlope, F32 coverage) , "Rule based terrain <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">painting.\n</a>" "@param minHeight Minimum terrain height." "@param maxHeight Maximum terrain height." "@param minSlope Minimum terrain slope." "@param maxSlope Maximum terrain slope." "@param coverage Terrain coverage amount." )
DefineEngineMethod(TerrainEditor , clearSelection , void , () , "" )
DefineEngineMethod(TerrainEditor , getActionName , const char * , (U32 index) , "(int num)" )
DefineEngineMethod(TerrainEditor , getActiveTerrain , S32 , () , "" )
DefineEngineMethod(TerrainEditor , getBrushPos , const char * , () , "Returns <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> Point2I." )
DefineEngineMethod(TerrainEditor , getBrushPressure , F32 , () , "()" )
DefineEngineMethod(TerrainEditor , getBrushSize , const char * , () , "()" )
DefineEngineMethod(TerrainEditor , getBrushSoftness , F32 , () , "()" )
DefineEngineMethod(TerrainEditor , getBrushType , const char * , () , "()" )
DefineEngineMethod(TerrainEditor , getCurrentAction , const char * , () , "" )
DefineEngineMethod(TerrainEditor , getMaterialCount , S32 , () , "Returns the current material count." )
DefineEngineMethod(TerrainEditor , getMaterialIndex , S32 , (String name) , "( string name ) - Returns the index of the material with the given name or -1." )
DefineEngineMethod(TerrainEditor , getMaterialName , const char * , (S32 index) , "( int index ) - Returns the name of the material at the given index." )
DefineEngineMethod(TerrainEditor , getMaterials , const char * , () , "() gets the list of current terrain materials." )
DefineEngineMethod(TerrainEditor , getNumActions , S32 , () , "" )
DefineEngineMethod(TerrainEditor , getNumTextures , S32 , () , "" )
DefineEngineMethod(TerrainEditor , getSlopeLimitMaxAngle , F32 , () , "" )
DefineEngineMethod(TerrainEditor , getSlopeLimitMinAngle , F32 , () , "" )
DefineEngineMethod(TerrainEditor , getTerrainBlock , S32 , (S32 index) , "(S32 index)" )
DefineEngineMethod(TerrainEditor , getTerrainBlockCount , S32 , () , "()" )
DefineEngineMethod(TerrainEditor , getTerrainBlocksMaterialList , const char * , () , "() gets the list of current terrain materials <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> all terrain blocks." )
DefineEngineMethod(TerrainEditor , getTerrainUnderWorldPoint , S32 , (const char *ptOrX, const char *Y, const char *Z) , ("", "", "") , "(x/y/z) Gets the terrain block that is located under the given world <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">point.\n</a>" "@param x/y/z The world coordinates (floating point values) you wish <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> query at. " "These can be formatted as either <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> string (\"x y z\") or separately as (x, y, z)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@return Returns the ID of the requested terrain block (0 <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> not found).\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" )
DefineEngineMethod(TerrainEditor , markEmptySquares , void , () , "" )
DefineEngineMethod(TerrainEditor , mirrorTerrain , void , (S32 mirrorIndex) , "" )
DefineEngineMethod(TerrainEditor , processAction , void , (String action) , ("") , "(string action=<a href="/coding/file/typesx86unix_8h/#typesx86unix_8h_1a070d2ce7b6bb7e5c05602aa8c308d0c4">NULL</a>)" )
DefineEngineMethod(TerrainEditor , removeMaterial , void , (S32 index) , "( int index ) - Remove the material at the given index." )
DefineEngineMethod(TerrainEditor , reorderMaterial , void , (S32 index, S32 orderPos) , "( int index, int order ) " "- Reorder material at the given index <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the <a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> position, changing the order in which it is rendered/blended." )
DefineEngineMethod(TerrainEditor , resetSelWeights , void , (bool clear) , "(bool clear)" )
DefineEngineMethod(TerrainEditor , setAction , void , (const char *action_name) , "(string action_name)" )
DefineEngineMethod(TerrainEditor , setBrushPos , void , (Point2I pos) , "Location" )
DefineEngineMethod(TerrainEditor , setBrushPressure , void , (F32 pressure) , "(float pressure)" )
DefineEngineMethod(TerrainEditor , setBrushSize , void , (S32 w, S32 h) , (0) , "(int w [, int h])" )
DefineEngineMethod(TerrainEditor , setBrushSoftness , void , (F32 softness) , "(float softness)" )
DefineEngineMethod(TerrainEditor , setBrushType , void , (String type) , "(string type)" "One of box, ellipse , selection." )
DefineEngineMethod(TerrainEditor , setSlopeLimitMaxAngle , F32 , (F32 angle) , "" )
DefineEngineMethod(TerrainEditor , setSlopeLimitMinAngle , F32 , (F32 angle) , "" )
DefineEngineMethod(TerrainEditor , setTerraformOverlay , void , (bool overlayEnable) , "(bool overlayEnable) - sets the terraformer current heightmap <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> draw as an overlay over the current terrain." )
DefineEngineMethod(TerrainEditor , updateMaterial , bool , (U32 index, String matName) , "( int index, string matName )\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "Changes the material name at the index." )

Detailed Description

Public Defines

AUTOPAINT_UNDO() 

Public Functions

checkTerrainBlock(TerrainEditor * object, const char * funcName)

ConsoleDocClass(TerrainEditor , "@brief The base Terrain Editor <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tool\n\n</a>" "Editor use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n\n</a>" "@internal" )

DefineEngineMethod(TerrainEditor , addMaterial , S32 , (String matName) , "( string matName )\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "Adds <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> material." )

DefineEngineMethod(TerrainEditor , attachTerrain , void , (const char *terrain) , ("") , "(TerrainBlock terrain)" )

DefineEngineMethod(TerrainEditor , autoMaterialLayer , void , (F32 minHeight, F32 maxHeight, F32 minSlope, F32 maxSlope, F32 coverage) , "Rule based terrain <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">painting.\n</a>" "@param minHeight Minimum terrain height." "@param maxHeight Maximum terrain height." "@param minSlope Minimum terrain slope." "@param maxSlope Maximum terrain slope." "@param coverage Terrain coverage amount." )

DefineEngineMethod(TerrainEditor , clearSelection , void , () , "" )

DefineEngineMethod(TerrainEditor , getActionName , const char * , (U32 index) , "(int num)" )

DefineEngineMethod(TerrainEditor , getActiveTerrain , S32 , () , "" )

DefineEngineMethod(TerrainEditor , getBrushPos , const char * , () , "Returns <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> Point2I." )

DefineEngineMethod(TerrainEditor , getBrushPressure , F32 , () , "()" )

DefineEngineMethod(TerrainEditor , getBrushSize , const char * , () , "()" )

DefineEngineMethod(TerrainEditor , getBrushSoftness , F32 , () , "()" )

DefineEngineMethod(TerrainEditor , getBrushType , const char * , () , "()" )

DefineEngineMethod(TerrainEditor , getCurrentAction , const char * , () , "" )

DefineEngineMethod(TerrainEditor , getMaterialCount , S32 , () , "Returns the current material count." )

DefineEngineMethod(TerrainEditor , getMaterialIndex , S32 , (String name) , "( string name ) - Returns the index of the material with the given name or -1." )

DefineEngineMethod(TerrainEditor , getMaterialName , const char * , (S32 index) , "( int index ) - Returns the name of the material at the given index." )

DefineEngineMethod(TerrainEditor , getMaterials , const char * , () , "() gets the list of current terrain materials." )

DefineEngineMethod(TerrainEditor , getNumActions , S32 , () , "" )

DefineEngineMethod(TerrainEditor , getNumTextures , S32 , () , "" )

DefineEngineMethod(TerrainEditor , getSlopeLimitMaxAngle , F32 , () , "" )

DefineEngineMethod(TerrainEditor , getSlopeLimitMinAngle , F32 , () , "" )

DefineEngineMethod(TerrainEditor , getTerrainBlock , S32 , (S32 index) , "(S32 index)" )

DefineEngineMethod(TerrainEditor , getTerrainBlockCount , S32 , () , "()" )

DefineEngineMethod(TerrainEditor , getTerrainBlocksMaterialList , const char * , () , "() gets the list of current terrain materials <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> all terrain blocks." )

DefineEngineMethod(TerrainEditor , getTerrainUnderWorldPoint , S32 , (const char *ptOrX, const char *Y, const char *Z) , ("", "", "") , "(x/y/z) Gets the terrain block that is located under the given world <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">point.\n</a>" "@param x/y/z The world coordinates (floating point values) you wish <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> query at. " "These can be formatted as either <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> string (\"x y z\") or separately as (x, y, z)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@return Returns the ID of the requested terrain block (0 <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> not found).\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" )

DefineEngineMethod(TerrainEditor , markEmptySquares , void , () , "" )

DefineEngineMethod(TerrainEditor , mirrorTerrain , void , (S32 mirrorIndex) , "" )

DefineEngineMethod(TerrainEditor , processAction , void , (String action) , ("") , "(string action=<a href="/coding/file/typesx86unix_8h/#typesx86unix_8h_1a070d2ce7b6bb7e5c05602aa8c308d0c4">NULL</a>)" )

DefineEngineMethod(TerrainEditor , removeMaterial , void , (S32 index) , "( int index ) - Remove the material at the given index." )

DefineEngineMethod(TerrainEditor , reorderMaterial , void , (S32 index, S32 orderPos) , "( int index, int order ) " "- Reorder material at the given index <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the <a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> position, changing the order in which it is rendered/blended." )

DefineEngineMethod(TerrainEditor , resetSelWeights , void , (bool clear) , "(bool clear)" )

DefineEngineMethod(TerrainEditor , setAction , void , (const char *action_name) , "(string action_name)" )

DefineEngineMethod(TerrainEditor , setBrushPos , void , (Point2I pos) , "Location" )

DefineEngineMethod(TerrainEditor , setBrushPressure , void , (F32 pressure) , "(float pressure)" )

DefineEngineMethod(TerrainEditor , setBrushSize , void , (S32 w, S32 h) , (0) , "(int w [, int h])" )

DefineEngineMethod(TerrainEditor , setBrushSoftness , void , (F32 softness) , "(float softness)" )

DefineEngineMethod(TerrainEditor , setBrushType , void , (String type) , "(string type)" "One of box, ellipse , selection." )

DefineEngineMethod(TerrainEditor , setSlopeLimitMaxAngle , F32 , (F32 angle) , "" )

DefineEngineMethod(TerrainEditor , setSlopeLimitMinAngle , F32 , (F32 angle) , "" )

DefineEngineMethod(TerrainEditor , setTerraformOverlay , void , (bool overlayEnable) , "(bool overlayEnable) - sets the terraformer current heightmap <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> draw as an overlay over the current terrain." )

DefineEngineMethod(TerrainEditor , updateMaterial , bool , (U32 index, String matName) , "( int index, string matName )\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "Changes the material name at the index." )

IMPLEMENT_CONOBJECT(TerrainEditor )

   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 "gui/worldEditor/terrainEditor.h"
  26
  27#include "core/frameAllocator.h"
  28#include "core/strings/stringUnit.h"
  29#include "console/consoleTypes.h"
  30#include "console/simEvents.h"
  31#include "console/engineAPI.h"
  32#include "sim/netConnection.h"
  33#include "math/mathUtils.h"
  34#include "gfx/primBuilder.h"
  35#include "gfx/gfxDrawUtil.h"
  36#include "gui/core/guiCanvas.h"
  37#include "gui/worldEditor/terrainActions.h"
  38#include "terrain/terrMaterial.h"
  39
  40#include "T3D/Scene.h"
  41
  42IMPLEMENT_CONOBJECT(TerrainEditor);
  43
  44ConsoleDocClass( TerrainEditor,
  45   "@brief The base Terrain Editor tool\n\n"
  46   "Editor use only.\n\n"
  47   "@internal"
  48);
  49
  50Selection::Selection() :
  51   Vector<GridInfo>(__FILE__, __LINE__),
  52   mName(0),
  53   mUndoFlags(0),
  54   mHashListSize(1024)
  55{
  56   VECTOR_SET_ASSOCIATION(mHashLists);
  57
  58   // clear the hash list
  59   mHashLists.setSize(mHashListSize);
  60   reset();
  61}
  62
  63Selection::~Selection()
  64{
  65}
  66
  67void Selection::reset()
  68{
  69   PROFILE_SCOPE( TerrainEditor_Selection_Reset );
  70
  71   for(U32 i = 0; i < mHashListSize; i++)
  72      mHashLists[i] = -1;
  73   clear();
  74}
  75
  76bool Selection::validate()
  77{
  78   PROFILE_SCOPE( TerrainEditor_Selection_Validate );
  79
  80   // scan all the hashes and verify that the heads they point to point back to them
  81   U32 hashesProcessed = 0;
  82   for(U32 i = 0; i < mHashLists.size(); i++)
  83   {
  84      U32 entry = mHashLists[i];
  85      if(entry == -1)
  86         continue;
  87
  88      GridInfo info = (*this)[entry];
  89      U32 hashIndex = getHashIndex(info.mGridPoint.gridPos);
  90
  91      if( entry != mHashLists[hashIndex] )
  92      {
  93         AssertFatal(false, "Selection hash lists corrupted");
  94         return false;
  95      }
  96      hashesProcessed++;
  97   }
  98
  99   // scan all the entries and verify that anything w/ a prev == -1 is correctly in the hash table
 100   U32 headsProcessed = 0;
 101   for(U32 i = 0; i < size(); i++)
 102   {
 103      GridInfo info = (*this)[i];
 104      if(info.mPrev != -1)
 105         continue;
 106
 107      U32 hashIndex = getHashIndex(info.mGridPoint.gridPos);
 108
 109      if(mHashLists[hashIndex] != i)
 110      {
 111         AssertFatal(false, "Selection list heads corrupted");
 112         return false;
 113      }
 114      headsProcessed++;
 115   }
 116   AssertFatal(headsProcessed == hashesProcessed, "Selection's number of hashes and number of list heads differ.");
 117   return true;
 118}
 119
 120U32 Selection::getHashIndex(const Point2I & pos)
 121{
 122   PROFILE_SCOPE( TerrainEditor_Selection_GetHashIndex );
 123
 124   Point2F pnt = Point2F((F32)pos.x, (F32)pos.y) + Point2F(1.3f,3.5f);
 125   return( (U32)(mFloor(mHashLists.size() * mFmod(pnt.len() * 0.618f, 1.0f))) );
 126}
 127
 128S32 Selection::lookup(const Point2I & pos)
 129{
 130   PROFILE_SCOPE( TerrainEditor_Selection_Lookup );
 131
 132   U32 index = getHashIndex(pos);
 133
 134   S32 entry = mHashLists[index];
 135
 136   while(entry != -1)
 137   {
 138      if((*this)[entry].mGridPoint.gridPos == pos)
 139         return(entry);
 140
 141      entry = (*this)[entry].mNext;
 142   }
 143
 144   return(-1);
 145}
 146
 147void Selection::insert(GridInfo info)
 148{
 149   PROFILE_SCOPE( TerrainEditor_Selection_Insert );
 150
 151   //validate();
 152   // get the index into the hash table
 153   U32 index = getHashIndex(info.mGridPoint.gridPos);
 154
 155   // if there is an existing linked list, make it our next
 156   info.mNext = mHashLists[index];
 157   info.mPrev = -1;
 158
 159   // if there is an existing linked list, make us it's prev
 160   U32 indexOfNewEntry = size();
 161   if(info.mNext != -1)
 162      (*this)[info.mNext].mPrev = indexOfNewEntry;
 163
 164   // the hash table holds the heads of the linked lists. make us the head of this list.
 165   mHashLists[index] = indexOfNewEntry;
 166
 167   // copy us into the vector
 168   push_back(info);
 169   //validate();
 170}
 171
 172bool Selection::remove(const GridInfo &info)
 173{
 174   PROFILE_SCOPE( TerrainEditor_Selection_Remove );
 175
 176   if(size() < 1)
 177      return false;
 178
 179   //AssertFatal( validate(), "Selection hashLists corrupted before Selection.remove()");
 180
 181   U32 hashIndex = getHashIndex(info.mGridPoint.gridPos);
 182   S32 listHead = mHashLists[hashIndex];
 183   //AssertFatal(listHead < size(), "A Selection's hash table is corrupt.");
 184
 185   if(listHead == -1)
 186      return(false);
 187
 188   const S32 victimEntry = lookup(info.mGridPoint.gridPos);
 189   if( victimEntry == -1 )
 190      return(false);
 191
 192   const GridInfo victim = (*this)[victimEntry];
 193   const S32 vicPrev = victim.mPrev;
 194   const S32 vicNext = victim.mNext;
 195
 196   // remove us from the linked list, if there is one.
 197   if(vicPrev != -1)
 198      (*this)[vicPrev].mNext = vicNext;
 199   if(vicNext != -1)
 200      (*this)[vicNext].mPrev = vicPrev;
 201
 202   // if we were the head of the list, make our next the new head in the hash table.
 203   if(vicPrev == -1)
 204      mHashLists[hashIndex] = vicNext;
 205
 206   // if we're not the last element in the vector, copy the last element to our position.
 207   if(victimEntry != size() - 1)
 208   {
 209      // copy last into victim, and re-cache next & prev
 210      const GridInfo lastEntry = last();
 211      const S32 lastPrev = lastEntry.mPrev;
 212      const S32 lastNext = lastEntry.mNext;
 213      (*this)[victimEntry] = lastEntry;
 214
 215      // update the new element's next and prev, to reestablish it in it's linked list.
 216      if(lastPrev != -1)
 217         (*this)[lastPrev].mNext = victimEntry;
 218      if(lastNext != -1)
 219         (*this)[lastNext].mPrev = victimEntry;
 220
 221      // if it was the head of it's list, update the hash table with its new position.
 222      if(lastPrev == -1)
 223      {
 224         const U32 lastHash = getHashIndex(lastEntry.mGridPoint.gridPos);
 225         AssertFatal(mHashLists[lastHash] == size() - 1, "Selection hashLists corrupted during Selection.remove() (oldmsg)");
 226         mHashLists[lastHash] = victimEntry;
 227      }
 228   }
 229
 230   // decrement the vector, we're done here
 231   pop_back();
 232   //AssertFatal( validate(), "Selection hashLists corrupted after Selection.remove()");
 233   return true;
 234}
 235
 236bool Selection::add(const GridInfo &info)
 237{
 238   PROFILE_SCOPE( TerrainEditor_Selection_Add );
 239
 240   S32 index = lookup(info.mGridPoint.gridPos);
 241   if(index != -1)
 242      return(false);
 243
 244   insert(info);
 245   return(true);
 246}
 247
 248bool Selection::getInfo(Point2I pos, GridInfo & info)
 249{
 250   PROFILE_SCOPE( TerrainEditor_Selection_GetInfo );
 251
 252   S32 index = lookup(pos);
 253   if(index == -1)
 254      return(false);
 255
 256   info = (*this)[index];
 257   return(true);
 258}
 259
 260bool Selection::setInfo(GridInfo & info)
 261{
 262   PROFILE_SCOPE( TerrainEditor_Selection_SetInfo );
 263
 264   S32 index = lookup(info.mGridPoint.gridPos);
 265   if(index == -1)
 266      return(false);
 267
 268   S32 next = (*this)[index].mNext;
 269   S32 prev = (*this)[index].mPrev;
 270
 271   (*this)[index] = info;
 272   (*this)[index].mNext = next;
 273   (*this)[index].mPrev = prev;
 274
 275   return(true);
 276}
 277
 278F32 Selection::getAvgHeight()
 279{
 280   PROFILE_SCOPE( TerrainEditor_Selection_GetAvgHeight );
 281
 282   if(!size())
 283      return(0);
 284
 285   F32 avg = 0.f;
 286   for(U32 i = 0; i < size(); i++)
 287      avg += (*this)[i].mHeight;
 288
 289   return(avg / size());
 290}
 291
 292F32 Selection::getMinHeight()
 293{
 294   PROFILE_SCOPE( TerrainEditor_Selection_GetMinHeight );
 295
 296   if(!size())
 297      return(0);
 298
 299   F32 minHeight = (*this)[0].mHeight;
 300   for(U32 i = 1; i < size(); i++)
 301      minHeight = getMin(minHeight, (*this)[i].mHeight);
 302
 303   return minHeight;
 304}
 305
 306F32 Selection::getMaxHeight()
 307{
 308   PROFILE_SCOPE( TerrainEditor_Selection_GetMaxHeight );
 309
 310   if(!size())
 311      return(0);
 312
 313   F32 maxHeight = (*this)[0].mHeight;
 314   for(U32 i = 1; i < size(); i++)
 315      maxHeight = getMax(maxHeight, (*this)[i].mHeight);
 316
 317   return maxHeight;
 318}
 319
 320Brush::Brush(TerrainEditor * editor) :
 321   mTerrainEditor(editor)
 322{
 323   mSize = mTerrainEditor->getBrushSize();
 324}
 325
 326const Point2I & Brush::getPosition()
 327{
 328   return(mGridPoint.gridPos);
 329}
 330
 331const GridPoint & Brush::getGridPoint()
 332{
 333   return mGridPoint;
 334}
 335
 336void Brush::setPosition(const Point3F & pos)
 337{
 338   PROFILE_SCOPE( TerrainEditor_Brush_SetPosition_Point3F );
 339
 340   mTerrainEditor->worldToGrid(pos, mGridPoint);
 341   update();
 342}
 343
 344void Brush::setPosition(const Point2I & pos)
 345{
 346   PROFILE_SCOPE( TerrainEditor_Brush_SetPosition_Point2I );
 347
 348   mGridPoint.gridPos = pos;
 349   update();
 350}
 351
 352void Brush::update()
 353{
 354   PROFILE_SCOPE( TerrainEditor_Brush_update );
 355
 356   if ( mGridPoint.terrainBlock )
 357      rebuild();
 358}
 359
 360void Brush::render()
 361{
 362   PROFILE_SCOPE( TerrainEditor_Brush_Render );
 363
 364   // Render the brush's outline via the derived brush class.
 365   _renderOutline();
 366
 367   // Render the brush's interior grid points.
 368
 369   const U32 pointCount = mSize.x * mSize.y;
 370   if ( pointCount == 0 )
 371      return;
 372
 373   if ( mRenderList.empty() || empty() )
 374      return;
 375
 376   Vector<GFXVertexPCT> pointList;
 377   pointList.reserve( pointCount );
 378
 379   for(S32 x = 0; x < mSize.x; x++)
 380   {
 381      for(S32 y = 0; y < mSize.y; y++)
 382      {
 383         S32 id = mRenderList[x*mSize.x+y];
 384         if ( id == -1 )
 385            continue;
 386
 387         const GridInfo &gInfo = (*this)[ id ];
 388
 389         Point3F pos;
 390         mTerrainEditor->gridToWorld( gInfo.mGridPoint.gridPos, pos, gInfo.mGridPoint.terrainBlock );
 391
 392         if ( !mTerrainEditor->project( pos, &pos ) )
 393            continue;
 394
 395         pointList.increment();
 396         GFXVertexPCT &pointInfo = pointList.last();
 397
 398         pointInfo.point = pos;
 399
 400         pointInfo.color.set( 255, 0, 255, gInfo.mWeight * 255 );
 401
 402         pointInfo.texCoord.set( 1.0f, 0.0f );
 403      }
 404   }
 405
 406   mTerrainEditor->renderPoints( pointList );
 407}
 408
 409void BoxBrush::rebuild()
 410{
 411   PROFILE_SCOPE( TerrainEditor_BoxBrush_Rebuild );
 412
 413   reset();
 414
 415   const F32 squareSize = mGridPoint.terrainBlock->getSquareSize();
 416
 417   mRenderList.setSize(mSize.x*mSize.y);
 418
 419   Point3F center( F32(mSize.x - 1) / 2.0f * squareSize, F32(mSize.y - 1) / 2.0f * squareSize, 0.0f );
 420
 421   Filter filter;
 422   filter.set(1, &mTerrainEditor->mSoftSelectFilter);
 423
 424   const Point3F mousePos = mTerrainEditor->getMousePos();
 425
 426   F32 xFactorScale = center.x / ( center.x + 0.5f );
 427   F32 yFactorScale = center.y / ( center.y + 0.5f );
 428
 429   const F32 softness = mTerrainEditor->getBrushSoftness();
 430   const F32 pressure = mTerrainEditor->getBrushPressure();
 431
 432   Point3F posw( 0,0,0 );
 433   Point2I posg( 0,0 );
 434   Vector<GridInfo> infos;
 435
 436   for ( S32 x = 0; x < mSize.x; x++ )
 437   {
 438      for(S32 y = 0; y < mSize.y; y++)
 439      {
 440         F32 xFactor = 0.0f;
 441         if ( center.x > 0 )
 442            xFactor = mAbs( center.x - x ) / center.x * xFactorScale;
 443
 444         F32 yFactor = 0.0f;
 445         if ( center.y > 0 )
 446            yFactor = mAbs( center.y - y ) / center.y * yFactorScale;
 447
 448         S32 &rl = mRenderList[x*mSize.x+y];
 449
 450         posw.x = mousePos.x + (F32)x * squareSize - center.x;
 451         posw.y = mousePos.y + (F32)y * squareSize - center.y;
 452         // round to grid coords
 453         GridPoint gridPoint = mGridPoint;
 454         mTerrainEditor->worldToGrid( posw, gridPoint );
 455
 456         // Check that the grid point is valid within the terrain.  This assumes
 457         // that there is no wrap around past the edge of the terrain.
 458         if(!mTerrainEditor->isPointInTerrain(gridPoint))
 459         {
 460            rl = -1;
 461            continue;
 462         }
 463
 464         infos.clear();
 465         mTerrainEditor->getGridInfos( gridPoint, infos );
 466
 467         for (U32 z = 0; z < infos.size(); z++)
 468         {
 469            infos[z].mWeight = pressure *
 470               mLerp( infos[z].mWeight, filter.getValue(xFactor > yFactor ? xFactor : yFactor), softness );
 471
 472            push_back(infos[z]);
 473         }
 474
 475         rl = size()-1;
 476      }
 477   }
 478}
 479
 480void BoxBrush::_renderOutline()
 481{
 482   F32 squareSize = mGridPoint.terrainBlock->getSquareSize();
 483
 484   RayInfo ri;
 485   Point3F start( 0, 0, 5000.0f );
 486   Point3F end( 0, 0, -5000.0f );
 487   bool hit;
 488
 489   Vector<Point3F> pointList;
 490   pointList.reserve( 64 );
 491
 492   const ColorI col( 255, 0, 255, 255 );
 493
 494   const Point3F &mousePos = mTerrainEditor->getMousePos();
 495
 496   static const Point2F offsetArray [5] =
 497   {
 498      Point2F( -1, -1 ),
 499      Point2F( 1, -1 ),
 500      Point2F( 1, 1 ),
 501      Point2F( -1, 1 ),
 502      Point2F( -1, -1 ) // repeat of offset[0]
 503   };
 504
 505   // 64 total steps, 4 sides to the box, 16 steps per side.
 506   // 64 / 4 = 16
 507   const U32 steps = 16;
 508
 509   for ( S32 i = 0; i < 4; i++ )
 510   {
 511      const Point2F &offset = offsetArray[i];
 512      const Point2F &next = offsetArray[i+1];
 513
 514      for ( S32 j = 0; j < steps; j++ )
 515      {
 516         F32 frac = (F32)j / ( (F32)steps - 1.0f );
 517
 518         Point2F tmp;
 519         tmp.interpolate( offset, next, frac );
 520
 521         start.x = end.x = mousePos.x + tmp.x * squareSize * 0.5f * (F32)mSize.x;
 522         start.y = end.y = mousePos.y + tmp.y * squareSize * 0.5f * (F32)mSize.y;
 523
 524         hit = gServerContainer.castRay( start, end, TerrainObjectType, &ri );
 525
 526         if ( hit )
 527            pointList.push_back( ri.point );
 528      }
 529   }
 530
 531   mTerrainEditor->drawLineList( pointList, col, 1.0f );
 532}
 533
 534void EllipseBrush::rebuild()
 535{
 536   PROFILE_SCOPE( TerrainEditor_EllipseBrush_Rebuild );
 537
 538   reset();
 539
 540   const F32 squareSize = mGridPoint.terrainBlock->getSquareSize();
 541
 542   mRenderList.setSize(mSize.x*mSize.y);
 543
 544   Point3F center( F32(mSize.x - 1) / 2.0f * squareSize, F32(mSize.y - 1) / 2.0f * squareSize, 0.0f );
 545
 546   Filter filter;
 547   filter.set(1, &mTerrainEditor->mSoftSelectFilter);
 548
 549   const Point3F mousePos = mTerrainEditor->getMousePos();
 550
 551   // a point is in a circle if:
 552   // x^2 + y^2 <= r^2
 553   // a point is in an ellipse if:
 554   // (ax)^2 + (by)^2 <= 1
 555   // where a = 1/halfEllipseWidth and b = 1/halfEllipseHeight
 556
 557   // for a soft-selected ellipse,
 558   // the factor is simply the filtered: ((ax)^2 + (by)^2)
 559
 560   F32 a = 1.0f / (F32(mSize.x) * squareSize * 0.5f);
 561   F32 b = 1.0f / (F32(mSize.y) * squareSize * 0.5f);
 562
 563   const F32 softness = mTerrainEditor->getBrushSoftness();
 564   const F32 pressure = mTerrainEditor->getBrushPressure();
 565
 566   Point3F posw( 0,0,0 );
 567   Point2I posg( 0,0 );
 568   Vector<GridInfo> infos;
 569
 570   for ( S32 x = 0; x < mSize.x; x++ )
 571   {
 572      for ( S32 y = 0; y < mSize.y; y++ )
 573      {
 574         F32 xp = center.x - x * squareSize;
 575         F32 yp = center.y - y * squareSize;
 576
 577         F32 factor = (a * a * xp * xp) + (b * b * yp * yp);
 578         if ( factor > 1 )
 579         {
 580            mRenderList[x*mSize.x+y] = -1;
 581            continue;
 582         }
 583
 584         S32 &rl = mRenderList[x*mSize.x+y];
 585
 586         posw.x = mousePos.x + (F32)x * squareSize - center.x;
 587         posw.y = mousePos.y + (F32)y * squareSize - center.y;
 588
 589         // round to grid coords
 590         GridPoint gridPoint = mGridPoint;
 591         mTerrainEditor->worldToGrid( posw, gridPoint );
 592
 593         // Check that the grid point is valid within the terrain.  This assumes
 594         // that there is no wrap around past the edge of the terrain.
 595         if ( !mTerrainEditor->isPointInTerrain( gridPoint ) )
 596         {
 597            rl = -1;
 598            continue;
 599         }
 600
 601         infos.clear();
 602         mTerrainEditor->getGridInfos( gridPoint, infos );
 603
 604         for ( U32 z = 0; z < infos.size(); z++ )
 605         {
 606            infos[z].mWeight = pressure * mLerp( infos[z].mWeight, filter.getValue( factor ), softness );
 607            push_back(infos[z]);
 608         }
 609
 610         rl = size()-1;
 611      }
 612   }
 613}
 614
 615void EllipseBrush::_renderOutline()
 616{
 617   F32 squareSize = mGridPoint.terrainBlock->getSquareSize();
 618
 619   RayInfo ri;
 620   Point3F start( 0, 0, 5000.0f );
 621   Point3F end( 0, 0, -5000.0f );
 622   bool hit;
 623
 624   Vector<Point3F> pointList;
 625
 626   ColorI col( 255, 0, 255, 255 );
 627
 628   const U32 steps = 64;
 629
 630   const Point3F &mousePos = mTerrainEditor->getMousePos();
 631
 632   for ( S32 i = 0; i < steps; i++ )
 633   {
 634      F32 radians = (F32)i / (F32)(steps-1) * M_2PI_F;
 635      VectorF vec(0,1,0);
 636      MathUtils::vectorRotateZAxis( vec, radians );
 637
 638      start.x = end.x = mousePos.x + vec.x * squareSize * (F32)mSize.x * 0.5f;
 639      start.y = end.y = mousePos.y + vec.y * squareSize * (F32)mSize.y * 0.5f;
 640
 641      hit = gServerContainer.castRay( start, end, TerrainObjectType, &ri );
 642
 643      if ( hit )
 644         pointList.push_back( ri.point );
 645   }
 646
 647   mTerrainEditor->drawLineList( pointList, col, 1.0f );
 648}
 649
 650SelectionBrush::SelectionBrush(TerrainEditor * editor) :
 651   Brush(editor)
 652{
 653   //... grab the current selection
 654}
 655
 656void SelectionBrush::rebuild()
 657{
 658   reset();
 659   //... move the selection
 660}
 661
 662void SelectionBrush::render(Vector<GFXVertexPCT> & vertexBuffer, S32 & verts, S32 & elems, S32 & prims, const LinearColorF & inColorFull, const LinearColorF & inColorNone, const LinearColorF & outColorFull, const LinearColorF & outColorNone) const
 663{
 664   //... render the selection
 665}
 666
 667TerrainEditor::TerrainEditor() :
 668   mActiveTerrain(0),
 669   mMousePos(0,0,0),
 670   mMouseBrush(0),
 671   mInAction(false),
 672   mGridUpdateMin( S32_MAX, S32_MAX ),
 673   mUndoSel(0),
 674   mGridUpdateMax( 0, 0 ),
 675   mNeedsGridUpdate( false ),
 676   mMaxBrushSize(256,256),
 677   mNeedsMaterialUpdate( false ),
 678   mMouseDown( false )
 679{
 680   VECTOR_SET_ASSOCIATION(mActions);
 681
 682   //
 683   resetCurrentSel();
 684
 685   //
 686   mBrushPressure = 1.0f;
 687   mBrushSize.set(1,1);
 688   mBrushSoftness = 1.0f;
 689   mBrushChanged = true;
 690   mMouseBrush = new BoxBrush(this);
 691   mMouseDownSeq = 0;
 692   mIsDirty = false;
 693   mIsMissionDirty = false;
 694   mPaintIndex = -1;
 695
 696   // add in all the actions here..
 697   mActions.push_back(new SelectAction(this));
 698   mActions.push_back(new DeselectAction(this));
 699   mActions.push_back(new ClearAction(this));
 700   mActions.push_back(new SoftSelectAction(this));
 701   mActions.push_back(new OutlineSelectAction(this));
 702   mActions.push_back(new PaintMaterialAction(this));
 703   mActions.push_back(new ClearMaterialsAction(this));
 704   mActions.push_back(new RaiseHeightAction(this));
 705   mActions.push_back(new LowerHeightAction(this));
 706   mActions.push_back(new SetHeightAction(this));
 707   mActions.push_back(new SetEmptyAction(this));
 708   mActions.push_back(new ClearEmptyAction(this));
 709   mActions.push_back(new ScaleHeightAction(this));
 710   mActions.push_back(new BrushAdjustHeightAction(this));
 711   mActions.push_back(new AdjustHeightAction(this));
 712   mActions.push_back(new FlattenHeightAction(this));
 713   mActions.push_back(new SmoothHeightAction(this));
 714   mActions.push_back(new SmoothSlopeAction(this));
 715   mActions.push_back(new PaintNoiseAction(this));
 716   //mActions.push_back(new ThermalErosionAction(this));
 717
 718
 719   // set the default action
 720   mCurrentAction = mActions[0];
 721   mRenderBrush = mCurrentAction->useMouseBrush();
 722
 723   // persist data defaults
 724   mRenderBorder = true;
 725   mBorderHeight = 10;
 726   mBorderFillColor.set(0,255,0,20);
 727   mBorderFrameColor.set(0,255,0,128);
 728   mBorderLineMode = false;
 729   mSelectionHidden = false;
 730   mRenderVertexSelection = false;
 731   mRenderSolidBrush = false;
 732   mProcessUsesBrush = false;
 733
 734   //
 735   mAdjustHeightVal = 10;
 736   mSetHeightVal = 100;
 737   mScaleVal = 1;
 738   mSmoothFactor = 0.1f;
 739   mNoiseFactor = 1.0f;
 740   mMaterialGroup = 0;
 741   mSoftSelectRadius = 50.f;
 742   mAdjustHeightMouseScale = 0.1f;
 743
 744   mSoftSelectDefaultFilter = StringTable->insert("1.000000 0.833333 0.666667 0.500000 0.333333 0.166667 0.000000");
 745   mSoftSelectFilter = mSoftSelectDefaultFilter;
 746
 747   mSlopeMinAngle = 0.0f;
 748   mSlopeMaxAngle = 90.0f;
 749}
 750
 751TerrainEditor::~TerrainEditor()
 752{
 753   // mouse
 754   delete mMouseBrush;
 755
 756   // terrain actions
 757   U32 i;
 758   for(i = 0; i < mActions.size(); i++)
 759      delete mActions[i];
 760
 761   // undo stuff
 762   delete mUndoSel;
 763}
 764
 765TerrainAction * TerrainEditor::lookupAction(const char * name)
 766{
 767   for(U32 i = 0; i < mActions.size(); i++)
 768      if(!dStricmp(mActions[i]->getName(), name))
 769         return(mActions[i]);
 770   return(0);
 771}
 772
 773bool TerrainEditor::onAdd()
 774{
 775   if ( !Parent::onAdd() )
 776      return false;
 777
 778   GFXStateBlockDesc desc;
 779   desc.setZReadWrite( false );
 780   desc.zWriteEnable = false;
 781   desc.setCullMode( GFXCullNone );
 782   desc.setBlend( true, GFXBlendSrcAlpha, GFXBlendDestAlpha );
 783   mStateBlock = GFX->createStateBlock( desc );
 784
 785   return true;
 786}
 787
 788bool TerrainEditor::onWake()
 789{
 790   if ( !Parent::onWake() )
 791      return false;
 792
 793   // Push our default cursor on here once.
 794   GuiCanvas *root = getRoot();
 795   if ( root )
 796   {
 797      S32 currCursor = PlatformCursorController::curArrow;
 798
 799      PlatformWindow *window = root->getPlatformWindow();
 800      PlatformCursorController *controller = window->getCursorController();
 801      controller->pushCursor( currCursor );
 802   }
 803
 804   return true;
 805}
 806
 807void TerrainEditor::onSleep()
 808{
 809   // Pop our default cursor off.
 810   GuiCanvas *root = getRoot();
 811   if ( root )
 812   {
 813      PlatformWindow *window = root->getPlatformWindow();
 814      PlatformCursorController *controller = window->getCursorController();
 815      controller->popCursor();
 816   }
 817
 818   Parent::onSleep();
 819}
 820
 821void TerrainEditor::get3DCursor( GuiCursor *&cursor,
 822                                       bool &visible,
 823                                       const Gui3DMouseEvent &event_ )
 824{
 825   cursor = NULL;
 826   visible = false;
 827
 828   GuiCanvas *root = getRoot();
 829   if ( !root )
 830      return;
 831
 832   S32 currCursor = PlatformCursorController::curArrow;
 833
 834   if ( root->mCursorChanged == currCursor )
 835      return;
 836
 837   PlatformWindow *window = root->getPlatformWindow();
 838   PlatformCursorController *controller = window->getCursorController();
 839
 840   // We've already changed the cursor,
 841   // so set it back before we change it again.
 842   if( root->mCursorChanged != -1)
 843      controller->popCursor();
 844
 845   // Now change the cursor shape
 846   controller->pushCursor(currCursor);
 847   root->mCursorChanged = currCursor;
 848}
 849
 850void TerrainEditor::onDeleteNotify(SimObject * object)
 851{
 852   Parent::onDeleteNotify(object);
 853
 854   if (dynamic_cast<TerrainBlock*>(object) == mActiveTerrain)
 855      mActiveTerrain = NULL;
 856}
 857
 858TerrainBlock* TerrainEditor::getClientTerrain( TerrainBlock *serverTerrain ) const
 859{
 860   if ( !serverTerrain )
 861      serverTerrain = mActiveTerrain;
 862
 863   return serverTerrain ? dynamic_cast<TerrainBlock*>( serverTerrain->getClientObject() ) : NULL;
 864}
 865
 866bool TerrainEditor::isMainTile(const GridPoint & gPoint) const
 867{
 868   const S32 blockSize = (S32)gPoint.terrainBlock->getBlockSize();
 869
 870   Point2I testPos = gPoint.gridPos;
 871   if (!String::compare(getCurrentAction(),"paintMaterial"))
 872   {
 873      if (testPos.x == blockSize)
 874         testPos.x--;
 875      if (testPos.y == blockSize)
 876         testPos.y--;
 877   }
 878
 879   return (testPos.x >= 0 && testPos.x < blockSize && testPos.y >= 0 && testPos.y < blockSize);
 880}
 881
 882TerrainBlock* TerrainEditor::getTerrainUnderWorldPoint(const Point3F & wPos)
 883{
 884   PROFILE_SCOPE( TerrainEditor_GetTerrainUnderWorldPoint );
 885
 886   // Cast a ray straight down from the world position and see which
 887   // Terrain is the closest to our starting point
 888   Point3F startPnt = wPos;
 889   Point3F endPnt = wPos + Point3F(0.0f, 0.0f, -1000.0f);
 890
 891   S32 blockIndex = -1;
 892   F32 nearT = 1.0f;
 893
 894   for (U32 i = 0; i < mTerrainBlocks.size(); i++)
 895   {
 896      Point3F tStartPnt, tEndPnt;
 897
 898      mTerrainBlocks[i]->getWorldTransform().mulP(startPnt, &tStartPnt);
 899      mTerrainBlocks[i]->getWorldTransform().mulP(endPnt, &tEndPnt);
 900
 901      RayInfo ri;
 902      if (mTerrainBlocks[i]->castRayI(tStartPnt, tEndPnt, &ri, true))
 903      {
 904         if (ri.t < nearT)
 905         {
 906            blockIndex = i;
 907            nearT = ri.t;
 908         }
 909      }
 910   }
 911
 912   if (blockIndex > -1)
 913      return mTerrainBlocks[blockIndex];
 914
 915   return NULL;
 916}
 917
 918bool TerrainEditor::gridToWorld(const GridPoint & gPoint, Point3F & wPos)
 919{
 920   PROFILE_SCOPE( TerrainEditor_GridToWorld );
 921
 922   const MatrixF & mat = gPoint.terrainBlock->getTransform();
 923   Point3F origin;
 924   mat.getColumn(3, &origin);
 925
 926   wPos.x = gPoint.gridPos.x * gPoint.terrainBlock->getSquareSize() + origin.x;
 927   wPos.y = gPoint.gridPos.y * gPoint.terrainBlock->getSquareSize() + origin.y;
 928   wPos.z = getGridHeight(gPoint) + origin.z;
 929
 930   return isMainTile(gPoint);
 931}
 932
 933bool TerrainEditor::gridToWorld(const Point2I & gPos, Point3F & wPos, TerrainBlock* terrain)
 934{
 935   GridPoint gridPoint;
 936   gridPoint.gridPos = gPos;
 937   gridPoint.terrainBlock = terrain;
 938
 939   return gridToWorld(gridPoint, wPos);
 940}
 941
 942bool TerrainEditor::worldToGrid(const Point3F & wPos, GridPoint & gPoint)
 943{
 944   PROFILE_SCOPE( TerrainEditor_WorldToGrid );
 945
 946   // If the grid point TerrainBlock is NULL then find the closest Terrain underneath that
 947   // point - pad a little upward in case our incoming point already lies exactly on the terrain
 948   if (!gPoint.terrainBlock)
 949      gPoint.terrainBlock = getTerrainUnderWorldPoint(wPos + Point3F(0.0f, 0.0f, 0.05f));
 950
 951   if (gPoint.terrainBlock == NULL)
 952      return false;
 953
 954   gPoint.gridPos = gPoint.terrainBlock->getGridPos(wPos);
 955   return isMainTile(gPoint);
 956}
 957
 958bool TerrainEditor::worldToGrid(const Point3F & wPos, Point2I & gPos, TerrainBlock* terrain)
 959{
 960   GridPoint gridPoint;
 961   gridPoint.terrainBlock = terrain;
 962
 963   bool ret = worldToGrid(wPos, gridPoint);
 964
 965   gPos = gridPoint.gridPos;
 966
 967   return ret;
 968}
 969
 970bool TerrainEditor::gridToCenter(const Point2I & gPos, Point2I & cPos) const
 971{
 972   // TODO: What is this for... megaterrain or tiled terrains?
 973   cPos.x = gPos.x; // & TerrainBlock::BlockMask;
 974   cPos.y = gPos.y;// & TerrainBlock::BlockMask;
 975
 976   //if (gPos.x == TerrainBlock::BlockSize)
 977   //   cPos.x = gPos.x;
 978   //if (gPos.y == TerrainBlock::BlockSize)
 979   //   cPos.y = gPos.y;
 980
 981   //return isMainTile(gPos);
 982   return true;
 983}
 984
 985//------------------------------------------------------------------------------
 986
 987//bool TerrainEditor::getGridInfo(const Point3F & wPos, GridInfo & info)
 988//{
 989//   Point2I gPos;
 990//   worldToGrid(wPos, gPos);
 991//   return getGridInfo(gPos, info);
 992//}
 993
 994bool TerrainEditor::getGridInfo(const GridPoint & gPoint, GridInfo & info)
 995{
 996   //
 997   info.mGridPoint = gPoint;
 998   info.mMaterial = getGridMaterial(gPoint);
 999   info.mHeight = getGridHeight(gPoint);
1000   info.mWeight = 1.f;
1001   info.mPrimarySelect = true;
1002   info.mMaterialChanged = false;
1003
1004   Point2I cPos;
1005   gridToCenter(gPoint.gridPos, cPos);
1006
1007   return isMainTile(gPoint);
1008}
1009
1010bool TerrainEditor::getGridInfo(const Point2I & gPos, GridInfo & info, TerrainBlock* terrain)
1011{
1012   GridPoint gridPoint;
1013   gridPoint.gridPos = gPos;
1014   gridPoint.terrainBlock = terrain;
1015
1016   return getGridInfo(gridPoint, info);
1017}
1018
1019void TerrainEditor::getGridInfos(const GridPoint & gPoint, Vector<GridInfo>& infos)
1020{
1021   PROFILE_SCOPE( TerrainEditor_GetGridInfos );
1022
1023   // First we test against the brush terrain so that we can
1024   // favor it (this should be the same as the active terrain)
1025   bool foundBrush = false;
1026
1027   GridInfo baseInfo;
1028   if (getGridInfo(gPoint, baseInfo))
1029   {
1030      infos.push_back(baseInfo);
1031
1032      foundBrush = true;
1033   }
1034
1035   // We are going to need the world position to test against
1036   Point3F wPos;
1037   gridToWorld(gPoint, wPos);
1038
1039   // Now loop through our terrain blocks and decide which ones hit the point
1040   // If we already found a hit against our brush terrain we only add points
1041   // that are relatively close to the found point
1042   for (U32 i = 0; i < mTerrainBlocks.size(); i++)
1043   {
1044      // Skip if we've already found the point on the brush terrain
1045      if (foundBrush && mTerrainBlocks[i] == baseInfo.mGridPoint.terrainBlock)
1046         continue;
1047
1048      // Get our grid position
1049      Point2I gPos;
1050      worldToGrid(wPos, gPos, mTerrainBlocks[i]);
1051
1052      GridInfo info;
1053      if (getGridInfo(gPos, info, mTerrainBlocks[i]))
1054      {
1055         // Skip adding this if we already found a GridInfo from the brush terrain
1056         // and the resultant world point isn't equivalent
1057         if (foundBrush)
1058         {
1059            // Convert back to world (since the height can be different)
1060            // Possibly use getHeight() here?
1061            Point3F testWorldPt;
1062            gridToWorld(gPos, testWorldPt, mTerrainBlocks[i]);
1063
1064            if (mFabs( wPos.z - testWorldPt.z ) > 4.0f )
1065               continue;
1066         }
1067
1068         infos.push_back(info);
1069      }
1070   }
1071}
1072
1073void TerrainEditor::setGridInfo(const GridInfo & info, bool checkActive)
1074{
1075   PROFILE_SCOPE( TerrainEditor_SetGridInfo );
1076
1077   setGridHeight(info.mGridPoint, info.mHeight);
1078   setGridMaterial(info.mGridPoint, info.mMaterial);
1079}
1080
1081F32 TerrainEditor::getGridHeight(const GridPoint & gPoint)
1082{
1083   PROFILE_SCOPE( TerrainEditor_GetGridHeight );
1084
1085   Point2I cPos;
1086   gridToCenter( gPoint.gridPos, cPos );
1087   const TerrainFile *file = gPoint.terrainBlock->getFile();
1088   return fixedToFloat( file->getHeight( cPos.x, cPos.y ) );
1089}
1090
1091void TerrainEditor::gridUpdateComplete( bool materialChanged )
1092{
1093   PROFILE_SCOPE( TerrainEditor_GridUpdateComplete );
1094
1095   // TODO: This updates all terrains and not just the ones
1096   // that were changed.  We should keep track of the mGridUpdate
1097   // in world space and transform it into terrain space.
1098
1099   if(mGridUpdateMin.x <= mGridUpdateMax.x)
1100   {
1101      for (U32 i = 0; i < mTerrainBlocks.size(); i++)
1102      {
1103         TerrainBlock *clientTerrain = getClientTerrain( mTerrainBlocks[i] );
1104         if ( materialChanged )
1105            clientTerrain->updateGridMaterials(mGridUpdateMin, mGridUpdateMax);
1106
1107         mTerrainBlocks[i]->updateGrid(mGridUpdateMin, mGridUpdateMax);
1108         clientTerrain->updateGrid(mGridUpdateMin, mGridUpdateMax);
1109      }
1110   }
1111
1112   mGridUpdateMin.set( S32_MAX, S32_MAX );
1113   mGridUpdateMax.set( 0, 0 );
1114   mNeedsGridUpdate = false;
1115}
1116
1117void TerrainEditor::materialUpdateComplete()
1118{
1119   PROFILE_SCOPE( TerrainEditor_MaterialUpdateComplete );
1120
1121   if(mActiveTerrain && (mGridUpdateMin.x <= mGridUpdateMax.x))
1122   {
1123      TerrainBlock * clientTerrain = getClientTerrain(mActiveTerrain);
1124      clientTerrain->updateGridMaterials(mGridUpdateMin, mGridUpdateMax);
1125   }
1126   mGridUpdateMin.set( S32_MAX, S32_MAX );
1127   mGridUpdateMax.set( 0, 0 );
1128   mNeedsMaterialUpdate = false;
1129}
1130
1131void TerrainEditor::setGridHeight(const GridPoint & gPoint, const F32 height)
1132{
1133   PROFILE_SCOPE( TerrainEditor_SetGridHeight );
1134
1135   Point2I cPos;
1136   gridToCenter(gPoint.gridPos, cPos);
1137
1138   mGridUpdateMin.setMin( cPos );
1139   mGridUpdateMax.setMax( cPos );
1140
1141   gPoint.terrainBlock->setHeight(cPos, height);
1142}
1143
1144U8 TerrainEditor::getGridMaterial( const GridPoint &gPoint ) const
1145{
1146   PROFILE_SCOPE( TerrainEditor_GetGridMaterial );
1147
1148   Point2I cPos;
1149   gridToCenter( gPoint.gridPos, cPos );
1150   const TerrainFile *file = gPoint.terrainBlock->getFile();
1151   return file->getLayerIndex( cPos.x, cPos.y );
1152}
1153
1154void TerrainEditor::setGridMaterial( const GridPoint &gPoint, U8 index )
1155{
1156   PROFILE_SCOPE( TerrainEditor_SetGridMaterial );
1157
1158   Point2I cPos;
1159   gridToCenter( gPoint.gridPos, cPos );
1160   TerrainFile *file = gPoint.terrainBlock->getFile();
1161
1162   // If we changed the empty state then we need
1163   // to do a grid update as well.
1164   U8 currIndex = file->getLayerIndex( cPos.x, cPos.y );
1165   if (  ( currIndex == (U8)-1 && index != (U8)-1 ) ||
1166         ( currIndex != (U8)-1 && index == (U8)-1 ) )
1167   {
1168      mGridUpdateMin.setMin( cPos );
1169      mGridUpdateMax.setMax( cPos );
1170      mNeedsGridUpdate = true;
1171   }
1172
1173   file->setLayerIndex( cPos.x, cPos.y, index );
1174}
1175
1176//------------------------------------------------------------------------------
1177
1178TerrainBlock* TerrainEditor::collide(const Gui3DMouseEvent & evt, Point3F & pos)
1179{
1180   PROFILE_SCOPE( TerrainEditor_Collide );
1181
1182   if (mTerrainBlocks.size() == 0)
1183      return NULL;
1184
1185   if ( mMouseDown && !String::compare(getCurrentAction(),"paintMaterial") )
1186   {
1187      if ( !mActiveTerrain )
1188         return NULL;
1189
1190      Point3F tpos, tvec;
1191
1192      tpos = evt.pos;
1193      tvec = evt.vec;
1194
1195      mMousePlane.intersect( evt.pos, evt.vec, &pos );
1196
1197      return mActiveTerrain;
1198   }
1199
1200   const U32 mask = TerrainObjectType;
1201
1202   Point3F start( evt.pos );
1203   Point3F end( evt.pos + ( evt.vec * 10000.0f ) );
1204
1205   RayInfo rinfo;
1206   bool hit = gServerContainer.castRay( start, end, mask, &rinfo );
1207
1208   if ( !hit )
1209      return NULL;
1210
1211   pos = rinfo.point;
1212
1213   return (TerrainBlock*)(rinfo.object);
1214
1215   //
1216   //// call the terrain block's ray collision routine directly
1217   //Point3F startPnt = event.pos;
1218   //Point3F endPnt = event.pos + event.vec * 1000.0f;
1219
1220   //S32 blockIndex = -1;
1221   //F32 nearT = 1.0f;
1222
1223   //for (U32 i = 0; i < mTerrainBlocks.size(); i++)
1224   //{
1225   //   Point3F tStartPnt, tEndPnt;
1226
1227   //   mTerrainBlocks[i]->getWorldTransform().mulP(startPnt, &tStartPnt);
1228   //   mTerrainBlocks[i]->getWorldTransform().mulP(endPnt, &tEndPnt);
1229
1230   //   RayInfo ri;
1231   //   if (mTerrainBlocks[i]->castRayI(tStartPnt, tEndPnt, &ri, true))
1232   //   {
1233   //      if (ri.t < nearT)
1234   //      {
1235   //         blockIndex = i;
1236   //         nearT = ri.t;
1237   //      }
1238   //   }
1239   //}
1240
1241   //if (blockIndex > -1)
1242   //{
1243   //   pos.interpolate(startPnt, endPnt, nearT);
1244
1245   //   return mTerrainBlocks[blockIndex];
1246   //}
1247
1248   //return NULL;
1249}
1250
1251//------------------------------------------------------------------------------
1252
1253void TerrainEditor::updateGuiInfo()
1254{
1255   PROFILE_SCOPE( TerrainEditor_UpdateGuiInfo );
1256
1257   char buf[128];
1258
1259   // mouse num grids
1260   // mouse avg height
1261   // selection num grids
1262   // selection avg height
1263   dSprintf(buf, sizeof(buf), "%d %g %g %g %d %g",
1264      mMouseBrush->size(), mMouseBrush->getMinHeight(),
1265      mMouseBrush->getAvgHeight(), mMouseBrush->getMaxHeight(),
1266      mDefaultSel.size(), mDefaultSel.getAvgHeight());
1267   Con::executef(this, "onGuiUpdate", buf);
1268
1269   // If the brush setup has changed send out
1270   // a notification of that!
1271   if ( mBrushChanged && isMethod( "onBrushChanged" ) )
1272   {
1273      mBrushChanged = false;
1274      Con::executef( this, "onBrushChanged" );
1275   }
1276}
1277
1278//------------------------------------------------------------------------------
1279
1280void TerrainEditor::onPreRender()
1281{
1282   PROFILE_SCOPE( TerrainEditor_OnPreRender );
1283
1284   if ( mNeedsGridUpdate )
1285      gridUpdateComplete( mNeedsMaterialUpdate );
1286   else if ( mNeedsMaterialUpdate )
1287      materialUpdateComplete();
1288
1289   Parent::onPreRender();
1290}
1291
1292void TerrainEditor::renderScene(const RectI &)
1293{
1294   PROFILE_SCOPE( TerrainEditor_RenderScene );
1295
1296   if(mTerrainBlocks.size() == 0)
1297      return;
1298
1299   if(!mSelectionHidden)
1300      renderSelection(mDefaultSel, LinearColorF::RED, LinearColorF::GREEN, LinearColorF::BLUE, LinearColorF::BLUE, true, false);
1301
1302   if(mRenderBrush && mMouseBrush->size())
1303      renderBrush(*mMouseBrush, LinearColorF::GREEN, LinearColorF::RED, LinearColorF::BLUE, LinearColorF::BLUE, false, true);
1304
1305   if(mRenderBorder)
1306      renderBorder();
1307}
1308
1309//------------------------------------------------------------------------------
1310
1311void TerrainEditor::renderGui( Point2I offset, const RectI &updateRect )
1312{
1313   PROFILE_SCOPE( TerrainEditor_RenderGui );
1314
1315   if ( !mActiveTerrain )
1316      return;
1317
1318   // Just in case...
1319   if ( mMouseBrush->getGridPoint().terrainBlock != mActiveTerrain )
1320      mMouseBrush->setTerrain( mActiveTerrain );
1321
1322   mMouseBrush->render();
1323}
1324
1325void TerrainEditor::renderPoints( const Vector<GFXVertexPCT> &pointList )
1326{
1327   PROFILE_SCOPE( TerrainEditor_RenderPoints );
1328
1329   const U32 pointCount = pointList.size();
1330   const U32 vertCount = pointCount * 6;
1331
1332   GFXStateBlockDesc desc;
1333   desc.setBlend( true );
1334   desc.setZReadWrite( false, false );
1335   GFX->setupGenericShaders();
1336   GFX->setStateBlockByDesc( desc );
1337
1338   U32 vertsLeft = vertCount;
1339   U32 offset = 0;
1340
1341   while ( vertsLeft > 0 )
1342   {
1343      U32 vertsThisDrawCall = getMin( (U32)vertsLeft, (U32)GFX_MAX_DYNAMIC_VERTS );
1344      vertsLeft -= vertsThisDrawCall;
1345
1346      GFXVertexBufferHandle<GFXVertexPCT> vbuff( GFX, vertsThisDrawCall, GFXBufferTypeVolatile );
1347      GFXVertexPCT *vert = vbuff.lock();
1348
1349      const U32 loops = vertsThisDrawCall / 6;
1350
1351      for ( S32 i = 0; i < loops; i++ )
1352      {
1353         const GFXVertexPCT &pointInfo = pointList[i + offset];
1354
1355         vert[0].color = vert[1].color = vert[2].color = vert[3].color = vert[4].color = vert[5].color = pointInfo.color;
1356
1357
1358         const F32 halfSize = pointInfo.texCoord.x * 0.5f;
1359         const Point3F &pos = pointInfo.point;
1360
1361         Point3F p0( pos.x - halfSize, pos.y - halfSize, 0.0f );
1362         Point3F p1( pos.x + halfSize, pos.y - halfSize, 0.0f );
1363         Point3F p2( pos.x + halfSize, pos.y + halfSize, 0.0f );
1364         Point3F p3( pos.x - halfSize, pos.y + halfSize, 0.0f );
1365
1366         vert[0].point = p0;
1367         vert[1].point = p1;
1368         vert[2].point = p2;
1369
1370         vert[3].point = p0;
1371         vert[4].point = p2;
1372         vert[5].point = p3;
1373
1374         vert += 6;
1375      }
1376
1377      vbuff.unlock();
1378
1379      GFX->setVertexBuffer( vbuff );
1380
1381      GFX->drawPrimitive( GFXTriangleList, 0, vertsThisDrawCall / 3 );
1382
1383      offset += loops;
1384   }
1385}
1386
1387
1388//------------------------------------------------------------------------------
1389
1390void TerrainEditor::renderSelection( const Selection & sel, const LinearColorF & inColorFull, const LinearColorF & inColorNone, const LinearColorF & outColorFull, const LinearColorF & outColorNone, bool renderFill, bool renderFrame )
1391{
1392   PROFILE_SCOPE( TerrainEditor_RenderSelection );
1393
1394   // Draw nothing if nothing selected.
1395   if(sel.size() == 0)
1396      return;
1397
1398   Vector<GFXVertexPCT> vertexBuffer;
1399   LinearColorF color;
1400   ColorI iColor;
1401
1402   vertexBuffer.setSize(sel.size() * 5);
1403
1404   F32 squareSize = ( mActiveTerrain ) ? mActiveTerrain->getSquareSize() : 1;
1405
1406   // 'RenderVertexSelection' looks really bad so just always use the good one.
1407   if( false && mRenderVertexSelection)
1408   {
1409
1410      for(U32 i = 0; i < sel.size(); i++)
1411      {
1412         Point3F wPos;
1413         bool center = gridToWorld(sel[i].mGridPoint, wPos);
1414
1415         F32 weight = sel[i].mWeight;
1416
1417         if(center)
1418         {
1419            if ( weight < 0.f || weight > 1.f )
1420               color = inColorFull;
1421            else
1422               color.interpolate( inColorNone, inColorFull, weight );
1423         }
1424         else
1425         {
1426            if ( weight < 0.f || weight > 1.f)
1427               color = outColorFull;
1428            else
1429               color.interpolate( outColorFull, outColorNone, weight );
1430         }
1431         //
1432         iColor = color.toColorI();
1433
1434         GFXVertexPCT *verts = &(vertexBuffer[i * 5]);
1435
1436         verts[0].point = wPos + Point3F(-squareSize, squareSize, 0);
1437         verts[0].color = iColor;
1438         verts[1].point = wPos + Point3F( squareSize, squareSize, 0);
1439         verts[1].color = iColor;
1440         verts[2].point = wPos + Point3F( -squareSize, -squareSize, 0);
1441         verts[2].color = iColor;
1442         verts[3].point = wPos + Point3F( squareSize,  -squareSize, 0);
1443         verts[3].color = iColor;
1444         verts[4].point = verts[0].point;
1445         verts[4].color = iColor;
1446      }
1447   }
1448   else
1449   {
1450      // walk the points in the selection
1451      for(U32 i = 0; i < sel.size(); i++)
1452      {
1453         GridPoint selectedGridPoint = sel[i].mGridPoint;
1454         Point2I gPos = selectedGridPoint.gridPos;
1455
1456         GFXVertexPCT *verts = &(vertexBuffer[i * 5]);
1457
1458         bool center = gridToWorld(selectedGridPoint, verts[0].point);
1459         gridToWorld(Point2I(gPos.x + 1, gPos.y), verts[1].point, selectedGridPoint.terrainBlock);
1460         gridToWorld(Point2I(gPos.x + 1, gPos.y + 1), verts[2].point, selectedGridPoint.terrainBlock);
1461         gridToWorld(Point2I(gPos.x, gPos.y + 1), verts[3].point, selectedGridPoint.terrainBlock);
1462         verts[4].point = verts[0].point;
1463
1464         F32 weight = sel[i].mWeight;
1465
1466         if( !mRenderSolidBrush )
1467         {
1468            if ( center )
1469            {
1470               if ( weight < 0.f || weight > 1.f )
1471                  color = inColorFull;
1472               else
1473                  color.interpolate(inColorNone, inColorFull, weight );
1474            }
1475            else
1476            {
1477               if( weight < 0.f || weight > 1.f )
1478                  color = outColorFull;
1479               else
1480                  color.interpolate(outColorFull, outColorNone, weight );
1481            }
1482
1483            iColor = color.toColorI();
1484         }
1485         else
1486         {
1487            if ( center )
1488            {
1489               iColor = LinearColorF(inColorNone).toColorI();
1490            }
1491            else
1492            {
1493               iColor = LinearColorF(outColorFull).toColorI();
1494            }
1495         }
1496
1497         verts[0].color = iColor;
1498         verts[1].color = iColor;
1499         verts[2].color = iColor;
1500         verts[3].color = iColor;
1501         verts[4].color = iColor;
1502      }
1503   }
1504
1505   // Render this bad boy, by stuffing everything into a volatile buffer
1506   // and rendering...
1507   GFXVertexBufferHandle<GFXVertexPCT> selectionVB(GFX, vertexBuffer.size(), GFXBufferTypeStatic);
1508
1509   selectionVB.lock(0, vertexBuffer.size());
1510
1511   // Copy stuff
1512   dMemcpy((void*)&selectionVB[0], (void*)&vertexBuffer[0], sizeof(GFXVertexPCT) * vertexBuffer.size());
1513
1514   selectionVB.unlock();
1515
1516   GFX->setupGenericShaders();
1517   GFX->setStateBlock( mStateBlock );
1518   GFX->setVertexBuffer(selectionVB);
1519
1520   if(renderFill)
1521      for(U32 i=0; i < sel.size(); i++)
1522         GFX->drawPrimitive( GFXTriangleStrip, i*5, 4);
1523
1524   if(renderFrame)
1525      for(U32 i=0; i < sel.size(); i++)
1526         GFX->drawPrimitive( GFXLineStrip , i*5, 4);
1527}
1528
1529void TerrainEditor::renderBrush( const Brush & brush, const LinearColorF & inColorFull, const LinearColorF & inColorNone, const LinearColorF & outColorFull, const LinearColorF & outColorNone, bool renderFill, bool renderFrame )
1530{
1531}
1532
1533void TerrainEditor::renderBorder()
1534{
1535   // TODO: Disabled rendering the terrain borders... it was
1536   // very annoying getting a fullscreen green tint on things.
1537   //
1538   // We should consider killing this all together or coming
1539   // up with a new technique.
1540   /*
1541   Point2I pos(0,0);
1542   Point2I dir[4] = {
1543      Point2I(1,0),
1544      Point2I(0,1),
1545      Point2I(-1,0),
1546      Point2I(0,-1)
1547   };
1548
1549   GFX->setStateBlock( mStateBlock );
1550
1551   //
1552   if(mBorderLineMode)
1553   {
1554      PrimBuild::color(mBorderFrameColor);
1555
1556      PrimBuild::begin( GFXLineStrip, TerrainBlock::BlockSize * 4 + 1 );
1557      for(U32 i = 0; i < 4; i++)
1558      {
1559         for(U32 j = 0; j < TerrainBlock::BlockSize; j++)
1560         {
1561            Point3F wPos;
1562            gridToWorld(pos, wPos, mActiveTerrain);
1563            PrimBuild::vertex3fv( wPos );
1564            pos += dir[i];
1565         }
1566      }
1567
1568      Point3F wPos;
1569      gridToWorld(Point2I(0,0), wPos, mActiveTerrain);
1570      PrimBuild::vertex3fv( wPos );
1571      PrimBuild::end();
1572   }
1573   else
1574   {
1575      GridSquare * gs = mActiveTerrain->findSquare(TerrainBlock::BlockShift, Point2I(0,0));
1576      F32 height = F32(gs->maxHeight) * 0.03125f + mBorderHeight;
1577
1578      const MatrixF & mat = mActiveTerrain->getTransform();
1579      Point3F pos;
1580      mat.getColumn(3, &pos);
1581
1582      Point2F min(pos.x, pos.y);
1583      Point2F max(pos.x + TerrainBlock::BlockSize * mActiveTerrain->getSquareSize(),
1584                  pos.y + TerrainBlock::BlockSize * mActiveTerrain->getSquareSize());
1585
1586      ColorI & a = mBorderFillColor;
1587      ColorI & b = mBorderFrameColor;
1588
1589      for(U32 i = 0; i < 2; i++)
1590      {
1591         //
1592         if(i){ PrimBuild::color(a); PrimBuild::begin( GFXTriangleFan, 4 ); } else { PrimBuild::color(b); PrimBuild::begin( GFXLineStrip, 5 ); }
1593
1594         PrimBuild::vertex3f(min.x, min.y, 0);
1595         PrimBuild::vertex3f(max.x, min.y, 0);
1596         PrimBuild::vertex3f(max.x, min.y, height);
1597         PrimBuild::vertex3f(min.x, min.y, height);
1598         if(!i) PrimBuild::vertex3f( min.x, min.y, 0.f );
1599         PrimBuild::end();
1600
1601         //
1602         if(i){ PrimBuild::color(a); PrimBuild::begin( GFXTriangleFan, 4 ); } else { PrimBuild::color(b); PrimBuild::begin( GFXLineStrip, 5 ); }
1603         PrimBuild::vertex3f(min.x, max.y, 0);
1604         PrimBuild::vertex3f(max.x, max.y, 0);
1605         PrimBuild::vertex3f(max.x, max.y, height);
1606         PrimBuild::vertex3f(min.x, max.y, height);
1607         if(!i) PrimBuild::vertex3f( min.x, min.y, 0.f );
1608         PrimBuild::end();
1609
1610         //
1611         if(i){ PrimBuild::color(a); PrimBuild::begin( GFXTriangleFan, 4 ); } else { PrimBuild::color(b); PrimBuild::begin( GFXLineStrip, 5 ); }
1612         PrimBuild::vertex3f(min.x, min.y, 0);
1613         PrimBuild::vertex3f(min.x, max.y, 0);
1614         PrimBuild::vertex3f(min.x, max.y, height);
1615         PrimBuild::vertex3f(min.x, min.y, height);
1616         if(!i) PrimBuild::vertex3f( min.x, min.y, 0.f );
1617         PrimBuild::end();
1618
1619         //
1620         if(i){ PrimBuild::color(a); PrimBuild::begin( GFXTriangleFan, 4 ); } else { PrimBuild::color(b); PrimBuild::begin( GFXLineStrip, 5 ); }
1621         PrimBuild::vertex3f(max.x, min.y, 0);
1622         PrimBuild::vertex3f(max.x, max.y, 0);
1623         PrimBuild::vertex3f(max.x, max.y, height);
1624         PrimBuild::vertex3f(max.x, min.y, height);
1625         if(!i) PrimBuild::vertex3f( min.x, min.y, 0.f );
1626         PrimBuild::end();
1627      }
1628   }
1629   */
1630}
1631
1632void TerrainEditor::submitUndo( Selection *sel )
1633{
1634   // Grab the mission editor undo manager.
1635   UndoManager *undoMan = NULL;
1636   if ( !Sim::findObject( "EUndoManager", undoMan ) )
1637   {
1638      Con::errorf( "TerrainEditor::submitUndo() - EUndoManager not found!" );
1639      return;
1640   }
1641
1642   // Create and submit the action.
1643   TerrainEditorUndoAction *action = new TerrainEditorUndoAction( "Terrain Editor Action" );
1644   action->mSel = sel;
1645   action->mTerrainEditor = this;
1646   undoMan->addAction( action );
1647
1648   // Mark the editor as dirty!
1649   setDirty();
1650}
1651
1652void TerrainEditor::TerrainEditorUndoAction::undo()
1653{
1654   // NOTE: This function also handles TerrainEditorUndoAction::redo().
1655
1656   bool materialChanged = false;
1657
1658   for (U32 i = 0; i < mSel->size(); i++)
1659   {
1660      // Grab the current grid info for this point.
1661      GridInfo info;
1662      mTerrainEditor->getGridInfo( (*mSel)[i].mGridPoint, info );
1663      info.mMaterialChanged = (*mSel)[i].mMaterialChanged;
1664
1665      materialChanged |= info.mMaterialChanged;
1666
1667      // Restore the previous grid info.
1668      mTerrainEditor->setGridInfo( (*mSel)[i] );
1669
1670      // Save the old grid info so we can
1671      // restore it later.
1672      (*mSel)[i] = info;
1673   }
1674
1675   // Mark the editor as dirty!
1676   mTerrainEditor->setDirty();
1677   mTerrainEditor->gridUpdateComplete( materialChanged );
1678   mTerrainEditor->mMouseBrush->update();
1679}
1680
1681void TerrainEditor::submitMaterialUndo( String actionName )
1682{
1683   // Grab the mission editor undo manager.
1684   UndoManager *undoMan = NULL;
1685   if ( !Sim::findObject( "EUndoManager", undoMan ) )
1686   {
1687      Con::errorf( "TerrainEditor::submitMaterialUndo() - EUndoManager not found!" );
1688      return;
1689   }
1690
1691   TerrainBlock *terr = getClientTerrain();
1692
1693   // Create and submit the action.
1694   TerrainMaterialUndoAction *action = new TerrainMaterialUndoAction( actionName );
1695   action->mTerrain = terr;
1696   action->mMaterials = terr->getMaterials();
1697   action->mLayerMap = terr->getLayerMap();
1698   action->mEditor = this;
1699
1700   undoMan->addAction( action );
1701
1702   // Mark the editor as dirty!
1703   setDirty();
1704}
1705
1706void TerrainEditor::onMaterialUndo( TerrainBlock *terr )
1707{
1708   setDirty();
1709   scheduleMaterialUpdate();
1710   setGridUpdateMinMax();
1711
1712   terr->mDetailsDirty = true;
1713   terr->mLayerTexDirty = true;
1714
1715   Con::executef( this, "onMaterialUndo" );
1716}
1717
1718void TerrainEditor::TerrainMaterialUndoAction::undo()
1719{
1720   Vector<TerrainMaterial*> tempMaterials = mTerrain->getMaterials();
1721   Vector<U8> tempLayers = mTerrain->getLayerMap();
1722
1723   mTerrain->setMaterials(mMaterials);
1724   mTerrain->setLayerMap(mLayerMap);
1725
1726   mMaterials = tempMaterials;
1727   mLayerMap = tempLayers;
1728
1729   mEditor->onMaterialUndo( mTerrain );
1730}
1731
1732void TerrainEditor::TerrainMaterialUndoAction::redo()
1733{
1734   undo();
1735}
1736
1737class TerrainProcessActionEvent : public SimEvent
1738{
1739   U32 mSequence;
1740public:
1741   TerrainProcessActionEvent(U32 seq)
1742   {
1743      mSequence = seq;
1744   }
1745   void process(SimObject *object)
1746   {
1747      ((TerrainEditor *) object)->processActionTick(mSequence);
1748   }
1749};
1750
1751void TerrainEditor::processActionTick(U32 sequence)
1752{
1753   if(mMouseDownSeq == sequence)
1754   {
1755      Sim::postEvent(this, new TerrainProcessActionEvent(mMouseDownSeq), Sim::getCurrentTime() + 30);
1756      mCurrentAction->process(mMouseBrush, mLastEvent, false, TerrainAction::Update);
1757   }
1758}
1759
1760bool TerrainEditor::onInputEvent(const InputEventInfo & event)
1761{
1762   /*
1763   if (  mRightMousePassThru &&
1764         event.deviceType == KeyboardDeviceType &&
1765         event.objType == SI_KEY &&
1766         event.objInst == KEY_TAB &&
1767         event.action == SI_MAKE )
1768   {
1769      if ( isMethod( "onToggleToolWindows" ) )
1770         Con::executef( this, "onToggleToolWindows" );
1771   }
1772   */
1773
1774   return Parent::onInputEvent( event );
1775}
1776
1777void TerrainEditor::on3DMouseDown(const Gui3DMouseEvent & event)
1778{
1779   getRoot()->showCursor( false );
1780
1781   if(mTerrainBlocks.size() == 0)
1782      return;
1783
1784   if (!String::compare(getCurrentAction(),"paintMaterial"))
1785   {
1786      Point3F pos;
1787      TerrainBlock* hitTerrain = collide(event, pos);
1788
1789      if(!hitTerrain)
1790         return;
1791
1792      // Set the active terrain
1793      bool changed = mActiveTerrain != hitTerrain;
1794      mActiveTerrain = hitTerrain;
1795
1796      if (changed)
1797      {
1798         Con::executef(this, "onActiveTerrainChange", Con::getIntArg(hitTerrain->getId()));
1799         mMouseBrush->setTerrain(mActiveTerrain);
1800         //if(mRenderBrush)
1801            //mCursorVisible = false;
1802         mMousePos = pos;
1803
1804         mMouseBrush->setPosition(mMousePos);
1805
1806         return;
1807      }
1808   }
1809   else if ((event.modifier & SI_ALT) && !String::compare(getCurrentAction(),"setHeight"))
1810   {
1811      // Set value to terrain height at mouse position
1812      GridInfo info;
1813      getGridInfo(mMouseBrush->getGridPoint(), info);
1814      mSetHeightVal = info.mHeight;
1815      mBrushChanged = true;
1816      return;
1817   }
1818
1819   mMousePlane.set( mMousePos, Point3F(0,0,1) );
1820   mMouseDown = true;
1821
1822   mSelectionLocked = false;
1823
1824   mouseLock();
1825   mMouseDownSeq++;
1826   mUndoSel = new Selection;
1827   mCurrentAction->process(mMouseBrush, event, true, TerrainAction::Begin);
1828   // process on ticks - every 30th of a second.
1829   Sim::postEvent(this, new TerrainProcessActionEvent(mMouseDownSeq), Sim::getCurrentTime() + 30);
1830}
1831
1832void TerrainEditor::on3DMouseMove(const Gui3DMouseEvent & event)
1833{
1834   PROFILE_SCOPE( TerrainEditor_On3DMouseMove );
1835
1836   if(mTerrainBlocks.size() == 0)
1837      return;
1838
1839   Point3F pos;
1840   TerrainBlock* hitTerrain = collide(event, pos);
1841
1842   if(!hitTerrain)
1843   {
1844      mMouseBrush->reset();
1845      return;
1846   }
1847   else
1848   {
1849      // We do not change the active terrain as the mouse moves when
1850      // in painting mode.  This is because it causes the material
1851      // window to change as you cursor over to it.
1852      if ( String::compare(getCurrentAction(),"paintMaterial") != 0 )
1853      {
1854         // Set the active terrain
1855         bool changed = mActiveTerrain != hitTerrain;
1856         mActiveTerrain = hitTerrain;
1857
1858         if (changed)
1859            Con::executef(this, "onActiveTerrainChange", Con::getIntArg(hitTerrain->getId()));
1860      }
1861
1862      mMousePos = pos;
1863
1864      mMouseBrush->setTerrain(mActiveTerrain);
1865      mMouseBrush->setPosition(mMousePos);
1866   }
1867}
1868
1869void TerrainEditor::on3DMouseDragged(const Gui3DMouseEvent & event)
1870{
1871   PROFILE_SCOPE( TerrainEditor_On3DMouseDragged );
1872
1873   if ( mTerrainBlocks.empty() )
1874      return;
1875
1876   if ( !isMouseLocked() )
1877      return;
1878
1879   Point3F pos;
1880   bool selChanged = false;
1881
1882   if ( !mSelectionLocked )
1883   {
1884      TerrainBlock* hitTerrain = collide(event, pos);
1885
1886      if (!hitTerrain)
1887      {
1888         mMouseBrush->reset();
1889         return;
1890      }
1891
1892      // check if the mouse has actually moved in grid space
1893      Point2I gMouse;
1894      Point2I gLastMouse;
1895      worldToGrid( pos, gMouse );
1896      worldToGrid( mMousePos, gLastMouse );
1897
1898      mMousePos = pos;
1899      mMouseBrush->setPosition( mMousePos );
1900
1901      selChanged = gMouse != gLastMouse;
1902   }
1903   if (selChanged)
1904      mCurrentAction->process( mMouseBrush, event, true, TerrainAction::Update );
1905}
1906
1907void TerrainEditor::on3DMouseUp(const Gui3DMouseEvent & event)
1908{
1909   mMouseDown = false;
1910   getRoot()->showCursor( true );
1911
1912   if ( mTerrainBlocks.size() == 0 )
1913      return;
1914
1915   if ( isMouseLocked() )
1916   {
1917      mouseUnlock();
1918      mMouseDownSeq++;
1919      mCurrentAction->process( mMouseBrush, event, false, TerrainAction::End );
1920
1921      if ( mUndoSel->size() )
1922         submitUndo( mUndoSel );
1923      else
1924         delete mUndoSel;
1925
1926      mUndoSel = 0;
1927      mInAction = false;
1928   }
1929}
1930
1931bool TerrainEditor::onMouseWheelDown( const GuiEvent & event )
1932{
1933   if ( event.modifier & SI_PRIMARY_CTRL && event.modifier & SI_SHIFT )
1934   {
1935      setBrushPressure( mBrushPressure - 0.1f );
1936      return true;
1937   }
1938   else if ( event.modifier & SI_SHIFT )
1939   {
1940      setBrushSoftness( mBrushSoftness + 0.05f );
1941      return true;
1942   }
1943   else if ( event.modifier & SI_PRIMARY_CTRL )
1944   {
1945      Point2I newBrush = getBrushSize() - Point2I(1,1);
1946      setBrushSize( newBrush.x, newBrush.y );
1947      return true;
1948   }
1949
1950   return Parent::onMouseWheelDown( event );
1951}
1952
1953bool TerrainEditor::onMouseWheelUp( const GuiEvent & event )
1954{
1955   if ( event.modifier & SI_PRIMARY_CTRL && event.modifier & SI_SHIFT )
1956   {
1957      setBrushPressure( mBrushPressure + 0.1f );
1958      return true;
1959   }
1960   else if ( event.modifier & SI_SHIFT )
1961   {
1962      setBrushSoftness( mBrushSoftness - 0.05f );
1963      return true;
1964   }
1965   else if( event.modifier & SI_PRIMARY_CTRL )
1966   {
1967      Point2I newBrush = getBrushSize() + Point2I(1,1);
1968      setBrushSize( newBrush.x, newBrush.y );
1969      return true;
1970   }
1971
1972   return Parent::onMouseWheelUp( event );
1973}
1974
1975//------------------------------------------------------------------------------
1976// any console function which depends on a terrainBlock attached to the editor
1977// should call this
1978bool checkTerrainBlock(TerrainEditor * object, const char * funcName)
1979{
1980   if(!object->terrainBlockValid())
1981   {
1982      Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::%s: not attached to a terrain block!", funcName);
1983      return(false);
1984   }
1985   return(true);
1986}
1987
1988void TerrainEditor::attachTerrain(TerrainBlock *terrBlock)
1989{
1990   mActiveTerrain = terrBlock;
1991   mTerrainBlocks.push_back_unique(terrBlock);
1992}
1993
1994void TerrainEditor::detachTerrain(TerrainBlock *terrBlock)
1995{
1996   if (mActiveTerrain == terrBlock)
1997      mActiveTerrain = NULL; //do we want to set this to an existing terrain?
1998
1999   if (mMouseBrush->getGridPoint().terrainBlock == terrBlock)
2000      mMouseBrush->setTerrain(NULL);
2001
2002   // reset the brush as its gridinfos may still have references to the old terrain
2003   mMouseBrush->reset();
2004
2005   mTerrainBlocks.remove(terrBlock);
2006}
2007
2008TerrainBlock* TerrainEditor::getTerrainBlock(S32 index)
2009{
2010   if(index < 0 || index >= mTerrainBlocks.size())
2011      return NULL;
2012
2013   return mTerrainBlocks[index];
2014}
2015
2016void TerrainEditor::getTerrainBlocksMaterialList(Vector<StringTableEntry>& list)
2017{
2018   for(S32 i=0; i<mTerrainBlocks.size(); ++i)
2019   {
2020      TerrainBlock* tb = mTerrainBlocks[i];
2021      if(!tb)
2022         continue;
2023
2024      for(S32 m=0; m<tb->getMaterialCount(); ++m)
2025      {
2026         TerrainMaterial* mat = tb->getMaterial(m);
2027         if (mat)
2028            list.push_back_unique(mat->getInternalName());
2029      }
2030   }
2031}
2032
2033void TerrainEditor::setBrushType( const char *type )
2034{
2035   if ( mMouseBrush && String::compare( mMouseBrush->getType(), type ) == 0 )
2036      return;
2037
2038   if(!dStricmp(type, "box"))
2039   {
2040      delete mMouseBrush;
2041      mMouseBrush = new BoxBrush(this);
2042      mBrushChanged = true;
2043   }
2044   else if(!dStricmp(type, "ellipse"))
2045   {
2046      delete mMouseBrush;
2047      mMouseBrush = new EllipseBrush(this);
2048      mBrushChanged = true;
2049   }
2050   else if(!dStricmp(type, "selection"))
2051   {
2052      delete mMouseBrush;
2053      mMouseBrush = new SelectionBrush(this);
2054      mBrushChanged = true;
2055   }
2056   else {}
2057}
2058
2059const char* TerrainEditor::getBrushType() const
2060{
2061   if ( mMouseBrush )
2062      return mMouseBrush->getType();
2063
2064   return "";
2065}
2066
2067void TerrainEditor::setBrushSize( S32 w, S32 h )
2068{
2069   w = mClamp( w, 1, mMaxBrushSize.x );
2070   h = mClamp( h, 1, mMaxBrushSize.y );
2071
2072   if ( w == mBrushSize.x && h == mBrushSize.y )
2073      return;
2074
2075   mBrushSize.set( w, h );
2076   mBrushChanged = true;
2077
2078   if ( mMouseBrush )
2079   {
2080      mMouseBrush->setSize( mBrushSize );
2081
2082      if ( mMouseBrush->getGridPoint().terrainBlock )
2083         mMouseBrush->rebuild();
2084   }
2085}
2086
2087void TerrainEditor::setBrushPressure( F32 pressure )
2088{
2089   pressure = mClampF( pressure, 0.01f, 1.0f );
2090
2091   if ( mBrushPressure == pressure )
2092      return;
2093
2094   mBrushPressure = pressure;
2095   mBrushChanged = true;
2096
2097   if ( mMouseBrush && mMouseBrush->getGridPoint().terrainBlock )
2098      mMouseBrush->rebuild();
2099}
2100
2101void TerrainEditor::setBrushSoftness( F32 softness )
2102{
2103   softness = mClampF( softness, 0.01f, 1.0f );
2104
2105   if ( mBrushSoftness == softness )
2106      return;
2107
2108   mBrushSoftness = softness;
2109   mBrushChanged = true;
2110
2111   if ( mMouseBrush && mMouseBrush->getGridPoint().terrainBlock )
2112      mMouseBrush->rebuild();
2113}
2114
2115const char* TerrainEditor::getBrushPos()
2116{
2117   AssertFatal(mMouseBrush!=<a href="/coding/file/types_8lint_8h/#types_8lint_8h_1a070d2ce7b6bb7e5c05602aa8c308d0c4">NULL</a>, "TerrainEditor::getBrushPos: no mouse brush!");
2118
2119   Point2I pos = mMouseBrush->getPosition();
2120   static const U32 bufSize = 32;
2121   char * ret = Con::getReturnBuffer(bufSize);
2122   dSprintf(ret, bufSize, "%d %d", pos.x, pos.y);
2123   return(ret);
2124}
2125
2126void TerrainEditor::setBrushPos(Point2I pos)
2127{
2128   AssertFatal(mMouseBrush!=<a href="/coding/file/types_8lint_8h/#types_8lint_8h_1a070d2ce7b6bb7e5c05602aa8c308d0c4">NULL</a>, "TerrainEditor::setBrushPos: no mouse brush!");
2129   mMouseBrush->setPosition(pos);
2130}
2131
2132void TerrainEditor::setAction(const char* action)
2133{
2134   for(U32 i = 0; i < mActions.size(); i++)
2135   {
2136      if(!dStricmp(mActions[i]->getName(), action))
2137      {
2138         mCurrentAction = mActions[i];
2139
2140         //
2141         mRenderBrush = mCurrentAction->useMouseBrush();
2142         return;
2143      }
2144   }
2145}
2146
2147const char* TerrainEditor::getActionName(U32 index)
2148{
2149   if(index >= mActions.size())
2150      return("");
2151   return(mActions[index]->getName());
2152}
2153
2154const char* TerrainEditor::getCurrentAction() const
2155{
2156   return(mCurrentAction->getName());
2157}
2158
2159S32 TerrainEditor::getNumActions()
2160{
2161   return(mActions.size());
2162}
2163
2164void TerrainEditor::resetSelWeights(bool clear)
2165{
2166   //
2167   if(!clear)
2168   {
2169      for(U32 i = 0; i < mDefaultSel.size(); i++)
2170      {
2171         mDefaultSel[i].mPrimarySelect = false;
2172         mDefaultSel[i].mWeight = 1.f;
2173      }
2174      return;
2175   }
2176
2177   Selection sel;
2178
2179   U32 i;
2180   for(i = 0; i < mDefaultSel.size(); i++)
2181   {
2182      if(mDefaultSel[i].mPrimarySelect)
2183      {
2184         mDefaultSel[i].mWeight = 1.f;
2185         sel.add(mDefaultSel[i]);
2186      }
2187   }
2188
2189   mDefaultSel.reset();
2190
2191   for(i = 0; i < sel.size(); i++)
2192      mDefaultSel.add(sel[i]);
2193}
2194
2195void TerrainEditor::clearSelection()
2196{
2197   mDefaultSel.reset();
2198}
2199
2200void TerrainEditor::processAction(const char* sAction)
2201{
2202   if(!checkTerrainBlock(this, "processAction"))
2203      return;
2204
2205   TerrainAction * action = mCurrentAction;
2206   if (String::compare(sAction, "") != 0)
2207   {
2208      action = lookupAction(sAction);
2209
2210      if(!action)
2211      {
2212         Con::errorf(ConsoleLogEntry::General, "TerrainEditor::cProcessAction: invalid action name '%s'.", sAction);
2213         return;
2214      }
2215   }
2216
2217   if(!getCurrentSel()->size() && !mProcessUsesBrush)
2218      return;
2219
2220   mUndoSel = new Selection;
2221
2222   Gui3DMouseEvent event;
2223   if(mProcessUsesBrush)
2224      action->process(mMouseBrush, event, true, TerrainAction::Process);
2225   else
2226      action->process(getCurrentSel(), event, true, TerrainAction::Process);
2227
2228   // check if should delete the undo
2229   if(mUndoSel->size())
2230      submitUndo( mUndoSel );
2231   else
2232      delete mUndoSel;
2233
2234   mUndoSel = 0;
2235}
2236
2237S32 TerrainEditor::getNumTextures()
2238{
2239   if(!checkTerrainBlock(this, "getNumTextures"))
2240      return(0);
2241
2242   // walk all the possible material lists and count them..
2243   U32 count = 0;
2244   for (U32 t = 0; t < mTerrainBlocks.size(); t++)
2245      count += mTerrainBlocks[t]->getMaterialCount();
2246
2247   return count;
2248}
2249
2250void TerrainEditor::markEmptySquares()
2251{
2252   if(!checkTerrainBlock(this, "markEmptySquares"))
2253      return;
2254}
2255
2256void TerrainEditor::mirrorTerrain(S32 mirrorIndex)
2257{
2258   if(!checkTerrainBlock(this, "mirrorTerrain"))
2259      return;
2260
2261   // TODO!
2262   /*
2263   TerrainBlock * terrain = mActiveTerrain;
2264   setDirty();
2265
2266   //
2267   enum {
2268      top = BIT(0),
2269      bottom = BIT(1),
2270      left = BIT(2),
2271      right = BIT(3)
2272   };
2273
2274   U32 sides[8] =
2275   {
2276      bottom,
2277      bottom | left,
2278      left,
2279      left | top,
2280      top,
2281      top | right,
2282      right,
2283      bottom | right
2284   };
2285
2286   U32 n = TerrainBlock::BlockSize;
2287   U32 side = sides[mirrorIndex % 8];
2288   bool diag = mirrorIndex & 0x01;
2289
2290   Point2I src((side & right) ? (n - 1) : 0, (side & bottom) ? (n - 1) : 0);
2291   Point2I dest((side & left) ? (n - 1) : 0, (side & top) ? (n - 1) : 0);
2292   Point2I origSrc(src);
2293   Point2I origDest(dest);
2294
2295   // determine the run length
2296   U32 minStride = ((side & top) || (side & bottom)) ? n : n / 2;
2297   U32 majStride = ((side & left) || (side & right)) ? n : n / 2;
2298
2299   Point2I srcStep((side & right) ? -1 : 1, (side & bottom) ? -1 : 1);
2300   Point2I destStep((side & left) ? -1 : 1, (side & top) ? -1 : 1);
2301
2302   //
2303   U16 * heights = terrain->getHeightAddress(0,0);
2304   U8 * baseMaterials = terrain->getBaseMaterialAddress(0,0);
2305   TerrainBlock::Material * materials = terrain->getMaterial(0,0);
2306
2307   // create an undo selection
2308   Selection * undo = new Selection;
2309
2310   // walk through all the positions
2311   for(U32 i = 0; i < majStride; i++)
2312   {
2313      for(U32 j = 0; j < minStride; j++)
2314      {
2315         // skip the same position
2316         if(src != dest)
2317         {
2318            U32 si = src.x + (src.y << TerrainBlock::BlockShift);
2319            U32 di = dest.x + (dest.y << TerrainBlock::BlockShift);
2320
2321            // add to undo selection
2322            GridInfo info;
2323            getGridInfo(dest, info, terrain);
2324            undo->add(info);
2325
2326            //... copy info... (height, basematerial, material)
2327            heights[di] = heights[si];
2328            baseMaterials[di] = baseMaterials[si];
2329            materials[di] = materials[si];
2330         }
2331
2332         // get to the new position
2333         src.x += srcStep.x;
2334         diag ? (dest.y += destStep.y) : (dest.x += destStep.x);
2335      }
2336
2337      // get the next position for a run
2338      src.y += srcStep.y;
2339      diag ? (dest.x += destStep.x) : (dest.y += destStep.y);
2340
2341      // reset the minor run
2342      src.x = origSrc.x;
2343      diag ? (dest.y = origDest.y) : (dest.x = origDest.x);
2344
2345      // shorten the run length for diag runs
2346      if(diag)
2347         minStride--;
2348   }
2349
2350   // rebuild stuff..
2351   terrain->buildGridMap();
2352   terrain->rebuildEmptyFlags();
2353   terrain->packEmptySquares();
2354
2355   // add undo selection
2356   submitUndo( undo );
2357   */
2358}
2359
2360bool TerrainEditor::isPointInTerrain( const GridPoint & gPoint)
2361{
2362   PROFILE_SCOPE( TerrainEditor_IsPointInTerrain );
2363
2364   Point2I cPos;
2365   gridToCenter( gPoint.gridPos, cPos );
2366   const TerrainFile *file = gPoint.terrainBlock->getFile();
2367   return file->isPointInTerrain( cPos.x, cPos.y );
2368}
2369
2370void TerrainEditor::reorderMaterial( S32 index, S32 orderPos )
2371{
2372   TerrainBlock *terr = getClientTerrain();
2373   Vector<U8> layerMap = terr->getLayerMap();
2374   Vector<TerrainMaterial*> materials = terr->getMaterials();
2375
2376   TerrainMaterial *pMat = materials[index];
2377
2378   submitMaterialUndo( String::ToString( "Reordered %s Material", terr->getMaterialName(index) ) );
2379
2380   materials.erase( index );
2381   materials.insert( orderPos, pMat );
2382
2383   Vector<U8>::iterator itr = layerMap.begin();
2384   for ( ; itr != layerMap.end(); itr++ )
2385   {
2386      // Was previous material, set to new index.
2387      if ( *itr == index )
2388         *itr = orderPos;
2389      else
2390      {
2391         // We removed a Material prior to this one, bump it down.
2392         if ( *itr > index )
2393            (*itr)--;
2394         // We added a Material prior to this one, bump it up.
2395         if ( *itr >= orderPos )
2396            (*itr)++;
2397      }
2398   }
2399
2400   terr->setMaterials( materials );
2401   terr->setLayerMap( layerMap );
2402
2403   // We didn't really just "undo" but it happens to do everything we
2404   // need to update the materials and gui.
2405   onMaterialUndo( terr );
2406}
2407
2408//------------------------------------------------------------------------------
2409
2410DefineEngineMethod( TerrainEditor, attachTerrain, void, (const char * terrain), (""), "(TerrainBlock terrain)")
2411{
2412   Scene* scene = Scene::getRootScene();
2413   if (!scene)
2414   {
2415      Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::attach: no scene found");
2416      return;
2417   }
2418
2419   VectorPtr<TerrainBlock*> terrains;
2420
2421   // attach to first found terrainBlock
2422   if (String::compare (terrain,"")==0)
2423   {
2424      for(SimSetIterator itr(scene); *itr; ++itr)
2425      {
2426         TerrainBlock* terrBlock = dynamic_cast<TerrainBlock*>(*itr);
2427
2428         if (terrBlock)
2429            terrains.push_back(terrBlock);
2430      }
2431
2432      //if (terrains.size() == 0)
2433      //   Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::attach: no TerrainBlock objects found!");
2434   }
2435   else  // attach to named object
2436   {
2437      TerrainBlock* terrBlock = dynamic_cast<TerrainBlock*>(Sim::findObject(terrain));
2438
2439      if (terrBlock)
2440         terrains.push_back(terrBlock);
2441
2442      if(terrains.size() == 0)
2443         Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::attach: failed to attach to object '%s'", terrain);
2444   }
2445
2446   if (terrains.size() > 0)
2447   {
2448      for (U32 i = 0; i < terrains.size(); i++)
2449      {
2450         if (!terrains[i]->isServerObject())
2451         {
2452            terrains[i] = NULL;
2453
2454            Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::attach: cannot attach to client TerrainBlock");
2455         }
2456      }
2457   }
2458
2459   for (U32 i = 0; i < terrains.size(); i++)
2460   {
2461      if (terrains[i])
2462         object->attachTerrain(terrains[i]);
2463   }
2464}
2465
2466DefineEngineMethod( TerrainEditor, getTerrainBlockCount, S32, (), , "()")
2467{
2468   return object->getTerrainBlockCount();
2469}
2470
2471DefineEngineMethod( TerrainEditor, getTerrainBlock, S32, (S32 index), , "(S32 index)")
2472{
2473   TerrainBlock* tb = object->getTerrainBlock(index);
2474   if(!tb)
2475      return 0;
2476   else
2477      return tb->getId();
2478}
2479
2480DefineEngineMethod(TerrainEditor, getTerrainBlocksMaterialList, const char *, (), , "() gets the list of current terrain materials for all terrain blocks.")
2481{
2482   Vector<StringTableEntry> list;
2483   object->getTerrainBlocksMaterialList(list);
2484
2485   if(list.size() == 0)
2486      return "";
2487
2488   // Calculate the size of the return buffer
2489   S32 size = 0;
2490   for(U32 i = 0; i < list.size(); ++i)
2491   {
2492      size += dStrlen(list[i]);
2493      ++size;
2494   }
2495   ++size;
2496
2497   // Copy the material names
2498   char *ret = Con::getReturnBuffer(size);
2499   ret[0] = 0;
2500   for(U32 i = 0; i < list.size(); ++i)
2501   {
2502      dStrcat( ret, list[i], size );
2503      dStrcat( ret, "\n", size );
2504   }
2505
2506   return ret;
2507}
2508
2509DefineEngineMethod( TerrainEditor, setBrushType, void, (String type), , "(string type)"
2510              "One of box, ellipse, selection.")
2511{
2512   object->setBrushType(type);
2513}
2514
2515DefineEngineMethod( TerrainEditor, getBrushType, const char*, (), , "()")
2516{
2517   return object->getBrushType();
2518}
2519
2520DefineEngineMethod( TerrainEditor, setBrushSize, void, ( S32 w, S32 h), (0), "(int w [, int h])")
2521{
2522   object->setBrushSize( w, h==0?w:h );
2523}
2524
2525DefineEngineMethod( TerrainEditor, getBrushSize, const char*, (), , "()")
2526{
2527   Point2I size = object->getBrushSize();
2528
2529   static const U32 bufSize = 32;
2530   char * ret = Con::getReturnBuffer(bufSize);
2531   dSprintf(ret, bufSize, "%d %d", size.x, size.y);
2532   return ret;
2533}
2534
2535DefineEngineMethod( TerrainEditor, setBrushPressure, void, (F32 pressure), , "(float pressure)")
2536{
2537   object->setBrushPressure( pressure );
2538}
2539
2540DefineEngineMethod( TerrainEditor, getBrushPressure, F32, (), , "()")
2541{
2542   return object->getBrushPressure();
2543}
2544
2545DefineEngineMethod( TerrainEditor, setBrushSoftness, void, (F32 softness), , "(float softness)")
2546{
2547   object->setBrushSoftness( softness );
2548}
2549
2550DefineEngineMethod( TerrainEditor, getBrushSoftness, F32, (), , "()")
2551{
2552
2553   return object->getBrushSoftness();
2554}
2555
2556DefineEngineMethod( TerrainEditor, getBrushPos, const char*, (), , "Returns a Point2I.")
2557{
2558   return object->getBrushPos();
2559}
2560
2561DefineEngineMethod( TerrainEditor, setBrushPos, void, (Point2I pos), , "Location")
2562{
2563
2564   object->setBrushPos(pos);
2565}
2566
2567DefineEngineMethod( TerrainEditor, setAction, void, (const char * action_name), , "(string action_name)")
2568{
2569   object->setAction(action_name);
2570}
2571
2572DefineEngineMethod( TerrainEditor, getActionName, const char*, (U32 index), , "(int num)")
2573{
2574   return (object->getActionName(index));
2575}
2576
2577DefineEngineMethod( TerrainEditor, getNumActions, S32, (), , "")
2578{
2579   return(object->getNumActions());
2580}
2581
2582DefineEngineMethod( TerrainEditor, getCurrentAction, const char*, (), , "")
2583{
2584   return object->getCurrentAction();
2585}
2586
2587DefineEngineMethod( TerrainEditor, resetSelWeights, void, (bool clear), , "(bool clear)")
2588{
2589   object->resetSelWeights(clear);
2590}
2591
2592DefineEngineMethod( TerrainEditor, clearSelection, void, (), , "")
2593{
2594   object->clearSelection();
2595}
2596
2597DefineEngineMethod( TerrainEditor, processAction, void, (String action), (""), "(string action=NULL)")
2598{
2599   object->processAction(action);
2600}
2601
2602DefineEngineMethod( TerrainEditor, getActiveTerrain, S32, (), , "")
2603{
2604   S32 ret = 0;
2605
2606   TerrainBlock* terrain = object->getActiveTerrain();
2607
2608   if (terrain)
2609      ret = terrain->getId();
2610
2611   return ret;
2612}
2613
2614DefineEngineMethod( TerrainEditor, getNumTextures, S32, (), , "")
2615{
2616   return object->getNumTextures();
2617}
2618
2619DefineEngineMethod( TerrainEditor, markEmptySquares, void, (), , "")
2620{
2621   object->markEmptySquares();
2622}
2623
2624DefineEngineMethod( TerrainEditor, mirrorTerrain, void, (S32 mirrorIndex), , "")
2625{
2626   object->mirrorTerrain(mirrorIndex);
2627}
2628
2629DefineEngineMethod(TerrainEditor, setTerraformOverlay, void, (bool overlayEnable), , "(bool overlayEnable) - sets the terraformer current heightmap to draw as an overlay over the current terrain.")
2630{
2631   // XA: This one needs to be implemented :)
2632}
2633
2634DefineEngineMethod(TerrainEditor, updateMaterial, bool, ( U32 index, String matName ), ,
2635   "( int index, string matName )\n"
2636   "Changes the material name at the index." )
2637{
2638   TerrainBlock *terr = object->getClientTerrain();
2639   if ( !terr )
2640      return false;
2641
2642   if ( index >= terr->getMaterialCount() )
2643      return false;
2644
2645   terr->updateMaterial( index, matName );
2646
2647   object->setDirty();
2648
2649   return true;
2650}
2651
2652DefineEngineMethod(TerrainEditor, addMaterial, S32, ( String matName ), ,
2653   "( string matName )\n"
2654   "Adds a new material." )
2655{
2656   TerrainBlock *terr = object->getClientTerrain();
2657   if ( !terr )
2658      return false;
2659
2660   terr->addMaterial( matName );
2661
2662   object->setDirty();
2663
2664   return true;
2665}
2666
2667DefineEngineMethod( TerrainEditor, removeMaterial, void, ( S32 index ), , "( int index ) - Remove the material at the given index." )
2668{
2669   TerrainBlock *terr = object->getClientTerrain();
2670   if ( !terr )
2671      return;
2672
2673   if ( index < 0 || index >= terr->getMaterialCount() )
2674   {
2675      Con::errorf( "TerrainEditor::removeMaterial - index out of range!" );
2676      return;
2677   }
2678
2679   if ( terr->getMaterialCount() == 1 )
2680   {
2681      Con::errorf( "TerrainEditor::removeMaterial - cannot remove material, there is only one!" );
2682      return;
2683   }
2684
2685   const char *matName = terr->getMaterialName( index );
2686
2687   object->submitMaterialUndo( String::ToString( "Remove TerrainMaterial %s", matName ) );
2688
2689   terr->removeMaterial( index );
2690
2691   object->setDirty();
2692   object->scheduleMaterialUpdate();
2693   object->setGridUpdateMinMax();
2694}
2695
2696DefineEngineMethod(TerrainEditor, getMaterialCount, S32, (), ,
2697   "Returns the current material count." )
2698{
2699   TerrainBlock *terr = object->getClientTerrain();
2700   if ( terr )
2701      return terr->getMaterialCount();
2702
2703   return 0;
2704}
2705
2706DefineEngineMethod(TerrainEditor, getMaterials, const char *, (), , "() gets the list of current terrain materials.")
2707{
2708   TerrainBlock *terr = object->getClientTerrain();
2709   if ( !terr )
2710      return "";
2711
2712   char *ret = Con::getReturnBuffer(4096);
2713   ret[0] = 0;
2714   for(U32 i = 0; i < terr->getMaterialCount(); i++)
2715   {
2716      dStrcat( ret, terr->getMaterialName(i), 4096 );
2717      dStrcat( ret, "\n", 4096 );
2718   }
2719
2720   return ret;
2721}
2722
2723DefineEngineMethod( TerrainEditor, getMaterialName, const char*, (S32 index), , "( int index ) - Returns the name of the material at the given index." )
2724{
2725   TerrainBlock *terr = object->getClientTerrain();
2726   if ( !terr )
2727      return "";
2728
2729   if( index < 0 || index >= terr->getMaterialCount() )
2730   {
2731      Con::errorf( "TerrainEditor::getMaterialName - index out of range!" );
2732      return "";
2733   }
2734
2735   const char* name = terr->getMaterialName( index );
2736   return Con::getReturnBuffer( name );
2737}
2738
2739DefineEngineMethod( TerrainEditor, getMaterialIndex, S32, ( String name ), , "( string name ) - Returns the index of the material with the given name or -1." )
2740{
2741   TerrainBlock *terr = object->getClientTerrain();
2742   if ( !terr )
2743      return -1;
2744
2745   const U32 count = terr->getMaterialCount();
2746
2747   for( U32 i = 0; i < count; ++ i )
2748      if( dStricmp( name, terr->getMaterialName( i ) ) == 0 )
2749         return i;
2750
2751   return -1;
2752}
2753
2754DefineEngineMethod( TerrainEditor, reorderMaterial, void, ( S32 index, S32 orderPos ), , "( int index, int order ) "
2755  "- Reorder material at the given index to the new position, changing the order in which it is rendered / blended." )
2756{
2757   object->reorderMaterial( index, orderPos );
2758}
2759
2760DefineEngineMethod(TerrainEditor, getTerrainUnderWorldPoint, S32, (const char * ptOrX, const char * Y, const char * Z), ("", "", ""),
2761                                                                           "(x/y/z) Gets the terrain block that is located under the given world point.\n"
2762                                                                           "@param x/y/z The world coordinates (floating point values) you wish to query at. "
2763                                                                           "These can be formatted as either a string (\"x y z\") or separately as (x, y, z)\n"
2764                                                                           "@return Returns the ID of the requested terrain block (0 if not found).\n\n")
2765{
2766   TerrainEditor *tEditor = (TerrainEditor *) object;
2767   if(tEditor == NULL)
2768      return 0;
2769   Point3F pos;
2770   if(!String::isEmpty(ptOrX) && String::isEmpty(Y) && String::isEmpty(Z))
2771      dSscanf(ptOrX, "%f %f %f", &pos.x, &pos.y, &pos.z);
2772   else if(!String::isEmpty(ptOrX) && !String::isEmpty(Y) && !String::isEmpty(Z))
2773   {
2774      pos.x = dAtof(ptOrX);
2775      pos.y = dAtof(Y);
2776      pos.z = dAtof(Z);
2777   }
2778
2779   else
2780   {
2781      Con::errorf("TerrainEditor.getTerrainUnderWorldPoint(): Invalid argument count! Valid arguments are either \"x y z\" or x,y,z\n");
2782      return 0;
2783   }
2784
2785   TerrainBlock* terrain = tEditor->getTerrainUnderWorldPoint(pos);
2786   if(terrain != NULL)
2787   {
2788      return terrain->getId();
2789   }
2790
2791   return 0;
2792}
2793
2794//------------------------------------------------------------------------------
2795
2796void TerrainEditor::initPersistFields()
2797{
2798   addGroup("Misc");
2799   addField("isDirty", TypeBool, Offset(mIsDirty, TerrainEditor));
2800   addField("isMissionDirty", TypeBool, Offset(mIsMissionDirty, TerrainEditor));
2801   addField("renderBorder", TypeBool, Offset(mRenderBorder, TerrainEditor));                    ///< Not currently used
2802   addField("borderHeight", TypeF32, Offset(mBorderHeight, TerrainEditor));                     ///< Not currently used
2803   addField("borderFillColor", TypeColorI, Offset(mBorderFillColor, TerrainEditor));            ///< Not currently used
2804   addField("borderFrameColor", TypeColorI, Offset(mBorderFrameColor, TerrainEditor));          ///< Not currently used
2805   addField("borderLineMode", TypeBool, Offset(mBorderLineMode, TerrainEditor));                ///< Not currently used
2806   addField("selectionHidden", TypeBool, Offset(mSelectionHidden, TerrainEditor));
2807   addField("renderVertexSelection", TypeBool, Offset(mRenderVertexSelection, TerrainEditor));  ///< Not currently used
2808   addField("renderSolidBrush", TypeBool, Offset(mRenderSolidBrush, TerrainEditor));
2809   addField("processUsesBrush", TypeBool, Offset(mProcessUsesBrush, TerrainEditor));
2810   addField("maxBrushSize", TypePoint2I, Offset(mMaxBrushSize, TerrainEditor));
2811
2812   // action values...
2813   addField("adjustHeightVal", TypeF32, Offset(mAdjustHeightVal, TerrainEditor));               ///< RaiseHeightAction and LowerHeightAction
2814   addField("setHeightVal", TypeF32, Offset(mSetHeightVal, TerrainEditor));                     ///< SetHeightAction
2815   addField("scaleVal", TypeF32, Offset(mScaleVal, TerrainEditor));                             ///< ScaleHeightAction
2816   addField("smoothFactor", TypeF32, Offset(mSmoothFactor, TerrainEditor));                     ///< SmoothHeightAction
2817   addField("noiseFactor", TypeF32, Offset(mNoiseFactor, TerrainEditor));                       ///< PaintNoiseAction
2818   addField("materialGroup", TypeS32, Offset(mMaterialGroup, TerrainEditor));                   ///< Not currently used
2819   addField("softSelectRadius", TypeF32, Offset(mSoftSelectRadius, TerrainEditor));             ///< SoftSelectAction
2820   addField("softSelectFilter", TypeString, Offset(mSoftSelectFilter, TerrainEditor));          ///< SoftSelectAction brush filtering
2821   addField("softSelectDefaultFilter", TypeString, Offset(mSoftSelectDefaultFilter, TerrainEditor));  ///< SoftSelectAction brush filtering
2822   addField("adjustHeightMouseScale", TypeF32, Offset(mAdjustHeightMouseScale, TerrainEditor)); ///< Not currently used
2823   addField("paintIndex", TypeS32, Offset(mPaintIndex, TerrainEditor));                         ///< PaintMaterialAction
2824   endGroup("Misc");
2825
2826   Parent::initPersistFields();
2827}
2828
2829DefineEngineMethod( TerrainEditor, getSlopeLimitMinAngle, F32, (), , "")
2830{
2831   return object->mSlopeMinAngle;
2832}
2833
2834DefineEngineMethod( TerrainEditor, setSlopeLimitMinAngle, F32, (F32 angle), , "")
2835{
2836   if ( angle < 0.0f )
2837      angle = 0.0f;
2838   if ( angle > object->mSlopeMaxAngle )
2839      angle = object->mSlopeMaxAngle;
2840
2841   object->mSlopeMinAngle = angle;
2842   return angle;
2843}
2844
2845DefineEngineMethod( TerrainEditor, getSlopeLimitMaxAngle, F32, (), , "")
2846{
2847   return object->mSlopeMaxAngle;
2848}
2849
2850DefineEngineMethod( TerrainEditor, setSlopeLimitMaxAngle, F32, (F32 angle), , "")
2851{
2852   if ( angle > 90.0f )
2853      angle = 90.0f;
2854   if ( angle < object->mSlopeMinAngle )
2855      angle = object->mSlopeMinAngle;
2856
2857   object->mSlopeMaxAngle = angle;
2858   return angle;
2859}
2860
2861//------------------------------------------------------------------------------
2862void TerrainEditor::autoMaterialLayer( F32 mMinHeight, F32 mMaxHeight, F32 mMinSlope, F32 mMaxSlope, F32 mCoverage )
2863{
2864
2865#define AUTOPAINT_UNDO
2866
2867   if (!mActiveTerrain)
2868      return;
2869
2870   S32 mat = getPaintMaterialIndex();
2871   if (mat == -1)
2872      return;
2873
2874
2875     #ifndef AUTOPAINT_UNDO
2876     mUndoSel = new Selection;
2877     #endif
2878
2879
2880   U32 terrBlocks = mActiveTerrain->getBlockSize();
2881   for (U32 y = 0; y < terrBlocks; y++)
2882   {
2883      for (U32 x = 0; x < terrBlocks; x++)
2884      {
2885         // get info
2886         GridPoint gp;
2887         gp.terrainBlock = mActiveTerrain;
2888         gp.gridPos.set(x, y);
2889
2890         GridInfo gi;
2891         getGridInfo(gp, gi);
2892
2893         if (gi.mMaterial == mat)
2894            continue;
2895
2896         if (mRandI(0, 100) > mCoverage)
2897            continue;
2898
2899         Point3F wp;
2900         gridToWorld(gp, wp);
2901
2902         if (!(wp.z >= mMinHeight && wp.z <= mMaxHeight))
2903            continue;
2904
2905         // transform wp to object space
2906         Point3F op;
2907         mActiveTerrain->getWorldTransform().mulP(wp, &op);
2908
2909         Point3F norm;
2910         mActiveTerrain->getNormal(Point2F(op.x, op.y), &norm, true);
2911
2912         if (mMinSlope > 0)
2913            if (norm.z > mSin(mDegToRad(90.0f - mMinSlope)))
2914               continue;
2915
2916         if (mMaxSlope < 90)
2917            if (norm.z < mSin(mDegToRad(90.0f - mMaxSlope)))
2918               continue;
2919
2920         gi.mMaterialChanged = true;
2921         #ifndef AUTOPAINT_UNDO
2922         mUndoSel->add(gi);
2923         #endif
2924         gi.mMaterial = mat;
2925         setGridInfo(gi);
2926      }
2927   }
2928
2929   #ifndef AUTOPAINT_UNDO
2930   if(mUndoSel->size())
2931      submitUndo( mUndoSel );
2932   else
2933      delete mUndoSel;
2934   mUndoSel = 0;
2935   #endif
2936
2937
2938   scheduleMaterialUpdate();
2939}
2940
2941DefineEngineMethod( TerrainEditor, autoMaterialLayer, void, (F32 minHeight, F32 maxHeight, F32 minSlope, F32 maxSlope, F32 coverage),,
2942   "Rule based terrain painting.\n"
2943   "@param minHeight Minimum terrain height."
2944   "@param maxHeight Maximum terrain height."
2945   "@param minSlope Minimum terrain slope."
2946   "@param maxSlope Maximum terrain slope."
2947   "@param coverage Terrain coverage amount.")
2948{
2949   object->autoMaterialLayer( minHeight,maxHeight, minSlope, maxSlope, coverage );
2950}
2951