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