#include "opennurbs.h"

#if !defined(ON_COMPILING_OPENNURBS)
// This check is included in all opennurbs source .c and .cpp files to insure
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined 
// and the opennurbs .h files alter what is declared and how it is declared.
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
#endif

ON_MeshNgonBuffer::ON_MeshNgonBuffer()
{
  memset(m_ngon_buffer,0,sizeof(m_ngon_buffer));
}

const class ON_MeshNgon* ON_MeshNgonBuffer::Ngon() const
{
  return (const class ON_MeshNgon*)m_ngon_buffer;
}
  
const class ON_MeshNgon* ON_MeshNgonBuffer::CreateFromMeshFaceIndex(
  const class ON_Mesh* mesh,
  unsigned int face_index
  )
{
  const class ON_MeshFace* mesh_face = ( mesh && face_index < mesh->m_F.UnsignedCount() )
                                     ? &mesh->m_F[face_index] 
                                     : 0;
  return CreateFromMeshFace(mesh_face,face_index);
}
  
const class ON_MeshNgon* ON_MeshNgonBuffer::CreateFromMeshFace(
  const class ON_MeshFace* mesh_face,
  unsigned int face_index
  )
{
  return CreateFromQuad(mesh_face ? ((const unsigned int*)mesh_face->vi) : 0, face_index );
}
  
const class ON_MeshNgon* ON_MeshNgonBuffer::CreateFromTriangle(
  const unsigned int triangle_vertex_indices[3],
  unsigned int face_index
  )
{
  if ( 0 != triangle_vertex_indices )
  {
    const unsigned int quad_vertex_indices[4] = {
      triangle_vertex_indices[0],
      triangle_vertex_indices[1],
      triangle_vertex_indices[2],
      0
    };
    return ON_MeshNgon::NgonFromMeshFace(*this,face_index,quad_vertex_indices);
  }
  return ON_MeshNgon::NgonFromMeshFace(*this,face_index,0);
}

const class ON_MeshNgon* ON_MeshNgonBuffer::CreateFromQuad(
  const unsigned int quad_vertex_indices[4],
  unsigned int face_index
  )
{
  memset(m_ngon_buffer,0,sizeof(m_ngon_buffer));
  return ON_MeshNgon::NgonFromMeshFace(*this,face_index,quad_vertex_indices);
}

ON__UINT32 ON_MeshNgon::CRC32() const
{
  ON__UINT32 crc = 0;
  if ( m_Vcount > 0 && m_vi )
    crc = ON_CRC32(crc,m_Vcount*sizeof(m_vi[0]),m_vi);  
  if ( m_Fcount > 0 && m_fi )
    crc = ON_CRC32(crc,m_Fcount*sizeof(m_fi[0]),m_fi);
  return crc;
}

ON_SHA1_Hash ON_MeshNgon::ContentHash() const
{
  ON_SHA1 sha1;
  if ( m_Vcount > 0 && m_vi )
    sha1.AccumulateBytes(m_vi,m_Vcount*sizeof(m_vi[0]));  
  if ( m_Fcount > 0 && m_fi )
    sha1.AccumulateBytes(m_fi,m_Fcount*sizeof(m_fi[0]));
  return sha1.Hash();
}

static int Internal_compare_ngon_edges(const void* a, const void* b)
{
  const unsigned int* ai = (const unsigned int*)a;
  const unsigned int* bi = (const unsigned int*)b;
  if (*ai < *bi)
    return -1;
  if (*ai > *bi)
    return 1;
  ai++;
  bi++;
  if (*ai < *bi)
    return -1;
  if (*ai > *bi)
    return 1;
  return 0;
}

unsigned int ON_MeshNgon::BoundaryEdgeCount(const ON_MeshFaceList & mesh_face_list) const
{
  if (m_Fcount <= 0 || nullptr == m_fi)
    return 0;
  const unsigned int mesh_face_count = mesh_face_list.FaceCount();
  if (mesh_face_count <= 0)
    return 0;

  const unsigned int edge_capacity = 4 * mesh_face_count;
  ON_SimpleArray<ON_2udex> edge_buffer(edge_capacity);
  edge_buffer.SetCount(edge_capacity);
  ON_2udex* edges = edge_buffer.Array();
  unsigned int edge_count = 0;
  unsigned int face_vi[4];

  ON_2udex e;
  for (unsigned int i = 0; i < m_Fcount; i++)
  {
    mesh_face_list.QuadFvi(m_fi[i], face_vi);
    e.j = face_vi[3];
    for (unsigned int j = 0; j < 4; j++)
    {
      e.i = e.j;
      e.j = face_vi[j];
      if (e.i == e.j)
        continue;
      if (e.i < e.j)
      {
        edges[edge_count] = e;
      }
      else
      {
        edges[edge_count].i = e.j;
        edges[edge_count].j = e.i;
      }
      edge_count++;
    }
  }

  ON_qsort(edges, edge_count, sizeof(edges[0]), Internal_compare_ngon_edges);
  unsigned int boundary_edge_count = 0;

  for (unsigned int i = 0; i < edge_count; /*empty iterator*/)
  {
    e = edges[i++];
    const unsigned int i1 = i;
    for (/*empty init*/; i < edge_count; i++)
    {
      if (edges[i].i != e.i || edges[i].j != e.j)
        break;
    }
    if (i1 == i)
      boundary_edge_count++;
  }

  return boundary_edge_count;
}

unsigned int ON_MeshNgon::OuterBoundaryEdgeCount() const
{
  return (m_Vcount >= 3 && nullptr != m_vi) ? m_Vcount : 0;
}

int ON_MeshNgon::Orientation(
  const ON_Mesh* mesh,
  bool bPermitHoles
) const
{
  const ON_MeshFaceList face_list(mesh);
  return Orientation(face_list, bPermitHoles);
}


static int ON_Internal_CompareNgonEdge(
  const void* lhs,
  const void* rhs
)
{
  const int i = (((const unsigned int*)lhs)[0] <= ((const unsigned int*)lhs)[1]) ? 0 : 1;
  const int j = (((const unsigned int*)rhs)[0] <= ((const unsigned int*)rhs)[1]) ? 0 : 1;
  if (((const unsigned int*)lhs)[i] < ((const unsigned int*)rhs)[j])
    return -1;
  if (((const unsigned int*)lhs)[i] > ((const unsigned int*)rhs)[j])
    return 1;
  if (((const unsigned int*)lhs)[1-i] < ((const unsigned int*)rhs)[1-j])
    return -1;
  if (((const unsigned int*)lhs)[1-i] > ((const unsigned int*)rhs)[1-j])
    return 1;
  if (((const unsigned int*)lhs)[2] < ((const unsigned int*)rhs)[2])
    return -1;
  if (((const unsigned int*)lhs)[2] > ((const unsigned int*)rhs)[2])
    return 1;
  return 0;  
}

static int ON_Internal_MeshNgon_NotOriented()
{
  // optimized away in release builds
  return 0; // nice place for a breakpoint.
}

int ON_MeshNgon::Orientation(
  const ON_MeshFaceList& mesh_face_list,
  bool bPermitHoles
) const
{
  if (m_Fcount < 1 || nullptr == m_fi)
    return ON_Internal_MeshNgon_NotOriented();

  if (m_Vcount < 3 || nullptr == m_vi)
    return ON_Internal_MeshNgon_NotOriented();
  
  const unsigned int mesh_Fcount = mesh_face_list.FaceCount();

  if (mesh_Fcount < m_Fcount)
    return ON_Internal_MeshNgon_NotOriented();

  unsigned int face_vi[4];
  if (1 == m_Fcount && m_Vcount >= 3 && m_Vcount <= 4)
  {
    // fast check for common case of a single face ngon
    mesh_face_list.QuadFvi(m_fi[0], face_vi);
    if (m_vi[0] == face_vi[0]
      && m_vi[1] == face_vi[1]
      && m_vi[2] == face_vi[2]
      && m_vi[(3 % m_Vcount)] == face_vi[3]
      )
    {
      // ngon = mesh face
      return 1;
    }
  }

  const int a_capacity = (int)(4 * m_Fcount + 2 * m_Vcount);
  
  ON_SimpleArray<ON_3udex> buffer;
  buffer.Reserve(a_capacity);
  ON_3udex* edge = buffer.Array();


  ON_3udex e;

  unsigned int edge_count = 0;

  e.k = 0; // from a face
  for (unsigned int nfi = 0; nfi < m_Fcount; nfi++)
  {
    unsigned int fi = m_fi[nfi];
    if (fi >= mesh_Fcount)
      return ON_Internal_MeshNgon_NotOriented(); // invalid ngon m_fi[] value.
    mesh_face_list.QuadFvi(fi, face_vi);
    const unsigned int fvi_count = face_vi[2] != face_vi[3] ? 4 : 3;
    e.j = face_vi[3];
    for (unsigned int fvi = 0; fvi < fvi_count; fvi++)
    {
      e.i = e.j;
      e.j = face_vi[fvi];
      if (e.i == e.j)
        return ON_Internal_MeshNgon_NotOriented(); // invalid face
      edge[edge_count++] = e;
    }
  }

  e.k = 1; // from ngon boundary
  e.j = m_vi[m_Vcount - 1];
  for (unsigned int nvi = 0; nvi < m_Vcount; nvi++)
  {
    e.i = e.j;
    e.j = m_vi[nvi];
    if (e.i == e.j)
      return ON_Internal_MeshNgon_NotOriented(); // ngon m_vi[] has duplicates
    edge[edge_count++] = e;
  }

  if (edge_count < 6
    || (false == bPermitHoles && 0 != (edge_count % 2))
    )
  {
    return ON_Internal_MeshNgon_NotOriented();
  }

  ON_qsort(edge, edge_count, sizeof(edge[0]), ON_Internal_CompareNgonEdge);
  e.i = 0;
  e.j = 0;
  e.k = 0;
  int orientation = 0;
  unsigned int interior_boundary_edge_count = 0;
  unsigned int outer_boundary_edge_count = 0;
  for (unsigned int ei = 0; ei < edge_count; ei++)
  {
    if (0 != edge[ei].k )
      return ON_Internal_MeshNgon_NotOriented(); // invalid ngon boundary.

    e.k = 0;
    if (0 == ON_Internal_CompareNgonEdge(&e, &edge[ei]))
    {
      // non-manifold or invalid
      return ON_Internal_MeshNgon_NotOriented();
    }

    if (ei + 1 >= edge_count)
    {
      // last edge;
      if (bPermitHoles)
      {
        interior_boundary_edge_count++;
        break;
      }
      return ON_Internal_MeshNgon_NotOriented();
    }

    e = edge[ei+1];
    if (0 == e.k)
    {
      // edge[ei + 1] is a face edge
      const int c = ON_Internal_CompareNgonEdge(&edge[ei], &e);
      if (c > 0)
      {
        ON_ERROR("bug in this code");
        return ON_Internal_MeshNgon_NotOriented();
      }
      if (c < 0)
      {
        // face edge with no matching edge on another face or ngon outer boundary
        if (bPermitHoles)
        {
          // assume it is interior
          interior_boundary_edge_count++;
          continue;
        }

        // face edge with no matching edge
        return ON_Internal_MeshNgon_NotOriented();
      }

      // edge[ei] and e are face edges and they should have opposite orientations.
      if (e.j != edge[ei].i || e.i != edge[ei].j)
      {
        // face set is not oriented, it's a hole and bPermitHoles=false, or other invalid situation
        return ON_Internal_MeshNgon_NotOriented();
      }
    }
    else
    {
      // edge[ei + 1] is an ngon outer boundary edge
      outer_boundary_edge_count++;
      if (1 == orientation)
      {
        // edge[ei] is a face edge, e is an ngon outer boundary edge, and they should have the same orientations
        if (e.i != edge[ei].i || e.j != edge[ei].j)
          return ON_Internal_MeshNgon_NotOriented(); // ngon boundary orientation is not consistent or face set is not oriented.
      }
      else if (-1 == orientation)
      {
        // edge[ei] is a face edge, e is an ngon outer boundary edge, and they should have opposite orientations
        if (e.j != edge[ei].i || e.i != edge[ei].j)
          return ON_Internal_MeshNgon_NotOriented(); // ngon boundary orientation is not consistent or face set is not oriented.
      }
      else
      {
        // edge[ei] is a face edge, e is an ngon outer boundary edge, 
        // and e is the first ngon ngon outer boundary edge encountered.
        if (e.i == edge[ei].i && e.j == edge[ei].j)
          orientation = 1; // this ngon edge orientation is the same as the face's edge.
        else if (e.j == edge[ei].i && e.i == edge[ei].j)
          orientation = -1; // this ngon edge orientation is opposite the face's edge.
        else
          return ON_Internal_MeshNgon_NotOriented(); // the ngon boundary edge e is not an edge of a face
      }
    }
    ei++;
  }

  if ( 0 == orientation )
    return ON_Internal_MeshNgon_NotOriented();

  if ( outer_boundary_edge_count != m_Vcount )
    return ON_Internal_MeshNgon_NotOriented();

  if ( outer_boundary_edge_count != m_Vcount )
    return ON_Internal_MeshNgon_NotOriented();

  if (interior_boundary_edge_count >= 1 && interior_boundary_edge_count <= 2)
  {
    // a valid interior bound must have at least 3 edges
    return ON_Internal_MeshNgon_NotOriented();
  }

  return orientation;
}


unsigned int ON_MeshNgon::BoundaryEdgeCount(const ON_Mesh * mesh) const
{
  const ON_MeshFaceList face_list(mesh);
  return BoundaryEdgeCount(face_list);
}

unsigned ON_Mesh::NgonBoundaryEdgeCount(unsigned int ngon_index) const
{
  const ON_MeshNgon* ngon = Ngon(ngon_index);
  return (nullptr == ngon) ? 0 : ngon->BoundaryEdgeCount(this);
}

unsigned int ON_MeshNgon::Capacity() const
{
  if ( (0 != m_vi || 0 != m_fi) )
  {
    unsigned int capacity;
    const unsigned int* a = (const unsigned int*)(this+1);
    if ( (m_vi == a+1 || (0 == m_Vcount && 0 == m_vi && m_fi == a+1)) && *a >= 7 )
    {
      capacity = (unsigned int)*a++;
      if ( 7 == capacity || 15 == capacity || 31 == capacity || (capacity >= 63 && 7 == (capacity%8) && capacity < 0x100000U) )
      {
        if ( capacity >= m_Vcount + m_Fcount 
             && ((0 == m_Fcount && 0 == m_fi) || (m_fi >= m_vi + m_Vcount && m_fi < a + capacity))
           )
          return capacity;
      }
    }
  }

  return 0;
}

int ON_MeshNgon::Compare(
    const ON_MeshNgon* A,
    const ON_MeshNgon* B
    )
{
  const unsigned int* pA;
  const unsigned int* pB;
  int c;
  if ( 0 == A )
  {
    return ( 0 == B  ) ? 0 : -1;
  }
  
  if ( 0 == B )
  {
    return 1;
  }

  if ( A->m_Vcount < B->m_Vcount )
    return -1;
  if ( A->m_Vcount > B->m_Vcount )
    return 1;
  if ( A->m_Fcount < B->m_Fcount )
    return -1;
  if ( A->m_Fcount > B->m_Fcount )
    return -1;

  pA = A->m_vi;
  pB = B->m_vi;
  if ( 0 == pA )
  {
    return ( 0 == pB  ) ? 0 : -1;
  }
  if ( 0 == pB )
  {
    return 1;
  }
  c = A->m_Vcount;
  while (c--)
  {
    if ( *pA < *pB )
      return -1;
    if ( *pA > *pB )
      return 1;
  }

  pA = A->m_fi;
  pB = B->m_fi;
  if ( 0 == pA )
  {
    return ( 0 == pB  ) ? 0 : -1;
  }
  if ( 0 == pB )
  {
    return 1;
  }
  c = A->m_Fcount;
  while (c--)
  {
    if ( *pA < *pB )
      return -1;
    if ( *pA > *pB )
      return 1;
  }

  return 0;
}

static char* ToStringHelper( const char* src_str, char* dst_str, const char* dst_str_end )
{
  if ( 0 == dst_str || dst_str_end <= dst_str )
    return 0;

  if ( 0 == src_str )
    src_str = "";
  while ( dst_str < dst_str_end )
  {
    if ( 0 == (*dst_str = *src_str++) )
      return dst_str;
    dst_str++;
  }
  return 0;
}


static char* ToStringHelper( unsigned int u, char* dst_str, const char* dst_str_end )
{
  char ubuffer[32]; // unsinged int to string storage buffer
  unsigned int i, j;

  if ( ON_UNSET_UINT_INDEX == u )
    return ToStringHelper( "unset", dst_str, dst_str_end );

  i = u;
  j = (unsigned int)(sizeof(ubuffer)/sizeof(ubuffer[0]));
  j--;
  ubuffer[j] = 0;
  while(j > 0)
  {
    j--;
    ubuffer[j] = (char)('0'+i%10);
    i /= 10;
    if ( 0 == i )
      return ToStringHelper( &ubuffer[j], dst_str, dst_str_end );
  }

  return 0;
}

static char* ToStringHelper( const unsigned int* a, unsigned int count, char* dst_str, const char* dst_str_end )
{
  unsigned int i, imax;

  dst_str = ToStringHelper("[",dst_str,dst_str_end);
  dst_str = ToStringHelper(count,dst_str,dst_str_end);
  dst_str = ToStringHelper("]=",dst_str,dst_str_end);
  if ( 0 == a )
    dst_str = ToStringHelper("null",dst_str,dst_str_end);
  else
  {
    dst_str = ToStringHelper("(",dst_str,dst_str_end);
    if ( ON_UNSET_UINT_INDEX == count )
    {
      dst_str = ToStringHelper("?",dst_str,dst_str_end);
    }
    else
    {
      imax = (count <= 7 ? count : 4 );
      for ( i = 0; i < imax; i++ )
      {
        if ( i )
          dst_str = ToStringHelper(",",dst_str,dst_str_end);
        dst_str = ToStringHelper(a[i],dst_str,dst_str_end);
      }
      if ( i < count )
      {
        dst_str = ToStringHelper(",...,",dst_str,dst_str_end);
        dst_str = ToStringHelper(a[count-1],dst_str,dst_str_end);
      }
    }
    dst_str = ToStringHelper(")",dst_str,dst_str_end);
  }

  return dst_str;
}

static char* ToStringHelper( const ON_MeshNgon* ngon, char* buffer, size_t sizeof_buffer)
{
  if ( 0 == buffer || sizeof_buffer <= 0 )
    return 0;

  char* dst_str = buffer;
  char* dst_str_end = dst_str + ((sizeof_buffer/sizeof(buffer[0])) - 1);
  unsigned int capacity;

  if ( 0 == ngon )
    return ToStringHelper("null",dst_str,dst_str_end);
  else
  {
    dst_str = ToStringHelper("m_vi",dst_str,dst_str_end);
    dst_str = ToStringHelper(ngon->m_vi,ngon->m_Vcount,dst_str,dst_str_end);
    dst_str = ToStringHelper(" ",dst_str,dst_str_end);
    dst_str = ToStringHelper("m_fi",dst_str,dst_str_end);
    dst_str = ToStringHelper(ngon->m_fi,ngon->m_Fcount,dst_str,dst_str_end);

    capacity = ngon->Capacity();
    if ( capacity > 0 )
    {
      dst_str = ToStringHelper(" capacity=",dst_str,dst_str_end);
      dst_str = ToStringHelper(capacity,dst_str,dst_str_end);
    }
  }
  return dst_str;
}

ON_String ON_MeshNgon::ToString() const
{
  // supports this == null
  char buffer[256];
  return ( 0 == ToStringHelper(this,buffer,sizeof(buffer)) ) 
         ? ON_String::EmptyString
         : ON_String(buffer);
}

ON_wString ON_MeshNgon::ToWideString() const
{
  // supports this == null
  char buffer[256];
  return ( 0 == ToStringHelper(this,buffer,sizeof(buffer)) ) 
         ? ON_wString::EmptyString
         : ON_wString(buffer);
}

void ON_MeshNgon::Dump( 
  class ON_TextLog& text_log 
  )const
{
  // supports this == null
  char buffer[256];
  if ( 0 != ToStringHelper(this,buffer,sizeof(buffer)) ) 
    text_log.Print("%s",buffer);
}

