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:
commit
a12a0916ed
23 changed files with 31523 additions and 431 deletions
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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" };
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
307
apps/opencs/view/render/objectmarker.cpp
Normal file
307
apps/opencs/view/render/objectmarker.cpp
Normal 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;
|
||||
}
|
||||
}
|
77
apps/opencs/view/render/objectmarker.hpp
Normal file
77
apps/opencs/view/render/objectmarker.hpp
Normal 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
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
30805
files/opencs/selectionmarker.osgt
Normal file
30805
files/opencs/selectionmarker.osgt
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue