mirror of
https://github.com/mcneel/opennurbs.git
synced 2026-03-02 03:57:00 +08:00
15211 lines
396 KiB
C++
15211 lines
396 KiB
C++
/* $NoKeywords: $ */
|
|
/*
|
|
//
|
|
// Copyright (c) 1993-2012 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
|
|
|
|
|
|
|
|
|
|
// NEVER COPY OR MOVE THE NEXT 2 LINES
|
|
#define ON_BOZO_VACCINE_17F24E7521BE4a7b9F3D7F85225247E3
|
|
#define ON_BOZO_VACCINE_B5628CA982C44CAE9883487B3E4AB28B
|
|
// NEVER COPY OR MOVE THE PREVIOUS 2 LINES
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////
|
|
//
|
|
// Rhino V5 Double precision vertices user data
|
|
// Used to read and write version 5 .3dm files.
|
|
//
|
|
|
|
class /* DO NOT copy, move, or export this class */ ON_V5_MeshDoubleVertices : public ON_UserData
|
|
{
|
|
ON_OBJECT_DECLARE(ON_V5_MeshDoubleVertices);
|
|
|
|
public:
|
|
ON_V5_MeshDoubleVertices();
|
|
~ON_V5_MeshDoubleVertices();
|
|
|
|
// default copy constructor and operator= work fine.
|
|
|
|
/*
|
|
If the mesh has ON_V5_MeshDoubleVertices user data, then return
|
|
a pointer to it.
|
|
*/
|
|
static ON_V5_MeshDoubleVertices* GetV5(const ON_Mesh* mesh);
|
|
|
|
/*
|
|
Attach new ON_V5_MeshDoubleVertices user data to the mesh.
|
|
This will fail and return nullptr if the mesh already has
|
|
ON_V5_MeshDoubleVertices user data.
|
|
*/
|
|
static ON_V5_MeshDoubleVertices* AttachV5(const ON_Mesh* mesh);
|
|
|
|
// virtual ON_Object overrides
|
|
bool IsValid( class ON_TextLog* text_log = nullptr ) const override;
|
|
void Dump( ON_TextLog& ) const override;
|
|
unsigned int SizeOf() const override;
|
|
ON__UINT32 DataCRC(ON__UINT32) const override;
|
|
bool Write(ON_BinaryArchive&) const override;
|
|
bool Read(ON_BinaryArchive&) override;
|
|
|
|
// virtual ON_UserData overrides
|
|
bool GetDescription( ON_wString& ) override;
|
|
bool Archive() const override;
|
|
bool Transform( const ON_Xform& ) override;
|
|
|
|
bool DeleteAfterWrite(
|
|
const class ON_BinaryArchive& archive,
|
|
const class ON_Object* parent_object
|
|
) const override;
|
|
|
|
bool DeleteAfterRead(
|
|
const class ON_BinaryArchive& archive,
|
|
class ON_Object* parent_object
|
|
) const override;
|
|
|
|
|
|
#if !defined(ON_BOZO_VACCINE_17F24E7521BE4a7b9F3D7F85225247E3)
|
|
#error DO NOT copy, move or export the definition of ON_V5_MeshDoubleVertices
|
|
#endif
|
|
#undef ON_BOZO_VACCINE_17F24E7521BE4a7b9F3D7F85225247E3
|
|
|
|
ON__UINT32 DoubleCRC() const;
|
|
static ON__UINT32 FloatCRC( const ON_3fPointArray& );
|
|
|
|
// If m_dV.Count() != m_dcount or
|
|
// m_dCRC != ON_CRC32(0,m_dV.Count()*sizeof(ON_3dPoint),m_dV.Array()),
|
|
// then somebody has changed m_dV and not called
|
|
// SetDoublePrecisionVerticesAsValid() to mark the
|
|
// values as valid.
|
|
//
|
|
// If ON_Mesh.M_V.Count() != m_fcount or
|
|
// m_fCRC != ON_CRC32(0,m_V.Count()*sizeof(ON_3fPoint),m_V.Array()),
|
|
// then somebody has changed ON_Mesh.m_V and not called
|
|
// SetSinglePrecisionVerticesAsValid() to mark the
|
|
// values as valid.
|
|
//
|
|
// Whenever there is a question about which values are valid,
|
|
// it is assumed the m_V array is valid and the double precision
|
|
// informtion should be destroyed.
|
|
int m_fcount = 0; // single precision vertex count
|
|
int m_dcount = 0; // double precision vertex count
|
|
ON__UINT32 m_fCRC = 0; // crc of float vertex array
|
|
ON__UINT32 m_dCRC = 0; // crc of double vertex array
|
|
|
|
ON_3dPointArray m_V5_dV; // double precision mesh vertices
|
|
};
|
|
|
|
|
|
ON_MeshCurveParameters::ON_MeshCurveParameters()
|
|
{
|
|
memset(this,0,sizeof(*this));
|
|
}
|
|
|
|
ON_OBJECT_IMPLEMENT(ON_Mesh,ON_Geometry,"4ED7D4E4-E947-11d3-BFE5-0010830122F0");
|
|
|
|
/*
|
|
ON_MeshEdge& ON_MeshEdge::operator=( const ON_MeshEdge& src )
|
|
{
|
|
int fc = src.m_fcount+(src.m_fcount%1);
|
|
if ( fc <= 2 ) {
|
|
if ( m_fi && m_fi != m_private_fi ) onfree((void*)m_fi);
|
|
m_fi = m_private_fi;
|
|
fc = 2;
|
|
}
|
|
else {
|
|
if ( (m_fcount+(m_fcount%1)) != fc ) {
|
|
if ( m_fi == m_private_fi )
|
|
m_fi = 0;
|
|
m_fi = (int*)onrealloc(m_fi,fc*sizeof(*m_fi));
|
|
}
|
|
}
|
|
memcpy(m_fi,src.m_fi,fc*sizeof(*m_fi));
|
|
m_fcount=src.m_fcount;
|
|
return *this;
|
|
}
|
|
|
|
void ON_MeshEdge::AppendFaceIndex(int face_index)
|
|
{
|
|
if ( m_fcount>0 && !(m_fcount%1) ) {
|
|
if ( m_fi == m_private_fi ) {
|
|
m_fi = (int*)onmalloc((m_fcount+2)*sizeof(*m_fi));
|
|
m_fi[0] = m_private_fi[0];
|
|
m_fi[1] = m_private_fi[1];
|
|
}
|
|
else {
|
|
m_fi = (int*)onrealloc(m_fi,(m_fcount+2)*sizeof(*m_fi));
|
|
}
|
|
}
|
|
m_fi[m_fcount++] = face_index;
|
|
}
|
|
*/
|
|
|
|
bool
|
|
ON_MeshFace::IsValid(int mesh_vertex_count) const
|
|
{
|
|
return (vi[0] >= 0 && vi[0] < mesh_vertex_count
|
|
&& vi[1] >= 0 && vi[1] < mesh_vertex_count
|
|
&& vi[2] >= 0 && vi[2] < mesh_vertex_count
|
|
&& vi[3] >= 0 && vi[3] < mesh_vertex_count
|
|
&& vi[0] != vi[1] && vi[1] != vi[2] && vi[2] != vi[0]
|
|
&& (vi[2] == vi[3] || (vi[0] != vi[3] && vi[1] != vi[3])));
|
|
}
|
|
|
|
bool
|
|
ON_MeshFace::IsValid(unsigned int mesh_vertex_count) const
|
|
{
|
|
const unsigned int* uvi = (const unsigned int*)vi;
|
|
return (uvi[0] < mesh_vertex_count
|
|
&& uvi[1] < mesh_vertex_count
|
|
&& uvi[2] < mesh_vertex_count
|
|
&& uvi[3] < mesh_vertex_count
|
|
&& uvi[0] != uvi[1] && uvi[1] != uvi[2] && uvi[2] != uvi[0]
|
|
&& (uvi[2] == uvi[3] || (uvi[0] != uvi[3] && uvi[1] != uvi[3])));
|
|
}
|
|
|
|
|
|
bool
|
|
ON_MeshFace::IsValid(int mesh_vertex_count, const ON_3fPoint* V ) const
|
|
{
|
|
if ( !IsValid(mesh_vertex_count) )
|
|
return false;
|
|
if ( !(V[vi[0]] != V[vi[1]]) )
|
|
return false;
|
|
if ( !(V[vi[0]] != V[vi[2]]) )
|
|
return false;
|
|
if ( !(V[vi[1]] != V[vi[2]]) )
|
|
return false;
|
|
if ( vi[2] != vi[3] )
|
|
{
|
|
if ( !(V[vi[0]] != V[vi[3]]) )
|
|
return false;
|
|
if ( !(V[vi[1]] != V[vi[3]]) )
|
|
return false;
|
|
if ( !(V[vi[2]] != V[vi[3]]) )
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
ON_MeshFace::IsValid(int mesh_vertex_count, const ON_3dPoint* V ) const
|
|
{
|
|
if ( !IsValid(mesh_vertex_count) )
|
|
return false;
|
|
if ( !(V[vi[0]] != V[vi[1]]) )
|
|
return false;
|
|
if ( !(V[vi[0]] != V[vi[2]]) )
|
|
return false;
|
|
if ( !(V[vi[1]] != V[vi[2]]) )
|
|
return false;
|
|
if ( vi[2] != vi[3] )
|
|
{
|
|
if ( !(V[vi[0]] != V[vi[3]]) )
|
|
return false;
|
|
if ( !(V[vi[1]] != V[vi[3]]) )
|
|
return false;
|
|
if ( !(V[vi[2]] != V[vi[3]]) )
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool ON_MeshFace::Repair(
|
|
int mesh_vertex_count
|
|
)
|
|
{
|
|
ON_MeshFace f;
|
|
int fvi_count = 0;
|
|
f.vi[0] = f.vi[1] = f.vi[2] = f.vi[3] = -1;
|
|
|
|
if ( vi[0] >= 0 && vi[0] < mesh_vertex_count )
|
|
f.vi[fvi_count++] = vi[0];
|
|
|
|
if ( vi[1] >= 0 && vi[1] < mesh_vertex_count && f.vi[0] != vi[1] )
|
|
f.vi[fvi_count++] = vi[1];
|
|
|
|
if ( vi[2] >= 0 && vi[2] < mesh_vertex_count && f.vi[0] != vi[2] && f.vi[1] != vi[2] )
|
|
f.vi[fvi_count++] = vi[2];
|
|
|
|
if ( vi[3] >= 0 && vi[3] < mesh_vertex_count && f.vi[0] != vi[3] && f.vi[1] != vi[3] && f.vi[2] != vi[3] )
|
|
f.vi[fvi_count++] = vi[3];
|
|
|
|
if ( fvi_count < 3 )
|
|
return false;
|
|
|
|
if ( 3 == fvi_count )
|
|
f.vi[3] = f.vi[2];
|
|
|
|
if ( !f.IsValid(mesh_vertex_count) )
|
|
return false;
|
|
|
|
vi[0] = f.vi[0];
|
|
vi[1] = f.vi[1];
|
|
vi[2] = f.vi[2];
|
|
vi[3] = f.vi[3];
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ON_MeshFace::Repair(
|
|
int mesh_vertex_count,
|
|
const ON_3fPoint* V
|
|
)
|
|
{
|
|
ON_MeshFace f;
|
|
int fvi_count = 0;
|
|
f.vi[0] = f.vi[1] = f.vi[2] = f.vi[3] = -1;
|
|
|
|
if ( vi[0] >= 0 && vi[0] < mesh_vertex_count && V[vi[0]].IsValid() )
|
|
f.vi[fvi_count++] = vi[0];
|
|
|
|
if (vi[1] >= 0 && vi[1] < mesh_vertex_count && f.vi[0] != vi[1] && V[vi[1]].IsValid())
|
|
{
|
|
if ( 0 == fvi_count || V[f.vi[0]] != V[vi[1]] )
|
|
f.vi[fvi_count++] = vi[1];
|
|
}
|
|
|
|
if ( fvi_count < 1 )
|
|
return false;
|
|
|
|
if ( vi[2] >= 0 && vi[2] < mesh_vertex_count && f.vi[0] != vi[2] && f.vi[1] != vi[2] && V[vi[2]].IsValid() && V[f.vi[0]] != V[vi[2]] )
|
|
{
|
|
if ( 1 == fvi_count || V[f.vi[1]] != V[vi[2]] )
|
|
f.vi[fvi_count++] = vi[2];
|
|
}
|
|
|
|
if ( fvi_count < 2 )
|
|
return false;
|
|
|
|
if ( vi[3] >= 0 && vi[3] < mesh_vertex_count && f.vi[0] != vi[3] && f.vi[1] != vi[3] && f.vi[2] != vi[3] && V[vi[3]].IsValid() && V[f.vi[0]] != V[vi[3]] && V[f.vi[1]] != V[vi[3]] )
|
|
{
|
|
if ( 2 == fvi_count || V[f.vi[2]] != V[vi[3]] )
|
|
f.vi[fvi_count++] = vi[3];
|
|
}
|
|
|
|
if ( fvi_count < 3 )
|
|
return false;
|
|
|
|
if ( 3 == fvi_count )
|
|
f.vi[3] = f.vi[2];
|
|
|
|
if ( !f.IsValid(mesh_vertex_count) )
|
|
return false;
|
|
|
|
vi[0] = f.vi[0];
|
|
vi[1] = f.vi[1];
|
|
vi[2] = f.vi[2];
|
|
vi[3] = f.vi[3];
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool ON_MeshFace::Repair(
|
|
int mesh_vertex_count,
|
|
const ON_3dPoint* V
|
|
)
|
|
{
|
|
ON_MeshFace f;
|
|
int fvi_count = 0;
|
|
f.vi[0] = f.vi[1] = f.vi[2] = f.vi[3] = -1;
|
|
|
|
if ( vi[0] >= 0 && vi[0] < mesh_vertex_count && V[vi[0]].IsValid() )
|
|
f.vi[fvi_count++] = vi[0];
|
|
|
|
if (vi[1] >= 0 && vi[1] < mesh_vertex_count && f.vi[0] != vi[1] && V[vi[1]].IsValid())
|
|
{
|
|
if ( 0 == fvi_count || V[f.vi[0]] != V[vi[1]] )
|
|
f.vi[fvi_count++] = vi[1];
|
|
}
|
|
|
|
if ( fvi_count < 1 )
|
|
return false;
|
|
|
|
if ( vi[2] >= 0 && vi[2] < mesh_vertex_count && f.vi[0] != vi[2] && f.vi[1] != vi[2] && V[vi[2]].IsValid() && V[f.vi[0]] != V[vi[2]] )
|
|
{
|
|
if ( 1 == fvi_count || V[f.vi[1]] != V[vi[2]] )
|
|
f.vi[fvi_count++] = vi[2];
|
|
}
|
|
|
|
if ( fvi_count < 2 )
|
|
return false;
|
|
|
|
if ( vi[3] >= 0 && vi[3] < mesh_vertex_count && f.vi[0] != vi[3] && f.vi[1] != vi[3] && f.vi[2] != vi[3] && V[vi[3]].IsValid() && V[f.vi[0]] != V[vi[3]] && V[f.vi[1]] != V[vi[3]] )
|
|
{
|
|
if ( 2 == fvi_count || V[f.vi[2]] != V[vi[3]] )
|
|
f.vi[fvi_count++] = vi[3];
|
|
}
|
|
|
|
if ( fvi_count < 3 )
|
|
return false;
|
|
|
|
if ( 3 == fvi_count )
|
|
f.vi[3] = f.vi[2];
|
|
|
|
if ( !f.IsValid(mesh_vertex_count) )
|
|
return false;
|
|
|
|
vi[0] = f.vi[0];
|
|
vi[1] = f.vi[1];
|
|
vi[2] = f.vi[2];
|
|
vi[3] = f.vi[3];
|
|
|
|
return true;
|
|
}
|
|
|
|
ON_Mesh::ON_Mesh()
|
|
: m_packed_tex_rotate(0)
|
|
, m_parent(0)
|
|
, m_mesh_parameters(0)
|
|
, m_invalid_count(0)
|
|
, m_quad_count(0)
|
|
, m_triangle_count(0)
|
|
, m_mesh_is_closed(0)
|
|
, m_mesh_is_manifold(0)
|
|
, m_mesh_is_oriented(0)
|
|
, m_mesh_is_solid(0)
|
|
{
|
|
m_top.m_mesh = this;
|
|
m_srf_scale[0] = 0.0;
|
|
m_srf_scale[1] = 0.0;
|
|
m_kstat[0] = 0;
|
|
m_kstat[1] = 0;
|
|
m_kstat[2] = 0;
|
|
m_kstat[3] = 0;
|
|
InvalidateBoundingBoxes();
|
|
m_partition = 0;
|
|
m_hidden_count = 0;
|
|
}
|
|
|
|
|
|
ON_Mesh::ON_Mesh(
|
|
int initial_facet_capacity, // initial facet array capacity
|
|
int initial_vertex_capacity, // initial vertex array capacity
|
|
bool bHasVertexNormals, // true if mesh has unit vertex normals
|
|
bool bHasTextureCoordinates // true if mesh has texture coordinates
|
|
)
|
|
: m_V(initial_vertex_capacity)
|
|
, m_F(initial_facet_capacity)
|
|
, m_N(bHasVertexNormals?initial_vertex_capacity:0)
|
|
, m_T(bHasTextureCoordinates?initial_vertex_capacity:0)
|
|
, m_packed_tex_rotate(0)
|
|
, m_parent(0)
|
|
, m_mesh_parameters(0)
|
|
, m_invalid_count(0)
|
|
, m_quad_count(0)
|
|
, m_triangle_count(0)
|
|
, m_mesh_is_closed(0)
|
|
, m_mesh_is_manifold(0)
|
|
, m_mesh_is_oriented(0)
|
|
, m_mesh_is_solid(0)
|
|
{
|
|
m_top.m_mesh = this;
|
|
m_srf_scale[0] = 0.0;
|
|
m_srf_scale[1] = 0.0;
|
|
m_kstat[0] = 0;
|
|
m_kstat[1] = 0;
|
|
m_kstat[2] = 0;
|
|
m_kstat[3] = 0;
|
|
InvalidateBoundingBoxes();
|
|
m_partition = 0;
|
|
m_hidden_count = 0;
|
|
}
|
|
|
|
ON_Mesh::ON_Mesh( const ON_Mesh& src )
|
|
: m_packed_tex_rotate(0)
|
|
, m_parent(0)
|
|
, m_mesh_parameters(0)
|
|
, m_invalid_count(0)
|
|
, m_quad_count(0)
|
|
, m_triangle_count(0)
|
|
, m_mesh_is_closed(0)
|
|
, m_mesh_is_manifold(0)
|
|
, m_mesh_is_oriented(0)
|
|
, m_mesh_is_solid(0)
|
|
{
|
|
// Do not copy m_mesh_cache. Cached information will
|
|
// be recalculated if it is needed.
|
|
m_top.m_mesh = this;
|
|
m_srf_scale[0] = 0.0;
|
|
m_srf_scale[1] = 0.0;
|
|
|
|
m_kstat[0] = 0;
|
|
m_kstat[1] = 0;
|
|
m_kstat[2] = 0;
|
|
m_kstat[3] = 0;
|
|
InvalidateBoundingBoxes();
|
|
m_partition = 0;
|
|
m_hidden_count = 0;
|
|
ON_Mesh::operator=(src);
|
|
}
|
|
|
|
|
|
unsigned int ON_Mesh::SizeOf() const
|
|
{
|
|
unsigned int sz = ON_Geometry::SizeOf();
|
|
sz += m_V.SizeOfArray();
|
|
sz += m_F.SizeOfArray();
|
|
sz += m_N.SizeOfArray();
|
|
sz += m_FN.SizeOfArray();
|
|
sz += m_T.SizeOfArray();
|
|
sz += m_S.SizeOfArray();
|
|
sz += m_K.SizeOfArray();
|
|
sz += m_C.SizeOfArray();
|
|
sz += m_top.m_topv_map.SizeOfArray();
|
|
sz += m_top.m_topv.SizeOfArray();
|
|
sz += m_top.m_tope.SizeOfArray();
|
|
sz += m_top.m_topf.SizeOfArray();
|
|
return sz;
|
|
}
|
|
|
|
ON__UINT32 ON_Mesh::DataCRC(ON__UINT32 current_remainder) const
|
|
{
|
|
const ON_3fPoint* p = m_V.Array();
|
|
current_remainder = ON_CRC32(current_remainder,m_V.Count()*sizeof(p[0]),p);
|
|
current_remainder = ON_CRC32(current_remainder,m_F.Count()*sizeof(ON_MeshFace),m_F.Array());
|
|
const ON_3fVector* v = m_N.Array();
|
|
current_remainder = ON_CRC32(current_remainder,m_N.Count()*sizeof(v[0]),v);
|
|
return current_remainder;
|
|
}
|
|
|
|
ON_Mesh& ON_Mesh::operator=( const ON_Mesh& src )
|
|
{
|
|
if ( this != &src )
|
|
{
|
|
Destroy();
|
|
ON_Geometry::operator=(src);
|
|
|
|
m_V = src.m_V;
|
|
m_dV = src.m_dV;
|
|
m_F = src.m_F;
|
|
m_N = src.m_N;
|
|
m_FN = src.m_FN;
|
|
m_T = src.m_T;
|
|
m_TC = src.m_TC;
|
|
m_S = src.m_S;
|
|
m_H = src.m_H;
|
|
m_hidden_count = src.m_hidden_count;
|
|
|
|
|
|
unsigned int ngon_count = src.HasNgons() ? src.m_Ngon.UnsignedCount() : 0;
|
|
if ( ngon_count > 0 )
|
|
{
|
|
const unsigned int src_V_count = src.m_V.UnsignedCount();
|
|
const unsigned int src_F_count = src.m_F.UnsignedCount();
|
|
m_Ngon.Reserve(ngon_count);
|
|
for ( unsigned int i = 0; i < ngon_count; i++ )
|
|
{
|
|
const ON_MeshNgon* src_ngon = src.m_Ngon[i];
|
|
if ( 0 == src_ngon )
|
|
continue;
|
|
if ( src_ngon->m_Vcount <= 0 )
|
|
continue;
|
|
if ( 0 == src_ngon->m_vi )
|
|
continue;
|
|
if ( src_ngon->m_Vcount > src_V_count )
|
|
{
|
|
ON_ERROR("Corrupt ngon");
|
|
continue;
|
|
}
|
|
if ( src_ngon->m_Fcount > src_F_count )
|
|
{
|
|
ON_ERROR("Corrupt ngon");
|
|
continue;
|
|
}
|
|
if ( 0 == src_ngon->m_fi && src_ngon->m_Fcount > 0 )
|
|
{
|
|
ON_ERROR("Corrupt ngon");
|
|
continue;
|
|
}
|
|
ON_MeshNgon* ngon = m_NgonAllocator.CopyNgon(src_ngon);
|
|
if (ngon)
|
|
m_Ngon.Append(ngon);
|
|
}
|
|
if ( 0 != src.NgonMap())
|
|
{
|
|
this->CreateNgonMap();
|
|
}
|
|
}
|
|
|
|
m_Ctag = src.m_Ctag;
|
|
m_Ttag = src.m_Ttag;
|
|
m_packed_tex_domain[0] = src.m_packed_tex_domain[0];
|
|
m_packed_tex_domain[1] = src.m_packed_tex_domain[1];
|
|
m_srf_domain[0] = src.m_srf_domain[0];
|
|
m_srf_domain[1] = src.m_srf_domain[1];
|
|
m_srf_scale[0] = src.m_srf_scale[0];
|
|
m_srf_scale[1] = src.m_srf_scale[1];
|
|
m_packed_tex_rotate = src.m_packed_tex_rotate;
|
|
|
|
m_K = src.m_K;
|
|
m_C = src.m_C;
|
|
|
|
m_parent = src.m_parent;
|
|
//m_material_index = src.m_material_index;
|
|
|
|
if ( m_mesh_parameters ) {
|
|
delete m_mesh_parameters;
|
|
m_mesh_parameters = 0;
|
|
}
|
|
if ( src.m_mesh_parameters )
|
|
m_mesh_parameters = new ON_MeshParameters(*src.m_mesh_parameters);
|
|
|
|
m_invalid_count = src.m_invalid_count;
|
|
m_quad_count = src.m_quad_count;
|
|
m_triangle_count = src.m_triangle_count;
|
|
|
|
m_mesh_is_closed = src.m_mesh_is_closed;
|
|
m_mesh_is_manifold = src.m_mesh_is_manifold;
|
|
m_mesh_is_oriented = src.m_mesh_is_oriented;
|
|
m_mesh_is_solid = src.m_mesh_is_solid;
|
|
|
|
m_vertex_bbox = src.m_vertex_bbox;
|
|
m_tight_bbox_cache = src.m_tight_bbox_cache;
|
|
memcpy(m_nbox,src.m_nbox,sizeof(m_nbox));
|
|
memcpy(m_tbox,src.m_tbox,sizeof(m_tbox));
|
|
|
|
int i;
|
|
for ( i = 0; i < 4; i++ ) {
|
|
if ( m_kstat[i] )
|
|
{
|
|
delete m_kstat[i];
|
|
m_kstat[i] = 0;
|
|
}
|
|
if ( src.m_kstat[i] )
|
|
{
|
|
m_kstat[i] = new ON_MeshCurvatureStats(*src.m_kstat[i]);
|
|
}
|
|
}
|
|
|
|
// do not copy m_top
|
|
|
|
// Do not copy m_mesh_cache. Cached information will
|
|
// be recalculated if it is needed.
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
ON_Mesh::~ON_Mesh()
|
|
{
|
|
Destroy();
|
|
m_top.m_mesh = 0;
|
|
}
|
|
|
|
void ON_Mesh::MemoryRelocate()
|
|
{
|
|
// the back pointer on m_top needs to be updated.
|
|
m_top.m_mesh = this;
|
|
}
|
|
|
|
void ON_Mesh::Destroy()
|
|
{
|
|
PurgeUserData();
|
|
DestroyRuntimeCache( true );
|
|
m_Ttag.Default();
|
|
m_Ctag.Default();
|
|
m_dV.Destroy();
|
|
m_V.Destroy();
|
|
m_F.Destroy();
|
|
m_N.Destroy();
|
|
m_FN.Destroy();
|
|
m_T.Destroy();
|
|
m_TC.Destroy();
|
|
m_S.Destroy();
|
|
m_K.Destroy();
|
|
m_C.Destroy();
|
|
m_NgonMap.Destroy();
|
|
m_Ngon.Destroy();
|
|
m_NgonAllocator.DeallocateAllNgons();
|
|
m_vertex_bbox = ON_BoundingBox::UnsetBoundingBox;
|
|
m_tight_bbox_cache.RemoveAllBoundingBoxes();
|
|
}
|
|
|
|
void ON_Mesh::EmergencyDestroy()
|
|
{
|
|
DestroyRuntimeCache( false );
|
|
m_dV.EmergencyDestroy();
|
|
m_V.EmergencyDestroy();
|
|
m_F.EmergencyDestroy();
|
|
m_N.EmergencyDestroy();
|
|
m_FN.EmergencyDestroy();
|
|
m_T.EmergencyDestroy();
|
|
m_TC.EmergencyDestroy();
|
|
m_S.EmergencyDestroy();
|
|
m_K.EmergencyDestroy();
|
|
m_C.EmergencyDestroy();
|
|
m_NgonMap.EmergencyDestroy();
|
|
m_Ngon.EmergencyDestroy();
|
|
}
|
|
|
|
static bool ON_MeshIsNotValid(bool bSilentError)
|
|
{
|
|
return bSilentError ? false : ON_IsNotValid(); // good place for a breakpoint;
|
|
}
|
|
|
|
static unsigned int ON_MeshNgonIsNotValid(bool bSilentError)
|
|
{
|
|
if (!bSilentError)
|
|
ON_IsNotValid(); // good place for a breakpoint;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int compare2u(const void* a, const void*b)
|
|
{
|
|
unsigned int x = ((const unsigned int*)a)[0];
|
|
unsigned int y = ((const unsigned int*)b)[0];
|
|
if ( x < y)
|
|
return -1;
|
|
if ( x > y)
|
|
return 1;
|
|
x = ((const unsigned int*)a)[1];
|
|
y = ((const unsigned int*)b)[1];
|
|
if ( x < y)
|
|
return -1;
|
|
if ( x > y)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
unsigned int ON_MeshNgon::IsValid(
|
|
const ON_MeshNgon* ngon,
|
|
unsigned int ngon_index,
|
|
ON_TextLog* text_logx,
|
|
unsigned int mesh_vertex_count,
|
|
unsigned int mesh_face_count,
|
|
const ON_MeshFace* mesh_F,
|
|
ON_SimpleArray< unsigned int >& workspace_buffer
|
|
)
|
|
{
|
|
workspace_buffer.SetCount(0);
|
|
if ( nullptr == ngon )
|
|
return true; // null ngons are ok
|
|
|
|
const ON__INT_PTR lowbit = 1;
|
|
const ON__INT_PTR hightbits = ~lowbit;
|
|
bool bSilentError = ( 0 != (lowbit & ((ON__INT_PTR)text_logx)) );
|
|
ON_TextLog* text_log = (ON_TextLog*)(((ON__INT_PTR)text_logx) & hightbits);
|
|
|
|
if (ngon->m_Vcount < 3)
|
|
{
|
|
if ( text_log )
|
|
{
|
|
text_log->Print("ON_Mesh.Ngon(%u)->m_Vcount < 3.\n",ngon_index);
|
|
}
|
|
return ON_MeshNgonIsNotValid(bSilentError);
|
|
}
|
|
|
|
if (ngon->m_Fcount < 1)
|
|
{
|
|
if ( text_log )
|
|
{
|
|
text_log->Print("ON_Mesh.Ngon(%u)->m_Fcount < 1.\n",ngon_index);
|
|
}
|
|
return ON_MeshNgonIsNotValid(bSilentError);
|
|
}
|
|
|
|
if ( nullptr == ngon->m_vi )
|
|
{
|
|
if ( text_log )
|
|
{
|
|
text_log->Print("ON_Mesh.Ngon(%u)->m_vi is nullptr.\n",ngon_index);
|
|
}
|
|
return ON_MeshNgonIsNotValid(bSilentError);
|
|
}
|
|
|
|
if ( nullptr == ngon->m_fi )
|
|
{
|
|
if ( text_log )
|
|
{
|
|
text_log->Print("ON_Mesh.Ngon(%u)->m_fi is nullptr.\n",ngon_index);
|
|
}
|
|
return ON_MeshNgonIsNotValid(bSilentError);
|
|
}
|
|
|
|
for (unsigned int nvi = 0; nvi < ngon->m_Vcount; nvi++)
|
|
{
|
|
if (ngon->m_vi[nvi] >= mesh_vertex_count)
|
|
{
|
|
if ( text_log )
|
|
{
|
|
text_log->Print("ON_Mesh.Ngon(%u)->m_vi[%u] is invalid.\n",ngon_index,nvi);
|
|
}
|
|
return ON_MeshNgonIsNotValid(bSilentError);
|
|
}
|
|
}
|
|
|
|
for (unsigned int nfi = 0; nfi < ngon->m_Fcount; nfi++)
|
|
{
|
|
unsigned int fi = ngon->m_fi[nfi];
|
|
if (fi >= mesh_face_count)
|
|
{
|
|
if (text_log)
|
|
{
|
|
text_log->Print("ON_Mesh.Ngon(%u)->m_fi[%u] is invalid.\n", ngon_index, nfi);
|
|
}
|
|
return ON_MeshNgonIsNotValid(bSilentError);
|
|
}
|
|
}
|
|
|
|
if (nullptr == mesh_F)
|
|
{
|
|
// mesh faces are required to check the boundary
|
|
return true;
|
|
}
|
|
|
|
if (1 == ngon->m_Fcount && ngon->m_Vcount >= 3 && ngon->m_Vcount <= 4)
|
|
{
|
|
// fast test for a valid single face ngon
|
|
unsigned int fi = ngon->m_fi[0];
|
|
if (false == mesh_F[fi].IsValid(mesh_vertex_count))
|
|
{
|
|
if (text_log)
|
|
{
|
|
text_log->Print("ON_Mesh.Ngon(%u)->m_fi[0] is invalid.\n", ngon_index);
|
|
}
|
|
return ON_MeshNgonIsNotValid(bSilentError);
|
|
}
|
|
const unsigned int* fvi = (const unsigned int*)mesh_F[fi].vi;
|
|
for (unsigned int nvi = 0; nvi < ngon->m_Vcount; nvi++)
|
|
{
|
|
if (fvi[0] == ngon->m_vi[nvi] && fvi[1] == ngon->m_vi[(nvi+1)%ngon->m_Vcount] && fvi[2] == ngon->m_vi[(nvi+2)%ngon->m_Vcount])
|
|
{
|
|
if (3 == ngon->m_Vcount && fvi[3] == fvi[2])
|
|
return 3;
|
|
if (4 == ngon->m_Vcount && fvi[3] == ngon->m_vi[(nvi+3)%ngon->m_Vcount])
|
|
return 4;
|
|
}
|
|
}
|
|
// boundary may be reversed
|
|
}
|
|
|
|
unsigned int* workspace = workspace_buffer.Reserve(8*ngon->m_Fcount);
|
|
ON_2udex* edges = (ON_2udex*)workspace;
|
|
unsigned int edge_count = 0;
|
|
for (unsigned int nfi = 0; nfi < ngon->m_Fcount; nfi++)
|
|
{
|
|
unsigned int fi = ngon->m_fi[nfi];
|
|
|
|
const unsigned int* fvi = (const unsigned int*)(mesh_F[fi].vi);
|
|
unsigned int vi0;
|
|
unsigned int vi1 = fvi[3];
|
|
for (const unsigned int* fvi1 = fvi + 4; fvi < fvi1; fvi++)
|
|
{
|
|
vi0 = vi1;
|
|
vi1 = *fvi;
|
|
if (vi0 < vi1)
|
|
{
|
|
edges[edge_count].i = vi0;
|
|
edges[edge_count++].j = vi1;
|
|
}
|
|
else if (vi0 > vi1)
|
|
{
|
|
edges[edge_count].i = vi1;
|
|
edges[edge_count++].j = vi0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (edge_count < ngon->m_Vcount)
|
|
{
|
|
if (text_log)
|
|
{
|
|
text_log->Print("ON_Mesh.Ngon(%u) has invalid face or vertex list.\n",ngon_index);
|
|
}
|
|
return ON_MeshNgonIsNotValid(bSilentError);
|
|
}
|
|
|
|
ON_qsort(edges, edge_count, sizeof(edges[0]),compare2u);
|
|
|
|
unsigned int bdry_edge_count = 0;
|
|
for (unsigned int i = 0; i < edge_count; /*empty iterator*/)
|
|
{
|
|
const ON_2udex e0 = edges[i++];
|
|
if (i < edge_count && e0.i == edges[i].i && e0.j == edges[i].j)
|
|
{
|
|
// not a boundary edge
|
|
for (i++; i < edge_count; i++)
|
|
{
|
|
if (e0.i != edges[i].i || e0.j != edges[i].j)
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
edges[bdry_edge_count++] = e0;
|
|
}
|
|
}
|
|
|
|
if (bdry_edge_count < ngon->m_Vcount)
|
|
{
|
|
if (text_log)
|
|
{
|
|
text_log->Print("ON_Mesh.Ngon(%u) has invalid face or vertex list.\n",ngon_index);
|
|
}
|
|
return ON_MeshNgonIsNotValid(bSilentError);
|
|
}
|
|
|
|
ON_SortUnsignedIntArray( ON::sort_algorithm::quick_sort, workspace, 2*bdry_edge_count );
|
|
|
|
for (unsigned int nvi = 0; nvi < ngon->m_Vcount; nvi++)
|
|
{
|
|
if ( nullptr == ON_BinarySearchUnsignedIntArray( ngon->m_vi[nvi], workspace, 2*bdry_edge_count) )
|
|
{
|
|
if ( text_log )
|
|
{
|
|
text_log->Print("ON_Mesh.Ngon(%u)->m_vi[%u] is not a boundary vertex.\n",ngon_index,nvi);
|
|
}
|
|
return ON_MeshNgonIsNotValid(bSilentError);
|
|
}
|
|
}
|
|
|
|
// It is possible that the ngon is still not valid.
|
|
//
|
|
// If bdry_edge_count > ngon->m_Vcount, then there "could" be one or
|
|
// more inner "holes" and the ngon->m_vi[] could reference a vertex
|
|
// on an inner boundary.
|
|
//
|
|
// The vertices in ngon->m_v[] may not be a proper outer boundary.
|
|
//
|
|
// ...
|
|
|
|
|
|
return bdry_edge_count;
|
|
}
|
|
|
|
static void Internal_ON_Mesh_IsCorruptMessage(
|
|
bool bSilentError,
|
|
bool& bIsCorrupt,
|
|
ON_TextLog* text_log,
|
|
const wchar_t* corruption_description
|
|
)
|
|
{
|
|
if (false == bIsCorrupt)
|
|
{
|
|
if (false == bSilentError)
|
|
{
|
|
ON_ERROR("ON_Mesh data is corrupt.");
|
|
}
|
|
bIsCorrupt = true;
|
|
if (nullptr != text_log)
|
|
text_log->PrintString(corruption_description);
|
|
}
|
|
}
|
|
|
|
bool ON_Mesh::IsCorrupt(
|
|
bool bRepair,
|
|
bool bSilentError,
|
|
class ON_TextLog* text_log
|
|
) const
|
|
{
|
|
const unsigned int V_count = m_V.UnsignedCount();
|
|
unsigned int F_count = m_F.UnsignedCount();
|
|
|
|
bool bIsCorrupt = false;
|
|
|
|
// test faces first
|
|
for (unsigned int fi = 0; fi < F_count; fi++)
|
|
{
|
|
unsigned int* fvi = (unsigned int*)m_F[fi].vi;
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
if (fvi[j] >= V_count)
|
|
{
|
|
Internal_ON_Mesh_IsCorruptMessage(
|
|
bSilentError,
|
|
bIsCorrupt,
|
|
text_log,
|
|
L"ON_Mesh.m_F[] has out of range vertex indices.\n"
|
|
);
|
|
if (bRepair)
|
|
{
|
|
fvi[0] = V_count; // mark this corrupt face for deletion below
|
|
|
|
// deleting a face makes cached information invalid
|
|
// and the fact the face was corrupt makes any
|
|
// set values suspect.
|
|
const_cast<ON_Mesh*>(this)->m_invalid_count = 0;
|
|
const_cast<ON_Mesh*>(this)->m_quad_count = 0;
|
|
const_cast<ON_Mesh*>(this)->m_triangle_count = 0;
|
|
|
|
const_cast<ON_Mesh*>(this)->m_mesh_is_closed = 0;
|
|
const_cast<ON_Mesh*>(this)->m_mesh_is_manifold = 0;
|
|
const_cast<ON_Mesh*>(this)->m_mesh_is_oriented = 0;
|
|
const_cast<ON_Mesh*>(this)->m_mesh_is_solid = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bIsCorrupt && bRepair)
|
|
{
|
|
// remove corrupt faces
|
|
// N-gon indices reference faces that will get new indices
|
|
// or get deleted.
|
|
// If the creator couldn't get the vertex indices in a
|
|
// face set correctly, then they loose any N-gon work
|
|
// too that may or may not have been done correctly.
|
|
// (Harsh, I know).
|
|
const_cast<ON_Mesh*>(this)->RemoveAllNgons();
|
|
|
|
|
|
unsigned int new_F_count = 0;
|
|
ON_MeshFace* F = const_cast<ON_MeshFace*>(m_F.Array());
|
|
ON_3fVector* FN =
|
|
(F_count == m_FN.UnsignedCount())
|
|
? const_cast<ON_3fVector*>(m_FN.Array())
|
|
: nullptr;
|
|
if ( nullptr == FN)
|
|
const_cast<ON_Mesh*>(this)->m_F.SetCount(0);
|
|
|
|
for (unsigned int fi = 0; fi < F_count; fi++)
|
|
{
|
|
if (V_count ==(unsigned int)F[fi].vi[0])
|
|
continue; // corrupt face
|
|
|
|
F[new_F_count] = F[fi];
|
|
if (nullptr != FN)
|
|
FN[new_F_count] = FN[fi];
|
|
new_F_count++;
|
|
}
|
|
|
|
const_cast<ON_Mesh*>(this)->m_F.SetCount(new_F_count);
|
|
if (nullptr != FN)
|
|
const_cast<ON_Mesh*>(this)->m_FN.SetCount(new_F_count);
|
|
F_count = new_F_count;
|
|
}
|
|
|
|
if (0 != m_dV.UnsignedCount() && V_count != m_dV.UnsignedCount())
|
|
{
|
|
Internal_ON_Mesh_IsCorruptMessage(
|
|
bSilentError,
|
|
bIsCorrupt,
|
|
text_log,
|
|
L"ON_Mesh.m_dV[] has wrong size.\n"
|
|
);
|
|
if( bRepair )
|
|
const_cast<ON_Mesh*>(this)->m_dV.SetCount(0);
|
|
}
|
|
|
|
return bIsCorrupt;
|
|
}
|
|
|
|
unsigned int ON_MeshNgon::IsValid(
|
|
const ON_MeshNgon* ngon,
|
|
unsigned int ngon_index,
|
|
ON_TextLog* text_logx,
|
|
unsigned int mesh_vertex_count,
|
|
unsigned int mesh_face_count,
|
|
const ON_MeshFace* mesh_F
|
|
)
|
|
{
|
|
// If low bit of text_log pointer is 1, then ON_Error is not called when the ngon is invalid.
|
|
ON_SimpleArray< unsigned int > workspace_buffer;
|
|
return ON_MeshNgon::IsValid(ngon,ngon_index,text_logx,mesh_vertex_count,mesh_face_count,mesh_F,workspace_buffer);
|
|
}
|
|
|
|
bool ON_Mesh::IsValid( ON_TextLog* text_logx ) const
|
|
{
|
|
// If low bit of text_log pointer is 1, then ON_Error is not called when the mesh is invalid.
|
|
const ON__INT_PTR lowbit = 1;
|
|
const ON__INT_PTR hightbits = ~lowbit;
|
|
bool bSilentError = ( 0 != (lowbit & ((ON__INT_PTR)text_logx)) );
|
|
ON_TextLog* text_log = (ON_TextLog*)(((ON__INT_PTR)text_logx) & hightbits);
|
|
|
|
if (IsCorrupt(false, bSilentError, text_log))
|
|
return false;
|
|
|
|
const unsigned int facet_count = FaceUnsignedCount();
|
|
const unsigned int vertex_count = VertexUnsignedCount();
|
|
unsigned int fi, vi;
|
|
|
|
if (facet_count < 1)
|
|
{
|
|
if ( text_log )
|
|
{
|
|
text_log->Print("ON_Mesh.m_F.Count() < 1 (should be at least 1).\n");
|
|
}
|
|
return ON_MeshIsNotValid(bSilentError);
|
|
}
|
|
|
|
if ( vertex_count < 3 )
|
|
{
|
|
if ( text_log )
|
|
{
|
|
text_log->Print("ON_Mesh.m_V.Count() < 3 (should be at least 3).\n");
|
|
}
|
|
return ON_MeshIsNotValid(bSilentError);
|
|
}
|
|
|
|
|
|
if ( m_V.UnsignedCount() != vertex_count )
|
|
{
|
|
if ( text_log )
|
|
{
|
|
text_log->Print("ON_Mesh.m_V.Count() = %u (should be %u=vertex_count).\n",
|
|
m_V.UnsignedCount(),vertex_count);
|
|
}
|
|
return ON_MeshIsNotValid(bSilentError);
|
|
}
|
|
|
|
if ( m_dV.UnsignedCount() > 0 && m_dV.UnsignedCount() != vertex_count )
|
|
{
|
|
if ( text_log )
|
|
{
|
|
text_log->Print("ON_Mesh.m_dV.Count() = %u (should be 0 or %u=vertex_count).\n",
|
|
m_dV.UnsignedCount(),vertex_count);
|
|
}
|
|
return ON_MeshIsNotValid(bSilentError);
|
|
}
|
|
|
|
if ( 0 != m_dV.UnsignedCount() )
|
|
{
|
|
if (false == HasSynchronizedDoubleAndSinglePrecisionVertices())
|
|
{
|
|
if ( text_log )
|
|
{
|
|
text_log->Print("ON_Mesh.m_dV[] and m_V[] are not synchronized.\n",
|
|
m_dV.UnsignedCount(),vertex_count);
|
|
}
|
|
return ON_MeshIsNotValid(bSilentError);
|
|
}
|
|
}
|
|
|
|
if ( m_N.UnsignedCount() > 0 && m_N.UnsignedCount() != vertex_count )
|
|
{
|
|
if ( text_log )
|
|
{
|
|
text_log->Print("ON_Mesh.m_N.Count() = %u (should be 0 or %u=vertex_count).\n",
|
|
m_N.UnsignedCount(),vertex_count);
|
|
}
|
|
return ON_MeshIsNotValid(bSilentError);
|
|
}
|
|
|
|
if ( m_T.UnsignedCount() > 0 && m_T.UnsignedCount() != vertex_count )
|
|
{
|
|
if ( text_log )
|
|
{
|
|
text_log->Print("ON_Mesh.m_T.Count() = %u (should be 0 or %u=vertex_count).\n",
|
|
m_T.UnsignedCount(),vertex_count);
|
|
}
|
|
return ON_MeshIsNotValid(bSilentError);
|
|
}
|
|
|
|
if ( m_S.UnsignedCount() > 0 && m_S.UnsignedCount() != vertex_count )
|
|
{
|
|
if ( text_log )
|
|
{
|
|
text_log->Print("ON_Mesh.m_S.Count() = %u (should be 0 or %u=vertex_count).\n",
|
|
m_S.UnsignedCount(),vertex_count);
|
|
}
|
|
return ON_MeshIsNotValid(bSilentError);
|
|
}
|
|
|
|
if ( HasVertexNormals() )
|
|
{
|
|
float x;
|
|
for ( vi = 0; vi < vertex_count; vi++ ) {
|
|
x = m_N[vi][0]*m_N[vi][0] + m_N[vi][1]*m_N[vi][1] + m_N[vi][2]*m_N[vi][2];
|
|
if ( x < 0.985 || x > 1.015 )
|
|
{
|
|
if ( text_log )
|
|
{
|
|
text_log->Print("ON_Mesh.m_N[%u] is not a unit vector (length = %g).\n",vi,sqrt(x));
|
|
}
|
|
return ON_MeshIsNotValid(bSilentError);
|
|
}
|
|
}
|
|
}
|
|
|
|
int i;
|
|
for ( i = 0; i < 3; i++ )
|
|
{
|
|
const double float_max = 3.402823466e+38;
|
|
if ( m_vertex_bbox.IsNotEmpty() )
|
|
{
|
|
if (
|
|
fabs(m_vertex_bbox.m_min.MaximumCoordinate()) > float_max
|
|
|| fabs(m_vertex_bbox.m_max.MaximumCoordinate()) > float_max
|
|
)
|
|
{
|
|
// Greg Arden 9 May 2003. Fixes TRR#10604.
|
|
// Attempt to detect meshes with non-float-finite vertices by testing the bounding box.
|
|
if (text_log)
|
|
{
|
|
text_log->Print("ON_Mesh.m_fvertex_bbox is not finite. Check for invalid vertices\n");
|
|
}
|
|
return ON_MeshIsNotValid(bSilentError);
|
|
}
|
|
}
|
|
}
|
|
|
|
const ON_3dPoint* dV = 0;
|
|
while ( HasDoublePrecisionVertices() )
|
|
{
|
|
const unsigned int vertex_count_local = VertexUnsignedCount();
|
|
bool bValidDoubles = (vertex_count_local == m_dV.UnsignedCount());
|
|
if ( bValidDoubles )
|
|
dV = DoublePrecisionVertices().Array();
|
|
bool bValidFloats = (vertex_count_local == m_V.UnsignedCount());
|
|
bool bSynchronized = HasSynchronizedDoubleAndSinglePrecisionVertices();
|
|
if ( bSynchronized && bValidDoubles && bValidFloats )
|
|
break;
|
|
|
|
if ( !bSynchronized )
|
|
{
|
|
if ( text_log )
|
|
{
|
|
text_log->Print("Single and double precision vertices are not synchronized.\n");
|
|
}
|
|
return ON_MeshIsNotValid(bSilentError);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if ( 0 != dV )
|
|
{
|
|
for ( fi = 0; fi < facet_count; fi++ )
|
|
{
|
|
if ( !m_F[fi].IsValid( vertex_count, dV ) )
|
|
{
|
|
if ( text_log )
|
|
{
|
|
if ( !m_F[fi].IsValid( vertex_count) )
|
|
text_log->Print("ON_Mesh.m_F[%u].vi[] has invalid vertex indices.\n",fi);
|
|
else
|
|
text_log->Print("ON_Mesh.m_F[%u] has degenerate double precision vertex locations.\n",fi);
|
|
}
|
|
return ON_MeshIsNotValid(bSilentError);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//const ON_3fPoint* fV = m_V.Array();
|
|
for ( fi = 0; fi < facet_count; fi++ )
|
|
{
|
|
// This test was too harsh for float precision meshes
|
|
// with nearly degnerate faces after they are transformed
|
|
// by a transform with a reasonable sized translation
|
|
// component.
|
|
// See bug http://dev.mcneel.com/bugtrack/?q=87465
|
|
|
|
//if ( !m_F[fi].IsValid( vertex_count, fV ) )
|
|
//{
|
|
// if ( text_log )
|
|
// {
|
|
// if ( !m_F[fi].IsValid( vertex_count) )
|
|
// text_log->Print("ON_Mesh.m_F[%d].vi[] has invalid vertex indices.\n",fi);
|
|
// else
|
|
// text_log->Print("ON_Mesh.m_F[%d] has degenerate float precision vertex locations.\n",fi);
|
|
// }
|
|
// return ON_MeshIsNotValid(bSilentError);
|
|
//}
|
|
|
|
if ( !m_F[fi].IsValid( vertex_count ) )
|
|
{
|
|
if ( text_log )
|
|
text_log->Print("ON_Mesh.m_F[%u].vi[] has invalid vertex indices.\n",fi);
|
|
return ON_MeshIsNotValid(bSilentError);
|
|
}
|
|
}
|
|
}
|
|
|
|
const unsigned int ngon_count = m_Ngon.UnsignedCount();
|
|
if (ngon_count > 0)
|
|
{
|
|
ON_SimpleArray< unsigned int > workspace_buffer;
|
|
for (unsigned int ngon_index = 0; ngon_index < ngon_count; ngon_index++)
|
|
{
|
|
const ON_MeshNgon* ngon = m_Ngon[ngon_index];
|
|
if (nullptr == ngon)
|
|
continue; // valid - means the ngon was removed
|
|
if (0 == ON_MeshNgon::IsValid(ngon, ngon_index, text_logx, (unsigned int)vertex_count, (unsigned int)facet_count, m_F.Array(), workspace_buffer))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ON_Mesh::Dump( ON_TextLog& dump ) const
|
|
{
|
|
const int half_max = 8;
|
|
|
|
const int fcount = m_F.Count();
|
|
int i;
|
|
const int vcount = m_V.Count();
|
|
ON_3dPoint p, q;
|
|
|
|
bool bDoubles = vcount > 0
|
|
&& HasDoublePrecisionVertices()
|
|
&& HasSynchronizedDoubleAndSinglePrecisionVertices();
|
|
|
|
dump.Print("ON_Mesh: vertex count = %d facet count = %d\n", m_V.Count(), m_F.Count() );
|
|
dump.Print("double precision: %s\n",bDoubles?"true":"false");
|
|
dump.Print("vertex normals: %s\n",HasVertexNormals()?"true":"false");
|
|
dump.Print("face normals: %s\n",HasFaceNormals()?"true":"false");
|
|
dump.Print("n-gons: %s\n",HasNgons()?"true":"false");
|
|
dump.Print("srf parameters: %s\n",HasSurfaceParameters()?"true":"false");
|
|
dump.Print("tex coords: %s\n",HasTextureCoordinates()?"true":"false");
|
|
dump.Print("vertex kappa: %s\n",HasPrincipalCurvatures()?"true":"false");
|
|
dump.Print("vertex colors: %s\n",HasVertexColors()?"true":"false");
|
|
dump.Print("m_Ctag:\n"); dump.PushIndent(); m_Ctag.Dump(dump); dump.PopIndent();
|
|
dump.Print("m_packed_tex_rotate: %s\n",m_packed_tex_rotate?"true":"false");
|
|
dump.Print("m_packed_tex_domain: (%g,%g)x(%g,%g)\n",
|
|
m_packed_tex_domain[0][0],m_packed_tex_domain[0][1],
|
|
m_packed_tex_domain[1][0],m_packed_tex_domain[1][1]);
|
|
dump.Print("m_srf_domain: (%g,%g)x(%g,%g)\n",m_srf_domain[0][0],m_srf_domain[0][1],m_srf_domain[1][0],m_srf_domain[1][1]);
|
|
dump.Print("m_srf_scale: %g,%g\n",m_srf_scale[0],m_srf_scale[0]);
|
|
dump.Print("m_Ttag:\n"); dump.PushIndent(); m_Ttag.Dump(dump); dump.PopIndent();
|
|
|
|
dump.PushIndent();
|
|
|
|
dump.Print("%d mesh vertices:\n",m_V.Count());
|
|
{
|
|
dump.PushIndent();
|
|
const ON_3dPoint* D = 0;
|
|
if ( bDoubles )
|
|
{
|
|
D = DoublePrecisionVertices().Array();
|
|
}
|
|
for (i = 0; i < vcount; i++)
|
|
{
|
|
if ( i == half_max && 2*half_max < vcount )
|
|
{
|
|
dump.Print("...\n");
|
|
i = vcount-half_max;
|
|
}
|
|
else
|
|
{
|
|
p = m_V[i];
|
|
if ( 0 != D )
|
|
{
|
|
q = D[i];
|
|
dump.Print("m_V[%d] = (%.17g,%.17g,%.17g) D = (%.17g,%.17g,%.17g)\n",
|
|
i,
|
|
p.x,p.y,p.z,
|
|
q.x,q.y,q.z
|
|
);
|
|
}
|
|
else
|
|
{
|
|
dump.Print("m_V[%d] = (%g,%g,%g)\n",i,p.x,p.y,p.z);
|
|
}
|
|
}
|
|
}
|
|
dump.PopIndent();
|
|
}
|
|
|
|
if ( HasVertexNormals() )
|
|
{
|
|
dump.Print("%d mesh vertex normals:\n",m_N.Count());
|
|
{
|
|
dump.PushIndent();
|
|
for (i = 0; i < vcount; i++)
|
|
{
|
|
if ( i == half_max && 2*half_max < vcount )
|
|
{
|
|
dump.Print("...\n");
|
|
i = vcount-half_max;
|
|
}
|
|
else
|
|
{
|
|
p = m_N[i];
|
|
dump.Print("m_N[%d] = (%g,%g,%g)\n",i,p.x,p.y,p.z);
|
|
}
|
|
}
|
|
dump.PopIndent();
|
|
}
|
|
}
|
|
|
|
if ( HasTextureCoordinates() )
|
|
{
|
|
dump.Print("%d mesh vertex texture coordinates:\n",m_T.Count());
|
|
{
|
|
dump.PushIndent();
|
|
for (i = 0; i < vcount; i++)
|
|
{
|
|
if ( i == half_max && 2*half_max < vcount )
|
|
{
|
|
dump.Print("...\n");
|
|
i = vcount-half_max;
|
|
}
|
|
else
|
|
{
|
|
ON_2fPoint tp = m_T[i];
|
|
p.x = tp.x;
|
|
p.y = tp.y;
|
|
dump.Print("m_T[%d] = (%g,%g)\n",i,p.x,p.y);
|
|
}
|
|
}
|
|
dump.PopIndent();
|
|
}
|
|
}
|
|
|
|
|
|
if ( HasSurfaceParameters() )
|
|
{
|
|
dump.Print("%d mesh vertex surface parameters:\n",m_S.Count());
|
|
{
|
|
dump.PushIndent();
|
|
for (i = 0; i < vcount; i++)
|
|
{
|
|
if ( i == half_max && 2*half_max < vcount )
|
|
{
|
|
dump.Print("...\n");
|
|
i = vcount-half_max;
|
|
}
|
|
else
|
|
{
|
|
ON_2dPoint srfuv = m_S[i];
|
|
dump.Print("m_S[%d] = (%g,%g)\n",i,srfuv.x,srfuv.y);
|
|
}
|
|
}
|
|
dump.PopIndent();
|
|
}
|
|
}
|
|
|
|
dump.Print("%d mesh faces:\n",m_F.Count());
|
|
{
|
|
dump.PushIndent();
|
|
for (i = 0; i < fcount; i++)
|
|
{
|
|
if ( i == half_max && 2*half_max < fcount )
|
|
{
|
|
dump.Print("...\n");
|
|
i = fcount-half_max;
|
|
}
|
|
else if ( m_F[i].vi[2] == m_F[i].vi[3] )
|
|
dump.Print("m_F[%d].vi = (%d,%d,%d)\n",i,m_F[i].vi[0],m_F[i].vi[1],m_F[i].vi[2]);
|
|
else
|
|
dump.Print("m_F[%d].vi = (%d,%d,%d,%d)\n",i,m_F[i].vi[0],m_F[i].vi[1],m_F[i].vi[2],m_F[i].vi[3]);
|
|
}
|
|
dump.PopIndent();
|
|
}
|
|
|
|
if ( HasFaceNormals() )
|
|
{
|
|
dump.Print("%d mesh face normals:\n",m_FN.Count());
|
|
{
|
|
dump.PushIndent();
|
|
for (i = 0; i < fcount; i++)
|
|
{
|
|
if ( i == half_max && 2*half_max < fcount )
|
|
{
|
|
dump.Print("...\n");
|
|
i = fcount-half_max;
|
|
}
|
|
else
|
|
{
|
|
p = m_FN[i];
|
|
dump.Print("m_FN[%d] = (%g,%g,%g)\n",i,p.x,p.y,p.z);
|
|
}
|
|
}
|
|
dump.PopIndent();
|
|
}
|
|
}
|
|
|
|
if ( HasNgons() )
|
|
{
|
|
const ON_MeshFaceList mesh_face_list(this);
|
|
|
|
const int ngon_count = NgonCount();
|
|
dump.Print("%d mesh n-gons:\n",ngon_count);
|
|
{
|
|
dump.PushIndent();
|
|
for (i = 0; i < ngon_count; i++)
|
|
{
|
|
if ( i == half_max && 2*half_max < ngon_count )
|
|
{
|
|
dump.Print("...\n");
|
|
i = ngon_count-half_max;
|
|
}
|
|
else
|
|
{
|
|
const ON_MeshNgon* ngon = Ngon(i);
|
|
if ( 0 == ngon )
|
|
dump.Print("Ngon(%d) = null\n",i);
|
|
else
|
|
{
|
|
dump.Print("Ngon(%d): ",i);
|
|
ngon->Dump(dump);
|
|
const int bcount = (int)ngon->BoundaryEdgeCount(mesh_face_list);
|
|
const int outer_boundary_count = (int)ngon->OuterBoundaryEdgeCount();
|
|
const int inner_boundary_count = bcount - outer_boundary_count;
|
|
dump.Print(" boundary counts outer = %d, inner = %d", outer_boundary_count, inner_boundary_count);
|
|
dump.Print("\n");
|
|
}
|
|
}
|
|
}
|
|
dump.PopIndent();
|
|
}
|
|
}
|
|
|
|
|
|
dump.PopIndent();
|
|
}
|
|
|
|
|
|
bool ON_Mesh::WriteFaceArray( int vcount, int fcount, ON_BinaryArchive& file ) const
|
|
{
|
|
unsigned char cvi[4];
|
|
unsigned short svi[4];
|
|
const int* vi;
|
|
int i_size = 0;
|
|
if ( vcount < 256 ) {
|
|
i_size = 1; // unsigned chars
|
|
}
|
|
else if (vcount < 65536 ) {
|
|
i_size = 2; // unsigned shorts
|
|
}
|
|
else {
|
|
i_size = 4; // 4 byte ints
|
|
}
|
|
|
|
bool rc = file.WriteInt( i_size );
|
|
int i;
|
|
switch(i_size) {
|
|
case 1:
|
|
for ( i = 0; i < fcount && rc ; i++ ) {
|
|
vi = m_F[i].vi;
|
|
cvi[0] = (unsigned char)vi[0];
|
|
cvi[1] = (unsigned char)vi[1];
|
|
cvi[2] = (unsigned char)vi[2];
|
|
cvi[3] = (unsigned char)vi[3];
|
|
rc = file.WriteChar( 4, cvi );
|
|
}
|
|
break;
|
|
case 2:
|
|
for ( i = 0; i < fcount && rc ; i++ ) {
|
|
vi = m_F[i].vi;
|
|
svi[0] = (unsigned short)vi[0];
|
|
svi[1] = (unsigned short)vi[1];
|
|
svi[2] = (unsigned short)vi[2];
|
|
svi[3] = (unsigned short)vi[3];
|
|
rc = file.WriteShort( 4, svi );
|
|
}
|
|
break;
|
|
case 4:
|
|
for ( i = 0; i < fcount && rc ; i++ ) {
|
|
rc = file.WriteInt( 4, m_F[i].vi );
|
|
}
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Mesh::ReadFaceArray( int vcount, int fcount, ON_BinaryArchive& file )
|
|
{
|
|
unsigned char cvi[4];
|
|
unsigned short svi[4];
|
|
unsigned int* vi;
|
|
int i_size = 0;
|
|
|
|
if ( m_F.Capacity() < fcount )
|
|
m_F.SetCapacity(fcount);
|
|
bool rc = file.ReadInt( &i_size );
|
|
int i = 0;
|
|
switch(i_size) {
|
|
case 1:
|
|
for ( i = 0; i < fcount && rc ; i++ ) {
|
|
rc = file.ReadChar( 4, cvi );
|
|
vi = (unsigned int*)m_F[i].vi;
|
|
vi[0] = cvi[0];
|
|
vi[1] = cvi[1];
|
|
vi[2] = cvi[2];
|
|
vi[3] = cvi[3];
|
|
}
|
|
break;
|
|
case 2:
|
|
for ( i = 0; i < fcount && rc ; i++ ) {
|
|
rc = file.ReadShort( 4, svi );
|
|
vi = (unsigned int*)m_F[i].vi;
|
|
vi[0] = svi[0];
|
|
vi[1] = svi[1];
|
|
vi[2] = svi[2];
|
|
vi[3] = svi[3];
|
|
}
|
|
break;
|
|
case 4:
|
|
for ( i = 0; i < fcount && rc ; i++ ) {
|
|
rc = file.ReadInt( 4, m_F[i].vi );
|
|
}
|
|
break;
|
|
}
|
|
m_F.SetCount(i);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
bool ON_Mesh::Write_1( ON_BinaryArchive& file ) const
|
|
{
|
|
// ver 1.0 uncompressed format
|
|
|
|
bool rc = file.WriteArray( m_V );
|
|
if (rc) rc = file.WriteArray( m_N );
|
|
if (rc) rc = file.WriteArray( m_T );
|
|
if (rc) rc = file.WriteArray( m_K );
|
|
if (rc) rc = file.WriteArray( m_C );
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Mesh::Read_1( ON_BinaryArchive& file )
|
|
{
|
|
// common to all 1.x formats (uncompressed)
|
|
|
|
bool rc = file.ReadArray( m_V );
|
|
if (rc) rc = file.ReadArray( m_N );
|
|
if (rc) rc = file.ReadArray( m_T );
|
|
if (rc) rc = file.ReadArray( m_K );
|
|
if (rc) rc = file.ReadArray( m_C );
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Mesh::Write_2( int Vcount, ON_BinaryArchive& file ) const
|
|
{
|
|
// ver 2.0 compressed format
|
|
const ON::endian e = file.Endian();
|
|
|
|
bool rc = true;
|
|
|
|
if ( Vcount > m_V.Count() )
|
|
return false;
|
|
|
|
if ( Vcount > 0 )
|
|
{
|
|
const int Ncount = (m_V.Count() == m_N.Count()) ? Vcount : 0;
|
|
const int Tcount = (m_V.Count() == m_T.Count()) ? Vcount : 0;
|
|
const int Kcount = (m_V.Count() == m_K.Count()) ? Vcount : 0;
|
|
const int Ccount = (m_V.Count() == m_C.Count()) ? Vcount : 0;
|
|
|
|
if (e == ON::endian::big_endian)
|
|
{
|
|
// These calls temporarily put the m_V[], m_N[], m_T[], m_K[]
|
|
// and m_C[] arrays in little endian byte order because 3dm archives
|
|
// are always in little endian byte order.
|
|
//
|
|
// This code assumes sizeof(ON_Color)=4, sizeof(float)=4
|
|
// and sizeof(double)=8.
|
|
// If this is not the case, then changing the 4's and 8's below
|
|
// will not work. You will have to copy the mesh definition
|
|
// into temporary arrays of 4 byte floats/8 byte doubles
|
|
// and them compress the temporary arrays. If you do this,
|
|
// then remove the "restore" byte order calls below.
|
|
file.ToggleByteOrder( Vcount*3, 4, m_V.Array(), (void*)m_V.Array() );
|
|
file.ToggleByteOrder( Ncount*3, 4, m_N.Array(), (void*)m_N.Array() );
|
|
file.ToggleByteOrder( Tcount*2, 4, m_T.Array(), (void*)m_T.Array() );
|
|
file.ToggleByteOrder( Kcount*2, 8, m_K.Array(), (void*)m_K.Array() );
|
|
file.ToggleByteOrder( Ccount, 4, m_C.Array(), (void*)m_C.Array() );
|
|
}
|
|
if (rc) rc = file.WriteCompressedBuffer( Vcount*sizeof(ON_3fPoint), m_V.Array() );
|
|
if (rc) rc = file.WriteCompressedBuffer( Ncount*sizeof(ON_3fVector), m_N.Array() );
|
|
if (rc) rc = file.WriteCompressedBuffer( Tcount*sizeof(ON_2fPoint), m_T.Array() );
|
|
if (rc) rc = file.WriteCompressedBuffer( Kcount*sizeof(ON_SurfaceCurvature),m_K.Array() );
|
|
if (rc) rc = file.WriteCompressedBuffer( Ccount*sizeof(ON_Color), m_C.Array() );
|
|
if (e == ON::endian::big_endian)
|
|
{
|
|
// These calls restore the m_V[], m_N[], m_T[], m_K[] and m_C[] arrays
|
|
// to the correct big endian runtime byte order. This must be done even
|
|
// if rc is false.
|
|
file.ToggleByteOrder( Vcount*3, 4, m_V.Array(), (void*)m_V.Array() );
|
|
file.ToggleByteOrder( Ncount*3, 4, m_N.Array(), (void*)m_N.Array() );
|
|
file.ToggleByteOrder( Tcount*2, 4, m_T.Array(), (void*)m_T.Array() );
|
|
file.ToggleByteOrder( Kcount*2, 8, m_K.Array(), (void*)m_K.Array() );
|
|
file.ToggleByteOrder( Ccount, 4, m_C.Array(), (void*)m_C.Array() );
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Mesh::Read_2( int vcount, ON_BinaryArchive& file )
|
|
{
|
|
// common to all 2.x formats (compressed)
|
|
const ON::endian e = file.Endian();
|
|
|
|
bool rc = true;
|
|
|
|
|
|
if ( vcount > 0 )
|
|
{
|
|
size_t sz = 0;
|
|
bool bFailedCRC;
|
|
|
|
sz = 0;
|
|
if (rc) rc = file.ReadCompressedBufferSize( &sz );
|
|
if (rc && sz)
|
|
{
|
|
if ( sz == vcount*sizeof(m_V[0]) )
|
|
{
|
|
m_V.SetCapacity(vcount);
|
|
if (rc) rc = file.ReadCompressedBuffer( sz,m_V.Array(),&bFailedCRC);
|
|
if (rc) m_V.SetCount(vcount);
|
|
}
|
|
else
|
|
{
|
|
ON_ERROR("ON_Mesh::Read - compressed vertex point buffer size is wrong.");
|
|
rc = false; // buffer is wrong size
|
|
}
|
|
}
|
|
|
|
sz = 0;
|
|
if (rc) rc = file.ReadCompressedBufferSize( &sz );
|
|
if (rc && sz)
|
|
{
|
|
if ( sz == vcount*sizeof(m_N[0]) )
|
|
{
|
|
m_N.SetCapacity(vcount);
|
|
if (rc) rc = file.ReadCompressedBuffer( sz,m_N.Array(),&bFailedCRC );
|
|
if (rc) m_N.SetCount(vcount);
|
|
}
|
|
else
|
|
{
|
|
ON_ERROR("ON_Mesh::Read - compressed vertex normal buffer size is wrong.");
|
|
rc = false; // buffer is wrong size
|
|
}
|
|
}
|
|
|
|
sz = 0;
|
|
if (rc) rc = file.ReadCompressedBufferSize( &sz );
|
|
if (rc && sz)
|
|
{
|
|
if ( sz == vcount*sizeof(m_T[0]) )
|
|
{
|
|
m_T.SetCapacity(vcount);
|
|
if (rc) rc = file.ReadCompressedBuffer( sz,m_T.Array(),&bFailedCRC );
|
|
if (rc) m_T.SetCount(vcount);
|
|
}
|
|
else
|
|
{
|
|
ON_ERROR("ON_Mesh::Read - compressed texture coordinate buffer size is wrong.");
|
|
rc = false; // buffer is wrong size
|
|
}
|
|
}
|
|
|
|
sz = 0;
|
|
if (rc) rc = file.ReadCompressedBufferSize( &sz );
|
|
if (rc && sz)
|
|
{
|
|
if ( sz == vcount*sizeof(m_K[0]) )
|
|
{
|
|
m_K.SetCapacity(vcount);
|
|
if (rc) rc = file.ReadCompressedBuffer( sz,m_K.Array(),&bFailedCRC );
|
|
if (rc) m_K.SetCount(vcount);
|
|
}
|
|
else
|
|
{
|
|
ON_ERROR("ON_Mesh::Read - compressed vertex curvature buffer size is wrong.");
|
|
rc = false; // buffer is wrong size
|
|
}
|
|
}
|
|
|
|
sz = 0;
|
|
if (rc) rc = file.ReadCompressedBufferSize( &sz );
|
|
if (rc && sz)
|
|
{
|
|
if ( sz == vcount*sizeof(m_C[0]) )
|
|
{
|
|
m_C.SetCapacity(vcount);
|
|
if (rc) rc = file.ReadCompressedBuffer( sz,m_C.Array(),&bFailedCRC );
|
|
if (rc) m_C.SetCount(vcount);
|
|
}
|
|
else
|
|
{
|
|
ON_ERROR("ON_Mesh::Read - compressed vertex color buffer size is wrong.");
|
|
rc = false; // buffer is wrong size
|
|
}
|
|
}
|
|
|
|
if (e == ON::endian::big_endian)
|
|
{
|
|
// This code assumes sizeof(ON_Color)=4, sizeof(float)=4
|
|
// and sizeof(double)=8.
|
|
// If this is not the case, then changing the 4's and 8's below
|
|
// will not work. You will have to read the compressed
|
|
// information into temporary arrays of 4 byte floats/8 byte doubles
|
|
// and then convert those numbers to whatever is stored in the
|
|
// m_V[], m_N[], m_T[], m_K[] and m_C[] arrays/
|
|
file.ToggleByteOrder( m_V.Count()*3, 4, m_V.Array(), (void*)m_V.Array() );
|
|
file.ToggleByteOrder( m_N.Count()*3, 4, m_N.Array(), (void*)m_N.Array() );
|
|
file.ToggleByteOrder( m_T.Count()*2, 4, m_T.Array(), (void*)m_T.Array() );
|
|
file.ToggleByteOrder( m_K.Count()*2, 8, m_K.Array(), (void*)m_K.Array() );
|
|
file.ToggleByteOrder( m_C.Count()*3, 4, m_C.Array(), (void*)m_C.Array() );
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static
|
|
bool WriteMeshNgons( ON_BinaryArchive& file, const ON_SimpleArray<ON_MeshNgon*>& ngons )
|
|
{
|
|
unsigned int i, Vcount;
|
|
if ( !file.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,0) )
|
|
return false;
|
|
|
|
bool rc = false;
|
|
for(;;)
|
|
{
|
|
unsigned int ngon_count = ngons.UnsignedCount();
|
|
if (!file.WriteInt(ngon_count))
|
|
break;
|
|
|
|
for ( i = 0; i < ngon_count; i++)
|
|
{
|
|
const ON_MeshNgon* ngon = ngons[i];
|
|
Vcount = ( 0 != ngon && ngon->m_Vcount > 0 && ngon->m_Fcount > 0 && 0 != ngon->m_vi && 0 != ngon->m_fi )
|
|
? ngon->m_Vcount
|
|
: 0;
|
|
if ( !file.WriteInt(Vcount) )
|
|
break;
|
|
if (Vcount <= 0 )
|
|
continue;
|
|
if ( !file.WriteInt(ngon->m_Fcount) )
|
|
break;
|
|
if ( !file.WriteInt(Vcount,ngon->m_vi) )
|
|
break;
|
|
if ( !file.WriteInt(ngon->m_Fcount,ngon->m_fi) )
|
|
break;
|
|
}
|
|
|
|
if (i==ngon_count)
|
|
rc = true;
|
|
|
|
break;
|
|
}
|
|
|
|
if ( !file.EndWrite3dmChunk() )
|
|
rc = false;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static
|
|
bool ReadMeshNgons( ON_BinaryArchive& file, ON_MeshNgonAllocator& a, ON_SimpleArray<ON_MeshNgon*>& ngons )
|
|
{
|
|
ngons.SetCount(0);
|
|
|
|
int major_version = 0;
|
|
int minor_version = 0;
|
|
unsigned int i, Vcount, Fcount;
|
|
if ( !file.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version) )
|
|
return false;
|
|
|
|
bool rc = false;
|
|
for(;;)
|
|
{
|
|
unsigned int ngon_count = 0;
|
|
if (!file.ReadInt(&ngon_count))
|
|
break;
|
|
|
|
ngons.Reserve(ngon_count);
|
|
|
|
for ( i = 0; i < ngon_count; i++)
|
|
{
|
|
Vcount = 0;
|
|
if ( !file.ReadInt(&Vcount) )
|
|
break;
|
|
if ( Vcount <= 0 )
|
|
continue;
|
|
if ( !file.ReadInt(&Fcount) )
|
|
break;
|
|
ON_MeshNgon* ngon = a.AllocateNgon(Vcount,Fcount);
|
|
if ( !file.ReadInt(Vcount,ngon->m_vi) )
|
|
break;
|
|
if ( !file.ReadInt(ngon->m_Fcount,ngon->m_fi) )
|
|
break;
|
|
ngons.Append(ngon);
|
|
}
|
|
|
|
if (i == ngon_count)
|
|
rc = true;
|
|
|
|
break;
|
|
}
|
|
|
|
if ( !file.EndRead3dmChunk() )
|
|
rc = false;
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
static
|
|
bool WriteMeshDoublePrecisionVertices(
|
|
ON_BinaryArchive& file,
|
|
const ON_SimpleArray<ON_3dPoint>& dV
|
|
)
|
|
{
|
|
const int major_version = 1;
|
|
const int minor_version = 0;
|
|
if ( !file.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,major_version,minor_version) )
|
|
return false;
|
|
|
|
bool rc = false;
|
|
|
|
for (;;)
|
|
{
|
|
const unsigned int dVcount = dV.UnsignedCount();
|
|
if (!file.WriteInt(dVcount))
|
|
break;
|
|
if (0 == dVcount)
|
|
{
|
|
rc = true;
|
|
break;
|
|
}
|
|
|
|
const ON::endian e = file.Endian();
|
|
if (e == ON::endian::big_endian)
|
|
{
|
|
file.ToggleByteOrder(dVcount * 3, 8, dV.Array(), (void*)dV.Array());
|
|
}
|
|
if (!file.WriteCompressedBuffer(dVcount*sizeof(ON_3dPoint), dV.Array()))
|
|
break;
|
|
if (e == ON::endian::big_endian)
|
|
{
|
|
file.ToggleByteOrder(dVcount * 3, 8, dV.Array(), (void*)dV.Array());
|
|
}
|
|
|
|
rc = true;
|
|
break;
|
|
}
|
|
|
|
if ( !file.EndWrite3dmChunk() )
|
|
rc = false;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static
|
|
bool ReadMeshDoublePrecisionVertices(
|
|
ON_BinaryArchive& file,
|
|
ON_SimpleArray<ON_3dPoint>& dV
|
|
)
|
|
{
|
|
dV.SetCount(0);
|
|
|
|
int major_version = 0;
|
|
int minor_version = 0;
|
|
if ( !file.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version) )
|
|
return false;
|
|
|
|
bool rc = false;
|
|
for(;;)
|
|
{
|
|
if (1 != major_version)
|
|
break;
|
|
|
|
unsigned int dVcount = 0;
|
|
if (!file.ReadInt(&dVcount))
|
|
break;
|
|
|
|
if (0 == dVcount)
|
|
{
|
|
rc = true;
|
|
break;
|
|
}
|
|
|
|
size_t sz = 0;
|
|
if (!file.ReadCompressedBufferSize( &sz ))
|
|
break;
|
|
|
|
if (sz != ((size_t)dVcount)*sizeof(ON_3dPoint))
|
|
{
|
|
ON_ERROR("Compressed double precision vertex point buffer size is wrong.");
|
|
break;
|
|
}
|
|
|
|
dV.SetCapacity(dVcount);
|
|
dV.SetCount(dVcount);
|
|
ON_3dPoint* a = dV.Array();
|
|
if (nullptr == a || dVcount != dV.UnsignedCount())
|
|
{
|
|
ON_ERROR("Unable to allocate double precision vertex point array.");
|
|
break;
|
|
}
|
|
|
|
bool bFailedCRC = 0;
|
|
if (!file.ReadCompressedBuffer( sz, a, &bFailedCRC))
|
|
break;
|
|
|
|
rc = true;
|
|
break;
|
|
}
|
|
|
|
if (!rc)
|
|
dV.SetCount(0);
|
|
if ( !file.EndRead3dmChunk() )
|
|
rc = false;
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Mesh::Write( ON_BinaryArchive& file ) const
|
|
{
|
|
int i;
|
|
//const int major_version = 1; // uncompressed
|
|
//const int major_version = 2; // beta format (never used)
|
|
const int major_version = 3; // compressed
|
|
|
|
const int minor_version
|
|
= (file.Archive3dmVersion() >= 60)
|
|
//? 6 // ngons saved in V6 files as of mid November, 2013
|
|
//? 7 // double precision vertices no longer on user data.
|
|
? 8 // double precision vertex box
|
|
: 5;
|
|
|
|
bool rc = file.Write3dmChunkVersion(major_version,minor_version);
|
|
|
|
const unsigned int vcount = VertexUnsignedCount();
|
|
const unsigned int fcount = FaceUnsignedCount();
|
|
|
|
if (rc) rc = file.WriteInt( vcount );
|
|
if (rc) rc = file.WriteInt( fcount );
|
|
if (rc) rc = file.WriteInterval( m_packed_tex_domain[0] );
|
|
if (rc) rc = file.WriteInterval( m_packed_tex_domain[1] );
|
|
if (rc) rc = file.WriteInterval( m_srf_domain[0] );
|
|
if (rc) rc = file.WriteInterval( m_srf_domain[1] );
|
|
if (rc) rc = file.WriteDouble( 2, m_srf_scale );
|
|
|
|
// legacy float precision vertex bounding box
|
|
float fbbox[2][3] = { { 1.0f, 1.0f, 1.0f }, { -1.0f, -1.0f, -1.0f } };
|
|
if (m_vertex_bbox.IsNotEmpty())
|
|
{
|
|
fbbox[0][0] = ON_FloatFloor(m_vertex_bbox.m_min.x);
|
|
fbbox[0][1] = ON_FloatFloor(m_vertex_bbox.m_min.y);
|
|
fbbox[0][2] = ON_FloatFloor(m_vertex_bbox.m_min.z);
|
|
fbbox[1][0] = ON_FloatCeil(m_vertex_bbox.m_max.x);
|
|
fbbox[1][1] = ON_FloatCeil(m_vertex_bbox.m_max.y);
|
|
fbbox[1][2] = ON_FloatCeil(m_vertex_bbox.m_max.z);
|
|
}
|
|
if (rc) rc = file.WriteFloat( 6, &fbbox[0][0] );
|
|
|
|
if (rc) rc = file.WriteFloat( 6, &m_nbox[0][0] );
|
|
if (rc) rc = file.WriteFloat( 4, &m_tbox[0][0] );
|
|
|
|
// archive int value meaning: -1 = unknown 0 = mesh is not closed, 1 = mesh is closed
|
|
i = -1;
|
|
switch( m_mesh_is_closed )
|
|
{
|
|
case 0: // unset
|
|
i = -1;
|
|
break;
|
|
case 1: // closed
|
|
i = 1;
|
|
break;
|
|
case 2: // not closed
|
|
i = 0;
|
|
break;
|
|
}
|
|
if (rc) rc = file.WriteInt( i );
|
|
|
|
unsigned char b = m_mesh_parameters ? 1 : 0;
|
|
if (rc) rc = file.WriteChar(b);
|
|
if (rc && b) {
|
|
if (rc) rc = file.BeginWrite3dmChunk( TCODE_ANONYMOUS_CHUNK, 0 );
|
|
if (rc) {
|
|
rc = m_mesh_parameters->Write(file);
|
|
if ( !file.EndWrite3dmChunk() )
|
|
rc = false;
|
|
}
|
|
}
|
|
|
|
for ( i = 0; rc && i < 4; i++ ) {
|
|
b = m_kstat[i] ? 1 : 0;
|
|
rc = file.WriteChar(b);
|
|
if (b) {
|
|
rc = file.BeginWrite3dmChunk( TCODE_ANONYMOUS_CHUNK, 0 );
|
|
if (rc) {
|
|
rc = m_kstat[i]->Write(file);
|
|
if ( !file.EndWrite3dmChunk() )
|
|
rc = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rc) rc = WriteFaceArray( vcount, fcount, file );
|
|
|
|
if (rc) {
|
|
// major version is a hard coded 3
|
|
|
|
//if ( major_version == 1 )
|
|
// rc = Write_1(file);
|
|
//else if ( major_version == 3 )
|
|
rc = Write_2(vcount,file);
|
|
//else
|
|
// rc = false;
|
|
}
|
|
|
|
// added for minor version 1.2 and 3.2
|
|
i = m_packed_tex_rotate ? 1 : 0;
|
|
if (rc) rc = file.WriteInt( i );
|
|
|
|
// added for minor version 3.3
|
|
if (rc) rc = file.WriteUuid( m_Ttag.m_mapping_id );
|
|
|
|
// compressed m_S[]
|
|
if ( rc && vcount > 0U )
|
|
{
|
|
// Before 201011049 there was a bug that let m_S[] arrays
|
|
// with the wrong size get saved in files.
|
|
const unsigned int Scount = (vcount == m_S.UnsignedCount()) ? m_S.UnsignedCount() : 0;
|
|
const ON::endian e = file.Endian();
|
|
if (e == ON::endian::big_endian)
|
|
{
|
|
file.ToggleByteOrder( Scount*2, 8, m_S.Array(), (void*)m_S.Array() );
|
|
}
|
|
if (rc) rc = file.WriteCompressedBuffer( Scount*sizeof(ON_2dPoint),m_S.Array() );
|
|
if (e == ON::endian::big_endian)
|
|
{
|
|
file.ToggleByteOrder( Scount*2, 8, m_S.Array(), (void*)m_S.Array() );
|
|
}
|
|
}
|
|
|
|
// added for minor version 3.4
|
|
if (rc) rc = m_Ttag.Write(file);
|
|
|
|
// added for minor version 3.5
|
|
if (rc) rc = file.WriteChar( m_mesh_is_manifold );
|
|
if (rc) rc = file.WriteChar( m_mesh_is_oriented );
|
|
if (rc) rc = file.WriteChar( m_mesh_is_solid );
|
|
|
|
|
|
if ( rc && minor_version >= 6 )
|
|
{
|
|
// added n-gons version 3.6
|
|
bool bHasNgons = HasNgons();
|
|
rc = file.WriteBool(bHasNgons);
|
|
if (rc && bHasNgons)
|
|
rc = WriteMeshNgons(file,m_Ngon);
|
|
|
|
if (rc && minor_version >= 7)
|
|
{
|
|
// added explicit double precision vertices chunk version 3.7
|
|
// (used to be on user data)
|
|
const bool bHasDoublePrecisionVertices = HasDoublePrecisionVertices();
|
|
if (rc) rc = file.WriteBool(bHasDoublePrecisionVertices);
|
|
if (rc && bHasDoublePrecisionVertices)
|
|
rc = WriteMeshDoublePrecisionVertices(file, m_dV);
|
|
|
|
if (rc && minor_version >= 8)
|
|
{
|
|
rc = file.WriteBoundingBox(m_vertex_bbox);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (rc
|
|
&& 50 == file.Archive3dmVersion()
|
|
&& vcount > 0
|
|
&& HasSynchronizedDoubleAndSinglePrecisionVertices()
|
|
)
|
|
{
|
|
// save double precision vertices using V5 user data.
|
|
ON_V5_MeshDoubleVertices* ud = ON_V5_MeshDoubleVertices::AttachV5(this);
|
|
if (nullptr != ud)
|
|
{
|
|
ud->m_V5_dV = m_dV;
|
|
ud->m_dcount = ud->m_V5_dV.UnsignedCount();
|
|
ud->m_dCRC = ud->DoubleCRC();
|
|
ud->m_fcount = m_V.UnsignedCount();
|
|
ud->m_fCRC = ON_V5_MeshDoubleVertices::FloatCRC(m_V);
|
|
// This obsolete user data will be deleted after it is written to the V5 .3dm file.
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
//// This id was used in the ON_Mesh::m_mapping_id
|
|
//// field to indicate the texture coordinates are the
|
|
//// canonical ON_Mesh uv texture coordinates by the
|
|
//// OpenNURBS parameteric surface meshers like
|
|
//// ON_Surface::CreateMesh() and ON_Brep::CreateMesh().
|
|
//
|
|
//// {B988A6C2-61A6-45a7-AAEE-9AED7EF4E316}
|
|
static const ON_UUID obsolete_default_srfp_mapping_id = { 0xb988a6c2, 0x61a6, 0x45a7, { 0xaa, 0xee, 0x9a, 0xed, 0x7e, 0xf4, 0xe3, 0x16 } };
|
|
|
|
bool ON_TextureMapping::Internal_WriteV5(
|
|
ON_BinaryArchive& file
|
|
) const
|
|
{
|
|
bool rc = file.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,1);
|
|
if (rc)
|
|
{
|
|
|
|
for(;;)
|
|
{
|
|
rc = file.WriteUuid( Id() );
|
|
if (!rc) break;
|
|
|
|
rc = file.WriteInt( static_cast<unsigned int>(m_type) );
|
|
if (!rc) break;
|
|
|
|
rc = file.WriteInt( static_cast<unsigned int>(m_projection) );
|
|
if (!rc) break;
|
|
|
|
rc = file.WriteXform( m_Pxyz );
|
|
if (!rc) break;
|
|
|
|
// Do not write m_Nxyz - it is calculated from m_Pxyz.
|
|
rc = file.WriteXform( m_uvw );
|
|
if (!rc) break;
|
|
|
|
rc = file.WriteString(Name());
|
|
if (!rc) break;
|
|
|
|
rc = file.WriteObject(m_mapping_primitive.get());
|
|
if (!rc) break;
|
|
|
|
// 13 October 2006 ver 1.1 fields
|
|
rc = file.WriteInt( static_cast<unsigned int>(m_texture_space) );
|
|
if (!rc) break;
|
|
|
|
rc = file.WriteBool(m_bCapped);
|
|
if (!rc) break;
|
|
|
|
break;
|
|
}
|
|
|
|
if ( !file.EndWrite3dmChunk() )
|
|
rc = false;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool ON_TextureMapping::Internal_ReadV5(
|
|
ON_BinaryArchive& file
|
|
)
|
|
{
|
|
*this = ON_TextureMapping::Unset;
|
|
|
|
int major_version = 0;
|
|
int minor_version = 0;
|
|
|
|
bool rc = file.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version);
|
|
if (rc)
|
|
{
|
|
if ( 1 == major_version )
|
|
{
|
|
// DO NOT SAVE m_mapping_index in archive.
|
|
// 1.0 fields
|
|
for(;;)
|
|
{
|
|
ON_UUID id = ON_nil_uuid;
|
|
rc = file.ReadUuid( id );
|
|
if (!rc) break;
|
|
if (id == ON_nil_uuid || id == ON_TextureMapping::SurfaceParameterTextureMapping.Id())
|
|
{
|
|
// nil ids are no longer permitted and
|
|
// system component ids can not be used by
|
|
// ordinary components.
|
|
id = ON_CreateId();
|
|
}
|
|
SetId(id);
|
|
|
|
unsigned int type_as_unsigned = 0;
|
|
rc = file.ReadInt( &type_as_unsigned );
|
|
if (!rc) break;
|
|
m_type = TypeFromUnsigned(type_as_unsigned);
|
|
|
|
unsigned int projection_as_unsigned = 0;
|
|
rc = file.ReadInt( &projection_as_unsigned );
|
|
if (!rc) break;
|
|
m_projection = ProjectionFromUnsigned(projection_as_unsigned);
|
|
|
|
rc = file.ReadXform( m_Pxyz );
|
|
if (!rc) break;
|
|
|
|
m_Pxyz.GetSurfaceNormalXform(m_Nxyz);
|
|
|
|
rc = file.ReadXform( m_uvw );
|
|
if (!rc) break;
|
|
|
|
ON_wString name;
|
|
rc = file.ReadString(name);
|
|
if (!rc) break;
|
|
SetName(name);
|
|
|
|
ON_Object* pMappingPrimitive = nullptr;
|
|
rc = (file.ReadObject(&pMappingPrimitive) >= 0);
|
|
if (!rc) break;
|
|
|
|
m_mapping_primitive.reset(pMappingPrimitive);
|
|
|
|
if ( minor_version >= 1 )
|
|
{
|
|
unsigned int texture_space_as_unsigned = 0;
|
|
rc = file.ReadInt(&texture_space_as_unsigned);
|
|
if (!rc) break;
|
|
m_texture_space = TextureSpaceFromUnsigned(texture_space_as_unsigned);
|
|
|
|
rc = file.ReadBool(&m_bCapped);
|
|
if (!rc) break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !file.EndRead3dmChunk() )
|
|
rc = false;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool ON_TextureMapping::Write(
|
|
ON_BinaryArchive& archive
|
|
) const
|
|
{
|
|
return Internal_WriteV5(archive);
|
|
}
|
|
|
|
bool ON_TextureMapping::Read(
|
|
ON_BinaryArchive& archive
|
|
)
|
|
{
|
|
return Internal_ReadV5(archive);
|
|
}
|
|
|
|
|
|
static
|
|
void GetSurfaceParametersHelper( const ON_Mesh& mesh,
|
|
double tex_x, double tex_y,
|
|
double* srf_s, double* srf_t )
|
|
{
|
|
// convert texture coordinates to surface parameters
|
|
// Used to reconstruct m_S[] when old files are read.
|
|
double tex_s, tex_t;
|
|
|
|
if ( mesh.m_packed_tex_rotate )
|
|
{
|
|
// undo rotation and normalize
|
|
tex_s = mesh.m_packed_tex_domain[1].NormalizedParameterAt( tex_y );
|
|
tex_t = 1.0 - mesh.m_packed_tex_domain[0].NormalizedParameterAt( tex_x );
|
|
}
|
|
else
|
|
{
|
|
// normalize
|
|
tex_s = mesh.m_packed_tex_domain[0].NormalizedParameterAt( tex_x );
|
|
tex_t = mesh.m_packed_tex_domain[1].NormalizedParameterAt( tex_y );
|
|
}
|
|
*srf_s = mesh.m_srf_domain[0].ParameterAt(tex_s);
|
|
*srf_t = mesh.m_srf_domain[1].ParameterAt(tex_t);
|
|
}
|
|
|
|
|
|
bool ON_Mesh::Read( ON_BinaryArchive& file )
|
|
{
|
|
Destroy();
|
|
|
|
int major_version = 0;
|
|
int minor_version = 0;
|
|
int i;
|
|
bool rc = file.Read3dmChunkVersion(&major_version,&minor_version);
|
|
|
|
if (rc && (1 == major_version || 3 == major_version) )
|
|
{
|
|
int vcount = 0;
|
|
int fcount = 0;
|
|
|
|
if (rc) rc = file.ReadInt( &vcount );
|
|
if (rc) rc = file.ReadInt( &fcount );
|
|
if (rc) rc = file.ReadInterval( m_packed_tex_domain[0] );
|
|
if (rc) rc = file.ReadInterval( m_packed_tex_domain[1] );
|
|
if (rc) rc = file.ReadInterval( m_srf_domain[0] );
|
|
if (rc) rc = file.ReadInterval( m_srf_domain[1] );
|
|
if (rc) rc = file.ReadDouble( 2, m_srf_scale );
|
|
|
|
float fbbox[2][3] = { { 1.0f, 1.0f, 1.0f }, { -1.0f, -1.0f, -1.0f } };
|
|
if (rc) rc = file.ReadFloat( 6, &fbbox[0][0] );
|
|
if (rc && fbbox[0][0] <= fbbox[1][0] && fbbox[0][1] <= fbbox[1][1] && fbbox[0][2] <= fbbox[1][2] )
|
|
{
|
|
// When minor_version >= 8, m_vertex_bbox will be read from the archive later.
|
|
m_vertex_bbox.m_min = ON_3fPoint(fbbox[0]);
|
|
m_vertex_bbox.m_max = ON_3fPoint(fbbox[1]);
|
|
}
|
|
|
|
if (rc) rc = file.ReadFloat( 6, &m_nbox[0][0] );
|
|
if (rc) rc = file.ReadFloat( 4, &m_tbox[0][0] );
|
|
|
|
// int value meaning: -1 = unknown 0 = mesh is not closed, 1 = mesh is closed
|
|
i = -1;
|
|
if (rc) rc = file.ReadInt( &i );
|
|
if (rc)
|
|
{
|
|
switch(i)
|
|
{
|
|
case 0: // not closed;
|
|
SetClosed(0);
|
|
break;
|
|
case 1: // closed;
|
|
SetClosed(1);
|
|
break;
|
|
case 2: // 13 April 2010 Dale Lear - "2" value is obsolete but appears in old files
|
|
SetClosed(1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
unsigned char b = 0;
|
|
ON__UINT32 tcode=0;
|
|
ON__INT64 big_value=0;
|
|
if (rc) rc = file.ReadChar(&b);
|
|
if (rc && b)
|
|
{
|
|
// mesh parameters are in an anonymous chunk
|
|
rc = file.BeginRead3dmBigChunk(&tcode,&big_value);
|
|
if (rc)
|
|
{
|
|
if ( TCODE_ANONYMOUS_CHUNK == tcode )
|
|
{
|
|
m_mesh_parameters = new ON_MeshParameters();
|
|
rc = m_mesh_parameters->Read( file );
|
|
}
|
|
else
|
|
rc = false;
|
|
if (!file.EndRead3dmChunk())
|
|
rc = false;
|
|
}
|
|
}
|
|
|
|
for ( i = 0; rc && i < 4; i++ )
|
|
{
|
|
rc = file.ReadChar(&b);
|
|
if (rc && b)
|
|
{
|
|
// m_kstat[i] curvature stats are in an anonymous chunk
|
|
tcode = 0;
|
|
big_value = 0;
|
|
rc = file.BeginRead3dmBigChunk( &tcode, &big_value );
|
|
if (rc)
|
|
{
|
|
if ( TCODE_ANONYMOUS_CHUNK == tcode )
|
|
{
|
|
m_kstat[i] = new ON_MeshCurvatureStats();
|
|
rc = m_kstat[i]->Read(file);
|
|
}
|
|
else
|
|
rc = false;
|
|
if ( !file.EndRead3dmChunk() )
|
|
rc = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rc) rc = ReadFaceArray( vcount, fcount, file );
|
|
|
|
if (rc) {
|
|
if ( major_version==1) {
|
|
rc = Read_1(file);
|
|
}
|
|
else if ( major_version == 3 ) {
|
|
rc = Read_2(vcount,file);
|
|
}
|
|
else
|
|
rc = false;
|
|
}
|
|
|
|
if ( minor_version >= 2 )
|
|
{
|
|
int b_packed_tex_rotate = m_packed_tex_rotate;
|
|
if (rc) rc = file.ReadInt( &b_packed_tex_rotate );
|
|
m_packed_tex_rotate = b_packed_tex_rotate?true:false;
|
|
}
|
|
|
|
if ( 3 == major_version )
|
|
{
|
|
if ( minor_version >= 3 )
|
|
{
|
|
// added for minor version 3.3
|
|
if (rc) rc = file.ReadUuid( m_Ttag.m_mapping_id );
|
|
|
|
// compressed m_S[]
|
|
if ( rc && vcount > 0 )
|
|
{
|
|
size_t sz = 0;
|
|
bool bFailedCRC=false;
|
|
if (rc) rc = file.ReadCompressedBufferSize( &sz );
|
|
if (rc && sz)
|
|
{
|
|
if ( sz == vcount*sizeof(ON_2dPoint) )
|
|
{
|
|
m_S.SetCapacity(vcount);
|
|
if (rc) rc = file.ReadCompressedBuffer( sz, m_S.Array(), &bFailedCRC );
|
|
if (rc) m_S.SetCount(vcount);
|
|
if (ON::endian::big_endian == file.Endian())
|
|
{
|
|
file.ToggleByteOrder( m_S.Count()*2, 8, m_S.Array(), (void*)m_S.Array() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ON_ERROR("ON_Mesh::Read - surface parameter buffer size is wrong.");
|
|
if ( rc
|
|
&& file.ArchiveOpenNURBSVersion() <= 201011049
|
|
&& 0 == (sz % sizeof(ON_2dPoint))
|
|
&& sz >= sizeof(ON_2dPoint)
|
|
)
|
|
{
|
|
// Before 201011049 there was a bug that let m_S[] arrays with
|
|
// the wrong size get saved in files. There was also a bug in
|
|
// the Rhino .OBJ file reader that created meshes with m_S[]
|
|
// arrays that had the wrong size. The next 4 lines of code
|
|
// let us read the junk, discard it and then successfully read
|
|
// the rest of the file.
|
|
int Scount = (int)(sz / sizeof(ON_2dPoint));
|
|
m_S.SetCapacity(Scount);
|
|
rc = file.ReadCompressedBuffer( sz, m_S.Array(), &bFailedCRC );
|
|
m_S.Destroy();
|
|
}
|
|
else
|
|
{
|
|
rc = false; // buffer is wrong size
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( minor_version >= 4 && file.ArchiveOpenNURBSVersion() >= 200606010 )
|
|
{
|
|
if (rc) rc = m_Ttag.Read(file);
|
|
if ( minor_version >= 5 )
|
|
{
|
|
if (rc) rc = file.ReadChar( &m_mesh_is_manifold );
|
|
if (rc) rc = file.ReadChar( &m_mesh_is_oriented );
|
|
if (rc) rc = file.ReadChar( &m_mesh_is_solid );
|
|
|
|
if ( rc && minor_version >= 6 )
|
|
{
|
|
// added n-gons version 3.6
|
|
bool bHasNgons = false;
|
|
rc = file.ReadBool(&bHasNgons);
|
|
if (rc && bHasNgons)
|
|
rc = ReadMeshNgons(file, m_NgonAllocator, m_Ngon);
|
|
if (rc && minor_version >= 7)
|
|
{
|
|
bool bHasDoublePrecisionVertices = false;
|
|
rc = file.ReadBool(&bHasDoublePrecisionVertices);
|
|
bool bHasInvalidDoublePrecisionVertices = false;
|
|
if (bHasDoublePrecisionVertices)
|
|
{
|
|
// Added explicit double precision vertices chunk version 3.7
|
|
// (used to be on user data)
|
|
rc = ReadMeshDoublePrecisionVertices(file, m_dV);
|
|
if (rc && m_dV.UnsignedCount() == m_V.UnsignedCount())
|
|
{
|
|
// Validate m_dV[] - some files contain different values for m_V[] and m_dV[].
|
|
// When this happens, delete m_dV[].
|
|
const unsigned int dvcount = m_dV.UnsignedCount();
|
|
for (unsigned int vdex = 0; vdex < dvcount; vdex++)
|
|
{
|
|
const ON_3fPoint P(m_dV[vdex]);
|
|
const double m = fabs(m_V[vdex].MaximumCoordinate());
|
|
const double d = fabs((P - m_V[vdex]).MaximumCoordinate());
|
|
if (d <= m*1.0e-6)
|
|
continue;
|
|
bHasInvalidDoublePrecisionVertices = true;
|
|
m_dV.Destroy();
|
|
break;
|
|
}
|
|
}
|
|
if (rc && m_dV.UnsignedCount() == m_V.UnsignedCount() && minor_version <= 7)
|
|
{
|
|
m_vertex_bbox.Set(m_dV, false);
|
|
}
|
|
}
|
|
if (rc && minor_version >= 8)
|
|
{
|
|
rc = file.ReadBoundingBox(m_vertex_bbox);
|
|
}
|
|
if (bHasInvalidDoublePrecisionVertices)
|
|
{
|
|
m_vertex_bbox.Set(m_V, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( 0 == m_S.Count()
|
|
&& m_V.Count() > 0
|
|
&& HasTextureCoordinates()
|
|
&& m_srf_domain[0].IsIncreasing()
|
|
&& m_srf_domain[1].IsIncreasing()
|
|
&& m_packed_tex_domain[0].IsInterval()
|
|
&& m_packed_tex_domain[1].IsInterval()
|
|
&& 0 == m_Ttag.m_mapping_crc
|
|
&& ON_UuidIsNil(m_Ttag.m_mapping_id)
|
|
)
|
|
{
|
|
// This is a mesh from an old file - but there is enough
|
|
// information to calculate the m_S[] values from the
|
|
// m_T[] values.
|
|
m_S.SetCapacity(vcount);
|
|
m_S.SetCount(0);
|
|
ON_2dPoint sp;
|
|
ON_2fPoint tc;
|
|
for ( i = 0; i < vcount; i++)
|
|
{
|
|
tc = m_T[i];
|
|
sp.x = tc.x;
|
|
sp.y = tc.y;
|
|
GetSurfaceParametersHelper(*this,sp.x,sp.y,&sp.x,&sp.y);
|
|
m_S.Append(sp);
|
|
}
|
|
m_Ttag.SetDefaultSurfaceParameterMappingTag();
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
ON::object_type ON_Mesh::ObjectType() const
|
|
{
|
|
return ON::mesh_object;
|
|
}
|
|
|
|
int ON_Mesh::Dimension() const
|
|
{
|
|
return 3;
|
|
}
|
|
|
|
#if defined(ON_COMPILER_MSC)
|
|
// Disable the MSC /W4 warning
|
|
// C4189: 'breakpoint_here_for_bad_vbox' : local variable is initialized but not referenced
|
|
// on the line
|
|
// int breakpoint_here_for_bad_vbox = 0.
|
|
// The warning disable is here because MS is ignoring it
|
|
// if I put it inside of the function.
|
|
#pragma ON_PRAGMA_WARNING_PUSH
|
|
#pragma ON_PRAGMA_WARNING_DISABLE_MSC( 4189 )
|
|
#endif
|
|
|
|
float ON_FloatFloor(double x)
|
|
{
|
|
// If x is a NaN, you get what you deserve.
|
|
//
|
|
// If x is a finite valid double in the range -3.402823466e+38
|
|
// to +3.402823466e+38, then returned value of f is the largest
|
|
// float value that is mathematically less than or equal to the
|
|
// value of x.
|
|
//
|
|
// If x is not in the float range or x is a NaN, then you get
|
|
// what you deserve.
|
|
//
|
|
// ON_FLOAT_EPSILON = 1.192092896e-07 is the smallest number such that
|
|
// 1.0f + 1.192092896e-07f > 1.0f.
|
|
//
|
|
// If x < 0, then (1.0 + 0.5*1.192092896e-07)*x rounds x down so
|
|
// that converting the double precision mantissa cannot create a
|
|
// float value that is mathematically larger than the value of x.
|
|
//
|
|
// If x > 0, then (1.0 - 0.5*1.192092896e-07)*x rounds x down so
|
|
// that converting the double precision mantissa cannot create a
|
|
// float value that is mathematically larger than the value of x.
|
|
//
|
|
const double e = (x < 0.0) ? (1.0 + 0.5*ON_FLOAT_EPSILON) : (1.0 - 0.5*ON_FLOAT_EPSILON);
|
|
float f;
|
|
f = (float)(e*x);
|
|
return f;
|
|
}
|
|
|
|
float ON_FloatCeil(double x)
|
|
{
|
|
float f = (x != 0.0) ? (-ON_FloatFloor(-x)) : ((float)x);
|
|
return f;
|
|
}
|
|
|
|
bool ON_Mesh::GetBBox( // returns true if successful
|
|
double* boxmin, // minimum
|
|
double* boxmax, // maximum
|
|
bool bGrowBox
|
|
) const
|
|
{
|
|
bool rc = false;
|
|
const unsigned int facet_count = FaceCount();
|
|
const unsigned int vertex_count = VertexCount();
|
|
if ( facet_count >= 1 && vertex_count >= 3 )
|
|
{
|
|
rc = m_vertex_bbox.IsNotEmpty();
|
|
if ( false == rc )
|
|
{
|
|
if ( HasDoublePrecisionVertices() )
|
|
{
|
|
const ON_3dPointArray& dV = DoublePrecisionVertices();
|
|
if ( dV.UnsignedCount() == m_V.UnsignedCount() )
|
|
rc = m_vertex_bbox.Set(dV, false);
|
|
}
|
|
if (false == rc)
|
|
{
|
|
rc = m_vertex_bbox.Set(m_V, false);
|
|
}
|
|
}
|
|
|
|
if ( rc )
|
|
{
|
|
ON_BoundingBox vbox(m_vertex_bbox);
|
|
if ( bGrowBox )
|
|
{
|
|
vbox.Union( ON_BoundingBox(ON_3dPoint(boxmin),ON_3dPoint(boxmax)) );
|
|
}
|
|
boxmin[0] = vbox.m_min.x;
|
|
boxmin[1] = vbox.m_min.y;
|
|
boxmin[2] = vbox.m_min.z;
|
|
boxmax[0] = vbox.m_max.x;
|
|
boxmax[1] = vbox.m_max.y;
|
|
boxmax[2] = vbox.m_max.z;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
#if defined(ON_COMPILER_MSC)
|
|
#pragma ON_PRAGMA_WARNING_POP
|
|
#endif
|
|
|
|
bool ON_Mesh::IsDeformable() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool ON_Mesh::MakeDeformable()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool ON_Mesh::Transform(
|
|
const ON_Xform& xform
|
|
)
|
|
{
|
|
// Tansform user data before doing sanity checks in case rogue code
|
|
// damages m_V[], m_dV[] or other data members.
|
|
TransformUserData(xform);
|
|
DestroyTree();
|
|
|
|
const unsigned int vertex_count = VertexUnsignedCount();
|
|
|
|
const bool bIsValid_fV = (vertex_count == m_V.UnsignedCount());
|
|
if (false == bIsValid_fV)
|
|
m_V.SetCount(0);
|
|
|
|
bool bIsValid_dV = (vertex_count == m_dV.UnsignedCount());
|
|
|
|
// 2 Jan 2020 S. Baer (RH-54464)
|
|
// If the transform is moving the mesh into or out of "beyond single precision",
|
|
// set up double precision vertices in order to get our best possible precision
|
|
// after the transform.
|
|
if (false == bIsValid_dV)
|
|
{
|
|
ON_BoundingBox bbox = BoundingBox();
|
|
ON_BoundingBox transformed_bbox = bbox;
|
|
transformed_bbox.Transform(xform);
|
|
if (ON_BeyondSinglePrecision(bbox, nullptr) || ON_BeyondSinglePrecision(transformed_bbox, nullptr))
|
|
{
|
|
UpdateDoublePrecisionVertices();
|
|
bIsValid_dV = (vertex_count == m_dV.UnsignedCount());
|
|
}
|
|
}
|
|
|
|
|
|
if (false == bIsValid_dV)
|
|
m_dV.SetCount(0);
|
|
|
|
const bool bSyncheddV = bIsValid_fV && bIsValid_dV && HasSynchronizedDoubleAndSinglePrecisionVertices();
|
|
|
|
if (bIsValid_dV)
|
|
ON_TransformPointList(3, false, vertex_count, 3, &m_dV[0][0], xform);
|
|
|
|
double d = xform.Determinant();
|
|
bool rc = false;
|
|
if ( bSyncheddV )
|
|
{
|
|
// transforming the double precision vertices is the
|
|
// best way to set the floats.
|
|
UpdateSinglePrecisionVertices();
|
|
rc = true;
|
|
}
|
|
else if ( bIsValid_fV )
|
|
{
|
|
rc = ON_TransformPointList( 3, false, vertex_count, 3, &m_V[0][0], xform );
|
|
}
|
|
|
|
if ( rc )
|
|
{
|
|
m_Ctag.Transform(xform);
|
|
m_Ttag.Transform(xform);
|
|
int tci, tccnt = m_TC.Count();
|
|
for ( tci = 0; tci < tccnt; tci++ )
|
|
{
|
|
m_TC[tci].m_tag.Transform(xform);
|
|
}
|
|
}
|
|
|
|
if ( rc && 0.0 == d )
|
|
{
|
|
// mesh has been squashed to a plane (or worse)
|
|
if ( HasVertexNormals() )
|
|
{
|
|
ComputeFaceNormals();
|
|
ComputeVertexNormals();
|
|
}
|
|
else if ( HasFaceNormals() )
|
|
{
|
|
ComputeFaceNormals();
|
|
}
|
|
}
|
|
else if ( rc )
|
|
{
|
|
if ( HasVertexNormals() )
|
|
{
|
|
// See http://www.gignews.com/realtime020100.htm or these
|
|
// references.
|
|
//
|
|
// 1. Hanrahan, Pat,
|
|
// "A Survey of Ray-Surface Intersection Algorithms",
|
|
// chapter 3 in Andrew Glassner (editor),
|
|
// An Introduction to Ray Tracing,
|
|
// Academic Press Inc., London, 1989.
|
|
//
|
|
// 2. Turkowski, Ken,
|
|
// "Properties of Surface-Normal Transformations",
|
|
// in Andrew Glassner (editor),
|
|
// Graphics Gems, Academic Press, Inc.,
|
|
// pp. 539-547, 1990.
|
|
ON_Xform N_xform;
|
|
const double det = xform.GetSurfaceNormalXform(N_xform);
|
|
rc = ON_TransformVectorList( 3, vertex_count, 3, &m_N[0][0], N_xform )?true:false;
|
|
if ( det < 0.0 )
|
|
{
|
|
FlipVertexNormals();
|
|
}
|
|
UnitizeVertexNormals();
|
|
}
|
|
|
|
if ( rc && HasFaceNormals() )
|
|
{
|
|
ComputeFaceNormals();
|
|
}
|
|
}
|
|
|
|
if ( rc && HasPrincipalCurvatures() )
|
|
{
|
|
if ( fabs(fabs(d) - 1.0) > ON_SQRT_EPSILON )
|
|
{
|
|
// If it's a uniform scale, handle it, otherwise we can't do it.
|
|
double scale = xform.m_xform[0][0];
|
|
if ( 0.0 != scale && 0.0 != d
|
|
&& scale == xform.m_xform[1][1]
|
|
&& scale == xform.m_xform[2][2]
|
|
&& fabs(d - scale*scale*scale) <= d*ON_SQRT_EPSILON )
|
|
{
|
|
// uniform scale
|
|
const double ks = 1.0/scale;
|
|
ON_SurfaceCurvature* sc = m_K.Array();
|
|
int ki = m_K.Count();
|
|
while ( ki-- )
|
|
{
|
|
sc->k1 *= ks;
|
|
sc->k2 *= ks;
|
|
sc++;
|
|
}
|
|
|
|
// update curvature stats.
|
|
for ( int j = 0; j < 4; j++ )
|
|
{
|
|
if ( m_kstat[j] )
|
|
m_kstat[j]->Set( m_kstat[j]->m_style,m_K.Count(),m_K.Array(),m_N.Array() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ON_ERROR("ON_Mesh::Transform() cannot apply this transform to curvatures.\n");
|
|
rc = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
InvalidateVertexBoundingBox();
|
|
InvalidateVertexNormalBoundingBox();
|
|
if ( fabs(d) <= ON_ZERO_TOLERANCE )
|
|
DestroyTopology(); // transform may not be one-to-one on vertices
|
|
|
|
return rc;
|
|
}
|
|
|
|
void ON_Mesh::DestroyRuntimeCache( bool bDelete )
|
|
{
|
|
int i;
|
|
|
|
DestroyTree(bDelete);
|
|
|
|
if (bDelete )
|
|
{
|
|
DestroyPartition();
|
|
m_top.Destroy();
|
|
DeleteMeshParameters();
|
|
InvalidateCurvatureStats();
|
|
}
|
|
else
|
|
{
|
|
// do not free any memory
|
|
m_top.EmergencyDestroy();
|
|
}
|
|
|
|
InvalidateBoundingBoxes();
|
|
m_partition = 0;
|
|
m_mesh_parameters = 0;
|
|
m_top.m_mesh = this;
|
|
m_parent = 0;
|
|
//m_material_index = -1;
|
|
m_mesh_is_closed = 0;
|
|
m_mesh_is_manifold = 0;
|
|
m_mesh_is_oriented = 0;
|
|
m_mesh_is_solid = 0;
|
|
for ( i = 0; i < 4; i++ )
|
|
{
|
|
m_kstat[i] = 0;
|
|
}
|
|
}
|
|
|
|
bool ON_Mesh::SwapCoordinates(
|
|
int i, int j // indices of coords to swap
|
|
)
|
|
{
|
|
if ( i == j )
|
|
return true;
|
|
|
|
const unsigned int vertex_count = VertexUnsignedCount();
|
|
const bool bIsValid_fV = (vertex_count == m_V.UnsignedCount());
|
|
const bool bIsValid_dV = (vertex_count == m_dV.UnsignedCount());
|
|
|
|
bool rc = false;
|
|
if (bIsValid_dV && ON_SwapPointListCoordinates(vertex_count, 3, &m_dV[0][0], i, j))
|
|
rc = true;
|
|
|
|
if (bIsValid_fV && ON_SwapPointListCoordinates(vertex_count, 3, &m_V[0][0], i, j))
|
|
rc = true;
|
|
|
|
if ( rc && HasVertexNormals() )
|
|
{
|
|
rc = ON_SwapPointListCoordinates( vertex_count, 3, &m_N[0][0], i, j );
|
|
}
|
|
if ( rc )
|
|
{
|
|
float x;
|
|
if( m_vertex_bbox.IsNotEmpty())
|
|
m_vertex_bbox.SwapCoordinates(i, j);
|
|
|
|
if ( m_nbox[0][0] <= m_nbox[1][0] )
|
|
{
|
|
x = m_nbox[0][i]; m_nbox[0][i] = m_nbox[0][j]; m_nbox[0][j] = x;
|
|
x = m_nbox[1][i]; m_nbox[1][i] = m_nbox[1][j]; m_nbox[1][j] = x;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
void ON_Mesh::SetClosed(int b)
|
|
{
|
|
// 6 Novermber 2003 Dale Lear - let expert user set m_mesh_is_closed
|
|
char mesh_is_closed = 0;
|
|
switch(b)
|
|
{
|
|
case 0: // not closed - at least one boundary edge
|
|
mesh_is_closed = 2;
|
|
SetSolidOrientation(0);
|
|
break;
|
|
case 1: // all edges are shared
|
|
// DO NOT SET m_mesh_is_solid here.
|
|
// Meshes can be closed but not solid
|
|
mesh_is_closed = 1;
|
|
break;
|
|
case 2: // 31 April 2010 Dale Lear - 2 is obsolete - it's either 0 or 1 now.
|
|
mesh_is_closed = 1;
|
|
// DO NOT SET m_mesh_is_solid here.
|
|
// Meshes can be closed but not solid
|
|
break;
|
|
default:
|
|
mesh_is_closed = 0; // unset
|
|
break;
|
|
}
|
|
if ( 0 == mesh_is_closed || m_mesh_is_closed != mesh_is_closed )
|
|
{
|
|
m_mesh_is_closed = mesh_is_closed;
|
|
m_mesh_is_manifold = 0; // unset - will be reevaluated when needed
|
|
m_mesh_is_oriented = 0; // unset - will be reevaluated when needed
|
|
}
|
|
}
|
|
|
|
void ON_Mesh::SetSolidOrientation(int solid_orientation)
|
|
{
|
|
switch(solid_orientation)
|
|
{
|
|
case -1: // closed oriented manifold solid with inward face normals
|
|
SetClosed(1);
|
|
m_mesh_is_manifold = 1;
|
|
m_mesh_is_oriented = 1;
|
|
m_mesh_is_solid = 2;
|
|
break;
|
|
|
|
case 0: // not solid
|
|
m_mesh_is_solid = 3;
|
|
// DO NOT SET m_mesh_is_closed here.
|
|
// Meshes can be closed but not solid
|
|
break;
|
|
|
|
case 1: // closed oriented manifold solid with outward face normals
|
|
SetClosed(1);
|
|
m_mesh_is_manifold = 1;
|
|
m_mesh_is_oriented = 1;
|
|
m_mesh_is_solid = 1;
|
|
break;
|
|
|
|
default:
|
|
m_mesh_is_solid = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static
|
|
int ON_MeshIsManifold_CompareV( const void* a, const void* b )
|
|
{
|
|
return memcmp(a,b,sizeof(ON_3fPoint));
|
|
/*
|
|
float d;
|
|
const float* fa = (const float*)a;
|
|
const float* fb = (const float*)b;
|
|
if ( 0.0f == (d = (*fa++ - *fb++)) )
|
|
{
|
|
if ( 0.0f == (d = (*fa++ - *fb++)) )
|
|
{
|
|
if ( 0.0f == (d = (*fa++ - *fb++)) )
|
|
return 0;
|
|
}
|
|
}
|
|
return ( d < 0.0f ) ? -1 : 1;
|
|
*/
|
|
}
|
|
|
|
static
|
|
int ON_MeshGetVertexEdges_Compare2dex( const void* a, const void* b )
|
|
{
|
|
return ON_Compare2dex((const ON_2dex*)a,(const ON_2dex*)b);
|
|
}
|
|
|
|
static
|
|
int ON_MeshIsManifold_Compare3dex( const void* a, const void* b )
|
|
{
|
|
return ON_Compare3dex((const ON_3dex*)a,(const ON_3dex*)b);
|
|
}
|
|
|
|
//static
|
|
//int ON_MeshGetVertexEdges_CompareInt( const int* a, const int* b )
|
|
//{
|
|
// return (*a-*b);
|
|
//}
|
|
|
|
|
|
int ON_Mesh::GetVertexEdges(
|
|
int vertex_index_count,
|
|
const int* vertex_index,
|
|
bool bNoDuplicates,
|
|
ON_SimpleArray<ON_2dex>& edges
|
|
) const
|
|
{
|
|
// Get edges connected to vertices in vertex_index[] array.
|
|
const int edges_count0 = edges.Count();
|
|
|
|
const int mesh_vcount = m_V.Count();
|
|
|
|
//03/12/2007 TimH. The line below appears to be a typo. Using the following line works better.
|
|
//const int mesh_fcount = m_V.Count();
|
|
const int mesh_fcount = m_F.Count();
|
|
|
|
if ( vertex_index_count <= 0 || 0 == vertex_index
|
|
|| mesh_fcount <= 0 || mesh_vcount < 3 )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int vei, efi, fvi, ei, fi, j, n, vi;
|
|
const int* f_vi;
|
|
ON_2dex edge_ends;
|
|
const ON_MeshFace* f = m_F.Array();
|
|
|
|
if ( TopologyExists()
|
|
&& mesh_vcount == m_top.m_topv_map.Count()
|
|
&& m_top.m_tope.Count() > 0 )
|
|
{
|
|
// Topology looks good; use it to speed up the search.
|
|
const int* topv_map = m_top.m_topv_map;
|
|
const int top_vcount = m_top.m_topv.Count();
|
|
const int top_ecount = m_top.m_tope.Count();
|
|
int top_vi;
|
|
for ( n = 0; n < vertex_index_count; n++ )
|
|
{
|
|
vi = vertex_index[n];
|
|
if ( vi < 0 || vi >= mesh_vcount )
|
|
continue;
|
|
top_vi = topv_map[vi];
|
|
if ( top_vi < 0 || top_vi > top_vcount )
|
|
continue;
|
|
edge_ends.i = vi;
|
|
const ON_MeshTopologyVertex& v = m_top.m_topv[top_vi];
|
|
for ( vei = 0; vei < v.m_tope_count; vei++ )
|
|
{
|
|
ei = v.m_topei[vei];
|
|
if ( ei < 0 || ei >= top_ecount )
|
|
continue;
|
|
const ON_MeshTopologyEdge& e = m_top.m_tope[ei];
|
|
for ( efi = 0; efi < e.m_topf_count; efi++ )
|
|
{
|
|
fi = e.m_topfi[efi];
|
|
if ( fi < 0 || fi >= mesh_fcount )
|
|
continue;
|
|
f_vi = f[fi].vi;
|
|
for ( fvi = 0; fvi < 4; fvi++ )
|
|
{
|
|
if ( f_vi[fvi] == vi )
|
|
{
|
|
j = f_vi[(fvi+3)%4];
|
|
if ( j >= 0 && j < mesh_vcount && vi != j )
|
|
{
|
|
edge_ends.i = j;
|
|
edge_ends.j = vi;
|
|
edges.Append(edge_ends);
|
|
}
|
|
j = f_vi[ (2==fvi && f_vi[2]==f_vi[3]) ? 0 : ((fvi+1)%4) ];
|
|
if ( j >= 0 && j < mesh_vcount && vi != j )
|
|
{
|
|
edge_ends.i = vi;
|
|
edge_ends.j = j;
|
|
edges.Append(edge_ends);
|
|
}
|
|
break; // done with this face
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// slow-n-stupid search through all the faces
|
|
|
|
// Sort vertex_index[] array so we can use a quick
|
|
// binary search to see if a face is using one of
|
|
// the vertices in the list.
|
|
ON_Workspace ws;
|
|
for ( vi = 1; vi < vertex_index_count; vi++ )
|
|
{
|
|
if ( vertex_index[vi] < vertex_index[vi-1] )
|
|
{
|
|
// need to sort vertex_index[] array
|
|
int* tmp = ws.GetIntMemory(vertex_index_count);
|
|
memcpy(tmp,vertex_index,vertex_index_count*sizeof(tmp[0]));
|
|
ON_SortIntArray(ON::sort_algorithm::quick_sort,tmp,vertex_index_count);
|
|
vertex_index = tmp;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Find all the faces that use a vertex in the vertex_index[] array.
|
|
for ( fi = 0; fi < mesh_fcount; fi++ )
|
|
{
|
|
f_vi = f[fi].vi;
|
|
for ( fvi = 0; fvi < 4; fvi++ )
|
|
{
|
|
vi = f_vi[fvi];
|
|
if ( ON_BinarySearchIntArray(vi,vertex_index,vertex_index_count) )
|
|
{
|
|
// vi is in the vertex_index[] array. Add the edges
|
|
// of this face that begin and end at this vertex.
|
|
j = f_vi[(fvi+3)%4];
|
|
if ( j >= 0 && j < mesh_vcount && vi != j )
|
|
{
|
|
edge_ends.i = j;
|
|
edge_ends.j = vi;
|
|
edges.Append(edge_ends);
|
|
}
|
|
j = f_vi[ (2==fvi && f_vi[2]==f_vi[3]) ? 0 : ((fvi+1)%4) ];
|
|
if ( j >= 0 && j < mesh_vcount && vi != j )
|
|
{
|
|
edge_ends.i = vi;
|
|
edge_ends.j = j;
|
|
edges.Append(edge_ends);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bNoDuplicates && edges.Count() > edges_count0 )
|
|
{
|
|
for ( ei = edges_count0; ei < edges.Count(); ei++ )
|
|
{
|
|
edge_ends = edges[ei];
|
|
if ( edge_ends.i > edge_ends.j )
|
|
{
|
|
j = edge_ends.i; edge_ends.i = edge_ends.j; edge_ends.j = j;
|
|
edges[ei] = edge_ends;
|
|
}
|
|
}
|
|
ON_qsort( edges.Array() + edges_count0,
|
|
edges.Count() - edges_count0,
|
|
sizeof(edge_ends),
|
|
ON_MeshGetVertexEdges_Compare2dex);
|
|
edge_ends = edges[edges_count0];
|
|
for ( ei = j = edges_count0+1; ei < edges.Count(); ei++ )
|
|
{
|
|
if ( ON_Compare2dex(&edge_ends,&edges[ei]) )
|
|
{
|
|
edge_ends = edges[ei];
|
|
if ( j != ei )
|
|
edges[j] = edge_ends;
|
|
j++;
|
|
}
|
|
}
|
|
edges.SetCount(j);
|
|
}
|
|
|
|
return (edges.Count() - edges_count0);
|
|
}
|
|
|
|
int ON_Mesh::GetMeshEdges(
|
|
ON_SimpleArray<ON_2dex>& edges
|
|
) const
|
|
{
|
|
const int edges_count0 = edges.Count();
|
|
int fi, ei, j, fvi;
|
|
const int* f_vi;
|
|
const ON_MeshFace* f = m_F.Array();
|
|
const int mesh_vcount = m_V.Count();
|
|
const int mesh_fcount = m_F.Count();
|
|
edges.Reserve( edges_count0 + 4*mesh_fcount );
|
|
ON_2dex e;
|
|
|
|
// Find all the faces that use a vertex in the vertex_index[] array.
|
|
for ( fi = 0; fi < mesh_fcount; fi++ )
|
|
{
|
|
f_vi = f[fi].vi;
|
|
ei = f_vi[3];
|
|
for ( fvi = 0; fvi < 4; fvi++ )
|
|
{
|
|
e.i = ei;
|
|
ei = *f_vi++;
|
|
e.j = ei;
|
|
if ( e.i > e.j )
|
|
{
|
|
j = e.i; e.i = e.j; e.j = j;
|
|
}
|
|
if ( e.i != e.j && e.i >= 0 && e.j < mesh_vcount )
|
|
{
|
|
edges.Append(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if ( edges.Count() > edges_count0 )
|
|
{
|
|
ON_qsort( edges.Array() + edges_count0,
|
|
edges.Count() - edges_count0,
|
|
sizeof(e),
|
|
ON_MeshGetVertexEdges_Compare2dex);
|
|
e = edges[edges_count0];
|
|
for ( ei = j = edges_count0+1; ei < edges.Count(); ei++ )
|
|
{
|
|
if ( ON_Compare2dex(&e,&edges[ei]) )
|
|
{
|
|
e = edges[ei];
|
|
if ( j != ei )
|
|
edges[j] = e;
|
|
j++;
|
|
}
|
|
}
|
|
edges.SetCount(j);
|
|
}
|
|
|
|
return edges.Count() - edges_count0;
|
|
}
|
|
|
|
|
|
int ON_Mesh::SolidOrientation() const
|
|
{
|
|
|
|
if ( m_mesh_is_solid <= 0 || m_mesh_is_solid > 3 )
|
|
{
|
|
// NOTE: calling IsClosed() will set m_mesh_is_solid
|
|
// to 3 if mes is non-manifold
|
|
if ( IsSolid() )
|
|
{
|
|
}
|
|
}
|
|
|
|
switch(m_mesh_is_solid)
|
|
{
|
|
case 1:
|
|
return 1;
|
|
break;
|
|
|
|
case 2:
|
|
return -1;
|
|
break;
|
|
|
|
case 3:
|
|
return 0;
|
|
break;
|
|
}
|
|
|
|
return 0; // answer "no" if we don't know.
|
|
}
|
|
|
|
|
|
bool ON_Mesh::IsPointInside(
|
|
ON_3dPoint test_point,
|
|
double tolerance,
|
|
bool bStrictlyInside
|
|
) const
|
|
{
|
|
if ( IsSolid() )
|
|
{
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ON_Mesh::IsSolid() const
|
|
{
|
|
return ( IsClosed() && IsManifold() && IsOriented() );
|
|
}
|
|
|
|
|
|
bool ON_Mesh::IsManifold(
|
|
bool bTopologicalTest,
|
|
bool* pbIsOriented,
|
|
bool* pbHasBoundary
|
|
) const
|
|
{
|
|
bool bIsManifold = false;
|
|
if ( pbIsOriented )
|
|
*pbIsOriented = false;
|
|
if ( pbHasBoundary )
|
|
*pbHasBoundary = false;
|
|
const int vcount = m_V.Count();
|
|
const int fcount = m_F.Count();
|
|
if ( vcount > 0 && fcount > 0 )
|
|
{
|
|
ON_Workspace ws;
|
|
ON_3dex e;
|
|
int i, j, ecount;
|
|
const int* fvi;
|
|
ON_3fPoint v0;
|
|
const ON_3fPoint* v;
|
|
const ON_MeshFace* f;
|
|
int* vid = ws.GetIntMemory(vcount);
|
|
ON_3dex* edge = (ON_3dex*)ws.GetMemory(4*fcount*sizeof(*edge));
|
|
|
|
if ( bTopologicalTest )
|
|
{
|
|
// coincident vertices are assigned the same vertex id
|
|
ON_Sort(ON::sort_algorithm::quick_sort,vid,m_V.Array(),vcount,sizeof(m_V[0]),ON_MeshIsManifold_CompareV);
|
|
ecount = 0;
|
|
v = m_V.Array();
|
|
ecount = 0;
|
|
j = vcount;
|
|
for ( i = 0; i < vcount; i = j)
|
|
{
|
|
v0 = v[vid[i]];
|
|
vid[i] = ecount;
|
|
for ( j = i+1; j < vcount; j++ )
|
|
{
|
|
if ( ON_MeshIsManifold_CompareV(&v,v+vid[j]) )
|
|
{
|
|
ecount++;
|
|
break;
|
|
}
|
|
vid[j] = ecount;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// each vertex gets a unique id.
|
|
for ( i = 0; i < vcount; i++ )
|
|
vid[i] = i;
|
|
}
|
|
|
|
// build a list of edges
|
|
f = m_F.Array();
|
|
ecount = 0;
|
|
for ( i = 0; i < fcount; i++ )
|
|
{
|
|
fvi = (f++)->vi;
|
|
if ( fvi[0] >= 0 && fvi[0] < vcount
|
|
&& fvi[1] >= 0 && fvi[1] < vcount
|
|
&& fvi[2] >= 0 && fvi[2] < vcount
|
|
&& fvi[3] >= 0 && fvi[3] < vcount )
|
|
{
|
|
// loop unrolled for speed
|
|
j = ecount;
|
|
e.i = vid[fvi[0]]; e.j = vid[fvi[1]];
|
|
if ( 0 != (e.k = e.j - e.i) )
|
|
{
|
|
if ( e.k < 0 ) {e.k = e.i; e.i = e.j; e.j = e.k; e.k = 1;} else e.k = 0;
|
|
edge[ecount++] = e;
|
|
}
|
|
e.i = vid[fvi[1]]; e.j = vid[fvi[2]];
|
|
if ( 0 != (e.k = e.j - e.i) )
|
|
{
|
|
if ( e.k < 0 ) {e.k = e.i; e.i = e.j; e.j = e.k; e.k = 1;} else e.k = 0;
|
|
edge[ecount++] = e;
|
|
}
|
|
e.i = vid[fvi[2]]; e.j = vid[fvi[3]];
|
|
if ( 0 != (e.k = e.j - e.i) )
|
|
{
|
|
if ( e.k < 0 ) {e.k = e.i; e.i = e.j; e.j = e.k; e.k = 1;} else e.k = 0;
|
|
edge[ecount++] = e;
|
|
}
|
|
e.i = vid[fvi[3]]; e.j = vid[fvi[0]];
|
|
if ( 0 != (e.k = e.j - e.i) )
|
|
{
|
|
if ( e.k < 0 ) {e.k = e.i; e.i = e.j; e.j = e.k; e.k = 1;} else e.k = 0;
|
|
edge[ecount++] = e;
|
|
}
|
|
if ( ecount < j+3 )
|
|
ecount = j;
|
|
}
|
|
}
|
|
|
|
if ( ecount >= 4 )
|
|
{
|
|
bIsManifold = true;
|
|
bool bIsOriented = (pbIsOriented) ? true : false;
|
|
bool bHasBoundary = (pbHasBoundary) ? false : true;
|
|
ON_qsort(edge,ecount,sizeof(edge[0]),ON_MeshIsManifold_Compare3dex);
|
|
|
|
i = 0;
|
|
e = *edge;
|
|
while ( --ecount )
|
|
{
|
|
edge++;
|
|
if ( memcmp(&e,edge,2*sizeof(int)) )
|
|
{
|
|
if (!i)
|
|
bHasBoundary = true;
|
|
e = *edge;
|
|
i = 0;
|
|
}
|
|
else
|
|
{
|
|
if ( i++ )
|
|
{
|
|
bIsManifold = false;
|
|
break;
|
|
}
|
|
if ( e.k == edge->k )
|
|
bIsOriented = false;
|
|
}
|
|
}
|
|
|
|
if ( bIsManifold )
|
|
{
|
|
if ( pbIsOriented )
|
|
*pbIsOriented = bIsOriented;
|
|
if ( pbHasBoundary )
|
|
*pbHasBoundary = bHasBoundary;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bIsManifold;
|
|
}
|
|
|
|
static void ON_hsort_3udex(ON_3udex *e, size_t nel)
|
|
{
|
|
// dictionary sort e[]
|
|
size_t i_end, k, i, j;
|
|
ON_3udex e_tmp;
|
|
|
|
if (nel < 2) return;
|
|
k = nel >> 1;
|
|
i_end = nel - 1;
|
|
for (;;)
|
|
{
|
|
if (k)
|
|
{
|
|
--k;
|
|
e_tmp = e[k];
|
|
}
|
|
else
|
|
{
|
|
e_tmp = e[i_end];
|
|
e[i_end] = e[0];
|
|
if (!(--i_end))
|
|
{
|
|
e[0] = e_tmp;
|
|
break;
|
|
}
|
|
}
|
|
i = k;
|
|
j = (k << 1) + 1;
|
|
while (j <= i_end)
|
|
{
|
|
if (j < i_end && (e[j].i < e[j + 1].i || (e[j].i == e[j + 1].i && (e[j].j < e[j + 1].j || (e[j].j == e[j + 1].j && e[j].k < e[j + 1].k)))))
|
|
j++;
|
|
|
|
if (e_tmp.i < e[j].i || (e_tmp.i == e[j].i && (e_tmp.j < e[j].j || (e_tmp.j == e[j].j && e_tmp.k < e[j].k))))
|
|
{
|
|
e[i] = e[j];
|
|
i = j;
|
|
j = (j << 1) + 1;
|
|
}
|
|
else
|
|
j = i_end + 1;
|
|
}
|
|
e[i] = e_tmp;
|
|
}
|
|
}
|
|
|
|
static void ON_Mesh_SetClosedHelper(
|
|
bool bClosedOnly,
|
|
const ON_Mesh& mesh,
|
|
const char& m_mesh_is_manifold,
|
|
const char& m_mesh_is_oriented
|
|
)
|
|
{
|
|
// thread safe lazy evaluation for mesh's m_mesh_is_... flags
|
|
// Sets: m_mesh_is_closed.
|
|
// If bClosedOnly is false, also sets m_mesh_is_manifold and m_mesh_is_oriented
|
|
int is_closed = 0;
|
|
char is_manifold = 2;
|
|
char is_oriented = 2;
|
|
for (;;)
|
|
{
|
|
const unsigned int Vcount = mesh.m_V.UnsignedCount();
|
|
const unsigned int Fcount = mesh.m_F.UnsignedCount();
|
|
if ( Vcount < 3 || Fcount < 1 )
|
|
{
|
|
ON_ERROR("Mesh is not valid.");
|
|
break;
|
|
}
|
|
if ( bClosedOnly && (Vcount < 4 || Fcount < 4) )
|
|
{
|
|
// not closed - don't waste any more time.
|
|
break;
|
|
}
|
|
|
|
unsigned int i, j;
|
|
unsigned int Vidbuffer[256];
|
|
unsigned int* Vid = mesh.GetVertexLocationIds(
|
|
1,
|
|
(Vcount*sizeof(*Vid) <= sizeof(Vidbuffer) ? &Vidbuffer[0] : nullptr),
|
|
nullptr // unwanted Vindex[] values
|
|
);
|
|
if ( 0 == Vid )
|
|
{
|
|
ON_ERROR("Mesh has corrupt vertex information.");
|
|
bClosedOnly = false;
|
|
break;
|
|
}
|
|
|
|
// build an edge list where the "vertex" indices identify unique 3d locations
|
|
ON_3udex* E_list = (ON_3udex*)onmalloc(4 * Fcount*sizeof(E_list[0]));
|
|
ON_3udex E;
|
|
unsigned int Vid0;
|
|
const int* fvi;
|
|
unsigned int E_count = 0;
|
|
const ON_MeshFace* F = mesh.m_F.Array();
|
|
for ( j = 0; j < Fcount; j++ )
|
|
{
|
|
fvi = F[j].vi;
|
|
E.i = Vid[fvi[0]];
|
|
Vid0 = E.j = Vid[fvi[1]];
|
|
if ( E.i == E.j )
|
|
break;
|
|
if ( E.i > E.j )
|
|
{
|
|
i = E.i; E.i = E.j; E.j = i;
|
|
E.k = 1;
|
|
}
|
|
else
|
|
{
|
|
E.k = 0;
|
|
}
|
|
E_list[E_count++] = E;
|
|
|
|
E.i = Vid0;
|
|
Vid0 = E.j = Vid[fvi[2]];
|
|
if ( E.i == E.j )
|
|
break;
|
|
if ( E.i > E.j )
|
|
{
|
|
i = E.i; E.i = E.j; E.j = i;
|
|
E.k = 1;
|
|
}
|
|
else
|
|
{
|
|
E.k = 0;
|
|
}
|
|
E_list[E_count++] = E;
|
|
|
|
if ( fvi[2] != fvi[3] )
|
|
{
|
|
// quad
|
|
E.i = Vid0;
|
|
Vid0 = E.j = Vid[fvi[3]];
|
|
if ( E.i == E.j )
|
|
break;
|
|
if ( E.i > E.j )
|
|
{
|
|
i = E.i; E.i = E.j; E.j = i;
|
|
E.k = 1;
|
|
}
|
|
else
|
|
{
|
|
E.k = 0;
|
|
}
|
|
E_list[E_count++] = E;
|
|
}
|
|
|
|
E.i = Vid0;
|
|
E.j = Vid[fvi[0]];
|
|
if ( E.i == E.j )
|
|
break;
|
|
if ( E.i > E.j )
|
|
{
|
|
i = E.i; E.i = E.j; E.j = i;
|
|
E.k = 1;
|
|
}
|
|
else
|
|
{
|
|
E.k = 0;
|
|
}
|
|
E_list[E_count++] = E;
|
|
}
|
|
if ( Vid != &Vidbuffer[0] )
|
|
onfree(Vid);
|
|
|
|
if ( E_count < 3 || j != Fcount )
|
|
{
|
|
ON_ERROR("Mesh is corrupt or collapsed");
|
|
bClosedOnly = false;
|
|
break;
|
|
}
|
|
|
|
// sort the the edges
|
|
ON_hsort_3udex(E_list,E_count);
|
|
|
|
// Look for duplicate edges. If we find an edge with no duplicate,
|
|
// then the mesh is open. It is possible that degenerate meshes,
|
|
// like a flattened box, will be flagged as closed.
|
|
is_closed = (Fcount >= 4 && E_count >= 6) ? 1 : 0;
|
|
is_oriented = 1;
|
|
is_manifold = 1;
|
|
i = 0;
|
|
if ( !bClosedOnly || 1 == is_closed ) for ( i = 0; i < E_count; /*empty iterator*/ )
|
|
{
|
|
E = E_list[i];
|
|
if ( ++i >= E_count )
|
|
{
|
|
// boundary edge (and the last edge in our list)
|
|
is_closed = 0;
|
|
break;
|
|
}
|
|
|
|
if ( E.i != E_list[i].i || E.j != E_list[i].j )
|
|
{
|
|
// boundary edge
|
|
is_closed = 0;
|
|
if ( 2 == is_oriented && 2 == is_manifold )
|
|
{
|
|
bClosedOnly = false;
|
|
break;
|
|
}
|
|
if ( bClosedOnly )
|
|
break; // don't spend time with further testing
|
|
continue;
|
|
}
|
|
|
|
if ( E.k == E_list[i].k )
|
|
{
|
|
// opposite face normals along this edge - mesh is not oriented
|
|
is_oriented = 2;
|
|
}
|
|
|
|
if ( ++i >= E_count || E.i != E_list[i].i || E.j != E_list[i].j )
|
|
{
|
|
// two faces share this edge
|
|
continue;
|
|
}
|
|
|
|
// three or more faces share this edge - mesh is not oriented manifold
|
|
is_oriented = 2;
|
|
is_manifold = 2;
|
|
if ( 0 == is_closed )
|
|
{
|
|
bClosedOnly = false;
|
|
break;
|
|
}
|
|
|
|
// Check for more faces sharing this edge.
|
|
for ( i++; i < E_count; i++ )
|
|
{
|
|
if ( E.i != E_list[i].i || E.j != E_list[i].j )
|
|
{
|
|
// the edges E and Eid_list[i] are in different locations
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (E_count > 0 && i >= E_count)
|
|
{
|
|
// is_manifold and is_oriented are set correctly
|
|
bClosedOnly = false;
|
|
}
|
|
|
|
onfree(E_list);
|
|
|
|
break;
|
|
}
|
|
|
|
const_cast<ON_Mesh&>(mesh).SetClosed(is_closed);
|
|
if ( !bClosedOnly )
|
|
{
|
|
// is_manifold and is_oriented are set correctly
|
|
if ( 2 == is_manifold )
|
|
is_oriented = 2;
|
|
const_cast<char&>(m_mesh_is_manifold) = is_manifold;
|
|
const_cast<char&>(m_mesh_is_oriented) = is_oriented;
|
|
}
|
|
}
|
|
|
|
bool ON_Mesh::IsClosed() const
|
|
{
|
|
if ( m_mesh_is_closed <= 0 || m_mesh_is_closed > 2)
|
|
{
|
|
// thread safe lazy evaluation
|
|
ON_Mesh_SetClosedHelper( true, *this, m_mesh_is_manifold, m_mesh_is_oriented );
|
|
}
|
|
|
|
return (1 == m_mesh_is_closed);
|
|
}
|
|
|
|
bool ON_Mesh::IsManifold() const
|
|
{
|
|
if ( m_mesh_is_manifold <= 0 || m_mesh_is_manifold > 2 )
|
|
{
|
|
// thread safe lazy evaluation
|
|
ON_Mesh_SetClosedHelper( false, *this, m_mesh_is_manifold, m_mesh_is_oriented );
|
|
}
|
|
return (1 == m_mesh_is_manifold);
|
|
}
|
|
|
|
bool ON_Mesh::IsOriented() const
|
|
{
|
|
if ( m_mesh_is_oriented <= 0 || m_mesh_is_oriented > 2 )
|
|
{
|
|
// thread safe lazy evaluation
|
|
ON_Mesh_SetClosedHelper( false, *this, m_mesh_is_manifold, m_mesh_is_oriented );
|
|
}
|
|
return (1 == m_mesh_is_oriented);
|
|
}
|
|
|
|
|
|
/*
|
|
Returns
|
|
true if there are zero vertices and zero faces.
|
|
*/
|
|
bool ON_Mesh::IsEmpty() const
|
|
{
|
|
// Deep sigh.
|
|
// The "this == &ON_Mesh::Empty" check is to handle the case when
|
|
// confused developers const cast ON_Mesh::Empty and then modify it.
|
|
return (0 == VertexUnsignedCount() && 0 == FaceUnsignedCount()) || (this == &ON_Mesh::Empty);
|
|
}
|
|
|
|
bool ON_Mesh::IsNotEmpty() const
|
|
{
|
|
// Deep sigh.
|
|
// The "this != &ON_Mesh::Empty" check is to handle the case when
|
|
// confused developers const cast ON_Mesh::Empty and then modify it.
|
|
return ( VertexUnsignedCount() > 0 && FaceUnsignedCount() > 0 && this != &ON_Mesh::Empty);
|
|
}
|
|
|
|
bool ON_Mesh::SetVertex(
|
|
int vertex_index,
|
|
const ON_3dPoint& vertex_location
|
|
)
|
|
{
|
|
const unsigned int vertex_count = VertexUnsignedCount();
|
|
const bool rc = vertex_index >= 0 && ((unsigned int)vertex_index) <= vertex_count;
|
|
if ( rc )
|
|
{
|
|
const unsigned int i = (unsigned int)vertex_index;
|
|
if ( vertex_count == m_dV.UnsignedCount() )
|
|
{
|
|
if ( i < vertex_count )
|
|
m_dV[i] = vertex_location;
|
|
else
|
|
m_dV.Append(vertex_location);
|
|
}
|
|
if ( vertex_count == m_V.UnsignedCount() )
|
|
{
|
|
if ( i < vertex_count )
|
|
m_V[i] = vertex_location;
|
|
else
|
|
m_V.AppendNew() = ON_3fPoint(vertex_location);
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Mesh::SetVertex(
|
|
int vertex_index,
|
|
const ON_3fPoint& vertex_location
|
|
)
|
|
{
|
|
const unsigned int vertex_count = VertexUnsignedCount();
|
|
const bool rc = vertex_index >= 0 && ((unsigned int)vertex_index) <= vertex_count;
|
|
if ( rc )
|
|
{
|
|
const unsigned int i = (unsigned int)vertex_index;
|
|
if ( vertex_count == m_dV.UnsignedCount() )
|
|
{
|
|
if ( i < vertex_count )
|
|
m_dV[i] = vertex_location;
|
|
else
|
|
m_dV.Append(vertex_location);
|
|
}
|
|
if ( vertex_count == m_V.UnsignedCount() )
|
|
{
|
|
if ( i < vertex_count )
|
|
m_V[i] = vertex_location;
|
|
else
|
|
m_V.Append(vertex_location);
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Mesh::SetVertexNormal(
|
|
int vertex_index,
|
|
const ON_3dVector& normal
|
|
)
|
|
{
|
|
bool rc = false;
|
|
// use double precision for unitizing normal
|
|
ON_3dVector unit_vector = normal;
|
|
const bool bUnitVector = unit_vector.Unitize();
|
|
ON_3fVector v((float)unit_vector.x, (float)unit_vector.y, (float)unit_vector.z);
|
|
int normal_count = m_N.Count();
|
|
if ( vertex_index >= 0 ) {
|
|
if ( vertex_index < normal_count ) {
|
|
m_N[vertex_index] = v;
|
|
rc = bUnitVector;
|
|
}
|
|
else if ( vertex_index == normal_count ) {
|
|
m_N.Append(v);
|
|
rc = bUnitVector;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Mesh::SetVertexNormal(
|
|
int vertex_index,
|
|
const ON_3fVector& normal
|
|
)
|
|
{
|
|
ON_3dVector v(normal.x,normal.y,normal.z);
|
|
return SetVertexNormal(vertex_index,v);
|
|
}
|
|
|
|
bool ON_Mesh::SetTextureCoord(
|
|
int vertex_index,
|
|
double s, double t // texture coordinates
|
|
)
|
|
{
|
|
ON_2fPoint tc((float)s,(float)t);
|
|
bool rc = false;
|
|
int vertex_count = m_T.Count();
|
|
if ( vertex_index >= 0 ) {
|
|
if ( vertex_index < vertex_count ) {
|
|
m_T[vertex_index] = tc;
|
|
rc = true;
|
|
}
|
|
else if ( vertex_index == vertex_count ) {
|
|
m_T.Append(tc);
|
|
rc = true;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
unsigned int ON_Mesh::AppendDuplicateVertex(
|
|
unsigned int vertex_index
|
|
)
|
|
{
|
|
const unsigned int vertex_count = VertexUnsignedCount();
|
|
|
|
if ( vertex_index >= vertex_count )
|
|
return ON_UNSET_UINT_INDEX;
|
|
|
|
if (vertex_count == m_V.UnsignedCount())
|
|
{
|
|
ON_3fPoint V = m_V[vertex_index];
|
|
m_V.Append(V);
|
|
}
|
|
|
|
if ( vertex_count == m_dV.UnsignedCount())
|
|
{
|
|
ON_3dPoint D = m_dV[vertex_index];
|
|
m_dV.Append(D);
|
|
}
|
|
|
|
if ( vertex_count == m_N.UnsignedCount() )
|
|
{
|
|
ON_3fVector N = m_N[vertex_index];
|
|
m_N.Append(N);
|
|
}
|
|
|
|
if ( vertex_count == m_T.UnsignedCount() )
|
|
{
|
|
ON_2fPoint T = m_T[vertex_index];
|
|
m_T.Append(T);
|
|
}
|
|
|
|
if ( vertex_count == m_S.UnsignedCount() )
|
|
{
|
|
ON_2dPoint S = m_S[vertex_index];
|
|
m_S.Append(S);
|
|
}
|
|
|
|
if ( vertex_count == m_K.UnsignedCount() )
|
|
{
|
|
ON_SurfaceCurvature K = m_K[vertex_index];
|
|
m_K.Append(K);
|
|
}
|
|
|
|
if ( vertex_count == m_C.UnsignedCount() )
|
|
{
|
|
ON_Color C = m_C[vertex_index];
|
|
m_C.Append(C);
|
|
}
|
|
|
|
if ( vertex_count == m_H.UnsignedCount() )
|
|
{
|
|
bool H = m_H[vertex_index];
|
|
m_H.Append(H);
|
|
}
|
|
|
|
return vertex_count;
|
|
}
|
|
|
|
/*
|
|
Description:
|
|
Increases the capactiy of arrays to be at least the requested capacity.
|
|
Parameters:
|
|
new_vertex_capacity - [in]
|
|
desired capacity
|
|
Returns:
|
|
true if successful.
|
|
Remarks:
|
|
This function is useful if you are getting ready to add a known number
|
|
of vertices and want to increase the dynamic array capacities before
|
|
you begin adding vertices.
|
|
*/
|
|
bool ON_Mesh::ReserveVertexCapacity(
|
|
size_t new_vertex_capacity
|
|
)
|
|
{
|
|
const unsigned int vertex_count = VertexUnsignedCount();
|
|
if ( new_vertex_capacity <= (size_t)vertex_count )
|
|
return true;
|
|
|
|
if ( vertex_count == m_V.UnsignedCount() )
|
|
m_V.Reserve(new_vertex_capacity);
|
|
|
|
if ( vertex_count == m_dV.UnsignedCount() )
|
|
m_dV.Reserve(new_vertex_capacity);
|
|
|
|
if ( vertex_count == m_N.UnsignedCount() )
|
|
m_N.Reserve(new_vertex_capacity);
|
|
|
|
if ( vertex_count == m_T.UnsignedCount() )
|
|
m_T.Reserve(new_vertex_capacity);
|
|
|
|
if ( vertex_count == m_S.UnsignedCount() )
|
|
m_S.Reserve(new_vertex_capacity);
|
|
|
|
if ( vertex_count == m_K.UnsignedCount() )
|
|
m_K.Reserve(new_vertex_capacity);
|
|
|
|
if ( vertex_count == m_C.UnsignedCount() )
|
|
m_C.Reserve(new_vertex_capacity);
|
|
|
|
if ( vertex_count == m_H.UnsignedCount() )
|
|
m_H.Reserve(new_vertex_capacity);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool ON_Mesh::SetTriangle(
|
|
int face_index,
|
|
int a,int b ,int c // vertex indices
|
|
)
|
|
{
|
|
return SetQuad( face_index, a,b,c,c );
|
|
}
|
|
|
|
bool ON_Mesh::SetQuad(
|
|
int face_index,
|
|
int a, int b, int c, int d // vertex indices
|
|
)
|
|
{
|
|
bool rc = false;
|
|
int face_count = m_F.Count();
|
|
if ( face_index >= 0 ) {
|
|
ON_MeshFace f;
|
|
f.vi[0] = a;
|
|
f.vi[1] = b;
|
|
f.vi[2] = c;
|
|
f.vi[3] = d;
|
|
if ( face_index < face_count ) {
|
|
m_F[face_index] = f;
|
|
rc = true;
|
|
}
|
|
else if ( face_index == face_count ) {
|
|
m_F.Append(f);
|
|
rc = true;
|
|
}
|
|
if ( rc )
|
|
rc = f.IsValid(m_V.Count());
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
|
|
int ON_Mesh::FaceCount() const
|
|
{
|
|
return m_F.Count();
|
|
}
|
|
|
|
unsigned int ON_Mesh::FaceUnsignedCount() const
|
|
{
|
|
return m_F.UnsignedCount();
|
|
}
|
|
|
|
int ON_Mesh::QuadCount() const
|
|
{
|
|
// number of faces that are quads
|
|
if ( m_quad_count < 0
|
|
|| m_triangle_count < 0
|
|
|| m_invalid_count < 0
|
|
|| m_quad_count + m_triangle_count + m_invalid_count != FaceCount() )
|
|
{
|
|
const_cast<ON_Mesh*>(this)->CountQuads();
|
|
}
|
|
return m_quad_count;
|
|
}
|
|
|
|
int ON_Mesh::TriangleCount() const
|
|
{
|
|
// number of faces that are triangles
|
|
QuadCount(); // makes sure counts are valid
|
|
return m_triangle_count;
|
|
}
|
|
|
|
int ON_Mesh::InvalidFaceCount() const
|
|
{
|
|
// number of faces that are invalid
|
|
QuadCount(); // makes sure counts are valid
|
|
return m_invalid_count;
|
|
}
|
|
|
|
|
|
int ON_Mesh::VertexCount() const
|
|
{
|
|
// When m_V becomes obsolete, this will return m_dV.Count().
|
|
return m_V.Count();
|
|
}
|
|
|
|
unsigned int ON_Mesh::VertexUnsignedCount() const
|
|
{
|
|
// When m_V becomes obsolete, this will return m_dV.UnsignedCount().
|
|
return m_V.UnsignedCount();
|
|
}
|
|
|
|
bool ON_Mesh::HasNgons() const
|
|
{
|
|
return ( m_F.Count() > 0 && m_Ngon.Count() );
|
|
}
|
|
|
|
bool ON_Mesh::HasVertexNormals() const
|
|
{
|
|
const int vertex_count = VertexCount();
|
|
return ( vertex_count > 0 && m_N.Count() == vertex_count ) ? true : false;
|
|
}
|
|
|
|
bool ON_Mesh::HasFaceNormals() const
|
|
{
|
|
const int face_count = FaceCount();
|
|
return ( face_count > 0 && m_FN.Count() == face_count ) ? true : false;
|
|
}
|
|
|
|
bool ON_Mesh::HasTextureCoordinates() const
|
|
{
|
|
const int vertex_count = VertexCount();
|
|
return ( vertex_count > 0 && m_T.Count() == vertex_count ) ? true : false;
|
|
}
|
|
|
|
bool ON_Mesh::HasCachedTextureCoordinates() const
|
|
{
|
|
const int vertex_count = VertexCount();
|
|
if (vertex_count > 0 )
|
|
{
|
|
int tci, tccount = m_TC.Count();
|
|
for ( tci = 0; tci < tccount; tci++ )
|
|
{
|
|
if ( vertex_count == m_TC[tci].m_T.Count() )
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const ON_TextureCoordinates*
|
|
ON_Mesh::CachedTextureCoordinates( const ON_UUID& mapping_id ) const
|
|
{
|
|
const int vertex_count = VertexCount();
|
|
if (vertex_count > 0 )
|
|
{
|
|
const ON_TextureCoordinates* TC = m_TC.Array();
|
|
int tci, tccount = m_TC.Count();
|
|
for ( tci = 0; tci < tccount; tci++ )
|
|
{
|
|
if ( vertex_count == TC->m_T.Count()
|
|
&& mapping_id == TC->m_tag.m_mapping_id )
|
|
{
|
|
return TC;
|
|
}
|
|
TC++;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool ON_Mesh::HasSurfaceParameters() const
|
|
{
|
|
const int vertex_count = VertexCount();
|
|
return ( vertex_count > 0 && m_S.Count() == vertex_count ) ? true : false;
|
|
}
|
|
|
|
bool ON_Mesh::HasPrincipalCurvatures() const
|
|
{
|
|
const int vertex_count = VertexCount();
|
|
return ( vertex_count > 0 && m_K.Count() == vertex_count ) ? true : false;
|
|
}
|
|
|
|
bool ON_Mesh::HasVertexColors() const
|
|
{
|
|
const int vertex_count = VertexCount();
|
|
return ( vertex_count > 0 && m_C.Count() == vertex_count ) ? true : false;
|
|
}
|
|
|
|
void ON_Mesh::InvalidateBoundingBoxes()
|
|
{
|
|
InvalidateVertexBoundingBox();
|
|
InvalidateVertexNormalBoundingBox();
|
|
InvalidateTextureCoordinateBoundingBox();
|
|
InvalidateCurvatureStats();
|
|
}
|
|
|
|
void ON_Mesh::InvalidateVertexBoundingBox()
|
|
{
|
|
m_vertex_bbox = ON_BoundingBox::UnsetBoundingBox;
|
|
m_tight_bbox_cache.RemoveAllBoundingBoxes();
|
|
}
|
|
|
|
void ON_Mesh::InvalidateVertexNormalBoundingBox()
|
|
{
|
|
m_nbox[0][0] = m_nbox[0][1] = m_nbox[0][2] = 1.0;
|
|
m_nbox[1][0] = m_nbox[1][1] = m_nbox[1][2] = -1.0;
|
|
}
|
|
|
|
void ON_Mesh::InvalidateTextureCoordinateBoundingBox()
|
|
{
|
|
m_tbox[0][0] = m_tbox[0][1] = 1.0;
|
|
m_tbox[1][0] = m_tbox[1][1] = -1.0;
|
|
}
|
|
|
|
void ON_Mesh::InvalidateCurvatureStats()
|
|
{
|
|
int i;
|
|
for ( i = 0; i < 4; i++ ) {
|
|
if ( m_kstat[i] ) {
|
|
delete m_kstat[i];
|
|
m_kstat[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ON_Mesh::UnitizeVertexNormals()
|
|
{
|
|
bool rc = HasVertexNormals();
|
|
if ( rc ) {
|
|
const int vertex_count = VertexCount();
|
|
float* n = &m_N[0][0];
|
|
int i;
|
|
ON_3dVector N;
|
|
for ( i = 0; i < vertex_count; i++ ) {
|
|
N.x = n[0];
|
|
N.y = n[1];
|
|
N.z = n[2];
|
|
if ( !N.Unitize() )
|
|
rc = false;
|
|
*n++ = (float)N.x;
|
|
*n++ = (float)N.y;
|
|
*n++ = (float)N.z;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Mesh::UnitizeFaceNormals()
|
|
{
|
|
bool rc = HasFaceNormals();
|
|
if ( rc ) {
|
|
const int face_count = FaceCount();
|
|
float* n = &m_FN[0][0];
|
|
int i;
|
|
ON_3dVector N;
|
|
for ( i = 0; i < face_count; i++ ) {
|
|
N.x = n[0];
|
|
N.y = n[1];
|
|
N.z = n[2];
|
|
if ( !N.Unitize() )
|
|
rc = false;
|
|
*n++ = (float)N.x;
|
|
*n++ = (float)N.y;
|
|
*n++ = (float)N.z;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
bool ON_Mesh::GetCurvatureStats( // returns true if successful
|
|
ON::curvature_style kappa_style,
|
|
ON_MeshCurvatureStats& stats
|
|
) const
|
|
{
|
|
bool rc = false;
|
|
stats.Destroy();
|
|
int ksi;
|
|
switch ( kappa_style ) {
|
|
case ON::gaussian_curvature:
|
|
ksi = 0;
|
|
break;
|
|
case ON::mean_curvature:
|
|
ksi = 1;
|
|
break;
|
|
case ON::min_curvature: // minimum unsigned radius of curvature
|
|
ksi = 2;
|
|
break;
|
|
case ON::max_curvature: // maximum unsigned radius of curvature
|
|
ksi = 3;
|
|
break;
|
|
//case ON::section_curvature_x:
|
|
// ksi = 4;
|
|
// break;
|
|
//case ON::section_curvature_y:
|
|
// ksi = 5;
|
|
// break;
|
|
//case ON::section_curvature_z:
|
|
// ksi = 6;
|
|
// break;
|
|
default:
|
|
ksi = -1;
|
|
break;
|
|
}
|
|
if ( ksi >= 0 && ksi <= 3 && HasPrincipalCurvatures() ) {
|
|
ON_Mesh* p = (ON_Mesh*)this; // const lie
|
|
if ( !m_kstat[ksi] ) {
|
|
p->m_kstat[ksi] = new ON_MeshCurvatureStats();
|
|
p->m_kstat[ksi]->Set( kappa_style, m_K.Count(), m_K.Array(), m_N.Array() );
|
|
}
|
|
if ( p->m_kstat[ksi] ) {
|
|
stats = *p->m_kstat[ksi];
|
|
rc = true;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int ON_MeshTopology::WaitUntilReady(int sleep_value) const
|
|
{
|
|
|
|
return m_b32IsValid;
|
|
|
|
}
|
|
|
|
|
|
bool ON_Mesh::TopologyExists() const
|
|
{
|
|
return HasMeshTopology();
|
|
}
|
|
|
|
bool ON_Mesh::HasMeshTopology() const
|
|
{
|
|
return (1 == m_top.WaitUntilReady(0));
|
|
}
|
|
|
|
const ON_MeshTopology& ON_Mesh::Topology() const
|
|
{
|
|
int top_b32IsValid = m_top.WaitUntilReady(-1);
|
|
|
|
if ( 0 == top_b32IsValid )
|
|
{
|
|
ON_MeshTopology& top = const_cast<ON_MeshTopology&>(m_top);
|
|
top.m_mesh = this;
|
|
top_b32IsValid = top.Create() ? 1 : 0;
|
|
top.m_b32IsValid = top_b32IsValid;
|
|
}
|
|
|
|
return m_top;
|
|
}
|
|
|
|
static ON_MeshTriangle ON_UnsetMeshTriangleInitalizer()
|
|
{
|
|
ON_MeshTriangle unset_mesh_triangle;
|
|
unset_mesh_triangle.m_vi[0] = ON_UNSET_UINT_INDEX;
|
|
unset_mesh_triangle.m_vi[1] = ON_UNSET_UINT_INDEX;
|
|
unset_mesh_triangle.m_vi[2] = ON_UNSET_UINT_INDEX;
|
|
return unset_mesh_triangle;
|
|
}
|
|
|
|
|
|
const ON_MeshTriangle ON_MeshTriangle::UnsetMeshTriangle(ON_UnsetMeshTriangleInitalizer());
|
|
|
|
bool ON_MeshTriangle::IsValid(
|
|
size_t mesh_vertex_count
|
|
) const
|
|
{
|
|
if ( mesh_vertex_count < 2 || mesh_vertex_count >= (size_t)ON_UNSET_UINT_INDEX )
|
|
return false;
|
|
if ( m_vi[0] == m_vi[1] || m_vi[1] == m_vi[2] || m_vi[2] == m_vi[0] )
|
|
return false;
|
|
const unsigned int c = (unsigned int)mesh_vertex_count;
|
|
if ( m_vi[0] >= c || m_vi[1] >= c || m_vi[2] >= c )
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool ON_MeshTriangle::IsValid(
|
|
size_t mesh_vertex_count,
|
|
const class ON_3fPoint* V
|
|
) const
|
|
{
|
|
if ( false == IsValid(mesh_vertex_count) )
|
|
return false;
|
|
if ( 0 == V )
|
|
return false;
|
|
return ( V[m_vi[0]] != V[m_vi[1]] && V[m_vi[1]] != V[m_vi[2]] && V[m_vi[2]] != V[m_vi[0]] );
|
|
}
|
|
|
|
bool ON_MeshTriangle::IsValid(
|
|
size_t mesh_vertex_count,
|
|
const class ON_3dPoint* V
|
|
) const
|
|
{
|
|
if ( false == IsValid(mesh_vertex_count) )
|
|
return false;
|
|
if ( 0 == V )
|
|
return false;
|
|
return ( V[m_vi[0]] != V[m_vi[1]] && V[m_vi[1]] != V[m_vi[2]] && V[m_vi[2]] != V[m_vi[0]] );
|
|
}
|
|
|
|
bool ON_MeshTriangle::IsValid(
|
|
const class ON_3dPointListRef& vertex_list
|
|
) const
|
|
{
|
|
if ( false == IsValid(vertex_list.PointCount()) )
|
|
return false;
|
|
ON_3dPoint V[3] = {vertex_list[m_vi[0]],vertex_list[m_vi[1]],vertex_list[m_vi[2]]};
|
|
return ( V[0] != V[1] && V[1] != V[2] && V[2] != V[0] );
|
|
}
|
|
|
|
void ON_MeshTriangle::Flip()
|
|
{
|
|
unsigned int i = m_vi[1];
|
|
m_vi[1] = m_vi[2];
|
|
m_vi[2] = i;
|
|
}
|
|
|
|
|
|
static bool ON_MeshTriangle_GetTriangleNormal(
|
|
const double* p,
|
|
const double* q,
|
|
const double* r,
|
|
class ON_3dVector& triangle_normal
|
|
)
|
|
{
|
|
const double a[3] = {r[0]-p[0],r[1]-p[1],r[2]-p[2]};
|
|
const double b[3] = {p[0]-q[0],p[1]-q[1],p[2]-q[2]};
|
|
|
|
triangle_normal.x = a[1]*b[2] - b[1]*a[2];
|
|
triangle_normal.y = a[2]*b[0] - b[2]*a[0];
|
|
triangle_normal.z = a[0]*b[1] - b[0]*a[1];
|
|
return triangle_normal.Unitize();
|
|
}
|
|
|
|
bool ON_MeshTriangle::GetTriangleNormal(
|
|
const class ON_3dPoint* dV,
|
|
class ON_3dVector& triangle_normal
|
|
) const
|
|
{
|
|
if ( 0 == dV )
|
|
return false;
|
|
return ON_MeshTriangle_GetTriangleNormal(&dV[m_vi[0]].x,&dV[m_vi[1]].x,&dV[m_vi[2]].x,triangle_normal);
|
|
}
|
|
|
|
bool ON_MeshTriangle::GetTriangleNormal(
|
|
const class ON_3fPoint* fV,
|
|
class ON_3dVector& triangle_normal
|
|
) const
|
|
{
|
|
const ON_3dPoint p(fV[m_vi[0]]);
|
|
const ON_3dPoint q(fV[m_vi[1]]);
|
|
const ON_3dPoint r(fV[m_vi[2]]);
|
|
return ON_MeshTriangle_GetTriangleNormal(&p.x,&q.x,&r.x,triangle_normal);
|
|
}
|
|
|
|
bool ON_MeshTriangle::GetTriangleNormal(
|
|
const class ON_3dPointListRef& vertex_list,
|
|
class ON_3dVector& triangle_normal
|
|
) const
|
|
{
|
|
const ON_3dPoint p(vertex_list[m_vi[0]]);
|
|
const ON_3dPoint q(vertex_list[m_vi[1]]);
|
|
const ON_3dPoint r(vertex_list[m_vi[2]]);
|
|
return ON_MeshTriangle_GetTriangleNormal(&p.x,&q.x,&r.x,triangle_normal);
|
|
}
|
|
|
|
bool ON_MeshTriangle::GetTriangleNormal(
|
|
ON_3dPoint point0,
|
|
ON_3dPoint point1,
|
|
ON_3dPoint point2,
|
|
class ON_3dVector& triangle_normal
|
|
)
|
|
{
|
|
return ON_MeshTriangle_GetTriangleNormal(&point0.x,&point1.x,&point2.x,triangle_normal);
|
|
}
|
|
|
|
static ON_MeshFace ON_UnsetMeshFaceInitalizer()
|
|
{
|
|
ON_MeshFace unset_mesh_face;
|
|
unset_mesh_face.vi[0] = -1;
|
|
unset_mesh_face.vi[1] = -1;
|
|
unset_mesh_face.vi[2] = -1;
|
|
unset_mesh_face.vi[3] = -1;
|
|
return unset_mesh_face;
|
|
}
|
|
|
|
const ON_MeshFace ON_MeshFace::UnsetMeshFace(ON_UnsetMeshFaceInitalizer());
|
|
|
|
void ON_Mesh::DestroyTopology()
|
|
{
|
|
m_top.Destroy();
|
|
}
|
|
|
|
bool
|
|
ON_MeshFace::IsTriangle() const
|
|
{
|
|
return vi[2]==vi[3];
|
|
}
|
|
|
|
bool
|
|
ON_MeshFace::IsQuad() const
|
|
{
|
|
return vi[2]!=vi[3];
|
|
}
|
|
|
|
void
|
|
ON_MeshFace::Flip()
|
|
{
|
|
int i;
|
|
if ( vi[2] == vi[3] ) {
|
|
i = vi[1];
|
|
vi[1] = vi[2];
|
|
vi[2] = i;
|
|
vi[3] = i;
|
|
}
|
|
else {
|
|
i = vi[1];
|
|
vi[1] = vi[3];
|
|
vi[3] = i;
|
|
}
|
|
}
|
|
|
|
void
|
|
ON_Mesh::Flip()
|
|
{
|
|
FlipFaceOrientation();
|
|
FlipFaceNormals();
|
|
FlipVertexNormals();
|
|
FlipNgonOrientation();
|
|
|
|
// Do not modify m_S[] or m_T[]
|
|
// values here.
|
|
}
|
|
|
|
void
|
|
ON_Mesh::FlipVertexNormals()
|
|
{
|
|
int i;
|
|
const int vcount = VertexCount();
|
|
if ( HasVertexNormals() ) {
|
|
for ( i = 0; i < vcount; i++ ) {
|
|
m_N[i] = -m_N[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
ON_Mesh::FlipFaceNormals()
|
|
{
|
|
int i;
|
|
const int fcount = FaceCount();
|
|
if ( HasFaceNormals() ) {
|
|
for( i = 0; i < fcount; i++ ) {
|
|
m_FN[i] = -m_FN[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
ON_Mesh::FlipFaceOrientation()
|
|
{
|
|
int i;
|
|
const int fcount = FaceCount();
|
|
for( i = 0; i < fcount; i++ ) {
|
|
m_F[i].Flip();
|
|
}
|
|
if ( fcount > 0 )
|
|
DestroyTopology(); // flipping changes order of face corners
|
|
}
|
|
|
|
|
|
|
|
bool ON_MeshFace::ComputeFaceNormal( const ON_3dPoint* dV, ON_3dVector& FN ) const
|
|
{
|
|
if ( 0 != dV )
|
|
{
|
|
ON_3dVector a = dV[vi[2]] - dV[vi[0]];
|
|
ON_3dVector b = dV[vi[3]] - dV[vi[1]];
|
|
FN = ON_CrossProduct( a, b ); // works for triangles, quads, and nonplanar quads
|
|
if ( FN.Unitize() )
|
|
return true;
|
|
}
|
|
|
|
FN = ON_3dVector::ZeroVector;
|
|
return false;
|
|
}
|
|
|
|
bool ON_MeshFace::ComputeFaceNormal( const ON_3fPoint* fV, ON_3dVector& FN ) const
|
|
{
|
|
if ( 0 != fV )
|
|
{
|
|
ON_3dVector a = fV[vi[2]] - fV[vi[0]];
|
|
ON_3dVector b = fV[vi[3]] - fV[vi[1]];
|
|
FN = ON_CrossProduct( a, b ); // works for triangles, quads, and nonplanar quads
|
|
if ( FN.Unitize() )
|
|
return true;
|
|
}
|
|
|
|
FN = ON_3dVector::ZeroVector;
|
|
return false;
|
|
}
|
|
|
|
bool ON_MeshFace::ComputeFaceNormal( const class ON_3dPointListRef& vertex_list, ON_3dVector& FN ) const
|
|
{
|
|
ON_3dVector a = vertex_list[vi[2]] - vertex_list[vi[0]];
|
|
ON_3dVector b = vertex_list[vi[3]] - vertex_list[vi[1]];
|
|
FN = ON_CrossProduct( a, b ); // works for triangles, quads, and nonplanar quads
|
|
if ( FN.Unitize() )
|
|
return true;
|
|
|
|
FN = ON_3dVector::ZeroVector;
|
|
return false;
|
|
}
|
|
|
|
|
|
unsigned int ON_MeshFace::GetCornerNormals(
|
|
const class ON_3dPointListRef& vertex_list,
|
|
ON_3dVector corner_normals[4]
|
|
) const
|
|
{
|
|
unsigned int rc = 0;
|
|
|
|
if (vi[2] == vi[3])
|
|
{
|
|
if (ComputeFaceNormal(vertex_list, corner_normals[0]))
|
|
{
|
|
rc = 4;
|
|
}
|
|
else
|
|
{
|
|
corner_normals[0] = ON_3dVector::UnsetVector;
|
|
corner_normals[1] = corner_normals[0];
|
|
corner_normals[2] = corner_normals[0];
|
|
corner_normals[3] = corner_normals[0];
|
|
}
|
|
return rc;
|
|
}
|
|
else
|
|
{
|
|
const double unset_x = ON_3dVector::UnsetVector.x;
|
|
ON_3dVector C = vertex_list[vi[0]] - vertex_list[vi[3]];
|
|
if (!C.Unitize())
|
|
C = ON_3dVector::UnsetVector;
|
|
ON_3dVector B = C;
|
|
for (unsigned int i = 0; i < 4; i++)
|
|
{
|
|
ON_3dVector A = B;
|
|
if (3 == i)
|
|
B = C;
|
|
else
|
|
{
|
|
B = vertex_list[vi[i + 1]] - vertex_list[vi[i]];
|
|
if (!B.Unitize())
|
|
B = ON_3dVector::UnsetVector;
|
|
}
|
|
|
|
if (unset_x != A.x && unset_x != B.x)
|
|
{
|
|
corner_normals[i] = ON_CrossProduct(A, B);
|
|
if (corner_normals[i].Unitize())
|
|
{
|
|
rc++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
corner_normals[i] = ON_3dVector::UnsetVector;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool ON_MeshFace::GetPlaneEquation(
|
|
const class ON_3dPointListRef& vertex_list,
|
|
ON_PlaneEquation& face_plane_equation
|
|
) const
|
|
{
|
|
ON_3dVector N;
|
|
if (ComputeFaceNormal(vertex_list, N) && face_plane_equation.Create(vertex_list[vi[0]], N))
|
|
return true;
|
|
|
|
face_plane_equation = ON_PlaneEquation::UnsetPlaneEquation;
|
|
return false;
|
|
}
|
|
|
|
bool ON_MeshFace::IsPlanar(
|
|
double planar_tolerance,
|
|
double angle_tolerance_radians,
|
|
const class ON_3dPointListRef& vertex_list,
|
|
ON_PlaneEquation* face_plane_equation
|
|
) const
|
|
{
|
|
ON_PlaneEquation e;
|
|
|
|
for (;;)
|
|
{
|
|
if (!GetPlaneEquation(vertex_list, e))
|
|
break;
|
|
|
|
if ( face_plane_equation )
|
|
*face_plane_equation = e;
|
|
|
|
if (vi[2] == vi[3])
|
|
return true; // triangle
|
|
|
|
if (planar_tolerance >= 0.0)
|
|
{
|
|
double h0, h1, h;
|
|
h0 = h1 = 0.0;
|
|
for (int i = 1; i < 3; i++)
|
|
{
|
|
if (vi[i - 1] == vi[i])
|
|
continue;
|
|
h = e.ValueAt(vertex_list[vi[i]]);
|
|
if (h < h0)
|
|
h0 = h;
|
|
else if (h > h1)
|
|
h1 = h;
|
|
else
|
|
continue;
|
|
if (h1 - h0 > planar_tolerance)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (angle_tolerance_radians >= 0.0)
|
|
{
|
|
ON_3dVector corner_normals[4];
|
|
GetCornerNormals(vertex_list, corner_normals);
|
|
const double unset_x = ON_3dVector::UnsetVector.x;
|
|
const double cos_atol = (angle_tolerance_radians < ON_PI) ? cos(angle_tolerance_radians) : -1.0;
|
|
for (unsigned int i = 0; i < 2; i++)
|
|
{
|
|
if (unset_x != corner_normals[i].x && unset_x != corner_normals[i+2].x && corner_normals[i] * corner_normals[i+2] < cos_atol)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if (face_plane_equation)
|
|
*face_plane_equation = ON_PlaneEquation::UnsetPlaneEquation;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool ON_Mesh::ComputeFaceNormal( int fi )
|
|
{
|
|
if ( fi < 0 )
|
|
return false;
|
|
if ( fi >= m_F.Count() )
|
|
return false;
|
|
if ( m_FN.Count() != m_F.Count() )
|
|
return false;
|
|
|
|
ON_3dVector FN;
|
|
bool rc = ( HasDoublePrecisionVertices() )
|
|
? m_F[fi].ComputeFaceNormal(DoublePrecisionVertices().Array(),FN)
|
|
: m_F[fi].ComputeFaceNormal(m_V.Array(),FN);
|
|
|
|
m_FN[fi] = FN;
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Mesh::ComputeFaceNormals()
|
|
{
|
|
bool rc = false;
|
|
//const unsigned int vcount = VertexUnsignedCount();
|
|
const int fcount = FaceCount();
|
|
if ( fcount > 0 )
|
|
{
|
|
ON_3dVector a, b, n;
|
|
int fi;
|
|
const int* vi;
|
|
if ( m_FN.Capacity() < fcount )
|
|
m_FN.SetCapacity(fcount);
|
|
m_FN.SetCount(0);
|
|
rc = true;
|
|
if ( HasSynchronizedDoubleAndSinglePrecisionVertices() )
|
|
{
|
|
const ON_3dPointArray& dV = DoublePrecisionVertices();
|
|
for ( fi = 0; fi < fcount; fi++ ) {
|
|
vi = m_F[fi].vi;
|
|
a = dV[vi[2]] - dV[vi[0]];
|
|
b = dV[vi[3]] - dV[vi[1]];
|
|
n = ON_CrossProduct( a, b ); // works for triangles, quads, and nonplanar quads
|
|
n.Unitize();
|
|
m_FN.AppendNew() = ON_3fVector(n);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for ( fi = 0; fi < fcount; fi++ ) {
|
|
vi = m_F[fi].vi;
|
|
a = m_V[vi[2]] - m_V[vi[0]];
|
|
b = m_V[vi[3]] - m_V[vi[1]];
|
|
n = ON_CrossProduct( a, b ); // works for triangles, quads, and nonplanar quads
|
|
n.Unitize();
|
|
m_FN.AppendNew() = ON_3fVector(n);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_FN.Destroy();
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
//static int compareRadial3fPoint( const ON_3fPoint* a, const ON_3fPoint* b )
|
|
//{
|
|
// double ar = a->x+a->y+a->z;
|
|
// double br = b->x+b->y+b->z;
|
|
// if ( ar < br )
|
|
// return -1;
|
|
// if ( ar > br )
|
|
// return 1;
|
|
// return 0;
|
|
//}
|
|
|
|
bool ON_Mesh::CombineCoincidentVertices(
|
|
const ON_3fVector tolerance,
|
|
double cos_normal_angle // = -1.0 // cosine(break angle) -1.0 will merge all coincident vertices
|
|
)
|
|
{
|
|
// TODO - If you need this function, please ask Dale Lear to finish it.
|
|
//bool rc = false;
|
|
//const int vcount = VertexCount();
|
|
//if ( vcount > 0 && rc ) {
|
|
// ON_Workspace ws;
|
|
// int* index = ws.GetIntMemory(vcount);
|
|
// rc = m_V.Sort( ON::sort_algorithm::quick_sort, index, compareRadial3fPoint );
|
|
// int i, j;
|
|
// ON_3fPoint p0, p1, pmin, pmax;
|
|
// for ( i = 0; i < vcount; i++ ) {
|
|
// p0 = m_V[i];
|
|
// pmin = p0 - tolerance;
|
|
// pmax = p0 + tolerance;
|
|
// for ( j = i+1; j < vcount; j++ ) {
|
|
// p1 = m_V[j];
|
|
// // TODO
|
|
// }
|
|
// }
|
|
//}
|
|
return false;
|
|
}
|
|
|
|
|
|
struct tagMESHPOINTS
|
|
{
|
|
// p0 = bogus pointer - never dereferenced - that is used
|
|
// to calculate vertex index in CompareMeshPoint().
|
|
const char* p0;
|
|
ON_3fPoint* V;
|
|
ON_2fPoint* T;
|
|
ON_3fVector* N;
|
|
ON_SurfaceCurvature* K;
|
|
ON_Color* C;
|
|
};
|
|
|
|
static int CompareMeshPoint(const void* a,const void* b,void* ptr)
|
|
{
|
|
float d;
|
|
const struct tagMESHPOINTS * mp = (const struct tagMESHPOINTS *)ptr;
|
|
|
|
// use bogus pointer to convert a,b into vertex indices
|
|
int i = (int)(((const char*)a) - mp->p0); // the (int) is for 64 bit size_t conversion
|
|
int j = (int)(((const char*)b) - mp->p0);
|
|
|
|
d = mp->V[j].x - mp->V[i].x;
|
|
if ( d == 0.0f )
|
|
{
|
|
d = mp->V[j].y - mp->V[i].y;
|
|
if ( d == 0.0f )
|
|
{
|
|
d = mp->V[j].z - mp->V[i].z;
|
|
|
|
//if ( d == 0.0f )
|
|
// return 0;
|
|
|
|
if ( d == 0.0f && 0 != mp->N)
|
|
{
|
|
d = mp->N[j].x - mp->N[i].x;
|
|
if ( d == 0.0f )
|
|
{
|
|
d = mp->N[j].y - mp->N[i].y;
|
|
if ( d == 0.0f )
|
|
{
|
|
d = mp->N[j].z - mp->N[i].z;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( d == 0.0f && 0 != mp->T)
|
|
{
|
|
d = mp->T[j].x - mp->T[i].x;
|
|
if ( d == 0.0f )
|
|
{
|
|
d = mp->T[j].y - mp->T[i].y;
|
|
}
|
|
}
|
|
|
|
if ( d == 0.0f && 0 != mp->C )
|
|
{
|
|
int u = ((int)mp->C[j])-((int)mp->C[i]);
|
|
if ( u < 0 )
|
|
d = -1.0f;
|
|
else if ( u > 0 )
|
|
d = 1.0f;
|
|
}
|
|
|
|
if ( d == 0.0f && 0 != mp->K )
|
|
{
|
|
double dk = mp->K[j].k1 - mp->K[i].k1;
|
|
if ( dk < 0.0 )
|
|
d = -1.0;
|
|
else if ( dk > 0.0 )
|
|
d = 1.0;
|
|
else
|
|
{
|
|
dk = mp->K[j].k2 - mp->K[i].k2;
|
|
if ( dk < 0.0 )
|
|
d = -1.0;
|
|
else if ( dk > 0.0 )
|
|
d = 1.0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( d < 0.0f )
|
|
return -1;
|
|
if ( d > 0.0f )
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
unsigned int ON_Mesh::RemoveAllCreases()
|
|
{
|
|
unsigned int vertex_count0 = this->VertexUnsignedCount();
|
|
bool bChanged = this->CombineIdenticalVertices(true, true);
|
|
const unsigned int vertex_count1 = this->VertexUnsignedCount();
|
|
|
|
if (vertex_count0 == vertex_count1 && bChanged)
|
|
vertex_count0 = vertex_count1 + 1;
|
|
return (vertex_count0 - vertex_count1);
|
|
}
|
|
|
|
bool ON_Mesh::CombineIdenticalVertices(
|
|
bool bIgnoreVertexNormals,
|
|
bool bIgnoreTextureCoordinates
|
|
)
|
|
{
|
|
// 11 June 2003 - added and tested.
|
|
bool rc = false;
|
|
ON_Mesh& mesh = *this;
|
|
|
|
int vertex_count = mesh.m_V.Count();
|
|
if ( vertex_count > 0 )
|
|
{
|
|
ON_SimpleArray<int> index_array(vertex_count);
|
|
ON_SimpleArray<int> remap_array(vertex_count);
|
|
|
|
int remap_vertex_count = 0;
|
|
int merge_count = 0;
|
|
int i0, i1, k;
|
|
|
|
struct tagMESHPOINTS mp;
|
|
memset(&mp,0,sizeof(mp));
|
|
mp.p0 = (const char*)∓ // bogus pointer - never dereferenced
|
|
mp.V = mesh.m_V.Array();
|
|
mp.N = mesh.HasVertexNormals() ? mesh.m_N.Array() : 0;
|
|
mp.T = mesh.HasTextureCoordinates() ? mesh.m_T.Array() : 0;
|
|
mp.C = mesh.HasVertexColors() ? mesh.m_C.Array() : 0;
|
|
mp.K = mesh.HasPrincipalCurvatures() ? mesh.m_K.Array() : 0;
|
|
|
|
if ( bIgnoreVertexNormals )
|
|
{
|
|
mp.N = 0;
|
|
}
|
|
|
|
if ( bIgnoreTextureCoordinates )
|
|
{
|
|
mp.T = 0;
|
|
mp.C = 0;
|
|
mp.K = 0;
|
|
}
|
|
|
|
index_array.SetCount(vertex_count);
|
|
index_array.Zero();
|
|
int* index = index_array.Array();
|
|
|
|
remap_array.SetCount(vertex_count);
|
|
int* remap = remap_array.Array();
|
|
for ( k = 0; k < vertex_count; k++ )
|
|
remap[k] = -1;
|
|
|
|
ON_Sort(
|
|
ON::sort_algorithm::quick_sort,
|
|
index,
|
|
mp.p0, // data buffer
|
|
vertex_count,
|
|
sizeof(*mp.p0),
|
|
CompareMeshPoint,
|
|
&mp
|
|
);
|
|
|
|
for ( i0 = 0; i0 < vertex_count; i0 = i1 )
|
|
{
|
|
for ( i1 = i0+1; i1 < vertex_count; i1++ )
|
|
{
|
|
if ( CompareMeshPoint( mp.p0+index[i0], mp.p0+index[i1], &mp ) )
|
|
break;
|
|
else
|
|
merge_count++;
|
|
}
|
|
for ( /*empty*/; i0 < i1; i0++ )
|
|
{
|
|
remap[index[i0]] = remap_vertex_count;
|
|
}
|
|
remap_vertex_count++;
|
|
}
|
|
|
|
if ( bIgnoreVertexNormals )
|
|
{
|
|
mp.N = mesh.HasVertexNormals() ? mesh.m_N.Array() : 0;
|
|
}
|
|
|
|
if ( bIgnoreTextureCoordinates )
|
|
{
|
|
mp.T = mesh.HasTextureCoordinates() ? mesh.m_T.Array() : 0;
|
|
mp.C = mesh.HasVertexColors() ? mesh.m_C.Array() : 0;
|
|
mp.K = mesh.HasPrincipalCurvatures() ? mesh.m_K.Array() : 0;
|
|
}
|
|
|
|
if ( remap_vertex_count > 0 && remap_vertex_count < vertex_count )
|
|
{
|
|
ON_SimpleArray<ON_3fPoint> p_array(remap_vertex_count);
|
|
p_array.SetCount(remap_vertex_count);
|
|
ON_3fPoint* p = p_array.Array();
|
|
ON_3fVector* v = (ON_3fVector*)p;
|
|
|
|
for ( k = 0; k < vertex_count; k++ )
|
|
{
|
|
p[remap[k]] = mp.V[k];
|
|
}
|
|
for ( k = 0; k < remap_vertex_count; k++ )
|
|
mp.V[k] = p[k];
|
|
mesh.m_V.SetCount(remap_vertex_count);
|
|
|
|
if (vertex_count == m_dV.Count())
|
|
{
|
|
ON_SimpleArray<ON_3dPoint> dp_array;
|
|
ON_3dPoint* dp = dp_array.Reserve(remap_vertex_count);
|
|
ON_3dPoint* D = m_dV.Array();
|
|
for (k = 0; k < vertex_count; k++)
|
|
{
|
|
dp[remap[k]] = D[k];
|
|
}
|
|
for (k = 0; k < remap_vertex_count; k++)
|
|
D[k] = dp[k];
|
|
m_dV.SetCount(remap_vertex_count);
|
|
}
|
|
else
|
|
m_dV.Destroy();
|
|
|
|
if ( 0 != mp.N )
|
|
{
|
|
if ( bIgnoreVertexNormals )
|
|
{
|
|
// average vertex normals of combined vertices
|
|
p_array.Zero();
|
|
for ( k = 0; k < vertex_count; k++ )
|
|
{
|
|
v[remap[k]] += mp.N[k];
|
|
}
|
|
for ( k = 0; k < remap_vertex_count; k++ )
|
|
{
|
|
v[k].Unitize();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for ( k = 0; k < vertex_count; k++ )
|
|
{
|
|
v[remap[k]] = mp.N[k];
|
|
}
|
|
}
|
|
for ( k = 0; k < remap_vertex_count; k++ )
|
|
mp.N[k] = v[k];
|
|
mesh.m_N.SetCount(remap_vertex_count);
|
|
}
|
|
else
|
|
mesh.m_N.SetCount(0);
|
|
|
|
if ( 0 != mp.T && !bIgnoreTextureCoordinates )
|
|
{
|
|
for ( k = 0; k < vertex_count; k++ )
|
|
{
|
|
p[remap[k]] = mp.T[k];
|
|
}
|
|
for ( k = 0; k < remap_vertex_count; k++ )
|
|
mp.T[k] = p[k];
|
|
mesh.m_T.SetCount(remap_vertex_count);
|
|
}
|
|
else
|
|
mesh.m_T.SetCount(0);
|
|
|
|
if ( 0 != mp.C && !bIgnoreTextureCoordinates )
|
|
{
|
|
ON_SimpleArray<ON_Color> c_array(remap_vertex_count);
|
|
c_array.SetCount(remap_vertex_count);
|
|
ON_Color* c = c_array.Array();
|
|
for ( k = 0; k < vertex_count; k++ )
|
|
{
|
|
c[remap[k]] = mp.C[k];
|
|
}
|
|
for ( k = 0; k < remap_vertex_count; k++ )
|
|
mp.C[k] = c[k];
|
|
mesh.m_C.SetCount(remap_vertex_count);
|
|
}
|
|
else
|
|
mesh.m_C.SetCount(0);
|
|
|
|
if ( 0 != mp.K && !bIgnoreTextureCoordinates )
|
|
{
|
|
ON_SimpleArray<ON_SurfaceCurvature> s_array(remap_vertex_count);
|
|
s_array.SetCount(remap_vertex_count);
|
|
ON_SurfaceCurvature* s = s_array.Array();
|
|
for ( k = 0; k < vertex_count; k++ )
|
|
{
|
|
s[remap[k]] = mp.K[k];
|
|
}
|
|
for ( k = 0; k < remap_vertex_count; k++ )
|
|
mp.K[k] = s[k];
|
|
mesh.m_K.SetCount(remap_vertex_count);
|
|
}
|
|
else
|
|
mesh.m_K.SetCount(0);
|
|
|
|
const int face_count = mesh.m_F.Count();
|
|
ON_MeshFace* f = mesh.m_F.Array();
|
|
int* fvi;
|
|
for ( k = 0; k < face_count; k++ )
|
|
{
|
|
fvi = f[k].vi;
|
|
fvi[0] = remap[fvi[0]];
|
|
fvi[1] = remap[fvi[1]];
|
|
fvi[2] = remap[fvi[2]];
|
|
fvi[3] = remap[fvi[3]];
|
|
}
|
|
|
|
if ( HasNgons() )
|
|
{
|
|
for ( int ngon_index = 0; ngon_index < m_Ngon.Count(); ngon_index++ )
|
|
{
|
|
ON_MeshNgon* ngon = m_Ngon[ngon_index];
|
|
if ( 0 == ngon )
|
|
continue;
|
|
for ( unsigned int ngon_vertex_index = 0; ngon_vertex_index < ngon->m_Vcount; ngon_vertex_index++ )
|
|
ngon->m_vi[ngon_vertex_index] = remap[ngon->m_vi[ngon_vertex_index]];
|
|
}
|
|
}
|
|
|
|
//if (0 != V4V5_NgonList())
|
|
//{
|
|
// //This mesh has an ngon list.
|
|
// //Modify the vertex indexes in the ngons as
|
|
// //we did the faces above
|
|
// ON_V4V5_MeshNgonList* ngonlist = V4V5_ModifyNgonList();
|
|
// int kk, kkct = ngonlist->V4V5_NgonCount();
|
|
// for (kk = 0; kk < kkct; kk++)
|
|
// {
|
|
// ON_MeshNgon* ngon = ngonlist->V4V5_Ngon(kk);
|
|
// if (0 == ngon)
|
|
// continue;
|
|
// for ( k = 0; k < ngon->m_N; k++ )
|
|
// ngon->m_vi[k] = remap[ngon->m_vi[k]];
|
|
// }
|
|
//}
|
|
|
|
mesh.DestroyPartition();
|
|
mesh.DestroyTopology();
|
|
mesh.m_S.Destroy();
|
|
|
|
if ( mesh.m_V.Capacity() > 4*mesh.m_V.Count() && mesh.m_V.Capacity() > 50 )
|
|
{
|
|
// There is lots of unused memory in the dynamic arrays.
|
|
// Release what we can.
|
|
mesh.Compact();
|
|
}
|
|
rc = true;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
void ON_Mesh::Append( int mesh_count, const ON_Mesh* const* meshes )
|
|
{
|
|
if ( mesh_count <= 0 || 0 == meshes )
|
|
return;
|
|
|
|
int mi;
|
|
int vcount0 = VertexCount();
|
|
if ( vcount0 <= 0 )
|
|
m_F.SetCount(0);
|
|
int fcount0 = FaceCount();
|
|
int* vi;
|
|
int j;
|
|
const ON_Mesh* m;
|
|
|
|
// The calls to Has*() must happen before the m_V[] and m_F[] arrays get enlarged
|
|
// Allow the appendage of VertexNormals, TextureCoordinates, PrincipalCurvatures to empty meshes
|
|
// by checking for 0 == vcount0 && 0 == fcount0
|
|
bool bHasVertexNormals = (0 == vcount0 || HasVertexNormals());
|
|
bool bHasFaceNormals = (0 == vcount0 || 0 == fcount0 || HasFaceNormals());
|
|
bool bHasTextureCoordinates = (0 == vcount0 || HasTextureCoordinates());
|
|
bool bHasPrincipalCurvatures = (0 == vcount0 || HasPrincipalCurvatures());
|
|
bool bHasVertexColors = (0 == vcount0 || HasVertexColors());
|
|
bool bHasSurfaceParameters = (0 == vcount0 || HasSurfaceParameters());
|
|
bool bHasDoubles = (0 == vcount0 || HasSynchronizedDoubleAndSinglePrecisionVertices());
|
|
bool bHasNgonMap = (NgonCount() > 0 && 0 != NgonMap());
|
|
|
|
bool bHasSurfaceDomain
|
|
= bHasSurfaceParameters
|
|
&& (0 == vcount0 || (m_srf_domain[0].IsIncreasing() && m_srf_domain[1].IsIncreasing()));
|
|
|
|
ON_Interval srf_domain[2];
|
|
srf_domain[0] = (bHasSurfaceDomain && vcount0 > 0) ? m_srf_domain[0] : ON_Interval::EmptyInterval;
|
|
srf_domain[1] = (bHasSurfaceDomain && vcount0 > 0) ? m_srf_domain[1] : ON_Interval::EmptyInterval;
|
|
|
|
bool bHasTexturePackingDomain
|
|
= bHasTextureCoordinates
|
|
&& (0 == vcount0 || (m_packed_tex_domain[0].IsIncreasing() && m_packed_tex_domain[1].IsIncreasing()));
|
|
|
|
ON_Interval packed_tex_domain[2];
|
|
packed_tex_domain[0] = (bHasTexturePackingDomain && vcount0 > 0) ? m_packed_tex_domain[0] : ON_Interval::EmptyInterval;
|
|
packed_tex_domain[1] = (bHasTexturePackingDomain && vcount0 > 0) ? m_packed_tex_domain[1] : ON_Interval::EmptyInterval;
|
|
bool packed_tex_rotate = (bHasTexturePackingDomain && m_packed_tex_rotate) ? true : false;
|
|
|
|
double srf_scale[2] = { m_srf_scale[0], m_srf_scale[1]};
|
|
|
|
int fcount = fcount0;
|
|
int vcount = vcount0;
|
|
unsigned int merged_count = vcount0 > 0 ? 1 : 0;
|
|
for ( mi = 0; mi < mesh_count; mi++ )
|
|
{
|
|
m = meshes[mi];
|
|
if ( 0 == m )
|
|
continue;
|
|
int vcount1 = m->m_V.Count();
|
|
if ( vcount1 <= 0 )
|
|
continue;
|
|
|
|
merged_count++;
|
|
|
|
int fcount1 = m->m_F.Count();
|
|
if ( fcount1 > 0 )
|
|
fcount += fcount1;
|
|
vcount += vcount1;
|
|
if ( bHasVertexNormals && !m->HasVertexNormals() )
|
|
bHasVertexNormals = false;
|
|
if ( bHasTextureCoordinates && !m->HasTextureCoordinates())
|
|
bHasTextureCoordinates = false;
|
|
if ( bHasPrincipalCurvatures && !m->HasPrincipalCurvatures())
|
|
bHasPrincipalCurvatures = false;
|
|
if ( bHasVertexColors && !m->HasVertexColors())
|
|
bHasVertexColors = false;
|
|
if ( bHasSurfaceParameters && !m->HasSurfaceParameters())
|
|
bHasSurfaceParameters = false;
|
|
if ( bHasDoubles && !m->HasSynchronizedDoubleAndSinglePrecisionVertices())
|
|
bHasDoubles = false;
|
|
if ( bHasFaceNormals && fcount1 > 0 && !m->HasFaceNormals() )
|
|
bHasFaceNormals = false;
|
|
|
|
if (bHasSurfaceDomain)
|
|
{
|
|
bHasSurfaceDomain
|
|
= bHasSurfaceParameters
|
|
&& m->m_srf_domain[0].IsIncreasing()
|
|
&& m->m_srf_domain[1].IsIncreasing();
|
|
if (bHasSurfaceDomain)
|
|
{
|
|
srf_domain[0].Union(m->m_srf_domain[0]);
|
|
srf_domain[1].Union(m->m_srf_domain[1]);
|
|
}
|
|
}
|
|
|
|
if (1 == merged_count && bHasSurfaceParameters && bHasSurfaceDomain)
|
|
{
|
|
srf_scale[0] = m->m_srf_scale[0];
|
|
srf_scale[1] = m->m_srf_scale[1];
|
|
}
|
|
else
|
|
{
|
|
srf_scale[0] = 0.0;
|
|
srf_scale[1] = 0.0;
|
|
}
|
|
|
|
if (bHasTexturePackingDomain)
|
|
{
|
|
// 2014-04-01 Dale Lear
|
|
// Trying to merger packed_tex_domain[] intervals
|
|
// is questionable at best.
|
|
// A strong argument can be made for simply deleting
|
|
// packed texture domain information when there are
|
|
// two non-empty meshes that are merged.
|
|
bHasTexturePackingDomain
|
|
= bHasTextureCoordinates
|
|
&& m->m_packed_tex_domain[0].IsIncreasing()
|
|
&& m->m_packed_tex_domain[1].IsIncreasing();
|
|
if (packed_tex_rotate != (m->m_packed_tex_rotate?true:false))
|
|
{
|
|
if (0 == vcount0 && 0 == mi)
|
|
{
|
|
packed_tex_rotate = (m->m_packed_tex_rotate ? true : false);
|
|
}
|
|
else
|
|
{
|
|
bHasTexturePackingDomain = false;
|
|
}
|
|
}
|
|
if (bHasTexturePackingDomain)
|
|
{
|
|
if (1 == merged_count)
|
|
{
|
|
packed_tex_rotate = (m->m_packed_tex_rotate ? true : false);
|
|
}
|
|
else if (packed_tex_rotate != (m->m_packed_tex_rotate ? true : false))
|
|
{
|
|
bHasTexturePackingDomain = false;
|
|
}
|
|
packed_tex_domain[0].Union(m->m_packed_tex_domain[0]);
|
|
packed_tex_domain[1].Union(m->m_packed_tex_domain[1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if ( vcount <= vcount0 && fcount <= fcount0 )
|
|
return;
|
|
|
|
if (!bHasSurfaceParameters || !bHasSurfaceDomain)
|
|
{
|
|
srf_domain[0] = ON_Interval::EmptyInterval;
|
|
srf_domain[1] = ON_Interval::EmptyInterval;
|
|
srf_scale[0] = 0.0;
|
|
srf_scale[1] = 0.0;
|
|
}
|
|
|
|
if (!bHasTextureCoordinates || !bHasTexturePackingDomain)
|
|
{
|
|
packed_tex_domain[0] = ON_Interval::EmptyInterval;
|
|
packed_tex_domain[1] = ON_Interval::EmptyInterval;
|
|
packed_tex_rotate = false;
|
|
}
|
|
|
|
m_srf_domain[0] = srf_domain[0];
|
|
m_srf_domain[1] = srf_domain[1];
|
|
m_srf_scale[0] = srf_scale[0];
|
|
m_srf_scale[1] = srf_scale[1];
|
|
|
|
m_packed_tex_domain[0] = packed_tex_domain[0];
|
|
m_packed_tex_domain[1] = packed_tex_domain[1];
|
|
m_packed_tex_rotate = packed_tex_rotate;
|
|
|
|
m_top.Destroy();
|
|
|
|
// It is critical to call DoublePrecisionVertices() before
|
|
// we modify m_V[] because DoublePrecisionVertices() will
|
|
// attempt to update the double precision information
|
|
// when it notices that m_V has new vertices added.
|
|
|
|
m_V.Reserve(vcount);
|
|
m_F.Reserve(fcount);
|
|
for (mi = 0; mi < mesh_count; mi++ )
|
|
{
|
|
const unsigned int vcount0_local = m_V.UnsignedCount();
|
|
const unsigned int fcount0_local = m_F.UnsignedCount();
|
|
m = meshes[mi];
|
|
if ( 0 == m )
|
|
continue;
|
|
int vcount1 = m->m_V.Count();
|
|
if ( vcount1 <= 0 )
|
|
continue;
|
|
int fcount1 = m->m_F.Count();
|
|
if ( fcount1 > 0 )
|
|
{
|
|
j = m_F.Count();
|
|
m_F.Append(fcount1,m->m_F.Array());
|
|
fcount1 += j;
|
|
while (j < fcount1)
|
|
{
|
|
vi = m_F[j].vi;
|
|
vi[0] += (int)vcount0_local;
|
|
vi[1] += (int)vcount0_local;
|
|
vi[2] += (int)vcount0_local;
|
|
vi[3] += (int)vcount0_local;
|
|
j++;
|
|
}
|
|
}
|
|
m_V.Append(vcount1,m->m_V.Array());
|
|
if ( m->HasNgons() )
|
|
{
|
|
if ( 0 != m->NgonMap() )
|
|
bHasNgonMap = true;
|
|
m_NgonMap.Destroy();
|
|
unsigned int ngon_count = m->NgonUnsignedCount();
|
|
for ( unsigned int ni = 0; ni < ngon_count; ni++ )
|
|
{
|
|
const ON_MeshNgon* ngon0 = m->Ngon(ni);
|
|
if ( 0 == ngon0 )
|
|
continue;
|
|
if ( 0 == ngon0->m_Vcount && 0 == ngon0->m_Fcount )
|
|
continue;
|
|
ON_MeshNgon* ngon1 = this->m_NgonAllocator.CopyNgon(ngon0);
|
|
if ( 0 == ngon1 )
|
|
continue;
|
|
for ( unsigned int nvi = 0; nvi < ngon1->m_Vcount; nvi++ )
|
|
{
|
|
ngon1->m_vi[nvi] += vcount0_local;
|
|
}
|
|
for ( unsigned int nfi = 0; nfi < ngon1->m_Fcount; nfi++ )
|
|
{
|
|
ngon1->m_fi[nfi] += fcount0_local;
|
|
}
|
|
this->AddNgon(ngon1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bHasDoubles)
|
|
{
|
|
// Now update the double precision vertex locations.
|
|
m_dV.Reserve(vcount);
|
|
for (mi = 0; mi < mesh_count; mi++ )
|
|
{
|
|
m = meshes[mi];
|
|
if ( 0 == m || m->m_dV.Count() <= 0 )
|
|
continue;
|
|
m_dV.Append(m->m_dV.Count(),m->m_dV.Array());
|
|
}
|
|
if (m_dV.Count() != vcount)
|
|
bHasDoubles = false;
|
|
}
|
|
|
|
if ( false == bHasDoubles )
|
|
{
|
|
bHasDoubles = false;
|
|
DestroyDoublePrecisionVertices();
|
|
}
|
|
|
|
if ( bHasVertexNormals )
|
|
{
|
|
m_N.Reserve(vcount);
|
|
for (mi = 0; mi < mesh_count; mi++ )
|
|
{
|
|
m = meshes[mi];
|
|
if ( 0 == m )
|
|
continue;
|
|
m_N.Append(m->m_N.Count(), m->m_N.Array());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_N.Destroy();
|
|
}
|
|
|
|
|
|
if ( bHasFaceNormals )
|
|
{
|
|
m_FN.Reserve(fcount);
|
|
for (mi = 0; mi < mesh_count; mi++ )
|
|
{
|
|
m = meshes[mi];
|
|
if ( 0 == m || m->m_V.Count() <= 0 )
|
|
continue;
|
|
m_FN.Append(m->m_FN.Count(), m->m_FN.Array());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_FN.Destroy();
|
|
}
|
|
|
|
if ( bHasTextureCoordinates )
|
|
{
|
|
m_T.Reserve(vcount);
|
|
for (mi = 0; mi < mesh_count; mi++ )
|
|
{
|
|
m = meshes[mi];
|
|
if ( 0 == m )
|
|
continue;
|
|
m_T.Append(m->m_T.Count(), m->m_T.Array());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_T.Destroy();
|
|
}
|
|
|
|
|
|
if ( bHasSurfaceParameters )
|
|
{
|
|
m_S.Reserve(vcount);
|
|
for (mi = 0; mi < mesh_count; mi++ )
|
|
{
|
|
m = meshes[mi];
|
|
if ( 0 == m )
|
|
continue;
|
|
m_S.Append(m->m_S.Count(), m->m_S.Array());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_S.Destroy();
|
|
}
|
|
|
|
if ( bHasPrincipalCurvatures )
|
|
{
|
|
m_K.Reserve(vcount);
|
|
for (mi = 0; mi < mesh_count; mi++ )
|
|
{
|
|
m = meshes[mi];
|
|
if ( 0 == m )
|
|
continue;
|
|
m_K.Append(m->m_K.Count(), m->m_K.Array());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_K.Destroy();
|
|
}
|
|
|
|
if ( bHasVertexColors )
|
|
{
|
|
m_C.Reserve(vcount);
|
|
for (mi = 0; mi < mesh_count; mi++ )
|
|
{
|
|
m = meshes[mi];
|
|
if ( 0 == m )
|
|
continue;
|
|
m_C.Append(m->m_C.Count(), m->m_C.Array());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_C.Destroy();
|
|
}
|
|
|
|
if ( 0 != m_mesh_parameters )
|
|
{
|
|
for (mi = 0; mi < mesh_count; mi++ )
|
|
{
|
|
m = meshes[mi];
|
|
if ( 0 == m )
|
|
continue;
|
|
if ( 0 == m->m_mesh_parameters || *m_mesh_parameters != *m->m_mesh_parameters )
|
|
{
|
|
delete m_mesh_parameters;
|
|
m_mesh_parameters = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( j = 0; j < 4; j++ )
|
|
{
|
|
if ( m_kstat[j] )
|
|
{
|
|
// will be recomputed if required
|
|
delete m_kstat[j];
|
|
m_kstat[j] = 0;
|
|
}
|
|
}
|
|
|
|
SetClosed(-99);
|
|
SetSolidOrientation(-99);
|
|
InvalidateBoundingBoxes();
|
|
|
|
if ( NgonCount() > 0 && bHasNgonMap )
|
|
CreateNgonMap();
|
|
}
|
|
|
|
void ON_Mesh::Append( const ON_Mesh& m )
|
|
{
|
|
const ON_Mesh* meshes[1];
|
|
meshes[0] = &m;
|
|
Append(1,meshes);
|
|
}
|
|
|
|
|
|
ON_MeshParameters::MESH_STYLE ON_MeshParameters::MeshStyleFromUnsigned(
|
|
unsigned int mesh_style_as_unsigned
|
|
)
|
|
{
|
|
switch (mesh_style_as_unsigned)
|
|
{
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_STYLE::unset_mesh_style);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_STYLE::render_mesh_fast);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_STYLE::render_mesh_quality);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_STYLE::render_mesh_custom);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_STYLE::render_mesh_per_object);
|
|
}
|
|
|
|
ON_ERROR("Invalid mesh_style_as_unsigned parameter");
|
|
return ON_MeshParameters::MESH_STYLE::unset_mesh_style;
|
|
}
|
|
|
|
|
|
ON_MeshParameters::MESH_PARAMETER_ID ON_MeshParameters::MeshParameterIdFromUnsigned(
|
|
unsigned int mesh_parameter_id_as_unsigned
|
|
)
|
|
{
|
|
switch (mesh_parameter_id_as_unsigned)
|
|
{
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::unspecified_mesh_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::bComputeCurvature_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::bSimplePlanes_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::bRefine_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::bJaggedSeams_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::bDoublePrecision_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::mesher_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::texture_range_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::tolerance_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::relative_tolerance_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::min_tolerance_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::min_edge_length_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::max_edge_length_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::grid_aspect_ratio_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::grid_min_count_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::grid_max_count_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::grid_angle_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::grid_amplification_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::refine_angle_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::face_type_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::srf_domain_parameter_id);
|
|
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::bClosedObjectPostProcess_id);
|
|
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::mesher_id);
|
|
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::crv_tess_min_num_segments_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::crv_tess_angle_tol_in_degrees_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::crv_tess_max_dist_between_points_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::crv_tess_min_parametric_ratio_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::bEvaluatorBasedTessellation_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::srf_tess_chord_height_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::srf_tess_angle_tol_in_degrees_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::srf_tess_max_edge_length_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::srf_tess_min_edge_length_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::srf_tess_min_edge_length_ratio_uv_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::srf_tess_max_aspect_ratio_parameter_id);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::smoothing_passes_parameter_id);
|
|
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_MeshParameters::MESH_PARAMETER_ID::max_mesh_parameter_id);
|
|
}
|
|
|
|
ON_ERROR("Invalid mesh_parameter_id_as_unsigned parameter");
|
|
return ON_MeshParameters::MESH_PARAMETER_ID::unspecified_mesh_parameter_id;
|
|
|
|
}
|
|
|
|
void ON_MeshParameters::Internal_SetCharHelper(unsigned int u, unsigned char minc, unsigned char maxc, unsigned char* dest)
|
|
{
|
|
if (u > 255)
|
|
return;
|
|
const unsigned char c = (unsigned char)u;
|
|
if (c >= minc && c <= maxc && c != *dest)
|
|
{
|
|
m_geometry_settings_hash = ON_SHA1_Hash::ZeroDigest;
|
|
*dest = c;
|
|
}
|
|
}
|
|
|
|
void ON_MeshParameters::Internal_SetBoolHelper(bool b, bool* dest)
|
|
{
|
|
b = b ? true : false;
|
|
if (b != *dest)
|
|
{
|
|
m_geometry_settings_hash = ON_SHA1_Hash::ZeroDigest;
|
|
*dest = b;
|
|
}
|
|
}
|
|
|
|
void ON_MeshParameters::Internal_SetDoubleHelper(double x, double minx, double maxx, double* dest)
|
|
{
|
|
if (!ON_IsValid(x))
|
|
return;
|
|
if (ON_UNSET_VALUE != minx && !(x >= minx))
|
|
return;
|
|
if (ON_UNSET_VALUE != maxx && !(x <= maxx))
|
|
return;
|
|
if (x == *dest)
|
|
return;
|
|
m_geometry_settings_hash = ON_SHA1_Hash::ZeroDigest;
|
|
*dest = x;
|
|
}
|
|
|
|
void ON_MeshParameters::Internal_SetIntHelper(int i, int mini, int maxi, int* dest)
|
|
{
|
|
if (!ON_IsValid(i))
|
|
return;
|
|
if (ON_UNSET_INT_INDEX != mini && !(i >= mini))
|
|
return;
|
|
if (ON_UNSET_INT_INDEX != maxi && !(i <= maxi))
|
|
return;
|
|
if (i == *dest)
|
|
return;
|
|
m_geometry_settings_hash = ON_SHA1_Hash::ZeroDigest;
|
|
*dest = i;
|
|
}
|
|
|
|
const bool ON_MeshParameters::CustomSettings() const
|
|
{
|
|
return m_bCustomSettings;
|
|
}
|
|
|
|
void ON_MeshParameters::SetCustomSettings(
|
|
bool bCustomSettings
|
|
)
|
|
{
|
|
Internal_SetBoolHelper(bCustomSettings, &m_bCustomSettings);
|
|
}
|
|
|
|
const bool ON_MeshParameters::CustomSettingsEnabled() const
|
|
{
|
|
return m_bCustomSettingsEnabled;
|
|
}
|
|
|
|
void ON_MeshParameters::SetCustomSettingsEnabled(
|
|
bool bCustomSettingsEnabled
|
|
)
|
|
{
|
|
Internal_SetBoolHelper(bCustomSettingsEnabled, &m_bCustomSettingsEnabled);
|
|
}
|
|
|
|
|
|
const bool ON_MeshParameters::ComputeCurvature() const
|
|
{
|
|
return m_bComputeCurvature;
|
|
}
|
|
|
|
void ON_MeshParameters::SetComputeCurvature(
|
|
bool bComputeCurvature
|
|
)
|
|
{
|
|
Internal_SetBoolHelper(bComputeCurvature, &m_bComputeCurvature);
|
|
}
|
|
|
|
|
|
|
|
const bool ON_MeshParameters::SimplePlanes() const
|
|
{
|
|
return m_bSimplePlanes;
|
|
}
|
|
|
|
void ON_MeshParameters::SetSimplePlanes(
|
|
bool bSimplePlanes
|
|
)
|
|
{
|
|
Internal_SetBoolHelper(bSimplePlanes, &m_bSimplePlanes);
|
|
}
|
|
|
|
void ON_MeshParameters::SetSubDDisplayParameters(
|
|
const ON_SubDDisplayParameters& subd_parameters
|
|
)
|
|
{
|
|
m_subd_mesh_parameters = subd_parameters.EncodeAsUnsignedChar();
|
|
}
|
|
|
|
const ON_SubDDisplayParameters ON_MeshParameters::SubDDisplayParameters() const
|
|
{
|
|
return ON_SubDDisplayParameters::DecodeFromUnsignedChar(m_subd_mesh_parameters);
|
|
}
|
|
|
|
const bool ON_MeshParameters::Refine() const
|
|
{
|
|
return m_bRefine;
|
|
}
|
|
|
|
void ON_MeshParameters::SetRefine(
|
|
bool bRefine
|
|
)
|
|
{
|
|
Internal_SetBoolHelper(bRefine, &m_bRefine);
|
|
}
|
|
|
|
|
|
|
|
const bool ON_MeshParameters::JaggedSeams() const
|
|
{
|
|
return m_bJaggedSeams;
|
|
}
|
|
|
|
void ON_MeshParameters::SetJaggedSeams(
|
|
bool bJaggedSeams
|
|
)
|
|
{
|
|
Internal_SetBoolHelper(bJaggedSeams, &m_bJaggedSeams);
|
|
}
|
|
|
|
const bool ON_MeshParameters::DoublePrecision() const
|
|
{
|
|
return m_bDoublePrecision;
|
|
}
|
|
|
|
void ON_MeshParameters::SetDoublePrecision(
|
|
bool bDoublePrecision
|
|
)
|
|
{
|
|
Internal_SetBoolHelper(bDoublePrecision, &m_bDoublePrecision);
|
|
}
|
|
|
|
const unsigned int ON_MeshParameters::Mesher() const
|
|
{
|
|
return static_cast<unsigned int>(m_mesher);
|
|
|
|
}
|
|
|
|
void ON_MeshParameters::SetMesher(
|
|
unsigned int mesher
|
|
)
|
|
{
|
|
Internal_SetCharHelper(mesher, 0, 1, &m_mesher);
|
|
}
|
|
|
|
const unsigned int ON_MeshParameters::TextureRange() const
|
|
{
|
|
return static_cast<unsigned int>(m_texture_range);
|
|
}
|
|
|
|
void ON_MeshParameters::SetTextureRange(
|
|
unsigned int texture_range
|
|
)
|
|
{
|
|
Internal_SetCharHelper(texture_range, 1, 2, &m_texture_range);
|
|
}
|
|
|
|
const bool ON_MeshParameters::TextureRangeIsValid() const
|
|
{
|
|
return (1 == m_texture_range || 2 == m_texture_range);
|
|
}
|
|
|
|
void ON_MeshParameters::SetTextureRangePictureFrameHack()
|
|
{
|
|
// A 2012 hack to fix cited issues. Serch for calls in core Rhino
|
|
// code for comments with more details.
|
|
// Fix http://mcneel.myjetbrains.com/youtrack/issue/RH-17042
|
|
// Setting mp.TextureRange() = 0 here fixes RH-17042.
|
|
// What's one more hack where picture frames are concerned?
|
|
// As of March 26, tech support felt this picture frame tc bug was
|
|
// so critical that we held up the release of Rhino 5 SR2
|
|
// for this fix.
|
|
//
|
|
// In November of 2012, tech support felt the following
|
|
// five bugs were so critical, Rhino SR1 could not be released.
|
|
// http://dev.mcneel.com/bugtrack/?q=120560 = http://mcneel.myjetbrains.com/youtrack/issue/RH-15423
|
|
// http://dev.mcneel.com/bugtrack/?q=101150 = http://mcneel.myjetbrains.com/youtrack/issue/RH-12450
|
|
// http://dev.mcneel.com/bugtrack/?q=105787 = http://mcneel.myjetbrains.com/youtrack/issue/RH-13203
|
|
// http://dev.mcneel.com/bugtrack/?q=119292 = http://mcneel.myjetbrains.com/youtrack/issue/RH-15277
|
|
// http://dev.mcneel.com/bugtrack/?q=119294 = http://mcneel.myjetbrains.com/youtrack/issue/RH-15279
|
|
//
|
|
// Do not modify this code without testing all the bugs listed above.
|
|
// If you modify this code, put in an extensive comment that includes
|
|
// bug citations.
|
|
unsigned int invalid_texture_range = 0;
|
|
Internal_SetCharHelper(invalid_texture_range, 0, 0, &m_texture_range);
|
|
}
|
|
|
|
|
|
const bool ON_MeshParameters::ClosedObjectPostProcess() const
|
|
{
|
|
return m_bClosedObjectPostProcess;
|
|
}
|
|
void ON_MeshParameters::SetClosedObjectPostProcess(
|
|
bool bClosedObjectPostProcess
|
|
)
|
|
{
|
|
Internal_SetBoolHelper(bClosedObjectPostProcess, &m_bClosedObjectPostProcess);
|
|
}
|
|
|
|
|
|
const double ON_MeshParameters::Tolerance() const
|
|
{
|
|
return m_tolerance;
|
|
}
|
|
|
|
void ON_MeshParameters::SetTolerance(
|
|
double tolerance
|
|
)
|
|
{
|
|
Internal_SetDoubleHelper(tolerance, 0.0, ON_UNSET_VALUE, &m_tolerance);
|
|
}
|
|
|
|
const double ON_MeshParameters::RelativeTolerance() const
|
|
{
|
|
return m_relative_tolerance;
|
|
}
|
|
|
|
void ON_MeshParameters::SetRelativeTolerance(
|
|
double relative_tolerance
|
|
)
|
|
{
|
|
Internal_SetDoubleHelper(relative_tolerance, 0.0, 1.0, &m_relative_tolerance);
|
|
}
|
|
|
|
const double ON_MeshParameters::MinimumTolerance() const
|
|
{
|
|
return m_min_tolerance;
|
|
}
|
|
|
|
void ON_MeshParameters::SetMinimumTolerance(
|
|
double minimum_tolerance
|
|
)
|
|
{
|
|
Internal_SetDoubleHelper(minimum_tolerance, 0.0, ON_UNSET_VALUE, &m_min_tolerance);
|
|
}
|
|
|
|
const double ON_MeshParameters::MinimumEdgeLength() const
|
|
{
|
|
return m_min_edge_length;
|
|
}
|
|
|
|
void ON_MeshParameters::SetMinimumEdgeLength(
|
|
double minimum_edge_length
|
|
)
|
|
{
|
|
// Allow 0.0 or values >= ON_ZERO_TOLERANCE
|
|
const double minx
|
|
= (0.0 == minimum_edge_length)
|
|
? 0.0
|
|
: ON_ZERO_TOLERANCE;
|
|
Internal_SetDoubleHelper(minimum_edge_length, minx, ON_UNSET_VALUE, &m_min_edge_length);
|
|
}
|
|
|
|
const double ON_MeshParameters::MaximumEdgeLength() const { return m_max_edge_length; }
|
|
void ON_MeshParameters::SetMaximumEdgeLength(
|
|
double maximum_edge_length
|
|
)
|
|
{
|
|
Internal_SetDoubleHelper(maximum_edge_length, 0.0, ON_UNSET_VALUE, &m_max_edge_length);
|
|
}
|
|
|
|
const double ON_MeshParameters::GridAspectRatio() const
|
|
{
|
|
return m_grid_aspect_ratio;
|
|
}
|
|
|
|
void ON_MeshParameters::SetGridAspectRatio(
|
|
double grid_aspect_ratio
|
|
)
|
|
{
|
|
Internal_SetDoubleHelper(grid_aspect_ratio, 0.0, ON_UNSET_VALUE, &m_grid_aspect_ratio);
|
|
}
|
|
|
|
const int ON_MeshParameters::GridMinCount() const
|
|
{
|
|
return m_grid_min_count;
|
|
}
|
|
|
|
void ON_MeshParameters::SetGridMinCount(
|
|
int grid_min_count
|
|
)
|
|
{
|
|
Internal_SetIntHelper(grid_min_count, 0, ON_UNSET_INT_INDEX, &m_grid_min_count);
|
|
}
|
|
|
|
const int ON_MeshParameters::GridMaxCount() const
|
|
{
|
|
return m_grid_max_count;
|
|
}
|
|
|
|
void ON_MeshParameters::SetGridMaxCount(
|
|
int grid_max_count
|
|
)
|
|
{
|
|
Internal_SetIntHelper(grid_max_count, 0, ON_UNSET_INT_INDEX, &m_grid_max_count);
|
|
}
|
|
|
|
const double ON_MeshParameters::GridAngleRadians() const
|
|
{
|
|
return m_grid_angle_radians;
|
|
}
|
|
|
|
void ON_MeshParameters::SetGridAngleRadians(
|
|
double grid_angle_radians
|
|
)
|
|
{
|
|
if (grid_angle_radians > ON_PI && grid_angle_radians < (1.0 + ON_EPSILON)*2.0*ON_PI)
|
|
grid_angle_radians = ON_PI;
|
|
Internal_SetDoubleHelper(grid_angle_radians, 0.0, ON_PI, &m_grid_angle_radians);
|
|
}
|
|
|
|
const double ON_MeshParameters::GridAngleDegrees() const
|
|
{
|
|
return ON_DegreesFromRadians(GridAngleRadians());
|
|
}
|
|
|
|
void ON_MeshParameters::SetGridAngleDegrees(
|
|
double grid_angle_degrees
|
|
)
|
|
{
|
|
SetGridAngleRadians(ON_RadiansFromDegrees(grid_angle_degrees));
|
|
}
|
|
|
|
const double ON_MeshParameters::GridAmplification() const
|
|
{
|
|
return m_grid_amplification;
|
|
}
|
|
|
|
void ON_MeshParameters::SetGridAmplification(
|
|
double grid_amplification
|
|
)
|
|
{
|
|
Internal_SetDoubleHelper(grid_amplification, 0.0, ON_UNSET_VALUE, &m_grid_amplification);
|
|
}
|
|
|
|
const double ON_MeshParameters::RefineAngleRadians() const
|
|
{
|
|
return m_refine_angle_radians;
|
|
}
|
|
|
|
void ON_MeshParameters::SetRefineAngleRadians(
|
|
double refine_angle_radians
|
|
)
|
|
{
|
|
if (refine_angle_radians > ON_PI && refine_angle_radians < (1.0 + ON_EPSILON)*2.0*ON_PI)
|
|
refine_angle_radians = ON_PI;
|
|
Internal_SetDoubleHelper(refine_angle_radians, 0.0, ON_PI, &m_refine_angle_radians);
|
|
}
|
|
|
|
|
|
const double ON_MeshParameters::RefineAngleDegrees() const
|
|
{
|
|
return ON_DegreesFromRadians(RefineAngleRadians());
|
|
}
|
|
|
|
void ON_MeshParameters::SetRefineAngleDegrees(
|
|
double refine_angle_degrees
|
|
)
|
|
{
|
|
SetRefineAngleRadians(ON_RadiansFromDegrees(refine_angle_degrees));
|
|
}
|
|
|
|
const unsigned int ON_MeshParameters::FaceType() const
|
|
{
|
|
return static_cast<unsigned int>(m_face_type);
|
|
}
|
|
|
|
void ON_MeshParameters::SetFaceType(
|
|
unsigned int face_type
|
|
)
|
|
{
|
|
Internal_SetCharHelper(face_type, 0, 2, &m_face_type);
|
|
}
|
|
|
|
|
|
ON_MeshParameters::ON_MeshParameters(
|
|
double density,
|
|
double min_edge_length
|
|
)
|
|
{
|
|
// non-default values
|
|
SetTextureRange(0);
|
|
SetGridAngleRadians(0.0);
|
|
SetGridAmplification(0.0);
|
|
SetRefineAngleRadians(0.0);
|
|
SetMinimumEdgeLength(min_edge_length);
|
|
|
|
if ( ON_IsValid(density) )
|
|
{
|
|
if ( density < 0.0 )
|
|
density = 0.0;
|
|
else if ( density > 1.0 )
|
|
density = 1.0;
|
|
SetRelativeTolerance(density);
|
|
SetRefine((density < 0.65));
|
|
SetSimplePlanes((0.0 == density));
|
|
|
|
|
|
unsigned int subd_display_density = ON_SubDDisplayParameters::Default.DisplayDensity();
|
|
|
|
if (density <= ON_ZERO_TOLERANCE)
|
|
subd_display_density = 1;
|
|
else if (density < 1.0/6.0)
|
|
subd_display_density = ON_SubDDisplayParameters::CourseDensity;
|
|
else if (density < 1.0/3.0)
|
|
subd_display_density = (ON_SubDDisplayParameters::DefaultDensity+ON_SubDDisplayParameters::CourseDensity)/2;
|
|
else if (density <= 0.75)
|
|
subd_display_density = ON_SubDDisplayParameters::DefaultDensity;
|
|
else if (density <= 1.0-ON_ZERO_TOLERANCE)
|
|
subd_display_density = (ON_SubDDisplayParameters::DefaultDensity+ON_SubDDisplayParameters::MaximumDensity)/2;
|
|
else if (density >= 1.0 - ON_ZERO_TOLERANCE)
|
|
subd_display_density = ON_SubDDisplayParameters::MaximumDensity;
|
|
|
|
ON_SubDDisplayParameters subd_parameters(ON_SubDDisplayParameters::Default);
|
|
subd_parameters.SetDisplayDensity(subd_display_density);
|
|
SetSubDDisplayParameters(subd_parameters);
|
|
}
|
|
}
|
|
|
|
double ON_MeshParameters::MinimumEdgeLengthFromTolerance(
|
|
double max_edge_length,
|
|
double tolerance
|
|
)
|
|
{
|
|
// The 1.0e-4 is a guess. 1.0e-12 was too small to help with objects
|
|
// like the one in 67885.
|
|
double min_edge_length = 1.0e-4;
|
|
if ( max_edge_length > 0.0 && min_edge_length > 1.0e-3*max_edge_length )
|
|
min_edge_length = 1.0e-3*max_edge_length;
|
|
if ( tolerance > 0.0 && min_edge_length > 1.0e-2*tolerance )
|
|
min_edge_length = 1.0e-2*tolerance;
|
|
return min_edge_length;
|
|
}
|
|
|
|
double ON_MeshParameters::ToleranceFromObjectSize(
|
|
double relative_tolerance,
|
|
double actual_size
|
|
)
|
|
{
|
|
// min_rel_tol creates the most facets
|
|
//const double min_rel_tol = 5.0e-5;
|
|
|
|
//max rel tol creates the fewest facets
|
|
//const double max_rel_tol = 0.1;
|
|
//m_relative_tolerance = 0.0;
|
|
//if ( relative_tolerance <= 0.0 )
|
|
// m_relative_tolerance = max_rel_tol;
|
|
//else if ( relative_tolerance >= 1.0 )
|
|
// m_relative_tolerance = min_rel_tol;
|
|
//else
|
|
//{
|
|
// ON_Interval e(log10(max_rel_tol),log10(min_rel_tol));
|
|
// m_relative_tolerance = pow(10.0,e.ParameterAt(relative_tolerance));
|
|
//}
|
|
|
|
double tol = 0.0;
|
|
double x, e;
|
|
if ( ON_IsValid(relative_tolerance) && ON_IsValid(actual_size)
|
|
&& relative_tolerance > 0.0 && actual_size > 0.0 )
|
|
{
|
|
if ( relative_tolerance > 1.0 )
|
|
relative_tolerance = 1.0;
|
|
|
|
//e = (relative_tolerance*(12.0 + relative_tolerance*(2.0*relative_tolerance - 9.0)));
|
|
//e = (1.0 + relative_tolerance*(8.0 - 4.0*relative_tolerance));
|
|
//e = 1.0 + relative_tolerance*4.0;
|
|
//x = 5.0*pow(10.0,-e);
|
|
|
|
e = (relative_tolerance < 0.5)
|
|
? 1.0 + relative_tolerance*(6.0 - 4.0*relative_tolerance) // 1.0 + 4.0*relative_tolerance
|
|
: 2.0 + 2.0*relative_tolerance;
|
|
x = pow(10.0,-e);
|
|
|
|
tol = actual_size*x;
|
|
}
|
|
return tol;
|
|
}
|
|
|
|
bool operator==(const ON_MeshParameters& a, const ON_MeshParameters& b)
|
|
{
|
|
return 0 == ON_MeshParameters::Compare(a,b);
|
|
}
|
|
|
|
bool operator!=(const ON_MeshParameters& a, const ON_MeshParameters& b)
|
|
{
|
|
return 0 != ON_MeshParameters::Compare(a,b);
|
|
}
|
|
|
|
void ON_MeshParameters::Dump( ON_TextLog& text_log ) const
|
|
{
|
|
text_log.Print(L"Gridding:\n");
|
|
text_log.PushIndent();
|
|
text_log.Print(L"Min grid count = %d\n",m_grid_min_count);
|
|
text_log.Print(L"Max grid count = %d\n",m_grid_max_count);
|
|
text_log.Print(L"Gridding angle = %g radians (%g degrees)\n",GridAngleRadians(),GridAngleDegrees());
|
|
text_log.Print(L"Aspect ratio = %g\n",m_grid_aspect_ratio);
|
|
text_log.Print(L"Amplification = %g\n",m_grid_amplification);
|
|
text_log.PopIndent();
|
|
|
|
text_log.Print(L"Refining:\n");
|
|
text_log.PushIndent();
|
|
text_log.Print(L"Refine = %ls\n", m_bRefine? L"true" : L"false");
|
|
text_log.Print(L"Refine angle = %g radians (%g degrees)\n",RefineAngleRadians(),RefineAngleDegrees());
|
|
text_log.PopIndent();
|
|
|
|
text_log.Print(L"Metrics:\n");
|
|
text_log.PushIndent();
|
|
text_log.Print(L"Tolerance from size 1 object = %g (relative tolerance = %g)\n",ON_MeshParameters::ToleranceFromObjectSize(RelativeTolerance(),1.0),RelativeTolerance());
|
|
text_log.Print(L"Minimum tolerance = %g\n",MinimumTolerance());
|
|
text_log.Print(L"Tolerance = %g\n",m_tolerance);
|
|
text_log.Print(L"Min edge length = %g\n",m_min_edge_length);
|
|
text_log.Print(L"Max edge length = %g\n",m_max_edge_length);
|
|
text_log.PopIndent();
|
|
|
|
text_log.Print(L"Misceleanous:\n");
|
|
text_log.PushIndent();
|
|
text_log.Print(L"Face type = %d\n",m_face_type );
|
|
text_log.Print(L"Compute curvature = %ls\n",m_bComputeCurvature?L"true":L"false");
|
|
text_log.Print(L"Texture range = %d\n",m_texture_range);
|
|
text_log.Print(L"Simple planes = %ls\n",m_bSimplePlanes?L"true":L"false");
|
|
text_log.Print(L"Jagged Seams = %ls\n",m_bJaggedSeams?L"true":L"false");
|
|
text_log.Print(L"Double Precision = %ls\n",m_bDoublePrecision?L"true":L"false");
|
|
text_log.Print(L"Closed object mesh healing = %ls\n",ClosedObjectPostProcess()?L"true":L"false");
|
|
text_log.Print(L"Custom settings = %ls\n",m_bCustomSettings?L"true":L"false");
|
|
|
|
|
|
text_log.PopIndent();
|
|
}
|
|
|
|
static double ON_MeshParameters_SHA1Double(double t, double default_value)
|
|
{
|
|
return (ON_IsValid(t) && t > 0.0) ? t : default_value;
|
|
}
|
|
|
|
int ON_MeshParameters::Compare(
|
|
const ON_MeshParameters& a,
|
|
const ON_MeshParameters& b
|
|
)
|
|
{
|
|
return ON_SHA1_Hash::Compare(a.ContentHash(), b.ContentHash());
|
|
}
|
|
|
|
int ON_MeshParameters::CompareGeometrySettings(
|
|
const ON_MeshParameters& a,
|
|
const ON_MeshParameters& b
|
|
)
|
|
{
|
|
return ON_SHA1_Hash::Compare(a.GeometrySettingsHash(), b.GeometrySettingsHash());
|
|
}
|
|
|
|
ON_SHA1_Hash ON_MeshParameters::ContentHash() const
|
|
{
|
|
// Discuss any changes with Dale Lear
|
|
// Use ON_MeshParameters::GeometrySettingsHash() if you want to ignore any of these values, like m_bComputeCurvature.
|
|
ON_SHA1 sha1;
|
|
sha1.AccumulateBool(m_bCustomSettings);
|
|
sha1.AccumulateBool(m_bComputeCurvature);
|
|
sha1.AccumulateUnsigned32(m_texture_range);
|
|
const ON_SHA1_Hash geometry_settings_hash = GeometrySettingsHash();
|
|
sha1.AccumulateSubHash(geometry_settings_hash);
|
|
return sha1.Hash();
|
|
}
|
|
|
|
void ON_MeshParameters::Internal_AccumulatePangolinParameters(
|
|
const ON_MeshParameters & pangolin_defaults,
|
|
ON_SHA1& sha1
|
|
) const
|
|
{
|
|
// Pangolin parameters
|
|
if (pangolin_defaults.m_bEvaluatorBasedTessellation != m_bEvaluatorBasedTessellation)
|
|
sha1.AccumulateBool(m_bEvaluatorBasedTessellation);
|
|
if (pangolin_defaults.m_curve_tess_min_num_segments != m_curve_tess_min_num_segments)
|
|
sha1.AccumulateInteger32(m_curve_tess_min_num_segments);
|
|
if (pangolin_defaults.m_curve_tess_angle_tol_in_degrees != m_curve_tess_angle_tol_in_degrees)
|
|
sha1.AccumulateDouble(m_curve_tess_angle_tol_in_degrees);
|
|
if (pangolin_defaults.m_curve_tess_max_dist_between_points != m_curve_tess_max_dist_between_points)
|
|
sha1.AccumulateDouble(m_curve_tess_max_dist_between_points);
|
|
if (pangolin_defaults.m_curve_tess_min_parametric_ratio != m_curve_tess_min_parametric_ratio)
|
|
sha1.AccumulateDouble(m_curve_tess_min_parametric_ratio);
|
|
if (pangolin_defaults.m_surface_tess_angle_tol_in_degrees != m_surface_tess_angle_tol_in_degrees)
|
|
sha1.AccumulateDouble(m_surface_tess_angle_tol_in_degrees);
|
|
if (pangolin_defaults.m_surface_tess_max_edge_length != m_surface_tess_max_edge_length)
|
|
sha1.AccumulateDouble(m_surface_tess_max_edge_length);
|
|
if (pangolin_defaults.m_surface_tess_min_edge_length != m_surface_tess_min_edge_length)
|
|
sha1.AccumulateDouble(m_surface_tess_min_edge_length);
|
|
if (pangolin_defaults.m_surface_tess_min_edge_length_ratio_uv != m_surface_tess_min_edge_length_ratio_uv)
|
|
sha1.AccumulateDouble(m_surface_tess_min_edge_length_ratio_uv);
|
|
if (pangolin_defaults.m_surface_tess_max_aspect_ratio != m_surface_tess_max_aspect_ratio)
|
|
sha1.AccumulateDouble(m_surface_tess_max_aspect_ratio);
|
|
if (pangolin_defaults.m_smoothing_passes != m_smoothing_passes)
|
|
sha1.AccumulateInteger32(m_smoothing_passes);
|
|
}
|
|
|
|
ON_SHA1_Hash ON_MeshParameters::GeometrySettingsHash() const
|
|
{
|
|
// Discuss any changes with Dale Lear
|
|
if (m_geometry_settings_hash.IsZeroDigest())
|
|
{
|
|
ON_SHA1 sha1;
|
|
sha1.AccumulateBool(m_bSimplePlanes);
|
|
sha1.AccumulateBool(m_bRefine);
|
|
sha1.AccumulateBool(m_bJaggedSeams);
|
|
sha1.AccumulateUnsigned32(m_mesher);
|
|
sha1.AccumulateDouble(ON_MeshParameters_SHA1Double(m_tolerance,0.0));
|
|
sha1.AccumulateDouble(ON_MeshParameters_SHA1Double(m_relative_tolerance,0.0));
|
|
|
|
// Do not include m_min_tolerance as a geometry setting.
|
|
// It is a runtime lower bound clamp.
|
|
// If it is included here, Rhino will remesh everytime model tolerance changes.
|
|
sha1.AccumulateDouble(ON_MeshParameters_SHA1Double(m_min_edge_length,0.0));
|
|
sha1.AccumulateDouble(ON_MeshParameters_SHA1Double(m_max_edge_length,0.0));
|
|
sha1.AccumulateDouble(ON_MeshParameters_SHA1Double(m_grid_aspect_ratio,0.0));
|
|
sha1.AccumulateInteger32(m_grid_min_count);
|
|
sha1.AccumulateInteger32(m_grid_max_count);
|
|
sha1.AccumulateDouble(ON_MeshParameters_SHA1Double(m_grid_angle_radians,ON_PI));
|
|
sha1.AccumulateDouble(ON_MeshParameters_SHA1Double(m_refine_angle_radians,0.0));
|
|
sha1.AccumulateDouble(ON_MeshParameters_SHA1Double(m_grid_amplification,1.0));
|
|
sha1.AccumulateUnsigned32(m_face_type);
|
|
sha1.AccumulateBool(m_bClosedObjectPostProcess);
|
|
|
|
// The Pangolin parameters and any other, parameters we add in the future,
|
|
// contribute to the SHA1 only when they differ from default values.
|
|
// This keeps old SHA-1 values correct and prevents remeshing when openning
|
|
// old files.
|
|
sha1.AccumulateId(m_mesher_id);
|
|
|
|
// Pangolin parameters
|
|
Internal_AccumulatePangolinParameters(ON_MeshParameters::DefaultMesh,sha1);
|
|
|
|
m_geometry_settings_hash = sha1.Hash();
|
|
}
|
|
return m_geometry_settings_hash;
|
|
}
|
|
|
|
ON_UUID ON_MeshParameters::MesherId() const
|
|
{
|
|
return m_mesher_id;
|
|
}
|
|
|
|
void ON_MeshParameters::SetMesherId(
|
|
ON_UUID id
|
|
)
|
|
{
|
|
if (id != m_mesher_id)
|
|
{
|
|
m_geometry_settings_hash = ON_SHA1_Hash::ZeroDigest;
|
|
m_mesher_id = id;
|
|
}
|
|
}
|
|
|
|
|
|
const ON_MeshParameters::MESH_STYLE ON_MeshParameters::GeometrySettingsRenderMeshStyle(
|
|
const ON_MeshParameters* custom_mp,
|
|
MESH_STYLE no_match_found_result
|
|
) const
|
|
{
|
|
if ( 0 == ON_MeshParameters::CompareGeometrySettings(ON_MeshParameters::FastRenderMesh,*this))
|
|
return ON_MeshParameters::MESH_STYLE::render_mesh_fast;
|
|
|
|
if ( 0 == ON_MeshParameters::CompareGeometrySettings(ON_MeshParameters::QualityRenderMesh,*this))
|
|
return ON_MeshParameters::MESH_STYLE::render_mesh_quality;
|
|
|
|
if ( 0 != custom_mp && 0 == ON_MeshParameters::CompareGeometrySettings(*custom_mp,*this))
|
|
return ON_MeshParameters::MESH_STYLE::render_mesh_custom;
|
|
|
|
return no_match_found_result;
|
|
}
|
|
|
|
const int ON_MeshParameters::GeometrySettingsDensityPercentage(
|
|
int no_match_found_result
|
|
) const
|
|
{
|
|
for ( int n = 0; n <= 100; n++ )
|
|
{
|
|
double density = (50 == n) ? 0.5 : (n/100.0);
|
|
ON_MeshParameters mp_at_n(density);
|
|
|
|
mp_at_n.m_bDoublePrecision = m_bDoublePrecision;
|
|
mp_at_n.m_texture_range = m_texture_range;
|
|
|
|
if ( 0 == ON_MeshParameters::CompareGeometrySettings(mp_at_n,*this) )
|
|
return n;
|
|
}
|
|
return no_match_found_result;
|
|
}
|
|
|
|
ON__UINT32 ON_MeshParameters::DataCRC(ON__UINT32 current_remainder) const
|
|
{
|
|
const ON_SHA1_Hash content_hash = ContentHash();
|
|
const ON__UINT32 crc = ON_CRC32(current_remainder,sizeof(content_hash),&content_hash);
|
|
return crc;
|
|
}
|
|
|
|
bool ON_MeshParameters::Write( ON_BinaryArchive& file ) const
|
|
{
|
|
int minor_version = 4;
|
|
bool rc = file.Write3dmChunkVersion(1,minor_version);
|
|
if (rc)
|
|
{
|
|
if (rc) rc = file.WriteInt(m_bComputeCurvature);
|
|
if (rc) rc = file.WriteInt(m_bSimplePlanes);
|
|
if (rc) rc = file.WriteInt(m_bRefine);
|
|
if (rc) rc = file.WriteInt(m_bJaggedSeams);
|
|
if (rc) rc = file.WriteInt(0); // obsolete m_bWeld field
|
|
if (rc) rc = file.WriteDouble(m_tolerance);
|
|
if (rc) rc = file.WriteDouble(m_min_edge_length);
|
|
if (rc) rc = file.WriteDouble(m_max_edge_length);
|
|
if (rc) rc = file.WriteDouble(m_grid_aspect_ratio);
|
|
if (rc) rc = file.WriteInt(m_grid_min_count);
|
|
if (rc) rc = file.WriteInt(m_grid_max_count);
|
|
if (rc) rc = file.WriteDouble(m_grid_angle_radians);
|
|
if (rc) rc = file.WriteDouble(m_grid_amplification);
|
|
if (rc) rc = file.WriteDouble(m_refine_angle_radians);
|
|
if (rc) rc = file.WriteDouble(5.0*ON_PI/180.0); // obsolete m_combine_angle field
|
|
int mft = m_face_type;
|
|
if ( mft < 0 || mft > 2 )
|
|
{
|
|
ON_ERROR("ON_MeshParameters::Read() - m_face_type out of bounds.");
|
|
mft = 0;
|
|
}
|
|
if (rc) rc = file.WriteInt(mft);
|
|
|
|
// added for chunk version 1.1
|
|
if (rc) rc = file.WriteInt( m_texture_range );
|
|
|
|
// added for chunk version 1.2 - 14 October 2005
|
|
if (rc) rc = file.WriteBool( m_bCustomSettings );
|
|
if (rc) rc = file.WriteDouble( m_relative_tolerance );
|
|
// DO NOT SAVE m_min_tolerance - yet ??
|
|
|
|
// added for chunk version 1.3 - 20 February 2006
|
|
if (rc) rc = file.WriteChar(m_mesher);
|
|
|
|
// added for chunk version 1.4 - 3 March 2011
|
|
if (rc) rc = file.WriteBool( m_bCustomSettingsEnabled );
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_MeshParameters::Read( ON_BinaryArchive& file )
|
|
{
|
|
*this = ON_MeshParameters::DefaultMesh;
|
|
m_geometry_settings_hash = ON_SHA1_Hash::ZeroDigest;
|
|
|
|
int major_version = 0;
|
|
int minor_version = 0;
|
|
bool rc = file.Read3dmChunkVersion(&major_version,&minor_version);
|
|
if ( rc && major_version == 1 )
|
|
{
|
|
int i;
|
|
|
|
i = m_bComputeCurvature;
|
|
if (rc) rc = file.ReadInt(&i);
|
|
m_bComputeCurvature = i?true:false;
|
|
|
|
i = m_bSimplePlanes;
|
|
if (rc) rc = file.ReadInt(&i);
|
|
m_bSimplePlanes = i?true:false;
|
|
|
|
i = m_bRefine;
|
|
if (rc) rc = file.ReadInt(&i);
|
|
m_bRefine = i?true:false;
|
|
|
|
i = m_bJaggedSeams;
|
|
if (rc) rc = file.ReadInt(&i);
|
|
m_bJaggedSeams = i?true:false;
|
|
|
|
int obsolete_m_bWeld;
|
|
if (rc) rc = file.ReadInt(&obsolete_m_bWeld);
|
|
|
|
if (rc) rc = file.ReadDouble(&m_tolerance);
|
|
if (rc) rc = file.ReadDouble(&m_min_edge_length);
|
|
if (rc) rc = file.ReadDouble(&m_max_edge_length);
|
|
if (rc) rc = file.ReadDouble(&m_grid_aspect_ratio);
|
|
if (rc) rc = file.ReadInt(&m_grid_min_count);
|
|
if (rc) rc = file.ReadInt(&m_grid_max_count);
|
|
if (rc) rc = file.ReadDouble(&m_grid_angle_radians);
|
|
if (rc) rc = file.ReadDouble(&m_grid_amplification);
|
|
if (rc) rc = file.ReadDouble(&m_refine_angle_radians);
|
|
double obsolete_m_combine_angle;
|
|
if (rc) rc = file.ReadDouble(&obsolete_m_combine_angle);
|
|
unsigned int face_type = FaceType();
|
|
if (rc) rc = file.ReadInt(&face_type);
|
|
if (rc)
|
|
SetFaceType(face_type);
|
|
|
|
if ( rc && minor_version >= 1 )
|
|
{
|
|
unsigned int texture_range = TextureRange();
|
|
rc = file.ReadInt( &texture_range );
|
|
if (rc)
|
|
SetTextureRange(texture_range);
|
|
if ( rc && minor_version >= 2 )
|
|
{
|
|
rc = file.ReadBool(&m_bCustomSettings);
|
|
if (rc) rc = file.ReadDouble(&m_relative_tolerance);
|
|
if ( rc && minor_version >= 3 )
|
|
{
|
|
unsigned char mesher = (unsigned char)Mesher();
|
|
rc = file.ReadChar(&mesher);
|
|
if (rc)
|
|
SetMesher(mesher);
|
|
if ( rc && minor_version >= 4 )
|
|
{
|
|
rc = file.ReadBool(&m_bCustomSettingsEnabled);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
|
|
ON_MeshCurvatureStats::ON_MeshCurvatureStats()
|
|
{
|
|
Destroy(); // initializes defaults
|
|
}
|
|
|
|
ON_MeshCurvatureStats::~ON_MeshCurvatureStats()
|
|
{}
|
|
|
|
void ON_MeshCurvatureStats::Destroy()
|
|
{
|
|
m_style = ON::unknown_curvature_style;
|
|
m_infinity = 0.0;
|
|
m_count_infinite = 0;
|
|
m_count = 0;
|
|
m_mode = 0.0;
|
|
m_average = 0.0;
|
|
m_adev = 0.0;
|
|
m_range.Set(0.0,0.0);
|
|
}
|
|
|
|
void ON_MeshCurvatureStats::EmergencyDestroy()
|
|
{
|
|
Destroy(); // - no memory is freed so Destroy() will work
|
|
}
|
|
|
|
ON_MeshCurvatureStats::ON_MeshCurvatureStats(const ON_MeshCurvatureStats& src)
|
|
{
|
|
*this = src;
|
|
}
|
|
|
|
ON_MeshCurvatureStats& ON_MeshCurvatureStats::operator=(const ON_MeshCurvatureStats& src)
|
|
{
|
|
if ( this != &src ) {
|
|
m_style = src.m_style;
|
|
m_infinity = src.m_infinity;
|
|
m_count_infinite = src.m_count_infinite;
|
|
m_count = src.m_count;
|
|
m_mode = src.m_mode;
|
|
m_average = src.m_average;
|
|
m_adev = src.m_adev;
|
|
m_range = src.m_range;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
bool ON_MeshCurvatureStats::Write( ON_BinaryArchive& file ) const
|
|
{
|
|
int i;
|
|
bool rc = file.Write3dmChunkVersion(1,1);
|
|
if (rc) {
|
|
i = m_style;
|
|
if (rc) rc = file.WriteInt(i);
|
|
if (rc) rc = file.WriteDouble(m_infinity);
|
|
if (rc) rc = file.WriteInt(m_count_infinite);
|
|
if (rc) rc = file.WriteInt(m_count);
|
|
if (rc) rc = file.WriteDouble(m_mode);
|
|
if (rc) rc = file.WriteDouble(m_average);
|
|
if (rc) rc = file.WriteDouble(m_adev);
|
|
if (rc) rc = file.WriteInterval(m_range);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
bool ON_MeshCurvatureStats::Read( ON_BinaryArchive& file )
|
|
{
|
|
int major_version = 0;
|
|
int minor_version = 0;
|
|
Destroy();
|
|
bool rc = file.Read3dmChunkVersion(&major_version,&minor_version);
|
|
if (rc && major_version == 1) {
|
|
int i=0;
|
|
if (rc) rc = file.ReadInt(&i);
|
|
if (rc) m_style = ON::CurvatureStyle(i);
|
|
if (rc) rc = file.ReadDouble(&m_infinity);
|
|
if (rc) rc = file.ReadInt(&m_count_infinite);
|
|
if (rc) rc = file.ReadInt(&m_count);
|
|
if (rc) rc = file.ReadDouble(&m_mode);
|
|
if (rc) rc = file.ReadDouble(&m_average);
|
|
if (rc) rc = file.ReadDouble(&m_adev);
|
|
if (rc) rc = file.ReadInterval(m_range);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_MeshCurvatureStats::Set( ON::curvature_style kappa_style,
|
|
int Kcount,
|
|
const ON_SurfaceCurvature* K,
|
|
const ON_3fVector* N, // needed for normal sectional curvatures
|
|
double infinity
|
|
)
|
|
{
|
|
bool rc = (Kcount > 0 && K != nullptr);
|
|
|
|
Destroy();
|
|
|
|
if (rc) {
|
|
ON_Workspace ws;
|
|
//ON_3dVector tangent;
|
|
double k;
|
|
double* kappa = ws.GetDoubleMemory(Kcount);
|
|
int i;
|
|
|
|
switch( kappa_style ) {
|
|
case ON::gaussian_curvature:
|
|
m_style = kappa_style;
|
|
m_infinity = ( infinity > 0.0 ) ? infinity : 1.0e20;
|
|
break;
|
|
case ON::mean_curvature: // unsigned mean
|
|
m_style = kappa_style;
|
|
m_infinity = ( infinity > 0.0 ) ? infinity : 1.0e10;
|
|
break;
|
|
case ON::min_curvature: // minimum unsigned radius of curvature
|
|
m_style = kappa_style;
|
|
m_infinity = ( infinity > 0.0 ) ? infinity : 1.0e10;
|
|
break;
|
|
case ON::max_curvature: // maximum unsigned radius of curvature
|
|
m_style = kappa_style;
|
|
m_infinity = ( infinity > 0.0 ) ? infinity : 1.0e10;
|
|
break;
|
|
//case ON::section_curvature_x:
|
|
// if ( !N )
|
|
// kappa_style = ON::unknown_curvature_style;
|
|
// m_style = kappa_style;
|
|
// m_infinity = ( infinity > 0.0 ) ? infinity : 1.0e10;
|
|
// break;
|
|
//case ON::section_curvature_y:
|
|
// if ( !N )
|
|
// kappa_style = ON::unknown_curvature_style;
|
|
// m_style = kappa_style;
|
|
// m_infinity = ( infinity > 0.0 ) ? infinity : 1.0e10;
|
|
// break;
|
|
//case ON::section_curvature_z:
|
|
// if ( !N )
|
|
// kappa_style = ON::unknown_curvature_style;
|
|
// m_style = kappa_style;
|
|
// m_infinity = ( infinity > 0.0 ) ? infinity : 1.0e10;
|
|
// break;
|
|
default:
|
|
rc = false;
|
|
break;
|
|
}
|
|
|
|
for ( i = 0; i < Kcount; i++ ) {
|
|
switch( kappa_style ) {
|
|
case ON::gaussian_curvature:
|
|
k = K[i].GaussianCurvature();
|
|
break;
|
|
case ON::mean_curvature: // unsigned mean
|
|
k = fabs(K[i].MeanCurvature());
|
|
break;
|
|
case ON::min_curvature: // minimum unsigned radius of curvature
|
|
k = fabs(K[i].MinimumRadius());
|
|
break;
|
|
case ON::max_curvature: // maximum unsigned radius of curvature
|
|
k = fabs(K[i].MaximumRadius());
|
|
break;
|
|
//case ON::section_curvature_x:
|
|
// tangent.x = 0.0; tangent.y = -N[i].z; tangent.z = N[i].y;
|
|
// if ( fabs(tangent.y) <= ON_SQRT_EPSILON && fabs(tangent.z) <= ON_SQRT_EPSILON )
|
|
// tangent.Zero();
|
|
// else
|
|
// tangent.Unitize();
|
|
// k = fabs(K[i].NormalCurvature(tangent));
|
|
// break;
|
|
//case ON::section_curvature_y:
|
|
// tangent.x = N[i].z; tangent.y = 0.0; tangent.z = -N[i].x;
|
|
// if ( fabs(tangent.x) <= ON_SQRT_EPSILON && fabs(tangent.z) <= ON_SQRT_EPSILON )
|
|
// tangent.Zero();
|
|
// else
|
|
// tangent.Unitize();
|
|
// k = fabs(K[i].NormalCurvature(tangent));
|
|
// break;
|
|
//case ON::section_curvature_z:
|
|
// tangent.x = -N[i].y; tangent.y = N[i].x; tangent.z = 0.0;
|
|
// if ( fabs(tangent.x) <= ON_SQRT_EPSILON && fabs(tangent.y) <= ON_SQRT_EPSILON )
|
|
// tangent.Zero();
|
|
// else
|
|
// tangent.Unitize();
|
|
// k = fabs(K[i].NormalCurvature(tangent));
|
|
// break;
|
|
default:
|
|
k=0.0;
|
|
break;
|
|
}
|
|
if ( fabs(k) >= m_infinity ) {
|
|
m_count_infinite++;
|
|
continue;
|
|
}
|
|
if ( m_count ) {
|
|
if ( k < m_range.m_t[0] )
|
|
m_range.m_t[0] = k;
|
|
else if ( k > m_range.m_t[1] )
|
|
m_range.m_t[1] = k;
|
|
}
|
|
else {
|
|
m_range.m_t[0] = m_range.m_t[1] = k;
|
|
}
|
|
kappa[m_count++] = k;
|
|
}
|
|
|
|
|
|
if ( m_count == 0 )
|
|
rc = false;
|
|
else {
|
|
// sum curvatures
|
|
ON_SortDoubleArray( ON::sort_algorithm::quick_sort, kappa, m_count );
|
|
|
|
// mode
|
|
m_mode = kappa[m_count/2];
|
|
if ( 0 == (m_count % 2) ) {
|
|
m_mode += kappa[(m_count/2)-1];
|
|
m_mode *= 0.5;
|
|
}
|
|
|
|
// average
|
|
for ( i = 0; i < m_count; i++ ) {
|
|
m_average += kappa[i];
|
|
}
|
|
m_average = m_average/m_count;
|
|
|
|
// average deviation
|
|
for ( i = 0; i < m_count; i++ ) {
|
|
m_adev += fabs(kappa[i] - m_average);
|
|
}
|
|
m_adev = m_adev/m_count;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
struct EDGEINFO
|
|
{
|
|
int fi[2]; // m_F[] triangles on either side of the edge
|
|
int vi[4]; // potential m_V[] quad indices
|
|
int flag; // 0 = available,
|
|
// 1 = side of quad,
|
|
// 2 = bndry or nonmanifold edge,
|
|
// 3 = edge crease angle > angle_tol_radians
|
|
// 4 = edge is not longest side of triangle
|
|
// 5 = edge length is zero
|
|
// 6 = edge would be a boundary if mesh were exploded
|
|
// 7 = quad would not be convex
|
|
// 8 = if the edge were removed, the quad would not pass the min_diagonal_length_ratio test (not "square" enough)
|
|
// 16 = tha diagnoal is too short to remove in the first pass that makes the "obvious" quads.
|
|
double length;
|
|
};
|
|
|
|
bool ON_Mesh::ConvertTrianglesToQuads(
|
|
double angle_tol_radians,
|
|
double min_diagonal_length_ratio
|
|
)
|
|
{
|
|
ON_Workspace ws;
|
|
|
|
ON_3dPoint QCornerPoints[4];
|
|
ON_3dVector QuadSides[4];
|
|
ON_3dVector QuadCornerNormals[4];
|
|
double QuadCornerDots[4];
|
|
|
|
double d;
|
|
|
|
int i, ii, jj;
|
|
int diagonal_count;
|
|
const int* f0vi;
|
|
const int* f1vi;
|
|
const int* fei;
|
|
|
|
diagonal_count = 0;
|
|
|
|
if ( angle_tol_radians < 0.0
|
|
|| !ON_IsValid(angle_tol_radians)
|
|
|| angle_tol_radians > 0.5*ON_PI )
|
|
{
|
|
// 2.5 degrees
|
|
angle_tol_radians = 0.043633231299858239423092269212215;
|
|
}
|
|
else if ( angle_tol_radians < ON_ZERO_TOLERANCE )
|
|
{
|
|
angle_tol_radians = ON_ZERO_TOLERANCE;
|
|
}
|
|
|
|
angle_tol_radians = cos(angle_tol_radians);
|
|
if ( angle_tol_radians < 0.5 )
|
|
angle_tol_radians = 0.5;
|
|
else if ( angle_tol_radians > 1.0-ON_SQRT_EPSILON )
|
|
angle_tol_radians = 1.0-ON_SQRT_EPSILON;
|
|
|
|
const ON_MeshTopology& top = Topology();
|
|
|
|
if ( !HasFaceNormals() )
|
|
ComputeFaceNormals();
|
|
|
|
if ( min_diagonal_length_ratio < ON_ZERO_TOLERANCE )
|
|
min_diagonal_length_ratio = ON_ZERO_TOLERANCE;
|
|
|
|
double max_diagonal_length_ratio = 1.0/min_diagonal_length_ratio;
|
|
|
|
if( min_diagonal_length_ratio > max_diagonal_length_ratio )
|
|
{
|
|
d = min_diagonal_length_ratio;
|
|
min_diagonal_length_ratio = max_diagonal_length_ratio;
|
|
max_diagonal_length_ratio = d;
|
|
}
|
|
|
|
//double rel_tol = ON_SQRT_EPSILON;
|
|
double rel_tol = 8.0*ON_SQRT_EPSILON; // to fix RR 51543
|
|
if ( min_diagonal_length_ratio > 1.0-rel_tol )
|
|
min_diagonal_length_ratio = 1.0-rel_tol;
|
|
if ( max_diagonal_length_ratio < 1.0+rel_tol )
|
|
max_diagonal_length_ratio = 1.0+rel_tol;
|
|
|
|
|
|
const int face_count = m_F.Count();
|
|
int* face_flag = ws.GetIntMemory(face_count);
|
|
for ( i = 0; i < face_count; i++ )
|
|
{
|
|
f0vi = m_F[i].vi;
|
|
face_flag[i] = ( f0vi[2] == f0vi[3] ) ? 0 : 1;
|
|
}
|
|
|
|
const int edge_count = top.m_tope.Count();
|
|
struct EDGEINFO* EI = (struct EDGEINFO*)ws.GetMemory(edge_count*sizeof(*EI));
|
|
|
|
for ( i = 0; i < edge_count; i++ )
|
|
{
|
|
struct EDGEINFO& ei = EI[i];
|
|
const ON_MeshTopologyEdge& tope = top.m_tope[i];
|
|
ei.flag = 0;
|
|
ei.vi[0] = top.m_topv[tope.m_topvi[0]].m_vi[0];
|
|
ei.vi[2] = top.m_topv[tope.m_topvi[1]].m_vi[0];
|
|
ei.length = m_V[ei.vi[0]].DistanceTo(m_V[ei.vi[2]]);
|
|
if ( !(ei.length > 0.0) || !ON_IsValid(ei.length) )
|
|
{
|
|
// ei.length is a nan or worse
|
|
ei.flag = 5;
|
|
}
|
|
else if ( tope.m_topf_count != 2 )
|
|
{
|
|
ei.flag = 2;
|
|
}
|
|
else
|
|
{
|
|
ei.fi[0] = tope.m_topfi[0];
|
|
ei.fi[1] = tope.m_topfi[1];
|
|
if (face_flag[ei.fi[0]] || face_flag[ei.fi[1]] )
|
|
{
|
|
ei.flag = 1;
|
|
}
|
|
else if ( m_FN[ei.fi[0]] * m_FN[ei.fi[1]] < angle_tol_radians )
|
|
{
|
|
ei.flag = 3;
|
|
}
|
|
else
|
|
{
|
|
f0vi = m_F[ei.fi[0]].vi;
|
|
f1vi = m_F[ei.fi[1]].vi;
|
|
ei.flag = 6;
|
|
for ( ii = 0; ii < 3 && 0 != ei.flag; ii++ )
|
|
{
|
|
for (jj = 0; jj < 3; jj++)
|
|
{
|
|
if ( f0vi[ii] == f1vi[jj]
|
|
&& f0vi[(ii+1)%3] == f1vi[(jj+2)%3]
|
|
&& f0vi[(ii+2)%3] != f1vi[(jj+1)%3] )
|
|
{
|
|
if ( ei.fi[0] > ei.fi[1] )
|
|
{
|
|
jj = ei.fi[0]; ei.fi[0] = ei.fi[1]; ei.fi[1] = jj;
|
|
}
|
|
ei.vi[0] = f0vi[ii];
|
|
ei.vi[1] = f1vi[(jj+1)%3];
|
|
ei.vi[2] = f0vi[(ii+1)%3];
|
|
ei.vi[3] = f0vi[(ii+2)%3];
|
|
|
|
// convexity test
|
|
QCornerPoints[0] = m_V[ei.vi[0]];
|
|
QCornerPoints[1] = m_V[ei.vi[1]];
|
|
QCornerPoints[2] = m_V[ei.vi[2]];
|
|
QCornerPoints[3] = m_V[ei.vi[3]];
|
|
QuadSides[0] = QCornerPoints[1] - QCornerPoints[0];
|
|
QuadSides[1] = QCornerPoints[2] - QCornerPoints[1];
|
|
QuadSides[2] = QCornerPoints[3] - QCornerPoints[2];
|
|
QuadSides[3] = QCornerPoints[0] - QCornerPoints[3];
|
|
// Actually, opposite normal but
|
|
QuadCornerNormals[0] = ON_CrossProduct(QuadSides[0], QuadSides[3]);
|
|
QuadCornerNormals[1] = ON_CrossProduct(QuadSides[1], QuadSides[0]);
|
|
QuadCornerNormals[2] = ON_CrossProduct(QuadSides[2], QuadSides[1]);
|
|
QuadCornerNormals[3] = ON_CrossProduct(QuadSides[3], QuadSides[2]);
|
|
QuadCornerDots[0] = QuadCornerNormals[0] * QuadCornerNormals[1];
|
|
QuadCornerDots[1] = QuadCornerNormals[1] * QuadCornerNormals[2];
|
|
QuadCornerDots[2] = QuadCornerNormals[2] * QuadCornerNormals[3];
|
|
QuadCornerDots[3] = QuadCornerNormals[3] * QuadCornerNormals[0];
|
|
if (QuadCornerDots[0] > 0.0)
|
|
{
|
|
if (QuadCornerDots[1] > 0.0 && QuadCornerDots[2] > 0.0 && QuadCornerDots[3] > 0.0)
|
|
{
|
|
// If the diagonal were removed, the resulting quad would be convex
|
|
// so this edge is a candidate for being removed.
|
|
ei.flag = 0;
|
|
break;
|
|
}
|
|
}
|
|
else if (QuadCornerDots[0] < 0.0)
|
|
{
|
|
if (QuadCornerDots[1] < 0.0 && QuadCornerDots[2] < 0.0 && QuadCornerDots[3] < 0.0)
|
|
{
|
|
// If the diagonal were removed, the resulting quad would be convex
|
|
// so this edge is a candidate for being removed.
|
|
ei.flag = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If the diagonal were removed, the resulting quad would not be convex
|
|
// or one of the QuadCornerDots[] values is a nan.
|
|
// In either case, this edge will not be removed.
|
|
ei.flag = 7;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( i = 0; i < edge_count; i++ )
|
|
{
|
|
struct EDGEINFO& ei = EI[i];
|
|
if ( 0 != ei.flag )
|
|
continue;
|
|
|
|
d = m_V[ei.vi[1]].DistanceTo(m_V[ei.vi[3]]);
|
|
d /= ei.length;
|
|
if (d < min_diagonal_length_ratio || d > max_diagonal_length_ratio)
|
|
{
|
|
// quad wouldn't be square enough
|
|
ei.flag = 8;
|
|
continue;
|
|
}
|
|
|
|
if (face_count > 2)
|
|
{
|
|
// It is CRITICAL that the length compare use >=.
|
|
// Otherwise tesselations of equailateral triangles
|
|
// will not work right in this fuction.
|
|
fei = top.m_topf[ei.fi[0]].m_topei;
|
|
if ((i != fei[0] && EI[fei[0]].length >= ei.length)
|
|
|| (i != fei[1] && EI[fei[1]].length >= ei.length)
|
|
|| (i != fei[2] && EI[fei[2]].length >= ei.length)
|
|
)
|
|
{
|
|
// diagonal is not strictly longest in this triangle.
|
|
// A 2nd pass would be required to determine which
|
|
// diagonals could be removed.
|
|
// See the file "RH-5481 IssueClarified.3dm" in
|
|
// http://mcneel.myjetbrains.com/youtrack/issue/RH-5481
|
|
// for an example.
|
|
ei.flag = 16;
|
|
continue;
|
|
}
|
|
|
|
// It is CRITICAL that the length compare use >=.
|
|
// Otherwise tesselations of equailateral triangles
|
|
// will not work right in this fuction.
|
|
fei = top.m_topf[ei.fi[1]].m_topei;
|
|
if ((i != fei[0] && EI[fei[0]].length >= ei.length)
|
|
|| (i != fei[1] && EI[fei[1]].length >= ei.length)
|
|
|| (i != fei[2] && EI[fei[2]].length >= ei.length)
|
|
)
|
|
{
|
|
// diagonal is not strictly longest in this triangle
|
|
// A 2nd pass would be required to determine which
|
|
// diagonals could be removed.
|
|
// See the file "RH-5481 IssueClarified.3dm" in
|
|
// http://mcneel.myjetbrains.com/youtrack/issue/RH-5481
|
|
// for an example.
|
|
ei.flag = 16;
|
|
continue;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
ei.flag = 0;
|
|
|
|
diagonal_count++;
|
|
}
|
|
|
|
|
|
// Dale Lear 2016-March-28
|
|
// If we are going to fix bugs like the issue raised in
|
|
// http://mcneel.myjetbrains.com/youtrack/issue/RH-5481
|
|
// Additional passes need to be made here that decide which diagonals to
|
|
// remove hen the diagonal is shorter than a resulting quad side.
|
|
// See the file "RH-5481 IssueClarified.3dm" in
|
|
// http://mcneel.myjetbrains.com/youtrack/issue/RH-5481
|
|
// for an example.
|
|
|
|
|
|
if ( diagonal_count > 0 )
|
|
{
|
|
DestroyTree();
|
|
DestroyPartition();
|
|
m_top.Destroy();
|
|
m_invalid_count = 0;
|
|
m_triangle_count = 0;
|
|
m_quad_count = 0;
|
|
for (i = 0; i < edge_count; i++)
|
|
{
|
|
struct EDGEINFO& ei = EI[i];
|
|
if ( ei.flag )
|
|
continue;
|
|
memcpy( m_F[ei.fi[0]].vi, ei.vi, 4*sizeof(ei.vi[0]) );
|
|
memset( m_F[ei.fi[1]].vi, 0xFF, 4*sizeof(ei.vi[0]) );
|
|
}
|
|
CullDegenerateFaces();
|
|
}
|
|
|
|
return (diagonal_count > 0 );
|
|
}
|
|
|
|
bool ON_Mesh::ConvertQuadsToTriangles()
|
|
{
|
|
double planar_tolerance = ON_UNSET_VALUE;
|
|
double angle_tolerance_radians = ON_UNSET_VALUE;
|
|
unsigned int split_method = 1;
|
|
|
|
ConvertNonPlanarQuadsToTriangles(
|
|
planar_tolerance,
|
|
angle_tolerance_radians,
|
|
split_method
|
|
);
|
|
|
|
return ( QuadCount() == 0 && TriangleCount() == FaceCount() ) ? true : false;
|
|
}
|
|
|
|
double ON_TriangleArea3d(ON_3dPoint A, ON_3dPoint B, ON_3dPoint C)
|
|
{
|
|
// speed this up if needed
|
|
return 0.5*ON_CrossProduct(B-A,C-A).Length();
|
|
}
|
|
|
|
double ON_TriangleArea2d(ON_2dPoint A, ON_2dPoint B, ON_2dPoint C)
|
|
{
|
|
const double ABx = B.x-A.x;
|
|
const double ABy = B.y-A.y;
|
|
const double CAx = C.x-A.x;
|
|
const double CAy = C.y-A.y;
|
|
return 0.5*fabs((ABx*CAy) - (ABy*CAx));
|
|
}
|
|
|
|
unsigned int ON_Mesh::ConvertNonPlanarQuadsToTriangles(
|
|
double planar_tolerance,
|
|
double angle_tolerance_radians,
|
|
unsigned int split_method
|
|
)
|
|
{
|
|
bool bDeleteNgonsContainingSplitQuads = false;
|
|
return ConvertNonPlanarQuadsToTriangles(
|
|
planar_tolerance,
|
|
angle_tolerance_radians,
|
|
split_method,
|
|
bDeleteNgonsContainingSplitQuads
|
|
);
|
|
}
|
|
|
|
static int ON_CompareUnsignedInt(const unsigned int* i0, const unsigned int* i1)
|
|
{
|
|
if ( i0 < i1 )
|
|
return -1;
|
|
if ( i0 > i1 )
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
unsigned int ON_Mesh::ConvertNonPlanarQuadsToTriangles(
|
|
double planar_tolerance,
|
|
double angle_tolerance_radians,
|
|
unsigned int split_method,
|
|
bool bDeleteNgonsContainingSplitQuads
|
|
)
|
|
{
|
|
const unsigned int face_count0 = FaceUnsignedCount();
|
|
if ( face_count0 <= 0 )
|
|
return 0;
|
|
|
|
const ON_3dPointListRef vertex_list(this);
|
|
const unsigned int vertex_count = vertex_list.PointCount();
|
|
if ( vertex_count <= 0 )
|
|
return 0;
|
|
|
|
const unsigned int quad_count0 = (unsigned int)QuadCount();
|
|
|
|
if ( quad_count0 <= 0 )
|
|
return 0;
|
|
|
|
const bool bSplitAllQuads = (ON_UNSET_VALUE == planar_tolerance && ON_UNSET_VALUE == angle_tolerance_radians);
|
|
|
|
if (false == bSplitAllQuads && !(planar_tolerance >= 0.0) && !(angle_tolerance_radians >= 0.0))
|
|
return 0;
|
|
|
|
if ( 0 == split_method )
|
|
split_method = 1;
|
|
|
|
if ( split_method < 1 || split_method > 6 )
|
|
return 0;
|
|
|
|
const bool bHasFaceNormals = HasFaceNormals();
|
|
|
|
unsigned int ngon_count = HasNgons() ? NgonUnsignedCount() : 0;
|
|
unsigned int ngon_index = ON_UNSET_UINT_INDEX;
|
|
|
|
unsigned int fi;
|
|
int idmin;
|
|
double d0, d1, dmin, d;
|
|
ON_3dPoint P[4];
|
|
|
|
// use SetCapacity() instead of Reserve() because it's unlikely
|
|
// this mesh will have anything else added and we don't want to
|
|
// waste memory.
|
|
if ( ((unsigned int)m_F.Capacity()) < face_count0 + quad_count0 )
|
|
m_F.SetCapacity( face_count0 + quad_count0 );
|
|
if ( bHasFaceNormals && ((unsigned int)m_FN.Capacity()) < face_count0 + quad_count0 )
|
|
m_FN.SetCapacity( face_count0 + quad_count0 );
|
|
|
|
const double rel_tol = 8.0*( vertex_list.DoublePrecision() ? ON_EPSILON : ON_FLOAT_EPSILON );
|
|
|
|
ON_3dVector FN;
|
|
|
|
// If bDeleteNgonsContainingSplitQuads = true, then uint_buffer[] is a list
|
|
// of ngons to delete.
|
|
//
|
|
// if bDeleteNgonsContainingSplitQuads = false, then ... //
|
|
// If the mesh has n-gons, then ngon_new_face_count[ngon_index]
|
|
// is the number of new faces to add to an existing ngon.
|
|
// This happens when an n-gon contains a quad that is divided
|
|
// into two triangles.
|
|
ON_SimpleArray<unsigned int> uint_buffer;
|
|
|
|
|
|
unsigned int* ngon_new_face_count = nullptr;
|
|
bool bUpdateNgons = false;
|
|
bool bDeleteNgonMap = false;
|
|
if ( ngon_count > 0 )
|
|
{
|
|
if (bDeleteNgonsContainingSplitQuads)
|
|
{
|
|
uint_buffer.Reserve(64);
|
|
}
|
|
else
|
|
{
|
|
bDeleteNgonMap = (0 == NgonMap());
|
|
if (0 == CreateNgonMap())
|
|
{
|
|
ngon_count = 0;
|
|
bDeleteNgonMap = true;
|
|
bDeleteNgonsContainingSplitQuads = true;
|
|
}
|
|
else
|
|
{
|
|
uint_buffer.Reserve(ngon_count);
|
|
uint_buffer.SetCount(ngon_count);
|
|
uint_buffer.Zero();
|
|
ngon_new_face_count = uint_buffer.Array();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
for ( fi = 0; fi < face_count0; fi++ )
|
|
{
|
|
ON_MeshFace& f0 = m_F[fi];
|
|
if ( ngon_count > 0 )
|
|
{
|
|
ngon_index = m_NgonMap[fi];
|
|
if ( ngon_index >= ngon_count )
|
|
{
|
|
ngon_index = ON_UNSET_UINT_INDEX;
|
|
m_NgonMap[fi] = ngon_index;
|
|
}
|
|
}
|
|
|
|
if ( false == f0.IsValid(vertex_count) )
|
|
continue;
|
|
|
|
if ( false == f0.IsQuad() )
|
|
continue;
|
|
|
|
if (planar_tolerance >= 0.0 || angle_tolerance_radians >= 0.0)
|
|
{
|
|
if (true == f0.IsPlanar(planar_tolerance, angle_tolerance_radians, vertex_list, 0))
|
|
continue;
|
|
}
|
|
|
|
P[0] = vertex_list[f0.vi[0]];
|
|
P[1] = vertex_list[f0.vi[1]];
|
|
P[2] = vertex_list[f0.vi[2]];
|
|
P[3] = vertex_list[f0.vi[3]];
|
|
d0 = P[0].DistanceTo(P[2]);
|
|
d1 = P[1].DistanceTo(P[3]);
|
|
|
|
// if quad is degenerate, just turn it into a triangle
|
|
idmin = -1;
|
|
dmin = ((d0<=d1)?d0:d1)*rel_tol;
|
|
if ( dmin > ON_ZERO_TOLERANCE )
|
|
dmin = ON_ZERO_TOLERANCE;
|
|
d = P[0].DistanceTo(P[1]);
|
|
if ( d < dmin )
|
|
{
|
|
idmin = 0;
|
|
dmin = d;
|
|
}
|
|
d = P[1].DistanceTo(P[2]);
|
|
if ( d < dmin )
|
|
{
|
|
idmin = 1;
|
|
dmin = d;
|
|
}
|
|
d = P[2].DistanceTo(P[3]);
|
|
if ( d < dmin )
|
|
{
|
|
idmin = 2;
|
|
dmin = d;
|
|
}
|
|
d = P[3].DistanceTo(P[0]);
|
|
if ( d < dmin )
|
|
{
|
|
idmin = 3;
|
|
dmin = d;
|
|
}
|
|
|
|
|
|
if ( !(d0 > 0.0) )
|
|
{
|
|
if ( !(d1 > 0.0) )
|
|
continue;
|
|
// d0 = 0 or is invalid and d1 > 0
|
|
// force split along v[1],v[3]
|
|
idmin = -1;
|
|
d0 = 1.0;
|
|
d1 = 0.0;
|
|
}
|
|
else if ( !(d1 > 0.0) )
|
|
{
|
|
// d1 = 0 or is invalid and d0 > 0
|
|
// force split along v[0],v[1]
|
|
idmin = -1;
|
|
d1 = 1.0;
|
|
d0 = 0.0;
|
|
}
|
|
|
|
m_quad_count--;
|
|
m_triangle_count++;
|
|
if ( 0 == idmin ) // m_V[f0.vi[0]] == m_V[f0.vi[1]] (nearly)
|
|
{
|
|
// degenerate quad - remove duplicate vertex
|
|
f0.vi[0] = f0.vi[1];
|
|
f0.vi[1] = f0.vi[2];
|
|
f0.vi[2] = f0.vi[3];
|
|
}
|
|
else if ( 1 == idmin ) // m_V[f0.vi[1]] == m_V[f0.vi[2]] (nearly)
|
|
{
|
|
// degenerate quad - remove duplicate vertex
|
|
int vi0 = f0.vi[0];
|
|
f0.vi[0] = f0.vi[2];
|
|
f0.vi[1] = f0.vi[3];
|
|
f0.vi[2] = vi0;
|
|
f0.vi[3] = vi0;
|
|
}
|
|
else if ( 2 == idmin ) // m_V[f0.vi[2]] == m_V[f0.vi[3]] (nearly)
|
|
{
|
|
// degenerate quad - remove duplicate vertex
|
|
f0.vi[2] = f0.vi[1];
|
|
f0.vi[1] = f0.vi[0];
|
|
f0.vi[0] = f0.vi[3];
|
|
f0.vi[3] = f0.vi[2];
|
|
}
|
|
else if ( 3 == idmin ) // m_V[f0.vi[3]] == m_V[f0.vi[0]] (nearly)
|
|
{
|
|
// degenerate quad - remove duplicate vertex
|
|
f0.vi[3] = f0.vi[2];
|
|
}
|
|
else
|
|
{
|
|
// split non-degenerate quad into two triangles
|
|
|
|
if ( ngon_count > 0 )
|
|
{
|
|
if (bDeleteNgonsContainingSplitQuads)
|
|
{
|
|
if (ON_UNSET_INT_INDEX != ngon_index)
|
|
{
|
|
uint_buffer.Append(ngon_index);
|
|
bUpdateNgons = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// update n-gon map
|
|
m_NgonMap.Append(ngon_index);
|
|
if (ngon_index < ngon_count)
|
|
{
|
|
// The quad was in an n-gon and the new face index
|
|
// will be appended to the n-gons m_fi[] list.
|
|
ngon_new_face_count[ngon_index]++;
|
|
bUpdateNgons = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
ON_MeshFace& f1 = m_F.AppendNew();
|
|
bool bSplitAlongDiagonal02 = true;
|
|
switch(split_method)
|
|
{
|
|
case 1:
|
|
if ( d1 < d0 )
|
|
bSplitAlongDiagonal02 = false;
|
|
break;
|
|
|
|
case 2:
|
|
if ( d1 > d0 )
|
|
bSplitAlongDiagonal02 = false;
|
|
break;
|
|
|
|
case 3:
|
|
case 4:
|
|
{
|
|
double a0 = ON_TriangleArea3d(P[0],P[1],P[2]) + ON_TriangleArea3d(P[0],P[2],P[3]);
|
|
double a1 = ON_TriangleArea3d(P[0],P[1],P[3]) + ON_TriangleArea3d(P[1],P[2],P[3]);
|
|
if (a0 == a1)
|
|
{
|
|
if (d1 < d0)
|
|
bSplitAlongDiagonal02 = false;
|
|
}
|
|
else if ( 3 == split_method )
|
|
{
|
|
// minimize area
|
|
if (a1 < a0)
|
|
bSplitAlongDiagonal02 = false;
|
|
}
|
|
else
|
|
{
|
|
// maximize area
|
|
if (a1 > a0)
|
|
bSplitAlongDiagonal02 = false;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 5:
|
|
case 6:
|
|
{
|
|
ON_3dVector corner_normals[4];
|
|
if (f0.GetCornerNormals(vertex_list, corner_normals))
|
|
{
|
|
double a[2];
|
|
for (unsigned int i = 0; i < 2; i++)
|
|
{
|
|
a[i]
|
|
= (ON_UNSET_VALUE != corner_normals[i].x && ON_UNSET_VALUE != corner_normals[i + 2].x)
|
|
? corner_normals[i] * corner_normals[i + 2]
|
|
: 2.0; // some valid number bigger than 1.0
|
|
}
|
|
|
|
if (a[0] == a[1])
|
|
{
|
|
if (d1 < d)
|
|
bSplitAlongDiagonal02 = false;
|
|
}
|
|
else if (5 == split_method)
|
|
{
|
|
// minimize angle
|
|
if (a[0] > a[1])
|
|
bSplitAlongDiagonal02 = false;
|
|
}
|
|
else
|
|
{
|
|
// maximize angle
|
|
if (a[0] < a[1])
|
|
bSplitAlongDiagonal02 = false;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if ( bSplitAlongDiagonal02 )
|
|
{
|
|
f1.vi[0] = f0.vi[0];
|
|
f1.vi[1] = f0.vi[2];
|
|
f1.vi[2] = f0.vi[3];
|
|
f1.vi[3] = f1.vi[2];
|
|
f0.vi[3] = f0.vi[2];
|
|
}
|
|
else
|
|
{
|
|
f1.vi[0] = f0.vi[1];
|
|
f1.vi[1] = f0.vi[2];
|
|
f1.vi[2] = f0.vi[3];
|
|
f1.vi[3] = f1.vi[2];
|
|
f0.vi[2] = f0.vi[3];
|
|
}
|
|
|
|
if ( bHasFaceNormals )
|
|
{
|
|
m_F[fi].ComputeFaceNormal(vertex_list,FN);
|
|
m_FN[fi] = FN;
|
|
m_F[m_F.Count()-1].ComputeFaceNormal(vertex_list,FN);
|
|
m_FN.AppendNew() = FN;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( face_count0 != m_F.UnsignedCount() )
|
|
{
|
|
// we added some triangles which means cached topology
|
|
// and partition information is no longer valid.
|
|
DestroyTopology();
|
|
DestroyPartition();
|
|
}
|
|
|
|
if ( bUpdateNgons )
|
|
{
|
|
if (bDeleteNgonsContainingSplitQuads)
|
|
{
|
|
const unsigned int uint_buffer_count = uint_buffer.UnsignedCount();
|
|
if (uint_buffer_count > 0)
|
|
{
|
|
uint_buffer.QuickSort(ON_CompareUnsignedInt);
|
|
unsigned int ngon_index_count = 0;
|
|
unsigned int* ngon_index_list = uint_buffer.Array();
|
|
ngon_index = ON_UNSET_UINT_INDEX;
|
|
for (unsigned int i = 0; i < uint_buffer_count; i++ )
|
|
{
|
|
if (ngon_index == uint_buffer[i])
|
|
continue;
|
|
ngon_index = uint_buffer[i];
|
|
ngon_index_list[ngon_index_count++] = ngon_index;
|
|
}
|
|
RemoveNgons(ngon_index_count, ngon_index_list);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_NgonMap.UnsignedCount() == m_F.UnsignedCount())
|
|
{
|
|
// allocate room for new face indices
|
|
for (ngon_index = 0; ngon_index < ngon_count; ngon_index++)
|
|
{
|
|
if (ngon_new_face_count[ngon_index] <= 0)
|
|
continue;
|
|
ON_MeshNgon* ngon = this->m_Ngon[ngon_index];
|
|
if (0 == ngon)
|
|
continue;
|
|
unsigned int Fcount0 = ngon->m_Fcount;
|
|
ngon = m_NgonAllocator.ReallocateNgon(ngon, ngon->m_Vcount, ngon->m_Fcount + ngon_new_face_count[ngon_index]);
|
|
ngon->m_Fcount = Fcount0;
|
|
m_Ngon[ngon_index] = ngon;
|
|
}
|
|
|
|
// Add new faces to appropriate ngon face index lists
|
|
for (fi = face_count0; fi < m_F.UnsignedCount(); fi++)
|
|
{
|
|
ngon_index = m_NgonMap[fi];
|
|
if (ngon_index < ngon_count)
|
|
{
|
|
ON_MeshNgon* ngon = m_Ngon[ngon_index];
|
|
if (ngon)
|
|
ngon->m_fi[ngon->m_Fcount++] = fi;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bDeleteNgonMap = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bDeleteNgonMap )
|
|
m_NgonMap.Destroy();
|
|
|
|
return quad_count0 - QuadCount();
|
|
}
|
|
|
|
bool ON_Mesh::CountQuads()
|
|
{
|
|
const int fcount = FaceCount();
|
|
const int vcount = VertexCount();
|
|
int fi;
|
|
m_quad_count = 0;
|
|
m_triangle_count = 0;
|
|
m_invalid_count = 0;
|
|
for ( fi = 0; fi < fcount; fi++ ) {
|
|
const ON_MeshFace& f = m_F[fi];
|
|
if ( f.IsValid(vcount) ) {
|
|
if ( f.IsTriangle() )
|
|
m_triangle_count++;
|
|
else
|
|
m_quad_count++;
|
|
}
|
|
else
|
|
m_invalid_count++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ON_Mesh::ComputeVertexNormals()
|
|
{
|
|
bool rc = false;
|
|
const int fcount = FaceCount();
|
|
const int vcount = VertexCount();
|
|
int vi, fi, j;
|
|
ON_3fVector n;
|
|
|
|
if ( fcount > 0 && vcount > 0 ) {
|
|
rc = HasFaceNormals();
|
|
if ( !rc )
|
|
rc = ComputeFaceNormals();
|
|
if ( rc ) {
|
|
ON_Workspace ws;
|
|
//double* face_area = ws.GetDoubleMemory(fcount);
|
|
int* vfcount = ws.GetIntMemory( vcount );
|
|
memset ( vfcount, 0, vcount*sizeof(*vfcount) );
|
|
|
|
// count number of faces that use each vertex
|
|
for ( fi = 0; fi < fcount; fi++ ) {
|
|
ON_MeshFace& f = m_F[fi];
|
|
if ( f.IsValid(vcount) ) {
|
|
vfcount[f.vi[0]]++;
|
|
vfcount[f.vi[1]]++;
|
|
vfcount[f.vi[2]]++;
|
|
if ( f.IsQuad() )
|
|
vfcount[f.vi[3]]++;
|
|
}
|
|
}
|
|
|
|
// set vfi[vi][] = array of face indices that use vertex vi
|
|
int** vfi = (int**)ws.GetMemory( vcount*sizeof(vfi[0] ) );
|
|
{
|
|
int scratch_sz = 0;
|
|
for ( vi = 0; vi < vcount; vi++ ) {
|
|
scratch_sz += vfcount[vi];
|
|
}
|
|
int* scratch = ws.GetIntMemory(scratch_sz);
|
|
for ( vi = 0; vi < vcount; vi++ ) {
|
|
if ( vfcount[vi] ) {
|
|
vfi[vi] = scratch;
|
|
scratch += vfcount[vi];
|
|
}
|
|
vfcount[vi] = 0;
|
|
}
|
|
}
|
|
for ( fi = 0; fi < fcount; fi++ ) {
|
|
ON_MeshFace& f = m_F[fi];
|
|
if ( f.IsValid(vcount) ) {
|
|
vi = f.vi[0]; vfi[vi][vfcount[vi]++] = fi;
|
|
vi = f.vi[1]; vfi[vi][vfcount[vi]++] = fi;
|
|
vi = f.vi[2]; vfi[vi][vfcount[vi]++] = fi;
|
|
if ( f.IsQuad() ) {
|
|
vi = f.vi[3]; vfi[vi][vfcount[vi]++] = fi;
|
|
}
|
|
}
|
|
}
|
|
|
|
// average face normals to get an estimate for a vertex normal
|
|
m_N.SetCapacity(vcount);
|
|
m_N.SetCount(0);
|
|
for ( vi = 0; vi < vcount; vi++ )
|
|
{
|
|
n = ON_3fVector::ZeroVector;
|
|
for ( j = vfcount[vi]-1; j >= 0; j-- )
|
|
{
|
|
n += m_FN[vfi[vi][j]];
|
|
}
|
|
if ( !n.Unitize() )
|
|
{
|
|
// this vertex is not used by a face or the face normals cancel out.
|
|
// set a unit z normal and press on.
|
|
n.Set(0,0,1);
|
|
}
|
|
m_N.Append(n);
|
|
}
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Mesh::NormalizeTextureCoordinates()
|
|
{
|
|
ON_2fPoint t0;//, t1;
|
|
int ti;
|
|
const int vertex_count = m_V.Count();
|
|
bool rc = HasSurfaceParameters();
|
|
if ( rc )
|
|
{
|
|
const ON_2dPoint* S = m_S.Array();
|
|
ON_Interval udom = m_srf_domain[0];
|
|
ON_Interval vdom = m_srf_domain[1];
|
|
rc = udom.IsIncreasing() && vdom.IsIncreasing();
|
|
if ( !rc )
|
|
{
|
|
udom.Set(S[0].x,S[0].x);
|
|
vdom.Set(S[0].y,S[0].y);
|
|
for ( ti = 1; ti < vertex_count; ti++ )
|
|
{
|
|
if ( S[ti].x < udom.m_t[0] ) udom.m_t[0] = S[ti].x;
|
|
else if ( S[ti].x > udom.m_t[1] ) udom.m_t[1] = S[ti].x;
|
|
if ( S[ti].y < vdom.m_t[0] ) vdom.m_t[0] = S[ti].y;
|
|
else if ( S[ti].y > vdom.m_t[1] ) vdom.m_t[1] = S[ti].y;
|
|
}
|
|
rc = udom.IsIncreasing() && vdom.IsIncreasing();
|
|
}
|
|
|
|
if (rc)
|
|
{
|
|
m_T.Reserve(vertex_count);
|
|
m_T.SetCount(0);
|
|
for (ti = 0; ti < vertex_count; ti++ )
|
|
{
|
|
t0.x = (float)udom.NormalizedParameterAt(S[ti].x);
|
|
t0.y = (float)vdom.NormalizedParameterAt(S[ti].y);
|
|
m_T.Append(t0);
|
|
}
|
|
m_packed_tex_domain[0].Set(0.0,1.0);
|
|
m_packed_tex_domain[1].Set(0.0,1.0);
|
|
m_packed_tex_rotate = false;
|
|
m_Ttag.SetDefaultSurfaceParameterMappingTag();
|
|
if ( m_mesh_parameters )
|
|
m_mesh_parameters->SetTextureRange(1);
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool ON_Mesh::TransposeSurfaceParameters()
|
|
{
|
|
// swap m_srf_domain
|
|
ON_Interval temp = m_srf_domain[0];
|
|
m_srf_domain[0] = m_srf_domain[1];
|
|
m_srf_domain[1] = temp;
|
|
|
|
double t = m_srf_scale[0];
|
|
m_srf_scale[0] = m_srf_scale[1];
|
|
m_srf_scale[1] = t;
|
|
|
|
int S_count = m_S.Count();
|
|
ON_2dPoint* S_array = m_S.Array();
|
|
for (int i = 0; i < S_count; i++ )
|
|
{
|
|
ON_2dPoint& S = S_array[i];
|
|
t = S.x; S.x = S.y; S.y = t;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ON_Mesh::HasPackedTextureRegion() const
|
|
{
|
|
return ( ON_IsValid(m_srf_scale[0])
|
|
&& m_srf_scale[0] > 0.0
|
|
&& ON_IsValid(m_srf_scale[1])
|
|
&& m_srf_scale[1] > 0.0
|
|
&& m_packed_tex_domain[0].IsInterval()
|
|
&& m_packed_tex_domain[1].IsInterval()
|
|
&& 0.0 <= m_packed_tex_domain[0].Min()
|
|
&& m_packed_tex_domain[0].Max() <= 1.0
|
|
&& 0.0 <= m_packed_tex_domain[1].Min()
|
|
&& m_packed_tex_domain[1].Max() <= 1.0
|
|
&& ( fabs(m_packed_tex_domain[0].Length()) < 1.0
|
|
|| fabs(m_packed_tex_domain[1].Length()) < 1.0)
|
|
);
|
|
}
|
|
|
|
|
|
bool ON_Mesh::TransposeTextureCoordinates()
|
|
{
|
|
if ( !HasTextureCoordinates() )
|
|
return false;
|
|
|
|
const int vcnt = m_T.Count();
|
|
int i;
|
|
|
|
bool bPackedRegion = HasPackedTextureRegion();
|
|
|
|
bool bSrfParamTag = (!m_Ttag.IsSet() || m_Ttag.IsDefaultSurfaceParameterMapping());
|
|
|
|
if ( bPackedRegion && bSrfParamTag )
|
|
{
|
|
// The region of the bitmap the texture uses
|
|
// cannot change. The texture coordinates
|
|
// themselves get reflected in the subrectangle
|
|
// about either the lowerleft to upperright
|
|
// diagonal (llur = true) or the lowerright
|
|
// to upperleft diagonal (llur = false).
|
|
bool bRevU = m_packed_tex_domain[0].IsDecreasing();
|
|
bool bRevV = m_packed_tex_domain[1].IsDecreasing();
|
|
bool llur = (bRevU == bRevV) ? true : false;
|
|
if ( m_packed_tex_rotate )
|
|
llur = !llur;
|
|
|
|
ON_Interval TD[2];
|
|
TD[0] = m_packed_tex_domain[0];
|
|
TD[1] = m_packed_tex_domain[1];
|
|
TD[0].MakeIncreasing();
|
|
TD[1].MakeIncreasing();
|
|
for( i=0; i<vcnt; i++)
|
|
{
|
|
ON_2fPoint tc = m_T[i];
|
|
double x = TD[0].NormalizedParameterAt(tc[0]);
|
|
double y = TD[1].NormalizedParameterAt(tc[1]);
|
|
if(!llur)
|
|
{
|
|
x = 1.0-x;
|
|
y = 1.0-y;
|
|
}
|
|
double s = TD[0].ParameterAt(y);
|
|
double t = TD[1].ParameterAt(x);
|
|
m_T[i].Set((float)s,(float)t);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
float f;
|
|
for(i=0; i<vcnt; i++)
|
|
{
|
|
ON_2fPoint& tc = m_T[i];
|
|
f = tc.x; tc.x = tc.y; tc.y = f;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ON_Mesh::ReverseTextureCoordinates( int dir )
|
|
{
|
|
if ( dir < 0 || dir > 1 || !HasTextureCoordinates() )
|
|
return false;
|
|
|
|
bool bPackedRegion = HasPackedTextureRegion();
|
|
|
|
bool bSrfParamTag = (!m_Ttag.IsSet() || m_Ttag.IsDefaultSurfaceParameterMapping());
|
|
|
|
const int vcount = m_T.Count();
|
|
int i;
|
|
if ( bPackedRegion && bSrfParamTag )
|
|
{
|
|
// The region of the bitmap the texture uses
|
|
// cannot change. The texture coordinates
|
|
// themselves get reflected in the subrectangle
|
|
// about either the lowerleft to upperright
|
|
// diagonal (llur = true) or the lowerright
|
|
// to upperleft diagonal (llur = false).
|
|
if ( m_packed_tex_rotate )
|
|
dir = 1-dir;
|
|
const ON_Interval tex_dom = m_packed_tex_domain[dir];
|
|
double s;
|
|
m_packed_tex_domain[dir].Swap(); // Swap() is correct - Reverse() is wrong.
|
|
for (i = 0; i < vcount; i++ )
|
|
{
|
|
ON_2fPoint& tc = m_T[i];
|
|
s = 1.0 - tex_dom.NormalizedParameterAt(tc[dir]);
|
|
if ( dir )
|
|
tc.y = (float)tex_dom.ParameterAt(s);
|
|
else
|
|
tc.x = (float)tex_dom.ParameterAt(s);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < vcount; i++ )
|
|
{
|
|
ON_2fPoint& tc = m_T[i];
|
|
if ( dir )
|
|
tc.y = 1.0f-tc.y;
|
|
else
|
|
tc.x = 1.0f-tc.x;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ON_Mesh::ReverseSurfaceParameters( int dir )
|
|
{
|
|
if ( dir < 0 || dir > 1 || !HasSurfaceParameters() )
|
|
return false;
|
|
if ( m_srf_domain[dir].IsIncreasing() )
|
|
m_srf_domain[dir].Reverse();
|
|
int i, vcount = m_S.Count();
|
|
for (i = 0; i < vcount; i++)
|
|
{
|
|
ON_2dPoint& S = m_S[i];
|
|
if ( dir )
|
|
S.y = -S.y;
|
|
else
|
|
S.x = -S.x;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool ON_Mesh::EvaluateMeshGeometry( const ON_Surface& srf )
|
|
{
|
|
bool rc = false;
|
|
const int vcount = VertexCount();
|
|
const bool bHasSurfaceParameters = HasSurfaceParameters();
|
|
if ( bHasSurfaceParameters )
|
|
{
|
|
const bool bHasDoublePrecisionVertices = this->HasDoublePrecisionVertices();
|
|
if (false == bHasDoublePrecisionVertices)
|
|
this->m_dV.Destroy();
|
|
const bool bHasVertexNormals = HasVertexNormals();
|
|
m_N.SetCapacity(vcount);
|
|
int vi, side, hint[2];
|
|
ON_3dPoint point;
|
|
ON_3dVector normal, Ds, Dt, Dss, Dst, Dtt, K1, K2;
|
|
const ON_2dPoint* srf_st;
|
|
double s, t, kgauss, kmean;
|
|
side = 0;
|
|
hint[0] = 0;
|
|
hint[1] = 0;
|
|
const double smax = srf.Domain(0)[1];
|
|
const double tmax = srf.Domain(1)[1];
|
|
if ( HasPrincipalCurvatures() )
|
|
{
|
|
for ( vi = 0; vi < vcount; vi++ )
|
|
{
|
|
//ON_2fPoint& tex = m_T[vi];
|
|
// convert texture coordinates to normalizied texture coordinates
|
|
srf_st = &m_S[vi];
|
|
s = srf_st->x;
|
|
t = srf_st->y;
|
|
// 19 April 2006 Dale Lear - added side setter so singular normals
|
|
// are correctly evaluated RR 12482
|
|
side = ( smax == s ) ? ((tmax == t) ? 3 : 2) : ((tmax == t) ? 4 : 1);
|
|
srf.Ev2Der( s, t, point, Ds, Dt, Dss, Dst, Dtt, side, hint );
|
|
ON_EvNormal( side, Ds, Dt, Dss, Dst, Dtt, normal );
|
|
ON_EvPrincipalCurvatures( Ds, Dt, Dss, Dst, Dtt, normal,
|
|
&kgauss, &kmean,
|
|
&m_K[vi].k1, &m_K[vi].k2,
|
|
K1, K2 ); //m_K[vi].e1, m_K[vi].e2 );
|
|
m_V[vi] = ON_3fPoint(&point.x); // use ON_3fPoint double* conversion (quiets gcc)
|
|
if (bHasDoublePrecisionVertices)
|
|
m_dV[vi] = point;
|
|
if ( bHasVertexNormals )
|
|
m_N[vi] = ON_3fVector(&normal.x); // use ON_3fVector double* conversion (quiets gcc)
|
|
}
|
|
InvalidateCurvatureStats();
|
|
}
|
|
else if ( bHasVertexNormals )
|
|
{
|
|
for ( vi = 0; vi < vcount; vi++ )
|
|
{
|
|
//ON_2fPoint& tex = m_T[vi];
|
|
srf_st = &m_S[vi];
|
|
s = srf_st->x;
|
|
t = srf_st->y;
|
|
// 19 April 2006 Dale Lear - added side setter so singular normals
|
|
// are correctly evaluated RR 12482
|
|
side = ( smax == s ) ? ((tmax == t) ? 3 : 2) : ((tmax == t) ? 4 : 1);
|
|
srf.EvNormal( s, t, point, normal, side, hint );
|
|
m_V[vi] = ON_3fPoint(&point.x); // use ON_3fPoint double* conversion (quiets gcc)
|
|
if (bHasDoublePrecisionVertices)
|
|
m_dV[vi] = point;
|
|
m_N[vi] = ON_3fVector(&normal.x); // use ON_3fVector double* conversion (quiets gcc)
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for ( vi = 0; vi < vcount; vi++ )
|
|
{
|
|
//ON_2fPoint& tex = m_T[vi];
|
|
srf_st = &m_S[vi];
|
|
s = srf_st->x;
|
|
t = srf_st->y;
|
|
srf.EvPoint( s, t, point, side, hint );
|
|
m_V[vi] = ON_3fPoint(&point.x);
|
|
if (bHasDoublePrecisionVertices)
|
|
m_dV[vi] = point;
|
|
}
|
|
}
|
|
if ( HasFaceNormals() )
|
|
{
|
|
ComputeFaceNormals();
|
|
}
|
|
rc = true;
|
|
|
|
m_Ctag.Default();
|
|
InvalidateVertexBoundingBox();
|
|
InvalidateVertexNormalBoundingBox();
|
|
DeleteMeshParameters();
|
|
DestroyTree();
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
void ON_Mesh::SetMeshParameters( const ON_MeshParameters& mp )
|
|
{
|
|
DeleteMeshParameters();
|
|
m_mesh_parameters = new ON_MeshParameters(mp);
|
|
}
|
|
|
|
const ON_MeshParameters* ON_Mesh::MeshParameters() const
|
|
{
|
|
return m_mesh_parameters;
|
|
}
|
|
|
|
void ON_Mesh::DeleteMeshParameters()
|
|
{
|
|
if ( m_mesh_parameters ) {
|
|
delete m_mesh_parameters;
|
|
m_mesh_parameters = 0;
|
|
}
|
|
}
|
|
|
|
static bool isValid3fPoint(const ON_3fPoint* a)
|
|
{
|
|
return (ON_IS_VALID_FLOAT(a->x) && ON_IS_VALID_FLOAT(a->y) && ON_IS_VALID_FLOAT(a->z)) ? true : false;
|
|
}
|
|
|
|
static int compare3fPoint( const ON_3fPoint* a, const ON_3fPoint* b )
|
|
{
|
|
const bool aValid = isValid3fPoint(a);
|
|
const bool bValid = isValid3fPoint(b);
|
|
if (aValid != bValid)
|
|
return (aValid ? -1 : 1); // invalid points sort to end.
|
|
if (false == aValid)
|
|
return 0; // all invalid points are "equal"
|
|
if ( a->x < b->x ) return -1;
|
|
if ( a->x > b->x ) return 1;
|
|
if ( a->y < b->y ) return -1;
|
|
if ( a->y > b->y ) return 1;
|
|
if ( a->z < b->z ) return -1;
|
|
if ( a->z > b->z ) return 1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static bool isValid3dPoint(const ON_3dPoint* a)
|
|
{
|
|
return (ON_IS_VALID(a->x) && ON_IS_VALID(a->y) && ON_IS_VALID(a->z)) ? true : false;
|
|
}
|
|
|
|
static int compare3dPoint( const ON_3dPoint* a, const ON_3dPoint* b )
|
|
{
|
|
const bool aValid = isValid3dPoint(a);
|
|
const bool bValid = isValid3dPoint(b);
|
|
if (aValid != bValid)
|
|
return (aValid ? -1 : 1); // invalid points sort to end.
|
|
if (false == aValid)
|
|
return 0; // all invalid points are "equal"
|
|
if ( a->x < b->x ) return -1;
|
|
if ( a->x > b->x ) return 1;
|
|
if ( a->y < b->y ) return -1;
|
|
if ( a->y > b->y ) return 1;
|
|
if ( a->z < b->z ) return -1;
|
|
if ( a->z > b->z ) return 1;
|
|
return 0;
|
|
}
|
|
|
|
typedef int (*ON_COMPAR_LPVOID_LPVOID)(const void*,const void*);
|
|
|
|
static
|
|
unsigned int GetRemoveDegenerateFacesPointMap(
|
|
unsigned int pt_count,
|
|
const ON_3fPoint* fV,
|
|
const ON_3dPoint* dV,
|
|
ON_SimpleArray<unsigned int>& pt_map
|
|
)
|
|
{
|
|
// Faster than ON_Mesh::GetVertexLocationIds()
|
|
// This static is used only in CullDegenerateFaces().
|
|
//
|
|
// Builds a mapping array, pt_map[], such that the length of pt_map[]
|
|
// is pt_count and pt_map[i] == pt_map[j] if and only if pt[i] == pt[j]
|
|
// as 3d points. The values in map[] run from 0 to max_pt_index.
|
|
unsigned int vt0, vt1;
|
|
ON_3fPoint fp0;
|
|
ON_3dPoint dp0;
|
|
unsigned int* map;
|
|
unsigned int* index;
|
|
unsigned int max_pt_index = 0;
|
|
if (pt_count > 0 && (nullptr != dV || nullptr != fV))
|
|
{
|
|
index = (unsigned int*)onmalloc(pt_count*sizeof(*index));
|
|
|
|
if ( dV )
|
|
ON_Sort( ON::sort_algorithm::quick_sort, index, dV, pt_count, sizeof(*dV), (ON_COMPAR_LPVOID_LPVOID)compare3dPoint );
|
|
else
|
|
ON_Sort( ON::sort_algorithm::quick_sort, index, fV, pt_count, sizeof(*fV), (ON_COMPAR_LPVOID_LPVOID)compare3fPoint );
|
|
|
|
pt_map.SetCapacity( pt_count );
|
|
pt_map.SetCount( pt_count );
|
|
map = pt_map.Array();
|
|
for (vt0 = 0; vt0 < pt_count; vt0++)
|
|
map[vt0] = ON_UNSET_UINT_INDEX;
|
|
|
|
if ( dV )
|
|
{
|
|
for (vt0 = 0; vt0 < pt_count; vt0 = vt1, max_pt_index++)
|
|
{
|
|
dp0 = dV[index[vt0]];
|
|
for ( vt1=vt0+1; vt1<pt_count && 0==compare3dPoint(&dp0,dV+index[vt1]); vt1++ ) {
|
|
// empty
|
|
}
|
|
while ( vt0 < vt1 ) {
|
|
map[index[vt0++]] = max_pt_index;
|
|
}
|
|
}
|
|
|
|
// invalid points are sorted to the end
|
|
vt1 = pt_count;
|
|
while (vt1-- > 0)
|
|
{
|
|
if (isValid3dPoint(dV + index[vt1]))
|
|
break;
|
|
map[index[vt1]] = ON_UNSET_UINT_INDEX; // invalid point get invalid index
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (vt0 = 0; vt0 < pt_count; vt0 = vt1, max_pt_index++)
|
|
{
|
|
fp0 = fV[index[vt0]];
|
|
for ( vt1=vt0+1; vt1<pt_count && 0==compare3fPoint(&fp0,fV+index[vt1]); vt1++ ) {
|
|
// empty
|
|
}
|
|
while ( vt0 < vt1 ) {
|
|
map[index[vt0++]] = max_pt_index;
|
|
}
|
|
}
|
|
|
|
// invalid points are sorted to the end
|
|
vt1 = pt_count;
|
|
while (vt1-- > 0)
|
|
{
|
|
if (isValid3fPoint(fV + index[vt1]))
|
|
break;
|
|
map[index[vt1]] = ON_UNSET_UINT_INDEX; // invalid point get invalid index
|
|
}
|
|
}
|
|
onfree(index);
|
|
}
|
|
if ( max_pt_index == 0 )
|
|
pt_map.Destroy();
|
|
|
|
return max_pt_index;
|
|
}
|
|
|
|
unsigned int ON_Mesh::CullDegenerateFaces()
|
|
{
|
|
const unsigned int face_count0 = m_F.UnsignedCount();
|
|
|
|
DeleteComponents(
|
|
nullptr,
|
|
0,
|
|
true, // bIgnoreInvalidComponents
|
|
true, // bRemoveDegenerateFaces
|
|
false, // bRemoveUnusedVertices
|
|
true // bRemoveEmptyNgons
|
|
);
|
|
|
|
const unsigned int face_count1 = m_F.UnsignedCount();
|
|
|
|
return (face_count0 > face_count1) ? face_count0 - face_count1 : 0;
|
|
}
|
|
|
|
unsigned int ON_Mesh::CullDegenerates()
|
|
{
|
|
const int mesh_vertex_count0 = VertexCount();
|
|
const int mesh_face_count0 = FaceCount();
|
|
const int mesh_quad_count0 = QuadCount();
|
|
const int mesh_tri_count0 = TriangleCount();
|
|
|
|
// now cull bad faces, invalid vertices, and unreferenced vertices
|
|
DeleteComponents(
|
|
nullptr,
|
|
0,
|
|
true,
|
|
true,
|
|
true,
|
|
true
|
|
);
|
|
|
|
const int mesh_vertex_count1 = VertexUnsignedCount();
|
|
const int mesh_face_count1 = FaceUnsignedCount();
|
|
const int mesh_quad_count1 = QuadCount();
|
|
const int mesh_tri_count1 = TriangleCount();
|
|
if (
|
|
mesh_vertex_count0 == mesh_vertex_count1
|
|
&& mesh_face_count0 == mesh_face_count1
|
|
&& mesh_quad_count0 == mesh_quad_count1
|
|
&& mesh_tri_count0 == mesh_tri_count1
|
|
)
|
|
return 0;
|
|
|
|
int rc = abs(mesh_vertex_count1 - mesh_vertex_count0) + abs(mesh_face_count1 - mesh_face_count0);
|
|
if (0 == rc)
|
|
{
|
|
// need to return nonzero if a degenerate quad got changed into a triangle
|
|
rc = abs(mesh_quad_count1 - mesh_quad_count0);
|
|
if (0 == rc)
|
|
{
|
|
rc = abs(mesh_tri_count1 - mesh_tri_count0);
|
|
}
|
|
}
|
|
|
|
return (unsigned int)rc;
|
|
}
|
|
|
|
int ON_Mesh::CullUnusedVertices()
|
|
{
|
|
const unsigned int vcount0 = m_V.UnsignedCount();
|
|
|
|
DeleteComponents(
|
|
nullptr, // ci_list
|
|
0, // ci_count
|
|
true, // bIgnoreInvalidComponents
|
|
false, // bRemoveDegenerateFaces
|
|
true, // bRemoveUnusedVertices
|
|
false // bRemoveEmptyNgons
|
|
);
|
|
|
|
if ( 0 == m_V.UnsignedCount() )
|
|
Destroy();
|
|
|
|
return ((int)(vcount0 - m_V.UnsignedCount()));
|
|
}
|
|
|
|
bool ON_Mesh::Compact()
|
|
{
|
|
unsigned int meshVcount = m_V.UnsignedCount();
|
|
unsigned int meshFcount = m_F.UnsignedCount();
|
|
|
|
if (m_FN.UnsignedCount() != meshFcount)
|
|
m_FN.Destroy();
|
|
|
|
if (m_N.UnsignedCount() != meshVcount)
|
|
m_N.Destroy();
|
|
|
|
if (m_T.UnsignedCount() != meshVcount)
|
|
m_T.Destroy();
|
|
|
|
if (m_S.UnsignedCount() != meshVcount)
|
|
m_S.Destroy();
|
|
|
|
if (m_K.UnsignedCount() != meshVcount)
|
|
m_K.Destroy();
|
|
|
|
if (m_C.UnsignedCount() != meshVcount)
|
|
m_C.Destroy();
|
|
|
|
if (m_H.UnsignedCount() != meshVcount)
|
|
m_H.Destroy();
|
|
|
|
CullUnusedVertices();
|
|
|
|
m_V.Shrink();
|
|
m_F.Shrink();
|
|
m_N.Shrink();
|
|
m_FN.Shrink();
|
|
m_K.Shrink();
|
|
m_C.Shrink();
|
|
m_S.Shrink();
|
|
m_T.Shrink();
|
|
|
|
return true;
|
|
}
|
|
|
|
void ON_Mesh::Cleanup(
|
|
bool bRemoveNgons,
|
|
bool bRemoveDegenerateFaces,
|
|
bool bCompact
|
|
)
|
|
{
|
|
V4V5_DestroyNgonList(); // old junk
|
|
|
|
if ( bRemoveNgons )
|
|
SetNgonCount(0);
|
|
|
|
if (bRemoveDegenerateFaces)
|
|
CullDegenerateFaces();
|
|
|
|
DestroyRuntimeCache(true);
|
|
|
|
if ( bCompact )
|
|
Compact();
|
|
}
|
|
|
|
void ON_Mesh::Cleanup(bool bRemoveNgons)
|
|
{
|
|
const bool bRemoveDegenerateFaces = true;
|
|
const bool bCompact = true;
|
|
Cleanup(
|
|
bRemoveNgons,
|
|
bRemoveDegenerateFaces,
|
|
bCompact
|
|
);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
//
|
|
// ON_SurfaceCurvature
|
|
//
|
|
double ON_SurfaceCurvature::GaussianCurvature() const
|
|
{
|
|
return k1*k2;
|
|
}
|
|
|
|
double ON_SurfaceCurvature::MeanCurvature() const
|
|
{
|
|
return 0.5*(k1+k2);
|
|
}
|
|
|
|
double ON_SurfaceCurvature::MinimumRadius() const
|
|
{
|
|
double k;
|
|
k = (fabs(k1)>=fabs(k2)) ? fabs(k1) : fabs(k2); // k = maximum directional curvature
|
|
k = ( k > 1.0e-300 ) ? 1.0/k : 1.0e300; // 1/k = minimum radius of curvature
|
|
return k;
|
|
}
|
|
|
|
double ON_SurfaceCurvature::MaximumRadius() const
|
|
{
|
|
double k;
|
|
if ( k1*k2 <= 0.0 ) {
|
|
// if principal curvatures have opposite signs, there
|
|
// is a direction with zero directional curvature
|
|
k = 0.0;
|
|
}
|
|
else {
|
|
k = (fabs(k1)<=fabs(k2)) ? fabs(k1) : fabs(k2);
|
|
}
|
|
// k = minimum directional curvature
|
|
k = ( k > 1.0e-300 ) ? 1.0/k : 1.0e300; // 1/k = maximum radius of curvature
|
|
return k;
|
|
}
|
|
|
|
//double ON_SurfaceCurvature::NormalCurvature(const ON_3dVector& tangent) const
|
|
//{
|
|
// double c = tangent*e1;
|
|
// double s = tangent*e2;
|
|
// return k1*c*c + k2*s*s;
|
|
//}
|
|
|
|
//double ON_SurfaceCurvature::NormalSectionCurvature( const ON_3dVector& section_normal, const ON_3dVector& surface_normal ) const
|
|
//{
|
|
// ON_3dVector tangent = ON_CrossProduct( section_normal, surface_normal );
|
|
// if ( fabs(tangent.x) <= ON_SQRT_EPSILON && fabs(tangent.y) <= ON_SQRT_EPSILON && fabs(tangent.z) <= ON_SQRT_EPSILON )
|
|
// tangent.Zero();
|
|
// else
|
|
// tangent.Unitize();
|
|
// return NormalCurvature(tangent);
|
|
//}
|
|
|
|
ON_MeshTopology::ON_MeshTopology()
|
|
: m_mesh(0)
|
|
, m_memchunk(0)
|
|
, m_b32IsValid(0)
|
|
{
|
|
}
|
|
|
|
ON_MeshTopology::~ON_MeshTopology()
|
|
{
|
|
Destroy();
|
|
}
|
|
|
|
void ON_MeshTopology::Destroy()
|
|
{
|
|
m_topv_map.Destroy();
|
|
m_topv.Destroy();
|
|
m_tope.Destroy();
|
|
m_topf.Destroy();
|
|
struct memchunk *p, *n;
|
|
n = m_memchunk;
|
|
while(n)
|
|
{
|
|
p = n;
|
|
n = n->next;
|
|
onfree(p);
|
|
}
|
|
m_memchunk = 0;
|
|
if ( -1 != m_b32IsValid)
|
|
m_b32IsValid = 0;
|
|
}
|
|
|
|
void ON_MeshTopology::EmergencyDestroy()
|
|
{
|
|
m_mesh = 0;
|
|
m_topv_map.EmergencyDestroy();
|
|
m_topv.EmergencyDestroy();
|
|
m_tope.EmergencyDestroy();
|
|
m_topf.EmergencyDestroy();
|
|
m_memchunk = 0;
|
|
m_b32IsValid = 0;
|
|
}
|
|
|
|
int ON_MeshTopology::TopVertexCount() const
|
|
{
|
|
return m_topv.Count();
|
|
}
|
|
|
|
//////////
|
|
// number of topoligical edges
|
|
int ON_MeshTopology::TopEdgeCount() const
|
|
{
|
|
return m_tope.Count();
|
|
}
|
|
|
|
//////////
|
|
// number of topoligical faces (same as m_mesh.FaceCount())
|
|
int ON_MeshTopology::TopFaceCount() const
|
|
{
|
|
return m_topf.Count();
|
|
}
|
|
|
|
ON_3dPoint ON_MeshTopology::TopVertexPoint(int vtopi) const
|
|
{
|
|
|
|
const int mesh_vertex_index = m_topv[vtopi].m_vi[0];
|
|
if (mesh_vertex_index >= 0)
|
|
{
|
|
const unsigned int vertex_count = m_mesh->VertexUnsignedCount();
|
|
if ((unsigned int)mesh_vertex_index < vertex_count)
|
|
{
|
|
if (vertex_count == m_mesh->m_dV.UnsignedCount())
|
|
return m_mesh->m_dV[mesh_vertex_index];
|
|
if (vertex_count == m_mesh->m_V.UnsignedCount())
|
|
return m_mesh->m_V[mesh_vertex_index];
|
|
}
|
|
}
|
|
ON_ERROR("Invalid topology");
|
|
return ON_3dPoint::NanPoint;
|
|
}
|
|
|
|
ON_Line ON_MeshTopology::TopEdgeLine( int tope_index ) const
|
|
{
|
|
ON_Line L(ON_3dPoint::UnsetPoint,ON_3dPoint::UnsetPoint);
|
|
if ( m_mesh && tope_index >= 0 && tope_index < m_tope.Count() )
|
|
{
|
|
const int* topvi = m_tope[tope_index].m_topvi;
|
|
if ( topvi[0] >= 0 && topvi[0] < m_topv.Count()
|
|
&& topvi[1] >= 0 && topvi[1] < m_topv.Count() )
|
|
{
|
|
const ON_MeshTopologyVertex& v0 = m_topv[topvi[0]];
|
|
const ON_MeshTopologyVertex& v1 = m_topv[topvi[1]];
|
|
if ( v0.m_v_count > 0 && v0.m_vi && v1.m_v_count > 0 && v1.m_vi )
|
|
{
|
|
int vi0 = v0.m_vi[0];
|
|
int vi1 = v1.m_vi[0];
|
|
int vcount = m_mesh->m_V.Count();
|
|
if ( vi0 >= 0 && vi0 < vcount && vi1 >= 0 && vi1 < vcount )
|
|
{
|
|
const ON_3dPointListRef vertex_list(m_mesh);
|
|
L.from = vertex_list[vi0];
|
|
L.to = vertex_list[vi1];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return L;
|
|
}
|
|
|
|
////////
|
|
// returns index of edge that connects topological vertices
|
|
// returns -1 if no edge is found.
|
|
int ON_MeshTopology::TopEdge( int vtopi0, int vtopi1 ) const
|
|
{
|
|
int i0, i1, ei, vi0;
|
|
if ( vtopi0 > vtopi1 ) {vi0 = vtopi0; vtopi0 = vtopi1; vtopi1 = vi0;}
|
|
if ( vtopi0 < vtopi1 ) {
|
|
const int tope_count = TopEdgeCount();
|
|
const ON_MeshTopologyEdge* tope = m_tope.Array(); // to speed array access
|
|
i0 = 0;
|
|
i1 = tope_count;
|
|
while ( i0 < i1 )
|
|
{
|
|
ei = (i0+i1)/2;
|
|
vi0 = tope[ei].m_topvi[0];
|
|
if ( vi0 < vtopi0 ) {
|
|
if ( i0 == ei )
|
|
break;
|
|
i0 = ei;
|
|
}
|
|
else if ( vi0 > vtopi0 ) {
|
|
if ( i1 == ei )
|
|
break;
|
|
i1 = ei;
|
|
}
|
|
else {
|
|
while (ei > 0 && tope[ei-1].m_topvi[0] == vtopi0 )
|
|
ei--;
|
|
while ( ei < tope_count && tope[ei].m_topvi[0] == vtopi0 ) {
|
|
if ( tope[ei].m_topvi[1] == vtopi1 )
|
|
return ei;
|
|
ei++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool ON_MeshTopology::GetTopFaceVertices( int fi, int topvi[4] ) const
|
|
{
|
|
if ( fi >= 0 && fi < m_mesh->m_F.Count() ) {
|
|
const int* fvi = m_mesh->m_F[fi].vi;
|
|
topvi[0] = m_topv_map[fvi[0]];
|
|
topvi[1] = m_topv_map[fvi[1]];
|
|
topvi[2] = m_topv_map[fvi[2]];
|
|
topvi[3] = m_topv_map[fvi[3]];
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ON_MeshTopology::SortVertexEdges() const
|
|
{
|
|
bool rc = true;
|
|
int topvi, topv_count = m_topv.Count();
|
|
for ( topvi = 0; topvi < topv_count; topvi++ )
|
|
{
|
|
if ( !SortVertexEdges(topvi) )
|
|
rc = false;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
// TODO make public and add to header file new ON_SortIntArray
|
|
static
|
|
void ON_ReverseIntArray(
|
|
int* e, // array of ints
|
|
size_t nel // length of array
|
|
)
|
|
{
|
|
int ei;
|
|
size_t i;
|
|
nel--;
|
|
for ( i = 0; i<nel; i++, nel--)
|
|
{
|
|
ei = e[i];
|
|
e[i] = e[nel];
|
|
e[nel] = ei;
|
|
}
|
|
}
|
|
|
|
bool ON_MeshTopology::SortVertexEdges(int topvi) const
|
|
{
|
|
if ( topvi < 0 || topvi >= m_topv.Count() )
|
|
return false;
|
|
|
|
const ON_MeshTopologyVertex& topv = m_topv[topvi];
|
|
if ( topv.m_tope_count < 2 )
|
|
return true;
|
|
|
|
// Divide the edges that use this vertex into two sets:
|
|
// e1f[] = indices of edges that bound 1 face, 3 or
|
|
// more faces, or no faces (in that order).
|
|
// e2f[] = indices of edges that bound exactly 2 faces.
|
|
int i, j;
|
|
int topei;
|
|
int vei;
|
|
int efcnt;
|
|
ON_SimpleArray<int> new_tope_buffer(5*topv.m_tope_count);
|
|
new_tope_buffer.SetCount(5*topv.m_tope_count);
|
|
int* new_tope = new_tope_buffer.Array();
|
|
int* e2f = new_tope + topv.m_tope_count;
|
|
int* e1f = e2f + topv.m_tope_count;
|
|
int e1fcnt = 0;
|
|
int e2fcnt = 0;
|
|
{
|
|
int* e3f = e1f + topv.m_tope_count; // e3f[] = edges with 3 or more faces
|
|
int* e0f = e3f + topv.m_tope_count; // e0f[] = edges with no faces
|
|
int e3fcnt = 0;
|
|
int e0fcnt = 0;
|
|
|
|
for ( vei = 0; vei < topv.m_tope_count; vei++ )
|
|
{
|
|
topei = topv.m_topei[vei];
|
|
if ( topei >= 0 && topei < m_tope.Count() )
|
|
{
|
|
const ON_MeshTopologyEdge& tope = m_tope[topei];
|
|
if ( tope.m_topvi[0] == topvi || tope.m_topvi[1] == topvi )
|
|
{
|
|
efcnt = m_tope[topei].m_topf_count;
|
|
if ( efcnt < 0 )
|
|
{
|
|
ON_ERROR("ON_MeshTopology::SortVertexEdges(int topvi) - m_tope[topei].m_topf_count < 0");
|
|
return false;
|
|
}
|
|
switch(efcnt)
|
|
{
|
|
case 0: // edge has no faces
|
|
// (never happens if topology is from a valid ON_Mesh.)
|
|
e0f[e0fcnt++] = topei;
|
|
break;
|
|
|
|
case 1: // edge has exactly one face
|
|
e1f[e1fcnt++] = topei;
|
|
break;
|
|
|
|
case 2: // edge has exactly two faces
|
|
e2f[e2fcnt++] = topei;
|
|
break;
|
|
|
|
default: // edge has 3 or more faces
|
|
// (happens when mesh is non-manifold)
|
|
e3f[e3fcnt++] = topei;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// append list of non-manifold edges (3 or more faces) to e1f[]
|
|
for ( i = 0; i < e3fcnt; i++ )
|
|
{
|
|
e1f[e1fcnt++] = e3f[i];
|
|
}
|
|
|
|
// append list of wire edges (0 faces) to e1f[]
|
|
for ( i = 0; i < e0fcnt; i++ )
|
|
{
|
|
e1f[e1fcnt++] = e0f[i];
|
|
}
|
|
|
|
e0fcnt = 0;
|
|
e3fcnt = 0;
|
|
e0f = 0;
|
|
e3f = 0;
|
|
}
|
|
|
|
// Put the edge indices in new_tope[] in radial order.
|
|
// NOTE: The code below works for non-oriented meshes and
|
|
// non-manifold meshes. If you change the code, you
|
|
// must make sure that it still works in these cases.
|
|
if ( e1fcnt + e2fcnt != topv.m_tope_count )
|
|
{
|
|
ON_ERROR("ON_MeshTopology::SortVertexEdges() input vertex had bogus m_topei[]");
|
|
return false;
|
|
}
|
|
int fi = -1;
|
|
int next_topei = -1;
|
|
int efi, fecnt, fei, next_fei;
|
|
vei = 0;
|
|
int vei0 = 0;
|
|
int vei1 = 0;
|
|
int elist_dir = 0;
|
|
while(vei < topv.m_tope_count)
|
|
{
|
|
if ( next_topei >= 0 )
|
|
{
|
|
// continue with this group of edges
|
|
topei = next_topei;
|
|
next_topei = -1;
|
|
}
|
|
else if ( e1fcnt > 0 )
|
|
{
|
|
// start a new group of edges
|
|
topei = *e1f++;
|
|
e1fcnt--;
|
|
vei1 = vei;
|
|
}
|
|
else if ( e2fcnt > 0 )
|
|
{
|
|
// start a new group of edges
|
|
// (this only happens in non-manifold cases)
|
|
topei = *e2f++;
|
|
e2fcnt--;
|
|
vei1 = vei;
|
|
}
|
|
else
|
|
{
|
|
ON_ERROR("ON_MeshTopology::SortVertexEdges() input vertex had bogus topology information.");
|
|
return false;
|
|
}
|
|
|
|
if ( vei0 < vei1 )
|
|
{
|
|
if ( 1 == elist_dir )
|
|
{
|
|
// 30 December 2003 Dale Lear added this feature
|
|
// edges new_tope[vei0],...,new_tope[vei1-1] are radially sorted
|
|
// but the order is not counterclockwise with respect to
|
|
// the normal of the face attached to the first edge.
|
|
// So, this group of edges in new_tope[] needs to
|
|
// be reversed.
|
|
ON_ReverseIntArray( new_tope+vei0, vei1-vei0 );
|
|
}
|
|
elist_dir = 0;
|
|
vei0 = vei1;
|
|
}
|
|
|
|
new_tope[vei++] = topei;
|
|
|
|
// search faces connected to tope for the next edge
|
|
const ON_MeshTopologyEdge& tope = m_tope[topei];
|
|
for ( efi = 0; next_topei < 0 && efi < tope.m_topf_count; efi++ )
|
|
{
|
|
fi = tope.m_topfi[efi];
|
|
if ( fi < 0 || fi >= m_topf.Count() )
|
|
{
|
|
// bogus face index into m_topf[] array
|
|
continue;
|
|
}
|
|
|
|
// find fei so that topf.m_topei[fei] = topei
|
|
const ON_MeshTopologyFace& topf = m_topf[fi];
|
|
fecnt = topf.IsQuad() ? 4 : 3;
|
|
for ( fei = 0; fei < fecnt; fei++ )
|
|
{
|
|
if ( topf.m_topei[fei] != topei )
|
|
continue;
|
|
|
|
if ( tope.m_topvi[0] == topvi )
|
|
{
|
|
next_fei = ( topf.m_reve[fei] ) ? 1 : -1;
|
|
}
|
|
else
|
|
{
|
|
next_fei = ( topf.m_reve[fei] ) ? -1 : 1;
|
|
}
|
|
|
|
if ( 0 == elist_dir )
|
|
elist_dir = next_fei;
|
|
|
|
next_fei = (fei+next_fei+fecnt)%fecnt;
|
|
next_topei = topf.m_topei[next_fei];
|
|
|
|
// next_topei = candidate for the next edge
|
|
// Check to see that it is available by
|
|
// finding it in the e1f[] or e2f[] arrays.
|
|
j = 0;
|
|
for ( i = 0; i < e1fcnt; i++ )
|
|
{
|
|
if ( next_topei == e1f[i] )
|
|
{
|
|
// found it in the e1f list.
|
|
for ( j = i+1; j < e1fcnt; j++ )
|
|
e1f[j-1] = e1f[j];
|
|
e1fcnt--;
|
|
break;
|
|
}
|
|
}
|
|
if ( 0 == j )
|
|
{
|
|
// search the e2f[] list
|
|
for ( i = 0; i < e2fcnt; i++ )
|
|
{
|
|
if ( next_topei == e2f[i] )
|
|
{
|
|
for ( j = i+1; j < e2fcnt; j++ )
|
|
e2f[j-1] = e2f[j];
|
|
e2fcnt--;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ( 0 == j )
|
|
{
|
|
// the candidate was already used, check the next
|
|
// face attached to this edge.
|
|
next_topei = -1;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( topv.m_tope_count != vei )
|
|
{
|
|
ON_ERROR("ON_MeshTopology::SortVertexEdges() edge sorting error.");
|
|
return false;
|
|
}
|
|
|
|
vei1 = vei;
|
|
if ( vei0 < vei1 )
|
|
{
|
|
if ( 1 == elist_dir )
|
|
{
|
|
// 30 December 2003 Dale Lear added this feature
|
|
// edges new_tope[vei0],...,new_tope[vei1-1] are radially sorted
|
|
// but the order is not counterclockwise with respect to
|
|
// the normal of the face attached to the first edge.
|
|
// So, this group of edges in new_tope[] needs to
|
|
// be reversed.
|
|
ON_ReverseIntArray( new_tope+vei0, vei1-vei0 );
|
|
}
|
|
elist_dir = 0;
|
|
vei0 = vei1;
|
|
}
|
|
|
|
int* topv_m_topei = const_cast<int*>(topv.m_topei);
|
|
for ( vei = 0; vei < topv.m_tope_count; vei++ )
|
|
{
|
|
topv_m_topei[vei] = new_tope[vei];
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int* ON_MeshTopology::GetIntArray(int length)
|
|
{
|
|
int* a = 0;
|
|
if ( length > 0 ) {
|
|
struct memchunk* pm;
|
|
pm = (struct memchunk*)onmalloc(length*sizeof(*a) + sizeof(*pm));
|
|
if ( pm ) {
|
|
pm->next = m_memchunk;
|
|
m_memchunk = pm++;
|
|
a = (int*)pm;
|
|
}
|
|
}
|
|
return a;
|
|
}
|
|
|
|
bool ON_MeshTopology::IsWeldedEdge(int top_ei) const
|
|
{
|
|
if (top_ei < 0 || top_ei >= m_tope.Count() || nullptr == m_mesh)
|
|
return false;
|
|
|
|
const ON_MeshTopologyEdge& e = m_tope[top_ei];
|
|
if (e.m_topf_count <= 1 || e.m_topvi[0] < 0 || e.m_topvi[1] < 0)
|
|
return false;
|
|
|
|
const int face_count = m_topf.Count();
|
|
if (face_count < 2 || face_count != m_mesh->FaceCount())
|
|
return false;
|
|
|
|
if (e.m_topvi[0] < 0 || e.m_topvi[1] < 0 || e.m_topvi[0] == e.m_topvi[1] )
|
|
return false;
|
|
const int topv_count = m_topv.Count();
|
|
if (e.m_topvi[0] >= topv_count || e.m_topvi[1] >= topv_count || topv_count < 3)
|
|
return false;
|
|
|
|
const int meshv_count = m_mesh->VertexCount();
|
|
if (meshv_count < topv_count || meshv_count != m_topv_map.Count())
|
|
return false;
|
|
if (1 == m_topv[e.m_topvi[0]].m_v_count && 1 == m_topv[e.m_topvi[0]].m_v_count)
|
|
return true;
|
|
|
|
// need to examine faces
|
|
int mesh_vi[2] = { -1,-1 };
|
|
for (int efi = 0; efi < e.m_topf_count; ++efi)
|
|
{
|
|
int top_fi = e.m_topfi[efi];
|
|
if (top_fi < 0 || top_fi >= face_count)
|
|
return false;
|
|
const ON_MeshTopologyFace& f = m_topf[top_fi];
|
|
const int fe_count = f.IsTriangle() ? 3 : 4;
|
|
int fvi[2] = { -1, -1 };
|
|
for (int fei = 0; fei < fe_count; ++fei)
|
|
{
|
|
if (top_ei != f.m_topei[fei])
|
|
continue;
|
|
const ON_MeshFace& mf = m_mesh->m_F[top_fi];
|
|
const bool bRev = f.m_reve[fei];
|
|
fvi[bRev?1:0] = mf.vi[(fei + fe_count - 1) % fe_count];
|
|
fvi[bRev?0:1] = mf.vi[fei];
|
|
if (fvi[0] < 0 || fvi[0] >= meshv_count)
|
|
return false;
|
|
if (fvi[1] < 0 || fvi[1] >= meshv_count)
|
|
return false;
|
|
if (m_topv_map[fvi[0]] != e.m_topvi[0] || m_topv_map[fvi[1]] != e.m_topvi[1])
|
|
{
|
|
ON_ERROR("Bug in this loop or bad mesh topology.");
|
|
fvi[0] = -1;
|
|
fvi[1] = -1;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (0 == efi)
|
|
{
|
|
if (fvi[0] < 0 || fvi[1] < 0 || fvi[0] == fvi[1])
|
|
return false;
|
|
mesh_vi[0] = fvi[0];
|
|
mesh_vi[1] = fvi[1];
|
|
}
|
|
else
|
|
{
|
|
if (mesh_vi[0] != fvi[0])
|
|
return false;
|
|
if (mesh_vi[1] != fvi[1])
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return (mesh_vi[0] >= 0 && mesh_vi[1]);
|
|
}
|
|
|
|
|
|
bool ON_MeshTopologyFace::IsTriangle() const
|
|
{
|
|
return ( m_topei[2] == m_topei[3] && m_topei[0] != m_topei[1] )
|
|
? true : false;
|
|
}
|
|
|
|
bool ON_MeshTopologyFace::IsQuad() const
|
|
{
|
|
return ( m_topei[2] != m_topei[3] ) ? true : false;
|
|
}
|
|
|
|
bool ON_MeshTopologyFace::IsValid() const
|
|
{
|
|
return ( m_topei[0] != m_topei[1] ) ? true : false;
|
|
}
|
|
|
|
|
|
bool ON_MeshTopology::IsValid() const
|
|
{
|
|
ON_Workspace ws;
|
|
int topvi, topei, topfi, vi, fi, j, jmax, k, tfvi[4];
|
|
ON_3fPoint p;
|
|
|
|
WaitUntilReady(0);
|
|
|
|
// simple checks
|
|
if ( 1 != m_b32IsValid )
|
|
return false;
|
|
if ( !m_mesh )
|
|
return false;
|
|
if ( this != &(m_mesh->Topology()) )
|
|
return false;
|
|
const int v_count = m_mesh->VertexCount();
|
|
const int f_count = m_mesh->FaceCount();
|
|
const int topv_count = TopVertexCount();
|
|
const int tope_count = TopEdgeCount();
|
|
const int topf_count = TopFaceCount();
|
|
if ( topv_count > v_count || topv_count < 0 )
|
|
return false;
|
|
if ( topv_count == 0 && v_count > 0 )
|
|
return false;
|
|
if ( topf_count != f_count )
|
|
return false;
|
|
if ( f_count > 0 && tope_count < 3 )
|
|
return false;
|
|
if ( m_topv_map.Count() != v_count )
|
|
return false;
|
|
|
|
// check vertex information
|
|
for ( vi = 0; vi < v_count; vi++ ) {
|
|
topvi = m_topv_map[vi];
|
|
if ( topvi < 0 || topvi >= topv_count )
|
|
return false;
|
|
}
|
|
|
|
char* vCheck = (char*)ws.GetMemory( v_count*sizeof(*vCheck) );
|
|
memset( vCheck, 0, v_count*sizeof(*vCheck) );
|
|
for ( topvi = 0; topvi < topv_count; topvi++ )
|
|
{
|
|
const ON_MeshTopologyVertex& topv = m_topv[topvi];
|
|
if ( topv.m_v_count <= 0 )
|
|
return false;
|
|
if ( !topv.m_vi )
|
|
return false;
|
|
p = TopVertexPoint(topvi);
|
|
for ( j = 0; j < topv.m_v_count; j++ ) {
|
|
vi = topv.m_vi[j];
|
|
if ( vi < 0 )
|
|
return false;
|
|
if ( vi >= v_count )
|
|
return false;
|
|
if ( vCheck[vi] != 0 )
|
|
return false; // mesh.m_V[vi] is referenced multiple times
|
|
if ( compare3fPoint( &p, &m_mesh->m_V[vi] ) )
|
|
return false; // mesh.m_V[vi] not at same location as topv
|
|
if ( m_topv_map[vi] != topvi )
|
|
return false;
|
|
vCheck[vi] = 1;
|
|
}
|
|
|
|
// check that edges in m_topei[] list have topvi has a start/end
|
|
if ( topv.m_tope_count < 0 )
|
|
return false;
|
|
if ( topv.m_tope_count == 0 && topv.m_topei )
|
|
return false; // array should be nullptr
|
|
if ( topv.m_tope_count > 0 && !topv.m_topei )
|
|
return false; // array should not be nullptr
|
|
for ( j = 0; j < topv.m_tope_count; j++ ) {
|
|
topei = topv.m_topei[j];
|
|
if ( topei < 0 )
|
|
return false;
|
|
if ( topei >= tope_count )
|
|
return false;
|
|
const ON_MeshTopologyEdge& tope = m_tope[topei];
|
|
if ( tope.m_topvi[0] != topvi && tope.m_topvi[1] != topvi )
|
|
return false; // edge doesn't reference this top vtx
|
|
for ( k = 0; k < j; k++ ) {
|
|
if ( topv.m_topei[k] == topei )
|
|
return false; // edge listed multiple times
|
|
}
|
|
}
|
|
}
|
|
|
|
// make sure every mesh.m_V[] maps to a topoligical vertex
|
|
for ( vi = 0; vi < v_count; vi++ ) {
|
|
if ( vCheck[vi] != 1 )
|
|
return false; // mesh.m_V[vi] is not referenced
|
|
topvi = m_topv_map[vi];
|
|
if ( topvi < 0 )
|
|
return false;
|
|
if ( topvi >= topv_count )
|
|
return false;
|
|
const ON_MeshTopologyVertex& topv = m_topv[topvi];
|
|
for ( j = 0; j < topv.m_v_count; j++ ) {
|
|
if ( topv.m_vi[j] == vi )
|
|
break;
|
|
}
|
|
if ( j >= topv.m_v_count )
|
|
return false; // bogus m_topv_map[] array
|
|
}
|
|
|
|
// check edges
|
|
for ( topei = 0; topei < tope_count; topei++ ) {
|
|
const ON_MeshTopologyEdge& tope = m_tope[topei];
|
|
if ( tope.m_topvi[0] < 0 || tope.m_topvi[0] >= topv_count )
|
|
return false;
|
|
if ( tope.m_topvi[1] < 0 || tope.m_topvi[1] >= topv_count )
|
|
return false;
|
|
if ( tope.m_topvi[0] == tope.m_topvi[1] )
|
|
return false;
|
|
|
|
const ON_MeshTopologyVertex& topv0 = m_topv[tope.m_topvi[0]];
|
|
for ( j = 0; j < topv0.m_tope_count; j++ ) {
|
|
if ( topv0.m_topei[j] == topei )
|
|
break;
|
|
}
|
|
if ( j >= topv0.m_tope_count )
|
|
return false; // start vtx not linked to edge
|
|
|
|
const ON_MeshTopologyVertex& topv1 = m_topv[tope.m_topvi[1]];
|
|
for ( j = 0; j < topv1.m_tope_count; j++ ) {
|
|
if ( topv1.m_topei[j] == topei )
|
|
break;
|
|
}
|
|
if ( j >= topv1.m_tope_count )
|
|
return false; // end vtx not linked to edge
|
|
|
|
if ( tope.m_topf_count < 0 )
|
|
return false;
|
|
if ( tope.m_topf_count == 0 && tope.m_topfi )
|
|
return false; // array should be nullptr
|
|
if ( tope.m_topf_count > 0 && !tope.m_topfi )
|
|
return false; // array should not be nullptr
|
|
for ( j = 0; j < tope.m_topf_count; j++ ) {
|
|
fi = tope.m_topfi[j];
|
|
if ( fi < 0 || fi >= f_count )
|
|
return false;
|
|
const ON_MeshTopologyFace& topf = m_topf[fi];
|
|
for ( k = 0; k < 4; k++ ) {
|
|
if ( topf.m_topei[k] == topei )
|
|
break;
|
|
}
|
|
if ( k >= 4 )
|
|
return false; // edge not in face's list
|
|
}
|
|
}
|
|
|
|
// check faces
|
|
for ( fi = 0; fi < f_count; fi++ ) {
|
|
topfi = fi;
|
|
const ON_MeshTopologyFace& topf = m_topf[topfi];
|
|
const ON_MeshFace& f = m_mesh->m_F[fi];
|
|
if ( topf.m_topei[0] < 0 || topf.m_topei[0] >= tope_count )
|
|
return false;
|
|
if ( topf.m_topei[1] < 0 || topf.m_topei[1] >= tope_count )
|
|
return false;
|
|
if ( topf.m_topei[2] < 0 || topf.m_topei[2] >= tope_count )
|
|
return false;
|
|
if ( topf.m_topei[3] < 0 || topf.m_topei[3] >= tope_count )
|
|
return false;
|
|
tfvi[0] = m_topv_map[f.vi[0]];
|
|
tfvi[1] = m_topv_map[f.vi[1]];
|
|
tfvi[2] = m_topv_map[f.vi[2]];
|
|
tfvi[3] = m_topv_map[f.vi[3]];
|
|
if ( topf.m_topei[0] != 0 || topf.m_topei[1] != 0
|
|
|| topf.m_topei[2] != 0 || topf.m_topei[3] != 0 ) {
|
|
if ( !f.IsValid(v_count) )
|
|
return false;
|
|
if ( f.IsTriangle() ) {
|
|
if (topf.m_topei[2] != topf.m_topei[3] )
|
|
return false;
|
|
jmax = 3;
|
|
}
|
|
else {
|
|
if (topf.m_topei[2] == topf.m_topei[3] )
|
|
return false;
|
|
jmax = 4;
|
|
}
|
|
for ( j = 0; j < jmax; j++ ) {
|
|
const ON_MeshTopologyEdge& tope = m_tope[topf.m_topei[j]];
|
|
for ( k = 0; k < tope.m_topf_count; k++ ) {
|
|
if ( tope.m_topfi[k] == topfi )
|
|
break;
|
|
}
|
|
if ( k >= tope.m_topf_count )
|
|
return false;
|
|
|
|
// topedge m_tope[topf.m_topei[j]] ENDS at topvtx m_topv[tfvi[j]]
|
|
if ( topf.m_reve[j] ) {
|
|
if ( tope.m_topvi[1] != tfvi[(j+3)%4] )
|
|
return false;
|
|
if ( tope.m_topvi[0] != tfvi[j] )
|
|
return false;
|
|
}
|
|
else {
|
|
if ( tope.m_topvi[0] != tfvi[(j+3)%4] )
|
|
return false;
|
|
if ( tope.m_topvi[1] != tfvi[j] )
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// all topf.m_topei[] must be zeros
|
|
if ( topf.m_topei[0] != 0 || topf.m_topei[1] != 0
|
|
|| topf.m_topei[2] != 0 || topf.m_topei[3] != 0 )
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ON_MeshTopology::Dump( ON_TextLog& dump ) const
|
|
{
|
|
ON_3fPoint p;
|
|
int vi, ei, fi, j;
|
|
const int topv_count = m_topv.Count();
|
|
const int tope_count = m_tope.Count();
|
|
const int topf_count = m_topf.Count();
|
|
|
|
// topological vertex information
|
|
for ( vi = 0; vi < topv_count; vi++ ) {
|
|
const ON_MeshTopologyVertex& v = m_topv[vi];
|
|
dump.Print("topv %d: ",vi);
|
|
if (m_mesh) {
|
|
// dump geometric location of this vertex
|
|
p = m_mesh->m_V[v.m_vi[0]];
|
|
dump.Print("{%g,%g,%g} ", p.x, p.y, p.z);
|
|
}
|
|
|
|
// list all mesh geometry viertices that are coincident with this
|
|
// topological vertex
|
|
dump.Print("(");
|
|
for ( j = 0; j < v.m_v_count; j++ ) {
|
|
if ( j )
|
|
dump.Print(",");
|
|
dump.Print("m_V[%d]",v.m_vi[j]);
|
|
}
|
|
|
|
// list all toplogical edges that begin/end at this topological vertex
|
|
dump.Print(") (");
|
|
for ( j = 0; j < v.m_tope_count; j++ ) {
|
|
if ( j )
|
|
dump.Print(",");
|
|
dump.Print("%d",v.m_topei[j]);
|
|
}
|
|
dump.Print(")\n");
|
|
}
|
|
|
|
// topological edge information
|
|
for ( ei = 0; ei < tope_count; ei++ ) {
|
|
const ON_MeshTopologyEdge& e = m_tope[ei];
|
|
dump.Print("tope %d: topv%d to topvv%d (", ei, e.m_topvi[0], e.m_topvi[1] );
|
|
// list all mesh topolical faces attached to this topolical edge
|
|
for ( j = 0; j < e.m_topf_count; j++ ) {
|
|
if (j)
|
|
dump.Print(",");
|
|
dump.Print("f%d",e.m_topfi[j]);
|
|
}
|
|
dump.Print(")\n");
|
|
}
|
|
|
|
// topological face information
|
|
// mesh m_F[] index = mesh topology m_topf[] index
|
|
for ( fi = 0; fi < topf_count; fi++ ) {
|
|
const ON_MeshTopologyFace& f = m_topf[fi];
|
|
dump.Print("topf %d: (",fi);
|
|
for ( j = 0; j < 4; j++ ) {
|
|
if ( j == 3 && f.m_topei[3] == f.m_topei[2] )
|
|
break; // triangular face
|
|
if (j)
|
|
dump.Print(",");
|
|
dump.Print("%ce%d",f.m_reve[j]?'-':'+',f.m_topei[j]);
|
|
}
|
|
dump.Print(")\n");
|
|
}
|
|
}
|
|
|
|
|
|
bool ON_MeshTopology::Create()
|
|
{
|
|
// When -1 == m_b32IsValid, this ON_MeshTopology
|
|
// is the m_top field on an ON_Mesh and is being
|
|
// created in an ON_Mesh::Topology() call and a
|
|
// sleep lock is used to keep ON_Mesh::Topology()
|
|
// thread safe.
|
|
//
|
|
// When 0 == m_b32IsValid, this ON_MeshTopology
|
|
// is being created stand alone.
|
|
//
|
|
// When 1 == m_b32IsValid, this ON_MeshTopology
|
|
// is already created and valid.
|
|
|
|
if ( 1 == m_b32IsValid )
|
|
return true;
|
|
|
|
const int b32IsValid0 = m_b32IsValid;
|
|
|
|
if ( 0 == b32IsValid0 )
|
|
{
|
|
// no sleep lock is being used
|
|
m_b32IsValid = -1;
|
|
}
|
|
int b32IsValid = b32IsValid0;
|
|
|
|
while ( 0 == b32IsValid || -1 == b32IsValid )
|
|
{
|
|
// while() is for flow control - this is a while() {... break;} statment.
|
|
Destroy();
|
|
b32IsValid = 0;
|
|
|
|
// build vertex topology information
|
|
const int fcount = m_mesh->FaceCount();
|
|
const int vcount = m_mesh->VertexCount();
|
|
if ( 0 == vcount )
|
|
break;
|
|
|
|
unsigned int* vindex = (unsigned int*)GetIntArray(vcount);
|
|
m_topv_map.SetCapacity( vcount );
|
|
m_topv_map.SetCount( vcount );
|
|
if ( 0 == m_mesh->GetVertexLocationIds(
|
|
0, // first id
|
|
(unsigned int*)m_topv_map.Array(), // vertex ids returned here
|
|
vindex
|
|
) )
|
|
{
|
|
Destroy();
|
|
break;
|
|
}
|
|
|
|
{
|
|
int topv_count = m_topv_map[vindex[vcount-1]]+1;
|
|
m_topv_map.SetCapacity( vcount );
|
|
m_topv_map.SetCount( vcount );
|
|
m_topv.SetCapacity( topv_count );
|
|
int vt0, vt1, topvi;
|
|
for (vt0 = 0; vt0 < vcount; vt0 = vt1)
|
|
{
|
|
topvi = m_topv.Count();
|
|
#if defined(ON_DEBUG)
|
|
if ( topvi != m_topv_map[vindex[vt0]] )
|
|
{
|
|
// 20 April 2010 Dale Lear:
|
|
// If you get this error, save the mesh and tell Dale Lear.
|
|
ON_ERROR("ON_MeshTopology::Create() - topvi != vertex id from GetVertexLocationIds()");
|
|
}
|
|
#endif
|
|
ON_MeshTopologyVertex& topv = m_topv.AppendNew();
|
|
topv.m_vi = (const int*)(vindex+vt0);
|
|
for ( vt1=vt0+1; vt1<vcount && topvi == m_topv_map[vindex[vt1]]; vt1++ ) {
|
|
// empty
|
|
}
|
|
topv.m_v_count = vt1-vt0;
|
|
}
|
|
}
|
|
|
|
// build edge topology information
|
|
const int topv_count = m_topv.Count();
|
|
if ( topv_count >= 2 )
|
|
{
|
|
bool rc = false;
|
|
int ei, ecnt, vi0, vi1, fi, efi, topfvi[4];
|
|
ON_MeshFace f;
|
|
|
|
if ( fcount > 0 && vcount > 0 )
|
|
{
|
|
// When working on this code be sure to test bug# 9271 and 9254 and file fsv_r4.3dm
|
|
ON_Workspace ws;
|
|
ON_MeshFaceSide* e = (ON_MeshFaceSide*)ws.GetMemory( 4*fcount*sizeof(*e) );
|
|
ecnt = m_mesh->GetMeshFaceSideList( (const unsigned int*)m_topv_map.Array(), e );
|
|
|
|
if ( ecnt > 0 )
|
|
{
|
|
rc = true;
|
|
ON_MeshFaceSide::SortByVertexIndex( e, ecnt );
|
|
|
|
// count number of topological edges and allocate storage
|
|
int etop_count = 0;
|
|
ei = 0;
|
|
while( ei < ecnt )
|
|
{
|
|
vi0 = e[ei].m_vi[0];
|
|
vi1 = e[ei].m_vi[1];
|
|
ei++;
|
|
while (ei < ecnt && e[ei].m_vi[0] == (unsigned int)vi0 && e[ei].m_vi[1] == (unsigned int)vi1 )
|
|
ei++;
|
|
etop_count++;
|
|
}
|
|
m_tope.SetCapacity(etop_count);
|
|
|
|
// fill in the m_tope[] array information
|
|
int* efindex = GetIntArray(ecnt);
|
|
for ( ei = 0; ei < ecnt; /*empty*/ )
|
|
{
|
|
ON_MeshTopologyEdge& tope = m_tope.AppendNew();
|
|
tope.m_topvi[0] = (int)(vi0 = e[ei].m_vi[0]);
|
|
tope.m_topvi[1] = (int)(vi1 = e[ei].m_vi[1]);
|
|
tope.m_topfi = efindex;
|
|
tope.m_topf_count = 0;
|
|
*efindex++ = (int)e[ei].m_fi;
|
|
tope.m_topf_count++;
|
|
ei++;
|
|
while( ei < ecnt && e[ei].m_vi[0] == (unsigned int)vi0 && e[ei].m_vi[1] == (unsigned int)vi1 )
|
|
{
|
|
*efindex++ = (int)e[ei].m_fi;
|
|
tope.m_topf_count++;
|
|
ei++;
|
|
}
|
|
}
|
|
efindex = 0; // memory deallocated by ~ON_MeshTopology()
|
|
|
|
// connect vertices to edges;
|
|
ecnt = m_tope.Count();
|
|
int* ve_count = (int*)onmalloc(topv_count*sizeof(*ve_count));
|
|
// set ve_count[topvi] = number of edges that begin or end at m_topv[topvi]
|
|
memset( ve_count, 0, topv_count*sizeof(*ve_count));
|
|
for ( ei = 0; ei < ecnt; ei++ ) {
|
|
const ON_MeshTopologyEdge& tope = m_tope[ei];
|
|
ve_count[tope.m_topvi[0]]++;
|
|
ve_count[tope.m_topvi[1]]++;
|
|
}
|
|
// allocate and distribute storage for the mopv.m_topei[] array
|
|
int* vei = GetIntArray(2*ecnt);
|
|
for ( vi0 = 0; vi0 < topv_count; vi0++ ) {
|
|
ON_MeshTopologyVertex& topv = m_topv[vi0];
|
|
if ( ve_count[vi0] > 0 ) {
|
|
topv.m_topei = vei;
|
|
vei += ve_count[vi0];
|
|
}
|
|
}
|
|
onfree(ve_count); ve_count = 0;
|
|
vei = 0; // memory deallocated by ~ON_MeshTopology()
|
|
|
|
// fill in the m_topv[].m_topei[] values
|
|
for ( ei = 0; ei < ecnt; ei++ ) {
|
|
ON_MeshTopologyEdge& tope = m_tope[ei];
|
|
ON_MeshTopologyVertex& topv0 = m_topv[tope.m_topvi[0]];
|
|
ON_MeshTopologyVertex& topv1 = m_topv[tope.m_topvi[1]];
|
|
vei = const_cast<int*>(topv0.m_topei);
|
|
vei[topv0.m_tope_count++] = ei;
|
|
vei = const_cast<int*>(topv1.m_topei);
|
|
vei[topv1.m_tope_count++] = ei;
|
|
}
|
|
|
|
// build face topology information
|
|
m_topf.SetCapacity(fcount);
|
|
m_topf.SetCount(fcount);
|
|
memset( m_topf.Array(), 0, fcount*sizeof(ON_MeshTopologyFace) );
|
|
for ( fi = 0; fi < fcount; fi++ ) {
|
|
ON_MeshTopologyFace& f_local = m_topf[fi];
|
|
f_local.m_topei[0] = -1;
|
|
f_local.m_topei[1] = -1;
|
|
f_local.m_topei[2] = -1;
|
|
f_local.m_topei[3] = -1;
|
|
}
|
|
for ( ei = 0; ei < ecnt; ei++ ) {
|
|
ON_MeshTopologyEdge& tope = m_tope[ei];
|
|
for (efi = 0; efi < tope.m_topf_count; efi++ ) {
|
|
// Because ON_MeshFace.vi[2] == ON_MeshFace.vi[3] for triangles,
|
|
// we have topf.m_topei[j] BEGIN at ON_MeshFace.vi[(j+3)%4] and END at ON_MeshFace.vi[j]
|
|
fi = tope.m_topfi[efi];
|
|
f = m_mesh->m_F[fi];
|
|
topfvi[0] = m_topv_map[f.vi[0]];
|
|
topfvi[1] = m_topv_map[f.vi[1]];
|
|
topfvi[2] = m_topv_map[f.vi[2]];
|
|
topfvi[3] = m_topv_map[f.vi[3]];
|
|
ON_MeshTopologyFace& topf = m_topf[fi];
|
|
vi0 = tope.m_topvi[0];
|
|
vi1 = tope.m_topvi[1];
|
|
// unroll loop for speed
|
|
if ( vi0 == topfvi[3] && vi1 == topfvi[0] ) {
|
|
topf.m_topei[0] = ei;
|
|
topf.m_reve[0] = 0;
|
|
}
|
|
else if ( vi0 == topfvi[0] && vi1 == topfvi[1] ) {
|
|
topf.m_topei[1] = ei;
|
|
topf.m_reve[1] = 0;
|
|
}
|
|
else if ( vi0 == topfvi[1] && vi1 == topfvi[2] ) {
|
|
topf.m_topei[2] = ei;
|
|
topf.m_reve[2] = 0;
|
|
}
|
|
else if ( vi0 == topfvi[2] && vi1 == topfvi[3] ) {
|
|
topf.m_topei[3] = ei;
|
|
topf.m_reve[3] = 0;
|
|
}
|
|
else if ( vi1 == topfvi[3] && vi0 == topfvi[0] ) {
|
|
topf.m_topei[0] = ei;
|
|
topf.m_reve[0] = 1;
|
|
}
|
|
else if ( vi1 == topfvi[0] && vi0 == topfvi[1] ) {
|
|
topf.m_topei[1] = ei;
|
|
topf.m_reve[1] = 1;
|
|
}
|
|
else if ( vi1 == topfvi[1] && vi0 == topfvi[2] ) {
|
|
topf.m_topei[2] = ei;
|
|
topf.m_reve[2] = 1;
|
|
}
|
|
else if ( vi1 == topfvi[2] && vi0 == topfvi[3] ) {
|
|
topf.m_topei[3] = ei;
|
|
topf.m_reve[3] = 1;
|
|
}
|
|
}
|
|
}
|
|
for ( fi = 0; fi < fcount; fi++ )
|
|
{
|
|
ON_MeshTopologyFace& f_local = m_topf[fi];
|
|
bool bIsGood = false;
|
|
if ( f_local.m_topei[0] >= 0 && f_local.m_topei[1] >= 0 && f_local.m_topei[2] >=0
|
|
&& f_local.m_topei[0] != f_local.m_topei[1]
|
|
&& f_local.m_topei[1] != f_local.m_topei[2]
|
|
&& f_local.m_topei[2] != f_local.m_topei[0]
|
|
) {
|
|
if ( m_mesh->m_F[fi].IsTriangle() ) {
|
|
bIsGood = true;
|
|
f_local.m_topei[3] = f_local.m_topei[2];
|
|
}
|
|
else if ( f_local.m_topei[3] >= 0
|
|
&& f_local.m_topei[0] != f_local.m_topei[3]
|
|
&& f_local.m_topei[1] != f_local.m_topei[3]
|
|
&& f_local.m_topei[2] != f_local.m_topei[3] ) {
|
|
bIsGood = true;
|
|
}
|
|
}
|
|
if ( !bIsGood ) {
|
|
f_local.m_topei[0] = 0;
|
|
f_local.m_topei[1] = 0;
|
|
f_local.m_topei[2] = 0;
|
|
f_local.m_topei[3] = 0;
|
|
f_local.m_reve[0] = 0;
|
|
f_local.m_reve[1] = 0;
|
|
f_local.m_reve[2] = 0;
|
|
f_local.m_reve[3] = 0;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
b32IsValid = 1;
|
|
break;
|
|
}
|
|
|
|
if ( -1 != b32IsValid0 )
|
|
{
|
|
// no sleep lock is in use
|
|
m_b32IsValid = b32IsValid;
|
|
}
|
|
|
|
if ( 1 != b32IsValid )
|
|
{
|
|
Destroy();
|
|
}
|
|
|
|
return (1 == b32IsValid);
|
|
}
|
|
|
|
struct tagFPT
|
|
{
|
|
int x, y, z;
|
|
};
|
|
|
|
//static int compare_fpt( const struct tagFPT* a, const struct tagFPT* b )
|
|
//{
|
|
// if ( a->x < b->x )
|
|
// return -1;
|
|
// if ( a->x > b->x )
|
|
// return 1;
|
|
// if ( a->y < b->y )
|
|
// return -1;
|
|
// if ( a->y > b->y )
|
|
// return 1;
|
|
// if ( a->z < b->z )
|
|
// return -1;
|
|
// if ( a->z > b->z )
|
|
// return 1;
|
|
// return 0;
|
|
//}
|
|
|
|
static int compare_pmark( const int* a, const int* b )
|
|
{
|
|
if ( *a < *b )
|
|
return -1;
|
|
if ( *a > *b )
|
|
return 1;
|
|
if ( a < b )
|
|
return -1;
|
|
if ( a > b )
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int compare_vmap( const void* a, const void* b )
|
|
{
|
|
int i = *((int*)a);
|
|
int j = *((int*)b);
|
|
if ( i < j )
|
|
return -1;
|
|
if ( i > j )
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int DupVertex( ON_Mesh* mesh, int vi )
|
|
{
|
|
int vertex_count = mesh->m_V.Count();
|
|
ON_3fVector n;
|
|
ON_3fPoint v;
|
|
ON_Color c;
|
|
ON_2fPoint t;
|
|
ON_SurfaceCurvature k;
|
|
v = mesh->m_V[vi];
|
|
mesh->m_V.Append(v);
|
|
if (mesh->m_N.Count() == vertex_count) {
|
|
n = mesh->m_N[vi];
|
|
mesh->m_N.Append(n);
|
|
}
|
|
if (mesh->m_T.Count() == vertex_count) {
|
|
t = mesh->m_T[vi];
|
|
mesh->m_T.Append(t);
|
|
}
|
|
if (mesh->m_K.Count() == vertex_count) {
|
|
k = mesh->m_K[vi];
|
|
mesh->m_K.Append(k);
|
|
}
|
|
if (mesh->m_C.Count() == vertex_count) {
|
|
c = mesh->m_C[vi];
|
|
mesh->m_C.Append(c);
|
|
}
|
|
return vertex_count;
|
|
}
|
|
|
|
|
|
static int AddToPartition( ON_Mesh* mesh, ON_SimpleArray<int>& pmark, int vi, int partition_mark, int fi0 )
|
|
{
|
|
bool b = true;
|
|
int i, fi, new_vi, face_count, *fvi;
|
|
i = pmark[vi];
|
|
if ( !i ) {
|
|
pmark[vi] = partition_mark;
|
|
}
|
|
else if ( i != partition_mark && i != partition_mark-1 ) {
|
|
if ( i == partition_mark-2 )
|
|
pmark[vi] = partition_mark-1; // vertex vi shared between two partitions
|
|
else {
|
|
new_vi = DupVertex(mesh,vi);
|
|
face_count = mesh->m_F.Count();
|
|
for ( fi = fi0; fi < face_count; fi++ ) {
|
|
fvi = mesh->m_F[fi].vi;
|
|
if ( fvi[0] == vi )
|
|
fvi[0] = new_vi;
|
|
if ( fvi[1] == vi )
|
|
fvi[1] = new_vi;
|
|
if ( fvi[2] == vi )
|
|
fvi[2] = new_vi;
|
|
if ( fvi[3] == vi )
|
|
fvi[3] = new_vi;
|
|
}
|
|
pmark.Append(partition_mark);
|
|
}
|
|
}
|
|
else
|
|
b = false; // vertex already in this partition
|
|
return b;
|
|
}
|
|
|
|
bool ON_MeshPartition_IsValid( const ON_MeshPartition& p, const ON_Mesh& mesh )
|
|
{
|
|
bool rc = false;
|
|
const int* fvi;
|
|
int j, tcnt, fi, parti, partcount;
|
|
partcount = p.m_part.Count();
|
|
rc = ( partcount > 0 );
|
|
if ( p.m_partition_max_triangle_count < 1 )
|
|
rc = false;
|
|
if ( p.m_partition_max_vertex_count < 3 )
|
|
rc = false;
|
|
for ( parti = 0; parti < partcount && rc; parti++ ) {
|
|
const ON_MeshPart& part = p.m_part[parti];
|
|
if ( part.triangle_count < 1 )
|
|
rc = false;
|
|
if ( part.vertex_count < 1 )
|
|
rc = false;
|
|
if ( part.vertex_count != part.vi[1] - part.vi[0] )
|
|
rc = false;
|
|
tcnt = 0;
|
|
for ( fi = part.fi[0]; fi < part.fi[1]; fi++ ) {
|
|
fvi = mesh.m_F[fi].vi;
|
|
tcnt++;
|
|
if ( fvi[2] != fvi[3] )
|
|
tcnt++;
|
|
for ( j = 0; j < 4; j++ ) {
|
|
if ( fvi[j] < part.vi[0] || fvi[j] >= part.vi[1] )
|
|
rc = false;
|
|
}
|
|
}
|
|
if ( tcnt != part.triangle_count )
|
|
rc = false;
|
|
if ( parti ) {
|
|
if ( part.fi[0] != p.m_part[parti-1].fi[1] )
|
|
rc = false;
|
|
if ( part.vi[0] > p.m_part[parti-1].vi[1] )
|
|
rc = false;
|
|
}
|
|
}
|
|
if ( partcount ) {
|
|
if ( p.m_part[0].fi[0] != 0 || p.m_part[partcount-1].fi[1] != mesh.m_F.Count() )
|
|
rc = false;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static
|
|
bool ON_Mesh_CreatePartition_SortFaces(const ON_Mesh& mesh, int* fmap )
|
|
{
|
|
ON_RTree rt;
|
|
if ( !rt.CreateMeshFaceTree(&mesh) )
|
|
return false;
|
|
|
|
const int mesh_F_count = mesh.m_F.Count();
|
|
int fmap_count = 0;
|
|
|
|
const ON_RTreeBranch* branch;
|
|
ON_RTreeIterator rit(rt);
|
|
for ( rit.First(); 0 != (branch = rit.Value()); rit.Next() )
|
|
{
|
|
if ( fmap_count > mesh_F_count )
|
|
break;
|
|
fmap[fmap_count++] = (int)(branch->m_id);
|
|
}
|
|
|
|
if ( fmap_count != mesh_F_count )
|
|
{
|
|
ON_ERROR("ON_Mesh::CreatePartition unable to get fmap[]");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
const ON_MeshPartition* ON_Mesh::CreatePartition(
|
|
int partition_max_vertex_count, // maximum number of vertices in a partition
|
|
int partition_max_triangle_count // maximum number of triangles in a partition
|
|
)
|
|
{
|
|
ON_Workspace ws;
|
|
bool bNeedFaceSort = true;
|
|
if ( m_partition )
|
|
{
|
|
bNeedFaceSort = false;
|
|
if ( m_partition->m_partition_max_triangle_count > partition_max_triangle_count
|
|
|| m_partition->m_partition_max_vertex_count > partition_max_vertex_count )
|
|
DestroyPartition();
|
|
}
|
|
if ( !m_partition )
|
|
{
|
|
// TODO: create one
|
|
struct ON_MeshPart p;
|
|
int vertex_count = VertexCount();
|
|
const int face_count = FaceCount();
|
|
const int triangle_count = TriangleCount() + 2*QuadCount();
|
|
m_partition = new ON_MeshPartition();
|
|
int k = triangle_count/partition_max_triangle_count;
|
|
if ( k < vertex_count/partition_max_vertex_count )
|
|
k = vertex_count/partition_max_vertex_count;
|
|
k++;
|
|
m_partition->m_part.Reserve(k);
|
|
if ( vertex_count <= partition_max_vertex_count &&
|
|
triangle_count <= partition_max_triangle_count )
|
|
{
|
|
m_partition->m_partition_max_vertex_count = vertex_count;
|
|
m_partition->m_partition_max_triangle_count = triangle_count;
|
|
memset(&p,0,sizeof(p));
|
|
p.vi[0] = 0;
|
|
p.vi[1] = vertex_count;
|
|
p.fi[0] = 0;
|
|
p.fi[1] = face_count;
|
|
p.vertex_count = vertex_count;
|
|
p.triangle_count = triangle_count;
|
|
m_partition->m_part.Append(p);
|
|
}
|
|
else
|
|
{
|
|
int fi;
|
|
int* fvi;
|
|
DestroyTopology();
|
|
|
|
// sort faces
|
|
int* fmap = (int*)ws.GetMemory( face_count*sizeof(fmap[0]) );
|
|
if ( !ON_Mesh_CreatePartition_SortFaces(*this,fmap) )
|
|
{
|
|
for ( fi = 0; fi < face_count; fi++ )
|
|
fmap[fi] = fi;
|
|
}
|
|
|
|
//ON_SimpleArray<struct tagFPT> fpt(face_count);
|
|
//fpt.SetCount(face_count);
|
|
//if ( HasTextureCoordinates() )
|
|
//{
|
|
// ON_2fPoint fcenter;
|
|
// ON_BoundingBox bbox = ON_PointListBoundingBox(2,0,m_T.Count(),2,&m_T[0].x);
|
|
// const ON_Interval txdom(bbox.m_min.x,bbox.m_max.x);
|
|
// const ON_Interval tydom(bbox.m_min.y,bbox.m_max.y);
|
|
// for ( fi = 0; fi < face_count; fi++ ) {
|
|
// fvi = m_F[fi].vi;
|
|
// if ( fvi[2] == fvi[3] ) {
|
|
// fcenter = 0.333333333333333333f*(m_T[fvi[0]] + m_T[fvi[1]] + m_T[fvi[2]]);
|
|
// }
|
|
// else {
|
|
// fcenter = 0.25f*(m_T[fvi[0]] + m_T[fvi[1]] + m_T[fvi[2]] + m_T[fvi[3]]);
|
|
// }
|
|
// fpt[fi].x = (int)floor(txdom.NormalizedParameterAt(fcenter.x)*100);
|
|
// fpt[fi].y = (int)floor(tydom.NormalizedParameterAt(fcenter.y)*100);
|
|
// fpt[fi].z = 0;
|
|
// }
|
|
//}
|
|
//else
|
|
//{
|
|
// ON_3dPoint fcenter;
|
|
// ON_BoundingBox bbox = BoundingBox();
|
|
// const ON_Interval vxdom(bbox.m_min.x,bbox.m_max.x);
|
|
// const ON_Interval vydom(bbox.m_min.y,bbox.m_max.y);
|
|
// const ON_Interval vzdom(bbox.m_min.z,bbox.m_max.z);
|
|
// for ( fi = 0; fi < face_count; fi++ ) {
|
|
// fvi = m_F[fi].vi;
|
|
// if ( fvi[2] == fvi[3] ) {
|
|
// fcenter = 0.333333333333333333f*(m_V[fvi[0]] + m_V[fvi[1]] + m_V[fvi[2]]);
|
|
// }
|
|
// else {
|
|
// fcenter = 0.25f*(m_V[fvi[0]] + m_V[fvi[1]] + m_V[fvi[2]] + m_V[fvi[3]]);
|
|
// }
|
|
// fpt[fi].x = (int)floor(vxdom.NormalizedParameterAt(fcenter.x)*100);
|
|
// fpt[fi].y = (int)floor(vydom.NormalizedParameterAt(fcenter.y)*100);
|
|
// fpt[fi].z = (int)floor(vzdom.NormalizedParameterAt(fcenter.z)*100);
|
|
// }
|
|
//}
|
|
//fpt.Sort( ON::sort_algorithm::quick_sort, fmap, compare_fpt );
|
|
m_F.Permute( fmap );
|
|
if ( m_FN.Count() == face_count )
|
|
m_FN.Permute( fmap );
|
|
|
|
// sort vertices
|
|
ON_SimpleArray<int>pmark(2*vertex_count);
|
|
pmark.SetCount(vertex_count);
|
|
pmark.Zero();
|
|
int fi0, fi1, partition_mark, partition_vertex_count, partition_triangle_count;
|
|
fi1 = 0;
|
|
fi0 = 0;
|
|
for ( partition_mark = 3, fi0 = 0; fi0 < face_count; fi0 = fi1, partition_mark += 2 )
|
|
{
|
|
partition_vertex_count = 0;
|
|
partition_triangle_count = 0;
|
|
for ( fi1 = fi0;
|
|
fi1 < face_count
|
|
&& partition_triangle_count+2 <= partition_max_triangle_count
|
|
&& partition_vertex_count+4 <= partition_max_vertex_count;
|
|
fi1++ )
|
|
{
|
|
fvi = m_F[fi1].vi;
|
|
partition_triangle_count++;
|
|
if ( AddToPartition( this, pmark, fvi[0], partition_mark, fi0 ) )
|
|
partition_vertex_count++;
|
|
if ( AddToPartition( this, pmark, fvi[1], partition_mark, fi0 ) )
|
|
partition_vertex_count++;
|
|
if ( AddToPartition( this, pmark, fvi[2], partition_mark, fi0 ) )
|
|
partition_vertex_count++;
|
|
if ( fvi[2] != fvi[3] ) {
|
|
partition_triangle_count++; // quads = 2 triangles
|
|
if ( AddToPartition( this, pmark, fvi[3], partition_mark, fi0 ) )
|
|
partition_vertex_count++;
|
|
}
|
|
}
|
|
if ( fi0 < fi1 ) {
|
|
struct ON_MeshPart p_local;
|
|
memset(&p_local,0,sizeof(p_local));
|
|
p_local.fi[0] = fi0;
|
|
p_local.fi[1] = fi1;
|
|
p_local.vertex_count = partition_vertex_count;
|
|
p_local.triangle_count = partition_triangle_count;
|
|
m_partition->m_part.Append(p_local);
|
|
}
|
|
if ( partition_triangle_count > m_partition->m_partition_max_triangle_count )
|
|
m_partition->m_partition_max_triangle_count = partition_triangle_count;
|
|
if ( partition_vertex_count > m_partition->m_partition_max_vertex_count )
|
|
m_partition->m_partition_max_vertex_count = partition_vertex_count;
|
|
}
|
|
|
|
// the calls to AddToPartition() may have increased vertex count
|
|
vertex_count = m_V.Count();
|
|
|
|
// sort vertices
|
|
int* vmap = (int*)ws.GetMemory( vertex_count*sizeof(vmap[0]) );
|
|
pmark.Sort( ON::sort_algorithm::quick_sort, vmap, compare_pmark );
|
|
m_V.Permute( vmap );
|
|
if ( m_N.Count() == vertex_count )
|
|
m_N.Permute( vmap );
|
|
if ( m_T.Count() == vertex_count )
|
|
m_T.Permute( vmap );
|
|
if ( m_K.Count() == vertex_count )
|
|
m_K.Permute( vmap );
|
|
if ( m_C.Count() == vertex_count )
|
|
m_C.Permute( vmap );
|
|
pmark.Permute( vmap );
|
|
// pamv[] = inverse of mapv permutation
|
|
int* pamv = (int*)ws.GetMemory( vertex_count*sizeof(pamv[0]) );
|
|
ON_Sort( ON::sort_algorithm::quick_sort, pamv, vmap, vertex_count, sizeof(vmap[0]), compare_vmap );
|
|
for ( fi = 0; fi < face_count; fi++ ) {
|
|
fvi = m_F[fi].vi;
|
|
fvi[0] = pamv[fvi[0]];
|
|
fvi[1] = pamv[fvi[1]];
|
|
fvi[2] = pamv[fvi[2]];
|
|
fvi[3] = pamv[fvi[3]];
|
|
}
|
|
|
|
// fill in m_part.vi[]
|
|
int m, pi, partition_count = m_partition->m_part.Count();
|
|
int vi0, vi1, vi2, vi3;
|
|
for (vi2 = 0; vi2 < vertex_count && pmark[vi2]<2; vi2++)
|
|
{/*empty for body*/}
|
|
vi3=vi2;
|
|
for ( pi = 0; pi < partition_count; pi++ ) {
|
|
vi0 = vi2;
|
|
vi1 = vi3;
|
|
m = 2*pi + 4;
|
|
for ( vi2 = vi3; vi2 < vertex_count && pmark[vi2] < m; vi2++)
|
|
{/*empty for body*/}
|
|
for ( vi3 = vi2; vi3 < vertex_count && pmark[vi3] <= m; vi3++)
|
|
{/*empty for body*/}
|
|
m_partition->m_part[pi].vi[0] = vi0;
|
|
m_partition->m_part[pi].vi[1] = vi3;
|
|
}
|
|
}
|
|
// debugging - test partition
|
|
if ( m_partition && !ON_MeshPartition_IsValid( *m_partition, *this ) ) {
|
|
delete m_partition;
|
|
m_partition = 0;
|
|
}
|
|
}
|
|
|
|
return m_partition;
|
|
}
|
|
|
|
const ON_MeshPartition* ON_Mesh::Partition() const
|
|
{
|
|
return m_partition;
|
|
}
|
|
|
|
void ON_Mesh::DestroyPartition()
|
|
{
|
|
if ( m_partition ) {
|
|
delete m_partition;
|
|
m_partition = 0;
|
|
}
|
|
}
|
|
|
|
ON_MeshPartition::ON_MeshPartition()
|
|
{
|
|
m_partition_max_vertex_count = 0;
|
|
m_partition_max_triangle_count = 0;
|
|
m_part = 0;
|
|
}
|
|
|
|
ON_MeshPartition::~ON_MeshPartition()
|
|
{
|
|
m_part.Destroy();
|
|
}
|
|
|
|
|
|
ON_Mesh* ON_Mesh::MeshPart(
|
|
const ON_MeshPart& mesh_part,
|
|
ON_Mesh* mesh
|
|
) const
|
|
{
|
|
if ( this == mesh )
|
|
{
|
|
ON_ERROR("ON_Mesh::MeshPart this == mesh");
|
|
return 0;
|
|
}
|
|
|
|
if ( mesh )
|
|
mesh->Destroy();
|
|
|
|
if ( mesh_part.fi[0] < 0
|
|
|| mesh_part.fi[1] > m_F.Count()
|
|
|| mesh_part.fi[0] > mesh_part.fi[1]
|
|
)
|
|
{
|
|
ON_ERROR("ON_Mesh::MeshPart mesh_part.fi[] is not valid");
|
|
return 0;
|
|
}
|
|
|
|
if ( mesh_part.vi[0] < 0
|
|
|| mesh_part.vi[1] > m_V.Count()
|
|
|| mesh_part.vi[0] >= mesh_part.vi[1]
|
|
)
|
|
{
|
|
ON_ERROR("ON_Mesh::MeshPart mesh_part.vi[] is not valid");
|
|
return 0;
|
|
}
|
|
|
|
const int submesh_V_count = mesh_part.vi[1] - mesh_part.vi[0];
|
|
const int submesh_F_count = mesh_part.fi[1] - mesh_part.fi[0];
|
|
|
|
const bool bHasVertexNormals = HasVertexNormals();
|
|
const bool bHasTextureCoordinates = HasTextureCoordinates();
|
|
const bool bHasVertexColors = HasVertexColors();
|
|
const bool bHasFaceNormals = HasFaceNormals();
|
|
const bool bHasSurfaceParameters = HasSurfaceParameters();
|
|
const bool bHasPrincipalCurvatures = HasPrincipalCurvatures();
|
|
const bool bHasHiddenVertices = HiddenVertexCount() > 0;
|
|
|
|
ON_Mesh* submesh = (0 != mesh)
|
|
? mesh
|
|
: new ON_Mesh(mesh_part.triangle_count,
|
|
mesh_part.vertex_count,
|
|
bHasVertexNormals,
|
|
bHasTextureCoordinates
|
|
);
|
|
|
|
if ( bHasVertexColors )
|
|
submesh->m_C.Reserve(submesh_V_count);
|
|
if ( bHasSurfaceParameters )
|
|
submesh->m_S.Reserve(submesh_V_count);
|
|
if ( bHasPrincipalCurvatures )
|
|
submesh->m_K.Reserve(submesh_V_count);
|
|
if ( bHasHiddenVertices )
|
|
submesh->m_H.Reserve(submesh_V_count);
|
|
if ( bHasFaceNormals )
|
|
submesh->m_FN.Reserve(submesh_F_count);
|
|
|
|
// put vertex information into submesh
|
|
const int vi0 = mesh_part.vi[0];
|
|
const int vi1 = mesh_part.vi[1];
|
|
for ( int vi = vi0; vi < vi1; vi++ )
|
|
{
|
|
submesh->m_V.Append(m_V[vi]);
|
|
if ( bHasVertexNormals )
|
|
submesh->m_N.Append(m_N[vi]);
|
|
if ( bHasTextureCoordinates )
|
|
submesh->m_T.Append(m_T[vi]);
|
|
if ( bHasVertexColors )
|
|
submesh->m_C.Append(m_C[vi]);
|
|
if ( bHasSurfaceParameters )
|
|
submesh->m_S.Append(m_S[vi]);
|
|
if ( bHasPrincipalCurvatures )
|
|
submesh->m_K.Append(m_K[vi]);
|
|
if ( bHasHiddenVertices )
|
|
{
|
|
bool bHidden = m_H[vi];
|
|
submesh->m_H.Append(bHidden);
|
|
if ( bHidden )
|
|
submesh->m_hidden_count++;
|
|
}
|
|
}
|
|
if ( submesh->m_hidden_count <= 0 )
|
|
{
|
|
submesh->m_H.Destroy();
|
|
submesh->m_hidden_count = 0;
|
|
}
|
|
|
|
// put face information into submesh
|
|
int bad_face_count = 0;
|
|
const int fi0 = mesh_part.fi[0];
|
|
const int fi1 = mesh_part.fi[1];
|
|
for ( int fi = fi0; fi < fi1; fi++ )
|
|
{
|
|
ON_MeshFace f = m_F[fi];
|
|
f.vi[0] -= vi0;
|
|
f.vi[1] -= vi0;
|
|
f.vi[2] -= vi0;
|
|
f.vi[3] -= vi0;
|
|
if ( f.vi[0] >= submesh_V_count || f.vi[0] < 0
|
|
|| f.vi[1] >= submesh_V_count || f.vi[1] < 0
|
|
|| f.vi[2] >= submesh_V_count || f.vi[2] < 0
|
|
|| f.vi[3] >= submesh_V_count || f.vi[3] < 0
|
|
)
|
|
{
|
|
bad_face_count++;
|
|
ON_ERROR("ON_Mesh::MeshPart Invalid face in partition");
|
|
continue;
|
|
}
|
|
submesh->m_F.Append(f);
|
|
if ( bHasFaceNormals )
|
|
submesh->m_FN.Append(m_FN[fi]);
|
|
}
|
|
|
|
if ( submesh->m_F.Count() < 1 && bad_face_count > 0 )
|
|
{
|
|
if ( submesh != mesh )
|
|
delete submesh;
|
|
else
|
|
mesh->Destroy();
|
|
|
|
submesh = 0;
|
|
}
|
|
|
|
return submesh;
|
|
}
|
|
|
|
ON_Mesh* ON_Mesh::DuplicateFace( int face_index, ON_Mesh* mesh ) const
|
|
{
|
|
if ( mesh == this )
|
|
return nullptr;
|
|
if ( 0 != mesh )
|
|
mesh->Destroy();
|
|
if ( face_index < 0 || face_index >= m_F.Count() )
|
|
return nullptr;
|
|
const unsigned int vcnt = VertexUnsignedCount();
|
|
if ( vcnt < 3 )
|
|
return nullptr;
|
|
|
|
const ON_3dPoint* dV = ( vcnt == m_dV.UnsignedCount() ) ? m_dV.Array() : nullptr;
|
|
const ON_3fPoint* fV = (nullptr == dV && vcnt == m_V.UnsignedCount() ) ? m_V.Array() : nullptr;
|
|
bool bHasFaceNormals = HasFaceNormals();
|
|
bool bHasVertexNormals = HasVertexNormals();
|
|
bool bHasVertexColors = HasVertexColors();
|
|
bool bHasTextureCoordinates = HasTextureCoordinates();
|
|
bool bHasSurfaceParameters = HasSurfaceParameters();
|
|
bool bHasPrincipalCurvatures = HasPrincipalCurvatures();
|
|
|
|
ON_MeshFace f = m_F[face_index];
|
|
if ( nullptr != dV )
|
|
{
|
|
if ( !f.IsValid(vcnt,dV) )
|
|
{
|
|
// invalid vertex indices - see if it can be fixed
|
|
if ( !f.Repair(vcnt,dV) )
|
|
return nullptr;
|
|
}
|
|
}
|
|
else if (nullptr != fV)
|
|
{
|
|
if (!f.IsValid(vcnt, fV))
|
|
{
|
|
// invalid vertex indices - see if it can be fixed
|
|
if (!f.Repair(vcnt, fV))
|
|
return nullptr;
|
|
}
|
|
}
|
|
else
|
|
return nullptr;
|
|
|
|
const int newvcnt = f.IsTriangle() ? 3 : 4;
|
|
if ( 0 == mesh )
|
|
mesh = new ON_Mesh();
|
|
ON_3dPointArray* newdV = 0;
|
|
if ( dV )
|
|
{
|
|
newdV = &mesh->m_dV;
|
|
newdV->Reserve(newvcnt);
|
|
}
|
|
mesh->m_V.Reserve(newvcnt);
|
|
mesh->m_F.Reserve(1);
|
|
ON_MeshFace& newface = mesh->m_F.AppendNew();
|
|
newface.vi[0] = 0;
|
|
newface.vi[1] = 1;
|
|
newface.vi[2] = 2;
|
|
newface.vi[3] = (4 == newvcnt) ? 3 : newface.vi[2];
|
|
|
|
if ( bHasFaceNormals )
|
|
{
|
|
mesh->m_FN.Reserve(1);
|
|
mesh->m_FN.Append(m_FN[face_index]);
|
|
}
|
|
|
|
if ( bHasVertexNormals )
|
|
mesh->m_N.Reserve(newvcnt);
|
|
if ( bHasTextureCoordinates )
|
|
mesh->m_T.Reserve(newvcnt);
|
|
if ( bHasVertexColors )
|
|
mesh->m_C.Reserve(newvcnt);
|
|
if ( bHasSurfaceParameters )
|
|
mesh->m_S.Reserve(newvcnt);
|
|
if ( bHasPrincipalCurvatures )
|
|
mesh->m_K.Reserve(newvcnt);
|
|
for ( int vi = 0; vi < newvcnt; vi++ )
|
|
{
|
|
if ( dV )
|
|
newdV->Append(dV[f.vi[vi]]);
|
|
else
|
|
mesh->m_V.Append(fV[f.vi[vi]]);
|
|
if ( bHasVertexNormals )
|
|
mesh->m_N.Append(m_N[f.vi[vi]]);
|
|
if ( bHasTextureCoordinates )
|
|
mesh->m_T.Append(m_T[f.vi[vi]]);
|
|
if ( bHasVertexColors )
|
|
mesh->m_C.Append(m_C[f.vi[vi]]);
|
|
if ( bHasSurfaceParameters )
|
|
mesh->m_S.Append(m_S[f.vi[vi]]);
|
|
if ( bHasPrincipalCurvatures )
|
|
mesh->m_K.Append(m_K[f.vi[vi]]);
|
|
}
|
|
if ( nullptr != dV )
|
|
mesh->UpdateSinglePrecisionVertices();
|
|
|
|
return mesh;
|
|
}
|
|
|
|
|
|
//ON_OBJECT_IMPLEMENT(ON_MeshVertexRef,ON_Geometry,"C547B4BD-BDCD-49b6-A983-0C4A7F02E31A");
|
|
//
|
|
//ON_OBJECT_IMPLEMENT(ON_MeshEdgeRef,ON_Geometry,"ED727872-463A-4424-851F-9EC02CB0F155");
|
|
//
|
|
//ON_OBJECT_IMPLEMENT(ON_MeshFaceRef,ON_Geometry,"4F529AA5-EF8D-4c25-BCBB-162D510AA280");
|
|
|
|
ON_OBJECT_IMPLEMENT(ON_MeshComponentRef,ON_Geometry,"1FD2F2BE-3346-4D7A-AE94-73C0B326D8F4");
|
|
|
|
|
|
const ON_MeshComponentRef ON_MeshComponentRef::Unset;
|
|
|
|
|
|
ON_MeshComponentRef::ON_MeshComponentRef()
|
|
: m_mesh(0)
|
|
, m_mesh_ci(ON_COMPONENT_INDEX::invalid_type,-1)
|
|
{
|
|
}
|
|
|
|
ON_MeshComponentRef::ON_MeshComponentRef(
|
|
const class ON_Mesh* mesh,
|
|
ON_COMPONENT_INDEX ci
|
|
)
|
|
: m_mesh(mesh)
|
|
, m_mesh_ci(ci)
|
|
{
|
|
}
|
|
|
|
ON_MeshComponentRef::~ON_MeshComponentRef()
|
|
{
|
|
m_mesh = 0;
|
|
m_mesh_ci.Set(ON_COMPONENT_INDEX::invalid_type,-1);
|
|
}
|
|
|
|
ON_MeshComponentRef& ON_MeshComponentRef::operator=(const ON_MeshComponentRef& src)
|
|
{
|
|
if ( this != &src )
|
|
{
|
|
ON_Geometry::operator=(src);
|
|
m_mesh = src.m_mesh;
|
|
m_mesh_ci = src.m_mesh_ci;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
void ON_MeshComponentRef::Set(
|
|
const class ON_Mesh* mesh,
|
|
ON_COMPONENT_INDEX ci
|
|
)
|
|
{
|
|
m_mesh = mesh;
|
|
m_mesh_ci = ci;
|
|
}
|
|
|
|
int ON_MeshComponentRef::CompareMeshPointer(const ON_MeshComponentRef* lhs, const ON_MeshComponentRef* rhs)
|
|
{
|
|
if (lhs == rhs)
|
|
return 0;
|
|
|
|
// nullptrs are last
|
|
if (nullptr == lhs)
|
|
return 1;
|
|
if (nullptr == rhs)
|
|
return -1;
|
|
|
|
const ON__UINT_PTR lhs_ptr = (ON__UINT_PTR)lhs->m_mesh;
|
|
const ON__UINT_PTR rhs_ptr = (ON__UINT_PTR)rhs->m_mesh;
|
|
if (lhs_ptr < rhs_ptr)
|
|
return -1;
|
|
if (lhs_ptr > rhs_ptr)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int ON_MeshComponentRef::Compare(const ON_MeshComponentRef* lhs, const ON_MeshComponentRef* rhs)
|
|
{
|
|
if (lhs == rhs)
|
|
return 0;
|
|
|
|
// nullptrs are last
|
|
if (nullptr == lhs)
|
|
return 1;
|
|
if (nullptr == rhs)
|
|
return -1;
|
|
|
|
const ON__UINT_PTR lhs_ptr = (ON__UINT_PTR)lhs->m_mesh;
|
|
const ON__UINT_PTR rhs_ptr = (ON__UINT_PTR)rhs->m_mesh;
|
|
if (lhs_ptr < rhs_ptr)
|
|
return -1;
|
|
if (lhs_ptr > rhs_ptr)
|
|
return 1;
|
|
|
|
return ON_COMPONENT_INDEX::Compare(&lhs->m_mesh_ci, &rhs->m_mesh_ci);
|
|
}
|
|
|
|
int ON_MeshComponentRef::Compare2(const ON_MeshComponentRef*const* lhs, const ON_MeshComponentRef*const* rhs)
|
|
{
|
|
if (lhs == rhs)
|
|
return 0;
|
|
|
|
// nullptrs are last
|
|
if (nullptr == lhs)
|
|
return 1;
|
|
if (nullptr == rhs)
|
|
return -1;
|
|
|
|
return ON_MeshComponentRef::Compare(*lhs, *rhs);
|
|
}
|
|
|
|
bool ON_MeshComponentRef::IsValid( ON_TextLog* text_log ) const
|
|
{
|
|
if ( 0 == m_mesh)
|
|
{
|
|
if ( 0 != text_log )
|
|
{
|
|
text_log->Print("m_mesh = nullptr.\n");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if ( false == m_mesh_ci.IsMeshComponentIndex() )
|
|
{
|
|
if ( 0 != text_log )
|
|
{
|
|
text_log->Print("m_mesh_ci = ");
|
|
m_mesh_ci.Dump(*text_log);
|
|
text_log->Print(" has an invalid m_type.\n");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if ( false == m_mesh->IsValidMeshComponentIndex(m_mesh_ci) )
|
|
{
|
|
if ( 0 != text_log )
|
|
{
|
|
text_log->Print("m_mesh_ci = ");
|
|
m_mesh_ci.Dump(*text_log);
|
|
text_log->Print(" has an invalid m_index.\n");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ON_MeshComponentRef::Dump( ON_TextLog& text_log ) const
|
|
{
|
|
text_log.Print("m_mesh=%p, m_mesh_ci: ",m_mesh);
|
|
m_mesh_ci.Dump(text_log);
|
|
text_log.Print("\n");
|
|
}
|
|
|
|
unsigned int ON_MeshComponentRef::SizeOf() const
|
|
{
|
|
unsigned int sz = sizeof(*this) - sizeof(ON_Geometry);
|
|
sz += ON_Geometry::SizeOf();
|
|
return sz;
|
|
}
|
|
|
|
ON::object_type ON_MeshComponentRef::ObjectType() const
|
|
{
|
|
return ON::meshcomponent_reference;
|
|
}
|
|
|
|
// overrides of virtual ON_Geometry functions
|
|
int ON_MeshComponentRef::Dimension() const
|
|
{
|
|
return 3;
|
|
}
|
|
|
|
bool ON_MeshComponentRef::GetBBox(
|
|
double* boxmin,
|
|
double* boxmax,
|
|
bool bGrowBox
|
|
) const
|
|
{
|
|
const unsigned int* vdex_list = 0;
|
|
unsigned int vdex_count = 0;
|
|
unsigned int vdex_buffer[2];
|
|
|
|
if ( 0 == m_mesh )
|
|
return false;
|
|
|
|
switch(m_mesh_ci.m_type)
|
|
{
|
|
case ON_COMPONENT_INDEX::mesh_vertex:
|
|
if (m_mesh_ci.m_index >= 0)
|
|
vdex_buffer[vdex_count++] = (unsigned int)m_mesh_ci.m_index;
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::meshtop_vertex:
|
|
if ( m_mesh_ci.m_index >= 0 )
|
|
{
|
|
const ON_MeshTopology* top = MeshTopology();
|
|
if ( 0 != top && m_mesh_ci.m_index < top->m_topv.Count())
|
|
{
|
|
const ON_MeshTopologyVertex& v = top->m_topv[m_mesh_ci.m_index];
|
|
if ( v.m_v_count > 0 && 0 != v.m_vi && v.m_vi[0] >= 0 )
|
|
vdex_buffer[vdex_count++] = (unsigned int)v.m_vi[0];
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::meshtop_edge:
|
|
if ( m_mesh_ci.m_index >= 0 )
|
|
{
|
|
const ON_MeshTopology* top = MeshTopology();
|
|
if ( 0 != top && m_mesh_ci.m_index < top->m_tope.Count())
|
|
{
|
|
const ON_MeshTopologyEdge& e = top->m_tope[m_mesh_ci.m_index];
|
|
for ( unsigned int j = 0; j < 2; j++ )
|
|
{
|
|
if ( e.m_topvi[j] >= 0 && e.m_topvi[j] < top->m_topv.Count() )
|
|
{
|
|
const ON_MeshTopologyVertex& v = top->m_topv[e.m_topvi[j]];
|
|
if ( v.m_v_count > 0 && 0 != v.m_vi && v.m_vi[0] >= 0 )
|
|
{
|
|
vdex_buffer[vdex_count++] = (unsigned int)v.m_vi[0];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::mesh_face:
|
|
if (m_mesh_ci.m_index >= 0 && m_mesh_ci.m_index < m_mesh->m_F.Count())
|
|
{
|
|
vdex_list = (const unsigned int*)(m_mesh->m_F[m_mesh_ci.m_index].vi);
|
|
vdex_count = (vdex_list[2] == vdex_list[3]) ? 3 : 4;
|
|
}
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::mesh_ngon:
|
|
{
|
|
const ON_MeshNgon* ngon = m_mesh->Ngon(m_mesh_ci.m_index);
|
|
if ( ngon && ngon->m_Vcount > 0 && 0 != ngon->m_vi )
|
|
{
|
|
vdex_list = ngon->m_vi;
|
|
vdex_count = ngon->m_Vcount;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if ( 0 == vdex_list )
|
|
{
|
|
if ( 0 == vdex_count )
|
|
return 0;
|
|
vdex_list = vdex_buffer;
|
|
}
|
|
|
|
bool rc = false;
|
|
const ON_3dPointListRef vertex_list(m_mesh);
|
|
const unsigned int vertex_count = vertex_list.PointCount();
|
|
ON_3dPoint v[16];
|
|
const unsigned int v_capacity = (unsigned int)(sizeof(v)/sizeof(v[0]));
|
|
unsigned int v_count = 0;
|
|
for ( unsigned int i = 0; i < vdex_count; i++ )
|
|
{
|
|
if ( vdex_list[i] < vertex_count )
|
|
{
|
|
vertex_list.GetPoint(vdex_list[i],&v[v_count].x);
|
|
if ( v[v_count].IsValid() )
|
|
{
|
|
v_count++;
|
|
if ( v_capacity == v_count )
|
|
{
|
|
if ( ON_GetPointListBoundingBox( 3, 0, v_count, 3, &v[0].x, boxmin, boxmax, bGrowBox?true:false ) )
|
|
{
|
|
bGrowBox = true;
|
|
rc = true;
|
|
}
|
|
v_count = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( v_count > 0 )
|
|
{
|
|
if ( ON_GetPointListBoundingBox( 3, 0, v_count, 3, &v[0].x, boxmin, boxmax, bGrowBox?true:false ) )
|
|
rc = true;
|
|
v_count = 0;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool ON_MeshComponentRef::Transform(
|
|
const ON_Xform& xform
|
|
)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const ON_MeshTopology* ON_MeshComponentRef::MeshTopology() const
|
|
{
|
|
const ON_MeshTopology* top = 0;
|
|
|
|
if ( 0 != m_mesh )
|
|
{
|
|
if (m_mesh && m_mesh->HasMeshTopology())
|
|
{
|
|
top = &m_mesh->Topology();
|
|
}
|
|
else if ( m_mesh->m_V.UnsignedCount() >= 3 && m_mesh->m_F.UnsignedCount() > 0 )
|
|
{
|
|
if ( ON_COMPONENT_INDEX::meshtop_vertex == m_mesh_ci.m_type
|
|
|| ON_COMPONENT_INDEX::meshtop_edge == m_mesh_ci.m_type
|
|
)
|
|
{
|
|
// create missing topology
|
|
top = &m_mesh->Topology();
|
|
}
|
|
}
|
|
}
|
|
|
|
return top;
|
|
}
|
|
|
|
unsigned int ON_MeshComponentRef::VertexIndex() const
|
|
{
|
|
unsigned int vi = ON_UNSET_UINT_INDEX;
|
|
if ( 0 != m_mesh && m_mesh_ci.m_index >= 0 )
|
|
{
|
|
switch(m_mesh_ci.m_type)
|
|
{
|
|
case ON_COMPONENT_INDEX::meshtop_vertex:
|
|
{
|
|
const struct ON_MeshTopologyVertex* v = MeshTopologyVertex();
|
|
if ( 0 != v && 1 == v->m_v_count && 0 != v->m_vi
|
|
&& ((unsigned int)v->m_vi[0]) < m_mesh->m_V.UnsignedCount()
|
|
)
|
|
{
|
|
vi = (unsigned int)v->m_vi[0];
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::mesh_vertex:
|
|
if ( ((unsigned int)m_mesh_ci.m_index) < m_mesh->m_V.UnsignedCount() )
|
|
vi = (unsigned int)m_mesh_ci.m_index;
|
|
break;
|
|
}
|
|
}
|
|
return vi;
|
|
}
|
|
|
|
ON_3dPoint ON_MeshComponentRef::VertexPoint() const
|
|
{
|
|
double buffer[3];
|
|
ON_3dPointListRef(m_mesh).GetPoint(VertexIndex(),buffer);
|
|
return *((const ON_3dPoint*)buffer);
|
|
}
|
|
|
|
|
|
unsigned int ON_MeshComponentRef::GetVertexPoint(class ON_3dPoint& point) const
|
|
{
|
|
const unsigned int vi = VertexIndex();
|
|
if ( ON_UNSET_UINT_INDEX != vi )
|
|
{
|
|
const ON_3dPointListRef vertex_list(m_mesh);
|
|
vertex_list.GetPoint(vi,&point.x);
|
|
}
|
|
else
|
|
{
|
|
point = ON_3dPoint::UnsetPoint;
|
|
}
|
|
return vi;
|
|
}
|
|
|
|
const struct ON_MeshTopologyVertex* ON_MeshComponentRef::MeshTopologyVertex() const
|
|
{
|
|
const struct ON_MeshTopologyVertex* topv;
|
|
ON_3dPoint point;
|
|
GetMeshTopologyVertexAndPoint(topv,point);
|
|
return topv;
|
|
}
|
|
|
|
unsigned int ON_MeshComponentRef::MeshTopologyVertexIndex() const
|
|
{
|
|
const struct ON_MeshTopologyVertex* topv;
|
|
ON_3dPoint point;
|
|
return GetMeshTopologyVertexAndPoint(topv,point);
|
|
}
|
|
|
|
unsigned int ON_MeshComponentRef::GetMeshTopologyVertexPoint(
|
|
class ON_3dPoint& point
|
|
) const
|
|
{
|
|
const struct ON_MeshTopologyVertex* topv;
|
|
return GetMeshTopologyVertexAndPoint(topv,point);
|
|
}
|
|
|
|
unsigned int ON_MeshComponentRef::GetMeshTopologyVertex(
|
|
const struct ON_MeshTopologyVertex*& topv
|
|
) const
|
|
{
|
|
ON_3dPoint point;
|
|
return GetMeshTopologyVertexAndPoint(topv,point);
|
|
}
|
|
|
|
unsigned int ON_MeshComponentRef::GetMeshTopologyVertexAndPoint(
|
|
const struct ON_MeshTopologyVertex*& topv,
|
|
class ON_3dPoint& point
|
|
) const
|
|
{
|
|
if ( m_mesh && m_mesh_ci.m_index >= 0 )
|
|
{
|
|
const ON_MeshTopology* top = MeshTopology();
|
|
if ( 0 != top )
|
|
{
|
|
const ON_3dPointListRef vertex_list(m_mesh);
|
|
if ( top->m_topv_map.UnsignedCount() == vertex_list.PointCount() )
|
|
{
|
|
switch(m_mesh_ci.m_type)
|
|
{
|
|
case ON_COMPONENT_INDEX::mesh_vertex:
|
|
if ( (unsigned int)m_mesh_ci.m_index < vertex_list.PointCount() )
|
|
{
|
|
int tvi = top->m_topv_map[m_mesh_ci.m_index];
|
|
if ( tvi >= 0 && tvi < top->m_topv.Count() )
|
|
{
|
|
topv = &top->m_topv[tvi];
|
|
vertex_list.GetPoint(m_mesh_ci.m_index,&point.x);
|
|
return (unsigned int)tvi;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::meshtop_vertex:
|
|
if ( (unsigned int)m_mesh_ci.m_index < top->m_topv.UnsignedCount() )
|
|
{
|
|
const ON_MeshTopologyVertex& v = top->m_topv[m_mesh_ci.m_index];
|
|
topv = &v;
|
|
if ( v.m_v_count > 0 && 0 != v.m_vi && v.m_vi[0] >= 0 && (unsigned int)v.m_vi[0] < vertex_list.PointCount() )
|
|
vertex_list.GetPoint(v.m_vi[0],&point.x);
|
|
else
|
|
point = ON_3dPoint::UnsetPoint;
|
|
return (unsigned int)m_mesh_ci.m_index;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
topv = 0;
|
|
point = ON_3dPoint::UnsetPoint;
|
|
return ON_UNSET_UINT_INDEX;
|
|
}
|
|
|
|
unsigned int ON_MeshComponentRef::MeshTopologyEdgeIndex() const
|
|
{
|
|
const struct ON_MeshTopologyEdge* tope;
|
|
return GetMeshTopologyEdge(tope);
|
|
}
|
|
|
|
|
|
const struct ON_MeshTopologyEdge* ON_MeshComponentRef::MeshTopologyEdge() const
|
|
{
|
|
const struct ON_MeshTopologyEdge* tope;
|
|
GetMeshTopologyEdge(tope);
|
|
return tope;
|
|
}
|
|
|
|
unsigned int ON_MeshComponentRef::GetMeshTopologyEdge(
|
|
const struct ON_MeshTopologyEdge*& tope
|
|
) const
|
|
{
|
|
if ( ON_COMPONENT_INDEX::meshtop_edge == m_mesh_ci.m_type && m_mesh_ci.m_index >= 0 )
|
|
{
|
|
const ON_MeshTopology* top = MeshTopology();
|
|
if ( top && m_mesh_ci.m_index < top->m_tope.Count() )
|
|
{
|
|
tope = &top->m_tope[m_mesh_ci.m_index];
|
|
return (unsigned int)m_mesh_ci.m_index;
|
|
}
|
|
}
|
|
tope = 0;
|
|
return ON_UNSET_UINT_INDEX;
|
|
}
|
|
|
|
unsigned int ON_MeshComponentRef::GetMeshTopologyEdgeLine(
|
|
class ON_Line& line
|
|
) const
|
|
{
|
|
const struct ON_MeshTopologyEdge* tope;
|
|
return GetMeshTopologyEdgeAndLine(tope,line);
|
|
}
|
|
|
|
unsigned int ON_MeshComponentRef::GetMeshTopologyEdgeAndLine(
|
|
const struct ON_MeshTopologyEdge*& tope,
|
|
ON_Line& line
|
|
) const
|
|
{
|
|
tope = 0;
|
|
unsigned int topei = GetMeshTopologyEdge(tope);
|
|
if ( ON_UNSET_UINT_INDEX != topei && 0 != tope && tope->m_topvi[0] >= 0 && tope->m_topvi[1] >= 0 )
|
|
{
|
|
const ON_MeshTopology* top = MeshTopology();
|
|
if ( 0 != top )
|
|
{
|
|
int topv_count = top->m_topv.Count();
|
|
if ( tope->m_topvi[0] < topv_count && tope->m_topvi[1] < topv_count )
|
|
{
|
|
const ON_MeshTopologyVertex& v0 = top->m_topv[tope->m_topvi[0]];
|
|
const ON_MeshTopologyVertex& v1 = top->m_topv[tope->m_topvi[1]];
|
|
if ( v0.m_v_count > 0 && v1.m_v_count > 0 && 0 != v0.m_vi && 0 != v1.m_vi )
|
|
{
|
|
if ( v0.m_vi[0] >= 0 && v1.m_vi[0] >= 0 && v0.m_vi[0] < m_mesh->m_V.Count() && v1.m_vi[0] < m_mesh->m_V.Count() )
|
|
{
|
|
ON_3dPointListRef vlist(m_mesh);
|
|
vlist.GetPoint(v0.m_vi[0],&line.from.x);
|
|
vlist.GetPoint(v1.m_vi[0],&line.to.x);
|
|
return topei;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
line = ON_Line::UnsetLine;
|
|
return topei;
|
|
}
|
|
|
|
unsigned int ON_MeshComponentRef::MeshFaceIndex() const
|
|
{
|
|
const ON_MeshFace* mesh_face;
|
|
return GetMeshFace(mesh_face);
|
|
}
|
|
|
|
const class ON_MeshFace* ON_MeshComponentRef::MeshFace() const
|
|
{
|
|
const ON_MeshFace* mesh_face = 0;
|
|
GetMeshFace(mesh_face);
|
|
return mesh_face;
|
|
}
|
|
|
|
unsigned int ON_MeshComponentRef::GetMeshFace(
|
|
const ON_MeshFace*& mesh_face
|
|
) const
|
|
{
|
|
if ( 0 != m_mesh && m_mesh_ci.m_index >= 0 )
|
|
{
|
|
switch(m_mesh_ci.m_type)
|
|
{
|
|
case ON_COMPONENT_INDEX::mesh_face:
|
|
if ( m_mesh_ci.m_index < m_mesh->m_F.Count() )
|
|
{
|
|
mesh_face = &m_mesh->m_F[m_mesh_ci.m_index];
|
|
return (unsigned int)m_mesh_ci.m_index;
|
|
}
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::mesh_ngon:
|
|
{
|
|
const ON_MeshNgon* ngon = m_mesh->Ngon(m_mesh_ci.m_index);
|
|
if ( 0 != ngon && 1 == ngon->m_Fcount && 0 != ngon->m_fi )
|
|
{
|
|
if ( ngon->m_fi[0] < m_mesh->m_F.UnsignedCount() )
|
|
{
|
|
mesh_face = &m_mesh->m_F[ngon->m_fi[0]];
|
|
return ngon->m_fi[0];
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
mesh_face = 0;
|
|
return ON_UNSET_UINT_INDEX;
|
|
}
|
|
|
|
unsigned int ON_MeshComponentRef::MeshNgonIndex() const
|
|
{
|
|
unsigned int ngon_index = ON_UNSET_UINT_INDEX;
|
|
if ( 0 != m_mesh && m_mesh_ci.m_index >= 0 )
|
|
{
|
|
switch( m_mesh_ci.m_type)
|
|
{
|
|
case ON_COMPONENT_INDEX::mesh_face:
|
|
if ( m_mesh_ci.m_index < m_mesh->m_F.Count() )
|
|
{
|
|
ngon_index = m_mesh->NgonIndexFromFaceIndex(m_mesh_ci.m_index);
|
|
if ( ngon_index < ON_UNSET_UINT_INDEX && 0 == m_mesh->Ngon(ngon_index) )
|
|
ngon_index = ON_UNSET_UINT_INDEX;
|
|
}
|
|
break;
|
|
case ON_COMPONENT_INDEX::mesh_ngon:
|
|
if ( 0 != m_mesh->Ngon(m_mesh_ci.m_index) )
|
|
ngon_index = (unsigned int)m_mesh_ci.m_index;
|
|
break;
|
|
}
|
|
}
|
|
return ngon_index;
|
|
}
|
|
|
|
const class ON_MeshNgon* ON_MeshComponentRef::MeshNgon(
|
|
class ON_MeshNgonBuffer& ngon_buffer
|
|
) const
|
|
{
|
|
const class ON_MeshNgon* ngon = 0;
|
|
if ( 0 != m_mesh && m_mesh_ci.m_index >= 0 )
|
|
{
|
|
if ( ON_COMPONENT_INDEX::mesh_ngon == m_mesh_ci.m_type )
|
|
{
|
|
ngon = m_mesh->Ngon(m_mesh_ci.m_index);
|
|
}
|
|
else if ( ON_COMPONENT_INDEX::mesh_face == m_mesh_ci.m_type && m_mesh_ci.m_index < m_mesh->m_F.Count() )
|
|
{
|
|
ngon = ON_MeshNgon::NgonFromMeshFace(ngon_buffer,(unsigned int)m_mesh_ci.m_index,(const unsigned int*)m_mesh->m_F[m_mesh_ci.m_index].vi);
|
|
}
|
|
}
|
|
return ngon;
|
|
}
|
|
|
|
const class ON_MeshNgon* ON_MeshComponentRef::MeshNgon() const
|
|
{
|
|
unsigned int ngon_index = MeshNgonIndex();
|
|
return ( ON_UNSET_UINT_INDEX != ngon_index ) ? m_mesh->Ngon(ngon_index) : 0;
|
|
}
|
|
|
|
ON_MeshComponentRef ON_Mesh::MeshComponentRef(
|
|
ON_COMPONENT_INDEX ci
|
|
) const
|
|
{
|
|
ON_MeshComponentRef cr;
|
|
if ( ci.IsMeshComponentIndex() && ci.m_index >= 0 )
|
|
{
|
|
cr.Set( this, ci );
|
|
}
|
|
return cr;
|
|
}
|
|
|
|
const ON_COMPONENT_INDEX ON_MeshTopology::TopVertexComponentIndex(
|
|
ON_COMPONENT_INDEX ci
|
|
) const
|
|
{
|
|
switch (ci.m_type)
|
|
{
|
|
case ON_COMPONENT_INDEX::TYPE::mesh_vertex:
|
|
if (ci.m_index >= 0
|
|
&& nullptr != m_mesh
|
|
&& ci.m_index < m_mesh->VertexCount()
|
|
&& m_mesh->VertexCount() == m_topv_map.Count()
|
|
)
|
|
{
|
|
ci.m_type = ON_COMPONENT_INDEX::TYPE::meshtop_vertex;
|
|
ci.m_index = m_topv_map[ci.m_index];
|
|
}
|
|
// no break here
|
|
case ON_COMPONENT_INDEX::TYPE::meshtop_vertex:
|
|
if (ci.m_index >= 0 && ci.m_index < m_topv.Count())
|
|
{
|
|
return ci;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return ON_COMPONENT_INDEX::UnsetComponentIndex;
|
|
}
|
|
|
|
|
|
ON_MeshComponentRef ON_MeshTopology::MeshComponentRef(ON_COMPONENT_INDEX ci) const
|
|
{
|
|
ON_MeshComponentRef cr;
|
|
if ( 0 != m_mesh )
|
|
cr = m_mesh->MeshComponentRef(ci);
|
|
return cr;
|
|
}
|
|
|
|
const ON_Mesh* ON_MeshComponentRef::Mesh() const
|
|
{
|
|
return m_mesh;
|
|
}
|
|
|
|
ON_COMPONENT_INDEX ON_MeshComponentRef::ComponentIndex() const
|
|
{
|
|
ON_COMPONENT_INDEX ci;
|
|
if ( m_mesh && m_mesh->IsValidMeshComponentIndex(m_mesh_ci) )
|
|
ci = m_mesh_ci;
|
|
return ci;
|
|
}
|
|
|
|
bool ON_Mesh::IsValidMeshComponentIndex(
|
|
ON_COMPONENT_INDEX ci
|
|
) const
|
|
{
|
|
bool rc = ci.m_index >= 0;
|
|
if (rc)
|
|
{
|
|
switch(ci.m_type)
|
|
{
|
|
case ON_COMPONENT_INDEX::mesh_vertex:
|
|
if (ci.m_index >= m_V.Count())
|
|
rc = false;
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::meshtop_vertex:
|
|
if ( false == TopologyExists() || ci.m_index >= m_top.m_topv.Count() )
|
|
rc = false;
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::meshtop_edge:
|
|
if ( false == TopologyExists() || ci.m_index >= m_top.m_tope.Count() )
|
|
rc = false;
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::mesh_face:
|
|
if (ci.m_index >= m_F.Count())
|
|
rc = false;
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::mesh_ngon:
|
|
if ( 0 == Ngon(ci.m_index) )
|
|
rc = false;
|
|
break;
|
|
|
|
default:
|
|
rc = false;
|
|
break;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
ON_MeshComponentRef* ON_Mesh::MeshComponent(
|
|
ON_COMPONENT_INDEX ci
|
|
) const
|
|
{
|
|
ON_MeshComponentRef* cr_ptr = 0;
|
|
ON_MeshComponentRef cr = MeshComponentRef(ci);
|
|
if ( this == cr.Mesh() && cr.ComponentIndex() == ci )
|
|
cr_ptr = new ON_MeshComponentRef(cr);
|
|
return cr_ptr;
|
|
}
|
|
|
|
|
|
ON_3dVector ON_TriangleNormal(
|
|
const ON_3dPoint& A,
|
|
const ON_3dPoint& B,
|
|
const ON_3dPoint& C
|
|
)
|
|
{
|
|
// N = normal to triangle's plane
|
|
ON_3dVector N;
|
|
double a, b, c, d;
|
|
N.x = A.y*(B.z-C.z) + B.y*(C.z-A.z) + C.y*(A.z-B.z);
|
|
N.y = A.z*(B.x-C.x) + B.z*(C.x-A.x) + C.z*(A.x-B.x);
|
|
N.z = A.x*(B.y-C.y) + B.x*(C.y-A.y) + C.x*(A.y-B.y);
|
|
|
|
a = fabs(N.x);
|
|
b = fabs(N.y);
|
|
c = fabs(N.z);
|
|
if ( b > a )
|
|
{
|
|
if ( c > b )
|
|
{
|
|
// c is largest
|
|
if ( c > ON_DBL_MIN )
|
|
{
|
|
a /= c; b /= c; d = c*sqrt(1.0 + a*a + b*b);
|
|
}
|
|
else
|
|
{
|
|
d = c;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( b > ON_DBL_MIN )
|
|
{
|
|
// b is largest
|
|
a /= b; c /= b; d = b*sqrt(1.0 + c*c + a*a);
|
|
}
|
|
else
|
|
{
|
|
d = b;
|
|
}
|
|
}
|
|
}
|
|
else if ( c > a )
|
|
{
|
|
// c is largest
|
|
if ( c > ON_DBL_MIN )
|
|
{
|
|
a /= c; b /= c; d = c*sqrt(1.0 + a*a + b*b);
|
|
}
|
|
else
|
|
{
|
|
d = c;
|
|
}
|
|
}
|
|
else if ( a > ON_DBL_MIN )
|
|
{
|
|
// a is largest
|
|
b /= a; c /= a; d = a*sqrt(1.0 + b*b + c*c);
|
|
}
|
|
else
|
|
{
|
|
d = a;
|
|
}
|
|
|
|
if ( d > 0.0 )
|
|
{
|
|
N.x /= d; N.y /= d; N.z /= d;
|
|
}
|
|
|
|
return N;
|
|
}
|
|
|
|
bool ON_GetTrianglePlaneEquation(
|
|
const ON_3dPoint& A,
|
|
const ON_3dPoint& B,
|
|
const ON_3dPoint& C,
|
|
double* a,
|
|
double* b,
|
|
double* c,
|
|
double* d,
|
|
double* evaluation_tol
|
|
)
|
|
{
|
|
const ON_3dVector N(ON_TriangleNormal(A,B,C));
|
|
const double dd( -(N.x*A.x + N.y*A.y + N.z*A.z) );
|
|
|
|
*a = N.x;
|
|
*b = N.y;
|
|
*c = N.z;
|
|
*d = dd;
|
|
|
|
if ( 0 != evaluation_tol )
|
|
{
|
|
*evaluation_tol = fabs(N.x*A.x + N.y*A.y + N.z*A.z + dd);
|
|
double e = fabs(N.x*B.x + N.y*B.y + N.z*B.z + dd);
|
|
if ( e > *evaluation_tol )
|
|
*evaluation_tol = e;
|
|
e = fabs(N.x*C.x + N.y*C.y + N.z*C.z + dd);
|
|
if ( e > *evaluation_tol )
|
|
*evaluation_tol = e;
|
|
*evaluation_tol *= (1.0 + ON_EPSILON);
|
|
}
|
|
|
|
return (0.0 != N.x || 0.0 != N.y || 0.0 != N.z);
|
|
}
|
|
|
|
|
|
const bool* ON_Mesh::HiddenVertexArray() const
|
|
{
|
|
return ( ( m_hidden_count > 0 && m_H.Count() == m_V.Count() ) ? m_H.Array() : 0);
|
|
}
|
|
|
|
void ON_Mesh::DestroyHiddenVertexArray()
|
|
{
|
|
m_H.Destroy();
|
|
m_hidden_count = 0;
|
|
}
|
|
|
|
int ON_Mesh::HiddenVertexCount() const
|
|
{
|
|
return ((m_H.Count() == m_V.Count()) ? m_hidden_count : 0);
|
|
}
|
|
|
|
void ON_Mesh::SetVertexHiddenFlag( int meshvi, bool bHidden )
|
|
{
|
|
const int vcount = m_V.Count();
|
|
if ( meshvi >= 0 && meshvi < vcount )
|
|
{
|
|
if ( bHidden )
|
|
{
|
|
if ( vcount != m_H.Count() )
|
|
{
|
|
m_H.SetCapacity(vcount);
|
|
m_H.SetCount(vcount);
|
|
m_H.Zero();
|
|
m_H[meshvi] = true;
|
|
m_hidden_count = 1;
|
|
}
|
|
else if ( false == m_H[meshvi] )
|
|
{
|
|
m_H[meshvi] = true;
|
|
m_hidden_count++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// show this vertex
|
|
if ( m_hidden_count > 0 && vcount == m_H.Count() )
|
|
{
|
|
if ( m_H[meshvi] )
|
|
{
|
|
m_H[meshvi] = false;
|
|
m_hidden_count--;
|
|
if ( 0 == m_hidden_count )
|
|
{
|
|
DestroyHiddenVertexArray();
|
|
}
|
|
}
|
|
}
|
|
else if ( m_hidden_count > 0 || m_H.Capacity() > 0 )
|
|
{
|
|
// if m_H exists, it is bogus.
|
|
DestroyHiddenVertexArray();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool ON_Mesh::VertexIsHidden(int meshvi) const
|
|
{
|
|
const int vcount = m_V.Count();
|
|
return ((m_hidden_count > 0 && meshvi >= 0 && meshvi < vcount && vcount == m_H.Count())
|
|
? m_H[meshvi]
|
|
: false);
|
|
}
|
|
|
|
bool ON_Mesh::FaceIsHidden(int meshfi) const
|
|
{
|
|
const bool* bHiddenVertex = HiddenVertexArray();
|
|
if ( bHiddenVertex && meshfi >= 0 && meshfi < m_F.Count() )
|
|
{
|
|
ON_MeshFace f = m_F[meshfi];
|
|
if ( bHiddenVertex[f.vi[0]] || bHiddenVertex[f.vi[1]] || bHiddenVertex[f.vi[2]] || bHiddenVertex[f.vi[3]] )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ON_MeshTopology::TopVertexIsHidden( int topvi ) const
|
|
{
|
|
const bool* bHiddenVertex = m_mesh ? m_mesh->HiddenVertexArray() : 0;
|
|
if ( bHiddenVertex && topvi >= 0 && topvi < m_topv.Count() )
|
|
{
|
|
const ON_MeshTopologyVertex& topv = m_topv[topvi];
|
|
int i;
|
|
for ( i = 0; i < topv.m_v_count; i++ )
|
|
{
|
|
if ( !bHiddenVertex[topv.m_vi[i]] )
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ON_MeshTopology::TopEdgeIsHidden( int topei ) const
|
|
{
|
|
// ugly - but faster than calling TopVertexIsHidden()
|
|
const bool* bHiddenVertex = m_mesh ? m_mesh->HiddenVertexArray() : 0;
|
|
if ( bHiddenVertex && topei >= 0 && topei < m_tope.Count() )
|
|
{
|
|
const ON_MeshTopologyEdge& tope = m_tope[topei];
|
|
const ON_MeshTopologyVertex& topv0 = m_topv[tope.m_topvi[0]];
|
|
const ON_MeshTopologyVertex& topv1 = m_topv[tope.m_topvi[1]];
|
|
int i;
|
|
|
|
for ( i = 0; i < topv0.m_v_count; i++ )
|
|
{
|
|
if ( !bHiddenVertex[topv0.m_vi[i]] )
|
|
break;
|
|
}
|
|
if ( i >= topv0.m_v_count )
|
|
return true;
|
|
|
|
for ( i = 0; i < topv1.m_v_count; i++ )
|
|
{
|
|
if ( !bHiddenVertex[topv1.m_vi[i]] )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ON_MeshTopology::TopFaceIsHidden( int topfi ) const
|
|
{
|
|
// topology and mesh face indices are the same
|
|
return m_mesh ? m_mesh->FaceIsHidden(topfi) : false;
|
|
}
|
|
|
|
|
|
ON_MappingTag::ON_MappingTag()
|
|
{
|
|
Default();
|
|
}
|
|
|
|
ON_MappingTag::ON_MappingTag(const ON_TextureMapping & mapping, const ON_Xform * xform)
|
|
{
|
|
Default();
|
|
Set(mapping);
|
|
if (
|
|
ON_TextureMapping::TYPE::no_mapping != mapping.m_type && ON_TextureMapping::TYPE::srfp_mapping != mapping.m_type
|
|
&& nullptr != xform && xform->IsValid() && false == xform->IsIdentity(ON_ZERO_TOLERANCE) && false == xform->IsZero())
|
|
m_mesh_xform = *xform;
|
|
}
|
|
|
|
void ON_MappingTag::Dump( ON_TextLog& text_log ) const
|
|
{
|
|
text_log.Print("Texture/color coordinates tag:\n");
|
|
text_log.PushIndent();
|
|
text_log.Print("mapping id: "); text_log.Print(m_mapping_id); text_log.Print("\n");
|
|
text_log.Print("mapping crc: %08x\n",m_mapping_crc);
|
|
text_log.Print("mesh xform:\n");
|
|
text_log.PushIndent(); text_log.Print(m_mesh_xform); text_log.PopIndent();
|
|
text_log.PushIndent();
|
|
text_log.Print(m_mesh_xform);
|
|
text_log.PopIndent();
|
|
text_log.PopIndent();
|
|
}
|
|
|
|
void ON_MappingTag::Transform( const ON_Xform& xform )
|
|
{
|
|
if ( !ON_UuidIsNil(m_mapping_id) )
|
|
{
|
|
// Update mapping transformation.
|
|
// Do NOT change the value of m_mapping_crc.
|
|
m_mesh_xform = xform*m_mesh_xform;
|
|
}
|
|
}
|
|
|
|
void ON_MappingTag::SetDefaultSurfaceParameterMappingTag()
|
|
{
|
|
Set(ON_TextureMapping::SurfaceParameterTextureMapping);
|
|
}
|
|
|
|
void ON_MappingTag::Set(const ON_TextureMapping& mapping)
|
|
{
|
|
Default();
|
|
m_mapping_id = mapping.Id();
|
|
m_mapping_type = mapping.m_type;
|
|
m_mapping_crc = mapping.MappingCRC();
|
|
}
|
|
|
|
bool ON_MappingTag::IsSet() const
|
|
{
|
|
return (0 != m_mapping_crc || !ON_UuidIsNil(m_mapping_id));
|
|
}
|
|
|
|
bool ON_MappingTag::IsDefaultSurfaceParameterMapping() const
|
|
{
|
|
bool rc = (ON_TextureMapping::TYPE::srfp_mapping == m_mapping_type);
|
|
if ( rc )
|
|
{
|
|
// The crc needs to be checked to insure that m_uvw
|
|
// was the identity.
|
|
rc = (m_mapping_crc == ON_TextureMapping::SurfaceParameterTextureMapping.MappingCRC());
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
void ON_MappingTag::Default()
|
|
{
|
|
memset(this,0,sizeof(*this));
|
|
m_mesh_xform.m_xform[0][0] = 1.0;
|
|
m_mesh_xform.m_xform[1][1] = 1.0;
|
|
m_mesh_xform.m_xform[2][2] = 1.0;
|
|
m_mesh_xform.m_xform[3][3] = 1.0;
|
|
}
|
|
|
|
int ON_MappingTag::Compare(
|
|
const ON_MappingTag& other,
|
|
bool bCompareId,
|
|
bool bCompareCRC,
|
|
bool bCompareXform
|
|
) const
|
|
{
|
|
int rc = 0;
|
|
if ( !rc && bCompareId )
|
|
{
|
|
rc = ON_UuidCompare(m_mapping_id,other.m_mapping_id);
|
|
}
|
|
if ( !rc && bCompareCRC )
|
|
{
|
|
rc = ((int)m_mapping_crc) - ((int)other.m_mapping_crc);
|
|
}
|
|
if ( !rc && bCompareXform )
|
|
{
|
|
rc = m_mesh_xform.Compare(other.m_mesh_xform);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
|
|
bool ON_MappingTag::Write(ON_BinaryArchive& archive) const
|
|
{
|
|
bool rc = archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,1);
|
|
if (!rc)
|
|
return false;
|
|
|
|
for(;;)
|
|
{
|
|
rc = archive.WriteUuid(m_mapping_id);
|
|
if(!rc) break;
|
|
rc = archive.WriteInt(m_mapping_crc);
|
|
if(!rc) break;
|
|
rc = archive.WriteXform(m_mesh_xform);
|
|
if(!rc) break;
|
|
|
|
// 1.1 fields
|
|
rc = archive.WriteInt(static_cast<unsigned int>(m_mapping_type));
|
|
if (!rc) break;
|
|
|
|
break;
|
|
}
|
|
|
|
if ( !archive.EndWrite3dmChunk() )
|
|
rc = false;
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool ON_MappingTag::Read(ON_BinaryArchive& archive)
|
|
{
|
|
Default();
|
|
int mjv = 0, mnv = 0;
|
|
bool rc = archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&mjv,&mnv);
|
|
if (!rc)
|
|
return false;
|
|
|
|
for(;;)
|
|
{
|
|
rc = (1 == mjv);
|
|
if (!rc) break;
|
|
rc = archive.ReadUuid(m_mapping_id);
|
|
if(!rc) break;
|
|
if ( 0 == ON_UuidCompare(&obsolete_default_srfp_mapping_id,&m_mapping_id) )
|
|
m_mapping_id = ON_nil_uuid;
|
|
rc = archive.ReadInt(&m_mapping_crc);
|
|
if(!rc) break;
|
|
rc = archive.ReadXform(m_mesh_xform);
|
|
if(!rc) break;
|
|
|
|
if ( mnv >= 1 )
|
|
{
|
|
// 1.1 fields
|
|
unsigned int i = static_cast<unsigned int>(m_mapping_type);
|
|
rc = archive.ReadInt(&i);
|
|
if ( rc )
|
|
m_mapping_type = ON_TextureMapping::TypeFromUnsigned(i);
|
|
if (!rc) break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if ( !archive.EndRead3dmChunk() )
|
|
rc = false;
|
|
|
|
return rc;
|
|
}
|
|
|
|
ON_TextureCoordinates::ON_TextureCoordinates()
|
|
{
|
|
m_dim = 0;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////
|
|
//
|
|
// Double precision vertices
|
|
//
|
|
|
|
ON_OBJECT_IMPLEMENT(ON_V5_MeshDoubleVertices,ON_UserData,"17F24E75-21BE-4a7b-9F3D-7F85225247E3");
|
|
|
|
ON_V5_MeshDoubleVertices* ON_V5_MeshDoubleVertices::GetV5(const ON_Mesh* mesh)
|
|
{
|
|
return ON_V5_MeshDoubleVertices::Cast(mesh->GetUserData(ON_CLASS_ID(ON_V5_MeshDoubleVertices)));
|
|
}
|
|
|
|
ON_V5_MeshDoubleVertices* ON_V5_MeshDoubleVertices::AttachV5(const ON_Mesh* mesh)
|
|
{
|
|
ON_V5_MeshDoubleVertices* dv = ON_V5_MeshDoubleVertices::GetV5(mesh);
|
|
if ( 0 != dv )
|
|
return 0;
|
|
dv = new ON_V5_MeshDoubleVertices();
|
|
const_cast<ON_Mesh*>(mesh)->AttachUserData(dv);
|
|
return dv;
|
|
}
|
|
|
|
ON_V5_MeshDoubleVertices::ON_V5_MeshDoubleVertices()
|
|
{
|
|
m_userdata_uuid = ON_CLASS_ID(ON_V5_MeshDoubleVertices);
|
|
m_application_uuid = ON_opennurbs5_id;
|
|
m_userdata_copycount = 1;
|
|
}
|
|
|
|
ON_V5_MeshDoubleVertices::~ON_V5_MeshDoubleVertices()
|
|
{}
|
|
|
|
bool ON_V5_MeshDoubleVertices::IsValid( ON_TextLog* ) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void ON_V5_MeshDoubleVertices::Dump( ON_TextLog& text_log ) const
|
|
{
|
|
// TODO - print some double values
|
|
ON_UserData::Dump(text_log);
|
|
}
|
|
|
|
unsigned int ON_V5_MeshDoubleVertices::SizeOf() const
|
|
{
|
|
return m_V5_dV.SizeOfArray() + ON_UserData::SizeOf();
|
|
}
|
|
|
|
ON__UINT32 ON_V5_MeshDoubleVertices::DataCRC(ON__UINT32 current_remainder) const
|
|
{
|
|
current_remainder = ON_CRC32(current_remainder,sizeof(m_fcount),&m_fcount);
|
|
current_remainder = ON_CRC32(current_remainder,sizeof(m_dcount),&m_dcount);
|
|
current_remainder = ON_CRC32(current_remainder,sizeof(m_fCRC),&m_fCRC);
|
|
current_remainder = ON_CRC32(current_remainder,sizeof(m_dCRC),&m_dCRC);
|
|
current_remainder = m_V5_dV.DataCRC(current_remainder);
|
|
return current_remainder;
|
|
}
|
|
|
|
bool ON_V5_MeshDoubleVertices::DeleteAfterWrite(
|
|
const class ON_BinaryArchive& archive,
|
|
const class ON_Object* parent_object
|
|
) const
|
|
{
|
|
// ON_V5_MeshDoubleVertices is used when saving earlier versions of files and then deleted.
|
|
return true;
|
|
}
|
|
|
|
bool ON_V5_MeshDoubleVertices::DeleteAfterRead(
|
|
const class ON_BinaryArchive& archive,
|
|
class ON_Object* parent_object
|
|
) const
|
|
{
|
|
ON_Mesh* mesh = ON_Mesh::Cast(parent_object);
|
|
if (nullptr != mesh && 0 == mesh->m_dV.UnsignedCount() )
|
|
{
|
|
const unsigned int vertex_count = mesh->VertexUnsignedCount();
|
|
if (vertex_count == m_V5_dV.UnsignedCount())
|
|
{
|
|
mesh->m_dV = m_V5_dV;
|
|
if (false == mesh->HasSynchronizedDoubleAndSinglePrecisionVertices())
|
|
mesh->DestroyDoublePrecisionVertices();
|
|
else
|
|
{
|
|
// update vertex bounding boxes
|
|
mesh->InvalidateVertexBoundingBox();
|
|
mesh->BoundingBox();
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ON_V5_MeshDoubleVertices::Write(ON_BinaryArchive& file) const
|
|
{
|
|
bool rc = file.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,0);
|
|
if (!rc)
|
|
return false;
|
|
for(;;)
|
|
{
|
|
rc = file.WriteInt(m_fcount);
|
|
if (!rc)
|
|
break;
|
|
rc = file.WriteInt(m_dcount);
|
|
if (!rc)
|
|
break;
|
|
rc = file.WriteInt(m_fCRC);
|
|
if (!rc)
|
|
break;
|
|
rc = file.WriteInt(m_dCRC);
|
|
if (!rc)
|
|
break;
|
|
rc = file.WriteArray(m_V5_dV);
|
|
if (!rc)
|
|
break;
|
|
break;
|
|
}
|
|
if ( !file.EndWrite3dmChunk() )
|
|
rc = false;
|
|
return rc;
|
|
}
|
|
|
|
bool ON_V5_MeshDoubleVertices::Read(ON_BinaryArchive& file)
|
|
{
|
|
m_fcount = 0;
|
|
m_dcount = 0;
|
|
m_fCRC = 0;
|
|
m_dCRC = 0;
|
|
m_V5_dV.Destroy();
|
|
|
|
int major_version = 0;
|
|
int minor_version = 0;
|
|
bool rc = file.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version);
|
|
if (!rc)
|
|
return false;
|
|
for(;;)
|
|
{
|
|
rc = file.ReadInt(&m_fcount);
|
|
if (!rc)
|
|
break;
|
|
rc = file.ReadInt(&m_dcount);
|
|
if (!rc)
|
|
break;
|
|
rc = file.ReadInt(&m_fCRC);
|
|
if (!rc)
|
|
break;
|
|
rc = file.ReadInt(&m_dCRC);
|
|
if (!rc)
|
|
break;
|
|
rc = file.ReadArray(m_V5_dV);
|
|
if (!rc)
|
|
break;
|
|
break;
|
|
}
|
|
if ( !file.EndRead3dmChunk() )
|
|
rc = false;
|
|
return rc;
|
|
}
|
|
|
|
// virtual ON_UserData overrides
|
|
bool ON_V5_MeshDoubleVertices::GetDescription( ON_wString& description )
|
|
{
|
|
description = L"ON_Mesh double precision vertices";
|
|
return true;
|
|
}
|
|
|
|
bool ON_V5_MeshDoubleVertices::Archive() const
|
|
{
|
|
// refuse to save garbage in files.
|
|
|
|
if ( m_fcount != m_dcount )
|
|
{
|
|
ON_ERROR("m_fcount != m_dcount");
|
|
return false;
|
|
}
|
|
|
|
if ( m_dcount != m_V5_dV.Count() )
|
|
{
|
|
ON_ERROR("m_dcount != m_dV.Count()");
|
|
return false;
|
|
}
|
|
|
|
if ( m_dCRC != DoubleCRC() )
|
|
{
|
|
ON_ERROR("m_dCRC != DoubleCRC()");
|
|
return false;
|
|
}
|
|
|
|
const ON_Mesh* mesh = ON_Mesh::Cast( Owner() );
|
|
if ( 0 == mesh )
|
|
{
|
|
ON_ERROR("0 = ON_Mesh::Cast( Owner() )");
|
|
return false;
|
|
}
|
|
|
|
if ( m_fcount != mesh->m_V.Count() )
|
|
{
|
|
ON_ERROR("m_fcount != mesh->m_V.Count()");
|
|
return false;
|
|
}
|
|
|
|
if ( m_fCRC != ON_V5_MeshDoubleVertices::FloatCRC(mesh->m_V) )
|
|
{
|
|
ON_ERROR("m_fCRC != ON_V5_MeshDoubleVertices::FloatCRC(mesh->m_V)");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ON_V5_MeshDoubleVertices::Transform( const ON_Xform& xform )
|
|
{
|
|
if ( !xform.IsIdentity() )
|
|
{
|
|
const ON__UINT32 crc0 = DoubleCRC();
|
|
m_V5_dV.Transform(xform);
|
|
const ON__UINT32 crc1 = DoubleCRC();
|
|
if ( crc0 == m_dCRC && m_V5_dV.Count() == m_dcount )
|
|
m_dCRC = crc1; // update m_dCRC so it stays valid
|
|
else
|
|
m_dCRC = (0==crc1)?1:0; // make sure m_dCRC is not valid
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ON_Mesh::HasSynchronizedDoubleAndSinglePrecisionVertices() const
|
|
{
|
|
const unsigned int vertex_count = VertexUnsignedCount();
|
|
if (0 == vertex_count)
|
|
return false;
|
|
|
|
if (vertex_count != m_V.UnsignedCount())
|
|
return false;
|
|
if (vertex_count != m_dV.UnsignedCount())
|
|
return false;
|
|
|
|
ON_3dPoint P;
|
|
const ON_3fPoint* FV = m_V.Array();
|
|
const ON_3dPoint* DV = m_dV.Array();
|
|
for (unsigned int vi = 0; vi < vertex_count; vi++ )
|
|
{
|
|
// Compare float values.
|
|
P.x = (float)DV->x;
|
|
P.y = (float)DV->y;
|
|
P.z = (float)DV->z;
|
|
if ( !(P.x == FV->x && P.y == FV->y && P.z == FV->z) )
|
|
return false;
|
|
DV++;
|
|
FV++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ON_Mesh::HasDoublePrecisionVertices() const
|
|
{
|
|
const unsigned int vertex_count = VertexUnsignedCount();
|
|
return ( vertex_count > 0 && vertex_count == m_dV.UnsignedCount() );
|
|
}
|
|
|
|
bool ON_Mesh::HasSinglePrecisionVertices() const
|
|
{
|
|
const unsigned int vertex_count = VertexUnsignedCount();
|
|
return ( vertex_count > 0 && vertex_count == m_V.UnsignedCount() );
|
|
}
|
|
|
|
void ON_Mesh::DestroyDoublePrecisionVertices()
|
|
{
|
|
m_dV.Destroy();
|
|
}
|
|
|
|
ON__UINT32 ON_V5_MeshDoubleVertices::FloatCRC( const ON_3fPointArray& V )
|
|
{
|
|
return V.DataCRC(0);
|
|
}
|
|
|
|
ON__UINT32 ON_V5_MeshDoubleVertices::DoubleCRC() const
|
|
{
|
|
return m_V5_dV.DataCRC(0);
|
|
}
|
|
|
|
void ON_Mesh::UpdateSinglePrecisionVertices()
|
|
{
|
|
unsigned int vertex_count = m_dV.UnsignedCount();
|
|
m_V.Reserve(vertex_count);
|
|
m_V.SetCount(vertex_count);
|
|
if (0 == vertex_count)
|
|
return;
|
|
|
|
ON_3fPoint* fV = m_V.Array();
|
|
ON_3fPoint* fVend = fV + vertex_count;
|
|
const ON_3dPoint* dV = m_dV.Array();
|
|
while (fV < fVend)
|
|
{
|
|
fV->x = (float)dV->x;
|
|
fV->y = (float)dV->y;
|
|
fV->z = (float)dV->z;
|
|
fV++;
|
|
dV++;
|
|
}
|
|
}
|
|
|
|
void ON_Mesh::UpdateDoublePrecisionVertices()
|
|
{
|
|
const unsigned int vertex_count = m_V.UnsignedCount();
|
|
const bool bSelectiveUpdate = (vertex_count == m_dV.UnsignedCount());
|
|
|
|
m_dV.Reserve(vertex_count);
|
|
m_dV.SetCount(vertex_count);
|
|
if (0 == vertex_count)
|
|
return;
|
|
|
|
ON_3dPoint* dV = m_dV.Array();
|
|
ON_3dPoint* dVend = dV + vertex_count;
|
|
const ON_3fPoint* fV = m_V.Array();
|
|
if ( bSelectiveUpdate )
|
|
{
|
|
// double precision vertices already existed
|
|
// and there is a reasonable chance that
|
|
// a subset of the float precision vertices
|
|
// have been modified. So, attempt to
|
|
// keep the precision on double vertices
|
|
// that alread agree with the float vertices
|
|
// in float precision.
|
|
ON_3fPoint P;
|
|
while (dV < dVend)
|
|
{
|
|
P.x = (float)dV->x;
|
|
P.y = (float)dV->y;
|
|
P.z = (float)dV->z;
|
|
if ( !(P.x == fV->x && P.y == fV->y && P.z == fV->z) )
|
|
{
|
|
// (float)dV != fV, so update dV
|
|
dV->x = (double)fV->x;
|
|
dV->y = (double)fV->y;
|
|
dV->z = (double)fV->z;
|
|
}
|
|
dV++;
|
|
fV++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (dV < dVend)
|
|
{
|
|
dV->x = (double)fV->x;
|
|
dV->y = (double)fV->y;
|
|
dV->z = (double)fV->z;
|
|
dV++;
|
|
fV++;
|
|
}
|
|
}
|
|
}
|
|
|
|
ON_3dPointArray& ON_Mesh::DoublePrecisionVertices()
|
|
{
|
|
const unsigned int vertex_count = VertexUnsignedCount();
|
|
if ( vertex_count != m_dV.UnsignedCount() )
|
|
UpdateDoublePrecisionVertices();
|
|
|
|
return m_dV;
|
|
}
|
|
|
|
ON_3dPoint ON_Mesh::Vertex(int vertex_index) const
|
|
{
|
|
const unsigned int vertex_count = m_V.UnsignedCount();
|
|
if ( vertex_index < 0 || ((unsigned int)vertex_index) >= vertex_count )
|
|
return ON_3dPoint::UnsetPoint;
|
|
|
|
const ON_3fPoint F = m_V[vertex_index];
|
|
if (vertex_count == m_dV.UnsignedCount())
|
|
{
|
|
ON_3dPoint D = m_dV[vertex_index];
|
|
if ( F.x == (float)D.x && F.y == (float)D.y && F.z == (float)D.z )
|
|
{
|
|
// double precision vertex is valid
|
|
return D;
|
|
}
|
|
}
|
|
|
|
return F;
|
|
}
|
|
|
|
const ON_3dPointArray& ON_Mesh::DoublePrecisionVertices() const
|
|
{
|
|
ON_Mesh& mesh = const_cast<ON_Mesh&>(*this);
|
|
ON_3dPointArray& a = mesh.DoublePrecisionVertices();
|
|
return a;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
//
|
|
// BEGIN ON_PerObjectMeshParameters user data class
|
|
//
|
|
|
|
|
|
class /*NEVER EXPORT THIS CLASS DEFINITION*/ ON_PerObjectMeshParameters : public ON_UserData
|
|
{
|
|
#if !defined(ON_BOZO_VACCINE_B5628CA982C44CAE9883487B3E4AB28B)
|
|
#error Never copy this class definition or put this definition in a header file!
|
|
#endif
|
|
ON_OBJECT_DECLARE(ON_PerObjectMeshParameters);
|
|
|
|
public:
|
|
ON_PerObjectMeshParameters();
|
|
~ON_PerObjectMeshParameters();
|
|
// default copy constructor and operator= work fine.
|
|
|
|
public:
|
|
// virtual ON_Object override
|
|
bool IsValid( class ON_TextLog* text_log = nullptr ) const override;
|
|
// virtual ON_Object override
|
|
unsigned int SizeOf() const override;
|
|
// virtual ON_Object override
|
|
ON__UINT32 DataCRC(ON__UINT32 current_remainder) const override;
|
|
// virtual ON_Object override
|
|
bool Write(ON_BinaryArchive& binary_archive) const override;
|
|
// virtual ON_Object override
|
|
bool Read(ON_BinaryArchive& binary_archive) override;
|
|
// virtual ON_UserData override
|
|
bool Archive() const override;
|
|
// virtual ON_UserData override
|
|
bool GetDescription( ON_wString& description ) override;
|
|
|
|
public:
|
|
static
|
|
ON_PerObjectMeshParameters* FindOrCreate( const ON_Object*, bool bCreate );
|
|
|
|
ON_MeshParameters m_mp;
|
|
};
|
|
|
|
#undef ON_BOZO_VACCINE_B5628CA982C44CAE9883487B3E4AB28B
|
|
|
|
ON_OBJECT_IMPLEMENT(ON_PerObjectMeshParameters,ON_UserData,"B5628CA9-82C4-4CAE-9883-487B3E4AB28B");
|
|
|
|
ON_PerObjectMeshParameters* ON_PerObjectMeshParameters::FindOrCreate(const ON_Object* object,bool bCreate)
|
|
{
|
|
if ( 0 == object )
|
|
return 0;
|
|
ON_PerObjectMeshParameters* ud = ON_PerObjectMeshParameters::Cast(object->GetUserData(ON_CLASS_ID(ON_PerObjectMeshParameters)));
|
|
if ( !ud && bCreate )
|
|
{
|
|
ud = new ON_PerObjectMeshParameters();
|
|
const_cast< ON_Object* >(object)->AttachUserData(ud);
|
|
}
|
|
return ud;
|
|
}
|
|
|
|
ON_PerObjectMeshParameters::ON_PerObjectMeshParameters()
|
|
: m_mp(ON_MeshParameters::FastRenderMesh)
|
|
{
|
|
m_userdata_uuid = ON_CLASS_ID(ON_PerObjectMeshParameters);
|
|
m_application_uuid = ON_opennurbs5_id;
|
|
m_userdata_copycount = 1;
|
|
m_mp.SetCustomSettings(true);
|
|
m_mp.SetComputeCurvature(false);
|
|
}
|
|
|
|
ON_PerObjectMeshParameters::~ON_PerObjectMeshParameters()
|
|
{
|
|
}
|
|
|
|
// virtual ON_Object override
|
|
bool ON_PerObjectMeshParameters::IsValid( ON_TextLog* text_log ) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// virtual ON_Object override
|
|
unsigned int ON_PerObjectMeshParameters::SizeOf() const
|
|
{
|
|
size_t sz = sizeof(*this) - sizeof(ON_UserData);
|
|
return (unsigned int)sz;
|
|
}
|
|
|
|
// virtual ON_Object override
|
|
ON__UINT32 ON_PerObjectMeshParameters::DataCRC(ON__UINT32 current_remainder) const
|
|
{
|
|
return m_mp.DataCRC(current_remainder);
|
|
}
|
|
|
|
// virtual ON_Object override
|
|
bool ON_PerObjectMeshParameters::Write(ON_BinaryArchive& binary_archive) const
|
|
{
|
|
if ( !binary_archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,0) )
|
|
return false;
|
|
|
|
bool rc = false;
|
|
for(;;)
|
|
{
|
|
if ( !binary_archive.BeginWrite3dmBigChunk(TCODE_ANONYMOUS_CHUNK,0) )
|
|
break;
|
|
bool mprc = m_mp.Write(binary_archive);
|
|
if ( !binary_archive.EndWrite3dmChunk() )
|
|
break;
|
|
if ( !mprc )
|
|
break;
|
|
rc = true;
|
|
break;
|
|
}
|
|
|
|
if ( !binary_archive.EndWrite3dmChunk() )
|
|
rc = false;
|
|
|
|
return rc;
|
|
}
|
|
|
|
// virtual ON_Object override
|
|
bool ON_PerObjectMeshParameters::Read(ON_BinaryArchive& binary_archive)
|
|
{
|
|
m_mp = ON_MeshParameters::FastRenderMesh;
|
|
m_mp.SetCustomSettings(true);
|
|
m_mp.SetComputeCurvature(false);
|
|
|
|
int major_version = 0;
|
|
int minor_version = 0;
|
|
if ( !binary_archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version) )
|
|
return false;
|
|
|
|
bool rc = false;
|
|
for(;;)
|
|
{
|
|
if ( 1 != major_version )
|
|
break;
|
|
|
|
unsigned int tcode(0);
|
|
ON__INT64 value(0);
|
|
if (!binary_archive.BeginRead3dmBigChunk(&tcode,&value))
|
|
break;
|
|
bool mprc = false;
|
|
for(;;)
|
|
{
|
|
if (TCODE_ANONYMOUS_CHUNK != tcode )
|
|
break;
|
|
if (value <= 0)
|
|
break;
|
|
if (!m_mp.Read(binary_archive))
|
|
break;
|
|
mprc = true;
|
|
break;
|
|
}
|
|
if (!binary_archive.EndRead3dmChunk())
|
|
break;
|
|
if (!mprc)
|
|
break;
|
|
|
|
rc = true;
|
|
break;
|
|
}
|
|
|
|
if ( !binary_archive.EndRead3dmChunk() )
|
|
rc = false;
|
|
|
|
m_mp.SetCustomSettings(true);
|
|
m_mp.SetComputeCurvature(false);
|
|
|
|
return rc;
|
|
}
|
|
|
|
// virtual ON_UserData override
|
|
bool ON_PerObjectMeshParameters::Archive() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// virtual ON_UserData override
|
|
bool ON_PerObjectMeshParameters::GetDescription( ON_wString& description )
|
|
{
|
|
description = L"Custom Render Mesh Parameters";
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// END ON_PerObjectMeshParameters user data class
|
|
//
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
const ON_MeshParameters* ON_3dmObjectAttributes::CustomRenderMeshParameters() const
|
|
{
|
|
const ON_PerObjectMeshParameters* ud = ON_PerObjectMeshParameters::FindOrCreate(this,false);
|
|
return (0 != ud) ? &ud->m_mp : 0;
|
|
}
|
|
|
|
bool ON_3dmObjectAttributes::SetCustomRenderMeshParameters( const ON_MeshParameters& mp )
|
|
{
|
|
ON_PerObjectMeshParameters* ud = ON_PerObjectMeshParameters::FindOrCreate(this,true);
|
|
if ( 0 == ud )
|
|
return false;
|
|
ud->m_mp = mp;
|
|
ud->m_mp.SetCustomSettings(true);
|
|
ud->m_mp.SetComputeCurvature(false);
|
|
return true;
|
|
}
|
|
|
|
void ON_3dmObjectAttributes::DeleteCustomRenderMeshParameters()
|
|
{
|
|
ON_PerObjectMeshParameters* ud = ON_PerObjectMeshParameters::FindOrCreate(this,false);
|
|
if ( 0 != ud )
|
|
delete ud;
|
|
}
|
|
|
|
bool ON_3dmObjectAttributes::EnableCustomRenderMeshParameters(bool bEnable)
|
|
{
|
|
ON_PerObjectMeshParameters* ud = ON_PerObjectMeshParameters::FindOrCreate(this,false);
|
|
if ( 0 != ud )
|
|
ud->m_mp.SetCustomSettingsEnabled(bEnable);
|
|
return (!bEnable || nullptr != ud);
|
|
}
|
|
|
|
void ON_Mesh::DestroyTree( bool bDeleteTree )
|
|
{
|
|
}
|
|
|
|
|
|
|
|
|
|
static int compare2fPoint(const void* a, const void* b)
|
|
{
|
|
const float* af = (const float*)a;
|
|
const float* bf = (const float*)b;
|
|
if (af[0] < bf[0])
|
|
return -1;
|
|
if (af[0] > bf[0])
|
|
return 1;
|
|
if (af[1] < bf[1])
|
|
return -1;
|
|
if (af[1] > bf[1])
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int compare2dPoint(const void* a, const void* b)
|
|
{
|
|
const double* af = (const double*)a;
|
|
const double* bf = (const double*)b;
|
|
if (af[0] < bf[0])
|
|
return -1;
|
|
if (af[0] > bf[0])
|
|
return 1;
|
|
if (af[1] < bf[1])
|
|
return -1;
|
|
if (af[1] > bf[1])
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
//static int compare3fPoint(const void* a, const void* b)
|
|
//{
|
|
// const float* af = (const float*)a;
|
|
// const float* bf = (const float*)b;
|
|
// if (af[0] < bf[0])
|
|
// return -1;
|
|
// if (af[0] > bf[0])
|
|
// return 1;
|
|
// if (af[1] < bf[1])
|
|
// return -1;
|
|
// if (af[1] > bf[1])
|
|
// return 1;
|
|
// if (af[2] < bf[2])
|
|
// return -1;
|
|
// if (af[2] > bf[2])
|
|
// return 1;
|
|
// return 0;
|
|
//}
|
|
//
|
|
//static int compare3dPoint(const void* a, const void* b)
|
|
//{
|
|
// const double* af = (const double*)a;
|
|
// const double* bf = (const double*)b;
|
|
// if (af[0] < bf[0])
|
|
// return -1;
|
|
// if (af[0] > bf[0])
|
|
// return 1;
|
|
// if (af[1] < bf[1])
|
|
// return -1;
|
|
// if (af[1] > bf[1])
|
|
// return 1;
|
|
// if (af[2] < bf[2])
|
|
// return -1;
|
|
// if (af[2] > bf[2])
|
|
// return 1;
|
|
// return 0;
|
|
//}
|
|
|
|
|
|
static int comparedUnsignedPair(const void* a, const void* b)
|
|
{
|
|
const unsigned int* au = ((const unsigned int*)a);
|
|
const unsigned int* bu = ((const unsigned int*)b);
|
|
if (au[0] < bu[0])
|
|
return -1;
|
|
if (au[0] > bu[0])
|
|
return 1;
|
|
if (au[1] < bu[1])
|
|
return -1;
|
|
if (au[1] > bu[1])
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int* ON_GetPointLocationIdsHelper(
|
|
size_t point_dim,
|
|
size_t point_count,
|
|
size_t point_stride,
|
|
const float* fPoints,
|
|
const double* dPoints,
|
|
unsigned int first_vid,
|
|
unsigned int* Vid,
|
|
unsigned int* Vindex
|
|
)
|
|
{
|
|
// Used here and in opennurbs_plus_xmeshfast.cpp
|
|
|
|
if (point_count <= 0 || point_dim < 2 || point_dim > 3 || point_stride < point_dim)
|
|
return nullptr;
|
|
|
|
if (nullptr == fPoints && nullptr == dPoints)
|
|
return nullptr;
|
|
|
|
const unsigned int Vcount = (unsigned int)point_count;
|
|
if (0 == Vid)
|
|
Vid = (unsigned int*)onmalloc(Vcount*sizeof(*Vid));
|
|
|
|
if (1 == point_count)
|
|
{
|
|
Vid[0] = first_vid;
|
|
if (nullptr != Vindex)
|
|
Vindex[0] = 0;
|
|
return Vid;
|
|
}
|
|
|
|
if (2 == point_dim)
|
|
{
|
|
// Dictionary sort the 2d points (sort on x, then y).
|
|
if (nullptr != dPoints)
|
|
ON_Sort(ON::sort_algorithm::quick_sort, Vid, dPoints, Vcount, point_stride*sizeof(dPoints[0]), compare2dPoint);
|
|
else
|
|
ON_Sort(ON::sort_algorithm::quick_sort, Vid, fPoints, Vcount, point_stride*sizeof(fPoints[0]), compare2fPoint);
|
|
}
|
|
else
|
|
{
|
|
// Dictionary sort the 3d points (sort on x, then y, then z).
|
|
if (nullptr != dPoints)
|
|
ON_Sort(ON::sort_algorithm::quick_sort, Vid, dPoints, Vcount, point_stride*sizeof(dPoints[0]), (ON_COMPAR_LPVOID_LPVOID)compare3dPoint);
|
|
else
|
|
ON_Sort(ON::sort_algorithm::quick_sort, Vid, fPoints, Vcount, point_stride*sizeof(fPoints[0]), (ON_COMPAR_LPVOID_LPVOID)compare3fPoint);
|
|
}
|
|
|
|
// Assign a unique temporary id to each group of coincident points.
|
|
// The id is temporary because the order of the above sort will change
|
|
// if the point set is rotated.
|
|
struct tagUnsignedPair
|
|
{
|
|
unsigned int m_temporary_id;
|
|
unsigned int m_vertex_index;
|
|
} *up = new tagUnsignedPair[Vcount];
|
|
|
|
unsigned int temporary_id = Vid[0];
|
|
unsigned int i0 = 0;
|
|
if (nullptr != dPoints)
|
|
{
|
|
const double* dP0 = dPoints + point_stride*Vid[0];
|
|
if (3 == point_dim)
|
|
{
|
|
for (unsigned int i = 1; i < Vcount; i++)
|
|
{
|
|
const double* dP1 = dPoints + point_stride*Vid[i];
|
|
if (dP0[0] == dP1[0] && dP0[1] == dP1[1] && dP0[2] == dP1[2])
|
|
{
|
|
if (Vid[i] < temporary_id)
|
|
temporary_id = Vid[i];
|
|
}
|
|
else
|
|
{
|
|
while (i0 < i)
|
|
{
|
|
up[i0].m_temporary_id = temporary_id;
|
|
up[i0].m_vertex_index = Vid[i0];
|
|
i0++;
|
|
}
|
|
temporary_id = Vid[i0];
|
|
dP0 = dP1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (unsigned int i = 1; i < Vcount; i++)
|
|
{
|
|
const double* dP1 = dPoints + point_stride*Vid[i];
|
|
if (dP0[0] == dP1[0] && dP0[1] == dP1[1])
|
|
{
|
|
if (Vid[i] < temporary_id)
|
|
temporary_id = Vid[i];
|
|
}
|
|
else
|
|
{
|
|
while (i0 < i)
|
|
{
|
|
up[i0].m_temporary_id = temporary_id;
|
|
up[i0].m_vertex_index = Vid[i0];
|
|
i0++;
|
|
}
|
|
temporary_id = Vid[i0];
|
|
dP0 = dP1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const float* fP0 = fPoints + point_stride*Vid[0];
|
|
if (3 == point_dim)
|
|
{
|
|
for (unsigned int i = 1; i < Vcount; i++)
|
|
{
|
|
const float* fP1 = fPoints + point_stride*Vid[i];
|
|
if (fP0[0] == fP1[0] && fP0[1] == fP1[1] && fP0[2] == fP1[2])
|
|
{
|
|
if (Vid[i] < temporary_id)
|
|
temporary_id = Vid[i];
|
|
}
|
|
else
|
|
{
|
|
while (i0 < i)
|
|
{
|
|
up[i0].m_temporary_id = temporary_id;
|
|
up[i0].m_vertex_index = Vid[i0];
|
|
i0++;
|
|
}
|
|
temporary_id = Vid[i0];
|
|
fP0 = fP1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (unsigned int i = 1; i < Vcount; i++)
|
|
{
|
|
const float* fP1 = fPoints + point_stride*Vid[i];
|
|
if (fP0[0] == fP1[0] && fP0[1] == fP1[1])
|
|
{
|
|
if (Vid[i] < temporary_id)
|
|
temporary_id = Vid[i];
|
|
}
|
|
else
|
|
{
|
|
while (i0 < i)
|
|
{
|
|
up[i0].m_temporary_id = temporary_id;
|
|
up[i0].m_vertex_index = Vid[i0];
|
|
i0++;
|
|
}
|
|
temporary_id = Vid[i0];
|
|
fP0 = fP1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
while (i0 < Vcount)
|
|
{
|
|
up[i0].m_temporary_id = temporary_id;
|
|
up[i0].m_vertex_index = Vid[i0];
|
|
i0++;
|
|
}
|
|
|
|
// This sort is necessary to insure the vertex id does not change when
|
|
// the point set is transformed. It is important that the id be the
|
|
// same in these situations so that vertex id groups retain the same
|
|
// id when meshes are rotated.
|
|
ON_qsort(up, Vcount, sizeof(up[0]), comparedUnsignedPair);
|
|
|
|
temporary_id = up[0].m_temporary_id;
|
|
unsigned int id = first_vid;
|
|
Vid[up[0].m_vertex_index] = id;
|
|
for (unsigned int i = 1; i < Vcount; i++)
|
|
{
|
|
if (temporary_id != up[i].m_temporary_id)
|
|
{
|
|
temporary_id = up[i].m_temporary_id;
|
|
id++;
|
|
}
|
|
Vid[up[i].m_vertex_index] = id;
|
|
}
|
|
|
|
if (0 != Vindex)
|
|
{
|
|
for (unsigned int i = 0; i < Vcount; i++)
|
|
{
|
|
Vindex[i] = up[i].m_vertex_index;
|
|
}
|
|
}
|
|
|
|
delete[] up;
|
|
|
|
return Vid;
|
|
}
|
|
|
|
|
|
unsigned int* ON_GetPointLocationIds(
|
|
size_t point_dim,
|
|
size_t point_count,
|
|
size_t point_stride,
|
|
const float* points,
|
|
unsigned int first_point_id,
|
|
unsigned int* point_ids,
|
|
unsigned int* point_id_index
|
|
)
|
|
{
|
|
return ON_GetPointLocationIdsHelper(
|
|
point_dim,
|
|
point_count,
|
|
point_stride,
|
|
points,
|
|
nullptr,
|
|
first_point_id,
|
|
point_ids,
|
|
point_id_index
|
|
);
|
|
}
|
|
|
|
|
|
unsigned int* ON_GetPointLocationIds(
|
|
size_t point_dim,
|
|
size_t point_count,
|
|
size_t point_stride,
|
|
const double* points,
|
|
unsigned int first_point_id,
|
|
unsigned int* point_ids,
|
|
unsigned int* point_id_index
|
|
)
|
|
{
|
|
return ON_GetPointLocationIdsHelper(
|
|
point_dim,
|
|
point_count,
|
|
point_stride,
|
|
nullptr,
|
|
points,
|
|
first_point_id,
|
|
point_ids,
|
|
point_id_index
|
|
);
|
|
}
|
|
|
|
|
|
unsigned int* ON_GetPointLocationIds(
|
|
size_t point_count,
|
|
const class ON_2fPoint* points,
|
|
unsigned int first_point_id,
|
|
unsigned int* point_ids,
|
|
unsigned int* point_id_map
|
|
)
|
|
{
|
|
const unsigned int point_dim = 2;
|
|
return ON_GetPointLocationIds(point_dim, point_count, point_dim, (const float*)points, first_point_id, point_ids, point_id_map);
|
|
}
|
|
|
|
|
|
unsigned int* ON_GetPointLocationIds(
|
|
size_t point_count,
|
|
const class ON_3fPoint* points,
|
|
unsigned int first_point_id,
|
|
unsigned int* point_ids,
|
|
unsigned int* point_id_map
|
|
)
|
|
{
|
|
const unsigned int point_dim = 3;
|
|
return ON_GetPointLocationIds(point_dim, point_count, point_dim, (const float*)points, first_point_id, point_ids, point_id_map);
|
|
}
|
|
|
|
|
|
unsigned int* ON_GetPointLocationIds(
|
|
size_t point_count,
|
|
const class ON_2dPoint* points,
|
|
unsigned int first_point_id,
|
|
unsigned int* point_ids,
|
|
unsigned int* point_id_map
|
|
)
|
|
{
|
|
const unsigned int point_dim = 2;
|
|
return ON_GetPointLocationIds(point_dim, point_count, point_dim, (const double*)points, first_point_id, point_ids, point_id_map);
|
|
}
|
|
|
|
|
|
unsigned int* ON_GetPointLocationIds(
|
|
size_t point_count,
|
|
const class ON_3dPoint* points,
|
|
unsigned int first_point_id,
|
|
unsigned int* point_ids,
|
|
unsigned int* point_id_map
|
|
)
|
|
{
|
|
const unsigned int point_dim = 3;
|
|
return ON_GetPointLocationIds(point_dim, point_count, point_dim, (const double*)points, first_point_id, point_ids, point_id_map);
|
|
}
|
|
|
|
unsigned int* ON_Mesh::GetVertexLocationIds(
|
|
unsigned int first_vid,
|
|
unsigned int* Vid,
|
|
unsigned int* Vindex
|
|
) const
|
|
{
|
|
const unsigned int vertex_count = VertexUnsignedCount();
|
|
return (HasSynchronizedDoubleAndSinglePrecisionVertices())
|
|
? ON_GetPointLocationIds(vertex_count, m_dV.Array(), first_vid, Vid, Vindex)
|
|
: ON_GetPointLocationIds(vertex_count, m_V.Array(), first_vid, Vid, Vindex);
|
|
}
|
|
|
|
unsigned int ON_MeshFaceSide::GetFaceSideList(
|
|
size_t mesh_vertex_count,
|
|
const class ON_MeshFaceList& mesh_face_list,
|
|
const unsigned int* fi_list,
|
|
size_t fi_list_count,
|
|
const unsigned int* vertex_id_map,
|
|
ON_MeshFaceSide*& face_side_list
|
|
)
|
|
{
|
|
const unsigned int Vcount = (unsigned int)mesh_vertex_count;
|
|
if ( Vcount <= 0 )
|
|
return 0;
|
|
|
|
const unsigned int mesh_face_count = mesh_face_list.FaceCount();
|
|
const unsigned int Fcount = (0 == fi_list) ? mesh_face_count : ((unsigned int)fi_list_count);
|
|
|
|
if ( Fcount <= 0 )
|
|
return 0;
|
|
|
|
unsigned int fvi[4];
|
|
int face_side_count = 0;
|
|
ON_MeshFaceSide face_side = ON_MeshFaceSide::Unset;
|
|
ON_MeshFaceSide* fs_list = face_side_list;
|
|
|
|
if ( 0 == fs_list )
|
|
{
|
|
fs_list = (ON_MeshFaceSide*)onmalloc(4*Fcount*sizeof(fs_list[0]));
|
|
if ( 0 == fs_list )
|
|
return 0;
|
|
}
|
|
|
|
for ( face_side.m_fi = 0; face_side.m_fi < Fcount; face_side.m_fi++ )
|
|
{
|
|
if ( 0 != fi_list )
|
|
{
|
|
if ( fi_list[face_side.m_fi] >= mesh_face_count )
|
|
continue;
|
|
mesh_face_list.QuadFvi(fi_list[face_side.m_fi],fvi);
|
|
}
|
|
else
|
|
{
|
|
mesh_face_list.QuadFvi(face_side.m_fi,fvi);
|
|
}
|
|
|
|
// These checks are necessary to prevent crashes
|
|
if ( fvi[0] >= Vcount )
|
|
continue;
|
|
if ( fvi[1] >= Vcount )
|
|
continue;
|
|
if ( fvi[2] >= Vcount )
|
|
continue;
|
|
if ( fvi[3] >= Vcount )
|
|
continue;
|
|
|
|
if ( 0 != vertex_id_map )
|
|
{
|
|
fvi[0] = vertex_id_map[fvi[0]];
|
|
fvi[1] = vertex_id_map[fvi[1]];
|
|
fvi[2] = vertex_id_map[fvi[2]];
|
|
fvi[3] = vertex_id_map[fvi[3]];
|
|
}
|
|
|
|
if ( fvi[0] < fvi[1] )
|
|
{
|
|
face_side.m_vi[0] = fvi[0];
|
|
face_side.m_vi[1] = fvi[1];
|
|
face_side.m_side = 0;
|
|
face_side.m_dir = 0;
|
|
fs_list[face_side_count++] = face_side;
|
|
}
|
|
else if ( fvi[0] > fvi[1] )
|
|
{
|
|
face_side.m_vi[0] = fvi[1];
|
|
face_side.m_vi[1] = fvi[0];
|
|
face_side.m_side = 0;
|
|
face_side.m_dir = 1;
|
|
fs_list[face_side_count++] = face_side;
|
|
}
|
|
|
|
if ( fvi[1] < fvi[2] )
|
|
{
|
|
face_side.m_vi[0] = fvi[1];
|
|
face_side.m_vi[1] = fvi[2];
|
|
face_side.m_side = 1;
|
|
face_side.m_dir = 0;
|
|
fs_list[face_side_count++] = face_side;
|
|
}
|
|
else if ( fvi[1] > fvi[2] )
|
|
{
|
|
face_side.m_vi[0] = fvi[2];
|
|
face_side.m_vi[1] = fvi[1];
|
|
face_side.m_side = 1;
|
|
face_side.m_dir = 1;
|
|
fs_list[face_side_count++] = face_side;
|
|
}
|
|
|
|
if ( fvi[2] < fvi[3] )
|
|
{
|
|
face_side.m_vi[0] = fvi[2];
|
|
face_side.m_vi[1] = fvi[3];
|
|
face_side.m_side = 2;
|
|
face_side.m_dir = 0;
|
|
fs_list[face_side_count++] = face_side;
|
|
}
|
|
else if ( fvi[2] > fvi[3] )
|
|
{
|
|
face_side.m_vi[0] = fvi[3];
|
|
face_side.m_vi[1] = fvi[2];
|
|
face_side.m_side = 2;
|
|
face_side.m_dir = 1;
|
|
fs_list[face_side_count++] = face_side;
|
|
}
|
|
|
|
if ( fvi[3] < fvi[0] )
|
|
{
|
|
face_side.m_vi[0] = fvi[3];
|
|
face_side.m_vi[1] = fvi[0];
|
|
face_side.m_side = 3;
|
|
face_side.m_dir = 0;
|
|
fs_list[face_side_count++] = face_side;
|
|
}
|
|
else if ( fvi[3] > fvi[0] )
|
|
{
|
|
face_side.m_vi[0] = fvi[0];
|
|
face_side.m_vi[1] = fvi[3];
|
|
face_side.m_side = 3;
|
|
face_side.m_dir = 1;
|
|
fs_list[face_side_count++] = face_side;
|
|
}
|
|
}
|
|
|
|
if ( 0 == face_side_list )
|
|
{
|
|
if ( face_side_count <= 0 )
|
|
onfree(fs_list);
|
|
else
|
|
face_side_list = fs_list;
|
|
}
|
|
|
|
return face_side_count;
|
|
}
|
|
|
|
unsigned int ON_Mesh::GetMeshFaceSideList(
|
|
const unsigned int* Vid,
|
|
ON_MeshFaceSide*& face_side_list
|
|
) const
|
|
{
|
|
const unsigned int mesh_vertex_count = m_V.UnsignedCount();
|
|
if ( mesh_vertex_count < 2 )
|
|
return 0;
|
|
|
|
const ON_MeshFaceList mesh_face_list(this);
|
|
if ( mesh_face_list.FaceCount() < 1 )
|
|
return 0;
|
|
|
|
return (int)ON_MeshFaceSide::GetFaceSideList(
|
|
mesh_vertex_count,
|
|
mesh_face_list,
|
|
0,0,
|
|
Vid,
|
|
face_side_list
|
|
);
|
|
}
|
|
|
|
|
|
bool ON_Mesh::DeleteComponents(
|
|
const ON_SimpleArray<ON_COMPONENT_INDEX>& ci_list
|
|
)
|
|
{
|
|
return DeleteComponents(ci_list.Array(), ci_list.UnsignedCount());
|
|
}
|
|
|
|
bool ON_Mesh::DeleteComponent(
|
|
ON_COMPONENT_INDEX ci
|
|
)
|
|
{
|
|
return DeleteComponents(&ci, 1);
|
|
}
|
|
|
|
bool ON_Mesh::DeleteComponents(
|
|
const ON_COMPONENT_INDEX* ci_list,
|
|
size_t ci_count
|
|
)
|
|
{
|
|
if (ci_count <= 0)
|
|
return true;
|
|
|
|
if (nullptr == ci_list)
|
|
return false;
|
|
|
|
bool bIgnoreInvalidComponents = true;
|
|
bool bRemoveDegenerateFaces = false;
|
|
bool bRemoveUnusedVertices = true;
|
|
bool bRemoveEmptyNgons = true;
|
|
|
|
return DeleteComponents(
|
|
ci_list,ci_count,
|
|
bIgnoreInvalidComponents,
|
|
bRemoveDegenerateFaces,
|
|
bRemoveUnusedVertices,
|
|
bRemoveEmptyNgons
|
|
);
|
|
}
|
|
|
|
bool ON_Mesh::DeleteComponents(
|
|
const ON_COMPONENT_INDEX* ci_list,
|
|
size_t ci_count,
|
|
bool bIgnoreInvalidComponents,
|
|
bool bRemoveDegenerateFaces,
|
|
bool bRemoveUnusedVertices,
|
|
bool bRemoveEmptyNgons
|
|
)
|
|
{
|
|
return DeleteComponents(ci_list, ci_count,
|
|
bIgnoreInvalidComponents, bRemoveDegenerateFaces, bRemoveUnusedVertices, bRemoveEmptyNgons,
|
|
nullptr);
|
|
}
|
|
|
|
static int Internal_FaceDegenerateAreaCheck(
|
|
ON_MeshFace& f,
|
|
int vertex_count,
|
|
const ON_3fPoint* fV,
|
|
const ON_3dPoint* dV
|
|
)
|
|
{
|
|
// returns:
|
|
// 0: f is degenerate
|
|
// 1: f is not degenerate
|
|
// 2: f was a degenerate quad and fixed to be a good triangle
|
|
ON_3dPoint V[4];
|
|
double a[2];
|
|
const double atol = 1.0e-36; // a hair bigger than the smallest positive normalized float.
|
|
if (nullptr != dV)
|
|
{
|
|
V[0] = dV[f.vi[0]];
|
|
V[1] = dV[f.vi[1]];
|
|
V[2] = dV[f.vi[2]];
|
|
V[3] = dV[f.vi[3]];
|
|
}
|
|
else
|
|
{
|
|
V[0] = fV[f.vi[0]];
|
|
V[1] = fV[f.vi[1]];
|
|
V[2] = fV[f.vi[2]];
|
|
V[3] = fV[f.vi[3]];
|
|
}
|
|
|
|
if (f.IsTriangle())
|
|
{
|
|
a[0] = ON_CrossProduct(V[1]-V[0],V[2]-V[0]).Length();
|
|
return (a[0] > atol) ? 1 : 0;
|
|
}
|
|
|
|
a[0] = V[0].DistanceTo(V[2]);
|
|
a[1] = V[1].DistanceTo(V[3]);
|
|
|
|
// discard L shaped quads
|
|
if (false == (a[0] > atol && a[1] > atol))
|
|
return 0;
|
|
|
|
if (a[0] <= a[1] * 1e-8)
|
|
return 0; // quad is L shaped at floating point precision
|
|
if (a[1] <= a[0] * 1e-8)
|
|
return 0; // quad is L shaped at floating point precision
|
|
|
|
if (a[0] <= a[1])
|
|
{
|
|
a[0] = ON_CrossProduct(V[1] - V[0], V[2] - V[0]).Length();
|
|
a[1] = ON_CrossProduct(V[2] - V[0], V[3] - V[0]).Length();
|
|
if (a[0] > atol)
|
|
{
|
|
if (a[1] > atol)
|
|
return 1;
|
|
f.vi[3] = -1;
|
|
}
|
|
else if (a[1] > atol)
|
|
f.vi[1] = -1;
|
|
}
|
|
else if (a[1] < a[0] )
|
|
{
|
|
a[0] = ON_CrossProduct(V[2] - V[1], V[3] - V[1]).Length();
|
|
a[1] = ON_CrossProduct(V[3] - V[1], V[2] - V[1]).Length();
|
|
if (a[0] > atol)
|
|
{
|
|
if (a[1] > atol)
|
|
return 1;
|
|
f.vi[0] = -1;
|
|
}
|
|
else if (a[1] > atol)
|
|
f.vi[2] = -1;
|
|
}
|
|
else
|
|
return 0; // nan snuck through
|
|
|
|
const bool bRepaired = (nullptr != dV) ? f.Repair(vertex_count, dV) : f.Repair(vertex_count, fV);
|
|
return bRepaired ? 2 : 0;
|
|
}
|
|
|
|
bool ON_Mesh::DeleteComponents(
|
|
const ON_COMPONENT_INDEX* ci_list,
|
|
size_t ci_count,
|
|
bool bIgnoreInvalidComponents,
|
|
bool bRemoveDegenerateFaces,
|
|
bool bRemoveUnusedVertices,
|
|
bool bRemoveEmptyNgons,
|
|
unsigned int* faceMap
|
|
)
|
|
{
|
|
if (ci_count <= 0 && false == bRemoveUnusedVertices && false == bRemoveEmptyNgons && false == bRemoveDegenerateFaces)
|
|
return true;
|
|
if (0 == ci_list && ci_count > 0)
|
|
return false;
|
|
|
|
const ON_MeshTopology* top = m_top.IsValid() ? &m_top : 0;
|
|
const unsigned int vertex_count0 = m_V.UnsignedCount();
|
|
const unsigned int face_count0 = m_F.UnsignedCount();
|
|
const unsigned int ngon_count0 = NgonUnsignedCount();
|
|
const unsigned int topvertex_count0 = top ? top->m_topv.UnsignedCount() : 0;
|
|
const unsigned int topedge_count0 = top ? top->m_tope.UnsignedCount() : 0;
|
|
unsigned int cdex, fi;
|
|
ON_SimpleArray< unsigned int > point_id_map_buffer;
|
|
ON_MeshFace point_id_map_f;
|
|
unsigned int* point_id_map_fvi = (unsigned int*)point_id_map_f.vi;
|
|
|
|
|
|
if (false == bIgnoreInvalidComponents)
|
|
{
|
|
for (size_t i = 0; i < ci_count; i++)
|
|
{
|
|
cdex = (unsigned int)(ci_list[i].m_index);
|
|
switch (ci_list[i].m_type)
|
|
{
|
|
case ON_COMPONENT_INDEX::mesh_vertex:
|
|
if (cdex >= vertex_count0)
|
|
return false;
|
|
break;
|
|
case ON_COMPONENT_INDEX::meshtop_vertex:
|
|
if (cdex >= topvertex_count0)
|
|
return false;
|
|
break;
|
|
case ON_COMPONENT_INDEX::meshtop_edge:
|
|
if (cdex >= topedge_count0)
|
|
return false;
|
|
break;
|
|
case ON_COMPONENT_INDEX::mesh_face:
|
|
if (cdex >= face_count0)
|
|
return false;
|
|
break;
|
|
case ON_COMPONENT_INDEX::mesh_ngon:
|
|
if (cdex >= ngon_count0)
|
|
return false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (vertex_count0 <= 0)
|
|
return (face_count0 <= 0);
|
|
|
|
ON_SimpleArray<unsigned int> vertex_status_buffer(vertex_count0);
|
|
vertex_status_buffer.SetCount(vertex_count0);
|
|
vertex_status_buffer.Zero();
|
|
unsigned int* vertex_status = vertex_status_buffer.Array();
|
|
|
|
ON_SimpleArray<unsigned int> face_status_buffer;
|
|
unsigned int* face_status;
|
|
if (faceMap == nullptr)
|
|
{
|
|
face_status_buffer.SetCapacity(face_count0);
|
|
face_status_buffer.SetCount(face_count0);
|
|
face_status = face_status_buffer.Array();
|
|
}
|
|
else
|
|
{
|
|
face_status = faceMap;
|
|
}
|
|
memset(face_status, 0, m_F.UnsignedCount() * sizeof(unsigned int));
|
|
|
|
bool bDoomedFaces = false;
|
|
bool bDoomedVertices = false;
|
|
bool bDoomedNgons = false;
|
|
bool bModifiedFaces = false;
|
|
|
|
const unsigned int* fvi;
|
|
|
|
for (size_t i = 0; i < ci_count; i++)
|
|
{
|
|
cdex = (unsigned int)(ci_list[i].m_index);
|
|
switch (ci_list[i].m_type)
|
|
{
|
|
case ON_COMPONENT_INDEX::mesh_vertex:
|
|
if (cdex < vertex_count0)
|
|
{
|
|
bDoomedVertices = true;
|
|
vertex_status[cdex] = ON_UNSET_UINT_INDEX;
|
|
}
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::meshtop_vertex:
|
|
if (cdex < topvertex_count0)
|
|
{
|
|
const ON_MeshTopologyVertex& tv = top->m_topv[cdex];
|
|
for (int tvi = 0; tvi < tv.m_v_count; tvi++)
|
|
{
|
|
const unsigned int vi = (unsigned int)(tv.m_vi[tvi]);
|
|
if (vi < vertex_count0)
|
|
{
|
|
bDoomedVertices = true;
|
|
vertex_status[vi] = ON_UNSET_UINT_INDEX;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::meshtop_edge:
|
|
if (cdex < topedge_count0)
|
|
{
|
|
const ON_MeshTopologyEdge& te = top->m_tope[cdex];
|
|
if (0 != te.m_topfi)
|
|
{
|
|
for (int j = 0; j < te.m_topf_count; j++)
|
|
{
|
|
fi = (unsigned int)(te.m_topfi[j]);
|
|
if (fi < face_count0)
|
|
{
|
|
bDoomedFaces = true;
|
|
face_status[fi] = ON_UNSET_UINT_INDEX;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::mesh_face:
|
|
if (cdex < face_count0)
|
|
{
|
|
bDoomedFaces = true;
|
|
face_status[cdex] = ON_UNSET_UINT_INDEX;
|
|
}
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::mesh_ngon:
|
|
if (cdex < ngon_count0)
|
|
{
|
|
const ON_MeshNgon* ngon = Ngon(cdex);
|
|
if (0 != ngon)
|
|
{
|
|
const int ngon_index = cdex;
|
|
bDoomedNgons = true;
|
|
m_NgonMap.SetCount(0);
|
|
if (0 != ngon->m_fi)
|
|
{
|
|
for (unsigned int j = 0; j < ngon->m_Fcount; j++)
|
|
{
|
|
fi = ngon->m_fi[j];
|
|
if (fi < face_count0)
|
|
{
|
|
bDoomedFaces = true;
|
|
face_status[fi] = ON_UNSET_UINT_INDEX;
|
|
}
|
|
}
|
|
}
|
|
RemoveNgon(ngon_index);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
unsigned int point_id_count = 0;
|
|
const unsigned int* point_id_map = nullptr;
|
|
if (bRemoveDegenerateFaces)
|
|
{
|
|
// point_id_map[vi0] == point_id_map[vi1] if and only if m_V[vi0] == m_V[vi1].
|
|
const unsigned int point_count = m_V.UnsignedCount();
|
|
ON_3fPoint* fV = m_V.Array();
|
|
ON_3dPoint* dV = HasDoublePrecisionVertices() ? DoublePrecisionVertices().Array() : 0;
|
|
if (nullptr != dV)
|
|
{
|
|
ON_3dPoint* p1 = dV + point_count;
|
|
const ON_3dPoint badp(ON_DBL_MAX, ON_DBL_MAX, ON_DBL_MAX);
|
|
for (ON_3dPoint* p = dV; p < p1; ++p)
|
|
{
|
|
if (p->IsValid())
|
|
continue;
|
|
*p = badp; // so bad points will sort las
|
|
}
|
|
}
|
|
point_id_count = GetRemoveDegenerateFacesPointMap(point_count, fV, dV, point_id_map_buffer);
|
|
if (point_id_count > 0
|
|
&& point_id_count <= point_id_map_buffer.UnsignedCount()
|
|
&& point_count == point_id_map_buffer.UnsignedCount()
|
|
)
|
|
{
|
|
point_id_map = point_id_map_buffer.Array();
|
|
}
|
|
else
|
|
{
|
|
point_id_count = 0;
|
|
}
|
|
}
|
|
|
|
unsigned int face_count1 = 0;
|
|
{
|
|
const ON_3fPoint* fV = m_V.Array();
|
|
const ON_3dPoint* dV = HasDoublePrecisionVertices() ? DoublePrecisionVertices().Array() : 0;
|
|
for (fi = 0; fi < face_count0; fi++)
|
|
{
|
|
if (0 != face_status[fi])
|
|
continue; // this face status is already known
|
|
|
|
ON_MeshFace& f0 = m_F[fi];
|
|
fvi = (const unsigned int*)f0.vi;
|
|
for (unsigned int j = 0; j < 4; j++)
|
|
{
|
|
if (fvi[j] >= vertex_count0 || ON_UNSET_UINT_INDEX == vertex_status[fvi[j]])
|
|
{
|
|
bDoomedFaces = true;
|
|
face_status[fi] = ON_UNSET_UINT_INDEX;
|
|
break;
|
|
}
|
|
}
|
|
if (ON_UNSET_UINT_INDEX == face_status[fi])
|
|
continue; // f0 has invalid vertex indices
|
|
|
|
if (point_id_count > 0)
|
|
{
|
|
// if point_id_count > 0, then bRemoveDegenerateFaces = true.
|
|
// set point_id_map_f.vi[] to values of "topological indices"
|
|
point_id_map_fvi[0] = point_id_map[f0.vi[0]];
|
|
point_id_map_fvi[1] = point_id_map[f0.vi[1]];
|
|
point_id_map_fvi[2] = point_id_map[f0.vi[2]];
|
|
point_id_map_fvi[3] = point_id_map[f0.vi[3]];
|
|
if (false == point_id_map_f.IsValid(point_id_count))
|
|
{
|
|
// point_id_map_f invalid means we have a degenerate quad or worse.
|
|
|
|
if (f0.IsQuad() && (point_id_map_f.vi[0] == point_id_map_f.vi[2] || point_id_map_f.vi[1] == point_id_map_f.vi[3]))
|
|
{
|
|
// "L" quads just get deleted.
|
|
bDoomedFaces = true;
|
|
face_status[fi] = ON_UNSET_UINT_INDEX;
|
|
continue;
|
|
}
|
|
|
|
// the corners of face f0 are not perfect. We either have to convert a quad to a triangle
|
|
// or we have a degenerate face.
|
|
if ((nullptr != dV) ? f0.Repair(vertex_count0, dV) : f0.Repair(vertex_count0, fV))
|
|
{
|
|
// f0 what an invalid quad fixed to be a valid triangle
|
|
bModifiedFaces = true;
|
|
}
|
|
else
|
|
{
|
|
// face cannot be repaired by juggling vertex indices
|
|
bDoomedFaces = true;
|
|
face_status[fi] = ON_UNSET_UINT_INDEX;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
switch (Internal_FaceDegenerateAreaCheck(f0, (int)vertex_count0, fV, dV))
|
|
{
|
|
case 0:
|
|
bDoomedFaces = true;
|
|
face_status[fi] = ON_UNSET_UINT_INDEX;
|
|
break;
|
|
|
|
case 1:
|
|
// f0 is totally valid
|
|
break;
|
|
|
|
case 2:
|
|
// f0 what an invalid quad fixed to be a valid triangle
|
|
bModifiedFaces = true;
|
|
break;
|
|
}
|
|
|
|
if (ON_UNSET_UINT_INDEX == face_status[fi])
|
|
continue;
|
|
}
|
|
|
|
face_status[fi] = face_count1++;
|
|
for (unsigned int j = 0; j < 4; j++)
|
|
{
|
|
vertex_status[fvi[j]] = 1; // vertex is referenced by a face
|
|
}
|
|
}
|
|
}
|
|
|
|
unsigned int vertex_count1 = 0;
|
|
if ( bRemoveUnusedVertices )
|
|
{
|
|
for ( unsigned int vi = 0; vi < vertex_count0; vi++ )
|
|
{
|
|
if ( 1 == vertex_status[vi] )
|
|
vertex_status[vi] = vertex_count1++; // vertex referenced by a face
|
|
else
|
|
vertex_status[vi] = ON_UNSET_UINT_INDEX; // vertex deleted or not referenced by a face
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for ( unsigned int vi = 0; vi < vertex_count0; vi++ )
|
|
{
|
|
if ( vertex_status[vi] <= 1 )
|
|
vertex_status[vi] = vertex_count1++;
|
|
else
|
|
vertex_status[vi] = ON_UNSET_UINT_INDEX; // vertex explicitly deleted
|
|
}
|
|
}
|
|
|
|
if ( 0 == vertex_count1 || 0 == face_count1 )
|
|
{
|
|
Destroy();
|
|
return true;
|
|
}
|
|
|
|
if ( vertex_count1 < vertex_count0 || face_count1 < face_count0 )
|
|
{
|
|
DestroyTopology();
|
|
DestroyPartition();
|
|
DestroyTree();
|
|
|
|
for ( unsigned int ngon_index = 0; ngon_index < ngon_count0; ngon_index ++ )
|
|
{
|
|
const ON_MeshNgon* ngon = Ngon(ngon_index);
|
|
if ( 0 == ngon || 0 == ngon->m_vi )
|
|
continue;
|
|
|
|
if ( face_count1 < face_count0 )
|
|
{
|
|
for ( unsigned int j = 0; j < ngon->m_Fcount; j++ )
|
|
{
|
|
fi = ngon->m_fi[j];
|
|
if ( fi >= face_count0 || ON_UNSET_UINT_INDEX == face_status[fi] )
|
|
{
|
|
bDoomedNgons = true;
|
|
RemoveNgon(ngon_index);
|
|
ngon = 0;
|
|
break;
|
|
}
|
|
ngon->m_fi[j] = face_status[fi];
|
|
}
|
|
if ( 0 == ngon )
|
|
continue;
|
|
}
|
|
|
|
if ( vertex_count1 < vertex_count0 )
|
|
{
|
|
for ( unsigned int j = 0; j < ngon->m_Vcount; j++ )
|
|
{
|
|
const unsigned int vi = ngon->m_vi[j];
|
|
if ( vi >= vertex_count0 || ON_UNSET_UINT_INDEX == vertex_status[vi] )
|
|
{
|
|
bDoomedNgons = true;
|
|
RemoveNgon(ngon_index);
|
|
ngon = 0;
|
|
break;
|
|
}
|
|
ngon->m_vi[j] = vertex_status[vi];
|
|
}
|
|
}
|
|
}
|
|
|
|
unsigned int* fvi1;
|
|
ON_MeshFace* F = m_F.Array();
|
|
ON_3fVector* FN = HasFaceNormals() ? m_FN.Array() : 0;
|
|
if ( vertex_count1 == vertex_count0 )
|
|
{
|
|
for ( face_count1 = 0; face_count1 < face_count0; face_count1++ )
|
|
{
|
|
if ( ON_UNSET_UINT_INDEX == face_status[face_count1] )
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
face_count1 = 0;
|
|
}
|
|
for ( fi = face_count1; fi < face_count0; fi++ )
|
|
{
|
|
if ( ON_UNSET_UINT_INDEX == face_status[fi] )
|
|
continue;
|
|
fvi = (const unsigned int*)F[fi].vi;
|
|
fvi1 = (unsigned int*)(F[face_count1].vi);
|
|
fvi1[0] = vertex_status[fvi[0]];
|
|
fvi1[1] = vertex_status[fvi[1]];
|
|
fvi1[2] = vertex_status[fvi[2]];
|
|
fvi1[3] = vertex_status[fvi[3]];
|
|
if ( FN )
|
|
FN[face_count1] = FN[fi];
|
|
face_count1++;
|
|
}
|
|
|
|
if ( face_count1 < face_count0 )
|
|
{
|
|
m_F.SetCount(face_count1);
|
|
m_F.Shrink();
|
|
if ( FN )
|
|
{
|
|
m_FN.SetCount(face_count1);
|
|
m_FN.Shrink();
|
|
}
|
|
}
|
|
|
|
if ( vertex_count1 < vertex_count0 )
|
|
{
|
|
vertex_count1 = 0;
|
|
bool bValidSingles = (VertexUnsignedCount() == m_V.UnsignedCount());
|
|
bool bValidDoubles = (VertexUnsignedCount() == m_dV.UnsignedCount());
|
|
ON_3fPoint* fV = bValidSingles ? m_V.Array() : nullptr;
|
|
ON_3dPoint* dV = bValidDoubles ? m_dV.Array() : nullptr;
|
|
ON_2dPoint* S = HasSurfaceParameters() ? m_S.Array() : 0;
|
|
ON_3fVector* N = HasVertexNormals() ? m_N.Array() : 0;
|
|
ON_2fPoint* T = HasTextureCoordinates() ? m_T.Array() : 0;
|
|
ON_SurfaceCurvature* K = HasPrincipalCurvatures() ? m_K.Array() : 0;
|
|
ON_Color* C = HasVertexColors() ? m_C.Array() : 0;
|
|
|
|
for ( vertex_count1 = 0; vertex_count1 < vertex_count0; vertex_count1++ )
|
|
{
|
|
if ( ON_UNSET_UINT_INDEX == vertex_status[vertex_count1] )
|
|
break;
|
|
}
|
|
|
|
for ( unsigned int vi = vertex_count1+1; vi < vertex_count0; vi++ )
|
|
{
|
|
if ( ON_UNSET_UINT_INDEX == vertex_status[vi] )
|
|
continue;
|
|
if ( fV )
|
|
fV[vertex_count1] = fV[vi];
|
|
if ( dV )
|
|
dV[vertex_count1] = dV[vi];
|
|
if ( S )
|
|
S[vertex_count1] = S[vi];
|
|
if ( N )
|
|
N[vertex_count1] = N[vi];
|
|
if ( T )
|
|
T[vertex_count1] = T[vi];
|
|
if ( K )
|
|
K[vertex_count1] = K[vi];
|
|
if ( C )
|
|
C[vertex_count1] = C[vi];
|
|
vertex_count1++;
|
|
}
|
|
|
|
if ( fV )
|
|
{
|
|
m_V.SetCount(vertex_count1);
|
|
m_V.Shrink();
|
|
}
|
|
if ( dV )
|
|
{
|
|
m_dV.SetCount(vertex_count1);
|
|
m_dV.Shrink();
|
|
}
|
|
if ( S )
|
|
{
|
|
m_S.SetCount(vertex_count1);
|
|
m_S.Shrink();
|
|
}
|
|
if ( N )
|
|
{
|
|
m_N.SetCount(vertex_count1);
|
|
m_N.Shrink();
|
|
}
|
|
if ( T )
|
|
{
|
|
m_T.SetCount(vertex_count1);
|
|
m_T.Shrink();
|
|
}
|
|
if ( K )
|
|
{
|
|
m_K.SetCount(vertex_count1);
|
|
m_K.Shrink();
|
|
}
|
|
if ( C )
|
|
{
|
|
m_C.SetCount(vertex_count1);
|
|
m_C.Shrink();
|
|
}
|
|
|
|
InvalidateBoundingBoxes();
|
|
}
|
|
|
|
m_invalid_count = 0;
|
|
m_quad_count = 0;
|
|
m_triangle_count = 0;
|
|
|
|
SetClosed(-1);
|
|
}
|
|
else if (bModifiedFaces)
|
|
{
|
|
// converted degenerate quads into triangles
|
|
DestroyTopology();
|
|
m_invalid_count = 0;
|
|
m_quad_count = 0;
|
|
m_triangle_count = 0;
|
|
SetClosed(-1);
|
|
}
|
|
|
|
if ( bRemoveEmptyNgons )
|
|
RemoveEmptyNgons();
|
|
|
|
return true;
|
|
}
|
|
|
|
unsigned int* InitializeMap(unsigned int count,ON_SimpleArray< unsigned int >& map_buffer)
|
|
{
|
|
map_buffer.Reserve(count);
|
|
map_buffer.SetCount(count);
|
|
unsigned int* map = map_buffer.Array();
|
|
for ( unsigned int i = 0; i < count; i++ )
|
|
map[i] = ON_UNSET_UINT_INDEX;
|
|
return map;
|
|
}
|
|
|
|
ON_Mesh* ON_Mesh::CopyComponents(
|
|
const ON_COMPONENT_INDEX* ci_list,
|
|
size_t ci_count,
|
|
class ON_Mesh* destination_mesh
|
|
) const
|
|
{
|
|
if ( ci_count <= 0 || 0 == ci_list )
|
|
return 0;
|
|
|
|
const unsigned int vertex_count = m_V.UnsignedCount();
|
|
const unsigned int face_count = m_F.UnsignedCount();
|
|
|
|
if ( vertex_count <= 0 && face_count <= 0 )
|
|
return 0;
|
|
|
|
const ON_MeshTopology* top = TopologyExists() ? &Topology() : 0;
|
|
const unsigned int topv_count = top ? top->m_topv.UnsignedCount() : 0;
|
|
const unsigned int tope_count = top ? top->m_tope.UnsignedCount() : 0;
|
|
|
|
ON_SimpleArray< unsigned int > vertex_map_buffer;
|
|
ON_SimpleArray< unsigned int > face_map_buffer;
|
|
|
|
unsigned int* vertex_map = 0;
|
|
unsigned int* face_map = 0;
|
|
unsigned int i, fi, vi;
|
|
|
|
ON_MeshNgonBuffer ngon_buffer;
|
|
|
|
for ( size_t ci_index = 0; ci_index < ci_count; ci_index++ )
|
|
{
|
|
ON_COMPONENT_INDEX ci = ci_list[ci_index];
|
|
if ( !ci.IsMeshComponentIndex() )
|
|
continue;
|
|
if ( ci.m_index < 0 )
|
|
continue;
|
|
i = (unsigned int)ci.m_index;
|
|
|
|
switch(ci.m_type)
|
|
{
|
|
case ON_COMPONENT_INDEX::mesh_vertex:
|
|
if ( i >= vertex_count )
|
|
break;
|
|
if ( 0 == vertex_map )
|
|
vertex_map = InitializeMap(vertex_count,vertex_map_buffer);
|
|
vertex_map[i] = 0;
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::mesh_face:
|
|
case ON_COMPONENT_INDEX::mesh_ngon:
|
|
{
|
|
const ON_MeshNgon* ngon = NgonFromComponentIndex(ngon_buffer,ci);
|
|
if ( 0 == ngon )
|
|
break;
|
|
if ( 0 != ngon->m_vi )
|
|
{
|
|
for ( i = 0; i < ngon->m_Vcount; i++ )
|
|
{
|
|
vi = ngon->m_vi[i];
|
|
if ( vi >= vertex_count )
|
|
continue;
|
|
if ( 0 == vertex_map )
|
|
vertex_map = InitializeMap(vertex_count,vertex_map_buffer);
|
|
vertex_map[vi] = 0;
|
|
}
|
|
}
|
|
if ( 0 != ngon->m_fi )
|
|
{
|
|
for ( i = 0; i < ngon->m_Fcount; i++ )
|
|
{
|
|
fi = ngon->m_fi[i];
|
|
if ( fi >= face_count )
|
|
continue;
|
|
if ( 0 == face_map )
|
|
face_map = InitializeMap(face_count,face_map_buffer);
|
|
face_map[fi] = 0;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::meshtop_vertex:
|
|
if ( i < topv_count )
|
|
{
|
|
const ON_MeshTopologyVertex& v = top->m_topv[i];
|
|
if ( v.m_vi )
|
|
{
|
|
for ( int j = 0; j < v.m_v_count; j++ )
|
|
{
|
|
if ( v.m_vi[j] < 0 )
|
|
continue;
|
|
vi = (unsigned int)v.m_vi[j];
|
|
if ( vi < vertex_count )
|
|
{
|
|
if ( 0 == vertex_map )
|
|
vertex_map = InitializeMap(vertex_count,vertex_map_buffer);
|
|
vertex_map[vi] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::meshtop_edge:
|
|
if ( i < tope_count )
|
|
{
|
|
const ON_MeshTopologyEdge& e = top->m_tope[i];
|
|
if ( e.m_topfi )
|
|
{
|
|
for ( int j = 0; j < e.m_topf_count; j++ )
|
|
{
|
|
if ( e.m_topfi[j] < 0 )
|
|
continue;
|
|
fi = (unsigned int)e.m_topfi[j];
|
|
if ( fi >= face_count )
|
|
continue;
|
|
if ( 0 == face_map )
|
|
face_map = InitializeMap(face_count,face_map_buffer);
|
|
face_map[fi] = 0;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
unsigned int face_count1 = 0;
|
|
if ( 0 != face_map )
|
|
{
|
|
for ( i = 0; i < face_count; i++ )
|
|
{
|
|
if ( 0 == face_map[i] )
|
|
{
|
|
ON_MeshFace f = m_F[i];
|
|
if ( f.IsValid(vertex_count) )
|
|
{
|
|
face_map[i] = face_count1++;
|
|
if ( 0 == vertex_map )
|
|
vertex_map = InitializeMap(vertex_count,vertex_map_buffer);
|
|
vertex_map[f.vi[0]] = 0;
|
|
vertex_map[f.vi[1]] = 0;
|
|
vertex_map[f.vi[2]] = 0;
|
|
vertex_map[f.vi[3]] = 0;
|
|
}
|
|
else
|
|
face_map[i] = ON_UNSET_UINT_INDEX; // bogus input face
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( 0 == vertex_map )
|
|
return 0;
|
|
|
|
unsigned int vertex_count1 = 0;
|
|
for ( i = 0; i < vertex_count; i++ )
|
|
{
|
|
if ( 0 == vertex_map[i] )
|
|
vertex_map[i] = vertex_count1++;
|
|
}
|
|
|
|
if ( vertex_count1 <= 0 )
|
|
return 0;
|
|
|
|
ON_Mesh* mesh_copy = 0;
|
|
if ( destination_mesh )
|
|
{
|
|
destination_mesh->Destroy();
|
|
mesh_copy = destination_mesh;
|
|
}
|
|
else
|
|
{
|
|
mesh_copy = new ON_Mesh();
|
|
}
|
|
|
|
if ( face_count1 > 0 )
|
|
{
|
|
// copy face information
|
|
const ON_MeshFace* F = m_F.Array();
|
|
mesh_copy->m_F.Reserve(face_count1);
|
|
const ON_3fVector* FN = HasFaceNormals() ? m_FN.Array() : 0;
|
|
if ( 0 != FN )
|
|
mesh_copy->m_FN.Reserve(face_count1);
|
|
const unsigned int* fvi;
|
|
unsigned int* fvi1;
|
|
for ( fi = 0; fi < face_count; fi++ )
|
|
{
|
|
if ( ON_UNSET_UINT_INDEX == face_map[fi] )
|
|
continue;
|
|
fvi = (const unsigned int*)F[fi].vi;
|
|
fvi1 = (unsigned int*)(mesh_copy->m_F.AppendNew().vi);
|
|
fvi1[0] = vertex_map[fvi[0]];
|
|
fvi1[1] = vertex_map[fvi[1]];
|
|
fvi1[2] = vertex_map[fvi[2]];
|
|
fvi1[3] = vertex_map[fvi[3]];
|
|
if ( FN )
|
|
mesh_copy->m_FN.AppendNew() = FN[fi];
|
|
}
|
|
}
|
|
|
|
const bool bValidSingles = (VertexUnsignedCount() == m_V.UnsignedCount());
|
|
const ON_3fPoint* fV = bValidSingles ? m_V.Array() : nullptr;
|
|
const bool bValidDoubles = (VertexUnsignedCount() == m_dV.UnsignedCount());
|
|
const ON_3dPoint* dV = bValidDoubles ? m_dV.Array() : nullptr;
|
|
const ON_2dPoint* S = HasSurfaceParameters() ? m_S.Array() : 0;
|
|
const ON_3fVector* N = HasVertexNormals() ? m_N.Array() : 0;
|
|
const ON_2fPoint* T = HasTextureCoordinates() ? m_T.Array() : 0;
|
|
const ON_SurfaceCurvature* K = HasPrincipalCurvatures() ? m_K.Array() : 0;
|
|
const ON_Color* C = HasVertexColors() ? m_C.Array() : 0;
|
|
|
|
if (fV)
|
|
mesh_copy->m_V.Reserve(vertex_count1);
|
|
if (dV)
|
|
mesh_copy->m_dV.Reserve(vertex_count1);
|
|
if (S)
|
|
mesh_copy->m_S.Reserve(vertex_count1);
|
|
if (N)
|
|
mesh_copy->m_N.Reserve(vertex_count1);
|
|
if (T)
|
|
mesh_copy->m_T.Reserve(vertex_count1);
|
|
if (K)
|
|
mesh_copy->m_K.Reserve(vertex_count1);
|
|
if (C)
|
|
mesh_copy->m_C.Reserve(vertex_count1);
|
|
|
|
for ( vi = 0; vi < vertex_count; vi++ )
|
|
{
|
|
if ( ON_UNSET_UINT_INDEX == vertex_map[vi] )
|
|
continue;
|
|
if (fV)
|
|
mesh_copy->m_V.AppendNew() = fV[vi];
|
|
if (dV)
|
|
mesh_copy->m_dV.AppendNew() = dV[vi];
|
|
if ( S )
|
|
mesh_copy->m_S.AppendNew() = S[vi];
|
|
if ( N )
|
|
mesh_copy->m_N.AppendNew() = N[vi];
|
|
if ( T )
|
|
mesh_copy->m_T.AppendNew() = T[vi];
|
|
if ( K )
|
|
mesh_copy->m_K.AppendNew() = K[vi];
|
|
if ( C )
|
|
mesh_copy->m_C.AppendNew() = C[vi];
|
|
}
|
|
|
|
const unsigned int ngon_count = NgonCount();
|
|
for ( unsigned int ngon_index = 0; ngon_index < ngon_count; ngon_index++ )
|
|
{
|
|
const ON_MeshNgon* ngon = Ngon(ngon_index);
|
|
if ( 0 == ngon )
|
|
continue;
|
|
if ( 0 == ngon->m_Vcount || 0 == ngon->m_vi )
|
|
continue;
|
|
if ( 0 == ngon->m_Fcount || 0 == ngon->m_fi )
|
|
continue;
|
|
|
|
unsigned int j;
|
|
|
|
for ( j = 0; j < ngon->m_Vcount; j++ )
|
|
{
|
|
vi = ngon->m_vi[j];
|
|
if ( vi >= vertex_count )
|
|
break;
|
|
if ( ON_UNSET_UINT_INDEX == vertex_map[vi] )
|
|
break;
|
|
}
|
|
if ( j < ngon->m_Vcount )
|
|
continue;
|
|
|
|
for ( j = 0; j < ngon->m_Fcount; j++ )
|
|
{
|
|
fi = ngon->m_fi[j];
|
|
if ( fi >= face_count )
|
|
break;
|
|
if ( ON_UNSET_UINT_INDEX == face_map[fi] )
|
|
break;
|
|
}
|
|
if ( j < ngon->m_Fcount )
|
|
continue;
|
|
|
|
ON_MeshNgon* ngon1 = mesh_copy->AllocateNgon(ngon->m_Vcount,ngon->m_Fcount);
|
|
|
|
ngon1->m_Vcount = 0;
|
|
ngon1->m_Fcount = 0;
|
|
for ( j = 0; j < ngon->m_Vcount; j++ )
|
|
{
|
|
ngon1->m_vi[ngon1->m_Vcount++] = vertex_map[ngon->m_vi[j]];
|
|
}
|
|
for ( j = 0; j < ngon->m_Fcount; j++ )
|
|
{
|
|
ngon1->m_fi[ngon1->m_Fcount++] = face_map[ngon->m_fi[j]];
|
|
}
|
|
|
|
mesh_copy->AddNgon(ngon1);
|
|
|
|
if ( 0 != NgonMap() )
|
|
mesh_copy->CreateNgonMap();
|
|
}
|
|
|
|
return mesh_copy;
|
|
}
|
|
|
|
ON_Mesh* ON_Mesh::CopyComponents(
|
|
const ON_SimpleArray<ON_COMPONENT_INDEX>& ci_list,
|
|
class ON_Mesh* destination_mesh
|
|
) const
|
|
{
|
|
return CopyComponents(ci_list.Array(), ci_list.UnsignedCount(), destination_mesh);
|
|
}
|
|
|
|
bool ON_Mesh::SetSurfaceParamtersFromTextureCoodinates()
|
|
{
|
|
unsigned int i;
|
|
const unsigned int vcount = m_V.UnsignedCount();
|
|
|
|
bool rc;
|
|
ON_Interval dom;
|
|
if (vcount == m_T.UnsignedCount())
|
|
{
|
|
dom.Set(0.0, 1.0);
|
|
m_S.SetCount(0);
|
|
m_S.Reserve(vcount);
|
|
for (i = 0; i < vcount; i++)
|
|
{
|
|
ON_2dPoint S = m_T[i];
|
|
m_S.Append(S);
|
|
}
|
|
rc = true;
|
|
}
|
|
else
|
|
{
|
|
dom = ON_Interval::EmptyInterval;
|
|
m_S.Destroy();
|
|
rc = false;
|
|
}
|
|
|
|
m_srf_domain[0] = dom;
|
|
m_srf_domain[1] = dom;
|
|
m_srf_scale[0] = 0.0;
|
|
m_srf_scale[1] = 0.0;
|
|
m_packed_tex_domain[0] = dom;
|
|
m_packed_tex_domain[1] = dom;
|
|
m_packed_tex_rotate = false;
|
|
|
|
return rc;
|
|
|
|
//bool rc = (vcount > 0 && vcount == m_T.UnsignedCount() && vcount != m_S.UnsignedCount());
|
|
//if (!rc)
|
|
// return false;
|
|
//
|
|
//ON_2dPoint Tmin = m_T[0];
|
|
//ON_2dPoint Tmax = m_T[0];
|
|
//ON_2dPoint T;
|
|
//for (i = 1; i < vcount; i++)
|
|
//{
|
|
// T = m_T[i];
|
|
// if (T.x < Tmin.x)
|
|
// Tmin.x = T.x;
|
|
// else if (T.x > Tmax.x)
|
|
// Tmax.x = T.x;
|
|
// if (T.y < Tmin.y)
|
|
// Tmin.y = T.y;
|
|
// else if (T.y > Tmax.y)
|
|
// Tmax.y = T.y;
|
|
//}
|
|
//
|
|
//rc = (0.0f <= Tmin.x && Tmax.x <= 1.0f && 0.0f <= Tmin.y && Tmax.y <= 1.0f);
|
|
//if (!rc)
|
|
// return false;
|
|
|
|
//ON_Interval packed_tex_domain[2];
|
|
//packed_tex_domain[0] = m_packed_tex_domain[0];
|
|
//packed_tex_domain[1] = m_packed_tex_domain[1];
|
|
//const float Ttol = 1.0e-5f;
|
|
|
|
//if (packed_tex_domain[0].IsIncreasing() && packed_tex_domain[1].IsIncreasing())
|
|
//{
|
|
// rc = (packed_tex_domain[0].IsIncreasing() && packed_tex_domain[1].IsIncreasing());
|
|
// if (!rc)
|
|
// return false;
|
|
// rc = (packed_tex_domain[0][0] <= Tmin.x && Tmax.x <= packed_tex_domain[0][1]);
|
|
// if (!rc)
|
|
// return false;
|
|
// rc = (packed_tex_domain[1][0] <= Tmin.y && Tmax.y <= packed_tex_domain[1][1]);
|
|
// if (!rc)
|
|
// return false;
|
|
//}
|
|
//else if (Tmin.x > Ttol || Tmax.x < 1.0 - Ttol || Tmin.y > Ttol || Tmax.y < 1.0 - Ttol)
|
|
//{
|
|
// packed_tex_domain[0].Set(Tmin.x, Tmax.x);
|
|
// packed_tex_domain[1].Set(Tmin.y, Tmax.y);
|
|
//}
|
|
//else
|
|
//{
|
|
// packed_tex_domain[0].Set(0.0, 1.0);
|
|
// packed_tex_domain[1].Set(0.0, 1.0);
|
|
//}
|
|
|
|
//ON_Interval Sdomain[2];
|
|
//if (m_packed_tex_rotate)
|
|
//{
|
|
// Sdomain[0] = packed_tex_domain[1];
|
|
// Sdomain[1].Set(1.0 - packed_tex_domain[0][1], 1.0 - packed_tex_domain[0][0]);
|
|
//}
|
|
//else
|
|
//{
|
|
// Sdomain[0] = packed_tex_domain[0];
|
|
// Sdomain[1] = packed_tex_domain[1];
|
|
//}
|
|
|
|
//rc = Sdomain[0].IsIncreasing() && Sdomain[1].IsIncreasing();
|
|
//if (!rc)
|
|
// return false;
|
|
|
|
//m_packed_tex_domain[0] = packed_tex_domain[0];
|
|
//m_packed_tex_domain[1] = packed_tex_domain[1];
|
|
//m_srf_scale[0] = 0.0;
|
|
//m_srf_scale[1] = 0.0;
|
|
//m_srf_domain[0] = Sdomain[0];
|
|
//m_srf_domain[1] = Sdomain[1];
|
|
|
|
//m_S.Reserve(vcount);
|
|
//m_S.SetCount(0);
|
|
//ON_2dPoint S;
|
|
//if (m_packed_tex_rotate)
|
|
//{
|
|
// for (i = 0; i < vcount; i++)
|
|
// {
|
|
// T = m_T[i];
|
|
// double x = m_packed_tex_domain[0].NormalizedParameterAt(T.x);
|
|
// double y = m_packed_tex_domain[1].NormalizedParameterAt(T.y);
|
|
// S.x = m_srf_domain[0].ParameterAt(y);
|
|
// S.y = m_srf_domain[1].ParameterAt(1.0 - x);
|
|
// S = T;
|
|
// m_S.Append(S);
|
|
// }
|
|
//}
|
|
//else
|
|
//{
|
|
// for (i = 0; i < vcount; i++)
|
|
// {
|
|
// T = m_T[i];
|
|
// S = T;
|
|
// m_S.Append(S);
|
|
// }
|
|
//}
|
|
|
|
//return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// ON_MeshCache
|
|
//
|
|
|
|
class ON_MeshCacheItem
|
|
{
|
|
public:
|
|
ON_MeshCacheItem() = default;
|
|
~ON_MeshCacheItem() = default;
|
|
ON_MeshCacheItem(const ON_MeshCacheItem&) = default;
|
|
ON_MeshCacheItem& operator=(const ON_MeshCacheItem&) = default;
|
|
|
|
#if defined(ON_HAS_RVALUEREF)
|
|
// rvalue copy constructor
|
|
ON_MeshCacheItem( ON_MeshCacheItem&& ) ON_NOEXCEPT;
|
|
// rvalue assignment operator
|
|
ON_MeshCacheItem& operator=( ON_MeshCacheItem&& );
|
|
#endif
|
|
|
|
|
|
bool Write(
|
|
ON_BinaryArchive& archive
|
|
) const;
|
|
|
|
bool Read(
|
|
ON_BinaryArchive& archive
|
|
);
|
|
|
|
void Dump(
|
|
ON_TextLog& text_log
|
|
) const;
|
|
|
|
public:
|
|
ON_UUID m_mesh_id = ON_nil_uuid;
|
|
std::shared_ptr<ON_Mesh> m_mesh_sp;
|
|
ON_MeshCacheItem* m_next = nullptr;
|
|
};
|
|
|
|
#if defined(ON_HAS_RVALUEREF)
|
|
|
|
ON_MeshCacheItem::ON_MeshCacheItem( ON_MeshCacheItem&& src ) ON_NOEXCEPT
|
|
: m_mesh_id(src.m_mesh_id)
|
|
, m_mesh_sp(std::move(src.m_mesh_sp))
|
|
{
|
|
|
|
}
|
|
|
|
ON_MeshCacheItem& ON_MeshCacheItem::operator=( ON_MeshCacheItem&& src )
|
|
{
|
|
std::move(src.m_mesh_sp).swap(m_mesh_sp);
|
|
m_mesh_id = src.m_mesh_id;
|
|
return *this;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
bool ON_MeshCacheItem::Write(
|
|
ON_BinaryArchive& archive
|
|
) const
|
|
{
|
|
const ON_Mesh* mesh = m_mesh_sp.get();
|
|
if (nullptr == mesh)
|
|
return true; // not an error
|
|
|
|
if (!archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,0))
|
|
return false;
|
|
|
|
bool rc = false;
|
|
for (;;)
|
|
{
|
|
if (!archive.WriteUuid(m_mesh_id))
|
|
break;
|
|
if ( !archive.WriteObject(mesh) )
|
|
break;
|
|
rc = true;
|
|
break;
|
|
}
|
|
|
|
if (!archive.EndWrite3dmChunk())
|
|
rc = false;
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool ON_MeshCacheItem::Read(
|
|
ON_BinaryArchive& archive
|
|
)
|
|
{
|
|
m_mesh_id = ON_nil_uuid;
|
|
m_mesh_sp.reset();
|
|
|
|
int major_version = 0;
|
|
int minor_version = 0;
|
|
if (!archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version))
|
|
return false;
|
|
|
|
bool rc = false;
|
|
for (;;)
|
|
{
|
|
if ( 1 != major_version )
|
|
break;
|
|
|
|
if (!archive.ReadUuid(m_mesh_id))
|
|
break;
|
|
|
|
ON_Object* mesh_object = nullptr;
|
|
if ( !archive.ReadObject(&mesh_object) )
|
|
break;
|
|
if ( nullptr == mesh_object )
|
|
break;
|
|
ON_Mesh* mesh = ON_Mesh::Cast(mesh_object);
|
|
if (nullptr == mesh)
|
|
{
|
|
delete mesh_object;
|
|
break;
|
|
}
|
|
std::shared_ptr<ON_Mesh>(mesh).swap(m_mesh_sp);
|
|
rc = true;
|
|
break;
|
|
}
|
|
|
|
if (!archive.EndRead3dmChunk())
|
|
rc = false;
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
void ON_MeshCacheItem::Dump(
|
|
ON_TextLog& text_log
|
|
) const
|
|
{
|
|
const char* description = nullptr;
|
|
if ( ON_MeshCache::RenderMeshId == m_mesh_id )
|
|
description = "Render Mesh";
|
|
else if ( ON_MeshCache::AnalysisMeshId == m_mesh_id )
|
|
description = "Analysis Mesh";
|
|
else if ( ON_MeshCache::PreviewMeshId == m_mesh_id )
|
|
description = "Preview Mesh";
|
|
|
|
if (nullptr != description)
|
|
{
|
|
text_log.Print("%s\n", description);
|
|
}
|
|
else
|
|
{
|
|
char buffer[48] = { 0 };
|
|
ON_UuidToString(m_mesh_id, buffer);
|
|
text_log.Print("mesh id: %s\n",buffer);
|
|
}
|
|
|
|
text_log.PushIndent();
|
|
const ON_Mesh* mesh = m_mesh_sp.get();
|
|
if (nullptr == mesh)
|
|
text_log.Print("Null mesh\n");
|
|
else if (mesh->IsEmpty())
|
|
text_log.Print("Empty mesh\n");
|
|
else
|
|
{
|
|
const char* mp_style = "Custom";
|
|
const ON_MeshParameters* mp = mesh->MeshParameters();
|
|
if (mp)
|
|
{
|
|
if (0 == ON_MeshParameters::CompareGeometrySettings(*mp, ON_MeshParameters::FastRenderMesh))
|
|
mp_style = "Fast";
|
|
else if (0 == ON_MeshParameters::CompareGeometrySettings(*mp, ON_MeshParameters::QualityRenderMesh))
|
|
mp_style = "Quality";
|
|
}
|
|
text_log.Print("%s mesh with %d faces and %d vertices.\n", mp_style, mesh->FaceCount(), mesh->VertexCount());
|
|
}
|
|
text_log.PopIndent();
|
|
}
|
|
|
|
ON_UUID ON_MeshCache::MeshIdFromMeshType(
|
|
ON::mesh_type mesh_type
|
|
)
|
|
{
|
|
switch (mesh_type)
|
|
{
|
|
case ON::default_mesh:
|
|
return ON_MeshCache::CoarseMeshId;
|
|
break;
|
|
case ON::render_mesh:
|
|
return ON_MeshCache::RenderMeshId;
|
|
break;
|
|
case ON::analysis_mesh:
|
|
return ON_MeshCache::AnalysisMeshId;
|
|
break;
|
|
case ON::preview_mesh:
|
|
return ON_MeshCache::PreviewMeshId;
|
|
break;
|
|
case ON::any_mesh:
|
|
return ON_MeshCache::AnyMeshId;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ON_nil_uuid;
|
|
}
|
|
|
|
|
|
class ON_MeshCacheItem* ON_MeshCache::Internal_CreateItem()
|
|
{
|
|
// Items must come from the main memory pool to prevent
|
|
// crashing when meshing operations are canceled.
|
|
// Use placement operator new with main heap memory.
|
|
ON_MeshCacheItem* item;
|
|
void* p = onmalloc(sizeof(*item));
|
|
item = new (p) ON_MeshCacheItem();
|
|
return item;
|
|
}
|
|
|
|
class ON_MeshCacheItem* ON_MeshCache::Internal_CopyItem(const class ON_MeshCacheItem& src_item)
|
|
{
|
|
// Items must come from the main memory pool to prevent
|
|
// crashing when meshing operations are canceled.
|
|
// Use placement operator new with main heap memory.
|
|
ON_MeshCacheItem* item;
|
|
void* p = onmalloc(sizeof(*item));
|
|
item = new (p) ON_MeshCacheItem(src_item);
|
|
item->m_next = nullptr;
|
|
return item;
|
|
}
|
|
|
|
void ON_MeshCache::Internal_DeleteItem(class ON_MeshCacheItem* item, bool bDeleteMesh)
|
|
{
|
|
if (nullptr == item)
|
|
return;
|
|
void* p = (void*)item;
|
|
|
|
// When bDeleteMesh is false, the memory used by item->m_mesh_sp and the ON_Mesh
|
|
// it points to belongs to a memory pool that has been deleted because a worker
|
|
// thread was canceled. In this case, calling ~ON_MeshCacheItem() will crash the
|
|
// application.
|
|
if ( bDeleteMesh )
|
|
{
|
|
item->~ON_MeshCacheItem();
|
|
}
|
|
|
|
onfree(p);
|
|
}
|
|
|
|
void ON_MeshCache::Internal_CopyHelper(
|
|
const class ON_MeshCacheItem* src_impl
|
|
)
|
|
{
|
|
m_impl = 0;
|
|
ON_MeshCacheItem* prev = nullptr;
|
|
for (const ON_MeshCacheItem* src_item = src_impl; nullptr != src_item; src_item = src_item->m_next)
|
|
{
|
|
ON_MeshCacheItem* item_copy = Internal_CopyItem(*src_item);
|
|
if (nullptr == prev)
|
|
m_impl = item_copy;
|
|
else
|
|
prev->m_next = item_copy;
|
|
prev = item_copy;
|
|
}
|
|
}
|
|
|
|
class ON_MeshCacheItem* ON_MeshCache::Internal_FindHelper(
|
|
ON_UUID mesh_id
|
|
) const
|
|
{
|
|
unsigned int coarse_mesh_face_count = 0xFFFFFFFFU;
|
|
unsigned int fine_mesh_face_count = 0;
|
|
ON_MeshCacheItem* item_coarse_mesh = nullptr;
|
|
ON_MeshCacheItem* item_fine_mesh = nullptr;
|
|
|
|
const bool bCountFaces = (ON_MeshCache::FineMeshId == mesh_id || ON_MeshCache::CoarseMeshId == mesh_id);
|
|
|
|
for (ON_MeshCacheItem* item = m_impl; nullptr != item; item = item->m_next)
|
|
{
|
|
if (mesh_id == item->m_mesh_id)
|
|
return item;
|
|
if (false == bCountFaces)
|
|
continue;
|
|
const ON_Mesh* mesh = item->m_mesh_sp.get();
|
|
if (nullptr == mesh)
|
|
continue;
|
|
unsigned int mesh_face_count = mesh->FaceUnsignedCount();
|
|
if (mesh_face_count <= 0 || mesh->VertexUnsignedCount() < 3)
|
|
continue;
|
|
if (mesh_face_count > fine_mesh_face_count)
|
|
{
|
|
item_fine_mesh = item;
|
|
fine_mesh_face_count = mesh_face_count;
|
|
}
|
|
if (mesh_face_count < coarse_mesh_face_count)
|
|
{
|
|
item_coarse_mesh = item;
|
|
coarse_mesh_face_count = mesh_face_count;
|
|
}
|
|
}
|
|
|
|
if (ON_MeshCache::CoarseMeshId == mesh_id)
|
|
return item_coarse_mesh;
|
|
|
|
if (ON_MeshCache::FineMeshId == mesh_id)
|
|
return item_fine_mesh;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
ON_MeshCache::~ON_MeshCache()
|
|
{
|
|
ClearAllMeshes();
|
|
}
|
|
|
|
ON_MeshCache::ON_MeshCache(const ON_MeshCache& src)
|
|
{
|
|
Internal_CopyHelper(src.m_impl);
|
|
}
|
|
|
|
ON_MeshCache& ON_MeshCache::operator=(const ON_MeshCache& src)
|
|
{
|
|
if (this != &src)
|
|
{
|
|
ClearAllMeshes();
|
|
Internal_CopyHelper(src.m_impl);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
#if defined(ON_HAS_RVALUEREF)
|
|
// rvalue copy constructor
|
|
ON_MeshCache::ON_MeshCache( ON_MeshCache&& src) ON_NOEXCEPT
|
|
{
|
|
m_impl = src.m_impl;
|
|
src.m_impl = nullptr;
|
|
}
|
|
|
|
ON_MeshCache& ON_MeshCache::operator=( ON_MeshCache&& src )
|
|
{
|
|
if (this != &src)
|
|
{
|
|
m_impl = src.m_impl;
|
|
src.m_impl = nullptr;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
void ON_MeshCache::SetMesh(
|
|
ON::mesh_type mesh_type,
|
|
const std::shared_ptr<ON_Mesh>& mesh_sp
|
|
)
|
|
{
|
|
SetMesh(ON_MeshCache::MeshIdFromMeshType(mesh_type),mesh_sp);
|
|
}
|
|
|
|
void ON_MeshCache::SetMesh(
|
|
ON_UUID mesh_id,
|
|
const std::shared_ptr<ON_Mesh>& mesh_sp
|
|
)
|
|
{
|
|
if ( ON_nil_uuid == mesh_id )
|
|
return;
|
|
if ( ON_max_uuid == mesh_id )
|
|
return;
|
|
|
|
const ON_Mesh* mesh = mesh_sp.get();
|
|
|
|
if (nullptr == mesh || mesh->IsEmpty())
|
|
{
|
|
ClearMesh(mesh_id);
|
|
return;
|
|
}
|
|
|
|
if ( ON_MeshCache::AnyMeshId == mesh_id )
|
|
return;
|
|
|
|
ON_MeshCacheItem* item = Internal_FindHelper(mesh_id);
|
|
if (nullptr == item)
|
|
{
|
|
item = Internal_CreateItem();
|
|
item->m_mesh_id = mesh_id;
|
|
item->m_next = m_impl;
|
|
m_impl = item;
|
|
}
|
|
if ( nullptr == item )
|
|
return;
|
|
|
|
item->m_mesh_sp = mesh_sp;
|
|
}
|
|
|
|
void ON_MeshCache::ClearMesh(
|
|
ON::mesh_type mesh_type
|
|
)
|
|
{
|
|
ClearMesh(mesh_type, true);
|
|
}
|
|
|
|
void ON_MeshCache::ClearMesh(
|
|
ON::mesh_type mesh_type,
|
|
bool bDeleteMesh
|
|
)
|
|
{
|
|
ClearMesh(ON_MeshCache::MeshIdFromMeshType(mesh_type),bDeleteMesh);
|
|
}
|
|
|
|
void ON_MeshCache::ClearMesh(
|
|
ON_UUID mesh_id
|
|
)
|
|
{
|
|
ClearMesh(mesh_id, true);
|
|
}
|
|
|
|
void ON_MeshCache::ClearMesh(
|
|
ON_UUID mesh_id,
|
|
bool bDeleteMesh
|
|
)
|
|
{
|
|
if (ON_MeshCache::AnyMeshId == mesh_id)
|
|
ClearAllMeshes(bDeleteMesh);
|
|
else
|
|
{
|
|
ON_MeshCacheItem* prev = nullptr;
|
|
for (ON_MeshCacheItem* item = m_impl; nullptr != item; item = item->m_next)
|
|
{
|
|
if (mesh_id == item->m_mesh_id)
|
|
{
|
|
if ( nullptr == prev )
|
|
m_impl = item->m_next;
|
|
else
|
|
prev->m_next = item->m_next;
|
|
Internal_DeleteItem(item,bDeleteMesh);
|
|
return;
|
|
}
|
|
prev = item;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ON_MeshCache::ClearAllMeshes()
|
|
{
|
|
ClearAllMeshes(true);
|
|
}
|
|
|
|
void ON_MeshCache::ClearAllMeshes(bool bDeleteMesh)
|
|
{
|
|
if ( 0 != m_impl )
|
|
{
|
|
ON_MeshCacheItem* next = m_impl;
|
|
m_impl = nullptr;
|
|
for (ON_MeshCacheItem* item = next; nullptr != item; item = next)
|
|
{
|
|
next = item->m_next;
|
|
Internal_DeleteItem(item,bDeleteMesh);
|
|
}
|
|
}
|
|
}
|
|
|
|
const ON_Mesh* ON_MeshCache::Mesh(
|
|
ON::mesh_type mesh_type
|
|
) const
|
|
{
|
|
return Mesh(ON_MeshCache::MeshIdFromMeshType(mesh_type));
|
|
}
|
|
|
|
const ON_Mesh* ON_MeshCache::Mesh(
|
|
ON_UUID mesh_id
|
|
) const
|
|
{
|
|
return MeshSharedPtr(mesh_id).get();
|
|
}
|
|
|
|
std::shared_ptr<ON_Mesh> ON_MeshCache::MeshSharedPtr(
|
|
ON::mesh_type mesh_type
|
|
) const
|
|
{
|
|
return MeshSharedPtr(ON_MeshCache::MeshIdFromMeshType(mesh_type));
|
|
}
|
|
|
|
std::shared_ptr<ON_Mesh> ON_MeshCache::MeshSharedPtr(
|
|
ON_UUID mesh_id
|
|
) const
|
|
{
|
|
const ON_MeshCacheItem* item
|
|
= (ON_MeshCache::AnyMeshId == mesh_id)
|
|
? m_impl
|
|
: Internal_FindHelper(mesh_id);
|
|
if ( item )
|
|
return item->m_mesh_sp;
|
|
return std::shared_ptr<ON_Mesh>();
|
|
}
|
|
|
|
bool ON_MeshCache::Write(
|
|
ON_BinaryArchive& archive
|
|
) const
|
|
{
|
|
if (!archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,0))
|
|
return false;
|
|
bool rc = true;
|
|
char c;
|
|
for (const ON_MeshCacheItem* item = m_impl; nullptr != item; item = item->m_next)
|
|
{
|
|
c = 1;
|
|
rc = archive.WriteChar(c);
|
|
if ( false == rc )
|
|
break;
|
|
rc = item->Write(archive);
|
|
if ( false == rc )
|
|
break;
|
|
}
|
|
if (rc)
|
|
{
|
|
c = 0;
|
|
rc = archive.WriteChar(c);
|
|
}
|
|
if (!archive.EndWrite3dmChunk())
|
|
rc = false;
|
|
return rc;
|
|
}
|
|
|
|
bool ON_MeshCache::Read(
|
|
ON_BinaryArchive& archive
|
|
)
|
|
{
|
|
int major_version = 0;
|
|
int minor_version = 0;
|
|
if (!archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK, &major_version, &minor_version))
|
|
return false;
|
|
|
|
bool rc = false;
|
|
for (;;)
|
|
{
|
|
if (1 != major_version)
|
|
break;
|
|
ON_MeshCacheItem* prev = nullptr;
|
|
|
|
for (;;)
|
|
{
|
|
char c = 0;
|
|
if (!archive.ReadChar(&c))
|
|
break;
|
|
if (0 == c)
|
|
{
|
|
rc = true;
|
|
break;
|
|
}
|
|
if (1 != c)
|
|
break;
|
|
ON_MeshCacheItem* item = Internal_CreateItem();
|
|
if (!item->Read(archive))
|
|
{
|
|
Internal_DeleteItem(item,true);
|
|
break;
|
|
}
|
|
if (nullptr == prev)
|
|
m_impl = item;
|
|
else
|
|
prev->m_next = item;
|
|
prev = item;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!archive.EndRead3dmChunk())
|
|
rc = false;
|
|
|
|
return rc;
|
|
}
|
|
|
|
unsigned int ON_MeshCache::MeshCount() const
|
|
{
|
|
unsigned int count = 0;
|
|
for (const ON_MeshCacheItem* item = m_impl; nullptr != item; item = item->m_next)
|
|
{
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
void ON_MeshCache::Dump(
|
|
ON_TextLog& text_log
|
|
) const
|
|
{
|
|
unsigned int count = MeshCount();
|
|
text_log.Print("%u cached meshes.\n",count);
|
|
for (const ON_MeshCacheItem* item = m_impl; nullptr != item; item = item->m_next)
|
|
{
|
|
item->Dump(text_log);
|
|
}
|
|
}
|
|
|
|
bool ON_MeshCache::Transform(
|
|
const ON_Xform& xform
|
|
)
|
|
{
|
|
if (!xform.IsValid())
|
|
return false;
|
|
if (xform.IsZero())
|
|
return false;
|
|
if (xform.IsIdentity())
|
|
return true;
|
|
bool rc = true;
|
|
for (ON_MeshCacheItem* item = m_impl; nullptr != item; item = item->m_next)
|
|
{
|
|
ON_Mesh* mesh = item->m_mesh_sp.get();
|
|
if (nullptr == mesh || mesh->IsEmpty())
|
|
continue;
|
|
if (false == item->m_mesh_sp.unique())
|
|
{
|
|
// make a copy and transform the copy
|
|
std::shared_ptr<ON_Mesh>(new ON_Mesh(*mesh)).swap(item->m_mesh_sp);
|
|
mesh = item->m_mesh_sp.get();
|
|
}
|
|
if ( !mesh->Transform(xform) )
|
|
rc = false;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// ON_MeshRef
|
|
//
|
|
|
|
ON_MeshRef::ON_MeshRef() ON_NOEXCEPT
|
|
{}
|
|
|
|
ON_MeshRef::~ON_MeshRef()
|
|
{
|
|
m_mesh_sp.reset();
|
|
}
|
|
|
|
ON_MeshRef::ON_MeshRef(const ON_MeshRef& src) ON_NOEXCEPT
|
|
: m_mesh_sp(src.m_mesh_sp)
|
|
{}
|
|
|
|
ON_MeshRef& ON_MeshRef::operator=(const ON_MeshRef& src)
|
|
{
|
|
if ( this != &src )
|
|
m_mesh_sp = src.m_mesh_sp;
|
|
return *this;
|
|
}
|
|
|
|
#if defined(ON_HAS_RVALUEREF)
|
|
// rvalue copy constructor
|
|
ON_MeshRef::ON_MeshRef( ON_MeshRef&& src) ON_NOEXCEPT
|
|
: m_mesh_sp(std::move(src.m_mesh_sp))
|
|
{}
|
|
|
|
// rvalue assignment operator
|
|
ON_MeshRef& ON_MeshRef::operator=(ON_MeshRef&& src)
|
|
{
|
|
m_mesh_sp.reset();
|
|
m_mesh_sp = std::move(src.m_mesh_sp);
|
|
return *this;
|
|
}
|
|
#endif
|
|
|
|
const class ON_Mesh& ON_MeshRef::Mesh() const
|
|
{
|
|
const ON_Mesh* mesh = m_mesh_sp.get();
|
|
if ( nullptr == mesh )
|
|
mesh = &ON_Mesh::Empty;
|
|
return *mesh;
|
|
}
|
|
|
|
unsigned int ON_MeshRef::ReferenceCount() const
|
|
{
|
|
return (unsigned int)m_mesh_sp.use_count();
|
|
}
|
|
|
|
void ON_MeshRef::Clear()
|
|
{
|
|
m_mesh_sp.reset();
|
|
}
|
|
|
|
class ON_Mesh& ON_MeshRef::NewMesh()
|
|
{
|
|
ON_Mesh* mesh = new ON_Mesh();
|
|
ON_Mesh* managed_mesh = SetMeshForExperts(mesh);
|
|
return *managed_mesh;
|
|
}
|
|
|
|
class ON_Mesh& ON_MeshRef::CopyMesh(
|
|
const ON_MeshRef& src
|
|
)
|
|
{
|
|
return CopyMesh(src.Mesh());
|
|
}
|
|
|
|
class ON_Mesh& ON_MeshRef::CopyMesh(
|
|
const ON_Mesh& src
|
|
)
|
|
{
|
|
ON_Mesh* mesh_copy = new ON_Mesh(src);
|
|
ON_Mesh* managed_mesh = SetMeshForExperts(mesh_copy);
|
|
return *managed_mesh;
|
|
}
|
|
|
|
class ON_Mesh& ON_MeshRef::UniqueMesh()
|
|
{
|
|
const ON_Mesh& mesh = Mesh();
|
|
if (m_mesh_sp.use_count() > 1 )
|
|
return CopyMesh(mesh);
|
|
return const_cast< ON_Mesh& >(mesh);
|
|
}
|
|
|
|
class ON_Mesh* ON_MeshRef::SetMeshForExperts(
|
|
class ON_Mesh*& mesh
|
|
)
|
|
{
|
|
Clear();
|
|
ON_Mesh* managed_mesh = ( mesh == &ON_Mesh::Empty ) ? nullptr : mesh;
|
|
mesh = nullptr;
|
|
if (nullptr != managed_mesh )
|
|
m_mesh_sp = std::shared_ptr<class ON_Mesh>(managed_mesh);
|
|
return managed_mesh;
|
|
}
|
|
|
|
|
|
|
|
unsigned int ON_Mesh::DissolveOrDelete(
|
|
const ON_SimpleArray<ON_COMPONENT_INDEX>& ci_list
|
|
)
|
|
{
|
|
// Dissolve edges and vertices, delete faces
|
|
const unsigned int bailout_rc = ON_UNSET_UINT_INDEX;
|
|
|
|
const int ci_list_count = ci_list.UnsignedCount();
|
|
if (ci_list_count <= 0)
|
|
return bailout_rc;
|
|
|
|
|
|
const int mesh0_vertex_count = this->VertexCount();
|
|
if (mesh0_vertex_count < 3)
|
|
return bailout_rc;
|
|
const int mesh0_face_count = this->FaceCount();
|
|
if (mesh0_face_count < 1)
|
|
return bailout_rc;
|
|
|
|
const ON_MeshTopology& top0 = Topology();
|
|
const int top0_vertex_count = top0.m_topv.Count();
|
|
|
|
|
|
ON_SimpleArray<ON_COMPONENT_INDEX> faces(ci_list_count);
|
|
ON_SimpleArray<ON_COMPONENT_INDEX> edges_and_vertices(ci_list_count);
|
|
ON_SimpleArray<ON_2dex> edges(ci_list_count);
|
|
|
|
for (int i = 0; i < ci_list_count; ++i)
|
|
{
|
|
ON_COMPONENT_INDEX ci = ci_list[i];
|
|
if (ci.m_index < 0)
|
|
continue;
|
|
switch (ci.m_type)
|
|
{
|
|
case ON_COMPONENT_INDEX::TYPE::mesh_vertex:
|
|
if ( ci.m_index < mesh0_vertex_count)
|
|
edges_and_vertices.Append(ci);
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::TYPE::meshtop_vertex:
|
|
// must save input top vertex as an ordinary vertex index
|
|
// because input topology indices will be changed if faces are deleted.
|
|
if (ci.m_index < top0_vertex_count)
|
|
{
|
|
const ON_MeshTopologyVertex& v = top0.m_topv[ci.m_index];
|
|
if (v.m_v_count > 0 && nullptr != v.m_vi)
|
|
{
|
|
ON_COMPONENT_INDEX vci(ON_COMPONENT_INDEX::TYPE::mesh_vertex, v.m_vi[0]);
|
|
if ( vci.m_index >= 0 && vci.m_index < mesh0_vertex_count)
|
|
edges_and_vertices.Append(vci);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::TYPE::meshtop_edge:
|
|
// must save input edge as a pair of ordinary vertex indices
|
|
// because input topology indices will be changed if faces are deleted.
|
|
if ( ci.m_index < top0.m_tope.Count())
|
|
{
|
|
const ON_MeshTopologyEdge& e = top0.m_tope[ci.m_index];
|
|
ON_2dex evdex(-1, -1);
|
|
int* evi = &evdex.i;
|
|
for (int j = 0; j < 2; j++)
|
|
{
|
|
if (e.m_topvi[j] < 0 || e.m_topvi[j] >= top0_vertex_count)
|
|
break;
|
|
const ON_MeshTopologyVertex& v = top0.m_topv[e.m_topvi[j]];
|
|
if (v.m_v_count > 0 && nullptr != v.m_vi)
|
|
evi[j] = v.m_vi[0];
|
|
}
|
|
if (evdex.i > evdex.j)
|
|
{
|
|
const int x = evdex.i;
|
|
evdex.i = evdex.j;
|
|
evdex.j = x;
|
|
}
|
|
if (0 <= evdex.i && evdex.i < evdex.j && evdex.j < mesh0_vertex_count)
|
|
edges.Append(evdex);
|
|
}
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::TYPE::mesh_face:
|
|
faces.Append(ci);
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::TYPE::mesh_ngon:
|
|
{
|
|
// must convert ngon to a bucnh of face indices
|
|
const ON_MeshNgon* ngon = this->Ngon(ci.m_index);
|
|
if (nullptr == ngon || nullptr == ngon->m_fi)
|
|
break;
|
|
ON_COMPONENT_INDEX fci(ON_COMPONENT_INDEX::TYPE::mesh_face, -1);
|
|
for (unsigned nfi = 0; nfi < ngon->m_Fcount; ++nfi)
|
|
{
|
|
fci.m_index = ngon->m_fi[nfi];
|
|
if ( fci.m_index < mesh0_face_count)
|
|
faces.Append(fci);
|
|
}
|
|
}
|
|
break;
|
|
};
|
|
}
|
|
|
|
|
|
// delete the faces in a way that does not change ordinary vertex indices
|
|
DeleteComponents(
|
|
faces.Array(),
|
|
faces.UnsignedCount(),
|
|
true, // bIgnoreInvalidComponents,
|
|
true, // bRemoveDegenerateFaces,
|
|
false, // bRemoveUnusedVertices CRITICAL to preserve vertex index values
|
|
true, // bRemoveEmptyNgons,
|
|
nullptr // unsigned int* faceMap
|
|
);
|
|
|
|
if (FaceCount() <= 0)
|
|
{
|
|
Destroy();
|
|
return bailout_rc;
|
|
}
|
|
|
|
if (mesh0_vertex_count != VertexCount())
|
|
return bailout_rc; // vertex array was modified
|
|
|
|
if (edges.Count() > 0 || edges_and_vertices.Count() > 0)
|
|
{
|
|
// convert ordinary vertex indices into top1 indices
|
|
const ON_MeshTopology& top1 = Topology();
|
|
const int top1_vertex_count = top1.m_topv.Count();
|
|
const int top1_edge_count = top1.m_tope.Count();
|
|
if (mesh0_vertex_count == top1.m_topv_map.Count())
|
|
{
|
|
// get a clean list of top1.m_topv[] indices
|
|
int vertex_count = 0;
|
|
for (int i = 0; i < edges_and_vertices.Count(); ++i)
|
|
{
|
|
ON_COMPONENT_INDEX ci = edges_and_vertices[i];
|
|
ci.m_type = ON_COMPONENT_INDEX::TYPE::meshtop_vertex;
|
|
ci.m_index = top1.m_topv_map[ci.m_index];
|
|
if (ci.m_index >= 0 && ci.m_index < top1_vertex_count)
|
|
edges_and_vertices[vertex_count++] = ci;
|
|
}
|
|
edges_and_vertices.SetCount(vertex_count);
|
|
edges_and_vertices.QuickSortAndRemoveDuplicates(ON_COMPONENT_INDEX::Compare);
|
|
vertex_count = edges_and_vertices.Count();
|
|
|
|
// now add edges to edges_and_vertices[]
|
|
ON_COMPONENT_INDEX eci(ON_COMPONENT_INDEX::TYPE::meshtop_edge, -1);
|
|
for (int i = 0; i < edges.Count(); i++)
|
|
{
|
|
ON_2dex evi = edges[i];
|
|
const int topv[2] = { top1.m_topv_map[evi.i],top1.m_topv_map[evi.j] };
|
|
if (
|
|
topv[0] != topv[1]
|
|
&& topv[0] >= 0 && topv[0] < top1_vertex_count
|
|
&& topv[1] >= 0 && topv[1] < top1_vertex_count
|
|
)
|
|
{
|
|
const ON_MeshTopologyVertex& v0 = top1.m_topv[topv[0]];
|
|
if ( v0.m_tope_count > 0 && nullptr != v0.m_topei )
|
|
{
|
|
// find the edge connecting the two vertices
|
|
for (int k = 0; k < v0.m_tope_count; ++k)
|
|
{
|
|
eci.m_index = v0.m_topei[k];
|
|
if (eci.m_index >= 0 && eci.m_index < top1_edge_count)
|
|
{
|
|
const ON_MeshTopologyEdge& e = top1.m_tope[eci.m_index];
|
|
if (
|
|
(e.m_topvi[0] == topv[0] && e.m_topvi[1] == topv[1])
|
|
|| (e.m_topvi[0] == topv[1] && e.m_topvi[1] == topv[0]))
|
|
{
|
|
edges_and_vertices.Append(eci);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// dissolve the edges and vertices by merging faces
|
|
unsigned int rc
|
|
= (edges_and_vertices.Count() > 0)
|
|
? MergeFaceSets(edges_and_vertices)
|
|
: bailout_rc;
|
|
|
|
// Finally, we can remove unused vertices.
|
|
DeleteComponents(
|
|
nullptr,
|
|
0,
|
|
true, // bIgnoreInvalidComponents,
|
|
true, // bRemoveDegenerateFaces,
|
|
true, // bRemoveUnusedVertices,
|
|
true, // bRemoveEmptyNgons,
|
|
nullptr // unsigned int* faceMap
|
|
);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void Internal_WeldNgonCandiate(
|
|
const ON_Mesh& mesh,
|
|
const ON_MeshTopology& top,
|
|
unsigned char* fmarks,
|
|
const unsigned char xmark,
|
|
const ON_SimpleArray<unsigned>& ngon_fi
|
|
)
|
|
{
|
|
const unsigned ngon_face_count = ngon_fi.UnsignedCount();
|
|
if (ngon_face_count < 2)
|
|
return;
|
|
|
|
// Assumption fmarks[] has no xmark.
|
|
// Set xmark for all faces referenced in ngon_fi[] and
|
|
// use that mark to find interior vertices that need
|
|
// to be welded before the ngon can be created.
|
|
for (unsigned nfi = 0; nfi < ngon_face_count; ++nfi)
|
|
fmarks[ngon_fi[nfi]] |= xmark;
|
|
|
|
for (unsigned nfi = 0; nfi < ngon_face_count; ++nfi)
|
|
{
|
|
const int fi = ngon_fi[nfi];
|
|
const ON_MeshTopologyFace& f = top.m_topf[fi];
|
|
const int fv_count = f.IsTriangle() ? 3 : 4;
|
|
int* fvi = const_cast<int*>( mesh.m_F[fi].vi );
|
|
for (int fei = 0; fei < fv_count; ++fei)
|
|
{
|
|
const int evi = (0 == f.m_reve[fei]) ? 1 : 0;
|
|
const ON_MeshTopologyEdge& fe = top.m_tope[f.m_topei[fei]];
|
|
const int topvi = fe.m_topvi[evi];
|
|
const ON_MeshTopologyVertex& v = top.m_topv[topvi];
|
|
if (v.m_v_count <= 1 || nullptr == v.m_vi)
|
|
continue;
|
|
int mesh_vi[2] = { fvi[fei],fvi[fei] };
|
|
for (int pass = 0; pass < 2; ++pass)
|
|
{
|
|
for (int vei = 0; vei < v.m_v_count; ++vei)
|
|
{
|
|
const ON_MeshTopologyEdge& ve = top.m_tope[f.m_topei[fei]];
|
|
for (int efi = 0; efi < ve.m_topf_count; ++efi)
|
|
{
|
|
const int f1i = ve.m_topfi[efi];
|
|
if (fi == f1i)
|
|
continue;
|
|
if (0 == (fmarks[f1i] |= xmark))
|
|
continue; // this face is not in the ngon
|
|
const ON_MeshTopologyFace& f1 = top.m_topf[f1i];
|
|
int* f1vi = const_cast<int*>( mesh.m_F[f1i].vi );
|
|
const int f1v_count = f1.IsTriangle() ? 3 : 4;
|
|
for (int f1ei = 0; f1ei < f1v_count; ++f1ei)
|
|
{
|
|
const int k = (0 == f1.m_reve[f1ei]) ? 1 : 0;
|
|
const ON_MeshTopologyEdge& f1e = top.m_tope[f1.m_topei[f1ei]];
|
|
if (topvi == f1e.m_topvi[k])
|
|
{
|
|
if (0 == pass)
|
|
{
|
|
if (f1vi[f1ei] < mesh_vi[0])
|
|
mesh_vi[0] = f1vi[f1ei];
|
|
else if (f1vi[f1ei] > mesh_vi[1])
|
|
mesh_vi[1] = f1vi[f1ei];
|
|
}
|
|
else
|
|
{
|
|
// weld all faces in ngon to use vertex mesh_vi[0]
|
|
if (2 == f1ei && f1vi[2] == f1vi[3])
|
|
{
|
|
f1vi[2] = mesh_vi[0];
|
|
f1vi[3] = mesh_vi[0];
|
|
}
|
|
else
|
|
{
|
|
f1vi[f1ei] = mesh_vi[0];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (3 == f1v_count)
|
|
f1vi[3] = f1vi[2];
|
|
}
|
|
}
|
|
if (0 == pass)
|
|
{
|
|
if (mesh_vi[0] == mesh_vi[1])
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// weld all faces in ngon to use vertex mesh_vi[0]
|
|
if (2 == fei && fvi[2] == fvi[3])
|
|
{
|
|
fvi[2] = mesh_vi[0];
|
|
fvi[3] = mesh_vi[0];
|
|
}
|
|
else
|
|
{
|
|
fvi[fei] = mesh_vi[0];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// clear the xmarks set above
|
|
const unsigned char mask = ~xmark;
|
|
for (unsigned nfi = 0; nfi < ngon_face_count; ++nfi)
|
|
fmarks[ngon_fi[nfi]] &= mask;
|
|
}
|
|
|
|
static void Internal_GrowNgon(
|
|
const ON_MeshTopology& top,
|
|
unsigned char* emarks,
|
|
unsigned char* fmarks,
|
|
unsigned char etest_mask,
|
|
unsigned char etest_result,
|
|
unsigned char ftest_mask,
|
|
unsigned char ftest_result,
|
|
const unsigned char merged_mark,
|
|
ON_SimpleArray<unsigned>& ngon_fi
|
|
)
|
|
{
|
|
if (ngon_fi.Count() <= 0)
|
|
return;
|
|
|
|
const int face_count = top.m_topf.Count();
|
|
|
|
for (int nfi = 0; nfi < ngon_fi.Count(); ++nfi)
|
|
fmarks[ngon_fi[nfi]] |= merged_mark;
|
|
|
|
etest_mask |= merged_mark;
|
|
etest_result &= ~merged_mark;
|
|
|
|
ftest_mask |= merged_mark;
|
|
ftest_result &= ~merged_mark;
|
|
|
|
for (int nfi = 0; nfi < ngon_fi.Count(); ++nfi)
|
|
{
|
|
const int f0i = ngon_fi[nfi];
|
|
const ON_MeshTopologyFace& f0 = top.m_topf[f0i];
|
|
const int f0_ecount = f0.IsTriangle() ? 3 : 4;
|
|
for (int fei = 0; fei < f0_ecount; ++fei)
|
|
{
|
|
const int ei = f0.m_topei[fei];
|
|
if (ei < 0 || ei > top.m_tope.Count())
|
|
continue;
|
|
if (etest_result != (emarks[ei] & etest_mask))
|
|
continue;
|
|
emarks[ei] |= merged_mark;
|
|
const ON_MeshTopologyEdge& e = top.m_tope[ei];
|
|
if (2 != e.m_topf_count || nullptr == e.m_topfi)
|
|
continue;
|
|
const int efi = (f0i != e.m_topfi[0]) ? 0 : 1;
|
|
const int f1i = e.m_topfi[efi];
|
|
if (f1i < 0 || f1i >= face_count)
|
|
continue;
|
|
if (ftest_result != (fmarks[f1i] & ftest_mask))
|
|
continue;
|
|
fmarks[f1i] |= merged_mark;
|
|
ngon_fi.Append(f1i);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void Internal_AddMarkToFaceAndEdgesAndVertices(
|
|
const ON_Mesh& mesh,
|
|
const ON_MeshTopology& top,
|
|
const unsigned int* ngon0_map,
|
|
int face_index,
|
|
const unsigned char mark,
|
|
unsigned char* fmarks,
|
|
unsigned char* emarks,
|
|
unsigned char* vmarks
|
|
)
|
|
{
|
|
if (0 == mark || face_index < 0 || face_index > top.m_topf.Count())
|
|
return;
|
|
if (nullptr == fmarks && nullptr == emarks && nullptr == vmarks)
|
|
return;
|
|
|
|
unsigned fi_count = 1;
|
|
const int* fi_list = &face_index;
|
|
if (nullptr != ngon0_map)
|
|
{
|
|
const unsigned ni = ngon0_map[face_index];
|
|
if (ni < mesh.NgonUnsignedCount())
|
|
{
|
|
const ON_MeshNgon* ngon = mesh.Ngon(ni);
|
|
if (ngon->m_Fcount > 1 && nullptr != ngon->m_fi)
|
|
{
|
|
for (unsigned nfi = 0; nfi < ngon->m_Fcount; ++nfi)
|
|
{
|
|
if (face_index == (int)(ngon->m_fi[nfi]))
|
|
{
|
|
fi_count = ngon->m_Fcount;
|
|
fi_list = (const int*)ngon->m_fi;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (unsigned nfi = 0; nfi < fi_count; ++nfi)
|
|
{
|
|
int fi = fi_list[nfi];
|
|
if (fi < 0 || fi >= top.m_topf.Count())
|
|
continue;
|
|
if (nullptr != fmarks)
|
|
fmarks[fi] |= mark;
|
|
if (nullptr != emarks || nullptr != vmarks)
|
|
{
|
|
const ON_MeshTopologyFace& f = top.m_topf[fi];
|
|
//const int fecount = f.IsTriangle() ? 3 : 4;
|
|
const int tope_count = top.m_tope.Count();
|
|
const int topv_count = top.m_topv.Count();
|
|
for (int fei = 0; fei < 4; fei++)
|
|
{
|
|
const int ei = f.m_topei[fei];
|
|
if (ei < 0 || ei >= tope_count)
|
|
continue;
|
|
if (nullptr != emarks)
|
|
emarks[ei] |= mark;
|
|
if (nullptr != vmarks)
|
|
{
|
|
const ON_MeshTopologyEdge& e = top.m_tope[ei];
|
|
for (int evi = 0; evi < 2; ++evi)
|
|
{
|
|
const int topvi = e.m_topvi[evi];
|
|
if (topvi < 0 || topvi >= topv_count)
|
|
continue;
|
|
vmarks[topvi] |= mark;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void Internal_AddMarkToNgonInteriorEdges(
|
|
const ON_Mesh& mesh,
|
|
const ON_MeshTopology& top,
|
|
const unsigned int* ngon0_map,
|
|
int face_index,
|
|
const unsigned char interior_edge_mark,
|
|
unsigned char* emarks
|
|
)
|
|
{
|
|
if (nullptr == ngon0_map)
|
|
return;
|
|
const int face_count = top.m_topf.Count();
|
|
const int edge_count = top.m_tope.Count();
|
|
if (face_index < 0 || face_index >= face_count)
|
|
return;
|
|
const unsigned ni = ngon0_map[face_index];
|
|
if (ni >= mesh.NgonUnsignedCount())
|
|
return;
|
|
const ON_MeshNgon* ngon = mesh.Ngon(ni);
|
|
if (nullptr == ngon || ngon->m_Fcount < 2 || nullptr == ngon->m_fi)
|
|
return;
|
|
|
|
for (unsigned nfi = 0; nfi < ngon->m_Fcount; ++nfi)
|
|
{
|
|
const int fi = (int)ngon->m_fi[nfi];
|
|
if (fi < 0 || fi >= face_count)
|
|
continue;
|
|
const ON_MeshTopologyFace& f = top.m_topf[fi];
|
|
const int f_ecount = f.IsTriangle() ? 3 : 4;
|
|
for (int fei = 0; fei < f_ecount; ++fei)
|
|
{
|
|
const int ei = f.m_topei[fei];
|
|
if (ei < 0 || ei >= edge_count)
|
|
continue;
|
|
const ON_MeshTopologyEdge& e = top.m_tope[ei];
|
|
if (2 != e.m_topf_count || nullptr == e.m_topfi)
|
|
continue;
|
|
const int f1i = e.m_topfi[(fi != e.m_topfi[0]) ? 0 : 1];
|
|
if (ni == ngon0_map[f1i])
|
|
emarks[ei] |= interior_edge_mark;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void Internal_WeldAndAddNgon(
|
|
ON_Mesh& mesh,
|
|
const ON_MeshTopology& top,
|
|
const unsigned char xmark,
|
|
unsigned char* fmarks,
|
|
ON_SimpleArray<unsigned>& ngon_fi
|
|
)
|
|
{
|
|
if (ngon_fi.Count() < 2)
|
|
return;
|
|
|
|
// ngons must be welded and MergeFaces supports making an ngon across an unwelded edge.
|
|
Internal_WeldNgonCandiate(mesh,top,fmarks,xmark,ngon_fi);
|
|
|
|
if (
|
|
2 == ngon_fi.Count()
|
|
&&ngon_fi[0] != ngon_fi[1]
|
|
&& mesh.m_F[ngon_fi[0]].IsTriangle()
|
|
&& mesh.m_F[ngon_fi[1]].IsTriangle()
|
|
)
|
|
{
|
|
// make a single quad face
|
|
ON_MeshFace f[2] = {mesh.m_F[ngon_fi[0]],mesh.m_F[ngon_fi[1]]};
|
|
for (int f0ei = 0; f0ei < 3; ++f0ei)
|
|
{
|
|
const int f0vi[2] = { f[0].vi[f0ei],f[0].vi[(f0ei + 1) % 3] };
|
|
for (int f1ei = 0; f1ei < 3; ++f1ei)
|
|
{
|
|
const int f1vi[2] = { f[1].vi[f1ei],f[1].vi[(f1ei + 1) % 3] };
|
|
if (f0vi[0] != f1vi[1] || f0vi[1] != f1vi[0])
|
|
continue;
|
|
// merge triangles into a quad
|
|
ON_MeshFace q;
|
|
q.vi[f0ei] = f[0].vi[f0ei];
|
|
q.vi[(f0ei+1)%4] = f[1].vi[(f1ei+2)%3];
|
|
q.vi[(f0ei+2)%4] = f[0].vi[(f0ei+1)%3];
|
|
q.vi[(f0ei+3)%4] = f[0].vi[(f0ei+2)%3];
|
|
if (q.IsQuad() && q.IsValid(mesh.m_V.Count()))
|
|
{
|
|
mesh.m_F[ngon_fi[0]] = q;
|
|
q.vi[0] = -1;
|
|
q.vi[1] = -1;
|
|
q.vi[2] = -1;
|
|
q.vi[3] = -1;
|
|
mesh.m_F[ngon_fi[1]] = q;
|
|
ngon_fi.SetCount(1);
|
|
mesh.AddNgon(1, ngon_fi.Array());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mesh.AddNgon(ngon_fi.UnsignedCount(), ngon_fi.Array());
|
|
}
|
|
|
|
unsigned int ON_Mesh::MergeFaceSets(
|
|
const ON_SimpleArray<ON_COMPONENT_INDEX>& ci_list
|
|
)
|
|
{
|
|
const unsigned int bailout_rc = ON_UNSET_UINT_INDEX;
|
|
|
|
const int ci_list_count = ci_list.UnsignedCount();
|
|
if (ci_list_count <= 0)
|
|
return bailout_rc;
|
|
|
|
const int vertex_count = VertexCount();
|
|
const int face_count = FaceCount();
|
|
if (face_count < 1 || vertex_count < 3)
|
|
return bailout_rc;
|
|
|
|
|
|
const ON_MeshTopology& top = Topology();
|
|
if (top.m_topf.Count() != face_count)
|
|
return bailout_rc;
|
|
|
|
const int topv_count = top.m_topv.Count();
|
|
const int tope_count = top.m_tope.Count();
|
|
|
|
if (topv_count < 3 || topv_count > vertex_count || tope_count < 3)
|
|
return bailout_rc;
|
|
|
|
ON_SimpleArray<unsigned char> vmarks_buffer(topv_count);
|
|
vmarks_buffer.SetCount(topv_count);
|
|
vmarks_buffer.Zero();
|
|
unsigned char* vmarks = vmarks_buffer.Array();
|
|
|
|
ON_SimpleArray<unsigned char> emarks_buffer(tope_count);
|
|
emarks_buffer.SetCount(tope_count);
|
|
emarks_buffer.Zero();
|
|
unsigned char* emarks = emarks_buffer.Array();
|
|
|
|
ON_SimpleArray<unsigned char> fmarks_buffer(face_count);
|
|
fmarks_buffer.SetCount(face_count);
|
|
fmarks_buffer.Zero();
|
|
unsigned char* fmarks = fmarks_buffer.Array();
|
|
|
|
|
|
const unsigned char in_ci_list_mark = 1;
|
|
const unsigned char vmark = 2;
|
|
const unsigned char emark = 4;
|
|
const unsigned char fmark = 8;
|
|
const unsigned char xmark = 0x10;
|
|
const unsigned char v_list_mark = vmark | in_ci_list_mark;
|
|
const unsigned char e_list_mark = emark | in_ci_list_mark;
|
|
const unsigned char f_list_mark = fmark | in_ci_list_mark;
|
|
const unsigned char v_x_mark = vmark | xmark;
|
|
const unsigned char e_x_mark = emark | xmark;
|
|
//const unsigned char f_x_mark = fmark | xmark;
|
|
const unsigned char merged_mark = 0x80; // face has been merged
|
|
|
|
ON_MeshNgonBuffer ngon_buffer;
|
|
|
|
unsigned ngon0_count = this->NgonCount();
|
|
const unsigned int* ngon0_map = ( ngon0_count > 0) ? this->NgonMap(true) : nullptr;
|
|
|
|
// Mark faces, edges, and vertices referenced in ci_list[]
|
|
// by setting bits in fmarks[], emarks[], and vmarks[]
|
|
for (int i = 0; i < ci_list_count; ++i)
|
|
{
|
|
ON_COMPONENT_INDEX ci = ci_list[i];
|
|
if (ci.m_index < 0)
|
|
continue;
|
|
switch (ci.m_type)
|
|
{
|
|
case ON_COMPONENT_INDEX::mesh_vertex:
|
|
// convert ci.m_index from a ON_COMPONENT_INDEX::mesh_vertex
|
|
// to a ON_COMPONENT_INDEX::meshtop_vertex index.
|
|
if (ci.m_index >= vertex_count)
|
|
break;
|
|
ci.m_index = top.m_topv_map[ci.m_index];
|
|
if (ci.m_index < 0)
|
|
break;
|
|
// no break here
|
|
case ON_COMPONENT_INDEX::meshtop_vertex:
|
|
if (ci.m_index < topv_count)
|
|
{
|
|
const ON_MeshTopologyVertex& v = top.m_topv[ci.m_index];
|
|
if (nullptr == v.m_topei || v.m_tope_count < 2)
|
|
break;
|
|
|
|
// validate the vertex
|
|
unsigned ni[2] = { ON_UNSET_UINT_INDEX,ON_UNSET_UINT_INDEX };
|
|
bool bValid = (v.m_tope_count >= 2 && nullptr != v.m_topei);
|
|
for (int vei = 0; vei < v.m_tope_count && bValid; ++vei)
|
|
{
|
|
int topei = v.m_topei[vei];
|
|
bValid = (topei >= 0 && topei < tope_count);
|
|
if (bValid)
|
|
{
|
|
const ON_MeshTopologyEdge& e = top.m_tope[topei];
|
|
bValid = (nullptr != e.m_topfi && e.m_topf_count >= 1 && e.m_topf_count <= 2);
|
|
for (int efi = 0; efi < e.m_topf_count && bValid; ++efi)
|
|
{
|
|
int fi = e.m_topfi[efi];
|
|
bValid = (fi >= 0 && fi < face_count);
|
|
if (nullptr != ngon0_map && ni[0] == ni[1])
|
|
{
|
|
const unsigned k = ngon0_map[fi];
|
|
const ON_MeshNgon* ngon = (k < ngon0_count) ? Ngon(k) : nullptr;
|
|
if (nullptr != ngon && ngon->m_Fcount >= 2 && nullptr != ngon->m_fi)
|
|
{
|
|
if (ON_UNSET_UINT_INDEX == ni[0])
|
|
ni[0] = k;
|
|
ni[1] = k;
|
|
}
|
|
else
|
|
{
|
|
// this face is not part of an ngon - stop checking
|
|
ni[0] = ON_UNSET_UINT_INDEX;
|
|
ni[1] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (false == bValid)
|
|
break;
|
|
if (ON_UNSET_UINT_INDEX != ni[0] && ni[0] == ni[1])
|
|
break; // v is inside an ngon
|
|
|
|
vmarks[ci.m_index] |= v_list_mark;
|
|
|
|
// add a vmark to every face touching this vertex
|
|
for (int vei = 0; vei < v.m_tope_count; ++vei)
|
|
{
|
|
const ON_MeshTopologyEdge& e = top.m_tope[v.m_topei[vei]];
|
|
for (int efi = 0; efi < e.m_topf_count; ++efi)
|
|
Internal_AddMarkToFaceAndEdgesAndVertices(*this, top, ngon0_map, e.m_topfi[efi], vmark, fmarks, nullptr, nullptr);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::meshtop_edge:
|
|
if (ci.m_index < tope_count)
|
|
{
|
|
const ON_MeshTopologyEdge& e = top.m_tope[ci.m_index];
|
|
if (nullptr == e.m_topfi || 2 != e.m_topf_count)
|
|
continue;
|
|
if (e.m_topvi[0] < 0 || e.m_topvi[0] >= topv_count)
|
|
continue;
|
|
if (e.m_topvi[1] < 0 || e.m_topvi[1] >= topv_count)
|
|
continue;
|
|
if (e.m_topfi[0] < 0 || e.m_topfi[0] >= face_count)
|
|
continue;
|
|
if (e.m_topfi[1] < 0 || e.m_topfi[1] >= face_count)
|
|
continue;
|
|
|
|
if (nullptr != ngon0_map)
|
|
{
|
|
const unsigned ni[2] = { ngon0_map[e.m_topfi[0]],ngon0_map[e.m_topfi[1]] };
|
|
if (ni[0] < ngon0_count && ni[0] == ni[1])
|
|
continue; // edge is inside an ngon
|
|
}
|
|
|
|
emarks[ci.m_index] |= e_list_mark;
|
|
|
|
// add an emark to face tounching this edge
|
|
vmarks[e.m_topvi[0]] |= emark;
|
|
vmarks[e.m_topvi[1]] |= emark;
|
|
for (int efi = 0; efi < e.m_topf_count; ++efi)
|
|
{
|
|
const int fi = e.m_topfi[efi];
|
|
Internal_AddMarkToFaceAndEdgesAndVertices(*this, top, ngon0_map, fi, emark, fmarks, nullptr, nullptr);
|
|
Internal_AddMarkToNgonInteriorEdges(*this, top, ngon0_map, fi, e_list_mark, emarks);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ON_COMPONENT_INDEX::mesh_face:
|
|
if (ci.m_index >= face_count)
|
|
break;
|
|
if (ngon0_count > 0)
|
|
{
|
|
const unsigned ni = ngon0_map[ci.m_index];
|
|
if (ni < ngon0_count )
|
|
{
|
|
const ON_MeshNgon* ngon = Ngon(ni);
|
|
if (nullptr != ngon && ngon->m_Fcount > 1 && nullptr != ngon->m_fi)
|
|
{
|
|
ci.m_type = ON_COMPONENT_INDEX::mesh_ngon;
|
|
ci.m_index = (int)ni;
|
|
}
|
|
}
|
|
}
|
|
// no break here;
|
|
case ON_COMPONENT_INDEX::mesh_ngon:
|
|
if (ON_COMPONENT_INDEX::mesh_face == ci.m_type || ci.m_index < NgonCount())
|
|
{
|
|
const ON_MeshNgon* ngon =
|
|
(ON_COMPONENT_INDEX::mesh_face == ci.m_type)
|
|
? ngon_buffer.CreateFromMeshFaceIndex(this,ci.m_index)
|
|
: Ngon(ci.m_index);
|
|
if (nullptr == ngon || nullptr == ngon->m_fi || ngon->m_Fcount < 1)
|
|
break;
|
|
for (unsigned nfi = 0; nfi < ngon->m_Fcount; ++nfi)
|
|
{
|
|
int fi = ngon->m_fi[nfi];
|
|
if (fi >= 0 && fi < face_count)
|
|
{
|
|
const ON_MeshTopologyFace& f = top.m_topf[fi];
|
|
bool bValidFace = true;
|
|
for (int fei = 0; fei < 4 && bValidFace; fei++)
|
|
{
|
|
const int ei = f.m_topei[fei];
|
|
bValidFace = (ei >= 0 && ei < tope_count);
|
|
if (bValidFace)
|
|
{
|
|
const ON_MeshTopologyEdge& e = top.m_tope[ei];
|
|
for (int evi = 0; evi < 2 && bValidFace; ++evi)
|
|
{
|
|
int topvi = e.m_topvi[evi];
|
|
bValidFace = (topvi >= 0 && topvi < topv_count);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bValidFace)
|
|
{
|
|
fmarks[fi] |= f_list_mark;
|
|
Internal_AddMarkToFaceAndEdgesAndVertices(*this, top, ngon0_map, fi, fmark, fmarks, emarks, vmarks);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Resolve multi-marked faces and remove all marked faces from ngons.
|
|
unsigned int fmark_face_count = 0;
|
|
unsigned int vmark_face_count = 0;
|
|
unsigned int emark_face_count = 0;
|
|
|
|
for (int fi = 0; fi < face_count; ++fi)
|
|
{
|
|
if (f_list_mark == ( fmarks[fi] & f_list_mark ))
|
|
{
|
|
fmarks[fi] = (f_list_mark | xmark);
|
|
++fmark_face_count;
|
|
}
|
|
else if (emark == (fmarks[fi] & e_list_mark))
|
|
{
|
|
fmarks[fi] = emark;
|
|
++emark_face_count;
|
|
}
|
|
else if (vmark == (fmarks[fi] & v_list_mark))
|
|
{
|
|
fmarks[fi] = vmark;
|
|
++vmark_face_count;
|
|
}
|
|
else
|
|
{
|
|
fmarks[fi] = 0;
|
|
}
|
|
}
|
|
|
|
unsigned int emark_count = 0;
|
|
if (emark_face_count >= 2)
|
|
{
|
|
// ignore edges that are part of a boundary of a face in ci_list[]
|
|
emark_face_count = 0;
|
|
for (int ei = 0; ei < tope_count; ++ei)
|
|
{
|
|
unsigned char m = emarks[ei];
|
|
emarks[ei] = 0;
|
|
if (e_list_mark != m)
|
|
continue;
|
|
const ON_MeshTopologyEdge& e = top.m_tope[ei];
|
|
for (int efi = 0; efi < e.m_topf_count && 0 != m; ++efi)
|
|
{
|
|
const int fi = e.m_topfi[efi];
|
|
if (emark != fmarks[fi] && e_x_mark != fmarks[fi])
|
|
m = 0;
|
|
}
|
|
if (0 != m)
|
|
{
|
|
emarks[ei] = e_list_mark;
|
|
++emark_count;
|
|
for (int efi = 0; efi < e.m_topf_count && 0 != m; ++efi)
|
|
{
|
|
const int fi = e.m_topfi[efi];
|
|
Internal_AddMarkToFaceAndEdgesAndVertices(*this, top, ngon0_map, fi, emark, fmarks, emarks, vmarks);
|
|
if (emark == fmarks[fi])
|
|
{
|
|
fmarks[fi] |= xmark;
|
|
++emark_face_count;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (0 == emark_count)
|
|
emark_face_count = 0;
|
|
|
|
unsigned int vmark_count = 0;
|
|
if (vmark_face_count >= 2)
|
|
{
|
|
// ignore vertices that belong to faces or edges in ci_list[]
|
|
vmark_face_count = 0;
|
|
for (int topvi = 0; topvi < topv_count; ++topvi)
|
|
{
|
|
unsigned char m = vmarks[topvi];
|
|
vmarks[topvi] = 0;
|
|
if (v_list_mark != m)
|
|
continue;
|
|
const ON_MeshTopologyVertex& v = top.m_topv[topvi];
|
|
for (int vei = 0; vei < v.m_tope_count && 0 != m; ++vei)
|
|
{
|
|
const int ei = v.m_topei[vei];
|
|
if (0 != (emarks[ei] & (fmark|emark)) )
|
|
m = 0;
|
|
else
|
|
{
|
|
const ON_MeshTopologyEdge& e = top.m_tope[ei];
|
|
for (int efi = 0; efi < e.m_topf_count && 0 != m; ++efi)
|
|
{
|
|
const int fi = e.m_topfi[efi];
|
|
if (vmark != fmarks[fi] && v_x_mark != fmarks[fi])
|
|
m = 0;
|
|
}
|
|
}
|
|
}
|
|
if (0 != m)
|
|
{
|
|
vmarks[topvi] = v_list_mark;
|
|
++vmark_count;
|
|
for (int vei = 0; vei < v.m_tope_count && 0 != m; ++vei)
|
|
{
|
|
const int ei = v.m_topei[vei];
|
|
emarks[ei] |= vmark;
|
|
const ON_MeshTopologyEdge& e = top.m_tope[ei];
|
|
for (int efi = 0; efi < e.m_topf_count && 0 != m; ++efi)
|
|
{
|
|
const int fi = e.m_topfi[efi];
|
|
Internal_AddMarkToFaceAndEdgesAndVertices(*this, top, ngon0_map, fi, vmark, fmarks, nullptr, vmarks);
|
|
Internal_AddMarkToNgonInteriorEdges(*this, top, ngon0_map, fi, vmark, emarks);
|
|
if (vmark == fmarks[fi])
|
|
{
|
|
fmarks[fi] |= xmark;
|
|
++vmark_face_count;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (0 == vmark_count)
|
|
vmark_face_count = 0;
|
|
|
|
unsigned max_ngon_face_count = fmark_face_count;
|
|
if (max_ngon_face_count < emark_face_count)
|
|
max_ngon_face_count = emark_face_count;
|
|
if (max_ngon_face_count < vmark_face_count)
|
|
max_ngon_face_count = vmark_face_count;
|
|
|
|
if (max_ngon_face_count < 2 )
|
|
return bailout_rc;
|
|
|
|
ON_SimpleArray<unsigned> ngon_fi(max_ngon_face_count);
|
|
|
|
if (ngon0_count > 0)
|
|
{
|
|
// remove ngons that include marked faces
|
|
const unsigned int* ngon_map = ( ngon0_count > 0) ? this->NgonMap(true) : nullptr;
|
|
for (int fi = 0; fi < face_count; ++fi)
|
|
{
|
|
if (0 != (fmarks[fi] & xmark))
|
|
{
|
|
unsigned int ngon_index = (nullptr != ngon_map) ? ngon_map[fi] : ON_UNSET_UINT_INDEX;
|
|
if (ngon_index < ngon0_count)
|
|
ngon_fi.Append(ngon_index);
|
|
}
|
|
}
|
|
ngon_fi.QuickSortAndRemoveDuplicates(ON_CompareDecreasing< unsigned>);
|
|
for (int i = 0; i < ngon_fi.Count(); ++i)
|
|
this->RemoveNgon((unsigned)ngon_fi[i]);
|
|
this->RemoveEmptyNgons();
|
|
|
|
// Any ngons with indices >= ngon0_count are ngons added by the code below.
|
|
ngon0_count = this->NgonCount();
|
|
|
|
|
|
ngon_fi.SetCount(0);
|
|
}
|
|
|
|
// zero x mark in fmarks[] because it is used in Internal_WeldAndAddNgon()
|
|
for (int fi = 0; fi < face_count; ++fi)
|
|
fmarks[fi] &= ~xmark;
|
|
|
|
for (int fi = 0; fi < face_count; ++fi)
|
|
{
|
|
if (f_list_mark != (fmarks[fi] & (f_list_mark | merged_mark)))
|
|
continue; // face not in ci_list[] or already merged
|
|
|
|
// seed ngon with this face
|
|
ngon_fi.SetCount(0);
|
|
fmarks[fi] |= merged_mark;
|
|
ngon_fi.Append((unsigned)fi);
|
|
|
|
// grow ngon by adding other faces in ci_list[] that share and edge with a face in the ngon.
|
|
Internal_GrowNgon(
|
|
top, emarks, fmarks,
|
|
0, // etest_mask,
|
|
0, // etest_result,
|
|
f_list_mark, // ftest_mask,
|
|
f_list_mark, // ftest_result,
|
|
merged_mark,
|
|
ngon_fi
|
|
);
|
|
|
|
// weld ngon vertices and then add ngon
|
|
Internal_WeldAndAddNgon(*this, top, xmark, fmarks, ngon_fi);
|
|
}
|
|
|
|
for (int ei = 0; ei < tope_count; ++ei)
|
|
{
|
|
if ( e_list_mark != (emarks[ei] & (e_list_mark | merged_mark)) )
|
|
continue; // edge not in ci_list[] or already merged
|
|
emarks[ei] |= merged_mark;
|
|
const ON_MeshTopologyEdge& e = top.m_tope[ei];
|
|
|
|
// seed ngon with faces attached to this edge
|
|
ngon_fi.SetCount(0);
|
|
for (int efi = 0; efi < e.m_topf_count; ++efi)
|
|
{
|
|
const int fi = e.m_topfi[efi];
|
|
if (fi < 0 || fi >= face_count)
|
|
break;
|
|
if (emark != (fmarks[fi] & (emark|fmark|in_ci_list_mark|merged_mark)))
|
|
break;
|
|
fmarks[fi] |= merged_mark;
|
|
ngon_fi.Append(fi);
|
|
}
|
|
|
|
// grow ngon by jumping accross edges in ci_list[]
|
|
Internal_GrowNgon(
|
|
top, emarks, fmarks,
|
|
e_list_mark | fmark, // etest_mask,
|
|
e_list_mark, // etest_result,
|
|
emark | fmark, // ftest_mask,
|
|
emark, // ftest_result,
|
|
merged_mark,
|
|
ngon_fi
|
|
);
|
|
|
|
// weld ngon vertices and then add ngon
|
|
Internal_WeldAndAddNgon(*this, top, xmark, fmarks, ngon_fi);
|
|
}
|
|
|
|
|
|
unsigned int mark_mask = v_list_mark | merged_mark;
|
|
for (int vi = 0; vi < topv_count; ++vi)
|
|
{
|
|
if (v_list_mark != (vmarks[vi] & mark_mask))
|
|
continue; // edge not in ci_list[] or already merged
|
|
vmarks[vi] |= merged_mark;
|
|
const ON_MeshTopologyVertex& v = top.m_topv[vi];
|
|
|
|
// seed ngon with faces attached to this vertex
|
|
ngon_fi.SetCount(0);
|
|
for (int vei = 0; vei < v.m_tope_count; ++vei)
|
|
{
|
|
const ON_MeshTopologyEdge& e = top.m_tope[v.m_topei[vei]];
|
|
for (int efi = 0; efi < e.m_topf_count; ++efi)
|
|
{
|
|
int fi = e.m_topfi[efi];
|
|
if (fi < 0 || fi >= face_count)
|
|
continue;
|
|
if (vmark != (fmarks[fi] & (vmark|emark|fmark|in_ci_list_mark|merged_mark)))
|
|
continue;
|
|
fmarks[fi] |= merged_mark;
|
|
ngon_fi.Append(fi);
|
|
}
|
|
}
|
|
|
|
// grow ngon by jumping across edges with a vmark
|
|
Internal_GrowNgon(
|
|
top, emarks, fmarks,
|
|
vmark, // etest_mask,
|
|
vmark, // etest_result,
|
|
vmark | emark | fmark, // ftest_mask,
|
|
vmark, // ftest_result,
|
|
merged_mark,
|
|
ngon_fi
|
|
);
|
|
|
|
// weld ngon vertices and then add ngon
|
|
Internal_WeldAndAddNgon(*this, top, xmark, fmarks, ngon_fi);
|
|
}
|
|
|
|
// clean up
|
|
DeleteComponents(
|
|
nullptr,
|
|
0,
|
|
true, // bIgnoreInvalidComponents
|
|
true, // bRemoveDegenerateFaces
|
|
true, // bRemoveUnusedVertices
|
|
true // bRemoveEmptyNgons
|
|
);
|
|
|
|
const unsigned ngon1_count = this->NgonCount();
|
|
|
|
// return index where new ngons begin
|
|
return (ngon1_count > ngon0_count) ? ngon0_count : bailout_rc;
|
|
}
|
|
|