Something went wrong. Try again.
OpenMW is an open implementation of the Morrowind engine
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312#include <unordered_set>
#include <QFile>
#include <osg/ClipPlane>#include <osg/PositionAttitudeTransform>#include <osgUtil/CullVisitor>
#include <components/resource/resourcesystem.hpp>#include <components/resource/scenemanager.hpp>#include <components/sceneutil/material.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()); mResourceSystem->getSceneManager()->recreateShaders(mBaseNode);
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(); SceneUtil::Material* mat = static_cast<SceneUtil::Material*>(state->getAttribute(osg::StateAttribute::MATERIAL)); osg::Vec4f emis = mat->getEmission(); mat->setEmission(emis / 4); mat->updateStateSet(state); 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 (unsigned index = mRootNode->getNumParents(); index > 0;) mRootNode->getParent(--index)->removeChild(mRootNode);
osg::ref_ptr<osg::Group> widgetRoot = mMarkerNodes["unitArrows"]->asGroup(); for (unsigned 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(mat->getEmission() / 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);
SceneUtil::Material* mat = static_cast<SceneUtil::Material*>(matAttr); mat->setEmission(mOriginalColors[materialNodeName]); mat->updateStateSet(state);
mLastHighlightedNodes.emplace(std::make_pair(matNode->getName(), mat)); }
mLastHitNode = hitNode; }}