#include "worldspacewidget.hpp" #include #include #include #include #include #include #include #include #include #include #include "../../model/world/universalid.hpp" #include "../../model/world/idtable.hpp" #include "../../model/prefs/shortcut.hpp" #include "../../model/prefs/shortcuteventhandler.hpp" #include "../../model/prefs/state.hpp" #include "../render/orbitcameramode.hpp" #include "../widget/scenetoolmode.hpp" #include "../widget/scenetooltoggle2.hpp" #include "../widget/scenetoolrun.hpp" #include "object.hpp" #include "mask.hpp" #include "instancemode.hpp" #include "pathgridmode.hpp" #include "cameracontroller.hpp" CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) : SceneWidget (document.getData().getResourceSystem(), parent, 0, false) , mSceneElements(0) , mRun(0) , mDocument(document) , mInteractionMask (0) , mEditMode (0) , mLocked (false) , mDragMode(InteractionType_None) , mDragging (false) , mDragX(0) , mDragY(0) , mSpeedMode(false) , mDragFactor(0) , mDragWheelFactor(0) , mDragShiftFactor(0) , mToolTipPos (-1, -1) , mShowToolTips(false) , mToolTipDelay(0) , mInConstructor(true) { setAcceptDrops(true); QAbstractItemModel *referenceables = document.getData().getTableModel (CSMWorld::UniversalId::Type_Referenceables); connect (referenceables, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (referenceableDataChanged (const QModelIndex&, const QModelIndex&))); connect (referenceables, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (referenceableAboutToBeRemoved (const QModelIndex&, int, int))); connect (referenceables, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (referenceableAdded (const QModelIndex&, int, int))); QAbstractItemModel *references = document.getData().getTableModel (CSMWorld::UniversalId::Type_References); connect (references, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (referenceDataChanged (const QModelIndex&, const QModelIndex&))); connect (references, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (referenceAboutToBeRemoved (const QModelIndex&, int, int))); connect (references, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (referenceAdded (const QModelIndex&, int, int))); QAbstractItemModel *pathgrids = document.getData().getTableModel (CSMWorld::UniversalId::Type_Pathgrids); connect (pathgrids, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (pathgridDataChanged (const QModelIndex&, const QModelIndex&))); connect (pathgrids, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (pathgridAboutToBeRemoved (const QModelIndex&, int, int))); connect (pathgrids, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (pathgridAdded (const QModelIndex&, int, int))); QAbstractItemModel *debugProfiles = document.getData().getTableModel (CSMWorld::UniversalId::Type_DebugProfiles); connect (debugProfiles, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (debugProfileDataChanged (const QModelIndex&, const QModelIndex&))); connect (debugProfiles, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (debugProfileAboutToBeRemoved (const QModelIndex&, int, int))); mToolTipDelayTimer.setSingleShot (true); connect (&mToolTipDelayTimer, SIGNAL (timeout()), this, SLOT (showToolTip())); CSMPrefs::get()["3D Scene Input"].update(); CSMPrefs::get()["Tooltips"].update(); // Shortcuts CSMPrefs::Shortcut* primaryEditShortcut = new CSMPrefs::Shortcut("scene-edit-primary", "scene-speed-modifier", CSMPrefs::Shortcut::SM_Detach, this); connect(primaryEditShortcut, SIGNAL(activated(bool)), this, SLOT(primaryEdit(bool))); connect(primaryEditShortcut, SIGNAL(secondary(bool)), this, SLOT(speedMode(bool))); CSMPrefs::Shortcut* secondaryEditShortcut = new CSMPrefs::Shortcut("scene-edit-secondary", this); connect(secondaryEditShortcut, SIGNAL(activated(bool)), this, SLOT(secondaryEdit(bool))); CSMPrefs::Shortcut* primarySelectShortcut = new CSMPrefs::Shortcut("scene-select-primary", this); connect(primarySelectShortcut, SIGNAL(activated(bool)), this, SLOT(primarySelect(bool))); CSMPrefs::Shortcut* secondarySelectShortcut = new CSMPrefs::Shortcut("scene-select-secondary", this); connect(secondarySelectShortcut, SIGNAL(activated(bool)), this, SLOT(secondarySelect(bool))); CSMPrefs::Shortcut* abortShortcut = new CSMPrefs::Shortcut("scene-edit-abort", this); connect(abortShortcut, SIGNAL(activated()), this, SLOT(abortDrag())); mInConstructor = false; } CSVRender::WorldspaceWidget::~WorldspaceWidget () { } void CSVRender::WorldspaceWidget::settingChanged (const CSMPrefs::Setting *setting) { if (*setting=="3D Scene Input/drag-factor") mDragFactor = setting->toDouble(); else if (*setting=="3D Scene Input/drag-wheel-factor") mDragWheelFactor = setting->toDouble(); else if (*setting=="3D Scene Input/drag-shift-factor") mDragShiftFactor = setting->toDouble(); else if (*setting=="Rendering/object-marker-alpha" && !mInConstructor) { float alpha = setting->toDouble(); // getSelection is virtual, thus this can not be called from the constructor auto selection = getSelection(Mask_Reference); for (osg::ref_ptr tag : selection) { if (auto objTag = dynamic_cast(tag.get())) objTag->mObject->setMarkerTransparency(alpha); } } else if (*setting=="Tooltips/scene-delay") mToolTipDelay = setting->toInt(); else if (*setting=="Tooltips/scene") mShowToolTips = setting->isTrue(); else SceneWidget::settingChanged(setting); } void CSVRender::WorldspaceWidget::useViewHint (const std::string& hint) {} void CSVRender::WorldspaceWidget::selectDefaultNavigationMode() { selectNavigationMode("1st"); } void CSVRender::WorldspaceWidget::centerOrbitCameraOnSelection() { std::vector > selection = getSelection(~0); for (std::vector >::iterator it = selection.begin(); it!=selection.end(); ++it) { if (CSVRender::ObjectTag *objectTag = dynamic_cast (it->get())) { mOrbitCamControl->setCenter(objectTag->mObject->getPosition().asVec3()); } } } CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( CSVWidget::SceneToolbar *parent) { CSVWidget::SceneToolMode *tool = new CSVWidget::SceneToolMode (parent, "Camera Mode"); /// \todo replace icons /// \todo consider user-defined button-mapping tool->addButton (":scenetoolbar/1st-person", "1st", "First Person" "
  • Camera is held upright
  • " "
  • Mouse-Look while holding {scene-navi-primary}
  • " "
  • Movement keys: {free-forward}(forward), {free-left}(left), {free-backward}(back), {free-right}(right)
  • " "
  • Strafing (also vertically) by holding {scene-navi-secondary}
  • " "
  • Mouse wheel moves the camera forward/backward
  • " "
  • Hold {scene-speed-modifier} to speed up movement
  • " "
