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

  Program:   Visualization Toolkit
  Module:    vtkIncrementalOctreePointLocator.cxx

  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.

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

#include "vtkMath.h"
#include "vtkPoints.h"
#include "vtkIdList.h"
#include "vtkPolyData.h"
#include "vtkCellArray.h"
#include "vtkDataArray.h"
#include "vtkFloatArray.h"
#include "vtkDoubleArray.h"
#include "vtkObjectFactory.h"
#include "vtkIncrementalOctreeNode.h"
#include "vtkIncrementalOctreePointLocator.h"

#include <map>
#include <list>
#include <stack>
#include <queue>
#include <vector>

vtkStandardNewMacro( vtkIncrementalOctreePointLocator );

// ---------------------------------------------------------------------------
// ----------------------------- Sorting  Points -----------------------------
// ---------------------------------------------------------------------------

//----------------------------------------------------------------------------
// Helper class for sorting points in support of point location, specifically,
// vtkIncrementalOctreePointLocator::FindClosestNPoints().
namespace
{
  class SortPoints
  {
  public:
    SortPoints( int N )
    {
        this->NumberPoints = 0;
        this->NumRequested = N;
        this->LargestDist2 = VTK_DOUBLE_MAX;
    }

    void InsertPoint( double dist2, vtkIdType pntId )
    {
        // a new pair may be inserted as long as the squared distance is less
        // than the largest one of the current map OR the number of inserted
        // points is still less than that of requested points
        if (    dist2 <= this->LargestDist2
             || this->NumberPoints < this->NumRequested
           )
        {
          this->NumberPoints ++;
          std::map<  double,  std::list< vtkIdType >  >::iterator
            it = this->dist2ToIds.find( dist2 );

          if ( it == this->dist2ToIds.end() )
          {
            // no any entry corresponds to this squared distance
            std::list< vtkIdType >   idset;
            idset.push_back( pntId );
            this->dist2ToIds[ dist2 ] = idset;
          }
          else
          {
            // there is an entry corresponding to this squared distance
            it->second.push_back( pntId );
          }

          if ( this->NumberPoints > this->NumRequested )
          {
            // we need to go to the very last entry
            it = this->dist2ToIds.end();
            --it;

            // Even if we remove the very last entry, the number of points
            // will still be greater than that of requested points. This
            // indicates we can safely remove the very last entry and update
            // the largest squared distance with that of the entry before the
            // very last one.
            if (   this->NumberPoints - it->second.size()
                 > this->NumRequested
               )
            {
              this->NumberPoints -= it->second.size();
              std::map<  double,  std::list< vtkIdType >  >::iterator
                it2 = it;
              --it2;
              this->LargestDist2 = it2->first;
              this->dist2ToIds.erase( it );
            }
          }
        }
    }

    void GetSortedIds( vtkIdList * idList )
    {
        // determine how many points will be actually exported
        idList->Reset();
        vtkIdType numIds = static_cast<vtkIdType>(
          ( this->NumRequested < this->NumberPoints )
            ? this->NumRequested : this->NumberPoints);

        idList->SetNumberOfIds( numIds );

        // clear the counter and go to the very first entry
        vtkIdType counter = 0;
        std::map<  double,  std::list< vtkIdType >  >::iterator
          it = this->dist2ToIds.begin();

        // export the point indices
        while ( counter < numIds && it != this->dist2ToIds.end() )
        {
          std::list<vtkIdType>::iterator lit = it->second.begin();

          while ( counter < numIds && lit != it->second.end() )
          {
            idList->InsertId( counter, *lit );
            counter ++;
            ++lit;
          }

          ++it;
        }
    }

    double GetLargestDist2()
    {
        return this->LargestDist2;
    }

  private:
    size_t  NumRequested;
    size_t  NumberPoints;
    double  LargestDist2;
    std::map<  double,  std::list< vtkIdType >  >  dist2ToIds;
  };
}

// ---------------------------------------------------------------------------
// --------------------- vtkIncrementalOctreePointLocator --------------------
// ---------------------------------------------------------------------------

//----------------------------------------------------------------------------
vtkIncrementalOctreePointLocator::vtkIncrementalOctreePointLocator()
{
  this->FudgeFactor      = 0;
  this->OctreeMaxDimSize = 0;
  this->BuildCubicOctree = 0;
  this->MaxPointsPerLeaf = 128;
  this->InsertTolerance2 = 0.000001;
  this->LocatorPoints  = nullptr;
  this->OctreeRootNode = nullptr;
}

//----------------------------------------------------------------------------
vtkIncrementalOctreePointLocator::~vtkIncrementalOctreePointLocator()
{
  this->FreeSearchStructure();
}

//----------------------------------------------------------------------------
void vtkIncrementalOctreePointLocator::DeleteAllDescendants
  ( vtkIncrementalOctreeNode * node )
{
  if ( node->IsLeaf() == 0 )
  {
    for ( int i = 0; i < 8; i ++ )
    {
      vtkIncrementalOctreeNode * child = node->GetChild( i );
      vtkIncrementalOctreePointLocator::DeleteAllDescendants( child );
      child = nullptr;
    }
    node->DeleteChildNodes();
  }
}

//----------------------------------------------------------------------------
void vtkIncrementalOctreePointLocator::FreeSearchStructure()
{
  if ( this->OctreeRootNode )
  {
    vtkIncrementalOctreePointLocator::DeleteAllDescendants
                                      ( this->OctreeRootNode );
    this->OctreeRootNode->Delete();
    this->OctreeRootNode = nullptr;
  }

  if ( this->LocatorPoints )
  {
    this->LocatorPoints->UnRegister( this );
    this->LocatorPoints = nullptr;
  }
}

//----------------------------------------------------------------------------
int vtkIncrementalOctreePointLocator::GetNumberOfPoints()
{
  return ( this->OctreeRootNode == nullptr )
         ? 0
         : this->OctreeRootNode->GetNumberOfPoints();
}

