terrainEditor.cpp
Engine/source/gui/worldEditor/terrainEditor.cpp
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
