// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
/**
 * @class   vtkFitToHeightMapFilter
 * @brief   adjust polydata to fit image height map
 *
 * vtkFitToHeightMapFilter "drapes" or "covers" a height map image by
 * determining new point coordinates of an input polydata by projecting (in
 * the z-direction) onto the height map. Different projection approaches can
 * be used including projecting points, or averaging / taking the minimum /
 * taking the maximum of the cell's points or sampled interior points.  The
 * filter passes the input to the output, however new new point coordinates
 * are generated, and point normals are not passed through. Note that the
 * draping supports verts, lines, polygons, and triangle strips.
 *
 * @warning
 * Since new point coordinates are generated, planar polygons may become
 * non-planar. To guarantee planarity, use a triangle mesh as input. Also
 * note that strategies based on averaging across a cell, or the points of a
 * cell, set the z-value for all of the cell's points to the same
 * value. However, as currently written, depending on the order in which
 * cells are processed, the last z-value set is the final value (since points
 * may be shared by multiple cells). Thus the filter works best with triangle
 * meshes, or if cells are topologically disconnected (i.e., points are only
 * used by one cell - use vtkShrinkFilter to topologically disconnect
 * the cells in a mesh).
 *
 * @warning
 * The point normals are not passed through to the output as the projection
 * process may distort the projected polydata surface.
 *
 * @warning
 * The interior cell sampling is currently carried out by triangulating the
 * cells, and then taking the centroid of each resulting triangle. Thus the
 * number of samples for each cell is (number of cell points - 2). Future
 * implementations may use a different sampling strategy.
 *
 * @warning
 * Points outside of the height map image are clamped to the boundary of the
 * height map. This may produce unexpected behavior in some cases.
 *
 * @warning
 * This class has been threaded with vtkSMPTools. Using TBB or other
 * non-sequential type (set in the CMake variable
 * VTK_SMP_IMPLEMENTATION_TYPE) may improve performance significantly.
 *
 * @sa
 * vtkTrimmedExtrusionFilter vtkShrinkFilter vtkShrinkPolyData
 */

#ifndef vtkFitToHeightMapFilter_h
#define vtkFitToHeightMapFilter_h

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

VTK_ABI_NAMESPACE_BEGIN
class vtkImageData;

class VTKFILTERSMODELING_EXPORT vtkFitToHeightMapFilter : public vtkPolyDataAlgorithm
{
public:
  ///@{
  /**
   * Standard methods for construction, type and printing.
   */
  static vtkFitToHeightMapFilter* New();
  vtkTypeMacro(vtkFitToHeightMapFilter, vtkPolyDataAlgorithm);
  void PrintSelf(ostream& os, vtkIndent indent) override;
  ///@}

  /**
   * Set the height map for the filter.  Note that this method does not
   * connect the pipeline. The algorithm will work on the input data as it is
   * without updating the producer of the data.  See SetHeightMapConnection()
   * for connecting the pipeline.
   */
  void SetHeightMapData(vtkImageData* idata);

  ///@{
  /**
   * Specify the pipeline connection to the height map.
   */
  void SetHeightMapConnection(vtkAlgorithmOutput* algOutput);
  ///@}

  ///@{
  /**
   * Get a pointer to the height map.
   */
  vtkImageData* GetHeightMap();
  vtkImageData* GetHeightMap(vtkInformationVector* sourceInfo);
  ///@}

  // Strategies to fit the polydata.
  enum FittingStrategy
  {
    POINT_PROJECTION = 0,
    POINT_MINIMUM_HEIGHT = 1,
    POINT_MAXIMUM_HEIGHT = 2,
    POINT_AVERAGE_HEIGHT = 3,
    CELL_MINIMUM_HEIGHT = 4,
    CELL_MAXIMUM_HEIGHT = 5,
    CELL_AVERAGE_HEIGHT = 6,
  };

  ///@{
  /**
   * Specify a strategy for fitting, or projecting, the polydata to the
   * height field.  By default the points of the polydata are projected onto
   * the height field (POINT_PROJECTION).  However, to preserve planarity,
   * or to fit the polydata cells at the average/minimum/maximum height to
   * the height field, different strategies can be used (i.e., placing the
   * cells). The point-based strategies (e.g, POINT_MINIMUM_HEIGHT) set the
   * cell at the minimum height of the cell's points (and so on). The
   * cell-based strategies (e.g., CELL_MINIMUM_HEIGHT) sample the interior of
   * the cell and place the cell at the minimum height (and so on) of the
   * cell's sampled interior points.
   */
  vtkSetMacro(FittingStrategy, int);
  vtkGetMacro(FittingStrategy, int);
  void SetFittingStrategyToPointProjection() { this->SetFittingStrategy(POINT_PROJECTION); }
  void SetFittingStrategyToPointMinimumHeight() { this->SetFittingStrategy(POINT_MINIMUM_HEIGHT); }
  void SetFittingStrategyToPointMaximumHeight() { this->SetFittingStrategy(POINT_MAXIMUM_HEIGHT); }
  void SetFittingStrategyToAverageHeight() { this->SetFittingStrategy(POINT_AVERAGE_HEIGHT); }
  void SetFittingStrategyToCellMinimumHeight() { this->SetFittingStrategy(CELL_MINIMUM_HEIGHT); }
  void SetFittingStrategyToCellMaximumHeight() { this->SetFittingStrategy(CELL_MAXIMUM_HEIGHT); }
  void SetFittingStrategyToCellAverageHeight() { this->SetFittingStrategy(CELL_AVERAGE_HEIGHT); }
  ///@}

  ///@{
  /**
   * Indicate whether the z-offset from the image height map should be added
   * to the final result. Some height map images are offset in z-coordinate
   * which is independent of the height map values. By default this value is
   * true.
   */
  vtkSetMacro(UseHeightMapOffset, vtkTypeBool);
  vtkGetMacro(UseHeightMapOffset, vtkTypeBool);
  vtkBooleanMacro(UseHeightMapOffset, vtkTypeBool);
  ///@}

protected:
  vtkFitToHeightMapFilter();
  ~vtkFitToHeightMapFilter() override;

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

  int FittingStrategy;
  vtkTypeBool UseHeightMapOffset;
  double Offset;

  void AdjustPoints(vtkPolyData* output, vtkIdType numCells, vtkPoints* newPts);
  void AdjustCells(
    vtkPolyData* output, vtkIdType numCells, double* cellHts, vtkPoints* inPts, vtkPoints* newPts);

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

VTK_ABI_NAMESPACE_END
#endif