//----------------------------------------------------------------------------
void vtkIncrementalOctreePointLocator::GetBounds( double * bounds )
{
  if ( this->OctreeRootNode )
  {
    double *    minBounds = this->OctreeRootNode->GetMinBounds();
    double *    maxBounds = this->OctreeRootNode->GetMaxBounds();
    bounds[0] = minBounds[0];
    bounds[1] = maxBounds[0];
    bounds[2] = minBounds[1];
    bounds[3] = maxBounds[1];
    bounds[4] = minBounds[2];
    bounds[5] = maxBounds[2];
    minBounds = maxBounds = nullptr;
  }
}

//----------------------------------------------------------------------------
vtkIncrementalOctreeNode * vtkIncrementalOctreePointLocator::GetLeafContainer
  ( vtkIncrementalOctreeNode * node, const double pnt[3] )
{
  return (   ( node->IsLeaf() )
           ? node
           : this->GetLeafContainer
             (    node->GetChild(  node->GetChildIndex( pnt )  ),    pnt    )
         );
}

//----------------------------------------------------------------------------
vtkIdType vtkIncrementalOctreePointLocator::FindClosestInsertedPoint
  ( const double x[3] )
{
  if ( this->OctreeRootNode == nullptr ||
       this->OctreeRootNode->GetNumberOfPoints() == 0 ||
       this->OctreeRootNode->ContainsPoint( x )  == 0
     )
  {
    return -1;
  }

  double    miniDist2 = this->OctreeMaxDimSize * this->OctreeMaxDimSize * 4.0;
  double    elseDist2;      // inter-node search
  vtkIdType elsePntId;      // inter-node search

  vtkIncrementalOctreeNode * pLeafNode = this->GetLeafContainer( this->OctreeRootNode, x );
  vtkIdType pointIndx = this->FindClosestPointInLeafNode( pLeafNode, x, &miniDist2 );

  if ( miniDist2 > 0.0 )
  {
    if (   pLeafNode->GetDistance2ToInnerBoundary( x, this->OctreeRootNode )
         < miniDist2
       )
    {
      elsePntId = this->FindClosestPointInSphereWithoutTolerance
                        ( x, miniDist2, pLeafNode, &elseDist2 );
      if ( elseDist2 < miniDist2 )
      {
        pointIndx = elsePntId;
        miniDist2 = elseDist2;
      }
    }
  }

  pLeafNode = nullptr;
  return pointIndx;
}

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

  os << indent << "FudgeFactor: "      << this->FudgeFactor      << endl;
  os << indent << "LocatorPoints: "    << this->LocatorPoints    << endl;
  os << indent << "OctreeRootNode: "   << this->OctreeRootNode   << endl;
  os << indent << "BuildCubicOctree: " << this->BuildCubicOctree << endl;
  os << indent << "MaxPointsPerLeaf: " << this->MaxPointsPerLeaf << endl;
  os << indent << "InsertTolerance2: " << this->InsertTolerance2 << endl;
  os << indent << "OctreeMaxDimSize: " << this->OctreeMaxDimSize << endl;
}

//----------------------------------------------------------------------------
void vtkIncrementalOctreePointLocator::GenerateRepresentation
  ( int nodeLevel, vtkPolyData * polysData )
{
  if ( this->OctreeRootNode == nullptr )
  {
    vtkErrorMacro( "vtkIncrementalOctreePointLocator::GenerateRepresentation" );
    vtkErrorMacro( "(): the octree is not yet available" );
    return;
  }

  int            tempLevel;
  vtkPoints    * thePoints = nullptr;
  vtkCellArray * nodeQuads = nullptr;
  vtkIncrementalOctreeNode * pTempNode = nullptr;
  std::list < vtkIncrementalOctreeNode * > nodesList;
  std::queue< std::pair < vtkIncrementalOctreeNode *, int > > pairQueue;

  // recursively process the nodes in the octree
  pairQueue.push(  std::make_pair( this->OctreeRootNode, 0 )  );
  while ( !pairQueue.empty() )
  {
    pTempNode = pairQueue.front().first;
    tempLevel = pairQueue.front().second;
    pairQueue.pop();

    if ( tempLevel == nodeLevel )
    {
      nodesList.push_back( pTempNode );
    }
    else
    if ( !pTempNode->IsLeaf() )
    {
      for ( int i = 0; i < 8; i ++ )
      {
        pairQueue.push(    std::make_pair(  pTempNode->GetChild( i ),
                                               nodeLevel + 1
                                            )
                      );
      }
    }
  }

  // collect the vertices and quads of each node
  thePoints = vtkPoints::New();
  thePoints->Allocate(  8  *  static_cast < int > ( nodesList.size() )  );
  nodeQuads = vtkCellArray::New();
  nodeQuads->Allocate(  6  *  static_cast < int > ( nodesList.size() )  );
  for ( std::list< vtkIncrementalOctreeNode * >::iterator
        lit = nodesList.begin(); lit != nodesList.end(); ++lit )
  {
    vtkIncrementalOctreePointLocator::AddPolys( *lit, thePoints, nodeQuads );
  }

  // attach points and quads
  polysData->SetPoints( thePoints );
  polysData->SetPolys ( nodeQuads );
  thePoints->Delete();
  nodeQuads->Delete();
  thePoints = nullptr;
  nodeQuads = nullptr;
  pTempNode = nullptr;
}

//----------------------------------------------------------------------------
static vtkIdType OCTREE_NODE_FACES_LUT[6][4] =
{
  { 0, 1, 5, 4 },  { 0, 4, 6, 2 },
  { 6, 7, 3, 2 },  { 1, 3, 7, 5 },
  { 2, 3, 1, 0 },  { 4, 5, 7, 6 }
};

