// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkAffineRepresentation2D.h"
#include "vtkActor2D.h"
#include "vtkCellArray.h"
#include "vtkCoordinate.h"
#include "vtkInteractorObserver.h"
#include "vtkLeaderActor2D.h"
#include "vtkLine.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPoints.h"
#include "vtkPolyDataAlgorithm.h"
#include "vtkPolyDataMapper2D.h"
#include "vtkProperty2D.h"
#include "vtkRenderer.h"
#include "vtkStringFormatter.h"
#include "vtkTextMapper.h"
#include "vtkTextProperty.h"
#include "vtkTransform.h"
#include "vtkWindow.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkAffineRepresentation2D);

vtkCxxSetObjectMacro(vtkAffineRepresentation2D, Property, vtkProperty2D);
vtkCxxSetObjectMacro(vtkAffineRepresentation2D, SelectedProperty, vtkProperty2D);
vtkCxxSetObjectMacro(vtkAffineRepresentation2D, TextProperty, vtkTextProperty);

#define VTK_CIRCLE_RESOLUTION 64

//------------------------------------------------------------------------------
vtkAffineRepresentation2D::vtkAffineRepresentation2D()
{
  // It's best to have a small tolerance
  this->Tolerance = 3;

  // Initialize state
  this->InteractionState = vtkAffineRepresentation::Outside;

  // The width of the widget
  this->DisplayText = 1;
  this->BoxWidth = 100;
  this->CircleWidth = static_cast<int>(0.75 * this->BoxWidth);
  this->AxesWidth = static_cast<int>(0.60 * this->BoxWidth);
  this->CurrentWidth = 0.0;
  this->CurrentRadius = 0.0;
  this->CurrentAxesWidth = 0.0;

  // Keep track of transformations
  this->DisplayOrigin[0] = this->DisplayOrigin[1] = this->DisplayOrigin[2] = 0.0;
  this->Origin[0] = this->Origin[1] = this->Origin[2] = 0.0;

  // Create properties
  this->CreateDefaultProperties();

  // Text label
  this->TextMapper = vtkTextMapper::New();
  this->TextMapper->SetTextProperty(this->TextProperty);
  this->TextMapper->SetInput("foo");
  this->TextActor = vtkActor2D::New();
  this->TextActor->SetMapper(this->TextMapper);
  this->TextActor->VisibilityOff();

  // Box
  this->BoxPoints = vtkPoints::New();
  this->BoxPoints->SetNumberOfPoints(4);
  this->BoxCellArray = vtkCellArray::New();
  this->BoxCellArray->AllocateEstimate(1, 4);
  this->BoxCellArray->InsertNextCell(5);
  this->BoxCellArray->InsertCellPoint(0);
  this->BoxCellArray->InsertCellPoint(1);
  this->BoxCellArray->InsertCellPoint(2);
  this->BoxCellArray->InsertCellPoint(3);
  this->BoxCellArray->InsertCellPoint(0);
  this->Box = vtkPolyData::New();
  this->Box->SetPoints(this->BoxPoints);
  this->Box->SetLines(this->BoxCellArray);
  this->BoxMapper = vtkPolyDataMapper2D::New();
  this->BoxMapper->SetInputData(this->Box);
  this->BoxActor = vtkActor2D::New();
  this->BoxActor->SetMapper(this->BoxMapper);
  this->BoxActor->SetProperty(this->Property);

  this->HBoxPoints = vtkPoints::New();
  this->HBoxPoints->SetNumberOfPoints(4);
  this->HBoxCellArray = vtkCellArray::New();
  this->HBoxCellArray->AllocateEstimate(1, 4);
  this->HBoxCellArray->InsertNextCell(5);
  this->HBoxCellArray->InsertCellPoint(0);
  this->HBoxCellArray->InsertCellPoint(1);
  this->HBoxCellArray->InsertCellPoint(2);
  this->HBoxCellArray->InsertCellPoint(3);
  this->HBoxCellArray->InsertCellPoint(0);
  this->HBox = vtkPolyData::New();
  this->HBox->SetPoints(this->HBoxPoints);
  this->HBox->SetLines(this->HBoxCellArray);
  this->HBoxMapper = vtkPolyDataMapper2D::New();
  this->HBoxMapper->SetInputData(this->HBox);
  this->HBoxActor = vtkActor2D::New();
  this->HBoxActor->SetMapper(this->HBoxMapper);
  this->HBoxActor->VisibilityOff();
  this->HBoxActor->SetProperty(this->SelectedProperty);

  // Circle
  this->CirclePoints = vtkPoints::New();
  this->CirclePoints->SetNumberOfPoints(VTK_CIRCLE_RESOLUTION);
  this->CircleCellArray = vtkCellArray::New();
  this->CircleCellArray->AllocateEstimate(1, VTK_CIRCLE_RESOLUTION + 1);
  this->Circle = vtkPolyData::New();
  this->Circle->SetPoints(this->CirclePoints);
  this->Circle->SetLines(this->CircleCellArray);
  this->CircleMapper = vtkPolyDataMapper2D::New();
  this->CircleMapper->SetInputData(this->Circle);
  this->CircleActor = vtkActor2D::New();
  this->CircleActor->SetMapper(this->CircleMapper);
  this->CircleActor->SetProperty(this->Property);

  this->HCirclePoints = vtkPoints::New();
  this->HCircleCellArray = vtkCellArray::New();
  this->HCircleCellArray->AllocateEstimate(1, VTK_CIRCLE_RESOLUTION + 1);
  this->HCircle = vtkPolyData::New();
  this->HCircle->SetPoints(this->HCirclePoints);
  this->HCircle->SetLines(this->HCircleCellArray);
  this->HCircleMapper = vtkPolyDataMapper2D::New();
  this->HCircleMapper->SetInputData(this->HCircle);
  this->HCircleActor = vtkActor2D::New();
  this->HCircleActor->SetMapper(this->HCircleMapper);
  this->HCircleActor->VisibilityOff();
  this->HCircleActor->SetProperty(this->SelectedProperty);

  // Translation axes
  this->XAxis = vtkLeaderActor2D::New();
  this->XAxis->GetPositionCoordinate()->SetCoordinateSystemToDisplay();
  this->XAxis->GetPosition2Coordinate()->SetCoordinateSystemToDisplay();
  this->XAxis->SetArrowStyleToFilled();
  this->XAxis->SetProperty(this->Property);
  this->XAxis->SetMaximumArrowSize(12);

  this->YAxis = vtkLeaderActor2D::New();
  this->YAxis->GetPositionCoordinate()->SetCoordinateSystemToDisplay();
  this->YAxis->GetPosition2Coordinate()->SetCoordinateSystemToDisplay();
  this->YAxis->SetArrowStyleToFilled();
  this->YAxis->SetProperty(this->Property);
  this->YAxis->SetMaximumArrowSize(12);

  this->HXAxis = vtkLeaderActor2D::New();
  this->HXAxis->GetPositionCoordinate()->SetCoordinateSystemToDisplay();
  this->HXAxis->GetPosition2Coordinate()->SetCoordinateSystemToDisplay();
  this->HXAxis->SetArrowStyleToFilled();
  this->HXAxis->SetProperty(this->SelectedProperty);
  this->HXAxis->SetMaximumArrowSize(12);
  this->HXAxis->VisibilityOff();

  this->HYAxis = vtkLeaderActor2D::New();
  this->HYAxis->GetPositionCoordinate()->SetCoordinateSystemToDisplay();
  this->HYAxis->GetPosition2Coordinate()->SetCoordinateSystemToDisplay();
  this->HYAxis->SetArrowStyleToFilled();
  this->HYAxis->SetProperty(this->SelectedProperty);
  this->HYAxis->SetMaximumArrowSize(12);
  this->HYAxis->VisibilityOff();

  // Transformation matrix
  this->CurrentTransform = vtkTransform::New();
  this->TotalTransform = vtkTransform::New();

  this->CurrentTranslation[0] = 0.0;
  this->CurrentTranslation[1] = 0.0;
  this->CurrentTranslation[2] = 0.0;
  this->CurrentAngle = 0.0;
  this->CurrentScale[0] = 1.0;
  this->CurrentScale[1] = 1.0;
  this->CurrentShear[0] = 0.0;
  this->CurrentShear[1] = 0.0;
}

