#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

#include "opennurbs_subd_data.h"

/* $NoKeywords: $ */
/*
//
// Copyright (c) 1993-2015 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
*/

#if defined(OPENNURBS_SUBD_WIP)

void ON_SubDIncrementErrorCount()
{
  ON_SubD::ErrorCount++;
} 

ON_SubDComponentPtr::Type ON_SubDComponentPtr::ComponentPtrTypeFromUnsigned(
  unsigned int element_pointer_type_as_unsigned
  )
{
  switch (element_pointer_type_as_unsigned)
  {
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubDComponentPtr::Type::Unset);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubDComponentPtr::Type::Vertex);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubDComponentPtr::Type::Edge);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubDComponentPtr::Type::Face);
  }
  return ON_SUBD_RETURN_ERROR(ON_SubDComponentPtr::Type::Unset);
}

ON_SubD::VertexTag ON_SubD::VertexTagFromUnsigned(
  unsigned int vertex_tag_as_unsigned
  )
{
  switch (vertex_tag_as_unsigned)
  {
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::VertexTag::Unset);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::VertexTag::Smooth);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::VertexTag::Crease);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::VertexTag::Corner);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::VertexTag::Dart);
  }
  return ON_SUBD_RETURN_ERROR(ON_SubD::VertexTag::Unset);
}


bool ON_SubD::VertexTagIsSet(
  ON_SubD::VertexTag vertex_tag
)
{
  return (
    ON_SubD::VertexTag::Smooth == vertex_tag
    || ON_SubD::VertexTag::Crease == vertex_tag
    || ON_SubD::VertexTag::Corner == vertex_tag
    || ON_SubD::VertexTag::Dart == vertex_tag
    );
}


ON_SubD::EdgeTag ON_SubD::EdgeTagFromUnsigned(
  unsigned int edge_tag_as_unsigned
  )
{
  switch (edge_tag_as_unsigned)
  {
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::EdgeTag::Unset);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::EdgeTag::Smooth);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::EdgeTag::Crease);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::EdgeTag::Sharp);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::EdgeTag::X);
  }
  return ON_SUBD_RETURN_ERROR(ON_SubD::EdgeTag::Unset);
}

bool ON_SubD::EdgeTagIsSet(
  ON_SubD::EdgeTag edge_tag
)
{
  return (
    ON_SubD::EdgeTag::Smooth == edge_tag
    || ON_SubD::EdgeTag::Crease == edge_tag
    || ON_SubD::EdgeTag::Sharp == edge_tag
    || ON_SubD::EdgeTag::X == edge_tag
    );
}

ON_SubD::FacetType ON_SubD::FacetTypeFromUnsigned(
  unsigned int facet_type_as_unsigned
  )
{
  switch (facet_type_as_unsigned)
  {
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::FacetType::Unset);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::FacetType::Tri);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::FacetType::Quad);
  }
  return ON_SUBD_RETURN_ERROR(ON_SubD::FacetType::Unset);
}

//ON_SubD::VertexEdgeOrder ON_SubD::VertexEdgeOrderFromUnsigned(
//  unsigned int vertex_edge_order_as_unsigned
//  )
//{
//  switch (vertex_edge_order_as_unsigned)
//  {
//  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::VertexEdgeOrder::unset);
//  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::VertexEdgeOrder::radial);
//  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::VertexEdgeOrder::notradial);
//  }
//  return ON_SUBD_RETURN_ERROR(ON_SubD::VertexEdgeOrder::unset);
//}

ON_SubD::VertexFacetType ON_SubD::VertexFacetTypeFromUnsigned(
  unsigned int vertex_facet_type_as_unsigned
  )
{
  switch (vertex_facet_type_as_unsigned)
  {
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::VertexFacetType::Unset);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::VertexFacetType::Tri);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::VertexFacetType::Quad);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::VertexFacetType::Ngon);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::VertexFacetType::Mixed);
  }
  return ON_SUBD_RETURN_ERROR(ON_SubD::VertexFacetType::Unset);
}

ON_SubD::SubDType ON_SubD::SubDTypeFromUnsigned(
  unsigned int subd_type_as_unsigned
  )  
{
  switch (subd_type_as_unsigned)
  {
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::SubDType::Unset);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::SubDType::Custom);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::SubDType::TriLoopWarren);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::SubDType::QuadCatmullClark);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::SubDType::CustomTri);
  ON_ENUM_FROM_UNSIGNED_CASE(ON_SubD::SubDType::CustomQuad);
  }
  return ON_SUBD_RETURN_ERROR(ON_SubD::SubDType::Unset);
}

ON_SubD::SubDType ON_SubD::DefaultSubDType()
{
  return ON_SubD::SubDType::QuadCatmullClark;
}


unsigned int ON_SubD::FacetEdgeCount(
  ON_SubD::FacetType facet_type
  )
{
  if (ON_SubD::FacetType::Quad == facet_type)
    return 4;
  if (ON_SubD::FacetType::Tri == facet_type)
    return 3;
  return 0;
}

unsigned int ON_SubD::FacetEdgeCount(
  ON_SubD::SubDType subdivision_type
  )
{
  if (ON_SubD::SubDType::QuadCatmullClark == subdivision_type)
    return 4;
  if (ON_SubD::SubDType::TriLoopWarren == subdivision_type)
    return 3;
  return 0;

}

unsigned int ON_SubDSectorType::SectorPointRingCountFromEdgeCount(
  ON_SubD::SubDType subd_type,
  ON_SubD::VertexTag vertex_tag,
  unsigned int sector_edge_count
  )
{
  if (sector_edge_count >= ON_SubDSectorType::MinimumSectorEdgeCount(vertex_tag) && sector_edge_count <= ON_SubDVertex::MaximumEdgeCount)
  {
    if (ON_SubD::VertexTag::Smooth == vertex_tag || ON_SubD::VertexTag::Dart == vertex_tag)
    {
      // interior vertex
      if (ON_SubD::SubDType::QuadCatmullClark == subd_type)
        return (2 * sector_edge_count + 1);
      if (ON_SubD::SubDType::TriLoopWarren == subd_type)
        return (sector_edge_count + 1);
    }

    if (ON_SubD::VertexTag::Crease == vertex_tag || ON_SubD::VertexTag::Corner == vertex_tag)
    {
      // boundary vertex
      if (ON_SubD::SubDType::QuadCatmullClark == subd_type)
        return (2 * sector_edge_count);
      if (ON_SubD::SubDType::TriLoopWarren == subd_type)
        return (sector_edge_count + 1);
    }
  }
  return ON_SUBD_RETURN_ERROR(0);
}

unsigned int ON_SubDSectorType::SectorPointRingCountFromFaceCount(
  ON_SubD::SubDType subd_type,
  ON_SubD::VertexTag vertex_tag,
  unsigned int sector_face_count
  )
{
  unsigned int sector_edge_count = ON_SubDSectorType::SectorEdgeCountFromFaceCount(vertex_tag,sector_face_count);
  return (sector_edge_count > 0)
    ? ON_SubDSectorType::SectorPointRingCountFromEdgeCount(subd_type,vertex_tag,sector_edge_count)
    : ON_SUBD_RETURN_ERROR(0);
}

unsigned int ON_SubDSectorType::SectorFaceCountFromEdgeCount(
  ON_SubD::VertexTag vertex_tag,
  unsigned int sector_edge_count
  )
{
  if (sector_edge_count >= 2 && sector_edge_count <= ON_SubDVertex::MaximumEdgeCount)
  {
    unsigned int sector_face_count
      = (ON_SubD::VertexTag::Crease == vertex_tag || ON_SubD::VertexTag::Corner == vertex_tag)
      ? sector_edge_count-1
      : sector_edge_count;
    return sector_face_count;
  }
  return ON_SUBD_RETURN_ERROR(0);
}

unsigned int ON_SubDSectorType::SectorEdgeCountFromFaceCount(
  ON_SubD::VertexTag vertex_tag,
  unsigned int sector_face_count
  )
{
  if (sector_face_count > 0 && sector_face_count <= ON_SubDVertex::MaximumFaceCount)
  {
    unsigned int sector_edge_count
      = (ON_SubD::VertexTag::Crease == vertex_tag || ON_SubD::VertexTag::Corner == vertex_tag)
      ? sector_face_count+1
      : sector_face_count;
    return sector_edge_count;
  }
  return ON_SUBD_RETURN_ERROR(0);
}

unsigned int ON_SubDSectorType::MinimumSectorEdgeCount(
  ON_SubD::VertexTag vertex_tag
  )
{
  if (ON_SubD::VertexTag::Smooth == vertex_tag || ON_SubD::VertexTag::Dart == vertex_tag)
    return ON_SubDSectorType::MinimumSectorFaceCount(vertex_tag);
  if (ON_SubD::VertexTag::Corner == vertex_tag || ON_SubD::VertexTag::Crease == vertex_tag)
    return ON_SubDSectorType::MinimumSectorFaceCount(vertex_tag)+1;
  return ON_UNSET_UINT_INDEX;
}

unsigned int ON_SubDSectorType::MinimumSectorFaceCount(
  ON_SubD::VertexTag vertex_tag
  )
{
  if (ON_SubD::VertexTag::Smooth == vertex_tag || ON_SubD::VertexTag::Dart == vertex_tag)
    return 3; // can be reduced to 2 after calculating special case matrix and eigenvalues
  if (ON_SubD::VertexTag::Corner == vertex_tag)
    return 1;
  if (ON_SubD::VertexTag::Crease == vertex_tag)
    return 1;
  return ON_UNSET_UINT_INDEX;
}

bool ON_SubD::IsValidSectorEdgeCount(
  ON_SubD::VertexTag vertex_tag,
  unsigned int sector_edge_count
  )
{
  return (sector_edge_count >= ON_SubDSectorType::MinimumSectorEdgeCount(vertex_tag) && sector_edge_count <= ON_SubDVertex::MaximumEdgeCount);
}

bool ON_SubD::IsValidSectorFaceCount(
  ON_SubD::VertexTag vertex_tag,
  unsigned int sector_face_count
  )
{
  return (sector_face_count >= ON_SubDSectorType::MinimumSectorFaceCount(vertex_tag) && sector_face_count <= ON_SubDVertex::MaximumFaceCount);
}

bool ON_SubD::IsQuadOrTriFacetType(
  ON_SubD::FacetType facet_type
  )
{
  return (ON_SubD::FacetType::Quad == facet_type || ON_SubD::FacetType::Tri == facet_type);
}

bool ON_SubD::IsQuadOrTriSubDType(
  ON_SubD::SubDType subdivision_type
  )
{
  if (ON_SubD::SubDType::QuadCatmullClark == subdivision_type || ON_SubD::SubDType::CustomQuad == subdivision_type)
    return true;
  if (ON_SubD::SubDType::TriLoopWarren == subdivision_type || ON_SubD::SubDType::CustomTri == subdivision_type)
    return true;
  return false;
}

ON_SubD::FacetType ON_SubD::FacetTypeFromSubDType(
  ON_SubD::SubDType subdivision_type
  )
{
  if (ON_SubD::SubDType::QuadCatmullClark == subdivision_type || ON_SubD::SubDType::CustomQuad == subdivision_type)
    return ON_SubD::FacetType::Quad;
  if (ON_SubD::SubDType::TriLoopWarren == subdivision_type || ON_SubD::SubDType::CustomTri == subdivision_type)
    return ON_SubD::FacetType::Tri;
  return ON_SubD::FacetType::Unset;
}

bool ON_SubD::PointRingHasFacePoints(
  ON_SubD::SubDType subdivision_type
  )
{
  return (ON_SubD::SubDType::QuadCatmullClark == subdivision_type || ON_SubD::SubDType::CustomQuad == subdivision_type);
}

//////////////////////////////////////////////////////////////////////////
//
// ON_SubDVertexPtr
//
//
bool ON_SubDVertexPtr::IsNull() const
{
  return (nullptr == ON_SUBD_VERTEX_POINTER(m_ptr));
}

class ON_SubDVertex* ON_SubDVertexPtr::Vertex() const
{
  return ON_SUBD_VERTEX_POINTER(m_ptr);
}

ON__UINT_PTR ON_SubDVertexPtr::VertexPtrMark() const
{
  return ON_SUBD_VERTEX_MARK(m_ptr);
}

ON_ComponentStatus ON_SubDVertexPtr::Status() const
{
  const ON_SubDVertex* vertex = ON_SUBD_VERTEX_POINTER(m_ptr);
  return (nullptr == vertex) ? ON_ComponentStatus::NoneSet : vertex->m_status;
}

class ON_SubDVertexPtr ON_SubDVertexPtr::Create(
  const class ON_SubDVertex* vertex
  )
{
  return ON_SubDVertexPtr::Create(vertex,0);
}

class ON_SubDVertexPtr ON_SubDVertexPtr::Create(
  const class ON_SubDVertex* vertex,
  ON__UINT_PTR vertex_mark
  )
{
  ON_SubDVertexPtr vptr = { (ON__UINT_PTR)vertex | (vertex_mark & ON_SUBD_ELEMENT_MARK_MASK) };
  return vptr;
}

class ON_SubDVertexPtr ON_SubDVertexPtr::Create(
  const class ON_SubDComponentPtr& vertex_element
  )
{
  return ON_SubDVertexPtr::Create(vertex_element.Vertex(), vertex_element.ComponentMark());
}

//////////////////////////////////////////////////////////////////////////
//
// ON_SubDEdgePtr
//


bool ON_SubDEdgePtr::IsNull() const
{
  return (nullptr == ON_SUBD_EDGE_POINTER(m_ptr));
}

class ON_SubDEdge* ON_SubDEdgePtr::Edge() const
{
  return ON_SUBD_EDGE_POINTER(m_ptr);
}

ON__UINT_PTR ON_SubDEdgePtr::EdgeDirection() const
{
  return ON_SUBD_EDGE_DIRECTION(m_ptr);
}

ON_ComponentStatus ON_SubDEdgePtr::Status() const
{
  const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(m_ptr);
  return (nullptr == edge) ? ON_ComponentStatus::NoneSet : edge->m_status;
}

ON_SubDEdgePtr ON_SubDEdgePtr::Reversed() const
{
  return ON_SubDEdgePtr::Create(ON_SUBD_EDGE_POINTER(m_ptr), 1 - (m_ptr & 1));
}

class ON_SubDEdgePtr ON_SubDEdgePtr::Create(
  const class ON_SubDEdge* edge,
  ON__UINT_PTR direction
  )
{
  ON_SubDEdgePtr eptr = { (ON__UINT_PTR)edge | (direction & ON_SUBD_ELEMENT_MARK_MASK) };
  return eptr;
}

ON_SubDEdgePtr ON_SubDEdgePtr::Create(
  const class ON_SubDComponentPtr& edge_element
  )
{
  return ON_SubDEdgePtr::Create(edge_element.Edge(), edge_element.ComponentMark());
}

//////////////////////////////////////////////////////////////////////////
//
// ON_SubDFacePtr
//

bool ON_SubDFacePtr::IsNull() const
{
  return (nullptr == ON_SUBD_FACE_POINTER(m_ptr));
}

ON_SubDFace* ON_SubDFacePtr::Face() const
{
  return ON_SUBD_FACE_POINTER(m_ptr);
}

ON__UINT_PTR ON_SubDFacePtr::FaceDirection() const
{
  return ON_SUBD_FACE_DIRECTION(m_ptr);
}

ON_ComponentStatus ON_SubDFacePtr::Status() const
{
  const ON_SubDFace* face = ON_SUBD_FACE_POINTER(m_ptr);
  return (nullptr == face) ? ON_ComponentStatus::NoneSet : face->m_status;
}

ON_SubDFacePtr ON_SubDFacePtr::Create(
  const class ON_SubDFace* face,
  ON__UINT_PTR direction
  )
{
  ON_SubDFacePtr fptr = { (ON__UINT_PTR)face | (direction & ON_SUBD_ELEMENT_MARK_MASK) };
  return fptr;
}

ON_SubDFacePtr ON_SubDFacePtr::Create(
  const class ON_SubDComponentPtr& face_element
  )
{
  return ON_SubDFacePtr::Create(face_element.Face(), face_element.ComponentMark());
}


//////////////////////////////////////////////////////////////////////////
//
// ON_SubDComponentPtr
//

bool ON_SubDComponentPtr::IsNull() const
{
  return (0 == (ON_SUBD_ELEMENT_POINTER_MASK && m_ptr));
}

bool ON_SubDComponentPtr::IsNotNull() const
{
  if (nullptr != ON_SUBD_EDGE_POINTER(m_ptr))
  {
    switch (ON_SUBD_ELEMENT_TYPE_MASK & m_ptr)
    {
    case ON_SUBD_ELEMENT_TYPE_VERTEX:
    case ON_SUBD_ELEMENT_TYPE_EDGE:
    case ON_SUBD_ELEMENT_TYPE_FACE:
      return true;
    }
  }
  return false;
}

ON_SubDComponentPtr::Type ON_SubDComponentPtr::ComponentType() const
{
  switch (ON_SUBD_ELEMENT_TYPE_MASK & m_ptr)
  {
  case ON_SUBD_ELEMENT_TYPE_VERTEX:
    return ON_SubDComponentPtr::Type::Vertex;
  case ON_SUBD_ELEMENT_TYPE_EDGE:
    return ON_SubDComponentPtr::Type::Edge;
  case ON_SUBD_ELEMENT_TYPE_FACE:
    return ON_SubDComponentPtr::Type::Face;
  }
  return ON_SubDComponentPtr::Type::Unset;
}

ON__UINT_PTR ON_SubDComponentPtr::ComponentMark() const
{
  return ON_SUBD_ELEMENT_MARK(m_ptr);
}


ON_ComponentStatus ON_SubDComponentPtr::Status() const
{
  switch (ComponentType())
  {
  case ON_SubDComponentPtr::Type::Vertex:
    {
      const ON_SubDVertex* vertex = Vertex();
      if ( nullptr != vertex )
        return vertex->m_status;
    }
    break;
  case ON_SubDComponentPtr::Type::Edge:
    {
      const ON_SubDEdge* edge = Edge();
      if ( nullptr != edge )
        return edge->m_status;
    }
    break;
  case ON_SubDComponentPtr::Type::Face:
    {
      const ON_SubDFace* face = Face();
      if ( nullptr != face )
        return face->m_status;
    }
    break;
  }
  return ON_ComponentStatus::NoneSet;
}

unsigned int ON_SubDComponentPtr::SetStatus(
  ON_ComponentStatus status
  )
{
  switch (ComponentType())
  {
  case ON_SubDComponentPtr::Type::Vertex:
    {
      const ON_SubDVertex* vertex = Vertex();
      if (nullptr != vertex)
        return vertex->m_status.SetStatus(status);
    }
    break;
  case ON_SubDComponentPtr::Type::Edge:
    {
      const ON_SubDEdge* edge = Edge();
      if (nullptr != edge)
        return edge->m_status.SetStatus(status);
    }
    break;
  case ON_SubDComponentPtr::Type::Face:
    {
      const ON_SubDFace* face = Face();
      if (nullptr != face)
        return face->m_status.SetStatus(status);
    }
    break;
  }
  return ON_SUBD_RETURN_ERROR(0);
}


unsigned int ON_SubDComponentPtr::SetStates(
  ON_ComponentStatus states_to_set
  )
{
  switch (ComponentType())
  {
  case ON_SubDComponentPtr::Type::Vertex:
    {
      const ON_SubDVertex* vertex = Vertex();
      if (nullptr != vertex)
        return vertex->m_status.SetStates(states_to_set);
    }
    break;
  case ON_SubDComponentPtr::Type::Edge:
    {
      const ON_SubDEdge* edge = Edge();
      if (nullptr != edge)
        return edge->m_status.SetStates(states_to_set);
    }
    break;
  case ON_SubDComponentPtr::Type::Face:
    {
      const ON_SubDFace* face = Face();
      if (nullptr != face)
        return face->m_status.SetStates(states_to_set);
    }
    break;
  }
  return ON_SUBD_RETURN_ERROR(0);
}


unsigned int ON_SubDComponentPtr::ClearStates(
  ON_ComponentStatus states_to_clear
  )
{
  switch (ComponentType())
  {
  case ON_SubDComponentPtr::Type::Vertex:
    {
      const ON_SubDVertex* vertex = Vertex();
      if (nullptr != vertex)
        return vertex->m_status.ClearStates(states_to_clear);
    }
    break;
  case ON_SubDComponentPtr::Type::Edge:
    {
      const ON_SubDEdge* edge = Edge();
      if (nullptr != edge)
        return edge->m_status.ClearStates(states_to_clear);
    }
    break;
  case ON_SubDComponentPtr::Type::Face:
    {
      const ON_SubDFace* face = Face();
      if (nullptr != face)
        return face->m_status.ClearStates(states_to_clear);
    }
    break;
  }
  return ON_SUBD_RETURN_ERROR(0);
}


ON_SubDComponentPtr ON_SubDComponentPtr::ClearMark() const
{
  ON_SubDComponentPtr component_ptr = *this;
  component_ptr.m_ptr &= (ON_SUBD_ELEMENT_POINTER_MASK | ON_SUBD_ELEMENT_TYPE_MASK);
  return component_ptr;
}

ON_SubDComponentPtr ON_SubDComponentPtr::SetMark() const
{
  ON_SubDComponentPtr component_ptr = *this;
  component_ptr.m_ptr |= ON_SUBD_ELEMENT_MARK_MASK;
  return component_ptr;
}

ON_SubDComponentPtr ON_SubDComponentPtr::ToggleMark() const
{
  return (0 != (m_ptr & ON_SUBD_ELEMENT_MARK_MASK)) ? ClearMark() : SetMark();
}

const ON_SubDComponentPtr ON_SubDComponentPtr::CreateNull(
  ON_SubDComponentPtr::Type component_type,
  bool bMark
)
{
  ON_SubDComponentPtr component_ptr;
  switch (component_type)
  {
  case ON_SubDComponentPtr::Type::Unset:
    component_ptr.m_ptr = 0;
    break;
  case ON_SubDComponentPtr::Type::Vertex:
    component_ptr.m_ptr = ON_SUBD_ELEMENT_TYPE_VERTEX;
    break;
  case ON_SubDComponentPtr::Type::Edge:
    component_ptr.m_ptr = ON_SUBD_ELEMENT_TYPE_EDGE;
    break;
  case ON_SubDComponentPtr::Type::Face:
    component_ptr.m_ptr = ON_SUBD_ELEMENT_TYPE_FACE;
    break;
  default:
    component_ptr.m_ptr = 0;
    break;
  }
  if (bMark)
    component_ptr.m_ptr |= ON_SUBD_ELEMENT_MARK_MASK;
  return component_ptr;
}


class ON_SubDComponentBase* ON_SubDComponentPtr::ComponentBase() const
{
  switch ((ON_SUBD_ELEMENT_TYPE_MASK & m_ptr))
  {
  case ON_SUBD_ELEMENT_TYPE_VERTEX:
  case ON_SUBD_ELEMENT_TYPE_EDGE:
  case ON_SUBD_ELEMENT_TYPE_FACE:
    return ((class ON_SubDComponentBase*)ON_SUBD_ELEMENT_POINTER(m_ptr));
  }
  return nullptr;
}


class ON_SubDVertex* ON_SubDComponentPtr::Vertex() const
{
  if (ON_SUBD_ELEMENT_TYPE_VERTEX == (ON_SUBD_ELEMENT_TYPE_MASK & m_ptr))
    return ON_SUBD_VERTEX_POINTER(m_ptr);
  return nullptr;
}

class ON_SubDEdge* ON_SubDComponentPtr::Edge() const
{
  if (ON_SUBD_ELEMENT_TYPE_EDGE == (ON_SUBD_ELEMENT_TYPE_MASK & m_ptr))
    return ON_SUBD_EDGE_POINTER(m_ptr);
  return nullptr;
}

class ON_SubDFace* ON_SubDComponentPtr::Face() const
{
  if (ON_SUBD_ELEMENT_TYPE_FACE == (ON_SUBD_ELEMENT_TYPE_MASK & m_ptr))
    return ON_SUBD_FACE_POINTER(m_ptr);
  return nullptr;
}

ON_SubDVertexPtr ON_SubDComponentPtr::VertexPtr() const
{
  ON__UINT_PTR element_type = ON_SUBD_ELEMENT_TYPE(m_ptr);
  if ( ON_SUBD_ELEMENT_TYPE_VERTEX == element_type)
    return ON_SubDVertexPtr::Create(Vertex(), ComponentMark());

  if ( 0 == element_type )
    return ON_SubDVertexPtr::Null;

  return ON_SUBD_RETURN_ERROR(ON_SubDVertexPtr::Null);
}

ON_SubDEdgePtr ON_SubDComponentPtr::EdgePtr() const
{
  ON__UINT_PTR element_type = ON_SUBD_ELEMENT_TYPE(m_ptr);
  if ( ON_SUBD_ELEMENT_TYPE_EDGE == element_type)
    return ON_SubDEdgePtr::Create(Edge(), ComponentMark());

  if ( 0 == element_type )
    return ON_SubDEdgePtr::Null;

  return ON_SUBD_RETURN_ERROR(ON_SubDEdgePtr::Null);
}

ON_SubDFacePtr ON_SubDComponentPtr::FacePtr() const
{
  ON__UINT_PTR element_type = ON_SUBD_ELEMENT_TYPE(m_ptr);
  if ( ON_SUBD_ELEMENT_TYPE_FACE == element_type)
    return ON_SubDFacePtr::Create(Face(), ComponentMark());

  if ( 0 == element_type )
    return ON_SubDFacePtr::Null;

  return ON_SUBD_RETURN_ERROR(ON_SubDFacePtr::Null);
}

const ON_SubDComponentPtr ON_SubDComponentPtr::Create(
  const class ON_SubDVertex* vertex
  )
{
  if (nullptr != vertex)
  {
    ON_SubDComponentPtr vptr = { (ON__UINT_PTR)vertex | ON_SUBD_ELEMENT_TYPE_VERTEX };
    return vptr;
  }
  return ON_SubDComponentPtr::Null;
}

const ON_SubDComponentPtr ON_SubDComponentPtr::Create(
  const class ON_SubDEdge* edge
  )
{
  if (nullptr != edge)
  {
    ON_SubDComponentPtr eptr = { (ON__UINT_PTR)edge | ON_SUBD_ELEMENT_TYPE_EDGE };
    return eptr;
  }
  return ON_SubDComponentPtr::Null;
}


const ON_SubDComponentPtr ON_SubDComponentPtr::Create(
  const class ON_SubDFace* face
  )
{
  if (nullptr != face)
  {
    ON_SubDComponentPtr fptr = { (ON__UINT_PTR)face | ON_SUBD_ELEMENT_TYPE_FACE };
    return fptr;
  }
  return ON_SubDComponentPtr::Null;
}

const ON_SubDComponentPtr ON_SubDComponentPtr::Create(
  const class ON_SubDVertex* vertex,
  ON__UINT_PTR vertex_direction
  )
{
  if (nullptr != vertex)
  {
    ON_SubDComponentPtr vptr = { (ON__UINT_PTR)vertex | ON_SUBD_ELEMENT_TYPE_VERTEX | (vertex_direction & ON_SUBD_ELEMENT_MARK_MASK) };
    return vptr;
  }
  return ON_SubDComponentPtr::Null;
}

const ON_SubDComponentPtr ON_SubDComponentPtr::Create(
  const class ON_SubDEdge* edge,
  ON__UINT_PTR edge_direction
  )
{
  if (nullptr != edge)
  {    
    ON_SubDComponentPtr eptr = { (ON__UINT_PTR)edge | (ON_SUBD_ELEMENT_TYPE_EDGE | (edge_direction & ON_SUBD_ELEMENT_MARK_MASK)) };
    return eptr;
  }
  return ON_SubDComponentPtr::Null;
}

const ON_SubDComponentPtr ON_SubDComponentPtr::Create(
  const class ON_SubDFace* face,
  ON__UINT_PTR face_direction
  )
{
  if (nullptr != face)
  {
    ON_SubDComponentPtr fptr = { (ON__UINT_PTR)face | (ON_SUBD_ELEMENT_TYPE_FACE | (face_direction & ON_SUBD_ELEMENT_MARK_MASK)) };
    return fptr;
  }
  return ON_SubDComponentPtr::Null;
}

const ON_SubDComponentPtr ON_SubDComponentPtr::Create(
  ON_SubDVertexPtr vertexptr
  )
{
  return Create(vertexptr.Vertex(), vertexptr.VertexPtrMark());
}

const ON_SubDComponentPtr ON_SubDComponentPtr::Create(
  ON_SubDEdgePtr edgeptr
  )
{
  return Create(edgeptr.Edge(), edgeptr.EdgeDirection());
}

const ON_SubDComponentPtr ON_SubDComponentPtr::Create(
  ON_SubDFacePtr faceptr
  )
{
  return Create(faceptr.Face(), faceptr.FaceDirection());
}

int ON_SubDComponentPtr::CompareComponentPtrType(
  ON_SubDComponentPtr::Type a,
  ON_SubDComponentPtr::Type b
  )
{
  if ( a == b )
    return 0;
  switch (a)
  {
  case ON_SubDComponentPtr::Type::Vertex:
    return -1;
    break;
  case ON_SubDComponentPtr::Type::Edge:
    return (ON_SubDComponentPtr::Type::Vertex == b) ? 1 : -1;
    break;
  case ON_SubDComponentPtr::Type::Face:
    return (ON_SubDComponentPtr::Type::Vertex == b || ON_SubDComponentPtr::Type::Edge == b) ? 1 : -1;
    break;
  default:
    break;
  }
  return (((unsigned char)a) < ((unsigned char)b)) ? -1 : 1;
}

int ON_SubDComponentPtr::CompareType(
  const ON_SubDComponentPtr* a,
  const ON_SubDComponentPtr* b
  )
{
  if ( a == b )
    return 0;
  if ( nullptr == a )
    return 1;
  if ( nullptr == b )
    return -1;
  return ON_SubDComponentPtr::CompareComponentPtrType(a->ComponentType(), b->ComponentType());
}

int ON_SubDComponentPtr::Compare(
  const ON_SubDComponentPtr* a,
  const ON_SubDComponentPtr* b
  )
{
  if ( a == b )
    return 0;
  if ( nullptr == a )
    return 1;
  if ( nullptr == b )
    return -1;
  int rc = ON_SubDComponentPtr::CompareComponentPtrType(a->ComponentType(), b->ComponentType());
  if (0 == rc)
  {
    if ( a->m_ptr < b->m_ptr )
      return -1;
    if ( a->m_ptr > b->m_ptr )
      return 1;
  }
  return rc;
}


int ON_SubDComponentPoint::CompareComponentPtr(
  const ON_SubDComponentPoint* a,
  const ON_SubDComponentPoint* b
  )
{
  if ( a == b)
    return 0;
  
  if ( nullptr == a)
    return 1; // nullptr > non-null pointer.

  if ( nullptr == b)
    return -1; // nullptr > non-null pointer.

  // unset < vertex < edge < face
  ON__UINT_PTR x = (ON_SUBD_ELEMENT_TYPE_MASK & a->m_component_ptr.m_ptr);
  ON__UINT_PTR y = (ON_SUBD_ELEMENT_TYPE_MASK & b->m_component_ptr.m_ptr);
  if ( x < y )
    return -1;
  if ( x > y )
    return 1;

  if ( a->m_component_ptr.m_ptr < b->m_component_ptr.m_ptr )
    return -1;
  if ( a->m_component_ptr.m_ptr > b->m_component_ptr.m_ptr )
    return 1;

  return 0;
}

//////////////////////////////////////////////////////////////////////////
//
// ON_SubDFromMeshOptions
//

ON_SubDFromMeshOptions::ConvexCornerOption ON_SubDFromMeshOptions::ConvexCornerOptionFromUnsigned(
  unsigned int convex_corner_option_as_unsigned
  )
{
  switch (convex_corner_option_as_unsigned)
  {
	case (unsigned int)ON_SubDFromMeshOptions::ConvexCornerOption::Unset:
		return ON_SubDFromMeshOptions::ConvexCornerOption::Unset;
	case (unsigned int)ON_SubDFromMeshOptions::ConvexCornerOption::None:
		return ON_SubDFromMeshOptions::ConvexCornerOption::None;
	case (unsigned int)ON_SubDFromMeshOptions::ConvexCornerOption::AtMeshCorner:
		return ON_SubDFromMeshOptions::ConvexCornerOption::AtMeshCorner;
  default:
    break;
  }
	return ON_SubDFromMeshOptions::ConvexCornerOption::Unset;
}

void ON_SubDFromMeshOptions::SetConvexCornerOption(
  ON_SubDFromMeshOptions::ConvexCornerOption convex_corner_option
  )
{
  m_convex_corner_option = ON_SubDFromMeshOptions::ConvexCornerOptionFromUnsigned((unsigned int)convex_corner_option);
}

ON_SubDFromMeshOptions::ConvexCornerOption ON_SubDFromMeshOptions::ConvexCornerTest() const
{
  switch (m_convex_corner_option)
  {
  case ON_SubDFromMeshOptions::ConvexCornerOption::Unset:
  case ON_SubDFromMeshOptions::ConvexCornerOption::None:
    return m_convex_corner_option;

  case ON_SubDFromMeshOptions::ConvexCornerOption::AtMeshCorner:
    if ( m_maximum_convex_corner_edge_count >= 2 
         && m_maximum_convex_corner_edge_count <= ON_SubDVertex::MaximumEdgeCount
         && m_maximum_convex_corner_angle_radians >= 0.0
         && m_maximum_convex_corner_angle_radians < ON_PI
         )
         return m_convex_corner_option;
    break;
  }

  return ON_SubDFromMeshOptions::ConvexCornerOption::Unset;
}

void ON_SubDFromMeshOptions::SetMaximumConvexCornerEdgeCount(
  unsigned int maximum_convex_corner_edge_count
  )
{
  if ( maximum_convex_corner_edge_count >= 2 && maximum_convex_corner_edge_count <= ON_SubDVertex::MaximumEdgeCount)
    m_maximum_convex_corner_edge_count = (unsigned short)maximum_convex_corner_edge_count;
}

unsigned int ON_SubDFromMeshOptions::MaximumConvexCornerEdgeCount() const
{
  return m_maximum_convex_corner_edge_count;
}

void ON_SubDFromMeshOptions::SetMaximumConvexCornerAngleRadians(
  double maximum_convex_corner_angle_radians
  )
{
  if (maximum_convex_corner_angle_radians > 0.0 && maximum_convex_corner_angle_radians < ON_PI)
    m_maximum_convex_corner_angle_radians = maximum_convex_corner_angle_radians;
}

double ON_SubDFromMeshOptions::MaximumConvexCornerAngleRadians() const
{
  return m_maximum_convex_corner_angle_radians;
}

ON_SubDFromMeshOptions::ConvexCornerOption ON_SubDFromMeshOptions::CopyConvexCornerTest(
  ON_SubDFromMeshOptions source_parameters
  )
{
  SetConvexCornerOption(source_parameters.ConvexCornerTest());
  SetMaximumConvexCornerEdgeCount(source_parameters.MaximumConvexCornerEdgeCount());
  SetMaximumConvexCornerAngleRadians(source_parameters.MaximumConvexCornerAngleRadians());
  return ConvexCornerTest();
}

void ON_SubDFromMeshOptions::SetInteriorCreaseOption(
  ON_SubDFromMeshOptions::InteriorCreaseOption interior_crease_option
  )
{
  m_interior_crease_option = ON_SubDFromMeshOptions::InteriorCreaseOptionFromUnsigned((unsigned int)interior_crease_option);
}

ON_SubDFromMeshOptions::InteriorCreaseOption ON_SubDFromMeshOptions::InteriorCreaseTest() const
{
  return m_interior_crease_option;
}

void ON_SubDFromMeshOptions::SetMinimumCreaseAngleRadians(
  double minimum_crease_angle_radians
  )
{
  if (minimum_crease_angle_radians >= 0.0 && minimum_crease_angle_radians < ON_PI)
    m_minimum_crease_angle_radians = minimum_crease_angle_radians;
}


double ON_SubDFromMeshOptions::MinimumCreaseAngleRadians() const
{
  return m_minimum_crease_angle_radians;
}

ON_SubDFromMeshOptions::InteriorCreaseOption ON_SubDFromMeshOptions::CopyInteriorCreaseTest(
  ON_SubDFromMeshOptions source_parameters
  )
{
  SetInteriorCreaseOption(source_parameters.InteriorCreaseTest());
  SetMinimumCreaseAngleRadians(source_parameters.MinimumCreaseAngleRadians());
  return InteriorCreaseTest();
}

ON_SubDFromMeshOptions::InteriorCreaseOption ON_SubDFromMeshOptions::InteriorCreaseOptionFromUnsigned(
  unsigned int interior_crease_option_as_unsigned
  )
{
  switch (interior_crease_option_as_unsigned)
  {
  case (unsigned int)ON_SubDFromMeshOptions::InteriorCreaseOption::Unset:
    return ON_SubDFromMeshOptions::InteriorCreaseOption::Unset;
    break;
  case (unsigned int)ON_SubDFromMeshOptions::InteriorCreaseOption::None:
    return ON_SubDFromMeshOptions::InteriorCreaseOption::None;
    break;
  case (unsigned int)ON_SubDFromMeshOptions::InteriorCreaseOption::AtMeshCrease:
    return ON_SubDFromMeshOptions::InteriorCreaseOption::AtMeshCrease;
    break;
  case (unsigned int)ON_SubDFromMeshOptions::InteriorCreaseOption::AtMeshEdge:
    return ON_SubDFromMeshOptions::InteriorCreaseOption::AtMeshEdge;
    break;
  default:
    break;
  }

  return ON_SubDFromMeshOptions::InteriorCreaseOption::Unset;
}

ON_SubD::SubDType ON_SubDFromMeshOptions::SubDType() const
{
  return 
    (ON_SubD::SubDType::Unset == m_subd_type) 
    ? ON_SubD::DefaultSubDType()
    : m_subd_type;
}

void ON_SubDFromMeshOptions::SetSubDType(
  ON_SubD::SubDType subd_type
  )
{
  if (subd_type == ON_SubD::SubDTypeFromUnsigned((unsigned int)subd_type) )
  {
    m_subd_type = subd_type;
  }
}
//////////////////////////////////////////////////////////////////////////
//
// ON_SubDVertex
//

const ON_SubDEdgePtr ON_SubDVertex::EdgePtr(
  unsigned int i
) const
{
  return (i < m_edge_count) ? m_edges[i] : ON_SubDEdgePtr::Null;
}


const class ON_SubDEdge* ON_SubDVertex::Edge(
  unsigned int i
  ) const
{
  return (i < m_edge_count) ? ON_SUBD_EDGE_POINTER(m_edges[i].m_ptr) : nullptr;
}

ON__UINT_PTR ON_SubDVertex::EdgeDirection(
  unsigned int i
  ) const
{
  return (i < m_edge_count) ? ON_SUBD_EDGE_DIRECTION(m_edges[i].m_ptr) : 0;
}

unsigned int ON_SubDVertex::EdgeCount() const
{
  return m_edge_count;
}

unsigned int ON_SubDVertex::EdgeCount(
  ON_SubD::EdgeTag edge_tag
  ) const
{
  if (nullptr != m_edges)
  {
    unsigned int matching_edge_count = 0;
    const unsigned int edge_count = m_edge_count;
    for (unsigned int vei = 0; vei < edge_count; vei++)
    {
      const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(m_edges[vei].m_ptr);
      if (nullptr != e && edge_tag == e->m_edge_tag)
        matching_edge_count++;
    }
    return matching_edge_count;
  }
  return 0;
}

unsigned int ON_SubDVertex::EdgeArrayIndex(
  const ON_SubDEdge* edge
  ) const
{
  if ( nullptr == edge )
    return ON_UNSET_UINT_INDEX;
  const unsigned int edge_count = m_edge_count;
  if ( 0 == edge_count)
    return ON_UNSET_UINT_INDEX;
  const ON_SubDEdgePtr* eptr = m_edges;
  if ( nullptr == eptr)
    return ON_SUBD_RETURN_ERROR(ON_UNSET_UINT_INDEX);

  for ( unsigned int i = 0; i < edge_count; i++, eptr++)
  {
    if (edge == ON_SUBD_EDGE_POINTER(eptr->m_ptr))
      return i;
  }

  return ON_UNSET_UINT_INDEX;
}

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

const class ON_SubDFace* ON_SubDVertex::Face(
  unsigned int i
  ) const
{
  //return (i < m_face_count) ? ON_SUBD_FACE_POINTER(m_faces[i].m_ptr) : nullptr;
  return (i < m_face_count) ? m_faces[i] : nullptr;
}


unsigned int ON_SubDVertex::FaceArrayIndex(
  const ON_SubDFace* face
  ) const
{
  if ( nullptr == face )
    return ON_UNSET_UINT_INDEX;
  const unsigned int face_count = m_face_count;
  if ( 0 == face_count)
    return ON_UNSET_UINT_INDEX;
  if ( nullptr == m_faces)
    return ON_SUBD_RETURN_ERROR(ON_UNSET_UINT_INDEX);

  for ( unsigned int i = 0; i < face_count; i++)
  {
    if (face == m_faces[i] )
      return i;
  }

  return ON_UNSET_UINT_INDEX;
}


ON_SubD::FacetType ON_SubDVertex::FirstFaceFacetType() const
{
  if (0 == m_face_count)
    return ON_SubD::FacetType::Unset;

  if (nullptr == m_faces)
    return ON_SubD::FacetType::Unset;

  if (nullptr == m_faces[0])
    return ON_SubD::FacetType::Unset;

  if (3 == m_faces[0]->m_edge_count)
    return ON_SubD::FacetType::Tri;

  if (4 == m_faces[0]->m_edge_count)
    return ON_SubD::FacetType::Quad;

  return ON_SubD::FacetType::Unset;
}


bool ON_SubDVertex::IsTagged() const
{
  return (ON_SubD::VertexTag::Crease == m_vertex_tag || ON_SubD::VertexTag::Corner == m_vertex_tag || ON_SubD::VertexTag::Dart == m_vertex_tag);
}

//static bool AllFacesHaveCorrectEdgeCount(
//  unsigned short correct_edge_count,
//  unsigned int face_count,
//  const ON_SubDFace*const* vertex_faces
//  )
//{
//  if (nullptr == vertex_faces)
//    return false;
//  for (unsigned int vfi = 0; vfi < face_count; vfi++)
//  {
//    const ON_SubDFace* f = vertex_faces[vfi];
//    if (nullptr == f)
//      return false;
//    if (correct_edge_count != f->m_edge_count)
//      return false;
//  }
//  return true;
//}

//bool ON_SubDVertex::IsOrdinary(
//  ON_SubD::SubDType subdivision_type,
//  ON_SubD::VertexTag vertex_tag_filter,
//  bool bTestFaces
//  ) const
//{
//  if (ON_SubD::VertexTag::Unset == vertex_tag_filter || vertex_tag_filter == m_vertex_tag)
//  {
//    if (ON_SubD::VertexTag::Smooth == m_vertex_tag)
//    {
//      if (m_edge_count == m_face_count)
//      {
//        if (ON_SubD::SubDType::QuadCatmullClark == subdivision_type)
//        {
//          if (4 == m_edge_count)
//            return bTestFaces ? AllFacesHaveCorrectEdgeCount(4, m_face_count, m_faces) : true;
//        }
//        else if (ON_SubD::SubDType::TriLoopWarren == subdivision_type)
//        {
//          if (6 == m_edge_count)
//            return bTestFaces ? AllFacesHaveCorrectEdgeCount(3, m_face_count, m_faces) : true;
//        }
//      }
//    }
//    else if (ON_SubD::VertexTag::Crease == m_vertex_tag)
//    {
//      if (m_edge_count == m_face_count + 1)
//      {
//        if (ON_SubD::SubDType::QuadCatmullClark == subdivision_type)
//        {
//          if (3 == m_edge_count && 2 == EdgeCount(ON_SubD::EdgeTag::Crease))
//          {
//            return bTestFaces ? AllFacesHaveCorrectEdgeCount(4, m_face_count, m_faces) : true;
//          }
//        }
//        else if (ON_SubD::SubDType::TriLoopWarren == subdivision_type)
//        {
//          if (4 == m_edge_count && 2 == EdgeCount(ON_SubD::EdgeTag::Crease))
//          {
//            return bTestFaces ?  AllFacesHaveCorrectEdgeCount(3, m_face_count, m_faces) : true;
//          }
//        }
//      }
//    }
//  }
//
//  return false;
//}

bool ON_SubDVertex::IsSmooth() const
{
  return (ON_SubD::VertexTag::Smooth == m_vertex_tag);
}

bool ON_SubDVertex::IsCrease() const
{
  return (ON_SubD::VertexTag::Crease == m_vertex_tag);
}

bool ON_SubDVertex::IsCorner() const
{
  return (ON_SubD::VertexTag::Corner == m_vertex_tag);
}

bool ON_SubDVertex::IsDart() const
{
  return (ON_SubD::VertexTag::Dart == m_vertex_tag);
}

bool ON_SubDVertex::IsCreaseOrCorner() const
{
  return (ON_SubD::VertexTag::Crease == m_vertex_tag || ON_SubD::VertexTag::Corner == m_vertex_tag);
}

bool ON_SubDVertex::IsCreaseOrCornerOrDart() const
{
  return (
    ON_SubD::VertexTag::Crease == m_vertex_tag
    || ON_SubD::VertexTag::Corner == m_vertex_tag
    || ON_SubD::VertexTag::Dart == m_vertex_tag
    );
}



bool ON_SubDVertex::IsSmoothOrDart() const
{
  return (ON_SubD::VertexTag::Smooth == m_vertex_tag || ON_SubD::VertexTag::Dart == m_vertex_tag);
}

bool ON_SubDVertex::IsSmoothOrCrease() const
{
  return (ON_SubD::VertexTag::Smooth == m_vertex_tag || ON_SubD::VertexTag::Crease == m_vertex_tag);
}

bool ON_SubDEdge::IsCrease(
  bool bEdgeTagXresult
  ) const
{
  if (ON_SubD::EdgeTag::Crease == m_edge_tag)
    return true;
  if (ON_SubD::EdgeTag::X == m_edge_tag)
    return (bEdgeTagXresult ? true : false);
  return false;
}

unsigned int ON_SubDEdge::DartCount() const
{
  unsigned int dart_count = 0;
  if (nullptr != m_vertex[0] && ON_SubD::VertexTag::Dart == m_vertex[0]->m_vertex_tag)
    dart_count++;
  if (nullptr != m_vertex[1] && ON_SubD::VertexTag::Dart == m_vertex[1]->m_vertex_tag)
    dart_count++;
  return dart_count;
}

bool ON_SubDEdge::IsSmooth(
  bool bEdgeTagXresult
  ) const
{
  if (ON_SubD::EdgeTag::Smooth == m_edge_tag)
    return true;
  if (ON_SubD::EdgeTag::X == m_edge_tag)
    return (bEdgeTagXresult ? true : false);
  return false;
}

bool ON_SubDVertex::IsStandard(
  ON_SubD::SubDType subdivision_type
  ) const
{
  if (nullptr == m_edges)
    return false;

  if (nullptr == m_faces)
    return false;

  const unsigned int edge_count = m_edge_count;
  if (!ON_SubD::IsValidSectorEdgeCount(m_vertex_tag,edge_count))
    return false;

  const unsigned int face_count = m_face_count;
  if (face_count != ON_SubDSectorType::SectorFaceCountFromEdgeCount(m_vertex_tag, edge_count))
    return false;

  const unsigned short f_edge_count 
    = (ON_SubD::SubDType::QuadCatmullClark == subdivision_type) 
    ? 4
    : ((ON_SubD::SubDType::TriLoopWarren == subdivision_type) ? 3 : 0);
  if (0 == f_edge_count)
    return false;

  unsigned int crease_edge_face_count = ON_UNSET_UINT_INDEX;
  bool bTaggedVertex = false;
  switch (m_vertex_tag)
  {
  case ON_SubD::VertexTag::Unset:
    return false;
    break;

  case ON_SubD::VertexTag::Smooth:
    if (edge_count != face_count)
      return false;
    break;

  case ON_SubD::VertexTag::Crease:
    if (edge_count != face_count+1)
      return false;
    crease_edge_face_count = 1;
    bTaggedVertex = true;
    break;

  case ON_SubD::VertexTag::Corner:
    if (edge_count != face_count+1)
      return false;
    crease_edge_face_count = 1;
    bTaggedVertex = true;
    break;

  case ON_SubD::VertexTag::Dart:
    if (edge_count != face_count)
      return false;
    crease_edge_face_count = 2;
    bTaggedVertex = true;
    break;

  default:
    return false;
    break;
  }
  

  for (unsigned int vfi = 0; vfi < face_count; vfi++)
  {
    const ON_SubDFace* f = m_faces[vfi];
    if (nullptr == f)
      return false;
    if (f_edge_count != f->m_edge_count)
      return false;
  }

  unsigned int creased_edge_count = 0;
  double sector_weight = 0.0;
  for (unsigned int vei = 0; vei < edge_count; vei++)
  {
    const ON_SubDEdge* e = m_edges[vei].Edge();
    if (nullptr == e)
      return false;

    unsigned int evi;
    if (this == e->m_vertex[0])
      evi = 0;
    else if (this == e->m_vertex[1])
      evi = 1;
    else
      return false;

    const ON_SubDVertex* other_vertex = e->m_vertex[1-evi];

    if (nullptr == other_vertex)
      return false;

    if (ON_SubD::EdgeTag::Smooth == e->m_edge_tag)
    {
      if (2 != e->m_face_count)
        return false;

      if (bTaggedVertex && 0 == vei)
      {
        sector_weight = e->m_sector_coefficient[evi];
        if (!(0.0 <= sector_weight && sector_weight <= 1.0))
          return false;
      }
      
      if (!(sector_weight == e->m_sector_coefficient[evi]))
        return false;

      if (ON_SubD::VertexTag::Smooth == other_vertex->m_vertex_tag)
      {
        if ( !(0.0 == e->m_sector_coefficient[1-evi]) )
          return false;
      }
      else
      {
        if ( bTaggedVertex )
          return false;
        if ( !(0.5 == e->m_sector_coefficient[1-evi]) )
          return false;
      }
    }
    else if (ON_SubD::EdgeTag::Crease == e->m_edge_tag)
    {
      if (crease_edge_face_count != e->m_face_count)
        return false;
      creased_edge_count++;
      if (creased_edge_count > 2)
        return false;
      if (false == other_vertex->IsCreaseOrCornerOrDart())
        return false;
    }
    else
    {
      return false;
    }
  }

  switch (creased_edge_count)
  {
  case 0:
    if (false == IsSmooth())
      return false;
    break;
  case 1:
    if (false == IsDart())
      return false;
    break;
  case 2:
    if (false == IsCreaseOrCorner())
      return false;
    break;
  default:
    return false;
  }

  // The standard subdivison matrix will correctly calculate
  // the subdivision location for this vertex.
  return true;
}


unsigned int ON_SubDEdge::EdgeFlags() const
{

  if (nullptr == m_vertex[0] || nullptr == m_vertex[1] || m_vertex[0] == m_vertex[1])
    return ON_ComponentAttributes::EdgeFlags::Damaged;
  
  unsigned int edge_topology_attributes = ON_ComponentAttributes::EdgeFlags::Open;

  const double* P[2] = { m_vertex[0]->m_P, m_vertex[1]->m_P };
  if (P[0][0] == P[1][0] && P[0][1] == P[1][1] && P[0][2] == P[1][2])
    edge_topology_attributes |= ON_ComponentAttributes::EdgeFlags::Degenerate;

  switch (m_face_count)
  {
  case 0:
    edge_topology_attributes |= ON_ComponentAttributes::EdgeFlags::Wire;
    break;
  case 1:
    edge_topology_attributes |= ON_ComponentAttributes::EdgeFlags::Boundary;
    break;
  case 2:
    edge_topology_attributes |= ON_ComponentAttributes::EdgeFlags::Interior;
    {
      const ON_SubDFace* face[2] = { ON_SUBD_FACE_POINTER(m_face2[0].m_ptr), ON_SUBD_FACE_POINTER(m_face2[1].m_ptr) };
      if (nullptr == face[0] || nullptr == face[1] || face[0] == face[1])
        edge_topology_attributes |= ON_ComponentAttributes::EdgeFlags::Damaged;
      else
      {
        if (IsSmooth(true))
          edge_topology_attributes |= ON_ComponentAttributes::EdgeFlags::Smooth;
        else if ( m_vertex[0]->IsDart() || m_vertex[1]->IsDart() )
          edge_topology_attributes |= ON_ComponentAttributes::EdgeFlags::Dart;
        else
          edge_topology_attributes |= ON_ComponentAttributes::EdgeFlags::Crease;

        ON__UINT_PTR dir[2] = { ON_SUBD_FACE_DIRECTION(m_face2[0].m_ptr), ON_SUBD_FACE_DIRECTION(m_face2[1].m_ptr) };
        if (dir[0] == dir[1])
          edge_topology_attributes |= ON_ComponentAttributes::EdgeFlags::NotOriented;
        else
          edge_topology_attributes |= ON_ComponentAttributes::EdgeFlags::Oriented;
      }
    }
    break;
  default:
    edge_topology_attributes |= ON_ComponentAttributes::EdgeFlags::Nonmanifold;
    if ( nullptr == m_facex )
      edge_topology_attributes |= ON_ComponentAttributes::EdgeFlags::Damaged;
    break;
  }

  return edge_topology_attributes;
}

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

static int ComparePointerArrays(
  size_t count,
  const ON__UINT_PTR* a,
  const ON__UINT_PTR* b
  )
{
  if (count <= 0)
    return 0;
  if (nullptr == a)
    return ((nullptr == b) ? 0 : -1);
  if (nullptr == b)
    return 1;
  if (1 == count)
    return Compare_ON__UINT_PTR(a, b);

  unsigned int stack_buffer[128];
  unsigned int* adex
    = (2 * count <= sizeof(stack_buffer) / sizeof(stack_buffer[0]))
    ? stack_buffer
    : new (std::nothrow) unsigned int[2 * count];  
  if (nullptr == adex)
    return 0;
  
  unsigned int* bdex = adex + count;
  ON_Sort(ON::sort_algorithm::quick_sort, adex, a, count, sizeof(a[0]), Compare_ON__UINT_PTR);
  ON_Sort(ON::sort_algorithm::quick_sort, bdex, b, count, sizeof(b[0]), Compare_ON__UINT_PTR);
  
  int rc = 0;
  for (unsigned int i = 0; 0 == rc && i < count; i++)
  {
    rc = Compare_ON__UINT_PTR(a + adex[i], b + bdex[i]);
  }

  if (adex != stack_buffer)
    delete[] adex;

  return rc;
}

int ON_SubDVertex::CompareUnorderedEdges(
  const ON_SubDVertex* a,
  const ON_SubDVertex* b
  )
{
  if (nullptr == a)
    return ((nullptr == b) ? 0 : -1);
  if (nullptr == b)
    return 1;

  if (a->m_edge_count < b->m_edge_count)
    return -1;
  if (a->m_edge_count > b->m_edge_count)
    return 1;
  return ComparePointerArrays(a->m_edge_count, (const ON__UINT_PTR*)a->m_edges, (const ON__UINT_PTR*)b->m_edges);
}

int ON_SubDVertex::CompareUnorderedFaces(
  const ON_SubDVertex* a,
  const ON_SubDVertex* b
  )
{
  if (nullptr == a)
    return ((nullptr == b) ? 0 : -1);
  if (nullptr == b)
    return 1;

  if (a->m_face_count < b->m_face_count)
    return -1;
  if (a->m_face_count > b->m_face_count)
    return 1;
  return ComparePointerArrays(a->m_face_count, (const ON__UINT_PTR*)a->m_faces, (const ON__UINT_PTR*)b->m_faces);
}

int ON_SubDVertex::CompareUnorderedEdgesAndFaces(
  const ON_SubDVertex* a,
  const ON_SubDVertex* b
  )
{
  int rc = CompareUnorderedEdges(a, b);
  if (0 == rc)
    rc = CompareUnorderedFaces(a, b);
  return rc;
}

//////////////////////////////////////////////////////////////////////////
//
// ON_SubDEdge
//

void ON_SubDComponentBase::CopyBaseFrom(
  const ON_SubDComponentBase* src
  )
{
  if ( nullptr == src )
    src = &ON_SubDComponentBase::Unset;

  *this = *src;
  m_subd_point1 = nullptr;
  ON_SUBD_CACHE_CLEAR_LIMIT_FLAG(m_saved_points_flags);
}

void ON_SubDEdge::CopyFrom(
  const ON_SubDEdge* src,
  bool bReverseEdge,
  bool bCopyVertexArray,
  bool bCopyFaceArray
  )
{
  if (nullptr == src)
    src = &ON_SubDEdge::Empty;

  CopyBaseFrom(src);

  m_next_edge = nullptr;
  
  m_edge_tag = src->m_edge_tag;

  unsigned int end0 = bReverseEdge ? 1 : 0;

  if (bCopyVertexArray)
  {
    m_vertex[0] = src->m_vertex[end0];
    m_vertex[1] = src->m_vertex[1 - end0];
  }

  m_sector_coefficient[0] = src->m_sector_coefficient[end0];
  m_sector_coefficient[1] = src->m_sector_coefficient[1 - end0];

  if (bCopyFaceArray)
  {
    if (src->m_face_count > 0 && (src->m_face_count <= 2 || (nullptr != src->m_facex && nullptr != m_facex)))
    {
      m_face2[0] = src->m_face2[0];
      m_face2[1] = src->m_face2[1];
      unsigned int face_count = src->m_face_count;
      if (face_count > 2)
      {
        face_count -= 2;
        for (unsigned int efi = 0; efi < face_count; efi++)
          m_facex[efi] = src->m_facex[efi];
      }
      m_face_count = src->m_face_count;
    }
    else
      m_face_count = 0;
  }
}

unsigned int ON_SubDEdge::TaggedEndIndex() const
{
  unsigned int tagged_end_index = 3;
  for (unsigned int evi = 0; evi < 2; evi++)
  {
    const ON_SubDVertex* v = m_vertex[evi];
    if (nullptr == v || false == v->IsTagged())
      continue;
    tagged_end_index = (3 == tagged_end_index) ? evi : 2;
  }
  return tagged_end_index;
}

ON_SubDFacePtr ON_SubDEdge::FacePtr(
  unsigned int i
  ) const
{
  return (i < 2) ? m_face2[i] : ((i < m_face_count) ? m_facex[i - 2] : ON_SubDFacePtr::Null);
  //return (i < m_face_count) ? ((i < 2) ? m_face2[i] : m_facex[i - 2]) : ON_SubDFacePtr::Null;
}

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

const class ON_SubDFace* ON_SubDEdge::Face(
  unsigned int i
  ) const
{
  return (i < 2) ? ON_SUBD_FACE_POINTER(m_face2[i].m_ptr) : ((i < m_face_count) ? ON_SUBD_FACE_POINTER(m_facex[i - 2].m_ptr) : nullptr);
}

ON__UINT_PTR ON_SubDEdge::FaceDirection(
  unsigned int i
  ) const
{
  return (i < 2) ? ON_SUBD_FACE_DIRECTION(m_face2[i].m_ptr) : ((i < m_face_count) ? ON_SUBD_FACE_DIRECTION(m_facex[i - 2].m_ptr) : 0);
}

ON_SubDFacePtr ON_SubDEdge::FacePtr(
  const class ON_SubDFace* f
) const
{
  if (nullptr != f)
  {
    const ON_SubDFacePtr* fptr = m_face2;
    const unsigned int edge_face_count = m_face_count;
    for (unsigned int efi = 0; efi < edge_face_count; efi++, fptr++)
    {
      if (2 == efi)
      {
        fptr = m_facex;
        if (nullptr == fptr)
          break;
      }
      if (fptr->Face() == f)
        return *fptr;
    }
  }
  return ON_SubDFacePtr::Null;
}


unsigned int ON_SubDEdge::FaceArrayIndex(
  const ON_SubDFace* f
  ) const
{
  if (nullptr == f)
    return ON_UNSET_UINT_INDEX;
  const unsigned int face_count = m_face_count;
  if (face_count > 0)
  {
    if (f == ON_SUBD_FACE_POINTER(m_face2[0].m_ptr))
      return 0;
    if (face_count >= 1)
    {
      if (f == ON_SUBD_FACE_POINTER(m_face2[1].m_ptr))
        return 1;
      if (face_count > 2 && nullptr != m_facex)
      {
        const ON_SubDFacePtr* fptr = m_facex - 2;
        for (unsigned int efi = 2; efi < face_count; efi++)
        {
          if (f == ON_SUBD_FACE_POINTER(fptr[efi].m_ptr))
            return efi;
        }
      }
    }
  }
  return ON_UNSET_UINT_INDEX;
}


const ON_SubDFace* ON_SubDEdge::NeighborFace(
  const ON_SubDFace* face,
    bool bStopAtCrease
  ) const
{
  if ( nullptr == face || 2 != m_face_count )
    return nullptr;
  // Do not stop at x tags
  if (bStopAtCrease && ON_SubD::EdgeTag::Crease == m_edge_tag)
    return nullptr;
  const ON_SubDFace* f[2] = { ON_SUBD_FACE_POINTER(m_face2[0].m_ptr), ON_SUBD_FACE_POINTER(m_face2[1].m_ptr) };
  if (nullptr == f[0] || nullptr == f[1] )
    return ON_SUBD_RETURN_ERROR(nullptr);
  if (face == f[0])
  {
    if (face == f[1] )
      return ON_SUBD_RETURN_ERROR(nullptr);
    return f[1];
  }
  if (face == f[1])
  {
    return f[0];
  }
  return ON_SUBD_RETURN_ERROR(nullptr);
}

const ON_SubDFacePtr ON_SubDEdge::NeighborFacePtr(
  const ON_SubDFace* face,
    bool bStopAtCrease
  ) const
{
  if ( nullptr == face || 2 != m_face_count )
    return ON_SubDFacePtr::Null;
  // Do not stop at x tags
  if (bStopAtCrease && ON_SubD::EdgeTag::Crease == m_edge_tag)
    return ON_SubDFacePtr::Null;
  const ON_SubDFace* f[2] = { ON_SUBD_FACE_POINTER(m_face2[0].m_ptr), ON_SUBD_FACE_POINTER(m_face2[1].m_ptr) };
  if (nullptr == f[0] || nullptr == f[1] )
    return ON_SUBD_RETURN_ERROR(ON_SubDFacePtr::Null);
  if (face == f[0])
  {
    if (face == f[1] )
      return ON_SUBD_RETURN_ERROR(ON_SubDFacePtr::Null);
    return m_face2[1];
  }
  if (face == f[1])
  {
    return m_face2[0];
  }
  return ON_SUBD_RETURN_ERROR(ON_SubDFacePtr::Null);
}


const class ON_SubDVertex* ON_SubDEdge::Vertex(
  unsigned int i
  ) const
{
  return (i <= 1) ? m_vertex[i] : nullptr;
}


const ON_SubDVertex* ON_SubDEdge::OtherEndVertex(
  const ON_SubDVertex* vertex
  ) const
{
  if (nullptr != vertex)
  {
    if (m_vertex[0] == vertex)
    {
      if (m_vertex[1] != vertex )
        return m_vertex[1];
    }
    else if (m_vertex[1] == vertex )
      return m_vertex[0];
  }
  return nullptr;
}


//////////////////////////////////////////////////////////////////////////
//
// ON_SubDFace
//



//bool ON_SubDFace::IsOrdinary(
//  ON_SubD::SubDType subdivision_type,
//  bool bTestFaces
//  ) const
//{
//  unsigned int ordinary_face_edge_count = 0;
//  if (ON_SubD::SubDType::QuadCatmullClark == subdivision_type)
//    ordinary_face_edge_count = 4;
//  else if (ON_SubD::SubDType::TriLoopWarren == subdivision_type)
//    ordinary_face_edge_count = 3;
//  else
//    return false;
//
//  if (ordinary_face_edge_count != m_edge_count)
//    return false;
//
//  for (unsigned int fei = 0; fei < ordinary_face_edge_count; fei++)
//  {
//    ON__UINT_PTR eptr = m_edge4[fei].m_ptr;
//    const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(eptr);
//    if (nullptr == e || 2 != e->m_face_count || ON_SubD::EdgeTag::Smooth != e->m_edge_tag)
//      return false;
//    ON__UINT_PTR edir = ON_SUBD_EDGE_DIRECTION(eptr);
//    const ON_SubDVertex* v = e->m_vertex[edir];
//    if (nullptr == v || false == v->IsOrdinary(subdivision_type,ON_SubD::VertexTag::Unset,bTestFaces))
//      return false;
//  }
//  return true;
//}

void ON_SubDFace::CopyFrom(
  const ON_SubDFace* src,
  bool bCopyEdgeArray
  )
{
  if (nullptr == src)
    src = &ON_SubDFace::Empty;

  CopyBaseFrom(src);

  m_next_face = nullptr;

  m_zero_face_id = src->m_zero_face_id;
  m_parent_face_id = src->m_parent_face_id;

  if (bCopyEdgeArray)
  {
    if (src->m_edge_count > 0 && (src->m_edge_count <= 4 || (nullptr != src->m_edgex && nullptr != m_edgex)))
    {
      m_edge4[0] = src->m_edge4[0];
      m_edge4[1] = src->m_edge4[1];
      m_edge4[2] = src->m_edge4[2];
      m_edge4[3] = src->m_edge4[3];
      unsigned int edge_count = src->m_edge_count;
      if (edge_count > 4)
      {
        edge_count -= 4;
        for (unsigned int fei = 0; fei < edge_count; fei++)
          m_edgex[fei] = src->m_edgex[fei];
      }
      m_edge_count = src->m_edge_count;
    }
    else
      m_edge_count = 0;
  }
}

ON_SubDEdgePtr ON_SubDFace::EdgePtr(
  unsigned int i
  ) const
{
  return (i < 4) ? m_edge4[i] : ((i < m_edge_count) ? m_edgex[i-4] : ON_SubDEdgePtr::Null);
}

unsigned int ON_SubDFace::EdgeCount() const
{
  return m_edge_count;
}

const class ON_SubDVertex* ON_SubDFace::Vertex(
  unsigned int i
  ) const
{
  ON_SubDEdge* e;
  const ON__UINT_PTR edge_ptr = (i < 4) ? m_edge4[i].m_ptr : ((i < m_edge_count) ? m_edgex[i - 4].m_ptr : 0);
  return (nullptr != (e = ON_SUBD_EDGE_POINTER(edge_ptr))) ? e->m_vertex[ON_SUBD_EDGE_DIRECTION(edge_ptr)] : nullptr;
}

const ON_SubDVertex* ON_SubDFace::QuadOppositeVertex(
  const ON_SubDVertex* vertex
  ) const
{
  if ( nullptr == vertex )
    return ON_SUBD_RETURN_ERROR(nullptr);
  if ( 4 != m_edge_count)
    return nullptr; // not an error
  
  ON__UINT_PTR ptr0 = m_edge4[0].m_ptr;
  const ON_SubDEdge* e0 = ON_SUBD_EDGE_POINTER(ptr0);
  if ( nullptr == e0 )
    return ON_SUBD_RETURN_ERROR(nullptr);
  ptr0 = ON_SUBD_EDGE_DIRECTION(ptr0);
  
  ON__UINT_PTR ptr2 = m_edge4[2].m_ptr;
  const ON_SubDEdge* e2 = ON_SUBD_EDGE_POINTER(ptr2);
  if ( nullptr == e2 )
    return ON_SUBD_RETURN_ERROR(nullptr);
  ptr2 = ON_SUBD_EDGE_DIRECTION(ptr2);

  if (vertex == e0->m_vertex[ptr0])
    return e2->m_vertex[ptr2];

  if (vertex == e0->m_vertex[1-ptr0])
    return e2->m_vertex[1-ptr2];

  if (vertex == e2->m_vertex[ptr2])
    return e0->m_vertex[ptr0];

  if (vertex == e2->m_vertex[1-ptr2])
    return e0->m_vertex[1-ptr0];

  return ON_SUBD_RETURN_ERROR(nullptr);
}

const ON_SubDEdge* ON_SubDFace::QuadOppositeEdge(
  const ON_SubDEdge* edge
  ) const
{
  if ( nullptr == edge )
    return ON_SUBD_RETURN_ERROR(nullptr);
  if ( 4 != m_edge_count)
    return nullptr; // not an error
  
  for (unsigned int fei = 0; fei < 4; fei++)
  {
    const ON_SubDEdge* e0 = ON_SUBD_EDGE_POINTER(m_edge4[fei].m_ptr);
    if (nullptr == e0)
      return ON_SUBD_RETURN_ERROR(nullptr);
    if (e0 == edge)
    {
      e0 = ON_SUBD_EDGE_POINTER(m_edge4[(fei + 2) % 4].m_ptr);
      if (nullptr == e0)
        return ON_SUBD_RETURN_ERROR(nullptr);
      return e0;
    }
  }

  return ON_SUBD_RETURN_ERROR(nullptr);
}


const class ON_SubDEdge* ON_SubDFace::Edge(
  unsigned int i
  ) const
{
  return (i < 4) ? ON_SUBD_EDGE_POINTER(m_edge4[i].m_ptr) : ((i < m_edge_count) ? ON_SUBD_EDGE_POINTER(m_edgex[i - 4].m_ptr) : nullptr);
}

ON__UINT_PTR ON_SubDFace::EdgeDirection(
  unsigned int i
  ) const
{
  return (i < 4) ? ON_SUBD_EDGE_DIRECTION(m_edge4[i].m_ptr) : ((i < m_edge_count) ? ON_SUBD_EDGE_DIRECTION(m_edgex[i - 4].m_ptr) : 0);
}


ON_SubDEdgePtr ON_SubDFace::EdgePtr(
  const class ON_SubDEdge* e
) const
{
  if (nullptr != e)
  {
    const ON_SubDEdgePtr* eptr = m_edge4;
    const unsigned int face_edge_count = m_edge_count;
    for (unsigned int fei = 0; fei < face_edge_count; fei++, eptr++)
    {
      if (4 == fei)
      {
        eptr = m_edgex;
        if (nullptr == eptr)
          break;
      }
      if (e == ON_SUBD_EDGE_POINTER(eptr->m_ptr))
        return *eptr;
    }
  }

  return ON_SubDEdgePtr::Null;
}


unsigned int ON_SubDFace::EdgeArrayIndex(
  const ON_SubDEdge* e
  ) const
{
  if (nullptr != e)
  {
    const ON_SubDEdgePtr* eptr = m_edge4;
    const unsigned int face_edge_count = m_edge_count;
    for (unsigned int fei = 0; fei < face_edge_count; fei++, eptr++)
    {
      if (4 == fei)
      {
        eptr = m_edgex;
        if (nullptr == eptr)
          break;
      }
      if (e == ON_SUBD_EDGE_POINTER(eptr->m_ptr))
        return fei;
    }
  }

  return ON_UNSET_UINT_INDEX;
}


const ON_SubDEdge* ON_SubDFace::PrevEdge(
  const ON_SubDEdge* edge
  ) const
{
  unsigned int edge_index = EdgeArrayIndex(edge);
  if (ON_UNSET_UINT_INDEX == edge_index)
    return nullptr;
  const unsigned int edge_count = m_edge_count;
  edge_index = (edge_index + (edge_count - 1)) % edge_count;
  return Edge(edge_index);
}

const ON_SubDEdge* ON_SubDFace::NextEdge(
  const ON_SubDEdge* edge
  ) const
{
  unsigned int edge_index = EdgeArrayIndex(edge);
  if (ON_UNSET_UINT_INDEX == edge_index)
    return nullptr;
  edge_index = (edge_index + 1) % ((unsigned int)m_edge_count);
  return Edge(edge_index);
}

unsigned int ON_SubDFace::PrevEdgeArrayIndex(
  unsigned int edge_array_index
  ) const
{
  const unsigned int edge_count = m_edge_count;
  return (edge_array_index < edge_count) ? ((edge_array_index + edge_count - 1) % edge_count) : ON_UNSET_UINT_INDEX;
}

unsigned int ON_SubDFace::NextEdgeArrayIndex(
  unsigned int edge_array_index
  ) const
{
  const unsigned int edge_count = m_edge_count;
  return (edge_array_index < edge_count) ? ((edge_array_index +  1) % edge_count) : ON_UNSET_UINT_INDEX;
}

bool ON_SubDEdge::RemoveFaceFromArray(
  const ON_SubDFace* f
  )
{
  unsigned int i;
  if (nullptr == f)
    return false;

  if (m_face_count <= 2)
  {
    for (i = 0; i < m_face_count; i++)
    {
      if (f == ON_SUBD_FACE_POINTER(m_face2[i].m_ptr))
      {
        for (i++; i < m_face_count; i++)
          m_face2[i - 1] = m_face2[i];
        m_face_count--;
        return true;
      }
    }
  }
  else
  {
    for (i = 0; i < 2; i++)
    {
      if (f == ON_SUBD_FACE_POINTER(m_face2[i].m_ptr))
      {
        for (i++; i < 2; i++)
          m_face2[i - 1] = m_face2[i];
        m_face2[1] = m_facex[0];
        for (i = 3; i < m_face_count; i++)
          m_facex[i - 3] = m_facex[i - 2];
        m_face_count--;
        return true;
      }
    }
    for (i = 2; i < m_face_count; i++)
    {
      if (f == ON_SUBD_FACE_POINTER(m_facex[i - 4].m_ptr))
      {
        for (i++; i < m_face_count; i++)
          m_facex[i - 3] = m_facex[i - 2];
        m_face_count--;
        return true;
      }
    }
  }

  return false;
}

bool ON_SubDEdge::AddFaceToArray(
  ON_SubDFacePtr face_ptr
)
{
  if (m_face_count < 2)
    m_face2[m_face_count] = face_ptr;
  else if (nullptr != m_facex && m_face_count < 2 + m_facex_capacity)
    m_facex[m_face_count - 2] = face_ptr;
  else
  {
    // not enough room in m_facex.
    // If you really are trying to make a non-manifold subd, 
    // then use ON_SubD::GrowEdgeFaceArray().
    return ON_SUBD_RETURN_ERROR(false);
  }
  m_face_count++;
  return true;
}

bool ON_SubDEdge::RemoveFaceFromArray(
  unsigned int i,
  ON_SubDFacePtr& removed_face
  )
{
  removed_face = ON_SubDFacePtr::Null;
  unsigned int count = m_face_count;
  if ( i >= count )
    return ON_SUBD_RETURN_ERROR(false);
  if (i < 2)
  {
    removed_face = m_face2[i];
  }
  if (count > 2)
  {
    if ( nullptr == m_facex || m_facex_capacity + ((unsigned short)2) < m_face_count )
      return ON_SUBD_RETURN_ERROR(false);
    if ( i >= 2 )
      removed_face = m_facex[i-2];
  }

  unsigned int j = i+1;

  while (j < 2 && j < count )
    m_face2[i++] = m_face2[j++];

  if (count > 2)
  {
    m_face2[1] = m_facex[0];
    i = 0;
    j = 1;
    count -= 2;
    while (j < count )
      m_facex[i++] = m_facex[j++];
  }

  m_face_count--;

  return true;

}

bool ON_SubDFace::ReplaceEdgeInArray(
  unsigned int fei0,
  ON_SubDEdge* edge_to_remove,
  ON_SubDEdge* edge_to_insert
)
{
  const unsigned int face_edge_count = m_edge_count;
  ON_SubDEdgePtr* eptr = m_edge4;
  for (unsigned int fei = 0; fei < face_edge_count; fei++, eptr++)
  {
    if (4 == fei)
    {
      eptr = m_edgex;
      if (nullptr == eptr)
        break;
    }
    if (fei >= fei0 && edge_to_remove == eptr->Edge() )
    {
      const ON__UINT_PTR edir = eptr->EdgeDirection();
      *eptr = ON_SubDEdgePtr::Create(edge_to_insert,edir);
      return true;
    }
  }
  return false;
}


bool ON_SubDFace::RemoveEdgeFromArray(
  unsigned int i,
  ON_SubDEdgePtr& removed_edge
  )
{
  removed_edge = ON_SubDEdgePtr::Null;
  unsigned int count = m_edge_count;
  if ( i >= count )
    return ON_SUBD_RETURN_ERROR(false);
  if (i < 4)
  {
    removed_edge = m_edge4[i];
  }
  if (count > 4)
  {
    if ( nullptr == m_edgex || m_edgex_capacity + ((unsigned short)4) < m_edge_count )
      return ON_SUBD_RETURN_ERROR(false);
    if ( i >= 4 )
      removed_edge = m_edgex[i-4];
  }

  unsigned int j = i+1;

  while (j < 4 && j < count )
    m_edge4[i++] = m_edge4[j++];

  if (count > 4)
  {
    m_edge4[3] = m_edgex[0];
    i = 0;
    j = 1;
    count -= 4;
    while (j < count )
      m_edgex[i++] = m_edgex[j++];
  }

  m_edge_count--;

  return true;
}


bool ON_SubDFace::RemoveEdgeFromArray(
  const ON_SubDEdge* e
  )
{
  unsigned int i;
  if (nullptr == e)
    return false;

  if (m_edge_count <= 4)
  {
    for (i = 0; i < m_edge_count; i++)
    {
      if (e == ON_SUBD_EDGE_POINTER(m_edge4[i].m_ptr))
      {
        for (i++; i < m_edge_count; i++)
          m_edge4[i - 1] = m_edge4[i];
        m_edge_count--;
        return true;
      }
    }
  }
  else
  {
    for (i = 0; i < 4; i++)
    {
      if (e == ON_SUBD_EDGE_POINTER(m_edge4[i].m_ptr))
      {
        for (i++; i < 4; i++)
          m_edge4[i - 1] = m_edge4[i];
        m_edge4[3] = m_edgex[0];
        for (i = 5; i < m_edge_count; i++)
          m_edgex[i - 5] = m_edgex[i - 4];
        m_edge_count--;
        return true;
      }
    }
    for (i = 4; i < m_edge_count; i++)
    {
      if (e == ON_SUBD_EDGE_POINTER(m_edgex[i - 4].m_ptr))
      {
        for (i++; i < m_edge_count; i++)
          m_edgex[i - 5] = m_edgex[i - 4];
        m_edge_count--;
        return true;
      }
    }
  }

  return false;
}

unsigned int ON_SubDFace::VertexIndex(
  const ON_SubDVertex* vertex
  ) const
{
  if (nullptr == vertex)
    return ON_UNSET_UINT_INDEX;

  const ON_SubDEdgePtr* face_edges = m_edge4;
  const unsigned int edge_count = m_edge_count;

  for (unsigned int i = 0; i < edge_count; i += 2)
  {
    if (4 == i)
    {
      face_edges = m_edgex;
      if (nullptr == face_edges)
        break;
      face_edges -= 4;
    }
    ON__UINT_PTR e_ptr = face_edges[i].m_ptr;
    const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(e_ptr);
    if (nullptr != edge)
    {
      if (vertex == edge->m_vertex[0])
        return (0 == ON_SUBD_EDGE_DIRECTION(e_ptr)) ? i : ((i + 1) % edge_count);
      if (vertex == edge->m_vertex[1])
        return (0 == ON_SUBD_EDGE_DIRECTION(e_ptr)) ? ((i + 1) % edge_count) : i;
    }
  }

  return ON_UNSET_UINT_INDEX;
}

//////////////////////////////////////////////////////////////////////////
//
// ON_SubD
//

ON_OBJECT_IMPLEMENT(ON_SubD,ON_Geometry,"F09BA4D9-455B-42C3-BA3B-E6CCACEF853B");

ON_SubD::ON_SubD() ON_NOEXCEPT
  : ON_Geometry()
{}

ON_SubD::~ON_SubD()
{
  this->Destroy();
}


#if defined(ON_HAS_RVALUEREF)
ON_SubD::ON_SubD( ON_SubD&& src ) ON_NOEXCEPT
  : ON_Geometry(std::move(src))
  , m_subdimple_sp(std::move(src.m_subdimple_sp))
{}

ON_SubD& ON_SubD::operator=( ON_SubD&& src )
{
  if ( this != &src )
  {
    this->Destroy();
    ON_Geometry::operator=(std::move(src));
    m_subdimple_sp = std::move(src.m_subdimple_sp);
  }
  return *this;
}
#endif



//////////////////////////////////////////////////////////////////////////
//
// ON_SubD - ON_Object overrides
//

//virtual 
void ON_SubD::MemoryRelocate()
{
}

static bool ON_SubDIsNotValid(bool bSilentError)
{
  ON_SubDIncrementErrorCount();
  return bSilentError ? false : ON_IsNotValid();
}

static bool EdgeVertexWeightIsSet(
  double edge_vertex_weight
  )
{
  return (0.0 < edge_vertex_weight && edge_vertex_weight < 1.0);
}

static bool EdgeSectorWeightIsValid(
  double edge_vertex_weight,
  ON_SubD::SubDType subdivision_type,
  const ON_SubDEdge* edge
  )
{
  if (0.0 <= edge_vertex_weight && edge_vertex_weight < 1.0)
    return true;

  if (ON_SubDSectorType::UnsetSectorWeight == edge_vertex_weight && ON_SubD::SubDType::Unset == subdivision_type && nullptr != edge && 0 == edge->m_level)
    return true;

  return false;
}

static bool IsValidVertexEdgeLink(
  const ON_SubDVertex* vertex,
  const ON_SubDEdge* edge,
  ON__UINT_PTR end_index,
  ON_SubD::SubDType subdivision_type,
  bool bSilentError
  )
{
  if (nullptr == vertex || nullptr == edge)
    return ON_SubDIsNotValid(bSilentError);

  if (end_index > 1)
    return ON_SubDIsNotValid(bSilentError);

  if (edge->m_vertex[end_index] != vertex)
    return ON_SubDIsNotValid(bSilentError);

  if (vertex->m_level != edge->m_level)
    return ON_SubDIsNotValid(bSilentError);

  if (false == EdgeSectorWeightIsValid(edge->m_sector_coefficient[end_index],subdivision_type,edge))
    return ON_SubDIsNotValid(bSilentError);

  //const ON__UINT_PTR corner_type = edge->m_vertex[end_index].CornerType();
  //if (ON_SubDVertexPtr::NoCorner != corner_type && ON_SubD::VertexTag::Corner != vertex->m_vertex_tag)
  //  return ON_SubDIsNotValid(bSilentError);

  if ( edge->IsSmooth(true) )
  {
    // edge->m_edge_tag is ON_SubD::EdgeTag::Smooth or ON_SubD::EdgeTag::X
    if (ON_SubD::VertexTag::Smooth == vertex->m_vertex_tag)
    {
      if (false == (0.0 == edge->m_sector_coefficient[end_index]))
        return ON_SubDIsNotValid(bSilentError);
    }
    else
    {
      const unsigned int tagged_end_index = edge->TaggedEndIndex();
      if (ON_SubD::EdgeTag::X == edge->m_edge_tag)
      {
        if (2 != tagged_end_index)
          return ON_SubDIsNotValid(bSilentError);
      }
      else
      {
        if (tagged_end_index != (unsigned int)end_index)
          return ON_SubDIsNotValid(bSilentError);
      }

      if (ON_SubD::SubDType::Unset == subdivision_type)
      {
        if (false == (ON_SubDSectorType::UnsetSectorWeight == edge->m_sector_coefficient[end_index]))
          return ON_SubDIsNotValid(bSilentError);
      }
      else
      {
        ON_SubDSectorType st = ON_SubDSectorType::Create(subdivision_type,edge,(unsigned int)end_index);
        if (!st.IsValid())
          return ON_SubDIsNotValid(bSilentError);

        const double expected_vertex_weight = st.SectorWeight();
        if (false == (expected_vertex_weight == edge->m_sector_coefficient[end_index]))
          return ON_SubDIsNotValid(bSilentError);

        if (false == EdgeVertexWeightIsSet(expected_vertex_weight))
          return ON_SubDIsNotValid(bSilentError);
      }
    }
  }
  else if(ON_SubD::EdgeTag::Crease == edge->m_edge_tag)
  {
    // crease edge
    if (!(0.0 == edge->m_sector_coefficient[end_index]))
      return ON_SubDIsNotValid(bSilentError);

    if (ON_SubD::VertexTag::Smooth == vertex->m_vertex_tag)
      return ON_SubDIsNotValid(bSilentError);

    if (ON_SubD::VertexTag::Unset == vertex->m_vertex_tag)
      return ON_SubDIsNotValid(bSilentError);
  }
  else
  {
    return ON_SubDIsNotValid(bSilentError);
  }

  return true;
}

static bool IsValidVertexFaceLink(
  const ON_SubDVertex* vertex,
  const ON_SubDFace* face,
  unsigned int vertex_face_index,
  unsigned int face_vertex_index,
  bool bSilentError
  )
{
  if (nullptr == vertex || nullptr == face)
    return ON_SubDIsNotValid(bSilentError);

  if (vertex->m_level != face->m_level)
    return ON_SubDIsNotValid(bSilentError);

  const unsigned int vertex_face_count = vertex->m_face_count;
  if (vertex_face_count <= 0)
    return ON_SubDIsNotValid(bSilentError);
  if (nullptr == vertex->m_faces)
    return ON_SubDIsNotValid(bSilentError);

  if (vertex_face_index >= vertex_face_count && ON_UNSET_UINT_INDEX != vertex_face_index)
    return ON_SubDIsNotValid(bSilentError);

  const unsigned int face_vertex_count = face->m_edge_count;
  if (face_vertex_count <= 0)
    return ON_SubDIsNotValid(bSilentError);
  if (face_vertex_count > 4 && nullptr == face->m_edgex)
    return ON_SubDIsNotValid(bSilentError);

  if (face_vertex_index >= face_vertex_count && ON_UNSET_UINT_INDEX != face_vertex_index)
    return ON_SubDIsNotValid(bSilentError);

  for (unsigned int i = 0; i < vertex_face_count; i++)
  {
    if (face == vertex->Face(i))
    {
      if (ON_UNSET_UINT_INDEX == vertex_face_index)
        vertex_face_index = i;
      else if (i != vertex_face_index)
        return ON_SubDIsNotValid(bSilentError);
    }
    else if (i == vertex_face_index)
    {
      return ON_SubDIsNotValid(bSilentError);
    }
  }

  for (unsigned int i = 0; i < face_vertex_count; i++)
  {
    if (vertex == face->Vertex(i))
    {
      if (ON_UNSET_UINT_INDEX == face_vertex_index)
        face_vertex_index = i;
      else if (i != face_vertex_index)
        return ON_SubDIsNotValid(bSilentError);
    }
    else if (i == face_vertex_index)
    {
      return ON_SubDIsNotValid(bSilentError);
    }
  }

  return true;
}


static bool IsValidEdgeFaceLink(
  const ON_SubDEdge* edge,
  const ON_SubDFace* face,
  unsigned int edge_face_index,
  unsigned int face_edge_index,
  bool bSilentError
  )
{
  if (nullptr == edge || nullptr == face)
    return ON_SubDIsNotValid(bSilentError);

  if (edge->m_level != face->m_level)
    return ON_SubDIsNotValid(bSilentError);

  const unsigned int edge_face_count = edge->m_face_count;
  if (edge_face_count <= 0)
    return ON_SubDIsNotValid(bSilentError);
  if (edge_face_count > 2 && nullptr == edge->m_facex)
    return ON_SubDIsNotValid(bSilentError);

  if (edge_face_index >= edge_face_count && ON_UNSET_UINT_INDEX != edge_face_index)
    return ON_SubDIsNotValid(bSilentError);

  const unsigned int face_edge_count = face->m_edge_count;
  if (face_edge_count <= 0)
    return ON_SubDIsNotValid(bSilentError);
  if (face_edge_count > 4 && nullptr == face->m_edgex)
    return ON_SubDIsNotValid(bSilentError);

  if (face_edge_index >= face_edge_count && ON_UNSET_UINT_INDEX != face_edge_index)
    return ON_SubDIsNotValid(bSilentError);

  for (unsigned int i = 0; i < edge_face_count; i++)
  {
    if (face == edge->Face(i))
    {
      if (ON_UNSET_UINT_INDEX == edge_face_index)
        edge_face_index = i;
      else if (i != edge_face_index)
        return ON_SubDIsNotValid(bSilentError);
    }
    else if (i == edge_face_index)
    {
      return ON_SubDIsNotValid(bSilentError);
    }
  }

  for (unsigned int i = 0; i < face_edge_count; i++)
  {
    if (edge == face->Edge(i))
    {
      if (ON_UNSET_UINT_INDEX == face_edge_index)
        face_edge_index = i;
      else if (i != face_edge_index)
        return ON_SubDIsNotValid(bSilentError);
    }
    else if (i == face_edge_index)
    {
      return ON_SubDIsNotValid(bSilentError);
    }
  }

  return true;
}


static bool IsValidSubDVertex(
  const ON_SubDVertex* vertex,
  unsigned short level,
  unsigned int* vertex_id_range,
  unsigned short ordinary_valence_count,
  bool bSilentError
  )
{
  if (nullptr == vertex)
    return ON_SubDIsNotValid(bSilentError);
  
  if (level != vertex->m_level)
    return ON_SubDIsNotValid(bSilentError);
  
  if (nullptr != vertex_id_range)
  {
    if (vertex->m_id < vertex_id_range[0])
      return ON_SubDIsNotValid(bSilentError);
    if (vertex->m_id > vertex_id_range[1])
      return ON_SubDIsNotValid(bSilentError);
  }

  if ( vertex->m_edge_count < vertex->m_face_count)
    return ON_SubDIsNotValid(bSilentError);

  if (vertex->m_edge_count > 0 && nullptr == vertex->m_edges)
    return ON_SubDIsNotValid(bSilentError);

  if (vertex->m_face_count > 0 && nullptr == vertex->m_faces)
    return ON_SubDIsNotValid(bSilentError);

  switch (vertex->m_vertex_tag)
  {
  case ON_SubD::VertexTag::Smooth: // interior vertex
    if (vertex->m_edge_count != vertex->m_face_count)
      return ON_SubDIsNotValid(bSilentError);
    break;
  case ON_SubD::VertexTag::Crease:
    if (vertex->m_face_count <= 0)
      return ON_SubDIsNotValid(bSilentError);
    break;
  case ON_SubD::VertexTag::Corner:
    break;
  case ON_SubD::VertexTag::Dart: // interior vertex
    if (level > 0 && ordinary_valence_count != vertex->m_edge_count)
      return ON_SubDIsNotValid(bSilentError);
    if (vertex->m_edge_count != vertex->m_face_count)
      return ON_SubDIsNotValid(bSilentError);
    break;
  default:
    // invalid value for this enum
    return ON_SubDIsNotValid(bSilentError);
    break;
  }

  unsigned int count = vertex->m_edge_count;
  for (unsigned int i = 0; i < count; i++)
  {
    const ON_SubDEdge* edge = vertex->Edge(i);
    if (nullptr == edge)
      return ON_SubDIsNotValid(bSilentError);
  }

  count = vertex->m_face_count;
  for (unsigned int i = 0; i < count; i++)
  {
    const ON_SubDFace* face = vertex->Face(i);
    if (nullptr == face)
      return ON_SubDIsNotValid(bSilentError);
  }

  return true;
}

static bool IsValidSubDEdge(
  const ON_SubDEdge* edge,
  unsigned short level,
  unsigned int* edge_id_range,
  bool bSilentError
  )
{
  if (nullptr == edge)
    return ON_SubDIsNotValid(bSilentError);

  if (level != edge->m_level)
    return ON_SubDIsNotValid(bSilentError);

  if (nullptr != edge_id_range)
  {
    if (edge->m_id < edge_id_range[0])
      return ON_SubDIsNotValid(bSilentError);
    if (edge->m_id > edge_id_range[1])
      return ON_SubDIsNotValid(bSilentError);
  }

  const ON_SubDVertex* edge_vertex[2] = { 0 };
  for (unsigned int i = 0; i < 2; i++)
  {
    const ON_SubDVertex* vertex = edge->Vertex(i);
    if (nullptr == vertex)
      return ON_SubDIsNotValid(bSilentError);
    edge_vertex[i] = vertex;
  }
  if (edge_vertex[0] == edge_vertex[1])
    return ON_SubDIsNotValid(bSilentError);

  if (edge->IsSmooth(true))
  {
    // m_edge_tag is ON_SubD::EdgeTag::Smooth or ON_SubD::EdgeTag::X
    if ( 2 != edge->m_face_count)
      return ON_SubDIsNotValid(bSilentError);
  }
  else if (ON_SubD::EdgeTag::Crease != edge->m_edge_tag)
  {
    return ON_SubDIsNotValid(bSilentError);
  }

  if (edge->m_face_count > 2 && nullptr == edge->m_facex)
    return ON_SubDIsNotValid(bSilentError);


  return true;
}

static bool IsValidSubDFace(
  const ON_SubDFace* face,
  unsigned short level,
  unsigned int* face_id_range,
  unsigned short ordinary_face_edge_count,
  bool bSilentError
  )
{
  if (nullptr == face)
    return ON_SubDIsNotValid(bSilentError);

  if (level != face->m_level)
    return ON_SubDIsNotValid(bSilentError);

  if (nullptr != face_id_range)
  {
    if (face->m_id < face_id_range[0])
      return ON_SubDIsNotValid(bSilentError);
    if (face->m_id > face_id_range[1])
      return ON_SubDIsNotValid(bSilentError);
  }

  if (face->m_edge_count < 3)
    return ON_SubDIsNotValid(bSilentError);

  if (face->m_edge_count > 4 && nullptr == face->m_edgex)
    return ON_SubDIsNotValid(bSilentError);

  if (level > 0 && ordinary_face_edge_count != face->m_edge_count)
    return ON_SubDIsNotValid(bSilentError);

  return true;
}

bool ON_SubDimple::IsValidLevel(
  const ON_SubD& subd,
  unsigned int level_index,
  bool bSilentError,
  ON_TextLog* text_log
  ) const
{
  const unsigned int level_count = m_levels.UnsignedCount();
  if (level_index >= level_count || level_index >= 0xFFFF)
    return ON_SubDIsNotValid(bSilentError);
  const ON_SubDLevel* level = m_levels[level_index];
  if ( nullptr == level)
    return ON_SubDIsNotValid(bSilentError);

  if ( level->m_level_index != level_index)
    return ON_SubDIsNotValid(bSilentError);

  const ON_SubD::SubDType subdivision_type = level->m_subdivision_type;

  if (level_index <= 0)
  {
    if (level->m_vertex_count < 3)
      return ON_SubDIsNotValid(bSilentError);
    if (level->m_edge_count < 3)
      return ON_SubDIsNotValid(bSilentError);
    if (level->m_face_count < 1)
      return ON_SubDIsNotValid(bSilentError);
  }
  else
  {
    const ON_SubDLevel* previous_level = m_levels[level_index - 1];
    if (nullptr == previous_level)
      return ON_SubDIsNotValid(bSilentError);
    if (level->m_vertex_count <= previous_level->m_vertex_count)
      return ON_SubDIsNotValid(bSilentError);
    if (level->m_edge_count <= previous_level->m_edge_count)
      return ON_SubDIsNotValid(bSilentError);
    if (level->m_face_count <= previous_level->m_face_count)
      return ON_SubDIsNotValid(bSilentError);
    if (ON_SubD::SubDType::TriLoopWarren != level->m_subdivision_type && ON_SubD::SubDType::QuadCatmullClark != level->m_subdivision_type)
      return ON_SubDIsNotValid(bSilentError);
  }

  if (nullptr == level->m_vertex[0])
    return ON_SubDIsNotValid(bSilentError);
  if (nullptr == level->m_edge[0])
    return ON_SubDIsNotValid(bSilentError);
  if (nullptr == level->m_face[0])
    return ON_SubDIsNotValid(bSilentError);

  if (nullptr == level->m_vertex[1])
    return ON_SubDIsNotValid(bSilentError);
  if (nullptr == level->m_edge[1])
    return ON_SubDIsNotValid(bSilentError);
  if (nullptr == level->m_face[1])
    return ON_SubDIsNotValid(bSilentError);


  const unsigned short expected_level = (unsigned short)level_index;
  unsigned int i;
  const ON_SubDVertex* vertex;
  const ON_SubDEdge* edge;
  const ON_SubDFace* face;

  unsigned int v_id_range[2] = { ON_UNSET_UINT_INDEX, ON_UNSET_UINT_INDEX };
  unsigned int e_id_range[2] = { ON_UNSET_UINT_INDEX, ON_UNSET_UINT_INDEX };
  unsigned int f_id_range[2] = { ON_UNSET_UINT_INDEX, ON_UNSET_UINT_INDEX };

  unsigned int point_vertex_count = 0;
  unsigned int wire_edge_count = 0;

  // simple vertex validation
  if (level_index == subd.ActiveLevelIndex())
  {
    if (subd.FirstVertex() != level->m_vertex[0])
      return ON_SubDIsNotValid(bSilentError);
    ON_SubDVertexIterator vit = subd.VertexIterator();
    if (vit.FirstVertex() != level->m_vertex[0])
      return ON_SubDIsNotValid(bSilentError);
    ON_SubDVertexArray va = subd.VertexArray();
    if (va.VertexCount() != level->m_vertex_count)
      return ON_SubDIsNotValid(bSilentError);
    if (va[0] != level->m_vertex[0])
      return ON_SubDIsNotValid(bSilentError);
    if (va[level->m_vertex_count-1] != level->m_vertex[1])
      return ON_SubDIsNotValid(bSilentError);
  }
  const ON_SubDVertex* last_vertex = nullptr;
  for (i = 0, vertex = level->m_vertex[0]; i < level->m_vertex_count && nullptr != vertex; i++, vertex = vertex->m_next_vertex)
  {
    if (false == IsValidSubDVertex(vertex, expected_level, nullptr, level->m_ordinary_vertex_valence, bSilentError))
      return false;

    if (0 == i)
    {
      v_id_range[0] = v_id_range[1] = vertex->m_id;
    }
    else if (vertex->m_id < v_id_range[0])
      v_id_range[0] = vertex->m_id;
    else if (vertex->m_id > v_id_range[1])
      v_id_range[1] = vertex->m_id;

    if (0 == vertex->m_edge_count)
    {
      point_vertex_count++;
    }
    last_vertex = vertex;
  }
  if (level->m_vertex[1] != last_vertex)
    return ON_SubDIsNotValid(bSilentError);


  if (i != level->m_vertex_count || nullptr != vertex)
    return ON_SubDIsNotValid(bSilentError);
  if (1 + v_id_range[1] - v_id_range[0] < level->m_vertex_count)
    return ON_SubDIsNotValid(bSilentError);

  // currently, point vertices are not permitted
  if (point_vertex_count > 0)
    return ON_SubDIsNotValid(bSilentError);

  // simple edge validation
  if (level_index == subd.ActiveLevelIndex())
  {
    if (subd.FirstEdge() != level->m_edge[0])
      return ON_SubDIsNotValid(bSilentError);
    ON_SubDEdgeIterator eit = subd.EdgeIterator();
    if (eit.FirstEdge() != level->m_edge[0])
      return ON_SubDIsNotValid(bSilentError);
    ON_SubDEdgeArray ea = subd.EdgeArray();
    if (ea.EdgeCount() != level->m_edge_count)
      return ON_SubDIsNotValid(bSilentError);
    if (ea[0] != level->m_edge[0])
      return ON_SubDIsNotValid(bSilentError);
    if (ea[level->m_edge_count-1] != level->m_edge[1])
      return ON_SubDIsNotValid(bSilentError);
  }
  const ON_SubDEdge* last_edge = nullptr;
  for (i = 0, edge = level->m_edge[0]; i < level->m_edge_count && nullptr != edge; i++, edge = edge->m_next_edge)
  {
    if (false == IsValidSubDEdge(edge, expected_level, nullptr, bSilentError))
      return false;
    if (0 == edge->m_face_count)
    {
      wire_edge_count++;
    }
    if (0 == i)
    {
      e_id_range[0] = e_id_range[1] = edge->m_id;
    }
    else if (edge->m_id < e_id_range[0])
      e_id_range[0] = edge->m_id;
    else if (edge->m_id > e_id_range[1])
      e_id_range[1] = edge->m_id;

    last_edge = edge;
  }
  if (i != level->m_edge_count || nullptr != edge)
    return ON_SubDIsNotValid(bSilentError);
  if (1 + e_id_range[1] - e_id_range[0] < level->m_edge_count)
    return ON_SubDIsNotValid(bSilentError);
  if (level->m_edge[1] != last_edge)
    return ON_SubDIsNotValid(bSilentError);


  // currently, wire edges are not permitted
  if (wire_edge_count > 0)
    return ON_SubDIsNotValid(bSilentError);

  // simple face validation
  if (level_index == subd.ActiveLevelIndex())
  {
    if (subd.FirstFace() != level->m_face[0])
      return ON_SubDIsNotValid(bSilentError);
    ON_SubDFaceIterator fit = subd.FaceIterator();
    if (fit.FirstFace() != level->m_face[0])
      return ON_SubDIsNotValid(bSilentError);
    ON_SubDFaceArray fa = subd.FaceArray();
    if (fa.FaceCount() != level->m_face_count)
      return ON_SubDIsNotValid(bSilentError);
    if (fa[0] != level->m_face[0])
      return ON_SubDIsNotValid(bSilentError);
    if (fa[0] != level->m_face[0])
      return ON_SubDIsNotValid(bSilentError);
  }
  const ON_SubDFace* last_face = nullptr;
  for (i = 0, face = level->m_face[0]; i < level->m_face_count && nullptr != face; i++, face = face->m_next_face)
  {
    if (false == IsValidSubDFace(face, expected_level, nullptr, level->m_ordinary_face_edge_count, bSilentError))
      return false;

    if (0 == i)
    {
      f_id_range[0] = f_id_range[1] = face->m_id;
    }
    else if (face->m_id < f_id_range[0])
      f_id_range[0] = face->m_id;
    else if (face->m_id > f_id_range[1])
      f_id_range[1] = face->m_id;

    last_face = face;
  }
  if (i != level->m_face_count || nullptr != face)
    return ON_SubDIsNotValid(bSilentError);
  if (1 + f_id_range[1] - f_id_range[0] < level->m_face_count)
    return ON_SubDIsNotValid(bSilentError);  
  if (level->m_face[1] != last_face)
    return ON_SubDIsNotValid(bSilentError);


  // vertex topology validation
  for (vertex = level->m_vertex[0]; nullptr != vertex; vertex = vertex->m_next_vertex)
  {
    for (i = 0; i < vertex->m_edge_count; i++)
    {
      edge = vertex->Edge(i);
      if (false == IsValidSubDEdge(edge, expected_level, e_id_range, bSilentError))
        return false;
      if (false == IsValidVertexEdgeLink(vertex, edge, vertex->EdgeDirection(i), subdivision_type, bSilentError))
        return false;
    }

    for (i = 0; i < vertex->m_face_count; i++)
    {
      face = vertex->Face(i);
      if (false == IsValidSubDFace(face, expected_level, f_id_range, level->m_ordinary_face_edge_count, bSilentError))
        return false;
      if (false == IsValidVertexFaceLink(vertex, face, i, ON_UNSET_UINT_INDEX, bSilentError))
        return false;
    }
  }

  // edge topology validation
  for (edge = level->m_edge[0]; nullptr != edge; edge = edge->m_next_edge)
  {
    for (i = 0; i < 2; i++)
    {
      vertex = edge->m_vertex[i];
      if (false == IsValidSubDVertex(vertex, expected_level, v_id_range, level->m_ordinary_vertex_valence, bSilentError))
        return false;
      if (false == IsValidVertexEdgeLink(vertex, edge, i, subdivision_type, bSilentError))
        return false;
    }

    for (i = 0; i < edge->m_face_count; i++)
    {
      face = edge->Face(i);
      if (false == IsValidSubDFace(face, expected_level, f_id_range, level->m_ordinary_face_edge_count, bSilentError))
        return false;
      if (false == IsValidEdgeFaceLink(edge, face, i, ON_UNSET_UINT_INDEX, bSilentError))
        return false;
    }
  }

  // face topology validation
  for (face = level->m_face[0]; nullptr != face; face = face->m_next_face)
  {
    for (i = 0; i < face->m_edge_count; i++)
    {
      edge = face->Edge(i);
      if (false == IsValidSubDEdge(edge, expected_level, e_id_range, bSilentError))
        return false;
      if (false == IsValidEdgeFaceLink(edge, face, ON_UNSET_UINT_INDEX, i, bSilentError))
        return false;
    }

    for (i = 0; i < face->m_edge_count; i++)
    {
      vertex = face->Vertex(i);
      if (false == IsValidSubDVertex(vertex, expected_level, v_id_range, level->m_ordinary_vertex_valence, bSilentError))
        return false;
      if (false == IsValidVertexFaceLink(vertex, face, ON_UNSET_UINT_INDEX, i, bSilentError))
        return false;
    }
  }

  return true;
}

bool ON_SubDimple::IsValid(
  const ON_SubD& subd,
  bool bSilentError,
  ON_TextLog* text_log
  ) const
{
  const unsigned int level_count = m_levels.UnsignedCount();
  if (level_count < 1)
  {
    return ON_SubDIsNotValid(bSilentError);
  }
  for (unsigned int level_index = 0; level_index < level_count; level_index++)
  {
    if (false == IsValidLevel(subd, level_index, bSilentError, text_log))
      return false;
  }
  return true;
}

//virtual
bool ON_SubD::IsValid(ON_TextLog* text_logx) const
{
  // If low bit of text_log pointer is 1, then ON_Error is not called when the
  // knot vector is invalid.
  const ON__INT_PTR lowbit = 1;
  const ON__INT_PTR hightbits = ~lowbit;
  const bool bSilentError = (0 != (lowbit & ((ON__INT_PTR)text_logx)));
  ON_TextLog* text_log = (ON_TextLog*)(((ON__INT_PTR)text_logx) & hightbits);
  
  const ON_SubDimple* subdimple = SubDimple();
  if (nullptr == subdimple)
    return ON_SubDIsNotValid(bSilentError);

  return subdimple->IsValid(*this, bSilentError, text_log);
}

//virtual
void ON_SubD::Dump(ON_TextLog& text_log) const
{
  unsigned int component_sample_count = 16; // dump the first 16 vertices, edges, faces
  ON_2udex id_range;
  id_range.i = component_sample_count;
  id_range.j = 0;
  DumpTopology(id_range,id_range,id_range,text_log);
}

unsigned int ON_SubD::DumpTopology(ON_TextLog & text_log) const
{
  return DumpTopology(ON_2udex::Zero,ON_2udex::Zero,ON_2udex::Zero,text_log);
}


unsigned int ON_SubD::DumpTopology(
  ON_2udex vertex_id_range,
  ON_2udex edge_id_range,
  ON_2udex face_id_range,
  ON_TextLog& text_log
) const
{
  const class ON_SubDimple* subdimple = SubDimple();
  if (nullptr == subdimple)
  {
    text_log.Print(L"SubD: Empty\n");
    return 0;
  }

  const unsigned int level_count = LevelCount();
  const unsigned int active_level_index = ActiveLevel().m_level_index;
  if (level_count > 1)
    text_log.Print(L"SubD: %u levels. Level %u is active.\n", level_count, active_level_index);
  else
    text_log.Print(L"SubD:\n");

  ON_SubDLevelIterator lit(subdimple->LevelIterator());

  const ON_2udex empty_id_range(ON_UNSET_UINT_INDEX, 0);

  unsigned int error_count = 0;
  for (const ON_SubDLevel* level = lit.First(); nullptr != level; level = lit.Next())
  {
    ON_TextLogIndent indent1(text_log);
    if (nullptr == level)
      continue;
    ON_2udex level_vertex_id_range
      = (0 != vertex_id_range.j || active_level_index == level->m_level_index)
      ? vertex_id_range
      : empty_id_range;
    ON_2udex level_edge_id_range
      = (0 != edge_id_range.j || active_level_index == level->m_level_index)
      ? edge_id_range
      : empty_id_range;
    ON_2udex level_face_id_range
      = (0 != face_id_range.j || active_level_index == level->m_level_index)
      ? face_id_range
      : empty_id_range;

    error_count += level->DumpTopology(level_vertex_id_range, level_edge_id_range, level_face_id_range, text_log);
  }

  return error_count;
}

unsigned int ON_SubDLevel::DumpTopology(
  ON_2udex vertex_id_range,
  ON_2udex edge_id_range,
  ON_2udex face_id_range,
  ON_TextLog& text_log
) const
{
  // NOTE WELL:
  //  This is a debugging tool.
  //  The code in this function needs to work when the topology information is corrupt.
  const unsigned short level_index = (unsigned short)m_level_index;

  const unsigned int vertex_max_dump_count 
    = (vertex_id_range.i > 0 && vertex_id_range.i != ON_UNSET_UINT_INDEX && 0 == vertex_id_range.j)
    ? vertex_id_range.i 
    : 0;
  const unsigned int edge_max_dump_count 
    = (edge_id_range.i > 0 && edge_id_range.i != ON_UNSET_UINT_INDEX && 0 == edge_id_range.j)
    ? edge_id_range.i
    : 0;
  const unsigned int face_max_dump_count 
    = (face_id_range.i > 0 && face_id_range.i != ON_UNSET_UINT_INDEX && 0 == face_id_range.j)
    ? face_id_range.i 
    : 0;

  const bool bVertexIdTest = (vertex_max_dump_count > 0) || (vertex_id_range.i < vertex_id_range.j) || (ON_UNSET_UINT_INDEX == (vertex_id_range.i));
  const bool bEdgeIdTest =  (edge_max_dump_count > 0) || (edge_id_range.i < edge_id_range.j) || (ON_UNSET_UINT_INDEX == (edge_id_range.i));
  const bool bFaceIdTest =  (face_max_dump_count > 0) || (face_id_range.i < face_id_range.j) || (ON_UNSET_UINT_INDEX == (face_id_range.i));

  const char error_code_point = '!';
  char prefix[16] = {};

  unsigned int vertex_error_count = 0;
  unsigned int edge_error_count = 0;
  unsigned int face_error_count = 0;

  text_log.Print(L"SubD level %u topology: %u vertices, %u edges, ", m_level_index, m_vertex_count, m_edge_count);

  unsigned int ngon_count[65] = {};
  unsigned int maxN = (unsigned int)(sizeof(ngon_count) / sizeof(ngon_count[0])) - 1;

  unsigned int face_count = 0;
  unsigned int uniformN = 0;
  for (const ON_SubDFace* f = m_face[0]; nullptr != f; f = f->m_next_face)
  {
    if (face_count >= m_face_count && f->m_level != level_index)
      break;
    face_count++;
    unsigned int N = f->EdgeCount();
    if (1 == face_count)
      uniformN = N;
    else if (N != uniformN)
      uniformN = 0;
    unsigned int j = (N < maxN) ? N : maxN;
    if (N < maxN)
      ngon_count[j]++;
  }

  if (face_count != m_face_count)
    face_error_count++;


  if (uniformN > 0 && face_count == m_face_count)
  {
    if (3 == uniformN)
      text_log.Print(L"%u triangles\n", m_face_count);
    else if (4 == uniformN)
      text_log.Print(L"%u quads\n", m_face_count);
    else
      text_log.Print(L"%u %u-gons\n", m_face_count, uniformN);
  }
  else
  {
    text_log.Print(L"%u faces\n", m_face_count );
    text_log.PushIndent();
    for (unsigned int N = 0; N <= maxN; N++)
    {
      if (0 == ngon_count[N])
        continue;
      if (3 == N)
        text_log.Print(L"%u triangles\n", ngon_count[N]);
      else if (4 == N)
        text_log.Print(L"%u quads\n", ngon_count[N]);
      else if (N < maxN)
        text_log.Print(L"%u %u-gons\n", ngon_count[N], N);
      else
        text_log.Print(L"%u N-gons\n", ngon_count[N]);
    }
    text_log.PopIndent();
  }

  if (IsEmpty())
    return 0;

  ///////////////////////////////////////////////////////////////////
  //
  // Vertex Topology
  // vN (x, y, z)
  //   vEdges[vertex_edge_count] = { +eA, -eB, ... }
  //   vFaces[vertex_edge_count] = { fP, fQ, fR, ... }
  //
  unsigned int vertex_count = 0;
  unsigned int vertex_dump_count = 0;
  for (const ON_SubDVertex* v = m_vertex[0]; nullptr != v; v = v->m_next_vertex)
  {
    if (vertex_count >= m_vertex_count && v->m_level != level_index)
      break;
    vertex_count++;

    if (bVertexIdTest)
    {
      if (ON_UNSET_UINT_INDEX == vertex_id_range.i)
        continue;
      if (vertex_max_dump_count > 0)
      {
        if (vertex_count > vertex_max_dump_count)
          continue;
      }
      else
      {
        if (v->m_id < vertex_id_range.i || v->m_id >= vertex_id_range.j)
          continue;
      }
    }

    if (0 == vertex_dump_count)
      text_log.Print(L"Vertices:\n");
    vertex_dump_count++;
    ON_TextLogIndent vindent(text_log);

    text_log.Print(
      "v%u: (%g, %g, %g)\n",
      v->m_id, v->m_P[0], v->m_P[1], v->m_P[2]
    );

    text_log.PushIndent();

    const unsigned int vertex_edge_count = v->m_edge_count;
    text_log.Print("vEdges[%u] = {", vertex_edge_count);
    prefix[0] = ON_String::Space;
    prefix[1] = error_code_point;
    prefix[2] = 'e';
    prefix[3] = 0;
    for (unsigned int vei = 0; vei < vertex_edge_count; vei++)
    {
      prefix[1] = error_code_point;
      if (1 == vei)
      {
        prefix[0] = ',';
      }
      const ON_SubDEdge* e = v->Edge(vei);
      unsigned int eid = 0;
      if (nullptr != e)
      {
        if (v == e->m_vertex[0] && v != e->m_vertex[1])
          prefix[1] = '+';
        else if (v != e->m_vertex[0] && v == e->m_vertex[1])
          prefix[1] = '-';
        eid = e->m_id;
      }
      text_log.Print("%s%u", prefix, eid);
      if (error_code_point == prefix[1])
        vertex_error_count++;
    }
    text_log.Print(" }\n");

    const unsigned int vertex_face_count = v->m_face_count;
    text_log.Print("v.Faces[%u] = {", vertex_face_count);
    prefix[0] = ON_String::Space;
    prefix[1] = ON_String::Space;
    prefix[2] = 'f';
    prefix[3] = 0;
    for (unsigned int vfi = 0; vfi < vertex_face_count; vfi++)
    {
      prefix[1] = error_code_point;
      if (1 == vfi)
      {
        prefix[0] = ',';
      }
      const ON_SubDFace* f = v->Face(vfi);
      unsigned int fid = 0;
      if (nullptr != f)
      {
        if (f->VertexIndex(v) < ON_UNSET_UINT_INDEX)
          prefix[1] = ON_String::Space;
        fid = f->m_id;
      }
      text_log.Print("%s%u", prefix, fid);
      if (error_code_point == prefix[1])
        vertex_error_count++;
    }
    text_log.Print(" }\n");
    text_log.PopIndent();
  }

  if (vertex_dump_count > 0 && vertex_dump_count < vertex_count)
  {
    text_log.PushIndent();
    text_log.Print(L"... %u additional vertices.\n", vertex_count-vertex_dump_count);
    text_log.PopIndent();
  }

  ///////////////////////////////////////////////////////////////////
  //
  // Edge Topology
  // eN (+vA, -vB)
  //   eFaces[edge_face_count] = { fP, fQ, fR, ... }
  //
  unsigned int edge_count = 0;
  unsigned int edge_dump_count = 0;
  for (const ON_SubDEdge* e = m_edge[0]; nullptr != e; e = e->m_next_edge)
  {
    if (edge_count >= m_edge_count && e->m_level != level_index)
      break;
    edge_count++;

    if (bEdgeIdTest)
    {
      if (ON_UNSET_UINT_INDEX == edge_id_range.i)
        continue;
      if (edge_max_dump_count > 0)
      {
        if (edge_count > edge_max_dump_count)
          continue;
      }
      else
      {
        if (e->m_id < edge_id_range.i || e->m_id >= edge_id_range.j)
          continue;
      }
    }

    if (0 == edge_dump_count)
      text_log.Print(L"Edges:\n");
    edge_dump_count++;
    ON_TextLogIndent eindent(text_log);

    text_log.Print(
      "e%u:  (",
      e->m_id
    );

    prefix[0] = ON_String::Space;
    prefix[1] = error_code_point;
    prefix[2] = 'v';
    prefix[3] = 0;
    for (unsigned int evi = 0; evi < 2; evi++)
    {
      if (1 == evi)
        text_log.Print(" to");
      prefix[1] = error_code_point;
      const ON_SubDVertex* v = e->m_vertex[evi];
      unsigned int vid = 0;
      if (nullptr != v)
      {
        vid = v->m_id;
        if (v->EdgeArrayIndex(e) < ON_UNSET_INT_INDEX)
          prefix[1] = ON_String::Space;
      }
      if (error_code_point == prefix[1])
        edge_error_count++;
      text_log.Print("%s%u", prefix, vid);
    }
    text_log.Print(")\n");

    text_log.PushIndent();

    const unsigned int edge_face_count = e->m_face_count;
    text_log.Print("eFaces[%u] = {", edge_face_count);
    prefix[0] = ON_String::Space;
    prefix[1] = error_code_point;
    prefix[2] = 'f';
    prefix[3] = 0;
    for (unsigned int efi = 0; efi < edge_face_count; efi++)
    {
      prefix[1] = error_code_point;
      if (1 == efi)
      {
        prefix[0] = ',';
      }
      ON_SubDFacePtr fptr = e->FacePtr(efi);
      const ON_SubDFace* f = fptr.Face();
      const ON__UINT_PTR edge_fdir = fptr.FaceDirection();
      unsigned int fid = 0;
      if (nullptr != f)
      {
        fid = f->m_id;
        ON_SubDEdgePtr eptr = f->EdgePtr(e);
        if (eptr.Edge() == e && eptr.EdgeDirection() == edge_fdir)
        {
          prefix[1] = (0 == edge_fdir) ? '+' : '-';
        }
      }
      if (error_code_point == prefix[1])
        edge_error_count++;
      text_log.Print("%s%u", prefix, fid);
    }
    text_log.Print(" }\n");
    text_log.PopIndent();
  }

  if (edge_dump_count > 0 && edge_dump_count < edge_count)
  {
    text_log.PushIndent();
    text_log.Print(L"... %u additional edges.\n", edge_count - edge_dump_count);
    text_log.PopIndent();
  }


  ///////////////////////////////////////////////////////////////////
  //
  // Face Topology
  // fN
  //   fEdges[face_edge_count] = { +eA, -eB, +eC, ...}
  //   fVertices[face_edge_count] = { vP, vQ, vR, ... }
  //
  face_count = 0;
  unsigned int face_dump_count = 0;
  for (const ON_SubDFace* f = m_face[0]; nullptr != f; f = f->m_next_face)
  {
    if (face_count >= m_face_count && f->m_level != level_index)
      break;
    face_count++;

    if (bFaceIdTest)
    {
      if (ON_UNSET_UINT_INDEX == face_id_range.i)
        continue;
      if (face_max_dump_count > 0)
      {
        if (face_count > face_max_dump_count)
          continue;
      }
      else
      {
        if (f->m_id < face_id_range.i || f->m_id >= face_id_range.j)
          continue;
      }
    }

    if (0 == face_dump_count)
      text_log.Print(L"Faces:\n");
    face_dump_count++;
    ON_TextLogIndent eindent(text_log);

    text_log.Print(
      "f%u:\n",
      f->m_id
    );

    text_log.PushIndent();

    const unsigned int face_edge_count = f->m_edge_count;
    text_log.Print("fEdges[%u] = {", face_edge_count);
    prefix[0] = ON_String::Space;
    prefix[1] = error_code_point;
    prefix[2] = 'e';
    prefix[3] = 0;
    for (unsigned int fei = 0; fei < face_edge_count; fei++)
    {
      prefix[1] = error_code_point;
      if (1 == fei)
      {
        prefix[0] = ',';
      }
      const ON_SubDEdgePtr eptr = f->EdgePtr(fei);
      const ON_SubDEdge* e = eptr.Edge();
      const ON__UINT_PTR face_edir = eptr.EdgeDirection();
      unsigned int eid = 0;
      if (nullptr != e)
      {
        eid = e->m_id;
        ON_SubDFacePtr fptr = e->FacePtr(f);
        if (fptr.Face() == f && fptr.FaceDirection() == face_edir)
        {
          prefix[1] = (0 == face_edir) ? '+' : '-';
        }
      }
      if (error_code_point == prefix[1])
        face_error_count++;
      text_log.Print("%s%u", prefix, eid);
    }
    text_log.Print(" }\n");

    const ON_SubDEdgePtr last_eptr = f->EdgePtr(face_edge_count - 1);
    const ON_SubDEdge* last_edge = last_eptr.Edge();
    const ON_SubDVertex* v1
      = (nullptr != last_edge)
      ? last_edge->m_vertex[0 == last_eptr.EdgeDirection() ? 1 : 0]
      : nullptr;

    text_log.Print("fVertices[%u] = {", face_edge_count);
    prefix[0] = ON_String::Space;
    prefix[1] = error_code_point;
    prefix[2] = 'v';
    prefix[3] = 0;
    for (unsigned int fei = 0; fei < face_edge_count; fei++)
    {
      prefix[1] = error_code_point;
      if (1 == fei)
      {
        prefix[0] = ',';
      }
      const ON_SubDEdgePtr eptr = f->EdgePtr(fei);
      const ON_SubDEdge* e = eptr.Edge();
      const ON__UINT_PTR face_edir = eptr.EdgeDirection();
      unsigned int vid = 0;
      if (nullptr == e)
      {
        v1 = nullptr;
      }
      else
      {
        const ON_SubDVertex* v0 = e->m_vertex[0 == face_edir ? 0 : 1];
        if (nullptr != v0)
        {
          vid = v0->m_id;
          if (v1 == v0)
            prefix[1] = ON_String::Space;
        }
        v1 = e->m_vertex[0 == face_edir ? 1 : 0];
      }
      if (error_code_point == prefix[1])
        face_error_count++;
      text_log.Print("%s%u", prefix, vid);
    }
    text_log.Print(" }\n");

    text_log.PopIndent();
  }

  if (face_dump_count > 0 && face_dump_count < face_count)
  {
    text_log.PushIndent();
    text_log.Print(L"... %u additional faces.\n", face_count - face_dump_count);
    text_log.PopIndent();
  }
 
  const unsigned int topology_error_count
    = vertex_error_count
    + edge_error_count
    + face_error_count;

  text_log.PushIndent();
  if (0 == topology_error_count)
  {
    text_log.Print("No topology inconsistencies.\n");
  }
  else
  {
    text_log.Print(
      "ERRORS: %u vertex, %u edge, %u face topology inconsistencies marked with \"%c\".\n", 
      vertex_error_count,
      edge_error_count,
      face_error_count, 
      error_code_point
    );
  }
  text_log.PopIndent();

  return topology_error_count;
}


//virtual
unsigned int ON_SubD::SizeOf() const
{
  size_t sz = ON_Geometry::SizeOf();
  sz += sizeof(*this) - sizeof(ON_Geometry);
  const ON_SubDimple* subdimple = SubDimple();
  if ( subdimple )
    sz += subdimple->SizeOf();
  return (unsigned int)sz;
}

//virtual
ON__UINT32 ON_SubD::DataCRC(ON__UINT32 current_remainder) const
{
  return 0;
}

//virtual
ON::object_type ON_SubD::ObjectType() const
{
  return ON::subd_object;
}

//virtual
void ON_SubD::DestroyRuntimeCache( bool bDelete )
{
  const ON_SubDimple* dimple = SubDimple();
  if (nullptr != dimple)
  {
    unsigned int level_count = dimple->LevelCount();
    for (unsigned int level_index = 0; level_index < level_count; level_index++)
    {
      const ON_SubDLevel* level = dimple->SubDLevel(level_index);
      if (level)
      {
        level->ClearBoundingBox();
        level->ClearEdgeFlags();
        level->ClearSubdivisonAndLimitPoints();
        level->m_limit_mesh = ON_SubDLimitMesh::Empty;
        level->AggregateComponentStatus().MarkAsNotCurrent();
      }
    }
  }
  return;
}

//virtual 
int ON_SubD::Dimension() const
{
  return 3;
}

//virtual
bool ON_SubD::GetBBox(
        double* boxmin,
        double* boxmax,
        bool bGrowBox
        ) const
{ 
  int j;
  for ( j = 0; j < 3 && bGrowBox; j++ )
  {
    if ( !ON_IsValid(boxmin[j]) || !ON_IsValid(boxmax[j]) || boxmin[j] > boxmax[j])
      bGrowBox = false;
  }

  ON_BoundingBox bbox = ON_BoundingBox::EmptyBoundingBox;
  bool rc = false;

  bbox = ActiveLevel().BoundingBox();
  rc = bbox.IsValid();
  if (rc)
  {
    if (bGrowBox)
    {
      if (bbox.m_min.x < boxmin[0]) boxmin[0] = bbox.m_min.x;
      if (bbox.m_max.x > boxmax[0]) boxmax[0] = bbox.m_max.x;
      if (bbox.m_min.y < boxmin[1]) boxmin[1] = bbox.m_min.y;
      if (bbox.m_max.y > boxmax[1]) boxmax[1] = bbox.m_max.y;
      if (bbox.m_min.z < boxmin[2]) boxmin[2] = bbox.m_min.z;
      if (bbox.m_max.z > boxmax[2]) boxmax[2] = bbox.m_max.z;
    }
    else
    {
      boxmin[0] = bbox.m_min.x; boxmin[1] = bbox.m_min.y; boxmin[2] = bbox.m_min.z;
      boxmax[0] = bbox.m_max.x; boxmax[1] = bbox.m_max.y; boxmax[2] = bbox.m_max.z;
    }
  }

  return (rc || bGrowBox);
}



class /*DO NOT EXPORT*/ ON_SubD_GetTightBoundingBoxContext
{
public:
  ON_SubD_GetTightBoundingBoxContext(const ON_Xform* xform)
  {
    if (nullptr != xform && xform->IsNotIdentity() && false == xform->IsZero() )
      m_xform = *xform;
    else
      m_xform.m_xform[0][0] = ON_UNSET_VALUE;
    m_bbox.m_min.x = ON_UNSET_VALUE;
  }
  ON_BoundingBox m_bbox = ON_BoundingBox::EmptyBoundingBox;
  ON_Xform m_xform = ON_Xform::IdentityTransformation;
  static bool FragmentCallback(
    ON__UINT_PTR context_as_void, 
    const class ON_SubDLimitMeshFragment* fragment
    );
};

bool ON_SubD_GetTightBoundingBoxContext::FragmentCallback(
    ON__UINT_PTR context_as_void, 
    const class ON_SubDLimitMeshFragment* fragment
    )
{
  ON_SubD_GetTightBoundingBoxContext* context = (ON_SubD_GetTightBoundingBoxContext*)context_as_void;

    ON_BoundingBox bbox;
  if (ON_UNSET_VALUE == context->m_xform.m_xform[0][0])
  {
    bbox = fragment->m_bbox;
  }
  else
  {
    const double* P = fragment->m_P;
    const size_t P_stride = fragment->m_P_stride;
    bbox.m_min = context->m_xform*ON_3dPoint(P);
    bbox.m_max = bbox.m_min;
    for (const double* P1 = P + fragment->m_P_count*P_stride; P < P1; P += P_stride)
    {
      const ON_3dPoint Q = context->m_xform*ON_3dPoint(P);
      if ( Q.x < bbox.m_min.x )
        bbox.m_min.x = Q.x;
      else if ( Q.x > bbox.m_max.x )
        bbox.m_max.x = Q.x;
      if ( Q.y < bbox.m_min.y )
        bbox.m_min.y = Q.y;
      else if ( Q.y > bbox.m_max.y )
        bbox.m_max.y = Q.y;
      if ( Q.z < bbox.m_min.z )
        bbox.m_min.z = Q.z;
      else if ( Q.z > bbox.m_max.z )
        bbox.m_max.z = Q.z;
    }
  }

  if (ON_UNSET_VALUE == context->m_bbox.m_min.x)
    context->m_bbox = bbox;
  else
    context->m_bbox.Union(bbox);

  return true; // continue sending fragments
}

bool ON_SubD::GetTightBoundingBox( 
		ON_BoundingBox& tight_bbox, 
    bool bGrowBox,
		const ON_Xform* xform
    ) const
{
  if ( 0 != xform )
  {
    if ( !xform->IsValid() )
      return false;

    if ( xform->IsIdentity() )
      xform = 0;
  }

  if ( !tight_bbox.IsValid() )
    bGrowBox = false;

  const ON_SubDimple* subdimple = SubDimple();
  if ( 0 == subdimple )
    return bGrowBox?true:false;

  const unsigned int level_count = subdimple->LevelCount();
  if ( level_count <= 0 )
    return bGrowBox?true:false;

  ON_SubD_GetTightBoundingBoxContext context(xform);
  ON_SubDDisplayParameters display_parameters;
  display_parameters.m_display_density = 3;
  GetLimitSurfaceMeshInFragments(
    display_parameters,
    (ON__UINT_PTR)&context,
    ON_SubD_GetTightBoundingBoxContext::FragmentCallback
    );

  const bool rc = context.m_bbox.IsValid();
  if (rc)
  {
    if (bGrowBox)
      tight_bbox.Union(context.m_bbox);
    else
      tight_bbox = context.m_bbox;
    bGrowBox = true;
  }
  return (rc || bGrowBox);
}

//virtual
void ON_SubD::ClearBoundingBox()
{
  // For ON_SubD, ON_SubD::ClearBoundingBox() and ON_SubD::DestroyRuntimeCache(true)

  ON_SubD::DestroyRuntimeCache(true);
}

//virtual
bool ON_SubD::Transform( 
        const ON_Xform& xform
        )
{
  if ( this == &ON_SubD::Empty)
    return true;  // transform applied to empty subd is true on purpose

  // userdata transformation, etc.
  if (!this->ON_Geometry::Transform(xform))
    return false;

  ON_SubDimple* subdimple = SubDimple(false);
  if ( 0 == subdimple )
    return true;  // transform applied to empty subd is true on purpose

  return subdimple->Transform(xform);
}

//virtual
bool ON_SubD::IsDeformable() const
{
  return true;
}

//virtual
bool ON_SubD::MakeDeformable()
{
  return true;
}

//virtual
bool ON_SubD::SwapCoordinates(
      int i,
      int j
      )
{
  return false;
}
  


//virtual
bool ON_SubD::HasBrepForm() const
{
  return false;
}

//virtual
ON_Brep* ON_SubD::BrepForm( ON_Brep*) const
{
  return 0;
}

//virtual
ON_COMPONENT_INDEX ON_SubD::ComponentIndex() const
{
  return ON_Geometry::ComponentIndex();
}

//virtual
bool ON_SubD::EvaluatePoint( const class ON_ObjRef& objref, ON_3dPoint& P ) const
{
  return false;
}

//////////////////////////////////////////////////////////////
//
//
//

const class ON_SubDLevel& ON_SubD::ActiveLevel() const
{
  ON_SubDimple* subdimple = m_subdimple_sp.get();
  return (nullptr != subdimple) ? subdimple->ActiveLevel() : ON_SubDLevel::Empty;
}

class ON_SubDLevel const * ON_SubD::ActiveLevelConstPointer() const
{
  ON_SubDimple* subdimple = m_subdimple_sp.get();
  return (nullptr != subdimple) ? subdimple->ActiveLevelPointer() : nullptr;
}

class ON_SubDLevel* ON_SubD::ActiveLevelPointer()
{
  ON_SubDimple* subdimple = m_subdimple_sp.get();
  return (nullptr != subdimple) ? subdimple->ActiveLevelPointer() : nullptr;
}


ON_SubDimple* ON_SubD::SubDimple(bool bCreateIfNeeded)
{
  ON_SubDimple* subdimple = m_subdimple_sp.get();
  if (nullptr == subdimple && bCreateIfNeeded)
  {
    subdimple = new ON_SubDimple();
    m_subdimple_sp = std::shared_ptr<ON_SubDimple>(subdimple);
  }
  return subdimple;
}

const class ON_SubDimple* ON_SubD::SubDimple() const
{
  return m_subdimple_sp.get();
}

unsigned int ON_SubD::SubDimpleUseCount() const
{
  return (unsigned int)m_subdimple_sp.use_count();
}

void ON_SubD::ShareDimple(const ON_SubD& other_subd)
{
  if (m_subdimple_sp.get() != other_subd.m_subdimple_sp.get())
  {
    m_subdimple_sp.reset();
    m_subdimple_sp = other_subd.m_subdimple_sp;
  }
}

void ON_SubD::ShareDimple(const class ON_SubDLimitMeshImpl& subd_limple)
{
  std::shared_ptr<ON_SubDimple> limple_sp(subd_limple.m_subdimple_wp.lock());

  if (nullptr == limple_sp.get())
    const_cast< ON_SubDLimitMeshImpl& >(subd_limple).ClearFragmentFacePointers();

  if (m_subdimple_sp.get() != limple_sp.get())
  {
    m_subdimple_sp.reset();
    m_subdimple_sp = limple_sp;
  }
}

void ON_SubD::SwapDimple(class ON_SubDLimitMeshImpl& subd_limple )
{
  std::shared_ptr <ON_SubDimple> limple_sp(subd_limple.m_subdimple_wp.lock());
  if (m_subdimple_sp.get() != limple_sp.get())
  {
    m_subdimple_sp.swap(limple_sp);
    subd_limple.m_subdimple_wp = limple_sp;
  }
}


void ON_SubD::SwapDimple(ON_SubD& other_subd)
{
  if (this != &other_subd)
  {
    m_subdimple_sp.swap(other_subd.m_subdimple_sp);
  }
}

void ON_SubD::Clear()
{
  ON_SubDimple* subdimple = SubDimple(false);
  if ( subdimple )
    subdimple->Clear();
}

void ON_SubD::ClearHigherSubdivisionLevels(
  unsigned int max_level_index
  )
{
  ON_SubDimple* subdimple = SubDimple(false);
  if ( subdimple )
    subdimple->ClearHigherSubdivisionLevels(max_level_index);
}

void ON_SubD::ClearLowerSubdivisionLevels(
  unsigned int min_level_index
  )
{
  ON_SubDimple* subdimple = SubDimple(false);
  if ( subdimple )
    subdimple->ClearLowerSubdivisionLevels(min_level_index);
}

void ON_SubD::Destroy()
{
  m_subdimple_sp.reset();
}

bool ON_SubD::SetSubDType(
  ON_SubD::SubDType subdivision_type
  )
{
  ON_SubDimple* subdimple = SubDimple(true);
  return (subdimple) ? subdimple->SetSubDType(subdivision_type) : ON_SUBD_RETURN_ERROR(false);
}

class ON_SubDVertex* ON_SubD::AddVertex(
  ON_SubD::VertexTag vertex_tag,
  const double* P
  )
{
  ON_SubDimple* subdimple = SubDimple(true);
  if ( 0 == subdimple )
    return 0;
  class ON_SubDVertex* v = subdimple->AllocateVertex(vertex_tag, 0, P); // 0 = level
  subdimple->AddVertexToLevel(v);
  return v;
}


class ON_SubDEdge* ON_SubDimple::AddEdge(
  ON_SubD::EdgeTag edge_tag, 
  ON_SubDVertex* v0,
  double v0_sector_weight,
  ON_SubDVertex* v1,
  double v1_sector_weight
  )
{
  if ( false == ON_SubDSectorType::IsValidSectorWeightValue(v0_sector_weight,true) )
    return ON_SUBD_RETURN_ERROR(nullptr);

  if ( false == ON_SubDSectorType::IsValidSectorWeightValue(v1_sector_weight,true) )
    return ON_SUBD_RETURN_ERROR(nullptr);    

  const bool bEdgeTagSet = ON_SubD::EdgeTagIsSet(edge_tag);

  if ( bEdgeTagSet
    && ON_SubDSectorType::IgnoredSectorWeight != v0_sector_weight 
    && ON_SubDSectorType::UnsetSectorWeight != v0_sector_weight
    && nullptr != v0
    && ON_SubD::VertexTag::Smooth == v0->m_vertex_tag
    )
  {
    // minimizes checking when building subds because constant crease weights can be passed in
    v0_sector_weight = ON_SubDSectorType::IgnoredSectorWeight;
  }

  if ( bEdgeTagSet
    && ON_SubDSectorType::IgnoredSectorWeight != v1_sector_weight
    && ON_SubDSectorType::UnsetSectorWeight != v1_sector_weight
    && nullptr != v1
    && ON_SubD::VertexTag::Smooth == v1->m_vertex_tag
    )
  {
    // minimizes checking when building subds because constant crease weights can be passed in
    v1_sector_weight = ON_SubDSectorType::IgnoredSectorWeight;
  }

  class ON_SubDEdge* e = AllocateEdge(edge_tag);
  if ( nullptr == e)
    return ON_SUBD_RETURN_ERROR(nullptr);

  for (unsigned int i = 0; i < 2; i++)
  {
    ON_SubDVertex* v = (i ? v1 : v0);
    double vertex_weight = (i ? v1_sector_weight : v0_sector_weight);
    e->m_vertex[i] = v;
    e->m_sector_coefficient[i] = vertex_weight;
    if (nullptr != v)
    {
      if (false == m_heap.GrowVertexEdgeArrayByOne(v))
      {
        v->m_status.SetDamagedState(true);
        ReturnEdge(e);
        return ON_SUBD_RETURN_ERROR(nullptr);
      }
      v->m_edges[v->m_edge_count++] = ON_SubDEdgePtr::Create(e, i);
      if (e->m_level < v->m_level)
        e->m_level = v->m_level;
      //v->m_vertex_edge_order = ON_SubD::VertexEdgeOrder::unset;
    }
  }

  if ( nullptr == AddEdgeToLevel(e) )
    return ON_SUBD_RETURN_ERROR(nullptr);

  return e;
}

ON_SubD::EdgeTag ON_SubD::EdgeTagFromContext(
  unsigned int edge_face_count,
  const ON_SubDVertex* v0,
  const ON_SubDVertex* v1
)
{
  return
    (nullptr != v0 && nullptr != v1)
    ? ON_SubD::EdgeTagFromContext(edge_face_count, v0->m_vertex_tag, v1->m_vertex_tag)
    : ON_SubD::EdgeTag::Unset;
}

ON_SubD::EdgeTag ON_SubD::EdgeTagFromContext(
  unsigned int edge_face_count,
  const ON_SubD::VertexTag v0_tag,
  const ON_SubD::VertexTag v1_tag
)
{
  ON_SubD::EdgeTag edge_tag = ON_SubD::EdgeTag::Unset;

  for(;;)
  {
    if (edge_face_count > 0x7FFFU)
      break;

    if (1 == edge_face_count || edge_face_count >= 3 )
    {
      edge_tag = ON_SubD::EdgeTag::Crease;
      break;
    }

    const bool bSmooth0 = ON_SubD::VertexTag::Smooth == v0_tag;
    const bool bSmooth1 = ON_SubD::VertexTag::Smooth == v1_tag;

    if ( bSmooth0 || bSmooth1 )
    {
      if ( 2 == edge_face_count && bSmooth0 && bSmooth1)
        edge_tag = ON_SubD::EdgeTag::Smooth;
      break;
    }

    if ( ON_SubD::VertexTagIsSet(v0_tag) && ON_SubD::VertexTagIsSet(v1_tag) )
    {
      if (2 == edge_face_count)
        edge_tag = ON_SubD::EdgeTag::X;
      break;
    }

    break;
  }

  return edge_tag;
}

class ON_SubDEdge* ON_SubD::AddEdge(
  ON_SubD::EdgeTag edge_tag,
  ON_SubDVertex* v0,
  ON_SubDVertex* v1
  )
{
  // NO automatic setting - causes more problems than it helps.
  // Users can call ON_SubD::EdgeTagFromContext() if they want to
  // preset the edge tag based on context.

  ////if (ON_SubD::EdgeTag::Unset == edge_tag && nullptr != v0 && nullptr != v1 )
  ////{
  ////  if ( v0->IsCreaseOrCornerOrDart() && v1->IsCreaseOrCornerOrDart() )
  ////    edge_tag = ON_SubD::EdgeTag::Crease;
  ////  else if ( ON_SubD::VertexTag::Unset != v0->m_vertex_tag 
  ////    && ON_SubD::VertexTag::Unset != v1->m_vertex_tag
  ////    && (v0->IsSmooth() || v1->IsSmooth())
  ////    )
  ////    edge_tag = ON_SubD::EdgeTag::Smooth;
  ////}

  return AddEdgeWithSectorCoefficients(
    edge_tag,
    v0,
    ON_SubDSectorType::UnsetSectorWeight,
    v1,
    ON_SubDSectorType::UnsetSectorWeight
    );
}

ON_SubDEdge* ON_SubD::AddEdgeWithSectorCoefficients(
    ON_SubD::EdgeTag edge_tag,
    class ON_SubDVertex* v0,
    double v0_sector_coefficient,
    class ON_SubDVertex* v1,
    double v1_sector_coefficient
    )
{
  ON_SubDimple* subdimple = SubDimple(true);
  if (nullptr != subdimple) 
    return subdimple->AddEdge(edge_tag, v0, v0_sector_coefficient, v1, v1_sector_coefficient);
  return ON_SUBD_RETURN_ERROR(nullptr);
}

class ON_SubDFace* ON_SubDimple::AddFace(
  unsigned int edge_count,
  const ON_SubDEdgePtr* edge
  )
{
  if ( edge_count > 0 && nullptr == edge)
    return ON_SUBD_RETURN_ERROR(nullptr);

  ON_SubDFace* f = AllocateFace();
  if ( nullptr == f)
    return ON_SUBD_RETURN_ERROR(nullptr);

  if (edge_count > 0)
  {
    if (edge_count > 4)
    {
      if (false == m_heap.GrowFaceEdgeArray(f,edge_count))
      {
        ReturnFace(f);
        return ON_SUBD_RETURN_ERROR(nullptr);
      }
    }


    ON_SubDEdgePtr* f_edge = f->m_edge4;
    unsigned short f_level = 0;
    for (unsigned int i = 0; i < edge_count; i++)
    {
      if (4 == i)
        f_edge = f->m_edgex - 4;

      f_edge[i] = edge[i];
      ON__UINT_PTR eptr = edge[i].m_ptr;
      ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(eptr);
      if ( nullptr == e)
        continue;

      eptr = ON_SUBD_EDGE_DIRECTION(eptr);

      ON_SubDVertex* v = const_cast<ON_SubDVertex*>(e->m_vertex[eptr]);
      if (false == m_heap.GrowVertexFaceArrayByOne(v))
      {
        v->m_status.SetDamagedState(true);
        ReturnFace(f);
        return ON_SUBD_RETURN_ERROR(nullptr);
      }
      v->m_faces[v->m_face_count++] = f;
      //if (1 == v->m_face_count)
      //{
      //  if (4 == f->m_edge_count)
      //    v->m_vertex_facet_type = ON_SubD::VertexFacetType::Quad;
      //  else if (3 == f->m_edge_count)
      //    v->m_vertex_facet_type = ON_SubD::VertexFacetType::Tri;
      //  else if ( f->m_edge_count > 4)
      //    v->m_vertex_facet_type = ON_SubD::VertexFacetType::Ngon;
      //}
      //else
      //{
      //  const ON_SubDFace* f0 = v->m_faces[0];
      //  if (nullptr == f0 || f0->m_edge_count != f->m_edge_count)
      //    v->m_vertex_facet_type = ON_SubD::VertexFacetType::Mixed;
      //}
      //v->m_vertex_edge_order = ON_SubD::VertexEdgeOrder::unset;

      ON_SubDFacePtr* e_faces;
      if (e->m_face_count < 2)
      {
        e_faces = e->m_face2;
      }
      else
      {
        if (2 == e->m_face_count)
        {
          // Getting this error in a valid situation means it is time
          // to support non-manifold subdivision objects.
          ON_SubDIncrementErrorCount();
          ON_WARNING("creating non-manifold subdivision object");
        }
        if (false == m_heap.GrowEdgeFaceArrayByOne(e))
        {
          e->m_status.SetDamagedState(true);
          continue;
        }
        e_faces = e->m_facex - 2;
      }
      e_faces[e->m_face_count++] = ON_SubDFacePtr::Create(f, eptr);
      if (f_level < e->m_level)
        f_level = e->m_level;
    }

    f->m_level = f_level;
    f->m_edge_count = (unsigned short)edge_count;
  }

  if ( nullptr == AddFaceToLevel(f))
    return ON_SUBD_RETURN_ERROR(nullptr);

  return f;
}

unsigned int ON_SubDLevel::UpdateEdgeSectorCoefficients(
  bool bUnsetEdgeSectorCoefficientsOnly
  )
{
  unsigned int changed_edge_count = 0;
  const bool bUnsetSubdivisiontType = (false == ON_SubD::IsQuadOrTriSubDType(m_subdivision_type));

  for (const ON_SubDEdge* edge = m_edge[0]; nullptr != edge; edge = edge->m_next_edge)
  {
    ON_SubDEdge* e = const_cast<ON_SubDEdge*>(edge);
    if (bUnsetEdgeSectorCoefficientsOnly)
    {
      if (    e->m_sector_coefficient[0] >= 0.0 && e->m_sector_coefficient[0] <= 1.0
           && e->m_sector_coefficient[1] >= 0.0 && e->m_sector_coefficient[1] <= 1.0
         )
        continue;
    }
    const double  m_sector_coefficient0[2] = { e->m_sector_coefficient[0], e->m_sector_coefficient[1] };
    e->m_sector_coefficient[0] = ON_SubDSectorType::IgnoredSectorWeight;
    e->m_sector_coefficient[1] = ON_SubDSectorType::IgnoredSectorWeight;
    if (ON_SubD::EdgeTag::Smooth == edge->m_edge_tag || ON_SubD::EdgeTag::X == edge->m_edge_tag)
    {
      const unsigned int tagged_end_index = edge->TaggedEndIndex();
      if (tagged_end_index < 2)
      {
        e->m_sector_coefficient[tagged_end_index]
          = bUnsetSubdivisiontType
          ? ON_SubDSectorType::UnsetSectorWeight
          : ON_SubDSectorType::Create(m_subdivision_type, edge, tagged_end_index).SectorWeight();
      }
      else if (2 == tagged_end_index)
      {
        if (ON_SubD::EdgeTag::Smooth == edge->m_edge_tag)
          e->m_edge_tag = ON_SubD::EdgeTag::Crease;
        else if (ON_SubD::EdgeTag::X == edge->m_edge_tag)
        {
          e->m_sector_coefficient[0]
            = bUnsetSubdivisiontType
            ? ON_SubDSectorType::UnsetSectorWeight
            : ON_SubDSectorType::Create(m_subdivision_type, edge, 0).SectorWeight();
          e->m_sector_coefficient[1]
            = bUnsetSubdivisiontType
            ? ON_SubDSectorType::UnsetSectorWeight
            : ON_SubDSectorType::Create(m_subdivision_type, edge, 1).SectorWeight();
        }
      }
    }
    if (!(m_sector_coefficient0[0] == e->m_sector_coefficient[0] && m_sector_coefficient0[1] == e->m_sector_coefficient[1]))
      changed_edge_count++;
  }

  return changed_edge_count;
}

bool ON_SubDLevel::SetSubDType(
  ON_SubD::SubDType subd_type
  )
{
  const bool bQuadSubD = (ON_SubD::SubDType::QuadCatmullClark == subd_type);
  const bool bTriSubD = (ON_SubD::SubDType::TriLoopWarren == subd_type);
  if (false == bQuadSubD && false == bTriSubD)
    return ON_SUBD_RETURN_ERROR(false);

  const bool bUpdateEdgeWeights = (m_subdivision_type != subd_type);

  if (bQuadSubD)
  {
    m_subdivision_type = subd_type;
    m_ordinary_vertex_valence = 4;
    m_ordinary_face_edge_count = 4;
  }
  else if (bTriSubD)
  {
    m_subdivision_type = subd_type;
    m_ordinary_vertex_valence = 6;
    m_ordinary_face_edge_count = 3;
  }

  if (bUpdateEdgeWeights)
  {
    UpdateEdgeSectorCoefficients(false);
  }

  return true;
}

bool ON_SubDimple::SetSubDType(
  ON_SubD::SubDType subd_type
  )
{
  ON_SubDLevel* subd_level = ActiveLevel(0 == m_levels.UnsignedCount());
  if (nullptr == subd_level)
    return ON_SUBD_RETURN_ERROR(false);

  return subd_level->SetSubDType(subd_type);
}


class ON_SubDFace* ON_SubD::AddFace(
  unsigned int edge_count,
  const ON_SubDEdgePtr* edge
  )
{
  ON_SubDimple* subdimple = SubDimple(true);
  return (nullptr != subdimple) ? subdimple->AddFace(edge_count, edge) : nullptr;
}

bool ON_SubD::AddFaceEdgeConnection(
  ON_SubDFace* face,
  unsigned int i,
  ON_SubDEdge* edge,
  ON__UINT_PTR edge_direction
  )
{
  return AddFaceEdgeConnection(face, i, ON_SubDEdgePtr::Create(edge, edge_direction));
}

bool ON_SubD::AddFaceEdgeConnection(
  ON_SubDFace* face,
  unsigned int i,
  ON_SubDEdgePtr eptr
  )
{
  if (nullptr == face && i >= ON_SubDFace::MaximumEdgeCount)
  {
    return ON_SUBD_RETURN_ERROR(false);
  }

  unsigned int face_edge_count = (unsigned int)face->m_edge_count + 1U;
  if ( face_edge_count <= i )
    face_edge_count = i+1;

  ON_SubDEdge* edge = eptr.Edge();
  if (nullptr != edge)
  {
    if (edge->m_face_count >= edge->m_facex_capacity + (unsigned short)2)
    {
      if (false == GrowEdgeFaceArray(edge, 0))
        return ON_SUBD_RETURN_ERROR(false);
    }

    ON_SubDFacePtr fptr = ON_SubDFacePtr::Create(face,eptr.EdgeDirection());

    unsigned short efi = edge->m_face_count;
    if ( efi < 2 )
      edge->m_face2[efi] = fptr;
    else
    {
      if ( nullptr == edge->m_facex )
        return ON_SUBD_RETURN_ERROR(false);
      edge->m_facex[efi - 2] = fptr;
    }
    edge->m_face_count++;
  }

  if (face_edge_count > ((unsigned int)face->m_edgex_capacity) + 4U)
  {
    if (false == GrowFaceEdgeArray(face,face_edge_count))
      return ON_SUBD_RETURN_ERROR(false);
  }

  if (i >= ((unsigned int)face->m_edge_count))
  {
    unsigned int j = face->m_edge_count;
    for (/*empty init*/;j < 4; j++)
      face->m_edge4[j] = ON_SubDEdgePtr::Null;
    for (/*empty init*/;j < i; j++)
      face->m_edgex[j-4] = ON_SubDEdgePtr::Null;
  }
  else
  {
    for (unsigned int j = face_edge_count - 1; j > i; j--)
    {
      if (j > 4)
        face->m_edgex[j - 4] = face->m_edgex[j - 5];
      else if (4 == j)
        face->m_edgex[0] = face->m_edge4[3];
      else
        face->m_edge4[j] = face->m_edge4[j - 1];
    }
  }

  if ( i < 4 )
    face->m_edge4[i] = eptr;
  else 
    face->m_edgex[i-4] = eptr;
  face->m_edge_count = (unsigned short)face_edge_count;

  return true;
}


bool ON_SubD::RemoveFaceEdgeConnection(
  ON_SubDFace* face,
  ON_SubDEdge* edge
  )
{
  ON_SubDEdgePtr removed_edge;
  return RemoveFaceEdgeConnection(face, face->EdgeArrayIndex(edge), removed_edge);
}

bool ON_SubD::RemoveFaceEdgeConnection(
  ON_SubDFace* face,
  unsigned int i
  )
{
  ON_SubDEdgePtr removed_edge;
  return RemoveFaceEdgeConnection(face, i, removed_edge);
}

bool ON_SubD::RemoveFaceEdgeConnection(
  ON_SubDFace* face,
  unsigned int i,
  ON_SubDEdgePtr& removed_edge
  )
{
  removed_edge = ON_SubDEdgePtr::Null;
  if ( nullptr == face && i >= (unsigned int)face->m_edge_count )
  {
    return ON_SUBD_RETURN_ERROR(false);
  }

  if ( false == face->RemoveEdgeFromArray(i,removed_edge) )
    return ON_SUBD_RETURN_ERROR(false);

  ON_SubDEdge* edge = removed_edge.Edge();
  if ( nullptr == edge )
    return ON_SUBD_RETURN_ERROR(false);

  if (false == edge->RemoveFaceFromArray(face))
    return ON_SUBD_RETURN_ERROR(false);

  return true;
}


static bool ON_SubDFace_GetSubdivisionPointError(
  const class ON_SubDFace* face,
  double face_point[3],
  bool bDamagedState
  )
{
  if (nullptr == face || nullptr == face_point)
    return ON_SUBD_RETURN_ERROR(false); // caller passed a null pointer - edge is not necessarily damaged

  face->m_status.SetDamagedState(bDamagedState);

  return ON_SUBD_RETURN_ERROR(false);
}

bool ON_SubDFace::GetSubdivisionPoint(
    ON_SubD::SubDType subd_type,
    bool bUseSavedSubdivisionPoint,
    double subdivision_point[3]
  ) const
{
  if (nullptr == subdivision_point)
    return ON_SubDFace_GetSubdivisionPointError(this,subdivision_point,false);

  if (bUseSavedSubdivisionPoint && GetSavedSubdivisionPoint(subd_type,subdivision_point))
    return true;

  const unsigned int count = m_edge_count;
  if (count < 3)
    return ON_SubDFace_GetSubdivisionPointError(this, subdivision_point, true);

  double displacementV[3] = { 0 };
  const bool bApplyDisplacement = GetDisplacement(subd_type,displacementV);

  const class ON_SubDEdgePtr* edge_ptr = m_edge4;

  ON__UINT_PTR e_ptr;
  const ON_SubDEdge* e;
  ON__UINT_PTR edir;
  const double* vertexP[4];

  // Use faster code for the case when the face is a quad.
  // Since this is a Catmull-Clark subdivision scheme, this
  // case is the most common by far and code that gives quads
  // special treatment will run noticably faster.
  e_ptr = edge_ptr[0].m_ptr;
  e = ON_SUBD_EDGE_POINTER(e_ptr);
  edir = ON_SUBD_EDGE_DIRECTION(e_ptr);
  vertexP[0] = e->m_vertex[edir]->m_P;
  vertexP[1] = e->m_vertex[1 - edir]->m_P;

  e_ptr = edge_ptr[2].m_ptr;
  e = ON_SUBD_EDGE_POINTER(e_ptr);
  edir = ON_SUBD_EDGE_DIRECTION(e_ptr);
  vertexP[2] = e->m_vertex[edir]->m_P;
  vertexP[3] = e->m_vertex[1 - edir]->m_P;

  if (4 == count)
  {
    // most common case in quad subdivision schemes
    subdivision_point[0] = (vertexP[0][0] + vertexP[1][0] + vertexP[2][0] + vertexP[3][0])*0.25;
    subdivision_point[1] = (vertexP[0][1] + vertexP[1][1] + vertexP[2][1] + vertexP[3][1])*0.25;
    subdivision_point[2] = (vertexP[0][2] + vertexP[1][2] + vertexP[2][2] + vertexP[3][2])*0.25;

    if (bApplyDisplacement)
    {
      subdivision_point[0] += displacementV[0];
      subdivision_point[1] += displacementV[1];
      subdivision_point[2] += displacementV[2];
    }

    if (bUseSavedSubdivisionPoint)
      SetSavedSubdivisionPoint(subd_type,subdivision_point);


    return true;
  }

  if (3 == count)
  {
    // most common case in triangle subdivision schemes and
    // 2nd most common case in quad subdivision schemes
    subdivision_point[0] = (vertexP[0][0] + vertexP[1][0] + vertexP[2][0]) / 3.0;
    subdivision_point[1] = (vertexP[0][1] + vertexP[1][1] + vertexP[2][1]) / 3.0;
    subdivision_point[2] = (vertexP[0][2] + vertexP[1][2] + vertexP[2][2]) / 3.0;

    if (bApplyDisplacement)
    {
      subdivision_point[0] += displacementV[0];
      subdivision_point[1] += displacementV[1];
      subdivision_point[2] += displacementV[2];
    }

    if (bUseSavedSubdivisionPoint)
      SetSavedSubdivisionPoint(subd_type,subdivision_point);

    return true;
  }

  // count > 4
  double faceP[3]
    = {
    (vertexP[0][0] + vertexP[1][0] + vertexP[2][0] + vertexP[3][0]),
    (vertexP[0][1] + vertexP[1][1] + vertexP[2][1] + vertexP[3][1]),
    (vertexP[0][2] + vertexP[1][2] + vertexP[2][2] + vertexP[3][2]) 
    };

  if (nullptr == m_edgex)
  {
    return ON_SubDFace_GetSubdivisionPointError(this, subdivision_point, true);
  }

  edge_ptr = m_edgex - 4; // -4 because index i begins at 4
  unsigned int i;
  for (i = 4; i + 1 < count; i += 2)
  {
    e_ptr = edge_ptr[i].m_ptr;
    e = ON_SUBD_EDGE_POINTER(e_ptr);
    edir = ON_SUBD_EDGE_DIRECTION(e_ptr);
    vertexP[0] = e->m_vertex[edir]->m_P;
    vertexP[1] = e->m_vertex[1 - edir]->m_P;

    faceP[0] += vertexP[0][0];
    faceP[1] += vertexP[0][1];
    faceP[2] += vertexP[0][2];

    faceP[0] += vertexP[1][0];
    faceP[1] += vertexP[1][1];
    faceP[2] += vertexP[1][2];
  }

  if (i < count)
  {
    // odd number of edges and vertices
    e_ptr = edge_ptr[count - 1].m_ptr;
    e = ON_SUBD_EDGE_POINTER(e_ptr);
    edir = ON_SUBD_EDGE_DIRECTION(e_ptr);
    vertexP[0] = e->m_vertex[edir]->m_P;
    faceP[0] += vertexP[0][0];
    faceP[1] += vertexP[0][1];
    faceP[2] += vertexP[0][2];
  }

  const double n = count;

  subdivision_point[0] = faceP[0] / n;
  subdivision_point[1] = faceP[1] / n;
  subdivision_point[2] = faceP[2] / n;

  if (bApplyDisplacement)
  {
    subdivision_point[0] += displacementV[0];
    subdivision_point[1] += displacementV[1];
    subdivision_point[2] += displacementV[2];
  }

  if (bUseSavedSubdivisionPoint)
    SetSavedSubdivisionPoint(subd_type,subdivision_point);

  return true;
}

bool ON_SubDComponentBase::SetSavedSubdivisionPoint(
  ON_SubD::SubDType subd_type,
  const double subdivision_point[3]
  ) const
{
  if (ON_SubD::SubDType::Unset == subd_type)
  {
    ClearSavedSubdivisionPoint();
    return true;
  }
  
  if ( nullptr != subdivision_point
    && ON_IsValid(subdivision_point[0])
    && ON_IsValid(subdivision_point[1])
    && ON_IsValid(subdivision_point[2])
    )
  {
    const unsigned char c = (unsigned char)subd_type;
    if ( c != ON_SUBD_CACHE_TYPE(m_saved_points_flags))
      m_saved_points_flags = 0U;
    m_saved_subd_point1[0] = subdivision_point[0];
    m_saved_subd_point1[1] = subdivision_point[1];
    m_saved_subd_point1[2] = subdivision_point[2];
    m_saved_points_flags |= (ON_SUBD_CACHE_POINT_FLAG_MASK | c);
    return true;
  }

  ClearSavedSubdivisionPoint();
  return ON_SUBD_RETURN_ERROR(false);
}

bool ON_SubDComponentBase::GetSavedSubdivisionPoint(
  ON_SubD::SubDType subd_type,
  double subdivision_point[3]
  ) const
{
  if ( 0 == (ON_SUBD_CACHE_POINT_FLAG_MASK & m_saved_points_flags) )
    return false;

  if ( subd_type != (ON_SubD::SubDType)ON_SUBD_CACHE_TYPE(m_saved_points_flags) )
    return false;

  if (nullptr != subdivision_point)
  {
    subdivision_point[0] = m_saved_subd_point1[0];
    subdivision_point[1] = m_saved_subd_point1[1];
    subdivision_point[2] = m_saved_subd_point1[2];
  }

  return true;
}

void ON_SubDVertex::VertexModifiedNofification() const
{
  ClearSavedSubdivisionPoint();
  ClearSavedLimitPoints();
  if (nullptr != m_edges)
  {
    for (unsigned short vei = 0; vei < m_edge_count; vei++)
    {
      const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(m_edges[vei].m_ptr);
      if ( nullptr != edge )
        edge->ClearSavedSubdivisionPoint();
    }
  }
  if (nullptr != m_faces)
  {
    for (unsigned short vfi = 0; vfi < m_face_count; vfi++)
    {
      const ON_SubDFace* face = m_faces[vfi];
      if ( nullptr != face )
        face->ClearSavedSubdivisionPoint();
    }
  }
}

void ON_SubDEdge::EdgeModifiedNofification() const
{
  ClearSavedSubdivisionPoint();
  for (unsigned int evi = 0; evi < 2; evi++)
  {
    if (nullptr != m_vertex[evi])
    {
      m_vertex[evi]->ClearSavedSubdivisionPoint();
      m_vertex[evi]->ClearSavedLimitPoints();
    }
  }
  const ON_SubDFacePtr* fptr = m_face2;
  for (unsigned int efi = 0; efi < 2; efi++)
  {
    if (2 == efi)
    {
      fptr = m_facex;
      if ( nullptr == fptr)
        break;
    }
    const ON_SubDFace* face = ON_SUBD_FACE_POINTER(fptr->m_ptr);
    if ( nullptr != face )
      face->ClearSavedSubdivisionPoint();
    fptr++;
  }
}

void ON_SubDFace::FaceModifiedNofification() const
{
  ClearSavedSubdivisionPoint();
  const ON_SubDEdgePtr* eptr = m_edge4;
  for (unsigned int efi = 0; efi < m_edge_count; efi++)
  {
    if (4 == efi)
    {
      eptr = m_edgex;
      if ( nullptr == eptr)
        break;
    }
    const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(eptr->m_ptr);
    if (nullptr != edge)
    {
      edge->ClearSavedSubdivisionPoint();
      for (unsigned int evi = 0; evi < 2; evi++)
      {
        if (nullptr != edge->m_vertex[evi])
        {
          edge->m_vertex[evi]->ClearSavedSubdivisionPoint();
          edge->m_vertex[evi]->ClearSavedLimitPoints();
        }
      }
    }
    eptr++;
  }
}

void ON_SubDComponentBase::ClearSavedSubdivisionPoint() const
{
  ON_SUBD_CACHE_CLEAR_POINT_FLAG(m_saved_points_flags);
}

ON_SubD::SubDType ON_SubDComponentBase::SavedSubdivisionPointType() const
{
  return 
    (0 != (ON_SUBD_CACHE_POINT_FLAG_MASK & m_saved_points_flags))
    ? ((ON_SubD::SubDType)(ON_SUBD_CACHE_TYPE_MASK & m_saved_points_flags))
    : ON_SubD::SubDType::Unset;
}

ON_SubD::SubDType ON_SubDComponentBase::DisplacementType() const
{
  return 
    (0 != ON_SUBD_CACHE_DISPLACEMENT_FLAG(m_saved_points_flags))
    ? ((ON_SubD::SubDType)(ON_SUBD_CACHE_TYPE_MASK & m_saved_points_flags))
    : ON_SubD::SubDType::Unset;
}

bool ON_SubDComponentBase::SetDisplacement(
    ON_SubD::SubDType subd_type,
    const double displacement[3]
    )
{
  if ( ON_SubD::SubDType::Unset != subd_type
    && nullptr != displacement
    && ON_IsValid(displacement[0]) && ON_IsValid(displacement[1]) && ON_IsValid(displacement[2])
    )
  {
    if (0.0 == displacement[0] && 0.0 == displacement[1] && 0.0 == displacement[2])
    {
      ClearDisplacement();
      return true;
    }
    ON_SubD::SubDType f = (ON_SubD::SubDType)(ON_SUBD_CACHE_TYPE_MASK & m_saved_points_flags);
    if ( subd_type != f )
      m_saved_points_flags = (unsigned char)f;
    m_saved_points_flags |= ON_SUBD_CACHE_DISPLACEMENT_FLAG_MASK;
    m_displacement_V[0] = displacement[0];
    m_displacement_V[1] = displacement[1];
    m_displacement_V[2] = displacement[2];
    return true;
  }

  if (ON_SubD::SubDType::Unset == subd_type)
  {
    ClearDisplacement();
    return true;
  }
  
  return ON_SUBD_RETURN_ERROR(false);
}

void ON_SubDComponentBase::ClearDisplacement() const
{
  if (0 != (m_saved_points_flags & ON_SUBD_CACHE_DISPLACEMENT_FLAG_MASK))
  {
    ON_SUBD_CACHE_CLEAR_POINT_FLAG(m_saved_points_flags);
    ON_SUBD_CACHE_CLEAR_DISPLACEMENT_FLAG(m_saved_points_flags);
  }
}

bool ON_SubDComponentBase::GetDisplacement(
    ON_SubD::SubDType subd_type,
    double displacement[3]
    ) const
{
  const bool rc = (0 != (ON_SUBD_CACHE_DISPLACEMENT_FLAG_MASK & m_saved_points_flags))
        && subd_type == (ON_SubD::SubDType)(ON_SUBD_CACHE_TYPE_MASK & m_saved_points_flags);
  if (nullptr != displacement)
  {
    if (rc)
    {
      displacement[0] = m_displacement_V[0];
      displacement[1] = m_displacement_V[1];
      displacement[2] = m_displacement_V[2];
    }
    else
    {
      displacement[0] = 0.0;
      displacement[1] = 0.0;
      displacement[2] = 0.0;
    }
  }
  return rc;
}


bool ON_SubDFace::ReverseEdgeList()
{
  const unsigned int edge_count = m_edge_count;
  if ( 0 == edge_count)
    return true;
  if (edge_count > 4 && nullptr == m_edgex)
  {
    return ON_SUBD_RETURN_ERROR(false);
  }

  ON_SubDEdgePtr buffer[16];
  ON_SubDEdgePtr* reversed_eptrs;
  if ( edge_count <= sizeof(buffer)/sizeof(buffer[0]) )
    reversed_eptrs = buffer;
  else
  {
    reversed_eptrs = new(std::nothrow) ON_SubDEdgePtr[edge_count];
    if ( nullptr == reversed_eptrs)
      return ON_SUBD_RETURN_ERROR(false);
  }

  ON_SubDEdgePtr* face_eptrs = m_edge4;
  for (unsigned int fei = 0; fei < edge_count; fei++, face_eptrs++)
  {
    if (4 == fei)
      face_eptrs = m_edgex;

    ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(face_eptrs->m_ptr);
    if ( nullptr == e)
      continue;
    ON__UINT_PTR edir = ON_SUBD_EDGE_DIRECTION(face_eptrs->m_ptr);
    reversed_eptrs[edge_count-1-fei] = ON_SubDEdgePtr::Create(e,1-edir);

    ON_SubDFacePtr* edges_fptrs = e->m_face2;
    const unsigned int face_count = e->m_face_count;
    for (unsigned int efi = 0; efi < face_count; efi++, edges_fptrs++)
    {
      if (2 == efi)
      {
        edges_fptrs = e->m_facex;
        if ( nullptr == edges_fptrs)
          break;
      }
      if ( this != ON_SUBD_FACE_POINTER(edges_fptrs->m_ptr) )
        continue;
      *edges_fptrs = ON_SubDFacePtr::Create(this,1-ON_SUBD_FACE_DIRECTION(edges_fptrs->m_ptr));
      break;
    } 
  }

  face_eptrs = m_edge4;
  for (unsigned int fei = 0; fei < edge_count; fei++)
  {
    if (4 == fei)
      face_eptrs = m_edgex;
    *face_eptrs++ = reversed_eptrs[fei];
  }

  if ( reversed_eptrs != buffer )
    delete[] reversed_eptrs;

  return true;
}

static bool ON_SubDEdge_GetSubdivisionPointError(
  const class ON_SubDEdge* edge,
  double edge_point[3],
  const double* edgeP[2],
  bool bDamagedState
  )
{
  if (nullptr == edge || nullptr == edge_point)
    return false; // caller passed a null pointer - edge is not necessarily damaged

  ON_SubDIncrementErrorCount();
  edge->m_status.SetDamagedState(bDamagedState);

  if (nullptr != edgeP && nullptr != edgeP[0] && nullptr != edgeP[1])
  {
    const double edgePsum[3] = { edgeP[0][0] + edgeP[1][0], edgeP[0][1] + edgeP[1][1], edgeP[0][2] + edgeP[1][2] };
    edge_point[0] = 0.5*edgePsum[0];
    edge_point[1] = 0.5*edgePsum[1];
    edge_point[2] = 0.5*edgePsum[2];
  }
  return true;
}

unsigned int ON_SubDEdge::GetFacePointSum(
  const ON_SubDFace* face,
  const ON_SubDEdge* edge,
  double* facePsum
  )
{
  const ON_SubDEdge* e;
  ON__UINT_PTR e_ptr, edir;
  const double* vertexP[2];

  if (nullptr == face)
    return 0;
  
  const unsigned int n = face->m_edge_count;

  if (3 == n)
  {
    if (edge == ON_SUBD_EDGE_POINTER(face->m_edge4[0].m_ptr))
      e_ptr = face->m_edge4[1].m_ptr;
    else if (edge == ON_SUBD_EDGE_POINTER(face->m_edge4[1].m_ptr))
      e_ptr = face->m_edge4[2].m_ptr;
    else if (edge == ON_SUBD_EDGE_POINTER(face->m_edge4[2].m_ptr))
      e_ptr = face->m_edge4[0].m_ptr;
    else
      return 0;
      e = ON_SUBD_EDGE_POINTER(e_ptr);
      if (nullptr == e)
        return 0;
      if (nullptr == e->m_vertex[0] || nullptr == e->m_vertex[1])
        return 0;
      edir = ON_SUBD_EDGE_DIRECTION(e_ptr);
      if (edge->m_vertex[0] != e->m_vertex[edir] && edge->m_vertex[1] != e->m_vertex[edir])
        return 0;
      vertexP[0] = e->m_vertex[1 - edir]->m_P;
      facePsum[0] = vertexP[0][0];
      facePsum[1] = vertexP[0][1];
      facePsum[2] = vertexP[0][2];

      return n;
  }

  if (4 == n)
  {
    if (edge == ON_SUBD_EDGE_POINTER(face->m_edge4[0].m_ptr))
      e_ptr = face->m_edge4[2].m_ptr;
    else if (edge == ON_SUBD_EDGE_POINTER(face->m_edge4[1].m_ptr))
      e_ptr = face->m_edge4[3].m_ptr;
    else if (edge == ON_SUBD_EDGE_POINTER(face->m_edge4[2].m_ptr))
      e_ptr = face->m_edge4[0].m_ptr;
    else if (edge == ON_SUBD_EDGE_POINTER(face->m_edge4[3].m_ptr))
      e_ptr = face->m_edge4[1].m_ptr;
    else
      return 0;
    e = ON_SUBD_EDGE_POINTER(e_ptr);
    if (nullptr == e)
      return 0;
    if (nullptr == e->m_vertex[0] || nullptr == e->m_vertex[1])
      return 0;
    edir = ON_SUBD_EDGE_DIRECTION(e_ptr);
    vertexP[0] = e->m_vertex[edir]->m_P;
    vertexP[1] = e->m_vertex[1 - edir]->m_P;
    facePsum[0] = vertexP[0][0] + vertexP[1][0];
    facePsum[1] = vertexP[0][1] + vertexP[1][1];
    facePsum[2] = vertexP[0][2] + vertexP[1][2];

    return n;
  }

  if (n < 3)
    return 0;

  const ON_SubDEdgePtr* edgeptr = face->m_edge4;
  const ON_SubDVertex* edge_vertex[2] = { edge->m_vertex[0], edge->m_vertex[1] };
  facePsum[0] = 0.0;
  facePsum[1] = 0.0;
  facePsum[2] = 0.0;
  for (unsigned i = 0; i < n; i++)
  {
    if (4 == i)
      edgeptr = face->m_edgex - 4;
    e = ON_SUBD_EDGE_POINTER(edgeptr[i].m_ptr);
    if (nullptr == e)
      return 0;
    if (edge == e)
      continue;
    edir = ON_SUBD_EDGE_DIRECTION(edgeptr[i].m_ptr);
    const ON_SubDVertex* e_vertex[2] = { e->m_vertex[edir], e->m_vertex[1 - edir] };
    if (nullptr == e_vertex[0] || nullptr == e_vertex[1])
      return 0;
    if (edge_vertex[0] != e_vertex[0] && edge_vertex[1] != e_vertex[0])
    {
      vertexP[0] = e_vertex[0]->m_P;
      facePsum[0] += vertexP[0][0];
      facePsum[1] += vertexP[0][1];
      facePsum[2] += vertexP[0][2];
    }
    if (i + 1 < n)
    {
      // start of next edge = end of this edge
      if (edge_vertex[0] != e_vertex[1] && edge_vertex[1] != e_vertex[1])
      {
        vertexP[0] = e_vertex[1]->m_P;
        facePsum[0] += vertexP[0][0];
        facePsum[1] += vertexP[0][1];
        facePsum[2] += vertexP[0][2];
      }
      i++;
      if (4 == i && n > 4)
        edgeptr = face->m_edgex - 4;
    }
  }

  return n;
}

bool ON_SubDEdge::GetSubdivisionPoint(
    ON_SubD::SubDType subd_type,
    bool bUseSavedSubdivisionPoint,
    double subdivision_point[3]
  ) const
{
  if (nullptr == subdivision_point)
    return ON_SubDEdge_GetSubdivisionPointError(this, subdivision_point, nullptr, false);

  if (bUseSavedSubdivisionPoint && GetSavedSubdivisionPoint(subd_type, subdivision_point))
    return true;

  const ON_SubDVertex* edge_vertex[2] = { m_vertex[0], m_vertex[1] };
  if (nullptr == edge_vertex[0] || nullptr == edge_vertex[1])
    return ON_SubDEdge_GetSubdivisionPointError(this, subdivision_point, nullptr, true);

  double displacementV[3] = { 0 };
  const bool bApplyDisplacement = GetDisplacement(subd_type,displacementV);

  const double* edgeP[2] = { edge_vertex[0]->m_P, edge_vertex[1]->m_P };

  
  // If you extrude a polygon, so all the "vertical" edges are "x" edges,
  // and bSubDivideXEdgesAsSmooth is false, then the result has a barrel like
  // bulge in it.  Giulio pointed this out in April 2015.
  const bool bSubDivideXEdgesAsSmooth = true;

  if ( IsSmooth(bSubDivideXEdgesAsSmooth) )
  {
    // A smooth edge must have exactly two neighboring faces and
    // at most one end vertex can be tagged.

    if (2 != m_face_count)
      return ON_SubDEdge_GetSubdivisionPointError(this, subdivision_point, edgeP, true);

    const ON_SubDFace* faces[2] = { ON_SUBD_FACE_POINTER(m_face2[0].m_ptr), ON_SUBD_FACE_POINTER(m_face2[1].m_ptr) };
    if (nullptr == faces[0] || nullptr == faces[1])
      return ON_SubDEdge_GetSubdivisionPointError(this, subdivision_point, edgeP, true);

    // for each neighbor face, sum the vertex locations that are not on this edge
    double facePsum[2][3];
    const unsigned int face_edge_count[2]
      = { ON_SubDEdge::GetFacePointSum(faces[0], this, facePsum[0]),
      ON_SubDEdge::GetFacePointSum(faces[1], this, facePsum[1])
    };
    if (0 == face_edge_count[0] || 0 == face_edge_count[1])
      return ON_SubDEdge_GetSubdivisionPointError(this, subdivision_point, edgeP, true);

    const unsigned int tagged_end
      = (ON_SubD::VertexTag::Smooth != edge_vertex[0]->m_vertex_tag)
      ? 0
      : ((ON_SubD::VertexTag::Smooth != edge_vertex[1]->m_vertex_tag) ? 1 : ON_UNSET_UINT_INDEX);
    double edgePsum[3];
    if (
      ON_UNSET_UINT_INDEX == tagged_end
      || 0.5 == m_sector_coefficient[tagged_end]
      || (bSubDivideXEdgesAsSmooth && ON_SubD::EdgeTag::X == m_edge_tag)
      )
    {
      // ignore edge weights
      edgePsum[0] = 0.375*(edgeP[0][0] + edgeP[1][0]);
      edgePsum[1] = 0.375*(edgeP[0][1] + edgeP[1][1]);
      edgePsum[2] = 0.375*(edgeP[0][2] + edgeP[1][2]);
    }
    else if (ON_SubD::VertexTag::Smooth == edge_vertex[1 - tagged_end]->m_vertex_tag
      && m_sector_coefficient[tagged_end] > 0.0
      && m_sector_coefficient[tagged_end] < 1.0
      )
    {
      double w[2];
      w[tagged_end] = m_sector_coefficient[tagged_end];
      w[1 - tagged_end] = 1.0 - w[tagged_end];
      edgePsum[0] = 0.75*(w[0] * edgeP[0][0] + w[1] * edgeP[1][0]);
      edgePsum[1] = 0.75*(w[0] * edgeP[0][1] + w[1] * edgeP[1][1]);
      edgePsum[2] = 0.75*(w[0] * edgeP[0][2] + w[1] * edgeP[1][2]);
    }
    else
    {
      // error:
      //   Both ends of a smooth vertex are tagged
      //   or weights are incorrectly set
      //   or ...
      return ON_SubDEdge_GetSubdivisionPointError(this, subdivision_point, edgeP, true);
    }

    if (4 == face_edge_count[0] && 4 == face_edge_count[1])
    {
      // common case when both neighboring faces are quads
      subdivision_point[0] = edgePsum[0] + 0.0625*(facePsum[0][0] + facePsum[1][0]);
      subdivision_point[1] = edgePsum[1] + 0.0625*(facePsum[0][1] + facePsum[1][1]);
      subdivision_point[2] = edgePsum[2] + 0.0625*(facePsum[0][2] + facePsum[1][2]);

      if (bApplyDisplacement)
      {
        subdivision_point[0] += displacementV[0];
        subdivision_point[1] += displacementV[1];
        subdivision_point[2] += displacementV[2];
      }

      if (bUseSavedSubdivisionPoint)
        SetSavedSubdivisionPoint(subd_type,subdivision_point);

      return true;
    }
    
    if (3 == face_edge_count[0] && 3 == face_edge_count[1])
    {
      // common case when both neighboring faces are triangles
      subdivision_point[0] = edgePsum[0] + 0.125*(facePsum[0][0] + facePsum[1][0]);
      subdivision_point[1] = edgePsum[1] + 0.125*(facePsum[0][1] + facePsum[1][1]);
      subdivision_point[2] = edgePsum[2] + 0.125*(facePsum[0][2] + facePsum[1][2]);

      if (bApplyDisplacement)
      {
        subdivision_point[0] += displacementV[0];
        subdivision_point[1] += displacementV[1];
        subdivision_point[2] += displacementV[2];
      }

      if (bUseSavedSubdivisionPoint)
        SetSavedSubdivisionPoint(subd_type,subdivision_point);

      return true;
    }

    // general formula works for all cases including face_edge_count[0] != face_count[2]
    const double f0 = 0.125 / ((double)(face_edge_count[0]-2));
    const double f1 = 0.125 / ((double)(face_edge_count[1]-2));
    subdivision_point[0] = edgePsum[0] + f0 * facePsum[0][0] + f1 * facePsum[1][0];
    subdivision_point[1] = edgePsum[1] + f0 * facePsum[0][1] + f1 * facePsum[1][1];
    subdivision_point[2] = edgePsum[2] + f0 * facePsum[0][2] + f1 * facePsum[1][2];

    if (bApplyDisplacement)
    {
      subdivision_point[0] += displacementV[0];
      subdivision_point[1] += displacementV[1];
      subdivision_point[2] += displacementV[2];
    }

    if (bUseSavedSubdivisionPoint)
      SetSavedSubdivisionPoint(subd_type,subdivision_point);

    return true;
  }

  if ( IsCrease(false == bSubDivideXEdgesAsSmooth) )
  {
    subdivision_point[0] = 0.5*(edgeP[0][0] + edgeP[1][0]);
    subdivision_point[1] = 0.5*(edgeP[0][1] + edgeP[1][1]);
    subdivision_point[2] = 0.5*(edgeP[0][2] + edgeP[1][2]);

    if (bApplyDisplacement)
    {
      subdivision_point[0] += displacementV[0];
      subdivision_point[1] += displacementV[1];
      subdivision_point[2] += displacementV[2];
    }

    if (bUseSavedSubdivisionPoint)
      SetSavedSubdivisionPoint(subd_type,subdivision_point);

    return true;
  }

  // invalid edge->m_edge_tag
  return ON_SubDEdge_GetSubdivisionPointError(this, subdivision_point, edgeP, true);
}











static unsigned int GetSectorBoundaryEdgesError()
{
  return ON_SUBD_RETURN_ERROR(0);
}

unsigned int ON_SubDEdge::GetSectorBoundaryEdges(
  unsigned int edge_vertex_index,
  ON_SubDEdgePtr* edge_ptr0,
  ON_SubDEdgePtr* edge_ptr1
  ) const
{
  if (nullptr != edge_ptr0)
    *edge_ptr0 = ON_SubDEdgePtr::Null;
  if (nullptr != edge_ptr1)
    *edge_ptr1 = ON_SubDEdgePtr::Null;

  const unsigned int edge_face_count = m_face_count;
  if (edge_face_count <= 0 || edge_face_count > 2)
    return GetSectorBoundaryEdgesError();

  if (2 == edge_face_count && ON_SubD::EdgeTag::Crease == m_edge_tag)
    return GetSectorBoundaryEdgesError();

  if (0 != edge_vertex_index && 1 != edge_vertex_index)
    return GetSectorBoundaryEdgesError();

  const ON_SubDVertex* vertex = m_vertex[edge_vertex_index];
  if (nullptr == vertex || vertex->m_face_count <= 0)
    return GetSectorBoundaryEdgesError();

  const unsigned int vertex_face_count = vertex->m_face_count;
  unsigned int sector_face_count = 0;
  ON_SubDEdgePtr sector_boundary[2] = {};
  for (unsigned int edge_face_index = 0; edge_face_index < edge_face_count; edge_face_index++)
  {
    const ON_SubDEdge* edge0 = this;
    unsigned int edge0_end_index = edge_vertex_index;
    unsigned int edge0_face_index = edge_face_index;
    ON_SubDFacePtr face_ptr = edge0->m_face2[edge0_face_index];
    while (sector_face_count < vertex_face_count)
    {
      const ON_SubDFace* face = ON_SUBD_FACE_POINTER(face_ptr.m_ptr);
      if (0 == face)
        return GetSectorBoundaryEdgesError();
      ON__UINT_PTR face_dir = ON_SUBD_FACE_DIRECTION(face_ptr.m_ptr);

      sector_face_count++;

      unsigned int face_edge0_index = face->EdgeArrayIndex(edge0);
      if (ON_UNSET_UINT_INDEX == face_edge0_index)
        return 0;

      unsigned int face_edge1_index
        = face_edge0_index;
      face_edge1_index +=
        (1 == (edge0_end_index + face_dir))
        ? 1
        : (face->m_edge_count - 1);
      face_edge1_index %= face->m_edge_count;

      ON_SubDEdgePtr edge1_ptr = face->EdgePtr(face_edge1_index);
      const ON_SubDEdge* edge1 = ON_SUBD_EDGE_POINTER(edge1_ptr.m_ptr);
      if (nullptr == edge1)
        return GetSectorBoundaryEdgesError();

      unsigned int edge1_end_index = (0 == face_dir) ? (1 - edge0_end_index) : edge0_end_index;
      if (1 == ON_SUBD_EDGE_DIRECTION(edge1_ptr.m_ptr))
        edge1_end_index = 1 - edge1_end_index;
      if (vertex != edge1->m_vertex[edge1_end_index])
        return GetSectorBoundaryEdgesError();

      if ( edge1->IsSmooth(true) && 2 == edge1->m_face_count )
      {
        const ON_SubDFace* edge1_faces[2] = { ON_SUBD_FACE_POINTER(edge1->m_face2[0].m_ptr), ON_SUBD_FACE_POINTER(edge1->m_face2[1].m_ptr) };
        unsigned int edge1_face_index = (face == edge1_faces[0] ? 1 : 0);
        if (nullptr == edge1_faces[edge1_face_index] || face == edge1_faces[edge1_face_index])
          return GetSectorBoundaryEdgesError();
        face_ptr = edge1->m_face2[edge1_face_index];
        edge0 = edge1;
        edge0_face_index = edge1_face_index;
        edge0_end_index = edge1_end_index;
        continue;
      }

      sector_boundary[edge_face_index] = ON_SubDEdgePtr::Create(edge1, edge1_end_index);
      break;
    }
  }
  
  if (sector_face_count <= 0 || sector_boundary[0].IsNull())
    return GetSectorBoundaryEdgesError();
  if (1 == edge_face_count)
    sector_boundary[1] = ON_SubDEdgePtr::Create(this, edge_vertex_index);
  else if (sector_boundary[1].IsNull())
    return GetSectorBoundaryEdgesError();

  if (nullptr != edge_ptr0)
    *edge_ptr0 = sector_boundary[0];
  if (nullptr != edge_ptr1)
    *edge_ptr1 = sector_boundary[1];

  return sector_face_count;
}

class ON_ScratchBuffer
{
public:
  ON_ScratchBuffer(
    size_t sizeof_buffer,
    void* stack_buffer,
    size_t sizeof_stack_buffer
    )
    : m_buffer(nullptr)
    , m_heap_buffer(nullptr)
  {
    m_buffer
      = (sizeof_buffer > sizeof_stack_buffer || nullptr == stack_buffer)
      ? stack_buffer
      : (m_heap_buffer = new (std::nothrow) double[1 + sizeof_buffer / sizeof(double)]);
  }

  void* Buffer()
  {
    return m_buffer;
  }

  ~ON_ScratchBuffer()
  {
    if (nullptr != m_heap_buffer)
    {
      double* p = m_heap_buffer;
      m_heap_buffer = nullptr;
      delete[] p;
    }
  }

private:
  void* m_buffer;
  double* m_heap_buffer;

private:
  // prohibit use - no implementation
  ON_ScratchBuffer(const ON_ScratchBuffer&);
  ON_ScratchBuffer& operator-(const ON_ScratchBuffer&);
};

////static bool IsSmoothManifoldEdge(const ON_SubDEdge* edge)
////{
////  return (nullptr != edge && (ON_SubD::EdgeTag::Smooth == edge->m_edge_tag || ON_SubD::EdgeTag::X == edge->m_edge_tag) && 2 == edge->m_face_count);
////}

////unsigned int ON_SubDimple::GetSector(
////  const ON_SubDFace* starting_face,
////  ON__UINT_PTR face_vertex_index,
////  ON_SubDVertex& sector
////  ) const
////{
////  sector.m_edge_count = 0;
////  sector.m_face_count = 0;
////
////  if (nullptr == starting_face || face_vertex_index >= starting_face->m_edge_count)
////    return GetSectorError(sector);
////
////  unsigned short face_edge_index = (unsigned short)face_vertex_index;
////  ON__UINT_PTR edge0_ptr = starting_face->EdgePtr(face_edge_index > 0 ? (face_edge_index - 1) : (starting_face->m_edge_count - 1)).m_ptr;
////  ON__UINT_PTR edge1_ptr = starting_face->EdgePtr(face_edge_index).m_ptr;
////  const ON_SubDEdge* edge0 = ON_SUBD_EDGE_POINTER(edge0_ptr);
////  const ON_SubDEdge* edge1 = ON_SUBD_EDGE_POINTER(edge1_ptr);
////  if (nullptr == edge0 || nullptr == edge1)
////    return GetSectorError(sector);
////  ON__UINT_PTR edge0_end = 1 - ON_SUBD_EDGE_DIRECTION(edge0_ptr);
////  ON__UINT_PTR edge1_end = ON_SUBD_EDGE_DIRECTION(edge1_ptr);
////
////  const ON_SubDVertex* vertex = edge0->m_vertex[edge0_end];
////  if ( nullptr == vertex || vertex->m_face_count < 1 || vertex->m_edge_count < 2)
////    return GetSectorError(sector);
////  if (vertex != edge1->m_vertex[edge1_end])
////    return GetSectorError(sector);
////
////  const unsigned int sector_capacity = vertex->m_face_count;
////  const size_t buffer_capacity = (size_t)(3*sector_capacity + 2);
////  ON__UINT_PTR stack_buffer[3 * 16 + 2];
////  ON_ScratchBuffer buffer(buffer_capacity*sizeof(stack_buffer[0]), stack_buffer, sizeof(stack_buffer));
////  if (nullptr == buffer.Buffer())
////    return GetSectorError(sector);
////
////  const ON_SubDFace** sector_faces = (const ON_SubDFace**)buffer.Buffer();
////  const ON_SubDEdge** sector_edges = (const ON_SubDEdge**)(sector_faces + sector_capacity);
////  ON__UINT_PTR* sector_edge_ends = (ON__UINT_PTR*)(sector_edges + (sector_capacity + 1));
////
////  const ON_SubDFace* face0 = starting_face;
////  sector_faces[0] = face0;
////  sector_edges[0] = edge0;
////  sector_edge_ends[0] = edge0_end;
////  sector_edges[1] = edge1;
////  sector_edge_ends[1] = edge1_end;
////  unsigned int sector_index = 1;
////  unsigned int right_side_sector_count = 1;
////
////  if (false == IsSmoothManifoldEdge(edge0) && false == IsSmoothManifoldEdge(edge1))
////  {
////    // both edges act as creases
////    sector_edges[sector_capacity] = edge1;
////    sector_edge_ends[sector_capacity] = edge1_end;
////  }
////  else
////  {
////    // at least one input edge is a smooth manifold edge (2 faces)
////    for (unsigned int sector_side = 0; sector_side < 2; sector_side++)
////    {
////      const ON_SubDFace* face1 = nullptr;
////      while (sector_index < sector_capacity)
////      {
////        if (false == IsSmoothManifoldEdge(edge1))
////          break;
////
////        face1 = edge1->NeighborFace(face0,true);
////        if (sector_faces[0] == face1)
////        {
////          // circled around vertex back where we started.
////          // Since the edge trap at the end of the for loop
////          // did not break, this is an error condition.
////          return GetSectorError(sector);
////        }
////
////        if (nullptr == face1 || face0 == face1)
////          return GetSectorError(sector);
////
////        unsigned int face1_edge_count = face1->m_edge_count;
////        if (face1_edge_count < 2)
////          return GetSectorError(sector);
////
////        unsigned int face1_edge0_index = face1->EdgeArrayIndex(edge1);
////        if (ON_UNSET_UINT_INDEX == face1_edge0_index)
////          return GetSectorError(sector);
////        edge0_ptr = face1->EdgePtr(face1_edge0_index).m_ptr;
////        edge0 = ON_SUBD_EDGE_POINTER(edge0_ptr);
////        if (edge0 != edge1)
////          return GetSectorError(sector);
////        ON__UINT_PTR edge0_dir = ON_SUBD_EDGE_DIRECTION(edge0_ptr); // edge0 - face1 natural orientation relationship
////        edge0_end = edge1_end;
////
////        // If 1 == (edge0_end + edge0_dir), then face0 and face1 have same natural 
////        // orientations across shared edge and new edge1 = next edge on face1.
////        // If 1 != edge0_to_edge1_index_delta, then face0 and face1 opposite same natural 
////        // orientations across shared edge and new edge1 = prev edge on face1.
////        unsigned int face1_edge1_index
////          = (1 == (edge0_end + edge0_dir))
////          ? (face1_edge0_index + 1)
////          : (face1_edge0_index + (face1_edge_count - 1));
////        face1_edge1_index %= face1_edge_count;
////
////        edge1_ptr = face1->EdgePtr(face1_edge1_index).m_ptr;
////        edge1 = ON_SUBD_EDGE_POINTER(edge1_ptr);
////        if (nullptr == edge1)
////          return GetSectorError(sector);
////        ON__UINT_PTR edge1_dir = ON_SUBD_EDGE_DIRECTION(edge1_ptr); // edge1 - face1 natural orientation relationship
////        edge1_end = (1 == ((edge0_end + edge0_dir + edge1_dir) % 2)) ? 0 : 1;
////        if (vertex != edge1->m_vertex[edge1_end])
////          return GetSectorError(sector);
////        if (vertex == edge1->m_vertex[1 - edge1_end])
////          return GetSectorError(sector);
////
////        face0 = face1;
////        sector_faces[sector_index] = face0;
////        if (0 == sector_side)
////        {
////          sector_edges[sector_index] = edge0;
////          sector_edge_ends[sector_index] = edge0_end;
////          sector_index++;
////          if (edge1 == sector_edges[0])
////          {
////            // circled around back to where we started
////            break;
////          }
////        }
////        else
////        {
////          sector_edges[sector_index] = edge1;
////          sector_edge_ends[sector_index] = edge1_end;
////          sector_index++;
////        }
////      }
////
////      if (0 == sector_side)
////      {
////        // finished first side
////
////        // Mark where the first side information ends.
////        right_side_sector_count = sector_index;
////
////        // Save the final boundary information in a place where 
////        // the 2nd pass will not write over it.
////        sector_edges[sector_capacity] = edge1;
////        sector_edge_ends[sector_capacity] = edge1_end;
////
////        if (sector_edges[0] == sector_edges[sector_capacity])
////        {
////          // If sector_edge_ends[0] is a smooth 2-faced edge, then
////          // we circled around the vertex through smooth 2-faced edges
////          // until we got back to the starting vertex.
////          //
////          // If sector_edge_ends[0] is a crease, then the vertex is a dart.
////          //
////          // If sector_edge_ends[0] has more than 2 faces, then we've gone
////          // around a manifold sector that "begins/ends" at sector_edge_ends[0]
////          break;
////        }
////
////        // prepare for second side
////        face0 = sector_faces[0];
////        edge1 = sector_edges[0];
////        edge1_end = sector_edge_ends[0];
////      }
////      else
////      {
////        // finished second side
////        if (sector_faces[0] == face1)
////          return GetSectorError(sector);
////
////        break;
////      }
////    }
////  }
////
////  if (0 == sector_index)
////    return GetSectorError(sector);
////
////  if (nullptr == sector_edges[0] || nullptr == sector_edges[sector_index - 1] || nullptr == sector_edges[sector_capacity])
////    return GetSectorError(sector);
////
////  if (IsSmoothManifoldEdge(sector_edges[sector_capacity]))
////  {
////    if (right_side_sector_count < 2)
////      return GetSectorError(sector);
////    if (sector_index > right_side_sector_count)
////      return GetSectorError(sector);
////    if (sector_edges[0] != sector_edges[sector_capacity])
////      return GetSectorError(sector);
////  }
////  else if (sector_index > right_side_sector_count)
////  {
////    if (IsSmoothManifoldEdge(sector_edges[sector_index-1]))
////      return GetSectorError(sector);
////  }
////
////  if (!InitializeSector(vertex, sector))
////    return GetSectorError(sector);
////
////  if (false == const_cast<ON_SubDimple*>(this)->m_heap.GrowVertexEdgeArray(&sector, sector_index + 1))
////    return GetSectorError(sector);
////  if (false == const_cast<ON_SubDimple*>(this)->m_heap.GrowVertexFaceArray(&sector, sector_index))
////    return GetSectorError(sector);
////  unsigned int sector_face_count = 0;
////  while (sector_index > right_side_sector_count)
////  {
////    sector_index--;
////    sector.m_edges[sector_face_count] = ON_SubDEdgePtr::Create(sector_edges[sector_index], sector_edge_ends[sector_index]);
////    sector.m_faces[sector_face_count] = sector_faces[sector_index];
////    sector_face_count++;
////  }
////  for (sector_index = 0; sector_index < right_side_sector_count; sector_index++)
////  {
////    sector.m_edges[sector_face_count] = ON_SubDEdgePtr::Create(sector_edges[sector_index], sector_edge_ends[sector_index]);
////    sector.m_faces[sector_face_count] = sector_faces[sector_index];
////    sector_face_count++;
////  }
////
////  sector.m_edges[sector_face_count] = ON_SubDEdgePtr::Create(sector_edges[sector_capacity], sector_edge_ends[sector_capacity]);
////  sector.m_face_count = (unsigned short)sector_face_count;
////  sector.m_edge_count = sector.m_face_count+1;
////
////  return sector_face_count;
////}

////unsigned int ON_SubDimple::GetSector(
////  const ON_SubDVertex* vertex,
////  const ON_SubDFace* face,
////  ON_SubDVertex& sector
////  ) const
////{
////  unsigned int face_vertex_index = (nullptr != face) ? face->VertexIndex(vertex) : ON_UNSET_UINT_INDEX;
////  if (ON_UNSET_UINT_INDEX == face_vertex_index)
////    return GetSectorError(sector);
////  return GetSector(face, face_vertex_index, sector);
////}
////
////unsigned int ON_SubDimple::GetSector(
////  const ON_SubDVertex* vertex,
////  ON_SubDFacePtr face_ptr,
////  ON_SubDVertex& sector
////  ) const
////{
////  if (nullptr == vertex)
////    return GetSectorError(sector);
////  return GetSector(vertex, ON_SUBD_FACE_POINTER(face_ptr.m_ptr), sector);
////}
////
////unsigned int ON_SubDimple::GetSector(
////  const ON_SubDEdge* smooth_edge,
////  ON__UINT_PTR smooth_edge_end_index,
////  ON_SubDVertex& sector
////  ) const
////{
////  if (nullptr == smooth_edge || smooth_edge_end_index > 1)
////    return GetSectorError(sector);
////  const ON_SubDVertex* vertex = smooth_edge->m_vertex[smooth_edge_end_index];
////  if (nullptr == vertex)
////    return GetSectorError(sector);
////
////  switch (smooth_edge->m_face_count)
////  {
////  case 0:
////    if (!InitializeSector(vertex, sector))
////      break;
////    sector.m_edges = (ON_SubDEdgePtr*)const_cast<ON_SubDimple*>(this)->m_heap.GrowArrayByOneElement(sector.m_edge_count, (ON__UINT_PTR*)sector.m_edges);
////    sector.m_edges[sector.m_edge_count++] = ON_SubDEdgePtr::Create(smooth_edge, smooth_edge_end_index);
////    return true;
////    break;
////
////  case 1:
////    return GetSector(vertex, ON_SUBD_FACE_POINTER(smooth_edge->m_face2[0].m_ptr), sector);
////    break;
////
////  case 2:
////    if (ON_SubD::EdgeTag::Smooth == smooth_edge->m_edge_tag)
////      return GetSector(vertex, ON_SUBD_FACE_POINTER(smooth_edge->m_face2[0].m_ptr), sector);
////    break;
////  }
////
////  return GetSectorError(sector);
////}
////
////unsigned int ON_SubDimple::GetSector(
////  const ON_SubDVertex* vertex,
////  const ON_SubDEdge* smooth_edge,
////  ON_SubDVertex& sector
////  ) const
////{
////  if (nullptr == vertex || nullptr == smooth_edge)
////    return GetSectorError(sector);
////  unsigned int smooth_edge_end_index
////    = (vertex == smooth_edge->m_vertex[0])
////    ? 0
////    : ((vertex == smooth_edge->m_vertex[1]) ? 1 : ON_UNSET_UINT_INDEX);
////  if (ON_UNSET_UINT_INDEX == smooth_edge_end_index)
////    return GetSectorError(sector);
////  return GetSector(smooth_edge, smooth_edge_end_index, sector);
////}
////
////
////unsigned int ON_SubDimple::GetSector(
////  ON_SubDEdgePtr smooth_edge_ptr,
////  ON_SubDVertex& sector
////  ) const
////{
////  return GetSector(ON_SUBD_EDGE_POINTER(smooth_edge_ptr.m_ptr), ON_SUBD_EDGE_DIRECTION(smooth_edge_ptr.m_ptr), sector);
////}
////
////unsigned int ON_SubD::GetSector(
////  const ON_SubDFace* face,
////  ON__UINT_PTR face_vertex_index,
////  ON_SubDVertex& sector
////  ) const
////{
////  const ON_SubDimple* subdimple = SubDimple();
////  return (nullptr != subdimple) ? subdimple->GetSector(face, face_vertex_index, sector) : GetSectorError(sector);
////}
////
////unsigned int ON_SubD::GetSector(
////  const ON_SubDVertex* vertex,
////  const ON_SubDFace* face,
////  ON_SubDVertex& sector
////  ) const
////{
////  const ON_SubDimple* subdimple = SubDimple();
////  return (nullptr != subdimple) ? subdimple->GetSector(vertex, face, sector) : GetSectorError(sector);
////}
////
////unsigned int ON_SubD::GetSector(
////  const ON_SubDVertex* vertex,
////  ON_SubDFacePtr face_ptr,
////  ON_SubDVertex& sector
////  ) const
////{
////  const ON_SubDimple* subdimple = SubDimple();
////  return (nullptr != subdimple) ? subdimple->GetSector(vertex, face_ptr, sector) : GetSectorError(sector);
////}
////
////unsigned int ON_SubD::GetSector(
////  const ON_SubDVertex* vertex,
////  const ON_SubDEdge* smooth_edge,
////  ON_SubDVertex& sector
////  ) const
////{
////  const ON_SubDimple* subdimple = SubDimple();
////  return (nullptr != subdimple) ? subdimple->GetSector(vertex, smooth_edge, sector) : GetSectorError(sector);
////}
////
////unsigned int ON_SubD::GetSector(
////  const ON_SubDEdge* smooth_edge,
////  ON__UINT_PTR smooth_edge_end_index,
////  ON_SubDVertex& sector
////  ) const
////{
////  const ON_SubDimple* subdimple = SubDimple();
////  return (nullptr != subdimple) ? subdimple->GetSector(smooth_edge, smooth_edge_end_index, sector) : GetSectorError(sector);
////}
////
////unsigned int ON_SubD::GetSector(
////  ON_SubDEdgePtr smooth_edge_ptr,
////  ON_SubDVertex& sector
////  ) const
////{
////  const ON_SubDimple* subdimple = SubDimple();
////  return (nullptr != subdimple) ? subdimple->GetSector(smooth_edge_ptr, sector) : GetSectorError(sector);
////}



class FACE_AND_FACE_POINT
{
public:
  const ON_SubDFace* m_face;
  double m_faceP[3];
  static int CompareFacePointer(const void* a, const void* b);
};

int FACE_AND_FACE_POINT::CompareFacePointer(const void* a, const void* b)
{
  ON__UINT_PTR fa = (ON__UINT_PTR)(((const FACE_AND_FACE_POINT*)a)->m_face);
  ON__UINT_PTR fb = (ON__UINT_PTR)(((const FACE_AND_FACE_POINT*)b)->m_face);
  if (fa < fb)
    return -1;
  if (fa > fb)
    return 1;
  return 0;
}

bool ON_SubDSectorLimitPoint::IsUnset() const
{
  return (m_limitP[0] == ON_UNSET_VALUE);
}

bool ON_SubDSectorLimitPoint::IsNan() const
{
  return !(m_limitP[0] == m_limitP[0]);
}

bool ON_SubDSectorLimitPoint::IsZero() const
{
  const double* p = m_limitP;
  const double* p1 = p+12;
  while (p < p1)
  {
    if (!(0.0 == *p++))
      return false;
  }
  return true;
}

bool ON_SubDSectorLimitPoint::IsSet() const
{
  double x, y;
  const double* p = m_limitP;
  const double* p1 = p+3;
  while (p < p1)
  {
    x = *p++;
    if (ON_UNSET_VALUE == x || !(x == x))
      return false;
  }

  p = m_limitT1;
  p1 = p+6;
  while (p < p1)
  {
    const double* p2 = p+3;
    y = 0.0;
    while (p < p2)
    {
      x = *p++;
      if (ON_UNSET_VALUE == x || !(x == x))
        return false;
      if ( 0.0 != x )
       y = x;
    }
    if (!(y != 0.0))
      return false;
  }

  p = m_limitN;
  p1 = p+3;
  y = 0.0;
  while (p < p1)
  {
    x = *p++;
    if (ON_UNSET_VALUE == x || !(x == x))
      return false;
    y += x*x;
  }
  if (!(fabs(y - 1.0) <= 1e-4))
    return false;

  return true;
}

void ON_SubDVertex::CopyFrom(
  const ON_SubDVertex* src,
  bool bCopyEdgeArray,
  bool bCopyFaceArray,
  bool bCopyLimitPointList
  )
{
  if (nullptr == src)
    src = &ON_SubDVertex::Empty;

  ClearSavedLimitPoints();
  CopyBaseFrom(src);

  m_vertex_tag = src->m_vertex_tag;
  //m_vertex_edge_order = src->m_vertex_edge_order;
  //m_vertex_facet_type = src->m_vertex_facet_type;

  m_P[0] = src->m_P[0];
  m_P[1] = src->m_P[1];
  m_P[2] = src->m_P[2];


  if (bCopyLimitPointList)
  {
    ON_SubD::SubDType limit_point_subd_type = src->SavedLimitPointType();
    if (ON_SubD::SubDType::Unset != limit_point_subd_type)
    {
      for (const ON_SubDSectorLimitPoint* p = &src->m_limit_point; nullptr != p; p = p->m_next_sector_limit_point)
      {
        ON_SubDSectorLimitPoint limit_point = *p;
        limit_point.m_next_sector_limit_point = (ON_SubDSectorLimitPoint*)1; // disable checks
        SetSavedLimitPoint(limit_point_subd_type, limit_point);
      }
    }
  }

  if (bCopyEdgeArray)
  {
    if (src->m_edge_count > 0 && nullptr != src->m_edges && nullptr != m_edges && src->m_edge_count <= m_edge_capacity)
    {
      m_edge_count = src->m_edge_count;
      const unsigned int edge_count = src->m_edge_count;
      for (unsigned int vei = 0; vei < edge_count; vei++)
        m_edges[vei] = src->m_edges[vei];
    }
    else
      m_edge_count = 0;
  }

  if (bCopyFaceArray)
  {
    if (src->m_face_count > 0 && nullptr != src->m_faces && nullptr != m_faces && src->m_face_count <= m_face_capacity)
    {
      m_face_count = src->m_face_count;
      const unsigned int face_count = src->m_face_count;
      for (unsigned int vfi = 0; vfi < face_count; vfi++)
        m_faces[vfi] = src->m_faces[vfi];
    }
    else
      m_face_count = 0;
  }
}


static bool ON_SubDVertex_GetSubdivisionPointError(
  const class ON_SubDVertex* vertex,
  double vertex_point[3],
  const double* vertexP,
  bool bDamagedState
  )
{
  if (nullptr == vertex || nullptr == vertex_point)
    return false; // caller passed a null pointer - vertex is not necessarily damaged

  ON_SubDIncrementErrorCount();
  vertex->m_status.SetDamagedState(bDamagedState);

  vertex->ClearSavedSubdivisionPoint();

  if (nullptr != vertexP)
  {
    vertex_point[0] = vertexP[0];
    vertex_point[1] = vertexP[1];
    vertex_point[2] = vertexP[2];
  }

  return true;
}

bool ON_SubDVertex::GetGeneralQuadSubdivisionPoint(
  const class ON_SubDVertex* vertex,
  bool bUseSavedSubdivisionPoint,
  double vertex_point[3]
  )
{
  const ON_SubD::SubDType subdivision_type = ON_SubD::SubDType::QuadCatmullClark;

  const double* vertexP = vertex->m_P;

  const unsigned int n = vertex->m_face_count;

  // It is critical to use the centroids of the neighboring faces
  // in this step because the number of edges in each face's
  // boundary may not be constant.
  double facePsum[3] = { 0 };
  const ON_SubDFace*const* vertex_faces = vertex->m_faces;
  for (unsigned int i = 0; i < n; i++)
  {
    const ON_SubDFace* face = vertex_faces[i];
    if (nullptr != face)
    {
      double faceC[3];
      if (face->GetSubdivisionPoint(subdivision_type, bUseSavedSubdivisionPoint, faceC))
      {
        facePsum[0] += faceC[0];
        facePsum[1] += faceC[1];
        facePsum[2] += faceC[2];
        continue;
      }
    }
    // treat missing or damaged face as infinitesimally small
    facePsum[0] += vertexP[0];
    facePsum[1] += vertexP[1];
    facePsum[2] += vertexP[2];
  }

  double edgePsum[3] = { 0 };
  class ON_SubDEdgePtr* edges = vertex->m_edges;
  for (unsigned int i = 0; i < n; i++)
  {
    const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(edges[i].m_ptr);
    if (nullptr != edge)
    {
      const ON_SubDVertex* edge_vertex = edge->OtherEndVertex(vertex);
      if (nullptr != edge_vertex)
      {
        const double* edgeP = edge_vertex->m_P;
        edgePsum[0] += edgeP[0];
        edgePsum[1] += edgeP[1];
        edgePsum[2] += edgeP[2];
        continue;
      }
    }
    // treat missing or damaged edge as infinitesimally small
    edgePsum[0] += vertexP[0];
    edgePsum[1] += vertexP[1];
    edgePsum[2] += vertexP[2];
  }

  const double v_weight = 1.0 - 2.0 / ((double)n);
  const double ef_weight = 1.0 / ((double)(n*n));
  vertex_point[0] = v_weight*vertexP[0] + ef_weight*(edgePsum[0] + facePsum[0]);
  vertex_point[1] = v_weight*vertexP[1] + ef_weight*(edgePsum[1] + facePsum[1]);
  vertex_point[2] = v_weight*vertexP[2] + ef_weight*(edgePsum[2] + facePsum[2]);

  if (bUseSavedSubdivisionPoint)
    vertex->SetSavedSubdivisionPoint(ON_SubD::SubDType::QuadCatmullClark,vertex_point);

  return true;
}

bool ON_SubDVertex::GetQuadPoint(
  const class ON_SubDVertex* vertex,
  bool bUseSavedSubdivisionPoint,
  double vertex_point[3]
  )
{
  // This function is used to convert an arbitrary control polygon into the
  // "level 1" quad subD.  It cannot use the faster sub-D formulas because
  // a face can have an arbitrary number of edges.
  if (nullptr == vertex || nullptr == vertex_point)
    return ON_SubDVertex_GetSubdivisionPointError(vertex,vertex_point,nullptr,false);

  const double* vertexP = vertex->m_P;

  const unsigned int n = (nullptr != vertex->m_edges ? vertex->m_edge_count : 0);
  if (ON_SubD::VertexTag::Smooth == vertex->m_vertex_tag || ON_SubD::VertexTag::Dart == vertex->m_vertex_tag)
  {
    if (n < 3 || n != vertex->m_face_count || nullptr == vertex->m_faces)
      return ON_SubDVertex_GetSubdivisionPointError(vertex, vertex_point, vertexP, true);
    
    double facePsum[3] = { 0 };
    const ON_SubDFace*const* vertex_faces = vertex->m_faces;

    const ON_SubDFace* face = vertex_faces[0];
    if (nullptr == face)
      return ON_SubDVertex_GetSubdivisionPointError(vertex, vertex_point, vertexP, true);

    ////// for debugging code below, uncomment this line
    ////// and look for differences in results.
    ////return GetGeneralQuadSubdivisionPoint(vertex, vertex_point);

    const unsigned int k = (nullptr == face) ? 0U : face->m_edge_count;
    if (4 == k)
    {
      // possibly (probably?) every face is a quad
      double sum[3];
      for (unsigned int i = 0; i < n; i++)
      {
        const ON_SubDFace* vface = vertex_faces[i];
        const unsigned int face_n = ON_SubDVertex::GetFacePointSum(vface, vertex, sum);
        if (4 != face_n)
        {
          // The first face is a quadrangle and this face is not a quadrangle.
          //
          // It is critical to use the centroids of the neighboring faces
          // for this vertex subdivision point because the number of edges
          // in each face's boundary is not constant.
          return ON_SubDVertex::GetGeneralQuadSubdivisionPoint(vertex, bUseSavedSubdivisionPoint, vertex_point);
        }
        facePsum[0] += sum[0];
        facePsum[1] += sum[1];
        facePsum[2] += sum[2];
      }
    }
    else if (3 == k)
    {
      // possibly (probably?) every face is a triangle
      for (unsigned int i = 0; i < n; i++)
      {
        const ON_SubDFace* vface = vertex_faces[i];
        if (k != ((nullptr == vface) ? 0U : vface->m_edge_count))
        {
          // The first face is a triangle and this face is not a triangle.
          //
          // It is critical to use the centroids of the neighboring faces
          // for this vertex subdivision point because the number of edges
          // in each face's boundary is not constant.
          return ON_SubDVertex::GetGeneralQuadSubdivisionPoint(vertex, bUseSavedSubdivisionPoint, vertex_point);
        }
      }
    }
    else
    {
      // The first face has 5 or more edges.  
      // It is likely this is the initial subdivision being applied
      // to the level zero SubD control polygon.
      //
      // It may be critical to use the centroids of the neighboring faces
      // for this vertex subdivision point because the number of edges
      // in each face's boundary may not constant.  In any case, this
      // situation is not common and typically happens only on the
      // first subdivision step.
      return ON_SubDVertex::GetGeneralQuadSubdivisionPoint(vertex, bUseSavedSubdivisionPoint, vertex_point);
    }

    double edgePsum[3] = { 0 };
    class ON_SubDEdgePtr* edges = vertex->m_edges;
    for (unsigned int i = 0; i < n; i++)
    {
      const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(edges[i].m_ptr);
      if (nullptr != edge)
      {
        const ON_SubDVertex* edge_vertex = edge->OtherEndVertex(vertex);
        if (nullptr != edge_vertex)
        {
          const double* edgeP = edge_vertex->m_P;
          edgePsum[0] += edgeP[0];
          edgePsum[1] += edgeP[1];
          edgePsum[2] += edgeP[2];
          continue;
        }
      }
      // treat missing or damaged edge as infinitesimally small
      edgePsum[0] += vertexP[0];
      edgePsum[1] += vertexP[1];
      edgePsum[2] += vertexP[2];
    }

    if (4 == k)
    {
      // all faces were quads
      const double v_weight = 1.0 - 1.75 / ((double)n);
      const double e_weight = 1.5 / ((double)(n*n));
      const double f_weight = 0.25 / ((double)(n*n));
      vertex_point[0] = v_weight*vertexP[0] + e_weight*edgePsum[0] + f_weight*facePsum[0];
      vertex_point[1] = v_weight*vertexP[1] + e_weight*edgePsum[1] + f_weight*facePsum[1];
      vertex_point[2] = v_weight*vertexP[2] + e_weight*edgePsum[2] + f_weight*facePsum[2];
    }
    else
    {
      // all faces were triangles
      const double v_weight = 1.0 - 5.0 / ((double)(3 * n));
      const double e_weight = 5.0 / ((double)(3*n*n));
      vertex_point[0] = v_weight*vertexP[0] + e_weight*edgePsum[0];
      vertex_point[1] = v_weight*vertexP[1] + e_weight*edgePsum[1];
      vertex_point[2] = v_weight*vertexP[2] + e_weight*edgePsum[2];
    }

    if (bUseSavedSubdivisionPoint)
      vertex->SetSavedSubdivisionPoint(ON_SubD::SubDType::QuadCatmullClark,vertex_point);

    return true;
  }

  // vertex->m_vertex_tag is damaged
  return ON_SubDVertex_GetSubdivisionPointError(vertex, vertex_point, vertexP, true);
}


bool ON_SubDVertex::GetSubdivisionPoint(
  ON_SubD::SubDType subd_type,
    bool bUseSavedSubdivisionPoint,
    double subdivision_point[3]
  ) const
{
  // This function is used to convert an arbitrary control polygon into the
  // "level 1" subD.  It cannot use the faster sub-D formulas because
  // a face can have an arbitrary number of edges.
  if (nullptr == subdivision_point 
    || (ON_SubD::SubDType::TriLoopWarren != subd_type && ON_SubD::SubDType::QuadCatmullClark != subd_type))
    return ON_SubDVertex_GetSubdivisionPointError(this, subdivision_point, nullptr, false);

  if ( bUseSavedSubdivisionPoint && GetSavedSubdivisionPoint(subd_type,subdivision_point) )
    return true;

  double displacementV[3] = { 0 };
  const bool bApplyDisplacement = GetDisplacement(subd_type,displacementV);

  const double* vertexP = m_P;

  const unsigned int n = (nullptr != m_edges ? m_edge_count : 0);
  if (n < 2)
    return ON_SubDVertex_GetSubdivisionPointError(this, subdivision_point, vertexP, true);

  if (ON_SubD::VertexTag::Smooth == m_vertex_tag || ON_SubD::VertexTag::Dart == m_vertex_tag)
  {
    if (ON_SubD::SubDType::QuadCatmullClark == subd_type)
      return ON_SubDVertex::GetQuadPoint(this, bUseSavedSubdivisionPoint, subdivision_point);
    else if (ON_SubD::SubDType::TriLoopWarren == subd_type)
      return ON_SubDVertex::GetTriPoint(this, bUseSavedSubdivisionPoint, subdivision_point);
  }

  if (ON_SubD::VertexTag::Crease == m_vertex_tag)
  {
    class ON_SubDEdgePtr* edges = m_edges;
    const ON_SubDVertex* edge0_vertex = nullptr;

    for (unsigned int i = 0; i < n; i++)
    {
      const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(edges[i].m_ptr);
      if (nullptr == edge)
      {
        ON_SubDVertex_GetSubdivisionPointError(this, subdivision_point, vertexP, true);
        continue;
      }

      if (ON_SubD::EdgeTag::Crease != edge->m_edge_tag)
        continue;
      const ON_SubDVertex* edge_vertex = edge->OtherEndVertex(this);

      if (nullptr == edge_vertex)
      {
        ON_SubDVertex_GetSubdivisionPointError(this, subdivision_point, vertexP, true);
        continue;
      }

      if (nullptr == edge0_vertex)
      {
        edge0_vertex = edge_vertex;
        continue;
      }

      if (edge0_vertex == edge_vertex)
      {
        ON_SubDVertex_GetSubdivisionPointError(this, subdivision_point, vertexP, true);
        continue;
      }

      // We found the two crease edges that share this crease vertex.
      // (The parenthesis around the edgeP sum is to insure this result 
      // is independent of the order of the edges.)
      vertexP = m_P;
      const double* edgeP[2] = { edge0_vertex->m_P, edge_vertex->m_P };
      subdivision_point[0] = (vertexP[0] * 6.0 + (edgeP[0][0] + edgeP[1][0]))*0.125;
      subdivision_point[1] = (vertexP[1] * 6.0 + (edgeP[0][1] + edgeP[1][1]))*0.125;
      subdivision_point[2] = (vertexP[2] * 6.0 + (edgeP[0][2] + edgeP[1][2]))*0.125;

      if (bApplyDisplacement)
      {
        subdivision_point[0] += displacementV[0];
        subdivision_point[1] += displacementV[1];
        subdivision_point[2] += displacementV[2];
      }

      if (bUseSavedSubdivisionPoint)
        SetSavedSubdivisionPoint(subd_type,subdivision_point);

      return true;
    }

    return ON_SubDVertex_GetSubdivisionPointError(this, subdivision_point, vertexP, true);
  }

  if (ON_SubD::VertexTag::Corner == m_vertex_tag)
  {
    vertexP = m_P;
    subdivision_point[0] = vertexP[0];
    subdivision_point[1] = vertexP[1];
    subdivision_point[2] = vertexP[2];

    if (bApplyDisplacement)
    {
      subdivision_point[0] += displacementV[0];
      subdivision_point[1] += displacementV[1];
      subdivision_point[2] += displacementV[2];
    }

    if (bUseSavedSubdivisionPoint)
      SetSavedSubdivisionPoint(subd_type,subdivision_point);

    return true;
  }

  // vertex is damaged
  return ON_SubDVertex_GetSubdivisionPointError(this, subdivision_point, vertexP, true);
}

unsigned int ON_SubDVertex::GetFacePointSum(
  const ON_SubDFace* face,
  const ON_SubDVertex* vertex,
  double* facePsum
  )
{
  const ON_SubDEdge* e;
  ON__UINT_PTR e_ptr, edir;
  const double* faceP;

  if (nullptr == face)
    return 0;

  const unsigned int n = face->m_edge_count;

  facePsum[0] = 0.0;
  facePsum[1] = 0.0;
  facePsum[2] = 0.0;

  if (3 == n)
  {
    return n;
  }

  if (4 == n)
  {
    for (unsigned int i = 0; i < 4; i++)
    {
      e_ptr = face->m_edge4[i].m_ptr;
      e = ON_SUBD_EDGE_POINTER(e_ptr);
      if (nullptr != e && (vertex == e->m_vertex[0] || vertex == e->m_vertex[1]))
      {
        edir = ON_SUBD_EDGE_DIRECTION(e_ptr);
        e_ptr = face->m_edge4[(i + ((vertex == e->m_vertex[edir]) ? 2 : 3)) % 4].m_ptr;
        e = ON_SUBD_EDGE_POINTER(e_ptr);
        edir = ON_SUBD_EDGE_DIRECTION(e_ptr);
        if (nullptr == e || nullptr == e->m_vertex[edir])
          return 0;
        faceP = e->m_vertex[edir]->m_P;
        facePsum[0] = faceP[0];
        facePsum[1] = faceP[1];
        facePsum[2] = faceP[2];
        return n;
      }
    }
    return 0;
  }

  if (n <= 4 || nullptr == face->m_edgex)
    return 0;

  e_ptr = face->m_edgex[n-5].m_ptr;
  e = ON_SUBD_EDGE_POINTER(e_ptr);
  if (nullptr == e)
    return 0;
  edir = ON_SUBD_EDGE_DIRECTION(e_ptr);
  unsigned int skipped_edge_count = (vertex == e->m_vertex[edir]) ? 1 : 0;
  unsigned int facePcount = 0;
  const ON_SubDEdgePtr* edge_ptrs = face->m_edge4;
  for (unsigned int i = skipped_edge_count; i < n; i++)
  {
    if (4 == i)
      edge_ptrs = face->m_edgex - 4;
    e_ptr = edge_ptrs[i].m_ptr;
    e = ON_SUBD_EDGE_POINTER(e_ptr);
    if (nullptr == e)
      return 0;
    edir = ON_SUBD_EDGE_DIRECTION(e_ptr);
    if (vertex == e->m_vertex[0] || vertex == e->m_vertex[1])
    {
      skipped_edge_count++;
      if (skipped_edge_count > 2)
      {
        facePsum[0] = 0.0;
        facePsum[1] = 0.0;
        facePsum[2] = 0.0;
        return 0;
      }
      if (vertex == e->m_vertex[edir])
      {
        i++;
        if (4 == i)
          edge_ptrs = face->m_edgex - 4;
      }
      continue;
    }
    faceP = e->m_vertex[edir]->m_P;
    facePsum[0] += faceP[0];
    facePsum[1] += faceP[1];
    facePsum[2] += faceP[2];
    facePcount++;
  }

  if (n == facePcount + 3)
    return n;

  facePsum[0] = 0.0;
  facePsum[1] = 0.0;
  facePsum[2] = 0.0;
  return 0;
}

bool ON_SubDVertex::GetTriPoint(
  const class ON_SubDVertex* vertex,
  bool bUseSavedSubdivisionPoint,
  double vertex_point[3]
  )
{
  if (nullptr == vertex || nullptr == vertex_point)
    return ON_SubDVertex_GetSubdivisionPointError(vertex, vertex_point, nullptr, false);

  const double* vertexP = vertex->m_P;

  const unsigned int n = (nullptr != vertex->m_edges ? vertex->m_edge_count : 0);
  if (ON_SubD::VertexTag::Smooth == vertex->m_vertex_tag || ON_SubD::VertexTag::Dart == vertex->m_vertex_tag)
  {
    if (n < 3)
      return ON_SubDVertex_GetSubdivisionPointError(vertex, vertex_point, vertexP, true);

    double edgePsum[3] = { 0 };
    const ON_SubDEdgePtr* edges = vertex->m_edges;
    ON__UINT_PTR e_ptr;
    for (unsigned int i = 0; i < n; i++)
    {
      e_ptr = edges[i].m_ptr;
      const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(e_ptr);
      if (nullptr != edge)
      {
        const ON_SubDVertex* edge_vertex = (vertex != edge->m_vertex[0]) ? edge->m_vertex[0] : edge->m_vertex[1];
        if (nullptr != edge_vertex)
        {
          const double* edgeP = edge_vertex->m_P;
          edgePsum[0] += edgeP[0];
          edgePsum[1] += edgeP[1];
          edgePsum[2] += edgeP[2];
          continue;
        }
      }
      // treat missing or damaged face as infinitesimally small
      edgePsum[0] += vertexP[0];
      edgePsum[1] += vertexP[1];
      edgePsum[2] += vertexP[2];
    }

    double v_weight, e_weight;
    if (3 == n)
    {
      v_weight = 0.4375;  // 7/16
      e_weight = 0.1875;  // 3/16 = (9/16) / 3
    }
    else
    {
      v_weight = 0.625; // 5/8
      e_weight = 0.375/((double)n); // = (3/8)/n
    }
    vertex_point[0] = v_weight*vertexP[0] + e_weight*edgePsum[0];
    vertex_point[1] = v_weight*vertexP[1] + e_weight*edgePsum[1];
    vertex_point[2] = v_weight*vertexP[2] + e_weight*edgePsum[2];

    if (bUseSavedSubdivisionPoint)
      vertex->SetSavedSubdivisionPoint(ON_SubD::SubDType::TriLoopWarren,vertex_point);

    return true;
  }

  // vertex->m_vertex_tag is damaged
  return ON_SubDVertex_GetSubdivisionPointError(vertex, vertex_point, vertexP, true);
}


unsigned int ON_SubDimple::GlobalSubdivide(
  ON_SubD::SubDType subdivision_type,
  bool bUseSavedSubdivisionPoints
  )
{
  const bool bQuadSubD = (ON_SubD::SubDType::QuadCatmullClark == subdivision_type);
  const bool bTriSubD = (ON_SubD::SubDType::TriLoopWarren == subdivision_type);

  if (false == bQuadSubD && false == bTriSubD)
    return ON_SUBD_RETURN_ERROR(0);

  if (m_levels.UnsignedCount() <= 0)
    return ON_SUBD_RETURN_ERROR(0U);
  const unsigned int level0_index = m_levels.UnsignedCount()-1;

  if ( nullptr == m_levels[level0_index])
    return ON_SUBD_RETURN_ERROR(0U);

  const ON_SubDLevel& level0 = *m_levels[level0_index];
  if (level0.IsEmpty())
    return ON_SUBD_RETURN_ERROR(0U);
  if ( level0.m_edge_count <= 0U )
    return ON_SUBD_RETURN_ERROR(0U);

  const unsigned int level1_index = level0_index+1;
  
  if (0 == level0_index && subdivision_type != level0.m_subdivision_type )
  {
    if (false == m_levels[level0_index]->SetSubDType(subdivision_type))
      return ON_SUBD_RETURN_ERROR(0);
  }

  ON_SubDLevel* level1 = SubDLevel(level1_index,true);
  if ( nullptr == level1 )
    return ON_SUBD_RETURN_ERROR(0);
  if (false == level1->SetSubDType(subdivision_type))
    return ON_SUBD_RETURN_ERROR(0);

  double P[3];
  ON_SubDVertex* v;

  if (bQuadSubD)
  {
    // Add face points
    for (const ON_SubDFace* f0 = level0.m_face[0]; nullptr != f0; f0 = f0->m_next_face)
    {
      if (false == f0->GetSubdivisionPoint(subdivision_type, bUseSavedSubdivisionPoints, P))
        continue;
      if (nullptr == f0->m_subd_point1)
      {
        const_cast<ON_SubDFace*>(f0)->m_subd_point1 = v = AllocateVertex(ON_SubD::VertexTag::Smooth, level1_index, P);
        AddVertexToLevel(v);
      }
      else
      {
        v = const_cast<ON_SubDVertex*>(f0->m_subd_point1);
        v->m_P[0] = P[0];
        v->m_P[1] = P[1];
        v->m_P[2] = P[2];
      }
    }
  }

  // Add edge points
  for (const ON_SubDEdge* e0 = level0.m_edge[0]; nullptr != e0; e0 = e0->m_next_edge)
  {
    if (false == e0->GetSubdivisionPoint(subdivision_type, bUseSavedSubdivisionPoints, P))
      continue;
    // (the subdivision point of an edge tagged as ON_SubD::EdgeTag::X is a smooth vertex.)
    const ON_SubD::VertexTag vertex_tag
      = ON_SubD::EdgeTag::Crease == e0->m_edge_tag
      ? ON_SubD::VertexTag::Crease
      : ON_SubD::VertexTag::Smooth;
    if (nullptr == e0->m_subd_point1)
    {
      const_cast<ON_SubDEdge*>(e0)->m_subd_point1 = v = AllocateVertex(vertex_tag, level1_index, P );
      AddVertexToLevel(v);
    }
    else
    {
      v = const_cast<ON_SubDVertex*>(e0->m_subd_point1);
      v->m_vertex_tag = vertex_tag;
      v->m_P[0] = P[0];
      v->m_P[1] = P[1];
      v->m_P[2] = P[2];
    }
  }

  // Add vertex points
  for (const ON_SubDVertex* v0 = level0.m_vertex[0]; nullptr != v0; v0 = v0->m_next_vertex)
  {
    if (false == v0->GetSubdivisionPoint(subdivision_type, bUseSavedSubdivisionPoints, P))
      continue;
    if (nullptr == v0->m_subd_point1)
    {
      const_cast<ON_SubDVertex*>(v0)->m_subd_point1 = v = AllocateVertex(v0->m_vertex_tag, level1_index, P);
      AddVertexToLevel(v);
    }
    else
    {
      v = const_cast<ON_SubDVertex*>(v0->m_subd_point1);
      v->m_vertex_tag = v0->m_vertex_tag;
      v->m_P[0] = P[0];
      v->m_P[1] = P[1];
      v->m_P[2] = P[2];
    }
    
  }

  bool bUpdateEdgeWeights = false;

  // subdivide edges
  for (const ON_SubDEdge* e0 = level0.m_edge[0]; nullptr != e0; e0 = e0->m_next_edge)
  {
    if (nullptr == e0->m_subd_point1)
      continue;
    ON_SubDVertex* end_vertex[2] = { const_cast<ON_SubDVertex*>(e0->m_vertex[0]->m_subd_point1), const_cast<ON_SubDVertex*>(e0->m_vertex[1]->m_subd_point1) };
    ON_SubDVertex* mid_vertex = const_cast<ON_SubDVertex*>(e0->m_subd_point1);
    double w[2] = { e0->m_sector_coefficient[0], e0->m_sector_coefficient[1] };
    if (bTriSubD && ON_SubD::EdgeTag::Smooth == e0->m_edge_tag && !(0.0 == w[0] && 0.0 == w[1]))
    {
      // If a neighboring face is not a triangle, the weight will need to be recalculated.
      for (unsigned int i = 0; i < e0->m_face_count; i++)
      {
        const ON_SubDFace* f = e0->Face(i);
        if (nullptr != f && 3 != f->m_edge_count)
        {
          bUpdateEdgeWeights = true;
          if (!(0.0 == w[0]))
            w[0] = ON_SubDSectorType::UnsetSectorWeight;
          if (!(0.0 == w[1]))
            w[1] = ON_SubDSectorType::UnsetSectorWeight;
          break;
        }
      }
    }
    ON_SubD::EdgeTag edge_tag = e0->m_edge_tag;
    if (ON_SubD::EdgeTag::X == edge_tag && 2 == e0->m_face_count)
    {
      if ( nullptr != mid_vertex && ON_SubD::VertexTag::Smooth == mid_vertex->m_vertex_tag )
        edge_tag = ON_SubD::EdgeTag::Smooth;
    }
    AddEdge(edge_tag, end_vertex[0], w[0], mid_vertex, 0.0);
    AddEdge(edge_tag, mid_vertex, 0.0, end_vertex[1], w[1]);
  }

  // subdivide faces
  if (bTriSubD)
  {
    for (const ON_SubDFace* f0 = level0.m_face[0]; nullptr != f0; f0 = f0->m_next_face)
    {
      bool bUnsetEdgeWeight = false;
      GlobalTriSubdivideFace(f0, bUseSavedSubdivisionPoints, &bUnsetEdgeWeight);
      if (bUnsetEdgeWeight)
        bUpdateEdgeWeights = true;
    }

    if (bUpdateEdgeWeights)
    {
      for (const ON_SubDEdge* e1 = level1->m_edge[0]; nullptr != e1; e1 = e1->m_next_edge)
      {
        if (ON_SubD::EdgeTag::Smooth != e1->m_edge_tag)
          continue;
        for (unsigned int i = 0; i < 2; i++)
        {
          if (ON_SubDSectorType::UnsetSectorWeight == e1->m_sector_coefficient[i])
          {
            const double w = ON_SubDSectorType::Create(subdivision_type, e1, i).SectorWeight();
            const_cast<ON_SubDEdge*>(e1)->m_sector_coefficient[i] = w;
          }
        }
      }
    }
  }

  if (bQuadSubD)
  {
    for (const ON_SubDFace* f0 = level0.m_face[0]; nullptr != f0; f0 = f0->m_next_face)
    {
      GlobalQuadSubdivideFace(bUseSavedSubdivisionPoints,f0);
    }
  }

  return level1_index;
}

unsigned int ON_SubDimple::GlobalQuadSubdivideFace(
  bool bUseSavedSubdivisionPoint,
  const ON_SubDFace* f0
  )
{
  // This is a private member function.  
  // The caller insures f0 != nullptr.

  const ON_SubD::SubDType subdivision_type = ON_SubD::SubDType::QuadCatmullClark;

  const unsigned int f0_edge_count = f0->m_edge_count;
  if (f0_edge_count < 3)
    return 0;

  const unsigned int parent_face_id = f0->m_id;
  const unsigned int zero_face_id = (0 == f0->m_level) ? parent_face_id : f0->m_zero_face_id;

  if (nullptr == f0->m_subd_point1)
  {
    // add face centroid
    double faceC[3];
    if (false == f0->GetSubdivisionPoint(subdivision_type, bUseSavedSubdivisionPoint, faceC))
      return 0;
    f0->SetSavedSubdivisionPoint(subdivision_type,faceC);
    unsigned int level1_index = f0->m_level + 1;
    ON_SubDVertex* v = AllocateVertex(ON_SubD::VertexTag::Smooth, level1_index, faceC );
    AddVertexToLevel(v);
    const_cast<ON_SubDFace*>(f0)->m_subd_point1 = v;
  }


  ON_SubDEdge* E0[2];
  ON__UINT_PTR E0dir[2];
  ON_SubDEdge* E1[4];
  ON__UINT_PTR E1dir[4];
  ON_SubDEdgePtr f1edges[4];
  ON__UINT_PTR e_ptr;
  ON_SubDEdge* FirstE1(nullptr);
  double w;

  e_ptr = f0->EdgePtr(f0_edge_count - 1).m_ptr;
  E0[1] = ON_SUBD_EDGE_POINTER(e_ptr);
  E0dir[1] = ON_SUBD_EDGE_DIRECTION(e_ptr);
  E1[2] = nullptr;

  unsigned int f1_count = 0;

  const double w_2facesector = ON_SubDSectorType::CreaseSectorWeight(subdivision_type, 2);

  for (unsigned int i = 0; i < f0_edge_count; i++)
  {
    E0[0] = E0[1];
    E0dir[0] = E0dir[1];
    e_ptr = f0->EdgePtr(i).m_ptr;
    E0[1] = ON_SUBD_EDGE_POINTER(e_ptr);
    E0dir[1] = ON_SUBD_EDGE_DIRECTION(e_ptr);

    if (nullptr == E0[0] || nullptr == E0[1])
      continue;
    if (nullptr == E0[0]->m_subd_point1 || nullptr == E0[1]->m_subd_point1)
      continue;

    e_ptr = E0[0]->m_subd_point1->m_edges[0 == E0dir[0] ? 1 : 0].m_ptr;
    E1[0] = ON_SUBD_EDGE_POINTER(e_ptr);
    E1dir[0] = E0dir[0];

    e_ptr = E0[1]->m_subd_point1->m_edges[0 == E0dir[1] ? 0 : 1].m_ptr;
    E1[1] = ON_SUBD_EDGE_POINTER(e_ptr);
    E1dir[1] = E0dir[1];

    E1[3] = E1[2];
    if (nullptr == E1[3])
    {
      //  The value of E0[0]->m_subd_point1->m_vertex_tag should be either 
      //  ON_SubD::VertexTag::Smooth or ON_SubD::VertexTag::Crease. In the
      //  case when it's value is "crease", the resulting edge end weight
      //  will be 0.5 because the edge has two adjacent faces and "theta"
      //  will be pi/2.  
      //  The resulting quad edge weight is 0.5 = 1/2 + 1/3*cos(pi/2).
      w = (ON_SubD::VertexTag::Crease == E0[0]->m_subd_point1->m_vertex_tag) ? w_2facesector : 0.0;
      E1[3] = AddEdge(ON_SubD::EdgeTag::Smooth, const_cast<ON_SubDVertex*>(f0->m_subd_point1), 0.0, const_cast<ON_SubDVertex*>(E0[0]->m_subd_point1), w);
      if (nullptr == FirstE1)
        FirstE1 = E1[3];
    }
    E1dir[3] = 0;

    if (i + 1 < f0_edge_count || nullptr == FirstE1)
    {
      //  The value of E0[0]->m_subd_point1->m_vertex_tag should be either 
      //  ON_SubD::VertexTag::Smooth or ON_SubD::VertexTag::Crease. In the
      //  case when it's value is "crease", the resulting edge end weight
      //  will be zero because the edge has two adjacent faces and "theta"
      //  will be pi/2.  The resulting edge weight is 0.5.
      w = (ON_SubD::VertexTag::Crease == E0[1]->m_subd_point1->m_vertex_tag) ? w_2facesector : 0.0;
      E1[2] = AddEdge(ON_SubD::EdgeTag::Smooth, const_cast<ON_SubDVertex*>(f0->m_subd_point1), 0.0, const_cast<ON_SubDVertex*>(E0[1]->m_subd_point1), w);
    }
    else
    {
      E1[2] = FirstE1;
    }
    E1dir[2] = 1;

    f1edges[0] = ON_SubDEdgePtr::Create(E1[0], E1dir[0]);
    f1edges[1] = ON_SubDEdgePtr::Create(E1[1], E1dir[1]);
    f1edges[2] = ON_SubDEdgePtr::Create(E1[2], E1dir[2]);
    f1edges[3] = ON_SubDEdgePtr::Create(E1[3], E1dir[3]);

    ON_SubDFace* f1 = AddFace(4, f1edges);
    if (nullptr != f1)
    {
      f1->m_zero_face_id = zero_face_id;
      f1->m_parent_face_id = parent_face_id;
      f1_count++;
    }
  }

  // return number of new faces
  return f1_count;
}


static double TriCornerSectorWeight(
  ON_SubDEdgePtr e0_ptr,
  ON_SubDEdgePtr e1_ptr,
  ON_SubD::VertexTag vertex_tag
  )
{
  const ON_SubD::SubDType subdivision_type = ON_SubD::SubDType::TriLoopWarren;

  if (ON_SubD::VertexTag::Smooth == vertex_tag)
    return 0.0;

  if (ON_SubD::VertexTag::Unset == vertex_tag)
    return ON_SUBD_RETURN_ERROR(ON_SubDSectorType::ErrorSectorWeight);

  const ON_SubDEdge* e0 = ON_SUBD_EDGE_POINTER(e0_ptr.m_ptr);
  if (nullptr == e0)
    return ON_SUBD_RETURN_ERROR(ON_SubDSectorType::ErrorSectorWeight);
  ON__INT_PTR e0dir = ON_SUBD_EDGE_DIRECTION(e0_ptr.m_ptr);

  const ON_SubDEdge* e1 = ON_SUBD_EDGE_POINTER(e1_ptr.m_ptr);
  if (nullptr == e1)
    return ON_SUBD_RETURN_ERROR(ON_SubDSectorType::ErrorSectorWeight);
  ON__INT_PTR e1dir = ON_SUBD_EDGE_DIRECTION(e1_ptr.m_ptr);

  // flip direction of e0 so that both edges are leaving the vertex
  e0dir = 1 - e0dir;
    
  if (ON_SubD::EdgeTag::Crease == e0->m_edge_tag && ON_SubD::EdgeTag::Crease == e1->m_edge_tag)
  {
    // The radial edge we are about to add has two faces in its sector between the
    // creased edges e0 and e1.
    unsigned int sector_face_count = 2;      
    if (ON_SubD::VertexTag::Crease == vertex_tag)
    {
      return ON_SubDSectorType::CreaseSectorWeight(subdivision_type, sector_face_count);
    }
    
    if (ON_SubD::VertexTag::Corner == vertex_tag)
    {
      const double corner_sector_angle_radians 
        = ON_SubDSectorType::CornerSectorAngleRadiansFromEdges(ON_SubDEdgePtr::Create(e0, e0dir), ON_SubDEdgePtr::Create(e1, e1dir));
      return ON_SubDSectorType::CreateCornerSectorType(subdivision_type, sector_face_count, corner_sector_angle_radians).SectorWeight();
    }
    
    return ON_SUBD_RETURN_ERROR(ON_SubDSectorType::ErrorSectorWeight);
  }

  if (ON_SubD::VertexTag::Crease == vertex_tag || ON_SubD::VertexTag::Corner == vertex_tag || ON_SubD::VertexTag::Dart == vertex_tag)
  {
    // The weight calculation requires all edges in the sector exist
    // and has to be delayed until the subdivision topology is complete.
    return ON_SubDSectorType::UnsetSectorWeight;
  }
    
  double w0
    = (ON_SubD::EdgeTag::Smooth == e0->m_edge_tag)
    ? e0->m_sector_coefficient[e0dir]
    : ON_SubDSectorType::UnsetSectorWeight;

  double w1
    = (ON_SubD::EdgeTag::Smooth == e1->m_edge_tag)
    ? e1->m_sector_coefficient[e1dir]
    : ON_SubDSectorType::UnsetSectorWeight;

  double w = (w0 == w1) ? w0 : ((ON_SubDSectorType::UnsetSectorWeight != w0) ? w0 : w1);
  if (w == w && ON_SubDSectorType::UnsetSectorWeight != w)
    return w;

  return ON_SUBD_RETURN_ERROR(ON_SubDSectorType::ErrorSectorWeight);
}

unsigned int ON_SubDimple::GlobalTriSubdivideFace(
  const ON_SubDFace* f0,
  bool bUseSavedSubdivisionPoint,
  bool* bUnsetEdgeWeight
  )
{
  const ON_SubD::SubDType subdivision_type = ON_SubD::SubDType::TriLoopWarren;

  // This is a private member function.  
  // The caller insures f0 != nullptr and bUnsetEdgeWeight != nullptr.

  *bUnsetEdgeWeight = false;

  const unsigned int f0_edge_count = f0->m_edge_count;
  if (f0_edge_count < 3)
    return 0;

  const unsigned int parent_face_id = f0->m_id;
  const unsigned int zero_face_id = (0 == f0->m_level) ? parent_face_id : f0->m_zero_face_id;


  ON_SubDEdge* E0[3];
  ON__UINT_PTR E0dir[3];
  ON_SubDEdge* E1[9];
  ON_SubDEdgePtr f1edges[3];
  ON__UINT_PTR e_ptr;
  ON_SubDFace* f1;

  e_ptr = f0->EdgePtr(f0_edge_count - 1).m_ptr;
  E0[1] = ON_SUBD_EDGE_POINTER(e_ptr);
  E0dir[1] = ON_SUBD_EDGE_DIRECTION(e_ptr);
  E1[2] = nullptr;

  unsigned int f1_count = 0;

  if (3 == f0_edge_count)
  {
    unsigned int j = 0;
    for (unsigned int i = 0; i < 3; i++)
    {
      e_ptr = f0->m_edge4[i].m_ptr;
      E0[i] = ON_SUBD_EDGE_POINTER(e_ptr);
      if (nullptr == E0[i] || nullptr == E0[i]->m_subd_point1)
        break;
      E0dir[i] = ON_SUBD_EDGE_DIRECTION(e_ptr);

      e_ptr = E0[i]->m_subd_point1->m_edges[E0dir[i]].m_ptr;
      E1[j] = ON_SUBD_EDGE_POINTER(e_ptr);
      if (nullptr == E1[j])
        break;
      j++;

      e_ptr = E0[i]->m_subd_point1->m_edges[1 - E0dir[i]].m_ptr;
      E1[j] = ON_SUBD_EDGE_POINTER(e_ptr);
      if (nullptr == E1[j])
        break;
      j++;
    }
    if (6 != j)
      return 0;

    //  The value of E0[0]->m_subd_point1->m_vertex_tag should be either 
    //  ON_SubD::VertexTag::Smooth or ON_SubD::VertexTag::Crease. In the
    //  case when it's value is "crease", the resulting edge end weight
    //  will be 0.5 because the edge has three adjacent faces and "theta"
    //  will be pi/3.  
    //  The resulting tri edge weight is 0.5 = 1/3 + 1/3*cos(pi/3).
    const double w_3facesector = ON_SubDSectorType::CreaseSectorWeight(subdivision_type, 3);

    double w0 = (ON_SubD::VertexTag::Crease == E0[0]->m_subd_point1->m_vertex_tag) ? w_3facesector : 0.0;
    double w1 = (ON_SubD::VertexTag::Crease == E0[2]->m_subd_point1->m_vertex_tag) ? w_3facesector : 0.0;
    E1[6] = AddEdge(ON_SubD::EdgeTag::Smooth, const_cast<ON_SubDVertex*>(E0[0]->m_subd_point1), w0, const_cast<ON_SubDVertex*>(E0[2]->m_subd_point1), w1);
    w0 = w1;
    w1 = (ON_SubD::VertexTag::Crease == E0[1]->m_subd_point1->m_vertex_tag) ? w_3facesector : 0.0;
    E1[7] = AddEdge(ON_SubD::EdgeTag::Smooth, const_cast<ON_SubDVertex*>(E0[2]->m_subd_point1), w0, const_cast<ON_SubDVertex*>(E0[1]->m_subd_point1), w1);
    w0 = w1;
    w1 = (ON_SubD::VertexTag::Crease == E0[0]->m_subd_point1->m_vertex_tag) ? w_3facesector : 0.0;
    E1[8] = AddEdge(ON_SubD::EdgeTag::Smooth, const_cast<ON_SubDVertex*>(E0[1]->m_subd_point1), w0, const_cast<ON_SubDVertex*>(E0[0]->m_subd_point1), w1);

    f1edges[0] = ON_SubDEdgePtr::Create(E1[0], E0dir[0]);
    f1edges[1] = ON_SubDEdgePtr::Create(E1[6], 0);
    f1edges[2] = ON_SubDEdgePtr::Create(E1[5], E0dir[2]);
    f1 = AddFace(3, f1edges);
    if (nullptr != f1)
    {
      f1->m_zero_face_id = zero_face_id;
      f1->m_parent_face_id = parent_face_id;
      f1_count++;
    }

    f1edges[0] = ON_SubDEdgePtr::Create(E1[4], E0dir[2]);
    f1edges[1] = ON_SubDEdgePtr::Create(E1[7], 0);
    f1edges[2] = ON_SubDEdgePtr::Create(E1[3], E0dir[1]);
    f1 = AddFace(3, f1edges);
    if (nullptr != f1)
    {
      f1->m_zero_face_id = zero_face_id;
      f1->m_parent_face_id = parent_face_id;
      f1_count++;
    }

    f1edges[0] = ON_SubDEdgePtr::Create(E1[2], E0dir[1]);
    f1edges[1] = ON_SubDEdgePtr::Create(E1[8], 0);
    f1edges[2] = ON_SubDEdgePtr::Create(E1[1], E0dir[0]);
    f1 = AddFace(3, f1edges);
    if (nullptr != f1)
    {
      f1->m_zero_face_id = zero_face_id;
      f1->m_parent_face_id = parent_face_id;
      f1_count++;
    }

    f1edges[0] = ON_SubDEdgePtr::Create(E1[6], 1);
    f1edges[1] = ON_SubDEdgePtr::Create(E1[8], 1);
    f1edges[2] = ON_SubDEdgePtr::Create(E1[7], 1);
    f1 = AddFace(3, f1edges);
    if (nullptr != f1)
    {
      f1->m_zero_face_id = zero_face_id;
      f1->m_parent_face_id = parent_face_id;
      f1_count++;
    }

    // return number of new faces
    return f1_count;
  }

  if (f0_edge_count < 2)
    return 0;

  // This code  builds triangles from the subdivided edges to the face's centroid.
  //
  // In general, there is no single robust way to deal with polygons with lots of sides.
  // In particular, the code below is not approprite for non-convex polygons and is
  // of limited use for polygons that are not close to regular.
  //
  // The basic issue is that if a SubD control net makes it to this point,
  // higher level code and, ultimately, the user, must be responsible
  // for creating a "reasonable" input control net.
  //
  // This code is here so something happens in trianglular subd when
  // a non-triangle is subdivided.
  //
  // This code is "reasonable" when a user wants to apply a triangular
  // subdivision to a "nice" quad mesh SubD control polygon.

  ON_SubDVertex* center_vertex = const_cast<ON_SubDVertex*>(f0->m_subd_point1);
  if (nullptr == center_vertex)
  {
    // add face centroid
    double faceC[3];
    if (false == f0->GetSubdivisionPoint(subdivision_type, bUseSavedSubdivisionPoint, faceC))
      return 0;
    f0->SetSavedSubdivisionPoint(subdivision_type, faceC);
    unsigned int level1_index = f0->m_level + 1;
    center_vertex = AllocateVertex(ON_SubD::VertexTag::Smooth, level1_index, faceC);
    if (nullptr == center_vertex)
      return 0;
    AddVertexToLevel(center_vertex);
    const_cast<ON_SubDFace*>(f0)->m_subd_point1 = center_vertex;
  }

  E1[4] = nullptr; // radial edge from end of previous vertex to this vertex
  E1[8] = nullptr; // used to save pointer to first radial edge.
  const ON_SubDVertex* edge1_vertex;
  ON_SubDVertex* right_vertex = nullptr;
  for (unsigned int i = 0; i < f0_edge_count; i++)
  {
    // This case is a special case and it will be called on control 
    // polygons created by inexperienced programmers,
    // It is important that all information be validated.  
    // When invalid information is detected, that edge/triangle
    // is simply skipped and there will be a "smooth sided hole"
    // in the resulting mesh.
    //
    ON_SubDVertex* left_vertex = right_vertex;
    right_vertex = nullptr;

    // E1[4] = previous "right" radial
    // E1[2] = current "left" radial from left_vertex to center_vertex
    E1[2] = E1[4];
    E1[4] = nullptr;

    const ON_SubDEdgePtr ei_ptr = f0->EdgePtr(i);

    E0[0] = ON_SUBD_EDGE_POINTER(ei_ptr.m_ptr);
    E0dir[0] = ON_SUBD_EDGE_DIRECTION(ei_ptr.m_ptr);
    if (nullptr == E0[0])
      continue;

    ON_SubDVertex* mid_vertex = const_cast<ON_SubDVertex*>(E0[0]->m_subd_point1);
    if (nullptr == mid_vertex || mid_vertex->m_edge_count < 2 || nullptr == mid_vertex->m_edges)
      continue;

    // E1[0] = "left" side of E0[0]
    e_ptr = mid_vertex->m_edges[E0dir[0]].m_ptr;
    E1[0] = ON_SUBD_EDGE_POINTER(e_ptr);
    if (nullptr == E1[0])
      continue;

    if (mid_vertex != E1[0]->m_vertex[1 - E0dir[0]])
      continue;

    // E1[1] = "right" side of E0[0]
    e_ptr = mid_vertex->m_edges[1 - E0dir[0]].m_ptr;
    E1[1] = ON_SUBD_EDGE_POINTER(e_ptr);
    if (nullptr == E1[1])
      continue;

    if (mid_vertex != E1[1]->m_vertex[E0dir[0]])
      continue;

    // If E0[0] is a crease:
    //  Then mid_vertex should be tagged as a crease vertex and both
    //  E1[0] and E1[1] should also be tagged as creased edges.
    //  The new edge from mid_vertex to center_vertex
    //  the "sector_face_count" will be 2 and the "theta" used to calculate the
    //  edge weight will be pi/2.
    //  mid_weight = 1/3 + 1/3*cos(pi/2) = 1/3  ( cos(pi/2) = zero )
    bool bValidTags;
    if (ON_SubD::EdgeTag::Smooth == E0[0]->m_edge_tag || ON_SubD::EdgeTag::X == E0[0]->m_edge_tag)
    {
      bValidTags 
        = ON_SubD::VertexTag::Smooth == mid_vertex->m_vertex_tag 
        && ON_SubD::EdgeTag::Smooth == E1[0]->m_edge_tag 
        && ON_SubD::EdgeTag::Smooth == E1[1]->m_edge_tag;
    }
    else if (ON_SubD::EdgeTag::Crease == E0[0]->m_edge_tag)
    {
      bValidTags 
        = ON_SubD::VertexTag::Crease == mid_vertex->m_vertex_tag 
        && ON_SubD::EdgeTag::Crease == E1[0]->m_edge_tag 
        && ON_SubD::EdgeTag::Crease == E1[1]->m_edge_tag;
    }
    else
      bValidTags = false;

    if (false == bValidTags)
    {
      ON_SubDIncrementErrorCount();
      continue;
    }

    const double mid_weight
      = (ON_SubD::EdgeTag::Crease == E0[0]->m_edge_tag)
      ? ON_SubDSectorType::CreaseSectorWeight(subdivision_type, 2)
      : ON_SubDSectorType::IgnoredSectorWeight;

    // E1[2] = radial from "start" of E1[0] to face centroid.
    edge1_vertex = E1[0]->m_vertex[E0dir[0]];
    if (nullptr == left_vertex || nullptr == E1[2] || left_vertex != E1[2]->m_vertex[0] || left_vertex != edge1_vertex)
    {      
      // when input is valid, this clause is executed when i = 0.
      left_vertex = const_cast<ON_SubDVertex*>(edge1_vertex);
      if (nullptr != left_vertex)
      {
        const double left_w = TriCornerSectorWeight(f0->EdgePtr((i + (f0_edge_count-1)) % f0_edge_count), ei_ptr, left_vertex->m_vertex_tag);
        if (ON_SubDSectorType::UnsetSectorWeight == left_w)
          *bUnsetEdgeWeight = true;
        E1[2] = AddEdge(ON_SubD::EdgeTag::Smooth, left_vertex, left_w, center_vertex, 0.0);
        if (0 == i)
        {
          // E1[8] = first radial edge
          E1[8] = E1[2];
        }
      }
      else
        E1[2] = nullptr;
    }

     // E1[3] = radial from E0[0]->m_subd_point1 to face centroid.
    E1[3] = AddEdge(ON_SubD::EdgeTag::Smooth, mid_vertex, mid_weight, center_vertex, 0.0);
    if (nullptr == E1[3])
      continue;

    // E1[4] = radial from "end" of E1[1] to face centroid.
    if (i + 1 < f0_edge_count)
    {
      right_vertex = const_cast<ON_SubDVertex*>(E1[1]->m_vertex[1 - E0dir[0]]);
      if (nullptr != right_vertex)
      {
        const double right_w = TriCornerSectorWeight(ei_ptr, f0->EdgePtr((i + 1) % f0_edge_count), right_vertex->m_vertex_tag);
        if (ON_SubDSectorType::UnsetSectorWeight == right_w)
          *bUnsetEdgeWeight = true;
        E1[4] = AddEdge(ON_SubD::EdgeTag::Smooth, right_vertex, right_w, center_vertex, 0.0);
      }
      else
        E1[4] = nullptr;
    }
    else
    {
      // E1[8] = first radial edge
      E1[4] = E1[8];
    }

    if (nullptr != E1[0] && nullptr != E1[2])
    {
      // "left" triangle with "base" E1[0] and apex at face centroid
      // the "base" ON_SubDEdgePtr::Create(E1[0], E0dir[0]) runs from V1[0] to E0[0]->m_subd_point1.
      f1edges[0] = ON_SubDEdgePtr::Create(E1[0], E0dir[0]); // V1[0] to E0[0]->m_subd_point1
      f1edges[1] = ON_SubDEdgePtr::Create(E1[3], 0); // E0[0]->m_subd_point1 to center_vertex
      f1edges[2] = ON_SubDEdgePtr::Create(E1[2], 1); // center_vertex to V1[0]
      f1 = AddFace(3, f1edges);
      if (nullptr != f1)
      {
        f1->m_zero_face_id = zero_face_id;
        f1->m_parent_face_id = parent_face_id;
        f1_count++;
      }
    }

    if (nullptr != E1[1] && nullptr != E1[4] )
    {
      // "right" triangle with "base" E1[1] and apex at face centroid
      // The "base" ON_SubDEdgePtr::Create(E1[1], E0dir[0]) runs from E0[0]->m_subd_point1 to V1[1].
      f1edges[0] = ON_SubDEdgePtr::Create(E1[1], E0dir[0]); // E0[0]->m_subd_point1 to V1[1]
      f1edges[1] = ON_SubDEdgePtr::Create(E1[4], 0); // V1[1] to center_vertex
      f1edges[2] = ON_SubDEdgePtr::Create(E1[3], 1); // center_vertex to E0[0]->m_subd_point1
      f1 = AddFace(3, f1edges);
      if (nullptr != f1)
      {
        f1->m_zero_face_id = zero_face_id;
        f1->m_parent_face_id = parent_face_id;
        f1_count++;
      }
    }
  }

  // return number of faces added
  return f1_count;
}

bool ON_SubD::Subdivide(
  ON_SubD::SubDType subd_type,
  unsigned int level_index,
  unsigned int count
  )
{
  ON_SubDimple* subdimple = SubDimple(false);
  if (nullptr == subdimple)
    return ON_SUBD_RETURN_ERROR(false);
  return subdimple->Subdivide(subd_type,level_index,count);
}


bool ON_SubDimple::Subdivide(
  ON_SubD::SubDType subd_type,
  unsigned int level_index,
  unsigned int count
  )
{
  if (level_index >= m_levels.UnsignedCount() || nullptr == m_levels[level_index])
    return ON_SUBD_RETURN_ERROR(false);

  if (count <= 0)
    return ON_SUBD_RETURN_ERROR(false);

  if (level_index+count > ON_SubD::maximum_subd_level)
    return ON_SUBD_RETURN_ERROR(false);


  if (ON_SubD::SubDType::Unset == subd_type)
  {
    subd_type = m_levels[level_index]->m_subdivision_type;
    if ( ON_SubD::SubDType::Unset == subd_type )
      subd_type = ON_SubD::DefaultSubDType();
  }

  if (false == ON_SubD::IsQuadOrTriSubDType(subd_type))
    return ON_SUBD_RETURN_ERROR(false);

  if (subd_type != m_levels[level_index]->m_subdivision_type)
  {
    if (false == m_levels[level_index]->SetSubDType(subd_type))
      return ON_SUBD_RETURN_ERROR(false);
  }

  ClearHigherSubdivisionLevels(level_index + 1);
  if ( level_index + 1 != m_levels.UnsignedCount() )
    return ON_SUBD_RETURN_ERROR(false);

  m_active_level = m_levels[level_index];
 
  const bool bUseSavedSubdivisionPoints = true;
  for (unsigned int i = level_index+1; i <= level_index+count; i++)
  {
    unsigned int rc = GlobalSubdivide(subd_type,bUseSavedSubdivisionPoints);
    if (i != rc)
      return ON_SUBD_RETURN_ERROR(false);
    m_active_level = m_levels[i];
  }
  return true;
}

ON_SubDEdgePtr ON_SubDimple::MergeEdges(
  ON_SubDEdgePtr eptr0,
  ON_SubDEdgePtr eptr1
  )
{
  if ( false == ON_SubD::EdgesCanBeMerged(eptr0,eptr1) )
    return ON_SUBD_RETURN_ERROR(ON_SubDEdgePtr::Null);

  ON_SubDEdge* e[2] = { ON_SUBD_EDGE_POINTER(eptr0.m_ptr), ON_SUBD_EDGE_POINTER(eptr1.m_ptr) };
  ON__UINT_PTR edir[2] = { ON_SUBD_EDGE_DIRECTION(eptr0.m_ptr), ON_SUBD_EDGE_DIRECTION(eptr1.m_ptr) };
  const ON_SubDVertex* end_v[2] = {e[0]->m_vertex[edir[0]], e[1]->m_vertex[1 - edir[1]]};
  if (nullptr == end_v[0] || nullptr == end_v[1] || end_v[0] == end_v[1] )
    return ON_SUBD_RETURN_ERROR(ON_SubDEdgePtr::Null);

  ON_SubD::EdgeTag merged_edge_tag 
    = (e[0]->IsSmooth(true) || e[1]->IsSmooth(true))
    ? ON_SubD::EdgeTag::Smooth
    : ON_SubD::EdgeTag::Crease;

  for (unsigned int j = 0; j < e[1]->m_face_count; j++)
  {
    ON_SubDFace* f = const_cast<ON_SubDFace*>(e[1]->Face(j));
    if (nullptr == f)
      continue;
    f->RemoveEdgeFromArray(e[1]);
  }
  
  // remove middle vertex
  ON_SubDVertex* middle_v = const_cast< ON_SubDVertex* >(e[1]->m_vertex[edir[1]]);
  if (nullptr != middle_v && middle_v != end_v[0] && middle_v != end_v[1] )
  {
    if (middle_v->m_edge_count > 0 && nullptr != middle_v->m_edges)
    {
      unsigned int vei0 = middle_v->EdgeArrayIndex(e[0]);
      unsigned int vei1 = middle_v->EdgeArrayIndex(e[1]);      
      unsigned int vei_count = middle_v->m_edge_count;
      middle_v->m_edge_count = 0;
      for (unsigned int vei = 0; vei < vei_count; vei++)
      {
        if ( vei == vei0 || vei == vei1 )
          continue;

        // happens when middle_v is a multiple sector corner, non-manifold, or other rare cases
        if (vei > middle_v->m_edge_count)
          middle_v->m_edges[middle_v->m_edge_count] = middle_v->m_edges[vei];
        middle_v->m_edge_count++;
      }
    }
    if (0 == middle_v->m_edge_count || nullptr == middle_v->m_edges)
    {
      ReturnVertex(middle_v);
      middle_v = nullptr;
    }
  }

  e[0]->m_vertex[1-edir[0]] = nullptr;
  e[1]->m_vertex[edir[1]] = nullptr;
  e[1]->m_vertex[1-edir[1]] = nullptr;

  for (unsigned int i = 0; i < end_v[1]->m_edge_count; i++)
  {
    if (e[1] == ON_SUBD_EDGE_POINTER(end_v[1]->m_edges[i].m_ptr))
    {
      const_cast< ON_SubDVertex* >(end_v[1])->m_edges[i] = ON_SubDEdgePtr::Create(e[0], 1 - edir[0]);
      e[0]->m_vertex[1 - edir[0]] = end_v[1];
      break;
    }
  }

  e[0]->m_sector_coefficient[1 - edir[0]] = e[1]->m_sector_coefficient[1 - edir[1]];

  const bool bTagged[2] = { end_v[0]->IsCreaseOrCorner(), end_v[1]->IsCreaseOrCorner() };
  if (ON_SubD::EdgeTag::Smooth == merged_edge_tag || false == bTagged[0] || false == bTagged[1])
  {
    e[0]->m_edge_tag
      = (bTagged[0] && bTagged[1])
      ? ON_SubD::EdgeTag::X
      : ON_SubD::EdgeTag::Smooth;
    if ( false == bTagged[0])
      e[0]->m_sector_coefficient[0] = ON_SubDSectorType::IgnoredSectorWeight;
    else if (!(e[0]->m_sector_coefficient[0] > 0.0 && e[0]->m_sector_coefficient[0] < 1.0))
      e[0]->m_sector_coefficient[0] = ON_SubDSectorType::UnsetSectorWeight;
    if ( false == bTagged[1])
      e[0]->m_sector_coefficient[1] = ON_SubDSectorType::IgnoredSectorWeight;
    else if (!(e[0]->m_sector_coefficient[1] > 0.0 && e[0]->m_sector_coefficient[1] < 1.0))
      e[0]->m_sector_coefficient[1] = ON_SubDSectorType::UnsetSectorWeight;
  }
  else
  {
    e[0]->m_edge_tag = ON_SubD::EdgeTag::Crease;
    e[0]->m_sector_coefficient[0] = ON_SubDSectorType::IgnoredSectorWeight;
    e[0]->m_sector_coefficient[1] = ON_SubDSectorType::IgnoredSectorWeight;
  }

  ReturnEdge(e[1]);

  return eptr0;
}

ON_SubDEdgePtr ON_SubD::MergeEdges(
  ON_SubDEdgePtr eptr0,
  ON_SubDEdgePtr eptr1
  )
{
  ON_SubDimple* subdimple = SubDimple(false);
  return (nullptr != subdimple) ? subdimple->MergeEdges(eptr0, eptr1) : ON_SubDEdgePtr::Null;
}

static bool EdgesAreMergableTest(
  ON_SubDEdge* e[2],
  ON__UINT_PTR edir[2],
  bool bTestColinearity,
  double distance_tolerance,
  double maximum_aspect,
  double sin_angle_tolerance
  )
{
  if (
    nullptr == e[0] || nullptr == e[1] 
    || e[0] == e[1]
    || edir[0] > 1 || edir[1] > 1
    || e[0]->m_face_count != e[1]->m_face_count
    //|| e[0]->m_edge_tag != e[1]->m_edge_tag
    )
  {
    return false;
  }

  if ( nullptr == e[1]->m_vertex[0] || nullptr == e[1]->m_vertex[1] )
  {
    // Setting e[1] = nullptr used in edge merging code and doesn't hurt other uses of this static function
    e[1] = nullptr;
    return false;
  }

  // v[0] = start
  // v[1] = end
  // v[2] = middle (will be removed)
  const ON_SubDVertex* v[4] = { e[0]->m_vertex[edir[0]], e[1]->m_vertex[1 - edir[1]], e[0]->m_vertex[1 - edir[0]], e[1]->m_vertex[edir[1]] };

  if (nullptr == v[0] || nullptr == v[1] || nullptr == v[2] || v[0] == v[1] || v[2] != v[3])
  {
    return false;
  }

  if (bTestColinearity)
  {
    if (ON_UNSET_UINT_INDEX == v[2]->EdgeArrayIndex(e[0]) || ON_UNSET_UINT_INDEX == v[2]->EdgeArrayIndex(e[1]))
      bTestColinearity = false;
  }

  // edges must have the same faces
  switch (e[0]->m_face_count)
  {
  case 0:
    break;

  case 1:
    if (ON_SUBD_FACE_POINTER(e[0]->m_face2[0].m_ptr) == ON_SUBD_FACE_POINTER(e[1]->m_face2[0].m_ptr))
      break;

    return false;

  case 2:
    if (ON_SUBD_FACE_POINTER(e[0]->m_face2[0].m_ptr) == ON_SUBD_FACE_POINTER(e[1]->m_face2[0].m_ptr)
      && ON_SUBD_FACE_POINTER(e[0]->m_face2[1].m_ptr) == ON_SUBD_FACE_POINTER(e[1]->m_face2[1].m_ptr))
      break;

    if (ON_SUBD_FACE_POINTER(e[0]->m_face2[0].m_ptr) == ON_SUBD_FACE_POINTER(e[1]->m_face2[1].m_ptr)
      && ON_SUBD_FACE_POINTER(e[0]->m_face2[1].m_ptr) == ON_SUBD_FACE_POINTER(e[1]->m_face2[0].m_ptr))
      break;

    return false;

  default:
    // non-manifold edge
    {
      unsigned int j, k;
      for (j = 0; j < e[0]->m_face_count; j++)
      {
        const ON_SubDFace* f = e[0]->Face(j);
        for (k = 0; k < e[1]->m_face_count; k++)
        {
          if (f == e[1]->Face(k))
            break;
        }
        if (k < e[1]->m_face_count)
          continue;
        break;
      }
      if (j != e[0]->m_face_count)
        return false;
    }
    break;
  }

  if (bTestColinearity)
  {
    const ON_3dPoint* P[3] = { (const ON_3dPoint*)v[0]->m_P, (const ON_3dPoint*)v[2]->m_P, (const ON_3dPoint*)v[1]->m_P };

    ON_3dVector D(*P[2] - *P[0]);
    const double d = D.Length();
    if (!(d > 0.0))
      return false;
    const ON_3dVector V(*P[1] - *P[0]);
    const double t = (V*D) / (d*d);
    if (!(t > ON_EPSILON && t < 1.0 - ON_EPSILON))
      return false;

    const ON_3dPoint M = (1.0 - t)*(*P[0]) + t*(*P[2]);
    const double h = P[1]->DistanceTo(M);

    if (0.0 == h)
      return true;

    if (!(h > 0.0))
      return false;

    const double miniscule_distance_tolerance = ON_ZERO_TOLERANCE;
    if (h <= miniscule_distance_tolerance && !(distance_tolerance >= 0.0 && distance_tolerance < miniscule_distance_tolerance))
    {
      // skip parameter tests for miniscule h.
      return true;
    }

    const double miniscule_maximum_aspect = 1e-4;
    if (h <= miniscule_maximum_aspect * d && !(maximum_aspect >= 0.0 && maximum_aspect < miniscule_maximum_aspect))
    {
      // skip parameter tests for miniscule h/d.
      return true;
    }

    if (distance_tolerance >= 0.0 && !(h <= distance_tolerance))
      return false; // failed distance to chord test

    if (maximum_aspect >= 0.0 && !(h <= maximum_aspect * d))
      return false; // failed maximum aspect test

    if (sin_angle_tolerance >= 0.0 && sin_angle_tolerance < 1.0 && !(ON_CrossProduct(V, (*P[1] - *P[2])).Length() <= sin_angle_tolerance))
      return false; // failed minimum angle test
  }
  return true;
}

bool ON_SubD::EdgesCanBeMerged(
  ON_SubDEdgePtr eptr0,
  ON_SubDEdgePtr eptr1
  )
{
  ON_SubDEdge* e[2] = { ON_SUBD_EDGE_POINTER(eptr0.m_ptr), ON_SUBD_EDGE_POINTER(eptr1.m_ptr) };
  ON__UINT_PTR edir[2] = { ON_SUBD_EDGE_DIRECTION(eptr0.m_ptr), ON_SUBD_EDGE_DIRECTION(eptr1.m_ptr) };
  return EdgesAreMergableTest(e,edir,false,ON_DBL_QNAN,ON_DBL_QNAN,ON_DBL_QNAN);
}

unsigned int ON_SubDimple::MergeColinearEdges(
  double distance_tolerance,
  double maximum_aspect,
  double sin_angle_tolerance
  )
{
  if (1 != m_levels.UnsignedCount())
    return false;

  unsigned int removed_edge_count = 0;

  for (const ON_SubDFace* f = ActiveLevel().m_face[0]; nullptr != f; f = f->m_next_face)
  {
    unsigned int edge_count = f->m_edge_count;
    if (edge_count < 3)
      continue;
    ON_SubDEdge* e[2] = { 0 };
    ON__UINT_PTR edir[2] = { 0 };
    unsigned int i0 = ON_UNSET_UINT_INDEX;
    for (unsigned int i = 0; i <= edge_count; i++)
    {
      e[0] = e[1];
      edir[0] = edir[1];
      ON__UINT_PTR eptr = f->EdgePtr(i%edge_count).m_ptr;
      e[1] = ON_SUBD_EDGE_POINTER(eptr);
      edir[1] = ON_SUBD_EDGE_DIRECTION(eptr);
      if (0 == i)
        continue;

      if (EdgesAreMergableTest(e, edir, true, distance_tolerance, maximum_aspect, sin_angle_tolerance))
      {
        if (ON_UNSET_UINT_INDEX == i0)
        {
          i0 = i;
        }
        if (i < edge_count)
          continue;
      }

      if (ON_UNSET_UINT_INDEX != i0)
      {
        const ON_SubDEdgePtr e0 = f->EdgePtr(i0 - 1);
        while(i0 < i)
        {
          if (e0.m_ptr != MergeEdges(e0, f->EdgePtr(i0)).m_ptr)
          {
            ON_ERROR("Bug in edge merging.");
            break;
          }
          removed_edge_count++;
          i--;
          edge_count--;
        }
        i0 = ON_UNSET_UINT_INDEX;
      }
    }
  }

  return removed_edge_count;
}

unsigned int ON_SubD::MergeColinearEdges(
  double distance_tolerance,
  double maximum_aspect,
  double sin_angle_tolerance
  )
{
  ON_SubDimple* subdimple = SubDimple(false);
  return (nullptr != subdimple) ? subdimple->MergeColinearEdges(distance_tolerance, maximum_aspect, sin_angle_tolerance) : 0;
}

ON_SubD::SubDType ON_SubD::ActiveLevelSubDType() const
{
  return ActiveLevel().m_subdivision_type;
}

unsigned int ON_SubD::LevelCount() const
{
  const ON_SubDimple* subdimple = SubDimple();
  return (0 != subdimple ? subdimple->LevelCount() : 0);
}


unsigned int ON_SubD::ActiveLevelIndex() const
{
  return ActiveLevel().m_level_index;
}

bool ON_SubD::IsEmpty() const
{
  return (nullptr == SubDimple());
}

/////////////////////////////////////////////////////////
//
// Element (Vertex, Edge, Face) access
//
ON_COMPONENT_INDEX ON_SubDComponentPtr::ComponentIndex() const
{
  switch (ComponentType())
  {
  case ON_SubDComponentPtr::Type::Vertex:
    {
      const ON_SubDVertex* vertex = ON_SUBD_VERTEX_POINTER(m_ptr);
      if ( nullptr != vertex)
        return ON_COMPONENT_INDEX(ON_COMPONENT_INDEX::TYPE::subd_vertex,vertex->m_id);
    }
    break;
  case ON_SubDComponentPtr::Type::Edge:
    {
      const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(m_ptr);
      if ( nullptr != edge)
        return ON_COMPONENT_INDEX(ON_COMPONENT_INDEX::TYPE::subd_edge,edge->m_id);
    }
    break;
  case ON_SubDComponentPtr::Type::Face:
    {
      const ON_SubDFace* face = ON_SUBD_FACE_POINTER(m_ptr);
      if ( nullptr != face)
        return ON_COMPONENT_INDEX(ON_COMPONENT_INDEX::TYPE::subd_face,face->m_id);
    }
    break;
  default:
    if (IsNull() )
      return ON_COMPONENT_INDEX::UnsetComponentIndex;
    break;
  }
  return ON_SUBD_RETURN_ERROR(ON_COMPONENT_INDEX::UnsetComponentIndex);
}

ON_SubDComponentPtr ON_SubD::ComponentPtrFromComponentIndex(
  ON_COMPONENT_INDEX component_index
  ) const
{
  if (0 != component_index.m_index && -1 != component_index.m_index)
  {
    switch (component_index.m_type)
    {
    case ON_COMPONENT_INDEX::TYPE::subd_vertex:
      return ON_SubDComponentPtr::Create(VertexFromId(component_index.m_index));
    case ON_COMPONENT_INDEX::TYPE::subd_edge:
      return ON_SubDComponentPtr::Create(EdgeFromId(component_index.m_index));
    case ON_COMPONENT_INDEX::TYPE::subd_face:
      return ON_SubDComponentPtr::Create(FaceFromId(component_index.m_index));
    default:
      break;
    }
  }
  else if (ON_COMPONENT_INDEX::TYPE::invalid_type == component_index.m_type)
  {
    return ON_SubDComponentPtr::Null;
  }

  return ON_SUBD_RETURN_ERROR(ON_SubDComponentPtr::Null);
}

/////////////////////////////////////////////////////////
//
// Vertex access
//

unsigned int ON_SubD::VertexCount() const
{
  return ActiveLevel().m_vertex_count;
}

const ON_SubDVertex* ON_SubD::FirstVertex() const
{
  return ActiveLevel().m_vertex[0];
}

ON_SubDVertexIterator ON_SubD::VertexIterator() const
{
  return ON_SubDVertexIterator(*this);
}

ON_SubDVertexIterator ON_SubDRef::VertexIterator() const
{
  return ON_SubDVertexIterator(*this);
}

ON_SubDVertexArray ON_SubD::VertexArray() const
{
  return ON_SubDVertexArray(*this);
}

const class ON_SubDVertex* ON_SubD::VertexFromId(
  unsigned int vertex_id
  ) const
{
  for (;;)
  {
    if (0 == vertex_id || ON_UNSET_UINT_INDEX == vertex_id )
      break;
    const ON_SubDimple* subdimple = SubDimple();
    if (nullptr == subdimple)
      break;
    return subdimple->VertexFromId(vertex_id);
  }
  return nullptr;
}

ON_COMPONENT_INDEX ON_SubDVertex::ComponentIndex() const
{
  return ON_COMPONENT_INDEX(ON_COMPONENT_INDEX::TYPE::subd_vertex,m_id);
}

ON_SubDComponentPtr ON_SubDVertex::ComponentPtr() const
{
  return ON_SubDComponentPtr::Create(this);
}

/////////////////////////////////////////////////////////
//
// Edge access
//

unsigned int ON_SubD::EdgeCount() const
{
  return ActiveLevel().m_edge_count;
}

const ON_SubDEdge* ON_SubD::FirstEdge() const
{
  return ActiveLevel().m_edge[0];
}

ON_SubDEdgeIterator ON_SubD::EdgeIterator() const
{
  return ON_SubDEdgeIterator(*this);
}

ON_SubDEdgeIterator ON_SubDRef::EdgeIterator() const
{
  return ON_SubDEdgeIterator(*this);
}

ON_SubDEdgeArray ON_SubD::EdgeArray() const
{
  return ON_SubDEdgeArray(*this);
}

const class ON_SubDEdge* ON_SubD::EdgeFromId(
  unsigned int edge_id
  ) const
{
  for (;;)
  {
    if (0 == edge_id || ON_UNSET_UINT_INDEX == edge_id )
      break;
    const ON_SubDimple* subdimple = SubDimple();
    if (nullptr == subdimple)
      break;
    return subdimple->EdgeFromId(edge_id);
  }
  return nullptr;
}

ON_COMPONENT_INDEX ON_SubDEdge::ComponentIndex() const
{
  return ON_COMPONENT_INDEX(ON_COMPONENT_INDEX::TYPE::subd_edge,m_id);
}

ON_SubDComponentPtr ON_SubDEdge::ComponentPtr() const
{
  return ON_SubDComponentPtr::Create(this);
}

/////////////////////////////////////////////////////////
//
// Face access
//

unsigned int ON_SubD::FaceCount() const
{
  return ActiveLevel().m_face_count;
}

const ON_SubDFace* ON_SubD::FirstFace() const
{
  return ActiveLevel().m_face[0];
}

ON_SubDFaceIterator ON_SubD::FaceIterator() const
{
  return ON_SubDFaceIterator(*this);
}

ON_SubDFaceIterator ON_SubDRef::FaceIterator() const
{
  return ON_SubDFaceIterator(*this);
}


ON_SubDFaceArray ON_SubD::FaceArray() const
{
  return ON_SubDFaceArray(*this);
}

const class ON_SubDFace* ON_SubD::FaceFromId(
  unsigned int face_id
  ) const
{
  for (;;)
  {
    if (0 == face_id || ON_UNSET_UINT_INDEX == face_id )
      break;
    const ON_SubDimple* subdimple = SubDimple();
    if (nullptr == subdimple)
      break;
    return subdimple->FaceFromId(face_id);
  }
  return nullptr;
}


ON_COMPONENT_INDEX ON_SubDFace::ComponentIndex() const
{
  return ON_COMPONENT_INDEX(ON_COMPONENT_INDEX::TYPE::subd_face,m_id);
}

ON_SubDComponentPtr ON_SubDFace::ComponentPtr() const
{
  return ON_SubDComponentPtr::Create(this);
}

/////////////////////////////////////////////////////////
//
// ON_SubD properties
//

bool ON_SubD::IsOriented(
  unsigned int level_index
  ) const
{
  for (const ON_SubDEdge* edge = FirstEdge(); nullptr != edge; edge = edge->m_next_edge)
  {
    if ( 2 != edge->m_face_count )
      continue;
    if (nullptr == ON_SUBD_FACE_POINTER(edge->m_face2[0].m_ptr) || nullptr == ON_SUBD_FACE_POINTER(edge->m_face2[1].m_ptr) )
      continue;
    if ( ON_SUBD_FACE_DIRECTION(edge->m_face2[0].m_ptr) == ON_SUBD_FACE_DIRECTION(edge->m_face2[1].m_ptr) )
      return false;
  }
  return true;
}

// reverses the orientation of all facets
bool ON_SubD::ReverseOrientation(
  unsigned int level_index
  ) const
{
  for (const ON_SubDFace* face = FirstFace(); nullptr != face; face = face->m_next_face)
    const_cast<ON_SubDFace*>(face)->ReverseEdgeList();
  return true;
}

// Attempts to orient all facet to match the first facet.

static unsigned int OrientFaceNeighbors(
  unsigned int recursion_level,
  const ON_SubDFace** face_list,
  unsigned int id0,
  const ON_SubDFace* face
  )
{
  ON_SubDFace* next_set[4];
  const unsigned int next_set_capacity = (unsigned int)(sizeof(next_set) / sizeof(next_set[0]));
  unsigned int next_set_count = 0;

  const unsigned int edge_count = face->m_edge_count;
  const ON_SubDEdgePtr* face_eptr = face->m_edge4;
  const ON_SubDEdge* e;
  ON_SubDFace* neighbor_face;

  if (nullptr != face_list[face->m_id - id0])
  {
    // search for an oriented neighbor
    neighbor_face = nullptr;
    for (unsigned int fei = 0; fei < edge_count; fei++, face_eptr++)
    {
      if (4 == fei)
      {
        face_eptr = face->m_edgex;
        if (nullptr == face_eptr)
          break;
      }
      e = ON_SUBD_EDGE_POINTER(face_eptr->m_ptr);
      if (nullptr == e || 2 != e->m_face_count)
        continue;
      neighbor_face = ON_SUBD_FACE_POINTER(e->m_face2[0].m_ptr);
      if (face == neighbor_face)
        neighbor_face = ON_SUBD_FACE_POINTER(e->m_face2[1].m_ptr);
      else if (face != ON_SUBD_FACE_POINTER(e->m_face2[1].m_ptr))
        continue;
      if (nullptr == neighbor_face)
        continue;

      if (nullptr == face_list[neighbor_face->m_id - id0])
        return OrientFaceNeighbors(recursion_level,face_list,id0,neighbor_face);
    }

    // nothing near face is oriented. 
    return 0;
  }

  unsigned int orient_count = 0;
  for (unsigned int fei = 0; fei < edge_count; fei++, face_eptr++)
  {
    if (4 == fei)
    {
      face_eptr = face->m_edgex;
      if ( nullptr == face_eptr)
        break;
    }
    e = ON_SUBD_EDGE_POINTER(face_eptr->m_ptr);
    if (nullptr == e || 2 != e->m_face_count)
      continue;
    neighbor_face = ON_SUBD_FACE_POINTER(e->m_face2[0].m_ptr);
    if (face == neighbor_face)
      neighbor_face = ON_SUBD_FACE_POINTER(e->m_face2[1].m_ptr);
    else if (face != ON_SUBD_FACE_POINTER(e->m_face2[1].m_ptr))
      continue;
    if (nullptr == neighbor_face)
      continue;

    if (nullptr == face_list[neighbor_face->m_id - id0])
      continue;

    if (ON_SUBD_FACE_DIRECTION(e->m_face2[0].m_ptr) == ON_SUBD_FACE_DIRECTION(e->m_face2[1].m_ptr))
      neighbor_face->ReverseEdgeList();
    face_list[neighbor_face->m_id - id0] = nullptr;
    orient_count++;

    if (recursion_level < 12)
    {
      if (next_set_count >= next_set_capacity)
      {
        for (unsigned i = 0; i < next_set_capacity; i++)
          orient_count += OrientFaceNeighbors(recursion_level + 1, face_list, id0, next_set[i]);
        next_set_count = 0;
      }
      next_set[next_set_count++] = neighbor_face;
    }
  }

  for ( unsigned i = 0; i < next_set_count; i++)
    orient_count += OrientFaceNeighbors(recursion_level+1,face_list,id0,next_set[i]);

  return orient_count;
}

bool ON_SubD::Orient(
  unsigned int level_index
  ) const
{
  const ON_SubDFace* first_face = FirstFace();
  if ( nullptr == first_face || nullptr == first_face->m_next_face)
    return true;

  unsigned int nonzero_face_count = 0;
  ON_SimpleArray< const ON_SubDFace* > faces_array(FaceCount());
  unsigned int face_id0 = first_face->m_id;
  unsigned int face_id1 = first_face->m_id;
  for (const ON_SubDFace* face = FirstFace(); nullptr != face; face = face->m_next_face)
  {
    faces_array.Append(face);
    if (face->m_id > face_id1)
      face_id1 = face->m_id;
    else if (face->m_id < face_id1)
      face_id0 = face->m_id;
    nonzero_face_count++;
  }

  const unsigned int face_count = faces_array.UnsignedCount();
  if (face_count <= 1)
    return true;

  const ON_SubDFace** faces = faces_array.Array();
  if (face_id1 - face_id0 > faces_array.UnsignedCount())
  {
    faces_array.Reserve(face_id1 - face_id0);
    faces_array.SetCount(face_id1 - face_id0);
    faces_array.Zero();
    for (const ON_SubDFace* face = FirstFace(); nullptr != face; face = face->m_next_face)
    {
      faces[face->m_id-face_id0] = face;
    }
  }

  unsigned int orient_count = 0;
  unsigned int connected_region_count = 0;
  bool bSearchForNewComponent = true;
  unsigned int first_face_index = 0;

  for (;;)
  {
    unsigned int orient_count0 = orient_count;
    while (first_face_index < face_count && nullptr == faces[first_face_index])
      first_face_index++;

    if ( first_face_index >= face_count)
      break;

    for (unsigned int i = first_face_index; i < face_count && orient_count < nonzero_face_count; i++)
    {
      const ON_SubDFace* face = faces[i];
      if (nullptr == face)
        continue;
      if (bSearchForNewComponent)
      {
        // first face in new connected component
        orient_count++;
        connected_region_count++;
        faces[i] = nullptr;
        bSearchForNewComponent = false;
        first_face_index = i+1;
      }
      orient_count += OrientFaceNeighbors(0, faces, face_id0, face);
    }
    
    if ( orient_count >= nonzero_face_count)
      break;

    if (orient_count0 >= orient_count)
    {
      if (bSearchForNewComponent)
        break;
      bSearchForNewComponent = true;
    }
  }

  return (connected_region_count > 0 && orient_count > 0);
}

const ON_SubDVertex* ON_SubD::TriangulateFace(
  ON_SubDFace* face
  )
{
  // TODO add  implementation
  return ON_SUBD_RETURN_ERROR(nullptr);
}

const ON_SubDFace* ON_SubD::MergeFaces(
  ON_SubDEdge* edge
  )
{
  // TODO add  implementation
  return ON_SUBD_RETURN_ERROR(nullptr);
}

const ON_SubDEdge* ON_SubDimple::SplitEdge(
  ON_SubDEdge* edge,
  ON_3dPoint vertex_location
  )
{
  if ( nullptr == edge )
    return ON_SUBD_RETURN_ERROR(nullptr);
  if (nullptr == edge->m_vertex[0] || nullptr == edge->m_vertex[1] )
    return ON_SUBD_RETURN_ERROR(nullptr);
  if (nullptr == edge->m_vertex[0]->m_edges || edge->m_vertex[0]->m_edge_count <= 0 || edge->m_vertex[0]->m_edge_capacity < edge->m_vertex[0]->m_edge_count )
    return ON_SUBD_RETURN_ERROR(nullptr);
  if (nullptr == edge->m_vertex[1]->m_edges || edge->m_vertex[1]->m_edge_count <= 0 || edge->m_vertex[1]->m_edge_capacity < edge->m_vertex[1]->m_edge_count )
    return ON_SUBD_RETURN_ERROR(nullptr);

  if (ON_3dPoint::UnsetPoint == vertex_location)
  {
    ON_Line L;
    L.from = ON_3dPoint(edge->m_vertex[0]->m_P);
    L.to = ON_3dPoint(edge->m_vertex[1]->m_P);
    vertex_location = L.PointAt(0.5);
  }
  if ( false == vertex_location.IsValid() )
    return ON_SUBD_RETURN_ERROR(nullptr);

  if ( vertex_location == ON_3dPoint(edge->m_vertex[0]->m_P) || vertex_location == ON_3dPoint(edge->m_vertex[1]->m_P) )
    return ON_SUBD_RETURN_ERROR(nullptr);

  ON_SubD::EdgeTag edge_tag = edge->m_edge_tag;
  ON_SubD::VertexTag vertex_tag;
  switch (edge->m_edge_tag)
  {
  case ON_SubD::EdgeTag::Smooth:
    vertex_tag = ON_SubD::VertexTag::Smooth;
    break;
  case ON_SubD::EdgeTag::Crease:
    vertex_tag = ON_SubD::VertexTag::Crease;
    break;
  case ON_SubD::EdgeTag::X:
    vertex_tag = ON_SubD::VertexTag::Smooth;
    edge_tag = ON_SubD::EdgeTag::Smooth;
    break;
  default:
    return ON_SUBD_RETURN_ERROR(nullptr);
    break;
  }

  ON_SubDVertex* end_vertex[2] = { const_cast<ON_SubDVertex*>(edge->m_vertex[0]), const_cast<ON_SubDVertex*>(edge->m_vertex[1]) };

  ON_SubDVertex* new_vertex = nullptr;
  ON_SubDEdge* new_edge = nullptr;  
  for (;;)
  {
    new_vertex = AllocateVertex(vertex_tag, edge->m_level, static_cast<const double*>(vertex_location), 2, edge->m_face_count);
    if (nullptr == new_vertex)
     break;

    new_edge = AllocateEdge(edge_tag, edge->m_level, edge->m_face_count);
    if (nullptr == new_edge)
      break;

    // change end_vertex[1] edge reference from edge to new_edge
    bool bConnectedNewEdgeToEndVertex = false;
    const ON_SubDEdgePtr old_edge_reference = ON_SubDEdgePtr::Create(edge,1);
    for (unsigned short vei = 0; vei < end_vertex[1]->m_edge_count; vei++)
    {
      if (old_edge_reference.m_ptr == end_vertex[1]->m_edges[vei].m_ptr)
      {
        // change end_vertex[1]->m_edges[vei] reference
        // from "edge" to "new_edge".
        bConnectedNewEdgeToEndVertex = true;
        end_vertex[1]->m_edges[vei] = ON_SubDEdgePtr::Create(new_edge,1);
        break;
      }
    }

    if (false == bConnectedNewEdgeToEndVertex)
    {
      // end_vertex[1]->m_edges[] does not reference edge
      break;
    }

    // finish setting new_vertex and end_vertex[] <-> new_edge connections
    new_edge->m_vertex[0] = new_vertex;
    new_edge->m_vertex[1] = end_vertex[1];
    new_vertex->m_edges[new_vertex->m_edge_count++] = ON_SubDEdgePtr::Create(new_edge,0);

    // finish setting new_vertex <-> input edge and connections
    edge->m_edge_tag = edge_tag; // changes "X" to "Smooth" if required
    edge->m_vertex[1] = new_vertex;
    new_vertex->m_edges[new_vertex->m_edge_count++] = ON_SubDEdgePtr::Create(edge,1);
       
    // add new_vertex and new_edge <-> edge->m_face[] connections.
    const ON_SubDFacePtr* fptr0 = edge->m_face2;
    ON_SubDFacePtr* fptr1 = new_edge->m_face2;
    for (unsigned short efi = 0; efi < edge->m_face_count; efi++)
    {
      if (2 == efi)
      {
        fptr0 = edge->m_facex;
        fptr1 = new_edge->m_facex;
      }

      ON_SubDFace* face = ON_SUBD_FACE_POINTER(fptr0->m_ptr);

      if (nullptr != face)
      {
        face->FaceModifiedNofification();

        // add new_vertex reference to face
        new_vertex->m_faces[new_vertex->m_face_count++] = face;

        // insert new_edge into face->m_edge[] list after "edge"
        if (GrowFaceEdgeArray(face, face->m_edge_count + 1))
        {
          if ( face->m_edge_count < 4 )
            face->m_edge4[face->m_edge_count] = ON_SubDEdgePtr::Null;
          else
            face->m_edgex[face->m_edge_count-4] = ON_SubDEdgePtr::Null;
          face->m_edge_count++;
          ON_SubDEdgePtr* face_edge = face->m_edge4;
          for (unsigned short fei = 0; fei < face->m_edge_count; fei++)
          {
            if (4 == fei)
              face_edge = face->m_edgex;
            if (edge == ON_SUBD_EDGE_POINTER(face_edge->m_ptr))
            {
              ON__UINT_PTR edir = ON_SUBD_EDGE_DIRECTION(face_edge->m_ptr);
              ON_SubDEdgePtr eptr = ON_SubDEdgePtr::Create(new_edge,edir);
              if (0 == edir)
              {
                fei++;
                face_edge++;
              }
              for (/*empty init*/; fei < face->m_edge_count; fei++)
              {
                if (4 == fei)
                  face_edge = face->m_edgex;
                const ON_SubDEdgePtr tmp = *face_edge;
                *face_edge = eptr;
                eptr = tmp;
                face_edge++;
              }
              break;
            }
            face_edge++;
          }
        }
      }

      *fptr1++ = *fptr0++;
      new_edge->m_face_count++;
    }

    // original ending vertex
    new_edge->m_sector_coefficient[1] = edge->m_sector_coefficient[1];

    // Either edge was a crease, new_edge is a crease, and sector weights do not applie
    // or edge was X or Smooth, edge is smooth, new_edge is smooth, new_vertex is smooth,
    // and the sector weights at this vertex do not apply.
    edge->m_sector_coefficient[1] = ON_SubDSectorType::IgnoredSectorWeight;
    new_edge->m_sector_coefficient[0] = ON_SubDSectorType::IgnoredSectorWeight;

    AddVertexToLevel(new_vertex);
    AddEdgeToLevel(new_edge);

    end_vertex[0]->VertexModifiedNofification();
    end_vertex[1]->VertexModifiedNofification();

    // TODO
    //   Delete any levels greater than this level.
    return new_edge;
  }

  if ( nullptr != new_vertex)
    ReturnVertex(new_vertex);
  if ( nullptr != new_edge)
    ReturnEdge(new_edge);
  return ON_SUBD_RETURN_ERROR(nullptr);
}

const ON_SubDEdge* ON_SubDimple::SplitFace(
  ON_SubDFace* face,
  unsigned int fvi0,
  unsigned int fvi1
  )
{
  if ( nullptr == face || face->m_edge_count < 4)
    return ON_SUBD_RETURN_ERROR(nullptr);

  if (face->m_level >= m_levels.UnsignedCount())
    return ON_SUBD_RETURN_ERROR(nullptr);
  ON_SubDLevel* level = m_levels[face->m_level];
  if ( nullptr == level)
    return ON_SUBD_RETURN_ERROR(nullptr);

  const unsigned int edge_count = face->m_edge_count;
  if ( edge_count < 4 || fvi0 == fvi1 || fvi0 >= edge_count || fvi1 >= edge_count || (edge_count > 4 && nullptr == face->m_edgex) )
    return ON_SUBD_RETURN_ERROR(nullptr);

  ON_SubDVertex* v[2] = { const_cast<ON_SubDVertex*>(face->Vertex(fvi0)), const_cast<ON_SubDVertex*>(face->Vertex(fvi1)) };

  if (nullptr == v[0] || v[0]->m_face_count <= 0 || nullptr == v[0]->m_faces || v[0]->m_edge_count < 2 || nullptr == v[0]->m_edges )
    return ON_SUBD_RETURN_ERROR(nullptr);

  if (nullptr == v[1] || v[1]->m_face_count <= 0 || nullptr == v[1]->m_faces || v[1]->m_edge_count < 2 || nullptr == v[1]->m_edges)
    return ON_SUBD_RETURN_ERROR(nullptr);

  if (v[0] == v[1])
    return ON_SUBD_RETURN_ERROR(nullptr);

  // make sure each side is at least a triangle
  const bool bReverseEdge = (fvi0 > fvi1);
  if ( bReverseEdge )
  {
    unsigned int i = fvi0;
    fvi0 = fvi1;
    fvi1 = i;
  }

  // edge_count0 = number of edges remaining on first face
  const unsigned int edge_count0 = (0 == fvi0) ? (fvi1+1) : (edge_count + 1 + fvi0 - fvi1);
  if ( edge_count0 < 3 || edge_count0 >= edge_count)
    return ON_SUBD_RETURN_ERROR(nullptr);

  // edge_count1 = number of edges on new face
  const unsigned int edge_count1 = edge_count + 2 - edge_count0;
  if ( edge_count1 < 3 || edge_count1 >= edge_count)
    return ON_SUBD_RETURN_ERROR(nullptr);


  // make sure face topology is valid
  ON_SubDEdgePtr* face_eptr = face->m_edge4;
  for (unsigned int fei = 0; fei < edge_count; fei++, face_eptr++)
  {
    if (4 == fei)
      face_eptr = face->m_edgex;
    const ON_SubDEdge* face_e = ON_SUBD_EDGE_POINTER(face_eptr->m_ptr);
    if (nullptr == face_e)
      return ON_SUBD_RETURN_ERROR(nullptr);
    if (ON_UNSET_UINT_INDEX == face_e->FaceArrayIndex(face) )
      return ON_SUBD_RETURN_ERROR(nullptr);
    const ON_SubDVertex* face_v = face_e->m_vertex[ON_SUBD_EDGE_DIRECTION(face_eptr->m_ptr)];
    if ( nullptr == face_v)
      return ON_SUBD_RETURN_ERROR(nullptr);
    if (ON_UNSET_UINT_INDEX == face_v->FaceArrayIndex(face) )
      return ON_SUBD_RETURN_ERROR(nullptr);
  }

  // create diagonal edge
  ON_SubD::EdgeTag etag = ON_SubD::EdgeTag::Unset;
  const bool bSmoothVertex[2] = {ON_SubD::VertexTag::Smooth == v[0]->m_vertex_tag,ON_SubD::VertexTag::Smooth == v[1]->m_vertex_tag};
  double sector_weight[2] = {
    bSmoothVertex[0] ? ON_SubDSectorType::IgnoredSectorWeight : ON_SubDSectorType::UnsetSectorWeight,
    bSmoothVertex[1] ? ON_SubDSectorType::IgnoredSectorWeight : ON_SubDSectorType::UnsetSectorWeight
  };

  if (bSmoothVertex[0] || bSmoothVertex[1])
    etag = ON_SubD::EdgeTag::Smooth;
  else
    etag = ON_SubD::EdgeTag::X;

  ON_SubDEdge* e = nullptr;
  ON_SubDFace* f = nullptr;
  for (;;)
  {
    f = AllocateFace();
    if (nullptr == f)
      break;
    f->m_level = face->m_level;
    AddFaceToLevel(f);

    if (edge_count1 > 4)
    {
      if ( false == m_heap.GrowFaceEdgeArray(f,edge_count1) )
        break;
    }

    if (false == m_heap.GrowVertexFaceArrayByOne(v[0]))
      break;
    if (false == m_heap.GrowVertexFaceArrayByOne(v[1]))
      break;

    e = AddEdge(etag, v[0], sector_weight[0], v[1], sector_weight[1]);
    if (nullptr == e)
      return ON_SUBD_RETURN_ERROR(nullptr);

    unsigned int fvi_limits[2];
    if (0 == fvi0)
    {
      fvi_limits[0] = fvi1;
      fvi_limits[1] = edge_count;
    }
    else
    {
      fvi_limits[0] = fvi0;
      fvi_limits[1] = fvi1;
    }

    face_eptr = (fvi_limits[0] < 4) ? (face->m_edge4 + fvi_limits[0]) : (face->m_edgex + (fvi_limits[0]-4));

    ON_SubDEdgePtr* face1_eptr = f->m_edge4;
    *face1_eptr++ = ON_SubDEdgePtr::Create(e,bReverseEdge?0:1);
    f->m_edge_count++;

    for (unsigned fvi = fvi_limits[0]; fvi < fvi_limits[1]; fvi++)
    {
      if ( 4 == fvi)
        face_eptr = face->m_edgex;

      if ( 4 == f->m_edge_count)
        face1_eptr = f->m_edgex;

      // topology validation above checked that face_e is not null
      ON_SubDEdge* face_e = ON_SUBD_EDGE_POINTER(face_eptr->m_ptr);
      ON__UINT_PTR face_edir = ON_SUBD_EDGE_DIRECTION(face_eptr->m_ptr);
      // topology validation above checked that face_edex is valid.
      unsigned int face_edex = face_e->FaceArrayIndex(face);

      // topology validation above checked that face_v is not null
      ON_SubDVertex* face_v = const_cast<ON_SubDVertex*>(face_e->m_vertex[face_edir]);

      if (v[0] != face_v && v[1] != face_v)
      {
        // topology validation above checked that face_vdex is valid.
        unsigned int face_vdex = face_v->FaceArrayIndex(face);

        // change face_v reference from "face" to "f"
        face_v->m_faces[face_vdex] = f;
      }

      // change face_e reference from "face" to "f"
      ON_SubDFacePtr* e_fptr = (face_edex < 2) ? (face_e->m_face2 + face_edex) : (face_e->m_facex + (face_edex-2));
      *e_fptr = ON_SubDFacePtr::Create(f,face_edir);

      // add edge to new face "f"
      *face1_eptr++ = ON_SubDEdgePtr::Create(face_e,face_edir);
      f->m_edge_count++;

      // remove edge from original face "face"
      *face_eptr++ = ON_SubDEdgePtr::Null;
    }

    if ( edge_count1 != f->m_edge_count )
      break;

    if (0 == fvi0)
    {
      face->m_edge_count = (unsigned short)(edge_count0-1);
      face_eptr = (face->m_edge_count < 4) ? (face->m_edge4 + face->m_edge_count) : (face->m_edgex + (face->m_edge_count-4));
      *face_eptr = ON_SubDEdgePtr::Create(e,bReverseEdge?1:0);
      face->m_edge_count++;
    }
    else
    {
      face->m_edge_count = (unsigned short)fvi0;
      face1_eptr = (face->m_edge_count < 4) ? (face->m_edge4 + face->m_edge_count) : (face->m_edgex + (face->m_edge_count-4));
      *face1_eptr++ = ON_SubDEdgePtr::Create(e,bReverseEdge?1:0);
      face->m_edge_count++;
      face_eptr = (fvi1 < 4) ? (face->m_edge4 + fvi1) : (face->m_edgex + (fvi1-4));
      for (unsigned int fvi = fvi1; fvi < edge_count; fvi++)
      {
        if (4 == fvi)
          face_eptr = face->m_edgex;
        if ( 4 == face->m_edge_count)
          face1_eptr = face->m_edgex;
        *face1_eptr++ = *face_eptr++;
        face->m_edge_count++;
      }
    }
    if ( edge_count0 != face->m_edge_count )
      break;

    face_eptr = (face->m_edge_count < 4) ? (face->m_edge4 + face->m_edge_count) : (face->m_edgex + (face->m_edge_count-4));
    for (unsigned int fvi = face->m_edge_count; fvi < edge_count; fvi++)
    {
      if ( 4 == fvi )
        face_eptr = face->m_edgex;
      *face_eptr++ = ON_SubDEdgePtr::Null;
    }

    e->m_face2[0] = ON_SubDFacePtr::Create(face,bReverseEdge?1:0);
    e->m_face2[1] = ON_SubDFacePtr::Create(f,bReverseEdge?0:1);
    e->m_face_count = 2;

    v[0]->m_faces[v[0]->m_face_count++] = f;
    //v[0]->m_vertex_edge_order = ON_SubD::VertexEdgeOrder::unset;
    v[1]->m_faces[v[1]->m_face_count++] = f;
    //v[1]->m_vertex_edge_order = ON_SubD::VertexEdgeOrder::unset;

    if (face->m_edge_count <= 4 && nullptr != face->m_edgex)
      m_heap.ReturnFaceExtraArray(face);

    if ( false == bSmoothVertex[0] || false == bSmoothVertex[1])
    {
      // update sector weights because they depend on the number of edges
      ON_SubD::SubDType subd_type = level->m_subdivision_type;
      if (ON_SubD::IsQuadOrTriSubDType(subd_type))
      {
        for (unsigned int vi = 0; vi < 2; vi++)
        {
          if ( bSmoothVertex[vi] )
            continue;
          ON_SubDSectorIterator sit;
          sit.Initialize(face, 0, v[vi]);
          sit.IncrementToCrease(-1);
          sit.InitializeToCurrentFace();
          if (v[vi]->IsCreaseOrCorner() || ON_SubD::VertexTag::Dart == v[vi]->m_vertex_tag)
          {
            const ON_SubDSectorType sector_type = ON_SubDSectorType::Create(subd_type, sit);
            sector_weight[vi] = sector_type.SectorWeight();
          }
          for (;;)
          {
            const ON_SubDEdge* edge = sit.CurrentEdge(0);
            if (nullptr == edge)
              break;
            unsigned int evi;
            if (v[vi] == edge->m_vertex[0])
              evi = 0;
            else if (v[vi] == edge->m_vertex[1])
              evi = 1;
            else
              evi = 2;
            if (evi < 2)
              const_cast<ON_SubDEdge*>(edge)->m_sector_coefficient[vi] = sector_weight[vi];
            if (nullptr == sit.CurrentFace() || sit.InitialFace() == sit.NextFace(true))
              break;
          }
        }
      }
    }

    return e;
  }

  if ( nullptr != f  )
   ReturnFace(f);

  if (nullptr != e)
  {
    v[0]->m_edge_count--;
    v[1]->m_edge_count--;
    ReturnEdge(e);
  }

  return ON_SUBD_RETURN_ERROR(nullptr);
}



const ON_SubDEdge* ON_SubD::SplitEdge(
  ON_SubDEdge* edge,
  ON_3dPoint vertex_location
  )
{
  ON_SubDimple* subdimple = SubDimple(false);
  if ( nullptr == subdimple )
    return ON_SUBD_RETURN_ERROR(nullptr);
  return subdimple->SplitEdge(edge,vertex_location);
}


const ON_SubDEdge* ON_SubD::SplitFace(
  ON_SubDFace* face,
  unsigned int fvi0,
  unsigned int fvi1
  )
{
  ON_SubDimple* subdimple = SubDimple(false);
  if ( 0 == subdimple )
    return ON_SUBD_RETURN_ERROR(nullptr);
  return subdimple->SplitFace(face,fvi0,fvi1);
}

static unsigned int OppositeCornerIndex(
  const ON_SubDFace* face,
  unsigned int fvi0
  )
{
  if ( nullptr == face)
    return ON_SUBD_RETURN_ERROR(ON_UNSET_UINT_INDEX);

  const unsigned int edge_count = face->m_edge_count;

  if ( edge_count < 3 )
    return ON_SUBD_RETURN_ERROR(ON_UNSET_UINT_INDEX);
    
  if ( edge_count > 4 && nullptr == face->m_edgex )
    return ON_SUBD_RETURN_ERROR(ON_UNSET_UINT_INDEX);

  if ( 3 == face->m_edge_count )
    return ON_UNSET_UINT_INDEX; // not an error

  const ON_SubDVertex* face_v = face->Vertex(fvi0);
  if (nullptr == face_v)
    return ON_SUBD_RETURN_ERROR(ON_UNSET_UINT_INDEX);

  const ON_3dPoint P0(face_v->m_P);

  const ON_SubDVertex* best_v = nullptr;
  unsigned int best_fvi = ON_UNSET_UINT_INDEX;
  double best_d = 0.0;

  const unsigned int i0 = (fvi0 + 2) % edge_count;
  const unsigned int i1 = (fvi0 + edge_count - 1) % edge_count;
  const ON_SubDEdgePtr* eptr = i0 < 4 ? (face->m_edge4 + i0) : (face->m_edgex + (i0-4));
  for (unsigned int i = i0; i != i1; i = (i + 1) % edge_count, eptr++)
  {
    if ( i == 0 )
      eptr = face->m_edge4;
    else if ( i == 4 )
      eptr = face->m_edgex;
    const ON_SubDEdge* face_e = ON_SUBD_EDGE_POINTER(eptr->m_ptr);
    if ( nullptr == face_e )
      return ON_SUBD_RETURN_ERROR(ON_UNSET_UINT_INDEX);
    ON__UINT_PTR face_edir = ON_SUBD_EDGE_DIRECTION(eptr->m_ptr);
    face_v = face_e->m_vertex[face_edir];
    if ( nullptr == face_v )
      return ON_SUBD_RETURN_ERROR(ON_UNSET_UINT_INDEX);
    const ON_3dPoint P1(face_v->m_P);
    double d = P0.DistanceTo(P1);
    if (nullptr == best_v || (face_v->IsSmoothOrDart() && best_v->IsCreaseOrCorner()) )
    {
      best_v = face_v;
      best_d = d;
      best_fvi = i;
      continue;
    }   

    if (d > best_d && (face_v->IsSmoothOrDart() || best_v->IsCreaseOrCorner()))
    {
      best_v = face_v;
      best_d = d;
      best_fvi = i;
      continue;
    }
  }

  if ( best_fvi < edge_count )
    return best_fvi;

  return ON_SUBD_RETURN_ERROR(ON_UNSET_UINT_INDEX);
}



bool ON_SubD::RepairInvalidSectors(
  unsigned int level_index
  )
{
  bool rc = true;
  for (const ON_SubDVertex* vertex = FirstVertex(); nullptr != vertex; vertex = vertex->m_next_vertex)
  {
    if (vertex->IsSmoothOrDart()
      && 2 == vertex->m_edge_count && 2 == vertex->m_face_count
      && nullptr != vertex->m_edges && nullptr != vertex->m_faces
      )
    {
      const unsigned int fvi0[2] = {
        nullptr == vertex->m_faces[0] ? ON_UNSET_UINT_INDEX : vertex->m_faces[0]->VertexIndex(vertex),
        nullptr == vertex->m_faces[1] ? ON_UNSET_UINT_INDEX : vertex->m_faces[1]->VertexIndex(vertex)};
      const unsigned int fvi1[2] = {
        OppositeCornerIndex(vertex->m_faces[0],fvi0[0]),
        OppositeCornerIndex(vertex->m_faces[1],fvi0[1])
      };
      // split adjacent faces
      for (unsigned int pass = 0; pass < 2; pass++)
      {
        if ( 2 != vertex->m_edge_count || 2 != vertex->m_face_count)
          break;
        for (unsigned int vfi = 0; vfi < 2; vfi++)
        {
          if (ON_UNSET_UINT_INDEX == fvi0[vfi] || ON_UNSET_UINT_INDEX == fvi1[vfi])
            continue;
          const ON_SubDFace* face = vertex->m_faces[vfi];
          if (nullptr == face)
            continue;
          const ON_SubDVertex* face_v = face->Vertex(fvi1[vfi]);
          if (nullptr == face_v)
            continue;
          // first pass splits corner vertices.
          // If no corners are found during the first pass, then
          // second pass splits any neighbor quad.
          if (0 == pass && face_v->IsCreaseOrCorner())
            continue;
          SplitFace(const_cast<ON_SubDFace*>(face), fvi0[vfi], fvi1[vfi]);
        }
      }
      if (2 == vertex->m_edge_count && 2 == vertex->m_face_count)
      {
        TriangulateFace(const_cast<ON_SubDFace*>(vertex->m_faces[0]));
        TriangulateFace(const_cast<ON_SubDFace*>(vertex->m_faces[1]));
        if (2 == vertex->m_edge_count && 2 == vertex->m_face_count)
        {
          // cannot fix this vertex
          ON_SubDIncrementErrorCount();
          rc = false;
        }
      }
    }
  }

  return rc;
}

void ON_SubD::MarkAggregateComponentStatusAsNotCurrent() const
{
  const ON_SubDLevel* level = ActiveLevelConstPointer();
  if ( level )
    level->MarkAggregateComponentStatusAsNotCurrent();
}

unsigned int ON_SubDLevel::ClearStates(
  ON_ComponentStatus states_to_clear
  ) const
{
  unsigned int rc = 0;
  m_aggregates.m_aggregate_status.ClearAggregateStatus(states_to_clear);
  for (const ON_SubDVertex* vertex = m_vertex[0]; nullptr != vertex; vertex = vertex->m_next_vertex)
    rc += vertex->m_status.ClearStates(states_to_clear);
  for (const ON_SubDEdge* edge = m_edge[0]; nullptr != edge; edge = edge->m_next_edge)
    rc += edge->m_status.ClearStates(states_to_clear);
  for (const ON_SubDFace* face = m_face[0]; nullptr != face; face = face->m_next_face)
    rc += face->m_status.ClearStates(states_to_clear);
  return rc;
}

unsigned int ON_SubD::ClearComponentStates(
  ON_ComponentStatus states_to_clear
  ) const
{
  const ON_SubDLevel* level = ActiveLevelConstPointer();
  if ( level )
    return level->ClearStates(states_to_clear);
  return ON_SUBD_RETURN_ERROR(0);
}

ON_AggregateComponentStatus ON_SubD::AggregateComponentStatus() const
{  
  return ActiveLevel().AggregateComponentStatus();
}

unsigned int ON_SubDLevel::GetComponentsWithSetStates(
  ON_ComponentStatus states_filter,
  bool bAllEqualStates,
  ON_SimpleArray< ON_SubDComponentPtr >& components_with_set_states
  ) const
{
  components_with_set_states.SetCount(0);
  if (states_filter.IsClear())
    return 0;

  ON_AggregateComponentStatus acs = AggregateComponentStatus();

  ON_ComponentStatus as = acs.AggregateStatus();
  if (bAllEqualStates)
  {
    if ( false == as.AllEqualStates(states_filter, states_filter) )
      return 0;
  }
  else
  {
    if ( false == as.SomeEqualStates(states_filter, states_filter) )
      return 0;
  }

  unsigned int c = 0;
  if ( states_filter.IsSelected() && c < m_aggregates.m_aggregate_status.SelectedCount() )
    c = m_aggregates.m_aggregate_status.SelectedCount();
  if ( states_filter.IsHighlighted() && c < m_aggregates.m_aggregate_status.HighlightedCount() )
    c = m_aggregates.m_aggregate_status.HighlightedCount();
  if ( states_filter.IsHidden() && c < m_aggregates.m_aggregate_status.HiddenCount() )
    c = m_aggregates.m_aggregate_status.HiddenCount();
  if ( states_filter.IsLocked() && c < m_aggregates.m_aggregate_status.LockedCount() )
    c = m_aggregates.m_aggregate_status.LockedCount();
  if ( states_filter.IsDamaged() && c < m_aggregates.m_aggregate_status.DamagedCount() )
    c = m_aggregates.m_aggregate_status.DamagedCount();
  if ( states_filter.IsSelected() && c < m_aggregates.m_aggregate_status.SelectedCount() )
    c = m_aggregates.m_aggregate_status.SelectedCount();
  components_with_set_states.Reserve(c);

  if (bAllEqualStates)
  {
    for (const ON_SubDVertex* vertex = m_vertex[0]; nullptr != vertex; vertex = vertex->m_next_vertex)
    {
      if (vertex->m_status.AllEqualStates(states_filter, states_filter))
        components_with_set_states.Append(ON_SubDComponentPtr::Create(vertex));
    }
    for (const ON_SubDEdge* edge = m_edge[0]; nullptr != edge; edge = edge->m_next_edge)
    {
      if (edge->m_status.AllEqualStates(states_filter, states_filter))
        components_with_set_states.Append(ON_SubDComponentPtr::Create(edge));
    }
    for (const ON_SubDFace* face = m_face[0]; nullptr != face; face = face->m_next_face)
    {
      if (face->m_status.AllEqualStates(states_filter, states_filter))
        components_with_set_states.Append(ON_SubDComponentPtr::Create(face));
    }
  }
  else
  {
    for (const ON_SubDVertex* vertex = m_vertex[0]; nullptr != vertex; vertex = vertex->m_next_vertex)
    {
      if (vertex->m_status.SomeEqualStates(states_filter, states_filter))
        components_with_set_states.Append(ON_SubDComponentPtr::Create(vertex));
    }
    for (const ON_SubDEdge* edge = m_edge[0]; nullptr != edge; edge = edge->m_next_edge)
    {
      if (edge->m_status.SomeEqualStates(states_filter, states_filter))
        components_with_set_states.Append(ON_SubDComponentPtr::Create(edge));
    }
    for (const ON_SubDFace* face = m_face[0]; nullptr != face; face = face->m_next_face)
    {
      if (face->m_status.SomeEqualStates(states_filter, states_filter))
        components_with_set_states.Append(ON_SubDComponentPtr::Create(face));
    }
  }

  return components_with_set_states.UnsignedCount();
}


unsigned int ON_SubD::GetComponentsWithSetStates(
  ON_ComponentStatus states_filter,
  bool bAllEqualStates,
  ON_SimpleArray< ON_SubDComponentPtr >& components_with_set_states
  ) const
{
  return ActiveLevel().GetComponentsWithSetStates(
    states_filter,
    bAllEqualStates,
    components_with_set_states
    );
}


unsigned int ON_SubD::GetComponentsWithSetStates(
  ON_ComponentStatus states_filter,
  bool bAllEqualStates,
  ON_SimpleArray< ON_COMPONENT_INDEX >& components_with_set_states
  ) const
{
  components_with_set_states.SetCount(0);
  ON_SimpleArray< ON_SubDComponentPtr > cptr;
  GetComponentsWithSetStates(
    states_filter,
    bAllEqualStates,
    cptr
    );
  unsigned int count = cptr.UnsignedCount();
  if (count > 0)
  {
    components_with_set_states.Reserve(count);
    components_with_set_states.SetCount(count);
    const ON_SubDComponentPtr* cp = cptr.Array();
    ON_COMPONENT_INDEX* ci = components_with_set_states.Array();
    for ( const ON_SubDComponentPtr* cp1 = cp+count; cp < cp1; cp++ )
      *ci++ = cp->ComponentIndex();
  }
  return count;
}

unsigned int ON_SubDLevel::SetStates(
  ON_SubDComponentPtr component_ptr,
  ON_ComponentStatus states_to_set
  ) const
{
  if (0 != component_ptr.SetStates(states_to_set))
  {
    m_aggregates.m_aggregate_status.MarkAsNotCurrent();
    return 1;
  }
  return 0;
}

unsigned int ON_SubDLevel::ClearStates(
  ON_SubDComponentPtr component_ptr,
  ON_ComponentStatus states_to_clear
  ) const
{
  if (0 != component_ptr.ClearStates(states_to_clear))
  {
    m_aggregates.m_aggregate_status.MarkAsNotCurrent();
    return 1;
  }
  return 0;
}

unsigned int ON_SubDLevel::SetStatus(
  ON_SubDComponentPtr component_ptr,
  ON_ComponentStatus status_to_copy
  ) const
{
  if (0 != component_ptr.SetStatus(status_to_copy))
  {
    m_aggregates.m_aggregate_status.MarkAsNotCurrent();
    return 1;
  }
  return 0;
}

unsigned int ON_SubD::SetComponentStates(
  ON_COMPONENT_INDEX component_index,
  ON_ComponentStatus states_to_set
  ) const
{
  return SetComponentStates(ComponentPtrFromComponentIndex(component_index),states_to_set);
}

unsigned int ON_SubD::SetComponentStates(
  ON_SubDComponentPtr component_ptr,
  ON_ComponentStatus states_to_set
  ) const
{
  const ON_SubDLevel* level = ActiveLevelConstPointer();
  if ( nullptr != level )
    return level->SetStates(component_ptr,states_to_set);
  return ON_SUBD_RETURN_ERROR(0);
}

unsigned int ON_SubD::ClearComponentStates(
  ON_COMPONENT_INDEX component_index,
  ON_ComponentStatus states_to_clear
  ) const
{
  return ClearComponentStates(ComponentPtrFromComponentIndex(component_index),states_to_clear);
}

unsigned int ON_SubD::ClearComponentStates(
  ON_SubDComponentPtr component_ptr,
  ON_ComponentStatus states_to_clear
  ) const
{
  const ON_SubDLevel* level = ActiveLevelConstPointer();
  if ( nullptr != level )
    return level->ClearStates(component_ptr,states_to_clear);
  return ON_SUBD_RETURN_ERROR(0);
}

unsigned int ON_SubD::SetComponentStatus(
  ON_COMPONENT_INDEX component_index,
  ON_ComponentStatus status_to_copy
  ) const
{
  return ClearComponentStates(ComponentPtrFromComponentIndex(component_index),status_to_copy);
}

unsigned int ON_SubD::SetComponentStatus(
  ON_SubDComponentPtr component_ptr,
  ON_ComponentStatus status_to_copy
  ) const
{
  const ON_SubDLevel* level = ActiveLevelConstPointer();
  if ( nullptr != level )
    return level->SetStatus(component_ptr,status_to_copy);
  return ON_SUBD_RETURN_ERROR(0);
}

void ON_SubDLevel::ClearSubdivisonAndLimitPoints() const
{
  for (const ON_SubDVertex* vertex = m_vertex[0]; nullptr != vertex; vertex = vertex->m_next_vertex)
  {
    vertex->ClearSavedSubdivisionPoint();
    vertex->ClearSavedLimitPoints();
  }
  for (const ON_SubDEdge* edge = m_edge[0]; nullptr != edge; edge = edge->m_next_edge)
  {
    edge->ClearSavedSubdivisionPoint();
  }
  for (const ON_SubDFace* face = m_face[0]; nullptr != face; face = face->m_next_face)
  {
    face->ClearSavedSubdivisionPoint();
  }
  m_aggregates.m_bDirtyBoundingBox = true;
}

unsigned int ON_SubD::ComponentPtrFromComponentIndex(
  const ON_COMPONENT_INDEX* ci_list,
  size_t ci_count,
  bool bIncludeVertices,
  bool bIncludeEdges,
  bool bIncludeFaces,
  ON_SimpleArray<ON_SubDComponentPtr>& cptr_list
) const
{
  if ( ci_count <= 0 )
    return 0;

  if (
    false == bIncludeVertices
    && false == bIncludeEdges
    && false == bIncludeFaces
    )
    return 0;

  if ( nullptr == ci_list )
    return ON_SUBD_RETURN_ERROR(0);

  const unsigned int count0 = cptr_list.UnsignedCount();

  cptr_list.Reserve(count0 + ci_count);

  const bool bFilter
    = false == bIncludeVertices
    || false == bIncludeEdges
    || false == bIncludeFaces;



  for (size_t i = 0; i < ci_count; i++)
  {
    const ON_COMPONENT_INDEX ci = ci_list[i];
    if (bFilter)
    {
      if (false == bIncludeVertices || ON_COMPONENT_INDEX::TYPE::subd_vertex == ci.m_type)
        continue;
      if (false == bIncludeEdges || ON_COMPONENT_INDEX::TYPE::subd_edge == ci.m_type)
        continue;
      if (false == bIncludeFaces || ON_COMPONENT_INDEX::TYPE::subd_face == ci.m_type)
        continue;
    }
    ON_SubDComponentPtr cptr = ComponentPtrFromComponentIndex(ci_list[i]);
    if (cptr.IsNull())
      continue;
    cptr_list.Append(cptr);
  }

  return (cptr_list.UnsignedCount() - count0);
}

unsigned int ON_SubD::ComponentPtrFromComponentIndex(
  const ON_COMPONENT_INDEX* ci_list,
  size_t ci_count,
  ON_SimpleArray<ON_SubDComponentPtr>& cptr_list
) const
{
  return ComponentPtrFromComponentIndex(ci_list, ci_count, true, true, true, cptr_list);
}



bool ON_SubD::DeleteComponents(
  const ON_COMPONENT_INDEX* ci_list,
  size_t ci_count
  )
{
  ON_SimpleArray<ON_SubDComponentPtr> cptr_list;
  if (ComponentPtrFromComponentIndex(ci_list,ci_count,cptr_list) <= 0)
    return true; // nothing to delete

  return DeleteComponents(cptr_list.Array(),cptr_list.UnsignedCount());
}

bool ON_SubD::DeleteComponents(
  const ON_SubDComponentPtr* cptr_list,
  size_t cptr_count
  )
{
  if ( cptr_count <= 0 )
    return true;

  if ( nullptr == cptr_list )
    return ON_SUBD_RETURN_ERROR(false);

  ON_SubDimple* subdimple = SubDimple(false);
  if ( nullptr == subdimple )
    return ON_SUBD_RETURN_ERROR(false);

  const unsigned int level_count = subdimple->LevelCount();
  if (level_count <= 0)
    return ON_SUBD_RETURN_ERROR(false);
  
  unsigned int level_index = level_count;
  for (size_t i = 0; i < cptr_count; i++)
  {
    const ON_SubDComponentBase* c = cptr_list[i].ComponentBase();
    if ( nullptr == c)
      continue;
    if ( c->m_level < level_index )
      level_index = c->m_level;
  }
  if ( level_index == level_count )
    return ON_SUBD_RETURN_ERROR(false);

  if ( false == subdimple->SetActiveLevel(level_index) )
    return ON_SUBD_RETURN_ERROR(false);

  subdimple->ClearHigherSubdivisionLevels(level_index);
  const ON_SubDLevel* level = subdimple->ActiveLevelPointer();
  if ( nullptr == level || level->m_level_index != level_index)
    return ON_SUBD_RETURN_ERROR(false);

  // Make sure no components have a status = ON_ComponentStatus::AllSet
  // because this uncommon status value is used to mark components that will be be deleted.
  ON_SubDComponentIterator cit(*this);
  for (ON_SubDComponentPtr cptr = cit.FirstComponent(); cptr.IsNotNull(); cptr = cit.NextComponent())
  {
    if ( ON_ComponentStatus::AllSet == cptr.Status() )
      cptr.ClearStates(ON_ComponentStatus::Damaged);
  }

  // Set the status of every compoent in cptr_list[] to ON_ComponentStatus::AllSet.
  // If that component is a vertex, set the status of every edge and face that
  // touch the vertex to ON_ComponentStatus::AllSet.
  // If that component is an edge, set the status of every face that
  // touches the edge to ON_ComponentStatus::AllSet.
  for (size_t i = 0; i < cptr_count; i++)
  {
    ON_SubDComponentPtr cptr = cptr_list[i];
    const ON_SubDComponentBase* c = cptr.ComponentBase();
    if (nullptr == c)
      continue;
    if (c->m_level != level_index)
      continue;
    c->m_status = ON_ComponentStatus::AllSet;
    switch (cptr.ComponentType())
    {
    case ON_SubDComponentPtr::Type::Vertex:
    {
      const ON_SubDVertex* vertex = cptr.Vertex();
      if (nullptr == vertex)
        continue;
      for (unsigned short vei = 0; vei < vertex->m_edge_count; vei++)
      {
        const ON_SubDEdge* edge = vertex->Edge(vei);
        if (nullptr == edge)
          continue;
        edge->m_status = ON_ComponentStatus::AllSet;
      }
      for (unsigned short vfi = 0; vfi < vertex->m_face_count; vfi++)
      {
        const ON_SubDFace* face = vertex->Face(vfi);
        if (nullptr == face)
          continue;
        face->m_status = ON_ComponentStatus::AllSet;
      }
    }
    break;
    case ON_SubDComponentPtr::Type::Edge:
    {
      const ON_SubDEdge* edge = cptr.Edge();
      if (nullptr == edge)
        continue;
      for (unsigned short efi = 0; efi < edge->m_face_count; efi++)
      {
        const ON_SubDFace* face = edge->Face(efi);
        if (nullptr == face)
          continue;
        face->m_status = ON_ComponentStatus::AllSet;
      }
    }
    break;
    }
  }

  // Minimum count of what will be deleted. (
  unsigned int deleted_vertex_count = 0;
  unsigned int deleted_edge_count = 0;
  unsigned int deleted_face_count = 0;
  for (ON_SubDComponentPtr cptr = cit.FirstComponent(); cptr.IsNotNull(); cptr = cit.NextComponent())
  {
    if (ON_ComponentStatus::AllSet == cptr.Status())
    {
      switch (cptr.ComponentType())
      {
      case ON_SubDComponentPtr::Type::Vertex:
        deleted_vertex_count++;
        break;

      case ON_SubDComponentPtr::Type::Edge:
        deleted_edge_count++;
        break;

      case ON_SubDComponentPtr::Type::Face:
        deleted_face_count++;
        break;
      }
      continue;
    }
  }

  if ( 0 == deleted_vertex_count && 0 == deleted_edge_count && 0 ==  deleted_face_count )
    return false;

  if (deleted_vertex_count >= level->m_vertex_count || deleted_edge_count >= level->m_edge_count || deleted_face_count >= level->m_face_count)
  {
    Destroy();
    return true;
  }

  unsigned int deleted_component_count = subdimple->DeleteComponents(level_index);

  if (0 == subdimple->LevelCount())
  {
    Destroy();
    return true;
  }

  if (deleted_component_count > 0 || level_index > 0)
  {
    // remove lower levels
    subdimple->ClearLowerSubdivisionLevels(level_index);
  }

  return (deleted_component_count > 0);
}

unsigned int ON_SubDLevel::UpdateEdgeTags(
  bool bUnsetEdgeTagsOnly
  )
{
  // Update edge flags and sector weights.
  unsigned int edge_change_count = 0;
  ON_SubDEdge* next_edge = m_edge[0];
  for (ON_SubDEdge* edge = next_edge; nullptr != edge; edge = next_edge)
  {
    next_edge = const_cast<ON_SubDEdge*>(edge->m_next_edge);

    const ON_SubD::EdgeTag edge_tag0 = edge->m_edge_tag;
    if (bUnsetEdgeTagsOnly && ON_SubD::EdgeTag::Unset != edge_tag0 )
    {
      continue;
    }

    if (nullptr == edge->m_vertex[0] || nullptr == edge->m_vertex[1])
    {
      ON_SUBD_ERROR("nullptr edge->m_vertex[] values");
      continue;
    }

    const double edge_sector_coefficient0[2] = { edge->m_sector_coefficient[0], edge->m_sector_coefficient[1] };

    if (2 != edge->m_face_count)
    {
      edge->m_edge_tag = ON_SubD::EdgeTag::Crease;
      edge->m_sector_coefficient[0] = ON_SubDSectorType::IgnoredSectorWeight;
      edge->m_sector_coefficient[1] = ON_SubDSectorType::IgnoredSectorWeight;
    }
    else
    {
      edge->m_sector_coefficient[0] = ON_SubDSectorType::UnsetSectorWeight;
      edge->m_sector_coefficient[1] = ON_SubDSectorType::UnsetSectorWeight;
      const bool bBothVertexTagsAreSet
        =  ON_SubD::VertexTag::Unset != edge->m_vertex[0]->m_vertex_tag
        && ON_SubD::VertexTag::Unset != edge->m_vertex[1]->m_vertex_tag
        ;
      const unsigned int tagged_end_index = edge->TaggedEndIndex();
      if (0 == tagged_end_index || 1 == tagged_end_index)
        edge->m_sector_coefficient[tagged_end_index] = ON_SubDSectorType::IgnoredSectorWeight;

      switch (edge_tag0)
      {
      case ON_SubD::EdgeTag::Unset:
        if (2 == tagged_end_index)
        {
          edge->m_edge_tag = ON_SubD::EdgeTag::X;
        }
        else if ( bBothVertexTagsAreSet )
        {
          edge->m_edge_tag = ON_SubD::EdgeTag::Smooth;
          if (3 == tagged_end_index)
          {
            edge->m_sector_coefficient[0] = ON_SubDSectorType::IgnoredSectorWeight;
            edge->m_sector_coefficient[1] = ON_SubDSectorType::IgnoredSectorWeight;
          }
        }
        break;

      case ON_SubD::EdgeTag::Smooth:
        if (2 == tagged_end_index)
        {
          edge->m_edge_tag = ON_SubD::EdgeTag::X;
        }
        else if (3 == tagged_end_index && bBothVertexTagsAreSet)
        {
          edge->m_sector_coefficient[0] = ON_SubDSectorType::IgnoredSectorWeight;
          edge->m_sector_coefficient[1] = ON_SubDSectorType::IgnoredSectorWeight;
        }
        break;

      case ON_SubD::EdgeTag::Crease:
        edge->m_sector_coefficient[0] = ON_SubDSectorType::IgnoredSectorWeight;
        edge->m_sector_coefficient[1] = ON_SubDSectorType::IgnoredSectorWeight;
        break;

      case ON_SubD::EdgeTag::Sharp:
        ON_SUBD_ERROR("ON_SubD::EdgeTag::Sharp not handled.");
        break;

      case ON_SubD::EdgeTag::X:
        if ( 2 != tagged_end_index && bBothVertexTagsAreSet)
          edge->m_edge_tag = ON_SubD::EdgeTag::Smooth;
        break;

      default:
        break;
      }
    }

    if (!(edge_tag0 == edge->m_edge_tag
      && edge_sector_coefficient0[0] == edge->m_sector_coefficient[0]
      && edge_sector_coefficient0[1] == edge->m_sector_coefficient[1]))
      edge_change_count++;
  }

  return edge_change_count;
}


unsigned int ON_SubDLevel::UpdateVertexTags(
  bool bUnsetVertexTagsOnly
  )
{
  // Update edge flags and sector weights.
  unsigned int vertex_change_count = 0;
 

  ON_SubDVertex* next_vertex = m_vertex[0];
  for (ON_SubDVertex* vertex = next_vertex; nullptr != vertex; vertex = next_vertex)
  {
    next_vertex = const_cast<ON_SubDVertex*>(vertex->m_next_vertex);

    const ON_SubD::VertexTag vertex_tag0 = vertex->m_vertex_tag;
    if (bUnsetVertexTagsOnly && ON_SubD::VertexTag::Unset != vertex_tag0 )
    {
      continue;
    }

    const unsigned int edge_count = vertex->m_edge_count;
    unsigned int creased_edge_count = 0;
    unsigned int sharp_edge_count = 0;
    for (unsigned int vei = 0; vei < edge_count; vei++)
    {
      const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(vertex->m_edges[vei].m_ptr);
      if (nullptr == edge)
      {
        ON_SUBD_ERROR("nullptr vertex->m_edges[] values");
        continue;
      }
      if (ON_SubD::EdgeTag::Sharp == edge->m_edge_tag)
      {
        ON_SUBD_ERROR("ON_SubD::EdgeTag::Sharp is not supported yet.");
        continue;
      }
      if ( ON_SubD::EdgeTag::Crease == edge->m_edge_tag
        || 2 != edge->m_face_count 
        )
        creased_edge_count++;
      else if ( ON_SubD::EdgeTag::Sharp == edge->m_edge_tag )
        sharp_edge_count++;

      // NOTE: 
      //  edges tagged as ON_SubD::EdgeTag::Unset with two faces
      //  ending at a vertex with 3 or more edges
      //  will eventually be tagged as smooth once this vertex
      //  is tagged as smooth.
    }


    ON_SubD::VertexTag vertex_tag1 = vertex_tag0;
    if (edge_count <= 2 || (creased_edge_count+sharp_edge_count) >= 2)
    {
      if ( ON_SubD::VertexTag::Corner != vertex_tag0 )
        vertex_tag1 = ON_SubD::VertexTag::Crease;
    }
    else
    {
      if ( 1 == creased_edge_count && 0 == sharp_edge_count )
        vertex_tag1 = ON_SubD::VertexTag::Dart;
      else
        vertex_tag1 = ON_SubD::VertexTag::Smooth;
    }

    if (vertex_tag0 != vertex_tag1)
    {
      vertex->m_vertex_tag = vertex_tag1;
      vertex_change_count++;
    }
  }

  return vertex_change_count;
}


unsigned int ON_SubDLevel::UpdateAllTagsAndSectorCoefficients(
  bool bUnsetValuesOnly
)
{
  unsigned int change_count = 0;

  bool bUpdateEdges = true;
  bool bUpdateVertices = true;
  for ( unsigned int it_count = 0; it_count < 8; it_count++)
  {
    const unsigned int edge_change_count 
      = bUpdateEdges
      ? UpdateEdgeTags(bUnsetValuesOnly)
      : 0;
    bUpdateVertices = (edge_change_count > 0 || 0 == it_count);
    change_count += edge_change_count;

    const unsigned int vertex_change_count 
      = bUpdateVertices
      ? UpdateVertexTags(bUnsetValuesOnly)
      : 0;
    bUpdateEdges = (vertex_change_count > 0);    
    change_count += vertex_change_count;

    bUpdateVertices = false;
    if ( false == bUpdateEdges)
      break;
  }

  if (bUpdateVertices && bUpdateEdges)
  {
    ON_SUBD_ERROR("Recursion limit exceeded.");
  }

  change_count += UpdateEdgeSectorCoefficients(false);

  return change_count;
}



unsigned int ON_SubD::UpdateVertexTags(
    bool bUnsetVertexTagsOnly
    )
{
  ON_SubDLevel* level = ActiveLevelPointer();
  if ( nullptr == level )
    return ON_SUBD_RETURN_ERROR(0);

  return level->UpdateVertexTags(bUnsetVertexTagsOnly);
}

unsigned int ON_SubD::UpdateEdgeTags(
  bool bUnsetEdgeTagsOnly
  )
{
  ON_SubDLevel* level = ActiveLevelPointer();
  if ( nullptr == level )
    return ON_SUBD_RETURN_ERROR(0);

  return level->UpdateEdgeTags(bUnsetEdgeTagsOnly);
}


unsigned int ON_SubD::UpdateEdgeSectorCoefficients(
    bool bUnsetSectorCoefficientsOnly
    )
{
  ON_SubDLevel* level = ActiveLevelPointer();
  if ( nullptr == level )
    return ON_SUBD_RETURN_ERROR(0);

  return level->UpdateEdgeSectorCoefficients(bUnsetSectorCoefficientsOnly);
}

unsigned int ON_SubD::UpdateAllTagsAndSectorCoefficients(
  bool bUnsetValuesOnly
)
{
  ON_SubDLevel* level = ActiveLevelPointer();
  if ( nullptr == level )
    return ON_SUBD_RETURN_ERROR(0);

  return level->UpdateAllTagsAndSectorCoefficients(bUnsetValuesOnly);
}

unsigned int ON_SubDimple::DeleteComponents(
  unsigned int level_index
  )
{
  unsigned int deleted_component_count = 0;

  if (level_index >= m_levels.UnsignedCount())
    return ON_SUBD_RETURN_ERROR(0);

  ON_SubDLevel* level = m_levels[level_index];
  if (nullptr == level)
    return ON_SUBD_RETURN_ERROR(0);

  ON_SubDFace* next_face = level->m_face[0];
  for (ON_SubDFace* face = next_face; nullptr != face; face = next_face)
  {
    next_face = const_cast< ON_SubDFace* >(face->m_next_face);
    bool bDelete = (ON_ComponentStatus::AllSet == face->m_status || 0 == face->m_edge_count);
    if (false == bDelete)
    {
      for (unsigned short fei = 0; fei < face->m_edge_count && false == bDelete; fei++)
      {
        const ON_SubDEdge* edge = face->Edge(fei);
        if (nullptr == edge
          || nullptr == edge->m_vertex[0]
          || nullptr == edge->m_vertex[1]
          || ON_ComponentStatus::AllSet == edge->m_status
          || ON_ComponentStatus::AllSet == edge->m_vertex[0]->m_status
          || ON_ComponentStatus::AllSet == edge->m_vertex[1]->m_status
          )
        {
          bDelete = true;
          if (nullptr != edge && ON_ComponentStatus::AllSet != edge->m_status)
            edge->m_status = ON_ComponentStatus::AllSet;
        }
      }
      if (false == bDelete)
        continue;
    }
    level->RemoveFace(face);
    m_heap.ReturnFace(face);
    deleted_component_count++;
  }

  ON_SubDEdge* next_edge = level->m_edge[0];
  for (ON_SubDEdge* edge = next_edge; nullptr != edge; edge = next_edge)
  {
    next_edge = const_cast< ON_SubDEdge* >(edge->m_next_edge);
    bool bDelete = (ON_ComponentStatus::AllSet == edge->m_status || 0 == edge->m_face_count );
    if (false == bDelete)
    {
      for (unsigned short evi = 0; evi < 2 && false == bDelete; evi++)
      {
        if (nullptr == edge->m_vertex[0]
          || nullptr == edge->m_vertex[1]
          || ON_ComponentStatus::AllSet == edge->m_vertex[0]->m_status
          || ON_ComponentStatus::AllSet == edge->m_vertex[1]->m_status
          )
          bDelete = true;
      }
      if (false == bDelete)
        continue;
    }

    level->RemoveEdge(edge);
    m_heap.ReturnEdge(edge);
    deleted_component_count++;
  }

  ON_SubDVertex* next_vertex = level->m_vertex[0];
  for (ON_SubDVertex* vertex = next_vertex; nullptr != vertex; vertex = next_vertex)
  {
    next_vertex = const_cast<ON_SubDVertex*>(vertex->m_next_vertex);
    bool bDelete = (ON_ComponentStatus::AllSet == vertex->m_status || 0 == vertex->m_face_count || 0 == vertex->m_edge_count );
    if ( false == bDelete )
      continue;

    level->RemoveVertex(vertex);
    m_heap.ReturnVertex(vertex);
    deleted_component_count++;
  }

  if ( 0 == deleted_component_count )
    return 0;

  // Remove edge references to deleted faces
  next_edge = level->m_edge[0];
  for (ON_SubDEdge* edge = next_edge; nullptr != edge; edge = next_edge)
  {
    next_edge = const_cast<ON_SubDEdge*>(edge->m_next_edge);
    ON_SubDFacePtr* fptr0 = edge->m_face2;
    ON_SubDFacePtr* fptr1 = edge->m_face2;
    const unsigned short edge_face_count = edge->m_face_count;
    edge->m_face_count = 0;
    for (unsigned short efi = 0; efi < edge_face_count; efi++, fptr0++)
    {
      if (2 == efi)
        fptr0 = edge->m_face2;
      const ON_SubDFace* face = fptr0->Face();
      if (nullptr == face || ON_UNSET_UINT_INDEX == face->ArchiveId())
        continue;
      *fptr1++ = *fptr0;
      edge->m_face_count++;
      if (2 == edge->m_face_count)
        fptr1 = edge->m_facex;
    }

    if (0 == edge->m_face_count)
    {
      level->RemoveEdge(edge);
      m_heap.ReturnEdge(edge);
      deleted_component_count++;
      continue;
    }
    
    if (edge->m_face_count <= 2 && nullptr != edge->m_facex)
      m_heap.ReturnEdgeExtraArray(edge);

    if ( edge->m_face_count != 2 )
      edge->m_edge_tag = ON_SubD::EdgeTag::Crease;

    edge->m_sector_coefficient[0] = ON_SubDSectorType::UnsetSectorWeight;
    edge->m_sector_coefficient[1] = ON_SubDSectorType::UnsetSectorWeight;
  }

  // Remove vertex references to deleted edges and faces
  next_vertex = level->m_vertex[0];
  for (ON_SubDVertex* vertex = next_vertex; nullptr != vertex; vertex = next_vertex)
  {
    next_vertex = const_cast<ON_SubDVertex*>(vertex->m_next_vertex);

    unsigned int count = vertex->m_edge_count;
    vertex->m_edge_count = 0;

    bool bInteriorVertex = true;
    unsigned int crease_count = 0;
    for (unsigned short vei = 0; vei < count; vei++)
    {
      const ON_SubDEdge* edge = vertex->m_edges[vei].Edge();
      if (nullptr == edge || ON_UNSET_UINT_INDEX == edge->ArchiveId())
      {
        bInteriorVertex = false;
        continue;
      }
      if (edge->IsCrease(false))
        crease_count++;
      if (2 != edge->m_face_count)
        bInteriorVertex = false;
      vertex->m_edges[vertex->m_edge_count++] = vertex->m_edges[vei];
    }

    if ( crease_count > 2 )
      vertex->m_vertex_tag = ON_SubD::VertexTag::Corner;
    else if (false == bInteriorVertex || crease_count > 1)
    {
      if (false == vertex->IsCreaseOrCorner())
        vertex->m_vertex_tag = ON_SubD::VertexTag::Crease;
    }

    count = vertex->m_face_count;
    vertex->m_face_count = 0;
    for (unsigned short vfi = 0; vfi < count; vfi++)
    {
      const ON_SubDFace* face = vertex->m_faces[vfi];
      if (nullptr == face || ON_UNSET_UINT_INDEX == face->ArchiveId()) 
        continue;
      vertex->m_faces[vertex->m_face_count++] = vertex->m_faces[vfi];
    }

    if (0 == vertex->m_face_count && 0 == vertex->m_edge_count )
    {
      level->RemoveVertex(vertex);
      m_heap.ReturnVertex(vertex);
      deleted_component_count++;
    }
  }

  if (0 == level->m_edge_count || 0 == level->m_edge_count || 0 == level->m_face_count)
  {
    Destroy();
  }
  else
  {
    // remove all information that is no longer valid
    level->m_limit_mesh.Clear();
    level->MarkAggregateComponentStatusAsNotCurrent();
    level->ClearSubdivisonAndLimitPoints();
    level->ClearBoundingBox();
    level->ClearEdgeFlags();

    ClearHigherSubdivisionLevels(level_index);

    // Update vertex tags, edge tags, and sector weights.
    level->UpdateAllTagsAndSectorCoefficients(false);
  }

  return deleted_component_count;
}


/*
Descripiton:
  Clears the ON_ComponentState
*/
unsigned int ON_SubD::ClearComponentMarks(
  bool bClearVertexMarks,
  bool bClearEdgeMarks,
  bool bClearFaceMarks,
  ON_SimpleArray< const ON_SubDComponentBase* >* marked_component_list
) const
{
  unsigned int clear_count = 0;

  if (bClearVertexMarks)
  {
    ON_SubDVertexIterator vit(*this);
    for (const ON_SubDVertex* v = vit.FirstVertex(); nullptr != v; v = vit.NextVertex())
    {
      if (v->m_status.ClearRuntimeMark())
      {
        if (nullptr != marked_component_list)
          marked_component_list->Append(v);
        clear_count++;
      }
    }
  }

  if (bClearEdgeMarks)
  {
    ON_SubDEdgeIterator eit(*this);
    for (const ON_SubDEdge* e = eit.FirstEdge(); nullptr != e; e = eit.NextEdge())
    {
      if (e->m_status.ClearRuntimeMark())
      {
        if (nullptr != marked_component_list)
          marked_component_list->Append(e);
        clear_count++;
      }
    }
  }

  if (bClearFaceMarks)
  {
    ON_SubDFaceIterator fit(*this);
    for (const ON_SubDFace* f = fit.FirstFace(); nullptr != f; f = fit.NextFace())
    {
      if (f->m_status.ClearRuntimeMark())
      {
        if (nullptr != marked_component_list)
          marked_component_list->Append(f);
        clear_count++;
      }
    }
  }

  return clear_count;
}

unsigned int ON_SubD::SetComponentMarks(
  bool bClearBeforeSet,
  const ON_SimpleArray< const ON_SubDComponentBase* >& marked_component_list
) const
{
  unsigned int set_count = 0;

  if (bClearBeforeSet)
    ClearComponentMarks(true, true, true, nullptr);

  const unsigned count = marked_component_list.Count();
  if (count <= 0)
    return 0;
  const ON_SubDComponentBase*const* a = marked_component_list.Array();
  if (nullptr == a)
    return 0;

  for (const ON_SubDComponentBase*const* a1 = a; a < a1; a++)
  {
    const ON_SubDComponentBase* c = *a;
    if (nullptr == c)
      continue;
    if (c->m_status.SetRuntimeMark())
      set_count++;
  }

  return set_count;
}



unsigned int ON_SubD::GetMarkedComponents(
    bool bIncludeVertices,
    bool bIncludeEdges,
    bool bIncludeFaces,
  ON_SimpleArray< const ON_SubDComponentBase* >& marked_component_list
) const
{
  unsigned int mark_count = 0;

  if (bIncludeVertices)
  {
    ON_SubDVertexIterator vit(*this);
    for (const ON_SubDVertex* v = vit.FirstVertex(); nullptr != v; v = vit.NextVertex())
    {
      if (v->m_status.RuntimeMark())
      {
        if (nullptr != marked_component_list)
          marked_component_list.Append(v);
        mark_count++;
      }
    }
  }

  if (bIncludeEdges)
  {
    ON_SubDEdgeIterator eit(*this);
    for (const ON_SubDEdge* e = eit.FirstEdge(); nullptr != e; e = eit.NextEdge())
    {
      if (e->m_status.RuntimeMark())
      {
        if (nullptr != marked_component_list)
          marked_component_list.Append(e);
        mark_count++;
      }
    }
  }

  if (bIncludeFaces)
  {
    ON_SubDFaceIterator fit(*this);
    for (const ON_SubDFace* f = fit.FirstFace(); nullptr != f; f = fit.NextFace())
    {
      if (f->m_status.RuntimeMark())
      {
        if (nullptr != marked_component_list)
          marked_component_list.Append(f);
        mark_count++;
      }
    }
  }

  return mark_count;
}


unsigned int ON_SubD::TransformComponents(
  const ON_Xform& xform,
  const ON_COMPONENT_INDEX* ci_list,
  size_t ci_count
)
{
  if (
    false ==  xform.IsValidAndNotZeroAndNotIdentity()
    || ci_count <= 0
    || nullptr == ci_list
    )
    return 0;

  ON_SimpleArray<ON_SubDComponentPtr> cptr_list;
  if (ComponentPtrFromComponentIndex(ci_list,ci_count,cptr_list) <= 0)
    return true; // nothing to delete

  return TransformComponents(xform,cptr_list.Array(),cptr_list.UnsignedCount());
}


static unsigned int Internal_MarkVertices(
  const ON_SubD& subd,
  const ON_SubDComponentPtr* cptr_list,
  size_t cptr_count,
  const ON_Xform& xform
)
{
  unsigned int v_mark_count = 0;

  const bool bTransform = xform.IsValidAndNotZeroAndNotIdentity();

  for (size_t i = 0; i < cptr_count; i++)
  {
    switch (cptr_list[i].ComponentType())
    {
    case ON_SubDComponentPtr::Type::Vertex:
      {
        const ON_SubDVertex* v = cptr_list[i].Vertex();
        if (nullptr != v && false == v->m_status.RuntimeMark())
        {
          v->m_status.SetRuntimeMark();
          if ( bTransform)
            const_cast<ON_SubDVertex*>(v)->Transform(false, xform);
          v_mark_count++;
        }
      }
      break;

    case ON_SubDComponentPtr::Type::Edge:
      {
        const ON_SubDEdge* e = cptr_list[i].Edge();
        if (nullptr != e)
        {
          for (unsigned int evi = 0; evi < 2; evi++)
          {
            const ON_SubDVertex* v = e->m_vertex[evi];
            if (nullptr != v && false == v->m_status.RuntimeMark())
            {
              v->m_status.SetRuntimeMark();
              if ( bTransform)
                const_cast<ON_SubDVertex*>(v)->Transform(false, xform);
              v_mark_count++;
            }
          }
        }
      }
      break;

    case ON_SubDComponentPtr::Type::Face:
      {
        const ON_SubDFace* f = cptr_list[i].Face();
        if (nullptr != f)
        {
          const unsigned int face_vertex_count = f->m_edge_count;
          for (unsigned int fvi = 0; fvi < face_vertex_count; fvi++)
          {
            const ON_SubDVertex* v = f->Vertex(fvi);
            if (nullptr != v && false == v->m_status.RuntimeMark())
            {
              v->m_status.SetRuntimeMark();
              if ( bTransform)
                const_cast<ON_SubDVertex*>(v)->Transform(false, xform);
              v_mark_count++;
            }
          }
        }
      }
      break;
    }
  }

  return v_mark_count;
}

unsigned int ON_SubD::TransformComponents(
  const ON_Xform& xform,
  const ON_SubDComponentPtr* cptr_list,
  size_t cptr_count
)
{
  if ( 
    false == xform.IsValidAndNotZeroAndNotIdentity()
    || cptr_count <= 0
    || nullptr == cptr_list
    )
    return 0;

  ON_SimpleArray<const ON_SubDComponentBase*> marked_components;
  const bool bRestoreMarks = ClearComponentMarks(true, true, true, &marked_components) > 0;

  const unsigned int v_count = Internal_MarkVertices(*this, cptr_list, cptr_count, xform);

  if (v_count > 0)
  {
    this->ClearEvaluationCache();
  }

  if (bRestoreMarks)
    SetComponentMarks(true, marked_components);

  return (v_count > 0);
}

unsigned int ON_SubD::ExtrudeComponents(
  const ON_Xform& xform,
  const ON_COMPONENT_INDEX* ci_list,
  size_t ci_count,
  bool bPermitNonManifoldEdgeCreation,
  ON_SubD::EdgeTag original_edge_tag,
  ON_SubD::EdgeTag moved_edge_tag
)
{
  if ( 
    false == xform.IsValidAndNotZeroAndNotIdentity()
    || xform.IsIdentity()
    || ci_count <= 0
    || nullptr == ci_list
    )
    return 0;

  ON_SimpleArray<ON_SubDComponentPtr> cptr_list;
  if (ComponentPtrFromComponentIndex(ci_list,ci_count,cptr_list) <= 0)
    return true; // nothing to extrude

  return ExtrudeComponents(
    xform,
    cptr_list.Array(),
    cptr_list.UnsignedCount(),
    bPermitNonManifoldEdgeCreation,
    original_edge_tag,
    moved_edge_tag
    );
}

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

  static const ON_Internal_ExtrudedVertexPair Unset;

  // the marked vertex was in the original subd and will be moved.
  ON_SubDVertex* m_marked_vertex = nullptr;

  // the unmarked vertex replaces the marked vertex at the original location.
  ON_SubDVertex* m_unmarked_vertex = nullptr;

  // from new vertex to original vertex
  ON_SubDEdge* m_new_side = nullptr;

  static int CompareMarkedVertexId(
    const ON_Internal_ExtrudedVertexPair* lhs,
    const ON_Internal_ExtrudedVertexPair* rhs
  );
};

const ON_Internal_ExtrudedVertexPair ON_Internal_ExtrudedVertexPair::Unset ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_Internal_ExtrudedVertexPair);


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

  static const ON_Internal_ExtrudedSide Unset;

  // the marked edge was in the original object and will be moved.
  ON_SubDEdge* m_marked_edge = nullptr;

  // the unmarked edge replaces the marked edge at the original location.
  ON_SubDEdge* m_unmarked_edge = nullptr;

  // start at new vertex and end at original vertex;
  ON_SubDEdge* m_new_side0 = nullptr;
  ON_SubDEdge* m_new_side1 = nullptr;

  ON_SubDFace* m_new_face = nullptr;
};

const ON_Internal_ExtrudedSide ON_Internal_ExtrudedSide::Unset ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_Internal_ExtrudedSide);

int ON_Internal_ExtrudedVertexPair::CompareMarkedVertexId(
  const ON_Internal_ExtrudedVertexPair* lhs,
  const ON_Internal_ExtrudedVertexPair* rhs
)
{
  const unsigned int lhs_id = lhs->m_marked_vertex->m_id;
  const unsigned int rhs_id = rhs->m_marked_vertex->m_id;
  if (lhs_id < rhs_id)
    return -1;
  if (lhs_id > rhs_id)
    return 1;
  return 0;
}

static void Internal_SetEdgeVertices(
  ON_SubD& subd,
  ON_Internal_ExtrudedVertexPair& vertex_pair
  )
{
  // marked edges use the marked vertex.
  ON_SubDVertex* marked_vertex = vertex_pair.m_marked_vertex;
  ON_SubDVertex* unmarked_vertex =  vertex_pair.m_unmarked_vertex;
  const unsigned int vertex_edge_count = marked_vertex->EdgeCount();
  unsigned int marked_edge_count = 0;
  unsigned int unmarked_edge_count = 0;
  unsigned int new_edge_count = 0;
  for (unsigned int vei = 0; vei < vertex_edge_count; vei++)
  {
    ON_SubDEdgePtr eptr = marked_vertex->m_edges[vei];
    const ON_SubDEdge* e = eptr.Edge();
    if (nullptr == e)
      continue;
    if (vertex_pair.m_new_side == e)
      new_edge_count++;
    else if (e->m_status.RuntimeMark())
      marked_edge_count++;
    else
      unmarked_edge_count++;
  }

  if (unmarked_edge_count <= 0)
    return;

  unmarked_edge_count += unmarked_vertex->m_edge_count;

  if ( unmarked_vertex->m_edge_capacity < (unmarked_edge_count+new_edge_count) )
  {
    subd.GrowVertexEdgeArray(unmarked_vertex, unmarked_edge_count);    
  }

  marked_vertex->m_edge_count = 0;
  for (unsigned int vei = 0; vei < vertex_edge_count; vei++)
  {
    ON_SubDEdgePtr eptr = marked_vertex->m_edges[vei];
    ON_SubDEdge* e = eptr.Edge();
    if (nullptr == e)
      continue;
    if (vertex_pair.m_new_side == e || e->m_status.RuntimeMark())
    {
      marked_vertex->m_edges[marked_vertex->m_edge_count] = eptr;
      marked_vertex->m_edge_count++;
    }
    else
    {
      if (e->m_vertex[0] == marked_vertex)
        e->m_vertex[0] = unmarked_vertex;
      else if (e->m_vertex[1] == marked_vertex)
        e->m_vertex[1] = unmarked_vertex;
      unmarked_vertex->m_edges[unmarked_vertex->m_edge_count] = eptr;
      unmarked_vertex->m_edge_count++;
    }
  }
}

static ON_SubDFace* Internal_AddNewFace(
  ON_SubD& subd,
  ON_Internal_ExtrudedSide& side
)
{
  // All components that will be moved have the runtime mark set.
  // All other components have a clear runtime mark.
  // The original_edge will be moved.
  // The new_edge will not be moved.
  // new edge and original edge go the same direction.
  // new_side_edges[2] run from new to original edges.
  
  // change edges of unmarked faces to use the new edge
  ON__UINT_PTR edir = 0;
  ON_SubDEdge* marked_edge = side.m_marked_edge; // will be moved
  ON_SubDEdge* unmarked_edge = side.m_unmarked_edge; // fixed
  unsigned int marked_edge_face_count0 = marked_edge->m_face_count;
  ON_SubDFacePtr* marked_edge_fptr1 = marked_edge->m_face2;
  const ON_SubDFacePtr* marked_edge_fptr0 = marked_edge_fptr1;
  unsigned int marked_edge_face_count1 = 0;
  subd.GrowEdgeFaceArray(unmarked_edge, marked_edge_face_count0);
  for (unsigned int efi = 0; efi < marked_edge_face_count0; efi++, marked_edge_fptr0++)
  {
    if (2 == efi)
      marked_edge_fptr0 = marked_edge->m_facex;

    if (2 == marked_edge_face_count1)
      marked_edge_fptr1 = marked_edge->m_facex;

    ON_SubDFace* f = marked_edge_fptr0->Face();
    if (nullptr == f)
    {
      ON_SUBD_ERROR("null face pointer");
      continue;
    }

    if (f->m_status.RuntimeMark())
    {
      edir = marked_edge_fptr0->FaceDirection();
      marked_edge_face_count1++;
      *marked_edge_fptr1 = *marked_edge_fptr0;
      marked_edge_fptr1++;
      continue; // this face will be moved and keeps edge e1
    }

    // f is unmarked.
    // change referenced edge from marked_edge to unmarked_edge.
    f->ReplaceEdgeInArray(0, marked_edge, unmarked_edge);

    unmarked_edge->AddFaceToArray(*marked_edge_fptr0);
  }

  // When marked_edge is a manifold edge, face_count goes from 2 to 1.
  marked_edge->m_face_count = static_cast<unsigned short>(marked_edge_face_count1);

  ON_SubDEdge* side0 = (0 == edir) ? side.m_new_side0 : side.m_new_side1;
  ON_SubDEdge* side1 = (0 == edir) ? side.m_new_side1 : side.m_new_side0;
  ON_SubDEdgePtr new_face_eptr[4];
  new_face_eptr[0] = ON_SubDEdgePtr::Create(side.m_marked_edge, 1-edir);
  new_face_eptr[1] = ON_SubDEdgePtr::Create(side0, 1);
  new_face_eptr[2] = ON_SubDEdgePtr::Create(side.m_unmarked_edge, edir);
  new_face_eptr[3] = ON_SubDEdgePtr::Create(side1, 0);

  side.m_new_face = subd.AddFace(4, new_face_eptr);

  return side.m_new_face;
}

bool Internal_ExtrudeAsCrease(
  const ON_SubDVertex* v
)
{
  unsigned int marked_faces_crease_count = 0;
  unsigned int unmarked_faces_crease_count = 0;
  const unsigned int vertex_edge_count = v->m_edge_count;
  for (unsigned int vei = 0; vei < vertex_edge_count; vei++)
  {
    const ON_SubDEdge* e = v->Edge(vei);
    if (nullptr == e)
      continue;
    if (ON_SubD::EdgeTag::Smooth == e->m_edge_tag)
      continue;
    if (ON_SubD::EdgeTag::Crease != e->m_edge_tag)
      return false;
    if (e->m_status.RuntimeMark())
      return false;
    if (2 != e->m_face_count)
      return false;
    const ON_SubDFace* f = e->Face(0);
    if (nullptr == f)
      return false;
    const bool bMarkedFace = f->m_status.RuntimeMark();
    f = e->Face(1);
    if (nullptr == f)
      return false;
    if (bMarkedFace != f->m_status.RuntimeMark())
      return false;
    if (bMarkedFace)
      marked_faces_crease_count++;
    else
      unmarked_faces_crease_count++;
  }
  return (1 == marked_faces_crease_count && 1 == unmarked_faces_crease_count);
}

static bool Internal_NonManifoldEdgeWillBeCreated( const ON_SubDVertex* v )
{
  if (nullptr == v || false == v->m_status.RuntimeMark())
    return false;
  v->m_status.ClearRuntimeMark();

  const unsigned int vertex_edge_count = v->m_edge_count;
  unsigned int boundary_count = 0;
  for (unsigned int vei = 0; vei < vertex_edge_count; vei++)
  {
    const ON_SubDEdge* e = v->Edge(vei);
    if (nullptr == e || 0 == e->m_face_count)
      continue;
    if (e->m_face_count > 2)
      return true;
    const ON_SubDFace* f = e->Face(0);
    const bool b0 = (nullptr != f) ? f->m_status.RuntimeMark() : false;
    f = (e->m_face_count > 1) ? e->Face(1) : nullptr;
    const bool b1 = (nullptr != f) ? f->m_status.RuntimeMark() : false;
    if (b0 == b1)
      continue;
    boundary_count++;
    if (boundary_count > 2)
      return true;
  }
  return false;
}

unsigned int ON_SubD::ExtrudeComponents(
  const ON_Xform& xform,
  const ON_SubDComponentPtr* cptr_list,
  size_t cptr_count,
  bool bPermitNonManifoldEdgeCreation,
  ON_SubD::EdgeTag original_edge_tag,
  ON_SubD::EdgeTag moved_edge_tag
)
{
  if (
    false == xform.IsValidAndNotZeroAndNotIdentity()
    || cptr_count <= 0
    || nullptr == cptr_list
    )
    return 0;

  if (ON_SubD::EdgeTag::Crease != original_edge_tag)
    original_edge_tag = ON_SubD::EdgeTag::Unset;

  if (ON_SubD::EdgeTag::Crease != moved_edge_tag)
    moved_edge_tag = ON_SubD::EdgeTag::Unset;

  ON_SimpleArray<const ON_SubDComponentBase*> marked_components;
  const bool bRestoreMarks = ClearComponentMarks(true, true, true, &marked_components) > 0;

  // Marks very vertex touching a component in the cptr_list.
  // Skips applying the transform because it is the identity.
  const unsigned int v_count = Internal_MarkVertices(*this, cptr_list, cptr_count, ON_Xform::IdentityTransformation);

  if (false == bPermitNonManifoldEdgeCreation)
  {
  }

  unsigned int f_count = 0;
  for (;;)
  {
    if (0 == v_count)
      break;

    // Mark the faces that will be moved.
    ON_SimpleArray<const ON_SubDFace*> marked_faces(128);
    ON_SubDFaceIterator fit(*this);
    for (const ON_SubDFace* f = fit.FirstFace(); nullptr != f; f = fit.NextFace())
    {
      const unsigned int face_vertex_count = f->m_edge_count;
      if (face_vertex_count < 3)
        continue;
      for ( unsigned int fvi = 0; fvi < face_vertex_count; fvi++)
      {
        const ON_SubDVertex* v = f->Vertex(fvi);
        if (nullptr != v && v->m_status.RuntimeMark())
          continue;
        f = nullptr;
        break;
      }
      if (nullptr != f)
      {
        f->m_status.SetRuntimeMark();
        marked_faces.Append(f);
      }
    }

    f_count = marked_faces.UnsignedCount();

    if (0 == f_count)
    {
      // No faces are moving.
      break;
    }

    if (f_count == FaceCount())
    {
      // Every face is moving.
      Transform(xform);
      break;
    }

    // Mark edges on the boundary of the moved subset.
    ON_SimpleArray<ON_Internal_ExtrudedSide> new_sides(64);
    ON_SubDEdgeIterator eit(*this);
    for (const ON_SubDEdge* e = eit.FirstEdge(); nullptr != e; e = eit.NextEdge())
    {
      bool bMarkedFace = false;
      bool bUnmarkedFace = false;
      const unsigned int edge_face_count = e->m_face_count;
      for (unsigned int efi = 0; efi < edge_face_count; efi++)
      {
        const ON_SubDFace* f = e->Face(efi);
        if (nullptr == f)
          continue;
        if (f->m_status.RuntimeMark())
          bMarkedFace = true; // f is in the subset of moved faces.
        else
          bUnmarkedFace = true; // f is in the subset of stationary faces.

        if (bMarkedFace && bUnmarkedFace)
        {
          // e is on the boundary between the subset of moved faces and stationary faces.
          e->m_status.SetRuntimeMark();
          new_sides.AppendNew().m_marked_edge = const_cast<ON_SubDEdge*>(e);
          break;
        }
      }
    }

    const unsigned int e_count = new_sides.UnsignedCount();
    if (e_count <= 0)
    {
      // no edges between moved and stationary faces
      Transform(xform);
      break;
    }

    if (false == bPermitNonManifoldEdgeCreation)
    {
      bool bPermitNonManifoldEdgeWillBeCreated = false;
      for (unsigned int fi = 0; fi < f_count; fi++)
      {
        const ON_SubDFace* f = marked_faces[fi];
        const unsigned int face_vertex_count = f->m_edge_count;
        for (unsigned int fvi = 0; fvi < face_vertex_count; fvi++)
        {
          bPermitNonManifoldEdgeWillBeCreated = Internal_NonManifoldEdgeWillBeCreated(f->Vertex(fvi));
          if (bPermitNonManifoldEdgeWillBeCreated)
            break;
        }
        if (bPermitNonManifoldEdgeWillBeCreated)
          break;
      }

      if (bPermitNonManifoldEdgeWillBeCreated)
        break;
    }

    // clear vertex  marks.
    ClearComponentMarks(true, false, false, nullptr);    

    // Duplicate vertices that are on a edge between a marked and unmarked face.
    ON_SimpleArray<ON_Internal_ExtrudedVertexPair> vertex_pairs(e_count+8);
    for (unsigned int i = 0; i < e_count; i++)
    {
      const ON_SubDEdge* e = new_sides[i].m_marked_edge;
      for (unsigned int evi = 0; evi < 2; evi++)
      {
        ON_SubDVertex* v = const_cast<ON_SubDVertex*>(e->m_vertex[evi]);
        if (nullptr == v)
          continue;
        if (v->m_status.RuntimeMark())
          continue;

        // Mark this vertex. It will eventually get moved
        v->m_status.SetRuntimeMark();
        ON_Internal_ExtrudedVertexPair& vpair = vertex_pairs.AppendNew();
        vpair.m_marked_vertex = v;

        ON_SubD::VertexTag new_vertex_tag;
        ON_SubD::EdgeTag new_edge_tag;
        switch (v->m_vertex_tag)
        {
        case ON_SubD::VertexTag::Crease:
          new_vertex_tag = v->m_vertex_tag;
          new_edge_tag 
            = Internal_ExtrudeAsCrease(v)
            ? ON_SubD::EdgeTag::Crease
            : ON_SubD::EdgeTag::Unset;
          break;
        case ON_SubD::VertexTag::Corner:
          new_vertex_tag = v->m_vertex_tag;
          new_edge_tag = ON_SubD::EdgeTag::Crease;
          break;
        default:
          new_vertex_tag = ON_SubD::VertexTag::Unset;
          new_edge_tag = ON_SubD::EdgeTag::Unset;
          break;
        }

        // original vertex will eventually be moved.
        v->m_vertex_tag = new_vertex_tag;

        // new vertex will become part of the stationary subset.
        // It is not marked.
        vpair.m_unmarked_vertex = this->AddVertex(new_vertex_tag, v->m_P);

        // transform the marked boundary vertex
        v->Transform(false, xform);

        // edge from stationary subset to moved subset.
        vpair.m_new_side = this->AddEdge(new_edge_tag, vpair.m_unmarked_vertex, vpair.m_marked_vertex);
      }
    }

    // sort vertex pairs so they can be located by the original vertex id.
    vertex_pairs.QuickSort(ON_Internal_ExtrudedVertexPair::CompareMarkedVertexId);

    // remove unmarked faces from marked vertices
    for (unsigned int i = 0; i < vertex_pairs.UnsignedCount(); i++)
    {
      ON_SubDVertex* marked_vertex = vertex_pairs[i].m_marked_vertex;
      ON_SubDVertex* unmarked_vertex = vertex_pairs[i].m_unmarked_vertex;
      const unsigned int vertex_face_count0 = marked_vertex->m_face_count;
      GrowVertexFaceArray(unmarked_vertex, vertex_face_count0);
      marked_vertex->m_face_count = 0;
      for (unsigned int vfi = 0; vfi < vertex_face_count0; vfi++)
      {
        const ON_SubDFace* f = marked_vertex->m_faces[vfi];
        if (nullptr == f )
          continue;
        ON_SubDVertex* v
          = (f->m_status.RuntimeMark())
          ? marked_vertex
          : unmarked_vertex;
        v->m_faces[v->m_face_count] = f;
        v->m_face_count++;
      }
    }

    // build new side edges
    ON_Internal_ExtrudedVertexPair key[2];
    for (unsigned int i = 0; i < e_count; i++)
    {
      const ON_SubDEdge* e = new_sides[i].m_marked_edge;
      for (unsigned int evi = 0; evi < 2; evi++)
      {
        key[evi].m_marked_vertex = const_cast<ON_SubDVertex*>(e->m_vertex[evi]);
        const int i0 =
          (nullptr != key[evi].m_marked_vertex)
          ? vertex_pairs.BinarySearch(&key[evi], ON_Internal_ExtrudedVertexPair::CompareMarkedVertexId)
          : -1;
        if (i0 < 0)
        {
          key[evi] = ON_Internal_ExtrudedVertexPair::Unset;
          continue;
        }
        key[evi] = vertex_pairs[i0];
      } 
      new_sides[i].m_unmarked_edge = this->AddEdge(ON_SubD::EdgeTag::Unset, key[0].m_unmarked_vertex, key[1].m_unmarked_vertex);
      new_sides[i].m_new_side0 = key[0].m_new_side;
      new_sides[i].m_new_side1 = key[1].m_new_side;
    }

    // Mark everything a moved face touches
    // including interior edges and vertices.
    // Transform any vertices that are not already marked.
    for (unsigned int i = 0; i < f_count; i++)
    {
      const ON_SubDFace* f = marked_faces[i];
      const unsigned int face_edge_count = f->m_edge_count;
      for (unsigned int fei = 0; fei < face_edge_count; fei++)
      {
        const ON_SubDEdge* e = f->Edge(fei);
        if (nullptr == e)
          continue;
        e->m_status.SetRuntimeMark();
        for (unsigned int evi = 0; evi < 2; evi++)
        {
          ON_SubDVertex* v = const_cast<ON_SubDVertex*>(e->m_vertex[evi]);
          if (nullptr !=v && false == v->m_status.RuntimeMark())
          {
            v->Transform(false, xform);
            v->m_status.SetRuntimeMark();
          }
        }
      }
    }

    // For the original boundary vertrex, move unmarked edges to use the new vertex.
    for (unsigned int i = 0; i < vertex_pairs.UnsignedCount(); i++)
    {
      Internal_SetEdgeVertices(*this, vertex_pairs[i]);
    }

    // build new side faces
    for (unsigned int i = 0; i < e_count; i++)
    {
      Internal_AddNewFace(*this, new_sides[i]);
    }

    // remove cached subdivision calculations
    ClearEvaluationCache();

    // Calculate vertex tags, edge tags, edge sector weights.
    this->UpdateAllTagsAndSectorCoefficients(true);
    break;
  }


  if (bRestoreMarks)
    SetComponentMarks(true, marked_components);

#if defined(ON_DEBUG)
  IsValid();
#endif

  return f_count;
}



#if defined(ON_SUBD_CENSUS)
//////////////////////////////////////////////////////////////////////////
//
// ON_CensusCounter
//


class ON_PointerHashElement
{
public:
  ON__UINT_PTR m_sn = 0;
  ON__UINT_PTR m_ptr = 0;
  ON_PointerHashElement* m_next = nullptr;
};

class ON_PointerHashTable
{
public:

  void Add(ON_CensusCounter::Class c, ON__UINT_PTR ptr);
  void Remove(ON_CensusCounter::Class c, ON__UINT_PTR ptr);

  ON__UINT_PTR SerialNumber(ON_CensusCounter::Class c, ON__UINT_PTR ptr) const;
  
  enum 
  {
    hash_count = 1024
  };
  ON_PointerHashElement* m_table[(unsigned int)ON_CensusCounter::Class::count][hash_count];


  unsigned int m_count = 0;


  static bool TheOneExists();
  static ON_PointerHashTable* TheOne();
  static void DestroyTheOne();

private:
  ON_PointerHashTable();
  ~ON_PointerHashTable();
  ON_PointerHashTable(const ON_PointerHashTable&) = delete;
  ON_PointerHashTable& operator=(const ON_PointerHashTable&) = delete;

  static unsigned int PointerHash(ON__UINT_PTR ptr);
  ON_FixedSizePool m_fsp;
  static ON_PointerHashTable* m_the_one;
};

ON_PointerHashTable* ON_PointerHashTable::m_the_one = nullptr;

ON_PointerHashTable::ON_PointerHashTable()
{
  memset(m_table,0,sizeof(m_table));
  m_fsp.Create(sizeof(ON_PointerHashElement),0,0);
}

ON_PointerHashTable::~ON_PointerHashTable()
{
  memset(m_table,0,sizeof(m_table));
  m_count = 0;
  m_fsp.Destroy();
}

bool ON_PointerHashTable::TheOneExists()
{
  return (nullptr != ON_PointerHashTable::m_the_one);
}

ON_PointerHashTable* ON_PointerHashTable::TheOne()
{
  if (nullptr == ON_PointerHashTable::m_the_one)
  {
    ON_PointerHashTable::m_the_one = new ON_PointerHashTable();
  }
  return ON_PointerHashTable::m_the_one;
}

void ON_PointerHashTable::DestroyTheOne()
{
  if (nullptr != ON_PointerHashTable::m_the_one)
  {
    delete ON_PointerHashTable::m_the_one;
    ON_PointerHashTable::m_the_one = nullptr;
  }
}

unsigned int ON_PointerHashTable::PointerHash(ON__UINT_PTR ptr)
{
  return (ON_CRC32(0, sizeof(ptr),&ptr) % ON_PointerHashTable::hash_count);
}

void ON_PointerHashTable::Add(ON_CensusCounter::Class c, ON__UINT_PTR ptr)
{
  static ON__UINT_PTR sn = 0;
  // not thread safe - a crude debugging tool
  ON_PointerHashElement* phe = (ON_PointerHashElement*)m_fsp.AllocateDirtyElement();
  phe->m_sn = ++sn;
  phe->m_ptr = ptr;
  const unsigned int hash_dex = ON_PointerHashTable::PointerHash(ptr);
  phe->m_next = m_table[(unsigned int)c][hash_dex];
  m_table[(unsigned int)c][hash_dex] = phe;
  m_count++;
}

void ON_PointerHashTable::Remove(ON_CensusCounter::Class c, ON__UINT_PTR ptr)
{
  const unsigned int hash_dex = ON_PointerHashTable::PointerHash(ptr);
  ON_PointerHashElement* phe = m_table[(unsigned int)c][hash_dex];
  for (ON_PointerHashElement* phe0 = nullptr; nullptr != phe; phe = phe->m_next)
  {
    if (ptr == phe->m_ptr)
    {
      if ( nullptr == phe0 )
        m_table[(unsigned int)c][hash_dex] = phe->m_next;
      else
        phe0 = phe->m_next;
      m_fsp.ReturnElement(phe);
      m_count--;
      return;
    }
    phe0 = phe;
  }
  // pointer not in the table
  ON_SubDIncrementErrorCount();
  return;
}


ON__UINT_PTR ON_PointerHashTable::SerialNumber(ON_CensusCounter::Class c, ON__UINT_PTR ptr) const
{
  const unsigned int hash_dex = ON_PointerHashTable::PointerHash(ptr);
  for ( ON_PointerHashElement* phe = m_table[(unsigned int)c][hash_dex]; nullptr != phe; phe = phe->m_next)
  {
    if (ptr == phe->m_ptr)
      return phe->m_sn;
  }
  // pointer not in the table
  return 0;
}

void ON_CensusCounter::RegisterBirth(ON_CensusCounter::Class c, ON__UINT_PTR ptr)
{
  ON_PointerHashTable::TheOne()->Add(c,ptr);
}

void ON_CensusCounter::RegisterDeath(ON_CensusCounter::Class c, ON__UINT_PTR ptr)
{
  ON_PointerHashTable::TheOne()->Remove(c,ptr);
}


void ON_CensusCounter::Clear()
{
  ON_PointerHashTable::DestroyTheOne();
}

class ON_SubDLimitMeshImpl* ON_SubDLimitMesh::SubLimple() const
{
  return m_impl_sp.get();
}

unsigned int ON_SubDLimitMesh::SubLimpleUseCount() const
{
  return m_impl_sp.use_count();
}

void ON_CensusCounter::CensusReport(
  class ON_TextLog& text_log
  )
{
  ON_PointerHashTable* ht = ON_PointerHashTable::TheOneExists() ? ON_PointerHashTable::TheOne() : nullptr;
  if ( nullptr == ht )
  {
    text_log.Print("No class census information exists.\n");
    return;
  }

  text_log.Print("%d items exist.\n",ht->m_count);
  for (unsigned int i = 0; i < (unsigned int)ON_CensusCounter::Class::count; i++)
  {
    bool bPrintClassName = true;
    const char* sClassName = nullptr;
    const ON_CensusCounter::Class c = (ON_CensusCounter::Class)i;
    switch (c)
    {
    case ON_CensusCounter::Class::subd:
      sClassName = "ON_SubD";
      break;
    case ON_CensusCounter::Class::subd_impl:
      sClassName = "ON_SubDimple";
      break;
    case ON_CensusCounter::Class::subd_limit_mesh:
      sClassName = "ON_SubDLimitMesh";
      break;
    case ON_CensusCounter::Class::subd_limit_mesh_impl:
      sClassName = "ON_SubDLimitMeshImpl";
      break;
    case ON_CensusCounter::Class::subd_ref:
      sClassName = "ON_SubDef";
      break;
    default:
      sClassName = "Bug in ON_CensusCounter";
      break;
    }

    for (unsigned int j = 0; j < ON_PointerHashTable::hash_count; j++)
    {
      for (ON_PointerHashElement* e = ht->m_table[i][j]; nullptr != e; e = e->m_next)
      {
        const unsigned int sn = (unsigned int)e->m_sn;

        if (bPrintClassName)
        {
          text_log.Print("\n\n%s census:\n",sClassName);
          bPrintClassName = false;
        }

        switch (c)
        {
        case ON_CensusCounter::Class::subd:
          {
            const ON_SubD* subd = (const ON_SubD*)e->m_ptr;
            if ( subd == &ON_SubD::Empty )
              text_log.Print("ON_SubD::Empty (%u) ", sn);
            else
              text_log.Print("ON_SubD(%u) ", sn);
            const ON_SubDimple* dimple = subd->SubDimple();
            if ( nullptr == dimple )
              text_log.Print(" ON_SubDimple(nullptr)\n");
            else
            {
              unsigned int dimple_sn = (unsigned int)ht->SerialNumber(ON_CensusCounter::Class::subd_impl, (ON__UINT_PTR)dimple);
              text_log.Print(" ON_SubDimple(%u)x%u\n", dimple_sn, subd->SubDimpleUseCount());
            }            
          }
          break;

        case ON_CensusCounter::Class::subd_impl:
          {
           text_log.Print("ON_SubDimple(%u)\n", sn);
          }
          break;

        case ON_CensusCounter::Class::subd_limit_mesh:
          {
            const ON_SubDLimitMesh* subd_limit_mesh = (const ON_SubDLimitMesh*)e->m_ptr;
            if ( subd_limit_mesh == &ON_SubDLimitMesh::Empty )
              text_log.Print("ON_SubDLimitMesh::Empty (%u) ", sn);
            else
              text_log.Print("ON_SubDLimitMesh(%u) ", sn);
            const class ON_SubDLimitMeshImpl* limple = subd_limit_mesh->SubLimple();
            if ( nullptr == limple )
              text_log.Print(" ON_SubDLimitMeshImpl(nullptr)\n");
            else
            {
              unsigned int limple_sn = (unsigned int)ht->SerialNumber(ON_CensusCounter::Class::subd_limit_mesh_impl, (ON__UINT_PTR)limple);
              text_log.Print(" ON_SubDLimitMeshImpl(%u)x%u\n", limple_sn, subd_limit_mesh->SubLimpleUseCount());
            }            
          }
          break;

        case ON_CensusCounter::Class::subd_limit_mesh_impl:
          {
            const ON_SubDLimitMeshImpl* subd_limple = (const ON_SubDLimitMeshImpl*)e->m_ptr;
            text_log.Print("ON_SubDLimitMeshImpl(%u)", sn);

            std::shared_ptr<ON_SubDimple> limple_sp(subd_limple->m_subdimple_wp.lock());
            const ON_SubDimple* dimple = limple_sp.get();
            if ( nullptr == dimple )
              text_log.Print(" ON_SubDimple(nullpr)\n");
            else
            {
              unsigned int dimple_sn = (unsigned int)ht->SerialNumber(ON_CensusCounter::Class::subd_impl, (ON__UINT_PTR)dimple);
              text_log.Print(" ON_SubDimple(%u)x%u+1\n", dimple_sn, (unsigned int)(limple_sp.use_count()-1));
            }            

          }
          break;

        case ON_CensusCounter::Class::subd_ref:
          {
            const ON_SubDRef* subd_ref = (const ON_SubDRef*)e->m_ptr;
            const ON_SubD* subd = &subd_ref->SubD();
            ON__UINT_PTR subd_sn = ht->SerialNumber(ON_CensusCounter::Class::subd, (ON__UINT_PTR)subd);
            text_log.Print("ON_SubDRef(%u) ON_SubD(%u)x%u\n", sn, subd_sn, subd_ref->ReferenceCount());
          }
          break;
        }
      }
    }
  }
}


static ON__UINT_PTR ON_SubDCensusCounter_This(ON_SubDCensusCounter* p)
{
  const ON_SubD* pSubD = (const ON_SubD*)((unsigned char*)p - sizeof(ON_Geometry));
  return (ON__UINT_PTR)pSubD;
}

ON_SubDCensusCounter::ON_SubDCensusCounter() ON_NOEXCEPT
{
  ON_CensusCounter::RegisterBirth(ON_CensusCounter::Class::subd,ON_SubDCensusCounter_This(this));
}

ON_SubDCensusCounter::~ON_SubDCensusCounter() ON_NOEXCEPT
{
  ON_CensusCounter::RegisterDeath(ON_CensusCounter::Class::subd,ON_SubDCensusCounter_This(this));
}

ON_SubDCensusCounter::ON_SubDCensusCounter(const ON_SubDCensusCounter&) ON_NOEXCEPT
{
  ON_CensusCounter::RegisterBirth(ON_CensusCounter::Class::subd,ON_SubDCensusCounter_This(this));
}

ON_SubDRefCensusCounter::ON_SubDRefCensusCounter() ON_NOEXCEPT
{
  ON_CensusCounter::RegisterBirth(ON_CensusCounter::Class::subd_ref,(ON__UINT_PTR)this);
}

ON_SubDRefCensusCounter::~ON_SubDRefCensusCounter() ON_NOEXCEPT
{
  ON_CensusCounter::RegisterDeath(ON_CensusCounter::Class::subd_ref,(ON__UINT_PTR)this);
}

ON_SubDRefCensusCounter::ON_SubDRefCensusCounter(const ON_SubDRefCensusCounter&) ON_NOEXCEPT
{
  ON_CensusCounter::RegisterBirth(ON_CensusCounter::Class::subd_ref,(ON__UINT_PTR)this);
}


ON_SubDImpleCensusCounter::ON_SubDImpleCensusCounter() ON_NOEXCEPT
{
  ON_CensusCounter::RegisterBirth(ON_CensusCounter::Class::subd_impl,(ON__UINT_PTR)this);
}

ON_SubDImpleCensusCounter::~ON_SubDImpleCensusCounter() ON_NOEXCEPT
{
  ON_CensusCounter::RegisterDeath(ON_CensusCounter::Class::subd_impl,(ON__UINT_PTR)this);
}

ON_SubDImpleCensusCounter::ON_SubDImpleCensusCounter(const ON_SubDImpleCensusCounter&) ON_NOEXCEPT
{
  ON_CensusCounter::RegisterBirth(ON_CensusCounter::Class::subd_impl,(ON__UINT_PTR)this);
}



ON_SubDLimitMeshCensusCounter::ON_SubDLimitMeshCensusCounter() ON_NOEXCEPT
{
  ON_CensusCounter::RegisterBirth(ON_CensusCounter::Class::subd_limit_mesh,(ON__UINT_PTR)this);
}

ON_SubDLimitMeshCensusCounter::~ON_SubDLimitMeshCensusCounter() ON_NOEXCEPT
{
  ON_CensusCounter::RegisterDeath(ON_CensusCounter::Class::subd_limit_mesh,(ON__UINT_PTR)this);
}

ON_SubDLimitMeshCensusCounter::ON_SubDLimitMeshCensusCounter(const ON_SubDLimitMeshCensusCounter&) ON_NOEXCEPT
{
  ON_CensusCounter::RegisterBirth(ON_CensusCounter::Class::subd_limit_mesh,(ON__UINT_PTR)this);
}




ON_SubDLimitMeshImplCensusCounter::ON_SubDLimitMeshImplCensusCounter() ON_NOEXCEPT
{
  ON_CensusCounter::RegisterBirth(ON_CensusCounter::Class::subd_limit_mesh_impl,(ON__UINT_PTR)this);
}

ON_SubDLimitMeshImplCensusCounter::~ON_SubDLimitMeshImplCensusCounter() ON_NOEXCEPT
{
  ON_CensusCounter::RegisterDeath(ON_CensusCounter::Class::subd_limit_mesh_impl,(ON__UINT_PTR)this);
}

ON_SubDLimitMeshImplCensusCounter::ON_SubDLimitMeshImplCensusCounter(const ON_SubDLimitMeshImplCensusCounter&) ON_NOEXCEPT
{
  ON_CensusCounter::RegisterBirth(ON_CensusCounter::Class::subd_limit_mesh_impl,(ON__UINT_PTR)this);
}

#endif

#endif
