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

  Program:   Visualization Toolkit
  Module:    vtkGaussianSplatter.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   vtkGaussianSplatter
 * @brief   splat points into a volume with an elliptical, Gaussian distribution
 *
 * vtkGaussianSplatter is a filter that injects input points into a
 * structured points (volume) dataset. As each point is injected, it "splats"
 * or distributes values to nearby voxels. Data is distributed using an
 * elliptical, Gaussian distribution function. The distribution function is
 * modified using scalar values (expands distribution) or normals
 * (creates ellipsoidal distribution rather than spherical).
 *
 * In general, the Gaussian distribution function f(x) around a given
 * splat point p is given by
 *
 *     f(x) = ScaleFactor * exp( ExponentFactor*((r/Radius)**2) )
 *
 * where x is the current voxel sample point; r is the distance |x-p|
 * ExponentFactor <= 0.0, and ScaleFactor can be multiplied by the scalar
 * value of the point p that is currently being splatted.
 *
 * If points normals are present (and NormalWarping is on), then the splat
 * function becomes elliptical (as compared to the spherical one described
 * by the previous equation). The Gaussian distribution function then
 * becomes:
 *
 *     f(x) = ScaleFactor *
 *               exp( ExponentFactor*( ((rxy/E)**2 + z**2)/R**2) )
 *
 * where E is a user-defined eccentricity factor that controls the elliptical
 * shape of the splat; z is the distance of the current voxel sample point
 * along normal N; and rxy is the distance of x in the direction
 * prependicular to N.
 *
 * This class is typically used to convert point-valued distributions into
 * a volume representation. The volume is then usually iso-surfaced or
 * volume rendered to generate a visualization. It can be used to create
 * surfaces from point distributions, or to create structure (i.e.,
 * topology) when none exists.
 *
 * @warning
 * The input to this filter is any dataset type. This filter can be used
 * to resample any form of data, i.e., the input data need not be
 * unstructured.
 *
 * @warning
 * Some voxels may never receive a contribution during the splatting process.
 * The final value of these points can be specified with the "NullValue"
 * instance variable.
 *
 * @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
 * vtkShepardMethod vtkCheckerboardSplatter
*/

#ifndef vtkGaussianSplatter_h
#define vtkGaussianSplatter_h

#include "vtkImagingHybridModule.h" // For export macro
#include "vtkImageAlgorithm.h"

#define VTK_ACCUMULATION_MODE_MIN 0
#define VTK_ACCUMULATION_MODE_MAX 1
#define VTK_ACCUMULATION_MODE_SUM 2

class vtkDoubleArray;
class vtkCompositeDataSet;
class vtkGaussianSplatterAlgorithm;

class VTKIMAGINGHYBRID_EXPORT vtkGaussianSplatter : public vtkImageAlgorithm
{
public:
  vtkTypeMacro(vtkGaussianSplatter,vtkImageAlgorithm);
  void PrintSelf(ostream& os, vtkIndent indent) override;

  /**
   * Construct object with dimensions=(50,50,50); automatic computation of
   * bounds; a splat radius of 0.1; an exponent factor of -5; and normal and
   * scalar warping turned on.
   */
  static vtkGaussianSplatter *New();

  //@{
  /**
   * Set / get the dimensions of the sampling structured point set. Higher
   * values produce better results but are much slower.
   */
  void SetSampleDimensions(int i, int j, int k);
  void SetSampleDimensions(int dim[3]);
  vtkGetVectorMacro(SampleDimensions,int,3);
  //@}

  //@{
  /**
   * Set / get the (xmin,xmax, ymin,ymax, zmin,zmax) bounding box in which
   * the sampling is performed. If any of the (min,max) bounds values are
   * min >= max, then the bounds will be computed automatically from the input
   * data. Otherwise, the user-specified bounds will be used.
   */
  vtkSetVector6Macro(ModelBounds,double);
  vtkGetVectorMacro(ModelBounds,double,6);
  //@}

  //@{
  /**
   * Set / get the radius of propagation of the splat. This value is expressed
   * as a percentage of the length of the longest side of the sampling
   * volume. Smaller numbers greatly reduce execution time.
   */
  vtkSetClampMacro(Radius,double,0.0,1.0);
  vtkGetMacro(Radius,double);
  //@}

  //@{
  /**
   * Multiply Gaussian splat distribution by this value. If ScalarWarping
   * is on, then the Scalar value will be multiplied by the ScaleFactor
   * times the Gaussian function.
   */
  vtkSetClampMacro(ScaleFactor,double,0.0,VTK_DOUBLE_MAX);
  vtkGetMacro(ScaleFactor,double);
  //@}

  //@{
  /**
   * Set / get the sharpness of decay of the splats. This is the
   * exponent constant in the Gaussian equation. Normally this is
   * a negative value.
   */
  vtkSetMacro(ExponentFactor,double);
  vtkGetMacro(ExponentFactor,double);
  //@}

  //@{
  /**
   * Turn on/off the generation of elliptical splats. If normal warping is
   * on, then the input normals affect the distribution of the splat. This
   * boolean is used in combination with the Eccentricity ivar.
   */
  vtkSetMacro(NormalWarping,vtkTypeBool);
  vtkGetMacro(NormalWarping,vtkTypeBool);
  vtkBooleanMacro(NormalWarping,vtkTypeBool);
  //@}

  //@{
  /**
   * Control the shape of elliptical splatting. Eccentricity is the ratio
   * of the major axis (aligned along normal) to the minor (axes) aligned
   * along other two axes. So Eccentricity > 1 creates needles with the
   * long axis in the direction of the normal; Eccentricity<1 creates
   * pancakes perpendicular to the normal vector.
   */
  vtkSetClampMacro(Eccentricity,double,0.001,VTK_DOUBLE_MAX);
  vtkGetMacro(Eccentricity,double);
  //@}

