Files
opennurbs/opennurbs_mesh_tools.cpp
2024-08-22 01:43:04 -07:00

1564 lines
41 KiB
C++

//
// Copyright (c) 1993-2022 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
#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
/////////////////////////////////////////////////////////////////////////////
// SwapMeshEdge()
//
bool ON_Mesh::SwapEdge_Helper( int topei, bool bTestOnly )
{
ON_Mesh& mesh = *this;
const ON_MeshTopology& top = mesh.Topology();
const int F_count = mesh.FaceCount();
const int V_count = mesh.VertexCount();
const int topv_count = top.m_topv.Count();
//const int tope_count = top.m_tope.Count();
if ( topei < 0 || topei >= top.m_tope.Count() )
{
return false;
}
if ( top.m_topf.Count() != F_count )
return false;
const ON_MeshTopologyEdge& tope = top.m_tope[topei];
if ( tope.m_topf_count != 2
|| tope.m_topvi[0] == tope.m_topvi[1]
|| tope.m_topvi[0] < 0
|| tope.m_topvi[1] < 0
|| tope.m_topvi[0] >= topv_count
|| tope.m_topvi[1] >= topv_count )
{
return false;
}
int fi0 = tope.m_topfi[0];
int fi1 = tope.m_topfi[1];
if ( fi0 < 0 || fi0 >= F_count || fi1 < 0 || fi1 >= F_count || fi0 == fi1 )
return false;
const ON_MeshFace& f0 = mesh.m_F[fi0];
const ON_MeshFace& f1 = mesh.m_F[fi1];
if ( !f0.IsValid(V_count) )
return false;
if ( !f1.IsValid(V_count) )
return false;
if ( !f0.IsTriangle() )
return false;
if ( !f1.IsTriangle() )
return false;
const ON_MeshTopologyFace& topf0 = top.m_topf[fi0];
const ON_MeshTopologyFace& topf1 = top.m_topf[fi1];
int fei0;
for ( fei0 = 0; fei0 < 3; fei0++ )
{
if ( topf0.m_topei[fei0] == topei )
break;
}
if ( fei0 >= 3 )
return false;
int f0_vi0 = f0.vi[(fei0+3)%4];
int f0_vi1 = f0.vi[fei0];
int f0_vi2 = f0.vi[(fei0+1)%3];
int fei1;
for ( fei1 = 0; fei1 < 3; fei1++ )
{
if ( topf1.m_topei[fei1] == topei )
break;
}
if ( fei1 >= 3 )
return false;
int f1_vi0 = f1.vi[(fei1+3)%4];
int f1_vi1 = f1.vi[fei1];
int f1_vi2 = f1.vi[(fei1+1)%3];
if ( topf0.m_reve[fei0] == topf1.m_reve[fei1] )
return false;
if ( f0_vi0 != f1_vi1 )
return false;
if ( f0_vi1 != f1_vi0 )
return false;
const ON_MeshTopologyVertex& topv0 = top.m_topv[tope.m_topvi[0]];
const ON_MeshTopologyVertex& topv1 = top.m_topv[tope.m_topvi[1]];
if ( topv0.m_v_count < 1 || topv1.m_v_count < 1 )
{
return false;
}
if ( topv0.m_vi[0] < 0 || topv0.m_vi[0] >= V_count )
{
return false;
}
if ( topv1.m_vi[0] < 0 || topv1.m_vi[0] >= V_count )
{
return false;
}
if ( bTestOnly )
return true;
ON_MeshFace newf0;
ON_MeshFace newf1;
newf0.vi[0] = f0_vi1;
newf0.vi[1] = f0_vi2;
newf0.vi[2] = f1_vi2;
newf0.vi[3] = newf0.vi[2];
newf1.vi[0] = f1_vi1;
newf1.vi[1] = f1_vi2;
newf1.vi[2] = f0_vi2;
newf1.vi[3] = newf1.vi[2];
mesh.m_F[fi0] = newf0;
mesh.m_F[fi1] = newf1;
mesh.DestroyTopology();
mesh.DestroyPartition();
return true;
}
bool ON_Mesh::IsSwappableEdge(int topei )
{
return const_cast<ON_Mesh*>(this)->SwapEdge_Helper( topei, true );
}
bool ON_Mesh::SwapEdge( int topei )
{
return SwapEdge_Helper( topei, false );
}
/////////////////////////////////////////////////////////////////////////////
// CollapseMeshEdge()
//
// DO NOT PUT THIS CLASS IN A HEADER FILE
class ON__MESHEDGE
{
public:
// ON_Mesh m_V[] indices
int vi0;
int vi1; // always > vi0
// ON_MeshTopology m_topvi[] indices
int topvi0;
int topvi1;
// ON_Mesh m_V[] index of new vertex
int newvi;
// location of vertex that will replace the edge
ON_3dPoint newV;
ON_3fVector newN;
ON_2fPoint newT;
};
// DO NOT PUT THIS CLASS IN A HEADER FILE
class ON__NEWVI
{
public:
// ON_Mesh m_V[] indices
int oldvi;
int newvi;
};
typedef int (*QSORTCMPFUNC)(const void*,const void*);
static int CompareMESHEDGE( const ON__MESHEDGE* a, const ON__MESHEDGE* b )
{
// sort based on "vi0" and vi1" values (vi0 is always < vi1)
int i = (a->vi0 - b->vi0);
if ( 0 == i )
{
i = a->vi1 - b->vi1;
}
return i;
}
static int CompareNEWVI( const ON__NEWVI* a, const ON__NEWVI* b )
{
// sort based on "oldvi" value
return a->oldvi - b->oldvi;
}
static int CompareInt( const void* a, const void* b )
{
return ( *((int*)a) - *((int*)b) );
}
static void RemoveFaceOrVertexFromNgon(ON_Mesh& mesh, const unsigned int faceidx, const int* vertidx = nullptr)
{
unsigned int NgonIdx = mesh.NgonIndexFromFaceIndex(faceidx);
const ON_MeshNgon* pOldNgon = nullptr;
pOldNgon = mesh.Ngon(NgonIdx);
if (nullptr != pOldNgon)
{
// Make a copy of the ngon we need to update.
ON_MeshNgon NewNgon;
NewNgon.m_Vcount = pOldNgon->m_Vcount;
NewNgon.m_Fcount = pOldNgon->m_Fcount;
NewNgon.m_vi = (unsigned int*)onmalloc((NewNgon.m_Vcount+ NewNgon.m_Fcount)*sizeof(unsigned int));
memcpy(NewNgon.m_vi, pOldNgon->m_vi, NewNgon.m_Vcount*sizeof(unsigned int));
NewNgon.m_fi = &NewNgon.m_vi[NewNgon.m_Vcount];
memcpy(NewNgon.m_fi, pOldNgon->m_fi, NewNgon.m_Fcount*sizeof(unsigned int));
if (nullptr != NewNgon.m_vi && nullptr != NewNgon.m_fi)
{
unsigned int ct = 0;
if (nullptr == vertidx)
{
while (NewNgon.m_Fcount > ct)
{
// find the face index in the ngon's list of faces
if (faceidx == NewNgon.m_fi[ct])
break;
ct++;
}
if (NewNgon.m_Fcount > ct)
{
// if ct isn't bogus and is not at the end already, shift the indexes
if (NewNgon.m_Fcount - 1 > ct)
memcpy(&NewNgon.m_fi[ct], &NewNgon.m_fi[ct + 1], (NewNgon.m_Fcount - (ct + 1))*sizeof(NewNgon.m_fi[0]));
NewNgon.m_Fcount--;
}
}
else
{
unsigned int idx = ON_UNSET_UINT_INDEX;
while (NewNgon.m_Vcount > ct)
{
idx = *vertidx;
// find the vertex index in the ngon's list of vertexes
if (idx == NewNgon.m_vi[ct])
break;
ct++;
}
if (NewNgon.m_Vcount > ct)
{
// if vi_idx isn't bogus and is not at the end already, shift the indexes
if (NewNgon.m_Vcount - 1 > ct)
memcpy(&NewNgon.m_vi[ct], &NewNgon.m_vi[ct + 1], (NewNgon.m_Vcount - (ct + 1))*sizeof(NewNgon.m_vi[0]));
NewNgon.m_Vcount--;
}
}
mesh.ModifyNgon(NgonIdx, &NewNgon);
}
if (nullptr != NewNgon.m_vi)
onfree(NewNgon.m_vi);
}
}
bool ON_Mesh::CollapseEdge( int topei )
{
ON_Mesh& mesh = *this;
ON__MESHEDGE me;
memset(&me,0,sizeof(me));
const ON_MeshTopology& top = mesh.Topology();
const int F_count = mesh.FaceCount();
const int V_count = mesh.VertexCount();
const int topv_count = top.m_topv.Count();
//const int tope_count = top.m_tope.Count();
if ( topei < 0 || topei >= top.m_tope.Count() )
{
return false;
}
const ON_MeshTopologyEdge& tope = top.m_tope[topei];
if ( tope.m_topf_count < 1
|| tope.m_topvi[0] == tope.m_topvi[1]
|| tope.m_topvi[0] < 0
|| tope.m_topvi[1] < 0
|| tope.m_topvi[0] >= topv_count
|| tope.m_topvi[1] >= topv_count )
{
return false;
}
const ON_MeshTopologyVertex& topv0 = top.m_topv[tope.m_topvi[0]];
const ON_MeshTopologyVertex& topv1 = top.m_topv[tope.m_topvi[1]];
if ( topv0.m_v_count < 1 || topv1.m_v_count < 1 )
{
return false;
}
if ( topv0.m_vi[0] < 0 || topv0.m_vi[0] >= V_count )
{
return false;
}
if ( topv1.m_vi[0] < 0 || topv1.m_vi[0] >= V_count )
{
return false;
}
// create a ON__MESHEDGE for each face (usually one or two) that uses the edge
ON__MESHEDGE* me_list = (ON__MESHEDGE*)alloca(tope.m_topf_count*sizeof(me_list[0]));
int me_list_count = 0;
int efi;
for ( efi = 0; efi < tope.m_topf_count; efi++ )
{
int fi = tope.m_topfi[efi];
if ( fi < 0 || fi >= F_count )
continue;
const ON_MeshFace& f = mesh.m_F[fi];
if ( !f.IsValid(V_count) )
continue;
me.vi1 = f.vi[3];
me.topvi1 = top.m_topv_map[me.vi1];
int fvi;
for ( fvi = 0; fvi < 4; fvi++ )
{
me.vi0 = me.vi1;
me.topvi0 = me.topvi1;
me.vi1 = f.vi[fvi];
me.topvi1 = top.m_topv_map[me.vi1];
if ( me.vi0 != me.vi1 )
{
if ( (me.topvi0 == tope.m_topvi[0] && me.topvi1 == tope.m_topvi[1])
|| (me.topvi0 == tope.m_topvi[1] && me.topvi1 == tope.m_topvi[0]) )
{
if ( me.vi0 > me.vi1 )
{
int i = me.vi0; me.vi0 = me.vi1; me.vi1 = i;
i = me.topvi0; me.topvi0 = me.topvi1; me.topvi1 = i;
}
me_list[me_list_count++] = me;
break;
}
}
}
}
if (me_list_count<1)
{
return false;
}
// Sort me_list[] so edges using same vertices are adjacent
// to each other in the list. This is needed so that non-manifold
// crease edges will be properly collapsed.
ON_qsort(me_list,me_list_count,sizeof(me_list[0]),(QSORTCMPFUNC)CompareMESHEDGE);
// create new vertex or vertices that edge will be
// collapsed to.
mesh.m_C.Destroy();
mesh.m_K.Destroy();
int mei;
bool bHasVertexNormals = mesh.HasVertexNormals();
bool bHasTextureCoordinates = mesh.HasTextureCoordinates();
bool bHasFaceNormals = mesh.HasFaceNormals();
if ( topv0.m_v_count == 1 || topv1.m_v_count == 1 )
{
// a single new vertex
ON_Line Vline(ON_3dPoint::Origin,ON_3dPoint::Origin);
ON_Line Tline(ON_3dPoint::Origin,ON_3dPoint::Origin);
ON_3dVector N0(0,0,0);
ON_3dVector N1(0,0,0);
ON_3dPoint P;
int vi, tvi, cnt;
int newvi = topv0.m_vi[0];
cnt = 0;
for ( tvi = 0; tvi < topv0.m_v_count; tvi++ )
{
vi = topv0.m_vi[tvi];
if ( vi < 0 || vi > V_count )
continue;
if ( vi < newvi )
newvi = vi;
cnt++;
P = mesh.Vertex(vi);
Vline.from += P;
if ( bHasVertexNormals )
{
N0 += ON_3dVector(mesh.m_N[vi]);
}
if ( bHasTextureCoordinates )
{
P = mesh.m_T[vi];
Tline.from += P;
}
}
if (cnt > 1)
{
double s = 1.0/((double)cnt);
Vline.from.x *= s;
Vline.from.y *= s;
Vline.from.z *= s;
Tline.from.x *= s;
Tline.from.y *= s;
Tline.from.z *= s;
N0 = s*N0;
}
cnt = 0;
for (tvi = 0; tvi < topv1.m_v_count; tvi++)
{
vi = topv1.m_vi[tvi];
if (vi < 0 || vi > V_count)
continue;
if (vi < newvi)
newvi = vi;
cnt++;
P = mesh.Vertex(vi);
Vline.to += P;
if ( bHasVertexNormals )
{
N1 += ON_3dVector(mesh.m_N[vi]);
}
if ( bHasTextureCoordinates )
{
P = mesh.m_T[vi];
Tline.to += P;
}
}
if (cnt > 1)
{
double s = 1.0/((double)cnt);
Vline.to.x *= s;
Vline.to.y *= s;
Vline.to.z *= s;
Tline.to.x *= s;
Tline.to.y *= s;
Tline.to.z *= s;
N1 = s*N1;
}
ON_3fPoint newV(Vline.PointAt(0.5));
ON_3fVector newN(ON_3fVector::ZeroVector);
ON_2fPoint newT(ON_2fPoint::Origin);
if ( bHasVertexNormals )
{
N0.Unitize();
N1.Unitize();
ON_3dVector N = N0+N1;
if ( !N.Unitize() )
{
N = (topv0.m_v_count == 1) ? mesh.m_N[topv0.m_vi[0]] :mesh.m_N[topv1.m_vi[0]];
}
newN = N;
}
if ( bHasTextureCoordinates )
{
newT = Tline.PointAt(0.5);
}
for ( mei = 0; mei < me_list_count; mei++ )
{
me_list[mei].newvi = newvi;
me_list[mei].newV = newV;
me_list[mei].newN = newN;
me_list[mei].newT = newT;
}
}
else
{
// collapsing a "crease" edge - attempt to preserve
// the crease.
memset(&me,0,sizeof(me));
me.vi0 = -1;
me.vi1 = -1;
for ( mei = 0; mei < me_list_count; mei++ )
{
// cook up new vertex
me_list[mei].newvi = mesh.VertexCount();
me = me_list[mei];
ON_Line line;
line.from = mesh.Vertex(me.vi0);
line.to = mesh.Vertex(me.vi1);
me.newV = line.PointAt(0.5);
if ( bHasVertexNormals )
{
ON_3dVector N0(mesh.m_N[me.vi0]);
ON_3dVector N1(mesh.m_N[me.vi1]);
ON_3dVector N = N0 + N1;
if ( !N.Unitize() )
N = N0;
me.newN = N;
}
if ( bHasTextureCoordinates )
{
line.from = mesh.m_T[me.vi0];
line.to = mesh.m_T[me.vi1];
me.newT = line.PointAt(0.5);
}
me.newvi = (me.vi0 < me.vi1) ? me.vi0 : me.vi1;
me_list[mei].newvi = me.newvi;
me_list[mei].newV = me.newV;
me_list[mei].newN = me.newN;
me_list[mei].newT = me.newT;
}
}
// We are done averaging old mesh values.
// Change values in mesh m_V[], m_N[] and m_T[] arrays.
for ( mei = 0; mei < me_list_count; mei++ )
{
// We need to set the vertex location of all mesh vertices associated with the topological
// edge or we risk "tearing apart" vertexes at corners where there are more than 2 mesh vertexes
// associated with the topological vertex. Don't to this for anything but the location.
int i;
for (i = 0; top.m_topv[me_list[mei].topvi0].m_v_count > i; i++)
mesh.SetVertex(top.m_topv[me_list[mei].topvi0].m_vi[i], me_list[mei].newV);
for (i = 0; top.m_topv[me_list[mei].topvi1].m_v_count > i; i++)
mesh.SetVertex(top.m_topv[me_list[mei].topvi1].m_vi[i], me_list[mei].newV);
if ( bHasVertexNormals )
{
mesh.m_N[me_list[mei].vi0] = me_list[mei].newN;
mesh.m_N[me_list[mei].vi1] = me_list[mei].newN;
}
if ( bHasTextureCoordinates )
{
mesh.m_T[me_list[mei].vi0] = me_list[mei].newT;
mesh.m_T[me_list[mei].vi1] = me_list[mei].newT;
}
}
// make a map of old to new
int old2new_map_count = 0;
ON__NEWVI* old2new_map = (ON__NEWVI*)alloca(2*me_list_count*sizeof(old2new_map[0]));
for ( mei = 0; mei < me_list_count; mei++ )
{
old2new_map[old2new_map_count].oldvi = me_list[mei].vi0;
old2new_map[old2new_map_count].newvi = me_list[mei].newvi;
old2new_map_count++;
old2new_map[old2new_map_count].oldvi = me_list[mei].vi1;
old2new_map[old2new_map_count].newvi = me_list[mei].newvi;
old2new_map_count++;
}
// sort old2new_map[] so we can use a fast bsearch() call
// to update faces.
ON_qsort(old2new_map,old2new_map_count,sizeof(old2new_map[0]),(QSORTCMPFUNC)CompareNEWVI);
// count faces that use the vertices that are being changed
int bad_fi_count = 0;
int topv_end, vei, fi, fvi23, fvi;
ON__NEWVI nvi;
for ( topv_end = 0; topv_end < 2; topv_end++ )
{
const ON_MeshTopologyVertex& topv = (topv_end) ? topv1 : topv0;
for ( vei = 0; vei < topv.m_tope_count; vei++ )
{
topei = topv.m_topei[vei];
if ( topei < 0 && topei >= top.m_tope.Count() )
continue;
bad_fi_count += top.m_tope[topei].m_topf_count;
}
}
int* bad_fi = (int*)alloca(bad_fi_count*sizeof(*bad_fi));
bad_fi_count = 0;
// Go through all the faces that use the vertices at the
// ends of the edge and update the vi[] values to use the
// new vertices.
for ( topv_end = 0; topv_end < 2; topv_end++ )
{
const ON_MeshTopologyVertex& topv = (topv_end) ? topv1 : topv0;
for ( vei = 0; vei < topv.m_tope_count; vei++ )
{
topei = topv.m_topei[vei];
if ( topei < 0 && topei >= top.m_tope.Count() )
continue;
const ON_MeshTopologyEdge& e = top.m_tope[topei];
for ( efi = 0; efi < e.m_topf_count; efi++ )
{
fi = e.m_topfi[efi];
if ( fi < 0 || fi >= F_count )
continue;
bool bChangedFace = false;
ON_MeshFace& f = mesh.m_F[fi];
for ( fvi = 0; fvi < 4; fvi++ )
{
nvi.oldvi = f.vi[fvi];
ON__NEWVI* p = (ON__NEWVI*)bsearch(&nvi,old2new_map,old2new_map_count,sizeof(old2new_map[0]),(QSORTCMPFUNC)CompareNEWVI);
if ( 0 != p && p->oldvi != p->newvi)
{
RemoveFaceOrVertexFromNgon(mesh, fi, &p->oldvi);
f.vi[fvi] = p->newvi;
bChangedFace = true;
}
}
if ( bChangedFace )
{
if ( !f.IsValid(V_count) )
{
if ( f.vi[3] == f.vi[0] )
{
f.vi[0] = f.vi[1];
f.vi[1] = f.vi[2];
f.vi[2] = f.vi[3];
}
else if ( f.vi[0] == f.vi[1] )
{
fvi23 = f.vi[0];
f.vi[0] = f.vi[2];
f.vi[1] = f.vi[3];
f.vi[2] = fvi23;
f.vi[3] = fvi23;
}
else if ( f.vi[1] == f.vi[2] )
{
fvi23 = f.vi[1];
f.vi[1] = f.vi[0];
f.vi[0] = f.vi[3];
f.vi[2] = fvi23;
f.vi[3] = fvi23;
}
if ( f.vi[0] == f.vi[1] || f.vi[1] == f.vi[2] || f.vi[2] == f.vi[0] || f.vi[2] != f.vi[3] )
{
bad_fi[bad_fi_count++] = fi;
}
}
if ( bHasFaceNormals )
{
// invalid faces are removed below
ON_3fVector a, b, n;
a = mesh.Vertex(f.vi[2]) - mesh.Vertex(f.vi[0]);
b = mesh.Vertex(f.vi[3]) - mesh.Vertex(f.vi[1]);
n = ON_CrossProduct( a, b );
n.Unitize();
mesh.m_FN[fi] = n;
}
}
}
}
}
if ( bad_fi_count > 0 )
{
// remove collapsed faces from mesh
ON_qsort(bad_fi,bad_fi_count,sizeof(bad_fi[0]),CompareInt);
int bfi = 1;
int dest_fi = bad_fi[0];
unsigned int* face_index_map = nullptr;
ON_SimpleArray<unsigned int> face_index_map_array;
if (mesh.HasNgons())
{
face_index_map_array.Reserve(mesh.m_F.UnsignedCount());
face_index_map = face_index_map_array.Array();
for (fi = 0; fi < dest_fi; fi++)
face_index_map[fi] = fi;
for (fi = dest_fi; fi < mesh.m_F.Count(); fi++)
face_index_map[fi] = ON_UNSET_UINT_INDEX;
}
for ( fi = dest_fi+1; fi < F_count && bfi < bad_fi_count; fi++ )
{
if ( fi == bad_fi[bfi] )
{
bfi++;
if (nullptr != face_index_map)
face_index_map[fi] = ON_UNSET_UINT_INDEX;
}
else
{
// remove collapsed faces from ngons
if (nullptr != face_index_map)
face_index_map[fi] = dest_fi;
mesh.m_F[dest_fi++] = mesh.m_F[fi];
}
}
while (fi<F_count)
{
if (nullptr != face_index_map)
face_index_map[fi] = dest_fi;
mesh.m_F[dest_fi++] = mesh.m_F[fi++];
}
mesh.m_F.SetCount(dest_fi);
const unsigned int new_mesh_F_count = mesh.m_F.UnsignedCount();
bool bUpdateNgonMap = false;
for ( unsigned int ni = 0; ni < mesh.m_Ngon.UnsignedCount(); ni++ )
{
ON_MeshNgon* ngon = m_Ngon[ni];
if (nullptr == ngon)
continue;
unsigned int new_ngon_F_count = 0;
for (unsigned int nfi = 0; nfi < ngon->m_Fcount; nfi++)
{
unsigned int new_fi = face_index_map[ngon->m_fi[nfi]];
if (new_fi >= new_mesh_F_count)
continue;
ngon->m_fi[new_ngon_F_count++] = new_fi;
}
ON_MeshNgon* new_ngon;
if (0 == new_ngon_F_count)
{
new_ngon = nullptr;
}
else if (new_ngon_F_count != ngon->m_Fcount)
{
new_ngon = m_NgonAllocator.AllocateNgon(ngon->m_Vcount, new_ngon_F_count);
// copy old ngon vertex list to new ngon
for (unsigned int nvi_for_loop = 0; nvi_for_loop < new_ngon->m_Vcount; nvi_for_loop++)
new_ngon->m_vi[nvi_for_loop] = ngon->m_vi[nvi_for_loop];
// update new ngon face list
for (unsigned int nfi = 0; nfi < new_ngon->m_Fcount; nfi++)
new_ngon->m_fi[nfi] = ngon->m_fi[nfi];
}
else
{
continue;
}
bUpdateNgonMap = true;
m_Ngon[ni] = new_ngon;
mesh.m_NgonAllocator.DeallocateNgon(ngon);
}
if (bUpdateNgonMap)
{
mesh.m_NgonMap.Destroy();
mesh.CreateNgonMap();
}
if ( bHasFaceNormals )
{
bfi = 1;
dest_fi = bad_fi[0];
for ( fi = dest_fi+1; fi < F_count && bfi < bad_fi_count; fi++ )
{
if ( fi == bad_fi[bfi] )
{
bfi++;
}
else
{
mesh.m_FN[dest_fi++] = mesh.m_FN[fi];
}
}
while (fi<F_count)
{
mesh.m_FN[dest_fi++] = mesh.m_FN[fi++];
}
mesh.m_FN.SetCount(dest_fi);
}
}
mesh.Compact();
mesh.DestroyTopology();
mesh.DestroyPartition();
return true;
}
//static int NewIndex( int old_index, int missing_index_count, const int* missing_index_list )
//{
// // tool to calculate new indices when some elements are deleted from a list
// int delta;
// for ( delta = 0; delta < missing_index_count; delta++ )
// {
// if ( old_index < missing_index_list[delta] )
// break;
// }
// return old_index - delta;
//}
bool ON_Mesh::DeleteFace( int meshfi )
{
// Do NOT add a call Compact() in this function.
// Compact() is slow and this function may be called
// many times in sequence.
// It is the callers responsibility to call Compact()
// when it is needed.
bool rc = false;
if ( meshfi >= 0 && meshfi < m_F.Count() )
{
if ( m_top.m_topf.Count() > 0 )
{
DestroyTopology();
}
DestroyPartition();
DestroyTree();
if ( m_FN.Count() == m_F.Count() )
{
m_FN.Remove(meshfi);
}
m_F.Remove(meshfi);
// 6 Mar 2010 S. Baer
// Invalidate the cached IsClosed flag. This forces the mesh to
// recompute IsClosed the next time it is called
SetClosed(-1);
rc = true;
}
return rc;
}
ON_Mesh* ON_ControlPolygonMesh(
const ON_NurbsSurface& nurbs_surface,
bool bCleanMesh,
ON_Mesh* input_mesh
)
{
int u0 = 0;
int u1 = nurbs_surface.CVCount(0);
int v0 = 0;
int v1 = nurbs_surface.CVCount(1);
if ( 0 == nurbs_surface.m_cv || !nurbs_surface.IsValid() )
{
ON_ERROR("ON_ControlPolygonMesh - surface is not valid");
return nullptr;
}
ON_SimpleArray<double> gu(u1);
ON_SimpleArray<double> gv(v1);
gu.SetCount(u1);
gv.SetCount(v1);
nurbs_surface.GetGrevilleAbcissae(0,gu.Array());
nurbs_surface.GetGrevilleAbcissae(1,gv.Array());
ON_Interval d0 = nurbs_surface.Domain(0);
ON_Interval d1 = nurbs_surface.Domain(1);
bool bPeriodic0 = nurbs_surface.IsPeriodic(0)?true:false;
bool bIsClosed0 = bPeriodic0 ? true : (nurbs_surface.IsClosed(0)?true:false);
bool bPeriodic1 = nurbs_surface.IsPeriodic(1)?true:false;
bool bIsClosed1 = bPeriodic1 ? true : (nurbs_surface.IsClosed(1)?true:false);
if ( bPeriodic0 )
{
u1 -= (nurbs_surface.Degree(0) - 1);
while ( u1 < nurbs_surface.CVCount(0) && gu[u0] < d0[0] && gu[u1-1] <= d0[1] )
{
u0++;
u1++;
}
d0.Set(gu[u0],gu[u1-1]);
}
if ( bPeriodic1 )
{
v1 -= (nurbs_surface.Degree(1) - 1);
while ( v1 < nurbs_surface.CVCount(1) && gv[v0] < d1[0] && gv[v1-1] <= d1[1] )
{
v0++;
v1++;
}
d1.Set(gv[v0],gv[v1-1]);
}
ON_Mesh* mesh = (0 == input_mesh) ? new ON_Mesh() : input_mesh;
int vertex_count = (u1-u0)*(v1-v0);
int face_count = (u1-u0-1)*(v1-v0-1);
ON_3dPointArray& dpv = mesh->DoublePrecisionVertices();
dpv.Reserve(vertex_count);
mesh->m_N.Reserve(vertex_count);
mesh->m_T.Reserve(vertex_count);
mesh->m_S.Reserve(vertex_count);
mesh->m_F.Reserve(face_count);
mesh->m_srf_domain[0] = d0;
mesh->m_srf_domain[1] = d1;
ON_3dPoint V;
ON_3dVector N;
ON_2dPoint S;
ON_2dPoint T;
int hint[2] = {0,0};
int i, j;
int k = -1;
for ( j = v0; j < v1; j++)
{
S.y = gv[j];
T.y = d1.NormalizedParameterAt(S.y);
for ( i = u0; i < u1; i++)
{
nurbs_surface.GetCV( i, j, V);
S.x = gu[i];
T.x = d0.NormalizedParameterAt(S.x);
nurbs_surface.EvNormal(gu[i],gv[j],N,0,hint);
dpv.AppendNew() = V;
mesh->m_N.AppendNew() = N;
mesh->m_S.AppendNew() = S;
mesh->m_T.AppendNew() = T;
if ( i > u0 && j > v0 )
{
ON_MeshFace& f = mesh->m_F.AppendNew();
f.vi[0] = k++;
f.vi[1] = k;
f.vi[2] = dpv.Count()-1;
f.vi[3] = f.vi[2]-1;
}
}
k++;
}
mesh->UpdateSinglePrecisionVertices();
u1 -= u0;
v1 -= v0;
// make sure closed seams are spot on
if ( bIsClosed0 )
{
i = 0;
for ( j = 0; j < v1; j++ )
{
k = i + (u1-1);
mesh->SetVertex(k, mesh->Vertex(i));
if ( bPeriodic0 )
{
mesh->m_N[k] = mesh->m_N[i];
}
i = k+1;
// do NOT synch texture coordinates
}
}
if ( bIsClosed1 )
{
for ( i = 0, k = u1*(v1-1); i < u1; i++, k++ )
{
mesh->SetVertex(k, mesh->Vertex(i));
if ( bPeriodic1 )
{
mesh->m_N[k] = mesh->m_N[i];
}
// do NOT synch texture coordinates
}
}
// make sure singular ends are spot on
i=0;
for ( k = 0; k < 4; k++ )
{
if ( nurbs_surface.IsSingular(k) )
{
switch(k)
{
case 0: // 0 = south
i = 0;
j = 1;
k = u1;
break;
case 1: // 1 = east
i = u1-1;
j = u1;
k = u1*v1;
break;
case 2: // 2 = north
i = u1*(v1-1);
j = 1;
k = u1*v1;
break;
case 3: // 3 = west
i = 0;
j = u1;
k = u1*(v1-1)+1;
break;
}
V = mesh->Vertex(i);
for ( i = i+j; i < k; i += j )
{
mesh->SetVertex(i, V);
}
}
}
if ( bCleanMesh )
{
// Clean up triangles etc.
ON_3dPoint P[4];
ON_SimpleArray<int> badfi(32);
for ( i = 0; i < mesh->m_F.Count(); i++ )
{
ON_MeshFace& f = mesh->m_F[i];
P[0] = mesh->Vertex(f.vi[0]);
P[1] = mesh->Vertex(f.vi[1]);
P[2] = mesh->Vertex(f.vi[2]);
P[3] = mesh->Vertex(f.vi[3]);
if ( P[0] == P[1] )
{
f.vi[1] = f.vi[2];
f.vi[2] = f.vi[3];
P[1] = P[2];
P[2] = P[3];
}
if ( P[1] == P[2] )
{
f.vi[2] = f.vi[3];
P[2] = P[3];
}
if ( P[2] == P[3] )
{
f.vi[2] = f.vi[3];
P[2] = P[3];
}
if ( P[3] == P[0] )
{
f.vi[0] = f.vi[1];
f.vi[1] = f.vi[2];
f.vi[2] = f.vi[3];
P[0] = P[1];
P[1] = P[2];
P[2] = P[3];
}
if ( f.vi[0] == f.vi[1]
|| f.vi[1] == f.vi[2]
|| f.vi[3] == f.vi[0]
|| P[0] == P[2] || P[1] == P[3] )
{
badfi.Append(i);
}
}
if ( badfi.Count() > 0 )
{
if ( badfi.Count() == mesh->m_F.Count() )
{
if ( input_mesh )
{
mesh->Destroy();
}
else
{
delete mesh;
}
mesh = 0;
}
else
{
// remove bad faces
i = badfi[0];
j = i+1;
k = 1;
for ( j = i+1; j < mesh->m_F.Count(); j++ )
{
if ( k < badfi.Count() && j == badfi[k] )
{
k++;
}
else
{
mesh->m_F[i++] = mesh->m_F[j];
}
}
mesh->m_F.SetCount(i);
}
// 29 May 2008: Mikko, TRR 34687:
// Added crash protection. At this point mesh is nullptr if it contained all bad faces.
if ( mesh)
mesh->CullUnusedVertices();
}
}
return mesh;
}
///////////////////////////////////////////////////////////////////////
//
// mesh components
static bool IsUnweldedEdge(int edgeidx, const ON_MeshTopology& Top)
{
const ON_MeshTopologyEdge& edge = Top.m_tope[edgeidx];
if (1 == edge.m_topf_count)
return true;
if (1 == Top.m_topv[edge.m_topvi[0]].m_v_count || 1 == Top.m_topv[edge.m_topvi[1]].m_v_count)
{
//Both ends of the edge have to have more than one mesh vertex or they are for sure welded.
//However having more than 1 vertex at both ends does not necessarily mean it is unwelded.
return false;
}
ON_3dPoint ptA = Top.m_mesh->Vertex(Top.m_topv[edge.m_topvi[0]].m_vi[0]);
ON_3dPoint ptB = Top.m_mesh->Vertex(Top.m_topv[edge.m_topvi[1]].m_vi[0]);
ON_SimpleArray<int> ptAindexes(Top.m_topv[edge.m_topvi[0]].m_v_count);
ON_SimpleArray<int> ptBindexes(Top.m_topv[edge.m_topvi[1]].m_v_count);
int i, ict = edge.m_topf_count;
int j, jct;
int k, kct;
for (i=0; ict>i; i++)
{
const ON_MeshFace& face = Top.m_mesh->m_F[edge.m_topfi[i]];
jct = face.IsQuad()?4:3;
for (j=0; jct>j; j++)
{
if (ptA == Top.m_mesh->Vertex(face.vi[j]))
{
if (0 == ptAindexes.Count())
{
ptAindexes.Append(face.vi[j]);
continue;
}
else
{
kct = ptAindexes.Count();
for (k=0; kct>k; k++)
{
if (ptAindexes[k] == face.vi[j])
return false;
}
ptAindexes.Append(face.vi[j]);
}
}
else if (ptB == Top.m_mesh->Vertex(face.vi[j]))
{
if (0 == ptBindexes.Count())
{
ptBindexes.Append(face.vi[j]);
continue;
}
else
{
kct = ptBindexes.Count();
for (k=0; kct>k; k++)
{
if (ptBindexes[k] == face.vi[j])
return false;
}
ptBindexes.Append(face.vi[j]);
}
}
}
}
return true;
}
static void FindAdjacentFaces(const ON_MeshTopology& Top,
ON_SimpleArray<int>& FacesToCheck,
const ON_SimpleArray<int>& SortedFaceArray,
ON_SimpleArray<int>& DupFaceArray,
bool bUseVertexConnections,
bool bTopologicalConnections)
{
int fi, vi, ei, facecount = FacesToCheck.Count(), totalcount = SortedFaceArray.Count();
DupFaceArray.Zero();
ON_SimpleArray<int> OldFacesToCheck = FacesToCheck;
FacesToCheck.Empty();
for (fi=0;fi<facecount;fi++)
{
if (totalcount > OldFacesToCheck[fi])
{
if (0 == SortedFaceArray[OldFacesToCheck[fi]])
{
FacesToCheck.Append(OldFacesToCheck[fi]);
DupFaceArray[OldFacesToCheck[fi]] = 1;
}
if (false == bUseVertexConnections)
{
int j;
const ON_MeshTopologyFace& face = Top.m_topf[OldFacesToCheck[fi]];
for(ei=0;ei<(face.IsQuad()?4:3);ei++)
{
const ON_MeshTopologyEdge& edge = Top.m_tope[face.m_topei[ei]];
if (1 == edge.m_topf_count || (false == bTopologicalConnections && true == IsUnweldedEdge(face.m_topei[ei], Top)))
continue;
for(j=0;j<edge.m_topf_count;j++)
{
if (0 == SortedFaceArray[edge.m_topfi[j]] && 1 != DupFaceArray[edge.m_topfi[j]])
{
FacesToCheck.Append(edge.m_topfi[j]);
DupFaceArray[edge.m_topfi[j]] = 1;
}
}
}
}
else
{
int j, k, m;
ON_3dPoint Pt;
const ON_MeshFace& face = Top.m_mesh->m_F[OldFacesToCheck[fi]];
for(vi=0;vi<(face.IsQuad()?4:3);vi++)
{
const ON_MeshTopologyVertex& vertex = Top.m_topv[Top.m_topv_map[face.vi[vi]]];
for (j=0; vertex.m_tope_count>j; j++)
{
const ON_MeshTopologyEdge& edge = Top.m_tope[vertex.m_topei[j]];
for (k=0; edge.m_topf_count>k; k++)
{
if (true == bTopologicalConnections)
{
if (0 == SortedFaceArray[edge.m_topfi[k]] && 1 != DupFaceArray[edge.m_topfi[k]])
{
FacesToCheck.Append(edge.m_topfi[k]);
DupFaceArray[edge.m_topfi[k]] = 1;
}
}
else
{
Pt = Top.m_mesh->Vertex(vertex.m_vi[0]);
const ON_MeshFace& thisface = Top.m_mesh->m_F[edge.m_topfi[k]];
for (m=0; m<(thisface.IsQuad()?4:3);m++)
{
if (Pt != Top.m_mesh->Vertex(thisface.vi[m]))
continue;
if (face.vi[vi] == thisface.vi[m] && 0 == SortedFaceArray[edge.m_topfi[k]] && 1 != DupFaceArray[edge.m_topfi[k]])
{
//Faces share vertex
FacesToCheck.Append(edge.m_topfi[k]);
DupFaceArray[edge.m_topfi[k]] = 1;
break;
}
}
}
}
}
}
}
}
}
}
int ON_Mesh::GetConnectedComponents( bool bUseVertexConnections,
bool bTopologicalConnections,
ON_SimpleArray<int>& facet_component_labels
) const
{
int i, facecount = m_F.Count(), meshidx = 0;
//This array will act as an associative array to m_F since ON_MeshFace do not have something
//like m_trim_user.i on a ON_BrepTrim. It will have the indice of the final mesh the face
//belongs to.
if (facecount != facet_component_labels.Count())
{
facet_component_labels.Reserve(facecount);
facet_component_labels.SetCount(facecount);
}
//initialize to 0
facet_component_labels.MemSet(0);
ON_SimpleArray<int> DupFaceArray(facecount);
DupFaceArray.SetCount(facecount);
const ON_MeshTopology& Top = Topology();
if (!Top.IsValid())
return 0;
ON_SimpleArray<int> FacesToCheck;
FacesToCheck.Reserve(64);
i = 0;
while (i < facecount)
{
meshidx++;
FacesToCheck.Append(i);
while(0 != FacesToCheck.Count())
{
//Figure out which faces are connected to each other
FindAdjacentFaces(Top, FacesToCheck, facet_component_labels, DupFaceArray, bUseVertexConnections, bTopologicalConnections);
int j;
for (j=0;j<FacesToCheck.Count();j++)
facet_component_labels[FacesToCheck[j]] = meshidx;
}
for(;i<facecount;i++)
{
if (0 == facet_component_labels[i])
break;
}
}
return meshidx;
}
int ON_Mesh::GetConnectedComponents( bool bUseVertexConnections,
bool bTopologicalConnections,
ON_SimpleArray<ON_Mesh*>* components
) const
{
int i, j, k, kct, facecount = m_F.Count();
ON_SimpleArray<int> SortedFaceArray(facecount);
SortedFaceArray.SetCount(facecount);
int compct = GetConnectedComponents(bUseVertexConnections, bTopologicalConnections, SortedFaceArray);
if (0 == compct || 0 == components)
return compct;
bool bHasFaceNormals = HasFaceNormals();
bool bHasPrincipalCurvatures = HasPrincipalCurvatures();
bool bHasSurfaceParameters = HasSurfaceParameters();
bool bHasTextureCoordinates = HasTextureCoordinates();
bool bHasVertexColors = HasVertexColors();
bool bHasVertexNormals = HasVertexNormals();
ON_MeshFace newface;
newface.vi[0] = -1; newface.vi[1] = -1; newface.vi[2] = -1; newface.vi[3] = -1;
ON_SimpleArray<int> vertidxarray(VertexCount());
vertidxarray.SetCount(VertexCount());
ON_SimpleArray<ON_MeshNgon> ngons;
ON_SimpleArray<unsigned int> ngonfacecount;
unsigned int ngonct = this->NgonUnsignedCount();
ON_SimpleArray<unsigned int> ngonfacevertexarray;
if (0 < ngonct)
{
// if the original mesh had ngons setup an array of new ngons
// to be filled in as the face/vertex remap occurs
ngons.Reserve(ngonct);
// this array will hold the current index for the face of the
// new ngon
ngonfacecount.Reserve(ngonct);
ngonfacecount.SetCount(ngonct);
ngonfacecount.MemSet(0);
unsigned int j_local, ct;
// first figure out how much space we'll need for
// face and vertex arrays
ct = 0;
for (j_local = 0; ngonct > j_local; j_local++)
{
const ON_MeshNgon* ngon = this->Ngon(j_local);
if (nullptr == ngon)
continue;
ct += ngon->m_Vcount + ngon->m_Fcount;
}
ngonfacevertexarray.Reserve(ct);
ngonfacevertexarray.SetCount(ct);
unsigned int* tmp = ngonfacevertexarray.Array();
//initialize to unset, not zero, so we can check for ngons
//that were not part of this mark and prevent adding them to
//output
memset(tmp, ON_UNSET_UINT_INDEX, ct*sizeof(tmp[0]));
ct = 0;
for (j_local = 0; ngonct > j_local; j_local++)
{
const ON_MeshNgon* ngon = this->Ngon(j_local);
if (nullptr == ngon)
continue;
ON_MeshNgon& new_ngon = ngons.AppendNew();
new_ngon.m_Vcount = ngon->m_Vcount;
new_ngon.m_Fcount = ngon->m_Fcount;
new_ngon.m_vi = &ngonfacevertexarray[ct];
ct += new_ngon.m_Vcount;
new_ngon.m_fi = &ngonfacevertexarray[ct];
ct += new_ngon.m_Fcount;
}
}
const ON_3dPoint* pMesh_D = 0;
if ( HasDoublePrecisionVertices() )
{
if ( VertexCount() > 0 && HasSynchronizedDoubleAndSinglePrecisionVertices() )
{
pMesh_D = DoublePrecisionVertices().Array();
}
}
ON_3dPointArray bogus_D;
for (i=1;compct>=i;i++)
{
kct = vertidxarray.Count();
for (k=0; kct>k; k++)
vertidxarray[k] = -1;
ON_Mesh* pNewMesh = new ON_Mesh();
if (0 == pNewMesh)
continue;
// Jussi, July 16 2024, RH-82825:
// Copy packed texture information so that surface paramater mapping
// can be reapplied without changing texture appearance.
pNewMesh->m_packed_tex_domain[0] = m_packed_tex_domain[0];
pNewMesh->m_packed_tex_domain[1] = m_packed_tex_domain[1];
pNewMesh->m_packed_tex_rotate = m_packed_tex_rotate;
pNewMesh->m_srf_domain[0] = m_srf_domain[0];
pNewMesh->m_srf_domain[1] = m_srf_domain[1];
pNewMesh->m_srf_scale[0] = m_srf_scale[0];
pNewMesh->m_srf_scale[1] = m_srf_scale[1];
ON_3dPointArray& pNewMesh_D = ( 0 != pMesh_D )
? pNewMesh->DoublePrecisionVertices()
: bogus_D;
for (j=0;j<facecount;j++)
{
if ( i == SortedFaceArray[j] )
{
const ON_MeshFace& face = m_F[j];
kct = face.IsTriangle()?3:4;
for (k=0; k<kct; k++)
{
if (-1 != vertidxarray[face.vi[k]])
{
newface.vi[k] = vertidxarray[face.vi[k]];
continue;
}
int newvi;
if ( 0 != pMesh_D )
{
newvi = pNewMesh_D.Count();
pNewMesh_D.Append(pMesh_D[face.vi[k]]);
}
else
{
newvi = pNewMesh->VertexCount();
pNewMesh->m_V.AppendNew() = ON_3fPoint(Vertex(face.vi[k]));
}
newface.vi[k] = vertidxarray[face.vi[k]] = newvi;
if (true == bHasPrincipalCurvatures)
pNewMesh->m_K.Append(m_K[face.vi[k]]);
if (true == bHasSurfaceParameters)
pNewMesh->m_S.Append(m_S[face.vi[k]]);
if (true == bHasTextureCoordinates)
pNewMesh->m_T.Append(m_T[face.vi[k]]);
if (true == bHasVertexColors)
pNewMesh->m_C.Append(m_C[face.vi[k]]);
if (true == bHasVertexNormals)
pNewMesh->m_N.Append(m_N[face.vi[k]]);
}
if (3 == kct)
newface.vi[3] = newface.vi[2];
if (0 < ngonct)
{
// Add new face indexes to the new ngons
unsigned int idx = this->NgonIndexFromFaceIndex(j);
if (ON_UNSET_UINT_INDEX != idx)
ngons[idx].m_fi[ngonfacecount[idx]++] = pNewMesh->FaceCount();
}
pNewMesh->m_F.Append(newface);
if (true == bHasFaceNormals)
pNewMesh->m_FN.Append(m_FN[j]);
}
}
if (0 < ngonct)
{
unsigned int j_local, k_local;
for (j_local = 0; ngonct > j_local; j_local++)
{
const ON_MeshNgon* oldngon = this->Ngon(j_local);
if (nullptr == oldngon)
continue;
ON_MeshNgon& newngon = ngons[j_local];
if (oldngon->m_Vcount != newngon.m_Vcount)
continue; //should never happen
for (k_local = 0; newngon.m_Vcount > k_local; k_local++)
newngon.m_vi[k_local] = vertidxarray[oldngon->m_vi[k_local]];
}
bool bIsValid;
for (j_local = 0; ngonct > j_local; j_local++)
{
bIsValid = true;
ON_MeshNgon& ngon = ngons[j_local];
// if the faces/ngons were not marked then the
// output ngon will have not been set, do not
// add it to the output mesh
for (k_local = 0; ngon.m_Vcount > k_local && bIsValid; k_local++)
{
if (ON_UNSET_UINT_INDEX == ngon.m_vi[k_local])
bIsValid = false;
}
for (k_local = 0; ngon.m_Fcount > k_local && bIsValid; k_local++)
{
if (ON_UNSET_UINT_INDEX == ngon.m_fi[k_local])
bIsValid = false;
}
if (bIsValid)
pNewMesh->AddNgon(ngons[j_local].m_Vcount, ngons[j_local].m_vi, ngons[j_local].m_Fcount, ngons[j_local].m_fi);
}
pNewMesh->CreateNgonMap();
}
if ( 0 != pMesh_D )
pNewMesh->UpdateSinglePrecisionVertices();
pNewMesh->Compact();
if (0 < pNewMesh->m_F.Count())
components->Append(pNewMesh);
else
{
delete pNewMesh;
pNewMesh = 0;
}
}
return compct;
}