1
0
forked from 0ad/0ad

Atlas: Terrain painting

This was SVN commit r3092.
This commit is contained in:
Ykkrosh 2005-11-05 04:59:54 +00:00
parent a679206ca1
commit faaee7d1b5
12 changed files with 522 additions and 61 deletions

View File

@ -407,15 +407,6 @@ float CTerrain::FlattenArea(float x0,float x1,float z0,float z1)
///////////////////////////////////////////////////////////////////////////////
void CTerrain::RaiseVertex(int x, int z, int amount)
{
// Ignore out-of-bounds vertices
if ((unsigned)x >= m_MapSize || (unsigned)z >= m_MapSize)
return;
m_Heightmap[x + z*m_MapSize] = (u16)clamp(m_Heightmap[x + z*m_MapSize] + amount, 0, 65535);
}
void CTerrain::MakeDirty(int x0, int z0, int x1, int z1)
{
// flag vertex data as dirty for affected patches, and rebuild bounds of these patches
@ -431,3 +422,14 @@ void CTerrain::MakeDirty(int x0, int z0, int x1, int z1)
}
}
}
void CTerrain::MakeDirty()
{
for (u32 j = 0; j < m_MapSizePatches; j++) {
for (u32 i = 0; i < m_MapSizePatches; i++) {
CPatch* patch = GetPatch(i,j);
patch->CalcBounds();
patch->SetDirty(RENDERDATA_UPDATE_VERTICES);
}
}
}

View File

@ -68,11 +68,10 @@ public:
// the average height of the flattened area
float FlattenArea(float x0,float x1,float z0,float z1);
// raise a given vertex, clamped to min/max height; ignored if out of bounds
void RaiseVertex(int x, int y, int amount);
// mark a specific square of tiles as dirty - use this after modifying the heightmap
void MakeDirty(int x0, int z0, int x1, int z1);
// mark the entire map as dirty
void MakeDirty();
private:
// delete any data allocated by this terrain

View File

@ -236,6 +236,7 @@ extern float fmaxf(float a, float b);
# define STL_HASH_MULTIMAP __gnu_cxx::hash_multimap
# define STL_HASH_SET __gnu_cxx::hash_set
# define STL_HASH_MULTISET __gnu_cxx::hash_multiset
# define STL_HASH_VALUE __gnu_cxx::hash
# define STL_SLIST __gnu_cxx::slist
// Hack: GCC Doesn't have a hash instance for std::string included (and it looks
@ -247,7 +248,7 @@ namespace __gnu_cxx
size_t operator()(const std::string& __x) const
{
return __stl_hash_string(__x.c_str());
}
}
};
}
@ -260,12 +261,14 @@ namespace __gnu_cxx
# define STL_HASH_MULTIMAP stdext::hash_multimap
# define STL_HASH_SET stdext::hash_set
# define STL_HASH_MULTISET stdext::hash_multiset
# define STL_HASH_VALUE stdext::hash_value
// VC6 and anything else (most likely name)
# else
# define STL_HASH_MAP std::hash_map
# define STL_HASH_MULTIMAP std::hash_multimap
# define STL_HASH_SET std::hash_set
# define STL_HASH_MULTISET std::hash_multiset
# define STL_HASH_VALUE std::hash_value
# endif // MSC_VERSION >= 1300
#endif // !__GNUC__

View File

