1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-10-17 05:46:37 +00:00

Merge branch 'too-many-markers' into 'master'

FEAT(CS): Replace selection markers with a real one (#8139)

Closes #8139

See merge request OpenMW/openmw!4349
This commit is contained in:
Alexei Kotov 2025-07-14 23:18:46 +03:00
commit a12a0916ed
23 changed files with 31523 additions and 431 deletions

View file

@ -88,7 +88,7 @@ opencs_units (view/render
scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget
previewwidget editmode instancemode instanceselectionmode instancemovemode previewwidget editmode instancemode instanceselectionmode instancemovemode
orbitcameramode pathgridmode selectionmode pathgridselectionmode cameracontroller orbitcameramode pathgridmode selectionmode pathgridselectionmode cameracontroller
cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw commands cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw commands objectmarker
) )
opencs_units (view/render opencs_units (view/render

View file

@ -180,7 +180,10 @@ void CSMPrefs::State::declare()
declareInt(mValues->mRendering.mCameraOrthoSize, "Orthographic Projection Size Parameter") declareInt(mValues->mRendering.mCameraOrthoSize, "Orthographic Projection Size Parameter")
.setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world.") .setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world.")
.setRange(10, 10000); .setRange(10, 10000);
declareDouble(mValues->mRendering.mObjectMarkerAlpha, "Object Marker Transparency").setPrecision(2).setRange(0, 1); declareDouble(mValues->mRendering.mObjectMarkerScale, "Object Marker Scale Factor")
.setPrecision(2)
.setRange(.01f, 100.f)
.setTooltip("Multiplier for the size of object selection markers.");
declareBool(mValues->mRendering.mSceneUseGradient, "Use Gradient Background"); declareBool(mValues->mRendering.mSceneUseGradient, "Use Gradient Background");
declareColour(mValues->mRendering.mSceneDayBackgroundColour, "Day Background Colour"); declareColour(mValues->mRendering.mSceneDayBackgroundColour, "Day Background Colour");
declareColour(mValues->mRendering.mSceneDayGradientColour, "Day Gradient Colour") declareColour(mValues->mRendering.mSceneDayGradientColour, "Day Gradient Colour")
@ -376,6 +379,7 @@ void CSMPrefs::State::declare()
declareShortcut(mValues->mKeyBindings.mSceneScaleSubmode, "Scale Object Submode"); declareShortcut(mValues->mKeyBindings.mSceneScaleSubmode, "Scale Object Submode");
declareShortcut(mValues->mKeyBindings.mSceneRotateSubmode, "Rotate Object Submode"); declareShortcut(mValues->mKeyBindings.mSceneRotateSubmode, "Rotate Object Submode");
declareShortcut(mValues->mKeyBindings.mSceneCameraCycle, "Cycle Camera Mode"); declareShortcut(mValues->mKeyBindings.mSceneCameraCycle, "Cycle Camera Mode");
declareShortcut(mValues->mKeyBindings.mSceneToggleMarker, "Toggle Selection Marker");
declareSubcategory("1st/Free Camera"); declareSubcategory("1st/Free Camera");
declareShortcut(mValues->mKeyBindings.mFreeForward, "Forward"); declareShortcut(mValues->mKeyBindings.mFreeForward, "Forward");

View file

@ -258,7 +258,7 @@ namespace CSMPrefs
Settings::SettingValue<int> mCameraFov{ mIndex, sName, "camera-fov", 90 }; Settings::SettingValue<int> mCameraFov{ mIndex, sName, "camera-fov", 90 };
Settings::SettingValue<bool> mCameraOrtho{ mIndex, sName, "camera-ortho", false }; Settings::SettingValue<bool> mCameraOrtho{ mIndex, sName, "camera-ortho", false };
Settings::SettingValue<int> mCameraOrthoSize{ mIndex, sName, "camera-ortho-size", 100 }; Settings::SettingValue<int> mCameraOrthoSize{ mIndex, sName, "camera-ortho-size", 100 };
Settings::SettingValue<double> mObjectMarkerAlpha{ mIndex, sName, "object-marker-alpha", 0.5 }; Settings::SettingValue<double> mObjectMarkerScale{ mIndex, sName, "object-marker-scale", 5.0 };
Settings::SettingValue<bool> mSceneUseGradient{ mIndex, sName, "scene-use-gradient", true }; Settings::SettingValue<bool> mSceneUseGradient{ mIndex, sName, "scene-use-gradient", true };
Settings::SettingValue<std::string> mSceneDayBackgroundColour{ mIndex, sName, "scene-day-background-colour", Settings::SettingValue<std::string> mSceneDayBackgroundColour{ mIndex, sName, "scene-day-background-colour",
"#6e7880" }; "#6e7880" };
@ -491,7 +491,7 @@ namespace CSMPrefs
Settings::SettingValue<std::string> mSceneScaleSubmode{ mIndex, sName, "scene-submode-scale", "V" }; Settings::SettingValue<std::string> mSceneScaleSubmode{ mIndex, sName, "scene-submode-scale", "V" };
Settings::SettingValue<std::string> mSceneRotateSubmode{ mIndex, sName, "scene-submode-rotate", "R" }; Settings::SettingValue<std::string> mSceneRotateSubmode{ mIndex, sName, "scene-submode-rotate", "R" };
Settings::SettingValue<std::string> mSceneCameraCycle{ mIndex, sName, "scene-cam-cycle", "Tab" }; Settings::SettingValue<std::string> mSceneCameraCycle{ mIndex, sName, "scene-cam-cycle", "Tab" };
Settings::SettingValue<std::string> mSceneToggleMarkers{ mIndex, sName, "scene-toggle-markers", "F4" }; Settings::SettingValue<std::string> mSceneToggleMarker{ mIndex, sName, "scene-toggle-marker", "F4" };
Settings::SettingValue<std::string> mFreeForward{ mIndex, sName, "free-forward", "W" }; Settings::SettingValue<std::string> mFreeForward{ mIndex, sName, "free-forward", "W" };
Settings::SettingValue<std::string> mFreeBackward{ mIndex, sName, "free-backward", "S" }; Settings::SettingValue<std::string> mFreeBackward{ mIndex, sName, "free-backward", "S" };
Settings::SettingValue<std::string> mFreeLeft{ mIndex, sName, "free-left", "A" }; Settings::SettingValue<std::string> mFreeLeft{ mIndex, sName, "free-left", "A" };

View file

@ -25,9 +25,10 @@
#include "cellwater.hpp" #include "cellwater.hpp"
#include "instancedragmodes.hpp" #include "instancedragmodes.hpp"
#include "mask.hpp" #include "mask.hpp"
#include "object.hpp" #include "objectmarker.hpp"
#include "pathgrid.hpp" #include "pathgrid.hpp"
#include "terrainstorage.hpp" #include "terrainstorage.hpp"
#include "worldspacewidget.hpp"
#include <apps/opencs/model/world/cell.hpp> #include <apps/opencs/model/world/cell.hpp>
#include <apps/opencs/model/world/cellcoordinates.hpp> #include <apps/opencs/model/world/cellcoordinates.hpp>
@ -107,9 +108,6 @@ bool CSVRender::Cell::addObjects(int start, int end)
auto object = std::make_unique<Object>(mData, mCellNode, id, false); auto object = std::make_unique<Object>(mData, mCellNode, id, false);
if (mSubModeElementMask & Mask_Reference)
object->setSubMode(mSubMode);
mObjects.insert(std::make_pair(id, object.release())); mObjects.insert(std::make_pair(id, object.release()));
modified = true; modified = true;
} }
@ -168,9 +166,10 @@ void CSVRender::Cell::unloadLand()
mCellBorder.reset(); mCellBorder.reset();
} }
CSVRender::Cell::Cell( CSVRender::Cell::Cell(CSMDoc::Document& document, ObjectMarker* selectionMarker, osg::Group* rootNode,
CSMDoc::Document& document, osg::Group* rootNode, const std::string& id, bool deleted, bool isExterior) const std::string& id, bool deleted, bool isExterior)
: mData(document.getData()) : mSelectionMarker(selectionMarker)
, mData(document.getData())
, mId(ESM::RefId::stringRefId(id)) , mId(ESM::RefId::stringRefId(id))
, mDeleted(deleted) , mDeleted(deleted)
, mSubMode(0) , mSubMode(0)
@ -466,7 +465,10 @@ void CSVRender::Cell::setSelection(int elementMask, Selection mode)
} }
iter->second->setSelected(selected); iter->second->setSelected(selected);
if (selected)
mSelectionMarker->addToSelectionHistory(iter->second->getReferenceId(), false);
} }
mSelectionMarker->updateSelectionMarker();
} }
if (mPathgrid && elementMask & Mask_Pathgrid) if (mPathgrid && elementMask & Mask_Pathgrid)
{ {
@ -506,8 +508,10 @@ void CSVRender::Cell::selectAllWithSameParentId(int elementMask)
if (!iter->second->getSelected() && ids.find(iter->second->getReferenceableId()) != ids.end()) if (!iter->second->getSelected() && ids.find(iter->second->getReferenceableId()) != ids.end())
{ {
iter->second->setSelected(true); iter->second->setSelected(true);
mSelectionMarker->addToSelectionHistory(iter->second->getReferenceId(), false);
} }
} }
mSelectionMarker->updateSelectionMarker();
} }
void CSVRender::Cell::handleSelectDrag(Object* object, DragMode dragMode) void CSVRender::Cell::handleSelectDrag(Object* object, DragMode dragMode)
@ -520,6 +524,9 @@ void CSVRender::Cell::handleSelectDrag(Object* object, DragMode dragMode)
else if (dragMode == DragMode_Select_Invert) else if (dragMode == DragMode_Select_Invert)
object->setSelected(!object->getSelected()); object->setSelected(!object->getSelected());
if (object->getSelected())
mSelectionMarker->addToSelectionHistory(object->getReferenceId(), false);
} }
void CSVRender::Cell::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) void CSVRender::Cell::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode)
@ -542,6 +549,8 @@ void CSVRender::Cell::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3
} }
} }
} }
mSelectionMarker->updateSelectionMarker();
} }
void CSVRender::Cell::selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) void CSVRender::Cell::selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode)
@ -555,6 +564,8 @@ void CSVRender::Cell::selectWithinDistance(const osg::Vec3d& point, float distan
if (distanceFromObject < distance) if (distanceFromObject < distance)
handleSelectDrag(object.second, dragMode); handleSelectDrag(object.second, dragMode);
} }
mSelectionMarker->updateSelectionMarker();
} }
void CSVRender::Cell::setCellArrows(int mask) void CSVRender::Cell::setCellArrows(int mask)
@ -625,9 +636,11 @@ void CSVRender::Cell::selectFromGroup(const std::vector<std::string>& group)
if (objectName == object->getReferenceId()) if (objectName == object->getReferenceId())
{ {
object->setSelected(true, osg::Vec4f(1, 0, 1, 1)); object->setSelected(true, osg::Vec4f(1, 0, 1, 1));
mSelectionMarker->addToSelectionHistory(object->getReferenceId(), false);
} }
} }
} }
mSelectionMarker->updateSelectionMarker();
} }
void CSVRender::Cell::unhideAll() void CSVRender::Cell::unhideAll()
@ -673,8 +686,7 @@ void CSVRender::Cell::setSubMode(int subMode, unsigned int elementMask)
mSubModeElementMask = elementMask; mSubModeElementMask = elementMask;
if (elementMask & Mask_Reference) if (elementMask & Mask_Reference)
for (std::map<std::string, Object*>::const_iterator iter(mObjects.begin()); iter != mObjects.end(); ++iter) mSelectionMarker->setSubMode(subMode);
iter->second->setSubMode(subMode);
} }
void CSVRender::Cell::reset(unsigned int elementMask) void CSVRender::Cell::reset(unsigned int elementMask)
@ -685,3 +697,11 @@ void CSVRender::Cell::reset(unsigned int elementMask)
if (mPathgrid && elementMask & Mask_Pathgrid) if (mPathgrid && elementMask & Mask_Pathgrid)
mPathgrid->resetIndicators(); mPathgrid->resetIndicators();
} }
CSVRender::Object* CSVRender::Cell::getObjectByReferenceId(const std::string& referenceId)
{
if (auto iter = mObjects.find(Misc::StringUtils::lowerCase(referenceId)); iter != mObjects.end())
return iter->second;
else
return nullptr;
}

