mirror of
https://github.com/OpenMW/openmw.git
synced 2025-07-27 21:44:07 +00:00
307 lines
10 KiB
C++
307 lines
10 KiB
C++
#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;
|
|
}
|
|
}
|