1870 lines
50 KiB
C++
Executable File
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");
|
|
}
|