diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index e2e5aa2e5..ec6f802cf 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -68,7 +68,7 @@ opencs_units (view/world opencs_units_noqt (view/world subviews enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate - scripthighlighter idvalidator dialoguecreator + scripthighlighter idvalidator dialoguecreator physicssystem physicsmanager ) opencs_units (view/widget @@ -82,7 +82,7 @@ opencs_units (view/render opencs_units_noqt (view/render navigation navigation1st navigationfree navigationorbit lighting lightingday lightingnight - lightingbright object cell terrainstorage textoverlay overlaymask overlaysystem + lightingbright object cell terrainstorage textoverlay overlaymask overlaysystem mousestate ) opencs_hdrs_noqt (view/render @@ -165,7 +165,7 @@ qt4_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) qt4_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR_QT}) qt4_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${CMAKE_CURRENT_BINARY_DIR} ${BULLET_INCLUDE_DIRS}) if(APPLE) set (OPENCS_MAC_ICON ${CMAKE_SOURCE_DIR}/files/mac/opencs.icns) @@ -175,6 +175,7 @@ endif(APPLE) add_executable(opencs MACOSX_BUNDLE + ${OENGINE_BULLET} ${OPENCS_SRC} ${OPENCS_UI_HDR} ${OPENCS_MOC_SRC} @@ -203,6 +204,7 @@ target_link_libraries(opencs ${OGRE_STATIC_PLUGINS} ${SHINY_LIBRARIES} ${Boost_LIBRARIES} + ${BULLET_LIBRARIES} ${QT_LIBRARIES} components ) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 8f419fa79..591667ebb 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -21,7 +21,7 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) : mUserSettings (mCfgMgr), mOverlaySystem (0), mDocumentManager (mCfgMgr), - mViewManager (mDocumentManager), + mViewManager (mDocumentManager), mPhysicsManager (0), mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL) { std::pair > config = readConfig(); @@ -34,6 +34,7 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) ogreInit.init ((mCfgMgr.getUserConfigPath() / "opencsOgre.log").string()); mOverlaySystem.reset (new CSVRender::OverlaySystem); + mPhysicsManager.reset (new CSVWorld::PhysicsManager); Bsa::registerResources (Files::Collections (config.first, !mFsStrict), config.second, true, mFsStrict); diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index cd39d53a4..d55b0e873 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -28,6 +28,7 @@ #include "view/settings/dialog.hpp" #include "view/render/overlaysystem.hpp" +#include "view/world/physicsmanager.hpp" namespace OgreInit { @@ -44,6 +45,7 @@ namespace CS Files::ConfigurationManager mCfgMgr; CSMSettings::UserSettings mUserSettings; std::auto_ptr mOverlaySystem; + std::auto_ptr mPhysicsManager; CSMDoc::DocumentManager mDocumentManager; CSVDoc::ViewManager mViewManager; CSVDoc::StartupDialogue mStartup; diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index a25e36fc1..6f830e6e3 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1299,7 +1299,7 @@ namespace CSMWorld { ESXRecordT record2 = record.get(); - ESM::Position& position = record.get().*mPosition; + ESM::Position& position = record2.*mPosition; position.pos[mIndex] = data.toFloat(); @@ -1333,7 +1333,7 @@ namespace CSMWorld { ESXRecordT record2 = record.get(); - ESM::Position& position = record.get().*mPosition; + ESM::Position& position = record2.*mPosition; position.rot[mIndex] = data.toFloat(); diff --git a/apps/opencs/view/doc/subview.cpp b/apps/opencs/view/doc/subview.cpp index a9dce25be..a399b5b5b 100644 --- a/apps/opencs/view/doc/subview.cpp +++ b/apps/opencs/view/doc/subview.cpp @@ -42,4 +42,4 @@ std::string CSVDoc::SubView::getTitle() const void CSVDoc::SubView::closeRequest() { emit closeRequest (this); -} \ No newline at end of file +} diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 8a6665cf2..8b5efbea7 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -16,6 +16,7 @@ #include "../../model/world/idtable.hpp" #include "../world/subviews.hpp" +#include "../world/physicsmanager.hpp" #include "../tools/subviews.hpp" @@ -406,6 +407,8 @@ CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int to mSubViewFactory.add (CSMWorld::UniversalId::Type_RunLog, new SubViewFactory); connect (mOperations, SIGNAL (abortOperation (int)), this, SLOT (abortOperation (int))); + + CSVWorld::PhysicsManager::instance()->setupPhysics(document); } CSVDoc::View::~View() diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 5f6b6b46a..c4fd66884 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -16,6 +16,7 @@ #include "../world/vartypedelegate.hpp" #include "../world/recordstatusdelegate.hpp" #include "../world/idtypedelegate.hpp" +#include "../world/physicsmanager.hpp" #include "../../model/settings/usersettings.hpp" @@ -218,6 +219,7 @@ void CSVDoc::ViewManager::removeDocAndView (CSMDoc::Document *document) mDocumentManager.removeDocument(document); (*iter)->deleteLater(); mViews.erase (iter); + CSVWorld::PhysicsManager::instance()->removeDocument(document); updateIndices(); return; diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 9ff24780c..75e11cc10 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -5,10 +5,12 @@ #include #include +#include #include "../../model/world/idtable.hpp" #include "../../model/world/columns.hpp" #include "../../model/world/data.hpp" +#include "../world/physicssystem.hpp" #include "elements.hpp" #include "terrainstorage.hpp" @@ -49,7 +51,7 @@ bool CSVRender::Cell::addObjects (int start, int end) std::string id = Misc::StringUtils::lowerCase (references.data ( references.index (i, idColumn)).toString().toUtf8().constData()); - mObjects.insert (std::make_pair (id, new Object (mData, mCellNode, id, false))); + mObjects.insert (std::make_pair (id, new Object (mData, mCellNode, id, false, mPhysics))); modified = true; } } @@ -58,8 +60,8 @@ bool CSVRender::Cell::addObjects (int start, int end) } CSVRender::Cell::Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, - const std::string& id, const Ogre::Vector3& origin) -: mData (data), mId (Misc::StringUtils::lowerCase (id)) + const std::string& id, CSVWorld::PhysicsSystem *physics, const Ogre::Vector3& origin) +: mData (data), mId (Misc::StringUtils::lowerCase (id)), mSceneMgr(sceneManager), mPhysics(physics) { mCellNode = sceneManager->getRootSceneNode()->createChildSceneNode(); mCellNode->setPosition (origin); @@ -81,11 +83,23 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, const ESM::Land* esmLand = land.getRecord(mId).get().mLand.get(); mTerrain->loadCell(esmLand->mX, esmLand->mY); + + if(esmLand) + { + float verts = ESM::Land::LAND_SIZE; + float worldsize = ESM::Land::REAL_SIZE; + mX = esmLand->mX; + mY = esmLand->mY; + mPhysics->addHeightField(sceneManager, + esmLand->mLandData->mHeights, mX, mY, 0, worldsize / (verts-1), verts); + } } } CSVRender::Cell::~Cell() { + mPhysics->removeHeightField(mSceneMgr, mX, mY); + for (std::map::iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) delete iter->second; @@ -178,7 +192,7 @@ bool CSVRender::Cell::referenceDataChanged (const QModelIndex& topLeft, for (std::map::iterator iter (ids.begin()); iter!=ids.end(); ++iter) { mObjects.insert (std::make_pair ( - iter->first, new Object (mData, mCellNode, iter->first, false))); + iter->first, new Object (mData, mCellNode, iter->first, false, mPhysics))); modified = true; } diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 98056c354..8bc58714a 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -23,6 +23,11 @@ namespace CSMWorld class Data; } +namespace CSVWorld +{ + class PhysicsSystem; +} + namespace CSVRender { class Cell @@ -32,6 +37,10 @@ namespace CSVRender Ogre::SceneNode *mCellNode; std::map mObjects; std::auto_ptr mTerrain; + CSVWorld::PhysicsSystem *mPhysics; + Ogre::SceneManager *mSceneMgr; + int mX; + int mY; /// Ignored if cell does not have an object with the given ID. /// @@ -45,8 +54,8 @@ namespace CSVRender public: - Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, - const std::string& id, const Ogre::Vector3& origin = Ogre::Vector3 (0, 0, 0)); + Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, const std::string& id, + CSVWorld::PhysicsSystem *physics, const Ogre::Vector3& origin = Ogre::Vector3 (0, 0, 0)); ~Cell(); diff --git a/apps/opencs/view/render/mousestate.cpp b/apps/opencs/view/render/mousestate.cpp new file mode 100644 index 000000000..af835d0a5 --- /dev/null +++ b/apps/opencs/view/render/mousestate.cpp @@ -0,0 +1,461 @@ +#include "mousestate.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +#include "../../model/settings/usersettings.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/universalid.hpp" +#include "../world/physicssystem.hpp" + +#include "elements.hpp" +#include "worldspacewidget.hpp" + +namespace CSVRender +{ + // mouse picking + // FIXME: need to virtualise mouse buttons + // + // State machine: + // + // [default] mousePressEvent->check if the mouse is pointing at an object + // if yes, create collision planes then go to [grab] + // else check for terrain + // + // [grab] mouseReleaseEvent->if same button and new obj, go to [edit] + // mouseMoveEvent->if same button, go to [drag] + // other mouse events or buttons, go back to [default] (i.e. like 'cancel') + // + // [drag] mouseReleaseEvent->if same button, place the object at the new + // location, update the document then go to [edit] + // mouseMoveEvent->update position to the user based on ray to the collision + // planes and render the object at the new location, but do not update + // the document yet + // + // [edit] TODO, probably fine positional adjustments or rotations; clone/delete? + // + // + // press press (obj) + // [default] --------> [grab] <-------------------- [edit] + // ^ (obj) | | ------> [drag] -----> ^ + // | | | move ^ | release | + // | | | | | | + // | | | +-+ | + // | | | move | + // +----------------+ +--------------------------+ + // release release + // (same obj) (new obj) + // + // + + MouseState::MouseState(WorldspaceWidget *parent) + : mParent(parent), mPhysics(parent->getPhysics()), mSceneManager(parent->getSceneManager()) + , mCurrentObj(""), mMouseState(Mouse_Default), mOldPos(0,0), mMouseEventTimer(0), mPlane(0) + , mGrabbedSceneNode(""), mOrigObjPos(Ogre::Vector3()), mOrigMousePos(Ogre::Vector3()) + , mCurrentMousePos(Ogre::Vector3()), mOffset(0.0f) + , mColIndexPosX(0), mColIndexPosY(0), mColIndexPosZ(0), mIdTableModel(0) + { + const CSMWorld::RefCollection& references = mParent->mDocument.getData().getReferences(); + + mColIndexPosX = references.findColumnIndex(CSMWorld::Columns::ColumnId_PositionXPos); + mColIndexPosY = references.findColumnIndex(CSMWorld::Columns::ColumnId_PositionYPos); + mColIndexPosZ = references.findColumnIndex(CSMWorld::Columns::ColumnId_PositionZPos); + + mIdTableModel = static_cast( + mParent->mDocument.getData().getTableModel(CSMWorld::UniversalId::Type_Reference)); + + mMouseEventTimer = new QElapsedTimer(); + mMouseEventTimer->invalidate(); + + std::pair planeRes = planeAxis(); + mPlane = new Ogre::Plane(planeRes.first, 0); + Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().createPlane("mouse", + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + *mPlane, + 300000,300000, // FIXME: use far clip dist? + 1,1, // segments + true, // normals + 1, // numTexCoordSets + 1,1, // uTile, vTile + planeRes.second // upVector + ); + } + + MouseState::~MouseState () + { + delete mMouseEventTimer; + delete mPlane; + } + + void MouseState::mouseMoveEvent (QMouseEvent *event) + { + switch(mMouseState) + { + case Mouse_Grab: + { + // check if min elapsed time to stop false detection of drag + if(!mMouseEventTimer->isValid() || !mMouseEventTimer->hasExpired(100)) // ms + break; + + mMouseEventTimer->invalidate(); + mMouseState = Mouse_Drag; + + /* FALL_THROUGH */ + } + case Mouse_Drag: + { + if(event->pos() != mOldPos) // TODO: maybe don't update less than a quantum? + { + mOldPos = event->pos(); + + // ray test against the plane to provide feedback to the user the + // relative movement of the object on the x-y plane + std::pair planeResult = mousePositionOnPlane(event->pos(), *mPlane); + if(planeResult.first) + { + if(mGrabbedSceneNode != "") + { + std::pair planeRes = planeAxis(); + Ogre::Vector3 pos = mOrigObjPos + planeRes.first*mOffset; + mSceneManager->getSceneNode(mGrabbedSceneNode)->setPosition(pos+planeResult.second-mOrigMousePos); + mCurrentMousePos = planeResult.second; + mPhysics->moveSceneNodes(mGrabbedSceneNode, pos+planeResult.second-mOrigMousePos); + updateSceneWidgets(); + } + } + } + break; + } + case Mouse_Edit: + case Mouse_Default: + { + break; // error event, ignore + } + /* NO_DEFAULT_CASE */ + } + } + + void MouseState::mousePressEvent (QMouseEvent *event) + { + switch(mMouseState) + { + case Mouse_Grab: + case Mouse_Drag: + { + break; + } + case Mouse_Edit: + case Mouse_Default: + { + if(event->buttons() & Qt::RightButton) + { + std::pair result = objectUnderCursor(event->x(), event->y()); + if(result.first == "") + break; + + mGrabbedSceneNode = result.first; + // ray test agaist the plane to get a starting position of the + // mouse in relation to the object position + std::pair planeRes = planeAxis(); + mPlane->redefine(planeRes.first, result.second); + std::pair planeResult = mousePositionOnPlane(event->pos(), *mPlane); + if(planeResult.first) + { + mOrigMousePos = planeResult.second; + mCurrentMousePos = planeResult.second; + mOffset = 0.0f; + } + + mOrigObjPos = mSceneManager->getSceneNode(mGrabbedSceneNode)->getPosition(); + mMouseEventTimer->start(); + + mMouseState = Mouse_Grab; + } + break; + } + /* NO_DEFAULT_CASE */ + } + } + + void MouseState::mouseReleaseEvent (QMouseEvent *event) + { + switch(mMouseState) + { + case Mouse_Grab: + { + std::pair result = objectUnderCursor(event->x(), event->y()); + if(result.first != "") + { + if(result.first == mCurrentObj) + { + // unselect object + mMouseState = Mouse_Default; + mCurrentObj = ""; + } + else + { + // select object + mMouseState = Mouse_Edit; + mCurrentObj = result.first; + + } + } + break; + } + case Mouse_Drag: + { + // final placement + std::pair planeResult = mousePositionOnPlane(event->pos(), *mPlane); + if(planeResult.first) + { + if(mGrabbedSceneNode != "") + { + std::pair planeRes = planeAxis(); + Ogre::Vector3 pos = mOrigObjPos+planeRes.first*mOffset+planeResult.second-mOrigMousePos; + // use the saved scene node name since the physics model has not moved yet + std::string referenceId = mPhysics->sceneNodeToRefId(mGrabbedSceneNode); + + mParent->mDocument.getUndoStack().beginMacro (QObject::tr("Move Object")); + mParent->mDocument.getUndoStack().push(new CSMWorld::ModifyCommand(*mIdTableModel, + mIdTableModel->getModelIndex(referenceId, mColIndexPosX), pos.x)); + mParent->mDocument.getUndoStack().push(new CSMWorld::ModifyCommand(*mIdTableModel, + mIdTableModel->getModelIndex(referenceId, mColIndexPosY), pos.y)); + mParent->mDocument.getUndoStack().push(new CSMWorld::ModifyCommand(*mIdTableModel, + mIdTableModel->getModelIndex(referenceId, mColIndexPosZ), pos.z)); + mParent->mDocument.getUndoStack().endMacro(); + + // FIXME: highlight current object? + //mCurrentObj = mGrabbedSceneNode; // FIXME: doesn't work? + mCurrentObj = ""; // whether the object is selected + + mMouseState = Mouse_Edit; + + // reset states + mCurrentMousePos = Ogre::Vector3(); // mouse pos to use in wheel event + mOrigMousePos = Ogre::Vector3(); // starting pos of mouse in world space + mOrigObjPos = Ogre::Vector3(); // starting pos of object in world space + mGrabbedSceneNode = ""; // id of the object + mOffset = 0.0f; // used for z-axis movement + mOldPos = QPoint(0, 0); // to calculate relative movement of mouse + } + } + break; + } + case Mouse_Edit: + case Mouse_Default: + { + // probably terrain, check + std::pair result = terrainUnderCursor(event->x(), event->y()); + if(result.first != "") + { + // FIXME: terrain editing + } + break; + } + /* NO_DEFAULT_CASE */ + } + mMouseEventTimer->invalidate(); + } + + void MouseState::mouseDoubleClickEvent (QMouseEvent *event) + { + event->ignore(); + } + + bool MouseState::wheelEvent (QWheelEvent *event) + { + switch(mMouseState) + { + case Mouse_Grab: + mMouseState = Mouse_Drag; + + /* FALL_THROUGH */ + case Mouse_Drag: + { + // move the object along the z axis during Mouse_Drag or Mouse_Grab + if (event->delta()) + { + // seems positive is up and negative is down + mOffset += (event->delta()/1); // FIXME: arbitrary number, make config option? + + std::pair planeRes = planeAxis(); + Ogre::Vector3 pos = mOrigObjPos + planeRes.first*mOffset; + mSceneManager->getSceneNode(mGrabbedSceneNode)->setPosition(pos+mCurrentMousePos-mOrigMousePos); + mPhysics->moveSceneNodes(mGrabbedSceneNode, pos+mCurrentMousePos-mOrigMousePos); + updateSceneWidgets(); + } + break; + } + case Mouse_Edit: + case Mouse_Default: + { + return false; + } + /* NO_DEFAULT_CASE */ + } + + return true; + } + + void MouseState::cancelDrag() + { + switch(mMouseState) + { + case Mouse_Grab: + case Mouse_Drag: + { + // cancel operation & return the object to the original position + mSceneManager->getSceneNode(mGrabbedSceneNode)->setPosition(mOrigObjPos); + // update all SceneWidgets and their SceneManagers + mPhysics->moveSceneNodes(mGrabbedSceneNode, mOrigObjPos); + updateSceneWidgets(); + + // reset states + mMouseState = Mouse_Default; + mCurrentMousePos = Ogre::Vector3(); + mOrigMousePos = Ogre::Vector3(); + mOrigObjPos = Ogre::Vector3(); + mGrabbedSceneNode = ""; + mCurrentObj = ""; + mOldPos = QPoint(0, 0); + mMouseEventTimer->invalidate(); + mOffset = 0.0f; + + break; + } + case Mouse_Edit: + case Mouse_Default: + { + break; + } + /* NO_DEFAULT_CASE */ + } + } + + //plane Z, upvector Y, mOffset z : x-y plane, wheel up/down + //plane Y, upvector X, mOffset y : y-z plane, wheel left/right + //plane X, upvector Y, mOffset x : x-z plane, wheel closer/further + std::pair MouseState::planeAxis() + { + bool screenCoord = true; + Ogre::Vector3 dir = getCamera()->getDerivedDirection(); + + QString wheelDir = "Closer/Further"; + if(wheelDir == "Left/Right") + { + if(screenCoord) + return std::make_pair(getCamera()->getDerivedRight(), getCamera()->getDerivedUp()); + else + return std::make_pair(Ogre::Vector3::UNIT_Y, Ogre::Vector3::UNIT_Z); + } + else if(wheelDir == "Up/Down") + { + if(screenCoord) + return std::make_pair(getCamera()->getDerivedUp(), Ogre::Vector3(-dir.x, -dir.y, -dir.z)); + else + return std::make_pair(Ogre::Vector3::UNIT_Z, Ogre::Vector3::UNIT_X); + } + else + { + if(screenCoord) + return std::make_pair(Ogre::Vector3(-dir.x, -dir.y, -dir.z), getCamera()->getDerivedRight()); + else + return std::make_pair(Ogre::Vector3::UNIT_X, Ogre::Vector3::UNIT_Y); + } + } + + std::pair MouseState::mousePositionOnPlane(const QPoint &pos, const Ogre::Plane &plane) + { + // using a really small value seems to mess up with the projections + float nearClipDistance = getCamera()->getNearClipDistance(); // save existing + getCamera()->setNearClipDistance(10.0f); // arbitrary number + Ogre::Ray mouseRay = getCamera()->getCameraToViewportRay( + (float) pos.x() / getViewport()->getActualWidth(), + (float) pos.y() / getViewport()->getActualHeight()); + getCamera()->setNearClipDistance(nearClipDistance); // restore + std::pair planeResult = mouseRay.intersects(plane); + + if(planeResult.first) + return std::make_pair(true, mouseRay.getPoint(planeResult.second)); + else + return std::make_pair(false, Ogre::Vector3()); // should only happen if the plane is too small + } + + std::pair MouseState::terrainUnderCursor(const int mouseX, const int mouseY) + { + if(!getViewport()) + return std::make_pair("", Ogre::Vector3()); + + float x = (float) mouseX / getViewport()->getActualWidth(); + float y = (float) mouseY / getViewport()->getActualHeight(); + + std::pair result = mPhysics->castRay(x, y, mSceneManager, getCamera()); + if(result.first != "") + { + // FIXME: is there a better way to distinguish terrain from objects? + QString name = QString(result.first.c_str()); + if(name.contains(QRegExp("^HeightField"))) + { + return result; + } + } + + return std::make_pair("", Ogre::Vector3()); + } + + std::pair MouseState::objectUnderCursor(const int mouseX, const int mouseY) + { + if(!getViewport()) + return std::make_pair("", Ogre::Vector3()); + + float x = (float) mouseX / getViewport()->getActualWidth(); + float y = (float) mouseY / getViewport()->getActualHeight(); + + std::pair result = mPhysics->castRay(x, y, mSceneManager, getCamera()); + if(result.first != "") + { + // NOTE: anything not terrain is assumed to be an object + QString name = QString(result.first.c_str()); + if(!name.contains(QRegExp("^HeightField"))) + { + uint32_t visibilityMask = getViewport()->getVisibilityMask(); + bool ignoreObjects = !(visibilityMask & (uint32_t)CSVRender::Element_Reference); + + if(!ignoreObjects && mSceneManager->hasSceneNode(result.first)) + { + return result; + } + } + } + + return std::make_pair("", Ogre::Vector3()); + } + + void MouseState::updateSceneWidgets() + { + std::map sceneWidgets = mPhysics->sceneWidgets(); + + std::map::iterator iter = sceneWidgets.begin(); + for(; iter != sceneWidgets.end(); ++iter) + { + (*iter).second->updateScene(); + } + } + + Ogre::Camera *MouseState::getCamera() + { + return mParent->getCamera(); + } + + Ogre::Viewport *MouseState::getViewport() + { + return mParent->getCamera()->getViewport(); + } +} diff --git a/apps/opencs/view/render/mousestate.hpp b/apps/opencs/view/render/mousestate.hpp new file mode 100644 index 000000000..27907bb33 --- /dev/null +++ b/apps/opencs/view/render/mousestate.hpp @@ -0,0 +1,90 @@ +#ifndef OPENCS_VIEW_MOUSESTATE_H +#define OPENCS_VIEW_MOUSESTATE_H + +#include +#include +#include + +class QElapsedTimer; +class QMouseEvent; +class QWheelEvent; + +namespace Ogre +{ + class Plane; + class SceneManager; + class Camera; + class Viewport; +} + +namespace CSVWorld +{ + class PhysicsSystem; +} + +namespace CSMWorld +{ + class IdTable; +} + +namespace CSVRender +{ + class WorldspaceWidget; + + class MouseState + { + enum MouseStates + { + Mouse_Grab, + Mouse_Drag, + Mouse_Edit, + Mouse_Default + }; + MouseStates mMouseState; + + WorldspaceWidget *mParent; + CSVWorld::PhysicsSystem *mPhysics; // local copy + Ogre::SceneManager *mSceneManager; // local copy + + QPoint mOldPos; + std::string mCurrentObj; + std::string mGrabbedSceneNode; + QElapsedTimer *mMouseEventTimer; + Ogre::Plane *mPlane; + Ogre::Vector3 mOrigObjPos; + Ogre::Vector3 mOrigMousePos; + Ogre::Vector3 mCurrentMousePos; + float mOffset; + + CSMWorld::IdTable *mIdTableModel; + int mColIndexPosX; + int mColIndexPosY; + int mColIndexPosZ; + + public: + + MouseState(WorldspaceWidget *parent); + ~MouseState(); + + void mouseMoveEvent (QMouseEvent *event); + void mousePressEvent (QMouseEvent *event); + void mouseReleaseEvent (QMouseEvent *event); + void mouseDoubleClickEvent (QMouseEvent *event); + bool wheelEvent (QWheelEvent *event); + + void cancelDrag(); + + private: + + std::pair mousePositionOnPlane(const QPoint &pos, const Ogre::Plane &plane); + std::pair terrainUnderCursor(const int mouseX, const int mouseY); + std::pair objectUnderCursor(const int mouseX, const int mouseY); + std::pair planeAxis(); + void updateSceneWidgets(); + + Ogre::Camera *getCamera(); // friend access + Ogre::Viewport *getViewport(); // friend access + }; +} + +#endif // OPENCS_VIEW_MOUSESTATE_H diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 359392d81..21219db8f 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -9,6 +9,8 @@ #include "../../model/world/ref.hpp" #include "../../model/world/refidcollection.hpp" +#include "../world/physicssystem.hpp" + #include "elements.hpp" void CSVRender::Object::clearSceneNode (Ogre::SceneNode *node) @@ -38,6 +40,9 @@ void CSVRender::Object::clear() void CSVRender::Object::update() { + if(!mObject.isNull()) + mPhysics->removePhysicsObject(mBase->getName()); + clear(); std::string model; @@ -73,6 +78,23 @@ void CSVRender::Object::update() { mObject = NifOgre::Loader::createObjects (mBase, "Meshes\\" + model); mObject->setVisibilityFlags (Element_Reference); + + if (mPhysics && !mReferenceId.empty()) + { + const CSMWorld::CellRef& reference = getReference(); + + // position + Ogre::Vector3 position; + if (!mForceBaseToZero) + position = Ogre::Vector3(reference.mPos.pos[0], reference.mPos.pos[1], reference.mPos.pos[2]); + + // orientation + Ogre::Quaternion xr (Ogre::Radian (-reference.mPos.rot[0]), Ogre::Vector3::UNIT_X); + Ogre::Quaternion yr (Ogre::Radian (-reference.mPos.rot[1]), Ogre::Vector3::UNIT_Y); + Ogre::Quaternion zr (Ogre::Radian (-reference.mPos.rot[2]), Ogre::Vector3::UNIT_Z); + + mPhysics->addObject("meshes\\" + model, mBase->getName(), mReferenceId, reference.mScale, position, xr*yr*zr); + } } } @@ -110,8 +132,9 @@ const CSMWorld::CellRef& CSVRender::Object::getReference() const } CSVRender::Object::Object (const CSMWorld::Data& data, Ogre::SceneNode *cellNode, - const std::string& id, bool referenceable, bool forceBaseToZero) -: mData (data), mBase (0), mForceBaseToZero (forceBaseToZero) + const std::string& id, bool referenceable, CSVWorld::PhysicsSystem *physics, + bool forceBaseToZero) +: mData (data), mBase (0), mForceBaseToZero (forceBaseToZero), mPhysics(physics) { mBase = cellNode->createChildSceneNode(); @@ -133,6 +156,8 @@ CSVRender::Object::~Object() { clear(); + mPhysics->removeObject(mBase->getName()); + if (mBase) mBase->getCreator()->destroySceneNode (mBase); } @@ -214,4 +239,4 @@ std::string CSVRender::Object::getReferenceId() const std::string CSVRender::Object::getReferenceableId() const { return mReferenceableId; -} \ No newline at end of file +} diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index df39d7393..eba2dc814 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -16,6 +16,11 @@ namespace CSMWorld class CellRef; } +namespace CSVWorld +{ + class PhysicsSystem; +} + namespace CSVRender { class Object @@ -26,6 +31,7 @@ namespace CSVRender Ogre::SceneNode *mBase; NifOgre::ObjectScenePtr mObject; bool mForceBaseToZero; + CSVWorld::PhysicsSystem *mPhysics; /// Not implemented Object (const Object&); @@ -51,7 +57,8 @@ namespace CSVRender public: Object (const CSMWorld::Data& data, Ogre::SceneNode *cellNode, - const std::string& id, bool referenceable, bool forceBaseToZero = false); + const std::string& id, bool referenceable, + CSVWorld::PhysicsSystem *physics = NULL, bool forceBaseToZero = false); /// \param forceBaseToZero If this is a reference ignore the coordinates and place /// it at 0, 0, 0 instead. @@ -77,4 +84,4 @@ namespace CSVRender }; } -#endif \ No newline at end of file +#endif diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 8b143e93f..49e7e1f09 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -108,7 +108,8 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() if (index > 0 && cells.getRecord (index).mState!=CSMWorld::RecordBase::State_Deleted && mCells.find (*iter)==mCells.end()) { - Cell *cell = new Cell (mDocument.getData(), getSceneManager(), iter->getId (mWorldspace)); + Cell *cell = new Cell (mDocument.getData(), getSceneManager(), + iter->getId (mWorldspace), getPhysics()); mCells.insert (std::make_pair (*iter, cell)); float height = cell->getTerrainHeightAt(Ogre::Vector3( @@ -169,7 +170,7 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() return modified; } -void CSVRender::PagedWorldspaceWidget::mouseReleaseEvent (QMouseEvent *event) +void CSVRender::PagedWorldspaceWidget::mousePressEvent (QMouseEvent *event) { if(event->button() == Qt::RightButton) { @@ -179,19 +180,34 @@ void CSVRender::PagedWorldspaceWidget::mouseReleaseEvent (QMouseEvent *event) if(mDisplayCellCoord && iter->second->isEnabled() && iter->second->container().contains(event->x(), event->y())) { - std::cout << "clicked: " << iter->second->getCaption() << std::endl; - break; + return; } } } + WorldspaceWidget::mousePressEvent(event); } -void CSVRender::PagedWorldspaceWidget::mouseDoubleClickEvent (QMouseEvent *event) +void CSVRender::PagedWorldspaceWidget::mouseReleaseEvent (QMouseEvent *event) { if(event->button() == Qt::RightButton) { - std::cout << "double clicked" << std::endl; + std::map::iterator iter = mTextOverlays.begin(); + for(; iter != mTextOverlays.end(); ++iter) + { + if(mDisplayCellCoord && + iter->second->isEnabled() && iter->second->container().contains(event->x(), event->y())) + { + std::cout << "clicked: " << iter->second->getCaption() << std::endl; + return; + } + } } + WorldspaceWidget::mouseReleaseEvent(event); +} + +void CSVRender::PagedWorldspaceWidget::mouseDoubleClickEvent (QMouseEvent *event) +{ + WorldspaceWidget::mouseDoubleClickEvent(event); } void CSVRender::PagedWorldspaceWidget::updateOverlay() @@ -429,7 +445,6 @@ CSVRender::WorldspaceWidget::dropRequirments CSVRender::PagedWorldspaceWidget::g } } - unsigned int CSVRender::PagedWorldspaceWidget::getElementMask() const { return WorldspaceWidget::getElementMask() | mControlElements->getSelection(); diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 17b6d10c5..fe79e761e 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -81,6 +81,8 @@ namespace CSVRender virtual void updateOverlay(); + virtual void mousePressEvent (QMouseEvent *event); + virtual void mouseReleaseEvent (QMouseEvent *event); virtual void mouseDoubleClickEvent (QMouseEvent *event); diff --git a/apps/opencs/view/render/previewwidget.cpp b/apps/opencs/view/render/previewwidget.cpp index 75b4e9396..f972c6361 100644 --- a/apps/opencs/view/render/previewwidget.cpp +++ b/apps/opencs/view/render/previewwidget.cpp @@ -10,7 +10,7 @@ CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data, const std::string& id, bool referenceable, QWidget *parent) : SceneWidget (parent), mData (data), - mObject (data, getSceneManager()->getRootSceneNode(), id, referenceable, true) + mObject (data, getSceneManager()->getRootSceneNode(), id, referenceable, NULL, true) { setNavigation (&mOrbit); diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 47d8b80e9..55cf039fc 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -410,6 +410,11 @@ namespace CSVRender } } + void SceneWidget::updateScene() + { + flagAsModified(); + } + void SceneWidget::updateOverlay() { } diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 1adbf3f17..699d6a7a5 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -47,6 +47,8 @@ namespace CSVRender virtual void setVisibilityMask (unsigned int mask); + virtual void updateScene(); + protected: void setNavigation (Navigation *navigation); @@ -71,23 +73,23 @@ namespace CSVRender virtual void mouseReleaseEvent (QMouseEvent *event); + virtual void mouseMoveEvent (QMouseEvent *event); + + void wheelEvent (QWheelEvent *event); + + void keyPressEvent (QKeyEvent *event); + private: void paintEvent(QPaintEvent* e); void resizeEvent(QResizeEvent* e); bool event(QEvent* e); - void keyPressEvent (QKeyEvent *event); - void keyReleaseEvent (QKeyEvent *event); void focusOutEvent (QFocusEvent *event); - void wheelEvent (QWheelEvent *event); - void leaveEvent (QEvent *event); - void mouseMoveEvent (QMouseEvent *event); - void updateOgreWindow(); void setLighting (Lighting *lighting); diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index aab3791fc..8012b1b24 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -56,7 +56,7 @@ CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& update(); - mCell.reset (new Cell (document.getData(), getSceneManager(), mCellId)); + mCell.reset (new Cell (document.getData(), getSceneManager(), mCellId, getPhysics())); } void CSVRender::UnpagedWorldspaceWidget::cellDataChanged (const QModelIndex& topLeft, @@ -98,7 +98,7 @@ bool CSVRender::UnpagedWorldspaceWidget::handleDrop (const std::vectorgetId(); - mCell.reset (new Cell (getDocument().getData(), getSceneManager(), mCellId)); + mCell.reset (new Cell (getDocument().getData(), getSceneManager(), mCellId, getPhysics())); update(); emit cellChanged(*data.begin()); diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index ebb15ea2c..9c8676064 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -16,10 +16,13 @@ #include "../widget/scenetooltoggle.hpp" #include "../widget/scenetoolrun.hpp" +#include "../world/physicsmanager.hpp" +#include "../world/physicssystem.hpp" + #include "elements.hpp" CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) -: SceneWidget (parent), mDocument(document), mSceneElements(0), mRun(0) +: SceneWidget (parent), mDocument(document), mSceneElements(0), mRun(0), mPhysics(0), mMouse(0) { setAcceptDrops(true); @@ -50,6 +53,19 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg this, SLOT (debugProfileDataChanged (const QModelIndex&, const QModelIndex&))); connect (debugProfiles, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (debugProfileAboutToBeRemoved (const QModelIndex&, int, int))); + + // associate WorldSpaceWidgets (and their SceneManagers) with Documents + // then create physics if there is a new document + mPhysics = CSVWorld::PhysicsManager::instance()->addSceneWidget(document, this); + mPhysics->addSceneManager(getSceneManager(), this); + mMouse = new MouseState(this); +} + +CSVRender::WorldspaceWidget::~WorldspaceWidget () +{ + delete mMouse; + mPhysics->removeSceneManager(getSceneManager()); + CSVWorld::PhysicsManager::instance()->removeSceneWidget(this); } void CSVRender::WorldspaceWidget::selectNavigationMode (const std::string& mode) @@ -319,3 +335,66 @@ void CSVRender::WorldspaceWidget::elementSelectionChanged() void CSVRender::WorldspaceWidget::updateOverlay() { } + +CSVWorld::PhysicsSystem *CSVRender::WorldspaceWidget::getPhysics() +{ + assert(mPhysics); + return mPhysics; +} + +void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) +{ + if(event->buttons() & Qt::RightButton) + { + mMouse->mouseMoveEvent(event); + } + SceneWidget::mouseMoveEvent(event); +} + +void CSVRender::WorldspaceWidget::mousePressEvent (QMouseEvent *event) +{ + if(event->buttons() & Qt::RightButton) + { + mMouse->mousePressEvent(event); + } + //SceneWidget::mousePressEvent(event); +} + +void CSVRender::WorldspaceWidget::mouseReleaseEvent (QMouseEvent *event) +{ + if(event->button() == Qt::RightButton) + { + if(!getViewport()) + { + SceneWidget::mouseReleaseEvent(event); + return; + } + mMouse->mouseReleaseEvent(event); + } + SceneWidget::mouseReleaseEvent(event); +} + +void CSVRender::WorldspaceWidget::mouseDoubleClickEvent (QMouseEvent *event) +{ + if(event->button() == Qt::RightButton) + { + mMouse->mouseDoubleClickEvent(event); + } + //SceneWidget::mouseDoubleClickEvent(event); +} + +void CSVRender::WorldspaceWidget::wheelEvent (QWheelEvent *event) +{ + if(!mMouse->wheelEvent(event)) + SceneWidget::wheelEvent(event); +} + +void CSVRender::WorldspaceWidget::keyPressEvent (QKeyEvent *event) +{ + if(event->key() == Qt::Key_Escape) + { + mMouse->cancelDrag(); + } + else + SceneWidget::keyPressEvent(event); +} diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 4830d772b..5bad3933d 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -2,6 +2,7 @@ #define OPENCS_VIEW_WORLDSPACEWIDGET_H #include "scenewidget.hpp" +#include "mousestate.hpp" #include "navigation1st.hpp" #include "navigationfree.hpp" @@ -13,6 +14,7 @@ namespace CSMWorld { class UniversalId; } + namespace CSVWidget { class SceneToolMode; @@ -21,6 +23,11 @@ namespace CSVWidget class SceneToolRun; } +namespace CSVWorld +{ + class PhysicsSystem; +} + namespace CSVRender { class WorldspaceWidget : public SceneWidget @@ -33,6 +40,8 @@ namespace CSVRender CSVWidget::SceneToolToggle *mSceneElements; CSVWidget::SceneToolRun *mRun; CSMDoc::Document& mDocument; + CSVWorld::PhysicsSystem *mPhysics; + MouseState *mMouse; public: @@ -53,6 +62,7 @@ namespace CSVRender }; WorldspaceWidget (CSMDoc::Document& document, QWidget *parent = 0); + ~WorldspaceWidget (); CSVWidget::SceneToolMode *makeNavigationSelector (CSVWidget::SceneToolbar *parent); ///< \attention The created tool is not added to the toolbar (via addTool). Doing that @@ -90,6 +100,15 @@ namespace CSVRender virtual void updateOverlay(); + CSVWorld::PhysicsSystem *getPhysics(); + + virtual void mouseMoveEvent (QMouseEvent *event); + virtual void mousePressEvent (QMouseEvent *event); + virtual void mouseReleaseEvent (QMouseEvent *event); + virtual void mouseDoubleClickEvent (QMouseEvent *event); + virtual void wheelEvent (QWheelEvent *event); + virtual void keyPressEvent (QKeyEvent *event); + private: void dragEnterEvent(QDragEnterEvent *event); @@ -132,7 +151,10 @@ namespace CSVRender signals: void closeRequest(); + void dataDropped(const std::vector& data); + + friend class MouseState; }; } diff --git a/apps/opencs/view/world/physicsmanager.cpp b/apps/opencs/view/world/physicsmanager.cpp new file mode 100644 index 000000000..f8e022e5d --- /dev/null +++ b/apps/opencs/view/world/physicsmanager.cpp @@ -0,0 +1,99 @@ +#include "physicsmanager.hpp" + +#include + +#include "../render/worldspacewidget.hpp" +#include "physicssystem.hpp" + +namespace CSVWorld +{ + PhysicsManager *PhysicsManager::mPhysicsManagerInstance = 0; + + PhysicsManager::PhysicsManager() + { + assert(!mPhysicsManagerInstance); + mPhysicsManagerInstance = this; + } + + PhysicsManager::~PhysicsManager() + { + std::map::iterator iter = mPhysics.begin(); + for(; iter != mPhysics.end(); ++iter) + delete iter->second; // shouldn't be any left but just in case + } + + PhysicsManager *PhysicsManager::instance() + { + assert(mPhysicsManagerInstance); + return mPhysicsManagerInstance; + } + + // create a physics instance per document, called from CSVDoc::View() to get Document* + void PhysicsManager::setupPhysics(CSMDoc::Document *doc) + { + std::map >::iterator iter = mSceneWidgets.find(doc); + if(iter == mSceneWidgets.end()) + { + mSceneWidgets[doc] = std::list (); // zero elements + mPhysics[doc] = new PhysicsSystem(); + } + } + + // destroy physics, called from CSVDoc::ViewManager + void PhysicsManager::removeDocument(CSMDoc::Document *doc) + { + std::map::iterator iter = mPhysics.find(doc); + if(iter != mPhysics.end()) + { + delete iter->second; + mPhysics.erase(iter); + } + + std::map >::iterator it = mSceneWidgets.find(doc); + if(it != mSceneWidgets.end()) + { + mSceneWidgets.erase(it); + } + } + + // called from CSVRender::WorldspaceWidget() to get widgets' association with Document& + PhysicsSystem *PhysicsManager::addSceneWidget(CSMDoc::Document &doc, CSVRender::WorldspaceWidget *widget) + { + CSVRender::SceneWidget *sceneWidget = static_cast(widget); + + std::map >::iterator iter = mSceneWidgets.begin(); + for(; iter != mSceneWidgets.end(); ++iter) + { + if((*iter).first == &doc) + { + (*iter).second.push_back(sceneWidget); + return mPhysics[(*iter).first]; // TODO: consider using shared_ptr instead + } + } + + throw std::runtime_error("No physics system found for the given document."); + } + + void PhysicsManager::removeSceneWidget(CSVRender::WorldspaceWidget *widget) + { + CSVRender::SceneWidget *sceneWidget = static_cast(widget); + + std::map >::iterator iter = mSceneWidgets.begin(); + for(; iter != mSceneWidgets.end(); ++iter) + { + std::list::iterator itWidget = (*iter).second.begin(); + for(; itWidget != (*iter).second.end(); ++itWidget) + { + if((*itWidget) == sceneWidget) + { + (*iter).second.erase(itWidget); + + //if((*iter).second.empty()) // last one for the document + // NOTE: do not delete physics until the document itself is closed + + break; + } + } + } + } +} diff --git a/apps/opencs/view/world/physicsmanager.hpp b/apps/opencs/view/world/physicsmanager.hpp new file mode 100644 index 000000000..e17c9ac84 --- /dev/null +++ b/apps/opencs/view/world/physicsmanager.hpp @@ -0,0 +1,54 @@ +#ifndef CSV_WORLD_PHYSICSMANAGER_H +#define CSV_WORLD_PHYSICSMANAGER_H + +#include +#include + +namespace Ogre +{ + class SceneManager; +} + +namespace CSMDoc +{ + class Document; +} + +namespace CSVRender +{ + class WorldspaceWidget; + class SceneWidget; +} + +namespace CSVWorld +{ + class PhysicsSystem; +} + +namespace CSVWorld +{ + class PhysicsManager + { + static PhysicsManager *mPhysicsManagerInstance; + + std::map > mSceneWidgets; + std::map mPhysics; + + public: + + PhysicsManager(); + ~PhysicsManager(); + + static PhysicsManager *instance(); + + void setupPhysics(CSMDoc::Document *); + + PhysicsSystem *addSceneWidget(CSMDoc::Document &doc, CSVRender::WorldspaceWidget *widget); + + void removeSceneWidget(CSVRender::WorldspaceWidget *widget); + + void removeDocument(CSMDoc::Document *doc); + }; +} + +#endif // CSV_WORLD_PHYSICSMANAGER_H diff --git a/apps/opencs/view/world/physicssystem.cpp b/apps/opencs/view/world/physicssystem.cpp new file mode 100644 index 000000000..73063b6be --- /dev/null +++ b/apps/opencs/view/world/physicssystem.cpp @@ -0,0 +1,325 @@ +#include "physicssystem.hpp" + +#include + +#include +#include +#include + +#include +#include +#include "../../model/settings/usersettings.hpp" +#include "../render/elements.hpp" + +namespace CSVWorld +{ + PhysicsSystem::PhysicsSystem() + { + // Create physics. shapeLoader is deleted by the physic engine + NifBullet::ManualBulletShapeLoader* shapeLoader = new NifBullet::ManualBulletShapeLoader(); + mEngine = new OEngine::Physic::PhysicEngine(shapeLoader); + } + + PhysicsSystem::~PhysicsSystem() + { + // FIXME: OEngine does not behave well when multiple instances are created + // and deleted, sometimes resulting in crashes. Skip the deletion until the physics + // code is moved out of OEngine. + //delete mEngine; + } + + // looks up the scene manager based on the scene node name (inefficient) + // NOTE: referenceId is assumed to be unique per document + // NOTE: searching is done here rather than after rayTest, hence slower to load but + // faster to find (guessing, not verified w/ perf test) + void PhysicsSystem::addObject(const std::string &mesh, + const std::string &sceneNodeName, const std::string &referenceId, float scale, + const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, bool placeable) + { + Ogre::SceneManager *sceneManager = findSceneManager(sceneNodeName); + if(sceneManager) + { + // update maps (NOTE: sometimes replaced) + mSceneNodeToRefId[sceneNodeName] = referenceId; + mSceneNodeToMesh[sceneNodeName] = mesh; + mRefIdToSceneNode[referenceId][sceneManager] = sceneNodeName; + } + else + { + std::cerr << "Attempt to add an object without a corresponding SceneManager: " + + referenceId + " : " + sceneNodeName << std::endl; + return; + } + + // update physics, only one physics model per referenceId + if(mEngine->getRigidBody(referenceId, true) == NULL) + { + mEngine->createAndAdjustRigidBody(mesh, + referenceId, scale, position, rotation, + 0, // scaledBoxTranslation + 0, // boxRotation + true, // raycasting + placeable); + } + } + + // normal delete (e.g closing a scene subview or ~Object()) + // the scene node is destroyed so the mappings should be removed + // + // TODO: should think about using some kind of reference counting within RigidBody + void PhysicsSystem::removeObject(const std::string &sceneNodeName) + { + std::string referenceId = mSceneNodeToRefId[sceneNodeName]; + + if(referenceId != "") + { + mSceneNodeToRefId.erase(sceneNodeName); + mSceneNodeToMesh.erase(sceneNodeName); + + // find which SceneManager has this object + Ogre::SceneManager *sceneManager = findSceneManager(sceneNodeName); + if(!sceneManager) + { + std::cerr << "Attempt to remove an object without a corresponding SceneManager: " + + sceneNodeName << std::endl; + return; + } + + // illustration: erase the object "K" from the object map + // + // RidigBody SubView Ogre + // --------------- -------------- ------------- + // ReferenceId "A" (SceneManager X SceneNode "J") + // (SceneManager Y SceneNode "K") <--- erase + // (SceneManager Z SceneNode "L") + // + // ReferenceId "B" (SceneManager X SceneNode "M") + // (SceneManager Y SceneNode "N") <--- notice not deleted + // (SceneManager Z SceneNode "O") + std::map >::iterator itRef = + mRefIdToSceneNode.begin(); + for(; itRef != mRefIdToSceneNode.end(); ++itRef) + { + if((*itRef).second.find(sceneManager) != (*itRef).second.end()) + { + (*itRef).second.erase(sceneManager); + break; + } + } + + // check whether the physics model should be deleted + if(mRefIdToSceneNode.find(referenceId) == mRefIdToSceneNode.end()) + { + mEngine->removeRigidBody(referenceId); + mEngine->deleteRigidBody(referenceId); + } + } + } + + // Object::clear() is called when reference data is changed. It clears all + // contents of the SceneNode and removes the physics object + // + // A new physics object will be created and assigned to this sceneNodeName by + // Object::update() + void PhysicsSystem::removePhysicsObject(const std::string &sceneNodeName) + { + std::string referenceId = mSceneNodeToRefId[sceneNodeName]; + + if(referenceId != "") + { + mEngine->removeRigidBody(referenceId); + mEngine->deleteRigidBody(referenceId); + } + } + + void PhysicsSystem::replaceObject(const std::string &sceneNodeName, float scale, + const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, bool placeable) + { + std::string referenceId = mSceneNodeToRefId[sceneNodeName]; + std::string mesh = mSceneNodeToMesh[sceneNodeName]; + + if(referenceId != "") + { + // delete the physics object + mEngine->removeRigidBody(referenceId); + mEngine->deleteRigidBody(referenceId); + + // create a new physics object + mEngine->createAndAdjustRigidBody(mesh, referenceId, scale, position, rotation, + 0, 0, true, placeable); + + // update other scene managers if they have the referenceId + // FIXME: rotation or scale not updated + moveSceneNodeImpl(sceneNodeName, referenceId, position); + } + } + + // FIXME: adjustRigidBody() seems to lose objects, work around by deleting and recreating objects + void PhysicsSystem::moveObject(const std::string &sceneNodeName, + const Ogre::Vector3 &position, const Ogre::Quaternion &rotation) + { + mEngine->adjustRigidBody(mEngine->getRigidBody(sceneNodeName, true /*raycasting*/), + position, rotation); + } + + void PhysicsSystem::moveSceneNodeImpl(const std::string sceneNodeName, + const std::string referenceId, const Ogre::Vector3 &position) + { + std::map::const_iterator iter = mSceneWidgets.begin(); + for(; iter != mSceneWidgets.end(); ++iter) + { + std::string name = refIdToSceneNode(referenceId, (*iter).first); + if(name != sceneNodeName && (*iter).first->hasSceneNode(name)) + { + (*iter).first->getSceneNode(name)->setPosition(position); + } + } + } + + void PhysicsSystem::moveSceneNodes(const std::string sceneNodeName, const Ogre::Vector3 &position) + { + moveSceneNodeImpl(sceneNodeName, sceneNodeToRefId(sceneNodeName), position); + } + + void PhysicsSystem::addHeightField(Ogre::SceneManager *sceneManager, + float* heights, int x, int y, float yoffset, float triSize, float sqrtVerts) + { + std::string name = "HeightField_" + + QString::number(x).toStdString() + "_" + QString::number(y).toStdString(); + + if(mTerrain.find(name) == mTerrain.end()) + mEngine->addHeightField(heights, x, y, yoffset, triSize, sqrtVerts); + + mTerrain.insert(std::pair(name, sceneManager)); + } + + void PhysicsSystem::removeHeightField(Ogre::SceneManager *sceneManager, int x, int y) + { + std::string name = "HeightField_" + + QString::number(x).toStdString() + "_" + QString::number(y).toStdString(); + + if(mTerrain.count(name) == 1) + mEngine->removeHeightField(x, y); + + std::multimap::iterator iter = mTerrain.begin(); + for(; iter != mTerrain.end(); ++iter) + { + if((*iter).second == sceneManager) + { + mTerrain.erase(iter); + break; + } + } + } + + // sceneMgr: to lookup the scene node name from the object's referenceId + // camera: primarily used to get the visibility mask for the viewport + // + // returns the found object's scene node name and its position in the world space + // + // WARNING: far clip distance is a global setting, if it changes in future + // this method will need to be updated + std::pair PhysicsSystem::castRay(float mouseX, + float mouseY, Ogre::SceneManager *sceneMgr, Ogre::Camera *camera) + { + // NOTE: there could be more than one camera for the scene manager + // TODO: check whether camera belongs to sceneMgr + if(!sceneMgr || !camera || !camera->getViewport()) + return std::make_pair("", Ogre::Vector3(0,0,0)); // FIXME: this should be an exception + + // using a really small value seems to mess up with the projections + float nearClipDistance = camera->getNearClipDistance(); // save existing + camera->setNearClipDistance(10.0f); // arbitrary number + Ogre::Ray ray = camera->getCameraToViewportRay(mouseX, mouseY); + camera->setNearClipDistance(nearClipDistance); // restore + + Ogre::Vector3 from = ray.getOrigin(); + CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + float farClipDist = userSettings.setting("Scene/far clip distance", QString("300000")).toFloat(); + Ogre::Vector3 to = ray.getPoint(farClipDist); + + btVector3 _from, _to; + _from = btVector3(from.x, from.y, from.z); + _to = btVector3(to.x, to.y, to.z); + + uint32_t visibilityMask = camera->getViewport()->getVisibilityMask(); + bool ignoreHeightMap = !(visibilityMask & (uint32_t)CSVRender::Element_Terrain); + bool ignoreObjects = !(visibilityMask & (uint32_t)CSVRender::Element_Reference); + + Ogre::Vector3 norm; // not used + std::pair result = + mEngine->rayTest(_from, _to, !ignoreObjects, ignoreHeightMap, &norm); + + // result.first is the object's referenceId + if(result.first == "") + return std::make_pair("", Ogre::Vector3(0,0,0)); + else + { + std::string name = refIdToSceneNode(result.first, sceneMgr); + if(name == "") + name = result.first; + else + name = refIdToSceneNode(result.first, sceneMgr); + + return std::make_pair(name, ray.getPoint(farClipDist*result.second)); + } + } + + std::string PhysicsSystem::refIdToSceneNode(std::string referenceId, Ogre::SceneManager *sceneMgr) + { + return mRefIdToSceneNode[referenceId][sceneMgr]; + } + + std::string PhysicsSystem::sceneNodeToRefId(std::string sceneNodeName) + { + return mSceneNodeToRefId[sceneNodeName]; + } + + void PhysicsSystem::addSceneManager(Ogre::SceneManager *sceneMgr, CSVRender::SceneWidget *sceneWidget) + { + mSceneWidgets[sceneMgr] = sceneWidget; + } + + std::map PhysicsSystem::sceneWidgets() + { + return mSceneWidgets; + } + + void PhysicsSystem::removeSceneManager(Ogre::SceneManager *sceneMgr) + { + mSceneWidgets.erase(sceneMgr); + } + + Ogre::SceneManager *PhysicsSystem::findSceneManager(std::string sceneNodeName) + { + std::map::const_iterator iter = mSceneWidgets.begin(); + for(; iter != mSceneWidgets.end(); ++iter) + { + if((*iter).first->hasSceneNode(sceneNodeName)) + { + return (*iter).first; + } + } + + return NULL; + } + + void PhysicsSystem::toggleDebugRendering(Ogre::SceneManager *sceneMgr) + { + // FIXME: should check if sceneMgr is in the list + if(!sceneMgr) + return; + + mEngine->setSceneManager(sceneMgr); + + CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + if(!(userSettings.setting("debug/mouse-picking", QString("false")) == "true" ? true : false)) + { + std::cerr << "Turn on mouse-picking debug option to see collision shapes." << std::endl; + return; + } + + mEngine->toggleDebugRendering(); + mEngine->stepSimulation(0.0167); // DebugDrawer::step() not directly accessible + } +} diff --git a/apps/opencs/view/world/physicssystem.hpp b/apps/opencs/view/world/physicssystem.hpp new file mode 100644 index 000000000..0036bf769 --- /dev/null +++ b/apps/opencs/view/world/physicssystem.hpp @@ -0,0 +1,94 @@ +#ifndef CSV_WORLD_PHYSICSSYSTEM_H +#define CSV_WORLD_PHYSICSSYSTEM_H + +#include +#include + +namespace Ogre +{ + class Vector3; + class Quaternion; + class SceneManager; + class Camera; +} + +namespace OEngine +{ + namespace Physic + { + class PhysicEngine; + } +} + +namespace CSVRender +{ + class SceneWidget; +} + +namespace CSVWorld +{ + class PhysicsSystem + { + std::map mSceneNodeToRefId; + std::map > mRefIdToSceneNode; + std::map mSceneNodeToMesh; + std::map mSceneWidgets; + OEngine::Physic::PhysicEngine* mEngine; + std::multimap mTerrain; + + public: + + PhysicsSystem(); + ~PhysicsSystem(); + + void addSceneManager(Ogre::SceneManager *sceneMgr, CSVRender::SceneWidget * scene); + + void removeSceneManager(Ogre::SceneManager *sceneMgr); + + void addObject(const std::string &mesh, + const std::string &sceneNodeName, const std::string &referenceId, float scale, + const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, + bool placeable=false); + + void removeObject(const std::string &sceneNodeName); + void removePhysicsObject(const std::string &sceneNodeName); + + void replaceObject(const std::string &sceneNodeName, + float scale, const Ogre::Vector3 &position, + const Ogre::Quaternion &rotation, bool placeable=false); + + void moveObject(const std::string &sceneNodeName, + const Ogre::Vector3 &position, const Ogre::Quaternion &rotation); + + void moveSceneNodes(const std::string sceneNodeName, const Ogre::Vector3 &position); + + void addHeightField(Ogre::SceneManager *sceneManager, + float* heights, int x, int y, float yoffset, float triSize, float sqrtVerts); + + void removeHeightField(Ogre::SceneManager *sceneManager, int x, int y); + + void toggleDebugRendering(Ogre::SceneManager *sceneMgr); + + // return the object's SceneNode name and position for the given SceneManager + std::pair castRay(float mouseX, + float mouseY, Ogre::SceneManager *sceneMgr, Ogre::Camera *camera); + + std::string sceneNodeToRefId(std::string sceneNodeName); + + // for multi-scene manager per physics engine + std::map sceneWidgets(); + + private: + + void moveSceneNodeImpl(const std::string sceneNodeName, + const std::string referenceId, const Ogre::Vector3 &position); + + void updateSelectionHighlight(std::string sceneNode, const Ogre::Vector3 &position); + + std::string refIdToSceneNode(std::string referenceId, Ogre::SceneManager *sceneMgr); + + Ogre::SceneManager *findSceneManager(std::string sceneNodeName); + }; +} + +#endif // CSV_WORLD_PHYSICSSYSTEM_H diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index ce68da362..433bb828f 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -38,7 +38,7 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); - CSVRender::WorldspaceWidget* wordspaceWidget = NULL; + CSVRender::WorldspaceWidget* worldspaceWidget = NULL; widgetType whatWidget; if (id.getId()=="sys::default") @@ -47,7 +47,7 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D CSVRender::PagedWorldspaceWidget *newWidget = new CSVRender::PagedWorldspaceWidget (this, document); - wordspaceWidget = newWidget; + worldspaceWidget = newWidget; makeConnections(newWidget); } @@ -57,12 +57,12 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D CSVRender::UnpagedWorldspaceWidget *newWidget = new CSVRender::UnpagedWorldspaceWidget (id.getId(), document, this); - wordspaceWidget = newWidget; + worldspaceWidget = newWidget; makeConnections(newWidget); } - replaceToolbarAndWorldspace(wordspaceWidget, makeToolbar(wordspaceWidget, whatWidget)); + replaceToolbarAndWorldspace(worldspaceWidget, makeToolbar(worldspaceWidget, whatWidget)); layout->insertLayout (0, mLayout, 1); @@ -131,13 +131,11 @@ CSVWidget::SceneToolbar* CSVWorld::SceneSubView::makeToolbar (CSVRender::Worldsp void CSVWorld::SceneSubView::setEditLock (bool locked) { - } void CSVWorld::SceneSubView::updateEditorSetting(const QString &settingName, const QString &settingValue) { - } void CSVWorld::SceneSubView::setStatusBar (bool show)