//----------------------------------------------------------------------------
void vtkIncrementalOctreePointLocator::AddPolys
  ( vtkIncrementalOctreeNode * node, vtkPoints * points, vtkCellArray * polygs )
{
  int         i;
  double      bounds[6];
  double      ptCord[3];
  vtkIdType   pntIds[8];
  vtkIdType   idList[4];

  node->GetBounds( bounds );
  for ( i = 0; i < 8; i ++ )
  {
    ptCord[0] = bounds[ i & 1 ];
    ptCord[1] = bounds[ i & 2 ];
    ptCord[2] = bounds[ i & 4 ];
    pntIds[i] = points->InsertNextPoint( ptCord );
  }

  for ( i = 0; i < 6; i ++ )
  {
    idList[0] = pntIds[ OCTREE_NODE_FACES_LUT[i][0] ];
    idList[1] = pntIds[ OCTREE_NODE_FACES_LUT[i][1] ];
    idList[2] = pntIds[ OCTREE_NODE_FACES_LUT[i][2] ];
    idList[3] = pntIds[ OCTREE_NODE_FACES_LUT[i][3] ];
    polygs->InsertNextCell( 4, idList );
  }
}

//----------------------------------------------------------------------------
vtkIdType vtkIncrementalOctreePointLocator::FindClosestPointInLeafNode
  ( vtkIncrementalOctreeNode * leafNode, const double point[3], double * dist2 )
{
  // NOTE: dist2 MUST be inited with a very huge value below,  but instead of
  // this->OctreeMaxDimSize * this->OctreeMaxDimSize * 4.0, because the point
  // under check may be outside the octree and hence the squared distance can
  // be greater than the latter or other similar octree-based specific values.
  *dist2 = VTK_DOUBLE_MAX;

  if ( leafNode->GetPointIdSet() == nullptr )
  {
    return -1;
  }

  int         numPts = 0;
  double      tmpDst = 0.0;
  double      tmpPnt[3];
  vtkIdType   tmpIdx = -1;
  vtkIdType   pntIdx = -1;
  vtkIdList * idList = leafNode->GetPointIdSet();
  numPts = idList->GetNumberOfIds( );

  for ( int i = 0; i < numPts; i ++ )
  {
    tmpIdx  = idList->GetId( i );
    this->LocatorPoints->GetPoint( tmpIdx, tmpPnt );
    tmpDst  = vtkMath::Distance2BetweenPoints( tmpPnt, point );
    if (  tmpDst  <  ( *dist2 )  )
    {
      *dist2  = tmpDst;
      pntIdx  = tmpIdx;
    }

    if (  ( *dist2 )  ==  0.0  )
    {
      break;
    }
  }

  idList = nullptr;

  return pntIdx;
}

//----------------------------------------------------------------------------
vtkIdType vtkIncrementalOctreePointLocator::FindClosestPointInSphere
  ( const double point[3], double radius2, vtkIncrementalOctreeNode * maskNode,
    double * minDist2, const double * refDist2 )
{
  vtkIdType  pointIndx = -1;
  std::stack< vtkIncrementalOctreeNode * >  nodesBase;
  nodesBase.push( this->OctreeRootNode );

  while ( !nodesBase.empty() && ( *minDist2 ) > 0.0 )
  {
    vtkIncrementalOctreeNode * checkNode = nodesBase.top();
    nodesBase.pop();

    if ( !checkNode->IsLeaf() )
    {
      for ( int i = 0; i < 8; i ++ )
      {
        vtkIncrementalOctreeNode * childNode = checkNode->GetChild( i );

        // use ( radius2 + radius2 ) to skip empty nodes
        double distToData = ( childNode->GetNumberOfPoints() )
                            ? childNode->GetDistance2ToBoundary
                                         ( point, this->OctreeRootNode, 1 )
                            : ( radius2 + radius2 );

        // If a child node is not the mask node AND its distance, specifically
        // the data bounding box (determined by the points inside or under) to
        // the point, is less than the threshold radius (one exception is the
        // point's container nodes), it is pushed to the stack as a suspect.
        if (    ( childNode != maskNode )
             && (    (  distToData <= ( *refDist2 )  )
                  || (  childNode->ContainsPoint( point )  ==  1  )
                )
           )
        {
          nodesBase.push( childNode );
        }

        childNode = nullptr;
      }
    }
    else
    {
      // now that the node under check is a leaf, let's find the closest
      // point therein and the minimum distance
      double tempDist2;
      int    tempPntId = this->FindClosestPointInLeafNode
                               ( checkNode, point, &tempDist2 );

      if (  tempDist2  <  ( *minDist2 )  )
      {
        *minDist2 = tempDist2;
        pointIndx = tempPntId;
      }
    }

    checkNode = nullptr;
  }

  return  (  ( *minDist2 )  <=  radius2  )  ?  pointIndx  :  -1;
}

// ---------------------------------------------------------------------------
// ----------------------------- Point  Location -----------------------------
// ---------------------------------------------------------------------------

//----------------------------------------------------------------------------
void vtkIncrementalOctreePointLocator::BuildLocator()
{
  // assume point location is necessary for vtkPointSet data only
  if ( !this->DataSet || !this->DataSet->IsA( "vtkPointSet" ) )
  {
    vtkErrorMacro( "Dataset is nullptr or it is not of type vtkPointSet" );
    return;
  }

  int  numPoints = this->DataSet->GetNumberOfPoints();
  if ( numPoints < 1 || numPoints >= VTK_INT_MAX )
  {
    // current implementation does not support 64-bit point indices
    // due to performance consideration
    vtkErrorMacro( << "No points to build an octree with or " );
    vtkErrorMacro( << "failure to support 64-bit point ids"  );
    return;
  }

  // construct an octree only if necessary
  if (    ( this->BuildTime > this->MTime )
       && ( this->BuildTime > this->DataSet->GetMTime() )
     )
  {
    return;
  }
  vtkDebugMacro( << "Creating an incremental octree" );

  // build an octree by populating it with check-free insertion of point ids
  double       theBounds[6];
  double       theCoords[3];
  vtkIdType    pointIndx;
  vtkPoints *  thePoints = vtkPointSet::SafeDownCast( this->DataSet )
                           ->GetPoints();
  thePoints->GetBounds( theBounds );
  this->InitPointInsertion( thePoints, theBounds );

  for ( pointIndx = 0; pointIndx < numPoints; pointIndx ++ )
  {
    thePoints->GetPoint( pointIndx, theCoords );

    // the 3D point coordinate is actually not inserted to vtkPoints at all
    // while only the point index is inserted to the vtkIdList of the
    // container leaf
    this->InsertPointWithoutChecking( theCoords, pointIndx, 0 );
  }
  thePoints = nullptr;

  this->BuildTime.Modified();
}

