diff --git a/CMakeLists.txt b/CMakeLists.txt index a59cb622..5a1044c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -406,6 +406,10 @@ set( OPENNURBS_PLUS_HEADERS opennurbs_plus_x.h ) +if (BUILD_TESTING) + LIST(APPEND OPENNURBS_PLUS_HEADERS opennurbs_plus_testheader.h) +endif(BUILD_TESTING) + set( OPENNURBS_PLUS_SOURCES opennurbs_plus_bezier.cpp opennurbs_plus_brep.cpp @@ -501,6 +505,10 @@ endif() add_definitions(-DON_CMAKE_BUILD) +if (ANDROID) + include_directories("freetype263/include") +endif() + if( APPLE) find_library( CORE_GRAPHICS_LIBRARY CoreGraphics) message( STATUS "CORE_GRAPHICS_LIBRARY is ${CORE_GRAPHICS_LIBRARY}") @@ -523,7 +531,7 @@ if( APPLE) ) target_compile_definitions(opennurbsStatic PRIVATE ${OPENNURBS_APPLE_DEFINES}) target_compile_definitions(OpenNURBS PRIVATE ${OPENNURBS_APPLE_DEFINES}) - + # xcode properties are the same for both static and shared libs set_target_properties( opennurbsStatic OpenNURBS PROPERTIES @@ -574,14 +582,23 @@ target_compile_definitions(OpenNURBS PRIVATE OPENNURBS_EXPORTS Z_PREFIX MY_ZCALL target_include_directories(opennurbsStatic PUBLIC .) target_include_directories(OpenNURBS PUBLIC .) -target_precompile_headers(opennurbsStatic PRIVATE opennurbs.h) -target_precompile_headers(OpenNURBS PRIVATE opennurbs.h) +set(PRECOMPILED_HEADERS opennurbs.h) +if (BUILD_TESTING) + LIST(APPEND PRECOMPILED_HEADERS opennurbs_plus_testheader.h) +endif(BUILD_TESTING) + +target_precompile_headers(opennurbsStatic PRIVATE ${PRECOMPILED_HEADERS}) +target_precompile_headers(OpenNURBS PRIVATE ${PRECOMPILED_HEADERS}) install( TARGETS opennurbsStatic DESTINATION "lib") install( FILES ${OPENNURBS_PUBLIC_HEADERS} DESTINATION "include/opennurbsStatic") -if (ANDROID OR LINUX) +if (ANDROID) +target_link_libraries( OpenNURBS zlib opennurbs_public_freetype android_uuid android) +target_link_libraries( opennurbsStatic zlib opennurbs_public_freetype android_uuid android) +endif() +if (LINUX AND NOT ANDROID) target_link_libraries( OpenNURBS zlib opennurbs_public_freetype android_uuid) target_link_libraries( opennurbsStatic zlib opennurbs_public_freetype android_uuid) endif() diff --git a/opennurbs_brep.cpp b/opennurbs_brep.cpp index 6b03b711..5d6b6010 100644 --- a/opennurbs_brep.cpp +++ b/opennurbs_brep.cpp @@ -789,7 +789,7 @@ public: std::shared_ptr m_render_mesh; std::shared_ptr m_analysis_mesh; std::shared_ptr m_preview_mesh; - std::mutex m_mesh_mutex; + std::recursive_mutex m_mesh_mutex; }; ON_BrepFace::ON_BrepFace() @@ -810,7 +810,7 @@ ON_BrepFace::ON_BrepFace(int face_index) unsigned int ON_BrepFace::SizeOf() const { - std::lock_guard lock(m_pImpl->m_mesh_mutex); + std::lock_guard lock(m_pImpl->m_mesh_mutex); unsigned int sz = ON_SurfaceProxy::SizeOf(); sz += (sizeof(*this) - sizeof(ON_SurfaceProxy)); @@ -841,8 +841,8 @@ ON_BrepFace& ON_BrepFace::operator=(const ON_BrepFace& src) m_face_uuid = src.m_face_uuid; m_per_face_color = src.m_per_face_color; - std::lock_guard lock(m_pImpl->m_mesh_mutex); - std::lock_guard lock2(src.m_pImpl->m_mesh_mutex); + std::lock_guard lock(m_pImpl->m_mesh_mutex); + std::lock_guard lock2(src.m_pImpl->m_mesh_mutex); m_pImpl->m_render_mesh = src.m_pImpl->m_render_mesh ? src.m_pImpl->m_render_mesh : nullptr; m_pImpl->m_analysis_mesh = src.m_pImpl->m_analysis_mesh ? src.m_pImpl->m_analysis_mesh : nullptr; @@ -1099,7 +1099,7 @@ const std::shared_ptr& ON_BrepFace::UniqueMesh(ON::mesh_type mesh const std::shared_ptr& ON_BrepFace::SharedMesh(ON::mesh_type mesh_type) const { - std::lock_guard lock(m_pImpl->m_mesh_mutex); + std::lock_guard lock(m_pImpl->m_mesh_mutex); std::shared_ptr* pMesh = nullptr; @@ -1140,7 +1140,7 @@ const ON_Mesh* ON_BrepFace::Mesh( ON::mesh_type mt ) const bool ON_BrepFace::SetMesh(ON::mesh_type mt, const std::shared_ptr& mesh) { - std::lock_guard lock(m_pImpl->m_mesh_mutex); + std::lock_guard lock(m_pImpl->m_mesh_mutex); bool rc = true; switch (mt) @@ -1163,7 +1163,7 @@ bool ON_BrepFace::SetMesh(ON::mesh_type mt, const std::shared_ptr bool ON_BrepFace::SetMesh(ON::mesh_type mt, ON_Mesh* mesh) { - std::lock_guard lock(m_pImpl->m_mesh_mutex); + std::lock_guard lock(m_pImpl->m_mesh_mutex); bool rc = true; switch (mt) @@ -1191,7 +1191,7 @@ void ON_BrepFace::DestroyMesh( ON::mesh_type mt, bool bDeleteMesh ) void ON_BrepFace::DestroyMesh(ON::mesh_type mt) { - std::lock_guard lock(m_pImpl->m_mesh_mutex); + std::lock_guard lock(m_pImpl->m_mesh_mutex); switch(mt) { case ON::render_mesh: diff --git a/opennurbs_brep.h b/opennurbs_brep.h index 53571c08..db57a562 100644 --- a/opennurbs_brep.h +++ b/opennurbs_brep.h @@ -3865,6 +3865,17 @@ public: */ bool MergeFaces(); + /* + Description: + Will turn a linear polycurve into a single line if possible + Parameters: + edge_index - [in] index of the edge to simplify. + tolerance - [in] used in call to ON_Curve::IsLinear; + Returns: + true on success. + */ + bool SimplifyEdge(int edge_index, double tolerance); + /* Description: Removes nested polycurves from the m_C2[] and m_C3[] arrays. diff --git a/opennurbs_curve.cpp b/opennurbs_curve.cpp index 442d5539..6559a8bd 100644 --- a/opennurbs_curve.cpp +++ b/opennurbs_curve.cpp @@ -2565,16 +2565,31 @@ public: static int CompareJoinEnds(void* ctext, const void* aA, const void* bB) { + //The greater tan_dot is, the more tangent the ends are. + // Be sure that they have been adjusted for start meets start or end mets end. JoinEndCompareContext* context = (JoinEndCompareContext*)ctext; const CurveJoinEndData* a = (CurveJoinEndData*)aA; const CurveJoinEndData* b = (CurveJoinEndData*)bB; if (context->bUseTan){ + //If one is real close and the other isn't,take the close one. if (a->dist < context->dist_tol && b->dist >= context->dist_tol) return -1; if (a->dist >= context->dist_tol && b->dist < context->dist_tol) return 1; + + //If one is tangent and the other isn't, take the tangent one. if (a->tan_dot > context->dot_tol && b->tan_dot <= context->dot_tol) return -1; if (a->tan_dot <= context->dot_tol && b->tan_dot > context->dot_tol) return 1; - if (a->dist < b->dist) return -1; - if (a->dist > b->dist) return 1; + + //If both are close, take the more tangent one + if (a->dist < context->dist_tol && b->dist < context->dist_tol){ + if (a->tan_dot > b->tan_dot) return -1; + if (a->tan_dot < b->tan_dot) return 1; + } + + //either both or neither are tangent. Take the closest. + if (a->dist < b->dist) + return -1; + if (a->dist > b->dist) + return 1; if (a->id[0] < b->id[0]) return -1; if (a->id[0] > b->id[0]) return 1; if (a->id[1] < b->id[1]) return -1; @@ -2597,6 +2612,402 @@ static int CompareJoinEnds(void* ctext, const void* aA, const void* bB) /////////////////////////////////////////////////////////////////////////////////////// ////////////////////////// end of utilities for curve joining /////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//This next bunch of code is to allow V5 style curve joining. +// The special case line/polyline code has been removed, +// as has the code to pick the most tangent result when +// multiple possibilites are present +/////////////////////////////////////////////// + +struct OldCurveJoinSeg { + int id; + bool bRev; +}; + +//distance from curve[id[0] end[end[0]] to curve[id[1]] end[end[1]] is dist. +struct OldCurveJoinEndData { + int id[2]; //index into array of curves + int end[2]; //0 for start, 1 for end + double dist; +}; + + +static int OldCompareEndData(const OldCurveJoinEndData* a, const OldCurveJoinEndData* b) + +{ + if (a->dist < b->dist) return -1; + if (a->dist > b->dist) return 1; + return 0; +} + +static void OldReverseSegs(ON_SimpleArray& SArray) + +{ + int i; + for (i=0; i& InCurves, + ON_SimpleArray& OutCurves, + double join_tol, + bool bPreserveDirection, // = false + ON_SimpleArray* key //=0 +) + +{ + + int i, count = OutCurves.Count(); + if (InCurves.Count() < 1) + return 0; + + int dim = InCurves[0]->Dimension(); + for (i=1; iDimension() != dim) return 0; + } + + if (key) { + key->Reserve(InCurves.Count()); + for (i=0; iAppend(-1); + } + + //Copy curves, take out closed curves. + OutCurves.Reserve(InCurves.Count()); + ON_SimpleArray IC(InCurves.Count()); + ON_SimpleArray cmap(InCurves.Count()); + for (i=0; iDuplicateCurve(); + if (!C) continue; + if (C->IsClosed()) { + if (key) (*key)[i] = OutCurves.Count(); + OutCurves.Append(C); + } + else { + cmap.Append(i); + IC.Append(C); + } + } + + //IC is a list of copies of all open curves. match endpoints and join into polycurves. + //copy curves that are not joined. + ON_3dPointArray Start(IC.Count()); + Start.SetCount(IC.Count()); + ON_3dPointArray End(IC.Count()); + End.SetCount(IC.Count()); + for (i=0; iPointAtStart(); + End[i] = IC[i]->PointAtEnd(); + } + + //get a list of all possible joins + ON_SimpleArray EData(IC.Count()); + for (i=0; i 0, then IC[i] is part of + //polycurve endarray[i][0] - 1, and the start of IC[i] is interior to the polycurve. + //if endarray[i][1] > 0, then the end of IC[i] is interior. if both endarray[i][0] > 0 + //and endarray[i][1] > 0, then they are equal. + + for (i=0; i > SegsArray(IC.Count()); + + for (i=0; i 0 || endarray[ED.id[1]][ED.end[1]] > 0) + continue; //one of these endpoints has already been join to a closer end + if (endarray[ED.id[0]][1 - ED.end[0]] == 0){ + if (endarray[ED.id[1]][1 - ED.end[1]] == 0){//new curve + endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] = SegsArray.Count()+1; + ON_SimpleArray& SArray = SegsArray.AppendNew(); + SArray.Reserve(4); + OldCurveJoinSeg& Seg0 = SArray.AppendNew(); + OldCurveJoinSeg& Seg1 = SArray.AppendNew(); + if (ED.end[0]) { + Seg0.id = ED.id[0]; + Seg0.bRev = false; + Seg1.id = ED.id[1]; + Seg1.bRev = (ED.end[1]) ? true : false; + } + else { + Seg1.id = ED.id[0]; + Seg1.bRev = false; + Seg0.id = ED.id[1]; + Seg0.bRev = (ED.end[1]) ? false : true; + } + } + + else { + + //second curve is part of an existing sequence. Insert or append first curve. + ON_SimpleArray& SArray = SegsArray[endarray[ED.id[1]][1 - ED.end[1]] - 1]; + endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] = + endarray[ED.id[1]][1 - ED.end[1]]; + + if (SArray[0].id == ED.id[1]){ + OldCurveJoinSeg Seg; + Seg.id = ED.id[0]; + Seg.bRev = (ED.end[0]) ? false : true; + SArray.Insert(0, Seg); + } + else { + OldCurveJoinSeg& Seg = SArray.AppendNew(); + Seg.id = ED.id[0]; + Seg.bRev = (ED.end[0]) ? true : false; + } + } + } + else if (endarray[ED.id[1]][1 - ED.end[1]] == 0){ + //first curve is part of an existing sequence. Insert or append second curve. + ON_SimpleArray& SArray = SegsArray[endarray[ED.id[0]][1 - ED.end[0]] - 1]; + endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] = + endarray[ED.id[0]][1 - ED.end[0]]; + + if (SArray[0].id == ED.id[0]){ + OldCurveJoinSeg Seg; + Seg.id = ED.id[1]; + Seg.bRev = (ED.end[1]) ? false : true; + SArray.Insert(0, Seg); + } + else { + OldCurveJoinSeg& Seg = SArray.AppendNew(); + Seg.id = ED.id[1]; + Seg.bRev = (ED.end[1]) ? true : false; + } + } + else { + //both are in existing sequences. join the sequences. + if (endarray[ED.id[0]][1 - ED.end[0]] == endarray[ED.id[1]][1 - ED.end[1]]) + //closes off this curve + endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] = + endarray[ED.id[0]][1 - ED.end[0]]; + else { + int segid0 = endarray[ED.id[0]][1 - ED.end[0]]; + int segid1 = endarray[ED.id[1]][1 - ED.end[1]]; + ON_SimpleArray& SArray0 = SegsArray[segid0 - 1]; + ON_SimpleArray& SArray1 = SegsArray[segid1 - 1]; + if (SArray0[0].id == ED.id[0]){ + if (SArray1[0].id == ED.id[1]){ + OldReverseSegs(SArray0); + int j; + for (j=0; j 0) endarray[SArray1[j].id][0] = segid0; + if (endarray[SArray1[j].id][1] > 0) endarray[SArray1[j].id][1] = segid0; + SArray0.Append(SArray1[j]); + } + endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] = segid0; + SArray1.SetCount(0); + } + else { + int j; + for (j=0; j 0) endarray[SArray0[j].id][0] = segid1; + if (endarray[SArray0[j].id][1] > 0) endarray[SArray0[j].id][1] = segid1; + SArray1.Append(SArray0[j]); + } + endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] = segid1; + SArray0.SetCount(0); + } + } + else if (SArray1[0].id == ED.id[1]){ + int j; + for (j=0; j 0) endarray[SArray1[j].id][0] = segid0; + if (endarray[SArray1[j].id][1] > 0) endarray[SArray1[j].id][1] = segid0; + SArray0.Append(SArray1[j]); + } + endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] = segid0; + SArray1.SetCount(0); + } + else { + OldReverseSegs(SArray1); + int j; + for (j=0; j 0) endarray[SArray1[j].id][0] = segid0; + if (endarray[SArray1[j].id][1] > 0) endarray[SArray1[j].id][1] = segid0; + SArray0.Append(SArray1[j]); + } + endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] = segid0; + SArray1.SetCount(0); + } + } + } + } + + //make polycurves out of sequences + + for (i=0; i& SArray = SegsArray[i]; + if (SArray.Count() < 2) continue; + if (!bPreserveDirection){//if number of reversed segs is more than half, reverse. + int scount= 0; + int j; + for (j=0; j SArray.Count()) + OldReverseSegs(SArray); + } + ON_PolyCurve* PC = new ON_PolyCurve(SArray.Count()); + bool pc_added = false; + int j; + int min_seg = 0; + int min_id = -1; + for (j=0; jReverse(); + if (PC->Count()){ + bool bSet = true; + if (!ON_ForceMatchCurveEnds(*PC, 1, *C, 0)) { + ON_3dPoint P = PC->PointAtEnd(); + ON_3dPoint Q = C->PointAtStart(); + P = 0.5*(P+Q); + if (!PC->SetEndPoint(P) || !C->SetStartPoint(P)) { + ON_NurbsCurve* NC = C->NurbsCurve(); + if (NC && NC->SetStartPoint(P)){ + delete C; + C = NC; + } + else { + bSet = false; + delete NC; + if (PC->Count()) { + pc_added = true; + OutCurves.Append(PC); + } + if (key) + (*key)[cmap[SArray[j].id]]++; + OutCurves.Append(C); + int k; + for (k=j+1; kCount(); si++){ + const ON_Curve* SC = pPoly->SegmentCurve(si); + ON_Curve* SCCopy = SC->DuplicateCurve(); + if (SCCopy) PC->Append(SCCopy); + } + delete pPoly; + } + else PC->Append(C); + } + if (!PC->Count()) delete PC; + else if (!pc_added) { + if (!PC->IsClosed() && PC->IsClosable(join_tol)){ + if (!ON_ForceMatchCurveEnds(*PC, 0, *PC, 1)) + PC->SetEndPoint(PC->PointAtStart()); + } + if (PC->IsClosed() && min_id >= 0){ + //int sc = PC->SpanCount(); + double t = PC->SegmentDomain(min_seg)[0]; + PC->ChangeClosedCurveSeam(t); + } + + // 28 October 2010 Dale Lear + // I added the RemoveNesting() and SynchronizeSegmentDomains() + // lines so Rhino will create higher quality geometry. + PC->RemoveNesting(); + PC->SynchronizeSegmentDomains(); + + OutCurves.Append(PC); + } + } + + //add in singletons + for (i=0; iIsClosed()){ + ON_3dPoint s= OutCurves[i]->PointAtStart(); + ON_3dPoint e = OutCurves[i]->PointAtEnd(); + if(s.DistanceTo(e)SetEndPoint( s ); + } + } + */ + + //Chuck added this, 1/16/03. + for(i=0; iIsClosed()) continue; + if (C->IsClosable(join_tol)) + C->SetEndPoint(C->PointAtStart()); + } + + onfree((void*)endarray); + onfree((void*)endspace); + + return OutCurves.Count() - count; +} bool ON_Curve::IsClosable(double tolerance, @@ -2894,6 +3305,8 @@ static bool GetCurveEndData(const ON_SimpleArray& IC, ED.id[0] = Pair.a->m_cid; ED.id[1] = Pair.b->m_cid; ED.tan_dot = Pair.dot; + if (ED.end[0] == ED.end[1]) + ED.tan_dot *= -1.0; } return true; } diff --git a/opennurbs_curve.h b/opennurbs_curve.h index 2cefcd24..7f75c253 100644 --- a/opennurbs_curve.h +++ b/opennurbs_curve.h @@ -1367,6 +1367,35 @@ int ON_JoinCurves(const ON_SimpleArray& InCurves, ON_SimpleArray* key = 0 ); +/* +Description: +Join all contiguous curves of an array of ON_Curves using an older sort algorithm. +Unless this loder version is necessary, use ON_JoinCurves instead. +Parameters: + InCurves - [in] Array of curves to be joined (not modified) + OutCurves - [out] Resulting joined curves and copies of curves that were not joined to anything +are appended. + join_tol - [in] Distance tolerance used to decide if endpoints are close enough + bPreserveDirection - [in] If true, curve endpoints will be compared to curve startpoints. + If false, all start and endpoints will be compared, and copies of input + curves may be reversed in output. + key - [out] if key is not null, InCurves[i] was joined into OutCurves[key[i]]. +Returns: + Number of curves added to Outcurves +Remarks: + Closed curves are copied to OutCurves. + Curves that cannot be joined to others are copied to OutCurves. When curves are joined, the results + are ON_PolyCurves. All members of InCurves must have same dimension, at most 3. +*/ +ON_DECL +int ON_JoinCurvesOld(const ON_SimpleArray& InCurves, + ON_SimpleArray& OutCurves, + double join_tol, + bool bPreserveDirection, // = false + ON_SimpleArray* key //=0 +); + + /* Description: diff --git a/opennurbs_file_utilities.cpp b/opennurbs_file_utilities.cpp index a2d41e04..e7959646 100644 --- a/opennurbs_file_utilities.cpp +++ b/opennurbs_file_utilities.cpp @@ -2937,15 +2937,89 @@ ON_ContentHash ON_ContentHash::CreateFromFile( return ON_ContentHash::Create(sha1_file_name_hash,hash_byte_count,sha1_hash,hash_time,file_contents_last_modified_time); } +#include + +using ContentHashMap = std::unordered_map; + +std::weak_ptr g_pContentHashCache; + +class ON_ContentHash::Cache::Private +{ +public: + std::shared_ptr p; +}; + +ON_ContentHash::Cache::Cache() + : m_private(new Private) +{ + + m_private->p = g_pContentHashCache.lock(); + + + if (!m_private->p) + { + m_private->p.reset(new ContentHashMap); + g_pContentHashCache = m_private->p; + } +} + +ON_ContentHash::Cache::~Cache() +{ + delete m_private; +} + +void ON_ContentHash::Cache::Add(const wchar_t* path, const ON_ContentHash& hash) +{ + auto map = g_pContentHashCache.lock(); + if (map) + { + map->insert(std::make_pair(path, hash)); + } +} + + +const ON_ContentHash* ON_ContentHash::Cache::FromFile(const wchar_t* path) +{ + auto map = g_pContentHashCache.lock(); + if (map) + { + auto it = map->find(path); + if (it != map->end()) + { + return &it->second; + } + } + return nullptr; +} + +const ON_ContentHash* ON_ContentHash::Cache::FromFile(const char* p) +{ + ON_wString s(p); + return FromFile((const wchar_t*)s); +} + +void ON_ContentHash::Cache::Add(const char* p, const ON_ContentHash& h) +{ + ON_wString s(p); + Add(s, h); +} ON_ContentHash ON_ContentHash::CreateFromFile( const wchar_t* filename ) { + if (auto pHash = Cache::FromFile(filename)) + { + return *pHash; + } + ON_SHA1_Hash sha1_file_name_hash = (nullptr == filename) ? ON_SHA1_Hash::ZeroDigest : ON_SHA1_Hash::FileSystemPathHash(filename); FILE* fp = ON_FileStream::Open(filename, L"rb"); ON_ContentHash hash = ON_ContentHash::CreateFromFile(sha1_file_name_hash,fp); ON_FileStream::Close(fp); + + Cache::Add(filename, hash); + return hash; } @@ -2953,10 +3027,18 @@ ON_ContentHash ON_ContentHash::CreateFromFile( const char* filename ) { + if (auto pHash = Cache::FromFile(filename)) + { + return *pHash; + } + ON_SHA1_Hash sha1_file_name_hash = (nullptr == filename) ? ON_SHA1_Hash::ZeroDigest : ON_SHA1_Hash::FileSystemPathHash(filename); FILE* fp = ON_FileStream::Open(filename, "rb"); ON_ContentHash hash = ON_ContentHash::CreateFromFile(sha1_file_name_hash,fp); ON_FileStream::Close(fp); + + Cache::Add(filename, hash); + return hash; } diff --git a/opennurbs_file_utilities.h b/opennurbs_file_utilities.h index eed5a9de..a28f630f 100644 --- a/opennurbs_file_utilities.h +++ b/opennurbs_file_utilities.h @@ -955,6 +955,23 @@ public: const char* filename ); + class ON_CLASS Cache + { + public: + Cache(); + ~Cache(); + + static const ON_ContentHash* FromFile(const wchar_t*); + static const ON_ContentHash* FromFile(const char*); + + static void Add(const wchar_t*, const ON_ContentHash&); + static void Add(const char*, const ON_ContentHash&); + + private: + class Private; + Private* m_private; + }; + /* Returns: True if the SHA-1 hash has been set. diff --git a/opennurbs_freetype.cpp b/opennurbs_freetype.cpp index 4ff043fe..212ced5a 100644 --- a/opennurbs_freetype.cpp +++ b/opennurbs_freetype.cpp @@ -251,6 +251,12 @@ Warning C4263 ..: member function does not override any base class virtual membe #pragma ON_PRAGMA_WARNING_POP #endif +#if defined(ON_RUNTIME_ANDROID) +#include +#include +#include +#endif + class ON_FontFileBuffer { public: @@ -419,9 +425,14 @@ private: const LOGFONT* logfont ); #endif + #if defined (ON_RUNTIME_COCOA_AVAILABLE) static ON_FreeTypeFace* Internal_CreateFaceFromAppleFont(CTFontRef); #endif + +#if defined (ON_RUNTIME_ANDROID) + static ON_FreeTypeFace* Internal_CreateFaceWithAndroidNdk(const ON_Font& font); +#endif }; FT_MemoryRec_ ON_FreeType::m_memory_rec; @@ -1430,6 +1441,34 @@ ON_FreeTypeFace* ON_FreeType::Internal_CreateFaceFromAppleFont (CTFontRef fontRe #endif // ON_RUNTIME_APPLE +#if defined(ON_RUNTIME_ANDROID) +ON_FreeTypeFace* ON_FreeType::Internal_CreateFaceWithAndroidNdk(const ON_Font& font) +{ + AFontMatcher* ndkFontMatcher = AFontMatcher_create(); + if (nullptr == ndkFontMatcher) + return nullptr; + ON_String familyName = font.FamilyName(); + const uint16_t* text = (const uint16_t*)"XYZ"; + ON_FreeTypeFace* rc = nullptr; + AFont* ndkFont = AFontMatcher_match(ndkFontMatcher, familyName, text, 3, nullptr); + if (ndkFont) + { + size_t index = AFont_getCollectionIndex(ndkFont); + const char* path = AFont_getFontFilePath(ndkFont); + FT_Face ftFace; + FT_Error err = FT_New_Face (ON_FreeType::Library(), path, 0, &ftFace); // get first face + if (0 == err) + { + rc = new ON_FreeTypeFace(); + rc->m_face = ftFace; + } + AFont_close(ndkFont); + } + AFontMatcher_destroy(ndkFontMatcher); + return rc; +} + +#endif ON_FreeTypeFace* ON_FreeType::CreateFace( const ON_Font& font @@ -1446,12 +1485,17 @@ ON_FreeTypeFace* ON_FreeType::CreateFace( #if defined(ON_RUNTIME_WIN) LOGFONT logfont = font.WindowsLogFont(0,nullptr); f = ON_FreeType::Internal_CreateFaceFromWindowsFont(&logfont); +#endif -#elif defined (ON_RUNTIME_COCOA_AVAILABLE) +#if defined (ON_RUNTIME_COCOA_AVAILABLE) bool bIsSubstituteFont = false; f = ON_FreeType::Internal_CreateFaceFromAppleFont(font.AppleCTFont(bIsSubstituteFont)); #endif +#if defined(ON_RUNTIME_ANDROID) + f = ON_FreeType::Internal_CreateFaceWithAndroidNdk(font); +#endif + // Create empty holder so this function doesn't repeatedly // try to load the freetype face. if (nullptr == f) diff --git a/opennurbs_material.cpp b/opennurbs_material.cpp index 893420f8..75cf2482 100644 --- a/opennurbs_material.cpp +++ b/opennurbs_material.cpp @@ -3666,6 +3666,28 @@ int ON_TextureMapping::EvaluateBoxMapping( return side0; } +static ON_3fVector MeshFaceNormal(const ON_Mesh & mesh, const ON_MeshFace & face) +{ + ON_3fVector vtFaceNormal = ON_CrossProduct( + mesh.m_V[face.vi[2]] - mesh.m_V[face.vi[0]], + mesh.m_V[face.vi[3]] - mesh.m_V[face.vi[1]]); + + vtFaceNormal.Unitize(); + + return vtFaceNormal; +} + +static ON_3fVector MeshFaceNormal(const ON_Mesh & mesh, int iFaceIndex) +{ + if (mesh.HasFaceNormals()) + { + return mesh.m_FN[iFaceIndex]; + } + else + { + return MeshFaceNormal(mesh, mesh.m_F[iFaceIndex]); + } +} static bool ProjectToFaceSpace(const ON_3dPoint & ptV0, const ON_3dPoint & ptV1, const ON_3dPoint & ptV2, const ON_3dPoint & ptP, double & xOut, double & yOut) { @@ -3709,6 +3731,1374 @@ static bool ProjectToFaceSpace(const ON_3dPoint & ptV0, const ON_3dPoint & ptV1, } +ON_WeightedAverageHash::ON_WeightedAverageHash() +{ + Zero(); +} + +void ON_WeightedAverageHash::Zero() +{ + for (int i = 0; i < dim; i++) + m_sum[i] = ON_3dPoint::Origin; +} + +bool ON_WeightedAverageHash::Write(ON_BinaryArchive& binary_archive) const +{ + for (int i = 0; i < dim; i++) + { + if (!binary_archive.WritePoint(m_sum[i])) + return false; + } + return true; +} + +bool ON_WeightedAverageHash::Read(ON_BinaryArchive& binary_archive) +{ + for (int i = 0; i < dim; i++) + { + if (!binary_archive.ReadPoint(m_sum[i])) + return false; + } + return true; +} + +bool ON_WeightedAverageHash::Matches(const ON_WeightedAverageHash& b, const ON_Xform& bt, double tol) const +{ + double maxDist = 0.0; + double minDist = DBL_MAX; + for (int i = 0; i < ON_WeightedAverageHash::dim; i++) + { + const ON_3dPoint bi = bt * b.m_sum[i]; + const double dist = bi.DistanceTo(m_sum[i]); + if (maxDist < dist) + maxDist = dist; + if (minDist > dist) + minDist = dist; + } + if (maxDist <= tol) + return true; + else + return false; +} + +void ON_WeightedAverageHash::Transform(const ON_Xform& xform) +{ + for (int i = 0; i < ON_WeightedAverageHash::dim; i++) + { + m_sum[i] = xform * m_sum[i]; + } +} +ON_GeometryFingerprint::ON_GeometryFingerprint() +{ + Zero(); +} + +void ON_GeometryFingerprint::Zero() +{ + m_topologyCRC = 0; + m_pointWAH.Zero(); + m_edgeWAH.Zero(); +} + +bool ON_GeometryFingerprint::Write(ON_BinaryArchive& binary_archive) const +{ + if (!binary_archive.WriteInt(m_topologyCRC)) + return false; + if (!m_pointWAH.Write(binary_archive)) + return false; + if (!m_edgeWAH.Write(binary_archive)) + return false; + return true; +} + +bool ON_GeometryFingerprint::Read(ON_BinaryArchive& binary_archive) +{ + if (!binary_archive.ReadInt(&m_topologyCRC)) + return false; + if (!m_pointWAH.Read(binary_archive)) + return false; + if (!m_edgeWAH.Read(binary_archive)) + return false; + return true; +} +bool ON_GeometryFingerprint::Matches(const ON_GeometryFingerprint& b, const ON_Xform& bt, double tol) const +{ + if (m_topologyCRC != b.m_topologyCRC) + return false; + if (!m_pointWAH.Matches(b.m_pointWAH, bt, tol)) + return false; + if (!m_edgeWAH.Matches(b.m_edgeWAH, bt, tol)) + return false; + return true; +} +void ON_GeometryFingerprint::Transform(const ON_Xform& xform) +{ + m_pointWAH.Transform(xform); + m_edgeWAH.Transform(xform); +} + +ON_MappingMeshInfo::ON_MappingMeshInfo() +{ +} + +void ON_MappingMeshInfo::GenerateDerivedData() +{ + m_sourceIdFaceStart.Empty(); + m_sourceIdFaceCount.Empty(); + m_sourceIdFaceList.Empty(); + for (int fi = 0; fi < m_faceSourceIds.Count(); fi++) + { + const int sourceId = m_faceSourceIds[fi]; + if (sourceId >= 0) + { + while (m_sourceIdFaceCount.Count() <= sourceId) + { + m_sourceIdFaceCount.Append(0); + } + m_sourceIdFaceCount[sourceId]++; + } + } + int nTotal = 0; + for (int sid = 0; sid < m_sourceIdFaceCount.Count(); sid++) + { + m_sourceIdFaceStart.Append(nTotal); + nTotal += m_sourceIdFaceCount[sid]; + } + m_sourceIdFaceList.SetCapacity(nTotal); + m_sourceIdFaceList.SetCount(nTotal); + m_sourceIdFaceList.MemSet(0); + m_sourceIdFaceCount.MemSet(0); + for (int fi = 0; fi < m_faceSourceIds.Count(); fi++) + { + const int sourceId = m_faceSourceIds[fi]; + if (sourceId >= 0) + { + m_sourceIdFaceList[m_sourceIdFaceStart[sourceId] + m_sourceIdFaceCount[sourceId]] = fi; + m_sourceIdFaceCount[sourceId]++; + } + } +} + +const int* ON_MappingMeshInfo::SourceIdFaces(const int sourceId, int& countOut) const +{ + countOut = 0; + if (sourceId < 0 || sourceId >= m_sourceIdFaceCount.Count()) + return nullptr; + countOut = m_sourceIdFaceCount[sourceId]; + return m_sourceIdFaceList.Array() + m_sourceIdFaceStart[sourceId]; +} + +ON_RenderMeshInfo::ON_RenderMeshInfo() + : m_sourceFaceId(ON_UNSET_INT_INDEX) +{ +} + + +#pragma region CTtMappingMeshInfoUserData + +class ON_CLASS CTtMappingMeshInfoUserData : public ON_UserData +{ + ON_OBJECT_DECLARE(CTtMappingMeshInfoUserData); +public: + const static bool m_bArchive = true; + CTtMappingMeshInfoUserData() + { + m_application_uuid = ON_opennurbs_id; + m_userdata_uuid = ON_CLASS_ID(CTtMappingMeshInfoUserData); + m_userdata_copycount = 1; + } + CTtMappingMeshInfoUserData(const CTtMappingMeshInfoUserData& src) + : ON_UserData(src) + { + m_userdata_copycount = src.m_userdata_copycount; + m_info = src.m_info; + } + ~CTtMappingMeshInfoUserData() + { + } + CTtMappingMeshInfoUserData& operator=(const CTtMappingMeshInfoUserData& src) + { + ON_UserData::operator = (src); + m_info = src.m_info; + return *this; + } + +#if defined(ON_COMPILER_CLANG) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winconsistent-missing-override" +#endif + virtual bool GetDescription(ON_wString& description) + { + description = L"TtMappingMeshInfoUserData"; + return true; + } + + virtual bool Archive() const + { + return m_bArchive; + } + + virtual bool Write(ON_BinaryArchive& binary_archive) const + { + if (!m_bArchive) + return false; + + const int nVersion = 1; + + if (!binary_archive.WriteInt(nVersion)) + return false; + + if (!m_info.m_geometryFingerprint.Write(binary_archive)) + return false; + + if (!binary_archive.WriteInt(m_info.m_faceSourceIds.Count())) + return false; + + for (size_t i = 0; i < m_info.m_faceSourceIds.Count(); i++) + { + if (!binary_archive.WriteInt(m_info.m_faceSourceIds[i])) + return false; + } + + return true; + } + + virtual bool Read(ON_BinaryArchive& binary_archive) + { + if (!m_bArchive) + return false; + + int nVersion = 1; + + if (!binary_archive.ReadInt(&nVersion)) + return false; + + if (1 != nVersion) + return false; + + if (!m_info.m_geometryFingerprint.Read(binary_archive)) + return false; + + int nCount = 0; + + if (!binary_archive.ReadInt(&nCount)) + return false; + + m_info.m_faceSourceIds.SetCapacity(nCount); + for (int i = 0; i < nCount; i++) + { + int value = -1; + if (!binary_archive.ReadInt(&value)) + return false; + m_info.m_faceSourceIds.Append(value); + } + + m_info.GenerateDerivedData(); + + return true; + } +#if defined(ON_COMPILER_CLANG) +#pragma clang diagnostic pop +#endif + + void SetInfo(const ON_MappingMeshInfo& info) + { + m_info = info; + m_info.GenerateDerivedData(); + } + const ON_MappingMeshInfo& Info() const + { + return m_info; + } +protected: + ON_MappingMeshInfo m_info; +}; + +ON_OBJECT_IMPLEMENT(CTtMappingMeshInfoUserData, ON_UserData, "1706ADC5-52BF-4BE2-8402-4501EB2AE675"); + + +const ON_MappingMeshInfo* ON_Mesh::GetMappingMeshInfo() const +{ + CTtMappingMeshInfoUserData* pUD = CTtMappingMeshInfoUserData::Cast(GetUserData(ON_CLASS_ID(CTtMappingMeshInfoUserData))); + if (nullptr == pUD) + return nullptr; + return &pUD->Info(); +} + + +#pragma endregion + +class CTtRenderMeshInfoUserData : public ON_UserData +{ + ON_OBJECT_DECLARE(CTtRenderMeshInfoUserData); +public: + const static bool m_bArchive = true; + CTtRenderMeshInfoUserData() + { + m_application_uuid = ON_opennurbs_id; + m_userdata_uuid = ON_CLASS_ID(CTtRenderMeshInfoUserData); + m_userdata_copycount = 1; + } + CTtRenderMeshInfoUserData(const CTtRenderMeshInfoUserData& src) + : ON_UserData(src) + { + m_userdata_copycount = src.m_userdata_copycount; + m_info = src.m_info; + } + ~CTtRenderMeshInfoUserData() + { + } + CTtRenderMeshInfoUserData& operator=(const CTtRenderMeshInfoUserData& src) + { + ON_UserData::operator = (src); + m_info = src.m_info; + return *this; + } + +#if defined(ON_COMPILER_CLANG) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winconsistent-missing-override" +#endif + virtual bool GetDescription(ON_wString& description) + { + description = L"TtRenderMeshInfoUserData"; + return true; + } + + virtual bool Archive() const + { + return m_bArchive; + } + + virtual bool Write(ON_BinaryArchive& binary_archive) const + { + if (!m_bArchive) + return false; + const int nVersion = 1; + if (!binary_archive.WriteInt(nVersion)) + return false; + if (!m_info.m_geometryFingerprint.Write(binary_archive)) + return false; + if (!binary_archive.WriteInt(m_info.m_sourceFaceId)) + return false; + return true; + } + + virtual bool Read(ON_BinaryArchive& binary_archive) + { + if (!m_bArchive) + return true; + int nVersion = 1; + if (!binary_archive.ReadInt(&nVersion)) + return false; + if (1 != nVersion) + return false; + if (!m_info.m_geometryFingerprint.Read(binary_archive)) + return false; + if (!binary_archive.ReadInt(&m_info.m_sourceFaceId)) + return false; + return true; + } +#if defined(ON_COMPILER_CLANG) +#pragma clang diagnostic pop +#endif + + void SetInfo(const ON_RenderMeshInfo& info) + { + m_info = info; + } + const ON_RenderMeshInfo& Info() const + { + return m_info; + } + virtual bool Transform(const ON_Xform& xform) override + { + m_info.m_geometryFingerprint.Transform(xform); + return ON_UserData::Transform(xform); + } +protected: + ON_RenderMeshInfo m_info; +}; + +ON_OBJECT_IMPLEMENT(CTtRenderMeshInfoUserData, ON_UserData, "4960A046-8201-4F0F-8F22-FCB6F91C765D"); + + +const ON_RenderMeshInfo* ON_Mesh::GetRenderMeshInfo() const +{ + CTtRenderMeshInfoUserData* pUD = CTtRenderMeshInfoUserData::Cast(GetUserData(ON_CLASS_ID(CTtRenderMeshInfoUserData))); + if (nullptr == pUD) + return nullptr; + return &pUD->Info(); +} + +// Closest point mapping interface +class IClosestPointMapper +{ +public: + virtual ~IClosestPointMapper() {} + virtual bool IsValid() const = 0; + virtual bool ClosestPointTC(const ON_3dPoint& pt, const ON_3fVector& vtNormalHint, ON_3dPoint& tcOut) const = 0; + virtual bool MatchFaceTC(int count, const ON_3dPoint* pPts, ON_3dPoint* pTcsOut) const = 0; +}; + +// Closest point projection of texture coordinates for a mesh face. Samples projection close to face vertices and then extrapolates to vertices. +static void PTCHelper(const IClosestPointMapper& mapper, const ON_Mesh & meshTo, const ON_3fVector & vtFaceNormal, const ON_3dPoint * vtx, int i0, int i1, int i2, ON_3dPoint * tOut) +{ + const double n = 3.0; + const double m = (n + 1.0) / (n - 1.0); + const double k = 1.0 / (n - 1.0); + + ON_3dPoint sample_points[3]; + + sample_points[0] = n / (n + 2.0) * vtx[i0] + 1.0 / (n + 2.0) * (vtx[i1] + vtx[i2]); + sample_points[1] = n / (n + 2.0) * vtx[i1] + 1.0 / (n + 2.0) * (vtx[i0] + vtx[i2]); + sample_points[2] = n / (n + 2.0) * vtx[i2] + 1.0 / (n + 2.0) * (vtx[i0] + vtx[i1]); + + ON_3dPoint sample_tcs[3] = { ON_3dPoint::Origin, ON_3dPoint::Origin, ON_3dPoint::Origin }; + + for (int i = 0; i < 3; i++) + { + mapper.ClosestPointTC(sample_points[i], vtFaceNormal, sample_tcs[i]); + } + + tOut[0] = m * sample_tcs[0] - k * (sample_tcs[1] + sample_tcs[2]); + tOut[1] = m * sample_tcs[1] - k * (sample_tcs[0] + sample_tcs[2]); + tOut[2] = m * sample_tcs[2] - k * (sample_tcs[0] + sample_tcs[1]); +} + + +static bool MatchFace(const ON_Mesh& mesh, const ON_2fPointArray& tcs, const int fi, const double maxTolerance, const int count, const ON_3dPoint* pPts, ON_3dPoint* pTcsOut, double& toleranceOut) +{ + toleranceOut = DBL_MAX; + if (fi < 0 || fi >= mesh.m_F.Count()) + return false; + if (count < 3 || count > 4) + return false; + + const ON_MeshFace& mface = mesh.m_F[fi]; + + const int mfvc = mface.IsQuad() ? 4 : 3; + + int ptvi[4] = { -1, -1, -1, -1 }; + double tol[4] = { DBL_MAX, DBL_MAX , DBL_MAX , DBL_MAX }; + for (int mfvi = 0; mfvi < mfvc; mfvi++) + { + const ON_3dPoint& mfvPt = mesh.Vertex(mface.vi[mfvi]); + for (int pti = 0; pti < count; pti++) + { + const double dist = pPts[pti].DistanceTo(mfvPt); + if (ptvi[pti] == -1 || dist < tol[pti]) + { + ptvi[pti] = mface.vi[mfvi]; + tol[pti] = dist; + } + } + } + + double maxTol = 0.0; + for (int pti = 0; pti < count; pti++) + { + if (ptvi[pti] == -1) + return false; + if (tol[pti] > maxTol) + maxTol = tol[pti]; + } + + toleranceOut = maxTol; + if (maxTol > maxTolerance) + return false; + + for (int pti = 0; pti < count; pti++) + { + pTcsOut[pti] = ON_3dPoint(tcs[ptvi[pti]]); + } + return true; +} + +static void AddIfNotIncluded(ON_SimpleArray& a, int i) +{ + if (a.Search(i) < 0) + a.Append(i); +} + +#include + +bool CreateSubMesh(const ON_Mesh& mesh, const ON_2fPointArray& tc, const int nFis, const int* pFis, ON_Mesh& subMeshOut) +{ + if (nFis == 0 || pFis == nullptr) + return false; + if (tc.Count() != mesh.VertexCount()) + return false; + + subMeshOut = ON_Mesh(nFis, 0, 0, 0); + std::unordered_map vmap; + for (int i = 0; i < nFis; i++) + { + const ON_MeshFace& sourceFace = mesh.m_F[pFis[i]]; + ON_MeshFace& targetFace = subMeshOut.m_F.AppendNew(); + const int fvc = sourceFace.IsQuad() ? 4 : 3; + for (int fvi = 0; fvi < fvc; fvi++) + { + const int sourceVi = sourceFace.vi[fvi]; + const auto vmapit = vmap.find(sourceVi); + if (vmapit == vmap.end()) + { + const int targetVi = subMeshOut.m_V.Count(); + targetFace.vi[fvi] = targetVi; + vmap[sourceVi] = targetVi; + subMeshOut.m_V.Append(mesh.m_V[sourceVi]); + if (mesh.HasDoublePrecisionVertices()) + subMeshOut.m_dV.Append(mesh.m_dV[sourceVi]); + if (mesh.HasVertexNormals()) + subMeshOut.m_N.Append(mesh.m_N[sourceVi]); + subMeshOut.m_T.Append(tc[sourceVi]); + } + else + { + const int targetVi = vmapit->second; + targetFace.vi[fvi] = targetVi; + } + if (fvc == 3) + targetFace.vi[3] = targetFace.vi[2]; + } + if (mesh.HasFaceNormals()) + subMeshOut.m_FN.Append(mesh.m_FN[pFis[i]]); + } + return true; +} + +void ClosestPtToMeshFace(const ON_Mesh* mesh, const int fi, const ON_3dPoint& ptIn, ON_3dPoint& POut, double(&tOut)[4]); + +#if !defined(OPENNURBS_PLUS) +bool MeshFaceTreeClosestPointTC(const ON_Mesh& mesh, const ON_RTree& tree, const ON_3dPoint& pt, ON_3dPoint& tcOut); +#endif + + +// Closest point mapping class for mesh primitive mapping +class CMeshClosestPointMapper : public IClosestPointMapper +{ +public: + CMeshClosestPointMapper(const ON_Mesh& mesh, const ON_2fPointArray& tc, const ON_RenderMeshInfo* pRenderMeshInfo, const ON_Xform& objectXform) + : m_mesh(mesh), + m_tc(tc), + m_pMappingMeshInfo(mesh.GetMappingMeshInfo()), + m_pRenderMeshInfo(pRenderMeshInfo), + m_objectXform(objectXform), + m_pSourceCPM(nullptr), + m_totalMappingTol(0.0), + m_failedFaceMatches(0), + m_seamTool(mesh, tc) + { + m_pMeshFaceTree = new ON_RTree(); + m_pMeshFaceTree->CreateMeshFaceTree(&mesh); + m_bMeshInfosMatch = false; + if (m_pMappingMeshInfo != nullptr && m_pRenderMeshInfo != nullptr) + { + if (m_pMappingMeshInfo->m_geometryFingerprint.Matches(m_pRenderMeshInfo->m_geometryFingerprint, m_objectXform, 0.001)) + { + m_bMeshInfosMatch = true; + int nFis = 0; + const int* pFis = m_pMappingMeshInfo->SourceIdFaces(m_pRenderMeshInfo->m_sourceFaceId, nFis); + if (nFis > 0 && pFis != nullptr && nFis < m_mesh.FaceCount()) + { + if (CreateSubMesh(m_mesh, m_tc, nFis, pFis, m_sourceMesh)) + { + m_pSourceCPM = new CMeshClosestPointMapper(m_sourceMesh, m_sourceMesh.m_T, nullptr, objectXform); + } + } + } + } + } + + virtual ~CMeshClosestPointMapper() + { + if (nullptr != m_pSourceCPM) + delete m_pSourceCPM; + m_pSourceCPM = nullptr; +#if !defined(OPENNURBS_PLUS) + delete m_pMeshFaceTree; +#endif + } + + virtual bool IsValid() const + { + if (m_mesh.VertexCount() != m_tc.Count()) + return false; + + + return true; + } + + virtual bool ClosestPointTC(const ON_3dPoint& pt, const ON_3fVector& vtNormalHint, ON_3dPoint& tcOut) const + { + if (!IsValid()) + return false; + + + return MeshFaceTreeClosestPointTC(m_mesh, *m_pMeshFaceTree, pt, tcOut); + } + + static bool RTreeCallback(void* a_context, ON__INT_PTR a_id) + { + if (nullptr == a_context) + return false; + CMeshClosestPointMapper& mapper = *(CMeshClosestPointMapper*)a_context; + + // Jussi, Apr 25 2024, RH-81671: + // Stop collecting faces. There's probably a mismatch between the mapping and the geometry. + if (mapper.m_resFis.Count() > 1000) + return false; + + mapper.m_resFis.Append((int)a_id); + + return true; + } + + static bool TcContinuous(const ON_Mesh& mesh, const ON_2fPointArray& tc, const ON_MeshTopologyEdge& te) + { + if (te.m_topf_count != 2) + return false; + + int vi[2][2] = { -1, -1, -1, -1 }; + for (int i = 0; i < 2; i++) + { + const int fi = te.m_topfi[i]; + const ON_MeshFace& face = mesh.m_F[fi]; + const int fvc = face.IsQuad() ? 4 : 3; + for (int j = 0; j < 2; j++) + { + const int tvi = te.m_topvi[j]; + for (int fvi = 0; fvi < fvc; fvi++) + { + if (tvi == mesh.Topology().m_topv_map[face.vi[fvi]]) + { + vi[i][j] = face.vi[fvi]; + } + } + if (vi[i][j] == -1) + { + return false; + } + } + } + if (vi[0][0] == vi[1][0] && vi[0][1] == vi[1][1]) + return true; + if (tc[vi[0][0]] != tc[vi[1][0]]) + return false; + if (tc[vi[0][1]] != tc[vi[1][1]]) + return false; + return true; + } + + class SeamTool + { + private: + class Data + { + public: + static const int capacity = 4; + int m_nSeamed; + int m_nSeamless; + int m_neighbourFis[capacity]; + void Set(const ON_SimpleArray& seamless, const ON_SimpleArray& seamed) + { + if (seamless.Count() <= capacity) + m_nSeamless = seamless.Count(); + else + m_nSeamless = capacity; + for (int i = 0; i < m_nSeamless; i++) + m_neighbourFis[i] = seamless[i]; + + if (m_nSeamless + seamed.Count() <= capacity) + m_nSeamed = seamed.Count(); + else + m_nSeamed = capacity - m_nSeamless; + for (int i = 0; i < m_nSeamed; i++) + m_neighbourFis[m_nSeamless + i] = seamed[i]; + } + }; + public: + SeamTool(const ON_Mesh& mesh, const ON_2fPointArray& tc) + : m_mesh(mesh), m_tc(tc), m_bProcessed(false), m_faceData(nullptr) + { + } + virtual ~SeamTool() + { + delete [] m_faceData; + m_faceData = nullptr; + } + int SeamlessNeighbours(int fi, int*& pFisOut) const + { + if (!m_bProcessed) + Process(); + + pFisOut = m_faceData[fi].m_neighbourFis; + return m_faceData[fi].m_nSeamless; + } + int SeamedNeighbours(int fi, int*& pFisOut) const + { + if (!m_bProcessed) + Process(); + + pFisOut = m_faceData[fi].m_neighbourFis + m_faceData[fi].m_nSeamless; + return m_faceData[fi].m_nSeamed; + } + private: + void Process() const + { + m_faceData = new Data[m_mesh.FaceCount()]; + for (int fi = 0; fi < m_mesh.FaceCount(); fi++) + { + ON_SimpleArray seamedFis(4); + ON_SimpleArray seamlessFis(4); + const ON_MeshTopologyFace& tf = m_mesh.Topology().m_topf[fi]; + const int fec = tf.IsTriangle() ? 3 : 4; + for (int i = 0; i < fec; i++) + { + const ON_MeshTopologyEdge& te = m_mesh.Topology().m_tope[tf.m_topei[i]]; + if (te.m_topf_count == 2) + { + const int ofi = te.m_topfi[0] == fi ? te.m_topfi[1] : te.m_topfi[0]; + if (TcContinuous(m_mesh, m_tc, te)) + { + seamlessFis.Append(ofi); + } + else + { + seamedFis.Append(ofi); + } + } + else if (te.m_topf_count > 2) + { + for (int j = 0; j < te.m_topf_count; j++) + { + if (te.m_topfi[j] != fi) + { + seamedFis.Append(te.m_topfi[j]); + } + } + } + } + m_faceData[fi].Set(seamlessFis, seamedFis); + } + m_bProcessed = true; + } + + const ON_Mesh& m_mesh; + const ON_2fPointArray& m_tc; + + mutable bool m_bProcessed; + mutable Data* m_faceData; + }; + + static ON_3dPoint Average(int count, const ON_3dPoint* pPts) + { + ON_3dPoint res = ON_3dPoint::Origin; + if (count > 0) + { + for (int i = 0; i < count; i++) + { + res += pPts[i]; + } + res /= (double)count; + } + return res; + } + + static ON_3dPoint FaceMid(const ON_Mesh& mesh, int fi) + { + const ON_MeshFace& face = mesh.m_F[fi]; + const int fvc = face.IsQuad() ? 4 : 3; + ON_3dPoint ptMid = ON_3dPoint::Origin; + for (int fvi = 0; fvi < fvc; fvi++) + { + ptMid += mesh.Vertex(face.vi[fvi]); + } + return ptMid / (double)fvc; + } + + class ClosestPointData + { + public: + int m_fi; + double m_t[4]; + ON_3dPoint m_P; + }; + + class SamplePoint + { + public: + SamplePoint(const ON_3dPoint& pt, const ON_Mesh& mesh) + : m_pt(pt), m_mesh(mesh) + { + } + const ClosestPointData& ClosestPoint(int fi) + { + if (m_closestMeshPts.capacity() < 30) + { + m_closestMeshPts.reserve(30); + } + for (int i = 0; i < m_closestMeshPts.size(); i++) + { + if (m_closestMeshPts[i].m_fi == fi) + { + return m_closestMeshPts[i]; + } + } + m_closestMeshPts.emplace_back(); + ClosestPointData& q = m_closestMeshPts.back(); + ::ClosestPtToMeshFace(&m_mesh, fi, m_pt, q.m_P, q.m_t); + q.m_fi = fi; + return q; + } + const ON_3dPoint& Point() const + { + return m_pt; + } + + private: + const ON_3dPoint m_pt; + const ON_Mesh& m_mesh; + std::vector m_closestMeshPts; + }; + + class TcSeamlessPatch + { + public: + TcSeamlessPatch(const ON_Mesh& mesh, const ON_2fPointArray& tc, const SeamTool& seamTool) + : m_mesh(mesh), m_tc(tc), m_seamTool(seamTool) + { + m_bEvaluated = false; + } + virtual ~TcSeamlessPatch() + { + } + void Create(int fi, int steps) + { + m_fis.reserve(25); + Expand(fi, 0); + for (int step = 1; step <= steps; step++) + { + std::vector stepFis = m_nextStepFis; + m_nextStepFis.clear(); + for (int nfi : stepFis) + { + Expand(nfi, step); + } + } + } + + bool Evaluate(SamplePoint& samplePt, ClosestPointData& qOut) const + { + double smallestDist = DBL_MAX; + for (int fi : m_fis) + { + const ClosestPointData& q = samplePt.ClosestPoint(fi); + const double dist = q.m_P.DistanceTo(samplePt.Point()); + if (dist < smallestDist) + { + qOut = q; + smallestDist = dist; + } + } + return smallestDist < DBL_MAX; + } + + bool Evaluate(const int count, SamplePoint* pPts, double& maxDistInOut, int tcCount, ON_3dPoint* pTcsOut) const + { + if (!m_bEvaluated) + { + m_maxDist = 0.0; + for (int pi = 0; pi < count; pi++) + { + const ON_3dPoint pt = pPts[pi].Point(); + if (!Evaluate(pPts[pi], m_q[pi])) + return false; + const double dist = m_q[pi].m_P.DistanceTo(pt); + if (dist > m_maxDist) + m_maxDist = dist; + } + m_bEvaluated = true; + } + + if (m_maxDist <= maxDistInOut) + { + for (int pi = 0; pi < tcCount; pi++) + { + const int fi = m_q[pi].m_fi; + if (0 <= fi && fi < m_mesh.FaceCount()) + { + const ON_MeshFace& face = m_mesh.m_F[fi]; + const ON_2dPoint tc = m_q[pi].m_t[0] * m_tc[face.vi[0]] + m_q[pi].m_t[1] * m_tc[face.vi[1]] + m_q[pi].m_t[2] * m_tc[face.vi[2]] + m_q[pi].m_t[3] * m_tc[face.vi[3]]; + pTcsOut[pi].x = tc.x; + pTcsOut[pi].y = tc.y; + pTcsOut[pi].z = 0.0; + } + else + { + return false; + } + } + maxDistInOut = m_maxDist; + return true; + } + return false; + } + protected: + bool HasFace(int fi) + { + for (int addedFi : m_fis) + { + if (fi == addedFi) + return true; + } + return false; + } + bool IsBanned(int fi) + { + for (int bannedFi : m_bannedFis) + { + if (fi == bannedFi) + return true; + } + return false; + } + void AddNextStepFi(int fi) + { + for (int nextStepFi : m_nextStepFis) + { + if (fi == nextStepFi) + return; + } + m_nextStepFis.push_back(fi); + } + void Expand(int fi, int step) + { + if (!HasFace(fi)) + { + if (!IsBanned(fi)) + { + m_fis.push_back(fi); + + int* pSeamed = nullptr; + const int nSeamed = m_seamTool.SeamedNeighbours(fi, pSeamed); + for (int i = 0; i < nSeamed; i++) + { + if (!IsBanned(pSeamed[i])) + { + m_bannedFis.push_back(pSeamed[i]); + } + } + + int* pSeamless = nullptr; + const int nSeamless = m_seamTool.SeamlessNeighbours(fi, pSeamless); + for (int i = 0; i < nSeamless; i++) + { + const int ofi = pSeamless[i]; + if (!HasFace(ofi)) + { + if (!IsBanned(ofi)) + AddNextStepFi(ofi); + } + } + } + } + } + const ON_Mesh& m_mesh; + const ON_2fPointArray& m_tc; + const SeamTool& m_seamTool; + std::vector m_fis; + std::vector m_bannedFis; + std::vector m_nextStepFis; + + // Cached results assuming same set of sample points is used all the time + mutable bool m_bEvaluated; + mutable ClosestPointData m_q[5]; + mutable double m_maxDist; + }; + + class TcSeamlessPatchCache + { + public: + TcSeamlessPatchCache(const ON_Mesh& mesh, const ON_2fPointArray& tc, const SeamTool& seamTool, int steps) + : m_mesh(mesh), m_tc(tc), m_seamTool(seamTool), m_steps(steps) + { + } + virtual ~TcSeamlessPatchCache() + { + for (auto& pit : m_patches) + { + delete pit.second; + } + } + + const TcSeamlessPatch& Get(int fi) + { + auto pit = m_patches.find(fi); + if (pit != m_patches.end()) + { + return *pit->second; + } + else + { + TcSeamlessPatch* pNewPatch = new TcSeamlessPatch(m_mesh, m_tc, m_seamTool); + m_patches[fi] = pNewPatch; + pNewPatch->Create(fi, m_steps); + return *pNewPatch; + } + } + + private: + const ON_Mesh& m_mesh; + const ON_2fPointArray& m_tc; + const SeamTool& m_seamTool; + const int m_steps; + std::unordered_map m_patches; + }; + + virtual bool MatchFaceTC(int count, const ON_3dPoint* pPts, ON_3dPoint* pTcsOut) const + { + if (nullptr != m_pSourceCPM) + return m_pSourceCPM->MatchFaceTC(count, pPts, pTcsOut); + + if (nullptr == m_pMeshFaceTree) + return false; + if (count > 4 || pPts == nullptr || pTcsOut == nullptr) + return false; + + const double initialMappingTol = 1.1e-5; + + ON_3dPoint amendedPts[5]; + memcpy(amendedPts, pPts, sizeof(ON_3dPoint) * count); + amendedPts[count] = Average(count, pPts); + const ON_3dPoint* pAmendedPts = amendedPts; + const int amendedCount = count + 1; + + SamplePoint samplePts[5] = { + SamplePoint(amendedPts[0], m_mesh), + SamplePoint(amendedPts[1], m_mesh), + SamplePoint(amendedPts[2], m_mesh), + SamplePoint(amendedPts[3], m_mesh), + SamplePoint(amendedPts[4], m_mesh) + }; + + TcSeamlessPatchCache patchCache(m_mesh, m_tc, m_seamTool, 5); + + for (double mappingTol = initialMappingTol; mappingTol <= 20.0; mappingTol = mappingTol * 10.0) + { + if (AdaptedMatchFaceTC(amendedCount, pAmendedPts, samplePts, count, pTcsOut, mappingTol, mappingTol == initialMappingTol, patchCache)) + { + if (m_totalMappingTol < mappingTol) + m_totalMappingTol = mappingTol; + return true; + } + } + m_failedFaceMatches++; + return false; + } + + bool AdaptedMatchFaceTC(int count, const ON_3dPoint* pPts, SamplePoint* pSamplePts, int tcCount, ON_3dPoint* pTcsOut, double mappingTol, bool bEqualFaceCheck, TcSeamlessPatchCache& patchCache) const + { + // This code is dummy iteration through all faces to find a matching one + //double minTol = DBL_MAX; + //double matchTol = DBL_MAX; + //int matchFi = -1; + //for (int fi = 0; fi < m_mesh.m_F.Count(); fi++) + //{ + // double tol = 0.0; + // if (MatchFace(m_mesh, m_tc, fi, mappingTol, count, pPts, pTcsOut, tol)) + // { + // if (tol < matchTol) + // { + // matchFi = fi; + // matchTol = tol; + // } + // //return true; // Uncomment this to return here + // } + // if (tol < minTol) + // { + // minTol = tol; + // } + //} + + ON_SimpleArray commonFis; + ON_SimpleArray allFis; + for (int fvi = 0; fvi < count; fvi++) + { + ON_RTreeSphere sphere; + sphere.m_point[0] = pPts[fvi].x; + sphere.m_point[1] = pPts[fvi].y; + sphere.m_point[2] = pPts[fvi].z; + sphere.m_radius = mappingTol; + m_resFis.SetCount(0); + m_pMeshFaceTree->Search(&sphere, RTreeCallback, (void*)this); + if (fvi == 0) + { + commonFis = m_resFis; + } + else + { + ON_SimpleArray newCommonFis(commonFis.Count()); + for (int i = 0; i < commonFis.Count(); i++) + { + if (m_resFis.Search(commonFis[i]) >= 0) + newCommonFis.Append(commonFis[i]); + } + commonFis = newCommonFis; + } + for (int i = 0; i < m_resFis.Count(); i++) + AddIfNotIncluded(allFis, m_resFis[i]); + } + + if (bEqualFaceCheck) + { + double bestTol = DBL_MAX; + ON_3dPointArray bestTcs(tcCount); + for (int i = 0; i < commonFis.Count(); i++) + { + double tol = 0.0; + if (MatchFace(m_mesh, m_tc, commonFis[i], mappingTol, tcCount, pPts, pTcsOut, tol)) + { + if (tol == 0.0) + { + return true; + } + else if (tol < bestTol) + { + bestTcs.SetCount(0); + bestTcs.Append(tcCount, pTcsOut); + bestTol = tol; + } + } + } + if (bestTol < mappingTol) + { + for (int pi = 0; pi < tcCount; pi++) + { + pTcsOut[pi] = bestTcs[pi]; + } + return true; + } + } + + if (commonFis.Count() > 0) + { + bool bSuccess = false; + double maxDist = mappingTol; + for (int i = 0; i < commonFis.Count(); i++) + { + const TcSeamlessPatch& sp = patchCache.Get(commonFis[i]); + if (sp.Evaluate(count, pSamplePts, maxDist, tcCount, pTcsOut)) + { + bSuccess = true; + } + } + if (bSuccess) + return true; + } + else + { + bool bSuccess = false; + double maxDist = mappingTol; + for (int i = 0; i < allFis.Count(); i++) + { + const TcSeamlessPatch& sp = patchCache.Get(allFis[i]); + if (sp.Evaluate(count, pSamplePts, maxDist, tcCount, pTcsOut)) + { + bSuccess = true; + } + } + if (bSuccess) + return true; + } + + + return false; + } + +protected: + const ON_Mesh& m_mesh; + const ON_2fPointArray& m_tc; + const ON_MappingMeshInfo* m_pMappingMeshInfo; + const ON_RenderMeshInfo* m_pRenderMeshInfo; + ON_RTree* m_pMeshFaceTree; + mutable ON_SimpleArray m_resFis; + const ON_Xform m_objectXform; + bool m_bMeshInfosMatch; + ON_Mesh m_sourceMesh; + CMeshClosestPointMapper* m_pSourceCPM; + SeamTool m_seamTool; + + // Mapping evaluation statistics + mutable double m_totalMappingTol; + mutable int m_failedFaceMatches; +}; + +// Closest point projection of texture coordinates for a mesh. +// Projects first all mesh faces separately and then takes average for each vertex. +static bool ProjectTextureCoordinates(const IClosestPointMapper& mapper, const ON_Mesh & meshTo, ON_2fPointArray & tcTo, const double* PT, const double* NT) +{ + if (!mapper.IsValid()) + return false; + + ON_3dPointArray tcSum(meshTo.VertexCount()); + tcSum.SetCount(meshTo.VertexCount()); + tcSum.MemSet(0); + + ON_SimpleArray tcTerms(tcSum.Count()); + tcTerms.SetCount(tcSum.Count()); + tcTerms.MemSet(0); + + ON_3dPointArray matchedTcSum(meshTo.VertexCount()); + matchedTcSum.SetCount(meshTo.VertexCount()); + matchedTcSum.MemSet(0); + + ON_SimpleArray matchedTcTerms(matchedTcSum.Count()); + matchedTcTerms.SetCount(matchedTcSum.Count()); + matchedTcTerms.MemSet(0); + + tcTo.Reserve(tcSum.Count()); + tcTo.SetCount(tcSum.Count()); + tcTo.MemSet(0); + + const unsigned meshTo_vertex_count = meshTo.m_V.UnsignedCount(); + for (int f = 0; f < meshTo.m_F.Count(); f++) + { + const ON_MeshFace & faceTo = meshTo.m_F[f]; + + // https://mcneel.myjetbrains.com/youtrack/issue/RH-59811 + // This crash may have happened because faceTo.vi[] has invalid indices. + const unsigned* faceTo_vi = (const unsigned*)(faceTo.vi); // using unsigned so we can reduce compares from 8 to 4. + if (faceTo_vi[0] >= meshTo_vertex_count + || faceTo_vi[1] >= meshTo_vertex_count + || faceTo_vi[2] >= meshTo_vertex_count + || faceTo_vi[3] >= meshTo_vertex_count) + { + ON_ERROR("meshTo.m_F[] has invalid faces."); + continue; + } + + + ON_3fVector vtFaceNormal = MeshFaceNormal(meshTo, f); + + if (nullptr != NT) + { + const double Nx = PT[0] * vtFaceNormal.x + PT[1] * vtFaceNormal.y + PT[ 2] * vtFaceNormal.z; + const double Ny = PT[4] * vtFaceNormal.x + PT[5] * vtFaceNormal.y + PT[ 6] * vtFaceNormal.z; + const double Nz = PT[8] * vtFaceNormal.x + PT[9] * vtFaceNormal.y + PT[10] * vtFaceNormal.z; + vtFaceNormal.Set((float)Nx, (float)Ny, (float)Nz); + vtFaceNormal.Unitize(); + } + + ON_3dPoint vtx[4] = { ON_3dPoint(meshTo.m_V[faceTo.vi[0]]), ON_3dPoint(meshTo.m_V[faceTo.vi[1]]), ON_3dPoint(meshTo.m_V[faceTo.vi[2]]), ON_3dPoint(meshTo.m_V[faceTo.vi[3]]) }; + + if (nullptr != PT) + { + for (int i = 0; i < 4; i++) + { + double w = PT[12] * vtx[i].x + PT[13] * vtx[i].y + PT[14] * vtx[i].z + PT[15]; + w = (0.0 != w) ? 1.0/w : 1.0; + const double Px = w * (PT[0] * vtx[i].x + PT[1] * vtx[i].y + PT[ 2] * vtx[i].z + PT[ 3]); + const double Py = w * (PT[4] * vtx[i].x + PT[5] * vtx[i].y + PT[ 6] * vtx[i].z + PT[ 7]); + const double Pz = w * (PT[8] * vtx[i].x + PT[9] * vtx[i].y + PT[10] * vtx[i].z + PT[11]); + vtx[i].x = Px; + vtx[i].y = Py; + vtx[i].z = Pz; + } + } + + ON_3dPoint t[4] = { ON_3dPoint::Origin, ON_3dPoint::Origin, ON_3dPoint::Origin, ON_3dPoint::Origin }; + + const int fvc = faceTo.IsQuad() ? 4 : 3; + if (mapper.MatchFaceTC(fvc, vtx, t)) + { + for (int fvi = 0; fvi < fvc; fvi++) + { + tcSum[faceTo.vi[fvi]] += t[fvi]; + tcTerms[faceTo.vi[fvi]] ++; + matchedTcSum[faceTo.vi[fvi]] += t[fvi]; + matchedTcTerms[faceTo.vi[fvi]] ++; + } + } + else + { + if (faceTo.IsTriangle()) + { + PTCHelper(mapper, meshTo, vtFaceNormal, vtx, 0, 1, 2, t); + + tcSum[faceTo.vi[0]] += t[0]; + tcSum[faceTo.vi[1]] += t[1]; + tcSum[faceTo.vi[2]] += t[2]; + tcTerms[faceTo.vi[0]] ++; + tcTerms[faceTo.vi[1]] ++; + tcTerms[faceTo.vi[2]] ++; + } + else + { + const ON_3dVector vtN012(ON_CrossProduct(vtx[1] - vtx[0], vtx[2] - vtx[1])); + const ON_3dVector vtN230(ON_CrossProduct(vtx[3] - vtx[2], vtx[0] - vtx[3])); + + if (0.0 < ON_DotProduct(vtN012, vtN230)) + { + // Face can be split into triangles 012 and 230 + + PTCHelper(mapper, meshTo, vtFaceNormal, vtx, 0, 1, 2, t); + + tcSum[faceTo.vi[0]] += 0.5 * t[0]; + tcSum[faceTo.vi[1]] += t[1]; + tcSum[faceTo.vi[2]] += 0.5 * t[2]; + + PTCHelper(mapper, meshTo, vtFaceNormal, vtx, 2, 3, 0, t); + + tcSum[faceTo.vi[2]] += 0.5 * t[0]; + tcSum[faceTo.vi[3]] += t[1]; + tcSum[faceTo.vi[0]] += 0.5 * t[2]; + } + else + { + // Face can be split into triangles 013 and 123 + + PTCHelper(mapper, meshTo, vtFaceNormal, vtx, 0, 1, 3, t); + + tcSum[faceTo.vi[0]] += t[0]; + tcSum[faceTo.vi[1]] += 0.5 * t[1]; + tcSum[faceTo.vi[3]] += 0.5 * t[2]; + + PTCHelper(mapper, meshTo, vtFaceNormal, vtx, 1, 2, 3, t); + + tcSum[faceTo.vi[1]] += 0.5 * t[0]; + tcSum[faceTo.vi[2]] += t[1]; + tcSum[faceTo.vi[3]] += 0.5 * t[2]; + } + + tcTerms[faceTo.vi[0]] ++; + tcTerms[faceTo.vi[1]] ++; + tcTerms[faceTo.vi[2]] ++; + tcTerms[faceTo.vi[3]] ++; + } + } + } + + for (int v = 0; v < tcSum.Count(); v++) + { + if (0 < matchedTcTerms[v]) + { + tcTo[v] = matchedTcSum[v] / (double)matchedTcTerms[v]; + } + else if (0 < tcTerms[v]) + { + tcTo[v] = tcSum[v] / (double)tcTerms[v]; + } + else + { + tcTo[v] = ON_3dPoint::Origin; + } + } + + return true; +} + + int ON_TextureMapping::Evaluate( const ON_3dPoint& P, const ON_3dVector& N, @@ -4343,6 +5733,32 @@ bool ON_CALLBACK_CDECL MTCB(void* a_context, ON__INT_PTR a_id) return true; } + +bool MeshFaceTreeClosestPointTC(const ON_Mesh& mesh, const ON_RTree& tree, const ON_3dPoint& pt, ON_3dPoint& tcOut) +{ + ON__MTCBDATA data; + data.m_sphere.m_point[0] = pt.x; + data.m_sphere.m_point[1] = pt.y; + data.m_sphere.m_point[2] = pt.z; + data.m_sphere.m_radius = mesh.BoundingBox().FarPoint(pt).DistanceTo(pt); + data.m_pMesh = &mesh; + data.m_pt = pt; + data.m_dist = DBL_MAX; + do + { + tree.Search(&data.m_sphere, MTCB, (void*)&data); + } while (data.m_bRestart); + if (0 <= data.m_face_index && data.m_face_index < mesh.m_F.Count()) + { + const ON_MeshFace& face = mesh.m_F[data.m_face_index]; + tcOut = + data.m_t[0] * ON_3dPoint(mesh.m_T[face.vi[0]]) + + data.m_t[1] * ON_3dPoint(mesh.m_T[face.vi[1]]) + + data.m_t[2] * ON_3dPoint(mesh.m_T[face.vi[2]]) + + data.m_t[3] * ON_3dPoint(mesh.m_T[face.vi[3]]); + } + return true; +} #endif bool ON_TextureMapping::GetTextureCoordinates( @@ -4454,52 +5870,47 @@ bool ON_TextureMapping::GetTextureCoordinates( && nullptr != m_mapping_primitive) { rc = false; - if (ON_TextureMapping::TYPE::mesh_mapping_primitive == m_type) - { - const ON_Xform matP = nullptr != PT ? m_Pxyz * ON_Xform(PT) : m_Pxyz; - const ON_Xform matN = nullptr != NT ? m_Nxyz * ON_Xform(NT) : m_Nxyz; + ON_2fPointArray temp_tcs; - const ON_Mesh* pMesh = CustomMappingMeshPrimitive(); - if (nullptr != pMesh) - { - ON_RTree faceTree; - if (faceTree.CreateMeshFaceTree(pMesh)) - { - for (int vi = 0; vi < mesh.VertexCount(); vi++) - { - T[vi] = ON_3fPoint::Origin; + // Jussi, 11-Nov-2010: Apply mapping primitive transformations first - const ON_3dPoint vtx(mesh.m_V[vi]); - const ON_3dPoint ptV = matP * vtx; - const ON_3fVector vtVN = mesh.HasVertexNormals() ? ON_3fVector(matN * ON_3dVector(mesh.m_N[vi])) : ON_3fVector::ZeroVector; + ON_Xform matP(m_Pxyz); + ON_Xform matN(m_Nxyz); - ON__MTCBDATA data; - data.m_sphere.m_point[0] = ptV.x; - data.m_sphere.m_point[1] = ptV.y; - data.m_sphere.m_point[2] = ptV.z; - data.m_sphere.m_radius = pMesh->BoundingBox().FarPoint(ptV).DistanceTo(ptV); - data.m_pMesh = pMesh; - data.m_pt = ptV; - data.m_dist = DBL_MAX; - do - { - faceTree.Search(&data.m_sphere, MTCB, (void*)&data); - } while (data.m_bRestart); - if (0 <= data.m_face_index && data.m_face_index < pMesh->m_F.Count()) - { - const ON_MeshFace& face = pMesh->m_F[data.m_face_index]; - const ON_3dPoint ptUV = - data.m_t[0] * ON_3dPoint(pMesh->m_T[face.vi[0]]) + - data.m_t[1] * ON_3dPoint(pMesh->m_T[face.vi[1]]) + - data.m_t[2] * ON_3dPoint(pMesh->m_T[face.vi[2]]) + - data.m_t[3] * ON_3dPoint(pMesh->m_T[face.vi[3]]); - const ON_3dPoint vtc = m_uvw * ptUV; - T[vi].Set((float)vtc.x, (float)vtc.y, 0.0f); - } - } - } - } - } + if (nullptr != PT) + matP = matP * ON_Xform(PT); + + if (nullptr != NT) + matN = matN * ON_Xform(NT); + + if (ON_TextureMapping::TYPE::mesh_mapping_primitive == m_type) + { + const ON_Mesh * pMesh = CustomMappingMeshPrimitive(); + if (nullptr != pMesh) + { + CMeshClosestPointMapper meshMapper(*pMesh, pMesh->m_T, mesh.GetRenderMeshInfo(), matP); + rc = ProjectTextureCoordinates(meshMapper, mesh, temp_tcs, &matP.m_xform[0][0], &matN.m_xform[0][0]); + } + } + + if (rc) + { + for (int i_local = 0; i_local < temp_tcs.Count(); i_local++) + { + // Jussi, 11-Nov-2010: Fix for the problem reported by Michael Fritzsche: Custom mesh mapping does not + // obey uvw repeat settings. Solution: multiply the 'mapping coordinate' with m_uvw to + // get the final texture coordinate. + + ON_3dPoint ptT(temp_tcs[i_local].x, temp_tcs[i_local].y, 0.0); + ptT = m_uvw * ptT; + T[i_local].Set((float)ptT.x, (float)ptT.y, (float)ptT.z); + } + + if (nullptr != Tsd) + { + memset(Tsd, 0, vcnt * sizeof(int)); + } + } } else if ( mesh_N && ( ON_TextureMapping::PROJECTION::ray_projection == m_projection diff --git a/opennurbs_math.cpp b/opennurbs_math.cpp index e2d9bfb4..e599b052 100644 --- a/opennurbs_math.cpp +++ b/opennurbs_math.cpp @@ -696,32 +696,34 @@ ON_EvNormal(int limit_dir, const double DuoDu = Du.LengthSquared(); const double DuoDv = Du*Dv; const double DvoDv = Dv.LengthSquared(); - if ( ON_EvJacobian(DuoDu,DuoDv,DvoDv,nullptr) ) { - N = ON_CrossProduct(Du,Dv); + if (ON_EvJacobian(DuoDu, DuoDv, DvoDv, nullptr)) + { + N = ON_CrossProduct(Du, Dv); } - else { + else if (Duu.IsValid() && Duv.IsValid() && Dvv.IsValid()) + { /* degenerate jacobian - try to compute normal using limit * * P,Du,Dv,Duu,Duv,Dvv = srf and partials evaluated at (u0,v0). * Su,Sv,Suu,Suv,Svv = partials evaluated at (u,v). * Assume that srf : R^2 -> R^3 is analytic near (u0,v0). * - * srf(u0+u,v0+v) = srf(u0,v0) + u*Du + v*Dv + * srf(u0+u,v0+v) = srf(u0,v0) + u*Du + v*Dv * + (1/2)*u^2*Duu + u*v*Duv + (1/2)v^2*Dvv * + cubic and higher order terms. * - * Su X Sv = Du X Dv + u*(Du X Duv + Duu X Dv) + v*(Du X Dvv + Duv X Dv) + * Su X Sv = Du X Dv + u*(Du X Duv + Duu X Dv) + v*(Du X Dvv + Duv X Dv) * + quadratic and higher order terms - * - * Set - * (1) A = (Du X Duv + Duu X Dv), + * + * Set + * (1) A = (Du X Duv + Duu X Dv), * (2) B = (Du X Dvv + Duv X Dv) and assume - * Du X Dv = 0. Then + * Du X Dv = 0. Then * * |Su X Sv|^2 = u^2*AoA + 2uv*AoB + v^2*BoB + cubic and higher order terms * * If u = a*t, v = b*t and (a^2*AoA + 2ab*AoB + b^2*BoB) != 0, then - * + * * Su X Sv a*A + b*B * lim --------- = ---------------------------------- * t->0 |Su X Sv| sqrt(a^2*AoA + 2ab*AoB + b^2*BoB) @@ -729,7 +731,7 @@ ON_EvNormal(int limit_dir, * All I need is the direction, so I just need to compute a*A + b*B as carefully * as possible. Note that * (3) a*A + b*B = Du X (a*Duv + b*Dvv) - Dv X (a*Duu + b*Duv). - * Formaula (3) requires fewer flops than using formulae (1) and (2) to + * Formaula (3) requires fewer flops than using formulae (1) and (2) to * compute a*A + b*B. In addition, when |Du| << |Dv| or |Du| >> |Dv|, * formula (3) reduces the number of subtractions between numbers of * similar size. Since the (nearly) zero first partial is the most common @@ -762,32 +764,34 @@ ON_EvNormal(int limit_dir, * */ - double a,b; - ON_3dVector V, Au, Av; + double a, b; + ON_3dVector V, Au, Av; - switch(limit_dir) { - case 2: /* from 2nd quadrant to point */ - a = -1.0; b = 1.0; break; - case 3: /* from 3rd quadrant to point */ - a = -1.0; b = -1.0; break; - case 4: /* from 4rth quadrant to point */ - a = 1.0; b = -1.0; break; - default: /* from 1rst quadrant to point */ - a = 1.0; b = 1.0; break; - } + switch (limit_dir) { + case 2: /* from 2nd quadrant to point */ + a = -1.0; b = 1.0; break; + case 3: /* from 3rd quadrant to point */ + a = -1.0; b = -1.0; break; + case 4: /* from 4rth quadrant to point */ + a = 1.0; b = -1.0; break; + default: /* from 1rst quadrant to point */ + a = 1.0; b = 1.0; break; + } - V = a*Duv + b*Dvv; - Av.x = Du.y*V.z - Du.z*V.y; - Av.y = Du.z*V.x - Du.x*V.z; - Av.z = Du.x*V.y - Du.y*V.x; + V = a * Duv + b * Dvv; + Av.x = Du.y * V.z - Du.z * V.y; + Av.y = Du.z * V.x - Du.x * V.z; + Av.z = Du.x * V.y - Du.y * V.x; - V = a*Duu + b*Duv; - Au.x = V.y*Dv.z - V.z*Dv.y; - Au.y = V.z*Dv.x - V.x*Dv.z; - Au.z = V.x*Dv.y - V.y*Dv.x; + V = a * Duu + b * Duv; + Au.x = V.y * Dv.z - V.z * Dv.y; + Au.y = V.z * Dv.x - V.x * Dv.z; + Au.z = V.x * Dv.y - V.y * Dv.x; - N = Av + Au; + N = Av + Au; } + else + N = ON_3dVector::ZeroVector; return N.Unitize(); } @@ -4283,21 +4287,21 @@ bool ON_EvPrincipalCurvatures( const double g = Dt.x*Dt.x + Dt.y*Dt.y + Dt.z*Dt.z; - if (gauss) *gauss = 0.0; - if (mean) *mean = 0.0; - if (kappa1) *kappa1 = 0.0; - if (kappa2) *kappa2 = 0.0; + if (nullptr != gauss) *gauss = 0.0; + if (nullptr != mean) *mean = 0.0; + if (nullptr != kappa1) *kappa1 = 0.0; + if (nullptr != kappa2) *kappa2 = 0.0; K1.x = K1.y = K1.z = 0.0; K2.x = K2.y = K2.z = 0.0; const double jac = e*g - f*f; - if ( jac == 0.0 ) - return false; + if ( false == (jac != 0.0) ) + return false; // jac is zero or nan x = 1.0/jac; const double det = (l*n - m*m)*x; // = Gaussian curvature const double trace = (g*l - 2.0*f*m + e*n)*x; // = 2*(mean curvature) - if (gauss) *gauss = det; - if (mean) *mean = 0.5*trace; + if (nullptr != gauss) *gauss = det; + if (nullptr != mean) *mean = 0.5*trace; { // solve k^2 - trace*k + det = 0 to get principal curvatures diff --git a/opennurbs_mesh.cpp b/opennurbs_mesh.cpp index e56722f7..953ed44f 100644 --- a/opennurbs_mesh.cpp +++ b/opennurbs_mesh.cpp @@ -4595,28 +4595,33 @@ bool SetCachedTextureCoordinatesFromMaterial(const ON_Mesh& mesh, std::function< return ON_Mesh_Private_SetCachedTextureCoordinatesFromMaterial(meshes, per_vertex_channels, mapping_ref, perform_cleanup, nullptr); } +ON_DECL ON_TextureMapping ONX_Model_GetTextureMappingHelper(const ONX_Model& onx_model, const ON_MappingChannel* pMC) +{ + ON_TextureMapping mapping = ON_TextureMapping::SurfaceParameterTextureMapping; + + ONX_ModelComponentIterator it(onx_model, ON_ModelComponent::Type::TextureMapping); + + for (ON_ModelComponentReference cr = it.FirstComponentReference(); false == cr.IsEmpty(); cr = it.NextComponentReference()) + { + const ON_TextureMapping* texture_mapping = ON_TextureMapping::Cast(cr.ModelComponent()); + if (nullptr == texture_mapping) + continue; + + if (ON_UuidCompare(&texture_mapping->Id(), &pMC->m_mapping_id) == 0) + { + mapping = *texture_mapping; + break; + } + } + + return mapping; +} + bool ON_Mesh::SetCachedTextureCoordinatesFromMaterial(const ONX_Model& onx_model, const ON_Material& material, const ON_MappingRef* mapping_ref) const { auto get_mapping_func = [&onx_model](const ON_MappingChannel* pMC) { - ON_TextureMapping mapping = ON_TextureMapping::SurfaceParameterTextureMapping; - - ONX_ModelComponentIterator it(onx_model, ON_ModelComponent::Type::TextureMapping); - - for ( ON_ModelComponentReference cr = it.FirstComponentReference(); false == cr.IsEmpty(); cr = it.NextComponentReference()) - { - const ON_TextureMapping* texture_mapping = ON_TextureMapping::Cast(cr.ModelComponent()); - if (nullptr == texture_mapping) - continue; - - if(ON_UuidCompare(&texture_mapping->Id(), &pMC->m_mapping_id) == 0) - { - mapping = *texture_mapping; - break; - } - } - - return mapping; + return ONX_Model_GetTextureMappingHelper(onx_model, pMC); }; ON_SimpleArray mappings_to_cache; @@ -4664,24 +4669,7 @@ const ON_TextureCoordinates* ON_Mesh::GetCachedTextureCoordinates(const ONX_Mode { auto get_mapping_func = [&onx_model](const ON_MappingChannel* pMC) { - ON_TextureMapping mapping = ON_TextureMapping::SurfaceParameterTextureMapping; - - ONX_ModelComponentIterator it(onx_model, ON_ModelComponent::Type::TextureMapping); - - for (ON_ModelComponentReference cr = it.FirstComponentReference(); false == cr.IsEmpty(); cr = it.NextComponentReference()) - { - const ON_TextureMapping* texture_mapping = ON_TextureMapping::Cast(cr.ModelComponent()); - if (nullptr == texture_mapping) - continue; - - if (ON_UuidCompare(&texture_mapping->Id(), &pMC->m_mapping_id) == 0) - { - mapping = *texture_mapping; - break; - } - } - - return mapping; + return ONX_Model_GetTextureMappingHelper(onx_model, pMC); }; return ::Internal_GetCachedTextureCoordinates(*this, get_mapping_func, texture, mapping_ref); @@ -17845,3 +17833,314 @@ unsigned int ON_Mesh::MergeFaceSets( return (ngon1_count > ngon0_count) ? ngon0_count : bailout_rc; } +static +void ClosestPtToEdge(const ON_3dPoint& P, + const ON_3dPoint& A, const ON_3dPoint& B, + double& a, double& b) +{ + const double D[3] = { A.x - B.x, A.y - B.y, A.z - B.z }; + double t = D[0] * D[0] + D[1] * D[1] + D[2] * D[2]; + if (t <= 0.0) + { + a = 1.0; + b = 0.0; + } + else + { + const double P0[3] = { A.x - P.x, A.y - P.y, A.z - P.z }; + const double P1[3] = { P.x - B.x, P.y - B.y, P.z - B.z }; + t = 1.0 / t; + if (P0[0] * P0[0] + P0[1] * P0[1] + P0[2] * P0[2] <= P1[0] * P1[0] + P1[1] * P1[1] + P1[2] * P1[2]) + { + // A is closest to P + t = (P0[0] * D[0] + P0[1] * D[1] + P0[2] * D[2]) * t; + if (t <= ON_ZERO_TOLERANCE) + { + a = 1.0; + b = 0.0; + } + else if (t >= 1.0 - ON_ZERO_TOLERANCE) + { + a = 0.0; + b = 1.0; + } + else + { + a = 1.0 - t; + b = t; + } + } + else + { + // B is closest to P + t = (P1[0] * D[0] + P1[1] * D[1] + P1[2] * D[2]) * t; + if (t <= ON_ZERO_TOLERANCE) + { + a = 0.0; + b = 1.0; + } + else if (t >= 1.0 - ON_ZERO_TOLERANCE) + { + a = 1.0; + b = 0.0; + } + else + { + a = t; + b = 1.0 - t; + } + } + } +} + +static bool ClosestPtToTriangleHelper(const ON_3dPoint& R, + const ON_3dPoint& S, + const ON_3dPoint& T, + ON_3dPoint Q, + double* r, double* s, double* t) +{ + double a00, a01, a10, a11, b0, b1, ss, tt; + + const ON_3dPoint V0(R.x - T.x, R.y - T.y, R.z - T.z); + const ON_3dPoint V1(S.x - T.x, S.y - T.y, S.z - T.z); + + Q.x -= T.x; + Q.y -= T.y; + Q.z -= T.z; + + a00 = V0.x * V0.x + V0.y * V0.y + V0.z * V0.z; + if (a00 <= 0.0) + return false; + a00 = 1.0 / a00; + + a11 = V1.x * V1.x + V1.y * V1.y + V1.z * V1.z; + if (a11 <= 0.0) + return false; + a11 = 1.0 / a11; + + a01 = V0.x * V1.x + V0.y * V1.y + V0.z * V1.z; + a10 = a01 * a11; + a01 *= a00; + + b0 = (V0.x * Q.x + V0.y * Q.y + V0.z * Q.z) * a00; + b1 = (V1.x * Q.x + V1.y * Q.y + V1.z * Q.z) * a11; + + if (a00 <= a11) + { + a11 = 1.0 - a01 * a10; + if (0.0 == a11) + return false; + tt = (b1 - a10 * b0) / a11; + ss = (b0 - a01 * tt); + } + else + { + a00 = 1.0 - a01 * a10; + if (0.0 == a00) + return false; + ss = (b0 - a01 * b1) / a00; + tt = (b1 - a10 * ss); + } + + *r = ss; + *s = tt; + *t = 1.0 - ss - tt; + + return true; +} + +static void ClosestPtToTriangle(const ON_3dPoint& input, ON_3dPoint& output, const ON_3dPoint* tri, double& baryA, double& baryB, double& baryC) +{ + if (!ClosestPtToTriangleHelper(tri[0], tri[1], tri[2], input, &baryA, &baryB, &baryC)) + { + // bogus triangle - check edges + double a, b, c, d; + + ClosestPtToEdge(input, tri[0], tri[1], baryA, baryB); + baryC = 0.0; + d = input.DistanceTo(baryA * tri[0] + baryB * tri[1]); + + ClosestPtToEdge(input, tri[1], tri[2], a, b); + c = input.DistanceTo(a * tri[1] + b * tri[2]); + if (c < d) + { + d = c; + baryA = 0.0; + baryB = a; + baryC = b; + } + + ClosestPtToEdge(input, tri[2], tri[0], a, b); + c = input.DistanceTo(a * tri[2] + b * tri[0]); + if (c < d) + { + baryA = b; + baryB = 0.0; + baryC = a; + } + } + else + { + if (baryA <= ON_ZERO_TOLERANCE) + baryA = 0.0; + + if (baryB <= ON_ZERO_TOLERANCE) + baryB = 0.0; + + if (baryC <= ON_ZERO_TOLERANCE) + baryC = 0.0; + + if (baryA == 0.0 || baryB == 0.0 || baryC == 0.0) + { + // Candidate bary-coords + double baryA0, baryB0, baryC0; + double baryA1, baryB1, baryC1; + + baryA0 = baryB0 = baryC0 = -1.0; + baryA1 = baryB1 = baryC1 = -1.0; + + bool has_second_cadidate_coords = false; + + if (0.0 == baryA) + { + if (0.0 == baryB) { + ClosestPtToEdge(input, tri[0], tri[2], baryA0, baryC0); + baryB0 = 1.0 - baryA0 - baryC0; + + ClosestPtToEdge(input, tri[1], tri[2], baryB1, baryC1); + baryA1 = 1.0 - baryB1 - baryC1; + has_second_cadidate_coords = true; + } + else if (0.0 == baryC) { + ClosestPtToEdge(input, tri[0], tri[1], baryA0, baryB0); + baryC0 = 1.0 - baryA0 - baryB0; + + ClosestPtToEdge(input, tri[2], tri[1], baryC1, baryB1); + baryA1 = 1.0 - baryC1 - baryB1; + has_second_cadidate_coords = true; + } + else { + ClosestPtToEdge(input, tri[1], tri[2], baryB0, baryC0); + baryA0 = 1.0 - baryB0 - baryC0; + } + } + else if (0.0 == baryB) + { + if (0.0 == baryC) { + ClosestPtToEdge(input, tri[1], tri[0], baryB0, baryA0); + baryC0 = 1.0 - baryB0 - baryA0; + + ClosestPtToEdge(input, tri[2], tri[0], baryC1, baryA1); + baryB1 = 1.0 - baryC1 - baryA1; + has_second_cadidate_coords = true; + } + else { + ClosestPtToEdge(input, tri[2], tri[0], baryC0, baryA0); + baryB0 = 1.0 - baryC0 - baryA0; + } + } + else if (0.0 == baryC) { + ClosestPtToEdge(input, tri[0], tri[1], baryA0, baryB0); + baryC0 = 1.0 - baryA0 - baryB0; + } + + // Start by assuming the first bary-coords are correct + baryA = baryA0; + baryB = baryB0; + baryC = baryC0; + + if (has_second_cadidate_coords) + { + ON_3dPoint point0 = baryA0 * tri[0] + baryB0 * tri[1] + baryC0 * tri[2]; + ON_3dPoint point1 = baryA1 * tri[0] + baryB1 * tri[1] + baryC1 * tri[2]; + + // If other bary-coords are closer to point, use those instead + if ((point0 - input).LengthSquared() > (point1 - input).LengthSquared()) { + baryA = baryA1; + baryB = baryB1; + baryC = baryC1; + } + } + + if (baryA <= ON_ZERO_TOLERANCE) + baryA = 0.0; + + if (baryB <= ON_ZERO_TOLERANCE) + baryB = 0.0; + + if (baryC <= ON_ZERO_TOLERANCE) + baryC = 0.0; + } + } + + output = baryA * tri[0] + baryB * tri[1] + baryC * tri[2]; +} + +void ClosestPtToMeshFace(const ON_Mesh* mesh, const int fi, const ON_3dPoint& ptIn, ON_3dPoint& POut, double(&tOut)[4]) +{ + // no need to check input - it should be perfect - if it's not, + // fix bug in tree creation code + + const int* face_vi = mesh->m_F[fi].vi; + const ON_3fPoint* mesh_V = mesh->m_V; + ON_3dPoint tri[3]; + + if (face_vi[2] == face_vi[3]) + { + tri[0] = mesh_V[face_vi[0]]; + tri[1] = mesh_V[face_vi[1]]; + tri[2] = mesh_V[face_vi[2]]; + ClosestPtToTriangle(ptIn, POut, tri, tOut[0], tOut[1], tOut[2]); + tOut[3] = 0.0; + } + else + { + // split quad into two triangles + ON_3dPoint POut2; + double tOut2[4] = { 0.0, 0.0, 0.0, 0.0 }; + double d0 = mesh_V[face_vi[0]].DistanceTo(mesh_V[face_vi[2]]); + double d1 = mesh_V[face_vi[1]].DistanceTo(mesh_V[face_vi[3]]); + if (d0 <= d1) + { + // diagonal from 0 to 2 is shortest, so use triangles 0,1,2 and 0,2,3 + tri[0] = mesh_V[face_vi[0]]; + tri[1] = mesh_V[face_vi[1]]; + tri[2] = mesh_V[face_vi[2]]; + ClosestPtToTriangle(ptIn, POut, tri, tOut[0], tOut[1], tOut[2]); + tOut[3] = 0.0; + + // tri[0] = mesh_V[face_vi[0]]; + tri[1] = tri[2]; // tri[1] = mesh_V[face_vi[2]]; + tri[2] = mesh_V[face_vi[3]]; + ClosestPtToTriangle(ptIn, POut2, tri, tOut2[0], tOut2[2], tOut2[3]); + tOut2[1] = 0.0; + } + else + { + // diagonal from 1 to 3 is shortest, so use triangles 0,1,3 and 1,2,3 + tri[0] = mesh_V[face_vi[0]]; + tri[1] = mesh_V[face_vi[1]]; + tri[2] = mesh_V[face_vi[3]]; + ClosestPtToTriangle(ptIn, POut, tri, tOut[0], tOut[1], tOut[3]); + tOut[2] = 0.0; + + tri[0] = tri[1]; // tri[0] = mesh_V[face_vi[1]]; + tri[1] = mesh_V[face_vi[2]]; + //tri[2] = mesh_V[face_vi[3]]; + ClosestPtToTriangle(ptIn, POut2, tri, tOut2[1], tOut2[2], tOut2[3]); + tOut2[0] = 0.0; + } + + d0 = ptIn.DistanceTo(POut); + d1 = ptIn.DistanceTo(POut2); + if (d1 < d0) + { + // point on 2nd triangle was closer + POut = POut2; + tOut[0] = tOut2[0]; + tOut[1] = tOut2[1]; + tOut[2] = tOut2[2]; + tOut[3] = tOut2[3]; + } + } +} \ No newline at end of file diff --git a/opennurbs_mesh.h b/opennurbs_mesh.h index 3e463856..f676279e 100644 --- a/opennurbs_mesh.h +++ b/opennurbs_mesh.h @@ -6214,6 +6214,9 @@ private: bool ReadFaceArray( int, int, ON_BinaryArchive& ); bool SwapEdge_Helper( int, bool ); +public: + const ON_MappingMeshInfo* GetMappingMeshInfo() const; + const ON_RenderMeshInfo* GetRenderMeshInfo() const; }; ////////////////////////////////////////////////////////////////////////// diff --git a/opennurbs_objref.h b/opennurbs_objref.h index 9d522c48..22c8e6df 100644 --- a/opennurbs_objref.h +++ b/opennurbs_objref.h @@ -25,6 +25,10 @@ public: bool Write( ON_BinaryArchive& ) const; bool Read( ON_BinaryArchive& ); + bool SetFromSubDComponentParameter(const class ON_SubDComponentParameter& subd_cp); + + bool GetSubDComponentParameter(class ON_SubDComponentParameter& subd_cp) const; + // If m_point != ON_3dPoint::UnsetPoint and m_t_type != 0, then // m_t_type, m_t, and m_t_ci record the m_geometry evaluation @@ -83,6 +87,14 @@ public: // // 8: m_geometry points to a mesh or mesh vertex object and m_t_ci // identifies a vertex on the mesh object. + // + // 9: A SubD component parameter. + // m_t_ci identifies the SubD component. + // m_t[], m_s[], are used to encode a collection of indicies and doubles + // that depend on the SubD component type. + // Use the SetFromSubDComponentParameter() and GetSubDComponentParameter() + // to convert between ON_ObjRefEvaluationParameter + // and ON_SubDComponentParameter values. // int m_t_type; private: diff --git a/opennurbs_point.h b/opennurbs_point.h index c16996ec..65b7ffc1 100644 --- a/opennurbs_point.h +++ b/opennurbs_point.h @@ -2503,6 +2503,307 @@ bool operator!=( const ON_SurfaceCurvature& rhs ); +/// +/// ON_SurfaceValues stores surface evaluation values (point, normal, curvatures, derivatives) in a single class +/// +class ON_WIP_CLASS ON_SurfaceValues +{ +public: + + enum : unsigned + { + /// + /// The maximum order of a paritial derivative that + /// an ON_SurfaceValues class can possibly store is 15. + /// + MaximumDerivativeOrder = 15 + }; + + /// + /// NOTE WELL: CreateFromLocalArray is for experts dealing with unusual situations. + /// Create a ON_SurfaceValues where the storage for deritave values is managed externally. + /// This is typically used by experts to easily get surface evaluation results stored in value_array[] when + /// the evaluation function returns the results in a ON_SurfaceValues class. + /// The caller is responsible for ensuring that the value_array[] memory is not freed or deleted + /// while the returned ON_SurfaceValues class is in scope. + /// Any copy of the returned values will not reference value_array[]. + /// + /// + /// 0 <= derivative_order <= ON_SurfaceValues::MaximumDerivativeOrder + /// derivative_order = maximum order of a partial derivative that can be stored in value_array[]. + /// 1 for point and 1st partial derivatives (value_array[] has at least 3*value_array_stride doubles). + /// 2 for point, 1st and 2nd partial derivatives (value_array[] has at least 6*value_array_stride doubles). + /// And so on. + /// + /// + /// value_array_stride >= 3. + /// + /// + /// An array with a capacity of at least + /// value_array_stride * ((order_capacity + 1) * (order_capacity + 2) / 2) + /// doubles. + /// + /// + /// After the call, values will use value_array to store point and derivative evaluations. + /// + /// + /// True if successful. + /// + static bool CreateFromLocalArrayForExperts( + unsigned maximum_derivative_order, + size_t value_array_stride, + double* value_array, + ON_SurfaceValues& values + ); + + /// + /// NOTE WELL: CreateFromVectorArray is for experts dealing with unusual situations. + /// Create a ON_SurfaceValues where the storage for the values is deritave values is managed externally. + /// This is typically used by experts to easily get surface evaluation results stored in value_array[] + /// when the evaluation function returns the results in a ON_SurfaceValues class. + /// The caller is responsible for ensuring that the value_array[] memory is not freed or deleted + /// while the returned ON_SurfaceValues class is in scope. + /// Any copy of the returned values will not reference value_array[]. + /// + /// + /// 0 <= derivative_order <= ON_SurfaceValues::MaximumDerivativeOrder + /// derivative_order = maximum order of a partial derivative that can be stored in value_array[]. + /// 1 for point and 1st partial derivatives (value_array[] has at least 3*value_array_stride doubles). + /// 2 for point, 1st and 2nd partial derivatives (value_array[] has at least 6*value_array_stride doubles). + /// And so on. + /// + /// + /// The capacity and count of value_array[] will be set to ((order_capacity + 1) * (order_capacity + 2) / 2). + /// + /// + /// After the call, values will use value_array to store point and derivative evaluations. + /// Note that if values is copied, all derivative information will be lost. + /// This class is for experts dealing with unusual situations. + /// + /// + /// True if successful. + /// + static bool CreateFromVectorArrayForExperts( + unsigned order_capacity, + ON_SimpleArray& value_array, + ON_SurfaceValues& values + ); + + /// + /// + /// + /// + /// 0 <= derivative_order <= ON_SurfaceValues::MaximumDerivativeOrder + /// derivative_order = maximum order of a partial derivative that can be stored in value_array[]. + /// 1 for point and 1st partial derivatives (value_array[] has at least 3*value_array_stride doubles). + /// 2 for point, 1st and 2nd partial derivatives (value_array[] has at least 6*value_array_stride doubles). + /// And so on. + /// + /// + /// True if the capacity to store derivatives was reserved. + /// + bool ReserveDerivativeOrderCapacity( + unsigned order_capacity + ); + + /// + /// The order of the partial derivative returned by Derivative(i, j) is i + j. + /// The memory to store partial derivatives can be allocated by calling + /// ReserveDerivativeOrderCapacity(max_order). + /// + /// + /// Returns the maximum order of a partial derivative + /// that can be stored. + /// + unsigned DerivativeOrderCapacity() const; + + ON_SurfaceValues() = default; + ~ON_SurfaceValues(); + ON_SurfaceValues(const ON_SurfaceValues& src); + ON_SurfaceValues operator=(const ON_SurfaceValues& src); + + static const ON_SurfaceValues Nan; + + void SetPoint(ON_3dPoint P); + void ClearPoint(); + const ON_3dPoint Point() const; + bool PointIsSet() const; + + void SetNormal(ON_3dVector N); + void ClearNormal(); + const ON_3dVector Normal() const; + bool NormalIsSet() const; + + void SetPrincipalCurvatures(double kappa1, double kappa2); + void SetPrincipalCurvatures(ON_SurfaceCurvature kappa); + void ClearPrincipalCurvatures(); + bool PrincipalCurvaturesSet() const; + const ON_SurfaceCurvature PrincipalCurvatures() const; + + //void SetPrincipalVectorCurvatures( + // ON_3dVector K1, + // ON_3dVector K2 + //); + //void ClearPrincipalVectorCurvatures(); + //bool PrincipalVectorCurvaturesAreSet() const; + //const ON_3dVector PrincipalVectorCurvature(unsigned i) const; + + void SetDerivative(unsigned i, unsigned j, ON_3dVector D); + void ClearDerivative(unsigned i, unsigned j); + void ClearDerivatives(); + bool DerivativeIsSet(unsigned i, unsigned j) const; + bool DerivativesAreSet(unsigned order) const; + + /// + /// If (u,v) are the surface parameters, then + /// Derivatieve(0,0) = point, + /// Derivative(1,0) = 1st partial with respect to u (D/Du). + /// Derivative(0,1) = 1st partial with respect to v (D/Dv). + /// Derivative(2,0) = 2nd partial with respect to u (D/Du^2). + /// Derivative(1,1) = 2nd mixed partial (D/DuDv). + /// Derivative(0,2) = 2nd partial with respect to v (D/Dv^2). + /// Derivative(i,j) = (i+j)-th order partial D/Du^iDv^j. + /// + /// + /// Number of partial derivatives in the 1st surface parameter. + /// + /// + /// Number of partial derivatives in the 2nd surface parameter. + /// + /// Specified partial derivative. + const ON_3dVector Derivative(unsigned i, unsigned j) const; + + /// + /// Unsets all surface evaluation values. + /// + void Clear(); + + /// + /// Used by experts when calling legacy bispan evaluation code that takes (num_der, stride, ders) parameters. + /// Pretend this does not exist. + /// + /// + /// The returned value is the maximum order of a partial derivative that can be stored in derivative_values. + /// + /// + /// Returns the number of doubles between successive partial derivatives. + /// + /// + /// Returns a pointer to an array of stride*(derivative_order_capacity + 1)*(derivative_order_capacity + 2)/2 doubles. + /// + /// + /// True if the returned inforamation can be passed to a legacy bispan evaluator. + /// + bool GetDerivativeArrayForExperts( + unsigned& derivative_order_capacity, + size_t& stride, + double*& derivative_values + ); + + /// + /// Used by experts when calling legacy bispan evaluation code that takes (num_der, stride, ders) parameters. + /// Pretend this does not exist. + /// + /// + /// + /// + void SetDerivativeArrayForExperts( + unsigned max_derivative_order, + size_t stride, + const double* derivative_values + ); + +private: + /// + /// True if this class should manage the m_values array. + /// + bool Internal_DerivativeMemoryIsManaged() const; + + /// + /// True if and expert is managing the memory m_derivatives + /// pionts at and that expert has made sure the memory will + /// exist for the duration of this class's scope. + /// + bool Internal_DerivativeMemoryIsExternal() const; + + unsigned Internal_DerivativeOrderCapacity() const; + + unsigned Internal_DerivativeVectorCount() const; + + unsigned Internal_DerivativeVectorCapacity() const; + + /// + /// Number of partial derivatives with order &;t;=max_order. + /// (max_order+1)*(max_order+2)/2 + /// + static unsigned Internal_DerivativeVectorCount(unsigned max_order); + + /// + /// + /// derivative_order_capacity = maximum derivative order that can be stored. + /// + void Internal_AllocateManagedDerivativeMemory(unsigned derivative_order_capacity); + + double* Internal_Derivative(unsigned i, unsigned j, bool bSet) const; + void Internal_CopyFrom(const ON_SurfaceValues& src); + void Internal_Destroy(); + + enum MasksAndBits : unsigned char + { + /// + /// point_set bit is used in m_derivatives_count_etc + /// + point_set = 0x80u, + + /// + /// normal_set bit is used in m_derivatives_count_etc + /// + normal_set = 0x40u, + + /// + /// principal_curvatures_set bit is used in m_derivatives_count_etc + /// + principal_curvatures_set = 0x20u, + + ///// + ///// principal_vector_curvatures_set bit is used in m_derivatives_count_etc + ///// + //principal_vector_curvatures_set = 0x10u, + + /// + /// derivatives_count_mask maxk is used with m_derivatives_count_etc AND m_derivatives_capacity_etc + /// + derivatives_count_mask = 0x0Fu, + + /// + /// derivatives_managed bit is used in m_derivatives_capacity_etc + /// + derivatives_managed = 0x10u, + }; + + // maximum derivative order set = (MasksAndBits::derivatives_count_mask & m_derivatives_count_etc); + unsigned char m_derivatives_count_etc = 0; + + // maximum derivative order capacity = (MasksAndBits::derivatives_count_mask & m_derivatives_capacity_etc); + // this class manages m_derivatives = (0 != (MasksAndBits::derivatives_managed & m_derivatives_capacity_etc)); + unsigned char m_derivatives_capacity_etc = 0; + + unsigned short m_reserved = 0; + unsigned m_derivatives_stride = 0; + + // If (0 != (MasksAndBits::derivatives_managed & m_derivatives_capacity_etc)), + // then this class manages the memory m_derivatives points at. + // Otherwise the memory is expertly managed outside this class. + // If nullptr != m_derivatives, then m_derivatives points to an array + // of at least m_derivatives_stride*Internal_DerivativeVectorCapacity() doubles, where N = Internal_DerivativeCapacity(); + double* m_derivatives = nullptr; + + ON_3dPoint m_P; + ON_3dPoint m_N; + // ON_3dVector m_K[2]; // principal vector curvatures + ON_SurfaceCurvature m_kappa; // principal scalar curvatures +}; + #if defined(ON_DLL_TEMPLATE) diff --git a/opennurbs_public_version.h b/opennurbs_public_version.h index 18f847b6..a4f99228 100644 --- a/opennurbs_public_version.h +++ b/opennurbs_public_version.h @@ -6,7 +6,7 @@ // To update version numbers, edit ..\build\build_dates.msbuild #define RMA_VERSION_MAJOR 8 -#define RMA_VERSION_MINOR 7 +#define RMA_VERSION_MINOR 8 //////////////////////////////////////////////////////////////// // @@ -14,10 +14,10 @@ // first step in each build. // #define RMA_VERSION_YEAR 2024 -#define RMA_VERSION_MONTH 5 -#define RMA_VERSION_DATE 17 -#define RMA_VERSION_HOUR 15 -#define RMA_VERSION_MINUTE 43 +#define RMA_VERSION_MONTH 6 +#define RMA_VERSION_DATE 2 +#define RMA_VERSION_HOUR 11 +#define RMA_VERSION_MINUTE 0 //////////////////////////////////////////////////////////////// // @@ -35,8 +35,8 @@ // 3 = build system release build #define RMA_VERSION_BRANCH 0 -#define VERSION_WITH_COMMAS 8,7,24138,15430 -#define VERSION_WITH_PERIODS 8.7.24138.15430 +#define VERSION_WITH_COMMAS 8,8,24154,11000 +#define VERSION_WITH_PERIODS 8.8.24154.11000 #define COPYRIGHT "Copyright (C) 1993-2024, Robert McNeel & Associates. All Rights Reserved." #define SPECIAL_BUILD_DESCRIPTION "Public OpenNURBS C++ 3dm file IO library." @@ -44,11 +44,11 @@ #define RMA_VERSION_NUMBER_MAJOR_WSTRING L"8" #define RMA_PREVIOUS_VERSION_NUMBER_MAJOR_WSTRING L"7" -#define RMA_VERSION_NUMBER_SR_STRING "SR7" -#define RMA_VERSION_NUMBER_SR_WSTRING L"SR7" +#define RMA_VERSION_NUMBER_SR_STRING "SR8" +#define RMA_VERSION_NUMBER_SR_WSTRING L"SR8" -#define RMA_VERSION_WITH_PERIODS_STRING "8.7.24138.15430" -#define RMA_VERSION_WITH_PERIODS_WSTRING L"8.7.24138.15430" +#define RMA_VERSION_WITH_PERIODS_STRING "8.8.24154.11000" +#define RMA_VERSION_WITH_PERIODS_WSTRING L"8.8.24154.11000" diff --git a/opennurbs_string.h b/opennurbs_string.h index 81d13d90..8423b2f9 100644 --- a/opennurbs_string.h +++ b/opennurbs_string.h @@ -23,7 +23,7 @@ Parameters Use ON::sort_algorithm::heap_sort only after doing meaningful performance testing using optimized release builds that demonstrate ON::sort_algorithm::heap_sort is significantly better. - index - [out] + index - [out] Pass in an array of count integers. The returned index[] is a permutation of (0,1,..,count-1) such that compare(B[index[i]],B[index[i+1]) <= 0 @@ -39,7 +39,7 @@ Parameters Comparison function a la qsort(). */ ON_DECL -void ON_Sort( +void ON_Sort( ON::sort_algorithm method, int* index, const void* base, @@ -70,7 +70,7 @@ Parameters Use ON::sort_algorithm::heap_sort only after doing meaningful performance testing using optimized release builds that demonstrate ON::sort_algorithm::heap_sort is significantly better. - index - [out] + index - [out] Pass in an array of count integers. The returned index[] is a permutation of (0,1,..,count-1) such that compare(B[index[i]],B[index[i+1]) <= 0 @@ -89,7 +89,7 @@ Parameters pointer passed as the third argument to compare(). */ ON_DECL -void ON_Sort( +void ON_Sort( ON::sort_algorithm method, int* index, const void* base, @@ -130,19 +130,19 @@ Parameters pointer passed as the third argument to compare(). Remarks: As a rule, use quick sort unless extensive tests in your case - prove that heap sort is faster. - - This implementation of quick sort is generally faster than + prove that heap sort is faster. + + This implementation of quick sort is generally faster than heap sort, even when the input arrays are nearly sorted. The only common case when heap sort is faster occurs when - the arrays are strictly "chevron" (3,2,1,2,3) or "carat" + the arrays are strictly "chevron" (3,2,1,2,3) or "carat" (1,2,3,2,1) ordered, and in these cases heap sort is about - 50% faster. If the "chevron" or "caret" ordered arrays - have a little randomness added, the two algorithms have + 50% faster. If the "chevron" or "caret" ordered arrays + have a little randomness added, the two algorithms have the same speed. */ ON_DECL -void ON_hsort( +void ON_hsort( void* base, size_t count, size_t sizeof_element, @@ -150,7 +150,7 @@ void ON_hsort( ); ON_DECL -void ON_qsort( +void ON_qsort( void* base, size_t count, size_t sizeof_element, @@ -158,7 +158,7 @@ void ON_qsort( ); ON_DECL -void ON_hsort( +void ON_hsort( void* base, size_t count, size_t sizeof_element, @@ -167,7 +167,7 @@ void ON_hsort( ); ON_DECL -void ON_qsort( +void ON_qsort( void* base, size_t count, size_t sizeof_element, @@ -179,12 +179,12 @@ void ON_qsort( Description: Sort an array of doubles in increasing order in place. Parameters: - sort_algorithm - [in] + sort_algorithm - [in] ON::sort_algorithm::quick_sort (best in general) or ON::sort_algorithm::heap_sort Use ON::sort_algorithm::heap_sort only if you have done extensive testing with - optimized release builds and are confident heap sort is + optimized release builds and are confident heap sort is significantly faster in your case. - a - [in / out] + a - [in / out] The values in a[] are sorted so that a[i] <= a[i+1]. a[] cannot contain NaNs. nel - [in] @@ -234,12 +234,12 @@ void ON_SortDoubleArrayDecreasing( Description: Sort an array of ints in place. Parameters: - sort_algorithm - [in] + sort_algorithm - [in] ON::sort_algorithm::quick_sort (best in general) or ON::sort_algorithm::heap_sort Use ON::sort_algorithm::heap_sort only if you have done extensive testing with - optimized release builds and are confident heap sort is + optimized release builds and are confident heap sort is significantly faster in your case. - a - [in / out] + a - [in / out] The values in a[] are sorted so that a[i] <= a[i+1]. nel - [in] length of array a[] @@ -255,12 +255,12 @@ void ON_SortIntArray( Description: Sort an array of unsigned ints in place. Parameters: - sort_algorithm - [in] + sort_algorithm - [in] ON::sort_algorithm::quick_sort (best in general) or ON::sort_algorithm::heap_sort Use ON::sort_algorithm::heap_sort only if you have done extensive testing with - optimized release builds and are confident heap sort is + optimized release builds and are confident heap sort is significantly faster in your case. - a - [in / out] + a - [in / out] The values in a[] are sorted so that a[i] <= a[i+1]. nel - [in] length of array a[] @@ -276,12 +276,12 @@ void ON_SortUnsignedIntArray( Description: Sort an array of unsigned 64-bit ints in place. Parameters: - sort_algorithm - [in] + sort_algorithm - [in] ON::sort_algorithm::quick_sort (best in general) or ON::sort_algorithm::heap_sort Use ON::sort_algorithm::heap_sort only if you have done extensive testing with - optimized release builds and are confident heap sort is + optimized release builds and are confident heap sort is significantly faster in your case. - a - [in / out] + a - [in / out] The values in a[] are sorted so that a[i] <= a[i+1]. nel - [in] length of array a[] @@ -299,12 +299,12 @@ void ON_SortUINT64Array( Description: Sort an array of unsigned null terminated char strings in place. Parameters: - sort_algorithm - [in] + sort_algorithm - [in] ON::sort_algorithm::quick_sort (best in general) or ON::sort_algorithm::heap_sort Use ON::sort_algorithm::heap_sort only if you have done extensive testing with - optimized release builds and are confident heap sort is + optimized release builds and are confident heap sort is significantly faster in your case. - a - [in / out] + a - [in / out] The values in a[] are sorted so that strcmp(a[i],a[i+1]) <= 0. nel - [in] length of array a[] @@ -317,23 +317,23 @@ void ON_SortStringArray( ); ON_DECL -const int* ON_BinarySearchIntArray( - int key, - const int* base, +const int* ON_BinarySearchIntArray( + int key, + const int* base, size_t nel ); ON_DECL -const unsigned int* ON_BinarySearchUnsignedIntArray( - unsigned int key, - const unsigned int* base, +const unsigned int* ON_BinarySearchUnsignedIntArray( + unsigned int key, + const unsigned int* base, size_t nel ); ON_DECL const void* ON_BinarySearchArrayForUnsingedInt( - unsigned int key, - const void* base, + unsigned int key, + const void* base, size_t count, size_t sizeof_element, size_t key_offset @@ -359,9 +359,9 @@ const void* ON_BinarySearchArrayFirst2udex( ON_DECL -const double* ON_BinarySearchDoubleArray( - double key, - const double* base, +const double* ON_BinarySearchDoubleArray( + double key, + const double* base, size_t nel ); @@ -393,16 +393,16 @@ public: Description: Set check sum values for a buffer Parameters: - size - [in] + size - [in] number of bytes in buffer - buffer - [in] + buffer - [in] time - [in] last modified time in seconds since Jan 1, 1970, UCT Returns: True if checksum is set. */ - bool SetBufferCheckSum( - size_t size, + bool SetBufferCheckSum( + size_t size, const void* buffer, time_t time ); @@ -415,7 +415,7 @@ public: Returns: True if checksum is set. */ - bool SetFileCheckSum( + bool SetFileCheckSum( FILE* fp ); @@ -427,7 +427,7 @@ public: Returns: True if checksum is set. */ - bool SetFileCheckSum( + bool SetFileCheckSum( const wchar_t* filename ); @@ -440,8 +440,8 @@ public: Returns: True if the buffer has a matching checksum. */ - bool CheckBuffer( - size_t size, + bool CheckBuffer( + size_t size, const void* buffer ) const; @@ -455,7 +455,7 @@ public: Returns: True if the file has a matching checksum. */ - bool CheckFile( + bool CheckFile( FILE* fp, bool bSkipTimeCheck = false ) const; @@ -470,7 +470,7 @@ public: Returns: True if the file has a matching checksum. */ - bool CheckFile( + bool CheckFile( const wchar_t* filename, bool bSkipTimeCheck = false ) const; @@ -639,7 +639,7 @@ Parameters: If element_count2 >= 0, then that number of elements are compared in string2[]. bOrdinalIgnoreCase - [in] If bOrdinalIgnoreCase, then letters with a capital and small codepoint value <= 127 - are compared using the smallest codepoint value. This amounts to converting the + are compared using the smallest codepoint value. This amounts to converting the letters a-z to A-Z before comparing. Returns: 0: the strings are the same @@ -678,7 +678,7 @@ Parameters: If element_count2 >= 0, then that number of elements are compared in string2[]. bOrdinalIgnoreCase - [in] If bOrdinalIgnoreCase, then letters with a capital and small codepoint value <= 127 - are compared using the smallest codepoint value. This amounts to converting the + are compared using the smallest codepoint value. This amounts to converting the letters a-z to A-Z before comparing. Returns: 0: the strings are the same @@ -717,7 +717,7 @@ Parameters: If element_count2 >= 0, then that number of elements are compared in string2[]. bOrdinalIgnoreCase - [in] If bOrdinalIgnoreCase, then letters with a capital and small codepoint value <= 127 - are compared using the smallest codepoint value. This amounts to converting the + are compared using the smallest codepoint value. This amounts to converting the letters a-z to A-Z before comparing. Returns: 0: the strings are the same @@ -755,7 +755,7 @@ Parameters: If element_count2 >= 0, then that number of elements are compared in string2[]. bOrdinalIgnoreCase - [in] If bOrdinalIgnoreCase, then letters with a capital and small codepoint value <= 127 - are compared using the smallest codepoint value. This amounts to converting the + are compared using the smallest codepoint value. This amounts to converting the letters a-z to A-Z before comparing. Returns: 0: the strings are the same @@ -781,7 +781,7 @@ int ON_StringCompareOrdinalWideChar( ); ///////////////////////////////////////////////////////////////////////////// -// +// // ON_String is a UTF-8 char string on all platforms // ON_wString is a UTF-16 encoded wchar_t string on Windows platforms // ON_wString is a UTF-32 encoded wchar_t string on Windows platforms @@ -891,7 +891,7 @@ const ON_SHA1_Hash ON_StringContentHash( ); /// -/// A char string. +/// A char string. /// Any multibyte encoding can be used. If the encoding is unknown, assume it is UTF-8. /// class ON_CLASS ON_String @@ -902,20 +902,14 @@ public: ON_String() ON_NOEXCEPT; ON_String( const ON_String& ); - enum : int + enum : size_t { /// - /// This ON_String::MaximumStringLength value of 100,000,000 - /// is used for string length sanity checking in both ON_String and - /// ON_wString. - /// The design of the ON_String and ON_wString classes could support - /// string lengths up to 0xFFFFFFFEU = 4,294,967,294 but - /// a cap of 100 million seems generous enough for current usage - /// and is small enough to detect many corrupt memory - /// situations. + /// The design of the ON_String and ON_wString classes supports + /// string lengths up to 0xFFFFFFFEU = 4,294,967,294 /// This value is used for both ON_String and ON_wString. /// - MaximumStringLength = 100000000 + MaximumStringLength = 4294967294 }; /// @@ -925,12 +919,12 @@ public: static const ON_String EmptyString; /// - /// Even though a char string has endian independent byte order, - /// it is valid for UTF-8 encoded text to begin with the UTF-8 encoding of U+FEFF. + /// Even though a char string has endian independent byte order, + /// it is valid for UTF-8 encoded text to begin with the UTF-8 encoding of U+FEFF. /// A UTF-8 BOM is sometimes used to mark a char string as UTF-8 encoded. - /// A UTF-8 BOM can occur when UTF-16 and UTF-32 encoded text with a byte + /// A UTF-8 BOM can occur when UTF-16 and UTF-32 encoded text with a byte /// order mark is converted to UTF-8 encoded text. Conversely a UTF-8 BOM - /// is sometimes used when UTF-8 encode text will be converted to UTF-16/UTF-32 + /// is sometimes used when UTF-8 encode text will be converted to UTF-16/UTF-32 /// encoded text and a BOM is desired in the result. /// static const ON_String ByteOrderMark; @@ -1035,7 +1029,7 @@ public: True if c is '0', '1', ..., or '9'. */ static bool IsDecimalDigit(char c); - + /* Description: Decode this char string using the encoding specified by windows_code_page @@ -1051,7 +1045,7 @@ public: const ON_wString MultiByteDecode(int windows_code_page) const; private: - // Use IsEmpty() or IsNotEmpty() when you want a bool + // Use IsEmpty() or IsNotEmpty() when you want a bool // to test for the empty string. explicit operator bool() const { return IsNotEmpty(); } public: @@ -1066,12 +1060,12 @@ public: ON_String( const char* ); ON_String( const char*, int /*length*/ ); // from substring - ON_String( char, int = 1 /* repeat count */ ); + ON_String( char, int = 1 /* repeat count */ ); ON_String( const unsigned char* ); ON_String( const unsigned char*, int /*length*/ ); // from substring - ON_String( unsigned char, int = 1 /* repeat count */ ); - + ON_String( unsigned char, int = 1 /* repeat count */ ); + // construct a UTF-8 string string from a UTF-16 string. ON_String( const wchar_t* src ); // src = UTF-16 string ON_String( const wchar_t* src, int length ); // from a UTF-16 substring @@ -1082,13 +1076,13 @@ public: bool LoadResourceString( HINSTANCE, UINT); // load from Windows string resource // 2047 chars max #endif - + #if defined(ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) ON_String(CFStringRef); - + CFStringRef ToAppleCFString() const; #endif - + /* Parameters: bLengthTest - [in] @@ -1165,7 +1159,7 @@ public: const char* string ); - bool IsEmpty() const; // returns true if length == 0 + bool IsEmpty() const; // returns true if length == 0 bool IsNotEmpty() const; // returns true if length > 0 void Empty(); // sets length to zero - if possible, memory is retained @@ -1244,7 +1238,7 @@ public: /* Description: - Compare this string and other_string by normalizing (NFC) + Compare this string and other_string by normalizing (NFC) and using invariant culture ordering. Parameters: other_string - [in] @@ -1252,19 +1246,19 @@ public: Remarks: 1) Ordinal compares are the fastest. 2) Equal(...) is faster than Compare(...) - */ + */ int Compare( const ON_String& other_string, const class ON_Locale& locale, bool bIgnoreCase ) const; - + int Compare( const char* other_string, const class ON_Locale& locale, bool bIgnoreCase ) const; - + /* Description: Compare string1 and string2 by normalizing (NFC) and using invariant culture ordering. @@ -1275,7 +1269,7 @@ public: Remarks: 1) Ordinal compares are the fastest. 2) Equal(...) is faster than Compare(...) - */ + */ static int Compare( const char* string1, const char* string2, @@ -1352,17 +1346,17 @@ public: 3) If locale, linguistic issues, UTF-8 encoding issues or unicode normalization or collation issues need to be considered, then CompareOrdinal() is the wrong function to use. - */ + */ int CompareOrdinal( const ON_String& other_string, bool bIgnoreCase ) const; - + int CompareOrdinal( const char* other_string, bool bIgnoreCase ) const; - + /* Description: Compare string1 and string2 unsigned byte by unsigned byte. @@ -1425,7 +1419,7 @@ public: /* Description: Compare this string and other_path as file system paths using - appropriate tests for the current operating system. + appropriate tests for the current operating system. Parameters: other_path - [in] Remarks: @@ -1504,9 +1498,9 @@ public: /* Description: - Compare this string and other_name as a name attribute of an object + Compare this string and other_name as a name attribute of an object like ON_3dmObjectAttributes.m_name, ON_Layer.m_name, and so on. - These comparisons ignore case and use appropriate string normalization. + These comparisons ignore case and use appropriate string normalization. Parameters: other_name - [in] null terminated string @@ -1520,9 +1514,9 @@ public: /* Description: - Compare this string and other_name as a name attribute of an object + Compare this string and other_name as a name attribute of an object like ON_3dmObjectAttributes.m_name, ON_Layer.m_name, and so on. - These comparisons ignore case and use appropriate string normalization. + These comparisons ignore case and use appropriate string normalization. Parameters: name1 - [in] null terminated string @@ -1601,7 +1595,7 @@ public: c - [in] If c is in the range A to Z or a to z, the map specified by map_type is applied. All other values of c are - unchanged. + unchanged. Remarks: MapCharacterOrdinal is not appropriate for general string mapping. */ @@ -1630,7 +1624,7 @@ public: map_type - [in] Returns: Number of mapped_string[] elements that were mapped from string[]. - + When the number of string[] input elements is >= mapped_string_capacity, mapped_string_capacity mapped_string[] elements are set and mapped_string_capacity is returned. @@ -1665,8 +1659,8 @@ public: Parameters: locale - [in] Locale to use when converting case. It is common to pass one of - the preset locales ON_Locale::Ordinal, ON_Locale::InvariantCulture, - or ON_Locale::m_CurrentCulture. + the preset locales ON_Locale::Ordinal, ON_Locale::InvariantCulture, + or ON_Locale::m_CurrentCulture. map_type - [in] selects the mapping @@ -1676,9 +1670,9 @@ public: element_count - [in] The number of char elements to map from input string[]. - + If element_count < 1, then string[] must be null terminated and - ON_wString::Length(string)+1 elements are mapped. + ON_wString::Length(string)+1 elements are mapped. The +1 insures the output is null terminated. mapped_string - [out] @@ -1691,7 +1685,7 @@ public: Returns: If mapped_string_capacity > 0, then the number elements set in mapped_string[] - is returned. + is returned. If mapped_string_capacity == 0, then the number elements required to perform the mapping is returned. @@ -1824,10 +1818,10 @@ public: false: Use 0-9, a - b true: Use 0-9, A - F bReverse - [in] - false: + false: The digits in the string will be in the order bytes[0], bytes[1], ..., bytes[byte_count-1]. - true: + true: The digits in the string will be in the order bytes[byte_count-1], ..., bytes[1], bytes[0]. */ @@ -1836,12 +1830,12 @@ public: size_t byte_count, bool bCapitalDigits, bool bReverse - ); + ); /* Parameters: format - [in] - Format control. + Format control. Positional parameters of the form %N$x where N >= 1 and x is the standard format specification are supported. Avoid using %S (capital S). See the Remarks for details. @@ -1891,7 +1885,7 @@ public: ); bool FormatVargs( - const char* format, + const char* format, va_list args ); @@ -1902,11 +1896,11 @@ public: /* Description: - A platform independent, secure, culture invariant way to format a char string. - This function is provide to be used when it is critical that + A platform independent, secure, culture invariant way to format a char string. + This function is provide to be used when it is critical that the formatting be platform independent, secure and culture invariant. Parameters: - buffer - [out] + buffer - [out] not null buffer_capacity - [in] > 0 @@ -1915,7 +1909,7 @@ public: Avoid using %S (capital S). See the Remarks for details. ... - [in] Returns: - >= 0: + >= 0: The number of char elements written to buffer[], not including the null terminator. A null terminator is always added (buffer[returned value] = 0). The last element of buffer[] is always set to zero (buffer[buffer_capacity-1] = 0). @@ -1924,8 +1918,8 @@ public: Remarks: The way Windows handles the %S (capital S) format parameter depends on locale and code page settings. It is strongly recommended that you never use %S to - include any string that may possibly contain elements with values > 127. - The following examples illustrate a way to predictably use UTF-8 and wchar_t + include any string that may possibly contain elements with values > 127. + The following examples illustrate a way to predictably use UTF-8 and wchar_t parameters in buffers of the other element type. const char* utf8_string = ...; @@ -1941,7 +1935,7 @@ public: ON_String::Format(char_buffer, char_buffer_capacity, "%s", ON_String(wide_string)); */ static int ON_VARGS_FUNC_CDECL FormatIntoBuffer( - char* buffer, + char* buffer, size_t buffer_capacity, const char* format, ... @@ -1968,7 +1962,7 @@ public: /* Returns: - >= 0: + >= 0: Number of char elements in the formatted string, not including the null terminator. < 0: Invalid input @@ -2079,7 +2073,7 @@ public: double* value ); - + // Low level access to string contents as character array char* ReserveArray(size_t); // make sure internal array has at least // the requested capacity. @@ -2107,7 +2101,7 @@ public: /* OBSOLETE - use ON_FileSystemPath::SplitPath */ - static void SplitPath( + static void SplitPath( const char* path, ON_String* drive, ON_String* dir, @@ -2141,16 +2135,16 @@ public: UTF8 = 3, /// - /// The sequence of byte values could be a UTF-8 encoding with oversized + /// The sequence of byte values could be a UTF-8 encoding with oversized /// encodings or surrogate pair code points. /// SloppyUTF8 = 4, /// /// The sequence of byte values could be a BIG5 encode string - /// that may also contain single byte ASCII code points. + /// that may also contain single byte ASCII code points. /// BIG5 is a double byte encoding and the first byte - /// always has the high bit set. + /// always has the high bit set. /// BIG5andASCII = 5, }; @@ -2255,14 +2249,14 @@ protected: /* Returns: - True if lhs and rhs are identical as arrays of char elements. + True if lhs and rhs are identical as arrays of char elements. */ ON_DECL bool operator==( const ON_String& lhs, const ON_String& rhs ); /* Returns: - True if lhs and rhs are not identical as arrays of char elements. + True if lhs and rhs are not identical as arrays of char elements. */ ON_DECL bool operator!=(const ON_String& lhs, const ON_String& rhs); @@ -2297,14 +2291,14 @@ bool operator>=(const ON_String& lhs, const ON_String& rhs); /* Returns: - True if lhs and rhs are identical as arrays of char elements. + True if lhs and rhs are identical as arrays of char elements. */ ON_DECL bool operator==( const ON_String& lhs, const char* rhs ); /* Returns: - True if lhs and rhs are not identical as arrays of char elements. + True if lhs and rhs are not identical as arrays of char elements. */ ON_DECL bool operator!=(const ON_String& lhs, const char* rhs); @@ -2339,14 +2333,14 @@ bool operator>=(const ON_String& lhs, const char* rhs); /* Returns: - True if lhs and rhs are identical as arrays of char elements. + True if lhs and rhs are identical as arrays of char elements. */ ON_DECL bool operator==( const char* lhs, const ON_String& rhs ); /* Returns: - True if lhs and rhs are not identical as arrays of char elements. + True if lhs and rhs are not identical as arrays of char elements. */ ON_DECL bool operator!=(const char* lhs, const ON_String& rhs); @@ -2380,9 +2374,9 @@ ON_DECL bool operator>=(const char* lhs, const ON_String& rhs); /// -/// A wide character string. -/// The default encoding is the encoding the compiler uses for wchar_t* s = L"..."; strings. -/// This is typically 2 byte wchar_t UTF-16 on Windows and 4 byte wchar_t UTF-32 on MacOS. +/// A wide character string. +/// The default encoding is the encoding the compiler uses for wchar_t* s = L"..."; strings. +/// This is typically 2 byte wchar_t UTF-16 on Windows and 4 byte wchar_t UTF-32 on MacOS. /// However, some MacOS SDK functions return 4 byte wchar_t UTF-16 strings. /// class ON_CLASS ON_wString @@ -2446,27 +2440,27 @@ public: RichText = 90, /// - /// The WideChar string as an XML value with special characters encoded in the &amp; format + /// The WideChar string as an XML value with special characters encoded in the &amp; format /// and code points above basic latin UTF-16 encoded. /// XML = 101, /// - /// The WideChar string as an XML value with special characters encoded in the &amp; format + /// The WideChar string as an XML value with special characters encoded in the &amp; format /// and code points above basic latin encoded in the &#hhhh; format /// using lower case hex digits (0123456789abcdef). /// XMLalternate1 = 102, /// - /// The WideChar string as an XML value with special characters encoded in the &amp; format + /// The WideChar string as an XML value with special characters encoded in the &amp; format /// and code points above basic latin encoded in the hexadecimal &#xhhhh; format /// with upper case hex digits (0123456789ABCDEF). /// XMLalternate2 = 103, /// - /// The WideChar string as an XML value with special characters and code points above + /// The WideChar string as an XML value with special characters and code points above /// basic latin encoded in the decimal code point &#nnnn; format. /// XMLalternate3 = 104, @@ -2481,9 +2475,9 @@ public: /// /// Get a rich text example. /// - /// - /// The rich text font name. - /// This name is not well defined and the best choice can be platform specific. + /// + /// The rich text font name. + /// This name is not well defined and the best choice can be platform specific. /// For Windows use the LOGFONT always works. /// For Mac OS the font family name generally works. /// If you have an ON_Font, then ON_Font.RichTextFontName() or ON_Font.FontQuartet().QuartetName() are good choices. @@ -2493,7 +2487,7 @@ public: /// Pass true to include a rich text bold-italic face line. /// Pass true to include both plain and underline in the sample. /// - /// A rich text example using the specified face and the specified + /// A rich text example using the specified face and the specified /// static const ON_wString RichTextExample( ON_wString rich_text_font_name, @@ -2506,7 +2500,7 @@ public: /// /// Get a rich text example. /// - /// + /// /// Every rich text face supported by font will be in the sample. /// /// @@ -2519,7 +2513,7 @@ public: /// /// Get a rich text example. /// - /// + /// /// Every rich text face supported by the font quartet will be in the sample. /// /// @@ -2642,10 +2636,10 @@ public: /// DEGREE SIGN U+00B0 (X°) (Rhino annotation degree symbol) static const wchar_t DegreeSymbol = (wchar_t)ON_UnicodeCodePoint::ON_DegreeSymbol; - + /// Place of interest sign/looped square. (Used to indicate the command key on Mac) static const wchar_t PlaceOfInterestSign = (wchar_t)ON_UnicodeCodePoint::ON_PlaceOfInterestSign; - + /// PLUS-MINUS SIGN U+00B1 (±) (Rhino annotation plus/minus symbol) static const wchar_t PlusMinusSymbol = (wchar_t)ON_UnicodeCodePoint::ON_PlusMinusSymbol; @@ -2796,7 +2790,7 @@ public: ); static unsigned DecimalDigitFromWideChar( - wchar_t c, + wchar_t c, bool bAcceptOrdinaryDigit, bool bAcceptSuperscriptDigit, bool bAcceptSubscriptDigit, @@ -2904,17 +2898,13 @@ public: ON_wString() ON_NOEXCEPT; ON_wString( const ON_wString& ); - enum : int + enum : size_t { - // This ON_String::MaximumStringLength value of 100,000,000 - // is used for string length sanity checking in both ON_String and - // ON_wString. - // The design of the ON_String and ON_wString classes could support - // string lengths up to 0xFFFFFFFEU = 4,294,967,294 but - // a cap of 100 million seems generous enough for current usage - // and is small enough to detect many corrupt memory - // situations. - // This value is used for both ON_String and ON_wString. + /// + /// The design of the ON_String and ON_wString classes supports + /// string lengths up to 0xFFFFFFFEU = 4,294,967,294 + /// This value is used for both ON_String and ON_wString. + /// MaximumStringLength = ON_String::MaximumStringLength }; @@ -2961,7 +2951,7 @@ public: True - this is valid. False There is a serious memory corruption bug in your code. - This was not valid and converted to ON_wString::EmptyString to prevent future crashes. + This was not valid and converted to ON_wString::EmptyString to prevent future crashes. */ bool IsValid( bool bLengthTest @@ -3138,7 +3128,7 @@ public: Remarks: 1) Ordinal compares are the fastest. 2) Equal(...) is faster than Compare(...) - */ + */ int Compare( const ON_wString& other_string, const class ON_Locale& locale, @@ -3150,7 +3140,7 @@ public: const class ON_Locale& locale, bool bIgnoreCase ) const; - + /* Description: Compare string1 and string2 by normalizing (NFC) and using invariant culture ordering. @@ -3161,7 +3151,7 @@ public: Remarks: 1) Ordinal compares are the fastest. 2) Equal(...) is faster than Compare(...) - */ + */ static int Compare( const wchar_t* string1, const wchar_t* string2, @@ -3197,12 +3187,12 @@ public: const class ON_Locale& locale, bool bIgnoreCase ); - + bool EqualOrdinal( const ON_wString& other_string, bool bOrdinalIgnoreCase ) const; - + bool EqualOrdinal( const wchar_t* other_string, bool bOrdinalIgnoreCase @@ -3239,17 +3229,17 @@ public: 3) If locale, linguistic issues, UTF-8 encoding issues or unicode normalization or collation issues need to be considered, then CompareOrdinal() is the wrong function to use. - */ + */ int CompareOrdinal( const ON_wString& other_string, bool bOrdinalIgnoreCase ) const; - + int CompareOrdinal( const wchar_t* other_string, bool bOrdinalIgnoreCase ) const; - + /* Description: Compare this string1 and string2 wchar_t element by wchar_t element. @@ -3377,9 +3367,9 @@ public: /* Description: - Compare this string and other_name as a name attribute of an object + Compare this string and other_name as a name attribute of an object like ON_3dmObjectAttributes.m_name, ON_Layer.m_name, and so on. - These comparisons ignore case and use appropriate string normalization. + These comparisons ignore case and use appropriate string normalization. Parameters: other_name - [in] null terminated string @@ -3393,9 +3383,9 @@ public: /* Description: - Compare this string and other_name as a name attribute of an object + Compare this string and other_name as a name attribute of an object like ON_3dmObjectAttributes.m_name, ON_Layer.m_name, and so on. - These comparisons ignore case and use appropriate string normalization. + These comparisons ignore case and use appropriate string normalization. Parameters: name1 - [in] null terminated string @@ -3460,7 +3450,7 @@ public: Description: Replaces all %xx where xx a two digit hexadecimal number, with a single character. Returns false if the original - string contained + string contained */ bool UrlDecode(); @@ -3472,7 +3462,7 @@ public: Parameters: token - [in] whitespace - [in] if not null, this is a 0 terminated - string that lists the characters considered to be + string that lists the characters considered to be white space. If null, then (1,2,...,32,127) is used. Returns: Number of whitespace characters replaced. @@ -3486,7 +3476,7 @@ public: Removes all whitespace characters from the string. Parameters: whitespace - [in] if not null, this is a zero-terminated - string that lists the characters considered to be + string that lists the characters considered to be white space. If null, then (1,2,...,32,127) is used. Returns: Number of whitespace characters removed. @@ -3496,14 +3486,14 @@ public: int RemoveWhiteSpace( const wchar_t* whitespace = 0 ); /* - Parameters: + Parameters: prefix - [in] locale - [in] When no local is available, pass ON_Locale::Ordinal. bIgnoreCase - [in] true to ignore case. Returns: - If the string begins with prefix, the returned string has prefix removed. + If the string begins with prefix, the returned string has prefix removed. Otherwise the returned string is identical to the string. */ const ON_wString RemovePrefix( @@ -3513,14 +3503,14 @@ public: ) const; /* - Parameters: + Parameters: suffix - [in] locale - [in] When no local is available, pass ON_Locale::Ordinal. bIgnoreCase - [in] true to ignore case. Returns: - If the string ends with suffix, the returned string has suffix removed. + If the string ends with suffix, the returned string has suffix removed. Otherwise the returned string is identical to the string. */ const ON_wString RemoveSuffix( @@ -3557,7 +3547,7 @@ public: Parameters: c - [in] If sizeof(wchar_t) >= 2 and c is not a value used int surrogate pairs, - the map specified by map_type is applied. If c is a value used in + the map specified by map_type is applied. If c is a value used in surrogate pairs, the value is not changed. Remarks: 1) MapCharacterOrdinal is not appropriate for general string mapping @@ -3586,7 +3576,7 @@ public: mapped_string - [out] mapped_string_capacity - [in] number of available elements in mapped_string[]. - mapped_string_capacity must be >= mapped_element_count where + mapped_string_capacity must be >= mapped_element_count where mapped_element_count = (element_count >= 0) element_count ? ON_wString::Length(string). map_type - [in] Returns: @@ -3621,8 +3611,8 @@ public: Parameters: locale - [in] Locale to use when converting case. It is common to pass one of - the preset locales ON_Locale::Ordinal, ON_Locale::InvariantCulture, - or ON_Locale::m_CurrentCulture. + the preset locales ON_Locale::Ordinal, ON_Locale::InvariantCulture, + or ON_Locale::m_CurrentCulture. map_type - [in] selects the mapping @@ -3632,9 +3622,9 @@ public: element_count - [in] The number of wchar_t elements to map from input string[]. - + If element_count < 1, then string[] must be null terminated and - ON_wString::Length(string)+1 elements are mapped. + ON_wString::Length(string)+1 elements are mapped. The +1 insures the output is null terminated. mapped_string - [out] @@ -3647,7 +3637,7 @@ public: Returns: If mapped_string_capacity > 0, then the number elements set in mapped_string[] - is returned. + is returned. If mapped_string_capacity == 0, then the number elements required to perform the mapping is returned. @@ -3677,7 +3667,7 @@ public: // upper/lower/reverse conversion /*ON_DEPRECATED */ void MakeUpper(); /*ON_DEPRECATED */ void MakeLower(); - + ON_wString Reverse() const; /* @@ -3690,7 +3680,7 @@ public: ' (apostrophe or single quote) is replaced with ' " (double-quote) is replaced with " - Optionally, unocode code points U+hhhh where hhhh >= 0x80 are replaced + Optionally, unocode code points U+hhhh where hhhh >= 0x80 are replaced with &#xhhhh; using the minimal number of hex digits. Parameters: bUseUnicodeCodePointsForSpecialCharacters - [in] @@ -3703,7 +3693,7 @@ public: high quality XML reader, pass false. (The XMLspecification supports text files that are UTF=8, UTF-18, or UTF-32 encoded.) Returns: - A string with every instance of an xml special character replaced + A string with every instance of an xml special character replaced with its xml encoding and, optionally, every code point > 127 replaced with &xhhhh;. */ @@ -3772,7 +3762,7 @@ public: &#xhhhh; (hhhh = any muber of hexadecimal digits) Parameters: buffer - [in] - buffer to parse. + buffer to parse. The first character of buffer[] must be the leading ampersand. The second character of buffer[] must be the number sign. The buffer must include the terminating semicolon. @@ -3941,9 +3931,9 @@ public: Parameters: c - [in] utf8_single_byte_ct must have a value between 0 and 0x7F. - When w is a 2 byte UTF-16 wchar_t value (like Microsoft's wchar_t), + When w is a 2 byte UTF-16 wchar_t value (like Microsoft's wchar_t), it must be in the range 0 to 0xD7FF or 0xE000 to 0xFFFF. - When w is a 4 byte UTF-32 wchar_t value (like Apple's wchar_t), + When w is a 4 byte UTF-32 wchar_t value (like Apple's wchar_t), it must be in the range 0 to 0xD7FF or 0xE000 to 0x10FFFF. Returns: Number of characters removed. @@ -4126,7 +4116,7 @@ public: If 0, then : (colon) is used. Returns: A string value for the current coordinated universal time (UTC). - */ + */ static const ON_wString FromCurrentCoordinatedUniversalTime( ON_DateFormat date_format, ON_TimeFormat time_format, @@ -4162,7 +4152,7 @@ public: wchar_t date_time_separator, wchar_t time_separator ); - + /* Description: Get the Gregorian calendar date and time as a string. @@ -4295,7 +4285,7 @@ public: Returns: A wide string encoding of the Unicode code points. Remarks: - If more control over the conversion process is required, + If more control over the conversion process is required, then use ON_ConvertUTF32ToWideChar(). */ static const ON_wString FromUnicodeCodePoints( @@ -4320,10 +4310,10 @@ public: false: Use 0-9, a - b true: Use 0-9, A - F bReverse - [in] - false: + false: The digits in the string will be in the order bytes[0], bytes[1], ..., bytes[byte_count-1]. - true: + true: The digits in the string will be in the order bytes[byte_count-1], ..., bytes[1], bytes[0]. */ @@ -4391,10 +4381,10 @@ public: Description: A platform independent, secure, culture invariant way to format a wchar_t string with support for positional format parameters. - This function is designed to be used when it is critical that + This function is designed to be used when it is critical that the formatting be platform independent, secure and culture invariant. Parameters: - buffer - [out] + buffer - [out] not null buffer_capacity - [in] > 0 @@ -4403,7 +4393,7 @@ public: Avoid using %S (capital S). See the Remarks for details. ... - [in] Returns: - >= 0: + >= 0: The number of wchar_t elements written to buffer[], not including the null terminator. A null terminator is always added (buffer[returned value] = 0). The last element of buffer[] is always set to zero (buffer[buffer_capacity-1] = 0). @@ -4429,7 +4419,7 @@ public: ON_String::Format(char_buffer, char_buffer_capacity, "%s", ON_String(wide_string)); */ static int ON_VARGS_FUNC_CDECL FormatIntoBuffer( - wchar_t* buffer, + wchar_t* buffer, size_t buffer_capacity, const wchar_t* format, ... @@ -4483,7 +4473,7 @@ public: bProper - [in] When in doubt, pass true. If true, then proper fractions will be returned when abs(numerator)>=abs(denominator). - For example, if bProper is true, then 4/3 is converted to 1-1/3. + For example, if bProper is true, then 4/3 is converted to 1-1/3. The symbol between the whole number and the proper fraction is specified by mixed_fraction_separator_code_point. proper_fraction_separator_cp - [in] @@ -4546,12 +4536,12 @@ public: A string (digits, signs, parenthesis). bUseVulgarFractionCodePoints - [in] If true and if Unicode code point exists for the fraction (halves, thirds, fourths, fifths, sixths, eights, ...), - that code point will be used; + that code point will be used; Returns: A Unicode encoding of the fraction numerator/denominator. */ static const ON_wString FormatToVulgarFraction( - const ON_wString numerator, + const ON_wString numerator, const ON_wString denominator ); @@ -4850,14 +4840,14 @@ protected: /* Returns: - True if lhs and rhs are identical as arrays of wchar_t elements. + True if lhs and rhs are identical as arrays of wchar_t elements. */ ON_DECL bool operator==( const ON_wString& lhs, const ON_wString& rhs ); /* Returns: - True if lhs and rhs are not identical as arrays of wchar_t elements. + True if lhs and rhs are not identical as arrays of wchar_t elements. */ ON_DECL bool operator!=(const ON_wString& lhs, const ON_wString& rhs); @@ -4892,14 +4882,14 @@ bool operator>=(const ON_wString& lhs, const ON_wString& rhs); /* Returns: - True if lhs and rhs are identical as arrays of wchar_t elements. + True if lhs and rhs are identical as arrays of wchar_t elements. */ ON_DECL bool operator==( const ON_wString& lhs, const wchar_t* rhs ); /* Returns: - True if lhs and rhs are not identical as arrays of wchar_t elements. + True if lhs and rhs are not identical as arrays of wchar_t elements. */ ON_DECL bool operator!=(const ON_wString& lhs, const wchar_t* rhs); @@ -4934,14 +4924,14 @@ bool operator>=(const ON_wString& lhs, const wchar_t* rhs); /* Returns: - True if lhs and rhs are identical as arrays of wchar_t elements. + True if lhs and rhs are identical as arrays of wchar_t elements. */ ON_DECL bool operator==( const wchar_t* lhs, const ON_wString& rhs ); /* Returns: - True if lhs and rhs are not identical as arrays of wchar_t elements. + True if lhs and rhs are not identical as arrays of wchar_t elements. */ ON_DECL bool operator!=(const wchar_t* lhs, const ON_wString& rhs); @@ -4986,10 +4976,10 @@ Description: bool bEqualNameHash = ON_NameHash::Create(parent_id1,name1) == ON_NameHash::Create(parent_id2,name2); bool bEqualParentId = (parent_id1 == parent_id2) bool bEqualAttributeName = ON_String::EqualAttributeName(name1,name2); - + If (bEqualParentId && bEqualAttributeName) is true, then bEqualNameHash is true. If bEqualParentId is false, then bEqualNameHash is false. - With probability 1-epsilon, if bEqualAttributeName is false, then bEqualNameHash is false, + With probability 1-epsilon, if bEqualAttributeName is false, then bEqualNameHash is false, where epsilon is an extremely tiny number. */ class ON_CLASS ON_NameHash @@ -5063,7 +5053,7 @@ public: ); static ON_NameHash Create( - const ON_UUID& parent_id, + const ON_UUID& parent_id, const ON_wString& name ); static ON_NameHash Create( @@ -5116,7 +5106,7 @@ public: bool bIgnoreCase ); static ON_NameHash Create( - const ON_UUID& parent_id, + const ON_UUID& parent_id, const ON_wString& name, bool bIgnoreCase ); @@ -5223,7 +5213,7 @@ public: ON__UINT32 NameCRC(ON__UINT32 current_remainder) const; ON_UUID ParentId() const; - + private: enum : unsigned int @@ -5241,7 +5231,7 @@ private: // m_sha1_hash = SHA-1 hash of ordinal minimum mapped unicode (UTF-32) code points // If the name is empty, m_length = 0 and m_sha1_hash = ON_SHA1_Hash::EmptyContentHash. // If the name is not valid, m_length = 0 and m_sha1_hash = ON_SHA1_Hash::ZeroDigest. - ON_SHA1_Hash m_sha1_hash = ON_SHA1_Hash::ZeroDigest; + ON_SHA1_Hash m_sha1_hash = ON_SHA1_Hash::ZeroDigest; // When names appear in a tree structure, m_parent_id identifies the // parent node. @@ -5251,7 +5241,7 @@ public: /* Description: Internal_CreateFromDotNet() is public for technical reasons. It is used - in Rhino Common p-invoke code that provides a .NET interface to the + in Rhino Common p-invoke code that provides a .NET interface to the services ON_NameHash provided by the ON_NameHash class. This function should be ignored and never called from ordinary C++ code. If you choose to ignore the preceding admonition, you will have to read @@ -5323,7 +5313,7 @@ bool operator>=( class ON_CLASS ON_UnitSystem { public: - // Default construction sets this to ON_UnitSystem::Meters + // Default construction sets this to ON_UnitSystem::Meters ON_UnitSystem() = default; ~ON_UnitSystem() = default; @@ -5344,7 +5334,7 @@ public: custom_unit_name - [in] length unit name (no spaces) meters_per_custom_unit - [in] - a positive number + a positive number Example: // 1 League = 5556 meters const ON_UnitSystem Leagues = ON_UnitSystem::CreateCustomUnitSystem(L"Leagues", 1.0/5556.0); @@ -5403,14 +5393,14 @@ public: bool operator!=(const ON_UnitSystem&) const; /* - Returns + Returns False if UnitSystem() is ON::LengthUnitSystem::Unset. False if UnitSystem() is ON::LengthUnitSystem::CustomUnits and MetersPerUnits() is not positive. True if UnitSystem() is ON::LengthUnitSystem::None. True otherwise. See Also: IsSet() - */ + */ bool IsValid() const; bool Read( class ON_BinaryArchive& ); @@ -5443,7 +5433,7 @@ public: custom_unit_name - [in] length unit name (no spaces) meters_per_custom_unit - [in] - a positive number + a positive number Example: // 1 League = 5556 meters ON_UnitSystem Leagues; @@ -5533,7 +5523,7 @@ private: ON::LengthUnitSystem m_unit_system = ON::LengthUnitSystem::Meters; unsigned int m_reserved = 0; - // The m_meters_per_custom_unit and m_custom_unit_name values apply when + // The m_meters_per_custom_unit and m_custom_unit_name values apply when // m_unit_system = ON::LengthUnitSystem::CustomUnits. // In all other cases they should be ignored. double m_meters_per_custom_unit = 1.0; // 1 meter = m_meters_per_custom_unit custom units diff --git a/opennurbs_subd.cpp b/opennurbs_subd.cpp index fd8c1c88..7eb685e6 100644 --- a/opennurbs_subd.cpp +++ b/opennurbs_subd.cpp @@ -1652,6 +1652,20 @@ ON_SubDComponentId::ON_SubDComponentId(ON_SubDFacePtr fptr, unsigned face_corner } } +ON_SubDComponentId::ON_SubDComponentId(unsigned face_id, ON__UINT_PTR face_dir, ON_SubDFaceCornerDex face_cdex) +{ + if (face_id > 0) + { + m_id = face_id; + Internal_SetType(ON_SubDComponentPtr::Type::Face); + Internal_SetDir(0 == face_dir ? 0u : 1u); + if (face_cdex.IsSet() && face_cdex.EdgeCount() < 4096u) + { + Internal_SetValueA(face_cdex.CornerIndex()); + Internal_SetValueB(face_cdex.EdgeCount()); + } + } +} int ON_SubDComponentId::CompareTypeAndId(const ON_SubDComponentId& lhs, const ON_SubDComponentId& rhs) @@ -1772,6 +1786,39 @@ unsigned ON_SubDComponentId::ComponentDirection() const return (m_type_and_dir & ON_SubDComponentId::bits_dir_mask); } +const ON_wString ON_SubDComponentId::ToString(bool bUnsetIsEmptyString, bool bDirectionPrefix) const +{ + ON_wString str; + const unsigned id = this->ComponentId(); + if (id > 0) + { + const char prefix = bDirectionPrefix ? (1 == this->ComponentDirection() ? '-' : '+') : 0; + switch (this->ComponentType()) + { + + case ON_SubDComponentPtr::Type::Vertex: + str = (0 == prefix) ? ON_wString::FormatToString(L"v%u", id) : ON_wString::FormatToString(L"%cv%u", prefix, id); + break; + + case ON_SubDComponentPtr::Type::Edge: + str = (0 == prefix) ? ON_wString::FormatToString(L"e%u", id) : ON_wString::FormatToString(L"%ce%u", prefix, id); + break; + + case ON_SubDComponentPtr::Type::Face: + { + const ON_SubDFaceCornerDex cdex = this->FaceCornerDex(); + if (cdex.IsSet()) + str = (0 == prefix) ? ON_wString::FormatToString(L"f%u.%u", id, cdex.CornerIndex()) : ON_wString::FormatToString(L"%cf%u.%u", prefix, id, cdex.CornerIndex()); + else + str = (0 == prefix) ? ON_wString::FormatToString(L"f%u", id) : ON_wString::FormatToString(L"%cf%u", prefix, id); + } + break; + + } + } + return (str.IsNotEmpty() || bUnsetIsEmptyString) ? str : ON_wString("unset"); +} + ON_SubDComponentPtr::Type ON_SubDComponentId::ComponentType() const { return ON_SubDComponentPtr::ComponentPtrTypeFromUnsigned(m_type_and_dir & ON_SubDComponentId::bits_type_mask); @@ -1841,6 +1888,11 @@ const ON_SubDComponentPtr ON_SubDComponentId::ComponentPtr(const class ON_SubD& return ON_SubDComponentPtr::Null; } +unsigned ON_SubDComponentId::VertexId() const +{ + return this->IsVertexId() ? this->ComponentId() : 0u; +} + const ON_SubDVertex* ON_SubDComponentId::Vertex(const class ON_SubD& subd) const { return this->ComponentPtr(subd).Vertex(); @@ -1861,6 +1913,11 @@ const ON_SubDVertexPtr ON_SubDComponentId::VertexPtr(const class ON_SubD* subd) return (nullptr != subd) ? this->ComponentPtr(*subd).VertexPtr() : ON_SubDVertexPtr::Null; } +unsigned ON_SubDComponentId::EdgeId() const +{ + return this->IsEdgeId() ? this->ComponentId() : 0u; +} + const ON_SubDEdge* ON_SubDComponentId::Edge(const class ON_SubD& subd) const { return this->ComponentPtr(subd).Edge(); @@ -1881,6 +1938,11 @@ const ON_SubDEdgePtr ON_SubDComponentId::EdgePtr(const class ON_SubD* subd) cons return (nullptr != subd) ? this->ComponentPtr(*subd).EdgePtr() : ON_SubDEdgePtr::Null; } +unsigned ON_SubDComponentId::FaceId() const +{ + return this->IsFaceId() ? this->ComponentId() : 0u; +} + const ON_SubDFace* ON_SubDComponentId::Face(const class ON_SubD& subd) const { return this->ComponentPtr(subd).Face(); @@ -7324,6 +7386,31 @@ const ON_SubDEdge* ON_SubDFace::QuadOppositeEdge( } +unsigned int ON_SubDFace::GetCornerEdges( + const ON_SubDVertex* corner_vertex, + ON_SubDEdgePtr& entering_edge, + ON_SubDEdgePtr& leaving_edge +) const +{ + for (;;) + { + const unsigned edge_count = this->EdgeCount(); + if (edge_count < 3) + break; + if (nullptr == corner_vertex) + break; + const unsigned fvi = this->VertexIndex(corner_vertex); + if (fvi >= edge_count) + break; + entering_edge = this->EdgePtr((fvi + edge_count - 1) % edge_count); + leaving_edge = this->EdgePtr(fvi); + return entering_edge.IsNotNull() && leaving_edge.IsNotNull(); + } + entering_edge = ON_SubDEdgePtr::Null; + leaving_edge = ON_SubDEdgePtr::Null; + return ON_UNSET_UINT_INDEX; +} + const class ON_SubDEdge* ON_SubDFace::Edge( unsigned int i ) const @@ -11564,9 +11651,149 @@ ON_COMPONENT_INDEX ON_SubD::ComponentIndex() const return ON_Geometry::ComponentIndex(); } + +bool ON_ObjRefEvaluationParameter::SetFromSubDComponentParameter( + const ON_SubDComponentParameter& cp +) +{ + switch (cp.ComponentType()) + { + case ON_SubDComponentPtr::Type::Vertex: + { + this->m_t_type = 9; + const ON_SubDComponentId vertex_id = cp.ComponentIdAndType(); + const ON_SubDComponentId edge_id = cp.VertexEdge(); + const ON_SubDComponentId face_id = cp.VertexFace(); + this->m_t_ci.Set(ON_COMPONENT_INDEX::subd_vertex, vertex_id.ComponentId()); + this->m_t[0] = (double)edge_id.ComponentId(); + this->m_t[1] = (double)face_id.ComponentId(); + this->m_t[2] = 0.0; + this->m_t[3] = 0.0; + } + break; + + case ON_SubDComponentPtr::Type::Edge: + { + this->m_t_type = 9; + const ON_SubDComponentId edge_id = cp.ComponentIdAndType(); + const ON_SubDComponentId face_id = cp.EdgeFace(); + const double ep = cp.EdgeParameter(); + this->m_t_ci.Set(ON_COMPONENT_INDEX::subd_edge, edge_id.ComponentId()); + this->m_t[0] = (double)face_id.ComponentId(); + this->m_t[1] = ep; + this->m_t[2] = 0.0; + this->m_t[3] = 0.0; + this->m_s[0] = ON_Interval(0.0, 1.0); + } + break; + + case ON_SubDComponentPtr::Type::Face: + { + this->m_t_type = 9; + const ON_SubDComponentId face_id = cp.ComponentIdAndType(); + const ON_SubDFaceParameter fp = cp.FaceParameter(); + this->m_t_ci.Set(ON_COMPONENT_INDEX::subd_face, face_id.ComponentId()); + this->m_t[0] = (double)fp.FaceCornerDex().CornerIndex(); + this->m_t[1] = (double)fp.FaceCornerDex().EdgeCount(); + this->m_t[2] = fp.FaceCornerParameters().x; + this->m_t[3] = fp.FaceCornerParameters().y; + this->m_s[0] = ON_Interval(0.0, 0.5); + this->m_s[1] = ON_Interval(0.0, 0.5); + } + break; + } + + this->Default(); + return false; +} + +static bool Internal_IsValidUnsigned(double x) +{ + return x >= 0 && x <= ((double)0xFFFFFFFFu) && x == floor(x); +} + +static const ON_SubDComponentId Internal_ComponentIdFromDouble( + ON_SubDComponentPtr::Type component_type, + double component_id_as_double +) +{ + return + (ON_SubDComponentPtr::Type::Unset != component_type && Internal_IsValidUnsigned(component_id_as_double)) + ? ON_SubDComponentId(component_type, (unsigned)component_id_as_double) + : ON_SubDComponentId::Unset; +} + + +bool ON_ObjRefEvaluationParameter::GetSubDComponentParameter( + ON_SubDComponentParameter& cp +) const +{ + cp = ON_SubDComponentParameter::Unset; + if (9 == this->m_t_type) + { + switch (this->m_t_ci.m_type) + { + case ON_COMPONENT_INDEX::TYPE::subd_vertex: + if (false == Internal_IsValidUnsigned(this->m_t[0])) + break; + if (false == Internal_IsValidUnsigned(this->m_t[1])) + break; + if (false == (0.0 == this->m_t[2])) + break; + if (false == (0.0 == this->m_t[3])) + break; + { + const ON_SubDComponentId vertex_id(ON_SubDComponentPtr::Type::Vertex, this->m_t_ci.m_index); + cp = ON_SubDComponentParameter( + vertex_id, + Internal_ComponentIdFromDouble(ON_SubDComponentPtr::Type::Edge, this->m_t[0]), + Internal_ComponentIdFromDouble(ON_SubDComponentPtr::Type::Face, this->m_t[1])); + } + break; + case ON_COMPONENT_INDEX::TYPE::subd_edge: + if (false == Internal_IsValidUnsigned(this->m_t[0])) + break; + if (false == (0.0 <= this->m_t[1] && this->m_t[1] <= 1.0)) + break; + if (false == (0.0 == this->m_t[2])) + break; + if (false == (0.0 == this->m_t[3])) + break; + { + const ON_SubDComponentId edge_id(ON_SubDComponentPtr::Type::Edge, this->m_t_ci.m_index); + cp = ON_SubDComponentParameter( + edge_id, + this->m_t[1], + Internal_ComponentIdFromDouble(ON_SubDComponentPtr::Type::Face, this->m_t[0])); + } + break; + case ON_COMPONENT_INDEX::TYPE::subd_face: + if (false == Internal_IsValidUnsigned(this->m_t[0])) + break; + if (false == Internal_IsValidUnsigned(this->m_t[1])) + break; + if (false == (this->m_t[0] < this->m_t[1])) + break; + if (false == (0.0 <= this->m_t[2] && this->m_t[2] <= 0.5)) + break; + if (false == (0.0 <= this->m_t[3] && this->m_t[3] <= 0.5)) + break; + { + const ON_SubDComponentId face_id(ON_SubDComponentPtr::Type::Face, this->m_t_ci.m_index); + const ON_SubDFaceCornerDex cdex((unsigned)this->m_t[0], (unsigned)this->m_t[1]); + ON_SubDFaceParameter fp(cdex, this->m_t[2], this->m_t[3]); + cp = ON_SubDComponentParameter(face_id, fp); + } + break; + } + } + return cp.IsSet(); +} + //virtual bool ON_SubD::EvaluatePoint( const class ON_ObjRef& objref, ON_3dPoint& P ) const { + P = ON_3dPoint::NanPoint; return false; } @@ -21085,9 +21312,18 @@ bool ON_SubDVertexQuadSector::Subdivide() } bool ON_SubDVertexQuadSector::SubdivideUntilSharpnessIsZero() +{ + unsigned int subdivision_count = 0; + return SubdivideUntilSharpnessIsZero(subdivision_count); +} + +bool ON_SubDVertexQuadSector::SubdivideUntilSharpnessIsZero( + unsigned int& subdivision_count +) { bool rc = true; double maxs = this->MaximumSharpness(); + subdivision_count = 0; if (maxs > 0.0) { // for(i < n) used to prevent infinite looping when this content is not valid. @@ -21095,6 +21331,8 @@ bool ON_SubDVertexQuadSector::SubdivideUntilSharpnessIsZero() for (unsigned i = 0; i < n && maxs > 0.0 && rc; ++i) { rc = Subdivide(); + if (rc) + ++subdivision_count; maxs = this->MaximumSharpness(); } if (rc && false == (0.0 == maxs)) diff --git a/opennurbs_subd.h b/opennurbs_subd.h index 86293782..aa64b732 100644 --- a/opennurbs_subd.h +++ b/opennurbs_subd.h @@ -1182,6 +1182,23 @@ public: /// const ON_2dPoint QuadFaceParameters() const; + /// + /// An evaluation point is at a SubD vertex if IsSet() is true + /// and FaceCornerParameters() = (0,0, 0,0). + /// + /// + /// True if the evaluation point is at a SubD vertex. + /// + const bool AtVertex() const; + + /// + /// An evaluation point is on a subD edge if IsSet() is true and + /// at least one of the values in FaceCornerParameters() is 0.0. + /// + /// + /// True if the evaluation point is on a SubD edge. + /// + const bool OnEdge() const; private: /// @@ -3156,11 +3173,34 @@ public: ON_SubDComponentId(ON_SubDFacePtr fptr); ON_SubDComponentId(const class ON_SubDFace* f, unsigned face_corner_index); ON_SubDComponentId(ON_SubDFacePtr fptr, unsigned face_corner_index); + ON_SubDComponentId(unsigned face_id, ON__UINT_PTR face_dir, ON_SubDFaceCornerDex face_cdex); static int CompareTypeAndId(const ON_SubDComponentId& lhs, const ON_SubDComponentId& rhs); static int CompareTypeAndIdAndDirection(const ON_SubDComponentId& lhs, const ON_SubDComponentId& rhs); static int CompareTypeAndIdFromPointer(const ON_SubDComponentId* lhs, const ON_SubDComponentId* rhs); + /// + /// Returns a string cN where N is ComponentId() and c is v, e or f for a vertex, edge or face component. + /// Examples: + /// If ComponentType is vertex and ComponentId() is 17, then the string will be "v17". + /// If ComponentType is face, ComponentId() is 49, and FaceCornerDex is not set, + /// the string will be "f49". + /// If ComponentType is face, ComponentId() is 49, and FaceCornerDex() is ON_SubDFaceCornerDex(2,3), + /// the string will be "f49.2". + /// + /// + /// If bUnsetIsEmptyString is truen, then an unset component id returns the empty string. + /// Otherwise and unset component id returns "unset". + /// + /// + /// If bDirectionPrefix is true, then the string begins with "+" when + /// ComponentDirection() is 0 and "-" when ComponentDirection() is 1. + /// + /// + const ON_wString ToString( + bool bUnsetIsEmptyString, + bool bDirectionPrefix + ) const; /// /// The type of the referenced component. @@ -3235,16 +3275,19 @@ public: /// const ON_SubDComponentPtr ComponentPtr(const class ON_SubD* subd) const; + unsigned VertexId() const; const ON_SubDVertex* Vertex(const class ON_SubD& subd) const; const ON_SubDVertexPtr VertexPtr(const class ON_SubD& subd) const; const ON_SubDVertex* Vertex(const class ON_SubD* subd) const; const ON_SubDVertexPtr VertexPtr(const class ON_SubD* subd) const; + unsigned EdgeId() const; const ON_SubDEdge* Edge(const class ON_SubD& subd) const; const ON_SubDEdgePtr EdgePtr(const class ON_SubD& subd) const; const ON_SubDEdge* Edge(const class ON_SubD* subd) const; const ON_SubDEdgePtr EdgePtr(const class ON_SubD* subd) const; + unsigned FaceId() const; const ON_SubDFace* Face(const class ON_SubD& subd) const; const ON_SubDFacePtr FacePtr(const class ON_SubD& subd) const; const ON_SubDFace* Face(const class ON_SubD* subd) const; @@ -4840,6 +4883,12 @@ public: ON_SubDComponentParameter(ON_SubDComponentId cid); ON_SubDComponentParameter(ON_SubDComponentPtr cptr); + ON_SubDComponentParameter( + ON_SubDComponentId vertex_id, + ON_SubDComponentId active_edge_id, + ON_SubDComponentId active_face_id + ); + ON_SubDComponentParameter( const class ON_SubDVertex* v, const class ON_SubDEdge* active_edge, @@ -4852,6 +4901,13 @@ public: const class ON_SubDFace* active_face ); + ON_SubDComponentParameter( + ON_SubDComponentId edge_id, + double p, + ON_SubDComponentId active_face_id + ); + + /// /// /// @@ -4885,6 +4941,11 @@ public: const class ON_SubDFace* active_face ); + ON_SubDComponentParameter( + ON_SubDComponentId face_id, + ON_SubDFaceParameter fp + ); + ON_SubDComponentParameter( const ON_SubDFace* face, ON_SubDFaceParameter fp @@ -4947,6 +5008,10 @@ public: /// static int Compare(const ON_SubDComponentParameter* lhs, const ON_SubDComponentParameter* rhs); + const ON_wString ToString(bool bUnsetIsEmptyString) const; + + bool IsSet() const; + const ON_SubDComponentId ComponentIdAndType() const; unsigned ComponentId() const; ON_SubDComponentPtr::Type ComponentType() const; @@ -4957,6 +5022,12 @@ public: /// bool IsVertexParameter() const; + /// + /// If IsVertexParameter() is true, then the vertex's id is returned. + /// Otherwise 0 is returned. + /// + unsigned VertexId() const; + /// /// If this parameter references a vertex and the subd has a vertex /// with this->ComponentId(), then that vertex is returned. @@ -5000,6 +5071,12 @@ public: /// bool IsEdgeParameter() const; + /// + /// If IsEdgeParameter() is true, then the edge's id is returned. + /// Otherwise 0 is returned. + /// + unsigned EdgeId() const; + /// /// If this parameter references an edge and the subd has an edge /// with this->ComponentId(), then that edge is returned. @@ -5048,6 +5125,12 @@ public: /// bool IsFaceParameter() const; + /// + /// If IsFaceParameter() is true, then the face's id is returned. + /// Otherwise 0 is returned. + /// + unsigned FaceId() const; + /// /// If this parameter references a face and the subd has a face /// with this->ComponentId(), then that face is returned. @@ -5584,18 +5667,46 @@ public: double* point_ring, size_t point_ring_capacity, size_t point_ring_stride - ); + ); static unsigned int GetSectorPointRing( bool bSubdivideIfNeeded, const class ON_SubDSectorIterator& sit, ON_SimpleArray& point_ring - ); + ); + + static unsigned int GetSectorPointRing( + const class ON_SubDSectorIterator& sit, + unsigned& subdivision_count, + double* point_ring, + size_t point_ring_capacity, + size_t point_ring_stride + ); + + /// + /// Get a ring of points that can be used to calculate the subdivision and + /// limit points of sit.CenterVertex(). + /// + /// + /// Initialized sector iterator. + /// + /// + /// TNumber of subdivisions performed to get point_ring[] is returned in subdivision_count. + /// + /// + /// The points are returned in point_ring[] + /// + /// + /// Number of points in point_ring[] + /// + static unsigned int GetSectorPointRing( + const class ON_SubDSectorIterator& sit, + unsigned& subdivision_count, + ON_SimpleArray& point_ring + ); /* Parameters: - subd_type - [in] - A quad based subdivision algorithm. bPermitNoSubdivisions - [in] When in doubt, pass false. If bPermitNoSubdivisions is true and no extraordinary components @@ -5609,10 +5720,10 @@ public: component_ring_count - [in] component_ring_count specifies the number of components in the component_ring[] array. component_ring[] - [in] - If vertex0 is null, then component_ring[0] is the central vertex, - component_ring[1] and subsequent components with odd indices are - sector edges, component_ring[2] and subsequent components with even - indices are sector faces, all sorted radially. + component_ring[0] is the central vertex, + component_ring[1] and subsequent components with odd indices are sector edges. sorted radially. + component_ring[2] and subsequent components with even indices are sector faces. + The edges and faces are sorted radially (component_ring[1] and component_ring[3] are edges of component_ring[2], etc). point_ring_stride - [in] point_ring - [out] point locations are returned here. @@ -5635,6 +5746,48 @@ public: size_t point_ring_stride ); + /// + /// Get a ring of points that can be mulitplied by subdivsion and limit point matrices + /// to calculate the subdivision point and limit point for the central vertex in component_ring[0]. + /// No input validation is performed. This function will crash if the input is not valid. + /// Call GetSubdivisionPointRing() if you want a crash proof call. + /// + /// + /// When in doubt, pass false. If bPermitNoSubdivisions is true and no extraordinary components + /// or sharp edges are in the ring, then locations of the input component_ring[] control net points + /// are returned. Otherwise one or more subdivisions are applied to obtain the output ring points. + /// + /// + /// component_ring[0] is the central vertex. + /// component_ring[1] and subsequent components with odd indices are sector edges. + /// component_ring[2] and subsequent components with even indices are sector faces. + /// The edges and faces are sorted radially(component_ring[1] and component_ring[3] are edges of component_ring[2], etc). + /// + /// + /// Number of components in the component_ring[] array. + /// + /// + /// The number of subdivisions used to calculate point_ring[] is returned in subdivision_count. + /// + /// + /// The point ring points are returned in point_ring[] + /// + /// + /// Number of doubles between subsequent points in point_ring[]. + /// + /// + /// If successful, component_ring_count is returned which is the number of points set in point_ring[]. + /// Otherwise 0 is returned. + /// + static unsigned int GetQuadSectorPointRing( + bool bPermitNoSubdivisions, + const class ON_SubDComponentPtr* component_ring, + size_t component_ring_count, + unsigned int& subdivision_count, + double* point_ring, + size_t point_ring_stride + ); + static const class ON_SubDVertex* SubdivideSector( const class ON_SubDVertex* center_vertex, const class ON_SubDComponentPtr* component_ring, @@ -16806,6 +16959,29 @@ public: const ON_SubDEdge* edge ) const; + /// + /// Get the edges in the face's boundary that are on either side of a face corner. + /// + /// + /// Vertex used to identify the face corner. + /// + /// + /// The face's boundary edge ending at corner_vertex is retuned here. + /// + /// + /// The face's boundary edge beginning at corner_vertex is retuned here. + /// + /// + /// If successful, the index of the vertex in the face's vertex list is returned; + /// this value is >=0 and < EdgeCount(). + /// Otherwise ON_UNSET_UINT_INDEX is returned. + /// + unsigned int GetCornerEdges( + const ON_SubDVertex* corner_vertex, + ON_SubDEdgePtr& entering_edge, + ON_SubDEdgePtr& leaving_edge + ) const; + /* Parameters: subdivision_point - [out] @@ -18491,6 +18667,45 @@ public: int increment_direction ); + /* + Description: + Increment the iterator until it reaches a face with + a crease + Parameters: + increment_direction - [in] + > 0 advance next until CurrentEdge(1) is a crease. + <= 0 advance previous until CurrentEdge(0) is a crease. + Returns: + nullptr - the sector has no creases. + not nullptr - incremented to a crease + */ + + /// + /// Increment the iterator until it reaches a face with a crease on the + /// side indicated by increment_direction. + /// + /// + /// > 0 advance next until CurrentEdge(1) is a crease. + /// <= 0 advance previous until CurrentEdge(0) is a crease. + /// + /// + /// The number of inrements it took to reach a crease. + /// + /// + /// ON_SubDEdgeTag::Smooth: + /// The sector has no creases and the iterator was not changed. + /// ON_SubDEdgeTag::Crease: + /// The sector has a crease edge and this-<CurrentEdge(increment_direction > 0 ? 1 : 0) + /// is a crease. + /// ON_SubDEdgeTag::Unset: + /// This sector iterator is not valid. No changes. + /// + ON_SubDEdgeTag IncrementToCrease( + int increment_direction, + unsigned* increment_count + ); + + /* Description: Reset iterator to initial face. diff --git a/opennurbs_subd_data.h b/opennurbs_subd_data.h index 2842dfbf..f8bc1387 100644 --- a/opennurbs_subd_data.h +++ b/opennurbs_subd_data.h @@ -3119,6 +3119,13 @@ public: ) const; ON_3dPoint PointAt(double normalized_parameter) const; + + bool Evaluate( + double normalized_parameter, + int der_count, + int v_stride, + double* v + ) const; public: @@ -3506,6 +3513,20 @@ public: /// bool SubdivideUntilSharpnessIsZero(); + /// + /// Subdivide the vertex sector until all the edges attached to the center + /// vertex have zero sharpness. + /// + /// + /// The number of subdivisions is returned in subdivision_count. + /// + /// If successful, true is returned. Otherwise false is returned. + /// + bool SubdivideUntilSharpnessIsZero( + unsigned int& subdivision_count + ); + + const ON_SubDVertex* CenterVertex() const; ON_SubDVertexTag CenterVertexTag() const; diff --git a/opennurbs_subd_eval.cpp b/opennurbs_subd_eval.cpp index 4aff27ca..39d8d9c0 100644 --- a/opennurbs_subd_eval.cpp +++ b/opennurbs_subd_eval.cpp @@ -1679,6 +1679,16 @@ const ON_2dPoint ON_SubDFaceParameter::FaceCornerParameters() const return IsSet() ? ON_2dPoint(m_s, m_t) : ON_2dPoint::NanPoint; } +const bool ON_SubDFaceParameter::AtVertex() const +{ + return IsSet() && 0.0 == m_s && 0.0 == m_t; +} + +const bool ON_SubDFaceParameter::OnEdge() const +{ + return IsSet() && (0.0 == m_s || 0.0 == m_t); +} + const ON_2dPoint ON_SubDFaceParameter::QuadFaceParameters() const { if (IsSet() && m_cdex.IsQuadFace()) @@ -1712,6 +1722,17 @@ ON_SubDComponentParameter::ON_SubDComponentParameter(ON_SubDComponentPtr cptr) Internal_Init(ON_SubDComponentId(cptr)); } +ON_SubDComponentParameter::ON_SubDComponentParameter(ON_SubDComponentId vertex_id, ON_SubDComponentId active_edge_id, ON_SubDComponentId active_face_id) +{ + if (vertex_id.IsVertexId() && Internal_Init(vertex_id)) + { + if (active_edge_id.IsEdgeId()) + m_p0.v_active_e = active_edge_id; + if (active_face_id.IsFaceId()) + m_p1.v_active_f = active_face_id; + } +} + ON_SubDComponentParameter::ON_SubDComponentParameter( const ON_SubDVertex* v, const class ON_SubDEdge* active_edge, @@ -1758,6 +1779,16 @@ ON_SubDComponentParameter::ON_SubDComponentParameter( } } +ON_SubDComponentParameter::ON_SubDComponentParameter(ON_SubDComponentId edge_id, double p, ON_SubDComponentId active_face_id) +{ + if (edge_id.IsEdgeId() && Internal_Init(edge_id)) + { + m_p0.eptr_s = (p >= 0.0 && p <= 1.0) ? p : ON_DBL_QNAN; + if (active_face_id.IsFaceId()) + m_p1.e_active_f = active_face_id; + } +} + ON_SubDComponentParameter::ON_SubDComponentParameter( const ON_SubDEdge* e, double edge_s, @@ -1796,6 +1827,24 @@ ON_SubDComponentParameter::ON_SubDComponentParameter( } } +ON_SubDComponentParameter::ON_SubDComponentParameter(ON_SubDComponentId face_id, ON_SubDFaceParameter fp) +{ + if (face_id.IsFaceId() && Internal_Init(face_id)) + { + const ON_SubDFaceCornerDex cdex = fp.FaceCornerDex(); + if (cdex.IsSet()) + { + const ON_2dPoint p = fp.FaceCornerParameters(); + if (p.IsValid()) + { + this->m_cid = ON_SubDComponentId(face_id.FaceId(), face_id.ComponentDirection(), cdex); + this->m_p0.f_corner_s = p.x; + this->m_p1.f_corner_t = p.y; + } + } + } +} + ON_SubDComponentParameter::ON_SubDComponentParameter( const ON_SubDFace* f, ON_SubDFaceParameter fp @@ -1918,6 +1967,69 @@ int ON_SubDComponentParameter::CompareAll(const ON_SubDComponentParameter& lhs, return rc; } +const ON_wString ON_SubDComponentParameter::ToString(bool bUnsetIsEmptyString) const +{ + ON_wString str; + const unsigned id = this->ComponentId(); + if (id > 0) + { + switch (this->ComponentType()) + { + + case ON_SubDComponentPtr::Type::Vertex: + { + const ON_SubDComponentId edge = this->VertexEdge(); + const unsigned eid = edge.ComponentId(); + const unsigned fid = this->VertexFace().ComponentId(); + if (eid > 0) + { + if (fid > 0) + str = ON_wString::FormatToString(L"v%u=e%u(%u)@f%u", id, eid, edge.ComponentDirection(), fid); + else + str = ON_wString::FormatToString(L"v%u=e%u(%u)", id, eid, edge.ComponentDirection()); + } + else if (fid > 0) + str = ON_wString::FormatToString(L"v%u@f%u", id, fid); + else + str = ON_wString::FormatToString(L"v%u", id); + } + break; + + case ON_SubDComponentPtr::Type::Edge: + { + const double t = this->EdgeParameter(); + const unsigned fid = this->EdgeFace().ComponentId(); + if (fid > 0) + str = ON_wString::FormatToString(L"e%u(%g)@f%u", id, t, fid); + else + str = ON_wString::FormatToString(L"e%u(%g)", id, t); + } + break; + + case ON_SubDComponentPtr::Type::Face: + { + const ON_SubDFaceParameter fp = this->FaceParameter(); + const unsigned corner_index = fp.FaceCornerDex().CornerIndex(); + const ON_2dPoint p = fp.FaceCornerParameters(); + const unsigned fid = this->EdgeFace().ComponentId(); + str = ON_wString::FormatToString(L"f%u.%u(%g, %g)", id, corner_index, p.x, p.y); + } + break; + + } + } + + return + str.IsNotEmpty() + ? str + : (bUnsetIsEmptyString ? ON_wString::EmptyString : ON_wString("unset")); +} + +bool ON_SubDComponentParameter::IsSet() const +{ + return ComponentId() > 0; +} + const ON_SubDComponentId ON_SubDComponentParameter::ComponentIdAndType() const { return m_cid; @@ -1938,16 +2050,31 @@ bool ON_SubDComponentParameter::IsVertexParameter() const return ON_SubDComponentPtr::Type::Vertex == this->ComponentType(); } +unsigned ON_SubDComponentParameter::VertexId() const +{ + return (ON_SubDComponentPtr::Type::Vertex == this->ComponentType()) ? this->ComponentId() : 0u; +} + bool ON_SubDComponentParameter::IsEdgeParameter() const { return ON_SubDComponentPtr::Type::Edge == this->ComponentType(); } +unsigned ON_SubDComponentParameter::EdgeId() const +{ + return (ON_SubDComponentPtr::Type::Edge == this->ComponentType()) ? this->ComponentId() : 0u; +} + bool ON_SubDComponentParameter::IsFaceParameter() const { return ON_SubDComponentPtr::Type::Face == this->ComponentType(); } +unsigned ON_SubDComponentParameter::FaceId() const +{ + return (ON_SubDComponentPtr::Type::Face == this->ComponentType()) ? this->ComponentId() : 0u; +} + const ON_SubDVertex* ON_SubDComponentParameter::Vertex(const ON_SubD* subd) const { return this->ComponentPtr(subd).Vertex(); diff --git a/opennurbs_subd_fragment.cpp b/opennurbs_subd_fragment.cpp index 6da0addd..460cedbb 100644 --- a/opennurbs_subd_fragment.cpp +++ b/opennurbs_subd_fragment.cpp @@ -1857,8 +1857,8 @@ const ON_SubDFaceParameter ON_SubDMeshFragment::VertexSubDFaceParameter( break; // corner parameters run from 0.0 to 0.5. const double gdelta = 0.5 / ((double)gc); - const double corner_s = gdelta * grid2dex_i; - const double corner_t = gdelta * grid2dex_j; + const double corner_s = 0.5 - gdelta * grid2dex_i; + const double corner_t = 0.5 - gdelta * grid2dex_j; const ON_SubDFaceCornerDex cdex(m_face_fragment_index, face_edge_count); const ON_SubDFaceParameter p(cdex, corner_s, corner_t); if (p.IsNotSet()) diff --git a/opennurbs_subd_iter.cpp b/opennurbs_subd_iter.cpp index 1a3261c0..bf81174f 100644 --- a/opennurbs_subd_iter.cpp +++ b/opennurbs_subd_iter.cpp @@ -1855,10 +1855,24 @@ const ON_SubDFace* ON_SubDSectorIterator::IncrementFace( const ON_SubDFace* ON_SubDSectorIterator::IncrementToCrease( int increment_direction - ) +) { + const ON_SubDEdgeTag etag = this->IncrementToCrease( + increment_direction, + nullptr + ); + return (ON_SubDEdgeTag::Crease == etag) ? this->CurrentFace() : nullptr; +} + +ON_SubDEdgeTag ON_SubDSectorIterator::IncrementToCrease( + int increment_direction, + unsigned* increment_count +) +{ + if (nullptr != increment_count) + *increment_count = 0; if (nullptr == m_center_vertex) - return ON_SUBD_RETURN_ERROR(nullptr); + return ON_SUBD_RETURN_ERROR(ON_SubDEdgeTag::Unset); const unsigned int N = m_center_vertex->m_edge_count; const unsigned int edge_side = increment_direction > 0 ? 1 : 0; @@ -1866,7 +1880,7 @@ const ON_SubDFace* ON_SubDSectorIterator::IncrementToCrease( ON_SubDSectorIterator sit(*this); const ON_SubDFace* face0 = sit.CurrentFace(); if (nullptr == face0) - return ON_SUBD_RETURN_ERROR(nullptr); + return ON_SUBD_RETURN_ERROR(ON_SubDEdgeTag::Unset); // The for (unsigned int i = 0; i < N; i++) {} prevents infinite looping // if the topology is pointers contain an invalid cycle. @@ -1874,22 +1888,24 @@ const ON_SubDFace* ON_SubDSectorIterator::IncrementToCrease( { const ON_SubDEdge* edge = sit.CurrentEdge(edge_side); if (nullptr == edge) - return ON_SUBD_RETURN_ERROR(nullptr); + return ON_SUBD_RETURN_ERROR(ON_SubDEdgeTag::Unset); if (edge->m_face_count != 2 || ON_SubDEdgeTag::Crease == edge->m_edge_tag) { + if (nullptr != increment_count) + *increment_count = i; *this = sit; - return CurrentFace(); + return ON_SubDEdgeTag::Crease; } const ON_SubDFace* face = sit.IncrementFace(increment_direction,ON_SubDSectorIterator::StopAt::AnyCrease); if (nullptr == face) - return ON_SUBD_RETURN_ERROR(nullptr); + return ON_SUBD_RETURN_ERROR(ON_SubDEdgeTag::Unset); if ( face == face0 ) - return nullptr; // not an error - no crease and back where we started + return ON_SubDEdgeTag::Smooth; // no crease and back where we started } - return ON_SUBD_RETURN_ERROR(nullptr); + return ON_SUBD_RETURN_ERROR(ON_SubDEdgeTag::Unset); } bool ON_SubDSectorIterator::InitializeToCurrentFace() diff --git a/opennurbs_subd_ring.cpp b/opennurbs_subd_ring.cpp index 8a3373fa..0e00be23 100644 --- a/opennurbs_subd_ring.cpp +++ b/opennurbs_subd_ring.cpp @@ -31,10 +31,31 @@ unsigned int ON_SubD::GetQuadSectorPointRing( size_t component_ring_count, double* point_ring, size_t point_ring_stride - ) +) +{ + unsigned int subdivision_count = 0; + return ON_SubD::GetQuadSectorPointRing( + bPermitNoSubdivisions, + component_ring, + component_ring_count, + subdivision_count, + point_ring, + point_ring_stride + ); +} + +unsigned int ON_SubD::GetQuadSectorPointRing( + bool bPermitNoSubdivisions, + const class ON_SubDComponentPtr* component_ring, + size_t component_ring_count, + unsigned int& subdivision_count, + double* point_ring, + size_t point_ring_stride +) { // MINIMAL VALIDATION CHECKS TO PREVENT CRASHES // CALLER MUST INSURE INPUT IS CORRECT + subdivision_count = 0; const ON_SubDVertex* vertex0 = (nullptr != component_ring) ? ON_SUBD_VERTEX_POINTER(component_ring[0].m_ptr) : nullptr; if (nullptr == vertex0) @@ -57,10 +78,10 @@ unsigned int ON_SubD::GetQuadSectorPointRing( if ((size_t)point_ring_count != component_ring_count) return ON_SUBD_RETURN_ERROR(0); - // If final subdivision_count = 0, then no subdivision is needed. - // If final subdivision_count = 1, then exactly 1 subdivision is needed. - // If final subdivision_count = 2, then at least two subdivisions are needed. - unsigned subdivision_count = bPermitNoSubdivisions ? 0u : 1u; + // If the final value of target_subdivision_count = 0, then no subdivision is needed. + // If the final value of target_subdivision_count = 1, then exactly 1 subdivision is needed. + // If the final value of target_subdivision_count = 2, then at least two subdivisions are needed. + unsigned target_subdivision_count = bPermitNoSubdivisions ?0u : 1u; for (unsigned eptr_dex = 1u; eptr_dex < point_ring_count; eptr_dex += 2u) { @@ -79,27 +100,27 @@ unsigned int ON_SubD::GetQuadSectorPointRing( const double x0 = s0.MaximumEndSharpness(); if (x0 > 0.0) { - if (subdivision_count < 1u) + if (target_subdivision_count < 1u) { // at least one subdivision is required to remove sharpness - subdivision_count = 1u; + target_subdivision_count = 1u; } const double x1 = s0.Subdivided(end0).MaximumEndSharpness(); if (x1 > 0.0) { // at least two subdivisions are required to remove sharpness. - subdivision_count = 2u; + target_subdivision_count = 2u; break; } } - if (subdivision_count >= 1u) + if (target_subdivision_count >= 1u) continue; if (ON_SubDEdgeTag::SmoothX == etag) { // one subdivision is reqired to handle SmoothX edges - subdivision_count = 1u; + target_subdivision_count = 1u; } else { @@ -109,7 +130,7 @@ unsigned int ON_SubD::GetQuadSectorPointRing( if (v1->IsDartOrCreaseOrCorner() && 0.5 != e->m_sector_coefficient[1u - end0]) { // one subdivision is reqired to handle extraordinary sector coeffient at v1. - subdivision_count = 1u; + target_subdivision_count = 1u; } } } @@ -139,13 +160,13 @@ unsigned int ON_SubD::GetQuadSectorPointRing( vertex0_sharpness = vertex0->VertexSharpness(); if (vertex0_sharpness > 1.0) { - if (subdivision_count < 2u) - subdivision_count = 2u; // put a breakpoint here to see when this test matters (use files from RH-76871). + if (target_subdivision_count < 2u) + target_subdivision_count = 2u; // put a breakpoint here to see when this test matters (use files from RH-76871). } else if (vertex0_sharpness > 0.0) { - if (subdivision_count < 1u) - subdivision_count = 1u; // put a breakpoint here to see when this test matters (use files from RH-76871 with constant edge sharpness = 1). + if (target_subdivision_count < 1u) + target_subdivision_count = 1u; // put a breakpoint here to see when this test matters (use files from RH-76871 with constant edge sharpness = 1). } } else @@ -156,7 +177,7 @@ unsigned int ON_SubD::GetQuadSectorPointRing( vertex0_sharpness = ON_DBL_QNAN; } - if (subdivision_count > 1u) + if (target_subdivision_count > 1u) { // we need to subdivide at least twice to get the point ring @@ -169,7 +190,8 @@ unsigned int ON_SubD::GetQuadSectorPointRing( return ON_SUBD_RETURN_ERROR(0); if (point_ring_count != vqs.SectorVertexCount()) return ON_SUBD_RETURN_ERROR(0); - if (false == vqs.SubdivideUntilSharpnessIsZero()) + unsigned int sharp_subdivision_count = 0; + if (false == vqs.SubdivideUntilSharpnessIsZero(sharp_subdivision_count)) return ON_SUBD_RETURN_ERROR(0); // harvest the ring points from vqs. @@ -181,10 +203,12 @@ unsigned int ON_SubD::GetQuadSectorPointRing( point_ring[2] = P.z; point_ring += point_ring_stride; } + // subdivision_count = total number of subdivisions to get point_ring[] + subdivision_count = sharp_subdivision_count + 1; return point_ring_count; } - if (0u == subdivision_count) + if (0u == target_subdivision_count) { for (unsigned fptr_dex = 2u; fptr_dex < point_ring_count; fptr_dex += 2u) { @@ -195,7 +219,7 @@ unsigned int ON_SubD::GetQuadSectorPointRing( continue; // a subdivision is required to handle non-quad faces - subdivision_count = 1u; + target_subdivision_count = 1u; break; } } @@ -203,13 +227,14 @@ unsigned int ON_SubD::GetQuadSectorPointRing( // get ring points with 0 or 1 subdivision double subP[3]; const double* Q = nullptr; - if (0 == subdivision_count) + if (0 == target_subdivision_count) Q = vertex0->m_P; else { if (false == vertex0->GetSubdivisionPoint(subP)) return ON_SUBD_RETURN_ERROR(0); Q = subP; + subdivision_count = 1; } double* P = point_ring; @@ -225,7 +250,7 @@ unsigned int ON_SubD::GetQuadSectorPointRing( if (nullptr == e) return ON_SUBD_RETURN_ERROR(0); - if (0u == subdivision_count) + if (0u == target_subdivision_count) { const ON_SubDVertex* vertexQ = e->OtherEndVertex(vertex0); if (nullptr == vertexQ) @@ -248,7 +273,7 @@ unsigned int ON_SubD::GetQuadSectorPointRing( const ON_SubDFace* f = ON_SUBD_FACE_POINTER(component_ring[eptr_dex+1u].m_ptr); if (nullptr == f) return ON_SUBD_RETURN_ERROR(0); - if (0u == subdivision_count) + if (0u == target_subdivision_count) { const ON_SubDVertex* vertexQ = f->QuadOppositeVertex(vertex0); if (nullptr == vertexQ) @@ -557,6 +582,76 @@ unsigned int ON_SubD::GetSectorPointRing( return ON_SUBD_RETURN_ERROR(0); } + +unsigned int ON_SubD::GetSectorPointRing( + const class ON_SubDSectorIterator& sit, + unsigned& subdivision_count, + double* point_ring, + size_t point_ring_capacity, + size_t point_ring_stride +) +{ + const ON_SubDVertex* center_vertex = sit.CenterVertex(); + if (nullptr == center_vertex) + return ON_SUBD_RETURN_ERROR(0); + const unsigned int center_vertex_element_count = center_vertex->m_edge_count + center_vertex->m_face_count + 1; + + ON_SubDComponentPtr stack_component_ring[41]; + unsigned int component_ring_capacity = sizeof(stack_component_ring) / sizeof(stack_component_ring[0]); + ON_SubDComponentPtr* component_ring = stack_component_ring; + if (component_ring_capacity < point_ring_capacity && component_ring_capacity < center_vertex_element_count) + { + component_ring_capacity = (unsigned int)((center_vertex_element_count < point_ring_capacity) ? center_vertex_element_count : point_ring_capacity); + component_ring = new(std::nothrow) ON_SubDComponentPtr[component_ring_capacity]; + if (nullptr == component_ring) + return ON_SUBD_RETURN_ERROR(0); + } + + unsigned int point_ring_count = 0; + unsigned int component_ring_count = ON_SubD::GetSectorComponentRing(sit, component_ring, component_ring_capacity); + if (component_ring_count > 0) + { + const bool bObsoleteAndIgnoredParameter = false; + point_ring_count = ON_SubD::GetQuadSectorPointRing( + false, // false means subdivisions are permitted + bObsoleteAndIgnoredParameter, + nullptr, + component_ring, + component_ring_count, + point_ring, point_ring_stride + ); + } + + if (component_ring != stack_component_ring) + delete[] component_ring; + + return point_ring_count; +} + + +unsigned int ON_SubD::GetSectorPointRing( + const class ON_SubDSectorIterator& sit, + unsigned& subdivision_count, + ON_SimpleArray& point_ring +) +{ + subdivision_count = 0; + point_ring.SetCount(0); + const ON_SubDVertex* center_vertex = sit.CenterVertex(); + if (nullptr == center_vertex) + return ON_SUBD_RETURN_ERROR(0); + const unsigned int point_ring_capacity = (center_vertex->m_edge_count + center_vertex->m_face_count); + ON_3dPoint* point_ring_array = point_ring.Reserve(point_ring_capacity); + if (nullptr == point_ring_array) + return ON_SUBD_RETURN_ERROR(0); + unsigned int point_ring_count = GetSectorPointRing(sit, subdivision_count , &point_ring_array[0].x, point_ring_capacity, 3); + if (point_ring_count > 0) + { + point_ring.SetCount(point_ring_count); + return point_ring_count; + } + return ON_SUBD_RETURN_ERROR(0);} + const ON_SubDVertex* ON_SubD::SubdivideSector( const ON_SubDVertex* center_vertex, const ON_SubDComponentPtr* component_ring, diff --git a/opennurbs_system.h b/opennurbs_system.h index 44210dc3..e2c3766d 100644 --- a/opennurbs_system.h +++ b/opennurbs_system.h @@ -614,11 +614,7 @@ typedef ON__UINT32 wchar_t; #if defined(ON_RUNTIME_ANDROID) // May work reasonably for Android versions < 8-ish as of Sep 2018. // Test carefully if working right is important. - -// We are currently not using Freetype in OpenNURBS at all. -// Leaving this in place for testing in the future if we find that we need the -// library again for glyph metrics. -// #define OPENNURBS_FREETYPE_SUPPORT +#define OPENNURBS_FREETYPE_SUPPORT #else // not Windows, Apple, or Android diff --git a/opennurbs_texture_mapping.h b/opennurbs_texture_mapping.h index df1287c1..0755580f 100644 --- a/opennurbs_texture_mapping.h +++ b/opennurbs_texture_mapping.h @@ -777,6 +777,61 @@ ON_DLL_TEMPLATE template class ON_CLASS ON_SimpleArray ON_DLL_TEMPLATE template class ON_CLASS ON_ObjectArray; #endif +class ON_CLASS ON_WeightedAverageHash +{ +public: + static const int dim = 5; + ON_WeightedAverageHash(); + ON_3dPoint m_sum[dim]; + void Zero(); + bool Write(ON_BinaryArchive& binary_archive) const; + bool Read(ON_BinaryArchive& binary_archive); + bool Matches(const ON_WeightedAverageHash& b, const ON_Xform& bt, double tol) const; + void Transform(const ON_Xform& xform); +private: + unsigned char m_reserved[32]; +}; +class ON_CLASS ON_GeometryFingerprint +{ +public: + ON_GeometryFingerprint(); + void Zero(); + bool Write(ON_BinaryArchive& binary_archive) const; + bool Read(ON_BinaryArchive& binary_archive); + bool Matches(const ON_GeometryFingerprint& b, const ON_Xform& bt, double tol) const; + void Transform(const ON_Xform& xform); + unsigned int m_topologyCRC; + ON_WeightedAverageHash m_pointWAH; + ON_WeightedAverageHash m_edgeWAH; +private: + unsigned char m_reserved[32]; +}; +class ON_CLASS ON_MappingMeshInfo +{ +public: + ON_MappingMeshInfo(); + ON_SimpleArray m_faceSourceIds; + ON_GeometryFingerprint m_geometryFingerprint; + + // Derived data + void GenerateDerivedData(); + const int* SourceIdFaces(const int sourceId, int& countOut) const; +private: + ON_SimpleArray m_sourceIdFaceStart; + ON_SimpleArray m_sourceIdFaceCount; + ON_SimpleArray m_sourceIdFaceList; +private: + unsigned char m_reserved[32]; +}; +class ON_CLASS ON_RenderMeshInfo +{ +public: + ON_RenderMeshInfo(); + int m_sourceFaceId; + ON_GeometryFingerprint m_geometryFingerprint; +private: + unsigned char m_reserved[32]; +}; #endif diff --git a/opennurbs_wstring.cpp b/opennurbs_wstring.cpp index 70fe5ad4..b5889e59 100644 --- a/opennurbs_wstring.cpp +++ b/opennurbs_wstring.cpp @@ -16,7 +16,7 @@ #if !defined(ON_COMPILING_OPENNURBS) // This check is included in all opennurbs source .c and .cpp files to insure // ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled. -// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined +// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined // and the opennurbs .h files alter what is declared and how it is declared. #error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs #endif @@ -25,12 +25,12 @@ static int w2c_size( int, const wchar_t* ); // gets minimum "c_count" arg for w2c(). static int w2c( int, // w_count = number of wide chars to convert const wchar_t*, // source wide char string - int, // c_count, + int, // c_count, char* // array of at least c_count+1 characters ); static int c2w( int, // c_count = number of chars to convert const char*, // source byte char string - int, // w_count, + int, // w_count, wchar_t* // array of at least c_count+1 wide characters ); @@ -52,21 +52,21 @@ static int w2c_size( int w_count, const wchar_t* w ) return rc; } -static int w2c( int w_count, - const wchar_t* w, - int c_count, +static int w2c( int w_count, + const wchar_t* w, + int c_count, char* c // array of at least c_count+1 characters ) { // convert wide char string to UTF-8 string int rc = 0; - if ( c ) + if ( c ) c[0] = 0; // returns length of converted c[] if ( c_count > 0 && c ) { c[0] = 0; - if ( w ) + if ( w ) { unsigned int error_status = 0; unsigned int error_mask = 0xFFFFFFFF; @@ -79,7 +79,7 @@ static int w2c( int w_count, } if ( rc > 0 && rc <= c_count ) c[rc] = 0; - else + else { c[c_count] = 0; rc = 0; @@ -89,20 +89,20 @@ static int w2c( int w_count, return rc; } -static int c2w( int c_count, - const char* c, - int w_count, +static int c2w( int c_count, + const char* c, + int w_count, wchar_t* w // array of at least w_count+1 wide characters ) { // convert UTF-8 string to UTF-16 string int rc = 0; - if ( w ) + if ( w ) w[0] = 0; // returns length of converted c[] if ( w_count > 0 && w && c_count > 0 && c && c[0] ) { w[0] = 0; - if ( c ) + if ( c ) { unsigned int error_status = 0; unsigned int error_mask = 0xFFFFFFFF; @@ -162,7 +162,7 @@ public: {} public: - // NOTE WELL: + // NOTE WELL: // ref_count must be a signed 32-bit integer type that // supports atomic increment/decrement operations. std::atomic ref_count; @@ -177,7 +177,7 @@ private: ON_Internal_Empty_wString(const ON_Internal_Empty_wString&) = delete; ON_Internal_Empty_wString& operator=(const ON_Internal_Empty_wString&) = delete; -public: +public: ON_Internal_Empty_wString() : header(-1,0) {} @@ -185,7 +185,7 @@ public: public: ON_wStringHeader header; - wchar_t s = 0; + wchar_t s = 0; }; static ON_Internal_Empty_wString empty_wstring; @@ -265,7 +265,7 @@ bool ON_wString::IsValid( const int string_capacity = hdr->string_capacity; if (string_capacity <= 0) break; - if (string_capacity > ON_String::MaximumStringLength) + if (string_capacity > ON_wString::MaximumStringLength) break; const int string_length = hdr->string_length; if (string_length < 0) @@ -322,7 +322,7 @@ ON_wStringHeader* ON_wString::IncrementedHeader() const ON_wStringHeader* hdr = (ON_wStringHeader*)m_s; if (nullptr == hdr) return nullptr; - + hdr--; if (hdr == pEmptyStringHeader) return nullptr; @@ -345,12 +345,12 @@ ON_wStringHeader* ON_wString::Header() const wchar_t* ON_wString::CreateArray( int capacity ) { Destroy(); - if (capacity > ON_String::MaximumStringLength) + if (capacity > ON_wString::MaximumStringLength) { - ON_ERROR("Requested capacity > ON_String::MaximumStringLength"); + ON_ERROR("Requested capacity > ON_wString::MaximumStringLength"); return nullptr; } - if ( capacity > 0 ) + if ( capacity > 0 ) { // This scope does not need atomic operations void* buffer = onmalloc( sizeof(ON_wStringHeader) + (capacity+1)*sizeof(*m_s) ); @@ -373,7 +373,7 @@ void ON_wString::Destroy() void ON_wString::Empty() { Destroy(); - Create(); + Create(); } void ON_wString::EmergencyDestroy() @@ -398,7 +398,7 @@ void ON_wString::CopyArray() // Call CopyArray() before modifying array contents. // hdr0 = original header ON_wStringHeader* hdr0 = Header(); - if ( hdr0 != pEmptyStringHeader && nullptr != hdr0 && (int)(hdr0->ref_count) > 1 ) + if ( hdr0 != pEmptyStringHeader && nullptr != hdr0 && (int)(hdr0->ref_count) > 1 ) { // Calling Create() here insures hdr0 remains valid until we decrement below. Create(); @@ -423,17 +423,17 @@ wchar_t* ON_wString::ReserveArray( size_t array_capacity ) if (array_capacity > (size_t)ON_wString::MaximumStringLength) { - ON_ERROR("Requested capacity > ON_String::MaximumStringLength"); + ON_ERROR("Requested capacity > ON_wString::MaximumStringLength"); return nullptr; } const int capacity = (int)array_capacity; // for 64 bit compiler ON_wStringHeader* hdr0 = Header(); - if ( hdr0 == pEmptyStringHeader || nullptr == hdr0 ) + if ( hdr0 == pEmptyStringHeader || nullptr == hdr0 ) { CreateArray(capacity); } - else if ( (int)(hdr0->ref_count) > 1 ) + else if ( (int)(hdr0->ref_count) > 1 ) { // Calling Create() here insures hdr0 remains valid until we decrement below. Create(); @@ -442,7 +442,7 @@ wchar_t* ON_wString::ReserveArray( size_t array_capacity ) CreateArray(capacity); ON_wStringHeader* hdr1 = Header(); const int size = (capacity < hdr0->string_length) ? capacity : hdr0->string_length; - if ( size > 0 ) + if ( size > 0 ) { memcpy( hdr1->string_array(), hdr0->string_array(), size*sizeof(*m_s) ); hdr1->string_length = size; @@ -453,7 +453,7 @@ wchar_t* ON_wString::ReserveArray( size_t array_capacity ) // we might end up deleting hdr0. ON_wStringHeader_DecrementRefCountAndDeleteIfZero(hdr0); } - else if ( capacity > hdr0->string_capacity ) + else if ( capacity > hdr0->string_capacity ) { hdr0 = (ON_wStringHeader*)onrealloc( hdr0, sizeof(ON_wStringHeader) + (capacity+1)*sizeof(*m_s) ); m_s = hdr0->string_array(); @@ -470,14 +470,14 @@ void ON_wString::ShrinkArray() { Create(); } - else if ( hdr0 != pEmptyStringHeader ) + else if ( hdr0 != pEmptyStringHeader ) { - if ( hdr0->string_length < 1 ) + if ( hdr0->string_length < 1 ) { Destroy(); Create(); } - else if ( (int)(hdr0->ref_count) > 1 ) + else if ( (int)(hdr0->ref_count) > 1 ) { // Calling Create() here insures hdr0 remains valid until we decrement below. Create(); @@ -535,19 +535,19 @@ void ON_wString::CopyToArray( int size, const unsigned char* s ) void ON_wString::CopyToArray( int size, const wchar_t* s ) { - if (size > ON_String::MaximumStringLength) + if (size > ON_wString::MaximumStringLength) { - ON_ERROR("Requested size > ON_String::MaximumStringLength."); + ON_ERROR("Requested size > ON_wString::MaximumStringLength."); size = 0; } - if ( size > 0 && s && s[0] ) + if ( size > 0 && s && s[0] ) { ON_wStringHeader* hdr0 = Header(); // Calling Create() here preserves hdr0 in case s is in its m_s[] buffer. Create(); - // ReserveArray() will allocate a new header + // ReserveArray() will allocate a new header ReserveArray(size); ON_wStringHeader* hdr1 = Header(); if (nullptr != hdr1 && hdr1 != pEmptyStringHeader) @@ -573,7 +573,7 @@ void ON_wString::AppendToArray( const ON_wString& s ) void ON_wString::AppendToArray( int size, const char* s ) { - if ( size > 0 && s && s[0] ) + if ( size > 0 && s && s[0] ) { if (nullptr == ReserveArray(size + Header()->string_length)) return; @@ -589,7 +589,7 @@ void ON_wString::AppendToArray( int size, const unsigned char* s ) void ON_wString::AppendToArray( int size, const wchar_t* s ) { - if ( size > 0 && s && s[0] ) + if ( size > 0 && s && s[0] ) { if (nullptr == ReserveArray(size + Header()->string_length)) return; @@ -686,7 +686,7 @@ ON_wString::ON_wString(const ON_String& src) ON_wString::ON_wString( const char* s ) { Create(); - if ( s && s[0] ) + if ( s && s[0] ) { CopyToArray( (int)strlen(s), s ); // the (int) is for 64 bit size_t conversion } @@ -764,12 +764,12 @@ ON_wString::ON_wString( const wchar_t* s, int length ) ON_wString::ON_wString( wchar_t c, int repeat_count ) { Create(); - if (repeat_count > ON_String::MaximumStringLength) + if (repeat_count > ON_wString::MaximumStringLength) { - ON_ERROR("Requested size > ON_String::MaximumStringLength"); + ON_ERROR("Requested size > ON_wString::MaximumStringLength"); return; } - if ( repeat_count > 0 ) + if ( repeat_count > 0 ) { ReserveArray(repeat_count); for (int i=0;i= (size_t)ON_String::MaximumStringLength) + if (string_length > (size_t)ON_wString::MaximumStringLength) { - ON_ERROR("Requested size > ON_String::MaximumStringLength"); + ON_ERROR("Requested size > ON_wString::MaximumStringLength"); return nullptr; } int length = (int)string_length; // for 64 bit compilers @@ -1319,7 +1319,7 @@ wchar_t* ON_wString::SetLength(size_t string_length) { ReserveArray(length); } - if ( length >= 0 && length <= Header()->string_capacity ) + if ( length >= 0 && length <= Header()->string_capacity ) { CopyArray(); Header()->string_length = length; @@ -1405,7 +1405,7 @@ bool ON_WildCardMatch(const wchar_t* s, const wchar_t* pattern) pattern++; while ( *pattern == '*' ) pattern++; - + if ( !pattern[0] ) return true; @@ -1428,7 +1428,7 @@ bool ON_WildCardMatch(const wchar_t* s, const wchar_t* pattern) } return false; } - + if ( *pattern == '\\' ) { switch( pattern[1] ) { @@ -1448,7 +1448,7 @@ bool ON_WildCardMatch(const wchar_t* s, const wchar_t* pattern) pattern++; s++; } - + return ON_WildCardMatch(s,pattern); } @@ -1459,12 +1459,12 @@ bool ON_WildCardMatchNoCase(const wchar_t* s, const wchar_t* pattern) return ( !s || !s[0] ) ? true : false; } - if ( *pattern == '*' ) + if ( *pattern == '*' ) { pattern++; while ( *pattern == '*' ) pattern++; - + if ( !pattern[0] ) return true; @@ -1488,7 +1488,7 @@ bool ON_WildCardMatchNoCase(const wchar_t* s, const wchar_t* pattern) } return false; } - + if ( *pattern == '\\' ) { switch( pattern[1] ) @@ -1510,7 +1510,7 @@ bool ON_WildCardMatchNoCase(const wchar_t* s, const wchar_t* pattern) pattern++; s++; } - + return ON_WildCardMatchNoCase(s,pattern); } @@ -1664,7 +1664,7 @@ int ON_wString::Replace( const wchar_t* token1, const wchar_t* token2 ) // ReserveArray(newlen); // but when newlen < len and the string had multiple // references, the ReserveArray(newlen) call truncated - // the input array. + // the input array. if (nullptr == ReserveArray((newlen < len) ? len : newlen)) return 0; @@ -2260,7 +2260,7 @@ int ON_wString::FindOneOf (const wchar_t* character_set) const { if ( nullptr == character_set || 0 == character_set[0] || IsEmpty() ) return -1; - + const wchar_t* s1 = character_set; while ( 0 != *s1 ) s1++; @@ -2280,8 +2280,8 @@ int ON_wString::FindOneOf (const wchar_t* character_set) const break; e.m_error_status = 0; int buffer_count = ON_ConvertUTF32ToWideChar( - false, - sUTF32, 1, + false, + sUTF32, 1, buffer, buffer_capacity, &e.m_error_status, e.m_error_mask, @@ -2320,7 +2320,7 @@ int ON_wString::ReverseFind( wchar_t c ) const { // find first single character int i = Length(); - while( i > 0 ) + while( i > 0 ) { if (c == m_s[--i]) return i; @@ -2356,7 +2356,7 @@ int ON_wString::ReverseFind(const wchar_t* s) const void ON_wString::MakeReverse() { - if ( IsNotEmpty() ) + if ( IsNotEmpty() ) { CopyArray(); ON_wString::Reverse(m_s,Length()); @@ -2390,7 +2390,7 @@ static void ON_String_ReverseUTF16( { // c, b0[0] is a surrogate pair *s1-- = *b0++; - } + } *s1-- = c; } } @@ -2620,7 +2620,7 @@ void ON_wString::SetAt( int i, wchar_t c ) ON_wString ON_wString::Mid(int i, int count) const { - if ( i >= 0 && i < Length() && count > 0 ) + if ( i >= 0 && i < Length() && count > 0 ) { if ( count > Length() - i ) count = Length() - i; @@ -3068,7 +3068,7 @@ const wchar_t* ON_wString::ParseXMLCharacterEncoding( case 'g': if (buffer_length >= 4 - && 't' == buffer[2] + && 't' == buffer[2] && ON_wString::Semicolon == buffer[3] ) { @@ -3141,7 +3141,7 @@ const ON_wString ON_wString::RichTextExample( // Sample text s += ON_wString(L"{\\f0 ") + rich_text_font_name + ON_wString(L" rich text example:\\par}"); - s += ON_wString(L"{\\f0 Regular"); + s += ON_wString(L"{\\f0 Regular"); if (bUnderline) s += ON_wString(L" }{\\f0\\ul underlined"); s += ON_wString(L"\\par}"); @@ -3149,7 +3149,7 @@ const ON_wString ON_wString::RichTextExample( if (bBold) { s += ON_wString(L"{\\f0\\b Bold}"); - if (bUnderline) + if (bUnderline) s += ON_wString(L" }{\\f0\\b\\ul underlined"); s += ON_wString(L"\\par}"); } @@ -3157,7 +3157,7 @@ const ON_wString ON_wString::RichTextExample( if (bItalic) { s += ON_wString(L"{\\f0\\i Italic}"); - if (bUnderline) + if (bUnderline) s += ON_wString(L" }{\\f0\\i\\ul underlined"); s += ON_wString(L"\\par}"); } @@ -3262,7 +3262,7 @@ const ON_wString ON_wString::Example(ON_wString::ExampleType t) break; case ON_wString::ExampleType::XML: - /// The UTF string as an XML value with special characters encoded in the &amp; format + /// The UTF string as an XML value with special characters encoded in the &amp; format /// and code points above basic latin UTF encoded. s = ON_wString( ON_wString(L"The math teacher said, "It isn't true that 2") @@ -3287,21 +3287,21 @@ const ON_wString ON_wString::Example(ON_wString::ExampleType t) break; case ON_wString::ExampleType::XMLalternate1: - /// The UTF string as an XML value with special characters encoded in the &amp; format + /// The UTF string as an XML value with special characters encoded in the &amp; format /// and code points above basic latin encoded in the &#hhhh; format /// using lower case hex digits (0123456789abcdef). s = ON_wString(L"The math teacher said, "It isn't true that 2³=3² & Σ > 3¢ & Σ < 2 ₽ & Σ > €99." 🗑!"); break; case ON_wString::ExampleType::XMLalternate2: - /// The UTF string as an XML value with special characters encoded in the &amp; format + /// The UTF string as an XML value with special characters encoded in the &amp; format /// and code points above basic latin encoded in the hexadecimal &#xhhhh; format /// with upper case hex digits (0123456789ABCDEF). s = ON_wString(L"The math teacher said, "It isn't true that 2³=3² & Σ > 3¢ & Σ < 2 ₽ & Σ > €99." 🗑!"); break; case ON_wString::ExampleType::XMLalternate3: - /// The UTF string as an XML value with special characters and code points above + /// The UTF string as an XML value with special characters and code points above /// basic latin encoded in the decimal code point &#nnnn; format. s = ON_wString(L"The math teacher said, "It isn't true that 2³=3² & Σ > 3¢ & Σ < 2 ₽ & Σ > €99." 🗑!"); break; @@ -3448,7 +3448,7 @@ const ON_wString ON_wString::FormatToVulgarFraction( const ON_wString fraction = ON_wString::FromUnicodeCodePoints(cp, cp_count, ON_UnicodeCodePoint::ON_ReplacementCharacter); if (0 == n) return fraction; - return ON_wString::FormatToString(L"%d", n) + return ON_wString::FormatToString(L"%d", n) + ON_wString::FromUnicodeCodePoint(proper_fraction_separator_cp) + fraction; } @@ -3492,7 +3492,7 @@ static const ON_wString Internal_VulgarFractionXator(int updown, const ON_wStrin e = ON_UnicodeErrorParameters::MaskErrors; ON__UINT32 cp0 = ON_UnicodeCodePoint::ON_InvalidCodePoint; delta = ON_DecodeWideChar(s0 + i, len - i, &e, &cp0); - ON__UINT32 cp1 + ON__UINT32 cp1 = (delta > 0 && ON_IsValidUnicodeCodePoint(cp0)) ? (updown > 0 ? ON_UnicodeSuperscriptFromCodePoint(cp0,cp0) : ON_UnicodeSubcriptFromCodePoint(cp0,cp0)) : ON_UnicodeCodePoint::ON_ReplacementCharacter; diff --git a/opennurbs_xml.cpp b/opennurbs_xml.cpp index 9f4737a6..ee92a202 100644 --- a/opennurbs_xml.cpp +++ b/opennurbs_xml.cpp @@ -4708,11 +4708,11 @@ static size_t CallbackDone = 0; static char CallbackBuffer[1000]; #endif -static std::mutex g_mutex; +static std::recursive_mutex g_mutex; static void ThreadFunc(wchar_t c) { - std::lock_guard lg(g_mutex); + std::lock_guard lg(g_mutex); ON_wString s = c; for (int i = 0; i < 100; i++)