@ -7,6 +7,7 @@
#include "General/Datafile.h"
#include "ScenarioEditor/Tools/Common/Brushes.h"
#include "ScenarioEditor/Tools/Common/Tools.h"
#include "ScenarioEditor/Tools/Common/MiscState.h"
#include "GameInterface/Messages.h"
@ -22,8 +23,9 @@ TerrainSidebar::TerrainSidebar(wxWindow* parent)
{
wxSizer* sizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Elevation tools"));
sizer->Add(new ToolButton(this, _("Modify"), _T("AlterElevation"), wxSize(50,20)));
sizer->Add(new ToolButton(this, _("Smooth"), _T(""), wxSize(50,20)));
sizer->Add(new ToolButton(this, _("Sample"), _T(""), wxSize(50,20)));
// sizer->Add(new ToolButton(this, _("Smooth"), _T(""), wxSize(50,20)));
// sizer->Add(new ToolButton(this, _("Sample"), _T(""), wxSize(50,20)));
sizer->Add(new ToolButton(this, _("Paint"), _T("PaintTerrain"), wxSize(50,20)));
m_MainSizer->Add(sizer);
}
@ -54,17 +56,19 @@ public:
{
}
void OnSelected()
void OnDisplay()
{
if (m_Loaded)
return;
const int imageWidth = 64;
const int imageHeight = 32;
m_TextureNames.Clear();
wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
wxListCtrl* list = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_ICON | wxLC_SINGLE_SEL | wxLC_AUTOARRANGE);
const int imageWidth = 64;
const int imageHeight = 32;
wxImageList* imglist = new wxImageList(imageWidth, imageHeight, false, 0);
AtlasMessage::qGetTerrainGroupPreviews qry(m_Name.c_str(), imageWidth, imageHeight);
@ -76,12 +80,20 @@ public:
wxImage img (imageWidth, imageHeight, it->imagedata);
imglist->Add(wxBitmap(img));
// Add spaces into the name, so Windows doesn't just say
// "grass_..." in the list ctrl for every terrain
wxString name = it->name.c_str();
name.Replace(_T("_"), _T(" "));
wxListItem item;
list->InsertItem(i, name, i);
wxString name = it->name.c_str();
m_TextureNames.Add(name);
// Add spaces into the displayed name, so Windows doesn't just say
// "grass_..." in the list ctrl for every terrain
name.Replace(_T("_"), _T(" "));
item.SetText(name);
item.SetData(i);
item.SetId(i);
item.SetImage(i);
list->InsertItem(item);
++i;
}
list->AssignImageList(imglist, wxIMAGE_LIST_NORMAL);
@ -93,11 +105,24 @@ public:
m_Loaded = true;
}
void OnSelect(wxListEvent& evt)
{
g_SelectedTexture = m_TextureNames[evt.GetData()];
}
private:
bool m_Loaded;
wxString m_Name;
wxArrayString m_TextureNames;
DECLARE_EVENT_TABLE();
};
BEGIN_EVENT_TABLE(TextureNotebookPage, wxPanel)
EVT_LIST_ITEM_SELECTED(wxID_ANY, TextureNotebookPage::OnSelect)
END_EVENT_TABLE();
class TextureNotebook : public wxNotebook
{
public:
@ -124,7 +149,7 @@ protected:
{
if (event.GetSelection() != -1)
{
static_cast<TextureNotebookPage*>(GetPage(event.GetSelection()))->OnSelected();
static_cast<TextureNotebookPage*>(GetPage(event.GetSelection()))->OnDisplay();
}
event.Skip();
}

View File

@ -0,0 +1,5 @@
#include "stdafx.h"
#include "MiscState.h"
wxString g_SelectedTexture = _T("grass1_spring");

View File

@ -0,0 +1,6 @@
#ifndef MISCSTATE_H__
#define MISCSTATE_H__
extern wxString g_SelectedTexture;
#endif // MISCSTATE_H__

View File

@ -0,0 +1,122 @@
#include "stdafx.h"
#include "Common/Tools.h"
#include "Common/Brushes.h"
#include "Common/MiscState.h"
#include "GameInterface/Messages.h"
using AtlasMessage::Position;
class PaintTerrain : public StateDrivenTool<PaintTerrain>
{
DECLARE_DYNAMIC_CLASS(PaintTerrain);
Position m_Pos;
public:
PaintTerrain()
{
SetState(&Waiting);
}
void OnEnable(PaintTerrain*)
{
// TODO: multiple independent brushes?
g_Brush_Elevation.MakeActive();
}
void OnDisable(PaintTerrain*)
{
POST_MESSAGE(BrushPreview(false, Position()));
}
struct sWaiting : public State
{
bool OnMouse(PaintTerrain* obj, wxMouseEvent& evt)
{
if (evt.LeftDown())
{
obj->m_Pos = Position(evt.GetPosition());
SET_STATE(PaintingHigh);
return true;
}
else if (evt.RightDown())
{
obj->m_Pos = Position(evt.GetPosition());
SET_STATE(PaintingLow);
return true;
}
else if (evt.Moving())
{
POST_MESSAGE(BrushPreview(true, Position(evt.GetPosition())));
return true;
}
else
{
return false;
}
}
}
Waiting;
struct sPainting_common : public State
{
void OnEnter(PaintTerrain* obj)
{
Paint(obj);
}
void OnLeave(PaintTerrain*)
{
ScenarioEditor::GetCommandProc().FinaliseLastCommand();
}
bool OnMouse(PaintTerrain* obj, wxMouseEvent& evt)
{
if (IsMouseUp(evt))
{
SET_STATE(Waiting);
return true;
}
else if (evt.Dragging())
{
wxPoint pos = evt.GetPosition();
obj->m_Pos = Position(pos);
Paint(obj);
return true;
}
else
{
return false;
}
}
void Paint(PaintTerrain* obj)
{
POST_MESSAGE(BrushPreview(true, obj->m_Pos));
POST_COMMAND(PaintTerrain, (obj->m_Pos, g_SelectedTexture.c_str(), GetPriority()));
}
virtual bool IsMouseUp(wxMouseEvent& evt) = 0;
virtual int GetPriority() = 0;
};
struct sPaintingHigh : public sPainting_common
{
bool IsMouseUp(wxMouseEvent& evt) { return evt.LeftUp(); }
int GetPriority() { return AtlasMessage::ePaintTerrainPriority::HIGH; }
}
PaintingHigh;
struct sPaintingLow : public sPainting_common
{
bool IsMouseUp(wxMouseEvent& evt) { return evt.RightUp(); }
int GetPriority() { return AtlasMessage::ePaintTerrainPriority::LOW; }
}
PaintingLow;
};
IMPLEMENT_DYNAMIC_CLASS(PaintTerrain, StateDrivenTool<PaintTerrain>);

View File

@ -0,0 +1,90 @@
#ifndef DELTAARRAY_H__
#define DELTAARRAY_H__
template<typename T> class DeltaArray2D
{
public:
T get(int x, int y);
void set(int x, int y, const T& val);
void OverlayWith(const DeltaArray2D<T>& overlayer);
void Undo();
void Redo();
protected:
virtual T getOld(int x, int y) = 0;
virtual void setNew(int x, int y, const T& val) = 0;
private:
struct hashfunc {
enum {
bucket_size = 4,
min_buckets = 8
};
size_t operator()(const std::pair<int, int>& p) const {
return STL_HASH_VALUE(p.first << 16) + STL_HASH_VALUE(p.second);
}
bool operator()(const std::pair<int, int>& a, const std::pair<int, int>& b) const {
return (a < b);
}
};
// TODO: more efficient representation
typedef STL_HASH_MAP<std::pair<int, int>, std::pair<T, T>, hashfunc> Data; // map of <x,y> -> <old_val, new_val>
Data m_Data;
};
//////////////////////////////////////////////////////////////////////////
template<typename T>
T DeltaArray2D<T>::get(int x, int y)
{
Data::iterator it = m_Data.find(std::make_pair(x, y));
if (it == m_Data.end())
return getOld(x, y);
else
return it->second.second;
}
template<typename T>
void DeltaArray2D<T>::set(int x, int y, const T& val)
{
Data::iterator it = m_Data.find(std::make_pair(x, y));
if (it == m_Data.end())
m_Data.insert(std::make_pair(std::make_pair(x, y), std::make_pair(getOld(x, y), val)));
else
it->second.second = val;
setNew(x, y, val);
}
template <typename T>
void DeltaArray2D<T>::OverlayWith(const DeltaArray2D<T>& overlayer)
{
for (Data::const_iterator it = overlayer.m_Data.begin(); it != overlayer.m_Data.end(); ++it)
{
Data::iterator it2 = m_Data.find(it->first);
if (it2 == m_Data.end())
m_Data.insert(*it);
else
{
//debug_assert(it2->second.second == it->second.first);
it2->second.second = it->second.second;
}
}
}
template <typename T>
void DeltaArray2D<T>::Undo()
{
for (Data::iterator it = m_Data.begin(); it != m_Data.end(); ++it)
setNew(it->first.first, it->first.second, it->second.first);
}
template <typename T>
void DeltaArray2D<T>::Redo()
{
for (Data::iterator it = m_Data.begin(); it != m_Data.end(); ++it)
setNew(it->first.first, it->first.second, it->second.second);
}
#endif // DELTAARRAY_H__

View File

@ -6,37 +6,60 @@
#include "graphics/Terrain.h"
#include "ps/Game.h"
#include "maths/MathUtil.h"
#include "../Brushes.h"
#include "../DeltaArray.h"
namespace AtlasMessage {
BEGIN_COMMAND(AlterElevation)
// TODO: much more efficient version of this, and without the memory leaks
u16* OldTerrain;
u16* NewTerrain;
class TerrainArray : public DeltaArray2D<u16>
{
public:
void Init()
{
m_Heightmap = g_Game->GetWorld()->GetTerrain()->GetHeightMap();
m_VertsPerSide = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide();
}
void RaiseVertex(int x, int y, int amount)
{
// Ignore out-of-bounds vertices
if ((unsigned)x >= m_VertsPerSide || (unsigned)y >= m_VertsPerSide)
return;
set(x,y, (u16)clamp(get(x,y) + amount, 0, 65535));
}
protected:
u16 getOld(int x, int y)
{
return m_Heightmap[y*m_VertsPerSide + x];
}
void setNew(int x, int y, const u16& val)
{
m_Heightmap[y*m_VertsPerSide + x] = val;
}
u16* m_Heightmap;
size_t m_VertsPerSide;
};
TerrainArray m_TerrainDelta;
void Construct()
{
OldTerrain = NewTerrain = NULL;
m_TerrainDelta.Init();
}
void Destruct()
{
delete OldTerrain;
delete NewTerrain;
}
void Do()
{
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
int verts = terrain->GetVerticesPerSide()*terrain->GetVerticesPerSide();
OldTerrain = new u16[verts];
memcpy2(OldTerrain, terrain->GetHeightMap(), verts*sizeof(u16));
int amount = (int)d->amount;
// If the framerate is very high, 'amount' is often very
@ -62,33 +85,27 @@ BEGIN_COMMAND(AlterElevation)
// TODO: proper variable raise amount (store floats in terrain delta array?)
float b = g_CurrentBrush.Get(dx, dy);
if (b)
terrain->RaiseVertex(x0+dx, y0+dy, amount*b);
m_TerrainDelta.RaiseVertex(x0+dx, y0+dy, amount*b);
}
terrain->MakeDirty(x0, y0, x0+g_CurrentBrush.m_W, y0+g_CurrentBrush.m_H);
g_Game->GetWorld()->GetTerrain()->MakeDirty(x0, y0, x0+g_CurrentBrush.m_W, y0+g_CurrentBrush.m_H);
}
void Undo()
{
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
if (! NewTerrain)
{
int verts = terrain->GetVerticesPerSide()*terrain->GetVerticesPerSide();
NewTerrain = new u16[verts];
memcpy2(NewTerrain, terrain->GetHeightMap(), verts*sizeof(u16));
}
terrain->SetHeightMap(OldTerrain); // CTerrain duplicates the data
m_TerrainDelta.Undo();
g_Game->GetWorld()->GetTerrain()->MakeDirty();
}
void Redo()
{
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
terrain->SetHeightMap(NewTerrain); // CTerrain duplicates the data
m_TerrainDelta.Redo();
g_Game->GetWorld()->GetTerrain()->MakeDirty();
}
void MergeWithSelf(cAlterElevation* prev)
{
std::swap(prev->NewTerrain, NewTerrain);
prev->m_TerrainDelta.OverlayWith(m_TerrainDelta);
}
END_COMMAND(AlterElevation);

View File

@ -70,7 +70,7 @@ MESSAGEHANDLER(GenerateMap)
// Cover terrain with default texture
// TODO: split into fCoverWithTexture
CTextureEntry* texentry = g_TexMan.FindTexture("grass1_spring.dds"); // TODO: make default customisable
CTextureEntry* texentry = g_TexMan.FindTexture("grass1_spring"); // TODO: make default customisable
Handle tex = texentry ? texentry->GetHandle() : 0;
int patches = terrain->GetPatchesPerSide();
@ -81,7 +81,10 @@ MESSAGEHANDLER(GenerateMap)
for (int z = 0; z < PATCH_SIZE; ++z)
for (int x = 0; x < PATCH_SIZE; ++x)
{
patch->m_MiniPatches[z][x].Tex1 = tex;
patch->m_MiniPatches[z][x].Tex1Priority = 0;
}
}
}