//----------------------------------------------------------------------------
vtkIdType vtkIncrementalOctreePointLocator::FindClosestPointInSphereWithoutTolerance
  ( const double point[3], double radius2,
    vtkIncrementalOctreeNode * maskNode, double * minDist2 )
{
  // It might be unsafe to use a ratio less than 1.1 since radius2 itself
  // could be very small and 1.00001 might just be equal to radius2.
  *minDist2 = radius2 * 1.1;
  return this->FindClosestPointInSphere( point, radius2, maskNode,
                                               minDist2, minDist2 );
}

//----------------------------------------------------------------------------
vtkIdType vtkIncrementalOctreePointLocator::FindClosestPoint
  ( double x, double y, double z )
{
  double dumbDist2;
  double theCoords[3] = { x, y, z };
  return this->FindClosestPoint( theCoords, &dumbDist2 );
}

//----------------------------------------------------------------------------
vtkIdType vtkIncrementalOctreePointLocator::FindClosestPoint( const double x[3] )
{
  double dumbDist2;
  return this->FindClosestPoint( x, &dumbDist2 );
}

//----------------------------------------------------------------------------
vtkIdType vtkIncrementalOctreePointLocator::FindClosestPoint
  ( double x, double y, double z, double * miniDist2 )
{
  double theCoords[3] = { x, y, z };
  return this->FindClosestPoint( theCoords, miniDist2 );
}

//----------------------------------------------------------------------------
vtkIdType vtkIncrementalOctreePointLocator::FindClosestPoint
  ( const double x[3], double * miniDist2 )
{
  this->BuildLocator();

  // init miniDist2 for early exit
  *miniDist2 = this->OctreeMaxDimSize * this->OctreeMaxDimSize * 4.0;
  if ( this->OctreeRootNode == nullptr ||
       this->OctreeRootNode->GetNumberOfPoints() == 0
     )
  {
    return -1;
  }

  double    elseDist2;      // inter-node search
  vtkIdType elsePntId;      // inter-node search
  vtkIdType pointIndx = -1;
  vtkIncrementalOctreeNode * pLeafNode = nullptr;

  if (  this->OctreeRootNode->ContainsPoint( x )  )
  {  // the point is inside the octree
    pLeafNode = this->GetLeafContainer( this->OctreeRootNode, x );
    pointIndx = this->FindClosestPointInLeafNode( pLeafNode, x, miniDist2 );

    if (  ( *miniDist2 )  >  0.0  )
    {
      if (   pLeafNode->GetDistance2ToInnerBoundary( x, this->OctreeRootNode )
           < ( *miniDist2 )
         )
      {
        elsePntId = this->FindClosestPointInSphereWithoutTolerance
                          ( x, *miniDist2, pLeafNode, &elseDist2 );
        if (  elseDist2  <  ( *miniDist2)  )
        {
          pointIndx  = elsePntId;
          *miniDist2 = elseDist2;
        }
      }
    }
  }
  else // the point is outside the octree
  {
    double   initialPt[3];
    double * minBounds = this->OctreeRootNode->GetMinBounds();
    double * maxBounds = this->OctreeRootNode->GetMaxBounds();
    this->OctreeRootNode->GetDistance2ToBoundary
                          ( x, initialPt, this->OctreeRootNode, 1 );

    // This initial (closest) point might be outside the octree a little bit
    if ( initialPt[0] <= minBounds[0] )
    {
      initialPt[0] = minBounds[0] + this->FudgeFactor;
    }
    else
    if ( initialPt[0] >= maxBounds[0] )
    {
      initialPt[0] = maxBounds[0] - this->FudgeFactor;
    }

    if ( initialPt[1] <= minBounds[1] )
    {
      initialPt[1] = minBounds[1] + this->FudgeFactor;
    }
    else
    if ( initialPt[1] >= maxBounds[1] )
    {
      initialPt[1] = maxBounds[1] - this->FudgeFactor;
    }

    if ( initialPt[2] <= minBounds[2] )
    {
      initialPt[2] = minBounds[2] + this->FudgeFactor;
    }
    else
    if ( initialPt[2] >= maxBounds[2] )
    {
      initialPt[2] = maxBounds[2] - this->FudgeFactor;
    }

    pLeafNode = this->GetLeafContainer( this->OctreeRootNode, initialPt );
    pointIndx = this->FindClosestPointInLeafNode( pLeafNode, x, miniDist2 );
    elsePntId = this->FindClosestPointInSphereWithoutTolerance
                      ( x, *miniDist2,  pLeafNode, &elseDist2 );

    if (  elseDist2  <  ( *miniDist2 )  )
    {
      pointIndx  = elsePntId;
      *miniDist2 = elseDist2;
    }

    minBounds = maxBounds = nullptr;
  }

  pLeafNode = nullptr;
  return pointIndx;
}

//----------------------------------------------------------------------------
vtkIdType vtkIncrementalOctreePointLocator::FindClosestPointWithinRadius
  ( double radius, const double x[3], double & dist2 )
{
  this->BuildLocator();
  return this->FindClosestPointInSphereWithoutTolerance
               ( x, radius * radius, nullptr, &dist2 );
}