  //@{
  /**
   * Turn on/off the scaling of splats by scalar value.
   */
  vtkSetMacro(ScalarWarping,vtkTypeBool);
  vtkGetMacro(ScalarWarping,vtkTypeBool);
  vtkBooleanMacro(ScalarWarping,vtkTypeBool);
  //@}

  //@{
  /**
   * Turn on/off the capping of the outer boundary of the volume
   * to a specified cap value. This can be used to close surfaces
   * (after iso-surfacing) and create other effects.
   */
  vtkSetMacro(Capping,vtkTypeBool);
  vtkGetMacro(Capping,vtkTypeBool);
  vtkBooleanMacro(Capping,vtkTypeBool);
  //@}

  //@{
  /**
   * Specify the cap value to use. (This instance variable only has effect
   * if the ivar Capping is on.)
   */
  vtkSetMacro(CapValue,double);
  vtkGetMacro(CapValue,double);
  //@}

  //@{
  /**
   * Specify the scalar accumulation mode. This mode expresses how scalar
   * values are combined when splats are overlapped. The Max mode acts
   * like a set union operation and is the most commonly used; the Min
   * mode acts like a set intersection, and the sum is just weird.
   */
  vtkSetClampMacro(AccumulationMode,int,
                   VTK_ACCUMULATION_MODE_MIN,VTK_ACCUMULATION_MODE_SUM);
  vtkGetMacro(AccumulationMode,int);
  void SetAccumulationModeToMin()
    {this->SetAccumulationMode(VTK_ACCUMULATION_MODE_MIN);}
  void SetAccumulationModeToMax()
    {this->SetAccumulationMode(VTK_ACCUMULATION_MODE_MAX);}
  void SetAccumulationModeToSum()
    {this->SetAccumulationMode(VTK_ACCUMULATION_MODE_SUM);}
  const char *GetAccumulationModeAsString();
  //@}

  //@{
  /**
   * Set the Null value for output points not receiving a contribution from the
   * input points. (This is the initial value of the voxel samples.)
   */
  vtkSetMacro(NullValue,double);
  vtkGetMacro(NullValue,double);
  //@}

  //@{
  /**
   * Compute the size of the sample bounding box automatically from the
   * input data. This is an internal helper function.
   */
  void ComputeModelBounds(vtkDataSet *input, vtkImageData *output,
                          vtkInformation *outInfo);
  void ComputeModelBounds(vtkCompositeDataSet *input, vtkImageData *output,
                          vtkInformation *outInfo);
  //@}

  //@{
  /**
   * Provide access to templated helper class. Note that SamplePoint() method
   * is public here because some compilers don't handle friend functions
   * properly.
   */
  friend class vtkGaussianSplatterAlgorithm;
  double SamplePoint(double x[3]) //for compilers who can't handle this
    {return (this->*Sample)(x);}
  void SetScalar(int idx, double dist2, double *sPtr)
  {
    double v = (this->*SampleFactor)(this->S) * exp(static_cast<double>
      (this->ExponentFactor*(dist2)/(this->Radius2)));
  //@}

    if ( ! this->Visited[idx] )
    {
      this->Visited[idx] = 1;
      *sPtr = v;
    }
    else
    {
      switch (this->AccumulationMode)
      {
        case VTK_ACCUMULATION_MODE_MIN:
          if ( *sPtr > v )
          {
            *sPtr = v;
          }
          break;
        case VTK_ACCUMULATION_MODE_MAX:
          if ( *sPtr < v )
          {
            *sPtr = v;
          }
          break;
        case VTK_ACCUMULATION_MODE_SUM:
          *sPtr += v;
          break;
      }
    }//not first visit
  }

protected:
  vtkGaussianSplatter();
  ~vtkGaussianSplatter() override {}

  int FillInputPortInformation(int port, vtkInformation* info) override;
  int RequestInformation (vtkInformation *,
                                  vtkInformationVector **,
                                  vtkInformationVector *) override;
  int RequestData(vtkInformation *,
                          vtkInformationVector **,
                          vtkInformationVector *) override;
  void Cap(vtkDoubleArray *s);

  int SampleDimensions[3]; // dimensions of volume to splat into
  double Radius; // maximum distance splat propagates (as fraction 0->1)
  double ExponentFactor; // scale exponent of gaussian function
  double ModelBounds[6]; // bounding box of splatting dimensions
  vtkTypeBool NormalWarping; // on/off warping of splat via normal
  double Eccentricity;// elliptic distortion due to normals
  vtkTypeBool ScalarWarping; // on/off warping of splat via scalar
  double ScaleFactor; // splat size influenced by scale factor
  vtkTypeBool Capping; // Cap side of volume to close surfaces
  double CapValue; // value to use for capping
  int AccumulationMode; // how to combine scalar values

  double Gaussian(double x[3]);
  double EccentricGaussian(double x[3]);
  double ScalarSampling(double s)
    {return this->ScaleFactor * s;}
  double PositionSampling(double)
    {return this->ScaleFactor;}

private:
  double Radius2;
  double (vtkGaussianSplatter::*Sample)(double x[3]);
  double (vtkGaussianSplatter::*SampleFactor)(double s);
  char *Visited;
  double Eccentricity2;
  double *P;
  double *N;
  double S;
  double Origin[3];
  double Spacing[3];
  double SplatDistance[3];
  double NullValue;

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

#endif
