Medical Imaging Interaction Toolkit  2018.4.99-12ad79a3
Medical Imaging Interaction Toolkit
mitkGizmoInteractor.cpp
Go to the documentation of this file.
1 /*============================================================================
2 
3 The Medical Imaging Interaction Toolkit (MITK)
4 
5 Copyright (c) German Cancer Research Center (DKFZ)
6 All rights reserved.
7 
8 Use of this source code is governed by a 3-clause BSD license that can be
9 found in the LICENSE file.
10 
11 ============================================================================*/
12 
13 #include "mitkGizmoInteractor.h"
14 #include "mitkGizmoMapper2D.h"
15 
16 // MITK includes
17 #include <mitkInteractionConst.h>
19 #include <mitkInternalEvent.h>
21 #include <mitkOperationEvent.h>
22 #include <mitkRotationOperation.h>
23 #include <mitkScaleOperation.h>
24 #include <mitkSurface.h>
25 #include <mitkUndoController.h>
26 #include <mitkVtkMapper.h>
27 
28 // VTK includes
29 #include <vtkCamera.h>
30 #include <vtkInteractorObserver.h>
31 #include <vtkInteractorStyle.h>
32 #include <vtkMath.h>
33 #include <vtkPointData.h>
34 #include <vtkPolyData.h>
35 #include <vtkRenderWindowInteractor.h>
36 #include <vtkVector.h>
37 #include <vtkVectorOperators.h>
38 
39 mitk::GizmoInteractor::GizmoInteractor()
40 {
41  m_ColorForHighlight[0] = 1.0;
42  m_ColorForHighlight[1] = 0.5;
43  m_ColorForHighlight[2] = 0.0;
44  m_ColorForHighlight[3] = 1.0;
45 
46  // TODO if we want to get this configurable, the this is the recipe:
47  // - make the 2D mapper add corresponding properties to control "enabled" and "color"
48  // - make the interactor evaluate those properties
49  // - in an ideal world, modify the state machine on the fly and skip mouse move handling
50 }
51 
52 mitk::GizmoInteractor::~GizmoInteractor()
53 {
54 }
55 
56 void mitk::GizmoInteractor::ConnectActionsAndFunctions()
57 {
58  CONNECT_CONDITION("PickedHandle", HasPickedHandle);
59 
60  CONNECT_FUNCTION("DecideInteraction", DecideInteraction);
61  CONNECT_FUNCTION("MoveAlongAxis", MoveAlongAxis);
62  CONNECT_FUNCTION("RotateAroundAxis", RotateAroundAxis);
63  CONNECT_FUNCTION("MoveFreely", MoveFreely);
64  CONNECT_FUNCTION("ScaleEqually", ScaleEqually);
65  CONNECT_FUNCTION("FeedUndoStack", FeedUndoStack);
66 }
67 
69 {
71 
72  m_Gizmo = dynamic_cast<Gizmo *>(node->GetData());
73 
74  // setup picking from just this object
75  m_Picker.clear();
76 }
77 
79 {
80  if (node && node->GetData())
81  {
82  m_ManipulatedObjectGeometry = node->GetData()->GetGeometry();
83  }
84 }
85 
86 bool mitk::GizmoInteractor::HasPickedHandle(const InteractionEvent *interactionEvent)
87 {
88  auto positionEvent = dynamic_cast<const InteractionPositionEvent *>(interactionEvent);
89  if (positionEvent == nullptr ||
90  m_Gizmo.IsNull() ||
91  m_ManipulatedObjectGeometry.IsNull() ||
92  interactionEvent->GetSender()->GetRenderWindow()->GetNeverRendered())
93  {
94  return false;
95  }
96 
97  if (interactionEvent->GetSender()->GetMapperID() == BaseRenderer::Standard2D)
98  {
99  m_PickedHandle = PickFrom2D(positionEvent);
100  }
101  else
102  {
103  m_PickedHandle = PickFrom3D(positionEvent);
104  }
105 
106  UpdateHandleHighlight();
107 
108  return m_PickedHandle != Gizmo::NoHandle;
109 }
110 
111 void mitk::GizmoInteractor::DecideInteraction(StateMachineAction *, InteractionEvent *interactionEvent)
112 {
113  assert(m_PickedHandle != Gizmo::NoHandle);
114 
115  auto positionEvent = dynamic_cast<const InteractionPositionEvent *>(interactionEvent);
116  if (positionEvent == nullptr)
117  {
118  return;
119  }
120 
121  // if something relevant was picked, we calculate a number of
122  // important points and axes for the upcoming geometry manipulations
123 
124  // note initial state
125  m_InitialClickPosition2D = positionEvent->GetPointerPositionOnScreen();
126  m_InitialClickPosition3D = positionEvent->GetPositionInWorld();
127 
128  auto renderer = positionEvent->GetSender()->GetVtkRenderer();
129  renderer->SetWorldPoint(m_InitialClickPosition3D[0], m_InitialClickPosition3D[1], m_InitialClickPosition3D[2], 0);
130  renderer->WorldToDisplay();
131  m_InitialClickPosition2DZ = renderer->GetDisplayPoint()[2];
132 
133  m_InitialGizmoCenter3D = m_Gizmo->GetCenter();
134  positionEvent->GetSender()->WorldToDisplay(m_InitialGizmoCenter3D, m_InitialGizmoCenter2D);
135 
136  m_InitialManipulatedObjectGeometry = m_ManipulatedObjectGeometry->Clone();
137 
138  switch ( m_PickedHandle ) {
141  case Gizmo::ScaleX:
142  m_AxisOfMovement = m_InitialManipulatedObjectGeometry->GetAxisVector(0);
143  break;
146  case Gizmo::ScaleY:
147  m_AxisOfMovement = m_InitialManipulatedObjectGeometry->GetAxisVector(1);
148  break;
151  case Gizmo::ScaleZ:
152  m_AxisOfMovement = m_InitialManipulatedObjectGeometry->GetAxisVector(2);
153  break;
154  default:
155  break;
156  }
157  m_AxisOfMovement.Normalize();
158  m_AxisOfRotation = m_AxisOfMovement;
159 
160  // for translation: test whether the user clicked into the "object's real" axis direction
161  // or into the other one
162  Vector3D intendedAxis = m_InitialClickPosition3D - m_InitialGizmoCenter3D;
163 
164  if ( intendedAxis * m_AxisOfMovement < 0 ) {
165  m_AxisOfMovement *= -1.0;
166  }
167 
168  // for rotation: test whether the axis of rotation is more looking in the direction
169  // of the camera or in the opposite
170  vtkCamera *camera = renderer->GetActiveCamera();
171  vtkVector3d cameraDirection(camera->GetDirectionOfProjection());
172 
173  double angle_rad = vtkMath::AngleBetweenVectors(cameraDirection.GetData(), m_AxisOfRotation.GetDataPointer());
174 
175  if ( angle_rad < vtkMath::Pi() / 2.0 ) {
176  m_AxisOfRotation *= -1.0;
177  }
178 
179  InternalEvent::Pointer decision;
180  switch (m_PickedHandle)
181  {
185  decision = InternalEvent::New(interactionEvent->GetSender(), this, "StartTranslationAlongAxis");
186  break;
190  decision = InternalEvent::New(interactionEvent->GetSender(), this, "StartRotationAroundAxis");
191  break;
192  case Gizmo::MoveFreely:
193  decision = InternalEvent::New(interactionEvent->GetSender(), this, "MoveFreely");
194  break;
195  case Gizmo::ScaleX:
196  case Gizmo::ScaleY:
197  case Gizmo::ScaleZ:
198  // Implementation note: Why didn't we implement per-axis scaling yet?
199  // When this was implemented, the mitk::ScaleOperation was able to only describe
200  // uniform scaling around a central point. Since we use operations for all modifications
201  // in order to have undo/redo working, any axis-specific scaling should also
202  // use operations.
203  // Consequence: enhance ScaleOperation when there is need to have scaling per axis.
204  decision = InternalEvent::New(interactionEvent->GetSender(), this, "ScaleEqually");
205  break;
206  default:
207  break;
208  }
209 
210  interactionEvent->GetSender()->GetDispatcher()->QueueEvent(decision);
211 }
212 
213 void mitk::GizmoInteractor::MoveAlongAxis(StateMachineAction *, InteractionEvent *interactionEvent)
214 {
215  auto positionEvent = dynamic_cast<const InteractionPositionEvent *>(interactionEvent);
216  if (positionEvent == nullptr)
217  {
218  return;
219  }
220 
221  Vector3D mouseMovement3D = positionEvent->GetPositionInWorld() - m_InitialClickPosition3D;
222  double projectedMouseMovement3D = mouseMovement3D * m_AxisOfMovement;
223  Vector3D movement3D = projectedMouseMovement3D * m_AxisOfMovement;
224 
225  ApplyTranslationToManipulatedObject(movement3D);
227 }
228 
229 void mitk::GizmoInteractor::RotateAroundAxis(StateMachineAction *, InteractionEvent *interactionEvent)
230 {
231  auto positionEvent = dynamic_cast<const InteractionPositionEvent *>(interactionEvent);
232  if (positionEvent == nullptr)
233  {
234  return;
235  }
236 
237  Vector2D originalVector = m_InitialClickPosition2D - m_InitialGizmoCenter2D;
238  Vector2D currentVector = positionEvent->GetPointerPositionOnScreen() - m_InitialGizmoCenter2D;
239 
240  originalVector.Normalize();
241  currentVector.Normalize();
242 
243  double angle_rad = std::atan2(currentVector[1], currentVector[0]) - std::atan2(originalVector[1], originalVector[0]);
244 
245  ApplyRotationToManipulatedObject(vtkMath::DegreesFromRadians(angle_rad));
247 }
248 
249 void mitk::GizmoInteractor::MoveFreely(StateMachineAction *, InteractionEvent *interactionEvent)
250 {
251  auto positionEvent = dynamic_cast<const InteractionPositionEvent *>(interactionEvent);
252  if (positionEvent == nullptr)
253  {
254  return;
255  }
256 
257  Point2D currentPosition2D = positionEvent->GetPointerPositionOnScreen();
258 
259  // re-use the initial z value to really move parallel to the camera plane
260  auto renderer = positionEvent->GetSender()->GetVtkRenderer();
261  renderer->SetDisplayPoint(currentPosition2D[0], currentPosition2D[1], m_InitialClickPosition2DZ);
262  renderer->DisplayToWorld();
263  vtkVector3d worldPointVTK(renderer->GetWorldPoint());
264  Point3D worldPointITK(worldPointVTK.GetData());
265  Vector3D movementITK(worldPointITK - m_InitialClickPosition3D);
266 
267  ApplyTranslationToManipulatedObject(movementITK);
269 }
270 
271 void mitk::GizmoInteractor::ScaleEqually(StateMachineAction *, InteractionEvent *interactionEvent)
272 {
273  auto positionEvent = dynamic_cast<const InteractionPositionEvent *>(interactionEvent);
274  if (positionEvent == nullptr)
275  {
276  return;
277  }
278 
279  Point2D currentPosition2D = positionEvent->GetPointerPositionOnScreen();
280  double relativeSize = (currentPosition2D - m_InitialGizmoCenter2D).GetNorm() /
281  (m_InitialClickPosition2D - m_InitialGizmoCenter2D).GetNorm();
282 
283  ApplyEqualScalingToManipulatedObject(relativeSize);
285 }
286 
287 void mitk::GizmoInteractor::ApplyTranslationToManipulatedObject(const Vector3D &translation)
288 {
289  assert(m_ManipulatedObjectGeometry.IsNotNull());
290 
291  auto manipulatedGeometry = m_InitialManipulatedObjectGeometry->Clone();
292  m_FinalDoOperation.reset(new PointOperation(OpMOVE, translation));
293  if (m_UndoEnabled)
294  {
295  m_FinalUndoOperation.reset(new PointOperation(OpMOVE, -translation));
296  }
297 
298  manipulatedGeometry->ExecuteOperation(m_FinalDoOperation.get());
299  m_ManipulatedObjectGeometry->SetIndexToWorldTransform(manipulatedGeometry->GetIndexToWorldTransform());
300 }
301 
302 void mitk::GizmoInteractor::ApplyEqualScalingToManipulatedObject(double scalingFactor)
303 {
304  assert(m_ManipulatedObjectGeometry.IsNotNull());
305  auto manipulatedGeometry = m_InitialManipulatedObjectGeometry->Clone();
306 
307  m_FinalDoOperation.reset(new ScaleOperation(OpSCALE, scalingFactor - 1.0, m_InitialGizmoCenter3D));
308  if (m_UndoEnabled)
309  {
310  m_FinalUndoOperation.reset(new ScaleOperation(OpSCALE, -(scalingFactor - 1.0), m_InitialGizmoCenter3D));
311  }
312 
313  manipulatedGeometry->ExecuteOperation(m_FinalDoOperation.get());
314  m_ManipulatedObjectGeometry->SetIndexToWorldTransform(manipulatedGeometry->GetIndexToWorldTransform());
315 }
316 
317 void mitk::GizmoInteractor::ApplyRotationToManipulatedObject(double angle_deg)
318 {
319  assert(m_ManipulatedObjectGeometry.IsNotNull());
320 
321  auto manipulatedGeometry = m_InitialManipulatedObjectGeometry->Clone();
322 
323  m_FinalDoOperation.reset(new RotationOperation(OpROTATE, m_InitialGizmoCenter3D, m_AxisOfRotation, angle_deg));
324  if (m_UndoEnabled)
325  {
326  m_FinalUndoOperation.reset(new RotationOperation(OpROTATE, m_InitialGizmoCenter3D, m_AxisOfRotation, -angle_deg));
327  }
328 
329  manipulatedGeometry->ExecuteOperation(m_FinalDoOperation.get());
330  m_ManipulatedObjectGeometry->SetIndexToWorldTransform(manipulatedGeometry->GetIndexToWorldTransform());
331 }
332 
333 void mitk::GizmoInteractor::FeedUndoStack(StateMachineAction *, InteractionEvent *)
334 {
335  if (m_UndoEnabled)
336  {
337  OperationEvent *operationEvent = new OperationEvent(m_ManipulatedObjectGeometry,
338  // OperationEvent will destroy operations!
339  // --> release() and not get()
340  m_FinalDoOperation.release(),
341  m_FinalUndoOperation.release(),
342  "Direct geometry manipulation");
343  mitk::OperationEvent::IncCurrObjectEventId(); // save each modification individually
344  m_UndoController->SetOperationEvent(operationEvent);
345  }
346 }
347 
348 mitk::Gizmo::HandleType mitk::GizmoInteractor::PickFrom2D(const InteractionPositionEvent *positionEvent)
349 {
350  BaseRenderer *renderer = positionEvent->GetSender();
351 
353  auto gizmo_mapper = dynamic_cast<GizmoMapper2D *>(mapper);
354  auto &picker = m_Picker[renderer];
355 
356  if (picker == nullptr)
357  {
358  picker = vtkSmartPointer<vtkCellPicker>::New();
359  picker->SetTolerance(0.005);
360 
361  if (gizmo_mapper)
362  { // doing this each time is bizarre
363  picker->AddPickList(gizmo_mapper->GetVtkProp(renderer));
364  picker->PickFromListOn();
365  }
366  }
367 
368  auto displayPosition = positionEvent->GetPointerPositionOnScreen();
369  picker->Pick(displayPosition[0], displayPosition[1], 0, positionEvent->GetSender()->GetVtkRenderer());
370 
371  vtkIdType pickedPointID = picker->GetPointId();
372  if (pickedPointID == -1)
373  {
374  return Gizmo::NoHandle;
375  }
376 
377  vtkPolyData *polydata = gizmo_mapper->GetVtkPolyData(renderer);
378 
379  if (polydata && polydata->GetPointData() && polydata->GetPointData()->GetScalars())
380  {
381  double dataValue = polydata->GetPointData()->GetScalars()->GetTuple1(pickedPointID);
382  return m_Gizmo->GetHandleFromPointDataValue(dataValue);
383  }
384 
385  return Gizmo::NoHandle;
386 }
387 
388 mitk::Gizmo::HandleType mitk::GizmoInteractor::PickFrom3D(const InteractionPositionEvent *positionEvent)
389 {
390  BaseRenderer *renderer = positionEvent->GetSender();
391  auto &picker = m_Picker[renderer];
392  if (picker == nullptr)
393  {
394  picker = vtkSmartPointer<vtkCellPicker>::New();
395  picker->SetTolerance(0.005);
397  auto vtk_mapper = dynamic_cast<VtkMapper *>(mapper);
398  if (vtk_mapper)
399  { // doing this each time is bizarre
400  picker->AddPickList(vtk_mapper->GetVtkProp(renderer));
401  picker->PickFromListOn();
402  }
403  }
404 
405  auto displayPosition = positionEvent->GetPointerPositionOnScreen();
406  picker->Pick(displayPosition[0], displayPosition[1], 0, positionEvent->GetSender()->GetVtkRenderer());
407 
408  vtkIdType pickedPointID = picker->GetPointId();
409  if (pickedPointID == -1)
410  {
411  return Gizmo::NoHandle;
412  }
413 
414  // _something_ picked
415  return m_Gizmo->GetHandleFromPointID(pickedPointID);
416 }
417 
418 void mitk::GizmoInteractor::UpdateHandleHighlight()
419 {
420  if (m_HighlightedHandle != m_PickedHandle) {
421 
422  auto node = GetDataNode();
423  if (node == nullptr) return;
424 
425  auto base_prop = node->GetProperty("LookupTable");
426  if (base_prop == nullptr) return;
427 
428  auto lut_prop = dynamic_cast<LookupTableProperty*>(base_prop);
429  if (lut_prop == nullptr) return;
430 
431  auto lut = lut_prop->GetLookupTable();
432  if (lut == nullptr) return;
433 
434  // Table size is expected to constructed as one entry per gizmo-part enum value
435  assert(lut->GetVtkLookupTable()->GetNumberOfTableValues() > std::max(m_PickedHandle, m_HighlightedHandle));
436 
437  // Reset previously overwritten color
438  if (m_HighlightedHandle != Gizmo::NoHandle)
439  {
440  lut->SetTableValue(m_HighlightedHandle, m_ColorReplacedByHighlight);
441  }
442 
443  // Overwrite currently highlighted color
444  if (m_PickedHandle != Gizmo::NoHandle)
445  {
446  lut->GetTableValue(m_PickedHandle, m_ColorReplacedByHighlight);
447  lut->SetTableValue(m_PickedHandle, m_ColorForHighlight);
448  }
449 
450  // Mark node modified to allow repaint
451  node->Modified();
453 
454  m_HighlightedHandle = m_PickedHandle;
455  }
456 }
virtual void SetTableValue(int index, double rgba[4])
SetTableValue convenience method wrapping the vtkLookupTable::SetTableValue() method.
void SetGizmoNode(DataNode *node)
Super class for all position events.
void SetManipulatedObjectNode(DataNode *node)
vtkRenderer * GetVtkRenderer() const
Organizes the rendering process.
Constants for most interaction classes, due to the generic StateMachines.
bool SetOperationEvent(UndoStackItem *operationEvent)
Base class of all Vtk Mappers in order to display primitives by exploiting Vtk functionality.
Definition: mitkVtkMapper.h:48
DataNode * GetDataNode() const
BaseRenderer * GetSender() const
HandleType
Names for the different parts of the gizmo.
Definition: mitkGizmo.h:61
BaseData * GetData() const
Get the data object (instance of BaseData, e.g., an Image) managed by this DataNode.
The LookupTableProperty class Property to associate mitk::LookupTable to an mitk::DataNode.
static RenderingManager * GetInstance()
Represents an action, that is executed after a certain event (in statemachine-mechanism) TODO: implem...
virtual MapperSlotId GetMapperID()
Get the MapperSlotId to use.
Operation that handles all actions on one Point.
virtual LookupTable * GetLookupTable()
static T max(T x, T y)
Definition: svm.cpp:56
mitk::Mapper * GetMapper(MapperSlotId id) const
vtkRenderWindow * GetRenderWindow() const
Access the RenderWindow into which this renderer renders.
virtual void SetDataNode(DataNode *dataNode)
#define CONNECT_CONDITION(a, f)
void ForceImmediateUpdateAll(RequestType type=REQUEST_UPDATE_ALL)
The ScaleOperation is an operation to scale any mitk::BaseGeometry.
Dispatcher::Pointer GetDispatcher() const
Returns the Dispatcher which handles Events for this BaseRenderer.
static Pointer New(BaseRenderer *_arga, DataInteractor *_argb, const std::string &_argc)
static void IncCurrObjectEventId()
Increases the current ObjectEventId For example if a button click generates operations the ObjectEven...
#define CONNECT_FUNCTION(a, f)
Operation, that holds everything necessary for an rotation operation on mitk::BaseData.
mitk::BaseGeometry * GetGeometry(int t=0) const
Return the geometry, which is a TimeGeometry, of the data as non-const pointer.
Definition: mitkBaseData.h:138
void RequestUpdateAll(RequestType type=REQUEST_UPDATE_ALL)
Represents a pair of operations: undo and the according redo.
Class for nodes of the DataTree.
Definition: mitkDataNode.h:64