//------------------------------------------------------------------------------
vtkAffineRepresentation2D::~vtkAffineRepresentation2D()
{
  this->Property->Delete();
  this->SelectedProperty->Delete();
  this->TextProperty->Delete();

  this->TextMapper->Delete();
  this->TextActor->Delete();

  this->BoxPoints->Delete();
  this->BoxCellArray->Delete();
  this->Box->Delete();
  this->BoxMapper->Delete();
  this->BoxActor->Delete();

  this->HBoxPoints->Delete();
  this->HBoxCellArray->Delete();
  this->HBox->Delete();
  this->HBoxMapper->Delete();
  this->HBoxActor->Delete();

  this->CirclePoints->Delete();
  this->CircleCellArray->Delete();
  this->Circle->Delete();
  this->CircleMapper->Delete();
  this->CircleActor->Delete();

  this->HCirclePoints->Delete();
  this->HCircleCellArray->Delete();
  this->HCircle->Delete();
  this->HCircleMapper->Delete();
  this->HCircleActor->Delete();

  this->XAxis->Delete();
  this->YAxis->Delete();
  this->HXAxis->Delete();
  this->HYAxis->Delete();

  this->CurrentTransform->Delete();
  this->TotalTransform->Delete();
}

//------------------------------------------------------------------------------
void vtkAffineRepresentation2D::GetTransform(vtkTransform* t)
{
  this->CurrentTransform->Identity();
  this->CurrentTransform->Translate(this->Origin[0], this->Origin[1], this->Origin[2]);
  if (this->InteractionState != vtkAffineRepresentation::MoveOrigin &&
    this->InteractionState != vtkAffineRepresentation::MoveOriginX &&
    this->InteractionState != vtkAffineRepresentation::MoveOriginY)
  {
    this->CurrentTransform->Translate(
      this->CurrentTranslation[0], this->CurrentTranslation[1], this->CurrentTranslation[2]);
  }

  this->ApplyShear();
  this->CurrentTransform->RotateZ(vtkMath::DegreesFromRadians(this->CurrentAngle));
  this->CurrentTransform->Scale(this->CurrentScale[0], this->CurrentScale[1], 1.0);
  this->CurrentTransform->Translate(-this->Origin[0], -this->Origin[1], -this->Origin[2]);

  t->DeepCopy(this->CurrentTransform);
  t->Concatenate(this->TotalTransform);
}

