// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
/**
 * @class   vtkImplicitCylinderRepresentation
 * @brief   defining the representation for a vtkImplicitCylinderWidget
 *
 * This class is a concrete representation for the
 * vtkImplicitCylinderWidget. It represents an infinite cylinder
 * defined by a radius, a center, and an axis. The cylinder is placed
 * within its associated bounding box and the intersection of the
 * cylinder with the bounding box is shown to visually indicate the
 * orientation and position of the representation. This cylinder
 * representation can be manipulated by using the
 * vtkImplicitCylinderWidget to adjust the cylinder radius, axis,
 * and/or center point. (Note that the bounding box is defined during
 * invocation of the superclass' PlaceWidget() method.)
 *
 * To use this representation, you normally specify a radius, center,
 * and axis. Optionally you can specify a minimum and maximum radius,
 * and a resolution for the cylinder. Finally, place the widget and
 * its representation in the scene using PlaceWidget().
 *
 * @sa
 * vtkImplicitCylinderWidget vtkImplicitPlaneWidget vtkImplicitPlaneWidget
 */

#ifndef vtkImplicitCylinderRepresentation_h
#define vtkImplicitCylinderRepresentation_h

#include "vtkInteractionWidgetsModule.h" // For export macro
#include "vtkWidgetRepresentation.h"
#include "vtkWrappingHints.h" // For VTK_MARSHALAUTO

VTK_ABI_NAMESPACE_BEGIN
class vtkActor;
class vtkPolyDataMapper;
class vtkCellPicker;
class vtkConeSource;
class vtkLineSource;
class vtkSphereSource;
class vtkTubeFilter;
class vtkCylinder;
class vtkProperty;
class vtkImageData;
class vtkOutlineFilter;
class vtkFeatureEdges;
class vtkPolyData;
class vtkPolyDataAlgorithm;
class vtkTransform;
class vtkBox;
class vtkLookupTable;

#define VTK_MAX_CYL_RESOLUTION 2048

