1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-10-16 15:46:34 +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
previewwidget editmode instancemode instanceselectionmode instancemovemode
orbitcameramode pathgridmode selectionmode pathgridselectionmode cameracontroller
cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw commands
cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw commands objectmarker
)
opencs_units (view/render

View file

@ -180,7 +180,10 @@ void CSMPrefs::State::declare()
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.")
.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");
declareColour(mValues->mRendering.mSceneDayBackgroundColour, "Day Background 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.mSceneRotateSubmode, "Rotate Object Submode");
declareShortcut(mValues->mKeyBindings.mSceneCameraCycle, "Cycle Camera Mode");
declareShortcut(mValues->mKeyBindings.mSceneToggleMarker, "Toggle Selection Marker");
declareSubcategory("1st/Free Camera");
declareShortcut(mValues->mKeyBindings.mFreeForward, "Forward");

View file

@ -258,7 +258,7 @@ namespace CSMPrefs
Settings::SettingValue<int> mCameraFov{ mIndex, sName, "camera-fov", 90 };
Settings::SettingValue<bool> mCameraOrtho{ mIndex, sName, "camera-ortho", false };
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<std::string> mSceneDayBackgroundColour{ mIndex, sName, "scene-day-background-colour",
"#6e7880" };
@ -491,7 +491,7 @@ namespace CSMPrefs
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> 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> mFreeBackward{ mIndex, sName, "free-backward", "S" };
Settings::SettingValue<std::string> mFreeLeft{ mIndex, sName, "free-left", "A" };

View file

@ -25,9 +25,10 @@
#include "cellwater.hpp"
#include "instancedragmodes.hpp"
#include "mask.hpp"
#include "object.hpp"
#include "objectmarker.hpp"
#include "pathgrid.hpp"
#include "terrainstorage.hpp"
#include "worldspacewidget.hpp"
#include <apps/opencs/model/world/cell.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);
if (mSubModeElementMask & Mask_Reference)
object->setSubMode(mSubMode);
mObjects.insert(std::make_pair(id, object.release()));
modified = true;
}
@ -168,9 +166,10 @@ void CSVRender::Cell::unloadLand()
mCellBorder.reset();
}
CSVRender::Cell::Cell(
CSMDoc::Document& document, osg::Group* rootNode, const std::string& id, bool deleted, bool isExterior)
: mData(document.getData())
CSVRender::Cell::Cell(CSMDoc::Document& document, ObjectMarker* selectionMarker, osg::Group* rootNode,
const std::string& id, bool deleted, bool isExterior)
: mSelectionMarker(selectionMarker)
, mData(document.getData())
, mId(ESM::RefId::stringRefId(id))
, mDeleted(deleted)
, mSubMode(0)
@ -466,7 +465,10 @@ void CSVRender::Cell::setSelection(int elementMask, Selection mode)
}
iter->second->setSelected(selected);
if (selected)
mSelectionMarker->addToSelectionHistory(iter->second->getReferenceId(), false);
}
mSelectionMarker->updateSelectionMarker();
}
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())
{
iter->second->setSelected(true);
mSelectionMarker->addToSelectionHistory(iter->second->getReferenceId(), false);
}
}
mSelectionMarker->updateSelectionMarker();
}
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)
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)
@ -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)
@ -555,6 +564,8 @@ void CSVRender::Cell::selectWithinDistance(const osg::Vec3d& point, float distan
if (distanceFromObject < distance)
handleSelectDrag(object.second, dragMode);
}
mSelectionMarker->updateSelectionMarker();
}
void CSVRender::Cell::setCellArrows(int mask)
@ -625,9 +636,11 @@ void CSVRender::Cell::selectFromGroup(const std::vector<std::string>& group)
if (objectName == object->getReferenceId())
{
object->setSelected(true, osg::Vec4f(1, 0, 1, 1));
mSelectionMarker->addToSelectionHistory(object->getReferenceId(), false);
}
}
}
mSelectionMarker->updateSelectionMarker();
}
void CSVRender::Cell::unhideAll()
@ -673,8 +686,7 @@ void CSVRender::Cell::setSubMode(int subMode, unsigned int elementMask)
mSubModeElementMask = elementMask;
if (elementMask & Mask_Reference)
for (std::map<std::string, Object*>::const_iterator iter(mObjects.begin()); iter != mObjects.end(); ++iter)
iter->second->setSubMode(subMode);
mSelectionMarker->setSubMode(subMode);
}
void CSVRender::Cell::reset(unsigned int elementMask)
@ -685,3 +697,11 @@ void CSVRender::Cell::reset(unsigned int elementMask)
if (mPathgrid && elementMask & Mask_Pathgrid)
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/ref_ptr>
#include "../../model/doc/document.hpp"
#include "../../model/world/cellcoordinates.hpp"
#include "instancedragmodes.hpp"
#include "worldspacewidget.hpp"
#include <components/esm/refid.hpp>
#include <components/misc/algorithm.hpp>
@ -44,8 +44,11 @@ namespace CSVRender
class CellBorder;
class CellMarker;
class ObjectMarker;
class Cell
{
ObjectMarker* const mSelectionMarker;
CSMWorld::Data& mData;
ESM::RefId mId;
osg::ref_ptr<osg::Group> mCellNode;
@ -90,8 +93,8 @@ namespace CSVRender
public:
/// \note Deleted covers both cells that are deleted and cells that don't exist in
/// the first place.
Cell(CSMDoc::Document& document, osg::Group* rootNode, const std::string& id, bool deleted = false,
bool isExterior = false);
Cell(CSMDoc::Document& document, ObjectMarker* selectionMarker, osg::Group* rootNode, const std::string& id,
bool deleted = false, bool isExterior = false);
~Cell();
@ -182,6 +185,8 @@ namespace CSVRender
/// true state.
void reset(unsigned int elementMask);
CSVRender::Object* getObjectByReferenceId(const std::string& referenceId);
friend class CellNodeCallback;
};
}

