/* Copyright (c) 2002-2012 Croteam Ltd. 
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as published by
the Free Software Foundation


This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */

#include "stdh.h"

#include <Engine/Templates/BSP.h>
#include <Engine/Templates/BSP_internal.h>

#include <Engine/Base/Stream.h>
#include <Engine/Math/Vector.h>
#include <Engine/Math/Plane.h>
#include <Engine/Math/OBBox.h>
#include <Engine/Math/Functions.h>

#include <Engine/Templates/StaticStackArray.cpp>
#include <Engine/Templates/DynamicArray.cpp>

#include <Engine/Base/CRC.h>

static ULONG HashPtr64To32(UINT64 a)
{
  // null pointers must be preserved
  if (a == 0)
  {
    return 0;
  }

  ULONG r;
  CRC_Start(r);
  CRC_AddBYTE(r, UBYTE(a >> 56));
  CRC_AddBYTE(r, UBYTE(a >> 48));
  CRC_AddBYTE(r, UBYTE(a >> 40));
  CRC_AddBYTE(r, UBYTE(a >> 32));
  CRC_AddBYTE(r, UBYTE(a >> 24));
  CRC_AddBYTE(r, UBYTE(a >> 16));
  CRC_AddBYTE(r, UBYTE(a >> 8));
  CRC_AddBYTE(r, UBYTE(a >> 0));
  CRC_Finish(r);

  return r;
}


// epsilon value used for BSP cutting
//#define BSP_EPSILON ((Type) 0.015625)       // 1/2^6 ~= 1.5 cm
#define BSP_EPSILON Type((1.0/65536.0)*4*mth_fCSGEpsilon) // 1/2^16
//#define BSP_EPSILON Type(0.00390625) // 1/2^8
//#define EPSILON (1.0f/8388608.0f) // 1/2^23
//#define EPSILON 0.0009765625f // 1/2^10
//#define EPSILON 0.03125f    // 1/2^5
//#define EPSILON 0.00390625f // 1/2^8

template <class Type>
inline BOOL EpsilonEq(const Type &a, const Type &b) { return Abs(a-b)<=BSP_EPSILON; };
template <class Type>
inline BOOL EpsilonNe(const Type &a, const Type &b) { return Abs(a-b)> BSP_EPSILON; };

/////////////////////////////////////////////////////////////////////
// BSP vertex

/*
 * Assignment operator with coordinates only.
 */
template<class Type, int iDimensions>
BSPVertex<Type, iDimensions> &BSPVertex<Type, iDimensions>::operator=(const Vector<Type, iDimensions> &vCoordinates)
{
  *(Vector<Type, iDimensions> *)this = vCoordinates;
  return *this;
}

/////////////////////////////////////////////////////////////////////
// BSP vertex container

/*
 * Default constructor.
 */
template<class Type, int iDimensions>
BSPVertexContainer<Type, iDimensions>::BSPVertexContainer(void)
{
}

template<class Type, int iDimensions>
void BSPVertexContainer<Type, iDimensions>::AddVertex(const Vector<Type, iDimensions> &vPoint)
{
  bvc_aVertices.Push() = vPoint;
}

/*
 * Initialize for a direction.
 */
template<class Type, int iDimensions>
void BSPVertexContainer<Type, iDimensions>::Initialize(const Vector<Type, iDimensions> &vDirection)
{
  bvc_vDirection = vDirection;

  // init array of vertices
  bvc_aVertices.SetAllocationStep(32);

  // find largest axis of direction vector
  INDEX iMaxAxis = 0;
  Type tMaxAxis = (Type)0;//vDirection(1);
  for( INDEX iAxis=1; iAxis<=iDimensions; iAxis++) {
    if( Abs(vDirection(iAxis)) > Abs(tMaxAxis) ) {
      tMaxAxis = vDirection(iAxis);
      iMaxAxis = iAxis;
    }
  }

/* This assert would seem natural here, but it is not possible because of parallel planes!
    // must be greater or equal than minimal max axis of any normalized vector in that space
    ASSERT( Abs(tMaxAxis) > (1.0/sqrt(double(iDimensions))-0.01) );
*/

  // remember that axis index and sign for sorting
  bvc_iMaxAxis = iMaxAxis;
  bvc_tMaxAxisSign = Sgn(tMaxAxis);
}

/*
 * Unnitialize.
 */
template<class Type, int iDimensions>
void BSPVertexContainer<Type, iDimensions>::Uninitialize(void)
{
  // delete array of vertices
  bvc_aVertices.Delete();
  // destroy axis index and sign
  bvc_iMaxAxis = -1;
  bvc_tMaxAxisSign = (Type)0;
}

static INDEX qsort_iCompareAxis;

template<class Type, int iDimensions>
class CVertexComparator {
public:
  /*
   * Compare two vertices.
   */
  static inline int CompareVertices(const Vector<Type, iDimensions> &vx0, const Vector<Type, iDimensions> &vx1, INDEX iAxis)
  {
         if (vx0(iAxis)<vx1(iAxis)) return -1;
    else if (vx0(iAxis)>vx1(iAxis)) return 1;
    else                            return 0;
  }

  /*
   * Compare two vertices for quick-sort.
   */
  static int qsort_CompareVertices_plus( const void *pvVertex0, const void *pvVertex1)
  {
    BSPVertex<Type, iDimensions> &vx0 = *(BSPVertex<Type, iDimensions> *)pvVertex0;
    BSPVertex<Type, iDimensions> &vx1 = *(BSPVertex<Type, iDimensions> *)pvVertex1;
    return +CompareVertices(vx0, vx1, qsort_iCompareAxis);
  }
  static int qsort_CompareVertices_minus( const void *pvVertex0, const void *pvVertex1)
  {
    BSPVertex<Type, iDimensions> &vx0 = *(BSPVertex<Type, iDimensions> *)pvVertex0;
    BSPVertex<Type, iDimensions> &vx1 = *(BSPVertex<Type, iDimensions> *)pvVertex1;
    return -CompareVertices(vx0, vx1, qsort_iCompareAxis);
  }
};
/*
 * Sort vertices in this container along the largest axis of container direction.
 */
