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

#include <algorithm>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkSphere);

//------------------------------------------------------------------------------
// Construct sphere with center at (0,0,0) and radius=0.5.
vtkSphere::vtkSphere()
{
  this->Radius = 0.5;

  this->Center[0] = 0.0;
  this->Center[1] = 0.0;
  this->Center[2] = 0.0;
}

//------------------------------------------------------------------------------
// Evaluate sphere equation ((x-x0)^2 + (y-y0)^2 + (z-z0)^2) - R^2.
double vtkSphere::EvaluateFunction(double x[3])
{
  return (((x[0] - this->Center[0]) * (x[0] - this->Center[0]) +
            (x[1] - this->Center[1]) * (x[1] - this->Center[1]) +
            (x[2] - this->Center[2]) * (x[2] - this->Center[2])) -
    this->Radius * this->Radius);
}

//------------------------------------------------------------------------------
// Evaluate sphere gradient.
void vtkSphere::EvaluateGradient(double x[3], double n[3])
{
  n[0] = 2.0 * (x[0] - this->Center[0]);
  n[1] = 2.0 * (x[1] - this->Center[1]);
  n[2] = 2.0 * (x[2] - this->Center[2]);
}

// The following methods are used to compute bounding spheres.
//
#define VTK_ASSIGN_POINT(_x, _y)                                                                   \
  do                                                                                               \
  {                                                                                                \
    _x[0] = _y[0];                                                                                 \
    _x[1] = _y[1];                                                                                 \
    _x[2] = _y[2];                                                                                 \
  } while (false)

