mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-17 01:10:10 +00:00
98750a122d
shortcut to cycle camera modes
823 lines
30 KiB
C++
823 lines
30 KiB
C++
#include "worldspacewidget.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <set>
|
|
|
|
#include <QDragEnterEvent>
|
|
#include <QDragMoveEvent>
|
|
#include <QDropEvent>
|
|
#include <QGuiApplication>
|
|
#include <QMouseEvent>
|
|
#include <QScreen>
|
|
#include <QToolTip>
|
|
#include <QWindow>
|
|
|
|
#include <apps/opencs/model/doc/document.hpp>
|
|
#include <apps/opencs/model/prefs/category.hpp>
|
|
#include <apps/opencs/model/prefs/setting.hpp>
|
|
#include <apps/opencs/model/world/columns.hpp>
|
|
#include <apps/opencs/model/world/data.hpp>
|
|
#include <apps/opencs/model/world/record.hpp>
|
|
#include <apps/opencs/view/render/editmode.hpp>
|
|
#include <apps/opencs/view/render/scenewidget.hpp>
|
|
#include <apps/opencs/view/widget/modebutton.hpp>
|
|
|
|
#include <components/esm/defs.hpp>
|
|
#include <components/misc/scalableicon.hpp>
|
|
|
|
#include <osg/Camera>
|
|
#include <osg/Group>
|
|
#include <osg/Matrixd>
|
|
#include <osg/Node>
|
|
#include <osg/Referenced>
|
|
#include <osg/Viewport>
|
|
#include <osgUtil/IntersectionVisitor>
|
|
#include <osgViewer/View>
|
|
|
|
#include <osgUtil/LineSegmentIntersector>
|
|
|
|
#include "../../model/world/idtable.hpp"
|
|
#include "../../model/world/tablemimedata.hpp"
|
|
#include "../../model/world/universalid.hpp"
|
|
|
|
#include "../../model/prefs/shortcut.hpp"
|
|
#include "../../model/prefs/state.hpp"
|
|
|
|
#include "../render/orbitcameramode.hpp"
|
|
|
|
#include "../widget/scenetoolmode.hpp"
|
|
#include "../widget/scenetoolrun.hpp"
|
|
#include "../widget/scenetooltoggle2.hpp"
|
|
|
|
#include "cameracontroller.hpp"
|
|
#include "instancemode.hpp"
|
|
#include "mask.hpp"
|
|
#include "object.hpp"
|
|
#include "pathgridmode.hpp"
|
|
|
|
CSVRender::WorldspaceWidget::WorldspaceWidget(CSMDoc::Document& document, QWidget* parent)
|
|
: SceneWidget(document.getData().getResourceSystem(), parent, Qt::WindowFlags(), false)
|
|
, mSceneElements(nullptr)
|
|
, mRun(nullptr)
|
|
, mDocument(document)
|
|
, mInteractionMask(0)
|
|
, mEditMode(nullptr)
|
|
, 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)
|
|
, mSelectedNavigationMode(0)
|
|
{
|
|
setAcceptDrops(true);
|
|
|
|
QAbstractItemModel* referenceables = document.getData().getTableModel(CSMWorld::UniversalId::Type_Referenceables);
|
|
|
|
connect(referenceables, &QAbstractItemModel::dataChanged, this, &WorldspaceWidget::referenceableDataChanged);
|
|
connect(referenceables, &QAbstractItemModel::rowsAboutToBeRemoved, this,
|
|
&WorldspaceWidget::referenceableAboutToBeRemoved);
|
|
connect(referenceables, &QAbstractItemModel::rowsInserted, this, &WorldspaceWidget::referenceableAdded);
|
|
|
|
QAbstractItemModel* references = document.getData().getTableModel(CSMWorld::UniversalId::Type_References);
|
|
|
|
connect(references, &QAbstractItemModel::dataChanged, this, &WorldspaceWidget::referenceDataChanged);
|
|
connect(references, &QAbstractItemModel::rowsAboutToBeRemoved, this, &WorldspaceWidget::referenceAboutToBeRemoved);
|
|
connect(references, &QAbstractItemModel::rowsInserted, this, &WorldspaceWidget::referenceAdded);
|
|
|
|
QAbstractItemModel* pathgrids = document.getData().getTableModel(CSMWorld::UniversalId::Type_Pathgrids);
|
|
|
|
connect(pathgrids, &QAbstractItemModel::dataChanged, this, &WorldspaceWidget::pathgridDataChanged);
|
|
connect(pathgrids, &QAbstractItemModel::rowsAboutToBeRemoved, this, &WorldspaceWidget::pathgridAboutToBeRemoved);
|
|
connect(pathgrids, &QAbstractItemModel::rowsInserted, this, &WorldspaceWidget::pathgridAdded);
|
|
|
|
QAbstractItemModel* debugProfiles = document.getData().getTableModel(CSMWorld::UniversalId::Type_DebugProfiles);
|
|
|
|
connect(debugProfiles, &QAbstractItemModel::dataChanged, this, &WorldspaceWidget::debugProfileDataChanged);
|
|
connect(debugProfiles, &QAbstractItemModel::rowsAboutToBeRemoved, this,
|
|
&WorldspaceWidget::debugProfileAboutToBeRemoved);
|
|
|
|
mToolTipDelayTimer.setSingleShot(true);
|
|
connect(&mToolTipDelayTimer, &QTimer::timeout, this, &WorldspaceWidget::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);
|
|
CSMPrefs::Shortcut* primaryOpenShortcut = new CSMPrefs::Shortcut("scene-open-primary", this);
|
|
|
|
connect(primaryOpenShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this, &WorldspaceWidget::primaryOpen);
|
|
connect(primaryEditShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this, &WorldspaceWidget::primaryEdit);
|
|
connect(primaryEditShortcut, qOverload<bool>(&CSMPrefs::Shortcut::secondary), this, &WorldspaceWidget::speedMode);
|
|
|
|
CSMPrefs::Shortcut* secondaryEditShortcut = new CSMPrefs::Shortcut("scene-edit-secondary", this);
|
|
connect(
|
|
secondaryEditShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this, &WorldspaceWidget::secondaryEdit);
|
|
|
|
CSMPrefs::Shortcut* primarySelectShortcut = new CSMPrefs::Shortcut("scene-select-primary", this);
|
|
connect(
|
|
primarySelectShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this, &WorldspaceWidget::primarySelect);
|
|
|
|
CSMPrefs::Shortcut* secondarySelectShortcut = new CSMPrefs::Shortcut("scene-select-secondary", this);
|
|
connect(secondarySelectShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this,
|
|
&WorldspaceWidget::secondarySelect);
|
|
|
|
CSMPrefs::Shortcut* tertiarySelectShortcut = new CSMPrefs::Shortcut("scene-select-tertiary", this);
|
|
connect(tertiarySelectShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this,
|
|
&WorldspaceWidget::tertiarySelect);
|
|
|
|
CSMPrefs::Shortcut* abortShortcut = new CSMPrefs::Shortcut("scene-edit-abort", this);
|
|
connect(abortShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &WorldspaceWidget::abortDrag);
|
|
|
|
connect(new CSMPrefs::Shortcut("scene-toggle-visibility", this), qOverload<>(&CSMPrefs::Shortcut::activated), this,
|
|
&WorldspaceWidget::toggleHiddenInstances);
|
|
|
|
connect(new CSMPrefs::Shortcut("scene-unhide-all", this), qOverload<>(&CSMPrefs::Shortcut::activated), this,
|
|
&WorldspaceWidget::unhideAll);
|
|
|
|
connect(new CSMPrefs::Shortcut("scene-clear-selection", this), qOverload<>(&CSMPrefs::Shortcut::activated), this,
|
|
[this] { this->clearSelection(Mask_Reference); });
|
|
|
|
CSMPrefs::Shortcut* switchPerspectiveShortcut = new CSMPrefs::Shortcut("scene-cam-cycle", this);
|
|
connect(switchPerspectiveShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this,
|
|
&WorldspaceWidget::cycleNavigationMode);
|
|
|
|
mInConstructor = false;
|
|
}
|
|
|
|
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<TagBase> tag : selection)
|
|
{
|
|
if (auto objTag = dynamic_cast<ObjectTag*>(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<osg::ref_ptr<TagBase>> selection = getSelection(Mask_Reference);
|
|
|
|
for (std::vector<osg::ref_ptr<TagBase>>::iterator it = selection.begin(); it != selection.end(); ++it)
|
|
{
|
|
if (CSVRender::ObjectTag* objectTag = static_cast<CSVRender::ObjectTag*>(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"
|
|
"<ul><li>Camera is held upright</li>"
|
|
"<li>Mouse-Look while holding {scene-navi-primary}</li>"
|
|
"<li>Movement keys: {free-forward}(forward), {free-left}(left), {free-backward}(back), {free-right}(right)</li>"
|
|
"<li>Strafing (also vertically) by holding {scene-navi-secondary}</li>"
|
|
"<li>Mouse wheel moves the camera forward/backward</li>"
|
|
"<li>Hold {scene-speed-modifier} to speed up movement</li>"
|
|
"</ul>");
|
|
tool->addButton(":scenetoolbar/free-camera", "free",
|
|
"Free Camera"
|
|
"<ul><li>Mouse-Look while holding {scene-navi-primary}</li>"
|
|
"<li>Movement keys: {free-forward}(forward), {free-left}(left), {free-backward}(back), {free-right}(right)</li>"
|
|
"<li>Roll camera with {free-roll-left} and {free-roll-right} keys</li>"
|
|
"<li>Strafing (also vertically) by holding {scene-navi-secondary}</li>"
|
|
"<li>Mouse wheel moves the camera forward/backward</li>"
|
|
"<li>Hold {free-forward:mod} to speed up movement</li>"
|
|
"</ul>");
|
|
tool->addButton(
|
|
new CSVRender::OrbitCameraMode(this, Misc::ScalableIcon::load(":scenetoolbar/orbiting-camera"),
|
|
"Orbiting Camera"
|
|
"<ul><li>Always facing the centre point</li>"
|
|
"<li>Rotate around the centre point via {orbit-up}, {orbit-left}, {orbit-down}, {orbit-right} or by moving "
|
|
"the mouse while holding {scene-navi-primary}</li>"
|
|
"<li>Roll camera with {orbit-roll-left} and {orbit-roll-right} keys</li>"
|
|
"<li>Strafing (also vertically) by holding {scene-navi-secondary} (includes relocation of the centre "
|
|
"point)</li>"
|
|
"<li>Mouse wheel moves camera away or towards centre point but can not pass through it</li>"
|
|
"<li>Hold {scene-speed-modifier} to speed up movement</li>"
|
|
"</ul>",
|
|
tool),
|
|
"orbit");
|
|
|
|
mCameraMode = tool;
|
|
|
|
connect(mCameraMode, &CSVWidget::SceneToolMode::modeChanged, this, &WorldspaceWidget::selectNavigationMode);
|
|
|
|
return mCameraMode;
|
|
}
|
|
|
|
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, &CSVWidget::SceneToolToggle2::selectionChanged, this,
|
|
&WorldspaceWidget::elementSelectionChanged);
|
|
|
|
return mSceneElements;
|
|
}
|
|
|
|
CSVWidget::SceneToolRun* CSVRender::WorldspaceWidget::makeRunTool(CSVWidget::SceneToolbar* parent)
|
|
{
|
|
CSMWorld::IdTable& debugProfiles = dynamic_cast<CSMWorld::IdTable&>(
|
|
*mDocument.getData().getTableModel(CSMWorld::UniversalId::Type_DebugProfiles));
|
|
|
|
std::vector<std::string> 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 < size; ++i)
|
|
{
|
|
int state = debugProfiles.data(debugProfiles.index(i, stateColumn)).toInt();
|
|
|
|
bool default_ = debugProfiles.data(debugProfiles.index(i, defaultColumn)).toInt();
|
|
|
|
if (state != CSMWorld::RecordBase::State_Deleted && default_)
|
|
profiles.emplace_back(debugProfiles.data(debugProfiles.index(i, idColumn)).toString().toUtf8().constData());
|
|
}
|
|
|
|
std::sort(profiles.begin(), profiles.end());
|
|
|
|
mRun = new CSVWidget::SceneToolRun(
|
|
parent, "Run OpenMW from the current camera position", ":scenetoolbar/play", profiles);
|
|
|
|
connect(mRun, &CSVWidget::SceneToolRun::runRequest, this, &WorldspaceWidget::runRequest);
|
|
|
|
return mRun;
|
|
}
|
|
|
|
CSVWidget::SceneToolMode* CSVRender::WorldspaceWidget::makeEditModeSelector(CSVWidget::SceneToolbar* parent)
|
|
{
|
|
mEditMode = new CSVWidget::SceneToolMode(parent, "Edit Mode");
|
|
|
|
addEditModeSelectorButtons(mEditMode);
|
|
|
|
connect(mEditMode, &CSVWidget::SceneToolMode::modeChanged, this, &WorldspaceWidget::editModeChanged);
|
|
|
|
return mEditMode;
|
|
}
|
|
|
|
CSVRender::WorldspaceWidget::DropType CSVRender::WorldspaceWidget::getDropType(
|
|
const std::vector<CSMWorld::UniversalId>& data)
|
|
{
|
|
DropType output = Type_Other;
|
|
|
|
for (std::vector<CSMWorld::UniversalId>::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()[0] == '#' ? 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<CSMWorld::UniversalId>& universalIdData, DropType type)
|
|
{
|
|
if (type == Type_DebugProfile)
|
|
{
|
|
if (mRun)
|
|
{
|
|
for (std::vector<CSMWorld::UniversalId>::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<CSVRender::EditMode&>(*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, mRootNode, 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
|
|
{
|
|
// may be okay to just use devicePixelRatio() directly
|
|
QScreen* screen = SceneWidget::windowHandle() && SceneWidget::windowHandle()->screen()
|
|
? SceneWidget::windowHandle()->screen()
|
|
: QGuiApplication::primaryScreen();
|
|
|
|
// (0,0) is considered the lower left corner of an OpenGL window
|
|
int x = localPos.x() * screen->devicePixelRatio();
|
|
int y = height() * screen->devicePixelRatio() - localPos.y() * screen->devicePixelRatio();
|
|
|
|
// 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<osgUtil::LineSegmentIntersector> 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<osg::Node*>::iterator nodeIter = intersection.nodePath.begin();
|
|
nodeIter != intersection.nodePath.end(); ++nodeIter)
|
|
{
|
|
osg::Node* node = *nodeIter;
|
|
if (osg::ref_ptr<CSVRender::TagBase> tag = dynamic_cast<CSVRender::TagBase*>(node->getUserData()))
|
|
{
|
|
WorldspaceHitResult hit = { true, std::move(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, nullptr, 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, nullptr, 0, 0, 0, start + direction };
|
|
return hit;
|
|
}
|
|
|
|
CSVRender::EditMode* CSVRender::WorldspaceWidget::getEditMode()
|
|
{
|
|
return dynamic_cast<CSVRender::EditMode*>(mEditMode->getCurrent());
|
|
}
|
|
|
|
void CSVRender::WorldspaceWidget::abortDrag()
|
|
{
|
|
if (mDragging)
|
|
{
|
|
EditMode& editMode = dynamic_cast<CSVRender::EditMode&>(*mEditMode->getCurrent());
|
|
|
|
editMode.dragAborted();
|
|
mDragMode = InteractionType_None;
|
|
}
|
|
}
|
|
|
|
void CSVRender::WorldspaceWidget::dragEnterEvent(QDragEnterEvent* event)
|
|
{
|
|
const CSMWorld::TableMimeData* mime = dynamic_cast<const CSMWorld::TableMimeData*>(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<EditMode&>(*mEditMode->getCurrent()).dragEnterEvent(event);
|
|
}
|
|
}
|
|
|
|
void CSVRender::WorldspaceWidget::dragMoveEvent(QDragMoveEvent* event)
|
|
{
|
|
const CSMWorld::TableMimeData* mime = dynamic_cast<const CSMWorld::TableMimeData*>(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<EditMode&>(*mEditMode->getCurrent()).dragMoveEvent(event);
|
|
}
|
|
}
|
|
|
|
void CSVRender::WorldspaceWidget::dropEvent(QDropEvent* event)
|
|
{
|
|
const CSMWorld::TableMimeData* mime = dynamic_cast<const CSMWorld::TableMimeData*>(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<EditMode&>(*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<CSMWorld::IdTable&>(
|
|
*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<CSMWorld::IdTable&>(
|
|
*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<CSVRender::EditMode&>(*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, hit), this);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CSVRender::WorldspaceWidget::elementSelectionChanged()
|
|
{
|
|
setVisibilityMask(getVisibilityMask());
|
|
flagAsModified();
|
|
updateOverlay();
|
|
}
|
|
|
|
void CSVRender::WorldspaceWidget::updateOverlay() {}
|
|
|
|
void CSVRender::WorldspaceWidget::mouseMoveEvent(QMouseEvent* event)
|
|
{
|
|
dynamic_cast<CSVRender::EditMode&>(*mEditMode->getCurrent()).mouseMoveEvent(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<CSVRender::EditMode&>(*mEditMode->getCurrent());
|
|
|
|
editMode.drag(event->pos(), diffX, diffY, factor);
|
|
}
|
|
else if (mDragMode != InteractionType_None)
|
|
{
|
|
EditMode& editMode = dynamic_cast<CSVRender::EditMode&>(*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)
|
|
{
|
|
mDragX = event->localPos().x();
|
|
mDragY = height() - event->localPos().y();
|
|
}
|
|
}
|
|
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<CSVRender::EditMode&>(*mEditMode->getCurrent());
|
|
editMode.dragWheel(event->angleDelta().y(), factor);
|
|
}
|
|
else
|
|
SceneWidget::wheelEvent(event);
|
|
}
|
|
|
|
void CSVRender::WorldspaceWidget::handleInteractionPress(const WorldspaceHitResult& hit, InteractionType type)
|
|
{
|
|
EditMode& editMode = dynamic_cast<CSVRender::EditMode&>(*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);
|
|
else if (type == InteractionType_TertiarySelect)
|
|
editMode.tertiarySelectPressed(hit);
|
|
else if (type == InteractionType_PrimaryOpen)
|
|
editMode.primaryOpenPressed(hit);
|
|
}
|
|
|
|
void CSVRender::WorldspaceWidget::primaryOpen(bool activate)
|
|
{
|
|
handleInteraction(InteractionType_PrimaryOpen, activate);
|
|
}
|
|
|
|
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::tertiarySelect(bool activate)
|
|
{
|
|
handleInteraction(InteractionType_TertiarySelect, activate);
|
|
}
|
|
|
|
void CSVRender::WorldspaceWidget::speedMode(bool activate)
|
|
{
|
|
mSpeedMode = activate;
|
|
}
|
|
|
|
void CSVRender::WorldspaceWidget::toggleHiddenInstances()
|
|
{
|
|
const std::vector<osg::ref_ptr<TagBase>> selection = getSelection(Mask_Reference);
|
|
|
|
if (selection.empty())
|
|
return;
|
|
|
|
const CSVRender::ObjectTag* firstSelection = static_cast<CSVRender::ObjectTag*>(selection.begin()->get());
|
|
assert(firstSelection != nullptr);
|
|
|
|
const CSVRender::Mask firstMask
|
|
= firstSelection->mObject->getRootNode()->getNodeMask() == Mask_Hidden ? Mask_Reference : Mask_Hidden;
|
|
|
|
for (const auto& object : selection)
|
|
if (const auto objectTag = static_cast<CSVRender::ObjectTag*>(object.get()))
|
|
objectTag->mObject->getRootNode()->setNodeMask(firstMask);
|
|
}
|
|
|
|
void CSVRender::WorldspaceWidget::cycleNavigationMode()
|
|
{
|
|
switch (++mSelectedNavigationMode)
|
|
{
|
|
case (CameraMode::FirstPerson):
|
|
mCameraMode->setButton("1st");
|
|
break;
|
|
case (CameraMode::Orbit):
|
|
mCameraMode->setButton("orbit");
|
|
break;
|
|
case (CameraMode::Free):
|
|
mCameraMode->setButton("free");
|
|
break;
|
|
default:
|
|
mCameraMode->setButton("1st");
|
|
mSelectedNavigationMode = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|