//------------------------------------------------------------------------------
void vtkAffineRepresentation2D::PlaceWidget(double bounds[6])
{
  this->Origin[0] = (bounds[1] + bounds[0]) / 2.0;
  this->Origin[1] = (bounds[3] + bounds[2]) / 2.0;
  this->Origin[2] = (bounds[5] + bounds[4]) / 2.0;

  this->TotalTransform->Identity();
}

//------------------------------------------------------------------------------
void vtkAffineRepresentation2D::SetOrigin(double ox, double oy, double oz)
{
  if (this->Origin[0] != ox || this->Origin[1] != oy || this->Origin[2] != oz)
  {
    this->Origin[0] = ox;
    this->Origin[1] = oy;
    this->Origin[2] = oz;

    this->BuildRepresentation();
    this->Modified();
  }
}

//------------------------------------------------------------------------------
int vtkAffineRepresentation2D::ComputeInteractionState(int X, int Y, int modify)
{
  double p[3], tol = static_cast<double>(this->Tolerance);
  p[0] = static_cast<double>(X);
  p[1] = static_cast<double>(Y);
  p[2] = 0.0;
  this->InteractionState = vtkAffineRepresentation::Outside;

  // Box---------------------------------------------------------------
  double p1[3], p2[3], p3[3], p4[3];
  this->BoxPoints->GetPoint(0, p1); // min corner
  this->BoxPoints->GetPoint(2, p3); // max corner

  int e0 = (p[1] >= (p1[1] - tol) && p[1] <= (p1[1] + tol));
  int e1 = (p[0] >= (p3[0] - tol) && p[0] <= (p3[0] + tol));
  int e2 = (p[1] >= (p3[1] - tol) && p[1] <= (p3[1] + tol));
  int e3 = (p[0] >= (p1[0] - tol) && p[0] <= (p1[0] + tol));

  // Points
  if (e0 && e1)
  {
    this->InteractionState = vtkAffineRepresentation::ScaleSE;
  }
  else if (e1 && e2)
  {
    this->InteractionState = vtkAffineRepresentation::ScaleNE;
  }
  else if (e2 && e3)
  {
    this->InteractionState = vtkAffineRepresentation::ScaleNW;
  }
  else if (e3 && e0)
  {
    this->InteractionState = vtkAffineRepresentation::ScaleSW;
  }

  // Edges
  else if (e0)
  {
    if (!modify)
    {
      this->InteractionState = vtkAffineRepresentation::ScaleSEdge;
    }
    else
    {
      this->InteractionState = vtkAffineRepresentation::ShearSEdge;
    }
  }
  else if (e1)
  {
    if (!modify)
    {
      this->InteractionState = vtkAffineRepresentation::ScaleEEdge;
    }
    else
    {
      this->InteractionState = vtkAffineRepresentation::ShearEEdge;
    }
  }
  else if (e2)
  {
    if (!modify)
    {
      this->InteractionState = vtkAffineRepresentation::ScaleNEdge;
    }
    else
    {
      this->InteractionState = vtkAffineRepresentation::ShearNEdge;
    }
  }
  else if (e3)
  {
    if (!modify)
    {
      this->InteractionState = vtkAffineRepresentation::ScaleWEdge;
    }
    else
    {
      this->InteractionState = vtkAffineRepresentation::ShearWEdge;
    }
  }

  // Return if necessary
  if (this->InteractionState != vtkAffineRepresentation::Outside)
  {
    return this->InteractionState;
  }

  // Circle---------------------------------------------------------------
  double radius = sqrt((p[0] - this->DisplayOrigin[0]) * (p[0] - this->DisplayOrigin[0]) +
    (p[1] - this->DisplayOrigin[1]) * (p[1] - this->DisplayOrigin[1]));
  if (radius >= (this->CurrentRadius - tol) && radius <= (this->CurrentRadius + tol))
  {
    this->InteractionState = vtkAffineRepresentation::Rotate;
    return this->InteractionState;
  }

  // Translation Arrows----------------------------------------------------
  this->XAxis->GetPositionCoordinate()->GetValue(p1);
  this->XAxis->GetPosition2Coordinate()->GetValue(p2);
  this->YAxis->GetPositionCoordinate()->GetValue(p3);
  this->YAxis->GetPosition2Coordinate()->GetValue(p4);

  e0 = (p[0] >= (p1[0] - tol) && p[0] <= (p2[0] + tol));
  e1 = (p[1] >= (p1[1] - tol) && p[1] <= (p1[1] + tol));
  e2 = (p[1] >= (p3[1] - tol) && p[1] <= (p4[1] + tol));
  e3 = (p[0] >= (p3[0] - tol) && p[0] <= (p3[0] + tol));

  if (e0 && e1 && e2 && e3)
  {
    if (!modify)
    {
      this->InteractionState = vtkAffineRepresentation::Translate;
    }
    else
    {
      this->InteractionState = vtkAffineRepresentation::MoveOrigin;
    }
  }
  else if (e0 && e1)
  {
    if (!modify)
    {
      this->InteractionState = vtkAffineRepresentation::TranslateX;
    }
    else
    {
      this->InteractionState = vtkAffineRepresentation::MoveOriginX;
    }
  }
  else if (e2 && e3)
  {
    if (!modify)
    {
      this->InteractionState = vtkAffineRepresentation::TranslateY;
    }
    else
    {
      this->InteractionState = vtkAffineRepresentation::MoveOriginY;
    }
  }

  return this->InteractionState;
}

