text: /*============================================================================
  
  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.
  
  ============================================================================*/
  
  #include "mitkGizmoMapper2D.h"
  
  #include "mitkGizmo.h"
  
  // MITK includes
  #include <mitkBaseRenderer.h>
  #include <mitkCameraController.h>
  #include <mitkLookupTableProperty.h>
  #include <mitkVtkInterpolationProperty.h>
  #include <mitkVtkRepresentationProperty.h>
  #include <mitkVtkScalarModeProperty.h>
  
  // VTK includes
  #include <vtkAppendPolyData.h>
  #include <vtkCamera.h>
  #include <vtkCellArray.h>
  #include <vtkCharArray.h>
  #include <vtkConeSource.h>
  #include <vtkMath.h>
  #include <vtkPointData.h>
  #include <vtkSphereSource.h>
  #include <vtkVectorOperators.h>
  
  mitk::GizmoMapper2D::LocalStorage::LocalStorage()
  {
    m_VtkPolyDataMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
    m_Actor = vtkSmartPointer<vtkActor>::New();
    m_Actor->SetMapper(m_VtkPolyDataMapper);
  }
  
  const mitk::Gizmo *mitk::GizmoMapper2D::GetInput()
  {
    return static_cast<const Gizmo *>(GetDataNode()->GetData());
  }
  
  void mitk::GizmoMapper2D::ResetMapper(mitk::BaseRenderer *renderer)
  {
    LocalStorage *ls = m_LSH.GetLocalStorage(renderer);
    ls->m_Actor->VisibilityOff();
  }
  
  namespace
  {
    //! Helper method: will assign given value to all points in given polydata object.
    void AssignScalarValueTo(vtkPolyData *polydata, char value)
    {
      vtkSmartPointer<vtkCharArray> pointData = vtkSmartPointer<vtkCharArray>::New();
  
      int numberOfPoints = polydata->GetNumberOfPoints();
      pointData->SetNumberOfComponents(1);
      pointData->SetNumberOfTuples(numberOfPoints);
      pointData->FillComponent(0, value);
      polydata->GetPointData()->SetScalars(pointData);
    }
  
    //! Helper method: will create a vtkPolyData representing a disk
    //! around center, inside the plane defined by viewRight and viewUp,
    //! and with the given radius.
    vtkSmartPointer<vtkPolyData> Create2DDisk(mitk::Vector3D viewRight,
                                              mitk::Vector3D viewUp,
                                              mitk::Point3D center,
                                              double radius)
  
    {
      // build the axis itself (as a tube around the line defining the axis)
      vtkSmartPointer<vtkPolyData> disk = vtkSmartPointer<vtkPolyData>::New();
  
      mitk::Vector3D ringPointer;
      unsigned int numberOfRingPoints = 36;
      vtkSmartPointer<vtkPoints> ringPoints = vtkSmartPointer<vtkPoints>::New();
      vtkSmartPointer<vtkCellArray> ringPoly = vtkSmartPointer<vtkCellArray>::New();
      ringPoly->InsertNextCell(numberOfRingPoints + 1);
      for (unsigned int segment = 0; segment < numberOfRingPoints; ++segment)
      {
        double x = std::cos((double)(segment) / (double)numberOfRingPoints * 2.0 * vtkMath::Pi());
        double y = std::sin((double)(segment) / (double)numberOfRingPoints * 2.0 * vtkMath::Pi());
  
        ringPointer = viewRight * x + viewUp * y;
  
        ringPoints->InsertPoint(segment, (center + ringPointer * radius).GetDataPointer());
        ringPoly->InsertCellPoint(segment);
      }
      ringPoly->InsertCellPoint(0);
  
      disk->SetPoints(ringPoints);
      disk->SetPolys(ringPoly);
  
      return disk;
    }
  
    //! Helper method: will create a vtkPolyData representing a 2D arrow
    //! that is oriented from arrowStart to arrowTip, orthogonal
    //! to camera direction. The arrow tip will contain scalar values
    //! of vertexValueScale, the arrow shaft will contain scalar values
    //! of vertexValueMove. Those values are used for picking during interaction.
    vtkSmartPointer<vtkPolyData> Create2DArrow(mitk::Vector3D cameraDirection,
                                               mitk::Point3D arrowStart,
                                               mitk::Point3D arrowTip,
                                               int vertexValueMove,
                                               int vertexValueScale)
    {
      mitk::Vector3D arrowDirection = arrowTip - arrowStart;
      mitk::Vector3D arrowOrthogonal = itk::CrossProduct(cameraDirection, arrowDirection);
      arrowOrthogonal.Normalize();
  
      double triangleFraction = 0.2;
  
      vtkSmartPointer<vtkPolyData> arrow = vtkSmartPointer<vtkPolyData>::New();
      vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
      // shaft : points 0, 1
      points->InsertPoint(0, arrowStart.GetDataPointer());
      points->InsertPoint(1, (arrowStart + arrowDirection * (1.0 - triangleFraction)).GetDataPointer());
  
      // tip : points 2, 3, 4
      points->InsertPoint(2, arrowTip.GetDataPointer());
      points->InsertPoint(3,
                          (arrowStart + (1.0 - triangleFraction) * arrowDirection +
                           arrowOrthogonal * (0.5 * triangleFraction * arrowDirection.GetNorm()))
                            .GetDataPointer());
      points->InsertPoint(4,
                          (arrowStart + (1.0 - triangleFraction) * arrowDirection -
                           arrowOrthogonal * (0.5 * triangleFraction * arrowDirection.GetNorm()))
                            .GetDataPointer());
      arrow->SetPoints(points);
  
      // define line connection for shaft
      vtkSmartPointer<vtkCellArray> lines = vtkSmartPointer<vtkCellArray>::New();
      vtkIdType shaftLinePoints[] = {0, 1};
      lines->InsertNextCell(2, shaftLinePoints);
      arrow->SetLines(lines);
  
      // define polygon for triangle
      vtkSmartPointer<vtkCellArray> polys = vtkSmartPointer<vtkCellArray>::New();
      vtkIdType tipLinePoints[] = {2, 3, 4};
      polys->InsertNextCell(3, tipLinePoints);
      arrow->SetPolys(polys);
  
      // assign scalar values
      vtkSmartPointer<vtkCharArray> pointData = vtkSmartPointer<vtkCharArray>::New();
      pointData->SetNumberOfComponents(1);
      pointData->SetNumberOfTuples(5);
      pointData->FillComponent(0, vertexValueScale);
      pointData->SetTuple1(0, vertexValueMove);
      pointData->SetTuple1(1, vertexValueMove);
      arrow->GetPointData()->SetScalars(pointData);
  
      return arrow;
    }
  }
  
  vtkPolyData *mitk::GizmoMapper2D::GetVtkPolyData(mitk::BaseRenderer *renderer)
  {
    return m_LSH.GetLocalStorage(renderer)->m_VtkPolyDataMapper->GetInput();
  }
  
  void mitk::GizmoMapper2D::GenerateDataForRenderer(mitk::BaseRenderer *renderer)
  {
    auto gizmo = GetInput();
    auto node = GetDataNode();
  
    LocalStorage *ls = m_LSH.GetLocalStorage(renderer);
  
    // check if something important has changed and we need to re-render
    if ((ls->m_LastUpdateTime >= node->GetMTime()) && (ls->m_LastUpdateTime >= gizmo->GetPipelineMTime()) &&
        (ls->m_LastUpdateTime >= renderer->GetCameraController()->GetMTime()) &&
        (ls->m_LastUpdateTime >= renderer->GetCurrentWorldPlaneGeometryUpdateTime()) &&
        (ls->m_LastUpdateTime >= renderer->GetCurrentWorldPlaneGeometry()->GetMTime()) &&
        (ls->m_LastUpdateTime >= node->GetPropertyList()->GetMTime()) &&
        (ls->m_LastUpdateTime >= node->GetPropertyList(renderer)->GetMTime()))
    {
      return;
    }
  
    ls->m_LastUpdateTime.Modified();
  
    // some special handling around visibility: let two properties steer
    // visibility in 2D instead of many renderer specific "visible" properties
    bool visible2D = true;
    this->GetDataNode()->GetBoolProperty("show in 2D", visible2D);
    if (!visible2D && renderer->GetMapperID() == BaseRenderer::Standard2D)
    {
      ls->m_Actor->VisibilityOff();
      return;
    }
    else
    {
      ls->m_Actor->VisibilityOn();
    }
  
    auto camera = renderer->GetVtkRenderer()->GetActiveCamera();
  
    auto plane = renderer->GetCurrentWorldPlaneGeometry();
  
    Point3D gizmoCenterView = plane->ProjectPointOntoPlane(gizmo->GetCenter());
  
    Vector3D viewUp;
    camera->GetViewUp(viewUp.GetDataPointer());
    Vector3D cameraDirection;
    camera->GetDirectionOfProjection(cameraDirection.GetDataPointer());
    Vector3D viewRight = itk::CrossProduct(viewUp, cameraDirection);
  
    auto appender = vtkSmartPointer<vtkAppendPolyData>::New();
  
    double diagonal = std::min(renderer->GetSizeX(), renderer->GetSizeY()) * renderer->GetScaleFactorMMPerDisplayUnit();
    double arrowLength = 0.3 * diagonal; // fixed in relation to window size
    auto disk = Create2DDisk(viewRight, viewUp, gizmoCenterView - cameraDirection, 0.1 * arrowLength);
    AssignScalarValueTo(disk, Gizmo::MoveFreely);
    appender->AddInputData(disk);
  
    // loop over directions -1 and +1 for arrows
    for (double direction = -1.0; direction < 2.0; direction += 2.0)
    {
      auto axisX =
        Create2DArrow(cameraDirection,
                      gizmoCenterView,
                      plane->ProjectPointOntoPlane(gizmo->GetCenter() + (gizmo->GetAxisX() * arrowLength) * direction),
                      Gizmo::MoveAlongAxisX,
                      Gizmo::ScaleX);
      appender->AddInputData(axisX);
  
      auto axisY =
        Create2DArrow(cameraDirection,
                      gizmoCenterView,
                      plane->ProjectPointOntoPlane(gizmo->GetCenter() + (gizmo->GetAxisY() * arrowLength) * direction),
                      Gizmo::MoveAlongAxisY,
                      Gizmo::ScaleY);
      appender->AddInputData(axisY);
  
      auto axisZ =
        Create2DArrow(cameraDirection,
                      gizmoCenterView,
                      plane->ProjectPointOntoPlane(gizmo->GetCenter() + (gizmo->GetAxisZ() * arrowLength) * direction),
                      Gizmo::MoveAlongAxisZ,
                      Gizmo::ScaleZ);
      appender->AddInputData(axisZ);
    }
  
    ls->m_VtkPolyDataMapper->SetInputConnection(appender->GetOutputPort());
  
    ApplyVisualProperties(renderer);
  }
  
  void mitk::GizmoMapper2D::ApplyVisualProperties(BaseRenderer *renderer)
  {
    LocalStorage *ls = m_LSH.GetLocalStorage(renderer);
  
    float lineWidth = 3.0f;
    ls->m_Actor->GetProperty()->SetLineWidth(lineWidth);
  
    mitk::LookupTableProperty::Pointer lookupTableProp;
    this->GetDataNode()->GetProperty(lookupTableProp, "LookupTable", renderer);
    if (lookupTableProp.IsNotNull())
    {
      ls->m_VtkPolyDataMapper->SetLookupTable(lookupTableProp->GetLookupTable()->GetVtkLookupTable());
    }
  
    bool scalarVisibility = false;
    this->GetDataNode()->GetBoolProperty("scalar visibility", scalarVisibility);
    ls->m_VtkPolyDataMapper->SetScalarVisibility((scalarVisibility ? 1 : 0));
  
    if (scalarVisibility)
    {
      mitk::VtkScalarModeProperty *scalarMode;
      if (this->GetDataNode()->GetProperty(scalarMode, "scalar mode", renderer))
        ls->m_VtkPolyDataMapper->SetScalarMode(scalarMode->GetVtkScalarMode());
      else
        ls->m_VtkPolyDataMapper->SetScalarModeToDefault();
  
      bool colorMode = false;
      this->GetDataNode()->GetBoolProperty("color mode", colorMode);
      ls->m_VtkPolyDataMapper->SetColorMode((colorMode ? 1 : 0));
  
      double scalarsMin = 0;
      this->GetDataNode()->GetDoubleProperty("ScalarsRangeMinimum", scalarsMin, renderer);
  
      double scalarsMax = 1.0;
      this->GetDataNode()->GetDoubleProperty("ScalarsRangeMaximum", scalarsMax, renderer);
  
      ls->m_VtkPolyDataMapper->SetScalarRange(scalarsMin, scalarsMax);
    }
  }
  
  void mitk::GizmoMapper2D::SetDefaultProperties(mitk::DataNode *node,
                                                 mitk::BaseRenderer *renderer /*= nullptr*/,
                                                 bool /*= false*/)
  {
    node->SetProperty("color", ColorProperty::New(0.3, 0.3, 0.3)); // a little lighter than the
                                                                   // "plane widgets" of
                                                                   // QmitkStdMultiWidget
    node->SetProperty("scalar visibility", BoolProperty::New(true), renderer);
    node->SetProperty("ScalarsRangeMinimum", DoubleProperty::New(0), renderer);
    node->SetProperty("ScalarsRangeMaximum", DoubleProperty::New((int)Gizmo::NoHandle), renderer);
  
    double colorMoveFreely[] = {1, 0, 0, 1}; // RGBA
    double colorAxisX[] = {0.753, 0, 0, 1};  // colors copied from QmitkStdMultiWidget to
    double colorAxisY[] = {0, 0.69, 0, 1};   // look alike
    double colorAxisZ[] = {0, 0.502, 1, 1};
    double colorInactive[] = {0.7, 0.7, 0.7, 1};
  
    // build a nice color table
    vtkSmartPointer<vtkLookupTable> lut = vtkSmartPointer<vtkLookupTable>::New();
    lut->SetNumberOfTableValues((int)Gizmo::NoHandle + 1);
    lut->SetTableRange(0, (int)Gizmo::NoHandle);
    lut->SetTableValue(Gizmo::MoveFreely, colorMoveFreely);
    lut->SetTableValue(Gizmo::MoveAlongAxisX, colorAxisX);
    lut->SetTableValue(Gizmo::MoveAlongAxisY, colorAxisY);
    lut->SetTableValue(Gizmo::MoveAlongAxisZ, colorAxisZ);
    lut->SetTableValue(Gizmo::RotateAroundAxisX, colorAxisX);
    lut->SetTableValue(Gizmo::RotateAroundAxisY, colorAxisY);
    lut->SetTableValue(Gizmo::RotateAroundAxisZ, colorAxisZ);
    lut->SetTableValue(Gizmo::ScaleX, colorAxisX);
    lut->SetTableValue(Gizmo::ScaleY, colorAxisY);
    lut->SetTableValue(Gizmo::ScaleZ, colorAxisZ);
    lut->SetTableValue(Gizmo::NoHandle, colorInactive);
  
    mitk::LookupTable::Pointer mlut = mitk::LookupTable::New();
    mlut->SetVtkLookupTable(lut);
  
    mitk::LookupTableProperty::Pointer lutProp = mitk::LookupTableProperty::New();
    lutProp->SetLookupTable(mlut);
    node->SetProperty("LookupTable", lutProp, renderer);
  
    node->SetProperty("helper object", BoolProperty::New(true), renderer);
    node->SetProperty("visible", BoolProperty::New(true), renderer);
    node->SetProperty("show in 2D", BoolProperty::New(true), renderer);
    // no "show in 3D" because this would require a specialized mapper for gizmos in 3D
  }
  