"); tool->addButton (":scenetoolbar/free-camera", "free", "Free Camera" "
  • Mouse-Look while holding {scene-navi-primary}
  • " "
  • Movement keys: {free-forward}(forward), {free-left}(left), {free-backward}(back), {free-right}(right)
  • " "
  • Roll camera with {free-roll-left} and {free-roll-right} keys
  • " "
  • Strafing (also vertically) by holding {scene-navi-secondary}
  • " "
  • Mouse wheel moves the camera forward/backward
  • " "
  • Hold {free-forward:mod} to speed up movement
  • " "
"); tool->addButton( new CSVRender::OrbitCameraMode(this, QIcon(":scenetoolbar/orbiting-camera"), "Orbiting Camera" "
  • Always facing the centre point
  • " "
  • Rotate around the centre point via {orbit-up}, {orbit-left}, {orbit-down}, {orbit-right} or by moving " "the mouse while holding {scene-navi-primary}
  • " "
  • Roll camera with {orbit-roll-left} and {orbit-roll-right} keys
  • " "
  • Strafing (also vertically) by holding {scene-navi-secondary} (includes relocation of the centre point)
  • " "
  • Mouse wheel moves camera away or towards centre point but can not pass through it
  • " "
  • Hold {scene-speed-modifier} to speed up movement
  • " "