//------------------------------------------------------------------------------
// Record the current event position, and the rectilinear wipe position.
void vtkAffineRepresentation2D::StartWidgetInteraction(double startEventPos[2])
{
  // Initialize bookkeeping variables
  this->StartEventPosition[0] = startEventPos[0];
  this->StartEventPosition[1] = startEventPos[1];
  this->StartEventPosition[2] = 0.0;
  vtkInteractorObserver::ComputeDisplayToWorld(
    this->Renderer, startEventPos[0], startEventPos[1], 0.0, this->StartWorldPosition);

  this->StartAngle = VTK_FLOAT_MAX;

  this->WidgetInteraction(startEventPos);
}

//------------------------------------------------------------------------------
// Based on the displacement vector (computed in display coordinates) and
// the cursor state (which corresponds to which part of the widget has been
// selected), the widget points are modified.
// First construct a local coordinate system based on the display coordinates
// of the widget.
void vtkAffineRepresentation2D::WidgetInteraction(double eventPos[2])
{
  // Dispatch to the correct method
  switch (this->InteractionState)
  {
    case vtkAffineRepresentation::ShearWEdge:
    case vtkAffineRepresentation::ShearEEdge:
    case vtkAffineRepresentation::ShearNEdge:
    case vtkAffineRepresentation::ShearSEdge:
      this->Shear(eventPos);
      break;

    case vtkAffineRepresentation::ScaleNE:
    case vtkAffineRepresentation::ScaleSW:
    case vtkAffineRepresentation::ScaleNW:
    case vtkAffineRepresentation::ScaleSE:
    case vtkAffineRepresentation::ScaleNEdge:
    case vtkAffineRepresentation::ScaleSEdge:
    case vtkAffineRepresentation::ScaleWEdge:
    case vtkAffineRepresentation::ScaleEEdge:
      this->Scale(eventPos);
      break;

    case vtkAffineRepresentation::Rotate:
      this->Rotate(eventPos);
      break;

    case vtkAffineRepresentation::TranslateX:
    case vtkAffineRepresentation::TranslateY:
    case vtkAffineRepresentation::Translate:
    case vtkAffineRepresentation::MoveOriginX:
    case vtkAffineRepresentation::MoveOriginY:
    case vtkAffineRepresentation::MoveOrigin:
      this->Translate(eventPos);
      break;
  }

  // Book keeping
  this->LastEventPosition[0] = eventPos[0];
  this->LastEventPosition[1] = eventPos[1];

  this->Modified();
}

//------------------------------------------------------------------------------
void vtkAffineRepresentation2D::EndWidgetInteraction(double vtkNotUsed(eventPos)[2])
{
  // Have to play games here because of the "pipelined" nature of the
  // transformations.
  this->GetTransform(this->TempTransform);
  this->TotalTransform->SetMatrix(this->TempTransform->GetMatrix());

  // Adjust the origin as necessary
  this->Origin[0] += this->CurrentTranslation[0];
  this->Origin[1] += this->CurrentTranslation[1];
  this->Origin[2] += this->CurrentTranslation[2];

  // Reset the current transformations
  this->CurrentTranslation[0] = 0.0;
  this->CurrentTranslation[1] = 0.0;
  this->CurrentTranslation[2] = 0.0;

  this->CurrentAngle = 0.0;

  this->CurrentScale[0] = 1.0;
  this->CurrentScale[1] = 1.0;

  this->CurrentShear[0] = 0.0;
  this->CurrentShear[1] = 0.0;
}

//------------------------------------------------------------------------------
// Translate everything
void vtkAffineRepresentation2D::Translate(double eventPos[2])
{
  double x1[3], x2[3], y1[3], y2[3], dpos[3];
  dpos[0] = dpos[1] = dpos[2] = 0.0;

  this->XAxis->GetPositionCoordinate()->GetValue(x1);
  this->XAxis->GetPosition2Coordinate()->GetValue(x2);
  this->YAxis->GetPositionCoordinate()->GetValue(y1);
  this->YAxis->GetPosition2Coordinate()->GetValue(y2);

  switch (this->InteractionState)
  {
    case vtkAffineRepresentation::TranslateX:
    case vtkAffineRepresentation::MoveOriginX:
      dpos[0] = eventPos[0] - this->StartEventPosition[0];
      break;

    case vtkAffineRepresentation::TranslateY:
    case vtkAffineRepresentation::MoveOriginY:
      dpos[1] = eventPos[1] - this->StartEventPosition[1];
      break;

    case vtkAffineRepresentation::Translate:
    case vtkAffineRepresentation::MoveOrigin:
      dpos[0] = eventPos[0] - this->StartEventPosition[0];
      dpos[1] = eventPos[1] - this->StartEventPosition[1];
      break;
  }

  x1[0] += dpos[0];
  x2[0] += dpos[0];
  y1[0] += dpos[0];
  y2[0] += dpos[0];
  x1[1] += dpos[1];
  x2[1] += dpos[1];
  y1[1] += dpos[1];
  y2[1] += dpos[1];

  this->HXAxis->GetPositionCoordinate()->SetValue(x1);
  this->HXAxis->GetPosition2Coordinate()->SetValue(x2);
  this->HYAxis->GetPositionCoordinate()->SetValue(y1);
  this->HYAxis->GetPosition2Coordinate()->SetValue(y2);

  // Update the transform
  double wxyz[4];
  vtkInteractorObserver::ComputeDisplayToWorld(this->Renderer,
    this->StartEventPosition[0] + dpos[0], this->StartEventPosition[1] + dpos[1], 0.0, wxyz);

  this->CurrentTranslation[0] = wxyz[0] - this->StartWorldPosition[0];
  this->CurrentTranslation[1] = wxyz[1] - this->StartWorldPosition[1];
  this->CurrentTranslation[2] = wxyz[2] - this->StartWorldPosition[2];

  // Draw the text if necessary
  if (this->DisplayText)
  {
    char str[256];
    auto result = vtk::format_to_n(str, sizeof(str), "({:0.2g}, {:0.2g})",
      this->CurrentTranslation[0], this->CurrentTranslation[1]);
    *result.out = '\0';
    this->UpdateText(str, eventPos);
  }
}

