diff --git a/include/iselection.h b/include/iselection.h index 82e1bff632..696aa5fa04 100644 --- a/include/iselection.h +++ b/include/iselection.h @@ -159,6 +159,9 @@ class SelectionSystem : virtual void unregisterManipulator(const selection::ManipulatorPtr& manipulator) = 0; virtual selection::Manipulator::Type getActiveManipulatorType() = 0; + + // Returns the currently active Manipulator, which is always non-null + virtual const selection::ManipulatorPtr& getActiveManipulator() = 0; virtual void setActiveManipulator(std::size_t manipulatorId) = 0; virtual void setActiveManipulator(selection::Manipulator::Type manipulatorType) = 0; @@ -257,8 +260,14 @@ class SelectionSystem : virtual void rotateSelected(const Quaternion& rotation) = 0; virtual void scaleSelected(const Vector3& scaling) = 0; + virtual const Matrix4& getPivot2World() const = 0; virtual void pivotChanged() const = 0; + // Feedback events invoked by the ManipulationMouseTool + virtual void onManipulationStart() = 0; + virtual void onManipulationChanged() = 0; + virtual void onManipulationEnd() = 0; + virtual bool SelectManipulator(const render::View& view, const Vector2& devicePoint, const Vector2& deviceEpsilon) = 0; virtual void SelectPoint(const render::View& view, const Vector2& devicePoint, const Vector2& deviceEpsilon, EModifier modifier, bool face) = 0; virtual void SelectArea(const render::View& view, const Vector2& devicePoint, const Vector2& deviceDelta, EModifier modifier, bool face) = 0; diff --git a/radiant/selection/ManipulateMouseTool.cpp b/radiant/selection/ManipulateMouseTool.cpp index 528c725755..8d6ee27bab 100644 --- a/radiant/selection/ManipulateMouseTool.cpp +++ b/radiant/selection/ManipulateMouseTool.cpp @@ -1,8 +1,14 @@ #include "ManipulateMouseTool.h" #include "i18n.h" +#include "iundo.h" +#include "iscenegraph.h" #include "registry/registry.h" #include "Device.h" +#include "Rectangle.h" +#include "Pivot2World.h" +#include "SelectionTest.h" +#include "SceneWalkers.h" namespace ui { @@ -36,7 +42,7 @@ ManipulateMouseTool::Result ManipulateMouseTool::onMouseDown(Event& ev) Vector2 epsilon(_selectEpsilon / ev.getInteractiveView().getDeviceWidth(), _selectEpsilon / ev.getInteractiveView().getDeviceHeight()); - if (_selectionSystem.SelectManipulator(_view, ev.getDevicePosition(), epsilon)) + if (selectManipulator(_view, ev.getDevicePosition(), epsilon)) { return Result::Activated; } @@ -49,7 +55,7 @@ ManipulateMouseTool::Result ManipulateMouseTool::onMouseMove(Event& ev) // Get the view afresh each time, chasemouse might have changed the view since onMouseDown _view = render::View(ev.getInteractiveView().getVolumeTest()); - _selectionSystem.MoveSelected(_view, ev.getDevicePosition()); + handleMouseMove(_view, ev.getDevicePosition()); return Result::Continued; } @@ -57,20 +63,18 @@ ManipulateMouseTool::Result ManipulateMouseTool::onMouseMove(Event& ev) ManipulateMouseTool::Result ManipulateMouseTool::onMouseUp(Event& ev) { // Notify the selectionsystem about the finished operation - _selectionSystem.endMove(); + endMove(); return Result::Finished; } void ManipulateMouseTool::onMouseCaptureLost(IInteractiveView& view) { - // Update the views - _selectionSystem.cancelMove(); + cancelMove(); } ManipulateMouseTool::Result ManipulateMouseTool::onCancel(IInteractiveView&) { - // Update the views - _selectionSystem.cancelMove(); + cancelMove(); return Result::Finished; } @@ -85,4 +89,229 @@ unsigned int ManipulateMouseTool::getRefreshMode() return RefreshMode::Force | RefreshMode::AllViews; // update cam view too } +bool ManipulateMouseTool::selectManipulator(const render::View& view, const Vector2& devicePoint, const Vector2& deviceEpsilon) +{ + const selection::ManipulatorPtr& activeManipulator = _selectionSystem.getActiveManipulator(); + + assert(activeManipulator); + + bool dragComponentMode = activeManipulator->getType() == selection::Manipulator::Drag && _selectionSystem.Mode() == SelectionSystem::eComponent; + + if (!nothingSelected() || dragComponentMode) + { + // Unselect any currently selected manipulators to be sure + activeManipulator->setSelected(false); + + const Matrix4& pivot2World = _selectionSystem.getPivot2World(); + + // Test, if the current manipulator can be selected + if (!nothingSelected() || dragComponentMode) + { + render::View scissored(view); + ConstructSelectionTest(scissored, selection::Rectangle::ConstructFromPoint(devicePoint, deviceEpsilon)); + + // The manipulator class checks on its own, if any of its components can be selected + activeManipulator->testSelect(scissored, pivot2World); + } + + // Save the pivot2world matrix + _pivot2worldStart = pivot2World; + + _selectionSystem.onManipulationStart(); + + // This is true, if a manipulator could be selected + _manipulationActive = activeManipulator->isSelected(); + + // is a manipulator selected / the pivot moving? + if (_manipulationActive) + { + selection::Pivot2World pivot; + pivot.update(pivot2World, view.GetModelview(), view.GetProjection(), view.GetViewport()); + + _manip2pivotStart = _pivot2worldStart.getFullInverse().getMultipliedBy(pivot._worldSpace); + + Matrix4 device2manip; + ConstructDevice2Manip(device2manip, _pivot2worldStart, view.GetModelview(), view.GetProjection(), view.GetViewport()); + activeManipulator->getActiveComponent()->Construct(device2manip, devicePoint.x(), devicePoint.y()); + + _deviceStart = devicePoint; + + _undoBegun = false; + } + + //SceneChangeNotify(); + } + + return _manipulationActive; +} + +void ManipulateMouseTool::handleMouseMove(const render::View& view, const Vector2& devicePoint) +{ + const selection::ManipulatorPtr& activeManipulator = _selectionSystem.getActiveManipulator(); + assert(activeManipulator); + + // Check if the active manipulator is selected in the first place + if (!activeManipulator->isSelected()) return; + + // Initalise the undo system, if not yet done + if (!_undoBegun) + { + _undoBegun = true; + GlobalUndoSystem().start(); + } + + Matrix4 device2manip; + ConstructDevice2Manip(device2manip, _pivot2worldStart, view.GetModelview(), view.GetProjection(), view.GetViewport()); + + Vector2 constrainedDevicePoint(devicePoint); + + // Constrain the movement to the axes, if the modifier is held + if (wxGetKeyState(WXK_SHIFT)) + { + // Get the movement delta relative to the start point + Vector2 delta = devicePoint - _deviceStart; + + // Set the "minor" value of the movement to zero + if (fabs(delta[0]) > fabs(delta[1])) + { + // X axis is major, reset the y-value to the start + delta[1] = 0; + } + else + { + // Y axis is major, reset the x-value to the start + delta[0] = 0; + } + + // Add the modified delta to the start point, constrained to one axis + constrainedDevicePoint = _deviceStart + delta; + } + + // Get the manipulatable from the currently active manipulator (done by selection test) + // and call the Transform method (can be anything) + activeManipulator->getActiveComponent()->Transform(_manip2pivotStart, device2manip, constrainedDevicePoint.x(), constrainedDevicePoint.y()); + + _selectionSystem.onManipulationChanged(); +} + +void ManipulateMouseTool::freezeTransforms() +{ + GlobalSceneGraph().foreachNode(scene::freezeTransformableNode); + + _selectionSystem.onManipulationEnd(); +} + +void ManipulateMouseTool::endMove() +{ + freezeTransforms(); + + const selection::ManipulatorPtr& activeManipulator = _selectionSystem.getActiveManipulator(); + assert(activeManipulator); + + // greebo: Deselect all faces if we are in brush and drag mode + if ((_selectionSystem.Mode() == SelectionSystem::ePrimitive || _selectionSystem.Mode() == SelectionSystem::eGroupPart) && + activeManipulator->getType() == selection::Manipulator::Drag) + { + SelectAllComponentWalker faceSelector(false, SelectionSystem::eFace); + GlobalSceneGraph().root()->traverse(faceSelector); + } + + // Remove all degenerated brushes from the scene graph (should emit a warning) + _selectionSystem.foreachSelected(RemoveDegenerateBrushWalker()); + + _manipulationActive = false; + _selectionSystem.pivotChanged(); + + // Update the views + SceneChangeNotify(); + + // If we started an undoable operation, end it now and tell the console what happened + if (_undoBegun) + { + std::ostringstream command; + + if (activeManipulator->getType() == selection::Manipulator::Translate) + { + command << "translateTool"; + //outputTranslation(command); + } + else if (activeManipulator->getType() == selection::Manipulator::Rotate) + { + command << "rotateTool"; + //outputRotation(command); + } + else if (activeManipulator->getType() == selection::Manipulator::Scale) + { + command << "scaleTool"; + //outputScale(command); + } + else if (activeManipulator->getType() == selection::Manipulator::Drag) + { + command << "dragTool"; + } + + _undoBegun = false; + + // Finish the undo move + GlobalUndoSystem().finish(command.str()); + } +} + +void ManipulateMouseTool::cancelMove() +{ + const selection::ManipulatorPtr& activeManipulator = _selectionSystem.getActiveManipulator(); + assert(activeManipulator); + + // Unselect any currently selected manipulators to be sure + activeManipulator->setSelected(false); + + // Tell all the scene objects to revert their transformations + _selectionSystem.foreachSelected([](const scene::INodePtr& node) + { + ITransformablePtr transform = Node_getTransformable(node); + + if (transform) + { + transform->revertTransform(); + } + }); + + _manipulationActive = false; + _selectionSystem.pivotChanged(); + + // greebo: Deselect all faces if we are in brush and drag mode + if (_selectionSystem.Mode() == SelectionSystem::ePrimitive && activeManipulator->getType() == selection::Manipulator::Drag) + { + SelectAllComponentWalker faceSelector(false, SelectionSystem::eFace); + GlobalSceneGraph().root()->traverse(faceSelector); + } + + if (_undoBegun) + { + // Cancel the undo operation, if one has been begun + GlobalUndoSystem().cancel(); + _undoBegun = false; + } + + // Update the views + SceneChangeNotify(); +} + +bool ManipulateMouseTool::nothingSelected() const +{ + switch (_selectionSystem.Mode()) + { + case SelectionSystem::eComponent: + return _selectionSystem.countSelectedComponents() == 0; + + case SelectionSystem::eGroupPart: + case SelectionSystem::ePrimitive: + case SelectionSystem::eEntity: + return _selectionSystem.countSelected() == 0; + + default: + return false; + }; +} + } diff --git a/radiant/selection/ManipulateMouseTool.h b/radiant/selection/ManipulateMouseTool.h index 7611b15a3f..e3125368e8 100644 --- a/radiant/selection/ManipulateMouseTool.h +++ b/radiant/selection/ManipulateMouseTool.h @@ -2,6 +2,7 @@ #include "imousetool.h" #include "render/View.h" +#include "math/Vector2.h" class SelectionSystem; @@ -23,6 +24,14 @@ class ManipulateMouseTool : SelectionSystem& _selectionSystem; + Matrix4 _pivot2worldStart; + bool _manipulationActive; + + Matrix4 _manip2pivotStart; + + Vector2 _deviceStart; + bool _undoBegun; + public: ManipulateMouseTool(SelectionSystem& selectionSystem); @@ -38,6 +47,14 @@ class ManipulateMouseTool : virtual unsigned int getPointerMode() override; virtual unsigned int getRefreshMode() override; + +private: + bool selectManipulator(const render::View& view, const Vector2& devicePoint, const Vector2& deviceEpsilon); + void handleMouseMove(const render::View& view, const Vector2& devicePoint); + void freezeTransforms(); + void endMove(); + void cancelMove(); + bool nothingSelected() const; }; } diff --git a/radiant/selection/RadiantSelectionSystem.cpp b/radiant/selection/RadiantSelectionSystem.cpp index b510101ef2..0da59bc09c 100644 --- a/radiant/selection/RadiantSelectionSystem.cpp +++ b/radiant/selection/RadiantSelectionSystem.cpp @@ -266,6 +266,11 @@ Manipulator::Type RadiantSelectionSystem::getActiveManipulatorType() return _activeManipulator->getType(); } +const ManipulatorPtr& RadiantSelectionSystem::getActiveManipulator() +{ + return _activeManipulator; +} + void RadiantSelectionSystem::setActiveManipulator(std::size_t manipulatorId) { Manipulators::const_iterator found = _manipulators.find(manipulatorId); @@ -519,7 +524,7 @@ void RadiantSelectionSystem::foreachPatch(const std::function& fun // Start a move, the current pivot point is saved as a start point void RadiantSelectionSystem::startMove() { - _pivot2worldStart = GetPivot2World(); + _pivot2worldStart = getPivot2World(); } /* greebo: This is called by the ManipulateObserver class on the mouseDown event. It checks, if a manipulator @@ -539,7 +544,7 @@ bool RadiantSelectionSystem::SelectManipulator(const render::View& view, const V ConstructSelectionTest(scissored, Rectangle::ConstructFromPoint(device_point, device_epsilon)); // The manipulator class checks on its own, if any of its components can be selected - _activeManipulator->testSelect(scissored, GetPivot2World()); + _activeManipulator->testSelect(scissored, getPivot2World()); } // Save the pivot2world matrix @@ -552,7 +557,7 @@ bool RadiantSelectionSystem::SelectManipulator(const render::View& view, const V if (_pivotMoving) { Pivot2World pivot; - pivot.update(GetPivot2World(), view.GetModelview(), view.GetProjection(), view.GetViewport()); + pivot.update(getPivot2World(), view.GetModelview(), view.GetProjection(), view.GetViewport()); _manip2pivotStart = _pivot2worldStart.getFullInverse().getMultipliedBy(pivot._worldSpace); @@ -868,6 +873,33 @@ void RadiantSelectionSystem::outputScale(std::ostream& ostream) { ostream << " -scale " << _scale.x() << " " << _scale.y() << " " << _scale.z(); } +void RadiantSelectionSystem::onManipulationStart() +{ + _pivotMoving = true; + _pivot2worldStart = getPivot2World(); +} + +void RadiantSelectionSystem::onManipulationChanged() +{ + _requestWorkZoneRecalculation = true; + _requestSceneGraphChange = false; + + GlobalSceneGraph().sceneChanged(); + + requestIdleCallback(); +} + +void RadiantSelectionSystem::onManipulationEnd() +{ + _pivotMoving = false; + + // The selection bounds have possibly changed, request an idle callback + _requestWorkZoneRecalculation = true; + _requestSceneGraphChange = true; + + requestIdleCallback(); +} + // Shortcut call for an instantly applied rotation of the current selection void RadiantSelectionSystem::rotateSelected(const Quaternion& rotation) { // Apply the transformation and freeze the changes @@ -949,7 +981,7 @@ void RadiantSelectionSystem::renderWireframe(RenderableCollector& collector, con } // Lets the ConstructPivot() method do the work and returns the result that is stored in the member variable -const Matrix4& RadiantSelectionSystem::GetPivot2World() const +const Matrix4& RadiantSelectionSystem::getPivot2World() const { // Questionable const design - almost everything needs to be declared const here... const_cast(this)->ConstructPivot(); @@ -1155,7 +1187,7 @@ void RadiantSelectionSystem::renderSolid(RenderableCollector& collector, const V collector.SetState(_state, RenderableCollector::eWireframeOnly); collector.SetState(_state, RenderableCollector::eFullMaterials); - _activeManipulator->render(collector, volume, GetPivot2World()); + _activeManipulator->render(collector, volume, getPivot2World()); } } diff --git a/radiant/selection/RadiantSelectionSystem.h b/radiant/selection/RadiantSelectionSystem.h index a7ddcb409e..0139eb659c 100644 --- a/radiant/selection/RadiantSelectionSystem.h +++ b/radiant/selection/RadiantSelectionSystem.h @@ -119,9 +119,10 @@ class RadiantSelectionSystem : std::size_t registerManipulator(const ManipulatorPtr& manipulator); void unregisterManipulator(const ManipulatorPtr& manipulator); - Manipulator::Type getActiveManipulatorType(); - void setActiveManipulator(std::size_t manipulatorId); - void setActiveManipulator(Manipulator::Type manipulatorType); + Manipulator::Type getActiveManipulatorType() override; + const ManipulatorPtr& getActiveManipulator() override; + void setActiveManipulator(std::size_t manipulatorId) override; + void setActiveManipulator(Manipulator::Type manipulatorType) override; std::size_t countSelected() const; std::size_t countSelectedComponents() const; @@ -168,6 +169,10 @@ class RadiantSelectionSystem : void outputRotation(std::ostream& ostream); void outputScale(std::ostream& ostream); + void onManipulationStart() override; + void onManipulationChanged() override; + void onManipulationEnd() override; + void rotateSelected(const Quaternion& rotation); void translateSelected(const Vector3& translation); void scaleSelected(const Vector3& scaling); @@ -192,7 +197,7 @@ class RadiantSelectionSystem : return Highlight::NoHighlight; // never highlighted } - const Matrix4& GetPivot2World() const; + const Matrix4& getPivot2World() const override; static void constructStatic(); static void destroyStatic();