", tool), "orbit"); connect (tool, SIGNAL (modeChanged (const std::string&)), this, SLOT (selectNavigationMode (const std::string&))); return tool; } CSVWidget::SceneToolToggle2 *CSVRender::WorldspaceWidget::makeSceneVisibilitySelector (CSVWidget::SceneToolbar *parent) { mSceneElements = new CSVWidget::SceneToolToggle2 (parent, "Scene Element Visibility", ":scenetoolbar/scene-view-c", ":scenetoolbar/scene-view-"); addVisibilitySelectorButtons (mSceneElements); mSceneElements->setSelectionMask (0xffffffff); connect (mSceneElements, SIGNAL (selectionChanged()), this, SLOT (elementSelectionChanged())); return mSceneElements; } CSVWidget::SceneToolRun *CSVRender::WorldspaceWidget::makeRunTool ( CSVWidget::SceneToolbar *parent) { CSMWorld::IdTable& debugProfiles = dynamic_cast ( *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_DebugProfiles)); std::vector profiles; int idColumn = debugProfiles.findColumnIndex (CSMWorld::Columns::ColumnId_Id); int stateColumn = debugProfiles.findColumnIndex (CSMWorld::Columns::ColumnId_Modification); int defaultColumn = debugProfiles.findColumnIndex ( CSMWorld::Columns::ColumnId_DefaultProfile); int size = debugProfiles.rowCount(); for (int i=0; i& data) { DropType output = Type_Other; for (std::vector::const_iterator iter (data.begin()); iter!=data.end(); ++iter) { DropType type = Type_Other; if (iter->getType()==CSMWorld::UniversalId::Type_Cell || iter->getType()==CSMWorld::UniversalId::Type_Cell_Missing) { type = iter->getId().substr (0, 1)=="#" ? Type_CellsExterior : Type_CellsInterior; } else if (iter->getType()==CSMWorld::UniversalId::Type_DebugProfile) type = Type_DebugProfile; if (iter==data.begin()) output = type; else if (output!=type) // mixed types -> ignore return Type_Other; } return output; } CSVRender::WorldspaceWidget::dropRequirments CSVRender::WorldspaceWidget::getDropRequirements (DropType type) const { if (type==Type_DebugProfile) return canHandle; return ignored; } bool CSVRender::WorldspaceWidget::handleDrop (const std::vector& universalIdData, DropType type) { if (type==Type_DebugProfile) { if (mRun) { for (std::vector::const_iterator iter (universalIdData.begin()); iter!=universalIdData.end(); ++iter) mRun->addProfile (iter->getId()); } return true; } return false; } unsigned int CSVRender::WorldspaceWidget::getVisibilityMask() const { return mSceneElements->getSelectionMask(); } void CSVRender::WorldspaceWidget::setInteractionMask (unsigned int mask) { mInteractionMask = mask | Mask_CellMarker | Mask_CellArrow; } unsigned int CSVRender::WorldspaceWidget::getInteractionMask() const { return mInteractionMask & getVisibilityMask(); } void CSVRender::WorldspaceWidget::setEditLock (bool locked) { dynamic_cast (*mEditMode->getCurrent()).setEditLock (locked); } void CSVRender::WorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { tool->addButton (Button_Reference, Mask_Reference, "Instances"); tool->addButton (Button_Water, Mask_Water, "Water"); tool->addButton (Button_Pathgrid, Mask_Pathgrid, "Pathgrid"); } void CSVRender::WorldspaceWidget::addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool) { /// \todo replace EditMode with suitable subclasses tool->addButton (new InstanceMode (this, tool), "object"); tool->addButton (new PathgridMode (this, tool), "pathgrid"); } CSMDoc::Document& CSVRender::WorldspaceWidget::getDocument() { return mDocument; } CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick (const QPoint& localPos, unsigned int interactionMask) const { // (0,0) is considered the lower left corner of an OpenGL window int x = localPos.x(); int y = height() - localPos.y(); // Convert from screen space to world space osg::Matrixd wpvMat; wpvMat.preMult (mView->getCamera()->getViewport()->computeWindowMatrix()); wpvMat.preMult (mView->getCamera()->getProjectionMatrix()); wpvMat.preMult (mView->getCamera()->getViewMatrix()); wpvMat = osg::Matrixd::inverse (wpvMat); osg::Vec3d start = wpvMat.preMult (osg::Vec3d(x, y, 0)); osg::Vec3d end = wpvMat.preMult (osg::Vec3d(x, y, 1)); osg::Vec3d direction = end - start; // Get intersection osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector( osgUtil::Intersector::MODEL, start, end)); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); osgUtil::IntersectionVisitor visitor(intersector); visitor.setTraversalMask(interactionMask); mView->getCamera()->accept(visitor); // Get relevant data for (osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin(); it != intersector->getIntersections().end(); ++it) { osgUtil::LineSegmentIntersector::Intersection intersection = *it; // reject back-facing polygons if (direction * intersection.getWorldIntersectNormal() > 0) { continue; } for (std::vector::iterator nodeIter = intersection.nodePath.begin(); nodeIter != intersection.nodePath.end(); ++nodeIter) { osg::Node* node = *nodeIter; if (osg::ref_ptr tag = dynamic_cast(node->getUserData())) { WorldspaceHitResult hit = { true, tag, 0, 0, 0, intersection.getWorldIntersectPoint() }; if (intersection.indexList.size() >= 3) { hit.index0 = intersection.indexList[0]; hit.index1 = intersection.indexList[1]; hit.index2 = intersection.indexList[2]; } return hit; } } // Something untagged, probably terrain WorldspaceHitResult hit = { true, 0, 0, 0, 0, intersection.getWorldIntersectPoint() }; if (intersection.indexList.size() >= 3) { hit.index0 = intersection.indexList[0]; hit.index1 = intersection.indexList[1]; hit.index2 = intersection.indexList[2]; } return hit; } // Default placement direction.normalize(); direction *= CSMPrefs::get()["3D Scene Editing"]["distance"].toInt(); WorldspaceHitResult hit = { false, 0, 0, 0, 0, start + direction }; return hit; } void CSVRender::WorldspaceWidget::abortDrag() { if (mDragging) { EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); editMode.dragAborted(); mDragging = false; mDragMode = InteractionType_None; } } void CSVRender::WorldspaceWidget::dragEnterEvent (QDragEnterEvent* event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped return; if (mime->fromDocument (mDocument)) { if (mime->holdsType (CSMWorld::UniversalId::Type_Cell) || mime->holdsType (CSMWorld::UniversalId::Type_Cell_Missing) || mime->holdsType (CSMWorld::UniversalId::Type_DebugProfile)) { // These drops are handled through the subview object. event->accept(); } else dynamic_cast (*mEditMode->getCurrent()).dragEnterEvent (event); } } void CSVRender::WorldspaceWidget::dragMoveEvent(QDragMoveEvent *event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped return; if (mime->fromDocument (mDocument)) { if (mime->holdsType (CSMWorld::UniversalId::Type_Cell) || mime->holdsType (CSMWorld::UniversalId::Type_Cell_Missing) || mime->holdsType (CSMWorld::UniversalId::Type_DebugProfile)) { // These drops are handled through the subview object. event->accept(); } else dynamic_cast (*mEditMode->getCurrent()).dragMoveEvent (event); } } void CSVRender::WorldspaceWidget::dropEvent (QDropEvent* event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped return; if (mime->fromDocument (mDocument)) { if (mime->holdsType (CSMWorld::UniversalId::Type_Cell) || mime->holdsType (CSMWorld::UniversalId::Type_Cell_Missing) || mime->holdsType (CSMWorld::UniversalId::Type_DebugProfile)) { emit dataDropped(mime->getData()); } else dynamic_cast (*mEditMode->getCurrent()).dropEvent (event); } } void CSVRender::WorldspaceWidget::runRequest (const std::string& profile) { mDocument.startRunning (profile, getStartupInstruction()); } void CSVRender::WorldspaceWidget::debugProfileDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (!mRun) return; CSMWorld::IdTable& debugProfiles = dynamic_cast ( *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_DebugProfiles)); int idColumn = debugProfiles.findColumnIndex (CSMWorld::Columns::ColumnId_Id); int stateColumn = debugProfiles.findColumnIndex (CSMWorld::Columns::ColumnId_Modification); for (int i=topLeft.row(); i<=bottomRight.row(); ++i) { int state = debugProfiles.data (debugProfiles.index (i, stateColumn)).toInt(); // As of version 0.33 this case can not happen because debug profiles exist only in // project or session scope, which means they will never be in deleted state. But we // are adding the code for the sake of completeness and to avoid surprises if debug // profile ever get extended to content scope. if (state==CSMWorld::RecordBase::State_Deleted) mRun->removeProfile (debugProfiles.data ( debugProfiles.index (i, idColumn)).toString().toUtf8().constData()); } } void CSVRender::WorldspaceWidget::debugProfileAboutToBeRemoved (const QModelIndex& parent, int start, int end) { if (parent.isValid()) return; if (!mRun) return; CSMWorld::IdTable& debugProfiles = dynamic_cast ( *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_DebugProfiles)); int idColumn = debugProfiles.findColumnIndex (CSMWorld::Columns::ColumnId_Id); for (int i=start; i<=end; ++i) { mRun->removeProfile (debugProfiles.data ( debugProfiles.index (i, idColumn)).toString().toUtf8().constData()); } } void CSVRender::WorldspaceWidget::editModeChanged (const std::string& id) { dynamic_cast (*mEditMode->getCurrent()).setEditLock (mLocked); mDragging = false; mDragMode = InteractionType_None; } void CSVRender::WorldspaceWidget::showToolTip() { if (mShowToolTips) { QPoint pos = QCursor::pos(); WorldspaceHitResult hit = mousePick (mapFromGlobal (pos), getInteractionMask()); if (hit.tag) { bool hideBasics = CSMPrefs::get()["Tooltips"]["scene-hide-basic"].isTrue(); QToolTip::showText (pos, hit.tag->getToolTip (hideBasics), this); } } } void CSVRender::WorldspaceWidget::elementSelectionChanged() { setVisibilityMask (getVisibilityMask()); flagAsModified(); updateOverlay(); } void CSVRender::WorldspaceWidget::updateOverlay() { } void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) { if (mDragging) { int diffX = event->x() - mDragX; int diffY = (height() - event->y()) - mDragY; mDragX = event->x(); mDragY = height() - event->y(); double factor = mDragFactor; if (mSpeedMode) factor *= mDragShiftFactor; EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); editMode.drag (event->pos(), diffX, diffY, factor); } else if (mDragMode != InteractionType_None) { EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); if (mDragMode == InteractionType_PrimaryEdit) mDragging = editMode.primaryEditStartDrag (event->pos()); else if (mDragMode == InteractionType_SecondaryEdit) mDragging = editMode.secondaryEditStartDrag (event->pos()); else if (mDragMode == InteractionType_PrimarySelect) mDragging = editMode.primarySelectStartDrag (event->pos()); else if (mDragMode == InteractionType_SecondarySelect) mDragging = editMode.secondarySelectStartDrag (event->pos()); if (mDragging) { #if QT_VERSION >= QT_VERSION_CHECK(5,0,0) mDragX = event->localPos().x(); mDragY = height() - event->localPos().y(); #else mDragX = event->posF().x(); mDragY = height() - event->posF().y(); #endif if (mDragMode == InteractionType_PrimaryEdit) { editMode.drag (event->pos(), mDragX, mDragY, mDragFactor); // note: terraintexturemode only uses pos } } } else { if (event->globalPos()!=mToolTipPos) { mToolTipPos = event->globalPos(); if (mShowToolTips) { QToolTip::hideText(); mToolTipDelayTimer.start (mToolTipDelay); } } SceneWidget::mouseMoveEvent(event); } } void CSVRender::WorldspaceWidget::wheelEvent (QWheelEvent *event) { if (mDragging) { double factor = mDragWheelFactor; if (mSpeedMode) factor *= mDragShiftFactor; EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); editMode.dragWheel (event->delta(), factor); } else SceneWidget::wheelEvent(event); } void CSVRender::WorldspaceWidget::handleInteractionPress (const WorldspaceHitResult& hit, InteractionType type) { EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); if (type == InteractionType_PrimaryEdit) editMode.primaryEditPressed (hit); else if (type == InteractionType_SecondaryEdit) editMode.secondaryEditPressed (hit); else if (type == InteractionType_PrimarySelect) editMode.primarySelectPressed (hit); else if (type == InteractionType_SecondarySelect) editMode.secondarySelectPressed (hit); } CSVRender::EditMode *CSVRender::WorldspaceWidget::getEditMode() { return dynamic_cast (mEditMode->getCurrent()); } void CSVRender::WorldspaceWidget::primaryEdit(bool activate) { handleInteraction(InteractionType_PrimaryEdit, activate); } void CSVRender::WorldspaceWidget::secondaryEdit(bool activate) { handleInteraction(InteractionType_SecondaryEdit, activate); } void CSVRender::WorldspaceWidget::primarySelect(bool activate) { handleInteraction(InteractionType_PrimarySelect, activate); } void CSVRender::WorldspaceWidget::secondarySelect(bool activate) { handleInteraction(InteractionType_SecondarySelect, activate); } void CSVRender::WorldspaceWidget::speedMode(bool activate) { mSpeedMode = activate; } void CSVRender::WorldspaceWidget::handleInteraction(InteractionType type, bool activate) { if (activate) { if (!mDragging) mDragMode = type; } else { mDragMode = InteractionType_None; if (mDragging) { EditMode* editMode = getEditMode(); editMode->dragCompleted(mapFromGlobal(QCursor::pos())); mDragging = false; } else { WorldspaceHitResult hit = mousePick(mapFromGlobal(QCursor::pos()), getInteractionMask()); handleInteractionPress(hit, type); } } }