//------------------------------------------------------------------------------
// Inspired by Graphics Gems Vol. I ("An Efficient Bounding Sphere" by Jack Ritter).
// The algorithm works in two parts: first an initial estimate of the largest sphere;
// second an adjustment to the sphere to make sure that it includes all the points.
// Typically this returns a bounding sphere that is ~5% larger than the minimal
// bounding sphere.
template <class T>
void vtkSphereComputeBoundingSphere(T* pts, vtkIdType numPts, T sphere[4], vtkIdType hints[2])
{
  sphere[0] = sphere[1] = sphere[2] = sphere[3] = 0.0;
  if (numPts < 1)
  {
    return;
  }

  vtkIdType i;
  T *p, d1[3], d2[3];
  if (hints)
  {
    p = pts + 3 * hints[0];
    VTK_ASSIGN_POINT(d1, p);
    p = pts + 3 * hints[1];
    VTK_ASSIGN_POINT(d2, p);
  }
  else // no hints provided, compute an initial guess
  {
    T xMin[3], xMax[3], yMin[3], yMax[3], zMin[3], zMax[3];
    xMin[0] = xMin[1] = xMin[2] = VTK_FLOAT_MAX;
    yMin[0] = yMin[1] = yMin[2] = VTK_FLOAT_MAX;
    zMin[0] = zMin[1] = zMin[2] = VTK_FLOAT_MAX;
    xMax[0] = xMax[1] = xMax[2] = -VTK_FLOAT_MAX;
    yMax[0] = yMax[1] = yMax[2] = -VTK_FLOAT_MAX;
    zMax[0] = zMax[1] = zMax[2] = -VTK_FLOAT_MAX;

    // First part: Estimate the points furthest apart to define the largest sphere.
    // Find the points that span the greatest distance on the x-y-z axes. Use these
    // two points to define a sphere centered between the two points.
    for (p = pts, i = 0; i < numPts; ++i, p += 3)
    {
      if (p[0] < xMin[0])
        VTK_ASSIGN_POINT(xMin, p);
      if (p[0] > xMax[0])
        VTK_ASSIGN_POINT(xMax, p);
      if (p[1] < yMin[1])
        VTK_ASSIGN_POINT(yMin, p);
      if (p[1] > yMax[1])
        VTK_ASSIGN_POINT(yMax, p);
      if (p[2] < zMin[2])
        VTK_ASSIGN_POINT(zMin, p);
      if (p[2] > zMax[2])
        VTK_ASSIGN_POINT(zMax, p);
    }
    T xSpan = (xMax[0] - xMin[0]) * (xMax[0] - xMin[0]) +
      (xMax[1] - xMin[1]) * (xMax[1] - xMin[1]) + (xMax[2] - xMin[2]) * (xMax[2] - xMin[2]);
    T ySpan = (yMax[0] - yMin[0]) * (yMax[0] - yMin[0]) +
      (yMax[1] - yMin[1]) * (yMax[1] - yMin[1]) + (yMax[2] - yMin[2]) * (yMax[2] - yMin[2]);
    T zSpan = (zMax[0] - zMin[0]) * (zMax[0] - zMin[0]) +
      (zMax[1] - zMin[1]) * (zMax[1] - zMin[1]) + (zMax[2] - zMin[2]) * (zMax[2] - zMin[2]);

    if (xSpan > ySpan)
    {
      if (xSpan > zSpan)
      {
        VTK_ASSIGN_POINT(d1, xMin);
        VTK_ASSIGN_POINT(d2, xMax);
      }
      else
      {
        VTK_ASSIGN_POINT(d1, zMin);
        VTK_ASSIGN_POINT(d2, zMax);
      }
    }
    else // ySpan > xSpan
    {
      if (ySpan > zSpan)
      {
        VTK_ASSIGN_POINT(d1, yMin);
        VTK_ASSIGN_POINT(d2, yMax);
      }
      else
      {
        VTK_ASSIGN_POINT(d1, zMin);
        VTK_ASSIGN_POINT(d2, zMax);
      }
    }
  } // no hints provided

  // Compute initial estimated sphere
  sphere[0] = (d1[0] + d2[0]) / 2.0;
  sphere[1] = (d1[1] + d2[1]) / 2.0;
  sphere[2] = (d1[2] + d2[2]) / 2.0;
  T r2 = vtkMath::Distance2BetweenPoints(d1, d2) / 4.0;
  sphere[3] = sqrt(r2);

  // Second part: Make a pass over the points to make sure that they fit inside the sphere.
  // If not, adjust the sphere to fit the point.
  T dist, dist2, delta;
  for (p = pts, i = 0; i < numPts; ++i, p += 3)
  {
    dist2 = vtkMath::Distance2BetweenPoints(p, sphere);
    if (dist2 > r2)
    {
      dist = sqrt(dist2);
      sphere[3] = (sphere[3] + dist) / 2.0;
      r2 = sphere[3] * sphere[3];
      delta = dist - sphere[3];
      sphere[0] = (sphere[3] * sphere[0] + delta * p[0]) / dist;
      sphere[1] = (sphere[3] * sphere[1] + delta * p[1]) / dist;
      sphere[2] = (sphere[3] * sphere[2] + delta * p[2]) / dist;
    }
  }
}
#undef VTK_ASSIGN_POINT

#define VTK_ASSIGN_SPHERE(_x, _y)                                                                  \
  do                                                                                               \
  {                                                                                                \
    _x[0] = _y[0];                                                                                 \
    _x[1] = _y[1];                                                                                 \
    _x[2] = _y[2];                                                                                 \
    _x[3] = _y[3];                                                                                 \
  } while (false)