//------------------------------------------------------------------------------
void vtkAffineRepresentation2D::Scale(double eventPos[2])
{
  // Determine the relative motion
  double d[3];
  d[0] = eventPos[0] - this->StartEventPosition[0];
  d[1] = eventPos[1] - this->StartEventPosition[1];

  double x0[3], x1[3], x2[3], x3[3];
  double p0[3], p1[3], p2[3], p3[3];
  this->BoxPoints->GetPoint(0, x0);
  this->BoxPoints->GetPoint(1, x1);
  this->BoxPoints->GetPoint(2, x2);
  this->BoxPoints->GetPoint(3, x3);

  double xChange = 0.0, yChange = 0.0;
  switch (this->InteractionState)
  {
    case vtkAffineRepresentation::ScaleEEdge:
      xChange = 1.0;
      break;

    case vtkAffineRepresentation::ScaleWEdge:
      xChange = -1.0;
      break;

    case vtkAffineRepresentation::ScaleNEdge:
      yChange = 1.0;
      break;

    case vtkAffineRepresentation::ScaleSEdge:
      yChange = -1.0;
      break;

    case vtkAffineRepresentation::ScaleNE:
      xChange = 1.0;
      yChange = 1.0;
      break;

    case vtkAffineRepresentation::ScaleSW:
      xChange = -1.0;
      yChange = -1.0;
      break;

    case vtkAffineRepresentation::ScaleNW:
      xChange = -1.0;
      yChange = 1.0;
      break;

    case vtkAffineRepresentation::ScaleSE:
      xChange = 1.0;
      yChange = -1.0;
      break;
  }

  p0[0] = x0[0] - xChange * d[0];
  p1[0] = x1[0] + xChange * d[0];
  p2[0] = x2[0] + xChange * d[0];
  p3[0] = x3[0] - xChange * d[0];

  p0[1] = x0[1] - yChange * d[1];
  p1[1] = x1[1] - yChange * d[1];
  p2[1] = x2[1] + yChange * d[1];
  p3[1] = x3[1] + yChange * d[1];

  p0[2] = x0[2];
  p1[2] = x1[2];
  p2[2] = x2[2];
  p3[2] = x3[2];

  this->HBoxPoints->SetPoint(0, p0);
  this->HBoxPoints->SetPoint(1, p1);
  this->HBoxPoints->SetPoint(2, p2);
  this->HBoxPoints->SetPoint(3, p3);
  this->HBoxPoints->Modified();

  this->CurrentScale[0] = (p1[0] - p0[0]) / (x1[0] - x0[0]);
  this->CurrentScale[1] = (p2[1] - p1[1]) / (x2[1] - x1[1]);

  if (this->DisplayText)
  {
    char str[256];
    auto result = vtk::format_to_n(
      str, sizeof(str), "({:0.2g}, {:0.2g})", this->CurrentScale[0], this->CurrentScale[1]);
    *result.out = '\0';
    this->UpdateText(str, eventPos);
  }
}

//------------------------------------------------------------------------------
void vtkAffineRepresentation2D::Rotate(double eventPos[2])
{
  double deltaAngle;
  // Compute the initial selection angle, and then the change in angle between
  // the starting point and subsequent points. The angle is constrained so that
  // it is in the range (-Pi < deltaAngle <= Pi).
  if (this->StartAngle >= VTK_FLOAT_MAX)
  {
    double delX = this->StartEventPosition[0] - this->DisplayOrigin[0];
    double delY = this->StartEventPosition[1] - this->DisplayOrigin[1];
    this->StartAngle = atan2(delY, delX);
    deltaAngle = 0.0;
  }
  else
  {
    double delEX = eventPos[0] - this->DisplayOrigin[0];
    double delEY = eventPos[1] - this->DisplayOrigin[1];
    double angle2 = atan2(delEY, delEX);
    // Compute difference in angle
    deltaAngle = angle2 - this->StartAngle;
    if (fabs(deltaAngle) > vtkMath::Pi()) // angle always less than Pi
    {
      if (deltaAngle > 0)
      {
        deltaAngle = -2.0 * vtkMath::Pi() + deltaAngle;
      }
      else
      {
        deltaAngle = 2.0 * vtkMath::Pi() + deltaAngle;
      }
    }
  }

  // Update the angle
  this->CurrentAngle = deltaAngle;

  // Create the arc
  vtkIdType pid;
  this->HCirclePoints->Reset();
  this->HCircleCellArray->Reset();
  this->HCircleCellArray->InsertNextCell(0);
  double p[3];
  p[2] = 0.0;
  double theta, delTheta = 2.0 * vtkMath::Pi() / VTK_CIRCLE_RESOLUTION;
  int numDivs = static_cast<int>(fabs(deltaAngle) / delTheta) + 1;
  delTheta = deltaAngle / numDivs;
  for (int i = 0; i <= numDivs; i++)
  {
    theta = this->StartAngle + i * delTheta;
    p[0] = this->DisplayOrigin[0] + this->CurrentRadius * cos(theta);
    p[1] = this->DisplayOrigin[1] + this->CurrentRadius * sin(theta);
    pid = this->HCirclePoints->InsertNextPoint(p);
    this->HCircleCellArray->InsertCellPoint(pid);
  }
  pid = this->HCirclePoints->InsertNextPoint(this->DisplayOrigin);
  this->HCircleCellArray->InsertCellPoint(pid);
  this->HCircleCellArray->InsertCellPoint(0);
  this->HCircleCellArray->UpdateCellCount(this->HCirclePoints->GetNumberOfPoints() + 1);
  this->HCirclePoints->Modified();

  if (this->DisplayText)
  {
    char str[256];
    double angle = vtkMath::DegreesFromRadians(deltaAngle);
    auto result = vtk::format_to_n(str, sizeof(str), "({:.1f})", angle);
    *result.out = '\0';
    this->UpdateText(str, eventPos);
  }
}