template<class Type, int iDimensions>
void BSPVertexContainer<Type, iDimensions>::Sort(void)
{
  // if there are no vertices, or the container is not line
  if (bvc_aVertices.Count()==0 || IsPlannar()) {
    // do not attempt to sort
    return;
  }

  // sort by max. axis
  qsort_iCompareAxis = bvc_iMaxAxis;

  // if the sign of axis is positive
  if (bvc_tMaxAxisSign>0) {
    // sort them normally
    if (bvc_aVertices.Count()>0) {
      qsort(&bvc_aVertices[0], bvc_aVertices.Count(), sizeof(BSPVertex<Type, iDimensions>),
        CVertexComparator<Type, iDimensions>::qsort_CompareVertices_plus);
    }
  // if it is negative
  } else {
    // sort them inversely
    if (bvc_aVertices.Count()>0) {
      qsort(&bvc_aVertices[0], bvc_aVertices.Count(), sizeof(BSPVertex<Type, iDimensions>),
          CVertexComparator<Type, iDimensions>::qsort_CompareVertices_minus);
    }
  }
}

/*
 * Elliminate paired vertices.
 */
template<class Type, int iDimensions>
void BSPVertexContainer<Type, iDimensions>::ElliminatePairedVertices(void)
{
  // if there are no vertices, or the container is not line
  if (bvc_aVertices.Count()==0 || IsPlannar()) {
    // do not attempt to sort
    return;
  }

  // initially, last vertices are far away
  Type tLastInside;  tLastInside  = (Type)32000;
  BSPVertex<Type, iDimensions> *pbvxLastInside  = NULL;

  // for all vertices in container
  for (INDEX iVertex=0; iVertex<bvc_aVertices.Count(); iVertex++) {
    BSPVertex<Type, iDimensions> &bvx = bvc_aVertices[iVertex];    // reference to this vertex
    Type t = bvx(bvc_iMaxAxis);                 // coordinate along max. axis

    // if last inside vertex is next to this one
    if ( EpsilonEq(t, tLastInside) ) {
      // last vertex is far away
      tLastInside  = (Type)32000;
      IFDEBUG(pbvxLastInside = NULL);

    // otherwise
    } else {
      // make this last inside vertex
      tLastInside = t;
      pbvxLastInside = &bvx;
    }
  }
}

/*
 * Create edges from vertices in one container -- must be sorted before.
 */
template<class Type, int iDimensions>
void BSPVertexContainer<Type, iDimensions>::CreateEdges(CDynamicArray<BSPEdge<Type, iDimensions> > &abed, UINT64 ulEdgeTag)
{
  // if there are no vertices, or the container is not line
  if (bvc_aVertices.Count()==0 || IsPlannar()) {
    // do not attempt to sort
    return;
  }

  // initially, edge is inactive
  BOOL bActive = FALSE;
  BSPEdge<Type, iDimensions> *pbed = NULL;

  // for all vertices in container
  for (INDEX iVertex=0; iVertex<bvc_aVertices.Count(); iVertex++) {
    BSPVertex<Type, iDimensions> &bvx = bvc_aVertices[iVertex];    // reference to this vertex

    // if edge is inactive
    if (!bActive) {
      // create new edge
      pbed = abed.New();
      pbed->bed_ulEdgeTag = ulEdgeTag;
      // set start vertex
      pbed->bed_vVertex0 = bvx;
    } else {
      // set end vertex
      pbed->bed_vVertex1 = bvx;
      // trash edge pointer
      IFDEBUG(pbed = NULL);
    }
    // toggle edge
    bActive = !bActive;
  }
}

/////////////////////////////////////////////////////////////////////
// BSP edge

/*
 * Constructor with two vectors.
 */
template<class Type, int iDimensions>
BSPEdge<Type, iDimensions>::BSPEdge(const Vector<Type, iDimensions> &vVertex0, const Vector<Type, iDimensions> &vVertex1, UINT64 ulTag)
  : bed_vVertex0(vVertex0)
  , bed_vVertex1(vVertex1)
  , bed_ulEdgeTag(ulTag)
{}

// remove all edges marked for removal
template<class Type, int iDimensions>
void BSPEdge<Type, iDimensions>::RemoveMarkedBSPEdges(CDynamicArray<BSPEdge<Type, iDimensions> > &abed)
{
  typedef BSPEdge<Type, iDimensions> edge_t; // local declaration, to fix macro expansion in FOREACHINDYNAMICARRAY
  // conut edges left
  INDEX ctEdgesLeft = 0;
  {FOREACHINDYNAMICARRAY(abed, edge_t, itbed) {
    if (itbed->bed_ulEdgeTag != 0) {
      ctEdgesLeft++;
    }
  }}
  // make a copy of array without removed edges
  CDynamicArray<BSPEdge<Type, iDimensions> > abed2;
  abed2.New(ctEdgesLeft);
  abed2.Lock();
  INDEX iedNew = 0;
  {FOREACHINDYNAMICARRAY(abed, edge_t, itbed) {
    edge_t &bed = *itbed;
    if (bed.bed_ulEdgeTag != 0) {
      abed2[iedNew] = bed;
      iedNew++;
    }
  }}
  abed2.Unlock();
  // use that copy instead the original array
  abed.Clear();
  abed.MoveArray(abed2);
}

// optimize a polygon made out of BSP edges using tag information
template<class Type, int iDimensions>
void BSPEdge<Type, iDimensions>::OptimizeBSPEdges(CDynamicArray<BSPEdge<Type, iDimensions> > &abed)
{
  typedef BSPEdge<Type, iDimensions> edge_t; // local declaration, to fix macro expansion in FOREACHINDYNAMICARRAY

  // if there are no edges
  if (abed.Count()==0) {
    // do nothing
    return;
  }
  BOOL bSomeJoined;
  // repeat
  do {
    bSomeJoined = FALSE;
    // for each edge
    {FOREACHINDYNAMICARRAY(abed, edge_t, itbed1) {
      edge_t &bed1 = *itbed1;
      // if it is already marked
      if (bed1.bed_ulEdgeTag == 0) {
        // skip it
        continue;
      }
      // if it is dummy edge
      if (bed1.bed_vVertex0==bed1.bed_vVertex1) {
        // mark it for removal
        bSomeJoined = TRUE;
        bed1.bed_ulEdgeTag = 0;
        // skip it
        continue;
      }

      // for each other edge
      {FOREACHINDYNAMICARRAY(abed, edge_t, itbed2) {
        edge_t &bed2 = *itbed2;
        if (&bed1==&bed2) {
          continue;
        }
        // if it is already marked
        if (bed2.bed_ulEdgeTag == 0) {
          // skip it
          continue;
        }
        // if they originate from same edge (plane)
        if (bed1.bed_ulEdgeTag == bed2.bed_ulEdgeTag) {
          // if they are complemented
          if (bed1.bed_vVertex0==bed2.bed_vVertex1 && bed1.bed_vVertex1==bed2.bed_vVertex0) {
            // marked them both
            bSomeJoined = TRUE;
            bed1.bed_ulEdgeTag = 0;
            bed2.bed_ulEdgeTag = 0;
            // skip them both
            break;
          }
          // if second one continues after first one
          if (bed1.bed_vVertex1==bed2.bed_vVertex0) {
            // extend end of first edge to the end of second one
            bed1.bed_vVertex1=bed2.bed_vVertex1;
            bSomeJoined = TRUE;
            // marked second edge
            bed2.bed_ulEdgeTag = 0;
          // if second one continues before first one
          } else if (bed1.bed_vVertex0==bed2.bed_vVertex1) {
            // extend start of first edge to the start of second one
            bed1.bed_vVertex0=bed2.bed_vVertex0;
            bSomeJoined = TRUE;
            // marked second edge
            bed2.bed_ulEdgeTag = 0;
          }
        }
      }}
    }}
    // while some edges can be joined
  } while(bSomeJoined);

  // remove all marked edges
  RemoveMarkedBSPEdges(abed);
}

