mirror of
https://github.com/mcneel/opennurbs.git
synced 2026-03-02 12:37:02 +08:00
5589 lines
140 KiB
C++
5589 lines
140 KiB
C++
#include "opennurbs.h"
|
|
|
|
#if !defined(ON_COMPILING_OPENNURBS)
|
|
// This check is included in all opennurbs source .c and .cpp files to insure
|
|
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
|
|
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined
|
|
// and the opennurbs .h files alter what is declared and how it is declared.
|
|
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
|
|
#endif
|
|
|
|
ON_MeshNgonBuffer::ON_MeshNgonBuffer()
|
|
{
|
|
memset(m_ngon_buffer,0,sizeof(m_ngon_buffer));
|
|
}
|
|
|
|
const class ON_MeshNgon* ON_MeshNgonBuffer::Ngon() const
|
|
{
|
|
return (const class ON_MeshNgon*)m_ngon_buffer;
|
|
}
|
|
|
|
const class ON_MeshNgon* ON_MeshNgonBuffer::CreateFromMeshFaceIndex(
|
|
const class ON_Mesh* mesh,
|
|
unsigned int face_index
|
|
)
|
|
{
|
|
const class ON_MeshFace* mesh_face = ( mesh && face_index < mesh->m_F.UnsignedCount() )
|
|
? &mesh->m_F[face_index]
|
|
: 0;
|
|
if (nullptr == mesh_face || false == mesh_face->IsValid(mesh->m_V.Count()))
|
|
return nullptr;
|
|
return CreateFromMeshFace(mesh_face,face_index);
|
|
}
|
|
|
|
const class ON_MeshNgon* ON_MeshNgonBuffer::CreateFromMeshFace(
|
|
const class ON_MeshFace* mesh_face,
|
|
unsigned int face_index
|
|
)
|
|
{
|
|
return CreateFromQuad(mesh_face ? ((const unsigned int*)mesh_face->vi) : 0, face_index );
|
|
}
|
|
|
|
const class ON_MeshNgon* ON_MeshNgonBuffer::CreateFromTriangle(
|
|
const unsigned int triangle_vertex_indices[3],
|
|
unsigned int face_index
|
|
)
|
|
{
|
|
if ( 0 != triangle_vertex_indices )
|
|
{
|
|
const unsigned int quad_vertex_indices[4] = {
|
|
triangle_vertex_indices[0],
|
|
triangle_vertex_indices[1],
|
|
triangle_vertex_indices[2],
|
|
0
|
|
};
|
|
return ON_MeshNgon::NgonFromMeshFace(*this,face_index,quad_vertex_indices);
|
|
}
|
|
return ON_MeshNgon::NgonFromMeshFace(*this,face_index,0);
|
|
}
|
|
|
|
const class ON_MeshNgon* ON_MeshNgonBuffer::CreateFromQuad(
|
|
const unsigned int quad_vertex_indices[4],
|
|
unsigned int face_index
|
|
)
|
|
{
|
|
memset(m_ngon_buffer,0,sizeof(m_ngon_buffer));
|
|
return ON_MeshNgon::NgonFromMeshFace(*this,face_index,quad_vertex_indices);
|
|
}
|
|
|
|
ON__UINT32 ON_MeshNgon::CRC32() const
|
|
{
|
|
ON__UINT32 crc = 0;
|
|
if ( m_Vcount > 0 && m_vi )
|
|
crc = ON_CRC32(crc,m_Vcount*sizeof(m_vi[0]),m_vi);
|
|
if ( m_Fcount > 0 && m_fi )
|
|
crc = ON_CRC32(crc,m_Fcount*sizeof(m_fi[0]),m_fi);
|
|
return crc;
|
|
}
|
|
|
|
ON_SHA1_Hash ON_MeshNgon::ContentHash() const
|
|
{
|
|
ON_SHA1 sha1;
|
|
if ( m_Vcount > 0 && m_vi )
|
|
sha1.AccumulateBytes(m_vi,m_Vcount*sizeof(m_vi[0]));
|
|
if ( m_Fcount > 0 && m_fi )
|
|
sha1.AccumulateBytes(m_fi,m_Fcount*sizeof(m_fi[0]));
|
|
return sha1.Hash();
|
|
}
|
|
|
|
static int Internal_compare_ngon_edges(const void* a, const void* b)
|
|
{
|
|
const unsigned int* ai = (const unsigned int*)a;
|
|
const unsigned int* bi = (const unsigned int*)b;
|
|
if (*ai < *bi)
|
|
return -1;
|
|
if (*ai > *bi)
|
|
return 1;
|
|
ai++;
|
|
bi++;
|
|
if (*ai < *bi)
|
|
return -1;
|
|
if (*ai > *bi)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
unsigned int ON_MeshNgon::BoundaryEdgeCount(const ON_MeshFaceList & mesh_face_list) const
|
|
{
|
|
if (m_Fcount <= 0 || nullptr == m_fi)
|
|
return 0;
|
|
const unsigned int mesh_face_count = mesh_face_list.FaceCount();
|
|
if (mesh_face_count <= 0)
|
|
return 0;
|
|
|
|
const unsigned int edge_capacity = 4 * mesh_face_count;
|
|
ON_SimpleArray<ON_2udex> edge_buffer(edge_capacity);
|
|
edge_buffer.SetCount(edge_capacity);
|
|
ON_2udex* edges = edge_buffer.Array();
|
|
unsigned int edge_count = 0;
|
|
unsigned int face_vi[4];
|
|
|
|
ON_2udex e;
|
|
for (unsigned int i = 0; i < m_Fcount; i++)
|
|
{
|
|
mesh_face_list.QuadFvi(m_fi[i], face_vi);
|
|
e.j = face_vi[3];
|
|
for (unsigned int j = 0; j < 4; j++)
|
|
{
|
|
e.i = e.j;
|
|
e.j = face_vi[j];
|
|
if (e.i == e.j)
|
|
continue;
|
|
if (e.i < e.j)
|
|
{
|
|
edges[edge_count] = e;
|
|
}
|
|
else
|
|
{
|
|
edges[edge_count].i = e.j;
|
|
edges[edge_count].j = e.i;
|
|
}
|
|
edge_count++;
|
|
}
|
|
}
|
|
|
|
ON_qsort(edges, edge_count, sizeof(edges[0]), Internal_compare_ngon_edges);
|
|
unsigned int boundary_edge_count = 0;
|
|
|
|
for (unsigned int i = 0; i < edge_count; /*empty iterator*/)
|
|
{
|
|
e = edges[i++];
|
|
const unsigned int i1 = i;
|
|
for (/*empty init*/; i < edge_count; i++)
|
|
{
|
|
if (edges[i].i != e.i || edges[i].j != e.j)
|
|
break;
|
|
}
|
|
if (i1 == i)
|
|
boundary_edge_count++;
|
|
}
|
|
|
|
return boundary_edge_count;
|
|
}
|
|
|
|
unsigned int ON_MeshNgon::OuterBoundaryEdgeCount() const
|
|
{
|
|
return (m_Vcount >= 3 && nullptr != m_vi) ? m_Vcount : 0;
|
|
}
|
|
|
|
int ON_MeshNgon::Orientation(
|
|
const ON_Mesh* mesh,
|
|
bool bPermitHoles
|
|
) const
|
|
{
|
|
const ON_MeshFaceList face_list(mesh);
|
|
return Orientation(face_list, bPermitHoles);
|
|
}
|
|
|
|
|
|
static int ON_Internal_CompareNgonEdge(
|
|
const void* lhs,
|
|
const void* rhs
|
|
)
|
|
{
|
|
const int i = (((const unsigned int*)lhs)[0] <= ((const unsigned int*)lhs)[1]) ? 0 : 1;
|
|
const int j = (((const unsigned int*)rhs)[0] <= ((const unsigned int*)rhs)[1]) ? 0 : 1;
|
|
if (((const unsigned int*)lhs)[i] < ((const unsigned int*)rhs)[j])
|
|
return -1;
|
|
if (((const unsigned int*)lhs)[i] > ((const unsigned int*)rhs)[j])
|
|
return 1;
|
|
if (((const unsigned int*)lhs)[1-i] < ((const unsigned int*)rhs)[1-j])
|
|
return -1;
|
|
if (((const unsigned int*)lhs)[1-i] > ((const unsigned int*)rhs)[1-j])
|
|
return 1;
|
|
if (((const unsigned int*)lhs)[2] < ((const unsigned int*)rhs)[2])
|
|
return -1;
|
|
if (((const unsigned int*)lhs)[2] > ((const unsigned int*)rhs)[2])
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int ON_Internal_MeshNgon_NotOriented()
|
|
{
|
|
// optimized away in release builds
|
|
return 0; // nice place for a breakpoint.
|
|
}
|
|
|
|
int ON_MeshNgon::Orientation(
|
|
const ON_MeshFaceList& mesh_face_list,
|
|
bool bPermitHoles
|
|
) const
|
|
{
|
|
if (m_Fcount < 1 || nullptr == m_fi)
|
|
return ON_Internal_MeshNgon_NotOriented();
|
|
|
|
if (m_Vcount < 3 || nullptr == m_vi)
|
|
return ON_Internal_MeshNgon_NotOriented();
|
|
|
|
const unsigned int mesh_Fcount = mesh_face_list.FaceCount();
|
|
|
|
if (mesh_Fcount < m_Fcount)
|
|
return ON_Internal_MeshNgon_NotOriented();
|
|
|
|
unsigned int face_vi[4];
|
|
if (1 == m_Fcount && m_Vcount >= 3 && m_Vcount <= 4)
|
|
{
|
|
// fast check for common case of a single face ngon
|
|
mesh_face_list.QuadFvi(m_fi[0], face_vi);
|
|
if (m_vi[0] == face_vi[0]
|
|
&& m_vi[1] == face_vi[1]
|
|
&& m_vi[2] == face_vi[2]
|
|
&& m_vi[(3 % m_Vcount)] == face_vi[3]
|
|
)
|
|
{
|
|
// ngon = mesh face
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
const int a_capacity = (int)(4 * m_Fcount + 2 * m_Vcount);
|
|
|
|
ON_SimpleArray<ON_3udex> buffer;
|
|
buffer.Reserve(a_capacity);
|
|
ON_3udex* edge = buffer.Array();
|
|
|
|
|
|
ON_3udex e;
|
|
|
|
unsigned int edge_count = 0;
|
|
|
|
e.k = 0; // from a face
|
|
for (unsigned int nfi = 0; nfi < m_Fcount; nfi++)
|
|
{
|
|
unsigned int fi = m_fi[nfi];
|
|
if (fi >= mesh_Fcount)
|
|
return ON_Internal_MeshNgon_NotOriented(); // invalid ngon m_fi[] value.
|
|
mesh_face_list.QuadFvi(fi, face_vi);
|
|
const unsigned int fvi_count = face_vi[2] != face_vi[3] ? 4 : 3;
|
|
e.j = face_vi[3];
|
|
for (unsigned int fvi = 0; fvi < fvi_count; fvi++)
|
|
{
|
|
e.i = e.j;
|
|
e.j = face_vi[fvi];
|
|
if (e.i == e.j)
|
|
return ON_Internal_MeshNgon_NotOriented(); // invalid face
|
|
edge[edge_count++] = e;
|
|
}
|
|
}
|
|
|
|
e.k = 1; // from ngon boundary
|
|
e.j = m_vi[m_Vcount - 1];
|
|
for (unsigned int nvi = 0; nvi < m_Vcount; nvi++)
|
|
{
|
|
e.i = e.j;
|
|
e.j = m_vi[nvi];
|
|
if (e.i == e.j)
|
|
return ON_Internal_MeshNgon_NotOriented(); // ngon m_vi[] has duplicates
|
|
edge[edge_count++] = e;
|
|
}
|
|
|
|
if (edge_count < 6
|
|
|| (false == bPermitHoles && 0 != (edge_count % 2))
|
|
)
|
|
{
|
|
return ON_Internal_MeshNgon_NotOriented();
|
|
}
|
|
|
|
ON_qsort(edge, edge_count, sizeof(edge[0]), ON_Internal_CompareNgonEdge);
|
|
e.i = 0;
|
|
e.j = 0;
|
|
e.k = 0;
|
|
int orientation = 0;
|
|
unsigned int interior_boundary_edge_count = 0;
|
|
unsigned int outer_boundary_edge_count = 0;
|
|
for (unsigned int ei = 0; ei < edge_count; ei++)
|
|
{
|
|
if (0 != edge[ei].k )
|
|
return ON_Internal_MeshNgon_NotOriented(); // invalid ngon boundary.
|
|
|
|
e.k = 0;
|
|
if (0 == ON_Internal_CompareNgonEdge(&e, &edge[ei]))
|
|
{
|
|
// non-manifold or invalid
|
|
return ON_Internal_MeshNgon_NotOriented();
|
|
}
|
|
|
|
if (ei + 1 >= edge_count)
|
|
{
|
|
// last edge;
|
|
if (bPermitHoles)
|
|
{
|
|
interior_boundary_edge_count++;
|
|
break;
|
|
}
|
|
return ON_Internal_MeshNgon_NotOriented();
|
|
}
|
|
|
|
e = edge[ei+1];
|
|
if (0 == e.k)
|
|
{
|
|
// edge[ei + 1] is a face edge
|
|
const int c = ON_Internal_CompareNgonEdge(&edge[ei], &e);
|
|
if (c > 0)
|
|
{
|
|
ON_ERROR("bug in this code");
|
|
return ON_Internal_MeshNgon_NotOriented();
|
|
}
|
|
if (c < 0)
|
|
{
|
|
// face edge with no matching edge on another face or ngon outer boundary
|
|
if (bPermitHoles)
|
|
{
|
|
// assume it is interior
|
|
interior_boundary_edge_count++;
|
|
continue;
|
|
}
|
|
|
|
// face edge with no matching edge
|
|
return ON_Internal_MeshNgon_NotOriented();
|
|
}
|
|
|
|
// edge[ei] and e are face edges and they should have opposite orientations.
|
|
if (e.j != edge[ei].i || e.i != edge[ei].j)
|
|
{
|
|
// face set is not oriented, it's a hole and bPermitHoles=false, or other invalid situation
|
|
return ON_Internal_MeshNgon_NotOriented();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// edge[ei + 1] is an ngon outer boundary edge
|
|
outer_boundary_edge_count++;
|
|
if (1 == orientation)
|
|
{
|
|
// edge[ei] is a face edge, e is an ngon outer boundary edge, and they should have the same orientations
|
|
if (e.i != edge[ei].i || e.j != edge[ei].j)
|
|
return ON_Internal_MeshNgon_NotOriented(); // ngon boundary orientation is not consistent or face set is not oriented.
|
|
}
|
|
else if (-1 == orientation)
|
|
{
|
|
// edge[ei] is a face edge, e is an ngon outer boundary edge, and they should have opposite orientations
|
|
if (e.j != edge[ei].i || e.i != edge[ei].j)
|
|
return ON_Internal_MeshNgon_NotOriented(); // ngon boundary orientation is not consistent or face set is not oriented.
|
|
}
|
|
else
|
|
{
|
|
// edge[ei] is a face edge, e is an ngon outer boundary edge,
|
|
// and e is the first ngon ngon outer boundary edge encountered.
|
|
if (e.i == edge[ei].i && e.j == edge[ei].j)
|
|
orientation = 1; // this ngon edge orientation is the same as the face's edge.
|
|
else if (e.j == edge[ei].i && e.i == edge[ei].j)
|
|
orientation = -1; // this ngon edge orientation is opposite the face's edge.
|
|
else
|
|
return ON_Internal_MeshNgon_NotOriented(); // the ngon boundary edge e is not an edge of a face
|
|
}
|
|
}
|
|
ei++;
|
|
}
|
|
|
|
if ( 0 == orientation )
|
|
return ON_Internal_MeshNgon_NotOriented();
|
|
|
|
if ( outer_boundary_edge_count != m_Vcount )
|
|
return ON_Internal_MeshNgon_NotOriented();
|
|
|
|
if ( outer_boundary_edge_count != m_Vcount )
|
|
return ON_Internal_MeshNgon_NotOriented();
|
|
|
|
if (interior_boundary_edge_count >= 1 && interior_boundary_edge_count <= 2)
|
|
{
|
|
// a valid interior bound must have at least 3 edges
|
|
return ON_Internal_MeshNgon_NotOriented();
|
|
}
|
|
|
|
return orientation;
|
|
}
|
|
|
|
|
|
unsigned int ON_MeshNgon::BoundaryEdgeCount(const ON_Mesh * mesh) const
|
|
{
|
|
const ON_MeshFaceList face_list(mesh);
|
|
return BoundaryEdgeCount(face_list);
|
|
}
|
|
|
|
unsigned ON_Mesh::NgonBoundaryEdgeCount(unsigned int ngon_index) const
|
|
{
|
|
const ON_MeshNgon* ngon = Ngon(ngon_index);
|
|
return (nullptr == ngon) ? 0 : ngon->BoundaryEdgeCount(this);
|
|
}
|
|
|
|
unsigned int ON_MeshNgon::Capacity() const
|
|
{
|
|
if ( (0 != m_vi || 0 != m_fi) )
|
|
{
|
|
unsigned int capacity;
|
|
const unsigned int* a = (const unsigned int*)(this+1);
|
|
if ( (m_vi == a+1 || (0 == m_Vcount && 0 == m_vi && m_fi == a+1)) && *a >= 7 )
|
|
{
|
|
capacity = (unsigned int)*a++;
|
|
if ( 7 == capacity || 15 == capacity || 31 == capacity || (capacity >= 63 && 7 == (capacity%8) && capacity < 0x100000U) )
|
|
{
|
|
if ( capacity >= m_Vcount + m_Fcount
|
|
&& ((0 == m_Fcount && 0 == m_fi) || (m_fi >= m_vi + m_Vcount && m_fi < a + capacity))
|
|
)
|
|
return capacity;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ON_MeshNgon::Compare(
|
|
const ON_MeshNgon* A,
|
|
const ON_MeshNgon* B
|
|
)
|
|
{
|
|
const unsigned int* pA;
|
|
const unsigned int* pB;
|
|
int c;
|
|
if ( 0 == A )
|
|
{
|
|
return ( 0 == B ) ? 0 : -1;
|
|
}
|
|
|
|
if ( 0 == B )
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
if ( A->m_Vcount < B->m_Vcount )
|
|
return -1;
|
|
if ( A->m_Vcount > B->m_Vcount )
|
|
return 1;
|
|
if ( A->m_Fcount < B->m_Fcount )
|
|
return -1;
|
|
if ( A->m_Fcount > B->m_Fcount )
|
|
return -1;
|
|
|
|
pA = A->m_vi;
|
|
pB = B->m_vi;
|
|
if ( 0 == pA )
|
|
{
|
|
return ( 0 == pB ) ? 0 : -1;
|
|
}
|
|
if ( 0 == pB )
|
|
{
|
|
return 1;
|
|
}
|
|
c = A->m_Vcount;
|
|
while (c--)
|
|
{
|
|
if ( *pA < *pB )
|
|
return -1;
|
|
if ( *pA > *pB )
|
|
return 1;
|
|
}
|
|
|
|
pA = A->m_fi;
|
|
pB = B->m_fi;
|
|
if ( 0 == pA )
|
|
{
|
|
return ( 0 == pB ) ? 0 : -1;
|
|
}
|
|
if ( 0 == pB )
|
|
{
|
|
return 1;
|
|
}
|
|
c = A->m_Fcount;
|
|
while (c--)
|
|
{
|
|
if ( *pA < *pB )
|
|
return -1;
|
|
if ( *pA > *pB )
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static char* ToStringHelper( const char* src_str, char* dst_str, const char* dst_str_end )
|
|
{
|
|
if ( 0 == dst_str || dst_str_end <= dst_str )
|
|
return 0;
|
|
|
|
if ( 0 == src_str )
|
|
src_str = "";
|
|
while ( dst_str < dst_str_end )
|
|
{
|
|
if ( 0 == (*dst_str = *src_str++) )
|
|
return dst_str;
|
|
dst_str++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static char* ToStringHelper( unsigned int u, char* dst_str, const char* dst_str_end )
|
|
{
|
|
char ubuffer[32]; // unsigned int to string storage buffer
|
|
unsigned int i, j;
|
|
|
|
if ( ON_UNSET_UINT_INDEX == u )
|
|
return ToStringHelper( "unset", dst_str, dst_str_end );
|
|
|
|
i = u;
|
|
j = (unsigned int)(sizeof(ubuffer)/sizeof(ubuffer[0]));
|
|
j--;
|
|
ubuffer[j] = 0;
|
|
while(j > 0)
|
|
{
|
|
j--;
|
|
ubuffer[j] = (char)('0'+i%10);
|
|
i /= 10;
|
|
if ( 0 == i )
|
|
return ToStringHelper( &ubuffer[j], dst_str, dst_str_end );
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static char* ToStringHelper( const unsigned int* a, unsigned int count, char* dst_str, const char* dst_str_end )
|
|
{
|
|
unsigned int i, imax;
|
|
|
|
dst_str = ToStringHelper("[",dst_str,dst_str_end);
|
|
dst_str = ToStringHelper(count,dst_str,dst_str_end);
|
|
dst_str = ToStringHelper("]=",dst_str,dst_str_end);
|
|
if ( 0 == a )
|
|
dst_str = ToStringHelper("null",dst_str,dst_str_end);
|
|
else
|
|
{
|
|
dst_str = ToStringHelper("(",dst_str,dst_str_end);
|
|
if ( ON_UNSET_UINT_INDEX == count )
|
|
{
|
|
dst_str = ToStringHelper("?",dst_str,dst_str_end);
|
|
}
|
|
else
|
|
{
|
|
imax = (count <= 7 ? count : 4 );
|
|
for ( i = 0; i < imax; i++ )
|
|
{
|
|
if ( i )
|
|
dst_str = ToStringHelper(",",dst_str,dst_str_end);
|
|
dst_str = ToStringHelper(a[i],dst_str,dst_str_end);
|
|
}
|
|
if ( i < count )
|
|
{
|
|
dst_str = ToStringHelper(",...,",dst_str,dst_str_end);
|
|
dst_str = ToStringHelper(a[count-1],dst_str,dst_str_end);
|
|
}
|
|
}
|
|
dst_str = ToStringHelper(")",dst_str,dst_str_end);
|
|
}
|
|
|
|
return dst_str;
|
|
}
|
|
|
|
static char* ToStringHelper( const ON_MeshNgon* ngon, char* buffer, size_t sizeof_buffer)
|
|
{
|
|
if ( 0 == buffer || sizeof_buffer <= 0 )
|
|
return 0;
|
|
|
|
char* dst_str = buffer;
|
|
char* dst_str_end = dst_str + ((sizeof_buffer/sizeof(buffer[0])) - 1);
|
|
unsigned int capacity;
|
|
|
|
if ( 0 == ngon )
|
|
return ToStringHelper("null",dst_str,dst_str_end);
|
|
else
|
|
{
|
|
dst_str = ToStringHelper("m_vi",dst_str,dst_str_end);
|
|
dst_str = ToStringHelper(ngon->m_vi,ngon->m_Vcount,dst_str,dst_str_end);
|
|
dst_str = ToStringHelper(" ",dst_str,dst_str_end);
|
|
dst_str = ToStringHelper("m_fi",dst_str,dst_str_end);
|
|
dst_str = ToStringHelper(ngon->m_fi,ngon->m_Fcount,dst_str,dst_str_end);
|
|
|
|
capacity = ngon->Capacity();
|
|
if ( capacity > 0 )
|
|
{
|
|
dst_str = ToStringHelper(" capacity=",dst_str,dst_str_end);
|
|
dst_str = ToStringHelper(capacity,dst_str,dst_str_end);
|
|
}
|
|
}
|
|
return dst_str;
|
|
}
|
|
|
|
ON_String ON_MeshNgon::ToString() const
|
|
{
|
|
// supports this == null
|
|
char buffer[256];
|
|
return ( 0 == ToStringHelper(this,buffer,sizeof(buffer)) )
|
|
? ON_String::EmptyString
|
|
: ON_String(buffer);
|
|
}
|
|
|
|
ON_wString ON_MeshNgon::ToWideString() const
|
|
{
|
|
// supports this == null
|
|
char buffer[256];
|
|
return ( 0 == ToStringHelper(this,buffer,sizeof(buffer)) )
|
|
? ON_wString::EmptyString
|
|
: ON_wString(buffer);
|
|
}
|
|
|
|
void ON_MeshNgon::Dump(
|
|
class ON_TextLog& text_log
|
|
)const
|
|
{
|
|
// supports this == null
|
|
char buffer[256];
|
|
if ( 0 != ToStringHelper(this,buffer,sizeof(buffer)) )
|
|
text_log.Print("%s",buffer);
|
|
}
|
|
|
|
void ON_MeshNgon::AppendToString(
|
|
class ON_String& s
|
|
)const
|
|
{
|
|
// supports this == null
|
|
char buffer[256];
|
|
if ( 0 != ToStringHelper(this,buffer,sizeof(buffer)) )
|
|
s += buffer;
|
|
}
|
|
|
|
void ON_MeshNgon::AppendToString(
|
|
class ON_wString& s
|
|
)const
|
|
{
|
|
// supports this == null
|
|
char buffer[256];
|
|
if ( 0 != ToStringHelper(this,buffer,sizeof(buffer)) )
|
|
s += buffer;
|
|
}
|
|
|
|
unsigned int ON_Mesh::NgonUnsignedCount() const
|
|
{
|
|
return m_Ngon.UnsignedCount();
|
|
}
|
|
|
|
int ON_Mesh::NgonCount() const
|
|
{
|
|
return m_Ngon.Count();
|
|
}
|
|
|
|
const ON_MeshNgon* const * ON_Mesh::Ngons() const
|
|
{
|
|
return m_Ngon.Array();
|
|
}
|
|
|
|
const ON_MeshNgon* ON_Mesh::Ngon(
|
|
unsigned int ngon_index
|
|
) const
|
|
{
|
|
return (ngon_index < m_Ngon.UnsignedCount()) ? m_Ngon[ngon_index] : 0;
|
|
}
|
|
|
|
|
|
bool ON_Mesh::IsValidNewNgonInformation(
|
|
unsigned int Vcount,
|
|
const unsigned int* ngon_vi,
|
|
unsigned int Fcount,
|
|
const unsigned int* ngon_fi
|
|
) const
|
|
{
|
|
unsigned int i;
|
|
const unsigned int meshVcount = m_V.UnsignedCount();
|
|
const unsigned int meshFcount = m_F.UnsignedCount();
|
|
if ( Vcount < 3 )
|
|
return false;
|
|
|
|
for ( i = 0; i < Vcount; i++ )
|
|
{
|
|
if ( ngon_vi[i] >= meshVcount )
|
|
return false;
|
|
}
|
|
|
|
const unsigned int* ngon_map = (Fcount > 0) ? NgonMap() : 0;
|
|
|
|
for ( i = 0; i < Fcount; i++ )
|
|
{
|
|
if ( ngon_fi[i] >= meshFcount )
|
|
return false;
|
|
if ( ngon_map && ON_UNSET_UINT_INDEX != ngon_map[ngon_fi[i]] )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ON_Mesh::RemoveNgonMap()
|
|
{
|
|
m_NgonMap.Destroy();
|
|
}
|
|
|
|
const unsigned int* ON_Mesh::NgonMap() const
|
|
{
|
|
if ( m_Ngon.UnsignedCount() > 0 && m_F.UnsignedCount() == m_NgonMap.UnsignedCount() )
|
|
return m_NgonMap.Array();
|
|
return 0;
|
|
}
|
|
|
|
unsigned int ON_Mesh::NgonIndexFromFaceIndex(
|
|
unsigned int face_index
|
|
) const
|
|
{
|
|
unsigned int ngon_index = ON_UNSET_UINT_INDEX;
|
|
const unsigned int meshFcount = m_F.UnsignedCount();
|
|
const unsigned int ngonCount = m_Ngon.UnsignedCount();
|
|
if (face_index < meshFcount && ngonCount > 0)
|
|
{
|
|
if (meshFcount == m_NgonMap.UnsignedCount())
|
|
{
|
|
// fast way - use m_NgonMap[]
|
|
ngon_index = m_NgonMap[face_index];
|
|
}
|
|
else
|
|
{
|
|
// slow way - search
|
|
// cannot build ngon map without making it a piece of thread safe
|
|
// cached information.
|
|
for (unsigned int ni = 0; ni < ngonCount; ni++)
|
|
{
|
|
const ON_MeshNgon* ngon = m_Ngon[ni];
|
|
if (0 == ngon || 0 == ngon->m_fi)
|
|
continue;
|
|
for (unsigned int nfi = 0; nfi < ngon->m_Fcount; nfi++)
|
|
{
|
|
if (face_index == ngon->m_fi[nfi])
|
|
return ni;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ngon_index;
|
|
}
|
|
|
|
const unsigned int* ON_Mesh::NgonMap(
|
|
bool bCreateIfNeeded
|
|
)
|
|
{
|
|
const unsigned int* fdex_to_ndex_map = NgonMap();
|
|
if ( 0 == fdex_to_ndex_map && bCreateIfNeeded )
|
|
fdex_to_ndex_map = CreateNgonMap();
|
|
return fdex_to_ndex_map;
|
|
}
|
|
|
|
bool ON_Mesh::CreateNgonMap(ON_SimpleArray<unsigned int>& map) const
|
|
{
|
|
unsigned int* ngon_map = 0;
|
|
const ON_MeshNgon* ngon;
|
|
unsigned int fi;
|
|
unsigned int ngon_index;
|
|
unsigned int j;
|
|
|
|
const unsigned int ngon_count = m_Ngon.UnsignedCount();
|
|
|
|
const unsigned int meshFcount = m_F.UnsignedCount();
|
|
if (meshFcount <= 0)
|
|
{
|
|
map.SetCount(0);
|
|
return false;
|
|
}
|
|
|
|
map.Reserve(meshFcount);
|
|
map.SetCount(meshFcount);
|
|
ngon_map = map.Array();
|
|
|
|
for ( fi = 0; fi < meshFcount; fi++ )
|
|
ngon_map[fi] = ON_UNSET_UINT_INDEX;
|
|
|
|
const ON_MeshNgon*const* ngons = m_Ngon.Array();
|
|
for ( ngon_index = 0; ngon_index < ngon_count; ngon_index++ )
|
|
{
|
|
ngon = ngons[ngon_index];
|
|
if ( 0 == ngon || 0 == ngon->m_fi )
|
|
continue;
|
|
for ( j = 0; j < ngon->m_Fcount; j++ )
|
|
{
|
|
fi = ngon->m_fi[j];
|
|
if (fi < meshFcount)
|
|
{
|
|
if (ON_UNSET_UINT_INDEX == ngon_map[fi])
|
|
ngon_map[fi] = ngon_index;
|
|
else
|
|
{
|
|
ON_ERROR("mesh face referenced more than one time by an ngon.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ON_Mesh::CreateNgonMap(unsigned int* map) const
|
|
{
|
|
ON_SimpleArray<unsigned int> new_map {};
|
|
new_map.SetArray(map, m_F.Count(), m_F.Count());
|
|
|
|
if (!CreateNgonMap(new_map)) return false;
|
|
new_map.KeepArray();
|
|
return true;
|
|
}
|
|
|
|
const unsigned int* ON_Mesh::CreateNgonMap()
|
|
{
|
|
if (!CreateNgonMap(m_NgonMap)) return nullptr;
|
|
|
|
return m_NgonMap.Array();
|
|
}
|
|
|
|
|
|
|
|
bool ON_Mesh::ModifyNgon(
|
|
unsigned int ngon_index,
|
|
unsigned int Vcount,
|
|
const unsigned int* ngon_vi,
|
|
unsigned int Fcount,
|
|
const unsigned int* ngon_fi
|
|
)
|
|
{
|
|
ON_MeshNgon ngon;
|
|
ngon.m_Vcount = Vcount;
|
|
ngon.m_vi = (unsigned int*)ngon_vi;
|
|
ngon.m_Fcount = Fcount;
|
|
ngon.m_fi = (unsigned int*)ngon_fi;
|
|
bool rc = ModifyNgon(ngon_index,&ngon);
|
|
ngon.m_vi = 0;
|
|
ngon.m_fi = 0;
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Mesh::InsertNgon(
|
|
unsigned int ngon_index,
|
|
const ON_MeshNgon* ngon
|
|
)
|
|
{
|
|
if ( ngon_index >= ON_UNSET_UINT_INDEX )
|
|
return false;
|
|
|
|
if ( ngon_index >= m_Ngon.UnsignedCount() )
|
|
{
|
|
SetNgonCount(ngon_index+1);
|
|
}
|
|
else
|
|
{
|
|
bool bUpdateNgonMap = (m_NgonMap.UnsignedCount() == m_F.UnsignedCount());
|
|
m_Ngon.Insert(ngon_index,0);
|
|
if (bUpdateNgonMap)
|
|
CreateNgonMap();
|
|
}
|
|
return ModifyNgon(ngon_index,ngon);
|
|
}
|
|
|
|
bool ON_Mesh::ModifyNgon(
|
|
unsigned int ngon_index,
|
|
const ON_MeshNgon* ngon
|
|
)
|
|
{
|
|
ON_MeshNgon* ngon1 = 0;
|
|
|
|
if ( ngon_index >= m_Ngon.UnsignedCount() )
|
|
return false;
|
|
|
|
if ( m_Ngon[ngon_index] )
|
|
RemoveNgon(ngon_index);
|
|
|
|
if ( 0 == ngon )
|
|
return true;
|
|
|
|
if ( 0 == ngon->m_Vcount || 0 == ngon->m_Fcount )
|
|
return true;
|
|
|
|
if ( IsValidNewNgonInformation(ngon->m_Vcount,ngon->m_vi,ngon->m_Fcount,ngon->m_fi) )
|
|
{
|
|
ngon1 = m_NgonAllocator.CopyNgon(ngon);
|
|
if ( ngon1 )
|
|
{
|
|
const unsigned int meshFcount = m_F.UnsignedCount();
|
|
unsigned int* ngon_map = (meshFcount > 0 && meshFcount == m_NgonMap.UnsignedCount()) ? m_NgonMap.Array() : 0;
|
|
if ( ngon_map )
|
|
{
|
|
for ( unsigned j = 0; j < ngon->m_Fcount; j++ )
|
|
{
|
|
unsigned int fi = ngon->m_fi[j];
|
|
if ( fi < meshFcount )
|
|
ngon_map[fi] = ngon_index;
|
|
}
|
|
}
|
|
|
|
m_Ngon[ngon_index] = ngon1;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool Internal_ListContainNgon(
|
|
unsigned int count,
|
|
const int* fi,
|
|
ON_SimpleArray< const ON_MeshNgon* >& tested_ngons,
|
|
const ON_MeshNgon* ngon
|
|
)
|
|
{
|
|
if (nullptr == ngon || ngon->m_Fcount <= 0)
|
|
return true; // nothing to test.
|
|
|
|
if (ngon->m_Fcount > count)
|
|
return false;
|
|
|
|
if (tested_ngons.Search(ngon) >= 0)
|
|
return true; // already tested this ngon
|
|
|
|
// fi[] is a sorted list
|
|
for (unsigned int k = 0; k < ngon->m_Fcount; ++k)
|
|
{
|
|
if (nullptr == ON_BinarySearchIntArray((int)ngon->m_fi[k], fi, count))
|
|
return false; // ngon->m_fi[k] is not in fi[]
|
|
}
|
|
|
|
if (ngon->m_Fcount > 1)
|
|
tested_ngons.Append(ngon); // no need to test this one again
|
|
|
|
// Every index in ngon->m_fi[] is also in fi[]
|
|
return true;
|
|
}
|
|
|
|
unsigned int ON_Mesh::AddNgons(
|
|
const ON_SimpleArray<ON_COMPONENT_INDEX>& ci_list
|
|
)
|
|
{
|
|
const int ci_count = ci_list.UnsignedCount();
|
|
if (ci_count < 2)
|
|
return 0;
|
|
|
|
const int face_count = FaceCount();
|
|
if (face_count < 2)
|
|
return 0;
|
|
const int ngon_count = NgonCount();
|
|
const unsigned int ngon_ucount = NgonUnsignedCount();
|
|
|
|
/////////////////////
|
|
//
|
|
// fi_list[] = list of faces to be merged into ngons
|
|
ON_SimpleArray<int> fi_list(ci_count);
|
|
ON_COMPONENT_INDEX ci;
|
|
for (int i = 0; i < ci_count; ++i)
|
|
{
|
|
ci = ci_list[i];
|
|
if (ci.m_index < 0)
|
|
continue;
|
|
switch (ci.m_type)
|
|
{
|
|
case ON_COMPONENT_INDEX::TYPE::mesh_face:
|
|
if (ci.m_index < face_count)
|
|
fi_list.Append(ci.m_index);
|
|
break;
|
|
case ON_COMPONENT_INDEX::TYPE::mesh_ngon:
|
|
if (ci.m_index < ngon_count)
|
|
{
|
|
const ON_MeshNgon* ngon = Ngon(ci.m_index);
|
|
if (nullptr == ngon)
|
|
break;
|
|
for (unsigned j = 0; j < ngon->m_Fcount; ++j)
|
|
{
|
|
const int fi = ngon->m_fi[j];
|
|
if (fi >= 0 && fi < face_count)
|
|
fi_list.Append(fi);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
fi_list.QuickSortAndRemoveDuplicates(ON_CompareIncreasing<int>);
|
|
const int fi_count = fi_list.Count();
|
|
if (fi_count < 2)
|
|
return 0;
|
|
|
|
/////////////////////
|
|
//
|
|
// Partition fi_list[] into subsets where each subset will be a new ngon.
|
|
const ON_MeshTopology& top = Topology();
|
|
if (face_count != top.m_topf.Count())
|
|
return 0;
|
|
|
|
const int edge_count = top.TopEdgeCount();
|
|
|
|
ON_SimpleArray<bool> face_available_buffer(face_count);
|
|
face_available_buffer.SetCount(face_count);
|
|
face_available_buffer.Zero();
|
|
bool* face_available = face_available_buffer.Array();
|
|
|
|
for (int i = 0; i < fi_count; ++i)
|
|
face_available[fi_list[i]] = true;
|
|
|
|
/////////////////////
|
|
//
|
|
// ngon_faces[] is the same set of indiced that are in fi_list[], but with indices for
|
|
// each new ngon grouped together.
|
|
// ngon_partition[] is used to find the new ngon subsets.
|
|
// If i0 = ngon_partition[j] && i1 = ngon_partition[j+1], then
|
|
// (ngon_faces[i0],...,ngon_faces[i1-1]) are the face indices for a new ngon.
|
|
//
|
|
ON_SimpleArray<int> ngon_faces(fi_count);
|
|
ON_SimpleArray<unsigned int> ngon_partition(64);
|
|
|
|
const unsigned int* ngon_map = (ngon_count > 0) ? NgonMap(true) : nullptr;
|
|
|
|
ON_SimpleArray< const ON_MeshNgon* > old_ngons_inside_new_ngon(32);
|
|
|
|
for (int i = 0; i < fi_count; ++i)
|
|
{
|
|
const int face_index = fi_list[i];
|
|
if (false == face_available[face_index])
|
|
continue; // face is no longer available.
|
|
|
|
// find the new ngon contain face_index.
|
|
face_available[face_index] = false;
|
|
const unsigned int count0 = ngon_faces.UnsignedCount();
|
|
unsigned int i0 = 0;
|
|
unsigned int i1 = count0;
|
|
ngon_partition.Append(count0);
|
|
ngon_faces.Append(face_index);
|
|
for (;;)
|
|
{
|
|
i0 = i1;
|
|
i1 = ngon_faces.Count();
|
|
if (i0 >= i1)
|
|
break;
|
|
for (/*empty init*/; i0 < i1; ++i0)
|
|
{
|
|
const int fi = ngon_faces[i0];
|
|
const ON_MeshTopologyFace& topf = top.m_topf[fi];
|
|
const int fe_count = topf.IsQuad() ? 4 : 3;
|
|
for (int fei = 0; fei < fe_count; ++fei)
|
|
{
|
|
int ei = topf.m_topei[fei];
|
|
if (ei < 0 || ei >= edge_count)
|
|
continue;
|
|
const ON_MeshTopologyEdge& tope = top.m_tope[ei];
|
|
if (2 != tope.m_topf_count)
|
|
continue;
|
|
if (2 != tope.m_topf_count || false == top.IsWeldedEdge(ei))
|
|
continue;
|
|
int neighbor_fi = tope.m_topfi[(fi == tope.m_topfi[0]) ? 1 : 0];
|
|
if (neighbor_fi < 0 || neighbor_fi >= face_count || false == face_available[neighbor_fi])
|
|
continue;
|
|
face_available[neighbor_fi] = false;
|
|
ngon_faces.Append(neighbor_fi);
|
|
}
|
|
}
|
|
}
|
|
|
|
const unsigned new_ngon_F_count = ngon_faces.UnsignedCount() - count0;
|
|
int* new_ngon_fi = ngon_faces.Array() + count0;
|
|
ON_SortIntArray(ON::sort_algorithm::quick_sort, new_ngon_fi, new_ngon_F_count);
|
|
bool bSkipNewNgon = (new_ngon_F_count < 2);
|
|
if (false == bSkipNewNgon && nullptr != ngon_map)
|
|
{
|
|
old_ngons_inside_new_ngon.SetCount(0);
|
|
|
|
// Make sure the new ngon doesn't contain proper subsets of existing ngons.
|
|
for (unsigned j = 0; j < new_ngon_F_count; ++j)
|
|
{
|
|
int fi = new_ngon_fi[j];
|
|
const unsigned int ngon_dex = ngon_map[fi];
|
|
if (ngon_dex >= ngon_ucount)
|
|
continue;
|
|
const ON_MeshNgon* existing_ngon = Ngon(ngon_dex);
|
|
if (nullptr == existing_ngon)
|
|
continue;
|
|
if (false == Internal_ListContainNgon(new_ngon_F_count, new_ngon_fi, old_ngons_inside_new_ngon, existing_ngon))
|
|
{
|
|
// existing ngon has faces that are not in new ngon.
|
|
bSkipNewNgon = true;
|
|
break;
|
|
}
|
|
if (existing_ngon->m_Fcount == new_ngon_F_count)
|
|
{
|
|
// new ngon and existing ngon are the same.
|
|
bSkipNewNgon = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (bSkipNewNgon)
|
|
{
|
|
ngon_faces.SetCount(count0);
|
|
ngon_partition.Remove();
|
|
}
|
|
}
|
|
|
|
if (ngon_partition.UnsignedCount() < 1 || ngon_faces.UnsignedCount() < 2)
|
|
return 0;
|
|
|
|
ngon_partition.Append(ngon_faces.UnsignedCount());
|
|
|
|
bool bRemoveddNgons = false;
|
|
if (nullptr != ngon_map)
|
|
{
|
|
// Delete ngons that are being merged into bigger ngons
|
|
ON_SimpleArray<bool> delete_old_ngon(ngon_count);
|
|
delete_old_ngon.SetCount(ngon_count);
|
|
for (int j = 0; j < ngon_faces.Count(); ++j)
|
|
{
|
|
unsigned ngon_dex = ngon_map[ngon_faces[j]];
|
|
if (ngon_dex < ngon_ucount)
|
|
delete_old_ngon[ngon_dex] = true;
|
|
}
|
|
for (int j = ngon_count; j > 0; --j)
|
|
{
|
|
unsigned int ngon_dex = (unsigned int)(j - 1);
|
|
if (delete_old_ngon[ngon_dex])
|
|
{
|
|
RemoveNgon(ngon_dex);
|
|
bRemoveddNgons = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add new ngons
|
|
unsigned int new_ngon_count = 0;
|
|
unsigned int i1 = ngon_partition[0];
|
|
for (unsigned int i = 1; i < ngon_partition.UnsignedCount(); ++i)
|
|
{
|
|
const unsigned int i0 = i1;
|
|
i1 = ngon_partition[i];
|
|
if (i0 + 2 <= i1)
|
|
{
|
|
if (AddNgon(i1 - i0, (const unsigned int*)(ngon_faces.Array() + i0)) >= 0)
|
|
++new_ngon_count;
|
|
}
|
|
}
|
|
|
|
if (bRemoveddNgons || new_ngon_count > 0)
|
|
{
|
|
// clean up ngon storage
|
|
m_NgonMap.SetCount(0);
|
|
int count = 0;
|
|
for (int i = 0; i < m_Ngon.Count(); ++i)
|
|
{
|
|
ON_MeshNgon* n = m_Ngon[i];
|
|
if (nullptr == n)
|
|
continue;
|
|
m_Ngon[count++] = n;
|
|
}
|
|
m_Ngon.SetCount(count);
|
|
NgonMap(true);
|
|
}
|
|
|
|
return new_ngon_count;
|
|
}
|
|
|
|
int ON_Mesh::AddNgon(const ON_SimpleArray<unsigned int>& ngon_fi)
|
|
{
|
|
return AddNgon(ngon_fi.UnsignedCount(), ngon_fi.Array());
|
|
}
|
|
|
|
int ON_Mesh::AddNgon(const ON_SimpleArray<unsigned int>& ngon_fi, bool bPermitHoles)
|
|
{
|
|
return AddNgon(ngon_fi.UnsignedCount(), ngon_fi.Array(), bPermitHoles);
|
|
}
|
|
|
|
int ON_Mesh::AddNgon(
|
|
unsigned int Fcount,
|
|
const unsigned int* ngon_fi
|
|
)
|
|
{
|
|
return AddNgon(Fcount, ngon_fi, false);
|
|
}
|
|
|
|
int ON_Mesh::AddNgon(
|
|
unsigned int Fcount,
|
|
const unsigned int* ngon_fi,
|
|
bool bPermitHoles
|
|
)
|
|
{
|
|
ON_MeshVertexFaceMap* vertexFaceMap = nullptr;
|
|
return AddNgon_Expert(Fcount, ngon_fi, bPermitHoles, vertexFaceMap);
|
|
}
|
|
|
|
int ON_Mesh::AddNgon_Expert(
|
|
unsigned int Fcount,
|
|
const unsigned int* ngon_fi,
|
|
bool bPermitHoles,
|
|
ON_MeshVertexFaceMap* vertexFaceMap
|
|
)
|
|
{
|
|
unsigned int ngon_index = ON_UNSET_UINT_INDEX;
|
|
if (Fcount < 1 || nullptr == ngon_fi)
|
|
return ngon_index;
|
|
|
|
ON_SimpleArray<unsigned int> ngon_vi;
|
|
const ON_3dPointListRef mesh_vertex_list(this);
|
|
const ON_MeshFaceList& mesh_face_list(this);
|
|
|
|
const int face_count = m_F.Count();
|
|
const unsigned int ngon_count0 = HasNgons() ? NgonUnsignedCount() : 0U;
|
|
bool bCheckNgonMap = (ngon_count0 > 0 && face_count == m_NgonMap.Count());
|
|
for (unsigned int i = 0; i < Fcount; ++i)
|
|
{
|
|
unsigned int fi = ngon_fi[i];
|
|
if (fi >= (unsigned int)face_count)
|
|
return ngon_index;
|
|
if (bCheckNgonMap && m_NgonMap[fi] < ngon_count0)
|
|
return ngon_index; // input faces are already in an ngon
|
|
}
|
|
|
|
unsigned int vi_count;
|
|
if (bPermitHoles)
|
|
vi_count = ON_MeshNgon::FindNgonBoundary(
|
|
mesh_vertex_list,
|
|
mesh_face_list,
|
|
vertexFaceMap,
|
|
Fcount,
|
|
ngon_fi,
|
|
ngon_vi
|
|
);
|
|
else
|
|
vi_count = ON_MeshNgon::FindNgonOuterBoundary(
|
|
mesh_vertex_list,
|
|
mesh_face_list,
|
|
vertexFaceMap,
|
|
Fcount,
|
|
ngon_fi,
|
|
ngon_vi
|
|
);
|
|
|
|
if (vi_count < 3 || ngon_vi.Count() < 3)
|
|
return ngon_index; //
|
|
|
|
return AddNgon(ngon_vi.UnsignedCount(), ngon_vi.Array(), Fcount, ngon_fi);
|
|
}
|
|
|
|
int ON_Mesh::AddNgon(
|
|
unsigned int Vcount,
|
|
const unsigned int* ngon_vi,
|
|
unsigned int Fcount,
|
|
const unsigned int* ngon_fi
|
|
)
|
|
{
|
|
unsigned int ngon_index = ON_UNSET_UINT_INDEX;
|
|
for(;;)
|
|
{
|
|
if ( Vcount < 3 )
|
|
break;
|
|
if ( 0 == ngon_vi )
|
|
break;
|
|
if ( Fcount <= 0 )
|
|
break;
|
|
if ( 0 == ngon_fi )
|
|
break;
|
|
|
|
ON_MeshNgon* ngon = m_NgonAllocator.AllocateNgon(Vcount,Fcount);
|
|
if ( 0 == ngon )
|
|
break;
|
|
memcpy(ngon->m_vi,ngon_vi,ngon->m_Vcount*sizeof(ngon->m_vi[0]));
|
|
memcpy(ngon->m_fi,ngon_fi,ngon->m_Fcount*sizeof(ngon->m_fi[0]));
|
|
ngon_index = AddNgon(ngon);
|
|
break;
|
|
}
|
|
|
|
return ngon_index;
|
|
}
|
|
|
|
bool ON_Mesh::OrientNgons(bool bPermitHoles)
|
|
{
|
|
bool rc = true;
|
|
for (;;)
|
|
{
|
|
const unsigned int ngon_count = m_Ngon.UnsignedCount();
|
|
if (0 == ngon_count)
|
|
break;
|
|
|
|
ON_MeshNgon** ngons = m_Ngon.Array();
|
|
if (0 == ngons)
|
|
break;
|
|
|
|
const ON_MeshFaceList face_list = ON_MeshFaceList(this);
|
|
for (unsigned int ni = 0; ni < ngon_count; ni++)
|
|
{
|
|
ON_MeshNgon* ngon = ngons[ni];
|
|
if (nullptr == ngon)
|
|
continue;
|
|
if (0 == ngon->m_Fcount && 0 == ngon->m_Vcount)
|
|
continue;
|
|
const int ngon_orientation = ngon->Orientation(face_list,bPermitHoles);
|
|
if (1 == ngon_orientation)
|
|
{
|
|
continue;
|
|
}
|
|
if (-1 == ngon_orientation)
|
|
{
|
|
ngon->ReverseOuterBoundary();
|
|
continue;
|
|
}
|
|
rc = false; // ngon cannot be oriented
|
|
}
|
|
break;
|
|
}
|
|
return rc;
|
|
|
|
}
|
|
|
|
void ON_MeshNgon::ReverseOuterBoundary()
|
|
{
|
|
if (m_Vcount < 3 || nullptr == m_vi)
|
|
return;
|
|
unsigned int vi;
|
|
unsigned int i0 = 1;
|
|
unsigned i1 = m_Vcount - 1;
|
|
while (i0 < i1)
|
|
{
|
|
vi = m_vi[i0];
|
|
m_vi[i0] = m_vi[i1];
|
|
m_vi[i1] = vi;
|
|
i0++;
|
|
i1--;
|
|
}
|
|
}
|
|
|
|
void ON_Mesh::FlipNgonOrientation()
|
|
{
|
|
const unsigned int ngon_count = m_Ngon.UnsignedCount();
|
|
if ( 0 == ngon_count )
|
|
return;
|
|
ON_MeshNgon** ngons = m_Ngon.Array();
|
|
if ( 0 == ngons )
|
|
return;
|
|
for (unsigned int ni = 0; ni < ngon_count; ni++)
|
|
{
|
|
ON_MeshNgon* ngon = ngons[ni];
|
|
if (nullptr == ngon || ngon->m_Vcount <= 0)
|
|
continue;
|
|
ngon->ReverseOuterBoundary();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ON_Mesh::RemoveNgon(
|
|
unsigned int ngon_index
|
|
)
|
|
{
|
|
return (1==RemoveNgons(1,&ngon_index));
|
|
}
|
|
|
|
unsigned int ON_Mesh::RemoveNgons(
|
|
unsigned int ngon_index_count,
|
|
const unsigned int* ngon_index_list
|
|
)
|
|
{
|
|
if ( ngon_index_count <= 0 || 0 == ngon_index_list )
|
|
return 0;
|
|
const unsigned int ngon_count = m_Ngon.UnsignedCount();
|
|
if ( 0 == ngon_count )
|
|
return 0;
|
|
ON_MeshNgon** ngons = m_Ngon.Array();
|
|
if ( 0 == ngons )
|
|
return 0;
|
|
|
|
unsigned int removed_count = 0;
|
|
unsigned int i, k, ngon_index, face_index;
|
|
const unsigned int* fi;
|
|
ON_MeshNgon* ngon;
|
|
const unsigned int meshFcount = m_F.UnsignedCount();
|
|
unsigned int* ngon_map = ( meshFcount == m_NgonMap.UnsignedCount() ) ? m_NgonMap.Array() : 0;
|
|
for ( k = 0; k < ngon_index_count; k++ )
|
|
{
|
|
ngon_index = ngon_index_list[k];
|
|
if (ngon_index >= ngon_count )
|
|
continue;
|
|
ngon = ngons[ngon_index];
|
|
if ( 0 == ngon )
|
|
continue;
|
|
ngons[ngon_index] = 0;
|
|
fi = (const unsigned int*)ngon->m_fi;
|
|
if ( 0 != fi )
|
|
{
|
|
if ( ngon_map )
|
|
{
|
|
for ( i = 0; i < ngon->m_Fcount; i++ )
|
|
{
|
|
face_index = fi[i];
|
|
if ( face_index < meshFcount && ngon_map[face_index] == ngon_index )
|
|
ngon_map[face_index] = ON_UNSET_UINT_INDEX;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_NgonAllocator.DeallocateNgon(ngon);
|
|
removed_count++;
|
|
}
|
|
|
|
return removed_count;
|
|
}
|
|
|
|
ON_3dPoint ON_Mesh::NgonCenter(unsigned int ngon_index) const
|
|
{
|
|
return NgonCenter(ngon_index < m_Ngon.UnsignedCount() ? m_Ngon[ngon_index] : 0);
|
|
}
|
|
|
|
ON_3dPoint ON_Mesh::NgonCenter(const ON_MeshNgon* ngon) const
|
|
{
|
|
ON_3dPoint center(ON_3dPoint::UnsetPoint);
|
|
if ( 0 != ngon && ngon->m_Vcount > 0 && 0 != ngon->m_vi )
|
|
{
|
|
ON_3dPointListRef mesh_vertex_list;
|
|
mesh_vertex_list.SetFromMesh(this);
|
|
const unsigned int meshVcount = mesh_vertex_list.PointCount();
|
|
if ( meshVcount > 0 )
|
|
{
|
|
ON_3dPoint P;
|
|
double count = 0.0;
|
|
for ( unsigned int i = 0; i < ngon->m_Vcount; i++ )
|
|
{
|
|
unsigned int vi = (unsigned int)ngon->m_vi[i];
|
|
if ( vi < meshVcount )
|
|
{
|
|
mesh_vertex_list.GetPoint(vi,&P.x);
|
|
if ( 0.0 == count )
|
|
{
|
|
center = P;
|
|
count = 1.0;
|
|
}
|
|
else
|
|
{
|
|
center += P;
|
|
count += 1.0;
|
|
}
|
|
}
|
|
}
|
|
if ( count > 0.0 )
|
|
center /= count;
|
|
}
|
|
}
|
|
return center;
|
|
}
|
|
|
|
ON_BoundingBox ON_Mesh::NgonBoundaryBoundingBox(unsigned int ngon_index) const
|
|
{
|
|
return NgonBoundaryBoundingBox(ngon_index < m_Ngon.UnsignedCount() ? m_Ngon[ngon_index] : 0);
|
|
}
|
|
|
|
ON_BoundingBox ON_Mesh::NgonBoundaryBoundingBox(const ON_MeshNgon* ngon) const
|
|
{
|
|
ON_BoundingBox bbox;
|
|
ON_3dPointListRef mesh_vertex_list;
|
|
mesh_vertex_list.SetFromMesh(this);
|
|
const unsigned int meshVcount = mesh_vertex_list.PointCount();
|
|
if ( meshVcount > 0 )
|
|
{
|
|
if ( 0 != ngon && ngon->m_Vcount > 0 && 0 != ngon->m_vi )
|
|
{
|
|
ON_3dPoint P;
|
|
bool bGrow = false;
|
|
for ( unsigned int i = 0; i < ngon->m_Vcount; i++ )
|
|
{
|
|
unsigned int vi = ngon->m_vi[i];
|
|
if ( vi < meshVcount )
|
|
{
|
|
mesh_vertex_list.GetPoint(vi,&P.x);
|
|
if ( bbox.Set(P,bGrow) )
|
|
bGrow = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return bbox;
|
|
}
|
|
|
|
unsigned int ON_MeshNgon::GetOuterBoundaryPoints(
|
|
const class ON_3dPointListRef& mesh_vertex_list,
|
|
bool bAppendStartPoint,
|
|
ON_SimpleArray<ON_3dPoint>& ngon_boundary_points
|
|
) const
|
|
{
|
|
if ( m_Vcount > 0 )
|
|
{
|
|
ngon_boundary_points.Reserve(bAppendStartPoint ? (m_Vcount+1) : m_Vcount);
|
|
ngon_boundary_points.SetCount( GetOuterBoundaryPoints(mesh_vertex_list,bAppendStartPoint,ngon_boundary_points.Array()) );
|
|
}
|
|
else
|
|
{
|
|
ngon_boundary_points.SetCount(0);
|
|
}
|
|
return ngon_boundary_points.UnsignedCount();
|
|
}
|
|
|
|
unsigned int ON_MeshNgon::GetOuterBoundaryPoints(
|
|
const class ON_3dPointListRef& mesh_vertex_list,
|
|
bool bAppendStartPoint,
|
|
ON_3dPoint* ngon_boundary_points
|
|
) const
|
|
{
|
|
if ( m_Vcount <= 0 || 0 == m_vi )
|
|
return 0;
|
|
|
|
const unsigned int meshVcount = mesh_vertex_list.PointCount();
|
|
if ( meshVcount <= 0 )
|
|
return 0;
|
|
|
|
for ( unsigned int i = 0; i < m_Vcount; i++ )
|
|
{
|
|
unsigned int vi = m_vi[i];
|
|
if ( vi >= meshVcount )
|
|
return 0;
|
|
mesh_vertex_list.GetPoint(vi,&ngon_boundary_points[i].x);
|
|
}
|
|
|
|
if ( bAppendStartPoint )
|
|
{
|
|
ngon_boundary_points[m_Vcount] = ngon_boundary_points[0];
|
|
return (m_Vcount+1);
|
|
}
|
|
|
|
return m_Vcount;
|
|
}
|
|
|
|
unsigned int ON_Mesh::GetNgonBoundaryPoints(
|
|
const ON_MeshNgon* ngon,
|
|
bool bAppendStartPoint,
|
|
ON_SimpleArray<ON_3dPoint>& ngon_boundary_points
|
|
) const
|
|
{
|
|
if ( ngon )
|
|
{
|
|
ON_3dPointListRef mesh_vertex_list;
|
|
mesh_vertex_list.SetFromMesh(this);
|
|
return ngon->GetOuterBoundaryPoints(mesh_vertex_list,bAppendStartPoint,ngon_boundary_points);
|
|
}
|
|
ngon_boundary_points.SetCount(0);
|
|
return 0;
|
|
}
|
|
|
|
unsigned int ON_Mesh::GetNgonBoundaryPoints(
|
|
const ON_MeshNgon* ngon,
|
|
bool bAppendStartPoint,
|
|
ON_3dPoint* ngon_boundary_points
|
|
) const
|
|
{
|
|
if ( ngon )
|
|
{
|
|
ON_3dPointListRef mesh_vertex_list;
|
|
mesh_vertex_list.SetFromMesh(this);
|
|
return ngon->GetOuterBoundaryPoints(mesh_vertex_list,bAppendStartPoint,ngon_boundary_points);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void ON_Mesh::SetNgonCount(
|
|
unsigned int ngon_count
|
|
)
|
|
{
|
|
if ( ngon_count <= 0 )
|
|
{
|
|
m_NgonMap.Destroy();
|
|
m_Ngon.Destroy();
|
|
m_NgonAllocator.DeallocateAllNgons();
|
|
return;
|
|
}
|
|
|
|
unsigned int ngon_count0 = m_Ngon.UnsignedCount();
|
|
ON_MeshNgon** a;
|
|
|
|
if ( ngon_count > ngon_count0 )
|
|
{
|
|
m_Ngon.Reserve(ngon_count);
|
|
m_Ngon.SetCount(ngon_count);
|
|
a = m_Ngon.Array();
|
|
if ( a )
|
|
{
|
|
memset(a+ngon_count0,0,(ngon_count-ngon_count0)*sizeof(a[0]));
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( ngon_count < ngon_count0 )
|
|
{
|
|
bool bUpdateNgonMap = (m_NgonMap.UnsignedCount() == ngon_count0);
|
|
a = m_Ngon.Array();
|
|
if ( bUpdateNgonMap && 2*ngon_count >= ngon_count0 )
|
|
{
|
|
// less expensive to clean up map array
|
|
while ( --ngon_count0 >= ngon_count )
|
|
{
|
|
if ( a[ngon_count0] )
|
|
RemoveNgon(ngon_count0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// less expensive to reacalculate map array
|
|
while ( --ngon_count0 >= ngon_count )
|
|
{
|
|
if ( a[ngon_count0] )
|
|
{
|
|
|
|
m_NgonAllocator.DeallocateNgon(a[ngon_count0]);
|
|
a[ngon_count0] = 0;
|
|
}
|
|
}
|
|
m_Ngon.SetCount(ngon_count);
|
|
if ( bUpdateNgonMap )
|
|
CreateNgonMap();
|
|
else
|
|
m_NgonMap.SetCount(0);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void ON_Mesh::RemoveEmptyNgons()
|
|
{
|
|
ON_MeshNgon* ngon;
|
|
ON_MeshNgon** ngons = m_Ngon.Array();
|
|
const unsigned int ngon_count0 = m_Ngon.UnsignedCount();
|
|
unsigned int ngon_count1 = 0;
|
|
unsigned int i;
|
|
|
|
for ( i = 0; i < ngon_count0; i++ )
|
|
{
|
|
ngon = ngons[i];
|
|
if ( 0 == ngon )
|
|
continue;
|
|
if ( 0 != ngon && ngon->m_Vcount > 0 && 0 != ngon->m_vi && ngon->m_Fcount > 0 && 0 != ngon->m_fi)
|
|
{
|
|
if (i > ngon_count1 )
|
|
ngons[ngon_count1] = ngons[i];
|
|
ngon_count1++;
|
|
}
|
|
else
|
|
{
|
|
ngons[i] = 0;
|
|
m_NgonAllocator.DeallocateNgon(ngon);
|
|
}
|
|
}
|
|
|
|
if ( ngon_count1 < ngon_count0 )
|
|
{
|
|
m_Ngon.SetCount(ngon_count1);
|
|
if ( ngon_count0 == m_NgonMap.UnsignedCount() )
|
|
CreateNgonMap();
|
|
else
|
|
m_NgonMap.SetCount(0);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void ON_Mesh::RemoveAllNgons()
|
|
{
|
|
SetNgonCount(0);
|
|
}
|
|
|
|
ON_MeshNgon* ON_Mesh::AllocateNgon(
|
|
unsigned int Vcount,
|
|
unsigned int Fcount
|
|
)
|
|
{
|
|
ON_MeshNgon* ngon = m_NgonAllocator.AllocateNgon(Vcount,Fcount);
|
|
if ( ngon )
|
|
{
|
|
if ( ngon->m_Vcount > 0 )
|
|
memset(ngon->m_vi,0xFF,ngon->m_Vcount*sizeof(ngon->m_vi[0]));
|
|
if ( ngon->m_Fcount > 0 )
|
|
memset(ngon->m_fi,0xFF,ngon->m_Fcount*sizeof(ngon->m_fi[0]));
|
|
}
|
|
return ngon;
|
|
}
|
|
|
|
bool ON_Mesh::DeallocateNgon(
|
|
ON_MeshNgon* ngon
|
|
)
|
|
{
|
|
return m_NgonAllocator.DeallocateNgon(ngon);
|
|
}
|
|
|
|
unsigned int ON_Mesh::AddNgon(
|
|
ON_MeshNgon* ngon
|
|
)
|
|
{
|
|
int ngon_index = ON_UNSET_UINT_INDEX;
|
|
|
|
if ( ngon )
|
|
{
|
|
ngon_index = m_Ngon.Count();
|
|
const unsigned int meshFcount = m_F.UnsignedCount();
|
|
if ( meshFcount <= 0 && ngon->m_Fcount > 0 )
|
|
return ON_UNSET_UINT_INDEX;
|
|
|
|
if ( meshFcount > 0 )
|
|
{
|
|
unsigned int fi;
|
|
unsigned int* ngon_map;
|
|
if ( 0 == ngon_index )
|
|
{
|
|
m_NgonMap.Reserve(meshFcount);
|
|
m_NgonMap.SetCount(meshFcount);
|
|
ngon_map = m_NgonMap.Array();
|
|
if ( 0 != ngon_map )
|
|
memset(ngon_map,0xFF,m_NgonMap.SizeOfArray());
|
|
}
|
|
else if ( meshFcount == m_NgonMap.UnsignedCount() )
|
|
ngon_map = m_NgonMap.Array();
|
|
else
|
|
ngon_map = 0;
|
|
|
|
if ( 0 != ngon_map )
|
|
{
|
|
for ( unsigned int i = 0; i < ngon->m_Fcount; i++ )
|
|
{
|
|
fi = ngon->m_fi[i];
|
|
if ( fi >= meshFcount )
|
|
{
|
|
// clean up ngon_map
|
|
for ( i = 0; i < ngon->m_Fcount; i++ )
|
|
{
|
|
fi = ngon->m_fi[i];
|
|
if ( fi < meshFcount )
|
|
ngon_map[fi] = ON_UNSET_UINT_INDEX;
|
|
}
|
|
return ON_UNSET_UINT_INDEX;
|
|
}
|
|
ngon_map[fi] = ngon_index;
|
|
}
|
|
}
|
|
else
|
|
m_NgonMap.SetCount(0);
|
|
}
|
|
m_Ngon.Append(ngon);
|
|
}
|
|
return ngon_index;
|
|
}
|
|
|
|
|
|
|
|
struct ON_MeshNgonLink
|
|
{
|
|
struct ON_MeshNgonLink* next;
|
|
struct ON_MeshNgonLink* prev;
|
|
};
|
|
|
|
static size_t SizeofNgon(unsigned int capacity)
|
|
{
|
|
return sizeof(ON_MeshNgon) + (capacity+1)*sizeof(unsigned int);
|
|
}
|
|
|
|
ON_MeshNgonAllocator::ON_MeshNgonAllocator() ON_NOEXCEPT
|
|
: m_31(0)
|
|
, m_63(0)
|
|
, m_active(0)
|
|
{}
|
|
|
|
ON_MeshNgonAllocator::~ON_MeshNgonAllocator()
|
|
{
|
|
void DeallocateAllNgons();
|
|
}
|
|
|
|
ON_MeshNgon* ON_MeshNgonAllocator::CopyNgon(
|
|
const ON_MeshNgon* ngon
|
|
)
|
|
{
|
|
ON_MeshNgon* ngon_copy = AllocateNgon(ngon->m_vi ? ngon->m_Vcount : 0,ngon->m_fi ? ngon->m_Fcount : 0);
|
|
if ( ngon_copy )
|
|
{
|
|
if ( ngon_copy->m_Vcount > 0 )
|
|
memcpy(ngon_copy->m_vi,ngon->m_vi,ngon_copy->m_Vcount*sizeof(ngon_copy->m_vi[0]));
|
|
if ( ngon_copy->m_Fcount > 0 )
|
|
memcpy(ngon_copy->m_fi,ngon->m_fi,ngon_copy->m_Fcount*sizeof(ngon_copy->m_fi[0]));
|
|
}
|
|
return ngon_copy;
|
|
}
|
|
|
|
ON_MeshNgon* ON_MeshNgonAllocator::AllocateNgon(
|
|
unsigned int Vcount,
|
|
unsigned int Fcount
|
|
)
|
|
{
|
|
struct ON_MeshNgonLink* link;
|
|
ON_MeshNgon* ngon;
|
|
unsigned int* a;
|
|
unsigned int capacity;
|
|
|
|
if ( Vcount < 3 )
|
|
return 0;
|
|
|
|
capacity = Vcount+Fcount;
|
|
if ( capacity <= 7 )
|
|
{
|
|
capacity = 7;
|
|
if ( SizeofNgon(capacity) != m_7.SizeofElement() )
|
|
m_7.Create(SizeofNgon(capacity),0,0);
|
|
ngon = (ON_MeshNgon*)m_7.AllocateElement();
|
|
}
|
|
else if ( capacity <= 15 )
|
|
{
|
|
capacity = 15;
|
|
if ( SizeofNgon(capacity) != m_15.SizeofElement() )
|
|
m_15.Create(SizeofNgon(capacity),0,0);
|
|
ngon = (ON_MeshNgon*)m_15.AllocateElement();
|
|
}
|
|
else
|
|
{
|
|
for(;;)
|
|
{
|
|
link = 0;
|
|
if ( capacity <= 31 )
|
|
{
|
|
capacity = 31;
|
|
if ( 0 != m_31 )
|
|
{
|
|
link = (struct ON_MeshNgonLink*)m_31;
|
|
m_31 = link->next;
|
|
break;
|
|
}
|
|
}
|
|
else if ( capacity <= 63 )
|
|
{
|
|
capacity = 63;
|
|
if ( 0 != m_63 )
|
|
{
|
|
link = (struct ON_MeshNgonLink*)m_63;
|
|
m_63 = link->next;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// make capacity % 8 = 7
|
|
unsigned int n = (capacity % 8);
|
|
capacity += (7-n);
|
|
}
|
|
link = (struct ON_MeshNgonLink*)onmalloc(sizeof(*link) + SizeofNgon(capacity));
|
|
break;
|
|
}
|
|
if ( 0 == link )
|
|
return 0;
|
|
ngon = (ON_MeshNgon*)(link+1);
|
|
link->next = (struct ON_MeshNgonLink*)m_active;
|
|
if ( link->next )
|
|
link->next->prev = link;
|
|
link->prev = 0;
|
|
m_active = link;
|
|
}
|
|
|
|
if ( 0 != ngon )
|
|
{
|
|
a = (unsigned int*)(ngon+1);
|
|
*a++ = capacity;
|
|
ngon->m_vi = a;
|
|
ngon->m_fi = (Fcount > 0) ? (ngon->m_vi + Vcount) : 0;
|
|
ngon->m_Vcount = Vcount;
|
|
ngon->m_Fcount = Fcount;
|
|
//memset(a,0xFF,capacity*sizeof(a[0])); // almost always a waste of time - user must init
|
|
}
|
|
|
|
return ngon;
|
|
}
|
|
|
|
ON_MeshNgon* ON_MeshNgonAllocator::ReallocateNgon(
|
|
ON_MeshNgon* ngon,
|
|
unsigned int Vcount,
|
|
unsigned int Fcount
|
|
)
|
|
{
|
|
if ( 0 == Vcount && 0 == Fcount )
|
|
{
|
|
DeallocateNgon(ngon);
|
|
return 0;
|
|
}
|
|
|
|
if ( Vcount < 3 )
|
|
return 0;
|
|
|
|
if ( 0 == ngon )
|
|
return AllocateNgon(Vcount,Fcount);
|
|
|
|
if ( Vcount <= ngon->m_Vcount && Fcount <= ngon->m_Fcount )
|
|
{
|
|
ngon->m_Vcount = Vcount;
|
|
ngon->m_Fcount = Fcount;
|
|
return ngon;
|
|
}
|
|
|
|
unsigned int capacity = ngon->Capacity();
|
|
ON_MeshNgon* ngon1 = ( Vcount + Fcount <= capacity ) ? ngon : AllocateNgon(Vcount,Fcount);
|
|
if ( 0 == ngon1 )
|
|
return 0;
|
|
|
|
unsigned int* vi = ngon1->m_vi;
|
|
unsigned int* fi = (Fcount > 0) ? (vi + Vcount) : 0;
|
|
unsigned int i, c;
|
|
|
|
if ( 0 != fi && (0 == ngon->m_Fcount || 0 != ngon->m_fi) )
|
|
{
|
|
c = Fcount < ngon->m_Fcount ? Fcount : ngon->m_Fcount;
|
|
for ( i = 0; i < c; i++ )
|
|
fi[i] = ngon->m_fi[i];
|
|
for ( i = c; i < Fcount; i++ )
|
|
fi[i] = ON_UNSET_UINT_INDEX;
|
|
}
|
|
|
|
if ( 0 != vi && ( 0 == ngon->m_Vcount || 0 != ngon->m_vi) )
|
|
{
|
|
c = Vcount < ngon->m_Vcount ? Vcount : ngon->m_Vcount;
|
|
for ( i = 0; i < c; i++ )
|
|
vi[i] = ngon->m_vi[i];
|
|
for ( i = c; i < Vcount; i++ )
|
|
vi[i] = ON_UNSET_UINT_INDEX;
|
|
}
|
|
|
|
ngon1->m_Vcount = Vcount;
|
|
ngon1->m_Fcount = Fcount;
|
|
ngon1->m_vi = vi;
|
|
ngon1->m_fi = fi;
|
|
|
|
if (ngon1 != ngon )
|
|
DeallocateNgon(ngon);
|
|
|
|
return ngon1;
|
|
}
|
|
|
|
|
|
bool ON_MeshNgonAllocator::DeallocateNgon(
|
|
ON_MeshNgon* ngon
|
|
)
|
|
{
|
|
struct ON_MeshNgonLink* link;
|
|
unsigned int capacity = ngon ? ngon->Capacity() : 0;
|
|
|
|
if ( 7 == capacity )
|
|
{
|
|
m_7.ReturnElement(ngon);
|
|
return true;
|
|
}
|
|
|
|
if ( 15 == capacity )
|
|
{
|
|
m_15.ReturnElement(ngon);
|
|
return true;
|
|
}
|
|
|
|
if ( 31 == capacity || capacity >= 63 )
|
|
{
|
|
link = ((struct ON_MeshNgonLink*)ngon)-1;
|
|
|
|
// remove from active list
|
|
if ( link == m_active )
|
|
{
|
|
if ( link->prev )
|
|
return false;
|
|
m_active = link->next;
|
|
}
|
|
else if ( link->prev )
|
|
link->prev->next = link->next;
|
|
else
|
|
return false;
|
|
if ( link->next )
|
|
link->next->prev = link->prev;
|
|
|
|
switch(capacity)
|
|
{
|
|
case 31:
|
|
link->prev = 0;
|
|
link->next = (struct ON_MeshNgonLink*)m_31;
|
|
m_31 = link;
|
|
break;
|
|
case 63:
|
|
link->prev = 0;
|
|
link->next = (struct ON_MeshNgonLink*)m_63;
|
|
m_63 = link;
|
|
break;
|
|
default:
|
|
// deallocate heap
|
|
onfree(link);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void ON_MeshNgonAllocator::DeallocateAllNgons()
|
|
{
|
|
struct ON_MeshNgonLink* link;
|
|
struct ON_MeshNgonLink* next;
|
|
struct ON_MeshNgonLink* links[3] = {(struct ON_MeshNgonLink*)m_31,(struct ON_MeshNgonLink*)m_63,(struct ON_MeshNgonLink*)m_active};
|
|
m_7.Destroy();
|
|
m_15.Destroy();
|
|
m_31 = 0;
|
|
m_63 = 0;
|
|
m_active = 0;
|
|
for ( int i = 0; i < sizeof(links)/sizeof(links[0]); i++ )
|
|
{
|
|
next = links[i];
|
|
while (next)
|
|
{
|
|
link = next;
|
|
next = next->next;
|
|
onfree(link);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#if defined(ON_HAS_RVALUEREF)
|
|
ON_MeshNgonAllocator::ON_MeshNgonAllocator(ON_MeshNgonAllocator&& src)
|
|
: m_31(src.m_31)
|
|
, m_63(src.m_63)
|
|
, m_active(src.m_active)
|
|
{
|
|
src.m_31 = 0;
|
|
src.m_63 = 0;
|
|
src.m_active = 0;
|
|
}
|
|
|
|
ON_MeshNgonAllocator& ON_MeshNgonAllocator::operator=(ON_MeshNgonAllocator&& src)
|
|
{
|
|
if ( this != &src )
|
|
{
|
|
DeallocateAllNgons();
|
|
m_31 = src.m_31;
|
|
m_63 = src.m_63;
|
|
m_active = src.m_active;
|
|
src.m_31 = 0;
|
|
src.m_63 = 0;
|
|
src.m_active = 0;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
// DO NOT COPY THIS DEFINE
|
|
#define ON_NGON_BOZO_VACCINE
|
|
|
|
// Do not copy or move the definition of
|
|
// the ON_NGON_MEMBLK structure.
|
|
struct ON_NGON_MEMBLK
|
|
{
|
|
#if !defined(ON_NGON_BOZO_VACCINE)
|
|
#error You are a bozo! Read the comments.
|
|
#endif
|
|
struct ON_NGON_MEMBLK* next;
|
|
};
|
|
|
|
|
|
class ON_V4V5_MeshNgon
|
|
{
|
|
public:
|
|
// Number of N-gon corners (N >= 3)
|
|
int N;
|
|
|
|
// N-gon vertex indices
|
|
// An array of N indices into the mesh's m_V[] vertex array.
|
|
// If the ON_MeshNgon is returned by the ON_V4V5_MeshNgonList::AddNgon()
|
|
// function, then the memory for vi is managed by the ON_V4V5_MeshNgonList
|
|
// class.
|
|
int* vi;
|
|
|
|
// N-gon face indices
|
|
// An array of Fcount indices into the mesh's m_F[] face array.
|
|
// Often, only N-2 indices are used. Unused indices are set to -1.
|
|
// If the ON_MeshNgon is returned by the ON_V4V5_MeshNgonList::AddNgon()
|
|
// function, then the memory for fi is managed by the ON_V4V5_MeshNgonList
|
|
// class.
|
|
int* fi;
|
|
};
|
|
|
|
class ON_V4V5_MeshNgonList
|
|
{
|
|
public:
|
|
ON_V4V5_MeshNgonList();
|
|
~ON_V4V5_MeshNgonList();
|
|
ON_V4V5_MeshNgonList(const ON_V4V5_MeshNgonList&);
|
|
ON_V4V5_MeshNgonList& operator=(const ON_V4V5_MeshNgonList&);
|
|
|
|
|
|
/*
|
|
Description:
|
|
Add an N-gon to the list
|
|
Parameters:
|
|
N - [in] number of vertices ( >= 5)
|
|
vi - [in] array of N vertex indices into the mesh's m_V[] array.
|
|
fi - [in] array of N face indices into the mesh's m_F[] array.
|
|
Unused indices are set to -1. In many cases
|
|
there are N-2 valid indices and these are triangles.
|
|
Remarks:
|
|
Adding an N-gon may invalidate any pointers previously
|
|
returned by Ngon.
|
|
*/
|
|
bool V4V5_AddNgon(int N, const int* vi, const int* fi);
|
|
class ON_V4V5_MeshNgon* V4V5_AddNgon(int N);
|
|
|
|
/*
|
|
Returns:
|
|
Number of Ngons
|
|
*/
|
|
int V4V5_NgonCount() const;
|
|
|
|
/*
|
|
Parameters:
|
|
Ngon_index - [in] zero based index
|
|
Returns:
|
|
nullptr or a pointer to the Ngon
|
|
*/
|
|
ON_V4V5_MeshNgon* V4V5_Ngon(int Ngon_index) const;
|
|
|
|
/*
|
|
Description:
|
|
If you know about how many ngons you will need,
|
|
then use the function to reserve space for them.
|
|
*/
|
|
bool V4V5_ReserveNgonCapacity(int capacity);
|
|
|
|
/*
|
|
Description:
|
|
Destroy N-gon list
|
|
*/
|
|
void V4V5_Destroy();
|
|
|
|
/*
|
|
Returns:
|
|
Approximate number of bytes used by this class.
|
|
*/
|
|
unsigned int V4V5_SizeOf() const;
|
|
|
|
private:
|
|
int m_ngons_count;
|
|
int m_ngons_capacity;
|
|
ON_V4V5_MeshNgon* m_ngons;
|
|
struct ON_NGON_MEMBLK* m_memblk_list;
|
|
};
|
|
|
|
ON_V4V5_MeshNgonList::ON_V4V5_MeshNgonList()
|
|
{
|
|
m_ngons_count = 0;
|
|
m_ngons_capacity = 0;
|
|
m_ngons = 0;
|
|
m_memblk_list = 0;
|
|
}
|
|
|
|
ON_V4V5_MeshNgonList::~ON_V4V5_MeshNgonList()
|
|
{
|
|
V4V5_Destroy();
|
|
}
|
|
|
|
void ON_V4V5_MeshNgonList::V4V5_Destroy()
|
|
{
|
|
m_ngons_count = 0;
|
|
m_ngons_capacity = 0;
|
|
if ( 0 != m_ngons )
|
|
{
|
|
onfree(m_ngons);
|
|
m_ngons = 0;
|
|
}
|
|
struct ON_NGON_MEMBLK* p = m_memblk_list;
|
|
m_memblk_list = 0;
|
|
while(p)
|
|
{
|
|
struct ON_NGON_MEMBLK* next = p->next;
|
|
onfree(p);
|
|
p = next;
|
|
}
|
|
}
|
|
|
|
|
|
ON_V4V5_MeshNgonList::ON_V4V5_MeshNgonList(const ON_V4V5_MeshNgonList& src)
|
|
{
|
|
m_ngons_count = 0;
|
|
m_ngons_capacity = 0;
|
|
m_ngons = 0;
|
|
m_memblk_list = 0;
|
|
if ( src.m_ngons_count > 0 && 0 != src.m_ngons )
|
|
{
|
|
*this = src;
|
|
}
|
|
}
|
|
|
|
|
|
ON_V4V5_MeshNgonList& ON_V4V5_MeshNgonList::operator=(const ON_V4V5_MeshNgonList& src)
|
|
{
|
|
if ( this != &src )
|
|
{
|
|
V4V5_Destroy();
|
|
V4V5_ReserveNgonCapacity(src.m_ngons_count);
|
|
for ( int i = 0; i < src.m_ngons_count; i++ )
|
|
{
|
|
const ON_V4V5_MeshNgon& ngon = src.m_ngons[i];
|
|
V4V5_AddNgon(ngon.N,ngon.vi,ngon.fi);
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
|
|
bool ON_V4V5_MeshNgonList::V4V5_ReserveNgonCapacity(int capacity)
|
|
{
|
|
bool rc = true;
|
|
if ( capacity > m_ngons_capacity )
|
|
{
|
|
m_ngons = (ON_V4V5_MeshNgon*)onrealloc(m_ngons,capacity*sizeof(m_ngons[0]));
|
|
if ( 0 == m_ngons )
|
|
{
|
|
m_ngons_capacity = 0;
|
|
m_ngons_count = 0;
|
|
rc = false;
|
|
}
|
|
else
|
|
{
|
|
m_ngons_capacity = capacity;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
class ON_V4V5_MeshNgon* ON_V4V5_MeshNgonList::V4V5_AddNgon(int N)
|
|
{
|
|
if ( N < 3 || N > 100000 )
|
|
return 0;
|
|
|
|
if ( m_ngons_count >= m_ngons_capacity )
|
|
{
|
|
int capacity = 2*m_ngons_count;
|
|
if (capacity < m_ngons_count+16)
|
|
capacity = m_ngons_count+16;
|
|
if ( !V4V5_ReserveNgonCapacity(capacity) )
|
|
return 0;
|
|
}
|
|
ON_V4V5_MeshNgon& ngon = m_ngons[m_ngons_count++];
|
|
|
|
ngon.N = N;
|
|
struct ON_NGON_MEMBLK* blk = (struct ON_NGON_MEMBLK*)onmalloc(sizeof(*blk) + (2*N)*sizeof(int));
|
|
if ( 0 == blk )
|
|
return 0;
|
|
ngon.vi = (int*)(blk + 1);
|
|
ngon.fi = ngon.vi + N;
|
|
memset(ngon.vi,0xFF,(2*N)*sizeof(int)); // set all indicies to -1
|
|
blk->next = m_memblk_list;
|
|
m_memblk_list = blk;
|
|
return &ngon;
|
|
}
|
|
|
|
bool ON_V4V5_MeshNgonList::V4V5_AddNgon(int N, const int* vi, const int* fi)
|
|
{
|
|
if ( 0 == vi || 0 == fi )
|
|
return false;
|
|
class ON_V4V5_MeshNgon* ngon = V4V5_AddNgon(N);
|
|
if ( 0 == ngon )
|
|
return false;
|
|
memcpy(ngon->vi,vi,N*sizeof(ngon->vi[0]));
|
|
memcpy(ngon->fi,fi,N*sizeof(ngon->fi[0]));
|
|
return true;
|
|
}
|
|
|
|
int ON_V4V5_MeshNgonList::V4V5_NgonCount() const
|
|
{
|
|
return m_ngons_count;
|
|
}
|
|
|
|
ON_V4V5_MeshNgon* ON_V4V5_MeshNgonList::V4V5_Ngon(int Ngon_index) const
|
|
{
|
|
return (Ngon_index < 0 || Ngon_index >= m_ngons_count) ? 0 : m_ngons+Ngon_index;
|
|
}
|
|
|
|
|
|
class /* DO NOT EXPORT THIS CLASS */ ON_V4V5_MeshNgonUserData : public ON_UserData
|
|
{
|
|
#if !defined(ON_NGON_BOZO_VACCINE)
|
|
#error You are a bozo! Read the comments.
|
|
#endif
|
|
ON_OBJECT_DECLARE(ON_V4V5_MeshNgonUserData);
|
|
|
|
public:
|
|
ON_V4V5_MeshNgonUserData();
|
|
~ON_V4V5_MeshNgonUserData();
|
|
ON_V4V5_MeshNgonUserData(const ON_V4V5_MeshNgonUserData&);
|
|
ON_V4V5_MeshNgonUserData& operator=(const ON_V4V5_MeshNgonUserData&);
|
|
|
|
// vitual ON_UserData override
|
|
bool IsValid( class ON_TextLog* text_log = nullptr ) const override;
|
|
unsigned int SizeOf() const override;
|
|
bool Write(ON_BinaryArchive&) const override;
|
|
bool Read(ON_BinaryArchive&) override;
|
|
|
|
// vitual ON_UserData override
|
|
bool GetDescription( ON_wString& ) override;
|
|
bool Archive() const override;
|
|
|
|
public:
|
|
ON_V4V5_MeshNgonList* m_ngon_list;
|
|
|
|
// used to validate ngon list.
|
|
// If the information here does not match the
|
|
// information in the mesh, then the ngon list
|
|
// is known to be invalid.
|
|
int m_mesh_F_count;
|
|
int m_mesh_V_count;
|
|
};
|
|
|
|
ON_OBJECT_IMPLEMENT(ON_V4V5_MeshNgonUserData,ON_UserData,"31F55AA3-71FB-49f5-A975-757584D937FF");
|
|
|
|
ON_V4V5_MeshNgonUserData::ON_V4V5_MeshNgonUserData()
|
|
{
|
|
m_userdata_uuid = ON_CLASS_ID(ON_V4V5_MeshNgonUserData);
|
|
m_application_uuid = ON_opennurbs4_id;
|
|
m_userdata_copycount = 1;
|
|
m_ngon_list = 0;
|
|
m_mesh_F_count = 0;
|
|
m_mesh_V_count = 0;
|
|
}
|
|
|
|
ON_V4V5_MeshNgonUserData::~ON_V4V5_MeshNgonUserData()
|
|
{
|
|
if ( 0 != m_ngon_list )
|
|
{
|
|
delete m_ngon_list;
|
|
m_ngon_list = 0;
|
|
}
|
|
}
|
|
|
|
ON_V4V5_MeshNgonUserData::ON_V4V5_MeshNgonUserData(const ON_V4V5_MeshNgonUserData& src)
|
|
: ON_UserData(src)
|
|
, m_mesh_F_count(src.m_mesh_F_count)
|
|
, m_mesh_V_count(src.m_mesh_V_count)
|
|
{
|
|
m_ngon_list = (0 != src.m_ngon_list)
|
|
? new ON_V4V5_MeshNgonList(*src.m_ngon_list)
|
|
: 0;
|
|
}
|
|
|
|
ON_V4V5_MeshNgonUserData& ON_V4V5_MeshNgonUserData::operator=(const ON_V4V5_MeshNgonUserData& src)
|
|
{
|
|
if ( this != &src )
|
|
{
|
|
if (0 != m_ngon_list )
|
|
{
|
|
delete m_ngon_list;
|
|
m_ngon_list = 0;
|
|
}
|
|
ON_UserData::operator=(src);
|
|
if (0 != src.m_ngon_list)
|
|
{
|
|
m_ngon_list = new ON_V4V5_MeshNgonList(*src.m_ngon_list);
|
|
}
|
|
m_mesh_F_count = src.m_mesh_F_count;
|
|
m_mesh_V_count = src.m_mesh_V_count;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
bool ON_V4V5_MeshNgonUserData::IsValid( ON_TextLog* text_log ) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
unsigned int ON_V4V5_MeshNgonList::V4V5_SizeOf() const
|
|
{
|
|
unsigned int sz = sizeof(*this);
|
|
int icount = 0;
|
|
for ( int i = 0; i < m_ngons_count; i++ )
|
|
{
|
|
icount += 2*m_ngons[i].N;
|
|
}
|
|
sz += m_ngons_capacity*sizeof(m_ngons[0]);
|
|
sz += icount*sizeof(int);
|
|
return sz;
|
|
}
|
|
|
|
unsigned int ON_V4V5_MeshNgonUserData::SizeOf() const
|
|
{
|
|
unsigned int sz = ON_UserData::SizeOf();
|
|
if ( 0 != m_ngon_list )
|
|
sz += m_ngon_list->V4V5_SizeOf();
|
|
return sz;
|
|
}
|
|
|
|
|
|
bool ON_V4V5_MeshNgonUserData::Write(ON_BinaryArchive& archive) const
|
|
{
|
|
bool rc = archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,1);
|
|
if (!rc)
|
|
return false;
|
|
for (;;)
|
|
{
|
|
int count = ( 0 == m_ngon_list ) ? 0 : m_ngon_list->V4V5_NgonCount();
|
|
const ON_V4V5_MeshNgon* ngon_array = (count > 0) ? m_ngon_list->V4V5_Ngon(0) : 0;
|
|
if ( 0 == ngon_array )
|
|
count = 0;
|
|
rc = archive.WriteInt(count);
|
|
if (count <= 0 || !rc)
|
|
break;
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
const class ON_V4V5_MeshNgon& ngon = ngon_array[i];
|
|
|
|
rc = archive.WriteInt(ngon.N);
|
|
if (!rc)
|
|
break;
|
|
rc = archive.WriteInt(ngon.N,ngon.vi);
|
|
if (!rc)
|
|
break;
|
|
rc = archive.WriteInt(ngon.N,ngon.fi);
|
|
if (!rc)
|
|
break;
|
|
}
|
|
if (!rc)
|
|
break;
|
|
|
|
// chunk version 1.1 added face and vertex validation counts.
|
|
rc = archive.WriteInt(m_mesh_F_count);
|
|
if (!rc)
|
|
break;
|
|
rc = archive.WriteInt(m_mesh_V_count);
|
|
if (!rc)
|
|
break;
|
|
|
|
break;
|
|
}
|
|
if ( !archive.EndWrite3dmChunk() )
|
|
rc = false;
|
|
return rc;
|
|
}
|
|
|
|
bool ON_V4V5_MeshNgonUserData::Read(ON_BinaryArchive& archive)
|
|
{
|
|
if ( 0 != m_ngon_list )
|
|
{
|
|
delete m_ngon_list;
|
|
m_ngon_list = 0;
|
|
}
|
|
int major_version = 0;
|
|
int minor_version = 0;
|
|
bool rc = archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version);
|
|
if (!rc)
|
|
return false;
|
|
for (;;)
|
|
{
|
|
rc = (1 == major_version);
|
|
if (!rc)
|
|
break;
|
|
int count = 0;
|
|
rc = archive.ReadInt(&count);
|
|
if (count <= 0 || !rc)
|
|
break;
|
|
|
|
m_ngon_list = new ON_V4V5_MeshNgonList();
|
|
if ( 0 == m_ngon_list )
|
|
break;
|
|
|
|
m_ngon_list->V4V5_ReserveNgonCapacity(count);
|
|
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
int N = 0;
|
|
rc = archive.ReadInt(&N);
|
|
if (!rc)
|
|
break;
|
|
if ( N <= 0 )
|
|
continue;
|
|
class ON_V4V5_MeshNgon* ngon = m_ngon_list->V4V5_AddNgon(N);
|
|
if ( 0 == ngon )
|
|
break;
|
|
|
|
rc = archive.ReadInt(N,ngon->vi);
|
|
if (!rc)
|
|
break;
|
|
rc = archive.ReadInt(N,ngon->fi);
|
|
if (!rc)
|
|
break;
|
|
ngon->N = N;
|
|
}
|
|
if (!rc)
|
|
break;
|
|
|
|
if ( minor_version >= 1 )
|
|
{
|
|
// chunk version 1.1 added face and vertex validation counts.
|
|
rc = archive.ReadInt(&m_mesh_F_count);
|
|
if (!rc)
|
|
break;
|
|
rc = archive.ReadInt(&m_mesh_V_count);
|
|
if (!rc)
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
if ( !archive.EndRead3dmChunk() )
|
|
rc = false;
|
|
return rc;
|
|
}
|
|
|
|
// vitual ON_UserData override
|
|
bool ON_V4V5_MeshNgonUserData::GetDescription( ON_wString& description )
|
|
{
|
|
description = L"Mesh N-gon list";
|
|
return true;
|
|
}
|
|
|
|
bool ON_V4V5_MeshNgonUserData::Archive() const
|
|
{
|
|
return ( 0 != m_ngon_list && m_ngon_list->V4V5_NgonCount() > 0 );
|
|
}
|
|
|
|
static
|
|
bool ON_ValidateNgon(
|
|
const ON_V4V5_MeshNgon* ngon,
|
|
int mesh_V_count,
|
|
int mesh_F_count
|
|
)
|
|
{
|
|
unsigned int j;
|
|
if ( 0 == ngon || ngon->N < 0 )
|
|
return false;
|
|
const unsigned int N = ngon->N;
|
|
for ( j = 0; j < N; j++ )
|
|
{
|
|
if ( ngon->vi[j] < 0 || ngon->vi[j] >= mesh_V_count )
|
|
return false;
|
|
if ( ngon->fi[j] < 0 || ngon->fi[j] >= mesh_F_count )
|
|
{
|
|
if ( -1 == ngon->fi[j] )
|
|
{
|
|
for ( j++; j < N; j++ )
|
|
{
|
|
if ( ngon->vi[j] < 0 || ngon->vi[j] >= mesh_V_count )
|
|
return false;
|
|
if ( -1 != ngon->fi[j] )
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static
|
|
bool ON_ValidateMeshNgonUserData(
|
|
ON_V4V5_MeshNgonUserData* ngud,
|
|
const ON_Mesh& mesh
|
|
)
|
|
{
|
|
int i;
|
|
if ( 0 == ngud || 0 == ngud->m_ngon_list )
|
|
return false;
|
|
|
|
const int mesh_V_count = mesh.m_V.Count();
|
|
const int mesh_F_count = mesh.m_F.Count();
|
|
|
|
if ( 0 == ngud->m_mesh_V_count && 0 == ngud->m_mesh_F_count )
|
|
{
|
|
// This is old user data that did not have validation counts
|
|
// saved in the file.
|
|
|
|
// Set validation counts to -1 so we never do this slow validation again.
|
|
ngud->m_mesh_V_count = -1;
|
|
ngud->m_mesh_F_count = -1;
|
|
|
|
const int ngon_count = ngud->m_ngon_list->V4V5_NgonCount();
|
|
|
|
for ( i = 0; i < ngon_count; i++ )
|
|
{
|
|
if ( !ON_ValidateNgon(ngud->m_ngon_list->V4V5_Ngon(i),mesh_V_count,mesh_F_count) )
|
|
return false;
|
|
}
|
|
|
|
// Set validation counts to proper values because we will
|
|
// assume this old ngon information is valid since the indices
|
|
// are in range. This assumption may not be valid, but
|
|
// at least the ngon won't cause a crash.
|
|
ngud->m_mesh_V_count = mesh_V_count;
|
|
ngud->m_mesh_F_count = mesh_F_count;
|
|
}
|
|
|
|
return ( ngud->m_mesh_F_count == mesh_F_count && ngud->m_mesh_V_count == mesh_V_count );
|
|
}
|
|
|
|
|
|
const class ON_V4V5_MeshNgonList* ON_Mesh::V4V5_NgonList() const
|
|
{
|
|
ON_UserData* ud = GetUserData(ON_CLASS_ID(ON_V4V5_MeshNgonUserData));
|
|
ON_V4V5_MeshNgonUserData* ngud = ON_V4V5_MeshNgonUserData::Cast(ud);
|
|
|
|
if ( 0 != ngud && !ON_ValidateMeshNgonUserData(ngud,*this) )
|
|
{
|
|
delete ngud;
|
|
ngud = 0;
|
|
}
|
|
|
|
return (0 == ngud) ? 0 : ngud->m_ngon_list;
|
|
}
|
|
|
|
|
|
class ON_V4V5_MeshNgonList* ON_Mesh::V4V5_ModifyNgonList()
|
|
{
|
|
ON_UserData* ud = GetUserData(ON_CLASS_ID(ON_V4V5_MeshNgonUserData));
|
|
ON_V4V5_MeshNgonUserData* ngud = ON_V4V5_MeshNgonUserData::Cast(ud);
|
|
if ( 0 == ngud )
|
|
{
|
|
if ( ud )
|
|
{
|
|
delete ud;
|
|
ud = 0;
|
|
}
|
|
ngud = new ON_V4V5_MeshNgonUserData();
|
|
ngud->m_mesh_F_count = m_F.Count();
|
|
ngud->m_mesh_V_count = m_V.Count();
|
|
AttachUserData(ngud);
|
|
}
|
|
else if ( 0 != ngud->m_ngon_list && !ON_ValidateMeshNgonUserData(ngud,*this) )
|
|
{
|
|
delete ngud->m_ngon_list;
|
|
ngud->m_ngon_list = 0;
|
|
}
|
|
|
|
if ( 0 == ngud->m_ngon_list )
|
|
{
|
|
ngud->m_ngon_list = new ON_V4V5_MeshNgonList();
|
|
ngud->m_mesh_F_count = m_F.Count();
|
|
ngud->m_mesh_V_count = m_V.Count();
|
|
}
|
|
|
|
return ngud->m_ngon_list;
|
|
}
|
|
|
|
|
|
void ON_Mesh::V4V5_DestroyNgonList()
|
|
{
|
|
ON_UserData* ud = GetUserData(ON_CLASS_ID(ON_V4V5_MeshNgonUserData));
|
|
if ( 0 != ud )
|
|
{
|
|
delete ud;
|
|
ud = 0;
|
|
}
|
|
}
|
|
|
|
static void InitializeVertexFaceMap(
|
|
ON__UINT_PTR* vertex_face_map,
|
|
unsigned int i0,
|
|
unsigned int i1,
|
|
unsigned int* a
|
|
)
|
|
{
|
|
if ( i1 > i0 )
|
|
{
|
|
ON__UINT_PTR vertex_face_count;
|
|
vertex_face_map += i0;
|
|
//i1 -= i0;
|
|
for ( i1 -= i0; i1 > 0; i1-- )
|
|
{
|
|
vertex_face_count = vertex_face_map[0];
|
|
if ( 0 != vertex_face_count )
|
|
{
|
|
a[0] = 0;
|
|
vertex_face_map[0] = (ON__UINT_PTR)a;
|
|
a += (vertex_face_count+1);
|
|
}
|
|
vertex_face_map++;
|
|
}
|
|
}
|
|
}
|
|
|
|
const ON_MeshFaceList ON_MeshFaceList::EmptyFaceList;
|
|
|
|
ON_MeshFaceList::ON_MeshFaceList(
|
|
const ON_Mesh* mesh
|
|
)
|
|
{
|
|
SetFromMesh(mesh);
|
|
}
|
|
|
|
unsigned int ON_MeshFaceList::SetFromTriangleList(
|
|
size_t triangle_count,
|
|
size_t triangle_stride,
|
|
const unsigned int* triangles
|
|
)
|
|
{
|
|
if ( triangle_count > 0 && triangle_stride >= 3 && 0 != triangles )
|
|
{
|
|
m_bQuadFaces = false;
|
|
m_face_count = (unsigned int)triangle_count;
|
|
m_face_stride = (unsigned int)triangle_stride;
|
|
m_faces = (const unsigned int*)triangles;
|
|
}
|
|
else
|
|
{
|
|
m_bQuadFaces = false;
|
|
m_face_count = 0;
|
|
m_face_stride = 0;
|
|
m_faces = 0;
|
|
}
|
|
return m_face_count;
|
|
}
|
|
|
|
unsigned int ON_MeshFaceList::SetFromQuadList(
|
|
size_t quad_count,
|
|
size_t quad_stride,
|
|
const unsigned int* quads
|
|
)
|
|
{
|
|
if ( quad_count > 0 && quad_stride >= 4 && 0 != quads )
|
|
{
|
|
m_bQuadFaces = true;
|
|
m_face_count = (unsigned int)quad_count;
|
|
m_face_stride = (unsigned int)quad_stride;
|
|
m_faces = quads;
|
|
}
|
|
else
|
|
{
|
|
m_bQuadFaces = false;
|
|
m_face_count = 0;
|
|
m_face_stride = 0;
|
|
m_faces = 0;
|
|
}
|
|
return m_face_count;
|
|
}
|
|
|
|
unsigned int ON_MeshFaceList::SetFromMesh(const ON_Mesh* mesh)
|
|
{
|
|
if ( 0 != mesh )
|
|
{
|
|
SetFromQuadList(mesh->m_F.UnsignedCount(),4,(const unsigned int*)mesh->m_F.Array());
|
|
}
|
|
else
|
|
{
|
|
SetFromQuadList(0,0,0);
|
|
}
|
|
return m_face_count;
|
|
}
|
|
|
|
|
|
unsigned int ON_MeshFaceList::GetVertexIndexInterval(
|
|
unsigned int minimum_valid_vertex_index,
|
|
unsigned int maximum_valid_vertex_index,
|
|
unsigned int* minimum_vertex_index,
|
|
unsigned int* maximum_vertex_index
|
|
) const
|
|
{
|
|
unsigned int vertex0 = 0;
|
|
unsigned int vertex1 = 0;
|
|
unsigned int v0, v1, vi;
|
|
unsigned int valid_face_count = 0;
|
|
for (;;)
|
|
{
|
|
if (0 == m_face_count || m_face_stride < 3 || nullptr == m_faces)
|
|
break;
|
|
const unsigned int face_vertex_count = m_bQuadFaces ? 4 : 3;
|
|
const unsigned int delta_f = m_face_stride - face_vertex_count;
|
|
const unsigned int* f1 = m_faces + (m_face_count*m_face_stride);
|
|
for (const unsigned int* f = m_faces; f < f1; f += delta_f)
|
|
{
|
|
v0 = v1 = *f++;
|
|
vi = *f++;
|
|
if (vi < v0) v0 = vi; else if (vi > v1) v1 = vi;
|
|
vi = *f++;
|
|
if (vi < v0) v0 = vi; else if (vi > v1) v1 = vi;
|
|
if (m_bQuadFaces)
|
|
{
|
|
vi = *f++;
|
|
if (vi < v0) v0 = vi; else if (vi > v1) v1 = vi;
|
|
}
|
|
if (v0 < minimum_valid_vertex_index || v1 > maximum_valid_vertex_index)
|
|
continue;
|
|
if (0 == valid_face_count)
|
|
{
|
|
vertex0 = v0;
|
|
vertex1 = v1;
|
|
}
|
|
else
|
|
{
|
|
if (v0 < vertex0)
|
|
vertex0 = v0;
|
|
if (v1 > vertex1)
|
|
vertex1 = v1;
|
|
}
|
|
valid_face_count++;
|
|
}
|
|
break;
|
|
}
|
|
if (minimum_vertex_index)
|
|
*minimum_vertex_index = vertex0;
|
|
if (maximum_vertex_index)
|
|
*maximum_vertex_index = vertex1;
|
|
return valid_face_count;
|
|
}
|
|
|
|
struct ON_MeshVertexFaceMap_BLK
|
|
{
|
|
struct ON_MeshVertexFaceMap_BLK* m_next;
|
|
static const size_t m_target_block_size;
|
|
};
|
|
|
|
// Four pages less a bit for overhead
|
|
const size_t ON_MeshVertexFaceMap_BLK::m_target_block_size(4096*4 - sizeof(ON_MeshVertexFaceMap_BLK) - 32);
|
|
|
|
|
|
void* ON_MeshVertexFaceMap::m_alloc(size_t sz)
|
|
{
|
|
// The returned pointer is used for arrays of pointers
|
|
// and arrays of unsigned ints.
|
|
// This code will need to be modified if the definition of
|
|
// struct ON_MeshVertexFaceMap_BLK changes in order to
|
|
// make certain alignment constraints are met on all
|
|
// "reasonable" CPUs.
|
|
|
|
struct ON_MeshVertexFaceMap_BLK* blk;
|
|
|
|
if ( sz <= 0 )
|
|
return 0;
|
|
|
|
if (sz % sizeof(struct ON_MeshVertexFaceMap_BLK))
|
|
sz += sizeof(struct ON_MeshVertexFaceMap_BLK);
|
|
|
|
blk = new (std::nothrow) ON_MeshVertexFaceMap_BLK[1 + sz/sizeof(struct ON_MeshVertexFaceMap_BLK)];
|
|
|
|
if ( 0 == blk )
|
|
return 0;
|
|
|
|
blk->m_next = (struct ON_MeshVertexFaceMap_BLK*)m_p;
|
|
m_p = blk;
|
|
return (blk+1);
|
|
}
|
|
|
|
void ON_MeshVertexFaceMap::Destroy()
|
|
{
|
|
ON_MeshVertexFaceMap_BLK* blk;
|
|
ON_MeshVertexFaceMap_BLK* next;
|
|
|
|
blk = (ON_MeshVertexFaceMap_BLK*)m_p;
|
|
|
|
m_vertex_count = 0;
|
|
m_face_count = 0;
|
|
m_vertex_face_map = 0;
|
|
m_p = 0;
|
|
|
|
while ( blk )
|
|
{
|
|
next = blk->m_next;
|
|
delete blk;
|
|
blk = next;
|
|
}
|
|
}
|
|
|
|
void ON_MeshVertexFaceMap::m_copy(const ON_MeshVertexFaceMap& src)
|
|
{
|
|
const size_t blk_sz = ON_MeshVertexFaceMap_BLK::m_target_block_size;
|
|
size_t sz, delta_sz;
|
|
|
|
const unsigned int* const * src_vf_map;
|
|
unsigned int** vf_map;
|
|
unsigned int* a;
|
|
const unsigned int* src_a;
|
|
unsigned int i0, i1, vf_count, a_count;
|
|
|
|
for(;;)
|
|
{
|
|
vf_map = 0;
|
|
sz = 0;
|
|
delta_sz = 0;
|
|
|
|
vf_count = src.m_vertex_count;
|
|
src_vf_map = src.m_vertex_face_map;
|
|
|
|
if ( vf_count <= 0 )
|
|
break;
|
|
|
|
if ( 0 == src_vf_map )
|
|
break;
|
|
|
|
sz = vf_count*sizeof(vf_map[0]);
|
|
|
|
for ( i0 = i1 = 0; i1 < vf_count; i1++)
|
|
{
|
|
if ( nullptr == src_vf_map[i1] || src_vf_map[i1][0] == 0 )
|
|
continue; // 0 faces use vertex i1.
|
|
delta_sz += src_vf_map[i1][0]*sizeof(a[0]);
|
|
if ( sz + delta_sz > blk_sz )
|
|
{
|
|
if ( 0 == vf_map )
|
|
{
|
|
vf_map = (unsigned int**)m_alloc(sz);
|
|
a = (unsigned int*)(vf_map ? (vf_map + vf_count) : 0);
|
|
}
|
|
else
|
|
{
|
|
a = (unsigned int*)m_alloc(sz);
|
|
}
|
|
if ( 0 == a )
|
|
break;
|
|
while(i0 < i1)
|
|
{
|
|
src_a = src_vf_map[i0];
|
|
if ( 0 != src_a && (a_count = src_a[0]) > 0 )
|
|
{
|
|
vf_map[i0] = a;
|
|
*a++ = *src_a++; // count
|
|
*a++ = *src_a++; // initial face index
|
|
while(--a_count > 0 )
|
|
*a++ = *src_a++; // additional face indices
|
|
}
|
|
else
|
|
{
|
|
vf_map[i0] = 0;
|
|
}
|
|
i0++;
|
|
}
|
|
sz = 0;
|
|
}
|
|
sz += delta_sz;
|
|
}
|
|
|
|
if ( i0 < i1 && sz > 0 )
|
|
{
|
|
if ( 0 == vf_map )
|
|
{
|
|
vf_map = (unsigned int**)m_alloc(sz);
|
|
a = (unsigned int*)(vf_map ? (vf_map + vf_count) : 0);
|
|
}
|
|
else
|
|
{
|
|
a = (unsigned int*)m_alloc(sz);
|
|
}
|
|
if ( 0 == a )
|
|
break;
|
|
while(i0 < i1)
|
|
{
|
|
src_a = src_vf_map[i0];
|
|
if ( 0 != src_a && (a_count = src_a[0]) > 0 )
|
|
{
|
|
vf_map[i0] = a;
|
|
*a++ = *src_a++; // count
|
|
*a++ = *src_a++; // initial face index
|
|
while(--a_count > 0 )
|
|
*a++ = *src_a++; // additional face indices
|
|
}
|
|
else
|
|
{
|
|
vf_map[i0] = 0;
|
|
}
|
|
i0++;
|
|
}
|
|
sz = 0;
|
|
}
|
|
|
|
return; // success
|
|
}
|
|
|
|
if ( 0 != vf_map )
|
|
Destroy(); // failure
|
|
}
|
|
|
|
ON_MeshVertexFaceMap::ON_MeshVertexFaceMap() ON_NOEXCEPT
|
|
: m_vertex_count(0)
|
|
, m_face_count(0)
|
|
, m_vertex_face_map(0)
|
|
, m_p(0)
|
|
{
|
|
}
|
|
|
|
ON_MeshVertexFaceMap::~ON_MeshVertexFaceMap()
|
|
{
|
|
Destroy();
|
|
}
|
|
|
|
ON_MeshVertexFaceMap::ON_MeshVertexFaceMap(const ON_MeshVertexFaceMap& src)
|
|
: m_vertex_count(0)
|
|
, m_face_count(0)
|
|
, m_vertex_face_map(0)
|
|
, m_p(0)
|
|
{
|
|
m_copy(src);
|
|
}
|
|
|
|
ON_MeshVertexFaceMap& ON_MeshVertexFaceMap::operator=(const ON_MeshVertexFaceMap& src)
|
|
{
|
|
if ( this != &src )
|
|
{
|
|
Destroy();
|
|
m_copy(src);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
|
|
#if defined(ON_HAS_RVALUEREF)
|
|
ON_MeshVertexFaceMap::ON_MeshVertexFaceMap( ON_MeshVertexFaceMap&& src) ON_NOEXCEPT
|
|
: m_vertex_count(src.m_vertex_count)
|
|
, m_face_count(src.m_face_count)
|
|
, m_vertex_face_map(src.m_vertex_face_map)
|
|
, m_p(src.m_p)
|
|
{
|
|
src.m_vertex_count = 0;
|
|
src.m_face_count = 0;
|
|
src.m_vertex_face_map = 0;
|
|
src.m_p = 0;
|
|
}
|
|
|
|
ON_MeshVertexFaceMap& ON_MeshVertexFaceMap::operator=( ON_MeshVertexFaceMap&& src) ON_NOEXCEPT
|
|
{
|
|
if ( this != &src )
|
|
{
|
|
Destroy();
|
|
|
|
m_vertex_count = src.m_vertex_count;
|
|
m_face_count = src.m_face_count;
|
|
m_vertex_face_map = src.m_vertex_face_map;
|
|
m_p = src.m_p;
|
|
|
|
src.m_vertex_count = 0;
|
|
src.m_face_count = 0;
|
|
src.m_vertex_face_map = 0;
|
|
src.m_p = 0;
|
|
}
|
|
return *this;
|
|
}
|
|
#endif
|
|
|
|
bool ON_MeshVertexFaceMap::SetFromMesh(
|
|
const ON_Mesh* mesh,
|
|
bool bMapInvalidFaces
|
|
)
|
|
{
|
|
ON_MeshFaceList face_list;
|
|
|
|
if ( face_list.SetFromMesh(mesh) > 0 )
|
|
{
|
|
return SetFromFaceList(
|
|
mesh->m_V.UnsignedCount(),
|
|
face_list,
|
|
bMapInvalidFaces
|
|
);
|
|
}
|
|
|
|
// failure
|
|
Destroy();
|
|
return false;
|
|
}
|
|
|
|
|
|
bool ON_MeshVertexFaceMap::SetFromFaceList(
|
|
unsigned int vertex_count,
|
|
const ON_MeshFaceList& face_list,
|
|
bool bMapInvalidFaces
|
|
)
|
|
{
|
|
size_t sz, delta_sz;
|
|
unsigned int* a;
|
|
ON__UINT_PTR* vertex_face_map = 0;
|
|
unsigned int fi, j, k, vi0, vi1, Fvi[4];
|
|
|
|
Destroy();
|
|
|
|
const unsigned int face_count = face_list.FaceCount();
|
|
|
|
|
|
for(;;)
|
|
{
|
|
const unsigned int max_valid_vertex_count = 0xFFFF0000U;
|
|
if (0 == vertex_count || vertex_count > max_valid_vertex_count)
|
|
{
|
|
if ( face_list.GetVertexIndexInterval(0,max_valid_vertex_count-1,0,&vertex_count) < 1 )
|
|
break;
|
|
vertex_count++;
|
|
}
|
|
if ( vertex_count <= 0 )
|
|
break;
|
|
if ( face_count <= 0 )
|
|
break;
|
|
|
|
sz = vertex_count*sizeof(vertex_face_map[0]);
|
|
vertex_face_map = (ON__UINT_PTR*)m_alloc(sz);
|
|
if ( 0 == vertex_face_map )
|
|
break;
|
|
memset(vertex_face_map,0,sz);
|
|
|
|
// count the number of faces at each vertex
|
|
sz = 0;
|
|
for ( fi = 0; fi < face_count; fi++ )
|
|
{
|
|
face_list.QuadFvi(fi,Fvi);
|
|
k = (Fvi[2]!=Fvi[3]) ? 4 : 3;
|
|
for ( j = 0; j < k; j++ )
|
|
{
|
|
if ( Fvi[j] < vertex_count )
|
|
{
|
|
sz++;
|
|
if ( 0 == vertex_face_map[Fvi[j]]++ )
|
|
sz++;
|
|
continue;
|
|
}
|
|
else if ( !bMapInvalidFaces )
|
|
break; // bogus face
|
|
}
|
|
}
|
|
|
|
if ( sz*sizeof(a[0]) <= 4*ON_MeshVertexFaceMap_BLK::m_target_block_size )
|
|
{
|
|
a = (unsigned int*)m_alloc(sz*sizeof(a[0]));
|
|
if ( 0 == a )
|
|
return 0;
|
|
InitializeVertexFaceMap(vertex_face_map,0,vertex_count,a);
|
|
}
|
|
else
|
|
{
|
|
// allocate memory in 4 page chunks
|
|
sz = 0;
|
|
k = 0;
|
|
for ( j = 0; j < vertex_count; j++ )
|
|
{
|
|
if ( vertex_face_map[j] > 0 )
|
|
{
|
|
delta_sz = (vertex_face_map[j]+1)*sizeof(a[0]);
|
|
if ( sz + delta_sz > ON_MeshVertexFaceMap_BLK::m_target_block_size )
|
|
{
|
|
a = (unsigned int*)m_alloc(sz);
|
|
if ( 0 == a )
|
|
return 0;
|
|
InitializeVertexFaceMap(vertex_face_map,k,j,a);
|
|
k = j;
|
|
sz = 0;
|
|
}
|
|
sz += delta_sz;
|
|
}
|
|
}
|
|
|
|
if ( sz > 0 )
|
|
{
|
|
a = (unsigned int*)m_alloc(sz);
|
|
if ( 0 == a )
|
|
return 0;
|
|
InitializeVertexFaceMap(vertex_face_map,k,vertex_count,a);
|
|
}
|
|
}
|
|
|
|
// Fill in the vertex_face_map[].
|
|
sz = 0; // sz is used to count vertex_face_map[] entries that need to be nulled out
|
|
for ( fi = 0; fi < face_count; fi++ )
|
|
{
|
|
face_list.QuadFvi(fi,Fvi);
|
|
k = (Fvi[2]!=Fvi[3]) ? 3 : 2;
|
|
vi0 = Fvi[3];
|
|
for ( j = 0; j <= k; j++ )
|
|
{
|
|
vi1 = Fvi[j];
|
|
if ( vi0 == vi1 )
|
|
{
|
|
if ( bMapInvalidFaces )
|
|
{
|
|
// caller wants the indices of invalid faces left in the vertex face map
|
|
continue;
|
|
}
|
|
}
|
|
else if ( vi1 < vertex_count )
|
|
{
|
|
a = (unsigned int*)vertex_face_map[vi1];
|
|
vi0 = ++(a[0]); // vi0 temporarily used as an a[] index
|
|
a[vi0] = fi;
|
|
vi0 = vi1; // ... now set vi0 = previous vertex index
|
|
continue;
|
|
}
|
|
|
|
// The code in this for loop below this comment
|
|
// is run only when F[fi].vi[] is not valid.
|
|
|
|
if ( bMapInvalidFaces )
|
|
{
|
|
// caller wants the indices of invalid faces left in the vertex face map
|
|
continue;
|
|
}
|
|
|
|
// The face vertex list F[fi].vi[] is not valid - remove any references to it.
|
|
if ( j > 0 )
|
|
{
|
|
for (k = 0; k < j; k++ )
|
|
{
|
|
a = (unsigned int*)vertex_face_map[Fvi[k]];
|
|
a[0]--;
|
|
if ( 0 == a[0] )
|
|
{
|
|
// If sz > 0, then further cleanup may be required
|
|
// if no other valid faces use this vertex. This
|
|
// is done below.
|
|
sz++;
|
|
}
|
|
}
|
|
}
|
|
else if ( vi1 < vertex_count )
|
|
{
|
|
// If mesh face F[fi] is the only face that references the vertex
|
|
// with index vi1 = F[fi].vi[0], then vertex_face_map[vi1]
|
|
// will be removed.
|
|
a = (unsigned int*)vertex_face_map[vi1];
|
|
if ( 0 != a && 0 == a[0] )
|
|
sz++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( sz > 0 )
|
|
{
|
|
// need to check for empty vertex_face_map[] arrays and remove them
|
|
for ( j = 0; j < vertex_count; j++ )
|
|
{
|
|
a = (unsigned int*)vertex_face_map[j];
|
|
if ( 0 != a && 0 == a[0] )
|
|
{
|
|
// Every face that uses this vertex is not valid.
|
|
vertex_face_map[j] = 0;
|
|
sz--;
|
|
if ( 0 == sz )
|
|
{
|
|
// finished with repairs.
|
|
// Note:
|
|
// When a vertex is used by both valid and invalid faces,
|
|
// sz will not return to zero and this loop will terminate with
|
|
// j == meshVcount.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_vertex_face_map = (const unsigned int *const*)vertex_face_map;
|
|
m_vertex_count = vertex_count;
|
|
return true;
|
|
}
|
|
|
|
if ( vertex_face_map )
|
|
Destroy();
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
unsigned int ON_MeshVertexFaceMap::VertexCount() const
|
|
{
|
|
return m_vertex_count;
|
|
}
|
|
|
|
unsigned int ON_MeshVertexFaceMap::FaceCount() const
|
|
{
|
|
return m_face_count;
|
|
}
|
|
|
|
unsigned int ON_MeshVertexFaceMap::VertexFaceCount(
|
|
unsigned int vertex_index
|
|
) const
|
|
{
|
|
const unsigned int* vf_list = (vertex_index < m_vertex_count) ? m_vertex_face_map[vertex_index] : 0;
|
|
return vf_list ? vf_list[0] : 0;
|
|
}
|
|
|
|
const unsigned int* ON_MeshVertexFaceMap::VertexFaceList(
|
|
unsigned int vertex_index
|
|
) const
|
|
{
|
|
const unsigned int* vf_list = (vertex_index < m_vertex_count) ? m_vertex_face_map[vertex_index] : 0;
|
|
return (vf_list && vf_list[0] > 0) ? (vf_list+1) : 0;
|
|
}
|
|
|
|
const unsigned int *const* ON_MeshVertexFaceMap::VertexFaceMap() const
|
|
{
|
|
return m_vertex_face_map;
|
|
}
|
|
|
|
static bool FaceInPlane(
|
|
double planar_tolerance,
|
|
ON_PlaneEquation e,
|
|
const ON_3dPointListRef& vertex_list,
|
|
const unsigned int Fvi[4]
|
|
)
|
|
{
|
|
// Note:
|
|
// This function is called only with Fvi[] lists that have been checked to
|
|
// have valid enteries.
|
|
double point_buffer[3];
|
|
|
|
if ( !(fabs(e.ValueAt(vertex_list.GetPoint(Fvi[0],point_buffer))) <= planar_tolerance) )
|
|
return false;
|
|
if ( !(fabs(e.ValueAt(vertex_list.GetPoint(Fvi[1],point_buffer))) <= planar_tolerance) )
|
|
return false;
|
|
if ( !(fabs(e.ValueAt(vertex_list.GetPoint(Fvi[2],point_buffer))) <= planar_tolerance) )
|
|
return false;
|
|
if ( (Fvi[3] != Fvi[2]) && !(fabs(e.ValueAt(vertex_list.GetPoint(Fvi[3],point_buffer))) <= planar_tolerance) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool GetFacePlaneEquation(
|
|
const ON_3dPointListRef& vertex_list,
|
|
bool bQuadFaces,
|
|
const unsigned int* Fvi,
|
|
double planar_tolerance,
|
|
ON_PlaneEquation& e
|
|
)
|
|
{
|
|
// The P = (ON_3dPoint*)buffer; technique is used to
|
|
// avoid calling constructor code in this performance
|
|
// critical calculation.
|
|
ON_3dPoint* P;
|
|
double buffer[4][3];
|
|
unsigned int i;
|
|
const unsigned int vertex_count = vertex_list.PointCount();
|
|
const unsigned int k = bQuadFaces?4:3;
|
|
|
|
if ( Fvi[0] == Fvi[1] || Fvi[1] == Fvi[2] || Fvi[0] == Fvi[k-1] )
|
|
{
|
|
// all FindNgon() code after this line can
|
|
// assume Fvi[0] !+ Fvi[1], Fvi[1] != Fvi[2] and Fvi[3] != Fvi[0]
|
|
return false;
|
|
}
|
|
|
|
P = (ON_3dPoint*)buffer;
|
|
for ( i = 0; i < k; i++ )
|
|
{
|
|
if ( Fvi[i] >= vertex_count )
|
|
return false;
|
|
vertex_list.GetPoint(Fvi[i],buffer[i]);
|
|
}
|
|
|
|
if ( !bQuadFaces )
|
|
P[3] = P[2];
|
|
|
|
const ON_3dVector A(P[2].x-P[0].x,P[2].y-P[0].y,P[2].z-P[0].z);
|
|
const ON_3dVector B(P[3].x-P[1].x,P[3].y-P[1].y,P[3].z-P[1].z);
|
|
ON_3dVector N(A.y*B.z - B.y*A.z, A.z*B.x - B.z*A.x, A.x*B.y - B.x*A.y );
|
|
double d = N.Length();
|
|
if ( !(d > 0.0) )
|
|
return false;
|
|
N.x /= d;
|
|
N.y /= d;
|
|
N.z /= d;
|
|
d = -(N.x*P[0].x + N.y*P[0].y + N.z*P[0].z);
|
|
if ( !(fabs(N.x*P[1].x + N.y*P[1].y + N.z*P[1].z + d) <= planar_tolerance) )
|
|
return false;
|
|
if ( !(fabs(N.x*P[2].x + N.y*P[2].y + N.z*P[2].z + d) <= planar_tolerance) )
|
|
return false;
|
|
if ( !(fabs(N.x*P[3].x + N.y*P[3].y + N.z*P[3].z + d) <= planar_tolerance) )
|
|
return false;
|
|
|
|
e.x = N.x;
|
|
e.y = N.y;
|
|
e.z = N.z;
|
|
e.d = d;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
Returns:
|
|
index into the ON_MeshNgon.m_fi[] array.
|
|
*/
|
|
static ON__UINT_PTR DecodeNFS_NgonFaceIndex( ON__UINT_PTR nfs )
|
|
{
|
|
return (nfs/8);
|
|
}
|
|
|
|
static unsigned int DecodeNFS_EdgeIndex( ON__UINT_PTR nfs )
|
|
{
|
|
return ((unsigned int)(nfs & 0x3));
|
|
}
|
|
|
|
/*
|
|
Parameters:
|
|
ngon_face_index - [in]
|
|
ON_MeshNgon.m_fi[] array index
|
|
face_edge_index - [in]
|
|
0 to 3
|
|
the edge runs from
|
|
vertex index ON_MeshFace.vi[face_edge_index]
|
|
to
|
|
vertex index ON_MeshFace.vi[(face_edge_index+1)%4].
|
|
|
|
Returns:
|
|
index into the ON_MeshNgon.m_fi[] array.
|
|
*/
|
|
static ON__UINT_PTR EncodeNFS_Neighbor(ON__UINT_PTR ngon_face_index, ON__UINT_PTR face_edge_index)
|
|
{
|
|
// The 0x4 makes the value non-zero when ngon_face_index and face_edge_index are both zero.
|
|
return ((ngon_face_index*8) | face_edge_index | 0x4 );
|
|
}
|
|
|
|
static ON__UINT_PTR EncodeNFS_BoundaryIndex(ON__UINT_PTR boundary_index)
|
|
{
|
|
// 0 = (boundary_mark & 0x4)
|
|
// 0 != boundary_mark
|
|
return (boundary_index*8 | 0x1);
|
|
}
|
|
|
|
static bool IsNFS_Neighbor(ON__UINT_PTR nfs)
|
|
{
|
|
return ( 0 != (nfs&0x4) );
|
|
}
|
|
|
|
struct NgonNeighbors
|
|
{
|
|
// NFS = N-gon face and Side information
|
|
// DecodeNFS_NgonFaceIndex(m_NFS[side_index]) = index into the ON_MeshNgon.m_fi[] array
|
|
// DecodeNFS_EdgeIndex(m_NFS[side_index]) = 0,1,2 or 3 identifies the edge
|
|
ON__UINT_PTR m_NFS[4];
|
|
};
|
|
|
|
static unsigned int FaceListIndex(
|
|
unsigned int face_index_count,
|
|
const unsigned int* face_index_list,
|
|
unsigned int face_index
|
|
)
|
|
{
|
|
for ( unsigned int i = 0; i < face_index_count; i++ )
|
|
{
|
|
if ( face_index == face_index_list[i] )
|
|
return i;
|
|
}
|
|
return ON_UNSET_UINT_INDEX;
|
|
}
|
|
|
|
static unsigned int SetFaceNeighborMap(
|
|
unsigned int mesh_vertex_count,
|
|
const ON_MeshFaceList& mesh_face_list,
|
|
const unsigned int *const* vertex_face_map,
|
|
ON_MeshVertexFaceMap* vertex_face_map_obj,
|
|
unsigned int face_index_count,
|
|
const unsigned int* face_index_list,
|
|
struct NgonNeighbors* face_nbr_map
|
|
)
|
|
{
|
|
//const unsigned int mesh_vertex_count = mesh_vertex_list.VertexCount();
|
|
const unsigned int mesh_face_count = mesh_face_list.FaceCount();
|
|
unsigned int face_index, nbr_face_index, viA, viB, Adex;
|
|
unsigned int Fvi[4], nbr_Fvi[4]; // quad face vertex indices
|
|
unsigned int face_side, nbr_face_side; // 0 to 3 = face side index
|
|
unsigned int fdex, nbr_fdex; // indices into the face_index_list[] array
|
|
const unsigned int* face_listA;
|
|
const unsigned int* face_listB;
|
|
|
|
unsigned int boundary_count = 0;
|
|
|
|
if ( face_index_count <= 0 || 0 == face_index_list || 0 == face_nbr_map )
|
|
return 0;
|
|
|
|
ON_MeshVertexFaceMap vf_tmp;
|
|
ON_MeshVertexFaceMap* vf_map = &vf_tmp;
|
|
|
|
const unsigned int* const* localVertexFaceMap = vertex_face_map;
|
|
if (nullptr == localVertexFaceMap)
|
|
{
|
|
if (vertex_face_map_obj != nullptr)
|
|
{
|
|
localVertexFaceMap = vertex_face_map_obj->VertexFaceMap();
|
|
}
|
|
}
|
|
if (vertex_face_map_obj != nullptr && vertex_face_map == nullptr)
|
|
{
|
|
vf_map = vertex_face_map_obj;
|
|
}
|
|
|
|
if (nullptr == localVertexFaceMap)
|
|
{
|
|
if ( !vf_map->SetFromFaceList(mesh_vertex_count,mesh_face_list,false) )
|
|
return 0;
|
|
localVertexFaceMap = vf_map->VertexFaceMap();
|
|
if ( 0 == localVertexFaceMap)
|
|
return 0;
|
|
mesh_vertex_count = vf_map->VertexCount();
|
|
}
|
|
|
|
memset(face_nbr_map,0,face_index_count*sizeof(face_nbr_map[0]));
|
|
for ( fdex = 0; fdex < face_index_count; fdex++ )
|
|
{
|
|
face_index = face_index_list[fdex];
|
|
if ( face_index >= mesh_face_count )
|
|
continue;
|
|
|
|
mesh_face_list.QuadFvi(face_index,Fvi);
|
|
viB = Fvi[0];
|
|
face_listB = ( viB <= mesh_vertex_count ) ? localVertexFaceMap[viB] : 0;
|
|
if ( 0 != face_listB && face_listB[0] <= 1 )
|
|
face_listB = 0;
|
|
|
|
for ( face_side = 0; face_side < 4; face_side++ )
|
|
{
|
|
viA = viB;
|
|
viB = Fvi[(face_side+1)%4];
|
|
if ( viA == viB )
|
|
continue;
|
|
|
|
boundary_count++;
|
|
|
|
face_listA = face_listB;
|
|
face_listB = ( viB <= mesh_vertex_count ) ? localVertexFaceMap[viB] : 0;
|
|
if ( 0 == face_listB )
|
|
continue;
|
|
if ( face_listB[0] <= 1 )
|
|
{
|
|
face_listB = 0;
|
|
continue;
|
|
}
|
|
if ( 0 == face_listA )
|
|
continue;
|
|
|
|
if ( 0 != face_nbr_map[fdex].m_NFS[face_side] )
|
|
{
|
|
boundary_count--;
|
|
continue; // already found a neighbor for this side
|
|
}
|
|
|
|
// look for a neighbor from viB to viA
|
|
for ( Adex = 1; Adex <= face_listA[0]; Adex++ )
|
|
{
|
|
nbr_face_index = face_listA[Adex];
|
|
if ( face_index == nbr_face_index)
|
|
continue;
|
|
nbr_fdex = FaceListIndex(face_index_count,face_index_list,nbr_face_index);
|
|
if ( nbr_fdex >= face_index_count )
|
|
continue; // nbr_face_index is not in the face_index_list[] array
|
|
if ( nbr_fdex <= fdex )
|
|
continue; // this combination of faces has already been checked.
|
|
|
|
mesh_face_list.QuadFvi(nbr_face_index,nbr_Fvi);
|
|
for ( nbr_face_side = 0; nbr_face_side < 4; nbr_face_side++ )
|
|
{
|
|
if ( viB != nbr_Fvi[nbr_face_side] )
|
|
continue;
|
|
|
|
if ( viA != nbr_Fvi[(nbr_face_side+1)%4] )
|
|
continue; // happens for the duplicate vertex in a triangle
|
|
|
|
if ( 0 != face_nbr_map[nbr_fdex].m_NFS[nbr_face_side] )
|
|
continue; // this side of the neighbor has a neighbor (unexpected)
|
|
|
|
// record the fact these faces share an edge
|
|
face_nbr_map[nbr_fdex].m_NFS[nbr_face_side] = EncodeNFS_Neighbor(fdex,face_side);
|
|
face_nbr_map[fdex].m_NFS[face_side] = EncodeNFS_Neighbor(nbr_fdex,nbr_face_side);
|
|
boundary_count--;
|
|
nbr_face_side = 5;
|
|
break;
|
|
}
|
|
if ( 5 == nbr_face_side )
|
|
break; // we found a neighbor and are finished seaching
|
|
}
|
|
}
|
|
}
|
|
|
|
return boundary_count;
|
|
}
|
|
|
|
|
|
static int GetCoplanarConnectedFaces(
|
|
const ON_3dPointListRef& vertex_list,
|
|
const ON_MeshFaceList& face_list,
|
|
const unsigned int ngonIndex,
|
|
unsigned int* ngonMap,
|
|
const unsigned int *const* vertex_face_map,
|
|
double planar_tolerance,
|
|
unsigned int starting_face_index,
|
|
ON_PlaneEquation e,
|
|
ON_SimpleArray<unsigned int>& ngon_fi,
|
|
ON_SimpleArray<struct NgonNeighbors>& ngon_nbr_map
|
|
)
|
|
{
|
|
ON__UINT_PTR face_side, nbr_face_side;
|
|
unsigned int boundary_count, ngon_fi_dex0, ngon_fi_dex1, nbr_ngon_fi_dex, viA, viB, Adex, nbr_face_index, face_index;
|
|
unsigned int Fvi[4], nbr_Fvi[4];
|
|
const unsigned int* face_listA;
|
|
const unsigned int* face_listB;
|
|
const unsigned int vertex_count = vertex_list.PointCount();
|
|
|
|
ngon_fi.SetCount(0);
|
|
ngon_fi.Append(starting_face_index);
|
|
|
|
ngon_nbr_map.SetCount(0);
|
|
ngon_nbr_map.AppendNew();
|
|
|
|
ngonMap[starting_face_index] = ngonIndex;
|
|
|
|
boundary_count = 0;
|
|
|
|
ngon_fi_dex1 = 0;
|
|
while( ngon_fi_dex1 < ngon_fi.UnsignedCount() )
|
|
{
|
|
ngon_fi_dex0 = ngon_fi_dex1;
|
|
for (ngon_fi_dex1 = ngon_fi.Count(); ngon_fi_dex0 < ngon_fi_dex1; ngon_fi_dex0++ )
|
|
{
|
|
// look for neighbors of F[face_index]
|
|
face_index = ngon_fi[ngon_fi_dex0];
|
|
face_list.QuadFvi(face_index,Fvi);
|
|
viB = Fvi[0];
|
|
face_listB = ( viB <= vertex_count ) ? vertex_face_map[viB] : 0;
|
|
if ( 0 != face_listB && face_listB[0] <= 1 )
|
|
face_listB = 0;
|
|
for ( face_side = 0; face_side < 4; face_side++ )
|
|
{
|
|
viA = viB;
|
|
viB = Fvi[(face_side+1)%4];
|
|
if ( viA == viB )
|
|
continue;
|
|
|
|
boundary_count++;
|
|
|
|
face_listA = face_listB;
|
|
face_listB = ( viB <= vertex_count ) ? vertex_face_map[viB] : 0;
|
|
if ( 0 == face_listB )
|
|
continue;
|
|
if ( face_listB[0] <= 1 )
|
|
{
|
|
face_listB = 0;
|
|
continue;
|
|
}
|
|
if ( 0 == face_listA )
|
|
continue;
|
|
|
|
if ( 0 != ngon_nbr_map[ngon_fi_dex0].m_NFS[face_side] )
|
|
{
|
|
boundary_count--;
|
|
continue; // already found a neighbor for this side
|
|
}
|
|
|
|
// look for a coplanar neighbor from viB to viA
|
|
for ( Adex = 1; Adex <= face_listA[0]; Adex++ )
|
|
{
|
|
nbr_face_index = face_listA[Adex];
|
|
if ( face_index == nbr_face_index || nbr_face_index <= starting_face_index)
|
|
continue;
|
|
if ( ON_UNSET_UINT_INDEX != ngonMap[nbr_face_index] && ngonIndex != ngonMap[nbr_face_index])
|
|
continue; // ON_Mesh.m_F[nbr_face_index] belongs to another n-gon
|
|
|
|
face_list.QuadFvi(nbr_face_index,nbr_Fvi);
|
|
for ( nbr_face_side = 0; nbr_face_side < 4; nbr_face_side++ )
|
|
{
|
|
if ( viB != nbr_Fvi[nbr_face_side] )
|
|
continue;
|
|
|
|
if ( viA != nbr_Fvi[(nbr_face_side+1)%4] )
|
|
continue; // happens for the duplicate vertex in a triangle
|
|
|
|
if ( !FaceInPlane(planar_tolerance,e,vertex_list,nbr_Fvi) )
|
|
break; // shared edge but not coplanar
|
|
|
|
if ( ON_UNSET_UINT_INDEX == ngonMap[nbr_face_index] )
|
|
{
|
|
// add ON_Mesh.m_F[nbr_face_index] to this ngon
|
|
nbr_ngon_fi_dex = ngon_fi.Count();
|
|
ngonMap[nbr_face_index] = ngonIndex;
|
|
ngon_fi.Append(nbr_face_index);
|
|
ngon_nbr_map.AppendNew().m_NFS[nbr_face_side] = EncodeNFS_Neighbor(ngon_fi_dex0,face_side);
|
|
}
|
|
else
|
|
{
|
|
// If things are as expected, then ON_Mesh.m_F[nbr_face_index]
|
|
// face was been added to the ngon after the face we are working
|
|
// on ( ON_Mesh.m_F[ngon_fi[ngon_fi_dex0]] ).
|
|
for ( nbr_ngon_fi_dex = ngon_fi.Count()-1; nbr_ngon_fi_dex > ngon_fi_dex0; nbr_ngon_fi_dex-- )
|
|
{
|
|
if (nbr_face_index == ngon_fi[nbr_ngon_fi_dex])
|
|
break;
|
|
}
|
|
if ( nbr_ngon_fi_dex <= ngon_fi_dex0 )
|
|
break; // not expected for an oriented manifold mesh
|
|
|
|
if ( 0 != ngon_nbr_map[nbr_ngon_fi_dex].m_NFS[nbr_face_side] )
|
|
break; // not expected for an oriented manifold mesh
|
|
|
|
ngon_nbr_map[nbr_ngon_fi_dex].m_NFS[nbr_face_side] = EncodeNFS_Neighbor(ngon_fi_dex0,face_side);
|
|
}
|
|
|
|
if ( nbr_ngon_fi_dex > ngon_fi_dex0 )
|
|
{
|
|
ngon_nbr_map[ngon_fi_dex0].m_NFS[face_side] = EncodeNFS_Neighbor(nbr_ngon_fi_dex,nbr_face_side);
|
|
boundary_count--;
|
|
nbr_face_side = 5;
|
|
}
|
|
break;
|
|
}
|
|
if ( 5 == nbr_face_side )
|
|
break; // we found a neighbor and are finished seaching
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return boundary_count;
|
|
}
|
|
|
|
|
|
static void UnsetNgonMap(
|
|
const unsigned int* ngon_face_index_list,
|
|
unsigned int ngon_face_index_count,
|
|
const unsigned int ngonIndex,
|
|
const unsigned int ngonUnsetIndex,
|
|
unsigned int* ngonMap
|
|
)
|
|
{
|
|
int fi;
|
|
for ( unsigned int i = 0; i < ngon_face_index_count; i++ )
|
|
{
|
|
fi = ngon_face_index_list[i];
|
|
if (ngonIndex == ngonMap[fi])
|
|
ngonMap[fi] = ngonUnsetIndex;
|
|
}
|
|
}
|
|
|
|
static unsigned int EncodeNgonSide(
|
|
unsigned int ngon_fdex,
|
|
unsigned int quad_side_index,
|
|
unsigned int bReversed
|
|
)
|
|
{
|
|
return (ngon_fdex*8 + quad_side_index + (bReversed ? 4 : 0));
|
|
}
|
|
|
|
|
|
/*
|
|
Description:
|
|
NEVER EXPORT THIS WORKER FUNCTION.
|
|
|
|
Given a set of connected faces, this function finds the
|
|
boundary of the n-gon.
|
|
Parameters:
|
|
face_list - [in]
|
|
ngon_fi_count - [in]
|
|
>= 0
|
|
ngon_fi - [in]
|
|
array of length ngon_fi_count that contains the
|
|
indices of faces that make up the ngon.
|
|
ngon_boundary_index - [in]
|
|
index of the next boundary segment to be added.
|
|
ngon_nbr_map - [in/out]
|
|
array of length ngon_fi_count that contains the
|
|
face neighbor information.
|
|
ngon_nbr_map[face_index].m_NFS[j] must be zero
|
|
for open boundaryies. If the face side identified by
|
|
ngon_nbr_map[face_index].m_NFS[j] is assigned to an
|
|
ngon boundary, then it is marked as a boundary segment.
|
|
ngon_vi - [out]
|
|
If true is returned, then ngon_vi is an array of vertex
|
|
indices that make up the boundary of the ngon.
|
|
ngon_side - [out]
|
|
ngon_side[i]/8 = ON_MeshNgon.m_fi[] index
|
|
ngon_side[i]%4 = quad side index (side=0 means vtx 0 to vtx 1)
|
|
(ngon_side[i] & 8) != 0 means side is reversed.
|
|
Returns:
|
|
Number of vertices/segments added for the boundary component.
|
|
*/
|
|
static unsigned int GetNgonBoundarySegments(
|
|
const ON_MeshFaceList& face_list,
|
|
unsigned int ngon_fi_count,
|
|
const unsigned int* ngon_fi,
|
|
unsigned int ngon_boundary_index,
|
|
struct NgonNeighbors* ngon_nbr_map,
|
|
ON_SimpleArray<unsigned int>* ngon_vi,
|
|
ON_SimpleArray<unsigned int>* ngon_side
|
|
)
|
|
{
|
|
unsigned int j0, next_j0, j1;
|
|
unsigned int Fvi[4];
|
|
unsigned int vi0, vi1, fi, max_vertex_count;
|
|
unsigned int ngon_fdex, nbr_ngon_fdex, face_vist_count, max_face_visit_count;
|
|
|
|
const unsigned int ngon_boundary_index0 = ngon_boundary_index;
|
|
|
|
if ( ngon_fi_count <= 0 || 0 == ngon_fi || 0 == ngon_nbr_map )
|
|
return 0;
|
|
|
|
if ( 1 == ngon_fi_count )
|
|
{
|
|
if ( 0 != ngon_nbr_map[0].m_NFS[0]
|
|
|| 0 != ngon_nbr_map[0].m_NFS[1]
|
|
|| 0 != ngon_nbr_map[0].m_NFS[2]
|
|
|| 0 != ngon_nbr_map[0].m_NFS[3]
|
|
)
|
|
{
|
|
return 0;
|
|
}
|
|
face_list.QuadFvi(ngon_fi[0],Fvi);
|
|
|
|
if ( ngon_vi )
|
|
ngon_vi->Append(Fvi[0]);
|
|
if ( ngon_side )
|
|
ngon_side->AppendNew() = EncodeNgonSide(0,0,false);
|
|
ngon_nbr_map[0].m_NFS[0] = EncodeNFS_BoundaryIndex(ngon_boundary_index++);
|
|
|
|
if ( ngon_vi )
|
|
ngon_vi->Append(Fvi[1]);
|
|
if ( ngon_side )
|
|
ngon_side->AppendNew() = EncodeNgonSide(0,1,false);
|
|
ngon_nbr_map[0].m_NFS[1] = EncodeNFS_BoundaryIndex(ngon_boundary_index++);
|
|
|
|
if ( Fvi[2] != Fvi[3] )
|
|
{
|
|
if ( ngon_vi )
|
|
ngon_vi->Append(Fvi[2]);
|
|
if ( ngon_side )
|
|
ngon_side->AppendNew() = EncodeNgonSide(0,2,false);
|
|
ngon_nbr_map[0].m_NFS[2] = EncodeNFS_BoundaryIndex(ngon_boundary_index++);
|
|
}
|
|
|
|
if ( ngon_vi )
|
|
ngon_vi->Append(Fvi[3]);
|
|
if ( ngon_side )
|
|
ngon_side->AppendNew() = EncodeNgonSide(0,3,false);
|
|
ngon_nbr_map[0].m_NFS[3] = EncodeNFS_BoundaryIndex(ngon_boundary_index++);
|
|
|
|
return ngon_boundary_index - ngon_boundary_index0;
|
|
}
|
|
|
|
|
|
fi = 0; // keep lint quiet - at this point ngon_fi_count > 0
|
|
j0 = 0; // keep lint quiet - at this point ngon_fi_count > 0
|
|
|
|
// find a boundary edge
|
|
for ( ngon_fdex = 0; ngon_fdex < ngon_fi_count; ngon_fdex++ )
|
|
{
|
|
fi = ngon_fi[ngon_fdex];
|
|
face_list.QuadFvi(fi,Fvi);
|
|
for ( j0 = 0; j0 < 4; j0++ )
|
|
{
|
|
if ( Fvi[j0] == Fvi[(j0+1)%4] )
|
|
continue;
|
|
if ( 0 != ngon_nbr_map[ngon_fdex].m_NFS[j0] )
|
|
continue; // has a neighbor or is part of a previously found boundary component when holes are allowed
|
|
|
|
// we being at the vertex with index Fvi[j]
|
|
break;
|
|
}
|
|
if ( j0 < 4 )
|
|
break;
|
|
}
|
|
|
|
if ( ngon_fdex >= ngon_fi_count )
|
|
return 0;
|
|
|
|
// max_vertex_count is used to prevent infinite recursion
|
|
// when the mesh information is not valid or there is
|
|
// a bug in this code. The maximum value occurs
|
|
// for a strip of quads.
|
|
max_vertex_count = 2*ngon_fi_count + 2;
|
|
|
|
|
|
// max_face_visit_count is used to prevent infinite recursion
|
|
// when the mesh information is not valid or there is
|
|
// a bug in this code. The maximum value occurs
|
|
// for a strip of quads when the initial face is
|
|
// at the end of a strip.
|
|
max_face_visit_count = 2*ngon_fi_count - 1;
|
|
|
|
ngon_nbr_map[ngon_fdex].m_NFS[j0] = EncodeNFS_BoundaryIndex(ngon_boundary_index++);
|
|
if ( ngon_side )
|
|
ngon_side->AppendNew() = EncodeNgonSide(ngon_fdex,j0,false);
|
|
|
|
vi0 = Fvi[j0];
|
|
j0 = (j0+1)%4;
|
|
vi1 = Fvi[j0];
|
|
if ( ngon_vi )
|
|
{
|
|
ngon_vi->Append(vi0);
|
|
ngon_vi->Append(vi1);
|
|
}
|
|
for ( face_vist_count = 0;
|
|
face_vist_count <= max_face_visit_count && ngon_boundary_index - ngon_boundary_index0 <= max_vertex_count;
|
|
face_vist_count++
|
|
)
|
|
{
|
|
for (j1 = 0; j1 < 4; j1++, j0 = next_j0 )
|
|
{
|
|
if ( 0 != ngon_nbr_map[ngon_fdex].m_NFS[j0] )
|
|
break; // has a neighbor or is a boundary
|
|
|
|
next_j0 = (j0+1)%4;
|
|
if ( vi1 == Fvi[next_j0] )
|
|
continue; // skip over duplicate vertex in triangles
|
|
|
|
ngon_nbr_map[ngon_fdex].m_NFS[j0] = EncodeNFS_BoundaryIndex(ngon_boundary_index++);
|
|
if ( ngon_side )
|
|
ngon_side->AppendNew() = EncodeNgonSide(ngon_fdex,j0,false);
|
|
|
|
vi1 = Fvi[next_j0];
|
|
if ( vi0 == vi1 )
|
|
{
|
|
// this is where successful searches end with (ngon_vi.Count() >= 3)
|
|
if ( ngon_boundary_index - ngon_boundary_index0 >= 3 )
|
|
{
|
|
return ngon_boundary_index - ngon_boundary_index0;
|
|
}
|
|
return 0;
|
|
}
|
|
if ( ngon_vi )
|
|
ngon_vi->Append(vi1);
|
|
}
|
|
|
|
if ( j1 >= 4 )
|
|
break; // not expected - there is a bug or a bogus mesh
|
|
|
|
if ( false == IsNFS_Neighbor(ngon_nbr_map[ngon_fdex].m_NFS[j0]) )
|
|
break; // not expected - there is a bug or a bogus mesh
|
|
|
|
// switch to neighbor face;
|
|
nbr_ngon_fdex = (unsigned int)DecodeNFS_NgonFaceIndex(ngon_nbr_map[ngon_fdex].m_NFS[j0]);
|
|
|
|
if ( nbr_ngon_fdex == ngon_fdex || nbr_ngon_fdex >= ngon_fi_count )
|
|
{
|
|
// Unexpected - bail rather than crash.
|
|
// This means there is a bug in the code that
|
|
// set the values in the ngon_nbr_map[] array.
|
|
// The same face may be visited multiple times (strip of quads for example),
|
|
// but a face can never be its own neighbor.
|
|
break;
|
|
}
|
|
|
|
// move onto the neighbor
|
|
j0 = DecodeNFS_EdgeIndex(ngon_nbr_map[ngon_fdex].m_NFS[j0]);
|
|
|
|
ngon_fdex = nbr_ngon_fdex;
|
|
fi = ngon_fi[ngon_fdex];
|
|
face_list.QuadFvi(fi,Fvi);
|
|
// faces have compatible orientations
|
|
// (The shared edge is reversed when going to neighboring face)
|
|
j0 = (j0+1)%4; // move the the vertex at the end of the shared edge
|
|
|
|
if ( vi1 != Fvi[j0] )
|
|
{
|
|
// not expected - there is a bug or a bogus mesh
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
class ON_MeshNgon* ON_MeshNgon::NgonFromMeshFace(
|
|
class ON_MeshNgonBuffer& ngon_buffer,
|
|
unsigned int mesh_face_index,
|
|
const unsigned int* fvi
|
|
)
|
|
{
|
|
// The nullptr check is weird.
|
|
// Speculation: There is / was some way that a null reference (like a * of a bona fide nullptr)
|
|
// was in use?
|
|
if ( ON_IsNullPtr(&ngon_buffer) || 0 == fvi )
|
|
return 0;
|
|
|
|
// local_fvi[] allows the fvi[] input to be stored in the buffer[] memory.
|
|
unsigned int local_fvi[4] = {fvi[0],fvi[1],fvi[2],fvi[3]};
|
|
fvi = local_fvi;
|
|
|
|
unsigned int Vcount = (fvi[3] < ON_UNSET_UINT_INDEX && fvi[3] != fvi[2]) ? 4 : 3;
|
|
|
|
ON_MeshNgon* ngon = (ON_MeshNgon*)(&ngon_buffer);
|
|
ngon->m_vi = (unsigned int*)(ngon+1);
|
|
ngon->m_fi = ngon->m_vi + Vcount;
|
|
|
|
ngon->m_Vcount = Vcount;
|
|
ngon->m_vi[0] = fvi[0];
|
|
ngon->m_vi[1] = fvi[1];
|
|
ngon->m_vi[2] = fvi[2];
|
|
if ( 4 == ngon->m_Vcount )
|
|
ngon->m_vi[3] = fvi[3];
|
|
|
|
ngon->m_Fcount = 1;
|
|
ngon->m_fi[0] = mesh_face_index;
|
|
|
|
return ngon;
|
|
}
|
|
|
|
class ON_MeshNgon** ON_MeshNgon::NgonListFromMeshFace(
|
|
class ON_MeshNgonBuffer& ngon_buffer,
|
|
unsigned int mesh_face_index,
|
|
const unsigned int* fvi
|
|
)
|
|
{
|
|
ON_MeshNgon* ngon = NgonFromMeshFace(ngon_buffer,mesh_face_index,fvi);
|
|
if ( 0 == ngon )
|
|
return 0;
|
|
|
|
ON_MeshNgon** ngon_list = (ON_MeshNgon**)(ngon+1);
|
|
ngon_list[0] = ngon;
|
|
|
|
return ngon_list;
|
|
}
|
|
|
|
const ON_MeshNgon* ON_Mesh::NgonFromComponentIndex(
|
|
class ON_MeshNgonBuffer& ngon_buffer,
|
|
ON_COMPONENT_INDEX ci
|
|
) const
|
|
{
|
|
const ON_MeshNgon* ngon = 0;
|
|
switch( ci.m_type )
|
|
{
|
|
case ON_COMPONENT_INDEX::mesh_face:
|
|
if ( ci.m_index >= 0 && ci.m_index < m_F.Count() )
|
|
{
|
|
ngon = ON_MeshNgon::NgonFromMeshFace(ngon_buffer,(unsigned int)ci.m_index,(const unsigned int*)m_F[ci.m_index].vi);
|
|
}
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::mesh_ngon:
|
|
ngon = Ngon(ci.m_index);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ngon;
|
|
}
|
|
|
|
static unsigned int FindNgonBoundary_Helper(
|
|
const ON_3dPointListRef& mesh_vertex_list,
|
|
const ON_MeshFaceList& mesh_face_list,
|
|
const unsigned int *const* vertex_face_map,
|
|
ON_MeshVertexFaceMap* vertex_face_map_obj,
|
|
size_t ngon_fi_count,
|
|
const unsigned int* ngon_fi,
|
|
ON_SimpleArray<unsigned int>& ngon_vi,
|
|
bool permitOnlyOneBoundary
|
|
)
|
|
{
|
|
const unsigned int mesh_vertex_count = mesh_vertex_list.PointCount();
|
|
const unsigned int ngon_boundary_index = 1;
|
|
unsigned int boundary_edge_count;
|
|
|
|
|
|
for (;;)
|
|
{
|
|
if (mesh_vertex_count <= 0 || ON_UNSET_UINT_INDEX == mesh_vertex_count)
|
|
break;
|
|
|
|
if (ngon_fi_count <= 0 || 0 == ngon_fi)
|
|
break;
|
|
|
|
ON_SimpleArray<struct NgonNeighbors> ngon_nbr_map;
|
|
ngon_nbr_map.Reserve((unsigned int)ngon_fi_count);
|
|
ngon_nbr_map.SetCount((unsigned int)ngon_fi_count);
|
|
boundary_edge_count = SetFaceNeighborMap(
|
|
mesh_vertex_count,
|
|
mesh_face_list,
|
|
vertex_face_map,
|
|
vertex_face_map_obj,
|
|
(unsigned int)ngon_fi_count,
|
|
ngon_fi,
|
|
ngon_nbr_map.Array()
|
|
);
|
|
|
|
if (boundary_edge_count <= 0)
|
|
break;
|
|
|
|
ngon_vi.SetCount(0);
|
|
ngon_vi.Reserve(boundary_edge_count);
|
|
if (!GetNgonBoundarySegments(
|
|
mesh_face_list,
|
|
(unsigned int)ngon_fi_count,
|
|
ngon_fi,
|
|
ngon_boundary_index,
|
|
ngon_nbr_map.Array(),
|
|
&ngon_vi,
|
|
0
|
|
))
|
|
break;
|
|
|
|
if (permitOnlyOneBoundary && boundary_edge_count != ngon_vi.UnsignedCount())
|
|
break; // inner boundaries exist - ngon has holes
|
|
|
|
return ngon_vi.UnsignedCount();
|
|
}
|
|
|
|
// failure
|
|
ngon_vi.SetCount(0);
|
|
return 0;
|
|
}
|
|
|
|
static
|
|
double Internal_NgonBoundaryArea(
|
|
const ON_Plane& ngon_plane,
|
|
const ON_3dPointListRef& vertex_list,
|
|
const ON_SimpleArray< unsigned int >& ngon_vi
|
|
)
|
|
{
|
|
ON_2dPoint p0, p1;
|
|
const unsigned int count = ngon_vi.UnsignedCount();
|
|
if (count < 3)
|
|
return 0.0;
|
|
if (false == ngon_plane.ClosestPointTo(vertex_list[ngon_vi[count - 1]], &p1.x, &p1.y))
|
|
return 0.0;
|
|
double twice_area = 0.0;
|
|
for (unsigned int i = 0; i < count; i++)
|
|
{
|
|
p0 = p1;
|
|
if (false == ngon_plane.ClosestPointTo(vertex_list[ngon_vi[i]], &p1.x, &p1.y))
|
|
return 0.0;
|
|
twice_area += (p0.x - p1.x) * (p0.y + p1.y);
|
|
}
|
|
return fabs(0.5 * twice_area);
|
|
}
|
|
|
|
static unsigned int FindNgonBoundaries_Helper(
|
|
const ON_3dPointListRef& mesh_vertex_list,
|
|
const ON_MeshFaceList& mesh_face_list,
|
|
const unsigned int* const* vertex_face_map,
|
|
ON_MeshVertexFaceMap* vertex_face_map_obj,
|
|
size_t ngon_fi_count,
|
|
const unsigned int* ngon_fi,
|
|
ON_SimpleArray<unsigned int>& ngon_vi,
|
|
ON_SimpleArray<unsigned int>& ngon_vi_markers,
|
|
bool permitOnlyOneBoundary
|
|
)
|
|
{
|
|
const unsigned int mesh_vertex_count = mesh_vertex_list.PointCount();
|
|
unsigned int boundary_edge_count;
|
|
unsigned int ngon_boundary_index = 0;
|
|
|
|
for (;;)
|
|
{
|
|
if (mesh_vertex_count <= 0 || ON_UNSET_UINT_INDEX == mesh_vertex_count)
|
|
break;
|
|
|
|
if (ngon_fi_count <= 0 || 0 == ngon_fi)
|
|
break;
|
|
|
|
ON_SimpleArray<struct NgonNeighbors> ngon_nbr_map;
|
|
ngon_nbr_map.Reserve((unsigned int)ngon_fi_count);
|
|
ngon_nbr_map.SetCount((unsigned int)ngon_fi_count);
|
|
boundary_edge_count = SetFaceNeighborMap(mesh_vertex_count, mesh_face_list, vertex_face_map, vertex_face_map_obj, (unsigned int)ngon_fi_count, ngon_fi, ngon_nbr_map.Array());
|
|
|
|
ngon_boundary_index++;
|
|
ngon_vi.SetCount(0);
|
|
if (0 == GetNgonBoundarySegments(mesh_face_list, (unsigned int)ngon_fi_count, ngon_fi, ngon_boundary_index, ngon_nbr_map.Array(), &ngon_vi, 0))
|
|
break;
|
|
|
|
ngon_vi_markers.Append(ngon_vi.Count());
|
|
|
|
if (ngon_vi.UnsignedCount() < boundary_edge_count)
|
|
{
|
|
ON_SimpleArray<unsigned int> tmp;
|
|
// ngon has holes.
|
|
// The boundary with the largest area is the outer boundary.
|
|
ON_3dVector A(mesh_vertex_list[ngon_vi[1]] - mesh_vertex_list[ngon_vi[0]]);
|
|
ON_3dVector B(mesh_vertex_list[ngon_vi[ngon_vi.Count()-1]] - mesh_vertex_list[ngon_vi[0]]);
|
|
A.Unitize(); B.Unitize();
|
|
const ON_Plane ngon_plane(mesh_vertex_list[ngon_vi[0]], ON_CrossProduct(A, B));
|
|
|
|
double ngon_vi_area = Internal_NgonBoundaryArea(ngon_plane, mesh_vertex_list, ngon_vi);
|
|
|
|
for (;;)
|
|
{
|
|
ON_SimpleArray<unsigned int> ngon_vi1;
|
|
ngon_boundary_index++;
|
|
if (0 == GetNgonBoundarySegments(mesh_face_list, (unsigned int)ngon_fi_count, ngon_fi, ngon_boundary_index, ngon_nbr_map.Array(), &ngon_vi1, 0))
|
|
break;
|
|
if (ngon_vi1.UnsignedCount() < 3)
|
|
break;
|
|
double ngon_vi1_area = Internal_NgonBoundaryArea(ngon_plane, mesh_vertex_list, ngon_vi1);
|
|
if (ngon_vi1_area > ngon_vi_area)
|
|
{
|
|
ngon_vi_area = ngon_vi1_area;
|
|
tmp.Empty();
|
|
tmp.Append(ngon_vi1.Count(), ngon_vi1.Array());
|
|
tmp.Append(ngon_vi.Count(), ngon_vi.Array());
|
|
ngon_vi = tmp;
|
|
ngon_vi_markers.Insert(0, ngon_vi1.Count());
|
|
}
|
|
else
|
|
{
|
|
ngon_vi_markers.Append(ngon_vi1.Count());
|
|
ngon_vi.Append(ngon_vi1.Count(), ngon_vi1.Array());
|
|
}
|
|
}
|
|
}
|
|
|
|
//if (permitOnlyOneBoundary && boundary_edge_count != ngon_vi.UnsignedCount())
|
|
// break; // inner boundaries exist - ngon has holes
|
|
|
|
return ngon_vi.UnsignedCount();
|
|
}
|
|
|
|
// failure
|
|
ngon_vi.SetCount(0);
|
|
return 0;
|
|
}
|
|
|
|
|
|
unsigned int ON_MeshNgon::FindNgonOuterBoundary(
|
|
const ON_3dPointListRef& mesh_vertex_list,
|
|
const ON_MeshFaceList& mesh_face_list,
|
|
const unsigned int *const* vertex_face_map,
|
|
size_t ngon_fi_count,
|
|
const unsigned int* ngon_fi,
|
|
ON_SimpleArray<unsigned int>& ngon_vi
|
|
)
|
|
{
|
|
return FindNgonBoundary_Helper(
|
|
mesh_vertex_list,
|
|
mesh_face_list,
|
|
vertex_face_map,
|
|
nullptr,
|
|
ngon_fi_count,
|
|
ngon_fi,
|
|
ngon_vi,
|
|
true
|
|
);
|
|
}
|
|
|
|
unsigned int ON_MeshNgon::FindNgonOuterBoundary(
|
|
const ON_3dPointListRef& mesh_vertex_list,
|
|
const ON_MeshFaceList& mesh_face_list,
|
|
ON_MeshVertexFaceMap* vertex_face_map,
|
|
size_t ngon_fi_count,
|
|
const unsigned int* ngon_fi,
|
|
ON_SimpleArray<unsigned int>& ngon_vi
|
|
)
|
|
{
|
|
return FindNgonBoundary_Helper(
|
|
mesh_vertex_list,
|
|
mesh_face_list,
|
|
nullptr,
|
|
vertex_face_map,
|
|
ngon_fi_count,
|
|
ngon_fi,
|
|
ngon_vi,
|
|
true
|
|
);
|
|
}
|
|
|
|
unsigned int ON_MeshNgon::FindNgonBoundaries(
|
|
const class ON_3dPointListRef& mesh_vertex_list,
|
|
const class ON_MeshFaceList& mesh_face_list,
|
|
ON_MeshVertexFaceMap* vertex_face_map,
|
|
size_t ngon_fi_count,
|
|
const unsigned int* ngon_fi,
|
|
ON_SimpleArray<unsigned int>& ngon_vi,
|
|
ON_SimpleArray<unsigned int>& ngon_vi_markers
|
|
)
|
|
{
|
|
return FindNgonBoundaries_Helper(
|
|
mesh_vertex_list,
|
|
mesh_face_list,
|
|
nullptr,
|
|
vertex_face_map,
|
|
ngon_fi_count,
|
|
ngon_fi,
|
|
ngon_vi,
|
|
ngon_vi_markers,
|
|
false
|
|
);
|
|
}
|
|
|
|
unsigned int ON_MeshNgon::FindNgonBoundary(
|
|
const ON_3dPointListRef& mesh_vertex_list,
|
|
const ON_MeshFaceList& mesh_face_list,
|
|
const unsigned int *const* vertex_face_map,
|
|
size_t ngon_fi_count,
|
|
const unsigned int* ngon_fi,
|
|
ON_SimpleArray<unsigned int>& ngon_vi
|
|
)
|
|
{
|
|
return FindNgonBoundary_Helper(
|
|
mesh_vertex_list,
|
|
mesh_face_list,
|
|
vertex_face_map,
|
|
nullptr,
|
|
ngon_fi_count,
|
|
ngon_fi,
|
|
ngon_vi,
|
|
false
|
|
);
|
|
}
|
|
|
|
|
|
|
|
unsigned int ON_MeshNgon::FindNgonBoundary(
|
|
const ON_3dPointListRef& mesh_vertex_list,
|
|
const ON_MeshFaceList& mesh_face_list,
|
|
ON_MeshVertexFaceMap* vertex_face_map,
|
|
size_t ngon_fi_count,
|
|
const unsigned int* ngon_fi,
|
|
ON_SimpleArray<unsigned int>& ngon_vi
|
|
)
|
|
{
|
|
return FindNgonBoundary_Helper(
|
|
mesh_vertex_list,
|
|
mesh_face_list,
|
|
nullptr,
|
|
vertex_face_map,
|
|
ngon_fi_count,
|
|
ngon_fi,
|
|
ngon_vi,
|
|
false
|
|
);
|
|
}
|
|
|
|
unsigned int ON_MeshNgon::GetBoundarySides(
|
|
const class ON_MeshFaceList& mesh_face_list,
|
|
ON_SimpleArray<unsigned int>& ngon_boundary_sides
|
|
) const
|
|
{
|
|
unsigned int ngon_boundary_segment_count = 0;
|
|
unsigned int boundary_edge_count;
|
|
|
|
ngon_boundary_sides.SetCount(0);
|
|
|
|
|
|
for(;;)
|
|
{
|
|
const unsigned int ngon_fi_count(m_Fcount);
|
|
const unsigned int* ngon_fi(m_fi);
|
|
if ( ngon_fi_count <= 0 || 0 == ngon_fi )
|
|
break;
|
|
|
|
const unsigned int mesh_vertex_count = 0xFFFFFFFE;
|
|
const unsigned int *const* vertex_face_map = 0;
|
|
|
|
ON_SimpleArray<struct NgonNeighbors> ngon_nbr_map;
|
|
ngon_nbr_map.Reserve(ngon_fi_count);
|
|
ngon_nbr_map.SetCount(ngon_fi_count);
|
|
boundary_edge_count = SetFaceNeighborMap(
|
|
mesh_vertex_count,
|
|
mesh_face_list,
|
|
vertex_face_map,
|
|
nullptr,
|
|
ngon_fi_count,
|
|
ngon_fi,
|
|
ngon_nbr_map.Array()
|
|
);
|
|
|
|
if ( boundary_edge_count <= 0 )
|
|
break;
|
|
|
|
ngon_boundary_sides.SetCount(0);
|
|
ngon_boundary_sides.Reserve(boundary_edge_count);
|
|
|
|
for(;;)
|
|
{
|
|
// keep adding boundary components until we
|
|
// find all of them or there is an error
|
|
unsigned int segment_count = GetNgonBoundarySegments(
|
|
mesh_face_list,
|
|
(unsigned int)ngon_fi_count,
|
|
ngon_fi,
|
|
ngon_boundary_segment_count,
|
|
ngon_nbr_map.Array(),
|
|
0,
|
|
&ngon_boundary_sides
|
|
);
|
|
if ( segment_count <= 0 )
|
|
break; //error
|
|
|
|
ngon_boundary_segment_count += segment_count;
|
|
if ( ngon_boundary_segment_count >= boundary_edge_count )
|
|
return ngon_boundary_sides.UnsignedCount(); // found them all
|
|
}
|
|
|
|
break; // error if we get here
|
|
}
|
|
|
|
// failure
|
|
ngon_boundary_sides.SetCount(0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
unsigned int ON_Mesh::GetNgonOuterBoundary(
|
|
unsigned int ngon_fi_count,
|
|
const unsigned int* ngon_fi,
|
|
ON_SimpleArray<unsigned int>& ngon_vi
|
|
) const
|
|
{
|
|
ON_3dPointListRef mesh_vertex_list;
|
|
ON_MeshFaceList mesh_face_list;
|
|
mesh_vertex_list.SetFromMesh(this);
|
|
mesh_face_list.SetFromMesh(this);
|
|
return ON_MeshNgon::FindNgonOuterBoundary(mesh_vertex_list,mesh_face_list,(ON_MeshVertexFaceMap*)nullptr,ngon_fi_count,ngon_fi,ngon_vi);
|
|
}
|
|
|
|
unsigned int ON_MeshNgon::FindPlanarNgons(
|
|
const ON_3dPointListRef& vertex_list,
|
|
const ON_MeshFaceList& face_list,
|
|
const unsigned int *const* vertex_face_map,
|
|
double planar_tolerance,
|
|
unsigned int minimum_ngon_vertex_count,
|
|
unsigned int minimum_ngon_face_count,
|
|
bool bAllowHoles,
|
|
ON_MeshNgonAllocator& NgonAllocator,
|
|
ON_SimpleArray<unsigned int>& NgonMap,
|
|
ON_SimpleArray<ON_MeshNgon*>& Ngons
|
|
)
|
|
{
|
|
const unsigned int NgonsCount0 = Ngons.UnsignedCount();
|
|
const unsigned int vertex_count = vertex_list.PointCount();
|
|
const unsigned int face_count = face_list.FaceCount();
|
|
unsigned int ngon_boundary_index = 0;
|
|
const bool bQuadFaces = (4 == face_list.FaceVertexCount());
|
|
|
|
ON_MeshVertexFaceMap ws_vfmap;
|
|
|
|
ON_MeshNgon* ngon;
|
|
unsigned int Fvi[4];
|
|
unsigned int face_index, ngon_boundary_edge_count;
|
|
ON_SimpleArray<unsigned int> ngon_vi;
|
|
ON_SimpleArray<unsigned int> ngon_fi;
|
|
ON_SimpleArray<struct NgonNeighbors> ngon_nbr_map;
|
|
ON_PlaneEquation e;
|
|
|
|
bool bCleanUpNgonMap = false;
|
|
|
|
for (;;)
|
|
{
|
|
if ( face_count <= 0 || face_count != NgonMap.UnsignedCount() )
|
|
NgonMap.SetCount(0);
|
|
|
|
if ( face_count <= 0 || vertex_count <= 0 )
|
|
break;
|
|
|
|
if ( 0 == vertex_face_map )
|
|
{
|
|
if ( !ws_vfmap.SetFromFaceList(vertex_count,face_list,false) )
|
|
break;
|
|
vertex_face_map = ws_vfmap.VertexFaceMap();
|
|
if ( 0 == vertex_face_map )
|
|
break;
|
|
}
|
|
|
|
ngon_vi.Reserve(128);
|
|
ngon_fi.Reserve(128);
|
|
ngon_nbr_map.Reserve(128);
|
|
|
|
unsigned int* ngonMap = NgonMap.Array();
|
|
if ( face_count != NgonMap.UnsignedCount() )
|
|
{
|
|
NgonMap.Reserve(face_count);
|
|
NgonMap.SetCount(face_count);
|
|
ngonMap = NgonMap.Array(); // in case a reallocation occured.
|
|
for ( face_index = 0; face_index < face_count; face_index++ )
|
|
ngonMap[face_index] = ON_UNSET_UINT_INDEX;
|
|
}
|
|
|
|
int ngonIndex = NgonsCount0;
|
|
unsigned int ngonUnsetIndex;
|
|
const unsigned int omitted_face_mark = ON_UNSET_UINT_INDEX - 1;
|
|
|
|
for ( face_index = 0; face_index < face_count; face_index++ )
|
|
{
|
|
if ( ON_UNSET_UINT_INDEX != ngonMap[face_index] )
|
|
continue; // this face is not eligable for being in an n-gon
|
|
|
|
face_list.QuadFvi(face_index,Fvi);
|
|
if ( !GetFacePlaneEquation(vertex_list,bQuadFaces,Fvi,planar_tolerance,e) )
|
|
continue; // degenerate face
|
|
|
|
for (;;)
|
|
{
|
|
ngon = 0;
|
|
ngonUnsetIndex = ON_UNSET_UINT_INDEX;
|
|
|
|
// Note:
|
|
// ngon_boundary_count includes interior edges when the n-gon has holes
|
|
ngon_boundary_edge_count = GetCoplanarConnectedFaces(
|
|
vertex_list,
|
|
face_list,
|
|
ngonIndex, ngonMap, vertex_face_map,
|
|
planar_tolerance, face_index, e,
|
|
ngon_fi,
|
|
ngon_nbr_map
|
|
);
|
|
|
|
if ( ngon_boundary_edge_count < minimum_ngon_vertex_count )
|
|
break;
|
|
|
|
if ( ngon_fi.UnsignedCount() < minimum_ngon_face_count )
|
|
break;
|
|
|
|
ngon_boundary_index++;
|
|
ngon_vi.SetCount(0);
|
|
if ( !GetNgonBoundarySegments(face_list,
|
|
ngon_fi.UnsignedCount(),ngon_fi.Array(),
|
|
ngon_boundary_index,
|
|
ngon_nbr_map.Array(),
|
|
&ngon_vi,
|
|
0
|
|
)
|
|
)
|
|
break;
|
|
|
|
if ( ngon_vi.UnsignedCount() < minimum_ngon_vertex_count )
|
|
break;
|
|
|
|
if ( ngon_vi.UnsignedCount() < ngon_boundary_edge_count )
|
|
{
|
|
// ngon has holes.
|
|
// The boundary with the largest area is the outer boundary.
|
|
const ON_Plane ngon_plane(vertex_list[ngon_vi[0]], e.UnitNormal());
|
|
|
|
double ngon_vi_area = Internal_NgonBoundaryArea(ngon_plane, vertex_list, ngon_vi);
|
|
|
|
for(;;)
|
|
{
|
|
ON_SimpleArray<unsigned int> ngon_vi1;
|
|
ngon_boundary_index++;
|
|
if ( !GetNgonBoundarySegments(face_list,
|
|
ngon_fi.UnsignedCount(),ngon_fi.Array(),
|
|
ngon_boundary_index,
|
|
ngon_nbr_map.Array(),
|
|
&ngon_vi1, 0)
|
|
)
|
|
break;
|
|
if ( ngon_vi1.UnsignedCount() < 3 )
|
|
break;
|
|
double ngon_vi1_area = Internal_NgonBoundaryArea(ngon_plane, vertex_list, ngon_vi1);
|
|
if (ngon_vi1_area > ngon_vi_area)
|
|
{
|
|
ngon_vi_area = ngon_vi1_area;
|
|
ngon_vi = ngon_vi1;
|
|
}
|
|
}
|
|
|
|
if (false == bAllowHoles)
|
|
{
|
|
ngonUnsetIndex = omitted_face_mark;
|
|
bCleanUpNgonMap = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ngon = NgonAllocator.AllocateNgon(ngon_vi.UnsignedCount(),ngon_fi.UnsignedCount());
|
|
if ( 0 == ngon )
|
|
break;
|
|
|
|
memcpy(ngon->m_vi,ngon_vi.Array(),ngon->m_Vcount*sizeof(ngon->m_vi[0]));
|
|
memcpy(ngon->m_fi,ngon_fi.Array(),ngon->m_Fcount*sizeof(ngon->m_fi[0]));
|
|
break; // finished new ngon
|
|
}
|
|
|
|
if (0 == ngon)
|
|
{
|
|
// undo any modifications that were made to ngonMap[]
|
|
UnsetNgonMap(ngon_fi.Array(),ngon_fi.UnsignedCount(),ngonIndex,ngonUnsetIndex,ngonMap);
|
|
continue;
|
|
}
|
|
|
|
// found a new ngon
|
|
Ngons.Append(ngon);
|
|
ngonIndex++;
|
|
}
|
|
if (bCleanUpNgonMap)
|
|
{
|
|
for (face_index = 0; face_index < face_count; face_index++)
|
|
{
|
|
if (omitted_face_mark == ngonMap[face_index])
|
|
ngonMap[face_index] = ON_UNSET_UINT_INDEX;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
return Ngons.UnsignedCount() - NgonsCount0;
|
|
}
|
|
|
|
unsigned int ON_Mesh::AddPlanarNgons(
|
|
const unsigned int *const* vertex_face_map,
|
|
double planar_tolerance,
|
|
unsigned int minimum_ngon_vertex_count,
|
|
unsigned int minimum_ngon_face_count,
|
|
bool bAllowHoles
|
|
)
|
|
{
|
|
const ON_3dPointListRef vertex_list(this);
|
|
const ON_MeshFaceList face_list(this);
|
|
|
|
unsigned int added_ngon_count = ON_MeshNgon::FindPlanarNgons(
|
|
vertex_list,
|
|
face_list,
|
|
vertex_face_map,
|
|
planar_tolerance,
|
|
minimum_ngon_vertex_count,
|
|
minimum_ngon_face_count,
|
|
bAllowHoles,
|
|
m_NgonAllocator,
|
|
m_NgonMap,
|
|
m_Ngon
|
|
);
|
|
|
|
return added_ngon_count;
|
|
}
|
|
|
|
class ON_VertexIndexRef
|
|
{
|
|
public:
|
|
const unsigned int* m_vertex_index_ptr; // address of a unsigned int vertex index
|
|
unsigned int m_old_vertex_index;
|
|
unsigned int m_new_vertex_index; // new vertex index value
|
|
};
|
|
|
|
class /* DO NOT EXPORT THIS CLASS OR PUT IT IN A HEADER FILE */ ON_MeshSeparateNgonInfo
|
|
{
|
|
// This class is used to pass around information needed
|
|
// by the functions used by ON_Mesh::SeparateNgons()
|
|
// to duplicated shared vertices.
|
|
public:
|
|
ON_MeshSeparateNgonInfo(
|
|
ON_Mesh* mesh,
|
|
unsigned int** vertex_face_map
|
|
)
|
|
: m_mesh(mesh)
|
|
, m_vertex_face_map(vertex_face_map)
|
|
, m_face_count(mesh ? mesh->FaceUnsignedCount() : 0)
|
|
, m_vertex_count0(mesh ? mesh->VertexUnsignedCount() : 0)
|
|
, m_vertex_count1(m_vertex_count0)
|
|
, m_vertex_count2(m_vertex_count0)
|
|
{
|
|
if ( 0 != mesh )
|
|
{
|
|
m_ngondex_from_facedex_map = ( m_face_count != mesh->m_NgonMap.UnsignedCount() )
|
|
? mesh->CreateNgonMap()
|
|
: mesh->NgonMap();
|
|
|
|
if ( 0 == m_vertex_face_map )
|
|
{
|
|
m_local_vfmap.SetFromMesh(mesh,true);
|
|
m_vertex_face_map = const_cast<unsigned int**>(m_local_vfmap.VertexFaceMap());
|
|
}
|
|
}
|
|
}
|
|
|
|
public:
|
|
|
|
bool IsValid() const
|
|
{
|
|
if ( 0 == m_mesh )
|
|
return false;
|
|
if ( m_vertex_count0 <= 3 )
|
|
return false;
|
|
if ( m_face_count <= 1 )
|
|
return false;
|
|
if ( 0 == m_vertex_face_map )
|
|
return false;
|
|
if ( 0 == m_ngondex_from_facedex_map )
|
|
return false;
|
|
if ( 0 == m_ngondex_from_facedex_map )
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// After SetNgon() sets the ngon,
|
|
bool FindSharedNgonVertices(
|
|
unsigned int ngon_index
|
|
);
|
|
|
|
// FindSharedNgonVertices() uses TestNgonVertex()
|
|
bool TestNgonVertex(
|
|
const unsigned int* vertex_index_ptr
|
|
);
|
|
|
|
void DuplicateSharedNgonVertices();
|
|
|
|
bool AddVertexIndexRef(
|
|
const unsigned int* vertex_index_ptr,
|
|
unsigned int new_vertex_index
|
|
)
|
|
{
|
|
if ( 0 == vertex_index_ptr )
|
|
return false;
|
|
unsigned int old_vertex_index = *vertex_index_ptr;
|
|
if ( old_vertex_index >= m_vertex_count0 )
|
|
return false;
|
|
if ( new_vertex_index < m_vertex_count0 )
|
|
return false;
|
|
if ( 0 == m_virefpool.SizeofElement() )
|
|
m_virefpool.Create(sizeof(ON_VertexIndexRef),0,0);
|
|
ON_VertexIndexRef* viref = (ON_VertexIndexRef*)m_virefpool.AllocateElement();
|
|
if ( 0 == viref )
|
|
return false;
|
|
viref->m_vertex_index_ptr = vertex_index_ptr;
|
|
viref->m_old_vertex_index = old_vertex_index;
|
|
viref->m_new_vertex_index = new_vertex_index;
|
|
return true;
|
|
}
|
|
|
|
ON_Mesh* m_mesh = nullptr;
|
|
unsigned int** m_vertex_face_map = nullptr;
|
|
const unsigned int* m_ngondex_from_facedex_map = nullptr;
|
|
unsigned int* m_ngon_vmap = nullptr;
|
|
const unsigned int m_face_count; // mesh face count
|
|
const unsigned int m_vertex_count0; // original vertex count
|
|
unsigned int m_vertex_count1 = 0;
|
|
unsigned int m_vertex_count2 = 0;
|
|
unsigned int m_ni = ON_UNSET_UINT_INDEX;
|
|
unsigned int m_fi = ON_UNSET_UINT_INDEX;
|
|
unsigned int m_vi= ON_UNSET_UINT_INDEX;
|
|
unsigned int m_ngon_vmark = 0;
|
|
const ON_MeshNgon* m_ngon = nullptr;
|
|
|
|
private:
|
|
ON_SimpleArray<unsigned int> m_ngon_vmap_buffer;
|
|
ON_MeshVertexFaceMap m_local_vfmap;
|
|
ON_FixedSizePool m_virefpool;
|
|
};
|
|
|
|
void ON_MeshSeparateNgonInfo::DuplicateSharedNgonVertices()
|
|
{
|
|
unsigned int vertex_count = m_mesh->VertexUnsignedCount();
|
|
ON_FixedSizePoolIterator fit(m_virefpool);
|
|
for ( ON_VertexIndexRef* vr = (ON_VertexIndexRef*)fit.FirstElement();
|
|
0 != vr;
|
|
vr = (ON_VertexIndexRef*)fit.NextElement()
|
|
)
|
|
{
|
|
if ( 0 == vr->m_vertex_index_ptr )
|
|
continue;
|
|
if ( vr->m_old_vertex_index != *vr->m_vertex_index_ptr )
|
|
continue;
|
|
if ( vr->m_old_vertex_index >= m_vertex_count0 )
|
|
continue;
|
|
if ( vr->m_new_vertex_index < m_vertex_count0 )
|
|
continue;
|
|
if ( vr->m_new_vertex_index > vertex_count )
|
|
continue;
|
|
if ( vr->m_new_vertex_index == vertex_count )
|
|
{
|
|
if ( vertex_count != m_mesh->AppendDuplicateVertex(vr->m_old_vertex_index) )
|
|
return;
|
|
vertex_count++;
|
|
}
|
|
*const_cast<unsigned int*>(vr->m_vertex_index_ptr) = vr->m_new_vertex_index;
|
|
}
|
|
}
|
|
|
|
bool ON_MeshSeparateNgonInfo::TestNgonVertex(
|
|
const unsigned int* vertex_index_ptr
|
|
)
|
|
{
|
|
if ( 0 == vertex_index_ptr )
|
|
return false;
|
|
m_vi = *vertex_index_ptr;
|
|
if ( m_vi >= m_vertex_count0 )
|
|
{
|
|
// The source of this vertex index contained a bogus value.
|
|
return true;
|
|
}
|
|
|
|
const unsigned int n = m_ngon_vmap[m_vi];
|
|
|
|
if ( m_ngon_vmark == n )
|
|
{
|
|
// This vertex is known to be referenced only
|
|
// by this ngon and does not need to be tested
|
|
// or duplicated.
|
|
return true;
|
|
}
|
|
|
|
if ( m_vertex_count1 <= n && n < m_vertex_count2 )
|
|
{
|
|
// this vertex is flagged for duplication in this ngon.
|
|
return AddVertexIndexRef(vertex_index_ptr,n);
|
|
}
|
|
|
|
unsigned int* vfm = m_vertex_face_map[m_vi];
|
|
const unsigned int vfm_count0 = (0 != vfm) ? *vfm++ : 0;
|
|
if ( vfm_count0 > 0 )
|
|
{
|
|
for ( unsigned int vfmi = 0; vfmi < vfm_count0; vfmi++ )
|
|
{
|
|
unsigned int vfi = vfm[vfmi];
|
|
if( m_fi == vfi )
|
|
continue;
|
|
if ( vfi >= m_face_count )
|
|
continue;
|
|
if ( m_ni == m_ngondex_from_facedex_map[vfi] )
|
|
continue; // face[vfi] is in this ngon
|
|
|
|
// this vertex is shared by a face not belonging to this ngon.
|
|
if ( false == AddVertexIndexRef(vertex_index_ptr,m_vertex_count2) )
|
|
return false;
|
|
m_ngon_vmap[m_vi] = m_vertex_count2;
|
|
m_vertex_count2++;
|
|
|
|
// Any faces in this ngon that reference vertex m_vi
|
|
// will be modified to reference vertex m_vertex_count2.
|
|
// These faces need to be removed from vfm[], so
|
|
// future searches for faces that share vertex m_vi
|
|
// will be correct.
|
|
unsigned int vfm_count1 = 0;
|
|
for ( vfmi = 0; vfmi < vfm_count0; vfmi++ )
|
|
{
|
|
unsigned int vfi_local = vfm[vfmi];
|
|
if( m_fi == vfi_local )
|
|
continue;
|
|
if ( vfi_local < m_face_count && m_ni == m_ngondex_from_facedex_map[vfi_local] )
|
|
continue; // face[vfi_local] is in this ngon
|
|
if ( vfmi > vfm_count1 )
|
|
vfm[vfm_count1] = vfi_local;
|
|
vfm_count1++;
|
|
}
|
|
vfm[-1] = vfm_count1;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// vertex is not shared
|
|
m_ngon_vmap[m_vi] = m_ngon_vmark;
|
|
return true;
|
|
}
|
|
|
|
bool ON_MeshSeparateNgonInfo::FindSharedNgonVertices(
|
|
unsigned int ngon_index
|
|
)
|
|
{
|
|
m_ni = ngon_index;
|
|
m_fi = ON_UNSET_UINT_INDEX;
|
|
m_vi = ON_UNSET_UINT_INDEX;
|
|
m_ngon_vmark = 0;
|
|
m_ngon = m_mesh ? m_mesh->Ngon(m_ni) : 0;
|
|
|
|
if ( 0 == m_ngon )
|
|
return true;
|
|
if ( m_ngon->m_Vcount <= 0 && m_ngon->m_Fcount <= 0 )
|
|
return true;
|
|
if ( m_ngon->m_Vcount > 0 && 0 == m_ngon->m_vi )
|
|
return true;
|
|
if ( m_ngon->m_Fcount > 0 && 0 == m_ngon->m_fi )
|
|
return true;
|
|
|
|
unsigned int prev_ngon_mark = m_ngon_vmark;
|
|
m_ngon_vmark = m_ni+1 < m_vertex_count0 ? (m_ni+1) : 1;
|
|
if ( 0 == m_ngon_vmap || prev_ngon_mark >= m_ngon_vmark )
|
|
{
|
|
if ( 0 == m_ngon_vmap )
|
|
{
|
|
m_ngon_vmap_buffer.Reserve(m_vertex_count0);
|
|
m_ngon_vmap_buffer.SetCount(m_vertex_count0);
|
|
m_ngon_vmap = m_ngon_vmap_buffer.Array();
|
|
}
|
|
m_ngon_vmap_buffer.Zero();
|
|
}
|
|
|
|
m_vertex_count2 = m_vertex_count1;
|
|
|
|
for ( unsigned int k = 0; k < m_ngon->m_Fcount; k++ )
|
|
{
|
|
m_fi = m_ngon->m_fi[k];
|
|
if ( m_fi >= m_face_count )
|
|
continue; // bogus face index
|
|
|
|
const unsigned int* fvi = (const unsigned int*)m_mesh->m_F[m_fi].vi;
|
|
for ( unsigned int j = 0; j < 4; j++ )
|
|
{
|
|
if ( false == TestNgonVertex(fvi+j) )
|
|
return false;
|
|
}
|
|
}
|
|
|
|
m_fi = ON_UNSET_UINT_INDEX;
|
|
|
|
for ( unsigned k = 0; k < m_ngon->m_Vcount; k++ )
|
|
{
|
|
if ( false == TestNgonVertex(m_ngon->m_vi + k) )
|
|
return false;
|
|
}
|
|
|
|
m_vertex_count1 = m_vertex_count2;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ON_Mesh::SeparateNgons(
|
|
unsigned int** vertex_face_map,
|
|
unsigned int ngon_index0,
|
|
unsigned int ngon_index1
|
|
)
|
|
{
|
|
if ( ngon_index1 > NgonUnsignedCount() )
|
|
ngon_index1 = NgonUnsignedCount();
|
|
if ( ngon_index1 <= ngon_index0 )
|
|
return false;
|
|
|
|
ON_MeshSeparateNgonInfo X(this,vertex_face_map);
|
|
if ( false == X.IsValid() )
|
|
return false;
|
|
|
|
// Count number of new vertices that are required
|
|
// to separate the ngons and save a list of what
|
|
// needs to be updated in "X". This loop modifies
|
|
// vertex_face_map[] by removing references
|
|
// to faces of vertices that will be duplicated.
|
|
for ( unsigned int ni = ngon_index0; ni < ngon_index1; ni++ )
|
|
{
|
|
if( false == X.FindSharedNgonVertices(ni) )
|
|
return 0;
|
|
}
|
|
|
|
// Add new vertices to ngons.
|
|
if ( X.m_vertex_count1 > X.m_vertex_count0 )
|
|
{
|
|
// Duplicate the shared vertices and update the
|
|
// ngon and face vertex index lists to reference
|
|
// the duplicated vertices.
|
|
X.DuplicateSharedNgonVertices();
|
|
}
|
|
|
|
return (VertexUnsignedCount() > X.m_vertex_count0);
|
|
}
|
|
|
|
|
|
bool ON_Mesh::SetNgonVertexNormals(
|
|
unsigned int ngon_index0,
|
|
unsigned int ngon_index1
|
|
)
|
|
{
|
|
if ( false == HasVertexNormals() )
|
|
return false;
|
|
|
|
if ( ngon_index1 > NgonUnsignedCount() )
|
|
ngon_index1 = NgonUnsignedCount();
|
|
if ( ngon_index1 <= ngon_index0 )
|
|
return false;
|
|
|
|
ON_3dPointListRef vertex_list(this);
|
|
ON_MeshFaceList face_list(this);
|
|
|
|
unsigned int ni, nvi, vi, nfi, fi;
|
|
const ON_MeshNgon* ngon;
|
|
|
|
const unsigned int vertex_count0 = vertex_list.PointCount();
|
|
const unsigned int face_count0 = face_list.FaceCount();
|
|
|
|
ON_3dVector dN;
|
|
ON_3fVector fN;
|
|
bool rc = false;
|
|
|
|
for ( ni = ngon_index0; ni < ngon_index1; ni++ )
|
|
{
|
|
ngon = m_Ngon[ni];
|
|
if ( 0 == ngon )
|
|
continue;
|
|
|
|
// validate ngon
|
|
if ( ngon->m_Vcount > 0 && 0 == ngon->m_vi )
|
|
continue;
|
|
if ( ngon->m_Fcount <= 0 || 0 == ngon->m_fi )
|
|
continue;
|
|
|
|
// Get ngon plane equation and normal.
|
|
fi = ngon->m_fi[0];
|
|
if ( fi >= face_count0 )
|
|
continue;
|
|
if ( !m_F[fi].ComputeFaceNormal(vertex_list,dN) )
|
|
continue;
|
|
|
|
fN = dN;
|
|
|
|
// update boundary vertex normals
|
|
for ( nvi = 0; nvi < ngon->m_Vcount; nvi++ )
|
|
{
|
|
vi = ngon->m_vi[nvi];
|
|
if ( vi >= vertex_count0 )
|
|
continue;
|
|
m_N[vi] = fN;
|
|
rc = true;
|
|
}
|
|
|
|
// update face vertex normals
|
|
for (nfi = 0; nfi < ngon->m_Fcount; nfi++ )
|
|
{
|
|
fi = ngon->m_fi[nfi];
|
|
if ( fi >= face_count0 )
|
|
continue;
|
|
unsigned int* fvi = (unsigned int*)m_F[fi].vi;
|
|
for ( unsigned int j = 0; j < 4; j++ )
|
|
{
|
|
vi = fvi[j];
|
|
if ( vi >= vertex_count0 )
|
|
continue;
|
|
rc = true;
|
|
m_N[vi] = fN;
|
|
}
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Mesh::RemoveNgonInteriorVertices(
|
|
const unsigned int *const* vertex_face_map,
|
|
unsigned int ngon_index0,
|
|
unsigned int ngon_index1
|
|
)
|
|
{
|
|
if ( ngon_index1 > NgonUnsignedCount() )
|
|
ngon_index1 = NgonUnsignedCount();
|
|
if ( ngon_index1 <= ngon_index0 )
|
|
return false;
|
|
|
|
ON_MeshVertexFaceMap m_local_vfmap;
|
|
|
|
for ( unsigned int ni = ngon_index0; ni < ngon_index1; ni++ )
|
|
{
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
unsigned int ON_MeshTopology::NgonIndexFromTopologyVertexIndex(
|
|
unsigned int topvi,
|
|
const unsigned int* mesh_facedex_to_ngondex_map
|
|
) const
|
|
{
|
|
if ( topvi >= m_topv.UnsignedCount() )
|
|
return ON_UNSET_UINT_INDEX;
|
|
const ON_MeshTopologyVertex& v = m_topv[topvi];
|
|
if ( v.m_tope_count <= 2 || 0 == v.m_topei )
|
|
return ON_UNSET_UINT_INDEX;
|
|
|
|
const ON_Mesh* mesh = m_mesh;
|
|
if ( 0 == mesh || false == mesh->HasNgons() )
|
|
return ON_UNSET_UINT_INDEX;
|
|
if ( 0 == mesh_facedex_to_ngondex_map )
|
|
{
|
|
mesh_facedex_to_ngondex_map = mesh->NgonMap();
|
|
if ( 0 == mesh_facedex_to_ngondex_map )
|
|
return ON_UNSET_UINT_INDEX;
|
|
}
|
|
|
|
const unsigned int ngon_index = NgonIndexFromTopologyEdgeIndex((unsigned int)(v.m_topei[0]),mesh_facedex_to_ngondex_map);
|
|
if ( ON_UNSET_UINT_INDEX == ngon_index )
|
|
return ON_UNSET_UINT_INDEX;
|
|
for ( int tvtei = 1; tvtei < v.m_tope_count; tvtei++ )
|
|
{
|
|
if ( ngon_index != NgonIndexFromTopologyEdgeIndex((unsigned int)(v.m_topei[tvtei]),mesh_facedex_to_ngondex_map) )
|
|
return ON_UNSET_UINT_INDEX;
|
|
}
|
|
return ngon_index;
|
|
}
|
|
|
|
unsigned int ON_MeshTopology::NgonIndexFromTopologyEdgeIndex(
|
|
unsigned int topei,
|
|
const unsigned int* mesh_facedex_to_ngondex_map
|
|
) const
|
|
{
|
|
if ( topei >= m_tope.UnsignedCount() )
|
|
return ON_UNSET_UINT_INDEX;
|
|
const ON_MeshTopologyEdge& e = m_tope[topei];
|
|
if ( e.m_topf_count <= 0 || 0 == e.m_topfi )
|
|
return ON_UNSET_UINT_INDEX;
|
|
const ON_Mesh* mesh = m_mesh;
|
|
if ( 0 == mesh || false == mesh->HasNgons() )
|
|
return ON_UNSET_UINT_INDEX;
|
|
if ( 0 == mesh_facedex_to_ngondex_map )
|
|
{
|
|
mesh_facedex_to_ngondex_map = mesh->NgonMap();
|
|
if ( 0 == mesh_facedex_to_ngondex_map )
|
|
return ON_UNSET_UINT_INDEX;
|
|
}
|
|
const unsigned int mesh_face_count = mesh->FaceUnsignedCount();
|
|
|
|
const ON_MeshNgon* ngon = 0;
|
|
unsigned int ngon_index = ON_UNSET_UINT_INDEX;
|
|
unsigned int rc = ON_UNSET_UINT_INDEX;
|
|
for ( int tefi = 0; tefi < e.m_topf_count; tefi++ )
|
|
{
|
|
unsigned int fi = (unsigned int)e.m_topfi[tefi];
|
|
if ( fi >= mesh_face_count )
|
|
{
|
|
// bogus face index
|
|
return ON_UNSET_UINT_INDEX;
|
|
}
|
|
|
|
if ( 0 != ngon && ngon_index == mesh_facedex_to_ngondex_map[fi] )
|
|
{
|
|
// this edge has 2 or more faces and all the faces
|
|
// that have been checked belong to the same ngon.
|
|
rc = ngon_index;
|
|
continue;
|
|
}
|
|
|
|
if ( 0 != ngon || ON_UNSET_UINT_INDEX == mesh_facedex_to_ngondex_map[fi] )
|
|
{
|
|
// this edge is not interior to the same ngon
|
|
return ON_UNSET_UINT_INDEX;
|
|
}
|
|
|
|
ngon_index = mesh_facedex_to_ngondex_map[fi];
|
|
ngon = mesh->Ngon(ngon_index);
|
|
if ( 0 == ngon )
|
|
{
|
|
// bogus ngon information - let user pick this edge
|
|
return ON_UNSET_UINT_INDEX;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
ON_Plane ON_Plane::FromPointList(
|
|
size_t point_list_count,
|
|
const ON_3dPoint* point_list
|
|
)
|
|
{
|
|
ON_3dPointListRef vertex_list;
|
|
vertex_list.SetFromDoubleArray(point_list_count,3,(const double*)point_list);
|
|
return ON_Plane::FromPointList(vertex_list);
|
|
}
|
|
|
|
ON_Plane ON_Plane::FromPointList(
|
|
const ON_SimpleArray< ON_3dPoint >& point_list
|
|
)
|
|
{
|
|
return ON_Plane::FromPointList(point_list.UnsignedCount(),point_list.Array());
|
|
}
|
|
|
|
|
|
ON_Plane ON_Plane::FromPointList(
|
|
size_t point_list_count,
|
|
const ON_3fPoint* point_list
|
|
)
|
|
{
|
|
ON_3dPointListRef vertex_list;
|
|
vertex_list.SetFromFloatArray(point_list_count,3,(const float*)point_list);
|
|
return ON_Plane::FromPointList(vertex_list);
|
|
}
|
|
|
|
ON_Plane ON_Plane::FromPointList(
|
|
const ON_SimpleArray< ON_3fPoint >& point_list
|
|
)
|
|
{
|
|
return ON_Plane::FromPointList(point_list.UnsignedCount(),point_list.Array());
|
|
}
|
|
|
|
|
|
ON_Plane ON_Plane::FromPointList(
|
|
const class ON_3dPointListRef& point_list
|
|
)
|
|
{
|
|
return ON_Plane::FromPointList(point_list.PointCount(),0,point_list);
|
|
}
|
|
|
|
ON_Plane ON_Plane::FromPointList(
|
|
size_t point_index_count,
|
|
const unsigned int* point_index_list,
|
|
const class ON_3dPointListRef& point_list
|
|
)
|
|
{
|
|
const size_t point_index_stride = (0 != point_index_list) ? 1 : 0;
|
|
|
|
return ON_Plane::FromPointList(
|
|
point_index_count,
|
|
point_index_stride,
|
|
point_index_list,
|
|
point_list
|
|
);
|
|
}
|
|
|
|
ON_Plane ON_Plane::FromPointList(
|
|
size_t point_index_count,
|
|
size_t point_index_stride,
|
|
const unsigned int* point_index_list,
|
|
const class ON_3dPointListRef& point_list
|
|
)
|
|
{
|
|
unsigned int i;
|
|
ON_3dPoint Pi, Pi0, Pi1;
|
|
|
|
//////////////////////////////////////////////////////////
|
|
//
|
|
// Do not tolerate garbage.
|
|
//
|
|
if ( point_index_count < 3 || point_index_count >= (size_t)ON_UNSET_UINT_INDEX )
|
|
return ON_Plane::UnsetPlane;
|
|
|
|
if ( 0 != point_index_list )
|
|
{
|
|
if ( point_index_stride < 1 || point_index_stride >= (size_t)ON_UNSET_UINT_INDEX )
|
|
return ON_Plane::UnsetPlane;
|
|
}
|
|
|
|
const unsigned int point_count = (unsigned int)point_index_count;
|
|
const unsigned int point_stride = (0 != point_index_list ) ? ((unsigned int)point_index_stride) : 1;
|
|
const unsigned int vertex_count = point_list.PointCount();
|
|
|
|
if ( vertex_count < point_count )
|
|
return ON_Plane::UnsetPlane;
|
|
|
|
if ( point_index_list )
|
|
{
|
|
for ( i = 0; i < point_count; i += point_stride )
|
|
{
|
|
if ( point_index_list[i] >= vertex_count )
|
|
return ON_Plane::UnsetPlane;
|
|
}
|
|
}
|
|
|
|
i = point_stride*(point_count-1);
|
|
Pi = point_list[point_index_list ? point_index_list[i] : i];
|
|
for ( i = 0; i < point_count; i += point_stride )
|
|
{
|
|
Pi0 = Pi;
|
|
Pi = point_list[point_index_list ? point_index_list[i] : i];
|
|
if ( false == Pi.IsValid() )
|
|
return ON_Plane::UnsetPlane;
|
|
if ( false == (Pi0 != Pi) )
|
|
return ON_Plane::UnsetPlane;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
//
|
|
// Find a plane to project the 3d points to.
|
|
//
|
|
ON_3dVector N(ON_3dVector::ZeroVector);
|
|
ON_3dVector X(ON_3dVector::UnsetVector);
|
|
if ( point_index_count <= 4 )
|
|
{
|
|
// use "standard" face normal for quads and triangles.
|
|
const unsigned int index_0123[4] = {0,1,2,3};
|
|
if ( 0 == point_index_list )
|
|
point_index_list = index_0123;
|
|
Pi0 = point_list[point_index_list[point_stride*(point_count-1)]];
|
|
Pi1 = point_list[point_index_list[point_stride]];
|
|
X = Pi1 - Pi0;
|
|
Pi0 = point_list[point_index_list[0]];
|
|
Pi1 = point_list[point_index_list[2*point_stride]];
|
|
N = ON_CrossProduct(
|
|
X,
|
|
Pi1-Pi0
|
|
);
|
|
if ( X.Length() < Pi1.DistanceTo(Pi0) )
|
|
X = Pi1-Pi0;
|
|
}
|
|
|
|
if (N == ON_3dVector::ZeroVector)
|
|
{
|
|
// find far apart points
|
|
unsigned int i0 = 0;
|
|
unsigned int i1 = point_stride;
|
|
ON_Line L(point_list[point_index_list ? point_index_list[i0] : i0],point_list[point_index_list ? point_index_list[i1] : i1]);
|
|
double d = L.Length();
|
|
for ( i = i1+point_stride; i < point_count*point_stride; i += point_stride )
|
|
{
|
|
Pi = point_list[point_index_list ? point_index_list[i] : i];
|
|
double d0 = L.from.DistanceTo(Pi);
|
|
double d1 = L.to.DistanceTo(Pi);
|
|
if ( d0 > d && d0 >= d1 )
|
|
{
|
|
d = d0;
|
|
i1 = i;
|
|
L.to = Pi;
|
|
}
|
|
else if ( d1 > d )
|
|
{
|
|
d = d1;
|
|
i0 = i;
|
|
L.from = Pi;
|
|
}
|
|
}
|
|
if ( i0 == i1 || !(d > 0.0) )
|
|
return ON_Plane::UnsetPlane;
|
|
X = L.Direction();
|
|
|
|
d = 0.0;
|
|
unsigned int i2 = ON_UNSET_UINT_INDEX;
|
|
ON_3dPoint P(ON_3dPoint::UnsetPoint);
|
|
for ( i = 0; i < point_count*point_stride; i += point_stride )
|
|
{
|
|
if ( i == i0 || i == i1 )
|
|
continue;
|
|
Pi = point_list[point_index_list ? point_index_list[i] : i];
|
|
double t1 = ON_UNSET_VALUE;
|
|
if ( !L.ClosestPointTo(Pi,&t1) || !(t1 != ON_UNSET_VALUE) )
|
|
continue;
|
|
ON_3dPoint Q = L.PointAt(t1);
|
|
double d1 = Q.DistanceTo(Pi);
|
|
if ( d1 > d )
|
|
{
|
|
d = d1;
|
|
P = Q;
|
|
i2 = i;
|
|
}
|
|
}
|
|
if ( ON_UNSET_UINT_INDEX == i2 || i0 == i2 || i1 == i2 || !(d > 0.0) || !P.IsValid() )
|
|
return ON_Plane::UnsetPlane;
|
|
Pi = point_list[point_index_list ? point_index_list[i2] : i2];
|
|
N = ON_CrossProduct(X,Pi-P);
|
|
}
|
|
|
|
ON_Plane plane;
|
|
|
|
if ( 0.0 != N.x && 0.0 == N.y && 0.0 == N.z )
|
|
i = 0;
|
|
else if ( 0.0 == N.x && 0.0 != N.y && 0.0 == N.z )
|
|
i = 1;
|
|
else if ( 0.0 == N.x && 0.0 == N.y && 0.0 != N.z )
|
|
i = 2;
|
|
else
|
|
i = 3;
|
|
|
|
if ( i < 3 )
|
|
{
|
|
ON_3dVector a[3] = {ON_3dVector::XAxis,ON_3dVector::YAxis,ON_3dVector::ZAxis};
|
|
plane.zaxis = a[i];
|
|
plane.xaxis = a[(i+1)%3];
|
|
plane.yaxis = a[(i+2)%3];
|
|
}
|
|
else
|
|
{
|
|
plane.zaxis = N;
|
|
plane.xaxis = X;
|
|
if ( !plane.zaxis.Unitize() )
|
|
return ON_Plane::UnsetPlane;
|
|
if ( !plane.xaxis.Unitize() )
|
|
return ON_Plane::UnsetPlane;
|
|
plane.yaxis = ON_CrossProduct(plane.zaxis,plane.xaxis);
|
|
}
|
|
plane.origin = point_list[point_index_list ? point_index_list[0] : 0];
|
|
plane.UpdateEquation();
|
|
if ( !plane.IsValid() )
|
|
return ON_Plane::UnsetPlane;
|
|
|
|
return plane;
|
|
}
|
|
|
|
|
|
|
|
|
|
const ON_MeshNgonIterator ON_MeshNgonIterator::EmptyMeshNgonIterator;
|
|
|
|
ON_MeshNgonIterator::ON_MeshNgonIterator(
|
|
const class ON_Mesh* mesh
|
|
)
|
|
{
|
|
if ( 0 != mesh )
|
|
SetMesh(mesh,mesh->NgonMap());
|
|
}
|
|
|
|
ON_MeshNgonIterator::ON_MeshNgonIterator(
|
|
const ON_MeshNgonIterator& src
|
|
)
|
|
: m_mesh(src.m_mesh)
|
|
, m_facedex_to_ngondex_map(src.m_facedex_to_ngondex_map)
|
|
, m_current_ngon_ci(src.m_current_ngon_ci)
|
|
, m_mesh_face_count(src.m_mesh_face_count)
|
|
, m_mesh_ngon_count(src.m_mesh_ngon_count)
|
|
, m_iterator_index(src.m_iterator_index)
|
|
{
|
|
if ( ON_COMPONENT_INDEX::mesh_face == m_current_ngon_ci.m_type
|
|
|| ON_COMPONENT_INDEX::mesh_ngon == m_current_ngon_ci.m_type
|
|
)
|
|
{
|
|
if ( 0 != m_mesh )
|
|
m_current_ngon = (ON__UINT_PTR)(m_mesh->NgonFromComponentIndex(m_ngon_buffer,m_current_ngon_ci));
|
|
}
|
|
}
|
|
|
|
ON_MeshNgonIterator& ON_MeshNgonIterator::operator=(
|
|
const ON_MeshNgonIterator& src
|
|
)
|
|
{
|
|
if ( this != &src )
|
|
{
|
|
m_mesh = src.m_mesh;
|
|
m_facedex_to_ngondex_map = src.m_facedex_to_ngondex_map;
|
|
m_current_ngon_ci = src.m_current_ngon_ci;
|
|
m_current_ngon = 0;
|
|
m_mesh_face_count = src.m_mesh_face_count;
|
|
m_mesh_ngon_count = src.m_mesh_ngon_count;
|
|
m_iterator_index = src.m_iterator_index;
|
|
if ( ON_COMPONENT_INDEX::mesh_face == m_current_ngon_ci.m_type
|
|
|| ON_COMPONENT_INDEX::mesh_ngon == m_current_ngon_ci.m_type
|
|
)
|
|
{
|
|
if ( 0 != m_mesh )
|
|
m_current_ngon = (ON__UINT_PTR)(m_mesh->NgonFromComponentIndex(m_ngon_buffer,m_current_ngon_ci));
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
void ON_MeshNgonIterator::SetMesh(
|
|
const class ON_Mesh* mesh,
|
|
const unsigned int* meshfdex_to_meshngondex_map
|
|
)
|
|
{
|
|
*this = ON_MeshNgonIterator::EmptyMeshNgonIterator;
|
|
m_mesh = mesh;
|
|
m_facedex_to_ngondex_map = (0 != m_mesh && 0 == meshfdex_to_meshngondex_map)
|
|
? m_mesh->NgonMap()
|
|
: meshfdex_to_meshngondex_map;
|
|
if ( 0 != m_mesh )
|
|
{
|
|
m_mesh_face_count = m_mesh->FaceUnsignedCount();
|
|
m_mesh_ngon_count = m_mesh->NgonCount();
|
|
m_current_ngon_ci.Set(ON_COMPONENT_INDEX::invalid_type,1);
|
|
}
|
|
}
|
|
|
|
const ON_Mesh* ON_MeshNgonIterator::Mesh() const
|
|
{
|
|
return m_mesh;
|
|
}
|
|
|
|
const class ON_MeshNgon* ON_MeshNgonIterator::FirstNgon()
|
|
{
|
|
m_current_ngon_ci.Set(ON_COMPONENT_INDEX::invalid_type,1);
|
|
m_current_ngon = 0;
|
|
m_iterator_index = 0;
|
|
return NextNgon();
|
|
}
|
|
|
|
const class ON_MeshNgon* ON_MeshNgonIterator::NextNgon()
|
|
{
|
|
unsigned int fi, ni;
|
|
|
|
const class ON_MeshNgon* ngon = 0;
|
|
switch( m_current_ngon_ci.m_type )
|
|
{
|
|
case ON_COMPONENT_INDEX::invalid_type:
|
|
m_current_ngon = 0;
|
|
if ( 1 != m_current_ngon_ci.m_index )
|
|
break;
|
|
m_current_ngon_ci.Set(ON_COMPONENT_INDEX::mesh_ngon,-1);
|
|
m_iterator_index = ON_UNSET_UINT_INDEX;
|
|
// no break here - iterator will look through ngon list
|
|
|
|
case ON_COMPONENT_INDEX::mesh_ngon:
|
|
for ( ni = ( -1 == m_current_ngon_ci.m_index ) ? 0 : m_current_ngon_ci.UnsignedIndex()+1;
|
|
ni < m_mesh_ngon_count;
|
|
ni++
|
|
)
|
|
{
|
|
ngon = m_mesh->Ngon(ni);
|
|
if ( 0 == ngon )
|
|
continue;
|
|
if ( ON_UNSET_UINT_INDEX == m_iterator_index )
|
|
m_iterator_index = 0;
|
|
else
|
|
m_iterator_index++;
|
|
m_current_ngon_ci.m_index = (int)ni;
|
|
m_current_ngon = (ON__UINT_PTR)ngon;
|
|
return ngon;
|
|
}
|
|
m_current_ngon_ci.Set(ON_COMPONENT_INDEX::mesh_face,-1);
|
|
// no break here - iterator will look for mesh faces that are not part of ngons
|
|
|
|
case ON_COMPONENT_INDEX::mesh_face:
|
|
for ( fi = ( -1 == m_current_ngon_ci.m_index ) ? 0 : m_current_ngon_ci.UnsignedIndex()+1;
|
|
fi < m_mesh_face_count;
|
|
fi++
|
|
)
|
|
{
|
|
ni = m_facedex_to_ngondex_map ? m_facedex_to_ngondex_map[fi] : ON_UNSET_UINT_INDEX;
|
|
if ( ni < m_mesh_ngon_count )
|
|
continue;
|
|
ngon = ON_MeshNgon::NgonFromMeshFace(m_ngon_buffer,fi,(const unsigned int*)m_mesh->m_F[fi].vi);
|
|
if ( 0 == ngon )
|
|
continue;
|
|
if ( ON_UNSET_UINT_INDEX == m_iterator_index )
|
|
m_iterator_index = 0;
|
|
else
|
|
m_iterator_index++;
|
|
m_current_ngon_ci.m_index = (int)fi;
|
|
m_current_ngon = (ON__UINT_PTR)ngon;
|
|
return ngon;
|
|
}
|
|
// no break here - iterator is finished
|
|
|
|
default:
|
|
m_current_ngon_ci.Set(ON_COMPONENT_INDEX::invalid_type,0);
|
|
m_current_ngon = 0;
|
|
}
|
|
|
|
return ngon;
|
|
}
|
|
|
|
const class ON_MeshNgon* ON_MeshNgonIterator::CurrentNgon()
|
|
{
|
|
const class ON_MeshNgon* ngon = 0;
|
|
if ( 0 != m_current_ngon )
|
|
{
|
|
ngon = (0 != m_mesh)
|
|
? m_mesh->NgonFromComponentIndex(m_ngon_buffer,m_current_ngon_ci)
|
|
: 0;
|
|
if ( m_current_ngon != (ON__UINT_PTR)ngon )
|
|
{
|
|
ngon = 0;
|
|
m_current_ngon = 0;
|
|
}
|
|
}
|
|
return ngon;
|
|
}
|
|
|
|
ON_COMPONENT_INDEX ON_MeshNgonIterator::CurrentNgonComponentIndex() const
|
|
{
|
|
return m_current_ngon_ci;
|
|
}
|
|
|
|
bool ON_MeshNgonIterator::CurrentNgonIsMeshFace() const
|
|
{
|
|
return ( ON_COMPONENT_INDEX::mesh_face == m_current_ngon_ci.m_type
|
|
&& m_current_ngon == (ON__UINT_PTR)&m_ngon_buffer
|
|
);
|
|
}
|
|
|
|
bool ON_MeshNgonIterator::CurrentNgonIsMeshNgon() const
|
|
{
|
|
return ( 0 != m_current_ngon
|
|
&& 0 != m_mesh
|
|
&& ON_COMPONENT_INDEX::mesh_ngon == m_current_ngon_ci.m_type
|
|
&& m_current_ngon == (ON__UINT_PTR)m_mesh->Ngon(m_current_ngon_ci.m_index)
|
|
);
|
|
}
|
|
|
|
void ON_MeshNgonIterator::Reset()
|
|
{
|
|
const ON_Mesh* mesh = m_mesh;
|
|
const unsigned int* fdex_to_ndex_map = m_facedex_to_ngondex_map;
|
|
SetMesh(mesh,fdex_to_ndex_map);
|
|
}
|
|
|
|
|
|
unsigned int ON_MeshNgonIterator::Count() const
|
|
{
|
|
unsigned int count = 0;
|
|
|
|
if (nullptr != m_mesh)
|
|
{
|
|
count = m_mesh->m_F.UnsignedCount();
|
|
unsigned int explicit_ngon_count = m_mesh->NgonUnsignedCount();
|
|
if (explicit_ngon_count > 0)
|
|
{
|
|
// subtract the number of mesh faces that are in explicit ngons from count.
|
|
const ON_MeshNgon* const * ngons = m_mesh->Ngons();
|
|
if (nullptr != ngons)
|
|
{
|
|
for (unsigned int ni = 0; ni < explicit_ngon_count; ni++)
|
|
{
|
|
const ON_MeshNgon* ngon = ngons[ni];
|
|
if (nullptr == ngon)
|
|
explicit_ngon_count--;
|
|
else if (ngon->m_Fcount <= count)
|
|
count -= ngon->m_Fcount;
|
|
else
|
|
{
|
|
ON_ERROR("Invalid ngon information on mesh");
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add the number of explicit ngons to count
|
|
count += explicit_ngon_count;
|
|
}
|
|
}
|
|
|
|
// return the number of explicit ngons + the number of faces that are not in an explicit ngon.
|
|
return count;
|
|
}
|