void ON_MeshNgon::AppendToString( 
  class ON_String& s
  )const
{
  // supports this == null
  char buffer[256];
  if ( 0 != ToStringHelper(this,buffer,sizeof(buffer)) ) 
    s += buffer;
}

void ON_MeshNgon::AppendToString( 
  class ON_wString& s
  )const
{
  // supports this == null
  char buffer[256];
  if ( 0 != ToStringHelper(this,buffer,sizeof(buffer)) ) 
    s += buffer;
}

unsigned int ON_Mesh::NgonUnsignedCount() const
{
  return m_Ngon.UnsignedCount();
}

int ON_Mesh::NgonCount() const
{
  return m_Ngon.Count();
}

const ON_MeshNgon* const * ON_Mesh::Ngons() const
{
  return m_Ngon.Array();
}

const ON_MeshNgon* ON_Mesh::Ngon(
  unsigned int ngon_index
  ) const
{
  return (ngon_index < m_Ngon.UnsignedCount()) ? m_Ngon[ngon_index] : 0;
}


bool ON_Mesh::IsValidNewNgonInformation(
  unsigned int Vcount, 
  const unsigned int* ngon_vi,
  unsigned int Fcount, 
  const unsigned int* ngon_fi
  ) const
{
  unsigned int i;
  const unsigned int meshVcount = m_V.UnsignedCount();
  const unsigned int meshFcount = m_F.UnsignedCount();
  if ( Vcount < 3 )
    return false;

  for ( i = 0; i < Vcount; i++ )
  {
    if ( ngon_vi[i] >= meshVcount )
      return false;
  }

  const unsigned int* ngon_map = (Fcount > 0) ? NgonMap() : 0;

  for ( i = 0; i < Fcount; i++ )
  {
    if ( ngon_fi[i] >= meshFcount )
      return false;
    if ( ngon_map && ON_UNSET_UINT_INDEX != ngon_map[ngon_fi[i]] )
      return false;
  }

  return true;
}

void ON_Mesh::RemoveNgonMap()
{
  m_NgonMap.Destroy();
}

const unsigned int* ON_Mesh::NgonMap() const
{
  if ( m_Ngon.UnsignedCount() > 0 && m_F.UnsignedCount() == m_NgonMap.UnsignedCount() )
    return m_NgonMap.Array();
  return 0;
}

unsigned int ON_Mesh::NgonIndexFromFaceIndex(
  unsigned int face_index
  ) const
{
  unsigned int ngon_index = ON_UNSET_UINT_INDEX;
  const unsigned int meshFcount = m_F.UnsignedCount();
  const unsigned int ngonCount = m_Ngon.UnsignedCount();
  if (face_index < meshFcount && ngonCount > 0)
  {
    if (meshFcount == m_NgonMap.UnsignedCount())
    {
      // fast way - use m_NgonMap[]
      ngon_index = m_NgonMap[face_index];
    }
    else
    {
      // slow way - search
      //  cannot build ngon map without making it a piece of thread safe
      //  cached information.
      for (unsigned int ni = 0; ni < ngonCount; ni++)
      {
        const ON_MeshNgon* ngon = m_Ngon[ni];
        if (0 == ngon || 0 == ngon->m_fi)
          continue;
        for (unsigned int nfi = 0; nfi < ngon->m_Fcount; nfi++)
        {
          if (face_index == ngon->m_fi[nfi])
            return ni;
        }
      }
    }
  }
  return ngon_index;
}

const unsigned int* ON_Mesh::NgonMap(
  bool bCreateIfNeeded
  )
{
  const unsigned int* fdex_to_ndex_map = NgonMap();
  if ( 0 == fdex_to_ndex_map && bCreateIfNeeded )
    fdex_to_ndex_map = CreateNgonMap();
  return fdex_to_ndex_map;
}

const unsigned int* ON_Mesh::CreateNgonMap()
{
  unsigned int* ngon_map = 0;
  const ON_MeshNgon* ngon;
  unsigned int fi;
  unsigned int ngon_index;
  unsigned int j;

  m_NgonMap.SetCount(0);

  const unsigned int ngon_count = m_Ngon.UnsignedCount();
  
  const unsigned int meshFcount = m_F.UnsignedCount();
  if  ( meshFcount <= 0 )
    return 0;

  m_NgonMap.Reserve(meshFcount);
  m_NgonMap.SetCount(meshFcount);
  ngon_map = m_NgonMap.Array();

  for ( fi = 0; fi < meshFcount; fi++ )
    ngon_map[fi] = ON_UNSET_UINT_INDEX;
    
  const ON_MeshNgon*const* ngons = m_Ngon.Array();
  for ( ngon_index = 0; ngon_index < ngon_count; ngon_index++ )
  {
    ngon = ngons[ngon_index];
    if ( 0 == ngon || 0 == ngon->m_fi )
      continue;
    for ( j = 0; j < ngon->m_Fcount; j++ )
    {
      fi = ngon->m_fi[j];
      if (fi < meshFcount)
      {
        if (ON_UNSET_UINT_INDEX == ngon_map[fi])
          ngon_map[fi] = ngon_index;
        else
        {
          ON_ERROR("mesh face referenced more than one time by an ngon.");
        }
      }
    }
  }

  return ngon_map;
}

bool ON_Mesh::ModifyNgon(
  unsigned int ngon_index,
  unsigned int Vcount, 
  const unsigned int* ngon_vi,
  unsigned int Fcount, 
  const unsigned int* ngon_fi
  )
{
  ON_MeshNgon ngon;
  ngon.m_Vcount = Vcount;
  ngon.m_vi = (unsigned int*)ngon_vi;
  ngon.m_Fcount = Fcount;
  ngon.m_fi = (unsigned int*)ngon_fi;
  bool rc = ModifyNgon(ngon_index,&ngon);
  ngon.m_vi = 0;
  ngon.m_fi = 0;
  return rc;
}

bool ON_Mesh::InsertNgon(
  unsigned int ngon_index,
  const ON_MeshNgon* ngon
  )
{
  if ( ngon_index >= ON_UNSET_UINT_INDEX )
    return false;

  if ( ngon_index >= m_Ngon.UnsignedCount() )
  {
    SetNgonCount(ngon_index+1);
  }
  else
  {
    bool bUpdateNgonMap = (m_NgonMap.UnsignedCount() == m_F.UnsignedCount());
    m_Ngon.Insert(ngon_index,0);
    if (bUpdateNgonMap)
      CreateNgonMap();
  }
  return ModifyNgon(ngon_index,ngon);
}

bool ON_Mesh::ModifyNgon(
  unsigned int ngon_index,
  const ON_MeshNgon* ngon
  )
{
  ON_MeshNgon* ngon1 = 0;
  
  if ( ngon_index >= m_Ngon.UnsignedCount() )
    return false;
  
  if ( m_Ngon[ngon_index] )
    RemoveNgon(ngon_index);

  if ( 0 == ngon )
    return true;

  if ( 0 == ngon->m_Vcount || 0 == ngon->m_Fcount )
    return true;

  if ( IsValidNewNgonInformation(ngon->m_Vcount,ngon->m_vi,ngon->m_Fcount,ngon->m_fi) )
  {
    ngon1 = m_NgonAllocator.CopyNgon(ngon);
    if ( ngon1 )
    {
      const unsigned int meshFcount = m_F.UnsignedCount();
      unsigned int* ngon_map = (meshFcount > 0 && meshFcount == m_NgonMap.UnsignedCount()) ? m_NgonMap.Array() : 0;
      if ( ngon_map )
      {
        for ( unsigned j = 0; j < ngon->m_Fcount; j++ )
        {
          unsigned int fi = ngon->m_fi[j];
          if ( fi < meshFcount )
            ngon_map[fi] = ngon_index;
        }
      }

      m_Ngon[ngon_index] = ngon1;
      return true;
    }
  }

  return false;
}

int ON_Mesh::AddNgon(
  unsigned int Vcount, 
  const unsigned int* ngon_vi,
  unsigned int Fcount, 
  const unsigned int* ngon_fi
  )
{
  unsigned int ngon_index = ON_UNSET_UINT_INDEX;
  for(;;)
  {
    if ( Vcount < 3 )
      break;
    if ( 0 == ngon_vi )
      break;
    if ( Fcount <= 0 )
      break;
    if ( 0 == ngon_fi )
      break;

    ON_MeshNgon* ngon = m_NgonAllocator.AllocateNgon(Vcount,Fcount);
    if ( 0 == ngon )
      break;
    memcpy(ngon->m_vi,ngon_vi,ngon->m_Vcount*sizeof(ngon->m_vi[0]));
    memcpy(ngon->m_fi,ngon_fi,ngon->m_Fcount*sizeof(ngon->m_fi[0]));
    ngon_index = AddNgon(ngon);
    break;
  }

  return ngon_index;
}

bool ON_Mesh::OrientNgons(bool bPermitHoles)
{
  bool rc = true;
  for (;;)
  {
    const unsigned int ngon_count = m_Ngon.UnsignedCount();
    if (0 == ngon_count)
      break;

    ON_MeshNgon** ngons = m_Ngon.Array();
    if (0 == ngons)
      break;

    const ON_MeshFaceList face_list = ON_MeshFaceList(this);
    for (unsigned int ni = 0; ni < ngon_count; ni++)
    {
      ON_MeshNgon* ngon = ngons[ni];
      if (nullptr == ngon)
        continue;
      if (0 == ngon->m_Fcount && 0 == ngon->m_Vcount)
        continue;
      const int ngon_orientation = ngon->Orientation(face_list,bPermitHoles);
      if (1 == ngon_orientation)
      {
        continue;
      }
      if (-1 == ngon_orientation)
      {
        ngon->ReverseOuterBoundary();
        continue;
      }
      rc = false; // ngon cannot be oriented
    }
    break;
  }
  return rc; 

}

void ON_MeshNgon::ReverseOuterBoundary()
{
  if (m_Vcount < 3 || nullptr == m_vi)
    return;
  unsigned int vi;
  unsigned int i0 = 1;
  unsigned i1 = m_Vcount - 1;
  while (i0 < i1)
  {
    vi = m_vi[i0];
    m_vi[i0] = m_vi[i1];
    m_vi[i1] = vi;
    i0++;
    i1--;
  }
}

void ON_Mesh::FlipNgonOrientation()
{
  const unsigned int ngon_count = m_Ngon.UnsignedCount();
  if ( 0 == ngon_count )
    return;
  ON_MeshNgon** ngons = m_Ngon.Array();
  if ( 0 == ngons )
    return;
  for (unsigned int ni = 0; ni < ngon_count; ni++)
  {
    ON_MeshNgon* ngon = ngons[ni];
    if (nullptr == ngon || ngon->m_Vcount <= 0)
      continue;
    ngon->ReverseOuterBoundary();
  }
}




bool ON_Mesh::RemoveNgon(
  unsigned int ngon_index
  )
{
  return (1==RemoveNgons(1,&ngon_index));
}

unsigned int ON_Mesh::RemoveNgons(
  unsigned int ngon_index_count,
  const unsigned int* ngon_index_list
  )
{
  if ( ngon_index_count <= 0 || 0 == ngon_index_list )
    return 0;
  const unsigned int ngon_count = m_Ngon.UnsignedCount();
  if ( 0 == ngon_count )
    return 0;
  ON_MeshNgon** ngons = m_Ngon.Array();
  if ( 0 == ngons )
    return 0;

  unsigned int removed_count = 0;
  unsigned int i, k, ngon_index, face_index;
  const unsigned int* fi;
  ON_MeshNgon* ngon;
  const unsigned int meshFcount = m_F.UnsignedCount();
  unsigned int* ngon_map = ( meshFcount == m_NgonMap.UnsignedCount() ) ? m_NgonMap.Array() : 0;
  for ( k = 0; k < ngon_index_count; k++ )
  {
    ngon_index = ngon_index_list[k];
    if (ngon_index >= ngon_count )
      continue;
    ngon = ngons[ngon_index];
    if ( 0 == ngon )
      continue;
    ngons[ngon_index] = 0;
    fi = (const unsigned int*)ngon->m_fi;
    if ( 0 != fi )
    {
      if ( ngon_map )
      {
        for ( i = 0; i < ngon->m_Fcount; i++ )
        {
          face_index = fi[i];
          if ( face_index < meshFcount && ngon_map[face_index] == ngon_index )
            ngon_map[face_index] = ON_UNSET_UINT_INDEX;
        }
      }
    }

    m_NgonAllocator.DeallocateNgon(ngon);
    removed_count++;
  }

  return removed_count;
}

ON_3dPoint ON_Mesh::NgonCenter(unsigned int ngon_index) const
{
  return NgonCenter(ngon_index < m_Ngon.UnsignedCount() ? m_Ngon[ngon_index] : 0);
}

ON_3dPoint ON_Mesh::NgonCenter(const ON_MeshNgon* ngon) const
{
  ON_3dPoint center(ON_3dPoint::UnsetPoint);
  if ( 0 != ngon && ngon->m_Vcount > 0 && 0 != ngon->m_vi )
  {
    ON_3dPointListRef mesh_vertex_list;
    mesh_vertex_list.SetFromMesh(this);
    const unsigned int meshVcount = mesh_vertex_list.PointCount();
    if ( meshVcount > 0 )
    {
      ON_3dPoint P;
      double count = 0.0;
      for ( unsigned int i = 0; i < ngon->m_Vcount; i++ )
      {
        unsigned int vi = (unsigned int)ngon->m_vi[i];
        if ( vi < meshVcount )
        {
          mesh_vertex_list.GetPoint(vi,&P.x);
          if ( 0.0 == count )
          {
            center = P;
            count = 1.0;
          }
          else
          {
            center += P;
            count += 1.0;
          }
        }
      }
      if ( count > 0.0 )
        center /= count;
    }
  }
  return center;
}

ON_BoundingBox ON_Mesh::NgonBoundaryBoundingBox(unsigned int ngon_index) const
{
  return NgonBoundaryBoundingBox(ngon_index < m_Ngon.UnsignedCount() ? m_Ngon[ngon_index] : 0);
}

ON_BoundingBox ON_Mesh::NgonBoundaryBoundingBox(const ON_MeshNgon* ngon) const
{
  ON_BoundingBox bbox;
  ON_3dPointListRef mesh_vertex_list;
  mesh_vertex_list.SetFromMesh(this);
  const unsigned int meshVcount = mesh_vertex_list.PointCount();
  if ( meshVcount > 0 )
  {
    if ( 0 != ngon && ngon->m_Vcount > 0 && 0 != ngon->m_vi )
    {
      ON_3dPoint P;
      bool bGrow = false;
      for ( unsigned int i = 0; i < ngon->m_Vcount; i++ )
      {
        unsigned int vi = ngon->m_vi[i];
        if ( vi < meshVcount )
        {
          mesh_vertex_list.GetPoint(vi,&P.x);
          if ( bbox.Set(P,bGrow) )
            bGrow = true;
        }
      }
    }
  }
  return bbox;
}

unsigned int ON_MeshNgon::GetOuterBoundaryPoints(
  const class ON_3dPointListRef& mesh_vertex_list,
  bool bAppendStartPoint,
  ON_SimpleArray<ON_3dPoint>& ngon_boundary_points
  ) const
{  
  if ( m_Vcount > 0 )
  {
    ngon_boundary_points.Reserve(bAppendStartPoint ? (m_Vcount+1) : m_Vcount);
    ngon_boundary_points.SetCount( GetOuterBoundaryPoints(mesh_vertex_list,bAppendStartPoint,ngon_boundary_points.Array()) );
  }
  else
  {
    ngon_boundary_points.SetCount(0);
  }
  return ngon_boundary_points.UnsignedCount();
}

unsigned int ON_MeshNgon::GetOuterBoundaryPoints(
  const class ON_3dPointListRef& mesh_vertex_list,
  bool bAppendStartPoint,
  ON_3dPoint* ngon_boundary_points
  ) const
{
  if ( m_Vcount <= 0 || 0 == m_vi )
    return 0;

  const unsigned int meshVcount = mesh_vertex_list.PointCount();
  if ( meshVcount <= 0 )
    return 0;

  for ( unsigned int i = 0; i < m_Vcount; i++ )
  {
    unsigned int vi = m_vi[i];
    if ( vi >= meshVcount )
      return 0;
    mesh_vertex_list.GetPoint(vi,&ngon_boundary_points[i].x);
  }
  
  if ( bAppendStartPoint )
  {
    ngon_boundary_points[m_Vcount] = ngon_boundary_points[0];
    return (m_Vcount+1);
  }

  return m_Vcount;
}

unsigned int ON_Mesh::GetNgonBoundaryPoints(
  const ON_MeshNgon* ngon,
  bool bAppendStartPoint,
  ON_SimpleArray<ON_3dPoint>& ngon_boundary_points
  ) const
{
  if ( ngon )
  {
    ON_3dPointListRef mesh_vertex_list;
    mesh_vertex_list.SetFromMesh(this);
    return ngon->GetOuterBoundaryPoints(mesh_vertex_list,bAppendStartPoint,ngon_boundary_points);
  }
  ngon_boundary_points.SetCount(0);
  return 0;
}

unsigned int ON_Mesh::GetNgonBoundaryPoints(
  const ON_MeshNgon* ngon,
  bool bAppendStartPoint,
  ON_3dPoint* ngon_boundary_points
  ) const
{
  if ( ngon )
  {
    ON_3dPointListRef mesh_vertex_list;
    mesh_vertex_list.SetFromMesh(this);
    return ngon->GetOuterBoundaryPoints(mesh_vertex_list,bAppendStartPoint,ngon_boundary_points);
  }
  return 0;
}

void ON_Mesh::SetNgonCount(
  unsigned int ngon_count
  )
{
  if ( ngon_count <= 0 )
  {
    m_NgonMap.Destroy();
    m_Ngon.Destroy();
    m_NgonAllocator.DeallocateAllNgons();
    return;
  }

  unsigned int ngon_count0 = m_Ngon.UnsignedCount();
  ON_MeshNgon** a;
  
  if ( ngon_count > ngon_count0 )
  {
    m_Ngon.Reserve(ngon_count);
    m_Ngon.SetCount(ngon_count);
    a = m_Ngon.Array();
    if ( a )
    {
      memset(a+ngon_count0,0,(ngon_count-ngon_count0)*sizeof(a[0]));
    }
    return;
  }
  
  if ( ngon_count < ngon_count0 )
  {
    bool bUpdateNgonMap = (m_NgonMap.UnsignedCount() == ngon_count0);
    a = m_Ngon.Array();
    if ( bUpdateNgonMap && 2*ngon_count >=  ngon_count0 )
    {
      // less expensive to clean up map array
      while ( --ngon_count0 >= ngon_count )
      {
        if ( a[ngon_count0] )
          RemoveNgon(ngon_count0);
      }
    }
    else
    {
      // less expensive to reacalculate map array
      while ( --ngon_count0 >= ngon_count )
      {
        if ( a[ngon_count0] )
        {

          m_NgonAllocator.DeallocateNgon(a[ngon_count0]);
          a[ngon_count0] = 0;
        }
      }
      m_Ngon.SetCount(ngon_count);
      if ( bUpdateNgonMap )
        CreateNgonMap();
      else
        m_NgonMap.SetCount(0);
    }

    return;
  }

  return;
}

void ON_Mesh::RemoveEmptyNgons()
{
  ON_MeshNgon* ngon;
  ON_MeshNgon** ngons = m_Ngon.Array();
  const unsigned int ngon_count0 = m_Ngon.UnsignedCount();
  unsigned int ngon_count1 = 0;
  unsigned int i;

  for ( i = 0; i < ngon_count0; i++ )
  {
    ngon = ngons[i];
    if ( 0 == ngon )
      continue;
    if ( 0 != ngon && ngon->m_Vcount > 0 && 0 != ngon->m_vi && ngon->m_Fcount > 0 && 0 != ngon->m_fi)
    {
      if (i > ngon_count1 )
        ngons[ngon_count1] = ngons[i];
      ngon_count1++;
    }
    else
    {
      ngons[i] = 0;
      m_NgonAllocator.DeallocateNgon(ngon);
    }
  }

  if ( ngon_count1 < ngon_count0 )
  {
    m_Ngon.SetCount(ngon_count1);
    if ( ngon_count0 == m_NgonMap.UnsignedCount() )
      CreateNgonMap();
    else
      m_NgonMap.SetCount(0);
  }

  return;
}