/////////////////////////////////////////////////////////////////////
// BSP polygon

/*
 * Add an edge to the polygon.
 */
template<class Type, int iDimensions>
inline void BSPPolygon<Type, iDimensions>::AddEdge(const Vector<Type, iDimensions> &vPoint0, const Vector<Type, iDimensions> &vPoint1, UINT64 ulTag)
{
  *bpo_abedPolygonEdges.New() = BSPEdge<Type, iDimensions>(vPoint0, vPoint1, ulTag);
}

/////////////////////////////////////////////////////////////////////
// BSP node

/*
 * Recursive destructor.
 */
template<class Type, int iDimensions>
void BSPNode<Type, iDimensions>::DeleteBSPNodeRecursively(void)
{
  // delete sub-trees first, before deleting this node
  if (bn_pbnFront!=NULL) {
    bn_pbnFront->DeleteBSPNodeRecursively();
  }
  if (bn_pbnBack!=NULL) {
    bn_pbnBack->DeleteBSPNodeRecursively();
  }
  delete this;
}

/*
 * Constructor for a leaf node.
 */
template<class Type, int iDimensions>
BSPNode<Type, iDimensions>::BSPNode(enum BSPNodeLocation bnl)
  : bn_bnlLocation(bnl)
  , bn_pbnFront(NULL)
  , bn_pbnBack(NULL)
{
  ASSERT(bnl == BNL_INSIDE || bnl == BNL_OUTSIDE);
}

/*
 * Constructor for a branch node.
 */
template<class Type, int iDimensions>
BSPNode<Type, iDimensions>::BSPNode(const Plane<Type, iDimensions> &plSplitPlane, UINT64 ulPlaneTag,
                 BSPNode<Type, iDimensions> &bnFront, BSPNode<Type, iDimensions> &bnBack)
  : Plane<Type, iDimensions>(plSplitPlane)
  , bn_pbnFront(&bnFront)
  , bn_pbnBack(&bnBack)
  , bn_bnlLocation(BNL_BRANCH)
  , bn_ulPlaneTag(ulPlaneTag)
{
}

/*
 * Constructor for cloning a bsp (sub)tree.
 */
template<class Type, int iDimensions>
BSPNode<Type, iDimensions>::BSPNode(BSPNode<Type, iDimensions> &bnRoot)
  : Plane<Type, iDimensions>(bnRoot)                             // copy the plane
  , bn_bnlLocation(bnRoot.bn_bnlLocation)     // copy the location
  , bn_ulPlaneTag(bnRoot.bn_ulPlaneTag)       // copy the plane tag
{
  // if this has a front child
  if (bnRoot.bn_pbnFront != NULL) {
    // clone front sub tree
    bn_pbnFront = new BSPNode<Type, iDimensions>(*bnRoot.bn_pbnFront);
  // otherwise
  } else {
    // no front sub tree
    bn_pbnFront = NULL;
  }

  // if this has a back child
  if (bnRoot.bn_pbnBack != NULL) {
    // clone back sub tree
    bn_pbnBack  = new BSPNode<Type, iDimensions>(*bnRoot.bn_pbnBack);
  // otherwise
  } else {
    // no back sub tree
    bn_pbnBack  = NULL;
  }
}

/* Test if a sphere is inside, outside, or intersecting. (Just a trivial rejection test) */
template<class Type, int iDimensions>
FLOAT BSPNode<Type, iDimensions>::TestSphere(const Vector<Type, iDimensions> &vSphereCenter, Type tSphereRadius) const
{
  // if this is an inside node
  if (bn_bnlLocation == BNL_INSIDE) {
    // it is inside
    return 1;
  // if this is an outside node
  } else if (bn_bnlLocation == BNL_OUTSIDE) {
    // it is outside
    return -1;
  // if this is a branch
  } else {
    ASSERT(bn_bnlLocation == BNL_BRANCH);
    // test the sphere against the split plane
    Type tCenterDistance = PointDistance(vSphereCenter);
    // if the sphere is in front of the plane
    if (tCenterDistance > +tSphereRadius) {
      // recurse down the front node
      return bn_pbnFront->TestSphere(vSphereCenter, tSphereRadius);
    // if the sphere is behind the plane
    } else if (tCenterDistance < -tSphereRadius) {
      // recurse down the back node
      return bn_pbnBack->TestSphere(vSphereCenter, tSphereRadius);
    // if the sphere is split by the plane
    } else {
      // if front node touches
      FLOAT fFront = bn_pbnFront->TestSphere(vSphereCenter, tSphereRadius);
      if (fFront==0) {
        // it touches
        return 0;
      }
      // if back node touches
      FLOAT fBack = bn_pbnBack->TestSphere(vSphereCenter, tSphereRadius);
      if (fBack==0) {
        // it touches
        return 0;
      }
      // if front and back have same classification
      if (fFront==fBack) {
        // return it
        return fFront;
      // if front and back have different classification
      } else {
        // it touches
        return 0;
      }
    }
  }
}
/* Test if a box is inside, outside, or intersecting. (Just a trivial rejection test) */
template<class Type, int iDimensions>
FLOAT BSPNode<Type, iDimensions>::TestBox(const OBBox<Type> &box) const
{
  // if this is an inside node
  if (bn_bnlLocation == BNL_INSIDE) {
    // it is inside
    return 1;
  // if this is an outside node
  } else if (bn_bnlLocation == BNL_OUTSIDE) {
    // it is outside
    return -1;
  // if this is a branch
  } else {
    ASSERT(bn_bnlLocation == BNL_BRANCH);
    // test the box against the split plane
    Type tTest = box.TestAgainstPlane(*this);
    // if the sphere is in front of the plane
    if (tTest>0) {
      // recurse down the front node
      return bn_pbnFront->TestBox(box);
    // if the sphere is behind the plane
    } else if (tTest<0) {
      // recurse down the back node
      return bn_pbnBack->TestBox(box);
    // if the sphere is split by the plane
    } else {
      // if front node touches
      FLOAT fFront = bn_pbnFront->TestBox(box);
      if (fFront==0) {
        // it touches
        return 0;
      }
      // if back node touches
      FLOAT fBack = bn_pbnBack->TestBox(box);
      if (fBack==0) {
        // it touches
        return 0;
      }
      // if front and back have same classification
      if (fFront==fBack) {
        // return it
        return fFront;
      // if front and back have different classification
      } else {
        // it touches
        return 0;
      }
    }
  }
}

