#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"

#if defined(OPENNURBS_SUBD_WIP)


bool ON_SubDQuadNeighborhood::IsValid() const
{
  unsigned int count = 0;
  for (unsigned int i = 0; i < 4; i++)
  {
    if (m_bExtraordinaryCornerVertex[i])
      count++;
  }
  if (count != m_extraordinary_corner_vertex_count)
    return ON_SUBD_RETURN_ERROR(false);

  count = 0;
  for (unsigned int i = 0; i < 4; i++)
  {
    if (m_bExactQuadrantPatch[i])
    {
      if (m_bExtraordinaryCornerVertex[i] )
        return ON_SUBD_RETURN_ERROR(false);
      count++;
    }
  }
  if (count != m_exact_quadrant_patch_count)
    return ON_SUBD_RETURN_ERROR(false);

  count = 0;
  for (unsigned int i = 0; i < 4; i++)
  {
    if (m_bBoundaryCrease[i])
    {
      //if ( m_bDartCrease[i] )
      //  return ON_SUBD_RETURN_ERROR(false);
      count++;
    }
  }
  if (count != m_boundary_crease_count)
    return ON_SUBD_RETURN_ERROR(false);

  //count = 0;
  //for (unsigned int i = 0; i < 4; i++)
  //{
  //  if (m_bDartCrease[i])
  //    count++;
  //}
  //if (count != m_dart_crease_count)
  //  return ON_SUBD_RETURN_ERROR(false);

  const ON_SubDFace* quad_face = m_face_grid[1][1];
  if (nullptr == quad_face || 4 != quad_face->m_edge_count)
    return ON_SUBD_RETURN_ERROR(false);

  int quad_fvi0 = 0;
  int quad_dir = 0;
  {
    for (quad_fvi0 = 0; quad_fvi0 < 4; quad_fvi0++)
    {
      const ON_SubDVertex* v = quad_face->Vertex(quad_fvi0);
      if ( nullptr == v )
        return ON_SUBD_RETURN_ERROR(false);
      if (m_vertex_grid[1][1] == v)
      {
        v = quad_face->Vertex((quad_fvi0+1)%4);
        if ( nullptr == v )
          return ON_SUBD_RETURN_ERROR(false);
        if (m_vertex_grid[2][1] == v)
          quad_dir = 1;
        else if (m_vertex_grid[1][2] == v)
          quad_dir = -1;
        else
          return ON_SUBD_RETURN_ERROR(false);
        break;
      }
    }
    if (0 == quad_dir || quad_fvi0 >= 4)
      return ON_SUBD_RETURN_ERROR(false);
  }

  const ON_2dex face_corner_dex[4] = { { 0, 0 }, { 2, 0 }, { 2, 2 }, { 0, 2 } };
  const ON_2dex face_side_dex[4] = { { 1, 0 }, { 2, 1 }, { 1, 2 }, { 0, 1 } };

  const ON_2dex grid_quad_vertex_dex[4] = { { 1,1 }, { 2,1 }, { 2,2 }, { 1,2 } };
  const ON_2dex grid_corner_dex[4] = { { 0, 0 }, { 3, 0 }, { 3, 3 }, { 0, 3 } };
  const ON_2dex grid_side_dex[4][2] = { {{ 1, 0 }, { 2, 0 }}, {{ 3, 1 }, { 3, 2 }}, {{ 2, 3 }, { 1, 3 }}, {{ 0, 2 }, { 0, 1 }} };

  ON_2dex dex;

  const int fvi_map[4] = {
    quad_fvi0,
    (quad_fvi0+4+quad_dir)%4,
    (quad_fvi0+4+2*quad_dir)%4,
    (quad_fvi0+4+3*quad_dir)%4};

  const ON_SubDVertex* quad_vertex[4] = {
    quad_face->Vertex(fvi_map[0]), 
    quad_face->Vertex(fvi_map[1]),
    quad_face->Vertex(fvi_map[2]),
    quad_face->Vertex(fvi_map[3]) };

  const ON_SubDEdgePtr quad_eptr[4] = {
    quad_face->m_edge4[fvi_map[0]],
    quad_face->m_edge4[fvi_map[1]], 
    quad_face->m_edge4[fvi_map[2]],
    quad_face->m_edge4[fvi_map[3]] };

  const ON_SubDEdge* quad_edge[4] = {
    quad_eptr[0].Edge(),
    quad_eptr[1].Edge(),
    quad_eptr[2].Edge(),
    quad_eptr[3].Edge() };

  for (unsigned int fi = 0; fi < 4; fi++)
  {
    if (nullptr == quad_edge[fi])
      return ON_SUBD_RETURN_ERROR(false);
    if (nullptr == quad_edge[fi]->m_vertex[0])
      return ON_SUBD_RETURN_ERROR(false);
    if (nullptr == quad_edge[fi]->m_vertex[1])
      return ON_SUBD_RETURN_ERROR(false);
    if (quad_edge[fi]->m_face_count < 1)
      return ON_SUBD_RETURN_ERROR(false);
    if (nullptr == quad_vertex[fi])
      return ON_SUBD_RETURN_ERROR(false);
    if (quad_vertex[fi]->m_edge_count < 2 || nullptr == quad_vertex[fi]->m_edges )
      return ON_SUBD_RETURN_ERROR(false);
    if (quad_vertex[fi]->m_face_count < 1 || nullptr == quad_vertex[fi]->m_faces )
      return ON_SUBD_RETURN_ERROR(false);
    dex = grid_quad_vertex_dex[fi];
    if ( quad_vertex[fi] != m_vertex_grid[dex.i][dex.j])
      return ON_SUBD_RETURN_ERROR(false);
    if ( quad_edge[fi] != m_center_edges[fi] )
      return ON_SUBD_RETURN_ERROR(false);
    if ( quad_vertex[(fi+1)%4] != quad_edge[fi]->m_vertex[1-quad_eptr[fi].EdgeDirection()] )
      return ON_SUBD_RETURN_ERROR(false);
  }
  
  const ON_SubDFace* side_face[4] = {};
  const ON_SubDFace* corner_face[4] = {};

  for (unsigned int fi = 0; fi < 4; fi++)
  {
    dex = grid_quad_vertex_dex[fi];
    if (quad_vertex[fi] != m_vertex_grid[dex.i][dex.j])
      return ON_SUBD_RETURN_ERROR(false);

    dex = face_side_dex[fi];
    side_face[fi] = m_face_grid[dex.i][dex.j];
    if ( quad_face == side_face[fi])
      return ON_SUBD_RETURN_ERROR(false);

    if (m_bBoundaryCrease[fi])
    {
      // A tag of ON_SubD::EdgeTag::X is an error here
      if ( false == quad_edge[fi]->IsCrease(false) )
        return ON_SUBD_RETURN_ERROR(false);

      if ( ON_UNSET_UINT_INDEX == quad_edge[fi]->FaceArrayIndex(quad_face) )
        return ON_SUBD_RETURN_ERROR(false);

      if (2 == quad_edge[fi]->m_face_count)
      {
        if ( false == quad_edge[fi]->m_vertex[0]->IsCreaseOrCorner())
          return ON_SUBD_RETURN_ERROR(false);
        if ( false == quad_edge[fi]->m_vertex[1]->IsCreaseOrCorner())
          return ON_SUBD_RETURN_ERROR(false);
      }

      if (nullptr != side_face[fi])
        return ON_SUBD_RETURN_ERROR(false);
    }
    else
    {
      if (nullptr == side_face[fi])
        return ON_SUBD_RETURN_ERROR(false);

      // A tag of ON_SubD::EdgeTag::X is permitted here
      if ( 2 != quad_edge[fi]->m_face_count)
        return ON_SUBD_RETURN_ERROR(false);

      if (quad_edge[fi]->IsCrease(false))
      {
        unsigned int dart_count = 0;
        unsigned int crease_count = 0;
        if ( ON_SubD::VertexTag::Dart == quad_edge[fi]->m_vertex[0]->m_vertex_tag)
          dart_count++;
        else if ( quad_edge[fi]->m_vertex[0]->IsCreaseOrCorner())
          crease_count++;
        else
          return ON_SUBD_RETURN_ERROR(false);

        if ( ON_SubD::VertexTag::Dart == quad_edge[fi]->m_vertex[1]->m_vertex_tag)
          dart_count++;
        else if ( quad_edge[fi]->m_vertex[1]->IsCreaseOrCorner())
          crease_count++;
        else
          return ON_SUBD_RETURN_ERROR(false);

        if ( 0 == dart_count )
          return ON_SUBD_RETURN_ERROR(false);

        if ( 2 != dart_count + crease_count )
          return ON_SUBD_RETURN_ERROR(false);
      }
      else
      {
        if (false == quad_edge[fi]->IsSmooth(true))
          return ON_SUBD_RETURN_ERROR(false);
      }
      const ON_SubDFace* edge_faces[2] = { ON_SUBD_FACE_POINTER(quad_edge[fi]->m_face2[0].m_ptr), ON_SUBD_FACE_POINTER(quad_edge[fi]->m_face2[1].m_ptr) };
      if (quad_face == edge_faces[0])
      {
        if (side_face[fi] != edge_faces[1])
          return ON_SUBD_RETURN_ERROR(false);
      }
      else if (quad_face == edge_faces[1])
      {
        if (side_face[fi] != edge_faces[0])
          return ON_SUBD_RETURN_ERROR(false);
      }
      else
        return ON_SUBD_RETURN_ERROR(false);

      if (nullptr == m_edge_grid[fi][0] || nullptr == m_edge_grid[fi][1])
        return ON_SUBD_RETURN_ERROR(false);

      unsigned int side_fei[3] = {};
      side_fei[1] = side_face[fi]->EdgeArrayIndex(quad_edge[fi]);
      if ( ON_UNSET_UINT_INDEX == side_fei[1] )
        return ON_SUBD_RETURN_ERROR(false);

      const ON_SubDEdgePtr side_face_eptr = side_face[fi]->EdgePtr(side_fei[1]);
      if ( quad_edge[fi] != side_face_eptr.Edge())
        return ON_SUBD_RETURN_ERROR(false);

      unsigned int k = (side_face_eptr.EdgeDirection() == quad_eptr[fi].EdgeDirection()) ? 2 : 0;
      unsigned int side_face_edge_count = side_face[fi]->m_edge_count;
      side_fei[2-k] = (side_fei[1] + side_face_edge_count - 1) % side_face_edge_count;
      side_fei[k] = (side_fei[1] + 1) % side_face_edge_count;
      if ( m_edge_grid[fi][0] != side_face[fi]->Edge(side_fei[0]))
        return ON_SUBD_RETURN_ERROR(false);
      if ( m_edge_grid[fi][1] != side_face[fi]->Edge(side_fei[2]))
        return ON_SUBD_RETURN_ERROR(false);
    }

    dex = face_corner_dex[fi];
    corner_face[fi] = m_face_grid[dex.i][dex.j];
    if ( quad_face == corner_face[fi])
      return ON_SUBD_RETURN_ERROR(false);

    bool bCornerShouldExist 
      = 4 == quad_vertex[fi]->m_edge_count
      && 4 == quad_vertex[fi]->m_face_count
      && quad_vertex[fi]->IsSmoothOrDart()
      && false == m_bBoundaryCrease[fi]
      && false == m_bBoundaryCrease[(fi + 3) % 4];
    if (bCornerShouldExist)
    {
      for (unsigned int vei = 0; vei < 4; vei++)
      {
        const ON_SubDEdge* e = quad_vertex[fi]->Edge(vei);
        if ( nullptr == e)
          return ON_SUBD_RETURN_ERROR(false);
        if (2 == e->m_face_count)
        {
          if (e->IsSmooth(true))
            continue;
          if (e->IsCrease(false) && ON_SubD::VertexTag::Dart == quad_vertex[fi]->m_vertex_tag)
            continue;
        }
        if (false == e->IsCrease(false))
          return ON_SUBD_RETURN_ERROR(false);
        bCornerShouldExist = false;
        break;
      }
    }

    dex = grid_corner_dex[fi];
    if (bCornerShouldExist)
    {
      if (nullptr == corner_face[fi])
        return ON_SUBD_RETURN_ERROR(false);
      if (side_face[fi] == corner_face[fi])
        return ON_SUBD_RETURN_ERROR(false);
      if (ON_UNSET_UINT_INDEX == corner_face[fi]->VertexIndex(quad_vertex[fi]))
        return ON_SUBD_RETURN_ERROR(false);
      const unsigned int corner_face_vi = corner_face[fi]->VertexIndex(quad_vertex[fi]);
      if ( ON_UNSET_UINT_INDEX == corner_face_vi)
        return ON_SUBD_RETURN_ERROR(false);
      if (4 == corner_face[fi]->m_edge_count)
      {
        if (nullptr == m_vertex_grid[dex.i][dex.j])
          return ON_SUBD_RETURN_ERROR(false);
        if ( corner_face[fi]->Vertex((corner_face_vi+2)%4) != m_vertex_grid[dex.i][dex.j] )
          return ON_SUBD_RETURN_ERROR(false);
      }
      else
      {
        if (nullptr != m_vertex_grid[dex.i][dex.j])
          return ON_SUBD_RETURN_ERROR(false);
      }
    }
    //else 
    //{
    //  if (nullptr != corner_face[fi])
    //    return ON_SUBD_RETURN_ERROR(false);
    //  if (nullptr != m_vertex_grid[dex.i][dex.j])
    //    return ON_SUBD_RETURN_ERROR(false);
    //}

    if (false == m_bBoundaryCrease[fi])
    {
      for (unsigned int j = 0; j < 2; j++)
      {
        const ON_SubDVertex* v = quad_vertex[(fi + j) % 4];
        unsigned int k = (v != m_edge_grid[fi][j]->m_vertex[0]) ? 0 : 1;
        if (v != m_edge_grid[fi][j]->m_vertex[1 - k])
          return ON_SUBD_RETURN_ERROR(false);
        dex = grid_side_dex[fi][j];
        if (m_vertex_grid[dex.i][dex.j] != m_edge_grid[fi][j]->m_vertex[k])
          return ON_SUBD_RETURN_ERROR(false);
      }
    }
  }

  return true;
}


void ON_SubDQuadNeighborhood::Clear(
  ON_SubDQuadNeighborhood* subd_quad_nbd,
  bool bRetainFixedSizeHeap
)
{
  if (nullptr != subd_quad_nbd)
  {
    ON_SubD_FixedSizeHeap* fsh = (bRetainFixedSizeHeap) ? subd_quad_nbd->m_fsh : nullptr;
    subd_quad_nbd->Internal_Destroy(true);
    subd_quad_nbd->m_fsh = fsh;
  }
}

ON_SubDQuadNeighborhood::~ON_SubDQuadNeighborhood()
{
  Internal_Destroy(false);
}

void ON_SubDQuadNeighborhood::Internal_Destroy(bool bReinitialize)
{
  if (nullptr != m_fsh)
  {
    if (bReinitialize)
      m_fsh->Reset();
    m_fsh = nullptr;
  }

  if (bReinitialize)
  {
    m_bIsCubicPatch = false;
    m_initial_subdivision_level = 0;
    m_current_subdivision_level = 0;

    m_extraordinary_corner_vertex_count = 0;
    m_bExtraordinaryCornerVertex[0] = false;
    m_bExtraordinaryCornerVertex[1] = false;
    m_bExtraordinaryCornerVertex[2] = false;
    m_bExtraordinaryCornerVertex[3] = false;

    m_exact_quadrant_patch_count = 0;
    m_bExactQuadrantPatch[0] = false;
    m_bExactQuadrantPatch[1] = false;
    m_bExactQuadrantPatch[2] = false;
    m_bExactQuadrantPatch[3] = false;

    m_boundary_crease_count = 0;
    m_bBoundaryCrease[0] = false;
    m_bBoundaryCrease[1] = false;
    m_bBoundaryCrease[2] = false;
    m_bBoundaryCrease[3] = false;

    m_bCenterEdgeLimitPoint[0] = false;
    m_bCenterEdgeLimitPoint[1] = false;
    m_bCenterEdgeLimitPoint[2] = false;
    m_bCenterEdgeLimitPoint[3] = false;

    m_center_edge_limit_point[0] = ON_SubDSectorLimitPoint::Nan;
    m_center_edge_limit_point[1] = ON_SubDSectorLimitPoint::Nan;
    m_center_edge_limit_point[2] = ON_SubDSectorLimitPoint::Nan;
    m_center_edge_limit_point[3] = ON_SubDSectorLimitPoint::Nan;

    for (unsigned int i = 0; i < 4; i++)
    {
      m_vertex_grid[i][0] = nullptr;
      m_vertex_grid[i][1] = nullptr;
      m_vertex_grid[i][2] = nullptr;
      m_vertex_grid[i][3] = nullptr;

      m_edge_grid[i][0] = nullptr;
      m_edge_grid[i][1] = nullptr;

      if (i < 3)
      {
        m_face_grid[i][0] = nullptr;
        m_face_grid[i][1] = nullptr;
        m_face_grid[i][2] = nullptr;
      }
    }

    double* dst = &m_srf_cv1[0][0][0];
    double* dst1 = dst + sizeof(m_srf_cv1) / sizeof(m_srf_cv1[0][0][0]);
    while (dst < dst1)
      *dst++ = ON_UNSET_VALUE;
  }
}

static bool IsOrdinarySmoothQuadCornerVertex(
  const ON_SubDVertex* corner_vertex
  )
{
  if ( nullptr == corner_vertex)
    return ON_SUBD_RETURN_ERROR(false);

  if ( 4 != corner_vertex->m_face_count)
    return false;

  if ( 4 != corner_vertex->m_edge_count)
    return false;

  if ( false == corner_vertex->IsSmooth() )
    return false;

  for (unsigned vfi = 0; vfi < 4; vfi++)
  {
    const ON_SubDFace* face = corner_vertex->m_faces[vfi];
    if ( nullptr == face )
      return ON_SUBD_RETURN_ERROR(false);
    if ( 4 != face->m_edge_count )
      return false;
  }

  for (unsigned vei = 0; vei < 4; vei++)
  {
    const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(corner_vertex->m_edges[vei].m_ptr);
    if ( nullptr == e )
      return ON_SUBD_RETURN_ERROR(false);    
    if ( false == e->IsSmooth(false) )
      return false;
    if ( 2 != e->m_face_count)
      return ON_SUBD_RETURN_ERROR(false);

    const unsigned int outer_vertex_index = (corner_vertex == e->m_vertex[0]) ? 1U : 0U;
    const ON_SubDVertex* outer_vertex = e->m_vertex[outer_vertex_index];
    if ( nullptr == outer_vertex || corner_vertex == outer_vertex )
      return ON_SUBD_RETURN_ERROR(false);
    if ( outer_vertex->IsSmooth() )
      continue;
    const double sector_coefficient = e->m_sector_coefficient[outer_vertex_index];
    if ( !(0.5 == sector_coefficient) )
      return false;
  }
  return true;
}

