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

2948 lines
81 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "editor/worldEditor.h"
#include "sceneGraph/sceneGraph.h"
#include "sceneGraph/sceneState.h"
#include "sim/sceneObject.h"
#include "platform/event.h"
#include "gui/core/guiCanvas.h"
#include "game/gameConnection.h"
#include "core/memstream.h"
#include "collision/clippedPolyList.h"
#include "game/shapeBase.h"
#include "console/consoleInternal.h"
#include "game/sphere.h"
#include "sim/simPath.h"
#include "game/cameraSpline.h"
#include "interior/interiorInstance.h"
IMPLEMENT_CONOBJECT(WorldEditor);
// unnamed namespace for static data
namespace {
static Point3F BoxPnts[] = {
Point3F(0,0,0),
Point3F(0,0,1),
Point3F(0,1,0),
Point3F(0,1,1),
Point3F(1,0,0),
Point3F(1,0,1),
Point3F(1,1,0),
Point3F(1,1,1)
};
static U32 BoxVerts[][4] = {
{0,2,3,1}, // -x
{7,6,4,5}, // +x
{0,1,5,4}, // -y
{3,2,6,7}, // +y
{0,4,6,2}, // -z
{3,7,5,1} // +z
};
static Point3F BoxNormals[] = {
Point3F(-1, 0, 0),
Point3F( 1, 0, 0),
Point3F( 0,-1, 0),
Point3F( 0, 1, 0),
Point3F( 0, 0,-1),
Point3F( 0, 0, 1)
};
//
U32 getBoxNormalIndex(const VectorF & normal)
{
const F32 * pNormal = ((const F32 *)normal);
F32 max = 0;
S32 index = -1;
for(U32 i = 0; i < 3; i++)
if(mFabs(pNormal[i]) >= mFabs(max))
{
max = pNormal[i];
index = i*2;
}
AssertFatal(index >= 0, "Failed to get best normal");
if(max > 0.f)
index++;
return(index);
}
//
Point3F getBoundingBoxCenter(SceneObject * obj)
{
Box3F box = obj->getObjBox();
MatrixF mat = obj->getTransform();
VectorF scale = obj->getScale();
Point3F center(0,0,0);
Point3F projPnts[8];
for(U32 i = 0; i < 8; i++)
{
Point3F pnt;
pnt.set(BoxPnts[i].x ? box.max.x : box.min.x,
BoxPnts[i].y ? box.max.y : box.min.y,
BoxPnts[i].z ? box.max.z : box.min.z);
// scale it
pnt.convolve(scale);
mat.mulP(pnt, &projPnts[i]);
center += projPnts[i];
}
center /= 8;
return(center);
}
//
const char * parseObjectFormat(SimObject * obj, const char * format)
{
static char buf[1024];
U32 curPos = 0;
U32 len = dStrlen(format);
for(U32 i = 0; i < len; i++)
{
if(format[i] == '$')
{
U32 j;
for(j = i+1; j < len; j++)
if(format[j] == '$')
break;
if(j == len)
break;
char token[80];
AssertFatal((j - i) < (sizeof(token) - 1), "token too long");
dStrncpy(token, &format[i+1], (j - i - 1));
token[j-i-1] = 0;
U32 remaining = sizeof(buf) - curPos - 1;
// look at the token
if(!dStricmp(token, "id"))
curPos += dSprintf(buf + curPos, remaining, "%d", obj->getId());
else if(!dStricmp(token, "name"))
curPos += dSprintf(buf + curPos, remaining, "%s", obj->getName());
else if(!dStricmp(token, "class"))
curPos += dSprintf(buf + curPos, remaining, "%s", obj->getClassName());
else if(!dStricmp(token, "namespace") && obj->getNamespace())
curPos += dSprintf(buf + curPos, remaining, "%s", obj->getNamespace()->mName);
//
i = j;
}
else
buf[curPos++] = format[i];
}
buf[curPos] = 0;
return(buf);
}
//
F32 snapFloat(F32 val, F32 snap)
{
if(snap == 0.f)
return(val);
F32 a = mFmod(val, snap);
if(mFabs(a) > (snap / 2))
val < 0.f ? val -= snap : val += snap;
return(val - a);
}
//
EulerF extractEuler(const MatrixF & matrix)
{
const F32 * mat = (const F32*)matrix;
EulerF r;
r.x = mAsin(mat[MatrixF::idx(2,1)]);
if(mCos(r.x) != 0.f)
{
r.y = mAtan(-mat[MatrixF::idx(2,0)], mat[MatrixF::idx(2,2)]);
r.z = mAtan(-mat[MatrixF::idx(0,1)], mat[MatrixF::idx(1,1)]);
}
else
{
r.y = 0.f;
r.z = mAtan(mat[MatrixF::idx(1,0)], mat[MatrixF::idx(0,0)]);
}
return(r);
}
}
//------------------------------------------------------------------------------
// Class WorldEditor::Selection
//------------------------------------------------------------------------------
WorldEditor::Selection::Selection() :
mCentroidValid(false),
mAutoSelect(false)
{
registerObject();
}
WorldEditor::Selection::~Selection()
{
unregisterObject();
}
bool WorldEditor::Selection::objInSet(SceneObject * obj)
{
for(U32 i = 0; i < mObjectList.size(); i++)
if(mObjectList[i] == (SimObject*)obj)
return(true);
return(false);
}
bool WorldEditor::Selection::addObject(SceneObject * obj)
{
if(objInSet(obj))
return(false);
mCentroidValid = false;
mObjectList.pushBack(obj);
deleteNotify(obj);
if(mAutoSelect)
{
obj->setSelected(true);
SceneObject * clientObj = WorldEditor::getClientObj(obj);
if(clientObj)
clientObj->setSelected(true);
}
return(true);
}
bool WorldEditor::Selection::removeObject(SceneObject * obj)
{
if(!objInSet(obj))
return(false);
mCentroidValid = false;
mObjectList.remove(obj);
clearNotify(obj);
if(mAutoSelect)
{
obj->setSelected(false);
SceneObject * clientObj = WorldEditor::getClientObj(obj);
if(clientObj)
clientObj->setSelected(false);
}
return(true);
}
void WorldEditor::Selection::clear()
{
while(mObjectList.size())
removeObject((SceneObject*)mObjectList[0]);
}
void WorldEditor::Selection::onDeleteNotify(SimObject * obj)
{
removeObject((SceneObject*)obj);
}
void WorldEditor::Selection::updateCentroid()
{
if(mCentroidValid)
return;
mCentroidValid = true;
//
mCentroid.set(0,0,0);
mBoxCentroid = mCentroid;
if(!mObjectList.size())
return;
//
for(U32 i = 0; i < mObjectList.size(); i++)
{
const MatrixF & mat = ((SceneObject*)mObjectList[i])->getTransform();
Point3F wPos;
mat.getColumn(3, &wPos);
//
mBoxCentroid += getBoundingBoxCenter((SceneObject*)mObjectList[i]);
mCentroid += wPos;
}
mCentroid /= mObjectList.size();
mBoxCentroid /= mObjectList.size();
}
const Point3F & WorldEditor::Selection::getCentroid()
{
updateCentroid();
return(mCentroid);
}
const Point3F & WorldEditor::Selection::getBoxCentroid()
{
updateCentroid();
return(mBoxCentroid);
}
void WorldEditor::Selection::enableCollision()
{
for(U32 i = 0; i < mObjectList.size(); i++)
((SceneObject*)mObjectList[i])->enableCollision();
}
void WorldEditor::Selection::disableCollision()
{
for(U32 i = 0; i < mObjectList.size(); i++)
((SceneObject*)mObjectList[i])->disableCollision();
}
//------------------------------------------------------------------------------
void WorldEditor::Selection::offset(const Point3F & offset)
{
for(U32 i = 0; i < mObjectList.size(); i++)
{
MatrixF mat = ((SceneObject*)mObjectList[i])->getTransform();
Point3F wPos;
mat.getColumn(3, &wPos);
// adjust
wPos += offset;
mat.setColumn(3, wPos);
((SceneObject*)mObjectList[i])->setTransform(mat);
}
mCentroidValid = false;
}
void WorldEditor::Selection::orient(const MatrixF & rot, const Point3F & center)
{
// Orient all the selected objects to the given rotation
for(U32 i = 0; i < size(); i++)
{
MatrixF mat = rot;
mat.setPosition(((SceneObject*)mObjectList[i])->getPosition());
((SceneObject*)mObjectList[i])->setTransform(mat);
}
mCentroidValid = false;
}
void WorldEditor::Selection::rotate(const EulerF & rot, const Point3F & center)
{
// single selections will rotate around own axis, multiple about world
if(mObjectList.size() == 1)
{
MatrixF mat = ((SceneObject*)mObjectList[0])->getTransform();
Point3F pos;
mat.getColumn(3, &pos);
// get offset in obj space
Point3F offset = pos - center;
MatrixF wMat = ((SceneObject*)mObjectList[0])->getWorldTransform();
wMat.mulV(offset);
//
MatrixF transform(EulerF(0,0,0), -offset);
transform.mul(MatrixF(rot));
transform.mul(MatrixF(EulerF(0,0,0), offset));
mat.mul(transform);
((SceneObject*)mObjectList[0])->setTransform(mat);
}
else
{
for(U32 i = 0; i < size(); i++)
{
MatrixF mat = ((SceneObject*)mObjectList[i])->getTransform();
Point3F pos;
mat.getColumn(3, &pos);
// get offset in obj space
Point3F offset = pos - center;
MatrixF transform(rot);
Point3F wOffset;
transform.mulV(offset, &wOffset);
MatrixF wMat = ((SceneObject*)mObjectList[i])->getWorldTransform();
wMat.mulV(offset);
//
transform.set(EulerF(0,0,0), -offset);
mat.setColumn(3, Point3F(0,0,0));
wMat.setColumn(3, Point3F(0,0,0));
transform.mul(wMat);
transform.mul(MatrixF(rot));
transform.mul(mat);
mat.mul(transform);
mat.normalize();
mat.setColumn(3, wOffset + center);
((SceneObject*)mObjectList[i])->setTransform(mat);
}
}
mCentroidValid = false;
}
void WorldEditor::Selection::scale(const VectorF & scale)
{
for(U32 i = 0; i < mObjectList.size(); i++)
{
VectorF current = ((SceneObject*)mObjectList[i])->getScale();
current.convolve(scale);
((SceneObject*)mObjectList[i])->setScale(current);
}
mCentroidValid = false;
}
//------------------------------------------------------------------------------
SceneObject * WorldEditor::getClientObj(SceneObject * obj)
{
AssertFatal(obj->isServerObject(), "WorldEditor::getClientObj: not a server object!");
NetConnection * toServer = NetConnection::getConnectionToServer();
NetConnection * toClient = NetConnection::getLocalClientConnection();
S32 index = toClient->getGhostIndex(obj);
if(index == -1)
return(0);
return(dynamic_cast<SceneObject*>(toServer->resolveGhost(index)));
}
void WorldEditor::setClientObjInfo(SceneObject * obj, const MatrixF & mat, const VectorF & scale)
{
SceneObject * clientObj = getClientObj(obj);
if(!clientObj)
return;
clientObj->setTransform(mat);
clientObj->setScale(scale);
}
void WorldEditor::updateClientTransforms(Selection & sel)
{
for(U32 i = 0; i < sel.size(); i++)
{
SceneObject * clientObj = getClientObj(sel[i]);
if(!clientObj)
continue;
// If it's an interior we're going to need to relight the scene
// since we've moved it, so notify script of this. Don't bother
// ourselves with dynamic lit interiors, of course.
InteriorInstance *ii = dynamic_cast<InteriorInstance*>(clientObj);
if(ii != NULL && ii->mUseGLLighting == false)
{
// Set it to be dynamically lit as well.
ii->mDoSimpleDynamicRender = true;
Con::executef( 1, "onNeedRelight" );
}
clientObj->setTransform(sel[i]->getTransform());
clientObj->setScale(sel[i]->getScale());
}
}
//------------------------------------------------------------------------------
// simple undo mechanism: bascially maintains stacks of state information
// from the selections (transform info only)
WorldEditor::SelectionState * WorldEditor::createUndo(Selection & sel)
{
SelectionState * sState = new SelectionState;
for(U32 i = 0; i < sel.size(); i++)
{
SelectionState::Entry entry;
entry.mMatrix = sel[i]->getTransform();
entry.mScale = sel[i]->getScale();
entry.mObjId = sel[i]->getId();
sState->mEntries.push_back(entry);
}
return(sState);
}
void WorldEditor::addUndo(Vector<SelectionState *> & list, SelectionState * sel)
{
AssertFatal(sel, "WorldEditor::addUndo - invalid selection");
list.push_front(sel);
if(list.size() == mUndoLimit)
{
SelectionState * ss = list[list.size()-1];
delete ss;
list.pop_back();
}
setDirty();
}
bool WorldEditor::processUndo(Vector<SelectionState*> & src, Vector<SelectionState*> & dest)
{
if(!src.size())
return(false);
SelectionState * task = src.front();
src.pop_front();
for(U32 i = 0; i < task->mEntries.size(); i++)
{
SceneObject * obj = static_cast<SceneObject*>(Sim::findObject(task->mEntries[i].mObjId));
if(obj)
{
setClientObjInfo(obj, task->mEntries[i].mMatrix, task->mEntries[i].mScale);
obj->setTransform(task->mEntries[i].mMatrix);
obj->setScale(task->mEntries[i].mScale);
}
}
addUndo(dest, task);
mSelected.invalidateCentroid();
return(true);
}
void WorldEditor::clearUndo(Vector<SelectionState *> & list)
{
for(U32 i = 0; i < list.size(); i++)
delete list[i];
list.clear();
}
//------------------------------------------------------------------------------
// edit stuff
bool WorldEditor::deleteSelection(Selection & sel)
{
if(sel.size())
setDirty();
while(sel.size())
sel[0]->deleteObject();
return(true);
}
bool WorldEditor::copySelection(Selection & sel)
{
mStreamBufs.clear();
for(U32 i = 0; i < sel.size(); i++)
{
// um.. can we say lame?
mStreamBufs.increment();
MemStream stream(2048, mStreamBufs.last(), false, true);
stream.write(7, (void*)"return ");
sel[i]->write(stream, 0);
if(stream.getStatus() != Stream::Ok)
{
mStreamBufs.clear();
return(false);
}
}
return(true);
}
bool WorldEditor::pasteSelection()
{
if(!mSelectionLocked)
mSelected.clear();
for(U32 i = 0; i < mStreamBufs.size(); i++)
{
const char * eval = Con::evaluate((const char *)mStreamBufs[i]);
SceneObject * obj = dynamic_cast<SceneObject*>(Sim::findObject(eval));
if(obj && !mSelectionLocked)
mSelected.addObject(obj);
}
// drop it ...
dropSelection(mSelected);
if(mSelected.size())
{
if( isMethod("onClick") )
{
char buf[16];
dSprintf(buf, sizeof(buf), "%d", mSelected[0]->getId());
SimObject * obj = 0;
if(mRedirectID)
obj = Sim::findObject(mRedirectID);
Con::executef(obj ? obj : this, 2, "onClick", buf);
}
}
return(true);
}
//------------------------------------------------------------------------------
void WorldEditor::hideSelection(bool hide)
{
// set server/client objects hide field
for(U32 i = 0; i < mSelected.size(); i++)
{
// client
SceneObject * clientObj = getClientObj(mSelected[i]);
if(!clientObj)
continue;
clientObj->setHidden(hide);
// server
mSelected[i]->setHidden(hide);
}
}
void WorldEditor::lockSelection(bool lock)
{
//
for(U32 i = 0; i < mSelected.size(); i++)
mSelected[i]->setLocked(lock);
}
//------------------------------------------------------------------------------
// the centroid get's moved to the drop point...
void WorldEditor::dropSelection(Selection & sel)
{
if(!sel.size())
return;
setDirty();
Point3F centroid = mObjectsUseBoxCenter ? sel.getBoxCentroid() : sel.getCentroid();
switch(mDropType)
{
case DropAtCentroid:
// already there
break;
case DropAtOrigin:
{
sel.offset(Point3F(-centroid));
break;
}
case DropAtCameraWithRot:
{
sel.offset(Point3F(smCamPos - centroid));
sel.orient(smCamMatrix, centroid);
break;
}
case DropAtCamera:
{
sel.offset(Point3F(smCamPos - centroid));
break;
}
case DropBelowCamera:
{
Point3F offset = smCamPos - centroid;
offset.z -= 15.f;
sel.offset(offset);
break;
}
case DropAtScreenCenter:
{
Gui3DMouseEvent event;
event.pos = smCamPos;
Point2I offset = localToGlobalCoord(Point2I(0,0));
Point3F sp(offset.x + (getExtent().x / 2), offset.y + (getExtent().y / 2), 1);
Point3F wp;
unproject(sp, &wp);
event.vec = wp - smCamPos;
event.vec.normalizeSafe();
// if fails to hit screen, then place at camera
CollisionInfo info;
if(collide(event, info))
sel.offset(Point3F(info.pos - centroid));
else
sel.offset(Point3F(event.pos - centroid));
break;
}
case DropToGround:
{
for(U32 i = 0; i < sel.size(); i++)
{
Point3F start;
MatrixF mat = sel[i]->getTransform();
mat.getColumn(3, &start);
Point3F end = start;
start.z = -2000.f;
end.z = 2000.f;
RayInfo ri;
bool hit;
if(mBoundingBoxCollision)
hit = gServerContainer.collideBox(start, end, TerrainObjectType, &ri);
else
hit = gServerContainer.castRay(start, end, TerrainObjectType, &ri);
if(hit)
{
mat.setColumn(3, ri.point);
sel[i]->setTransform(mat);
}
else
sel.offset(Point3F(smCamPos - centroid));
}
break;
}
}
//
updateClientTransforms(sel);
}
//------------------------------------------------------------------------------
SceneObject * WorldEditor::getControlObject()
{
GameConnection * connection = GameConnection::getLocalClientConnection();
if(connection)
return(dynamic_cast<SceneObject*>(connection->getControlObject()));
return(0);
}
bool WorldEditor::collide(const Gui3DMouseEvent & event, CollisionInfo & info)
{
// turn off the collsion with the control object
SceneObject * controlObj = getControlObject();
if(controlObj)
controlObj->disableCollision();
//
Point3F startPnt = event.pos;
Point3F endPnt = event.pos + event.vec * mProjectDistance;
//
RayInfo ri;
bool hit;
if(mBoundingBoxCollision)
hit = gServerContainer.collideBox(startPnt, endPnt, 0xFFFFFFFF, &ri);
else
hit = gServerContainer.castRay(startPnt, endPnt, 0xFFFFFFFF, &ri);
//
if(hit)
{
info.pos = ri.point;
info.obj = ri.object;
info.normal = ri.normal;
AssertFatal(info.obj, "WorldEditor::collide - client container returned non SceneObject");
}
if(controlObj)
controlObj->enableCollision();
return(hit);
}
//------------------------------------------------------------------------------
// main render functions
void WorldEditor::renderSelectionWorldBox(Selection & sel)
{
if(!mRenderSelectionBox)
return;
//
if(!sel.size())
return;
// build the world bounds
Box3F selBox = sel[0]->getWorldBox();
U32 i;
for(i = 1; i < sel.size(); i++)
{
const Box3F & wBox = sel[i]->getWorldBox();
selBox.min.setMin(wBox.min);
selBox.max.setMax(wBox.max);
}
//
glDisable(GL_CULL_FACE);
glColor4ub(mSelectionBoxColor.red, mSelectionBoxColor.green,
mSelectionBoxColor.blue, mSelectionBoxColor.alpha);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// create the box points
Point3F projPnts[8];
for(i = 0; i < 8; i++)
{
Point3F pnt;
pnt.set(BoxPnts[i].x ? selBox.max.x : selBox.min.x,
BoxPnts[i].y ? selBox.max.y : selBox.min.y,
BoxPnts[i].z ? selBox.max.z : selBox.min.z);
projPnts[i] = pnt;
}
// do the box
for(U32 j = 0; j < 6; j++)
{
glBegin(GL_LINE_LOOP);
for(U32 k = 0; k < 4; k++)
glVertex3f(projPnts[BoxVerts[j][k]].x,
projPnts[BoxVerts[j][k]].y,
projPnts[BoxVerts[j][k]].z);
glEnd();
}
}
void WorldEditor::renderObjectBox(SceneObject * obj, const ColorI & col)
{
AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry");
glDisable(GL_CULL_FACE);
glColor4ub(col.red, col.green, col.blue, col.alpha);
// project the points...
Box3F box = obj->getObjBox();
MatrixF mat = obj->getTransform();
VectorF scale = obj->getScale();
Point3F projPnts[8];
for(U32 i = 0; i < 8; i++)
{
Point3F pnt;
pnt.set(BoxPnts[i].x ? box.max.x : box.min.x,
BoxPnts[i].y ? box.max.y : box.min.y,
BoxPnts[i].z ? box.max.z : box.min.z);
// scale it
pnt.convolve(scale);
mat.mulP(pnt, &projPnts[i]);
}
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// do the box
for(U32 j = 0; j < 6; j++)
{
glBegin(GL_LINE_LOOP);
for(U32 k = 0; k < 4; k++)
glVertex3f(projPnts[BoxVerts[j][k]].x,
projPnts[BoxVerts[j][k]].y,
projPnts[BoxVerts[j][k]].z);
glEnd();
}
// // render the collision polys?
// // jff - this is using the worldbox... use obj box? which sides?
// if(mRenderBoxIntersect)
// {
// obj->disableCollision();
// Box3F bBox = obj->getWorldBox();
//
// bBox.min.z -= mProjectDistance;
// bBox.max.z += mProjectDistance;
//
// ClippedPolyList polyList;
// polyList.mPlaneList.clear();
// polyList.mNormal.set(0,0,0);
// polyList.mPlaneList.setSize(4);
// polyList.mPlaneList[0].set(bBox.min, VectorF(-1,0,0));
// polyList.mPlaneList[1].set(bBox.max, VectorF(0,1,0));
// polyList.mPlaneList[2].set(bBox.max, VectorF(1,0,0));
// polyList.mPlaneList[3].set(bBox.min, VectorF(0,-1,0));
//
// // build the poly list
// if(gServerContainer.buildPolyList(bBox, -1, &polyList))
// {
// glEnable(GL_CULL_FACE);
// glDisable(GL_DEPTH_TEST);
// glVertexPointer(3,GL_FLOAT,sizeof(ClippedPolyList::Vertex), polyList.mVertexList.address());
// glEnableClientState(GL_VERTEX_ARRAY);
//
// glColor4ub(mBoxIntersectColor.red, mBoxIntersectColor.green, mBoxIntersectColor.blue, mBoxIntersectColor.alpha);
//
// glEnable(GL_BLEND);
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//
// // render em...
// ClippedPolyList::Poly * p;
// for (p = polyList.mPolyList.begin(); p < polyList.mPolyList.end(); p++) {
// glDrawElements(GL_POLYGON,p->vertexCount,
// GL_UNSIGNED_INT,&polyList.mIndexList[p->vertexStart]);
// }
//
// glDisableClientState(GL_VERTEX_ARRAY);
// glEnable(GL_DEPTH_TEST);
// glDisable(GL_CULL_FACE);
// }
// obj->enableCollision();
// }
dglSetCanonicalState();
AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit");
}
//------------------------------------------------------------------------------
void WorldEditor::renderObjectFace(SceneObject * obj, const VectorF & normal, const ColorI & col)
{
// get the normal index
VectorF objNorm;
obj->getWorldTransform().mulV(normal, &objNorm);
U32 normI = getBoxNormalIndex(objNorm);
//
Box3F box = obj->getObjBox();
MatrixF mat = obj->getTransform();
VectorF scale = obj->getScale();
Point3F projPnts[4];
for(U32 i = 0; i < 4; i++)
{
Point3F pnt;
pnt.set(BoxPnts[BoxVerts[normI][i]].x ? box.max.x : box.min.x,
BoxPnts[BoxVerts[normI][i]].y ? box.max.y : box.min.y,
BoxPnts[BoxVerts[normI][i]].z ? box.max.z : box.min.z);
// scale it
pnt.convolve(scale);
mat.mulP(pnt, &projPnts[i]);
}
glDisable(GL_CULL_FACE);
glColor4ub(col.red, col.green, col.blue, col.alpha);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_DEPTH_TEST);
//
glBegin(GL_QUADS);
for(U32 k = 0; k < 4; k++)
glVertex3f(projPnts[k].x, projPnts[k].y, projPnts[k].z);
glEnd();
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
}
//------------------------------------------------------------------------------
void WorldEditor::renderPlane(const Point3F & origin)
{
if(!(mRenderPlane || mRenderPlaneHashes))
return;
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4ub(mGridColor.red, mGridColor.green, mGridColor.blue, mGridColor.alpha);
Point2F start(origin.x - mPlaneDim / 2, origin.y - mPlaneDim / 2);
//
if(mRenderPlane)
{
// draw the plane
glBegin(GL_QUADS);
glVertex3f(start.x, start.y, origin.z);
glVertex3f(start.x, start.y + mPlaneDim, origin.z);
glVertex3f(start.x + mPlaneDim, start.y + mPlaneDim, origin.z);
glVertex3f(start.x + mPlaneDim, start.y, origin.z);
glEnd();
}
//
if(mRenderPlaneHashes)
{
if(mGridSize.x > 0)
{
U32 xSteps = (U32)(mPlaneDim / mGridSize.x);
F32 hashStart = mCeil(start.x / mGridSize.x) * mGridSize.x;
for(U32 i = 0; i < xSteps; i++)
{
glBegin(GL_LINE_LOOP);
glVertex3f(hashStart + mGridSize.x * i, start.y, origin.z + 0.001f);
glVertex3f(hashStart + mGridSize.x * i, start.y + mPlaneDim, origin.z + 0.001f);
glEnd();
}
}
if(mGridSize.y > 0)
{
U32 ySteps = (U32)(mPlaneDim / mGridSize.y);
F32 hashStart = mCeil(start.y / mGridSize.y) * mGridSize.y;
for(U32 i = 0; i < ySteps; i++)
{
glBegin(GL_LINE_LOOP);
glVertex3f(start.x, hashStart + mGridSize.y * i, origin.z + 0.001f);
glVertex3f(start.x + mPlaneDim, hashStart + mGridSize.y * i, origin.z + 0.001f);
glEnd();
}
}
}
glDisable(GL_BLEND);
}
//------------------------------------------------------------------------------
void WorldEditor::renderMousePopupInfo()
{
Point2I pos(mLastMouseEvent.mousePoint.x,
mLastMouseEvent.mousePoint.y + mCurrentCursor->getExtent().y - mCurrentCursor->getHotSpot().y);
if(mCurrentMode == mDefaultMode && !mMouseDragged)
return;
char buf[256];
switch(mCurrentMode)
{
case Move:
{
if(!mSelected.size())
return;
Point3F pos = mObjectsUseBoxCenter ? mSelected.getBoxCentroid() : mSelected.getCentroid();
dSprintf(buf, sizeof(buf), "x: %0.3f, y: %0.3f, z: %0.3f", pos.x, pos.y, pos.z);
break;
}
case Rotate: {
if(!bool(mHitObject) || (mSelected.size() != 1))
return;
// print out the angle-axis 'fo
AngAxisF aa(mHitObject->getTransform());
dSprintf(buf, sizeof(buf), "x: %0.3f, y: %0.3f, z: %0.3f, a: %0.3f",
aa.axis.x, aa.axis.y, aa.axis.z, mRadToDeg(aa.angle));
break;
}
case Scale: {
if(!bool(mHitObject) || (mSelected.size() != 1))
return;
VectorF scale = mHitObject->getScale();
Box3F box = mHitObject->getObjBox();
box.min.convolve(scale);
box.max.convolve(scale);
box.max -= box.min;
dSprintf(buf, sizeof(buf), "w: %0.3f, h: %0.3f, d: %0.3f", box.max.x, box.max.y, box.max.z);
break;
}
default:
return;
}
U32 width = mProfile->mFont->getStrWidth(buf);
if(mRenderPopupBackground)
{
Point2I min(pos.x - width / 2 - 2, pos.y - 1);
Point2I max(pos.x + width / 2 + 2, pos.y + mProfile->mFont->getHeight() + 1);
dglDrawRectFill(min, max, mPopupBackgroundColor);
}
dglSetBitmapModulation(mPopupTextColor);
dglDrawText(mProfile->mFont, Point2I(pos.x - width / 2, pos.y), buf);
}
//------------------------------------------------------------------------------
void WorldEditor::renderPaths(SimObject *obj)
{
if (obj == NULL)
return;
bool selected = false;
// Loop through subsets
if (SimSet *set = dynamic_cast<SimSet*>(obj))
for(SimSetIterator itr(set); *itr; ++itr) {
renderPaths(*itr);
if ((*itr)->isSelected())
selected = true;
}
// Render the path if it, or any of it's immediate sub-objects, is selected.
if (Path *path = dynamic_cast<Path*>(obj))
if (selected || path->isSelected())
renderSplinePath(path);
}
void WorldEditor::renderSplinePath(Path *path)
{
// at the time of writing the path properties are not part of the path object
// so we don't know to render it looping, splined, linear etc.
// for now we render all paths splined+looping
Vector<Point3F> positions;
Vector<QuatF> rotations;
path->sortMarkers();
CameraSpline spline;
for(SimSetIterator itr(path); *itr; ++itr)
{
Marker *pathmarker = dynamic_cast<Marker*>(*itr);
if (!pathmarker)
continue;
Point3F pos;
pathmarker->getTransform().getColumn(3, &pos);
QuatF rot;
rot.set(pathmarker->getTransform());
CameraSpline::Knot::Type type;
switch (pathmarker->mKnotType)
{
case Marker::KnotTypePositionOnly: type = CameraSpline::Knot::POSITION_ONLY; break;
case Marker::KnotTypeKink: type = CameraSpline::Knot::KINK; break;
case Marker::KnotTypeNormal:
default: type = CameraSpline::Knot::NORMAL; break;
}
CameraSpline::Knot::Path path;
switch (pathmarker->mSmoothingType)
{
case Marker::SmoothingTypeLinear: path = CameraSpline::Knot::LINEAR; break;
case Marker::SmoothingTypeSpline:
default: path = CameraSpline::Knot::SPLINE; break;
}
spline.push_back(new CameraSpline::Knot(pos, rot, 1.0f, type, path));
}
F32 t = 0.0f;
S32 size = spline.size();
if (size <= 1)
return;
// DEBUG
//spline.renderTimeMap();
if (path->isLooping())
{
CameraSpline::Knot *front = new CameraSpline::Knot(*spline.front());
CameraSpline::Knot *back = new CameraSpline::Knot(*spline.back());
spline.push_back(front);
spline.push_front(back);
t = 1.0f;
size += 2;
}
while (t < size - 1)
{
CameraSpline::Knot k;
spline.value(t, &k);
t = spline.advanceDist(t, 2.0f);
VectorF a(-0.15f, -0.25f, 0.0f);
VectorF b( 0.0f, 0.25f, 0.0f);
VectorF c( 0.15f, -0.25f, 0.0f);
VectorF t1, t2, t3;
k.mRotation.mulP(a, &t1); t1 += k.mPosition;
k.mRotation.mulP(b, &t2); t2 += k.mPosition;
k.mRotation.mulP(c, &t3); t3 += k.mPosition;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// OUTLINE
glColor4f(0, 1, 0, 1);
glLineWidth(1);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glBegin(GL_TRIANGLES);
glVertex3f(t1.x, t1.y, t1.z);
glVertex3f(t2.x, t2.y, t2.z);
glVertex3f(t3.x, t3.y, t3.z);
glEnd();
// FILL
glColor4f(0, 2, 0, 0.2);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glBegin(GL_TRIANGLES);
glVertex3f(t1.x, t1.y, t1.z);
glVertex3f(t2.x, t2.y, t2.z);
glVertex3f(t3.x, t3.y, t3.z);
glEnd();
glDisable(GL_BLEND);
}
}
//------------------------------------------------------------------------------
// render the handle/text/...
void WorldEditor::renderScreenObj(SceneObject * obj, Point2I sPos)
{
// do not render control object stuff
if(obj == getControlObject() || obj->isHidden())
return;
ClassInfo::Entry * entry = getClassEntry(obj);
if(!entry)
entry = &mDefaultClassEntry;
TextureObject * bitmap;
if(mRenderObjHandle)
{
// offset
if(obj->isLocked())
bitmap = entry->mLockedHandle ? entry->mLockedHandle : mDefaultClassEntry.mLockedHandle;
else
{
if(mSelected.objInSet(obj))
bitmap = entry->mSelectHandle ? entry->mSelectHandle : mDefaultClassEntry.mSelectHandle;
else
bitmap = entry->mDefaultHandle ? entry->mDefaultHandle : mDefaultClassEntry.mDefaultHandle;
}
sPos.x -= (bitmap->bitmapWidth / 2);
sPos.y -= (bitmap->bitmapHeight / 2);
dglClearBitmapModulation();
dglDrawBitmap(bitmap, sPos);
}
//
if(mRenderObjText)
{
const char * str = parseObjectFormat(obj, mObjTextFormat);
Point2I extent(mProfile->mFont->getStrWidth(str), mProfile->mFont->getHeight());
Point2I pos(sPos);
if(mRenderObjHandle)
{
pos.x += (bitmap->bitmapWidth / 2) - (extent.x / 2);
pos.y += (bitmap->bitmapHeight / 2) + 3;
}
dglSetBitmapModulation(mObjectTextColor);
dglDrawText(mProfile->mFont, pos, str);
}
}
//------------------------------------------------------------------------------
// axis gizmo stuff
void WorldEditor::calcAxisInfo()
{
if(!mSelected.size())
return;
// get the centroid..
mSelected.invalidateCentroid();
Point3F centroid = mObjectsUseBoxCenter ? mSelected.getBoxCentroid() : mSelected.getCentroid();
mAxisGizmoCenter = centroid;
VectorF axisVector[3] = {
VectorF(1,0,0),
VectorF(0,1,0),
VectorF(0,0,1)
};
// adjust to object space if just one object...
if((mSelected.size() == 1) && !((mLastMouseEvent.modifier & SI_SHIFT) && (mCurrentMode == Move)))
{
const MatrixF & mat = mSelected[0]->getTransform();
for(U32 i = 0; i < 3; i++)
{
VectorF tmp;
mat.mulV(axisVector[i], &tmp);
mAxisGizmoVector[i] = tmp;
mAxisGizmoVector[i].normalizeSafe();
}
}
else
for(U32 i = 0; i < 3; i++)
mAxisGizmoVector[i] = axisVector[i];
// get the projected size...
SceneObject * obj = getControlObject();
if(!obj)
return;
//
Point3F camPos;
obj->getTransform().getColumn(3, &camPos);
// assumes a 90deg FOV
Point3F dir = mAxisGizmoCenter - camPos;
mAxisGizmoProjLen = (F32(mAxisGizmoMaxScreenLen) / F32(getExtent().x)) * dir.magnitudeSafe() * mTan(mDegToRad(45.0));
}
bool WorldEditor::collideAxisGizmo(const Gui3DMouseEvent & event)
{
if(!mAxisGizmoActive || !mSelected.size())
return(false);
// get the projected size...
GameConnection* connection = GameConnection::getConnectionToServer();
if(!connection)
return false;
// Grab the camera's transform
MatrixF mat;
connection->getControlCameraTransform(0, &mat);
// Get the camera position
Point3F camPos;
mat.getColumn(3,&camPos);
// assumes a 90deg FOV
Point3F dir = mAxisGizmoCenter - camPos;
mAxisGizmoProjLen = (F32(mAxisGizmoMaxScreenLen) / F32(getExtent().x)) * dir.magnitudeSafe() * mTan(mDegToRad(45.0));
dir.normalizeSafe();
mAxisGizmoSelAxis = -1;
// find axis to use...
for(U32 i = 0; i < 3; i++)
{
VectorF up, normal;
mCross(dir, mAxisGizmoVector[i], &up);
mCross(up, mAxisGizmoVector[i], &normal);
if(normal.isZero())
break;
PlaneF plane(mAxisGizmoCenter, normal);
// width of the axis poly is 1/10 the run
Point3F a = up * mAxisGizmoProjLen / 10;
Point3F b = mAxisGizmoVector[i] * mAxisGizmoProjLen;
Point3F poly [] = {
Point3F(mAxisGizmoCenter + a),
Point3F(mAxisGizmoCenter + a + b),
Point3F(mAxisGizmoCenter - a + b),
Point3F(mAxisGizmoCenter - a)
};
Point3F end = camPos + event.vec * mProjectDistance;
F32 t = plane.intersect(camPos, end);
if(t >= 0 && t <= 1)
{
Point3F pos;
pos.interpolate(camPos, end, t);
// check if inside our 'poly' of this axis vector...
bool inside = true;
for(U32 j = 0; inside && (j < 4); j++)
{
U32 k = (j+1) % 4;
VectorF vec1 = poly[k] - poly[j];
VectorF vec2 = pos - poly[k];
if(mDot(vec1, vec2) > 0.f)
inside = false;
}
//
if(inside)
{
mAxisGizmoSelAxis = i;
return(true);
}
}
}
return(false);
}
//------------------------------------------------------------------------------
// Setup for the arrow heads
typedef struct _point3 {
double x,y,z;
} Point3;
Point3 agCone_vertex[] = {
{-0.000240, -0.000240, 0.000240},
{-0.000240, -0.119040, 0.206008},
{-0.000240, -0.000240, 0.237840},
{-0.000240, -0.206008, 0.119040},
{-0.000240, -0.237840, 0.000240},
{-0.000240, -0.206008, -0.118560},
{-0.000240, -0.119040, -0.205528},
{-0.000240, -0.000240, -0.237360},
{-0.000240, 0.118560, -0.205528},
{-0.000240, 0.205528, -0.118560},
{-0.000240, 0.237360, 0.000240},
{-0.000240, 0.205528, 0.119040},
{-0.000240, 0.118560, 0.206008},
{1.439760, -0.000240, 0.000240}
};
U32 agCone_vidx[] = {
0, 1, 2,
0, 3, 1,
0, 4, 3,
0, 5, 4,
0, 6, 5,
0, 7, 6,
0, 8, 7,
0, 9, 8,
0, 10, 9,
0, 11, 10,
0, 12, 11,
0, 2, 12,
2, 13, 13,
2, 1, 13,
1, 13, 13,
1, 3, 13,
3, 13, 13,
3, 4, 13,
4, 13, 13,
4, 5, 13,
5, 13, 13,
5, 6, 13,
6, 13, 13,
6, 7, 13,
7, 13, 13,
7, 8, 13,
8, 13, 13,
8, 9, 13,
9, 13, 13,
9, 10, 13,
10, 13, 13,
10, 11, 13,
11, 13, 13,
11, 12, 13,
12, 13, 13,
12, 2, 13,
13, 13, 13,
13, 13, 13,
13, 13, 13,
13, 13, 13,
13, 13, 13,
13, 13, 13,
13, 13, 13,
13, 13, 13,
13, 13, 13,
13, 13, 13,
13, 13, 13,
13, 13, 13
};
void WorldEditor::renderAxisGizmo()
{
calcAxisInfo();
//ColorI axisColor( 0x00, 0x80, 0x80 ); // Dark teal
ColorI axisColors[3];
axisColors[0].set(255, 0, 0);
axisColors[1].set(0, 255, 0);
axisColors[2].set(0, 0, 255);
//ColorI selectColor( 0x00, 0xff, 0xff ); // Bright teal
ColorI selectColor( 0xff, 0xff, 0x00 ); // Bright yellow
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glBegin(GL_LINES);
// render each of them...
for(U32 i = 0; i < 3; i++)
{
Point3F & centroid = mAxisGizmoCenter;
if( i == mAxisGizmoSelAxis )
glColor3ub( selectColor.red, selectColor.green, selectColor.blue );
else
glColor3ub( axisColors[i].red, axisColors[i].green, axisColors[i].blue );
glVertex3f(centroid.x, centroid.y, centroid.z);
glVertex3f(centroid.x + mAxisGizmoVector[i].x * mAxisGizmoProjLen,
centroid.y + mAxisGizmoVector[i].y * mAxisGizmoProjLen,
centroid.z + mAxisGizmoVector[i].z * mAxisGizmoProjLen);
}
glEnd();
// Also draw arrows on the ends of the lines
F32 arrowLength = mFabs(mAxisGizmoProjLen * 0.15);
glEnable(GL_CULL_FACE);
for (S32 i = 0; i < 3; i++)
{
if( i == mAxisGizmoSelAxis )
glColor3ub( selectColor.red, selectColor.green, selectColor.blue );
else
glColor3ub( axisColors[i].red, axisColors[i].green, axisColors[i].blue );
glPushMatrix();
MatrixF trans(true);
// adjust to object space if just one object...
if((mSelected.size() == 1) && !((mLastMouseEvent.modifier & SI_SHIFT) && (mCurrentMode == Move)))
{
trans = mSelected[0]->getTransform();
trans.setPosition(Point3F(0.0f, 0.0f, 0.0f));
}
glTranslatef(mAxisGizmoCenter.x + mAxisGizmoVector[i].x * mAxisGizmoProjLen * 0.85f,
mAxisGizmoCenter.y + mAxisGizmoVector[i].y * mAxisGizmoProjLen * 0.85f,
mAxisGizmoCenter.z + mAxisGizmoVector[i].z * mAxisGizmoProjLen * 0.85f);
dglMultMatrix(&trans);
if (i == 1)
glRotatef(90.0f, 0.0f, 0.0f, 1.0f);
else if (i == 2)
glRotatef(-90.0f, 0.0f, 1.0f, 0.0f);
glBegin(GL_TRIANGLES);
for (U32 i = 0; i < sizeof(agCone_vidx) / sizeof(U32); i++)
{
glVertex3f(agCone_vertex[agCone_vidx[i]].x * arrowLength,
agCone_vertex[agCone_vidx[i]].y * arrowLength,
agCone_vertex[agCone_vidx[i]].z * arrowLength );
}
glEnd();
glPopMatrix();
}
glDisable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
}
void WorldEditor::renderAxisGizmoText()
{
char axisText[] = "xyz";
ColorI fillColor = mProfile->mFillColorNA;
ColorI textColor = mProfile->mFontColor;
for(U32 i = 0; i < 3; i++)
{
const Point3F & centroid = mAxisGizmoCenter;
Point3F pos(centroid.x + mAxisGizmoVector[i].x * mAxisGizmoProjLen,
centroid.y + mAxisGizmoVector[i].y * mAxisGizmoProjLen,
centroid.z + mAxisGizmoVector[i].z * mAxisGizmoProjLen);
Point3F sPos;
if(project(pos, &sPos))
{
if( i == mAxisGizmoSelAxis ) {
// Draw background box
if( i == mAxisGizmoSelAxis ) {
fillColor = mProfile->mFillColor;
textColor = mProfile->mFontColorHL;
}
else {
fillColor = mProfile->mFillColorNA;
textColor = mProfile->mFontColor;
}
dglDrawRectFill( Point2I((S32)sPos.x - 3, (S32)sPos.y + 2 ),
Point2I( (S32)sPos.x + 8, (S32)sPos.y + mProfile->mFont->getHeight() + 3 ),
fillColor );
dglDrawRect( Point2I( (S32)sPos.x - 3, (S32)sPos.y + 2 ),
Point2I( (S32)sPos.x + 8, (S32)sPos.y + mProfile->mFont->getHeight() + 3 ),
textColor );
}
char buf[2];
buf[0] = axisText[i]; buf[1] = '\0';
dglSetBitmapModulation(textColor);
dglDrawText(mProfile->mFont, Point2I((S32)sPos.x, (S32)sPos.y), buf);
}
}
}
//------------------------------------------------------------------------------
Point3F WorldEditor::snapPoint(const Point3F & pnt)
{
if(!mSnapToGrid)
return(pnt);
Point3F snap;
snap.x = snapFloat(pnt.x, mGridSize.x);
snap.y = snapFloat(pnt.y, mGridSize.y);
snap.z = snapFloat(pnt.z, mGridSize.z);
return(snap);
}
//------------------------------------------------------------------------------
// ClassInfo stuff
WorldEditor::ClassInfo::~ClassInfo()
{
for(U32 i = 0; i < mEntries.size(); i++)
delete mEntries[i];
}
bool WorldEditor::objClassIgnored(const SceneObject * obj)
{
ClassInfo::Entry * entry = getClassEntry(obj);
if(mToggleIgnoreList)
return(!(entry ? entry->mIgnoreCollision : false));
else
return(entry ? entry->mIgnoreCollision : false);
}
WorldEditor::ClassInfo::Entry * WorldEditor::getClassEntry(StringTableEntry name)
{
AssertFatal(name, "WorldEditor::getClassEntry - invalid args");
for(U32 i = 0; i < mClassInfo.mEntries.size(); i++)
if(!dStricmp(name, mClassInfo.mEntries[i]->mName))
return(mClassInfo.mEntries[i]);
return(0);
}
WorldEditor::ClassInfo::Entry * WorldEditor::getClassEntry(const SceneObject * obj)
{
AssertFatal(obj, "WorldEditor::getClassEntry - invalid args");
return(getClassEntry(obj->getClassName()));
}
bool WorldEditor::addClassEntry(ClassInfo::Entry * entry)
{
AssertFatal(entry, "WorldEditor::addClassEntry - invalid args");
if(getClassEntry(entry->mName))
return(false);
mClassInfo.mEntries.push_back(entry);
return(true);
}
//------------------------------------------------------------------------------
// Mouse cursor stuff
bool WorldEditor::grabCursors()
{
struct _cursorInfo {
U32 index;
const char * name;
} infos[] = {
{HandCursor, "EditorHandCursor"},
{RotateCursor, "EditorRotateCursor"},
{ScaleCursor, "EditorRotateCursor"},
{MoveCursor, "EditorMoveCursor"},
{ArrowCursor, "EditorArrowCursor"},
{DefaultCursor, "DefaultCursor"},
};
//
for(U32 i = 0; i < (sizeof(infos) / sizeof(infos[0])); i++)
{
SimObject * obj = Sim::findObject(infos[i].name);
if(!obj)
{
Con::errorf(ConsoleLogEntry::Script, "WorldEditor::grabCursors: failed to find cursor '%s'.", infos[i].name);
return(false);
}
GuiCursor *cursor = dynamic_cast<GuiCursor*>(obj);
if(!cursor)
{
Con::errorf(ConsoleLogEntry::Script, "WorldEditor::grabCursors: object is not a cursor '%s'.", infos[i].name);
return(false);
}
//
mCursors[infos[i].index] = cursor;
}
//
mCurrentCursor = mCursors[DefaultCursor];
return(true);
}
void WorldEditor::setCursor(U32 cursor)
{
AssertFatal(cursor < NumCursors, "WorldEditor::setCursor: invalid cursor");
mCurrentCursor = mCursors[cursor];
}
//------------------------------------------------------------------------------
WorldEditor::WorldEditor()
{
// init the field data
mPlanarMovement = true;
mUndoLimit = 40;
mDropType = DropAtScreenCenter;
mProjectDistance = 2000.f;
mBoundingBoxCollision = true;
mRenderPlane = true;
mRenderPlaneHashes = true;
mGridColor.set(255,255,255,20);
mPlaneDim = 500;
mGridSize.set(10,10,10);
mRenderPopupBackground = true;
mPopupBackgroundColor.set(100,100,100);
mPopupTextColor.set(255,255,0);
mSelectHandle = StringTable->insert("common/editor/SelectHandle");
mDefaultHandle = StringTable->insert("common/editor/DefaultHandle");
mLockedHandle = StringTable->insert("common/editor/LockedHandle");
mObjectTextColor.set(255,255,255);
mObjectsUseBoxCenter = true;
mAxisGizmoMaxScreenLen = 200;
mAxisGizmoActive = true;
mMouseMoveScale = 0.2f;
mMouseRotateScale = 0.01f;
mMouseScaleScale = 0.01f;
mMinScaleFactor = 0.1f;
mMaxScaleFactor = 4000.f;
mObjSelectColor.set(255,0,0);
mObjMouseOverSelectColor.set(0,0,255);
mObjMouseOverColor.set(0,255,0);
mShowMousePopupInfo = true;
mDragRectColor.set(255,255,0);
mRenderObjText = true;
mRenderObjHandle = true;
mObjTextFormat = StringTable->insert("$id$: $name$");
mFaceSelectColor.set(0,0,100,100);
mRenderSelectionBox = true;
mSelectionBoxColor.set(255,255,0);
mSelectionLocked = false;
mSnapToGrid = false;
mSnapRotations = false;
mRotationSnap = 15.f;
mToggleIgnoreList = false;
mRenderNav = false;
mIsDirty = false;
mRedirectID = 0;
//
mHitInfo.obj = 0;
mHitObject = mHitInfo.obj;
//
mDefaultMode = mCurrentMode = Move;
mCurrentCursor = 0;
mMouseDown = false;
mDragSelect = false;
//
mSelected.autoSelect(true);
mDragSelected.autoSelect(false);
}
WorldEditor::~WorldEditor()
{
clearUndo(mUndoList);
clearUndo(mRedoList);
}
//------------------------------------------------------------------------------
bool WorldEditor::onAdd()
{
if(!Parent::onAdd())
return(false);
// grab all the cursors
if(!grabCursors())
return(false);
// create the default class entry
mDefaultClassEntry.mName = 0;
mDefaultClassEntry.mIgnoreCollision = false;
mDefaultClassEntry.mDefaultHandle = TextureHandle(mDefaultHandle, BitmapTexture);
mDefaultClassEntry.mSelectHandle = TextureHandle(mSelectHandle, BitmapTexture);
mDefaultClassEntry.mLockedHandle = TextureHandle(mLockedHandle, BitmapTexture);
if(!(mDefaultClassEntry.mDefaultHandle && mDefaultClassEntry.mSelectHandle && mDefaultClassEntry.mLockedHandle))
return(false);
return(true);
}
//------------------------------------------------------------------------------
void WorldEditor::onEditorEnable()
{
// go through and copy the hidden field to the client objects...
for(SimSetIterator itr(Sim::getRootGroup()); *itr; ++itr)
{
SceneObject * obj = dynamic_cast<SceneObject *>(*itr);
if(!obj)
continue;
// only work with a server obj...
if(obj->isClientObject())
continue;
// grab the client object
SceneObject * clientObj = getClientObj(obj);
if(!clientObj)
continue;
//
clientObj->setHidden(obj->isHidden());
}
}
//------------------------------------------------------------------------------
void WorldEditor::get3DCursor(GuiCursor *&cursor, bool &visible, const Gui3DMouseEvent &event)
{
event;
cursor = mCurrentCursor;
visible = true;
}
void WorldEditor::on3DMouseMove(const Gui3DMouseEvent & event)
{
// determine the current movement mode based on the modifiers:
if( event.modifier & SI_ALT )
{
// it's either rotate or scale
// probably rotate more often... so rotate = ALT, scale = ALT CTRL
if(event.modifier & SI_CTRL)
mCurrentMode = Scale;
else
mCurrentMode = Rotate;
}
else
mCurrentMode = Move;
setCursor(ArrowCursor);
mHitInfo.obj = 0;
//
mUsingAxisGizmo = false;
if(collideAxisGizmo(event))
{
setCursor(HandCursor);
mUsingAxisGizmo = true;
mHitMousePos = event.mousePoint;
mHitCentroid = mSelected.getCentroid();
mHitRotation = extractEuler(mSelected[0]->getTransform());
mHitObject = mSelected[0];
}
else
{
CollisionInfo info;
if(collide(event, info) && !objClassIgnored(info.obj))
{
setCursor(HandCursor);
mHitInfo = info;
}
}
//
mHitObject = mHitInfo.obj;
mLastMouseEvent = event;
}
void WorldEditor::on3DMouseDown(const Gui3DMouseEvent & event)
{
mMouseDown = true;
mMouseDragged = false;
mLastRotation = 0.f;
mouseLock();
// use ctrl to toggle vertical movement
mUseVertMove = (event.modifier & SI_CTRL);
// determine the current movement mode based on the modifiers:
if( event.modifier & SI_ALT )
{
// it's either rotate or scale
// probably rotate more often... so rotate = ALT, scale = ALT CTRL
if(event.modifier & SI_CTRL)
mCurrentMode = Scale;
else
mCurrentMode = Rotate;
}
else
mCurrentMode = Move;
// check gizmo first
mUsingAxisGizmo = false;
mNoMouseDrag = false;
if(collideAxisGizmo(event))
{
mUsingAxisGizmo = true;
mHitMousePos = event.mousePoint;
mHitCentroid = mSelected.getCentroid();
mHitRotation = extractEuler(mSelected[0]->getTransform());
mHitObject = mSelected[0];
}
else
{
CollisionInfo info;
if(collide(event, info) && !objClassIgnored(info.obj))
{
if(!mSelectionLocked)
{
if(event.modifier & SI_SHIFT)
{
mNoMouseDrag = true;
if(mSelected.objInSet(info.obj))
{
mSelected.removeObject(info.obj);
Con::executef(this, 3, "onUnSelect", avar("%d", info.obj->getId()));
}
else
{
mSelected.addObject(info.obj);
Con::executef(this, 3, "onSelect", avar("%d", info.obj->getId()));
}
}
else
{
if(!mSelected.objInSet(info.obj))
{
mNoMouseDrag = true;
for (U32 i = 0; i < mSelected.size(); i++)
Con::executef(this, 3, "onUnSelect", avar("%d", mSelected[i]->getId()));
mSelected.clear();
mSelected.addObject(info.obj);
Con::executef(this, 3, "onSelect", avar("%d", info.obj->getId()));
}
}
}
if(event.mouseClickCount > 1)
{
//
char buf[16];
dSprintf(buf, sizeof(buf), "%d", info.obj->getId());
SimObject * obj = 0;
if(mRedirectID)
obj = Sim::findObject(mRedirectID);
Con::executef(obj ? obj : this, 2, "onDblClick", buf);
}
else {
char buf[16];
dSprintf(buf, sizeof(buf), "%d", info.obj->getId());
SimObject * obj = 0;
if(mRedirectID)
obj = Sim::findObject(mRedirectID);
Con::executef(obj ? obj : this, 2, "onClick", buf);
}
mHitInfo = info;
mHitObject = mHitInfo.obj;
mHitOffset = info.pos - mSelected.getCentroid();
mHitRotation = extractEuler(mHitObject->getTransform());
mHitMousePos = event.mousePoint;
mHitCentroid = mSelected.getCentroid();
}
else if(!mSelectionLocked)
{
if(!(event.modifier & SI_SHIFT))
mSelected.clear();
mDragSelect = true;
mDragSelected.clear();
mDragRect.set(Point2I(event.mousePoint), Point2I(0,0));
mDragStart = event.mousePoint;
}
}
mLastMouseEvent = event;
}
void WorldEditor::on3DMouseUp(const Gui3DMouseEvent & event)
{
mMouseDown = false;
mUsingAxisGizmo = false;
// check if selecting objects....
if(mDragSelect)
{
mDragSelect = false;
// add all the objects from the drag selection into the normal selection
clearSelection();
for(U32 i = 0; i < mDragSelected.size(); i++)
{
Con::executef(this, 3, "onSelect", avar("%d", mDragSelected[i]->getId()));
mSelected.addObject(mDragSelected[i]);
}
mDragSelected.clear();
if(mSelected.size())
{
char buf[16];
dSprintf(buf, sizeof(buf), "%d", mSelected[0]->getId());
SimObject * obj = 0;
if(mRedirectID)
obj = Sim::findObject(mRedirectID);
Con::executef(obj ? obj : this, 2, "onClick", buf);
}
mouseUnlock();
return;
}
if (mMouseDragged && mSelected.size())
{
if(mSelected.size())
{
if( isMethod("onEndDrag") )
{
char buf[16];
dSprintf(buf, sizeof(buf), "%d", mSelected[0]->getId());
SimObject * obj = 0;
if(mRedirectID)
obj = Sim::findObject(mRedirectID);
Con::executef(obj ? obj : this, 2, "onEndDrag", buf);
}
}
}
if (mHitInfo.obj)
mHitInfo.obj->inspectPostApply();
mHitInfo.obj = 0;
//
if(collideAxisGizmo(event))
setCursor(HandCursor);
else
{
CollisionInfo info;
if(collide(event, info) && !objClassIgnored(info.obj))
{
setCursor(HandCursor);
// if(!mMouseDragged && !mSelectionLocked)
// {
// if(event.modifier & SI_SHIFT)
// {
// if(mSelected.objInSet(info.obj))
// mSelected.removeObject(info.obj);
// else
// mSelected.addObject(info.obj);
// }
// else
// {
// mSelected.clear();
// mSelected.addObject(info.obj);
// }
// }
mHitInfo = info;
}
else
setCursor(ArrowCursor);
}
//
mHitObject = mHitInfo.obj;
mouseUnlock();
}
void WorldEditor::on3DMouseDragged(const Gui3DMouseEvent & event)
{
if(!mMouseDown)
return;
if(mNoMouseDrag)
return;
//
if(!mMouseDragged)
{
if(!mUsingAxisGizmo)
{
// vert drag on new object.. reset hit offset
if(bool(mHitObject) && !mSelected.objInSet(mHitObject))
{
if(!mSelectionLocked)
mSelected.addObject(mHitObject);
mHitOffset = mHitInfo.pos - mSelected.getCentroid();
}
}
// create and add an undo state
if(!mDragSelect)
{
addUndo(mUndoList, createUndo(mSelected));
clearUndo(mRedoList);
}
mMouseDragged = true;
}
// update the drag selection
if(mDragSelect)
{
// build the drag selection on the renderScene method - make sure no neg extent!
mDragRect.point.x = (event.mousePoint.x < mDragStart.x) ? event.mousePoint.x : mDragStart.x;
mDragRect.extent.x = (event.mousePoint.x > mDragStart.x) ? event.mousePoint.x - mDragStart.x : mDragStart.x - event.mousePoint.x;
mDragRect.point.y = (event.mousePoint.y < mDragStart.y) ? event.mousePoint.y : mDragStart.y;
mDragRect.extent.y = (event.mousePoint.y > mDragStart.y) ? event.mousePoint.y - mDragStart.y : mDragStart.y - event.mousePoint.y;
return;
}
if(!mUsingAxisGizmo && (!bool(mHitObject) || !mSelected.objInSet(mHitObject)))
return;
// anything locked?
for(U32 i = 0; i < mSelected.size(); i++)
if(mSelected[i]->isLocked())
return;
// do stuff
switch(mCurrentMode)
{
case Move:
{
setCursor(MoveCursor);
// grabbed axis gizmo?
if(mUsingAxisGizmo)
{
F32 offset;
Point3F projPnt = mHitCentroid;;
if( mAxisGizmoSelAxis != 2 ) {
offset = ( event.mousePoint.x - mHitMousePos.x ) * mMouseMoveScale;
Point3F cv, camVector, objVector;
S32 col = ( mAxisGizmoSelAxis != 0 );
smCamMatrix.getColumn( 1, &camVector );
mSelected[0]->getTransform().getColumn( col, &objVector );
mCross( objVector, camVector, &cv );
if( cv.z < 0 )
offset *= -1;
}
else
offset = ( event.mousePoint.y - mHitMousePos.y ) * mMouseMoveScale * -1;
//for(S32 i = 0; i < 3; i++)
// if(i == mAxisGizmoSelAxis)
// ((F32*)projPnt)[i] += offset;
((F32*)projPnt)[mAxisGizmoSelAxis] += offset;
//
if((mSelected.size() == 1) && !(event.modifier & SI_SHIFT))
{
MatrixF mat = mSelected[0]->getTransform();
Point3F offset;
mat.mulV(projPnt - mHitCentroid, &offset);
mSelected.offset(offset + mHitCentroid - mSelected.getCentroid());
}
else
{
// snap to the selected axis
Point3F snap = snapPoint(projPnt);
((F32*)projPnt)[mAxisGizmoSelAxis] = ((F32*)snap)[mAxisGizmoSelAxis];
mSelected.offset(projPnt - mSelected.getCentroid());
}
updateClientTransforms(mSelected);
}
else
{
// ctrl modifier movement?
if(mUseVertMove)
{
if(mPlanarMovement)
{
// do a projection onto the z axis
F64 pDist = mSqrt((event.pos.x - mHitInfo.pos.x) * (event.pos.x - mHitInfo.pos.x) +
(event.pos.y - mHitInfo.pos.y) * (event.pos.y - mHitInfo.pos.y));
Point3F vec(mHitInfo.pos.x - event.pos.x, mHitInfo.pos.y - event.pos.y, 0.f);
vec.normalizeSafe();
F64 projDist = mDot(event.vec, vec);
if(projDist == 0.f)
return;
F64 scale = pDist / projDist;
vec = event.pos + (event.vec * scale);
vec.x = mHitInfo.pos.x;
vec.y = mHitInfo.pos.y;
mSelected.offset(vec - mSelected.getCentroid() - mHitOffset);
updateClientTransforms(mSelected);
}
else
{
// do a move on the z axis
F32 diff = mLastMouseEvent.mousePoint.x - event.mousePoint.x;
F32 offset = diff * mMouseMoveScale;
Point3F projPnt = mSelected.getCentroid();
projPnt.z += offset;
// snap just to z axis
Point3F snapped = snapPoint(projPnt);
projPnt.z = snapped.z;
mSelected.offset(projPnt - mSelected.getCentroid());
updateClientTransforms(mSelected);
}
}
else
{
// move on XY plane
if(mPlanarMovement)
{
// on z
F32 cos = mDot(event.vec, Point3F(0,0,-1));
F32 a = event.pos.z - mHitInfo.pos.z;
if(cos != 0.f)
{
F32 c = a / cos;
Point3F projPnt = event.vec * c;
projPnt += event.pos;
projPnt -= mHitOffset;
//
F32 z = projPnt.z;
projPnt = snapPoint(projPnt);
projPnt.z = z;
mSelected.offset(projPnt - mSelected.getCentroid());
updateClientTransforms(mSelected);
}
}
else
{
// offset the pnt of collision - no snapping involved
mSelected.disableCollision();
CollisionInfo info;
if(collide(event, info))
{
mSelected.offset(info.pos - mSelected.getCentroid() - mHitOffset);
updateClientTransforms(mSelected);
}
mSelected.enableCollision();
}
}
}
break;
}
case Scale:
{
setCursor(ScaleCursor);
// can scale only single selections
if(mSelected.size() > 1)
break;
if(mUsingAxisGizmo)
{
//
F32 diff = mLastMouseEvent.mousePoint.x - event.mousePoint.x;
// offset the correct axis
EulerF curScale = ((SceneObject*)mSelected[0])->getScale();
EulerF scale;
F32 * pCurScale = ((F32*)curScale);
F32 * pScale = ((F32*)scale);
for(S32 i = 0; i < 3; i++)
{
if(i == mAxisGizmoSelAxis)
pScale[i] = 1.f + (diff * mMouseScaleScale);
else
pScale[i] = 1.f;
// clamp
if(pCurScale[i] * pScale[i] < mMinScaleFactor)
pScale[i] = mMinScaleFactor / pCurScale[i];
if(pCurScale[i] * pScale[i] > mMaxScaleFactor)
pScale[i] = mMaxScaleFactor / pCurScale[i];
}
mSelected.scale(scale);
updateClientTransforms(mSelected);
}
else
{
if(!mBoundingBoxCollision)
{
// the hit normal is not useful, enable bounding box
// collision and get the collided face/normal
CollisionInfo info;
mBoundingBoxCollision = true;
bool hit = collide(event, info);
mBoundingBoxCollision = false;
// hit and the hit object?
if(!hit || (info.obj != (SceneObject*)mHitObject))
break;
mHitInfo = info;
}
VectorF normal;
mCross(mHitInfo.normal, event.vec, &normal);
VectorF planeNormal;
mCross(mHitInfo.normal, normal, &planeNormal);
PlaneF plane;
plane.set(mHitInfo.pos, planeNormal);
Point3F vecProj = event.pos + event.vec * mProjectDistance;
F32 t = plane.intersect(event.pos, vecProj);
if(t < 0.f || t > 1.f)
break;
Point3F pnt;
pnt.interpolate(event.pos, vecProj, t);
// figure out which axis we are working with, then
// find the distance to the correct face on the bbox
VectorF objNorm;
const MatrixF & worldMat = mHitObject->getWorldTransform();
worldMat.mulV(mHitInfo.normal, &objNorm);
U32 normIndex = getBoxNormalIndex(objNorm);
Box3F box = mHitObject->getObjBox();
VectorF curScale = mHitObject->getScale();
// scale and transform the bbox
Point3F size = box.max - box.min;
box.min.convolve(curScale);
box.max.convolve(curScale);
Box3F projBox;
const MatrixF & objMat = mHitObject->getTransform();
objMat.mulP(box.min, &projBox.min);
objMat.mulP(box.max, &projBox.max);
// if positive normal then grab max, else min
Point3F boxPnt;
Point3F offset;
if(normIndex & 0x01)
{
boxPnt = projBox.max;
worldMat.mulV(mSelected.getCentroid() - projBox.min, &offset);
}
else
{
boxPnt = projBox.min;
worldMat.mulV(mSelected.getCentroid() - projBox.max, &offset);
}
plane.set(boxPnt, mHitInfo.normal);
F32 dist = plane.distToPlane(pnt);
// set the scale for the correct axis
Point3F scale;
F32 * pScale = ((F32*)scale);
F32 * pSize = ((F32*)size);
F32 * pCurScale = ((F32*)curScale);
F32 * pOffset = ((F32*)offset);
for(U32 i = 0; i < 3; i++)
{
if((normIndex >> 1) == i)
{
// get the new scale
pScale[i] = (pSize[i] * pCurScale[i] + dist) / pSize[i];
// clamp
if(pScale[i] < mMinScaleFactor)
pScale[i] = mMinScaleFactor;
if(pScale[i] > mMaxScaleFactor)
pScale[i] = mMaxScaleFactor;
pOffset[i] = pScale[i] / pCurScale[i] * pOffset[i] - pOffset[i];
}
else
{
pScale[i] = pCurScale[i];
pOffset[i] = 0;
}
}
objMat.mulV(offset, &pnt);
mSelected.offset(pnt);
mHitObject->setScale(scale);
updateClientTransforms(mSelected);
}
break;
}
case Rotate:
{
setCursor(RotateCursor);
// default to z axis
S32 axis = 2;
if(mUsingAxisGizmo)
axis = mAxisGizmoSelAxis;
F32 angle = (event.mousePoint.x - mHitMousePos.x) * mMouseRotateScale;
//
if(mSnapRotations)
{
angle = mDegToRad(snapFloat(mRadToDeg(angle), mRotationSnap));
if(mSelected.size() == 1)
angle -= ((F32*)mHitRotation)[axis];
}
EulerF rot(0.f, 0.f, 0.f);
((F32*)rot)[axis] = (angle - mLastRotation);
Point3F centroid = mObjectsUseBoxCenter ? mSelected.getBoxCentroid() : mSelected.getCentroid();
mSelected.rotate(rot, centroid);
updateClientTransforms(mSelected);
mLastRotation = angle;
break;
}
}
mLastMouseEvent = event;
}
void WorldEditor::on3DMouseEnter(const Gui3DMouseEvent &)
{
}
void WorldEditor::on3DMouseLeave(const Gui3DMouseEvent &)
{
setCursor(DefaultCursor);
}
void WorldEditor::on3DRightMouseDown(const Gui3DMouseEvent & event)
{
}
void WorldEditor::on3DRightMouseUp(const Gui3DMouseEvent & event)
{
}
//------------------------------------------------------------------------------
void WorldEditor::updateGuiInfo()
{
SimObject * obj = 0;
if(mRedirectID)
obj = Sim::findObject(mRedirectID);
char buf[] = "";
Con::executef(obj ? obj : this, 2, "onGuiUpdate", buf);
}
//------------------------------------------------------------------------------
static void findObjectsCallback(SceneObject* obj, void * val)
{
Vector<SceneObject*> * list = (Vector<SceneObject*>*)val;
list->push_back(obj);
}
void WorldEditor::renderScene(const RectI & updateRect)
{
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
sgRelightFilter::sgRenderAllowedObjects(this);
// Render the paths
renderPaths(Sim::findObject("MissionGroup"));
// just walk the selected
U32 i;
for(i = 0; i < mSelected.size(); i++)
{
if((const SceneObject *)mHitObject == mSelected[i])
continue;
renderObjectBox(mSelected[i], mObjSelectColor);
}
// do the drag selection
for(i = 0; i < mDragSelected.size(); i++)
renderObjectBox(mDragSelected[i], mObjSelectColor);
// draw the mouse over obj
if(bool(mHitObject))
{
ColorI & col = mSelected.objInSet(mHitObject) ? mObjMouseOverSelectColor : mObjMouseOverColor;
renderObjectBox(mHitObject, col);
if(mCurrentMode == Scale && !mUsingAxisGizmo && mSelected.size() == 1)
renderObjectFace(mHitObject, mHitInfo.normal, mFaceSelectColor);
}
// stuff to do if there is a selection
if(mSelected.size())
{
if(mAxisGizmoActive)
renderAxisGizmo();
renderSelectionWorldBox(mSelected);
renderPlane(mObjectsUseBoxCenter ? mSelected.getBoxCentroid() : mSelected.getCentroid());
}
// draw the handles and text's now...
dglSetClipRect(updateRect);
if(mSelected.size() && mAxisGizmoActive)
renderAxisGizmoText();
// update what is in the selction
if(mDragSelect)
mDragSelected.clear();
//
Vector<SceneObject *> objects;
gServerContainer.findObjects(0xFFFFFFFF, findObjectsCallback, &objects);
for(i = 0; i < objects.size(); i++)
{
SceneObject * obj = objects[i];
if(objClassIgnored(obj))
continue;
Point3F wPos;
if(mObjectsUseBoxCenter)
wPos = getBoundingBoxCenter(obj);
else
obj->getTransform().getColumn(3, &wPos);
Point3F sPos;
if(project(wPos, &sPos))
{
// check if object needs to be added into the regions select
if(mDragSelect)
if(mDragRect.pointInRect(Point2I((S32)sPos.x, (S32)sPos.y)) && !mSelected.objInSet(obj))
mDragSelected.addObject(obj);
//
renderScreenObj(obj, Point2I((S32)sPos.x, (S32)sPos.y));
}
}
//
if(mShowMousePopupInfo && mMouseDown)
renderMousePopupInfo();
// seletion box
if(mDragSelect)
dglDrawRect(mDragRect, mDragRectColor);
}
//------------------------------------------------------------------------------
// Console stuff
static EnumTable::Enums dropEnums[] =
{
{ WorldEditor::DropAtOrigin, "atOrigin" },
{ WorldEditor::DropAtCamera, "atCamera" },
{ WorldEditor::DropAtCameraWithRot, "atCameraRot" },
{ WorldEditor::DropBelowCamera, "belowCamera" },
{ WorldEditor::DropAtScreenCenter, "screenCenter" },
{ WorldEditor::DropAtCentroid, "atCentroid" },
{ WorldEditor::DropToGround, "toGround" }
};
static EnumTable gEditorDropTable(7, &dropEnums[0]);
void WorldEditor::initPersistFields()
{
Parent::initPersistFields();
addGroup("Misc");
addField("isDirty", TypeBool, Offset(mIsDirty, WorldEditor));
addField("planarMovement", TypeBool, Offset(mPlanarMovement, WorldEditor));
addField("undoLimit", TypeS32, Offset(mUndoLimit, WorldEditor));
addField("dropType", TypeEnum, Offset(mDropType, WorldEditor), 1, &gEditorDropTable);
addField("projectDistance", TypeF32, Offset(mProjectDistance, WorldEditor));
addField("boundingBoxCollision", TypeBool, Offset(mBoundingBoxCollision, WorldEditor));
addField("renderPlane", TypeBool, Offset(mRenderPlane, WorldEditor));
addField("renderPlaneHashes", TypeBool, Offset(mRenderPlaneHashes, WorldEditor));
addField("gridColor", TypeColorI, Offset(mGridColor, WorldEditor));
addField("planeDim", TypeF32, Offset(mPlaneDim, WorldEditor));
addField("gridSize", TypePoint3F, Offset(mGridSize, WorldEditor));
addField("renderPopupBackground", TypeBool, Offset(mRenderPopupBackground, WorldEditor));
addField("popupBackgroundColor", TypeColorI, Offset(mPopupBackgroundColor, WorldEditor));
addField("popupTextColor", TypeColorI, Offset(mPopupTextColor, WorldEditor));
addField("objectTextColor", TypeColorI, Offset(mObjectTextColor, WorldEditor));
addField("objectsUseBoxCenter", TypeBool, Offset(mObjectsUseBoxCenter, WorldEditor));
addField("axisGizmoMaxScreenLen", TypeS32, Offset(mAxisGizmoMaxScreenLen, WorldEditor));
addField("axisGizmoActive", TypeBool, Offset(mAxisGizmoActive, WorldEditor));
addField("mouseMoveScale", TypeF32, Offset(mMouseMoveScale, WorldEditor));
addField("mouseRotateScale", TypeF32, Offset(mMouseRotateScale, WorldEditor));
addField("mouseScaleScale", TypeF32, Offset(mMouseScaleScale, WorldEditor));
addField("minScaleFactor", TypeF32, Offset(mMinScaleFactor, WorldEditor));
addField("maxScaleFactor", TypeF32, Offset(mMaxScaleFactor, WorldEditor));
addField("objSelectColor", TypeColorI, Offset(mObjSelectColor, WorldEditor));
addField("objMouseOverSelectColor", TypeColorI, Offset(mObjMouseOverSelectColor, WorldEditor));
addField("objMouseOverColor", TypeColorI, Offset(mObjMouseOverColor, WorldEditor));
addField("showMousePopupInfo", TypeBool, Offset(mShowMousePopupInfo, WorldEditor));
addField("dragRectColor", TypeColorI, Offset(mDragRectColor, WorldEditor));
addField("renderObjText", TypeBool, Offset(mRenderObjText, WorldEditor));
addField("renderObjHandle", TypeBool, Offset(mRenderObjHandle, WorldEditor));
addField("objTextFormat", TypeString, Offset(mObjTextFormat, WorldEditor));
addField("faceSelectColor", TypeColorI, Offset(mFaceSelectColor, WorldEditor));
addField("renderSelectionBox", TypeBool, Offset(mRenderSelectionBox, WorldEditor));
addField("selectionBoxColor", TypeColorI, Offset(mSelectionBoxColor, WorldEditor));
addField("selectionLocked", TypeBool, Offset(mSelectionLocked, WorldEditor));
addField("snapToGrid", TypeBool, Offset(mSnapToGrid, WorldEditor));
addField("snapRotations", TypeBool, Offset(mSnapRotations, WorldEditor));
addField("rotationSnap", TypeF32, Offset(mRotationSnap, WorldEditor));
addField("toggleIgnoreList", TypeBool, Offset(mToggleIgnoreList, WorldEditor));
addField("renderNav", TypeBool, Offset(mRenderNav, WorldEditor));
addField("selectHandle",TypeFilename, Offset(mSelectHandle, WorldEditor));
addField("defaultHandle",TypeFilename, Offset(mDefaultHandle, WorldEditor));
addField("lockedHandle",TypeFilename, Offset(mLockedHandle, WorldEditor));
endGroup("Misc");
}
//------------------------------------------------------------------------------
// These methods are needed for the console interfaces.
void WorldEditor::ignoreObjClass(U32 argc, const char** argv)
{
for(S32 i = 2; i < argc; i++)
{
ClassInfo::Entry * entry = getClassEntry(argv[i]);
if(entry)
entry->mIgnoreCollision = true;
else
{
entry = new ClassInfo::Entry;
entry->mName = StringTable->insert(argv[i]);
entry->mIgnoreCollision = true;
if(!addClassEntry(entry))
delete entry;
}
}
}
void WorldEditor::clearIgnoreList()
{
for(U32 i = 0; i < mClassInfo.mEntries.size(); i++)
mClassInfo.mEntries[i]->mIgnoreCollision = false;
}
void WorldEditor::undo()
{
processUndo(mUndoList, mRedoList);
}
void WorldEditor::redo()
{
processUndo(mRedoList, mUndoList);
}
void WorldEditor::clearSelection()
{
if(mSelectionLocked)
return;
Con::executef(this, 2, "onClearSelection");
mSelected.clear();
}
void WorldEditor::selectObject(const char* obj)
{
if(mSelectionLocked)
return;
SceneObject * select;
if(Sim::findObject(obj, select) && !objClassIgnored(select))
{
Con::executef(this, 3, "onSelect", avar("%d", select->getId()));
mSelected.addObject(select);
}
}
void WorldEditor::unselectObject(const char* obj)
{
if(mSelectionLocked)
return;
SceneObject * select;
if(Sim::findObject(obj, select) && !objClassIgnored(select))
{
mSelected.removeObject(select);
Con::executef(this, 3, "onUnSelect", avar("%d", select->getId()));
}
}
S32 WorldEditor::getSelectionSize()
{
return mSelected.size();
}
S32 WorldEditor::getSelectObject(S32 index)
{
// Return the object's id
return mSelected[index]->getId();
}
const char* WorldEditor::getSelectionCentroid()
{
const Point3F & centroid = mSelected.getCentroid();
char * ret = Con::getReturnBuffer(100);
dSprintf(ret, 100, "%g %g %g", centroid.x, centroid.y, centroid.z);
return(ret);
}
void WorldEditor::dropCurrentSelection()
{
dropSelection(mSelected);
}
void WorldEditor::deleteCurrentSelection()
{
deleteSelection(mSelected);
}
const char* WorldEditor::getMode()
{
if(mCurrentMode == Move)
return("move");
else if(mCurrentMode == Rotate)
return("rotate");
else if(mCurrentMode == Scale)
return("scale");
else return "";
}
bool WorldEditor::setMode(const char* mode)
{
// Set the mode
if(!dStricmp(mode, "move"))
mCurrentMode = WorldEditor::Move;
else if(!dStricmp(mode, "rotate"))
mCurrentMode = WorldEditor::Rotate;
else if(!dStricmp(mode, "scale"))
mCurrentMode = WorldEditor::Scale;
else return false;
return true;
}
void WorldEditor::addUndoState()
{
addUndo(mUndoList, createUndo(mSelected));
clearUndo(mRedoList);
}
void WorldEditor::redirectConsole(S32 objID)
{
mRedirectID = objID;
}
//------------------------------------------------------------------------------
ConsoleMethod( WorldEditor, ignoreObjClass, void, 3, 0, "(string class_name, ...)")
{
object->ignoreObjClass(argc, argv);
}
ConsoleMethod( WorldEditor, clearIgnoreList, void, 2, 2, "")
{
object->clearIgnoreList();
}
ConsoleMethod( WorldEditor, undo, void, 2, 2, "")
{
object->undo();
}
ConsoleMethod( WorldEditor, redo, void, 2, 2, "")
{
object->redo();
}
ConsoleMethod( WorldEditor, clearSelection, void, 2, 2, "")
{
object->clearSelection();
}
ConsoleMethod( WorldEditor, selectObject, void, 3, 3, "(SceneObject obj)")
{
object->selectObject(argv[2]);
}
ConsoleMethod( WorldEditor, unselectObject, void, 3, 3, "(SceneObject obj)")
{
object->unselectObject(argv[2]);
}
ConsoleMethod( WorldEditor, getSelectionSize, S32, 2, 2, "")
{
return object->getSelectionSize();
}
ConsoleMethod( WorldEditor, getSelectedObject, S32, 3, 3, "(int index)")
{
S32 index = dAtoi(argv[2]);
if(index < 0 || index >= object->getSelectionSize())
{
Con::errorf(ConsoleLogEntry::General, "WorldEditor::getSelectedObject: invalid object index");
return(-1);
}
return(object->getSelectObject(index));
}
ConsoleMethod( WorldEditor, getSelectionCentroid, const char *, 2, 2, "")
{
return object->getSelectionCentroid();
}
ConsoleMethod( WorldEditor, dropSelection, void, 2, 2, "")
{
// object->dropSelection(object->mSelected);
object->dropCurrentSelection();
}
ConsoleMethod( WorldEditor, deleteSelection, void, 2, 2, "")
{
// object->deleteSelection(object->mSelected);
object->deleteCurrentSelection();
}
void WorldEditor::copyCurrentSelection()
{
copySelection(mSelected);
}
ConsoleMethod( WorldEditor, copySelection, void, 2, 2, "")
{
object->copyCurrentSelection();
}
ConsoleMethod( WorldEditor, pasteSelection, void, 2, 2, "")
{
object->pasteSelection();
}
bool WorldEditor::canPasteSelection()
{
return(mStreamBufs.size() != 0);
}
ConsoleMethod( WorldEditor, canPasteSelection, bool, 2, 2, "")
{
return object->canPasteSelection();
}
ConsoleMethod( WorldEditor, hideSelection, void, 3, 3, "(bool hide)")
{
object->hideSelection(dAtob(argv[2]));
}
ConsoleMethod( WorldEditor, lockSelection, void, 3, 3, "(bool lock)")
{
object->lockSelection(dAtob(argv[2]));
}
ConsoleMethod( WorldEditor, redirectConsole, void, 3, 3, "( int objID )")
{
object->redirectConsole(dAtoi(argv[2]));
}
ConsoleMethod( WorldEditor, getMode, const char*, 2, 2, "")
{
const char* tmp;
if(!(tmp=object->getMode()))
{
Con::warnf(ConsoleLogEntry::General, avar("worldEditor.getMode: unknown mode"));
return("");
}
return tmp;
}
ConsoleMethod( WorldEditor, setMode, void, 3, 3, "(string newMode)"
"Sets the mode to one of move, rotate, scale.")
{
if(!object->setMode(argv[2]))
Con::warnf(ConsoleLogEntry::General, avar("worldEditor.setMode: invalid mode '%s'", argv[2]));
}
ConsoleMethod( WorldEditor, addUndoState, void, 2, 2, "")
{
object->addUndoState();
}