diff --git a/Core/Code/Controllers/mitkSlicesCoordinator.cpp b/Core/Code/Controllers/mitkSlicesCoordinator.cpp index 056075e..e7aceb3 100644 --- a/Core/Code/Controllers/mitkSlicesCoordinator.cpp +++ b/Core/Code/Controllers/mitkSlicesCoordinator.cpp @@ -92,10 +92,10 @@ void SlicesCoordinator::OnSliceControllerRemoved(SliceNavigationController*) // implement in subclasses } -bool SlicesCoordinator::ExecuteAction(Action*, StateEvent const*) +bool SlicesCoordinator::ExecuteAction(Action* a, StateEvent const* e) { - // implement in subclasses - return false; + // implement in subclasses, BUT call default implementation to make these CONNECT_ACTION statements work out + return Superclass::ExecuteAction(a,e); } } // namespace diff --git a/Core/Code/Controllers/mitkSlicesRotator.cpp b/Core/Code/Controllers/mitkSlicesRotator.cpp index c2a7336..e923b2e 100644 --- a/Core/Code/Controllers/mitkSlicesRotator.cpp +++ b/Core/Code/Controllers/mitkSlicesRotator.cpp @@ -51,7 +51,12 @@ SlicesRotator::Pointer SlicesRotator::New() SlicesRotator::SlicesRotator(const char* machine) : SlicesCoordinator(machine) { - + // make sure that AcSWITCHON and AcSWITCHOFF are defined int constants somewhere (e.g. mitkInteractionConst.h) + CONNECT_ACTION( AcMOVE, DoSelectSlice ); + CONNECT_ACTION( AcCHECKPOINT, DoDecideBetweenRotationAndSliceSelection ); + CONNECT_ACTION( AcROTATESTART, DoStartRotation ); + CONNECT_ACTION( AcROTATE, DoRotationStep ); + CONNECT_ACTION( AcROTATEEND, DoEndRotation ); } SlicesRotator::~SlicesRotator() @@ -60,7 +65,6 @@ SlicesRotator::~SlicesRotator() } -// check if the slices of this SliceNavigationController can be rotated (???) Possible void SlicesRotator::OnSliceControllerAdded(SliceNavigationController* snc) { if (!snc) return; @@ -72,7 +76,7 @@ void SlicesRotator::OnSliceControllerAdded(SliceNavigationController* snc) void SlicesRotator::OnSliceControllerRemoved(SliceNavigationController* snc) { if (!snc) return; - // nothing to do + // nothing to do, base class does the bookkeeping } /// Is called whenever a SliceNavigationController invokes an event. Will update the list @@ -81,7 +85,7 @@ void SlicesRotator::SetGeometry(const itk::EventObject& /*EventObject*/) { // there is no way to determine the sender? // ==> update whole list of SNCs - UpdateRelevantSNCs(); + UpdateRotatableSNCs(); } @@ -92,7 +96,7 @@ void SlicesRotator::RotateToPoint( SliceNavigationController *rotationPlaneSNC, SliceNavigationController *thirdSNC = NULL; SNCVector::iterator iter; - for ( iter = m_RelevantSNCs.begin(); iter != m_RelevantSNCs.end(); ++iter ) + for ( iter = m_RotatableSNCs.begin(); iter != m_RotatableSNCs.end(); ++iter ) { if ( ((*iter) != rotationPlaneSNC) && ((*iter) != rotatedPlaneSNC) ) @@ -196,7 +200,7 @@ void SlicesRotator::RotateToPoint( SliceNavigationController *rotationPlaneSNC, else { SNCVector::iterator iter; - for ( iter = m_RelevantSNCs.begin(); iter != m_RelevantSNCs.end(); ++iter ) + for ( iter = m_RotatableSNCs.begin(); iter != m_RotatableSNCs.end(); ++iter ) { BaseRenderer *renderer = (*iter)->GetRenderer(); if ( renderer == NULL ) @@ -231,13 +235,13 @@ void SlicesRotator::RotateToPoint( SliceNavigationController *rotationPlaneSNC, (*iter)->SendCreatedWorldGeometryUpdate(); } } -} +} // end RotateToPoint /// Updates the list of SliceNavigationControllers that can handle rotation -void SlicesRotator::UpdateRelevantSNCs() +void SlicesRotator::UpdateRotatableSNCs() { - m_RelevantSNCs.clear(); + m_RotatableSNCs.clear(); for (SNCVector::iterator iter = m_SliceNavigationControllers.begin(); iter != m_SliceNavigationControllers.end(); ++iter) { @@ -248,333 +252,262 @@ void SlicesRotator::UpdateRelevantSNCs() const SlicedGeometry3D* slicedGeometry = dynamic_cast( timeSlicedGeometry->GetGeometry3D(0) ); if (!slicedGeometry) continue; - Geometry2D* firstSlice(NULL); - //Geometry2D* secondSlice(NULL); - if (slicedGeometry->IsValidSlice(0)) firstSlice = slicedGeometry->GetGeometry2D(0); - //if (slicedGeometry->IsValidSlice(1)) secondSlice = slicedGeometry->GetGeometry2D(1); - - // if the direction vector of these two slices is the same, then accept this slice stack as rotatable - Vector3D right1 = firstSlice->GetAxisVector(0); - Vector3D up1 = firstSlice->GetAxisVector(1); - vnl_vector_fixed< ScalarType, 3 > vnlDirection1 = vnl_cross_3d(right1.GetVnlVector(), up1.GetVnlVector()); - Vector3D direction1; - direction1.SetVnlVector(vnlDirection1); - - Vector3D right2 = firstSlice->GetAxisVector(0); - Vector3D up2 = firstSlice->GetAxisVector(1); - vnl_vector_fixed< ScalarType, 3 > vnlDirection2 = vnl_cross_3d(right2.GetVnlVector(), up2.GetVnlVector()); - Vector3D direction2; - direction2.SetVnlVector(vnlDirection2); - - bool equal = true; - const ScalarType eps = 0.0001; - for (int i = 0; i < 3; ++i) - if ( fabs(direction1[i] - direction2[i]) > eps ) - equal = false; - - if (equal) // equal direction vectors + if (slicedGeometry->IsValidSlice(0)) { - m_RelevantSNCs.push_back( *iter ); + // there were some lines of additional checks here in previous versions, + // all of which would always evaluate to true, so the check was irrelevant. + // Since the original intent was not documented, I removed all checks, + // i.e. m_RotatableSNCs ends up being a list of all the registered + // SliceNavigationControllers which have a SlicedGeometry3D with at least one slice, + // which covers most standard cases. + m_RotatableSNCs.push_back( *iter ); } } } -bool SlicesRotator::ExecuteAction(Action* action, StateEvent const* stateEvent) +bool SlicesRotator::DoSelectSlice(Action* a, const StateEvent* e) { - const ScalarType ThreshHoldDistancePixels = 12.0; - - bool ok = false; - - switch ( action->GetActionId() ) + // just reach through + for (SNCVector::iterator iter = m_RotatableSNCs.begin(); iter != m_RotatableSNCs.end(); ++iter) { - case AcMOVE: + if ( !(*iter)->GetSliceLocked() ) { - // just reach through - for (SNCVector::iterator iter = m_RelevantSNCs.begin(); iter != m_RelevantSNCs.end(); ++iter) - { - if ( !(*iter)->GetSliceLocked() ) - { - (*iter)->ExecuteAction(action, stateEvent); - } - } - - ok = true; - break; + (*iter)->ExecuteAction(a,e); } - case AcROTATE: - { - const DisplayPositionEvent* posEvent = dynamic_cast(stateEvent->GetEvent()); - if (!posEvent) break; - - Point3D cursor = posEvent->GetWorldPosition(); - - Vector3D toProjected = m_LastCursorPosition - m_CenterOfRotation; - Vector3D toCursor = cursor - m_CenterOfRotation; - - // cross product: | A x B | = |A| * |B| * sin(angle) - Vector3D axisOfRotation; - vnl_vector_fixed< ScalarType, 3 > vnlDirection = vnl_cross_3d( toCursor.GetVnlVector(), toProjected.GetVnlVector() ); - axisOfRotation.SetVnlVector(vnlDirection); - - // scalar product: A * B = |A| * |B| * cos(angle) - // tan = sin / cos - ScalarType angle = - atan2( (double)(axisOfRotation.GetNorm()), (double)(toCursor * toProjected) ); - angle *= 180.0 / vnl_math::pi; - m_LastCursorPosition = cursor; + } - // create RotationOperation and apply to all SNCs that should be rotated - RotationOperation op(OpROTATE, m_CenterOfRotation, axisOfRotation, angle); + return true; +} - // TEST - int i = 0; +bool SlicesRotator::DoDecideBetweenRotationAndSliceSelection(Action*, const StateEvent* e) +{ + // Decide between moving and rotation slices. + // 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 + */ + const DisplayPositionEvent* posEvent = dynamic_cast(e->GetEvent()); + if (!posEvent) return false; + + BaseRenderer* clickedRenderer = e->GetEvent()->GetSender(); + const PlaneGeometry* ourViewportGeometry = dynamic_cast( clickedRenderer->GetCurrentWorldGeometry2D() ); + if (!ourViewportGeometry) return false; - for (SNCVector::iterator iter = m_SNCsToBeRotated.begin(); iter != m_SNCsToBeRotated.end(); ++iter) - { - BaseRenderer *renderer = (*iter)->GetRenderer(); - if ( renderer == NULL ) - { - continue; - } + DisplayGeometry* clickedDisplayGeometry = clickedRenderer->GetDisplayGeometry(); + if (!clickedDisplayGeometry) return false; + + MITK_DEBUG << "============================================="; + MITK_DEBUG << "Renderer under cursor is " << clickedRenderer->GetName(); + + Point3D cursorPosition = posEvent->GetWorldPosition(); + const PlaneGeometry* geometryToBeRotated = NULL; // this one is under the mouse cursor + const PlaneGeometry* anyOtherGeometry = NULL; // this is also visible (for calculation of intersection ONLY) + Line3D intersectionLineWithGeometryToBeRotated; - DisplayGeometry *displayGeometry = renderer->GetDisplayGeometry(); + bool hitMultipleLines(false); + m_SNCsToBeRotated.clear(); - // MITK_INFO << i << ":" << std::endl; + const double threshholdDistancePixels = 12.0; + + for (SNCVector::iterator iter = m_RotatableSNCs.begin(); iter != m_RotatableSNCs.end(); ++iter) + { + const PlaneGeometry* otherRenderersRenderPlane = (*iter)->GetCurrentPlaneGeometry(); + if (otherRenderersRenderPlane == NULL) continue; // ignore, we don't see a plane + MITK_DEBUG << " Checking plane of renderer " << (*iter)->GetRenderer()->GetName(); + + // check if there is an intersection + Line3D intersectionLine; // between rendered/clicked geometry and the one being analyzed + if (!ourViewportGeometry->IntersectionLine( otherRenderersRenderPlane, intersectionLine )) + { + continue; // we ignore this plane, it's parallel to our plane + } - Point2D point2DWorld, point2DDisplayPre, point2DDisplayPost; - displayGeometry->Map( m_CenterOfRotation, point2DWorld ); - displayGeometry->WorldToDisplay( point2DWorld, point2DDisplayPre ); + // check distance from intersection line + double distanceFromIntersectionLine = intersectionLine.Distance( cursorPosition ); + ScalarType distancePixels = distanceFromIntersectionLine / clickedDisplayGeometry->GetScaleFactorMMPerDisplayUnit(); + MITK_DEBUG << " Distance of plane from cursor " << distanceFromIntersectionLine << " mm, which is around " << distancePixels << " px" ; - // MITK_INFO << " WorldPre: " << point2DWorld << " / DisplayPre: " << point2DDisplayPre << std::endl; + // far away line, only remember for linked rotation if necessary + if (distanceFromIntersectionLine > threshholdDistancePixels) + { + MITK_DEBUG << " Plane is too far away --> remember as otherRenderersRenderPlane"; + anyOtherGeometry = otherRenderersRenderPlane; // we just take the last one, so overwrite each iteration (we just need some crossing point) + // TODO what about multiple crossings? NOW we have undefined behavior / random crossing point is used - const Geometry3D* geometry3D = (*iter)->GetCreatedWorldGeometry(); - const TimeSlicedGeometry* timeSlicedGeometry = dynamic_cast(geometry3D); - if (!timeSlicedGeometry) continue; - - const_cast(timeSlicedGeometry)->ExecuteOperation(&op); + if (m_LinkPlanes) + { + m_SNCsToBeRotated.push_back(*iter); + } + } + else // close to cursor + { + MITK_DEBUG << " Plane is close enough to cursor..."; + if ( geometryToBeRotated == NULL ) // first one close to the cursor + { + MITK_DEBUG << " It is the first close enough geometry, remember as geometryToBeRotated"; + geometryToBeRotated = otherRenderersRenderPlane; + intersectionLineWithGeometryToBeRotated = intersectionLine; + m_SNCsToBeRotated.push_back(*iter); + } + else + { + MITK_DEBUG << " Second or later close enough geometry"; + // compare to the line defined by geometryToBeRotated: if identical, just rotate this otherRenderersRenderPlane together with the primary one + // if different, DON'T rotate - //vtkLinearTransform *inverseTransformVtk = - // displayGeometry->GetVtkTransform()->GetLinearInverse(); + if ( intersectionLine.IsParallel( intersectionLineWithGeometryToBeRotated ) + && intersectionLine.Distance( intersectionLineWithGeometryToBeRotated.GetPoint1() ) < mitk::eps ) + { + MITK_DEBUG << " This line is the same as intersectionLineWithGeometryToBeRotated which we already know"; + m_SNCsToBeRotated.push_back(*iter); + } + else + { + MITK_DEBUG << " This line is NOT the same as intersectionLineWithGeometryToBeRotated which we already know"; + hitMultipleLines = true; + } + } + } - //ScalarType pvtkCenterOfRotation[3]; - //pvtkCenterOfRotation[0] = m_CenterOfRotation[0]; - //pvtkCenterOfRotation[1] = m_CenterOfRotation[1]; - //pvtkCenterOfRotation[2] = m_CenterOfRotation[2]; + } - //ScalarType scaleFactorMMPerUnitX = - // displayGeometry->GetExtentInMM(0) / displayGeometry->GetExtent(0); - //ScalarType scaleFactorMMPerUnitY = - // displayGeometry->GetExtentInMM(1) / displayGeometry->GetExtent(1); + bool moveSlices(true); - //ScalarType scaleFactorMMPerDisplayUnit = displayGeometry->GetScaleFactorMMPerDisplayUnit(); - //Vector2D &originInMM = displayGeometry->GetOriginInMM(); + if ( geometryToBeRotated && anyOtherGeometry && ourViewportGeometry && !hitMultipleLines ) + { + // assure all three are valid, so calculation of center of rotation can be done + moveSlices = false; + } - ////displayGeometry->Map( m_CenterOfRotation, point2DWorld ); + MITK_DEBUG << "geometryToBeRotated: " << (void*)geometryToBeRotated; + MITK_DEBUG << "anyOtherGeometry: " << (void*)anyOtherGeometry; + MITK_DEBUG << "ourViewportGeometry: " << (void*)ourViewportGeometry; + MITK_DEBUG << "hitMultipleLines? " << hitMultipleLines; + MITK_DEBUG << "moveSlices? " << moveSlices; - //ScalarType pvtkDisplayPost[3]; - //inverseTransformVtk->TransformPoint( pvtkCenterOfRotation, pvtkDisplayPost ); - //pvtkDisplayPost[0] *= scaleFactorMMPerUnitX; - //pvtkDisplayPost[1] *= scaleFactorMMPerUnitY; + StateEvent* decidedEvent(NULL); - ////displayGeometry->WorldToDisplay( point2DWorld, point2DDisplayPost ); - //pvtkDisplayPost[0] -= originInMM[0]; - //pvtkDisplayPost[1] -= originInMM[1]; + // question in state machine is: "rotate?" + if (moveSlices) // i.e. NOT 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 - //pvtkDisplayPost[0] /= scaleFactorMMPerDisplayUnit; - //pvtkDisplayPost[1] /= scaleFactorMMPerDisplayUnit; + 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()); + } + } - //point2DDisplayPost[0] = pvtkDisplayPost[0]; - //point2DDisplayPost[1] = pvtkDisplayPost[1]; + this->HandleEvent( decidedEvent ); + delete decidedEvent; + return true; +} - displayGeometry->Map( m_CenterOfRotation, point2DWorld ); - displayGeometry->WorldToDisplay( point2DWorld, point2DDisplayPost ); - Vector2D vector2DDisplayDiff = point2DDisplayPost - point2DDisplayPre; +bool SlicesRotator::DoStartRotation(Action*, const StateEvent*) +{ + this->SetMouseCursor( rotate_cursor_xpm, 0, 0 ); + this->InvokeEvent( SliceRotationEvent() ); // notify listeners + return true; +} - //Vector2D origin = displayGeometry->GetOriginInMM(); - // MITK_INFO << " WorldPost: " << point2DWorld << " / DisplayPost: " << point2DDisplayPost << std::endl; - // MITK_INFO << " Diff - " << vector2DDisplayDiff << std::endl; - // MITK_INFO << " Origin - " << origin << std::endl; - ++i; +bool SlicesRotator::DoEndRotation(Action*, const StateEvent*) +{ + this->ResetMouseCursor(); + this->InvokeEvent( SliceRotationEvent() ); // notify listeners + return true; +} - displayGeometry->MoveBy( vector2DDisplayDiff ); - (*iter)->SendCreatedWorldGeometryUpdate(); - } - // MITK_INFO << "--------------------------------" << std::endl; +bool SlicesRotator::DoRotationStep(Action*, const StateEvent* e) +{ + const DisplayPositionEvent* posEvent = dynamic_cast(e->GetEvent()); + if (!posEvent) return false; - - - // TEST - //BaseRenderer* renderer = stateEvent->GetEvent()->GetSender(); // TODO this is NOT SNC-specific! Should be! - // - //DisplayGeometry* displayGeometry = renderer->GetDisplayGeometry(); - //if (!displayGeometry) break; + Point3D cursor = posEvent->GetWorldPosition(); - //Point2D point2DWorld, point2DDisplay; - //displayGeometry->Map( m_CenterOfRotation, point2DWorld ); - //displayGeometry->WorldToDisplay( point2DWorld, point2DDisplay ); + Vector3D toProjected = m_LastCursorPosition - m_CenterOfRotation; + Vector3D toCursor = cursor - m_CenterOfRotation; - //MITK_INFO << "RotationCenter: " << m_CenterOfRotation << std::endl; - //MITK_INFO << "PointWorld: " << point2DWorld << std::endl; - //MITK_INFO << "PointDisplay: " << point2DDisplay << std::endl; - //MITK_INFO << "--------------------------------------------" << std::endl; + // cross product: | A x B | = |A| * |B| * sin(angle) + Vector3D axisOfRotation; + vnl_vector_fixed< ScalarType, 3 > vnlDirection = vnl_cross_3d( toCursor.GetVnlVector(), toProjected.GetVnlVector() ); + axisOfRotation.SetVnlVector(vnlDirection); + // scalar product: A * B = |A| * |B| * cos(angle) + // tan = sin / cos + ScalarType angle = - atan2( (double)(axisOfRotation.GetNorm()), (double)(toCursor * toProjected) ); + angle *= 180.0 / vnl_math::pi; + m_LastCursorPosition = cursor; + // create RotationOperation and apply to all SNCs that should be rotated + RotationOperation rotationOperation(OpROTATE, m_CenterOfRotation, axisOfRotation, angle); - RenderingManager::GetInstance()->RequestUpdateAll(); - - this->InvokeEvent( SliceRotationEvent() ); // notify listeners - - ok = true; - break; - } - case AcCHECKPOINT: - { - // decide between moving and rotation - - // Alle SNCs (Anzahl N) nach dem Abstand von posEvent->GetWorldPosition() zur aktuellen Ebene fragen. - // Anzahl der Ebenen zaehlen, die naeher als ein gewisser Schwellwertwert sind -> nNah. - // Wenn nNah == N - // Generiere ein PointEvent und schicke das an alle SNCs -> bewege den kreuz-mittelpunkt - // Wenn nNah == 2 - // Streiche stateEvent->Sender aus der Liste der nahen Ebenen - // fuer die uebrigen generiere eine RotationOperation und fuehre die aus - // sonst - // - const DisplayPositionEvent* posEvent = dynamic_cast(stateEvent->GetEvent()); - if (!posEvent) break; - - Point3D cursor = posEvent->GetWorldPosition(); - //m_LastCursorPosition = cursor; - - unsigned int numNearPlanes = 0; - m_SNCsToBeRotated.clear(); - Geometry2D* geometryToBeRotated = NULL; // this one is grabbed - Geometry2D* otherGeometry = NULL; // this is also visible (for calculation of intersection) - Geometry2D* clickedGeometry = NULL; // the event originates from this one - //SlicedGeometry3D* clickedSlicedGeometry; - - for (SNCVector::iterator iter = m_RelevantSNCs.begin(); iter != m_RelevantSNCs.end(); ++iter) - { - unsigned int slice = (*iter)->GetSlice()->GetPos(); - unsigned int time = (*iter)->GetTime()->GetPos(); - - const Geometry3D* geometry3D = (*iter)->GetCreatedWorldGeometry(); - const TimeSlicedGeometry* timeSlicedGeometry = dynamic_cast( geometry3D ); - if (!timeSlicedGeometry) continue; + // iterate the OTHER slice navigation controllers: these are filled in DoDecideBetweenRotationAndSliceSelection + for (SNCVector::iterator iter = m_SNCsToBeRotated.begin(); iter != m_SNCsToBeRotated.end(); ++iter) + { + // - remember the center of rotation on the 2D display BEFORE rotation + // - execute rotation + // - calculate new center of rotation on 2D display + // - move display IF the center of rotation has moved slightly before and after rotation - const SlicedGeometry3D* slicedGeometry = dynamic_cast( timeSlicedGeometry->GetGeometry3D(time) ); - if (!slicedGeometry) continue; + // DM 2012-10: this must probably be due to rounding errors only, right? + // We don't have documentation on if/why this code is needed + BaseRenderer *renderer = (*iter)->GetRenderer(); + if ( !renderer ) continue; - Geometry2D* geometry2D = slicedGeometry->GetGeometry2D(slice); - if (!geometry2D) continue; // this is not necessary? + DisplayGeometry *displayGeometry = renderer->GetDisplayGeometry(); - ScalarType distanceMM = geometry2D->Distance( cursor ); + Point2D rotationCenter2DWorld, point2DDisplayPreRotation, point2DDisplayPostRotation; + displayGeometry->Map( m_CenterOfRotation, rotationCenter2DWorld ); + displayGeometry->WorldToDisplay( rotationCenter2DWorld, point2DDisplayPreRotation ); - BaseRenderer* renderer = stateEvent->GetEvent()->GetSender(); // TODO this is NOT SNC-specific! Should be! - - DisplayGeometry* displayGeometry = renderer->GetDisplayGeometry(); - if (!displayGeometry) continue; - - ScalarType distancePixels = distanceMM / displayGeometry->GetScaleFactorMMPerDisplayUnit(); - if ( distancePixels <= ThreshHoldDistancePixels ) - { - ++numNearPlanes; // count this one as a plane near to the cursor - } - - if ( *iter == renderer->GetSliceNavigationController() ) // don't rotate the one where the user clicked - { - clickedGeometry = geometry2D; - //clickedSlicedGeometry = const_cast(slicedGeometry); - } - else - { - // @TODO here waits some bug to be found - maybe fixed by the || m_LinkPlanes in next line - if ( (distancePixels <= ThreshHoldDistancePixels) - && !(*iter)->GetSliceRotationLocked() - && (m_SNCsToBeRotated.empty() || m_LinkPlanes) ) - { - // this one is behind the clicked "line" - m_SNCsToBeRotated.push_back(*iter); - geometryToBeRotated = geometry2D; - } - else - { - otherGeometry = geometry2D; - - if ( m_LinkPlanes ) - { - // All slices are rotated, i.e. the relative angles between - // slices remain fixed - m_SNCsToBeRotated.push_back(*iter); - } - } - } - } + const Geometry3D* geometry3D = (*iter)->GetCreatedWorldGeometry(); + const TimeSlicedGeometry* timeSlicedGeometry = dynamic_cast(geometry3D); + if (!timeSlicedGeometry) continue; - bool move (true); + const_cast(timeSlicedGeometry)->ExecuteOperation(&rotationOperation); - if ( geometryToBeRotated && otherGeometry && clickedGeometry - && ( numNearPlanes == 2 ) ) - { - // assure all three are valid, so calculation of center of rotation can be done - move = false; - } - - StateEvent *newStateEvent(NULL); + displayGeometry->Map( m_CenterOfRotation, rotationCenter2DWorld ); + displayGeometry->WorldToDisplay( rotationCenter2DWorld, point2DDisplayPostRotation ); + Vector2D vector2DDisplayDiff = point2DDisplayPostRotation - point2DDisplayPreRotation; - // question in state machine is: "rotate?" - if (move) - { - // move all planes to posEvent->GetWorldPosition() - newStateEvent = new StateEvent(EIDNO, stateEvent->GetEvent()); - } - else - { - // determine center of rotation TODO requires two plane geometries... - PlaneGeometry* planeGeometry = dynamic_cast(clickedGeometry); - PlaneGeometry* planeGeometry1 = dynamic_cast(geometryToBeRotated); - PlaneGeometry* planeGeometry2 = dynamic_cast(otherGeometry); - - if (!planeGeometry || !planeGeometry1 || !planeGeometry2) break; - - Line3D intersection; - if (!planeGeometry->IntersectionLine( planeGeometry1, intersection )) break; - m_LastCursorPosition = intersection.Project(cursor); - if (!planeGeometry2->IntersectionPoint(intersection, m_CenterOfRotation)) break; - // everything's fine - newStateEvent = new StateEvent(EIDYES, stateEvent->GetEvent()); + displayGeometry->MoveBy( vector2DDisplayDiff ); - } + (*iter)->SendCreatedWorldGeometryUpdate(); + } - if (!newStateEvent) MITK_ERROR << "rotation would be nice but is impossible... " << std::endl; - - this->HandleEvent( newStateEvent ); - delete newStateEvent; + RenderingManager::GetInstance()->RequestUpdateAll(); - ok = true; - break; - } - case AcROTATESTART: - { - this->SetMouseCursor( rotate_cursor_xpm, 0, 0 ); - this->InvokeEvent( SliceRotationEvent() ); // notify listeners - break; - } - case AcROTATEEND: - { - this->ResetMouseCursor(); - this->InvokeEvent( SliceRotationEvent() ); // notify listeners - break; - } - default: - { - break; - } - } + this->InvokeEvent( SliceRotationEvent() ); // notify listeners - return ok; + return true; } } // namespace diff --git a/Core/Code/Controllers/mitkSlicesRotator.h b/Core/Code/Controllers/mitkSlicesRotator.h index cf02880..9b737b3 100644 --- a/Core/Code/Controllers/mitkSlicesRotator.h +++ b/Core/Code/Controllers/mitkSlicesRotator.h @@ -27,79 +27,134 @@ See LICENSE.txt or http://www.mitk.org for details. namespace mitk { /** - * \brief Enables rotation of visible slices (for sliced geometries). - * \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. 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 { -public: + public: - mitkClassMacro(SlicesRotator, SlicesCoordinator); - - static Pointer New(); + mitkClassMacro(SlicesRotator, SlicesCoordinator); + + static Pointer New(); + + /** + \brief New Macro with one parameter for creating this object with static New(..) method. + + Needs to be the "slices-rotator" pattern of StateMachine.xml to work as expected. + **/ + mitkNewMacro1Param(Self, const char*); + + /** + \brief Callback for modifications in observed SliceNavigationControllers -- forwards to UpdateRotatableSNCs(). - /** - * @brief New Macro with one parameter for creating this object with static New(..) method - **/ - mitkNewMacro1Param(Self, const char*); + This method is called when an observed SliceNavigationController changes its + world geometry. The connection is established by calling the other SliceNavigationController's + method ConnectGeometrySendEvent (or similar). + */ + virtual void SetGeometry(const itk::EventObject& EventObject); - virtual void SetGeometry(const itk::EventObject& EventObject); + /** + \brief NOT USED by anything open-source. - virtual void RotateToPoint( SliceNavigationController *rotationPlaneSNC, - SliceNavigationController *rotatedPlaneSNC, - const Point3D &point, bool linked = false ); + \TODO check if this is actually still needed, with SliceNavigationController::ReorientSlices() now being implemented this could be obsolete!? + */ + virtual void RotateToPoint( SliceNavigationController *rotationPlaneSNC, + SliceNavigationController *rotatedPlaneSNC, + const Point3D &point, + bool linked = false ); -protected: + protected: - SlicesRotator(const char* machine); - // clear list of controllers - virtual ~SlicesRotator(); + SlicesRotator(const char* machine); + virtual ~SlicesRotator(); - // check if the slices of this SliceNavigationController can be rotated (???) Possible - virtual void OnSliceControllerAdded(SliceNavigationController* snc); + /** + \brief Called from SlicesCoordinator after a new controller is added (to internal list m_SliceNavigationControllers). + */ + virtual void OnSliceControllerAdded(SliceNavigationController* snc); - virtual void OnSliceControllerRemoved(SliceNavigationController* snc); + /* + \brief Called from SlicesCoordinator after a new controller is being removed (to internal list m_SliceNavigationControllers). + */ + virtual void OnSliceControllerRemoved(SliceNavigationController* snc); - virtual void UpdateRelevantSNCs(); + /** + \brief Check all observed SliceNavigationControllers: remember those that are rotatable in m_RotatableSNCs. + */ + virtual void UpdateRotatableSNCs(); - virtual bool ExecuteAction(Action * action, StateEvent const* stateEvent); + // following methods called from superclass ExecuteAction + bool DoSelectSlice(Action*, const StateEvent*); + bool DoDecideBetweenRotationAndSliceSelection(Action*, const StateEvent*); + bool DoStartRotation(Action*, const StateEvent*); + bool DoEndRotation(Action*, const StateEvent*); + bool DoRotationStep(Action*, const StateEvent*); - SNCVector m_RelevantSNCs; /// all SNCs that currently have CreatedWorldGeometries, that can be rotated. - SNCVector m_SNCsToBeRotated; /// all SNCs that will be rotated + SNCVector m_RotatableSNCs; /// all SNCs that currently have CreatedWorldGeometries, that can be rotated. + SNCVector m_SNCsToBeRotated; /// all SNCs that will be rotated (exceptions are the ones parallel to the one being clicked) - Point3D m_LastCursorPosition; - Point3D m_CenterOfRotation; + Point3D m_LastCursorPosition; /// used for calculation of the rotation angle + Point3D m_CenterOfRotation; /// used for calculation of the rotation angle };