//----------------------------------------------------------------------------
vtkIdType vtkIncrementalOctreePointLocator::FindClosestPointWithinSquaredRadius
  ( double radius2, const double x[3], double & dist2 )
{
  this->BuildLocator();
  return this->FindClosestPointInSphereWithoutTolerance
               ( x, radius2, nullptr, &dist2 );
}

//----------------------------------------------------------------------------
void vtkIncrementalOctreePointLocator::FindPointsWithinSquaredRadius
  ( vtkIncrementalOctreeNode * node, double radius2,
    const double point[3], vtkIdList * idList )
{
  int         i, j;
  int         numberPnts;
  double      tempValue0;
  double      tempValue1;
  double      pt2PtDist2;
  double      pointCoord[3];
  double      nodeBounds[6];
  double      outMinDst2 = 0.0; // min distance to the node: for outside point
  double      maximDist2 = 0.0; // max distance to the node: inside or outside
  vtkIdType   localIndex = 0;
  vtkIdType   pointIndex = 0;
  vtkIdList * nodePntIds = nullptr;

  node->GetBounds( nodeBounds );

  for ( i = 0; i < 3; i ++ ) // for each axis
  {
    j = ( i << 1 );
    tempValue0 = point[i] - nodeBounds[j];
    tempValue1 = nodeBounds[ j + 1 ] - point[i];

    if ( tempValue0 < 0.0 )
    {
      outMinDst2 += tempValue0 * tempValue0;
      maximDist2 += tempValue1 * tempValue1;
    }
    else
    if ( tempValue1 < 0.0 )
    {
      outMinDst2 += tempValue1 * tempValue1;
      maximDist2 += tempValue0 * tempValue0;
    }
    else
    if ( tempValue1 > tempValue0 )
    {
      maximDist2 += tempValue1 * tempValue1;
    }
    else
    {
      maximDist2 += tempValue0 * tempValue0;
    }
  }

  if ( outMinDst2 > radius2 )
  {
    // the node is totally outside the search sphere
    return;
  }

  if ( maximDist2 <= radius2 )
  {
    // the node is totally inside the search sphere
    node->ExportAllPointIdsByInsertion( idList );
    return;
  }

  // the node intersects with, but is not totally inside, the search sphere
  if ( node->IsLeaf() )
  {
    numberPnts = node->GetNumberOfPoints();
    nodePntIds = node->GetPointIdSet();

    for ( localIndex = 0; localIndex < numberPnts; localIndex ++ )
    {
      pointIndex = nodePntIds->GetId( localIndex );
      this->LocatorPoints->GetPoint( pointIndex, pointCoord );

      pt2PtDist2 = vtkMath::Distance2BetweenPoints( pointCoord, point );
      if ( pt2PtDist2 <= radius2 )
      {
        idList->InsertNextId( pointIndex );
      }
    }

    nodePntIds = nullptr;
  }
  else
  {
    for ( i = 0; i < 8; i ++ )
    {
      this->FindPointsWithinSquaredRadius(  node->GetChild( i ),
                                            radius2,  point,  idList  );
    }
  }
}

//----------------------------------------------------------------------------
void vtkIncrementalOctreePointLocator::FindPointsWithinSquaredRadius
  ( double R2, const double x[3], vtkIdList * result )
{
  result->Reset();
  this->BuildLocator();
  this->FindPointsWithinSquaredRadius( this->OctreeRootNode, R2, x, result );
}

//----------------------------------------------------------------------------
void vtkIncrementalOctreePointLocator::FindPointsWithinRadius( double R,
  const double x[3], vtkIdList * result )
{
  result->Reset();
  this->BuildLocator();
  this->FindPointsWithinSquaredRadius
        ( this->OctreeRootNode, R * R, x, result );
}