// find minimum/maximum parameters of points on a line that are inside - recursive
template<class Type, int iDimensions>
void BSPNode<Type, iDimensions>::FindLineMinMax(
  BSPLine<Type, iDimensions> &bl, 
  const Vector<Type, iDimensions> &v0,
  const Vector<Type, iDimensions> &v1,
  Type t0, Type t1)
{
  // if this is an inside node
  if (bn_bnlLocation == BNL_INSIDE) {
    // just update min/max
    bl.bl_tMin = Min(bl.bl_tMin, t0);
    bl.bl_tMax = Max(bl.bl_tMax, t1);
    return;
  // if this is an outside node
  } else if (bn_bnlLocation == BNL_OUTSIDE) {
    // do nothing
    return;
  // if this is a branch
  } else {
    ASSERT(bn_bnlLocation == BNL_BRANCH);
    // test the points against the split plane
    Type tD0 = PointDistance(v0);
    Type tD1 = PointDistance(v1);
    // if both are front
    if (tD0>=0 && tD1>=0) {
      // recurse down the front node
      bn_pbnFront->FindLineMinMax(bl, v0, v1, t0, t1);
      return;
    // if both are back
    } else if (tD0<0 && tD1<0) {
      // recurse down the back node
      bn_pbnBack->FindLineMinMax(bl, v0, v1, t0, t1);
      return;
    // if on different sides
    } else {
      // find split point
      Type tFraction = tD0/(tD0-tD1);
      Vector<Type, iDimensions> vS = v0+(v1-v0)*tFraction;
      Type tS = t0+(t1-t0)*tFraction;
      // if first is front
      if (tD0>=0) {
        // recurse first part down the front node
        bn_pbnFront->FindLineMinMax(bl, v0, vS, t0, tS);
        // recurse second part down the back node
        bn_pbnBack->FindLineMinMax(bl, vS, v1, tS, t1);
        return;
      // if first is back
      } else {
        // recurse first part down the back node
        bn_pbnBack->FindLineMinMax(bl, v0, vS, t0, tS);
        // recurse second part down the front node
        bn_pbnFront->FindLineMinMax(bl, vS, v1, tS, t1);
        return;
      }
    }
  }
}

/////////////////////////////////////////////////////////////////////
// BSP cutter

/*
 * Constructor for splitting a polygon with a BSP tree.
 */
template<class Type, int iDimensions>
BSPCutter<Type, iDimensions>::BSPCutter(BSPPolygon<Type, iDimensions> &bpoPolygon, BSPNode<Type, iDimensions> &bnRoot)
{
  // cut the polygon with entire tree
  CutPolygon(bpoPolygon, bnRoot);
}

/*
 * Destructor.
 */
template<class Type, int iDimensions>
BSPCutter<Type, iDimensions>::~BSPCutter(void)
{
}

/*
 * Cut a polygon with a BSP tree.
 */
template<class Type, int iDimensions>
void BSPCutter<Type, iDimensions>::CutPolygon(BSPPolygon<Type, iDimensions> &bpoPolygon, BSPNode<Type, iDimensions> &bn)
{
  // if the polygon has no edges
  if (bpoPolygon.bpo_abedPolygonEdges.Count()==0) {
    // skip cutting
    return;
  }

  // if this node is inside node
  if (bn.bn_bnlLocation == BNL_INSIDE) {
    // add entire polygon to inside part
    bc_abedInside.MoveArray(bpoPolygon.bpo_abedPolygonEdges);

  // if this node is outside node
  } else if (bn.bn_bnlLocation == BNL_OUTSIDE) {
    // add entire polygon to outside part
    bc_abedOutside.MoveArray(bpoPolygon.bpo_abedPolygonEdges);

  // if this node is a branch
  } else if (bn.bn_bnlLocation == BNL_BRANCH) {
    BSPPolygon<Type, iDimensions> bpoFront;   // part of polygon in front of this splitter
    BSPPolygon<Type, iDimensions> bpoBack;    // part of polygon behind this splitter

    // split the polygon with split plane of this node
    BOOL bOnPlane = SplitPolygon(bpoPolygon, (Plane<Type, iDimensions> &)bn, bn.bn_ulPlaneTag, bpoFront, bpoBack);

    // if the polygon is not on the split plane
    if (!bOnPlane) {
      // recursively split front part with front part of bsp
      CutPolygon(bpoFront, *bn.bn_pbnFront);
      // recursively split back part with back part of bsp
      CutPolygon(bpoBack, *bn.bn_pbnBack);

    // if the polygon is on the split plane
    } else {
      BSPNode<Type, iDimensions> *pbnFront;  // front node (relative to the polygon orientation)
      BSPNode<Type, iDimensions> *pbnBack;   // back node (relative to the polygon orientation)

      // check the direction of the polygon with the front direction of the split plane
      Type tDirection = (Vector<Type, iDimensions> &)bpoPolygon%(Vector<Type, iDimensions> &)bn;
      // if the directions are same
      if (tDirection > +BSP_EPSILON) {
        // make nodes relative to polygon same as relative to the split plane
        pbnFront = bn.bn_pbnFront;
        pbnBack  = bn.bn_pbnBack;

      // if the directions are opposite
      } else if (tDirection < -BSP_EPSILON) {
        // make nodes relative to polygon opposite as relative to the split plane
        pbnFront = bn.bn_pbnBack;
        pbnBack  = bn.bn_pbnFront;
      // if the directions are indeterminate
      } else {
        // that must not be
        ASSERT(FALSE);
      }

      // cut it with front part of bsp
      BSPCutter<Type, iDimensions> bcFront(bpoPolygon, *pbnFront);
      // there must be no on-border parts
      ASSERT(bcFront.bc_abedBorderInside.Count()==0 && bcFront.bc_abedBorderOutside.Count()==0);

      // make a polygon from parts that are inside in front part of BSP
      BSPPolygon<Type, iDimensions> bpoInsideFront((Plane<Type, iDimensions> &)bpoPolygon, bcFront.bc_abedInside, bpoPolygon.bpo_ulPlaneTag);
      // cut them with back part of bsp
      BSPCutter<Type, iDimensions> bcBackInsideFront(bpoInsideFront, *pbnBack);

      // make a polygon from parts that are outside in front part of BSP
      BSPPolygon<Type, iDimensions> bpoOutsideFront((Plane<Type, iDimensions> &)bpoPolygon, bcFront.bc_abedOutside, bpoPolygon.bpo_ulPlaneTag);
      // cut them with back part of bsp
      BSPCutter<Type, iDimensions> bcBackOutsideFront(bpoOutsideFront, *pbnBack);

      // add parts that are inside both in front and back to inside part
      bc_abedInside.MoveArray(bcBackInsideFront.bc_abedInside);
      // add parts that are outside both in front and back to outside part
      bc_abedOutside.MoveArray(bcBackOutsideFront.bc_abedOutside);

      // add parts that are inside in front and outside back to on-border-inside-part
      bc_abedBorderInside.MoveArray(bcBackInsideFront.bc_abedOutside);
      // add parts that are outside in front and inside back to on-border-outside-part
      bc_abedBorderOutside.MoveArray(bcBackOutsideFront.bc_abedInside);
    }
  } else {
    ASSERTALWAYS("Bad node type");
  }
}