View file

@ -362,7 +362,29 @@ CSVRender::InstanceMode::InstanceMode(
for (const char axis : "xyz")
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)
@ -460,52 +482,58 @@ void CSVRender::InstanceMode::secondaryEditPressed(const WorldspaceHitResult& hi
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;
object->setSelected(true);
return;
}
// hit an Object, select it
CSVRender::Object* object = objectTag->mObject;
object->setSelected(true);
worldspaceWidget.getSelectionMarker()->addToSelectionHistory(object->getReferenceId());
}
}
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;
object->setSelected(!object->getSelected());
return;
}
// hit an Object, toggle its selection state
CSVRender::Object* object = objectTag->mObject;
object->setSelected(!object->getSelected());
const auto selectionMarker = getWorldspaceWidget().getSelectionMarker();
if (object->getSelected())
selectionMarker->addToSelectionHistory(object->getReferenceId(), false);
selectionMarker->updateSelectionMarker();
}
}
void CSVRender::InstanceMode::tertiarySelectPressed(const WorldspaceHitResult& hit)
{
auto* snapTarget = dynamic_cast<CSVRender::ObjectTag*>(getWorldspaceWidget().getSnapTarget(Mask_Reference).get());
if (snapTarget)
if (auto* snapTarget
= dynamic_cast<CSVRender::ObjectTag*>(getWorldspaceWidget().getSnapTarget(Mask_Reference).get()))
{
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;
object->setSnapTarget(!object->getSnapTarget());
return;
}
// hit an Object, toggle its selection state
CSVRender::Object* object = objectTag->mObject;
object->setSnapTarget(!object->getSnapTarget());
}
}
@ -514,23 +542,26 @@ bool CSVRender::InstanceMode::primaryEditStartDrag(const QPoint& pos)
if (mDragMode != DragMode_None || mLocked)
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())
{
// 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())
{
getWorldspaceWidget().clearSelection(Mask_Reference);
worldspaceWidget.clearSelection(Mask_Reference);
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{
CSVRender::Object* object = objectTag->mObject;
object->setSelected(true);
worldspaceWidget.getSelectionMarker()->addToSelectionHistory(object->getReferenceId());
}
}
selection = getWorldspaceWidget().getSelection(Mask_Reference);
selection = worldspaceWidget.getSelection(Mask_Reference);
if (selection.empty())
return false;
}
@ -591,23 +622,26 @@ bool CSVRender::InstanceMode::secondaryEditStartDrag(const QPoint& pos)
if (mDragMode != DragMode_None || mLocked)
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())
{
// 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())
{
getWorldspaceWidget().clearSelection(Mask_Reference);
worldspaceWidget.clearSelection(Mask_Reference);
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{
CSVRender::Object* object = objectTag->mObject;
object->setSelected(true);
worldspaceWidget.getSelectionMarker()->addToSelectionHistory(object->getReferenceId());
}
}
selection = getWorldspaceWidget().getSelection(Mask_Reference);
selection = worldspaceWidget.getSelection(Mask_Reference);
if (selection.empty())
return false;
}
@ -641,10 +675,10 @@ bool CSVRender::InstanceMode::secondaryEditStartDrag(const QPoint& pos)
mDragMode = DragMode_Scale_Snap;
// 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));
int widgetHeight = getWorldspaceWidget().height();
int widgetHeight = worldspaceWidget.height();
float dx = pos.x() - center.x();
float dy = (widgetHeight - pos.y()) - center.y();

