/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkHyperStreamline.h

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
/**
 * @class   vtkHyperStreamline
 * @brief   generate hyperstreamline in arbitrary dataset
 *
 * vtkHyperStreamline is a filter that integrates through a tensor field to
 * generate a hyperstreamline. The integration is along the maximum eigenvector
 * and the cross section of the hyperstreamline is defined by the two other
 * eigenvectors. Thus the shape of the hyperstreamline is "tube-like", with
 * the cross section being elliptical. Hyperstreamlines are used to visualize
 * tensor fields.
 *
 * The starting point of a hyperstreamline can be defined in one of two ways.
 * First, you may specify an initial position. This is a x-y-z global
 * coordinate. The second option is to specify a starting location. This is
 * cellId, subId, and cell parametric coordinates.
 *
 * The integration of the hyperstreamline occurs through the major eigenvector
 * field. IntegrationStepLength controls the step length within each cell
 * (i.e., this is the fraction of the cell length). The length of the
 * hyperstreamline is controlled by MaximumPropagationDistance. This parameter
 * is the length of the hyperstreamline in units of distance. The tube itself
 * is composed of many small sub-tubes - NumberOfSides controls the number of
 * sides in the tube, and StepLength controls the length of the sub-tubes.
 *
 * Because hyperstreamlines are often created near regions of singularities, it
 * is possible to control the scaling of the tube cross section by using a
 * logarithmic scale. Use LogScalingOn to turn this capability on. The Radius
 * value controls the initial radius of the tube.
 *
 * @sa
 * vtkTensorGlyph
 */

#ifndef vtkHyperStreamline_h
#define vtkHyperStreamline_h

#include "vtkFiltersGeneralModule.h" // For export macro
#include "vtkPolyDataAlgorithm.h"

#define VTK_INTEGRATE_FORWARD 0
#define VTK_INTEGRATE_BACKWARD 1
#define VTK_INTEGRATE_BOTH_DIRECTIONS 2

#define VTK_INTEGRATE_MAJOR_EIGENVECTOR 0
#define VTK_INTEGRATE_MEDIUM_EIGENVECTOR 1
#define VTK_INTEGRATE_MINOR_EIGENVECTOR 2

class vtkHyperArray;

class VTKFILTERSGENERAL_EXPORT vtkHyperStreamline : public vtkPolyDataAlgorithm
{
public:
  vtkTypeMacro(vtkHyperStreamline, vtkPolyDataAlgorithm);
  void PrintSelf(ostream& os, vtkIndent indent) override;

  /**
   * Construct object with initial starting position (0,0,0); integration
   * step length 0.2; step length 0.01; forward integration; terminal
   * eigenvalue 0.0; number of sides 6; radius 0.5; and logarithmic scaling
   * off.
   */
  static vtkHyperStreamline* New();

  /**
   * Specify the start of the hyperstreamline in the cell coordinate system.
   * That is, cellId and subId (if composite cell), and parametric coordinates.
   */
  void SetStartLocation(vtkIdType cellId, int subId, double pcoords[3]);

  /**
   * Specify the start of the hyperstreamline in the cell coordinate system.
   * That is, cellId and subId (if composite cell), and parametric coordinates.
   */
  void SetStartLocation(vtkIdType cellId, int subId, double r, double s, double t);

  /**
   * Get the starting location of the hyperstreamline in the cell coordinate
   * system. Returns the cell that the starting point is in.
   */
  vtkIdType GetStartLocation(int& subId, double pcoords[3]);

  /**
   * Specify the start of the hyperstreamline in the global coordinate system.
   * Starting from position implies that a search must be performed to find
   * initial cell to start integration from.
   */
  void SetStartPosition(double x[3]);

  /**
   * Specify the start of the hyperstreamline in the global coordinate system.
   * Starting from position implies that a search must be performed to find
   * initial cell to start integration from.
   */
  void SetStartPosition(double x, double y, double z);

  /**
   * Get the start position of the hyperstreamline in global x-y-z coordinates.
   */
  double* GetStartPosition() VTK_SIZEHINT(3);

  //@{
  /**
   * Set / get the maximum length of the hyperstreamline expressed as absolute
   * distance (i.e., arc length) value.
   */
  vtkSetClampMacro(MaximumPropagationDistance, double, 0.0, VTK_DOUBLE_MAX);
  vtkGetMacro(MaximumPropagationDistance, double);
  //@}

  //@{
  /**
   * Set / get the eigenvector field through which to ingrate. It is
   * possible to integrate using the major, medium or minor
   * eigenvector field.  The major eigenvector is the eigenvector
   * whose corresponding eigenvalue is closest to positive infinity.
   * The minor eigenvector is the eigenvector whose corresponding
   * eigenvalue is closest to negative infinity.  The medium
   * eigenvector is the eigenvector whose corresponding eigenvalue is
   * between the major and minor eigenvalues.
   */
  vtkSetClampMacro(
    IntegrationEigenvector, int, VTK_INTEGRATE_MAJOR_EIGENVECTOR, VTK_INTEGRATE_MINOR_EIGENVECTOR);
  vtkGetMacro(IntegrationEigenvector, int);
  void SetIntegrationEigenvectorToMajor()
  {
    this->SetIntegrationEigenvector(VTK_INTEGRATE_MAJOR_EIGENVECTOR);
  }
  void SetIntegrationEigenvectorToMedium()
  {
    this->SetIntegrationEigenvector(VTK_INTEGRATE_MEDIUM_EIGENVECTOR);
  }
  void SetIntegrationEigenvectorToMinor()
  {
    this->SetIntegrationEigenvector(VTK_INTEGRATE_MINOR_EIGENVECTOR);
  }
  //@}