View file

@ -9,9 +9,9 @@
#include <osg/Vec3d> #include <osg/Vec3d>
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include "../../model/doc/document.hpp"
#include "../../model/world/cellcoordinates.hpp" #include "../../model/world/cellcoordinates.hpp"
#include "instancedragmodes.hpp" #include "instancedragmodes.hpp"
#include "worldspacewidget.hpp"
#include <components/esm/refid.hpp> #include <components/esm/refid.hpp>
#include <components/misc/algorithm.hpp> #include <components/misc/algorithm.hpp>
@ -44,8 +44,11 @@ namespace CSVRender
class CellBorder; class CellBorder;
class CellMarker; class CellMarker;
class ObjectMarker;
class Cell class Cell
{ {
ObjectMarker* const mSelectionMarker;
CSMWorld::Data& mData; CSMWorld::Data& mData;
ESM::RefId mId; ESM::RefId mId;
osg::ref_ptr<osg::Group> mCellNode; osg::ref_ptr<osg::Group> mCellNode;
@ -90,8 +93,8 @@ namespace CSVRender
public: public:
/// \note Deleted covers both cells that are deleted and cells that don't exist in /// \note Deleted covers both cells that are deleted and cells that don't exist in
/// the first place. /// the first place.
Cell(CSMDoc::Document& document, osg::Group* rootNode, const std::string& id, bool deleted = false, Cell(CSMDoc::Document& document, ObjectMarker* selectionMarker, osg::Group* rootNode, const std::string& id,
bool isExterior = false); bool deleted = false, bool isExterior = false);
~Cell(); ~Cell();
@ -182,6 +185,8 @@ namespace CSVRender
/// true state. /// true state.
void reset(unsigned int elementMask); void reset(unsigned int elementMask);
CSVRender::Object* getObjectByReferenceId(const std::string& referenceId);
friend class CellNodeCallback; friend class CellNodeCallback;
}; };
} }

View file

@ -362,7 +362,29 @@ CSVRender::InstanceMode::InstanceMode(
for (const char axis : "xyz") for (const char axis : "xyz")
connect(new CSMPrefs::Shortcut(std::string("scene-axis-") + axis, worldspaceWidget), connect(new CSMPrefs::Shortcut(std::string("scene-axis-") + axis, worldspaceWidget),
qOverload<>(&CSMPrefs::Shortcut::activated), this, [this, axis] { this->setDragAxis(axis); }); qOverload<>(&CSMPrefs::Shortcut::activated), this, [this, axis] {
this->setDragAxis(axis);
std::string axisStr(1, toupper(axis));
switch (getSubMode())
{
case (Object::Mode_Move):
axisStr += "_Axis";
break;
case (Object::Mode_Rotate):
axisStr += "_Axis_Rot";
break;
case (Object::Mode_Scale):
axisStr += "_Axis_Scale";
break;
}
auto selectionMarker = getWorldspaceWidget().getSelectionMarker();
if (mDragAxis != -1)
selectionMarker->updateMarkerHighlight(axisStr, axis - 'x');
else
selectionMarker->resetMarkerHighlight();
});
} }
void CSVRender::InstanceMode::activate(CSVWidget::SceneToolbar* toolbar) void CSVRender::InstanceMode::activate(CSVWidget::SceneToolbar* toolbar)
@ -460,52 +482,58 @@ void CSVRender::InstanceMode::secondaryEditPressed(const WorldspaceHitResult& hi
void CSVRender::InstanceMode::primarySelectPressed(const WorldspaceHitResult& hit) void CSVRender::InstanceMode::primarySelectPressed(const WorldspaceHitResult& hit)
{ {
getWorldspaceWidget().clearSelection(Mask_Reference); auto& worldspaceWidget = getWorldspaceWidget();
if (hit.tag) worldspaceWidget.clearSelection(Mask_Reference);
if (!hit.tag)
return;
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{ {
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get())) // hit an Object, select it
{ CSVRender::Object* object = objectTag->mObject;
// hit an Object, select it object->setSelected(true);
CSVRender::Object* object = objectTag->mObject; worldspaceWidget.getSelectionMarker()->addToSelectionHistory(object->getReferenceId());
object->setSelected(true);
return;
}
} }
} }
void CSVRender::InstanceMode::secondarySelectPressed(const WorldspaceHitResult& hit) void CSVRender::InstanceMode::secondarySelectPressed(const WorldspaceHitResult& hit)
{ {
if (hit.tag) if (!hit.tag)
return;
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{ {
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get())) // hit an Object, toggle its selection state
{ CSVRender::Object* object = objectTag->mObject;
// hit an Object, toggle its selection state object->setSelected(!object->getSelected());
CSVRender::Object* object = objectTag->mObject;
object->setSelected(!object->getSelected()); const auto selectionMarker = getWorldspaceWidget().getSelectionMarker();
return;
} if (object->getSelected())
selectionMarker->addToSelectionHistory(object->getReferenceId(), false);
selectionMarker->updateSelectionMarker();
} }
} }
void CSVRender::InstanceMode::tertiarySelectPressed(const WorldspaceHitResult& hit) void CSVRender::InstanceMode::tertiarySelectPressed(const WorldspaceHitResult& hit)
{ {
auto* snapTarget = dynamic_cast<CSVRender::ObjectTag*>(getWorldspaceWidget().getSnapTarget(Mask_Reference).get()); if (auto* snapTarget
= dynamic_cast<CSVRender::ObjectTag*>(getWorldspaceWidget().getSnapTarget(Mask_Reference).get()))
if (snapTarget)
{ {
snapTarget->mObject->setSnapTarget(false); snapTarget->mObject->setSnapTarget(false);
} }
if (hit.tag) if (!hit.tag)
return;
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{ {
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get())) // hit an Object, toggle its selection state
{ CSVRender::Object* object = objectTag->mObject;
// hit an Object, toggle its selection state object->setSnapTarget(!object->getSnapTarget());
CSVRender::Object* object = objectTag->mObject;
object->setSnapTarget(!object->getSnapTarget());
return;
}
} }
} }
@ -514,23 +542,26 @@ bool CSVRender::InstanceMode::primaryEditStartDrag(const QPoint& pos)
if (mDragMode != DragMode_None || mLocked) if (mDragMode != DragMode_None || mLocked)
return false; return false;
WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); auto& worldspaceWidget = getWorldspaceWidget();
std::vector<osg::ref_ptr<TagBase>> selection = getWorldspaceWidget().getSelection(Mask_Reference); WorldspaceHitResult hit = worldspaceWidget.mousePick(pos, worldspaceWidget.getInteractionMask());
std::vector<osg::ref_ptr<TagBase>> selection = worldspaceWidget.getSelection(Mask_Reference);
if (selection.empty()) if (selection.empty())
{ {
// Only change selection at the start of drag if no object is already selected // Only change selection at the start of drag if no object is already selected
if (hit.tag && CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) if (hit.tag && CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue())
{ {
getWorldspaceWidget().clearSelection(Mask_Reference); worldspaceWidget.clearSelection(Mask_Reference);
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get())) if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{ {
CSVRender::Object* object = objectTag->mObject; CSVRender::Object* object = objectTag->mObject;
object->setSelected(true); object->setSelected(true);
worldspaceWidget.getSelectionMarker()->addToSelectionHistory(object->getReferenceId());
} }
} }
selection = getWorldspaceWidget().getSelection(Mask_Reference); selection = worldspaceWidget.getSelection(Mask_Reference);
if (selection.empty()) if (selection.empty())
return false; return false;
} }
@ -591,23 +622,26 @@ bool CSVRender::InstanceMode::secondaryEditStartDrag(const QPoint& pos)
if (mDragMode != DragMode_None || mLocked) if (mDragMode != DragMode_None || mLocked)
return false; return false;
WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); auto& worldspaceWidget = getWorldspaceWidget();
std::vector<osg::ref_ptr<TagBase>> selection = getWorldspaceWidget().getSelection(Mask_Reference); WorldspaceHitResult hit = worldspaceWidget.mousePick(pos, worldspaceWidget.getInteractionMask());
std::vector<osg::ref_ptr<TagBase>> selection = worldspaceWidget.getSelection(Mask_Reference);
if (selection.empty()) if (selection.empty())
{ {
// Only change selection at the start of drag if no object is already selected // Only change selection at the start of drag if no object is already selected
if (hit.tag && CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) if (hit.tag && CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue())
{ {
getWorldspaceWidget().clearSelection(Mask_Reference); worldspaceWidget.clearSelection(Mask_Reference);
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get())) if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{ {
CSVRender::Object* object = objectTag->mObject; CSVRender::Object* object = objectTag->mObject;
object->setSelected(true); object->setSelected(true);
worldspaceWidget.getSelectionMarker()->addToSelectionHistory(object->getReferenceId());
} }
} }
selection = getWorldspaceWidget().getSelection(Mask_Reference); selection = worldspaceWidget.getSelection(Mask_Reference);
if (selection.empty()) if (selection.empty())
return false; return false;
} }
@ -641,10 +675,10 @@ bool CSVRender::InstanceMode::secondaryEditStartDrag(const QPoint& pos)
mDragMode = DragMode_Scale_Snap; mDragMode = DragMode_Scale_Snap;
// Calculate scale factor // Calculate scale factor
std::vector<osg::ref_ptr<TagBase>> editedSelection = getWorldspaceWidget().getEdited(Mask_Reference); std::vector<osg::ref_ptr<TagBase>> editedSelection = worldspaceWidget.getEdited(Mask_Reference);
osg::Vec3f center = getScreenCoords(getSelectionCenter(editedSelection)); osg::Vec3f center = getScreenCoords(getSelectionCenter(editedSelection));
int widgetHeight = getWorldspaceWidget().height(); int widgetHeight = worldspaceWidget.height();
float dx = pos.x() - center.x(); float dx = pos.x() - center.x();
float dy = (widgetHeight - pos.y()) - center.y(); float dy = (widgetHeight - pos.y()) - center.y();