View File

@ -6,8 +6,14 @@
#include "graphics/TextureManager.h"
#include "graphics/TextureEntry.h"
#include "graphics/Terrain.h"
#include "ps/Game.h"
#include "ps/World.h"
#include "lib/ogl.h"
#include "lib/res/graphics/ogl_tex.h"
#include "../Brushes.h"
#include "../DeltaArray.h"
namespace AtlasMessage {
@ -18,31 +24,207 @@ QUERYHANDLER(GetTerrainGroups)
msg->groupnames.push_back(CStrW(it->first));
}
static bool CompareTerrain(const sTerrainGroupPreview& a, const sTerrainGroupPreview& b)
{
return (wcscmp(a.name.c_str(), b.name.c_str()) < 0);
}
QUERYHANDLER(GetTerrainGroupPreviews)
{
CTerrainGroup* group = g_TexMan.FindGroup(CStrW(msg->groupname));
for (std::vector<CTextureEntry*>::const_iterator it = group->GetTerrains().begin(); it != group->GetTerrains().end(); ++it)
{
msg->previews.push_back(AtlasMessage::sTerrainGroupPreview());
msg->previews.push_back(sTerrainGroupPreview());
msg->previews.back().name = CStrW((*it)->GetTag());
u32 c = (*it)->GetBaseColor();
unsigned char* buf = (unsigned char*)malloc(msg->imagewidth*msg->imageheight*3);
// TODO: An actual preview of the texture. (There's no need to shrink
// the entire texture to fit, since it's the small details in the
// texture that are interesting, so we could just crop a chunk out of
// the middle.)
for (int i = 0; i < msg->imagewidth*msg->imageheight; ++i)
// It's not good to shrink the entire texture to fit the small preview
// window, since it's the fine details in the texture that are
// interesting; so just go down one mipmap level, then crop a chunk
// out of the middle.
// Read the size of the texture. (Usually loads the texture from
// disk, which is slow.)
GLint w, h;
int level = 1; // level 0 is the original size
ogl_tex_bind((*it)->GetHandle());
glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &w);
glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_HEIGHT, &h);
if (w < msg->imagewidth || h < msg->imageheight)
{
buf[i*3+0] = (c>>16) & 0xff;
buf[i*3+1] = (c>>8) & 0xff;
buf[i*3+2] = (c>>0) & 0xff;
// Oops, too small to preview - just use a flat colour
u32 c = (*it)->GetBaseColor();
for (int i = 0; i < msg->imagewidth*msg->imageheight; ++i)
{
buf[i*3+0] = (c>>16) & 0xff;
buf[i*3+1] = (c>>8) & 0xff;
buf[i*3+2] = (c>>0) & 0xff;
}
}
else
{
// Read the whole texture into a new buffer
unsigned char* texdata = new unsigned char[w*h*3];
glGetTexImage(GL_TEXTURE_2D, level, GL_RGB, GL_UNSIGNED_BYTE, texdata);
// Extract the middle section (as a representative preview),
// and copy into buf
unsigned char* texdata_ptr = texdata + (w*(h - msg->imageheight)/2 + (w - msg->imagewidth)/2) * 3;
unsigned char* buf_ptr = buf;
for (int y = 0; y < msg->imageheight; ++y)
{
memcpy(buf_ptr, texdata_ptr, msg->imagewidth*3);
buf_ptr += msg->imagewidth*3;
texdata_ptr += w*3;
}
delete[] texdata;
}
msg->previews.back().imagedata = buf;
}
// Sort the list alphabetically by name
std::sort(msg->previews.begin(), msg->previews.end(), CompareTerrain);
}
//////////////////////////////////////////////////////////////////////////
BEGIN_COMMAND(PaintTerrain)
struct TerrainTile
{
TerrainTile(Handle t, int p) : tex(t), priority(p) {}
Handle tex;
int priority;
};
class TerrainArray : public DeltaArray2D<TerrainTile>
{
public:
void Init()
{
m_Terrain = g_Game->GetWorld()->GetTerrain();
m_VertsPerSide = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide();
}
void PaintTile(int x, int y, Handle tex, int priority)
{
// Ignore out-of-bounds tiles
if ((unsigned)x >= m_VertsPerSide-1 || (unsigned)y >= m_VertsPerSide-1)
return;
set(x,y, TerrainTile(tex, priority == ePaintTerrainPriority::HIGH ? 1 : -1));
/* (TODO: fix all this)
// Priority system: If the new tile should have a high priority,
// set it to one plus the maximum priority of all surrounding tiles
// TODO: the blending system is broken when adjacent tiles have
// the same priority but different textures
int greatestSame = 0;
int greatestDiff = 0;
int scale = (priority == ePaintTerrainPriority::HIGH ? +1 : -1);
CMiniPatch* tile;
#define TILE(dx, dy) \
tile = m_Terrain->GetTile(x dx, y dy); \
if (tile) { \
if (tile->Tex1 == tex && tile->Tex1Priority*scale > greatestSame) \
greatestSame = tile->Tex1Priority*scale; \
else if (tile->Tex1 != tex && tile->Tex1Priority*scale > greatestDiff) \
greatestDiff = tile->Tex1Priority*scale; \
}
TILE(-1, -1) TILE(+0, -1) TILE(+1, -1)
TILE(-1, +0) TILE(+1, +0)
TILE(-1, +1) TILE(+0, +1) TILE(+1, +1)
#undef TILE
// If the greatest priority is of the same texture as this one,
// give this one the same priority (to minimise confusion in the
// blending system)
if (greatestSame > greatestDiff)
{
tile = m_Terrain->GetTile(x,y);
// Don't bother updating tiles that are exactly the same
// (e.g. when dragging big brushes around)
if (tile->Tex1 != tex || tile->Tex1Priority != greatestSame)
set(x,y, TerrainTile(tex, greatestSame*scale));
}
// Set this texture to have a priority greater than the surrounding ones
else
set(x,y, TerrainTile(tex, (greatestDiff+1)*scale));
*/
}
protected:
TerrainTile getOld(int x, int y)
{
CMiniPatch* mp = m_Terrain->GetTile(x, y);
return TerrainTile(mp->Tex1, mp->Tex1Priority);
}
void setNew(int x, int y, const TerrainTile& val)
{
CMiniPatch* mp = m_Terrain->GetTile(x, y);
mp->Tex1 = val.tex;
mp->Tex1Priority = val.priority;
}
CTerrain* m_Terrain;
size_t m_VertsPerSide;
};
TerrainArray m_TerrainDelta;
void Construct()
{
m_TerrainDelta.Init();
}
void Destruct()
{
}
void Do()
{
d->pos.GetWorldSpace(g_CurrentBrush.m_Centre);
int x0, y0;
g_CurrentBrush.GetBottomRight(x0, y0);
CTextureEntry* texentry = g_TexMan.FindTexture(CStrW(d->texture));
if (! texentry)
{
debug_warn("Can't find texentry"); // TODO: nicer error handling
return;
}
Handle texture = texentry->GetHandle();
for (int dy = 0; dy < g_CurrentBrush.m_H; ++dy)
for (int dx = 0; dx < g_CurrentBrush.m_W; ++dx)
{
if (g_CurrentBrush.Get(dx, dy) > 0.5f) // TODO: proper solid brushes
m_TerrainDelta.PaintTile(x0+dx, y0+dy, texture, d->priority);
}
g_Game->GetWorld()->GetTerrain()->MakeDirty(x0, y0, x0+g_CurrentBrush.m_W, y0+g_CurrentBrush.m_H);
}
void Undo()
{
m_TerrainDelta.Undo();
g_Game->GetWorld()->GetTerrain()->MakeDirty();
}
void Redo()
{
m_TerrainDelta.Redo();
g_Game->GetWorld()->GetTerrain()->MakeDirty();
}
void MergeWithSelf(cPaintTerrain* prev)
{
prev->m_TerrainDelta.OverlayWith(m_TerrainDelta);
}
END_COMMAND(PaintTerrain);
}

View File

@ -115,6 +115,13 @@ COMMAND(AlterElevation, MERGE,
((float, amount))
);
struct ePaintTerrainPriority { enum { HIGH, LOW }; };
COMMAND(PaintTerrain, MERGE,
((Position, pos))
((std::wstring, texture))
((int, priority))
);
//////////////////////////////////////////////////////////////////////////
#include "MessagesSetup.h"