+ // For basic decision logic see class documentation.
+
+ /*
+ Detail logic:
+
+ 1. Find the SliceNavigationController that has sent the event: this one defines our rendering plane and will NOT be rotated. Must not even be counted or checked..
+ 2. Inspect every other SliceNavigationController
+ - calculate the line intersection of this SliceNavigationController's plane with our rendering plane
+ - if there is no interesection, ignore and continue
+ - IF there is an intersection
+ - check the mouse cursor's distance from that line.
+ 0. if the line is NOT near the cursor, remember the plane as "one of the other planes" (which can be rotated in "locked" mode)
+ 1. on first line near the cursor, just remember this intersection line as THE other plane that we want to rotate
+ 2. on every consecutive line near the cursor, check if the line is geometrically identical to the line that we want to rotate
+ - if yes, we just push this line to the "other" lines and rotate it along
+ - if no, then we have a situation where the mouse is near two other lines (e.g. crossing point) and don't want to rotate
+ // move all planes to posEvent->GetWorldPosition()
+ decidedEvent = new StateEvent(EIDNO, e->GetEvent());
+ MITK_DEBUG << "Rotation not possible, not enough information (other planes crossing rendering plane) ";
+ }
+ else
+ { // we DO have enough information for rotation
+ m_LastCursorPosition = intersectionLineWithGeometryToBeRotated.Project(cursorPosition); // remember where the last cursor position ON THE LINE has been observed
+ if (anyOtherGeometry->IntersectionPoint(intersectionLineWithGeometryToBeRotated, m_CenterOfRotation)) // find center of rotation by intersection with any of the OTHER lines
+ {
+ decidedEvent = new StateEvent(EIDYES, e->GetEvent());
+ MITK_DEBUG << "Rotation possible";
+ }
+ else
+ {
+ MITK_DEBUG << "Rotation not possible, cannot determine the center of rotation!?";
+ decidedEvent = new StateEvent(EIDNO, e->GetEvent());
- * This class takes care of several SliceNavigationControllers and handles
- * slice selection / slice rotation. It is added as listener to
- * GlobalInteraction by QmitkStdMultiWidget.
- *
- * The SlicesRotator class adds the possibility of slice rotation to the
- * "normal" behaviour of SliceNavigationControllers. This additional class
- * is needed, because one has to be aware of several "visible slices"
- * (selected Geometry2Ds of some SliceNavigationControllers) in order to
- * choose between rotation and slice selection.
- *
- * Rotation is achieved by modifying (rotating) the generated
- * TimeSlicedGeometry of the corresponding SliceNavigationController.
- *
- * With SlicesRotator, the rule to choose between slice rotation and
- * selection is simple: For a mouse down event, count the number of visible
- * planes, which are "near" the cursor. If this number equals 2 (one for the
- * window, which currently holds the cursor, one for the intersection line of
- * another visible slice), then initiate rotation, else select slices near
- * the cursor. If "LinkPlanes" is set to true, the rotation is applied to the
- * planes of all registered SNCs, not only of the one associated with the
- * directly selected plane.
- *
- * In contrast to the situation without the SlicesRotator, the
- * SliceNavigationControllers are now not directly registered as listeners to
- * GlobalInteraction. SlicesRotator is registered as a listener and decides
- * whether something should be rotated or whether another slice should be
- * selected. In the latter case, a PositionEvent is just forwarded to the
- * SliceNavigationController.
- *
- * \sa SlicesSwiveller
+ \brief Coordinates rotation of multiple visible rendering planes (represented as lines in other render windows).
+ \ingroup NavigationControl
+
+ This class takes care of several SliceNavigationControllers and handles slice selection
+ / slice rotation. It is added as listener to GlobalInteraction by QmitkStdMultiWidget.
+
+ The SlicesRotator class adds the possibility of slice rotation to the "normal" behaviour
+ of SliceNavigationControllers (which is picking one plane from a stack of planes).
+
+ This additional class SlicesRotator is needed, because one has to be aware of multiple
+ "visible slices" (selected Geometry2Ds of some SliceNavigationControllers) in order to
+ choose between rotation and slice selection. Such functionality could not be implemented
+ by a single SliceNavigationController.
+
+ Rotation is achieved by modifying (rotating) the generated TimeSlicedGeometry of the
+ corresponding SliceNavigationControllers.
+
+ \section mitkSlicesRotator_StandardCase The standard case: three orthogonal views (MPR)
+
+ With SlicesRotator, the rule to choose between slice rotation and selection is simple:
+ For a mouse down event, count the number of visible planes, which are "near" the cursor.
+ If this number is 2 (one for the window, which currently holds the cursor, one for the
+ intersection line of another visible slice), then initiate rotation, else select slices
+ near the cursor. If the "LinkPlanes" flag is set, the rotation is applied to the planes
+ of all registered SNCs, not only of the one associated with the directly selected plane.
+
+ In contrast to the situation without the SlicesRotator, the SliceNavigationControllers
+ are now NOT directly registered as listeners to GlobalInteraction. SlicesRotator is
+ registered as a listener and decides whether something should be rotated or whether
+ another slice should be selected. In the latter case, a PositionEvent is just forwarded
+ to the SliceNavigationController.
+
+ \section mitkSlicesRotator_GeneralizedCase The generalized case: any number of views
+
+ Above section as well as the original implementation of this class assumes that we have
+ exactly three 2D vies in our scene. This used to be the standard setup of the MITK
+ associated application for a long time. With custom applications based on MITK it is
+ easy to create different situations. One usual use case would be to have one extra
+ render window display the contents of any of the other ones and behave exactly like it
+ (could e.g. be used on a second screen).
+
+ In this situation the above assumption "we rotate when there are exactly 2 slices close
+ to the cursor" will not hold: since we always have two render windows displaying the
+ exact same slice, the number of 2 is the minimum we get. Whenever the user clicks in one
+ of those windows and the cursor is close to one of the orthogonal planes, we will get a
+ count of 3 or more planes that are "close to the cursor".
+
+ For the class to behave correctly, we actually need to distinguish three separate cases:
+ 1. the cursor is not close to any orthogonal planes. This should result in slice selection.
+ 2. the cursor is close to just one orthogonal plane OR multiple which are not distinguishable visually. This should result in rotation.
+ 3. the cursor is close to multiple orthogonal planes which are rendered as distinguishable lines on the render window. This is the case when we hit the crosshair-center of the view. In this case, we need to also just select slices.
+
+ \section mitkSlicesRotator_Solution Deciding between slice selection and rotation
+
+ The "counting nearby lines in the renderwindow" can also work for the general case
+ described above. Only one details needs to be accounted for: we must not count a line
+ when it is identical to another line. I.e. we just count how many visible lines on the
+ screen are very close to the cursor. When this number is 1, we rotate, otherwise we let
+ the SliceNavigationControllers do their slice selection job.
+
+ \sa SlicesSwiveller
*/
class MITK_CORE_EXPORT SlicesRotator : public SlicesCoordinator