From 01cdb463e645a2f0829dd87bb1fe29b5bb5bc306 Mon Sep 17 00:00:00 2001 From: Bozo The Builder Date: Thu, 12 Mar 2020 09:00:26 -0700 Subject: [PATCH] Sync changes from upstream repository MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andrew Le Bihan Co-authored-by: chuck Co-authored-by: Dale Fugier Co-authored-by: Dale Lear Co-authored-by: David Eränen Co-authored-by: Greg Arden Co-authored-by: John Croudy Co-authored-by: Lowell Walmsley Co-authored-by: Nathan Letwory Co-authored-by: piac Co-authored-by: Steve Baer Co-authored-by: Tim Hemmelman --- opennurbs_apple_nsfont.cpp | 63 +- opennurbs_array_defs.h | 15 + opennurbs_brep.cpp | 80 +- opennurbs_brep.h | 74 +- opennurbs_compstat.cpp | 20 + opennurbs_compstat.h | 28 +- opennurbs_convex_poly.cpp | 10 - opennurbs_dimension.cpp | 2 +- opennurbs_dimensionstyle.cpp | 11 + opennurbs_dimensionstyle.h | 12 +- opennurbs_extensions.cpp | 177 +- opennurbs_extensions.h | 39 +- opennurbs_font.cpp | 353 +++- opennurbs_font.h | 231 ++- opennurbs_freetype.cpp | 3 +- opennurbs_fsp.h | 5 + opennurbs_material.cpp | 130 +- opennurbs_material.h | 76 +- opennurbs_model_component.cpp | 6 + opennurbs_model_component.h | 8 +- opennurbs_nurbssurface.cpp | 203 ++- opennurbs_nurbssurface.h | 61 +- opennurbs_public_version.h | 16 +- opennurbs_rendering.h | 6 + opennurbs_subd.cpp | 2949 ++++++++++++++++++++++----------- opennurbs_subd.h | 702 ++++++-- opennurbs_subd_archive.cpp | 173 +- opennurbs_subd_copy.cpp | 40 +- opennurbs_subd_data.cpp | 61 +- opennurbs_subd_data.h | 236 ++- opennurbs_subd_eval.cpp | 16 + opennurbs_subd_frommesh.cpp | 466 ++++++ opennurbs_subd_heap.cpp | 313 ++-- opennurbs_subd_mesh.cpp | 6 +- opennurbs_subd_texture.cpp | 38 +- opennurbs_sumsurface.cpp | 54 +- opennurbs_sumsurface.h | 9 +- opennurbs_symmetry.cpp | 156 +- opennurbs_symmetry.h | 45 +- opennurbs_text.cpp | 100 +- opennurbs_text.h | 8 + opennurbs_textcontext.cpp | 63 + opennurbs_textglyph.cpp | 72 +- opennurbs_textiterator.cpp | 232 ++- opennurbs_textiterator.h | 8 + opennurbs_textrun.cpp | 113 +- opennurbs_texture_mapping.h | 3 +- opennurbs_uuid.cpp | 4 + opennurbs_uuid.h | 2 + opennurbs_win_dwrite.cpp | 133 +- 50 files changed, 5774 insertions(+), 1857 deletions(-) diff --git a/opennurbs_apple_nsfont.cpp b/opennurbs_apple_nsfont.cpp index 34954018..8c2cb855 100644 --- a/opennurbs_apple_nsfont.cpp +++ b/opennurbs_apple_nsfont.cpp @@ -464,22 +464,25 @@ const ON_wString ON_Font::AppleCTFontFaceName( return ON_wString::EmptyString; } -CTFontRef ON_Font::AppleCTFont() const +CTFontRef ON_Font::AppleCTFont(bool& bIsSubstituteFont) const { // Using PointSize() added January 2018. + bIsSubstituteFont = false; const double pointSize = ON_Font::IsValidPointSize(m_point_size) ? m_point_size : 0.0; - return ON_Font::AppleCTFont(pointSize); + return ON_Font::AppleCTFont(pointSize, bIsSubstituteFont); } CTFontRef ON_Font::AppleCTFont( - const wchar_t* name, - double pointSize + const wchar_t* postscript_name, + double pointSize, + bool& bIsSubstituteFont ) { - ON_wString local_name(name); + bIsSubstituteFont = false; + ON_wString local_name(postscript_name); local_name.TrimLeftAndRight(); if (local_name.IsEmpty()) return nullptr; @@ -498,25 +501,28 @@ CTFontRef ON_Font::AppleCTFont() const if (false == bHavePointSize) appleFont = ON_Font::AppleCTFontSetSize(appleFont, 0.0, true); - // DEBUGGING TEST if (nullptr != appleFont) { - ON_wString postscript_name = ON_Font::AppleCTFontPostScriptName(appleFont); - if ( false == ON_wString::EqualOrdinal(postscript_name,local_name,true) ) - return appleFont; + const ON_wString ctfont_postscript_name = ON_Font::AppleCTFontPostScriptName(appleFont); + if (false == ON_wString::EqualOrdinal(ctfont_postscript_name, local_name, true)) + bIsSubstituteFont = true; } return appleFont; } -CTFontRef ON_Font::AppleCTFont(double pointSize) const +CTFontRef ON_Font::AppleCTFont(double pointSize, bool& bIsSubstituteFont) const { const bool bHavePointSize = ( pointSize > 0.0 ); const CGFloat size = (CGFloat)(bHavePointSize ? pointSize : 1000.0 ); CTFontRef appleCTFont = nullptr; + ON_wString appleCTFontPostscriptName; + bIsSubstituteFont = false; + const ON_Font* installed_font = nullptr; + bool bInstalledFontIsSubstitute = false; ON_wString tested_names[7]; for(int name_dex = 0; name_dex < 7; name_dex++) { @@ -525,7 +531,10 @@ CTFontRef ON_Font::AppleCTFont(double pointSize) const { installed_font = InstalledFont(true); if (nullptr == installed_font) + { + bInstalledFontIsSubstitute = true; installed_font = &ON_Font::Default; + } } switch (name_dex) @@ -559,9 +568,7 @@ CTFontRef ON_Font::AppleCTFont(double pointSize) const { for ( int i = 0; i < name_dex; i++ ) { - if (tested_names[name_dex].IsEmpty()) - continue; - if ( ON_wString::EqualOrdinal(name,tested_names[name_dex],true) ) + if ( ON_wString::EqualOrdinal(name,tested_names[i],true) ) { name = ON_wString::EmptyString; break; @@ -572,21 +579,24 @@ CTFontRef ON_Font::AppleCTFont(double pointSize) const continue; tested_names[name_dex] = name; - CTFontRef appleCTFontWithName = ON_Font::AppleCTFont(name,size); - if (nullptr == appleCTFontWithName) + bool bCandidateIsSubstitute = false; + CTFontRef candidate = ON_Font::AppleCTFont(name, size, bCandidateIsSubstitute); + if (nullptr == candidate) continue; - const ON_wString postscript_name = ON_Font::AppleCTFontPostScriptName(appleCTFontWithName); - if ( ON_wString::EqualOrdinal(postscript_name,name,true)) + const ON_wString candidate_name = ON_Font::AppleCTFontPostScriptName(candidate); + + if (nullptr == appleCTFont || ON_wString::EqualOrdinal(name,candidate_name,true)) { if (nullptr != appleCTFont) CFRelease(appleCTFont); - appleCTFont = appleCTFontWithName; - break; + appleCTFont = candidate; + appleCTFontPostscriptName = candidate_name; + bIsSubstituteFont = bCandidateIsSubstitute || bInstalledFontIsSubstitute; + if (ON_wString::EqualOrdinal(name, appleCTFontPostscriptName, true)) + break; } - if ( nullptr == appleCTFont ) - appleCTFont = appleCTFontWithName; else - CFRelease(appleCTFontWithName); + CFRelease(candidate); } if ( nullptr != appleCTFont && false == bHavePointSize) @@ -634,7 +644,8 @@ void ON_AppleFontGetFontMetrics( if (nullptr == font) break; - CTFontRef appleFont = font->AppleCTFont(); + bool bIsSubstituteFont = false; + CTFontRef appleFont = font->AppleCTFont(bIsSubstituteFont); if (nullptr == appleFont) break; @@ -972,7 +983,8 @@ unsigned int ON_AppleFontGetGlyphMetrics( if (nullptr == font) return 0; - CTFontRef appleFont = font->AppleCTFont(); + bool bIsSubstituteFont = false; + CTFontRef appleFont = font->AppleCTFont(bIsSubstituteFont); if (nullptr == appleFont) return 0; @@ -1042,7 +1054,8 @@ bool ON_AppleFontGetGlyphOutline( } #endif - CTFontRef appleFont = font->AppleCTFont(); + bool bIsSubstituteFont = false; + CTFontRef appleFont = font->AppleCTFont(bIsSubstituteFont); if (nullptr == appleFont) return false; diff --git a/opennurbs_array_defs.h b/opennurbs_array_defs.h index 9d1ad077..754ef888 100644 --- a/opennurbs_array_defs.h +++ b/opennurbs_array_defs.h @@ -494,6 +494,11 @@ void ON_SimpleArray::Append( const T& x ) p = (T*)temp; } Reserve(newcapacity); + if (nullptr == m_a) + { + ON_ERROR("allocation failure"); + return; + } } m_a[m_count++] = *p; if (p != &x) @@ -1572,11 +1577,21 @@ void ON_ClassArray::Append( const T& x ) T temp; // ON_*Array<> templates do not require robust copy constructor. temp = x; // ON_*Array<> templates require a robust operator=. Reserve( newcapacity ); + if (nullptr == m_a) + { + ON_ERROR("allocation failure"); + return; + } m_a[m_count++] = temp; return; } } Reserve(newcapacity); + if (nullptr == m_a) + { + ON_ERROR("allocation failure"); + return; + } } m_a[m_count++] = x; } diff --git a/opennurbs_brep.cpp b/opennurbs_brep.cpp index 50c137ed..b15a8dad 100644 --- a/opennurbs_brep.cpp +++ b/opennurbs_brep.cpp @@ -937,6 +937,24 @@ void ON_BrepFace::Dump( ON_TextLog& dump ) const } +void ON_BrepFace::SetMaterialChannelIndex(int material_channel_index) const +{ + if (material_channel_index >= 0 && material_channel_index <= ON_Material::MaximumMaterialChannelIndex) + { + const_cast(this)->m_face_material_channel = material_channel_index; + } + else + { + ON_ERROR("Invalid material_channel_index value."); + const_cast(this)->m_face_material_channel = 0; + } +} + +int ON_BrepFace::MaterialChannelIndex() const +{ + return m_face_material_channel; +} + /* int ON_BrepFace::MaterialIndex() const { @@ -1152,6 +1170,8 @@ ON_Brep::~ON_Brep() { DestroyMesh(ON::any_mesh,true); // everything is in array classes that destroy themselves. + delete m_region_topology; + m_region_topology = 0; } unsigned int ON_Brep::SizeOf() const @@ -6107,8 +6127,8 @@ bool ON_Brep::GetTightBoundingBox(ON_BoundingBox& tight_bbox, bool bGrowBox, con ON_BoundingBox local_bbox; // Test vertexes first, this will quickly give us a base bbox to work with. - int i, ict = m_V.Count(); - for (i = 0; ict > i; i++) + int vct = m_V.Count(); + for (int i = 0; vct > i; i++) { if (m_V[i].GetTightBoundingBox(local_bbox, bGrowBox, xform)) bGrowBox = true; @@ -6116,8 +6136,8 @@ bool ON_Brep::GetTightBoundingBox(ON_BoundingBox& tight_bbox, bool bGrowBox, con ON_SimpleArray iso_curves; ON_SimpleArray greville_abcissae; - ict = m_F.Count(); - for (i = 0; ict > i; i++) + int fct = m_F.Count(); + for (int i = 0; fct > i; i++) { const ON_BrepFace& face = m_F[i]; @@ -6159,28 +6179,28 @@ bool ON_Brep::GetTightBoundingBox(ON_BoundingBox& tight_bbox, bool bGrowBox, con } } - ict = iso_curves.Count(); - for (i = 0; ict > i; i++) + int ict = iso_curves.Count(); + for (int j = 0; ict > j; j++) { - if (nullptr == iso_curves[i]) + if (nullptr == iso_curves[j]) continue; // See if entire iso_curve bbox is included in current local bbox, it is // not necessary to calculate the tight bounding box. // Continue to next iso_curve if it is. - if (false == local_bbox.Includes(iso_curves[i]->BoundingBox())) + if (false == local_bbox.Includes(iso_curves[j]->BoundingBox())) { - if (iso_curves[i]->GetTightBoundingBox(local_bbox, bGrowBox, xform)) + if (iso_curves[j]->GetTightBoundingBox(local_bbox, bGrowBox, xform)) bGrowBox = true; } - delete iso_curves[i]; - iso_curves[i] = nullptr; + delete iso_curves[j]; + iso_curves[j] = nullptr; } } - ict = m_E.Count(); - for (i = 0; ict > i; i++) + int ect = m_E.Count(); + for (int i = 0; ect > i; i++) { // See if entire edge bbox is included in current local bbox, it is // not necessary to calculate the tight bounding box. @@ -8616,13 +8636,17 @@ void ON_Brep::DeleteFace(ON_BrepFace& face, bool bDeleteFaceEdges ) } static void PropagateLabel(const ON_Brep& B, - ON_SimpleArray& fids, - int label - ) + const ON_SimpleArray& fids, + int label, + ON_SimpleArray& new_fids +) + //on input, each face in fids must have m_face_user.i = label { - if (fids.Count() == 0) return; - ON_SimpleArray new_fids(B.m_F.Count()); + if (fids.Count() == 0) + return; + new_fids.SetCount(0); + new_fids.Reserve(B.m_F.Count()); for (int face_i=0; face_i& fids, + int label +) + +{ + ON_SimpleArray new_fids; + for (int i=0; im_brep = this; + } } return *this; } diff --git a/opennurbs_brep.h b/opennurbs_brep.h index 0dc12c80..8cabe47a 100644 --- a/opennurbs_brep.h +++ b/opennurbs_brep.h @@ -57,8 +57,13 @@ public: // archives and may be changed by some computations. mutable ON_U m_vertex_user; +public: mutable ON_ComponentStatus m_status = ON_ComponentStatus::NoneSet; +private: + ON__UINT16 m_reserved1 = 0U; + +public: // index of the vertex in the ON_Brep.m_V[] array int m_vertex_index = -1; @@ -198,8 +203,13 @@ public: // archives and may be changed by some computations. mutable ON_U m_edge_user; +public: mutable ON_ComponentStatus m_status = ON_ComponentStatus::NoneSet; +private: + ON__UINT16 m_reserved1 = 0U; + +public: // index of edge in ON_Brep.m_E[] array int m_edge_index = -1; @@ -412,8 +422,13 @@ public: // archives and may be changed by some computations. mutable ON_U m_trim_user; +public: mutable ON_ComponentStatus m_status = ON_ComponentStatus::NoneSet; +private: + ON__UINT16 m_reserved1 = 0U; + +public: int m_trim_index = -1; // index of trim in ON_Brep.m_T[] array // types of trim - access through m_type member. Also see m_iso and ON_Surface::ISO @@ -846,8 +861,13 @@ public: // archives and may be changed by some computations. mutable ON_U m_loop_user; +public: mutable ON_ComponentStatus m_status = ON_ComponentStatus::NoneSet; +private: + ON__UINT16 m_reserved1 = 0U; + +public: int m_loop_index = -1; // index of loop in ON_Brep.m_L[] array enum TYPE { @@ -949,8 +969,13 @@ public: // archives and may be changed by some computations. mutable ON_U m_face_user; +public: mutable ON_ComponentStatus m_status = ON_ComponentStatus::NoneSet; +private: + ON__UINT16 m_reserved1 = 0U; + +public: int m_face_index = -1; // index of face in ON_Brep.m_F[] array ON_BrepFace(); @@ -1152,21 +1177,44 @@ public: bool m_bRev = false; // true if face orientation is opposite // of natural surface orientation - // m_face_material_channel provides a way to have individual - // brep faces use a rendering material that is different - // from the rendering material used by the parent brep. - // If m_face_material_channel is zero - // channel and m_face_material_channel.m_j is the back face - // materal. The default is (0,0) which indicates the face - // should use the parent brep's material. - // If "mat" is the brep's rendering material and - // 0 < m_material_channel.m_i < mat.m_material_channel.Count(), - // then this face should use the material with id - // mat.m_material_channel[face.m_material_channel.m_i-1].m_id. - // If m_material_channel.m_i or the id is invalid in any way, - // then the default should be used. + // The application specifies a base ON_Material used to render the brep this face belongs to. + // If m_material_channel_index > 0 AND face_material_id = base.MaterialChannelIdFromIndex(m_material_channel_index) + // is not nil, then face_material_id identifies an override rendering material for this face. + // Othewise base will be used to render this face. int m_face_material_channel = 0; +public: + /* + Description: + Set this face's rendering material channel index. + + Parameters: + material_channel_index - [in] + A value between 0 and ON_Material::MaximumMaterialChannelIndex, inclusive. + This value is typically 0 or the value returned from ON_Material::MaterialChannelIndexFromId(). + + Remarks: + If base_material is the ON_Material assigned to render this brep and + ON_UUID face_material_id = base_material.MaterialChannelIdFromIndex( material_channel_index ) + is not nil, then face_material_id identifies an override rendering material for this face. + Otherwise base_material is used to reneder this face. + */ + void SetMaterialChannelIndex(int material_channel_index) const; + + /* + Returns: + This face's rendering material channel index. + + Remarks: + If base_material is the ON_Material assigned to render this subd, MaterialChannelIndex() > 0, + and ON_UUID face_material_id = base_material.MaterialChannelIdFromIndex( face.MaterialChannelIndex() ) + is not nil, then face_material_id identifies an override rendering material for this face. + Otherwise base_material is used to reneder this face. + */ + int MaterialChannelIndex() const; + +public: + // Persistent id for this face. Default is ON_nil_uuid. ON_UUID m_face_uuid = ON_nil_uuid; private: diff --git a/opennurbs_compstat.cpp b/opennurbs_compstat.cpp index 65fa7bbe..28e717a5 100644 --- a/opennurbs_compstat.cpp +++ b/opennurbs_compstat.cpp @@ -392,6 +392,14 @@ bool ON_ComponentStatus::RuntimeMark() const } +bool ON_ComponentStatus::IsMarked( + ON__UINT8 mark_bits +) const +{ + return (0 == mark_bits) ? (0 != (m_status_flags & RUNTIME_MARK_BIT)) : (mark_bits == m_mark_bits); +} + + bool ON_ComponentStatus::SetRuntimeMark( bool bRuntimeMark ) @@ -421,6 +429,18 @@ bool ON_ComponentStatus::ClearRuntimeMark() return false; } +ON__UINT8 ON_ComponentStatus::MarkBits() const +{ + return m_mark_bits; +} + +ON__UINT8 ON_ComponentStatus::SetMarkBits(ON__UINT8 bits) +{ + const ON__UINT8 rc = m_mark_bits; + m_mark_bits = bits; + return rc; +} + unsigned int ON_ComponentStatus::SetHiddenState( bool bIsHidden diff --git a/opennurbs_compstat.h b/opennurbs_compstat.h index 80fd4d20..5b274f6b 100644 --- a/opennurbs_compstat.h +++ b/opennurbs_compstat.h @@ -268,6 +268,19 @@ public: */ bool ClearRuntimeMark(); + ON__UINT8 MarkBits() const; + + ON__UINT8 SetMarkBits(ON__UINT8 bits); + + /* + Returns: + (0==mark_bits) ? RuntimeMark() : (mark_bits == MarkBits() + */ + bool IsMarked( + ON__UINT8 mark_bits + ) const; + + ////////////////////////////////////////////////////////////////////////// // // Selection @@ -490,7 +503,18 @@ public: private: friend class ON_AggregateComponentStatus; + + // NOTE: + // Hidden, Selected, ..., Mark() bool values are saved + // as single bits on m_status_flags. unsigned char m_status_flags = 0U; + + // extra bits for advanced marking + // no rules for use and runtime only - never saved in 3dm archives + // NOTE: Mark() and MarkBits() are independent. + // bool Mark() is a bit on m_status_flags. + // ON__UINT8 MarkBits() returns m_mark_bits. + ON__UINT8 m_mark_bits = 0U; }; @@ -614,7 +638,9 @@ private: private: unsigned char m_current = 0; // 0 = empty, 1 = current, 2 = dirty - unsigned short m_reserved2 = 0; + +private: + unsigned char m_reserved1 = 0; private: // number of components diff --git a/opennurbs_convex_poly.cpp b/opennurbs_convex_poly.cpp index 84075397..a593d262 100644 --- a/opennurbs_convex_poly.cpp +++ b/opennurbs_convex_poly.cpp @@ -703,15 +703,6 @@ static int MatchingSupport(const ON_4dex& A, const ON_4dex& B) return (i == 4) ? nsup : -1; } -static int Gregdebugcounter = 0; - - -static void DebugCounter() -{ -#ifdef ON_DEBUG - Gregdebugcounter++; -#endif -} // Gilbert Johnson Keerthi algorithm @@ -850,7 +841,6 @@ bool ClosestPoint(const ON_ConvexPoly& A, const ON_ConvexPoly& B, if (Simp.GetClosestPointToOrigin(Bary)) { - DebugCounter(); bFirstPass = false; v = Simp.Evaluate(Bary); vlenlast = vlen; diff --git a/opennurbs_dimension.cpp b/opennurbs_dimension.cpp index 226fbe13..e3c1782c 100644 --- a/opennurbs_dimension.cpp +++ b/opennurbs_dimension.cpp @@ -3319,8 +3319,8 @@ void ON_DimAngular::Set2dDefPoint1(ON_2dPoint pt) m_plane.Rotate(xdir.y, xdir.x, m_plane.zaxis); m_vec_2.Rotate(-xdir.y, xdir.x); m_dimline_pt.Rotate(-xdir.y, xdir.x, ON_2dPoint::Origin); - m_ext_offset_1 = r; } + m_ext_offset_1 = r; } void ON_DimAngular::Set2dDefPoint2(ON_2dPoint pt) diff --git a/opennurbs_dimensionstyle.cpp b/opennurbs_dimensionstyle.cpp index 5788aa83..d593bdc2 100644 --- a/opennurbs_dimensionstyle.cpp +++ b/opennurbs_dimensionstyle.cpp @@ -3457,6 +3457,16 @@ const ON_Font& ON_DimStyle::Font() const return (nullptr != m_managed_font) ? *m_managed_font : ON_Font::Default; } +const ON_Font& ON_DimStyle::ParentDimStyleFont() const +{ + // If this dimstyle has a parent dimstyle and this dimstyle's font overrides the parent dimstyle's font, + // then the parent dimstyle's font is returned. Otherwise this dimstyle's font is returned. + return + (nullptr != m_parent_dimstyle_managed_font && false == (ON_nil_uuid == ParentId()) && IsFieldOverride(ON_DimStyle::field::Font) ) + ? *m_parent_dimstyle_managed_font + : Font(); +} + const bool ON_DimStyle::FontSubstituted() const { return @@ -5205,6 +5215,7 @@ void ON_DimStyle::OverrideFields(const ON_DimStyle& source, const ON_DimStyle& p { SetParentId(parent.Id()); } + m_parent_dimstyle_managed_font = parent.m_managed_font; // leave the Unset, Name, Index fields as is. They cannot be overridden for (unsigned int i = static_cast(ON_DimStyle::field::Index)+1; i < static_cast(ON_DimStyle::field::Count); i++) diff --git a/opennurbs_dimensionstyle.h b/opennurbs_dimensionstyle.h index 96a2c46d..f5dc767e 100644 --- a/opennurbs_dimensionstyle.h +++ b/opennurbs_dimensionstyle.h @@ -1322,6 +1322,13 @@ public: */ const class ON_Font& Font() const; + /* + Returns: + If a parent dimstyle is in play, this is the managed font used by the parent dimstyle. + Otherwise, this is the font returned by Font(). + */ + const class ON_Font& ParentDimStyleFont() const; + /* Returns: A copy of the font_characteristics information. @@ -2339,7 +2346,10 @@ private: bool m_text_underlined = false; // extra/extended line under text block in leaders and radial dimensions private: - ON__UINT_PTR m_reserved = 0; + // The parent dimstyle's managed font. + // Use the ParentDimStyleFont() member function to query this field. + // + const ON_Font* m_parent_dimstyle_managed_font = nullptr; private: void Internal_ContentChange() const; diff --git a/opennurbs_extensions.cpp b/opennurbs_extensions.cpp index b976689e..a5c06a8b 100644 --- a/opennurbs_extensions.cpp +++ b/opennurbs_extensions.cpp @@ -3881,36 +3881,127 @@ bool ONX_Model::IsRDKDocumentInformation(const ONX_Model_UserData& docud) } bool ONX_Model::GetRDKEmbeddedFiles(const ONX_Model_UserData& docud, ON_ClassArray& paths, ON_SimpleArray& embedded_files_as_buffers) +{ + ON_SimpleArray dummy; + return GetRDKEmbeddedFiles(docud, paths, embedded_files_as_buffers, dummy); +} + + +//Returns the number of embedded files +static unsigned int SkipArchiveToFiles(ON_Read3dmBufferArchive& archive, int iGooLen) +{ + if (!archive.ReadMode()) + return 0; + + int version = 0; + if (!archive.ReadInt(&version)) + return 0; + + if (4 != version) + return 0; + + //Read out the document data, and throw it away. + { + int slen = 0; + if (!archive.ReadInt(&slen)) + return 0; + if (slen <= 0) + return 0; + if (slen + 4 > iGooLen) + return 0; + ON_String s; + s.SetLength(slen); + if (!archive.ReadChar(slen, s.Array())) + return 0; + } + + unsigned int iCount = 0; + if (!archive.ReadInt(&iCount)) + return 0; + + return iCount; +} + +static bool SeekPastCompressedBuffer(ON_BinaryArchive& archive) +{ + if (!archive.ReadMode()) + return false; + + bool rc = false; + unsigned int buffer_crc0 = 0; + char method = 0; + + size_t sizeof__outbuffer; + if (!archive.ReadCompressedBufferSize(&sizeof__outbuffer)) + return false; + + if (0 == sizeof__outbuffer) + return true; + + if (!archive.ReadInt(&buffer_crc0)) // 32 bit crc of uncompressed buffer + return false; + + if (!archive.ReadChar(&method)) + return false; + + if (method != 0 && method != 1) + return false; + + switch (method) + { + case 0: // uncompressed + rc = archive.SeekForward(sizeof__outbuffer); + break; + case 1: // compressed + { + ON__UINT32 tcode = 0; + ON__INT64 big_value = 0; + rc = archive.BeginRead3dmBigChunk(&tcode, &big_value); + if (rc) + rc = archive.EndRead3dmChunk(); + } + break; + } + + return rc; +} + +bool ONX_Model::GetRDKEmbeddedFilePaths(const ONX_Model_UserData& docud, ON_ClassArray& paths) { if (!ONX_Model::IsRDKDocumentInformation(docud)) return false; ON_Read3dmBufferArchive a(docud.m_goo.m_value, docud.m_goo.m_goo, false, docud.m_usertable_3dm_version, docud.m_usertable_opennurbs_version); - int version = 0; - if (!a.ReadInt(&version)) + const unsigned int iCount = SkipArchiveToFiles(a, docud.m_goo.m_value); + + if (0 == iCount) return false; - if (4 != version) - return false; - - //Read out the document data, and throw it away. + for (unsigned int i = 0; i < iCount; i++) { - int slen = 0; - if (!a.ReadInt(&slen)) - return 0; - if (slen <= 0) - return 0; - if (slen + 4 > docud.m_goo.m_value) - return 0; - ON_String s; - s.SetLength(slen); - if (!a.ReadChar(slen, s.Array())) - return 0; + ON_wString sPath; + if (!a.ReadString(sPath)) + return false; + + paths.Append(sPath); + + SeekPastCompressedBuffer(a); } - unsigned int iCount = 0; - if (!a.ReadInt(&iCount)) + return paths.Count() > 0; +} + +bool ONX_Model::GetRDKEmbeddedFiles(const ONX_Model_UserData& docud, ON_ClassArray& paths, ON_SimpleArray& embedded_files_as_buffers, ON_SimpleArray& buffer_sizes) +{ + if (!ONX_Model::IsRDKDocumentInformation(docud)) + return false; + + ON_Read3dmBufferArchive a(docud.m_goo.m_value, docud.m_goo.m_goo, false, docud.m_usertable_3dm_version, docud.m_usertable_opennurbs_version); + + const unsigned int iCount = SkipArchiveToFiles(a, docud.m_goo.m_value); + + if (0 == iCount) return false; int unpacked = 0; @@ -3933,6 +4024,7 @@ bool ONX_Model::GetRDKEmbeddedFiles(const ONX_Model_UserData& docud, ON_ClassArr { embedded_files_as_buffers.Append(buffer); paths.Append(sPath); + buffer_sizes.Append(size); unpacked++; } else @@ -3945,6 +4037,53 @@ bool ONX_Model::GetRDKEmbeddedFiles(const ONX_Model_UserData& docud, ON_ClassArr return unpacked > 0; } +bool ONX_Model::GetRDKEmbeddedFile(const ONX_Model_UserData& docud, const wchar_t* path, ON_SimpleArray& bytes) +{ + if (!ONX_Model::IsRDKDocumentInformation(docud)) + return false; + + ON_Read3dmBufferArchive a(docud.m_goo.m_value, docud.m_goo.m_goo, false, docud.m_usertable_3dm_version, docud.m_usertable_opennurbs_version); + + const unsigned int iCount = SkipArchiveToFiles(a, docud.m_goo.m_value); + + if (0 == iCount) + return false; + + for (unsigned int i = 0; i < iCount; i++) + { + ON_wString sPath; + if (!a.ReadString(sPath)) + return false; + + if (0 == sPath.ComparePath(path)) + { + size_t size; + if (!a.ReadCompressedBufferSize(&size)) + return false; + + bytes.Destroy(); + bytes.Reserve(size); + + bool bFailedCRC = false; + bool bRet = a.ReadCompressedBuffer(size, bytes.Array(), &bFailedCRC); + + if (!bRet || bFailedCRC) + { + return false; + } + + bytes.SetCount((int)size); + return true; + } + else + { + SeekPastCompressedBuffer(a); + } + } + + return false; +} + bool ONX_Model::GetRDKDocumentInformation(const ONX_Model_UserData& docud,ON_wString& rdk_xml_document_data) { diff --git a/opennurbs_extensions.h b/opennurbs_extensions.h index 919ae166..5543974e 100644 --- a/opennurbs_extensions.h +++ b/opennurbs_extensions.h @@ -1545,13 +1545,50 @@ public: // // BEGIN Render Development Toolkit (RDK) information // + // The following functions allow the developer access to the information saved per document or per-object in the 3dm file by the + // RDK plug-in, built into Rhino. There are two parts to this information - the XML data that constitutes the information + // about materials, textures and environments in addition to some of the document settings such as sun data, skylighting + // ground plane and so on - and the embedded support files which are saved as byte-per-byte copies of the actual file data + // for the original files. Typically, these embedded files will be textured used by materials, environments or decals. + + // Call this function to determine if RDK document information has been saved in this model and can be read using the GetRDKDocumentInfomation function. + // Returns true if RDK document information is available. static bool IsRDKDocumentInformation(const ONX_Model_UserData& docud); + + // This function returns the entire XML associated with the RDK document data for this file. The XML will include details about + // materials, textures and environments as well as sun, skylighting, ground plane and so on. + // Returns true if RDK document information is available. static bool GetRDKDocumentInformation(const ONX_Model_UserData& docud,ON_wString& rdk_xml_document_data); - //This is only supported for Version 6 files onwards. + // This function returns the embedded support files written with this document. The returned arrays will be empty if no support filers were saved. + // Typically, these files will be used by materials and environments. Rhino unpacks these files into a folder with the suffix "embedded_files" next to the + // 3dm file on disk. + // This is only supported for Version 6 files onwards. + // Returns true if embedded files were found. + ON_DEPRECATED_MSG("This function is deprecated as it did not return the buffer sizes, making it useless") static bool GetRDKEmbeddedFiles(const ONX_Model_UserData& docud, ON_ClassArray& paths, ON_SimpleArray& embedded_files_as_buffers); + // This function returns the embedded support files written with this document. The returned arrays will be empty if no support filers were saved. + // Typically, these files will be used by materials and environments. Rhino unpacks these files into a folder with the suffix "embedded_files" next to the + // 3dm file on disk. + // This is only supported for Version 6 files onwards. + // Returns true if embedded files were found. + static bool GetRDKEmbeddedFiles(const ONX_Model_UserData& docud, ON_ClassArray& paths, ON_SimpleArray& embedded_files_as_buffers, ON_SimpleArray& buffer_sizes); + + // This function returns the paths of the embedded support files written with this document. The returned arrays will be empty if no support filers were saved. + // This function is similar to GetRDKEmbeddedFiles, but is faster and uses less memory to return only the paths. Use the paths (exactly the strings returned from this function) to + // extract the embedded files using GetRDKEmbeddedFile + static bool GetRDKEmbeddedFilePaths(const ONX_Model_UserData& docud, ON_ClassArray& paths); + + // This function extracts one embedded file from the support files written with this document. Use the exact path as returned from GetRDKEmbeddedFilePaths + static bool GetRDKEmbeddedFile(const ONX_Model_UserData& docud, const wchar_t* path, ON_SimpleArray& bytes); + + // Call this function to determine if RDK object information has saved in this model and can be read using the GetRDKObjectInformation function. + // Returns true if RDK object information is available. static bool IsRDKObjectInformation(const ON_UserData& objectud); + + // This function returns the entire XML associated with the RDK object. The XML includes details about decals. + // Returns true if RDK object information is available. static bool GetRDKObjectInformation(const ON_Object& object,ON_wString& rdk_xml_object_data); // // END Render Development Toolkit (RDK) information diff --git a/opennurbs_font.cpp b/opennurbs_font.cpp index d77a189f..17efefc9 100644 --- a/opennurbs_font.cpp +++ b/opennurbs_font.cpp @@ -3768,18 +3768,21 @@ const ON_Font* ON_FontList::Internal_FromNames( key.m_font_weight = prefered_weight; key.m_font_stretch = prefered_stretch; key.m_font_style = prefered_style; - - + const bool bKeyHasFamilyAndFace = key.m_loc_family_name.IsNotEmpty() && key.m_loc_face_name.IsNotEmpty(); + const bool bKeyHasPostScriptName = key.m_loc_postscript_name.IsNotEmpty(); + const bool bKeyWindowsLogfontName = key.m_loc_windows_logfont_name.IsNotEmpty(); if (false == bKeyHasFamilyAndFace) bRequireFaceMatch = false; int pass_count = 0; + int postscript_name_pass[2] = { -1, -1 }; + // First compare family AND face name. In general, there will not be multiple // fonts with the same family and face name combination. - if (key.m_loc_family_name.IsNotEmpty() && key.m_loc_face_name.IsNotEmpty()) + if (bKeyHasFamilyAndFace) { sorted_lists[pass_count] = &m_by_family_name; compare_funcs[pass_count] = ON_FontList::CompareFamilyAndFaceName; @@ -3793,7 +3796,7 @@ const ON_Font* ON_FontList::Internal_FromNames( #if defined(ON_RUNTIME_WIN) // On Windows, check LOGFONT.lfFaceName before PostScript // It is common for 4 distinct faces to have the same LOGFONT lfFaceName. - if (key.m_loc_windows_logfont_name.IsNotEmpty()) + if (bKeyWindowsLogfontName) { sorted_lists[pass_count] = &m_by_windows_logfont_name; compare_funcs[pass_count] = ON_FontList::CompareWindowsLogfontName; @@ -3809,21 +3812,23 @@ const ON_Font* ON_FontList::Internal_FromNames( // On Windows, when simulated fonts or OpenType variable face fonts are in use, // it is very common for distict faces to have the same PostScript font name. // It is less common in MacOS. - if (key.m_loc_postscript_name.IsNotEmpty()) + if (bKeyHasPostScriptName) { sorted_lists[pass_count] = &m_by_postscript_name; compare_funcs[pass_count] = ON_FontList::ComparePostScriptName; + postscript_name_pass[0] = pass_count; pass_count++; sorted_lists[pass_count] = &m_by_english_postscript_name; compare_funcs[pass_count] = ON_FontList::CompareEnglishPostScriptName; + postscript_name_pass[1] = pass_count; pass_count++; } #if !defined(ON_RUNTIME_WIN) // Windows LOGFONT.lfFaceName checked after PostScript // It is common for 4 distinct faces to have the same LOGFONT lfFaceName. - if (key.m_loc_windows_logfont_name.IsNotEmpty()) + if (bKeyWindowsLogfontName) { sorted_lists[pass_count] = &m_by_windows_logfont_name; compare_funcs[pass_count] = ON_FontList::CompareWindowsLogfontName; @@ -3859,6 +3864,8 @@ const ON_Font* ON_FontList::Internal_FromNames( const ON_Font* pkey = &key; + const ON_Font* postscript_name_match_candidate = nullptr; + for (int pass = 0; pass < pass_count; pass++) { ON_FontPtrCompareFunc compare_func = compare_funcs[pass]; @@ -3901,6 +3908,23 @@ const ON_Font* ON_FontList::Internal_FromNames( continue; } + // If we're on a Mac, the PostScript name is the most reliable identification + // and it overrides the subsequent name tests when we have a single installed font that is an exact match. + const bool bUniquePostScriptNameMatch + = (subset.i+1 == subset.j) + && (pass == postscript_name_pass[0] || pass == postscript_name_pass[1]) + ; + if (bUniquePostScriptNameMatch) + { + if (nullptr == postscript_name_match_candidate) + postscript_name_match_candidate = candidate; + else if (postscript_name_match_candidate != candidate) + { + // localized and english names produced different candidates. + postscript_name_match_candidate = nullptr; + } + } + if (bMatchUnderlineStrikethroughAndPointSize) { if (candidate->IsUnderlined() != bUnderlined) @@ -3929,10 +3953,17 @@ const ON_Font* ON_FontList::Internal_FromNames( continue; candidate_dev = ON_Font::WeightStretchStyleDeviation(prefered_weight, prefered_stretch, prefered_style, candidate); + // On Apple, we need to try all passes to make sure we check the postscript name. + // On Apple platforms we have to keep testing if (0 == candidate_dev) { +#if defined(ON_RUNTIME_APPLE) + if (postscript_name_match_candidate == candidate) + return candidate; +#else if ( bCandidateFamilyAndFaceMatch || false == bKeyHasFamilyAndFace) return candidate; +#endif } if ( @@ -3948,6 +3979,22 @@ const ON_Font* ON_FontList::Internal_FromNames( } } + if (nullptr != postscript_name_match_candidate && font != postscript_name_match_candidate) + { + if (nullptr == font) + { + font = postscript_name_match_candidate; + } +#if defined(ON_RUNTIME_APPLE) + else + { + // ON Apple platforms, the postscript name is the most reliable indentification + if (false == ON_wString::EqualOrdinal(font->PostScriptName(), postscript_name_match_candidate->PostScriptName(), true)) + font = postscript_name_match_candidate; + } +#endif + } + return font; } @@ -6240,7 +6287,8 @@ bool ON_Font::SetFromAppleFontName( bool rc = false; #if defined (ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) // If the current computer has the same Apple font, this will return the highest fidelity match. - CTFontRef appleFont = ON_Font::AppleCTFont(apple_font_name,point_size); + bool bIsSubstituteFont = false; + CTFontRef appleFont = ON_Font::AppleCTFont(apple_font_name,point_size, bIsSubstituteFont); if (nullptr != appleFont) { // In some cases, the NSfont.fontName is different from apple_font_name @@ -6638,6 +6686,18 @@ const ON_wString ON_Font::FakeWindowsLogfontNameFromFamilyAndPostScriptNames( Internal_FakeWindowsLogfontName(L"Gill Sans", L"GillSans-SemiBoldItalic", L"Gill Sans Semibold", ON_FontFaceQuartet::Member::Italic), Internal_FakeWindowsLogfontName(L"Gill Sans", L"GillSans-UltraBold", L"Gill Sans Ultrabold", ON_FontFaceQuartet::Member::Regular), + // Gill Sans Std https://mcneel.myjetbrains.com/youtrack/issue/RH-57050 + // Gill Sans Std is a customize version of Gill Sans that some customer was using. + // It is not the Gill Sans that ships with modern versions of Mac OS or Windows. + // The next line insures the fake Mac LOGFONT name is the same as the one + // Windows uses and insures a file created on Mac/Windows and then read on Windows/Mac + // will have identical Rhino RTF in the .3dm file. + // This is a cosmetic change that will reduce confusion when developers are looking + // at text that uses this rare font and will feel more comfortable if the name Windows + // automatically assigns to the LOGFONT is an exact match with the fake LOGFONT name + // used in Mac OS. + Internal_FakeWindowsLogfontName(L"Gill Sans Std", L"GillSansStd-Light", L"Gill Sans Std Light", ON_FontFaceQuartet::Member::Regular), + Internal_FakeWindowsLogfontName(L"Helvetica", L"Helvetica-Light", L"Helvetica Light", ON_FontFaceQuartet::Member::Regular), Internal_FakeWindowsLogfontName(L"Helvetica", L"Helvetica-LightOblique", L"Helvetica Light", ON_FontFaceQuartet::Member::Italic), @@ -7037,6 +7097,20 @@ static ON_OutlineFigure::Type Internal_FigureTypeFromHashedFontName( // SD Stroke Open Type Font InternalHashToName(L"SDstroke"), // Family InternalHashToName(L"SDstroke-latin"), // PostScript + + + // Single line fonts + // Included here for now until + // https://mcneel.myjetbrains.com/youtrack/issue/RH-57328 + // Gets fonts with the field 10 description set. + InternalHashToName(L"SLF-RHN Architect"), // Family + InternalHashToName(L"SLF-RHN-Architect"), // PostScript + + InternalHashToName(L"SLF-RHN Industrial"), // Family + InternalHashToName(L"SLF-RHN-Industrial"), // PostScript + + InternalHashToName(L"SLF-RHN White Linen"), // Family + InternalHashToName(L"SLF-RHN-WhiteLiinen"), // PostScript }; static InternalHashToName double_stroke_name_map[] = @@ -7149,7 +7223,8 @@ static ON_OutlineFigure::Type Internal_FigureTypeFromHashedFontName( if (nullptr != e) return ON_OutlineFigure::Type::Perimeter; - return ON_OutlineFigure::Type::Unknown; + // return Unset instead of Unknown in this internal tool + return ON_OutlineFigure::Type::Unset; } static bool Internal_IsEngravingFont( @@ -7240,8 +7315,51 @@ ON_OutlineFigure::Type ON_OutlineFigure::FigureTypeFromFontName( return Internal_FigureTypeFromHashedFontName(&key); } +ON_OutlineFigure::Type ON_OutlineFigure::FigureTypeFromField10Description( + const ON_wString field_10_description +) +{ + ON_wString description(field_10_description); + + // ignore white space and hyphens + description.Remove(ON_wString::Space); + description.Remove(ON_wString::Tab); + description.Remove(ON_wString::HyphenMinus); + + const int description_len = description.Length(); + + if (description_len > 0) + { + const ON_wString s[2] = { + ON_wString(L"singlestroke"), + ON_wString(L"doublestroke") + }; + const ON_OutlineFigure::Type t[2] = { + ON_OutlineFigure::Type::SingleStroke, + ON_OutlineFigure::Type::DoubleStroke + }; + size_t count = sizeof(t) / sizeof(t[0]); + for (size_t i = 0; i < count; ++i) + { + const int s_len = s[i].Length(); + if (description_len > s_len) + continue; + for (int j = 0; j < description_len - s_len; ++j) + { + if ( ON_wString::EqualOrdinal(s[i], s_len, field_10_description, s_len, true) ) + return t[i]; + } + } + } + + return ON_OutlineFigure::Type::Unset; +} + ON_OutlineFigure::Type ON_Font::OutlineFigureType() const { + if (m_outline_figure_type != ON_OutlineFigure::Type::Unset) + return m_outline_figure_type; + const ON_wString names[] = { FamilyName(), @@ -7287,9 +7405,10 @@ ON_OutlineFigure::Type ON_Font::OutlineFigureType() const ) return figure_type; } - return ON_OutlineFigure::Type::Unknown; + return ON_OutlineFigure::Type::Unknown; // unknown means don't try again } + bool ON_Font::IsSingleStrokeFont() const { return ON_OutlineFigure::Type::SingleStroke == OutlineFigureType(); @@ -7630,7 +7749,9 @@ const ON_SHA1_Hash ON_Font::FontNameHash( if (bStopAtHyphen) { // The hyphens in these special cases are integral parts of the - // family part of the PostScript name and cannot terminate hashing. + // family part of the PostScript name and cannot terminate hashing. + if (font_name == (font_name0 + 6) && ON_wString::EqualOrdinal(L"Arial-Black", 11, font_name0, 11, true)) + continue; if (font_name == (font_name0 + 3) && ON_wString::EqualOrdinal(L"MS-", 3, font_name0, 3, true)) continue; if (font_name == (font_name0 + 4) && ON_wString::EqualOrdinal(L"OCR-A", 5, font_name0, 5, true)) @@ -7642,7 +7763,17 @@ const ON_SHA1_Hash ON_Font::FontNameHash( if (font_name == (font_name0 + 13) && ON_wString::EqualOrdinal(L"MecSoft_Font-1", 14, font_name0, 14, true)) continue; - // Terminate hashing at this hypen + // Single Line Font face names include the following. The hypen after SLF and the hyphen after RHN- + // are part of the face name. + // SLF-RHN-Architect + // SLF-RHN-Industrial + // SLF-RHN-WhiteLiinen (yup, Linen with ii) + if (font_name == (font_name0 + 4) && ON_wString::EqualOrdinal(L"SLF-", 4, font_name0, 4, true)) + continue; + if (font_name == (font_name0 + 8) && ON_wString::EqualOrdinal(L"SLF-RHN-", 8, font_name0, 8, true)) + continue; + + // Terminate hashing at this hypen because it separates the family name from the face name in a postscript name. break; } continue; @@ -7662,6 +7793,66 @@ const ON_SHA1_Hash ON_Font::FontNameHash( return sha1.Hash(); } +int ON_Font::CompareFontName(const ON_wString& lhs, const ON_wString& rhs) +{ + return ON_Font::CompareFontNameWideChar(lhs,rhs); +} + +int ON_Font::CompareFontNameWideChar(const wchar_t* lhs, const wchar_t* rhs) +{ + if (lhs == rhs) + return 0; + if (nullptr == lhs) + return 1; + if (nullptr == rhs) + return -1; + if (ON_Font::FontNameHash(lhs, false) == ON_Font::FontNameHash(rhs, false)) + return 0; + return ON_wString::CompareOrdinal(lhs, rhs, true); +} + +int ON_Font::CompareFontNamePointer(const ON_wString* lhs, const ON_wString* rhs) +{ + if (lhs == rhs) + return 0; + if (nullptr == lhs) + return 1; + if (nullptr == rhs) + return -1; + return ON_Font::CompareFontName(*lhs, *rhs); +} + + +int ON_Font::CompareFontNameToHyphen(const ON_wString& lhs, const ON_wString& rhs) +{ + return ON_Font::CompareFontNameToHyphenWideChar(lhs, rhs); +} + +int ON_Font::CompareFontNameToHyphenWideChar(const wchar_t* lhs, const wchar_t* rhs) +{ + if (lhs == rhs) + return 0; + if (nullptr == lhs) + return 1; + if (nullptr == rhs) + return -1; + if (ON_Font::FontNameHash(lhs, true) == ON_Font::FontNameHash(rhs, true)) + return 0; + return ON_wString::CompareOrdinal(lhs, rhs, true); +} + +int ON_Font::CompareFontNameToHyphenPointer(const ON_wString* lhs, const ON_wString* rhs) +{ + if (lhs == rhs) + return 0; + if (nullptr == lhs) + return 1; + if (nullptr == rhs) + return -1; + return ON_Font::CompareFontNameToHyphen(*lhs, *rhs); +} + + static bool IsAtoZ( const wchar_t* s ) @@ -7974,7 +8165,8 @@ bool ON_Font::SetFromPostScriptName( // For example, the some of the 14 or so faces Helvetica Neue // are not round tripped using the NSFont by name creation and // are round tripped by using the CTFont API. - CTFontRef apple_font = ON_Font::AppleCTFont(postscript_font_name,0.0); + bool bIsSubstituteFont = false; + CTFontRef apple_font = ON_Font::AppleCTFont(postscript_font_name,0.0, bIsSubstituteFont); if (nullptr != apple_font) { return SetFromAppleCTFont(apple_font,true); @@ -8213,6 +8405,25 @@ void ON_Font::Dump(ON_TextLog& dump) const { dump.Print(L"Origin = %ls\n", static_cast(s)); } + + s = ON_wString::EmptyString; + const ON_OutlineFigure::Type outline_figure_type = this->OutlineFigureType(); + switch (outline_figure_type) + { + case ON_OutlineFigure::Type::Unset: break; + case ON_OutlineFigure::Type::Unknown: break; + case ON_OutlineFigure::Type::SingleStroke: s = L"Engraving - single stroke"; break; + case ON_OutlineFigure::Type::DoubleStroke: s = L"Engraving - double stroke"; break; + case ON_OutlineFigure::Type::Perimeter: s = L"Perimeter"; break; + case ON_OutlineFigure::Type::NotPerimeter: s = L"Not a perimeter"; break; + case ON_OutlineFigure::Type::Mixed: s = L"Mixed"; break; + default: s = ON_wString::FormatToString(L"%u", static_cast(outline_figure_type)); break; + }; + + if (s.IsNotEmpty()) + { + dump.Print(L"Outline type = %ls\n", static_cast(s)); + } } if (ON_Font::IsValidPointSize(m_point_size)) @@ -8333,27 +8544,17 @@ void ON_Font::Dump(ON_TextLog& dump) const #if defined(ON_OS_WINDOWS_GDI) { - // LOGFONT details const LOGFONT logfont = this->WindowsLogFont(0, nullptr); - //TEXTMETRIC logfont_tm; - //bool bHaveTextMetric - // = logfont.lfHeight > 0 - // && ON_Font::GetWindowsTextMetrics(MM_TEXT, nullptr, logfont, logfont_tm); - //if ( bHaveTextMetric ) - // dump.Print(L"Windows LOGFONT and TEXTMETRIC:\n"); - //else - // dump.Print(L"Windows LOGFONT:\n"); - //ON_TextLogIndent indent_logfont(dump); ON_Font::DumpLogfont(&logfont, dump); - //if (bHaveTextMetric) - //{ - // ON_Font::DumpTextMetric(&logfont_tm, dump); - //} } #elif defined (ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) { - CTFontRef apple_font = AppleCTFont(); - dump.Print(L"Apple Core Text font:\n"); + bool bIsSubstituteFont = false; + CTFontRef apple_font = AppleCTFont(bIsSubstituteFont); + if (bIsSubstituteFont) + dump.Print(L"Apple Core Text font (substitute):\n"); + else + dump.Print(L"Apple Core Text font:\n"); dump.PushIndent(); ON_Font::DumpCTFont(apple_font, dump); dump.PopIndent(); @@ -8869,6 +9070,8 @@ ON_Font::ON_Font( m_panose1 = dwrite_font_information.m_panose1; + m_outline_figure_type = dwrite_font_information.m_outline_figure_type; + this->SetSimulated( dwrite_font_information.m_bSimulatedBold, false, @@ -8961,22 +9164,84 @@ bool ON_Font::Write( // version 1.5 explicit names in loc and en and PANOSE1 settings if (!file.WriteString(m_locale_name)) break; - if (!file.WriteString(m_loc_postscript_name)) - break; - if (!file.WriteString(m_en_postscript_name)) - break; - if (!file.WriteString(m_loc_windows_logfont_name)) - break; - if (!file.WriteString(m_en_windows_logfont_name)) - break; - if (!file.WriteString(m_loc_family_name)) - break; - if (!file.WriteString(m_en_family_name)) - break; - if (!file.WriteString(m_loc_face_name)) - break; - if (!file.WriteString(m_en_face_name)) + + bool bFontNamesSaved = false; + for(;;) + { + // NEVER commit code with this define set. + // + // This code is used by developers to create files used to test + // code and UI for handing situations when a font is not installed + // on a device. + // + // To create a missing font test .3dm file, you must modify code + // int opennurbs_font.cpp and in opennurbs_text.cpp. + // Search for CREATE_MISSING_FONT_TEST_3DM_FILE to find the locations. +//#define CREATE_MISSING_FONT_TEST_3DM_FILE +#if defined(CREATE_MISSING_FONT_TEST_3DM_FILE) + // All fonts in the Family with MISSING_FONT_TEST_valid_family_name + // will be saved as a family named missing_family_name. + const ON_wString MISSING_FONT_TEST_valid_font_family_name(L"Courier New"); + if (MISSING_FONT_TEST_valid_font_family_name == FamilyName() && FaceName().IsNotEmpty()) + { + // Write Missing Font names + const ON_wString missing_font_family_name(L"Missing Font Test"); + const ON_wString missing_font_face_name(FaceName()); + const ON_wString missing_font_logfont_name(missing_font_family_name); + ON_wString missing_font_postscript_name = missing_font_family_name + L"-" + missing_font_face_name; + missing_font_postscript_name.Remove(ON_wString::Space); + // local and english postscript name + if (!file.WriteString(missing_font_postscript_name)) + break; + if (!file.WriteString(missing_font_postscript_name)) + break; + // local and english logfont name + if (!file.WriteString(missing_font_logfont_name)) + break; + if (!file.WriteString(missing_font_logfont_name)) + break; + // local and english family name + if (!file.WriteString(missing_font_family_name)) + break; + if (!file.WriteString(missing_font_family_name)) + break; + // local and english face name + if (!file.WriteString(missing_font_face_name)) + break; + if (!file.WriteString(missing_font_face_name)) + break; + + bFontNamesSaved = true; + break; + } +#endif + + // write correct names + if (!file.WriteString(m_loc_postscript_name)) + break; + if (!file.WriteString(m_en_postscript_name)) + break; + if (!file.WriteString(m_loc_windows_logfont_name)) + break; + if (!file.WriteString(m_en_windows_logfont_name)) + break; + if (!file.WriteString(m_loc_family_name)) + break; + if (!file.WriteString(m_en_family_name)) + break; + if (!file.WriteString(m_loc_face_name)) + break; + if (!file.WriteString(m_en_face_name)) + break; + + bFontNamesSaved = true; break; + } + + if (false == bFontNamesSaved) + break; // errors writing font names + + if (!m_panose1.Write(file)) break; @@ -9183,7 +9448,7 @@ bool ON_Font::Read( else *this = ON_Font::Default; Internal_SetFontCharacteristicsFromUnsigned(fc); - if (windows_logfont_name.IsNotEmpty()) + if (postscript_name.IsNotEmpty()) { m_loc_postscript_name = postscript_name; m_en_postscript_name = postscript_name; diff --git a/opennurbs_font.h b/opennurbs_font.h index cd6e65f3..35a901a2 100644 --- a/opennurbs_font.h +++ b/opennurbs_font.h @@ -938,6 +938,26 @@ public: const wchar_t* font_name ); + /* + Description: + Opennurbs searches the description saved in field 10 of the name table + for the strings "Engraving - single stroke" / "Engraving - double stroke" / "Engraving" + to identify fonts that are desgned for engraving (and which tend to render poorly when + used to dispaly text devices like screens, monitors, and printers). + The SLF (single line fonts) are examples of fonts that have Engraving in field 10. + Parameters: + field_10_description - [in] + Field 10 string from the font name table. + Returns: + If the description contains "single stroke", returns ON_OutlineFigure::Type::SingleStroke. + If the description contains "double stroke", returns ON_OutlineFigure::Type::DoubleStroke. + Otherwise returns ON_OutlineFigure::Type::Unset; + */ + static ON_OutlineFigure::Type FigureTypeFromField10Description( + const ON_wString field_10_description + ); + + /* Returns: Figure orientation. @@ -2128,6 +2148,16 @@ public: ON_wString m_loc_gdi_subfamily_name; ON_wString m_en_gdi_subfamily_name; + // from IDWriteFont.GetInformationalStrings( DWRITE_INFORMATIONAL_STRING_DESCRIPTION, ... ) + // Opennurbs searches the description saved in field 10 of the name table + // for the strings "Engraving - single stroke" / "Engraving - double stroke" / "Engraving" + // to identify fonts that are desgned for engraving (and which tend to render poorly when + // used to dispaly text devices like screens, monitors, and printers). + // The SLF (single line fonts) are examples of fonts that have Engraving in field 10. + ON_wString m_loc_field_10_description; + ON_wString m_en_field_10_description; + + // from IDWriteGdiInterop.ConvertFontToLOGFONT LOGFONT m_gdi_interop_logfont; @@ -2146,6 +2176,8 @@ public: ON_FontGlyph m_xbox; ON_PANOSE1 m_panose1; + + ON_OutlineFigure::Type m_outline_figure_type = ON_OutlineFigure::Type::Unset; }; #endif @@ -3571,7 +3603,7 @@ public: /* Returns: - The font's Family name. + The font's family name. Remarks: Typically a font family has many faces. @@ -3593,7 +3625,7 @@ public: /* Returns: - The font's Family name. + The font's face name. Remarks: Typically a font family has many faces and the face name gives a clue about the weight, stretch, and style of the face. @@ -4123,22 +4155,48 @@ public: ); /* + Parameters + bIsSubstituteFont - [out] + true if the returned CTFontRef was a substitute automatically + created by Mac OS. Returns: An Apple CTFontRef managed by the Apple platform. Do not delete this font. */ - CTFontRef AppleCTFont() const; + CTFontRef AppleCTFont( + bool& bIsSubstituteFont + ) const; /* + Parameters + bIsSubstituteFont - [out] + true if the returned CTFontRef was a substitute automatically + created by Mac OS. Returns: An Apple CTFontRef managed by the Apple platform. Do not delete this font. */ - CTFontRef AppleCTFont(double point_size) const; + CTFontRef AppleCTFont( + double point_size, + bool& bIsSubstituteFont + ) const; + /* + Parameters + postscript_name - [in] + PostScript name of the desired font + point_size - [in] + bIsSubstituteFont - [out] + true if the returned CTFontRef was a substitute automatically + created by Mac OS (failed to match postscript name). + Returns: + An Apple CTFontRef managed by the Apple platform. + Do not delete this font. + */ static CTFontRef AppleCTFont( - const wchar_t* name, - double point_size + const wchar_t* postscript_name, + double point_size, + bool& bIsSubstituteFont ); @@ -4308,9 +4366,19 @@ public: If true, the hash calculation terminates at the first hyphen. This is useful when font_name is a PostScript name and you don't want to include face weight or style information in the hash. + + For example, if bStopAtHyphen is true, then the four PostScript names + "Calibri", "Calibri-Bold", "Calibri-Italic", and "Calibri-BoldItalic" + have the same hash. + + There are some fonts where a hyphen is an integral part of the font face name. + Examples include "Arial-Black", "AvenirLT-Roman", "MecSoftFont-1", "MS-Gothic", + "MS-PGothic", "MS-UIGothic", "SLF-RHN-Architect", "SLF-RHN-Industrial", + "SLF-RHN-WhiteLiinen", and so on. + These hyphens are exempt from the bStopAtHyphen check. Returns: - A hash of the font_name parameter that ignores spaces, hyphens, and case. - For example, the three names "Yu Gothic Regular", "YuGothic-Regular", + A hash of the font_name parameter that ignores spaces, hyphens, underbars, and case. + For example, the four names "Yu Gothic Regular", "YuGothic-Regular", "YUGOTHICREGULAR", and "yugothicregular" have the same FontNameHash(). The hash will be identical for UTF-16 and UTF-32 encodings. */ @@ -4319,6 +4387,132 @@ public: bool bStopAtHyphen ); + /* + Description: + Compare the font names ignoring hyphens, underbars, and spaces. + Paramaters: + lhs - [in] + font name to compare + rhs - [in] + font name to compare + Returns: + If ON_Font::FontNameHash(lsh,false) and ON_Font::FontNameHash(rsh,false) are equal, + then 0 is returned. + Otherwise ON_wString::CompareOrdinal(lhs,rhs,true) is returned. + Remarks: + Useful for sorting font names. + */ + static int CompareFontName( + const ON_wString& lhs, + const ON_wString& rhs + ); + + /* + Description: + Compare the font names ignoring hyphens, underbars, and spaces. + Paramaters: + lhs - [in] + font name to compare + rhs - [in] + font name to compare + Returns: + If ON_Font::FontNameHash(lsh,false) and ON_Font::FontNameHash(rsh,false) are equal, + then 0 is returned. + Otherwise ON_wString::CompareOrdinal(lhs,rhs,true) is returned. + Remarks: + Useful for sorting font names. + */ + static int CompareFontNameWideChar( + const wchar_t* lhs, + const wchar_t* rhs + ); + + /* + Description: + Compare the font names ignoring hyphens, underbars, and spaces. + Paramaters: + lhs - [in] + font name to compare + rhs - [in] + font name to compare + Returns: + If ON_Font::FontNameHash(lsh,false) and ON_Font::FontNameHash(rsh,false) are equal, + then 0 is returned. + Otherwise ON_wString::CompareOrdinal(lhs,rhs,true) is returned. + Remarks: + Useful for sorting font names. + */ + static int CompareFontNamePointer( + const ON_wString* lhs, + const ON_wString* rhs + ); + + /* + Description: + Compare the font names ignoring hyphens, underbars, and spaces. + Ignore portions of PostScript names after the hyphen that separates the + font family and font face. + Paramaters: + lhs - [in] + font name to compare + rhs - [in] + font name to compare + Returns: + If ON_Font::FontNameHash(lsh,true) and ON_Font::FontNameHash(rsh,true) are equal, + then 0 is returned. + Otherwise ON_wString::CompareOrdinal(lhs,rhs,true) is returned. + Remarks: + Useful for sorting font names. + */ + static int CompareFontNameToHyphen( + const ON_wString& lhs, + const ON_wString& rhs + ); + + /* + Description: + Compare the font names ignoring hyphens, underbars, and spaces. + Ignore portions of PostScript names after the hyphen that separates the + font family and font face. + Paramaters: + lhs - [in] + font name to compare + rhs - [in] + font name to compare + Returns: + If ON_Font::FontNameHash(lsh,true) and ON_Font::FontNameHash(rsh,true) are equal, + then 0 is returned. + Otherwise ON_wString::CompareOrdinal(lhs,rhs,true) is returned. + Remarks: + Useful for sorting font names. + */ + static int CompareFontNameToHyphenPointer( + const ON_wString* lhs, + const ON_wString* rhs + ); + + /* + Description: + Compare the font names ignoring hyphens, underbars, and spaces. + Ignore portions of PostScript names after the hyphen that separates the + font family and font face. + Paramaters: + lhs - [in] + font name to compare + rhs - [in] + font name to compare + Returns: + If ON_Font::FontNameHash(lsh,true) and ON_Font::FontNameHash(rsh,true) are equal, + then 0 is returned. + Otherwise ON_wString::CompareOrdinal(lhs,rhs,true) is returned. + Remarks: + Useful for sorting font names. + */ + static int CompareFontNameToHyphenWideChar( + const wchar_t* lhs, + const wchar_t* rhs + ); + /* Paramaters: dirty_font_name - [in] @@ -4449,6 +4643,18 @@ public: const wchar_t* preferedLocale ); + + // Returns the desription saved in field 10. + // Opennurbs searches the description saved in field 10 of the name table + // for the strings "Engraving - single stroke" / "Engraving - double stroke" / "Engraving" + // to identify fonts that are desgned for engraving (and which tend to render poorly when + // used to dispaly text devices like screens, monitors, and printers). + // The SLF (single line fonts) are examples of fonts that have Engraving in field 10. + static const ON_wString Field10DescriptionFromWindowsDWriteFont( + struct IDWriteFont* dwrite_font, + const wchar_t* preferedLocale + ); + /* Parameters: dwrite_font - [in] @@ -5420,7 +5626,14 @@ private: private: double m_apple_font_width_trait = ON_UNSET_VALUE; - double m_reserved3 = 0.0; + +private: + ON_OutlineFigure::Type m_outline_figure_type = ON_OutlineFigure::Type::Unset; + +private: + ON__UINT8 m_reserved1 = 0; + ON__UINT16 m_reserved2 = 0; + ON__UINT32 m_reserved3 = 0; double m_reserved4 = 0.0; private: diff --git a/opennurbs_freetype.cpp b/opennurbs_freetype.cpp index 89d1e1be..37f505e9 100644 --- a/opennurbs_freetype.cpp +++ b/opennurbs_freetype.cpp @@ -1448,7 +1448,8 @@ ON_FreeTypeFace* ON_FreeType::CreateFace( f = ON_FreeType::Internal_CreateFaceFromWindowsFont(&logfont); #elif defined (ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) - f = ON_FreeType::Internal_CreateFaceFromAppleFont(font.AppleCTFont()); + bool bIsSubstituteFont = false; + f = ON_FreeType::Internal_CreateFaceFromAppleFont(font.AppleCTFont(bIsSubstituteFont)); #endif // Create empty holder so this function doesn't repeatedly diff --git a/opennurbs_fsp.h b/opennurbs_fsp.h index a9200681..81993aec 100644 --- a/opennurbs_fsp.h +++ b/opennurbs_fsp.h @@ -272,6 +272,11 @@ public: size_t id_offset ) const; + /* + Returns: + If successful, (1 + maximum assigned id value) is returned. + Otherwise 0 is returned. + */ unsigned int ResetElementId( size_t id_offset, unsigned int initial_id diff --git a/opennurbs_material.cpp b/opennurbs_material.cpp index a11f93bd..6334b5dd 100644 --- a/opennurbs_material.cpp +++ b/opennurbs_material.cpp @@ -1066,6 +1066,101 @@ void ON_Material::SetReflectivity( double reflectivity ) m_reflectivity = reflectivity; } +const ON_UUID ON_Material::MaterialChannelIdFromIndex( + int material_channel_index +) const +{ + for (;;) + { + if (material_channel_index <= 0) + break; + const int count = m_material_channel.Count(); + if (count <= 0) + break; + const ON_UuidIndex* a = m_material_channel.Array(); + for ( const ON_UuidIndex* a1 = a + count; a < a1; ++a ) + { + if (material_channel_index == a->m_i) + return a->m_id; + } + break; + } + + return ON_nil_uuid; +} + +int ON_Material::MaterialChannelIndexFromId( + ON_UUID material_channel_id +) const +{ + for (;;) + { + if (ON_nil_uuid == material_channel_id) + break; + const unsigned count = m_material_channel.UnsignedCount(); + if (0 == count) + break; + const ON_UuidIndex* a = m_material_channel.Array(); + for ( const ON_UuidIndex* a1 = a + count; a < a1; ++a) + { + if (material_channel_id == a->m_id) + return a->m_i; + } + break; + } + + return 0; +} + +int ON_Material::MaterialChannelIndexFromId( + ON_UUID material_channel_id, + bool bAddIdIfNotPresent +) +{ + for (;;) + { + if (ON_nil_uuid == material_channel_id) + break; + int unused_index = 0; + + const int count = m_material_channel.Count(); + if (count > 0) + { + const ON_UuidIndex* a = m_material_channel.Array(); + for (const ON_UuidIndex* a1 = a + count; a < a1; ++a) + { + if (material_channel_id == a->m_id) + return a->m_i; + if (a->m_i > unused_index) + unused_index = a->m_i; + } + } + + if (false == bAddIdIfNotPresent) + break; + if (count >= ON_Material::MaximumMaterialChannelIndex) + break; // some rogue actor filled the m_material_channel[] array. + + ++unused_index; + if ( unused_index <= 0 || unused_index > ON_Material::MaximumMaterialChannelIndex) + { + // int overflow or too big for a material channel index + for (unused_index = 1; unused_index <= count + 1; ++unused_index) + { + if (ON_nil_uuid == MaterialChannelIdFromIndex(unused_index)) + break; + } + } + const ON_UuidIndex ui(material_channel_id, unused_index); + m_material_channel.Append(ui); + return ui.m_i; + } + + return 0; +} + + + bool operator==( const ON_Material& a, const ON_Material& b ) { return (0 == ON_Material::Compare(a,b)); @@ -1518,8 +1613,28 @@ int ON_Material::CompareAppearance( const ON_Material& a, const ON_Material& b ) if ( 0 == rc ) rc = CompareTextureAttributesAppearance(a,b); - if ( 0 == rc ) - rc = ON_UuidCompare( &a.m_plugin_id, &b.m_plugin_id ); + // This was added because the plugin uuid for a material gets changed to + // universal uuid by RhRdkCreateImportedMaterial. In the case of import_vrml + // that happens just before it's added to the material table. That breaks the + // comparison unless you know that and change your plugin id before checking + // to see if the material exists. This is a change that Dale L did for me to work + // around that. + if (0 == rc) + { + static const ON_UUID uuidUniversal = + { + // {99999999-9999-9999-9999-999999999999} + 0x99999999, 0x9999, 0x9999, { 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99 } + }; + if ( + 0 != ON_UuidCompare(&uuidUniversal, &a.m_plugin_id) + && + 0 != ON_UuidCompare(&uuidUniversal, &b.m_plugin_id) + ) + { + rc = ON_UuidCompare(&a.m_plugin_id, &b.m_plugin_id); + } + } return rc; } @@ -2430,6 +2545,7 @@ ON_TextureMapping::TYPE ON_TextureMapping::TypeFromUnsigned( ON_ENUM_FROM_UNSIGNED_CASE(ON_TextureMapping::TYPE::mesh_mapping_primitive); ON_ENUM_FROM_UNSIGNED_CASE(ON_TextureMapping::TYPE::srf_mapping_primitive); ON_ENUM_FROM_UNSIGNED_CASE(ON_TextureMapping::TYPE::brep_mapping_primitive); + ON_ENUM_FROM_UNSIGNED_CASE(ON_TextureMapping::TYPE::ocs_mapping); } ON_ERROR("Invalid type_as_unsigned value."); @@ -2449,6 +2565,7 @@ const ON_wString ON_TextureMapping::TypeToString(ON_TextureMapping::TYPE texture ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::TYPE::mesh_mapping_primitive); ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::TYPE::srf_mapping_primitive); ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::TYPE::brep_mapping_primitive); + ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::TYPE::ocs_mapping); } ON_ERROR("Invalid texture_mapping_type value."); @@ -3420,7 +3537,7 @@ int ON_TextureMapping::Evaluate( ) const { int rc; - ON_3dPoint Q = P*P_xform; + ON_3dPoint Q = P_xform*P; if ( ON_TextureMapping::PROJECTION::ray_projection == m_projection ) { // need a transformed normal @@ -3579,6 +3696,7 @@ ON__UINT32 ON_TextureMapping::MappingCRC() const case ON_TextureMapping::TYPE::cylinder_mapping: case ON_TextureMapping::TYPE::sphere_mapping: case ON_TextureMapping::TYPE::box_mapping: + case ON_TextureMapping::TYPE::ocs_mapping: default: break; } @@ -5837,6 +5955,12 @@ bool ON_ObjectRenderingAttributes::DeleteMappingRef( return (0 != mr); } +//static +int ON_ObjectRenderingAttributes::OCSMappingChannelId(void) +{ + return 100000; +} + const ON_MappingChannel* ON_ObjectRenderingAttributes::MappingChannel( const ON_UUID& plugin_id, const ON_UUID& mapping_id diff --git a/opennurbs_material.h b/opennurbs_material.h index ead2871a..622df8ef 100644 --- a/opennurbs_material.h +++ b/opennurbs_material.h @@ -511,24 +511,70 @@ public: /* Description: - Used to provide per face material support. - The parent object reference a basic material. - When a brep face or mesh facet wants to use - a material besides the base material, it specifies - a channelSupports material channel. The default - material channel is 0 and that indicates the base - material. A channel of n > 0 means that face - used the material with id m_material_channel[n-1]. - If (n-1) >= m_material_channel.Count(), then the base - material is used. The value of - m_material_channel[n].m_id is persistent. The - value of m_material_channel[n].m_i is a runtime - index in the CRhinoDoc::m_material_table[]. If - CRhinoDoc::m_material_table[m_i].m_uuid != m_id, - then m_id is assumed to be correct. + The m_material_channel[] array is used to provide per face rendering material support for ON_SubD and ON_Brep objects. + ON_Mesh objects to not support per face render materials. + The application specifies a base ON_Material for rendering the subd or brep and a way to find materials from ON_UUID values. + ON_Material.Id() retuns the id for any given material. + + ON_BrepFace::MaterialChannelIndex() and ON_SubDFace::MaterialChannelIndex() + specify a material channel index. If this value is 0, then the base + material is used to render the face. Otherwise the material with + id = base.MaterialChannelIdFromIndex( face.MaterialChannelIndex() ) + is used to render the face. */ ON_SimpleArray m_material_channel; + enum : int + { + /// + /// Material channel index values stored in the ON_UuidIndex.m_i field of elements in the m_material_channel[] array + /// must be between 0 and ON_Material::MaximumMaterialChannelIndex, inclusive. + /// + MaximumMaterialChannelIndex = 65535 + }; + + /* + Parameters: + material_channel_index - [in] + Returns: + If material_channel_index > 0, the m_id ON_UUID value of the first element in the + m_material_channel[] array with material_channel_index = ON_Uuid_index.m_i is returned. + This id identifies an ON_Material. + Otherwise ON_nil_uuid is returned. + */ + const ON_UUID MaterialChannelIdFromIndex( + int material_channel_index + ) const; + + /* + Parameters: + material_channel_id - [in] + Returns: + If material_channel_id is not nil, the m_i index value of the first element in the + m_material_channel[] array with material_channel_id = ON_Uuid_index.m_id is returned. + Otherwise 0 is returned. + */ + int MaterialChannelIndexFromId( + ON_UUID material_channel_id + ) const; + + /* + Parameters: + material_channel_id - [in] + bAddIdIfNotPresent - [in] + Returns: + If material_channel_id is not nil, the m_i index value of the first element in the + m_material_channel[] array with material_channel_id = ON_Uuid_index.m_id is returned. + If material_channel_id is not nil an no element of the m_material_channel[] array + has a matching id, a new element is added with a unique channel index > 0 and that + index is returned. + Otherwise 0 is returned. + */ + int MaterialChannelIndexFromId( + ON_UUID material_channel_id, + bool bAddIdIfNotPresent + ); + private: ON_UUID m_plugin_id = ON_nil_uuid; diff --git a/opennurbs_model_component.cpp b/opennurbs_model_component.cpp index 6207ff91..691795ac 100644 --- a/opennurbs_model_component.cpp +++ b/opennurbs_model_component.cpp @@ -694,6 +694,12 @@ ON_ModelComponent::ON_ModelComponent( } } +ON__UINT64 ON_ModelComponent::NextRuntimeSerialNumber() +{ + const ON__UINT64 last_runtime_serial_number(ON_ModelComponent::Internal_RuntimeSerialNumberGenerator); + return (last_runtime_serial_number + 1); +} + ON__UINT64 ON_ModelComponent::RuntimeSerialNumber() const { return m_runtime_serial_number; diff --git a/opennurbs_model_component.h b/opennurbs_model_component.h index 972b8fa5..a70891b6 100644 --- a/opennurbs_model_component.h +++ b/opennurbs_model_component.h @@ -1347,6 +1347,13 @@ public: */ ON__UINT64 RuntimeSerialNumber() const; + /* + Returns: + The next ON_ModelComponent instance will have runtime serial number + >= ON_ModelComponent::NextRuntimeSerialNumber(); + */ + static ON__UINT64 NextRuntimeSerialNumber(); + /* Description: Whenever an attribute is changed, the content version number is incremented. @@ -1422,7 +1429,6 @@ private: ON__UINT16 m_locked_status = 0; ON__UINT16 m_set_status = 0; ON_ComponentStatus m_component_status = ON_ComponentStatus::NoneSet; - ON__UINT8 m_reserved1 = 0; ON__UINT16 m_reserved2 = 0; // m_component_index is the index of the component in the model identified diff --git a/opennurbs_nurbssurface.cpp b/opennurbs_nurbssurface.cpp index 1b49df49..cb98740c 100644 --- a/opennurbs_nurbssurface.cpp +++ b/opennurbs_nurbssurface.cpp @@ -3353,6 +3353,7 @@ static bool ValidateHermiteData( return true; } + class ON_NurbsSurface* ON_NurbsSurface::CreateHermiteSurface( const ON_SimpleArray& u, const ON_SimpleArray& v, @@ -3360,75 +3361,87 @@ class ON_NurbsSurface* ON_NurbsSurface::CreateHermiteSurface( const ON_ClassArray>& u_Tan, const ON_ClassArray>& v_Tan, const ON_ClassArray>& Twist, - class ON_NurbsSurface* hsrf ) + class ON_NurbsSurface* hsrf) { - if (!ValidateHermiteData( u, v, GridPoints, u_Tan, v_Tan, Twist )) - return nullptr; + if (!ValidateHermiteData( u, v, GridPoints, u_Tan, v_Tan, Twist )) + return nullptr; - int n = u.Count(); - int m = v.Count(); + int n = u.Count(); + int m = v.Count(); - if (hsrf == nullptr) - { - hsrf = ON_NurbsSurface::New(); - } + if (hsrf == nullptr) + { + hsrf = ON_NurbsSurface::New(); + } + const int dim = 3; + const int order = 4; - bool rc = hsrf->Create(3, false, 4, 4, 3 * n - 2, 3 * m - 2); - if (rc) - { - // Set the knots - for (int i = 0; i < n; i++) - { - hsrf->SetKnot(0, 3 * i, u[i]); - hsrf->SetKnot(0, 3 * i + 1, u[i]); - hsrf->SetKnot(0, 3 * i + 2, u[i]); - } - for (int j = 0; j < m; j++) - { - hsrf->SetKnot(1, 3 * j, v[j]); - hsrf->SetKnot(1, 3 * j + 1, v[j]); - hsrf->SetKnot(1, 3 * j + 2, v[j]); - } + /* Surface has double knot in the interior*/ + bool rc = hsrf->Create(dim, false, order, order, 2 * n , 2 * m ); + if (rc) + { + // Set the knots duble interior knots + hsrf->SetKnot(0, 0, u[0]); + for (int i = 0; i < n; i++) + { + hsrf->SetKnot(0, 2*i + 1, u[i]); + hsrf->SetKnot(0, 2*i + 2, u[i]); + } + hsrf->SetKnot(0, 2*n + 1, u[n-1]); - // Set GridPoints - for (int i = 0; i < n; i++) - for (int j = 0; j < m ; j++) - hsrf->SetCV(3*i, 3*j, GridPoints[i][j]); + hsrf->SetKnot(1, 0, v[0]); + for (int j = 0; j< m; j++) + { + hsrf->SetKnot(1, 2 * j + 1, v[j]); + hsrf->SetKnot(1, 2 * j + 2, v[j]); + } + hsrf->SetKnot(1, 2 * m + 1, v[m - 1]); - // set the points on v - isos between grid points - for (int j = 0; j < m-1; j++) - for (int i = 0; i < n; i++) - { - double delv = 1.0/3.0 * (v[j + 1] - v[j]); - hsrf->SetCV(3 * i, 3 * j + 1, GridPoints[i][j] + delv * v_Tan[i][j]); - hsrf->SetCV(3 * i, 3 * j + 2, GridPoints[i][j+1] - delv * v_Tan[i][j+1]); - } - // set the points on u - isos between grid points - for (int i = 0; i < n - 1; i++) - for (int j= 0; j < m; j++) - { - double delu = 1.0/3.0 * (u[i + 1] - u[i]); - hsrf->SetCV(3 * i + 1, 3 * j, GridPoints[i][j] + delu * u_Tan[i][j]); - hsrf->SetCV(3 * i + 2, 3 * j, GridPoints[i+1][j] - delu * u_Tan[i+1][j]); - } + // Set corner GridPoints + hsrf->SetCV( 0 , 0 , GridPoints[ 0 ][ 0 ]); + hsrf->SetCV( 0 , 2*m-1, GridPoints[ 0 ][m - 1]); + hsrf->SetCV(2*n-1, 0 , GridPoints[n - 1][ 0 ]); + hsrf->SetCV(2*n-1, 2*m-1, GridPoints[n - 1][m - 1]); - // set the interior points off the grid iso's - for( int i=0; iSetCV(3 * i + 1, 3 * j + 1, GridPoints[i][j] + ( delu * u_Tan[i][j] + delv * v_Tan[i][j]) + deluv * Twist[i][j]); - hsrf->SetCV(3 * i + 2, 3 * j + 1, GridPoints[i+1][j] + (-delu * u_Tan[i+1][j] + delv * v_Tan[i+1][j]) - deluv * Twist[i+1][j]); - hsrf->SetCV(3 * i + 1, 3 * j + 2, GridPoints[i][j+1] + ( delu * u_Tan[i][j+1] - delv * v_Tan[i][j+1]) - deluv * Twist[i][j+1]); - hsrf->SetCV(3 * i + 2, 3 * j + 2, GridPoints[i+1][j+1]+ (-delu * u_Tan[i+1][j+1]- delv * v_Tan[i+1][j+1])+ deluv * Twist[i+1][j+1]); - } - } - if (!rc) - hsrf = nullptr; - return hsrf; + // set the points on v - isos edges + for (int j = 0; j < m - 1; j++) + { + double delv = 1.0 / 3.0 * (v[j + 1] - v[j]); + hsrf->SetCV( 0 , 2 * j + 1, GridPoints[ 0 ][ j ] + delv * v_Tan[ 0 ][ j ]); + hsrf->SetCV( 0 , 2 * j + 2, GridPoints[ 0 ][j + 1] - delv * v_Tan[ 0 ][j + 1]); + hsrf->SetCV(2*n-1, 2 * j + 1, GridPoints[n-1][ j ] + delv * v_Tan[n-1][ j ]); + hsrf->SetCV(2*n-1, 2 * j + 2, GridPoints[n-1][j + 1] - delv * v_Tan[n-1][j + 1]); + } + + // set the points on u - isos edges + for (int i = 0; i < n - 1; i++) + { + double delu = 1.0 / 3.0 * (u[i + 1] - u[i]); + hsrf->SetCV(2 * i + 1, 0 , GridPoints[ i ][ 0 ] + delu * u_Tan[ i ][ 0 ]); + hsrf->SetCV(2 * i + 2, 0 , GridPoints[i+1][ 0 ] - delu * u_Tan[i+1][ 0 ]); + hsrf->SetCV(2 * i + 1, 2*m-1, GridPoints[ i ][m-1] + delu * u_Tan[ i ][ m-1 ]); + hsrf->SetCV(2 * i + 2, 2*m-1, GridPoints[i+1][m-1] - delu * u_Tan[i+1][ m-1 ]); + } + + + // set the interior points + for( int i=0; iSetCV(2*i+1, 2*j+1, GridPoints[ i ][ j ] + ( delu * u_Tan[ i ][ j ] + delv * v_Tan[ i ][ j ]) + deluv * Twist[ i ][ j ]); + hsrf->SetCV(2*i+2, 2*j+1, GridPoints[i+1][ j ] + (-delu * u_Tan[i+1][ j ] + delv * v_Tan[i+1][ j ]) - deluv * Twist[i+1][ j ]); + hsrf->SetCV(2*i+1, 2*j+2, GridPoints[ i ][j+1] + ( delu * u_Tan[ i ][j+1] - delv * v_Tan[ i ][j+1]) - deluv * Twist[ i ][j+1]); + hsrf->SetCV(2*i+2, 2*j+2, GridPoints[i+1][j+1] + (-delu * u_Tan[i+1][j+1] - delv * v_Tan[i+1][j+1]) + deluv * Twist[i+1][j+1]); + } + } + if (!rc) + hsrf = nullptr; + return hsrf; + } @@ -3439,8 +3452,8 @@ ON_HermiteSurface::ON_HermiteSurface() } ON_HermiteSurface::ON_HermiteSurface(int u_count, int v_count) - : m_u_count(u_count) - , m_v_count(v_count) + : m_u_count(0) + , m_v_count(0) { Create(u_count, v_count); } @@ -3470,42 +3483,42 @@ bool ON_HermiteSurface::Create(int u_count, int v_count) for (int i = 0; i < m_v_count; i++) m_v_parameters[i] = ON_UNSET_VALUE; - m_grid_points.SetCapacity(m_v_count); - for (int i = 0; i < m_v_count; i++) + m_grid_points.SetCapacity(m_u_count); + for (int i = 0; i < m_u_count; i++) { ON_SimpleArray& arr = m_grid_points.AppendNew(); - arr.SetCapacity(m_u_count); - arr.SetCount(m_u_count); + arr.SetCapacity(m_v_count); + arr.SetCount(m_v_count); arr.Zero(); } - m_u_tangents.SetCapacity(m_v_count); - for (int i = 0; i < m_v_count; i++) + m_u_tangents.SetCapacity(m_u_count); + for (int i = 0; i < m_u_count; i++) { ON_SimpleArray& arr = m_u_tangents.AppendNew(); - arr.SetCapacity(m_u_count); - arr.SetCount(m_u_count); - for (int j = 0; j < m_u_count; j++) + arr.SetCapacity(m_v_count); + arr.SetCount(m_v_count); + for (int j = 0; j < m_v_count; j++) arr[j] = ON_3dPoint::UnsetPoint; } - m_v_tangents.SetCapacity(m_v_count); - for (int i = 0; i < m_v_count; i++) + m_v_tangents.SetCapacity(m_u_count); + for (int i = 0; i < m_u_count; i++) { ON_SimpleArray& arr = m_v_tangents.AppendNew(); - arr.SetCapacity(m_u_count); - arr.SetCount(m_u_count); - for (int j = 0; j < m_u_count; j++) + arr.SetCapacity(m_v_count); + arr.SetCount(m_v_count); + for (int j = 0; j < m_v_count; j++) arr[j] = ON_3dVector::UnsetVector; } - m_twists.SetCapacity(m_v_count); - for (int i = 0; i < m_v_count; i++) + m_twists.SetCapacity(m_u_count); + for (int i = 0; i < m_u_count; i++) { ON_SimpleArray& arr = m_twists.AppendNew(); - arr.SetCapacity(m_u_count); - arr.SetCount(m_u_count); - for (int j = 0; j < m_u_count; j++) + arr.SetCapacity(m_v_count); + arr.SetCount(m_v_count); + for (int j = 0; j < m_v_count; j++) arr[j] = ON_3dVector::UnsetVector; } @@ -3594,6 +3607,16 @@ bool ON_HermiteSurface::IsValid() const ); } +int ON_HermiteSurface::UCount() const +{ + return m_u_count; +} + +int ON_HermiteSurface::VCount() const +{ + return m_v_count; +} + bool ON_HermiteSurface::InBounds(int u, int v) const { return ( @@ -3636,56 +3659,56 @@ ON_3dPoint ON_HermiteSurface::PointAt(int u, int v) const { ON_3dPoint rc = ON_3dPoint::UnsetPoint; if (InBounds(u, v)) - rc = m_grid_points[v][u]; + rc = m_grid_points[u][v]; return rc; } void ON_HermiteSurface::SetPointAt(int u, int v, const ON_3dPoint& point) { if (InBounds(u, v)) - m_grid_points[v][u] = point; + m_grid_points[u][v] = point; } ON_3dVector ON_HermiteSurface::UTangentAt(int u, int v) const { ON_3dVector rc = ON_3dVector::UnsetVector; if (InBounds(u, v)) - rc = m_u_tangents[v][u]; + rc = m_u_tangents[u][v]; return rc; } void ON_HermiteSurface::SetUTangentAt(int u, int v, const ON_3dVector& dir) { if (InBounds(u, v)) - m_u_tangents[v][u] = dir; + m_u_tangents[u][v] = dir; } ON_3dVector ON_HermiteSurface::VTangentAt(int u, int v) const { ON_3dVector rc = ON_3dVector::UnsetVector; if (InBounds(u, v)) - rc = m_v_tangents[v][u]; + rc = m_v_tangents[u][v]; return rc; } void ON_HermiteSurface::SetVTangentAt(int u, int v, const ON_3dVector& dir) { if (InBounds(u, v)) - m_v_tangents[v][u] = dir; + m_v_tangents[u][v] = dir; } ON_3dVector ON_HermiteSurface::TwistAt(int u, int v) const { ON_3dVector rc = ON_3dVector::UnsetVector; if (InBounds(u, v)) - rc = m_twists[v][u]; + rc = m_twists[u][v]; return rc; } void ON_HermiteSurface::SetTwistAt(int u, int v, const ON_3dVector& dir) { if (InBounds(u, v)) - m_twists[v][u] = dir; + m_twists[u][v] = dir; } const ON_SimpleArray& ON_HermiteSurface::UParameters() const diff --git a/opennurbs_nurbssurface.h b/opennurbs_nurbssurface.h index 194c0130..77d9ebb2 100644 --- a/opennurbs_nurbssurface.h +++ b/opennurbs_nurbssurface.h @@ -948,19 +948,19 @@ public: bool GetGrevilleAbcissae( // see ON_GetGrevilleAbcissa() for details int dir, // dir - double* g // g[cv count] + double* g // g[cv count] ) const; bool SetClampedGrevilleKnotVector( - int dir, // dir - int g_stride, // g_stride - const double* g // g[], CVCount(dir) many Greville abcissa + int dir, // dir + int g_stride, // g_stride + const double* g // g[], CVCount(dir) many Greville abcissa ); bool SetPeriodicGrevilleKnotVector( - int dir, // dir - int g_stride, // g_stride - const double* g // g[], Greville abcissa + int dir, // dir + int g_stride, // g_stride + const double* g // g[], Greville abcissa ); bool ZeroCVs(); // zeros all CVs (any weights set to 1); @@ -1048,23 +1048,23 @@ public: /* Description: - Create an ON_NurbsSurface satisfying interpolation conditions at a grid of points. + Create an ON_NurbsSurface satisfying Hermite interpolation conditions at a grid of points. Parameters: u_Parameters - v_Parameters - [in] Specifies the "u" parameters defining the grid of parameter values + v_Parameters - [in] Specifies the "u"( or "v") parameters defining the grid of parameter values u_Parameters.Count()>1 u_Parameters are strictly increasing, i.e. u_Parameters[i] < u_Parameters[i+1] same conditions on v_Parameters Let n = u_Parameters.Count() and m = v_Parameters.Count(). - Each of GridPoints, u_Tangents, v_Tangents and TwistVectors are data on a grid of points. - The size of each of these arrays must be n x m, so + Each of GridPoints, u_Tangents, v_Tangents and TwistVectors are data on a grid of parameters. + The size of each of these arrays must be n x m, s GridPoints.Count() == n and GridPoints[i].Count() == m. GridPoints - [in] Grid of points to interpolate. - u_Tangents - [in] Grid of Tangent directions to interpolate. - v_Tangents - [in] Grid of Tangent directions to interpolate. - TwistVectors - [in] Grid of twist vectors to interpolate. + u_Tangents - [in] Grid of Tangent directions ( actually first derivatives) to interpolate. + v_Tangents - [in] Grid of Tangent directions ( actually first derivatives) to interpolate. + TwistVectors - [in] Grid of twist vectors (mixed second partial derovative) to interpolate. hermite_surface -[in] optional existing ON_NurbsSurface returned here. Returns: @@ -1073,15 +1073,9 @@ public: The Hermite surface, H, is bicubic on each patch [u_i, u_(i+1)] x [v_j, v_(j+1)] and satisfies H( u_i, v_j) = GridData[i][j] - The first derivatives may be discontinuous at the knots - H_u_+/- ( u_i, v_j) = (Del_+/- u)_i * u_Tangents[i][j] - H_v_+/-( u_i, v_j) = (Del_+/- v)_i * v_Tangents[i][j] - Here the forward and backward difference operators - (Del_+ u)_i = u_(i+1) - u_i and (Del_- u)_i = u_i - u_(i-1) - are used for the forward (H_u_+) and backward (H_u_-) derivatives respectively. - Similarly the mixed partial derivative is defined by - H_uv_++ ( u_i, v_j) = (Del_+ u)_i * (Del_+ v)_j * TwistVector[i][j] - with 3 other possible variation in signs + H_u(u_i, v_j) = u_Tangents[i][j] + H_v(u_i, v_j) = v_Tangents[i][j] + H_uv(u_i, v_j) = Twist[i][j] */ static class ON_NurbsSurface* CreateHermiteSurface( @@ -2050,7 +2044,7 @@ ON_DLL_TEMPLATE template class ON_CLASS ON_ClassArray& VParameters() const; // Grid of points to interpolate. const ON_ClassArray>& GridPoints() const; - // Grid of tangents in "u" direction to interpolate. + // Grid of "u" tangent directions (actually first derivatives) to interpolate. const ON_ClassArray>& UTangents() const; - // Grid of tangents in "v" direction to interpolate. + // Grid of "v" tangent directions (actually first derivatives) to interpolate. const ON_ClassArray>& VTangents() const; - // Grid of twist vectors to interpolate. + // Grid of twist vectors (mixed second partial derivatives) to interpolate. const ON_ClassArray>& Twists() const; private: diff --git a/opennurbs_public_version.h b/opennurbs_public_version.h index cf9bd6a2..f18f9816 100644 --- a/opennurbs_public_version.h +++ b/opennurbs_public_version.h @@ -14,10 +14,10 @@ // first step in each build. // #define RMA_VERSION_YEAR 2020 -#define RMA_VERSION_MONTH 1 -#define RMA_VERSION_DATE 16 -#define RMA_VERSION_HOUR 10 -#define RMA_VERSION_MINUTE 47 +#define RMA_VERSION_MONTH 3 +#define RMA_VERSION_DATE 11 +#define RMA_VERSION_HOUR 14 +#define RMA_VERSION_MINUTE 39 //////////////////////////////////////////////////////////////// // @@ -35,8 +35,8 @@ // 3 = build system release build #define RMA_VERSION_BRANCH 0 -#define VERSION_WITH_COMMAS 7,0,20016,10470 -#define VERSION_WITH_PERIODS 7.0.20016.10470 +#define VERSION_WITH_COMMAS 7,0,20071,14390 +#define VERSION_WITH_PERIODS 7.0.20071.14390 #define COPYRIGHT "Copyright (C) 1993-2020, Robert McNeel & Associates. All Rights Reserved." #define SPECIAL_BUILD_DESCRIPTION "Public OpenNURBS C++ 3dm file IO library." @@ -47,8 +47,8 @@ #define RMA_VERSION_NUMBER_SR_STRING "SR0" #define RMA_VERSION_NUMBER_SR_WSTRING L"SR0" -#define RMA_VERSION_WITH_PERIODS_STRING "7.0.20016.10470" -#define RMA_VERSION_WITH_PERIODS_WSTRING L"7.0.20016.10470" +#define RMA_VERSION_WITH_PERIODS_STRING "7.0.20071.14390" +#define RMA_VERSION_WITH_PERIODS_WSTRING L"7.0.20071.14390" diff --git a/opennurbs_rendering.h b/opennurbs_rendering.h index 5c071b28..d4042efe 100644 --- a/opennurbs_rendering.h +++ b/opennurbs_rendering.h @@ -112,6 +112,12 @@ public: const ON_UUID& mapping_id ) const; + /* + Returns: + The mapping channel id to use when calling MappingChannel to retrieve the OCS mapping if there is one. + See CRhinoTextureMapping::OcsMappingTransformForObject for an easy helper function to get the transform per object + */ + static int OCSMappingChannelId(void); /* Parameters: diff --git a/opennurbs_subd.cpp b/opennurbs_subd.cpp index 6cd4b238..5c8c5be7 100644 --- a/opennurbs_subd.cpp +++ b/opennurbs_subd.cpp @@ -884,6 +884,25 @@ bool ON_SubDComponentPtr::SetMark( return (nullptr != c) ? c->m_status.SetRuntimeMark(bMark) : false; } +ON__UINT8 ON_SubDComponentPtr::MarkBits() const +{ + const ON_SubDComponentBase* c = this->ComponentBase(); + return (nullptr != c) ? c->m_status.MarkBits() : 0U; +} + +ON__UINT8 ON_SubDComponentPtr::SetMarkBits(ON__UINT8 mark_bits) const +{ + const ON_SubDComponentBase* c = this->ComponentBase(); + return (nullptr != c) ? c->m_status.SetMarkBits(mark_bits) : false; +} + +ON__UINT8 ON_SubDComponentPtr::ClearMarkBits() const +{ + const ON_SubDComponentBase* c = this->ComponentBase(); + return (nullptr != c) ? c->m_status.SetMarkBits(0) : false; +} + + bool ON_SubDVertexPtr::Mark() const { const ON_SubDVertex* c = this->Vertex(); @@ -910,6 +929,23 @@ bool ON_SubDVertexPtr::SetMark( return (nullptr != c) ? c->m_status.SetRuntimeMark(bMark) : false; } +ON__UINT8 ON_SubDVertexPtr::MarkBits() const +{ + const ON_SubDVertex* c = this->Vertex(); + return (nullptr != c) ? c->m_status.MarkBits() : 0U; +} + +ON__UINT8 ON_SubDVertexPtr::SetMarkBits(ON__UINT8 mark_bits) const +{ + const ON_SubDVertex* c = this->Vertex(); + return (nullptr != c) ? c->m_status.SetMarkBits(mark_bits) : false; +} + +ON__UINT8 ON_SubDVertexPtr::ClearMarkBits() const +{ + const ON_SubDVertex* c = this->Vertex(); + return (nullptr != c) ? c->m_status.SetMarkBits(0) : false; +} bool ON_SubDEdgePtr::Mark() const { @@ -937,6 +973,23 @@ bool ON_SubDEdgePtr::SetMark( return (nullptr != c) ? c->m_status.SetRuntimeMark(bMark) : false; } +ON__UINT8 ON_SubDEdgePtr::MarkBits() const +{ + const ON_SubDEdge* c = this->Edge(); + return (nullptr != c) ? c->m_status.MarkBits() : 0U; +} + +ON__UINT8 ON_SubDEdgePtr::SetMarkBits(ON__UINT8 mark_bits) const +{ + const ON_SubDEdge* c = this->Edge(); + return (nullptr != c) ? c->m_status.SetMarkBits(mark_bits) : false; +} + +ON__UINT8 ON_SubDEdgePtr::ClearMarkBits() const +{ + const ON_SubDEdge* c = this->Edge(); + return (nullptr != c) ? c->m_status.SetMarkBits(0) : false; +} bool ON_SubDFacePtr::Mark() const { @@ -964,6 +1017,24 @@ bool ON_SubDFacePtr::SetMark( return (nullptr != c) ? c->m_status.SetRuntimeMark(bMark) : false; } +ON__UINT8 ON_SubDFacePtr::MarkBits() const +{ + const ON_SubDFace* c = this->Face(); + return (nullptr != c) ? c->m_status.MarkBits() : 0U; +} + +ON__UINT8 ON_SubDFacePtr::SetMarkBits(ON__UINT8 mark_bits) const +{ + const ON_SubDFace* c = this->Face(); + return (nullptr != c) ? c->m_status.SetMarkBits(mark_bits) : false; +} + +ON__UINT8 ON_SubDFacePtr::ClearMarkBits() const +{ + const ON_SubDFace* c = this->Face(); + return (nullptr != c) ? c->m_status.SetMarkBits(0) : false; +} + ON__UINT_PTR ON_SubDComponentPtr::ComponentDirection() const { return ON_SUBD_COMPONENT_DIRECTION(m_ptr); @@ -995,7 +1066,7 @@ const ON_SubDComponentPtr ON_SubDComponentPtr::SetComponentDirection(ON__UINT_PT return component_ptr; } -const ON_SubDComponentPtr ON_SubDComponentPtr::ToggleComponentDirection() const +const ON_SubDComponentPtr ON_SubDComponentPtr::Reversed() const { return (0 != (m_ptr & ON_SUBD_COMPONENT_DIRECTION_MASK)) ? ClearComponentDirection() : SetComponentDirection(); } @@ -1395,6 +1466,11 @@ const ON_SubDComponentPtrPair ON_SubDComponentPtrPair::SwapPair() const return ON_SubDComponentPtrPair::Create(m_pair[1], m_pair[0]); } +const ON_SubDComponentPtrPair ON_SubDComponentPtrPair::ReversedPair() const +{ + return ON_SubDComponentPtrPair::Create(m_pair[0].Reversed(), m_pair[1].Reversed()); +} + const ON_SubDComponentPtr ON_SubDComponentPtrPair::First() const { return m_pair[0]; @@ -1420,6 +1496,21 @@ bool ON_SubDComponentPtrPair::BothAreNull() const return (0 == (ON_SUBD_COMPONENT_POINTER_MASK & m_pair[0].m_ptr)) && 0 == (ON_SUBD_COMPONENT_POINTER_MASK & m_pair[1].m_ptr); } +bool ON_SubDComponentPtrPair::FirstIsNotNull() const +{ + return m_pair[0].IsNotNull(); +} + +bool ON_SubDComponentPtrPair::SecondIsNotNull() const +{ + return m_pair[1].IsNotNull(); +} + +bool ON_SubDComponentPtrPair::BothAreNotNull() const +{ + return m_pair[0].IsNotNull() && m_pair[1].IsNotNull(); +} + ////////////////////////////////////////////////////////////////////////// // // ON_ToSubDParameters @@ -1700,6 +1791,20 @@ unsigned int ON_SubDVertex::MarkedEdgeCount() const return mark_count; } +bool ON_SubDVertex::ClearEdgeMarks() const +{ + bool rc = true; + for (unsigned short vei = 0; vei < m_edge_count; ++vei) + { + const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(m_edges[vei].m_ptr); + if (nullptr == e) + rc = false; + else + e->m_status.ClearRuntimeMark(); + } + return rc; +} + unsigned int ON_SubDVertex::MarkedFaceCount() const { unsigned int mark_count = 0; @@ -1712,6 +1817,21 @@ unsigned int ON_SubDVertex::MarkedFaceCount() const return mark_count; } +bool ON_SubDVertex::ClearFaceMarks() const +{ + bool rc = true; + for (unsigned short vfi = 0; vfi < m_face_count; ++vfi) + { + const ON_SubDFace* f = m_faces[vfi]; + if (nullptr == f) + rc = false; + else + f->m_status.ClearRuntimeMark(); + } + return rc; +} + + unsigned int ON_SubDVertex::MinimumFaceEdgeCount() const { unsigned short min_count = 0xFFFFU; @@ -2165,6 +2285,94 @@ bool ON_SubDVertex::HasBoundaryVertexTopology() const return false; } +const ON_SubDComponentPtrPair ON_SubDVertex::BoundaryEdgePair() const +{ + ON_SubDComponentPtrPair epair = ON_SubDComponentPtrPair::Null; + if (nullptr != m_edges && m_edge_count >= 2) + { + for (unsigned short vei = 0; vei < m_edge_count; ++vei) + { + ON_SubDEdgePtr eptr = m_edges[vei]; + const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(eptr.m_ptr); + if (nullptr == e) + continue; + if (false == e->HasBoundaryEdgeTopology()) + continue; + const ON__UINT_PTR edir = ON_SUBD_EDGE_DIRECTION(eptr.m_ptr); + if (this != e->m_vertex[edir]) + { + // m_edges[vei] is corrupt ... + ON_SUBD_ERROR("m_edges[vei] has incorrect edge orientation flag."); + if (this == e->m_vertex[1 - edir]) + eptr = eptr.Reversed(); // we can still return the requested information. + else + return ON_SubDComponentPtrPair::Null; + } + if (epair.m_pair[0].IsNull()) + epair.m_pair[0] = ON_SubDComponentPtr::Create(eptr); + else if (epair.m_pair[1].IsNull()) + epair.m_pair[1] = ON_SubDComponentPtr::Create(eptr); + else + return ON_SubDComponentPtrPair::Null; // 3 or more boundary edges + } + } + return (epair.m_pair[1].IsNotNull()) ? epair : ON_SubDComponentPtrPair::Null; +} + +const ON_SubDComponentPtrPair ON_SubDVertex::CreasedEdgePair(bool bInteriorEdgesOnly) const +{ + ON_SubDComponentPtrPair creased_eptr_pair = ON_SubDComponentPtrPair::Null; + if (nullptr != m_edges && m_edge_count >= 2) + { + for (unsigned short vei = 0; vei < m_edge_count; ++vei) + { + const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(m_edges[vei].m_ptr); + if (nullptr == e) + continue; + if (bInteriorEdgesOnly && false == e->HasInteriorEdgeTopology(false)) + continue; + if (ON_SubD::EdgeTag::Crease == e->m_edge_tag) + { + if (e == ON_SUBD_EDGE_POINTER(creased_eptr_pair.m_pair[0].m_ptr) || e == ON_SUBD_EDGE_POINTER(creased_eptr_pair.m_pair[1].m_ptr)) + { + ON_SUBD_ERROR("Duplicate entries in m_edges[] list."); + continue; + } + if (creased_eptr_pair.FirstIsNull()) + creased_eptr_pair.m_pair[0] = ON_SubDComponentPtr::Create(m_edges[vei]); + else if (creased_eptr_pair.SecondIsNull()) + creased_eptr_pair.m_pair[1] = ON_SubDComponentPtr::Create(m_edges[vei]); + else + return ON_SubDComponentPtrPair::Null; // 3 or more creases + } + } + } + return creased_eptr_pair.SecondIsNull() ? ON_SubDComponentPtrPair::Null : creased_eptr_pair; +} + +const ON_SubDEdgePtr ON_SubDVertex::CreasedEdge(bool bInteriorEdgesOnly) const +{ + ON_SubDEdgePtr creased_eptr = ON_SubDEdgePtr::Null; + if (nullptr != m_edges) + { + for (unsigned short vei = 0; vei < m_edge_count; ++vei) + { + const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(m_edges[vei].m_ptr); + if (nullptr == e) + continue; + if (bInteriorEdgesOnly && false == e->HasInteriorEdgeTopology(false)) + continue; + if (ON_SubD::EdgeTag::Crease == e->m_edge_tag) + { + if (creased_eptr.IsNull()) + creased_eptr = m_edges[vei]; + else + return ON_SubDEdgePtr::Null; // 2 or more creases. + } + } + } + return creased_eptr; +} bool ON_SubDVertexEdgeProperties::HasInteriorVertexTopology() const { @@ -2637,23 +2845,88 @@ const ON_SubDFacePtr ON_SubDEdge::FacePtr( //return (i < m_face_count) ? ((i < 2) ? m_face2[i] : m_facex[i - 2]) : ON_SubDFacePtr::Null; } +unsigned ON_SubDEdge::VertexCount() const +{ + return + (nullptr != m_vertex[0]) + ? ((nullptr != m_vertex[1] && m_vertex[0] != m_vertex[1]) ? 2U : 1U) + : ((nullptr != m_vertex[1] ? 1U : 0U)) + ; +} + unsigned int ON_SubDEdge::FaceCount() const { return m_face_count; } -bool ON_SubDEdge::IsInteriorEdge() const +bool ON_SubDEdge::HasBoundaryEdgeTopology() const +{ + for (;;) + { + if (1 != m_face_count) + break; + const ON_SubDFace* f = ON_SUBD_FACE_POINTER(m_face2[0].m_ptr); + if (nullptr == f) + break; + const ON_SubDEdgePtr feptr = f->EdgePtrFromEdge(this); + if (this != ON_SUBD_EDGE_POINTER(feptr.m_ptr)) + { + ON_SUBD_ERROR("m_face2[0] does not reference this edge."); + break; + } + if (ON_SUBD_FACE_DIRECTION(m_face2[0].m_ptr) != ON_SUBD_FACE_DIRECTION(feptr.m_ptr)) + { + ON_SUBD_ERROR("m_face2[0] has inconsistent direction flags."); + break; + } + if (nullptr == m_vertex[0] || nullptr == m_vertex[1] || m_vertex[0] == m_vertex[1]) + { + ON_SUBD_ERROR("m_vertex[] has null or invalid pointers."); + break; + } + return true; + } + return false; +} + +bool ON_SubDEdge::HasInteriorEdgeTopology(bool bRequireOppositeFaceDirections) const { for (;;) { if (2 != m_face_count) break; - if (ON_SUBD_FACE_DIRECTION(m_face2[0].m_ptr) == ON_SUBD_FACE_DIRECTION(m_face2[1].m_ptr)) + if (bRequireOppositeFaceDirections && ON_SUBD_FACE_DIRECTION(m_face2[0].m_ptr) == ON_SUBD_FACE_DIRECTION(m_face2[1].m_ptr)) break; - if (nullptr == ON_SUBD_FACE_POINTER(m_face2[0].m_ptr)) + const ON_SubDFace* f[2] = { ON_SUBD_FACE_POINTER(m_face2[0].m_ptr), ON_SUBD_FACE_POINTER(m_face2[1].m_ptr) }; + if ( nullptr == f[0] || nullptr == f[1] || f[0] == f[1]) break; - if (nullptr == ON_SUBD_FACE_POINTER(m_face2[1].m_ptr)) + const ON_SubDEdgePtr feptr[2] = { f[0]->EdgePtrFromEdge(this), f[1]->EdgePtrFromEdge(this) }; + if (this != ON_SUBD_EDGE_POINTER(feptr[0].m_ptr)) + { + ON_SUBD_ERROR("m_face2[0] does not reference this edge."); break; + } + if (ON_SUBD_FACE_DIRECTION(feptr[0].m_ptr) != ON_SUBD_FACE_DIRECTION(m_face2[0].m_ptr)) + { + ON_SUBD_ERROR("m_face2[0] has inconsistent direction flags."); + break; + } + if (this != ON_SUBD_EDGE_POINTER(feptr[1].m_ptr)) + { + ON_SUBD_ERROR("m_face2[1] does not reference this edge."); + break; + } + if (ON_SUBD_FACE_DIRECTION(feptr[1].m_ptr) != ON_SUBD_FACE_DIRECTION(m_face2[1].m_ptr)) + { + ON_SUBD_ERROR("m_face2[1] has inconsistent direction flags."); + break; + } + if (nullptr == m_vertex[0] || nullptr == m_vertex[1] || m_vertex[0] == m_vertex[1]) + { + ON_SUBD_ERROR("m_vertex[] has null or invalid pointers."); + break; + } + return true; } return false; } @@ -3364,6 +3637,96 @@ bool ON_SubDVertex::RemoveFaceFromArray(const ON_SubDFace * f) return true; } +ON_SubD::VertexTag ON_SubDVertex::SuggestedVertexTag( + bool bApplyInputTagBias, + bool bReturnBestGuessWhenInvalid +) const +{ + unsigned wire_count = 0; + unsigned boundary_count = 0; + unsigned interior_count = 0; + + unsigned crease_count = 0; + + const unsigned edge_count = (nullptr != m_edges ? m_edge_count : 0U); + if ( edge_count < 2) + return ON_SubD::VertexTag::Corner; + + for (unsigned vei = 0; vei < edge_count; ++vei) + { + const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(m_edges[vei].m_ptr); + if (nullptr == e) + continue; + switch(e->m_face_count) + { + case 0: + ++wire_count; + ++crease_count; + break; + case 1: + ++boundary_count; + ++crease_count; + break; + case 2: + ++interior_count; + if (ON_SubD::EdgeTag::Crease == e->m_edge_tag) + ++crease_count; + break; + default: // nonmanifold edge + return ON_SubD::VertexTag::Corner; + break; + } + } + + if (crease_count >= 3) + return ON_SubD::VertexTag::Corner; + + if (wire_count > 0) + { + if (2 == wire_count && 0 == boundary_count && 0 == interior_count) + return (bApplyInputTagBias && ON_SubD::VertexTag::Corner == m_vertex_tag) ? ON_SubD::VertexTag::Corner : ON_SubD::VertexTag::Crease; + return ON_SubD::VertexTag::Corner; + } + + ON_SubD::VertexTag best_guess_tag = ON_SubD::VertexTag::Unset; + // crease_count >= 3 handled above + switch (crease_count) + { + case 0: + if (interior_count >= 2) + return ON_SubD::VertexTag::Smooth; + if (bReturnBestGuessWhenInvalid) + { + // can occure when there is a nullptr edge + best_guess_tag = ON_SubD::VertexTag::Smooth; + } + break; + case 1: + if (0 == boundary_count && interior_count >= 2) + return ON_SubD::VertexTag::Dart; + if (bReturnBestGuessWhenInvalid) + { + // topology is far from valid and dart evaluation is very delicate. + // We need more boundary edges, but using corner will at least give a well defined vertex surface point. + best_guess_tag = ON_SubD::VertexTag::Corner; + } + break; + case 2: + if( (0 == boundary_count && interior_count >= 2) || (2 == boundary_count) ) + return (bApplyInputTagBias && ON_SubD::VertexTag::Corner == m_vertex_tag) ? ON_SubD::VertexTag::Corner : ON_SubD::VertexTag::Crease; + if (bReturnBestGuessWhenInvalid) + { + // topology is far from valid and dart evaluation is very delicate. + // We need more boundary edges, but using corner will at least give a well defined vertex surface point. + best_guess_tag = ON_SubD::VertexTag::Corner; + } + break; + } + + return best_guess_tag; +} + + bool ON_SubDEdge::RemoveFaceFromArray( const ON_SubDFace* f ) @@ -3682,6 +4045,41 @@ unsigned int ON_SubDFace::VertexIndex( return ON_UNSET_UINT_INDEX; } +const ON_SubDComponentPtrPair ON_SubDFace::VertexEdgePair( + const ON_SubDVertex* vertex +) const +{ + return ON_SubDFace::VertexEdgePair(VertexIndex(vertex)); +} + +const ON_SubDComponentPtrPair ON_SubDFace::VertexEdgePair( + unsigned vertex_index +) const +{ + for (;;) + { + const unsigned edge_count = m_edge_count; + if (edge_count < 3) + break; + if (vertex_index >= edge_count) + break; + if (edge_count > 4 && nullptr == m_edgex) + break; + const unsigned fei0 = (vertex_index + (edge_count - 1)) % edge_count; + const ON_SubDEdgePtr eptr[2] = + { + (fei0 < 4 ? m_edge4[fei0] : m_edgex[fei0 - 4]), + (vertex_index < 4 ? m_edge4[vertex_index] : m_edgex[vertex_index - 4]), + }; + const ON_SubDVertex* v = eptr[0].RelativeVertex(1); + if (nullptr == v || v != eptr[1].RelativeVertex(0)) + break; + return ON_SubDComponentPtrPair::Create( ON_SubDComponentPtr::Create(eptr[0]), ON_SubDComponentPtr::Create(eptr[1]) ); + } + return ON_SubDComponentPtrPair::Null; +} + + ////////////////////////////////////////////////////////////////////////// // // ON_SubD @@ -3731,12 +4129,14 @@ ON__UINT64 ON_SubD::ContentSerialNumber() const return (nullptr != subdimple) ? subdimple->ContentSerialNumber() : 0; } -ON__UINT64 ON_SubD::ChangeContentSerialNumberForExperts() +ON__UINT64 ON_SubD::ChangeContentSerialNumberForExperts( + bool bChangePreservesSymmetry +) { if (this == &ON_SubD::Empty) return 0; ON_SubDimple* subdimple = m_subdimple_sp.get(); - return (nullptr != subdimple) ? subdimple->ChangeContentSerialNumber() : 0; + return (nullptr != subdimple) ? subdimple->ChangeContentSerialNumber(bChangePreservesSymmetry) : 0; } ON_SubDComponentLocation ON_SubD::ToggleSubDAppearanceValue(ON_SubDComponentLocation subd_appearance) @@ -4535,7 +4935,7 @@ bool ON_SubDimple::IsValidLevel( return ON_SubDIsNotValid(bSilentError); if (1 + v_id_range[1] - v_id_range[0] < level->m_vertex_count) return ON_SubDIsNotValid(bSilentError); - if ( v_id_range[1] > m_max_vertex_id ) + if ( v_id_range[1] > MaximumVertexId() ) return ON_SubDIsNotValid(bSilentError); // currently, point vertices are not permitted @@ -4584,7 +4984,7 @@ bool ON_SubDimple::IsValidLevel( return ON_SubDIsNotValid(bSilentError); if (level->m_edge[1] != last_edge) return ON_SubDIsNotValid(bSilentError); - if ( e_id_range[1] > m_max_edge_id ) + if ( e_id_range[1] > MaximumEdgeId() ) return ON_SubDIsNotValid(bSilentError); // As of NOvember 12, 2019 @@ -4632,7 +5032,7 @@ bool ON_SubDimple::IsValidLevel( return ON_SubDIsNotValid(bSilentError); if (level->m_face[1] != last_face) return ON_SubDIsNotValid(bSilentError); - if ( f_id_range[1] > m_max_face_id ) + if ( f_id_range[1] > MaximumFaceId() ) return ON_SubDIsNotValid(bSilentError); @@ -4737,45 +5137,24 @@ bool ON_SubDimple::IsValid( ON_TextLog* text_log ) const { - const unsigned int level_count = m_levels.UnsignedCount(); - if (level_count < 1) + if (false == m_heap.IsValid(bSilentError, text_log)) { - return ON_SubDIsNotValid(bSilentError); - } - for (unsigned int level_index = 0; level_index < level_count; level_index++) - { - if (false == IsValidLevel(subd, level_index, bSilentError, text_log)) - return false; - } - - if (false == m_heap.IsValid()) - { - // id values are not increasing in the heap blocks. if (nullptr != text_log) text_log->Print("Component ids are not set correctly. m_heap.ResetId() will fix this but may break externally stored component references.\n"); return ON_SubDIsNotValid(bSilentError); } - if (MaximumVertexId() < m_heap.MaximumVertexId()) + const unsigned int level_count = m_levels.UnsignedCount(); + if (level_count < 1) { - if (nullptr != text_log) - text_log->Print("MaximumVertexId() = %u < m_heap.MaximumVertexId() = %u\n", MaximumVertexId(), m_heap.MaximumVertexId()); return ON_SubDIsNotValid(bSilentError); } - if (MaximumEdgeId() < m_heap.MaximumEdgeId()) + for (unsigned int level_index = 0; level_index < level_count; level_index++) { - if (nullptr != text_log) - text_log->Print("MaximumEdgeId() = %u < m_heap.MaximumEdgeId() = %u\n", MaximumEdgeId(), m_heap.MaximumEdgeId()); - return ON_SubDIsNotValid(bSilentError); - } - - if (MaximumFaceId() Print("MaximumFaceId() = %u < m_heap.MaximumFaceId() = %u\n", MaximumFaceId(), m_heap.MaximumFaceId()); - return ON_SubDIsNotValid(bSilentError); - } + if (false == IsValidLevel(subd, level_index, bSilentError, text_log)) + return false; + } return true; } @@ -5909,7 +6288,7 @@ void ON_SubD::DestroyRuntimeCache( bool bDelete ) level->AggregateComponentStatus().MarkAsNotCurrent(); } } - dimple->ChangeContentSerialNumber(); + dimple->ChangeContentSerialNumber(false); } return; } @@ -6133,22 +6512,35 @@ void ON_SubD::Clear() subdimple->Clear(); } -void ON_SubD::ClearHigherSubdivisionLevels( +unsigned int ON_SubD::ClearHigherSubdivisionLevels( unsigned int max_level_index ) { ON_SubDimple* subdimple = SubDimple(false); - if ( subdimple ) - subdimple->ClearHigherSubdivisionLevels(max_level_index); + return + (nullptr != subdimple) + ? subdimple->ClearHigherSubdivisionLevels(max_level_index) + : 0U; } -void ON_SubD::ClearLowerSubdivisionLevels( +unsigned int ON_SubD::ClearLowerSubdivisionLevels( unsigned int min_level_index ) { ON_SubDimple* subdimple = SubDimple(false); - if ( subdimple ) - subdimple->ClearLowerSubdivisionLevels(min_level_index); + return + (nullptr != subdimple) + ? subdimple->ClearLowerSubdivisionLevels(min_level_index) + : 0U; +} + +unsigned ON_SubD::ClearInactiveLevels() +{ + ON_SubDimple* subdimple = SubDimple(false); + return + (nullptr != subdimple) + ? subdimple->ClearInactiveLevels() + : 0U; } void ON_SubD::Destroy() @@ -6177,14 +6569,52 @@ class ON_SubDVertex* ON_SubD::AddVertex( return v; } +ON_SubDVertex* ON_SubD::AddVertexForExperts( + unsigned int candidate_vertex_id, + ON_SubD::VertexTag vertex_tag, + const double* P, + unsigned int initial_edge_capacity, + unsigned int initial_face_capacity +) +{ + ON_SubDimple* subdimple = SubDimple(true); + if (0 == subdimple) + return 0; + ON_SubDVertex* v = subdimple->AllocateVertex( candidate_vertex_id, vertex_tag, subdimple->ActiveLevelIndex(), P, initial_edge_capacity, initial_face_capacity); + subdimple->AddVertexToLevel(v); + return v; +} + + class ON_SubDEdge* ON_SubDimple::AddEdge( - ON_SubD::EdgeTag edge_tag, + ON_SubD::EdgeTag edge_tag, ON_SubDVertex* v0, double v0_sector_weight, ON_SubDVertex* v1, double v1_sector_weight - ) +) +{ + return AddEdge( + 0U, + edge_tag, + v0, + v0_sector_weight, + v1, + v1_sector_weight, + 0U + ); +} + +class ON_SubDEdge* ON_SubDimple::AddEdge( + unsigned int candidate_edge_id, + ON_SubD::EdgeTag edge_tag, + ON_SubDVertex* v0, + double v0_sector_weight, + ON_SubDVertex* v1, + double v1_sector_weight, + unsigned initial_face_capacity +) { if ( false == ON_SubDSectorType::IsValidSectorCoefficientValue(v0_sector_weight,true) ) return ON_SUBD_RETURN_ERROR(nullptr); @@ -6219,7 +6649,7 @@ class ON_SubDEdge* ON_SubDimple::AddEdge( v1_sector_weight = ON_SubDSectorType::IgnoredSectorCoefficient; } - class ON_SubDEdge* e = AllocateEdge(edge_tag); + class ON_SubDEdge* e = AllocateEdge(candidate_edge_id,edge_tag, 0, 0); if ( nullptr == e) return ON_SUBD_RETURN_ERROR(nullptr); @@ -6251,6 +6681,11 @@ class ON_SubDEdge* ON_SubDimple::AddEdge( if ( nullptr == AddEdgeToLevel(e) ) return ON_SUBD_RETURN_ERROR(nullptr); + if (initial_face_capacity > 2) + { + m_heap.GrowEdgeFaceArray(e, initial_face_capacity); + } + return e; } @@ -6413,17 +6848,34 @@ ON_SubDEdge* ON_SubD::AddEdgeWithSectorCoefficients( return ON_SUBD_RETURN_ERROR(nullptr); } +class ON_SubDEdge* ON_SubD::AddEdgeForExperts( + unsigned int candidate_edge_id, + ON_SubD::EdgeTag edge_tag, + class ON_SubDVertex* v0, + double v0_sector_coefficient, + class ON_SubDVertex* v1, + double v1_sector_coefficient, + unsigned int initial_face_capacity +) +{ + ON_SubDimple* subdimple = SubDimple(true); + if (nullptr != subdimple) + return subdimple->AddEdge( candidate_edge_id, edge_tag, v0, v0_sector_coefficient, v1, v1_sector_coefficient, initial_face_capacity); + return ON_SUBD_RETURN_ERROR(nullptr); +} + + class ON_SubDFace* ON_SubDimple::AddFace( unsigned int edge_count, const ON_SubDEdgePtr* edge ) { - return AddFace(nullptr, edge_count, edge); + return AddFace( 0U, edge_count, edge); } class ON_SubDFace* ON_SubDimple::AddFace( - const ON_SubDFace* candidate_face, + unsigned int candidate_face_id, unsigned int edge_count, const ON_SubDEdgePtr* edge ) @@ -6450,7 +6902,7 @@ class ON_SubDFace* ON_SubDimple::AddFace( } } - ON_SubDFace* f = AllocateFace(candidate_face); + ON_SubDFace* f = AllocateFace( candidate_face_id, 0, 0); if ( nullptr == f) return ON_SUBD_RETURN_ERROR(nullptr); f->SetSubdivisionLevel(f_level); @@ -6703,14 +7155,14 @@ class ON_SubDFace* ON_SubD::AddFace( } -class ON_SubDFace* ON_SubD::AddFace( - const ON_SubDFace* candidate_face, +class ON_SubDFace* ON_SubD::AddFaceForExperts( + unsigned int candiate_face_id, const ON_SubDEdgePtr* edge, unsigned int edge_count ) { ON_SubDimple* subdimple = SubDimple(true); - return (nullptr != subdimple) ? subdimple->AddFace(candidate_face, edge_count, edge) : nullptr; + return (nullptr != subdimple) ? subdimple->AddFace( candiate_face_id, edge_count, edge) : nullptr; } bool ON_SubD::AddFaceEdgeConnection( @@ -7192,6 +7644,24 @@ bool ON_SubDComponentBase::SetMark( return m_status.SetRuntimeMark(bMark); } + +ON__UINT8 ON_SubDComponentBase::MarkBits() const +{ + return m_status.MarkBits(); +} + +ON__UINT8 ON_SubDComponentBase::SetMarkBits( + ON__UINT8 mark_bits +) const +{ + return m_status.SetMarkBits(mark_bits); +} + +ON__UINT8 ON_SubDComponentBase::ClearMarkBits() const +{ + return m_status.SetMarkBits(0); +} + bool ON_SubDComponentPtr::IsActive() const { const ON_SubDComponentBase* c = this->ComponentBase(); @@ -7319,13 +7789,16 @@ static void Internal_ClearFaceNeighborhoodCache(const ON_SubDFace* face) // Clear cached values for every component associated with this face. face->ClearSavedSubdivisionPoints(); const ON_SubDEdgePtr* eptr = face->m_edge4; - for (unsigned int efi = 0; efi < face->m_edge_count; efi++) + for (unsigned int efi = 0; efi < face->m_edge_count; ++efi, ++eptr) { if (4 == efi) { eptr = face->m_edgex; - if ( nullptr == eptr) + if ( nullptr == eptr || face->m_edgex_capacity < face->m_edge_count - 4) + { + ON_SUBD_ERROR("Invalid face edge count or edgex information."); break; + } } const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(eptr->m_ptr); if (nullptr != edge) @@ -7339,7 +7812,6 @@ static void Internal_ClearFaceNeighborhoodCache(const ON_SubDFace* face) vertex->ClearSavedSubdivisionPoints(); } } - eptr++; } } @@ -8885,6 +9357,10 @@ bool ON_SubDimple::LocalSubdivide( // Get face subdivision points ON_SimpleArray faces(face_count); ON_SimpleArray face_points(face_count); + + // this subd is being modifed. + ChangeContentSerialNumber(false); + for (const ON_SubDFace* f0 = level0.m_face[0]; nullptr != f0; f0 = f0->m_next_face) { if (false == f0->m_status.RuntimeMark()) @@ -8957,8 +9433,8 @@ bool ON_SubDimple::LocalSubdivide( } // save face status and candidate_face + unsigned int candidate_face_id = f->m_id; const ON_ComponentStatus fstatus = f->m_status; - const ON_SubDFace* candidate_face = f; for (unsigned fei = 0; fei < e_count; ++fei) { @@ -9014,8 +9490,8 @@ bool ON_SubDimple::LocalSubdivide( eptrs[fei], ON_SubDEdgePtr::Create(r[1],1) }; - ON_SubDFace* q = AddFace(candidate_face, 4, qbdry); - candidate_face = nullptr; + ON_SubDFace* q = AddFace(candidate_face_id, 4, qbdry); + candidate_face_id = 0; q->m_status = fstatus; } @@ -9026,6 +9502,9 @@ bool ON_SubDimple::LocalSubdivide( level0.ClearEvaluationCache(); level0.ClearEdgeFlags(); + if (nullptr != m_active_level) + m_active_level->UpdateAllTagsAndSectorCoefficients(true); + return true; } @@ -9055,6 +9534,10 @@ unsigned int ON_SubDimple::GlobalSubdivide() double P[3]; ON_SubDVertex* v; + // If the object is currently symmetric, a global subdivision will not break symmetry + const bool bChangePreservesSymmetry = true; + this->ChangeContentSerialNumber(bChangePreservesSymmetry); + // Add face points for (const ON_SubDFace* f0 = level0.m_face[0]; nullptr != f0; f0 = f0->m_next_face) { @@ -9296,7 +9779,6 @@ bool ON_SubD::LocalSubdivide( ptr_list.Append(f); } const bool rc = LocalSubdivide(ptr_list); - UpdateAllTagsAndSectorCoefficients(true); return rc; } @@ -10361,11 +10843,11 @@ const ON_SubDEdge* ON_SubDimple::SplitEdge( ON_SubDEdge* new_edge = nullptr; for (;;) { - new_vertex = AllocateVertex(vertex_tag, edge->SubdivisionLevel(), static_cast(vertex_location), 2, edge->m_face_count); + new_vertex = AllocateVertex( 0U, vertex_tag, edge->SubdivisionLevel(), static_cast(vertex_location), 2, edge->m_face_count); if (nullptr == new_vertex) break; - new_edge = AllocateEdge(edge_tag, edge->SubdivisionLevel(), edge->m_face_count); + new_edge = AllocateEdge( 0U, edge_tag, edge->SubdivisionLevel(), edge->m_face_count); if (nullptr == new_edge) break; @@ -11250,6 +11732,14 @@ bool ON_SubD::DeleteComponents( return DeleteComponents(cptr_list.Array(),cptr_list.UnsignedCount(),false); } +bool ON_SubD::DeleteComponents( + const ON_SimpleArray& cptr_list, + bool bMarkDeletedFaceEdges +) +{ + return DeleteComponents(cptr_list.Array(), cptr_list.UnsignedCount(), bMarkDeletedFaceEdges); +} + bool ON_SubD::DeleteComponents( const ON_SubDComponentPtr* cptr_list, size_t cptr_count, @@ -11437,6 +11927,40 @@ bool ON_SubD::DeleteComponentsForExperts( return (deleted_component_count > 0); } +bool ON_SubD::DeleteMarkedComponents( + bool bDeleteMarkedComponents, + ON__UINT8 mark_bits, + bool bMarkDeletedFaceEdges +) +{ + ON_SimpleArray cptr_list; + GetMarkedComponents(bDeleteMarkedComponents, mark_bits, true, true, true, cptr_list); + return DeleteComponents( + cptr_list.Array(), + cptr_list.UnsignedCount(), + bMarkDeletedFaceEdges + ); +} + +bool ON_SubD::DeleteMarkedComponentsForExperts( + bool bDeleteMarkedComponents, + ON__UINT8 mark_bits, + bool bDeleteIsolatedEdges, + bool bUpdateTagsAndCoefficients, + bool bMarkDeletedFaceEdges +) +{ + ON_SimpleArray cptr_list; + GetMarkedComponents(bDeleteMarkedComponents, mark_bits, true, true, true, cptr_list); + return DeleteComponentsForExperts( + cptr_list.Array(), + cptr_list.UnsignedCount(), + bDeleteIsolatedEdges, + bUpdateTagsAndCoefficients, + bMarkDeletedFaceEdges + ); +} + unsigned int ON_SubDLevel::UpdateEdgeTags( bool bUnsetEdgeTagsOnly ) @@ -11558,48 +12082,11 @@ unsigned int ON_SubDLevel::UpdateVertexTags( continue; } - const unsigned int edge_count = vertex->m_edge_count; - unsigned int creased_edge_count = 0; - unsigned int sharp_edge_count = 0; - for (unsigned int vei = 0; vei < edge_count; vei++) - { - const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(vertex->m_edges[vei].m_ptr); - if (nullptr == edge) - { - ON_SUBD_ERROR("nullptr vertex->m_edges[] values"); - continue; - } - if ( ON_SubD::EdgeTag::Crease == edge->m_edge_tag || 2 != edge->m_face_count ) - creased_edge_count++; - else if (((ON_SubD::EdgeTag)3) == edge->m_edge_tag) // ON_SubD::EdgeTag::Sharp - { - ON_SUBD_ERROR("ON_SubD::EdgeTag::Sharp is not valid in this version of opennurbs."); - sharp_edge_count++; - } + const ON_SubD::VertexTag vertex_tag1 = vertex->SuggestedVertexTag(true, false); + if (ON_SubD::VertexTag::Unset == vertex_tag1) + continue; - // NOTE: - // edges tagged as ON_SubD::EdgeTag::Unset with two faces - // ending at a vertex with 3 or more edges - // will be tagged as smooth in subsequent passes - // once this vertex is tagged as smooth. - } - - - ON_SubD::VertexTag vertex_tag1 = vertex_tag0; - if ( (creased_edge_count+sharp_edge_count) >= 2 ) - { - if ( ON_SubD::VertexTag::Corner != vertex_tag0 ) - vertex_tag1 = ON_SubD::VertexTag::Crease; - } - else if ( edge_count >= 2 ) - { - if ( 1 == creased_edge_count && 0 == sharp_edge_count ) - vertex_tag1 = ON_SubD::VertexTag::Dart; - else - vertex_tag1 = ON_SubD::VertexTag::Smooth; - } - - if (vertex_tag0 != vertex_tag1) + if ( vertex_tag0 != vertex_tag1) { vertex->m_vertex_tag = vertex_tag1; vertex_change_count++; @@ -12009,7 +12496,7 @@ unsigned int ON_SubDimple::DeleteComponents( } } - ChangeContentSerialNumber(); + ChangeContentSerialNumber(false); return deleted_component_count; } @@ -12091,6 +12578,147 @@ unsigned int ON_SubD::ClearComponentMarks( return clear_count; } +unsigned int ON_SubD::ClearGroupIds() const +{ + return ClearComponentGroupIds(true, true, true); +} + +unsigned int ON_SubD::ClearVertexGroupIds() const +{ + return ClearComponentGroupIds(true, false, false); +} + +unsigned int ON_SubD::ClearEdgeGroupIds() const +{ + return ClearComponentGroupIds(false, true, false); +} + +unsigned int ON_SubD::ClearFaceGroupIds() const +{ + return ClearComponentGroupIds(false, false, true); +} + +unsigned int ON_SubD::ClearComponentGroupIds( + bool bClearVertexGroupIds, + bool bClearEdgeGroupIds, + bool bClearFaceGroupIds +) const +{ + unsigned int clear_count = 0; + + if (bClearVertexGroupIds) + { + ON_SubDVertexIterator vit(*this); + for (const ON_SubDVertex* v = vit.FirstVertex(); nullptr != v; v = vit.NextVertex()) + { + if (0 != v->m_group_id) + { + v->m_group_id = 0; + clear_count++; + } + } + } + + if (bClearEdgeGroupIds) + { + ON_SubDEdgeIterator eit(*this); + for (const ON_SubDEdge* e = eit.FirstEdge(); nullptr != e; e = eit.NextEdge()) + { + if (0 != e->m_group_id) + { + e->m_group_id = 0; + clear_count++; + } + } + } + + if (bClearFaceGroupIds) + { + ON_SubDFaceIterator fit(*this); + for (const ON_SubDFace* f = fit.FirstFace(); nullptr != f; f = fit.NextFace()) + { + if (0 != f->m_group_id) + { + f->m_group_id = 0; + clear_count++; + } + } + } + + return clear_count; +} + + +unsigned int ON_SubD::ClearMarkBits() const +{ + return ClearComponentMarkBits(true, true, true); +} + +unsigned int ON_SubD::ClearVertexMarkBits() const +{ + return ClearComponentMarkBits(true, false, false); +} + +unsigned int ON_SubD::ClearEdgeMarkBits() const +{ + return ClearComponentMarkBits(false, true, false); +} + +unsigned int ON_SubD::ClearFaceMarkBits() const +{ + return ClearComponentMarkBits(false, false, true); +} + +unsigned int ON_SubD::ClearComponentMarkBits( + bool bClearVertexMarkBits, + bool bClearEdgeMarkBits, + bool bClearFaceMarkBits +) const +{ + unsigned int clear_count = 0; + + if (bClearVertexMarkBits) + { + ON_SubDVertexIterator vit(*this); + for (const ON_SubDVertex* v = vit.FirstVertex(); nullptr != v; v = vit.NextVertex()) + { + if (0 != v->m_status.MarkBits()) + { + v->m_status.SetMarkBits(0); + clear_count++; + } + } + } + + if (bClearEdgeMarkBits) + { + ON_SubDEdgeIterator eit(*this); + for (const ON_SubDEdge* e = eit.FirstEdge(); nullptr != e; e = eit.NextEdge()) + { + if (0 != e->m_status.MarkBits()) + { + e->m_status.SetMarkBits(0); + clear_count++; + } + } + } + + if (bClearFaceMarkBits) + { + ON_SubDFaceIterator fit(*this); + for (const ON_SubDFace* f = fit.FirstFace(); nullptr != f; f = fit.NextFace()) + { + if (0 != f->m_status.MarkBits()) + { + f->m_status.SetMarkBits(0); + clear_count++; + } + } + } + + return clear_count; +} + unsigned int ON_SubD::UnselectComponents( bool bUnselectAllVertices, bool bUnselectAllEdges, @@ -12162,7 +12790,59 @@ unsigned int ON_SubD::SetComponentMarks( return set_count; } +unsigned int ON_SubD::GetMarkedComponents( + bool bAddMarkedComponents, + ON__UINT8 mark_bits, + bool bIncludeVertices, + bool bIncludeEdges, + bool bIncludeFaces, + ON_SimpleArray< ON_SubDComponentPtr >& component_list +) const +{ + bAddMarkedComponents = bAddMarkedComponents ? true : false; // so we can use == on boolean values + unsigned int mark_count = 0; + if (bIncludeVertices) + { + ON_SubDVertexIterator vit(*this); + for (const ON_SubDVertex* v = vit.FirstVertex(); nullptr != v; v = vit.NextVertex()) + { + if (bAddMarkedComponents == v->m_status.IsMarked(mark_bits)) + { + component_list.Append(v->ComponentPtr()); + mark_count++; + } + } + } + + if (bIncludeEdges) + { + ON_SubDEdgeIterator eit(*this); + for (const ON_SubDEdge* e = eit.FirstEdge(); nullptr != e; e = eit.NextEdge()) + { + if (bAddMarkedComponents == e->m_status.IsMarked(mark_bits)) + { + component_list.Append(e->ComponentPtr()); + mark_count++; + } + } + } + + if (bIncludeFaces) + { + ON_SubDFaceIterator fit(*this); + for (const ON_SubDFace* f = fit.FirstFace(); nullptr != f; f = fit.NextFace()) + { + if (bAddMarkedComponents == f->m_status.IsMarked(mark_bits)) + { + component_list.Append(f->ComponentPtr()); + mark_count++; + } + } + } + + return mark_count; +} unsigned int ON_SubD::GetMarkedComponents( bool bIncludeVertices, @@ -12474,9 +13154,16 @@ static unsigned int Internal_MarkStuffAndMaybeMoveVertices( } } } + else if ( + bTransform && nullptr != xform + && (ON_SubDComponentLocation::Surface == component_location || ON_SubDComponentLocation::Unset == component_location) + && 1 == cptr_count + && nullptr != cptr_list[0].Vertex() + ) + { + } else { - for (size_t i = 0; i < cptr_count; i++) { switch (cptr_list[i].ComponentType()) @@ -12505,34 +13192,42 @@ static unsigned int Internal_MarkStuffAndMaybeMoveVertices( if (nullptr == e) continue; ++list_edge_count; - if (e->m_status.RuntimeMark()) + if (e->Mark()) continue; if (bTransform) { - e->m_status.SetRuntimeMark(); + e->SetMark(); for (unsigned int evi = 0; evi < 2; ++evi) { const ON_SubDVertex* v = e->m_vertex[evi]; - if (nullptr != v && false == v->m_status.RuntimeMark()) + if (nullptr != v && false == v->Mark()) { - v->m_status.SetRuntimeMark(); + v->SetMark(); const_cast(v)->Transform(false, *xform); ++marked_vertex_count; } } } - else if (bExtrudeBoundaries && 1 == e->m_face_count && nullptr != e->m_face2[0].Face()) - ++potential_isolated_edge_count; + else + { + if (bExtrudeBoundaries && 1 == e->m_face_count && nullptr != e->m_face2[0].Face()) + { + // It's a boundary edge and we will mark it and its vertices later after we make sure + // a face touching this edge isn't in the cptr_list[]. + ++potential_isolated_edge_count; + } + // otherwise ignore interior edges + } } break; case ON_SubDComponentPtr::Type::Face: { const ON_SubDFace* f = cptr_list[i].Face(); - if (nullptr != f) + if (nullptr != f && false == f->Mark()) { ++list_face_count; - f->m_status.SetRuntimeMark(); + f->SetMark(); const unsigned int face_vertex_count = f->m_edge_count; for (unsigned int fvi = 0; fvi < face_vertex_count; ++fvi) { @@ -12569,20 +13264,20 @@ static unsigned int Internal_MarkStuffAndMaybeMoveVertices( if (nullptr == e) continue; - if (e->m_status.RuntimeMark()) + if (e->Mark()) continue; // this edge us part of a boundary belonging to a face in cptr_list[] if (1 == e->m_face_count && nullptr != e->m_face2[0].Face()) { // this boundary edge was explicitly picked its attached face was not picked. // It will be extruded to a face. - e->m_status.SetRuntimeMark(); + e->SetMark(); for (unsigned int evi = 0; evi < 2; ++evi) { const ON_SubDVertex* v = e->m_vertex[evi]; - if (nullptr != v && false == v->m_status.RuntimeMark()) + if (nullptr != v && false == v->Mark()) { - v->m_status.SetRuntimeMark(); + v->SetMark(); ++marked_vertex_count; } } @@ -12591,10 +13286,10 @@ static unsigned int Internal_MarkStuffAndMaybeMoveVertices( } } } - } - const_cast(subd).ChangeContentSerialNumberForExperts(); + if ( bTransform ) + const_cast(subd).ChangeContentSerialNumberForExperts(false); return marked_vertex_count; } @@ -12701,9 +13396,7 @@ unsigned int ON_SubD::ExtrudeComponents( { const bool bExtrudeBoundaries = true; const bool bPermitNonManifoldEdgeCreation = false; - const ON_SubD::EdgeTag original_edge_tag = ON_SubD::EdgeTag::Unset; - const ON_SubD::EdgeTag moved_edge_tag = ON_SubD::EdgeTag::Unset; - return ExtrudeComponents(xform, ci_list, ci_count, bExtrudeBoundaries, bPermitNonManifoldEdgeCreation, original_edge_tag, moved_edge_tag); + return ExtrudeComponents(xform, ci_list, ci_count, bExtrudeBoundaries, bPermitNonManifoldEdgeCreation); } unsigned int ON_SubD::ExtrudeComponents( @@ -12711,9 +13404,7 @@ unsigned int ON_SubD::ExtrudeComponents( const ON_COMPONENT_INDEX* ci_list, size_t ci_count, bool bExtrudeBoundaries, - bool bPermitNonManifoldEdgeCreation, - ON_SubD::EdgeTag original_edge_tag, - ON_SubD::EdgeTag moved_edge_tag + bool bPermitNonManifoldEdgeCreation ) { if ( @@ -12733,128 +13424,777 @@ unsigned int ON_SubD::ExtrudeComponents( cptr_list.Array(), cptr_list.UnsignedCount(), bExtrudeBoundaries, - bPermitNonManifoldEdgeCreation, - original_edge_tag, - moved_edge_tag + bPermitNonManifoldEdgeCreation ); } -class ON_Internal_ExtrudedVertexPair +/* +ON_Internal_ExtrudedVertex manages a vertex that is extruded into a "side" edge during the extrusion process. +*/ +class ON_Internal_ExtrudedVertex { public: - ON_Internal_ExtrudedVertexPair() = default; - ~ON_Internal_ExtrudedVertexPair() = default; - ON_Internal_ExtrudedVertexPair(const ON_Internal_ExtrudedVertexPair&) = default; - ON_Internal_ExtrudedVertexPair& operator=(const ON_Internal_ExtrudedVertexPair&) = default; + ON_Internal_ExtrudedVertex() = default; + ~ON_Internal_ExtrudedVertex() = default; + ON_Internal_ExtrudedVertex(const ON_Internal_ExtrudedVertex&) = default; + ON_Internal_ExtrudedVertex& operator=(const ON_Internal_ExtrudedVertex&) = default; - static const ON_Internal_ExtrudedVertexPair Unset; + static const ON_Internal_ExtrudedVertex Unset; - // the marked vertex was in the original subd and will be moved. - ON_SubDVertex* m_marked_vertex = nullptr; + ///////////////////////////////////////////////////////////// + // + // Up to 2 extruded edges that share this extruded vertex. + // In rare cases there may be more. This information is used only + // to speed up the setting of m_side_group_id. + // + class ON_Internal_ExtrudedEdge* m_extruded_edges[2] = {}; + unsigned char m_extruded_edges_count = 0; // 0, 1, 2, or 3. 3 means 3 or more - // the unmarked vertex replaces the marked vertex at the original location. - ON_SubDVertex* m_unmarked_vertex = nullptr; + ///////////////////////////////////////////////////////////// + // + // State of the vertex before the extrusion was applied + // - // from new vertex to original vertex - ON_SubDEdge* m_new_side = nullptr; + // tag the original vertex had before the extrusion was performed. + ON_SubD::VertexTag m_initial_vertex_tag = ON_SubD::VertexTag::Unset; - static int CompareMarkedVertexId( - const ON_Internal_ExtrudedVertexPair* lhs, - const ON_Internal_ExtrudedVertexPair* rhs - ); -}; + // in complicated cases when the initial vertex is a crease/corner, m_connecting_edge_tag may be set to crease. + ON_SubD::EdgeTag m_connecting_edge_tag = ON_SubD::EdgeTag::Unset; -const ON_Internal_ExtrudedVertexPair ON_Internal_ExtrudedVertexPair::Unset ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_Internal_ExtrudedVertexPair); + // id of the original vertex - used to sort and search an array of ON_Internal_ExtrudedVertex elements. + unsigned int m_initial_vertex_id = 0; + //// The face counts do not include the side faces created during the extrusion process. + //unsigned short m_moved_face_count = 0; + //unsigned short m_stationary_face_count = 0; -class ON_Internal_ExtrudedSide -{ -public: - ON_Internal_ExtrudedSide() = default; - ~ON_Internal_ExtrudedSide() = default; - ON_Internal_ExtrudedSide(const ON_Internal_ExtrudedSide&) = default; - ON_Internal_ExtrudedSide& operator=(const ON_Internal_ExtrudedSide&) = default; + //// Edge counts do not include the side edge "m_connecting_edge" that is created during the extrusion process. + //// There are three sets of edges + //// Existing edges that will be extruded into side faces. + //// Existing edges that will be moved during the extrusion process. + //// Existing edges that will be stationary during the extrusion process. + //unsigned short m_extruded_edge_count = 0; // these are edges in the new_sides[] array + //unsigned short m_wire_edge_count = 0; // moved or stationary + //unsigned short m_moved_crease_edge_count = 0; + //unsigned short m_moved_smooth_edge_count = 0; + //unsigned short m_stationary_crease_edge_count = 0; + //unsigned short m_stationary_smooth_edge_count = 0; + + ///////////////////////////////////////////////////////////// + // + // Additional information that will be created or modified during the extrusion. + // - static const ON_Internal_ExtrudedSide Unset; + // Used to sort the sides into groups of edges that are connected + unsigned m_side_group_id = 0; - bool m_bExtrudedBoundaryEdge = false; - bool m_bHasMovedFace = false; // true if the edge touches a fae that will be moved. - bool m_bHasStationaryFace = false; // true if the edge touches a face that will remain stationary. - ON_SubD::EdgeTag m_original_marked_edge_tag = ON_SubD::EdgeTag::Unset; + // This is the vertex in the original object. (This vertex is moved). + ON_SubDVertex* m_original_vertex = nullptr; - ON_SubD::EdgeTag MarkedEdgeTag() const + // This vertex is new. It is a copy of the original vertex and remains at the original location. + ON_SubDVertex* m_copied_vertex = nullptr; + + // m_connecting_edge begins at m_copied_vertex and terminates at m_original_vertex; + ON_SubDEdge* m_connecting_edge = nullptr; + + + bool SetFromInitialVertex(ON_SubDVertex* initial_vertex) { - // this tag is calculated just before the side face is made - if ( m_bExtrudedBoundaryEdge ) + *this = ON_Internal_ExtrudedVertex::Unset; + + if ( nullptr == initial_vertex || 0 == initial_vertex->m_id ) + return false; + + // validate initial_vertex topology information so subsequent code assumptions are met. + if (initial_vertex->m_edge_count <= 0) + return false; + if (initial_vertex->m_edge_count > initial_vertex->m_edge_capacity) + return false; + if (nullptr == initial_vertex->m_edges) + return false; + for (unsigned short vei = 0; vei < initial_vertex->m_edge_count; ++vei) { - const ON_SubDVertex* v0 = nullptr != m_marked_edge ? m_marked_edge->m_vertex[0] : nullptr; - const ON_SubDVertex* v1 = nullptr != m_marked_edge ? m_marked_edge->m_vertex[1] : nullptr; - if ( - m_bHasMovedFace - && false == m_bHasStationaryFace - && nullptr != v0 - && nullptr != v1 - ) + const ON_SubDEdgePtr eptr = initial_vertex->m_edges[vei]; + const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(eptr.m_ptr); + if (nullptr == e || nullptr == e->m_vertex[0] || nullptr == e->m_vertex[1]) + return false; + if (e->m_vertex[0]->m_id == e->m_vertex[1]->m_id) + return false; + if (initial_vertex != e->m_vertex[ON_SUBD_EDGE_DIRECTION(eptr.m_ptr)]) + return false; + } + + if (initial_vertex->m_face_count > 0) + { + if (initial_vertex->m_face_count > initial_vertex->m_face_capacity) + return false; + if (nullptr == initial_vertex->m_faces) + return false; + } + + m_initial_vertex_tag = initial_vertex->m_vertex_tag; + m_initial_vertex_id = initial_vertex->m_id; + m_original_vertex = initial_vertex; + + return true; + } + + bool SetConnectingEdgeTag(); + + bool ExtrudeVertex(ON_SubD& subd, const ON_Xform& xform) + { + if (nullptr != m_copied_vertex) + return ON_SUBD_RETURN_ERROR(false); + + if (nullptr == m_original_vertex) + return ON_SUBD_RETURN_ERROR(false); + + //bool bMarkedInteriorCrease = false; + //bool bUnmarkedInteriorCrease = false; + //for (unsigned short vei = 0; vei < m_original_vertex->m_edge_count; ++vei) + //{ + // const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(m_original_vertex->m_edges[vei].m_ptr); + // if (nullptr == e || 2 != e->m_face_count) + // continue; + // const ON_SubDFace* f[2] = { ON_SUBD_FACE_POINTER(e->m_face2[0].m_ptr), ON_SUBD_FACE_POINTER(e->m_face2[1].m_ptr) }; + // if (nullptr == f[0] || nullptr == f[1]) + // continue; + // const bool bFaceMark = f[0]->Mark(); + // if (bFaceMark == f[1]->Mark()) + // { + // if (bFaceMark) + // { + // bMarkedInteriorCrease = true; + // if (bUnmarkedInteriorCrease) + // break; + // } + // else + // { + // bUnmarkedInteriorCrease = true; + // if (bMarkedInteriorCrease) + // break; + // } + // } + //} + + const ON_3dPoint P = m_original_vertex->ControlNetPoint(); + for (;;) + { + // transform the original vertex + m_original_vertex->m_vertex_tag = ON_SubD::VertexTag::Unset; + + if (false == m_original_vertex->Transform(false, xform)) + break; + + m_copied_vertex = subd.AddVertex(ON_SubD::VertexTag::Unset, P); + if (nullptr == m_copied_vertex) + break; + + const unsigned short face_count = m_original_vertex->m_face_count; + if (face_count > 0 && false == subd.GrowVertexFaceArray(m_copied_vertex, face_count)) + break; + + if (false == subd.GrowVertexEdgeArray(m_copied_vertex, m_original_vertex->m_edge_count + 1)) + break; + + // edge from m_copied_vertex (stationary) to m_original_vertex (moved). + m_connecting_edge = subd.AddEdge(m_connecting_edge_tag, m_copied_vertex, m_original_vertex); + if (nullptr == m_connecting_edge) + break; + + //if (bMarkedInteriorCrease && bUnmarkedInteriorCrease) + // m_connecting_edge->m_edge_tag = ON_SubD::EdgeTag::Crease; + + for (unsigned short vei = 0; vei < m_original_vertex->m_edge_count; ++vei) { - return - (v0->IsDartOrCreaseOrCorner() && v1->IsDartOrCreaseOrCorner()) - ? ON_SubD::EdgeTag::SmoothX - : ON_SubD::EdgeTag::Smooth; + ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(m_original_vertex->m_edges[vei].m_ptr); + if (nullptr != e) + e->UnsetSectorCoefficientsForExperts(); + } + + return true; + } + + if (nullptr != m_copied_vertex) + { + ON_SubDComponentPtr cptr = m_copied_vertex->ComponentPtr(); + subd.DeleteComponentsForExperts(&cptr, 1, false, false, false); + m_copied_vertex = nullptr; + } + + ON_SUBD_ERROR("Unable to extrude m_original_vertex"); + m_original_vertex->m_vertex_tag = this->m_initial_vertex_tag; + m_original_vertex->SetControlNetPoint(P,false); + return false; + } + + bool AttachUnmarkedFacesToCopiedVertex() + { + if (nullptr == m_copied_vertex) + return ON_SUBD_RETURN_ERROR(false); + if (0 != m_copied_vertex->m_face_count) + return ON_SUBD_RETURN_ERROR(false); + if (nullptr == m_original_vertex) + return ON_SUBD_RETURN_ERROR(false); + if (0 == m_original_vertex->m_face_count) + return true; // wire edge case + + const unsigned short face_count = m_original_vertex->m_face_count; + + if (face_count > m_original_vertex->m_face_capacity) + return ON_SUBD_RETURN_ERROR(false); + if (face_count > m_copied_vertex->m_face_capacity) + return ON_SUBD_RETURN_ERROR(false); + + // marked faces remain attached to m_original_vertex + // unmarked faces are attached to m_copied_vertex + m_original_vertex->m_face_count = 0; + for (unsigned short vfi = 0; vfi < face_count; vfi++) + { + const ON_SubDFace* f = m_original_vertex->m_faces[vfi]; + if (nullptr == f) + continue; + ON_SubDVertex* v + = (f->Mark()) + ? m_original_vertex + : m_copied_vertex; + v->m_faces[v->m_face_count] = f; + v->m_face_count++; + } + for (unsigned short vfi = m_original_vertex->m_face_count; vfi < face_count; ++vfi) + m_original_vertex->m_faces[vfi] = nullptr; + + return true; + } + + + void UndoAttachUnmarkedFacesToCopiedVertex() + { + // This is used to resort the subd to a somewhat valid state after a critical error occurs + if (nullptr == m_copied_vertex) + return; + if (0 == m_copied_vertex->m_face_count) + return; + if (nullptr == m_original_vertex) + return; + // Move faces from m_copied_vertex back to m_original_vertex (which is where they started). + for (unsigned short vfi = 0; vfi < m_copied_vertex->m_face_count; vfi++) + { + const ON_SubDFace* f = m_copied_vertex->m_faces[vfi]; + if (nullptr == f) + continue; + m_copied_vertex->m_faces[vfi] = nullptr; + if (m_original_vertex->m_face_count < m_original_vertex->m_face_capacity) + { + m_original_vertex->m_faces[m_original_vertex->m_face_count] = f; + m_original_vertex->m_face_count++; } } - - return m_original_marked_edge_tag; + m_copied_vertex->m_face_count = 0; } + + bool AddExtrudedEdgeReference( + class ON_Internal_ExtrudedEdge* extruded_edge, + bool bSetExtrudedEdgeToo + ); - ON_SubD::EdgeTag UnmarkedEdgeTag( - const ON_SubDVertex* v0, - const ON_SubDVertex* v1 - ) const + static int CompareInitialVertexId( + const ON_Internal_ExtrudedVertex* lhs, + const ON_Internal_ExtrudedVertex* rhs + ); + + void SetBothVertexTags(ON_SubD::VertexTag vertex_tag) { - // This tag is calculate before m_unmarked_edge is created. - if ( - m_bExtrudedBoundaryEdge - && m_bHasStationaryFace - && false == m_bHasMovedFace - && nullptr != v0 - && nullptr != v1 - ) + if (nullptr != m_original_vertex) + m_original_vertex->m_vertex_tag = vertex_tag; + if (nullptr != m_copied_vertex) + m_copied_vertex->m_vertex_tag = vertex_tag; + if (nullptr != m_connecting_edge) { - return - (v0->IsDartOrCreaseOrCorner() && v1->IsDartOrCreaseOrCorner()) - ? ON_SubD::EdgeTag::SmoothX - : ON_SubD::EdgeTag::Smooth; + switch (vertex_tag) + { + case ON_SubD::VertexTag::Unset: + m_connecting_edge->m_edge_tag = ON_SubD::EdgeTag::Unset; + break; + case ON_SubD::VertexTag::Smooth: + m_connecting_edge->m_edge_tag = ON_SubD::EdgeTag::Smooth; + break; + case ON_SubD::VertexTag::Crease: + m_connecting_edge->m_edge_tag = ON_SubD::EdgeTag::SmoothX; + break; + case ON_SubD::VertexTag::Corner: + m_connecting_edge->m_edge_tag = ON_SubD::EdgeTag::Crease; + break; + case ON_SubD::VertexTag::Dart: + m_connecting_edge->m_edge_tag = ON_SubD::EdgeTag::SmoothX; + break; + default: + m_connecting_edge->m_edge_tag = ON_SubD::EdgeTag::Unset; + break; + } } - - return - (ON_SubD::EdgeTag::Crease == m_original_marked_edge_tag) - ? ON_SubD::EdgeTag::Crease - : ON_SubD::EdgeTag::Unset - ; } - // the marked edge was in the original object and will be moved. - ON_SubDEdge* m_marked_edge = nullptr; + bool IsValidTopology(bool bCheckCopies) const; - // the unmarked edge replaces the marked edge at the original location. - ON_SubDEdge* m_unmarked_edge = nullptr; - - // start at new vertex and end at original vertex; - ON_SubDEdge* m_new_side0 = nullptr; - ON_SubDEdge* m_new_side1 = nullptr; - - ON_SubDFace* m_new_face = nullptr; }; -const ON_Internal_ExtrudedSide ON_Internal_ExtrudedSide::Unset ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_Internal_ExtrudedSide); +const ON_Internal_ExtrudedVertex ON_Internal_ExtrudedVertex::Unset ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_Internal_ExtrudedVertex); -int ON_Internal_ExtrudedVertexPair::CompareMarkedVertexId( - const ON_Internal_ExtrudedVertexPair* lhs, - const ON_Internal_ExtrudedVertexPair* rhs +/* +ON_Internal_ExtrudedVertex manages an edge that is extruded into a "side" face extrusion process. +*/ +class ON_Internal_ExtrudedEdge +{ +public: + ON_Internal_ExtrudedEdge() = default; + ~ON_Internal_ExtrudedEdge() = default; + ON_Internal_ExtrudedEdge(const ON_Internal_ExtrudedEdge&) = default; + ON_Internal_ExtrudedEdge& operator=(const ON_Internal_ExtrudedEdge&) = default; + + static const ON_Internal_ExtrudedEdge Unset; + + // tag m_original_edge had before the extrusion was performed. + ON_SubD::EdgeTag m_initial_edge_tag = ON_SubD::EdgeTag::Unset; + + unsigned int m_initial_vertex_id[2] = {}; + + unsigned int m_initial_edge_face_count = 0; + + // Used to sort the sides into groups of edges that are connected + unsigned m_side_group_id = 0; + + // This is the edge in the original object. (This edge is moved). + ON_SubDEdge* m_original_edge = nullptr; + + // This edge is new. It is a copy of the original edge and remains at the original location. + ON_SubDEdge* m_copied_edge = nullptr; + + // m_extruded_vertex[0]->m_connecting_edge begins at m_copied_edge->m_vertex[0] and terminates at m_original_edge->m_vertex[0] + // m_extruded_vertex[1]->m_connecting_edge begins at m_copied_edge->m_vertex[1] and terminates at m_original_edge->m_vertex[1] + ON_Internal_ExtrudedVertex* m_extruded_vertex[2] = {}; + + // This is the new "side" quad with boundary made from the 4 edges above. + ON_SubDFace* m_new_face = nullptr; + + static int CompareSideGroupId(const ON_Internal_ExtrudedEdge* lhs, const ON_Internal_ExtrudedEdge* rhs); + + bool SetFromInitialEdge( + ON_SubDEdge* initial_edge + ) + { + *this = ON_Internal_ExtrudedEdge::Unset; + + if (nullptr == initial_edge) + return false; + if (nullptr == initial_edge->m_vertex[0] || nullptr == initial_edge->m_vertex[1] || initial_edge->m_vertex[0]->m_id == initial_edge->m_vertex[1]->m_id) + return false; + + // validate edge / face topology information - corrupt information will cause great difficulties during the extrusion process + const ON_SubDFacePtr* fptr = initial_edge->m_face2; + for (unsigned short efi = 0; efi < initial_edge->m_face_count; ++efi, ++fptr) + { + if (2 == efi) + { + fptr = initial_edge->m_facex; + if (nullptr == fptr) + return false; // corrupt edge / face topology information + if (initial_edge->m_facex_capacity < initial_edge->m_face_count - 2) + return false; // corrupt edge / face topology information + } + const ON_SubDFace* f = ON_SUBD_FACE_POINTER(fptr->m_ptr); + if (nullptr == f || f->m_edge_count < 3) + return false; // corrupt edge / face topology information + + bool bFoundInitialEdge = false; + const ON_SubDEdgePtr* eptr = f->m_edge4; + for (unsigned short fei = 0; fei < f->m_edge_count; ++fei, ++eptr) + { + if (4 == fei) + { + eptr = f->m_edgex; + if (nullptr == eptr) + return false; // corrupt edge / face topology information + if (f->m_edgex_capacity < f->m_edge_count - 4) + return false; // corrupt edge / face topology information + } + const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(eptr->m_ptr); + if (nullptr == e) + return false; + if (nullptr == e->m_vertex[0] || nullptr == e->m_vertex[1] || e->m_vertex[0]->m_id == e->m_vertex[1]->m_id) + return false; + if (e == initial_edge) + { + if (bFoundInitialEdge) + return false; + bFoundInitialEdge = true; + if (ON_SUBD_EDGE_DIRECTION(eptr->m_ptr) != ON_SUBD_FACE_DIRECTION(fptr->m_ptr)) + return false; + } + } + if (false == bFoundInitialEdge) + return false; + } + + m_initial_edge_tag = initial_edge->m_edge_tag; + m_initial_vertex_id[0] = initial_edge->m_vertex[0]->m_id; + m_initial_vertex_id[1] = initial_edge->m_vertex[1]->m_id; + m_initial_edge_face_count = initial_edge->m_face_count; + m_original_edge = initial_edge; + + return true; + } + + ON_SubDFace* ExtrudeFace(ON_SubD& subd); + + const ON_Internal_ExtrudedVertex* ExtrudedVertex(ON__UINT_PTR evi) const + { + return (0 == evi || 1 == evi) ? m_extruded_vertex[evi] : nullptr; + } + + /// Returns "side" edge created by extruding the reference vertex + ON_SubDEdge* ConnectingEdge(ON__UINT_PTR evi) const + { + const ON_Internal_ExtrudedVertex* extruded_vertex = ExtrudedVertex(evi); + return (nullptr != extruded_vertex) ? extruded_vertex->m_connecting_edge : nullptr; + } + + ON_SubDEdge* CreateCopiedEdge(ON_SubD& subd) + { + if (nullptr != m_copied_edge) + return ON_SUBD_RETURN_ERROR(nullptr); + ON_SubDVertex* copied_vertex[2] = {}; + for (unsigned evi = 0; evi < 2; evi++) + { + if (nullptr == m_extruded_vertex[evi]) + return ON_SUBD_RETURN_ERROR(nullptr); + copied_vertex[evi] = m_extruded_vertex[evi]->m_copied_vertex; + if (nullptr == copied_vertex[evi]) + return ON_SUBD_RETURN_ERROR(nullptr); + } + m_copied_edge = subd.AddEdge(ON_SubD::EdgeTag::Unset, copied_vertex[0], copied_vertex[1]); + if (nullptr == m_copied_edge) + return ON_SUBD_RETURN_ERROR(nullptr); + m_original_edge->m_edge_tag = ON_SubD::EdgeTag::Unset; + return m_copied_edge; + } + + /// returns true if an unset vertex pair had its id set which means recursive id setting should continue. + bool SetSideGroupId(unsigned side_group_id) + { + if (m_side_group_id > 0) + return false; + bool rc = false; + m_side_group_id = side_group_id; + for (unsigned evi = 0; evi < 2; ++evi) + { + if (nullptr != m_extruded_vertex[evi] && 0 == m_extruded_vertex[evi]->m_side_group_id) + { + rc = true; + m_extruded_vertex[evi]->m_side_group_id = side_group_id; + } + } + return rc; + } + + /// Returns true if an unset vertex pair had its id set which means recursive id setting should continue. + bool SetSideGroupIdFromVertexPairs() + { + if (m_side_group_id > 0) + return false; + for (unsigned evi = 0; evi < 2; ++evi) + { + if (nullptr != m_extruded_vertex[evi] && m_extruded_vertex[evi]->m_side_group_id > 0) + return SetSideGroupId(m_extruded_vertex[evi]->m_side_group_id); + } + return false; + } + + bool InitialCrease() const + { + return (ON_SubD::EdgeTag::Crease == m_initial_edge_tag); + } + + bool InitialCreaseWithCorner() const + { + if (ON_SubD::EdgeTag::Crease == m_initial_edge_tag) + { + for (unsigned evi = 0; evi < 2; ++evi) + { + if (nullptr != m_extruded_vertex[evi] && ON_SubD::VertexTag::Corner == m_extruded_vertex[evi]->m_initial_vertex_tag) + return true; + } + } + return false; + } + + void SetBothEdgeTags(ON_SubD::EdgeTag edge_tag) + { + if (ON_SubD::EdgeTag::Crease == edge_tag) + { + if (nullptr != m_original_edge) + m_original_edge->m_edge_tag = ON_SubD::EdgeTag::Crease; + if (nullptr != m_copied_edge) + m_copied_edge->m_edge_tag = ON_SubD::EdgeTag::Crease; + for (unsigned evi = 0; evi < 2; ++evi) + { + if (nullptr != m_extruded_vertex[evi] && ON_SubD::EdgeTag::Unset == m_extruded_vertex[evi]->m_connecting_edge->m_edge_tag) + { + if (ON_SubD::VertexTag::Corner == m_extruded_vertex[evi]->m_initial_vertex_tag) + m_extruded_vertex[evi]->SetBothVertexTags(ON_SubD::VertexTag::Corner); + else if (nullptr != m_extruded_vertex[evi]->m_connecting_edge) + m_extruded_vertex[evi]->m_connecting_edge->m_edge_tag = ON_SubD::EdgeTag::Smooth; + } + } + } + } + + bool IsValidTopology( + bool bCheckCopies + ) const; +}; + +static bool Internal_IsNotValidExtrudedTopology() +{ + return ON_SUBD_RETURN_ERROR(false); +} + +bool ON_Internal_ExtrudedVertex::IsValidTopology(bool bCheckCopies) const +{ + if (nullptr == this->m_original_vertex) + return Internal_IsNotValidExtrudedTopology(); + if (this->m_initial_vertex_id != this->m_original_vertex->m_id) + return Internal_IsNotValidExtrudedTopology(); + if (this->m_extruded_edges_count == 0) + return Internal_IsNotValidExtrudedTopology(); + if (bCheckCopies && nullptr == this->m_copied_vertex) + return Internal_IsNotValidExtrudedTopology(); + for (unsigned vei = 0; vei < 2 && vei < this->m_extruded_edges_count; ++vei) + { + if (nullptr == this->m_extruded_edges[vei]) + return Internal_IsNotValidExtrudedTopology(); + const ON_Internal_ExtrudedEdge& extruded_edge = *this->m_extruded_edges[vei]; + if (nullptr == extruded_edge.m_original_edge) + return Internal_IsNotValidExtrudedTopology(); + const unsigned evi = (this->m_original_vertex == extruded_edge.m_original_edge->m_vertex[0]) ? 0 : 1; + if (this->m_initial_vertex_id != extruded_edge.m_initial_vertex_id[evi]) + return Internal_IsNotValidExtrudedTopology(); + for (unsigned k = 0; k < (bCheckCopies ? 2U : 1U); ++k) + { + const ON_SubDEdge* e = (0 == k) ? extruded_edge.m_original_edge : extruded_edge.m_copied_edge; + if (nullptr == e) + return Internal_IsNotValidExtrudedTopology(); + if (e->m_vertex[0] == e->m_vertex[1]) + return Internal_IsNotValidExtrudedTopology(); + const ON_SubDVertex* v = (0 == k) ? this->m_original_vertex : this->m_copied_vertex; + if (nullptr == v) + return Internal_IsNotValidExtrudedTopology(); + if (e->m_vertex[evi] != v) + return Internal_IsNotValidExtrudedTopology(); + } + if (extruded_edge.m_side_group_id != this->m_side_group_id) + return Internal_IsNotValidExtrudedTopology(); + } + + return true; +} + +bool ON_Internal_ExtrudedEdge::IsValidTopology( + bool bCheckCopies +) const +{ + if (nullptr == m_original_edge) + return Internal_IsNotValidExtrudedTopology(); + + if (bCheckCopies && nullptr == m_copied_edge) + return Internal_IsNotValidExtrudedTopology(); + + for (unsigned evi = 0; evi < 2; ++evi) + { + if (nullptr == this->m_extruded_vertex[evi]) + return Internal_IsNotValidExtrudedTopology(); + const ON_Internal_ExtrudedVertex& extruded_vertex = *this->m_extruded_vertex[evi]; + for (unsigned k = 0; k < (bCheckCopies ? 2U : 1U); ++k) + { + const ON_SubDEdge* e = (0 == k) ? this->m_original_edge : this->m_copied_edge; + if (nullptr == e) + return Internal_IsNotValidExtrudedTopology(); + const ON_SubDVertex* v = (0 == k) ? extruded_vertex.m_original_vertex : extruded_vertex.m_copied_vertex; + if (nullptr == v) + return Internal_IsNotValidExtrudedTopology(); + if (e->m_vertex[evi] != v) + return Internal_IsNotValidExtrudedTopology(); + if (v->EdgeArrayIndex(e) >= v->m_edge_count) + return Internal_IsNotValidExtrudedTopology(); + } + if (extruded_vertex.m_initial_vertex_id != extruded_vertex.m_original_vertex->m_id) + return Internal_IsNotValidExtrudedTopology(); + if (extruded_vertex.m_initial_vertex_id != this->m_initial_vertex_id[evi]) + return Internal_IsNotValidExtrudedTopology(); + if (this->m_side_group_id != extruded_vertex.m_side_group_id) + return Internal_IsNotValidExtrudedTopology(); + } + return true; +} + +bool ON_Internal_ExtrudedVertex::AddExtrudedEdgeReference( + class ON_Internal_ExtrudedEdge* extruded_edge, + bool bSetExtrudedEdgeToo ) { - const unsigned int lhs_id = lhs->m_marked_vertex->m_id; - const unsigned int rhs_id = rhs->m_marked_vertex->m_id; + if (nullptr == m_original_vertex || 0 == m_initial_vertex_id || m_initial_vertex_id != m_original_vertex->m_id) + return ON_SUBD_RETURN_ERROR(false); + + if (nullptr == extruded_edge || nullptr == extruded_edge->m_original_edge ) + return ON_SUBD_RETURN_ERROR(false); + + unsigned int evi; + + for (evi = 0; evi < 2; ++evi) + { + if (m_original_vertex == extruded_edge->m_original_edge->m_vertex[evi] && m_initial_vertex_id == extruded_edge->m_initial_vertex_id[evi]) + break; + } + if (evi > 1) + { + // edge and vertex are not attached + return ON_SUBD_RETURN_ERROR(false); + } + + if ( nullptr != extruded_edge->m_extruded_vertex[evi] && this != extruded_edge->m_extruded_vertex[evi]) + return ON_SUBD_RETURN_ERROR(false); + + switch (m_extruded_edges_count) + { + case 0: + m_extruded_edges[0] = extruded_edge; + m_extruded_edges_count = 1; + break; + + case 1: + if (m_extruded_edges[0] != extruded_edge) + { + m_extruded_edges[1] = extruded_edge; + m_extruded_edges_count = 2; + } + break; + + case 2: + if (m_extruded_edges[0] != extruded_edge && m_extruded_edges[1] != extruded_edge) + m_extruded_edges_count = 3; // rare case with 3 or more extruded edges sharing an extruded vertex + break; + + case 3: + // do nothing this is vertex will be extruded into a non-manifold "side" connecting edge + break; + + default: + // invalid value of m_extruded_edges_count + return ON_SUBD_RETURN_ERROR(false); + break; + } + + if (bSetExtrudedEdgeToo && nullptr == extruded_edge->m_extruded_vertex[evi]) + extruded_edge->m_extruded_vertex[evi] = this; + + return true; +} + +bool ON_Internal_ExtrudedVertex::SetConnectingEdgeTag() +{ + if (nullptr == m_original_vertex || 0 == m_initial_vertex_id) + return ON_SUBD_RETURN_ERROR(false); // to early + + if (nullptr != m_copied_vertex || nullptr != m_connecting_edge) + return ON_SUBD_RETURN_ERROR(false); // to late + + if (m_initial_vertex_id != m_original_vertex->m_id || m_initial_vertex_tag != m_original_vertex->m_vertex_tag || ON_SubD::EdgeTag::Unset != m_connecting_edge_tag) + return ON_SUBD_RETURN_ERROR(false); // corrupt information + + if (m_extruded_edges_count != 2) + { + // simple case + m_connecting_edge_tag = ON_SubD::EdgeTag::Crease; + return true; + } + + //if (ON_SubD::VertexTag::Corner == m_initial_vertex_tag) + //{ + // // simple case + // m_connecting_edge_tag = ON_SubD::EdgeTag::Crease; + // return true; + //} + + if (ON_SubD::VertexTag::Crease != m_initial_vertex_tag && ON_SubD::VertexTag::Corner != m_initial_vertex_tag) + return true; // leaving m_connecting_edge_tag unset for anything else works best + + + bool bMovedCrease = false; + bool bStationaryCrease = false; + + const ON_SubDEdge* extruded_edge[2] = {}; + for (unsigned vei = 0; vei < 2; ++vei) + { + extruded_edge[vei] = (nullptr != m_extruded_edges[vei]) ? m_extruded_edges[vei]->m_original_edge : nullptr; + if (nullptr == extruded_edge[vei]) + return ON_SUBD_RETURN_ERROR(false); // corrupt information + if (1 == extruded_edge[vei]->m_face_count) + { + if (extruded_edge[vei]->MarkedFaceCount() > 0) + bStationaryCrease = true; // the boundary (crease) will not move but the attached face does move + else + { + // the boundary (crease) will move but the attached face does not move + if (ON_SubD::VertexTag::Corner == m_initial_vertex_tag) + { + m_connecting_edge_tag = ON_SubD::EdgeTag::Crease; + return true; + } + bMovedCrease = true; + } + } + } + + // determine vertex topology and edge demographics + for (unsigned short vei = 0; vei < m_original_vertex->m_edge_count; ++vei) + { + const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(m_original_vertex->m_edges[vei].m_ptr); + if (nullptr == e || false == e->IsCrease() || e == extruded_edge[0] || e == extruded_edge[1]) + continue; + if (e->MarkedFaceCount() > 0) + bMovedCrease = true; + else + bStationaryCrease = true; + } + + // used the collected vertex topology and edge demographics to determine if connecting_edge_tag should be Crease + if (bMovedCrease && bStationaryCrease) + m_connecting_edge_tag = ON_SubD::EdgeTag::Crease; + + return true; +} + + +int ON_Internal_ExtrudedEdge::CompareSideGroupId(const ON_Internal_ExtrudedEdge* lhs, const ON_Internal_ExtrudedEdge* rhs) +{ + const unsigned lhs_side_group_id = lhs->m_side_group_id; + const unsigned rhs_side_group_id = rhs->m_side_group_id; + if (lhs_side_group_id < rhs_side_group_id) + return -1; + if (lhs_side_group_id > rhs_side_group_id) + return 1; + return 0; +} + +const ON_Internal_ExtrudedEdge ON_Internal_ExtrudedEdge::Unset ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_Internal_ExtrudedEdge); + +int ON_Internal_ExtrudedVertex::CompareInitialVertexId( + const ON_Internal_ExtrudedVertex* lhs, + const ON_Internal_ExtrudedVertex* rhs +) +{ + const unsigned int lhs_id = lhs->m_initial_vertex_id; + const unsigned int rhs_id = rhs->m_initial_vertex_id; if (lhs_id < rhs_id) return -1; if (lhs_id > rhs_id) @@ -12862,36 +14202,14 @@ int ON_Internal_ExtrudedVertexPair::CompareMarkedVertexId( return 0; } -static ON_SubD::EdgeTag Internal_AdjustedEdgeTag(const ON_SubDEdge* edge) -{ - if (nullptr == edge || nullptr == edge->m_vertex[0] || nullptr == edge->m_vertex[1]) - return ON_SubD::EdgeTag::Unset; - - - // adjust moved edge tag because vertex tags can change when they get moved. - const ON_SubD::VertexTag evtag[2] = { edge->m_vertex[0]->m_vertex_tag ,edge->m_vertex[1]->m_vertex_tag }; - - if (ON_SubD::VertexTag::Unset == evtag[0] || ON_SubD::VertexTag::Unset == evtag[1]) - return ON_SubD::EdgeTag::Unset; - - if (ON_SubD::VertexTag::Smooth == evtag[0] || ON_SubD::VertexTag::Smooth == evtag[1]) - return ON_SubD::EdgeTag::Smooth; - - const ON_SubD::EdgeTag etag = edge->m_edge_tag; - if (ON_SubD::EdgeTag::Smooth == etag || ON_SubD::EdgeTag::SmoothX == etag ) - return ON_SubD::EdgeTag::SmoothX; - - return etag; -} - static void Internal_SetEdgeVertices( ON_SubD& subd, - ON_Internal_ExtrudedVertexPair& vertex_pair + ON_Internal_ExtrudedVertex& extruded_vertex ) { // marked edges use the marked vertex. - ON_SubDVertex* marked_vertex = vertex_pair.m_marked_vertex; - ON_SubDVertex* unmarked_vertex = vertex_pair.m_unmarked_vertex; + ON_SubDVertex* marked_vertex = extruded_vertex.m_original_vertex; + ON_SubDVertex* unmarked_vertex = extruded_vertex.m_copied_vertex; const unsigned int vertex_edge_count = marked_vertex->EdgeCount(); unsigned int marked_edge_count = 0; unsigned int unmarked_edge_count = 0; @@ -12902,7 +14220,7 @@ static void Internal_SetEdgeVertices( const ON_SubDEdge* e = eptr.Edge(); if (nullptr == e) continue; - if (vertex_pair.m_new_side == e) + if (extruded_vertex.m_connecting_edge == e) new_edge_count++; else if (e->m_status.RuntimeMark()) marked_edge_count++; @@ -12916,9 +14234,7 @@ static void Internal_SetEdgeVertices( unmarked_edge_count += unmarked_vertex->m_edge_count; if ( unmarked_vertex->m_edge_capacity < (unmarked_edge_count+new_edge_count) ) - { subd.GrowVertexEdgeArray(unmarked_vertex, unmarked_edge_count); - } marked_vertex->m_edge_count = 0; for (unsigned int vei = 0; vei < vertex_edge_count; vei++) @@ -12927,7 +14243,7 @@ static void Internal_SetEdgeVertices( ON_SubDEdge* e = eptr.Edge(); if (nullptr == e) continue; - if (vertex_pair.m_new_side == e || e->m_status.RuntimeMark()) + if (extruded_vertex.m_connecting_edge == e || e->m_status.RuntimeMark()) { marked_vertex->m_edges[marked_vertex->m_edge_count] = eptr; marked_vertex->m_edge_count++; @@ -12944,9 +14260,8 @@ static void Internal_SetEdgeVertices( } } -static ON_SubDFace* Internal_AddNewFace( - ON_SubD& subd, - ON_Internal_ExtrudedSide& side +ON_SubDFace* ON_Internal_ExtrudedEdge::ExtrudeFace( + ON_SubD& subd ) { // All components that will be moved have the runtime mark set. @@ -12958,8 +14273,8 @@ static ON_SubDFace* Internal_AddNewFace( // change edges of unmarked faces to use the new edge ON__UINT_PTR edir = 0; - ON_SubDEdge* marked_edge = side.m_marked_edge; // will be moved - ON_SubDEdge* unmarked_edge = side.m_unmarked_edge; // fixed + ON_SubDEdge* marked_edge = this->m_original_edge; // will be moved + ON_SubDEdge* unmarked_edge = this->m_copied_edge; // a copy of the original edge at the original location unsigned int marked_edge_face_count0 = marked_edge->m_face_count; ON_SubDFacePtr* marked_edge_fptr1 = marked_edge->m_face2; const ON_SubDFacePtr* marked_edge_fptr0 = marked_edge_fptr1; @@ -12999,21 +14314,17 @@ static ON_SubDFace* Internal_AddNewFace( // When marked_edge is a manifold edge, face_count goes from 2 to 1. marked_edge->m_face_count = static_cast(marked_edge_face_count1); - ON_SubDEdge* side0 = (0 == edir) ? side.m_new_side0 : side.m_new_side1; - ON_SubDEdge* side1 = (0 == edir) ? side.m_new_side1 : side.m_new_side0; + ON_SubDEdge* side0 = this->ConnectingEdge(edir); + ON_SubDEdge* side1 = this->ConnectingEdge(1 - edir); ON_SubDEdgePtr new_face_eptr[4]; - new_face_eptr[0] = ON_SubDEdgePtr::Create(side.m_marked_edge, 1-edir); + new_face_eptr[0] = ON_SubDEdgePtr::Create(this->m_original_edge, 1-edir); new_face_eptr[1] = ON_SubDEdgePtr::Create(side0, 1); - new_face_eptr[2] = ON_SubDEdgePtr::Create(side.m_unmarked_edge, edir); + new_face_eptr[2] = ON_SubDEdgePtr::Create(this->m_copied_edge, edir); new_face_eptr[3] = ON_SubDEdgePtr::Create(side1, 0); - const ON_SubD::EdgeTag marked_edge_tag = side.MarkedEdgeTag(); - if (marked_edge_tag != marked_edge->m_edge_tag) - marked_edge->m_edge_tag = marked_edge_tag; + this->m_new_face = subd.AddFace(new_face_eptr, 4); - side.m_new_face = subd.AddFace(new_face_eptr, 4); - - if (nullptr != side.m_new_face) + if (nullptr != this->m_new_face) { // When isolated edges are extruded, we need to flip the face. // In all other cases, we don't. @@ -13021,7 +14332,7 @@ static ON_SubDFace* Internal_AddNewFace( bool bFlipSet = false; for (unsigned int fei = 0; fei < 4; fei++) { - const ON_SubDEdgePtr eptr = side.m_new_face->m_edge4[fei]; + const ON_SubDEdgePtr eptr = this->m_new_face->m_edge4[fei]; const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(eptr.m_ptr); if (nullptr == e || e->m_face_count > 2) { @@ -13038,13 +14349,13 @@ static ON_SubDFace* Internal_AddNewFace( bFlipSet = false; break; } - if (side.m_new_face != f[0] && side.m_new_face != f[1] ) + if (this->m_new_face != f[0] && this->m_new_face != f[1] ) { bFlipSet = false; break; } const ON__UINT_PTR fdir[2] = { ON_SUBD_FACE_DIRECTION(fptr[0].m_ptr), ON_SUBD_FACE_DIRECTION(fptr[1].m_ptr) }; - if (fedir != fdir[(f[0] == side.m_new_face) ? 0 : 1]) + if (fedir != fdir[(f[0] == this->m_new_face) ? 0 : 1]) { bFlipSet = false; break; @@ -13062,393 +14373,10 @@ static ON_SubDFace* Internal_AddNewFace( } } if (bFlip) - side.m_new_face->ReverseEdgeList(); + this->m_new_face->ReverseEdgeList(); } - return side.m_new_face; -} - -static ON_SubD::EdgeTag Internal_ConnectingEdgeTagAtVertex( - bool bExtrudeBoundaries, - const ON_SubDVertex* v, - ON_SubD::VertexTag& moved_vertex_tag, - ON_SubD::VertexTag& stationary_vertex_tag -) -{ - moved_vertex_tag = ON_SubD::VertexTag::Unset; - stationary_vertex_tag = ON_SubD::VertexTag::Unset; - if (ON_SubD::VertexTag::Crease != v->m_vertex_tag && ON_SubD::VertexTag::Dart != v->m_vertex_tag) - { - ON_SUBD_ERROR("This function requires a crease or dart vertex as input."); - return ON_SubD::EdgeTag::Unset; - } - - const unsigned int vertex_edge_count = v->m_edge_count; - - // total_count = number of edges currently attached to v that are creases - unsigned int total_count = 0; - - - //////////////////////////////////////////////////////////////////////////////////////////////////// - // - // stationary, moved_to_crease, moved_to_smooth, and split are mutually exclusive sitations. - - // stationary_count = number of edges currently attached to v that are creases and remain in current locations - unsigned int stationary_count = 0; - - // moved_to_crease_count = number of edges currently attached to v that are creases and move and remain creases - unsigned int moved_to_crease_count = 0; - - // moved_to_crease_count = number of edges currently attached to v that are creases and move and become smooth - unsigned int moved_to_smooth_count = 0; - - // split_count = number of edges currently attached to v that are creases and are between a moved and stationary face - unsigned int split_count = 0; - - - //////////////////////////////////////////////////////////////////////////////////////////////////// - // - // bdry, interior, wire, nonman are mutually exclusive sitations. - - // bdry_count = number of edges currently attached to v that are creases and have 1 face. - unsigned int bdry_count = 0; - - // interior_count = number of edges currently attached to v that are creases and have 2 faces - unsigned int interior_count = 0; - - // wire_count = number of edges currently attached to v that are creases and have 0 faces - unsigned int wire_count = 0; - - // nonman_count = number of edges currently attached to v that are creases and have 3 or more faces - unsigned int nonman_count = 0; - - - //////////////////////////////////////////////////////////////////////////////////////////////////// - // - // tally up what is happening at each crease edge attached to vertex v - // - for (unsigned int vei = 0; vei < vertex_edge_count; vei++) - { - const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(v->m_edges[vei].m_ptr); - if (nullptr == e) - continue; - - if (ON_SubD::EdgeTag::Crease != e->m_edge_tag) - continue; - - const bool bBoundaryEdge = (1 == e->m_face_count) && (nullptr != e->m_face2[0].Face()); - const bool bMovedBoundaryEdge = bBoundaryEdge && e->m_status.RuntimeMark(); - //const bool bStationaryBoundaryEdge = bBoundaryEdge && false == e->m_status.RuntimeMark(); - - ++total_count; - - if (0 == e->m_face_count) - ++wire_count; - else if (1 == e->m_face_count) - ++bdry_count; - else if (2 == e->m_face_count) - ++interior_count; - else if (2 == e->m_face_count) - ++nonman_count; - - unsigned int moved_face_count = 0; - unsigned int stationary_face_count = 0; - for (unsigned short evi = 0; evi < e->m_face_count; evi++) - { - const ON_SubDFace* f = e->Face(evi); - if (nullptr == f) - { - ON_SUBD_ERROR("Edge has null face."); - return ON_SubD::EdgeTag::Unset; - } - if (f->m_status.RuntimeMark()) - ++moved_face_count; - else - ++stationary_face_count; - } - - if (moved_face_count > 0 && stationary_face_count > 0) - { - // This is edge is between a moved face and a stationary face. - // It will "split" into two edges - ++split_count; - } - else if (moved_face_count > 0) - { - // This edge and all faced currently attached to it are moving. - if (bExtrudeBoundaries && bMovedBoundaryEdge) - ++moved_to_smooth_count; - else - ++moved_to_crease_count; - } - else if (stationary_face_count > 0) - { - // This edge is moving and all faced currently attached to it are stationary. - // A new face will be attached to this edge - if (bExtrudeBoundaries && bMovedBoundaryEdge) - { - // This boundary edge will move and a new face will be createed between it - // and the face the edge is currently attached to. - ++moved_to_crease_count; - } - else - ++stationary_count; // this crease (interior or boundary) and its attached face or faces do not move - } - } - - if (total_count != wire_count + split_count + moved_to_smooth_count + moved_to_crease_count + stationary_count) - { - ON_SUBD_ERROR("Bug in counting code above or invalid topology near this vertex."); - return ON_SubD::EdgeTag::Unset; - } - - if (ON_SubD::VertexTag::Dart == v->m_vertex_tag) - { - if ( - 1 == total_count - && 1 == interior_count - && 0 == bdry_count - && 0 == wire_count - && 0 == nonman_count - ) - { - if (0 == split_count) - { - if (1 == moved_to_crease_count && 0 == moved_to_smooth_count && 0 == stationary_count) - { - // dart crease and both attached faces are moving - moved_vertex_tag = ON_SubD::VertexTag::Dart; - stationary_vertex_tag = ON_SubD::VertexTag::Smooth; - return ON_SubD::EdgeTag::Smooth; - } - else if (0 == moved_to_crease_count && 0 == moved_to_smooth_count && 1 == stationary_count) - { - // dart crease and both attached faces are stationary - moved_vertex_tag = ON_SubD::VertexTag::Smooth; - stationary_vertex_tag = ON_SubD::VertexTag::Dart; - return ON_SubD::EdgeTag::Smooth; - } - } - else if (1 == split_count) - { - // Along the dart crease, one attached face is moving and the other is stationary. - // The dart crease will get split into two darts and the new edge will be smooth. - moved_vertex_tag = ON_SubD::VertexTag::Dart; - stationary_vertex_tag = ON_SubD::VertexTag::Dart; - return ON_SubD::EdgeTag::SmoothX; // will X becomes Smooth on 1st subdivision - } - } - ON_SUBD_ERROR("Unexpected dart vertex edge tags - bug in counting code above or current tags or topology are not invalid."); - return ON_SubD::EdgeTag::Unset; - } - - - ///////////////////////////////////////////////////////////////////////////////////////////// - // - // The rest of this function is for the case when ON_SubD::VertexTag::Crease == v->m_vertex_tag - // - if (1 == wire_count) - { - moved_vertex_tag = ON_SubD::VertexTag::Crease; - stationary_vertex_tag = ON_SubD::VertexTag::Crease; - return ON_SubD::EdgeTag::Crease; - } - if (2 == wire_count) - { - moved_vertex_tag = ON_SubD::VertexTag::Crease; - stationary_vertex_tag = ON_SubD::VertexTag::Crease; - return ON_SubD::EdgeTag::SmoothX; // will X becomes Smooth on 1st subdivision - } - - if (0 != wire_count || 0 != nonman_count) - { - // If extrusions involving nonmanifold regions need to be supported, lots of changes are required. - ON_SUBD_ERROR("Currently, non-manifold cases are not supported."); - return ON_SubD::EdgeTag::Unset; - } - - for(;;) - { - if ( - ON_SubD::VertexTag::Crease == v->m_vertex_tag - && 2 == (split_count + moved_to_crease_count + moved_to_smooth_count + stationary_count) - ) - { - if (2 == interior_count && 0 == bdry_count) - break; - if (0 == interior_count && 2 == bdry_count) - break; - } - ON_SUBD_ERROR("Unexpected crease vertex edge tags - bug in counting code above or current tags or topology are not invalid."); - return ON_SubD::EdgeTag::Unset; - } - - if (0 == interior_count && 2 == bdry_count) - { - // The vertex v is currently a boundary crease vertex and split_count always 0 in this case. - // - // Since 2 = split_count + stationary_count + moved_to_crease_count + moved_to_smooth_count, - // stationary_count = 0,1,2 is used as the next "case reduction" filter. - - if (0 != split_count || 2 != (stationary_count + moved_to_crease_count + moved_to_smooth_count) ) - { - ON_SUBD_ERROR("Bug in boundary crease case counting code above."); - return ON_SubD::EdgeTag::Unset; - } - - - if (0 == stationary_count) - { - // both attached crease edges are moving - // 3 possibilities (moved_to_crease_count,moved_to_smooth_count) = (2,0), (0,2), or (1,1) - if (2 == moved_to_crease_count ) - { - moved_vertex_tag = ON_SubD::VertexTag::Crease; - stationary_vertex_tag = ON_SubD::VertexTag::Smooth; - return ON_SubD::EdgeTag::Smooth; - } - - if (2 == moved_to_smooth_count ) - { - // attached faces are moving and moved edges will be copied. - // The moved edges will convert from crease to smooth and the stationary copies will become creases. - // New faces will be created between the moved edge and the stationary copy. - moved_vertex_tag = ON_SubD::VertexTag::Smooth; - stationary_vertex_tag = ON_SubD::VertexTag::Crease; - return ON_SubD::EdgeTag::Smooth; - } - - if ( 1 == moved_to_crease_count && 1 == moved_to_smooth_count ) - { - // new edge runs from moved crease to crease that replaces moved to smooth edge - moved_vertex_tag = ON_SubD::VertexTag::Crease; - stationary_vertex_tag = ON_SubD::VertexTag::Crease; - return ON_SubD::EdgeTag::Crease; - } - } - else if (1 == stationary_count) - { - // 1 attached crease edge is moving and the other attached crease edge is stationary - // 2 possibilities (moved_to_crease_count,moved_to_smooth_count) = (1,0) or (0,1) - if (1 == moved_to_crease_count && 0 == moved_to_smooth_count) - { - // moved edge continues to be a boundary (and crease) edge. - // The new edge connecting the stationary crease and the moved edge is a crease. - moved_vertex_tag = ON_SubD::VertexTag::Crease; - stationary_vertex_tag = ON_SubD::VertexTag::Crease; - return ON_SubD::EdgeTag::Crease; - } - if (0 == moved_to_crease_count && 1 == moved_to_smooth_count) - { - // moved crease becomes interior smooth edge - // The new edge connecting the stationary crease and the moved edge is a crease. - moved_vertex_tag = ON_SubD::VertexTag::Smooth; - stationary_vertex_tag = ON_SubD::VertexTag::Crease; - return ON_SubD::EdgeTag::Smooth; - } - } - else if (2 == stationary_count) - { - // neither attached crease edge is moving - // 1 possibility (moved_to_crease_count,moved_to_smooth_count) = (0,0) - moved_vertex_tag = ON_SubD::VertexTag::Smooth; - stationary_vertex_tag = ON_SubD::VertexTag::Crease; - return ON_SubD::EdgeTag::Smooth; - } - } - else if (2 == interior_count && 0 == bdry_count) - { - // The vertex v is currently an interior crease vertex and moved_to_smooth_count always 0 in this case. - // (move_to_smooth_count>0 requires a boundary edge) - // - // Since 2 = split_count + stationary_count + moved_to_crease_count + moved_to_smooth_count, - // split_count = 0,1,2 is used as the next "case reduction" filter. - - if (0 != moved_to_smooth_count || 2 != (split_count + stationary_count + moved_to_crease_count) ) - { - ON_SUBD_ERROR("Bug in interior crease case counting code above."); - return ON_SubD::EdgeTag::Unset; - } - - if (0 == split_count) - { - // stationary_count can be 0, 1 or 2. - if (0 == stationary_count) - { - // both creases and all attached faces are moving - moved_vertex_tag = ON_SubD::VertexTag::Crease; - stationary_vertex_tag = ON_SubD::VertexTag::Smooth; - return ON_SubD::EdgeTag::Smooth; - } - else if (1 == stationary_count) - { - // TODO - } - else if (2 == stationary_count) - { - // both creases and all attached faces are stationary - moved_vertex_tag = ON_SubD::VertexTag::Smooth; - stationary_vertex_tag = ON_SubD::VertexTag::Crease; - return ON_SubD::EdgeTag::Smooth; - } - } - else if (1 == split_count) - { - // stationary_count can be 0 or 1 - if (0 == stationary_count) - { - // 1 = moved_to_crease_count, 0 = moved_to_smooth_count. - moved_vertex_tag = ON_SubD::VertexTag::Dart; - stationary_vertex_tag = ON_SubD::VertexTag::Crease; - return ON_SubD::EdgeTag::SmoothX; // will X becomes Smooth on 1st subdivision - } - else if (1 == stationary_count) - { - // moved_to_crease_count = 0 and moved_to_smooth_count = 0 - moved_vertex_tag = ON_SubD::VertexTag::Dart; - stationary_vertex_tag = ON_SubD::VertexTag::Crease; - return ON_SubD::EdgeTag::SmoothX; // will X becomes Smooth on 1st subdivision - } - } - else if (2 == split_count) - { - // stationary_count = 0, moved_to_crease_count = 0, and moved_to_smooth_count = 0 - moved_vertex_tag = ON_SubD::VertexTag::Crease; - stationary_vertex_tag = ON_SubD::VertexTag::Crease; - return ON_SubD::EdgeTag::SmoothX; // will X becomes Smooth on 1st subdivision - } - } - - ON_SUBD_ERROR("Unexpected crease vertex edge tags - bug in counting code above or current tags or topology are not invalid."); - return ON_SubD::EdgeTag::Unset; -} - -static bool Internal_NonManifoldEdgeWillBeCreated( const ON_SubDVertex* v ) -{ - if (nullptr == v || false == v->m_status.RuntimeMark()) - return false; - v->m_status.ClearRuntimeMark(); - - const unsigned int vertex_edge_count = v->m_edge_count; - unsigned int boundary_count = 0; - for (unsigned int vei = 0; vei < vertex_edge_count; vei++) - { - const ON_SubDEdge* e = v->Edge(vei); - if (nullptr == e || 0 == e->m_face_count) - continue; - if (e->m_face_count > 2) - return true; - const ON_SubDFace* f = e->Face(0); - const bool b0 = (nullptr != f) ? f->m_status.RuntimeMark() : false; - f = (e->m_face_count > 1) ? e->Face(1) : nullptr; - const bool b1 = (nullptr != f) ? f->m_status.RuntimeMark() : false; - if (b0 == b1) - continue; - boundary_count++; - if (boundary_count > 2) - return true; - } - return false; + return this->m_new_face; } unsigned int ON_SubD::ExtrudeComponents( @@ -13459,9 +14387,7 @@ unsigned int ON_SubD::ExtrudeComponents( { const bool bExtrudeBoundaries = true; const bool bPermitNonManifoldEdgeCreation = false; - const ON_SubD::EdgeTag original_edge_tag = ON_SubD::EdgeTag::Unset; - const ON_SubD::EdgeTag moved_edge_tag = ON_SubD::EdgeTag::Unset; - return ExtrudeComponents(xform, cptr_list, cptr_count, bExtrudeBoundaries, bPermitNonManifoldEdgeCreation, original_edge_tag, moved_edge_tag); + return ExtrudeComponents(xform, cptr_list, cptr_count, bExtrudeBoundaries, bPermitNonManifoldEdgeCreation); } unsigned int ON_SubD::ExtrudeComponents( @@ -13469,9 +14395,7 @@ unsigned int ON_SubD::ExtrudeComponents( const ON_SubDComponentPtr* cptr_list, size_t cptr_count, bool bExtrudeBoundaries, - bool bPermitNonManifoldEdgeCreation, - ON_SubD::EdgeTag original_edge_tag, - ON_SubD::EdgeTag moved_edge_tag + bool bPermitNonManifoldEdgeCreation ) { if (nullptr == cptr_list || cptr_count <= 0) @@ -13481,9 +14405,7 @@ unsigned int ON_SubD::ExtrudeComponents( cptr_list, cptr_count, bExtrudeBoundaries, - bPermitNonManifoldEdgeCreation, - original_edge_tag, - moved_edge_tag + bPermitNonManifoldEdgeCreation ); } @@ -13498,19 +14420,79 @@ unsigned int ON_SubD::Extrude(const ON_Xform & xform) nullptr, 0, bExtrudeBoundaries, - bPermitNonManifoldEdgeCreation, - ON_SubD::EdgeTag::Unset, ON_SubD::EdgeTag::Unset + bPermitNonManifoldEdgeCreation ); } +static bool Internal_SetSideGroupIds(ON_SimpleArray& new_sides) +{ + const unsigned count = new_sides.UnsignedCount(); + if (count <= 0) + return false; + unsigned side_group_id = 1; + for (unsigned int i = 0; i < count; ++i) + { + if (false == new_sides[i].SetSideGroupId(side_group_id)) + continue; + unsigned j1 = i + 1; + while (j1 > 0) + { + unsigned j0 = j1; + j1 = 0; + for (unsigned j = j0; j < count; ++j) + { + if (new_sides[j].SetSideGroupIdFromVertexPairs() && 0 == j1) + j1 = j + 1; + } + } + ++side_group_id; + } + + if (side_group_id <= 1) + return false; + + if (side_group_id > 2) + new_sides.QuickSort(ON_Internal_ExtrudedEdge::CompareSideGroupId); + + return true; +} + + +static bool Internal_DebugValdateExtrudedTopology( + bool bCheckCopies, + const ON_SimpleArray& extruded_edges, + const ON_SimpleArray& extruded_vertices +) +{ + const unsigned edge_count = extruded_edges.UnsignedCount(); + const unsigned vertex_count = extruded_vertices.UnsignedCount(); + + for (unsigned ei = 0; ei < edge_count; ++ei) + { + const ON_Internal_ExtrudedEdge& extruded_edge = extruded_edges[ei]; + if (false == extruded_edge.IsValidTopology(bCheckCopies)) + return false; + } + + for (unsigned vi = 0; vi < vertex_count; ++vi) + { + const ON_Internal_ExtrudedVertex& extruded_vertex = extruded_vertices[vi]; + if (false == extruded_vertex.IsValidTopology(bCheckCopies)) + return false; + } + + if (vertex_count < edge_count) + return Internal_IsNotValidExtrudedTopology(); + + return true; +} + unsigned int ON_SubD::Internal_ExtrudeComponents( const ON_Xform& xform, const ON_SubDComponentPtr* cptr_list, size_t cptr_count, bool bExtrudeBoundaries, - bool bPermitNonManifoldEdgeCreation, - ON_SubD::EdgeTag original_edge_tag, - ON_SubD::EdgeTag moved_edge_tag + bool bPermitNonManifoldEdgeCreation ) { const bool bHaveCptrList = (cptr_count > 0 && nullptr != cptr_list); @@ -13529,20 +14511,13 @@ unsigned int ON_SubD::Internal_ExtrudeComponents( || (false == bHaveCptrList && false == bExtrudeAll) ) return 0; - - // The extrusion is initially calculated as if bot original_edge_tag and moved_edge_tag - // are Unset. A post process (will be added later to) sets the tags as specified - if (ON_SubD::EdgeTag::Crease != original_edge_tag && ON_SubD::EdgeTag::Smooth != original_edge_tag) - original_edge_tag = ON_SubD::EdgeTag::Unset; - - if (ON_SubD::EdgeTag::Crease != moved_edge_tag && ON_SubD::EdgeTag::Smooth != moved_edge_tag) - moved_edge_tag = ON_SubD::EdgeTag::Unset; ON_SubDComponentMarksClearAndRestore mark_guard(*this); - // Marks very vertex touching a component in the cptr_list. - // Skips applying the transform because it is the identity. - + // Marks every face in cptr_list[]. + // Marks every edge attached to a marked face. + // Marks every subd boundary edge in the cptr_list[]. + // Marks every vertex touching a marked edge or marked face. unsigned int list_vertex_count = 0; unsigned int list_edge_count = 0; unsigned int list_face_count = 0; @@ -13552,352 +14527,356 @@ unsigned int ON_SubD::Internal_ExtrudeComponents( bExtrudeBoundaries, list_vertex_count, list_edge_count, list_face_count ); - - unsigned int moved_face_count = 0; // moved face count - unsigned int new_face_count = 0; // number of new faces on moved boundary - for (;;) + if (0 == marked_vertex_count) + return 0; + + // Set moved_faces[] = list of faces that will move. + ON_SimpleArray moved_faces(list_face_count + 128); + ON_SubDFaceIterator fit(*this); + for (const ON_SubDFace* f = fit.FirstFace(); nullptr != f; f = fit.NextFace()) { - if (0 == marked_vertex_count) - break; + const unsigned int face_vertex_count = f->m_edge_count; + if (face_vertex_count < 3 || false == f->m_status.RuntimeMark()) + continue; + moved_faces.Append(f); + } + const unsigned int moved_face_count = moved_faces.UnsignedCount(); - // Set moved_faces[] = list of faces that will move. - ON_SimpleArray moved_faces(list_face_count + 128); - ON_SubDFaceIterator fit(*this); - for (const ON_SubDFace* f = fit.FirstFace(); nullptr != f; f = fit.NextFace()) + ON_SimpleArray extruded_edges(64); + ON_SubDEdgeIterator eit(*this); + for (const ON_SubDEdge* e = eit.FirstEdge(); nullptr != e; e = eit.NextEdge()) + { + const bool bMarkedEdge = e->Mark(); + e->ClearMark(); + e->ClearMarkBits(); + if (nullptr == e->m_vertex[0] || nullptr == e->m_vertex[1]) + continue; + + const unsigned int edge_face_count = e->m_face_count; + + // marked wire edges and marked boundary edges get extruded whenever bExtrudeBoundaries is true. + bool bExtrudeEdge = bMarkedEdge && (0 == edge_face_count || (1 == edge_face_count && bExtrudeBoundaries)); + + bool bAttachedToMarkedFace = false; + bool bAttachedToUnmarkedFace = false; + for (unsigned int efi = 0; efi < edge_face_count; efi++) { - const unsigned int face_vertex_count = f->m_edge_count; - if (face_vertex_count < 3 || false == f->m_status.RuntimeMark()) + const ON_SubDFace* f = e->Face(efi); + if (nullptr == f) continue; - moved_faces.Append(f); - } + if (f->Mark()) + bAttachedToMarkedFace = true; // f is in the subset of faces that will be moved. + else + bAttachedToUnmarkedFace = true; // f is in the subset of faces that are stationary (will not be moved). - moved_face_count = moved_faces.UnsignedCount(); - - // Mark edges on the boundary of the moved subset. - - // Set moved_edges[] = list of edges that are either - // 1) between a moved face and a stationary face - // 2) are a boundary edge to be extruded. - ON_SimpleArray moved_edges(list_edge_count + list_vertex_count); - - ON_SimpleArray new_sides(64); - ON_SubDEdgeIterator eit(*this); - for (const ON_SubDEdge* e = eit.FirstEdge(); nullptr != e; e = eit.NextEdge()) - { - const bool bExtrudedBoundaryEdge - = bExtrudeBoundaries - && e->m_status.RuntimeMark() - && 1 == e->m_face_count - && ON_SubD::EdgeTag::Crease == e->m_edge_tag - ; - bool bExtrudeEdge = bExtrudedBoundaryEdge; - - e->m_status.ClearRuntimeMark(); - - bool bMarkedFace = false; - bool bUnmarkedFace = false; - const unsigned int edge_face_count = e->m_face_count; - for (unsigned int efi = 0; efi < edge_face_count; efi++) + if (bAttachedToMarkedFace && bAttachedToUnmarkedFace) { - const ON_SubDFace* f = e->Face(efi); - if (nullptr == f) - continue; - if (f->m_status.RuntimeMark()) - bMarkedFace = true; // f is in the subset of moved faces. - else - bUnmarkedFace = true; // f is in the subset of stationary faces. - - if (bMarkedFace && bUnmarkedFace) - { - // e is on the boundary between the subset of moved faces and stationary faces. - bExtrudeEdge = true; - break; - } - } - - if (bExtrudeEdge) - { - if (false == bMarkedFace) - moved_edges.Append(e); // e is not part of a moved face's boundary - e->m_status.SetRuntimeMark(); - ON_Internal_ExtrudedSide& newside = new_sides.AppendNew(); - newside.m_marked_edge = const_cast(e); - newside.m_bExtrudedBoundaryEdge = bExtrudedBoundaryEdge; - newside.m_bHasMovedFace = bMarkedFace; - newside.m_bHasStationaryFace = bUnmarkedFace; - newside.m_original_marked_edge_tag = e->m_edge_tag; + // e is on the boundary between the subset of moved faces and stationary faces + // and will be extruded into a face. + bExtrudeEdge = true; + break; } } - new_face_count = new_sides.UnsignedCount(); - - if (0 == new_face_count) + if (bExtrudeEdge) { - // no new faces will be made - if (moved_face_count > 0) + ON_Internal_ExtrudedEdge& extruded_edge = extruded_edges.AppendNew(); + if (false == extruded_edge.SetFromInitialEdge(const_cast(e))) { - // Every face is moving - Transform(xform); + ON_SUBD_ERROR("null e or vertex pointers"); + return 0; } - break; + + // Marking e here indicates e is an extruded edge. + // ON_Internal_ExtrudedVertex.SetFromInitialVertex() assumes marked edges are extruded edges. + e->SetMark(); + } + } + + const unsigned int extruded_edge_count = extruded_edges.UnsignedCount(); + + if (0 == extruded_edge_count) + { + // no new faces will be made + if (moved_face_count > 0) + { + // Every face is moving + Transform(xform); + return FaceCount(); + } + return 0; + } + + // Mark edges on the boundary of the moved subset. + // clear vertex marks. + this->ClearVertexMarks(); + this->ClearVertexMarkBits(); + + // build a list extruded_vertices[] of vertices that will be extruded into "side" edges + ON_SimpleArray extruded_vertices(extruded_edge_count + 8); + for (unsigned int i = 0; i < extruded_edge_count; i++) + { + ON_Internal_ExtrudedEdge& extruded_edge = extruded_edges[i]; + const ON_SubDEdge* e = extruded_edge.m_original_edge; + if (nullptr == e) + { + ON_SUBD_ERROR("Should never be the case."); + return 0; + } + for (unsigned int evi = 0; evi < 2; evi++) + { + ON_SubDVertex* v = const_cast(e->m_vertex[evi]); + if (v->Mark()) + continue; // this vertex has already been added to extruded_vertices[] + + // Mark this vertex to indicate it will be extruded into a "side" edge. + v->SetMark(); + + ON_Internal_ExtrudedVertex& extruded_vertex = extruded_vertices.AppendNew(); + if (false == extruded_vertex.SetFromInitialVertex(v)) + { + ON_SUBD_ERROR("Should never be the case."); + return 0; + } + + // Partially setting the topology connections here reduces the number of binary searches by about half. + // The scecond paramter is false because the sorting of extruded_vertices[] will change the memory + // location where extruded_vertex is stored. + if (false == extruded_vertex.AddExtrudedEdgeReference(&extruded_edge, false)) + return 0; + } + } + + // sort vertex pairs so they can be located by the original vertex id. + extruded_vertices.QuickSort(ON_Internal_ExtrudedVertex::CompareInitialVertexId); + + // After sorting but before searching, set extruded_edge.m_extruded_vertex[] pointer + // for the extruded_edge used to generate the extruded_vertex in the for() loop + // immediately before the sorting of extruded_vertices[]. + // This reduces the number of binary searches by about half. + for (unsigned int i = 0; i < extruded_vertices.UnsignedCount(); i++) + { + ON_Internal_ExtrudedVertex& extruded_vertex = extruded_vertices[i]; + if ( + extruded_vertex.m_initial_vertex_id > 0 + && 1 == extruded_vertex.m_extruded_edges_count + && nullptr != extruded_vertex.m_extruded_edges[0] + ) + { + if (extruded_vertex.AddExtrudedEdgeReference(extruded_vertex.m_extruded_edges[0], true)) + continue; } - if (false == bPermitNonManifoldEdgeCreation) + ON_SUBD_ERROR("bug introduced in code above since Feb 2020"); + return 0; + } + + // Use the sorted extruded_vertices[] array and initial vertex ids to + // finish set the topology connections between extruded_vertices[] and extruded_edges[] + ON_Internal_ExtrudedVertex bsearch_key; + for (unsigned int i = 0; i < extruded_edges.UnsignedCount(); i++) + { + ON_Internal_ExtrudedEdge& extruded_edge = extruded_edges[i]; + for (unsigned int evi = 0; evi < 2; evi++) { - bool bNonManifoldEdgeWillBeCreated = false; - for (unsigned int fi = 0; fi < moved_face_count; fi++) + if (nullptr != extruded_edge.m_extruded_vertex[evi]) + continue; // this topology connection was set above and we can skip the binary search step + + // Use a binary search to find the element of extruded_vertices[] that corresponds to extruded_edge.m_original_edge->m_vertex[evi]. + bsearch_key.m_initial_vertex_id = extruded_edge.m_initial_vertex_id[evi]; + const int i0 = extruded_vertices.BinarySearch(&bsearch_key, ON_Internal_ExtrudedVertex::CompareInitialVertexId); + if (i0 < 0) { - const ON_SubDFace* f = moved_faces[fi]; - const unsigned int face_vertex_count = f->m_edge_count; - for (unsigned int fvi = 0; fvi < face_vertex_count; fvi++) - { - bNonManifoldEdgeWillBeCreated = Internal_NonManifoldEdgeWillBeCreated(f->Vertex(fvi)); - if (bNonManifoldEdgeWillBeCreated) - break; - } - if (bNonManifoldEdgeWillBeCreated) - break; + ON_SUBD_ERROR("Should never be the case."); + return 0; } - - if (bNonManifoldEdgeWillBeCreated) - break; // not allowd to create non-manifold edges - - for (unsigned int ei = 0; ei < moved_edges.UnsignedCount(); ei++) - { - const ON_SubDEdge* e = moved_edges[ei]; - for (unsigned int evi = 0; evi < 2; evi++) - { - bNonManifoldEdgeWillBeCreated = Internal_NonManifoldEdgeWillBeCreated(e->m_vertex[evi]); - if (bNonManifoldEdgeWillBeCreated) - break; - } - if (bNonManifoldEdgeWillBeCreated) - break; - } - - if (bNonManifoldEdgeWillBeCreated) - break; // not allowd to create non-manifold edges + if (false == extruded_vertices[i0].AddExtrudedEdgeReference(&extruded_edge, true)) + return 0; } + } - // clear vertex marks. - ClearComponentMarks(true, false, false, nullptr); - - // Duplicate vertices that are on an edge between a marked and unmarked face - // or a moved boundary edge - ON_SimpleArray vertex_pairs(new_face_count+8); - for (unsigned int i = 0; i < new_face_count; i++) + if (false == bPermitNonManifoldEdgeCreation) + { + for (unsigned int i = 0; i < extruded_vertices.UnsignedCount(); i++) { - const ON_SubDEdge* e = new_sides[i].m_marked_edge; + // 3 or more "side" faces will be attached to the edge made by extruding the vertex. + // Thus, this edge will be a nonmanifold edge. + if (extruded_vertices[i].m_extruded_edges_count >= 3) + return 0; + } + } + + if (false == Internal_DebugValdateExtrudedTopology(false, extruded_edges, extruded_vertices)) + return 0; + + // Need to set some tag information that is used when we created the extruded edges and faces + // before modifications are made to this subd. + for (unsigned int i = 0; i < extruded_vertices.UnsignedCount(); ++i) + extruded_vertices[i].SetConnectingEdgeTag(); + + // Extrude vertices into side edges - creates new vertices and connecting edges. + for (unsigned int i = 0; i < extruded_vertices.UnsignedCount(); ++i) + { + if (false == extruded_vertices[i].ExtrudeVertex(*this,xform)) + return 0; + } + + // Create "side" face edge that will be opposite the original edge. + for (unsigned int i = 0; i < extruded_edges.UnsignedCount(); ++i) + { + if (nullptr == extruded_edges[i].CreateCopiedEdge(*this)) + return 0; + } + + if (false == Internal_DebugValdateExtrudedTopology(true, extruded_edges, extruded_vertices)) + return 0; + + // At this point moved_faces[] = set of marked faces. + // Efficiency determines whither the face mark or the moved_faces[] array + // is used to decide if a face is moved or stationary. + for (unsigned int i = 0; i < extruded_vertices.UnsignedCount(); ++i) + { + // Attach unmarked faces (stationary) to m_copied_vertex. + // Leave the marked faces (transformed) attached to m_original_vertex. + if (false == extruded_vertices[i].AttachUnmarkedFacesToCopiedVertex()) + { + for (unsigned j = 0; j < i; ++j) + extruded_vertices[i].UndoAttachUnmarkedFacesToCopiedVertex(); + return 0; + } + } + + // From this point on, we don't bail out, we just press on doing the best that can be done. + + // Mark everything a moved face touches + // including interior edges and vertices. + // Transform any vertices that are not already marked. + // (The vertices in extruded_vertices[] are currently marked and have already been transformed.) + for (unsigned int i = 0; i < moved_face_count; i++) + { + const ON_SubDFace* f = moved_faces[i]; + const unsigned int face_edge_count = f->m_edge_count; + for (unsigned int fei = 0; fei < face_edge_count; ++fei) + { + const ON_SubDEdge* e = f->Edge(fei); + if (nullptr == e) + continue; + e->SetMark(); for (unsigned int evi = 0; evi < 2; evi++) { ON_SubDVertex* v = const_cast(e->m_vertex[evi]); + if (nullptr != v && false == v->Mark()) + { + v->Transform(false, xform); + v->SetMark(); + } + } + } + } + + // For the original boundary vertrex, move unmarked edges to use the new vertex. + for (unsigned int i = 0; i < extruded_vertices.UnsignedCount(); i++) + Internal_SetEdgeVertices(*this, extruded_vertices[i]); + + // extrude edges into new "side" faces. + for (unsigned int i = 0; i < extruded_edge_count; i++) + extruded_edges[i].ExtrudeFace(*this); + + // remove cached subdivision calculations + ClearEvaluationCache(); + + // Set tags that are clearly determined by the extrusion. + if ( Internal_SetSideGroupIds(extruded_edges)) + { + const unsigned count = extruded_edges.UnsignedCount(); + unsigned i1 = 0; + for (unsigned i0 = 0; i0 < count; i0 = (i1 > i0) ? i1 : (i0 + 1)) + { + const unsigned side_group_id = extruded_edges[i0].m_side_group_id; + unsigned crease_count = 0; + unsigned interior_crease_count = 0; + unsigned corner_count = 0; + for (i1 = i0; i1 < count && side_group_id == extruded_edges[i1].m_side_group_id; ++i1) + { + ON_Internal_ExtrudedEdge& extruded_edge = extruded_edges[i1]; + if (nullptr != extruded_edge.m_original_edge && 2 != extruded_edge.m_original_edge->m_face_count) + { + extruded_edge.m_original_edge->m_edge_tag = ON_SubD::EdgeTag::Crease; + } + if (nullptr != extruded_edge.m_copied_edge) + { + if ( + 2 != extruded_edge.m_copied_edge->m_face_count + || + (2 == extruded_edge.m_initial_edge_face_count && ON_SubD::EdgeTag::Crease == extruded_edge.m_initial_edge_tag) + ) + extruded_edge.m_copied_edge->m_edge_tag = ON_SubD::EdgeTag::Crease; + } + if (extruded_edge.InitialCrease()) + { + ++crease_count; + if (2 == extruded_edge.m_initial_edge_face_count) + ++interior_crease_count; + if (extruded_edge.InitialCreaseWithCorner()) + { + ++corner_count; + for (unsigned k = 0; k < 2; ++k) + { + ON_SubDEdge* e = (0 == k) ? extruded_edge.m_original_edge : extruded_edge.m_copied_edge; + if (2 != e->m_face_count) + { + e->m_edge_tag = ON_SubD::EdgeTag::Crease; + for (unsigned evi = 0; evi < 2; ++evi) + { + ON_Internal_ExtrudedVertex* extruded_vertex = extruded_edge.m_extruded_vertex[evi]; + if (ON_SubD::VertexTag::Corner == extruded_vertex->m_initial_vertex_tag) + { + ON_SubDVertex* v = (0 == k) ? extruded_vertex->m_original_vertex : extruded_vertex->m_copied_vertex; + v->m_vertex_tag = ON_SubD::VertexTag::Corner; + } + } + } + } + } + } + } + if ( + (crease_count == (i1 - i0)) + && + (corner_count > 0 || crease_count == interior_crease_count) + ) + { + for (unsigned i = i0; i < i1; ++i) + extruded_edges[i].SetBothEdgeTags(ON_SubD::EdgeTag::Crease); + } + } + } + + for (unsigned i = 0; i < extruded_vertices.UnsignedCount(); ++i) + { + ON_Internal_ExtrudedVertex& pair = extruded_vertices[i]; + if (ON_SubD::VertexTag::Corner == pair.m_initial_vertex_tag) + { + for (unsigned vdex = 0; vdex < 2; ++vdex) + { + ON_SubDVertex* v = (0 == vdex) ? pair.m_original_vertex : pair.m_copied_vertex; if (nullptr == v) continue; - if (v->m_status.RuntimeMark()) - continue; - - // Mark this vertex. It will eventually get moved - v->m_status.SetRuntimeMark(); - ON_Internal_ExtrudedVertexPair& vpair = vertex_pairs.AppendNew(); - vpair.m_marked_vertex = v; - - ON_SubD::VertexTag moved_vertex_tag = ON_SubD::VertexTag::Unset; // the original vertex gets moved - ON_SubD::VertexTag stationary_vertex_tag = ON_SubD::VertexTag::Unset; // (this one get allocated) - ON_SubD::EdgeTag connecting_edge_tag = ON_SubD::EdgeTag::Unset; // from stationary to moved vertex - switch (v->m_vertex_tag) - { - case ON_SubD::VertexTag::Dart: - connecting_edge_tag = Internal_ConnectingEdgeTagAtVertex(bExtrudeBoundaries, v, moved_vertex_tag, stationary_vertex_tag); - break; - case ON_SubD::VertexTag::Crease: - connecting_edge_tag = Internal_ConnectingEdgeTagAtVertex(bExtrudeBoundaries, v, moved_vertex_tag, stationary_vertex_tag); - break; - case ON_SubD::VertexTag::Corner: - moved_vertex_tag = v->m_vertex_tag; - stationary_vertex_tag = v->m_vertex_tag; - connecting_edge_tag = ON_SubD::EdgeTag::Crease; - break; - case ON_SubD::VertexTag::Smooth: - moved_vertex_tag = ON_SubD::VertexTag::Smooth; - stationary_vertex_tag = ON_SubD::VertexTag::Smooth; - connecting_edge_tag = ON_SubD::EdgeTag::Smooth; - break; - default: - moved_vertex_tag = ON_SubD::VertexTag::Unset; - stationary_vertex_tag = ON_SubD::VertexTag::Unset; - connecting_edge_tag = ON_SubD::EdgeTag::Unset; - break; - } - - // original vertex will eventually be moved. - v->m_vertex_tag = moved_vertex_tag; - - // new vertex will become part of the stationary subset. - // It is not marked. - vpair.m_unmarked_vertex = this->AddVertex(stationary_vertex_tag, v->m_P); - - // transform the marked boundary vertex - v->Transform(false, xform); - - // edge from stationary subset to moved subset. - ON_SubDEdge* connecting_edge = this->AddEdge(connecting_edge_tag, vpair.m_unmarked_vertex, vpair.m_marked_vertex); - vpair.m_new_side = connecting_edge; + if (v->EdgeProperties().m_crease_edge_count >= 3) + v->m_vertex_tag = ON_SubD::VertexTag::Corner; } } - - // sort vertex pairs so they can be located by the original vertex id. - vertex_pairs.QuickSort(ON_Internal_ExtrudedVertexPair::CompareMarkedVertexId); - - // remove unmarked faces from marked vertices - for (unsigned int i = 0; i < vertex_pairs.UnsignedCount(); i++) - { - ON_SubDVertex* marked_vertex = vertex_pairs[i].m_marked_vertex; - ON_SubDVertex* unmarked_vertex = vertex_pairs[i].m_unmarked_vertex; - const unsigned int vertex_face_count0 = marked_vertex->m_face_count; - GrowVertexFaceArray(unmarked_vertex, vertex_face_count0); - marked_vertex->m_face_count = 0; - for (unsigned int vfi = 0; vfi < vertex_face_count0; vfi++) - { - const ON_SubDFace* f = marked_vertex->m_faces[vfi]; - if (nullptr == f ) - continue; - ON_SubDVertex* v - = (f->m_status.RuntimeMark()) - ? marked_vertex - : unmarked_vertex; - v->m_faces[v->m_face_count] = f; - v->m_face_count++; - } - } - - // build new side edges - ON_Internal_ExtrudedVertexPair key[2]; - for (unsigned int i = 0; i < new_face_count; i++) - { - const ON_SubDEdge* e = new_sides[i].m_marked_edge; - for (unsigned int evi = 0; evi < 2; evi++) - { - key[evi].m_marked_vertex = const_cast(e->m_vertex[evi]); - const int i0 = - (nullptr != key[evi].m_marked_vertex) - ? vertex_pairs.BinarySearch(&key[evi], ON_Internal_ExtrudedVertexPair::CompareMarkedVertexId) - : -1; - if (i0 < 0) - { - key[evi] = ON_Internal_ExtrudedVertexPair::Unset; - continue; - } - key[evi] = vertex_pairs[i0]; - } - - const ON_SubD::EdgeTag unmarked_edge_tag = new_sides[i].UnmarkedEdgeTag(key[0].m_unmarked_vertex, key[1].m_unmarked_vertex); - new_sides[i].m_unmarked_edge = this->AddEdge(unmarked_edge_tag, key[0].m_unmarked_vertex, key[1].m_unmarked_vertex); - new_sides[i].m_new_side0 = key[0].m_new_side; - new_sides[i].m_new_side1 = key[1].m_new_side; - } - - // Mark everything a moved face touches - // including interior edges and vertices. - // Transform any vertices that are not already marked. - for (unsigned int i = 0; i < moved_face_count; i++) - { - const ON_SubDFace* f = moved_faces[i]; - const unsigned int face_edge_count = f->m_edge_count; - for (unsigned int fei = 0; fei < face_edge_count; ++fei) - { - const ON_SubDEdge* e = f->Edge(fei); - if (nullptr == e) - continue; - e->m_status.SetRuntimeMark(); - for (unsigned int evi = 0; evi < 2; evi++) - { - ON_SubDVertex* v = const_cast(e->m_vertex[evi]); - if (nullptr !=v && false == v->m_status.RuntimeMark()) - { - v->Transform(false, xform); - v->m_status.SetRuntimeMark(); - } - } - } - } - - // Mark everything vertex a moved edge touches - // Transform any vertices that are not already marked. - for (unsigned int i = 0; i < moved_edges.UnsignedCount(); i++) - { - const ON_SubDEdge* e = moved_edges[i]; - const unsigned int edge_face_count = e->m_face_count; - for (unsigned int efi = 0; efi < edge_face_count; ++efi) - { - e->m_status.SetRuntimeMark(); - for (unsigned int evi = 0; evi < 2; evi++) - { - ON_SubDVertex* v = const_cast(e->m_vertex[evi]); - if (nullptr !=v && false == v->m_status.RuntimeMark()) - { - v->Transform(false, xform); - v->m_status.SetRuntimeMark(); - } - } - } - } - - // For the original boundary vertrex, move unmarked edges to use the new vertex. - for (unsigned int i = 0; i < vertex_pairs.UnsignedCount(); i++) - { - Internal_SetEdgeVertices(*this, vertex_pairs[i]); - } - - // build new side faces - for (unsigned int i = 0; i < new_face_count; i++) - { - Internal_AddNewFace(*this, new_sides[i]); - } - - // Any edge touching the vertices in vertex_pairs[] may need its tag adjusted - // because those vertices may have had their tags adjusted. - // Some edges get checked twice, but adding code to check just once - // takes more time than checking twice. - for (unsigned int i = 0; i < vertex_pairs.UnsignedCount(); i++) - { - for (unsigned int j = 0; j < 2; j++) - { - const ON_SubDVertex* v = (0 == j) ? vertex_pairs[i].m_marked_vertex : vertex_pairs[i].m_unmarked_vertex; - if (nullptr == v || nullptr == v->m_edges) - continue; - for (unsigned int vei = 0; vei < v->m_edge_count; vei++) - { - ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(v->m_edges[vei].m_ptr); - if (nullptr == e) - continue; - const ON_SubD::EdgeTag adjusted_etag = Internal_AdjustedEdgeTag(e); - if (ON_SubD::EdgeTag::Unset != adjusted_etag && e->m_edge_tag != adjusted_etag) - e->m_edge_tag = adjusted_etag; - } - } - } - - // remove cached subdivision calculations - ClearEvaluationCache(); - - // Calculate vertex tags, edge tags, edge sector weights. - this->UpdateAllTagsAndSectorCoefficients(true); - break; } + // Calculate unset vertex tags, unset edge tags, edge sector weights. + this->UpdateAllTagsAndSectorCoefficients(true); + #if defined(ON_DEBUG) IsValid(); #endif - // TODO - run through new_sides[] array and attempt to adjust tags - //if (ON_SubD::EdgeTag::Unset != original_edge_tag || ON_SubD::EdgeTag::Unset != moved_edge_tag) - //{ - // - //} - - - // number of moved faces and new boundary faces - return moved_face_count + new_face_count; + // number of moved faces and new faces created by extruding edges + return moved_face_count + extruded_edge_count; } unsigned int ON_SubD::SetVertexTags( @@ -14587,7 +15566,7 @@ ON_UniqueTester::Block* ON_UniqueTester::Block::NewBlock() size_t sz2 = ON_UniqueTester::Block::BlockCapacity * sizeof(m_a[0]); void* p = onmalloc(sz1 + sz2); Block* blk = new (p) Block(); - blk->m_a = (ON__UINT_PTR*)((char*)(p)) + sz1; + blk->m_a = (ON__UINT_PTR*)(((char*)(p)) + sz1); return blk; } diff --git a/opennurbs_subd.h b/opennurbs_subd.h index 942eb420..ec78a4bd 100644 --- a/opennurbs_subd.h +++ b/opennurbs_subd.h @@ -259,6 +259,15 @@ public: bool SetMark( bool bMark ) const; + + + ON__UINT8 MarkBits() const; + + ON__UINT8 SetMarkBits( + ON__UINT8 mark_bits + ) const; + + ON__UINT8 ClearMarkBits() const; }; @@ -536,6 +545,14 @@ public: bool SetMark( bool bMark ) const; + + ON__UINT8 MarkBits() const; + + ON__UINT8 SetMarkBits( + ON__UINT8 mark_bits + ) const; + + ON__UINT8 ClearMarkBits() const; }; #if defined(ON_DLL_TEMPLATE) @@ -665,6 +682,14 @@ public: bool SetMark( bool bMark ) const; + + ON__UINT8 MarkBits() const; + + ON__UINT8 SetMarkBits( + ON__UINT8 mark_bits + ) const; + + ON__UINT8 ClearMarkBits() const; }; #if defined(ON_DLL_TEMPLATE) @@ -741,6 +766,11 @@ public: True if the ComponentBase() pointer is nullptr. Note that type and mark may be set. */ bool IsNull() const; + + /* + Returns: + True if type is set and ComponentBase() pointer is not nullptr. Note that mark may be set as well. + */ bool IsNotNull() const; /* @@ -815,13 +845,13 @@ public: with ON_SubDComponentPtr.ComponentDirection() = 1. */ const ON_SubDComponentPtr SetComponentDirection(ON__UINT_PTR dir) const; - + /* Returns: An ON_SubDComponentPtr referencing the same ON_SubDComponentBase with ComponentDirection() = 1 - this->ComponentDirection(). */ - const ON_SubDComponentPtr ToggleComponentDirection() const; + const ON_SubDComponentPtr Reversed() const; const ON_ComponentStatus Status() const; @@ -910,6 +940,14 @@ public: bool bMark ) const; + ON__UINT8 MarkBits() const; + + ON__UINT8 SetMarkBits( + ON__UINT8 mark_bits + ) const; + + ON__UINT8 ClearMarkBits() const; + static const ON_SubDComponentPtr CreateNull( ON_SubDComponentPtr::Type component_type, @@ -1012,6 +1050,13 @@ public: */ const ON_SubDComponentPtrPair SwapPair() const; + + /* + Returns: + A pair with components reversed. + */ + const ON_SubDComponentPtrPair ReversedPair() const; + /* Returns: First ON_SubDComponentPt in the pair. @@ -1032,19 +1077,34 @@ public: ON_SubDComponentPtr::Type ComponentType() const; /* - Returns true if First() is ON_SubDComponentPtr::Null. + Returns FIrst().IsNull(). */ bool FirstIsNull() const; /* - Returns true if Second() is ON_SubDComponentPtr::Null. + Returns Second().IsNull(). */ bool SecondIsNull() const; /* - Returns true if both First() and Second() are ON_SubDComponentPtr::Null. + Returns First().IsNull() && Second().IsNull(). */ bool BothAreNull() const; + + /* + Returns First().IsNotNull(). + */ + bool FirstIsNotNull() const; + + /* + Returns Second().IsNotNull(). + */ + bool SecondIsNotNull() const; + + /* + Returns FirstIsNotNull() && SecondIsNotNull(). + */ + bool BothAreNotNull() const; public: const static ON_SubDComponentPtrPair Null; @@ -1668,6 +1728,14 @@ public: Change the content serial number. This should be done ONLY when the active level, vertex location, vertex or edge flag, or subd topology is changed. + Parameters: + bChangePreservesSymmetry - [in] + When in doubt, pass false. + If the change preserves global symmtery, then pass true. + (For example, a global subdivide preserves all types of symmetry.) + Note well: + Transformations do not preserve symmetries that are + set with respect to world coordinate systems. Returns: The new value of ConentSerialNumber(). Remarks: @@ -1675,7 +1743,9 @@ public: functions typically take care of changing the content serial number. A "top level" user of ON_SubD should never need to call this function. */ - ON__UINT64 ChangeContentSerialNumberForExperts(); + ON__UINT64 ChangeContentSerialNumberForExperts( + bool bChangePreservesSymmetry + ); /* Description: @@ -2244,6 +2314,7 @@ public: ); + ON_SubD() ON_NOEXCEPT; virtual ~ON_SubD(); @@ -2442,6 +2513,15 @@ public: ON_SubD* subd ); +private: + static ON_SubD* Internal_CreateFromMeshWithValidNgons( + const class ON_Mesh* level_zero_mesh_with_valid_ngons, + const class ON_ToSubDParameters* from_mesh_parameters, + ON_SubD* subd + ); + +public: + /* Description: Creates a SubD box @@ -2618,8 +2698,10 @@ public: Paramters: max_level_index - [in] Remove all levels after max_level_index + Returns: + Number of removed levels. */ - void ClearHigherSubdivisionLevels( + unsigned int ClearHigherSubdivisionLevels( unsigned int max_level_index ); @@ -2629,11 +2711,20 @@ public: Paramters: min_level_index - [in] Remove all levels before min_level_index + Returns: + Number of removed levels. */ - void ClearLowerSubdivisionLevels( + unsigned int ClearLowerSubdivisionLevels( unsigned int min_level_index ); + /* + Remove all levels except the active level. + Returns: + Number of removed levels. + */ + unsigned ClearInactiveLevels(); + bool IsEmpty() const; bool IsNotEmpty() const; @@ -2908,6 +2999,11 @@ public: bool bMarkDeletedFaceEdges ); + bool DeleteComponents( + const ON_SimpleArray& cptr_list, + bool bMarkDeletedFaceEdges + ); + bool DeleteComponentsForExperts( const ON_SubDComponentPtr* cptr_list, size_t cptr_count, @@ -2916,6 +3012,40 @@ public: bool bMarkDeletedFaceEdges ); + /* + Description: + Delete marked components. + Parameters: + bDeleteMarkedComponents - [in] + If true, marked components are deleted. + If false, unmarked components are deleted. + mark_bits - [in] + A component is marked if component.m_status.IsMarked(mark_bits) is true. + */ + bool DeleteMarkedComponents( + bool bDeleteMarkedComponents, + ON__UINT8 mark_bits, + bool bMarkDeletedFaceEdges + ); + + /* + Description: + Delete marked components. + Parameters: + bDeleteMarkedComponents - [in] + If true, marked components are deleted. + If false, unmarked components are deleted. + mark_bits - [in] + A component is marked if component.m_status.IsMarked(mark_bits) is true. + */ + bool DeleteMarkedComponentsForExperts( + bool bDeleteMarkedComponents, + ON__UINT8 mark_bits, + bool bDeleteIsolatedEdges, + bool bUpdateTagsAndCoefficients, + bool bMarkDeletedFaceEdges + ); + ///////////////////////////////////////////////////////// // @@ -3247,6 +3377,109 @@ public: ); + /* + Descripiton: + Sets the m_group_id value to 0 for every vertex, edge and face. + Returns: + Number of marks that were cleared. + */ + unsigned int ClearGroupIds() const; + + /* + Descripiton: + Sets the m_group_id value to 0 for every vertex. + Returns: + Number of group id values that were changed. + */ + unsigned int ClearVertexGroupIds() const; + + /* + Descripiton: + Sets the m_group_id value to 0 for every edge. + Returns: + Number of group id values that were changed. + */ + unsigned int ClearEdgeGroupIds() const; + + /* + Descripiton: + Sets the m_group_id value to 0 for every face. + Returns: + Number of group id values that were changed. + */ + unsigned int ClearFaceGroupIds() const; + + /* + Descripiton: + Sets the m_group_id value to 0 for the specified components. + Parameters: + bClearVertexGroupIds - [in] + If true, m_group_id for every vertex is set to zero. + bClearEdgeGroupIds - [in] + If true, m_group_id for every edge is set to zero. + bClearFaceGroupIds - [in] + If true, m_group_id for every face is set to zero. + Returns: + Number of group id values that were changed. + */ + unsigned int ClearComponentGroupIds( + bool bClearVertexGroupIds, + bool bClearEdgeGroupIds, + bool bClearFaceGroupIds + ) const; + + + /* + Descripiton: + Sets the m_status.MarkBits() value to 0 for every vertex, edge and face. + Returns: + Number of marks that were cleared. + */ + unsigned int ClearMarkBits() const; + + /* + Descripiton: + Sets the m_status.MarkBits() value to 0 for every vertex. + Returns: + Number of group id values that were changed. + */ + unsigned int ClearVertexMarkBits() const; + + /* + Descripiton: + Sets the m_status.MarkBits() value to 0 for every edge. + Returns: + Number of group id values that were changed. + */ + unsigned int ClearEdgeMarkBits() const; + + /* + Descripiton: + Sets the m_status.MarkBits() value to 0 for every face. + Returns: + Number of group id values that were changed. + */ + unsigned int ClearFaceMarkBits() const; + + /* + Descripiton: + Sets the m_status.MarkBits() value to 0 for the specified components. + Parameters: + bClearVertexMarkBits - [in] + If true, m_status.MarkBits() for every vertex is set to zero. + bClearEdgeMarkBits - [in] + If true, m_status.MarkBits() for every edge is set to zero. + bClearFaceMarkBits - [in] + If true, m_status.MarkBits() for every face is set to zero. + Returns: + Number of group id values that were changed. + */ + unsigned int ClearComponentMarkBits( + bool bClearVertexMarkBits, + bool bClearEdgeMarkBits, + bool bClearFaceMarkBits + ) const; + /* Descripiton: Clears the m_status.RuntimeMark() for every vertex, edge and face. @@ -3314,6 +3547,24 @@ public: ON_SimpleArray< const class ON_SubDComponentBase* >& marked_component_list ) const; + /* + Parameters: + bAddMarkedComponents - [in] + If true, marked components are added to component_list[]. + If false, unmarked components are added to component_list[]. + mark_bits - [in] + If mark_bits is zero, then a component is "marked" if component.Mark() is true. + Otherwise a component is "marked" if mark_bits = component.MarkBits(). + */ + unsigned int GetMarkedComponents( + bool bAddMarkedComponents, + ON__UINT8 mark_bits, + bool bIncludeVertices, + bool bIncludeEdges, + bool bIncludeFaces, + ON_SimpleArray< class ON_SubDComponentPtr >& component_list + ) const; + unsigned int UnselectComponents( bool bUnselectAllVertices, bool bUnselectAllEdges, @@ -3404,9 +3655,7 @@ public: const ON_COMPONENT_INDEX* ci_list, size_t ci_count, bool bExtrudeBoundaries, - bool bPermitNonManifoldEdgeCreation, - ON_SubD::EdgeTag original_edge_tag, - ON_SubD::EdgeTag moved_edge_tag + bool bPermitNonManifoldEdgeCreation ); unsigned int ExtrudeComponents( @@ -3414,10 +3663,8 @@ public: const ON_SubDComponentPtr* cptr_list, size_t cptr_count, bool bExtrudeBoundaries, - bool bPermitNonManifoldEdgeCreation, - ON_SubD::EdgeTag original_edge_tag, - ON_SubD::EdgeTag moved_edge_tag - ); + bool bPermitNonManifoldEdgeCreation + ); private: unsigned int Internal_ExtrudeComponents( @@ -3425,10 +3672,8 @@ private: const ON_SubDComponentPtr* cptr_list, size_t cptr_count, bool bExtrudeBoundaries, - bool bPermitNonManifoldEdgeCreation, - ON_SubD::EdgeTag original_edge_tag, - ON_SubD::EdgeTag moved_edge_tag - ); + bool bPermitNonManifoldEdgeCreation + ); public: @@ -3563,6 +3808,32 @@ public: const double* P ); + /* + Description: + Expert user tool to add a vertex with specified information. This function + is useful when copying portions of an existing SubD to a new SubD. + Parameters: + candidate_vertex_id - [in] + If candidate_vertex_id is > 0 and is available, + the returned value with have id = candidate_vertex_id. + Otherwise a new id will be assigned. + vertex_tag - [in] + Pass ON_SubD::VertexTag::Unset if not known. + P - [in] + nullptr or a 3d point. + initial_edge_capacity - [in] + Initial capacity of the m_edges[] array. Pass 0 if unknown. + initial_face_capacity - [in] + Initial capacity of the m_faces[] array. Pass 0 if unknown. + */ + class ON_SubDVertex* AddVertexForExperts( + unsigned int candidate_vertex_id, + ON_SubD::VertexTag vertex_tag, + const double* P, + unsigned int initial_edge_capacity, + unsigned int initial_face_capacity + ); + /* Description: Search for a vertex with a specificed control net point. @@ -3727,12 +3998,13 @@ public: edge_tag - [in] This expert user function does not automatically set the edge tag. v0 - [in] - v0_sector_coefficient - [in] - v1 - [in] - v1_sector_coefficient - [in] The edge begins at v0 and ends at v1. - The edge will be on the same level as the vertices. - The edges sector weights are set + v0_sector_coefficient - [in] + Pass ON_SubDSectorType::UnsetSectorCoefficient if unknown. + v1 - [in] + The edge begins at v0 and ends at v1. + v1_sector_coefficient - [in] + Pass ON_SubDSectorType::UnsetSectorCoefficient if unknown. */ class ON_SubDEdge* AddEdgeWithSectorCoefficients( ON_SubD::EdgeTag edge_tag, @@ -3742,6 +4014,38 @@ public: double v1_sector_coefficient ); + /* + Description: + Expert user tool to add an edge with specified information. This function + is useful when copying portions of an existing SubD to a new SubD. + Parameters: + candidate_edge_id - [in] + If candidate_edge_id is > 0 and is available, + the returned edge with have id = candidate_edge_id. + Otherwise a new id will be assigned. + edge_tag - [in] + Pass ON_SubD::EdgeTag::Unset if not known. + v0 - [in] + The edge begins at v0 and ends at v1. + v0_sector_coefficient - [in] + Pass ON_SubDSectorType::UnsetSectorCoefficient if unknown. + v1 - [in] + The edge begins at v0 and ends at v1. + v1_sector_coefficient - [in] + Pass ON_SubDSectorType::UnsetSectorCoefficient if unknown. + initial_face_capacity - [in] + Initial face capacity. Pass 0 if unknown. + */ + class ON_SubDEdge* AddEdgeForExperts( + unsigned int candidate_edge_id, + ON_SubD::EdgeTag edge_tag, + class ON_SubDVertex* v0, + double v0_sector_coefficient, + class ON_SubDVertex* v1, + double v1_sector_coefficient, + unsigned int initial_face_capacity + ); + /* Parameters: edge0 - [in] @@ -3902,19 +4206,18 @@ public: /* Parameters: - candidate_face - [in] - If this face was previously deleted and is not currently used, - it will be reused. This allows editing operations that merge - faces to have the result of the merge be returned in a face - that was part of the input set. + candidate_face_id - [in] + If candidate_face_id is > 0 and is available, + the returned face with have id = candidate_face_id. + Otherwise a new id will be assigned. + edge[] - [in] + The edge[] array must be sorted and correct oriented + (edge[i].RelativeVertex(1) == edge[(i+1)%edge_count].RelativeVertex(0)). edge_count - [in] Must be >= 3. - edge[] - [in] - The edge list must be sorted and correct oriented - (edge[i].RelativeVertex(1) == edge[(i+1)%edge_count].RelativeVertex(0)). */ - class ON_SubDFace* AddFace( - const ON_SubDFace* candidate_face, + class ON_SubDFace* AddFaceForExperts( + unsigned candidate_face_id, const class ON_SubDEdgePtr* edge, unsigned int edge_count ); @@ -4109,58 +4412,6 @@ public: size_t capacity ); -////#if defined(OPENNURBS_PLUS) -//// /* -//// Description: -//// Get the limit surface point location and normal for -//// the face's center from the limit mesh grid. -//// Parameters: -//// face - [in] -//// A face in this SubD. -//// P - [out] -//// P = limit surface location or ON_3dPoint::NanPoint if not available. -//// N - [out] -//// N = limit surface unit normal or ON_3dVector::NanVector if not available. -//// Returns: -//// True if the point and normal were set from the limit mesh frament. -//// False if the limit mesh fragment was not found and nan values were returned. -//// */ -//// // TODO - remove this and remove cached limit mesh. -//// bool GetFaceCenterPointAndNormalX( -//// const class ON_SubDFace* face, -//// double* P, -//// double* N -//// ) const; -////#endif - -////#if defined(OPENNURBS_PLUS) -//// /* -//// Description: -//// Get the limit surface point location and normal for -//// the edge's midpoint from the limit mesh grid. -//// Parameters: -//// edge - [in] -//// An edge in this SubD. -//// edge_face_index - [in] -//// Index of the face to use for the normal. If the edge is a crease, then -//// each attached face may have a different normal. Pass 0 when in doubt. -//// P - [out] -//// P = limit surface location or ON_3dPoint::NanPoint if not available. -//// N - [out] -//// N = limit surface unit normal or ON_3dVector::NanVector if not available. -//// Returns: -//// True if the point and normal were set from the limit mesh frament. -//// False if the limit mesh fragment was not found and nan values were returned. -//// */ -//// // TODO - remove this and remove cached limit mesh. -//// bool GetEdgeCenterPointAndNormalX( -//// const class ON_SubDEdge* edge, -//// unsigned int edge_face_index, -//// double* P, -//// double* N -//// ) const; -////#endif - /* Description: @@ -7249,12 +7500,16 @@ public: // Once assigned, m_id is never changed and that allows ON_SubD component // indices to work. - // Id assigned to this component. Never modify this value. It is assigned - // by allocators and used to find the component from an ON_COMPONENT_INDEX. + // Id assigned to this component. NEVER MODIFY THE m_id VALUE. + // It is assigned by allocators and used to find the component + // from an ON_COMPONENT_INDEX. unsigned int m_id = 0; private: // The m_archive_id must be immediately after the m_id field. + // A value of ON_UNSET_UINT_INDEX indicate the component was + // in use and then deleted. See ON_SubDHeap Return...() functions + // for more details. mutable unsigned int m_archive_id = 0; public: @@ -7284,8 +7539,10 @@ public: Returns: The current value of the component mark ( m_status->RuntimeMark() ). Remarks: - SubD components have a mutable runtime mark that can be used + SubD components have a mutable runtime mark that can be used in any context where a single thread cares about the marks. + Any code can use Mark() at any time. You cannot assume that + other functions including const will not change its value. It is widely used in many calculations to keep track of sets of components that are in a certain context specfic state. */ @@ -7295,8 +7552,10 @@ public: Description: Clears (sets to false) the value of the component mark. Remarks: - SubD components have a mutable runtime mark that can be used + SubD components have a mutable runtime mark that can be used in any context where a single thread cares about the marks. + Any code can use Mark() at any time. You cannot assume that + other functions including const will not change its value. It is widely used in many calculations to keep track of sets of components that are in a certain context specfic state. Returns: @@ -7308,8 +7567,10 @@ public: Description: Sets (sets to true) the value of the component mark. Remarks: - SubD components have a mutable runtime mark that can be used + SubD components have a mutable runtime mark that can be used in any context where a single thread cares about the marks. + Any code can use Mark() at any time. You cannot assume that + other functions including const will not change its value. It is widely used in many calculations to keep track of sets of components that are in a certain context specfic state. Returns: @@ -7325,6 +7586,8 @@ public: Remarks: SubD components have a mutable runtime mark that can be used in any context where a single thread cares about the marks. + Any code can use Mark() at any time. You cannot assume that + other functions including const will not change its value. It is widely used in many calculations to keep track of sets of components that are in a certain context specfic state. Returns: @@ -7334,6 +7597,73 @@ public: bool bMark ) const; + /* + Returns: + The current value of the component mark bits ( m_status->MarkBits() ). + Remarks: + Mark() and MarkBits() are independent. + + SubD components have a mutable runtime mark bits that can be used + in any context where a single thread cares about the mark bits value. + Any code can use MarkBits() at any time. You cannot assume that + other functions including const will not change their value. + It is widely used in many calculations to keep track of sets of + components that are in a certain context specfic state. + + MarkBits() is used in more complex calculations where the single true/false + provided by Mark() is not sufficient. Typically MarkBits() is used when + a collecection of components needs to be partitioned into more than two + sets or when the value of Mark() cannot be changed. + */ + ON__UINT8 MarkBits() const; + + /* + Returns: + Set the component mark bits ( m_status->SetMarkBits( mark_bits ) ). + Remarks: + Mark() and MarkBits() are independent. + + SubD components have a mutable runtime mark bits that can be used + in any context where a single thread cares about the mark bits value. + Any code can use MarkBits() at any time. You cannot assume that + other functions including const will not change their value. + It is widely used in many calculations to keep track of sets of + components that are in a certain context specfic state. + + MarkBits() is used in more complex calculations where the single true/false + provided by Mark() is not sufficient. Typically MarkBits() is used when + a collecection of components needs to be partitioned into more than two + sets or when the value of Mark() cannot be changed. + Returns: + Input value of MarkBits(). + */ + ON__UINT8 SetMarkBits( + ON__UINT8 mark_bits + ) const; + + /* + Returns: + Set the component mark bits to 0 ( m_status->SetMarkBits( 0 ) ). + Remarks: + Mark() and MarkBits() are independent. + + SubD components have a mutable runtime mark bits that can be used + in any context where a single thread cares about the mark bits value. + Any code can use MarkBits() at any time. You cannot assume that + other functions including const will not change their value. + It is widely used in many calculations to keep track of sets of + components that are in a certain context specfic state. + + MarkBits() is used in more complex calculations where the single true/false + provided by Mark() is not sufficient. Typically MarkBits() is used when + a collecection of components needs to be partitioned into more than two + sets or when the value of Mark() cannot be changed. + Returns: + Input value of MarkBits(). + */ + ON__UINT8 ClearMarkBits() const; + + public: ////////////////////////////////////////////////////////////// @@ -7460,10 +7790,6 @@ protected: mutable unsigned char m_saved_points_flags = 0U; unsigned char m_level = 0U; - -private: - unsigned char m_reserved_byte = 0U; - public: // All the faces with the same nonzero value of m_group_id are in the same "group". @@ -7562,7 +7888,7 @@ public: /* Returns: - True if there are no null edges and there are at least two edges and they all have two faces. + True if there are no null edges, exactly two boundary edges, and any other edges have two faces. Remarks: Tags are ignored. */ @@ -7660,6 +7986,10 @@ public: ON_SubDVertex(const ON_SubDVertex&) = default; ON_SubDVertex& operator=(const ON_SubDVertex&) = default; +public: + unsigned int VertexId() const; + + public: /* Description: @@ -7788,6 +8118,26 @@ public: const class ON_SubDFace* f ); + /* + Parameters: + bApplyInputTagBias - [in] + If bApplyInputTagBias is true, the current tag value is used + to choose between possible output results. When in doubt, pass false. + bReturnBestGuessWhenInvalid + If bReturnBestGuessWhenInvalid is true and the topology and current edges + tags do not meet the conditions for a valid vertex, then a best guess for + a vertex tag is returned. Otherwise ON_SubD::VertexTag::Unset is returned. + When in doubt pass false and check for unset before using. + Returns: + The suggested value for this vertices tag based on its current tag value and + its current edges. Assumes the vertex and edge topology are correct and the + edge tags are correctly set. + */ + ON_SubD::VertexTag SuggestedVertexTag( + bool bApplyInputTagBias, + bool bReturnBestGuessWhenInvalid + ) const; + public: double m_P[3]; // vertex control net location @@ -8045,6 +8395,36 @@ public: */ bool HasBoundaryVertexTopology() const; + /* + Returns: + If this vertex has two boundary edges, they are returned in the pair with + BoundaryEdgePair().First().EdgePtr().RelativeVetex(0) and + BoundaryEdgePair().Second().EdgePtr().RelativeVetex(0) + equal to this vertex. + Otherwise ON_SubDComponentPtrPair::Null is returned. + */ + const ON_SubDComponentPtrPair BoundaryEdgePair() const; + + /* + Returns: + If this vertex has two creased edges, they are returned in the pair. + Otherwise ON_SubDComponentPtrPair::Null is returned. + */ + const ON_SubDComponentPtrPair CreasedEdgePair( + bool bInteriorEdgesOnly + ) const; + + + /* + Returns: + If this vertex has one creased edge, it is returned. + Otherwise ON_SubDEdgePtr::Null is returned. + */ + const ON_SubDEdgePtr CreasedEdge( + bool bInteriorEdgesOnly + ) const; + + /* Parameters: bUseSavedSubdivisionPoint - [in] @@ -8174,12 +8554,31 @@ public: */ unsigned int MarkedEdgeCount() const; + /* + Description: + Clears all marks on edges. + Returns: + true if all edges are not null. + false if any edges are null. + */ + bool ClearEdgeMarks() const; + /* Returns: Number of faces attached to this vertex with Face().m_status.RuntimeMark() = true; */ unsigned int MarkedFaceCount() const; + + /* + Description: + Clears all marks on faces. + Returns: + true if all faces are not null. + false if any faces are null. + */ + bool ClearFaceMarks() const; + /* Returns: Minimum number of edges for any face attached to this vertex. @@ -8275,6 +8674,9 @@ public: ON_SubDEdge(const ON_SubDEdge&) = default; ON_SubDEdge& operator=(const ON_SubDEdge&) = default; +public: + unsigned int EdgeId() const; + public: /* Description: @@ -8491,13 +8893,35 @@ private: mutable class ON_SubDEdgeSurfaceCurve* m_limit_curve = nullptr; public: - unsigned int FaceCount() const; /* Returns: - True if the edge has two faces and with opposite face directions. + Number of distinct non-nullptr vertices. + If the edge is valid, this will be 2. */ - bool IsInteriorEdge() const; + unsigned int VertexCount() const; + + unsigned int FaceCount() const; + + /* + Parameters: + bRequireSameFaceOrientation - [in] + If true, the attached faces must use the edge with opposite directions (oriented manifold). + Returns: + True if the edge has two distinct faces. + */ + bool HasInteriorEdgeTopology( + bool bRequireOppositeFaceDirections + ) const; + + /* + Parameters: + bRequireSameFaceOrientation - [in] + If true, the attached faces must use the edge with opposite directions (oriented manifold). + Returns: + True if the edge has two distinct faces. + */ + bool HasBoundaryEdgeTopology() const; const ON_SubDFacePtr FacePtr( unsigned int i @@ -8901,6 +9325,9 @@ public: ON_SubDFace(const ON_SubDFace&) = default; ON_SubDFace& operator=(const ON_SubDFace&) = default; +public: + unsigned int FaceId() const; + public: /* Description: @@ -9012,7 +9439,43 @@ private: private: unsigned char m_reserved1 = 0; - unsigned short m_reserved2 = 0; + +private: + // The application specifies a base ON_Material used to render the subd this face belongs to. + // If m_material_channel_index > 0 AND face_material_id = base.MaterialChannelIdFromIndex(m_material_channel_index) + // is not nil, then face_material_id identifies an override rendering material for this face. + // Othewise base will be used to render this face. + mutable unsigned short m_material_channel_index = 0; + +public: + /* + Description: + Set this face's rendering material channel index. + + Parameters: + material_channel_index - [in] + A value between 0 and ON_Material::MaximumMaterialChannelIndex, inclusive. + This value is typically 0 or the value returned from ON_Material::MaterialChannelIndexFromId(). + + Remarks: + If base_material is the ON_Material assigned to render this subd and + ON_UUID face_material_id = base_material.MaterialChannelIdFromIndex( material_channel_index ) + is not nil, then face_material_id identifies an override rendering material for this face. + Otherwise base_material is used to reneder this face. + */ + void SetMaterialChannelIndex(int material_channel_index) const; + + /* + Returns: + This face's rendering material channel index. + + Remarks: + If base_material is the ON_Material assigned to render this subd, MaterialChannelIndex() > 0, + and ON_UUID face_material_id = base_material.MaterialChannelIdFromIndex( face.MaterialChannelIndex() ) + is not nil, then face_material_id identifies an override rendering material for this face. + Otherwise base_material is used to reneder this face. + */ + int MaterialChannelIndex() const; public: const bool TextureDomainIsSet() const; @@ -9178,6 +9641,28 @@ public: const ON_SubDVertex* vertex ) const; + /* + Returns; + If the vertex is in this face's boundary, pair of face boundary edges at the vertex is returned + with face boundary orientations, that is vertex = pair.First().EdgePtr().RelativeVertex(1) + and vertex = pair.Second().EdgePtr().RelativeVertex(0). Otherwise, ON_SubDComponentPtrPair::Null + is returned. + */ + const ON_SubDComponentPtrPair VertexEdgePair( + const ON_SubDVertex* vertex + ) const; + + /* + Returns; + If the vertex is in this face's boundary, pair of face boundary edges at the vertex is returned + with face boundary orientations, that is vertex = pair.First().EdgePtr().RelativeVertex(1) + and vertex = pair.Second().EdgePtr().RelativeVertex(0). Otherwise, ON_SubDComponentPtrPair::Null + is returned. + */ + const ON_SubDComponentPtrPair VertexEdgePair( + unsigned vertex_index + ) const; + const class ON_SubDEdge* Edge( unsigned int i ) const; @@ -9186,6 +9671,11 @@ public: unsigned int i ) const; + /* + Returns: + If e is part of the face's boundary, then the index of the edge is returned. + Otherwise, ON_UNSET_UINT_INDEX is returned. + */ unsigned int EdgeArrayIndex( const ON_SubDEdge* e ) const; @@ -10791,7 +11281,8 @@ public: initial iterator orientation. Returns: The requested edge or nullptr if the iterator is not initialized, - has terminated, or is not valid. + has terminated, or is not valid. + When the sector iterator is initialized and valid, sit.CenterVertex() = CurrentEdge(*).RelativeVertex(0). */ const ON_SubDEdge* CurrentEdge( unsigned int face_side_index @@ -10948,13 +11439,12 @@ private: // m_current_eptr[0].Edge() = "prev" side edge // m_current_eptr[1].Edge() = "next" side edge - // When m_current_eptr[i].Edge() is not null, - // center vertex = m_current_eptr[i].Edge()->m_vertex[m_current_eptr[i].Direction()] - ON_SubDEdgePtr m_current_eptr[2]; // default = { ON_SubDEdgePtr::Null, ON_SubDEdgePtr::Null }; + // center vertex = m_current_eptr[i].RelativeVertex(0) + ON_SubDEdgePtr m_current_eptr[2] = {}; // default = { ON_SubDEdgePtr::Null, ON_SubDEdgePtr::Null }; unsigned int m_initial_fvi = 0; unsigned int m_current_fvi = 0; - unsigned int m_current_fei[2]; // default = { 0, 0 }; // "prev" and "next" + unsigned int m_current_fei[2] = {}; // default = { 0, 0 }; // "prev" and "next" // m_initial_face_dir // 0: "next" means clockwise with respect to the initial face's orientation. @@ -12655,9 +13145,9 @@ private: ON_SubDRef m_subd_ref; ON_SimpleArray m_edge_chain; ON_UniqueTester m_unique_tester; - bool m_bEnableStatusCheck = false; ON_ComponentStatus m_status_check_pass = ON_ComponentStatus::NoneSet; ON_ComponentStatus m_status_check_fail = ON_ComponentStatus::Selected; + bool m_bEnableStatusCheck = false; }; class ON_CLASS ON_SubDComponentFilter diff --git a/opennurbs_subd_archive.cpp b/opennurbs_subd_archive.cpp index 6d69b795..5cb26581 100644 --- a/opennurbs_subd_archive.cpp +++ b/opennurbs_subd_archive.cpp @@ -57,7 +57,7 @@ static bool Internal_ReadDouble3( enum : unsigned char { ON_SubDComponentArchiveAnonymousChunkMark = 254U, - ON_SubDComponentArchiveEndMark = 255U + ON_SubDComponentArchiveAdditionEndMark = 255U }; static bool Internal_ReadComponentAdditionSize(ON_BinaryArchive& archive, unsigned char valid_sz, unsigned char* sz) @@ -69,10 +69,9 @@ static bool Internal_ReadComponentAdditionSize(ON_BinaryArchive& archive, unsign if (0 == valid_sz) return ON_SUBD_RETURN_ERROR(false); - *sz = (1 == valid_sz) ? 2 : 1; if (false == archive.ReadChar(sz)) return ON_SUBD_RETURN_ERROR(false); - if ( 0 != *sz && valid_sz != *sz ) + if ( 0 != *sz && valid_sz != *sz && ON_SubDComponentArchiveAdditionEndMark != *sz ) return ON_SUBD_RETURN_ERROR(false); return true; } @@ -106,7 +105,7 @@ static bool Internal_FinishReadingComponentAdditions(ON_BinaryArchive& archive) for (;;) { - if (ON_SubDComponentArchiveEndMark == sz) + if (ON_SubDComponentArchiveAdditionEndMark == sz) return true; if (ON_SubDComponentArchiveAnonymousChunkMark == sz) { @@ -120,7 +119,9 @@ static bool Internal_FinishReadingComponentAdditions(ON_BinaryArchive& archive) else if ( sz > 0 ) { // skip an addition a future version added as a fixed number of bytes - if (false == archive.SeekForward(sz)) + // use archive.ReadByte(sz,buffer) instead of archive.SeekForward(sz) so CRC is properly calculated. + char buffer[256]; + if (false == archive.ReadByte(sz,buffer)) break; } sz = 0; @@ -134,7 +135,7 @@ static bool Internal_FinishWritingComponentAdditions(ON_BinaryArchive& archive) { if (archive.Archive3dmVersion() < 70) return ON_SUBD_RETURN_ERROR(false); - const unsigned char sz = ON_SubDComponentArchiveEndMark; + const unsigned char sz = ON_SubDComponentArchiveAdditionEndMark; return archive.WriteChar(sz); } @@ -273,8 +274,11 @@ static bool ReadBase( unsigned char sz; // 24 byte displacement addition + sz = 0; if (false == Internal_ReadComponentAdditionSize(archive, 24, &sz)) break; + if (ON_SubDComponentArchiveAdditionEndMark == sz) + return true; // end of additions if (0 != sz) { double V[3] = {}; @@ -284,8 +288,11 @@ static bool ReadBase( } // 4 byte group id addition + sz = 0; if (false == Internal_ReadComponentAdditionSize(archive, 4, &sz)) break; + if (ON_SubDComponentArchiveAdditionEndMark == sz) + return true; // end of additions if (0 != sz) { if (!archive.ReadInt(&base.m_group_id)) @@ -767,6 +774,7 @@ bool ON_SubDVertex::Read( break; ON_SubDVertex* v = subdimple->AllocateVertex( + base.m_id, // serialization must preserve ON_SubDVertex.m_id ON_SubD::VertexTagFromUnsigned(vertex_tag), base.SubdivisionLevel(), P, @@ -889,6 +897,7 @@ bool ON_SubDEdge::Read( break; ON_SubDEdge* e = subdimple->AllocateEdge( + base.m_id, // serialization must preserve ON_SubDEdge.m_id ON_SubD::EdgeTagFromUnsigned(edge_tag), base.SubdivisionLevel(), face_count @@ -972,6 +981,32 @@ bool ON_SubDFace::Write( break; } + //////if ( ON::Version() >= ON_VersionNumberConstruct(7, 0, 2020, 2, 18, 0) ) + { + ////// https://mcneel.myjetbrains.com/youtrack/issue/RH-56820 + ////// Today is Feb 3, 2020. + ////// We need to give Rhino 7 WIP users until Feb 18, 2020 to get automatically + ////// updated before we can save the material index in 3dm archives. + ////// This is because there was a bug in Internal_FinishReadingComponentAdditions() + ////// that failed to keep the chunk CRCs up to date when older versions of Rhino + ////// skipped over newer information. + ////// This CRC bug was fixed Feb 3, 2020 and made available to customers Feb 4, 2020. + ////// On or after Feb 18, 2020, this comment and the if ( ON::Version() >= ON_VersionNumberConstruct(7, 0, 2020, 2, 18, 0) ).... + ////// check can be removed. + // + // Done Feb 20, 2020. The comment can be deleted in March 2020 or later because by then this code will have been well tested. + + const int material_channel_index = MaterialChannelIndex(); + const bool bWriteMaterialChannelIndex = (material_channel_index > 0 && material_channel_index <= ON_Material::MaximumMaterialChannelIndex); + if (false == Internal_WriteComponentAdditionSize(bWriteMaterialChannelIndex, archive, 4)) + break; + if (bWriteMaterialChannelIndex) + { + if (!archive.WriteInt(material_channel_index)) + break; + } + } + return Internal_FinishWritingComponentAdditions(archive); } return ON_SUBD_RETURN_ERROR(false); @@ -1006,6 +1041,7 @@ bool ON_SubDFace::Read( break; ON_SubDFace* f = subdimple->AllocateFace( + base.m_id, // serialzation must preserve ON_SubDFace.m_id base.SubdivisionLevel(), edge_count ); @@ -1034,8 +1070,12 @@ bool ON_SubDFace::Read( // read additions unsigned char sz; + + sz = 0; if (false == Internal_ReadComponentAdditionSize(archive, 34, &sz)) break; + if (ON_SubDComponentArchiveAdditionEndMark == sz) + return true; // end of additions if ( 0 != sz) { // 34 bytes of texture domain information @@ -1055,6 +1095,20 @@ bool ON_SubDFace::Read( f->SetTextureDomain(ON_SubD::TextureDomainTypeFromUnsigned(domain_type), texture_domain_origin, texture_domain_delta, packing_rot); } + sz = 0; + if (false == Internal_ReadComponentAdditionSize(archive, 4, &sz)) + break; + if (ON_SubDComponentArchiveAdditionEndMark == sz) + return true; // end of additions + if (0 != sz) + { + // 4 bytes of material channel index + int material_channel_index = 0; + if (false == archive.ReadInt(&material_channel_index)) + break; + f->SetMaterialChannelIndex(material_channel_index); + } + return Internal_FinishReadingComponentAdditions(archive); } return ON_SUBD_RETURN_ERROR(false); @@ -1087,7 +1141,9 @@ unsigned int ON_SubDLevel::SetArchiveId( for (unsigned int listdex = 0; listdex < 3; listdex++) { - bLevelLinkedListIncreasingId[listdex] = false; + bLevelLinkedListIncreasingId[listdex] + = nullptr != first_link[listdex] + && first_link[listdex]->m_id > 0U; unsigned int prev_id = 0; archive_id_partition[listdex] = archive_id; unsigned int linked_list_count = 0; @@ -1096,18 +1152,17 @@ unsigned int ON_SubDLevel::SetArchiveId( ++linked_list_count; if (prev_id < clink->m_id) { - bLevelLinkedListIncreasingId[listdex] = true; prev_id = clink->m_id; clink->SetArchiveId(archive_id++); continue; } + // the for(..) scope we are currently in is exited below. bLevelLinkedListIncreasingId[listdex] = false; // m_id values are not increasing in the linked list. // This happens when the subd is edited and components are deleted // and then added back later. - // Finish counting components in the linked list. for (clink = clink->m_next; nullptr != clink; clink = clink->m_next) ++linked_list_count; @@ -1417,7 +1472,7 @@ bool ON_SubDimple::Write( { const_cast< ON_SubDHeap* >(&m_heap)->ClearArchiveId(); - const int minor_version = (archive.Archive3dmVersion() < 70) ? 0 : 2; + const int minor_version = (archive.Archive3dmVersion() < 70) ? 0 : 3; if ( !archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK, 1, minor_version) ) return ON_SUBD_RETURN_ERROR(false); bool rc = false; @@ -1436,11 +1491,11 @@ bool ON_SubDimple::Write( if (!archive.WriteInt(level_count)) break; - if (!archive.WriteInt(m_max_vertex_id)) + if (!archive.WriteInt(MaximumVertexId())) break; - if (!archive.WriteInt(m_max_edge_id)) + if (!archive.WriteInt(MaximumEdgeId())) break; - if (!archive.WriteInt(m_max_face_id)) + if (!archive.WriteInt(MaximumFaceId())) break; // a global bounding box was saved before May 2015. @@ -1475,6 +1530,10 @@ bool ON_SubDimple::Write( if (false == m_symmetry.Write(archive)) break; + // runtime content number used to compare with the on on m_symmetry + if (false == archive.WriteBigInt(ContentSerialNumber())) + break; + rc = true; break; } @@ -1497,9 +1556,12 @@ bool ON_SubDimple::Read( return ON_SUBD_RETURN_ERROR(false); bool rc = false; - unsigned int max_vertex_id = 0; - unsigned int max_edge_id = 0; - unsigned int max_face_id = 0; + + // Code before Feb 10, 2020 cared about these values + unsigned int obsolete_archive_max_vertex_id = 0; + unsigned int obsolete_archive_max_edge_id = 0; + unsigned int obsolete_archive_max_face_id = 0; + ON__UINT64 saved_content_serial_number = 0; for (;;) { @@ -1511,11 +1573,11 @@ bool ON_SubDimple::Read( break; const unsigned int level_count = i; - if (!archive.ReadInt(&max_vertex_id)) + if (!archive.ReadInt(&obsolete_archive_max_vertex_id)) break; - if (!archive.ReadInt(&max_edge_id)) + if (!archive.ReadInt(&obsolete_archive_max_edge_id)) break; - if (!archive.ReadInt(&max_face_id)) + if (!archive.ReadInt(&obsolete_archive_max_face_id)) break; @@ -1536,17 +1598,6 @@ bool ON_SubDimple::Read( m_active_level = level; } - - const unsigned int heap_max_vertex_id = m_heap.MaximumVertexId(); - const unsigned int heap_max_edge_id = m_heap.MaximumEdgeId(); - const unsigned int heap_max_face_id = m_heap.MaximumFaceId(); - if (m_max_vertex_id < heap_max_vertex_id) - m_max_vertex_id = heap_max_vertex_id; - if (m_max_edge_id < heap_max_edge_id) - m_max_edge_id = heap_max_edge_id; - if (m_max_face_id < heap_max_face_id) - m_max_face_id = heap_max_face_id; - if ( level_index != level_count) break; @@ -1564,6 +1615,12 @@ bool ON_SubDimple::Read( { if (false == m_symmetry.Read(archive)) break; + + if (minor_version >= 3) + { + if (false == archive.ReadBigInt(&saved_content_serial_number)) + break; + } } } @@ -1574,54 +1631,24 @@ bool ON_SubDimple::Read( rc = false; // Heap id validation. Always an error if max_heap_..._id > m_max_..._id. - if (false == m_heap.IsValid()) + if (false == m_heap.IsValid(false,nullptr)) { ON_SUBD_ERROR("m_heap.IsValid() is false."); - m_heap.ResetId(); // breaks component id references, but this is a serious error. - } - const unsigned int max_heap_vertex_id = m_heap.MaximumVertexId(); - const unsigned int max_heap_edge_id = m_heap.MaximumEdgeId(); - const unsigned int max_heap_face_id = m_heap.MaximumFaceId(); - if (m_max_vertex_id < max_heap_vertex_id) - { - ON_SUBD_ERROR("m_max_vertex_id is too small."); - m_max_vertex_id = max_heap_vertex_id; - } - if (m_max_edge_id < max_heap_edge_id) - { - ON_SUBD_ERROR("m_max_edge_id is too small."); - m_max_edge_id = max_heap_edge_id; - } - if (m_max_face_id < max_heap_face_id) - { - ON_SUBD_ERROR("m_max_face_id is too small."); - m_max_face_id = max_heap_face_id; - } - - if (max_vertex_id > m_max_vertex_id) - m_max_vertex_id = max_vertex_id; - if (max_edge_id > m_max_edge_id) - m_max_edge_id = max_edge_id; - if (max_face_id > m_max_face_id) - m_max_face_id = max_face_id; - - const unsigned int archive_version = archive.ArchiveOpenNURBSVersion(); - const unsigned int bad_counts_cutoff_version = ON_VersionNumberConstruct(6, 12, 2018, 12, 12, 0); - if (archive_version > bad_counts_cutoff_version) - { - if( m_max_vertex_id != max_vertex_id - || m_max_edge_id != max_edge_id - || m_max_face_id != max_face_id - ) - { - ON_SUBD_ERROR("Correct m_max_verrtex/edge/face_id differs from value saved in 3dm archive."); - } + m_heap.ResetIds(); // breaks component id references, but this is a serious error. } - if (minor_version >= 1) - { + const bool bSetSymmetricObjectContentSerialNumber + = saved_content_serial_number > 0 + && m_symmetry.IsSet() + && saved_content_serial_number == m_symmetry.SymmetricObjectContentSerialNumber() + ; - } + ChangeContentSerialNumber(false); + + if ( bSetSymmetricObjectContentSerialNumber ) + m_symmetry.SetSymmetricObjectContentSerialNumber(ContentSerialNumber()); + else + m_symmetry.ClearSymmetricObjectContentSerialNumber(); if (rc) return true; diff --git a/opennurbs_subd_copy.cpp b/opennurbs_subd_copy.cpp index 88e9abf5..60b4770e 100644 --- a/opennurbs_subd_copy.cpp +++ b/opennurbs_subd_copy.cpp @@ -90,6 +90,7 @@ ON_SubDVertex* ON_SubDArchiveIdMap::CopyVertex( if ( nullptr == source_vertex ) return ON_SUBD_RETURN_ERROR(nullptr); ON_SubDVertex* vertex = subdimple.AllocateVertex( + source_vertex->m_id, source_vertex->m_vertex_tag, source_vertex->m_level, source_vertex->m_P, @@ -137,6 +138,7 @@ ON_SubDEdge* ON_SubDArchiveIdMap::CopyEdge( if ( nullptr == source_edge ) return ON_SUBD_RETURN_ERROR(nullptr); ON_SubDEdge* edge = subdimple.AllocateEdge( + source_edge->m_id, source_edge->m_edge_tag, source_edge->m_level, source_edge->m_face_count); @@ -175,7 +177,7 @@ ON_SubDFace* ON_SubDArchiveIdMap::CopyFace( { if ( nullptr == source_face ) return ON_SUBD_RETURN_ERROR(nullptr); - ON_SubDFace* face = subdimple.AllocateFace(source_face->m_level,source_face->m_edge_count); + ON_SubDFace* face = subdimple.AllocateFace( source_face->m_id, source_face->m_level,source_face->m_edge_count); if ( nullptr == face ) return ON_SUBD_RETURN_ERROR(nullptr); @@ -643,9 +645,24 @@ ON__UINT64 ON_SubDimple::ContentSerialNumber() const return m_subd_content_serial_number; } -ON__UINT64 ON_SubDimple::ChangeContentSerialNumber() const +ON__UINT64 ON_SubDimple::ChangeContentSerialNumber( + bool bChangePreservesSymmetry +) const { - return (m_subd_content_serial_number = ON_NextContentSerialNumber()); + const bool bUpdateSymmetricObjectContentSerialNumber + = bChangePreservesSymmetry + && m_subd_content_serial_number > 0 + && m_symmetry.IsSet() + && m_subd_content_serial_number == m_symmetry.SymmetricObjectContentSerialNumber() + ; + + m_subd_content_serial_number = ON_NextContentSerialNumber(); + if (bUpdateSymmetricObjectContentSerialNumber) + m_symmetry.SetSymmetricObjectContentSerialNumber(m_subd_content_serial_number); + else + m_symmetry.ClearSymmetricObjectContentSerialNumber(); + + return m_subd_content_serial_number; } void ON_SubDimple::SetManagedMeshSubDWeakPointers( @@ -727,19 +744,19 @@ ON_SubDimple::ON_SubDimple(const ON_SubDimple& src) if ( src.m_active_level == src_level ) m_active_level = level; } - if (src.m_max_vertex_id > m_max_vertex_id) - m_max_vertex_id = src.m_max_vertex_id; - if (src.m_max_edge_id > m_max_edge_id) - m_max_edge_id = src.m_max_edge_id; - if (src.m_max_face_id > m_max_face_id) - m_max_face_id = src.m_max_face_id; m_subd_appearance = src.m_subd_appearance; m_texture_domain_type = src.m_texture_domain_type; m_texture_mapping_tag = src.m_texture_mapping_tag; + m_symmetry = src.m_symmetry; - ChangeContentSerialNumber(); + ChangeContentSerialNumber(false); + + if (src.m_subd_content_serial_number == src.m_symmetry.SymmetricObjectContentSerialNumber()) + m_symmetry.SetSymmetricObjectContentSerialNumber(ContentSerialNumber()); + else + m_symmetry.ClearSymmetricObjectContentSerialNumber(); } bool ON_SubDLevel::IsEmpty() const @@ -762,9 +779,6 @@ int ON_SubDComponentBaseLink::CompareId(ON_SubDComponentBaseLink const*const* lh return 0; } - - - void ON_SubDLevelComponentIdIterator::Initialize( bool bLevelLinkedListIncreasingId, ON_SubDComponentPtr::Type ctype, diff --git a/opennurbs_subd_data.cpp b/opennurbs_subd_data.cpp index 96cd3dc3..a50d3f23 100644 --- a/opennurbs_subd_data.cpp +++ b/opennurbs_subd_data.cpp @@ -45,7 +45,14 @@ ON_SubDimple::~ON_SubDimple() void ON_SubDimple::Clear() { + m_subd_appearance = ON_SubD::DefaultSubDAppearance; + m_texture_domain_type = ON_SubDTextureDomainType::Unset; + m_texture_mapping_tag = ON_MappingTag::Unset; + for (unsigned i = 0; i < m_levels.UnsignedCount(); ++i) + delete m_levels[i]; + m_active_level = nullptr; m_heap.Clear(); + m_symmetry = ON_Symmetry::Unset; } void ON_SubDimple::ClearLevelContents( @@ -56,7 +63,7 @@ void ON_SubDimple::ClearLevelContents( return; if (level == m_active_level) - ChangeContentSerialNumber(); + ChangeContentSerialNumber(false); level->ResetFaceArray(); level->ResetEdgeArray(); @@ -94,10 +101,12 @@ void ON_SubDimple::ClearLevelContents( } -void ON_SubDimple::ClearHigherSubdivisionLevels( +unsigned int ON_SubDimple::ClearHigherSubdivisionLevels( unsigned int max_level_index ) { + const unsigned int original_level_count = m_levels.UnsignedCount(); + if (max_level_index+1 < m_levels.UnsignedCount()) { unsigned int level_count = m_levels.UnsignedCount(); @@ -106,7 +115,7 @@ void ON_SubDimple::ClearHigherSubdivisionLevels( if ( level_count > max_level_index ) { m_active_level = m_levels[max_level_index]; - ChangeContentSerialNumber(); + ChangeContentSerialNumber(false); } } @@ -120,7 +129,7 @@ void ON_SubDimple::ClearHigherSubdivisionLevels( if (level_count != m_levels.UnsignedCount()) { Clear(); - return; + break; } if ( nullptr == level ) @@ -131,10 +140,12 @@ void ON_SubDimple::ClearHigherSubdivisionLevels( delete level; } } + + return original_level_count - m_levels.UnsignedCount(); } -void ON_SubDimple::ClearLowerSubdivisionLevels( +unsigned int ON_SubDimple::ClearLowerSubdivisionLevels( unsigned int min_level_index ) { @@ -145,7 +156,7 @@ void ON_SubDimple::ClearLowerSubdivisionLevels( if (nullptr != m_active_level && m_active_level->m_level_index < min_level_index) { m_active_level = m_levels[min_level_index]; - ChangeContentSerialNumber(); + ChangeContentSerialNumber(false); } for ( unsigned int level_index = 0; level_index < min_level_index; level_index++) @@ -188,6 +199,16 @@ void ON_SubDimple::ClearLowerSubdivisionLevels( } m_levels.SetCount(new_level_index); } + + return original_level_count - m_levels.UnsignedCount(); +} + +unsigned int ON_SubDimple::ClearInactiveLevels() +{ + const unsigned active_level_index = this->ActiveLevelIndex(); + unsigned c1 = ClearHigherSubdivisionLevels(active_level_index); + unsigned c0 = ClearLowerSubdivisionLevels(active_level_index); + return c0 + c1; } void ON_SubDimple::Destroy() @@ -212,7 +233,7 @@ ON_SubDLevel* ON_SubDimple::ActiveLevel(bool bCreateIfNeeded) { unsigned int level_index = (m_levels.UnsignedCount() > 0) ? (m_levels.UnsignedCount()-1) : 0U; m_active_level = SubDLevel(level_index,bCreateIfNeeded && 0 == m_levels.UnsignedCount()); - ChangeContentSerialNumber(); + ChangeContentSerialNumber(false); } return m_active_level; } @@ -233,7 +254,7 @@ class ON_SubDLevel* ON_SubDimple::SubDLevel( if (nullptr == m_active_level) { m_active_level = level; - ChangeContentSerialNumber(); + ChangeContentSerialNumber(false); } } @@ -790,6 +811,8 @@ bool ON_SubDimple::Transform( const ON_Xform& xform ) { + const ON__UINT64 content_serial_number0 = ContentSerialNumber(); + if (false == xform.IsValid()) return false; if (xform.IsZero()) @@ -832,9 +855,31 @@ bool ON_SubDimple::Transform( } + if (m_symmetry.IsSet()) { + const ON_Symmetry symmetry0 = m_symmetry; m_symmetry = m_symmetry.TransformConditionally(xform); + bool bSetContentSerialNumber = false; + if (content_serial_number0 == symmetry0.SymmetricObjectContentSerialNumber()) + { + // see if the transformed object will still be symmetric. + if (ON_Symmetry::Coordinates::Object == m_symmetry.SymmetryCoordinates()) + { + // object is still symmetric. + bSetContentSerialNumber = true; + } + else if (ON_Symmetry::Coordinates::World == m_symmetry.SymmetryCoordinates()) + { + // if transform didn't move the symmetric + if ( 0 == ON_Symmetry::CompareSymmetryTransformation(&symmetry0, &m_symmetry, ON_UNSET_VALUE) ) + bSetContentSerialNumber = true; + } + } + if (bSetContentSerialNumber) + m_symmetry.SetSymmetricObjectContentSerialNumber(ContentSerialNumber()); + else + m_symmetry.ClearSymmetricObjectContentSerialNumber(); } else { diff --git a/opennurbs_subd_data.h b/opennurbs_subd_data.h index d3ae0565..82f7acd7 100644 --- a/opennurbs_subd_data.h +++ b/opennurbs_subd_data.h @@ -1268,17 +1268,22 @@ public: ON_SubDHeap(); ~ON_SubDHeap(); - class ON_SubDVertex* AllocateVertexAndSetId(unsigned int& max_vertex_id); +private: + static class ON_SubDComponentBase* Internal_AllocateComponentAndSetId( + ON_FixedSizePool& fspc, + ON_SubDComponentBase*& unused_list, + unsigned int& max_id, + unsigned int candidate_id + ); +public: + class ON_SubDVertex* AllocateVertexAndSetId(unsigned int candidate_vertex_id); void ReturnVertex(class ON_SubDVertex* v); - class ON_SubDEdge* AllocateEdgeAndSetId(unsigned int& max_edge_id); - + class ON_SubDEdge* AllocateEdgeAndSetId(unsigned int candidate_edge_id); void ReturnEdge(class ON_SubDEdge* e); - class ON_SubDFace* AllocateFaceAndSetId(unsigned int& max_face_id); - class ON_SubDFace* AllocateFaceAndSetId(const ON_SubDFace* candidate_face, unsigned int& max_face_id); - + class ON_SubDFace* AllocateFaceAndSetId(unsigned int candidate_face_id); void ReturnFace(class ON_SubDFace* f); /* @@ -1299,16 +1304,62 @@ public: unsigned int face_id ) const; - unsigned int MaximumVertexId() const; - unsigned int MaximumEdgeId() const; - unsigned int MaximumFaceId() const; + /* + Returns: + Maximum vertex id assigned to a vertex managed by this heap. + Remarks: + The next vertex that is created (not reused from m_unused_vertex) will + be assigned id = MaximumVertexId() + 1; + */ + unsigned int MaximumVertexId() const + { + return m_max_vertex_id; + } - bool IsValid() const; - void ResetId(); + /* + Returns: + Maximum edge id assigned to an edge managed by this heap. + Remarks: + The next edge that is created (not reused from m_unused_edge) will + be assigned id = MaximumEdgeId() + 1; + */ + unsigned int MaximumEdgeId() const + { + return m_max_edge_id; + } + + /* + Returns: + Maximum face id assigned to a face managed by this heap. + Remarks: + The next face that is created (not reused from m_unused_face) will + be assigned id = MaximumFaceId() + 1; + */ + unsigned int MaximumFaceId() const + { + return m_max_face_id; + } + +public: + /* + Returns: + True if this heap and the ids are valid. + Remarks: + If false is returned, ResetIds() generally will make the heap valid. + */ + bool IsValid( + bool bSilentError, + ON_TextLog* text_log + ) const; + + /* + Description: + Resets all ids to begin with 1. + This breaks persistent id references, but will restore ability for the heap to work correctly. + */ + void ResetIds(); private: - - /* Returns: Array of count ON__UINT_PTR @@ -1522,17 +1573,32 @@ private: // Used to allocate edge curves size_t m_sizeof_limit_curve = 0; class ON_FixedSizePoolElement* m_unused_limit_curves = nullptr; - + + // list of vertices previously allocated from m_fspv and returned by ReturnVertex(). + ON_SubDVertex* m_unused_vertex = nullptr; - ON_SubDVertex* m_unused_vertex = nullptr; + // list of edges previously allocated from m_fspv and returned by ReturnVertex(). ON_SubDEdge* m_unused_edge = nullptr; + + // list of faces previously allocated from m_fspv and returned by ReturnVertex(). ON_SubDFace* m_unused_face = nullptr; - unsigned int m_max_fspv_id = 0; - unsigned int m_max_fspe_id = 0; - unsigned int m_max_fspf_id = 0; + // Maximum vertex id assigned to a vertex in m_fspv. + // If m_unused_vertex is not nullptr, some vertices are currently deleted. + // Deleted vertices can be reused, but their id is never changed. + unsigned int m_max_vertex_id = 0; - unsigned int m_reserved = 0; + // Maximum edge id assigned to an edge in m_fspe. + // If m_unused_edge is not nullptr, some edges are currently deleted. + // Deleted edges can be reused, but their id is never changed. + unsigned int m_max_edge_id = 0; + + // Maximum face id assigned to a face in m_fspf. + // If m_unused_face is not nullptr, some faces are currently deleted. + // Deleted faces can be reused, but their id is never changed. + unsigned int m_max_face_id = 0; + + unsigned int m_reserved2 = 0; ON__UINT_PTR* AllocateOversizedElement( size_t* capacity @@ -1584,12 +1650,22 @@ public: /* Description: Change the content serial number. + Parameters: + bChangePreservesSymmetry - [in] + When in doubt, pass false. + If the change preserves global symmtery, then pass true. + (For example, a global subdivide preserves all types of symmetry.) + Note well: + Transformations do not preserve symmetries that are + set with respect to world coordinate systems. Returns: The new value of ConentSerialNumber(). Remarks: The value can change by any amount. */ - ON__UINT64 ChangeContentSerialNumber() const; + ON__UINT64 ChangeContentSerialNumber( + bool bChangePreservesSymmetry + ) const; private: mutable ON__UINT64 m_subd_content_serial_number = 0; @@ -1691,6 +1767,16 @@ public: double v1_sector_weight ); + class ON_SubDEdge* AddEdge( + unsigned int candidate_edge_id, + ON_SubD::EdgeTag edge_tag, + ON_SubDVertex* v0, + double v0_sector_weight, + ON_SubDVertex* v1, + double v1_sector_weight, + unsigned initial_face_capacity + ); + /* Description: Split and edge. @@ -1715,10 +1801,10 @@ public: ); class ON_SubDFace* AddFace( - const ON_SubDFace* candidate_face, + unsigned int candidate_face_id, unsigned int edge_count, const ON_SubDEdgePtr* edge - ); + ); /* Description: @@ -1750,7 +1836,19 @@ public: const double* P ) { - class ON_SubDVertex* v = m_heap.AllocateVertexAndSetId(m_max_vertex_id); + return AllocateVertex( 0U, vertex_tag, level, P, 0, 0); + } + + class ON_SubDVertex* AllocateVertex( + unsigned int candidate_id, + ON_SubD::VertexTag vertex_tag, + unsigned int level, + const double* P, + unsigned int edge_capacity, + unsigned int face_capacity + ) + { + class ON_SubDVertex* v = m_heap.AllocateVertexAndSetId( candidate_id); v->SetSubdivisionLevel(level); v->m_vertex_tag = vertex_tag; if (nullptr != P) @@ -1759,19 +1857,6 @@ public: v->m_P[1] = P[1]; v->m_P[2] = P[2]; } - return v; - } - - class ON_SubDVertex* AllocateVertex( - ON_SubD::VertexTag vertex_tag, - unsigned int level, - const double* P, - unsigned int edge_capacity, - unsigned int face_capacity - ) - { - class ON_SubDVertex* v = AllocateVertex(vertex_tag,level,P); - if (edge_capacity > 0 && edge_capacity < ON_SubDVertex::MaximumEdgeCount ) m_heap.GrowVertexEdgeArray(v,edge_capacity); if (face_capacity > 0 && face_capacity < ON_SubDVertex::MaximumFaceCount ) @@ -1797,31 +1882,33 @@ public: m_heap.ReturnVertex(v); } - class ON_SubDEdge* AllocateEdge( + ON_SubDEdge* AllocateEdge( ON_SubD::EdgeTag edge_tag - ) + ) { - class ON_SubDEdge* e = m_heap.AllocateEdgeAndSetId(m_max_edge_id); + ON_SubDEdge* e = m_heap.AllocateEdgeAndSetId(0U); e->m_edge_tag = edge_tag; return e; } - class ON_SubDEdge* AllocateEdge( + ON_SubDEdge* AllocateEdge( + unsigned int candidate_edge_id, ON_SubD::EdgeTag edge_tag, unsigned int level, unsigned int face_capacity ) { - class ON_SubDEdge* e = AllocateEdge(edge_tag); + ON_SubDEdge* e = m_heap.AllocateEdgeAndSetId( candidate_edge_id); + e->m_edge_tag = edge_tag; e->SetSubdivisionLevel(level); if (face_capacity > 0 && face_capacity <= ON_SubDEdge::MaximumFaceCount ) m_heap.GrowEdgeFaceArray(e,face_capacity); return e; } - const class ON_SubDEdge* AddEdgeToLevel(class ON_SubDEdge* e) + const ON_SubDEdge* AddEdgeToLevel(class ON_SubDEdge* e) { - class ON_SubDLevel* subd_level = SubDLevel(e->SubdivisionLevel(),true); + ON_SubDLevel* subd_level = SubDLevel(e->SubdivisionLevel(),true); return (subd_level) ? subd_level->AddEdge(e) : nullptr; } @@ -1838,22 +1925,17 @@ public: class ON_SubDFace* AllocateFace() { - class ON_SubDFace* f = m_heap.AllocateFaceAndSetId(m_max_face_id); + class ON_SubDFace* f = m_heap.AllocateFaceAndSetId( 0U); return f; } - - class ON_SubDFace* AllocateFace(const ON_SubDFace* candidate_face) - { - class ON_SubDFace* f = m_heap.AllocateFaceAndSetId(candidate_face, m_max_face_id); - return f; - } - + class ON_SubDFace* AllocateFace( + unsigned int candidate_face_id, unsigned int level, unsigned int edge_capacity ) { - class ON_SubDFace* f = AllocateFace(); + class ON_SubDFace* f = m_heap.AllocateFaceAndSetId(candidate_face_id); if (nullptr != f) { f->SetSubdivisionLevel(level); @@ -1898,19 +1980,29 @@ public: /* Description: Removes every level above max_level_index + Returns: + Number of removed levels. */ - void ClearHigherSubdivisionLevels( + unsigned int ClearHigherSubdivisionLevels( unsigned int max_level_index ); /* Description: Removes every level below min_level_index + Returns: + Number of removed levels. */ - void ClearLowerSubdivisionLevels( + unsigned int ClearLowerSubdivisionLevels( unsigned int min_level_index ); + /* + Remove all levels except the active level. + Returns: + Number of removed levels. + */ + unsigned int ClearInactiveLevels(); void ClearLevelContents( ON_SubDLevel* level @@ -2001,11 +2093,6 @@ public: ) const; private: - unsigned int m_max_vertex_id = 0; - unsigned int m_max_edge_id = 0; - unsigned int m_max_face_id = 0; - - mutable ON_SubDComponentLocation m_subd_appearance = ON_SubD::DefaultSubDAppearance; mutable ON_SubDTextureDomainType m_texture_domain_type = ON_SubDTextureDomainType::Unset; @@ -2018,16 +2105,41 @@ private: public: unsigned int MaximumVertexId() const { - return m_max_vertex_id; + return m_heap.MaximumVertexId(); } unsigned int MaximumEdgeId() const { - return m_max_edge_id; + return m_heap.MaximumEdgeId(); } unsigned int MaximumFaceId() const { - return m_max_face_id; + return m_heap.MaximumFaceId(); } + + //void IncreaseMaximumVertexId( + // unsigned new_maximum_vertex_id + //) + //{ + // if (new_maximum_vertex_id > m_dimple_max_vertex_id && new_maximum_vertex_id + 1 < ON_UNSET_UINT_INDEX) + // m_dimple_max_vertex_id = new_maximum_vertex_id; + //} + + //void IncreaseMaximumEdgeId( + // unsigned new_maximum_edge_id + //) + //{ + // if (new_maximum_edge_id > m_dimple_max_edge_id && new_maximum_edge_id + 1 < ON_UNSET_UINT_INDEX) + // m_dimple_max_edge_id = new_maximum_edge_id; + //} + + //void IncreaseMaximumFaceId( + // unsigned new_maximum_face_id + //) + //{ + // if (new_maximum_face_id > m_dimple_max_face_id && new_maximum_face_id + 1 < ON_UNSET_UINT_INDEX) + // m_dimple_max_face_id = new_maximum_face_id; + //} + /* Returns: Active level diff --git a/opennurbs_subd_eval.cpp b/opennurbs_subd_eval.cpp index c676af67..e632d20f 100644 --- a/opennurbs_subd_eval.cpp +++ b/opennurbs_subd_eval.cpp @@ -1008,6 +1008,22 @@ void ON_SubDFace::ClearSavedSubdivisionPoints() const ON_SubDComponentBase::Internal_ClearSubdivisionPointAndSurfacePointFlags(); } + +unsigned int ON_SubDVertex::VertexId() const +{ + return m_id; +} + +unsigned int ON_SubDEdge::EdgeId() const +{ + return m_id; +} + +unsigned int ON_SubDFace::FaceId() const +{ + return m_id; +} + const ON_3dPoint ON_SubDFace::ControlNetCenterPoint() const { if (m_edge_count < 3) diff --git a/opennurbs_subd_frommesh.cpp b/opennurbs_subd_frommesh.cpp index 64f0486a..b0c21e62 100644 --- a/opennurbs_subd_frommesh.cpp +++ b/opennurbs_subd_frommesh.cpp @@ -256,10 +256,101 @@ static void Internal_CreateFromMesh_ValidateNonmanifoldVertex( return; } +class ON_NgonBoundaryChecker +{ +public: + /* + Parametes: + ngon - [in] + ngon to test + mesh [in] + mesh that ngon is a part of + bMustBeOriented - [in] + If true, the faces in the ngon must be compatibly oriented + */ + bool IsSimpleNgon( + const class ON_MeshNgon* ngon, + const class ON_Mesh* mesh, + bool bMustBeOriented + ); + + enum : unsigned int + { + HashTableSize = 256 + }; + +private: + void Internal_Reset(); + class ON_NgonBoundaryComponent* Internal_AddVertex(unsigned int vertex_index); + class ON_NgonBoundaryComponent* Internal_AddEdge(unsigned int vertex_index0, unsigned int vertex_index1, bool bMustBeOriented); + static unsigned int Internal_VertexHashIndex(unsigned int vertex_index); + static unsigned int Internal_EdgeHashIndex(unsigned int vertex_index0, unsigned int vertex_index1); + void Internal_InitialzeFixedSizePool(); + + void Internal_ReturnIsNotSimple(); + + // m_fsp manages the memory used for boundary components. + ON_FixedSizePool m_fsp; + class ON_NgonBoundaryComponent* m_hash_table[ON_NgonBoundaryChecker::HashTableSize] = {}; + unsigned m_vertex_count = 0; + unsigned m_edge_count = 0; + bool m_bIsSimple = false; + bool m_bIsNotSimple = false; +}; + ON_SubD* ON_SubD::CreateFromMesh( const class ON_Mesh* level_zero_mesh, const class ON_ToSubDParameters* from_mesh_options, ON_SubD* subd +) +{ + ON_Mesh* local_copy = nullptr; + if (nullptr != level_zero_mesh) + { + // remove ngons with holes and other damaged ngons so the underlying faces get used. + + ON_NgonBoundaryChecker bc; + const bool bMustBeOrientedNgon = false; + + const unsigned ngon_count = level_zero_mesh->NgonUnsignedCount(); + ON_SimpleArray ngons_with_holes(ngon_count); + for (unsigned ni = 0; ni < ngon_count; ++ni) + { + const class ON_MeshNgon* ngon = level_zero_mesh->Ngon(ni); + if ( nullptr == ngon) + continue; + if (ngon->m_Vcount < 3 || ngon->m_Fcount <= 1) + continue; + if ( false == bc.IsSimpleNgon(ngon, level_zero_mesh,bMustBeOrientedNgon) ) + ngons_with_holes.Append(ni); + } + + for (;;) + { + if (0 == ngons_with_holes.UnsignedCount()) + break; + local_copy = new ON_Mesh(*level_zero_mesh); + if (nullptr == local_copy) + break; + if (ngon_count != local_copy->NgonUnsignedCount()) + break; + const unsigned removed_count = local_copy->RemoveNgons(ngons_with_holes.UnsignedCount(), ngons_with_holes.Array()); + if (removed_count > 0) + level_zero_mesh = local_copy; + break; + } + } + + ON_SubD* subd_from_mesh = Internal_CreateFromMeshWithValidNgons(level_zero_mesh, from_mesh_options, subd); + if (nullptr != local_copy) + delete local_copy; + return subd_from_mesh; +} + +ON_SubD* ON_SubD::Internal_CreateFromMeshWithValidNgons( + const class ON_Mesh* level_zero_mesh, + const class ON_ToSubDParameters* from_mesh_options, + ON_SubD* subd ) { if (nullptr != subd) @@ -1463,3 +1554,378 @@ ON_SubD* ON_SubD::CreateSubDBox( return subd; } + +/* +The ONLY use for this class is in a calculation to determine if an ngon has a single simple outer boundary. +*/ +class ON_NgonBoundaryComponent +{ +public: + enum class Type : unsigned char + { + Unset = 0, + Vertex = 1, + Edge = 2 + }; + + static const ON_NgonBoundaryComponent Unset; + + unsigned int IsBoundaryVertex() const; + + unsigned int IsBoundaryEdge() const; + + ON_NgonBoundaryComponent::Type m_type = ON_NgonBoundaryComponent::Type::Unset; + mutable unsigned char m_mark = 0; + unsigned char m_face_count = 0; // 0, 1, 2, or 3 = number of faces attached to this edge. 3 means 3 or more + unsigned char m_attached_count = 0; // 0, 1, or 2 = number of values set in m_attached_to[] + + unsigned int m_index = 0; + + // If this component is a vertex, these will be the first two edges attached to the vertex. + // If this component is an edge, these will be the vertices at the start and end. + ON_NgonBoundaryComponent* m_attached_to[2] = {}; + +private: + bool Internal_IsAttachedToTwo(ON_NgonBoundaryComponent::Type attached_type) const; + friend class ON_NgonBoundaryChecker; + // hash table pointer + // m_next is a list in a ON_NgonBoundaryChecker.m_vertex_hash_table[] element. + ON_NgonBoundaryComponent* m_next = nullptr; +}; + +bool ON_NgonBoundaryComponent::Internal_IsAttachedToTwo(ON_NgonBoundaryComponent::Type attached_type) const +{ + return + 2 == m_attached_count + && nullptr != m_attached_to[0] + && nullptr != m_attached_to[1] + && m_attached_to[0] != m_attached_to[1] + && attached_type == m_attached_to[0]->m_type + && attached_type == m_attached_to[1]->m_type + ; +} + +unsigned int ON_NgonBoundaryComponent::IsBoundaryVertex() const +{ + return + ON_NgonBoundaryComponent::Type::Vertex == m_type + && 0 == m_face_count + && Internal_IsAttachedToTwo(ON_NgonBoundaryComponent::Type::Edge) + && 1 == m_attached_to[0]->m_face_count + && 1 == m_attached_to[1]->m_face_count + ; +} + +unsigned int ON_NgonBoundaryComponent::IsBoundaryEdge() const +{ + return + ON_NgonBoundaryComponent::Type::Edge == m_type + && 1 == m_face_count + && Internal_IsAttachedToTwo(ON_NgonBoundaryComponent::Type::Vertex) + && 0 == m_attached_to[0]->m_face_count + && 0 == m_attached_to[1]->m_face_count + ; +} + +const ON_NgonBoundaryComponent ON_NgonBoundaryComponent::Unset ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_NgonBoundaryComponent); + +void ON_NgonBoundaryChecker::Internal_ReturnIsNotSimple() +{ + m_bIsSimple = false; + m_bIsNotSimple = true; +} + +void ON_NgonBoundaryChecker::Internal_Reset() +{ + m_fsp.ReturnAll(); + for (unsigned i = 0; i < ON_NgonBoundaryChecker::HashTableSize; ++i) + m_hash_table[i] = nullptr; + m_vertex_count = 0; + m_edge_count = 0; + m_bIsSimple = false; + m_bIsNotSimple = false; +} + +unsigned int ON_NgonBoundaryChecker::Internal_VertexHashIndex(unsigned int vertex_index) +{ + return ON_CRC32(0, sizeof(vertex_index), &vertex_index) % ON_NgonBoundaryChecker::HashTableSize; +} + + +unsigned int ON_NgonBoundaryChecker::Internal_EdgeHashIndex(unsigned int vertex_index0, unsigned int vertex_index1) +{ + return (vertex_index0 < vertex_index1) + ? (ON_CRC32(vertex_index0, sizeof(vertex_index1), &vertex_index1) % ON_NgonBoundaryChecker::HashTableSize) + : (ON_CRC32(vertex_index1, sizeof(vertex_index0), &vertex_index0) % ON_NgonBoundaryChecker::HashTableSize) + ; +} + +void ON_NgonBoundaryChecker::Internal_InitialzeFixedSizePool() +{ + if (0 == m_fsp.SizeofElement()) + m_fsp.Create(sizeof(ON_NgonBoundaryComponent), 0, 0); +} + +ON_NgonBoundaryComponent* ON_NgonBoundaryChecker::Internal_AddVertex(unsigned int vertex_index) +{ + if (m_bIsNotSimple) + return nullptr; + + const unsigned hash_index = ON_NgonBoundaryChecker::Internal_VertexHashIndex(vertex_index); + ON_NgonBoundaryComponent* v; + for ( + v = m_hash_table[hash_index]; + nullptr != v; + v = v->m_next + ) + { + if (ON_NgonBoundaryComponent::Type::Vertex == v->m_type && vertex_index == v->m_index) + return v; + } + if (nullptr == v) + { + Internal_InitialzeFixedSizePool(); + v = (ON_NgonBoundaryComponent*)m_fsp.AllocateElement(); + v->m_type = ON_NgonBoundaryComponent::Type::Vertex; + v->m_index = vertex_index; + v->m_next = m_hash_table[hash_index]; + m_hash_table[hash_index] = v; + ++m_vertex_count; + } + return v; +} + +ON_NgonBoundaryComponent* ON_NgonBoundaryChecker::Internal_AddEdge(unsigned int vertex_index0, unsigned int vertex_index1, bool bMustBeOriented) +{ + if (m_bIsNotSimple) + return nullptr; + + if (vertex_index0 == vertex_index1) + return (Internal_ReturnIsNotSimple(),nullptr); + + ON_NgonBoundaryComponent* v[2] = { Internal_AddVertex(vertex_index0), Internal_AddVertex(vertex_index1) }; + if (nullptr == v[0] || nullptr == v[1]) + return (Internal_ReturnIsNotSimple(), nullptr); + + const unsigned hash_index = ON_NgonBoundaryChecker::Internal_EdgeHashIndex(vertex_index0, vertex_index1); + ON_NgonBoundaryComponent* e; + for ( + e = m_hash_table[hash_index]; + nullptr != e; + e = e->m_next + ) + { + if ( + ON_NgonBoundaryComponent::Type::Edge == e->m_type + && + ((e->m_attached_to[0] == v[0] && e->m_attached_to[1] == v[1]) || (e->m_attached_to[0] == v[1] && e->m_attached_to[1] == v[0])) + ) + { + if (1 == e->m_face_count) + { + if (bMustBeOriented) + { + if (e->m_attached_to[0] != v[1] || e->m_attached_to[1] != v[0]) + { + // The 2 faces attached to this edge are not compatibly oriented. + return (Internal_ReturnIsNotSimple(), nullptr); + } + } + // this is an interior edge + e->m_face_count = 2; + return e; + } + // nonmanifold edge + return (Internal_ReturnIsNotSimple(), nullptr); + } + } + + e = (ON_NgonBoundaryComponent*)m_fsp.AllocateElement(); + e->m_type = ON_NgonBoundaryComponent::Type::Edge; + e->m_face_count = 1; + e->m_attached_count = 2; + e->m_attached_to[0] = v[0]; + e->m_attached_to[1] = v[1]; + e->m_next = m_hash_table[hash_index]; + m_hash_table[hash_index] = e; + ++m_edge_count; + return e; +} + + +bool ON_NgonBoundaryChecker::IsSimpleNgon( + const class ON_MeshNgon* ngon, + const class ON_Mesh* mesh, + bool bMustBeOriented +) +{ + Internal_Reset(); + + if (nullptr == ngon || nullptr == mesh) + return (Internal_ReturnIsNotSimple(),false); + + const unsigned ngon_face_count = ngon->m_Fcount; + if (ngon_face_count < 1 || nullptr == ngon->m_fi) + return (Internal_ReturnIsNotSimple(), false); + + const int mesh_vertex_count = mesh->VertexCount(); + const unsigned mesh_face_count = mesh->m_F.UnsignedCount(); + if (mesh_vertex_count < 3 || mesh_face_count < 1) + return (Internal_ReturnIsNotSimple(), false); + + const ON_MeshFace* a = mesh->m_F.Array(); + for (unsigned i = 0; i < ngon_face_count; ++i) + { + const unsigned fi = ngon->m_fi[i]; + if (fi >= mesh_face_count) + return (Internal_ReturnIsNotSimple(), false); // invalid face index in this ngon + const int* fvi = a[fi].vi; + if (fvi[0] < 0 || fvi[0] >= mesh_vertex_count) + return (Internal_ReturnIsNotSimple(), false); // invalid face in this ngon + if (fvi[1] < 0 || fvi[1] >= mesh_vertex_count) + return (Internal_ReturnIsNotSimple(), false); // invalid face in this ngon + if (fvi[2] < 0 || fvi[2] >= mesh_vertex_count) + return (Internal_ReturnIsNotSimple(), false); // invalid face in this ngon + if (fvi[3] < 0 || fvi[3] >= mesh_vertex_count) + return (Internal_ReturnIsNotSimple(), false); // invalid face in this ngon + + if (nullptr == this->Internal_AddEdge(fvi[0], fvi[1], bMustBeOriented)) + return (Internal_ReturnIsNotSimple(), false); + if (nullptr == this->Internal_AddEdge(fvi[1], fvi[2], bMustBeOriented)) + return (Internal_ReturnIsNotSimple(), false); + if (fvi[2] != fvi[3]) + { + if (nullptr == this->Internal_AddEdge(fvi[2], fvi[3], bMustBeOriented)) + return (Internal_ReturnIsNotSimple(), false); + } + if (nullptr == this->Internal_AddEdge(fvi[3], fvi[0], bMustBeOriented)) + return (Internal_ReturnIsNotSimple(), false); + } + + if (m_edge_count < 3 || m_vertex_count < 3) + return (Internal_ReturnIsNotSimple(), false); + + // A simple ngon has Euler number = ( V - E + F) = 1. + if (m_vertex_count + ngon_face_count != m_edge_count + 1) + return (Internal_ReturnIsNotSimple(), false); // wrong Euler number + + // set vertex attachments + for (unsigned hash_index = 0; hash_index < ON_NgonBoundaryChecker::HashTableSize; ++hash_index) + { + for (ON_NgonBoundaryComponent* e = m_hash_table[hash_index]; nullptr != e; e = e->m_next) + { + if (1 != e->m_face_count) + continue; + for (unsigned evi = 0; evi < 2; ++evi) + { + ON_NgonBoundaryComponent* v = e->m_attached_to[evi]; + if (v->m_attached_count >= 2) + return (Internal_ReturnIsNotSimple(), false); // vertex is attached to 3 or more boundary edges + v->m_attached_to[v->m_attached_count++] = e; + } + } + } + + bool bBoundaryIsMarked = false; + ON_FixedSizePoolIterator fspit(m_fsp); + for (ON_NgonBoundaryComponent* e = (ON_NgonBoundaryComponent * )fspit.FirstElement(); nullptr != e; e = (ON_NgonBoundaryComponent * )fspit.NextElement()) + { + if (1 != e->m_face_count) + continue; // vertex components alwasy have m_face_count = 0; + + // e is a boundary edge + if (bBoundaryIsMarked) + { + if (0 == e->m_mark) + { + // this is a boundary edge that part of the boundary we marked. The ngon is not simple. + return (Internal_ReturnIsNotSimple(), false); + } + } + else + { + if ( false == e->IsBoundaryEdge()) + return (Internal_ReturnIsNotSimple(), false); + + // e is the first boundary edge in the pool + if (0 != e->m_mark) + { + ON_ERROR("Bug in this code - all edges should have m_mark = 0 at this point."); + return (Internal_ReturnIsNotSimple(), false); + } + + // Walk along the boundary beginning at e0 and mark every edge in the boundary. + ON_NgonBoundaryComponent* e0 = e; + ON_NgonBoundaryComponent* v0 = e0->m_attached_to[0]; + if ( nullptr == v0 || 0 != v0->m_mark) + { + ON_ERROR("Bug in this code - vertices should have m_mark = 0 at this point."); + return (Internal_ReturnIsNotSimple(), false); + } + if (false == v0->IsBoundaryVertex()) + return (Internal_ReturnIsNotSimple(), false); + + ON_NgonBoundaryComponent* e1 = e0; + ON_NgonBoundaryComponent* v1 = v0; + for (unsigned i = 0; i < m_edge_count; ++i) // counter limits infinite loop if there is a bug + { + if (0 != v1->m_mark) + return (Internal_ReturnIsNotSimple(), false); + if (0 != e1->m_mark) + return (Internal_ReturnIsNotSimple(), false); + + // mark v1 and e1 as part of the boundary. + v1->m_mark = 1; + e1->m_mark = 1; + + // set v1 = "next" vertex in the boundary + if ( v1 == e1->m_attached_to[0]) + v1 = e1->m_attached_to[1]; + else if (v1 == e1->m_attached_to[1]) + { + if (bMustBeOriented) + return (Internal_ReturnIsNotSimple(), false); + v1 = e1->m_attached_to[0]; + } + else + return (Internal_ReturnIsNotSimple(), false); + + if (nullptr == v1) + return (Internal_ReturnIsNotSimple(), false); + + // set e1 = "next" edge in the boundary + if (e1 == v1->m_attached_to[0]) + e1 = v1->m_attached_to[1]; + else if (e1 == v1->m_attached_to[1]) + e1 = v1->m_attached_to[0]; + else + return (Internal_ReturnIsNotSimple(), false); + + if ( nullptr == e1) + return (Internal_ReturnIsNotSimple(), false); + + if (e0 == e1 || v0 == v1) + { + if (e0 == e1 && v0 == v1) + { + // all edges and vertices in this boundary are marked. There should be no unmarked boundary edges. + bBoundaryIsMarked = true; + break; + } + return (Internal_ReturnIsNotSimple(), false); + } + + if (false == v1->IsBoundaryVertex()) + return (Internal_ReturnIsNotSimple(), false); + if (false == e1->IsBoundaryEdge()) + return (Internal_ReturnIsNotSimple(), false); + } + if ( false == bBoundaryIsMarked) + return (Internal_ReturnIsNotSimple(), false); // for loop finished without marking a boundary + } + } + + m_bIsSimple = (bBoundaryIsMarked && false == m_bIsNotSimple); + return m_bIsSimple; +} + diff --git a/opennurbs_subd_heap.cpp b/opennurbs_subd_heap.cpp index ed3fd6ff..d0f5a24a 100644 --- a/opennurbs_subd_heap.cpp +++ b/opennurbs_subd_heap.cpp @@ -910,37 +910,117 @@ ON_SubDHeap::~ON_SubDHeap() Destroy(); } -class ON_SubDVertex* ON_SubDHeap::AllocateVertexAndSetId(unsigned int& max_vertex_id) +class ON_SubDComponentBase* ON_SubDHeap::Internal_AllocateComponentAndSetId( + ON_FixedSizePool& fspc, + ON_SubDComponentBase*& unused_list, + unsigned int& max_id, + unsigned int candidate_id +) { - // In order for m_fspv.ElementFromId() to work, it is critical that - // once a vertex is allocated from m_fspv, the value of m_id never - // changes. This is imporant because the value of m_id must persist - // in binary archives in order for ON_COMPONENT_INDEX values to - // persist in binary archives. - ON_SubDVertex* v; - if (m_unused_vertex) + // fspc is a m_fspv / m_fspe / m_fspf fixed size pool on an ON_SubDHeap. + // unused_list is the corresponding m_unused_vertex / m_unused_edge / m_unused_face list on that ON_SubDHeap. + + + // In order for m_fspv.ElementFromId(), m_fspe.ElementFromId() , m_fspf.ElementFromId() + // to work, it is critical that once a vertex/edge/face is allocated from m_fspv/mfspe/mfspf + // the value of m_id never changes. This is imporant because the value of m_id must persist + // in binary archives in order for ON_COMPONENT_INDEX values to persist in binary archives. + + ON_SubDComponentBaseLink* c; + if (candidate_id >3000000000U) { - v = m_unused_vertex; - m_unused_vertex = (ON_SubDVertex*)(m_unused_vertex->m_next_vertex); - const unsigned int id = v->m_id; - if (ON_UNSET_UINT_INDEX == (&v->m_id)[1]) + // Requests for a candidate_id value above 3 billion are ignored to insure + // there is plenty of room for ids. + // It's almost certainly a bug if candidate_id > several millon or so. + candidate_id = 0; + } + + if (nullptr != unused_list && candidate_id <= max_id) + { + ON_SubDComponentBaseLink* prev = nullptr; + + if (candidate_id > 0 && candidate_id != unused_list->m_id) { - memset(v, 0, sizeof(*v)); - v->m_id = id; + // Caller wants a specific id. If it's found here, the context is probably + // some editing code where the caller deleted the component and now wants it back + // to preserve the id structure. + + for (prev = static_cast(unused_list); nullptr != prev; prev = const_cast(prev->m_next)) + { + // If candidate_id is somewhere in the unused list after the first element, return it. + if (nullptr != prev->m_next && candidate_id == prev->m_next->m_id) + break; + } + } + + if (nullptr != prev) + { + // The candidate was found somewhere in the unused_list after the first element. + c = const_cast(prev->m_next); + prev->m_next = c->m_next; } else { - // something is modifying ids of returned elements + // Return element at the head of the unusued list. + c = static_cast(unused_list); + unused_list = const_cast(c->m_next); + } + const unsigned int id = c->m_id; + if (ON_UNSET_UINT_INDEX == (&c->m_id)[1] && c->m_status.IsDeleted() ) + { + // When a vertex/edge/face is put on the unused list, m_archive_id is set to ON_UNSET_UINT_INDEX and m_status = ON_ComponentStatus::Deleted. + memset(c, 0, fspc.SizeofElement()); + c->m_id = id; + } + else + { + // Something is modifying returned elements. This is a serious bug. ON_SubDIncrementErrorCount(); - v->m_id = ++max_vertex_id; + memset(c, 0, fspc.SizeofElement()); + c->m_id = ++max_id; } } else { - v = (class ON_SubDVertex*)m_fspv.AllocateElement(); - v->m_id = ++max_vertex_id; + if (candidate_id > max_id) + { + // Caller wants a specific id. This is common when copying subds + // and some of the components of the original subd were deleted. + max_id = candidate_id; + } + else + { +#if defined(ON_DEBUG) + // TEMPORARY ERROR CHECK added Feb 2020 to test new code. Can be removed in April 2020 or earlier if needed. + // Ask Dale Lear if confused. + if (0 != candidate_id) + { + ON_SUBD_ERROR("Unable to assign candidate_id"); + } +#endif + // otherwise assign the next id to this component. + candidate_id = ++max_id; + } + + // allocate a new vertex. + c = (ON_SubDComponentBaseLink*)fspc.AllocateElement(); + c->m_id = candidate_id; } - return v; + return c; +} + + +class ON_SubDVertex* ON_SubDHeap::AllocateVertexAndSetId(unsigned int candidate_vertex_id) +{ + ON_SubDComponentBase* unused_list = m_unused_vertex; + ON_SubDComponentBase* c = ON_SubDHeap::Internal_AllocateComponentAndSetId( + m_fspv, + unused_list, + m_max_vertex_id, + candidate_vertex_id + ); + m_unused_vertex = static_cast(unused_list); + return static_cast(c); } void ON_SubDHeap::ReturnVertex(class ON_SubDVertex* v) @@ -957,37 +1037,19 @@ void ON_SubDHeap::ReturnVertex(class ON_SubDVertex* v) } } -class ON_SubDEdge* ON_SubDHeap::AllocateEdgeAndSetId(unsigned int& max_edge_id) +class ON_SubDEdge* ON_SubDHeap::AllocateEdgeAndSetId( + unsigned int candidate_edge_id + ) { - // In order for m_fspe.ElementFromId() to work, it is critical that - // once a edge is allocated from m_fspe, the value of m_id never - // changes. This is imporant because the value of m_id must persist - // in binary archives in order for ON_COMPONENT_INDEX values to - // persist in binary archives. - ON_SubDEdge* e; - if (m_unused_edge) - { - e = m_unused_edge; - m_unused_edge = (ON_SubDEdge*)(m_unused_edge->m_next_edge); - const unsigned int id = e->m_id; - if (ON_UNSET_UINT_INDEX == (&e->m_id)[1]) - { - memset(e, 0, sizeof(*e)); - e->m_id = id; - } - else - { - // something is modifying ids of returned elements - ON_SubDIncrementErrorCount(); - e->m_id = ++max_edge_id; - } - } - else - { - e = (class ON_SubDEdge*)m_fspe.AllocateElement(); - e->m_id = ++max_edge_id; - } - return e; + ON_SubDComponentBase* unused_list = m_unused_edge; + ON_SubDComponentBase* c = ON_SubDHeap::Internal_AllocateComponentAndSetId( + m_fspe, + unused_list, + m_max_edge_id, + candidate_edge_id + ); + m_unused_edge = static_cast(unused_list); + return static_cast(c); } void ON_SubDHeap::ReturnEdge(class ON_SubDEdge* e) @@ -1005,58 +1067,19 @@ void ON_SubDHeap::ReturnEdge(class ON_SubDEdge* e) } } -class ON_SubDFace* ON_SubDHeap::AllocateFaceAndSetId(unsigned int& max_face_id) +class ON_SubDFace* ON_SubDHeap::AllocateFaceAndSetId( + unsigned int candidate_face_id +) { - return AllocateFaceAndSetId(nullptr, max_face_id); -} - -class ON_SubDFace* ON_SubDHeap::AllocateFaceAndSetId(const ON_SubDFace* candidate_face, unsigned int& max_face_id) -{ - // In order for m_fspf.ElementFromId() to work, it is critical that - // once a face is allocated from m_fspf, the value of m_id never - // changes. This is imporant because the value of m_id must persist - // in binary archives in order for ON_COMPONENT_INDEX values to - // persist in binary archives. - ON_SubDFace* f; - if (m_unused_face) - { - f = nullptr; - if (nullptr != candidate_face && candidate_face != m_unused_face) - { - for (f = m_unused_face; nullptr != f; f = const_cast(f->m_next_face)) - { - if (candidate_face == f->m_next_face) - { - f->m_next_face = f->m_next_face->m_next_face; - f = const_cast(candidate_face); - break; - } - } - } - if (nullptr == f) - { - f = m_unused_face; - m_unused_face = const_cast(m_unused_face->m_next_face); - } - const unsigned int id = f->m_id; - if (ON_UNSET_UINT_INDEX == (&f->m_id)[1]) - { - memset(f, 0, sizeof(*f)); - f->m_id = id; - } - else - { - // something is modifying ids of returned elements - ON_SubDIncrementErrorCount(); - f->m_id = ++max_face_id; - } - } - else - { - f = (class ON_SubDFace*)m_fspf.AllocateElement(); - f->m_id = ++max_face_id; - } - return f; + ON_SubDComponentBase* unused_list = m_unused_face; + ON_SubDComponentBase* c = ON_SubDHeap::Internal_AllocateComponentAndSetId( + m_fspf, + unused_list, + m_max_face_id, + candidate_face_id + ); + m_unused_face = static_cast(unused_list); + return static_cast(c); } void ON_SubDHeap::ReturnFace(class ON_SubDFace* f) @@ -1083,12 +1106,15 @@ void ON_SubDHeap::Clear() onfree(p); p = next; } + m_fspv.ReturnAll(); m_fspe.ReturnAll(); m_fspf.ReturnAll(); + m_fsp5.ReturnAll(); m_fsp9.ReturnAll(); m_fsp17.ReturnAll(); + m_limit_block_pool.ReturnAll(); m_unused_full_fragments = nullptr; @@ -1098,6 +1124,10 @@ void ON_SubDHeap::Clear() m_unused_vertex = nullptr; m_unused_edge = nullptr; m_unused_face = nullptr; + + m_max_vertex_id = 0; + m_max_edge_id = 0; + m_max_face_id = 0; } void ON_SubDHeap::Destroy() @@ -1176,35 +1206,76 @@ const class ON_SubDFace* ON_SubDHeap::FaceFromId( return face; } -unsigned int ON_SubDHeap::MaximumVertexId() const +static bool ON_SubDHeapIsNotValid(bool bSilentError) { - return m_fspv.MaximumElementId(ON_SubDHeap::m_offset_vertex_id); + ON_SubDIncrementErrorCount(); + return bSilentError ? false : ON_IsNotValid(); } -unsigned int ON_SubDHeap::MaximumEdgeId() const +bool ON_SubDHeap::IsValid( + bool bSilentError, + ON_TextLog* text_log +) const { - return m_fspe.MaximumElementId(ON_SubDHeap::m_offset_edge_id); + if (false == m_fspv.ElementIdIsIncreasing(ON_SubDHeap::m_offset_vertex_id)) + { + if (nullptr != text_log) + text_log->Print("m_fspv.ElementIdIsIncreasing() is false."); + return ON_SubDHeapIsNotValid(bSilentError); + } + + if (false == m_fspe.ElementIdIsIncreasing(ON_SubDHeap::m_offset_edge_id)) + { + if (nullptr != text_log) + text_log->Print("m_fspe.ElementIdIsIncreasing() is false."); + return ON_SubDHeapIsNotValid(bSilentError); + } + + if (false == m_fspf.ElementIdIsIncreasing(ON_SubDHeap::m_offset_face_id)) + { + if (nullptr != text_log) + text_log->Print("m_fspf.ElementIdIsIncreasing() is false."); + return ON_SubDHeapIsNotValid(bSilentError); + } + + const unsigned max_fspv_max_id = m_fspv.MaximumElementId(ON_SubDHeap::m_offset_vertex_id); + if (m_max_vertex_id != max_fspv_max_id) + { + if (nullptr != text_log) + text_log->Print("m_max_vertex_id = %u != %u = m_fspv.MaximumElementId()\n", m_max_vertex_id, max_fspv_max_id); + return ON_SubDHeapIsNotValid(bSilentError); + } + + const unsigned max_fspe_max_id = m_fspe.MaximumElementId(ON_SubDHeap::m_offset_edge_id); + if (m_max_edge_id != max_fspe_max_id) + { + if (nullptr != text_log) + text_log->Print("m_max_edge_id = %u != %u = m_fspe.MaximumElementId()\n", m_max_edge_id, max_fspe_max_id); + return ON_SubDHeapIsNotValid(bSilentError); + } + + const unsigned max_fspf_max_id = m_fspf.MaximumElementId(ON_SubDHeap::m_offset_face_id); + if (m_max_face_id != max_fspf_max_id) + { + if (nullptr != text_log) + text_log->Print("m_max_face_id = %u != %u = m_fspf.MaximumElementId()\n", m_max_face_id, max_fspf_max_id); + return ON_SubDHeapIsNotValid(bSilentError); + } + + return true; } -unsigned int ON_SubDHeap::MaximumFaceId() const -{ - return m_fspf.MaximumElementId(ON_SubDHeap::m_offset_face_id); -} - - -bool ON_SubDHeap::IsValid() const -{ - return m_fspv.ElementIdIsIncreasing(ON_SubDHeap::m_offset_vertex_id) - && m_fspe.ElementIdIsIncreasing(ON_SubDHeap::m_offset_edge_id) - && m_fspf.ElementIdIsIncreasing(ON_SubDHeap::m_offset_face_id); -} - -void ON_SubDHeap::ResetId() +void ON_SubDHeap::ResetIds() { const unsigned int first_id = 1; - m_fspv.ResetElementId(ON_SubDHeap::m_offset_vertex_id,first_id); - m_fspe.ResetElementId(ON_SubDHeap::m_offset_edge_id,first_id); - m_fspf.ResetElementId(ON_SubDHeap::m_offset_face_id,first_id); + const unsigned int next_vertex_id = m_fspv.ResetElementId(ON_SubDHeap::m_offset_vertex_id,first_id); + const unsigned int next_edge_id = m_fspe.ResetElementId(ON_SubDHeap::m_offset_edge_id,first_id); + const unsigned int next_face_id = m_fspf.ResetElementId(ON_SubDHeap::m_offset_face_id,first_id); + + // m_max_..._id = maximum assigned id = m_next_..._id - 1 + m_max_vertex_id = (next_vertex_id > first_id) ? (next_vertex_id - 1U) : 0U; + m_max_edge_id = (next_edge_id > first_id) ? (next_edge_id - 1U) : 0U; + m_max_face_id = (next_face_id > first_id) ? (next_face_id - 1U) : 0U; } size_t ON_SubDHeap::OversizedElementCapacity(size_t count) diff --git a/opennurbs_subd_mesh.cpp b/opennurbs_subd_mesh.cpp index 464836b1..eba44f74 100644 --- a/opennurbs_subd_mesh.cpp +++ b/opennurbs_subd_mesh.cpp @@ -1616,7 +1616,7 @@ ON_Mesh* ON_SubD::GetControlNetMesh( const ON_SubDVertex* vertex = face->Vertex(0); meshf.vi[1] = (nullptr != vertex) ? vertex->ArchiveId() : 0; - if (meshf.vi[1] < 1 || meshf.vi[1] >= (int)subd_vertex_count) + if (meshf.vi[1] < 1 || meshf.vi[1] > (int)subd_vertex_count) continue; meshf.vi[1]--; @@ -1731,7 +1731,7 @@ void ON_SubD::ClearEvaluationCache() const if (nullptr != level) { - const_cast(this)->ChangeContentSerialNumberForExperts(); + const_cast(this)->ChangeContentSerialNumberForExperts(false); level->ClearEvaluationCache(); } } @@ -1742,7 +1742,7 @@ void ON_SubD::ClearNeighborhoodEvaluationCache(const ON_SubDVertex * vertex, boo if (nullptr != level) { - const_cast(this)->ChangeContentSerialNumberForExperts(); + const_cast(this)->ChangeContentSerialNumberForExperts(false); level->ClearNeighborhoodEvaluationCache(vertex, bTagChanged); } } diff --git a/opennurbs_subd_texture.cpp b/opennurbs_subd_texture.cpp index 03d345fa..dcbb111e 100644 --- a/opennurbs_subd_texture.cpp +++ b/opennurbs_subd_texture.cpp @@ -488,6 +488,13 @@ bool ON_SubD::SetTextureCoordinates( // The mesh topology, 3d vertex locations, 3d vertex normals // CANNOT be modified to insert "texture seams." // + + ON_Xform P_xform, N_xform; + if (subd_xform && ON_TextureMapping::TYPE::srfp_mapping != mt) + { + subd_xform->GetMappingXforms(P_xform, N_xform); + } + ON_3dPoint tc; ON_SubDMeshFragmentIterator frit(*this); for (const ON_SubDMeshFragment* fragment = frit.FirstFragment(); nullptr != fragment; fragment = frit.NextFragment()) @@ -514,10 +521,18 @@ bool ON_SubD::SetTextureCoordinates( const size_t N_stride = (N_count == P_count) ? fragment->m_N_stride : 0; for (double* T1 = T + T_stride * T_count; T < T1; T += T_stride, P += P_stride, N += N_stride) { - if (ON_TextureMapping::TYPE::srfp_mapping != mt) + if (ON_TextureMapping::TYPE::srfp_mapping == mt) tc = ON_3dPoint(T[0], T[1], 0.0); - else if (false == mapping.Evaluate(ON_3dPoint(P), ON_3dVector(N), &tc)) - tc = ON_3dPoint::NanPoint; + else + { + bool ok = subd_xform ? + mapping.Evaluate(ON_3dPoint(P), ON_3dVector(N), &tc, P_xform, N_xform) : + mapping.Evaluate(ON_3dPoint(P), ON_3dVector(N), &tc); + + if(!ok) + tc = ON_3dPoint::NanPoint; + } + if (bApplyUVW) tc = mapping.m_uvw * tc; T[0] = tc.x; @@ -602,6 +617,23 @@ void ON_SubDFace::SetTextureDomain( } } +void ON_SubDFace::SetMaterialChannelIndex(int material_channel_index) const +{ + if ( material_channel_index >= 0 && material_channel_index <= ON_Material::MaximumMaterialChannelIndex ) + { + m_material_channel_index = (unsigned short)material_channel_index; + } + else + { + ON_ERROR("Invalid material_channel_index value."); + m_material_channel_index = 0; + } +} + +int ON_SubDFace::MaterialChannelIndex() const +{ + return (int)m_material_channel_index; +} const bool ON_SubDFace::TextureDomainIsSet() const { diff --git a/opennurbs_sumsurface.cpp b/opennurbs_sumsurface.cpp index c5fd6bf0..339c9212 100644 --- a/opennurbs_sumsurface.cpp +++ b/opennurbs_sumsurface.cpp @@ -48,11 +48,11 @@ ON_SumSurface* ON_SumSurface::New( const ON_SumSurface& rev_surface ) return new ON_SumSurface(rev_surface); } -ON_SumSurface::ON_SumSurface() : m_basepoint(0.0,0.0,0.0) +ON_SumSurface::ON_SumSurface() { ON__SET__THIS__PTR(m_s_ON_SumSurface_ptr); - m_curve[0] = 0; - m_curve[1] = 0; + m_curve[0] = nullptr; + m_curve[1] = nullptr; } ON_SumSurface::~ON_SumSurface() @@ -68,25 +68,41 @@ void ON_SumSurface::Destroy() { if ( m_curve[i] ) { delete m_curve[i]; - m_curve[i] = 0; + m_curve[i] = nullptr; } } - m_bbox.Destroy(); - m_basepoint.Set(0.0,0.0,0.0); + m_bbox = ON_BoundingBox::EmptyBoundingBox; + m_basepoint = ON_3dPoint::Origin; } void ON_SumSurface::EmergencyDestroy() { - m_curve[0] = 0; - m_curve[1] = 0; + m_curve[0] = nullptr; + m_curve[1] = nullptr; + m_bbox = ON_BoundingBox::EmptyBoundingBox; + m_basepoint = ON_3dPoint::Origin; } -ON_SumSurface::ON_SumSurface( const ON_SumSurface& src ) +void ON_SumSurface::Internal_CopyFrom(const ON_SumSurface& src) +{ + m_curve[0] = nullptr; + m_curve[1] = nullptr; + for ( int i = 0; i < 2; i++ ) + { + if ( nullptr != src.m_curve[i] ) + m_curve[i] = src.m_curve[i]->DuplicateCurve(); + } + m_basepoint = src.m_basepoint; + m_bbox = src.m_bbox; +} + +ON_SumSurface::ON_SumSurface( const ON_SumSurface& src ) + : ON_Surface(src) { ON__SET__THIS__PTR(m_s_ON_SumSurface_ptr); - m_curve[0] = 0; - m_curve[1] = 0; - *this = src; + m_curve[0] = nullptr; + m_curve[1] = nullptr; + Internal_CopyFrom(src); } unsigned int ON_SumSurface::SizeOf() const @@ -113,18 +129,8 @@ ON_SumSurface& ON_SumSurface::operator=(const ON_SumSurface& src ) if ( this != &src ) { Destroy(); - for ( int i = 0; i < 2; i++ ) - { - if ( src.m_curve[i] ) - { - ON_Object* obj = src.m_curve[i]->DuplicateCurve(); - m_curve[i] = ON_Curve::Cast(obj); - if ( !m_curve[i] ) - delete obj; - } - } - m_basepoint = src.m_basepoint; - m_bbox = src.m_bbox; + ON_Surface::operator=(src); + Internal_CopyFrom(src); } return *this; } diff --git a/opennurbs_sumsurface.h b/opennurbs_sumsurface.h index 0a3566a9..dee3511b 100644 --- a/opennurbs_sumsurface.h +++ b/opennurbs_sumsurface.h @@ -33,10 +33,10 @@ public: // for expert users // surface-PointAt(s,t) // = m_curve[0]->PointAt(s) + m_curve[1]->PointAt(t) + m_basepoint; - ON_Curve* m_curve[2]; // m_curve[0] and m_curve[1] are deleted by ~ON_SumSuface. + ON_Curve* m_curve[2] = {}; // m_curve[0] and m_curve[1] are deleted by ~ON_SumSuface. // Use a ON_ProxyCurve if this is problem. - ON_3dVector m_basepoint; - ON_BoundingBox m_bbox; // lazy evaluation used in ON_SumSurface::BoundingBox() + ON_3dVector m_basepoint = ON_3dPoint::Origin; + ON_BoundingBox m_bbox = ON_BoundingBox::EmptyBoundingBox; // lazy evaluation used in ON_SumSurface::BoundingBox() public: @@ -56,6 +56,9 @@ public: ON_SumSurface( const ON_SumSurface& ); ON_SumSurface& operator=(const ON_SumSurface&); +private: + void Internal_CopyFrom(const ON_SumSurface&); +public: /* Description: Extrude a curve to create a surface. diff --git a/opennurbs_symmetry.cpp b/opennurbs_symmetry.cpp index aad1e515..9a25554e 100644 --- a/opennurbs_symmetry.cpp +++ b/opennurbs_symmetry.cpp @@ -107,7 +107,7 @@ const ON_wString ON_Symmetry::SymmetryCoordinatesToString(ON_Symmetry::Coordinat bool ON_Symmetry::Write(ON_BinaryArchive& archive) const { - if (false == archive.BeginWrite3dmAnonymousChunk(2)) + if (false == archive.BeginWrite3dmAnonymousChunk(3)) return false; bool rc = false; @@ -164,6 +164,11 @@ bool ON_Symmetry::Write(ON_BinaryArchive& archive) const if (false == archive.WriteChar(ucoordinates)) break; + // ON_Symmetry::Coordinates added Feb 11, 2020 chunk version 3 + if ( false == archive.WriteBigInt(SymmetricObjectContentSerialNumber()) ) + break; + + rc = true; break; } if (false == archive.EndWrite3dmChunk()) @@ -288,6 +293,14 @@ bool ON_Symmetry::Read(ON_BinaryArchive& archive) if (ON_Symmetry::Coordinates::Unset != symmetry_coordinates && m_coordinates != symmetry_coordinates) m_coordinates = symmetry_coordinates; + if (chunk_version < 3) + break; + + ON__UINT64 symmetric_object_content_serial_number = 0; + rc = archive.ReadBigInt(&symmetric_object_content_serial_number); + if (rc) + SetSymmetricObjectContentSerialNumber(symmetric_object_content_serial_number); + break; } @@ -471,6 +484,111 @@ const ON_Symmetry ON_Symmetry::TransformUnconditionally(const ON_Xform& xform) c return ON_Symmetry::Unset; } +static bool Internal_SamePlane(const ON_Symmetry* lhs, const ON_Symmetry* rhs, double zero_tolerance) +{ + const ON_PlaneEquation lhs_e = lhs->ReflectionPlane().UnitizedPlaneEquation(); + const ON_PlaneEquation rhs_e = rhs->ReflectionPlane().UnitizedPlaneEquation(); + return + fabs(lhs_e.x - rhs_e.x) <= zero_tolerance + && fabs(lhs_e.y - rhs_e.y) <= zero_tolerance + && fabs(lhs_e.z - rhs_e.z) <= zero_tolerance + && fabs(lhs_e.d - rhs_e.d) <= zero_tolerance + ; +} + +static bool Internal_SameRotation(const ON_Symmetry* lhs, const ON_Symmetry* rhs, double zero_tolerance) +{ + const ON_Line lhs_l = lhs->RotationAxis(); + const ON_Line rhs_l = rhs->RotationAxis(); + if ( + lhs_l.DistanceTo(rhs_l.from) <= zero_tolerance + && lhs_l.DistanceTo(rhs_l.to) <= zero_tolerance + && rhs_l.DistanceTo(lhs_l.from) <= zero_tolerance + && rhs_l.DistanceTo(lhs_l.to) <= zero_tolerance + ) + { + const ON_3dVector lhs_t = lhs->RotationAxis().Tangent(); + const ON_3dVector rhs_t = lhs->RotationAxis().Tangent(); + const double lhs_a = lhs->RotationAngleRadians(); + const double rhs_a = ((lhs_t * rhs_t < 0.0) ? -1.0 : 1.0) * rhs->RotationAngleRadians(); + if (fabs(lhs_a - rhs_a) <= zero_tolerance) + { + // a point 1 unit from the common axis will rotate within zero tolrance + return true; + } + } + return false; +} + +static bool Internal_SameTransformation(const ON_Xform lhs_x, const ON_Xform rhs_x, double zero_tolerance) +{ + return (lhs_x * rhs_x.Inverse()).IsIdentity(zero_tolerance) && (rhs_x * lhs_x.Inverse()).IsIdentity(zero_tolerance); +} + +static bool Internal_SameTransformation(const ON_Symmetry* lhs, const ON_Symmetry* rhs, double zero_tolerance) +{ + ON_Xform lhs_x; + ON_Xform rhs_x; + if (lhs->InversionOrder() != rhs->InversionOrder()) + return false; + if (lhs->CyclicOrder() != rhs->CyclicOrder()) + return false; + if (lhs->InversionOrder() > 1 && false == Internal_SameTransformation(lhs->InversionTransform(), rhs->InversionTransform(), zero_tolerance)) + return false; + if (lhs->CyclicOrder() > 1 && false == Internal_SameTransformation(lhs->CyclicTransform(), rhs->CyclicTransform(), zero_tolerance)) + return false; + return true; +} + + +int ON_Symmetry::CompareSymmetryTransformation(const ON_Symmetry* lhs, const ON_Symmetry* rhs, double zero_tolerance) +{ + for (;;) + { + const ON_Symmetry::Type lhs_type = (nullptr != lhs) ? lhs->SymmetryType() : ON_Symmetry::Type::Unset; + const ON_Symmetry::Type rhs_type = (nullptr != rhs) ? rhs->SymmetryType() : ON_Symmetry::Type::Unset; + if (lhs_type != rhs_type) + break; + + if (ON_Symmetry::Type::Unset == lhs_type) + return 0; // both are unset + + if (false == (zero_tolerance >= 0.0 && zero_tolerance < ON_UNSET_POSITIVE_FLOAT)) + zero_tolerance = ON_Symmetry::ZeroTolerance; + + switch (lhs_type) + { + case ON_Symmetry::Type::Unset: + break; + + case ON_Symmetry::Type::Reflect: + if (Internal_SamePlane(lhs, rhs, zero_tolerance)) + return 0; + break; + + case ON_Symmetry::Type::Rotate: + if (Internal_SameRotation(lhs, rhs, zero_tolerance)) + return 0; + break; + + case ON_Symmetry::Type::ReflectAndRotate: + if (Internal_SamePlane(lhs, rhs, zero_tolerance) && Internal_SameRotation(lhs, rhs, zero_tolerance)) + return 0; + break; + + case ON_Symmetry::Type::Inversion: + case ON_Symmetry::Type::Cyclic: + if (Internal_SameTransformation(lhs, rhs, zero_tolerance)) + return 0; + + default: + break; + } + } + + return ON_Symmetry::Compare(lhs, rhs); +} + const ON_Symmetry ON_Symmetry::CreateInversionSymmetry( ON_UUID symmetry_id, ON_Xform inversion_transform, @@ -1076,7 +1194,6 @@ const ON_Xform ON_Symmetry::MotifTransformation( return x; } - const ON_Xform ON_Symmetry::Internal_ReflectAndRotateTransformation(unsigned index) const { ON_Xform r = Internal_RotationXform(index / 2, m_cyclic_order); @@ -1084,3 +1201,38 @@ const ON_Xform ON_Symmetry::Internal_ReflectAndRotateTransformation(unsigned ind r = r * m_inversion_transform; return r; } + +ON_SHA1_Hash ON_Symmetry::Sha1Hash() const +{ + ON_SHA1 sha1; + sha1.AccumulateBytes(&m_type, sizeof(m_type)); + sha1.AccumulateBytes(&m_coordinates, sizeof(m_coordinates)); + sha1.AccumulateInteger8(m_inversion_order); + sha1.AccumulateInteger32(m_cyclic_order); + sha1.AccumulateId(m_id); + sha1.AccumulateDoubleArray(16, &m_inversion_transform.m_xform[0][0]); + sha1.AccumulateDoubleArray(16, &m_cyclic_transform.m_xform[0][0]); + sha1.AccumulateDoubleArray(4,&m_reflection_plane.x); + sha1.Accumulate3dPoint(m_rotation_axis.from); + sha1.Accumulate3dPoint(m_rotation_axis.to); + return sha1.Hash(); +} + +void ON_Symmetry::SetSymmetricObjectContentSerialNumber(ON__UINT64 symmetric_object_content_serial_number) const +{ + if (0 == symmetric_object_content_serial_number) + ClearSymmetricObjectContentSerialNumber(); // so a debugger breakpoint can be set in one place to watching clearing + else + m_symmetric_object_content_serial_number = symmetric_object_content_serial_number; +} + +void ON_Symmetry::ClearSymmetricObjectContentSerialNumber() const +{ + m_symmetric_object_content_serial_number = 0U; +} + +ON__UINT64 ON_Symmetry::SymmetricObjectContentSerialNumber() const +{ + return m_symmetric_object_content_serial_number; +} + diff --git a/opennurbs_symmetry.h b/opennurbs_symmetry.h index 9cca2149..535e378a 100644 --- a/opennurbs_symmetry.h +++ b/opennurbs_symmetry.h @@ -243,6 +243,13 @@ public: static int Compare(const ON_Symmetry* lhs, const ON_Symmetry* rhs); + /* + Returns: + 0 if the symmetry transformations are the same to tolerance. + Otherwise returns ON_Symmetry::Compare(lhs,rhs). + */ + static int CompareSymmetryTransformation(const ON_Symmetry* lhs, const ON_Symmetry* rhs, double zero_tolerance); + public: /* Returns: @@ -437,6 +444,34 @@ public: */ const ON_Symmetry TransformUnconditionally(const ON_Xform& xform) const; +public: + /* + Returns: + A SHA1 hash value that uniquely identifies the symmetry settings. + Remarks: + The symmetric object content serial number is not incuded in the hash. + */ + ON_SHA1_Hash Sha1Hash() const; + + /* + Description: + Set the mutable value returned by SymmetricObjectContentSerialNumber(). + */ + void SetSymmetricObjectContentSerialNumber(ON__UINT64 symmetric_object_content_serial_number) const; + + /* + Description: + Set the mutable value returned by SymmetricObjectContentSerialNumber() to 0. + */ + void ClearSymmetricObjectContentSerialNumber() const; + + /* + Returns: + Set a runtime serial number that corresponded to the content of the symmetric object + after it the application verified it was symmetric. + */ + ON__UINT64 SymmetricObjectContentSerialNumber() const; + private: static const double ZeroTolerance; @@ -452,7 +487,7 @@ private: // m_cyclic_order (0 = unset, 1 = identity (no cyclic), >= 2 cyclic order (non-identity cyclic) unsigned int m_cyclic_order = 0; - ON__UINT_PTR m_reserved2 = 0; + mutable ON__UINT64 m_symmetric_object_content_serial_number = 0; // id is a preset value for the 3 built in symmetries and user defined for others ON_UUID m_id = ON_nil_uuid; @@ -469,6 +504,14 @@ private: // Set when type is Rotate or ReflectAndRotate ON_Line m_rotation_axis = ON_Line::NanLine; +private: + ON__UINT_PTR m_reservedX = 0; + ON__UINT_PTR m_reservedY = 0; + double m_reservedA = 0.0; + double m_reservedB = 0.0; + double m_reservedC = 0.0; + double m_reservedD = 0.0; + private: static int Internal_CompareDouble(const double* lhs, const double* rhs, size_t count); const ON_Xform Internal_ReflectAndRotateTransformation(unsigned index) const; diff --git a/opennurbs_text.cpp b/opennurbs_text.cpp index e204a0e6..6acef1b8 100644 --- a/opennurbs_text.cpp +++ b/opennurbs_text.cpp @@ -262,7 +262,8 @@ bool ON_TextContent::Internal_ParseRtf( bool bComposeAndUpdateRtf ) { - if (nullptr == rtf_string) + const ON_wString rtf_w_string(rtf_string); + if (rtf_w_string.IsEmpty()) return false; dimstyle = &ON_DimStyle::DimStyleOrDefault(dimstyle); @@ -272,51 +273,6 @@ bool ON_TextContent::Internal_ParseRtf( ? dimstyle->TextPositionPropertiesHash() : ON_TextContent::Empty.DimStyleTextPositionPropertiesHash(); - // If the rtf string default font facename is different that the - // dimstyle font facename, change the rtf string to match the dimstyle - // This can happen when there's an existing annotation with rtf text - // because of some formatting and the style has been changed to use - // a different font. - // The intent is to leave any font changes in place and replace - // only the font used for text not explicitly set to some other font - ON_wString default_fontname = ON_Font::RichTextFontName(&dimstyle->Font(),true); - ON_wString rtf_w_string(rtf_string); - int ix = rtf_w_string.Find(L"{\\rtf1"); - if (ix != -1) - { - ON_wString dimstyle_facename = ON_Font::RichTextFontName(&dimstyle->Font(),true); - int idxdeff = rtf_w_string.Find(L"\\deff", ix + 5); - int deff = 0; - const wchar_t* n = rtf_w_string.Array() + idxdeff + 5; - const wchar_t* r = ON_wString::ToNumber(n, 0, &deff); - if (nullptr != r) - { - int idxftbl = rtf_w_string.Find(L"\\fonttbl"); - if (idxftbl > 0) - { - ON_wString fmt; - fmt.Format(L"\\f%d", deff); - idxdeff = rtf_w_string.Find(fmt, idxftbl + 8); - if (idxdeff > 0) - { - int idxface = rtf_w_string.Find(L" ", idxdeff); - int idx1 = rtf_w_string.Find(L";", idxface); - if (idx1 > 0) - { - default_fontname = rtf_w_string.SubString(idxface + 1, idx1 - idxface - 1); - if (0 != default_fontname.CompareOrdinal(dimstyle_facename, true)) - { - ON_wString t1 = rtf_w_string.Left(idxface + 1); - ON_wString t2 = rtf_w_string.Right(rtf_w_string.Length() - idx1); - rtf_w_string = t1 + default_fontname.Array(); - rtf_w_string = rtf_w_string + t2; - } - } - } - } - } - } - // Turn off recomposing a rtf string for the text // so that the source string stored on the text is // what the edit control produced @@ -347,7 +303,7 @@ bool ON_TextContent::Internal_ParseRtf( if (rc) rc = ON_TextContent::MeasureTextContent(this, true, false); if (rc) - m_default_font = &dimstyle->Font(); + m_default_font = &dimstyle->ParentDimStyleFont(); if (rc && bComposeAndUpdateRtf) { rc = RtfComposer::Compose(this, str, false); @@ -1022,7 +978,6 @@ bool ON_TextContent::WrapText(double wrapwidth) const void ON_TextContent::Dump(ON_TextLog& text_log) const // for debugging {} - bool ON_TextContent::Write( ON_BinaryArchive& archive ) const @@ -1030,12 +985,57 @@ bool ON_TextContent::Write( const int content_version = 0; if (false == archive.BeginWrite3dmAnonymousChunk(content_version)) return false; - + bool rc = false; for (;;) { - if (!archive.WriteString(m_text)) + bool bRichTextStringSaved = false; + for (;;) + { + // NEVER commit code with this define set. + // + // This code is used by developers to create files used to test + // code and UI for handing situations when a font is not installed + // on a device. + // + // To create a missing font test .3dm file, you must modify code + // int opennurbs_font.cpp and in opennurbs_text.cpp. + // Search for CREATE_MISSING_FONT_TEST_3DM_FILE to find the locations. +//#define CREATE_MISSING_FONT_TEST_3DM_FILE +#if defined(CREATE_MISSING_FONT_TEST_3DM_FILE) + if (m_text.Length() > 7 && ON_wString::EqualOrdinal( L"{\\rtf1\\", 7, static_cast(m_text), 7, false) ) + { + // All fonts in the LOGFONT with MISSING_FONT_TEST_valid_logfont_name + // will be saved as a family named missing_family_name. + // The initial space and terminating ;} are a hack to get the entry in the rtf font table. + // Keep in mind this is developer hack code for creating files to test + // handling of missing fonts. Customers never run this code. + const ON_wString MISSING_FONT_TEST_valid_font_rtf_name(L" Courier New;}"); + if (m_text.Find(static_cast(MISSING_FONT_TEST_valid_font_rtf_name))) + { + const ON_wString missing_font_rtf_name(L" Missing Font Test;}"); + ON_wString modified_text(static_cast(m_text)); + if (modified_text.Replace(static_cast(MISSING_FONT_TEST_valid_font_rtf_name), static_cast(missing_font_rtf_name)) > 0) + { + if (!archive.WriteString(modified_text)) + break; + bRichTextStringSaved = true; + break; + } + } + } +#endif + // write correct m_text + if (!archive.WriteString(m_text)) + break; + + bRichTextStringSaved = true; break; + } + + if (false == bRichTextStringSaved) + break; + // obsolete plane was always world xy if (!archive.WritePlane(ON_Plane::World_xy)) break; diff --git a/opennurbs_text.h b/opennurbs_text.h index 99950f21..822179d6 100644 --- a/opennurbs_text.h +++ b/opennurbs_text.h @@ -523,6 +523,14 @@ public: const ON_Font* FirstCharFont() const; + // Get the exact strings in the RTF font table and add them to font_table[] + // Example + // Input rich_text = {\rtf1\deff0{\fonttbl{\f0 ArialMT;}{\f1 SegoeUI;}}\f0 \fs40{\f1 This is Segoe UI}} + // returns font_table[] = { "ArialMT", "SegoeUI"} + static bool GetRichTextFontTable( + const ON_wString rich_text, + ON_ClassArray< ON_wString >& font_table + ); // Dimension text formatting static bool FormatDistance( diff --git a/opennurbs_textcontext.cpp b/opennurbs_textcontext.cpp index 3ac57595..967cbe9e 100644 --- a/opennurbs_textcontext.cpp +++ b/opennurbs_textcontext.cpp @@ -249,4 +249,67 @@ const ON_Font* ON_TextContent::FirstCharFont() const return &ON_Font::Default; } +//static +bool ON_TextContent::GetRichTextFontTable( + const ON_wString rich_text, + ON_ClassArray< ON_wString >& font_table +) +{ + int table_pos = rich_text.Find(L"\\fonttbl"); + if (table_pos < 0) + return false; + + const wchar_t* rtf = rich_text.Array(); + int open = 1; + int table_len = 0; + int len = rich_text.Length(); + for (int i = table_pos + 8; i < len && open > 0; i++) + { + if (L'{' == rich_text[i]) + { + open++; + } + else if (L'}' == rich_text[i]) + { + open--; + table_len = i; + } + } + + for (int i = table_pos + 8; i < table_len; i++) + { + int font_pos = rich_text.Find(L"\\f", i); + if (font_pos > i) + { + for (int j = font_pos + 2; j < table_len; j++) + { + if (rtf[j] == L' ') + { + for (int si = 0; si + j < table_len; si++) + { + if (rich_text[si + j] != L' ') + { + j += si; + break; + } + } + for (int ni = 1; ni + j < table_len; ni++) + { + if (rtf[ni + j] == L';' || rtf[ni + j] == L'}') + { + font_table.AppendNew() = rich_text.SubString(j, ni); + i = ni + j; + j = len; + break; + } + } + } + } + } + } + return true; +} + + + //-------------------------------------------------------------------- diff --git a/opennurbs_textglyph.cpp b/opennurbs_textglyph.cpp index 361d3bac..788f4cff 100644 --- a/opennurbs_textglyph.cpp +++ b/opennurbs_textglyph.cpp @@ -535,27 +535,49 @@ void ON_FontGlyph::Dump( ) const { ON_wString s; - const ON_FontGlyph* g = this; - bool bPrintMaps = false; - for (int pass = 0; pass < 2; pass++) + const ON_FontGlyph* g[2] = { this, bIncludeSubstitute ? this->SubstituteGlyph() : nullptr }; + ON_wString apple_substitute_postscript_name; + +#if defined (ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) + if (nullptr == g[1] ) { - if (nullptr == g) + // Apple glyph substitution is handled differently from Windows + // because Apple's font SDK is more limited and finding the best + // font from a codepoint is not easily done. + const ON_Font* glyph_font = g[0]->Font(); + if (nullptr != glyph_font) + { + bool bIsSubstituteFont = false; + CTFontRef apple_font = glyph_font->AppleCTFont(0.0, bIsSubstituteFont); + if (bIsSubstituteFont && nullptr != apple_font) + { + apple_substitute_postscript_name = glyph_font->AppleCTFontPostScriptName(apple_font); + apple_substitute_postscript_name.TrimLeftAndRight(); + if (apple_substitute_postscript_name.IsNotEmpty()) + g[1] = g[0]; + } + } + } +#endif + + const bool bAppleSubstitute = apple_substitute_postscript_name.IsNotEmpty(); + bool bPrintMaps = false; + for (int pass = 0; pass < (nullptr != g[1] ? 2 : 1); pass++) + { + if (nullptr == g[pass]) break; - if (pass > 0) - { - if (false == bIncludeSubstitute) - break; - s += L" -> substitute: "; - } - if ( ON_IsValidUnicodeCodePoint(g->CodePoint()) ) + if (pass > 0) + s += L" -> substitute: "; + + if ( ON_IsValidUnicodeCodePoint(g[pass]->CodePoint()) ) { - const unsigned int code_point = g->CodePoint(); - const unsigned int glyph_index = g->FontGlyphIndex(); + const unsigned int code_point = g[pass]->CodePoint(); + const unsigned int glyph_index = (1 == pass || false == bAppleSubstitute) ? g[pass]->FontGlyphIndex() : 0; wchar_t w[8] = { 0 }; ON_EncodeWideChar(code_point, 7, w); - const ON_Font* font = g->Font(); + const ON_Font* font = g[pass]->Font(); s += ON_wString::FormatToString( L"[%ls] U+%04X", w, @@ -566,7 +588,10 @@ void ON_FontGlyph::Dump( { if (nullptr != font) { - const ON_wString font_description = (font) ? font->Description() : ON_wString::EmptyString; + const ON_wString font_description + = (1 == pass && bAppleSubstitute) + ? apple_substitute_postscript_name + : ((font) ? font->Description() : ON_wString::EmptyString); unsigned int font_sn = (font) ? font->RuntimeSerialNumber() : 0; s += ON_wString::FormatToString( L" %ls <%u>", @@ -585,14 +610,14 @@ void ON_FontGlyph::Dump( s += ON_wString::FormatToString(L" glyph index = %u", glyph_index); bPrintMaps = bIncludeCharMaps; } - else if (bIncludeFont) + else { s += L" (no glyph)"; } - const ON_TextBox gbox = g->FontUnitGlyphBox(); - const bool bGlyphBoxIsSet = gbox.IsSet() || g->GlyphBox().IsSet(); - const bool bManagedGlyph = (g->IsManaged()); + const ON_TextBox gbox = g[pass]->FontUnitGlyphBox(); + const bool bGlyphBoxIsSet = gbox.IsSet() || g[pass]->GlyphBox().IsSet(); + const bool bManagedGlyph = (g[pass]->IsManaged()); if (bManagedGlyph) { if (false == bGlyphBoxIsSet) @@ -616,11 +641,10 @@ void ON_FontGlyph::Dump( { s =+ L"ON_FontGlyph::Unset"; } - const ON_FontGlyph* sub_g = g->SubstituteGlyph(); - if (nullptr == sub_g) + if (nullptr == g[1]) break; + bPrintMaps = false; - g = sub_g; } if (s.IsEmpty()) @@ -632,10 +656,10 @@ void ON_FontGlyph::Dump( #if !defined(ON_RUNTIME_APPLE) && defined(OPENNURBS_FREETYPE_SUPPORT) // Look in opennurbs_system_rumtime.h for the correct place to define OPENNURBS_FREETYPE_SUPPORT. // Do NOT define OPENNURBS_FREETYPE_SUPPORT here or in your project setting ("makefile"). - if ( bPrintMaps && nullptr != g ) + if ( bPrintMaps && nullptr != g[0] ) { text_log.PushIndent(); - g->TestFreeTypeFaceCharMaps(&text_log); + g[nullptr != g[1] ? 1 : 0]->TestFreeTypeFaceCharMaps(&text_log); text_log.PopIndent(); } #endif diff --git a/opennurbs_textiterator.cpp b/opennurbs_textiterator.cpp index 4fbd800a..c2e9baa2 100644 --- a/opennurbs_textiterator.cpp +++ b/opennurbs_textiterator.cpp @@ -362,6 +362,14 @@ void ON_TextBuilder::GroupEnd() m_level--; } +void ON_TextBuilder::RunBegin() +{ +} + +void ON_TextBuilder::RunEnd() +{ +} + bool ON_TextBuilder::ReadingFontTable() { return (m_level >= m_font_table_level); @@ -1184,6 +1192,38 @@ void ON_TextRunBuilder::GroupEnd() // '}' m_level--; } +void ON_TextRunBuilder::RunBegin() // like { with no pushing properties +{ + int cp32_count = m_current_codepoints.Count(); + if (cp32_count > 0) + { + FlushText(cp32_count, m_current_codepoints.Array()); + m_current_codepoints.Empty(); + } + FinishCurrentRun(); + + m_current_run.Init(m_current_font, m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), + m_current_props.IsBold(), m_current_props.IsItalic(), m_current_props.IsUnderlined(), m_current_props.IsStrikethrough()); +} + + +void ON_TextRunBuilder::RunEnd() // like '}' with no popping properties +{ + int cp32_count = m_current_codepoints.Count(); + if (cp32_count > 0) + { + FlushText(cp32_count, m_current_codepoints.Array()); + m_current_codepoints.Empty(); + } + FinishCurrentRun(); + + m_current_run.Init(m_current_font, m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), + m_current_props.IsBold(), m_current_props.IsItalic(), m_current_props.IsUnderlined(), m_current_props.IsStrikethrough()); + + if (m_level <= m_font_table_level) + m_font_table_level = 10000; +} + void ON_TextRunBuilder::FlushText(size_t count, ON__UINT32* cp) { if (count < 1 || 0 == cp || 0 == cp[0]) @@ -1863,6 +1903,39 @@ void ON_RtfStringBuilder::GroupEnd() m_current_run = PopRun(); } +void ON_RtfStringBuilder::RunBegin() +{ + // not skipping the color table or we're not in it + if (!SkipColorTbl() || m_current_run.Type() != ON_TextRun::RunType::kColortbl) + m_string_out += m_current_run.TextString(); + + m_current_run.EmptyText(); + m_current_run.SetTerminated(true); + + m_current_run.SetType(ON_TextRun::RunType::kText); + m_current_run.ClearHasContent(); + m_have_rtf = true; +} + +void ON_RtfStringBuilder::RunEnd() +{ + if (m_current_run.Type() != ON_TextRun::RunType::kColortbl) + { + if (m_level >= 0) + { + if (m_current_run.Type() == ON_TextRun::RunType::kFonttbl) + { + m_font_table_level = 10000; + } + m_string_out = m_string_out + m_current_run.TextString(); + m_current_run.EmptyText(); + } + } + + if (m_current_run.Type() == ON_TextRun::RunType::kColortbl) + SetInColorTable(false); +} + void ON_RtfStringBuilder::BeginFontTable() { m_font_table_level = m_level; @@ -2304,6 +2377,31 @@ void ON_RtfFirstChar::GroupEnd() m_level--; } +void ON_RtfFirstChar::RunBegin() +{ + m_have_rtf = true; +} + +void ON_RtfFirstChar::RunEnd() +{ + if (m_current_run.Type() == ON_TextRun::RunType::kFontdef) + { + // String is a font facename. Make a font with that facename + // and a font definition run + ON_wString str; + str = m_current_run.Text(); + if (!str.IsEmpty()) + { + str.Remove(L';'); // facename delimiter from rtf + ON_FaceNameKey& fn_key = m_facename_map.AppendNew(); + fn_key.m_rtf_font_index = m_font_index; + fn_key.m_rtf_font_name = str; + fn_key.m_charset = m_current_props.CharSet(); + fn_key.m_codepage = m_current_props.CodePage(); + } + } +} + void ON_RtfFirstChar::BeginFontTable() { m_font_table_level = m_level; @@ -2725,9 +2823,9 @@ bool ON_RtfParser::Parse() case ON_UnicodeCodePoint::ON_LineSeparator: case ON_UnicodeCodePoint::ON_ParagraphSeparator: FlushCurText(m_builder.m_current_codepoints); - m_builder.GroupEnd(); + m_builder.RunEnd(); ProcessTag(L"par", nullptr, false); - m_builder.GroupBegin(); + m_builder.RunBegin(); break; case '\'': @@ -3007,11 +3105,12 @@ bool RtfComposer::Compose( bool chg_strikeout = false; bool chg_facename = false; - // First facename is from the ON_TextContent + // First facename is from the ON_TextContent (style_font) // Any after that are from runs ON_ClassArray< ON_wString > fonttable(8); - // Creates a fonttable entry the first time + // Creates a fonttable entry if the font isn't in the fonttable + // Returns the table index if it is unsigned int stylefont_key = GetFacenameKey(style_fontname, fonttable); int runcount = runs->Count(); @@ -3060,7 +3159,7 @@ bool RtfComposer::Compose( if (!chg_strikeout) chg_strikeout = run_font->IsStrikethrough() != style_strikeout; - const ON_wString& run_fontname = run_font->RichTextFontName(); + const ON_wString run_fontname = run_font->RichTextFontName(); if (run_fontname.IsEmpty()) return false; unsigned int run_font_key = GetFacenameKey(run_fontname, fonttable); @@ -3091,6 +3190,17 @@ bool RtfComposer::Compose( ) { runholders.AppendNew() = run; + const ON_Font* run_font = run->Font(); + if (nullptr != run_font) + { + const ON_wString run_fontname = run_font->RichTextFontName(); + if (!run_fontname.IsEmpty()) + { + unsigned int run_font_key = GetFacenameKey(run_fontname, fonttable); + if (!chg_facename) + chg_facename = run_font_key != stylefont_key; + } + } } } } // end of getting runinfo @@ -3114,6 +3224,7 @@ bool RtfComposer::Compose( { if (make_rtf || (run->IsStacked() == ON_TextRun::Stacked::kStacked && run->m_stacked_text != 0)) { + unsigned int run_font_key = ON_UNSET_UINT_INDEX; if (make_rtf) { const ON_Font* run_font = run->Font(); @@ -3124,7 +3235,7 @@ bool RtfComposer::Compose( run_strings += L"{"; bool addspace = false; - unsigned int run_font_key = GetFacenameKey(run_font, fonttable); + run_font_key = GetFacenameKey(run_font, fonttable); temp.Format(L"\\f%d", run_font_key); run_strings += temp; addspace = true; @@ -3182,19 +3293,47 @@ bool RtfComposer::Compose( { GetRunText(run, run_strings, make_rtf); } + + if (ri < runcount - 1) + { + ON_TextRun* par_run = runholders[ri+1]; + if (nullptr != par_run && (ON_TextRun::RunType::kNewline == par_run->Type() || ON_TextRun::RunType::kParagraph == par_run->Type())) + { + const ON_Font* par_run_font = par_run->Font(); + if (nullptr != par_run_font) + { + unsigned int par_run_font_key = GetFacenameKey(par_run_font, fonttable); + if (par_run_font_key == run_font_key) + { + run_strings += L"\\par"; + ri++; + } + } + } + } if (make_rtf) run_strings += L"}"; } - else + else // not making rtf, just collect text from runs { GetRunText(run, run_strings, make_rtf); } } - else if (ri < runcount - 1 && multiline && (ON_TextRun::RunType::kNewline == run->Type() || ON_TextRun::RunType::kParagraph == run->Type())) + else if (ON_TextRun::RunType::kNewline == run->Type() || ON_TextRun::RunType::kParagraph == run->Type()) { if (make_rtf) - run_strings += L"{\\par}"; - else + { + temp = L"{\\par}"; + const ON_Font* par_run_font = run->Font(); + if (nullptr != par_run_font) + { + unsigned int par_run_font_key = GetFacenameKey(par_run_font, fonttable); + if(par_run_font_key != stylefont_key) + temp.Format(L"{\\f%d \\par}", par_run_font_key); + } + run_strings += temp; + } + else if(ri < runcount - 1 && multiline) // not making rtf run_strings += L"\n"; } } // end of collecting run text @@ -3230,7 +3369,10 @@ bool RtfComposer::Compose( } - rtf += L"}\\fs40"; + temp.Format(L"}\\f%d \\fs40", stylefont_key); + rtf += temp; + + //rtf += L"}\\fs40"; if (style_bold) rtf += L"\\b"; if (style_italic) @@ -3240,7 +3382,7 @@ bool RtfComposer::Compose( rtf += run_strings; // backing out the change made for RH-49725 because it causes the problem reported in RH-50733 - rtf += L"\\par}"; + rtf += L"}"; } else { @@ -3249,6 +3391,7 @@ bool RtfComposer::Compose( return true; } +// Turns on or off composing for debugging bool RtfComposer::m_bComposeRTF = true; bool RtfComposer::RecomposeRTF() { @@ -3259,6 +3402,15 @@ void RtfComposer::SetRecomposeRTF(bool b) RtfComposer::m_bComposeRTF = b; } +static const ON_wString Internal_PostScriptNameIfAvailable(const ON_Font& managed_font) +{ + ON_wString style_fontname = managed_font.PostScriptName(); + if (style_fontname.IsNotEmpty()) + return style_fontname; + + return ON_wString::FormatToString(L"Postscript-name-not-available-%u", managed_font.ManagedFontSerialNumber()); +} + const ON_wString RtfComposer::ComposeAppleRTF( const ON_TextContent* text) { @@ -3273,9 +3425,7 @@ const ON_wString RtfComposer::ComposeAppleRTF( const ON_Font& style_font = text->DefaultFont(); - const ON_wString style_fontname = style_font.PostScriptName(); - if (style_fontname.IsEmpty()) - return rtf_string; + const ON_wString style_fontname = Internal_PostScriptNameIfAvailable(style_font); // First facename is from the ON_TextContent // Any after that are from runs @@ -3347,9 +3497,7 @@ const ON_wString RtfComposer::ComposeAppleRTF( if (nullptr == run_font) continue; - const ON_wString& run_fontname = run_font->PostScriptName(); - if (run_fontname.IsEmpty()) - return rtf_string; + const ON_wString& run_fontname = Internal_PostScriptNameIfAvailable(*run_font); // add properties for this string run_strings += L"{"; @@ -3398,25 +3546,49 @@ const ON_wString RtfComposer::ComposeAppleRTF( { GetRunText(run, run_strings, true_bool); } + + if (ri < runcount - 2) + { + ON_TextRun* par_run = runholders[ri + 1]; + if (nullptr != par_run && (ON_TextRun::RunType::kNewline == par_run->Type() || ON_TextRun::RunType::kParagraph == par_run->Type())) + { + const ON_Font* par_run_font = par_run->Font(); + if (nullptr != par_run_font) + { + const ON_wString& par_run_fontname = Internal_PostScriptNameIfAvailable(*par_run_font); + if (!par_run_fontname.IsEmpty()) + { + unsigned int par_run_font_key = GetFacenameKey(par_run_fontname, fonttable); + if (par_run_font_key == run_font_key) + { + run_strings += L"\\\n"; + ri++; + } + } + } + } + } + run_strings += L"}"; } else if (ri < runcount - 1 && multiline && (ON_TextRun::RunType::kNewline == run->Type() || ON_TextRun::RunType::kParagraph == run->Type())) { - run_strings += L"{\\par}"; + temp = L"{\\par}"; + const ON_Font* run_font = run->Font(); + if (nullptr != run_font) + { + const ON_wString run_fontname = Internal_PostScriptNameIfAvailable(*run_font); + unsigned int run_font_key = GetFacenameKey(run_fontname, fonttable); + if(run_font_key != deffont_key) + temp.Format(L"{\\f%d \\par}", run_font_key); + } + run_strings += temp; } } // end of collecting run text int nfont = fonttable.Count(); if (run_strings.Length() > 0) { - // deff0 means use font0 for the default font throughout the string. - // If we include font0 in the font table, when we send - // the string back to the RTF control, the font listed as - // font0 will be used instead of the default font for the - // style. - // So the default font is listed as 0 and there is no - // entry in the table for font0. - rtf_string.Format(L"{\\rtf1"); if (0 < nfont) { @@ -3432,10 +3604,12 @@ const ON_wString RtfComposer::ComposeAppleRTF( rtf_string += fonttable_string; } - rtf_string += L"}\\fs40"; + temp.Format(L"}\\f%d \\fs40", deffont_key); + rtf_string += temp; + //rtf_string += L"}\\fs40"; rtf_string += run_strings; - rtf_string += L"\\par}"; + rtf_string += L"}"; } return rtf_string; } diff --git a/opennurbs_textiterator.h b/opennurbs_textiterator.h index 67bf1183..f7687c54 100644 --- a/opennurbs_textiterator.h +++ b/opennurbs_textiterator.h @@ -278,6 +278,8 @@ public: virtual void FlushText(size_t count, ON__UINT32* cp_array); virtual void GroupBegin(); virtual void GroupEnd(); + virtual void RunBegin(); + virtual void RunEnd(); virtual void FinishFontDef(); virtual bool ReadingFontTable(); virtual bool ReadingFontDefinition(); @@ -376,6 +378,8 @@ public: void FlushText(size_t count, ON__UINT32* cp_array) override; void GroupBegin() override; void GroupEnd() override; + void RunBegin() override; + void RunEnd() override; void FinishFontDef() override; bool AppendCodePoint(ON__UINT32 codept) override; void FormatChange() override; @@ -637,6 +641,8 @@ public: void GroupBegin() override; void GroupEnd() override; + void RunBegin() override; + void RunEnd() override; void BeginHeader() override; void BeginFontTable() override; @@ -808,6 +814,8 @@ public: void GroupBegin() override; void GroupEnd() override; + void RunBegin() override; + void RunEnd() override; void BeginHeader() override; void BeginFontTable() override; diff --git a/opennurbs_textrun.cpp b/opennurbs_textrun.cpp index b7100f1f..2c1820d6 100644 --- a/opennurbs_textrun.cpp +++ b/opennurbs_textrun.cpp @@ -1061,6 +1061,7 @@ int ON_TextRun::WrapTextRun( ON_TextBox text_box; ON_FontGlyph::GetGlyphList(display_string, font, ON_NextLine, glyph_list, text_box); + int glyph_count = glyph_list.Count(); const ON_FontGlyph* Aglyph = font->CodePointGlyph((ON__UINT32)L'A'); if (nullptr == Aglyph) @@ -1088,18 +1089,15 @@ int ON_TextRun::WrapTextRun( else // Part of the run has already been picked off and added to the previous line { // Find width of remaining characters - for (int ci = start_char_offset; ci < (int)wcscount; ci++) + for (int ci = start_char_offset; ci < wcscount && ci < glyph_count; ci++) { const ON_FontGlyph* gi = glyph_list[ci]; if (nullptr != gi) { - //if (not surrogate pair) - { - const ON_TextBox glyph_box = gi->GlyphBox(); - double charwidth = glyph_box.m_advance.i * height_scale; - runwidth += charwidth; - } + const ON_TextBox glyph_box = gi->GlyphBox(); + double charwidth = glyph_box.m_advance.i * height_scale; + runwidth += charwidth; } } } @@ -1146,62 +1144,73 @@ int ON_TextRun::WrapTextRun( double curwidth = 0.0; double linefeedheight = font->FontMetrics().LineSpace() * height_scale; + int sp_count = 0; for (int ci = start_char_offset; ci < (int)wcscount; ci++) { - const ON_FontGlyph* gi = glyph_list[ci]; - if (nullptr != gi) + if ((ci + 1) < wcscount && + display_string[ci] >= 0xD800 && display_string[ci] < 0xDC00 && + display_string[ci + 1] >= 0xDC00 && display_string[ci + 1] < 0xE000) { + sp_count++; + continue; + } + + int gi = ci - sp_count; + if (gi >= glyph_count) + break; + + const ON_FontGlyph* glyph = glyph_list[gi]; + if (nullptr != glyph) + { + const ON_TextBox glyph_box = glyph->GlyphBox(); + curwidth += glyph_box.m_advance.i * height_scale; + run_length++; + + if (linewidth + curwidth > wrapwidth) // reached wrapping width { - const ON_TextBox glyph_box = gi->GlyphBox(); - curwidth += glyph_box.m_advance.i * height_scale; - run_length++; + if (found_space) // store run up to last space + run_length = last_space - start_char_offset + 1; + else if (0.0 < linewidth) // A line is already started + run_length = 0; + else // no space yet - store run up to this char position + run_length = ci - start_char_offset; - if (linewidth + curwidth > wrapwidth) // reached wrapping width + if (0 < run_length) { - if (found_space) // store run up to last space - run_length = last_space - start_char_offset + 1; - else if (0.0 < linewidth) // A line is already started - run_length = 0; - else // no space yet - store run up to this char position - run_length = ci - start_char_offset; - - if (0 < run_length) + ON_TextRun* newrun = ON_TextRun::GetManagedTextRun(); // make a new run + if (nullptr != newrun) { - ON_TextRun* newrun = ON_TextRun::GetManagedTextRun(); // make a new run - if (nullptr != newrun) - { - *newrun = *this; - wcsncpy(temp_display_str, display_string + start_char_offset, run_length); - temp_display_str[run_length] = 0; - newrun->SetDisplayString(temp_display_str); - newrun->SetOffset(ON_2dVector(0.0, y_offset + Offset().y)); - newruns.AppendRun(newrun); - } + *newrun = *this; + wcsncpy(temp_display_str, display_string + start_char_offset, run_length); + temp_display_str[run_length] = 0; + newrun->SetDisplayString(temp_display_str); + newrun->SetOffset(ON_2dVector(0.0, y_offset + Offset().y)); + newruns.AppendRun(newrun); } - // add a soft return - ON_TextRun* lfrun = ON_TextRun::GetManagedTextRun(); - if (nullptr != lfrun) - { - lfrun->SetFont(Font()); - lfrun->SetType(ON_TextRun::RunType::kSoftreturn); - lfrun->SetTextHeight(this->TextHeight()); - newruns.AppendRun(lfrun); - - // Starting a new line now - linewidth = 0.0; - curwidth = 0.0; - y_offset -= linefeedheight; - } - - int wrapcount = WrapTextRun(call_count + 1, run_length + start_char_offset, wrapwidth, y_offset, linewidth, newruns); - onfree(temp_display_str); - return new_count + wrapcount; } - if (iswspace(display_string[ci])) + // add a soft return + ON_TextRun* lfrun = ON_TextRun::GetManagedTextRun(); + if (nullptr != lfrun) { - found_space = true; - last_space = ci; + lfrun->SetFont(Font()); + lfrun->SetType(ON_TextRun::RunType::kSoftreturn); + lfrun->SetTextHeight(this->TextHeight()); + newruns.AppendRun(lfrun); + + // Starting a new line now + linewidth = 0.0; + curwidth = 0.0; + y_offset -= linefeedheight; } + + int wrapcount = WrapTextRun(call_count + 1, run_length + start_char_offset, wrapwidth, y_offset, linewidth, newruns); + onfree(temp_display_str); + return new_count + wrapcount; + } + if (iswspace(display_string[ci])) + { + found_space = true; + last_space = ci; } } } diff --git a/opennurbs_texture_mapping.h b/opennurbs_texture_mapping.h index f3c153ea..ea746baf 100644 --- a/opennurbs_texture_mapping.h +++ b/opennurbs_texture_mapping.h @@ -83,7 +83,8 @@ public: box_mapping = 5, mesh_mapping_primitive = 6, // m_mapping_primitive is an ON_Mesh srf_mapping_primitive = 7, // m_mapping_primitive is an ON_Surface - brep_mapping_primitive = 8 // m_mapping_primitive is an ON_Brep + brep_mapping_primitive = 8, // m_mapping_primitive is an ON_Brep + ocs_mapping = 9 // same as plane_mapping - used to differentiate between OCS and plane mapping in the UI }; static ON_TextureMapping::TYPE TypeFromUnsigned( diff --git a/opennurbs_uuid.cpp b/opennurbs_uuid.cpp index cb5bced4..6f83a8a7 100644 --- a/opennurbs_uuid.cpp +++ b/opennurbs_uuid.cpp @@ -481,6 +481,10 @@ ON_UUID ON_UuidFromString( const wchar_t* sUUID ) } +ON_UuidIndex::ON_UuidIndex(ON_UUID id, int index) + : m_id(id) + , m_i(index) +{} int ON_UuidIndex::CompareIdAndIndex( const ON_UuidIndex* a, const ON_UuidIndex* b ) { int i; diff --git a/opennurbs_uuid.h b/opennurbs_uuid.h index 1784647c..91d9fc45 100644 --- a/opennurbs_uuid.h +++ b/opennurbs_uuid.h @@ -307,6 +307,8 @@ public: ON_UuidIndex(const ON_UuidIndex&) = default; ON_UuidIndex& operator=(const ON_UuidIndex&) = default; + ON_UuidIndex(ON_UUID id, int index); + /* Dictionary compare m_id and then m_i. */ diff --git a/opennurbs_win_dwrite.cpp b/opennurbs_win_dwrite.cpp index fcfa26a7..05873d8b 100644 --- a/opennurbs_win_dwrite.cpp +++ b/opennurbs_win_dwrite.cpp @@ -457,10 +457,20 @@ void ON_Font::DumpWindowsDWriteFont( // Other names: Internal_DWriteFontInformation info_strings[] = { + // DWRITE_INFORMATIONAL_STRING_DESCRIPTION gets field 10 from the ttf file. + // Opennurbs searches the description saved in field 10 of the name table + // for the strings "Engraving - single stroke" / "Engraving - double stroke" / "Engraving" + // to identify fonts that are desgned for engraving (and which tend to render poorly when + // used to dispaly text devices like screens, monitors, and printers). + // The SLF (single line fonts) are examples of fonts that have Engraving in field 10. + Internal_DWriteFontInformation(DWRITE_INFORMATIONAL_STRING_DESCRIPTION,L"DWrite field 10 description"), + Internal_DWriteFontInformation(DWRITE_INFORMATIONAL_STRING_FULL_NAME,L"DWrite full name"), Internal_DWriteFontInformation(DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME,L"DWrite PostScript name"), Internal_DWriteFontInformation(DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES,L"GDI family name"), - Internal_DWriteFontInformation(DWRITE_INFORMATIONAL_STRING_WIN32_SUBFAMILY_NAMES,L"GDI sub-family name") + Internal_DWriteFontInformation(DWRITE_INFORMATIONAL_STRING_WIN32_SUBFAMILY_NAMES,L"GDI sub-family name"), + + Internal_DWriteFontInformation(DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_CID_NAME,L"Dwrite PostScript CID findfont name") }; const size_t info_strings_count = sizeof(info_strings) / sizeof(info_strings[0]); for (size_t i = 0; i < info_strings_count; i++) @@ -475,6 +485,14 @@ void ON_Font::DumpWindowsDWriteFont( const ON_wString postscriptName = ON_Font::PostScriptNameFromWindowsDWriteFont(dwrite_font, preferedLocale); text_log.Print(L"ON_Font PostScript name = \"%ls\"\n", static_cast(postscriptName)); + // Opennurbs searches the description saved in field 10 of the name table + // for the strings "Engraving - single stroke" / "Engraving - double stroke" / "Engraving" + // to identify fonts that are desgned for engraving (and which tend to render poorly when + // used to dispaly text devices like screens, monitors, and printers). + // The SLF (single line fonts) are examples of fonts that have Engraving in field 10. + const ON_wString field_10_description = ON_Font::Field10DescriptionFromWindowsDWriteFont(dwrite_font, preferedLocale); + text_log.Print(L"ON_Font field 10 description = \"%ls\"\n", static_cast(field_10_description)); + bool bGotLogfont = false; for (;;) { @@ -627,7 +645,38 @@ const ON_wString ON_Font::PostScriptNameFromWindowsDWriteFont( //// postscriptName += suffix; //// } ////} - return loc_PostScriptName; + return loc_PostScriptName.IsNotEmpty() ? loc_PostScriptName : en_PostScriptName; + } + return ON_wString::EmptyString; +} + + +// Returns the desription saved in field 10. +// Opennurbs searches the description saved in field 10 of the name table +// for the strings "Engraving - single stroke" / "Engraving - double stroke" / "Engraving" +// to identify fonts that are desgned for engraving (and which tend to render poorly when +// used to dispaly text devices like screens, monitors, and printers). +// The SLF (single line fonts) are examples of fonts that have Engraving in field 10. +const ON_wString ON_Font::Field10DescriptionFromWindowsDWriteFont( + struct IDWriteFont* dwrite_font, + const wchar_t* preferedLocale +) +{ + for (;;) + { + if (nullptr == dwrite_font) + break; + BOOL exists = false; + Microsoft::WRL::ComPtr descriptions = nullptr; + HRESULT hr = dwrite_font->GetInformationalStrings(DWRITE_INFORMATIONAL_STRING_DESCRIPTION, &descriptions, &exists); + if (FAILED(hr)) + break; + ON_wString loc_description; + ON_wString en_description; + if (exists) + Internal_GetLocalizeStrings(preferedLocale, descriptions.Get(), loc_description, en_description); + + return loc_description.IsNotEmpty() ? loc_description : en_description; } return ON_wString::EmptyString; } @@ -654,6 +703,7 @@ void ON_WindowsDWriteFontInformation::Dump(ON_TextLog& text_log) const text_log.Print(L"GDI LOGFONT name = \"%ls\"\n", static_cast(m_loc_gdi_family_name)); text_log.Print(L"GDI subfamily name = \"%ls\"\n", static_cast(m_loc_gdi_subfamily_name)); text_log.Print(L"Full name = \"%ls\"\n", static_cast(m_loc_full_name)); + text_log.Print(L"Field 10 Description = \"%ls\"\n", static_cast(m_loc_field_10_description)); text_log.PopIndent(); text_log.Print(L"English Names:\n"); @@ -664,6 +714,7 @@ void ON_WindowsDWriteFontInformation::Dump(ON_TextLog& text_log) const text_log.Print(L"GDI LOGFONT name = \"%ls\"\n", static_cast(m_en_gdi_family_name)); text_log.Print(L"GDI subfamily name = \"%ls\"\n", static_cast(m_en_gdi_subfamily_name)); text_log.Print(L"Full name = \"%ls\"\n", static_cast(m_en_full_name)); + text_log.Print(L"Field 10 Description = \"%ls\"\n", static_cast(m_en_field_10_description)); text_log.PopIndent(); if (m_bSimulatedBold || m_bSimulatedOblique || m_bSimulatedOther) @@ -1015,6 +1066,15 @@ unsigned int ON_Font::GetInstalledWindowsDWriteFonts( Internal_GetLocalizeStrings(preferedLocale, gdiSubfamilyNames.Get(), fi.m_loc_gdi_subfamily_name, fi.m_en_gdi_subfamily_name); } + // Field 10 Description + // Opennurbs searches the description saved in field 10 of the name table + // for the strings "Engraving - single stroke" / "Engraving - double stroke" / "Engraving" + // to identify fonts that are desgned for engraving (and which tend to render poorly when + // used to dispaly text devices like screens, monitors, and printers). + // The SLF (single line fonts) are examples of fonts that have Engraving in field 10. + fi.m_loc_field_10_description = ON_Font::Field10DescriptionFromWindowsDWriteFont(dwriteFont.Get(), preferedLocale); + fi.m_en_field_10_description = ON_Font::Field10DescriptionFromWindowsDWriteFont(dwriteFont.Get(), englishLocale); + BOOL isSystemFont = false; LOGFONTW logfont; memset(&logfont, 0, sizeof(logfont)); @@ -1076,6 +1136,33 @@ unsigned int ON_Font::GetInstalledWindowsDWriteFonts( break; } + + + + if (ON_OutlineFigure::Type::Unset == fi.m_outline_figure_type) + { + fi.m_outline_figure_type = ON_OutlineFigure::FigureTypeFromField10Description(fi.m_loc_field_10_description); + if (ON_OutlineFigure::Type::Unset == fi.m_outline_figure_type) + fi.m_outline_figure_type = ON_OutlineFigure::FigureTypeFromField10Description(fi.m_loc_field_10_description); + + if (ON_OutlineFigure::Type::Unset == fi.m_outline_figure_type) + fi.m_outline_figure_type = ON_OutlineFigure::FigureTypeFromFontName(fi.m_loc_family_name); + if (ON_OutlineFigure::Type::Unset == fi.m_outline_figure_type + && false == fi.m_en_family_name.EqualOrdinal(fi.m_loc_family_name, true) + ) + fi.m_outline_figure_type = ON_OutlineFigure::FigureTypeFromFontName(fi.m_en_family_name); + + if (ON_OutlineFigure::Type::Unset == fi.m_outline_figure_type) + fi.m_outline_figure_type = ON_OutlineFigure::FigureTypeFromFontName(fi.m_loc_postscript_name); + if (ON_OutlineFigure::Type::Unset == fi.m_outline_figure_type + && false == fi.m_en_postscript_name.EqualOrdinal(fi.m_loc_postscript_name, true) + ) + fi.m_outline_figure_type = ON_OutlineFigure::FigureTypeFromFontName(fi.m_en_postscript_name); + + if (ON_OutlineFigure::Type::Unset == fi.m_outline_figure_type) + fi.m_outline_figure_type = ON_OutlineFigure::Type::Unknown; + } + for (;;) { Microsoft::WRL::ComPtr dwriteFontFace = nullptr; @@ -1187,7 +1274,11 @@ unsigned int ON_Font::GetInstalledWindowsDWriteFonts( if (a[i].m_bSimulatedBold) { // skip engraving fonts with simulated bold. - const ON_OutlineFigure::Type figure_type = ON_OutlineFigure::FigureTypeFromFontName(a[i].FamilyName()); + ON_OutlineFigure::Type figure_type = ON_OutlineFigure::FigureTypeFromField10Description(a[i].m_loc_field_10_description); + if (ON_OutlineFigure::Type::Unset == figure_type ) + figure_type = ON_OutlineFigure::FigureTypeFromField10Description(a[i].m_en_field_10_description); + if (ON_OutlineFigure::Type::Unset == figure_type) + figure_type = ON_OutlineFigure::FigureTypeFromFontName(a[i].FamilyName()); if (ON_OutlineFigure::Type::SingleStroke == figure_type) continue; if (ON_OutlineFigure::Type::DoubleStroke == figure_type) @@ -1790,7 +1881,41 @@ bool ON_Font::SetFromWindowsDWriteFont( Internal_GetLocalizeStrings(preferedLocale, faceNames.Get(), m_loc_face_name, m_en_face_name); } } - + + // Opennurbs searches the description saved in field 10 of the name table + // for the strings "Engraving - single stroke" / "Engraving - double stroke" / "Engraving" + // to identify fonts that are desgned for engraving (and which tend to render poorly when + // used to dispaly text devices like screens, monitors, and printers). + // The SLF (single line fonts) are examples of fonts that have Engraving in field 10. + ON_wString loc_field_10_description = ON_Font::Field10DescriptionFromWindowsDWriteFont(dwrite_font, preferedLocale); + ON_wString en_field_10_description = ON_Font::Field10DescriptionFromWindowsDWriteFont(dwrite_font, L"en-us"); + if (ON_OutlineFigure::Type::Unset == m_outline_figure_type) + m_outline_figure_type = ON_OutlineFigure::FigureTypeFromField10Description(loc_field_10_description); + if (ON_OutlineFigure::Type::Unset == m_outline_figure_type) + m_outline_figure_type = ON_OutlineFigure::FigureTypeFromField10Description(en_field_10_description); + + /* + Look for known engraving fonts by name + */ + if (ON_OutlineFigure::Type::Unset == m_outline_figure_type) + m_outline_figure_type = ON_OutlineFigure::FigureTypeFromFontName(m_loc_postscript_name); + if (ON_OutlineFigure::Type::Unset == m_outline_figure_type + && false == m_loc_postscript_name.EqualOrdinal(m_en_postscript_name, true) + ) + m_outline_figure_type = ON_OutlineFigure::FigureTypeFromFontName(m_en_postscript_name); + + if (ON_OutlineFigure::Type::Unset == m_outline_figure_type) + m_outline_figure_type = ON_OutlineFigure::FigureTypeFromFontName(m_loc_family_name); + if (ON_OutlineFigure::Type::Unset == m_outline_figure_type + && false == m_loc_family_name.EqualOrdinal(m_en_family_name, true) + ) + m_outline_figure_type = ON_OutlineFigure::FigureTypeFromFontName(m_en_family_name); + + // If it's still unset, we won't do any better in the future, + // so set m_outline_figure_type to unknow. + if (ON_OutlineFigure::Type::Unset == m_outline_figure_type) + m_outline_figure_type = ON_OutlineFigure::Type::Unknown; + m_font_origin = ON_Font::Origin::WindowsFont; return true; }