//----------------------------------------------------------------------------
void vtkIncrementalOctreePointLocator::FindClosestNPoints
  ( int N, const double x[3], vtkIdList * result )
{
  result->Reset();
  this->BuildLocator();

  int  totalPnts = this->OctreeRootNode->GetNumberOfPoints(); // possibly 0

  if ( N > totalPnts )
  {
    N = totalPnts;
    vtkWarningMacro( "Number of requested points > that of available points" );
  }

  if ( N <= 0 )
  {
    vtkWarningMacro( "invalid N or the octree is still empty" );
    return;
  }


  // We are going to find the lowest-possible node to start with, startNode,
  // by using a top-down recursive search mechanism. Such a starting node
  // belongs to one of the following cases (numPoints: number of points in or
  // under startNode).
  //
  // (1) startNode is a     leaf node AND numPoints = N
  // (2) startNode is a     leaf node AND numPoints > N
  // (3) startNode is a non-leaf node AND numPoints = N
  // (4) startNode is a non-leaf node AND numPoints > N
  //
  // * case 4 occurs, when none of the other three cases holds, by going one
  //   level up --- one-step regression.
  //
  // * The point may be outside startNode, as is usually the case, even if it
  //   is inside the octree root node. To address such scenarios, the initial
  //   point-inside-the-node case might be followed by the point-outside-the-
  //   node case to quickly locate the most compact startNode. Otherwise the
  //   resulting startNode might contain a huge number of points, which would
  //   significantly degrade the search performance.

  int         i;
  int         beenFound;
  int         numPoints;
  double      tempDist2;
  double      miniDist2;
  double      maxiDist2;
  double      pntCoords[3];
  vtkIdType   pointIndx;
  vtkIdList * pntIdList = nullptr;
  vtkIncrementalOctreeNode * startNode = nullptr;
  vtkIncrementalOctreeNode * pTheChild = nullptr;
  vtkIncrementalOctreeNode * pThisNode = this->OctreeRootNode;
  vtkIncrementalOctreeNode * theParent = pThisNode;
  std::queue< vtkIncrementalOctreeNode * > nodeQueue;

  beenFound = 0;
  numPoints = pThisNode->GetNumberOfPoints();
  while ( beenFound == 0 )
  {
    if (  pThisNode->ContainsPoint( x )  )      // point inside the node
    {
      while (  !pThisNode->IsLeaf()  &&  numPoints > N  )
      {
        theParent = pThisNode;
        pThisNode = pThisNode->GetChild(  pThisNode->GetChildIndex( x )  );
        numPoints = pThisNode->GetNumberOfPoints();
      }

      if ( numPoints )
      {
        // The point is still inside pThisNode
        beenFound = 1;
        pThisNode = ( numPoints >= N ) ? pThisNode : theParent;
      }
      else
      {
        // The point is inside an empty node (pThisNode), but outside the node
        // with closest points --- the closest node (a sibling of pThisNode).
        // We need to locate this closest node via the parent node and proceed
        // with it (the closest node) further in search for startNode, but by
        // means of the other case (point outside the node).
        miniDist2 = VTK_DOUBLE_MAX;
        for ( i = 0; i < 8; i ++ )
        {
          pTheChild = theParent->GetChild( i );
          tempDist2 = pTheChild->GetDistance2ToBoundary
                                 ( x, this->OctreeRootNode, 1 );
          if ( tempDist2 < miniDist2 )
          {
            miniDist2 = tempDist2;
            pThisNode = pTheChild;
          }
        }
      }
    }
    else                                        // point outside the node
    {
      while (  !pThisNode->IsLeaf()  &&  numPoints  >  N  )
      {
        // find the child closest (in terms of data) to the given point
        theParent = pThisNode;
        miniDist2 = VTK_DOUBLE_MAX;
        for ( i = 0; i < 8; i ++ )
        {
          pTheChild = theParent->GetChild( i );
          tempDist2 = pTheChild->GetDistance2ToBoundary
                                 ( x, this->OctreeRootNode, 1 );
          if ( tempDist2 < miniDist2 )
          {
            miniDist2 = tempDist2;
            pThisNode = pTheChild;
          }
        }
        numPoints = pThisNode->GetNumberOfPoints();
      }

      beenFound = 1;
      pThisNode = ( numPoints >= N ) ? pThisNode : theParent;
    }

    // update the number of points in the node in case of a switch from point-
    // inside-the-node to point-outside-the-node.
    numPoints = pThisNode->GetNumberOfPoints();
  }

  // this is where we can get the really most compact starting node
  startNode = pThisNode;
  numPoints = startNode->GetNumberOfPoints();


  // Given the starting node, we select the points inside it and sort them
  SortPoints  ptsSorter( N );
  pointIndx = 0;
  pntIdList = vtkIdList::New();
  pntIdList->SetNumberOfIds( numPoints );
  startNode->ExportAllPointIdsByDirectSet( &pointIndx, pntIdList );

  for ( i = 0; i < numPoints; i ++ )
  {
    pointIndx = pntIdList->GetId( i );
    this->LocatorPoints->GetPoint( pointIndx, pntCoords );
    tempDist2 = vtkMath::Distance2BetweenPoints( x, pntCoords );
    ptsSorter.InsertPoint( tempDist2, pointIndx );
  }


  // We still need to check other nodes in case they contain closer points
  nodeQueue.push( this->OctreeRootNode );
  maxiDist2 = ptsSorter.GetLargestDist2();
  while ( !nodeQueue.empty() )
  {
    pThisNode = nodeQueue.front();
    nodeQueue.pop();

    // skip the start node as we have just processed it
    if ( pThisNode == startNode )
    {
      continue;
    }

    if ( !pThisNode->IsLeaf() )
    {
      // this is a non-leaf node and we need to push some children if necessary
      for ( i = 0; i < 8; i ++ )
      {
        pTheChild = pThisNode->GetChild( i );
        if (    pTheChild->ContainsPointByData( x ) == 1
             || pTheChild->GetDistance2ToBoundary( x, this->OctreeRootNode, 1 )
                < maxiDist2
           )
        {
          nodeQueue.push( pTheChild );
        }
      }
    }
    else
    if (  pThisNode->GetDistance2ToBoundary( x, this->OctreeRootNode, 1 )
        < maxiDist2  )
    {
      // This is a leaf node AND its data bounding box is close enough for us
      // to process the points inside the node. Note that the success of the
      // above distance check indicates that there is at least one point in
      // the node. Otherwise the point-to-node distance (in terms of data)
      // would be VTK_DOUBLE_MAX.

      // obtain the point indices
      numPoints = pThisNode->GetNumberOfPoints();
      pointIndx = 0;
      pntIdList->Reset();
      pntIdList->SetNumberOfIds( numPoints );
      pThisNode->ExportAllPointIdsByDirectSet( &pointIndx, pntIdList );

      // insert the points to the sorter if necessary
      for ( i = 0; i < numPoints; i ++ )
      {
        pointIndx = pntIdList->GetId( i );
        this->LocatorPoints->GetPoint( pointIndx, pntCoords );
        tempDist2 = vtkMath::Distance2BetweenPoints( x, pntCoords );
        ptsSorter.InsertPoint( tempDist2, pointIndx );
      }

      // as we might have inserted some points, we need to update maxiDist2
      maxiDist2 = ptsSorter.GetLargestDist2();
    }
  }


  // obtain the point indices
  result->SetNumberOfIds( N );
  ptsSorter.GetSortedIds( result );


  // release memory
  pntIdList->Delete();
  pntIdList = nullptr;
  startNode = nullptr;
  pTheChild = nullptr;
  pThisNode = nullptr;
  theParent = nullptr;
}

// ---------------------------------------------------------------------------
// ----------------------------- Point Insertion -----------------------------
// ---------------------------------------------------------------------------

//----------------------------------------------------------------------------
int vtkIncrementalOctreePointLocator::InitPointInsertion( vtkPoints * points,
  const double bounds[6] )
{
  return this->InitPointInsertion( points, bounds, 0 );
}