View file

@ -18,25 +18,11 @@
#include <apps/opencs/model/world/universalid.hpp> #include <apps/opencs/model/world/universalid.hpp>
#include <apps/opencs/view/render/tagbase.hpp> #include <apps/opencs/view/render/tagbase.hpp>
#include <osg/Array>
#include <osg/BoundingSphere>
#include <osg/GL>
#include <osg/Geometry>
#include <osg/Group>
#include <osg/Math>
#include <osg/Node>
#include <osg/PositionAttitudeTransform>
#include <osg/PrimitiveSet>
#include <osg/Quat> #include <osg/Quat>
#include <osg/Shape>
#include <osg/ShapeDrawable> #include <osg/ShapeDrawable>
#include <osg/StateAttribute>
#include <osg/StateSet>
#include <osg/Vec3>
#include <osgFX/Scribe> #include <osgFX/Scribe>
#include "../../model/prefs/state.hpp"
#include "../../model/world/cellcoordinates.hpp" #include "../../model/world/cellcoordinates.hpp"
#include "../../model/world/commandmacro.hpp" #include "../../model/world/commandmacro.hpp"
#include "../../model/world/commands.hpp" #include "../../model/world/commands.hpp"
@ -63,11 +49,6 @@ namespace ESM
struct Light; struct Light;
} }
const float CSVRender::Object::MarkerShaftWidth = 30;
const float CSVRender::Object::MarkerShaftBaseLength = 70;
const float CSVRender::Object::MarkerHeadWidth = 50;
const float CSVRender::Object::MarkerHeadLength = 50;
namespace namespace
{ {
@ -95,12 +76,6 @@ QString CSVRender::ObjectTag::getToolTip(bool /*hideBasics*/, const WorldspaceHi
return QString::fromUtf8(mObject->getReferenceableId().c_str()); return QString::fromUtf8(mObject->getReferenceableId().c_str());
} }
CSVRender::ObjectMarkerTag::ObjectMarkerTag(Object* object, int axis)
: ObjectTag(object)
, mAxis(axis)
{
}
void CSVRender::Object::clear() {} void CSVRender::Object::clear() {}
void CSVRender::Object::update() void CSVRender::Object::update()
@ -204,238 +179,6 @@ const CSMWorld::CellRef& CSVRender::Object::getReference() const
return mData.getReferences().getRecord(mReferenceId).get(); return mData.getReferences().getRecord(mReferenceId).get();
} }
void CSVRender::Object::updateMarker()
{
for (int i = 0; i < 3; ++i)
{
if (mMarker[i])
{
mRootNode->removeChild(mMarker[i]);
mMarker[i] = osg::ref_ptr<osg::Node>();
}
if (mSelected)
{
if (mSubMode == 0)
{
mMarker[i] = makeMoveOrScaleMarker(i);
mMarker[i]->setUserData(new ObjectMarkerTag(this, i));
mRootNode->addChild(mMarker[i]);
}
else if (mSubMode == 1)
{
mMarker[i] = makeRotateMarker(i);
mMarker[i]->setUserData(new ObjectMarkerTag(this, i));
mRootNode->addChild(mMarker[i]);
}
else if (mSubMode == 2)
{
mMarker[i] = makeMoveOrScaleMarker(i);
mMarker[i]->setUserData(new ObjectMarkerTag(this, i));
mRootNode->addChild(mMarker[i]);
}
}
}
}
osg::ref_ptr<osg::Node> CSVRender::Object::makeMoveOrScaleMarker(int axis)
{
osg::ref_ptr<osg::Geometry> geometry(new osg::Geometry);
float shaftLength = MarkerShaftBaseLength + mBaseNode->getBound().radius();
// shaft
osg::Vec3Array* vertices = new osg::Vec3Array;
for (int i = 0; i < 2; ++i)
{
float length = i ? shaftLength : MarkerShaftWidth;
vertices->push_back(getMarkerPosition(-MarkerShaftWidth / 2, -MarkerShaftWidth / 2, length, axis));
vertices->push_back(getMarkerPosition(-MarkerShaftWidth / 2, MarkerShaftWidth / 2, length, axis));
vertices->push_back(getMarkerPosition(MarkerShaftWidth / 2, MarkerShaftWidth / 2, length, axis));
vertices->push_back(getMarkerPosition(MarkerShaftWidth / 2, -MarkerShaftWidth / 2, length, axis));
}
// head backside
vertices->push_back(getMarkerPosition(-MarkerHeadWidth / 2, -MarkerHeadWidth / 2, shaftLength, axis));
vertices->push_back(getMarkerPosition(-MarkerHeadWidth / 2, MarkerHeadWidth / 2, shaftLength, axis));
vertices->push_back(getMarkerPosition(MarkerHeadWidth / 2, MarkerHeadWidth / 2, shaftLength, axis));
vertices->push_back(getMarkerPosition(MarkerHeadWidth / 2, -MarkerHeadWidth / 2, shaftLength, axis));
// head
vertices->push_back(getMarkerPosition(0, 0, shaftLength + MarkerHeadLength, axis));
geometry->setVertexArray(vertices);
osg::DrawElementsUShort* primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, 0);
// shaft
for (int i = 0; i < 4; ++i)
{
int i2 = i == 3 ? 0 : i + 1;
primitives->push_back(i);
primitives->push_back(4 + i);
primitives->push_back(i2);
primitives->push_back(4 + i);
primitives->push_back(4 + i2);
primitives->push_back(i2);
}
// cap
primitives->push_back(0);
primitives->push_back(1);
primitives->push_back(2);
primitives->push_back(2);
primitives->push_back(3);
primitives->push_back(0);
// head, backside
primitives->push_back(0 + 8);
primitives->push_back(1 + 8);
primitives->push_back(2 + 8);
primitives->push_back(2 + 8);
primitives->push_back(3 + 8);
primitives->push_back(0 + 8);
for (int i = 0; i < 4; ++i)
{
primitives->push_back(12);
primitives->push_back(8 + (i == 3 ? 0 : i + 1));
primitives->push_back(8 + i);
}
geometry->addPrimitiveSet(primitives);
osg::Vec4Array* colours = new osg::Vec4Array;
for (int i = 0; i < 8; ++i)
colours->push_back(
osg::Vec4f(axis == 0 ? 1.0f : 0.2f, axis == 1 ? 1.0f : 0.2f, axis == 2 ? 1.0f : 0.2f, mMarkerTransparency));
for (int i = 8; i < 8 + 4 + 1; ++i)
colours->push_back(
osg::Vec4f(axis == 0 ? 1.0f : 0.0f, axis == 1 ? 1.0f : 0.0f, axis == 2 ? 1.0f : 0.0f, mMarkerTransparency));
geometry->setColorArray(colours, osg::Array::BIND_PER_VERTEX);
setupCommonMarkerState(geometry);
osg::ref_ptr<osg::Group> group(new osg::Group);
group->addChild(geometry);
return group;
}
osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker(int axis)
{
const float InnerRadius = std::max(MarkerShaftBaseLength, mBaseNode->getBound().radius());
const float OuterRadius = InnerRadius + MarkerShaftWidth;
const float SegmentDistance = 100.f;
const size_t SegmentCount = std::clamp<int>(OuterRadius * 2 * osg::PI / SegmentDistance, 24, 64);
const size_t VerticesPerSegment = 4;
const size_t IndicesPerSegment = 24;
const size_t VertexCount = SegmentCount * VerticesPerSegment;
const size_t IndexCount = SegmentCount * IndicesPerSegment;
const float Angle = 2 * osg::PI / SegmentCount;
const unsigned short IndexPattern[IndicesPerSegment]
= { 0, 4, 5, 0, 5, 1, 2, 6, 4, 2, 4, 0, 3, 7, 6, 3, 6, 2, 1, 5, 7, 1, 7, 3 };
osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry();
osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array(VertexCount);
osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array(1);
osg::ref_ptr<osg::DrawElementsUShort> primitives
= new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, IndexCount);
// prevent some depth collision issues from overlaps
osg::Vec3f offset = getMarkerPosition(0, MarkerShaftWidth / 4, 0, axis);
for (size_t i = 0; i < SegmentCount; ++i)
{
size_t index = i * VerticesPerSegment;
float innerX = InnerRadius * std::cos(i * Angle);
float innerY = InnerRadius * std::sin(i * Angle);
float outerX = OuterRadius * std::cos(i * Angle);
float outerY = OuterRadius * std::sin(i * Angle);
vertices->at(index++) = getMarkerPosition(innerX, innerY, MarkerShaftWidth / 2, axis) + offset;
vertices->at(index++) = getMarkerPosition(innerX, innerY, -MarkerShaftWidth / 2, axis) + offset;
vertices->at(index++) = getMarkerPosition(outerX, outerY, MarkerShaftWidth / 2, axis) + offset;
vertices->at(index++) = getMarkerPosition(outerX, outerY, -MarkerShaftWidth / 2, axis) + offset;
}
colors->at(0)
= osg::Vec4f(axis == 0 ? 1.0f : 0.2f, axis == 1 ? 1.0f : 0.2f, axis == 2 ? 1.0f : 0.2f, mMarkerTransparency);
for (size_t i = 0; i < SegmentCount; ++i)
{
size_t indices[IndicesPerSegment];
for (size_t j = 0; j < IndicesPerSegment; ++j)
{
indices[j] = i * VerticesPerSegment + j;
if (indices[j] >= VertexCount)
indices[j] -= VertexCount;
}
size_t elementOffset = i * IndicesPerSegment;
for (size_t j = 0; j < IndicesPerSegment; ++j)
{
primitives->setElement(elementOffset++, indices[IndexPattern[j]]);
}
}
geometry->setVertexArray(vertices);
geometry->setColorArray(colors, osg::Array::BIND_OVERALL);
geometry->addPrimitiveSet(primitives);
setupCommonMarkerState(geometry);
osg::ref_ptr<osg::Group> group = new osg::Group();
group->addChild(geometry);
return group;
}
void CSVRender::Object::setupCommonMarkerState(osg::ref_ptr<osg::Geometry> geometry)
{
osg::ref_ptr<osg::StateSet> state = geometry->getOrCreateStateSet();
state->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
state->setMode(GL_BLEND, osg::StateAttribute::ON);
state->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
}
osg::Vec3f CSVRender::Object::getMarkerPosition(float x, float y, float z, int axis)
{
switch (axis)
{
case 2:
return osg::Vec3f(x, y, z);
case 0:
return osg::Vec3f(z, x, y);
case 1:
return osg::Vec3f(y, z, x);
default:
throw std::logic_error("invalid axis for marker geometry");
}
}
CSVRender::Object::Object( CSVRender::Object::Object(
CSMWorld::Data& data, osg::Group* parentNode, const std::string& id, bool referenceable, bool forceBaseToZero) CSMWorld::Data& data, osg::Group* parentNode, const std::string& id, bool referenceable, bool forceBaseToZero)
: mData(data) : mData(data)
@ -446,8 +189,6 @@ CSVRender::Object::Object(
, mForceBaseToZero(forceBaseToZero) , mForceBaseToZero(forceBaseToZero)
, mScaleOverride(1) , mScaleOverride(1)
, mOverrideFlags(0) , mOverrideFlags(0)
, mSubMode(-1)
, mMarkerTransparency(0.5f)
{ {
mRootNode = new osg::PositionAttitudeTransform; mRootNode = new osg::PositionAttitudeTransform;
@ -476,7 +217,6 @@ CSVRender::Object::Object(
adjustTransform(); adjustTransform();
update(); update();
updateMarker();
} }
CSVRender::Object::~Object() CSVRender::Object::~Object()
@ -506,9 +246,6 @@ void CSVRender::Object::setSelected(bool selected, const osg::Vec4f& color)
} }
else else
mRootNode->addChild(mBaseNode); mRootNode->addChild(mBaseNode);
mMarkerTransparency = CSMPrefs::get()["Rendering"]["object-marker-alpha"].toDouble();
updateMarker();
} }
bool CSVRender::Object::getSelected() const bool CSVRender::Object::getSelected() const
@ -536,9 +273,6 @@ void CSVRender::Object::setSnapTarget(bool isSnapTarget)
} }
else else
mRootNode->addChild(mBaseNode); mRootNode->addChild(mBaseNode);
mMarkerTransparency = CSMPrefs::get()["Rendering"]["object-marker-alpha"].toDouble();
updateMarker();
} }
bool CSVRender::Object::getSnapTarget() const bool CSVRender::Object::getSnapTarget() const
@ -566,7 +300,6 @@ bool CSVRender::Object::referenceableDataChanged(const QModelIndex& topLeft, con
{ {
adjustTransform(); adjustTransform();
update(); update();
updateMarker();
return true; return true;
} }
@ -614,7 +347,6 @@ bool CSVRender::Object::referenceDataChanged(const QModelIndex& topLeft, const Q
= ESM::RefId::stringRefId(references.getData(index, columnIndex).toString().toUtf8().constData()); = ESM::RefId::stringRefId(references.getData(index, columnIndex).toString().toUtf8().constData());
update(); update();
updateMarker();
} }
return true; return true;
@ -626,7 +358,6 @@ bool CSVRender::Object::referenceDataChanged(const QModelIndex& topLeft, const Q
void CSVRender::Object::reloadAssets() void CSVRender::Object::reloadAssets()
{ {
update(); update();
updateMarker();
} }
std::string CSVRender::Object::getReferenceId() const std::string CSVRender::Object::getReferenceId() const
@ -720,12 +451,6 @@ void CSVRender::Object::setScale(float scale)
adjustTransform(); adjustTransform();
} }
void CSVRender::Object::setMarkerTransparency(float value)
{
mMarkerTransparency = value;
updateMarker();
}
void CSVRender::Object::apply(CSMWorld::CommandMacro& commands) void CSVRender::Object::apply(CSMWorld::CommandMacro& commands)
{ {
const CSMWorld::RefCollection& collection = mData.getReferences(); const CSMWorld::RefCollection& collection = mData.getReferences();
@ -796,18 +521,8 @@ void CSVRender::Object::apply(CSMWorld::CommandMacro& commands)
mOverrideFlags = 0; mOverrideFlags = 0;
} }
void CSVRender::Object::setSubMode(int subMode)
{
if (subMode != mSubMode)
{
mSubMode = subMode;
updateMarker();
}
}
void CSVRender::Object::reset() void CSVRender::Object::reset()
{ {
mOverrideFlags = 0; mOverrideFlags = 0;
adjustTransform(); adjustTransform();
updateMarker();
} }