class VTKINTERACTIONWIDGETS_EXPORT VTK_MARSHALAUTO vtkImplicitCylinderRepresentation
  : public vtkWidgetRepresentation
{
public:
  /**
   * Instantiate the class.
   */
  static vtkImplicitCylinderRepresentation* New();

  ///@{
  /**
   * Standard methods for the class.
   */
  vtkTypeMacro(vtkImplicitCylinderRepresentation, vtkWidgetRepresentation);
  void PrintSelf(ostream& os, vtkIndent indent) override;
  ///@}

  ///@{
  /**
   * Get the center of the cylinder. The center is located along the
   * cylinder axis.
   */
  void SetCenter(double x, double y, double z);
  void SetCenter(double x[3]);
  double* GetCenter() VTK_SIZEHINT(3);
  void GetCenter(double xyz[3]);
  ///@}

  ///@{
  /**
   * Set/Get the axis of rotation for the cylinder. If the axis is not
   * specified as a unit vector, it will be normalized.
   */
  void SetAxis(double x, double y, double z);
  void SetAxis(double a[3]);
  double* GetAxis() VTK_SIZEHINT(3);
  void GetAxis(double a[3]);
  ///@}

  ///@{
  /**
   * Set/Get the radius of the cylinder. Note that if the radius is
   * too big the cylinder will be outside of the bounding box.
   */
  void SetRadius(double r);
  double GetRadius();
  ///@}

  ///@{
  /**
   * Set/Get the minimum and maximum radius of the cylinder. This
   * helps prevent the cylinder from "disappearing" during
   * interaction.  Note that the minimum and maximum radius is
   * specified as a fraction of the diagonal length of the widget
   * bounding box.
   */
  vtkSetClampMacro(MinRadius, double, 0.001, 0.25);
  vtkGetMacro(MinRadius, double);
  vtkSetClampMacro(MaxRadius, double, 0.25, VTK_FLOAT_MAX);
  vtkGetMacro(MaxRadius, double);
  ///@}

  ///@{
  /**
   * Force the cylinder widget to be aligned with one of the x-y-z axes.
   * If one axis is set on, the other two will be set off.
   * Remember that when the state changes, a ModifiedEvent is invoked.
   * This can be used to snap the cylinder to the axes if it is originally
   * not aligned.
   */
  void SetAlongXAxis(vtkTypeBool);
  vtkGetMacro(AlongXAxis, vtkTypeBool);
  vtkBooleanMacro(AlongXAxis, vtkTypeBool);
  void SetAlongYAxis(vtkTypeBool);
  vtkGetMacro(AlongYAxis, vtkTypeBool);
  vtkBooleanMacro(AlongYAxis, vtkTypeBool);
  void SetAlongZAxis(vtkTypeBool);
  vtkGetMacro(AlongZAxis, vtkTypeBool);
  vtkBooleanMacro(AlongZAxis, vtkTypeBool);
  ///@}

  ///@{
  /**
   * Enable/disable the drawing of the cylinder. In some cases the cylinder
   * interferes with the object that it is operating on (e.g., the
   * cylinder interferes with the cut surface it produces resulting in
   * z-buffer artifacts.) By default it is off.
   */
  void SetDrawCylinder(vtkTypeBool drawCyl);
  vtkGetMacro(DrawCylinder, vtkTypeBool);
  vtkBooleanMacro(DrawCylinder, vtkTypeBool);
  ///@}

  ///@{
  /**
   * Set/Get the resolution of the cylinder. This is the number of
   * polygonal facets used to approximate the curved cylindrical
   * surface (for rendering purposes). An vtkCylinder is used under
   * the hood to provide an exact surface representation.
   */
  vtkSetClampMacro(Resolution, int, 8, VTK_MAX_CYL_RESOLUTION);
  vtkGetMacro(Resolution, int);
  ///@}

  ///@{
  /**
   * Turn on/off tubing of the wire outline of the cylinder
   * intersection (against the bounding box). The tube thickens the
   * line by wrapping with a vtkTubeFilter.
   */
  vtkSetMacro(Tubing, vtkTypeBool);
  vtkGetMacro(Tubing, vtkTypeBool);
  vtkBooleanMacro(Tubing, vtkTypeBool);
  ///@}

  ///@{
  /**
   * Turn on/off the ability to translate the bounding box by moving it
   * with the mouse.
   */
  vtkSetMacro(OutlineTranslation, vtkTypeBool);
  vtkGetMacro(OutlineTranslation, vtkTypeBool);
  vtkBooleanMacro(OutlineTranslation, vtkTypeBool);
  ///@}

  ///@{
  /**
   * Turn on/off the ability to move the widget outside of the bounds
   * specified in the PlaceWidget() invocation.
   */
  vtkSetMacro(OutsideBounds, vtkTypeBool);
  vtkGetMacro(OutsideBounds, vtkTypeBool);
  vtkBooleanMacro(OutsideBounds, vtkTypeBool);
  ///@}

  ///@{
  /**
   * Set/Get the bounds of the widget representation. PlaceWidget can also be
   * used to set the bounds of the widget but it may also have other effects
   * on the internal state of the representation. Use this function when only
   * the widget bounds are needs to be modified.
   */
  vtkSetVector6Macro(WidgetBounds, double);
  vtkGetVector6Macro(WidgetBounds, double);
  ///@}

  ///@{
  /**
   * Turn on/off whether the cylinder should be constrained to the widget bounds.
   * If on, the center will not be allowed to move outside the set widget bounds
   * and the radius will be limited by MinRadius and MaxRadius. This is the
   * default behaviour.
   * If off, the center can be freely moved and the radius can be set to
   * arbitrary values. The widget outline will change accordingly.
   */
  vtkSetMacro(ConstrainToWidgetBounds, vtkTypeBool);
  vtkGetMacro(ConstrainToWidgetBounds, vtkTypeBool);
  vtkBooleanMacro(ConstrainToWidgetBounds, vtkTypeBool);
  ///@}

  ///@{
  /**
   * Turn on/off the ability to scale the widget with the mouse.
   */
  vtkSetMacro(ScaleEnabled, vtkTypeBool);
  vtkGetMacro(ScaleEnabled, vtkTypeBool);
  vtkBooleanMacro(ScaleEnabled, vtkTypeBool);
  ///@}

  /**
   * Get the implicit function for the cylinder. The user must provide the
   * instance of the class vtkCylinder. Note that vtkCylinder is a subclass of
   * vtkImplicitFunction, meaning that it can be used by a variety of filters
   * to perform clipping, cutting, and selection of data.
   */
  void GetCylinder(vtkCylinder* cyl);

  /**
   * Grab the polydata that defines the cylinder. The polydata contains
   * polygons that are clipped by the bounding box.
   */
  void GetPolyData(vtkPolyData* pd);

  /**
   * Satisfies the superclass API.  This will change the state of the widget
   * to match changes that have been made to the underlying PolyDataSource.
   */
  void UpdatePlacement();

  ///@{
  /**
   * Get the properties on the axis (line and cone).
   */
  vtkGetObjectMacro(AxisProperty, vtkProperty);
  vtkGetObjectMacro(SelectedAxisProperty, vtkProperty);
  ///@}

  ///@{
  /**
   * Get the cylinder properties. The properties of the cylinder when selected
   * and unselected can be manipulated.
   */
  vtkGetObjectMacro(CylinderProperty, vtkProperty);
  vtkGetObjectMacro(SelectedCylinderProperty, vtkProperty);
  ///@}

  ///@{
  /**
   * Get the property of the outline.
   */
  vtkGetObjectMacro(OutlineProperty, vtkProperty);
  vtkGetObjectMacro(SelectedOutlineProperty, vtkProperty);
  ///@}

  ///@{
  /**
   * Get the property of the intersection edges. (This property also
   * applies to the edges when tubed.)
   */
  vtkGetObjectMacro(EdgesProperty, vtkProperty);
  ///@}

  ///@{
  /**
   * Set the color of all the widgets handles (edges, axis, selected cylinder)
   * and their color during interaction. Foreground color applies to the outlines and unselected
   * cylinder.
   */
  void SetInteractionColor(double, double, double);
  void SetInteractionColor(double c[3]) { this->SetInteractionColor(c[0], c[1], c[2]); }
  void SetHandleColor(double, double, double);
  void SetHandleColor(double c[3]) { this->SetHandleColor(c[0], c[1], c[2]); }
  void SetForegroundColor(double, double, double);
  void SetForegroundColor(double c[3]) { this->SetForegroundColor(c[0], c[1], c[2]); }
  ///@}

  ///@{
  /**
   * Set color to the edge
   */
  VTK_DEPRECATED_IN_9_3_0("Please use GetEdgesProperty or SetHandleColor instead.")
  void SetEdgeColor(vtkLookupTable*) {}
  VTK_DEPRECATED_IN_9_3_0("Please use GetEdgesProperty or SetHandleColor instead.")
  void SetEdgeColor(double, double, double) {}
  VTK_DEPRECATED_IN_9_3_0("Please use GetEdgesProperty or SetHandleColor instead.")
  void SetEdgeColor(double[3]) {}
  ///@}

  ///@{
  /**
   * Methods to interface with the vtkImplicitCylinderWidget.
   */
  int ComputeInteractionState(int X, int Y, int modify = 0) override;
  void PlaceWidget(double bounds[6]) override;
  void BuildRepresentation() override;
  void StartWidgetInteraction(double eventPos[2]) override;
  void WidgetInteraction(double newEventPos[2]) override;
  void EndWidgetInteraction(double newEventPos[2]) override;
  ///@}

  ///@{
  /**
   * Methods supporting the rendering process.
   */
  double* GetBounds() override;
  void GetActors(vtkPropCollection* pc) override;
  void ReleaseGraphicsResources(vtkWindow*) override;
  int RenderOpaqueGeometry(vtkViewport*) override;
  int RenderTranslucentPolygonalGeometry(vtkViewport*) override;
  vtkTypeBool HasTranslucentPolygonalGeometry() override;
  ///@}

  ///@{
  /**
   * Specify a translation distance used by the BumpCylinder() method. Note that the
   * distance is normalized; it is the fraction of the length of the bounding
   * box of the wire outline.
   */
  vtkSetClampMacro(BumpDistance, double, 0.000001, 1);
  vtkGetMacro(BumpDistance, double);
  ///@}

  /**
   * Translate the cylinder in the direction of the view vector by the
   * specified BumpDistance. The dir parameter controls which
   * direction the pushing occurs, either in the same direction as the
   * view vector, or when negative, in the opposite direction.  The factor
   * controls what percentage of the bump is used.
   */
  void BumpCylinder(int dir, double factor);

  /**
   * Push the cylinder the distance specified along the view
   * vector. Positive values are in the direction of the view vector;
   * negative values are in the opposite direction. The distance value
   * is expressed in world coordinates.
   */
  void PushCylinder(double distance);

  // Manage the state of the widget
  enum InteractionStateType
  {
    Outside = 0,
    Moving,
    MovingOutline,
    MovingCenter,
    RotatingAxis,
    AdjustingRadius,
    Scaling,
    TranslatingCenter
  };

  ///@{
  /**
   * The interaction state may be set from a widget (e.g.,
   * vtkImplicitCylinderWidget) or other object. This controls how the
   * interaction with the widget proceeds. Normally this method is used as
   * part of a handshaking process with the widget: First
   * ComputeInteractionState() is invoked that returns a state based on
   * geometric considerations (i.e., cursor near a widget feature), then
   * based on events, the widget may modify this further.
   */
  vtkSetClampMacro(InteractionState, int, Outside, TranslatingCenter);
  ///@}

  ///@{
  /**
   * Sets the visual appearance of the representation based on the
   * state it is in. This state is usually the same as InteractionState.
   */
  virtual void SetRepresentationState(int);
  vtkGetMacro(RepresentationState, int);
  ///@}

  /*
   * Register internal Pickers within PickingManager
   */
  void RegisterPickers() override;

  ///@{
  /**
   * Gets/Sets the constraint axis for translations. Returns Axis::NONE
   * if none.
   **/
  vtkGetMacro(TranslationAxis, int);
  vtkSetClampMacro(TranslationAxis, int, -1, 2);
  ///@}

  ///@{
  /**
   * Toggles constraint translation axis on/off.
   */
  void SetXTranslationAxisOn() { this->TranslationAxis = Axis::XAxis; }
  void SetYTranslationAxisOn() { this->TranslationAxis = Axis::YAxis; }
  void SetZTranslationAxisOn() { this->TranslationAxis = Axis::ZAxis; }
  void SetTranslationAxisOff() { this->TranslationAxis = Axis::NONE; }
  ///@}

  ///@{
  /**
   * Returns true if ConstrainedAxis
   **/
  bool IsTranslationConstrained() { return this->TranslationAxis != Axis::NONE; }
  ///@}

protected:
  vtkImplicitCylinderRepresentation();
  ~vtkImplicitCylinderRepresentation() override;

  int RepresentationState;

  int TranslationAxis;

  // Keep track of event positions
  double LastEventPosition[3];

  // Control the radius
  double MinRadius;
  double MaxRadius;

  // Controlling the push operation
  double BumpDistance;

  // Controlling ivars
  vtkTypeBool AlongXAxis;
  vtkTypeBool AlongYAxis;
  vtkTypeBool AlongZAxis;

  // The actual cylinder which is being manipulated
  vtkCylinder* Cylinder;

  // The facet resolution for rendering purposes.
  int Resolution;

  // The bounding box is represented by a single voxel image data
  vtkImageData* Box;
  vtkOutlineFilter* Outline;
  vtkPolyDataMapper* OutlineMapper;
  vtkActor* OutlineActor;
  void HighlightOutline(int highlight);
  vtkTypeBool OutlineTranslation; // whether the outline can be moved
  vtkTypeBool ScaleEnabled;       // whether the widget can be scaled
  vtkTypeBool OutsideBounds;      // whether the widget can be moved outside input's bounds
  double WidgetBounds[6];
  int ConstrainToWidgetBounds;

  // The cut cylinder is produced with a vtkCutter
  vtkPolyData* Cyl;
  vtkPolyDataMapper* CylMapper;
  vtkActor* CylActor;
  vtkTypeBool DrawCylinder;
  void HighlightCylinder(int highlight);

  // Optional tubes are represented by extracting boundary edges and tubing
  vtkFeatureEdges* Edges;
  vtkTubeFilter* EdgesTuber;
  vtkPolyDataMapper* EdgesMapper;
  vtkActor* EdgesActor;
  vtkTypeBool Tubing; // control whether tubing is on

  // The + normal cone (i.e., in positive direction along normal)
  vtkConeSource* ConeSource;
  vtkPolyDataMapper* ConeMapper;
  vtkActor* ConeActor;
  void HighlightNormal(int highlight);

  // The + normal line
  vtkLineSource* LineSource;
  vtkPolyDataMapper* LineMapper;
  vtkActor* LineActor;

  // The - normal cone
  vtkConeSource* ConeSource2;
  vtkPolyDataMapper* ConeMapper2;
  vtkActor* ConeActor2;

  // The - normal line
  vtkLineSource* LineSource2;
  vtkPolyDataMapper* LineMapper2;
  vtkActor* LineActor2;

  // The center positioning handle
  vtkSphereSource* Sphere;
  vtkPolyDataMapper* SphereMapper;
  vtkActor* SphereActor;

  // Do the picking
  vtkCellPicker* Picker;
  vtkCellPicker* CylPicker;

  // Transform the normal (used for rotation)
  vtkTransform* Transform;

  // Methods to manipulate the cylinder
  void Rotate(double X, double Y, double* p1, double* p2, double* vpn);
  void TranslateCylinder(double* p1, double* p2);
  void TranslateOutline(double* p1, double* p2);
  void TranslateCenter(double* p1, double* p2);
  void TranslateCenterOnAxis(double* p1, double* p2);
  void ScaleRadius(double* p1, double* p2);
  void AdjustRadius(double X, double Y, double* p1, double* p2);
  void Scale(double* p1, double* p2, double X, double Y);
  void SizeHandles();

  // Properties used to control the appearance of selected objects and
  // the manipulator in general.
  vtkProperty* AxisProperty;
  vtkProperty* SelectedAxisProperty;
  vtkProperty* CylinderProperty;
  vtkProperty* SelectedCylinderProperty;
  vtkProperty* OutlineProperty;
  vtkProperty* SelectedOutlineProperty;
  vtkProperty* EdgesProperty;
  void CreateDefaultProperties();

  // Intersect oriented infinite cylinder against bounding box
  void BuildCylinder();

  // Support GetBounds() method
  vtkBox* BoundingBox;

private:
  vtkImplicitCylinderRepresentation(const vtkImplicitCylinderRepresentation&) = delete;
  void operator=(const vtkImplicitCylinderRepresentation&) = delete;
};

VTK_ABI_NAMESPACE_END
#endif