/*
 * Split a polygon with a plane.
 * -- returns FALSE if polygon is laying on the plane
 */
template<class Type, int iDimensions>
BOOL BSPCutter<Type, iDimensions>::SplitPolygon(BSPPolygon<Type, iDimensions> &bpoPolygon, const Plane<Type, iDimensions> &plSplitPlane, UINT64 ulPlaneTag,
  BSPPolygon<Type, iDimensions> &bpoFront, BSPPolygon<Type, iDimensions> &bpoBack)
{
  (Plane<Type, iDimensions> &)bpoFront = (Plane<Type, iDimensions> &)bpoPolygon;
  bpoFront.bpo_ulPlaneTag = bpoPolygon.bpo_ulPlaneTag;
  (Plane<Type, iDimensions> &)bpoBack = (Plane<Type, iDimensions> &)bpoPolygon;
  bpoBack.bpo_ulPlaneTag = bpoPolygon.bpo_ulPlaneTag;

  // calculate the direction of split line
  Vector<Type, iDimensions> vSplitDirection = ((Vector<Type, iDimensions> &)plSplitPlane) * (Vector<Type, iDimensions> &)bpoPolygon;

  // if the polygon is parallel with the split plane
  if (vSplitDirection.Length() < +BSP_EPSILON) {
    // calculate the distance of the polygon from the split plane
    Type fDistance = plSplitPlane.PlaneDistance(bpoPolygon);

    // if the polygon is in front of plane
    if (fDistance > +BSP_EPSILON) {
      // move all edges to front array
      bpoFront.bpo_abedPolygonEdges.MoveArray(bpoPolygon.bpo_abedPolygonEdges);
      // the polygon is not on the plane
      return FALSE;

    // if the polygon is behind the plane
    } else if (fDistance < -BSP_EPSILON) {
      // move all edges to back array
      bpoBack.bpo_abedPolygonEdges.MoveArray(bpoPolygon.bpo_abedPolygonEdges);
      // the polygon is not on the plane
      return FALSE;

    // if the polygon is on the plane
    } else {
      // just return so
      return TRUE;
    }

  // if the polygon is not parallel with the split plane
  } else {
    // initialize front and back vertex containers
    BSPVertexContainer<Type, iDimensions> bvcFront, bvcBack;
    bvcFront.Initialize(vSplitDirection);
    bvcBack.Initialize(-vSplitDirection);

    typedef BSPEdge<Type, iDimensions> edge_t; // local declaration, to fix macro expansion in FOREACHINDYNAMICARRAY
    // for each edge in polygon
    {FOREACHINDYNAMICARRAY(bpoPolygon.bpo_abedPolygonEdges, edge_t, itbed) {
      // split the edge
      SplitEdge(itbed->bed_vVertex0, itbed->bed_vVertex1, itbed->bed_ulEdgeTag, plSplitPlane,
        bpoFront, bpoBack, bvcFront, bvcBack);
    }}

    // sort vertex containers
    bvcFront.Sort();
    bvcBack.Sort();
    // elliminate paired vertices
    bvcFront.ElliminatePairedVertices();
    bvcBack.ElliminatePairedVertices();
    // create more front polygon edges from front vertex container
    bvcFront.CreateEdges(bpoFront.bpo_abedPolygonEdges, ulPlaneTag);
    // create more back polygon edges from back vertex container
    bvcBack.CreateEdges(bpoBack.bpo_abedPolygonEdges, ulPlaneTag);

    // the polygon is not on the plane
    return FALSE;
  }
}

/*
 * Split an edge with a plane.
 */