View file

@ -58,14 +58,6 @@ namespace CSVRender
QString getToolTip(bool hideBasics, const WorldspaceHitResult& hit) const override; QString getToolTip(bool hideBasics, const WorldspaceHitResult& hit) const override;
}; };
class ObjectMarkerTag : public ObjectTag
{
public:
ObjectMarkerTag(Object* object, int axis);
int mAxis;
};
class Object class Object
{ {
public: public:
@ -76,12 +68,22 @@ namespace CSVRender
Override_Scale = 4 Override_Scale = 4
}; };
private: enum SubMode
static const float MarkerShaftWidth; {
static const float MarkerShaftBaseLength; Mode_Move,
static const float MarkerHeadWidth; Mode_Rotate,
static const float MarkerHeadLength; Mode_Scale,
Mode_None,
};
enum Axis
{
Axis_X,
Axis_Y,
Axis_Z
};
private:
CSMWorld::Data& mData; CSMWorld::Data& mData;
ESM::RefId mReferenceId; ESM::RefId mReferenceId;
ESM::RefId mReferenceableId; ESM::RefId mReferenceableId;
@ -96,9 +98,6 @@ namespace CSVRender
ESM::Position mPositionOverride; ESM::Position mPositionOverride;
float mScaleOverride; float mScaleOverride;
int mOverrideFlags; int mOverrideFlags;
osg::ref_ptr<osg::Node> mMarker[3];
int mSubMode;
float mMarkerTransparency;
std::unique_ptr<Actor> mActor; std::unique_ptr<Actor> mActor;
/// Not implemented /// Not implemented
@ -120,16 +119,6 @@ namespace CSVRender
/// Throws an exception if *this was constructed with referenceable /// Throws an exception if *this was constructed with referenceable
const CSMWorld::CellRef& getReference() const; const CSMWorld::CellRef& getReference() const;
void updateMarker();
osg::ref_ptr<osg::Node> makeMoveOrScaleMarker(int axis);
osg::ref_ptr<osg::Node> makeRotateMarker(int axis);
/// Sets up a stateset with properties common to all marker types.
void setupCommonMarkerState(osg::ref_ptr<osg::Geometry> geometry);
osg::Vec3f getMarkerPosition(float x, float y, float z, int axis);
public: public:
Object(CSMWorld::Data& data, osg::Group* cellNode, const std::string& id, bool referenceable, Object(CSMWorld::Data& data, osg::Group* cellNode, const std::string& id, bool referenceable,
bool forceBaseToZero = false); bool forceBaseToZero = false);
@ -199,8 +188,6 @@ namespace CSVRender
/// Apply override changes via command and end edit mode /// Apply override changes via command and end edit mode
void apply(CSMWorld::CommandMacro& commands); void apply(CSMWorld::CommandMacro& commands);
void setSubMode(int subMode);
/// Erase all overrides and restore the visual representation of the object to its /// Erase all overrides and restore the visual representation of the object to its
/// true state. /// true state.
void reset(); void reset();