//------------------------------------------------------------------------------
// Fiddle with matrix to apply shear
void vtkAffineRepresentation2D::ApplyShear() {}

//------------------------------------------------------------------------------
void vtkAffineRepresentation2D::Shear(double eventPos[2])
{
  // Determine the relative motion
  double d[3];
  d[0] = eventPos[0] - this->StartEventPosition[0];
  d[1] = eventPos[1] - this->StartEventPosition[1];

  double x0[3], x1[3], x2[3], x3[3];
  double p0[3], p1[3], p2[3], p3[3];
  this->BoxPoints->GetPoint(0, x0);
  this->BoxPoints->GetPoint(1, x1);
  this->BoxPoints->GetPoint(2, x2);
  this->BoxPoints->GetPoint(3, x3);

  double xChange = 0.0, yChange = 0.0;
  switch (this->InteractionState)
  {
    case vtkAffineRepresentation::ShearSEdge:
      xChange = 1.0;
      break;

    case vtkAffineRepresentation::ShearNEdge:
      xChange = -1.0;
      break;

    case vtkAffineRepresentation::ShearEEdge:
      yChange = 1.0;
      break;

    case vtkAffineRepresentation::ShearWEdge:
      yChange = -1.0;
      break;
  }

  p0[0] = x0[0] + xChange * d[0];
  p1[0] = x1[0] + xChange * d[0];
  p2[0] = x2[0] - xChange * d[0];
  p3[0] = x3[0] - xChange * d[0];

  p0[1] = x0[1] - yChange * d[1];
  p1[1] = x1[1] + yChange * d[1];
  p2[1] = x2[1] + yChange * d[1];
  p3[1] = x3[1] - yChange * d[1];

  p0[2] = x0[2];
  p1[2] = x1[2];
  p2[2] = x2[2];
  p3[2] = x3[2];

  this->HBoxPoints->SetPoint(0, p0);
  this->HBoxPoints->SetPoint(1, p1);
  this->HBoxPoints->SetPoint(2, p2);
  this->HBoxPoints->SetPoint(3, p3);
  this->HBoxPoints->Modified();

  // Update the current shear
  double sx = (x2[1] - x1[1]) / 2.0;
  double sy = ((p0[0] - x0[0]) + (p0[1] - x0[1]));
  double angle = vtkMath::DegreesFromRadians(atan2(sy, sx));
  if (this->InteractionState == vtkAffineRepresentation::ShearNEdge ||
    this->InteractionState == vtkAffineRepresentation::ShearSEdge)
  {
    this->CurrentShear[0] = angle;
  }
  else
  {
    this->CurrentShear[1] = angle;
  }

  // Display text if requested
  if (this->DisplayText)
  {
    char str[256];
    auto result = vtk::format_to_n(str, sizeof(str), "({:0.2g})", angle);
    *result.out = '\0';
    this->UpdateText(str, eventPos);
  }
}

//------------------------------------------------------------------------------
void vtkAffineRepresentation2D::Highlight(int highlight)
{
  if (highlight) // enable appropriate highlight actor
  {
    // Make the text visible
    if (this->DisplayText)
    {
      this->TextActor->VisibilityOn();
    }

    // The existing widget is set translucent
    this->Opacity = this->Property->GetOpacity();
    this->Property->SetOpacity(0.33);
    this->SelectedOpacity = this->SelectedProperty->GetOpacity();
    this->SelectedProperty->SetOpacity(1.0);

    switch (this->InteractionState)
    {
      case vtkAffineRepresentation::ShearWEdge:
      case vtkAffineRepresentation::ShearEEdge:
      case vtkAffineRepresentation::ShearNEdge:
      case vtkAffineRepresentation::ShearSEdge:
      case vtkAffineRepresentation::ScaleNE:
      case vtkAffineRepresentation::ScaleSW:
      case vtkAffineRepresentation::ScaleNW:
      case vtkAffineRepresentation::ScaleSE:
      case vtkAffineRepresentation::ScaleNEdge:
      case vtkAffineRepresentation::ScaleSEdge:
      case vtkAffineRepresentation::ScaleWEdge:
      case vtkAffineRepresentation::ScaleEEdge:
        this->HBoxActor->VisibilityOn();
        break;

      case vtkAffineRepresentation::Rotate:
        this->HCircleActor->VisibilityOn();
        break;

      case vtkAffineRepresentation::TranslateX:
      case vtkAffineRepresentation::TranslateY:
      case vtkAffineRepresentation::Translate:
      case vtkAffineRepresentation::MoveOriginX:
      case vtkAffineRepresentation::MoveOriginY:
      case vtkAffineRepresentation::MoveOrigin:
        this->HXAxis->VisibilityOn();
        this->HYAxis->VisibilityOn();
        break;
    }
  }

  else // turn off highlight actor
  {
    this->TextActor->VisibilityOff();
    this->Property->SetOpacity(this->Opacity);
    this->SelectedProperty->SetOpacity(this->SelectedOpacity);
    this->HBoxActor->VisibilityOff();
    this->HCircleActor->VisibilityOff();
    this->HXAxis->VisibilityOff();
    this->HYAxis->VisibilityOff();
  }
}