  /**
   * Use the major eigenvector field as the vector field through which
   * to integrate.  The major eigenvector is the eigenvector whose
   * corresponding eigenvalue is closest to positive infinity.
   */
  void IntegrateMajorEigenvector() { this->SetIntegrationEigenvectorToMajor(); }

  /**
   * Use the medium eigenvector field as the vector field through which
   * to integrate. The medium eigenvector is the eigenvector whose
   * corresponding eigenvalue is between the major and minor
   * eigenvalues.
   */
  void IntegrateMediumEigenvector() { this->SetIntegrationEigenvectorToMedium(); }

  /**
   * Use the minor eigenvector field as the vector field through which
   * to integrate. The minor eigenvector is the eigenvector whose
   * corresponding eigenvalue is closest to negative infinity.
   */
  void IntegrateMinorEigenvector() { this->SetIntegrationEigenvectorToMinor(); }

  //@{
  /**
   * Set / get a nominal integration step size (expressed as a fraction of
   * the size of each cell).
   */
  vtkSetClampMacro(IntegrationStepLength, double, 0.001, 0.5);
  vtkGetMacro(IntegrationStepLength, double);
  //@}

  //@{
  /**
   * Set / get the length of a tube segment composing the
   * hyperstreamline. The length is specified as a fraction of the
   * diagonal length of the input bounding box.
   */
  vtkSetClampMacro(StepLength, double, 0.000001, 1.0);
  vtkGetMacro(StepLength, double);
  //@}

  //@{
  /**
   * Specify the direction in which to integrate the hyperstreamline.
   */
  vtkSetClampMacro(IntegrationDirection, int, VTK_INTEGRATE_FORWARD, VTK_INTEGRATE_BOTH_DIRECTIONS);
  vtkGetMacro(IntegrationDirection, int);
  void SetIntegrationDirectionToForward() { this->SetIntegrationDirection(VTK_INTEGRATE_FORWARD); }
  void SetIntegrationDirectionToBackward()
  {
    this->SetIntegrationDirection(VTK_INTEGRATE_BACKWARD);
  }
  void SetIntegrationDirectionToIntegrateBothDirections()
  {
    this->SetIntegrationDirection(VTK_INTEGRATE_BOTH_DIRECTIONS);
  }
  //@}

  //@{
  /**
   * Set/get terminal eigenvalue.  If major eigenvalue falls below this
   * value, hyperstreamline terminates propagation.
   */
  vtkSetClampMacro(TerminalEigenvalue, double, 0.0, VTK_DOUBLE_MAX);
  vtkGetMacro(TerminalEigenvalue, double);
  //@}

  //@{
  /**
   * Set / get the number of sides for the hyperstreamlines. At a minimum,
   * number of sides is 3.
   */
  vtkSetClampMacro(NumberOfSides, int, 3, VTK_INT_MAX);
  vtkGetMacro(NumberOfSides, int);
  //@}

  //@{
  /**
   * Set / get the initial tube radius. This is the maximum "elliptical"
   * radius at the beginning of the tube. Radius varies based on ratio of
   * eigenvalues.  Note that tube section is actually elliptical and may
   * become a point or line in cross section in some cases.
   */
  vtkSetClampMacro(Radius, double, 0.0001, VTK_DOUBLE_MAX);
  vtkGetMacro(Radius, double);
  //@}

  //@{
  /**
   * Turn on/off logarithmic scaling. If scaling is on, the log base 10
   * of the computed eigenvalues are used to scale the cross section radii.
   */
  vtkSetMacro(LogScaling, vtkTypeBool);
  vtkGetMacro(LogScaling, vtkTypeBool);
  vtkBooleanMacro(LogScaling, vtkTypeBool);
  //@}

protected:
  vtkHyperStreamline();
  ~vtkHyperStreamline() override;

  // Integrate data
  int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override;
  int BuildTube(vtkDataSet* input, vtkPolyData* output);

  int FillInputPortInformation(int port, vtkInformation* info) override;

  // Flag indicates where streamlines start from (either position or location)
  int StartFrom;

  // Starting from cell location
  vtkIdType StartCell;
  int StartSubId;
  double StartPCoords[3];

  // starting from global x-y-z position
  double StartPosition[3];

  // array of hyperstreamlines
  vtkHyperArray* Streamers;
  int NumberOfStreamers;

  // length of hyperstreamline in absolute distance
  double MaximumPropagationDistance;

  // integration direction
  int IntegrationDirection;

  // the length (fraction of cell size) of integration steps
  double IntegrationStepLength;

  // the length of the tube segments composing the hyperstreamline
  double StepLength;

  // terminal propagation speed
  double TerminalEigenvalue;

  // number of sides of tube
  int NumberOfSides;

  // maximum radius of tube
  double Radius;

  // boolean controls whether scaling is clamped
  vtkTypeBool LogScaling;

  // which eigenvector to use as integration vector field
  int IntegrationEigenvector;

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

#endif
