// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkPicker.h"

#include "vtkAbstractVolumeMapper.h"
#include "vtkActor.h"
#include "vtkAssemblyNode.h"
#include "vtkAssemblyPath.h"
#include "vtkBox.h"
#include "vtkCamera.h"
#include "vtkCommand.h"
#include "vtkCompositeDataIterator.h"
#include "vtkCompositeDataSet.h"
#include "vtkImageData.h"
#include "vtkImageMapper3D.h"
#include "vtkImageSlice.h"
#include "vtkLODProp3D.h"
#include "vtkMapper.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPlane.h"
#include "vtkPoints.h"
#include "vtkProp3DCollection.h"
#include "vtkProperty.h"
#include "vtkRenderWindow.h"
#include "vtkRenderer.h"
#include "vtkTransform.h"
#include "vtkVertex.h"
#include "vtkVolume.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkPicker);

//------------------------------------------------------------------------------
// Construct object with initial tolerance of 1/40th of window. There are no
// pick methods and picking is performed from the renderer's actors.
vtkPicker::vtkPicker()
{
  this->Tolerance = 0.025; // 1/40th of the renderer window

  this->MapperPosition[0] = 0.0;
  this->MapperPosition[1] = 0.0;
  this->MapperPosition[2] = 0.0;

  this->Mapper = nullptr;
  this->DataSet = nullptr;
  this->CompositeDataSet = nullptr;
  this->FlatBlockIndex = -1;
  this->GlobalTMin = VTK_DOUBLE_MAX;
  this->Actors = vtkActorCollection::New();
  this->Prop3Ds = vtkProp3DCollection::New();
  this->PickedPositions = vtkPoints::New();
  this->Transform = vtkTransform::New();
}

//------------------------------------------------------------------------------
vtkPicker::~vtkPicker()
{
  this->Actors->Delete();
  this->Prop3Ds->Delete();
  this->PickedPositions->Delete();
  this->Transform->Delete();
}

//------------------------------------------------------------------------------
// Update state when prop3D is picked.
void vtkPicker::MarkPicked(vtkAssemblyPath* path, vtkProp3D* vtkNotUsed(prop3D),
  vtkAbstractMapper3D* m, double tMin, double mapperPos[3])
{
  vtkMapper* mapper;
  vtkAbstractVolumeMapper* volumeMapper;
  vtkImageMapper3D* imageMapper;

  if ((mapper = vtkMapper::SafeDownCast(m)) != nullptr)
  {
    this->MarkPickedData(path, tMin, mapperPos, mapper, mapper->GetInput());
  }
  else if ((volumeMapper = vtkAbstractVolumeMapper::SafeDownCast(m)) != nullptr)
  {
    this->MarkPickedData(path, tMin, mapperPos, volumeMapper, volumeMapper->GetDataSetInput());
  }
  else if ((imageMapper = vtkImageMapper3D::SafeDownCast(m)) != nullptr)
  {
    this->MarkPickedData(path, tMin, mapperPos, imageMapper, imageMapper->GetInput());
  }
  else
  {
    this->MarkPickedData(path, tMin, mapperPos, nullptr, nullptr);
  }
}

void vtkPicker::MarkPickedData(vtkAssemblyPath* path, double tMin, double mapperPos[3],
  vtkAbstractMapper3D* mapper, vtkDataSet* input, vtkIdType flatIndex)
{
  this->SetPath(path);
  this->GlobalTMin = tMin;

  for (int i = 0; i < 3; i++)
  {
    this->MapperPosition[i] = mapperPos[i];
  }
  // The point has to be transformed back into world coordinates.
  // Note: it is assumed that the transform is in the correct state.
  this->Transform->TransformPoint(mapperPos, this->PickPosition);

  this->Mapper = mapper;
  this->DataSet = input;
  this->CompositeDataSet = vtkCompositeDataSet::SafeDownCast(mapper->GetInputDataObject(0, 0));
  this->FlatBlockIndex = flatIndex;
}