//----------------------------------------------------------------------------
int vtkIncrementalOctreePointLocator::InitPointInsertion( vtkPoints * points,
  const double bounds[6], vtkIdType vtkNotUsed( estNumPts ) )
{
  int     i,  bbIndex;
  double  dimDiff[3], tmpBbox[6];

  if ( points == nullptr )
  {
    vtkErrorMacro( << "a valid vtkPoints object required for point insertion" );
    return 0;
  }

  // destroy the existing octree, if any
  this->FreeSearchStructure();

  // detach the old vtkPoints object, if any, before attaching a new one
  if ( this->LocatorPoints != nullptr )
  {
    this->LocatorPoints->UnRegister( this );
  }
  this->LocatorPoints = points;
  this->LocatorPoints->Register( this );

  // obtain the threshold squared distance
  this->InsertTolerance2 = this->Tolerance * this->Tolerance;

  // Fix bounds
  // (1) push out a little bit if the original volume is too flat --- a slab
  // (2) pull back the x, y, and z's lower bounds a little bit such that
  //     points are clearly "inside" the spatial region.  Point p is taken as
  //     "inside" range r = [r1, r2] if and only if r1 < p <= r2.
  this->OctreeMaxDimSize = 0.0;
  for ( i = 0; i < 3; i ++ )
  {
    bbIndex = ( i << 1 );
    tmpBbox[ bbIndex     ] = bounds[ bbIndex     ];
    tmpBbox[ bbIndex + 1 ] = bounds[ bbIndex + 1 ];
    dimDiff[i] = tmpBbox[ bbIndex + 1 ] - tmpBbox[ bbIndex ];
    this->OctreeMaxDimSize = ( dimDiff[i] > this->OctreeMaxDimSize )
                             ? dimDiff[i] : this->OctreeMaxDimSize;
  }

  if ( this->BuildCubicOctree )
  {
    // make the bounding box a cube and hence descendant octants cubes too
    for ( i = 0; i < 3; i ++ )
    {
      if ( dimDiff[i] != this->OctreeMaxDimSize )
      {
        double delta = this->OctreeMaxDimSize - dimDiff[i];
        tmpBbox[   i << 1       ] -= 0.5 * delta;
        tmpBbox[ ( i << 1 ) + 1 ] += 0.5 * delta;
        dimDiff[i] = this->OctreeMaxDimSize;
      }
    }
  }

  this->FudgeFactor  = this->OctreeMaxDimSize * 10e-6;
  double minSideSize = this->OctreeMaxDimSize * 10e-2;

  for ( i = 0; i < 3; i ++ )
  {
    if ( dimDiff[i] < minSideSize ) // case (1) above
    {
      bbIndex  =  ( i << 1 );
      double  tempVal = tmpBbox[ bbIndex ];
      tmpBbox[ bbIndex     ] = tmpBbox[ bbIndex + 1 ] - minSideSize;
      tmpBbox[ bbIndex + 1 ] = tempVal + minSideSize;
    }
    else                             // case (2) above
    {
      tmpBbox[ i << 1 ] -= this->FudgeFactor;
    }
  }

  // init the octree with an empty leaf node
  this->OctreeRootNode = vtkIncrementalOctreeNode::New();

  // this call internally inits the middle (center) and data range, too
  this->OctreeRootNode->SetBounds( tmpBbox[0], tmpBbox[1],
                                   tmpBbox[2], tmpBbox[3],
                                   tmpBbox[4], tmpBbox[5] );

  return 1;
}

//----------------------------------------------------------------------------
vtkIdType vtkIncrementalOctreePointLocator::FindClosestPointInSphereWithTolerance
  ( const double point[3], double radius2,
    vtkIncrementalOctreeNode * maskNode, double * minDist2 )
{
  *minDist2 = this->OctreeMaxDimSize * this->OctreeMaxDimSize * 4.0;
  return this->FindClosestPointInSphere( point, radius2, maskNode,
                                               minDist2, &radius2 );
}

//----------------------------------------------------------------------------
vtkIdType vtkIncrementalOctreePointLocator::FindDuplicateFloatTypePointInVisitedLeafNode
  ( vtkIncrementalOctreeNode * leafNode, const double point[3] )
{
  float *     tmpPnt = nullptr;
  vtkIdType   tmpIdx = -1;
  vtkIdType   pntIdx = -1;

  float       thePnt[3];
  thePnt[0] = static_cast< float >( point[0] );
  thePnt[1] = static_cast< float >( point[1] );
  thePnt[2] = static_cast< float >( point[2] );

  vtkIdList * idList = leafNode->GetPointIdSet();
  int numPts = idList->GetNumberOfIds( );
  float * pFloat = (  static_cast< vtkFloatArray * > ( this->LocatorPoints->GetData() )  )
                   ->GetPointer( 0 );

  for ( int i = 0; i < numPts; i ++ )
  {
    tmpIdx = idList->GetId( i );
    tmpPnt = pFloat + (  ( tmpIdx << 1 )  +  tmpIdx  );

    if (  ( thePnt[0] == tmpPnt[0] ) &&
          ( thePnt[1] == tmpPnt[1] ) &&
          ( thePnt[2] == tmpPnt[2] )
       )
    {
      pntIdx = tmpIdx;
      break;
    }
  }

  return pntIdx;
}

//----------------------------------------------------------------------------
vtkIdType vtkIncrementalOctreePointLocator::FindDuplicateDoubleTypePointInVisitedLeafNode
  ( vtkIncrementalOctreeNode * leafNode, const double point[3] )
{
  double *    tmpPnt = nullptr;
  vtkIdType   tmpIdx = -1;
  vtkIdType   pntIdx = -1;

  vtkIdList * idList = leafNode->GetPointIdSet();
  int numPts = idList->GetNumberOfIds( );
  double * pArray = (  static_cast< vtkDoubleArray * > ( this->LocatorPoints->GetData() )  )
                    ->GetPointer( 0 );

  for ( int i = 0; i < numPts; i ++ )
  {
    tmpIdx = idList->GetId( i );
    tmpPnt = pArray + (  ( tmpIdx << 1 )  +  tmpIdx  );

    if (  ( point[0] == tmpPnt[0] ) &&
          ( point[1] == tmpPnt[1] ) &&
          ( point[2] == tmpPnt[2] )
       )
    {
      pntIdx = tmpIdx;
      break;
    }
  }

  return pntIdx;
}