void ON_Mesh::RemoveAllNgons()
{
  SetNgonCount(0);
}

ON_MeshNgon* ON_Mesh::AllocateNgon(
  unsigned int Vcount, 
  unsigned int Fcount
  )
{
  ON_MeshNgon* ngon = m_NgonAllocator.AllocateNgon(Vcount,Fcount);
  if ( ngon  )
  {
    if ( ngon->m_Vcount > 0 )
      memset(ngon->m_vi,0xFF,ngon->m_Vcount*sizeof(ngon->m_vi[0]));
    if ( ngon->m_Fcount > 0 )
      memset(ngon->m_fi,0xFF,ngon->m_Fcount*sizeof(ngon->m_fi[0]));
  }
  return ngon;
}

bool ON_Mesh::DeallocateNgon(
  ON_MeshNgon* ngon
  )
{
  return m_NgonAllocator.DeallocateNgon(ngon);
}

unsigned int ON_Mesh::AddNgon(
  ON_MeshNgon* ngon
  )
{
  int ngon_index = ON_UNSET_UINT_INDEX;

  if ( ngon )
  {
    ngon_index = m_Ngon.Count();
    const unsigned int meshFcount = m_F.UnsignedCount();
    if ( meshFcount <= 0 && ngon->m_Fcount > 0 )
      return ON_UNSET_UINT_INDEX;

    if ( meshFcount > 0 )
    {
      unsigned int fi;
      unsigned int* ngon_map;
      if ( 0 == ngon_index )
      {
        m_NgonMap.Reserve(meshFcount);
        m_NgonMap.SetCount(meshFcount);
        ngon_map = m_NgonMap.Array();
        if ( 0 != ngon_map )
          memset(ngon_map,0xFF,m_NgonMap.SizeOfArray());
      }
      else if ( meshFcount == m_NgonMap.UnsignedCount() )
        ngon_map = m_NgonMap.Array();
      else
        ngon_map = 0;

      if ( 0 != ngon_map )
      {
        for ( unsigned int i = 0; i < ngon->m_Fcount; i++ )
        {
          fi = ngon->m_fi[i];
          if ( fi >= meshFcount )
          {
            // clean up ngon_map
            for ( i = 0; i < ngon->m_Fcount; i++ )
            {
              fi = ngon->m_fi[i];
              if ( fi < meshFcount )
                ngon_map[fi] = ON_UNSET_UINT_INDEX;
            }
            return ON_UNSET_UINT_INDEX;
          }
          ngon_map[fi] = ngon_index;
        }
      }
      else
        m_NgonMap.SetCount(0);
    }
    m_Ngon.Append(ngon);
  }
  return ngon_index;
}



struct ON_MeshNgonLink
{
  struct ON_MeshNgonLink* next;
  struct ON_MeshNgonLink* prev;
};

static size_t SizeofNgon(unsigned int capacity)
{
  return sizeof(ON_MeshNgon) + (capacity+1)*sizeof(unsigned int);
}

ON_MeshNgonAllocator::ON_MeshNgonAllocator() ON_NOEXCEPT
  : m_31(0)
  , m_63(0)
  , m_active(0)
{}

ON_MeshNgonAllocator::~ON_MeshNgonAllocator()
{
  void DeallocateAllNgons();
}

ON_MeshNgon* ON_MeshNgonAllocator::CopyNgon(
    const ON_MeshNgon* ngon
    )
{
  ON_MeshNgon* ngon_copy = AllocateNgon(ngon->m_vi ? ngon->m_Vcount : 0,ngon->m_fi ? ngon->m_Fcount : 0);
  if ( ngon_copy )
  {
    if ( ngon_copy->m_Vcount > 0 )
      memcpy(ngon_copy->m_vi,ngon->m_vi,ngon_copy->m_Vcount*sizeof(ngon_copy->m_vi[0]));
    if ( ngon_copy->m_Fcount > 0 )
      memcpy(ngon_copy->m_fi,ngon->m_fi,ngon_copy->m_Fcount*sizeof(ngon_copy->m_fi[0]));
  }
  return ngon_copy;
}

ON_MeshNgon* ON_MeshNgonAllocator::AllocateNgon(
  unsigned int Vcount,
  unsigned int Fcount
  )
{
  struct ON_MeshNgonLink* link;
  ON_MeshNgon* ngon;
  unsigned int* a;
  unsigned int capacity;

  if ( Vcount < 3  )
    return 0;

  capacity = Vcount+Fcount;
  if ( capacity <= 7 )
  {
    capacity = 7;
    if ( SizeofNgon(capacity) != m_7.SizeofElement() )
      m_7.Create(SizeofNgon(capacity),0,0);
    ngon = (ON_MeshNgon*)m_7.AllocateElement();
  }
  else if ( capacity <= 15 )
  {
    capacity = 15;
    if ( SizeofNgon(capacity) != m_15.SizeofElement() )
      m_15.Create(SizeofNgon(capacity),0,0);
    ngon = (ON_MeshNgon*)m_15.AllocateElement();
  }
  else 
  {
    for(;;)
    {
      link = 0;
      if ( capacity <= 31 )
      {
        capacity = 31;
        if ( 0 != m_31 )
        {
          link = (struct ON_MeshNgonLink*)m_31;
          m_31 = link->next;
          break;
        }
      }
      else if ( capacity <= 63 )
      {
        capacity = 63;
        if ( 0 != m_63 )
        {
          link = (struct ON_MeshNgonLink*)m_63;
          m_63 = link->next;
          break;
        }
      }
      else
      {
        // make capacity % 8 = 7
        unsigned int n = (capacity % 8);
        capacity += (7-n);
      }
      link = (struct ON_MeshNgonLink*)onmalloc(sizeof(*link) + SizeofNgon(capacity));
      break;
    }
    if ( 0 == link )
      return 0;
    ngon = (ON_MeshNgon*)(link+1);
    link->next = (struct ON_MeshNgonLink*)m_active;
    if ( link->next )
      link->next->prev = link;
    link->prev = 0;
    m_active = link;
  }

  if ( 0 != ngon )
  {
    a = (unsigned int*)(ngon+1);
    *a++ = capacity;
    ngon->m_vi = a;
    ngon->m_fi = (Fcount > 0) ? (ngon->m_vi + Vcount) : 0;
    ngon->m_Vcount = Vcount;
    ngon->m_Fcount = Fcount;
    //memset(a,0xFF,capacity*sizeof(a[0])); // almost always a waste of time - user must init
  }

  return ngon;
}

ON_MeshNgon* ON_MeshNgonAllocator::ReallocateNgon(
  ON_MeshNgon* ngon,
  unsigned int Vcount,
  unsigned int Fcount
  )
{
  if ( 0 == Vcount && 0 == Fcount )
  {
    DeallocateNgon(ngon);
    return 0;
  }
  
  if ( Vcount < 3 )
    return 0;

  if ( 0 == ngon )
    return AllocateNgon(Vcount,Fcount);

  if ( Vcount <= ngon->m_Vcount && Fcount <= ngon->m_Fcount )
  {
    ngon->m_Vcount = Vcount;
    ngon->m_Fcount = Fcount;
    return ngon;
  }

  unsigned int capacity = ngon->Capacity();
  ON_MeshNgon* ngon1 = ( Vcount + Fcount <= capacity ) ? ngon : AllocateNgon(Vcount,Fcount);
  if ( 0 == ngon1 )
    return 0;

  unsigned int* vi = ngon1->m_vi;
  unsigned int* fi = (Fcount > 0) ? (vi + Vcount) : 0;
  unsigned int i, c;

  if ( 0 != fi && (0 == ngon->m_Fcount || 0 != ngon->m_fi) )
  {
    c = Fcount < ngon->m_Fcount ? Fcount : ngon->m_Fcount;
    for ( i = 0; i < c; i++ )
      fi[i] = ngon->m_fi[i];
    for ( i = c; i < Fcount; i++ )
      fi[i] = ON_UNSET_UINT_INDEX;
  }

  if ( 0 != vi && ( 0 == ngon->m_Vcount || 0 != ngon->m_vi) )
  {
    c = Vcount < ngon->m_Vcount ? Vcount : ngon->m_Vcount;
    for ( i = 0; i < c; i++ )
      vi[i] = ngon->m_vi[i];
    for ( i = c; i < Vcount; i++ )
      vi[i] = ON_UNSET_UINT_INDEX;
  }

  ngon1->m_Vcount = Vcount;
  ngon1->m_Fcount = Fcount;
  ngon1->m_vi = vi;
  ngon1->m_fi = fi;

  if (ngon1 != ngon )
    DeallocateNgon(ngon);

  return ngon1;
}


bool ON_MeshNgonAllocator::DeallocateNgon(
  ON_MeshNgon* ngon
  )
{
  struct ON_MeshNgonLink* link;
  unsigned int capacity = ngon ? ngon->Capacity() : 0;
  
  if ( 7 == capacity )
  {
    m_7.ReturnElement(ngon);
    return true;
  }
  
  if ( 15 == capacity )
  {
    m_15.ReturnElement(ngon);
    return true;
  }
  
  if ( 31 == capacity || capacity >= 63 )
  {
    link = ((struct ON_MeshNgonLink*)ngon)-1;

    // remove from active list
    if ( link == m_active )
    {
      if ( link->prev )
        return false;
      m_active = link->next;
    }
    else if ( link->prev )
      link->prev->next = link->next;
    else
      return false;
    if ( link->next )
      link->next->prev = link->prev;

    switch(capacity)
    {
    case 31:
      link->prev = 0;
      link->next = (struct ON_MeshNgonLink*)m_31;
      m_31 = link;     
      break;
    case 63:
      link->prev = 0;
      link->next = (struct ON_MeshNgonLink*)m_63;
      m_63 = link;   
      break;
    default:
      // deallocate heap
      onfree(link);
    }

    return true;
  }

  return false;
}


void ON_MeshNgonAllocator::DeallocateAllNgons()
{
  struct ON_MeshNgonLink* link;
  struct ON_MeshNgonLink* next;
  struct ON_MeshNgonLink* links[3] = {(struct ON_MeshNgonLink*)m_31,(struct ON_MeshNgonLink*)m_63,(struct ON_MeshNgonLink*)m_active};
  m_7.Destroy();
  m_15.Destroy();
  m_31 = 0;
  m_63 = 0;
  m_active = 0;
  for ( int i = 0; i < sizeof(links)/sizeof(links[0]); i++ )
  {
    next = links[i];
    while (next)
    {
      link = next;
      next = next->next;
      onfree(link);
    }
  }
}


#if defined(ON_HAS_RVALUEREF)
ON_MeshNgonAllocator::ON_MeshNgonAllocator(ON_MeshNgonAllocator&& src)
  : m_31(src.m_31)
  , m_63(src.m_63)
  , m_active(src.m_active)
{
  src.m_31 = 0;
  src.m_63 = 0;
  src.m_active = 0;
}

ON_MeshNgonAllocator& ON_MeshNgonAllocator::operator=(ON_MeshNgonAllocator&& src)
{
  if ( this != &src )
  {
    DeallocateAllNgons();
    m_31 = src.m_31;
    m_63 = src.m_63;
    m_active = src.m_active;
    src.m_31 = 0;
    src.m_63 = 0;
    src.m_active = 0;
  }
  return *this;
}

#endif


// DO NOT COPY THIS DEFINE
#define ON_NGON_BOZO_VACCINE

// Do not copy or move the definition of
// the ON_NGON_MEMBLK structure.
struct ON_NGON_MEMBLK
{
#if !defined(ON_NGON_BOZO_VACCINE)
#error You are a bozo!  Read the comments.
#endif
  struct ON_NGON_MEMBLK* next;
};


class ON_V4V5_MeshNgon
{
public:
  // Number of N-gon corners (N >= 3)
  int N;

  // N-gon vertex indices
  // An array of N indices into the mesh's m_V[] vertex array.
  // If the ON_MeshNgon is returned by the ON_V4V5_MeshNgonList::AddNgon()
  // function, then the memory for vi is managed by the ON_V4V5_MeshNgonList
  // class.  
  int* vi;

  // N-gon face indices
  // An array of Fcount indices into the mesh's m_F[] face array.
  // Often, only N-2 indices are used. Unused indices are set to -1.
  // If the ON_MeshNgon is returned by the ON_V4V5_MeshNgonList::AddNgon()
  // function, then the memory for fi is managed by the ON_V4V5_MeshNgonList
  // class.  
  int* fi;
};

class ON_V4V5_MeshNgonList
{
public:
  ON_V4V5_MeshNgonList();
  ~ON_V4V5_MeshNgonList();
  ON_V4V5_MeshNgonList(const ON_V4V5_MeshNgonList&);
  ON_V4V5_MeshNgonList& operator=(const ON_V4V5_MeshNgonList&);


  /*
  Description:
    Add an N-gon to the list
  Parameters:
    N - [in] number of vertices ( >= 5)
    vi - [in] array of N vertex indices into the mesh's m_V[] array.
    fi - [in] array of N face indices into the mesh's m_F[] array.
              Unused indices are set to -1.  In many cases
              there are N-2 valid indices and these are triangles.
  Remarks:
    Adding an N-gon may invalidate any pointers previously
    returned by Ngon.
  */
  bool V4V5_AddNgon(int N, const int* vi, const int* fi);
  class ON_V4V5_MeshNgon* V4V5_AddNgon(int N);

  /*
  Returns:
    Number of Ngons
  */
  int V4V5_NgonCount() const;

  /*
  Parameters:
    Ngon_index - [in] zero based index
  Returns:
    nullptr or a pointer to the Ngon
  */
  ON_V4V5_MeshNgon* V4V5_Ngon(int Ngon_index) const;

  /*
  Description:
    If you know about how many ngons you will need,
    then use the function to reserve space for them.
  */
  bool V4V5_ReserveNgonCapacity(int capacity);

  /*
  Description:
    Destroy N-gon list
  */
  void V4V5_Destroy();

  /*
  Returns:
    Approximate number of bytes used by this class.
  */
  unsigned int V4V5_SizeOf() const;

private:
  int m_ngons_count;
  int m_ngons_capacity;
  ON_V4V5_MeshNgon* m_ngons;
  struct ON_NGON_MEMBLK* m_memblk_list;
};

ON_V4V5_MeshNgonList::ON_V4V5_MeshNgonList()
{
  m_ngons_count = 0;
  m_ngons_capacity = 0;
  m_ngons = 0;
  m_memblk_list = 0; 
}

ON_V4V5_MeshNgonList::~ON_V4V5_MeshNgonList()
{
  V4V5_Destroy();
}

void ON_V4V5_MeshNgonList::V4V5_Destroy()
{
  m_ngons_count = 0;
  m_ngons_capacity = 0;
  if ( 0 != m_ngons )
  {
    onfree(m_ngons);
    m_ngons = 0;
  }
  struct ON_NGON_MEMBLK* p = m_memblk_list;
  m_memblk_list = 0;
  while(p)
  {
    struct ON_NGON_MEMBLK* next = p->next;
    onfree(p);
    p = next;
  }
}


ON_V4V5_MeshNgonList::ON_V4V5_MeshNgonList(const ON_V4V5_MeshNgonList& src)
{
  m_ngons_count = 0;
  m_ngons_capacity = 0;
  m_ngons = 0;
  m_memblk_list = 0; 
  if ( src.m_ngons_count > 0 && 0 != src.m_ngons )
  {
    *this = src;
  }
}


ON_V4V5_MeshNgonList& ON_V4V5_MeshNgonList::operator=(const ON_V4V5_MeshNgonList& src)
{
  if ( this != &src )
  {
    V4V5_Destroy();
    V4V5_ReserveNgonCapacity(src.m_ngons_count);
    for ( int i = 0; i < src.m_ngons_count; i++ )
    {
      const ON_V4V5_MeshNgon& ngon = src.m_ngons[i];
      V4V5_AddNgon(ngon.N,ngon.vi,ngon.fi);
    }
  }
  return *this;
}


bool ON_V4V5_MeshNgonList::V4V5_ReserveNgonCapacity(int capacity)
{
  bool rc = true;
  if ( capacity > m_ngons_capacity )
  {
    m_ngons = (ON_V4V5_MeshNgon*)onrealloc(m_ngons,capacity*sizeof(m_ngons[0]));
    if ( 0 == m_ngons )
    {
      m_ngons_capacity = 0;
      m_ngons_count = 0;
      rc = false;
    }
    else
    {
      m_ngons_capacity = capacity;
    }
  }
  return rc;
}

class ON_V4V5_MeshNgon* ON_V4V5_MeshNgonList::V4V5_AddNgon(int N)
{
  if ( N < 3 || N > 100000 )
    return 0;

  if ( m_ngons_count >= m_ngons_capacity )
  {
    int capacity = 2*m_ngons_count;
    if (capacity < m_ngons_count+16)
      capacity = m_ngons_count+16;
    if ( !V4V5_ReserveNgonCapacity(capacity) )
      return 0;
  }
  ON_V4V5_MeshNgon& ngon = m_ngons[m_ngons_count++];

  ngon.N = N;
  struct ON_NGON_MEMBLK* blk = (struct ON_NGON_MEMBLK*)onmalloc(sizeof(*blk) + (2*N)*sizeof(int));
  if ( 0 == blk )
    return 0;
  ngon.vi = (int*)(blk + 1);
  ngon.fi = ngon.vi + N;
  memset(ngon.vi,0xFF,(2*N)*sizeof(int)); // set all indicies to -1
  blk->next = m_memblk_list;
  m_memblk_list = blk;
  return &ngon;
}

bool ON_V4V5_MeshNgonList::V4V5_AddNgon(int N, const int* vi, const int* fi)
{
  if ( 0 == vi || 0 == fi )
    return false;
  class ON_V4V5_MeshNgon* ngon = V4V5_AddNgon(N);
  if ( 0 == ngon )
    return false;
  memcpy(ngon->vi,vi,N*sizeof(ngon->vi[0]));
  memcpy(ngon->fi,fi,N*sizeof(ngon->fi[0]));
  return true;
}

int ON_V4V5_MeshNgonList::V4V5_NgonCount() const
{
  return m_ngons_count;
}

ON_V4V5_MeshNgon* ON_V4V5_MeshNgonList::V4V5_Ngon(int Ngon_index) const
{
  return (Ngon_index < 0 || Ngon_index >= m_ngons_count) ? 0 : m_ngons+Ngon_index;
}


class /* DO NOT EXPORT THIS CLASS */ ON_V4V5_MeshNgonUserData : public ON_UserData
{
#if !defined(ON_NGON_BOZO_VACCINE)
#error You are a bozo!  Read the comments.
#endif
  ON_OBJECT_DECLARE(ON_V4V5_MeshNgonUserData);

public:
  ON_V4V5_MeshNgonUserData();
  ~ON_V4V5_MeshNgonUserData();
  ON_V4V5_MeshNgonUserData(const ON_V4V5_MeshNgonUserData&);
  ON_V4V5_MeshNgonUserData& operator=(const ON_V4V5_MeshNgonUserData&);

  // vitual ON_UserData override
  bool IsValid( class ON_TextLog* text_log = nullptr ) const override;
  unsigned int SizeOf() const override;
  bool Write(ON_BinaryArchive&) const override;
  bool Read(ON_BinaryArchive&) override;

  // vitual ON_UserData override
  bool GetDescription( ON_wString& ) override;
  bool Archive() const override;

public:
  ON_V4V5_MeshNgonList* m_ngon_list;

  // used to validate ngon list.
  // If the information here does not match the
  // information in the mesh, then the ngon list
  // is known to be invalid.
  int m_mesh_F_count;
  int m_mesh_V_count;
};

ON_OBJECT_IMPLEMENT(ON_V4V5_MeshNgonUserData,ON_UserData,"31F55AA3-71FB-49f5-A975-757584D937FF");