// An approximation to the bounding sphere of a set of spheres. The algorithm
// creates an initial approximation from two spheres that are expected to be
// the farthest apart (taking into account their radius). A second pass may
// grow the bounding sphere if the remaining spheres are not contained within
// it. The hints[2] array indicates two spheres that are expected to be the
// farthest apart.
//------------------------------------------------------------------------------
template <class T>
void vtkSphereComputeBoundingSphere(
  T** spheres, vtkIdType numSpheres, T sphere[4], vtkIdType hints[2])
{
  if (numSpheres < 1)
  {
    sphere[0] = sphere[1] = sphere[2] = sphere[3] = 0.0;
    return;
  }
  else if (numSpheres == 1)
  {
    VTK_ASSIGN_SPHERE(sphere, spheres[0]);
    return;
  }

  // Okay two or more spheres
  vtkIdType i, j;
  T *s, s1[4], s2[4];
  if (hints)
  {
    s = spheres[hints[0]];
    VTK_ASSIGN_SPHERE(s1, s);
    s = spheres[hints[1]];
    VTK_ASSIGN_SPHERE(s2, s);
  }
  else // no hints provided, compute an initial guess
  {
    T xMin[4], xMax[4], yMin[4], yMax[4], zMin[4], zMax[4];
    xMin[0] = xMin[1] = xMin[2] = VTK_FLOAT_MAX;
    yMin[0] = yMin[1] = yMin[2] = VTK_FLOAT_MAX;
    zMin[0] = zMin[1] = zMin[2] = VTK_FLOAT_MAX;
    xMax[0] = xMax[1] = xMax[2] = -VTK_FLOAT_MAX;
    yMax[0] = yMax[1] = yMax[2] = -VTK_FLOAT_MAX;
    zMax[0] = zMax[1] = zMax[2] = -VTK_FLOAT_MAX;
    xMin[3] = xMax[3] = yMin[3] = yMax[3] = zMin[3] = zMax[3] = 0.0;

    // First part: Estimate the points furthest apart to define the largest sphere.
    // Find the points that span the greatest distance on the x-y-z axes. Use these
    // two points to define a sphere centered between the two points.
    for (i = 0; i < numSpheres; ++i)
    {
      s = spheres[i];
      if ((s[0] - s[3]) < (xMin[0] - xMin[3]))
        VTK_ASSIGN_SPHERE(xMin, s);
      if ((s[0] + s[3]) > (xMax[0] + xMax[3]))
        VTK_ASSIGN_SPHERE(xMax, s);
      if ((s[1] - s[3]) < (yMin[1] - yMin[3]))
        VTK_ASSIGN_SPHERE(yMin, s);
      if ((s[1] + s[3]) > (yMax[1] + yMax[3]))
        VTK_ASSIGN_SPHERE(yMax, s);
      if ((s[2] - s[3]) < (zMin[2] - zMin[3]))
        VTK_ASSIGN_SPHERE(zMin, s);
      if ((s[2] + s[3]) > (zMax[2] + zMax[3]))
        VTK_ASSIGN_SPHERE(zMax, s);
    }
    T xSpan =
      ((xMax[0] + xMax[3]) - (xMin[0] - xMin[3])) * ((xMax[0] + xMax[3]) - (xMin[0] - xMin[3])) +
      ((xMax[1] + xMax[3]) - (xMin[1] - xMin[3])) * ((xMax[1] + xMax[3]) - (xMin[1] - xMin[3])) +
      ((xMax[2] + xMax[3]) - (xMin[2] - xMin[3])) * ((xMax[2] + xMax[3]) - (xMin[2] - xMin[3]));
    T ySpan =
      ((yMax[0] + yMax[3]) - (yMin[0] - yMin[3])) * ((yMax[0] + yMax[3]) - (yMin[0] - yMin[3])) +
      ((yMax[1] + yMax[3]) - (yMin[1] - yMin[3])) * ((yMax[1] + yMax[3]) - (yMin[1] - yMin[3])) +
      ((yMax[2] + yMax[3]) - (yMin[2] - yMin[3])) * ((yMax[2] + yMax[3]) - (yMin[2] - yMin[3]));
    T zSpan =
      ((zMax[0] + zMax[3]) - (zMin[0] - zMin[3])) * ((zMax[0] + zMax[3]) - (zMin[0] - zMin[3])) +
      ((zMax[1] + zMax[3]) - (zMin[1] - zMin[3])) * ((zMax[1] + zMax[3]) - (zMin[1] - zMin[3])) +
      ((zMax[2] + zMax[3]) - (zMin[2] - zMin[3])) * ((zMax[2] + zMax[3]) - (zMin[2] - zMin[3]));

    if (xSpan > ySpan)
    {
      if (xSpan > zSpan)
      {
        VTK_ASSIGN_SPHERE(s1, xMin);
        VTK_ASSIGN_SPHERE(s2, xMax);
      }
      else
      {
        VTK_ASSIGN_SPHERE(s1, zMin);
        VTK_ASSIGN_SPHERE(s2, zMax);
      }
    }
    else // ySpan > xSpan
    {
      if (ySpan > zSpan)
      {
        VTK_ASSIGN_SPHERE(s1, yMin);
        VTK_ASSIGN_SPHERE(s2, yMax);
      }
      else
      {
        VTK_ASSIGN_SPHERE(s1, zMin);
        VTK_ASSIGN_SPHERE(s2, zMax);
      }
    }
  } // no hints provided

  // Compute initial estimated sphere, take into account the radius of each sphere
  T tmp, v[3], r2 = vtkMath::Distance2BetweenPoints(s1, s2) / 4.0;
  sphere[3] = r2 > 0.0 ? sqrt(r2) : s1[3];
  T t1 = -s1[3] / (2.0 * sphere[3]);
  T t2 = 1.0 + s2[3] / (2.0 * sphere[3]);
  for (i = 0; i < 3; ++i)
  {
    v[i] = s2[i] - s1[i];
    tmp = s1[i] + t1 * v[i];
    s2[i] = s1[i] + t2 * v[i];
    s1[i] = tmp;
    sphere[i] = (s1[i] + s2[i]) / 2.0;
  }
  r2 = vtkMath::Distance2BetweenPoints(s1, s2) / 4.0;
  if (r2 > 0.0)
  {
    sphere[3] = sqrt(r2);
  }
  else
  {
    sphere[3] = s1[3];
    r2 = sphere[3] * sphere[3];
  }

  // Second part: Make a pass over the points to make sure that they fit inside the sphere.
  // If not, adjust the sphere to fit the point.
  T dist, dist2, fac, sR2;
  for (i = 0; i < numSpheres; ++i)
  {
    s = spheres[i];
    sR2 = s[3] * s[3];
    dist2 = vtkMath::Distance2BetweenPoints(s, sphere);
    if (dist2 <= 0.0)
    {
      dist2 = s[3];
    }
    if (sR2 > dist2) // approximation to avoid square roots if possible
    {
      fac = 2.0 * sR2;
    }
    else
    {
      fac = 2.0 * dist2;
    }
    if ((dist2 + fac + sR2) > r2) // approximate test
    {
      dist = sqrt(dist2);
      if (((dist + s[3]) * (dist + s[3])) > r2) // more accurate test
      {
        for (j = 0; j < 3; ++j)
        {
          v[j] = s[j] - sphere[j];
          s1[j] = sphere[j] - (sphere[3] / dist) * v[j];
          s2[j] = sphere[j] + (1.0 + s[3] / dist) * v[j];
          sphere[j] = (s1[j] + s2[j]) / 2.0;
        }
        r2 = vtkMath::Distance2BetweenPoints(s1, s2) / 4.0;
        if (r2 > 0.0)
        {
          sphere[3] = sqrt(r2);
        }
        else
        {
          sphere[3] = std::max(s1[3], sphere[3]);
          r2 = sphere[3] * sphere[3];
        }
      }
    }
  }
}
#undef VTK_ASSIGN_SPHERE

// Type specific wrappers for the templated functions below
//------------------------------------------------------------------------------
void vtkSphere::ComputeBoundingSphere(
  float* pts, vtkIdType numPts, float sphere[4], vtkIdType hints[2])
{
  vtkSphereComputeBoundingSphere(pts, numPts, sphere, hints);
}
//------------------------------------------------------------------------------
void vtkSphere::ComputeBoundingSphere(
  double* pts, vtkIdType numPts, double sphere[4], vtkIdType hints[2])
{
  vtkSphereComputeBoundingSphere(pts, numPts, sphere, hints);
}
//------------------------------------------------------------------------------
void vtkSphere::ComputeBoundingSphere(
  float** spheres, vtkIdType numSpheres, float sphere[4], vtkIdType hints[2])
{
  vtkSphereComputeBoundingSphere(spheres, numSpheres, sphere, hints);
}
//------------------------------------------------------------------------------
void vtkSphere::ComputeBoundingSphere(
  double** spheres, vtkIdType numSpheres, double sphere[4], vtkIdType hints[2])
{
  vtkSphereComputeBoundingSphere(spheres, numSpheres, sphere, hints);
}

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

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