// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkCurveRepresentation.h"

#include "vtkActor.h"
#include "vtkAssemblyPath.h"
#include "vtkBoundingBox.h"
#include "vtkCallbackCommand.h"
#include "vtkCamera.h"
#include "vtkCellArray.h"
#include "vtkCellPicker.h"
#include "vtkHandleSource.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkInteractorObserver.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPickingManager.h"
#include "vtkPlaneSource.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper.h"
#include "vtkProperty.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkTransform.h"

#include <algorithm>
#include <cassert>
#include <iterator>

//------------------------------------------------------------------------------
VTK_ABI_NAMESPACE_BEGIN
vtkCurveRepresentation::vtkCurveRepresentation()
{
  this->LastEventPosition[0] = VTK_DOUBLE_MAX;
  this->LastEventPosition[1] = VTK_DOUBLE_MAX;
  this->LastEventPosition[2] = VTK_DOUBLE_MAX;

  this->Bounds[0] = VTK_DOUBLE_MAX;
  this->Bounds[1] = -VTK_DOUBLE_MAX;
  this->Bounds[2] = VTK_DOUBLE_MAX;
  this->Bounds[3] = -VTK_DOUBLE_MAX;
  this->Bounds[4] = VTK_DOUBLE_MAX;
  this->Bounds[5] = -VTK_DOUBLE_MAX;

  this->HandleSize = 5.0;

  this->InteractionState = vtkCurveRepresentation::Outside;
  this->ProjectToPlane = 0;   // default off
  this->ProjectionNormal = 0; // default YZ not used
  this->ProjectionPosition = 0.0;
  this->PlaneSource = nullptr;
  this->Closed = 0;

  this->LineActor = vtkActor::New();

  // Default bounds to get started
  double bounds[6] = { -0.5, 0.5, -0.5, 0.5, -0.5, 0.5 };

  // Initial creation of the widget, serves to initialize it
  this->PlaceFactor = 1.0;
  this->PlaceWidget(bounds);

  // Manage the picking stuff
  this->HandlePicker = vtkCellPicker::New();
  this->HandlePicker->SetTolerance(0.005);

  this->LinePicker = vtkCellPicker::New();
  this->LinePicker->SetTolerance(0.01);
  this->LinePicker->AddPickList(this->LineActor);
  this->LinePicker->PickFromListOn();

  this->LastPickPosition[0] = VTK_DOUBLE_MAX;
  this->LastPickPosition[1] = VTK_DOUBLE_MAX;
  this->LastPickPosition[2] = VTK_DOUBLE_MAX;

  this->CurrentHandle = nullptr;
  this->CurrentHandleIndex = -1;
  this->FirstSelected = true;

  this->Transform = vtkTransform::New();

  // Set up the initial properties
  this->HandleProperty = nullptr;
  this->SelectedHandleProperty = nullptr;
  this->LineProperty = nullptr;
  this->SelectedLineProperty = nullptr;
  this->CreateDefaultProperties();

  this->Centroid[0] = 0.0;
  this->Centroid[1] = 0.0;
  this->Centroid[2] = 0.0;

  this->TranslationAxis = Axis::NONE;
}

