/*========================================================================= Program: Visualization Toolkit Module: vtkContourWidget.cxx Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "vtkContourWidget.h" #include "vtkCallbackCommand.h" #include "vtkCommand.h" #include "vtkEvent.h" #include "vtkObjectFactory.h" #include "vtkOrientedGlyphContourRepresentation.h" #include "vtkPolyData.h" #include "vtkProperty.h" #include "vtkProperty2D.h" #include "vtkRenderWindowInteractor.h" #include "vtkRenderer.h" #include "vtkSphereSource.h" #include "vtkWidgetCallbackMapper.h" #include "vtkWidgetEvent.h" vtkStandardNewMacro(vtkContourWidget); //------------------------------------------------------------------------------ vtkContourWidget::vtkContourWidget() { this->ManagesCursor = 0; this->WidgetState = vtkContourWidget::Start; this->CurrentHandle = 0; this->AllowNodePicking = 0; this->FollowCursor = 0; this->ContinuousDraw = 0; this->ContinuousActive = 0; // These are the event callbacks supported by this widget this->CallbackMapper->SetCallbackMethod( vtkCommand::LeftButtonPressEvent, vtkWidgetEvent::Select, this, vtkContourWidget::SelectAction); this->CallbackMapper->SetCallbackMethod(vtkCommand::RightButtonPressEvent, vtkWidgetEvent::AddFinalPoint, this, vtkContourWidget::AddFinalPointAction); this->CallbackMapper->SetCallbackMethod( vtkCommand::MouseMoveEvent, vtkWidgetEvent::Move, this, vtkContourWidget::MoveAction); this->CallbackMapper->SetCallbackMethod(vtkCommand::LeftButtonReleaseEvent, vtkWidgetEvent::EndSelect, this, vtkContourWidget::EndSelectAction); this->CallbackMapper->SetCallbackMethod(vtkCommand::KeyPressEvent, vtkEvent::NoModifier, 127, 1, "Delete", vtkWidgetEvent::Delete, this, vtkContourWidget::DeleteAction); this->CallbackMapper->SetCallbackMethod(vtkCommand::KeyPressEvent, vtkEvent::NoModifier, 8, 1, "BackSpace", vtkWidgetEvent::Delete, this, vtkContourWidget::DeleteAction); this->CallbackMapper->SetCallbackMethod(vtkCommand::KeyPressEvent, vtkEvent::ShiftModifier, 127, 1, "Delete", vtkWidgetEvent::Reset, this, vtkContourWidget::ResetAction); this->CallbackMapper->SetCallbackMethod(vtkCommand::MiddleButtonPressEvent, vtkWidgetEvent::Translate, this, vtkContourWidget::TranslateContourAction); this->CallbackMapper->SetCallbackMethod(vtkCommand::MiddleButtonReleaseEvent, vtkWidgetEvent::EndTranslate, this, vtkContourWidget::EndSelectAction); this->CallbackMapper->SetCallbackMethod(vtkCommand::RightButtonPressEvent, vtkWidgetEvent::Scale, this, vtkContourWidget::ScaleContourAction); this->CallbackMapper->SetCallbackMethod(vtkCommand::RightButtonReleaseEvent, vtkWidgetEvent::EndScale, this, vtkContourWidget::EndSelectAction); this->CreateDefaultRepresentation(); } //------------------------------------------------------------------------------ vtkContourWidget::~vtkContourWidget() = default; //------------------------------------------------------------------------------ void vtkContourWidget::CreateDefaultRepresentation() { if (!this->WidgetRep) { vtkOrientedGlyphContourRepresentation* rep = vtkOrientedGlyphContourRepresentation::New(); this->WidgetRep = rep; vtkSphereSource* ss = vtkSphereSource::New(); ss->SetRadius(0.5); ss->Update(); rep->SetActiveCursorShape(ss->GetOutput()); ss->Delete(); rep->GetProperty()->SetColor(0.25, 1.0, 0.25); vtkProperty* property = vtkProperty::SafeDownCast(rep->GetActiveProperty()); if (property) { property->SetRepresentationToSurface(); property->SetAmbient(0.1); property->SetDiffuse(0.9); property->SetSpecular(0.0); } } } //------------------------------------------------------------------------------ void vtkContourWidget::CloseLoop() { vtkContourRepresentation* rep = reinterpret_cast(this->WidgetRep); if (!rep->GetClosedLoop() && rep->GetNumberOfNodes() > 1) { this->WidgetState = vtkContourWidget::Manipulate; rep->ClosedLoopOn(); this->Render(); } } //------------------------------------------------------------------------------ void vtkContourWidget::SetEnabled(int enabling) { // The handle widgets are not actually enabled until they are placed. // The handle widgets take their representation from the vtkContourRepresentation. if (enabling) { if (this->WidgetState == vtkContourWidget::Start) { reinterpret_cast(this->WidgetRep)->VisibilityOff(); } else { reinterpret_cast(this->WidgetRep)->VisibilityOn(); } } this->Superclass::SetEnabled(enabling); } // The following methods are the callbacks that the contour widget responds to. //------------------------------------------------------------------------------ void vtkContourWidget::SelectAction(vtkAbstractWidget* w) { vtkContourWidget* self = reinterpret_cast(w); vtkContourRepresentation* rep = reinterpret_cast(self->WidgetRep); int X = self->Interactor->GetEventPosition()[0]; int Y = self->Interactor->GetEventPosition()[1]; double pos[2]; pos[0] = X; pos[1] = Y; if (self->ContinuousDraw) { self->ContinuousActive = 0; } switch (self->WidgetState) { case vtkContourWidget::Start: case vtkContourWidget::Define: { // If we are following the cursor, let's add 2 nodes rightaway, on the // first click. The second node is the one that follows the cursor // around. if ((self->FollowCursor || self->ContinuousDraw) && (rep->GetNumberOfNodes() == 0)) { self->AddNode(); } self->AddNode(); if (self->ContinuousDraw) { self->ContinuousActive = 1; } break; } case vtkContourWidget::Manipulate: { if (rep->ActivateNode(X, Y)) { self->Superclass::StartInteraction(); self->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr); self->StartInteraction(); rep->SetCurrentOperationToTranslate(); rep->StartWidgetInteraction(pos); self->EventCallbackCommand->SetAbortFlag(1); } else if (rep->AddNodeOnContour(X, Y)) { if (rep->ActivateNode(X, Y)) { rep->SetCurrentOperationToTranslate(); rep->StartWidgetInteraction(pos); } self->EventCallbackCommand->SetAbortFlag(1); } else if (!rep->GetNeedToRender()) { rep->SetRebuildLocator(true); } break; } } if (rep->GetNeedToRender()) { self->Render(); rep->NeedToRenderOff(); } } //------------------------------------------------------------------------------ void vtkContourWidget::AddFinalPointAction(vtkAbstractWidget* w) { vtkContourWidget* self = reinterpret_cast(w); vtkContourRepresentation* rep = reinterpret_cast(self->WidgetRep); if (self->WidgetState != vtkContourWidget::Manipulate && rep->GetNumberOfNodes() >= 1) { // In follow cursor and continuous draw mode, the "extra" node // has already been added for us. if (!self->FollowCursor && !self->ContinuousDraw) { self->AddNode(); } if (self->ContinuousDraw) { self->ContinuousActive = 0; } self->WidgetState = vtkContourWidget::Manipulate; self->EventCallbackCommand->SetAbortFlag(1); self->InvokeEvent(vtkCommand::EndInteractionEvent, nullptr); } if (rep->GetNeedToRender()) { self->Render(); rep->NeedToRenderOff(); } } //------------------------------------------------------------------------------ void vtkContourWidget::AddNode() { int X = this->Interactor->GetEventPosition()[0]; int Y = this->Interactor->GetEventPosition()[1]; // If the rep already has at least 2 nodes, check how close we are to // the first vtkContourRepresentation* rep = reinterpret_cast(this->WidgetRep); int numNodes = rep->GetNumberOfNodes(); if (numNodes > 1) { int pixelTolerance = rep->GetPixelTolerance(); int pixelTolerance2 = pixelTolerance * pixelTolerance; double displayPos[2]; if (!rep->GetNthNodeDisplayPosition(0, displayPos)) { vtkErrorMacro("Can't get first node display position!"); return; } // if in continuous draw mode, we don't want to close the loop until we are at least // numNodes > pixelTolerance away int distance2 = static_cast( (X - displayPos[0]) * (X - displayPos[0]) + (Y - displayPos[1]) * (Y - displayPos[1])); if ((distance2 < pixelTolerance2 && numNodes > 2) || (this->ContinuousDraw && numNodes > pixelTolerance && distance2 < pixelTolerance2)) { // yes - we have made a loop. Stop defining and switch to // manipulate mode this->WidgetState = vtkContourWidget::Manipulate; rep->ClosedLoopOn(); this->Render(); this->EventCallbackCommand->SetAbortFlag(1); this->InvokeEvent(vtkCommand::EndInteractionEvent, nullptr); return; } } if (rep->AddNodeAtDisplayPosition(X, Y)) { if (this->WidgetState == vtkContourWidget::Start) { this->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr); } this->WidgetState = vtkContourWidget::Define; rep->VisibilityOn(); this->EventCallbackCommand->SetAbortFlag(1); this->InvokeEvent(vtkCommand::InteractionEvent, nullptr); } } //------------------------------------------------------------------------------ // Note that if you select the contour at a location that is not moused over // a control point, the translate action makes the closest contour node // jump to the current mouse location. Perhaps we should either // (a) Disable translations when not moused over a control point // (b) Fix the jumping behaviour by calculating motion vectors from the start // of the interaction. void vtkContourWidget::TranslateContourAction(vtkAbstractWidget* w) { vtkContourWidget* self = reinterpret_cast(w); if (self->WidgetState != vtkContourWidget::Manipulate) { return; } vtkContourRepresentation* rep = reinterpret_cast(self->WidgetRep); int X = self->Interactor->GetEventPosition()[0]; int Y = self->Interactor->GetEventPosition()[1]; double pos[2]; pos[0] = X; pos[1] = Y; if (rep->ActivateNode(X, Y)) { self->Superclass::StartInteraction(); self->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr); self->StartInteraction(); rep->SetCurrentOperationToShift(); // Here rep->StartWidgetInteraction(pos); self->EventCallbackCommand->SetAbortFlag(1); } else { double p[3]; int idx; if (rep->FindClosestPointOnContour(X, Y, p, &idx)) { rep->GetNthNodeDisplayPosition(idx, pos); rep->ActivateNode(pos); self->Superclass::StartInteraction(); self->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr); self->StartInteraction(); rep->SetCurrentOperationToShift(); // Here rep->StartWidgetInteraction(pos); self->EventCallbackCommand->SetAbortFlag(1); } } if (rep->GetNeedToRender()) { self->Render(); rep->NeedToRenderOff(); } } //------------------------------------------------------------------------------ // Note that if you select the contour at a location that is not moused over // a control point, the scale action makes the closest contour node // jump to the current mouse location. Perhaps we should either // (a) Disable scaling when not moused over a control point // (b) Fix the jumping behaviour by calculating motion vectors from the start // of the interaction. void vtkContourWidget::ScaleContourAction(vtkAbstractWidget* w) { vtkContourWidget* self = reinterpret_cast(w); if (self->WidgetState != vtkContourWidget::Manipulate) return; vtkContourRepresentation* rep = reinterpret_cast(self->WidgetRep); int X = self->Interactor->GetEventPosition()[0]; int Y = self->Interactor->GetEventPosition()[1]; double pos[2]; pos[0] = X; pos[1] = Y; if (rep->ActivateNode(X, Y)) { self->Superclass::StartInteraction(); self->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr); self->StartInteraction(); rep->SetCurrentOperationToScale(); // Here rep->StartWidgetInteraction(pos); self->EventCallbackCommand->SetAbortFlag(1); } else { double p[3]; int idx; if (rep->FindClosestPointOnContour(X, Y, p, &idx)) { rep->GetNthNodeDisplayPosition(idx, pos); rep->ActivateNode(pos); self->Superclass::StartInteraction(); self->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr); self->StartInteraction(); rep->SetCurrentOperationToScale(); // Here rep->StartWidgetInteraction(pos); self->EventCallbackCommand->SetAbortFlag(1); } } if (rep->GetNeedToRender()) { self->Render(); rep->NeedToRenderOff(); } } //------------------------------------------------------------------------------ void vtkContourWidget::DeleteAction(vtkAbstractWidget* w) { vtkContourWidget* self = reinterpret_cast(w); if (self->WidgetState == vtkContourWidget::Start) { return; } vtkContourRepresentation* rep = reinterpret_cast(self->WidgetRep); if (self->WidgetState == vtkContourWidget::Define) { if (rep->DeleteLastNode()) { self->InvokeEvent(vtkCommand::InteractionEvent, nullptr); } } else { int X = self->Interactor->GetEventPosition()[0]; int Y = self->Interactor->GetEventPosition()[1]; rep->ActivateNode(X, Y); if (rep->DeleteActiveNode()) { self->InvokeEvent(vtkCommand::InteractionEvent, nullptr); } rep->ActivateNode(X, Y); int numNodes = rep->GetNumberOfNodes(); if (numNodes < 3) { rep->ClosedLoopOff(); if (numNodes < 2) { self->WidgetState = vtkContourWidget::Define; } } } if (rep->GetNeedToRender()) { self->Render(); rep->NeedToRenderOff(); } } //------------------------------------------------------------------------------ void vtkContourWidget::MoveAction(vtkAbstractWidget* w) { vtkContourWidget* self = reinterpret_cast(w); if (self->WidgetState == vtkContourWidget::Start) { return; } int X = self->Interactor->GetEventPosition()[0]; int Y = self->Interactor->GetEventPosition()[1]; vtkContourRepresentation* rep = reinterpret_cast(self->WidgetRep); if (self->WidgetState == vtkContourWidget::Define) { if (self->FollowCursor || self->ContinuousDraw) { // Have the last node follow the mouse in this case... const int numNodes = rep->GetNumberOfNodes(); // First check if the last node is near the first node, if so, we intend // closing the loop. if (numNodes > 1) { double displayPos[2]; int pixelTolerance = rep->GetPixelTolerance(); int pixelTolerance2 = pixelTolerance * pixelTolerance; rep->GetNthNodeDisplayPosition(0, displayPos); int distance2 = static_cast( (X - displayPos[0]) * (X - displayPos[0]) + (Y - displayPos[1]) * (Y - displayPos[1])); const bool mustCloseLoop = (distance2 < pixelTolerance2 && numNodes > 2) || (self->ContinuousDraw && numNodes > pixelTolerance && distance2 < pixelTolerance2); if (mustCloseLoop != (rep->GetClosedLoop() == 1)) { if (rep->GetClosedLoop()) { // We need to open the closed loop. // We do this by adding a node at (X,Y). If by chance the point // placer says that (X,Y) is invalid, we'll add it at the location // of the first control point (which we know is valid). if (!rep->AddNodeAtDisplayPosition(X, Y)) { double closedLoopPoint[3]; rep->GetNthNodeWorldPosition(0, closedLoopPoint); rep->AddNodeAtDisplayPosition(closedLoopPoint); } rep->ClosedLoopOff(); } else { // We need to close the open loop. Delete the node that's following // the mouse cursor and close the loop between the previous node and // the first node. rep->DeleteLastNode(); rep->ClosedLoopOn(); } } else if (rep->GetClosedLoop() == 0) { if (self->ContinuousDraw && self->ContinuousActive) { rep->AddNodeAtDisplayPosition(X, Y); } else { // If we aren't changing the loop topology, simply update the position // of the latest node to follow the mouse cursor position (X,Y). rep->SetNthNodeDisplayPosition(numNodes - 1, X, Y); } } } } else { return; } } if (rep->GetCurrentOperation() == vtkContourRepresentation::Inactive) { rep->ComputeInteractionState(X, Y); rep->ActivateNode(X, Y); } else { double pos[2]; pos[0] = X; pos[1] = Y; self->WidgetRep->WidgetInteraction(pos); self->InvokeEvent(vtkCommand::InteractionEvent, nullptr); } if (self->WidgetRep->GetNeedToRender()) { self->Render(); self->WidgetRep->NeedToRenderOff(); } } //------------------------------------------------------------------------------ void vtkContourWidget::EndSelectAction(vtkAbstractWidget* w) { vtkContourWidget* self = reinterpret_cast(w); vtkContourRepresentation* rep = reinterpret_cast(self->WidgetRep); if (self->ContinuousDraw) { self->ContinuousActive = 0; } // Do nothing if inactive if (rep->GetCurrentOperation() == vtkContourRepresentation::Inactive) { rep->SetRebuildLocator(true); return; } rep->SetCurrentOperationToInactive(); self->EventCallbackCommand->SetAbortFlag(1); self->Superclass::EndInteraction(); self->InvokeEvent(vtkCommand::EndInteractionEvent, nullptr); // Node picking if (self->AllowNodePicking && self->Interactor->GetControlKey() && self->WidgetState == vtkContourWidget::Manipulate) { rep->ToggleActiveNodeSelected(); } if (self->WidgetRep->GetNeedToRender()) { self->Render(); self->WidgetRep->NeedToRenderOff(); } } //------------------------------------------------------------------------------ void vtkContourWidget::ResetAction(vtkAbstractWidget* w) { vtkContourWidget* self = reinterpret_cast(w); self->Initialize(nullptr); } //------------------------------------------------------------------------------ void vtkContourWidget::Initialize(vtkPolyData* pd, int state, vtkIdList* idList) { if (!this->GetEnabled()) { vtkErrorMacro(<< "Enable widget before initializing"); } if (this->WidgetRep) { vtkContourRepresentation* rep = reinterpret_cast(this->WidgetRep); if (pd == nullptr) { while (rep->DeleteLastNode()) { ; } rep->ClosedLoopOff(); this->Render(); rep->NeedToRenderOff(); rep->VisibilityOff(); this->WidgetState = vtkContourWidget::Start; } else { rep->Initialize(pd, idList); this->WidgetState = (rep->GetClosedLoop() || state == 1) ? vtkContourWidget::Manipulate : vtkContourWidget::Define; } } } //------------------------------------------------------------------------------ void vtkContourWidget::SetAllowNodePicking(vtkTypeBool val) { if (this->AllowNodePicking == val) { return; } this->AllowNodePicking = val; if (this->AllowNodePicking) { vtkContourRepresentation* rep = reinterpret_cast(this->WidgetRep); rep->SetShowSelectedNodes(this->AllowNodePicking); } } //------------------------------------------------------------------------------ void vtkContourWidget::PrintSelf(ostream& os, vtkIndent indent) { // Superclass typedef defined in vtkTypeMacro() found in vtkSetGet.h this->Superclass::PrintSelf(os, indent); os << indent << "WidgetState: " << this->WidgetState << endl; os << indent << "CurrentHandle: " << this->CurrentHandle << endl; os << indent << "AllowNodePicking: " << this->AllowNodePicking << endl; os << indent << "FollowCursor: " << (this->FollowCursor ? "On" : "Off") << endl; os << indent << "ContinuousDraw: " << (this->ContinuousDraw ? "On" : "Off") << endl; }