bool ON_SubDQuadNeighborhood::VertexGridIsExactCubicPatch(
  const ON_2dex min_face_grid_dex,
  const ON_2dex max_face_grid_dex,
  unsigned int boundary_corner_index
  ) const
{
  // Returns value for  m_bIsCubicPatch

  if (m_extraordinary_corner_vertex_count > 0)
    return false;
  if (m_exact_quadrant_patch_count < 4)
    return false;
  if (m_boundary_crease_count > 2)
    return false;

  int fcount_check;

  if (0 == m_boundary_crease_count)
    fcount_check = 9;
  else if (1 == m_boundary_crease_count)
    fcount_check = 6;
  else if (2 == m_boundary_crease_count)
  {
    if ( boundary_corner_index >= 4 )
      return false;
    fcount_check = 4;
  }
  else
  {
    ON_SUBD_ERROR("Bug in this code.");
    return false;
  }

  for (int i = min_face_grid_dex.i; i < max_face_grid_dex.i; i++)
  {
    for (int j = min_face_grid_dex.j; j < max_face_grid_dex.j; j++)
    {
      const ON_SubDFace* f = m_face_grid[i][j];
      if (nullptr == f || 4 != f->m_edge_count)
      {
        ON_SUBD_ERROR("Bug in this code.");
        return false;
      }
      fcount_check--;
    }
  }

  if (0 != fcount_check)
  {
    ON_SUBD_ERROR("Bug in this code.");
    return false;
  }

  // For the m_vertex_grid[][] to be used as an exact cubic bispan,
  // the outer vertices need to be checked.
  const ON_2dex min_vtx_grid_dex = min_face_grid_dex;
  const ON_2dex max_vtx_grid_dex = { max_face_grid_dex.i + 1, max_face_grid_dex.j + 1 };
  ON_2dex min_smooth_vtx_grid_dex = min_vtx_grid_dex;
  ON_2dex max_smooth_vtx_grid_dex = max_vtx_grid_dex;
  ON_2dex dex;
  if (m_boundary_crease_count > 0)
  {
    ON_2dex crease_vtx_dex[8];
    memset(crease_vtx_dex, 0, sizeof(crease_vtx_dex));
    unsigned int crease_vtx_count = 0;
    if (m_bBoundaryCrease[0])
    {
      dex.j = 1;
      for (dex.i = min_vtx_grid_dex.i; dex.i < max_vtx_grid_dex.i && crease_vtx_count < 8; dex.i++)
        crease_vtx_dex[crease_vtx_count++] = dex;
      min_smooth_vtx_grid_dex.j++;
    }
    if (m_bBoundaryCrease[1])
    {
      dex.i = 2;
      for (dex.j = min_vtx_grid_dex.j; dex.j < max_vtx_grid_dex.j && crease_vtx_count < 8; dex.j++)
        crease_vtx_dex[crease_vtx_count++] = dex;
      max_smooth_vtx_grid_dex.i--;
    }
    if (m_bBoundaryCrease[2])
    {
      dex.j = 2;
      for (dex.i = min_vtx_grid_dex.i; dex.i < max_vtx_grid_dex.i && crease_vtx_count < 8; dex.i++)
        crease_vtx_dex[crease_vtx_count++] = dex;
      max_smooth_vtx_grid_dex.j--;
    }
    if (m_bBoundaryCrease[3])
    {
      dex.i = 1;
      for (dex.j = min_vtx_grid_dex.j; dex.j < max_vtx_grid_dex.j && crease_vtx_count < 8; dex.j++)
        crease_vtx_dex[crease_vtx_count++] = dex;
      min_smooth_vtx_grid_dex.i++;
    }

    switch (m_boundary_crease_count)
    {
    case 1:
      if (4 != crease_vtx_count)
      {
        ON_SUBD_ERROR("Invalid input or a bug above.");
        return false;
      }
      break;

    case 2:
      if (6 != crease_vtx_count)
      {
        ON_SUBD_ERROR("Invalid input or a bug above.");
        return false;
      }
      break;

    default:
      ON_SUBD_ERROR("Invalid input or a bug above.");
      return false;
    }

    for (unsigned int i = 0; i < crease_vtx_count; i++)
    {
      dex = crease_vtx_dex[i];
      const ON_SubDVertex* vertex = m_vertex_grid[dex.i][dex.j];
      if (nullptr == vertex || false == vertex->IsCrease())
      {
        return false;
      }
    }
  }

  for (dex.i = min_smooth_vtx_grid_dex.i; dex.i < max_smooth_vtx_grid_dex.i; dex.i++ )
  {
    for (dex.j = min_smooth_vtx_grid_dex.j; dex.j < max_smooth_vtx_grid_dex.j; dex.j++)
    {
      const ON_SubDVertex* vertex = m_vertex_grid[dex.i][dex.j];
      if (IsOrdinarySmoothQuadCornerVertex(vertex))
        continue;
      return false;
    }
  }

  return true;
}