View file

@ -0,0 +1,307 @@
#include <unordered_set>
#include <QFile>
#include <osg/ClipPlane>
#include <osg/Material>
#include <osg/PositionAttitudeTransform>
#include <osgUtil/CullVisitor>
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/visitor.hpp>
#include "../../model/prefs/state.hpp"
#include "objectmarker.hpp"
#include "worldspacewidget.hpp"
namespace
{
class FindMaterialVisitor : public osg::NodeVisitor
{
public:
FindMaterialVisitor(CSVRender::NodeMap& map)
: osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
, mMap(map)
{
}
void apply(osg::Geometry& node) override
{
osg::StateSet* state = node.getStateSet();
if (state->getAttribute(osg::StateAttribute::MATERIAL))
mMap.emplace(node.getName(), &node);
traverse(node);
}
private:
CSVRender::NodeMap& mMap;
};
class ToCamera : public SceneUtil::NodeCallback<ToCamera, osg::Node*, osgUtil::CullVisitor*>
{
public:
ToCamera(osg::ref_ptr<osg::ClipPlane> clipPlane)
: mClipPlane(std::move(clipPlane))
{
}
void operator()(osg::Node* node, osgUtil::CullVisitor* cv)
{
osg::Vec3f normal = cv->getEyePoint();
mClipPlane->setClipPlane(normal.x(), normal.y(), normal.z(), 0);
traverse(node, cv);
}
private:
osg::ref_ptr<osg::ClipPlane> mClipPlane;
};
auto addTagToActiveMarkerNodes = [](CSVRender::NodeMap& mMarkerNodes, CSVRender::Object* object,
std::initializer_list<std::string> suffixes) {
for (const auto& markerSuffix : suffixes)
{
for (char axis = 'X'; axis <= 'Z'; ++axis)
mMarkerNodes[axis + markerSuffix]->setUserData(new CSVRender::ObjectMarkerTag(object, axis - 'X'));
}
};
}
namespace CSVRender
{
ObjectMarkerTag::ObjectMarkerTag(Object* object, int axis)
: ObjectTag(object)
, mAxis(axis)
{
}
ObjectMarker::ObjectMarker(WorldspaceWidget* worldspaceWidget, Resource::ResourceSystem* resourceSystem)
: mWorldspaceWidget(worldspaceWidget)
, mResourceSystem(resourceSystem)
, mMarkerScale(CSMPrefs::get()["Rendering"]["object-marker-scale"].toDouble())
, mSubMode(Object::Mode_None)
{
mBaseNode = new osg::PositionAttitudeTransform;
mBaseNode->setNodeMask(Mask_Reference);
mBaseNode->setScale(osg::Vec3f(mMarkerScale, mMarkerScale, mMarkerScale));
mRootNode = new osg::PositionAttitudeTransform;
mRootNode->addChild(mBaseNode);
worldspaceWidget->setSelectionMarkerRoot(mRootNode);
QFile file(":render/selection-marker");
if (!file.open(QIODevice::ReadOnly))
throw std::runtime_error("Failed to open selection marker file");
auto markerData = file.readAll();
mResourceSystem->getSceneManager()->loadSelectionMarker(mBaseNode, markerData.data(), markerData.size());
osg::ref_ptr<osg::StateSet> baseNodeState = mBaseNode->getOrCreateStateSet();
baseNodeState->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
baseNodeState->setRenderBinDetails(1000, "RenderBin");
FindMaterialVisitor matMapper(mMarkerNodes);
mBaseNode->accept(matMapper);
for (const auto& [name, node] : mMarkerNodes)
{
osg::StateSet* state = node->getStateSet();
osg::Material* mat = static_cast<osg::Material*>(state->getAttribute(osg::StateAttribute::MATERIAL));
osg::Vec4f emis = mat->getEmission(osg::Material::FRONT_AND_BACK);
mat->setEmission(osg::Material::FRONT_AND_BACK, emis / 4);
mOriginalColors.emplace(name, emis);
}
SceneUtil::NodeMap sceneNodes;
SceneUtil::NodeMapVisitor nodeMapper(sceneNodes);
mBaseNode->accept(nodeMapper);
mMarkerNodes.insert(sceneNodes.begin(), sceneNodes.end());
osg::ref_ptr<osg::Node> rotateMarkers = mMarkerNodes["rotateMarkers"];
osg::ClipPlane* clip = new osg::ClipPlane(0);
rotateMarkers->setCullCallback(new ToCamera(clip));
rotateMarkers->getStateSet()->setAttributeAndModes(clip, osg::StateAttribute::ON);
}
void ObjectMarker::toggleVisibility()
{
bool isVisible = mBaseNode->getNodeMask() == Mask_Reference;
mBaseNode->setNodeMask(isVisible ? Mask_Hidden : Mask_Reference);
}
void ObjectMarker::updateScale(const float scale)
{
mMarkerScale = scale;
mBaseNode->setScale(osg::Vec3f(scale, scale, scale));
}
void ObjectMarker::setSubMode(const int subMode)
{
if (subMode == mSubMode)
return;
mSubMode = subMode;
resetMarkerHighlight();
updateSelectionMarker();
}
bool ObjectMarker::hitBehindMarker(const osg::Vec3d& hitPos, osg::ref_ptr<osg::Camera> camera)
{
if (mSubMode != Object::Mode_Rotate)
return false;
osg::Vec3d center, eye, forwardVector, _;
std::vector<osg::Node*> rotMark = mMarkerNodes["rotateMarkers"]->getParentalNodePaths()[0];
const osg::Vec3f markerPos = osg::computeLocalToWorld(rotMark).getTrans();
camera->getViewMatrixAsLookAt(eye, center, _);
forwardVector = center - eye;
forwardVector.normalize();
return (hitPos - markerPos) * forwardVector > 0;
}
bool ObjectMarker::attachMarker(const std::string& refId)
{
const auto& object = mWorldspaceWidget->getObjectByReferenceId(refId);
if (!object)
removeFromSelectionHistory(refId);
if (!object || !object->getSelected())
return false;
if (!object->getRootNode()->addChild(mRootNode))
throw std::runtime_error("Failed to add marker to object");
std::string parentMarkerNode;
switch (mSubMode)
{
case (Object::Mode_Rotate):
parentMarkerNode = "rotateMarkers";
addTagToActiveMarkerNodes(mMarkerNodes, object, { "_Axis_Rot" });
break;
case (Object::Mode_Scale):
parentMarkerNode = "scaleMarkers";
addTagToActiveMarkerNodes(mMarkerNodes, object, { "_Axis_Scale", "_Wall_Scale" });
break;
case (Object::Mode_Move):
default:
parentMarkerNode = "moveMarkers";
addTagToActiveMarkerNodes(mMarkerNodes, object, { "_Axis", "_Wall" });
break;
}
mMarkerNodes[parentMarkerNode]->asGroup()->setNodeMask(Mask_Reference);
return true;
}
void ObjectMarker::detachMarker()
{
for (std::size_t index = mRootNode->getNumParents(); index > 0;)
mRootNode->getParent(--index)->removeChild(mRootNode);
osg::ref_ptr<osg::Group> widgetRoot = mMarkerNodes["unitArrows"]->asGroup();
for (std::size_t index = widgetRoot->getNumChildren(); index > 0;)
widgetRoot->getChild(--index)->setNodeMask(Mask_Hidden);
}
void ObjectMarker::addToSelectionHistory(const std::string& refId, bool update)
{
auto foundObject = std::find_if(mSelectionHistory.begin(), mSelectionHistory.end(),
[&refId](const std::string& objId) { return objId == refId; });
if (foundObject == mSelectionHistory.end())
mSelectionHistory.push_back(refId);
else
std::rotate(foundObject, foundObject + 1, mSelectionHistory.end());
if (update)
updateSelectionMarker(refId);
}
void ObjectMarker::removeFromSelectionHistory(const std::string& refId)
{
mSelectionHistory.erase(std::remove_if(mSelectionHistory.begin(), mSelectionHistory.end(),
[&refId](const std::string& objId) { return objId == refId; }),
mSelectionHistory.end());
}
void ObjectMarker::updateSelectionMarker(const std::string& refId)
{
if (mSelectionHistory.empty())
return;
detachMarker();
if (refId.empty())
{
for (std::size_t index = mSelectionHistory.size(); index > 0;)
if (attachMarker(mSelectionHistory[--index]))
break;
}
else
attachMarker(refId);
}
void ObjectMarker::resetMarkerHighlight()
{
if (mLastHighlightedNodes.empty())
return;
for (const auto& [nodeName, mat] : mLastHighlightedNodes)
mat->setEmission(osg::Material::FRONT_AND_BACK, mat->getEmission(osg::Material::FRONT_AND_BACK) / 4);
mLastHighlightedNodes.clear();
mLastHitNode.clear();
}
void ObjectMarker::updateMarkerHighlight(const std::string_view hitNode, const int axis)
{
if (hitNode == mLastHitNode)
return;
resetMarkerHighlight();
std::string colorName;
switch (axis)
{
case Object::Axis_X:
colorName = "red";
break;
case Object::Axis_Y:
colorName = "green";
break;
case Object::Axis_Z:
colorName = "blue";
break;
default:
throw std::runtime_error("Invalid axis for highlighting: " + std::to_string(axis));
}
std::vector<std::string> targetMaterials = { colorName + "-material" };
if (mSubMode != Object::Mode_Rotate)
targetMaterials.emplace_back(colorName + "_alpha-material");
for (const auto& materialNodeName : targetMaterials)
{
osg::ref_ptr<osg::Node> matNode = mMarkerNodes[materialNodeName];
osg::StateSet* state = matNode->getStateSet();
osg::StateAttribute* matAttr = state->getAttribute(osg::StateAttribute::MATERIAL);
osg::Material* mat = static_cast<osg::Material*>(matAttr);
mat->setEmission(osg::Material::FRONT_AND_BACK, mOriginalColors[materialNodeName]);
mLastHighlightedNodes.emplace(std::make_pair(matNode->getName(), mat));
}
mLastHitNode = hitNode;
}
}

