// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
/**
 * @class   vtkProjectedTerrainPath
 * @brief   project a polyline onto a terrain
 *
 * vtkProjectedTerrainPath projects an input polyline onto a terrain. (The
 * terrain is defined by a 2D height image and is the second input to the
 * filter.) The polyline projection is controlled via several modes as
 * follows. 1) Simple mode projects the polyline points onto the terrain,
 * taking into account the height offset instance variable. 2) Non-occluded
 * mode insures that no parts of the polyline are occluded by the terrain
 * (e.g. a line passes through a mountain). This may require recursive
 * subdivision of the polyline. 3) Hug mode insures that the polyine points
 * remain within a constant distance from the surface. This may also require
 * recursive subdivision of the polyline. Note that both non-occluded mode
 * and hug mode also take into account the height offset, so it is possible
 * to create paths that hug terrain a certain distance above it. To use this
 * filter, define two inputs: 1) a polyline, and 2) an image whose scalar
 * values represent a height field. Then specify the mode, and the height
 * offset to use.
 *
 * An description of the algorithm is as follows. The filter begins by
 * projecting the polyline points to the image (offset by the specified
 * height offset).  If the mode is non-occluded or hug, then the maximum
 * error along each line segment is computed and placed into a priority
 * queue. Each line segment is then split at the point of maximum error, and
 * the two new line segments are evaluated for maximum error. This process
 * continues until the line is not occluded by the terrain (non-occluded
 * mode) or satisfies the error on variation from the surface (hug
 * mode). (Note this process is repeated for each polyline in the
 * input. Also, the maximum error is computed in two parts: a maximum
 * positive error and maximum negative error. If the polyline is above the
 * terrain--i.e., the height offset is positive--in non-occluded or hug mode
 * all negative errors are eliminated. If the polyline is below the
 * terrain--i.e., the height offset is negative--in non-occluded or hug mode
 * all positive errors are eliminated.)
 *
 * @warning
 * This algorithm requires the entire input image to be in memory, hence it
 * may not work for extremely large images.
 *
 * @warning
 * The input height image is assumed to be positioned in the x-y plane so the
 * scalar value is the z-coordinate, height value.
 *
 * @warning
 * A priority queue is used so that the 1) the total number of line segments
 * can be controlled, and 2) the algorithm can terminate when the errors in
 * the queue are less than the specified error tolerance.
 *
 * @sa
 * vtkGreedyTerrainDecimation
 */

#ifndef vtkProjectedTerrainPath_h
#define vtkProjectedTerrainPath_h

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

VTK_ABI_NAMESPACE_BEGIN
class vtkPriorityQueue;
class vtkImageData;
class vtkEdgeList;
class vtkPoints;

class VTKFILTERSHYBRID_EXPORT vtkProjectedTerrainPath : public vtkPolyDataAlgorithm
{
public:
  ///@{
  /**
   * Standard methods for printing and determining type information.
   */
  vtkTypeMacro(vtkProjectedTerrainPath, vtkPolyDataAlgorithm);
  void PrintSelf(ostream& os, vtkIndent indent) override;
  ///@}

  /**
   * Instantiate the class.
   */
  static vtkProjectedTerrainPath* New();

  ///@{
  /**
   * Specify the second input (the terrain) onto which the polyline(s) should be projected.
   * Note: This assigns a data object as the input terrain.
   * To establish a pipeline connection, use
   * SetSourceConnection() method.
   */
  void SetSourceData(vtkImageData* source);
  vtkImageData* GetSource();
  ///@}

  /**
   * Specify the second input (the terrain) onto which the polyline(s) should be projected.
   * Note: vtkImageData* is required
   */
  void SetSourceConnection(vtkAlgorithmOutput* algOutput);

  enum
  {
    SIMPLE_PROJECTION = 0,
    NONOCCLUDED_PROJECTION,
    HUG_PROJECTION
  };

  ///@{
  /**
   * Determine how to control the projection process. Simple projection
   * just projects the original polyline points. Non-occluded projection
   * insures that the polyline does not intersect the terrain surface.
   * Hug projection is similar to non-occulded projection except that
   * produces a path that is nearly parallel to the terrain (within the
   * user specified height tolerance).
   */
  vtkSetClampMacro(ProjectionMode, int, SIMPLE_PROJECTION, HUG_PROJECTION);
  vtkGetMacro(ProjectionMode, int);
  void SetProjectionModeToSimple() { this->SetProjectionMode(SIMPLE_PROJECTION); }
  void SetProjectionModeToNonOccluded() { this->SetProjectionMode(NONOCCLUDED_PROJECTION); }
  void SetProjectionModeToHug() { this->SetProjectionMode(HUG_PROJECTION); }
  ///@}

  ///@{
  /**
   * This is the height above (or below) the terrain that the projected
   * path should be. Positive values indicate distances above the terrain;
   * negative values indicate distances below the terrain.
   */
  vtkSetMacro(HeightOffset, double);
  vtkGetMacro(HeightOffset, double);
  ///@}

  ///@{
  /**
   * This is the allowable variation in the altitude of the path
   * with respect to the variation in the terrain. It only comes
   * into play if the hug projection mode is enabled.
   */
  vtkSetClampMacro(HeightTolerance, double, 0.0, VTK_FLOAT_MAX);
  vtkGetMacro(HeightTolerance, double);
  ///@}

  ///@{
  /**
   * This instance variable can be used to limit the total number of line
   * segments created during subdivision. Note that the number of input line
   * segments will be the minimum number that cab be output.
   */
  vtkSetClampMacro(MaximumNumberOfLines, vtkIdType, 1, VTK_ID_MAX);
  vtkGetMacro(MaximumNumberOfLines, vtkIdType);
  ///@}

protected:
  vtkProjectedTerrainPath();
  ~vtkProjectedTerrainPath() override;

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

  // Supporting methods
  void GetImageIndex(double x[3], double loc[2], int ij[2]);
  double GetHeight(double loc[2], int ij[2]);
  void ComputeError(vtkIdType edgeId);
  void RemoveOcclusions();
  void HugTerrain();
  void SplitEdge(vtkIdType eId, double t);

  // ivars that the API addresses
  int ProjectionMode;
  double HeightOffset;
  double HeightTolerance;
  vtkIdType MaximumNumberOfLines;

  // Bookkeeping arrays
  int Dimensions[3];
  int Extent[6];
  double Origin[3];
  double Spacing[3];
  vtkDataArray* Heights;
  vtkPoints* Points;
  vtkIdType NumLines;

  // Errors above/below terrain. In both instances, negative values are
  // inserted because the priority queue puts smallest values on top.
  vtkPriorityQueue* PositiveLineError; // errors above terrain
  vtkPriorityQueue* NegativeLineError; // errors below terrain

  // This is a PIMPL'd vector representing edges
  vtkEdgeList* EdgeList;

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

VTK_ABI_NAMESPACE_END
#endif