void ON_SubDQuadNeighborhood::SetPatchStatus(
  const unsigned int fvi0
  )
{
  m_bIsCubicPatch = false;
  const unsigned int delta_subdivision_level 
    = (m_current_subdivision_level > m_initial_subdivision_level)
    ? ((unsigned int)(m_current_subdivision_level - m_initial_subdivision_level))
    : 0U;

  ON_2dex min_face_grid_dex = { 0, 0 };
  ON_2dex max_face_grid_dex = { 3, 3 };

  m_boundary_crease_count = 0;
  if (m_bBoundaryCrease[0])
  {
    m_boundary_crease_count++;
    min_face_grid_dex.j++;
  }
  if (m_bBoundaryCrease[1])
  {
    m_boundary_crease_count++;
    max_face_grid_dex.i--;
  }
  if (m_bBoundaryCrease[2])
  {
    m_boundary_crease_count++;
    max_face_grid_dex.j--;
  }
  if (m_bBoundaryCrease[3])
  {
    m_boundary_crease_count++;
    min_face_grid_dex.i++;
  }


  const unsigned int fvi1 = (fvi0+1)%4;
  const unsigned int fvi2 = (fvi0+2)%4;
  const unsigned int fvi3 = (fvi0+3)%4;

  bool bCenterEdgeIsSmooth[4] = {};
  bCenterEdgeIsSmooth[fvi0] = m_center_edges[fvi0]->IsSmooth(false);
  bCenterEdgeIsSmooth[fvi1] = (0 == delta_subdivision_level) ? m_center_edges[fvi1]->IsSmooth(false) : true;
  bCenterEdgeIsSmooth[fvi2] = (0 == delta_subdivision_level) ? m_center_edges[fvi2]->IsSmooth(false) : true;
  bCenterEdgeIsSmooth[fvi3] = m_center_edges[fvi3]->IsSmooth(false);

  bool bCenterEdgeIsCrease[4] = {};
  bCenterEdgeIsCrease[fvi0] = bCenterEdgeIsSmooth[fvi0] ? false : m_center_edges[fvi0]->IsCrease(false);
  bCenterEdgeIsCrease[fvi1] = (0 == delta_subdivision_level) ? m_center_edges[fvi1]->IsCrease(false) : false;
  bCenterEdgeIsCrease[fvi2] = (0 == delta_subdivision_level) ? m_center_edges[fvi2]->IsCrease(false) : false;
  bCenterEdgeIsCrease[fvi3] = bCenterEdgeIsSmooth[fvi3] ? false : m_center_edges[fvi3]->IsCrease(false);

  bool bEdgeTagX = false;
  for (unsigned int i = 0; i < 4; i++)
  {
    if (
      nullptr != m_center_edges[i]
      && ON_SubD::EdgeTag::X != m_center_edges[i]->m_edge_tag
      && (bCenterEdgeIsSmooth[i] != bCenterEdgeIsCrease[i])
      )
    {
      continue;
    }
    bEdgeTagX = true;
    break;
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Set 
  //  m_bExtraordinaryCornerVertex[], m_extraordinary_corner_vertex_count
  //  m_bExactQuadrantPatch[], and m_exact_quadrant_patch_count
  // 
  unsigned int boundary_crease_corner_index = 86U;
  unsigned int boundary_corner_corner_index = 86U;

  bool bExtraordinaryCornerVertex[4] = {};
  bExtraordinaryCornerVertex[fvi0] = true;
  bExtraordinaryCornerVertex[fvi1] = (0 == delta_subdivision_level || bEdgeTagX);
  bExtraordinaryCornerVertex[fvi2] = bExtraordinaryCornerVertex[fvi1];
  bExtraordinaryCornerVertex[fvi3] = bExtraordinaryCornerVertex[fvi1];

  if (false == bEdgeTagX)
  {
    const ON_SubDVertex* quad_vertex[4] = {
      m_vertex_grid[1][1],
      m_vertex_grid[2][1],
      m_vertex_grid[2][2],
      m_vertex_grid[1][2]
    };

    const bool bQuadVertexIsSmoothOrCrease[4] = 
    {
      quad_vertex[0]->IsSmoothOrCrease(),
      quad_vertex[1]->IsSmoothOrCrease(),
      quad_vertex[2]->IsSmoothOrCrease(),
      quad_vertex[3]->IsSmoothOrCrease()
    };

    for (unsigned int corner_index = 0; corner_index < 4; corner_index++)
    {
      if (false == bExtraordinaryCornerVertex[corner_index])
        continue;

      if (false == bQuadVertexIsSmoothOrCrease[corner_index] && false == quad_vertex[corner_index]->IsCorner())
        continue;
      if (false == bQuadVertexIsSmoothOrCrease[(corner_index+1)%4])
        continue;
      if (false == bQuadVertexIsSmoothOrCrease[(corner_index+3)%4])
        continue;

      if (bCenterEdgeIsSmooth[corner_index] && bCenterEdgeIsSmooth[(corner_index + 3) % 4])
      {
        if (IsOrdinarySmoothQuadCornerVertex(quad_vertex[corner_index]))
          bExtraordinaryCornerVertex[corner_index] = false;
        continue;
      }

      if (bCenterEdgeIsCrease[corner_index] && bCenterEdgeIsSmooth[(corner_index + 3) % 4])
      {
        if (quad_vertex[corner_index]->IsCrease())
        {
          const ON_SubDEdge* e = m_edge_grid[(corner_index + 3) % 4][1];
          if (nullptr != e && e->IsCrease(false))
          {
            const ON_SubDFace* f = SideFace((corner_index + 3) % 4);
            if (nullptr != f && 4 == f->m_edge_count)
              bExtraordinaryCornerVertex[corner_index] = false;
          }
        }
        continue;
      }

      if (bCenterEdgeIsSmooth[corner_index] && bCenterEdgeIsCrease[(corner_index + 3) % 4])
      {
        if (quad_vertex[corner_index]->IsCrease())
        {
          const ON_SubDEdge* e = m_edge_grid[corner_index][0];
          if (nullptr != e && e->IsCrease(false))
          {
            const ON_SubDFace* f = SideFace(corner_index);
            if (nullptr != f && 4 == f->m_edge_count)
              bExtraordinaryCornerVertex[corner_index] = false;
          }
        }
        continue;
      }

      if (bCenterEdgeIsCrease[corner_index] && bCenterEdgeIsCrease[(corner_index + 3) % 4])
      {
        if (
          (quad_vertex[corner_index]->IsCrease() || quad_vertex[corner_index]->IsCorner())
          && quad_vertex[(corner_index + 1) % 4]->IsCrease()
          && quad_vertex[(corner_index + 3) % 4]->IsCrease()
          && IsOrdinarySmoothQuadCornerVertex(quad_vertex[(corner_index + 2) % 4])
          )
        {
          const ON_SubDEdge* e1 = m_edge_grid[(corner_index + 1) % 4][0];
          const ON_SubDEdge* e2 = m_edge_grid[(corner_index + 2) % 4][1];
          if (nullptr != e1 && nullptr != e2 && e1->IsCrease(false) && e2->IsCrease(false))
          {
            if (delta_subdivision_level > 0)
            {
              if (quad_vertex[corner_index]->IsCrease())
              {
                boundary_crease_corner_index = corner_index;
                bExtraordinaryCornerVertex[corner_index] = false;
              }
              else if ( quad_vertex[corner_index]->IsCorner() )
              {
                boundary_corner_corner_index = corner_index;
              }
            }
          }
        }
        continue;
      }

    }
  }

  m_extraordinary_corner_vertex_count = 0;
  for (unsigned int corner_index = 0; corner_index < 4; corner_index++)
  {
    m_bExtraordinaryCornerVertex[corner_index] = bExtraordinaryCornerVertex[corner_index];
    if (bExtraordinaryCornerVertex[corner_index])
    {
      m_extraordinary_corner_vertex_count++;
    }
  }

  m_exact_quadrant_patch_count = 0;
  for (unsigned int corner_index = 0; corner_index < 4; corner_index++)
  {
    m_bExactQuadrantPatch[corner_index] = false;
    if (m_boundary_crease_count > 2)
      continue;
    if (2 == m_boundary_crease_count)
    {
      if (boundary_crease_corner_index >= 4)
      {
        if ( boundary_corner_corner_index >= 4 || ((boundary_corner_corner_index+2)%4) != corner_index)
          continue;
      }
    }
    if (bExtraordinaryCornerVertex[corner_index])
      continue;
    if (bExtraordinaryCornerVertex[(corner_index + 1) % 4])
      continue;
    if (bExtraordinaryCornerVertex[(corner_index + 3) % 4])
      continue;
    m_bExactQuadrantPatch[corner_index] = true;
    m_exact_quadrant_patch_count++;
  }

  // Set m_bIsCubicPatch
  m_bIsCubicPatch = VertexGridIsExactCubicPatch(
    min_face_grid_dex,
    max_face_grid_dex,
    boundary_crease_corner_index
    );
}


bool ON_SubDQuadNeighborhood::Set(
  const ON_SubDFace* center_quad_face
  )
{
  ON_SubDQuadNeighborhood::Clear(this, false);

  if (nullptr == center_quad_face)
    return true;

  if (4 != center_quad_face->m_edge_count)
    return ON_SUBD_RETURN_ERROR(false);

  const ON_SubDVertex* qf_vertex[4];
  bool bIsDartVertex[4];
  bool bIsCreaseEdge[4];
  for (unsigned int qfei = 0; qfei < 4; qfei++)
  {
    const ON__UINT_PTR eptr = center_quad_face->m_edge4[qfei].m_ptr;
    const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(eptr);
    if (nullptr == edge)
      return ON_SUBD_RETURN_ERROR(false);
    if (nullptr == edge->m_vertex[0])
      return ON_SUBD_RETURN_ERROR(false);
    if (nullptr == edge->m_vertex[1])
      return ON_SUBD_RETURN_ERROR(false);

    bIsCreaseEdge[qfei] = edge->IsCrease(false);

    m_center_edges[qfei] = edge;

    qf_vertex[qfei] = edge->m_vertex[ON_SUBD_EDGE_DIRECTION(eptr)];
    
    bIsDartVertex[qfei] = qf_vertex[qfei]->IsDart();
  }

  for (unsigned int qfei = 0; qfei < 4; qfei++)
  {
    ON__UINT_PTR ev1 = 1-ON_SUBD_EDGE_DIRECTION(center_quad_face->m_edge4[qfei].m_ptr);
    if ( qf_vertex[(qfei+1)%4] != m_center_edges[qfei]->m_vertex[ev1] )
      return ON_SUBD_RETURN_ERROR(false);
  }
  
  const ON_SubDFace* qf_nbr_face[4] = {};
  for (unsigned int qfei = 0; qfei < 4; qfei++)
  {
    if (bIsCreaseEdge[qfei] && false == bIsDartVertex[qfei] && false == bIsDartVertex[(qfei+1)%4])
    {
      m_bBoundaryCrease[qfei] = true;
      m_boundary_crease_count++;
      continue;
    }
    qf_nbr_face[qfei] = m_center_edges[qfei]->NeighborFace(center_quad_face, false);
  }

  ON_SubDSectorIterator sit0, sit1;

  m_face_grid[1][1] = center_quad_face;
  m_face_grid[1][0] = qf_nbr_face[0];
  m_face_grid[2][1] = qf_nbr_face[1];
  m_face_grid[1][2] = qf_nbr_face[2];
  m_face_grid[0][1] = qf_nbr_face[3];

  m_vertex_grid[1][1] = qf_vertex[0];
  m_vertex_grid[2][1] = qf_vertex[1];
  m_vertex_grid[2][2] = qf_vertex[2];
  m_vertex_grid[1][2] = qf_vertex[3];

  const ON_SubDFace* corner_faces[4] = {};
  const ON_SubDVertex* outer_vertex[12] = {};

  for (unsigned int qfei = 0; qfei < 4; qfei++)
  {
    const unsigned int qfei3 = (qfei+3)%4;

    if ( nullptr == qf_nbr_face[qfei] && nullptr == qf_nbr_face[qfei3])
      continue;

    if (nullptr == sit0.Initialize(center_quad_face, 0, qfei))
      return ON_SUBD_RETURN_ERROR(false);

    if (qf_vertex[qfei] != sit0.CenterVertex())
      return ON_SUBD_RETURN_ERROR(false);

    sit1 = sit0;

    const bool b4EdgedCorner = (4 == qf_vertex[qfei]->m_edge_count && qf_vertex[qfei]->m_face_count >= 3);
    const ON_SubDEdge* e[2] = {};
    const ON_SubDVertex* v[2] = {};
    ON__UINT_PTR eptr;

    for (;;)
    {
      if (nullptr != qf_nbr_face[qfei])
      {
        if (qf_nbr_face[qfei] != sit0.PrevFace(false))
          return ON_SUBD_RETURN_ERROR(false);
        eptr = sit0.CurrentEdgePtr(0).m_ptr;
        e[0] = ON_SUBD_EDGE_POINTER(eptr);
        if (nullptr == e[0])
          return ON_SUBD_RETURN_ERROR(false);
        v[0] = e[0]->m_vertex[1 - ON_SUBD_EDGE_DIRECTION(eptr)];
        if (nullptr == v[0])
          return ON_SUBD_RETURN_ERROR(false);
        
        if (b4EdgedCorner)
        {
          corner_faces[qfei] = sit0.PrevFace(false == bIsDartVertex[qfei]);
          if (nullptr != corner_faces[qfei])
          {
            eptr = sit0.CurrentEdgePtr(0).m_ptr;
            e[1] = ON_SUBD_EDGE_POINTER(eptr);
            if (nullptr == e[1])
              return ON_SUBD_RETURN_ERROR(false);
            v[1] = e[1]->m_vertex[1 - ON_SUBD_EDGE_DIRECTION(eptr)];
            if (nullptr == v[1])
              return ON_SUBD_RETURN_ERROR(false);
            break;
          }
        }
      }

      if (nullptr == e[1] && nullptr != qf_nbr_face[qfei3])
      {
        if (qf_nbr_face[qfei3] != sit1.NextFace(false))
          return ON_SUBD_RETURN_ERROR(false);
        eptr = sit1.CurrentEdgePtr(1).m_ptr;
        e[1] = ON_SUBD_EDGE_POINTER(eptr);
        if (nullptr == e[1])
          return ON_SUBD_RETURN_ERROR(false);
        v[1] = e[1]->m_vertex[1 - ON_SUBD_EDGE_DIRECTION(eptr)];
        if (nullptr == v[1])
          return ON_SUBD_RETURN_ERROR(false);
        if (b4EdgedCorner && nullptr == corner_faces[qfei])
        {
          corner_faces[qfei] = sit1.NextFace(false == bIsDartVertex[qfei3]);
          if (nullptr != corner_faces[qfei] && nullptr == e[0] )
          {
            eptr = sit1.CurrentEdgePtr(1).m_ptr;
            e[0] = ON_SUBD_EDGE_POINTER(eptr);
            if (nullptr == e[0])
              return ON_SUBD_RETURN_ERROR(false);
            v[0] = e[0]->m_vertex[1 - ON_SUBD_EDGE_DIRECTION(eptr)];
            if (nullptr == v[0])
              return ON_SUBD_RETURN_ERROR(false);
            break;
          }
        }
      }
      break;
    }

    if ( nullptr != corner_faces[qfei] )
      outer_vertex[3 * qfei] = corner_faces[qfei]->QuadOppositeVertex(qf_vertex[qfei]);

    if (nullptr != e[0])
    {
      m_edge_grid[qfei][0] = e[0];
      outer_vertex[3 * qfei + 1] = v[0];
    }

    if (nullptr != e[1])
    {
      m_edge_grid[qfei3][1] = e[1];
      outer_vertex[(3 * qfei + 11) % 12] = v[1];
    }   
  }

  m_face_grid[0][0] = corner_faces[0]; // lower left corner
  m_face_grid[2][0] = corner_faces[1]; // lower right corner
  m_face_grid[2][2] = corner_faces[2]; // upper right corner
  m_face_grid[0][2] = corner_faces[3]; // upper left corner

  m_vertex_grid[0][0] = outer_vertex[0]; // lower left corner
  m_vertex_grid[1][0] = outer_vertex[1];
  m_vertex_grid[2][0] = outer_vertex[2];
  
  m_vertex_grid[3][0] = outer_vertex[3]; // lower right corner
  m_vertex_grid[3][1] = outer_vertex[4];
  m_vertex_grid[3][2] = outer_vertex[5];

  m_vertex_grid[3][3] = outer_vertex[6]; // upper right corner
  m_vertex_grid[2][3] = outer_vertex[7];
  m_vertex_grid[1][3] = outer_vertex[8];
  
  m_vertex_grid[0][3] = outer_vertex[9]; // upper left corner
  m_vertex_grid[0][2] = outer_vertex[10];
  m_vertex_grid[0][1] = outer_vertex[11];

  m_initial_subdivision_level = 0;
  m_current_subdivision_level = 0;
  
  SetPatchStatus(0);
  
#if defined(ON_DEBUG)
  IsValid(); // will trigger a break if "this" is not valid
#endif
  
  return true;
}

const ON_SubDFace* ON_SubDQuadNeighborhood::CenterQuad() const
{
  return m_face_grid[1][1];
}

const ON_SubDVertex* ON_SubDQuadNeighborhood::CenterVertex(
  int vi
  ) const
{
  if (0==vi)
    return m_vertex_grid[1][1];
  if (1==vi)
    return m_vertex_grid[2][1];
  if (2==vi)
    return m_vertex_grid[2][2];
  if (3==vi)
    return m_vertex_grid[1][2];
  return ON_SUBD_RETURN_ERROR(nullptr);
}

const ON_2dex ON_SubDQuadNeighborhood::CenterVertexDex(
  int vi
)
{
  if (0 == vi)
    return ON_2dex(1, 1);
  if (1==vi)
    return ON_2dex(2, 1);
  if (2==vi)
    return ON_2dex(2, 2);
  if (3==vi)
    return ON_2dex(1, 2);
  return ON_2dex(ON_UNSET_INT_INDEX,ON_UNSET_INT_INDEX);
}


const ON_SubDFace* ON_SubDQuadNeighborhood::SideFace(
  unsigned int fei
  ) const
{
  ON_2dex dex = ON_SubDQuadNeighborhood::GridDex(3,fei,1,0);
  return m_face_grid[dex.i][dex.j];
}

const ON_SubDFace* ON_SubDQuadNeighborhood::CornerFace(
  unsigned int fvi
  ) const
{
  ON_2dex dex = ON_SubDQuadNeighborhood::GridDex(3,fvi,0,0);
  return m_face_grid[dex.i][dex.j];
}



bool ON_SubDQuadNeighborhood::GetLimitSurfaceCV(
  //double srf_cv[4][4][3]
  double* srf_cv,
  unsigned int srf_cv_grid_size // 4 or 5
  ) const
{
  if (nullptr == m_face_grid[1][1] || false == m_bIsCubicPatch)
    return ON_SUBD_RETURN_ERROR(false);

  const double* srcP;
  double *dstP;

  // Get the central quad face corners
  int Pcount = 0;
  int i0, i1, j0, j1;
  if (m_boundary_crease_count > 0)
  {
    if (m_boundary_crease_count >1)
      return ON_SUBD_RETURN_ERROR(false);

    if (m_bBoundaryCrease[0])
    {
      j0 = j1 = 0;
      i0 = 0;
      i1 = 3;
    }
    else if (m_bBoundaryCrease[1])
    {
      i0 = i1 = 3;
      j0 = 0;
      j1 = 3;
    }
    else if (m_bBoundaryCrease[2])
    {
      j0 = j1 = 3;
      i0 = 0;
      i1 = 3;
    }
    else if (m_bBoundaryCrease[3])
    {
      i0 = i1 = 0;
      j0 = 0;
      j1 = 3;
    }
    else
      return ON_SUBD_RETURN_ERROR(false);
  }
  else
  {
    i0 = -1;
    i1 = -1;
    j0 = -1;
    j1 = -1;
  }

  for (int i = 0; i < 4; i++)
  {
    for (int j = 0; j < 4; j++)
    {
      if (i >= i0 && i <= i1 && j >= j0 && j <= j1)
        continue;
      if (nullptr == m_vertex_grid[i][j])
        return ON_SUBD_RETURN_ERROR(false);
      //dstP = srf_cv[i][j];
      dstP = srf_cv + 3*(i*srf_cv_grid_size+j);
      srcP = m_vertex_grid[i][j]->m_P;
      *dstP++ = *srcP++; *dstP++ = *srcP++; *dstP = *srcP;
      Pcount++;
    }
  }

  if ( 16 == Pcount)
    return true;

  if (12 == Pcount)
  {
    // crease case
    const int di = (i0 == i1) ? (0 == i0 ? 1 : -1) : 0;
    const int dj = (j0 == j1) ? (0 == j0 ? 1 : -1) : 0;
    i1++;
    j1++;
    for (int i = i0; i < i1; i++)
    {
      for (int j = j0; j < j1; j++)
      {
        const ON_SubDVertex* v[2] = { m_vertex_grid[i + di][j + dj], m_vertex_grid[i + 2 * di][j + 2 * dj] };
        if (nullptr == v[0] || nullptr == v[1])
          return ON_SUBD_RETURN_ERROR(false);
        if (ON_SubD::VertexTag::Crease != v[0]->m_vertex_tag)
          return ON_SUBD_RETURN_ERROR(false);
        //dstP = srf_cv[i][j];
        dstP = srf_cv + 3*(i*srf_cv_grid_size+j);
        dstP[0] = 2.0 * v[0]->m_P[0] - v[1]->m_P[0];
        dstP[1] = 2.0 * v[0]->m_P[1] - v[1]->m_P[1];
        dstP[2] = 2.0 * v[0]->m_P[2] - v[1]->m_P[2];
      }
    }
    return true;
  }


  return ON_SUBD_RETURN_ERROR(false);
}

bool ON_SubDQuadNeighborhood::GetSubdivisionPoint(
  const ON_SubDVertex* vertex,
  double subdivision_point[3]
  )
{
  if (nullptr == subdivision_point)
    return ON_SUBD_RETURN_ERROR(false);
  for (;;)
  {
    if (nullptr==vertex)
      break;
    double Q[3];
    if (!vertex->GetSubdivisionPoint(ON_SubD::SubDType::QuadCatmullClark,true,Q))
      break;
    subdivision_point[0] = Q[0];
    subdivision_point[1] = Q[1];
    subdivision_point[2] = Q[2];
  }
  subdivision_point[0] = ON_UNSET_VALUE;
  subdivision_point[1] = ON_UNSET_VALUE;
  subdivision_point[2] = ON_UNSET_VALUE;
  return ON_SUBD_RETURN_ERROR(false);
}

bool ON_SubDQuadNeighborhood::GetSubdivisionPoint(
  const ON_SubDEdge* edge,
  double subdivision_point[3]
  )
{
  if (nullptr == subdivision_point)
    return ON_SUBD_RETURN_ERROR(false);
  for (;;)
  {
    if (nullptr==edge)
      break;
    double Q[3];
    if (!edge->GetSubdivisionPoint(ON_SubD::SubDType::QuadCatmullClark,true,Q))
      break;
    subdivision_point[0] = Q[0];
    subdivision_point[1] = Q[1];
    subdivision_point[2] = Q[2];
  }
  subdivision_point[0] = ON_UNSET_VALUE;
  subdivision_point[1] = ON_UNSET_VALUE;
  subdivision_point[2] = ON_UNSET_VALUE;
  return ON_SUBD_RETURN_ERROR(false);
}


bool ON_SubDQuadNeighborhood::GetSubdivisionPoint(
  const ON_SubDFace* face,
  double subdivision_point[3]
  )
{
  if (nullptr == subdivision_point)
    return ON_SUBD_RETURN_ERROR(false);
  for (;;)
  {
    if (nullptr==face)
      break;
    double Q[3];
    if (!face->GetSubdivisionPoint(ON_SubD::SubDType::QuadCatmullClark,true,Q))
      break;
    subdivision_point[0] = Q[0];
    subdivision_point[1] = Q[1];
    subdivision_point[2] = Q[2];
  }
  subdivision_point[0] = ON_UNSET_VALUE;
  subdivision_point[1] = ON_UNSET_VALUE;
  subdivision_point[2] = ON_UNSET_VALUE;
  return ON_SUBD_RETURN_ERROR(false);
}

unsigned int ON_SubDQuadNeighborhood::SetLimitSubSurfaceExactCVs(
  unsigned int quadrant_index
  )
{
  if (nullptr == m_face_grid[1][1] || quadrant_index > 4)
    return ON_SUBD_RETURN_ERROR(0);

  const bool bUseSavedSubdivisionPoint = true;

  ON_2dex dex;
  ON_2dex deltadex;
  const ON_SubDFace* face;
  unsigned int i;
  double Q[3][3];
  double* P1;

  if (!ON_IsValid(m_srf_cv1[2][2][0]))
  {
    // all sub surfaces require inner 3x3 grid of subdivision points
    face = m_face_grid[1][1];
    if (4 != face->m_edge_count)
      return ON_SUBD_RETURN_ERROR(0);

    // The value of m_srf_cv1[2][2][0] is used to determine when the inner 3x3 grid is set, 
    // so its value must be set after the 8 cvs around it are successfully set.
    // The value 
    //   Q[2] = face subd point 
    // is calculated here because if face->GetSubdivisionPoint() fails,
    // nothing below should succeed. The value of Q[2] is assigned to m_srf_cv1[2][2]
    // after the 8 ring points are successfully calculated.
    if (!face->GetSubdivisionPoint(ON_SubD::SubDType::QuadCatmullClark, bUseSavedSubdivisionPoint, Q[2]))
      return ON_SUBD_RETURN_ERROR(0);

    // faces_vertices[] face, in counterclockwise order.  
    // m_center_edges[] are the 4 edges of face in counterclockwise order with 
    // m_center_edges[0] connecting face_vertices[0] and face_vertices[1].
    // However, it may be that face->Vertex(0) != face_vertices[0].
    const ON_SubDVertex* faces_vertices[4] = { m_vertex_grid[1][1], m_vertex_grid[2][1], m_vertex_grid[2][2], m_vertex_grid[1][2] };

    const ON_2dex srf_cv_dex[8] = { { 1, 1 }, { 2, 1 }, { 3, 1 }, { 3, 2 }, { 3, 3 }, { 2, 3 }, { 1, 3 }, { 1, 2 } };

    for (unsigned int fei = 0; fei < 4; fei++)
    {
      if (nullptr == faces_vertices[fei])
        return ON_SUBD_RETURN_ERROR(0);
      if (!faces_vertices[fei]->GetSubdivisionPoint(ON_SubD::SubDType::QuadCatmullClark, bUseSavedSubdivisionPoint, Q[0]))
        return ON_SUBD_RETURN_ERROR(0);
      if (nullptr == m_center_edges[fei])
        return ON_SUBD_RETURN_ERROR(0);
      if (!m_center_edges[fei]->GetSubdivisionPoint(ON_SubD::SubDType::QuadCatmullClark, bUseSavedSubdivisionPoint, Q[1]))
        return ON_SUBD_RETURN_ERROR(0);

      dex = srf_cv_dex[2 * fei];
      P1 = m_srf_cv1[dex.i][dex.j];
      P1[0] = Q[0][0]; P1[1] = Q[0][1]; P1[2] = Q[0][2];

      dex = srf_cv_dex[2 * fei + 1];
      P1 = m_srf_cv1[dex.i][dex.j];
      P1[0] = Q[1][0]; P1[1] = Q[1][1]; P1[2] = Q[1][2];
    }

    // m_srf_cv1[2][2][0] is used to determine when the inner 3x3 grid is set, 
    // so its value must be set after the 8 cvs around it are successfully set.
    P1 = m_srf_cv1[2][2];
    P1[0] = Q[2][0]; P1[1] = Q[2][1]; P1[2] = Q[2][2];
  }

  const unsigned int fvi_min = (4 == quadrant_index) ? 0 : quadrant_index;
  const unsigned int fvi_max = (4 == quadrant_index) ? 4 : fvi_min+1;
  unsigned int quadrant_count = 0;
  for (unsigned int fvi = fvi_min; fvi < fvi_max; fvi++)
  {
    if (false == m_bExactQuadrantPatch[fvi])
      continue;
    quadrant_count++;
    const unsigned int fvi3 = (fvi + 3) % 4;
    for (unsigned int side_pass = 0; side_pass < 2; side_pass++)
    {
      const unsigned int side_fvi = (0 == side_pass) ? fvi : fvi3;

      dex = ON_SubDQuadNeighborhood::GridDex(5, side_fvi, 1, 0);
      deltadex = ON_SubDQuadNeighborhood::DeltaDex(side_fvi, 1, 0);

      P1 = &m_srf_cv1[dex.i][dex.j][0];
      if ( !ON_IsValid(P1[0]) )
      {
        bool bEvaluateCrease = m_bBoundaryCrease[side_fvi] || m_center_edges[side_fvi]->IsCrease(false);
        if (bEvaluateCrease)
        {
          ON_2dex Adex = ON_SubDQuadNeighborhood::GridDex(5, side_fvi, 1, 1);
          ON_2dex Bdex = ON_SubDQuadNeighborhood::GridDex(5, side_fvi, 1, 2);
          for (i = 0; i < 3; i++)
          {
            const double* A = m_srf_cv1[Adex.i][Adex.j];
            const double* B = m_srf_cv1[Bdex.i][Bdex.j];
            Q[i][0] = 2.0*A[0] - B[0];
            Q[i][1] = 2.0*A[1] - B[1];
            Q[i][2] = 2.0*A[2] - B[2];
            Adex.i += deltadex.i;
            Adex.j += deltadex.j;
            Bdex.i += deltadex.i;
            Bdex.j += deltadex.j;
          }
        }
        else if ( m_center_edges[side_fvi]->IsSmooth(false) )
        {
          const ON_SubDEdge* edge = m_edge_grid[side_fvi][0];
          if (nullptr == edge)
            return ON_SUBD_RETURN_ERROR(0);
          if (!edge->GetSubdivisionPoint(ON_SubD::SubDType::QuadCatmullClark, bUseSavedSubdivisionPoint, Q[0]))
            return ON_SUBD_RETURN_ERROR(0);

          const ON_2dex fdex = ON_SubDQuadNeighborhood::GridDex(3, side_fvi, 1, 0);
          face = m_face_grid[fdex.i][fdex.j];
          if (nullptr == face)
            return ON_SUBD_RETURN_ERROR(0);
          if (!face->GetSubdivisionPoint(ON_SubD::SubDType::QuadCatmullClark, bUseSavedSubdivisionPoint, Q[1]))
            return ON_SUBD_RETURN_ERROR(0);

          edge = m_edge_grid[side_fvi][1];
          if (nullptr == edge)
            return ON_SUBD_RETURN_ERROR(0);
          if (!edge->GetSubdivisionPoint(ON_SubD::SubDType::QuadCatmullClark, bUseSavedSubdivisionPoint, Q[2]))
            return ON_SUBD_RETURN_ERROR(0);
        }

        for (i = 0; i < 3; i++)
        {
          P1 = m_srf_cv1[dex.i][dex.j];
          P1[0] = Q[i][0]; P1[1] = Q[i][1]; P1[2] = Q[i][2];
          dex.i += deltadex.i;
          dex.j += deltadex.j;
        }
      }
    }

    dex = ON_SubDQuadNeighborhood::GridDex(5, fvi, 0, 0);
    P1 = &m_srf_cv1[dex.i][dex.j][0];
    if (!ON_IsValid(P1[0]))
    {
      if (m_bBoundaryCrease[fvi] && m_bBoundaryCrease[fvi3])
      {
        dex = ON_SubDQuadNeighborhood::GridDex(5, fvi, 2, 1);
        Q[0][0] = m_srf_cv1[dex.i][dex.j][0];
        Q[0][1] = m_srf_cv1[dex.i][dex.j][1];
        Q[0][2] = m_srf_cv1[dex.i][dex.j][2];
        dex = ON_SubDQuadNeighborhood::GridDex(5, fvi, 1, 2);
        Q[1][0] = m_srf_cv1[dex.i][dex.j][0];
        Q[1][1] = m_srf_cv1[dex.i][dex.j][1];
        Q[1][2] = m_srf_cv1[dex.i][dex.j][2];
        dex = ON_SubDQuadNeighborhood::GridDex(5, fvi, 2, 2);
        Q[2][0] = m_srf_cv1[dex.i][dex.j][0];
        Q[2][1] = m_srf_cv1[dex.i][dex.j][1];
        Q[2][2] = m_srf_cv1[dex.i][dex.j][2];

        dex = ON_SubDQuadNeighborhood::GridDex(5, fvi, 1, 1);
        //const double c = 4.0;
        //const double b = -2.0;
        const double c = -8.0;
        const double b = 4.0;
        P1[0] = c*m_srf_cv1[dex.i][dex.j][0] + b*(Q[0][0] + Q[1][0]) + Q[2][0];
        P1[1] = c*m_srf_cv1[dex.i][dex.j][1] + b*(Q[0][1] + Q[1][1]) + Q[2][1];
        P1[2] = c*m_srf_cv1[dex.i][dex.j][2] + b*(Q[0][2] + Q[1][2]) + Q[2][2];
      }
      else if (m_bBoundaryCrease[fvi])
      {
        dex = ON_SubDQuadNeighborhood::GridDex(5, fvi, 0, 1);
        Q[0][0] = m_srf_cv1[dex.i][dex.j][0];
        Q[0][1] = m_srf_cv1[dex.i][dex.j][1];
        Q[0][2] = m_srf_cv1[dex.i][dex.j][2];
        dex = ON_SubDQuadNeighborhood::GridDex(5, fvi, 0, 2);
        Q[1][0] = m_srf_cv1[dex.i][dex.j][0];
        Q[1][1] = m_srf_cv1[dex.i][dex.j][1];
        Q[1][2] = m_srf_cv1[dex.i][dex.j][2];

        P1[0] = 2.0*Q[0][0] - Q[1][0];
        P1[1] = 2.0*Q[0][1] - Q[1][1];
        P1[2] = 2.0*Q[0][2] - Q[1][2];
      }
      else if (m_bBoundaryCrease[fvi3])
      {
        dex = ON_SubDQuadNeighborhood::GridDex(5, fvi, 1, 0);
        Q[0][0] = m_srf_cv1[dex.i][dex.j][0];
        Q[0][1] = m_srf_cv1[dex.i][dex.j][1];
        Q[0][2] = m_srf_cv1[dex.i][dex.j][2];
        dex = ON_SubDQuadNeighborhood::GridDex(5, fvi, 2, 0);
        Q[1][0] = m_srf_cv1[dex.i][dex.j][0];
        Q[1][1] = m_srf_cv1[dex.i][dex.j][1];
        Q[1][2] = m_srf_cv1[dex.i][dex.j][2];

        P1[0] = 2.0*Q[0][0] - Q[1][0];
        P1[1] = 2.0*Q[0][1] - Q[1][1];
        P1[2] = 2.0*Q[0][2] - Q[1][2];
      }
      else
      {
        dex = ON_SubDQuadNeighborhood::GridDex(3, fvi, 0, 0);
        const ON_SubDFace* face_ij = m_face_grid[dex.i][dex.j];
        if (nullptr == face_ij)
          return ON_SUBD_RETURN_ERROR(0);
        if (!face_ij->GetSubdivisionPoint(ON_SubD::SubDType::QuadCatmullClark, bUseSavedSubdivisionPoint, Q[0]))
          return ON_SUBD_RETURN_ERROR(0);
        P1[0] = Q[0][0]; P1[1] = Q[0][1]; P1[2] = Q[0][2];
      }
    }
  }

  return quadrant_count;
}

bool ON_SubDQuadNeighborhood::GetLimitSubSurfaceSinglePatchCV(
  unsigned int fvi,
  double srf_cv[4][4][3]
  )
{
  if (fvi >= 4)
    return ON_SUBD_RETURN_ERROR(false);

  if (false == m_bExactQuadrantPatch[fvi])
    return ON_SUBD_RETURN_ERROR(false);

  unsigned int quadrant_count = SetLimitSubSurfaceExactCVs(fvi);
  if ( 1 != quadrant_count )
    return ON_SUBD_RETURN_ERROR(false);

  ON_2dex dex;

  dex.i = 0;
  dex.j = 0;
  if (1 == fvi || 2 == fvi)
    dex.i++;
  if (2 == fvi || 3 == fvi)
    dex.j++;

  double* P1 = srf_cv[0][0];
  for (unsigned int i = 0; i < 4; i++)
  {
    for (unsigned int j = 0; j < 4; j++)
    {
      //double* P1 = srf_cv[i][j];
      const double* src = m_srf_cv1[i+dex.i][j+dex.j];
      *P1++ = *src++;
      *P1++ = *src++;
      *P1++ = *src;
    }
  }

  return true;
}


unsigned int ON_SubDQuadNeighborhood::ExtraordinaryCenterVertexIndex(
  ON_SubD::VertexTag vertex_tag_filter,
  unsigned int minimum_edge_count_filter
) const
{
  for(;;)
  {
    if (1 != m_extraordinary_corner_vertex_count)
      break;
    if (1 != m_exact_quadrant_patch_count)
      break;
    const unsigned int extraordinary_vertex_index
      = m_bExtraordinaryCornerVertex[0] ? 0
      : (m_bExtraordinaryCornerVertex[1] ? 1
        : (m_bExtraordinaryCornerVertex[2] ? 2 : 3));
      if (false == (m_bExtraordinaryCornerVertex[extraordinary_vertex_index]))
        break;
      if (false == (m_bExactQuadrantPatch[(extraordinary_vertex_index + 2) % 4]))
        break;
      const ON_2dex dex = CenterVertexDex(extraordinary_vertex_index);
      if (dex.i < 1 || dex.i > 2 || dex.j < 1 || dex.j > 2)
        break;
      if (nullptr == m_vertex_grid[dex.i][dex.j])
        break;
      if (ON_SubD::VertexTag::Corner != m_vertex_grid[dex.i][dex.j]->m_vertex_tag)
      {
        if (((unsigned int)(m_vertex_grid[dex.i][dex.j]->m_edge_count)) < minimum_edge_count_filter)
          break;
        if (
          ON_SubD::VertexTag::Unset != vertex_tag_filter
          && m_vertex_grid[dex.i][dex.j]->m_vertex_tag != vertex_tag_filter
          )
        {
          break;
        }
      }
      return extraordinary_vertex_index;
  }
  return ON_UNSET_UINT_INDEX;
}


bool ON_SubDQuadNeighborhood::Internal_GetApproximateCV(
  int i,
  int j,
  double unset_cv,
  double approximate_cv[3]
  ) const
{
  approximate_cv[0] = unset_cv;
  approximate_cv[1] = unset_cv;
  approximate_cv[2] = unset_cv;

  const ON_SubDEdge* e = nullptr;
  const ON_SubDFace* f = nullptr;
  if (0 == j)
  {
    switch (i)
    {
    case 0:
      if ( false == m_bExtraordinaryCornerVertex[0] )
        f = m_face_grid[0][0];
      break;
    case 1:
      e = m_edge_grid[0][0];
      break;
    case 2:
      f = m_face_grid[1][0];
      break;
    case 3:
      e = m_edge_grid[0][1];
      break;
    case 4:
      if ( false == m_bExtraordinaryCornerVertex[1] )
        f = m_face_grid[2][0];
      break;
    }
  }
  else if (4 == i)
  {
    switch (j)
    {
    // case 0:  // i = 0; j = 0 handled above
    case 1:
      e = m_edge_grid[1][0];
      break;
    case 2:
      f = m_face_grid[2][1];
      break;
    case 3:
      e = m_edge_grid[1][1];
      break;
    case 4:
      if ( false == m_bExtraordinaryCornerVertex[2] )
        f = m_face_grid[2][2];
      break;
    }
  }
  else if (4 == j)
  {
    switch (i)
    {
    case 0:
      if ( false == m_bExtraordinaryCornerVertex[3] )
        f = m_face_grid[0][2];
      break;
    case 1:
      e = m_edge_grid[2][1];
      break;
    case 2:
      f = m_face_grid[1][2];
      break;
    case 3:
      e = m_edge_grid[2][0];
      break;
    // case 4:  // i = 4; j = 4 handled above
    }
  }
  else if (0 == i)
  {
    switch (j)
    {
    // case 0:  // i = 0; j = 0 handled above
    case 1:
      e = m_edge_grid[3][1];
      break;
    case 2:
      f = m_face_grid[0][1];
      break;
    case 3:
      e = m_edge_grid[3][0];
      break;
    // case 4:  // i = 0; j = 4 handled above
    }
  }
  
  bool bHaveApproximateCV;
  const bool bUseSavedSubdivisionPoint = true;
  if (nullptr != e)
  {
    const int extraordinary_vertex_index = ExtraordinaryCenterVertexIndex(ON_SubD::VertexTag::Crease, 4);
    const ON_SubDVertex* extraordinary_vertex
      = (extraordinary_vertex_index >= 0 && extraordinary_vertex_index < 4)
      ? CenterVertex(extraordinary_vertex_index)
      : nullptr;
    if (
      (ON_SubD::EdgeTag::Smooth == e->m_edge_tag || ON_SubD::EdgeTag::Crease == e->m_edge_tag)
      && (e->m_vertex[0] == extraordinary_vertex || e->m_vertex[1] == extraordinary_vertex)
      )
    {
      // extraordinary crease vertices
      bHaveApproximateCV = false;
    }
    else
    {
      bHaveApproximateCV = e->GetSubdivisionPoint(ON_SubD::SubDType::QuadCatmullClark, bUseSavedSubdivisionPoint, approximate_cv);
    }
  }
  else if ( nullptr != f && 4 == f->m_edge_count )
    bHaveApproximateCV = f->GetSubdivisionPoint(ON_SubD::SubDType::QuadCatmullClark,bUseSavedSubdivisionPoint,approximate_cv);
  else
  {
    bHaveApproximateCV = false;
  }

  return bHaveApproximateCV;
}

static bool CheckCV(const double* P)
{
  if ( ((const ON_3dPoint*)P)->IsValid() )
    return true;
  ON_SUBD_ERROR("bogus cv");
  return false;
}

static bool Internal_InterpCV(
  double srf_cv[5][5][3],
  const ON_2udex srf_unset_cv_dex,
  const ON_2udex srf_patchcv00_dex,
  ON_NurbsSurface& tmp,
  const ON_SubDSectorLimitPoint* limit_point
  )
{
  if (nullptr == limit_point || false == limit_point->IsSet() )
    return false;
  const ON_3dPoint LP(limit_point->m_limitP);
  if (false == LP.IsValid())
  {
    ON_SUBD_ERROR("limit_point->m_limitP is not valid.");
    return false;
  }

  double* P1 = srf_cv[srf_unset_cv_dex.i][srf_unset_cv_dex.j];
  if (!(ON_UNSET_VALUE == P1[0]))
  {
    ON_SUBD_ERROR("srf_unset_cv_dex parameter does not index an unset cv");
    return false;
  }

  const ON_2udex patch_unset_cv_dex(srf_unset_cv_dex.i - srf_patchcv00_dex.i, srf_unset_cv_dex.j - srf_patchcv00_dex.j);
  if (0 != patch_unset_cv_dex.i && 3 != patch_unset_cv_dex.i)
  {
    ON_SUBD_ERROR("Unable to correctly set patch_unset_cv_dex.i");
    return false;
  }
  if (0 != patch_unset_cv_dex.j && 3 != patch_unset_cv_dex.j)
  {
    ON_SUBD_ERROR("Unable to correctly set patch_unset_cv_dex.j");
    return false;
  }

  tmp.m_cv = srf_cv[srf_patchcv00_dex.i][srf_patchcv00_dex.j];
  if (tmp.CV(patch_unset_cv_dex.i, patch_unset_cv_dex.j) != P1)
  {
    // There is a bug in this function or in input parameters.
    ON_SUBD_ERROR("Unable to correctly set tmp.m_cv.");
    tmp.m_cv = nullptr;
    return false;
  }
  P1[0] = 0.0;
  P1[1] = 0.0;
  P1[2] = 0.0;
  const double s = (0 == patch_unset_cv_dex.i) ? 0.0 : 1.0;
  const double t = (0 == patch_unset_cv_dex.j) ? 0.0 : 1.0;
  const ON_3dPoint A = tmp.PointAt(s,t);
  tmp.m_cv = nullptr;
  if (false == A.IsValid())
  {
    ON_SUBD_ERROR("tmp.PointAt(s,t) failed.");
    P1[0] = ON_UNSET_VALUE;
    P1[1] = ON_UNSET_VALUE;
    P1[2] = ON_UNSET_VALUE;
    return false;
  }

  // If C = unset cv, the tensor product B-spline coefficient of C is (1/6)*(1/6) = 1/36
  // and A + 1/36*C = limitP.  So, C = 36*(LP - A).
  const ON_3dPoint C = 36.0*(LP - A);
  if (false == C.IsValid())
  {
    ON_SUBD_ERROR("36*(LP - A).is not valid.");
    P1[0] = ON_UNSET_VALUE;
    P1[1] = ON_UNSET_VALUE;
    P1[2] = ON_UNSET_VALUE;
    return false;
  }
  P1[0] = C.x;
  P1[1] = C.y;
  P1[2] = C.z;

  return true;
}

unsigned int ON_SubDQuadNeighborhood::GetLimitSubSurfaceMultiPatchCV(
  bool bEnableApproximatePatch,
  double srf_cv[5][5][3],
  ON_SubDLimitNurbsFragment::BispanType patch_type[4]
  )
{
  // Each "patch" is a 4x4 grid of CVs and srf_cc are the cvs for a cubic NURBS with 2 x2 spans (5x5 cvs)
  // Patch 0 has cvs with indices [0][0] to [3][3].
  // Patch 1 has cvs with indices [1][0] to [4][3].
  // Patch 2 has cvs with indices [1][1] to [4][4].
  // Patch 3 has cvs with indices [0][1] to [3][4].
  // this->m_srf_cv1[5][5][3] are the CVS we could set from SubD control NET.
  // Unset CVS have coordinates with values ON_UNSET_VALUE
  // This function fills in unset cvs, returns them in srf_cv[5][5][3], 
  // and sets patch_type[N] to indicate if all the CVs for that patch are set.
  // It returns the total number of set patches.

  const ON_2udex patch_cv00[4] = { ON_2udex(0,0),ON_2udex(1,0),ON_2udex(1,1),ON_2udex(0,1) };

  if (nullptr != patch_type)
  {
    patch_type[0] = ON_SubDLimitNurbsFragment::BispanType::None;
    patch_type[1] = ON_SubDLimitNurbsFragment::BispanType::None;
    patch_type[2] = ON_SubDLimitNurbsFragment::BispanType::None;
    patch_type[3] = ON_SubDLimitNurbsFragment::BispanType::None;
  }
  unsigned int quadrant_count = SetLimitSubSurfaceExactCVs(4U);
  if ( quadrant_count <= 0 )
    return ON_SUBD_RETURN_ERROR(0);

  unsigned int patch_exact_cv_count[4] = {};
  unsigned int patch_approx_cv_count[4] = {};

  double* P1 = srf_cv[0][0];
  const double* src = m_srf_cv1[0][0];
  unsigned int exact_cv_count = 0;
  unsigned int approx_cv_count = 0;

  // Here "approximate" means the cv valud is not set in m_srf_cv1[][][] and 
  // the corresponding srf_cv[][][] value was set in this function.
  // It also means the resulting cubic patches using that cv are close to but 
  // generally not exactly equal to the SubD surface location. 
  // The need for approximation happens because we have an exceptional SubD vertex on the central face.
  // Recall that the SubD surface at the limit point of an exceptional vertex is typically G1 but not G2
  // and that a bicubic NURBS surface with simple interior knots is G2. Hence, the bicubic NURBS surface
  // in this exceptional case has to be an approximation. When there are no exceptional vertices,
  // the bicubic NURBS surface is exactly equal to the SubD surface 
  // (that's one of the selling points of Catmull-Clark SubD).
  //
  unsigned char srf_cv_status[5][5] = {}; // 0 = unset, 1 = set, 2 = approximate from subD control poly, 3 = approximate from interpolation


  // This loop counts the number of set cvs for each of the 4 patches and copies the cv locations
  // from the input srv cv array this->m_srf_cv1[][][] to the output cv array srf_cv[][][].
  for (unsigned int i = 0; i < 5U; i++)
  {
    for (unsigned int j = 0; j < 5U; j++, P1 += 3)
    {
      // patch_count = number of patches this cv is used by (1 patch for the m_srf_cv1[][][] grid corners up to 4 patches for the central 3x3 grid).
      unsigned int active_patch_count = 0;
      // indices of patches this cv is active on
      unsigned int active_patch_index[4] = {};
      for (unsigned int patch_dex = 0; patch_dex < 4; patch_dex++)
      {
        if (
          i >= patch_cv00[patch_dex].i && i < patch_cv00[patch_dex].i+4 
          && j >= patch_cv00[patch_dex].j && j < patch_cv00[patch_dex].j+4 
          )
          active_patch_index[active_patch_count++] = patch_dex;
      }
      if (ON_UNSET_VALUE != src[0])
      {
        // input cv at this->m_srf_cv1[i][j][] is set - copy it.
        srf_cv_status[i][j] = 1;
        P1[0] = *src++;
        P1[1] = *src++;
        P1[2] = *src++;
        CheckCV(P1);
        exact_cv_count++;
        for ( unsigned int k = 0; k < active_patch_count; k++ )
          patch_exact_cv_count[active_patch_index[k]]++;
      }
      else 
      {
        // input cv at this->m_srf_cv1[i][j][] is not set
        // output srf_cv[i][j][] = (ON_UNSET_VALUE,ON_UNSET_VALUE,ON_UNSET_VALUE)
        P1[0] = ON_UNSET_VALUE;
        P1[1] = ON_UNSET_VALUE;
        P1[2] = ON_UNSET_VALUE;
        src += 3;
        if (bEnableApproximatePatch)
        {
          // see if we can set the srf_cv[i][j][] using the SubD neighborhood edges or faces
          // This works for patches that are far enough away from exceptional SubD vertices.
          if (Internal_GetApproximateCV(i, j, ON_UNSET_VALUE, P1))
          {
            srf_cv_status[i][j] = 2;
            CheckCV(P1);
            approx_cv_count++;
            for ( unsigned int k = 0; k < active_patch_count; k++ )
              patch_approx_cv_count[active_patch_index[k]]++;
          }
        }
      }
    }
  }

  ON_SubDLimitNurbsFragment::BispanType pt[4] = 
  {
    ON_SubDLimitNurbsFragment::BispanType::None,
    ON_SubDLimitNurbsFragment::BispanType::None,
    ON_SubDLimitNurbsFragment::BispanType::None,
    ON_SubDLimitNurbsFragment::BispanType::None
  };

  unsigned int exact_quadrant_count = 0;
  unsigned int approx_quadrant_count = 0;
  unsigned int interp_quadrant_count = 0;
  for (unsigned int patch_dex = 0; patch_dex < 4; patch_dex++)
  {
    const unsigned int patch_set_cv_count = patch_exact_cv_count[patch_dex] + patch_approx_cv_count[patch_dex];
    if (16 != patch_set_cv_count)
    {
      if (patch_set_cv_count > 16)
      {
        ON_SUBD_ERROR("Bug in patch set cv counter during step 1.");
      }
      continue;
    }

    if (16 == patch_exact_cv_count[patch_dex] )
    {
      pt[patch_dex] = ON_SubDLimitNurbsFragment::BispanType::Exact;
      exact_quadrant_count++;
    }
    else
    {
      // No interpolation is required to approximate the patch bispan for this quadrant.
      pt[patch_dex] = ON_SubDLimitNurbsFragment::BispanType::Approximate;
      approx_quadrant_count++;
    }
  }

  if (quadrant_count != exact_quadrant_count)
  {
    ON_SUBD_ERROR("exact_quadrant_count != SetLimitSubSurfaceExactCVs()");
  }

  if (bEnableApproximatePatch && (exact_quadrant_count + approx_quadrant_count) < 4 )
  {
    // See if we can interpolate limit points to set some more srf_cv[][][] locations.
    double knot[6] = { -2.0, -1.0, 0.0, 1.0, 2.0, 3.0 };
    ON_NurbsSurface tmp;
    tmp.m_dim = 3;
    tmp.m_is_rat = 0;
    tmp.m_order[0] = 4;
    tmp.m_order[1] = 4;
    tmp.m_cv_count[0] = 4;
    tmp.m_cv_count[1] = 4;
    tmp.m_knot[0] = knot;
    tmp.m_knot[1] = knot;
    const size_t cv_stride0 = &(srf_cv[1][0][0]) - &(srf_cv[0][0][0]); // should be 3*15 = 15
    const size_t cv_stride1 = &(srf_cv[0][1][0]) - &(srf_cv[0][0][0]); // should be 3
    tmp.m_cv_stride[0] = (int)cv_stride0;
    tmp.m_cv_stride[1] = (int)cv_stride1;

    // First see if patch 1 and patch 3 can be completed using 
    // this->m_center_edge*_limit_point locations
    for ( unsigned int interp_patch_dex = 0; interp_patch_dex < 4; interp_patch_dex++)
    {
      if (exact_quadrant_count + approx_quadrant_count + interp_quadrant_count >= 4)
        break; // all patches have been set

      if (ON_SubDLimitNurbsFragment::BispanType::None != pt[interp_patch_dex])
        continue; // already have this patch 

      if (15 != (patch_exact_cv_count[interp_patch_dex] + patch_approx_cv_count[interp_patch_dex]))
        continue; // missing too many cvs for interopolation to work

      ON_2udex srf_cv_dex[2] = {};
      unsigned int edge_index[2] = {};
      switch (interp_patch_dex)
      {
      case 0:
        srf_cv_dex[0] = ON_2udex(0, 3); edge_index[0] = 3;
        srf_cv_dex[1] = ON_2udex(3, 0); edge_index[1] = 0;
        break;
      case 1:
        srf_cv_dex[0] = ON_2udex(1, 0); edge_index[0] = 0;
        srf_cv_dex[1] = ON_2udex(4, 3); edge_index[1] = 1;
        break;
      case 2:
        srf_cv_dex[0] = ON_2udex(4, 1); edge_index[0] = 1;
        srf_cv_dex[1] = ON_2udex(1, 4); edge_index[1] = 2;
        break;
      case 3:
        srf_cv_dex[0] = ON_2udex(3, 4); edge_index[0] = 2;
        srf_cv_dex[1] = ON_2udex(0, 1); edge_index[1] = 3;
        break;
      default:
        break;
      }

      const int n = (0 == srf_cv_status[srf_cv_dex[0].i][srf_cv_dex[0].j])  ? 0 : 1;
      const ON_2udex srf_unset_cv_dex = srf_cv_dex[n];

      if (0 != srf_cv_status[srf_unset_cv_dex.i][srf_unset_cv_dex.j])
        continue; // the CV we would set using interpolation is already set.

      if (false == m_bCenterEdgeLimitPoint[edge_index[n]])
        continue; // We don't have a limit point to interpolate.

      // We are missing exactly one patch cv and can set it using interpolation through LP
      if (false == Internal_InterpCV(
        srf_cv,
        srf_unset_cv_dex,
        patch_cv00[interp_patch_dex],
        tmp,
        &m_center_edge_limit_point[edge_index[n]]
      ))
      {
        continue;
      }

      srf_cv_status[srf_unset_cv_dex.i][srf_unset_cv_dex.j] = 3;
      pt[interp_patch_dex] = ON_SubDLimitNurbsFragment::BispanType::Approximate;
      interp_quadrant_count++;
      approx_cv_count++;
      for (unsigned int patch_dex = 0; patch_dex < 4; patch_dex++)
      {
        if (
          srf_unset_cv_dex.i >= patch_cv00[patch_dex].i && srf_unset_cv_dex.i < patch_cv00[patch_dex].i + 4
          && srf_unset_cv_dex.j >= patch_cv00[patch_dex].j && srf_unset_cv_dex.j < patch_cv00[patch_dex].j + 4
          )
        {
         const unsigned int patch_set_cv_count = patch_exact_cv_count[patch_dex] + patch_approx_cv_count[patch_dex];

          if (patch_set_cv_count < 16)
            patch_approx_cv_count[patch_dex]++;
          else
          {
            ON_SUBD_ERROR("Bug in patch set cv counter during step 2.");
          }
        }
      }
    }

    // See if interpolating a central quad limpt point will complete a patch.
    for ( unsigned int interp_patch_dex = 0; interp_patch_dex < 4; interp_patch_dex++)
    {
      if (exact_quadrant_count + approx_quadrant_count + interp_quadrant_count >= 4)
        break;

      if (ON_SubDLimitNurbsFragment::BispanType::None != pt[interp_patch_dex])
        continue; // already have this patch 

      if (15 != (patch_exact_cv_count[interp_patch_dex] + patch_approx_cv_count[interp_patch_dex]))
        continue; // missing too many cvs for interopolation to work

      const ON_2udex srf_unset_cv_dex(
        (1 == interp_patch_dex || 2 == interp_patch_dex) ? 4 : 0,
        (2 == interp_patch_dex || 3 == interp_patch_dex) ? 4 : 0
      );

      if (0 != srf_cv_status[srf_unset_cv_dex.i][srf_unset_cv_dex.j])
        continue;

      const ON_2udex vertex_grid_dex(0 == srf_unset_cv_dex.i ? 1 : 2, 0 == srf_unset_cv_dex.j ? 1 : 2);

      const ON_SubDVertex* vertex = m_vertex_grid[vertex_grid_dex.i][vertex_grid_dex.j];
      if (nullptr == vertex)
        continue;

      ON_SubDSectorLimitPoint vertex_limit_point = ON_SubDSectorLimitPoint::Unset;
      const bool bUseSavedLimitPoint = true;
      if (false == vertex->GetLimitPoint(
        ON_SubD::SubDType::QuadCatmullClark,
        m_face_grid[1][1],
        bUseSavedLimitPoint,
        vertex_limit_point
      ))
        continue;

      // Calculate value of the unset CV so the surface passes through the limit point.
      if (false == Internal_InterpCV(
        srf_cv,
        srf_unset_cv_dex,
        patch_cv00[interp_patch_dex],
        tmp,
        &vertex_limit_point
      ))
      {
        continue;
      }

      srf_cv_status[srf_unset_cv_dex.i][srf_unset_cv_dex.j] = 3;
      pt[interp_patch_dex] = ON_SubDLimitNurbsFragment::BispanType::Approximate;
      interp_quadrant_count++;
      approx_cv_count++;
      for (unsigned int patch_dex = 0; patch_dex < 4; patch_dex++)
      {
        if (
          srf_unset_cv_dex.i >= patch_cv00[patch_dex].i && srf_unset_cv_dex.i < patch_cv00[patch_dex].i + 4
          && srf_unset_cv_dex.j >= patch_cv00[patch_dex].j && srf_unset_cv_dex.j < patch_cv00[patch_dex].j + 4
          )
        {
         const unsigned int patch_set_cv_count = patch_exact_cv_count[patch_dex] + patch_approx_cv_count[patch_dex];

          if (patch_set_cv_count < 16)
            patch_approx_cv_count[patch_dex]++;
          else
          {
            ON_SUBD_ERROR("Bug in patch set cv counter during step 2.");
          }
        }
      }

    }

    // Not necessary at the time of writing, but will prevent crashes if tmp m_*_capacity values are 
    // corrupted by a bug.
    tmp.m_cv = nullptr;
    tmp.m_knot[0] = nullptr;
    tmp.m_knot[1] = nullptr;
  }

  unsigned qcheck=0;
  for (unsigned patch_dex = 0; patch_dex < 4; patch_dex++)
  {
    const unsigned int patch_set_cv_count = (patch_exact_cv_count[patch_dex] + patch_approx_cv_count[patch_dex]);
    if (
      ON_SubDLimitNurbsFragment::BispanType::Exact == pt[patch_dex]
      || ON_SubDLimitNurbsFragment::BispanType::Approximate == pt[patch_dex]
      )
    {
      if (16 != patch_set_cv_count)
      {
        ON_SUBD_ERROR("Patch cv count bug 1.");
      }
      qcheck++;
    }
    else
    {
      if (patch_set_cv_count >= 16)
      {
        ON_SUBD_ERROR("Patch cv count bug 1.");
      }
    }
  }

  if (qcheck != interp_quadrant_count + approx_quadrant_count + exact_quadrant_count)
  {
    ON_SUBD_ERROR("patch_type[] values are not correct.");
  }

  if (nullptr != patch_type)
  {
    patch_type[0] = pt[0];
    patch_type[1] = pt[1];
    patch_type[2] = pt[2];
    patch_type[3] = pt[3];
  }


  return (interp_quadrant_count + approx_quadrant_count + exact_quadrant_count);
}

static double ON_SubDQuadFaceTopology_CopySectorWeight(
  const ON_SubDEdge* e0,
  const ON_SubDVertex* e0v
  )
{
  if (nullptr == e0 || nullptr == e0v)
    return ON_SUBD_RETURN_ERROR(false);

  if (ON_SubD::EdgeTag::Smooth != e0->m_edge_tag && ON_SubD::EdgeTag::X != e0->m_edge_tag )
    return ON_SubDSectorType::IgnoredSectorWeight;

  if (e0v == e0->m_vertex[0])
    return e0->m_sector_coefficient[0];
  
  if (e0v == e0->m_vertex[1])
    return e0->m_sector_coefficient[1];

  return ON_SUBD_RETURN_ERROR(ON_UNSET_VALUE);
}

static ON_SubDEdge* ON_SubDQuadFaceTopology_SubdivideEdge(
  ON_SubD_FixedSizeHeap& fsh,
  ON_SubDVertex* qv1,
  const ON_SubDVertex* qv0,
  const ON_SubDEdge* e0
  )
{
  if (nullptr == qv1 || nullptr == e0)
    return ON_SUBD_RETURN_ERROR(nullptr);

  ON_SubDVertex* v1 = fsh.AllocateVertex(e0,ON_SubD::SubDType::QuadCatmullClark,true,4,4);
  if (nullptr == v1)
    return ON_SUBD_RETURN_ERROR(nullptr);

  double v0_weight;
  if ( e0->IsSmooth(true) && nullptr != qv0)
  {
    // qv1 is the subdivision point of qv0.
    if ( qv1->m_vertex_tag != qv0->m_vertex_tag )
      return ON_SUBD_RETURN_ERROR(nullptr);
    if ( ON_SubD::VertexTag::Smooth == qv0->m_vertex_tag)
      v0_weight = ON_SubDSectorType::IgnoredSectorWeight;
    else
    {
      v0_weight = ON_SubDQuadFaceTopology_CopySectorWeight(e0, qv0);
      if (false == ON_SubDSectorType::IsValidSectorWeightValue(v0_weight,false))
        return ON_SUBD_RETURN_ERROR(nullptr);
    }
  }
  else
    v0_weight = ON_SubDSectorType::IgnoredSectorWeight;

  const double v1_weight = ON_SubDSectorType::IgnoredSectorWeight;

  ON_SubDEdge* e1 = fsh.AllocateEdge(qv1,v0_weight,v1,v1_weight);
  if (nullptr == e1)
    return ON_SUBD_RETURN_ERROR(nullptr);
  if (e1->m_edge_tag != e0->m_edge_tag)
  {
    // On the first subdivision step,
    // e0 with tag ON_SubD::EdgeTag::X turns into 
    // e1 with tag ON_SubD::EdgeTag::Smooth.
    if ( ON_SubD::EdgeTag::Smooth != e1->m_edge_tag || ON_SubD::EdgeTag::X != e0->m_edge_tag)
      return ON_SUBD_RETURN_ERROR(nullptr);
  }

  return e1;
}

static ON_SubDFace* ON_SubDQuadFaceTopology_SubdivideFace(
  ON_SubD_FixedSizeHeap& fsh,
  const ON_SubDFace* f0,
  ON_SubDEdge* e1[2],
  double at_crease_weight
  )
{
  ON_SubDVertex* v[4];
  if (nullptr == f0 || nullptr == e1[0] || nullptr == e1[1])
    return ON_SUBD_RETURN_ERROR(nullptr);

  v[0] = const_cast<ON_SubDVertex*>(e1[0]->m_vertex[0]);
  if (nullptr == v[0] || v[0] != e1[1]->m_vertex[0])
    return ON_SUBD_RETURN_ERROR(nullptr);

  v[1] = const_cast<ON_SubDVertex*>(e1[0]->m_vertex[1]);
  if (nullptr == v[1] )
    return ON_SUBD_RETURN_ERROR(nullptr);

  v[3] = const_cast<ON_SubDVertex*>(e1[1]->m_vertex[1]);
  if (nullptr == v[3] )
    return ON_SUBD_RETURN_ERROR(nullptr);

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

  v[2] = fsh.AllocateVertex(f0,ON_SubD::SubDType::QuadCatmullClark,true,4,4);
  if ( nullptr == v[2])
    return ON_SUBD_RETURN_ERROR(nullptr);

  const double v1_weight = (ON_SubD::VertexTag::Crease == v[1]->m_vertex_tag) ? at_crease_weight : ON_SubDSectorType::IgnoredSectorWeight;
  const double v2_weight = ON_SubDSectorType::IgnoredSectorWeight;
  const double v3_weight = (ON_SubD::VertexTag::Crease == v[3]->m_vertex_tag) ? at_crease_weight : ON_SubDSectorType::IgnoredSectorWeight;

  ON_SubDEdge* e12 = fsh.AllocateEdge(v[1],v1_weight,v[2],v2_weight);
  if ( nullptr == e12)
    return ON_SUBD_RETURN_ERROR(nullptr);

  ON_SubDEdge* e23 = fsh.AllocateEdge(v[2],v2_weight,v[3],v3_weight);
  if ( nullptr == e23)
    return ON_SUBD_RETURN_ERROR(nullptr);

  ON_SubDEdgePtr f1_epts[4] = {
    ON_SubDEdgePtr::Create(e1[0], 0),
    ON_SubDEdgePtr::Create(e12, 0),
    ON_SubDEdgePtr::Create(e23, 0),
    ON_SubDEdgePtr::Create(e1[1], 1)
  };

  ON_SubDFace* f1 = fsh.AllocateQuad(f0->m_zero_face_id, f0->m_id, f1_epts);
  if ( nullptr == f1)
    return ON_SUBD_RETURN_ERROR(nullptr);

  return f1;
}

bool ON_SubDQuadNeighborhood::Subdivide(
  const unsigned int q0fvi,
  ON_SubD_FixedSizeHeap& fsh,
  class ON_SubDQuadNeighborhood* q1ft
  ) const
{
  if ( nullptr == q1ft || this == q1ft)
    return ON_SUBD_RETURN_ERROR(false);

  if ( m_fsh == &fsh )
    return ON_SUBD_RETURN_ERROR(false);

  const ON_SubD::SubDType subd_type = ON_SubD::SubDType::QuadCatmullClark;

  // When a smooth subdivision edge ends at a vertex that is a subdivision point
  // of a creased original edge, this is the value to assign to the new
  // edge's m_vertex_weight. The "2" is there because there would be 2
  // sector faces if the subdivision was complete.
  const double at_crease_weight = ON_SubDSectorType::CreaseSectorWeight(subd_type,2);
  
  if ( m_fsh == q1ft->m_fsh)
    q1ft->m_fsh = nullptr;
  ON_SubDQuadNeighborhood::Clear(q1ft, false);
  q1ft->m_fsh = nullptr;

  q1ft->m_initial_subdivision_level = m_initial_subdivision_level;
  q1ft->m_current_subdivision_level = m_current_subdivision_level + 1;

  if (q0fvi > 3)
    return ON_SUBD_RETURN_ERROR(false);

  const ON_SubDFace* qf0 = CenterQuad();
  if ( nullptr == qf0 || 4 != qf0->m_edge_count)
    return ON_SUBD_RETURN_ERROR(false);

  const unsigned int zero_face_id = qf0->m_zero_face_id;
  const unsigned int parent_face_id = qf0->m_id;

  const ON_SubDEdge* qf0_edges[4] = {
    m_center_edges[q0fvi],
    m_center_edges[(q0fvi+1)%4],
    m_center_edges[(q0fvi+2)%4],
    m_center_edges[(q0fvi+3)%4]};

  if (nullptr == qf0_edges[0] || nullptr == qf0_edges[1] || nullptr == qf0_edges[2] || nullptr == qf0_edges[3])
    return ON_SUBD_RETURN_ERROR(false);

  const ON_SubDVertex* qf0_vertices[4] = {
    CenterVertex(q0fvi),
    CenterVertex((q0fvi+1)%4),
    CenterVertex((q0fvi+2)%4),
    CenterVertex((q0fvi+3)%4)};
  if (nullptr == qf0_vertices[0] || nullptr == qf0_vertices[1] || nullptr == qf0_vertices[2] || nullptr == qf0_vertices[3])
    return ON_SUBD_RETURN_ERROR(false);

  const ON_SubDVertex* qv0 = qf0_vertices[0];
  if ( nullptr == qv0 || qv0->m_face_count > qv0->m_edge_count)
    return ON_SUBD_RETURN_ERROR(false);

  const unsigned int N = qv0->m_edge_count;
  if (N < ON_SubDSectorType::MinimumSectorEdgeCount(qv0->m_vertex_tag))
    return ON_SUBD_RETURN_ERROR(false);

  ON_SubDSectorIterator sit;
  if ( nullptr == sit.Initialize(qf0,0,qv0) )
    return ON_SUBD_RETURN_ERROR(false);

  const bool bIsDartSector = (ON_SubD::VertexTag::Dart == qv0->m_vertex_tag);
  const bool bIsCreaseOrCornerSector = qv0->IsCreaseOrCorner();

  const bool bBoundaryCrease1[4] = {
    m_bBoundaryCrease[q0fvi] || (bIsCreaseOrCornerSector && qf0_edges[0]->IsCrease(false)),
    false,
    false,
    m_bBoundaryCrease[(q0fvi+3)%4] || (bIsCreaseOrCornerSector && qf0_edges[3]->IsCrease(false))
  };

  const bool bStopAtInternalCrease = (false == bIsDartSector);

  if ( false == fsh.ReserveSubDWorkspace( subd_type, N) )
    return ON_SUBD_RETURN_ERROR(false);

  ON_SubDVertex* qv1 = fsh.AllocateVertex(qv0,subd_type,true,N,qv0->m_face_count);
  if ( nullptr == qv1 )
    return ON_SUBD_RETURN_ERROR(false);
  //qv1->m_vertex_facet_type = ON_SubD::VertexFacetType::Quad;
  //qv1->m_vertex_edge_order = ON_SubD::VertexEdgeOrder::radial;

  const ON_SubDEdge* edge0 = sit.CurrentEdge(0);
  if ( nullptr == edge0 )
    return ON_SUBD_RETURN_ERROR(false);

  ON_SubDFace* face_grid1[3][3] = {};
  ON_SubDVertex* vertex_grid1[4][4] = {};
  ON_SubDEdge* edge_grid1[4][2] = {};

  // edge1 = new edge from qv1 to edge0 subdivision point
  ON_SubDEdge* edge1 = ON_SubDQuadFaceTopology_SubdivideEdge(fsh,qv1,qv0,edge0);
  if (nullptr == edge1)
    return ON_SUBD_RETURN_ERROR(false);

  // prepare to rotate coutnerclockwise around qv0, 
  // subdividing edges and adding new faces
  const ON_SubDEdge* e0[2] = {nullptr, edge0};
  ON_SubDEdge* e1[2] = {nullptr, edge1};
  const ON_SubDFace* f0 = qf0;
  ON_SubDFace* f1 = nullptr;
  bool bAtBoundaryCrease = false;
  bool bFinished = false;
  for (unsigned int i = 0; i < N; i++)
  {
    if (nullptr == f0)
      return ON_SUBD_RETURN_ERROR(false);
    e0[0] = e0[1];
    e0[1] = sit.CurrentEdge(1);
    if (nullptr == e0[1])
      return ON_SUBD_RETURN_ERROR(false);
    e1[0] = e1[1];
    if (edge0 == e0[1])
      e1[1] = edge1; // back to where we started
    else
      e1[1] = ON_SubDQuadFaceTopology_SubdivideEdge(fsh, qv1, qv0, e0[1]);
    if (nullptr == e1[1])
      return ON_SUBD_RETURN_ERROR(false);

    f1 = ON_SubDQuadFaceTopology_SubdivideFace(fsh, f0, e1, at_crease_weight);
    if (nullptr == f1 || 4 != f1->m_edge_count || qv1 != f1->Vertex(0))
      return ON_SUBD_RETURN_ERROR(false);

    if (1 == i)
    {
      face_grid1[0][1] = f1;
      vertex_grid1[0][1] = const_cast<ON_SubDVertex*>(f1->Vertex(3));
      vertex_grid1[0][2] = const_cast<ON_SubDVertex*>(f1->Vertex(2));
      edge_grid1[3][0] = const_cast<ON_SubDEdge*>(f1->Edge(1));
      edge_grid1[3][1] = const_cast<ON_SubDEdge*>(f1->Edge(3));
    }

    if (0 == i)
    {
      // central face in new grid
      face_grid1[1][1] = f1;
      vertex_grid1[1][1] = const_cast<ON_SubDVertex*>(f1->Vertex(0));
      vertex_grid1[2][1] = const_cast<ON_SubDVertex*>(f1->Vertex(1));
      vertex_grid1[2][2] = const_cast<ON_SubDVertex*>(f1->Vertex(2));
      vertex_grid1[1][2] = const_cast<ON_SubDVertex*>(f1->Vertex(3));
      if (bBoundaryCrease1[3])
      {
        bAtBoundaryCrease = true;
      }
    }

    if (!bAtBoundaryCrease)
    {
      f0 = sit.NextFace(bStopAtInternalCrease);
      if (nullptr == f0)
        bAtBoundaryCrease = true;
      if (e0[1] != sit.CurrentEdge(0))
        return ON_SUBD_RETURN_ERROR(false);
    }

    if (bAtBoundaryCrease)
    {
      e1[1]->m_edge_tag = ON_SubD::EdgeTag::Crease;
      break;
    }

    if (0==i)
      continue;

    if (f0 == qf0 || e0[1] == edge0 || sit.CurrentEdge(0) == edge0)
    {
      // got back to starting face
      // If i+1 < N, it means the vertex is nonmanifold.
      bFinished = (f0 == qf0 && e0[1] == edge0 && sit.CurrentEdge(0) == edge0 && qv1->m_face_count == qv1->m_edge_count);
      break;
    }
  }

  ON_SubDFace* face_grid1_10 = nullptr;

  if (bAtBoundaryCrease)
  {
    if ( qv1->m_face_count < 1 || qv1->m_face_count + 1 != qv1->m_edge_count)
      return ON_SUBD_RETURN_ERROR(false);
    if (true == bBoundaryCrease1[0])
      bFinished = true;
  }
  else if (bFinished)
  {
    face_grid1_10 = f1;
    if ( qv1->m_face_count < 2 || qv1->m_face_count != qv1->m_edge_count)
      return ON_SUBD_RETURN_ERROR(false);
  }
  else
    return ON_SUBD_RETURN_ERROR(false);


  if (bFinished)
  {
    if (4 == qv1->m_edge_count && 4 == qv1->m_face_count)
    {
      f1 = const_cast<ON_SubDFace*>(qv1->m_faces[2]);
      if ( nullptr == f1 || qv1 != f1->Vertex(0) || 4 != f1->m_edge_count)
        return ON_SUBD_RETURN_ERROR(false);
      face_grid1[0][0] = f1;
      vertex_grid1[0][0] = const_cast<ON_SubDVertex*>(f1->Vertex(2));
    }
  }
  else if (false == bBoundaryCrease1[0])
  {
    if ( qf0 != sit.FirstFace())
      return ON_SUBD_RETURN_ERROR(false);
    f0 = sit.PrevFace(bStopAtInternalCrease);
    if ( nullptr == f0 )
      return ON_SUBD_RETURN_ERROR(false);
    if ( edge0 != sit.CurrentEdge(1) )
      return ON_SUBD_RETURN_ERROR(false);

    const unsigned int edge_mark = qv1->m_edge_count;

    e0[0] = edge0;
    e0[1] = nullptr;
    e1[0] = edge1;
    e1[1] = nullptr;
    f1 = nullptr;
    for (unsigned int i = 0; i < N - edge_mark; i++)
    {
      e0[1] = e0[0];
      e0[0] = sit.CurrentEdge(0);
      if (nullptr == e0[0])
        return ON_SUBD_RETURN_ERROR(false);
      e1[1] = e1[0];
      e1[0] =  ON_SubDQuadFaceTopology_SubdivideEdge(fsh, qv1, qv0, e0[0]);
      if (nullptr == e1[0])
        return ON_SUBD_RETURN_ERROR(false);

      f1 = ON_SubDQuadFaceTopology_SubdivideFace(fsh, f0, e1, at_crease_weight);
      if (nullptr == f1 || 4 != f1->m_edge_count || qv1 != f1->Vertex(0))
        return ON_SUBD_RETURN_ERROR(false);

      if (0 == i)
        face_grid1_10 = f1;

      f0 = sit.PrevFace(bStopAtInternalCrease);
      if (nullptr == f0 || ON_SubD::EdgeTag::Crease == e0[1]->m_edge_tag)
      {
        bFinished = (nullptr == f0 && ON_SubD::EdgeTag::Crease == e0[0]->m_edge_tag && qv1->m_face_count+1 == qv1->m_edge_count);
        if (false == bFinished)
          return ON_SUBD_RETURN_ERROR(false);
        //qv1->m_vertex_edge_order = ON_SubD::VertexEdgeOrder::unset; // edges no longer radially sorted
        break;
      }
    }
  }

  if (false == bFinished)
     return ON_SUBD_RETURN_ERROR(false);

  if (nullptr != face_grid1_10)
  {
    f1 = face_grid1_10;
    if (nullptr == f1 || vertex_grid1[1][1] != f1->Vertex(0) || vertex_grid1[2][1] != f1->Vertex(3) || 4 != f1->m_edge_count)
      return ON_SUBD_RETURN_ERROR(false);
    face_grid1[1][0] = f1;
    vertex_grid1[1][0] = const_cast<ON_SubDVertex*>(f1->Vertex(1));
    vertex_grid1[2][0] = const_cast<ON_SubDVertex*>(f1->Vertex(2));
    edge_grid1[0][0] = const_cast<ON_SubDEdge*>(f1->Edge(0));
    edge_grid1[0][1] = const_cast<ON_SubDEdge*>(f1->Edge(2));
  }

  // Add the 7 remaining elements to vertex_grid1[][]
  if (false == bBoundaryCrease1[0])
  {
    vertex_grid1[3][0] = fsh.AllocateVertex(m_edge_grid[q0fvi][1],ON_SubD::SubDType::QuadCatmullClark,true,2,1);
    if ( nullptr == vertex_grid1[3][0])
      return ON_SUBD_RETURN_ERROR(false);
  }

  vertex_grid1[3][1] = fsh.AllocateVertex(qf0_vertices[1],ON_SubD::SubDType::QuadCatmullClark,true,3,2);
  if ( nullptr == vertex_grid1[3][1])
    return ON_SUBD_RETURN_ERROR(false);


  vertex_grid1[3][2] = fsh.AllocateVertex(qf0_edges[1],ON_SubD::SubDType::QuadCatmullClark,true,3,2);
  if ( nullptr == vertex_grid1[3][2])
    return ON_SUBD_RETURN_ERROR(false);

  vertex_grid1[3][3] = fsh.AllocateVertex(qf0_vertices[2],ON_SubD::SubDType::QuadCatmullClark,true,2,1);
  if ( nullptr == vertex_grid1[3][3])
    return ON_SUBD_RETURN_ERROR(false);

  vertex_grid1[2][3] = fsh.AllocateVertex(qf0_edges[2],ON_SubD::SubDType::QuadCatmullClark,true,3,2);
  if ( nullptr == vertex_grid1[2][3])
    return ON_SUBD_RETURN_ERROR(false);

  vertex_grid1[1][3] = fsh.AllocateVertex(qf0_vertices[3],ON_SubD::SubDType::QuadCatmullClark,true,3,2);
  if ( nullptr == vertex_grid1[1][3])
    return ON_SUBD_RETURN_ERROR(false);

  if (false == bBoundaryCrease1[3])
  {
    vertex_grid1[0][3] = fsh.AllocateVertex(m_edge_grid[(q0fvi+3)%4][0],ON_SubD::SubDType::QuadCatmullClark,true,2,1);
    if ( nullptr == vertex_grid1[0][3])
      return ON_SUBD_RETURN_ERROR(false);
  }

  edge_grid1[1][0] = fsh.AllocateEdge(
    vertex_grid1[2][1], 
    ON_SubDSectorType::IgnoredSectorWeight,
    vertex_grid1[3][1], 
    ON_SubDQuadFaceTopology_CopySectorWeight(qf0_edges[0], qf0_vertices[1])
    );
  if ( nullptr == edge_grid1[1][0])
    return ON_SUBD_RETURN_ERROR(false);

  edge_grid1[1][1] = fsh.AllocateEdge(
    vertex_grid1[2][2], 
    ON_SubDSectorType::IgnoredSectorWeight,
    vertex_grid1[3][2],
    at_crease_weight // ignored unless vertex_grid1[3][2] is tagged as a crease
    );
  if ( nullptr == edge_grid1[1][1])
    return ON_SUBD_RETURN_ERROR(false);

  edge_grid1[2][0] = fsh.AllocateEdge(
    vertex_grid1[2][2], 
    ON_SubDSectorType::IgnoredSectorWeight,
    vertex_grid1[2][3],
    at_crease_weight // ignored unless vertex_grid1[2][3] is tagged as a crease
    );
  if ( nullptr == edge_grid1[2][0])
    return ON_SUBD_RETURN_ERROR(false);

  edge_grid1[2][1] = fsh.AllocateEdge(
    vertex_grid1[1][2],
    ON_SubDSectorType::IgnoredSectorWeight,
    vertex_grid1[1][3],
    ON_SubDQuadFaceTopology_CopySectorWeight(qf0_edges[3], qf0_vertices[3])
    );
  if ( nullptr == edge_grid1[2][1])
    return ON_SUBD_RETURN_ERROR(false);

  // Add the 5 remaining elements to face_grid1[][]
  ON_SubDEdge* fedges[4];
  ON_SubDEdgePtr feptrs[4];
  if (false == bBoundaryCrease1[0])
  {
    fedges[0] = nullptr;
    fedges[1] = nullptr;
    fedges[2] = edge_grid1[1][0];
    fedges[3] = edge_grid1[0][1];

    if (nullptr == fedges[2] || nullptr == fedges[3] )
      return ON_SUBD_RETURN_ERROR(false);

    fedges[0] = fsh.AllocateEdge(
      vertex_grid1[2][0],
      ON_SubDSectorType::IgnoredSectorWeight,
      vertex_grid1[3][0],
      at_crease_weight // ignored unless vertex_grid1[3][0] is tagged as a crease
      );
    if (nullptr == fedges[0])
      return ON_SUBD_RETURN_ERROR(false);
    // m_edge_grid[q0fvi][1]
    fedges[1] = fsh.AllocateEdge(
      vertex_grid1[3][0],
      ON_SubDSectorType::IgnoredSectorWeight,
      vertex_grid1[3][1],
      ON_SubDQuadFaceTopology_CopySectorWeight(m_edge_grid[q0fvi][1], qf0_vertices[1])
      );
    if (nullptr == fedges[1])
      return ON_SUBD_RETURN_ERROR(false);

    feptrs[0] = ON_SubDEdgePtr::Create(fedges[0],0);
    feptrs[1] = ON_SubDEdgePtr::Create(fedges[1],0);
    feptrs[2] = ON_SubDEdgePtr::Create(fedges[2],1);
    feptrs[3] = ON_SubDEdgePtr::Create(fedges[3],1);

    face_grid1[2][0] = fsh.AllocateQuad(zero_face_id, parent_face_id, feptrs);
    if (nullptr == face_grid1[2][0])
      return ON_SUBD_RETURN_ERROR(false);
  }

  // face_grid1[2][1]
  fedges[0] = edge_grid1[1][0];
  fedges[1] = nullptr;
  fedges[2] = edge_grid1[1][1];
  fedges[3] = ON_SUBD_EDGE_POINTER(face_grid1[1][1]->m_edge4[1].m_ptr);

  if (nullptr == fedges[0] || nullptr == fedges[2] || nullptr == fedges[3] )
    return ON_SUBD_RETURN_ERROR(false);

  fedges[1] = fsh.AllocateEdge(
    vertex_grid1[3][1],
    ON_SubDQuadFaceTopology_CopySectorWeight(qf0_edges[1], qf0_vertices[1]),
    vertex_grid1[3][2],
    ON_SubDSectorType::IgnoredSectorWeight
    );
  if (nullptr == fedges[1])
    return ON_SUBD_RETURN_ERROR(false);

  feptrs[0] = ON_SubDEdgePtr::Create(fedges[0],0);
  feptrs[1] = ON_SubDEdgePtr::Create(fedges[1],0);
  feptrs[2] = ON_SubDEdgePtr::Create(fedges[2],1);
  feptrs[3] = ON_SubDEdgePtr::Create(fedges[3],1);

  face_grid1[2][1] = fsh.AllocateQuad(zero_face_id, parent_face_id, feptrs);
  if (nullptr == face_grid1[2][1])
    return ON_SUBD_RETURN_ERROR(false);

  // face_grid1[2][2]
  fedges[0] = edge_grid1[1][1];
  fedges[1] = nullptr;
  fedges[2] = nullptr;
  fedges[3] = edge_grid1[2][0];

  if (nullptr == fedges[0] || nullptr == fedges[3] )
    return ON_SUBD_RETURN_ERROR(false);

  fedges[1] = fsh.AllocateEdge(
    vertex_grid1[3][2],
    ON_SubDSectorType::IgnoredSectorWeight,
    vertex_grid1[3][3],
    ON_SubDQuadFaceTopology_CopySectorWeight(qf0_edges[1], qf0_vertices[2])
    );
  if (nullptr == fedges[1])
    return ON_SUBD_RETURN_ERROR(false);

  fedges[2] = fsh.AllocateEdge(
    vertex_grid1[3][3],
    ON_SubDQuadFaceTopology_CopySectorWeight(qf0_edges[2], qf0_vertices[2]),
    vertex_grid1[2][3],
    ON_SubDSectorType::IgnoredSectorWeight
    );
  if (nullptr == fedges[2])
    return ON_SUBD_RETURN_ERROR(false);

  feptrs[0] = ON_SubDEdgePtr::Create(fedges[0],0);
  feptrs[1] = ON_SubDEdgePtr::Create(fedges[1],0);
  feptrs[2] = ON_SubDEdgePtr::Create(fedges[2],0);
  feptrs[3] = ON_SubDEdgePtr::Create(fedges[3],1);

  face_grid1[2][2] = fsh.AllocateQuad(zero_face_id, parent_face_id, feptrs);
  if (nullptr == face_grid1[2][2])
    return ON_SUBD_RETURN_ERROR(false);

  // face_grid1[1][2]  
  fedges[0] = ON_SUBD_EDGE_POINTER(face_grid1[1][1]->m_edge4[2].m_ptr);
  fedges[1] = edge_grid1[2][0];
  fedges[2] = nullptr;
  fedges[3] = edge_grid1[2][1];

  if (nullptr == fedges[0] || nullptr == fedges[1] || nullptr == fedges[3] )
    return ON_SUBD_RETURN_ERROR(false);

  fedges[2] = fsh.AllocateEdge(
    vertex_grid1[2][3],
    ON_SubDSectorType::IgnoredSectorWeight,
    vertex_grid1[1][3],
    ON_SubDQuadFaceTopology_CopySectorWeight(qf0_edges[2], qf0_vertices[3])
    );
  if (nullptr == fedges[2])
    return ON_SUBD_RETURN_ERROR(false);

  feptrs[0] = ON_SubDEdgePtr::Create(fedges[0],1);
  feptrs[1] = ON_SubDEdgePtr::Create(fedges[1],0);
  feptrs[2] = ON_SubDEdgePtr::Create(fedges[2],0);
  feptrs[3] = ON_SubDEdgePtr::Create(fedges[3],1);

  face_grid1[1][2] = fsh.AllocateQuad(zero_face_id, parent_face_id, feptrs);
  if (nullptr == face_grid1[1][2])
    return ON_SUBD_RETURN_ERROR(false);

  if (false == bBoundaryCrease1[3])
  {
    fedges[0] = edge_grid1[2][1];
    fedges[1] = nullptr;
    fedges[2] = nullptr;
    fedges[3] = edge_grid1[3][0];

    if (nullptr == fedges[0] || nullptr == fedges[3] )
      return ON_SUBD_RETURN_ERROR(false);

    fedges[1] = fsh.AllocateEdge(
      vertex_grid1[1][3],
      ON_SubDQuadFaceTopology_CopySectorWeight(m_edge_grid[(q0fvi+3)%4][0], qf0_vertices[3]),
      vertex_grid1[0][3],
      ON_SubDSectorType::IgnoredSectorWeight
      );
    if (nullptr == fedges[1])
      return ON_SUBD_RETURN_ERROR(false);

    fedges[2] = fsh.AllocateEdge(
      vertex_grid1[0][3],
      at_crease_weight, // ingored unless vertex_grid1[0][3] is tagged as a crease
      vertex_grid1[0][2],
      ON_SubDSectorType::IgnoredSectorWeight
      );
    if (nullptr == fedges[2])
      return ON_SUBD_RETURN_ERROR(false);

    feptrs[0] = ON_SubDEdgePtr::Create(fedges[0],0);
    feptrs[1] = ON_SubDEdgePtr::Create(fedges[1],0);
    feptrs[2] = ON_SubDEdgePtr::Create(fedges[2],0);
    feptrs[3] = ON_SubDEdgePtr::Create(fedges[3],1);

    face_grid1[0][2] = fsh.AllocateQuad(zero_face_id, parent_face_id, feptrs);
    if (nullptr == face_grid1[0][2])
      return ON_SUBD_RETURN_ERROR(false);
  }

  q1ft->m_fsh = &fsh;

  const ON_2dex deltaj = ON_SubDQuadNeighborhood::DeltaDex(q0fvi,0,1);
  ON_2dex dex;
  q1ft->m_face_grid[1][1] = face_grid1[1][1];
  for (unsigned int i = 0; i < 3; i++)
  {
    dex = ON_SubDQuadNeighborhood::GridDex(3, q0fvi, i, 0);
    for (unsigned int j = 0; j < 3; j++)
    {
      q1ft->m_face_grid[dex.i][dex.j] = face_grid1[i][j];
      dex.i += deltaj.i;
      dex.j += deltaj.j;
    }
  }

  for (unsigned int i = 0; i < 4; i++)
  {
    unsigned int j = (q0fvi + i) % 4;
    q1ft->m_bBoundaryCrease[j] = bBoundaryCrease1[i];
    q1ft->m_edge_grid[j][0] = edge_grid1[i][0];
    q1ft->m_edge_grid[j][1] = edge_grid1[i][1];
    q1ft->m_center_edges[j] = face_grid1[1][1]->Edge(i);

    dex = ON_SubDQuadNeighborhood::GridDex(4,q0fvi,i,0);
    for (j = 0; j < 4; j++)
    {
      q1ft->m_vertex_grid[dex.i][dex.j] = vertex_grid1[i][j];
      dex.i += deltaj.i;
      dex.j += deltaj.j;
    }
  }

  q1ft->SetPatchStatus(q0fvi);

  if ( m_bExtraordinaryCornerVertex[q0fvi] )
  {
    // evaluate extraordinary limit point as soon as possible and then save it for future use.
    ON_SubDSectorLimitPoint limit_point;
    const ON_SubD::SubDType subd_type_local_scope = ON_SubD::SubDType::QuadCatmullClark;

    const ON_SubDFace* qv0_sector_face = this->m_face_grid[1][1];
    const ON_SubDFace* qv1_sector_face = q1ft->m_face_grid[1][1];

    if (subd_type_local_scope == qv0->SavedLimitPointType() && qv0->GetLimitPoint(subd_type_local_scope,qv0_sector_face,true,limit_point) && limit_point.IsSet())
    {
      limit_point.m_sector_face = qv1_sector_face; // qv1's sector face
      limit_point.m_next_sector_limit_point = nullptr; // sector checks are required to get "first" face
      qv1->SetSavedLimitPoint(subd_type_local_scope,limit_point);
    }
    else if (qv1->GetLimitPoint(subd_type_local_scope, qv1_sector_face, true, limit_point))
    {
      limit_point.m_sector_face = qv0_sector_face;
      limit_point.m_next_sector_limit_point = nullptr; // sector checks are required to get "first" face
      qv0->SetSavedLimitPoint(subd_type_local_scope,limit_point);
    }
  }

#if defined(ON_DEBUG)
  q1ft->IsValid(); // will trigger a break if "this" is not valid
#endif

  return true;
}


ON_2dex ON_SubDQuadNeighborhood::GridDex(
  unsigned int grid_size,
  unsigned int corner_index,
  unsigned int i,
  unsigned int j
  )
{
  ON_2dex dex;

  switch (corner_index)
  {
  case 1:
    dex.i = grid_size - 1 - j;
    dex.j = i;
    break;
  case 2:
    dex.i = grid_size - 1 - i;
    dex.j = grid_size - 1 - j;
    break;
  case 3:
    dex.i = j;
    dex.j = grid_size - 1 - i;
    break;
  default:
    dex.i = i;
    dex.j = j;
  }

  return dex;
}

ON_2dex ON_SubDQuadNeighborhood::DeltaDex(
  unsigned int corner_index,
  int delta_i,
  int delta_j
  )
{
  ON_2dex deltadex;
  switch (corner_index)
  {
  case 1:
    deltadex.i = -delta_j;
    deltadex.j = delta_i;
    break;
  case 2:
    deltadex.i = -delta_i;
    deltadex.j = -delta_j;
    break;
  case 3:
    deltadex.i = delta_j;
    deltadex.j = -delta_i;
    break;
  default:
    deltadex.i = delta_i;
    deltadex.j = delta_j;
  }

  return deltadex;
}




bool ON_SubDFace::GetQuadLimitSurface(
  size_t limit_surface_cv_stride0,
  size_t limit_surface_cv_stride1,
  double* limit_surface_cv
  ) const
{
  double srf_cv[4][4][3];

  if ( 4 != m_edge_count)
    return false;

  ON_SubDQuadNeighborhood qft;
  if (false == qft.Set(this ))
    return false;

  if ( false == qft.m_bIsCubicPatch )
    return false;

  if (false == qft.GetLimitSurfaceCV(&srf_cv[0][0][0], 4U))
    return false;

  for (unsigned int i = 0; i < 4; i++)
  {
    double* dst_cv = limit_surface_cv + i*limit_surface_cv_stride0;
    for (unsigned int j = 0; j < 4; j++)
    {
      const double* src_cv = srf_cv[i][j];
      dst_cv[0] = src_cv[0];
      dst_cv[1] = src_cv[1];
      dst_cv[2] = src_cv[2];

      dst_cv += limit_surface_cv_stride1;
    }
  }

  return true;
}

bool ON_SubDFace::GetQuadLimitSurface(
class ON_NurbsSurface& nurbs_surface
  ) const
{
  if (false == nurbs_surface.Create(
    3, // dim
    false, // is_rat
    4, 4, // orders
    4, 4  // cv_counts
    ))
  {
    return false;
  }
  if (false == GetQuadLimitSurface(nurbs_surface.m_cv_stride[0], nurbs_surface.m_cv_stride[1], nurbs_surface.m_cv))
    return false;

  // evaluation domain will be [0,1] x [0,1]
  double k = -2.0;
  for (unsigned int i = 0; i < 6; i++)
  {
    nurbs_surface.m_knot[0][i] = nurbs_surface.m_knot[1][i] = k;
    k += 1.0;
  }

  return true;
}

bool ON_SubDFace::GetQuadLimitSurface(
class ON_BezierSurface& bezier_surface
  ) const
{
  if (false == bezier_surface.Create(
    3, // dim
    false, // is_rat
    4, 4 // orders
    ))
    return false;

  double knots[2][6] = { { -2.0, -1.0, 0.0, 1.0, 2.0, 3.0 }, { -2.0, -1.0, 0.0, 1.0, 2.0, 3.0 } };

  ON_NurbsSurface nurbs_surface;
  nurbs_surface.m_dim = 3;
  nurbs_surface.m_is_rat = false;
  nurbs_surface.m_order[0] = 4;
  nurbs_surface.m_order[1] = 4;
  nurbs_surface.m_cv_count[0] = 4;
  nurbs_surface.m_cv_count[1] = 4;
  nurbs_surface.m_cv_stride[0] = bezier_surface.m_cv_stride[0];
  nurbs_surface.m_cv_stride[0] = bezier_surface.m_cv_stride[0];
  nurbs_surface.m_cv = bezier_surface.m_cv;
  nurbs_surface.m_cv_capacity = 0;
  nurbs_surface.m_knot[0] = knots[0];
  nurbs_surface.m_knot[1] = knots[1];
  nurbs_surface.m_knot_capacity[0] = 0;
  nurbs_surface.m_knot_capacity[1] = 0;

  if (false == GetQuadLimitSurface(nurbs_surface))
    return false;

  if (false == nurbs_surface.ClampEnd(0, 2))
    return false;
  if (false == nurbs_surface.ClampEnd(1, 2))
    return false;

  return true;
}




ON_SubDFaceNeighborhood::~ON_SubDFaceNeighborhood()
{
  ReserveCapacity(ON_SubD::SubDType::Unset,nullptr);
}

bool ON_SubDFaceNeighborhood::ReserveCapacity(
  ON_SubD::SubDType subd_type,
  const ON_SubDFace* face
  )
{
  m_face0 = nullptr;
  m_subd_type = ON_SubD::SubDType::Unset;
  m_center_vertex1 = nullptr;
  m_face1_count = 0;
  m_face1 = nullptr;

  unsigned int v_capacity =  0;
  unsigned int e_capacity =  0;
  unsigned int f_capacity =  0;
  unsigned int a_capacity =  0;
  
  for (;;)
  {
    if (nullptr == face)
      break;

    const unsigned int N = face->m_edge_count;
    if (N <= 2)
      break;

    // ordinary valence
    //const unsigned short V0 
    //  = (ON_SubD::FacetType::Quad == ON_SubD::FacetTypeFromSubDType(subd_type))
    //  ? 4
    //  : 6;
    // set E = extraordinary vertex valence overhead
    unsigned int S = 0;
    //unsigned int E = 0;
    const ON_SubDEdgePtr* edges = face->m_edge4;
    ON__UINT_PTR edge_ptr;
    const ON_SubDEdge* edge;
    const ON_SubDVertex* vertex;
    unsigned int i;
    for (i = 0; i < N; i++)
    {
      if (4 == i)
      {
        if (nullptr == face->m_edgex)
          return ON_SUBD_RETURN_ERROR(false);
        edges = face->m_edgex - 4;
      }
      edge_ptr = edges[i].m_ptr;
      edge = ON_SUBD_EDGE_POINTER(edge_ptr);
      if (nullptr == edge)
        break;
      vertex = edge->m_vertex[ON_SUBD_EDGE_DIRECTION(edge_ptr)];
      if (nullptr == vertex)
        break;
      if (vertex->m_edge_count < 2 )
        break;
      if (vertex->m_edge_count < vertex->m_face_count )
        break;
      S += vertex->m_edge_count;
      //if (vertex->m_edge_count > V0 )
      //  E += (vertex->m_edge_count - V0);
    }
    if (i != N)
      break;

    if (ON_SubD::SubDType::QuadCatmullClark == subd_type)
    {
      //v_capacity =  1 + 6*N + 2*E;
      //e_capacity =  9*N + 3*E;
      //f_capacity =  4*N + E;
      //a_capacity =  36*N + 8*E;
      f_capacity =  S;
      e_capacity =  3*S - N;
      v_capacity =  2*S - 2*N + 1;
      a_capacity =  4*f_capacity + 2*e_capacity + 2*v_capacity;
    }
    else if (ON_SubD::SubDType::TriLoopWarren == subd_type)
    {
      // todo -- add tri support
      // (remember to add 4 to a_capacity for the ON_SubDFaceNeighborhood.m_face1[] array
      //  when N = 3).
      break;
    }
    else
    {
      break;
    }

    return m_fsh.ReserveSubDWorkspace(v_capacity, e_capacity, f_capacity, a_capacity);
  }

  m_fsh.ReserveSubDWorkspace(0,0,0,0);
  if (ON_SubD::SubDType::Unset == subd_type && nullptr == face )
    return true;
  return ON_SUBD_RETURN_ERROR(false);
}


bool ON_SubDFaceNeighborhood::Subdivide(
  ON_SubD::SubDType subd_type,
  const ON_SubDFace* face
  )
{
  if (false == ReserveCapacity(subd_type, face))
    return ON_SUBD_RETURN_ERROR(false);

  if (ON_SubD::SubDType::QuadCatmullClark == subd_type)
    return ON_SubDFaceNeighborhood::QuadSubdivideHelper(face);

  if (ON_SubD::SubDType::TriLoopWarren == subd_type)
    return ON_SubDFaceNeighborhood::TriSubdivideHelper(face);

  return ON_SUBD_RETURN_ERROR(false);
}


bool ON_SubDFaceNeighborhood::TriSubdivideHelper(
  const ON_SubDFace* face
  )
{
  // todo - add tri support
  return ON_SUBD_RETURN_ERROR(false);
}

////static bool RadialSort(
////  unsigned int vertex1_sort_mark,
////  ON_SubDVertex* vertex
////  )
////{
////  if (nullptr == vertex || vertex->m_face_count > vertex->m_edge_count)
////    return ON_SUBD_RETURN_ERROR(false);
////
////  if ( 0 == vertex1_sort_mark)
////    return true;
////
////  unsigned int e0_count = vertex1_sort_mark+1;
////  unsigned int f0_count= vertex1_sort_mark;
////  unsigned int e_count = vertex->m_edge_count;
////  unsigned int f_count = vertex->m_face_count;
////  if (e0_count > e_count || f0_count > f_count)
////    return ON_SUBD_RETURN_ERROR(false);
////
////  ON__UINT_PTR stack_buffer[40];
////  ON__UINT_PTR* buffer = 0;
////  if (e0_count <= sizeof(stack_buffer) / sizeof(stack_buffer[0]))
////    buffer = stack_buffer;
////  else
////  {
////    buffer = new (std::nothrow) ON__UINT_PTR[e0_count];
////    if ( nullptr == buffer)
////      return ON_SUBD_RETURN_ERROR(false);
////  }
////
////  unsigned int i, j;
////  ON__UINT_PTR* a;
////  
////  a = (ON__UINT_PTR*)vertex->m_edges;
////  for (i = 0; i < e0_count; i++)
////  {
////    buffer[i] = a[i];
////  }
////  j = 0;
////  for (i = e0_count; i < e_count; i++)
////  {
////    a[j++] = a[i];
////  }
////  for (i = 0; i < e0_count; i++)
////  {
////    a[j++] = buffer[i];
////  }
////
////  a = (ON__UINT_PTR*)vertex->m_faces;
////  for (i = 0; i < f0_count; i++)
////  {
////    buffer[i] = a[i];
////  }
////  j = 0;
////  for (i = f0_count; i < f_count; i++)
////  {
////    a[j++] = a[i];
////  }
////  for (i = 0; i < f0_count; i++)
////  {
////    a[j++] = buffer[i];
////  }
////
////  if ( buffer != stack_buffer)
////    delete[] buffer;
////  vertex->m_vertex_edge_order = ON_SubD::VertexEdgeOrder::radial;
////  return true;
////}

bool ON_SubDFaceNeighborhood::QuadSubdivideHelper(
  const ON_SubDFace* face
  )
{
  // input face is valid and space is reserved.


  const ON_SubD::SubDType subd_type = ON_SubD::SubDType::QuadCatmullClark;
  
  // When a smooth subdivision edge ends at a vertex that is a subdivision point
  // of a creased original edge, this is the value to assign to the new
  // edge's m_vertex_weight. The "2" is there because there would be 2
  // sector faces if the subdivision was complete.
  const double at_crease2_weight = ON_SubDSectorType::CreaseSectorWeight(subd_type,2);

  const unsigned int N = face->m_edge_count;

  const unsigned int zero_face_id = face->m_zero_face_id;
  const unsigned int parent_face_id = face->m_parent_face_id;

  const bool bUseSavedSubdivisionPoint = true;

  ON_SubDSectorLimitPoint limit_point;

  // create central vertex
  ON_SubDVertex* center_vertex1 = m_fsh.AllocateVertex(face,subd_type,bUseSavedSubdivisionPoint,N,N);
  if ( nullptr == center_vertex1)
    return ON_SUBD_RETURN_ERROR(false);
  //center_vertex1->m_vertex_edge_order = ON_SubD::VertexEdgeOrder::radial;

  ON_SubDVertex* ring_vertex1[2] = {};

  const ON_SubDVertex* vertex0 = nullptr;
  const ON_SubDEdge* prev_edge0 = nullptr;
  ON__UINT_PTR prev_edge0_dir = 0;
  const ON_SubDEdge* edge0 = nullptr;
  ON__UINT_PTR edge0_dir = 0;
  ON_SubDVertex* vertex1 = nullptr;
  ON_SubDEdge* edge1 = nullptr;
  ON_SubDFace* face1 = nullptr;
  ON_SubDEdgePtr face1_eptrs[4] = {ON_SubDEdgePtr::Null,ON_SubDEdgePtr::Null,ON_SubDEdgePtr::Null,ON_SubDEdgePtr::Null};
  const ON_SubDEdgePtr* face_m_edges = face->m_edge4;
  for (unsigned int i = 0; i < N; i++, face_m_edges++)
  {
    prev_edge0 = edge0;
    prev_edge0_dir = edge0_dir;

    if (4 == i)
    {
      if (nullptr == face->m_edgex)
        return ON_SUBD_RETURN_ERROR(false);
      face_m_edges = face->m_edgex;
    }
    const ON__UINT_PTR edge0_ptr = face_m_edges->m_ptr;

    edge0 = ON_SUBD_EDGE_POINTER(edge0_ptr);
    if (nullptr == edge0)
      return ON_SUBD_RETURN_ERROR(false);
    edge0_dir = ON_SUBD_EDGE_DIRECTION(edge0_ptr);

    vertex0 = edge0->m_vertex[edge0_dir];
    if (nullptr == vertex0)
      return ON_SUBD_RETURN_ERROR(false);
    if (vertex0->m_edge_count < 2)
      return ON_SUBD_RETURN_ERROR(false);

    // At this time (Jan, 2015) the primary purpose of creating a ON_SubDFaceNeighborhood
    // is to get the limit mesh and limit cubic surfaces when the original face
    // is not a quad.  I need to calculate and save the limit point for extraordinary
    // verticies while enough information is available to calculate it.  Doing the calculation
    // before calling m_fsh.AllocateVertex(), insures the information will be copied to
    // vertex1;
    if ( false == vertex0->GetLimitPoint(subd_type,face,true,limit_point) )
      return ON_SUBD_RETURN_ERROR(false);

    vertex1 = m_fsh.AllocateVertex(vertex0,subd_type,bUseSavedSubdivisionPoint,vertex0->m_edge_count,vertex0->m_edge_count);
    if ( nullptr == vertex1)
      return ON_SUBD_RETURN_ERROR(false);

    if (nullptr != limit_point.m_sector_face)
    {
      // Original vertex had sectors and that will prevent
      // m_fsh.AllocateVertex() from copying the limit point.
      // vertex1 does not have sectors.
      ON_SubDSectorLimitPoint saved_limit_point = limit_point;
      saved_limit_point.m_sector_face = nullptr;
      saved_limit_point.m_next_sector_limit_point = (ON_SubDSectorLimitPoint*)1; // causes unnecessary test to be skipped
      vertex1->SetSavedLimitPoint(subd_type, saved_limit_point);
    }


    if (0 == i)
    {
      ring_vertex1[0] = vertex1;
    }
    else
    {
      if ( nullptr == prev_edge0)
        return ON_SUBD_RETURN_ERROR(false);
      face1_eptrs[0] = face1_eptrs[3].Reversed();
      edge1 = m_fsh.AllocateEdge(
        ring_vertex1[1],
        ON_SubDSectorType::IgnoredSectorWeight,
        vertex1,
        ON_SubDQuadFaceTopology_CopySectorWeight(prev_edge0, vertex0)
        );
      if ( nullptr == edge1)
        return ON_SUBD_RETURN_ERROR(false);
      face1_eptrs[1] = ON_SubDEdgePtr::Create(edge1,0);
    }
    ring_vertex1[1] = vertex1;

    // This vertex is either a crease (3 edges, 2 faces) or a smooth ordinary vertex (4 edges, 4 faces).
    vertex1 = m_fsh.AllocateVertex(edge0,subd_type,bUseSavedSubdivisionPoint,4,4);
    if ( nullptr == vertex1)
      return ON_SUBD_RETURN_ERROR(false);

    if (0 != i)
    {
      edge1 = m_fsh.AllocateEdge(
        ring_vertex1[1], 
        ON_SubDQuadFaceTopology_CopySectorWeight(edge0,vertex0),
        vertex1,
        ON_SubDSectorType::IgnoredSectorWeight
        );
      if (nullptr == edge1)
        return ON_SUBD_RETURN_ERROR(false);
      face1_eptrs[2] = ON_SubDEdgePtr::Create(edge1,0);
    }
    ring_vertex1[1] = vertex1;

    edge1 = m_fsh.AllocateEdge(
      center_vertex1,
      ON_SubDSectorType::IgnoredSectorWeight,
      vertex1,
      at_crease2_weight // ingored unless vertex1 is tagged as a crease
      );
    if ( nullptr == edge1)
      return ON_SUBD_RETURN_ERROR(false);
    face1_eptrs[3] = ON_SubDEdgePtr::Create(edge1,1);

    if (0==i)
      continue;

    face1 = m_fsh.AllocateQuad(zero_face_id, parent_face_id, face1_eptrs);
    if ( nullptr == face1)
      return ON_SUBD_RETURN_ERROR(false);
  }

  // add the quad with diagonal from center to initial ring vertex
  // to finish quad subdivision of the initial face.
  face1_eptrs[0] = face1_eptrs[3].Reversed();

  edge1 = m_fsh.AllocateEdge(
    ring_vertex1[1],
    ON_SubDSectorType::IgnoredSectorWeight,
    ring_vertex1[0],
    ON_SubDQuadFaceTopology_CopySectorWeight(edge0,face->Vertex(0))
    );
  if ( nullptr == edge1)
    return ON_SUBD_RETURN_ERROR(false);
  face1_eptrs[1] = ON_SubDEdgePtr::Create(edge1,0);

  edge1 = m_fsh.AllocateEdge(
    ring_vertex1[0],
    ON_SubDQuadFaceTopology_CopySectorWeight(face->Edge(0),face->Vertex(0)),
    const_cast<ON_SubDVertex*>(ring_vertex1[0]->m_next_vertex),
    ON_SubDSectorType::IgnoredSectorWeight
    );
  if ( nullptr == edge1)
    return ON_SUBD_RETURN_ERROR(false);
  face1_eptrs[2] = ON_SubDEdgePtr::Create(edge1,0);

  face1_eptrs[3] = center_vertex1->m_edges[0].Reversed();

  face1 = m_fsh.AllocateQuad(zero_face_id, parent_face_id, face1_eptrs);
  if ( nullptr == face1)
    return ON_SUBD_RETURN_ERROR(false);


  // order edges and faces radially counterclockwise with respect to the initial face's orientation.
  vertex1 = center_vertex1;
  for (unsigned int i = 0; i < N; i++)
  {
    if ( nullptr == vertex1)
    {
      // If this failure occurs, there is a bug in the code above
      // or somebody changed the way ON_SubD_FixedSizeHeap::AllocateVertex()
      // sets m_next_vertex.
      return ON_SUBD_RETURN_ERROR(false);
    }
    vertex1 = const_cast<ON_SubDVertex*>(vertex1->m_next_vertex);
    if ( nullptr == vertex1)
    {
      // If this failure occurs, there is a bug in the code above
      // or somebody changed the way ON_SubD_FixedSizeHeap::AllocateVertex()
      // sets m_next_vertex.
      return ON_SUBD_RETURN_ERROR(false);
    }

    // vertex1 = subdivided corner vertex of initial face
    if ( 2 != vertex1->m_edge_count || 1 != vertex1->m_face_count )
    {
      // If this failure occurs, there is a bug in the code above
      // or somebody changed the way ON_SubD_FixedSizeHeap::AllocateVertex()
      // sets m_next_vertex.
      return ON_SUBD_RETURN_ERROR(false);
    }
    if (0 == i && vertex1 != ring_vertex1[0])
    {
      // If this failure occurs, there is a bug in the code above
      // or somebody changed the way ON_SubD_FixedSizeHeap::AllocateVertex()
      // sets m_next_vertex.
      return ON_SUBD_RETURN_ERROR(false);
    }
    face1_eptrs[0] = vertex1->m_edges[0];
    vertex1->m_edges[0] = vertex1->m_edges[1];
    vertex1->m_edges[1] = face1_eptrs[0];
    //vertex1->m_vertex_edge_order = ON_SubD::VertexEdgeOrder::radial;

    vertex1 = const_cast<ON_SubDVertex*>(vertex1->m_next_vertex);
    if ( nullptr == vertex1)
    {
      // If this failure occurs, there is a bug in the code above
      // or somebody changed the way ON_SubD_FixedSizeHeap::AllocateVertex()
      // sets m_next_vertex.
      return ON_SUBD_RETURN_ERROR(false);
    }
      
    // vertex1 = subdivided edge vertex of initial face
    if ( 3 != vertex1->m_edge_count || 2 != vertex1->m_face_count )
    {
      // If this failure occurs, there is a bug in the code above
      // or somebody changed the way ON_SubD_FixedSizeHeap::AllocateVertex()
      // sets m_next_vertex.
      return ON_SUBD_RETURN_ERROR(false);
    }

    if (0 == i)
    {
      // edges of the first vertex of this type are created
      // in a different order than subsequent vertices.
      face1_eptrs[0] = vertex1->m_edges[0];
      vertex1->m_edges[0] = vertex1->m_edges[1];
      vertex1->m_edges[1] = face1_eptrs[0];
    }
    else
    {
      face1_eptrs[0] = vertex1->m_edges[0];
      vertex1->m_edges[0] = vertex1->m_edges[2];
      vertex1->m_edges[2] = face1_eptrs[0];
      const ON_SubDFace* tmp_f = vertex1->m_faces[0];
      vertex1->m_faces[0] = vertex1->m_faces[1];
      vertex1->m_faces[1] = tmp_f;
    }
    //vertex1->m_vertex_edge_order = ON_SubD::VertexEdgeOrder::radial;
  }

  if ( vertex1 != ring_vertex1[1])
  {
    // If this failure occurs, there is a bug in the code above
    // or somebody changed the way ON_SubD_FixedSizeHeap::AllocateVertex()
    // sets m_next_vertex.
    return ON_SUBD_RETURN_ERROR(false);
  }  

  /////////////////////////////////////////////////////////////////////////////////
  //
  // At each corner of the original face, add the outer subdivision quads
  //

  // Create subdivision edges that radiate from the sides of the original faces
  ON_SubDVertex* vertex1_trio[3] = { nullptr, nullptr, center_vertex1 };
  ON_SubDEdge* edge1_quartet[4] = {};
  face_m_edges = face->m_edge4;
  for (unsigned int i = 0; i < N; i++, face_m_edges++)
  {
    vertex1_trio[2] = const_cast<ON_SubDVertex*>(vertex1_trio[2]->m_next_vertex->m_next_vertex);
    if (4 == i)
      face_m_edges = face->m_edgex;
    edge0 = ON_SUBD_EDGE_POINTER(face_m_edges->m_ptr);
    if ( false == edge0->IsSmooth(true) || edge0->m_face_count != 2 )
      continue;
    const ON_SubDFace* face0 = edge0->NeighborFace(face,true);
    if ( nullptr == face0 )
      return ON_SUBD_RETURN_ERROR(false);
    vertex1 = m_fsh.AllocateVertex(face0,subd_type,bUseSavedSubdivisionPoint,3,2);
    if ( nullptr == vertex1 )
      return ON_SUBD_RETURN_ERROR(false);
    edge1 = m_fsh.AllocateEdge(vertex1_trio[2],ON_SubDSectorType::IgnoredSectorWeight,vertex1,ON_SubDSectorType::IgnoredSectorWeight);
    if ( nullptr == edge1 )
      return ON_SUBD_RETURN_ERROR(false);
    if ( i+1 == N)
      edge1_quartet[3] = edge1;
  }

  // now visit each corner and add subdivision quads
  ON_SubDSectorIterator sit;
  const ON_SubDEdge* edge0_duo[2] = {nullptr, face->Edge(N-1) };
  const ON_SubDFace* neighbor0_duo[2] = {nullptr, (nullptr == edge0_duo[1] ? nullptr : edge0_duo[1]->NeighborFace(face,true)) };
  face_m_edges = face->m_edge4;
  for (unsigned int i = 0; i < N; i++, face_m_edges++)
  {
    if (4==i)
      face_m_edges = face->m_edgex;

    // edge0_duo[0] = face->Edge(i-1)
    // edge0_duo[1] = face->Edge(i)
    // neighbor0_duo[0] = original face neighbor across edge0_duo[0]
    // neighbor0_duo[1] = original face neighbor across edge0_duo[0]
    // vertex0 = face->Vertex(i)
    edge0_duo[0] = edge0_duo[1];
    neighbor0_duo[0] = neighbor0_duo[1];
    edge0_duo[1] = ON_SUBD_EDGE_POINTER(face_m_edges->m_ptr);
    neighbor0_duo[1] = (nullptr == edge0_duo[1] ? nullptr : edge0_duo[1]->NeighborFace(face,true));
    vertex0 = edge0_duo[1]->m_vertex[ON_SUBD_EDGE_DIRECTION(face_m_edges->m_ptr)];
    // tests above edge0_duo[0], edge0_duo[1], and vertex0 are not null.

    const double e0_w = ON_SubDQuadFaceTopology_CopySectorWeight(edge0_duo[0], vertex0);
    const double e1_w = ON_SubDQuadFaceTopology_CopySectorWeight(edge0_duo[1], vertex0);
    double sector_weight;
    if (e0_w == e1_w)
      sector_weight = e0_w;
    else
    {
      if (ON_SubDSectorType::IgnoredSectorWeight == e0_w)
        sector_weight = e1_w;
      else if (ON_SubDSectorType::IgnoredSectorWeight == e1_w)
        sector_weight = e0_w;
      else if (ON_SubDSectorType::UnsetSectorWeight == e0_w)
        sector_weight = e1_w;
      else if (ON_SubDSectorType::UnsetSectorWeight == e1_w)
        sector_weight = e0_w;
      else
        sector_weight = ON_UNSET_VALUE;
      if (!(sector_weight >= 0.0 && sector_weight < 1.0))
        sector_weight = ON_SubDSectorType::UnsetSectorWeight;
    }

    // vertex1_trio[0] = subdivision vertex on edge0_duo[0]
    // vertex1_trio[1] = subdivision vertex on face->Vertex(i)
    // vertex1_trio[2] = subdivision vertex on edge0_duo[1]
    // edge1_quartet[0] = null or subdivision edge from vertex1_trio[0] to neighbor0_duo[0] subdivision point
    // edge1_quartet[1] = subdivision edge from vertex1_trio[0] to vertex1_trio[1]
    // edge1_quartet[2] = subdivision edge from vertex1_trio[1] to vertex1_trio[2]
    // edge1_quartet[3] = null or subdivision edge from vertex1_trio[2] to neighbor0_duo[1] subdivision point
    vertex1_trio[0] = vertex1_trio[2];
    vertex1_trio[1] = const_cast< ON_SubDVertex* >(( 0 == i ) ? center_vertex1->m_next_vertex : vertex1_trio[0]->m_next_vertex);
    vertex1_trio[2] = const_cast< ON_SubDVertex* >(vertex1_trio[1]->m_next_vertex);
    edge1_quartet[0] = edge1_quartet[3];
    edge1_quartet[1] = ON_SUBD_EDGE_POINTER(vertex1_trio[1]->m_edges[1].m_ptr);
    edge1_quartet[2] = ON_SUBD_EDGE_POINTER(vertex1_trio[1]->m_edges[0].m_ptr);
    edge1_quartet[3] = (nullptr == vertex1_trio[2]) ? nullptr : ON_SUBD_EDGE_POINTER(vertex1_trio[2]->m_edges[3].m_ptr);

    
    if (edge0_duo[0]->IsCrease(false) && edge0_duo[1]->IsCrease(false))
    {
      // no outer quads at this corner
      continue;
    }
    if (2 != edge0_duo[0]->m_face_count && 2 != edge0_duo[1]->m_face_count)
    {
      // no outer quads at this corner
      continue;
    }
    if (vertex0->m_face_count <= 1 || vertex0->m_edge_count <= 2)
    {
      // error condition that we can tolerate
      ON_SubDIncrementErrorCount();
      continue;
    }

    /////////////////////////////////////////////////////////////////////////////////
    //
    // There is at least one outer subdivision quads around the inner corner subd quad at face->Vertex(i)
    
    face1_eptrs[0] = ON_SubDEdgePtr::Null;
    face1_eptrs[1] = ON_SubDEdgePtr::Null;
    face1_eptrs[2] = ON_SubDEdgePtr::Null;
    face1_eptrs[3] = ON_SubDEdgePtr::Create(edge1_quartet[1],1);
    
    if ( vertex0 != sit.Initialize(face,0,i) )
      return ON_SUBD_RETURN_ERROR(false);

    const ON_SubDFace* face0 = sit.NextFace(true);
    if ( edge0_duo[0] != sit.CurrentEdge(0) || face0 != neighbor0_duo[0])
      return ON_SUBD_RETURN_ERROR(false);

    if (nullptr != face0)
    {
      // construct the first subd quad counter-clockwise from the interior corner quad at face->Vertex(i).
      edge0 = sit.CurrentEdge(1);
      if ( nullptr == edge0 )
        return ON_SUBD_RETURN_ERROR(false);
      vertex1 = m_fsh.AllocateVertex(edge0,subd_type,bUseSavedSubdivisionPoint,3,2);
      if ( nullptr == vertex1 )
        return ON_SUBD_RETURN_ERROR(false);
      edge1 = m_fsh.AllocateEdge(const_cast<ON_SubDVertex*>(edge1_quartet[0]->m_vertex[1]),ON_SubDSectorType::IgnoredSectorWeight,vertex1,at_crease2_weight);
      if ( nullptr == edge1 )
        return ON_SUBD_RETURN_ERROR(false);
      face1_eptrs[0] = ON_SubDEdgePtr::Create(edge1_quartet[1], 1);
      face1_eptrs[1] = ON_SubDEdgePtr::Create(edge1_quartet[0], 0);
      face1_eptrs[2] = ON_SubDEdgePtr::Create(edge1,0);
      // vertex1_trio[1] is a subdivision of an input face vertex.
      edge1 = m_fsh.AllocateEdge(vertex1_trio[1], sector_weight, vertex1, at_crease2_weight);
      if ( nullptr == edge1 )
        return ON_SUBD_RETURN_ERROR(false);
      face1_eptrs[3] = ON_SubDEdgePtr::Create(edge1,1);
      face1 = m_fsh.AllocateQuad(zero_face_id, parent_face_id, face1_eptrs);
      if ( nullptr == face1 )
        return ON_SUBD_RETURN_ERROR(false);
    }
    else
    {
      // edge0_duo[0] is a crease
      face1_eptrs[0] = ON_SubDEdgePtr::Null;
      face1_eptrs[1] = ON_SubDEdgePtr::Null;
      face1_eptrs[2] = ON_SubDEdgePtr::Null;
      face1_eptrs[3] = ON_SubDEdgePtr::Null;
    }

    bool bFinishedCorner = false;
    bool bCreases = false;
    const unsigned int vertex_face_count = vertex0->m_face_count;
    unsigned int vertex1_sort_mark = 0;

    for (unsigned int sit_limit = 0; sit_limit < vertex_face_count; sit_limit++)
    {
      if ( nullptr != face0 )
        face0 = sit.NextFace(true);
      if (nullptr == face0)
      {
        if ( bCreases )
          return ON_SUBD_RETURN_ERROR(false);
        bCreases = true;
        if (nullptr == neighbor0_duo[1])
        {
          // face->Edge(i) is a crease
          bFinishedCorner = true;
          break; 
        }
        if ( nullptr == sit.Initialize(face,0,i))
          return ON_SUBD_RETURN_ERROR(false);
        face0 = sit.IncrementToCrease(-1);
        if ( nullptr == face0 || face0 == face)
          return ON_SUBD_RETURN_ERROR(false);
        edge0 = sit.CurrentEdge(0);
        if ( nullptr == edge0 )
          return ON_SUBD_RETURN_ERROR(false);
        vertex1_sort_mark = vertex1_trio[1]->m_face_count;
        vertex1 = m_fsh.AllocateVertex(edge0,subd_type,bUseSavedSubdivisionPoint,2,1);
        if ( nullptr == vertex1 )
          return ON_SUBD_RETURN_ERROR(false);
        edge1 = m_fsh.AllocateEdge(vertex1_trio[1],ON_SubDSectorType::IgnoredSectorWeight,vertex1,ON_SubDSectorType::IgnoredSectorWeight);
        if ( nullptr == edge1 )
          return ON_SUBD_RETURN_ERROR(false);
        face1_eptrs[0] = ON_SubDEdgePtr::Null;
        face1_eptrs[1] = ON_SubDEdgePtr::Null;
        face1_eptrs[2] = ON_SubDEdgePtr::Null;
        face1_eptrs[3] = ON_SubDEdgePtr::Create(edge1,1);
      }
      else if ( face0 == face)
        return ON_SUBD_RETURN_ERROR(false);

      edge0 = sit.CurrentEdge(1);
      if ( nullptr == edge0 || edge0 == edge0_duo[0] )
        return ON_SUBD_RETURN_ERROR(false);

      face1_eptrs[0] = face1_eptrs[3].Reversed();
      face1_eptrs[1] = ON_SubDEdgePtr::Null;
      face1_eptrs[2] = ON_SubDEdgePtr::Null;
      face1_eptrs[3] = ON_SubDEdgePtr::Null;

      if (face0 == neighbor0_duo[1])
      {
        // construct the last subd quad clockwise from the interior corner quad at face->Vertex(i).
        if (edge0 != edge0_duo[1])
          return ON_SUBD_RETURN_ERROR(false);
        edge1 = ON_SUBD_EDGE_POINTER(face1_eptrs[0].m_ptr);
        if ( nullptr == edge1 )
          return ON_SUBD_RETURN_ERROR(false);
        vertex1 = const_cast<ON_SubDVertex*>(edge1->m_vertex[1]);
        if ( nullptr == vertex1 )
          return ON_SUBD_RETURN_ERROR(false);
        // vertex1 is from subdividing an edge that radiates out from the input face.
        edge1 = m_fsh.AllocateEdge(
          vertex1, at_crease2_weight,
          const_cast<ON_SubDVertex*>(edge1_quartet[3]->m_vertex[1]),ON_SubDSectorType::IgnoredSectorWeight);
        face1_eptrs[1] = ON_SubDEdgePtr::Create(edge1,0);
        face1_eptrs[2] = ON_SubDEdgePtr::Create(edge1_quartet[3],1);
        face1_eptrs[3] = ON_SubDEdgePtr::Create(edge1_quartet[2],1);
        face1 = m_fsh.AllocateQuad(zero_face_id, parent_face_id, face1_eptrs);
        if ( nullptr == face1 )
          return ON_SUBD_RETURN_ERROR(false);
        bFinishedCorner = true;
        break;
      }

      if (edge0 == edge0_duo[1])
          return ON_SUBD_RETURN_ERROR(false);

      edge1 = ON_SUBD_EDGE_POINTER(face1_eptrs[0].m_ptr);
      if ( nullptr == edge1 )
        return ON_SUBD_RETURN_ERROR(false);
      vertex1 = const_cast<ON_SubDVertex*>(edge1->m_vertex[1]);
      if ( nullptr == vertex1 )
        return ON_SUBD_RETURN_ERROR(false);
      ON_SubDVertex* vertex2 = m_fsh.AllocateVertex(face0,subd_type,bUseSavedSubdivisionPoint,3,2);
      if ( nullptr == vertex2 )
        return ON_SUBD_RETURN_ERROR(false);
      // vertex1 is from subdividing an edge that radiates out from the input face.
      edge1 = m_fsh.AllocateEdge( vertex1, at_crease2_weight, vertex2, ON_SubDSectorType::IgnoredSectorWeight );
      if ( nullptr == edge1 )
        return ON_SUBD_RETURN_ERROR(false);
      face1_eptrs[1] = ON_SubDEdgePtr::Create(edge1,0);
      ON_SubDVertex* vertex3 = m_fsh.AllocateVertex(edge0,subd_type,bUseSavedSubdivisionPoint,3,2);
      if ( nullptr == vertex3 )
        return ON_SUBD_RETURN_ERROR(false);
      edge1 = m_fsh.AllocateEdge(vertex2,ON_SubDSectorType::IgnoredSectorWeight,vertex3,ON_SubDSectorType::IgnoredSectorWeight);
      if ( nullptr == edge1 )
        return ON_SUBD_RETURN_ERROR(false);
      face1_eptrs[2] = ON_SubDEdgePtr::Create(edge1,0);
      edge1 = m_fsh.AllocateEdge(vertex1_trio[1],sector_weight,vertex3,ON_SubDSectorType::IgnoredSectorWeight);
      if ( nullptr == edge1 )
        return ON_SUBD_RETURN_ERROR(false);
      face1_eptrs[3] = ON_SubDEdgePtr::Create(edge1,1);

      face1 = m_fsh.AllocateQuad(zero_face_id, parent_face_id, face1_eptrs);
      if ( nullptr == face1 )
        return ON_SUBD_RETURN_ERROR(false);
    }
    if ( !bFinishedCorner )
      return ON_SUBD_RETURN_ERROR(false);
  }

  m_center_vertex1 = center_vertex1;
  m_subd_type = subd_type;
  m_face0 = face;
  m_face1 = m_center_vertex1->m_faces;
  m_face1_count = m_center_vertex1->m_face_count;

  return true;
}

#endif