View file

@ -18,25 +18,11 @@
#include <apps/opencs/model/world/universalid.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/Shape>
#include <osg/ShapeDrawable>
#include <osg/StateAttribute>
#include <osg/StateSet>
#include <osg/Vec3>
#include <osgFX/Scribe>
#include "../../model/prefs/state.hpp"
#include "../../model/world/cellcoordinates.hpp"
#include "../../model/world/commandmacro.hpp"
#include "../../model/world/commands.hpp"
@ -63,11 +49,6 @@ namespace ESM
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
{
@ -95,12 +76,6 @@ QString CSVRender::ObjectTag::getToolTip(bool /*hideBasics*/, const WorldspaceHi
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::update()
@ -204,238 +179,6 @@ const CSMWorld::CellRef& CSVRender::Object::getReference() const
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(
CSMWorld::Data& data, osg::Group* parentNode, const std::string& id, bool referenceable, bool forceBaseToZero)
: mData(data)
@ -446,8 +189,6 @@ CSVRender::Object::Object(
, mForceBaseToZero(forceBaseToZero)
, mScaleOverride(1)
, mOverrideFlags(0)
, mSubMode(-1)
, mMarkerTransparency(0.5f)
{
mRootNode = new osg::PositionAttitudeTransform;
@ -476,7 +217,6 @@ CSVRender::Object::Object(
adjustTransform();
update();
updateMarker();
}
CSVRender::Object::~Object()
@ -506,9 +246,6 @@ void CSVRender::Object::setSelected(bool selected, const osg::Vec4f& color)
}
else
mRootNode->addChild(mBaseNode);
mMarkerTransparency = CSMPrefs::get()["Rendering"]["object-marker-alpha"].toDouble();
updateMarker();
}
bool CSVRender::Object::getSelected() const
@ -536,9 +273,6 @@ void CSVRender::Object::setSnapTarget(bool isSnapTarget)
}
else
mRootNode->addChild(mBaseNode);
mMarkerTransparency = CSMPrefs::get()["Rendering"]["object-marker-alpha"].toDouble();
updateMarker();
}
bool CSVRender::Object::getSnapTarget() const
@ -566,7 +300,6 @@ bool CSVRender::Object::referenceableDataChanged(const QModelIndex& topLeft, con
{
adjustTransform();
update();
updateMarker();
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());
update();
updateMarker();
}
return true;
@ -626,7 +358,6 @@ bool CSVRender::Object::referenceDataChanged(const QModelIndex& topLeft, const Q
void CSVRender::Object::reloadAssets()
{
update();
updateMarker();
}
std::string CSVRender::Object::getReferenceId() const
@ -720,12 +451,6 @@ void CSVRender::Object::setScale(float scale)
adjustTransform();
}
void CSVRender::Object::setMarkerTransparency(float value)
{
mMarkerTransparency = value;
updateMarker();
}
void CSVRender::Object::apply(CSMWorld::CommandMacro& commands)
{
const CSMWorld::RefCollection& collection = mData.getReferences();
@ -796,18 +521,8 @@ void CSVRender::Object::apply(CSMWorld::CommandMacro& commands)
mOverrideFlags = 0;
}
void CSVRender::Object::setSubMode(int subMode)
{
if (subMode != mSubMode)
{
mSubMode = subMode;
updateMarker();
}
}
void CSVRender::Object::reset()
{
mOverrideFlags = 0;
adjustTransform();
updateMarker();
}

View file