int vtkPicker::Pick3DPoint(double pos[3], vtkRenderer* renderer)
{
  int i;
  vtkProp* prop;
  vtkAbstractMapper3D* mapper = nullptr;
  int winSize[2] = { 1, 1 };
  double x, y;
  double* viewport;
  int pickable;
  int LODId;
  double windowLowerLeft[4], windowUpperRight[4];
  double bounds[6], tol;
  vtkActor* actor;
  vtkLODProp3D* prop3D;
  vtkVolume* volume;
  vtkImageSlice* imageSlice = nullptr;
  vtkAssemblyPath* path;
  vtkProperty* tempProperty;
  vtkCollectionSimpleIterator pit;

  //  Initialize picking process
  this->Initialize();
  this->Renderer = renderer;

  // Invoke start pick method if defined
  this->InvokeEvent(vtkCommand::StartPickEvent, nullptr);

  bounds[0] = bounds[1] = bounds[2] = bounds[3] = bounds[4] = bounds[5] = 0;

  // Compute the tolerance in world coordinates.  Do this by
  // determining the world coordinates of the diagonal points of the
  // window, computing the width of the window in world coordinates, and
  // multiplying by the tolerance.
  //
  viewport = renderer->GetViewport();
  if (renderer->GetRenderWindow())
  {
    const int* winSizePtr = renderer->GetRenderWindow()->GetSize();
    if (winSizePtr)
    {
      winSize[0] = winSizePtr[0];
      winSize[1] = winSizePtr[1];
    }
  }
  x = winSize[0] * viewport[0];
  y = winSize[1] * viewport[1];
  renderer->SetDisplayPoint(x, y, 0.0);
  renderer->DisplayToWorld();
  renderer->GetWorldPoint(windowLowerLeft);

  x = winSize[0] * viewport[2];
  y = winSize[1] * viewport[3];
  renderer->SetDisplayPoint(x, y, 0.0);
  renderer->DisplayToWorld();
  renderer->GetWorldPoint(windowUpperRight);

  for (tol = 0.0, i = 0; i < 3; i++)
  {
    tol += (windowUpperRight[i] - windowLowerLeft[i]) * (windowUpperRight[i] - windowLowerLeft[i]);
  }

  tol = sqrt(tol) * this->Tolerance;

  //  Loop over all props.  Transform ray (defined from position of
  //  camera to selection point) into coordinates of mapper (not
  //  transformed to actors coordinates!  Reduces overall computation!!!).
  //  Note that only vtkProp3D's can be picked by vtkPicker.
  //
  vtkPropCollection* props;
  vtkProp* propCandidate;
  if (this->PickFromList)
  {
    props = this->GetPickList();
  }
  else
  {
    props = renderer->GetViewProps();
  }

  for (props->InitTraversal(pit); (prop = props->GetNextProp(pit));)
  {
    for (prop->InitPathTraversal(); (path = prop->GetNextPath());)
    {
      pickable = 0;
      actor = nullptr;
      propCandidate = path->GetLastNode()->GetViewProp();
      if (propCandidate->GetPickable() && propCandidate->GetVisibility())
      {
        pickable = 1;
        if ((actor = vtkActor::SafeDownCast(propCandidate)) != nullptr)
        {
          mapper = actor->GetMapper();
          if (actor->GetProperty()->GetOpacity() <= 0.0)
          {
            pickable = 0;
          }
        }
        else if ((prop3D = vtkLODProp3D::SafeDownCast(propCandidate)) != nullptr)
        {
          LODId = prop3D->GetPickLODID();
          mapper = prop3D->GetLODMapper(LODId);

          // if the mapper is a vtkMapper (as opposed to a vtkVolumeMapper),
          // then check the transparency to see if the object is pickable
          if (vtkMapper::SafeDownCast(mapper) != nullptr)
          {
            prop3D->GetLODProperty(LODId, &tempProperty);
            if (tempProperty->GetOpacity() <= 0.0)
            {
              pickable = 0;
            }
          }
        }
        else if ((volume = vtkVolume::SafeDownCast(propCandidate)) != nullptr)
        {
          mapper = volume->GetMapper();
        }
        else if ((imageSlice = vtkImageSlice::SafeDownCast(propCandidate)))
        {
          mapper = imageSlice->GetMapper();
        }
        else
        {
          pickable = 0; // only vtkProp3D's (actors and volumes) can be picked
        }
      }

      //  If actor can be picked, get its composite matrix, invert it, and
      //  use the inverted matrix to transform the points into mapper
      //  coordinates.
      if (pickable)
      {
        const double* bnds = propCandidate->GetBounds();
        if (bnds)
        {
          bounds[0] = bnds[0] - tol;
          bounds[1] = bnds[1] + tol;
          bounds[2] = bnds[2] - tol;
          bounds[3] = bnds[3] + tol;
          bounds[4] = bnds[4] - tol;
          bounds[5] = bnds[5] + tol;
          if (pos[0] >= bounds[0] && pos[0] <= bounds[1] && pos[1] >= bounds[2] &&
            pos[1] <= bounds[3] && pos[2] >= bounds[4] && pos[2] <= bounds[5])
          {
            this->MarkPicked(path, static_cast<vtkProp3D*>(propCandidate), mapper, 0.0, pos);

            int prevIndex = this->Prop3Ds->IndexOfFirstOccurence(prop);

            if (prevIndex < 0)
            {
              this->Prop3Ds->AddItem(static_cast<vtkProp3D*>(prop));

              this->PickedPositions->InsertNextPoint(pos);

              // backwards compatibility: also add to this->Actors
              if (actor)
              {
                this->Actors->AddItem(actor);
              }
            }
          }
        }
      } // if visible and pickable and not transparent
    }   // for all parts
  }     // for all actors

  int picked = 0;

  if (this->Path)
  {
    // Invoke pick method if one defined - prop goes first
    this->Path->GetFirstNode()->GetViewProp()->Pick();
    this->InvokeEvent(vtkCommand::PickEvent, nullptr);
    picked = 1;
  }

  // Invoke end pick method if defined
  this->InvokeEvent(vtkCommand::EndPickEvent, nullptr);

  return picked;
}

