//----------------------------------------------------------------------------- // 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(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(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 & 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 & src, Vector & 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(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 & 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(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(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(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(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 positions; Vector rotations; path->sortMarkers(); CameraSpline spline; for(SimSetIterator itr(path); *itr; ++itr) { Marker *pathmarker = dynamic_cast(*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(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(*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 * list = (Vector*)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 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(); }