ON_V4V5_MeshNgonUserData::ON_V4V5_MeshNgonUserData()
{
  m_userdata_uuid = ON_CLASS_ID(ON_V4V5_MeshNgonUserData);
  m_application_uuid = ON_opennurbs4_id;
  m_userdata_copycount = 1;
  m_ngon_list = 0;
  m_mesh_F_count = 0;
  m_mesh_V_count = 0;
}

ON_V4V5_MeshNgonUserData::~ON_V4V5_MeshNgonUserData()
{
  if ( 0 != m_ngon_list )
  {
    delete m_ngon_list;
    m_ngon_list = 0;
  }
}

ON_V4V5_MeshNgonUserData::ON_V4V5_MeshNgonUserData(const ON_V4V5_MeshNgonUserData& src) 
: ON_UserData(src)
, m_mesh_F_count(src.m_mesh_F_count)
, m_mesh_V_count(src.m_mesh_V_count)
{
  m_ngon_list = (0 != src.m_ngon_list) 
              ? new ON_V4V5_MeshNgonList(*src.m_ngon_list)
              : 0;
}

ON_V4V5_MeshNgonUserData& ON_V4V5_MeshNgonUserData::operator=(const ON_V4V5_MeshNgonUserData& src)
{
  if ( this != &src )
  {
    if (0 != m_ngon_list )
    {
      delete m_ngon_list;
      m_ngon_list = 0;
    }
    ON_UserData::operator=(src);
    if (0 != src.m_ngon_list) 
    {
       m_ngon_list = new ON_V4V5_MeshNgonList(*src.m_ngon_list);
    }
    m_mesh_F_count = src.m_mesh_F_count;
    m_mesh_V_count = src.m_mesh_V_count;
  }
  return *this;
}

bool ON_V4V5_MeshNgonUserData::IsValid( ON_TextLog* text_log ) const
{
  return true;
}

unsigned int ON_V4V5_MeshNgonList::V4V5_SizeOf() const
{
  unsigned int sz = sizeof(*this);
  int icount = 0;
  for ( int i = 0; i < m_ngons_count; i++ )
  {
    icount += 2*m_ngons[i].N;
  }
  sz += m_ngons_capacity*sizeof(m_ngons[0]);
  sz += icount*sizeof(int);
  return sz;
}

unsigned int ON_V4V5_MeshNgonUserData::SizeOf() const
{
  unsigned int sz = ON_UserData::SizeOf();
  if ( 0 != m_ngon_list )
    sz += m_ngon_list->V4V5_SizeOf();
  return sz;
}


bool ON_V4V5_MeshNgonUserData::Write(ON_BinaryArchive& archive) const
{
  bool rc = archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,1);
  if (!rc)
    return false;
  for (;;)
  {
    int count = ( 0 == m_ngon_list ) ? 0 : m_ngon_list->V4V5_NgonCount();
    const ON_V4V5_MeshNgon* ngon_array = (count > 0) ? m_ngon_list->V4V5_Ngon(0) : 0;
    if ( 0 == ngon_array )
      count = 0;
    rc = archive.WriteInt(count);
    if (count <= 0 || !rc)
      break;
    for ( int i = 0; i < count; i++ )
    {
      const class ON_V4V5_MeshNgon& ngon = ngon_array[i];

      rc = archive.WriteInt(ngon.N);
      if (!rc)
        break;
      rc = archive.WriteInt(ngon.N,ngon.vi);
      if (!rc)
        break;
      rc = archive.WriteInt(ngon.N,ngon.fi);
      if (!rc)
        break;
    }
    if (!rc)
      break;

    // chunk version 1.1 added face and vertex validation counts.
    rc = archive.WriteInt(m_mesh_F_count);
    if (!rc)
      break;
    rc = archive.WriteInt(m_mesh_V_count);
    if (!rc)
      break;

    break;
  }
  if ( !archive.EndWrite3dmChunk() )
    rc = false;
  return rc;
}

bool ON_V4V5_MeshNgonUserData::Read(ON_BinaryArchive& archive)
{
  if ( 0 != m_ngon_list )
  {
    delete m_ngon_list;
    m_ngon_list = 0;
  }
  int major_version = 0;
  int minor_version = 0;
  bool rc = archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version);
  if (!rc)
    return false;
  for (;;)
  {
    rc = (1 == major_version);
    if (!rc)
      break;
    int count = 0;
    rc = archive.ReadInt(&count);
    if (count <= 0 || !rc)
      break;

    m_ngon_list = new ON_V4V5_MeshNgonList();
    if ( 0 == m_ngon_list )
      break;

    m_ngon_list->V4V5_ReserveNgonCapacity(count);

    for ( int i = 0; i < count; i++ )
    {
      int N = 0;
      rc = archive.ReadInt(&N);
      if (!rc)
        break;
      if ( N <= 0 )
        continue;
      class ON_V4V5_MeshNgon* ngon = m_ngon_list->V4V5_AddNgon(N);
      if ( 0 == ngon )
        break;

      rc = archive.ReadInt(N,ngon->vi);
      if (!rc)
        break;
      rc = archive.ReadInt(N,ngon->fi);
      if (!rc)
        break;
      ngon->N = N;
    }
    if (!rc)
      break;

    if ( minor_version >= 1 )
    {
      // chunk version 1.1 added face and vertex validation counts.
      rc = archive.ReadInt(&m_mesh_F_count);
      if (!rc)
        break;
      rc = archive.ReadInt(&m_mesh_V_count);
      if (!rc)
        break;
    }

    break;
  }
  if ( !archive.EndRead3dmChunk() )
    rc = false;
  return rc;
}

// vitual ON_UserData override
bool ON_V4V5_MeshNgonUserData::GetDescription( ON_wString& description )
{
  description = L"Mesh N-gon list";
  return true;
}

bool ON_V4V5_MeshNgonUserData::Archive() const
{
  return ( 0 != m_ngon_list && m_ngon_list->V4V5_NgonCount() > 0 );
}

static
bool ON_ValidateNgon(
  const ON_V4V5_MeshNgon* ngon,
  int mesh_V_count,
  int mesh_F_count
  )
{
  unsigned int j;
  if ( 0 == ngon || ngon->N < 0 )
    return false;
  const unsigned int N = ngon->N;
  for ( j = 0; j < N; j++ )
  {
    if ( ngon->vi[j] < 0 || ngon->vi[j] >= mesh_V_count )
      return false;
    if ( ngon->fi[j] < 0 || ngon->fi[j] >= mesh_F_count )
    {
      if ( -1 == ngon->fi[j] )
      {
        for ( j++; j < N; j++ )
        {
          if ( ngon->vi[j] < 0 || ngon->vi[j] >= mesh_V_count )
            return false;
          if ( -1 != ngon->fi[j] )
            return false;
        }
        break;
      }
      return false;
    }
  }
  return true;
}

static 
bool ON_ValidateMeshNgonUserData(
  ON_V4V5_MeshNgonUserData* ngud,
  const ON_Mesh& mesh
  )
{
  int i;
  if ( 0 == ngud || 0 == ngud->m_ngon_list )
    return false;

  const int mesh_V_count = mesh.m_V.Count();
  const int mesh_F_count = mesh.m_F.Count();

  if ( 0 == ngud->m_mesh_V_count && 0 == ngud->m_mesh_F_count )
  {
    // This is old user data that did not have validation counts
    // saved in the file.

    // Set validation counts to -1 so we never do this slow validation again.
    ngud->m_mesh_V_count = -1;
    ngud->m_mesh_F_count = -1;

    const int ngon_count = ngud->m_ngon_list->V4V5_NgonCount();

    for ( i = 0; i < ngon_count; i++ )
    {
      if ( !ON_ValidateNgon(ngud->m_ngon_list->V4V5_Ngon(i),mesh_V_count,mesh_F_count) )
        return false;
    }

    // Set validation counts to proper values because we will 
    // assume this old ngon information is valid since the indices
    // are in range.  This assumption may not be valid, but
    // at least the ngon won't cause a crash.
    ngud->m_mesh_V_count = mesh_V_count;
    ngud->m_mesh_F_count = mesh_F_count;
  }

  return ( ngud->m_mesh_F_count == mesh_F_count && ngud->m_mesh_V_count == mesh_V_count );
}
                         

const class ON_V4V5_MeshNgonList* ON_Mesh::V4V5_NgonList() const
{
  ON_UserData* ud = GetUserData(ON_CLASS_ID(ON_V4V5_MeshNgonUserData));
  ON_V4V5_MeshNgonUserData* ngud = ON_V4V5_MeshNgonUserData::Cast(ud);
  
  if ( 0 != ngud && !ON_ValidateMeshNgonUserData(ngud,*this) )
  {
    delete ngud;
    ngud = 0;
  }

  return (0 == ngud) ? 0 : ngud->m_ngon_list;
}


class ON_V4V5_MeshNgonList* ON_Mesh::V4V5_ModifyNgonList()
{
  ON_UserData* ud = GetUserData(ON_CLASS_ID(ON_V4V5_MeshNgonUserData));
  ON_V4V5_MeshNgonUserData* ngud = ON_V4V5_MeshNgonUserData::Cast(ud);
  if ( 0 == ngud )
  {
    if ( ud )
    {
      delete ud;
      ud = 0;
    }
    ngud = new ON_V4V5_MeshNgonUserData();
    ngud->m_mesh_F_count = m_F.Count();
    ngud->m_mesh_V_count = m_V.Count();
    AttachUserData(ngud);
  }
  else if ( 0 != ngud->m_ngon_list && !ON_ValidateMeshNgonUserData(ngud,*this) )
  {
    delete ngud->m_ngon_list;
    ngud->m_ngon_list = 0;
  }

  if ( 0 == ngud->m_ngon_list )
  {
    ngud->m_ngon_list = new ON_V4V5_MeshNgonList();
    ngud->m_mesh_F_count = m_F.Count();
    ngud->m_mesh_V_count = m_V.Count();
  }

  return ngud->m_ngon_list;
}


void ON_Mesh::V4V5_DestroyNgonList()
{
  ON_UserData* ud = GetUserData(ON_CLASS_ID(ON_V4V5_MeshNgonUserData));
  if ( 0 != ud )
  {
    delete ud;
    ud = 0;
  }
}

static void InitializeVertexFaceMap(
  ON__UINT_PTR* vertex_face_map,
  unsigned int i0,
  unsigned int i1,
  unsigned int* a
  )
{
  if ( i1 > i0 )
  {
    ON__UINT_PTR vertex_face_count;
    vertex_face_map += i0;
    //i1 -= i0;
    for ( i1 -= i0; i1 > 0; i1-- )
    {
      vertex_face_count = vertex_face_map[0];
      if ( 0 != vertex_face_count )
      {
        a[0] = 0;
        vertex_face_map[0] = (ON__UINT_PTR)a;
        a += (vertex_face_count+1);
      }
      vertex_face_map++;
    }
  }
}

const ON_MeshFaceList ON_MeshFaceList::EmptyFaceList;

ON_MeshFaceList::ON_MeshFaceList(
  const ON_Mesh* mesh
  )
{
  SetFromMesh(mesh);
}

unsigned int ON_MeshFaceList::SetFromTriangleList(
  size_t triangle_count,
  size_t triangle_stride,
  const unsigned int* triangles
  )
{
  if ( triangle_count > 0 && triangle_stride >= 3 && 0 != triangles )
  {
    m_bQuadFaces = false;
    m_face_count = (unsigned int)triangle_count;
    m_face_stride = (unsigned int)triangle_stride;
    m_faces = (const unsigned int*)triangles;
  }
  else
  {
    m_bQuadFaces = false;
    m_face_count = 0;
    m_face_stride = 0;
    m_faces = 0;
  }
  return m_face_count;
}
  
unsigned int ON_MeshFaceList::SetFromQuadList(
  size_t quad_count,
  size_t quad_stride,
  const unsigned int* quads
  )
{
  if ( quad_count > 0 && quad_stride >= 4 && 0 != quads )
  {
    m_bQuadFaces = true;
    m_face_count = (unsigned int)quad_count;
    m_face_stride = (unsigned int)quad_stride;
    m_faces = quads;
  }
  else
  {
    m_bQuadFaces = false;
    m_face_count = 0;
    m_face_stride = 0;
    m_faces = 0;
  }
  return m_face_count;
}

unsigned int ON_MeshFaceList::SetFromMesh(const ON_Mesh* mesh)
{
  if ( 0 != mesh )
  {
    SetFromQuadList(mesh->m_F.UnsignedCount(),4,(const unsigned int*)mesh->m_F.Array());
  }
  else
  {
    SetFromQuadList(0,0,0);
  }
  return m_face_count;
}


unsigned int ON_MeshFaceList::GetVertexIndexInterval(
  unsigned int minimum_valid_vertex_index,
  unsigned int maximum_valid_vertex_index,
  unsigned int* minimum_vertex_index,
  unsigned int* maximum_vertex_index
  ) const
{
  unsigned int vertex0 = 0;
  unsigned int vertex1 = 0;
  unsigned int v0, v1, vi;
  unsigned int valid_face_count = 0;
  for (;;)
  {
    if (0 == m_face_count || m_face_stride < 3 || nullptr == m_faces)
      break;
    const unsigned int face_vertex_count = m_bQuadFaces ? 4 : 3;
    const unsigned int delta_f = m_face_stride - face_vertex_count;
    const unsigned int* f1 = m_faces + (m_face_count*m_face_stride);
    for (const unsigned int* f = m_faces; f < f1; f += delta_f)
    {
      v0 = v1 = *f++;
      vi = *f++;
      if (vi < v0) v0 = vi; else if (vi > v1) v1 = vi;
      vi = *f++;
      if (vi < v0) v0 = vi; else if (vi > v1) v1 = vi;
      if (m_bQuadFaces)
      {
        vi = *f++;
        if (vi < v0) v0 = vi; else if (vi > v1) v1 = vi;
      }
      if (v0 < minimum_valid_vertex_index || v1 > maximum_valid_vertex_index)
        continue;
      if (0 == valid_face_count)
      {
        vertex0 = v0;
        vertex1 = v1;
      }
      else
      {
        if (v0 < vertex0)
          vertex0 = v0;
        if (v1 > vertex1)
          vertex1 = v1;
      }
      valid_face_count++;
    }
    break;
  }
  if (minimum_vertex_index)
    *minimum_vertex_index = vertex0;
  if (maximum_vertex_index)
    *maximum_vertex_index = vertex1;
  return valid_face_count;
}

struct ON_MeshVertexFaceMap_BLK
{
  struct ON_MeshVertexFaceMap_BLK* m_next;
  static const size_t m_target_block_size;
};

// Four pages less a bit for overhead
const size_t ON_MeshVertexFaceMap_BLK::m_target_block_size(4096*4 - sizeof(ON_MeshVertexFaceMap_BLK) - 32);


void* ON_MeshVertexFaceMap::m_alloc(size_t sz)
{
  // The returned pointer is used for arrays of pointers
  // and arrays of unsigned ints.
  // This code will need to be modified if the definition of 
  // struct ON_MeshVertexFaceMap_BLK changes in order to 
  // make certain alignment constraints are met on all
  // "reasonable" CPUs.

  struct ON_MeshVertexFaceMap_BLK* blk;

  if ( sz <= 0 )
    return 0;

  if (sz % sizeof(struct ON_MeshVertexFaceMap_BLK)) 
    sz += sizeof(struct ON_MeshVertexFaceMap_BLK);
  
  blk = new (std::nothrow) ON_MeshVertexFaceMap_BLK[1 + sz/sizeof(struct ON_MeshVertexFaceMap_BLK)];
  
  if ( 0 == blk )
    return 0;

  blk->m_next = (struct ON_MeshVertexFaceMap_BLK*)m_p;
  m_p = blk;
  return (blk+1);
}

void ON_MeshVertexFaceMap::Destroy()
{
  ON_MeshVertexFaceMap_BLK* blk;
  ON_MeshVertexFaceMap_BLK* next;

  blk = (ON_MeshVertexFaceMap_BLK*)m_p;
  
  m_vertex_count = 0;
  m_face_count = 0;
  m_vertex_face_map = 0;
  m_p = 0;

  while ( blk )
  {
    next = blk->m_next;
    delete blk;
    blk = next;
  }
}

void ON_MeshVertexFaceMap::m_copy(const ON_MeshVertexFaceMap& src)
{
  const size_t blk_sz = ON_MeshVertexFaceMap_BLK::m_target_block_size;
  size_t sz, delta_sz;

  const unsigned int* const * src_vf_map;
  unsigned int** vf_map;
  unsigned int* a;
  const unsigned int* src_a;
  unsigned int i0, i1, vf_count, a_count;

  for(;;)
  {
    vf_map = 0;
    sz = 0;
    delta_sz = 0;

    vf_count = src.m_vertex_count;
    src_vf_map =  src.m_vertex_face_map;
    
    if ( vf_count <= 0 )
      break;

    if ( 0 == src_vf_map )
      break;

    sz = vf_count*sizeof(vf_map[0]);

    for ( i0 = i1 = 0; i1 < vf_count; i1++)
    {
      if ( nullptr == src_vf_map[i1] || src_vf_map[i1][0] == 0 )
        continue; // 0 faces use vertex i1.
      delta_sz += src_vf_map[i1][0]*sizeof(a[0]);
      if ( sz + delta_sz > blk_sz )
      {
        if ( 0 == vf_map )
        {
          vf_map = (unsigned int**)m_alloc(sz);
          a = (unsigned int*)(vf_map ? (vf_map + vf_count) : 0);
        }
        else
        {
          a = (unsigned int*)m_alloc(sz);
        }
        if ( 0 == a )
          break;
        while(i0 < i1)
        {
          src_a = src_vf_map[i0];
          if ( 0 != src_a && (a_count = src_a[0]) > 0 )
          {
            vf_map[i0] = a;
            *a++ = *src_a++; // count
            *a++ = *src_a++; // initial face index
            while(--a_count > 0 )
              *a++ = *src_a++; // additional face indices
          }
          else
          {
            vf_map[i0] = 0;
          }
          i0++;
        }
        sz = 0;
      }
      sz += delta_sz;
    }

    if ( i0 < i1 && sz > 0 )
    {
      if ( 0 == vf_map )
      {
        vf_map = (unsigned int**)m_alloc(sz);
        a = (unsigned int*)(vf_map ? (vf_map + vf_count) : 0);
      }
      else
      {
        a = (unsigned int*)m_alloc(sz);
      }
      if ( 0 == a )
        break;
      while(i0 < i1)
      {
        src_a = src_vf_map[i0];
        if ( 0 != src_a && (a_count = src_a[0]) > 0 )
        {
          vf_map[i0] = a;
          *a++ = *src_a++; // count
          *a++ = *src_a++; // initial face index
          while(--a_count > 0 )
            *a++ = *src_a++; // additional face indices
        }
        else
        {
          vf_map[i0] = 0;
        }
        i0++;
      }
      sz = 0;
    }

    return; // success
  }

  if ( 0 != vf_map )
    Destroy(); // failure
}

ON_MeshVertexFaceMap::ON_MeshVertexFaceMap() ON_NOEXCEPT
  : m_vertex_count(0)
  , m_face_count(0)
  , m_vertex_face_map(0)
  , m_p(0)
{
}

ON_MeshVertexFaceMap::~ON_MeshVertexFaceMap()
{
  Destroy();
}

ON_MeshVertexFaceMap::ON_MeshVertexFaceMap(const ON_MeshVertexFaceMap& src)
  : m_vertex_count(0)
  , m_face_count(0)
  , m_vertex_face_map(0)
  , m_p(0)
{
  m_copy(src);
}

ON_MeshVertexFaceMap& ON_MeshVertexFaceMap::operator=(const ON_MeshVertexFaceMap& src)
{
  if ( this != &src )
  {
    Destroy();
    m_copy(src);
  }
  return *this;
}


#if defined(ON_HAS_RVALUEREF)
ON_MeshVertexFaceMap::ON_MeshVertexFaceMap( ON_MeshVertexFaceMap&& src) ON_NOEXCEPT
  : m_vertex_count(src.m_vertex_count)
  , m_face_count(src.m_face_count)
  , m_vertex_face_map(src.m_vertex_face_map)
  , m_p(src.m_p)
{
  src.m_vertex_count = 0;
  src.m_face_count = 0;
  src.m_vertex_face_map = 0;
  src.m_p = 0;
}