View file

@ -0,0 +1,77 @@
#ifndef OPENCS_VIEW_OBJECT_MARKER_H
#define OPENCS_VIEW_OBJECT_MARKER_H
#include "object.hpp"
namespace osg
{
class Camera;
class Material;
}
namespace CSVRender
{
using NodeMap = std::unordered_map<std::string, osg::ref_ptr<osg::Node>>;
class WorldspaceWidget;
class ObjectMarkerTag : public ObjectTag
{
public:
ObjectMarkerTag(Object* object, int axis);
int mAxis;
};
class ObjectMarker
{
friend class WorldspaceWidget;
WorldspaceWidget* mWorldspaceWidget;
Resource::ResourceSystem* mResourceSystem;
NodeMap mMarkerNodes;
osg::ref_ptr<osg::PositionAttitudeTransform> mBaseNode;
osg::ref_ptr<osg::PositionAttitudeTransform> mRootNode;
std::unordered_map<std::string, osg::Vec4f> mOriginalColors;
std::vector<std::string> mSelectionHistory;
std::string mLastHitNode;
std::unordered_map<std::string, osg::Material*> mLastHighlightedNodes;
float mMarkerScale;
int mSubMode;
ObjectMarker(WorldspaceWidget* worldspaceWidget, Resource::ResourceSystem* resourceSystem);
static std::unique_ptr<ObjectMarker> create(WorldspaceWidget* widget, Resource::ResourceSystem* resourceSystem)
{
return std::unique_ptr<ObjectMarker>(new ObjectMarker(widget, resourceSystem));
}
bool attachMarker(const std::string& refId);
void removeFromSelectionHistory(const std::string& refId);
public:
ObjectMarker(ObjectMarker&) = delete;
ObjectMarker(ObjectMarker&&) = delete;
ObjectMarker& operator=(const ObjectMarker&) = delete;
ObjectMarker& operator=(ObjectMarker&&) = delete;
void toggleVisibility();
bool hitBehindMarker(const osg::Vec3d& hitPos, osg::ref_ptr<osg::Camera> camera);
void detachMarker();
void addToSelectionHistory(const std::string& refId, bool update = true);
void updateSelectionMarker(const std::string& refId = std::string());
void resetMarkerHighlight();
void updateMarkerHighlight(const std::string_view hitNode, const int axis);
void setSubMode(const int subMode);
void updateScale(const float scale);
};
}
#endif // OPENCS_VIEW_OBJECT_MARKER_H

View file

@ -86,8 +86,8 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells()
{ {
modified = true; modified = true;
auto cell auto cell = std::make_unique<Cell>(getDocument(), mSelectionMarker.get(), mRootNode,
= std::make_unique<Cell>(mDocument, mRootNode, iter->first.getId(mWorldspace), deleted, true); iter->first.getId(mWorldspace), deleted, true);
delete iter->second; delete iter->second;
iter->second = cell.release(); iter->second = cell.release();
@ -465,7 +465,8 @@ void CSVRender::PagedWorldspaceWidget::addCellToScene(const CSMWorld::CellCoordi
bool deleted = index == -1 || cells.getRecord(index).mState == CSMWorld::RecordBase::State_Deleted; bool deleted = index == -1 || cells.getRecord(index).mState == CSMWorld::RecordBase::State_Deleted;
auto cell = std::make_unique<Cell>(mDocument, mRootNode, coordinates.getId(mWorldspace), deleted, true); auto cell = std::make_unique<Cell>(
getDocument(), mSelectionMarker.get(), mRootNode, coordinates.getId(mWorldspace), deleted, true);
EditMode* editMode = getEditMode(); EditMode* editMode = getEditMode();
cell->setSubMode(editMode->getSubMode(), editMode->getInteractionMask()); cell->setSubMode(editMode->getSubMode(), editMode->getInteractionMask());
@ -750,6 +751,7 @@ void CSVRender::PagedWorldspaceWidget::clearSelection(int elementMask)
iter->second->setSelection(elementMask, Cell::Selection_Clear); iter->second->setSelection(elementMask, Cell::Selection_Clear);
flagAsModified(); flagAsModified();
mSelectionMarker->detachMarker();
} }
void CSVRender::PagedWorldspaceWidget::invertSelection(int elementMask) void CSVRender::PagedWorldspaceWidget::invertSelection(int elementMask)
@ -907,6 +909,7 @@ void CSVRender::PagedWorldspaceWidget::setSubMode(int subMode, unsigned int elem
{ {
for (std::map<CSMWorld::CellCoordinates, Cell*>::const_iterator iter = mCells.begin(); iter != mCells.end(); ++iter) for (std::map<CSMWorld::CellCoordinates, Cell*>::const_iterator iter = mCells.begin(); iter != mCells.end(); ++iter)
iter->second->setSubMode(subMode, elementMask); iter->second->setSubMode(subMode, elementMask);
mSelectionMarker->updateSelectionMarker();
} }
void CSVRender::PagedWorldspaceWidget::reset(unsigned int elementMask) void CSVRender::PagedWorldspaceWidget::reset(unsigned int elementMask)
@ -986,3 +989,12 @@ void CSVRender::PagedWorldspaceWidget::loadSouthCell()
{ {
addCellToSceneFromCamera(0, -1); addCellToSceneFromCamera(0, -1);
} }
CSVRender::Object* CSVRender::PagedWorldspaceWidget::getObjectByReferenceId(const std::string& referenceId)
{
for (const auto& [_, cell] : mCells)
if (const auto& object = cell->getObjectByReferenceId(referenceId))
return object;
return nullptr;
}

View file

@ -174,6 +174,8 @@ namespace CSVRender
/// Erase all overrides and restore the visual representation to its true state. /// Erase all overrides and restore the visual representation to its true state.
void reset(unsigned int elementMask) override; void reset(unsigned int elementMask) override;
CSVRender::Object* getObjectByReferenceId(const std::string& referenceId) override;
protected: protected:
void addVisibilitySelectorButtons(CSVWidget::SceneToolToggle2* tool) override; void addVisibilitySelectorButtons(CSVWidget::SceneToolToggle2* tool) override;

View file

@ -445,6 +445,32 @@ namespace CSVRender
mCurrentCamControl->setup(mRootNode, Mask_Reference | Mask_Terrain, CameraController::WorldUp); mCurrentCamControl->setup(mRootNode, Mask_Reference | Mask_Terrain, CameraController::WorldUp);
mCamPositionSet = true; mCamPositionSet = true;
} }
if (mSelectionMarkerNode)
{
osg::MatrixList worldMats = mSelectionMarkerNode->getWorldMatrices();
if (!worldMats.empty())
{
osg::Matrixd markerWorldMat = worldMats[0];
osg::Vec3f eye, _;
mView->getCamera()->getViewMatrix().getLookAt(eye, _, _);
osg::Vec3f cameraLocalPos = eye * osg::Matrixd::inverse(markerWorldMat);
bool isInFrontRightQuadrant = (cameraLocalPos.x() > 0.1f) && (cameraLocalPos.y() > 0.1f);
bool isSignificantlyBehind = (cameraLocalPos.x() < 1.f) && (cameraLocalPos.y() < 1.f);
if (!isInFrontRightQuadrant && isSignificantlyBehind)
{
osg::Quat current = mSelectionMarkerNode->getAttitude();
mSelectionMarkerNode->setAttitude(current * osg::Quat(osg::PI, osg::Vec3f(0, 0, 1)));
}
float distance = (markerWorldMat.getTrans() - eye).length();
float scale = std::max(distance / 75.0f, 1.0f);
mSelectionMarkerNode->setScale(osg::Vec3(scale, scale, scale));
}
}
} }
void SceneWidget::settingChanged(const CSMPrefs::Setting* setting) void SceneWidget::settingChanged(const CSMPrefs::Setting* setting)

View file