//------------------------------------------------------------------------------
int vtkPicker::Pick3DPoint(double selectionPt[3], double focalPt[3], vtkRenderer* ren)
{
  // Initialize the picking process
  this->Initialize();
  this->Renderer = ren;

  // Invoke start pick method if defined
  this->InvokeEvent(vtkCommand::StartPickEvent, nullptr);

  int result = this->Pick3DInternal(ren, selectionPt, focalPt);

  // Invoke end pick method if defined
  this->InvokeEvent(vtkCommand::EndPickEvent, nullptr);

  return result;
}

//------------------------------------------------------------------------------
// Perform pick operation with selection point provided. Normally the
// first two values for the selection point are x-y pixel coordinate, and
// the third value is =0. Return non-zero if something was successfully picked.
int vtkPicker::Pick(double selectionX, double selectionY, double selectionZ, vtkRenderer* renderer)
{
  int i;
  vtkCamera* camera;
  double cameraPos[4], cameraFP[4];
  double *displayCoords, *worldCoords;
  double ray[3], rayLength;
  double tF, tB;
  double cameraDOP[3];
  double p1World[4], p2World[4];
  double* clipRange;

  //  Initialize picking process
  this->Initialize();
  this->Renderer = renderer;
  this->SelectionPoint[0] = selectionX;
  this->SelectionPoint[1] = selectionY;
  this->SelectionPoint[2] = selectionZ;

  // Invoke start pick method if defined
  this->InvokeEvent(vtkCommand::StartPickEvent, nullptr);

  if (renderer == nullptr)
  {
    vtkErrorMacro(<< "Must specify renderer!");
    return 0;
  }

  // Get camera focal point and position. Convert to display (screen)
  // coordinates. We need a depth value for z-buffer.
  //
  camera = renderer->GetActiveCamera();
  camera->GetPosition(cameraPos);
  cameraPos[3] = 1.0;
  camera->GetFocalPoint(cameraFP);
  cameraFP[3] = 1.0;

  renderer->SetWorldPoint(cameraFP[0], cameraFP[1], cameraFP[2], cameraFP[3]);
  renderer->WorldToDisplay();
  displayCoords = renderer->GetDisplayPoint();
  selectionZ = displayCoords[2];

  // Convert the selection point into world coordinates.
  //
  renderer->SetDisplayPoint(selectionX, selectionY, selectionZ);
  renderer->DisplayToWorld();
  worldCoords = renderer->GetWorldPoint();
  if (worldCoords[3] == 0.0)
  {
    vtkErrorMacro(<< "Bad homogeneous coordinates");
    return 0;
  }
  for (i = 0; i < 3; i++)
  {
    this->PickPosition[i] = worldCoords[i] / worldCoords[3];
  }

  // For robustness, re-project the point on the focal point plane
  double planeNormal[3];
  vtkMath::Subtract(cameraFP, cameraPos, planeNormal);
  vtkMath::Normalize(planeNormal);
  vtkPlane::ProjectPoint(this->PickPosition, cameraFP, planeNormal, this->PickPosition);

  //  Compute the ray endpoints.  The ray is along the line running from
  //  the camera position to the selection point, starting where this line
  //  intersects the front clipping plane, and terminating where this
  //  line intersects the back clipping plane.
  for (i = 0; i < 3; i++)
  {
    ray[i] = this->PickPosition[i] - cameraPos[i];
  }
  for (i = 0; i < 3; i++)
  {
    cameraDOP[i] = cameraFP[i] - cameraPos[i];
  }

  vtkMath::Normalize(cameraDOP);

  if ((rayLength = vtkMath::Dot(cameraDOP, ray)) == 0.0)
  {
    vtkWarningMacro("Cannot process points");
    return 0;
  }

  clipRange = camera->GetClippingRange();

  if (camera->GetParallelProjection())
  {
    tF = clipRange[0] - rayLength;
    tB = clipRange[1] - rayLength;
    for (i = 0; i < 3; i++)
    {
      p1World[i] = this->PickPosition[i] + tF * cameraDOP[i];
      p2World[i] = this->PickPosition[i] + tB * cameraDOP[i];
    }
  }
  else
  {
    tF = clipRange[0] / rayLength;
    tB = clipRange[1] / rayLength;
    for (i = 0; i < 3; i++)
    {
      p1World[i] = cameraPos[i] + tF * ray[i];
      p2World[i] = cameraPos[i] + tB * ray[i];
    }
  }
  p1World[3] = p2World[3] = 1.0;

  int result = this->Pick3DInternal(renderer, p1World, p2World);

  // Invoke end pick method if defined
  this->InvokeEvent(vtkCommand::EndPickEvent, nullptr);

  return result;
}

