mirror of
https://github.com/tribes2/engine.git
synced 2026-01-20 11:44:46 +00:00
1704 lines
49 KiB
C++
1704 lines
49 KiB
C++
//-----------------------------------------------------------------------------
|
|
// V12 Engine
|
|
//
|
|
// Copyright (c) 2001 GarageGames.Com
|
|
// Portions Copyright (c) 2001 by Sierra Online, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "editor/terrainEditor.h"
|
|
#include "game/collisionTest.h"
|
|
#include "terrain/terrData.h"
|
|
#include "gui/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 "sim/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();
|
|
}
|
|
|
|
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();
|
|
|
|
//
|
|
for(U32 x = 0; x < mSize.x; x++)
|
|
for(U32 y = 0; y < mSize.y; y++)
|
|
{
|
|
GridInfo info;
|
|
mTerrainEditor->getGridInfo(Point2I(mGridPos.x + x - (mSize.x / 2), mGridPos.y + y - (mSize.y / 2)), info);
|
|
push_back(info);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void EllipseBrush::rebuild()
|
|
{
|
|
reset();
|
|
Point3F center(F32(mSize.x) / 2, F32(mSize.y) / 2, 0);
|
|
|
|
for(U32 x = 0; x < mSize.x; x++)
|
|
for(U32 y = 0; y < mSize.y; y++)
|
|
{
|
|
F32 a = mSize.x >= mSize.y ? F32(mSize.x) / 2 : F32(mSize.y) / 2;
|
|
F32 b = mSize.x >= mSize.y ? F32(mSize.y) / 2 : F32(mSize.x) / 2;
|
|
Point3F dir(mSize.x >= mSize.y ? 1 : 0, mSize.x >= mSize.y ? 0 : 1, 0);
|
|
|
|
// first do quick check on minor
|
|
Point3F pos(F32(x) + 0.5, F32(y) + 0.5, 0);
|
|
Point3F vec = pos - center;
|
|
F32 len = vec.len();
|
|
|
|
bool addPoint = false;
|
|
if(len <= b)
|
|
addPoint = true;
|
|
else
|
|
{
|
|
F32 theta = mAcos(mDot(vec, dir));
|
|
F32 as = a*a;
|
|
F32 bs = b*b;
|
|
|
|
F32 r = mSqrt((bs * as) / ((bs * (1+2*mCos(theta))) + (as * (1-2*mCos(theta)))));
|
|
if(len <= r)
|
|
addPoint = true;
|
|
}
|
|
|
|
if(addPoint)
|
|
{
|
|
GridInfo info;
|
|
mTerrainEditor->getGridInfo(Point2I(mGridPos.x + x, mGridPos.y + y), info);
|
|
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)
|
|
{
|
|
VECTOR_SET_ASSOCIATION(mActions);
|
|
VECTOR_SET_ASSOCIATION(mUndoList);
|
|
VECTOR_SET_ASSOCIATION(mRedoList);
|
|
VECTOR_SET_ASSOCIATION(mBaseMaterialInfos);
|
|
|
|
//
|
|
resetCurrentSel();
|
|
|
|
//
|
|
mBrushSize.set(2,2);
|
|
mMouseBrush = new BoxBrush(this);
|
|
|
|
// 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 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;
|
|
|
|
//
|
|
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("Editor_ArrowCursor");
|
|
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)
|
|
{
|
|
Canvas->setCursor(cursor ? cursor : mDefaultCursor);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
TerrainBlock * TerrainEditor::getClientTerrain()
|
|
{
|
|
// do the client..
|
|
|
|
NetConnection * toServer = NetConnection::getServerConnection();
|
|
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);
|
|
|
|
float x = (wPos.x - origin.x) / (float)mTerrainBlock->getSquareSize();
|
|
float y = (wPos.y - origin.y) / (float)mTerrainBlock->getSquareSize();
|
|
|
|
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;
|
|
bool center = worldToGrid(wPos, gPos);
|
|
|
|
//
|
|
info.mGridPos = gPos;
|
|
info.mMaterial = getGridMaterial(gPos);
|
|
info.mHeight = getGridHeight(gPos);
|
|
info.mMaterialGroup = getGridMaterialGroup(gPos);
|
|
info.mWeight = 1.f;
|
|
info.mPrimarySelect = true;
|
|
|
|
return(center);
|
|
}
|
|
|
|
bool TerrainEditor::getGridInfo(const Point2I & gPos, GridInfo & info)
|
|
{
|
|
//
|
|
info.mGridPos = gPos;
|
|
info.mMaterial = getGridMaterial(gPos);
|
|
info.mHeight = getGridHeight(gPos);
|
|
info.mMaterialGroup = getGridMaterialGroup(gPos);
|
|
info.mWeight = 1.f;
|
|
info.mPrimarySelect = true;
|
|
|
|
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);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
F32 TerrainEditor::getGridHeight(const Point2I & gPos)
|
|
{
|
|
Point2I cPos;
|
|
gridToCenter(gPos, cPos);
|
|
return(fixedToFloat(mTerrainBlock->getHeight(cPos.x, cPos.y)));
|
|
}
|
|
|
|
void TerrainEditor::setGridHeight(const Point2I & gPos, const F32 height)
|
|
{
|
|
Point2I cPos;
|
|
gridToCenter(gPos, cPos);
|
|
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 %f %d %f",
|
|
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)
|
|
renderSelection(*mMouseBrush, ColorF(1,0,0), ColorF(0,1,0), ColorF(0,0,1), ColorF(0,0,1), false, true);
|
|
|
|
if(mRenderBorder)
|
|
renderBorder();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void TerrainEditor::renderSelection( const Selection & sel, const ColorF & inColorFull, const ColorF & inColorNone, const ColorF & outColorFull, const ColorF & outColorNone, bool renderFill, bool renderFrame )
|
|
{
|
|
for(U32 i = 0; i < sel.size(); i++)
|
|
{
|
|
Point3F wPos;
|
|
bool center = gridToWorld(sel[i].mGridPos, wPos);
|
|
|
|
ColorF color;
|
|
if(center)
|
|
{
|
|
if(sel[i].mWeight < 0.f || sel[i].mWeight > 1.f)
|
|
color = inColorFull;
|
|
else
|
|
{
|
|
Point3F pnt;
|
|
pnt.interpolate(Point3F(inColorNone.red, inColorNone.green, inColorNone.blue),
|
|
Point3F(inColorFull.red, inColorFull.green, inColorFull.blue),
|
|
sel[i].mWeight);
|
|
color.set(pnt.x, pnt.y, pnt.z);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(sel[i].mWeight < 0.f || sel[i].mWeight > 1.f)
|
|
color = outColorFull;
|
|
else
|
|
{
|
|
Point3F pnt;
|
|
pnt.interpolate(Point3F(outColorFull.red, outColorFull.green, outColorFull.blue),
|
|
Point3F(outColorNone.red, outColorNone.green, outColorNone.blue), sel[i].mWeight);
|
|
color.set(pnt.x, pnt.y, pnt.z);
|
|
}
|
|
}
|
|
|
|
if(mRenderVertexSelection)
|
|
{
|
|
//
|
|
if(renderFill)
|
|
{
|
|
glDisable(GL_CULL_FACE);
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA);
|
|
|
|
glBegin(GL_QUADS);
|
|
glColor3f(color.red, color.green, color.blue);
|
|
glVertex3f(wPos.x - 1, wPos.y - 1, wPos.z);
|
|
glVertex3f(wPos.x + 1, wPos.y - 1, wPos.z);
|
|
glVertex3f(wPos.x + 1, wPos.y + 1, wPos.z);
|
|
glVertex3f(wPos.x - 1, wPos.y + 1, wPos.z);
|
|
glEnd();
|
|
|
|
glDisable(GL_BLEND);
|
|
}
|
|
|
|
if(renderFrame)
|
|
{
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
|
glBegin(GL_QUADS);
|
|
glColor3f(color.red, color.green, color.blue);
|
|
glVertex3f(wPos.x - 1, wPos.y - 1, wPos.z);
|
|
glVertex3f(wPos.x + 1, wPos.y - 1, wPos.z);
|
|
glVertex3f(wPos.x + 1, wPos.y + 1, wPos.z);
|
|
glVertex3f(wPos.x - 1, wPos.y + 1, wPos.z);
|
|
glEnd();
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// walk the points in the selection
|
|
for(U32 i = 0; i < sel.size(); i++)
|
|
{
|
|
Point3F wPos[4];
|
|
Point2I gPos = sel[i].mGridPos;
|
|
|
|
bool center = gridToWorld(gPos, wPos[0]);
|
|
gridToWorld(Point2I(gPos.x + 1, gPos.y), wPos[1]);
|
|
gridToWorld(Point2I(gPos.x + 1, gPos.y + 1), wPos[2]);
|
|
gridToWorld(Point2I(gPos.x, gPos.y + 1), wPos[3]);
|
|
|
|
//
|
|
glDisable(GL_CULL_FACE);
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA);
|
|
|
|
if(renderFill)
|
|
{
|
|
glBegin(GL_QUADS);
|
|
glColor3f(color.red, color.green, color.blue);
|
|
for(U32 i = 0; i < 4; i++)
|
|
glVertex3f(wPos[i].x, wPos[i].y, wPos[i].z);
|
|
glEnd();
|
|
}
|
|
|
|
glDisable(GL_BLEND);
|
|
|
|
if(renderFrame)
|
|
{
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
|
glBegin(GL_QUADS);
|
|
glColor3f(color.red, color.green, color.blue);
|
|
for(U32 k = 0; k < 4; k++)
|
|
glVertex3f(wPos[k].x, wPos[k].y, wPos[k].z);
|
|
glEnd();
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
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, "TerrainEditor::addUndo - invalid selection");
|
|
list.push_front(sel);
|
|
if(list.size() == mUndoLimit)
|
|
{
|
|
Selection * undo = list[list.size()-1];
|
|
delete undo;
|
|
list.pop_back();
|
|
}
|
|
}
|
|
|
|
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]);
|
|
}
|
|
|
|
delete task;
|
|
addUndo(dest, save);
|
|
|
|
rebuild();
|
|
|
|
return(true);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
void TerrainEditor::rebuild()
|
|
{
|
|
// empty
|
|
if(mRebuildEmpty)
|
|
{
|
|
mTerrainBlock->rebuildEmptyFlags();
|
|
mTerrainBlock->packEmptySquares();
|
|
mRebuildEmpty = false;
|
|
}
|
|
|
|
// base texture gruop
|
|
if(mRebuildTextures)
|
|
{
|
|
mRebuildTextures = false;
|
|
|
|
TerrainBlock * clientTerrain = getClientTerrain();
|
|
if(clientTerrain)
|
|
clientTerrain->buildMipMap();
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void TerrainEditor::on3DMouseUp(const Gui3DMouseEvent & event)
|
|
{
|
|
if(!mTerrainBlock)
|
|
return;
|
|
|
|
if(mInAction)
|
|
{
|
|
mouseUnlock();
|
|
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();
|
|
}
|
|
}
|
|
|
|
void TerrainEditor::on3DMouseDown(const Gui3DMouseEvent & event)
|
|
{
|
|
if(!mTerrainBlock)
|
|
return;
|
|
|
|
if(mInAction || mUndoSel)
|
|
return;
|
|
|
|
mSelectionLocked = false;
|
|
|
|
mouseLock();
|
|
mInAction = true;
|
|
mUndoSel = new Selection;
|
|
mCurrentAction->process(mMouseBrush, event, true, TerrainAction::Begin);
|
|
}
|
|
|
|
void TerrainEditor::on3DMouseMove(const Gui3DMouseEvent & event)
|
|
{
|
|
if(!mTerrainBlock)
|
|
return;
|
|
|
|
Point3F pos;
|
|
if(!collide(event, pos))
|
|
{
|
|
mInAction = false;
|
|
mMouseBrush->reset();
|
|
Canvas->showCursor(true);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
if(mRenderBrush)
|
|
Canvas->showCursor(false);
|
|
mMousePos = pos;
|
|
|
|
mMouseBrush->setPosition(mMousePos);
|
|
}
|
|
}
|
|
|
|
void TerrainEditor::on3DMouseDragged(const Gui3DMouseEvent & event)
|
|
{
|
|
if(!mTerrainBlock)
|
|
return;
|
|
|
|
if(!mInAction)
|
|
return;
|
|
|
|
Point3F pos;
|
|
if(!mSelectionLocked)
|
|
{
|
|
if(!collide(event, pos))
|
|
{
|
|
mMouseBrush->reset();
|
|
Canvas->showCursor(true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(mRenderBrush)
|
|
Canvas->showCursor(false);
|
|
|
|
// 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;
|
|
}
|
|
|
|
mCurrentAction->process(mMouseBrush, event, selChanged, TerrainAction::Update);
|
|
}
|
|
|
|
void TerrainEditor::on3DMouseEnter(const Gui3DMouseEvent &)
|
|
{
|
|
if(!mTerrainBlock)
|
|
return;
|
|
|
|
if(mRenderBrush)
|
|
Canvas->showCursor(false);
|
|
}
|
|
|
|
void TerrainEditor::on3DMouseLeave(const Gui3DMouseEvent &)
|
|
{
|
|
if(!mTerrainBlock)
|
|
return;
|
|
|
|
mInAction = false;
|
|
Canvas->showCursor(true);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// any console function which depends on a terrainBlock attached to the editor
|
|
// should call this
|
|
bool checkTerrainBlock(TerrainEditor * terrainEditor, const char * funcName)
|
|
{
|
|
if(!terrainEditor->terrainBlockValid())
|
|
{
|
|
Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::%s: not attached to a terrain block!", funcName);
|
|
return(false);
|
|
}
|
|
return(true);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static void cAttachTerrain(SimObject * obj, S32 argc, const char ** argv)
|
|
{
|
|
TerrainEditor * terrainEditor = static_cast<TerrainEditor*>(obj);
|
|
|
|
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;
|
|
}
|
|
|
|
//
|
|
terrainEditor->mTerrainBlock = terrBlock;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static void cSetBrushType(SimObject * obj, S32, const char **argv)
|
|
{
|
|
TerrainEditor * terrainEditor = static_cast<TerrainEditor*>(obj);
|
|
|
|
if(!dStricmp(argv[2], "box"))
|
|
{
|
|
delete terrainEditor->mMouseBrush;
|
|
terrainEditor->mMouseBrush = new BoxBrush(terrainEditor);
|
|
}
|
|
else if(!dStricmp(argv[2], "ellipse"))
|
|
{
|
|
delete terrainEditor->mMouseBrush;
|
|
terrainEditor->mMouseBrush = new EllipseBrush(terrainEditor);
|
|
}
|
|
else if(!dStricmp(argv[2], "selection"))
|
|
{
|
|
delete terrainEditor->mMouseBrush;
|
|
terrainEditor->mMouseBrush = new SelectionBrush(terrainEditor);
|
|
}
|
|
else {}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static void cSetBrushSize(SimObject * obj, S32, const char **argv)
|
|
{
|
|
TerrainEditor * terrainEditor = static_cast<TerrainEditor*>(obj);
|
|
|
|
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;
|
|
}
|
|
|
|
terrainEditor->mBrushSize.set(w, h);
|
|
terrainEditor->mMouseBrush->setSize(terrainEditor->mBrushSize);
|
|
}
|
|
|
|
static const char * cGetBrushPos(SimObject * obj, S32, const char **)
|
|
{
|
|
TerrainEditor * tEditor = static_cast<TerrainEditor*>(obj);
|
|
AssertFatal(tEditor->mMouseBrush, "TerrainEditor::cGetBrushPos: no mouse brush!");
|
|
|
|
Point2I pos = tEditor->mMouseBrush->getPosition();
|
|
char * ret = Con::getReturnBuffer(32);
|
|
dSprintf(ret, sizeof(ret), "%d %d", pos.x, pos.y);
|
|
return(ret);
|
|
}
|
|
|
|
static void cSetBrushPos(SimObject * obj, S32 argc, const char ** argv)
|
|
{
|
|
TerrainEditor * tEditor = static_cast<TerrainEditor*>(obj);
|
|
|
|
//
|
|
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]);
|
|
}
|
|
|
|
AssertFatal(tEditor->mMouseBrush, "TerrainEditor::cSetBrushPos: no mouse brush!");
|
|
tEditor->mMouseBrush->setPosition(pos);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static void cSetAction(SimObject * obj, S32, const char **argv)
|
|
{
|
|
TerrainEditor * terrainEditor = static_cast<TerrainEditor*>(obj);
|
|
|
|
for(U32 i = 0; i < terrainEditor->mActions.size(); i++)
|
|
{
|
|
if(!dStricmp(terrainEditor->mActions[i]->getName(), argv[2]))
|
|
{
|
|
terrainEditor->mCurrentAction = terrainEditor->mActions[i];
|
|
|
|
//
|
|
terrainEditor->mRenderBrush = terrainEditor->mCurrentAction->useMouseBrush();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static const char * cGetActionName(SimObject * obj, S32, const char ** argv)
|
|
{
|
|
TerrainEditor * terrainEditor = static_cast<TerrainEditor*>(obj);
|
|
U32 index = dAtoi(argv[2]);
|
|
if(index >= terrainEditor->mActions.size())
|
|
return("");
|
|
return(terrainEditor->mActions[index]->getName());
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static S32 cGetNumActions(SimObject * obj, S32, const char **)
|
|
{
|
|
TerrainEditor * terrainEditor = static_cast<TerrainEditor*>(obj);
|
|
return terrainEditor->mActions.size();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static const char * cGetCurrentAction(SimObject * obj, S32, const char **)
|
|
{
|
|
TerrainEditor *terrainEditor = static_cast<TerrainEditor*>(obj);
|
|
return(terrainEditor->mCurrentAction->getName());
|
|
}
|
|
|
|
static void cResetSelWeights(SimObject * obj, S32, const char ** argv)
|
|
{
|
|
TerrainEditor * tEditor = static_cast<TerrainEditor*>(obj);
|
|
|
|
bool clear = dAtob(argv[2]);
|
|
|
|
//
|
|
if(!clear)
|
|
{
|
|
for(U32 i = 0; i < tEditor->mDefaultSel.size(); i++)
|
|
{
|
|
tEditor->mDefaultSel[i].mPrimarySelect = false;
|
|
tEditor->mDefaultSel[i].mWeight = 1.f;
|
|
}
|
|
return;
|
|
}
|
|
|
|
Selection sel;
|
|
|
|
U32 i;
|
|
for(i = 0; i < tEditor->mDefaultSel.size(); i++)
|
|
{
|
|
if(tEditor->mDefaultSel[i].mPrimarySelect)
|
|
{
|
|
tEditor->mDefaultSel[i].mWeight = 1.f;
|
|
sel.add(tEditor->mDefaultSel[i]);
|
|
}
|
|
}
|
|
|
|
tEditor->mDefaultSel.reset();
|
|
|
|
for(i = 0; i < sel.size(); i++)
|
|
tEditor->mDefaultSel.add(sel[i]);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static void cUndoAction(SimObject * obj, S32, const char **)
|
|
{
|
|
TerrainEditor * terrainEditor = static_cast<TerrainEditor*>(obj);
|
|
if(!checkTerrainBlock(terrainEditor, "undoAction"))
|
|
return;
|
|
|
|
terrainEditor->processUndo(terrainEditor->mUndoList, terrainEditor->mRedoList);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static void cRedoAction(SimObject * obj, S32, const char **)
|
|
{
|
|
TerrainEditor * terrainEditor = static_cast<TerrainEditor*>(obj);
|
|
if(!checkTerrainBlock(terrainEditor, "redoAction"))
|
|
return;
|
|
|
|
terrainEditor->processUndo(terrainEditor->mRedoList, terrainEditor->mUndoList);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static void cClearSelection(SimObject * obj, S32, const char **)
|
|
{
|
|
TerrainEditor * terrainEditor = static_cast<TerrainEditor*>(obj);
|
|
terrainEditor->mDefaultSel.reset();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static void cProcessAction(SimObject * obj, S32 argc, const char ** argv)
|
|
{
|
|
TerrainEditor * terrainEditor = static_cast<TerrainEditor*>(obj);
|
|
if(!checkTerrainBlock(terrainEditor, "processAction"))
|
|
return;
|
|
|
|
TerrainAction * action = terrainEditor->mCurrentAction;
|
|
if(argc == 3)
|
|
{
|
|
action = terrainEditor->lookupAction(argv[2]);
|
|
|
|
if(!action)
|
|
{
|
|
Con::errorf(ConsoleLogEntry::General, "TerrainEditor::cProcessAction: invalid action name '%s'.", argv[2]);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(!terrainEditor->getCurrentSel()->size() && !terrainEditor->mProcessUsesBrush)
|
|
return;
|
|
|
|
terrainEditor->mUndoSel = new Selection;
|
|
|
|
Gui3DMouseEvent event;
|
|
if(terrainEditor->mProcessUsesBrush)
|
|
action->process(terrainEditor->mMouseBrush, event, true, TerrainAction::Process);
|
|
else
|
|
action->process(terrainEditor->getCurrentSel(), event, true, TerrainAction::Process);
|
|
|
|
terrainEditor->rebuild();
|
|
|
|
// check if should delete the undo
|
|
if(terrainEditor->mUndoSel->size())
|
|
{
|
|
terrainEditor->addUndo(terrainEditor->mUndoList, terrainEditor->mUndoSel);
|
|
terrainEditor->clearUndo(terrainEditor->mRedoList);
|
|
}
|
|
else
|
|
delete terrainEditor->mUndoSel;
|
|
|
|
terrainEditor->mUndoSel = 0;
|
|
}
|
|
|
|
static void cBuildMaterialMap(SimObject * obj, S32, const char **)
|
|
{
|
|
TerrainEditor * terrainEditor = static_cast<TerrainEditor*>(obj);
|
|
if(!checkTerrainBlock(terrainEditor, "buildMaterialMap"))
|
|
return;
|
|
terrainEditor->mTerrainBlock->buildMaterialMap();
|
|
}
|
|
|
|
static S32 cGetNumTextures(SimObject * obj, S32, const char **)
|
|
{
|
|
TerrainEditor * terrainEditor = static_cast<TerrainEditor*>(obj);
|
|
if(!checkTerrainBlock(terrainEditor, "getNumTextures"))
|
|
return(0);
|
|
|
|
// walk all the possible material lists and count them..
|
|
U32 count = 0;
|
|
for(U32 i = 0; i < TerrainBlock::MaterialGroups; i++)
|
|
if(terrainEditor->mTerrainBlock->mMaterialFileName[i] &&
|
|
*terrainEditor->mTerrainBlock->mMaterialFileName[i])
|
|
count++;
|
|
return count;
|
|
}
|
|
|
|
static const char * cGetTextureName(SimObject * obj, S32, const char ** argv)
|
|
{
|
|
TerrainEditor * terrainEditor = static_cast<TerrainEditor*>(obj);
|
|
if(!checkTerrainBlock(terrainEditor, "getTextureName"))
|
|
return("");
|
|
|
|
// textures only exist on the client..
|
|
NetConnection * toServer = NetConnection::getServerConnection();
|
|
NetConnection * toClient = NetConnection::getLocalClientConnection();
|
|
|
|
S32 index = toClient->getGhostIndex(terrainEditor->mTerrainBlock);
|
|
|
|
TerrainBlock * terrBlock = dynamic_cast<TerrainBlock*>(toServer->resolveGhost(index));
|
|
if(!terrBlock)
|
|
return("");
|
|
|
|
// possibly in range?
|
|
S32 group = dAtoi(argv[2]);
|
|
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("");
|
|
|
|
char *retBuffer = Con::getReturnBuffer(256);
|
|
dSprintf(retBuffer, 256, "terrain/%s", terrBlock->mMaterialFileName[group]);
|
|
return retBuffer;
|
|
}
|
|
|
|
static void findObjectsCallback(SceneObject* obj, S32 val)
|
|
{
|
|
Vector<SceneObject*> * list = (Vector<SceneObject*>*)val;
|
|
list->push_back(obj);
|
|
}
|
|
|
|
static void cMarkEmptySquares(SimObject * obj, S32, const char **)
|
|
{
|
|
TerrainEditor * terrainEditor = static_cast<TerrainEditor*>(obj);
|
|
if(!checkTerrainBlock(terrainEditor, "markEmptySquares"))
|
|
return;
|
|
|
|
// build a list of all the marked interiors
|
|
Vector<InteriorInstance*> interiors;
|
|
U32 mask = InteriorObjectType;
|
|
gServerContainer.findObjects(mask, findObjectsCallback, (S32)&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 = terrainEditor->mTerrainBlock->getMaterial(x,y);
|
|
material->flags |= ~(TerrainBlock::Material::Empty);
|
|
|
|
Point3F a, b;
|
|
terrainEditor->gridToWorld(Point2I(x,y), a);
|
|
terrainEditor->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 = terrainEditor->mTerrainBlock->getTransform();
|
|
const MatrixF & terrWMat = terrainEditor->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..
|
|
terrainEditor->mTerrainBlock->buildGridMap();
|
|
terrainEditor->mTerrainBlock->rebuildEmptyFlags();
|
|
terrainEditor->mTerrainBlock->packEmptySquares();
|
|
}
|
|
|
|
static void cClearModifiedFlags(SimObject * obj, S32, const char **)
|
|
{
|
|
TerrainEditor * terrainEditor = static_cast<TerrainEditor*>(obj);
|
|
if(!checkTerrainBlock(terrainEditor, "clearModifiedFlags"))
|
|
return;
|
|
|
|
//
|
|
for(U32 i = 0; i < (TerrainBlock::BlockSize * TerrainBlock::BlockSize); i++)
|
|
terrainEditor->mTerrainBlock->materialMap[i].flags &= ~TerrainBlock::Material::Modified;
|
|
}
|
|
|
|
static void cMirrorTerrain(SimObject * obj, S32, const char ** argv)
|
|
{
|
|
TerrainEditor * terrainEditor = static_cast<TerrainEditor*>(obj);
|
|
if(!checkTerrainBlock(terrainEditor, "mirrorTerrain"))
|
|
return;
|
|
|
|
TerrainBlock * terrain = terrainEditor->mTerrainBlock;
|
|
S32 mirrorIndex = dAtoi(argv[2]);
|
|
|
|
//
|
|
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;
|
|
terrainEditor->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
|
|
terrainEditor->addUndo(terrainEditor->mUndoList, undo);
|
|
terrainEditor->clearUndo(terrainEditor->mRedoList);
|
|
}
|
|
|
|
static void cPushBaseMaterialInfo(SimObject * obj, S32, const char **)
|
|
{
|
|
TerrainEditor * terrainEditor = static_cast<TerrainEditor*>(obj);
|
|
if(!checkTerrainBlock(terrainEditor, "pushMaterialInfo"))
|
|
return;
|
|
|
|
TerrainBlock * terrain = terrainEditor->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);
|
|
|
|
terrainEditor->mBaseMaterialInfos.push_front(info);
|
|
}
|
|
|
|
static void cPopBaseMaterialInfo(SimObject * obj, S32, const char **)
|
|
{
|
|
TerrainEditor * terrainEditor = static_cast<TerrainEditor*>(obj);
|
|
if(!checkTerrainBlock(terrainEditor, "popMaterialInfo"))
|
|
return;
|
|
|
|
if(!terrainEditor->mBaseMaterialInfos.size())
|
|
return;
|
|
|
|
TerrainBlock * terrain = terrainEditor->mTerrainBlock;
|
|
|
|
BaseMaterialInfo * info = terrainEditor->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;
|
|
terrainEditor->mBaseMaterialInfos.pop_front();
|
|
|
|
// rebuild
|
|
terrain->refreshMaterialLists();
|
|
terrain->buildGridMap();
|
|
}
|
|
|
|
static void cSetLoneBaseMaterial(SimObject * obj, S32, const char ** argv)
|
|
{
|
|
TerrainEditor * terrainEditor = static_cast<TerrainEditor*>(obj);
|
|
if(!checkTerrainBlock(terrainEditor, "setLoneBaseMaterial"))
|
|
return;
|
|
|
|
TerrainBlock * terrain = terrainEditor->mTerrainBlock;
|
|
|
|
// force the material group
|
|
terrain->mMaterialFileName[0] = StringTable->insert(argv[2]);
|
|
dMemset(terrain->getBaseMaterialAddress(0,0),
|
|
TerrainBlock::BlockSize * TerrainBlock::BlockSize, 0);
|
|
|
|
terrain->refreshMaterialLists();
|
|
terrain->buildGridMap();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void TerrainEditor::consoleInit()
|
|
{
|
|
Con::addCommand("TerrainEditor", "attachTerrain", cAttachTerrain, "terrainEditor.attachTerrain(<terrainObj>);", 2, 3);
|
|
Con::addCommand("TerrainEditor", "setBrushType", cSetBrushType, "terrainEditor.setBrushType(box | ellipse | ...);", 3, 3);
|
|
Con::addCommand("TerrainEditor", "setBrushSize", cSetBrushSize, "terrainEditor.setBrushSize(x, y);", 4, 4);
|
|
Con::addCommand("TerrainEditor", "getBrushPos", cGetBrushPos, "terrainEditor.getBrushPos();", 2, 2);
|
|
Con::addCommand("TerrainEditor", "setBrushPos", cSetBrushPos, "terrainEditor.setBrushPos(x, y);", 3, 4);
|
|
Con::addCommand("TerrainEditor", "setAction", cSetAction, "terrainEditor.setAction(action_name);", 3, 3);
|
|
Con::addCommand("TerrainEditor", "getNumActions", cGetNumActions, "terrainEditor.getNumActions();", 2, 2);
|
|
Con::addCommand("TerrainEditor", "getActionName", cGetActionName, "terrainEditor.getActionName(num);", 3, 3);
|
|
Con::addCommand("TerrainEditor", "getCurrentAction", cGetCurrentAction, "terrainEditor.getCurrentAction();", 2, 2);
|
|
Con::addCommand("TerrainEditor", "resetSelWeights", cResetSelWeights, "terrainEditor.resetSelWeights(clear);", 3, 3);
|
|
Con::addCommand("TerrainEditor", "undo", cUndoAction, "terrainEditor.undo();", 2, 2);
|
|
Con::addCommand("TerrainEditor", "redo", cRedoAction, "terrainEditor.redo();", 2, 2);
|
|
Con::addCommand("TerrainEditor", "clearSelection", cClearSelection, "terrainEditor.clearSelection();", 2, 2);
|
|
Con::addCommand("TerrainEditor", "processAction", cProcessAction, "terrainEditor.processAction(<action>);", 2, 3);
|
|
Con::addCommand("TerrainEditor", "buildMaterialMap", cBuildMaterialMap, "terrainEditor.buildMaterialMap();", 2, 2);
|
|
Con::addCommand("TerrainEditor", "getNumTextures", cGetNumTextures, "terrainEditor.getNumTextures();", 2, 2);
|
|
Con::addCommand("TerrainEditor", "getTextureName", cGetTextureName, "terrainEditor.getTextureName(index);", 3, 3);
|
|
Con::addCommand("TerrainEditor", "markEmptySquares", cMarkEmptySquares, "terrainEditor.markEmptySquares();", 2, 2);
|
|
Con::addCommand("TerrainEditor", "clearModifiedFlags", cClearModifiedFlags, "terrainEditor.clearModifiedFlags();", 2, 2);
|
|
Con::addCommand("TerrainEditor", "mirrorTerrain", cMirrorTerrain, "terrainEditor.mirrorTerrain(dest octant index);", 3, 3);
|
|
Con::addCommand("TerrainEditor", "pushBaseMaterialInfo", cPushBaseMaterialInfo, "terrainEditor.pushBaseMaterialInfo();", 2, 2);
|
|
Con::addCommand("TerrainEditor", "popBaseMaterialInfo", cPopBaseMaterialInfo, "terrainEditor.popBaseMaterialInfo();", 2, 2);
|
|
Con::addCommand("TerrainEditor", "setLoneBaseMaterial", cSetLoneBaseMaterial, "terrainEditor.setLoneBaseMaterial(material list base name);", 3, 3);
|
|
}
|
|
|
|
void TerrainEditor::initPersistFields()
|
|
{
|
|
Parent::initPersistFields();
|
|
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));
|
|
}
|