@ -9,6 +9,7 @@
#include <QTimer> #include <QTimer>
#include <QWidget> #include <QWidget>
#include <osg/PositionAttitudeTransform>
#include <osg/Vec4f> #include <osg/Vec4f>
#include <osg/ref_ptr> #include <osg/ref_ptr>
@ -105,6 +106,11 @@ namespace CSVRender
void setExterior(bool isExterior); void setExterior(bool isExterior);
void setSelectionMarkerRoot(osg::ref_ptr<osg::PositionAttitudeTransform> selectionMarker)
{
mSelectionMarkerNode = selectionMarker;
}
protected: protected:
void setLighting(Lighting* lighting); void setLighting(Lighting* lighting);
///< \attention The ownership of \a lighting is not transferred to *this. ///< \attention The ownership of \a lighting is not transferred to *this.
@ -122,6 +128,7 @@ namespace CSVRender
Lighting* mLighting; Lighting* mLighting;
osg::ref_ptr<osg::PositionAttitudeTransform> mSelectionMarkerNode;
osg::ref_ptr<osg::Camera> mGradientCamera; osg::ref_ptr<osg::Camera> mGradientCamera;
osg::Vec4f mDefaultAmbient; osg::Vec4f mDefaultAmbient;
bool mHasDefaultAmbient; bool mHasDefaultAmbient;

View file

@ -79,7 +79,7 @@ CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget(
update(); update();
mCell = std::make_unique<Cell>(document, mRootNode, mCellId); mCell = std::make_unique<Cell>(document, mSelectionMarker.get(), mRootNode, mCellId);
} }
void CSVRender::UnpagedWorldspaceWidget::cellDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) void CSVRender::UnpagedWorldspaceWidget::cellDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight)
@ -127,7 +127,7 @@ bool CSVRender::UnpagedWorldspaceWidget::handleDrop(
mCellId = universalIdData.begin()->getId(); mCellId = universalIdData.begin()->getId();
mCell = std::make_unique<Cell>(getDocument(), mRootNode, mCellId); mCell = std::make_unique<Cell>(getDocument(), mSelectionMarker.get(), mRootNode, mCellId);
mCamPositionSet = false; mCamPositionSet = false;
mOrbitCamControl->reset(); mOrbitCamControl->reset();
@ -141,6 +141,7 @@ void CSVRender::UnpagedWorldspaceWidget::clearSelection(int elementMask)
{ {
mCell->setSelection(elementMask, Cell::Selection_Clear); mCell->setSelection(elementMask, Cell::Selection_Clear);
flagAsModified(); flagAsModified();
mSelectionMarker->detachMarker();
} }
void CSVRender::UnpagedWorldspaceWidget::invertSelection(int elementMask) void CSVRender::UnpagedWorldspaceWidget::invertSelection(int elementMask)
@ -218,6 +219,7 @@ std::vector<osg::ref_ptr<CSVRender::TagBase>> CSVRender::UnpagedWorldspaceWidget
void CSVRender::UnpagedWorldspaceWidget::setSubMode(int subMode, unsigned int elementMask) void CSVRender::UnpagedWorldspaceWidget::setSubMode(int subMode, unsigned int elementMask)
{ {
mCell->setSubMode(subMode, elementMask); mCell->setSubMode(subMode, elementMask);
mSelectionMarker->updateSelectionMarker();
} }
void CSVRender::UnpagedWorldspaceWidget::reset(unsigned int elementMask) void CSVRender::UnpagedWorldspaceWidget::reset(unsigned int elementMask)
@ -383,3 +385,8 @@ CSVRender::WorldspaceWidget::dropRequirments CSVRender::UnpagedWorldspaceWidget:
return ignored; return ignored;
} }
} }
CSVRender::Object* CSVRender::UnpagedWorldspaceWidget::getObjectByReferenceId(const std::string& referenceId)
{
return mCell->getObjectByReferenceId(referenceId);
}

View file

@ -104,6 +104,8 @@ namespace CSVRender
/// Erase all overrides and restore the visual representation to its true state. /// Erase all overrides and restore the visual representation to its true state.
void reset(unsigned int elementMask) override; void reset(unsigned int elementMask) override;
CSVRender::Object* getObjectByReferenceId(const std::string& id) override;
private: private:
void referenceableDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) override; void referenceableDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) override;

View file