int vtkPicker::Pick3DRay(double pos[3], double wori[4], vtkRenderer* renderer)
{
  //  Initialize picking process
  this->Initialize();
  this->Renderer = renderer;

  double wp1[4], wp2[4];

  double dist = renderer->GetActiveCamera()->GetClippingRange()[1];

  vtkNew<vtkTransform> trans;
  trans->RotateWXYZ(wori[0], wori[1], wori[2], wori[3]);
  double* rayDirection = trans->TransformDoubleVector(0.0, 0.0, -1.0);

  for (int i = 0; i < 3; i++)
  {
    this->PickPosition[i] = pos[i];
    wp1[i] = pos[i];
    wp2[i] = pos[i] + dist * rayDirection[i];
  }

  // Invoke start pick method if defined
  this->InvokeEvent(vtkCommand::StartPickEvent, nullptr);

  wp1[3] = 1.0;
  wp2[3] = 1.0;

  int result = this->Pick3DInternal(renderer, wp1, wp2);

  // Invoke end pick method if defined
  this->InvokeEvent(vtkCommand::EndPickEvent, nullptr);

  return result;
}

int vtkPicker::Pick3DInternal(vtkRenderer* renderer, double p1World[4], double p2World[4])
{
  int i;
  vtkProp* prop;
  vtkAbstractMapper3D* mapper = nullptr;
  double p1Mapper[4], p2Mapper[4];
  int winSize[2] = { 1, 1 };
  double x, y, t;
  double* viewport;
  double ray[3];
  int pickable;
  int LODId;
  double windowLowerLeft[4], windowUpperRight[4];
  double bounds[6], tol;
  double hitPosition[3];

  bounds[0] = bounds[1] = bounds[2] = bounds[3] = bounds[4] = bounds[5] = 0;

  if (renderer == nullptr)
  {
    vtkErrorMacro(<< "Must specify renderer!");
    return 0;
  }

  // Compute the tolerance in world coordinates.  Do this by
  // determining the world coordinates of the diagonal points of the
  // window, computing the width of the window in world coordinates, and
  // multiplying by the tolerance.
  //
  renderer->SetWorldPoint(0.5 * (p1World[0] + p2World[0]), 0.5 * (p1World[1] + p2World[1]),
    0.5 * (p1World[2] + p2World[2]), 1.0);
  renderer->WorldToDisplay();
  double* displayCoords = renderer->GetDisplayPoint();
  double tolZ = displayCoords[2];

  viewport = renderer->GetViewport();
  if (renderer->GetRenderWindow())
  {
    const int* winSizePtr = renderer->GetRenderWindow()->GetSize();
    if (winSizePtr)
    {
      winSize[0] = winSizePtr[0];
      winSize[1] = winSizePtr[1];
    }
  }
  x = winSize[0] * viewport[0];
  y = winSize[1] * viewport[1];
  renderer->SetDisplayPoint(x, y, tolZ);
  renderer->DisplayToWorld();
  renderer->GetWorldPoint(windowLowerLeft);

  x = winSize[0] * viewport[2];
  y = winSize[1] * viewport[3];
  renderer->SetDisplayPoint(x, y, tolZ);
  renderer->DisplayToWorld();
  renderer->GetWorldPoint(windowUpperRight);

  for (tol = 0.0, i = 0; i < 3; i++)
  {
    tol += (windowUpperRight[i] - windowLowerLeft[i]) * (windowUpperRight[i] - windowLowerLeft[i]);
  }

  tol = sqrt(tol) * this->Tolerance;

  //  Loop over all props.  Transform ray (defined from position of
  //  camera to selection point) into coordinates of mapper (not
  //  transformed to actors coordinates!  Reduces overall computation!!!).
  //  Note that only vtkProp3D's can be picked by vtkPicker.
  //
  vtkPropCollection* props;
  vtkProp* propCandidate;
  if (this->PickFromList)
  {
    props = this->GetPickList();
  }
  else
  {
    props = renderer->GetViewProps();
  }

  vtkActor* actor;
  vtkLODProp3D* prop3D;
  vtkVolume* volume;
  vtkImageSlice* imageSlice = nullptr;
  vtkAssemblyPath* path;
  vtkProperty* tempProperty;
  this->Transform->PostMultiply();
  vtkCollectionSimpleIterator pit;
  double scale[3];
  for (props->InitTraversal(pit); (prop = props->GetNextProp(pit));)
  {
    for (prop->InitPathTraversal(); (path = prop->GetNextPath());)
    {
      pickable = 0;
      actor = nullptr;
      propCandidate = path->GetLastNode()->GetViewProp();
      if (propCandidate->GetPickable() && propCandidate->GetVisibility())
      {
        pickable = 1;
        if ((actor = vtkActor::SafeDownCast(propCandidate)) != nullptr)
        {
          mapper = actor->GetMapper();
          if (actor->GetProperty()->GetOpacity() <= 0.0)
          {
            pickable = 0;
          }
        }
        else if ((prop3D = vtkLODProp3D::SafeDownCast(propCandidate)) != nullptr)
        {
          LODId = prop3D->GetPickLODID();
          mapper = prop3D->GetLODMapper(LODId);

          // if the mapper is a vtkMapper (as opposed to a vtkVolumeMapper),
          // then check the transparency to see if the object is pickable
          if (vtkMapper::SafeDownCast(mapper) != nullptr)
          {
            prop3D->GetLODProperty(LODId, &tempProperty);
            if (tempProperty->GetOpacity() <= 0.0)
            {
              pickable = 0;
            }
          }
        }
        else if ((volume = vtkVolume::SafeDownCast(propCandidate)) != nullptr)
        {
          mapper = volume->GetMapper();
        }
        else if ((imageSlice = vtkImageSlice::SafeDownCast(propCandidate)))
        {
          mapper = imageSlice->GetMapper();
        }
        else
        {
          pickable = 0; // only vtkProp3D's (actors and volumes) can be picked
        }
      }

      //  If actor can be picked, get its composite matrix, invert it, and
      //  use the inverted matrix to transform the ray points into mapper
      //  coordinates.
      if (pickable)
      {
        vtkMatrix4x4* lastMatrix = path->GetLastNode()->GetMatrix();
        if (lastMatrix == nullptr)
        {
          vtkErrorMacro(<< "Pick: Null matrix.");
          return 0;
        }
        this->Transform->SetMatrix(lastMatrix);
        this->Transform->Push();
        this->Transform->Inverse();
        this->Transform->GetScale(scale); // need to scale the tolerance

        this->Transform->TransformPoint(p1World, p1Mapper);
        this->Transform->TransformPoint(p2World, p2Mapper);

        for (i = 0; i < 3; i++)
        {
          ray[i] = p2Mapper[i] - p1Mapper[i];
        }

        this->Transform->Pop();

        //  Have the ray endpoints in mapper space, now need to compare this
        //  with the mapper bounds to see whether intersection is possible.
        //
        //  Get the bounding box of the modeller.  Note that the tolerance is
        //  added to the bounding box to make sure things on the edge of the
        //  bounding box are picked correctly.
        if (mapper != nullptr)
        {
          mapper->GetBounds(bounds);
        }

        bounds[0] -= tol;
        bounds[1] += tol;
        bounds[2] -= tol;
        bounds[3] += tol;
        bounds[4] -= tol;
        bounds[5] += tol;

        if (vtkBox::IntersectBox(bounds, p1Mapper, ray, hitPosition, t))
        {
          t = this->IntersectWithLine(p1Mapper, p2Mapper,
            tol * 0.333 * (scale[0] + scale[1] + scale[2]), path,
            static_cast<vtkProp3D*>(propCandidate), mapper);

          if (t < VTK_DOUBLE_MAX)
          {
            double p[3];
            p[0] = (1.0 - t) * p1World[0] + t * p2World[0];
            p[1] = (1.0 - t) * p1World[1] + t * p2World[1];
            p[2] = (1.0 - t) * p1World[2] + t * p2World[2];

            int prevIndex = this->Prop3Ds->IndexOfFirstOccurence(prop);

            if (prevIndex >= 0)
            {
              // If already in list, set point to the closest point
              double oldp[3];
              this->PickedPositions->GetPoint(prevIndex, oldp);
              if (vtkMath::Distance2BetweenPoints(p1World, p) <
                vtkMath::Distance2BetweenPoints(p1World, oldp))
              {
                this->PickedPositions->SetPoint(prevIndex, p);
              }
            }
            else
            {
              this->Prop3Ds->AddItem(static_cast<vtkProp3D*>(prop));

              this->PickedPositions->InsertNextPoint(p);

              // backwards compatibility: also add to this->Actors
              if (actor)
              {
                this->Actors->AddItem(actor);
              }
            }
          }
        }
      } // if visible and pickable and not transparent
    }   // for all parts
  }     // for all actors

  int picked = 0;

  if (this->Path)
  {
    // Invoke pick method if one defined - prop goes first
    this->Path->GetFirstNode()->GetViewProp()->Pick();
    this->InvokeEvent(vtkCommand::PickEvent, nullptr);
    picked = 1;
  }

  return picked;
}