@ -58,14 +58,6 @@ namespace CSVRender
QString getToolTip(bool hideBasics, const WorldspaceHitResult& hit) const override;
};
class ObjectMarkerTag : public ObjectTag
{
public:
ObjectMarkerTag(Object* object, int axis);
int mAxis;
};
class Object
{
public:
@ -76,12 +68,22 @@ namespace CSVRender
Override_Scale = 4
};
private:
static const float MarkerShaftWidth;
static const float MarkerShaftBaseLength;
static const float MarkerHeadWidth;
static const float MarkerHeadLength;
enum SubMode
{
Mode_Move,
Mode_Rotate,
Mode_Scale,
Mode_None,
};
enum Axis
{
Axis_X,
Axis_Y,
Axis_Z
};
private:
CSMWorld::Data& mData;
ESM::RefId mReferenceId;
ESM::RefId mReferenceableId;
@ -96,9 +98,6 @@ namespace CSVRender
ESM::Position mPositionOverride;
float mScaleOverride;
int mOverrideFlags;
osg::ref_ptr<osg::Node> mMarker[3];
int mSubMode;
float mMarkerTransparency;
std::unique_ptr<Actor> mActor;
/// Not implemented
@ -120,16 +119,6 @@ namespace CSVRender
/// Throws an exception if *this was constructed with referenceable
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:
Object(CSMWorld::Data& data, osg::Group* cellNode, const std::string& id, bool referenceable,
bool forceBaseToZero = false);
@ -199,8 +188,6 @@ namespace CSVRender
/// Apply override changes via command and end edit mode
void apply(CSMWorld::CommandMacro& commands);
void setSubMode(int subMode);
/// Erase all overrides and restore the visual representation of the object to its
/// true state.
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;
auto cell
= std::make_unique<Cell>(mDocument, mRootNode, iter->first.getId(mWorldspace), deleted, true);
auto cell = std::make_unique<Cell>(getDocument(), mSelectionMarker.get(), mRootNode,
iter->first.getId(mWorldspace), deleted, true);
delete iter->second;
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;
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();
cell->setSubMode(editMode->getSubMode(), editMode->getInteractionMask());
@ -750,6 +751,7 @@ void CSVRender::PagedWorldspaceWidget::clearSelection(int elementMask)
iter->second->setSelection(elementMask, Cell::Selection_Clear);
flagAsModified();
mSelectionMarker->detachMarker();
}
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)
iter->second->setSubMode(subMode, elementMask);
mSelectionMarker->updateSelectionMarker();
}
void CSVRender::PagedWorldspaceWidget::reset(unsigned int elementMask)
@ -986,3 +989,12 @@ void CSVRender::PagedWorldspaceWidget::loadSouthCell()
{
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.
void reset(unsigned int elementMask) override;
CSVRender::Object* getObjectByReferenceId(const std::string& referenceId) override;
protected:
void addVisibilitySelectorButtons(CSVWidget::SceneToolToggle2* tool) override;

View file

@ -445,6 +445,32 @@ namespace CSVRender
mCurrentCamControl->setup(mRootNode, Mask_Reference | Mask_Terrain, CameraController::WorldUp);
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)

View file

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

View file

@ -79,7 +79,7 @@ CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget(
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)
@ -127,7 +127,7 @@ bool CSVRender::UnpagedWorldspaceWidget::handleDrop(
mCellId = universalIdData.begin()->getId();
mCell = std::make_unique<Cell>(getDocument(), mRootNode, mCellId);
mCell = std::make_unique<Cell>(getDocument(), mSelectionMarker.get(), mRootNode, mCellId);
mCamPositionSet = false;
mOrbitCamControl->reset();
@ -141,6 +141,7 @@ void CSVRender::UnpagedWorldspaceWidget::clearSelection(int elementMask)
{
mCell->setSelection(elementMask, Cell::Selection_Clear);
flagAsModified();
mSelectionMarker->detachMarker();
}
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)
{
mCell->setSubMode(subMode, elementMask);
mSelectionMarker->updateSelectionMarker();
}
void CSVRender::UnpagedWorldspaceWidget::reset(unsigned int elementMask)
@ -383,3 +385,8 @@ CSVRender::WorldspaceWidget::dropRequirments CSVRender::UnpagedWorldspaceWidget:
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.
void reset(unsigned int elementMask) override;
CSVRender::Object* getObjectByReferenceId(const std::string& id) override;
private:
void referenceableDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) override;

View file