ON_MeshVertexFaceMap& ON_MeshVertexFaceMap::operator=( ON_MeshVertexFaceMap&& src) ON_NOEXCEPT
{
  if ( this != &src )
  {
    Destroy();

    m_vertex_count = src.m_vertex_count;
    m_face_count = src.m_face_count;
    m_vertex_face_map = src.m_vertex_face_map;
    m_p = src.m_p;

    src.m_vertex_count = 0;
    src.m_face_count = 0;
    src.m_vertex_face_map = 0;
    src.m_p = 0;
  }
  return *this;
}
#endif

bool ON_MeshVertexFaceMap::SetFromMesh(
  const ON_Mesh* mesh,
  bool bMapInvalidFaces
  )
{
  ON_MeshFaceList face_list;

  if ( face_list.SetFromMesh(mesh) > 0 )
  {
    return SetFromFaceList(
      mesh->m_V.UnsignedCount(),
      face_list,
      bMapInvalidFaces
      );
  }

  // failure
  Destroy();
  return false;
}


bool ON_MeshVertexFaceMap::SetFromFaceList(
  unsigned int vertex_count,
  const ON_MeshFaceList& face_list,
  bool bMapInvalidFaces
  )
{
  size_t sz, delta_sz;
  unsigned int* a;
  ON__UINT_PTR* vertex_face_map = 0;
  unsigned int fi, j, k, vi0, vi1, Fvi[4];

  Destroy();

  const unsigned int face_count = face_list.FaceCount();

  
  for(;;)
  {
    const unsigned int max_valid_vertex_count = 0xFFFF0000U;
    if (0 == vertex_count || vertex_count > max_valid_vertex_count)
    {
      if ( face_list.GetVertexIndexInterval(0,max_valid_vertex_count-1,0,&vertex_count) < 1 )
        break;
      vertex_count++;
    }
    if ( vertex_count <= 0  )
      break;
    if ( face_count <= 0  )
      break;
    
    sz = vertex_count*sizeof(vertex_face_map[0]);
    vertex_face_map = (ON__UINT_PTR*)m_alloc(sz);
    if ( 0 == vertex_face_map )
      break;
    memset(vertex_face_map,0,sz);

    // count the number of faces at each vertex
    sz = 0;
    for ( fi = 0; fi < face_count; fi++ )
    {
      face_list.QuadFvi(fi,Fvi);
      k = (Fvi[2]!=Fvi[3]) ? 4 : 3;
      for ( j = 0; j < k; j++ )
      {
        if ( Fvi[j] < vertex_count )
        {
          sz++;
          if ( 0 == vertex_face_map[Fvi[j]]++ )
            sz++;
          continue;
        }
        else if ( !bMapInvalidFaces )
          break; // bogus face
      }
    }

    if ( sz*sizeof(a[0]) <= 4*ON_MeshVertexFaceMap_BLK::m_target_block_size )
    {
      a = (unsigned int*)m_alloc(sz*sizeof(a[0]));
      if ( 0 == a )
        return 0;
      InitializeVertexFaceMap(vertex_face_map,0,vertex_count,a);
    }
    else
    {
      // allocate memory in 4 page chunks
      sz = 0;
      k = 0;
      for ( j = 0; j < vertex_count; j++ )
      {
        if ( vertex_face_map[j] > 0 )
        {
          delta_sz = (vertex_face_map[j]+1)*sizeof(a[0]);
          if ( sz + delta_sz > ON_MeshVertexFaceMap_BLK::m_target_block_size )
          {
            a = (unsigned int*)m_alloc(sz);
            if ( 0 == a )
              return 0;
            InitializeVertexFaceMap(vertex_face_map,k,j,a);
            k = j;
            sz = 0;
          }
          sz += delta_sz;
        }
      }

      if ( sz > 0 )
      {
        a = (unsigned int*)m_alloc(sz);
        if ( 0 == a )
          return 0;
        InitializeVertexFaceMap(vertex_face_map,k,vertex_count,a);
      }
    }

    // Fill in the vertex_face_map[].
    sz = 0; // sz is used to count vertex_face_map[] entries that need to be nulled out
    for ( fi = 0; fi < face_count; fi++ )
    {
      face_list.QuadFvi(fi,Fvi);
      k = (Fvi[2]!=Fvi[3]) ? 3 : 2;
      vi0 = Fvi[3];
      for ( j = 0; j <= k; j++ )
      {
        vi1 = Fvi[j];
        if ( vi0 == vi1 )
        {
          if ( bMapInvalidFaces )
          {
            // caller wants the indices of invalid faces left in the vertex face map
            continue;
          }
        }
        else if ( vi1 < vertex_count )
        {
          a = (unsigned int*)vertex_face_map[vi1];
          vi0 = ++(a[0]); // vi0 temporarily used as an a[] index
          a[vi0] = fi;
          vi0 = vi1; // ... now set vi0 = previous vertex index
          continue;
        }

        // The code in this for loop below this comment
        // is run only when F[fi].vi[] is not valid.

        if ( bMapInvalidFaces )
        {
          // caller wants the indices of invalid faces left in the vertex face map
          continue;
        }

        // The face vertex list F[fi].vi[] is not valid - remove any references to it.
        if ( j > 0 )
        {
          for (k = 0; k < j; k++ )
          {
            a = (unsigned int*)vertex_face_map[Fvi[k]];
            a[0]--;
            if ( 0 == a[0] )
            {
              // If sz > 0, then further cleanup may be required
              // if no other valid faces use this vertex.  This 
              // is done below.
              sz++;
            }
          }
        }
        else if ( vi1 < vertex_count )
        {
          // If mesh face F[fi] is the only face that references the vertex
          // with index vi1 = F[fi].vi[0], then vertex_face_map[vi1] 
          // will be removed.
          a = (unsigned int*)vertex_face_map[vi1];
          if ( 0 != a && 0 == a[0] )
            sz++;
        }
        break;
      }
    }

    if ( sz > 0 )
    {
      // need to check for empty vertex_face_map[] arrays and remove them
      for ( j = 0; j < vertex_count; j++ )
      {
        a = (unsigned int*)vertex_face_map[j];
        if ( 0 != a && 0 == a[0] )
        {
          // Every face that uses this vertex is not valid.
          vertex_face_map[j] = 0;
          sz--;
          if ( 0 == sz )
          {
            // finished with repairs. 
            // Note:
            //   When a vertex is used by both valid and invalid faces,
            //   sz will not return to zero and this loop will terminate with
            //   j == meshVcount.
            break; 
          }
        }
      }
    }

    m_vertex_face_map = (const unsigned int *const*)vertex_face_map;
    m_vertex_count = vertex_count;
    return true;
  }
  
  if ( vertex_face_map )
    Destroy();

  return false;
}


unsigned int ON_MeshVertexFaceMap::VertexCount() const
{
  return m_vertex_count;
}

unsigned int ON_MeshVertexFaceMap::FaceCount() const
{
  return m_face_count;
}

unsigned int ON_MeshVertexFaceMap::VertexFaceCount(
  unsigned int vertex_index
  ) const
{
  const unsigned int* vf_list = (vertex_index < m_vertex_count) ? m_vertex_face_map[vertex_index] : 0;
  return vf_list ? vf_list[0] : 0;
}
  
const unsigned int* ON_MeshVertexFaceMap::VertexFaceList(
  unsigned int vertex_index
  ) const
{
  const unsigned int* vf_list = (vertex_index < m_vertex_count) ? m_vertex_face_map[vertex_index] : 0;
  return (vf_list && vf_list[0] > 0) ? (vf_list+1) : 0;
}

const unsigned int *const* ON_MeshVertexFaceMap::VertexFaceMap() const
{
  return m_vertex_face_map;
}

static bool FaceInPlane(
  double planar_tolerance,
  ON_PlaneEquation e,
  const ON_3dPointListRef& vertex_list,
  const unsigned int Fvi[4]
  )
{
  // Note:
  //   This function is called only with Fvi[] lists that have been checked to
  //   have valid enteries.
  double point_buffer[3];

  if ( !(fabs(e.ValueAt(vertex_list.GetPoint(Fvi[0],point_buffer))) <= planar_tolerance) )
    return false;
  if ( !(fabs(e.ValueAt(vertex_list.GetPoint(Fvi[1],point_buffer))) <= planar_tolerance) )
    return false;
  if ( !(fabs(e.ValueAt(vertex_list.GetPoint(Fvi[2],point_buffer))) <= planar_tolerance) )
    return false;
  if ( (Fvi[3] != Fvi[2]) && !(fabs(e.ValueAt(vertex_list.GetPoint(Fvi[3],point_buffer))) <= planar_tolerance) )
    return false;

  return true;
}

static bool GetFacePlaneEquation(
  const ON_3dPointListRef& vertex_list,
  bool bQuadFaces,
  const unsigned int* Fvi,
  double planar_tolerance,
  ON_PlaneEquation& e
  )
{
  // The  P = (ON_3dPoint*)buffer; technique is used to
  // avoid calling constructor code in this performance
  // critical calculation.
  ON_3dPoint* P;
  double buffer[4][3];
  unsigned int i;
  const unsigned int vertex_count = vertex_list.PointCount();
  const unsigned int k = bQuadFaces?4:3;

  if ( Fvi[0] == Fvi[1] || Fvi[1] == Fvi[2] || Fvi[0] == Fvi[k-1] )
  {
    // all FindNgon() code after this line can
    // assume Fvi[0] !+ Fvi[1], Fvi[1] != Fvi[2] and Fvi[3] != Fvi[0] 
    return false; 
  }

  P = (ON_3dPoint*)buffer;
  for ( i = 0; i < k; i++ )
  {
    if ( Fvi[i] >= vertex_count )
      return false;
    vertex_list.GetPoint(Fvi[i],buffer[i]);
  }

  if ( !bQuadFaces )
    P[3] = P[2];

  const ON_3dVector A(P[2].x-P[0].x,P[2].y-P[0].y,P[2].z-P[0].z);
  const ON_3dVector B(P[3].x-P[1].x,P[3].y-P[1].y,P[3].z-P[1].z);
  ON_3dVector N(A.y*B.z - B.y*A.z, A.z*B.x - B.z*A.x, A.x*B.y - B.x*A.y );
  double d = N.Length();
  if ( !(d > 0.0) )
    return false;
  N.x /= d;
  N.y /= d;
  N.z /= d;
  d = -(N.x*P[0].x + N.y*P[0].y + N.z*P[0].z);
  if ( !(fabs(N.x*P[1].x + N.y*P[1].y + N.z*P[1].z + d) <= planar_tolerance) )
    return false;
  if ( !(fabs(N.x*P[2].x + N.y*P[2].y + N.z*P[2].z + d) <= planar_tolerance) )
    return false;
  if ( !(fabs(N.x*P[3].x + N.y*P[3].y + N.z*P[3].z + d) <= planar_tolerance) )
    return false;

  e.x = N.x;
  e.y = N.y;
  e.z = N.z;
  e.d = d;

  return true;
}

/*
Returns:
  index into the ON_MeshNgon.m_fi[] array.
*/
static ON__UINT_PTR DecodeNFS_NgonFaceIndex( ON__UINT_PTR nfs )
{
  return (nfs/8);
}

static unsigned int DecodeNFS_EdgeIndex( ON__UINT_PTR nfs )
{
  return ((unsigned int)(nfs & 0x3));
}

/*
Parameters:
  ngon_face_index - [in]
    ON_MeshNgon.m_fi[] array index
  face_edge_index - [in]
   0 to 3
   the edge runs from 
     vertex index ON_MeshFace.vi[face_edge_index] 
   to 
     vertex index ON_MeshFace.vi[(face_edge_index+1)%4].
    
Returns:
  index into the ON_MeshNgon.m_fi[] array.
*/
static ON__UINT_PTR EncodeNFS_Neighbor(ON__UINT_PTR ngon_face_index, ON__UINT_PTR face_edge_index)
{
  // The 0x4 makes the value non-zero when ngon_face_index and face_edge_index are both zero.
  return ((ngon_face_index*8) | face_edge_index | 0x4 );
}

static ON__UINT_PTR EncodeNFS_BoundaryIndex(ON__UINT_PTR boundary_index)
{
  // 0 = (boundary_mark & 0x4)
  // 0 != boundary_mark
  return (boundary_index*8 | 0x1);
}

static bool IsNFS_Neighbor(ON__UINT_PTR nfs)
{
  return ( 0 != (nfs&0x4) );
}

struct NgonNeighbors
{
  // NFS = N-gon face and Side information
  // DecodeNFS_NgonFaceIndex(m_NFS[side_index]) = index into the ON_MeshNgon.m_fi[] array
  // DecodeNFS_EdgeIndex(m_NFS[side_index]) = 0,1,2 or 3 identifies the edge
  ON__UINT_PTR m_NFS[4];
};

static unsigned int FaceListIndex(
  unsigned int face_index_count,
  const unsigned int* face_index_list,
  unsigned int face_index
  )
{
  for ( unsigned int i = 0; i < face_index_count; i++ )
  {
    if ( face_index == face_index_list[i] )
      return i;
  }
  return ON_UNSET_UINT_INDEX;
}

static unsigned int SetFaceNeighborMap(
  unsigned int mesh_vertex_count,
  const ON_MeshFaceList& mesh_face_list,
  const unsigned int *const* vertex_face_map,
  unsigned int face_index_count,
  const unsigned int* face_index_list,
  struct NgonNeighbors* face_nbr_map
  )
{
  //const unsigned int mesh_vertex_count = mesh_vertex_list.VertexCount();
  const unsigned int mesh_face_count = mesh_face_list.FaceCount();
  unsigned int face_index, nbr_face_index, viA, viB, Adex;
  unsigned int Fvi[4], nbr_Fvi[4]; // quad face vertex indices
  unsigned int face_side, nbr_face_side; // 0 to 3 = face side index
  unsigned int fdex, nbr_fdex; // indices into the face_index_list[] array
  const unsigned int* face_listA;
  const unsigned int* face_listB;

  unsigned int boundary_count = 0;

  if ( face_index_count <= 0 || 0 == face_index_list || 0 == face_nbr_map )
    return 0;

  ON_MeshVertexFaceMap vf_map;
  if ( nullptr == vertex_face_map )
  {
    if ( !vf_map.SetFromFaceList(mesh_vertex_count,mesh_face_list,false) )
      return 0;
    vertex_face_map = vf_map.VertexFaceMap();
    if ( 0 == vertex_face_map )
      return 0;
    mesh_vertex_count = vf_map.VertexCount();
  }

  memset(face_nbr_map,0,face_index_count*sizeof(face_nbr_map[0]));
  for ( fdex = 0; fdex < face_index_count; fdex++ )
  {
    face_index = face_index_list[fdex];
    if ( face_index >= mesh_face_count )
      continue;

    mesh_face_list.QuadFvi(face_index,Fvi);
    viB = Fvi[0];
    face_listB = ( viB <= mesh_vertex_count ) ? vertex_face_map[viB] : 0;
    if ( 0 != face_listB && face_listB[0] <= 1 )
      face_listB = 0;

    for ( face_side = 0; face_side < 4; face_side++ )
    {
      viA = viB;
      viB = Fvi[(face_side+1)%4];
      if ( viA == viB )
        continue;

      boundary_count++;

      face_listA = face_listB;
      face_listB = ( viB <= mesh_vertex_count ) ? vertex_face_map[viB] : 0;
      if ( 0 == face_listB )
        continue;
      if ( face_listB[0] <= 1 )
      {
        face_listB = 0;
        continue;
      }
      if ( 0 == face_listA )
        continue;

      if ( 0 != face_nbr_map[fdex].m_NFS[face_side] )
      {
        boundary_count--;
        continue; // already found a neighbor for this side
      }

      // look for a neighbor from viB to viA
      for ( Adex = 1; Adex <= face_listA[0]; Adex++ )
      {
        nbr_face_index = face_listA[Adex];
        if ( face_index == nbr_face_index)
          continue;
        nbr_fdex = FaceListIndex(face_index_count,face_index_list,nbr_face_index);
        if ( nbr_fdex >= face_index_count )
          continue; // nbr_face_index is not in the face_index_list[] array
        if ( nbr_fdex <= fdex )
          continue; // this combination of faces has already been checked.

        mesh_face_list.QuadFvi(nbr_face_index,nbr_Fvi);
        for ( nbr_face_side = 0; nbr_face_side < 4; nbr_face_side++ )
        {
          if ( viB != nbr_Fvi[nbr_face_side] )
            continue;

          if ( viA != nbr_Fvi[(nbr_face_side+1)%4] )
            continue; // happens for the duplicate vertex in a triangle

          if ( 0 != face_nbr_map[nbr_fdex].m_NFS[nbr_face_side] )
            continue; // this side of the neighbor has a neighbor (unexpected)

          // record the fact these faces share an edge
          face_nbr_map[nbr_fdex].m_NFS[nbr_face_side] = EncodeNFS_Neighbor(fdex,face_side);
          face_nbr_map[fdex].m_NFS[face_side] = EncodeNFS_Neighbor(nbr_fdex,nbr_face_side);
          boundary_count--;
          nbr_face_side = 5;
          break;
        }
        if ( 5 == nbr_face_side )
          break; // we found a neighbor and are finished seaching
      }
    }
  }

  return boundary_count;
}


static int GetCoplanarConnectedFaces(
  const ON_3dPointListRef& vertex_list,
  const ON_MeshFaceList& face_list,
  const unsigned int ngonIndex,
  unsigned int* ngonMap,
  const unsigned int *const* vertex_face_map,
  double planar_tolerance,
  unsigned int starting_face_index,
  ON_PlaneEquation e,
  ON_SimpleArray<unsigned int>& ngon_fi,
  ON_SimpleArray<struct NgonNeighbors>& ngon_nbr_map
  )
{
  ON__UINT_PTR face_side, nbr_face_side;
  unsigned int boundary_count, ngon_fi_dex0, ngon_fi_dex1, nbr_ngon_fi_dex, viA, viB, Adex, nbr_face_index, face_index;
  unsigned int Fvi[4], nbr_Fvi[4];
  const unsigned int* face_listA;
  const unsigned int* face_listB;
  const unsigned int vertex_count = vertex_list.PointCount();

  ngon_fi.SetCount(0);
  ngon_fi.Append(starting_face_index);

  ngon_nbr_map.SetCount(0);
  ngon_nbr_map.AppendNew();

  ngonMap[starting_face_index] = ngonIndex;

  boundary_count = 0;

  ngon_fi_dex1 = 0;
  while( ngon_fi_dex1 < ngon_fi.UnsignedCount() )
  {
    ngon_fi_dex0 = ngon_fi_dex1;
    for (ngon_fi_dex1 = ngon_fi.Count(); ngon_fi_dex0 < ngon_fi_dex1; ngon_fi_dex0++ )
    {
      // look for neighbors of F[face_index]
      face_index = ngon_fi[ngon_fi_dex0];
      face_list.QuadFvi(face_index,Fvi);
      viB = Fvi[0];
      face_listB = ( viB <= vertex_count ) ? vertex_face_map[viB] : 0;
      if ( 0 != face_listB && face_listB[0] <= 1 )
        face_listB = 0;
      for ( face_side = 0; face_side < 4; face_side++ )
      {
        viA = viB;
        viB = Fvi[(face_side+1)%4];
        if ( viA == viB )
          continue;

        boundary_count++;

        face_listA = face_listB;
        face_listB = ( viB <= vertex_count ) ? vertex_face_map[viB] : 0;
        if ( 0 == face_listB )
          continue;
        if ( face_listB[0] <= 1 )
        {
          face_listB = 0;
          continue;
        }
        if ( 0 == face_listA )
          continue;

        if ( 0 != ngon_nbr_map[ngon_fi_dex0].m_NFS[face_side] )
        {
          boundary_count--;
          continue; // already found a neighbor for this side
        }

        // look for a coplanar neighbor from viB to viA
        for ( Adex = 1; Adex <= face_listA[0]; Adex++ )
        {
          nbr_face_index = face_listA[Adex];
          if ( face_index == nbr_face_index || nbr_face_index <= starting_face_index)
            continue;
          if ( ON_UNSET_UINT_INDEX != ngonMap[nbr_face_index] && ngonIndex != ngonMap[nbr_face_index])
            continue; // ON_Mesh.m_F[nbr_face_index] belongs to another n-gon

          face_list.QuadFvi(nbr_face_index,nbr_Fvi);
          for ( nbr_face_side = 0; nbr_face_side < 4; nbr_face_side++ )
          {
            if ( viB != nbr_Fvi[nbr_face_side] )
              continue;

            if ( viA != nbr_Fvi[(nbr_face_side+1)%4] )
              continue; // happens for the duplicate vertex in a triangle

            if ( !FaceInPlane(planar_tolerance,e,vertex_list,nbr_Fvi) )
              break; // shared edge but not coplanar

            if ( ON_UNSET_UINT_INDEX == ngonMap[nbr_face_index] )
            {
              // add ON_Mesh.m_F[nbr_face_index] to this ngon
              nbr_ngon_fi_dex = ngon_fi.Count();
              ngonMap[nbr_face_index] = ngonIndex;
              ngon_fi.Append(nbr_face_index);
              ngon_nbr_map.AppendNew().m_NFS[nbr_face_side] = EncodeNFS_Neighbor(ngon_fi_dex0,face_side);
            }
            else
            {
              // If things are as expected, then ON_Mesh.m_F[nbr_face_index]
              // face was been added to the ngon after the face we are working
              // on ( ON_Mesh.m_F[ngon_fi[ngon_fi_dex0]] ).
              for ( nbr_ngon_fi_dex = ngon_fi.Count()-1; nbr_ngon_fi_dex > ngon_fi_dex0; nbr_ngon_fi_dex-- )
              {
                if (nbr_face_index == ngon_fi[nbr_ngon_fi_dex])
                  break;
              }
              if ( nbr_ngon_fi_dex <= ngon_fi_dex0 )
                break; // not expected for an oriented manifold mesh
                
              if ( 0 != ngon_nbr_map[nbr_ngon_fi_dex].m_NFS[nbr_face_side] )
                break; // not expected for an oriented manifold mesh

              ngon_nbr_map[nbr_ngon_fi_dex].m_NFS[nbr_face_side] = EncodeNFS_Neighbor(ngon_fi_dex0,face_side);
            }

            if ( nbr_ngon_fi_dex > ngon_fi_dex0 )
            {
              ngon_nbr_map[ngon_fi_dex0].m_NFS[face_side] = EncodeNFS_Neighbor(nbr_ngon_fi_dex,nbr_face_side);
              boundary_count--;
              nbr_face_side = 5;
            }
            break;
          }
          if ( 5 == nbr_face_side )
            break; // we found a neighbor and are finished seaching
        }
      }
    }
  }

  return boundary_count;
}