template<class Type, int iDimensions>
void BSPCutter<Type, iDimensions>::SplitEdge(const Vector<Type, iDimensions> &vPoint0, const Vector<Type, iDimensions> &vPoint1, UINT64 ulEdgeTag,
    const Plane<Type, iDimensions> &plSplitPlane,
    BSPPolygon<Type, iDimensions> &bpoFront, BSPPolygon<Type, iDimensions> &bpoBack,
    BSPVertexContainer<Type, iDimensions> &bvcFront, BSPVertexContainer<Type, iDimensions> &bvcBack)
{

  // calculate point distances from clip plane
  Type tDistance0 = plSplitPlane.PointDistance(vPoint0);
  Type tDistance1 = plSplitPlane.PointDistance(vPoint1);

  /* ---- first point behind plane ---- */
  if (tDistance0 < -BSP_EPSILON) {

    // if both are back
    if (tDistance1 < -BSP_EPSILON) {
      // add the whole edge to back node
      bpoBack.AddEdge(vPoint0, vPoint1, ulEdgeTag);
      // no split points

    // if first is back, second front
    } else if (tDistance1 > +BSP_EPSILON) {
      // calculate intersection coordinates
      Vector<Type, iDimensions> vPointMid = vPoint0-(vPoint0-vPoint1)*tDistance0/(tDistance0-tDistance1);
      // add front part to front node
      bpoFront.AddEdge(vPointMid, vPoint1, ulEdgeTag);
      // add back part to back node
      bpoBack.AddEdge(vPoint0, vPointMid, ulEdgeTag);
      // add split point to front _and_ back part of splitter
      bvcFront.AddVertex(vPointMid);
      bvcBack.AddVertex(vPointMid);

    // if first is back, second on the plane
    } else {
      // add the whole edge to back node
      bpoBack.AddEdge(vPoint0, vPoint1, ulEdgeTag);
      // add second point to back part of splitter
      bvcBack.AddVertex(vPoint1);
    }

  /* ---- first point in front of plane ---- */
  } else if (tDistance0 > +BSP_EPSILON) {
    // if first is front, second back
    if (tDistance1 < -BSP_EPSILON) {
      // calculate intersection coordinates
      Vector<Type, iDimensions> vPointMid = vPoint1-(vPoint1-vPoint0)*tDistance1/(tDistance1-tDistance0);
      // add front part to front node
      bpoFront.AddEdge(vPoint0, vPointMid, ulEdgeTag);
      // add back part to back node
      bpoBack.AddEdge(vPointMid, vPoint1, ulEdgeTag);
      // add split point to front _and_ back part of splitter
      bvcFront.AddVertex(vPointMid);
      bvcBack.AddVertex(vPointMid);


    // if both are front
    } else if (tDistance1 > +BSP_EPSILON) {
      // add the whole edge to front node
      bpoFront.AddEdge(vPoint0, vPoint1, ulEdgeTag);
      // no split points

    // if first is front, second on the plane
    } else {
      // add the whole edge to front node
      bpoFront.AddEdge(vPoint0, vPoint1, ulEdgeTag);
      // add second point to front part of splitter
      bvcFront.AddVertex(vPoint1);
    }

  /* ---- first point on the plane ---- */
  } else {
    // if first is on the plane, second back
    if (tDistance1 < -BSP_EPSILON) {
      // add the whole edge to back node
      bpoBack.AddEdge(vPoint0, vPoint1, ulEdgeTag);
      // add first point to back part of splitter
      bvcBack.AddVertex(vPoint0);

    // if first is on the plane, second in front of the plane
    } else if (tDistance1 > +BSP_EPSILON) {
      // add the whole edge to front node
      bpoFront.AddEdge(vPoint0, vPoint1, ulEdgeTag);
      // add first point to front part of splitter
      bvcFront.AddVertex(vPoint0);

    // if both are on the plane
    } else {
      // check the direction of the edge with the front direction of the splitter
      Type tDirection = (vPoint1-vPoint0)%bvcFront.bvc_vDirection;
      // if the directions are same
      if (tDirection > +BSP_EPSILON) {
        // add the whole edge to front node
        bpoFront.AddEdge(vPoint0, vPoint1, ulEdgeTag);
        // add both points to front part of the splitter
        bvcFront.AddVertex(vPoint0);
        bvcFront.AddVertex(vPoint1);


      // if the directions are opposite
      } else if (tDirection < -BSP_EPSILON) {
        // add the whole edge to back node
        bpoBack.AddEdge(vPoint0, vPoint1, ulEdgeTag);
        // add both points to back part of the splitter
        bvcBack.AddVertex(vPoint0);
        bvcBack.AddVertex(vPoint1);


      // if the directions are indeterminate
      } else {
        // that must mean that there is no edge in fact
        //ASSERT(Type(vPoint1-vPoint0) < 2*BSP_EPSILON); //!!!!
      }
    }
  }
}

/////////////////////////////////////////////////////////////////////
// BSP tree

/*
 * Default constructor.
 */
template<class Type, int iDimensions>
BSPTree<Type, iDimensions>::BSPTree(void)
{
  bt_pbnRoot = NULL;
}

/*
 * Destructor.
 */
template<class Type, int iDimensions>
BSPTree<Type, iDimensions>::~BSPTree(void)
{
  Destroy();
}

/*
 * Constructor with array of polygons oriented inwards.
 */
template<class Type, int iDimensions>
BSPTree<Type, iDimensions>::BSPTree(CDynamicArray<BSPPolygon<Type, iDimensions> > &abpoPolygons)
{
  bt_pbnRoot = NULL;
  Create(abpoPolygons);
}

/*
 * Create bsp-subtree from array of polygons oriented inwards.
 */
template<class Type, int iDimensions>
BSPNode<Type, iDimensions> *BSPTree<Type, iDimensions>::CreateSubTree(CDynamicArray<BSPPolygon<Type, iDimensions> > &abpoPolygons)
{
  // local declarations, to fix macro expansion in FOREACHINDYNAMICARRAY
  typedef BSPEdge<Type, iDimensions> edge_t;
  typedef BSPPolygon<Type, iDimensions> polygon_t;
  ASSERT(abpoPolygons.Count()>=1);

  // use first polygon as splitter
  abpoPolygons.Lock();
  BSPPolygon<Type, iDimensions> bpoSplitter = abpoPolygons[0];
  abpoPolygons.Unlock();
  // tags must be valid
  ASSERT(bpoSplitter.bpo_ulPlaneTag!=-1);

  // create two new polygon arrays - back and front
  CDynamicArray<BSPPolygon<Type, iDimensions> > abpoFront, abpoBack;

  // for each polygon in this array
  {FOREACHINDYNAMICARRAY(abpoPolygons, polygon_t, itbpo) {
    BSPPolygon<Type, iDimensions> bpoFront, bpoBack;

    // tags must be valid
    ASSERT(itbpo->bpo_ulPlaneTag!=-1);
    // if the polygon has plane tag same as the tag of the splitter
    if (itbpo->bpo_ulPlaneTag == bpoSplitter.bpo_ulPlaneTag) {
      // they are assumed coplanar, so skip it
      continue;
    }

    // split it by the plane of splitter polygon
    BOOL bOnPlane = BSPCutter<Type, iDimensions>::SplitPolygon(itbpo.Current(),
      bpoSplitter, bpoSplitter.bpo_ulPlaneTag, bpoFront, bpoBack);

    // if the polygon is not coplanar with the splitter
    if (!bOnPlane) {

      // if there are some parts that are front
      if (bpoFront.bpo_abedPolygonEdges.Count()>0) {
        // create a polygon in front array and add all inside parts to it
        BSPPolygon<Type, iDimensions> *pbpo = abpoFront.New(1);
        pbpo->bpo_abedPolygonEdges.MoveArray(bpoFront.bpo_abedPolygonEdges);
        *(Plane<Type, iDimensions> *)pbpo = itbpo.Current();
        pbpo->bpo_ulPlaneTag = itbpo->bpo_ulPlaneTag;
      }
      // if there are some parts that are back
      if (bpoBack.bpo_abedPolygonEdges.Count()>0) {
        // create a polygon in back array and add all outside parts to it
        BSPPolygon<Type, iDimensions> *pbpo = abpoBack.New(1);
        pbpo->bpo_abedPolygonEdges.MoveArray(bpoBack.bpo_abedPolygonEdges);
        *(Plane<Type, iDimensions> *)pbpo = itbpo.Current();
        pbpo->bpo_ulPlaneTag = itbpo->bpo_ulPlaneTag;
      }
    }
  }}

  // free this array (to not consume too much memory)
  abpoPolygons.Clear();

  BSPNode<Type, iDimensions> *pbnFront, *pbnBack;
  // if there is some polygon in front array
  if (abpoFront.Count()>0) {
    // create front subtree using front array
    pbnFront = CreateSubTree(abpoFront);
  // otherwise
  } else {
    // make front node an inside leaf node
    pbnFront = new BSPNode<Type, iDimensions>(BNL_INSIDE);
  }

  // if there is some polygon in back array
  if (abpoBack.Count()>0) {
    // create back subtree using back array
    pbnBack = CreateSubTree(abpoBack);
  // otherwise
  } else {
    // make back node an outside leaf node
    pbnBack = new BSPNode<Type, iDimensions>(BNL_OUTSIDE);
  }

  // make a splitter node with the front and back nodes
  return new BSPNode<Type, iDimensions>(bpoSplitter, bpoSplitter.bpo_ulPlaneTag, *pbnFront, *pbnBack);
}

