diff --git a/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp b/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp index 61b31bae58..363c3ef628 100644 --- a/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp +++ b/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp @@ -1,942 +1,1113 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #define PLANARFIGUREINTERACTOR_DBG MITK_DEBUG("PlanarFigureInteractor") << __LINE__ << ": " #include "mitkPlanarFigureInteractor.h" #include "mitkPlanarBezierCurve.h" #include "mitkPlanarCircle.h" #include "mitkPlanarFigure.h" #include "mitkPlanarPolygon.h" #include "mitkInteractionPositionEvent.h" #include "mitkInternalEvent.h" #include "mitkBaseRenderer.h" #include "mitkRenderingManager.h" #include "mitkAbstractTransformGeometry.h" #include "mitkPlaneGeometry.h" mitk::PlanarFigureInteractor::PlanarFigureInteractor() : DataInteractor() , m_Precision(6.5) , m_MinimumPointDistance(25.0) , m_IsHovering(false) { } mitk::PlanarFigureInteractor::~PlanarFigureInteractor() { } void mitk::PlanarFigureInteractor::ConnectActionsAndFunctions() { CONNECT_CONDITION("figure_is_on_current_slice", CheckFigureOnRenderingGeometry); CONNECT_CONDITION("figure_is_placed", CheckFigurePlaced); CONNECT_CONDITION("minimal_figure_is_finished", CheckMinimalFigureFinished); CONNECT_CONDITION("hovering_above_figure", CheckFigureHovering); CONNECT_CONDITION("hovering_above_point", CheckControlPointHovering); CONNECT_CONDITION("figure_is_selected", CheckSelection); CONNECT_CONDITION("point_is_valid", CheckPointValidity); CONNECT_CONDITION("figure_is_finished", CheckFigureFinished); CONNECT_CONDITION("reset_on_point_select_needed", CheckResetOnPointSelect); CONNECT_CONDITION("points_can_be_added_or_removed", CheckFigureIsExtendable); CONNECT_CONDITION("figure_can_be_deleted", CheckFigureIsDeletable); CONNECT_CONDITION("figure_is_editable", CheckFigureIsEditable); CONNECT_FUNCTION("finalize_figure", FinalizeFigure); CONNECT_FUNCTION("hide_preview_point", HidePreviewPoint) CONNECT_FUNCTION("hide_control_points", HideControlPoints) CONNECT_FUNCTION("set_preview_point_position", SetPreviewPointPosition) CONNECT_FUNCTION("move_current_point", MoveCurrentPoint); CONNECT_FUNCTION("deselect_point", DeselectPoint); CONNECT_FUNCTION("add_new_point", AddPoint); CONNECT_FUNCTION("add_initial_point", AddInitialPoint); CONNECT_FUNCTION("remove_selected_point", RemoveSelectedPoint); CONNECT_FUNCTION("request_context_menu", RequestContextMenu); CONNECT_FUNCTION("select_figure", SelectFigure); CONNECT_FUNCTION("select_point", SelectPoint); CONNECT_FUNCTION("end_interaction", EndInteraction); CONNECT_FUNCTION("start_hovering", StartHovering) CONNECT_FUNCTION("end_hovering", EndHovering); CONNECT_FUNCTION("delete_figure", DeleteFigure); CONNECT_FUNCTION("reset_on_point_select", PerformPointResetOnSelect); } bool mitk::PlanarFigureInteractor::CheckFigurePlaced(const InteractionEvent * /*interactionEvent*/) { - const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } bool isFigureFinished = false; planarFigure->GetPropertyList()->GetBoolProperty("initiallyplaced", isFigureFinished); return planarFigure->IsPlaced() && isFigureFinished; } void mitk::PlanarFigureInteractor::MoveCurrentPoint(StateMachineAction *, InteractionEvent *interactionEvent) { - auto *positionEvent = dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return; + } bool isEditable = true; GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } - const mitk::PlaneGeometry *planarFigureGeometry = planarFigure->GetPlaneGeometry(); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); + auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); + if (nullptr == planarFigureGeometry) + { + return; + } - if (abstractTransformGeometry != nullptr) + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); + if (nullptr != abstractTransformGeometry) + { return; + } // Extract point in 2D world coordinates (relative to PlaneGeometry of PlanarFigure) Point2D point2D; if (!this->TransformPositionEventToPoint2D(positionEvent, planarFigureGeometry, point2D) || !isEditable) { return; } planarFigure->InvokeEvent(StartInteractionPlanarFigureEvent()); // check if the control points shall be hidden during interaction bool hidecontrolpointsduringinteraction = false; GetDataNode()->GetBoolProperty("planarfigure.hidecontrolpointsduringinteraction", hidecontrolpointsduringinteraction); // hide the control points if necessary // interactionEvent->GetSender()->GetDataStorage()->BlockNodeModifiedEvents( true ); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", !hidecontrolpointsduringinteraction); // interactionEvent->GetSender()->GetDataStorage()->BlockNodeModifiedEvents( false ); // Move current control point to this point planarFigure->SetCurrentControlPoint(point2D); // Re-evaluate features planarFigure->EvaluateFeatures(); // Update rendered scene RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::FinalizeFigure(StateMachineAction *, InteractionEvent *) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } planarFigure->Modified(); planarFigure->DeselectControlPoint(); planarFigure->RemoveLastControlPoint(); planarFigure->SetProperty("initiallyplaced", mitk::BoolProperty::New(true)); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); GetDataNode()->Modified(); planarFigure->InvokeEvent(EndPlacementPlanarFigureEvent()); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); // Shape might change when figure is finalized, e.g., smoothing of subdivision polygon planarFigure->EvaluateFeatures(); RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::EndInteraction(StateMachineAction *, InteractionEvent *) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } + GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); planarFigure->Modified(); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); RenderingManager::GetInstance()->RequestUpdateAll(); } bool mitk::PlanarFigureInteractor::FilterEvents(InteractionEvent *interactionEvent, mitk::DataNode * /*dataNode*/) { if (interactionEvent->GetSender() == nullptr) return false; if (interactionEvent->GetSender()->GetMapperID() == BaseRenderer::Standard3D) return false; return true; } void mitk::PlanarFigureInteractor::EndHovering(StateMachineAction *, InteractionEvent *) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } + planarFigure->ResetPreviewContolPoint(); // Invoke end-hover event once the mouse is exiting the figure area m_IsHovering = false; planarFigure->InvokeEvent(EndHoverPlanarFigureEvent()); // Set bool property to indicate that planar figure is no longer in "hovering" mode GetDataNode()->SetBoolProperty("planarfigure.ishovering", false); RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::DeleteFigure(StateMachineAction *, InteractionEvent *interactionEvent) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } + planarFigure->RemoveAllObservers(); GetDataNode()->RemoveAllObservers(); interactionEvent->GetSender()->GetDataStorage()->Remove(GetDataNode()); RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::PerformPointResetOnSelect(StateMachineAction *, InteractionEvent *) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } + planarFigure->ResetOnPointSelect(); } bool mitk::PlanarFigureInteractor::CheckMinimalFigureFinished(const InteractionEvent * /*interactionEvent*/) { - const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } return planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMinimumNumberOfControlPoints(); } bool mitk::PlanarFigureInteractor::CheckFigureFinished(const InteractionEvent * /*interactionEvent*/) { - const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } return planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMaximumNumberOfControlPoints(); } bool mitk::PlanarFigureInteractor::CheckFigureIsExtendable(const InteractionEvent * /*interactionEvent*/) { bool isExtendable(false); GetDataNode()->GetBoolProperty("planarfigure.isextendable", isExtendable); return isExtendable; } bool mitk::PlanarFigureInteractor::CheckFigureIsDeletable(const InteractionEvent * /*interactionEvent*/) { bool isDeletable(true); GetDataNode()->GetBoolProperty("planarfigure.isdeletable", isDeletable); return isDeletable; } bool mitk::PlanarFigureInteractor::CheckFigureIsEditable(const InteractionEvent * /*interactionEvent*/) { bool isEditable(true); GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); return isEditable; } void mitk::PlanarFigureInteractor::DeselectPoint(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } const bool wasSelected = planarFigure->DeselectControlPoint(); if (wasSelected) { // Issue event so that listeners may update themselves planarFigure->Modified(); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); GetDataNode()->Modified(); } } void mitk::PlanarFigureInteractor::AddPoint(StateMachineAction *, InteractionEvent *interactionEvent) { - const mitk::InteractionPositionEvent *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return; - - const DataNode::Pointer node = this->GetDataNode(); - const BaseData::Pointer data = node->GetData(); + } /* * Added check for "initiallyplaced" due to bug 13097: * * There are two possible cases in which a point can be inserted into a PlanarPolygon: * * 1. The figure is currently drawn -> the point will be appended at the end of the figure * 2. A point is inserted at a userdefined position after the initial placement of the figure is finished * * In the second case we need to determine the proper insertion index. In the first case the index always has * to be -1 so that the point is appended to the end. * * These changes are necessary because of a macOS specific issue: If a users draws a PlanarPolygon then the * next point to be added moves according to the mouse position. If then the user left clicks in order to add * a point one would assume the last move position is identical to the left click position. This is actually the * case for windows and linux but somehow NOT for mac. Because of the insertion logic of a new point in the * PlanarFigure then for mac the wrong current selected point is determined. * * With this check here this problem can be avoided. However a redesign of the insertion logic should be considered */ + const DataNode::Pointer node = this->GetDataNode(); + const BaseData::Pointer data = node->GetData(); + bool isFigureFinished = false; data->GetPropertyList()->GetBoolProperty("initiallyplaced", isFigureFinished); bool selected = false; bool isEditable = true; node->GetBoolProperty("selected", selected); node->GetBoolProperty("planarfigure.iseditable", isEditable); if (!selected || !isEditable) { return; } - auto *planarFigure = dynamic_cast(data.GetPointer()); + auto planarFigure = dynamic_cast(data.GetPointer()); + if (nullptr == planarFigure) + { + return; + } // We can't derive a new control point from a polyline of a Bezier curve // as all control points contribute to each polyline point. if (dynamic_cast(planarFigure) != nullptr && isFigureFinished) return; - const mitk::PlaneGeometry *planarFigureGeometry = planarFigure->GetPlaneGeometry(); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); + auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); + if (nullptr == planarFigureGeometry) + { + return; + } - if (abstractTransformGeometry != nullptr) + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); + if (nullptr != abstractTransformGeometry) + { return; + } // If the planarFigure already has reached the maximum number if (planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMaximumNumberOfControlPoints()) { return; } // Extract point in 2D world coordinates (relative to PlaneGeometry of // PlanarFigure) Point2D point2D, projectedPoint; if (!this->TransformPositionEventToPoint2D(positionEvent, planarFigureGeometry, point2D)) { return; } // TODO: check segment of polyline we clicked in int nextIndex = -1; // We only need to check which position to insert the control point // when interacting with a PlanarPolygon. For all other types // new control points will always be appended const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); if (dynamic_cast(planarFigure) && isFigureFinished) { nextIndex = this->IsPositionOverFigure(positionEvent, planarFigure, planarFigureGeometry, projectionPlane, projectedPoint); } // Add point as new control point if (planarFigure->IsPreviewControlPointVisible()) { point2D = planarFigure->GetPreviewControlPoint(); } planarFigure->AddControlPoint(point2D, planarFigure->GetControlPointForPolylinePoint(nextIndex, 0)); if (planarFigure->IsPreviewControlPointVisible()) { planarFigure->SelectControlPoint(nextIndex); planarFigure->ResetPreviewContolPoint(); } // Re-evaluate features planarFigure->EvaluateFeatures(); // this->LogPrintPlanarFigureQuantities( planarFigure ); // Update rendered scene RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::AddInitialPoint(StateMachineAction *, InteractionEvent *interactionEvent) { - const mitk::InteractionPositionEvent *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { + return; + } + + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { return; + } - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); mitk::BaseRenderer *renderer = interactionEvent->GetSender(); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); // Invoke event to notify listeners that placement of this PF starts now planarFigure->InvokeEvent(StartPlacementPlanarFigureEvent()); // Use PlaneGeometry of the renderer clicked on for this PlanarFigure auto *planeGeometry = const_cast( dynamic_cast(renderer->GetSliceNavigationController()->GetCurrentPlaneGeometry())); if (planeGeometry != nullptr && abstractTransformGeometry == nullptr) { planarFigure->SetPlaneGeometry(planeGeometry); } else { return; } // Extract point in 2D world coordinates (relative to PlaneGeometry of // PlanarFigure) Point2D point2D; if (!this->TransformPositionEventToPoint2D(positionEvent, planeGeometry, point2D)) { return; } // Place PlanarFigure at this point planarFigure->PlaceFigure(point2D); // Re-evaluate features planarFigure->EvaluateFeatures(); // this->LogPrintPlanarFigureQuantities( planarFigure ); // Set a bool property indicating that the figure has been placed in // the current RenderWindow. This is required so that the same render // window can be re-aligned to the PlaneGeometry of the PlanarFigure later // on in an application. GetDataNode()->SetBoolProperty("PlanarFigureInitializedWindow", true, renderer); // Update rendered scene RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::StartHovering(StateMachineAction *, InteractionEvent *interactionEvent) { - const mitk::InteractionPositionEvent *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return; + } - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } if (!m_IsHovering) { // Invoke hover event once when the mouse is entering the figure area m_IsHovering = true; planarFigure->InvokeEvent(StartHoverPlanarFigureEvent()); // Set bool property to indicate that planar figure is currently in "hovering" mode GetDataNode()->SetBoolProperty("planarfigure.ishovering", true); RenderingManager::GetInstance()->RequestUpdateAll(); } } void mitk::PlanarFigureInteractor::SetPreviewPointPosition(StateMachineAction *, InteractionEvent *interactionEvent) { - const mitk::InteractionPositionEvent *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return; + } + + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); planarFigure->DeselectControlPoint(); mitk::Point2D pointProjectedOntoLine = positionEvent->GetPointerPositionOnScreen(); bool selected(false); bool isExtendable(false); bool isEditable(true); GetDataNode()->GetBoolProperty("selected", selected); GetDataNode()->GetBoolProperty("planarfigure.isextendable", isExtendable); GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); if (selected && isExtendable && isEditable) { renderer->DisplayToPlane(pointProjectedOntoLine, pointProjectedOntoLine); planarFigure->SetPreviewControlPoint(pointProjectedOntoLine); } RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::HideControlPoints(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", false); } void mitk::PlanarFigureInteractor::HidePreviewPoint(StateMachineAction *, InteractionEvent *) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } + planarFigure->ResetPreviewContolPoint(); RenderingManager::GetInstance()->RequestUpdateAll(); } bool mitk::PlanarFigureInteractor::CheckFigureHovering(const InteractionEvent *interactionEvent) { - const auto *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return false; + } - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); - const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); - const mitk::PlaneGeometry *planarFigureGeometry = planarFigure->GetPlaneGeometry(); - auto *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); - const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } - if (abstractTransformGeometry != nullptr) + auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); + if (nullptr == planarFigureGeometry) + { return false; + } + + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); + if (nullptr != abstractTransformGeometry) + { + return false; + } + + const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); + const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); mitk::Point2D pointProjectedOntoLine; int previousControlPoint = this->IsPositionOverFigure( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, pointProjectedOntoLine); bool isHovering = (previousControlPoint != -1); + return isHovering; +} - if (isHovering) +bool mitk::PlanarFigureInteractor::CheckControlPointHovering(const InteractionEvent *interactionEvent) +{ + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) { - return true; + return false; } - else + + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) { return false; } - return false; -} + auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); + if (nullptr == planarFigureGeometry) + { + return false; + } -bool mitk::PlanarFigureInteractor::CheckControlPointHovering(const InteractionEvent *interactionEvent) -{ - const auto *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); + if (nullptr != abstractTransformGeometry) + { return false; + } - const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); - const mitk::PlaneGeometry *planarFigureGeometry = dynamic_cast(planarFigure->GetGeometry(0)); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); - if (abstractTransformGeometry != nullptr) - return false; - int pointIndex = -1; pointIndex = mitk::PlanarFigureInteractor::IsPositionInsideMarker( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer); return pointIndex >= 0; } bool mitk::PlanarFigureInteractor::CheckSelection(const InteractionEvent * /*interactionEvent*/) { bool selected = false; GetDataNode()->GetBoolProperty("selected", selected); return selected; } void mitk::PlanarFigureInteractor::SelectFigure(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } + planarFigure->InvokeEvent(SelectPlanarFigureEvent()); } void mitk::PlanarFigureInteractor::SelectPoint(StateMachineAction *, InteractionEvent *interactionEvent) { - const mitk::InteractionPositionEvent *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return; + } - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); - const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); - const mitk::PlaneGeometry *planarFigureGeometry = dynamic_cast(planarFigure->GetGeometry(0)); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); - const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } + + auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); + if (nullptr == planarFigureGeometry) + { + return; + } - if (abstractTransformGeometry != nullptr) + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); + if (nullptr != abstractTransformGeometry) + { return; + } + + const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); + const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); const int pointIndex = mitk::PlanarFigureInteractor::IsPositionInsideMarker( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer); if (pointIndex >= 0) { // If mouse is above control point, mark it as selected planarFigure->SelectControlPoint(pointIndex); } else { planarFigure->DeselectControlPoint(); } } bool mitk::PlanarFigureInteractor::CheckPointValidity(const InteractionEvent *interactionEvent) { // Check if the distance of the current point to the previously set point in display coordinates // is sufficient (if a previous point exists) - // Extract display position - const auto *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return false; + } - const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } return IsMousePositionAcceptableAsNewControlPoint(positionEvent, planarFigure); } void mitk::PlanarFigureInteractor::RemoveSelectedPoint(StateMachineAction *, InteractionEvent *interactionEvent) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); - mitk::BaseRenderer *renderer = interactionEvent->GetSender(); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } const int selectedControlPoint = planarFigure->GetSelectedControlPoint(); planarFigure->RemoveControlPoint(selectedControlPoint); // Re-evaluate features planarFigure->EvaluateFeatures(); planarFigure->Modified(); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); RenderingManager::GetInstance()->RequestUpdateAll(); + mitk::BaseRenderer *renderer = interactionEvent->GetSender(); HandleEvent(mitk::InternalEvent::New(renderer, this, "Dummy-Event"), GetDataNode()); } void mitk::PlanarFigureInteractor::RequestContextMenu(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } bool selected = false; GetDataNode()->GetBoolProperty("selected", selected); // no need to invoke this if the figure is already selected if (!selected) { planarFigure->InvokeEvent(SelectPlanarFigureEvent()); } planarFigure->InvokeEvent(ContextMenuPlanarFigureEvent()); } bool mitk::PlanarFigureInteractor::CheckResetOnPointSelect(const InteractionEvent * /*interactionEvent*/) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } bool isEditable = true; GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); // Reset the PlanarFigure if required return isEditable && planarFigure->ResetOnPointSelectNeeded(); } bool mitk::PlanarFigureInteractor::CheckFigureOnRenderingGeometry(const InteractionEvent *interactionEvent) { - const auto *posEvent = - dynamic_cast(interactionEvent); - - if (posEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return false; + } - const mitk::Point3D worldPoint3D = posEvent->GetPositionInWorld(); - const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); - - const mitk::PlaneGeometry *planarFigurePlaneGeometry = dynamic_cast(planarFigure->GetGeometry(0)); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); + const mitk::Point3D worldPoint3D = positionEvent->GetPositionInWorld(); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } - if (abstractTransformGeometry != nullptr) + auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); + if (nullptr == planarFigureGeometry) + { return false; + } - const double planeThickness = planarFigurePlaneGeometry->GetExtentInMM(2); - if (planarFigurePlaneGeometry->Distance(worldPoint3D) > planeThickness) + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); + if (nullptr != abstractTransformGeometry) { - // don't react, when interaction is too far away return false; } - return true; + + const double planeThickness = planarFigureGeometry->GetExtentInMM(2); + return planarFigureGeometry->Distance(worldPoint3D) <= planeThickness; } void mitk::PlanarFigureInteractor::SetPrecision(mitk::ScalarType precision) { m_Precision = precision; } void mitk::PlanarFigureInteractor::SetMinimumPointDistance(ScalarType minimumDistance) { m_MinimumPointDistance = minimumDistance; } bool mitk::PlanarFigureInteractor::TransformPositionEventToPoint2D(const InteractionPositionEvent *positionEvent, const PlaneGeometry *planarFigureGeometry, Point2D &point2D) { + if (nullptr == positionEvent || nullptr == planarFigureGeometry) + { + return false; + } + const mitk::Point3D worldPoint3D = positionEvent->GetPositionInWorld(); // TODO: proper handling of distance tolerance if (planarFigureGeometry->Distance(worldPoint3D) > 0.1) { return false; } // Project point onto plane of this PlanarFigure planarFigureGeometry->Map(worldPoint3D, point2D); return true; } bool mitk::PlanarFigureInteractor::TransformObjectToDisplay(const mitk::Point2D &point2D, mitk::Point2D &displayPoint, const mitk::PlaneGeometry *objectGeometry, const mitk::PlaneGeometry *rendererGeometry, const mitk::BaseRenderer *renderer) const { + if (nullptr == objectGeometry || nullptr == rendererGeometry || nullptr == renderer) + { + return false; + } + mitk::Point3D point3D; // Map circle point from local 2D geometry into 3D world space objectGeometry->Map(point2D, point3D); const double planeThickness = objectGeometry->GetExtentInMM(2); // TODO: proper handling of distance tolerance if (rendererGeometry->Distance(point3D) < planeThickness / 3.0) { // Project 3D world point onto display geometry renderer->WorldToDisplay(point3D, displayPoint); return true; } return false; } bool mitk::PlanarFigureInteractor::IsPointNearLine(const mitk::Point2D &point, const mitk::Point2D &startPoint, const mitk::Point2D &endPoint, mitk::Point2D &projectedPoint) const { mitk::Vector2D n1 = endPoint - startPoint; n1.Normalize(); // Determine dot products between line vector and startpoint-point / endpoint-point vectors const double l1 = n1 * (point - startPoint); const double l2 = -n1 * (point - endPoint); // Determine projection of specified point onto line defined by start / end point const mitk::Point2D crossPoint = startPoint + n1 * l1; projectedPoint = crossPoint; const float dist1 = crossPoint.SquaredEuclideanDistanceTo(point); const float dist2 = endPoint.SquaredEuclideanDistanceTo(point); const float dist3 = startPoint.SquaredEuclideanDistanceTo(point); // Point is inside encompassing rectangle IF // - its distance to its projected point is small enough // - it is not further outside of the line than the defined tolerance if (((dist1 < 20.0) && (l1 > 0.0) && (l2 > 0.0)) || dist2 < 20.0 || dist3 < 20.0) { return true; } return false; } int mitk::PlanarFigureInteractor::IsPositionOverFigure(const InteractionPositionEvent *positionEvent, PlanarFigure *planarFigure, const PlaneGeometry *planarFigureGeometry, const PlaneGeometry *rendererGeometry, Point2D &pointProjectedOntoLine) const { + if (nullptr == positionEvent || nullptr == planarFigure || nullptr == planarFigureGeometry + || nullptr == rendererGeometry) + { + return -1; + } + mitk::Point2D displayPosition = positionEvent->GetPointerPositionOnScreen(); // Iterate over all polylines of planar figure, and check if // any one is close to the current display position typedef mitk::PlanarFigure::PolyLineType VertexContainerType; Point2D polyLinePoint; Point2D firstPolyLinePoint; Point2D previousPolyLinePoint; for (unsigned short loop = 0; loop < planarFigure->GetPolyLinesSize(); ++loop) { const VertexContainerType polyLine = planarFigure->GetPolyLine(loop); bool firstPoint(true); for (auto it = polyLine.begin(); it != polyLine.end(); ++it) { // Get plane coordinates of this point of polyline (if possible) if (!this->TransformObjectToDisplay( *it, polyLinePoint, planarFigureGeometry, rendererGeometry, positionEvent->GetSender())) { break; // Poly line invalid (not on current 2D plane) --> skip it } if (firstPoint) { firstPolyLinePoint = polyLinePoint; firstPoint = false; } else if (this->IsPointNearLine(displayPosition, previousPolyLinePoint, polyLinePoint, pointProjectedOntoLine)) { // Point is close enough to line segment --> Return index of the segment return std::distance(polyLine.begin(), it); } previousPolyLinePoint = polyLinePoint; } // For closed figures, also check last line segment if (planarFigure->IsClosed() && this->IsPointNearLine(displayPosition, polyLinePoint, firstPolyLinePoint, pointProjectedOntoLine)) { return 0; // Return index of first control point } } return -1; } int mitk::PlanarFigureInteractor::IsPositionInsideMarker(const InteractionPositionEvent *positionEvent, const PlanarFigure *planarFigure, const PlaneGeometry *planarFigureGeometry, const PlaneGeometry *rendererGeometry, const BaseRenderer *renderer) const { + if (nullptr == positionEvent || nullptr == planarFigure || nullptr == planarFigureGeometry + || nullptr == rendererGeometry || nullptr == renderer) + { + return -1; + } + const mitk::Point2D displayPosition = positionEvent->GetPointerPositionOnScreen(); // Iterate over all control points of planar figure, and check if // any one is close to the current display position mitk::Point2D displayControlPoint; const int numberOfControlPoints = planarFigure->GetNumberOfControlPoints(); for (int i = 0; i < numberOfControlPoints; i++) { if (this->TransformObjectToDisplay( planarFigure->GetControlPoint(i), displayControlPoint, planarFigureGeometry, rendererGeometry, renderer)) { // TODO: variable size of markers if (displayPosition.SquaredEuclideanDistanceTo(displayControlPoint) < 20.0) { return i; } } } return -1; } void mitk::PlanarFigureInteractor::LogPrintPlanarFigureQuantities(const PlanarFigure *planarFigure) { + if (nullptr == planarFigure) + { + MITK_INFO << "PlanarFigure invalid."; + } + MITK_INFO << "PlanarFigure: " << planarFigure->GetNameOfClass(); for (unsigned int i = 0; i < planarFigure->GetNumberOfFeatures(); ++i) { MITK_INFO << "* " << planarFigure->GetFeatureName(i) << ": " << planarFigure->GetQuantity(i) << " " << planarFigure->GetFeatureUnit(i); } } bool mitk::PlanarFigureInteractor::IsMousePositionAcceptableAsNewControlPoint( const mitk::InteractionPositionEvent *positionEvent, const PlanarFigure *planarFigure) { - assert(positionEvent && planarFigure); + if (nullptr == positionEvent || nullptr == planarFigure) + { + return false; + + } const BaseRenderer *renderer = positionEvent->GetSender(); - assert(renderer); + if (nullptr == renderer) + { + return false; + } // Get the timestep to support 3D+t const int timeStep(renderer->GetTimeStep(planarFigure)); bool tooClose(false); - const mitk::PlaneGeometry *planarFigureGeometry = - dynamic_cast(planarFigure->GetGeometry(timeStep)); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(timeStep)); + auto planarFigureGeometry = dynamic_cast(planarFigure->GetGeometry(timeStep)); + if (nullptr == planarFigureGeometry) + { + return false; + } - if (abstractTransformGeometry != nullptr) + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(timeStep)); + if (nullptr != abstractTransformGeometry) + { return false; + } Point2D point2D; // Get the point2D from the positionEvent if (!this->TransformPositionEventToPoint2D(positionEvent, planarFigureGeometry, point2D)) { return false; } // apply the controlPoint constraints of the planarFigure to get the // coordinates that would actually be used. const Point2D correctedPoint = const_cast(planarFigure)->ApplyControlPointConstraints(0, point2D); // map the 2D coordinates of the new point to world-coordinates // and transform those to display-coordinates mitk::Point3D newPoint3D; planarFigureGeometry->Map(correctedPoint, newPoint3D); mitk::Point2D newDisplayPosition; renderer->WorldToDisplay(newPoint3D, newDisplayPosition); const int selectedControlPoint = planarFigure->GetSelectedControlPoint(); for (int i = 0; i < (int)planarFigure->GetNumberOfControlPoints(); ++i) { if (i != selectedControlPoint) { // Try to convert previous point to current display coordinates mitk::Point3D previousPoint3D; // map the 2D coordinates of the control-point to world-coordinates planarFigureGeometry->Map(planarFigure->GetControlPoint(i), previousPoint3D); if (renderer->GetCurrentWorldPlaneGeometry()->Distance(previousPoint3D) < 0.1) // ugly, but assert makes this work { mitk::Point2D previousDisplayPosition; // transform the world-coordinates into display-coordinates renderer->WorldToDisplay(previousPoint3D, previousDisplayPosition); // Calculate the distance. We use display-coordinates here to make // the check independent of the zoom-level of the rendering scene. const double a = newDisplayPosition[0] - previousDisplayPosition[0]; const double b = newDisplayPosition[1] - previousDisplayPosition[1]; // If point is to close, do not set a new point tooClose = (a * a + b * b < m_MinimumPointDistance); } if (tooClose) return false; // abort loop early } } return !tooClose; // default } void mitk::PlanarFigureInteractor::ConfigurationChanged() { const mitk::PropertyList::Pointer properties = GetAttributes(); std::string precision = ""; if (properties->GetStringProperty("precision", precision)) { m_Precision = atof(precision.c_str()); } else { m_Precision = (ScalarType)6.5; } std::string minPointDistance = ""; if (properties->GetStringProperty("minPointDistance", minPointDistance)) { m_MinimumPointDistance = atof(minPointDistance.c_str()); } else { m_MinimumPointDistance = (ScalarType)25.0; } }