@ -52,7 +52,6 @@
#include "cameracontroller.hpp"
#include "instancemode.hpp"
#include "mask.hpp"
#include "object.hpp"
#include "pathgridmode.hpp"
CSVRender::WorldspaceWidget::WorldspaceWidget(CSMDoc::Document& document, QWidget* parent)
@ -74,8 +73,8 @@ CSVRender::WorldspaceWidget::WorldspaceWidget(CSMDoc::Document& document, QWidge
, mToolTipPos(-1, -1)
, mShowToolTips(false)
, mToolTipDelay(0)
, mInConstructor(true)
, mSelectedNavigationMode(0)
, mSelectionMarker(ObjectMarker::create(this, document.getData().getResourceSystem().get()))
{
setAcceptDrops(true);
@ -145,13 +144,14 @@ CSVRender::WorldspaceWidget::WorldspaceWidget(CSMDoc::Document& document, QWidge
&WorldspaceWidget::unhideAll);
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);
connect(switchPerspectiveShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this,
&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)
@ -162,17 +162,8 @@ void CSVRender::WorldspaceWidget::settingChanged(const CSMPrefs::Setting* settin
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 == "Rendering/object-marker-scale")
mSelectionMarker->updateScale(setting->toDouble());
else if (*setting == "Tooltips/scene-delay")
mToolTipDelay = setting->toInt();
else if (*setting == "Tooltips/scene")
@ -396,8 +387,29 @@ CSMDoc::Document& CSVRender::WorldspaceWidget::getDocument()
return mDocument;
}
CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick(
const QPoint& localPos, unsigned int interactionMask) const
template <typename Tag>
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
QScreen* screen = SceneWidget::windowHandle() && SceneWidget::windowHandle()->screen()
@ -405,8 +417,8 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick(
: 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();
int x = pointX * screen->devicePixelRatio();
int y = height() * screen->devicePixelRatio() - pointY * screen->devicePixelRatio();
// Convert from screen space to world space
osg::Matrixd wpvMat;
@ -418,6 +430,13 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick(
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;
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
osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector(
@ -430,51 +449,46 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick(
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;
auto intersections = intersector->getIntersections();
// reject back-facing polygons
if (direction * intersection.getWorldIntersectNormal() > 0)
{
continue;
}
std::vector<osgUtil::LineSegmentIntersector::Intersection> validIntersections
= { intersections.begin(), intersections.end() };
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;
}
}
const auto& removeBackfaces = [direction = direction](const osgUtil::LineSegmentIntersector::Intersection& i) {
return direction * i.getWorldIntersectNormal() > 0;
};
// 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;
}
validIntersections.erase(std::remove_if(validIntersections.begin(), validIntersections.end(), removeBackfaces),
validIntersections.end());
// Default placement
direction.normalize();
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;
}
@ -632,6 +646,41 @@ void CSVRender::WorldspaceWidget::elementSelectionChanged()
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)
{
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);
}
}

View file

@ -13,6 +13,7 @@
#include <apps/opencs/view/render/tagbase.hpp>
#include "instancedragmodes.hpp"
#include "objectmarker.hpp"
#include "scenewidget.hpp"
class QDragEnterEvent;
@ -89,7 +90,6 @@ namespace CSVRender
QPoint mToolTipPos;
bool mShowToolTips;
int mToolTipDelay;
bool mInConstructor;
int mSelectedNavigationMode;
public:
@ -186,6 +186,12 @@ namespace CSVRender
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
/// \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
@ -216,7 +222,14 @@ namespace CSVRender
EditMode* getEditMode();
virtual CSVRender::Object* getObjectByReferenceId(const std::string& id) = 0;
ObjectMarker* getSelectionMarker() { return mSelectionMarker.get(); }
const ObjectMarker* getSelectionMarker() const { return mSelectionMarker.get(); }
protected:
const std::unique_ptr<CSVRender::ObjectMarker> mSelectionMarker;
/// Visual elements in a scene
/// @note do not change the enumeration values, they are used in pre-existing button file names!
enum ButtonId
@ -252,6 +265,10 @@ namespace CSVRender
void cycleNavigationMode();
private:
bool hitBehindMarker(const osg::Vec3d& hitPos) const;
void handleMarkerHighlight(const int x, const int y);
void dragEnterEvent(QDragEnterEvent* event) override;
void dropEvent(QDropEvent* event) override;

View file

@ -129,7 +129,7 @@ add_component_dir (vfs
add_component_dir (resource
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

View file

@ -975,6 +975,14 @@ namespace Resource
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()
{
if (!mErrorMarker)

View file

@ -136,6 +136,9 @@ namespace Resource
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
{
// 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-custom">brush-custom.svg</file>
</qresource>
<qresource prefix="/render">
<file alias="selection-marker">selectionmarker.osgt</file>
</qresource>
</RCC>

File diff suppressed because it is too large Load diff