//------------------------------------------------------------------------------
void vtkAffineRepresentation2D::CreateDefaultProperties()
{
  this->Property = vtkProperty2D::New();
  this->Property->SetColor(0.0, 1.0, 0.0);
  this->Property->SetLineWidth(0.5);

  this->SelectedProperty = vtkProperty2D::New();
  this->SelectedProperty->SetColor(1.0, 0.0, 0.0);
  this->SelectedProperty->SetLineWidth(1.0);

  this->TextProperty = vtkTextProperty::New();
  this->TextProperty->SetFontSize(12);
  this->TextProperty->SetColor(1.0, 0.0, 0.0);
  this->TextProperty->SetBold(1);
  this->TextProperty->SetFontFamilyToArial();
  this->TextProperty->SetJustificationToLeft();
  this->TextProperty->SetVerticalJustificationToBottom();
}

//------------------------------------------------------------------------------
void vtkAffineRepresentation2D::UpdateText(const char* text, double eventPos[2])
{
  this->TextMapper->SetInput(text);
  this->TextActor->SetPosition(eventPos[0] + 7, eventPos[1] + 7);
}

//------------------------------------------------------------------------------
void vtkAffineRepresentation2D::BuildRepresentation()
{
  if (this->GetMTime() > this->BuildTime ||
    (this->Renderer && this->Renderer->GetVTKWindow() &&
      this->Renderer->GetVTKWindow()->GetMTime() > this->BuildTime))
  {
    // Determine where the origin is on the display
    vtkInteractorObserver::ComputeWorldToDisplay(
      this->Renderer, this->Origin[0], this->Origin[1], this->Origin[2], this->DisplayOrigin);

    // draw the box
    this->CurrentWidth = this->BoxWidth;
    this->CurrentWidth /= 2.0;
    double p1[3], p2[3], p3[3], p4[3];
    p1[0] = this->DisplayOrigin[0] - this->CurrentWidth;
    p1[1] = this->DisplayOrigin[1] - this->CurrentWidth;
    p1[2] = 0.0;
    p2[0] = this->DisplayOrigin[0] + this->CurrentWidth;
    p2[1] = this->DisplayOrigin[1] - this->CurrentWidth;
    p2[2] = 0.0;
    p3[0] = this->DisplayOrigin[0] + this->CurrentWidth;
    p3[1] = this->DisplayOrigin[1] + this->CurrentWidth;
    p3[2] = 0.0;
    p4[0] = this->DisplayOrigin[0] - this->CurrentWidth;
    p4[1] = this->DisplayOrigin[1] + this->CurrentWidth;
    p4[2] = 0.0;
    this->BoxPoints->SetPoint(0, p1);
    this->BoxPoints->SetPoint(1, p2);
    this->BoxPoints->SetPoint(2, p3);
    this->BoxPoints->SetPoint(3, p4);
    this->BoxPoints->Modified();

    // draw the circle
    int i;
    double theta, delTheta = 2.0 * vtkMath::Pi() / VTK_CIRCLE_RESOLUTION;
    this->CurrentRadius = this->CurrentWidth * 0.75;
    this->CircleCellArray->InsertNextCell(VTK_CIRCLE_RESOLUTION + 1);
    for (i = 0; i < VTK_CIRCLE_RESOLUTION; i++)
    {
      theta = i * delTheta;
      p1[0] = this->DisplayOrigin[0] + this->CurrentRadius * cos(theta);
      p1[1] = this->DisplayOrigin[1] + this->CurrentRadius * sin(theta);
      this->CirclePoints->SetPoint(i, p1);
      this->CircleCellArray->InsertCellPoint(i);
    }
    this->CircleCellArray->InsertCellPoint(0);

    // draw the translation axes
    this->CurrentAxesWidth = this->CurrentWidth * this->AxesWidth / this->BoxWidth;
    p1[0] = this->DisplayOrigin[0] - this->CurrentAxesWidth;
    p1[1] = this->DisplayOrigin[1];
    this->XAxis->GetPositionCoordinate()->SetValue(p1);
    p2[0] = this->DisplayOrigin[0] + this->CurrentAxesWidth;
    p2[1] = this->DisplayOrigin[1];
    this->XAxis->GetPosition2Coordinate()->SetValue(p2);

    p1[0] = this->DisplayOrigin[0];
    p1[1] = this->DisplayOrigin[1] - this->CurrentAxesWidth;
    this->YAxis->GetPositionCoordinate()->SetValue(p1);
    p2[0] = this->DisplayOrigin[0];
    p2[1] = this->DisplayOrigin[1] + this->CurrentAxesWidth;
    this->YAxis->GetPosition2Coordinate()->SetValue(p2);

    this->BuildTime.Modified();
  }
}

