Torque3D Documentation / _generateds / terrainEditor.cpp

terrainEditor.cpp

Engine/source/gui/worldEditor/terrainEditor.cpp

More...

Classes:

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" )
DefineConsoleMethod(TerrainEditor , addMaterial , S32 , (String matName) , "( string matName )\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "Adds a <a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> material." )
DefineConsoleMethod(TerrainEditor , attachTerrain , void , (const char *terrain) , ("") , "(TerrainBlock terrain)" )
DefineConsoleMethod(TerrainEditor , clearSelection , void , () , "" )
DefineConsoleMethod(TerrainEditor , getActionName , const char * , (U32 index) , "(int num)" )
DefineConsoleMethod(TerrainEditor , getActiveTerrain , S32 , () , "" )
DefineConsoleMethod(TerrainEditor , getBrushPos , const char * , () , "Returns a Point2I." )
DefineConsoleMethod(TerrainEditor , getBrushPressure , F32 , () , "()" )
DefineConsoleMethod(TerrainEditor , getBrushSize , const char * , () , "()" )
DefineConsoleMethod(TerrainEditor , getBrushSoftness , F32 , () , "()" )
DefineConsoleMethod(TerrainEditor , getBrushType , const char * , () , "()" )
DefineConsoleMethod(TerrainEditor , getCurrentAction , const char * , () , "" )
DefineConsoleMethod(TerrainEditor , getMaterialCount , S32 , () , "Returns the current material count." )
DefineConsoleMethod(TerrainEditor , getMaterialIndex , S32 , (String name) , "( string name ) - Returns the index of the material with the given name or -1." )
DefineConsoleMethod(TerrainEditor , getMaterialName , const char * , (S32 index) , "( int index ) - Returns the name of the material at the given index." )
DefineConsoleMethod(TerrainEditor , getMaterials , const char * , () , "() gets the list of current terrain materials." )
DefineConsoleMethod(TerrainEditor , getNumActions , S32 , () , "" )
DefineConsoleMethod(TerrainEditor , getNumTextures , S32 , () , "" )
DefineConsoleMethod(TerrainEditor , getSlopeLimitMaxAngle , F32 , () , "" )
DefineConsoleMethod(TerrainEditor , getSlopeLimitMinAngle , F32 , () , "" )
DefineConsoleMethod(TerrainEditor , getTerrainBlock , S32 , (S32 index) , "(S32 index)" )
DefineConsoleMethod(TerrainEditor , getTerrainBlockCount , S32 , () , "()" )
DefineConsoleMethod(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." )
DefineConsoleMethod(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 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_1a2594a51175f310ed96ad6cd7d6514878">if</a> not found).\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" )
DefineConsoleMethod(TerrainEditor , markEmptySquares , void , () , "" )
DefineConsoleMethod(TerrainEditor , mirrorTerrain , void , (S32 mirrorIndex) , "" )
DefineConsoleMethod(TerrainEditor , processAction , void , (String action) , ("") , "(string action=<a href="/coding/file/typesx86unix_8h/#typesx86unix_8h_1a070d2ce7b6bb7e5c05602aa8c308d0c4">NULL</a>)" )
DefineConsoleMethod(TerrainEditor , removeMaterial , void , (S32 index) , "( int index ) - Remove the material at the given index." )
DefineConsoleMethod(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." )
DefineConsoleMethod(TerrainEditor , resetSelWeights , void , (bool clear) , "(bool clear)" )
DefineConsoleMethod(TerrainEditor , setAction , void , (const char *action_name) , "(string action_name)" )
DefineConsoleMethod(TerrainEditor , setBrushPos , void , (Point2I pos) , "Location" )
DefineConsoleMethod(TerrainEditor , setBrushPressure , void , (F32 pressure) , "(float pressure)" )
DefineConsoleMethod(TerrainEditor , setBrushSize , void , (S32 w, S32 h) , (0) , "(int w [, int h])" )
DefineConsoleMethod(TerrainEditor , setBrushSoftness , void , (F32 softness) , "(float softness)" )
DefineConsoleMethod(TerrainEditor , setBrushType , void , (String type) , "(string type)" "One of box, ellipse , selection." )
DefineConsoleMethod(TerrainEditor , setSlopeLimitMaxAngle , F32 , (F32 angle) , "" )
DefineConsoleMethod(TerrainEditor , setSlopeLimitMinAngle , F32 , (F32 angle) , "" )
DefineConsoleMethod(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." )
DefineConsoleMethod(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." )
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." )

Detailed Description

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" )

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

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

DefineConsoleMethod(TerrainEditor , clearSelection , void , () , "" )

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

DefineConsoleMethod(TerrainEditor , getActiveTerrain , S32 , () , "" )

DefineConsoleMethod(TerrainEditor , getBrushPos , const char * , () , "Returns a Point2I." )

DefineConsoleMethod(TerrainEditor , getBrushPressure , F32 , () , "()" )

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

DefineConsoleMethod(TerrainEditor , getBrushSoftness , F32 , () , "()" )

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

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

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

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

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

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

DefineConsoleMethod(TerrainEditor , getNumActions , S32 , () , "" )

DefineConsoleMethod(TerrainEditor , getNumTextures , S32 , () , "" )

DefineConsoleMethod(TerrainEditor , getSlopeLimitMaxAngle , F32 , () , "" )

DefineConsoleMethod(TerrainEditor , getSlopeLimitMinAngle , F32 , () , "" )

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

DefineConsoleMethod(TerrainEditor , getTerrainBlockCount , S32 , () , "()" )

DefineConsoleMethod(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." )

DefineConsoleMethod(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 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_1a2594a51175f310ed96ad6cd7d6514878">if</a> not found).\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" )

DefineConsoleMethod(TerrainEditor , markEmptySquares , void , () , "" )

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

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

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

DefineConsoleMethod(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." )

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

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

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

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

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

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

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

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

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

DefineConsoleMethod(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." )

DefineConsoleMethod(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." )

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." )

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
  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 ColorF & inColorFull, const ColorF & inColorNone, const ColorF & outColorFull, const ColorF & 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   mUndoSel(0),
 673   mGridUpdateMin( S32_MAX, S32_MAX ),
 674   mGridUpdateMax( 0, 0 ),
 675   mMaxBrushSize(256,256),
 676   mNeedsGridUpdate( false ),
 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 (!dStrcmp(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 && !dStrcmp(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, ColorF::RED, ColorF::GREEN, ColorF::BLUE, ColorF::BLUE, true, false);
1301
1302   if(mRenderBrush && mMouseBrush->size())
1303      renderBrush(*mMouseBrush, ColorF::GREEN, ColorF::RED, ColorF::BLUE, ColorF::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)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 ColorF & inColorFull, const ColorF & inColorNone, const ColorF & outColorFull, const ColorF & 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   ColorF 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;
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;
1484         }
1485         else
1486         {
1487            if ( center )
1488            {
1489               iColor = inColorNone;
1490            }
1491            else
1492            {
1493               iColor = outColorFull;
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 ColorF & inColorFull, const ColorF & inColorNone, const ColorF & outColorFull, const ColorF & 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 (!dStrcmp(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) && !dStrcmp(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   }
1846   else
1847   {
1848      // We do not change the active terrain as the mouse moves when
1849      // in painting mode.  This is because it causes the material 
1850      // window to change as you cursor over to it.
1851      if ( dStrcmp(getCurrentAction(),"paintMaterial") != 0 )
1852      {
1853         // Set the active terrain
1854         bool changed = mActiveTerrain != hitTerrain;
1855         mActiveTerrain = hitTerrain;
1856
1857         if (changed)
1858            Con::executef(this, "onActiveTerrainChange", Con::getIntArg(hitTerrain->getId()));
1859      }
1860
1861      mMousePos = pos;
1862
1863      mMouseBrush->setTerrain(mActiveTerrain);
1864      mMouseBrush->setPosition(mMousePos);
1865   }  
1866}
1867
1868void TerrainEditor::on3DMouseDragged(const Gui3DMouseEvent & event)
1869{
1870   PROFILE_SCOPE( TerrainEditor_On3DMouseDragged );
1871
1872   if ( mTerrainBlocks.empty() )
1873      return;
1874
1875   if ( !isMouseLocked() )
1876      return;
1877    
1878   Point3F pos;
1879
1880   if ( !mSelectionLocked )
1881   {
1882      if ( !collide( event, pos)  )
1883         mMouseBrush->reset();
1884   }
1885   
1886   // check if the mouse has actually moved in grid space
1887   bool selChanged = false;
1888   if ( !mSelectionLocked )
1889   {
1890      Point2I gMouse;
1891      Point2I gLastMouse;
1892      worldToGrid( pos, gMouse );
1893      worldToGrid( mMousePos, gLastMouse );
1894
1895      mMousePos = pos;
1896      mMouseBrush->setPosition( mMousePos );
1897
1898      selChanged = gMouse != gLastMouse;
1899   }
1900
1901   mCurrentAction->process( mMouseBrush, event, true, TerrainAction::Update );
1902}
1903
1904void TerrainEditor::on3DMouseUp(const Gui3DMouseEvent & event)
1905{
1906   mMouseDown = false;
1907   getRoot()->showCursor( true );
1908
1909   if ( mTerrainBlocks.size() == 0 )
1910      return;   
1911
1912   if ( isMouseLocked() )
1913   {
1914      mouseUnlock();
1915      mMouseDownSeq++;
1916      mCurrentAction->process( mMouseBrush, event, false, TerrainAction::End );
1917
1918      if ( mUndoSel->size() )
1919         submitUndo( mUndoSel );
1920      else
1921         delete mUndoSel;
1922
1923      mUndoSel = 0;
1924      mInAction = false;
1925   }
1926}
1927
1928bool TerrainEditor::onMouseWheelDown( const GuiEvent & event )
1929{
1930   if ( event.modifier & SI_PRIMARY_CTRL && event.modifier & SI_SHIFT )
1931   {
1932      setBrushPressure( mBrushPressure - 0.1f );
1933      return true;
1934   }
1935   else if ( event.modifier & SI_SHIFT )
1936   {
1937      setBrushSoftness( mBrushSoftness + 0.05f );
1938      return true;
1939   }
1940   else if ( event.modifier & SI_PRIMARY_CTRL )
1941   {
1942      Point2I newBrush = getBrushSize() - Point2I(1,1);  
1943      setBrushSize( newBrush.x, newBrush.y );
1944      return true;
1945   }
1946      
1947   return Parent::onMouseWheelDown( event );
1948}
1949
1950bool TerrainEditor::onMouseWheelUp( const GuiEvent & event )
1951{
1952   if ( event.modifier & SI_PRIMARY_CTRL && event.modifier & SI_SHIFT )
1953   {
1954      setBrushPressure( mBrushPressure + 0.1f );
1955      return true;
1956   }
1957   else if ( event.modifier & SI_SHIFT )
1958   {
1959      setBrushSoftness( mBrushSoftness - 0.05f );
1960      return true;
1961   }
1962   else if( event.modifier & SI_PRIMARY_CTRL )
1963   {
1964      Point2I newBrush = getBrushSize() + Point2I(1,1);
1965      setBrushSize( newBrush.x, newBrush.y );
1966      return true;
1967   }
1968   
1969   return Parent::onMouseWheelUp( event );
1970}
1971
1972//------------------------------------------------------------------------------
1973// any console function which depends on a terrainBlock attached to the editor
1974// should call this
1975bool checkTerrainBlock(TerrainEditor * object, const char * funcName)
1976{
1977   if(!object->terrainBlockValid())
1978   {
1979      Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::%s: not attached to a terrain block!", funcName);
1980      return(false);
1981   }
1982   return(true);
1983}
1984
1985void TerrainEditor::attachTerrain(TerrainBlock *terrBlock)
1986{
1987   mActiveTerrain = terrBlock;
1988   mTerrainBlocks.push_back_unique(terrBlock);
1989}
1990
1991void TerrainEditor::detachTerrain(TerrainBlock *terrBlock)
1992{
1993   if (mActiveTerrain == terrBlock)
1994      mActiveTerrain = NULL; //do we want to set this to an existing terrain?
1995
1996   if (mMouseBrush->getGridPoint().terrainBlock == terrBlock)
1997      mMouseBrush->setTerrain(NULL);
1998
1999   // reset the brush as its gridinfos may still have references to the old terrain
2000   mMouseBrush->reset();
2001
2002   mTerrainBlocks.remove(terrBlock);
2003}
2004
2005TerrainBlock* TerrainEditor::getTerrainBlock(S32 index)
2006{
2007   if(index < 0 || index >= mTerrainBlocks.size())
2008      return NULL;
2009
2010   return mTerrainBlocks[index];
2011}
2012
2013void TerrainEditor::getTerrainBlocksMaterialList(Vector<StringTableEntry>& list)
2014{
2015   for(S32 i=0; i<mTerrainBlocks.size(); ++i)
2016   {
2017      TerrainBlock* tb = mTerrainBlocks[i];
2018      if(!tb)
2019         continue;
2020
2021      for(S32 m=0; m<tb->getMaterialCount(); ++m)
2022      {
2023         TerrainMaterial* mat = tb->getMaterial(m);
2024         if (mat)
2025            list.push_back_unique(mat->getInternalName());
2026      }
2027   }
2028}
2029
2030void TerrainEditor::setBrushType( const char *type )
2031{
2032   if ( mMouseBrush && dStrcmp( mMouseBrush->getType(), type ) == 0 )
2033      return;
2034
2035   if(!dStricmp(type, "box"))
2036   {
2037      delete mMouseBrush;
2038      mMouseBrush = new BoxBrush(this);
2039      mBrushChanged = true;
2040   }
2041   else if(!dStricmp(type, "ellipse"))
2042   {
2043      delete mMouseBrush;
2044      mMouseBrush = new EllipseBrush(this);
2045      mBrushChanged = true;
2046   }
2047   else if(!dStricmp(type, "selection"))
2048   {
2049      delete mMouseBrush;
2050      mMouseBrush = new SelectionBrush(this);
2051      mBrushChanged = true;
2052   }
2053   else {}   
2054}
2055
2056const char* TerrainEditor::getBrushType() const
2057{
2058   if ( mMouseBrush )
2059      return mMouseBrush->getType();
2060
2061   return "";
2062}
2063
2064void TerrainEditor::setBrushSize( S32 w, S32 h )
2065{
2066   w = mClamp( w, 1, mMaxBrushSize.x );
2067   h = mClamp( h, 1, mMaxBrushSize.y );
2068
2069   if ( w == mBrushSize.x && h == mBrushSize.y )
2070      return;
2071
2072   mBrushSize.set( w, h );
2073   mBrushChanged = true;
2074
2075   if ( mMouseBrush )
2076   {
2077      mMouseBrush->setSize( mBrushSize );
2078
2079      if ( mMouseBrush->getGridPoint().terrainBlock )
2080         mMouseBrush->rebuild();
2081   }
2082}
2083
2084void TerrainEditor::setBrushPressure( F32 pressure )
2085{
2086   pressure = mClampF( pressure, 0.01f, 1.0f );
2087   
2088   if ( mBrushPressure == pressure )
2089      return;
2090
2091   mBrushPressure = pressure;
2092   mBrushChanged = true;
2093
2094   if ( mMouseBrush && mMouseBrush->getGridPoint().terrainBlock )
2095      mMouseBrush->rebuild();
2096}
2097
2098void TerrainEditor::setBrushSoftness( F32 softness )
2099{
2100   softness = mClampF( softness, 0.01f, 1.0f );
2101
2102   if ( mBrushSoftness == softness )
2103      return;
2104
2105   mBrushSoftness = softness;
2106   mBrushChanged = true;
2107
2108   if ( mMouseBrush && mMouseBrush->getGridPoint().terrainBlock )
2109      mMouseBrush->rebuild();
2110}
2111
2112const char* TerrainEditor::getBrushPos()
2113{
2114   AssertFatal(mMouseBrush!=<a href="/coding/file/types_8lint_8h/#types_8lint_8h_1a070d2ce7b6bb7e5c05602aa8c308d0c4">NULL</a>, "TerrainEditor::getBrushPos: no mouse brush!");
2115
2116   Point2I pos = mMouseBrush->getPosition();
2117   static const U32 bufSize = 32;
2118   char * ret = Con::getReturnBuffer(bufSize);
2119   dSprintf(ret, bufSize, "%d %d", pos.x, pos.y);
2120   return(ret);
2121}
2122
2123void TerrainEditor::setBrushPos(Point2I pos)
2124{
2125   AssertFatal(mMouseBrush!=<a href="/coding/file/types_8lint_8h/#types_8lint_8h_1a070d2ce7b6bb7e5c05602aa8c308d0c4">NULL</a>, "TerrainEditor::setBrushPos: no mouse brush!");
2126   mMouseBrush->setPosition(pos);
2127}
2128
2129void TerrainEditor::setAction(const char* action)
2130{
2131   for(U32 i = 0; i < mActions.size(); i++)
2132   {
2133      if(!dStricmp(mActions[i]->getName(), action))
2134      {
2135         mCurrentAction = mActions[i];
2136
2137         //
2138         mRenderBrush = mCurrentAction->useMouseBrush();
2139         return;
2140      }
2141   }
2142}
2143
2144const char* TerrainEditor::getActionName(U32 index)
2145{
2146   if(index >= mActions.size())
2147      return("");
2148   return(mActions[index]->getName());
2149}
2150
2151const char* TerrainEditor::getCurrentAction() const
2152{
2153   return(mCurrentAction->getName());
2154}
2155
2156S32 TerrainEditor::getNumActions()
2157{
2158   return(mActions.size());
2159}
2160
2161void TerrainEditor::resetSelWeights(bool clear)
2162{
2163   //
2164   if(!clear)
2165   {
2166      for(U32 i = 0; i < mDefaultSel.size(); i++)
2167      {
2168         mDefaultSel[i].mPrimarySelect = false;
2169         mDefaultSel[i].mWeight = 1.f;
2170      }
2171      return;
2172   }
2173
2174   Selection sel;
2175
2176   U32 i;
2177   for(i = 0; i < mDefaultSel.size(); i++)
2178   {
2179      if(mDefaultSel[i].mPrimarySelect)
2180      {
2181         mDefaultSel[i].mWeight = 1.f;
2182         sel.add(mDefaultSel[i]);
2183      }
2184   }
2185
2186   mDefaultSel.reset();
2187
2188   for(i = 0; i < sel.size(); i++)
2189      mDefaultSel.add(sel[i]);
2190}
2191
2192void TerrainEditor::clearSelection()
2193{
2194   mDefaultSel.reset();
2195}
2196
2197void TerrainEditor::processAction(const char* sAction)
2198{
2199   if(!checkTerrainBlock(this, "processAction"))
2200      return;
2201
2202   TerrainAction * action = mCurrentAction;
2203   if (dStrcmp(sAction, "") != 0)
2204   {
2205      action = lookupAction(sAction);
2206
2207      if(!action)
2208      {
2209         Con::errorf(ConsoleLogEntry::General, "TerrainEditor::cProcessAction: invalid action name '%s'.", sAction);
2210         return;
2211      }
2212   }
2213
2214   if(!getCurrentSel()->size() && !mProcessUsesBrush)
2215      return;
2216
2217   mUndoSel = new Selection;
2218
2219   Gui3DMouseEvent event;
2220   if(mProcessUsesBrush)
2221      action->process(mMouseBrush, event, true, TerrainAction::Process);
2222   else
2223      action->process(getCurrentSel(), event, true, TerrainAction::Process);
2224
2225   // check if should delete the undo
2226   if(mUndoSel->size())
2227      submitUndo( mUndoSel );
2228   else
2229      delete mUndoSel;
2230
2231   mUndoSel = 0;
2232}
2233
2234S32 TerrainEditor::getNumTextures()
2235{
2236   if(!checkTerrainBlock(this, "getNumTextures"))
2237      return(0);
2238
2239   // walk all the possible material lists and count them..
2240   U32 count = 0;
2241   for (U32 t = 0; t < mTerrainBlocks.size(); t++)
2242      count += mTerrainBlocks[t]->getMaterialCount();
2243
2244   return count;
2245}
2246
2247void TerrainEditor::markEmptySquares()
2248{
2249   if(!checkTerrainBlock(this, "markEmptySquares"))
2250      return;
2251}
2252
2253void TerrainEditor::mirrorTerrain(S32 mirrorIndex)
2254{
2255   if(!checkTerrainBlock(this, "mirrorTerrain"))
2256      return;
2257
2258   // TODO!
2259   /*
2260   TerrainBlock * terrain = mActiveTerrain;
2261   setDirty();
2262
2263   //
2264   enum {
2265      top = BIT(0),
2266      bottom = BIT(1),
2267      left = BIT(2),
2268      right = BIT(3)
2269   };
2270
2271   U32 sides[8] =
2272   {
2273      bottom,
2274      bottom | left,
2275      left,
2276      left | top,
2277      top,
2278      top | right,
2279      right,
2280      bottom | right
2281   };
2282
2283   U32 n = TerrainBlock::BlockSize;
2284   U32 side = sides[mirrorIndex % 8];
2285   bool diag = mirrorIndex & 0x01;
2286
2287   Point2I src((side & right) ? (n - 1) : 0, (side & bottom) ? (n - 1) : 0);
2288   Point2I dest((side & left) ? (n - 1) : 0, (side & top) ? (n - 1) : 0);
2289   Point2I origSrc(src);
2290   Point2I origDest(dest);
2291
2292   // determine the run length
2293   U32 minStride = ((side & top) || (side & bottom)) ? n : n / 2;
2294   U32 majStride = ((side & left) || (side & right)) ? n : n / 2;
2295
2296   Point2I srcStep((side & right) ? -1 : 1, (side & bottom) ? -1 : 1);
2297   Point2I destStep((side & left) ? -1 : 1, (side & top) ? -1 : 1);
2298
2299   //
2300   U16 * heights = terrain->getHeightAddress(0,0);
2301   U8 * baseMaterials = terrain->getBaseMaterialAddress(0,0);
2302   TerrainBlock::Material * materials = terrain->getMaterial(0,0);
2303
2304   // create an undo selection
2305   Selection * undo = new Selection;
2306
2307   // walk through all the positions
2308   for(U32 i = 0; i < majStride; i++)
2309   {
2310      for(U32 j = 0; j < minStride; j++)
2311      {
2312         // skip the same position
2313         if(src != dest)
2314         {
2315            U32 si = src.x + (src.y << TerrainBlock::BlockShift);
2316            U32 di = dest.x + (dest.y << TerrainBlock::BlockShift);
2317
2318            // add to undo selection
2319            GridInfo info;
2320            getGridInfo(dest, info, terrain);
2321            undo->add(info);
2322
2323            //... copy info... (height, basematerial, material)
2324            heights[di] = heights[si];
2325            baseMaterials[di] = baseMaterials[si];
2326            materials[di] = materials[si];
2327         }
2328
2329         // get to the new position
2330         src.x += srcStep.x;
2331         diag ? (dest.y += destStep.y) : (dest.x += destStep.x);
2332      }
2333
2334      // get the next position for a run
2335      src.y += srcStep.y;
2336      diag ? (dest.x += destStep.x) : (dest.y += destStep.y);
2337
2338      // reset the minor run
2339      src.x = origSrc.x;
2340      diag ? (dest.y = origDest.y) : (dest.x = origDest.x);
2341
2342      // shorten the run length for diag runs
2343      if(diag)
2344         minStride--;
2345   }
2346
2347   // rebuild stuff..
2348   terrain->buildGridMap();
2349   terrain->rebuildEmptyFlags();
2350   terrain->packEmptySquares();
2351
2352   // add undo selection
2353   submitUndo( undo );
2354   */
2355}
2356
2357bool TerrainEditor::isPointInTerrain( const GridPoint & gPoint)
2358{
2359   PROFILE_SCOPE( TerrainEditor_IsPointInTerrain );
2360
2361   Point2I cPos;
2362   gridToCenter( gPoint.gridPos, cPos );
2363   const TerrainFile *file = gPoint.terrainBlock->getFile();
2364   return file->isPointInTerrain( cPos.x, cPos.y );
2365}
2366
2367void TerrainEditor::reorderMaterial( S32 index, S32 orderPos )
2368{   
2369   TerrainBlock *terr = getClientTerrain();
2370   Vector<U8> layerMap = terr->getLayerMap();
2371   Vector<TerrainMaterial*> materials = terr->getMaterials();
2372
2373   TerrainMaterial *pMat = materials[index];
2374
2375   submitMaterialUndo( String::ToString( "Reordered %s Material", terr->getMaterialName(index) ) );
2376
2377   materials.erase( index );
2378   materials.insert( orderPos, pMat );
2379
2380   Vector<U8>::iterator itr = layerMap.begin();
2381   for ( ; itr != layerMap.end(); itr++ )
2382   {
2383      // Was previous material, set to new index.
2384      if ( *itr == index )
2385         *itr = orderPos;
2386      else 
2387      {
2388         // We removed a Material prior to this one, bump it down.
2389         if ( *itr > index )
2390            (*itr)--;
2391         // We added a Material prior to this one, bump it up.
2392         if ( *itr >= orderPos )         
2393            (*itr)++;
2394      }
2395   }
2396
2397   terr->setMaterials( materials );
2398   terr->setLayerMap( layerMap );   
2399
2400   // We didn't really just "undo" but it happens to do everything we
2401   // need to update the materials and gui.
2402   onMaterialUndo( terr );
2403}
2404
2405//------------------------------------------------------------------------------
2406
2407DefineConsoleMethod( TerrainEditor, attachTerrain, void, (const char * terrain), (""), "(TerrainBlock terrain)")
2408{
2409   SimSet * missionGroup = dynamic_cast<SimSet*>(Sim::findObject("MissionGroup"));
2410   if (!missionGroup)
2411   {
2412      Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::attach: no mission group found");
2413      return;
2414   }
2415
2416   VectorPtr<TerrainBlock*> terrains;
2417
2418   // attach to first found terrainBlock
2419   if (dStrcmp (terrain,"")==0)
2420   {
2421      for(SimSetIterator itr(missionGroup); *itr; ++itr)
2422      {
2423         TerrainBlock* terrBlock = dynamic_cast<TerrainBlock*>(*itr);
2424
2425         if (terrBlock)
2426            terrains.push_back(terrBlock);
2427      }
2428
2429      //if (terrains.size() == 0)
2430      //   Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::attach: no TerrainBlock objects found!");
2431   }
2432   else  // attach to named object
2433   {
2434      TerrainBlock* terrBlock = dynamic_cast<TerrainBlock*>(Sim::findObject(terrain));
2435
2436      if (terrBlock)
2437         terrains.push_back(terrBlock);
2438
2439      if(terrains.size() == 0)
2440         Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::attach: failed to attach to object '%s'", terrain);
2441   }
2442
2443   if (terrains.size() > 0)
2444   {
2445      for (U32 i = 0; i < terrains.size(); i++)
2446      {
2447         if (!terrains[i]->isServerObject())
2448         {
2449            terrains[i] = NULL;
2450
2451            Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::attach: cannot attach to client TerrainBlock");
2452         }
2453      }
2454   }
2455
2456   for (U32 i = 0; i < terrains.size(); i++)
2457   {
2458      if (terrains[i])
2459         object->attachTerrain(terrains[i]);
2460   }
2461}
2462
2463DefineConsoleMethod( TerrainEditor, getTerrainBlockCount, S32, (), , "()")
2464{
2465   return object->getTerrainBlockCount();
2466}
2467
2468DefineConsoleMethod( TerrainEditor, getTerrainBlock, S32, (S32 index), , "(S32 index)")
2469{
2470   TerrainBlock* tb = object->getTerrainBlock(index);
2471   if(!tb)
2472      return 0;
2473   else
2474      return tb->getId();
2475}
2476
2477DefineConsoleMethod(TerrainEditor, getTerrainBlocksMaterialList, const char *, (), , "() gets the list of current terrain materials for all terrain blocks.")
2478{
2479   Vector<StringTableEntry> list;
2480   object->getTerrainBlocksMaterialList(list);
2481
2482   if(list.size() == 0)
2483      return "";
2484
2485   // Calculate the size of the return buffer
2486   S32 size = 0;
2487   for(U32 i = 0; i < list.size(); ++i)
2488   {
2489      size += dStrlen(list[i]);
2490      ++size;
2491   }
2492   ++size;
2493
2494   // Copy the material names
2495   char *ret = Con::getReturnBuffer(size);
2496   ret[0] = 0;
2497   for(U32 i = 0; i < list.size(); ++i)
2498   {
2499      dStrcat( ret, list[i] );
2500      dStrcat( ret, "\n" );
2501   }
2502
2503   return ret;
2504}
2505
2506DefineConsoleMethod( TerrainEditor, setBrushType, void, (String type), , "(string type)"
2507              "One of box, ellipse, selection.")
2508{
2509   object->setBrushType(type);
2510}
2511
2512DefineConsoleMethod( TerrainEditor, getBrushType, const char*, (), , "()")
2513{
2514   return object->getBrushType();
2515}
2516
2517DefineConsoleMethod( TerrainEditor, setBrushSize, void, ( S32 w, S32 h), (0), "(int w [, int h])")
2518{
2519   object->setBrushSize( w, h==0?w:h );
2520}
2521
2522DefineConsoleMethod( TerrainEditor, getBrushSize, const char*, (), , "()")
2523{
2524   Point2I size = object->getBrushSize();
2525
2526   static const U32 bufSize = 32;
2527   char * ret = Con::getReturnBuffer(bufSize);
2528   dSprintf(ret, bufSize, "%d %d", size.x, size.y);
2529   return ret;
2530}
2531
2532DefineConsoleMethod( TerrainEditor, setBrushPressure, void, (F32 pressure), , "(float pressure)")
2533{
2534   object->setBrushPressure( pressure );
2535}
2536
2537DefineConsoleMethod( TerrainEditor, getBrushPressure, F32, (), , "()")
2538{
2539   return object->getBrushPressure();
2540}
2541
2542DefineConsoleMethod( TerrainEditor, setBrushSoftness, void, (F32 softness), , "(float softness)")
2543{
2544   object->setBrushSoftness( softness );
2545}
2546
2547DefineConsoleMethod( TerrainEditor, getBrushSoftness, F32, (), , "()")
2548{
2549   
2550   return object->getBrushSoftness();
2551}
2552
2553DefineConsoleMethod( TerrainEditor, getBrushPos, const char*, (), , "Returns a Point2I.")
2554{
2555   return object->getBrushPos();
2556}
2557
2558DefineConsoleMethod( TerrainEditor, setBrushPos, void, (Point2I pos), , "Location")
2559{
2560
2561   object->setBrushPos(pos);
2562}
2563
2564DefineConsoleMethod( TerrainEditor, setAction, void, (const char * action_name), , "(string action_name)")
2565{
2566   object->setAction(action_name);
2567}
2568
2569DefineConsoleMethod( TerrainEditor, getActionName, const char*, (U32 index), , "(int num)")
2570{
2571   return (object->getActionName(index));
2572}
2573
2574DefineConsoleMethod( TerrainEditor, getNumActions, S32, (), , "")
2575{
2576   return(object->getNumActions());
2577}
2578
2579DefineConsoleMethod( TerrainEditor, getCurrentAction, const char*, (), , "")
2580{
2581   return object->getCurrentAction();
2582}
2583
2584DefineConsoleMethod( TerrainEditor, resetSelWeights, void, (bool clear), , "(bool clear)")
2585{
2586   object->resetSelWeights(clear);
2587}
2588
2589DefineConsoleMethod( TerrainEditor, clearSelection, void, (), , "")
2590{
2591   object->clearSelection();
2592}
2593
2594DefineConsoleMethod( TerrainEditor, processAction, void, (String action), (""), "(string action=NULL)")
2595{
2596   object->processAction(action);
2597}
2598
2599DefineConsoleMethod( TerrainEditor, getActiveTerrain, S32, (), , "")
2600{
2601   S32 ret = 0;
2602
2603   TerrainBlock* terrain = object->getActiveTerrain();
2604
2605   if (terrain)
2606      ret = terrain->getId();
2607
2608   return ret;
2609}
2610
2611DefineConsoleMethod( TerrainEditor, getNumTextures, S32, (), , "")
2612{
2613   return object->getNumTextures();
2614}
2615
2616DefineConsoleMethod( TerrainEditor, markEmptySquares, void, (), , "")
2617{
2618   object->markEmptySquares();
2619}
2620
2621DefineConsoleMethod( TerrainEditor, mirrorTerrain, void, (S32 mirrorIndex), , "")
2622{
2623   object->mirrorTerrain(mirrorIndex);
2624}
2625
2626DefineConsoleMethod(TerrainEditor, setTerraformOverlay, void, (bool overlayEnable), , "(bool overlayEnable) - sets the terraformer current heightmap to draw as an overlay over the current terrain.")
2627{
2628   // XA: This one needs to be implemented :)
2629}
2630
2631DefineConsoleMethod(TerrainEditor, updateMaterial, bool, ( U32 index, String matName ), , 
2632   "( int index, string matName )\n"
2633   "Changes the material name at the index." )
2634{
2635   TerrainBlock *terr = object->getClientTerrain();
2636   if ( !terr )
2637      return false;
2638   
2639   if ( index >= terr->getMaterialCount() )
2640      return false;
2641
2642   terr->updateMaterial( index, matName );
2643
2644   object->setDirty();
2645
2646   return true;
2647}
2648
2649DefineConsoleMethod(TerrainEditor, addMaterial, S32, ( String matName ), , 
2650   "( string matName )\n"
2651   "Adds a new material." )
2652{
2653   TerrainBlock *terr = object->getClientTerrain();
2654   if ( !terr )
2655      return false;
2656   
2657   terr->addMaterial( matName );
2658
2659   object->setDirty();
2660
2661   return true;
2662}
2663
2664DefineConsoleMethod( TerrainEditor, removeMaterial, void, ( S32 index ), , "( int index ) - Remove the material at the given index." )
2665{
2666   TerrainBlock *terr = object->getClientTerrain();
2667   if ( !terr )
2668      return;
2669      
2670   if ( index < 0 || index >= terr->getMaterialCount() )
2671   {
2672      Con::errorf( "TerrainEditor::removeMaterial - index out of range!" );
2673      return;
2674   }
2675
2676   if ( terr->getMaterialCount() == 1 )
2677   {
2678      Con::errorf( "TerrainEditor::removeMaterial - cannot remove material, there is only one!" );
2679      return;
2680   }
2681
2682   const char *matName = terr->getMaterialName( index );
2683
2684   object->submitMaterialUndo( String::ToString( "Remove TerrainMaterial %s", matName ) );
2685   
2686   terr->removeMaterial( index );
2687
2688   object->setDirty();
2689   object->scheduleMaterialUpdate();
2690   object->setGridUpdateMinMax();
2691}
2692
2693DefineConsoleMethod(TerrainEditor, getMaterialCount, S32, (), , 
2694   "Returns the current material count." )
2695{
2696   TerrainBlock *terr = object->getClientTerrain();
2697   if ( terr )
2698      return terr->getMaterialCount();
2699
2700   return 0;
2701}
2702
2703DefineConsoleMethod(TerrainEditor, getMaterials, const char *, (), , "() gets the list of current terrain materials.")
2704{
2705   TerrainBlock *terr = object->getClientTerrain();
2706   if ( !terr )
2707      return "";
2708
2709   char *ret = Con::getReturnBuffer(4096);
2710   ret[0] = 0;
2711   for(U32 i = 0; i < terr->getMaterialCount(); i++)
2712   {
2713      dStrcat( ret, terr->getMaterialName(i) );
2714      dStrcat( ret, "\n" );
2715   }
2716
2717   return ret;
2718}
2719
2720DefineConsoleMethod( TerrainEditor, getMaterialName, const char*, (S32 index), , "( int index ) - Returns the name of the material at the given index." )
2721{
2722   TerrainBlock *terr = object->getClientTerrain();
2723   if ( !terr )
2724      return "";
2725      
2726   if( index < 0 || index >= terr->getMaterialCount() )
2727   {
2728      Con::errorf( "TerrainEditor::getMaterialName - index out of range!" );
2729      return "";
2730   }
2731   
2732   const char* name = terr->getMaterialName( index );
2733   return Con::getReturnBuffer( name );
2734}
2735
2736DefineConsoleMethod( TerrainEditor, getMaterialIndex, S32, ( String name ), , "( string name ) - Returns the index of the material with the given name or -1." )
2737{
2738   TerrainBlock *terr = object->getClientTerrain();
2739   if ( !terr )
2740      return -1;
2741      
2742   const U32 count = terr->getMaterialCount();
2743   
2744   for( U32 i = 0; i < count; ++ i )
2745      if( dStricmp( name, terr->getMaterialName( i ) ) == 0 )
2746         return i;
2747         
2748   return -1;
2749}
2750
2751DefineConsoleMethod( TerrainEditor, reorderMaterial, void, ( S32 index, S32 orderPos ), , "( int index, int order ) "
2752  "- Reorder material at the given index to the new position, changing the order in which it is rendered / blended." )
2753{
2754   object->reorderMaterial( index, orderPos );
2755}
2756
2757DefineConsoleMethod(TerrainEditor, getTerrainUnderWorldPoint, S32, (const char * ptOrX, const char * Y, const char * Z), ("", "", ""), 
2758                                                                           "(x/y/z) Gets the terrain block that is located under the given world point.\n"
2759                                                                           "@param x/y/z The world coordinates (floating point values) you wish to query at. " 
2760                                                                           "These can be formatted as either a string (\"x y z\") or separately as (x, y, z)\n"
2761                                                                           "@return Returns the ID of the requested terrain block (0 if not found).\n\n")
2762{   
2763   TerrainEditor *tEditor = (TerrainEditor *) object;
2764   if(tEditor == NULL)
2765      return 0;
2766   Point3F pos;
2767   if(!String::isEmpty(ptOrX) && String::isEmpty(Y) && String::isEmpty(Z))
2768      dSscanf(ptOrX, "%f %f %f", &pos.x, &pos.y, &pos.z);
2769   else if(!String::isEmpty(ptOrX) && !String::isEmpty(Y) && !String::isEmpty(Z))
2770   {
2771      pos.x = dAtof(ptOrX);
2772      pos.y = dAtof(Y);
2773      pos.z = dAtof(Z);
2774   }
2775
2776   else
2777   {
2778      Con::errorf("TerrainEditor.getTerrainUnderWorldPoint(): Invalid argument count! Valid arguments are either \"x y z\" or x,y,z\n");
2779      return 0;
2780   }
2781
2782   TerrainBlock* terrain = tEditor->getTerrainUnderWorldPoint(pos);
2783   if(terrain != NULL)
2784   {
2785      return terrain->getId();
2786   }
2787
2788   return 0;
2789}
2790
2791//------------------------------------------------------------------------------
2792
2793void TerrainEditor::initPersistFields()
2794{
2795   addGroup("Misc"); 
2796   addField("isDirty", TypeBool, Offset(mIsDirty, TerrainEditor));
2797   addField("isMissionDirty", TypeBool, Offset(mIsMissionDirty, TerrainEditor));
2798   addField("renderBorder", TypeBool, Offset(mRenderBorder, TerrainEditor));                    ///< Not currently used
2799   addField("borderHeight", TypeF32, Offset(mBorderHeight, TerrainEditor));                     ///< Not currently used
2800   addField("borderFillColor", TypeColorI, Offset(mBorderFillColor, TerrainEditor));            ///< Not currently used
2801   addField("borderFrameColor", TypeColorI, Offset(mBorderFrameColor, TerrainEditor));          ///< Not currently used
2802   addField("borderLineMode", TypeBool, Offset(mBorderLineMode, TerrainEditor));                ///< Not currently used
2803   addField("selectionHidden", TypeBool, Offset(mSelectionHidden, TerrainEditor));
2804   addField("renderVertexSelection", TypeBool, Offset(mRenderVertexSelection, TerrainEditor));  ///< Not currently used
2805   addField("renderSolidBrush", TypeBool, Offset(mRenderSolidBrush, TerrainEditor));
2806   addField("processUsesBrush", TypeBool, Offset(mProcessUsesBrush, TerrainEditor));
2807   addField("maxBrushSize", TypePoint2I, Offset(mMaxBrushSize, TerrainEditor));
2808
2809   // action values...
2810   addField("adjustHeightVal", TypeF32, Offset(mAdjustHeightVal, TerrainEditor));               ///< RaiseHeightAction and LowerHeightAction
2811   addField("setHeightVal", TypeF32, Offset(mSetHeightVal, TerrainEditor));                     ///< SetHeightAction
2812   addField("scaleVal", TypeF32, Offset(mScaleVal, TerrainEditor));                             ///< ScaleHeightAction
2813   addField("smoothFactor", TypeF32, Offset(mSmoothFactor, TerrainEditor));                     ///< SmoothHeightAction
2814   addField("noiseFactor", TypeF32, Offset(mNoiseFactor, TerrainEditor));                       ///< PaintNoiseAction
2815   addField("materialGroup", TypeS32, Offset(mMaterialGroup, TerrainEditor));                   ///< Not currently used
2816   addField("softSelectRadius", TypeF32, Offset(mSoftSelectRadius, TerrainEditor));             ///< SoftSelectAction
2817   addField("softSelectFilter", TypeString, Offset(mSoftSelectFilter, TerrainEditor));          ///< SoftSelectAction brush filtering
2818   addField("softSelectDefaultFilter", TypeString, Offset(mSoftSelectDefaultFilter, TerrainEditor));  ///< SoftSelectAction brush filtering
2819   addField("adjustHeightMouseScale", TypeF32, Offset(mAdjustHeightMouseScale, TerrainEditor)); ///< Not currently used
2820   addField("paintIndex", TypeS32, Offset(mPaintIndex, TerrainEditor));                         ///< PaintMaterialAction
2821   endGroup("Misc");
2822
2823   Parent::initPersistFields();
2824}
2825
2826DefineConsoleMethod( TerrainEditor, getSlopeLimitMinAngle, F32, (), , "")
2827{
2828   return object->mSlopeMinAngle;
2829}
2830
2831DefineConsoleMethod( TerrainEditor, setSlopeLimitMinAngle, F32, (F32 angle), , "")
2832{
2833   if ( angle < 0.0f )
2834      angle = 0.0f;
2835   if ( angle > object->mSlopeMaxAngle )
2836      angle = object->mSlopeMaxAngle;
2837
2838   object->mSlopeMinAngle = angle;
2839   return angle;
2840}
2841
2842DefineConsoleMethod( TerrainEditor, getSlopeLimitMaxAngle, F32, (), , "")
2843{
2844   return object->mSlopeMaxAngle;
2845}
2846
2847DefineConsoleMethod( TerrainEditor, setSlopeLimitMaxAngle, F32, (F32 angle), , "")
2848{
2849   if ( angle > 90.0f )
2850      angle = 90.0f;
2851   if ( angle < object->mSlopeMinAngle )
2852      angle = object->mSlopeMinAngle;
2853      
2854   object->mSlopeMaxAngle = angle;
2855   return angle;
2856}
2857
2858//------------------------------------------------------------------------------  
2859void TerrainEditor::autoMaterialLayer( F32 mMinHeight, F32 mMaxHeight, F32 mMinSlope, F32 mMaxSlope, F32 mCoverage )  
2860{  
2861   if (!mActiveTerrain)  
2862      return;  
2863  
2864   S32 mat = getPaintMaterialIndex();  
2865   if (mat == -1)  
2866      return;  
2867  
2868   mUndoSel = new Selection;  
2869          
2870   U32 terrBlocks = mActiveTerrain->getBlockSize();  
2871   for (U32 y = 0; y < terrBlocks; y++) 
2872   {  
2873      for (U32 x = 0; x < terrBlocks; x++) 
2874      {  
2875         // get info  
2876         GridPoint gp;  
2877         gp.terrainBlock = mActiveTerrain;  
2878         gp.gridPos.set(x, y);  
2879  
2880         GridInfo gi;  
2881         getGridInfo(gp, gi);  
2882  
2883         if (gi.mMaterial == mat)  
2884            continue;  
2885
2886         if (mRandI(0, 100) > mCoverage)
2887            continue;
2888  
2889         Point3F wp;  
2890         gridToWorld(gp, wp);  
2891  
2892         if (!(wp.z >= mMinHeight && wp.z <= mMaxHeight))  
2893            continue;  
2894  
2895         // transform wp to object space  
2896         Point3F op;  
2897         mActiveTerrain->getWorldTransform().mulP(wp, &op);  
2898  
2899         Point3F norm;  
2900         mActiveTerrain->getNormal(Point2F(op.x, op.y), &norm, true);  
2901  
2902         if (mMinSlope > 0)  
2903            if (norm.z > mSin(mDegToRad(90.0f - mMinSlope)))  
2904               continue;  
2905  
2906         if (mMaxSlope < 90)  
2907            if (norm.z < mSin(mDegToRad(90.0f - mMaxSlope)))  
2908               continue;  
2909  
2910         gi.mMaterialChanged = true;  
2911         mUndoSel->add(gi);  
2912         gi.mMaterial = mat;  
2913         setGridInfo(gi);  
2914      }  
2915   }  
2916  
2917   if(mUndoSel->size())  
2918      submitUndo( mUndoSel );  
2919   else  
2920      delete mUndoSel;  
2921  
2922   mUndoSel = 0;  
2923  
2924   scheduleMaterialUpdate();     
2925}
2926
2927DefineEngineMethod( TerrainEditor, autoMaterialLayer, void, (F32 minHeight, F32 maxHeight, F32 minSlope, F32 maxSlope, F32 coverage),,
2928   "Rule based terrain painting.\n"
2929   "@param minHeight Minimum terrain height."
2930   "@param maxHeight Maximum terrain height."
2931   "@param minSlope Minimum terrain slope."
2932   "@param maxSlope Maximum terrain slope."
2933   "@param coverage Terrain coverage amount.")
2934{
2935   object->autoMaterialLayer( minHeight,maxHeight, minSlope, maxSlope, coverage );  
2936}
2937