@ -52,7 +52,6 @@
#include "cameracontroller.hpp" #include "cameracontroller.hpp"
#include "instancemode.hpp" #include "instancemode.hpp"
#include "mask.hpp" #include "mask.hpp"
#include "object.hpp"
#include "pathgridmode.hpp" #include "pathgridmode.hpp"
CSVRender::WorldspaceWidget::WorldspaceWidget(CSMDoc::Document& document, QWidget* parent) CSVRender::WorldspaceWidget::WorldspaceWidget(CSMDoc::Document& document, QWidget* parent)
@ -74,8 +73,8 @@ CSVRender::WorldspaceWidget::WorldspaceWidget(CSMDoc::Document& document, QWidge
, mToolTipPos(-1, -1) , mToolTipPos(-1, -1)
, mShowToolTips(false) , mShowToolTips(false)
, mToolTipDelay(0) , mToolTipDelay(0)
, mInConstructor(true)
, mSelectedNavigationMode(0) , mSelectedNavigationMode(0)
, mSelectionMarker(ObjectMarker::create(this, document.getData().getResourceSystem().get()))
{ {
setAcceptDrops(true); setAcceptDrops(true);
@ -145,13 +144,14 @@ CSVRender::WorldspaceWidget::WorldspaceWidget(CSMDoc::Document& document, QWidge
&WorldspaceWidget::unhideAll); &WorldspaceWidget::unhideAll);
connect(new CSMPrefs::Shortcut("scene-clear-selection", this), qOverload<>(&CSMPrefs::Shortcut::activated), this, connect(new CSMPrefs::Shortcut("scene-clear-selection", this), qOverload<>(&CSMPrefs::Shortcut::activated), this,
[this] { this->clearSelection(Mask_Reference); }); [this] { clearSelection(Mask_Reference); });
CSMPrefs::Shortcut* switchPerspectiveShortcut = new CSMPrefs::Shortcut("scene-cam-cycle", this); CSMPrefs::Shortcut* switchPerspectiveShortcut = new CSMPrefs::Shortcut("scene-cam-cycle", this);
connect(switchPerspectiveShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, connect(switchPerspectiveShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this,
&WorldspaceWidget::cycleNavigationMode); &WorldspaceWidget::cycleNavigationMode);
mInConstructor = false; connect(new CSMPrefs::Shortcut("scene-toggle-marker", this), qOverload<>(&CSMPrefs::Shortcut::activated), this,
[this]() { mSelectionMarker->toggleVisibility(); });
} }
void CSVRender::WorldspaceWidget::settingChanged(const CSMPrefs::Setting* setting) void CSVRender::WorldspaceWidget::settingChanged(const CSMPrefs::Setting* setting)
@ -162,17 +162,8 @@ void CSVRender::WorldspaceWidget::settingChanged(const CSMPrefs::Setting* settin
mDragWheelFactor = setting->toDouble(); mDragWheelFactor = setting->toDouble();
else if (*setting == "3D Scene Input/drag-shift-factor") else if (*setting == "3D Scene Input/drag-shift-factor")
mDragShiftFactor = setting->toDouble(); mDragShiftFactor = setting->toDouble();
else if (*setting == "Rendering/object-marker-alpha" && !mInConstructor) else if (*setting == "Rendering/object-marker-scale")
{ mSelectionMarker->updateScale(setting->toDouble());
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") else if (*setting == "Tooltips/scene-delay")
mToolTipDelay = setting->toInt(); mToolTipDelay = setting->toInt();
else if (*setting == "Tooltips/scene") else if (*setting == "Tooltips/scene")
@ -396,8 +387,29 @@ CSMDoc::Document& CSVRender::WorldspaceWidget::getDocument()
return mDocument; return mDocument;
} }
CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick( template <typename Tag>
const QPoint& localPos, unsigned int interactionMask) const std::optional<CSVRender::WorldspaceHitResult> CSVRender::WorldspaceWidget::checkTag(
const osgUtil::LineSegmentIntersector::Intersection& intersection) const
{
for (auto* node : intersection.nodePath)
{
if (auto* tag = dynamic_cast<Tag*>(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;
}
}
return std::nullopt;
}
std::tuple<osg::Vec3d, osg::Vec3d, osg::Vec3d> CSVRender::WorldspaceWidget::getStartEndDirection(
int pointX, int pointY) const
{ {
// may be okay to just use devicePixelRatio() directly // may be okay to just use devicePixelRatio() directly
QScreen* screen = SceneWidget::windowHandle() && SceneWidget::windowHandle()->screen() QScreen* screen = SceneWidget::windowHandle() && SceneWidget::windowHandle()->screen()
@ -405,8 +417,8 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick(
: QGuiApplication::primaryScreen(); : QGuiApplication::primaryScreen();
// (0,0) is considered the lower left corner of an OpenGL window // (0,0) is considered the lower left corner of an OpenGL window
int x = localPos.x() * screen->devicePixelRatio(); int x = pointX * screen->devicePixelRatio();
int y = height() * screen->devicePixelRatio() - localPos.y() * screen->devicePixelRatio(); int y = height() * screen->devicePixelRatio() - pointY * screen->devicePixelRatio();
// Convert from screen space to world space // Convert from screen space to world space
osg::Matrixd wpvMat; osg::Matrixd wpvMat;
@ -418,6 +430,13 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick(
osg::Vec3d start = wpvMat.preMult(osg::Vec3d(x, y, 0)); osg::Vec3d start = wpvMat.preMult(osg::Vec3d(x, y, 0));
osg::Vec3d end = wpvMat.preMult(osg::Vec3d(x, y, 1)); osg::Vec3d end = wpvMat.preMult(osg::Vec3d(x, y, 1));
osg::Vec3d direction = end - start; osg::Vec3d direction = end - start;
return { start, end, direction };
}
CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick(
const QPoint& localPos, unsigned int interactionMask) const
{
auto [start, end, direction] = getStartEndDirection(localPos.x(), localPos.y());
// Get intersection // Get intersection
osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector( osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector(
@ -430,51 +449,46 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick(
mView->getCamera()->accept(visitor); mView->getCamera()->accept(visitor);
// Get relevant data auto intersections = intersector->getIntersections();
for (osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin();
it != intersector->getIntersections().end(); ++it)
{
osgUtil::LineSegmentIntersector::Intersection intersection = *it;
// reject back-facing polygons std::vector<osgUtil::LineSegmentIntersector::Intersection> validIntersections
if (direction * intersection.getWorldIntersectNormal() > 0) = { intersections.begin(), intersections.end() };
{
continue;
}
for (std::vector<osg::Node*>::iterator nodeIter = intersection.nodePath.begin(); const auto& removeBackfaces = [direction = direction](const osgUtil::LineSegmentIntersector::Intersection& i) {
nodeIter != intersection.nodePath.end(); ++nodeIter) return direction * i.getWorldIntersectNormal() > 0;
{ };
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 validIntersections.erase(std::remove_if(validIntersections.begin(), validIntersections.end(), removeBackfaces),
WorldspaceHitResult hit = { true, nullptr, 0, 0, 0, intersection.getWorldIntersectPoint() }; validIntersections.end());
if (intersection.indexList.size() >= 3)
{
hit.index0 = intersection.indexList[0];
hit.index1 = intersection.indexList[1];
hit.index2 = intersection.indexList[2];
}
return hit;
}
// Default placement // Default placement
direction.normalize(); direction.normalize();
direction *= CSMPrefs::get()["3D Scene Editing"]["distance"].toInt(); direction *= CSMPrefs::get()["3D Scene Editing"]["distance"].toInt();
WorldspaceHitResult hit = { false, nullptr, 0, 0, 0, start + direction }; if (validIntersections.empty())
return WorldspaceHitResult{ false, nullptr, 0, 0, 0, start + direction };
const auto& firstHit = validIntersections.front();
for (const auto& hit : validIntersections)
if (const auto& markerHit = checkTag<ObjectMarkerTag>(hit))
{
if (mSelectionMarker->hitBehindMarker(markerHit->worldPos, mView->getCamera()))
return WorldspaceHitResult{ false, nullptr, 0, 0, 0, start + direction };
else
return *markerHit;
}
if (auto hit = checkTag<TagBase>(firstHit))
return *hit;
// Something untagged, probably terrain
WorldspaceHitResult hit = { true, nullptr, 0, 0, 0, firstHit.getWorldIntersectPoint() };
if (firstHit.indexList.size() >= 3)
{
hit.index0 = firstHit.indexList[0];
hit.index1 = firstHit.indexList[1];
hit.index2 = firstHit.indexList[2];
}
return hit; return hit;
} }
@ -632,6 +646,41 @@ void CSVRender::WorldspaceWidget::elementSelectionChanged()
void CSVRender::WorldspaceWidget::updateOverlay() {} void CSVRender::WorldspaceWidget::updateOverlay() {}
void CSVRender::WorldspaceWidget::handleMarkerHighlight(const int x, const int y)
{
auto [start, end, _] = getStartEndDirection(x, y);
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(Mask_Reference);
mView->getCamera()->accept(visitor);
bool hitMarker = false;
for (const auto& intersection : intersector->getIntersections())
{
if (mSelectionMarker->hitBehindMarker(intersection.getWorldIntersectPoint(), mView->getCamera()))
continue;
for (const auto& node : intersection.nodePath)
{
if (const auto& marker = dynamic_cast<ObjectMarkerTag*>(node->getUserData()))
{
hitMarker = true;
mSelectionMarker->updateMarkerHighlight(node->getName(), marker->mAxis);
break;
}
}
}
if (!hitMarker)
mSelectionMarker->resetMarkerHighlight();
}
void CSVRender::WorldspaceWidget::mouseMoveEvent(QMouseEvent* event) void CSVRender::WorldspaceWidget::mouseMoveEvent(QMouseEvent* event)
{ {
dynamic_cast<CSVRender::EditMode&>(*mEditMode->getCurrent()).mouseMoveEvent(event); dynamic_cast<CSVRender::EditMode&>(*mEditMode->getCurrent()).mouseMoveEvent(event);
@ -685,6 +734,8 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent(QMouseEvent* event)
} }
} }
const QPointF& pos = event->localPos();
handleMarkerHighlight(pos.x(), pos.y());
SceneWidget::mouseMoveEvent(event); SceneWidget::mouseMoveEvent(event);
} }
} }

View file

@ -13,6 +13,7 @@
#include <apps/opencs/view/render/tagbase.hpp> #include <apps/opencs/view/render/tagbase.hpp>
#include "instancedragmodes.hpp" #include "instancedragmodes.hpp"
#include "objectmarker.hpp"
#include "scenewidget.hpp" #include "scenewidget.hpp"
class QDragEnterEvent; class QDragEnterEvent;
@ -89,7 +90,6 @@ namespace CSVRender
QPoint mToolTipPos; QPoint mToolTipPos;
bool mShowToolTips; bool mShowToolTips;
int mToolTipDelay; int mToolTipDelay;
bool mInConstructor;
int mSelectedNavigationMode; int mSelectedNavigationMode;
public: public:
@ -186,6 +186,12 @@ namespace CSVRender
virtual void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) = 0; virtual void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) = 0;
template <typename Tag>
std::optional<WorldspaceHitResult> checkTag(
const osgUtil::LineSegmentIntersector::Intersection& intersection) const;
std::tuple<osg::Vec3d, osg::Vec3d, osg::Vec3d> getStartEndDirection(int pointX, int pointY) const;
/// Return the next intersection with scene elements matched by /// Return the next intersection with scene elements matched by
/// \a interactionMask based on \a localPos and the camera vector. /// \a interactionMask based on \a localPos and the camera vector.
/// If there is no such intersection, instead a point "in front" of \a localPos will be /// If there is no such intersection, instead a point "in front" of \a localPos will be
@ -216,7 +222,14 @@ namespace CSVRender
EditMode* getEditMode(); EditMode* getEditMode();
virtual CSVRender::Object* getObjectByReferenceId(const std::string& id) = 0;
ObjectMarker* getSelectionMarker() { return mSelectionMarker.get(); }
const ObjectMarker* getSelectionMarker() const { return mSelectionMarker.get(); }
protected: protected:
const std::unique_ptr<CSVRender::ObjectMarker> mSelectionMarker;
/// Visual elements in a scene /// Visual elements in a scene
/// @note do not change the enumeration values, they are used in pre-existing button file names! /// @note do not change the enumeration values, they are used in pre-existing button file names!
enum ButtonId enum ButtonId
@ -252,6 +265,10 @@ namespace CSVRender
void cycleNavigationMode(); void cycleNavigationMode();
private: private:
bool hitBehindMarker(const osg::Vec3d& hitPos) const;
void handleMarkerHighlight(const int x, const int y);
void dragEnterEvent(QDragEnterEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override;
void dropEvent(QDropEvent* event) override; void dropEvent(QDropEvent* event) override;

View file

@ -129,7 +129,7 @@ add_component_dir (vfs
add_component_dir (resource add_component_dir (resource
scenemanager keyframemanager imagemanager animblendrulesmanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem scenemanager keyframemanager imagemanager animblendrulesmanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem
resourcemanager stats animation foreachbulletobject errormarker cachestats bgsmfilemanager resourcemanager stats animation foreachbulletobject errormarker selectionmarker cachestats bgsmfilemanager
) )
add_component_dir (shader add_component_dir (shader

View file

@ -975,6 +975,14 @@ namespace Resource
return loadNonNif(errorMarker, file, mImageManager); return loadNonNif(errorMarker, file, mImageManager);
} }
void SceneManager::loadSelectionMarker(
osg::ref_ptr<osg::Group> parentNode, const char* markerData, long long markerSize) const
{
Files::IMemStream file(markerData, markerSize);
constexpr VFS::Path::NormalizedView selectionMarker("selectionmarker.osgt");
parentNode->addChild(loadNonNif(selectionMarker, file, mImageManager));
}
osg::ref_ptr<osg::Node> SceneManager::cloneErrorMarker() osg::ref_ptr<osg::Node> SceneManager::cloneErrorMarker()
{ {
if (!mErrorMarker) if (!mErrorMarker)

View file

@ -136,6 +136,9 @@ namespace Resource
osg::ref_ptr<osg::Texture> getOpaqueDepthTex(size_t frame); osg::ref_ptr<osg::Texture> getOpaqueDepthTex(size_t frame);
void loadSelectionMarker(
osg::ref_ptr<osg::Group> parentNode, const char* markerData, long long markerSize) const;
enum class UBOBinding enum class UBOBinding
{ {
// If we add more UBO's, we should probably assign their bindings dynamically according to the current count // If we add more UBO's, we should probably assign their bindings dynamically according to the current count

View file

@ -160,4 +160,7 @@
<file alias="brush-circle">brush-circle.svg</file> <file alias="brush-circle">brush-circle.svg</file>
<file alias="brush-custom">brush-custom.svg</file> <file alias="brush-custom">brush-custom.svg</file>
</qresource> </qresource>
<qresource prefix="/render">
<file alias="selection-marker">selectionmarker.osgt</file>
</qresource>
</RCC> </RCC>

File diff suppressed because it is too large Load diff