/*
 * Create bsp-tree from array of polygons oriented inwards.
 */
template<class Type, int iDimensions>
void BSPTree<Type, iDimensions>::Create(CDynamicArray<BSPPolygon<Type, iDimensions> > &abpoPolygons)
{
  typedef BSPPolygon<Type, iDimensions> polygon_t; // local declaration, to fix macro expansion in FOREACHINDYNAMICARRAY

  // free eventual existing tree
  Destroy();

  // create the tree using the recursive function
  bt_pbnRoot = CreateSubTree(abpoPolygons);
  // move the tree to array
  MoveNodesToArray();
}

/*
 * Destroy bsp-tree.
 */
template<class Type, int iDimensions>
void BSPTree<Type, iDimensions>::Destroy(void)
{
  // if tree is in array
  if (bt_abnNodes.Count()>0) {
    // clear array
    bt_abnNodes.Clear();
    bt_pbnRoot = NULL;
  // if there is some free tree
  } else if (bt_pbnRoot != NULL) {
    // delete it
    bt_pbnRoot->DeleteBSPNodeRecursively();
    bt_pbnRoot = NULL;
  }
}

/* Test if a sphere could touch any of inside nodes. (Just a trivial rejection test) */
template<class Type, int iDimensions>
FLOAT BSPTree<Type, iDimensions>::TestSphere(const Vector<Type, iDimensions> &vSphereCenter, Type tSphereRadius) const
{
  if (bt_pbnRoot==NULL) return FALSE;
  // just start recursive testing at root node
  return bt_pbnRoot->TestSphere(vSphereCenter, tSphereRadius);
}
/* Test if a box is inside, outside, or intersecting. (Just a trivial rejection test) */
template<class Type, int iDimensions>
FLOAT BSPTree<Type, iDimensions>::TestBox(const OBBox<Type> &box) const
{
  if (bt_pbnRoot==NULL) return FALSE;
  // just start recursive testing at root node
  return bt_pbnRoot->TestBox(box);
}

// find minimum/maximum parameters of points on a line that are inside
template<class Type, int iDimensions>
void BSPTree<Type, iDimensions>::FindLineMinMax(
  const Vector<Type, iDimensions> &v0,
  const Vector<Type, iDimensions> &v1,
  Type &tMin,
  Type &tMax) const
{
  // init line
  BSPLine<Type, iDimensions> bl;
  bl.bl_tMin = UpperLimit(Type(0));
  bl.bl_tMax = LowerLimit(Type(0));

  // recursively split it
  bt_pbnRoot->FindLineMinMax(bl, v0, v1, Type(0), Type(1));

  // return the min/max
  tMin = bl.bl_tMin;
  tMax = bl.bl_tMax;
}

static INDEX _ctNextIndex;
/* Move one subtree to array. */
template<class Type, int iDimensions>
void BSPTree<Type, iDimensions>::MoveSubTreeToArray(BSPNode<Type, iDimensions> *pbnSubtree)
{
  // if this is no node
  if (pbnSubtree==NULL) {
    // do nothing
    return;
  }
  // first move all subnodes
  MoveSubTreeToArray(pbnSubtree->bn_pbnFront);
  MoveSubTreeToArray(pbnSubtree->bn_pbnBack);

  // get the node in array
  BSPNode<Type, iDimensions> &bnInArray = bt_abnNodes[_ctNextIndex];
  _ctNextIndex--;

  // copy properties to the array node
  (Plane<Type, iDimensions>&)bnInArray = (Plane<Type, iDimensions>&)*pbnSubtree;
  bnInArray.bn_bnlLocation = pbnSubtree->bn_bnlLocation;
  bnInArray.bn_ulPlaneTag = pbnSubtree->bn_ulPlaneTag;
  // let plane tag hold pointer to node in array
  pbnSubtree->bn_ulPlaneTag = (UINT64)&bnInArray;

  // remap pointers to subnodes
  if (pbnSubtree->bn_pbnFront==NULL) {
    bnInArray.bn_pbnFront = NULL;
  } else {
    bnInArray.bn_pbnFront = (BSPNode<Type, iDimensions>*)pbnSubtree->bn_pbnFront->bn_ulPlaneTag;
  }
  if (pbnSubtree->bn_pbnBack==NULL) {
    bnInArray.bn_pbnBack = NULL;
  } else {
    bnInArray.bn_pbnBack = (BSPNode<Type, iDimensions>*)pbnSubtree->bn_pbnBack->bn_ulPlaneTag;
  }
}

/* Count nodes in subtree. */
template<class Type, int iDimensions>
INDEX BSPTree<Type, iDimensions>::CountNodes(BSPNode<Type, iDimensions> *pbnSubtree)
{
  if (pbnSubtree==NULL) {
    return 0;
  } else {
    return 1+
      CountNodes(pbnSubtree->bn_pbnFront)+
      CountNodes(pbnSubtree->bn_pbnBack);
  }
}