static void UnsetNgonMap(
  const unsigned int* ngon_face_index_list,
  unsigned int ngon_face_index_count,
  const unsigned int ngonIndex,
  const unsigned int ngonUnsetIndex,
  unsigned int* ngonMap
  )
{  
  int fi;
  for ( unsigned int i = 0; i < ngon_face_index_count; i++ )
  {
    fi = ngon_face_index_list[i];
    if (ngonIndex == ngonMap[fi])
      ngonMap[fi] = ngonUnsetIndex;
  }
}

static unsigned int EncodeNgonSide(
  unsigned int ngon_fdex,
  unsigned int quad_side_index,
  unsigned int bReversed
  )
{
  return (ngon_fdex*8 + quad_side_index + (bReversed ? 4 : 0));
}


/*
Description:
  NEVER EXPORT THIS WORKER FUNCTION.

  Given a set of connected faces, this function finds the
  boundary of the n-gon.
Parameters:
  face_list - [in]
  ngon_fi_count - [in]
    >= 0
  ngon_fi - [in]
    array of length ngon_fi_count that contains the
    indices of faces that make up the ngon.
  ngon_boundary_index - [in]
    index of the next boundary segment to be added.
  ngon_nbr_map - [in/out]
    array of length ngon_fi_count that contains the
    face neighbor information.  
    ngon_nbr_map[face_index].m_NFS[j] must be zero
    for open boundaryies.  If the face side identified by
    ngon_nbr_map[face_index].m_NFS[j] is assigned to an
    ngon boundary, then it is marked as a boundary segment.
  ngon_vi - [out]
    If true is returned, then ngon_vi is an array of vertex
    indices that make up the boundary of the ngon.
  ngon_side - [out]
    ngon_side[i]/8 = ON_MeshNgon.m_fi[] index
    ngon_side[i]%4 = quad side index (side=0 means vtx 0 to vtx 1)
    (ngon_side[i] & 8) != 0 means side is reversed.
Returns:
  Number of vertices/segments added for the boundary component.
*/
static unsigned int GetNgonBoundarySegments(
  const ON_MeshFaceList& face_list,
  unsigned int ngon_fi_count,
  const unsigned int* ngon_fi,
  unsigned int ngon_boundary_index,
  struct NgonNeighbors* ngon_nbr_map,
  ON_SimpleArray<unsigned int>* ngon_vi,
  ON_SimpleArray<unsigned int>* ngon_side
  )
{
  unsigned int j0, next_j0, j1;
  unsigned int Fvi[4];
  unsigned int vi0, vi1, fi, max_vertex_count;
  unsigned int ngon_fdex, nbr_ngon_fdex, face_vist_count, max_face_visit_count;

  const unsigned int ngon_boundary_index0 = ngon_boundary_index;

  if ( ngon_fi_count <= 0 || 0 == ngon_fi || 0 == ngon_nbr_map )
    return 0;

  if ( 1 == ngon_fi_count )
  {
    if (    0 != ngon_nbr_map[0].m_NFS[0] 
         || 0 != ngon_nbr_map[0].m_NFS[1] 
         || 0 != ngon_nbr_map[0].m_NFS[2] 
         || 0 != ngon_nbr_map[0].m_NFS[3] 
         )
    {
      return 0;
    }
    face_list.QuadFvi(ngon_fi[0],Fvi);

    if ( ngon_vi )
      ngon_vi->Append(Fvi[0]);
    if ( ngon_side )
      ngon_side->AppendNew() = EncodeNgonSide(0,0,false);
    ngon_nbr_map[0].m_NFS[0] = EncodeNFS_BoundaryIndex(ngon_boundary_index++);

    if ( ngon_vi )
      ngon_vi->Append(Fvi[1]);
    if ( ngon_side )
      ngon_side->AppendNew() = EncodeNgonSide(0,1,false);
    ngon_nbr_map[0].m_NFS[1] = EncodeNFS_BoundaryIndex(ngon_boundary_index++);

    if ( Fvi[2] != Fvi[3] )
    {
      if ( ngon_vi )
        ngon_vi->Append(Fvi[2]);
      if ( ngon_side )
        ngon_side->AppendNew() = EncodeNgonSide(0,2,false);
      ngon_nbr_map[0].m_NFS[2] = EncodeNFS_BoundaryIndex(ngon_boundary_index++);
    }

    if ( ngon_vi )
      ngon_vi->Append(Fvi[3]);
    if ( ngon_side )
      ngon_side->AppendNew() = EncodeNgonSide(0,3,false);
    ngon_nbr_map[0].m_NFS[3] = EncodeNFS_BoundaryIndex(ngon_boundary_index++);

    return ngon_boundary_index - ngon_boundary_index0;
  }


  fi = 0;  // keep lint quiet - at this point ngon_fi_count > 0
  j0 = 0;  // keep lint quiet - at this point ngon_fi_count > 0
  
  // find a boundary edge
  for ( ngon_fdex = 0; ngon_fdex < ngon_fi_count; ngon_fdex++ )
  {
    fi = ngon_fi[ngon_fdex];
    face_list.QuadFvi(fi,Fvi);
    for ( j0 = 0; j0 < 4; j0++ )
    {
      if ( Fvi[j0] == Fvi[(j0+1)%4] )
        continue;
      if ( 0 != ngon_nbr_map[ngon_fdex].m_NFS[j0] )
        continue; // has a neighbor or is part of a previously found boundary component when holes are allowed

      // we being at the vertex with index Fvi[j]
      break;
    }    
    if ( j0 < 4 )
      break;
  }
  
  if ( ngon_fdex >= ngon_fi_count )
    return 0;

  // max_vertex_count is used to prevent infinite recursion
  // when the mesh information is not valid or there is
  // a bug in this code.  The maximum value occurs
  // for a strip of quads.
  max_vertex_count = 2*ngon_fi_count + 2;


  // max_face_visit_count is used to prevent infinite recursion
  // when the mesh information is not valid or there is
  // a bug in this code.  The maximum value occurs
  // for a strip of quads when the initial face is
  // at the end of a strip.
  max_face_visit_count = 2*ngon_fi_count - 1;

  ngon_nbr_map[ngon_fdex].m_NFS[j0] = EncodeNFS_BoundaryIndex(ngon_boundary_index++);
  if ( ngon_side )
    ngon_side->AppendNew() = EncodeNgonSide(ngon_fdex,j0,false);

  vi0 = Fvi[j0];
  j0 = (j0+1)%4;
  vi1 = Fvi[j0];
  if ( ngon_vi )
  {
    ngon_vi->Append(vi0);
    ngon_vi->Append(vi1);
  }
  for ( face_vist_count = 0;
        face_vist_count <= max_face_visit_count && ngon_boundary_index - ngon_boundary_index0 <= max_vertex_count;
        face_vist_count++ 
      )
  {
    for (j1 = 0; j1 < 4; j1++, j0 = next_j0 )
    {
      if ( 0 != ngon_nbr_map[ngon_fdex].m_NFS[j0] )
        break; // has a neighbor or is a boundary

      next_j0 = (j0+1)%4;
      if ( vi1 == Fvi[next_j0] )
        continue; // skip over duplicate vertex in triangles

      ngon_nbr_map[ngon_fdex].m_NFS[j0] = EncodeNFS_BoundaryIndex(ngon_boundary_index++);
      if ( ngon_side )
        ngon_side->AppendNew() = EncodeNgonSide(ngon_fdex,j0,false);

      vi1 = Fvi[next_j0];
      if ( vi0 == vi1 )
      {
        // this is where successful searches end with (ngon_vi.Count() >= 3)
        if ( ngon_boundary_index - ngon_boundary_index0 >= 3 )
        {
          return ngon_boundary_index - ngon_boundary_index0;
        }
        return 0;
      }
      if ( ngon_vi )
        ngon_vi->Append(vi1);
    }

    if ( j1 >= 4 )
      break; // not expected - there is a bug or a bogus mesh

    if ( false == IsNFS_Neighbor(ngon_nbr_map[ngon_fdex].m_NFS[j0]) )
      break; // not expected - there is a bug or a bogus mesh

    // switch to neighbor face;
    nbr_ngon_fdex = (unsigned int)DecodeNFS_NgonFaceIndex(ngon_nbr_map[ngon_fdex].m_NFS[j0]);

    if ( nbr_ngon_fdex == ngon_fdex || nbr_ngon_fdex >= ngon_fi_count )
    {
      // Unexpected - bail rather than crash.
      // This means there is a bug in the code that 
      // set the values in the ngon_nbr_map[] array.
      // The same face may be visited multiple times (strip of quads for example),
      // but a face can never be its own neighbor.
      break;
    }

    // move onto the neighbor
    j0 = DecodeNFS_EdgeIndex(ngon_nbr_map[ngon_fdex].m_NFS[j0]);

    ngon_fdex = nbr_ngon_fdex;
    fi = ngon_fi[ngon_fdex];
    face_list.QuadFvi(fi,Fvi);
    // faces have compatible orientations 
    // (The shared edge is reversed when going to neighboring face)
    j0 = (j0+1)%4; // move the the vertex at the end of the shared edge

    if ( vi1 != Fvi[j0] )
    {
      // not expected - there is a bug or a bogus mesh
      break;
    }
  }

  return 0;
}


class ON_MeshNgon* ON_MeshNgon::NgonFromMeshFace(
  class ON_MeshNgonBuffer& ngon_buffer,
  unsigned int mesh_face_index,
  const unsigned int* fvi
  )
{
  // The nullptr check is weird.
  // Speculation: There is / was some way that a null reference (like a * of a bona fide nullptr)
  // was in use?
  if ( ON_IsNullPtr(&ngon_buffer) || 0 == fvi )
    return 0;

  // local_fvi[] allows the fvi[] input to be stored in the buffer[] memory.
  unsigned int local_fvi[4] = {fvi[0],fvi[1],fvi[2],fvi[3]};
  fvi = local_fvi;

  unsigned int Vcount = (fvi[3] < ON_UNSET_UINT_INDEX && fvi[3] != fvi[2]) ? 4 : 3;

  ON_MeshNgon* ngon = (ON_MeshNgon*)(&ngon_buffer);
  ngon->m_vi = (unsigned int*)(ngon+1);
  ngon->m_fi = ngon->m_vi + Vcount;

  ngon->m_Vcount = Vcount;
  ngon->m_vi[0] = fvi[0];
  ngon->m_vi[1] = fvi[1];
  ngon->m_vi[2] = fvi[2];
  if ( 4 == ngon->m_Vcount )
    ngon->m_vi[3] = fvi[3];

  ngon->m_Fcount = 1;
  ngon->m_fi[0] = mesh_face_index;

  return ngon;
}

class ON_MeshNgon** ON_MeshNgon::NgonListFromMeshFace(
  class ON_MeshNgonBuffer& ngon_buffer,
  unsigned int mesh_face_index,
  const unsigned int* fvi
  )
{
  ON_MeshNgon* ngon = NgonFromMeshFace(ngon_buffer,mesh_face_index,fvi);
  if ( 0 == ngon )
    return 0;

  ON_MeshNgon** ngon_list = (ON_MeshNgon**)(ngon+1);
  ngon_list[0] = ngon;

  return ngon_list;
}

const ON_MeshNgon* ON_Mesh::NgonFromComponentIndex(
  class ON_MeshNgonBuffer& ngon_buffer,
  ON_COMPONENT_INDEX ci
  ) const
{
  const ON_MeshNgon* ngon = 0;
  switch( ci.m_type )
  {
  case ON_COMPONENT_INDEX::mesh_face:
    if ( ci.m_index >= 0 && ci.m_index < m_F.Count() )
    {
      ngon = ON_MeshNgon::NgonFromMeshFace(ngon_buffer,(unsigned int)ci.m_index,(const unsigned int*)m_F[ci.m_index].vi);
    }
    break;

  case ON_COMPONENT_INDEX::mesh_ngon:
    ngon = Ngon(ci.m_index);
    break;
  }

  return ngon;
}

unsigned int ON_MeshNgon::FindNgonOuterBoundary(
  const ON_3dPointListRef& mesh_vertex_list,
  const ON_MeshFaceList& mesh_face_list,
  const unsigned int *const* vertex_face_map,
  size_t ngon_fi_count,
  const unsigned int* ngon_fi,
  ON_SimpleArray<unsigned int>& ngon_vi
  )
{
  const unsigned int mesh_vertex_count = mesh_vertex_list.PointCount();
  const unsigned int ngon_boundary_index = 1;
  unsigned int boundary_edge_count;


  for(;;)
  {
    if ( mesh_vertex_count <= 0 || ON_UNSET_UINT_INDEX == mesh_vertex_count )
      break;

    if ( ngon_fi_count <= 0 || 0 == ngon_fi )
      break;

    ON_SimpleArray<struct NgonNeighbors> ngon_nbr_map;
    ngon_nbr_map.Reserve((unsigned int)ngon_fi_count);
    ngon_nbr_map.SetCount((unsigned int)ngon_fi_count);
    boundary_edge_count = SetFaceNeighborMap(
      mesh_vertex_count,
      mesh_face_list,
      vertex_face_map,
      (unsigned int)ngon_fi_count,
      ngon_fi,
      ngon_nbr_map.Array()
      );

    if ( boundary_edge_count <= 0 )
      break;

    ngon_vi.SetCount(0);
    ngon_vi.Reserve(boundary_edge_count);
    if ( !GetNgonBoundarySegments(
      mesh_face_list,
      (unsigned int)ngon_fi_count,
      ngon_fi,
      ngon_boundary_index,
      ngon_nbr_map.Array(),
      &ngon_vi,
      0
      ))
      break;

    if ( boundary_edge_count != ngon_vi.UnsignedCount() )
      break; // inner boundaries exist - ngon has holes

    return ngon_vi.UnsignedCount();
  }

  // failure
  ngon_vi.SetCount(0);
  return 0;
}

unsigned int ON_MeshNgon::GetBoundarySides(
  const class ON_MeshFaceList& mesh_face_list,
  ON_SimpleArray<unsigned int>& ngon_boundary_sides
  ) const
{
  unsigned int ngon_boundary_segment_count = 0;
  unsigned int boundary_edge_count;

  ngon_boundary_sides.SetCount(0);


  for(;;)
  {
    const unsigned int ngon_fi_count(m_Fcount);
    const unsigned int* ngon_fi(m_fi);
    if ( ngon_fi_count <= 0 || 0 == ngon_fi )
      break;

    const unsigned int mesh_vertex_count = 0xFFFFFFFE;
    const unsigned int *const* vertex_face_map = 0;

    ON_SimpleArray<struct NgonNeighbors> ngon_nbr_map;
    ngon_nbr_map.Reserve(ngon_fi_count);
    ngon_nbr_map.SetCount(ngon_fi_count);
    boundary_edge_count = SetFaceNeighborMap(
      mesh_vertex_count,
      mesh_face_list,
      vertex_face_map,
      ngon_fi_count,
      ngon_fi,
      ngon_nbr_map.Array()
      );

    if ( boundary_edge_count <= 0 )
      break;

    ngon_boundary_sides.SetCount(0);
    ngon_boundary_sides.Reserve(boundary_edge_count);

    for(;;)
    {
      // keep adding boundary components until we
      // find all of them or there is an error
      unsigned int segment_count =  GetNgonBoundarySegments(
        mesh_face_list,
        (unsigned int)ngon_fi_count,
        ngon_fi,
        ngon_boundary_segment_count,
        ngon_nbr_map.Array(),
        0,
        &ngon_boundary_sides
        );
      if ( segment_count <= 0 )
        break; //error

      ngon_boundary_segment_count += segment_count;
      if ( ngon_boundary_segment_count >= boundary_edge_count )
        return ngon_boundary_sides.UnsignedCount(); // found them all
    }

    break; // error if we get here
  }

  // failure
  ngon_boundary_sides.SetCount(0);

  return 0;
}

unsigned int ON_Mesh::GetNgonOuterBoundary(
    unsigned int ngon_fi_count,
    const unsigned int* ngon_fi,
    ON_SimpleArray<unsigned int>& ngon_vi
    ) const
{
  ON_3dPointListRef mesh_vertex_list;
  ON_MeshFaceList mesh_face_list;
  mesh_vertex_list.SetFromMesh(this);
  mesh_face_list.SetFromMesh(this);
  return ON_MeshNgon::FindNgonOuterBoundary(mesh_vertex_list,mesh_face_list,0,ngon_fi_count,ngon_fi,ngon_vi);
}

static 
double Internal_NgonBoundaryArea(
  const ON_Plane& ngon_plane,
  const ON_3dPointListRef& vertex_list,
  const ON_SimpleArray< unsigned int >& ngon_vi
)
{
  ON_2dPoint p0, p1;
  const unsigned int count = ngon_vi.UnsignedCount();
  if (count < 3)
    return 0.0;
  if (false == ngon_plane.ClosestPointTo(vertex_list[ngon_vi[count-1]], &p1.x, &p1.y))
    return 0.0;
  double twice_area = 0.0;
  for (unsigned int i = 0; i < count; i++)
  {
    p0 = p1;
    if (false == ngon_plane.ClosestPointTo(vertex_list[ngon_vi[i]], &p1.x, &p1.y))
      return 0.0;
    twice_area += (p0.x - p1.x)*(p0.y + p1.y);
  }
  return fabs(0.5*twice_area);
}

