// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
/**
 * @class vtkMultiVolume
 * @brief Represents a world axis-aligned bounding-box containing a set of
 * volumes in a rendered scene.
 *
 * vtkVolume instances registered in this class can be overlapping. They are
 * intended to be all rendered simultaneously by a vtkGPUVolumeRayCastMapper
 * (inputs should be set directly in the mapper).
 *
 * This class holds the full transformation of a bounding-box containing
 * all of the registered volumes.
 *
 *      + TexToBBox : Texture-to-Data (scaling)
 *      + Matrix : Data-to-World (translation)
 *
 * @note This class is intended to be used only by mappers supporting multiple
 * inputs.
 *
 * @sa vtkVolume vtkAbstractVolumeMapper vtkGPUVolumeRayCastMapper
 */
#ifndef vtkMultiVolume_h
#define vtkMultiVolume_h
#include <array>         // for std::array
#include <unordered_map> // For std::unordered_map

#include "vtkMatrix4x4.h"             // For Matrix
#include "vtkRenderingVolumeModule.h" // For export macro
#include "vtkSmartPointer.h"          // For vtkSmartPointer
#include "vtkVolume.h"
#include "vtkWrappingHints.h" // For VTK_MARSHALMANUAL

VTK_ABI_NAMESPACE_BEGIN
class vtkAbstractVolumeMapper;
class vtkBoundingBox;
class vtkMatrix4x4;
class vtkRenderer;
class vtkVolumeProperty;
class vtkWindow;
class vtkVolumeProperty;
class vtkAbstractVolumeMapper;

class VTKRENDERINGVOLUME_EXPORT VTK_MARSHALMANUAL vtkMultiVolume : public vtkVolume
{
public:
  static vtkMultiVolume* New();
  vtkTypeMacro(vtkMultiVolume, vtkVolume);
  void PrintSelf(ostream& os, vtkIndent indent) override;

  ///@{
  /**
   * Add / Remove a vtkVolume instance.
   */
  void SetVolume(vtkVolume* volume, int port = 0);
  vtkVolume* GetVolume(int port = 0);
  void RemoveVolume(int port) { this->SetVolume(nullptr, port); }
  ///@}

  ///@{
  /**
   * Given that this class represents a bounding-box only there is no property
   * directly associated with it (a cannot be set directly).
   * This instance will return the property of the volume registered in the 0th
   * port (or nullptr if no volume has been set).
   * \sa vtkVolume
   */
  void SetProperty(vtkVolumeProperty* property) override;
  vtkVolumeProperty* GetProperty() override;
  ///@}

  std::unordered_map<int, vtkVolume*>& GetAllVolumes() { return this->Volumes; }
  void SetAllVolumes(std::unordered_map<int, vtkVolume*>& volumes);

  /**
   * Computes the bounds of the box containing all of the vtkVolume instances.
   * Returns the bounds (vtkVolume::Bounds) in world coordinates [xmin, xmax,
   * ymin, ymax, zmin, zmax] but also keeps cached the bounds in data coordinates
   * (vtkMultiVolume::DataBounds).
   */
  double* GetBounds() override;

  /**
   * \sa vtkVolume
   */
  vtkMTimeType GetMTime() override;

  /**
   * Checks whether the vtkProp passed is another vtkMultiVolume and tries to
   * copy accordingly. Otherwise it falls back to vtkVolume::ShallowCopy.
   * \sa vtkVolume
   */
  void ShallowCopy(vtkProp* prop) override;

  /**
   * As with other vtkProp3D, Matrix holds the transformation from data
   * coordinates to world coordinates.  Since this class represents an
   * axis-aligned bounding-box, this transformation only contains a translation
   * vector. Each registered vtkVolume contains its own transformation with
   * respect to the world coordinate system.
   * \sa vtkProp3D vtkVolume
   */
  using vtkVolume::GetMatrix;
  vtkMatrix4x4* GetMatrix() override { return this->Matrix; }

  /**
   * Returns the transformation from texture coordinates to data coordinates
   * of the bounding-box. Since this class represents an axis-aligned bounding
   * -boxThis, this transformation only contains a scaling diagonal.
   */
  vtkMatrix4x4* GetTextureMatrix() { return this->TexToBBox.GetPointer(); }

  /**
   * Total bounds in data coordinates.
   */
  double* GetDataBounds() { return this->DataBounds.data(); }

  vtkMTimeType GetBoundsTime() { return this->BoundsComputeTime.GetMTime(); }

  /**
   * Since vtkMultiVolume acts like a proxy volume to compute the bounding box
   * for its internal vtkVolume instances, there are no properties to be set directly
   * in this instance. For that reason, this override ignores the vtkVolumeProperty
   * check.
   */
  int RenderVolumetricGeometry(vtkViewport* vp) override;

  /**
   * Return the eight corners of the volume
   */
  double* GetDataGeometry() { return this->DataGeometry.data(); }

protected:
  vtkMultiVolume();
  ~vtkMultiVolume() override;

  /**
   * The transformation matrix of this vtkProp3D is not user-definable,
   * (only the registered vtkVolume instances define the total bounding-box).
   * For that reason this method does nothing.
   * \sa vtkProp3D
   */
  void ComputeMatrix() override {}

  /**
   * Returns the vtkVolume registered in port.
   */
  vtkVolume* FindVolume(int port);

  /**
   * Checks for changes in the registered vtkVolume instances which could
   * required the bounding-box to be recomputed.
   */
  bool VolumesChanged();

  /**
   * For a box defined by bounds in coordinate system X, compute its
   * axis-aligned bounds in coordinate system Y. T defines the transformation
   * from X to Y and bounds ([x_min, x_max, y_min, y_max, z_min, z_max])
   * the box in X.
   */
  std::array<double, 6> ComputeAABounds(double bounds[6], vtkMatrix4x4* T) const;

  std::array<double, 6> DataBounds;
  std::array<double, 24> DataGeometry;
  std::unordered_map<int, vtkVolume*> Volumes;
  vtkTimeStamp BoundsComputeTime;
  vtkSmartPointer<vtkMatrix4x4> TexToBBox;

private:
  vtkMultiVolume(const vtkMultiVolume&) = delete;
  void operator=(const vtkMultiVolume&) = delete;
};
VTK_ABI_NAMESPACE_END
#endif