//------------------------------------------------------------------------------
void vtkAffineRepresentation2D::ShallowCopy(vtkProp* prop)
{
  vtkAffineRepresentation2D* rep = vtkAffineRepresentation2D::SafeDownCast(prop);
  if (rep)
  {
    this->SetProperty(rep->GetProperty());
    this->SetSelectedProperty(rep->GetSelectedProperty());
    this->SetTextProperty(rep->GetTextProperty());
    this->BoxActor->SetProperty(this->Property);
    this->HBoxActor->SetProperty(this->SelectedProperty);
    this->CircleActor->SetProperty(this->Property);
    this->HCircleActor->SetProperty(this->SelectedProperty);
    this->XAxis->SetProperty(this->Property);
    this->YAxis->SetProperty(this->Property);
    this->HXAxis->SetProperty(this->SelectedProperty);
    this->HYAxis->SetProperty(this->SelectedProperty);
  }
  this->Superclass::ShallowCopy(prop);
}

//------------------------------------------------------------------------------
void vtkAffineRepresentation2D::GetActors2D(vtkPropCollection* pc)
{
  if (pc != nullptr && this->GetVisibility())
  {
    this->BoxActor->GetActors2D(pc);
    this->HBoxActor->GetActors2D(pc);
    this->CircleActor->GetActors2D(pc);
    this->HCircleActor->GetActors2D(pc);
    this->XAxis->GetActors2D(pc);
    this->YAxis->GetActors2D(pc);
    this->HXAxis->GetActors2D(pc);
    this->HYAxis->GetActors2D(pc);
  }
  this->Superclass::GetActors2D(pc);
}

//------------------------------------------------------------------------------
void vtkAffineRepresentation2D::ReleaseGraphicsResources(vtkWindow* win)
{
  this->TextActor->ReleaseGraphicsResources(win);
  this->BoxActor->ReleaseGraphicsResources(win);
  this->HBoxActor->ReleaseGraphicsResources(win);
  this->CircleActor->ReleaseGraphicsResources(win);
  this->HCircleActor->ReleaseGraphicsResources(win);
  this->XAxis->ReleaseGraphicsResources(win);
  this->YAxis->ReleaseGraphicsResources(win);
  this->HXAxis->ReleaseGraphicsResources(win);
  this->HYAxis->ReleaseGraphicsResources(win);
}

//------------------------------------------------------------------------------
int vtkAffineRepresentation2D::RenderOverlay(vtkViewport* viewport)
{
  this->BuildRepresentation();

  int count = 0;
  if (this->TextActor->GetVisibility())
  {
    count += this->TextActor->RenderOverlay(viewport);
  }

  count += this->BoxActor->RenderOverlay(viewport);
  if (this->HBoxActor->GetVisibility())
  {
    count += this->HBoxActor->RenderOverlay(viewport);
  }

  count += this->CircleActor->RenderOverlay(viewport);
  if (this->HCircleActor->GetVisibility())
  {
    count += this->HCircleActor->RenderOverlay(viewport);
  }

  count += this->XAxis->RenderOverlay(viewport);
  count += this->YAxis->RenderOverlay(viewport);
  if (this->HXAxis->GetVisibility())
  {
    count += this->HXAxis->RenderOverlay(viewport);
  }
  if (this->HYAxis->GetVisibility())
  {
    count += this->HYAxis->RenderOverlay(viewport);
  }

  return count;
}

//------------------------------------------------------------------------------
void vtkAffineRepresentation2D::PrintSelf(ostream& os, vtkIndent indent)
{
  // Superclass typedef defined in vtkTypeMacro() found in vtkSetGet.h
  this->Superclass::PrintSelf(os, indent);

  os << indent << "Display Text: " << (this->DisplayText ? "On\n" : "Off\n");

  os << indent << "Origin: (" << this->Origin[0] << "," << this->Origin[1] << "," << this->Origin[2]
     << ")\n";
  os << indent << "Box Width: " << this->BoxWidth << "\n";
  os << indent << "Circle Width: " << this->CircleWidth << "\n";
  os << indent << "Axes Width: " << this->AxesWidth << "\n";

  if (this->TextProperty)
  {
    os << indent << "Text Property:\n";
    this->TextProperty->PrintSelf(os, indent.GetNextIndent());
  }
  else
  {
    os << indent << "Property: (none)\n";
  }

  if (this->Property)
  {
    os << indent << "Property:\n";
    this->Property->PrintSelf(os, indent.GetNextIndent());
  }
  else
  {
    os << indent << "Property: (none)\n";
  }

  if (this->SelectedProperty)
  {
    os << indent << "Selected Property:\n";
    this->SelectedProperty->PrintSelf(os, indent.GetNextIndent());
  }
  else
  {
    os << indent << "Selected Property: (none)\n";
  }

  if (this->TextProperty)
  {
    os << indent << "Text Property:\n";
    this->TextProperty->PrintSelf(os, indent.GetNextIndent());
  }
  else
  {
    os << indent << "Text Property: (none)\n";
  }
}
VTK_ABI_NAMESPACE_END