//------------------------------------------------------------------------------
// Intersect data with specified ray.
double vtkPicker::IntersectWithLine(const double p1[3], const double p2[3], double tol,
  vtkAssemblyPath* path, vtkProp3D* prop3D, vtkAbstractMapper3D* mapper)
{
  double center[3], t, ray[3], rayFactor;

  // Get the data from the modeler
  if (mapper != nullptr)
  {
    mapper->GetCenter(center);
  }
  else
  {
    return VTK_DOUBLE_MAX;
  }

  if (!vtkPicker::CalculateRay(p1, p2, ray, rayFactor))
  {
    vtkDebugMacro("Zero length ray");
    return 2.0;
  }

  // Project the center point onto the ray and determine its parametric value
  //
  t = (ray[0] * (center[0] - p1[0]) + ray[1] * (center[1] - p1[1]) + ray[2] * (center[2] - p1[2])) /
    rayFactor;

  if (t >= 0.0 && t <= 1.0 && t < this->GlobalTMin)
  {
    // If this is a composite dataset, find the nearest picked dataset
    vtkCompositeDataSet* composite =
      vtkCompositeDataSet::SafeDownCast(mapper->GetInputDataObject(0, 0));
    if (composite)
    {
      double tMinDS = VTK_DOUBLE_MAX;
      double centerMinDS[3];
      vtkDataSet* minDS = nullptr;
      vtkIdType minDSIndex = -1;
      vtkSmartPointer<vtkCompositeDataIterator> iter;
      iter.TakeReference(composite->NewIterator());
      for (iter->InitTraversal(); !iter->IsDoneWithTraversal(); iter->GoToNextItem())
      {
        vtkDataSet* ds = vtkDataSet::SafeDownCast(iter->GetCurrentDataObject());
        if (!ds)
        {
          continue;
        }

        // First check if the bounding box of the data set is hit.
        double bounds[6];
        ds->GetBounds(bounds);
        bounds[0] -= tol;
        bounds[1] += tol;
        bounds[2] -= tol;
        bounds[3] += tol;
        bounds[4] -= tol;
        bounds[5] += tol;
        double tDummy;
        double xyzDummy[3];
        if (!vtkBox::IntersectBox(bounds, p1, ray, xyzDummy, tDummy))
        {
          // box not hit: no need to intersect...
          continue;
        }

        double centerDS[3];
        ds->GetCenter(centerDS);

        // Project the center point onto the ray and
        // determine its parametric value
        double tDS = (ray[0] * (centerDS[0] - p1[0]) + ray[1] * (centerDS[1] - p1[1]) +
                       ray[2] * (centerDS[2] - p1[2])) /
          rayFactor;

        if (tDS >= 0.0 && tDS <= 1.0 && tDS < tMinDS)
        {
          tMinDS = tDS;
          centerMinDS[0] = centerDS[0];
          centerMinDS[1] = centerDS[1];
          centerMinDS[2] = centerDS[2];
          minDS = ds;
          minDSIndex = iter->GetCurrentFlatIndex();
        }
      }
      // Note that the mapper position is not the center of the entire
      // composite data set but the center of the nearest data set
      this->MarkPickedData(path, tMinDS, centerMinDS, mapper, minDS, minDSIndex);
    }
    else
    {
      this->MarkPicked(path, prop3D, mapper, t, center);
    }
  }
  return t;
}