/* Move all nodes to array. */
template<class Type, int iDimensions>
void BSPTree<Type, iDimensions>::MoveNodesToArray(void)
{
  // if there is no tree
  if (bt_pbnRoot == NULL) {
    // do nothing
    return;
  }

  // count nodes
  INDEX ctNodes = CountNodes(bt_pbnRoot);
  // allocate large enough array
  bt_abnNodes.New(ctNodes);
  // start at the end of array
  _ctNextIndex = ctNodes-1;
  // recusively remap all nodes
  MoveSubTreeToArray(bt_pbnRoot);

  // delete the old nodes
  bt_pbnRoot->DeleteBSPNodeRecursively();

  // first node is always at start of array
  bt_pbnRoot = &bt_abnNodes[0];
}

/* Read/write entire bsp tree to disk. */
template<class Type, int iDimensions>
void BSPTree<Type, iDimensions>::Read_t(CTStream &strm) // throw char *
{
  // free eventual existing tree
  Destroy();

  // read current version and size
  INDEX iVersion;
  SLONG slSize;
  strm>>iVersion>>slSize;
  ASSERT(iVersion==1);

  // read count of nodes and create array
  INDEX ctNodes;
  strm>>ctNodes;
#ifdef _WIN64
  // 2 pointers have now more space, tag is a pointer too
  const int actualSizeOfBSPNode =
    sizeof(enum BSPNodeLocation) +
    sizeof(BSPNode<Type, iDimensions> *) +
    sizeof(BSPNode<Type, iDimensions> *) +
    sizeof(UINT64) +
    sizeof(Plane<Type, iDimensions>);

  // size on x86, as it's stored on disk
  const int sizeOfBSPNode =
    sizeof(enum BSPNodeLocation) +
    4 +
    4 +
    sizeof(ULONG) +
    sizeof(Plane<Type, iDimensions>);

  ASSERT(slSize == (SLONG) (sizeof(INDEX) + ctNodes * sizeOfBSPNode));
#else
  ASSERT(slSize == (SLONG) (sizeof(INDEX) + ctNodes * sizeof(BSPNode<Type, iDimensions>)));
#endif
  bt_abnNodes.New(ctNodes);
  // for each node
  for(INDEX iNode=0; iNode<ctNodes; iNode++) {
    BSPNode<Type, iDimensions> &bn = bt_abnNodes[iNode];
    // read it from disk
    strm.Read_t(&(Plane<Type, iDimensions>&)bn, sizeof(Plane<Type, iDimensions>));
    strm>>(INDEX&)bn.bn_bnlLocation;

    INDEX iFront;
    strm>>iFront;
    if (iFront==-1) {
      bn.bn_pbnFront=NULL;
    } else {
      bn.bn_pbnFront = &bt_abnNodes[iFront];
    }

    INDEX iBack;
    strm>>iBack;
    if (iBack==-1) {
      bn.bn_pbnBack=NULL;
    } else {
      bn.bn_pbnBack = &bt_abnNodes[iBack];
    }

  #ifdef _WIN64
    ULONG ulPlaneTag;
    strm >> ulPlaneTag;
    bn.bn_ulPlaneTag = (UINT64) ulPlaneTag;
  #else
    strm >> bn.bn_ulPlaneTag;
  #endif
  }

  // check end id
  strm.ExpectID_t("BSPE");  // bsp end

  // first node is always at start of array
  if (bt_abnNodes.Count()>0) {
    bt_pbnRoot = &bt_abnNodes[0];
  } else {
    bt_pbnRoot = NULL;
  }
}

template<class Type, int iDimensions>
void BSPTree<Type, iDimensions>::Write_t(CTStream &strm) // throw char *
{
  INDEX ctNodes = bt_abnNodes.Count();

  // calculate size of chunk to write
#ifdef _WIN64
  // 2 pointers have now more space
  const int actualSizeOfBSPNode =
    sizeof(enum BSPNodeLocation) +
    sizeof(BSPNode<Type, iDimensions> *) +
    sizeof(BSPNode<Type, iDimensions> *) +
    sizeof(UINT64) +
    sizeof(Plane<Type, iDimensions>);

  // size on x86
  const int sizeOfBSPNode =
    sizeof(enum BSPNodeLocation) +
    4 +
    4 +
    sizeof(ULONG) + 
    sizeof(Plane<Type, iDimensions>);

  SLONG slSize = sizeof(INDEX) + ctNodes * sizeOfBSPNode;
#else
  SLONG slSize = sizeof(INDEX) + ctNodes * sizeof(BSPNode<Type, iDimensions>);
#endif
  // write current version and size
  strm<<INDEX(1)<<slSize;

  // write count of nodes
  strm<<ctNodes;
  // for each node
  for(INDEX iNode=0; iNode<ctNodes; iNode++) {
    BSPNode<Type, iDimensions> &bn = bt_abnNodes[iNode];
    // write it to disk
    strm.Write_t(&(Plane<Type, iDimensions>&)bn, sizeof(Plane<Type, iDimensions>));
    strm<<(INDEX&)bn.bn_bnlLocation;

    INDEX iFront;
    if (bn.bn_pbnFront==NULL) {
      iFront=-1;
    } else {
      iFront = bt_abnNodes.Index(bn.bn_pbnFront);
    }
    strm<<iFront;

    INDEX iBack;
    if (bn.bn_pbnBack==NULL) {
      iBack=-1;
    } else {
      iBack = bt_abnNodes.Index(bn.bn_pbnBack);
    }
    strm<<iBack;

  #ifdef _WIN64
    strm<<HashPtr64To32(bn.bn_ulPlaneTag);
  #else
    strm<<(ULONG)bn.bn_ulPlaneTag;
  #endif
  }
  // write end id for checking
  strm.WriteID_t("BSPE");  // bsp end
}

// instantiate template classes implemented here for needed types
#pragma warning (disable: 4660) // if already instantiated by some class

// remove templates
template DOUBLEbspvertex3D;
template DOUBLEbspvertexcontainer3D;
template DOUBLEbspedge3D;
template DOUBLEbspnode3D;
template DOUBLEbsppolygon3D;
template DOUBLEbsptree3D;
template DOUBLEbspcutter3D;

template FLOATbspvertex3D;
template FLOATbspvertexcontainer3D;
template FLOATbspedge3D;
template FLOATbspnode3D;
template FLOATbsppolygon3D;
template FLOATbsptree3D;
template FLOATbspcutter3D;

#pragma warning (default: 4660)