unsigned int ON_MeshNgon::FindPlanarNgons(
  const ON_3dPointListRef& vertex_list,
  const ON_MeshFaceList& face_list,
  const unsigned int *const* vertex_face_map,
  double planar_tolerance,
  unsigned int minimum_ngon_vertex_count,
  unsigned int minimum_ngon_face_count,
  bool bAllowHoles,
  ON_MeshNgonAllocator& NgonAllocator,
  ON_SimpleArray<unsigned int>& NgonMap,
  ON_SimpleArray<ON_MeshNgon*>& Ngons
  )
{
  const unsigned int NgonsCount0 = Ngons.UnsignedCount();
  const unsigned int vertex_count = vertex_list.PointCount();
  const unsigned int face_count = face_list.FaceCount();
  unsigned int ngon_boundary_index = 0;
  const bool bQuadFaces = (4 == face_list.FaceVertexCount());

  ON_MeshVertexFaceMap ws_vfmap;

  ON_MeshNgon* ngon;
  unsigned int Fvi[4];
  unsigned int face_index, ngon_boundary_edge_count;
  ON_SimpleArray<unsigned int> ngon_vi;
  ON_SimpleArray<unsigned int> ngon_fi;
  ON_SimpleArray<struct NgonNeighbors> ngon_nbr_map;
  ON_PlaneEquation e;

  bool bCleanUpNgonMap = false;

  for (;;)
  {
    if ( face_count <= 0 || face_count != NgonMap.UnsignedCount() )
      NgonMap.SetCount(0);

    if ( face_count <= 0 || vertex_count <= 0 )
      break;
   
    if ( 0 == vertex_face_map )
    {
      if ( !ws_vfmap.SetFromFaceList(vertex_count,face_list,false) )
        break;
      vertex_face_map = ws_vfmap.VertexFaceMap();
      if ( 0 == vertex_face_map )
        break;
    }

    ngon_vi.Reserve(128);
    ngon_fi.Reserve(128);
    ngon_nbr_map.Reserve(128);

    unsigned int* ngonMap = NgonMap.Array();
    if ( face_count != NgonMap.UnsignedCount() )
    {
      NgonMap.Reserve(face_count);
      NgonMap.SetCount(face_count);
      ngonMap = NgonMap.Array(); // in case a reallocation occured.
      for ( face_index = 0; face_index < face_count; face_index++ )
        ngonMap[face_index] = ON_UNSET_UINT_INDEX;
    }

    int ngonIndex = NgonsCount0;
    unsigned int ngonUnsetIndex;
    const unsigned int omitted_face_mark = ON_UNSET_UINT_INDEX - 1;

    for ( face_index = 0; face_index < face_count; face_index++ )
    {
      if ( ON_UNSET_UINT_INDEX != ngonMap[face_index] )
        continue; // this face is not eligable for being in an n-gon

      face_list.QuadFvi(face_index,Fvi);
      if ( !GetFacePlaneEquation(vertex_list,bQuadFaces,Fvi,planar_tolerance,e) )
        continue; // degenerate face

      for (;;)
      {
        ngon = 0;
        ngonUnsetIndex = ON_UNSET_UINT_INDEX;

        // Note:
        //   ngon_boundary_count includes interior edges when the n-gon has holes
        ngon_boundary_edge_count = GetCoplanarConnectedFaces(
          vertex_list,
          face_list,
          ngonIndex, ngonMap, vertex_face_map,
          planar_tolerance, face_index, e, 
          ngon_fi,
          ngon_nbr_map
          );

        if ( ngon_boundary_edge_count < minimum_ngon_vertex_count )
          break;

        if ( ngon_fi.UnsignedCount() < minimum_ngon_face_count )
          break;

        ngon_boundary_index++;
        ngon_vi.SetCount(0);
        if ( !GetNgonBoundarySegments(face_list,
                            ngon_fi.UnsignedCount(),ngon_fi.Array(),
                            ngon_boundary_index,
                            ngon_nbr_map.Array(),
                            &ngon_vi,
                            0
                            ) 
           )
          break;

        if ( ngon_vi.UnsignedCount() < minimum_ngon_vertex_count )
          break;

        if ( ngon_vi.UnsignedCount() < ngon_boundary_edge_count )
        {
          // ngon has holes.
          // The boundary with the largest area is the outer boundary.
          const ON_Plane ngon_plane(vertex_list[ngon_vi[0]], e.UnitNormal());

          double ngon_vi_area = Internal_NgonBoundaryArea(ngon_plane, vertex_list, ngon_vi);

          for(;;)
          {
            ON_SimpleArray<unsigned int> ngon_vi1;
            ngon_boundary_index++;
            if ( !GetNgonBoundarySegments(face_list,
                                ngon_fi.UnsignedCount(),ngon_fi.Array(),
                                ngon_boundary_index,
                                ngon_nbr_map.Array(),
                                &ngon_vi1, 0) 
               )
              break;
            if ( ngon_vi1.UnsignedCount() < 3 )
              break;
            double ngon_vi1_area = Internal_NgonBoundaryArea(ngon_plane, vertex_list, ngon_vi1);
            if (ngon_vi1_area > ngon_vi_area)
            {
              ngon_vi_area = ngon_vi1_area;
              ngon_vi = ngon_vi1;
            }
          }

          if (false == bAllowHoles)
          {
            ngonUnsetIndex = omitted_face_mark;
            bCleanUpNgonMap = true;
            break;
          }
        }

        ngon = NgonAllocator.AllocateNgon(ngon_vi.UnsignedCount(),ngon_fi.UnsignedCount());
        if ( 0 == ngon )
          break;

        memcpy(ngon->m_vi,ngon_vi.Array(),ngon->m_Vcount*sizeof(ngon->m_vi[0]));
        memcpy(ngon->m_fi,ngon_fi.Array(),ngon->m_Fcount*sizeof(ngon->m_fi[0]));
        break; // finished new ngon
      }

      if (0 == ngon)
      {
        // undo any modifications that were made to ngonMap[]
        UnsetNgonMap(ngon_fi.Array(),ngon_fi.UnsignedCount(),ngonIndex,ngonUnsetIndex,ngonMap);
        continue;
      }

      // found a new ngon
      Ngons.Append(ngon);
      ngonIndex++;
    }
    if (bCleanUpNgonMap)
    {
      for (face_index = 0; face_index < face_count; face_index++)
      {
        if (omitted_face_mark == ngonMap[face_index])
          ngonMap[face_index] = ON_UNSET_UINT_INDEX;
      }
    }
    break;
  }



  return Ngons.UnsignedCount() - NgonsCount0;
}

unsigned int ON_Mesh::AddPlanarNgons(
  const unsigned int *const* vertex_face_map,
  double planar_tolerance,
  unsigned int minimum_ngon_vertex_count,
  unsigned int minimum_ngon_face_count,
  bool bAllowHoles
  )
{
  const ON_3dPointListRef vertex_list(this);
  const ON_MeshFaceList face_list(this);

  unsigned int added_ngon_count = ON_MeshNgon::FindPlanarNgons(
    vertex_list,
    face_list,
    vertex_face_map,
    planar_tolerance,
    minimum_ngon_vertex_count,
    minimum_ngon_face_count,
    bAllowHoles,
    m_NgonAllocator,
    m_NgonMap,
    m_Ngon
    );

  return added_ngon_count;
}

class ON_VertexIndexRef
{
public:
  const unsigned int* m_vertex_index_ptr; // address of a unsigned int vertex index
  unsigned int m_old_vertex_index;
  unsigned int m_new_vertex_index; // new vertex index value
};

class /* DO NOT EXPORT THIS CLASS OR PUT IT IN A HEADER FILE */ ON_MeshSeparateNgonInfo
{
  // This class is used to pass around information needed
  // by the functions used by ON_Mesh::SeparateNgons()
  // to duplicated shared vertices.
public:
  ON_MeshSeparateNgonInfo(
    ON_Mesh* mesh,
    unsigned int** vertex_face_map
    )
    : m_mesh(mesh)
    , m_vertex_face_map(vertex_face_map)
    , m_face_count(mesh ? mesh->FaceUnsignedCount() : 0)
    , m_vertex_count0(mesh ? mesh->VertexUnsignedCount() : 0)
    , m_vertex_count1(m_vertex_count0)
    , m_vertex_count2(m_vertex_count0)
  {
    if ( 0 != mesh )
    {
      m_ngondex_from_facedex_map = ( m_face_count != mesh->m_NgonMap.UnsignedCount() )
        ? mesh->CreateNgonMap()
        : mesh->NgonMap();
    
      if ( 0 == m_vertex_face_map )
      {
        m_local_vfmap.SetFromMesh(mesh,true);
        m_vertex_face_map = const_cast<unsigned int**>(m_local_vfmap.VertexFaceMap());
      }
    }
  }

public:

  bool IsValid() const
  {
    if ( 0 == m_mesh )
      return false;
    if ( m_vertex_count0 <= 3 )
      return false;
    if ( m_face_count <= 1 )
      return false;
    if ( 0 == m_vertex_face_map )
      return false;
    if ( 0 == m_ngondex_from_facedex_map )
      return false;
    if ( 0 == m_ngondex_from_facedex_map )
      return false;
    return true;
  }

  // After SetNgon() sets the ngon, 
  bool FindSharedNgonVertices(
    unsigned int ngon_index
    );

  // FindSharedNgonVertices() uses TestNgonVertex()
  bool TestNgonVertex(
    const unsigned int* vertex_index_ptr
    );

  void DuplicateSharedNgonVertices();

   bool AddVertexIndexRef(
    const unsigned int* vertex_index_ptr,
    unsigned int new_vertex_index
    )
  {
    if ( 0 == vertex_index_ptr )
      return false;
    unsigned int old_vertex_index = *vertex_index_ptr;
    if ( old_vertex_index >= m_vertex_count0 )
      return false;
    if ( new_vertex_index < m_vertex_count0 )
      return false;
    if ( 0 == m_virefpool.SizeofElement() )
      m_virefpool.Create(sizeof(ON_VertexIndexRef),0,0);
    ON_VertexIndexRef* viref = (ON_VertexIndexRef*)m_virefpool.AllocateElement();
    if ( 0 == viref )
      return false;
    viref->m_vertex_index_ptr = vertex_index_ptr;
    viref->m_old_vertex_index = old_vertex_index;
    viref->m_new_vertex_index = new_vertex_index;
    return true;
  }

  ON_Mesh* m_mesh = nullptr;
  unsigned int** m_vertex_face_map = nullptr;
  const unsigned int* m_ngondex_from_facedex_map = nullptr;
  unsigned int* m_ngon_vmap = nullptr;
  const unsigned int m_face_count;    // mesh face count
  const unsigned int m_vertex_count0; // original vertex count
  unsigned int m_vertex_count1 = 0;
  unsigned int m_vertex_count2 = 0;
  unsigned int m_ni = ON_UNSET_UINT_INDEX;
  unsigned int m_fi = ON_UNSET_UINT_INDEX;
  unsigned int m_vi= ON_UNSET_UINT_INDEX;
  unsigned int m_ngon_vmark = 0;
  const ON_MeshNgon* m_ngon = nullptr;

private:
  ON_SimpleArray<unsigned int> m_ngon_vmap_buffer;
  ON_MeshVertexFaceMap m_local_vfmap;
  ON_FixedSizePool m_virefpool;
};

void ON_MeshSeparateNgonInfo::DuplicateSharedNgonVertices()
{
  unsigned int vertex_count = m_mesh->VertexUnsignedCount();
  ON_FixedSizePoolIterator fit(m_virefpool);
  for ( ON_VertexIndexRef* vr = (ON_VertexIndexRef*)fit.FirstElement(); 
        0 != vr; 
        vr = (ON_VertexIndexRef*)fit.NextElement() 
      )
  {
    if ( 0 == vr->m_vertex_index_ptr )
      continue;
    if ( vr->m_old_vertex_index != *vr->m_vertex_index_ptr )
      continue;
    if ( vr->m_old_vertex_index >= m_vertex_count0 )
      continue;
    if ( vr->m_new_vertex_index < m_vertex_count0 )
      continue;
    if ( vr->m_new_vertex_index > vertex_count )
      continue;
    if ( vr->m_new_vertex_index == vertex_count )
    {
      if ( vertex_count != m_mesh->AppendDuplicateVertex(vr->m_old_vertex_index) )
        return;
      vertex_count++;
    }
    *const_cast<unsigned int*>(vr->m_vertex_index_ptr) = vr->m_new_vertex_index;
  }
}

bool ON_MeshSeparateNgonInfo::TestNgonVertex(
  const unsigned int* vertex_index_ptr
  )
{
  if ( 0 == vertex_index_ptr )
    return false;
  m_vi = *vertex_index_ptr;
  if ( m_vi >= m_vertex_count0 )
  {
    // The source of this vertex index contained a bogus value.
    return true;
  }

  const unsigned int n = m_ngon_vmap[m_vi];
  
  if ( m_ngon_vmark == n )
  {
    // This vertex is known to be referenced only
    // by this ngon and does not need to be tested
    // or duplicated.
    return true;
  }

  if ( m_vertex_count1 <= n && n < m_vertex_count2 )
  {
    // this vertex is flagged for duplication in this ngon.
    return AddVertexIndexRef(vertex_index_ptr,n);
  }

  unsigned int* vfm = m_vertex_face_map[m_vi];
  const unsigned int vfm_count0 = (0 != vfm) ? *vfm++ : 0;
  if ( vfm_count0 > 0 )
  {
    for ( unsigned int vfmi = 0; vfmi < vfm_count0; vfmi++ )
    {
      unsigned int vfi = vfm[vfmi];
      if( m_fi == vfi )
        continue;
      if ( vfi >= m_face_count )
        continue;
      if ( m_ni == m_ngondex_from_facedex_map[vfi] )
        continue; // face[vfi] is in this ngon

      // this vertex is shared by a face not belonging to this ngon.
      if ( false == AddVertexIndexRef(vertex_index_ptr,m_vertex_count2) )
        return false;
      m_ngon_vmap[m_vi] = m_vertex_count2;
      m_vertex_count2++;

      // Any faces in this ngon that reference vertex m_vi
      // will be modified to reference vertex m_vertex_count2.
      // These faces need to be removed from vfm[], so
      // future searches for faces that share vertex m_vi
      // will be correct.
      unsigned int vfm_count1 = 0;
      for ( vfmi = 0; vfmi < vfm_count0; vfmi++ )
      {
        unsigned int vfi_local = vfm[vfmi];
        if( m_fi == vfi_local )
          continue;
        if ( vfi_local < m_face_count && m_ni == m_ngondex_from_facedex_map[vfi_local] )
          continue; // face[vfi_local] is in this ngon
        if ( vfmi > vfm_count1 )
          vfm[vfm_count1] = vfi_local;
        vfm_count1++;
      }
      vfm[-1] = vfm_count1;
      return true;
    }
  }

  // vertex is not shared
  m_ngon_vmap[m_vi] = m_ngon_vmark;
  return true;
}

bool ON_MeshSeparateNgonInfo::FindSharedNgonVertices(
  unsigned int ngon_index
  )
{
  m_ni = ngon_index;
  m_fi = ON_UNSET_UINT_INDEX;
  m_vi = ON_UNSET_UINT_INDEX;
  m_ngon_vmark = 0;
  m_ngon = m_mesh ? m_mesh->Ngon(m_ni) : 0;

  if ( 0 == m_ngon )
    return true;
  if ( m_ngon->m_Vcount <= 0 && m_ngon->m_Fcount <= 0 )
    return true;
  if ( m_ngon->m_Vcount > 0 && 0 == m_ngon->m_vi )
    return true;
  if ( m_ngon->m_Fcount > 0 && 0 == m_ngon->m_fi )
    return true;

  unsigned int prev_ngon_mark = m_ngon_vmark;
  m_ngon_vmark = m_ni+1 < m_vertex_count0 ? (m_ni+1) : 1;
  if ( 0 == m_ngon_vmap || prev_ngon_mark >= m_ngon_vmark )
  {
    if ( 0 == m_ngon_vmap )
    {
      m_ngon_vmap_buffer.Reserve(m_vertex_count0);
      m_ngon_vmap_buffer.SetCount(m_vertex_count0);
      m_ngon_vmap = m_ngon_vmap_buffer.Array();
    }
    m_ngon_vmap_buffer.Zero();    
  }

  m_vertex_count2 = m_vertex_count1;

  for ( unsigned int k = 0; k < m_ngon->m_Fcount; k++ )
  {
    m_fi = m_ngon->m_fi[k];
    if ( m_fi >= m_face_count )
      continue; // bogus face index

    const unsigned int* fvi = (const unsigned int*)m_mesh->m_F[m_fi].vi;
    for ( unsigned int j = 0; j < 4; j++ )
    {
      if ( false == TestNgonVertex(fvi+j) )
        return false;
    }
  }

  m_fi = ON_UNSET_UINT_INDEX;

  for ( unsigned k = 0; k < m_ngon->m_Vcount; k++ )
  {
    if ( false == TestNgonVertex(m_ngon->m_vi + k) )
      return false;
  }

  m_vertex_count1 = m_vertex_count2;

  return true;
}

bool ON_Mesh::SeparateNgons(
  unsigned int** vertex_face_map,
  unsigned int ngon_index0,
  unsigned int ngon_index1
  )
{
  if ( ngon_index1 > NgonUnsignedCount() )
    ngon_index1 = NgonUnsignedCount();
  if ( ngon_index1 <= ngon_index0 )
    return false;

  ON_MeshSeparateNgonInfo X(this,vertex_face_map);
  if ( false == X.IsValid() )
    return false;

  // Count number of new vertices that are required
  // to separate the ngons and save a list of what
  // needs to be updated in "X".  This loop modifies
  // vertex_face_map[] by removing references
  // to faces of vertices that will be duplicated.
  for ( unsigned int ni = ngon_index0; ni < ngon_index1; ni++ )
  {
    if( false == X.FindSharedNgonVertices(ni) )
      return 0;
  }

  // Add new vertices to ngons.
  if ( X.m_vertex_count1 > X.m_vertex_count0 )
  {
    // Duplicate the shared vertices and update the
    // ngon and face vertex index lists to reference
    // the duplicated vertices.
    X.DuplicateSharedNgonVertices();
  }

  return (VertexUnsignedCount() > X.m_vertex_count0);
}


bool ON_Mesh::SetNgonVertexNormals(
  unsigned int ngon_index0,
  unsigned int ngon_index1
  )
{
  if ( false == HasVertexNormals() )
    return false;

  if ( ngon_index1 > NgonUnsignedCount() )
    ngon_index1 = NgonUnsignedCount();
  if ( ngon_index1 <= ngon_index0 )
    return false;

  ON_3dPointListRef vertex_list(this);
  ON_MeshFaceList face_list(this);
  
  unsigned int ni, nvi, vi, nfi, fi;
  const ON_MeshNgon* ngon;

  const unsigned int vertex_count0 = vertex_list.PointCount();
  const unsigned int face_count0 = face_list.FaceCount();

  ON_3dVector dN;
  ON_3fVector fN;
  bool rc = false;

  for ( ni = ngon_index0; ni < ngon_index1; ni++ )
  {
    ngon = m_Ngon[ni];
    if ( 0 == ngon )
      continue;

    // validate ngon
    if ( ngon->m_Vcount > 0 && 0 == ngon->m_vi )
      continue;
    if ( ngon->m_Fcount <= 0 || 0 == ngon->m_fi )
      continue;
      
    // Get ngon plane equation and normal.
    fi = ngon->m_fi[0];
    if ( fi >= face_count0 )
      continue;
    if ( !m_F[fi].ComputeFaceNormal(vertex_list,dN) )
      continue;

    fN = dN;

    // update boundary vertex normals
    for ( nvi = 0; nvi < ngon->m_Vcount; nvi++ )
    {
      vi = ngon->m_vi[nvi];
      if ( vi >= vertex_count0 )
        continue;
      m_N[vi] = fN;
      rc = true;
    }

    // update face vertex normals
    for (nfi = 0; nfi < ngon->m_Fcount; nfi++ )
    {
      fi = ngon->m_fi[nfi];
      if ( fi >= face_count0 )
        continue;
      unsigned int* fvi = (unsigned int*)m_F[fi].vi;
      for ( unsigned int j = 0; j < 4; j++ )
      {
        vi = fvi[j];
        if ( vi >= vertex_count0 )
          continue;
        rc = true;
        m_N[vi] = fN;
      }
    }
  }

  return rc;
}