//----------------------------------------------------------------------------
vtkIdType vtkIncrementalOctreePointLocator::FindDuplicatePointInLeafNode
  ( vtkIncrementalOctreeNode * leafNode, const double point[3] )
{
  if ( leafNode->GetPointIdSet() == nullptr )
  {
    return -1;
  }

  return ( this->LocatorPoints->GetDataType() == VTK_FLOAT )
         ? this->FindDuplicateFloatTypePointInVisitedLeafNode
                 ( leafNode, point )
         : this->FindDuplicateDoubleTypePointInVisitedLeafNode
                 ( leafNode, point );
}

//----------------------------------------------------------------------------
vtkIdType vtkIncrementalOctreePointLocator::IsInsertedPointForZeroTolerance
  ( const double x[3], vtkIncrementalOctreeNode ** leafContainer )
{
  // the target leaf node always exists there since the root node of the
  // octree has been initialized to cover all possible points to be inserted
  // and therefore we do not need to check it here
  *leafContainer = this->GetLeafContainer( this->OctreeRootNode, x );
  vtkIdType  pointIdx = this->FindDuplicatePointInLeafNode
                              ( *leafContainer, x );
  return pointIdx;
}

//----------------------------------------------------------------------------
vtkIdType vtkIncrementalOctreePointLocator::IsInsertedPointForNonZeroTolerance
  ( const double x[3], vtkIncrementalOctreeNode ** leafContainer )
{
  double     minDist2;  // min distance to ALL existing points
  double     elseDst2;  // min DiSTance to other nodes (inner boundaries)
  double     dist2Ext;  // min distance to an EXTended set of nodes
  vtkIdType  pntIdExt;

  // the target leaf node always exists there since the root node of the
  // octree has been initialized to cover all possible points to be inserted
  // and therefore we do not need to check it here
  *leafContainer = this->GetLeafContainer( this->OctreeRootNode, x );
  vtkIdType  pointIdx = this->FindClosestPointInLeafNode
                              ( *leafContainer, x, &minDist2 );

  if ( minDist2 == 0.0 )
  {
    return pointIdx;
  }

  // As no any 'duplicate' point exists in this leaf node, we need to expand
  // the search scope to capture possible closer points in other nodes.
  elseDst2 = ( *leafContainer )->GetDistance2ToInnerBoundary
                                 ( x, this->OctreeRootNode );

  if ( elseDst2 < this->InsertTolerance2 )
  {
    // one or multiple closer points might exist in the neighboring nodes
    pntIdExt = this->FindClosestPointInSphereWithTolerance
                     (  x, this->InsertTolerance2, *leafContainer, &dist2Ext );

    if ( dist2Ext < minDist2 )
    {
      minDist2 = dist2Ext;
      pointIdx = pntIdExt;
    }
  }

  return  ( minDist2 <= this->InsertTolerance2 )  ?  pointIdx  :  -1;
}

//----------------------------------------------------------------------------
vtkIdType vtkIncrementalOctreePointLocator::IsInsertedPoint
  ( double x, double  y, double z )
{
  double xyz[3] = { x, y, z };
  return this->IsInsertedPoint( xyz );
}

//----------------------------------------------------------------------------
vtkIdType vtkIncrementalOctreePointLocator::IsInsertedPoint( const double x[3] )
{
  vtkIncrementalOctreeNode * leafContainer = nullptr;
  return this->IsInsertedPoint( x, &leafContainer );
}

//----------------------------------------------------------------------------
vtkIdType vtkIncrementalOctreePointLocator::IsInsertedPoint
  ( const double x[3], vtkIncrementalOctreeNode ** leafContainer )
{
  return ( this->InsertTolerance2 == 0.0 )
         ? this->IsInsertedPointForZeroTolerance( x, leafContainer )
         : this->IsInsertedPointForNonZeroTolerance( x, leafContainer );
}

//----------------------------------------------------------------------------
int vtkIncrementalOctreePointLocator::InsertUniquePoint( const double point[3],
                                                         vtkIdType & pntId )
{
  vtkIncrementalOctreeNode * leafContainer = nullptr;
  pntId = this->IsInsertedPoint( point, &leafContainer );
  return (    ( pntId > -1 )
           ?  0
           :  leafContainer->InsertPoint( this->LocatorPoints,     point,
                                          this->MaxPointsPerLeaf, &pntId, 2 )
         );
}

//----------------------------------------------------------------------------
void vtkIncrementalOctreePointLocator::InsertPointWithoutChecking
  ( const double point[3], vtkIdType & pntId, int insert )
{
  this->GetLeafContainer( this->OctreeRootNode, point )
      ->InsertPoint( this->LocatorPoints,     point,
                     this->MaxPointsPerLeaf, &pntId, ( insert << 1 ) );
}

//----------------------------------------------------------------------------
void vtkIncrementalOctreePointLocator::InsertPoint
  ( vtkIdType ptId, const double x[3] )
{
  this->GetLeafContainer( this->OctreeRootNode, x )
      ->InsertPoint( this->LocatorPoints, x, this->MaxPointsPerLeaf, &ptId, 1 );
}

//----------------------------------------------------------------------------
vtkIdType vtkIncrementalOctreePointLocator::InsertNextPoint( const double x[3] )
{
  vtkIdType  pntId = -1;
  this->GetLeafContainer( this->OctreeRootNode, x )
      ->InsertPoint( this->LocatorPoints, x, this->MaxPointsPerLeaf, &pntId, 2 );
  return pntId;
}