//------------------------------------------------------------------------------
vtkCurveRepresentation::~vtkCurveRepresentation()
{
  this->LineActor->Delete();

  this->HandlePicker->Delete();
  this->LinePicker->Delete();

  if (this->HandleProperty)
  {
    this->HandleProperty->Delete();
  }
  if (this->SelectedHandleProperty)
  {
    this->SelectedHandleProperty->Delete();
  }
  if (this->LineProperty)
  {
    this->LineProperty->Delete();
  }
  if (this->SelectedLineProperty)
  {
    this->SelectedLineProperty->Delete();
  }

  this->Transform->Delete();
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::SetDirectional(bool val)
{
  if (this->Directional == val)
  {
    return;
  }

  this->Directional = val;
  this->Modified();

  if (this->NumberOfHandles < 2)
  {
    return;
  }

  if (this->Directional)
  {
    this->GetHandleSource(NumberOfHandles - 1)->SetDirectional(true);
  }
  else
  {
    this->GetHandleSource(this->NumberOfHandles - 1)->SetDirectional(false);
  }
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::SetClosed(vtkTypeBool closed)
{
  if (this->Closed == closed)
  {
    return;
  }
  this->Closed = closed;

  this->BuildRepresentation();
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::RegisterPickers()
{
  vtkPickingManager* pm = this->GetPickingManager();
  if (!pm)
  {
    return;
  }
  pm->AddPicker(this->HandlePicker, this);
  pm->AddPicker(this->LinePicker, this);
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::SetHandlePosition(int handle, double x, double y, double z)
{
  if (handle < 0 || handle >= this->NumberOfHandles)
  {
    vtkErrorMacro(<< "vtkCurveRepresentation: handle index out of range.");
    return;
  }
  this->GetHandleSource(handle)->SetPosition(x, y, z);
  this->GetHandleSource(handle)->Update();
  if (this->ProjectToPlane)
  {
    this->ProjectPointsToPlane();
  }
  this->BuildRepresentation();
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::SetHandlePosition(int handle, double xyz[3])
{
  this->SetHandlePosition(handle, xyz[0], xyz[1], xyz[2]);
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::GetHandlePosition(int handle, double xyz[3])
{
  if (handle < 0 || handle >= this->NumberOfHandles)
  {
    vtkErrorMacro(<< "vtkCurveRepresentation: handle index out of range.");
    return;
  }
  this->GetHandleSource(handle)->GetPosition(xyz);
}

//------------------------------------------------------------------------------
double* vtkCurveRepresentation::GetHandlePosition(int handle)
{
  if (handle < 0 || handle >= this->NumberOfHandles)
  {
    vtkErrorMacro(<< "vtkCurveRepresentation: handle index out of range.");
    return nullptr;
  }

  return this->GetHandleSource(handle)->GetPosition();
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::ProjectPointsToPlane()
{
  if (this->ProjectionNormal == VTK_PROJECTION_OBLIQUE)
  {
    if (this->PlaneSource != nullptr)
    {
      this->ProjectPointsToObliquePlane();
    }
    else
    {
      vtkGenericWarningMacro(<< "Set the plane source for oblique projections...");
    }
  }
  else
  {
    this->ProjectPointsToOrthoPlane();
  }
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::ProjectPointsToObliquePlane()
{
  double o[3];
  double u[3];
  double v[3];

  this->PlaneSource->GetPoint1(u);
  this->PlaneSource->GetPoint2(v);
  this->PlaneSource->GetOrigin(o);

  int i;
  for (i = 0; i < 3; ++i)
  {
    u[i] = u[i] - o[i];
    v[i] = v[i] - o[i];
  }
  vtkMath::Normalize(u);
  vtkMath::Normalize(v);

  double o_dot_u = vtkMath::Dot(o, u);
  double o_dot_v = vtkMath::Dot(o, v);
  double fac1;
  double fac2;
  double ctr[3];
  for (i = 0; i < this->NumberOfHandles; ++i)
  {
    this->GetHandleSource(i)->GetPosition(ctr);
    fac1 = vtkMath::Dot(ctr, u) - o_dot_u;
    fac2 = vtkMath::Dot(ctr, v) - o_dot_v;
    ctr[0] = o[0] + fac1 * u[0] + fac2 * v[0];
    ctr[1] = o[1] + fac1 * u[1] + fac2 * v[1];
    ctr[2] = o[2] + fac1 * u[2] + fac2 * v[2];
    this->GetHandleSource(i)->SetPosition(ctr);
    this->GetHandleSource(i)->Update();
  }
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::ProjectPointsToOrthoPlane()
{
  double ctr[3];
  for (int i = 0; i < this->NumberOfHandles; ++i)
  {
    this->GetHandleSource(i)->GetPosition(ctr);
    ctr[this->ProjectionNormal] = this->ProjectionPosition;
    this->GetHandleSource(i)->SetPosition(ctr);
    this->GetHandleSource(i)->Update();
  }
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::SetCurrentHandleIndex(int index)
{
  if (index < -1 || index >= this->NumberOfHandles)
  {
    index = -1;
  }

  if (index != this->CurrentHandleIndex)
  {
    this->CurrentHandleIndex = index;
    this->HighlightHandle(index == -1 ? nullptr : this->GetHandleActor(index));
  }
}

//------------------------------------------------------------------------------
int vtkCurveRepresentation::HighlightHandle(vtkProp* prop)
{
  // First unhighlight anything picked
  if (this->CurrentHandle)
  {
    this->CurrentHandle->SetProperty(this->HandleProperty);
  }

  this->CurrentHandle = static_cast<vtkActor*>(prop);

  if (this->CurrentHandle)
  {
    this->CurrentHandle->SetProperty(this->SelectedHandleProperty);
    return this->GetHandleIndex(prop);
  }
  return -1;
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::HighlightLine(int highlight)
{
  if (highlight)
  {
    this->LineActor->SetProperty(this->SelectedLineProperty);
  }
  else
  {
    this->LineActor->SetProperty(this->LineProperty);
  }
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::MovePoint(double* p1, double* p2)
{
  if (this->CurrentHandleIndex < 0 || this->CurrentHandleIndex >= this->NumberOfHandles)
  {
    vtkGenericWarningMacro(<< "Poly line handle index out of range.");
    return;
  }

  // Get the motion vector
  double v[3] = { 0, 0, 0 };
  // Move the center of the handle along the motion vector
  if (this->TranslationAxis == Axis::NONE)
  {
    v[0] = p2[0] - p1[0];
    v[1] = p2[1] - p1[1];
    v[2] = p2[2] - p1[2];
  }
  // Translation restriction handling
  else
  {
    v[this->TranslationAxis] = p2[this->TranslationAxis] - p1[this->TranslationAxis];
  }

  double* ctr = GetHandleSource(this->CurrentHandleIndex)->GetPosition();

  double newCtr[3];
  newCtr[0] = ctr[0] + v[0];
  newCtr[1] = ctr[1] + v[1];
  newCtr[2] = ctr[2] + v[2];
  this->GetHandleSource(this->CurrentHandleIndex)->SetPosition(newCtr);
  this->GetHandleSource(this->CurrentHandleIndex)->Update();
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::Translate(double* p1, double* p2)
{
  // Get the motion vector
  double v[3] = { 0, 0, 0 };
  // Move the center of the handle along the motion vector
  if (this->TranslationAxis == Axis::NONE)
  {
    v[0] = p2[0] - p1[0];
    v[1] = p2[1] - p1[1];
    v[2] = p2[2] - p1[2];
  }
  // Translation restriction handling
  else
  {
    // this->TranslationAxis in [0,2]
    assert(this->TranslationAxis > -1 && this->TranslationAxis < 3 &&
      "this->TranslationAxis should be in [0,2]");
    v[this->TranslationAxis] = p2[this->TranslationAxis] - p1[this->TranslationAxis];
  }

  double newCtr[3];
  for (int i = 0; i < this->NumberOfHandles; ++i)
  {
    double* ctr = this->GetHandleSource(i)->GetPosition();
    for (int j = 0; j < 3; ++j)
    {
      newCtr[j] = ctr[j] + v[j];
    }
    this->GetHandleSource(i)->SetPosition(newCtr);
    this->GetHandleSource(i)->Update();
  }
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::Scale(double* p1, double* p2, int vtkNotUsed(X), int Y)
{
  // Get the motion vector
  double v[3];
  v[0] = p2[0] - p1[0];
  v[1] = p2[1] - p1[1];
  v[2] = p2[2] - p1[2];

  double center[3] = { 0.0, 0.0, 0.0 };
  double avgdist = 0.0;
  double* prevctr = this->GetHandleSource(0)->GetPosition();
  double* ctr;

  center[0] += prevctr[0];
  center[1] += prevctr[1];
  center[2] += prevctr[2];

  int i;
  for (i = 1; i < this->NumberOfHandles; ++i)
  {
    ctr = this->GetHandleSource(i)->GetPosition();
    center[0] += ctr[0];
    center[1] += ctr[1];
    center[2] += ctr[2];
    avgdist += sqrt(vtkMath::Distance2BetweenPoints(ctr, prevctr));
    prevctr = ctr;
  }

  avgdist /= this->NumberOfHandles;

  center[0] /= this->NumberOfHandles;
  center[1] /= this->NumberOfHandles;
  center[2] /= this->NumberOfHandles;

  // Compute the scale factor
  double sf = vtkMath::Norm(v) / avgdist;
  if (Y > this->LastEventPosition[1])
  {
    sf = 1.0 + sf;
  }
  else
  {
    sf = 1.0 - sf;
  }

  // Move the handle points
  double newCtr[3];
  for (i = 0; i < this->NumberOfHandles; ++i)
  {
    ctr = this->GetHandleSource(i)->GetPosition();
    for (int j = 0; j < 3; ++j)
    {
      newCtr[j] = sf * (ctr[j] - center[j]) + center[j];
    }
    this->GetHandleSource(i)->SetPosition(newCtr);
    this->GetHandleSource(i)->Update();
  }
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::Spin(double* p1, double* p2, double* vpn)
{
  // Mouse motion vector in world space
  double v[3];
  v[0] = p2[0] - p1[0];
  v[1] = p2[1] - p1[1];
  v[2] = p2[2] - p1[2];

  // Axis of rotation
  double axis[3] = { 0.0, 0.0, 0.0 };

  if (this->ProjectToPlane)
  {
    if (this->ProjectionNormal == VTK_PROJECTION_OBLIQUE)
    {
      if (this->PlaneSource != nullptr)
      {
        double* normal = this->PlaneSource->GetNormal();
        axis[0] = normal[0];
        axis[1] = normal[1];
        axis[2] = normal[2];
        vtkMath::Normalize(axis);
      }
      else
      {
        axis[0] = 1.;
      }
    }
    else
    {
      axis[this->ProjectionNormal] = 1.;
    }
  }
  else
  {
    // Create axis of rotation and angle of rotation
    vtkMath::Cross(vpn, v, axis);
    if (vtkMath::Normalize(axis) == 0.0)
    {
      return;
    }
  }

  // Radius vector (from mean center to cursor position)
  double rv[3] = { p2[0] - this->Centroid[0], p2[1] - this->Centroid[1],
    p2[2] - this->Centroid[2] };

  // Distance between center and cursor location
  double rs = vtkMath::Normalize(rv);

  // Spin direction
  double ax_cross_rv[3];
  vtkMath::Cross(axis, rv, ax_cross_rv);

  // Spin angle
  double theta = 360.0 * vtkMath::Dot(v, ax_cross_rv) / rs;

  // Manipulate the transform to reflect the rotation
  this->Transform->Identity();
  this->Transform->Translate(this->Centroid[0], this->Centroid[1], this->Centroid[2]);
  this->Transform->RotateWXYZ(theta, axis);
  this->Transform->Translate(-this->Centroid[0], -this->Centroid[1], -this->Centroid[2]);

  // Set the handle points
  double newCtr[3];
  double ctr[3];
  for (int i = 0; i < this->NumberOfHandles; ++i)
  {
    this->GetHandleSource(i)->GetPosition(ctr);
    this->Transform->TransformPoint(ctr, newCtr);
    this->GetHandleSource(i)->SetPosition(newCtr);
    this->GetHandleSource(i)->Update();
  }
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::CreateDefaultProperties()
{
  this->HandleProperty = vtkProperty::New();
  this->HandleProperty->SetColor(1, 1, 1);

  this->SelectedHandleProperty = vtkProperty::New();
  this->SelectedHandleProperty->SetColor(1, 0, 0);

  this->LineProperty = vtkProperty::New();
  this->LineProperty->SetRepresentationToWireframe();
  this->LineProperty->SetAmbient(1.0);
  this->LineProperty->SetColor(1.0, 1.0, 0.0);
  this->LineProperty->SetLineWidth(2.0);

  this->SelectedLineProperty = vtkProperty::New();
  this->SelectedLineProperty->SetRepresentationToWireframe();
  this->SelectedLineProperty->SetAmbient(1.0);
  this->SelectedLineProperty->SetColor(0.0, 1.0, 0.0);
  this->SelectedLineProperty->SetLineWidth(2.0);
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::SetProjectionPosition(double position)
{
  this->ProjectionPosition = position;
  if (this->ProjectToPlane)
  {
    this->ProjectPointsToPlane();
  }
  this->BuildRepresentation();
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::SetPlaneSource(vtkPlaneSource* plane)
{
  if (this->PlaneSource == plane)
  {
    return;
  }
  this->PlaneSource = plane;
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::SizeHandles()
{
  if (this->NumberOfHandles > 0)
  {
    for (int i = 0; i < this->NumberOfHandles; ++i)
    {
      double radius = this->SizeHandlesInPixels(1.5, GetHandleSource(i)->GetPosition());
      this->GetHandleSource(i)->SetSize(radius);
      this->GetHandleSource(i)->Update();
    }
  }
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::CalculateCentroid()
{
  this->Centroid[0] = 0.0;
  this->Centroid[1] = 0.0;
  this->Centroid[2] = 0.0;

  double ctr[3];
  for (int i = 0; i < this->NumberOfHandles; ++i)
  {
    this->GetHandleSource(i)->GetPosition(ctr);
    this->Centroid[0] += ctr[0];
    this->Centroid[1] += ctr[1];
    this->Centroid[2] += ctr[2];
  }

  this->Centroid[0] /= this->NumberOfHandles;
  this->Centroid[1] /= this->NumberOfHandles;
  this->Centroid[2] /= this->NumberOfHandles;
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::EraseHandle(const int& index)
{
  if (this->NumberOfHandles < 3 || index < 0 || index >= this->NumberOfHandles)
  {
    return;
  }

  vtkPoints* newpoints = vtkPoints::New(VTK_DOUBLE);
  newpoints->SetNumberOfPoints(this->NumberOfHandles - 1);
  int count = 0;
  for (int i = 0; i < this->NumberOfHandles; ++i)
  {
    if (i != index)
    {
      newpoints->SetPoint(count++, this->GetHandleSource(i)->GetPosition());
    }
  }

  this->InitializeHandles(newpoints);
  newpoints->Delete();
}

//------------------------------------------------------------------------------
vtkTypeBool vtkCurveRepresentation::IsClosed()
{
  if (this->NumberOfHandles < 3 || !this->Closed)
  {
    return 0;
  }

  vtkPolyData* lineData = vtkPolyData::New();
  this->GetPolyData(lineData);
  if (!lineData || !(lineData->GetPoints()))
  {
    vtkErrorMacro(<< "No line data to query geometric closure");
    return 0;
  }

  vtkPoints* points = lineData->GetPoints();
  int numPoints = points->GetNumberOfPoints();

  if (numPoints < 3)
  {
    return 0;
  }

  int numEntries =
    lineData->GetLines()->GetNumberOfConnectivityIds() + lineData->GetLines()->GetNumberOfCells();

  double p0[3];
  double p1[3];

  points->GetPoint(0, p0);
  points->GetPoint(numPoints - 1, p1);
  int minusNth = (p0[0] == p1[0] && p0[1] == p1[1] && p0[2] == p1[2]) ? 1 : 0;
  int result;
  if (minusNth) // definitely closed
  {
    result = 1;
  }
  else // not physically closed, check connectivity
  {
    result = ((numEntries - numPoints) == 2) ? 1 : 0;
  }

  return result;
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::ReleaseGraphicsResources(vtkWindow* win)
{
  this->LineActor->ReleaseGraphicsResources(win);
  for (int cc = 0; cc < this->NumberOfHandles; cc++)
  {
    this->GetHandleActor(cc)->ReleaseGraphicsResources(win);
  }
}

//------------------------------------------------------------------------------
int vtkCurveRepresentation::RenderOpaqueGeometry(vtkViewport* win)
{
  this->BuildRepresentation();

  int count = 0;
  count += this->LineActor->RenderOpaqueGeometry(win);
  for (int cc = 0; cc < this->NumberOfHandles; cc++)
  {
    count += this->GetHandleActor(cc)->RenderOpaqueGeometry(win);
  }
  return count;
}

//------------------------------------------------------------------------------
int vtkCurveRepresentation::RenderTranslucentPolygonalGeometry(vtkViewport* win)
{
  int count = 0;
  count += this->LineActor->RenderTranslucentPolygonalGeometry(win);
  for (int cc = 0; cc < this->NumberOfHandles; cc++)
  {
    count += this->GetHandleActor(cc)->RenderTranslucentPolygonalGeometry(win);
  }
  return count;
}

//------------------------------------------------------------------------------
int vtkCurveRepresentation::RenderOverlay(vtkViewport* win)
{
  int count = 0;
  count += this->LineActor->RenderOverlay(win);
  for (int cc = 0; cc < this->NumberOfHandles; cc++)
  {
    count += this->GetHandleActor(cc)->RenderOverlay(win);
  }
  return count;
}

//------------------------------------------------------------------------------
vtkTypeBool vtkCurveRepresentation::HasTranslucentPolygonalGeometry()
{
  this->BuildRepresentation();
  int count = 0;
  count |= this->LineActor->HasTranslucentPolygonalGeometry();
  for (int cc = 0; cc < this->NumberOfHandles; cc++)
  {
    count |= this->GetHandleActor(cc)->HasTranslucentPolygonalGeometry();
  }
  return count;
}

//------------------------------------------------------------------------------
int vtkCurveRepresentation::ComputeInteractionState(int X, int Y, int vtkNotUsed(modify))
{
  this->InteractionState = vtkCurveRepresentation::Outside;
  if (!this->Renderer || !this->Renderer->IsInViewport(X, Y))
  {
    return this->InteractionState;
  }

  // Try and pick a handle first. This allows the picking of the handle even
  // if it is "behind" the poly line.
  int handlePicked = 0;

  vtkAssemblyPath* path = this->GetAssemblyPath(X, Y, 0., this->HandlePicker);

  // always get pick position
  this->HandlePicker->GetPickPosition(this->LastPickPosition);

  if (path != nullptr)
  {
    this->ValidPick = 1;
    this->InteractionState = vtkCurveRepresentation::OnHandle;
    this->SetCurrentHandleIndex(this->GetHandleIndex(path->GetFirstNode()->GetViewProp()));
    handlePicked = 1;
    this->FirstSelected = (this->CurrentHandleIndex == 0);
  }
  else
  {
    this->SetCurrentHandleIndex(-1);
  }

  if (!handlePicked)
  {
    path = this->GetAssemblyPath(X, Y, 0., this->LinePicker);

    if (path != nullptr)
    {
      this->ValidPick = 1;
      this->LinePicker->GetPickPosition(this->LastPickPosition);
      this->HighlightLine(1);
      this->InteractionState = vtkCurveRepresentation::OnLine;
    }
    else
    {
      this->HighlightLine(0);
    }
  }
  else
  {
    this->HighlightLine(0);
  }

  return this->InteractionState;
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::StartWidgetInteraction(double e[2])
{
  // Store the start position
  this->StartEventPosition[0] = e[0];
  this->StartEventPosition[1] = e[1];
  this->StartEventPosition[2] = 0.0;

  // Store the start position
  this->LastEventPosition[0] = e[0];
  this->LastEventPosition[1] = e[1];
  this->LastEventPosition[2] = 0.0;

  this->ComputeInteractionState(static_cast<int>(e[0]), static_cast<int>(e[1]), 0);
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::WidgetInteraction(double e[2])
{
  // Convert events to appropriate coordinate systems
  vtkCamera* camera = this->Renderer->GetActiveCamera();
  if (!camera)
  {
    return;
  }
  double focalPoint[4], pickPoint[4], prevPickPoint[4];
  double z, vpn[3];

  // Compute the two points defining the motion vector
  vtkInteractorObserver::ComputeWorldToDisplay(this->Renderer, this->LastPickPosition[0],
    this->LastPickPosition[1], this->LastPickPosition[2], focalPoint);
  z = focalPoint[2];
  vtkInteractorObserver::ComputeDisplayToWorld(
    this->Renderer, this->LastEventPosition[0], this->LastEventPosition[1], z, prevPickPoint);
  vtkInteractorObserver::ComputeDisplayToWorld(this->Renderer, e[0], e[1], z, pickPoint);

  // Process the motion
  if (this->InteractionState == vtkCurveRepresentation::Moving)
  {
    if (this->CurrentHandleIndex != -1)
    {
      this->MovePoint(prevPickPoint, pickPoint);
    }
    else
    {
      this->Translate(prevPickPoint, pickPoint);
    }
  }
  else if (this->InteractionState == vtkCurveRepresentation::Scaling)
  {
    this->Scale(prevPickPoint, pickPoint, static_cast<int>(e[0]), static_cast<int>(e[1]));
  }
  else if (this->InteractionState == vtkCurveRepresentation::Spinning)
  {
    camera->GetViewPlaneNormal(vpn);
    this->Spin(prevPickPoint, pickPoint, vpn);
  }

  if (this->ProjectToPlane)
  {
    this->ProjectPointsToPlane();
  }

  this->BuildRepresentation();

  // Store the position
  this->LastEventPosition[0] = e[0];
  this->LastEventPosition[1] = e[1];
  this->LastEventPosition[2] = 0.0;
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::PushHandle(double* pos)
{
  vtkPoints* newpoints = vtkPoints::New(VTK_DOUBLE);
  newpoints->SetNumberOfPoints(this->NumberOfHandles + 1);

  if (this->FirstSelected)
  {
    // pushing front
    newpoints->SetPoint(0, pos);
    for (int i = 0; i < this->NumberOfHandles; ++i)
    {
      newpoints->SetPoint(i + 1, this->GetHandleSource(i)->GetPosition());
    }
  }
  else
  {
    // pushing back
    newpoints->SetPoint(this->NumberOfHandles, pos);
    for (int i = 0; i < this->NumberOfHandles; ++i)
    {
      newpoints->SetPoint(i, this->GetHandleSource(i)->GetPosition());
    }
  }

  this->InitializeHandles(newpoints);
  newpoints->Delete();
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::EndWidgetInteraction(double[2])
{
  switch (this->InteractionState)
  {
    case vtkCurveRepresentation::Pushing:
      this->PushHandle(this->LastPickPosition);
      break;

    case vtkCurveRepresentation::Inserting:
      this->SetCurrentHandleIndex(this->InsertHandleOnLine(this->LastPickPosition));
      break;

    case vtkCurveRepresentation::Erasing:
      if (this->CurrentHandleIndex)
      {
        int index = this->CurrentHandleIndex;
        this->SetCurrentHandleIndex(-1);
        this->EraseHandle(index);
      }
  }

  this->HighlightLine(0);
  this->InteractionState = vtkCurveRepresentation::Outside;
}

//------------------------------------------------------------------------------
double* vtkCurveRepresentation::GetBounds()
{
  this->BuildRepresentation();

  vtkBoundingBox bbox;
  bbox.AddBounds(this->LineActor->GetBounds());
  for (int cc = 0; cc < this->NumberOfHandles; cc++)
  {
    bbox.AddBounds(GetHandleSource(cc)->GetOutput()->GetBounds());
  }
  bbox.GetBounds(this->Bounds);
  return this->Bounds;
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::SetLineColor(double r, double g, double b)
{
  this->GetLineProperty()->SetColor(r, g, b);
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::SetInteractionColor(double r, double g, double b)
{
  this->SelectedHandleProperty->SetColor(r, g, b);
  this->SelectedLineProperty->SetColor(r, g, b);
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::SetForegroundColor(double r, double g, double b)
{
  this->HandleProperty->SetColor(r, g, b);
  this->LineProperty->SetColor(r, g, b);
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::GetActors(vtkPropCollection* pc)
{
  if (pc != nullptr && this->GetVisibility())
  {
    pc->AddItem(this->LineActor);
    for (int i = 0; i < this->GetNumberOfHandles(); ++i)
    {
      pc->AddItem(this->GetHandleActor(i));
    }
  }
  this->Superclass::GetActors(pc);
}

//------------------------------------------------------------------------------
void vtkCurveRepresentation::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);

  if (this->HandleProperty)
  {
    os << indent << "Handle Property: " << this->HandleProperty << "\n";
  }
  else
  {
    os << indent << "Handle Property: (none)\n";
  }
  if (this->SelectedHandleProperty)
  {
    os << indent << "Selected Handle Property: " << this->SelectedHandleProperty << "\n";
  }
  else
  {
    os << indent << "Selected Handle Property: (none)\n";
  }
  if (this->LineProperty)
  {
    os << indent << "Line Property: " << this->LineProperty << "\n";
  }
  else
  {
    os << indent << "Line Property: (none)\n";
  }
  if (this->SelectedLineProperty)
  {
    os << indent << "Selected Line Property: " << this->SelectedLineProperty << "\n";
  }
  else
  {
    os << indent << "Selected Line Property: (none)\n";
  }

  os << indent << "Project To Plane: " << (this->ProjectToPlane ? "On" : "Off") << "\n";
  os << indent << "Projection Normal: " << this->ProjectionNormal << "\n";
  os << indent << "Projection Position: " << this->ProjectionPosition << "\n";
  os << indent << "Number Of Handles: " << this->NumberOfHandles << "\n";
  os << indent << "Closed: " << (this->Closed ? "On" : "Off") << "\n";
  os << indent << "InteractionState: " << this->InteractionState << endl;
}
VTK_ABI_NAMESPACE_END