bool ON_Mesh::RemoveNgonInteriorVertices(
  const unsigned int *const* vertex_face_map,
  unsigned int ngon_index0,
  unsigned int ngon_index1
  )
{
  if ( ngon_index1 > NgonUnsignedCount() )
    ngon_index1 = NgonUnsignedCount();
  if ( ngon_index1 <= ngon_index0 )
    return false;

  ON_MeshVertexFaceMap m_local_vfmap;

  for ( unsigned int ni = ngon_index0; ni < ngon_index1; ni++ )
  {
  }

  return false;
}

unsigned int ON_MeshTopology::NgonIndexFromTopologyVertexIndex(
  unsigned int topvi,
  const unsigned int* mesh_facedex_to_ngondex_map
  ) const
{
  if ( topvi >= m_topv.UnsignedCount() )
    return ON_UNSET_UINT_INDEX;
  const ON_MeshTopologyVertex& v = m_topv[topvi];
  if ( v.m_tope_count <= 2 || 0 == v.m_topei )
    return ON_UNSET_UINT_INDEX;

  const ON_Mesh* mesh = m_mesh;
  if ( 0 == mesh || false == mesh->HasNgons() )
    return ON_UNSET_UINT_INDEX;
  if ( 0 == mesh_facedex_to_ngondex_map )
  {
    mesh_facedex_to_ngondex_map = mesh->NgonMap();
    if ( 0 == mesh_facedex_to_ngondex_map )
      return ON_UNSET_UINT_INDEX;
  }
  
  const unsigned int ngon_index = NgonIndexFromTopologyEdgeIndex((unsigned int)(v.m_topei[0]),mesh_facedex_to_ngondex_map);
  if ( ON_UNSET_UINT_INDEX == ngon_index )
    return ON_UNSET_UINT_INDEX;
  for ( int tvtei = 1; tvtei < v.m_tope_count; tvtei++ )
  {
    if ( ngon_index != NgonIndexFromTopologyEdgeIndex((unsigned int)(v.m_topei[tvtei]),mesh_facedex_to_ngondex_map) )
      return ON_UNSET_UINT_INDEX;
  }
  return ngon_index;
}

unsigned int ON_MeshTopology::NgonIndexFromTopologyEdgeIndex(
  unsigned int topei,
  const unsigned int* mesh_facedex_to_ngondex_map
  ) const
{
  if ( topei >= m_tope.UnsignedCount() )
    return ON_UNSET_UINT_INDEX;
  const ON_MeshTopologyEdge& e = m_tope[topei];
  if ( e.m_topf_count <= 0 || 0 == e.m_topfi )
    return ON_UNSET_UINT_INDEX;
  const ON_Mesh* mesh = m_mesh;
  if ( 0 == mesh || false == mesh->HasNgons() )
    return ON_UNSET_UINT_INDEX;
  if ( 0 == mesh_facedex_to_ngondex_map )
  {
    mesh_facedex_to_ngondex_map = mesh->NgonMap();
    if ( 0 == mesh_facedex_to_ngondex_map )
      return ON_UNSET_UINT_INDEX;
  }
  const unsigned int mesh_face_count = mesh->FaceUnsignedCount();

  const ON_MeshNgon* ngon = 0;
  unsigned int ngon_index = ON_UNSET_UINT_INDEX;
  unsigned int rc = ON_UNSET_UINT_INDEX;
  for ( int tefi = 0; tefi < e.m_topf_count; tefi++ )
  {
    unsigned int fi = (unsigned int)e.m_topfi[tefi];
    if ( fi >= mesh_face_count )
    {
      // bogus face index
      return ON_UNSET_UINT_INDEX;
    }

    if ( 0 != ngon && ngon_index == mesh_facedex_to_ngondex_map[fi] )
    {
      // this edge has 2 or more faces and all the faces 
      // that have been checked belong to the same ngon.
      rc = ngon_index;
      continue;
    }

    if ( 0 != ngon || ON_UNSET_UINT_INDEX == mesh_facedex_to_ngondex_map[fi] )
    {
      // this edge is not interior to the same ngon
      return ON_UNSET_UINT_INDEX;
    }

    ngon_index = mesh_facedex_to_ngondex_map[fi];
    ngon = mesh->Ngon(ngon_index);
    if ( 0 == ngon )
    {
      // bogus ngon information - let user pick this edge
      return ON_UNSET_UINT_INDEX;
    }
  }
  return rc;
}


ON_Plane ON_Plane::FromPointList(
  size_t point_list_count,
  const ON_3dPoint* point_list
  )
{
  ON_3dPointListRef vertex_list;
  vertex_list.SetFromDoubleArray(point_list_count,3,(const double*)point_list);
  return ON_Plane::FromPointList(vertex_list);
}

ON_Plane ON_Plane::FromPointList(
  const ON_SimpleArray< ON_3dPoint >& point_list
  )
{
  return ON_Plane::FromPointList(point_list.UnsignedCount(),point_list.Array());
}


ON_Plane ON_Plane::FromPointList(
  size_t point_list_count,
  const ON_3fPoint* point_list
  )
{
  ON_3dPointListRef vertex_list;
  vertex_list.SetFromFloatArray(point_list_count,3,(const float*)point_list);
  return ON_Plane::FromPointList(vertex_list);
}

ON_Plane ON_Plane::FromPointList(
  const ON_SimpleArray< ON_3fPoint >& point_list
  )
{
  return ON_Plane::FromPointList(point_list.UnsignedCount(),point_list.Array());
}


ON_Plane ON_Plane::FromPointList(
  const class ON_3dPointListRef& point_list
  )
{
  return ON_Plane::FromPointList(point_list.PointCount(),0,point_list);
}

ON_Plane ON_Plane::FromPointList(
  size_t point_index_count,
  const unsigned int* point_index_list,
  const class ON_3dPointListRef& point_list
  )
{
  const size_t point_index_stride = (0 != point_index_list) ? 1 : 0;

  return ON_Plane::FromPointList(
    point_index_count,
    point_index_stride,
    point_index_list,
    point_list
    );
}

ON_Plane ON_Plane::FromPointList(
  size_t point_index_count,
  size_t point_index_stride,
  const unsigned int* point_index_list,
  const class ON_3dPointListRef& point_list
  )
{
  unsigned int i;
  ON_3dPoint Pi, Pi0, Pi1;

  //////////////////////////////////////////////////////////
  //
  // Do not tolerate garbage.
  //
  if ( point_index_count < 3 || point_index_count >= (size_t)ON_UNSET_UINT_INDEX )
    return ON_Plane::UnsetPlane;

  if ( 0 != point_index_list )
  {
    if ( point_index_stride < 1 || point_index_stride >= (size_t)ON_UNSET_UINT_INDEX )
      return ON_Plane::UnsetPlane;
  }

  const unsigned int point_count = (unsigned int)point_index_count;
  const unsigned int point_stride = (0 != point_index_list ) ? ((unsigned int)point_index_stride) : 1;
  const unsigned int vertex_count = point_list.PointCount();
  
  if ( vertex_count < point_count )
    return ON_Plane::UnsetPlane;

  if ( point_index_list )
  {
    for ( i = 0; i < point_count; i += point_stride )
    {
      if ( point_index_list[i] >= vertex_count )
        return ON_Plane::UnsetPlane;
    }
  }

  i = point_stride*(point_count-1);
  Pi = point_list[point_index_list ? point_index_list[i] : i];
  for ( i = 0; i < point_count; i += point_stride )
  {
    Pi0 = Pi;
    Pi = point_list[point_index_list ? point_index_list[i] : i];
    if ( false == Pi.IsValid() )
      return ON_Plane::UnsetPlane;
    if ( false == (Pi0 != Pi) )
      return ON_Plane::UnsetPlane;
  }

  //////////////////////////////////////////////////////////
  //
  // Find a plane to project the 3d points to.
  //
  ON_3dVector N(ON_3dVector::UnsetVector);
  ON_3dVector X(ON_3dVector::UnsetVector);
  if ( point_index_count <= 4 )
  {
    // use "standard" face normal for quads and triangles.
    const unsigned int index_0123[4] = {0,1,2,3};
    if ( 0 == point_index_list )
      point_index_list = index_0123;
    Pi0 = point_list[point_index_list[point_stride*(point_count-1)]];
    Pi1 = point_list[point_index_list[point_stride]];
    X = Pi1 - Pi0;
    Pi0 = point_list[point_index_list[0]];
    Pi1 = point_list[point_index_list[2*point_stride]];
    N = ON_CrossProduct(
      X,
      Pi1-Pi0
    );
    if ( X.Length() < Pi1.DistanceTo(Pi0) )
      X = Pi1-Pi0;
  }
  else
  {
    // find far apart points
    unsigned int i0 = 0;
    unsigned int i1 = point_stride;
    ON_Line L(point_list[point_index_list ? point_index_list[i0] : i0],point_list[point_index_list ? point_index_list[i1] : i1]);
    double d = L.Length();
    for ( i = i1+point_stride; i < point_count*point_stride; i += point_stride )
    {
      Pi = point_list[point_index_list ? point_index_list[i] : i];
      double d0 = L.from.DistanceTo(Pi);
      double d1 = L.to.DistanceTo(Pi);
      if ( d0 > d && d0 >= d1 )
      {
        d = d0;
        i1 = i;
        L.to = Pi;
      }
      else if ( d1 > d )
      {
        d = d1;
        i0 = i;
        L.from = Pi;
      }
    }
    if ( i0 == i1 || !(d > 0.0) )
      return ON_Plane::UnsetPlane;
    X = L.Direction();

    d = 0.0;
    unsigned int i2 = ON_UNSET_UINT_INDEX;
    ON_3dPoint P(ON_3dPoint::UnsetPoint);
    for ( i = 0; i < point_count*point_stride; i += point_stride )
    {
      if ( i == i0 || i == i1 )
        continue;
      Pi = point_list[point_index_list ? point_index_list[i] : i];
      double t1 = ON_UNSET_VALUE;
      if ( !L.ClosestPointTo(Pi,&t1) || !(t1 != ON_UNSET_VALUE) )
        continue;    
      ON_3dPoint Q = L.PointAt(t1);
      double d1 = Q.DistanceTo(Pi);
      if ( d1 > d )
      {
        d = d1;
        P = Q;
        i2 = i;
      }
    }
    if ( ON_UNSET_UINT_INDEX == i2 || i0 == i2 || i1 == i2 || !(d > 0.0) || !P.IsValid() )
      return ON_Plane::UnsetPlane;
    Pi = point_list[point_index_list ? point_index_list[i2] : i2];
    N = ON_CrossProduct(X,Pi-P);
  }

  ON_Plane plane;

  if      ( 0.0 != N.x && 0.0 == N.y && 0.0 == N.z )
    i = 0;
  else if ( 0.0 == N.x && 0.0 != N.y && 0.0 == N.z )
    i = 1;
  else if ( 0.0 == N.x && 0.0 == N.y && 0.0 != N.z )
    i = 2;
  else
    i = 3;

  if ( i < 3 )
  {
    ON_3dVector a[3] = {ON_3dVector::XAxis,ON_3dVector::YAxis,ON_3dVector::ZAxis};
    plane.zaxis = a[i];
    plane.xaxis = a[(i+1)%3];
    plane.yaxis = a[(i+2)%3];
  }
  else
  {
    plane.zaxis = N;
    plane.xaxis = X;
    if ( !plane.zaxis.Unitize() )
      return ON_Plane::UnsetPlane;
    if ( !plane.xaxis.Unitize() )
      return ON_Plane::UnsetPlane;
    plane.yaxis = ON_CrossProduct(plane.zaxis,plane.xaxis);
  }
  plane.origin = point_list[point_index_list ? point_index_list[0] : 0];
  plane.UpdateEquation();
  if ( !plane.IsValid() )
    return ON_Plane::UnsetPlane;
  
  return plane;
}




const ON_MeshNgonIterator ON_MeshNgonIterator::EmptyMeshNgonIterator;

ON_MeshNgonIterator::ON_MeshNgonIterator(
  const class ON_Mesh* mesh
  )
{
  if ( 0 != mesh )
    SetMesh(mesh,mesh->NgonMap());
}

ON_MeshNgonIterator::ON_MeshNgonIterator(
  const ON_MeshNgonIterator& src
  )
  : m_mesh(src.m_mesh)
  , m_facedex_to_ngondex_map(src.m_facedex_to_ngondex_map)
  , m_current_ngon_ci(src.m_current_ngon_ci)
  , m_mesh_face_count(src.m_mesh_face_count)
  , m_mesh_ngon_count(src.m_mesh_ngon_count)
  , m_iterator_index(src.m_iterator_index)
{
  if (    ON_COMPONENT_INDEX::mesh_face == m_current_ngon_ci.m_type 
       || ON_COMPONENT_INDEX::mesh_ngon == m_current_ngon_ci.m_type 
     )
  {
    if ( 0 != m_mesh )
      m_current_ngon = (ON__UINT_PTR)(m_mesh->NgonFromComponentIndex(m_ngon_buffer,m_current_ngon_ci));
  }
}

ON_MeshNgonIterator& ON_MeshNgonIterator::operator=(
  const ON_MeshNgonIterator& src
  )
{
  if ( this != &src )
  {
     m_mesh = src.m_mesh;
     m_facedex_to_ngondex_map = src.m_facedex_to_ngondex_map;
     m_current_ngon_ci = src.m_current_ngon_ci;
     m_current_ngon = 0;
     m_mesh_face_count = src.m_mesh_face_count;
     m_mesh_ngon_count = src.m_mesh_ngon_count;
     m_iterator_index = src.m_iterator_index;
    if (    ON_COMPONENT_INDEX::mesh_face == m_current_ngon_ci.m_type 
         || ON_COMPONENT_INDEX::mesh_ngon == m_current_ngon_ci.m_type 
       )
    {
      if ( 0 != m_mesh )
        m_current_ngon = (ON__UINT_PTR)(m_mesh->NgonFromComponentIndex(m_ngon_buffer,m_current_ngon_ci));
    }
  }
  return *this;
}

void ON_MeshNgonIterator::SetMesh(
  const class ON_Mesh* mesh,
  const unsigned int* meshfdex_to_meshngondex_map
  )
{
  *this = ON_MeshNgonIterator::EmptyMeshNgonIterator;
  m_mesh = mesh;
  m_facedex_to_ngondex_map = (0 != m_mesh && 0 == meshfdex_to_meshngondex_map)
                            ? m_mesh->NgonMap()
                            : meshfdex_to_meshngondex_map;
  if ( 0 != m_mesh )
  {
    m_mesh_face_count = m_mesh->FaceUnsignedCount();
    m_mesh_ngon_count = m_mesh->NgonCount();
    m_current_ngon_ci.Set(ON_COMPONENT_INDEX::invalid_type,1);
  }
}

const ON_Mesh* ON_MeshNgonIterator::Mesh() const
{
  return m_mesh;
}

const class ON_MeshNgon* ON_MeshNgonIterator::FirstNgon()
{
  m_current_ngon_ci.Set(ON_COMPONENT_INDEX::invalid_type,1);
  m_current_ngon = 0;
  m_iterator_index = 0;
  return NextNgon();
}

const class ON_MeshNgon* ON_MeshNgonIterator::NextNgon()
{
  unsigned int fi, ni;
  
  const class ON_MeshNgon* ngon = 0;
  switch( m_current_ngon_ci.m_type )
  {
  case ON_COMPONENT_INDEX::invalid_type:
    m_current_ngon = 0;
    if ( 1 != m_current_ngon_ci.m_index )
      break;
    m_current_ngon_ci.Set(ON_COMPONENT_INDEX::mesh_ngon,-1);
    m_iterator_index = ON_UNSET_UINT_INDEX;
    // no break here - iterator will look through ngon list

  case ON_COMPONENT_INDEX::mesh_ngon:
    for ( ni = ( -1 == m_current_ngon_ci.m_index ) ? 0 : m_current_ngon_ci.UnsignedIndex()+1;
          ni < m_mesh_ngon_count;
          ni++
        )
    {
      ngon = m_mesh->Ngon(ni);
      if ( 0 == ngon )
        continue;
      if ( ON_UNSET_UINT_INDEX == m_iterator_index )
        m_iterator_index = 0;
      else
        m_iterator_index++;
      m_current_ngon_ci.m_index = (int)ni;
      m_current_ngon = (ON__UINT_PTR)ngon;
      return ngon;
    }
    m_current_ngon_ci.Set(ON_COMPONENT_INDEX::mesh_face,-1);
    // no break here - iterator will look for mesh faces that are not part of ngons

  case ON_COMPONENT_INDEX::mesh_face:
    for ( fi = ( -1 == m_current_ngon_ci.m_index ) ? 0 : m_current_ngon_ci.UnsignedIndex()+1;
          fi < m_mesh_face_count;
          fi++
        )
    {
      ni = m_facedex_to_ngondex_map ? m_facedex_to_ngondex_map[fi] : ON_UNSET_UINT_INDEX;
      if ( ni < m_mesh_ngon_count )
        continue;
      ngon = ON_MeshNgon::NgonFromMeshFace(m_ngon_buffer,fi,(const unsigned int*)m_mesh->m_F[fi].vi);
      if ( 0 == ngon )
        continue;
      if ( ON_UNSET_UINT_INDEX == m_iterator_index )
        m_iterator_index = 0;
      else
        m_iterator_index++;
      m_current_ngon_ci.m_index = (int)fi;
      m_current_ngon = (ON__UINT_PTR)ngon;
      return ngon;
    }
    // no break here - iterator is finished

  default:
    m_current_ngon_ci.Set(ON_COMPONENT_INDEX::invalid_type,0);
    m_current_ngon = 0;
  }

  return ngon;
}
  
const class ON_MeshNgon* ON_MeshNgonIterator::CurrentNgon()
{
  const class ON_MeshNgon* ngon = 0;
  if ( 0 != m_current_ngon )
  {
    ngon = (0 != m_mesh)
         ? m_mesh->NgonFromComponentIndex(m_ngon_buffer,m_current_ngon_ci)
         : 0;
    if ( m_current_ngon != (ON__UINT_PTR)ngon )
    {
      ngon = 0;
      m_current_ngon = 0;
    }
  }
  return ngon;
}

ON_COMPONENT_INDEX ON_MeshNgonIterator::CurrentNgonComponentIndex() const
{
  return m_current_ngon_ci;
}

bool ON_MeshNgonIterator::CurrentNgonIsMeshFace() const
{
  return (    ON_COMPONENT_INDEX::mesh_face == m_current_ngon_ci.m_type 
           && m_current_ngon == (ON__UINT_PTR)&m_ngon_buffer 
         );
}

bool ON_MeshNgonIterator::CurrentNgonIsMeshNgon() const
{
  return (   0 != m_current_ngon 
          && 0 != m_mesh 
          && ON_COMPONENT_INDEX::mesh_ngon == m_current_ngon_ci.m_type
          && m_current_ngon == (ON__UINT_PTR)m_mesh->Ngon(m_current_ngon_ci.m_index)
          );
}
  
void ON_MeshNgonIterator::Reset()
{
  const ON_Mesh* mesh = m_mesh;
  const unsigned int* fdex_to_ndex_map = m_facedex_to_ngondex_map;
  SetMesh(mesh,fdex_to_ndex_map);
}


unsigned int ON_MeshNgonIterator::Count() const
{
	unsigned int count = 0;

  if (nullptr != m_mesh)
  {
    count = m_mesh->m_F.UnsignedCount();
    unsigned int explicit_ngon_count = m_mesh->NgonUnsignedCount();
    if (explicit_ngon_count > 0)
    {
      // subtract the number of mesh faces that are in explicit ngons from count.
      const ON_MeshNgon* const * ngons = m_mesh->Ngons();
      if (nullptr != ngons)
      {
        for (unsigned int ni = 0; ni < explicit_ngon_count; ni++)
        {
          const ON_MeshNgon* ngon = ngons[ni];
          if (nullptr == ngon)
            explicit_ngon_count--;
          else if (ngon->m_Fcount <= count)
            count -= ngon->m_Fcount;
          else
          {
            ON_ERROR("Invalid ngon information on mesh");
            return 0;
          }
        }
      }

      // add the number of explicit ngons to count
      count += explicit_ngon_count;
    }
  }

  // return the number of explicit ngons + the number of faces that are not in an explicit ngon.
	return count;
}