bool vtkPicker::CalculateRay(
  const double p1[3], const double p2[3], double ray[3], double& rayFactor)
{
  ray[0] = p2[0] - p1[0];
  ray[1] = p2[1] - p1[1];
  ray[2] = p2[2] - p1[2];

  rayFactor = vtkMath::Dot(ray, ray);
  return (rayFactor > 0.0);
}

//------------------------------------------------------------------------------
// Initialize the picking process.
void vtkPicker::Initialize()
{
  this->vtkAbstractPropPicker::Initialize();

  this->Actors->RemoveAllItems();
  this->Prop3Ds->RemoveAllItems();
  this->PickedPositions->Reset();

  this->MapperPosition[0] = 0.0;
  this->MapperPosition[1] = 0.0;
  this->MapperPosition[2] = 0.0;

  this->Mapper = nullptr;
  this->DataSet = nullptr;
  this->CompositeDataSet = nullptr;
  this->FlatBlockIndex = -1;
  this->GlobalTMin = VTK_DOUBLE_MAX;
}

//------------------------------------------------------------------------------
vtkActorCollection* vtkPicker::GetActors()
{
  if (this->Actors->GetNumberOfItems() != this->PickedPositions->GetNumberOfPoints())
  {
    vtkWarningMacro(<< "Not all Prop3Ds are actors, use GetProp3Ds instead");
  }
  return this->Actors;
}

//------------------------------------------------------------------------------
void vtkPicker::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);

  if (this->DataSet)
  {
    os << indent << "DataSet: " << this->DataSet << "\n";
  }
  else
  {
    os << indent << "DataSet: (none)";
  }
  if (this->CompositeDataSet)
  {
    os << indent << "CompositeDataSet: " << this->CompositeDataSet << "\n";
  }
  else
  {
    os << indent << "CompositeDataSet: (none)\n";
  }
  if (this->FlatBlockIndex > -1)
  {
    os << indent << "FlatBlockIndex: " << this->FlatBlockIndex << "\n";
  }
  else
  {
    os << indent << "FlatBlockIndex: (none)\n";
  }

  os << indent << "Mapper: " << this->Mapper << "\n";

  os << indent << "Tolerance: " << this->Tolerance << "\n";

  os << indent << "MapperPosition: (" << this->MapperPosition[0] << "," << this->MapperPosition[1]
     << "," << this->MapperPosition[2] << ")\n";
}
VTK_ABI_NAMESPACE_END
