tge/engine/editor/terrainEditor.cc
2017-04-17 06:17:10 -06:00

1870 lines
50 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "editor/terrainEditor.h"
#include "game/collisionTest.h"
#include "terrain/terrData.h"
#include "gui/core/guiCanvas.h"
#include "console/consoleTypes.h"
#include "editor/terrainActions.h"
#include "interior/interiorInstance.h"
#include "interior/interior.h"
#include "game/gameConnection.h"
#include "sim/netObject.h"
#include "core/frameAllocator.h"
IMPLEMENT_CONOBJECT(TerrainEditor);
Selection::Selection() :
Vector<GridInfo>(__FILE__, __LINE__),
mName(0),
mUndoFlags(0),
mHashListSize(1024)
{
VECTOR_SET_ASSOCIATION(mHashLists);
// clear the hash list
mHashLists.setSize(mHashListSize);
reset();
}
Selection::~Selection()
{
}
void Selection::reset()
{
for(U32 i = 0; i < mHashListSize; i++)
mHashLists[i] = -1;
clear();
}
U32 Selection::getHashIndex(const Point2I & pos)
{
Point2F pnt = Point2F(pos.x, pos.y) + Point2F(1.3f,3.5f);
return( (U32)(mFloor(mHashLists.size() * mFmod(pnt.len() * 0.618f, 1))) );
}
S32 Selection::lookup(const Point2I & pos)
{
U32 index = getHashIndex(pos);
S32 entry = mHashLists[index];
while(entry != -1)
{
if((*this)[entry].mGridPos == pos)
return(entry);
entry = (*this)[entry].mNext;
}
return(-1);
}
void Selection::insert(GridInfo & info)
{
U32 index = getHashIndex(info.mGridPos);
info.mNext = mHashLists[index];
info.mPrev = -1;
if(info.mNext != -1)
(*this)[info.mNext].mPrev = size();
mHashLists[index] = size();
push_back(info);
}
bool Selection::remove(const GridInfo & info)
{
U32 index = getHashIndex(info.mGridPos);
S32 entry = mHashLists[index];
if(entry == -1)
return(false);
// front?
if((*this)[entry].mGridPos == info.mGridPos)
mHashLists[index] = (*this)[entry].mNext;
while(entry != -1)
{
if((*this)[entry].mGridPos == info.mGridPos)
{
if((*this)[entry].mPrev != -1)
(*this)[(*this)[entry].mPrev].mNext = (*this)[entry].mNext;
if((*this)[entry].mNext != -1)
(*this)[(*this)[entry].mNext].mPrev = (*this)[entry].mPrev;
// swap?
if(entry != (size() - 1))
{
U32 last = size() - 1;
(*this)[entry] = (*this)[size()-1];
if((*this)[entry].mPrev != -1)
(*this)[(*this)[entry].mPrev].mNext = entry;
else
{
U32 idx = getHashIndex((*this)[entry].mGridPos);
AssertFatal(mHashLists[idx] == ((*this).size() - 1), "doh");
mHashLists[idx] = entry;
}
if((*this)[entry].mNext != -1)
(*this)[(*this)[entry].mNext].mPrev = entry;
}
pop_back();
return(true);
}
entry = (*this)[entry].mNext;
}
return(false);
}
// add unique grid info into the selection - test uniqueness by grid position
bool Selection::add(GridInfo & info)
{
S32 index = lookup(info.mGridPos);
if(index != -1)
return(false);
insert(info);
return(true);
}
bool Selection::getInfo(Point2I pos, GridInfo & info)
{
S32 index = lookup(pos);
if(index == -1)
return(false);
info = (*this)[index];
return(true);
}
bool Selection::setInfo(GridInfo & info)
{
S32 index = lookup(info.mGridPos);
if(index == -1)
return(false);
S32 next = (*this)[index].mNext;
S32 prev = (*this)[index].mPrev;
(*this)[index] = info;
(*this)[index].mNext = next;
(*this)[index].mPrev = prev;
return(true);
}
F32 Selection::getAvgHeight()
{
if(!size())
return(0);
F32 avg = 0.f;
for(U32 i = 0; i < size(); i++)
avg += (*this)[i].mHeight;
return(avg / size());
}
//------------------------------------------------------------------------------
Brush::Brush(TerrainEditor * editor) :
mTerrainEditor(editor)
{
mSize = mTerrainEditor->getBrushSize();
}
const Point2I & Brush::getPosition()
{
return(mGridPos);
}
void Brush::setPosition(const Point3F & pos)
{
Point2I gPos;
mTerrainEditor->worldToGrid(pos, gPos);
setPosition(gPos);
}
void Brush::setPosition(const Point2I & pos)
{
mGridPos = pos;
update();
}
//------------------------------------------------------------------------------
void Brush::update()
{
rebuild();
// soft selection?
// if(mTerrainEditor->mEnableSoftBrushes)
// {
// Gui3DMouseEvent event;
// TerrainAction * action = mTerrainEditor->lookupAction("softSelect");
// AssertFatal(action, "Brush::update: no 'softSelect' action found!");
//
// mTerrainEditor->setCurrentSel(this);
// action->process(this, event, true, TerrainAction::Process);
// mTerrainEditor->resetCurrentSel();
// }
}
//------------------------------------------------------------------------------
void BoxBrush::rebuild()
{
reset();
Filter filter;
filter.set(1, &mTerrainEditor->mSoftSelectFilter);
//
// mSize should always be odd.
S32 centerX = (mSize.x - 1) / 2;
S32 centerY = (mSize.y - 1) / 2;
F32 xFactorScale = F32(centerX) / (F32(centerX) + 0.5);
F32 yFactorScale = F32(centerY) / (F32(centerY) + 0.5);
for(S32 x = 0; x < mSize.x; x++)
{
for(S32 y = 0; y < mSize.y; y++)
{
GridInfo info;
mTerrainEditor->getGridInfo(Point2I(mGridPos.x + x - centerX, mGridPos.y + y - centerY), info);
if(mTerrainEditor->mEnableSoftBrushes && centerX != 0 && centerY != 0)
{
F32 xFactor = (mFabs(F32(centerX - x)) / F32(centerX)) * xFactorScale;
F32 yFactor = (mFabs(F32(centerY - y)) / F32(centerY)) * yFactorScale;
info.mWeight = filter.getValue(xFactor > yFactor ? xFactor : yFactor);
}
push_back(info);
}
}
}
//------------------------------------------------------------------------------
void EllipseBrush::rebuild()
{
reset();
Point3F center(F32(mSize.x - 1) / 2, F32(mSize.y - 1) / 2, 0);
Filter filter;
filter.set(1, &mTerrainEditor->mSoftSelectFilter);
// a point is in a circle if:
// x^2 + y^2 <= r^2
// a point is in an ellipse if:
// (ax)^2 + (by)^2 <= 1
// where a = 1/halfEllipseWidth and b = 1/halfEllipseHeight
// for a soft-selected ellipse,
// the factor is simply the filtered: ((ax)^2 + (by)^2)
F32 a = 1 / (F32(mSize.x) * 0.5);
F32 b = 1 / (F32(mSize.y) * 0.5);
for(U32 x = 0; x < mSize.x; x++)
{
for(U32 y = 0; y < mSize.y; y++)
{
F32 xp = center.x - x;
F32 yp = center.y - y;
F32 factor = (a * a * xp * xp) + (b * b * yp * yp);
if(factor > 1)
continue;
GridInfo info;
mTerrainEditor->getGridInfo(Point2I((S32)(mGridPos.x + x - center.x), (S32)(mGridPos.y + y - center.y)), info);
if(mTerrainEditor->mEnableSoftBrushes)
info.mWeight = filter.getValue(factor);
push_back(info);
}
}
}
//------------------------------------------------------------------------------
SelectionBrush::SelectionBrush(TerrainEditor * editor) :
Brush(editor)
{
//... grab the current selection
}
void SelectionBrush::rebuild()
{
reset();
//... move the selection
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
TerrainEditor::TerrainEditor() :
mTerrainBlock(0),
mMousePos(0,0,0),
mMouseBrush(0),
mInAction(false),
mUndoLimit(20),
mUndoSel(0),
mRebuildEmpty(false),
mRebuildTextures(false),
mGridUpdateMin(255, 255),
mGridUpdateMax(0, 0)
{
VECTOR_SET_ASSOCIATION(mActions);
VECTOR_SET_ASSOCIATION(mUndoList);
VECTOR_SET_ASSOCIATION(mRedoList);
VECTOR_SET_ASSOCIATION(mBaseMaterialInfos);
//
resetCurrentSel();
//
mBrushSize.set(1,1);
mMouseBrush = new BoxBrush(this);
mMouseDownSeq = 0;
mIsDirty = false;
mIsMissionDirty = false;
mPaintMaterial = NULL;
// add in all the actions here..
mActions.push_back(new SelectAction(this));
mActions.push_back(new SoftSelectAction(this));
mActions.push_back(new OutlineSelectAction(this));
mActions.push_back(new PaintMaterialAction(this));
mActions.push_back(new RaiseHeightAction(this));
mActions.push_back(new LowerHeightAction(this));
mActions.push_back(new SetHeightAction(this));
mActions.push_back(new SetEmptyAction(this));
mActions.push_back(new ClearEmptyAction(this));
mActions.push_back(new ScaleHeightAction(this));
mActions.push_back(new BrushAdjustHeightAction(this));
mActions.push_back(new AdjustHeightAction(this));
mActions.push_back(new FlattenHeightAction(this));
mActions.push_back(new SmoothHeightAction(this));
mActions.push_back(new SetMaterialGroupAction(this));
mActions.push_back(new SetModifiedAction(this));
mActions.push_back(new ClearModifiedAction(this));
// set the default action
mCurrentAction = mActions[0];
mRenderBrush = mCurrentAction->useMouseBrush();
// persist data defaults
mRenderBorder = true;
mBorderHeight = 10;
mBorderFillColor.set(0,255,0,20);
mBorderFrameColor.set(0,255,0,128);
mBorderLineMode = false;
mSelectionHidden = false;
mEnableSoftBrushes = false;
mRenderVertexSelection = false;
mProcessUsesBrush = false;
mCurrentCursor = NULL;
mCursorVisible = true;
//
mAdjustHeightVal = 10;
mSetHeightVal = 100;
mScaleVal = 1;
mSmoothFactor = 0.1f;
mMaterialGroup = 0;
mSoftSelectRadius = 50.f;
mAdjustHeightMouseScale = 0.1f;
mSoftSelectDefaultFilter = StringTable->insert("1.000000 0.833333 0.666667 0.500000 0.333333 0.166667 0.000000");
mSoftSelectFilter = mSoftSelectDefaultFilter;;
}
TerrainEditor::~TerrainEditor()
{
// mouse
delete mMouseBrush;
// terrain actions
U32 i;
for(i = 0; i < mActions.size(); i++)
delete mActions[i];
// undo stuff
clearUndo(mUndoList);
clearUndo(mRedoList);
delete mUndoSel;
// base material infos
for(i = 0; i < mBaseMaterialInfos.size(); i++)
delete mBaseMaterialInfos[i];
}
//------------------------------------------------------------------------------
TerrainAction * TerrainEditor::lookupAction(const char * name)
{
for(U32 i = 0; i < mActions.size(); i++)
if(!dStricmp(mActions[i]->getName(), name))
return(mActions[i]);
return(0);
}
//------------------------------------------------------------------------------
bool TerrainEditor::onAdd()
{
if(!Parent::onAdd())
return(false);
SimObject * obj = Sim::findObject("EditorArrowCursor");
if(!obj)
{
Con::errorf(ConsoleLogEntry::General, "TerrainEditor::onAdd: failed to load cursor");
return(false);
}
mDefaultCursor = dynamic_cast<GuiCursor*>(obj);
return(true);
}
//------------------------------------------------------------------------------
void TerrainEditor::onDeleteNotify(SimObject * object)
{
Parent::onDeleteNotify(object);
if(mTerrainBlock != dynamic_cast<TerrainBlock*>(object))
return;
mTerrainBlock = 0;
}
void TerrainEditor::setCursor(GuiCursor * cursor)
{
mCurrentCursor = cursor ? cursor : mDefaultCursor;
}
S32 TerrainEditor::getPaintMaterial()
{
if(!mPaintMaterial)
return -1;
return getClientTerrain()->getMaterialAlphaIndex(mPaintMaterial);
}
//------------------------------------------------------------------------------
TerrainBlock * TerrainEditor::getClientTerrain()
{
// do the client..
NetConnection * toServer = NetConnection::getConnectionToServer();
NetConnection * toClient = NetConnection::getLocalClientConnection();
S32 index = toClient->getGhostIndex(mTerrainBlock);
return(dynamic_cast<TerrainBlock*>(toServer->resolveGhost(index)));
}
//------------------------------------------------------------------------------
bool TerrainEditor::gridToWorld(const Point2I & gPos, Point3F & wPos)
{
const MatrixF & mat = mTerrainBlock->getTransform();
Point3F origin;
mat.getColumn(3, &origin);
wPos.x = gPos.x * (float)mTerrainBlock->getSquareSize() + origin.x;
wPos.y = gPos.y * (float)mTerrainBlock->getSquareSize() + origin.y;
wPos.z = getGridHeight(gPos);
return(!(gPos.x >> TerrainBlock::BlockShift || gPos.y >> TerrainBlock::BlockShift));
}
bool TerrainEditor::worldToGrid(const Point3F & wPos, Point2I & gPos)
{
const MatrixF & mat = mTerrainBlock->getTransform();
Point3F origin;
mat.getColumn(3, &origin);
F32 squareSize = (F32) mTerrainBlock->getSquareSize();
F32 halfSquareSize = squareSize / 2;
float x = (wPos.x - origin.x + halfSquareSize) / squareSize;
float y = (wPos.y - origin.y + halfSquareSize) / squareSize;
gPos.x = (S32)mFloor(x);
gPos.y = (S32)mFloor(y);
return(!(gPos.x >> TerrainBlock::BlockShift || gPos.y >> TerrainBlock::BlockShift));
}
bool TerrainEditor::gridToCenter(const Point2I & gPos, Point2I & cPos)
{
cPos.x = gPos.x & TerrainBlock::BlockMask;
cPos.y = gPos.y & TerrainBlock::BlockMask;
return(!(gPos.x >> TerrainBlock::BlockShift || gPos.y >> TerrainBlock::BlockShift));
}
//------------------------------------------------------------------------------
bool TerrainEditor::getGridInfo(const Point3F & wPos, GridInfo & info)
{
Point2I gPos;
worldToGrid(wPos, gPos);
return getGridInfo(gPos, info);
}
bool TerrainEditor::getGridInfo(const Point2I & gPos, GridInfo & info)
{
//
info.mGridPos = gPos;
info.mMaterial = getGridMaterial(gPos);
info.mHeight = getGridHeight(gPos);
info.mWeight = 1.f;
info.mPrimarySelect = true;
info.mMaterialChanged = false;
Point2I cPos;
gridToCenter(gPos, cPos);
info.mMaterialGroup = mTerrainBlock->getBaseMaterial(cPos.x, cPos.y);
mTerrainBlock->getMaterialAlpha(gPos, info.mMaterialAlpha);
return(!(gPos.x >> TerrainBlock::BlockShift || gPos.y >> TerrainBlock::BlockShift));
}
void TerrainEditor::setGridInfo(const GridInfo & info)
{
setGridHeight(info.mGridPos, info.mHeight);
setGridMaterial(info.mGridPos, info.mMaterial);
setGridMaterialGroup(info.mGridPos, info.mMaterialGroup);
if(info.mMaterialChanged)
mTerrainBlock->setMaterialAlpha(info.mGridPos, info.mMaterialAlpha);
}
//------------------------------------------------------------------------------
F32 TerrainEditor::getGridHeight(const Point2I & gPos)
{
Point2I cPos;
gridToCenter(gPos, cPos);
return(fixedToFloat(mTerrainBlock->getHeight(cPos.x, cPos.y)));
}
void TerrainEditor::gridUpdateComplete()
{
if(mGridUpdateMin.x <= mGridUpdateMax.x)
mTerrainBlock->updateGrid(mGridUpdateMin, mGridUpdateMax);
mGridUpdateMin.set(256,256);
mGridUpdateMax.set(0,0);
}
void TerrainEditor::materialUpdateComplete()
{
if(mGridUpdateMin.x <= mGridUpdateMax.x)
{
TerrainBlock * clientTerrain = getClientTerrain();
clientTerrain->updateGridMaterials(mGridUpdateMin, mGridUpdateMax);
mTerrainBlock->updateGrid(mGridUpdateMin, mGridUpdateMax);
}
mGridUpdateMin.set(256,256);
mGridUpdateMax.set(0,0);
}
void TerrainEditor::setGridHeight(const Point2I & gPos, const F32 height)
{
Point2I cPos;
gridToCenter(gPos, cPos);
if(cPos.x < mGridUpdateMin.x)
mGridUpdateMin.x = cPos.x;
if(cPos.y < mGridUpdateMin.y)
mGridUpdateMin.y = cPos.y;
if(cPos.x > mGridUpdateMax.x)
mGridUpdateMax.x = cPos.x;
if(cPos.y > mGridUpdateMax.y)
mGridUpdateMax.y = cPos.y;
mTerrainBlock->setHeight(cPos, height);
}
TerrainBlock::Material TerrainEditor::getGridMaterial(const Point2I & gPos)
{
Point2I cPos;
gridToCenter(gPos, cPos);
return(*mTerrainBlock->getMaterial(cPos.x, cPos.y));
}
void TerrainEditor::setGridMaterial(const Point2I & gPos, const TerrainBlock::Material & material)
{
Point2I cPos;
gridToCenter(gPos, cPos);
// check if empty has been altered...
TerrainBlock::Material * mat = mTerrainBlock->getMaterial(cPos.x, cPos.y);
if((mat->flags & TerrainBlock::Material::Empty) ^ (material.flags & TerrainBlock::Material::Empty))
mRebuildEmpty = true;
*mat = material;
}
U8 TerrainEditor::getGridMaterialGroup(const Point2I & gPos)
{
Point2I cPos;
gridToCenter(gPos, cPos);
return(mTerrainBlock->getBaseMaterial(cPos.x, cPos.y));
}
// basematerials are shared through a resource... so work on client object
// so wont need to load textures....
void TerrainEditor::setGridMaterialGroup(const Point2I & gPos, const U8 group)
{
Point2I cPos;
gridToCenter(gPos, cPos);
TerrainBlock * clientTerrain = getClientTerrain();
clientTerrain->setBaseMaterial(cPos.x, cPos.y, group);
}
//------------------------------------------------------------------------------
bool TerrainEditor::collide(const Gui3DMouseEvent & event, Point3F & pos)
{
if(!mTerrainBlock)
return(false);
// call the terrain block's ray collision routine directly
Point3F startPnt = event.pos;
Point3F endPnt = event.pos + event.vec * 1000;
Point3F tStartPnt, tEndPnt;
mTerrainBlock->getTransform().mulP(startPnt, &tStartPnt);
mTerrainBlock->getTransform().mulP(endPnt, &tEndPnt);
RayInfo ri;
if(mTerrainBlock->castRayI(tStartPnt, tEndPnt, &ri, true))
{
ri.point.interpolate(startPnt, endPnt, ri.t);
pos = ri.point;
return(true);
}
return(false);
}
//------------------------------------------------------------------------------
void TerrainEditor::updateGuiInfo()
{
char buf[128];
// mouse num grids
// mouse avg height
// selection num grids
// selection avg height
dSprintf(buf, sizeof(buf), "%d %g %d %g",
mMouseBrush->size(), mMouseBrush->getAvgHeight(),
mDefaultSel.size(), mDefaultSel.getAvgHeight());
Con::executef(this, 2, "onGuiUpdate", buf);
}
//------------------------------------------------------------------------------
void TerrainEditor::renderScene(const RectI &)
{
if(!mTerrainBlock)
return;
if(!mSelectionHidden)
renderSelection(mDefaultSel, ColorF(1,0,0), ColorF(0,1,0), ColorF(0,0,1), ColorF(0,0,1), true, false);
if(mRenderBrush && mMouseBrush->size())
renderSelection(*mMouseBrush, ColorF(1,0,0), ColorF(0,1,0), ColorF(0,0,1), ColorF(0,0,1), false, true);
if(mRenderBorder)
renderBorder();
}
//------------------------------------------------------------------------------
struct TerrVertex
{
Point3F point;
ColorI color;
};
void TerrainEditor::renderSelection( const Selection & sel, const ColorF & inColorFull, const ColorF & inColorNone, const ColorF & outColorFull, const ColorF & outColorNone, bool renderFill, bool renderFrame )
{
Vector<TerrVertex> vertexBuffer;
ColorF color;
ColorI iColor;
vertexBuffer.setSize(sel.size() * 4);
if(mRenderVertexSelection)
{
for(U32 i = 0; i < sel.size(); i++)
{
Point3F wPos;
bool center = gridToWorld(sel[i].mGridPos, wPos);
if(center)
{
if(sel[i].mWeight < 0.f || sel[i].mWeight > 1.f)
color = inColorFull;
else
color.interpolate(inColorNone, inColorFull, sel[i].mWeight);
}
else
{
if(sel[i].mWeight < 0.f || sel[i].mWeight > 1.f)
color = outColorFull;
else
color.interpolate(outColorFull, outColorNone, sel[i].mWeight);
}
//
iColor = color;
TerrVertex *verts = &(vertexBuffer[i * 4]);
verts[0].point = wPos + Point3F(-1, -1, 0);
verts[0].color = iColor;
verts[1].point = wPos + Point3F( 1, -1, 0);
verts[1].color = iColor;
verts[2].point = wPos + Point3F( 1, 1, 0);
verts[2].color = iColor;
verts[3].point = wPos + Point3F(-1, 1, 0);
verts[3].color = iColor;
}
}
else
{
// walk the points in the selection
for(U32 i = 0; i < sel.size(); i++)
{
Point2I gPos = sel[i].mGridPos;
TerrVertex *verts = &(vertexBuffer[i * 4]);
bool center = gridToWorld(gPos, verts[0].point);
gridToWorld(Point2I(gPos.x + 1, gPos.y), verts[1].point);
gridToWorld(Point2I(gPos.x + 1, gPos.y + 1), verts[2].point);
gridToWorld(Point2I(gPos.x, gPos.y + 1), verts[3].point);
if(center)
{
if(sel[i].mWeight < 0.f || sel[i].mWeight > 1.f)
color = inColorFull;
else
color.interpolate(inColorNone, inColorFull, sel[i].mWeight);
}
else
{
if(sel[i].mWeight < 0.f || sel[i].mWeight > 1.f)
color = outColorFull;
else
color.interpolate(outColorFull, outColorNone, sel[i].mWeight);
}
iColor = color;
verts[0].color = iColor;
verts[1].color = iColor;
verts[2].color = iColor;
verts[3].color = iColor;
}
}
//
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, sizeof(TerrVertex), &(vertexBuffer[0]));
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(TerrVertex), &(vertexBuffer[0].color));
if (dglDoesSupportCompiledVertexArray())
glLockArraysEXT(0, vertexBuffer.size());
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA);
if(renderFill)
glDrawArrays(GL_QUADS, 0, vertexBuffer.size());
glDisable(GL_BLEND);
if(renderFrame)
for(U32 i = 0; i < sel.size(); i++)
glDrawArrays(GL_LINE_LOOP, i * 4, 4);
if (dglDoesSupportCompiledVertexArray())
glUnlockArraysEXT();
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
}
//------------------------------------------------------------------------------
void TerrainEditor::renderBorder()
{
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Point2I pos(0,0);
Point2I dir[4] = {
Point2I(1,0),
Point2I(0,1),
Point2I(-1,0),
Point2I(0,-1)
};
//
if(mBorderLineMode)
{
glColor4ub(mBorderFrameColor.red, mBorderFrameColor.green, mBorderFrameColor.blue, mBorderFrameColor.alpha);
glBegin(GL_LINE_STRIP);
for(U32 i = 0; i < 4; i++)
{
for(U32 j = 0; j < TerrainBlock::BlockSize; j++)
{
Point3F wPos;
gridToWorld(pos, wPos);
glVertex3f(wPos.x, wPos.y, wPos.z);
pos += dir[i];
}
}
Point3F wPos;
gridToWorld(Point2I(0,0), wPos);
glVertex3f(wPos.x, wPos.y, wPos.z);
glEnd();
}
else
{
glEnable(GL_DEPTH_TEST);
GridSquare * gs = mTerrainBlock->findSquare(TerrainBlock::BlockShift, Point2I(0,0));
F32 height = F32(gs->maxHeight) * 0.03125f + mBorderHeight;
const MatrixF & mat = mTerrainBlock->getTransform();
Point3F pos;
mat.getColumn(3, &pos);
Point2F min(pos.x, pos.y);
Point2F max(pos.x + TerrainBlock::BlockSize * mTerrainBlock->getSquareSize(),
pos.y + TerrainBlock::BlockSize * mTerrainBlock->getSquareSize());
ColorI & a = mBorderFillColor;
ColorI & b = mBorderFrameColor;
for(U32 i = 0; i < 2; i++)
{
//
if(i){glColor4ub(a.red,a.green,a.blue,a.alpha);glBegin(GL_QUADS);} else {glColor4f(b.red,b.green,b.blue,b.alpha); glBegin(GL_LINE_LOOP);}
glVertex3f(min.x, min.y, 0);
glVertex3f(max.x, min.y, 0);
glVertex3f(max.x, min.y, height);
glVertex3f(min.x, min.y, height);
glEnd();
//
if(i){glColor4ub(a.red,a.green,a.blue,a.alpha);glBegin(GL_QUADS);} else {glColor4f(b.red,b.green,b.blue,b.alpha); glBegin(GL_LINE_LOOP);}
glVertex3f(min.x, max.y, 0);
glVertex3f(max.x, max.y, 0);
glVertex3f(max.x, max.y, height);
glVertex3f(min.x, max.y, height);
glEnd();
//
if(i){glColor4ub(a.red,a.green,a.blue,a.alpha);glBegin(GL_QUADS);} else {glColor4f(b.red,b.green,b.blue,b.alpha); glBegin(GL_LINE_LOOP);}
glVertex3f(min.x, min.y, 0);
glVertex3f(min.x, max.y, 0);
glVertex3f(min.x, max.y, height);
glVertex3f(min.x, min.y, height);
glEnd();
//
if(i){glColor4ub(a.red,a.green,a.blue,a.alpha);glBegin(GL_QUADS);} else {glColor4f(b.red,b.green,b.blue,b.alpha); glBegin(GL_LINE_LOOP);}
glVertex3f(max.x, min.y, 0);
glVertex3f(max.x, max.y, 0);
glVertex3f(max.x, max.y, height);
glVertex3f(max.x, min.y, height);
glEnd();
}
glDisable(GL_DEPTH_TEST);
}
glDisable(GL_BLEND);
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void TerrainEditor::addUndo(Vector<Selection *> & list, Selection * sel)
{
AssertFatal(sel!=NULL, "TerrainEditor::addUndo - invalid selection");
list.push_front(sel);
if(list.size() == mUndoLimit)
{
Selection * undo = list[list.size()-1];
delete undo;
list.pop_back();
}
setDirty();
}
void TerrainEditor::clearUndo(Vector<Selection *> & list)
{
for(U32 i = 0; i < list.size(); i++)
delete list[i];
list.clear();
}
bool TerrainEditor::processUndo(Vector<Selection *> & src, Vector<Selection *> & dest)
{
if(!src.size())
return(false);
Selection * task = src.front();
src.pop_front();
Selection * save = new Selection;
for(U32 i = 0; i < task->size(); i++)
{
GridInfo info;
getGridInfo((*task)[i].mGridPos, info);
save->add(info);
setGridInfo((*task)[i]);
}
gridUpdateComplete();
delete task;
addUndo(dest, save);
rebuild();
return(true);
}
//------------------------------------------------------------------------------
//
void TerrainEditor::rebuild()
{
// empty
if(mRebuildEmpty)
{
mTerrainBlock->rebuildEmptyFlags();
mTerrainBlock->packEmptySquares();
mRebuildEmpty = false;
}
}
//------------------------------------------------------------------------------
void TerrainEditor::on3DMouseUp(const Gui3DMouseEvent & event)
{
if(!mTerrainBlock)
return;
if(isMouseLocked())
{
mouseUnlock();
mMouseDownSeq++;
mCurrentAction->process(mMouseBrush, event, false, TerrainAction::End);
setCursor(0);
if(mUndoSel->size())
{
addUndo(mUndoList, mUndoSel);
clearUndo(mRedoList);
}
else
delete mUndoSel;
mUndoSel = 0;
mInAction = false;
rebuild();
}
}
class TerrainProcessActionEvent : public SimEvent
{
U32 mSequence;
public:
TerrainProcessActionEvent(U32 seq)
{
mSequence = seq;
}
void process(SimObject *object)
{
((TerrainEditor *) object)->processActionTick(mSequence);
}
};
void TerrainEditor::processActionTick(U32 sequence)
{
if(mMouseDownSeq == sequence)
{
Sim::postEvent(this, new TerrainProcessActionEvent(mMouseDownSeq), Sim::getCurrentTime() + 30);
mCurrentAction->process(mMouseBrush, mLastEvent, false, TerrainAction::Update);
}
}
void TerrainEditor::on3DMouseDown(const Gui3DMouseEvent & event)
{
if(!mTerrainBlock)
return;
mSelectionLocked = false;
mouseLock();
mMouseDownSeq++;
mUndoSel = new Selection;
mCurrentAction->process(mMouseBrush, event, true, TerrainAction::Begin);
// process on ticks - every 30th of a second.
Sim::postEvent(this, new TerrainProcessActionEvent(mMouseDownSeq), Sim::getCurrentTime() + 30);
}
void TerrainEditor::on3DMouseMove(const Gui3DMouseEvent & event)
{
if(!mTerrainBlock)
return;
Point3F pos;
if(!collide(event, pos))
{
mMouseBrush->reset();
mCursorVisible = true;
}
else
{
//
if(mRenderBrush)
mCursorVisible = false;
mMousePos = pos;
mMouseBrush->setPosition(mMousePos);
}
}
void TerrainEditor::on3DMouseDragged(const Gui3DMouseEvent & event)
{
if(!mTerrainBlock)
return;
if(!isMouseLocked())
return;
Point3F pos;
if(!mSelectionLocked)
{
if(!collide(event, pos))
{
mMouseBrush->reset();
return;
}
}
// check if the mouse has actually moved in grid space
bool selChanged = false;
if(!mSelectionLocked)
{
Point2I gMouse;
Point2I gLastMouse;
worldToGrid(pos, gMouse);
worldToGrid(mMousePos, gLastMouse);
//
mMousePos = pos;
mMouseBrush->setPosition(mMousePos);
selChanged = gMouse != gLastMouse;
}
if(selChanged)
mCurrentAction->process(mMouseBrush, event, true, TerrainAction::Update);
}
void TerrainEditor::getCursor(GuiCursor *&cursor, bool &visible, const GuiEvent &event)
{
event;
cursor = mCurrentCursor;
visible = mCursorVisible;
}
//------------------------------------------------------------------------------
// any console function which depends on a terrainBlock attached to the editor
// should call this
bool checkTerrainBlock(TerrainEditor * object, const char * funcName)
{
if(!object->terrainBlockValid())
{
Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::%s: not attached to a terrain block!", funcName);
return(false);
}
return(true);
}
//------------------------------------------------------------------------------
static void findObjectsCallback(SceneObject* obj, void *val)
{
Vector<SceneObject*> * list = (Vector<SceneObject*>*)val;
list->push_back(obj);
}
// XA: Methods added for interfacing with the consoleMethods.
void TerrainEditor::attachTerrain(TerrainBlock *terrBlock)
{
mTerrainBlock = terrBlock;
}
void TerrainEditor::setBrushType(const char* type)
{
if(!dStricmp(type, "box"))
{
delete mMouseBrush;
mMouseBrush = new BoxBrush(this);
}
else if(!dStricmp(type, "ellipse"))
{
delete mMouseBrush;
mMouseBrush = new EllipseBrush(this);
}
else if(!dStricmp(type, "selection"))
{
delete mMouseBrush;
mMouseBrush = new SelectionBrush(this);
}
else {}
}
void TerrainEditor::setBrushSize(S32 w, S32 h)
{
mBrushSize.set(w, h);
mMouseBrush->setSize(mBrushSize);
}
const char* TerrainEditor::getBrushPos()
{
AssertFatal(mMouseBrush!=NULL, "TerrainEditor::getBrushPos: no mouse brush!");
Point2I pos = mMouseBrush->getPosition();
char * ret = Con::getReturnBuffer(32);
dSprintf(ret, sizeof(ret), "%d %d", pos.x, pos.y);
return(ret);
}
void TerrainEditor::setBrushPos(Point2I pos)
{
AssertFatal(mMouseBrush!=NULL, "TerrainEditor::setBrushPos: no mouse brush!");
mMouseBrush->setPosition(pos);
}
void TerrainEditor::setAction(const char* action)
{
for(U32 i = 0; i < mActions.size(); i++)
{
if(!dStricmp(mActions[i]->getName(), action))
{
mCurrentAction = mActions[i];
//
mRenderBrush = mCurrentAction->useMouseBrush();
return;
}
}
}
const char* TerrainEditor::getActionName(U32 index)
{
if(index >= mActions.size())
return("");
return(mActions[index]->getName());
}
const char* TerrainEditor::getCurrentAction()
{
return(mCurrentAction->getName());
}
S32 TerrainEditor::getNumActions()
{
return(mActions.size());
}
void TerrainEditor::resetSelWeights(bool clear)
{
//
if(!clear)
{
for(U32 i = 0; i < mDefaultSel.size(); i++)
{
mDefaultSel[i].mPrimarySelect = false;
mDefaultSel[i].mWeight = 1.f;
}
return;
}
Selection sel;
U32 i;
for(i = 0; i < mDefaultSel.size(); i++)
{
if(mDefaultSel[i].mPrimarySelect)
{
mDefaultSel[i].mWeight = 1.f;
sel.add(mDefaultSel[i]);
}
}
mDefaultSel.reset();
for(i = 0; i < sel.size(); i++)
mDefaultSel.add(sel[i]);
}
void TerrainEditor::undo()
{
if(!checkTerrainBlock(this, "undoAction"))
return;
processUndo(mUndoList, mRedoList);
}
void TerrainEditor::redo()
{
if(!checkTerrainBlock(this, "redoAction"))
return;
processUndo(mRedoList, mUndoList);
}
void TerrainEditor::clearSelection()
{
mDefaultSel.reset();
}
void TerrainEditor::processAction(const char* sAction)
{
if(!checkTerrainBlock(this, "processAction"))
return;
TerrainAction * action = mCurrentAction;
if(sAction != "")
{
action = lookupAction(sAction);
if(!action)
{
Con::errorf(ConsoleLogEntry::General, "TerrainEditor::cProcessAction: invalid action name '%s'.", sAction);
return;
}
}
if(!getCurrentSel()->size() && !mProcessUsesBrush)
return;
mUndoSel = new Selection;
Gui3DMouseEvent event;
if(mProcessUsesBrush)
action->process(mMouseBrush, event, true, TerrainAction::Process);
else
action->process(getCurrentSel(), event, true, TerrainAction::Process);
rebuild();
// check if should delete the undo
if(mUndoSel->size())
{
addUndo(mUndoList, mUndoSel);
clearUndo(mRedoList);
}
else
delete mUndoSel;
mUndoSel = 0;
}
void TerrainEditor::buildMaterialMap()
{
if(!checkTerrainBlock(this, "buildMaterialMap"))
return;
mTerrainBlock->buildMaterialMap();
}
S32 TerrainEditor::getNumTextures()
{
if(!checkTerrainBlock(this, "getNumTextures"))
return(0);
// walk all the possible material lists and count them..
U32 count = 0;
for(U32 i = 0; i < TerrainBlock::MaterialGroups; i++)
if(mTerrainBlock->mMaterialFileName[i] &&
*mTerrainBlock->mMaterialFileName[i])
count++;
return count;
}
const char* TerrainEditor::getTextureName(S32 group)
{
if(!checkTerrainBlock(this, "getTextureName"))
return("");
// textures only exist on the client..
NetConnection * toServer = NetConnection::getConnectionToServer();
NetConnection * toClient = NetConnection::getLocalClientConnection();
S32 index = toClient->getGhostIndex(mTerrainBlock);
TerrainBlock * terrBlock = dynamic_cast<TerrainBlock*>(toServer->resolveGhost(index));
if(!terrBlock)
return("");
// possibly in range?
if(group < 0 || group >= TerrainBlock::MaterialGroups)
return("");
// now find the i-th group
U32 count = 0;
bool found = false;
for(U32 i = 0; !found && (i < TerrainBlock::MaterialGroups); i++)
{
// count it
if(terrBlock->mMaterialFileName[i] &&
*terrBlock->mMaterialFileName[i])
count++;
if((group + 1) == count)
{
group = i;
found = true;
}
}
if(!found)
return("");
return terrBlock->mMaterialFileName[group];
}
void TerrainEditor::markEmptySquares()
{
if(!checkTerrainBlock(this, "markEmptySquares"))
return;
// build a list of all the marked interiors
Vector<InteriorInstance*> interiors;
U32 mask = InteriorObjectType;
gServerContainer.findObjects(mask, findObjectsCallback, &interiors);
// walk the terrain and empty any grid which clips to an interior
for(U32 x = 0; x < TerrainBlock::BlockSize; x++)
for(U32 y = 0; y < TerrainBlock::BlockSize; y++)
{
TerrainBlock::Material * material = mTerrainBlock->getMaterial(x,y);
material->flags |= ~(TerrainBlock::Material::Empty);
Point3F a, b;
gridToWorld(Point2I(x,y), a);
gridToWorld(Point2I(x+1,y+1), b);
Box3F box;
box.min = a;
box.max = b;
box.min.setMin(b);
box.max.setMax(a);
const MatrixF & terrOMat = mTerrainBlock->getTransform();
const MatrixF & terrWMat = mTerrainBlock->getWorldTransform();
terrWMat.mulP(box.min);
terrWMat.mulP(box.max);
for(U32 i = 0; i < interiors.size(); i++)
{
MatrixF mat = interiors[i]->getWorldTransform();
mat.scale(interiors[i]->getScale());
mat.mul(terrOMat);
U32 waterMark = FrameAllocator::getWaterMark();
U16* zoneVector = (U16*)FrameAllocator::alloc(interiors[i]->getDetailLevel(0)->getNumZones());
U32 numZones = 0;
interiors[i]->getDetailLevel(0)->scanZones(box, mat,
zoneVector, &numZones);
if (numZones != 0)
{
Con::printf("%d %d", x, y);
material->flags |= TerrainBlock::Material::Empty;
FrameAllocator::setWaterMark(waterMark);
break;
}
FrameAllocator::setWaterMark(waterMark);
}
}
// rebuild stuff..
mTerrainBlock->buildGridMap();
mTerrainBlock->rebuildEmptyFlags();
mTerrainBlock->packEmptySquares();
}
void TerrainEditor::clearModifiedFlags()
{
if(!checkTerrainBlock(this, "clearModifiedFlags"))
return;
//
for(U32 i = 0; i < (TerrainBlock::BlockSize * TerrainBlock::BlockSize); i++)
mTerrainBlock->materialMap[i].flags &= ~TerrainBlock::Material::Modified;
}
void TerrainEditor::mirrorTerrain(S32 mirrorIndex)
{
if(!checkTerrainBlock(this, "mirrorTerrain"))
return;
TerrainBlock * terrain = mTerrainBlock;
setDirty();
//
enum {
top = BIT(0),
bottom = BIT(1),
left = BIT(2),
right = BIT(3)
};
U32 sides[8] =
{
bottom,
bottom | left,
left,
left | top,
top,
top | right,
right,
bottom | right
};
U32 n = TerrainBlock::BlockSize;
U32 side = sides[mirrorIndex % 8];
bool diag = mirrorIndex & 0x01;
Point2I src((side & right) ? (n - 1) : 0, (side & bottom) ? (n - 1) : 0);
Point2I dest((side & left) ? (n - 1) : 0, (side & top) ? (n - 1) : 0);
Point2I origSrc(src);
Point2I origDest(dest);
// determine the run length
U32 minStride = ((side & top) || (side & bottom)) ? n : n / 2;
U32 majStride = ((side & left) || (side & right)) ? n : n / 2;
Point2I srcStep((side & right) ? -1 : 1, (side & bottom) ? -1 : 1);
Point2I destStep((side & left) ? -1 : 1, (side & top) ? -1 : 1);
//
U16 * heights = terrain->getHeightAddress(0,0);
U8 * baseMaterials = terrain->getBaseMaterialAddress(0,0);
TerrainBlock::Material * materials = terrain->getMaterial(0,0);
// create an undo selection
Selection * undo = new Selection;
// walk through all the positions
for(U32 i = 0; i < majStride; i++)
{
for(U32 j = 0; j < minStride; j++)
{
// skip the same position
if(src != dest)
{
U32 si = src.x + (src.y << TerrainBlock::BlockShift);
U32 di = dest.x + (dest.y << TerrainBlock::BlockShift);
// add to undo selection
GridInfo info;
getGridInfo(dest, info);
undo->add(info);
//... copy info... (height, basematerial, material)
heights[di] = heights[si];
baseMaterials[di] = baseMaterials[si];
materials[di] = materials[si];
}
// get to the new position
src.x += srcStep.x;
diag ? (dest.y += destStep.y) : (dest.x += destStep.x);
}
// get the next position for a run
src.y += srcStep.y;
diag ? (dest.x += destStep.x) : (dest.y += destStep.y);
// reset the minor run
src.x = origSrc.x;
diag ? (dest.y = origDest.y) : (dest.x = origDest.x);
// shorten the run length for diag runs
if(diag)
minStride--;
}
// rebuild stuff..
terrain->buildGridMap();
terrain->rebuildEmptyFlags();
terrain->packEmptySquares();
// add undo selection to undo list and clear redo
addUndo(mUndoList, undo);
clearUndo(mRedoList);
}
void TerrainEditor::popBaseMaterialInfo()
{
if(!checkTerrainBlock(this, "popMaterialInfo"))
return;
if(!mBaseMaterialInfos.size())
return;
TerrainBlock * terrain = mTerrainBlock;
BaseMaterialInfo * info = mBaseMaterialInfos.front();
// names
for(U32 i = 0; i < TerrainBlock::MaterialGroups; i++)
terrain->mMaterialFileName[i] = info->mMaterialNames[i];
// base materials
dMemcpy(terrain->mBaseMaterialMap, info->mBaseMaterials,
TerrainBlock::BlockSize * TerrainBlock::BlockSize);
// kill it..
delete info;
mBaseMaterialInfos.pop_front();
// rebuild
terrain->refreshMaterialLists();
terrain->buildGridMap();
}
void TerrainEditor::pushBaseMaterialInfo()
{
if(!checkTerrainBlock(this, "pushMaterialInfo"))
return;
TerrainBlock * terrain = mTerrainBlock;
BaseMaterialInfo * info = new BaseMaterialInfo;
// copy the material list names
for(U32 i = 0; i < TerrainBlock::MaterialGroups; i++)
info->mMaterialNames[i] = terrain->mMaterialFileName[i];
// copy the base materials
dMemcpy(info->mBaseMaterials, terrain->mBaseMaterialMap,
TerrainBlock::BlockSize * TerrainBlock::BlockSize);
mBaseMaterialInfos.push_front(info);
}
void TerrainEditor::setLoneBaseMaterial(const char* materialListBaseName)
{
if(!checkTerrainBlock(this, "setLoneBaseMaterial"))
return;
TerrainBlock * terrain = mTerrainBlock;
// force the material group
terrain->mMaterialFileName[0] = StringTable->insert(materialListBaseName);
dMemset(terrain->getBaseMaterialAddress(0,0),
TerrainBlock::BlockSize * TerrainBlock::BlockSize, 0);
terrain->refreshMaterialLists();
terrain->buildGridMap();
}
//------------------------------------------------------------------------------
ConsoleMethod( TerrainEditor, attachTerrain, void, 2, 3, "(TerrainBlock terrain)")
{
TerrainBlock * terrBlock = 0;
SimSet * missionGroup = dynamic_cast<SimSet*>(Sim::findObject("MissionGroup"));
if(!missionGroup)
{
Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::attach: no mission group found");
return;
}
// attach to first found terrainBlock
if(argc == 2)
{
for(SimSetIterator itr(missionGroup); *itr; ++itr)
{
terrBlock = dynamic_cast<TerrainBlock*>(*itr);
if(terrBlock)
break;
}
if(!terrBlock)
Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::attach: no TerrainBlock objects found!");
}
else // attach to named object
{
terrBlock = dynamic_cast<TerrainBlock*>(Sim::findObject(argv[2]));
if(!terrBlock)
Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::attach: failed to attach to object '%s'", argv[2]);
}
if(terrBlock && !terrBlock->isServerObject())
{
Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::attach: cannot attach to client TerrainBlock");
terrBlock = 0;
}
object->attachTerrain(terrBlock);
}
ConsoleMethod( TerrainEditor, setBrushType, void, 3, 3, "(string type)"
"One of box, ellipse, selection.")
{
object->setBrushType(argv[2]);
}
ConsoleMethod( TerrainEditor, setBrushSize, void, 4, 4, "(int w, int h)")
{
S32 w = dAtoi(argv[2]);
S32 h = dAtoi(argv[3]);
//
if(w < 1 || w > Brush::MaxBrushDim || h < 1 || h > Brush::MaxBrushDim)
{
Con::errorf(ConsoleLogEntry::General, "TerrainEditor::cSetBrushSize: invalid brush dimension. [1-%d].", Brush::MaxBrushDim);
return;
}
object->setBrushSize(w, h);
}
ConsoleMethod( TerrainEditor, getBrushPos, const char*, 2, 2, "Returns a Point2I.")
{
return object->getBrushPos();
}
ConsoleMethod( TerrainEditor, setBrushPos, void, 3, 4, "(int x, int y)")
{
//
Point2I pos;
if(argc == 3)
dSscanf(argv[2], "%d %d", &pos.x, &pos.y);
else
{
pos.x = dAtoi(argv[2]);
pos.y = dAtoi(argv[3]);
}
object->setBrushPos(pos);
}
ConsoleMethod( TerrainEditor, setAction, void, 3, 3, "(string action_name)")
{
object->setAction(argv[2]);
}
ConsoleMethod( TerrainEditor, getActionName, const char*, 3, 3, "(int num)")
{
return (object->getActionName(dAtoi(argv[2])));
}
ConsoleMethod( TerrainEditor, getNumActions, S32, 2, 2, "")
{
return(object->getNumActions());
}
ConsoleMethod( TerrainEditor, getCurrentAction, const char*, 2, 2, "")
{
return object->getCurrentAction();
}
ConsoleMethod( TerrainEditor, resetSelWeights, void, 3, 3, "(bool clear)")
{
object->resetSelWeights(dAtob(argv[2]));
}
ConsoleMethod( TerrainEditor, undo, void, 2, 2, "")
{
object->undo();
}
ConsoleMethod( TerrainEditor, redo, void, 2, 2, "")
{
object->redo();
}
ConsoleMethod( TerrainEditor, clearSelection, void, 2, 2, "")
{
object->clearSelection();
}
ConsoleMethod( TerrainEditor, processAction, void, 2, 3, "(string action=NULL)")
{
if(argc == 3)
object->processAction(argv[2]);
else object->processAction("");
}
ConsoleMethod( TerrainEditor, buildMaterialMap, void, 2, 2, "")
{
object->buildMaterialMap();
}
ConsoleMethod( TerrainEditor, getNumTextures, S32, 2, 2, "")
{
return object->getNumTextures();
}
ConsoleMethod( TerrainEditor, getTextureName, const char*, 3, 3, "(int index)")
{
return object->getTextureName(dAtoi(argv[2]));
}
ConsoleMethod( TerrainEditor, markEmptySquares, void, 2, 2, "")
{
object->markEmptySquares();
}
ConsoleMethod( TerrainEditor, clearModifiedFlags, void, 2, 2, "")
{
object->clearModifiedFlags();
}
ConsoleMethod( TerrainEditor, mirrorTerrain, void, 3, 3, "")
{
object->mirrorTerrain(dAtoi(argv[2]));
}
ConsoleMethod(TerrainEditor, pushBaseMaterialInfo, void, 2, 2, "")
{
object->pushBaseMaterialInfo();
}
ConsoleMethod( TerrainEditor, popBaseMaterialInfo, void, 2, 2, "")
{
object->popBaseMaterialInfo();
}
ConsoleMethod( TerrainEditor, setLoneBaseMaterial, void, 3, 3, "(string materialListBaseName)")
{
object->setLoneBaseMaterial(argv[2]);
}
ConsoleMethod(TerrainEditor, setTerraformOverlay, void, 3, 3, "(bool overlayEnable) - sets the terraformer current heightmap to draw as an overlay over the current terrain.")
{
// XA: This one needs to be implemented :)
}
ConsoleMethod(TerrainEditor, setTerrainMaterials, void, 3, 3, "(string matList) sets the list of current terrain materials.")
{
TerrainEditor *tEditor = (TerrainEditor *) object;
TerrainBlock *terr = tEditor->getTerrainBlock();
if(!terr)
return;
Resource<TerrainFile> file = terr->getFile();
const char *fileList = argv[2];
for(U32 i = 0; i < TerrainBlock::MaterialGroups; i++)
{
U32 len;
const char *spos = dStrchr(fileList, '\n');
if(!spos)
len = dStrlen(fileList);
else
len = spos - fileList;
if(len)
file->mMaterialFileName[i] = StringTable->insertn(fileList, len, true);
else
file->mMaterialFileName[i] = 0;
fileList += len;
if(*fileList)
fileList++;
}
tEditor->getClientTerrain()->buildMaterialMap();
}
ConsoleMethod(TerrainEditor, getTerrainMaterials, const char *, 2, 2, "() gets the list of current terrain materials.")
{
char *ret = Con::getReturnBuffer(4096);
TerrainEditor *tEditor = (TerrainEditor *) object;
TerrainBlock *terr = tEditor->getTerrainBlock();
if(!terr)
return "";
ret[0] = 0;
Resource<TerrainFile> file = terr->getFile();
for(U32 i = 0; i < TerrainBlock::MaterialGroups; i++)
{
if(file->mMaterialFileName[i])
dStrcat(ret, file->mMaterialFileName[i]);
dStrcat(ret, "\n");
}
return ret;
}
//------------------------------------------------------------------------------
void TerrainEditor::initPersistFields()
{
Parent::initPersistFields();
addGroup("Misc");
addField("isDirty", TypeBool, Offset(mIsDirty, TerrainEditor));
addField("isMissionDirty", TypeBool, Offset(mIsMissionDirty, TerrainEditor));
addField("renderBorder", TypeBool, Offset(mRenderBorder, TerrainEditor));
addField("borderHeight", TypeF32, Offset(mBorderHeight, TerrainEditor));
addField("borderFillColor", TypeColorI, Offset(mBorderFillColor, TerrainEditor));
addField("borderFrameColor", TypeColorI, Offset(mBorderFrameColor, TerrainEditor));
addField("borderLineMode", TypeBool, Offset(mBorderLineMode, TerrainEditor));
addField("selectionHidden", TypeBool, Offset(mSelectionHidden, TerrainEditor));
addField("enableSoftBrushes", TypeBool, Offset(mEnableSoftBrushes, TerrainEditor));
addField("renderVertexSelection", TypeBool, Offset(mRenderVertexSelection, TerrainEditor));
addField("processUsesBrush", TypeBool, Offset(mProcessUsesBrush, TerrainEditor));
// action values...
addField("adjustHeightVal", TypeF32, Offset(mAdjustHeightVal, TerrainEditor));
addField("setHeightVal", TypeF32, Offset(mSetHeightVal, TerrainEditor));
addField("scaleVal", TypeF32, Offset(mScaleVal, TerrainEditor));
addField("smoothFactor", TypeF32, Offset(mSmoothFactor, TerrainEditor));
addField("materialGroup", TypeS32, Offset(mMaterialGroup, TerrainEditor));
addField("softSelectRadius", TypeF32, Offset(mSoftSelectRadius, TerrainEditor));
addField("softSelectFilter", TypeString, Offset(mSoftSelectFilter, TerrainEditor));
addField("softSelectDefaultFilter", TypeString, Offset(mSoftSelectDefaultFilter, TerrainEditor));
addField("adjustHeightMouseScale", TypeF32, Offset(mAdjustHeightMouseScale, TerrainEditor));
addField("paintMaterial", TypeCaseString, Offset(mPaintMaterial, TerrainEditor));
endGroup("Misc");
}