diff --git a/example_read/example_read.cpp b/example_read/example_read.cpp index c54aa384..30a31a1b 100644 --- a/example_read/example_read.cpp +++ b/example_read/example_read.cpp @@ -341,8 +341,8 @@ int main( int argc, const char *argv[] ) ON::CloseFile(dump_fp); } - const char* sDumpFilename = arg+5; - FILE* text_fp = ON::OpenFile(sDumpFilename,"w"); + const ON_wString sDumpFilename = ON_FileSystemPath::ExpandUser(arg + 5); + FILE* text_fp = ON::OpenFile(sDumpFilename,L"w"); if ( text_fp ) { dump_fp = text_fp; @@ -390,7 +390,7 @@ int main( int argc, const char *argv[] ) continue; } - ON_wString ws_arg = arg; + ON_wString ws_arg = ON_FileSystemPath::ExpandUser(arg); const wchar_t* wchar_arg = ws_arg; if ( ON::IsDirectory(wchar_arg) ) diff --git a/example_test/example_test.cpp b/example_test/example_test.cpp index 6de07385..f2e2d638 100644 --- a/example_test/example_test.cpp +++ b/example_test/example_test.cpp @@ -46,7 +46,7 @@ public: } void SetInitialDirecory( - const wchar_t* initial_directory, + const char* initial_directory, unsigned int counter ) { @@ -65,31 +65,31 @@ public: unsigned int m_max_directory_tree_depth = 0; unsigned int m_max_file_count = 0; - const ON_wString TextLogPathFromFullPath( - const wchar_t* full_path + const ON_String TextLogPathFromFullPath( + const char* full_path ) const; private: - ON_wString m_initial_directory; + ON_String m_initial_directory; unsigned int m_initial_directory_counter = 0; int m_initial_directory_length = 0; Internal_CTestContext(const Internal_CTestContext&) = delete; Internal_CTestContext operator=(const Internal_CTestContext&) = delete; - const ON_wString Internal_CleanPath(const wchar_t* dirty_path) const + const ON_String Internal_CleanPath(const char* dirty_path) const { - const wchar_t* volume = 0; - const wchar_t* path = 0; + const char* volume = 0; + const char* path = 0; // Use local path in case drive, dir, file_name_stem or ext are being reused. - on_wsplitpath(dirty_path, &volume, &path, nullptr, nullptr); - ON_wString clean_path(path); + on_splitpath(dirty_path, &volume, &path, nullptr, nullptr); + ON_String clean_path(path); if (clean_path.IsEmpty()) - return ON_wString(dirty_path); - clean_path.Replace(ON_wString::Backslash, ON_wString::Slash); + return ON_String(dirty_path); + clean_path.Replace(ON_String::Backslash, ON_String::Slash); if (nullptr != volume && volume < path) { - ON_wString clean_volume(volume, (int)(path - volume)); + ON_String clean_volume(volume, (int)(path - volume)); return (clean_volume + clean_path); } @@ -98,21 +98,21 @@ private: }; -const ON_wString Internal_CTestContext::TextLogPathFromFullPath(const wchar_t* full_path) const +const ON_String Internal_CTestContext::TextLogPathFromFullPath(const char* full_path) const { // replace initial directory with and use / for // the directory separator so that output files are standardized. - ON_wString text_log_path; - ON_wString path1 = Internal_CleanPath(full_path); + ON_String text_log_path; + ON_String path1 = Internal_CleanPath(full_path); if (m_initial_directory_length > 0 && - ON_wString::EqualOrdinal(m_initial_directory, m_initial_directory_length, path1, m_initial_directory_length, true) + ON_String::EqualOrdinal(m_initial_directory, m_initial_directory_length, path1, m_initial_directory_length, true) ) { text_log_path = (m_initial_directory_counter>0) - ? ON_wString::FormatToString(L"", m_initial_directory_counter) - : ON_wString(L""); - text_log_path += static_cast(path1) + m_initial_directory_length; + ? ON_String::FormatToString("", m_initial_directory_counter) + : ON_String(""); + text_log_path += static_cast(path1) + m_initial_directory_length; } else { @@ -124,7 +124,7 @@ const ON_wString Internal_CTestContext::TextLogPathFromFullPath(const wchar_t* f static const ONX_ErrorCounter Internal_TestModelRead( ON_TextLog& text_log, - const ON_wString text_log_3dm_file_name, + const ON_String text_log_3dm_file_name, ON_BinaryArchive& source_archive, bool bVerbose ) @@ -137,7 +137,8 @@ static const ONX_ErrorCounter Internal_TestModelRead( // text_log.PushIndent(); ONX_ModelTest test; - test.ReadTest(source_archive, test_type, true, text_log_3dm_file_name, &text_log); + const ON_wString wide_text_log_3dm_file_name(text_log_3dm_file_name); + test.ReadTest(source_archive, test_type, true, wide_text_log_3dm_file_name, &text_log); text_log.PrintNewLine(); text_log.Print("Test Results:\n"); text_log.PushIndent(); @@ -324,8 +325,8 @@ static const ONX_ErrorCounter Internal_TestModelRead( static const ONX_ErrorCounter Internal_TestFileRead( ON_TextLog& text_log, - const ON_wString fullpath, - const ON_wString text_log_path, + const ON_String fullpath, + const ON_String text_log_path, bool bVerbose ) { @@ -336,7 +337,7 @@ static const ONX_ErrorCounter Internal_TestFileRead( ONX_ErrorCounter error_counter; error_counter.ClearLibraryErrorsAndWarnings(); - const ON_wString path_to_print + const ON_String path_to_print = (text_log_path.IsNotEmpty()) ? text_log_path : fullpath; @@ -346,20 +347,21 @@ static const ONX_ErrorCounter Internal_TestFileRead( if (nullptr == fp) { text_log.Print( - L"Skipped file: %ls\n", - static_cast(path_to_print) + "Skipped file: %s\n", + static_cast(path_to_print) ); error_counter.IncrementFailureCount(); break; } text_log.Print( - L"File name: %ls\n", - static_cast(path_to_print) + "File name: %s\n", + static_cast(path_to_print) ); ON_BinaryFile source_archive(ON::archive_mode::read3dm, fp); - source_archive.SetArchiveFullPath(fullpath); + const ON_wString wide_full_path(fullpath); + source_archive.SetArchiveFullPath(static_cast(wide_full_path)); error_counter += Internal_TestModelRead(text_log, path_to_print, source_archive, bVerbose); break; @@ -374,7 +376,7 @@ static const ONX_ErrorCounter Internal_TestFileRead( return error_counter; } -static int Internal_ComparePath(const ON_wString* lhs, const ON_wString* rhs) +static int Internal_ComparePath(const ON_String* lhs, const ON_String* rhs) { // sort nullptr to end of list. if (lhs == rhs) @@ -383,15 +385,15 @@ static int Internal_ComparePath(const ON_wString* lhs, const ON_wString* rhs) return 1; if (nullptr == rhs) return -1; - int rc = ON_wString::CompareOrdinal(static_cast(*lhs), static_cast(*rhs), true); + int rc = ON_String::CompareOrdinal(static_cast(*lhs), static_cast(*rhs), true); if ( 0 == rc ) - rc = ON_wString::CompareOrdinal(static_cast(*lhs), static_cast(*rhs), false); + rc = ON_String::CompareOrdinal(static_cast(*lhs), static_cast(*rhs), false); return rc; } static const ONX_ErrorCounter Internal_TestReadFolder( ON_TextLog& text_log, - const wchar_t* directory_path, + const char* directory_path, unsigned int directory_tree_depth, bool bVerbose, Internal_CTestContext& test_context @@ -402,33 +404,33 @@ static const ONX_ErrorCounter Internal_TestReadFolder( if (nullptr == directory_path || 0 == directory_path[0]) { - text_log.Print(L"Empty directory name.\n"); + text_log.Print("Empty directory name.\n"); } ON_FileIterator fit; if (false == fit.Initialize(directory_path)) { text_log.Print( - L"Invalid directory name: %ls\n", + "Invalid directory name: %s\n", directory_path ); error_counter.IncrementFailureCount(); return error_counter; } - const ON_wString text_log_directory_name + const ON_String text_log_directory_name = (directory_tree_depth <= 1) - ? ON_wString(directory_path) + ? ON_String(directory_path) : test_context.TextLogPathFromFullPath(directory_path); text_log.Print( - L"Directory name: %ls\n", - static_cast(text_log_directory_name) + "Directory name: %s\n", + static_cast(text_log_directory_name) ); text_log.PushIndent(); - ON_ClassArray< ON_wString > sub_directories(32); - ON_ClassArray< ON_wString > skipped_files(32); - ON_ClassArray< ON_wString > tested_files(32); + ON_ClassArray< ON_String > sub_directories(32); + ON_ClassArray< ON_String > skipped_files(32); + ON_ClassArray< ON_String > tested_files(32); for ( bool bHaveItem = fit.FirstItem(); bHaveItem; bHaveItem = fit.NextItem() ) { @@ -444,7 +446,7 @@ static const ONX_ErrorCounter Internal_TestReadFolder( continue; } - ON_wString fullpath = fit.CurrentItemFullPathName(); + ON_String fullpath( fit.CurrentItemFullPathName() ); if (ON_FileStream::Is3dmFile(fullpath, false)) tested_files.Append(fullpath); @@ -463,10 +465,10 @@ static const ONX_ErrorCounter Internal_TestReadFolder( text_log.PrintNewLine(); for (int i = 0; i < skipped_files.Count(); i++) { - const ON_wString path_to_print = test_context.TextLogPathFromFullPath(skipped_files[i]); + const ON_String path_to_print = test_context.TextLogPathFromFullPath(skipped_files[i]); text_log.Print( - L"Skipped file: %ls\n", - static_cast(path_to_print) + "Skipped file: %s\n", + static_cast(path_to_print) ); } text_log.PrintNewLine(); @@ -474,8 +476,8 @@ static const ONX_ErrorCounter Internal_TestReadFolder( for ( int i = 0; i < tested_files.Count(); i++ ) { - const ON_wString full_path = tested_files[i]; - const ON_wString path_to_print = test_context.TextLogPathFromFullPath(full_path); + const ON_String full_path = tested_files[i]; + const ON_String path_to_print = test_context.TextLogPathFromFullPath(full_path); test_context.m_file_count++; const ONX_ErrorCounter file_error_counter = Internal_TestFileRead(text_log, full_path, path_to_print, bVerbose); error_counter += file_error_counter; @@ -485,7 +487,7 @@ static const ONX_ErrorCounter Internal_TestReadFolder( { if (test_context.m_max_file_count > 0 && test_context.m_file_count >= test_context.m_max_file_count) break; - const ON_wString sub_directory_path = sub_directories[i]; + const ON_String sub_directory_path = sub_directories[i]; test_context.m_directory_count++; error_counter += Internal_TestReadFolder(text_log, sub_directory_path, directory_tree_depth + 1, bVerbose, test_context); } @@ -495,9 +497,18 @@ static const ONX_ErrorCounter Internal_TestReadFolder( return error_counter; } + +static ONX_ErrorCounter Internal_InvalidArg(const ON_String arg, ON_TextLog& text_log) +{ + ONX_ErrorCounter err = ONX_ErrorCounter::Zero; + text_log.Print("Invalid command line parameter: \"%s\"\n", static_cast(arg)); + err.IncrementFailureCount(); + return err; +} + static ONX_ErrorCounter Internal_Test( unsigned int max_directory_tree_depth, - ON_wString full_path, + ON_String full_path, bool bVerbose, ON_TextLog& text_log, unsigned int& directory_counter, @@ -512,21 +523,16 @@ static ONX_ErrorCounter Internal_Test( { if (ON_FileStream::Is3dmFile(full_path, false)) { - text_log.Print(L"Testing 3dm file: %ls\n", static_cast(full_path)); - err = Internal_TestFileRead(text_log, full_path, ON_wString::EmptyString, bVerbose); + text_log.Print("Testing 3dm file: %s\n", static_cast(full_path)); + err = Internal_TestFileRead(text_log, full_path, ON_String::EmptyString, bVerbose); file_count++; } } - else if ( max_directory_tree_depth > 0 ) + else if (ON_FileSystem::IsDirectory(full_path)) { - if (full_path.Length() != 1 || false == ON_FileSystemPath::IsDirectorySeparator(full_path[0], true)) + if ( max_directory_tree_depth > 0 ) { - const wchar_t dir_seps[3] = { ON_FileSystemPath::DirectorySeparator,ON_FileSystemPath::AlternateDirectorySeparator,0 }; - full_path.TrimRight(dir_seps); - } - if (ON_FileSystem::IsDirectory(full_path)) - { - text_log.Print(L"Testing 3dm files in folder: %ls\n", static_cast(full_path)); + text_log.Print("Testing 3dm files in folder: %s\n", static_cast(full_path)); Internal_CTestContext test_context; directory_counter++; test_context.SetInitialDirecory(full_path,directory_counter); @@ -537,45 +543,136 @@ static ONX_ErrorCounter Internal_Test( folder_count += test_context.m_directory_count; } } + else + { + err += Internal_InvalidArg(full_path,text_log); + } return err; } +static ON_String Internal_PlatformId(bool bVerbose) +{ + const ON_String runtime = +#if defined(ON_RUNTIME_WIN) + bVerbose ? "Windows" : "Win" +#elif defined(ON_RUNTIME_APPLE_IOS) + bVerbose ? "Apple iOS" : "iOS" +#elif defined(ON_RUNTIME_APPLE_MACOS) + bVerbose ? "Apple Mac OS" : "MacOS" +#elif defined(ON_RUNTIME_APPLE) + "Apple" +#elif defined(ON_RUNTIME_ANDROID) + "Android" +#else + "Runtime" +#endif + ; + + const ON_String platform = +#if defined(ON_64BIT_RUNTIME) + bVerbose ? " 64 bit" : "64" +#elif defined(ON_32BIT_RUNTIME) + bVerbose ? " 32 bit" : "32" +#else + "" +#endif + ; + + const ON_String configuration = +#if defined(ON_DEBUG) + bVerbose ? " Debug" : "Debug" +#else + bVerbose ? " Release" : "Release" +#endif + ; + + ON_String platform_id(runtime+platform+configuration); + + return platform_id; +} + + +static ON_String Internal_DefaultOutFileName( + const ON_String exe_stem + ) +{ + ON_String default_file_name(exe_stem); + default_file_name.TrimLeftAndRight(); + if (default_file_name.IsEmpty()) + default_file_name = "example_test"; + default_file_name += "_log"; + + const ON_String platform_id = Internal_PlatformId(false); + if (platform_id.IsNotEmpty()) + default_file_name += ON_String(ON_String("_") + platform_id); + default_file_name += ".txt"; + + return default_file_name; +} + static void Internal_PrintHelp( - const char* example_test_exe_name, + const ON_String example_test_exe_stem, ON_TextLog& text_log ) { - if ( 0 == example_test_exe_name || 0 == example_test_exe_name[0]) - example_test_exe_name = "example_test"; + const ON_String default_out_filename = Internal_DefaultOutFileName(example_test_exe_stem); text_log.Print("\n"); text_log.Print("SYNOPSIS:\n"); - text_log.Print(" %s [-out:outputfilename.txt] [-r[:N]] \n",example_test_exe_name ); + text_log.Print(" %s [-out[:outfilename.txt]] [-recursive[:N]] [-verbose] \n", static_cast(example_test_exe_stem) ); text_log.Print("\n"); text_log.Print("DESCRIPTION:\n"); text_log.Print(" If a file is listed, it is read as an opennurbs model file.\n"); - text_log.Print(" If a directory is listed, all .3dm files in that directory\n"); - text_log.Print(" are read as opennurbs model files.\n"); + text_log.Print(" If a folder is listed, all .3dm files in that folder are read\n"); + text_log.Print(" as opennurbs model files.\n"); text_log.Print("\n"); text_log.Print(" Available options:\n"); - text_log.Print(" -out:outputfilename.txt\n"); - text_log.Print(" The output is written to the named file.\n"); + text_log.Print(" -help\n"); + text_log.Print(" Displays this message.\n"); + text_log.Print(" -out\n"); + text_log.Print(" If -out is not present, output is written to stdout.\n"); + text_log.Print(" If :outfilename.txt is appended to -out, output is written to the specifed file.\n"); + text_log.Print(" If :stdout is appended to -out, output is sent to stdout.\n"); + text_log.Print(" If :null is appended to -out, no output is written.\n"); + text_log.Print(" Otherise output is written to %s.\n",static_cast(default_out_filename)); text_log.Print(" -recursive\n"); - text_log.Print(" Recursivly reads files in subdirectories.\n"); + text_log.Print(" -r\n"); + text_log.Print(" Recursivly reads files in subfolders.\n"); text_log.Print(" If a :N is appended to the option, N limits the recursion depth.\n"); - text_log.Print(" -r:1 reads the directory and does not recurse.\n"); + text_log.Print(" -recursive:1 reads all 3dm files in the folder and does not recurse.\n"); text_log.Print(" -verbose\n"); - text_log.Print(" Include a full listing of 3dm file contents.\n"); + text_log.Print(" -v\n"); + text_log.Print(" For each 3dm file, a text summary of the 3dm file contents is created\n"); + text_log.Print(" in the folder containing the 3dm file.\n"); text_log.Print("\n"); text_log.Print("RETURNS:\n"); text_log.Print(" 0: All tested files passed.\n"); text_log.Print(" >0: Number of failures, errors, or warnings. See output for details.\n"); text_log.Print("\n"); text_log.Print("EXAMPLE:\n"); - text_log.Print(" %s -out:list.txt -resursive:2 .../example_files\n",example_test_exe_name); - text_log.Print(" with read all the opennurbs .3dm files in the\n"); - text_log.Print(" example_files/ directory and immediate subdirectories of example_files/.\n"); + text_log.Print(" %s -out:mytestresults.txt -resursive:2 .../example_files\n",static_cast(example_test_exe_stem)); + text_log.Print(" with read all the opennurbs .3dm files in the .../example_files folder\n"); + text_log.Print(" and immediate subfolders of .../example_files. Output will be saved in mytestresults.txt.\n"); +} + +static void Internal_PrintHelp( + const ON_String example_test_exe_stem, + ON_TextLog* text_log_ptr +) +{ + ON_TextLog print_to_stdout; + if (nullptr == text_log_ptr) + { + print_to_stdout.SetIndentSize(2); + text_log_ptr = &print_to_stdout; + } + return Internal_PrintHelp(example_test_exe_stem, *text_log_ptr); +} + +static char Internal_ParseOptionTailSeparator() +{ + return ':'; } static const char* Internal_ParseOptionHead( @@ -608,8 +705,13 @@ static const char* Internal_ParseOptionHead( if (option_name_length < 1) continue; if (ON_String::EqualOrdinal(option_name, option_name_length, s, option_name_length, true)) - return s + option_name_length; + { + const char* tail = s + option_name_length; + if (0 == tail[0] || Internal_ParseOptionTailSeparator() == tail[0]) + return tail; + } } + return nullptr; } @@ -619,89 +721,144 @@ static const ON_String Internal_ParseOptionTail( { for (;;) { - if (nullptr == s || ':' != s[0] || s[1] <= ON_String::Space) + if (nullptr == s || Internal_ParseOptionTailSeparator() != s[0] || s[1] <= ON_String::Space) break; ON_String tail(s+1); tail.TrimRight(); int len = tail.Length(); if (len < 1) break; - if ('"' == tail[0] && '"' == tail[len - 1]) - tail.TrimLeftAndRight("\""); - else if ('\'' == tail[0] && '\'' == tail[len - 1]) - tail.TrimLeftAndRight("'"); - if (tail.Length() < 1) - break; + if (len >= 2) + { + bool bTrim = false; + if ('"' == tail[0] && '"' == tail[len - 1]) + bTrim = true; + else if ('\'' == tail[0] && '\'' == tail[len - 1]) + bTrim = true; + if (bTrim) + { + tail = ON_String(s + 2, len - 2); + if (tail.Length() < 1) + break; + } + } return tail; } return ON_String::EmptyString; } -static bool Internal_ParseOption_OUT(const char* s, ON_String& output_file_name) +static bool Internal_ParseArg_HELP(const ON_String arg) { - output_file_name = ON_wString::EmptyString; - const char* tail = Internal_ParseOptionHead(s, "out" ); - if (nullptr == tail) - return false; - output_file_name = Internal_ParseOptionTail(tail); - return true; + const char* tail = Internal_ParseOptionHead(static_cast(arg), "help", "h", "?" ); + return (nullptr != tail && 0 == tail[0]); } -static bool Internal_Parse_ParsOption_RECURSE(const char* s, unsigned int& N) +static bool Internal_ParseArg_VERBOSE(const ON_String arg) { - const char* tail = Internal_ParseOptionHead(s, "r", "recurse", "recursive" ); + const char* tail = Internal_ParseOptionHead(static_cast(arg), "verbose", "v" ); + return (nullptr != tail && 0 == tail[0]); +} + +static bool Internal_ParseArg_OUT(const ON_String exe_stem, const ON_String arg, ON_String& output_file_path) +{ + const char* tail = Internal_ParseOptionHead(static_cast(arg), "out" ); if (nullptr == tail) return false; if (0 == tail[0]) { - N = 16; // sanity limit of default directory recursion depth - return true; + output_file_path = Internal_DefaultOutFileName(exe_stem); + } + else if (Internal_ParseOptionTailSeparator() == tail[0]) + { + output_file_path = ON_FileSystemPath::ExpandUser(Internal_ParseOptionTail(tail)); + output_file_path.TrimLeftAndRight(); + if ( + false == output_file_path.EqualOrdinal("stdout",true) + && false == output_file_path.EqualOrdinal("stderr",true) + && false == output_file_path.EqualOrdinal("null",true) + && false == output_file_path.EqualOrdinal("dev/null",true) + && ON_FileSystem::IsDirectory(output_file_path) + ) + { + const ON_wString wide_dir(output_file_path); + const ON_wString wide_fname(Internal_DefaultOutFileName(exe_stem)); + output_file_path = ON_String(ON_FileSystemPath::CombinePaths(wide_dir, false, wide_fname, true, false)); + } + } + else + { + output_file_path = ON_String::EmptyString; } - N = 0; - const ON_String num = Internal_ParseOptionTail(tail); - if (num.IsNotEmpty()) - { - unsigned int u = 0; - const char* s1 = ON_String::ToNumber(num, u, &u); - if (nullptr != s1 && s1 > static_cast(num) && u >= 1 && 0 == s1[0]) - N = u; - } return true; } -static bool Internal_Parse_ParsOption_VERBOSE(const char* s) +static bool Internal_ParseArg_RECURSE(const ON_String arg, unsigned int& N) { - const char* tail = Internal_ParseOptionHead(s, "v", "verbose"); - return (nullptr != tail && 0 == tail[0]); + const char* tail = Internal_ParseOptionHead(static_cast(arg), "recursive", "r" ); + if (nullptr == tail) + return false; + + if (0 == tail[0]) + { + N = 16; // sanity limit for default directory recursion depth; + return true; + } + + if (Internal_ParseOptionTailSeparator() == tail[0]) + { + const ON_String num = Internal_ParseOptionTail(tail); + if (num.IsNotEmpty()) + { + unsigned int u = 0; + const char* s1 = ON_String::ToNumber(num, u, &u); + if (nullptr != s1 && s1 > static_cast(num) && u >= 1 && 0 == s1[0]) + { + N = u; + return true; + } + } + } + + N = 0; + return false; +} + + +static const ON_String Internal_ParseArg_PATH(const ON_String arg, unsigned int max_directory_tree_depth) +{ + ON_String arg_full_path = ON_FileSystemPath::ExpandUser(static_cast(arg)); + arg_full_path.TrimLeftAndRight(); + + if (ON_FileSystem::IsFile(arg_full_path)) + return arg_full_path; + + if (max_directory_tree_depth > 0) + { + if (arg_full_path.Length() != 1 || false == ON_FileSystemPath::IsDirectorySeparator(arg_full_path[0], true)) + { + const char dir_seps[3] = { ON_FileSystemPath::DirectorySeparatorAsChar, ON_FileSystemPath::AlternateDirectorySeparatorAsChar, 0 }; + arg_full_path.TrimRight(dir_seps); + } + if (ON_FileSystem::IsDirectory(arg_full_path)) + return arg_full_path; + } + + return ON_String::EmptyString; } static void Internal_PrintIntroduction( - const ON_wString& exe_name, + const ON_String& example_test_exe_path, ON_TextLog& text_log ) { - text_log.Print(L"Executable: %ls\n", static_cast(exe_name)); + text_log.Print("Executable: %s\n", static_cast(example_test_exe_path)); text_log.Print("Compiled: " __DATE__ " " __TIME__ "\n"); - const ON_wString platform_name = -#if defined(ON_RUNTIME_WIN) - L"Windows" -#elif defined(ON_RUNTIME_APPLE_IOS) - L"Apple iOS" -#elif defined(ON_RUNTIME_APPLE_MACOS) - L"Apple Mac OS" -#elif defined(ON_RUNTIME_APPLE) - L"Apple" -#elif defined(ON_RUNTIME_ANDROID) - L"Android" -#else - L"Unknown" -#endif - ; + const ON_String platform_name = Internal_PlatformId(true); ON::Version(); - text_log.Print(L"Platform: %ls\n", static_cast(platform_name)); + text_log.Print("Platform: %s\n", static_cast(platform_name)); text_log.Print( "Opennurbs version: %u.%u %04u-%02u-%02u %02u:%02u (%u)\n", ON::VersionMajor(), @@ -725,28 +882,13 @@ int main( int argc, const char *argv[] ) // as a Windows DLL, then you may use either ON::OpenFile() or fopen() // to open the file. - const char* example_test_exe_name = 0; - if ( argc >= 1 && 0 != argv && 0 != argv[0] && 0 != argv[0][0] ) - { - on_splitpath( - argv[0], - nullptr, // volume - nullptr, // directory, - &example_test_exe_name, - nullptr // extension - ); - } - - if ( 0 == example_test_exe_name || 0 == example_test_exe_name[0] ) - { -#if defined(ON_OS_WINDOWS) - example_test_exe_name = "example_test.exe"; -#else - example_test_exe_name = "example_test"; -#endif - } - - const ON_wString exe_name((nullptr != argv && argc > 0) ? argv[0] : ((const char*)nullptr)); + ON_String arg(nullptr != argv ? argv[0] : ((const char*)nullptr)); + arg.TrimLeftAndRight(); + const ON_String example_test_exe_path(arg); + ON_String example_test_exe_stem; + ON_FileSystemPath::SplitPath(example_test_exe_path, nullptr, nullptr, &example_test_exe_stem, nullptr); + if (example_test_exe_stem.IsEmpty()) + example_test_exe_stem = "example_test"; ON_TextLog print_to_stdout; print_to_stdout.SetIndentSize(2); @@ -754,7 +896,7 @@ int main( int argc, const char *argv[] ) int argi; if ( argc < 2 ) { - Internal_PrintHelp(example_test_exe_name, print_to_stdout); + Internal_PrintHelp(example_test_exe_stem, print_to_stdout); return 0; } @@ -778,66 +920,90 @@ int main( int argc, const char *argv[] ) for ( argi = 1; argi < argc; argi++ ) { - const char* arg = argv[argi]; + arg = argv[argi]; + arg.TrimLeftAndRight(); + if (arg.IsEmpty()) + continue; + + if (argi + 1 == argc && Internal_ParseArg_HELP(arg)) + { + Internal_PrintHelp(example_test_exe_stem, text_log); + if ( 0 == folder_count && 0 == file_count && 0 == err.TotalCount() ) + return 0; + continue; + } // check for -out: option ON_String output_file_name; - if ( Internal_ParseOption_OUT(arg,output_file_name) ) + if ( Internal_ParseArg_OUT(example_test_exe_stem, arg, output_file_name) ) { // need a new introduction when output destination changes. bPrintIntroduction = true; - if (output_file_name.IsEmpty()) - continue; // invalid option systax - // change destination of text_log file - if ( text_log != &print_to_stdout ) + if ( text_log != &print_to_stdout && text_log != &ON_TextLog::Null ) { delete text_log; - text_log = nullptr; } if ( text_log_fp ) { ON::CloseFile(text_log_fp); + text_log_fp = nullptr; + } + + text_log = &print_to_stdout; + + if (output_file_name.IsEmpty() || output_file_name.EqualOrdinal("stdout", true)) + continue; + + if (output_file_name.EqualOrdinal("null", true) || output_file_name.EqualOrdinal("dev/null", true)) + { + text_log = &ON_TextLog::Null; + continue; } FILE* text_fp = ON::OpenFile(output_file_name,"w"); - if ( text_fp ) + if (nullptr == text_fp) { - text_log_fp = text_fp; - text_log = new ON_TextLog(text_log_fp); - text_log->SetIndentSize(2); + err += Internal_InvalidArg(arg, *text_log); + break; } + text_log_fp = text_fp; + text_log = new ON_TextLog(text_log_fp); + text_log->SetIndentSize(2); + continue; } // check for -recursive: option - if (Internal_Parse_ParsOption_RECURSE(arg, maximum_directory_depth)) + if (Internal_ParseArg_RECURSE(arg, maximum_directory_depth)) { continue; } - if (Internal_Parse_ParsOption_VERBOSE(arg)) + if (Internal_ParseArg_VERBOSE(arg)) { bVerbose = true; continue; } - ON_wString full_path(arg); - - if ( nullptr == text_log ) - text_log = &print_to_stdout; - if (bPrintIntroduction) { bPrintIntroduction = false; - Internal_PrintIntroduction(exe_name, *text_log); + Internal_PrintIntroduction(example_test_exe_path, *text_log); + } + + const ON_String arg_full_path = Internal_ParseArg_PATH(arg,maximum_directory_depth); + if (arg_full_path.IsEmpty()) + { + err += Internal_InvalidArg(arg, *text_log); + break; } err += Internal_Test( maximum_directory_depth, - full_path, + arg_full_path, bVerbose, *text_log, directory_arg_counter, @@ -849,13 +1015,13 @@ int main( int argc, const char *argv[] ) if (bPrintIntroduction) { bPrintIntroduction = false; - Internal_PrintIntroduction(exe_name, *text_log); + Internal_PrintIntroduction(example_test_exe_path, *text_log); } if (folder_count > 0) { text_log->Print( - L"Tested %u 3dm files in %u folders.", + "Tested %u 3dm files in %u folders.", file_count, folder_count ); @@ -863,7 +1029,7 @@ int main( int argc, const char *argv[] ) else { text_log->Print( - L"Tested %u 3dm files.", + "Tested %u 3dm files.", file_count ); } @@ -879,12 +1045,13 @@ int main( int argc, const char *argv[] ) err.Dump(*text_log); text_log->PrintNewLine(); - if ( text_log != &print_to_stdout ) + if ( text_log != &print_to_stdout && text_log != &ON_TextLog::Null ) { delete text_log; - text_log = nullptr; } + text_log = nullptr; + if ( text_log_fp ) { // close the text text_log file diff --git a/makefile b/makefile index 9dd25e07..c7496d1d 100644 --- a/makefile +++ b/makefile @@ -78,7 +78,6 @@ ON_INC = opennurbs.h \ opennurbs_archive.h \ opennurbs_array.h \ opennurbs_array_defs.h \ - opennurbs_atomic_op.h \ opennurbs_base32.h \ opennurbs_base64.h \ opennurbs_beam.h \ @@ -180,6 +179,7 @@ ON_INC = opennurbs.h \ opennurbs_revsurface.h \ opennurbs_rtree.h \ opennurbs_sha1.h \ + opennurbs_sleeplock.h \ opennurbs_sphere.h \ opennurbs_std_string.h \ opennurbs_string.h \ @@ -336,6 +336,7 @@ ON_SRC = opennurbs_3dm_attributes.cpp \ opennurbs_revsurface.cpp \ opennurbs_rtree.cpp \ opennurbs_sha1.cpp \ + opennurbs_sleeplock.cpp \ opennurbs_sort.cpp \ opennurbs_sphere.cpp \ opennurbs_statics.cpp \ diff --git a/opennurbs.h b/opennurbs.h index 2d319128..73b9d744 100644 --- a/opennurbs.h +++ b/opennurbs.h @@ -41,6 +41,7 @@ #include "opennurbs_unicode.h" /* unicode string conversion */ #if defined(ON_CPLUSPLUS) +#include "opennurbs_sleeplock.h" #include "opennurbs_topology.h" #include "opennurbs_cpp_base.h" // for safe use of STL classes as private data members #include "opennurbs_locale.h" diff --git a/opennurbs_3dm_attributes.cpp b/opennurbs_3dm_attributes.cpp index 8a3afde8..ff318819 100644 --- a/opennurbs_3dm_attributes.cpp +++ b/opennurbs_3dm_attributes.cpp @@ -679,7 +679,7 @@ bool ON_3dmObjectAttributes::Internal_WriteV5( ON_BinaryArchive& file ) const rc = file.Write3dmReferencedComponentIndex( ON_ModelComponent::Type::LinePattern, m_linetype_index); if (!rc) break; } - if ( -1 != m_material_index ) + if ( -1 != m_material_index && MaterialSource() == ON::material_from_object) { c = 4; rc = file.WriteChar(c); @@ -1027,12 +1027,26 @@ void ON_3dmObjectAttributes::Dump( ON_TextLog& dump ) const dump.Print("object mode = %s\n",sMode); // sSMode is const char* dump.Print("object layer index = %d\n",m_layer_index); - dump.Print("object material index = %d\n",m_material_index); + + const ON::object_material_source mat_source = MaterialSource(); + + if (ON::object_material_source::material_from_object == mat_source || false == dump.IsTextHash()) + { + // Depending on when a 3dm file was written, m_material_index may get set to an implicit -1 + // during writing as part of an old attempt to reduce the size of attributes when saved in .3dm files. + // When mat_source is not (ON::object_material_source::material_from_object. This causes the + // 3dm content to vary in a way that content hashing must ignore. The sample file + // C:\dev\github\mcneel\rhino\src4\opennurbs\example_files\V5\v5_teacup.3dm is an example. + // It's old enough that it contians material index values >= 0 that are not saved + // by SaveAs V5 writing code since circa 2010 or earlier. + dump.Print("object material index = %d\n", m_material_index); + } + const char* sMaterialSource = "unknown"; switch(MaterialSource()) { - case ON::material_from_layer: sMaterialSource = "layer material"; break; - case ON::material_from_object: sMaterialSource = "object material"; break; - case ON::material_from_parent: sMaterialSource = "parent material"; break; + case ON::object_material_source::material_from_layer: sMaterialSource = "layer material"; break; + case ON::object_material_source::material_from_object: sMaterialSource = "object material"; break; + case ON::object_material_source::material_from_parent: sMaterialSource = "parent material"; break; } dump.Print("material source = %s\n",sMaterialSource); // sMaterialSource is const char* const int group_count = GroupCount(); diff --git a/opennurbs_3dm_settings.h b/opennurbs_3dm_settings.h index 67326af9..8f33490f 100644 --- a/opennurbs_3dm_settings.h +++ b/opennurbs_3dm_settings.h @@ -1573,10 +1573,10 @@ private: double m_dNorthAngle = 0.0; int m_iStartDay = 1; int m_iStartMonth = 6; - int m_iStartYear = 2012; + int m_iStartYear = 2010; int m_iEndDay = 1; int m_iEndMonth = 6; - int m_iEndYear = 2010; + int m_iEndYear = 2012; int m_iStartHour = 6; int m_iStartMinutes = 0; int m_iStartSeconds = 0; diff --git a/opennurbs_annotationbase.cpp b/opennurbs_annotationbase.cpp index c6d2e3e4..c39acf10 100644 --- a/opennurbs_annotationbase.cpp +++ b/opennurbs_annotationbase.cpp @@ -2202,6 +2202,10 @@ bool ON_DimStyle::ZeroSuppressMatchesLengthDisplay( switch (length_display) { + case ON_DimStyle::LengthDisplay::ModelUnits: + rc = true; + break; + case ON_DimStyle::LengthDisplay::InchesFractional: if ( ON_DimStyle::suppress_zero::None != zero_suppress diff --git a/opennurbs_apple_nsfont.cpp b/opennurbs_apple_nsfont.cpp index 77ca37ff..e4cf9c58 100644 --- a/opennurbs_apple_nsfont.cpp +++ b/opennurbs_apple_nsfont.cpp @@ -899,38 +899,7 @@ bool ON_AppleFontGetGlyphOutline( outline.SetGlyphMetrics(glyph_metrics_in_font_design_units); else outline.SetGlyphMetrics(ON_TextBox::Unset); - - /* - for(;;) - { - if ( 0 == outline.FigureCount()) - break; - ON_wString postscriptName(CTFontCopyPostScriptName(appleFont)); - postscriptName.Remove(ON_String::Space); - postscriptName.Remove(ON_wString::HyphenMinus); - if ( false == postscriptName.EqualOrdinal(L"AppleColorEmoji",true) ) - break; - // AppleColorEmoji contains bitmaps and no outlines. - // Use NotoEmoji as a fallback. - CTFontRef notoEmoji = CTFontCreateWithName(CFSTR("NotoEmoji"), font_design_units_per_M, nullptr); - if (nullptr == notoEmoji) - break; - - CGPathRef applePath = CTFontCreatePathForGlyph(notoEmoji, appleGlyphIndex, nullptr); - if (nullptr == applePath) - return false; - - - outline = ON_Outline::Unset; - ON_AppleGlyphOutlineAccumlator acc2; - acc2.BeginGlyphOutline(font_design_units_per_M, figure_type, &outline); - CGPathApply(applePath, &acc2, ON_AppleGlyphOutlineAccumlator::Internal_PathApplierFunction); - CGPathRelease(applePath); - acc2.EndOutline(); - CFRelease(notoEmoji); - } - */ - + return true; } @@ -1034,6 +1003,32 @@ bool ON_AppleFontGetGlyphOutline( } } +#if defined(OPENNURBS_FREETYPE_SUPPORT) + if ( + ON_OutlineFigure::Type::SingleStroke == figure_type + // NO // ON_OutlineFigure::Type::DoubleStroke == font_figure_type + ) + { + // Apple's Mac OS CTFontCreatePathForGlyph() ignores subfigures that + // are lines. These are common in single stroke fonts. Typical results + // with singel stroke files are things like the R is missing it's right leg, + // h is missing is left side line, and so on. + // Freetype has many bugs, but it can parse some common single stroke files. + // Freetype is basically a file reading utility that can parse outline + // information in older formats of font files. It fails on newer formats + // and it also commonly fails to correctly map UNICODE code points to + // glyph indices. Freetype should only be used as a last resort. + const bool freetype_rc = ON_FreeTypeGetGlyphOutline( + glyph, + glyphIndex, + figure_type, + outline + ); + if (freetype_rc) + return true; + } +#endif + CTFontRef appleFont = font->AppleCTFont(); if (nullptr == appleFont) return false; diff --git a/opennurbs_archive.h b/opennurbs_archive.h index 2a7605bd..4fa351e4 100644 --- a/opennurbs_archive.h +++ b/opennurbs_archive.h @@ -967,18 +967,22 @@ public: /* Parameters: component_type - [in] - component_name_hash - [in] + model_component - [in] The value of ON_ModelComponent::UniqueNameIgnoresCase(component_type) must be used when creating the name hash (group names are case sensitive). - - If ON_ModelComponent::UniqueNameIncludesParent(component_type) is true, - then the parent_id must be used to calculate the name hash - (layer names require parent ids). */ const class ON_ComponentManifestItem& ItemFromName( const class ON_ModelComponent* model_component ) const; + /* + Parameters: + component_type - [in] + parent_id - [in] + If ON_ModelComponent::UniqueNameIncludesParent(component_type) is true, + then the parent_id must be used to calculate the name hash + (layer names require parent ids). + */ const class ON_ComponentManifestItem& ItemFromName( ON_ModelComponent::Type component_type, ON_UUID parent_id, diff --git a/opennurbs_array.h b/opennurbs_array.h index eff40c9c..dae53784 100644 --- a/opennurbs_array.h +++ b/opennurbs_array.h @@ -136,6 +136,7 @@ public: void Append( int, const T* ); // Append copy of an array T[count] + void Prepend( int, const T* ); // Prepend copy of an array T[count] void Insert( int, const T& ); // Insert copy of element. Uses // memmove() to perform any diff --git a/opennurbs_array_defs.h b/opennurbs_array_defs.h index 5974c5e5..320b0891 100644 --- a/opennurbs_array_defs.h +++ b/opennurbs_array_defs.h @@ -528,6 +528,43 @@ void ON_SimpleArray::Append( int count, const T* buffer ) } } + +template +void ON_SimpleArray::Prepend( int count, const T* buffer ) +{ + if ( count > 0 && nullptr != buffer ) + { + const size_t sizeof_buffer = count * sizeof(T); + void* temp = nullptr; + if ( count + m_count > m_capacity ) + { + int newcapacity = NewCapacity(); + if ( newcapacity < count + m_count ) + newcapacity = count + m_count; + if ( buffer >= m_a && buffer < (m_a + m_capacity) ) + { + // buffer is in the block of memory about to be reallocated + temp = onmalloc(sizeof_buffer); + memcpy(temp, buffer, sizeof_buffer); + buffer = (const T*)temp; + } + Reserve( newcapacity ); + } + + const size_t count0 = (size_t)m_count; + const size_t count1 = count0 + ((size_t)count); + T* p0 = m_a; + T* p = p0 + count0; + T* p1 = m_a + count1; + while (p > p0) + *(--p1) = *(--p); + memcpy( (void*)(m_a), (void*)(buffer), sizeof_buffer ); + if (nullptr != temp) + onfree(temp); + m_count = (int)count1; + } +} + template void ON_SimpleArray::Insert( int i, const T& x ) { diff --git a/opennurbs_atomic_op.h b/opennurbs_atomic_op.h index 167a60e6..d025c5aa 100644 --- a/opennurbs_atomic_op.h +++ b/opennurbs_atomic_op.h @@ -17,62 +17,6 @@ #if !defined(OPENNURBS_ATOMIC_OP_INC_) #define OPENNURBS_ATOMIC_OP_INC_ - -/* -Description: - Expert user tool to decrement the value of the parameter as an atomic operation. - -Parameters: - ptr_int32 - [in] - A non-null pointer to a signed 32-bit integer. - -Returns: - Decremented value. - -Example: - int i = 3; - // j will be 2 - int j = ON_AtomicDecrementInt32(&i); - -Remarks: - Caller is responsible for insuring ptr_int32 is not nullptr. -*/ -// ON_AtomicDecrementInt32(int* ptr_int32) - - -/* -Description: - Expert user tool to increment the value of the parameter as an atomic operation. - -Parameters: - ptr_int32 - [in] - A non-null pointer to a signed 32-bit integer. - -Returns: - Incremented value. - -Example: - int i = 3; - // j will be 4 - int j = ON_AtomicIncrementInt32(&i); - -Remarks: - Caller is responsible for insuring ptr_int32 points to - a signed 32-bit integer. -*/ -// ON_AtomicIncrementInt32(int* ptr_int32) - -#if defined(ON_RUNTIME_WIN) -#define ON_AtomicDecrementInt32(ptr_int32) InterlockedDecrement((long*)(ptr_int32)) -#define ON_AtomicIncrementInt32(ptr_int32) InterlockedIncrement((long*)(ptr_int32)) -#elif defined(ON_RUNTIME_APPLE_MACOS) -#include -#define ON_AtomicDecrementInt32(ptr_int32) OSAtomicDecrement32((int*)(ptr_int32)) -#define ON_AtomicIncrementInt32(ptr_int32) OSAtomicIncrement32((int*)(ptr_int32)) -#else -// NOT thread safe -#define ON_AtomicDecrementInt32(ptr_int32) (--(*ptr_int32)) -#define ON_AtomicIncrementInt32(ptr_int32) (++(*ptr_int32)) -#endif +#error OBSOLETE FILE #endif diff --git a/opennurbs_beam.cpp b/opennurbs_beam.cpp index da927499..11b5a7b7 100644 --- a/opennurbs_beam.cpp +++ b/opennurbs_beam.cpp @@ -2671,6 +2671,39 @@ ON_Brep* ON_Extrusion::BrepForm(ON_Brep* brep) const return BrepForm(brep, true); } +static int GetSmoothSideCount(const ON_Extrusion& E) + +{ + ON_SimpleArray profile_curves; + const int profile_count = E.GetProfileCurves(profile_curves ); + if ( profile_count < 1 || profile_count != profile_curves.Count() ) + return profile_count; + int side_count = 0; + for ( int profile_index = 0; profile_index < profile_count; profile_index++ ){ + const ON_Curve* profile_segment = profile_curves[profile_index]; + double t0 = ON_UNSET_VALUE; + double t1 = ON_UNSET_VALUE; + if ( !profile_segment->GetDomain(&t0,&t1) + || !ON_IsValid(t0) + || !ON_IsValid(t1) + || !(t0 < t1) + ) + return profile_count; + double t = t1; + while ( GetNextProfileSegmentDiscontinuity(profile_segment,t0,t1,&t) ){ + if ( t0 < t && t < t1 ){ + side_count++; + t0 = t; + t = t1; + continue; + } + break; + } + side_count++; + } + return side_count; +} + ON_Brep* ON_Extrusion::BrepForm( ON_Brep* brep, bool bSmoothFaces ) const { if ( brep ) @@ -2726,18 +2759,22 @@ ON_Brep* ON_Extrusion::BrepForm( ON_Brep* brep, bool bSmoothFaces ) const int cap_count = (3==is_capped) ? 2 : (0 != is_capped ? 1 : 0); - newbrep->m_S.Reserve(profile_count + cap_count); - newbrep->m_F.Reserve(profile_count + cap_count); - newbrep->m_L.Reserve((1 + cap_count)*profile_count); + //27 Nov 2018 - Chuck - if a kinky profile gets split up, + //there will be more than profile_count side faces. See RH-49655. + int side_count = (bSmoothFaces ) ? GetSmoothSideCount(*this) : profile_count; + + newbrep->m_S.Reserve(side_count + cap_count); + newbrep->m_F.Reserve(side_count + cap_count); + newbrep->m_L.Reserve((1 + cap_count)*side_count); // Note: // If the profiles have kinks and bSplitKinkyFaces // is true, then the m_C2, m_T, m_C3 and m_E arrays will // generally be longer than these initial reservations. - newbrep->m_C2.Reserve((4 + cap_count)*profile_count); - newbrep->m_T.Reserve((4 + cap_count)*profile_count); - newbrep->m_C3.Reserve(4*profile_count); - newbrep->m_E.Reserve(4*profile_count); + newbrep->m_C2.Reserve((4 + cap_count)*side_count); + newbrep->m_T.Reserve((4 + cap_count)*side_count); + newbrep->m_C3.Reserve(4*side_count); + newbrep->m_E.Reserve(4*side_count); int vidmap[4] = {0,1,2,3}; diff --git a/opennurbs_brep.cpp b/opennurbs_brep.cpp index 2a579d33..50c137ed 100644 --- a/opennurbs_brep.cpp +++ b/opennurbs_brep.cpp @@ -38,6 +38,26 @@ static bool ON_BrepIsNotValid() return ON_IsNotValid(); // <-- good place for a breakpoint } +//bool ON_Brep::GetLock() +//{ +// return m_sleep_lock.GetLock(); +//} +// +//bool ON_Brep::GetLockOrReturnFalse() +//{ +// return m_sleep_lock.GetLockOrReturnFalse(); +//} +// +//bool ON_Brep::ReturnLock() +//{ +// return m_sleep_lock.ReturnLock(); +//} + +ON_SleepLockGuard::ON_SleepLockGuard(const class ON_Brep& brep) + : m_sleep_lock(brep.m_sleep_lock) +{ + m_bIsManagingLock = m_sleep_lock.GetLock(); +} ON_BrepVertex::ON_BrepVertex() { @@ -6592,8 +6612,15 @@ ON_Brep::IsManifold( bool* pbIsOriented, bool* pbHasBoundary ) const bool bFlipTrim = trim.m_bRev3d; if (0 <= trim.m_li && brep_loop_count > trim.m_li) { - if ( m_F[m_L[trim.m_li].m_fi].m_bRev ) - bFlipTrim = !bFlipTrim; + if (m_L[trim.m_li].m_fi >= 0 && m_L[trim.m_li].m_fi < m_F.Count()){ + if ( m_F[m_L[trim.m_li].m_fi].m_bRev ) + bFlipTrim = !bFlipTrim; + } + else + { + ON_ERROR("Bogus face index in m_L[trim.m_li]"); + continue; + } } else { @@ -6604,8 +6631,15 @@ ON_Brep::IsManifold( bool* pbIsOriented, bool* pbHasBoundary ) const bool bFlipOther = other_trim.m_bRev3d; if (0 <= other_trim.m_li && brep_loop_count > other_trim.m_li) { - if ( m_F[m_L[other_trim.m_li].m_fi].m_bRev ) - bFlipOther = !bFlipOther; + if (m_L[other_trim.m_li].m_fi >= 0 && m_L[other_trim.m_li].m_fi < m_F.Count()){ + if ( m_F[m_L[other_trim.m_li].m_fi].m_bRev ) + bFlipOther = !bFlipOther; + } + else + { + ON_ERROR("Bogus face index in m_L[other_trim.m_li]"); + continue; + } } else { diff --git a/opennurbs_brep.h b/opennurbs_brep.h index ec69378e..078b4c76 100644 --- a/opennurbs_brep.h +++ b/opennurbs_brep.h @@ -1129,44 +1129,7 @@ public: */ bool TransformTrim( const ON_Xform& xform ); - /* - Description: - Expert user tool that replaces the 3d surface geometry - use by the face. - Parameters; - si - [in] brep surface index of new surface - bTransformTrimCurves - [in] - If unsure, then pass true. - If the surface's domain has changed and you are certain - its parameterization still jibes with the trim curve - locations, then pass false. - Returns: - True if successful. - Example: - - ON_Surface* pSurface = ...; - int si = brep.AddSurface(pSurface); - face.ChangeSurface(si); - - Remarks: - If the face had a surface and new surface has a different - shape, then you probably want to call something like - ON_Brep::RebuildEdges() to move the 3d edge curves so they - will lie on the new surface. This doesn't delete the old - surface; call ON_Brep::CullUnusedSurfaces() or ON_Brep::Compact - to remove unused surfaces. - See Also: - ON_Brep::RebuildEdges - ON_Brep::CullUnusedSurfaces - */ - bool ChangeSurface( - int si - ); - bool ChangeSurface( - int si, - bool bTransformTrimCurves - ); - + /* Returns: brep.m_S[] surface index of the 3d surface geometry used by @@ -3866,7 +3829,23 @@ protected: // 2 = solid with normals pointing in // 3 = not solid int m_is_solid = 0; + +public: + // Not ideal - used in debug/dev testing + //bool GetLock(); + //bool GetLockOrReturnFalse(); + //bool ReturnLock(); + +private: + // In calculations where multiple threads are using a brep and calling functions + // that may modify content, the calling code can use use ON_SleepLockGuard guard(Mutex) + // or similar techniques to make the calculations thread safe. + // Because Mutex is a public resource, it must be used with great care to + // prevent lock contention. + friend class ON_SleepLockGuard; + mutable ON_SleepLock m_sleep_lock; +protected: // These are friends so legacy tol values stored in v1 3dm files // can be used to set brep edge and trimming tolerances with a call // to ON_Brep::SetTolsFromLegacyValues(). diff --git a/opennurbs_compstat.cpp b/opennurbs_compstat.cpp index 694cbc64..4d5c8109 100644 --- a/opennurbs_compstat.cpp +++ b/opennurbs_compstat.cpp @@ -35,10 +35,10 @@ #define LOCKED_BIT (0x08U) #define HIDDEN_BIT (0x10U) -// A mark is a tool used in a wide variety of ways bay -// calculations. Its value is unpredictable outside the scope -// of a specific code block. High quality calculations with -// save and restore mark state, but this is not always the case. +// A mark is a tool used in a wide variety of ways by calculations. +// Its value is unpredictable outside the scope of a specific code block. +// High quality calculations will save and restore mark state, +// but this is not always the case. // This state is not saved in archives. #define RUNTIME_MARK_BIT (0x20U) @@ -132,11 +132,47 @@ bool ON_ComponentStatus::operator!=(ON_ComponentStatus b) return (m_status_flags&ALL_MASK) != (b.m_status_flags&ALL_MASK); } +const ON_ComponentStatus ON_ComponentStatus::LogicalAnd(ON_ComponentStatus lhs, ON_ComponentStatus rhs) +{ + ON_ComponentStatus x; + x.m_status_flags = (lhs.m_status_flags & rhs.m_status_flags); + return x; +} + +const ON_ComponentStatus ON_ComponentStatus::LogicalOr(ON_ComponentStatus lhs, ON_ComponentStatus rhs) +{ + ON_ComponentStatus x; + x.m_status_flags = (lhs.m_status_flags | rhs.m_status_flags); + return x; +} + +bool ON_ComponentStatus::StatusCheck( + ON_ComponentStatus candidate, + ON_ComponentStatus status_pass, + ON_ComponentStatus status_fail +) +{ + if (0 != ON_ComponentStatus::LogicalAnd(candidate, status_pass).m_status_flags) + return true; + if (0 != ON_ComponentStatus::LogicalAnd(candidate, status_fail).m_status_flags) + return false; + if (0 == status_fail.m_status_flags) + return true; + if (0 != status_pass.m_status_flags) + return false; + return true; +} + bool ON_ComponentStatus::IsClear() const { return (0 == (m_status_flags&ALL_MASK)); } +bool ON_ComponentStatus::IsNotClear() const +{ + return (0 != (m_status_flags&ALL_MASK)); +} + unsigned int ON_ComponentStatus::SetStatus( ON_ComponentStatus status_to_copy ) diff --git a/opennurbs_compstat.h b/opennurbs_compstat.h index cf468e0b..50fe1bfa 100644 --- a/opennurbs_compstat.h +++ b/opennurbs_compstat.h @@ -14,6 +14,8 @@ //////////////////////////////////////////////////////////////// */ +#if !defined(OPENNURBS_COMPSTAT_INC_) +#define OPENNURBS_COMPSTAT_INC_ ////////////////////////////////////////////////////////////////////////// // @@ -97,8 +99,72 @@ public: static const ON_ComponentStatus Locked; static const ON_ComponentStatus Deleted; static const ON_ComponentStatus Damaged; + static const ON_ComponentStatus Marked; + + /* + The six bits for SelectedPersistent, Highlighted, Hidden, Locked, and Damaged are set. + The two bits for Deleted and RuntimeMark are clear. + */ static const ON_ComponentStatus AllSet; + /* + Returns: + A logical and of the status bit in lhs and rhs. + */ + static const ON_ComponentStatus LogicalAnd(ON_ComponentStatus lhs, ON_ComponentStatus rhs); + + /* + Returns: + A logical and of the status bit in lhs and rhs. + */ + static const ON_ComponentStatus LogicalOr(ON_ComponentStatus lhs, ON_ComponentStatus rhs); + + /* + Description: + A tool for adding a status check filter. This tool pays attention to RuntimeMark(). + + Paramters: + candidate - [in] + pass_bits - [in] + fail_bits - [in] + + Returns: + Checking is perfomed in the folloing order and every bit, include the RuntimeMark() bit, are tested. + + First: + If ON_ComponentStatus::LogicalAnd(candidate,status_pass) has any set bits, + then true is returned. + + Second: + If ON_ComponentStatus::LogicalAnd(candidate,status_fail) has any set bits, + then false is returned. + + Third: + If status_fail has no set bits the true is returned. + + Forth: + If status_pass has any set bits then false is returned. + + Fifth: + True is returned. + + Examples: + StatusCheck(candidate,ON_ComponentStatus::Selected,ON_ComponentStatus::NoneSet) = candidate.>IsSelected(). + + StatusCheck(candidate,ON_ComponentStatus::NoneSet,ON_ComponentStatus::Selected) = !candidate.>IsSelected(). + + StatusCheck(candidate,ON_ComponentStatus::NoneSet,ON_ComponentStatus::NoneSet) = true; + + StatusCheck(candidate,ON_ComponentStatus::AllSet,ON_ComponentStatus::NoneSet) = true; + + StatusCheck(candidate,ON_ComponentStatus::NoneSet,ON_ComponentStatus::AllSet) = candidate.IsClear() && false==candidate.RuntimeMark(); + */ + static bool StatusCheck( + ON_ComponentStatus candidate, + ON_ComponentStatus status_pass, + ON_ComponentStatus status_fail + ); + ON_ComponentStatus() = default; ~ON_ComponentStatus() = default; ON_ComponentStatus(const ON_ComponentStatus&) = default; @@ -124,6 +190,15 @@ public: */ bool IsClear() const; + /* + Returns: + True if some setting besides runtime mark is 1 or true. + Ignores the runtime mark state. + Remarks: + The runtime mark setting is ignored by IsNotClear(). + */ + bool IsNotClear() const; + /* Description: Sets *this = status_to_copy and returns 1 if a state setting changed. @@ -549,3 +624,84 @@ private: unsigned int m_damaged_count = 0; }; +////////////////////////////////////////////////////////////////////////// +// +// ON_UniqueTester +// + +class ON_CLASS ON_UniqueTester +{ +public: + ON_UniqueTester() = default; + ~ON_UniqueTester(); + ON_UniqueTester(const ON_UniqueTester&); + ON_UniqueTester& operator=(const ON_UniqueTester&); + +public: + + /* + Description: + If p is not in the list, it is added. + Returns: + True if p is in the list. + */ + bool InList(ON__UINT_PTR x) const; + + /* + Description: + If p is not in the list, it is added. + Returns: + True if p is not in the list and was added. False if p was already in the list. + */ + bool AddToList(ON__UINT_PTR x); + + void ClearList(); + + unsigned int Count() const; + +public: + /* + Description: + Add x to the list. The expert caller is certain that x is not already in the list. + For large lists, using this function when appropriate, can result in substantial + speed improvments. + Parameters: + x - [in] + A value that is known to not be in the list. + */ + void ExpertAddNewToList(ON__UINT_PTR x); + +private: + class Block + { + public: + static Block* NewBlock(); + static void DeleteBlock(Block*); + + public: + enum : size_t {BlockCapacity=1000}; + size_t m_count = 0; + ON__UINT_PTR* m_a = nullptr; + class Block* m_next = nullptr; + bool InBlock(size_t sorted_count,ON__UINT_PTR p) const; + + void SortBlock(); + + private: + static int Compare(ON__UINT_PTR* lhs, ON__UINT_PTR* rhs); + Block() = default; + ~Block() = delete; + Block(const Block&) = delete; + Block& operator=(const Block&) = delete; + }; + + size_t m_sorted_count = 0; + Block* m_block_list = nullptr; + +private: + void Internal_CopyFrom(const ON_UniqueTester& src); + void Internal_Destroy(); + void Internal_AddValue(ON__UINT_PTR x); +}; + +#endif diff --git a/opennurbs_cpp_base.h b/opennurbs_cpp_base.h index aa3c5327..e0e81cfe 100644 --- a/opennurbs_cpp_base.h +++ b/opennurbs_cpp_base.h @@ -26,4 +26,83 @@ bool operator==(const struct ON_UUID_struct& a, const struct ON_UUID_struct& b); bool operator!=(const struct ON_UUID_struct& a, const struct ON_UUID_struct& b); #endif +class ON_CLASS ON_StopWatch +{ +public: + ON_StopWatch() = default; + ~ON_StopWatch() = default; + ON_StopWatch(const ON_StopWatch&) = default; + ON_StopWatch& operator=(const ON_StopWatch&) = default; + +public: + enum class State : unsigned char + { + /// + /// The stopwatch is off. + /// + Off = 0, + + /// + /// The stopwatch is started and running. + /// + Running = 1, + + /// + /// The stopwatch has been started and stopped. + /// + Stopped = 2 + }; + + /* + Description: + If the stopwatch is off or stopped, it is started. Otherwise nothing happens. + */ + void Start(); + + /* + Description: + If the stopwatch is running, then it is stopped. Otherwise nothing happens. + Returns: + If the stopwatch was running, the elapsed time from the most recent Start(). + Otherwise, 0.0 is returned. + */ + double Stop(); + + /* + Description: + The stopwatch is reset and turned off. Any previously set times are lost. + */ + void Reset(); + + /* + Returns: + Current state of the stopwatch. + */ + ON_StopWatch::State CurrentState() const; + + + /* + Returns: + The elapsed time in seconds. + Remarks: + If the stopwatch is running, the elapsed time is the duration from the most recent Start() to now. + If the stopwatch is stopped, the elapsed time is the duration between the most recent Start() and Stop(). + If the stopwatch is off, the elapsed time is zero. + */ + double ElapsedTime() const; + +private: + // current state + ON_StopWatch::State m_state = ON_StopWatch::State::Off; +#pragma ON_PRAGMA_WARNING_PUSH +#pragma ON_PRAGMA_WARNING_DISABLE_MSC( 4251 ) + // C4251: ... : class 'std::...' + // needs to have dll-interface to be used by clients ... + // m_start and m_stop are private and all code that manages them is explicitly implemented in the DLL. + std::chrono::high_resolution_clock::time_point m_start; // most recent Start() time. + std::chrono::high_resolution_clock::time_point m_stop; // most recent Stop() time. +#pragma ON_PRAGMA_WARNING_POP +}; + + #endif diff --git a/opennurbs_defines.cpp b/opennurbs_defines.cpp index 2c6c1234..07c501fc 100644 --- a/opennurbs_defines.cpp +++ b/opennurbs_defines.cpp @@ -2502,3 +2502,56 @@ ON_4udex::ON_4udex( , l(lValue) {} +void ON_StopWatch::Start() +{ + if (ON_StopWatch::State::Off == m_state || ON_StopWatch::State::Stopped == m_state) + { + m_state = ON_StopWatch::State::Running; + m_start = std::chrono::high_resolution_clock::now(); + } +} + +double ON_StopWatch::Stop() +{ + const std::chrono::high_resolution_clock::time_point t = std::chrono::high_resolution_clock::now(); + double d; + if ( ON_StopWatch::State::Running == m_state) + { + m_stop = t; + m_state = ON_StopWatch::State::Stopped; + d = ElapsedTime(); + } + else + { + d = 0.0; + } + return d; +} + +void ON_StopWatch::Reset() +{ + m_state = ON_StopWatch::State::Off; +} + +ON_StopWatch::State ON_StopWatch::CurrentState() const +{ + return m_state; +} + +double ON_StopWatch::ElapsedTime() const +{ + std::chrono::high_resolution_clock::time_point t = std::chrono::high_resolution_clock::now(); + if (ON_StopWatch::State::Stopped == m_state) + t = m_stop; + double d; + if (ON_StopWatch::State::Stopped == m_state || ON_StopWatch::State::Running == m_state) + { + //d = std::chrono::duration< double, std::chrono::high_resolution_clock::period >(t - m_start).count(); + d = std::chrono::duration >(t - m_start).count(); + } + else + { + d = 0.0; + } + return d; +} diff --git a/opennurbs_defines.h b/opennurbs_defines.h index 11ea358b..a8fb05e6 100644 --- a/opennurbs_defines.h +++ b/opennurbs_defines.h @@ -649,6 +649,30 @@ ON_StringMapOrdinalType ON_StringMapOrdinalTypeFromStringMapType( ON_StringMapType map_type ); +/// +/// ON_ChainDirection is used to specify directions when building +/// chains of components like edges or faces. +/// +enum class ON_ChainDirection : unsigned char +{ + Unset = 0, + + /// + /// Search for chain links before the current link. + /// + Previous = 1, + + /// + /// Search for chain links after the current link. + /// + Next = 2, + + /// + /// Search for chain links before and after the current link. + /// + Both = 3 +}; + // OpenNurbs enums class ON_CLASS ON { @@ -1943,14 +1967,14 @@ public: morph_control_object = 0x20000, // some type of ON_MorphControl subd_object = 0x40000, // some type of ON_SubD, ON_SubDRef, ON_SubDComponentRef, ON_SubD.... loop_object = 0x80000, // some type of ON_BrepLoop - brepvertex_filter = 0x100000, // selection filter value - not a real object type + brepvertex_filter = 0x100000, // selection filter value - not a real object type (ON_BrepEdge, ON_SubDVertex) polysrf_filter = 0x200000, // selection filter value - not a real object type - edge_filter = 0x400000, // selection filter value - not a real object type + edge_filter = 0x400000, // selection filter value - not a real object type (ON_BrepEdge, ON_SubDEdge) polyedge_filter = 0x800000, // selection filter value - not a real object type - meshvertex_filter = 0x01000000, // selection filter value - not a real object type - meshedge_filter = 0x02000000, // selection filter value - not a real object type - meshface_filter = 0x04000000, // selection filter for mesh triangle, quad or ngon - not a real object type - meshcomponent_reference = 0x07000000, // an ON_MeshComponentRef to vertex, edge, face, ngon + meshvertex_filter = 0x01000000, // selection filter value - not a real object type (ON_MeshTopologyVertex, ON_SubDVertex) + meshedge_filter = 0x02000000, // selection filter value - not a real object type (ON_MeshTopologyEdge, ON_SubDEdge) + meshface_filter = 0x04000000, // selection filter for ON_Mesh triangle, quad, ngon, or ON_SubDFace - not a real object type + meshcomponent_reference = 0x07000000, // an ON_MeshComponentRef or ON_SubDComponentRef cage_object = 0x08000000, // some type of ON_NurbsCage phantom_object = 0x10000000, clipplane_object = 0x20000000, diff --git a/opennurbs_dimension.cpp b/opennurbs_dimension.cpp index 6c10ce51..6a804f3e 100644 --- a/opennurbs_dimension.cpp +++ b/opennurbs_dimension.cpp @@ -734,7 +734,7 @@ bool ON_DimLinear::GetTextXform( if (text_outside && ON_DimStyle::ContentAngleStyle::Horizontal != text_angle_style && UseDefaultTextPoint()) { // move textpoint outside right arrow by 1/2 text width + 1-1/2 arrow width - double x = text_width * 0.5 + text_gap; + double x = (text_width * 0.5) + (text_gap * 3.0); if (force_text == ON_Dimension::ForceText::Left || force_text == ON_Dimension::ForceText::HintLeft) { if (arrowflipped[0]) @@ -1884,7 +1884,15 @@ bool ON_DimLinear::GetDisplayLines( } } - if ( + if (ArrowIsFlipped(0) && ArrowIsFlipped(1)) + { + // Don't draw dimline between extensions if arrows are flipped + lines[3].from = m_plane.PointAt(m_def_pt_2.x, m_dimline_pt.y); + lines[3].to = lines[2].to; + lines[2].to = m_plane.PointAt(0.0, m_dimline_pt.y); + isline[3] = true; + } + else if ( ON_DimStyle::TextLocation::InDimLine == text_location || ON::TextOrientation::InView == text_orientation || ON_DimStyle::ContentAngleStyle::Aligned != text_angle_style @@ -4099,13 +4107,13 @@ bool ON_DimRadial::GetTextXform( // Text position adjustment ON_2dVector shift(0.0, 0.0); - shift.y = -line_height / 2.0; if (ON_DimStyle::TextLocation::AboveDimLine == text_location) - shift.y = text_gap + text_height; + shift.y = text_gap; + if (ON_DimStyle::TextLocation::InDimLine == text_location) + shift.y = -line_height/2.0; shift.x = text_gap; shift.x += landing_length; - //shift.x += text_width / 2.0; if (-ON_SQRT_EPSILON > tail_dir.x) // text to left { @@ -4430,22 +4438,13 @@ bool ON_DimRadial::GetDisplayLines( if (landing_dir.Unitize()) { double landinglength = dimstyle->LeaderLandingLength() * dimscale; - ON_3dPoint kink_point = plane.PointAt(kneept2d.x, kneept2d.y); - ON_3dPoint dimline_point = plane.PointAt(dimlinept2d.x, dimlinept2d.y); + //ON_3dPoint kink_point = plane.PointAt(kneept2d.x, kneept2d.y); + //ON_3dPoint dimline_point = plane.PointAt(dimlinept2d.x, dimlinept2d.y); const ON_DimStyle::TextLocation text_location = dimstyle->DimRadialTextLocation(); - if ( - (ON_DimStyle::ContentAngleStyle::Horizontal == alignment && ON_2dPoint::UnsetPoint != kneept2d) - || ON_DimStyle::TextLocation::AboveDimLine == text_location - ) - { - if (!dimstyle->LeaderHasLanding() && fabs(dimline_point.x - kink_point.x) < ON_ZERO_TOLERANCE) - landinglength = dimstyle->TextHeight() * dimscale; - } - if(ON_DimStyle::TextLocation::AboveDimLine == text_location && ON::TextOrientation::InView != dimstyle->DimRadialTextOrientation()) - landinglength += text_rect[1].DistanceTo(text_rect[0]); + landinglength += text_rect[1].DistanceTo(text_rect[0]); // Add text width to draw under the text if (0.0 < landinglength) { diff --git a/opennurbs_extensions.cpp b/opennurbs_extensions.cpp index 2e953a91..e3b7dab6 100644 --- a/opennurbs_extensions.cpp +++ b/opennurbs_extensions.cpp @@ -1663,7 +1663,7 @@ ON_ModelComponentReference ONX_Model::Internal_AddModelComponent( const ON_NameHash name_hash = bIsEmbeddedFileReference ? original_name_hash - : ON_NameHash::Create(name_parent_id,name); + : ON_NameHash::Create(name_parent_id,name,ON_ModelComponent::UniqueNameIgnoresCase(component_type)); int manifest_item_index = ON_UNSET_INT_INDEX; const bool bIndexRequired = ON_ModelComponent::IndexRequired(component_type); @@ -3961,6 +3961,71 @@ bool ONX_Model::IsRDKDocumentInformation(const ONX_Model_UserData& docud) return ( 0 == ON_UuidCompare(rdk_plugin_id,docud.m_uuid) && docud.m_goo.m_value >= 4 && 0 != docud.m_goo.m_goo ); } +bool ONX_Model::GetRDKEmbeddedFiles(const ONX_Model_UserData& docud, ON_ClassArray& paths, ON_SimpleArray& embedded_files_as_buffers) +{ + if (!ONX_Model::IsRDKDocumentInformation(docud)) + return false; + + ON_Read3dmBufferArchive a(docud.m_goo.m_value, docud.m_goo.m_goo, false, docud.m_usertable_3dm_version, docud.m_usertable_opennurbs_version); + + int version = 0; + if (!a.ReadInt(&version)) + return false; + + if (4 != version) + return false; + + //Read out the document data, and throw it away. + { + int slen = 0; + if (!a.ReadInt(&slen)) + return 0; + if (slen <= 0) + return 0; + if (slen + 4 > docud.m_goo.m_value) + return 0; + ON_String s; + s.SetLength(slen); + if (!a.ReadChar(slen, s.Array())) + return 0; + } + + unsigned int iCount = 0; + if (!a.ReadInt(&iCount)) + return false; + + int unpacked = 0; + + for (unsigned int i = 0; i < iCount; i++) + { + ON_wString sPath; + if (!a.ReadString(sPath)) + return false; + + size_t size; + if (!a.ReadCompressedBufferSize(&size)) + return false; + + auto* buffer = new unsigned char[size]; + bool bFailedCRC = false; + if (a.ReadCompressedBuffer(size, buffer, &bFailedCRC)) + { + if (!bFailedCRC) + { + embedded_files_as_buffers.Append(buffer); + paths.Append(sPath); + unpacked++; + } + else + { + delete[] buffer; + } + } + } + + return unpacked > 0; +} + bool ONX_Model::GetRDKDocumentInformation(const ONX_Model_UserData& docud,ON_wString& rdk_xml_document_data) { @@ -3979,7 +4044,8 @@ bool ONX_Model::GetRDKDocumentInformation(const ONX_Model_UserData& docud,ON_wSt if ( !a.ReadString(rdk_xml_document_data) ) return false; } - else if ( 3 == version ) + else if ( 3 == version || 4 == version) //Version 4 files are exactly the same as version 3, but with an size_t (for the number of buffers) + //and the actual compressed buffers containing the embedded files { // UTF-8 string int slen = 0; @@ -4532,13 +4598,31 @@ static const ON_wString Internal_DumpModelfileName( text_file_path += L"original"; else text_file_path += L"copy"; + #if defined(ON_RUNTIME_WIN) - text_file_path += L"_WinOS"; +#if defined(ON_64BIT_RUNTIME) + text_file_path += L"_Win64"; +#elif defined(ON_32BIT_RUNTIME) + text_file_path += L"_Win32"; +#else + text_file_path += L"_Win"; +#endif #elif defined(ON_RUNTIME_APPLE_MACOS) text_file_path += L"_MacOS"; #elif defined(ON_RUNTIME_APPLE_IOS) text_file_path += L"_iOS"; +#elif defined(ON_RUNTIME_APPLE) + text_file_path += L"_AppleOS"; +#elif defined(ON_RUNTIME_ANDROID) + text_file_path += L"_AndroidOS"; #endif + +#if defined(ON_DEBUG) + text_file_path += L"Debug"; +#else + text_file_path += L"Release"; +#endif + text_file_path += L".txt"; return text_file_path; } diff --git a/opennurbs_extensions.h b/opennurbs_extensions.h index 7c8965fa..919ae166 100644 --- a/opennurbs_extensions.h +++ b/opennurbs_extensions.h @@ -1548,6 +1548,9 @@ public: static bool IsRDKDocumentInformation(const ONX_Model_UserData& docud); static bool GetRDKDocumentInformation(const ONX_Model_UserData& docud,ON_wString& rdk_xml_document_data); + //This is only supported for Version 6 files onwards. + static bool GetRDKEmbeddedFiles(const ONX_Model_UserData& docud, ON_ClassArray& paths, ON_SimpleArray& embedded_files_as_buffers); + static bool IsRDKObjectInformation(const ON_UserData& objectud); static bool GetRDKObjectInformation(const ON_Object& object,ON_wString& rdk_xml_object_data); // diff --git a/opennurbs_file_utilities.cpp b/opennurbs_file_utilities.cpp index 27ffda85..4776529d 100644 --- a/opennurbs_file_utilities.cpp +++ b/opennurbs_file_utilities.cpp @@ -769,6 +769,27 @@ const ON_wString ON_FileSystemPath::CleanPath( bool bDeleteWindowsUNCHostNameOrDiskLetter, const wchar_t directory_separator, const wchar_t* dirty_path +) +{ + return ON_FileSystemPath::CleanPath( + bTrimLeft, + bTrimRight, + bAllowWindowsUNCHostNameOrDiskLetter, + bDeleteWindowsUNCHostNameOrDiskLetter, + false, + directory_separator, + dirty_path + ); +} + +const ON_wString ON_FileSystemPath::CleanPath( + bool bTrimLeft, + bool bTrimRight, + bool bAllowWindowsUNCHostNameOrDiskLetter, + bool bDeleteWindowsUNCHostNameOrDiskLetter, + bool bExpandUser, + const wchar_t directory_separator, + const wchar_t* dirty_path ) { ON_wString local_dirty_path(dirty_path); @@ -778,6 +799,8 @@ const ON_wString ON_FileSystemPath::CleanPath( local_dirty_path.TrimRight(); if ( local_dirty_path.IsEmpty() ) return ON_wString_CleanPathFailed(); + if (bExpandUser) + local_dirty_path = ON_FileSystemPath::ExpandUser(local_dirty_path); dirty_path = local_dirty_path; @@ -973,6 +996,45 @@ const ON_wString ON_FileSystemPath::CleanPath( return clean_path; } +const ON_wString ON_FileSystemPath::ExpandUser( + const char* dirty_path +) +{ + const ON_wString dirty_local_path(dirty_path); + return ON_FileSystemPath::ExpandUser(static_cast(dirty_local_path)); +} + +const ON_wString ON_FileSystemPath::ExpandUser( + const wchar_t* dirty_path +) +{ + for(;;) + { + if (nullptr == dirty_path) + break; + if (ON_wString::Tilde != dirty_path[0]) + break; + if (false == ON_FileSystemPath::IsDirectorySeparator(dirty_path[1], true)) + break; + ON_wString expanduser_path = ON_FileSystemPath::PlatformPath(ON_FileSystemPath::PathId::HomeDirectory); + if (expanduser_path.IsEmpty()) + break; + const wchar_t dir_seps[3] + = + { + ON_FileSystemPath::DirectorySeparator, + ON_FileSystemPath::AlternateDirectorySeparator, + 0 + }; + expanduser_path.TrimRight(dir_seps); + if (expanduser_path.IsEmpty()) + break; + expanduser_path += ON_wString(dirty_path + 1); + return expanduser_path; + } + return ON_wString(dirty_path); +} + bool ON_FileSystem::PathExists( const char* path ) @@ -1699,7 +1761,7 @@ bool ON_FileSystemPath::IsValidFileName( return true; } -const ON_wString ON_FileSystemPath::PlatformPath( ON_FileSystemPath::PathId path_id ) +const ON_wString ON_FileSystemPath::PlatformPath(ON_FileSystemPath::PathId path_id) { #if defined(ON_RUNTIME_WIN) KNOWNFOLDERID platform_path_id; @@ -1710,16 +1772,25 @@ const ON_wString ON_FileSystemPath::PlatformPath( ON_FileSystemPath::PathId path #endif #if defined(ON_INTERNAL_SET_LOCAL_DIRECTORY_ID) - switch ( path_id) + switch (path_id) { case ON_FileSystemPath::PathId::DesktopDirectory: - ON_INTERNAL_SET_LOCAL_DIRECTORY_ID( FOLDERID_Desktop, NSDesktopDirectory ); + ON_INTERNAL_SET_LOCAL_DIRECTORY_ID(FOLDERID_Desktop, NSDesktopDirectory); break; case ON_FileSystemPath::PathId::DocumentsDirectory: - ON_INTERNAL_SET_LOCAL_DIRECTORY_ID( FOLDERID_Documents, NSDocumentDirectory ); + ON_INTERNAL_SET_LOCAL_DIRECTORY_ID(FOLDERID_Documents, NSDocumentDirectory); break; case ON_FileSystemPath::PathId::DownloadsDirectory: - ON_INTERNAL_SET_LOCAL_DIRECTORY_ID( FOLDERID_Downloads, NSDownloadsDirectory ); + ON_INTERNAL_SET_LOCAL_DIRECTORY_ID(FOLDERID_Downloads, NSDownloadsDirectory); + break; + case ON_FileSystemPath::PathId::HomeDirectory: +//#if defined(ON_RUNTIME_WIN) +// platform_path_id = FOLDERID_Profile; +//#elif defined(ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) +// platform_path_id = NSUserDirectory; +//#endif + ON_INTERNAL_SET_LOCAL_DIRECTORY_ID(FOLDERID_Profile, NSUserDirectory); + //ON_INTERNAL_SET_LOCAL_DIRECTORY_ID(FOLDERID_Profile, NSHomeDirectory); break; default: return ON_wString::EmptyString; @@ -1750,17 +1821,17 @@ const ON_wString ON_FileSystemPath::PlatformPath( ON_FileSystemPath::PathId path #elif defined(ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE) NSArray *apple_paths = NSSearchPathForDirectoriesInDomains(platform_path_id, NSUserDomainMask, YES); - if ([apple_paths count] > 0) + if ([apple_paths count] > 0) { - NSString* apple_path = [apple_paths objectAtIndex:0]; - if ( nullptr != apple_path ) + NSString* apple_path = [apple_paths objectAtIndex : 0]; + if (nullptr != apple_path) { ON_wString s; const int len = (int)apple_path.length; s.SetLength(len); int idx; - for ( idx=0; idx + /// ON_FileSystemPath::PathId identifies a collection of commonly used directories. + /// Use ON_FileSystemPath::PlatformPath() to get the name of the directory. + /// enum class PathId : unsigned int { + /// + /// The current user's home directory. + /// Unset = 0, + + /// + /// The current user's desktop directory. + /// DesktopDirectory = 1, + + /// + /// The current user's documents directory. + /// DocumentsDirectory = 2, - DownloadsDirectory = 3 + + /// + /// The current user's downloads directory. + /// + DownloadsDirectory = 3, + + /// + /// The current user's home directory. + /// + HomeDirectory = 4 }; /* diff --git a/opennurbs_font.cpp b/opennurbs_font.cpp index fa6be325..277554ca 100644 --- a/opennurbs_font.cpp +++ b/opennurbs_font.cpp @@ -1697,6 +1697,41 @@ const ON_Font* ON_FontFaceQuartet::Face( : (bBold ? BoldFace() : RegularFace()); } +const ON_Font* ON_FontFaceQuartet::ClosestFace( + bool bPreferedBold, + bool bPreferedItalic +) const +{ + const ON_Font* f = Face(bPreferedBold, bPreferedItalic); + if (nullptr != f) + return f; + + if (IsEmpty()) + return nullptr; + + if (nullptr == m_bold && nullptr == m_bold_italic) + bPreferedBold = false; + else if (nullptr == m_regular && nullptr == m_italic) + bPreferedBold = true; + + if (nullptr == m_italic && nullptr == m_bold_italic) + bPreferedItalic = false; + else if (nullptr == m_regular && nullptr == m_bold) + bPreferedItalic = true; + + f = Face(bPreferedBold, bPreferedItalic); + if (nullptr != f) + return f; + if (nullptr != m_regular) + return m_regular; + if (nullptr != m_bold) + return m_bold; + if (nullptr != m_italic) + return m_italic; + return m_bold_italic; +} + + unsigned int ON_FontFaceQuartet::FaceCount() const { unsigned int face_count = 0; @@ -2622,29 +2657,123 @@ const ON_Font* ON_Font::InstalledFontFromRichTextProperties( bool bRtfItalic ) { - ON_wString s(rtf_font_name); - s.TrimLeftAndRight(); - if (s.IsEmpty()) - s = ON_Font::DefaultFamilyName(); - rtf_font_name = s; + ON_wString local_rtf_font_name(rtf_font_name); + local_rtf_font_name.TrimLeftAndRight(); + if (local_rtf_font_name.IsEmpty()) + local_rtf_font_name = ON_Font::Default.RichTextFontName(); + rtf_font_name = local_rtf_font_name; - ON_Font font(ON_Font::Default); - if (bRtfBold) - font.SetFontWeight(ON_Font::Weight::Bold); - if (bRtfItalic) - font.SetFontStyle(ON_Font::Style::Italic); + // It is critical that bRequireFaceMatch because the input name must + // clearly identify a font in some way. The search below allows for this + // name to appear in various contexts (LOGFONT, PostScript, Family+Face, Family). + const bool bRequireFaceMatch = true; - font.Internal_ClearAllNames(); - font.m_loc_family_name = rtf_font_name; - font.m_en_family_name = font.m_loc_family_name; + // It is critical that bRequireStyleMatch = false. See below + const bool bRequireStyleMatch = false; - font.m_loc_postscript_name = rtf_font_name; - font.m_en_postscript_name = font.m_loc_postscript_name; + // The preferred_weight is a bias used to pick between faces that have matching name properties. + // An exact match is not required because bRequireStyleMatch = false; + const ON_Font::Weight preferred_weight = bRtfBold ? ON_Font::Weight::Semibold : ON_Font::Weight::Medium; - font.m_loc_windows_logfont_name = rtf_font_name; - font.m_en_windows_logfont_name = font.m_loc_windows_logfont_name; + // The preferred_style is a bias used to pick between faces that have matching name properties. + // An exact match is not required because bRequireStyleMatch = false; + const ON_Font::Style preferred_style = bRtfItalic ? ON_Font::Style::Italic : ON_Font::Style::Upright; - return ON_Font::InstalledFontList().FromFontProperties(&font,false,true); + const bool bUnderlined = false; + const bool bStrikethrough = false; + const wchar_t* ignore_postscript_name = nullptr; + const wchar_t* ignore_windows_logfont_name = nullptr; + const wchar_t* ignore_family_name = nullptr; + const wchar_t* ignore_prefered_face_name = nullptr; + + + // 1st: Try LOGFONT name. For Windows this is the very best choice. + // For MacOS this works the best in practice. If it fails, we move onto PostScript. + const ON_Font* installed_font = ON_Font::InstalledFontList().FromNames( + ignore_postscript_name, + rtf_font_name, // windows_logfont_name, + ignore_family_name, + ignore_prefered_face_name, + preferred_weight, ON_Font::Stretch::Unset, preferred_style, + bRequireFaceMatch, bRequireStyleMatch, bUnderlined, bStrikethrough, + 0.0 + ); + + while (nullptr == installed_font) + { + + // 2nd: Try PostScript name + installed_font = ON_Font::InstalledFontList().FromNames( + rtf_font_name, // postscript_name, + ignore_windows_logfont_name, + ignore_family_name, + ignore_prefered_face_name, + preferred_weight, ON_Font::Stretch::Unset, preferred_style, + bRequireFaceMatch, bRequireStyleMatch, bUnderlined, bStrikethrough, + 0.0 + ); + if (nullptr != installed_font) + break; + + // 3rd: Try Family + Face name + const wchar_t hyphen[2] = {ON_wString::HyphenMinus,0}; + for (const wchar_t* separator = rtf_font_name + 1; 0 != *separator; ++separator) + { + if (ON_wString::Space != *separator && ON_wString::HyphenMinus != *separator) + continue; + ON_wString family_name(rtf_font_name, (int)(separator - rtf_font_name)); + family_name.TrimLeftAndRight(); + family_name.TrimLeftAndRight(hyphen); + family_name.TrimLeftAndRight(); + if (family_name.IsEmpty()) + continue; + ON_wString face_name(separator + 1); + face_name.TrimLeftAndRight(); + face_name.TrimLeftAndRight(hyphen); + face_name.TrimLeftAndRight(); + if (face_name.IsEmpty()) + continue; + + installed_font = ON_Font::InstalledFontList().FromNames( + ignore_postscript_name, + ignore_windows_logfont_name, + family_name, face_name, + preferred_weight, ON_Font::Stretch::Unset, preferred_style, + bRequireFaceMatch, bRequireStyleMatch, bUnderlined, bStrikethrough, + 0.0 + ); + if (nullptr != installed_font) + break; + } + if (nullptr != installed_font) + break; + + + // 4th: Try Family name + installed_font = ON_Font::InstalledFontList().FromNames( + ignore_postscript_name, + ignore_windows_logfont_name, + rtf_font_name, // family name + ignore_prefered_face_name, + preferred_weight, ON_Font::Stretch::Unset, preferred_style, + bRequireFaceMatch, bRequireStyleMatch, bUnderlined, bStrikethrough, + 0.0 + ); + if (nullptr != installed_font) + break; + + // No installed font comes close to matching the input name parameter + break; + } + + if (nullptr != installed_font) + { + const ON_FontFaceQuartet q = installed_font->InstalledFontQuartet(); + const ON_Font* f = q.ClosestFace(bRtfBold, bRtfItalic); + return (nullptr != f) ? f : installed_font; + } + + return nullptr; } @@ -3826,10 +3955,37 @@ const ON_Font* ON_Font::ManagedFamilyMemberWithRichTextProperties( bUnderlined = bUnderlined ? true : false; bStrikethrough = bStrikethrough ? true : false; - const ON_Font::Weight desired_weight - = (bBold != IsBoldInQuartet()) - ? (bBold ? ON_Font::Weight::Bold : ON_Font::Weight::Normal) - : FontWeight(); + const ON_Font::Weight current_weight = FontWeight(); + ON_Font::Weight desired_weight = current_weight; + if (bBold != IsBoldInQuartet()) + { + const ON_FontFaceQuartet q = InstalledFontQuartet(); + const ON_Font* f = nullptr; + if (bBold) + { + // increase desired_weight + f = (bItalic) ? q.BoldItalicFace() : q.BoldFace(); + if ( nullptr == f) + f = (bItalic) ? q.BoldFace() : q.BoldItalicFace(); + if (nullptr != f) + { + if (ON_Font::Weight::Unset == current_weight || static_cast(f->FontWeight()) > static_cast(current_weight)) + desired_weight = f->FontWeight(); + } + } + else + { + // descrease desired_weight + f = (bItalic) ? q.ItalicFace() : q.RegularFace(); + if ( nullptr == f) + f = (bItalic) ? q.RegularFace() : q.ItalicFace(); + if (nullptr != f) + { + if (ON_Font::Weight::Unset == current_weight || static_cast(f->FontWeight()) < static_cast(current_weight)) + desired_weight = f->FontWeight(); + } + } + } const ON_Font::Style desired_style = (bItalic != IsItalic()) @@ -4282,11 +4438,14 @@ const ON_Font* ON_Font::GetManagedFont( const wchar_t* face_name ) { + const bool bBold = false; + const bool bItalic = false; + return ON_Font::GetManagedFont( point_size, face_name, - ON_Font::Default.m_font_weight, - ON_Font::Default.m_font_style + bBold, + bItalic ); } @@ -4319,14 +4478,14 @@ const ON_Font* ON_Font::GetManagedFont( const ON_Font* ON_Font::GetManagedFont( double point_size, - const wchar_t* face_name, + const wchar_t* name, bool bBold, bool bItalic ) { return ON_Font::GetManagedFont( point_size, - face_name, + name, bBold ? ON_Font::Weight::Bold : ON_Font::Default.FontWeight(), bItalic ? ON_Font::Style::Italic : ON_Font::Default.FontStyle() ); @@ -4343,16 +4502,38 @@ const ON_Font* ON_Font::GetManagedFont( const ON_Font* ON_Font::GetManagedFont( double point_size, - const wchar_t* face_name, + const wchar_t* name, ON_Font::Weight font_weight, ON_Font::Style font_style ) { - const unsigned int logfont_charset = static_cast(ON_Font::WindowsLogfontCharSetFromFaceName(face_name)); + // Using ON_Font::InstalledFontFromRichTextProperties() fixes lots + // of RTF parsing bugs including RH-49028 and "Arial Black" bugs. + // Depending on the context, name can be LOGFONT, PostScript, Family+Face, Family, ... + // The font_weight parameter is typically a "face" indication. + // Treating it as a literal value causes more bugs than it fixes. + const ON_Font* installed_font = ON_Font::InstalledFontFromRichTextProperties( + name, + ON_Font::IsBoldWeight(font_weight), + (ON_Font::Style::Italic == font_style) + ); + if (nullptr != installed_font) + { + if (point_size > 0.0 && point_size < ON_Font::AnnotationFontApplePointSize) + { + ON_Font f(*installed_font); + f.m_point_size = point_size; + return f.ManagedFont(); + } + return installed_font->ManagedFont(); + } + + // Less reliable approach + const unsigned int logfont_charset = static_cast(ON_Font::WindowsLogfontCharSetFromFaceName(name)); return ON_Font::GetManagedFont( point_size, - face_name, + name, font_weight, font_style, ON_Font::Default.m_font_stretch, @@ -5944,11 +6125,17 @@ const ON_wString ON_Font::WindowsLogfontName() const return WindowsLogfontName(ON_Font::NameLocale::LocalizedFirst); } +const ON_wString ON_Font::QuartetName( + ON_Font::NameLocale name_locale +) const +{ + // This may need adjustment for MacOS. + return WindowsLogfontName(name_locale); +} const ON_wString ON_Font::QuartetName() const { - // This may need adjustment for MacOS. - return WindowsLogfontName(); + return ON_Font::QuartetName(ON_Font::NameLocale::LocalizedFirst); } bool ON_Font::IsBoldInQuartet() const @@ -7523,9 +7710,7 @@ void ON_Font::Dump(ON_TextLog& dump) const // ON_Font::DumpTextMetric(&logfont_tm, dump); //} } -#endif - -#if defined (ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) +#elif defined (ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE) { CTFontRef apple_font = AppleCTFont(); dump.Print(L"Apple Core Text font:\n"); @@ -7533,9 +7718,7 @@ void ON_Font::Dump(ON_TextLog& dump) const ON_Font::DumpCTFont(apple_font, dump); dump.PopIndent(); } -#endif - -#if defined(OPENNURBS_FREETYPE_SUPPORT) +#elif defined(OPENNURBS_FREETYPE_SUPPORT) // Look in opennurbs_system_rumtime.h for the correct place to define OPENNURBS_FREETYPE_SUPPORT. // Do NOT define OPENNURBS_FREETYPE_SUPPORT here or in your project setting ("makefile"). if (runtime_sn >= 1) @@ -7544,7 +7727,6 @@ void ON_Font::Dump(ON_TextLog& dump) const DumpFreeType(dump); } #endif - } dump.PopIndent(); } diff --git a/opennurbs_font.h b/opennurbs_font.h index 39a58292..18fb953d 100644 --- a/opennurbs_font.h +++ b/opennurbs_font.h @@ -2198,6 +2198,11 @@ public: bool bItalic ) const; + const ON_Font* ClosestFace( + bool bPreferedBold, + bool bPreferedItalic + ) const; + void Dump(ON_TextLog& text_log) const; private: ON_wString m_quartet_name; @@ -3584,13 +3589,20 @@ public: */ const ON_wString WindowsLogfontName() const; + /* + Returns: + Name of the quartet for this font. See ON_FontFaceQuartet for more details. + */ + const ON_wString QuartetName( + ON_Font::NameLocale name_locale + ) const; + /* Returns: Name of the quartet for this font. See ON_FontFaceQuartet for more details. */ const ON_wString QuartetName() const; - /* Returns: A long description that includes family, face, weight, stretch and style information. diff --git a/opennurbs_freetype.cpp b/opennurbs_freetype.cpp index fad1ac82..c61144c1 100644 --- a/opennurbs_freetype.cpp +++ b/opennurbs_freetype.cpp @@ -2032,11 +2032,23 @@ unsigned int ON_FreeTypeGetGlyphMetrics( return glyph_index; } + bool ON_FreeTypeGetGlyphOutline( const ON_FontGlyph* glyph, ON_OutlineFigure::Type figure_type, class ON_Outline& outline ) +{ + const unsigned int glyph_index = 0; + return ON_FreeTypeGetGlyphOutline(glyph, glyph_index, figure_type, outline); +} + +bool ON_FreeTypeGetGlyphOutline( + const ON_FontGlyph* glyph, + unsigned int glyph_index, + ON_OutlineFigure::Type figure_type, + class ON_Outline& outline +) { outline = ON_Outline::Unset; @@ -2061,9 +2073,12 @@ bool ON_FreeTypeGetGlyphOutline( } } - const unsigned int glyph_index = glyph->FontGlyphIndex(); - if (glyph_index <= 0) - return false; + if (0 == glyph_index) + { + glyph_index = glyph->FontGlyphIndex(); + if (glyph_index <= 0) + return false; + } FT_Face ft_face = (FT_Face)(ON_Font::FreeTypeFace(font)); if (nullptr == ft_face) diff --git a/opennurbs_freetype.h b/opennurbs_freetype.h index d696b6af..d8112801 100644 --- a/opennurbs_freetype.h +++ b/opennurbs_freetype.h @@ -25,7 +25,9 @@ #endif #if defined(ON_RUNTIME_APPLE) -#error FreeType is not used in MacOS and iOS builds. It does not work as well as CTFont based code. +// Freetype is used to get single stroke font outlines. +// For everything else, use the CTFont based tools. +//#error FreeType is not used in MacOS and iOS builds. It does not work as well as CTFont based code. #endif /* @@ -78,6 +80,24 @@ bool ON_FreeTypeGetGlyphOutline( class ON_Outline& outline ); +/* +Parameters: + glyph - [in] + glyph_index - [in] + If known for certain, pass in the glyph index. If not known, pass in 0. + figure_type - [in] + If known for certain, pass in figure_type. Otherwise, pass in ON_OutlineFigure::Type::Unset. + outline - [out] + outline and metrics in font design units +*/ +ON_DECL +bool ON_FreeTypeGetGlyphOutline( + const class ON_FontGlyph* glyph, + unsigned int glyph_index, + ON_OutlineFigure::Type figure_type, + class ON_Outline& outline +); + /* Description: A wrapper for calculating parameters and calling FreeType library diff --git a/opennurbs_fsp.cpp b/opennurbs_fsp.cpp index 8935fea2..a871df55 100644 --- a/opennurbs_fsp.cpp +++ b/opennurbs_fsp.cpp @@ -10,6 +10,14 @@ ON_FixedSizePool::ON_FixedSizePool() { + ////const size_t sz = sizeof(*this); + ////ON_TextLog::Null.Print("L%d", sz); // supress compile errors. + // sz = 72 bytes before step 2 fixes for https://mcneel.myjetbrains.com/youtrack/issue/RH-49375 + // + // private data members will be rearranged but the size cannot change so that the + // C++ public SDK remains stable. + // + memset(this,0,sizeof(*this)); } @@ -38,8 +46,7 @@ ON_FixedSizePool& ON_FixedSizePool::operator=(ON_FixedSizePool&& src) { if (this != &src) { - Destroy(); - + Destroy(); m_first_block = src.m_first_block; m_al_element_stack = src.m_al_element_stack; m_al_block = src.m_al_block; @@ -49,7 +56,6 @@ ON_FixedSizePool& ON_FixedSizePool::operator=(ON_FixedSizePool&& src) m_block_element_count = src.m_block_element_count; m_active_element_count = src.m_active_element_count; m_total_element_count = src.m_total_element_count; - memset(&src,0,sizeof(*this)); } return *this; @@ -63,7 +69,6 @@ size_t ON_FixedSizePool::SizeofElement() const return m_sizeof_element; } - bool ON_FixedSizePool::Create( size_t sizeof_element, size_t element_count_estimate, @@ -173,25 +178,8 @@ size_t ON_FixedSizePool::TotalElementCount() const return m_total_element_count; } -//static void DebugIsValid(const ON_FixedSizePool* p) -//{ -// if( !p->IsValid() ) -// p->IsValid(); -//} - void* ON_FixedSizePool::AllocateDirtyElement() { - //static unsigned int debug_count = 0; - //debug_count++; - //const unsigned int debug_count_mark = 21432; - //if (21432 == debug_count) - //{ - // // A conditional breakpoint is too slow for large values of debug_count - // debug_count = debug_count_mark; // <- breakpoint here - //} - - //DebugIsValid(this); - void* p; if ( 0 != m_al_element_stack ) @@ -199,7 +187,6 @@ void* ON_FixedSizePool::AllocateDirtyElement() // use item on the returned stack first. p = m_al_element_stack; m_al_element_stack = *((void**)m_al_element_stack); - //DebugIsValid(this); } else { @@ -258,7 +245,6 @@ void* ON_FixedSizePool::AllocateDirtyElement() } m_al_element_array = (void*)(((char*)m_al_block)+2*sizeof(void*)); - //DebugIsValid(this); } m_al_count--; p = m_al_element_array; @@ -268,8 +254,6 @@ void* ON_FixedSizePool::AllocateDirtyElement() m_active_element_count++; - //DebugIsValid(this); - return p; } @@ -354,7 +338,7 @@ bool ON_FixedSizePool::IsValid() const } total_element_count += block_element_count; - if (total_element_count > m_total_element_count) + if (total_element_count > (size_t)m_total_element_count) { ON_ERROR("m_total_element_count is not correct or some other serious problem."); return false; @@ -367,7 +351,7 @@ bool ON_FixedSizePool::IsValid() const } } - if (total_element_count != m_total_element_count) + if (total_element_count != (size_t)m_total_element_count) { ON_ERROR("m_total_element_count or m_al_element_array is not correct or some other serious problem."); return false; @@ -380,11 +364,9 @@ bool ON_FixedSizePool::IsValid() const return false; } - return true; } - void* ON_FixedSizePool::AllocateElement() { void* p = AllocateDirtyElement(); @@ -414,6 +396,43 @@ void ON_FixedSizePool::ReturnElement(void* p) } } +void* ON_FixedSizePool::ThreadSafeAllocateDirtyElement() +{ + void* p = nullptr; + { + if ( m_sleep_lock.GetLock() ) + { + p = AllocateDirtyElement(); + m_sleep_lock.ReturnLock(); + } + } + return p; +} + +void* ON_FixedSizePool::ThreadSafeAllocateElement() +{ + void* p = nullptr; + { + if ( m_sleep_lock.GetLock() ) + { + p = AllocateElement(); + m_sleep_lock.ReturnLock(); + } + } + return p; +} + +void ON_FixedSizePool::ThreadSafeReturnElement(void* p) +{ + if (nullptr != p) + { + if ( m_sleep_lock.GetLock() ) + { + ReturnElement(p); + m_sleep_lock.ReturnLock(); + } + } +} ON_FixedSizePoolIterator::ON_FixedSizePoolIterator() : m_fsp(0) @@ -446,21 +465,6 @@ void ON_FixedSizePoolIterator::Reset() m_it_element = 0; } -//////void* ON_FixedSizePool::FirstElement() -//////{ -////// if ( m_first_block && m_total_element_count > 0 ) -////// { -////// m_qwerty_it_block = m_first_block; -////// m_qwerty_it_element = (void*)(((char*)m_qwerty_it_block)+2*sizeof(void*)); // m_qwerty_it_element points to first element in m_first_block -////// } -////// else -////// { -////// m_qwerty_it_block = 0; -////// m_qwerty_it_element = 0; -////// } -////// return m_qwerty_it_element; -//////} - void* ON_FixedSizePoolIterator::FirstElement() { if ( m_fsp && m_fsp->m_first_block && m_fsp->m_total_element_count > 0 ) @@ -476,33 +480,6 @@ void* ON_FixedSizePoolIterator::FirstElement() return m_it_element; } -//////void* ON_FixedSizePool::NextElement() -//////{ -////// if ( m_qwerty_it_element ) -////// { -////// m_qwerty_it_element = (void*)(((char*)m_qwerty_it_element) + m_sizeof_element); -////// if ( m_qwerty_it_element == m_al_element_array ) -////// { -////// m_qwerty_it_block = 0; -////// m_qwerty_it_element = 0; -////// } -////// else if ( m_qwerty_it_element == *((void**)(((char*)m_qwerty_it_block) + sizeof(void*))) ) -////// { -////// // m_qwerty_it_element = "end" pointer which means we are at the end of m_qwerty_it_block -////// m_qwerty_it_block = *((void**)m_qwerty_it_block); // m_qwerty_it_block = "next" block -////// m_qwerty_it_element = (0 != m_qwerty_it_block) // m_qwerty_it_element points to first element in m_qwerty_it_block -////// ? (void*)(((char*)m_qwerty_it_block)+2*sizeof(void*)) -////// : 0; -////// if ( m_qwerty_it_element == m_al_element_array ) -////// { -////// m_qwerty_it_block = 0; -////// m_qwerty_it_element = 0; -////// } -////// } -////// } -////// return m_qwerty_it_element; -//////} - void* ON_FixedSizePoolIterator::NextElement() { if ( m_it_element ) @@ -541,42 +518,6 @@ void* ON_FixedSizePoolIterator::CurrentElement() const return m_it_element; } -//////void* ON_FixedSizePool::FirstElement(size_t element_index) -//////{ -////// const char* block; -////// const char* block_end; -////// const char* next_block; -////// size_t block_count; -////// -////// m_qwerty_it_block = 0; -////// m_qwerty_it_element = 0; -////// if ( element_index < m_total_element_count ) -////// { -////// for ( block = (const char*)m_first_block; 0 != block; block = next_block ) -////// { -////// if ( block == m_al_block ) -////// { -////// next_block = 0; -////// block_end = (const char*)m_al_element_array; -////// } -////// else -////// { -////// next_block = *((const char**)block); -////// block_end = *((const char**)(block + sizeof(void*))); -////// } -////// block_count = (block_end - block)/m_sizeof_element; -////// if ( element_index < block_count ) -////// { -////// m_qwerty_it_block = (void*)block; -////// m_qwerty_it_element = ((void*)(block + (2*sizeof(void*) + element_index*m_sizeof_element))); -////// break; -////// } -////// element_index -= block_count; -////// } -////// } -////// return m_qwerty_it_element; -//////} - void* ON_FixedSizePoolIterator::FirstElement(size_t element_index) { const char* block; @@ -586,7 +527,7 @@ void* ON_FixedSizePoolIterator::FirstElement(size_t element_index) m_it_block = 0; m_it_element = 0; - if ( m_fsp && element_index < m_fsp->m_total_element_count ) + if ( m_fsp && element_index < (size_t)(m_fsp->m_total_element_count) ) { for ( block = (const char*)m_fsp->m_first_block; 0 != block; block = next_block ) { @@ -640,25 +581,6 @@ size_t ON_FixedSizePool::BlockElementCount( const void* block ) const return (block_end - block_head)/m_sizeof_element; } -//////void* ON_FixedSizePool::FirstBlock( size_t* block_element_count ) -//////{ -////// if ( m_first_block && m_total_element_count > 0 ) -////// { -////// m_qwerty_it_block = m_first_block; -////// m_qwerty_it_element = (void*)(((char*)m_qwerty_it_block)+2*sizeof(void*)); // m_qwerty_it_element points to first element in m_first_block -////// if ( 0 != block_element_count ) -////// *block_element_count = BlockElementCount(m_qwerty_it_block); -////// } -////// else -////// { -////// m_qwerty_it_block = 0; -////// m_qwerty_it_element = 0; -////// if ( 0 != block_element_count ) -////// *block_element_count = 0; -////// } -////// return m_qwerty_it_element; -//////} - void* ON_FixedSizePoolIterator::FirstBlock( size_t* block_element_count ) { if ( m_fsp && m_fsp->m_first_block && m_fsp->m_total_element_count > 0 ) @@ -678,37 +600,6 @@ void* ON_FixedSizePoolIterator::FirstBlock( size_t* block_element_count ) return m_it_element; } -//////void* ON_FixedSizePool::NextBlock( size_t* block_element_count ) -//////{ -////// if ( 0 != m_qwerty_it_block -////// && m_qwerty_it_block != m_al_block -////// && m_qwerty_it_element == (void*)(((char*)m_qwerty_it_block)+2*sizeof(void*)) ) -////// { -////// m_qwerty_it_block = *((void**)m_qwerty_it_block); -////// if ( m_qwerty_it_block == m_al_element_array ) -////// { -////// m_qwerty_it_block = 0; -////// m_qwerty_it_element = 0; -////// if ( 0 != block_element_count ) -////// *block_element_count = 0; -////// } -////// else -////// { -////// m_qwerty_it_element = (void*)(((char*)m_qwerty_it_block)+2*sizeof(void*)); // m_qwerty_it_element points to first element in m_first_block -////// if ( 0 != block_element_count ) -////// *block_element_count = BlockElementCount(m_qwerty_it_block); -////// } -////// } -////// else -////// { -////// m_qwerty_it_block = 0; -////// m_qwerty_it_element = 0; -////// if ( 0 != block_element_count ) -////// *block_element_count = 0; -////// } -////// return m_qwerty_it_element; -//////} - void* ON_FixedSizePoolIterator::NextBlock( size_t* block_element_count ) { if ( 0 != m_it_block @@ -742,7 +633,7 @@ void* ON_FixedSizePoolIterator::NextBlock( size_t* block_element_count ) void* ON_FixedSizePool::Element(size_t element_index) const { - if (element_index < m_total_element_count) + if (element_index < (size_t)m_total_element_count) { const char* block; const char* block_end; @@ -830,7 +721,6 @@ size_t ON_FixedSizePool::ElementIndex(const void* element_pointer) const return ON_MAX_SIZE_T; } - void* ON_FixedSizePool::ElementFromId( size_t id_offset, unsigned int id @@ -841,6 +731,12 @@ void* ON_FixedSizePool::ElementFromId( const char* next_block; unsigned int i0, i1; size_t count; + if (id_offset < sizeof(void*)) + { + // caller is confused. + ON_ERROR("id_offset is too small."); + return nullptr; + } if (id_offset + sizeof(id) > m_sizeof_element) { // caller is confused. @@ -890,3 +786,188 @@ void* ON_FixedSizePool::ElementFromId( return nullptr; } +unsigned int ON_FixedSizePool::MaximumElementId( + size_t id_offset +) const +{ + const char* block; + const char* block_end; + const char* next_block; + unsigned int maximum_id = 0; + if (id_offset < sizeof(void*)) + { + // caller is confused. + ON_ERROR("id_offset is too small."); + return 0; + } + if (id_offset + sizeof(maximum_id) > m_sizeof_element) + { + // caller is confused. + ON_ERROR("id_offset is too large."); + return 0; + } + + for (block = (const char*)m_first_block; 0 != block; block = next_block) + { + if (block == m_al_block) + { + next_block = nullptr; + block_end = (const char*)m_al_element_array; + block += (2 * sizeof(void*)); + } + else + { + next_block = *((const char**)block); + block += sizeof(void*); + block_end = *((const char**)(block)); + block += sizeof(void*); + } + + unsigned int i1 = *((const unsigned int*)(block_end-(m_sizeof_element-id_offset))); + if (i1 > maximum_id) + maximum_id = i1; + } + + return maximum_id; +} + +bool ON_FixedSizePool::ElementIdIsIncreasing( + size_t id_offset +) const +{ + const char* block; + const char* block_end; + const char* next_block; + const unsigned int* i0; + const unsigned int* i1; + unsigned int prev_id = 0; + unsigned int id; + bool bFirstId = true; + size_t count; + if (0 != ( m_sizeof_element % sizeof(id)) ) + { + // caller is confused. + ON_ERROR("m_sizeof_element must be a multiple of sizeof(unsigned int)."); + return nullptr; + } + if (id_offset < sizeof(void*) ) + { + // caller is confused. + ON_ERROR("id_offset is too small."); + return nullptr; + } + if (id_offset + sizeof(prev_id) > m_sizeof_element) + { + // caller is confused. + ON_ERROR("id_offset is too large."); + return nullptr; + } + + const size_t delta_i = m_sizeof_element / sizeof(id); + for (block = (const char*)m_first_block; 0 != block; block = next_block) + { + if (block == m_al_block) + { + next_block = nullptr; + block_end = (const char*)m_al_element_array; + block += (2 * sizeof(void*)); + } + else + { + next_block = *((const char**)block); + block += sizeof(void*); + block_end = *((const char**)(block)); + block += sizeof(void*); + } + + count = (block_end - block)/m_sizeof_element; + if (0 == count) + continue; + + i0 = ((const unsigned int*)(block + id_offset)); + i1 = ((const unsigned int*)(block_end-(m_sizeof_element-id_offset))); + + if (bFirstId) + { + prev_id = *i0; + bFirstId = false; + i0 += delta_i; + } + while (i0 <= i1) + { + id = *i0; + if (id <= prev_id) + return false; + prev_id = id; + i0 += delta_i; + } + } + + return true; +} + +unsigned int ON_FixedSizePool::ResetElementId( + size_t id_offset, + unsigned int initial_id +) +{ + const char* block; + const char* block_end; + const char* next_block; + unsigned int* i0; + unsigned int* i1; + unsigned int id = initial_id; + size_t count; + if (0 != ( m_sizeof_element % sizeof(id)) ) + { + // caller is confused. + ON_ERROR("m_sizeof_element must be a multiple of sizeof(unsigned int)."); + return 0; + } + if (id_offset < sizeof(void*)) + { + // caller is confused. + ON_ERROR("id_offset is too small."); + return 0; + } + if (id_offset + sizeof(id) > m_sizeof_element) + { + // caller is confused. + ON_ERROR("id_offset is too large."); + return 0; + } + + const size_t delta_i = m_sizeof_element / sizeof(id); + for (block = (const char*)m_first_block; 0 != block; block = next_block) + { + if (block == m_al_block) + { + next_block = nullptr; + block_end = (const char*)m_al_element_array; + block += (2 * sizeof(void*)); + } + else + { + next_block = *((const char**)block); + block += sizeof(void*); + block_end = *((const char**)(block)); + block += sizeof(void*); + } + + count = (block_end - block)/m_sizeof_element; + if (0 == count) + continue; + + i0 = ((unsigned int*)(block + id_offset)); + i1 = ((unsigned int*)(block_end-(m_sizeof_element-id_offset))); + + while (i0 <= i1) + { + *i0 = id++; + i0 += delta_i; + } + } + + return id; +} + diff --git a/opennurbs_fsp.h b/opennurbs_fsp.h index 0efcf5fc..65cce0f2 100644 --- a/opennurbs_fsp.h +++ b/opennurbs_fsp.h @@ -69,12 +69,16 @@ public: /* Returns: A pointer to sizeof_element bytes. The memory is zeroed. + Remarks: + If multiple threads are using this pool, then use ThreadSafeAllocateElement(). */ void* AllocateElement(); /* Returns: A pointer to sizeof_element bytes. The values in the returned block are undefined. + Remarks: + If multiple threads are using this pool, then use ThreadSafeAllocateDirtyElement(). */ void* AllocateDirtyElement(); @@ -87,6 +91,8 @@ public: It is critical that p be from this pool and that you return a pointer no more than one time. Remarks: + If multiple threads are using this pool, then use ThreadSafeReturnElement(). + If you find the following remarks confusing, but you really want to use ReturnElement(), then here are some simple guidelines. 1) SizeofElement() must be >= 16 @@ -114,6 +120,28 @@ public: */ void ReturnElement(void* p); + /* + Description: + Thread safe version of AllocateElement(). + Returns: + A pointer to sizeof_element bytes. The memory is zeroed. + */ + void* ThreadSafeAllocateElement(); + + /* + Description: + Thread safe version of AllocateDirtyElement(). + Returns: + A pointer to sizeof_element bytes. The values in the returned block are undefined. + */ + void* ThreadSafeAllocateDirtyElement(); + + /* + Description: + Thread safe version of ReturnElement(). + */ + void ThreadSafeReturnElement(void* p); + /* Description: Return all allocated elements to the pool. No heap is freed and @@ -197,8 +225,8 @@ public: /* Description: - If you are certain that all elements in hte pool (active and returned) - have an unsigned id that is unique and increasing, then you may use + If you are certain that all elements in the pool (active and returned) + have an unsigned 32-bit id that is unique and increasing, then you may use this function to find them. Parameters: id_offset - [in] @@ -211,6 +239,30 @@ public: unsigned int id ) const; + /* + Description: + If you are certain that all elements in the pool (active and returned) + have an unsigned 32-bit id that is unique and increasing, then you may use + this function to find the maximum assigned id. + Parameters: + id_offset - [in] + offset into the element where the id is stored. + Returns: + maximum id in all elements (active and returned). + */ + unsigned int MaximumElementId( + size_t id_offset + ) const; + + bool ElementIdIsIncreasing( + size_t id_offset + ) const; + + unsigned int ResetElementId( + size_t id_offset, + unsigned int initial_id + ); + public: // Primarily used for debugging bool IsValid() const; @@ -218,20 +270,27 @@ public: private: friend class ON_FixedSizePoolIterator; - void* m_first_block; + void* m_first_block = nullptr; // ReturnElement() adds to the m_al_element stack. // AllocateElement() will use the stack before using m_al_element_array[] - void* m_al_element_stack; + void* m_al_element_stack = nullptr; - void* m_al_block; // current element allocation block. + void* m_al_block = nullptr; // current element allocation block. // m_al_element_array[] is in m_al_block and has length m_al_count. - void* m_al_element_array; - size_t m_al_count; - size_t m_sizeof_element; - size_t m_block_element_count; // block element count - size_t m_active_element_count; // number of active elements - size_t m_total_element_count; // total number of elements (active + returned) + void* m_al_element_array = nullptr; + size_t m_al_count = 0; + size_t m_sizeof_element = 0; + size_t m_block_element_count = 0; // block element count + + //size_t m_active_element_count = 0; // number of active elements + //size_t m_total_element_count = 0; // total number of elements (active + returned) + + unsigned int m_active_element_count = 0; // number of active elements + unsigned int m_total_element_count = 0; // total number of elements (active + returned) + ON_SleepLock m_sleep_lock; + unsigned int m_reserved0 = 0; + private: // returns capacity of elements in existing block diff --git a/opennurbs_glyph_outline.cpp b/opennurbs_glyph_outline.cpp index d7e307ab..b7ec56f3 100644 --- a/opennurbs_glyph_outline.cpp +++ b/opennurbs_glyph_outline.cpp @@ -2964,10 +2964,10 @@ bool ON_FontGlyph::GetOutline( if (nullptr != ON_Font::Internal_CustomGetGlyphOutlineFunc) { rc = ON_Font::Internal_CustomGetGlyphOutlineFunc( - this, - bSingleStrokeFont, - outline - ); + this, + bSingleStrokeFont, + outline + ); } else { @@ -2988,7 +2988,11 @@ bool ON_FontGlyph::GetOutline( #elif defined(OPENNURBS_FREETYPE_SUPPORT) // Look in opennurbs_system_rumtime.h for the correct place to define OPENNURBS_FREETYPE_SUPPORT. // Do NOT define OPENNURBS_FREETYPE_SUPPORT here or in your project setting ("makefile"). - // Use freetype based tools (least reliable results) + // Use freetype based tools (least reliable results). + // Freetype is basically a file reading utility that can parse outline + // information in older formats of font files. It fails on newer formats + // and it also commonly fails to correctly map UNICODE code points to + // glyph indices. Freetype should only be used as a last resort. rc = ON_FreeTypeGetGlyphOutline( this, font_figure_type, diff --git a/opennurbs_hatch.cpp b/opennurbs_hatch.cpp index a1a7956a..b3d03a1d 100644 --- a/opennurbs_hatch.cpp +++ b/opennurbs_hatch.cpp @@ -2019,3 +2019,26 @@ ON_2dPoint ON_Hatch::BasePoint2d() const { return m_basepoint; } + +ON_CurveRegionBoundaryElement::ON_CurveRegionBoundaryElement() + : m_curve_id(-1), m_bReversed(false) +{} +ON_CurveRegionBoundaryElement::ON_CurveRegionBoundaryElement(const ON_CurveRegionBoundaryElement& src) +{ + *this = src; +} + +ON_CurveRegionBoundaryElement::~ON_CurveRegionBoundaryElement() +{} + +ON_CurveRegionBoundaryElement& ON_CurveRegionBoundaryElement::operator=(const ON_CurveRegionBoundaryElement& src) +{ + if (this != &src){ + m_curve_id = src.m_curve_id; + m_subdomain = src.m_subdomain; + m_bReversed = src.m_bReversed; + } + return *this; +} + + diff --git a/opennurbs_hatch.h b/opennurbs_hatch.h index e760983e..76065fbe 100644 --- a/opennurbs_hatch.h +++ b/opennurbs_hatch.h @@ -892,4 +892,26 @@ private: int m_pattern_index = -1; }; +//Part of a boundary. An element has a curve subdomain and a flag to say +//whether that piece of curve should be reversed +class ON_CLASS ON_CurveRegionBoundaryElement +{ +public : + ON_CurveRegionBoundaryElement(); + ON_CurveRegionBoundaryElement(const ON_CurveRegionBoundaryElement& src); + ~ON_CurveRegionBoundaryElement(); + ON_CurveRegionBoundaryElement& operator=(const ON_CurveRegionBoundaryElement& src); + int m_curve_id; + ON_Interval m_subdomain; + bool m_bReversed; +}; + +//A list of curve subdomains that form a closed boundary with active space on the left. +typedef ON_ClassArray ON_CurveRegionBoundary; + +//A list of region boundaries that bound a single connected region of the plane. +//The first boundary is always the outer boundary. +typedef ON_ClassArray ON_CurveRegion; + + #endif diff --git a/opennurbs_internal_V5_annotation.cpp b/opennurbs_internal_V5_annotation.cpp index b8b9c77d..06d68d52 100644 --- a/opennurbs_internal_V5_annotation.cpp +++ b/opennurbs_internal_V5_annotation.cpp @@ -920,9 +920,7 @@ ON_Object* ON_BinaryArchive::Internal_ConvertObject( ON_Mesh* mesh = nullptr; if ( Archive3dmVersion() < 60 ) { - // Use an ordinary mesh to V5 and earlier file formats. - const ON_SubDDisplayParameters dp = ON_SubDMeshProxyUserData::MeshProxyDisplayParameters(); - mesh = subd->GetLimitSurfaceMesh(dp, nullptr); + mesh = subd->GetControlNetMesh(nullptr); } else if ( ON_Internal_UseSubDMeshProxy(*this) ) { diff --git a/opennurbs_internal_glyph.h b/opennurbs_internal_glyph.h index 5cb478d1..e2cc4bb6 100644 --- a/opennurbs_internal_glyph.h +++ b/opennurbs_internal_glyph.h @@ -130,7 +130,7 @@ public: const class ON_FontGlyph* FindGlyph( const ON__UINT32 unicode_code_point ) const; - + // returns pointer to the persistent glyph item const ON_FontGlyph* InsertGlyph( const ON_FontGlyph& glyph @@ -142,7 +142,7 @@ private: friend class ON_Font; friend class ON_FontGlyph; unsigned int m_glyph_count = 0; - unsigned int m_reserved = 0; + mutable ON_SleepLock m_sleep_lock; ON_SimpleArray< const class ON_FontGlyph* > m_glyphs; }; diff --git a/opennurbs_material.cpp b/opennurbs_material.cpp index 7307ee09..02d67b98 100644 --- a/opennurbs_material.cpp +++ b/opennurbs_material.cpp @@ -1815,7 +1815,7 @@ ON_Color ON_Material::PreviewColor(void) const } } - return m_diffuse; + return Diffuse( ); } bool ON_Material::UseDiffuseTextureAlphaForObjectTransparencyTexture() const @@ -6294,3 +6294,31 @@ bool ON_TextureMapping::GetMappingSphere(ON_Sphere& sphere) const return rc && sphere.IsValid(); } + + + +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::BaseColor(void) { return L"pbr-base-color"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::BRDF(void) { return L"pbr-brdf"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Subsurface(void) { return L"pbr-subsurface"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::SubsurfaceScatteringColor(void) { return L"pbr-subsurface-scattering-color"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::SubsurfaceScatteringRadius(void) { return L"pbr-subsurface-scattering-radius"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Specular(void) { return L"pbr-specular"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::SpecularTint(void) { return L"pbr-specular-tint"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Metallic(void) { return L"pbr-metallic"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Roughness(void) { return L"pbr-roughness"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Anisotropic(void) { return L"pbr-anisotropic"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::AnisotropicRotation(void) { return L"pbr-anisotropic-rotation"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Sheen(void) { return L"pbr-sheen"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::SheenTint(void) { return L"pbr-sheen-tint"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Clearcoat(void) { return L"pbr-clearcoat"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::ClearcoatRoughness(void) { return L"pbr-clearcoat-roughness"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::OpacityIor(void) { return L"pbr-opacity-ior"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Opacity(void) { return L"pbr-opacity"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::OpacityRoughness(void) { return L"pbr-opacity-roughness"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Emission(void) { return L"pbr-emission"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::AmbientOcclusion(void) { return L"pbr-ambient-occlusion"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Smudge(void) { return L"pbr-smudge"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Displacement(void) { return L"pbr-displacement"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Normal(void) { return L"pbr-normal"; } +ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Bump(void) { return L"pbr-bump"; } + diff --git a/opennurbs_material.h b/opennurbs_material.h index 9022764d..78013d7a 100644 --- a/opennurbs_material.h +++ b/opennurbs_material.h @@ -543,5 +543,38 @@ ON_DLL_TEMPLATE template class ON_CLASS ON_ObjectArray; // will occur when user data back pointers are not updated. #endif +class ON_CLASS ON_PhysicallyBasedMaterial +{ +public: + class ParametersNames + { + public: + static ON_wString BaseColor(void); + static ON_wString BRDF(void); + static ON_wString Subsurface(void); + static ON_wString SubsurfaceScatteringColor(void); + static ON_wString SubsurfaceScatteringRadius(void); + static ON_wString Specular(void); + static ON_wString SpecularTint(void); + static ON_wString Metallic(void); + static ON_wString Roughness(void); + static ON_wString Anisotropic(void); + static ON_wString AnisotropicRotation(void); + static ON_wString Sheen(void); + static ON_wString SheenTint(void); + static ON_wString Clearcoat(void); + static ON_wString ClearcoatRoughness(void); + static ON_wString OpacityIor(void); + static ON_wString Opacity(void); + static ON_wString OpacityRoughness(void); + static ON_wString Emission(void); + static ON_wString AmbientOcclusion(void); + static ON_wString Smudge(void); + static ON_wString Displacement(void); + static ON_wString Normal(void); + static ON_wString Bump(void); + }; +}; + #endif diff --git a/opennurbs_math.cpp b/opennurbs_math.cpp index 7cf5f2e3..3a6e5b4f 100644 --- a/opennurbs_math.cpp +++ b/opennurbs_math.cpp @@ -1769,6 +1769,7 @@ ON_ComparePointList( // returns ); double A[3] = {0.0,0.0,0.0}; double B[3] = {0.0,0.0,0.0}; + //const size_t AB_size = dim*sizeof(A[0]); for ( i = 0; i < point_count && !rc; i++ ) { @@ -4588,3 +4589,332 @@ double ON_LinearInterpolation(double t, double x, double y) return z; } + +static +bool ON_SymTriDiag3x3EigenSolver(double A, double B, double C, + double D, double E, + double* e1, ON_3dVector& E1, + double* e2, ON_3dVector& E2, + double* e3, ON_3dVector& E3); + +static +bool TriDiagonalQLImplicit(double* d, double* e, int n, ON_Matrix* pV); + +/* +Description: +Find the eigen values and eigen vectors of a real symmetric +3x3 matrix + +A D F +D B E +F E C + +Parameters: +A - [in] matrix entry +B - [in] matrix entry +C - [in] matrix entry +D - [in] matrix entry +E - [in] matrix entry +F - [in] matrix entry +e1 - [out] eigen value +E1 - [out] eigen vector with eigen value e1 +e2 - [out] eigen value +E2 - [out] eigen vector with eigen value e2 +e3 - [out] eigen value +E3 - [out] eigen vector with eigen value e3 +Returns: +True if successful. +*/ +bool ON_Sym3x3EigenSolver(double A, double B, double C, + double D, double E, double F, + double* e1, ON_3dVector& E1, + double* e2, ON_3dVector& E2, + double* e3, ON_3dVector& E3 +) +{ + // STEP 1: reduce to tri-diagonal form + double cos_phi = 1.0; + double sin_phi = 0.0; + double AA = A, BB = B, CC = C, DD = D, EE = E; + if (F != 0.0) + { + double theta = 0.5*(C - A) / F; + + double t; + if (fabs(theta) > 1.0e154) + { + t = 0.5 / fabs(theta); + } + else if (fabs(theta) > 1.0) + { + t = 1.0 / (fabs(theta)*(1.0 + sqrt(1.0 + 1.0 / (theta*theta)))); + } + else + { + t = 1.0 / (fabs(theta) + sqrt(1.0 + theta * theta)); + } + + if (theta < 0.0) + t = -t; + + if (fabs(t) > 1.0) + { + double tt = 1.0 / t; + cos_phi = 1.0 / (fabs(t)*sqrt(1.0 + tt * tt)); + } + else + cos_phi = 1.0 / sqrt(1.0 + t * t); + + sin_phi = t * cos_phi; + + double tau = sin_phi / (1.0 + cos_phi); + + /* Debug only: check the algebra + double delAA, delCC, delDD, delEE; + delAA = cos_phi*cos_phi*A - 2 * cos_phi*sin_phi*F + sin_phi*sin_phi*C; + delCC = sin_phi*sin_phi*A + 2 * cos_phi*sin_phi*F + cos_phi*cos_phi*C; + delDD = cos_phi*D - sin_phi*E; + delEE = cos_phi*E + sin_phi*D; + */ + + AA = A - t * F; + BB = B; + CC = C + t * F; + DD = D - sin_phi * (E + tau * D); + EE = E + sin_phi * (D - tau * E); + + /* debug only test - FF should be close to zero. + delAA = AA - delAA; + delCC = CC - delCC; + delDD = DD - delDD; + delEE = EE - delEE; + double one = cos_phi*cos_phi + sin_phi*sin_phi; // should be close to 1 + double FF = (cos_phi*cos_phi - sin_phi*sin_phi)*F + sin_phi*cos_phi*(A-C); + */ + } + + // STEP 2: EigenSolve the tri-diagonal matrix + double ee1, ee2, ee3; + ON_3dVector EE1, EE2, EE3; + bool rc = ON_SymTriDiag3x3EigenSolver(AA, BB, CC, DD, EE, + &ee1, EE1, + &ee2, EE2, + &ee3, EE3); + + /* Step 3. Apply rotation to express results in orignal coordinate system */ + E1.Set(cos_phi*EE1.x + sin_phi * EE1.z, EE1.y, -sin_phi * EE1.x + cos_phi * EE1.z); + E2.Set(cos_phi*EE2.x + sin_phi * EE2.z, EE2.y, -sin_phi * EE2.x + cos_phi * EE2.z); + E3.Set(cos_phi*EE3.x + sin_phi * EE3.z, EE3.y, -sin_phi * EE3.x + cos_phi * EE3.z); + + if (e1) + *e1 = ee1; + if (e2) + *e2 = ee2; + if (e3) + *e3 = ee3; + + /* debugging check of results + { + err1.x = (A*E1.x + D*E1.y + F*E1.z) - ee1*E1.x; + err1.y = (D*E1.x + B*E1.y + E*E1.z) - ee1*E1.y; + err1.z = (F*E1.x + E*E1.y + C*E1.z) - ee1*E1.z; + + err2.x = (A*E2.x + D*E2.y + F*E2.z) - ee2*E2.x; + err2.y = (D*E2.x + B*E2.y + E*E2.z) - ee2*E2.y; + err2.z = (F*E2.x + E*E2.y + C*E2.z) - ee2*E2.z; + + err3.x = (A*E3.x + D*E3.y + F*E3.z) - ee3*E3.x; + err3.y = (D*E3.x + B*E3.y + E*E3.z) - ee3*E3.y; + err3.z = (F*E3.x + E*E3.y + C*E3.z) - ee3*E3.z; + } + */ + + return rc; +} + + +/* +Description: +Find the eigen values and eigen vectors of a tri-diagonal +real symmetric 3x3 matrix + +A D 0 +D B E +0 E C + +Parameters: +A - [in] matrix entry +B - [in] matrix entry +C - [in] matrix entry +D - [in] matrix entry +E - [in] matrix entry +e1 - [out] eigen value +E1 - [out] eigen vector with eigen value e1 +e2 - [out] eigen value +E2 - [out] eigen vector with eigen value e2 +e3 - [out] eigen value +E3 - [out] eigen vector with eigen value e3 +Returns: +True if successful. +*/ +bool ON_SymTriDiag3x3EigenSolver(double A, double B, double C, + double D, double E, + double* e1, ON_3dVector& E1, + double* e2, ON_3dVector& E2, + double* e3, ON_3dVector& E3 +) +{ + + double d[3] = { A,B,C }; + double e[3] = { D,E,0 }; + + ON_Matrix V(3, 3); + bool rc = TriDiagonalQLImplicit(d, e, 3, &V); + if (rc) + { + if (e1) *e1 = d[0]; + E1 = ON_3dVector(V[0][0], V[1][0], V[2][0]); + if (e2) *e2 = d[1]; + E2 = ON_3dVector(V[0][1], V[1][1], V[2][1]); + if (e3) *e3 = d[2]; + E3 = ON_3dVector(V[0][2], V[1][2], V[2][2]); + } + return rc; +} + + +/* +Description: + QL Algorithm with implict shifts, to determine the eigenvalues and eigenvectors of a + symmetric, tridiagonal matrix. + +Parametrers: + d - [in/out] On input d[0] to d[n-1] are the diagonal entries of the matrix. + As output d[0] to d[n-1] are the eigenvalues. + e - [in/out] On Input e[0] to e[n-1] are the off-diagonal entries of the matrix. + with e[n-1] not used, but must be allocated. + on output e is unpredictable. +n - [in] matrix is n by n +pV - [out] If not nullptr the it should be an n by n matix. + The kth column will be a normalized eigenvector of d[k] +*/ +bool TriDiagonalQLImplicit(double* d, double* e, int n, ON_Matrix* pV) +{ + /* Debug code + ON_SimpleArray OrigD(n); + ON_SimpleArray OrigE(n); + ON_SimpleArray Test(n); + + + OrigD.SetCount(n); + OrigE.SetCount(n); + Test.SetCount(n); + + for (int i = 0; i < n; i++) + { + OrigD[i] = d[i]; + OrigE[i] = e[i]; + } + // End of debug code */ + + if (pV) + { + if (pV->RowCount() != n || pV->ColCount() != n) + pV = nullptr; + } + + if (pV) + pV->SetDiagonal(1.0); + + e[n - 1] = 0.0; + + for (int l = 0; l= 0) ? (g + fabs(r)) : (g - fabs(r))); + double s = 1.0; + double c = 1.0; + double p = 0.0; + int i; + for (i = m - 1; i >= l; i--) + { + double f = s * e[i]; + double b = c * e[i]; + r = sqrt(f*f + g * g); + e[i + 1] = r; + if (r == 0.0) + { + d[i + 1] -= p; + e[m] = 0.0; + break; + } + s = f / r; + c = g / r; + g = d[i + 1] - p; + r = (d[i] - g) *s + 2.0*c*b; + + p = s * r; + d[i + 1] = g + p; + g = c * r - b; + + for (int k = 0; pV && k= l) + continue; + d[l] -= p; + e[l] = g; + e[m] = 0.0; + } + } while (m != l); + } + + /* Debug ONLY code + // verify results errors stored + // e[k] = | T * V(:k) - d[k] V(:k) | + + ON_Matrix & V = *pV; + + for (int k = 0; k < n; k++) + { + + double len2 = 0.0; + for (int i = 0; i < n; i++) + { + Test[i] = OrigD[i] * V[i][k]; + + Test[i] += (i>0)? OrigE[i - 1] * V[i - 1][k] : 0.0; + Test[i] += (i < n - 1) ? OrigE[i] * V[i + 1][k] : 0.0; + Test[i] += - d[k] * V[i][k]; + len2 += Test[i] * Test[i]; + } + e[k] = sqrt(len2); + } + + */ + + return true; +} + + + + diff --git a/opennurbs_mesh.cpp b/opennurbs_mesh.cpp index de7ee76f..866f3650 100644 --- a/opennurbs_mesh.cpp +++ b/opennurbs_mesh.cpp @@ -2521,11 +2521,29 @@ bool ON_Mesh::Read( ON_BinaryArchive& file ) { bool bHasDoublePrecisionVertices = false; rc = file.ReadBool(&bHasDoublePrecisionVertices); + bool bHasInvalidDoublePrecisionVertices = false; if (bHasDoublePrecisionVertices) { // Added explicit double precision vertices chunk version 3.7 // (used to be on user data) rc = ReadMeshDoublePrecisionVertices(file, m_dV); + if (rc && m_dV.UnsignedCount() == m_V.UnsignedCount()) + { + // Validate m_dV[] - some files contain different values for m_V[] and m_dV[]. + // When this happens, delete m_dV[]. + const unsigned int dvcount = m_dV.UnsignedCount(); + for (unsigned int vdex = 0; vdex < dvcount; vdex++) + { + const ON_3fPoint P(m_dV[vdex]); + const double m = fabs(m_V[vdex].MaximumCoordinate()); + const double d = fabs((P - m_V[vdex]).MaximumCoordinate()); + if (d <= m*1.0e-6) + continue; + bHasInvalidDoublePrecisionVertices = true; + m_dV.Destroy(); + break; + } + } if (rc && m_dV.UnsignedCount() == m_V.UnsignedCount() && minor_version <= 7) { m_vertex_bbox.Set(m_dV, false); @@ -2535,6 +2553,10 @@ bool ON_Mesh::Read( ON_BinaryArchive& file ) { rc = file.ReadBoundingBox(m_vertex_bbox); } + if (bHasInvalidDoublePrecisionVertices) + { + m_vertex_bbox.Set(m_V, false); + } } } } @@ -7709,6 +7731,9 @@ bool ON_Mesh::EvaluateMeshGeometry( const ON_Surface& srf ) const bool bHasSurfaceParameters = HasSurfaceParameters(); if ( bHasSurfaceParameters ) { + const bool bHasDoublePrecisionVertices = this->HasDoublePrecisionVertices(); + if (false == bHasDoublePrecisionVertices) + this->m_dV.Destroy(); const bool bHasVertexNormals = HasVertexNormals(); m_N.SetCapacity(vcount); int vi, side, hint[2]; @@ -7739,9 +7764,11 @@ bool ON_Mesh::EvaluateMeshGeometry( const ON_Surface& srf ) &kgauss, &kmean, &m_K[vi].k1, &m_K[vi].k2, K1, K2 ); //m_K[vi].e1, m_K[vi].e2 ); - m_V[vi] = &point.x; // use ON_3fPoint double* conversion (quiets gcc) + m_V[vi] = ON_3fPoint(&point.x); // use ON_3fPoint double* conversion (quiets gcc) + if (bHasDoublePrecisionVertices) + m_dV[vi] = point; if ( bHasVertexNormals ) - m_N[vi] = &normal.x; // use ON_3fVector double* conversion (quiets gcc) + m_N[vi] = ON_3fVector(&normal.x); // use ON_3fVector double* conversion (quiets gcc) } InvalidateCurvatureStats(); } @@ -7757,8 +7784,10 @@ bool ON_Mesh::EvaluateMeshGeometry( const ON_Surface& srf ) // are correctly evaluated RR 12482 side = ( smax == s ) ? ((tmax == t) ? 3 : 2) : ((tmax == t) ? 4 : 1); srf.EvNormal( s, t, point, normal, side, hint ); - m_V[vi] = &point.x; // use ON_3fPoint double* conversion (quiets gcc) - m_N[vi] = &normal.x; // use ON_3fVector double* conversion (quiets gcc) + m_V[vi] = ON_3fPoint(&point.x); // use ON_3fPoint double* conversion (quiets gcc) + if (bHasDoublePrecisionVertices) + m_dV[vi] = point; + m_N[vi] = ON_3fVector(&normal.x); // use ON_3fVector double* conversion (quiets gcc) } } else @@ -7770,7 +7799,9 @@ bool ON_Mesh::EvaluateMeshGeometry( const ON_Surface& srf ) s = srf_st->x; t = srf_st->y; srf.EvPoint( s, t, point, side, hint ); - m_V[vi] = &point.x; + m_V[vi] = ON_3fPoint(&point.x); + if (bHasDoublePrecisionVertices) + m_dV[vi] = point; } } if ( HasFaceNormals() ) @@ -9795,6 +9826,62 @@ void ON_MeshComponentRef::Set( m_mesh_ci = ci; } +int ON_MeshComponentRef::CompareMeshPointer(const ON_MeshComponentRef* lhs, const ON_MeshComponentRef* rhs) +{ + if (lhs == rhs) + return 0; + + // nullptrs are last + if (nullptr == lhs) + return 1; + if (nullptr == rhs) + return -1; + + const ON__UINT_PTR lhs_ptr = (ON__UINT_PTR)lhs->m_mesh; + const ON__UINT_PTR rhs_ptr = (ON__UINT_PTR)rhs->m_mesh; + if (lhs_ptr < rhs_ptr) + return -1; + if (lhs_ptr > rhs_ptr) + return 1; + + return 0; +} + + +int ON_MeshComponentRef::Compare(const ON_MeshComponentRef* lhs, const ON_MeshComponentRef* rhs) +{ + if (lhs == rhs) + return 0; + + // nullptrs are last + if (nullptr == lhs) + return 1; + if (nullptr == rhs) + return -1; + + const ON__UINT_PTR lhs_ptr = (ON__UINT_PTR)lhs->m_mesh; + const ON__UINT_PTR rhs_ptr = (ON__UINT_PTR)rhs->m_mesh; + if (lhs_ptr < rhs_ptr) + return -1; + if (lhs_ptr > rhs_ptr) + return 1; + + return ON_COMPONENT_INDEX::Compare(&lhs->m_mesh_ci, &rhs->m_mesh_ci); +} + +int ON_MeshComponentRef::Compare2(const ON_MeshComponentRef*const* lhs, const ON_MeshComponentRef*const* rhs) +{ + if (lhs == rhs) + return 0; + + // nullptrs are last + if (nullptr == lhs) + return 1; + if (nullptr == rhs) + return -1; + + return ON_MeshComponentRef::Compare(*lhs, *rhs); +} bool ON_MeshComponentRef::IsValid( ON_TextLog* text_log ) const { @@ -10327,6 +10414,35 @@ ON_MeshComponentRef ON_Mesh::MeshComponentRef( return cr; } +const ON_COMPONENT_INDEX ON_MeshTopology::TopVertexComponentIndex( + ON_COMPONENT_INDEX ci +) const +{ + switch (ci.m_type) + { + case ON_COMPONENT_INDEX::TYPE::mesh_vertex: + if (ci.m_index >= 0 + && nullptr != m_mesh + && ci.m_index < m_mesh->VertexCount() + && m_mesh->VertexCount() == m_topv_map.Count() + ) + { + ci.m_type = ON_COMPONENT_INDEX::TYPE::meshtop_vertex; + ci.m_index = m_topv_map[ci.m_index]; + } + // no break here + case ON_COMPONENT_INDEX::TYPE::meshtop_vertex: + if (ci.m_index >= 0 && ci.m_index < m_topv.Count()) + { + return ci; + } + break; + } + + return ON_COMPONENT_INDEX::UnsetComponentIndex; +} + + ON_MeshComponentRef ON_MeshTopology::MeshComponentRef(ON_COMPONENT_INDEX ci) const { ON_MeshComponentRef cr; diff --git a/opennurbs_mesh.h b/opennurbs_mesh.h index 98648771..3aab0f5e 100644 --- a/opennurbs_mesh.h +++ b/opennurbs_mesh.h @@ -1939,6 +1939,21 @@ public: ON_COMPONENT_INDEX ci ) const; + /* + Parameters: + ci - [in] + A component index with type of + ON_COMPONENT_INDEX::TYPE::mesh_vertex or + ON_COMPONENT_INDEX::TYPE::meshtop_vertex. + Return: + If ci correctly identifes a mesh topology vertex, then + component index with type of ON_COMPONENT_INDEX::TYPE::meshtop_vertex is returned. + Otherwise ON_COMPONENT_INDEX::UnsetComponentIndex is returned. + */ + const ON_COMPONENT_INDEX TopVertexComponentIndex( + ON_COMPONENT_INDEX ci + ) const; + /* Description: Get the 3d point location of a vertex. @@ -4685,6 +4700,29 @@ public: ~ON_MeshComponentRef(); ON_MeshComponentRef& operator=(const ON_MeshComponentRef&); + /* + Description: + Dictionary compare: + 1) Compare m_mesh pointer values as unsigned values + 2) ON_COMPONENT_INDEX::Compare() m_mesh_ci values + */ + static int Compare(const ON_MeshComponentRef* lhs, const ON_MeshComponentRef* rhs); + + /* + Description: + Dictionary compare: + 1) Compare m_mesh pointer values as unsigned values + 2) ON_COMPONENT_INDEX::Compare() m_mesh_ci values + */ + static int Compare2(const ON_MeshComponentRef* const * lhs, const ON_MeshComponentRef *const* rhs); + + /* + Description: + Compare m_mesh pointer values as unsigned values. + Ignore m_mesh_ci values. + */ + static int CompareMeshPointer(const ON_MeshComponentRef* lhs, const ON_MeshComponentRef* rhs); + private: // referenced mesh const class ON_Mesh* m_mesh; diff --git a/opennurbs_mesh_tools.cpp b/opennurbs_mesh_tools.cpp index 31931c07..06efc683 100644 --- a/opennurbs_mesh_tools.cpp +++ b/opennurbs_mesh_tools.cpp @@ -839,11 +839,11 @@ bool ON_Mesh::DeleteFace( int meshfi ) return rc; } -ON_Mesh* ON_ControlPolygonMesh( - const ON_NurbsSurface& nurbs_surface, - bool bCleanMesh, - ON_Mesh* input_mesh - ) +ON_Mesh* ON_ControlPolygonMesh( + const ON_NurbsSurface& nurbs_surface, + bool bCleanMesh, + ON_Mesh* input_mesh +) { int u0 = 0; int u1 = nurbs_surface.CVCount(0); @@ -904,10 +904,14 @@ ON_Mesh* ON_ControlPolygonMesh( dpv.Reserve(vertex_count); mesh->m_N.Reserve(vertex_count); mesh->m_T.Reserve(vertex_count); + mesh->m_S.Reserve(vertex_count); mesh->m_F.Reserve(face_count); + mesh->m_srf_domain[0] = d0; + mesh->m_srf_domain[1] = d1; ON_3dPoint V; ON_3dVector N; + ON_2dPoint S; ON_2dPoint T; int hint[2] = {0,0}; @@ -915,14 +919,17 @@ ON_Mesh* ON_ControlPolygonMesh( int k = -1; for ( j = v0; j < v1; j++) { - T.y = d1.NormalizedParameterAt(gv[j]); + S.y = gv[j]; + T.y = d1.NormalizedParameterAt(S.y); for ( i = u0; i < u1; i++) { nurbs_surface.GetCV( i, j, V); - T.x = d0.NormalizedParameterAt(gu[i]); + S.x = gu[i]; + T.x = d0.NormalizedParameterAt(S.x); nurbs_surface.EvNormal(gu[i],gv[j],N,0,hint); dpv.AppendNew() = V; mesh->m_N.AppendNew() = N; + mesh->m_S.AppendNew() = S; mesh->m_T.AppendNew() = T; if ( i > u0 && j > v0 ) { diff --git a/opennurbs_model_component.cpp b/opennurbs_model_component.cpp index e7dcbcf6..6207ff91 100644 --- a/opennurbs_model_component.cpp +++ b/opennurbs_model_component.cpp @@ -296,12 +296,6 @@ ON_UUID ON_ModelComponent::ModelObjectId() const return Id(); } -static ON__UINT64 ON_ModelComponentRuntimeSerialNumberGenerator() -{ - static ON__UINT64 sn = 0; - return ++sn; // TODO make this multi-thread safe by using an atomic increment operation - no need for mutexes, etc. -} - static ON__UINT64 ON_ModelComponentContentVersionNumberOne( ON__UINT64 runtime_serial_number ) @@ -312,7 +306,7 @@ static ON__UINT64 ON_ModelComponentContentVersionNumberOne( } ON_ModelComponent::ON_ModelComponent() ON_NOEXCEPT - : m_runtime_serial_number(ON_ModelComponentRuntimeSerialNumberGenerator()) + : m_runtime_serial_number(++ON_ModelComponent::Internal_RuntimeSerialNumberGenerator) , m_content_version_number(ON_ModelComponentContentVersionNumberOne(m_runtime_serial_number)) {} @@ -638,7 +632,7 @@ unsigned int ON_ModelComponent::CopyFrom( ON_ModelComponent::ON_ModelComponent(const ON_ModelComponent& src) : ON_Object(src) - , m_runtime_serial_number(ON_ModelComponentRuntimeSerialNumberGenerator()) + , m_runtime_serial_number(++ON_ModelComponent::Internal_RuntimeSerialNumberGenerator) , m_content_version_number(ON_ModelComponentContentVersionNumberOne(m_runtime_serial_number)) { CopyFrom(src,ON_ModelComponent::Attributes::AllAttributes); @@ -672,7 +666,7 @@ ON_ModelComponent::ON_ModelComponent( const ON_ModelComponent& src ) ON_NOEXCEPT : ON_Object(src) - , m_runtime_serial_number(ON_ModelComponentRuntimeSerialNumberGenerator()) + , m_runtime_serial_number(++ON_ModelComponent::Internal_RuntimeSerialNumberGenerator) , m_content_version_number(ON_ModelComponentContentVersionNumberOne(m_runtime_serial_number)) , m_component_type(component_type) { @@ -689,7 +683,7 @@ ON_ModelComponent::ON_ModelComponent( ON_ModelComponent::ON_ModelComponent( ON_ModelComponent::Type component_type ) ON_NOEXCEPT - : m_runtime_serial_number(ON_ModelComponentRuntimeSerialNumberGenerator()) + : m_runtime_serial_number(++ON_ModelComponent::Internal_RuntimeSerialNumberGenerator) , m_content_version_number(ON_ModelComponentContentVersionNumberOne(m_runtime_serial_number)) , m_component_type(component_type) { @@ -2017,52 +2011,48 @@ bool ON_ModelComponent::UniqueNameIncludesParent( ) { bool bUniqueNameIncludesParent = false; - if (ON_ModelComponent::UniqueNameRequired(component_type)) + // [Giulio] If this table is changed, then also + // RhinoCommon documentation in + // Rhino.DocObjects.ModelComponent.FindName() / FindNameHash + // will require appropriate tweaks. + switch (component_type) { - // [Giulio] If this table is changed, then also - // RhinoCommon documentation in - // Rhino.DocObjects.ModelComponent.FindName() / FindNameHash - // will require appropriate tweaks. - - switch (component_type) - { - case ON_ModelComponent::Type::Unset: - ON_ERROR("Invalid component_type parameter."); - break; - case ON_ModelComponent::Type::Image: - break; - case ON_ModelComponent::Type::TextureMapping: - break; - case ON_ModelComponent::Type::RenderMaterial: - break; - case ON_ModelComponent::Type::LinePattern: - break; - case ON_ModelComponent::Type::Layer: - bUniqueNameIncludesParent = true; - break; - case ON_ModelComponent::Type::Group: - break; - case ON_ModelComponent::Type::TextStyle: - break; - case ON_ModelComponent::Type::DimStyle: - break; - case ON_ModelComponent::Type::RenderLight: - break; - case ON_ModelComponent::Type::HatchPattern: - break; - case ON_ModelComponent::Type::InstanceDefinition: - break; - case ON_ModelComponent::Type::ModelGeometry: - break; - case ON_ModelComponent::Type::HistoryRecord: - break; - case ON_ModelComponent::Type::Mixed: - ON_ERROR("Invalid component_type parameter."); - break; - default: - ON_ERROR("Invalid component_type parameter."); - break; - } + case ON_ModelComponent::Type::Unset: + ON_ERROR("Invalid component_type parameter."); + break; + case ON_ModelComponent::Type::Image: + break; + case ON_ModelComponent::Type::TextureMapping: + break; + case ON_ModelComponent::Type::RenderMaterial: + break; + case ON_ModelComponent::Type::LinePattern: + break; + case ON_ModelComponent::Type::Layer: + bUniqueNameIncludesParent = true; + break; + case ON_ModelComponent::Type::Group: + break; + case ON_ModelComponent::Type::TextStyle: + break; + case ON_ModelComponent::Type::DimStyle: + break; + case ON_ModelComponent::Type::RenderLight: + break; + case ON_ModelComponent::Type::HatchPattern: + break; + case ON_ModelComponent::Type::InstanceDefinition: + break; + case ON_ModelComponent::Type::ModelGeometry: + break; + case ON_ModelComponent::Type::HistoryRecord: + break; + case ON_ModelComponent::Type::Mixed: + ON_ERROR("Invalid component_type parameter."); + break; + default: + ON_ERROR("Invalid component_type parameter."); + break; } return bUniqueNameIncludesParent; } @@ -2128,21 +2118,13 @@ bool ON_ModelComponent::UniqueNameIgnoresCase( ON_ModelComponent::Type component_type ) { + // This function must return true for components like ON_Material that do not require a unique name. return ( - ON_ModelComponent::UniqueNameRequired(component_type) && + // NO // ON_ModelComponent::UniqueNameRequired(component_type) && ON_ModelComponent::Type::Group != component_type ); } -bool UniqueNameIsCaseSensitive( - ON_ModelComponent::Type component_type -) -{ - return ( - ON_ModelComponent::Type::Group == component_type - ); -} - /////////////////////////////////////////////////////////////////////////////// // // Id interface diff --git a/opennurbs_model_component.h b/opennurbs_model_component.h index 6ed754de..972b8fa5 100644 --- a/opennurbs_model_component.h +++ b/opennurbs_model_component.h @@ -144,6 +144,9 @@ public: object names is some type of tree. Remarks: Currently, layers are the only object type where this property is true. + This function should be called "NameIncludesParent" because it + also applies to components like materials and geometry objects + that are not reqired to have a unique name. */ static bool UniqueNameIncludesParent( ON_ModelComponent::Type component_type @@ -153,10 +156,13 @@ public: Parameters: component_type - [in] Returns: - True if component names ignore case when testing for equality. + True if component name ignores case when testing for equality. Remarks: Currently all other component types except for groups ignore case when testing for equality. + This function should be called "NameIgnoresCase" because it + also applies to components like materials and geometry objects + that are not reqired to have a unique name. */ static bool UniqueNameIgnoresCase( ON_ModelComponent::Type component_type @@ -1445,6 +1451,15 @@ private: public: // For internal use. Never call this function. static unsigned int Internal_SystemComponentHelper(); + +private: +#pragma ON_PRAGMA_WARNING_PUSH +#pragma ON_PRAGMA_WARNING_DISABLE_MSC( 4251 ) + // C4251: ... needs to have dll-interface to be used by clients of class ... + // This warning is not correct. + // Internal_RuntimeSerialNumberGenerator is private and all code that manages m_mcr_lists is explicitly implemented in the DLL. + static std::atomic Internal_RuntimeSerialNumberGenerator; +#pragma ON_PRAGMA_WARNING_POP }; diff --git a/opennurbs_nurbscurve.h b/opennurbs_nurbscurve.h index 2b26bf87..926447de 100644 --- a/opennurbs_nurbscurve.h +++ b/opennurbs_nurbscurve.h @@ -1205,4 +1205,15 @@ public: // [ CV(i)[0], ..., CV(i)[m_dim] ]. }; +/* Adjust the second point to be within the domains, when the first point is + on the edge of on of the domains and some other conditions hold. + This function is here because it is needed in several places. It is not + meant for general use. + */ + +ON_DECL +bool ON_Adjust2ndPointToDomain(const ON_2dPoint& First, ON_2dPoint& Second, + const ON_Interval dom[2]); + + #endif diff --git a/opennurbs_object_history.cpp b/opennurbs_object_history.cpp index b2435b89..b9678f12 100644 --- a/opennurbs_object_history.cpp +++ b/opennurbs_object_history.cpp @@ -1858,25 +1858,48 @@ bool ON_HistoryRecord::SetObjRefValues( int value_id, int count, const ON_ObjRef { v->m_value.Destroy(); v->m_value.Reserve(count); - int i; - for ( i = 0; i < count; i++ ) + + if(count) { - // The call to DecrementProxyReferenceCount() is critical. - // It makes sure there are no active runtime pointers - // saved in the history record. If this call is not here, - // you will eventually crash and history update will never - // work right even when it doesn't crash. - ON_ObjRef& vor = v->m_value.AppendNew(); - vor = oref[i]; - vor.DecrementProxyReferenceCount(); - // Feb 12 2010 - Fixing bug in ExtrudeCrv history - // and probably lots of other subtle history bugs. - // History must lookup by UUID and not by runtime serial number. - vor.m_runtime_sn = 0; - ON_UUID object_id = v->m_value[i].m_uuid; - if ( !ON_UuidIsNil(object_id) ) + // 2019-01-23 - kike@mcneel.com + // Objects in instance definitions can not be modified in that case + // I add the root instance reference and all the instance definitions as 'antecedents' + const bool idef_geometry = oref && (oref->m__iref.Count() > 0); + + for(int i = 0; i < count; i++) { - m_antecedents.AddUuid(object_id); + // The call to DecrementProxyReferenceCount() is critical. + // It makes sure there are no active runtime pointers + // saved in the history record. If this call is not here, + // you will eventually crash and history update will never + // work right even when it doesn't crash. + ON_ObjRef& vor = v->m_value.AppendNew(); + vor = oref[i]; + vor.DecrementProxyReferenceCount(); + // Feb 12 2010 - Fixing bug in ExtrudeCrv history + // and probably lots of other subtle history bugs. + // History must lookup by UUID and not by runtime serial number. + vor.m_runtime_sn = 0; + + // 2019-01-23 - kike@mcneel.com + if(!idef_geometry) + { + ON_UUID object_id = v->m_value[i].m_uuid; + if(!ON_UuidIsNil(object_id)) + { + m_antecedents.AddUuid(object_id); + } + } + } + + // 2019-01-23 - kike@mcneel.com + if(idef_geometry) + { + if(auto iref = oref->m__iref.Last()) + m_antecedents.AddUuid(iref->m_iref_uuid); + + for(int r = 0; r < oref->m__iref.Count(); ++r) + m_antecedents.AddUuid(oref->m__iref[r].m_idef_uuid); } } } diff --git a/opennurbs_parse_number.cpp b/opennurbs_parse_number.cpp index 0ca9ddad..461ba077 100644 --- a/opennurbs_parse_number.cpp +++ b/opennurbs_parse_number.cpp @@ -1491,6 +1491,35 @@ int ON_ParseLengthExpression( return str_index; } +static bool Internal_IsInteger( + const wchar_t* str, + int str_count +) +{ + if (nullptr == str || str_count < 1) + return false; + for (int i = 0; i < str_count; ++i) + { + if (str[i] >= '0' && str[i] <= '9') + continue; + return false; + } + return true; +} + +static bool Internal_IsNegativeInteger( + const ON_ParseSettings& parse_settings, + const wchar_t* str, + int str_count +) +{ + if (nullptr == str || str_count < 2 || false == parse_settings.ParseUnaryMinus()) + return false; + if (false == parse_settings.IsUnaryMinus(str[0])) + return false; + return Internal_IsInteger(str + 1, str_count - 1); +} + int ON_ParseAngleExpression( const wchar_t* str, int str_count, @@ -1511,6 +1540,7 @@ int ON_ParseAngleExpression( if ( 0 != parse_results ) *parse_results = pr; + bool bNegativeDegreesMinutesSecondsIsPossible = false; int str_index = 0; ON__UINT32 cSurveyorsNotationNS = 0; ON__UINT32 cSurveyorsNotationEW = 0; @@ -1561,6 +1591,8 @@ int ON_ParseAngleExpression( int str_number_index = ON_ParseNumberExpression(str+str_index,str_number_count,input_parse_settings,&pr1,&x); if ( str_number_index > 0 ) { + if (0 == cSurveyorsNotationNS && x <= 0.0 && Internal_IsNegativeInteger(input_parse_settings, str + str_index, str_number_index - str_index)) + bNegativeDegreesMinutesSecondsIsPossible = true; pr |= pr1; } else @@ -1584,6 +1616,8 @@ int ON_ParseAngleExpression( str_index = ON_ParseNumberExpression(str,str_count,input_parse_settings,&pr,&x); if ( str_index <= 0 ) return 0; + if (0 == cSurveyorsNotationNS && x <= 0.0 && Internal_IsNegativeInteger(input_parse_settings, str, str_index)) + bNegativeDegreesMinutesSecondsIsPossible = true; } if ( str_count > 0 ) @@ -1595,6 +1629,9 @@ int ON_ParseAngleExpression( int end_of_unit_index = ON_ParseAngleUnitName(str+str_index,str_count,parse_settings.PreferedLocaleId(),&angle_us); + if (bNegativeDegreesMinutesSecondsIsPossible && ON::AngleUnitSystem::Degrees != angle_us) + bNegativeDegreesMinutesSecondsIsPossible = false; + if ( end_of_unit_index > 0 ) { if ( str_count > 0 ) @@ -1626,6 +1663,9 @@ int ON_ParseAngleExpression( next_parse_settings.SetParseIntegerDashFraction(false); next_parse_settings.SetParseRationalNumber(false); next_parse_settings.SetParsePi(false); + next_parse_settings.SetParseSurveyorsNotation(false); + next_parse_settings.SetParseUnaryMinus(false); + next_parse_settings.SetParseUnaryPlus(false); double arc_minutes_value = 0.0; double arc_seconds_value = 0.0; @@ -1643,8 +1683,11 @@ int ON_ParseAngleExpression( if ( next_str_index <= 0 ) break; - if ( !(next_value >= 0.0 && next_value < 60.0) ) + if (!(next_value >= 0.0 && next_value < 60.0)) + { + bNegativeDegreesMinutesSecondsIsPossible = false; break; + } if ( 0 == next_value_pass ) { @@ -1653,25 +1696,47 @@ int ON_ParseAngleExpression( if ( ON::AngleUnitSystem::Seconds == next_us && 0 == next_value_pass ) next_value_pass = 1; else + { + bNegativeDegreesMinutesSecondsIsPossible = false; break; + } } } else if ( 1 == next_value_pass ) { - if ( ON::AngleUnitSystem::Seconds != next_us ) + if (ON::AngleUnitSystem::Seconds != next_us) + { + bNegativeDegreesMinutesSecondsIsPossible = false; break; + } } - if ( ON::AngleUnitSystem::Minutes == next_us ) + if (ON::AngleUnitSystem::Minutes == next_us) arc_minutes_value = next_value; - else if ( ON::AngleUnitSystem::Seconds == next_us ) + else if (ON::AngleUnitSystem::Seconds == next_us) arc_seconds_value = next_value; + else + bNegativeDegreesMinutesSecondsIsPossible = false; str_index += next_str_index; pr.SetParseArcDegreesMinutesSeconds(true); pr |= next_pr; } + if ( + bNegativeDegreesMinutesSecondsIsPossible + && x <= 0.0 + && arc_minutes_value >= 0.0 + && arc_seconds_value >= 0.0 + ) + { + // parsing something like -90d40'30" + if (arc_minutes_value > 0.0) + arc_minutes_value = -arc_minutes_value; + if (arc_seconds_value > 0.0) + arc_seconds_value = -arc_seconds_value; + } + if ( 0 != arc_seconds_value ) { x = 60.0*(60.0*x + arc_minutes_value) + arc_seconds_value; @@ -1687,6 +1752,7 @@ int ON_ParseAngleExpression( } else { + bNegativeDegreesMinutesSecondsIsPossible = false; angle_us = parse_settings.DefaultAngleUnitSystem(); } diff --git a/opennurbs_plane.h b/opennurbs_plane.h index 9432416d..dae820cf 100644 --- a/opennurbs_plane.h +++ b/opennurbs_plane.h @@ -520,6 +520,9 @@ public: const static ON_Plane UnsetPlane; + // All values are ON_DBL_QNAN. + const static ON_Plane NanPlane; + public: // origin of plane ON_3dPoint origin; diff --git a/opennurbs_point.h b/opennurbs_point.h index 236adc4e..733ca47f 100644 --- a/opennurbs_point.h +++ b/opennurbs_point.h @@ -1470,6 +1470,7 @@ public: static const ON_PlaneEquation UnsetPlaneEquation; // (ON_UNSET_VALUE,ON_UNSET_VALUE,ON_UNSET_VALUE,ON_UNSET_VALUE) static const ON_PlaneEquation ZeroPlaneEquation; // (0.0,0.0,0.0,0.0) + static const ON_PlaneEquation NanPlaneEquation; // (ON_DBL_QNAN,ON_DBL_QNAN,ON_DBL_QNAN,ON_DBL_QNAN) ON_PlaneEquation(); diff --git a/opennurbs_polycurve.cpp b/opennurbs_polycurve.cpp index 638cf906..cd2ed219 100644 --- a/opennurbs_polycurve.cpp +++ b/opennurbs_polycurve.cpp @@ -913,15 +913,34 @@ static bool GetTestPlane( const ON_Curve& curve, ON_Plane& plane ) } Q = P+X; + double best_dot = 1.0; + ON_3dPoint best_Y = ON_3dPoint::Origin; + for ( i = 2; i <= 16; i += 2 ) { for ( j = 1; j < i; j += 2 ) { R = curve.PointAt( d.ParameterAt( ((double)j)/((double)i) ) ); - if ( plane.CreateFromFrame( P, X, R-P ) ) - return true; + ON_3dVector Y = R - P; + if (!Y.Unitize()) + continue; + if (! X.IsParallelTo (Y)){ + if ( plane.CreateFromFrame( P, X, Y ) ) + return true; + } + else { + double dot = fabs(X*Y); + if (dot < best_dot){ + best_Y = Y; + best_dot = dot; + } + } } } + if (best_dot < 1.0){ + if ( plane.CreateFromFrame( P, X, best_Y ) ) + return true; + } return false; } diff --git a/opennurbs_public.vcxproj b/opennurbs_public.vcxproj index 402131e0..7d59f3ca 100644 --- a/opennurbs_public.vcxproj +++ b/opennurbs_public.vcxproj @@ -147,7 +147,6 @@ - @@ -248,6 +247,7 @@ + @@ -261,6 +261,7 @@ + @@ -408,6 +409,7 @@ + @@ -438,6 +440,7 @@ + diff --git a/opennurbs_public.xcodeproj/project.pbxproj b/opennurbs_public.xcodeproj/project.pbxproj index ba1d61c4..8ca39de6 100644 --- a/opennurbs_public.xcodeproj/project.pbxproj +++ b/opennurbs_public.xcodeproj/project.pbxproj @@ -11,7 +11,11 @@ 1D4452FF1ED6454500CD7FC1 /* opennurbs.h in Headers */ = {isa = PBXBuildFile; fileRef = 1D4452FE1ED6454500CD7FC1 /* opennurbs.h */; }; 1D455699216D9DA100BC992F /* opennurbs_apple_nsfont.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1D455698216D9DA100BC992F /* opennurbs_apple_nsfont.cpp */; }; 1D45569B216D9DAE00BC992F /* opennurbs_apple_nsfont.h in Headers */ = {isa = PBXBuildFile; fileRef = 1D45569A216D9DAE00BC992F /* opennurbs_apple_nsfont.h */; }; + 1D741C1B21B9E3C700AA10E5 /* opennurbs_sleeplock.h in Headers */ = {isa = PBXBuildFile; fileRef = 1D741C1A21B9E3C600AA10E5 /* opennurbs_sleeplock.h */; }; + 1D741C1D21B9E3D700AA10E5 /* opennurbs_sleeplock.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1D741C1C21B9E3D600AA10E5 /* opennurbs_sleeplock.cpp */; }; 1D7B99581FF3009600E9A791 /* opennurbs_unicode_cpsb.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1D7B99571FF3009600E9A791 /* opennurbs_unicode_cpsb.cpp */; }; + 1D97149D218BC3A1008B4D65 /* opennurbs_testclass.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1D97149C218BC3A1008B4D65 /* opennurbs_testclass.cpp */; }; + 1D97149F218BC3B5008B4D65 /* opennurbs_testclass.h in Headers */ = {isa = PBXBuildFile; fileRef = 1D97149E218BC3B5008B4D65 /* opennurbs_testclass.h */; }; 1DBFBF3A1EDF3092005B50AF /* opennurbs_public_memory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1DBFBF391EDF3092005B50AF /* opennurbs_public_memory.cpp */; }; 1DBFBF3C1EDF333C005B50AF /* opennurbs_memory_util.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1DBFBF3B1EDF333C005B50AF /* opennurbs_memory_util.cpp */; }; 1DC317C81ED652B800DE6D26 /* opennurbs_3dm_attributes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1DC317601ED652B700DE6D26 /* opennurbs_3dm_attributes.cpp */; }; @@ -340,7 +344,11 @@ 1D4452FE1ED6454500CD7FC1 /* opennurbs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = opennurbs.h; sourceTree = ""; }; 1D455698216D9DA100BC992F /* opennurbs_apple_nsfont.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = opennurbs_apple_nsfont.cpp; sourceTree = ""; }; 1D45569A216D9DAE00BC992F /* opennurbs_apple_nsfont.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = opennurbs_apple_nsfont.h; sourceTree = ""; }; + 1D741C1A21B9E3C600AA10E5 /* opennurbs_sleeplock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = opennurbs_sleeplock.h; sourceTree = ""; }; + 1D741C1C21B9E3D600AA10E5 /* opennurbs_sleeplock.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = opennurbs_sleeplock.cpp; sourceTree = ""; }; 1D7B99571FF3009600E9A791 /* opennurbs_unicode_cpsb.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = opennurbs_unicode_cpsb.cpp; sourceTree = ""; }; + 1D97149C218BC3A1008B4D65 /* opennurbs_testclass.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = opennurbs_testclass.cpp; sourceTree = ""; }; + 1D97149E218BC3B5008B4D65 /* opennurbs_testclass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = opennurbs_testclass.h; sourceTree = ""; }; 1DB0280D1ED6421900FA9144 /* libopennurbs_public.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libopennurbs_public.a; sourceTree = BUILT_PRODUCTS_DIR; }; 1DBFBF391EDF3092005B50AF /* opennurbs_public_memory.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = opennurbs_public_memory.cpp; sourceTree = ""; }; 1DBFBF3B1EDF333C005B50AF /* opennurbs_memory_util.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = opennurbs_memory_util.cpp; sourceTree = ""; }; @@ -700,6 +708,8 @@ 1DC319EF1ED653EE00DE6D26 /* Header Files */ = { isa = PBXGroup; children = ( + 1D741C1A21B9E3C600AA10E5 /* opennurbs_sleeplock.h */, + 1D97149E218BC3B5008B4D65 /* opennurbs_testclass.h */, 1D45569A216D9DAE00BC992F /* opennurbs_apple_nsfont.h */, 1DC317611ED652B700DE6D26 /* opennurbs_3dm_attributes.h */, 1DC317631ED652B700DE6D26 /* opennurbs_3dm_properties.h */, @@ -858,6 +868,8 @@ 1DC319F01ED6546C00DE6D26 /* Source Files */ = { isa = PBXGroup; children = ( + 1D741C1C21B9E3D600AA10E5 /* opennurbs_sleeplock.cpp */, + 1D97149C218BC3A1008B4D65 /* opennurbs_testclass.cpp */, 1D455698216D9DA100BC992F /* opennurbs_apple_nsfont.cpp */, 1D21BE18208A731400C62038 /* opennurbs_glyph_outline.cpp */, 1D7B99571FF3009600E9A791 /* opennurbs_unicode_cpsb.cpp */, @@ -1099,6 +1111,7 @@ 1D4452FF1ED6454500CD7FC1 /* opennurbs.h in Headers */, 1DC317D21ED652B800DE6D26 /* opennurbs_arc.h in Headers */, 1DC318041ED652B800DE6D26 /* opennurbs_curveonsurface.h in Headers */, + 1D741C1B21B9E3C700AA10E5 /* opennurbs_sleeplock.h in Headers */, 1DC317CD1ED652B800DE6D26 /* opennurbs_3dm_settings.h in Headers */, 1DC318B01ED652F800DE6D26 /* opennurbs_internal_defines.h in Headers */, 1DC318C61ED652F800DE6D26 /* opennurbs_line.h in Headers */, @@ -1193,6 +1206,7 @@ 1DC318DC1ED652F800DE6D26 /* opennurbs_memory.h in Headers */, 1DC317FE1ED652B800DE6D26 /* opennurbs_cpp_base.h in Headers */, 1DC318CF1ED652F800DE6D26 /* opennurbs_lock.h in Headers */, + 1D97149F218BC3B5008B4D65 /* opennurbs_testclass.h in Headers */, 1DC318BC1ED652F800DE6D26 /* opennurbs_ipoint.h in Headers */, 1DC319EB1ED6534E00DE6D26 /* opennurbs_xform.h in Headers */, 1DC319161ED652F800DE6D26 /* opennurbs_private_wrap.h in Headers */, @@ -1345,6 +1359,7 @@ 1DC319EA1ED6534E00DE6D26 /* opennurbs_xform.cpp in Sources */, 1DC319D31ED6534E00DE6D26 /* opennurbs_topology.cpp in Sources */, 1DC318031ED652B800DE6D26 /* opennurbs_curveonsurface.cpp in Sources */, + 1D97149D218BC3A1008B4D65 /* opennurbs_testclass.cpp in Sources */, 1DC319101ED652F800DE6D26 /* opennurbs_polyline.cpp in Sources */, 1DC318C71ED652F800DE6D26 /* opennurbs_linecurve.cpp in Sources */, 1DC317FA1ED652B800DE6D26 /* opennurbs_compstat.cpp in Sources */, @@ -1429,6 +1444,7 @@ 1DC318DD1ED652F800DE6D26 /* opennurbs_mesh_ngon.cpp in Sources */, 1DC3199D1ED6534E00DE6D26 /* opennurbs_string_values.cpp in Sources */, 1DC319A71ED6534E00DE6D26 /* opennurbs_subd_heap.cpp in Sources */, + 1D741C1D21B9E3D700AA10E5 /* opennurbs_sleeplock.cpp in Sources */, 1DC317EE1ED652B800DE6D26 /* opennurbs_brep_region.cpp in Sources */, 1DC318CC1ED652F800DE6D26 /* opennurbs_locale.cpp in Sources */, ); diff --git a/opennurbs_public_examples.h b/opennurbs_public_examples.h index 69efb2af..bbc1987d 100644 --- a/opennurbs_public_examples.h +++ b/opennurbs_public_examples.h @@ -36,10 +36,10 @@ #endif #if defined(OPENNURBS_IMPORTS) -#pragma message( " --- dynamically linking opennurbs (DLL)." ) +#pragma message( " --- dynamically linking public opennurbs (DLL)." ) #pragma comment(lib, "\"" OPENNURBS_OUTPUT_DIR "/" "opennurbs_public.lib" "\"") #else -#pragma message( " --- statically linking opennurbs." ) +#pragma message( " --- statically linking public opennurbs." ) #pragma comment(lib, "\"" OPENNURBS_OUTPUT_DIR "/" "opennurbs_public_staticlib.lib" "\"") #pragma comment(lib, "\"" OPENNURBS_OUTPUT_DIR "/" "zlib.lib" "\"") #if defined(OPENNURBS_FREETYPE_SUPPORT) diff --git a/opennurbs_public_staticlib.vcxproj b/opennurbs_public_staticlib.vcxproj index 84f00361..7b095d82 100644 --- a/opennurbs_public_staticlib.vcxproj +++ b/opennurbs_public_staticlib.vcxproj @@ -148,7 +148,6 @@ - @@ -248,6 +247,7 @@ + @@ -261,6 +261,7 @@ + @@ -407,6 +408,7 @@ + @@ -437,6 +439,7 @@ + diff --git a/opennurbs_public_version.h b/opennurbs_public_version.h index e6f87711..a594236f 100644 --- a/opennurbs_public_version.h +++ b/opennurbs_public_version.h @@ -6,18 +6,18 @@ // To update version numbers, edit ..\build\build_dates.msbuild #define RMA_VERSION_MAJOR 6 -#define RMA_VERSION_MINOR 11 +#define RMA_VERSION_MINOR 14 //////////////////////////////////////////////////////////////// // // These are set automatically by the build system as the // first step in each build. // -#define RMA_VERSION_YEAR 2018 -#define RMA_VERSION_MONTH 10 -#define RMA_VERSION_DATE 9 -#define RMA_VERSION_HOUR 1 -#define RMA_VERSION_MINUTE 0 +#define RMA_VERSION_YEAR 2019 +#define RMA_VERSION_MONTH 4 +#define RMA_VERSION_DATE 8 +#define RMA_VERSION_HOUR 19 +#define RMA_VERSION_MINUTE 27 //////////////////////////////////////////////////////////////// // @@ -35,19 +35,19 @@ // 3 = build system release build #define RMA_VERSION_BRANCH 0 -#define VERSION_WITH_COMMAS 6,11,18282,1000 -#define VERSION_WITH_PERIODS 6.11.18282.01000 -#define COPYRIGHT "Copyright (C) 1993-2018, Robert McNeel & Associates. All Rights Reserved." +#define VERSION_WITH_COMMAS 6,14,19098,19271 +#define VERSION_WITH_PERIODS 6.14.19098.19271 +#define COPYRIGHT "Copyright (C) 1993-2019, Robert McNeel & Associates. All Rights Reserved." #define SPECIAL_BUILD_DESCRIPTION "Public OpenNURBS C++ 3dm file IO library." #define RMA_VERSION_NUMBER_MAJOR_STRING "6" #define RMA_VERSION_NUMBER_MAJOR_WSTRING L"6" -#define RMA_VERSION_NUMBER_SR_STRING "SR11" -#define RMA_VERSION_NUMBER_SR_WSTRING L"SR11" +#define RMA_VERSION_NUMBER_SR_STRING "SR14" +#define RMA_VERSION_NUMBER_SR_WSTRING L"SR14" -#define RMA_VERSION_WITH_PERIODS_STRING "6.11.18282.01000" -#define RMA_VERSION_WITH_PERIODS_WSTRING L"6.11.18282.01000" +#define RMA_VERSION_WITH_PERIODS_STRING "6.14.19098.19271" +#define RMA_VERSION_WITH_PERIODS_WSTRING L"6.14.19098.19271" diff --git a/opennurbs_sleeplock.cpp b/opennurbs_sleeplock.cpp new file mode 100644 index 00000000..c5c783f3 --- /dev/null +++ b/opennurbs_sleeplock.cpp @@ -0,0 +1,222 @@ +/* $NoKeywords: $ */ +/* +// +// Copyright (c) 1993-2012 Robert McNeel & Associates. All rights reserved. +// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert +// McNeel & Associates. +// +// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY. +// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF +// MERCHANTABILITY ARE HEREBY DISCLAIMED. +// +// For complete openNURBS copyright information see . +// +//////////////////////////////////////////////////////////////// +*/ + +#include "opennurbs.h" + +#if !defined(ON_COMPILING_OPENNURBS) +// This check is included in all opennurbs source .c and .cpp files to insure +// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled. +// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined +// and the opennurbs .h files alter what is declared and how it is declared. +#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs +#endif + +// ON_SleepLock::IsLocked() is not reliable. Do not use it. It will be deleted in the next SDK. +bool ON_SleepLock::IsLocked() const +{ + // ON_SleepLock::IsLocked() is not reliable. Do not use it. It will be deleted in the next SDK. +#if defined(ON_SLEEPLOCK_USES_STD_ATOMIC_FLAG) + const bool b = m_lockedX.test_and_set(std::memory_order_acquire); + if (false == b) + m_lockedX.clear(std::memory_order_release); + return b; +#else + return 0 != m_locked; +#endif +} + +bool ON_SleepLock::GetLockOrReturnFalse() +{ + // returns true if the value of the m_locked changed from 0 to 1. + // returns false if the value of m_locked was 1 and was not changed. + // In all cases, the final value of m_locked = 1. + +#if defined(ON_SLEEPLOCK_USES_STD_ATOMIC_FLAG) + // std::atomic_flag.test_and_set + // does the following as an atomic operation: + // bool b = m_lockedX; + // m_lockedX = true; + // // in all cases, the final value of m_lockedX = true. + bool locked = m_lockedX.test_and_set(std::memory_order_acquire); + return (false == locked); +#else + // Windows int locked = InterlockedCompareExchange(((LONG*)(&(m_locked))), 1, 0) + // does the following as an atomic operation: + // int locked = m_locked; + // if ( 0 == m_locked ) + // m_locked = 1; + // // in all cases, the final value of m_locked = 1. + int locked = InterlockedCompareExchange(((LONG*)(&(m_locked))), 1, 0); + return (0 == locked); +#endif +} + +bool ON_SleepLock::ReturnLock() +{ + // returns true if the input value of m_locked != 0. + // returns false of the input value of m_locked = 0. + // In all cases, the final value of m_locked = 0. + +#if defined(ON_SLEEPLOCK_USES_STD_ATOMIC_FLAG) + const bool b = m_lockedX.test_and_set(std::memory_order_acquire); + m_lockedX.clear(std::memory_order_release); + return b; +#else + // Windows int locked = InterlockedExchange(((LONG*)(&(m_locked))),0); + // does the following as an atomic operation: + // int locked = m_locked; + // m_locked = 0; + // // in all cases, the final value of m_locked = 0. + int locked = InterlockedExchange(((LONG*)(&(m_locked))),0); + return (0 != locked); +#endif +} + +bool ON_SleepLock::GetLock() +{ + return GetLock(ON_SleepLock::DefaultWaitInterval,0); +} + +bool ON_SleepLock::GetLock( + unsigned int interval_wait_msecs, + unsigned int max_wait_msecs + ) +{ + return GetLock(interval_wait_msecs,max_wait_msecs,false); +} + +bool ON_SleepLock::GetLock( + unsigned int interval_wait_msecs, + unsigned int max_wait_msecs, + bool bStealLockAfterWaiting + ) +{ + // int locked = ON_IntSleepLock_Test(m_locked,0,1) + // performs the following test and assignment in + // an atomic operation. + // + // int locked = m_locked; + // if ( 0 == locked ) + // m_locked = 1; + if ( GetLockOrReturnFalse() ) + return true; // acquired lock + + if ( interval_wait_msecs <= 0 ) + interval_wait_msecs = ON_SleepLock::DefaultWaitInterval; + + for(;;) + { + // Something else currently has this lock. + + // ON_PointerSleepLock_SuspendThisThread() suspends the thread + // (not the process) for the specified number of milliseconds. +#if defined(ON_SLEEPLOCK_USES_STD_ATOMIC_FLAG) + std::this_thread::sleep_for(std::chrono::milliseconds(interval_wait_msecs)); +#else + // Windows - sleep the thread (not the process) + ::Sleep(interval_wait_msecs); +#endif + + // locked = ON_IntSleepLock_Test(m_locked,0,1) + // performs the following test and assignment in + // an atomic operation. + // + // locked = m_locked; + // if ( 0 == locked ) + // m_locked = 1; + if ( GetLockOrReturnFalse() ) + return true; // acquired lock + + if ( max_wait_msecs > 0 ) + { + if ( interval_wait_msecs >= max_wait_msecs ) + { + if (bStealLockAfterWaiting) + { + // If the caller set max_wait_msecs to a value that is too small, + // has a bug in their code, then this is a bad idea, + // but it's also their own fault. + // + // If the resource is locked because another thread locked it and + // then forgot to unlock it or terminated before it could be unlocked, + // then this is a good thing because it gets this thread unblocked + // and back in business. + // + ON_WARNING("Stealing a resource lock."); + GetLockOrReturnFalse(); + return true; + } + + // Give up and return false. + break; + } + max_wait_msecs -= interval_wait_msecs; + } + } + + return false; +} + + +ON_SleepLockGuard::ON_SleepLockGuard(class ON_SleepLock& sleep_lock) + : m_sleep_lock(sleep_lock) +{ + m_bIsManagingLock = m_sleep_lock.GetLock(); +} + + +ON_SleepLockGuard::ON_SleepLockGuard( + class ON_SleepLock& sleep_lock, + unsigned int interval_wait_msecs, + unsigned int max_wait_msecs + ) + : m_sleep_lock(sleep_lock) +{ + m_bIsManagingLock = sleep_lock.GetLock(interval_wait_msecs,max_wait_msecs); +} + +ON_SleepLockGuard::ON_SleepLockGuard( + class ON_SleepLock& sleep_lock, + unsigned int interval_wait_msecs, + unsigned int max_wait_msecs, + bool bStealLockAfterWaiting + ) + : m_sleep_lock(sleep_lock) +{ + m_bIsManagingLock = sleep_lock.GetLock(interval_wait_msecs,max_wait_msecs,bStealLockAfterWaiting); +} + +ON_SleepLockGuard::~ON_SleepLockGuard() +{ + ReturnLock(); +} + + +void ON_SleepLockGuard::ReturnLock() +{ + if (m_bIsManagingLock) + { + m_bIsManagingLock = false; + m_sleep_lock.ReturnLock(); + } +} + +const bool ON_SleepLockGuard::IsManagingLock() const +{ + return m_bIsManagingLock; +} + + diff --git a/opennurbs_sleeplock.h b/opennurbs_sleeplock.h new file mode 100644 index 00000000..82dc331a --- /dev/null +++ b/opennurbs_sleeplock.h @@ -0,0 +1,230 @@ +#pragma once +/* $NoKeywords: $ */ +/* +// +// Copyright (c) 1993-2012 Robert McNeel & Associates. All rights reserved. +// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert +// McNeel & Associates. +// +// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY. +// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF +// MERCHANTABILITY ARE HEREBY DISCLAIMED. +// +// For complete openNURBS copyright information see . +// +//////////////////////////////////////////////////////////////// +*/ + +#if !defined(OPENNURBS_SLEEPLOCK_INC_) +#define OPENNURBS_SLEEPLOCK_INC_ + +#define OPENNURBS_SLEEPLOCK_AVAILABLE + +#if !defined(ON_COMPILER_MSC) || !defined(ON_RUNTIME_WIN) +// Use std::atomic_lock and std::this_thread::sleep_for(std::chrono::milliseconds(...)) +#define ON_SLEEPLOCK_USES_STD_ATOMIC_FLAG +#else +// Windows uses InterlockedExchange() and ::Sleep(...) because it +// is 1.5 times faster than std::atomic_lock. +#endif + + +class ON_CLASS ON_SleepLock +{ +public: + ON_SleepLock() = default; + ~ON_SleepLock() = default; + +private: + ON_SleepLock(const ON_SleepLock&) = delete; + ON_SleepLock& operator=(const ON_SleepLock&) = delete; + +public: + + enum : unsigned int + { + /// + /// The default ON_SleepLock interval is 1/20 of a second to wait between attempts to obtain the lock. + /// + DefaultWaitInterval = 50, + + /// + /// A ON_SleepLock interval of 1 millisecond (1/1000 second). + /// + OneMillisecond = 1, + + /// + /// A ON_SleepLock interval of 1 second. + /// + OneSecond = 1000, + + /// + /// A ON_SleepLock interval of 1 minute. + /// + OneMinute = 60000 + }; + + /* + Description: + ON_SleepLock::IsLocked() is not reliable. Do not use it. It will be deleted in the next SDK. + Returns: + An unreliable value. + */ + ON_DEPRECATED_MSG("ON_SleepLock::IsLocked() is not reliable. Do not use it. It will be deleted in the next SDK.") + bool IsLocked() const; + + /* + Description: + Gets the lock using a ON_SleepLock::DefaultWaitInterval millisecond wait interval with + no maximum waiting time. + Returns: + This version of GetLock() always returns true. + Remarks: + You must call ReturnLock() when finished using the protected resource. + */ + bool GetLock(); + + /* + Description: + Attempts to get the lock a single time. + Returns: + True if the lock was aquired. False if the lock was not aquired because it was already locked. + Remarks: + If GetLockOrReturnFalse() returns true, then you must call ReturnLock() when finished using the protected resource. + If GetLockOrReturnFalse() returns false, then you must not call ReturnLock(). + */ + bool GetLockOrReturnFalse(); + + /* + Description: + Gets the lock using a the specified waiting interval + and maximum waiting time. + Parameters: + interval_wait_msecs - [in] + number of milliseconds to wait between checking for the lock. + If interval_wait_msecs is 0, then ON_SleepLock::DefaultWaitInterval is used. + If interval_wait_msecs > max_wait_msecs > 0, + then one attempt is made to get the lock. + max_wait_msecs - [in] + maximum number of milliseconds to wait for lock. + If max_wait_msecs is 0, then no maximum wating time is used. + Returns: + True if the lock is obtained. + False if the maximum waiting time expired without getting + the lock. + + If GetLock() returns true, then you must call ReturnLock() when finished using the protected resource. + If GetLock() returns false, then you must not call ReturnLock(). + Remarks: + It is easy and clear to people reading your code if you use values + like + ON_SleepLock::OneSecond, + ON_SleepLock::OneSecond/10, + 47*ON_SleepLock::OneSecond, + ON_SleepLock::OneMinute, + or + ON_SleepLock::OneMinute/3 + to specify the values for interval_wait_msecs and max_wait_msecs. + */ + bool GetLock( + unsigned int interval_wait_msecs, + unsigned int max_wait_msecs + ); + + bool GetLock( + unsigned int interval_wait_msecs, + unsigned int max_wait_msecs, + bool bStealLockAfterWaiting + ); + + /* + Description: + Unconditionally returns the lock. + Returns: + True if the lock was changed from locked to unlocked. + False if the lock was already unlocked. + */ + bool ReturnLock(); + +private: + // When locked m_locked = 1, otherwise m_locked = 0 ; +#if defined(ON_SLEEPLOCK_USES_STD_ATOMIC_FLAG) + mutable std::atomic_flag m_lockedX = ATOMIC_FLAG_INIT; // = 0 +#else + // 1.5 times faster on Windows 10 + int m_locked = 0; +#endif +}; + +class ON_CLASS ON_SleepLockGuard +{ +public: + /* + Description: + Calls sleep_lock.GetLock(). + Parameters: + sleep_lock - [in] + ON_SleepLock to manage. + */ + ON_SleepLockGuard(class ON_SleepLock& sleep_lock); + + /* + Description: + Calls sleep_lock.GetLock(interval_wait_msecs,max_wait_msecs). + Parameters: + sleep_lock - [in] + ON_SleepLock to manage. + */ + ON_SleepLockGuard( + class ON_SleepLock& sleep_lock, + unsigned int interval_wait_msecs, + unsigned int max_wait_msecs + ); + + /* + Description: + Calls sleep_lock.GetLock(interval_wait_msecs,max_wait_msecs,bStealLockAfterWaiting). + Parameters: + sleep_lock - [in] + ON_SleepLock to manage. + */ + ON_SleepLockGuard( + class ON_SleepLock& sleep_lock, + unsigned int interval_wait_msecs, + unsigned int max_wait_msecs, + bool bStealLockAfterWaiting + ); + + /* + Description: + Calls sleep_lock.ReturnLock(). + */ + ~ON_SleepLockGuard(); + + /* + Description: + If this instance has the lock, it is returned and the destructor does nothing. + */ + void ReturnLock(); + + /* + Returns: + True if this instance currently has the lock. + */ + const bool IsManagingLock() const; + +private: + // Keep m_sleep_lock private. Never add a function that provides access to m_sleep_lock. + class ON_SleepLock& m_sleep_lock; + bool m_bIsManagingLock = false; + +private: + ON_SleepLockGuard() = delete; + ON_SleepLockGuard(const ON_SleepLockGuard&) = delete; + ON_SleepLockGuard& operator=(const ON_SleepLockGuard&) = delete; + +public: + ON_SleepLockGuard(const class ON_Brep&); +}; + +#endif diff --git a/opennurbs_statics.cpp b/opennurbs_statics.cpp index a5207439..d509f53a 100644 --- a/opennurbs_statics.cpp +++ b/opennurbs_statics.cpp @@ -1,4 +1,6 @@ #include "opennurbs.h" +#include "opennurbs_testclass.h" +#include "opennurbs_subd_data.h" #if !defined(ON_COMPILING_OPENNURBS) // This check is included in all opennurbs source .c and .cpp files to insure @@ -83,6 +85,21 @@ ON_MemoryAllocationTracking::~ON_MemoryAllocationTracking() #endif } +#if !defined(OPENNURBS_NO_STD_MUTEX) +// It is critical that ON_TestClass::global_mutex, ON_TestClass::CtorSerialNumberGenerator, and +// ON_TestClass::PopulationCounter be initialized before any instance of ON_TestClass is created. +std::mutex ON_TestClass::internal_counter_mutex; +ON__UINT64 ON_TestClass::internal_CtorSerialNumberGenerator = 0; +ON__UINT64 ON_TestClass::internal_PopulationCounter = 0; +#endif + +// It is critical that ON_ModelComponent::Internal_RuntimeSerialNumberGenerator +// be constructed before any instance of a class derived from ON_ModelComponent. +// That is why it is above the ClassId stuff in this .cpp file. +std::atomic ON_ModelComponent::Internal_RuntimeSerialNumberGenerator(0); + +std::atomic ON_SubDimple::Internal_RuntimeSerialNumberGenerator; + ON_ClassId* ON_ClassId::m_p0 = 0; // static pointer to first id in list ON_ClassId* ON_ClassId::m_p1 = 0; // static pointer to last id in list int ON_ClassId::m_mark0 = 0; @@ -453,6 +470,7 @@ const char ON_String::Slash = (char)ON_UnicodeCodePoint::ON_Slash; const char ON_String::Backslash = (char)ON_UnicodeCodePoint::ON_Backslash; const char ON_String::Underscore = (char)ON_UnicodeCodePoint::ON_Underscore; const char ON_String::Pipe = (char)ON_UnicodeCodePoint::ON_Pipe; +const char ON_String::Tilde = (char)ON_UnicodeCodePoint::ON_Tilde; // ON_wString is UTF-16 encoded when sizeof(wchar_t) = 2 // ON_wString is UTF-32 encoded when sizeof(wchar_t) = 4 @@ -472,6 +490,7 @@ const wchar_t ON_wString::Slash = (wchar_t)ON_UnicodeCodePoint::ON_Slash; const wchar_t ON_wString::Backslash = (wchar_t)ON_UnicodeCodePoint::ON_Backslash; const wchar_t ON_wString::Underscore = (char)ON_UnicodeCodePoint::ON_Underscore; const wchar_t ON_wString::Pipe = (wchar_t)ON_UnicodeCodePoint::ON_Pipe; +const wchar_t ON_wString::Tilde = (wchar_t)ON_UnicodeCodePoint::ON_Tilde; #if defined(ON_SIZEOF_WCHAR_T) && ON_SIZEOF_WCHAR_T >= 2 // ON_wString is UTF-16 encoded when sizeof(wchar_t) = 2 @@ -739,8 +758,6 @@ const ON_EarthAnchorPoint ON_EarthAnchorPoint::SeattleSpaceNeedle = Internal_Ear const ON_3dmAnnotationSettings ON_3dmAnnotationSettings::Default ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_3dmAnnotationSettings); -const ON_3dmSettings ON_3dmSettings::Default ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_3dmSettings); - const ON_3dmAnnotationContext ON_3dmAnnotationContext::Default ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_3dmAnnotationContext); const ON_3dmArchiveTableStatus ON_3dmArchiveTableStatus::Unset ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_3dmArchiveTableStatus); @@ -804,6 +821,7 @@ const ON_Line ON_Line::NanLine(ON_3dPoint::NanPoint, ON_3dPoint::NanPoint); const ON_PlaneEquation ON_PlaneEquation::UnsetPlaneEquation(ON_UNSET_VALUE, ON_UNSET_VALUE, ON_UNSET_VALUE, ON_UNSET_VALUE); const ON_PlaneEquation ON_PlaneEquation::ZeroPlaneEquation(0.0, 0.0, 0.0, 0.0); +const ON_PlaneEquation ON_PlaneEquation::NanPlaneEquation(ON_DBL_QNAN, ON_DBL_QNAN, ON_DBL_QNAN, ON_DBL_QNAN); const ON_Plane ON_xy_plane(ON_3dPoint::Origin, ON_3dVector::XAxis, ON_3dVector::YAxis); const ON_Plane ON_yz_plane(ON_3dPoint::Origin, ON_3dVector::YAxis, ON_3dVector::ZAxis); @@ -823,6 +841,18 @@ static ON_Plane ON_Plane_UnsetPlane() const ON_Plane ON_Plane::UnsetPlane(ON_Plane_UnsetPlane()); +static ON_Plane ON_Plane_NanPlane() +{ + ON_Plane nan_plane; + nan_plane.xaxis = ON_3dVector::NanVector; + nan_plane.yaxis = ON_3dVector::NanVector; + nan_plane.zaxis = ON_3dVector::NanVector; + nan_plane.origin = ON_3dPoint::NanPoint; + nan_plane.plane_equation = ON_PlaneEquation::NanPlaneEquation; + return nan_plane; +} +const ON_Plane ON_Plane::NanPlane(ON_Plane_NanPlane()); + // {F15F67AA-4AF9-4B25-A3B8-517CEDDAB134} const ON_UUID ON_MeshParameters::RhinoLegacyMesherId = { 0xf15f67aa, 0x4af9, 0x4b25,{ 0xa3, 0xb8, 0x51, 0x7c, 0xed, 0xda, 0xb1, 0x34 } }; @@ -970,8 +1000,11 @@ const ON_3dmUnitsAndTolerances ON_3dmUnitsAndTolerances::Millimeters ON_CLANG_CO const ON_Circle ON_Circle::UnitCircle ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_Circle); const ON_Arc ON_Arc::UnitCircle ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_Arc); + const ON_3dmRenderSettings ON_3dmRenderSettings::Default ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_3dmRenderSettings); +const ON_3dmSettings ON_3dmSettings::Default ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_3dmSettings); + const ON_ProgressStepCounter ON_ProgressStepCounter::Empty ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_ProgressStepCounter); const ON_HistoryRecord ON_HistoryRecord::Empty ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_HistoryRecord); @@ -2259,6 +2292,7 @@ const ON_SubDVertexPtr ON_SubDVertexPtr::Null = { 0 }; const ON_SubDEdgePtr ON_SubDEdgePtr::Null = { 0 }; const ON_SubDFacePtr ON_SubDFacePtr::Null = { 0 }; +const ON_SubDEdgeChain ON_SubDEdgeChain::Empty ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_SubDEdgeChain); static ON_SubDSectorLimitPoint ON_SubDSectorLimitPoint_Init(double x) { @@ -2329,6 +2363,7 @@ const ON_ComponentStatus ON_ComponentStatus::Hidden = ON_ComponentStatus(ON_Comp const ON_ComponentStatus ON_ComponentStatus::Locked = ON_ComponentStatus(ON_ComponentState::Locked); const ON_ComponentStatus ON_ComponentStatus::Deleted = ON_ComponentStatus(ON_ComponentState::Deleted); const ON_ComponentStatus ON_ComponentStatus::Damaged = ON_ComponentStatus(ON_ComponentState::Damaged); +const ON_ComponentStatus ON_ComponentStatus::Marked = ON_ComponentStatus(ON_ComponentState::RuntimeMarkSet); static ON_ComponentStatus ON_ComponentStatus_AllSet() { ON_ComponentStatus s; @@ -2378,23 +2413,6 @@ static ON_SubDLimitMeshFragment EmptyLimitMeshFragmentInit() const ON_SubDLimitMeshFragmentGrid ON_SubDLimitMeshFragmentGrid::Empty = EmptyLimitMeshFragmentGridInit(); const ON_SubDLimitMeshFragment ON_SubDLimitMeshFragment::Empty = EmptyLimitMeshFragmentInit(); -ON_SubDLimitNurbsFragment ON_SubDLimitPatchFragment_Init(double x) -{ - ON_SubDLimitNurbsFragment pf; - memset(&pf, 0, sizeof(pf)); - if (!(0.0 == x)) - { - double* p = &pf.m_patch_cv[0][0][0]; - double* p1 = p + sizeof(pf.m_patch_cv) / sizeof(p[0]); - while (p < p1) - *p++ = x; - } - return pf; -} - -const ON_SubDLimitNurbsFragment ON_SubDLimitNurbsFragment::Empty = ON_SubDLimitPatchFragment_Init(0.0); -const ON_SubDLimitNurbsFragment ON_SubDLimitNurbsFragment::Unset = ON_SubDLimitPatchFragment_Init(ON_UNSET_VALUE); -const ON_SubDLimitNurbsFragment ON_SubDLimitNurbsFragment::Nan = ON_SubDLimitPatchFragment_Init(ON_DBL_QNAN); static ON_SubDComponentBase UnsetComponentBaseInit() { diff --git a/opennurbs_string.cpp b/opennurbs_string.cpp index a2be0ba0..a214582b 100644 --- a/opennurbs_string.cpp +++ b/opennurbs_string.cpp @@ -24,36 +24,69 @@ #error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs #endif -#include "opennurbs_atomic_op.h" - ///////////////////////////////////////////////////////////////////////////// -// Empty strings point at empty_astring +// Empty strings point at empty_wstring - -struct ON_aStringHeader +class ON_aStringHeader { +private: + ON_aStringHeader() = delete; + +public: + ~ON_aStringHeader() = default; + ON_aStringHeader(const ON_aStringHeader&) = default; + ON_aStringHeader& operator=(const ON_aStringHeader&) = default; + +public: + ON_aStringHeader( + int initial_ref_count, + int capacity + ) + : ref_count(initial_ref_count) + , string_capacity(capacity) + {} + +public: // NOTE WELL: // ref_count must be a signed 32-bit integer type that // supports atomic increment/decrement operations. - int ref_count; // reference count (>=0 or -1 for empty string) - int string_length; // does not include nullptr terminator - int string_capacity; // does not include nullptr terminator - char* string_array() {return (char*)(this+1);} + std::atomic ref_count; + int string_length=0; // does not include null terminator + int string_capacity; // does not include null terminator + +public: + char* string_array() {return (char*)(this+1);} }; -static struct { +class ON_Internal_Empty_aString +{ +private: + ON_Internal_Empty_aString(const ON_Internal_Empty_aString&) = delete; + ON_Internal_Empty_aString& operator=(const ON_Internal_Empty_aString&) = delete; + +public: + ON_Internal_Empty_aString() + : header(-1,0) + {} + ~ON_Internal_Empty_aString() = default; + + +public: ON_aStringHeader header; - char s; -} empty_astring = { {-1, 0, 0}, 0 }; // ref_count=-1, length=0, capacity=0, s=0 + char s = 0; +}; + +static ON_Internal_Empty_aString empty_astring; static const ON_aStringHeader* pEmptyStringHeader = &empty_astring.header; static const char* pEmptyaString = &empty_astring.s; -static void ON_aStringHeader_DecrementRefCountAndDeleteIfZero(struct ON_aStringHeader* hdr) +static void ON_aStringHeader_DecrementRefCountAndDeleteIfZero(class ON_aStringHeader* hdr) { if (nullptr == hdr || hdr == pEmptyStringHeader) return; - const int ref_count = ON_AtomicDecrementInt32(&hdr->ref_count); + //const int ref_count = ON_AtomicDecrementInt32(&hdr->ref_count); + const int ref_count = --hdr->ref_count; if (0 == ref_count) { // zero entire header to help prevent crashes from corrupt string bug @@ -114,7 +147,7 @@ bool ON_String::IsValid( break; if (string_length > string_capacity) break; - const int ref_count = hdr->ref_count; + const int ref_count = (int)(hdr->ref_count); if (ref_count <= 0) break; const char* s1 = s + string_length; @@ -161,26 +194,27 @@ bool ON_String::IsValid( ON_aStringHeader* ON_String::IncrementedHeader() const { - ON_aStringHeader* p = (ON_aStringHeader*)m_s; - if (nullptr == p) + ON_aStringHeader* hdr = (ON_aStringHeader*)m_s; + if (nullptr == hdr) return nullptr; - p--; - if (p == pEmptyStringHeader) + hdr--; + if (hdr == pEmptyStringHeader) return nullptr; - ON_AtomicIncrementInt32(&p->ref_count); - return p; + //ON_AtomicIncrementInt32(&hdr->ref_count); + ++hdr->ref_count; + return hdr; } ON_aStringHeader* ON_String::Header() const { - ON_aStringHeader* p = (ON_aStringHeader*)m_s; - if (p) - p--; + ON_aStringHeader* hdr = (ON_aStringHeader*)m_s; + if (hdr) + hdr--; else - p = &empty_astring.header; - return p; + hdr = &empty_astring.header; + return hdr; } void ON_String::Create() @@ -191,7 +225,7 @@ void ON_String::Create() void ON_String::Destroy() { ON_aStringHeader* hdr = Header(); - if (hdr != pEmptyStringHeader && nullptr != hdr && hdr->ref_count > 0) + if (hdr != pEmptyStringHeader && nullptr != hdr && (int)(hdr->ref_count) > 0) ON_aStringHeader_DecrementRefCountAndDeleteIfZero(hdr); Create(); } @@ -228,12 +262,9 @@ char* ON_String::CreateArray( int capacity ) if ( capacity > 0 ) { // This scope does not need atomic operations - ON_aStringHeader* p = - (ON_aStringHeader*)onmalloc( sizeof(ON_aStringHeader) + (capacity+1)*sizeof(*m_s) ); - p->ref_count = 1; - p->string_length = 0; - p->string_capacity = capacity; - m_s = p->string_array(); + void* buffer = onmalloc( sizeof(ON_aStringHeader) + (capacity+1)*sizeof(*m_s) ); + ON_aStringHeader* hdr = new (buffer) ON_aStringHeader(1,capacity); + m_s = hdr->string_array(); memset( m_s, 0, (capacity+1)*sizeof(*m_s) ); return m_s; } @@ -246,7 +277,7 @@ void ON_String::CopyArray() // Call CopyArray() before modifying array contents. // hdr0 = original header ON_aStringHeader* hdr0 = Header(); - if ( hdr0 != pEmptyStringHeader && nullptr != hdr0 && 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(); @@ -282,7 +313,7 @@ char* ON_String::ReserveArray( size_t array_capacity ) { CreateArray(capacity); } - else if ( hdr0->ref_count > 1 ) + else if ( (int)(hdr0->ref_count) > 1 ) { // Calling Create() here insures hdr0 remains valid until we decrement below. Create(); @@ -326,7 +357,7 @@ void ON_String::ShrinkArray() Destroy(); Create(); } - else if ( hdr0->ref_count > 1 ) + else if ( (int)(hdr0->ref_count) > 1 ) { // Calling Create() here insures hdr0 remains valid until we decrement below. Create(); diff --git a/opennurbs_string.h b/opennurbs_string.h index 60434287..2dd6d156 100644 --- a/opennurbs_string.h +++ b/opennurbs_string.h @@ -914,6 +914,7 @@ public: static const char Backslash; // Unicode REVERSE SOLIDUS U+005C static const char Underscore; // Unicode LOW LINE U+005F static const char Pipe; // Unicode VERTICAL LINE U+007C + static const char Tilde; // Unicode TILDE U+007E private: // Use IsEmpty() or IsNotEmpty() when you want a bool @@ -1750,7 +1751,7 @@ public: ... ); - static const ON_wString ON_VARGS_FUNC_CDECL FormatToString( + static const ON_String ON_VARGS_FUNC_CDECL FormatToString( const char* format, ... ); @@ -1982,8 +1983,8 @@ protected: // m_s - 12 bytes points at the string's ON_aStringHeader // implementation helpers - struct ON_aStringHeader* Header() const; - struct ON_aStringHeader* IncrementedHeader() const; + class ON_aStringHeader* Header() const; + class ON_aStringHeader* IncrementedHeader() const; char* CreateArray(int); void CopyArray(); void CopyToArray( const ON_String& ); @@ -2150,6 +2151,7 @@ public: static const wchar_t Backslash; // Unicode REVERSE SOLIDUS U+005C static const wchar_t Underscore; // Unicode LOW LINE U+005F static const wchar_t Pipe; // Unicode VERTICAL LINE U+007C + static const wchar_t Tilde; // Unicode TILDE U+007E #if defined(ON_SIZEOF_WCHAR_T) && ON_SIZEOF_WCHAR_T >= 2 // Never cast these values as "char" @@ -3157,6 +3159,8 @@ public: ); /* + Description: + Get the Gregorian calendar current coordinated universal time as a string. Parameters: date_format - [in] date_format - [in] @@ -3172,8 +3176,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, @@ -3183,9 +3186,11 @@ public: ); /* + Description: + Get the Gregorian calendar date and time as a string. Parameters: t - [in] - time to format + Gregorian calendar time to format date_format - [in] date_format - [in] time_format - [in] @@ -3207,6 +3212,121 @@ public: wchar_t date_time_separator, wchar_t time_separator ); + + /* + Description: + Get the Gregorian calendar date and time as a string. + Parameters: + seconds - [in] + number of seconds since January 1, 1970 00:00:00. + date_format - [in] + date_format - [in] + time_format - [in] + date_separator - [in] + Character placed between the year, month and day values. + If 0, then ON_wString::HyphenMinus is used to + date_time_separator - [in] + Character placed between the date and time. + If 0, then ON_wString::Space is used. + time_separator - [in] + Character placed between the hour, minute, and second values. + If 0, then : (colon) is used. + */ + static const ON_wString FromSecondsSinceJanuaryFirst1970( + ON__UINT64 seconds_since_jan_first_1970, + ON_DateFormat date_format, + ON_TimeFormat time_format, + wchar_t date_separator, + wchar_t date_time_separator, + wchar_t time_separator + ); + + + /* + Description: + Get the Gregorian calendar date and time as a string. + The year value must be >= 1582 (beginning of Gregorian calendar). + Parameters: + year - [in] + year >= 1582 (beginning of Gregorian calendar) + month - [in] + 1 to 12 + mday - [in] + 1 to 31 + hour - [in] + 0 to 23 + minute - [in] + 0 to 59 + second - [in] + 0 to 59 + date_format - [in] + date_format - [in] + time_format - [in] + date_separator - [in] + Character placed between the year, month and day values. + If 0, then ON_wString::HyphenMinus is used to + date_time_separator - [in] + Character placed between the date and time. + If 0, then ON_wString::Space is used. + time_separator - [in] + Character placed between the hour, minute, and second values. + If 0, then : (colon) is used. + */ + static const ON_wString FromYearMonthDayHourMinuteSecond( + int year, + int month, + int mday, + int hour, + int minute, + int second, + ON_DateFormat date_format, + ON_TimeFormat time_format, + wchar_t date_separator, + wchar_t date_time_separator, + wchar_t time_separator + ); + + /* + Description: + Get the Gregorian calendar date and time as a string. + The year value must be >= 1582 (beginning of Gregorian calendar). + Parameters: + year - [in] + year >= 1582 (beginning of Gregorian calendar) + day_of_year - [in] + 1 to 366 + hour - [in] + 0 to 23 + minute - [in] + 0 to 59 + second - [in] + 0 to 59 + date_format - [in] + date_format - [in] + time_format - [in] + date_separator - [in] + Character placed between the year, month and day values. + If 0, then ON_wString::HyphenMinus is used to + date_time_separator - [in] + Character placed between the date and time. + If 0, then ON_wString::Space is used. + time_separator - [in] + Character placed between the hour, minute, and second values. + If 0, then : (colon) is used. + */ + static const ON_wString FromYearDayHourMinuteSecond( + int year, + int day_of_year, + int hour, + int minute, + int second, + ON_DateFormat date_format, + ON_TimeFormat time_format, + wchar_t date_separator, + wchar_t date_time_separator, + wchar_t time_separator + ); + /* Description: @@ -3539,8 +3659,8 @@ protected: // m_s - 12 bytes points at the string's ON_wStringHeader // implementation helpers - struct ON_wStringHeader* Header() const; - struct ON_wStringHeader* IncrementedHeader() const; + class ON_wStringHeader* Header() const; + class ON_wStringHeader* IncrementedHeader() const; wchar_t* CreateArray(int); void CopyArray(); void CopyToArray( const ON_wString& ); diff --git a/opennurbs_string_format.cpp b/opennurbs_string_format.cpp index 9c4bf58f..5355b2ff 100644 --- a/opennurbs_string_format.cpp +++ b/opennurbs_string_format.cpp @@ -379,6 +379,26 @@ const ON_wString ON_wString::FromCurrentCoordinatedUniversalTime( ); } +const ON_wString ON_wString::FromSecondsSinceJanuaryFirst1970( + ON__UINT64 seconds_since_jan_first_1970, + ON_DateFormat date_format, + ON_TimeFormat time_format, + wchar_t date_separator, + wchar_t date_time_separator, + wchar_t time_separator +) +{ + struct tm current_time; + memset(¤t_time, 0, sizeof(current_time)); + time_t uct = (time_t)seconds_since_jan_first_1970; + const struct tm* t = gmtime(&uct); + if (t) + { + current_time = *t; + } + return FromTime(current_time, date_format, time_format, date_separator, date_time_separator, time_separator); +} + const ON_wString ON_wString::FromTime( const struct tm& t, ON_DateFormat date_format, @@ -410,17 +430,113 @@ const ON_wString ON_wString::FromTime( else year += 1900; - bool bValidDate = (year >= 1900 && mday > 0 && yday > 0 && mon > 0); - bool bValidHMS = (t.tm_hour >= 0 && t.tm_min >= 0 && t.tm_sec >= 0 && t.tm_hour < 24 && t.tm_min <= 59 && t.tm_sec <= 59); + return (mon > 0 && mday > 0) + ? ON_wString::FromYearMonthDayHourMinuteSecond( + year, mon, mday, + (int)t.tm_hour, (int)t.tm_min, (int)t.tm_sec, + date_format, + time_format, + date_separator, + date_time_separator, + time_separator + ) + : ON_wString::FromYearDayHourMinuteSecond( + year, yday, + (int)t.tm_hour, (int)t.tm_min, (int)t.tm_sec, + date_format, + time_format, + date_separator, + date_time_separator, + time_separator + ); +} + + +const ON_wString ON_wString::FromYearDayHourMinuteSecond( + int year, + int day_of_year, + int hour, + int minute, + int second, + ON_DateFormat date_format, + ON_TimeFormat time_format, + wchar_t date_separator, + wchar_t date_time_separator, + wchar_t time_separator +) +{ + unsigned int month = 0; + unsigned int mday = 0; + if ( ON_DateFormat::Unset != date_format && ON_DateFormat::Omit != date_format && year > 0 && day_of_year > 0 && day_of_year <= 366 ) + { + ON_GetGregorianMonthAndDayOfMonth( + (unsigned int)year, + (unsigned int)day_of_year, + &month, + &mday + ); + } + + return ON_wString::FromYearMonthDayHourMinuteSecond( + year, + (int)month, + (int)mday, + hour, + minute, + second, + date_format, + time_format, + date_separator, + date_time_separator, + time_separator + ); +} + +const ON_wString ON_wString::FromYearMonthDayHourMinuteSecond( + int year, + int month, + int mday, + int hour, + int minute, + int second, + ON_DateFormat date_format, + ON_TimeFormat time_format, + wchar_t date_separator, + wchar_t date_time_separator, + wchar_t time_separator +) +{ + if (year < 1582) + year = 0; + + if (mday < 1 || mday > 31) + mday = 0; + + if (month < 1 || month > 12) + month = 0; + + const int yday + = (ON_DateFormat::YearDayOfYear == date_format) + ? ON_DayOfGregorianYear(year, month, mday) + : 0; if (0 == date_separator) date_separator = ON_wString::HyphenMinus; + if (0 == date_time_separator) + date_time_separator = ON_wString::Space; if (0 == time_separator) time_separator = ':'; + + bool bValidDate + = ON_DateFormat::YearDayOfYear == date_format + ? (yday > 0) + : (month > 0 && mday > 0); + bool bValidHMS = (hour >= 0 && minute >= 0 && second >= 0 && hour < 24 && minute <= 59 && second <= 59); - const wchar_t ds[2] = { date_separator,0 }; - const wchar_t ts[2] = { time_separator,0 }; - const wchar_t* ampm = (t.tm_hour >= 12) ? L"PM" : L"AM"; + + const wchar_t ds[2] = { date_separator, 0 }; + const wchar_t ts[2] = { time_separator, 0 }; + const wchar_t* ampm = (hour >= 12) ? L"PM" : L"AM"; ON_wString date; switch (date_format) @@ -432,16 +548,16 @@ const ON_wString ON_wString::FromTime( bValidDate = true; break; case ON_DateFormat::YearMonthDay: - date = ON_wString::FormatToString(L"%d%ls%d%ls%d", year, ds, mon, ds, mday); + date = ON_wString::FormatToString(L"%d%ls%d%ls%d", year, ds, month, ds, mday); break; case ON_DateFormat::YearDayMonth: - date = ON_wString::FormatToString(L"%d%ls%d%ls%d", year, ds, mday, ds, mon); + date = ON_wString::FormatToString(L"%d%ls%d%ls%d", year, ds, mday, ds, month); break; case ON_DateFormat::MonthDayYear: - date = ON_wString::FormatToString(L"%d%ls%d%ls%d", mon, ds, mday, ds, year); + date = ON_wString::FormatToString(L"%d%ls%d%ls%d", month, ds, mday, ds, year); break; case ON_DateFormat::DayMonthYear: - date = ON_wString::FormatToString(L"%d%ls%d%ls%d", mday, ds, mon, ds, year); + date = ON_wString::FormatToString(L"%d%ls%d%ls%d", mday, ds, month, ds, year); break; case ON_DateFormat::YearDayOfYear: date = ON_wString::FormatToString(L"%d%ls%d", year, ds, yday); @@ -462,16 +578,16 @@ const ON_wString ON_wString::FromTime( bValidHMS = true; break; case ON_TimeFormat::HourMinute12: - hms = ON_wString::FormatToString(L"%d%ls%d%ls", t.tm_hour%12, ts, t.tm_min, ampm); + hms = ON_wString::FormatToString(L"%02d%ls%02d%ls", hour%12, ts, minute, ampm); break; case ON_TimeFormat::HourMinuteSecond12: - hms = ON_wString::FormatToString(L"%d%ls%d%ls%d%ls", t.tm_hour%12, ts, t.tm_min, ts, t.tm_sec, ampm); + hms = ON_wString::FormatToString(L"%02d%ls%02d%ls%02d%ls", hour%12, ts, minute, ts, second, ampm); break; case ON_TimeFormat::HourMinute24: - hms = ON_wString::FormatToString(L"%d%ls%d", t.tm_hour, ts, t.tm_min); + hms = ON_wString::FormatToString(L"%02d%ls%02d", hour, ts, minute); break; case ON_TimeFormat::HourMinuteSecond24: - hms = ON_wString::FormatToString(L"%d%ls%d%ls%d", t.tm_hour, ts, t.tm_min, ts, t.tm_sec); + hms = ON_wString::FormatToString(L"%02d%ls%02d%ls%02d", hour, ts, minute, ts, second); break; default: break; @@ -480,8 +596,6 @@ const ON_wString ON_wString::FromTime( ON_wString result = date; if (result.IsNotEmpty() && hms.IsNotEmpty()) { - if (0 == date_time_separator) - date_time_separator = ON_wString::Space; const wchar_t dts[] = { date_time_separator,0 }; result += dts; } @@ -613,7 +727,7 @@ bool ON_VARGS_FUNC_CDECL ON_String::Format(const unsigned char* format, ...) return rc; } -const ON_wString ON_VARGS_FUNC_CDECL ON_String::FormatToString( +const ON_String ON_VARGS_FUNC_CDECL ON_String::FormatToString( const char* format, ... ) diff --git a/opennurbs_subd.cpp b/opennurbs_subd.cpp index 6b4572a7..505ec855 100644 --- a/opennurbs_subd.cpp +++ b/opennurbs_subd.cpp @@ -408,6 +408,24 @@ ON__UINT_PTR ON_SubDEdgePtr::EdgeDirection() const return ON_SUBD_EDGE_DIRECTION(m_ptr); } +const class ON_SubDVertex* ON_SubDEdgePtr::RelativeVertex( + int relative_vertex_index + ) const +{ + for (;;) + { + if (relative_vertex_index < 0 || relative_vertex_index>1) + break; + const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(m_ptr); + if (nullptr == edge) + break; + if (0 != ON_SUBD_EDGE_DIRECTION(m_ptr)) + relative_vertex_index = 1 - relative_vertex_index; + return edge->m_vertex[relative_vertex_index]; + } + return nullptr; +} + ON_ComponentStatus ON_SubDEdgePtr::Status() const { const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(m_ptr); @@ -1368,15 +1386,32 @@ bool ON_SubDVertex::IsSmoothOrCrease() const return (ON_SubD::VertexTag::Smooth == m_vertex_tag || ON_SubD::VertexTag::Crease == m_vertex_tag); } -bool ON_SubDEdge::IsCrease( - bool bEdgeTagXresult - ) const +bool ON_SubDEdge::IsCrease() const { - if (ON_SubD::EdgeTag::Crease == m_edge_tag) - return true; - if (ON_SubD::EdgeTag::X == m_edge_tag) - return (bEdgeTagXresult ? true : false); - return false; + return (ON_SubD::EdgeTag::Crease == m_edge_tag) ? true : false; +} + + +bool ON_SubDEdge::IsHardCrease() const +{ + return + ( + ON_SubD::EdgeTag::Crease == m_edge_tag + && nullptr != m_vertex[0] + && nullptr != m_vertex[1] + && m_vertex[0]->IsCreaseOrCorner() + && m_vertex[1]->IsCreaseOrCorner() + ) + ? true + : false; +} + +bool ON_SubDEdge::IsDartCrease() const +{ + return + (ON_SubD::EdgeTag::Crease == m_edge_tag && DartCount() > 0 ) + ? true + : false; } unsigned int ON_SubDEdge::DartCount() const @@ -1389,15 +1424,19 @@ unsigned int ON_SubDEdge::DartCount() const return dart_count; } -bool ON_SubDEdge::IsSmooth( - bool bEdgeTagXresult - ) const +bool ON_SubDEdge::IsSmooth() const { - if (ON_SubD::EdgeTag::Smooth == m_edge_tag) - return true; - if (ON_SubD::EdgeTag::X == m_edge_tag) - return (bEdgeTagXresult ? true : false); - return false; + return (ON_SubD::EdgeTag::Smooth == m_edge_tag || ON_SubD::EdgeTag::X == m_edge_tag) ? true : false; +} + +bool ON_SubDEdge::IsSmoothNotX() const +{ + return (ON_SubD::EdgeTag::Smooth == m_edge_tag) ? true : false; +} + +bool ON_SubDEdge::IsSmoothX() const +{ + return (ON_SubD::EdgeTag::X == m_edge_tag) ? true : false; } bool ON_SubDVertex::IsStandard( @@ -1591,7 +1630,7 @@ unsigned int ON_SubDEdge::EdgeFlags() const edge_topology_attributes |= ON_ComponentAttributes::EdgeFlags::Damaged; else { - if (IsSmooth(true)) + if (IsSmooth()) edge_topology_attributes |= ON_ComponentAttributes::EdgeFlags::Smooth; else if ( m_vertex[0]->IsDart() || m_vertex[1]->IsDart() ) edge_topology_attributes |= ON_ComponentAttributes::EdgeFlags::Dart; @@ -1947,6 +1986,20 @@ const ON_SubDVertex* ON_SubDEdge::OtherEndVertex( return nullptr; } +const ON_3dPoint ON_SubDEdge::EndPoint( unsigned int i) const +{ + if (i >= 2 || nullptr == m_vertex[i]) + return ON_3dPoint::NanPoint; + return (ON_3dPoint(m_vertex[i]->m_P)); +} + +const ON_3dVector ON_SubDEdge::Direction() const +{ + if (nullptr == m_vertex[0] || nullptr == m_vertex[1]) + return ON_3dVector::NanVector; + return (ON_3dPoint(m_vertex[1]->m_P) - ON_3dPoint(m_vertex[0]->m_P)); +} + ////////////////////////////////////////////////////////////////////////// // @@ -2511,6 +2564,13 @@ ON_SubD& ON_SubD::operator=( ON_SubD&& src ) #endif +ON__UINT64 ON_SubD::RuntimeSerialNumber() const +{ + ON_SubDimple* subdimple = m_subdimple_sp.get(); + return (nullptr != subdimple) ? subdimple->RuntimeSerialNumber : 0; +} + + ////////////////////////////////////////////////////////////////////////// // @@ -2577,7 +2637,7 @@ static bool IsValidVertexEdgeLink( //if (ON_SubDVertexPtr::NoCorner != corner_type && ON_SubD::VertexTag::Corner != vertex->m_vertex_tag) // return ON_SubDIsNotValid(bSilentError); - if ( edge->IsSmooth(true) ) + if ( edge->IsSmooth() ) { // edge->m_edge_tag is ON_SubD::EdgeTag::Smooth or ON_SubD::EdgeTag::X if (ON_SubD::VertexTag::Smooth == vertex->m_vertex_tag) @@ -2877,7 +2937,7 @@ static bool IsValidSubDEdge( if (edge_vertex[0] == edge_vertex[1]) return ON_SubDIsNotValid(bSilentError); - if (edge->IsSmooth(true)) + if (edge->IsSmooth()) { // m_edge_tag is ON_SubD::EdgeTag::Smooth or ON_SubD::EdgeTag::X if ( 2 != edge->m_face_count) @@ -3045,6 +3105,8 @@ bool ON_SubDimple::IsValidLevel( return ON_SubDIsNotValid(bSilentError); if (1 + v_id_range[1] - v_id_range[0] < level->m_vertex_count) return ON_SubDIsNotValid(bSilentError); + if ( v_id_range[1] > m_max_vertex_id ) + return ON_SubDIsNotValid(bSilentError); // currently, point vertices are not permitted if (point_vertex_count > 0) @@ -3092,12 +3154,13 @@ bool ON_SubDimple::IsValidLevel( return ON_SubDIsNotValid(bSilentError); if (level->m_edge[1] != last_edge) return ON_SubDIsNotValid(bSilentError); - - + if ( e_id_range[1] > m_max_edge_id ) + return ON_SubDIsNotValid(bSilentError); + // currently, wire edges are not permitted if (wire_edge_count > 0) return ON_SubDIsNotValid(bSilentError); - + // simple face validation if (level_index == subd.ActiveLevelIndex()) { @@ -3137,6 +3200,8 @@ bool ON_SubDimple::IsValidLevel( return ON_SubDIsNotValid(bSilentError); if (level->m_face[1] != last_face) return ON_SubDIsNotValid(bSilentError); + if ( f_id_range[1] > m_max_face_id ) + return ON_SubDIsNotValid(bSilentError); // vertex topology validation @@ -3224,6 +3289,36 @@ bool ON_SubDimple::IsValid( if (false == IsValidLevel(subd, level_index, bSilentError, text_log)) return false; } + + if (false == m_heap.IsValid()) + { + // id values are not increasing in the heap blocks. + if (nullptr != text_log) + text_log->Print("Component ids are not set correctly. m_heap.ResetId() will fix this but may break externally stored component references.\n"); + return ON_SubDIsNotValid(bSilentError); + } + + if (MaximumVertexId() < m_heap.MaximumVertexId()) + { + if (nullptr != text_log) + text_log->Print("MaximumVertexId() = %u < m_heap.MaximumVertexId() = %u\n", MaximumVertexId(), m_heap.MaximumVertexId()); + return ON_SubDIsNotValid(bSilentError); + } + + if (MaximumEdgeId() < m_heap.MaximumEdgeId()) + { + if (nullptr != text_log) + text_log->Print("MaximumEdgeId() = %u < m_heap.MaximumEdgeId() = %u\n", MaximumEdgeId(), m_heap.MaximumEdgeId()); + return ON_SubDIsNotValid(bSilentError); + } + + if (MaximumFaceId() Print("MaximumFaceId() = %u < m_heap.MaximumFaceId() = %u\n", MaximumFaceId(), m_heap.MaximumFaceId()); + return ON_SubDIsNotValid(bSilentError); + } + return true; } @@ -3276,10 +3371,13 @@ unsigned int ON_SubD::DumpTopology( const unsigned int level_count = LevelCount(); const unsigned int active_level_index = ActiveLevel().m_level_index; + + const ON__UINT64 runtime_sn = (text_log.IsTextHash()) ? 0 : RuntimeSerialNumber(); // TextHash ignores settings that don't depend on 3dm file content. + if (level_count > 1) - text_log.Print(L"SubD: %u levels. Level %u is active.\n", level_count, active_level_index); + text_log.Print(L"SubD[%" PRIu64 "]: %u levels. Level %u is active.\n", runtime_sn, level_count, active_level_index); else - text_log.Print(L"SubD:\n"); + text_log.Print(L"SubD[%" PRIu64 "]:\n", runtime_sn ); ON_SubDLevelIterator lit(subdimple->LevelIterator()); @@ -3304,13 +3402,23 @@ unsigned int ON_SubD::DumpTopology( ? face_id_range : empty_id_range; - error_count += level->DumpTopology(level_vertex_id_range, level_edge_id_range, level_face_id_range, text_log); + error_count += level->DumpTopology( + subdimple->MaximumVertexId(), + subdimple->MaximumEdgeId(), + subdimple->MaximumFaceId(), + level_vertex_id_range, + level_edge_id_range, + level_face_id_range, + text_log); } return error_count; } unsigned int ON_SubDLevel::DumpTopology( + const unsigned int validate_max_vertex_id, + const unsigned int validate_max_edge_id, + const unsigned int validate_max_face_id, ON_2udex vertex_id_range, ON_2udex edge_id_range, ON_2udex face_id_range, @@ -3413,25 +3521,49 @@ unsigned int ON_SubDLevel::DumpTopology( // unsigned int vertex_count = 0; unsigned int vertex_dump_count = 0; + ON_2udex skipped_vertex_id = ON_2udex::Zero; + unsigned int max_vertex_id = 0; for (const ON_SubDVertex* v = m_vertex[0]; nullptr != v; v = v->m_next_vertex) { if (vertex_count >= m_vertex_count && v->m_level != level_index) break; + if (v->m_id > max_vertex_id) + max_vertex_id = v->m_id; + vertex_count++; if (bVertexIdTest) { - if (ON_UNSET_UINT_INDEX == vertex_id_range.i) - continue; - if (vertex_max_dump_count > 0) + bool bSkip = true; + for (;;) { - if (vertex_count > vertex_max_dump_count) - continue; + if (ON_UNSET_UINT_INDEX == vertex_id_range.i) + break; + if (vertex_max_dump_count > 0) + { + if (vertex_count > vertex_max_dump_count) + break; + } + else + { + if (v->m_id < vertex_id_range.i || v->m_id >= vertex_id_range.j) + break; + } + bSkip = false; + break; } - else + if (bSkip) { - if (v->m_id < vertex_id_range.i || v->m_id >= vertex_id_range.j) - continue; + if (0 == skipped_vertex_id.j) + { + skipped_vertex_id.i = v->m_id; + skipped_vertex_id.j = v->m_id; + } + else if (v->m_id < skipped_vertex_id.i) + skipped_vertex_id.i = v->m_id; + else if (v->m_id > skipped_vertex_id.j) + skipped_vertex_id.j = v->m_id; + continue; } } @@ -3505,12 +3637,22 @@ unsigned int ON_SubDLevel::DumpTopology( text_log.PopIndent(); } + text_log.PushIndent(); if (vertex_dump_count > 0 && vertex_dump_count < vertex_count) { - text_log.PushIndent(); - text_log.Print(L"... %u additional vertices.\n", vertex_count-vertex_dump_count); - text_log.PopIndent(); + text_log.Print( + L"... %u additional vertices (v%u to v%u).\n", + vertex_count-vertex_dump_count, + skipped_vertex_id.i, + skipped_vertex_id.j + ); } + text_log.Print("Maximum vertex id = %u. ",max_vertex_id); + if (validate_max_vertex_id >= max_vertex_id) + text_log.Print("Next id = %u.\n", validate_max_vertex_id + 1); + else + text_log.Print("ERROR Next id = %u.\n", validate_max_vertex_id + 1); + text_log.PopIndent(); /////////////////////////////////////////////////////////////////// // @@ -3520,25 +3662,48 @@ unsigned int ON_SubDLevel::DumpTopology( // unsigned int edge_count = 0; unsigned int edge_dump_count = 0; + ON_2udex skipped_edge_id = ON_2udex::Zero; + unsigned int max_edge_id = 0; for (const ON_SubDEdge* e = m_edge[0]; nullptr != e; e = e->m_next_edge) { if (edge_count >= m_edge_count && e->m_level != level_index) break; + if (e->m_id > max_edge_id) + max_edge_id = e->m_id; edge_count++; if (bEdgeIdTest) { - if (ON_UNSET_UINT_INDEX == edge_id_range.i) - continue; - if (edge_max_dump_count > 0) + bool bSkip = true; + for (;;) { - if (edge_count > edge_max_dump_count) - continue; + if (ON_UNSET_UINT_INDEX == edge_id_range.i) + break; + if (edge_max_dump_count > 0) + { + if (edge_count > edge_max_dump_count) + break; + } + else + { + if (e->m_id < edge_id_range.i || e->m_id >= edge_id_range.j) + break; + } + bSkip = false; + break; } - else + if (bSkip) { - if (e->m_id < edge_id_range.i || e->m_id >= edge_id_range.j) - continue; + if (0 == skipped_edge_id.j) + { + skipped_edge_id.i = e->m_id; + skipped_edge_id.j = e->m_id; + } + else if (e->m_id < skipped_edge_id.i) + skipped_edge_id.i = e->m_id; + else if (e->m_id > skipped_edge_id.j) + skipped_edge_id.j = e->m_id; + continue; } } @@ -3611,12 +3776,21 @@ unsigned int ON_SubDLevel::DumpTopology( text_log.PopIndent(); } + text_log.PushIndent(); if (edge_dump_count > 0 && edge_dump_count < edge_count) { - text_log.PushIndent(); - text_log.Print(L"... %u additional edges.\n", edge_count - edge_dump_count); - text_log.PopIndent(); + text_log.Print(L"... %u additional edges (e%u to e%u).\n", + edge_count - edge_dump_count, + skipped_edge_id.i, + skipped_edge_id.j + ); } + text_log.Print("Maximum edge id = %u. ",max_edge_id); + if (validate_max_edge_id >= max_edge_id) + text_log.Print("Next id = %u.\n", validate_max_edge_id + 1); + else + text_log.Print("ERROR Next id = %u.\n", validate_max_edge_id + 1); + text_log.PopIndent(); /////////////////////////////////////////////////////////////////// @@ -3628,25 +3802,48 @@ unsigned int ON_SubDLevel::DumpTopology( // face_count = 0; unsigned int face_dump_count = 0; + ON_2udex skipped_face_id = ON_2udex::Zero; + unsigned int max_face_id = 0; for (const ON_SubDFace* f = m_face[0]; nullptr != f; f = f->m_next_face) { if (face_count >= m_face_count && f->m_level != level_index) break; + if (f->m_id > max_face_id) + max_face_id = f->m_id; face_count++; if (bFaceIdTest) { - if (ON_UNSET_UINT_INDEX == face_id_range.i) - continue; - if (face_max_dump_count > 0) + bool bSkip = true; + for (;;) { - if (face_count > face_max_dump_count) - continue; + if (ON_UNSET_UINT_INDEX == face_id_range.i) + break; + if (face_max_dump_count > 0) + { + if (face_count > face_max_dump_count) + break; + } + else + { + if (f->m_id < face_id_range.i || f->m_id >= face_id_range.j) + break; + } + bSkip = false; + break; } - else + if (bSkip) { - if (f->m_id < face_id_range.i || f->m_id >= face_id_range.j) - continue; + if (0 == skipped_face_id.j) + { + skipped_face_id.i = f->m_id; + skipped_face_id.j = f->m_id; + } + else if (f->m_id < skipped_face_id.i) + skipped_face_id.i = f->m_id; + else if (f->m_id > skipped_face_id.j) + skipped_face_id.j = f->m_id; + continue; } } @@ -3741,12 +3938,23 @@ unsigned int ON_SubDLevel::DumpTopology( text_log.PopIndent(); } + text_log.PushIndent(); if (face_dump_count > 0 && face_dump_count < face_count) { - text_log.PushIndent(); - text_log.Print(L"... %u additional faces.\n", face_count - face_dump_count); - text_log.PopIndent(); + text_log.Print( + L"... %u additional faces (f%u to f%u).\n", + face_count - face_dump_count, + skipped_face_id.i, + skipped_face_id.j + ); } + text_log.Print("Maximum face id = %u. ",max_face_id); + if (validate_max_face_id >= max_face_id) + text_log.Print("Next id = %u.\n", validate_max_face_id + 1); + else + text_log.Print("ERROR Next id = %u.\n", validate_max_face_id + 1); + text_log.PopIndent(); + const unsigned int topology_error_count = vertex_error_count @@ -3866,118 +4074,6 @@ bool ON_SubD::GetBBox( return (rc || bGrowBox); } - - -class /*DO NOT EXPORT*/ ON_SubD_GetTightBoundingBoxContext -{ -public: - ON_SubD_GetTightBoundingBoxContext(const ON_Xform* xform) - { - if (nullptr != xform && xform->IsNotIdentity() && false == xform->IsZero() ) - m_xform = *xform; - else - m_xform.m_xform[0][0] = ON_UNSET_VALUE; - m_bbox.m_min.x = ON_UNSET_VALUE; - } - ON_BoundingBox m_bbox = ON_BoundingBox::EmptyBoundingBox; - ON_Xform m_xform = ON_Xform::IdentityTransformation; - static bool FragmentCallback( - ON__UINT_PTR context_as_void, - const class ON_SubDLimitMeshFragment* fragment - ); -}; - -bool ON_SubD_GetTightBoundingBoxContext::FragmentCallback( - ON__UINT_PTR context_as_void, - const class ON_SubDLimitMeshFragment* fragment - ) -{ - ON_SubD_GetTightBoundingBoxContext* context = (ON_SubD_GetTightBoundingBoxContext*)context_as_void; - - ON_BoundingBox bbox; - if (ON_UNSET_VALUE == context->m_xform.m_xform[0][0]) - { - bbox = fragment->m_bbox; - } - else - { - const double* P = fragment->m_P; - const size_t P_stride = fragment->m_P_stride; - bbox.m_min = context->m_xform*ON_3dPoint(P); - bbox.m_max = bbox.m_min; - for (const double* P1 = P + fragment->m_P_count*P_stride; P < P1; P += P_stride) - { - const ON_3dPoint Q = context->m_xform*ON_3dPoint(P); - if ( Q.x < bbox.m_min.x ) - bbox.m_min.x = Q.x; - else if ( Q.x > bbox.m_max.x ) - bbox.m_max.x = Q.x; - if ( Q.y < bbox.m_min.y ) - bbox.m_min.y = Q.y; - else if ( Q.y > bbox.m_max.y ) - bbox.m_max.y = Q.y; - if ( Q.z < bbox.m_min.z ) - bbox.m_min.z = Q.z; - else if ( Q.z > bbox.m_max.z ) - bbox.m_max.z = Q.z; - } - } - - if (ON_UNSET_VALUE == context->m_bbox.m_min.x) - context->m_bbox = bbox; - else - context->m_bbox.Union(bbox); - - return true; // continue sending fragments -} - -bool ON_SubD::GetTightBoundingBox( - ON_BoundingBox& tight_bbox, - bool bGrowBox, - const ON_Xform* xform - ) const -{ - if ( 0 != xform ) - { - if ( !xform->IsValid() ) - return false; - - if ( xform->IsIdentity() ) - xform = 0; - } - - if ( !tight_bbox.IsValid() ) - bGrowBox = false; - - const ON_SubDimple* subdimple = SubDimple(); - if ( 0 == subdimple ) - return bGrowBox?true:false; - - const unsigned int level_count = subdimple->LevelCount(); - if ( level_count <= 0 ) - return bGrowBox?true:false; - - ON_SubD_GetTightBoundingBoxContext context(xform); - ON_SubDDisplayParameters display_parameters; - display_parameters.m_display_density = 3; - GetLimitSurfaceMeshInFragments( - display_parameters, - (ON__UINT_PTR)&context, - ON_SubD_GetTightBoundingBoxContext::FragmentCallback - ); - - const bool rc = context.m_bbox.IsValid(); - if (rc) - { - if (bGrowBox) - tight_bbox.Union(context.m_bbox); - else - tight_bbox = context.m_bbox; - bGrowBox = true; - } - return (rc || bGrowBox); -} - //virtual void ON_SubD::ClearBoundingBox() { @@ -4025,8 +4121,6 @@ bool ON_SubD::SwapCoordinates( { return false; } - - //virtual bool ON_SubD::HasBrepForm() const @@ -4110,10 +4204,16 @@ void ON_SubD::ShareDimple(const class ON_SubDLimitMeshImpl& subd_limple) { std::shared_ptr limple_sp(subd_limple.m_subdimple_wp.lock()); - if (nullptr == limple_sp.get()) - const_cast< ON_SubDLimitMeshImpl& >(subd_limple).ClearFragmentFacePointers(); + const ON_SubDimple* subd_imple = m_subdimple_sp.get(); - if (m_subdimple_sp.get() != limple_sp.get()) + if (nullptr == limple_sp.get()) + { + // weak pointer is nullptr, meaning there are no known references to the + // subd used to create this limit mesh. + const_cast(subd_limple).ClearFragmentFacePointers(true); + } + + if (subd_imple != limple_sp.get()) { m_subdimple_sp.reset(); m_subdimple_sp = limple_sp; @@ -4177,6 +4277,14 @@ bool ON_SubD::SetSubDType( return (subdimple) ? subdimple->SetSubDType(subdivision_type) : ON_SUBD_RETURN_ERROR(false); } + +class ON_SubDVertex* ON_SubD::AddVertex( + const double* P +) +{ + return AddVertex(ON_SubD::VertexTag::Unset, P); +} + class ON_SubDVertex* ON_SubD::AddVertex( ON_SubD::VertexTag vertex_tag, const double* P @@ -4314,6 +4422,15 @@ ON_SubD::EdgeTag ON_SubD::EdgeTagFromContext( return edge_tag; } + +class ON_SubDEdge* ON_SubD::AddEdge( + ON_SubDVertex* v0, + ON_SubDVertex* v1 +) +{ + return ON_SubD::AddEdge(ON_SubD::EdgeTag::Unset, v0, v1); +} + class ON_SubDEdge* ON_SubD::AddEdge( ON_SubD::EdgeTag edge_tag, ON_SubDVertex* v0, @@ -4555,6 +4672,54 @@ bool ON_SubDimple::SetSubDType( return subd_level->SetSubDType(subd_type); } +class ON_SubDFace* ON_SubD::AddTriangleFace( + class ON_SubDEdge* edge0, bool bReverseEdge0, + class ON_SubDEdge* edge1, bool bReverseEdge1, + class ON_SubDEdge* edge2, bool bReverseEdge2 +) +{ + return AddTriangleFace( + ON_SubDEdgePtr::Create(edge0, bReverseEdge0 ? 1 : 0), + ON_SubDEdgePtr::Create(edge1, bReverseEdge1 ? 1 : 0), + ON_SubDEdgePtr::Create(edge2, bReverseEdge2 ? 1 : 0) + ); +} + +class ON_SubDFace* ON_SubD::AddTriangleFace( + ON_SubDEdgePtr edge0, + ON_SubDEdgePtr edge1, + ON_SubDEdgePtr edge2 +) +{ + ON_SubDEdgePtr eptr3[3] = { edge0,edge1,edge2 }; + return AddFace(3, eptr3); +} + +class ON_SubDFace* ON_SubD::AddQuadFace( + class ON_SubDEdge* edge0, bool bReverseEdge0, + class ON_SubDEdge* edge1, bool bReverseEdge1, + class ON_SubDEdge* edge2, bool bReverseEdge2, + class ON_SubDEdge* edge3, bool bReverseEdge3 +) +{ + return AddQuadFace( + ON_SubDEdgePtr::Create(edge0, bReverseEdge0 ? 1 : 0), + ON_SubDEdgePtr::Create(edge1, bReverseEdge1 ? 1 : 0), + ON_SubDEdgePtr::Create(edge2, bReverseEdge2 ? 1 : 0), + ON_SubDEdgePtr::Create(edge3, bReverseEdge3 ? 1 : 0) + ); +} + +class ON_SubDFace* ON_SubD::AddQuadFace( + ON_SubDEdgePtr edge0, + ON_SubDEdgePtr edge1, + ON_SubDEdgePtr edge2, + ON_SubDEdgePtr edge3 + ) +{ + ON_SubDEdgePtr eptr4[4] = { edge0,edge1,edge2,edge3 }; + return AddFace(4, eptr4); +} class ON_SubDFace* ON_SubD::AddFace( unsigned int edge_count, @@ -4907,6 +5072,141 @@ bool ON_SubDComponentBase::GetSavedSubdivisionPoint( return true; } + +const ON_ComponentStatus ON_SubDComponentBase::Status() const +{ + return m_status; +} + +const ON_ComponentStatus ON_SubDVertex::NeighborhoodStatusLogicalOr( + bool bIncludeEdges, + bool bIncludeFaces +) const +{ + ON_ComponentStatus s(m_status); + if (bIncludeEdges && nullptr != m_edges) + { + for (unsigned short vei = 0; vei < m_edge_count; vei++) + { + const ON_SubDEdge* e = m_edges[vei].Edge(); + if (nullptr != e) + s = ON_ComponentStatus::LogicalOr(s, e->m_status); + } + } + if (bIncludeFaces && nullptr != m_faces) + { + for (unsigned short vfi = 0; vfi < m_face_count; vfi++) + { + const ON_SubDFace* f = m_faces[vfi]; + if (nullptr != f) + s = ON_ComponentStatus::LogicalOr(s, f->m_status); + } + } + return s; +} + +const ON_ComponentStatus ON_SubDEdge::NeighborhoodStatusLogicalOr( + bool bIncludeVertices, + bool bIncludeFaces +) const +{ + ON_ComponentStatus s(m_status); + if (bIncludeVertices) + { + for (unsigned int evi = 0; evi < 2; evi++) + { + const ON_SubDVertex* v = m_vertex[evi]; + if (nullptr != v) + s = ON_ComponentStatus::LogicalOr(s, v->m_status); + } + } + if (bIncludeFaces) + { + const ON_SubDFacePtr* fptr = m_face2; + for (unsigned short vfi = 0; vfi < m_face_count; vfi++) + { + const ON_SubDFace* f = fptr->Face(); + if (nullptr != f) + s = ON_ComponentStatus::LogicalOr(s, f->m_status); + if (1 == vfi) + { + fptr = m_facex; + if (nullptr == fptr) + break; + } + else + fptr++; + } + } + return s; +} + +const ON_ComponentStatus ON_SubDFace::NeighborhoodStatusLogicalOr(bool bIncludeVertices, bool bIncludeEdges) const +{ + ON_ComponentStatus s(m_status); + if (bIncludeVertices || bIncludeEdges) + { + const ON_SubDEdgePtr* eptr = m_edge4; + for (unsigned int fei = 0; fei < m_edge_count; fei++) + { + if (4 == fei) + { + eptr = m_edgex; + if (nullptr == eptr) + break; + } + const ON_SubDEdge* e = eptr->Edge(); + if (nullptr != e) + { + if (bIncludeEdges) + { + s = ON_ComponentStatus::LogicalOr(s, e->m_status); + } + if (bIncludeVertices) + { + const ON_SubDVertex* v = e->m_vertex[(0!=eptr->EdgeDirection())?1:0]; + if (nullptr != v) + s = ON_ComponentStatus::LogicalOr(s, v->m_status); + } + } + eptr++; + } + } + return s; +} + +static void Internal_ClearFaceNeighborhoodCache(const ON_SubDFace* face) +{ + // Clear cached values for every component associated with this face. + face->ClearSavedSubdivisionPoint(); + const ON_SubDEdgePtr* eptr = face->m_edge4; + for (unsigned int efi = 0; efi < face->m_edge_count; efi++) + { + if (4 == efi) + { + eptr = face->m_edgex; + if ( nullptr == eptr) + break; + } + const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(eptr->m_ptr); + if (nullptr != edge) + { + edge->ClearSavedSubdivisionPoint(); + edge->UnsetSectorCoefficients(); + for (unsigned int evi = 0; evi < 2; evi++) + { + const ON_SubDVertex* vertex = edge->m_vertex[evi]; + if (nullptr != vertex) + { + vertex->ClearSavedSubdivisionPoint(); + vertex->ClearSavedLimitPoints(); + } + } + } + eptr++; + } +} + void ON_SubDVertex::VertexModifiedNofification() const { ClearSavedSubdivisionPoint(); @@ -4916,17 +5216,21 @@ void ON_SubDVertex::VertexModifiedNofification() const for (unsigned short vei = 0; vei < m_edge_count; vei++) { const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(m_edges[vei].m_ptr); - if ( nullptr != edge ) + if (nullptr != edge) + { edge->ClearSavedSubdivisionPoint(); + edge->UnsetSectorCoefficients(); + } } - } - if (nullptr != m_faces) - { + + // This is needed to clear cached information in the Catmull-Clark + // ring that is not immediately adjacent to this vertex but whose values + // this vertex affects. for (unsigned short vfi = 0; vfi < m_face_count; vfi++) { - const ON_SubDFace* face = m_faces[vfi]; - if ( nullptr != face ) - face->ClearSavedSubdivisionPoint(); + const ON_SubDFace* face = this->m_faces[vfi]; + if (nullptr != face) + Internal_ClearFaceNeighborhoodCache(face); } } } @@ -4934,14 +5238,17 @@ void ON_SubDVertex::VertexModifiedNofification() const void ON_SubDEdge::EdgeModifiedNofification() const { ClearSavedSubdivisionPoint(); + UnsetSectorCoefficients(); for (unsigned int evi = 0; evi < 2; evi++) { + const_cast(this)->m_sector_coefficient[evi] = ON_SubDSectorType::UnsetSectorWeight; if (nullptr != m_vertex[evi]) - { - m_vertex[evi]->ClearSavedSubdivisionPoint(); - m_vertex[evi]->ClearSavedLimitPoints(); - } + m_vertex[evi]->VertexModifiedNofification(); } + + // If the topology pointers are complete and accurate, then the following + // is not required. It's here because this SubD may be under construction + // and we cannot assume the topology pointers are complete and accurate. const ON_SubDFacePtr* fptr = m_face2; for (unsigned int efi = 0; efi < 2; efi++) { @@ -4953,14 +5260,25 @@ void ON_SubDEdge::EdgeModifiedNofification() const } const ON_SubDFace* face = ON_SUBD_FACE_POINTER(fptr->m_ptr); if ( nullptr != face ) - face->ClearSavedSubdivisionPoint(); + Internal_ClearFaceNeighborhoodCache(face); fptr++; } } +void ON_SubDEdge::UnsetSectorCoefficients() const +{ + const_cast(this)->m_sector_coefficient[0] = ON_SubDSectorType::UnsetSectorWeight; + const_cast(this)->m_sector_coefficient[1] = ON_SubDSectorType::UnsetSectorWeight; +} + + void ON_SubDFace::FaceModifiedNofification() const { - ClearSavedSubdivisionPoint(); + Internal_ClearFaceNeighborhoodCache(this); + + // This is needed to clear cached information in the Catmull-Clark + // ring that is not immediately adjacent to this face but whose values + // this face affects. const ON_SubDEdgePtr* eptr = m_edge4; for (unsigned int efi = 0; efi < m_edge_count; efi++) { @@ -4973,14 +5291,18 @@ void ON_SubDFace::FaceModifiedNofification() const const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(eptr->m_ptr); if (nullptr != edge) { - edge->ClearSavedSubdivisionPoint(); - for (unsigned int evi = 0; evi < 2; evi++) + const ON_SubDFacePtr* fptr = edge->m_face2; + for (unsigned short fei = 0; fei < edge->m_face_count; fei++) { - if (nullptr != edge->m_vertex[evi]) + if (2 == fei) { - edge->m_vertex[evi]->ClearSavedSubdivisionPoint(); - edge->m_vertex[evi]->ClearSavedLimitPoints(); + fptr = edge->m_facex; + if (nullptr == fptr) + break; } + const ON_SubDFace* f = ON_SUBD_FACE_POINTER(fptr->m_ptr); + if (nullptr != f && f != this) + Internal_ClearFaceNeighborhoodCache(f); } } eptr++; @@ -5300,13 +5622,7 @@ bool ON_SubDEdge::GetSubdivisionPoint( const double* edgeP[2] = { edge_vertex[0]->m_P, edge_vertex[1]->m_P }; - - // If you extrude a polygon, so all the "vertical" edges are "x" edges, - // and bSubDivideXEdgesAsSmooth is false, then the result has a barrel like - // bulge in it. Giulio pointed this out in April 2015. - const bool bSubDivideXEdgesAsSmooth = true; - - if ( IsSmooth(bSubDivideXEdgesAsSmooth) ) + if ( IsSmooth() ) { // A smooth edge must have exactly two neighboring faces and // at most one end vertex can be tagged. @@ -5335,7 +5651,7 @@ bool ON_SubDEdge::GetSubdivisionPoint( if ( ON_UNSET_UINT_INDEX == tagged_end || 0.5 == m_sector_coefficient[tagged_end] - || (bSubDivideXEdgesAsSmooth && ON_SubD::EdgeTag::X == m_edge_tag) + || (ON_SubD::EdgeTag::X == m_edge_tag) ) { // ignore edge weights @@ -5424,7 +5740,7 @@ bool ON_SubDEdge::GetSubdivisionPoint( return true; } - if ( IsCrease(false == bSubDivideXEdgesAsSmooth) ) + if ( IsCrease() ) { subdivision_point[0] = 0.5*(edgeP[0][0] + edgeP[1][0]); subdivision_point[1] = 0.5*(edgeP[0][1] + edgeP[1][1]); @@ -5528,7 +5844,7 @@ unsigned int ON_SubDEdge::GetSectorBoundaryEdges( if (vertex != edge1->m_vertex[edge1_end_index]) return GetSectorBoundaryEdgesError(); - if ( edge1->IsSmooth(true) && 2 == edge1->m_face_count ) + if ( edge1->IsSmooth() && 2 == edge1->m_face_count ) { const ON_SubDFace* edge1_faces[2] = { ON_SUBD_FACE_POINTER(edge1->m_face2[0].m_ptr), ON_SUBD_FACE_POINTER(edge1->m_face2[1].m_ptr) }; unsigned int edge1_face_index = (face == edge1_faces[0] ? 1 : 0); @@ -7439,7 +7755,7 @@ ON_SubDEdgePtr ON_SubDimple::MergeEdges( return ON_SUBD_RETURN_ERROR(ON_SubDEdgePtr::Null); ON_SubD::EdgeTag merged_edge_tag - = (e[0]->IsSmooth(true) || e[1]->IsSmooth(true)) + = (e[0]->IsSmooth() || e[1]->IsSmooth()) ? ON_SubD::EdgeTag::Smooth : ON_SubD::EdgeTag::Crease; @@ -7771,6 +8087,11 @@ bool ON_SubD::IsEmpty() const return (nullptr == SubDimple()); } +bool ON_SubD::IsNotEmpty() const +{ + return (nullptr != SubDimple()); +} + ///////////////////////////////////////////////////////// // // Element (Vertex, Edge, Face) access @@ -8654,7 +8975,7 @@ const ON_SubDEdge* ON_SubDimple::SplitFace( evi = 2; if (evi < 2) const_cast(edge)->m_sector_coefficient[vi] = sector_weight[vi]; - if (nullptr == sit.CurrentFace() || sit.InitialFace() == sit.NextFace(true)) + if (nullptr == sit.CurrentFace() || sit.InitialFace() == sit.NextFace(ON_SubDSectorIterator::StopAt::AnyCrease)) break; } } @@ -9097,6 +9418,7 @@ void ON_SubDLevel::ClearSubdivisonAndLimitPoints() const for (const ON_SubDEdge* edge = m_edge[0]; nullptr != edge; edge = edge->m_next_edge) { edge->ClearSavedSubdivisionPoint(); + // NO // Leave these set - they are not "cached" values // edge->UnsetSectorCoefficients(); } for (const ON_SubDFace* face = m_face[0]; nullptr != face; face = face->m_next_face) { @@ -9549,6 +9871,10 @@ unsigned int ON_SubDLevel::UpdateAllTagsAndSectorCoefficients( change_count += UpdateEdgeSectorCoefficients(false); + if ( change_count > 0 ) + m_limit_mesh = ON_SubDLimitMesh::Empty; + + return change_count; } @@ -9588,6 +9914,12 @@ unsigned int ON_SubD::UpdateEdgeSectorCoefficients( return level->UpdateEdgeSectorCoefficients(bUnsetSectorCoefficientsOnly); } +void ON_SubD::SubDModifiedNofification() +{ + ClearLimitSurfaceMesh(); + UpdateAllTagsAndSectorCoefficients(false); +} + unsigned int ON_SubD::UpdateAllTagsAndSectorCoefficients( bool bUnsetValuesOnly ) @@ -9743,7 +10075,7 @@ unsigned int ON_SubDimple::DeleteComponents( bInteriorVertex = false; continue; } - if (edge->IsCrease(false)) + if (edge->IsCrease()) crease_count++; if (2 != edge->m_face_count) bInteriorVertex = false; @@ -9942,6 +10274,45 @@ unsigned int ON_SubD::GetMarkedComponents( return mark_count; } +ON_SubDComponentMarksClearAndRestore::ON_SubDComponentMarksClearAndRestore( + ON_SubD& subd +) +{ + m_subd.ShareContentsFrom(subd); + m_subd.ClearComponentMarks(true, true, true, &m_saved_marked_component_list); +} + +ON_SubDComponentMarksClearAndRestore::~ON_SubDComponentMarksClearAndRestore() +{ + Restore(true); +} + +const ON_SimpleArray& ON_SubDComponentMarksClearAndRestore::SavedMarkedComponentList() const +{ + return m_saved_marked_component_list; +} + +bool ON_SubDComponentMarksClearAndRestore::Restore( + bool bDisableFutureRestore ) +{ + const bool rc = m_bRestore; + if (rc) + { + if ( bDisableFutureRestore) + m_bRestore = false; + if (m_saved_marked_component_list.UnsignedCount() > 0) + m_subd.SetComponentMarks(true, m_saved_marked_component_list); + else + m_subd.ClearComponentMarks(true, true, true, nullptr); + m_saved_marked_component_list.Destroy(); + } + return rc; +} + +void ON_SubDComponentMarksClearAndRestore::DisableRestore() +{ + m_bRestore = false; +} unsigned int ON_SubD::TransformComponents( const ON_Xform& xform, @@ -10165,6 +10536,28 @@ int ON_Internal_ExtrudedVertexPair::CompareMarkedVertexId( return 0; } +static ON_SubD::EdgeTag Internal_AdjustedEdgeTag(const ON_SubDEdge* edge) +{ + if (nullptr == edge || nullptr == edge->m_vertex[0] || nullptr == edge->m_vertex[1]) + return ON_SubD::EdgeTag::Unset; + + + // adjust moved edge tag because vertex tags can change when they get moved. + const ON_SubD::VertexTag evtag[2] = { edge->m_vertex[0]->m_vertex_tag ,edge->m_vertex[1]->m_vertex_tag }; + + if (ON_SubD::VertexTag::Unset == evtag[0] || ON_SubD::VertexTag::Unset == evtag[1]) + return ON_SubD::EdgeTag::Unset; + + if (ON_SubD::VertexTag::Smooth == evtag[0] || ON_SubD::VertexTag::Smooth == evtag[1]) + return ON_SubD::EdgeTag::Smooth; + + const ON_SubD::EdgeTag etag = edge->m_edge_tag; + if (ON_SubD::EdgeTag::Smooth == etag || ON_SubD::EdgeTag::X == etag ) + return ON_SubD::EdgeTag::X; + + return etag; +} + static void Internal_SetEdgeVertices( ON_SubD& subd, ON_Internal_ExtrudedVertexPair& vertex_pair @@ -10293,41 +10686,165 @@ static ON_SubDFace* Internal_AddNewFace( return side.m_new_face; } -bool Internal_ExtrudeAsCrease( - const ON_SubDVertex* v +static ON_SubD::EdgeTag Internal_ConnectingEdgeTagAtVertex( + const ON_SubDVertex* v, + ON_SubD::VertexTag& moved_vertex_tag, + ON_SubD::VertexTag& stationary_vertex_tag ) { - unsigned int marked_faces_crease_count = 0; - unsigned int unmarked_faces_crease_count = 0; + moved_vertex_tag = v->m_vertex_tag; + stationary_vertex_tag = v->m_vertex_tag; + if (ON_SubD::VertexTag::Crease != v->m_vertex_tag && ON_SubD::VertexTag::Dart != v->m_vertex_tag) + { + ON_SUBD_ERROR("This function requires a crease or dart vertex as input."); + return ON_SubD::EdgeTag::Unset; + } + + unsigned int expected_crease_count = ON_UNSET_UINT_INDEX; + switch (v->m_vertex_tag) + { + case ON_SubD::VertexTag::Smooth: + expected_crease_count = 0; + break; + case ON_SubD::VertexTag::Dart: + expected_crease_count = 1; + break; + case ON_SubD::VertexTag::Crease: + expected_crease_count = 2; + break; + case ON_SubD::VertexTag::Corner: + break; + default: + break; + } + +/* + + = (ON_SubD::VertexTag::Dart == v->m_vertex_tag) + ? 1 + : ((ON_SubD::VertexTag::CDart == v->m_vertex_tag) ? 2 : 0; +*/ + const unsigned int vertex_edge_count = v->m_edge_count; + unsigned int sep_crease_count = 0; + unsigned int moved_crease_count = 0; + unsigned int stationary_crease_count = 0; for (unsigned int vei = 0; vei < vertex_edge_count; vei++) { - const ON_SubDEdge* e = v->Edge(vei); + const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(v->m_edges[vei].m_ptr); if (nullptr == e) continue; - if (ON_SubD::EdgeTag::Smooth == e->m_edge_tag) - continue; + if (ON_SubD::EdgeTag::Crease != e->m_edge_tag) - return false; + continue; + if (e->m_status.RuntimeMark()) - return false; - if (2 != e->m_face_count) - return false; - const ON_SubDFace* f = e->Face(0); - if (nullptr == f) - return false; - const bool bMarkedFace = f->m_status.RuntimeMark(); - f = e->Face(1); - if (nullptr == f) - return false; - if (bMarkedFace != f->m_status.RuntimeMark()) - return false; - if (bMarkedFace) - marked_faces_crease_count++; + { + sep_crease_count++; + continue; + } + + bool bThisEdgeMoves = false; + for (unsigned short evi = 0; evi < e->m_face_count; evi++) + { + const ON_SubDFace* f = e->Face(evi); + if (f->m_status.RuntimeMark()) + { + bThisEdgeMoves = true; + break; + } + } + if (bThisEdgeMoves) + ++moved_crease_count; else - unmarked_faces_crease_count++; + ++stationary_crease_count; } - return (1 == marked_faces_crease_count && 1 == unmarked_faces_crease_count); + + + if (ON_SubD::VertexTag::Corner == v->m_vertex_tag && (sep_crease_count + moved_crease_count + stationary_crease_count) >= 2) + expected_crease_count = (sep_crease_count + moved_crease_count + stationary_crease_count); + + if (expected_crease_count != (sep_crease_count + moved_crease_count + stationary_crease_count)) + ON_SUBD_ERROR("Unexpected tags - input is probably invalid."); + + ON_SubD::EdgeTag connecting_edge_tag; + if (0 == stationary_crease_count || 0 == moved_crease_count) + { + if (sep_crease_count >= 2 && ON_SubD::VertexTag::Corner == v->m_vertex_tag) + { + stationary_vertex_tag = ON_SubD::VertexTag::Corner; + moved_vertex_tag = ON_SubD::VertexTag::Corner; + } + else + { + switch (sep_crease_count + stationary_crease_count) + { + case 0: + stationary_vertex_tag = ON_SubD::VertexTag::Smooth; + break; + case 1: + stationary_vertex_tag = ON_SubD::VertexTag::Dart; + break; + case 2: + stationary_vertex_tag = ON_SubD::VertexTag::Crease; + break; + default: + stationary_vertex_tag = ON_SubD::VertexTag::Corner; + break; + } + + switch (sep_crease_count + moved_crease_count) + { + case 0: + moved_vertex_tag = ON_SubD::VertexTag::Smooth; + break; + case 1: + moved_vertex_tag = ON_SubD::VertexTag::Dart; + break; + case 2: + moved_vertex_tag = ON_SubD::VertexTag::Crease; + break; + default: + moved_vertex_tag = ON_SubD::VertexTag::Corner; + break; + } + } + + if ( + (0 == stationary_crease_count && 0 == moved_crease_count) + || ON_SubD::VertexTag::Smooth == stationary_vertex_tag + || ON_SubD::VertexTag::Dart == stationary_vertex_tag + || ON_SubD::VertexTag::Smooth == moved_vertex_tag + || ON_SubD::VertexTag::Dart == moved_vertex_tag + ) + connecting_edge_tag = ON_SubD::EdgeTag::Smooth; + else + connecting_edge_tag = ON_SubD::EdgeTag::Crease; + } + else + { + // Corner "upgrade" check is below. + stationary_vertex_tag = ON_SubD::VertexTag::Crease; + moved_vertex_tag = ON_SubD::VertexTag::Crease; + connecting_edge_tag = ON_SubD::EdgeTag::Crease; + } + + + if (ON_SubD::EdgeTag::Smooth == connecting_edge_tag) + { + if (ON_SubD::VertexTag::Smooth != stationary_vertex_tag && ON_SubD::VertexTag::Smooth!= moved_vertex_tag) + connecting_edge_tag = ON_SubD::EdgeTag::X; // at level 0, both ends are tagged. + } + else + { + // connecting edge is crease + if (sep_crease_count + stationary_crease_count + 1 > 2) + stationary_vertex_tag = ON_SubD::VertexTag::Corner; + if (sep_crease_count + moved_crease_count + 1 > 2) + moved_vertex_tag = ON_SubD::VertexTag::Corner; + } + + return connecting_edge_tag; } static bool Internal_NonManifoldEdgeWillBeCreated( const ON_SubDVertex* v ) @@ -10374,14 +10891,13 @@ unsigned int ON_SubD::ExtrudeComponents( ) return 0; - if (ON_SubD::EdgeTag::Crease != original_edge_tag) + if (ON_SubD::EdgeTag::Crease != original_edge_tag && ON_SubD::EdgeTag::Smooth != original_edge_tag) original_edge_tag = ON_SubD::EdgeTag::Unset; - if (ON_SubD::EdgeTag::Crease != moved_edge_tag) + if (ON_SubD::EdgeTag::Crease != moved_edge_tag && ON_SubD::EdgeTag::Smooth != moved_edge_tag) moved_edge_tag = ON_SubD::EdgeTag::Unset; - - ON_SimpleArray marked_components; - const bool bRestoreMarks = ClearComponentMarks(true, true, true, &marked_components) > 0; + + ON_SubDComponentMarksClearAndRestore mark_guard(*this); // Marks very vertex touching a component in the cptr_list. // Skips applying the transform because it is the identity. @@ -10513,39 +11029,47 @@ unsigned int ON_SubD::ExtrudeComponents( ON_Internal_ExtrudedVertexPair& vpair = vertex_pairs.AppendNew(); vpair.m_marked_vertex = v; - ON_SubD::VertexTag new_vertex_tag; - ON_SubD::EdgeTag new_edge_tag; + ON_SubD::VertexTag moved_vertex_tag; // the original vertex gets moved + ON_SubD::VertexTag stationary_vertex_tag; // (this one get allocated) + ON_SubD::EdgeTag connecting_edge_tag; // from stationary to moved vertex switch (v->m_vertex_tag) { + case ON_SubD::VertexTag::Dart: + connecting_edge_tag = Internal_ConnectingEdgeTagAtVertex(v, moved_vertex_tag, stationary_vertex_tag); + break; case ON_SubD::VertexTag::Crease: - new_vertex_tag = v->m_vertex_tag; - new_edge_tag - = Internal_ExtrudeAsCrease(v) - ? ON_SubD::EdgeTag::Crease - : ON_SubD::EdgeTag::Unset; + connecting_edge_tag = Internal_ConnectingEdgeTagAtVertex(v, moved_vertex_tag, stationary_vertex_tag); break; case ON_SubD::VertexTag::Corner: - new_vertex_tag = v->m_vertex_tag; - new_edge_tag = ON_SubD::EdgeTag::Crease; + moved_vertex_tag = v->m_vertex_tag; + stationary_vertex_tag = v->m_vertex_tag; + connecting_edge_tag = ON_SubD::EdgeTag::Crease; + break; + case ON_SubD::VertexTag::Smooth: + moved_vertex_tag = ON_SubD::VertexTag::Smooth; + stationary_vertex_tag = ON_SubD::VertexTag::Smooth; + connecting_edge_tag = ON_SubD::EdgeTag::Smooth; break; default: - new_vertex_tag = ON_SubD::VertexTag::Unset; - new_edge_tag = ON_SubD::EdgeTag::Unset; + moved_vertex_tag = ON_SubD::VertexTag::Unset; + stationary_vertex_tag = ON_SubD::VertexTag::Unset; + connecting_edge_tag = ON_SubD::EdgeTag::Unset; break; } // original vertex will eventually be moved. - v->m_vertex_tag = new_vertex_tag; + v->m_vertex_tag = moved_vertex_tag; // new vertex will become part of the stationary subset. // It is not marked. - vpair.m_unmarked_vertex = this->AddVertex(new_vertex_tag, v->m_P); + vpair.m_unmarked_vertex = this->AddVertex(stationary_vertex_tag, v->m_P); // transform the marked boundary vertex v->Transform(false, xform); // edge from stationary subset to moved subset. - vpair.m_new_side = this->AddEdge(new_edge_tag, vpair.m_unmarked_vertex, vpair.m_marked_vertex); + ON_SubDEdge* connecting_edge = this->AddEdge(connecting_edge_tag, vpair.m_unmarked_vertex, vpair.m_marked_vertex); + vpair.m_new_side = connecting_edge; } } @@ -10593,7 +11117,12 @@ unsigned int ON_SubD::ExtrudeComponents( } key[evi] = vertex_pairs[i0]; } - new_sides[i].m_unmarked_edge = this->AddEdge(ON_SubD::EdgeTag::Unset, key[0].m_unmarked_vertex, key[1].m_unmarked_vertex); + + const ON_SubD::EdgeTag unmoved_edge_tag + = (ON_SubD::EdgeTag::Crease == e->m_edge_tag) + ? ON_SubD::EdgeTag::Crease + : ON_SubD::EdgeTag::Unset; + new_sides[i].m_unmarked_edge = this->AddEdge(unmoved_edge_tag, key[0].m_unmarked_vertex, key[1].m_unmarked_vertex); new_sides[i].m_new_side0 = key[0].m_new_side; new_sides[i].m_new_side1 = key[1].m_new_side; } @@ -10635,6 +11164,29 @@ unsigned int ON_SubD::ExtrudeComponents( Internal_AddNewFace(*this, new_sides[i]); } + // Any edge touching the vertices in vertex_pairs[] may need its tag adjusted + // because those vertices may have had their tags adjusted. + // Some edges get checked twice, but adding code to check just once + // takes more time than checking twice. + for (unsigned int i = 0; i < vertex_pairs.UnsignedCount(); i++) + { + for (unsigned int j = 0; j < 2; j++) + { + const ON_SubDVertex* v = (0 == j) ? vertex_pairs[i].m_marked_vertex : vertex_pairs[i].m_unmarked_vertex; + if (nullptr == v || nullptr == v->m_edges) + continue; + for (unsigned int vei = 0; vei < v->m_edge_count; vei++) + { + ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(v->m_edges[vei].m_ptr); + if (nullptr == e) + continue; + const ON_SubD::EdgeTag adjusted_etag = Internal_AdjustedEdgeTag(e); + if (ON_SubD::EdgeTag::Unset != adjusted_etag && e->m_edge_tag != adjusted_etag) + e->m_edge_tag = adjusted_etag; + } + } + } + // remove cached subdivision calculations ClearEvaluationCache(); @@ -10643,10 +11195,6 @@ unsigned int ON_SubD::ExtrudeComponents( break; } - - if (bRestoreMarks) - SetComponentMarks(true, marked_components); - #if defined(ON_DEBUG) IsValid(); #endif @@ -10654,6 +11202,1328 @@ unsigned int ON_SubD::ExtrudeComponents( return f_count; } +unsigned int ON_SubD::SetVertexTags( + const ON_COMPONENT_INDEX* ci_list, + size_t ci_count, + ON_SubD::VertexTag vertex_tag +) +{ + if ( + ON_SubD::VertexTag::Smooth != vertex_tag + && ON_SubD::VertexTag::Crease != vertex_tag + && ON_SubD::VertexTag::Corner != vertex_tag + ) + return 0; + + if ( + ci_count <= 0 + || nullptr == ci_list + || VertexCount() <= 0 + ) + return 0; + + ON_SimpleArray cptr_list; + if (ComponentPtrFromComponentIndex(ci_list,ci_count,cptr_list) <= 0) + return 0; // nothing to change + + return SetVertexTags( + cptr_list.Array(), + cptr_list.UnsignedCount(), + vertex_tag + ); +} + +unsigned int ON_SubD::SetVertexTags( + const ON_SubDComponentPtr* cptr_list, + size_t cptr_count, + ON_SubD::VertexTag vertex_tag +) +{ + if ( + ON_SubD::VertexTag::Smooth != vertex_tag + && ON_SubD::VertexTag::Crease != vertex_tag + && ON_SubD::VertexTag::Corner != vertex_tag + ) + return 0; + + if (cptr_count <= 0 || nullptr == cptr_list) + return 0; + + ON_SubDComponentMarksClearAndRestore mark_guard(*this); + + const bool bNewVertexTagIsSmooth = (ON_SubD::VertexTag::Smooth == vertex_tag); + + // count and mark vertex candidates + // mark edges that may need to have their tag changed + unsigned int candidate_count = 0; + for (size_t i = 0; i < cptr_count; i++) + { + ON_SubDVertex* vertex = cptr_list[i].Vertex(); + if (nullptr == vertex) + continue; + if (vertex->m_vertex_tag == vertex_tag) + continue; + + if (ON_SubD::VertexTag::Corner != vertex_tag) + { + // new vertex_tag is Smooth or Crease + if (nullptr == vertex->m_edges || vertex->m_edge_count < 2) + continue; + + unsigned short min_face_count = 0xFFFFU; + unsigned short max_face_count = 0xFFFEU; + for (unsigned short vei = 0; vei < vertex->m_edge_count; vei++) + { + const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(vertex->m_edges[vei].m_ptr); + if (nullptr == edge) + continue; + // The next if (min_face_count > min_face_count) should be if (min_face_count > max_face_count) + // and is a bug that will not be fixed in 6.x because it exposes other bugs below. + // Without the fix, max_face_count is always 0xFFFEU and the continue always occurs. + // It will be fixed in 7.x. See RH-51598. + if (min_face_count > min_face_count) + { + min_face_count = edge->m_face_count; + max_face_count = edge->m_face_count; + } + else if (edge->m_face_count < min_face_count) + min_face_count = edge->m_face_count; + else if (edge->m_face_count > max_face_count) + max_face_count = edge->m_face_count; + } + + if (max_face_count > 2) + continue; + + if (bNewVertexTagIsSmooth && 2 != min_face_count ) + continue; + } + + candidate_count++; + vertex->m_status.SetRuntimeMark(); + if (nullptr != vertex->m_edges) + { + if (ON_SubD::VertexTag::Corner == vertex_tag) + { + const unsigned int crease_count = vertex->EdgeCount(ON_SubD::EdgeTag::Crease); + if (2 == crease_count) + continue; // do not crease additional edges + } + + for (unsigned short vei = 0; vei < vertex->m_edge_count; vei++) + { + const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(vertex->m_edges[vei].m_ptr); + if (nullptr == edge) + continue; + if (bNewVertexTagIsSmooth) + { + // new vertex_tag is Smooth + if (edge->IsSmoothNotX()) + continue; + } + else + { + // new vertex_tag is Crease or Corner + if (edge->IsCrease()) + continue; + } + + // This edge tag will need to be changed + edge->m_status.SetRuntimeMark(); + } + } + } + + if (0 == candidate_count) + return 0; + + bool bUpdateTags = (ON_SubD::VertexTag::Crease != vertex_tag); + + // This for loop is used when new vertex_tag is ON_SubD::VertexTag::Crease. + for (int pass = 0; pass < 2 && false == bUpdateTags; pass++) + { + // More careful analysis is neeeded to accurately mark smooth edges that will become creases + ON_SubDEdgeIterator eit(*this); + for (const ON_SubDEdge* edge = eit.FirstEdge(); nullptr != edge; edge = eit.NextEdge()) + { + if (false == edge->m_status.RuntimeMark()) + continue; + + edge->m_status.ClearRuntimeMark(); + if (false == edge->IsSmooth()) + continue; + + const ON_SubDVertex* v[2] = { edge->m_vertex[0], edge->m_vertex[1] }; + if (nullptr == v[0] || nullptr == v[1]) + continue; + + const ON_SubD::VertexTag vtag[2] = { + (v[0]->m_status.RuntimeMark() ? vertex_tag : v[0]->m_vertex_tag), + (v[1]->m_status.RuntimeMark() ? vertex_tag : v[1]->m_vertex_tag) + }; + + // At least one of v[0] and v[1] had m_vertex_tag changed. + + ON_SubD::EdgeTag edge_tag; + for (;;) + { + if (2 != edge->m_face_count) + { + edge_tag = ON_SubD::EdgeTag::Crease; + break; + } + + if (2 == v[0]->m_edge_count && (ON_SubD::VertexTag::Crease == vtag[0] || ON_SubD::VertexTag::Corner == vtag[0])) + { + edge_tag = ON_SubD::EdgeTag::Crease; + break; + } + + if (2 == v[1]->m_edge_count && (ON_SubD::VertexTag::Crease == vtag[1] || ON_SubD::VertexTag::Corner == vtag[1])) + { + edge_tag = ON_SubD::EdgeTag::Crease; + break; + } + + if ( + (ON_SubD::VertexTag::Crease == vtag[0] || ON_SubD::VertexTag::Corner == vtag[0] || ON_SubD::VertexTag::Dart == vtag[0]) + && + (ON_SubD::VertexTag::Crease == vtag[1] || ON_SubD::VertexTag::Corner == vtag[1] || ON_SubD::VertexTag::Dart == vtag[1]) + ) + { + edge_tag = ON_SubD::EdgeTag::Crease; + break; + } + + edge_tag = ON_SubD::EdgeTag::Smooth; + break; + } + + if (ON_SubD::EdgeTag::Crease == edge_tag) + edge->m_status.SetRuntimeMark(); + } + + // make sure new crease vertices will have the right number of creased edges + bUpdateTags = true; + for (size_t i = 0; i < cptr_count; i++) + { + ON_SubDVertex* vertex = cptr_list[i].Vertex(); + if (nullptr == vertex) + continue; + if (false == vertex->m_status.RuntimeMark()) + continue; + unsigned int crease_count = 0; + unsigned int marked_count = 0; + for (unsigned short vei = 0; vei < vertex->m_edge_count; vei++) + { + const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(vertex->m_edges[vei].m_ptr); + if (nullptr == edge) + continue; + if (edge->IsCrease()) + ++crease_count; + else if (edge->m_status.RuntimeMark()) + ++marked_count; + } + if (crease_count + marked_count <= 2 && (0 != crease_count || 0 != marked_count)) + continue; + + if (pass > 0) + return 0; + bUpdateTags = false; + if (2 == crease_count) + { + for (unsigned short vei = 0; vei < vertex->m_edge_count; vei++) + { + const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(vertex->m_edges[vei].m_ptr); + if (nullptr != edge) + edge->m_status.ClearRuntimeMark(); + } + } + else + { + vertex->m_status.ClearRuntimeMark(); + candidate_count--; + } + } + + if (0 == candidate_count) + return 0; + } + + if (false == bUpdateTags) + return 0; + + unsigned int changed_vertex_count = 0; + for (size_t i = 0; i < cptr_count; i++) + { + ON_SubDVertex* vertex = cptr_list[i].Vertex(); + if (nullptr == vertex) + continue; + if (false == vertex->m_status.RuntimeMark()) + continue; + changed_vertex_count++; + vertex->m_vertex_tag = vertex_tag; + vertex->VertexModifiedNofification(); + for (unsigned short vei = 0; vei < vertex->m_edge_count; vei++) + { + const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(vertex->m_edges[vei].m_ptr); + if (nullptr == edge) + continue; + if ( false == bNewVertexTagIsSmooth + && edge->m_status.RuntimeMark() + && false == edge->IsCrease() + ) + { + const_cast(edge)->m_edge_tag = ON_SubD::EdgeTag::Crease; + edge->EdgeModifiedNofification(); + } + edge->m_status.SetRuntimeMark(); + + const ON_SubDVertex* other_vertex = edge->OtherEndVertex(vertex); + other_vertex->m_status.SetRuntimeMark(); + + if ( false == bNewVertexTagIsSmooth + && ON_SubD::EdgeTag::Crease == edge->m_edge_tag + && other_vertex->IsSmooth() + ) + { + const_cast(other_vertex)->m_vertex_tag = ON_SubD::VertexTag::Dart; + other_vertex->VertexModifiedNofification(); + } + } + } + + if (0 == changed_vertex_count) + return 0; + + ON_SubDEdgeIterator eit(*this); + for (const ON_SubDEdge* edge = eit.FirstEdge(); nullptr != edge; edge = eit.NextEdge()) + { + if (false == edge->m_status.RuntimeMark()) + continue; + const ON_SubDVertex* v[2] = { edge->m_vertex[0], edge->m_vertex[1] }; + if (nullptr == v[0] || nullptr == v[1]) + continue; + + ON_SubD::EdgeTag edge_tag; + if (v[0]->IsCreaseOrCornerOrDart() && v[1]->IsCreaseOrCornerOrDart()) + edge_tag = ON_SubD::EdgeTag::Crease; + else + edge_tag = ON_SubD::EdgeTag::Smooth; + + if (edge->m_edge_tag == edge_tag) + continue; + const_cast(edge)->m_edge_tag = edge_tag; + edge->EdgeModifiedNofification(); + } + + ON_SubDVertexIterator vit(*this); + for (const ON_SubDVertex* vertex = vit.FirstVertex(); nullptr != vertex; vertex = vit.NextVertex()) + { + if (false == vertex->m_status.RuntimeMark()) + continue; + const unsigned int crease_count = vertex->EdgeCount(ON_SubD::EdgeTag::Crease); + ON_SubD::VertexTag vtag = vertex->m_vertex_tag; + if (2 == crease_count) + { + if ( false == vertex->IsCreaseOrCorner() ) + vtag = ON_SubD::VertexTag::Crease; + } + else if (1 == crease_count) + vtag = ON_SubD::VertexTag::Dart; + else if (crease_count > 2) + vtag = ON_SubD::VertexTag::Corner; + else + vtag = ON_SubD::VertexTag::Smooth; + if (vertex->m_vertex_tag == vtag) + continue; + const_cast(vertex)->m_vertex_tag = vtag; + } + + ClearLimitSurfaceMesh(); + UpdateAllTagsAndSectorCoefficients(false); + + return changed_vertex_count; +} + +unsigned int ON_SubD::SetEdgeTags( + const ON_COMPONENT_INDEX* ci_list, + size_t ci_count, + ON_SubD::EdgeTag edge_tag +) +{ + if (ON_SubD::EdgeTag::Smooth != edge_tag && ON_SubD::EdgeTag::Crease != edge_tag) + return 0; + + if ( + ci_count <= 0 + || nullptr == ci_list + || EdgeCount() <= 0 + ) + return 0; + + ON_SimpleArray cptr_list; + if (ComponentPtrFromComponentIndex(ci_list,ci_count,cptr_list) <= 0) + return 0; // nothing to change + + return SetEdgeTags( + cptr_list.Array(), + cptr_list.UnsignedCount(), + edge_tag + ); +} + +unsigned int ON_SubD::SetEdgeTags( + const ON_SubDComponentPtr* cptr_list, + size_t cptr_count, + ON_SubD::EdgeTag edge_tag +) +{ + if (ON_SubD::EdgeTag::Smooth != edge_tag && ON_SubD::EdgeTag::Crease != edge_tag) + return 0; + + if ( + cptr_count <= 0 + || nullptr == cptr_list + || EdgeCount() <= 0 + ) + return 0; + + unsigned int changed_edge_count = 0; + + const bool bChangeToSmooth = (ON_SubD::EdgeTag::Smooth == edge_tag) ? true : false; + + for (size_t i = 0; i < cptr_count; i++) + { + ON_SubDEdge* edge = cptr_list[i].Edge(); + if (nullptr == edge) + continue; + if (bChangeToSmooth == edge->IsSmooth()) + continue; + if (bChangeToSmooth && 2 != edge->FaceCount()) + continue; + + edge->EdgeModifiedNofification(); + + changed_edge_count++; + edge->m_edge_tag = edge_tag; + edge->UnsetSectorCoefficients(); + for (int evi = 0; evi < 2; evi++) + { + ON_SubDVertex* v = const_cast(edge->m_vertex[evi]); + if (nullptr == v) + continue; + v->m_vertex_tag = ON_SubD::VertexTag::Unset; + } + } + + if (0 == changed_edge_count) + return 0; + + ClearLimitSurfaceMesh(); + + ON_SubDVertexIterator vit(*this); + for (const ON_SubDVertex* v = vit.FirstVertex(); nullptr != v; v = vit.NextVertex()) + { + if (ON_SubD::VertexTag::Unset != v->m_vertex_tag) + continue; + unsigned crease_count = 0; + const unsigned vertex_edge_count = v->EdgeCount(); + for (unsigned vei = 0; vei < vertex_edge_count; vei++) + { + const ON_SubDEdge* e = v->Edge(vei); + if (nullptr == e) + continue; + if (e->IsCrease()) + { + crease_count++; + if (crease_count > 2) + break; + } + } + ON_SubD::VertexTag vertex_tag; + switch (crease_count) + { + case 0: + vertex_tag = ON_SubD::VertexTag::Smooth; + break; + case 1: + vertex_tag = ON_SubD::VertexTag::Dart; + break; + case 2: + vertex_tag = ON_SubD::VertexTag::Crease; + break; + default: + vertex_tag = ON_SubD::VertexTag::Corner; + break; + } + if (v->m_vertex_tag != vertex_tag) + { + const_cast(v)->m_vertex_tag = vertex_tag; + } + } + + ON_SubDEdgeIterator eit(*this); + for (const ON_SubDEdge* e = eit.FirstEdge(); nullptr != e; e = eit.NextEdge()) + { + ON_SubD::EdgeTag e_tag = e->m_edge_tag; + if (nullptr == e->m_vertex[0] || nullptr == e->m_vertex[1]) + e_tag = ON_SubD::EdgeTag::Unset; + else if (ON_SubD::EdgeTag::Smooth == e_tag + && ON_SubD::VertexTag::Smooth != e->m_vertex[0]->m_vertex_tag + && ON_SubD::VertexTag::Smooth != e->m_vertex[1]->m_vertex_tag) + e_tag = ON_SubD::EdgeTag::Unset; + if (e_tag != e->m_edge_tag) + { + const_cast(e)->m_edge_tag = e_tag; + e->UnsetSectorCoefficients(); + } + } + + UpdateAllTagsAndSectorCoefficients(false); + return changed_edge_count; +} + + +const ON_SubDEdgePtr ON_SubDEdgeChain::EdgeChainNeighbor( + ON_SubDEdgePtr starting_edge, + ON_ChainDirection search_direction, + bool bStopAtBreak, + bool bEnableStatusCheck, + ON_ComponentStatus status_pass, + ON_ComponentStatus status_fail + ) +{ + for (;;) + { + if (ON_ChainDirection::Previous != search_direction && ON_ChainDirection::Next != search_direction) + break; + const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(starting_edge.m_ptr); + if (nullptr == edge) + break; + bool bReverse = (ON_ChainDirection::Previous == search_direction); + if (0 != ON_SUBD_EDGE_DIRECTION(starting_edge.m_ptr)) + bReverse = !bReverse; + const ON_SubDVertex* v = edge->m_vertex[bReverse ? 0 : 1]; + if (nullptr == v) + break; + if (v->m_edge_count <= 1 || nullptr == v->m_edges) + break; + + const bool bIsSmooth = edge->IsSmooth(); + const bool bIsCrease = edge->IsCrease(); + if (bIsSmooth != (bIsCrease?false:true)) + break; + + if (bStopAtBreak) + { + if (bIsSmooth) + { + if (ON_SubD::VertexTag::Smooth != v->m_vertex_tag) + break; + } + else + { + if (ON_SubD::VertexTag::Crease != v->m_vertex_tag) + break; + } + } + + // Look for a single neighbor with same crease/smooth property and same face count + // This lets chains turn the right way when there are both creases and smooth + // edges. + const ON_SubDEdge* nxt = nullptr; + for (unsigned short vei = 0; vei < v->m_edge_count; vei++) + { + const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(v->m_edges[vei].m_ptr); + if (edge == e) + continue; + if (bIsSmooth != e->IsSmooth()) + continue; + if (bIsCrease != e->IsCrease()) + continue; + if (e->m_face_count != edge->m_face_count) + continue; + if (e->m_vertex[0] != v && e->m_vertex[1] != v) + continue; // bogus edge + if (nullptr == nxt) + { + nxt = e; + continue; + } + // ambiguous options here + nxt = nullptr; + break; + } + const int nxt_connecting_vertex_index = (ON_ChainDirection::Next == search_direction) ? 0 : 1; + if (nullptr != nxt) + { + if (false == bEnableStatusCheck || ON_ComponentStatus::StatusCheck(nxt->m_status, status_pass, status_fail)) + return ON_SubDEdgePtr::Create(nxt, (v == nxt->m_vertex[nxt_connecting_vertex_index]) ? 0 : 1); + } + + if (2 != edge->m_face_count || 4 != v->m_edge_count) + break; + const ON_SubDFace* f[2] = { ON_SUBD_FACE_POINTER(edge->m_face2[0].m_ptr),ON_SUBD_FACE_POINTER(edge->m_face2[1].m_ptr) }; + if (nullptr == f[0] || nullptr == f[1] || f[0] == f[1]) + break; + for (unsigned short vei = 0; vei < v->m_edge_count; vei++) + { + const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(v->m_edges[vei].m_ptr); + if (edge == e) + continue; + if (2 != e->m_face_count) + continue; + if (e->m_vertex[0] != v && e->m_vertex[1] != v) + continue; // bogus edge + const ON_SubDFace* nxtf[2] = { ON_SUBD_FACE_POINTER(e->m_face2[0].m_ptr),ON_SUBD_FACE_POINTER(e->m_face2[1].m_ptr) }; + if (nullptr == nxtf[0] || nullptr == nxtf[1] || nxtf[0] == nxtf[1]) + continue; + if (f[0] == nxtf[0] || f[1] == nxtf[0]) + continue; + if (f[0] == nxtf[1] || f[1] == nxtf[1]) + continue; + if (nullptr == nxt) + { + nxt = e; + continue; + } + // ambiguous options here + nxt = nullptr; + break; + } + if (nullptr != nxt) + { + if (bStopAtBreak && bIsSmooth != nxt->IsSmooth()) + break; + if (false == bEnableStatusCheck || ON_ComponentStatus::StatusCheck(nxt->m_status, status_pass, status_fail)) + return ON_SubDEdgePtr::Create(nxt, (v == nxt->m_vertex[nxt_connecting_vertex_index]) ? 0 : 1); + } + + break; + } + + return ON_SubDEdgePtr::Null; +} + +void ON_UniqueTester::Block::DeleteBlock(Block* blk) +{ + if (nullptr != blk) + onfree(blk); +} + +ON_UniqueTester::Block* ON_UniqueTester::Block::NewBlock() +{ + size_t sz1 = sizeof(Block); + while (0 != sz1 % 8) + sz1++; + size_t sz2 = ON_UniqueTester::Block::BlockCapacity * sizeof(m_a[0]); + void* p = onmalloc(sz1 + sz2); + Block* blk = new (p) Block(); + blk->m_a = (ON__UINT_PTR*)((char*)(p)) + sz1; + return blk; +} + +int ON_UniqueTester::Block::Compare(ON__UINT_PTR* lhs, ON__UINT_PTR* rhs) +{ + if (*lhs < *rhs) + return -1; + if (*lhs > *rhs) + return 1; + return 0; +} + +bool ON_UniqueTester::Block::InBlock(size_t sorted_count,ON__UINT_PTR x) const +{ + if (nullptr != m_a && m_count > 0) + { + if ( sorted_count > 0 && x >= m_a[0] && x <= m_a[sorted_count - 1]) + { + if (nullptr != bsearch(&x, m_a, sorted_count, sizeof(m_a[0]), (int(*)(const void*, const void*))ON_UniqueTester::Block::Compare)) + return true; + } + if (sorted_count < m_count) + { + const ON__UINT_PTR* p = m_a + sorted_count; + const ON__UINT_PTR* p1 = m_a + m_count; + while (p < p1) + { + if (x == *p++) + return true; + } + } + } + return false; +} + +void ON_UniqueTester::Block::SortBlock() +{ + ON_qsort(m_a, m_count, sizeof(m_a[0]), (int(*)(const void*, const void*))ON_UniqueTester::Block::Compare); +} + +void ON_UniqueTester::Internal_CopyFrom( + const ON_UniqueTester & src +) +{ + m_block_list = nullptr; + m_sorted_count = 0; + + Block* first_blk = nullptr; + + for ( Block* src_blk = src.m_block_list; nullptr != src_blk; src_blk = src_blk->m_next) + { + Block* blk = Block::NewBlock(); + memcpy( blk->m_a, src_blk->m_a, src_blk->m_count * sizeof(blk->m_a[0]) ); + blk->m_count = src_blk->m_count; + if (nullptr == first_blk) + { + first_blk = blk; + } + else + { + blk->m_next = m_block_list; + m_block_list = blk; + } + } + + if (nullptr != first_blk) + { + if (src.m_sorted_count != first_blk->m_count) + first_blk->SortBlock(); + first_blk->m_next = m_block_list; + m_block_list = first_blk; + m_sorted_count = first_blk->m_count; + } +} + +void ON_UniqueTester::Internal_Destroy() +{ + Block* nxt = m_block_list; + m_block_list = nullptr; + m_sorted_count = 0; + for ( Block* blk = nxt; nullptr != blk; blk = nxt) + { + nxt = blk->m_next; + Block::DeleteBlock(blk); + } +} + +ON_UniqueTester::~ON_UniqueTester() +{ + Internal_Destroy(); +} + +ON_UniqueTester::ON_UniqueTester(const ON_UniqueTester& src) +{ + Internal_CopyFrom(src); +} + +ON_UniqueTester& ON_UniqueTester::operator=(const ON_UniqueTester& src) +{ + if (this != &src) + { + Internal_Destroy(); + Internal_CopyFrom(src); + } + return *this; +} + +bool ON_UniqueTester::InList(ON__UINT_PTR x) const +{ + size_t sorted_count = m_sorted_count; + for ( const Block* blk = m_block_list; nullptr != blk; blk = blk->m_next) + { + if (blk->InBlock(sorted_count, x)) + return true; + sorted_count = ON_UniqueTester::Block::BlockCapacity; + } + return false; +} + +bool ON_UniqueTester::AddToList(ON__UINT_PTR x) +{ + if (nullptr != m_block_list && m_sorted_count + 50 == m_block_list->m_count) + { + m_block_list->SortBlock(); + m_sorted_count = m_block_list->m_count; + } + if (InList(x)) + return false; + Internal_AddValue(x); + return true; +} + + +void ON_UniqueTester::ExpertAddNewToList(ON__UINT_PTR x) +{ + Internal_AddValue(x); +} + +void ON_UniqueTester::Internal_AddValue(ON__UINT_PTR x) +{ + if (nullptr == m_block_list || ON_UniqueTester::Block::BlockCapacity == m_block_list->m_count) + { + if (nullptr != m_block_list && m_sorted_count < ON_UniqueTester::Block::BlockCapacity) + m_block_list->SortBlock(); + ON_UniqueTester::Block* blk = ON_UniqueTester::Block::NewBlock(); + blk->m_next = m_block_list; + m_block_list = blk; + m_sorted_count = 0; + } + m_block_list->m_a[m_block_list->m_count++] = x; + if ( 1 == m_block_list->m_count + || (m_sorted_count+1 == m_block_list->m_count && x > m_block_list->m_a[m_sorted_count-1]) + ) + ++m_sorted_count; +} + +void ON_UniqueTester::ClearList() +{ + Internal_Destroy(); +} + +unsigned int ON_UniqueTester::Count() const +{ + size_t count = 0; + for ( const Block* blk = m_block_list; nullptr != blk; blk = blk->m_next) + { + count += blk->m_count; + } + return (unsigned int)count; +} + + +const ON_SimpleArray& ON_SubDEdgeChain::EdgeChain() const +{ + return m_edge_chain; +} + +const ON_SubD& ON_SubDEdgeChain::SubD() const +{ + return m_subd_ref.SubD(); +} + +const ON_SubDRef ON_SubDEdgeChain::SubDRef() const +{ + return m_subd_ref; +} + +bool ON_SubDEdgeChain::InChain( + const ON_SubDEdgePtr edge_ptr +) const +{ + return InChain(ON_SUBD_EDGE_POINTER(edge_ptr.m_ptr)); +} + +bool ON_SubDEdgeChain::InChain( + const ON_SubDEdge* edge +) const +{ + return (nullptr == edge) ? false : m_unique_tester.InList((ON__UINT_PTR)edge); +} + +bool ON_SubDEdgeChain::InChain( + const ON_SubDVertex* vertex +) const +{ + return (nullptr == vertex) ? false : m_unique_tester.InList((ON__UINT_PTR)vertex); +} + +bool ON_SubDEdgeChain::IsClosedLoop() const +{ + const unsigned int count = m_edge_chain.UnsignedCount(); + return (count >= 2 && m_edge_chain[0].RelativeVertex(0) == m_edge_chain[count - 1].RelativeVertex(1)); +} + + +unsigned int ON_SubDEdgeChain::BeginEdgeChain( + ON_SubDRef subd_ref, + const ON_SubDEdge* initial_edge +) +{ + return ON_SubDEdgeChain::BeginEdgeChain(subd_ref, ON_SubDEdgePtr::Create(initial_edge, 0)); +} + +unsigned int ON_SubDEdgeChain::BeginEdgeChain( + ON_SubDRef subd_ref, + const ON_SimpleArray& initial_edge_chain +) +{ + return BeginEdgeChain(subd_ref, initial_edge_chain.UnsignedCount(), initial_edge_chain.Array()); +} + +unsigned int ON_SubDEdgeChain::BeginEdgeChain( + ON_SubDRef subd_ref, + size_t edge_count, + const ON_SubDEdge*const* initial_edge_chain +) +{ + ClearEdgeChain(); + + if ( + edge_count <= 0 + || subd_ref.SubD().IsEmpty() + || subd_ref.SubD().EdgeCount() < (unsigned int)edge_count + ) + return 0; + + if ( 1 == edge_count) + return ON_SubDEdgeChain::BeginEdgeChain(subd_ref, ON_SubDEdgePtr::Create(initial_edge_chain[0], 0)); + + const ON_SubDEdge* e0 = initial_edge_chain[0]; + if (nullptr == e0 || nullptr == e0->m_vertex[0] || nullptr == e0->m_vertex[1] ) + return 0; + const ON_SubDEdge* e1 = initial_edge_chain[1]; + if (nullptr == e1 || nullptr == e1->m_vertex[0] || nullptr == e1->m_vertex[1] ) + return 0; + + + ON_SubDEdgePtr eptr = ON_SubDEdgePtr::Create(e0, (e0->m_vertex[1] == e1->m_vertex[0] || e0->m_vertex[1] == e1->m_vertex[1]) ? 0 : 1); + + ON_SimpleArray eptr_chain(edge_count); + eptr_chain.Append(eptr); + const ON_SubDVertex* v = eptr.RelativeVertex(1); + for (unsigned int i = 1; i < edge_count; i++) + { + e1 = initial_edge_chain[i]; + if (nullptr == e1 || nullptr == e1->m_vertex[0] || nullptr == e1->m_vertex[1] ) + return 0; + if (v != e1->m_vertex[0] && v != e1->m_vertex[1]) + return 0; + eptr = ON_SubDEdgePtr::Create(e1, (v == e1->m_vertex[0]) ? 0 : 1); + eptr_chain.Append(eptr); + } + + return ON_SubDEdgeChain::BeginEdgeChain(subd_ref,eptr_chain); +} + +unsigned int ON_SubDEdgeChain::BeginEdgeChain( + ON_SubDRef subd_ref, + ON_SubDEdgePtr eptr +) +{ + return ON_SubDEdgeChain::BeginEdgeChain(subd_ref, 1, &eptr); +} + +unsigned int ON_SubDEdgeChain::BeginEdgeChain( + ON_SubDRef subd_ref, + const ON_SimpleArray& initial_edge_chain +) +{ + return ON_SubDEdgeChain::BeginEdgeChain(subd_ref, initial_edge_chain.UnsignedCount(), initial_edge_chain.Array() ); +} + +unsigned int ON_SubDEdgeChain::BeginEdgeChain( + ON_SubDRef subd_ref, + size_t edge_count, + const ON_SubDEdgePtr* initial_edge_chain +) +{ + ClearEdgeChain(); + + m_subd_ref = subd_ref; + + if (edge_count <= 0 || m_subd_ref.SubD().IsEmpty()) + return 0; + + if ( ((size_t)subd_ref.SubD().EdgeCount()) < edge_count ) + return 0; + + m_edge_chain.Reserve(edge_count + 128); + const ON_SubDVertex* first_vertex = nullptr; + const ON_SubDVertex* last_vertex = nullptr; + for (size_t i = 0; i < edge_count; i++) + { + const ON_SubDEdgePtr eptr = initial_edge_chain[i]; + const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(eptr.m_ptr); + if (nullptr == e) + continue; + if (m_unique_tester.InList((ON__UINT_PTR)e)) + continue; + const ON__UINT_PTR edir = ON_SUBD_EDGE_DIRECTION(eptr.m_ptr); + const ON_SubDVertex* v[2] = { e->m_vertex[edir], e->m_vertex[1 - edir] }; + if (nullptr == v[0] || nullptr == v[1] || v[0] == v[1] ) + continue; + if (nullptr == first_vertex) + { + first_vertex = v[0]; + last_vertex = v[1]; + m_unique_tester.ExpertAddNewToList((ON__UINT_PTR)first_vertex); + m_unique_tester.ExpertAddNewToList((ON__UINT_PTR)last_vertex); + } + else + { + if (last_vertex != v[0]) + continue; + if (v[1] != first_vertex) + { + if (m_unique_tester.AddToList((ON__UINT_PTR)v[1])) + continue; + } + } + m_edge_chain.Append(eptr); + m_unique_tester.ExpertAddNewToList((ON__UINT_PTR)e); + last_vertex = v[1]; + if (last_vertex == first_vertex) + break; + }; + + return m_edge_chain.UnsignedCount(); +} + +void ON_SubDEdgeChain::ClearEdgeChain() +{ + m_edge_chain.SetCount(0); + m_unique_tester.ClearList(); +} + +unsigned int ON_SubDEdgeChain::EdgeCount() const +{ + return m_edge_chain.UnsignedCount(); +} + +void ON_SubDEdgeChain::SetStatusCheck( + bool bEnableStatusCheck, + ON_ComponentStatus status_check_pass, + ON_ComponentStatus status_check_fail +) +{ + m_bEnableStatusCheck = bEnableStatusCheck ? true : false; + m_status_check_pass = status_check_pass; + m_status_check_fail = status_check_fail; +} + +bool ON_SubDEdgeChain::StatusCheckEnabled() const +{ + return m_bEnableStatusCheck; +} + +void ON_SubDEdgeChain::Reverse() +{ + ON_SubDEdgeChain::ReverseEdgeChain(m_edge_chain); +} + +const ON_SubDEdgePtr ON_SubDEdgeChain::FirstEdgePtr() const +{ + return m_edge_chain.UnsignedCount() > 0 ? m_edge_chain[0] : ON_SubDEdgePtr::Null; +} +const ON_SubDEdgePtr ON_SubDEdgeChain::LastEdgePtr() const +{ + return m_edge_chain.UnsignedCount() > 0 ? *(m_edge_chain.Last()) : ON_SubDEdgePtr::Null; +} + +const ON_SubDEdgePtr ON_SubDEdgeChain::EdgePtr(int edge_index) const +{ + return (edge_index >= 0 && edge_index < m_edge_chain.Count()) ? m_edge_chain[edge_index] : ON_SubDEdgePtr::Null; +} + +const ON_SubDEdge* ON_SubDEdgeChain::FirstEdge() const +{ + return FirstEdgePtr().Edge(); +} + +const ON_SubDEdge* ON_SubDEdgeChain::LastEdge() const +{ + return LastEdgePtr().Edge(); +} + +const ON_SubDEdge* ON_SubDEdgeChain::Edge(int edge_index) const +{ + return EdgePtr(edge_index).Edge(); +} + +const ON_SubDVertex* ON_SubDEdgeChain::FirstVertex() const +{ + return Vertex(0); +} + +const ON_SubDVertex* ON_SubDEdgeChain::LastVertex() const +{ + return Vertex(m_edge_chain.Count()); +} + +const ON_SubDVertex* ON_SubDEdgeChain::Vertex(int vertex_index) const +{ + const int edge_count = m_edge_chain.Count(); + if ( vertex_index >= 0 && vertex_index <= edge_count && edge_count > 0 ) + { + return + (vertex_index == edge_count) + ? m_edge_chain[edge_count - 1].RelativeVertex(1) + : m_edge_chain[vertex_index].RelativeVertex(0); + } + return nullptr; +} + + +unsigned int ON_SubDEdgeChain::AddOneNeighbor( + ON_ChainDirection direction, + bool bStopAtTagChange +) +{ + const unsigned int count0 = m_edge_chain.UnsignedCount(); + if (count0 <= 0 || IsClosedLoop() ) + return 0; + + ON_SubDEdgePtr eptr; + const ON_SubDEdge* e; + const ON_SubDVertex* v; + + const ON_SubDVertex* chain_ends[2] = { FirstVertex() ,LastVertex() }; + + eptr + = (ON_ChainDirection::Previous != direction) + ? ON_SubDEdgeChain::EdgeChainNeighbor(LastEdgePtr(), ON_ChainDirection::Next, bStopAtTagChange, m_bEnableStatusCheck, m_status_check_pass, m_status_check_fail) + : ON_SubDEdgePtr::Null; + e = eptr.Edge(); + v = eptr.RelativeVertex(0); + if ( nullptr != v && v == chain_ends[1] && false == InChain(e) ) + { + v = eptr.RelativeVertex(1); + if (v == chain_ends[0] || m_unique_tester.AddToList((ON__UINT_PTR)v)) + { + m_unique_tester.ExpertAddNewToList((ON__UINT_PTR)e); + m_edge_chain.Append(eptr); + } + } + + eptr + = (ON_ChainDirection::Next != direction) + ? ON_SubDEdgeChain::EdgeChainNeighbor(FirstEdgePtr(), ON_ChainDirection::Previous, bStopAtTagChange, m_bEnableStatusCheck, m_status_check_pass, m_status_check_fail) + : ON_SubDEdgePtr::Null; + e = eptr.Edge(); + v = eptr.RelativeVertex(1); + if ( nullptr != v && v == chain_ends[0] && false == InChain(e) ) + { + v = eptr.RelativeVertex(0); + if (v == chain_ends[1] || m_unique_tester.AddToList((ON__UINT_PTR)v)) + { + m_unique_tester.ExpertAddNewToList((ON__UINT_PTR)e); + m_edge_chain.Insert(0, eptr); + } + } + + return m_edge_chain.UnsignedCount() - count0; +} + +unsigned int ON_SubDEdgeChain::AddAllNeighbors( + ON_ChainDirection direction, + bool bStopAtTagChange +) +{ + const unsigned int count0 = m_edge_chain.UnsignedCount(); + if (count0 <= 0 || IsClosedLoop()) + return 0; + + if (ON_ChainDirection::Previous != direction) + while (1 == AddOneNeighbor(ON_ChainDirection::Next, bStopAtTagChange)) {} + if (ON_ChainDirection::Next != direction) + while (1 == AddOneNeighbor(ON_ChainDirection::Previous, bStopAtTagChange)) {} + + return m_edge_chain.UnsignedCount() - count0; +} + +unsigned int ON_SubDEdgeChain::AddEdge( + const ON_SubDEdge* edge +) +{ + const unsigned int count0 = m_edge_chain.UnsignedCount(); + if (count0 <= 0) + return 0; + + if ( + nullptr == edge + || nullptr == edge->m_vertex[0] + || nullptr == edge->m_vertex[1] + || edge->m_vertex[0] == edge->m_vertex[1] + ) + return 0; + + const ON_SubDVertex* v[2] = { FirstVertex(),LastVertex() }; + if (v[0] == v[1]) + return 0; + + if ( m_bEnableStatusCheck && false == ON_ComponentStatus::StatusCheck(edge->m_status, m_status_check_pass, m_status_check_fail)) + return 0; + + ON_SubDEdgePtr eptr = ON_SubDEdgePtr::Null; + + if (v[1] == edge->m_vertex[0]) + eptr = ON_SubDEdgePtr::Create(edge, 0); + else if (v[1] == edge->m_vertex[1]) + eptr = ON_SubDEdgePtr::Create(edge, 1); + else if (v[0] == edge->m_vertex[1]) + eptr = ON_SubDEdgePtr::Create(edge, 0); + else if (v[0] == edge->m_vertex[0]) + eptr = ON_SubDEdgePtr::Create(edge, 1); + else + return 0; + + if (m_unique_tester.InList((ON__UINT_PTR)edge)) + return 0; + + if (v[1] == eptr.RelativeVertex(0) ) + { + if (v[0] == eptr.RelativeVertex(1) || m_unique_tester.AddToList((ON__UINT_PTR)eptr.RelativeVertex(1))) + { + m_edge_chain.Append(eptr); + m_unique_tester.ExpertAddNewToList((ON__UINT_PTR)edge); + } + } + else if (v[0] == eptr.RelativeVertex(1) ) + { + if (v[1] == eptr.RelativeVertex(0) || m_unique_tester.AddToList((ON__UINT_PTR)eptr.RelativeVertex(0))) + { + m_edge_chain.Insert(0, eptr); + m_unique_tester.ExpertAddNewToList((ON__UINT_PTR)edge); + } + } + + return m_edge_chain.UnsignedCount() - count0; +} + +unsigned int ON_SubDEdgeChain::RemoveEdges( + const ON_SubDEdge* first_edge, + const ON_SubDEdge* last_edge +) +{ + unsigned int count0 = m_edge_chain.UnsignedCount(); + unsigned int i0 = 0; + unsigned int i1 = count0; + if (nullptr != first_edge) + { + while (i0 < count0 && first_edge != m_edge_chain[i0].Edge()) + ++i0; + } + if (nullptr != last_edge) + { + while (i1 > i0 && last_edge != m_edge_chain[i1 - 1].Edge()) + --i1; + } + + const unsigned int count1 = i1 - i0; + if (count1 >= count0) + return 0; + + if (i0 > 0) + { + for (unsigned int i = i0; i < i1; i++) + m_edge_chain[i - i0] = m_edge_chain[i]; + } + + m_edge_chain.SetCount(count1); + m_unique_tester.ClearList(); + for (unsigned int i = 0; i < count1; i++) + { + m_unique_tester.AddToList((ON__UINT_PTR)m_edge_chain[i].Edge()); + m_unique_tester.AddToList((ON__UINT_PTR)m_edge_chain[i].RelativeVertex(0)); + } + if ( FirstVertex() != LastVertex() ) + m_unique_tester.AddToList((ON__UINT_PTR)LastVertex()); + + return count0 - count1; +} + + +void ON_SubDEdgeChain::ReverseEdgeChain( + ON_SimpleArray< ON_SubDEdgePtr >& edge_chain +) +{ + ON_SubDEdgeChain::ReverseEdgeChain(edge_chain.UnsignedCount(), edge_chain.Array()); +} + +void ON_SubDEdgeChain::ReverseEdgeChain( + size_t edge_count, + ON_SubDEdgePtr* edge_chain +) +{ + if (edge_count <= 0 || nullptr == edge_chain) + return; + ON_SubDEdgePtr* p0 = edge_chain; + ON_SubDEdgePtr* p1 = p0 + (edge_count - 1); + while ( p0 < p1) + { + ON_SubDEdgePtr eptr = p0->Reversed(); + *p0 = p1->Reversed(); + *p1 = eptr; + ++p0; + --p1; + } + if (p0 == p1) + *p0 = p0->Reversed(); +} + +bool ON_SubDEdgeChain::IsValidEdgeChain( + const ON_SimpleArray< ON_SubDEdgePtr >& edge_chain, + bool bCheckForDuplicateEdges +) +{ + return ON_SubDEdgeChain::IsValidEdgeChain(edge_chain.UnsignedCount(), edge_chain.Array(), bCheckForDuplicateEdges); +} + +bool ON_SubDEdgeChain::IsValidEdgeChain( + size_t edge_count, + const ON_SubDEdgePtr* edge_chain, + bool bCheckForDuplicateEdges +) +{ + if (edge_count <= 0) + return true; + if (nullptr == edge_chain) + return false; + + const ON_SubDVertex* first_vertex = edge_chain->RelativeVertex(0); + if (nullptr == first_vertex) + return false; + + const ON_SubDVertex* v = first_vertex; + const ON_SubDEdgePtr* p0 = edge_chain; + const ON_SubDEdgePtr* p1 = edge_chain+1; + + for (const ON_SubDEdgePtr* p = p0; p < p1; ++p) + { + const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(p->m_ptr); + if (nullptr == e || nullptr == e->m_vertex[0] || nullptr == e->m_vertex[1]) + return false; + ON__UINT_PTR edir = ON_SUBD_EDGE_DIRECTION(p->m_ptr); + const ON_SubDVertex* v0 = e->m_vertex[edir]; + const ON_SubDVertex* v1 = e->m_vertex[1 - edir]; + if (v0 != v || nullptr == v1 || v0 == v1) + return false; + v = v1; + } + + if (bCheckForDuplicateEdges) + { + const ON_SubDVertex* last_vertex = v; + ON_UniqueTester tester; + for (const ON_SubDEdgePtr* p = p0; p < p1; ++p) + { + const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(p->m_ptr); + if (false == tester.AddToList((ON__UINT_PTR)e)) + return false; // duplicate edge + if (false == tester.AddToList((ON__UINT_PTR)e->m_vertex[ON_SUBD_EDGE_DIRECTION(p->m_ptr)])) + return false; // duplicate vertex + } + if (first_vertex != last_vertex) + { + if (false == tester.AddToList((ON__UINT_PTR)last_vertex)) + return false; // duplicate vertex + } + } + + return true; +} + +class ON_SubDLimitMeshImpl* ON_SubDLimitMesh::SubLimple() const +{ + return m_impl_sp.get(); +} + +unsigned int ON_SubDLimitMesh::SubLimpleUseCount() const +{ + return (unsigned int)(m_impl_sp.use_count()); +} #if defined(ON_SUBD_CENSUS) @@ -10813,16 +12683,6 @@ void ON_CensusCounter::Clear() ON_PointerHashTable::DestroyTheOne(); } -class ON_SubDLimitMeshImpl* ON_SubDLimitMesh::SubLimple() const -{ - return m_impl_sp.get(); -} - -unsigned int ON_SubDLimitMesh::SubLimpleUseCount() const -{ - return m_impl_sp.use_count(); -} - void ON_CensusCounter::CensusReport( class ON_TextLog& text_log ) diff --git a/opennurbs_subd.h b/opennurbs_subd.h index 740ded3a..a7f317e1 100644 --- a/opennurbs_subd.h +++ b/opennurbs_subd.h @@ -161,6 +161,11 @@ public: ); }; + +#if defined(ON_DLL_TEMPLATE) +ON_DLL_TEMPLATE template class ON_CLASS ON_SimpleArray; +#endif + class ON_CLASS ON_SubDEdgePtr { public: @@ -175,8 +180,27 @@ public: class ON_SubDEdge* Edge() const; + /* + Returns: + 0: the edge is oriented from Edge()->Vertex(0) to Edge()->Vertex(1). + 1: the edge is oriented from Edge()->Vertex(1) to Edge()->Vertex(0). + */ ON__UINT_PTR EdgeDirection() const; + /* + Parameters: + relative_vertex_index - [in] + 0: return Edge()->Vertex(EdgeDirection()) + 1: return Edge()->Vertex(1-EdgeDirection()) + Returns: + The vertex with EdgeDirection() taken into account. + nullptr if relative_vertex_index, Edge() is nullptr, or Edge()->Vertex() is nullptr. + */ + const class ON_SubDVertex* RelativeVertex( + int relative_vertex_index + ) const; + + ON_ComponentStatus Status() const; /* @@ -195,6 +219,10 @@ public: ); }; +#if defined(ON_DLL_TEMPLATE) +ON_DLL_TEMPLATE template class ON_CLASS ON_SimpleArray; +#endif + class ON_CLASS ON_SubDFacePtr { public: @@ -224,6 +252,11 @@ public: ); }; +#if defined(ON_DLL_TEMPLATE) +ON_DLL_TEMPLATE template class ON_CLASS ON_SimpleArray; +#endif + + class ON_CLASS ON_SubDComponentPtr { public: @@ -404,6 +437,11 @@ public: }; +#if defined(ON_DLL_TEMPLATE) +ON_DLL_TEMPLATE template class ON_CLASS ON_SimpleArray; +#endif + + #if defined(OPENNURBS_SUBD_WIP) #if 1 @@ -898,6 +936,18 @@ public: maximum_subd_level = 128 // uses as a sanity check on input parameters }; + /* + Returns: + A runtime serial number identifying this subd. + Remarks: + ON_SubD is a shared pointer to an implementation. As such, there can + be multiple ON_SubD instances that reference the same implementation. + The runtime serial number uniquely identifies a particular instance + of an implementation. + The empty subd has runtime serial number = 0. + */ + ON__UINT64 RuntimeSerialNumber() const; + #pragma region RH_C_SHARED_ENUM [ON_SubD::VertexTag] [Rhino.Geometry.SubD.SubDVertexTag] [nested:byte] /// /// SubD::VertexTag identifies the type of subdivision vertex. Different tags use @@ -915,19 +965,21 @@ public: /// /// Must be an interior vertex. - /// All edges ending at a smooth vertex must be tagged as ON_SubD::EdgeTag::Smooth. + /// All edges ending at a smooth vertex must be tagged as ON_SubD::EdgeTag::Smooth + /// and have 2 faces. /// Smooth = 1, /// - /// Can be an iterior or a boundary vertex. - /// Exactly two edges ending at a crease vertex must be tagged as ON_SubD::EdgeTag::Crease. - /// All other edges ending at a crease must be tagged as tagON_SubD::EdgeTag::smooth. + /// Can be an interior or a boundary vertex. + /// Exactly two edges ending at a crease vertex must be tagged as ON_SubD::EdgeTag::Crease and may + /// have 1 or 2 faces. + /// All other edges ending at a crease must be tagged as tagON_SubD::EdgeTag::Smooth and have 2 faces. /// Crease = 2, /// - /// Can be an iterior, boundary or isolated vertex. + /// Can be an interior, boundary, nonmanifold, or isolated vertex. /// The location of a corner vertex is fixed. /// The all subdivision points and the limit point are at the initial vertex location. /// The edges ending at a corner vertex can be smooth or crease edges. @@ -936,8 +988,9 @@ public: /// /// Must be an interior vertex. - /// Exactly one edge ending at a dart vertex must be tagged as ON_SubD::EdgeTag::Smooth. - /// All other edges ending at a dart vertex must be tagged as tagON_SubD::EdgeTag::smooth. + /// Every edge ending at a dart vertex must have 2 faces. + /// Exactly one edge ending at a dart vertex must be tagged as ON_SubD::EdgeTag::Crease + /// and every otherr edge must be tagged as tagON_SubD::EdgeTag::smooth. /// Dart = 4 }; @@ -1133,7 +1186,7 @@ public: /// Custom = 7 - // All values must be <= 31; i.e., (((unsigned char)0xE0U) & subd_type)) must be zero. + // All values must be <= 15; i.e., (((unsigned char)0xF0U) & subd_type)) must be zero. }; #pragma endregion @@ -1592,8 +1645,6 @@ public: // virtual ON_Geometry GetBBox override bool GetBBox( double* boxmin, double* boxmax, bool bGrowBox = false ) const override; - // virtual ON_Geometry GetTightBoundingBox override - bool GetTightBoundingBox( class ON_BoundingBox& tight_bbox, bool bGrowBox = false, const class ON_Xform* xform = nullptr ) const override; /* Description: @@ -1624,8 +1675,7 @@ public: int j ) override; - - + //virtual bool HasBrepForm() const override; @@ -1724,6 +1774,7 @@ public: ); bool IsEmpty() const; + bool IsNotEmpty() const; /* @@ -1920,7 +1971,6 @@ public: size_t cptr_count ); - ///////////////////////////////////////////////////////// // // Editing tools @@ -2018,6 +2068,19 @@ public: class ON_SubDEdge* edge ); + + /* + Description: + When finished editing a SubD, call this function to delete all cached evaluation + values and update all vertex tag, edge tags, edge sector coefficients. + Remarks: + This function is the most reliable (and heavy handed) way to update SubD component + information. Expert users can choose to be more selective when certain about + exactly what needs to be modified. + */ + void SubDModifiedNofification(); + + /* Description: Updates vertex tag, edge tag, and edge coefficient values @@ -2058,7 +2121,7 @@ public: Returns: Number of vertices that changed during the update. Remarks: - It is easiest to call UpdateTagsAndSectorCoefficients(). + It is easiest to call UpdateAllTagsAndSectorCoefficients(). */ unsigned int UpdateVertexTags( bool bUnsetVertexTagsOnly @@ -2082,7 +2145,7 @@ public: Number of edges that had a tag value changed or sector coefficient set to ON_SubDSectorType::UnsetSectorWeight. Remarks: - It is easiest to call UpdateTagsAndSectorCoefficients(). + It is easiest to call UpdateAllTagsAndSectorCoefficients(). */ unsigned int UpdateEdgeTags( bool bUnsetEdgeTagsOnly @@ -2106,7 +2169,7 @@ public: Number of edges that had a tag value changed or sector coefficient set to ON_SubDSectorType::UnsetSectorWeight. Remarks: - It is easiest to call UpdateTagsAndSectorCoefficients(). + It is easiest to call UpdateAllTagsAndSectorCoefficients(). */ unsigned int UpdateEdgeSectorCoefficients( bool bUnsetSectorCoefficientsOnly @@ -2176,6 +2239,68 @@ public: ON_SubD::EdgeTag moved_edge_tag ); + /* + Parameters: + ci_list - [in] + Array of ci_count ON_COMPONENT_INDEX values that identify the vertices. + Elements that do not identifiy a SubD vertex are ignored. + ci_count - [in] + Number of elements in the ci_list[] array. + vertex_tag - [in] + Desired tag. If a vertex has the desired tag or cannot accept the desired tag, + then that vertex is skipped. + If vertex_tag is ON_SubD::VertexTag::Corner, then every edge touching + that vertex is converted to a crease. + Returns: + number of vertex tags that were changed. + */ + unsigned int SetVertexTags( + const ON_COMPONENT_INDEX* ci_list, + size_t ci_count, + ON_SubD::VertexTag vertex_tag + ); + + /* + Parameters: + ci_list - [in] + Array of ci_count ON_COMPONENT_INDEX values that identify the vertices. + Elements that do not identifiy a SubD vertex are ignored. + ci_count - [in] + Number of elements in the ci_list[] array. + vertex_tag - [in] + Desired tag. If a vertex has the desired tag or cannot accept the desired tag, + then that vertex is skipped. + If vertex_tag is ON_SubD::VertexTag::Corner, then every edge touching + that vertex is converted to a crease. + Returns: + number of vertex tags that were changed. + */ + unsigned int SetVertexTags( + const ON_SubDComponentPtr* cptr_list, + size_t cptr_count, + ON_SubD::VertexTag vertex_tag + ); + + /* + Returns: + number of tags that were changed. + */ + unsigned int SetEdgeTags( + const ON_COMPONENT_INDEX* ci_list, + size_t ci_count, + ON_SubD::EdgeTag edge_tag + ); + + /* + Returns: + number of tags that were changed. + */ + unsigned int SetEdgeTags( + const ON_SubDComponentPtr* cptr_list, + size_t cptr_count, + ON_SubD::EdgeTag edge_tag + ); + /////* ////Description: //// Apply the built-in triangle subdivision subdivision algorithm globally. @@ -2248,6 +2373,18 @@ public: ON_SubD::SubDType subd_type ); + /* + Description: + Adds a vertex with tag = ON_SubD::VertexTag::Unset. + */ + class ON_SubDVertex* AddVertex( + const double* P + ); + + /* + Description: + Adds a vertex with specified tag. + */ class ON_SubDVertex* AddVertex( ON_SubD::VertexTag vertex_tag, const double* P @@ -2289,6 +2426,25 @@ public: const ON_SubDVertex* v1 ); + /* + Description: + Add an edge with tag = ON_SubD::EdgeTag::Unset to the subd. + Parameters: + v0 - [in] + v1 - [in] + The edge begins at v0 and ends at v1. + The edge will be on the same level as the vertices. + Returns: + Pointer to the allocated edge. + Remarks: + ON_SubD::EdgeTagFromContext() can be used to determine edge + tag values in simple situations. + */ + class ON_SubDEdge* AddEdge( + class ON_SubDVertex* v0, + class ON_SubDVertex* v1 + ); + /* Description: Add an edge to the subd. @@ -2346,6 +2502,32 @@ public: const class ON_SubDEdgePtr* edge ); + class ON_SubDFace* AddTriangleFace( + class ON_SubDEdge* edge0, bool bReverseEdge0, + class ON_SubDEdge* edge1, bool bReverseEdge1, + class ON_SubDEdge* edge2, bool bReverseEdge2 + ); + + class ON_SubDFace* AddTriangleFace( + ON_SubDEdgePtr edge0, + ON_SubDEdgePtr edge1, + ON_SubDEdgePtr edge2 + ); + + class ON_SubDFace* AddQuadFace( + class ON_SubDEdge* edge0, bool bReverseEdge0, + class ON_SubDEdge* edge1, bool bReverseEdge1, + class ON_SubDEdge* edge2, bool bReverseEdge2, + class ON_SubDEdge* edge3, bool bReverseEdge3 + ); + + class ON_SubDFace* AddQuadFace( + ON_SubDEdgePtr edge0, + ON_SubDEdgePtr edge1, + ON_SubDEdgePtr edge2, + ON_SubDEdgePtr edge3 + ); + /* Description: Expert user tool to insert an edge in the face's edge array. @@ -2466,42 +2648,14 @@ public: size_t capacity ); - /* - Description: - Get the limit surface mesh for this subD. - Parameters: - minimum_display_density - [in] - Returns: - A mesh of the subdivision limit surface. - Remarks: - The mesh is a reference counted mesh managed by this ON_SubD. - */ - class ON_SubDLimitMesh LimitSurfaceMesh() const; + + - ON_SubDLimitMesh UpdateLimitSurfaceMesh( - unsigned int minimum_display_density - ) const; void ClearLimitSurfaceMesh() const; void ClearEvaluationCache() const; - /* - Description: - Get an ON_Mesh of the subdivision limit surface - Parameters: - display_parameters - [in] - mesh - [in] - If not null, the returned mesh will be stored on - the input class. - Returns: - A mesh of the subdivision limit surface. - */ - class ON_Mesh* GetLimitSurfaceMesh( - const class ON_SubDDisplayParameters& display_parameters, - class ON_Mesh* mesh - ) const; - /* Description: @@ -2519,87 +2673,7 @@ public: ) const; - /* - Description: - Get the limit surface mesh as a set of fragments. - Parameters: - display_parameters - [in] - - fragment_callback_context - [in] - first parameter for the FragmentCallback function - - fragment_callback_function - [in] - A function pointer with prototype: - bool fragment_callback_function( - void *fragment_callback_context, - const class ON_SubDLimitMeshFragment* fragment - ); - - For each fragment that is produced, fragment_callback_function() is called. - You must copy the retuned fragment if you want to keep it for future use. - If fragment_callback_function returns false, the calculation is canceled. - Returns: - Number of fragments produced. - */ - unsigned int GetLimitSurfaceMeshInFragments( - const class ON_SubDDisplayParameters& display_parameters, - ON__UINT_PTR fragment_callback_context, - bool(*fragment_callback_function)(ON__UINT_PTR , const class ON_SubDLimitMeshFragment*) - ) const; - - /* - Returns: - The number of limit surface mesh fragments (ON_SubDLimitMeshFragment) that - GetLimitSurfaceMeshFragments() will produce. - */ - unsigned int LimitSurfaceMeshFragmentCount() const; - - - /* - Description: - Get the limit surface as a set of bicubic patch fragments. - Parameters: - display_parameters - [in] - - callback_context - [in] - first parameter for the callback functions - - begin_face_callback_function - [in] - nullptr or a function pointer with prototype: - - bool begin_face_callback_function( - void *fragment_callback_context, - const class ON_SubDFaceRegion& face_region, - ); - - At the beginning of each quad face, this function is called. - If the original SubD face is a quad, then face_region identifies that quad. - If the original SubD face is not a quad, then face_region identifies the - level 1 subdivision quad. The face region information is useful in building - a correspondence between the original SubD and the Nurbs patches. - If begin_face_callback_function returns false, the calculation is canceled. - - fragment_callback_function - [in] - A function pointer with prototype: - - bool fragment_callback_function( - void *fragment_callback_context, - const class ON_SubDLimitNurbsFragment* fragment - ); - - For each fragment that is produced, fragment_callback_function() is called. - You must copy the retuned fragment if you want to keep it for future use. - If fragment_callback_function returns false, the calculation is canceled. - Returns: - Number of fragments produced. - */ - unsigned int GetLimitSurfaceNurbsFragments( - const class ON_SubDDisplayParameters& display_parameters, - ON__UINT_PTR callback_context, - bool(*begin_face_callback_function)(ON__UINT_PTR ,const class ON_SubDFaceRegion&), - bool(*fragment_callback_function)(ON__UINT_PTR ,const class ON_SubDLimitNurbsFragment*) - ) const; #pragma region RH_C_SHARED_ENUM [ON_SubD::NurbsSurfaceType] [Rhino.Geometry.SubD.NurbsSurfaceType] [nested:byte] /// @@ -2641,88 +2715,9 @@ public: }; #pragma endregion - /* - Description: - Get the SubD limit surface as bicubic NURBS surfaces. - - Parameters: - display_parameters - [in] - Determines the density of NURBS patches near extraordinary vertices. - - nurbs_surface_type - [in] - Controls the size and knot properties of the returned NURBS sufaces. - - callback_context - [in] - first parameter for the callback functions - - nurbs_callback_function - [in] - nullptr or a function pointer with prototype: - - bool nurbs_callback_function( - ON__UINT_PTR callback_context, - const ON_SubDComponentRegion& subd_face_region, - const ON_SubDComponentRegion subd_edge_region[4], - const class ON_NurbsSurface*& nurbs_surface - ); - - For each NURBS surface that is produced, nurbs_callback_function() is called. - - subd_face_region identifies the region of the SubD that this NURBS surface - models. - - subd_edge_region[4] identifies the edges the NURBS surface abuts in - the order South,East,North,West. - - When an edge is a subdivision edge, then - subd_edge_region[i].m_level0_component.ComponentType() is ON_SubDComponentPtr::Type::Edge, - subd_edge_region[i].m_level0_component.ComponentBase() is nullptr, and - ON_SubDComponentRegion::IsTransientId(subd_edge_region[i].m_level0_component_id) is true - - The nurbs_surface pointer points to an ON_NurbsSurface on the heap. - You must take responsibility for managing this surface and deleting it - at the appropriate time. - - Returns: - Number of NURBS surfaces returned. - - */ - unsigned int GetLimitSurfaceNurbs( - const class ON_SubDDisplayParameters& display_parameters, - ON_SubD::NurbsSurfaceType nurbs_surface_type, - ON__UINT_PTR callback_context, - bool(*nurbs_callback_function)(ON__UINT_PTR , const class ON_SubDFaceRegion&, class ON_NurbsSurface*) - ) const; - - /* - Description: - Get the SubD limit surface as a list of bicubic NURBS patches. - Parameters: - display_parameters - [in] - Determines the density of NURBS patches near extraordinary vertices. - nurbs_surface_type - [in] - Controls the size and knot properties of the returned NURBS sufaces. - patches - [out] - The bicubic NURBS patches are appended to this array. - Returns: - Number of patches appended to patches[] - */ - unsigned int GetLimitSurfaceNurbs( - const class ON_SubDDisplayParameters& display_parameters, - ON_SubD::NurbsSurfaceType nurbs_surface_type, - ON_SimpleArray< ON_NurbsSurface* >& patches - ) const; - unsigned int GetLimitSurfaceNurbs( - const class ON_SubDDisplayParameters& display_parameters, - ON_SubD::NurbsSurfaceType nurbs_surface_type, - ON_SimpleArray< ON_SubDFaceRegionAndNurbs >& patches - ) const; - ON_Brep* GetLimitSurfaceNurbs( - const class ON_SubDDisplayParameters& display_parameters, - ON_Brep* destination_brep - ) const; public: /* @@ -2897,6 +2892,52 @@ private: }; +////////////////////////////////////////////////////////////////////////// +// +// ON_SubDComponentMarksClearAndRestore +// +class ON_SubDComponentMarksClearAndRestore +{ +public: + // Constructor saves current component marks on subd. + ON_SubDComponentMarksClearAndRestore( + ON_SubD& subd + ); + + // Destructor restores saved marks. + ~ON_SubDComponentMarksClearAndRestore(); + + /* + Description: + Restore marks saved by the constructor. + Parameters: + bDisableFutureRestores - [in] + If true, no additional restores, including by the destructor, will occur. + */ + bool Restore( + bool bDisableFutureRestores + ); + + // Call DisableRestore() to prevent the destructor from restoring saved marks. + void DisableRestore(); + + const ON_SimpleArray< const class ON_SubDComponentBase* >& SavedMarkedComponentList() const; + +private: + ON_SubD m_subd; + ON_SimpleArray< const class ON_SubDComponentBase* > m_saved_marked_component_list; + bool m_bRestore = true; + bool m_bReserved1 = false; + bool m_bReserved2 = false; + bool m_bReserved3 = false; + unsigned int m_reserved4 = 0; + +private: + ON_SubDComponentMarksClearAndRestore(const ON_SubDComponentMarksClearAndRestore&) = delete; + ON_SubDComponentMarksClearAndRestore& operator=(const ON_SubDComponentMarksClearAndRestore&) = delete; +}; + + ////////////////////////////////////////////////////////////////////////// // // ON_SubDSectorType @@ -4063,6 +4104,87 @@ public: unsigned int side_segment_count ); + /* + Description: + Copies location and optionally normal information from a side of src_fragment + to a side of dst_fragment. This is often used for "sealing" adjacent fragments + where the same edge is evaluated from different faces. All evaluations are as accurate + as IEEE double precision arithmetic can produce. However there are often tiny differences + (last few bits of the coordinate values differ) in vertext locations because we are + using finite precision arithmetic with operations in different orders. + In general, rendering, mesho topology, and edge analysis calculations perform poorly + when tinity differences exist. + + Parameters: + bTestNearEqual - [in] + If true, then the copy is performed only when the points or normals are nearly + identical. In this case if the discrepance between coordinates is too large, + then this function returns false. + bCopyNormals - [in] + If true, then normal information is copied along with point information. + src_fragment - [in] + source fragment + i0, i1 - [in] + indices for the src_fragment.m_S[] array. + dst_fragment - [in] + destination fragment + j0, j1 - [in] + indices for dst_fragment.m_S[] array. + abs(j0-j1) = abs(i0-i1); + Remarks: + It is required that max(i0,i1) - min(i0,i1) = max(j0,j1) - min(j0,j1) and + all indices are valid for the respective fragments. + The src_fragment point/normal identified by src_fragment.m_S[i] + is copied to he dst_fragment point/normal identified by dst_fragment.m_S[j], + where i0->j0, i1->j1. + */ + static bool SealAdjacentSides( + bool bTestNearEqual, + bool bCopyNormals, + const ON_SubDLimitMeshFragment& src_fragment, + unsigned int i0, + unsigned int i1, + ON_SubDLimitMeshFragment& dst_fragment, + unsigned int j0, + unsigned int j1 + ); + + /* + Parameters: + bTestNearEqual - [in] + If true, then the copy is performed only when the points or normals are nearly + identical. In this case if the discrepance between coordinates is too large, + then this function returns false. + src - [in] + dst - [in/out] + The 3d point (src[0],src[1],src2[2]) is copied to (dst[0],dst[1],dst[2]). + Returns: + True if a copy occured. + */ + static bool SealPoints( + bool bTestNearEqual, + const double* src, + double* dst + ); + + /* + Parameters: + bTestNearEqual - [in] + If true, then the copy is performed only when the points or normals are nearly + identical. In this case if the discrepance between coordinates is too large, + then this function returns false. + src - [in] + dst - [in/out] + The 3d point (src[0],src[1],src2[2]) is copied to (dst[0],dst[1],dst[2]). + Returns: + True if a copy occured. + */ + static bool SealNormals( + bool bTestNearEqual, + const double* src, + double* dst + ); + public: const class ON_SubDFace* m_face; @@ -4074,9 +4196,13 @@ public: // vertex, then the corresponding m_face_vertex_index[] value // will be <= ON_SubDFace::MaximumEdgeCount and m_face->Vertex(m_face_vertex_index[]) // is the vertex. Otherwise, the corresponding m_face_vertex_index[] value - // will be > ON_SubDFace::MaximumEdgeCount. For partial fragments, - // only some m_face_vertex_index[] identify vertices and the grid extends - // halfway along the neighboring face edges are + // will be > ON_SubDFace::MaximumEdgeCount. + // Catmull-Clark limit meshes: + // When the original SubD face is a quad, a full fragment is created and + // m_face_vertex_index[4] = {0,1,2,3}. + // When the original SuD face is an N-gon with N != 4, a partial fragment + // is delivered and m_face_vertex_index[2] identifies the face vertex + // for that fragment. m_face_vertex_index[0,1,3] = a value > ON_SubDFace::MaximumEdgeCount unsigned short m_face_vertex_index[4]; /* @@ -4094,17 +4220,164 @@ public: unsigned int grid_side_index ) const; + /* + Parameters: + grid_corner_index - [in] + grid side N is between corner index N and corner index (N+1)%4. + Remarks: + For partial fragments (IsPartialFragment() = true), grid_corner_index = 2 is the only + corner that corresponds to a SubD vertex. + For partial fragments (IsPartialFragment() = true), grid_side_index = 1 and grid_side_index = 2 + correspond to half of original SuD edges. + */ const class ON_SubDVertexPtr VertexPtr( unsigned int grid_corner_index ) const; + + /* + Parameters: + grid_corner_index - [in] + grid side N is between corner index N and corner index (N+1)%4. + Remarks: + For partial fragments (IsPartialFragment() = true), grid_corner_index = 2 is the only + corner that corresponds to a SubD vertex. + For partial fragments (IsPartialFragment() = true), grid_side_index = 1 and grid_side_index = 2 + correspond to half of original SuD edges. + */ const class ON_SubDVertex* Vertex( unsigned int grid_corner_index ) const; - ON_3dPoint CornerPoint( + + /* + Parameters: + grid_corner_index - [in] + grid side N is between corner index N and corner index (N+1)%4. + Returns: + Limit surface location at the grid corner or ON_3dPoint::NanPoint if the fragment is empty. + Remarks: + For partial fragments (IsPartialFragment() = true), grid_corner_index = 2 is the only + corner that corresponds to a SubD vertex. + For partial fragments (IsPartialFragment() = true), grid_side_index = 1 and grid_side_index = 2 + correspond to half of original SuD edges. + */ + const ON_3dPoint CornerPoint( unsigned int grid_corner_index ) const; + /* + Parameters: + grid_corner_index - [in] + grid side N is between corner index N and corner index (N+1)%4. + Returns: + Limit surface normal at the grid corner or ON_3dPoint::NanPoint if the fragment is empty. + Remarks: + For partial fragments (IsPartialFragment() = true), grid_corner_index = 2 is the only + corner that corresponds to a SubD vertex. + For partial fragments (IsPartialFragment() = true), grid_side_index = 1 and grid_side_index = 2 + correspond to half of original SuD edges. + */ + const ON_3dVector CornerNormal( + unsigned int grid_corner_index + ) const; + + /* + Parameters: + grid_corner_index - [in] + grid side N is between corner index N and corner index (N+1)%4. + Returns: + Limit surface frame at the grid corner or ON_3dPoint::NanPoint if the fragment is empty. + Remarks: + For partial fragments (IsPartialFragment() = true), grid_corner_index = 2 is the only + corner that corresponds to a SubD vertex. + For partial fragments (IsPartialFragment() = true), grid_side_index = 1 and grid_side_index = 2 + correspond to half of original SuD edges. + */ + bool GetCornerFrame( + unsigned int grid_corner_index, + ON_Plane& frame + ) const; + + /* + Parameters: + grid_side_index - [in] + grid side N is between corner index N and corner index (N+1)%4. + Returns: + Limit surface location at the midde of the grid side or ON_3dPoint::NanPoint if the fragment is empty. + Remarks: + For partial fragments (IsPartialFragment() = true), grid_corner_index = 2 is the only + corner that corresponds to a SubD vertex. + For partial fragments (IsPartialFragment() = true), grid_side_index = 1 and grid_side_index = 2 + correspond to half of original SuD edges. + */ + const ON_3dPoint SidePoint( + unsigned int grid_side_index + ) const; + + /* + Parameters: + grid_side_index - [in] + grid side N is between corner index N and corner index (N+1)%4. + Returns: + Limit surface normal at the grid corner or ON_3dPoint::NanPoint if the fragment is empty. + Remarks: + For partial fragments (IsPartialFragment() = true), grid_corner_index = 2 is the only + corner that corresponds to a SubD vertex. + For partial fragments (IsPartialFragment() = true), grid_side_index = 1 and grid_side_index = 2 + correspond to half of original SuD edges. + */ + const ON_3dVector SideNormal( + unsigned int grid_side_index + ) const; + + /* + Parameters: + grid_side_index - [in] + grid side N is between corner index N and corner index (N+1)%4. + Returns: + Limit surface frame at the grid corner or ON_3dPoint::NanPoint if the fragment is empty. + Remarks: + For partial fragments (IsPartialFragment() = true), grid_corner_index = 2 is the only + corner that corresponds to a SubD vertex. + For partial fragments (IsPartialFragment() = true), grid_side_index = 1 and grid_side_index = 2 + correspond to half of original SuD edges. + */ + bool GetSideFrame( + unsigned int grid_side_index, + ON_Plane& frame + ) const; + + /* + Parameters: + grid_side_index - [in] + grid side N is between corner index N and corner index (N+1)%4. + Returns: + Limit surface location at the center of the grid side or ON_3dPoint::NanPoint if the fragment is empty. + Remarks: + For partial fragments (IsPartialFragment() = true), grid_corner_index = 2 is the only + corner that corresponds to a SubD vertex. + For partial fragments (IsPartialFragment() = true), grid_side_index = 1 and grid_side_index = 2 + correspond to half of original SuD edges. + */ + const ON_3dPoint CenterPoint( + ) const; + + const ON_3dVector CenterNormal( + ) const; + + bool GetCenterFrame( + ON_Plane& frame + ) const; + +private: + bool Internal_GetFrameHelper( + unsigned int P_dex, + unsigned int Q_dex, + ON_Plane& frame + ) const; + +public: + /* Returns: Status of the face. @@ -4113,13 +4386,18 @@ public: /* Returns: - True if this fragment covers a subset of a face. + True if this fragment covers a portion of the original SubD face. + Remarks: + For partial fragments (IsPartialFragment() = true), grid_corner_index = 2 is the only + corner that corresponds to a SubD vertex. + For partial fragments (IsPartialFragment() = true), grid_side_index = 1 and grid_side_index = 2 + correspond to half of original SuD edges. */ - bool IsSubFragment() const; + bool IsPartialFragment() const; /* Returns: - True if this fragment covers an entier subd face. + True if this fragment covers an entire subd face. */ bool IsCompleteFragment() const; @@ -4235,6 +4513,12 @@ public: unsigned int m_display_density = 0; bool m_bUseMultipleThreads = false; + + // Edge sealing takes extra time and can be skipped if + // adjacent faces can have mesh fragments whose shared vertex + // locations are nearly (but generally not exactly) equal. + bool m_bSkipEdgeSealing = false; + ON_Terminator* m_terminator = nullptr; // optional progress reporting ON_ProgressReporter* m_progress_reporter = nullptr; @@ -4273,22 +4557,22 @@ public: //MinimumLevelOfDetail = 8 // 8 = fewest facets per fragment }; - static ON_SubDLimitMesh* Create( - const ON_SubD& subd, - const class ON_SubDDisplayParameters& limit_mesh_parameters, - ON_SubDLimitMesh* destination_mesh - ); + /* Description: - This version is for expert users who want to take - responsibility for managing the subd and limit mesh + Parameters: + destination_mesh - [in] + If destination_mesh is not nullptr, then the returned mesh + will be store here. Otherwise the returned mesh will be + allocated with a call to new ON_Mesh(). + Returns: + If this limit mesh is not empty, an ON_Mesh representation of this limit mesh. + Othewise, nullptr. */ - static ON_SubDLimitMesh* Create( - ON_SubDFaceIterator fit, - const class ON_SubDDisplayParameters& limit_mesh_parameters, - ON_SubDLimitMesh* destination_mesh - ); + ON_Mesh* ToMesh( + ON_Mesh* destination_mesh + ) const; ON_SubDLimitMesh() = default; ~ON_SubDLimitMesh() = default; @@ -4322,6 +4606,63 @@ public: ON_SubDDisplayParameters DisplayParameters() const; unsigned int FragmentCount() const; const ON_SubDLimitMeshFragment* FirstFragment() const; // linked list of mesh fragments + + /* + Parameters: + face - [in] + Returns: + The first fragment for this face or nullptr if none is found. + If the face is not a quad, then there will be multiple partial + fragments for the face and this is the first on. The others + follow using m_next_fragment. + */ + const ON_SubDLimitMeshFragment* FaceFragment( + const class ON_SubDFace* face + ) const; + + /* + Description: + Get the limit surface point location and normal for + the face's center from the limit mesh grid. + Parameters: + face - [in] + P - [out] + P = limit surface location or ON_3dPoint::NanPoint if not available. + N - [out] + N = limit surface unit normal or ON_3dVector::NanVector if not available. + Returns: + True if the point and normal were set from the limit mesh frament. + False if the limit mesh fragment was not found and nan values were returned. + */ + bool GetFaceCenterPointAndNormal( + const class ON_SubDFace* face, + double* P, + double* N + ) const; + + /* + Description: + Get the limit surface point location and normal for + the edge's midpoint from the limit mesh grid. + Parameters: + edge - [in] + edge_face_index - [in] + Index of the face to use for the normal. If the edge is a crease, then + each attached face may have a different normal. Pass 0 when in doubt. + P - [out] + P = limit surface location or ON_3dPoint::NanPoint if not available. + N - [out] + N = limit surface unit normal or ON_3dVector::NanVector if not available. + Returns: + True if the point and normal were set from the limit mesh frament. + False if the limit mesh fragment was not found and nan values were returned. + */ + bool GetEdgeCenterPointAndNormal( + const class ON_SubDEdge* edge, + unsigned int edge_face_index, + double* P, + double* N + ) const; /* Description: @@ -4396,160 +4737,6 @@ private: - -class ON_SUBD_CLASS ON_SubDLimitNurbsFragment -{ -public: - ON_SubDLimitNurbsFragment() = default; - ~ON_SubDLimitNurbsFragment() = default; - ON_SubDLimitNurbsFragment(const ON_SubDLimitNurbsFragment&) = default; - ON_SubDLimitNurbsFragment& operator=(const ON_SubDLimitNurbsFragment&) = default; - -public: - // No construction for performance reasons. - // If you require initialization, use = ON_SubDLimitMeshFragment::Empty - // - //ON_SubDLimitMeshFragment() = default; - //~ON_SubDLimitMeshFragment() = default; - //ON_SubDLimitMeshFragment(const ON_SubDLimitMeshFragment&) = default; - //ON_SubDLimitMeshFragment& operator=(const ON_SubDLimitMeshFragment&) = default; - - // Every field of ON_SubDLimitNurbsFragment::Empty is zero. - static const ON_SubDLimitNurbsFragment Empty; - - // Every m_patch_cv[][][] value is ON_UNSET_VALUE. - // Every other field of ON_SubDLimitNurbsFragment::Unset is zero. - static const ON_SubDLimitNurbsFragment Unset; - - // Every m_patch_cv[][][] value is ON_DBL_QNAN. - // Every other field of ON_SubDLimitNurbsFragment::Unset is zero. - static const ON_SubDLimitNurbsFragment Nan; - - - #pragma region RH_C_SHARED_ENUM [ON_SubDLimitNurbsFragment::PatchTypeXYX] [Rhino.Geometry.SubD.PatchFragmentTypeXYX] [internal:nested:byte] - enum class Type : unsigned char - { - ///Not a valid patch type. - Unset = 0, - - ///This fragment is a single 4x4 bicubic span covering the entire SubD region identified by m_face_region. - BicubicSingle = 1, - - ///The bispans in this fragment are quadrants of the SubD region identified by m_face_region. - BicubicQuadrant = 4 - }; -#pragma endregion - - #pragma region RH_C_SHARED_ENUM [ON_SubDLimitNurbsFragment::BispanType] [Rhino.Geometry.SubD.FragmentBispanType] [internal:nested:byte] - enum class BispanType : unsigned char - { - ///No bispan. - None = 0, - - ///Entire region identified by m_face_region is an exact bicubic patch. - Exact = 1, - - ///Entire region identified by m_face_region is approximately a bicubic patch. - Approximate = 2 - }; -#pragma endregion - -public: - // knot vector is uniform and not clamped. For example {-2,-1,0,1,2,3,4}. - double m_patch_cv[5][5][3]; - - // m_face_region identifies what part of the SubD is represented by this patch - ON_SubDFaceRegion m_face_region; - - ON_SubDLimitNurbsFragment::Type m_type = ON_SubDLimitNurbsFragment::Type::Unset; - - // m_patch_type[] reports what type of patch is returned in m_patch_cv[] - // m_patch_type[0] = the state for the uniform bi-cubic patch: - // CV[i][j] = m_patch_cv[i][j] (0 <= i <= 3, 0 <= j <= 3) - // knot[0] = {-2,-1,0,1,2,3} - // knot[1] = {-2,-1,0,1,2,3} - // m_patch_type[1] = the state for the uniform bi-cubic patch: - // CV[i][j] = m_patch_cv[i+1][j] (0 <= i <= 3, 0 <= j <= 3) - // knot[0] = {-1,0,1,2,3,4} - // knot[1] = {-2,-1,0,1,2,3} - // m_patch_type[2] = the state for the uniform bi-cubic patch: - // CV[i][j] = m_patch_cv[i+1][j+1] (0 <= i <= 3, 0 <= j <= 3) - // knot[0] = {-1,0,1,2,3,4} - // knot[1] = {-1,0,1,2,3,4} - // m_patch_type[3] = the state for the uniform bi-cubic patch: - // CV[i][j] = m_patch_cv[i][j+1] (0 <= i <= 3, 0 <= j <= 3) - // knot[0] = {-2,-1,0,1,2,3} - // knot[1] = {-1,0,1,2,3,4} - // - // Single bicubic bezier covers the region: - // m_patch_type[] = {Bicubic or ApproximateBicubic, Ignore, Ignore, Ignore} - // Bicubic quadrants - // Each m_patch_type[] element is one of BicubicQuadrant, ApproximateBicubicQuadrant, or None. - ON_SubDLimitNurbsFragment::BispanType m_bispan_type[4] = {}; - - -private: - // number of edges in the level 0 SubD face. - unsigned char m_reserved1 = 0; - unsigned short m_reserved2 = 0; - -public: - /* - True if there are no set bispans. - */ - bool IsEmpty() const; - - /* - Returns: - 0: unset NURBS fragment - 1: This NURBS fragment uses a single bicubic span to model the region identified by m_face_region. - 4: This NURBS fragment uses 4 bicubic spans to model the 4 quadrants of the region identified by m_face_region. - */ - unsigned int MaximumBispanCount() const; - - /* - Returns: - 0 to 4 indicating the number of bicubic spans that are set in - this NURBS fragment. - Remarks: - If 0 < SetBispanCount() == MaximumBispanCount(), - then the entire region identified by m_face_region is modeled by - this NURBS fragment. - - If 0 < SetBispanCount() < MaximumBispanCount(), - then that many quadrants of the region identified by m_face_region - are modeled by this set bispans in this NURBS fragment. - */ - unsigned int SetBispanCount() const; - - - /* - Returns: - 0 to 4 indicating the number of unset bicubic spans in "this". - */ - unsigned int UnsetBispanCount() const; - - /* - Returns: - One or more of the bispans approximate the SubD surface. - */ - bool IsApproximate() const; - - ON_NurbsSurface* GetSurface( - ON_NurbsSurface* destination_surface - ) const; - - ON_NurbsSurface* GetQuadrantSurface( - unsigned int quadrant_index, - ON_NurbsSurface* destination_surface - ) const; - - -private: - unsigned short m_reserved = 0; -}; - - class ON_SUBD_CLASS ON_SubDSectorLimitPoint { public: @@ -4666,6 +4853,9 @@ private: public: unsigned short m_level = 0; +public: + const ON_ComponentStatus Status() const; + public: mutable ON_ComponentStatus m_status = ON_ComponentStatus::NoneSet; @@ -4724,12 +4914,26 @@ public: void ClearDisplacement() const; protected: - // GetSubdivisionPoint( bUseSavedSubdivisionPoint=true ) can change the value of m_saved_points_flags - // m_saved_points_flags & 0x1F = ON_SubD::SubDType value - // m_saved_points_flags & 0x40 != 0 if m_cache_subd_P is set. - // m_saved_points_flags & 0x80 != 0 if m_displacementV is set. + enum SavedPointsFlags : unsigned char + { + // (m_saved_points_flags & SubDTypeMask) is an ON_SubD::SubDType value for the cached points. + SubDTypeMask = 0x0F, + + // if ( 0 != (m_saved_points_flags & SubDDisplacementVIsSet), then m_cache_subd_P is set. + SubdivisionPointIsSet = 0x20, + + // if ( 0 != (m_saved_points_flags & SubDDisplacementVIsSet), then m_displacementV is set. + DisplacementVectorIsSet = 0x40, + + // if ( 0 != (m_saved_points_flags & LimitPointIsSet), then ON_subDVertex.m_limit* values are set. + LimitPointIsSet = 0x80, + + // SubdivisionPointIsSet | DisplacementVectorIsSet | LimitPointIsSet + CachedPointMask = 0xE0 + }; + + // m_saved_points_flags is a bit field based on ON_SubDComponentBase::SavePointsFlags values. // GetLimitPoint( bUseSavedLimitPoint=true ) can change the value of m_saved_points_flags - // m_saved_points_flags & 0x20 != 0 if ON_subDVertex.m_limit* values are set. mutable unsigned char m_saved_points_flags = 0U; public: @@ -4797,6 +5001,23 @@ public: class ON_SubDVertex*& vertex ); + /* + Parameters: + bIncludeEdges - [in] + If true, then attached edges are included. + bIncludeFaces - [in] + If true, then attached faces are included. + Returns: + A ON_ComponentStatusLogicalOr() of this vertex's status and the + specified attached components. + See Also: + ON_SubDComponentBase::Status() + */ + const ON_ComponentStatus NeighborhoodStatusLogicalOr( + bool bIncludeEdges, + bool bIncludeFaces + ) const; + /* Description: Apply a tranxfomration matrix to vertex geometry information. @@ -4826,9 +5047,7 @@ public: ON_BoundingBox ControlNetBoundingBox() const; - ON_BoundingBox LimitSurfaceBoundingBox( - const ON_SubD& subd - ) const; + public: ON_COMPONENT_INDEX ComponentIndex() const; @@ -4872,13 +5091,13 @@ public: private: // Cached limit point and limit normal - // GetLimitPoint( bUseSavedLimitPoint=true ) can change the value of m_limitP_type + // GetLimitPoint( bUseSavedLimitPoint=true ) can change the value of m_limit_point. // If the limit point is set and vertex has a single sector, then // m_limit_point.m_sector_face = nullptr and m_limit_point.m_next_sector_limit_point = nullptr. // If the limit point is set and vertex has a multiple sectors, then // m_limit_point.m_sector_face = first face in the sector. - // If multiple limit points are set, then are in a linked list - // traversed using the ON_SubDSectorLimitPointm_next_sector_limit_point. + // If multiple limit points are set, then they are in a linked list + // traversed using the ON_SubDSectorLimitPoint.m_next_sector_limit_point. // The second and any additional limit points are managed by a fixed size pool. // Calling ClearLimitPoint() will return these to the pool. mutable ON_SubDSectorLimitPoint m_limit_point = ON_SubDSectorLimitPoint::Unset; @@ -5212,6 +5431,23 @@ public: class ON_SubD& subd, class ON_SubDEdge*& edge ); + + /* + Parameters: + bIncludeVertices - [in] + If true, then attached vertices are included. + bIncludeFaces - [in] + If true, then attached faces are included. + Returns: + A ON_ComponentStatusLogicalOr() of this vertex's status and the + specified attached components. + See Also: + ON_SubDComponentBase::Status() + */ + const ON_ComponentStatus NeighborhoodStatusLogicalOr( + bool bIncludeVertices, + bool bIncludeFaces + ) const; /* Description: @@ -5237,9 +5473,6 @@ public: ); ON_BoundingBox ControlNetBoundingBox() const; - ON_BoundingBox LimitSurfaceBoundingBox( - const ON_SubD& subd - ) const; /* @@ -5249,6 +5482,11 @@ public: */ void EdgeModifiedNofification() const; + /* + Description: + Expert user tool to unset sector coefficients. + */ + void UnsetSectorCoefficients() const; public: ON_COMPONENT_INDEX ComponentIndex() const; @@ -5423,6 +5661,20 @@ public: const ON_SubDVertex* vertex ) const; + /* + Returns: + If vertex is set, then the location is returned. + Otherwise ON_3dPoint::NanPoint is returned. + */ + const ON_3dPoint EndPoint( unsigned int i ) const; + + /* + Returns: + If vertices are set, then the vector from m_vertex[0] to m_vertex[1] is returned. + Otherwise ON_3dVector::NanVector is returned. + */ + const ON_3dVector Direction() const; + /* Description: Return the neighboring face. @@ -5465,27 +5717,58 @@ public: bool bStopAtCrease ) const; - - + /* + Returns: + True if m_edge_tag is ON_SubD::EdgeTag::Smooth or ON_SubD::EdgeTag::X. + False in all other cases. + */ + bool IsSmooth() const; + /* Returns: True if m_edge_tag is ON_SubD::EdgeTag::Smooth. - bEdgeTagXresult if m_edge_tag is ON_SubD::EdgeTag::X. - False in all other cases. + Remarks: + Expert user function. + This is used in rare cases when level 0 edges tagged as ON_SubD::EdgeTag::X + need special handling in low level evaluation code. Typical SDK level functions + and anything related to runtime user interface should call IsSmooth(). */ - bool IsSmooth( - bool bEdgeTagXresult - ) const; + bool IsSmoothNotX() const; + + /* + Returns: + True if m_edge_tag is ON_SubD::EdgeTag::X. + Remarks: + Expert user function. + This is used in rare cases when level 0 edges tagged as ON_SubD::EdgeTag::X + need special handling in low level evaluation code. Typical SDK level functions + and anything related to runtime user interface should call IsSmooth(). + An edge tagged as "X" can occur at level 0. It is subdivided as a smooth + vertex and both of its end vertices are tagged as ON_SubD::VertexTag::Crease, + ON_SubD::VertexTag::Corner, or ON_SubD::VertexTag::Dart. + This tag cannot appear at level N with N >= 1. + */ + bool IsSmoothX() const; /* Returns: True if m_edge_tag is ON_SubD::EdgeTag::Crease. - bEdgeTagXresult if m_edge_tag is ON_SubD::EdgeTag::X. - False in all other cases. */ - bool IsCrease( - bool bEdgeTagXresult - ) const; + bool IsCrease() const; + + /* + Returns: + True if m_edge_tag is ON_SubD::EdgeTag::Crease and both of its end vertices + are tagged as ON_SubD::VertexTag::Crease, or ON_SubD::VertexTag::Corner. + */ + bool IsHardCrease() const; + + /* + Returns: + True if m_edge_tag is ON_SubD::EdgeTag::Crease and at least one of its end vertices + are tagged as ON_SubD::VertexTag::Dart. + */ + bool IsDartCrease() const; /* Returns: @@ -5582,6 +5865,24 @@ public: class ON_SubDFace*& face ); + + /* + Parameters: + bIncludeVertices - [in] + If true, then attached vertices are included. + bIncludeEdges - [in] + If true, then attached edges are included. + Returns: + A ON_ComponentStatusLogicalOr() of this vertex's status and the + specified attached components. + See Also: + ON_SubDComponentBase::Status() + */ + const ON_ComponentStatus NeighborhoodStatusLogicalOr( + bool bIncludeVertices, + bool bIncludeEdges + ) const; + /* Description: Apply a tranxfomration matrix to vertex geometry information. @@ -5606,9 +5907,6 @@ public: ); ON_BoundingBox ControlNetBoundingBox() const; - ON_BoundingBox LimitSurfaceBoundingBox( - const ON_SubD& subd - ) const; ON_COMPONENT_INDEX ComponentIndex() const; @@ -5621,7 +5919,6 @@ public: */ void FaceModifiedNofification() const; - public: const class ON_SubDFace* m_prev_face = nullptr; // linked list of faces on this level const class ON_SubDFace* m_next_face = nullptr; // linked list of faces on this level @@ -6626,9 +6923,6 @@ public: return (m_face_current = m_face_last); } - unsigned int LimitSurfaceMeshFragmentCount( - ON_SubD::FacetType facet_type - ) const; private: void Internal_Init( @@ -6911,10 +7205,30 @@ public: unsigned int face_side_index ) const; + enum class StopAt : unsigned char + { + /// + /// Sector iteration will terminate when the edge being crossed does not have two faces. + /// + Boundary = 0, + + /// + /// Sector iteration will terminate when the edge being crossed does not have two faces + /// or that edge is tagged as ON_SubD::EdgeTag::Crease. + /// + AnyCrease = 1, + + /// + /// Sector iteration will terminate when the edge being crossed does not have two faces + /// or that edge is tagged as ON_SubD::EdgeTag::Crease and has no dart vertices. + /// + HardCrease = 2, + }; + /* Description: Advance the "current" face to the "next" face in the ring - by "hopping across" CurrentEdge(1). + by crossing CurrentEdge(1). If the current face is not reversed (1 == CurrentFaceDirection), then this rotation is counter-clockwise with respect to @@ -6924,9 +7238,8 @@ public: then this rotation is clockwise with respect to the current face's orientation. Parameters: - bStopAtCrease - [in] - If true and CurrentEdge(1) is tagged ON_SubD:EdgeTag::crease, - iteration terminates. + stop_at - [in] + Determines that type of CurrentEdge(1) will terminate iteration. Returns: When the input CurrentEdge(1) has exactly two faces and is not tagged as a crease or bStopAtCrease is false, the @@ -6936,13 +7249,13 @@ public: Identical to calling IncrementFace(+1,bStopAtCrease); */ const ON_SubDFace* NextFace( - bool bStopAtCrease + ON_SubDSectorIterator::StopAt stop_at ); /* Description: Advance the "current" face to the "previous" face in the ring - by "hopping across" CurrentEdge(0). + by crossing CurrentEdge(0). If the current face is not reversed (0 == CurrentFaceDirection), then this rotation is clockwise with respect to @@ -6952,9 +7265,8 @@ public: then this rotation is counter-clockwise with respect to the current face's orientation. Parameters: - bStopAtCrease - [in] - If true and CurrentEdge(0) is tagged ON_SubD:EdgeTag::crease, - iteration terminates. + stop_at - [in] + Determines that type of CurrentEdge(0) will terminate iteration. Returns: When the input CurrentEdge(0) has exactly two faces and is not tagged as a crease or bStopAtCrease is false, the @@ -6965,7 +7277,7 @@ public: Identical to calling IncrementFace(-1,bStopAtCrease); */ const ON_SubDFace* PrevFace( - bool bStopAtCrease + ON_SubDSectorIterator::StopAt stop_at ); /* @@ -6984,9 +7296,9 @@ public: increment_direction - [in] > 0 advance the "current" face to next face <= 0 advance the "current" face to previous face - bStopAtCrease - [in] + stop_at - [in] + Determines that type of CurrentEdge((increment_direction>0) ? 1 : 0) will terminate iteration. If true and the input value of CurrentEdge((increment_direction>0) ? 1 : 0) - is tagged as ON_SubD:EdgeTag::crease, iteration terminates. When iteration terminates at a crease, CurrentFace() becomes nullptr CurrentEdge((increment_direction>0) ? 1 : 0) becomes nullptr @@ -6997,7 +7309,7 @@ public: */ const ON_SubDFace* IncrementFace( int increment_direction, - bool bStopAtCrease + ON_SubDSectorIterator::StopAt stop_at ); /* @@ -7480,6 +7792,9 @@ public: const class ON_SubDEdge* Edge() const; const class ON_SubDFace* Face() const; + + static int Compare(const ON_SubDComponentRef* lhs, const ON_SubDComponentRef* rhs); + static int Compare2(const ON_SubDComponentRef* const* lhs, const ON_SubDComponentRef* const* rhs); private: ON_SubDRef m_subd_ref; @@ -7501,6 +7816,166 @@ public: bool GetBBox( double* boxmin, double* boxmax, bool bGrowBox = false ) const override; }; +class ON_CLASS ON_SubDComponentRefList +{ +public: + ON_SubDComponentRefList() = default; + ~ON_SubDComponentRefList(); + ON_SubDComponentRefList(const ON_SubDComponentRefList& src); + ON_SubDComponentRefList& operator=(const ON_SubDComponentRefList& src); + + /* + Description: + Append a ON_SubDComponentRef to the identified component. + Returns; + A pointer to the ON_SubDComponentRef managed by this class or + nullptr if the input is not valid. + */ + const ON_SubDComponentRef& Append( + const ON_SubDRef& subd_ref, + ON_COMPONENT_INDEX ci, + ON_SubDComponentLocation component_location + ); + + const ON_SubDComponentRef& Append( + const ON_SubDRef& subd_ref, + ON_SubDComponentPtr component_ptr, + ON_SubDComponentLocation component_location + ); + + /* + Description: + Appends a copy of src_ref and returns a pointer to the ON_SubDComponentRef + managed by this class. + */ + const ON_SubDComponentRef& Append(const ON_SubDComponentRef* src_ref); + + /* + Description: + Expert user function to transfer managment of an ON_SubDComponentRef on the heap + to this class. + */ + const ON_SubDComponentRef& AppendForExperts(ON_SubDComponentRef*& ref); + + /* + Description: + Expert user function to append a ON_SubDComponentRef to the identified component. + The expert user is responsible for insuring subd exists fot the lifetime of this + class. + */ + const ON_SubDComponentRef& AppendForExperts( + const ON_SubD& subd, + ON_COMPONENT_INDEX ci, + ON_SubDComponentLocation component_location + ); + + const ON_SubDComponentRef& AppendForExperts( + const ON_SubD& subd, + ON_SubDComponentPtr component_ptr, + ON_SubDComponentLocation component_location + ); + + /* + Description: + Sort by ON_SubDComponentRef::Compare2() and remove duplicates and empty elements. + Returns: + Length of clean list. + */ + int Clean(); + + + /* + Returns: + Number of refs in the list. + */ + int Count() const; + + void Remove(int i); + + /* + Description: + Transfers the ref to an expert user who is responsible for properly managing it. + */ + ON_SubDComponentRef* TransferForExperts(int i); + + const ON_SubDComponentRef& operator[](int) const; + + /* + Returns: + If the list is clean, the number of subd objects in the list. Mutiple components can belong to the same SubD. + If the list is not clean, 0. + Remarks: + Use Clean() to get a clean list. + */ + int SubDCount() const; + + /* + Returns: + Number of vertices. + */ + int VertexCount() const; + + /* + Returns: + Number of vertices with the specified tag. + */ + int VertexCount(ON_SubD::VertexTag vertex_tag) const; + + /* + Returns: + Number of edges. + */ + int EdgeCount() const; + + /* + Returns: + Number of edges with the specified tag. + */ + int EdgeCount(ON_SubD::EdgeTag edge_tag) const; + + + /* + Returns: + Number of faces. + */ + int FaceCount() const; + + /* + Returns: + Number of components. + */ + int ComponentCount() const; + +private: +#pragma ON_PRAGMA_WARNING_PUSH +#pragma ON_PRAGMA_WARNING_DISABLE_MSC( 4251 ) + // C4251: ... needs to have dll-interface to be used by clients of class ... + // m_subdimple_sp is private and all code that manages m_subdimple_sp is explicitly implemented in the DLL. + ON_SimpleArray< class ON_SubDComponentRef* > m_list; +#pragma ON_PRAGMA_WARNING_POP + + int m_subd_count = 0; + int m_subd_vertex_smooth_count = 0; + int m_subd_vertex_dart_count = 0; + int m_subd_vertex_crease_count = 0; + int m_subd_vertex_corner_count = 0; + int m_subd_edge_smooth_count = 0; + int m_subd_edge_crease_count = 0; + int m_subd_face_count = 0; + + bool m_bIsClean = false; // true if m_list is clean. + + unsigned char m_reserved1 = 0; + unsigned short m_reserved2 = 0; + unsigned int m_reserved3 = 0; + ON__UINT_PTR m_reserved4 = 0; + +private: + bool Internal_UpdateCount(const ON_SubDComponentRef& r, int delta); + void Internal_Destroy(); + void Internal_CopyFrom(const ON_SubDComponentRefList& src); +}; + ////////////////////////////////////////////////////////////////////////// // // ON_SubDComponentPoint @@ -7983,6 +8458,241 @@ private: ON_SubD_FixedSizeHeap& operator=(const ON_SubD_FixedSizeHeap&) = delete; }; +class ON_SUBD_CLASS ON_SubDEdgeChain +{ +public: + ON_SubDEdgeChain() = default; + ~ON_SubDEdgeChain() = default; + ON_SubDEdgeChain(const ON_SubDEdgeChain&) = default; + ON_SubDEdgeChain& operator=(const ON_SubDEdgeChain&) = default; + +public: + static const ON_SubDEdgeChain Empty; + +public: + + ///////////////////////////////////////////////////////// + // + // Edge chain tools + // + + /* + Description: + Get the neighboring link in an edge chain. + Parameters: + starting_edge - [in] + search_direction - [in] + ON_ChainDirection::Previous or ON_ChainDirection::Next. + The search direction is relative to this->EdgeDirection(). + bStopAtTagChange - [in] + If true and the edge is smooth, the chain will stop if it encounters a vertex that is not smooth. + If true and the edge is a crease, the chain will stop if it encounters a vertex is smooth. + bEnableStatusCheck - [in] + status_pass - [in] + status_fail - [in] + If bEnableStatusFilter is false, then no status checks are performed. + If bEnableStatusFilter is true, ON_ComponentStatus(candidate_edge->m_status,status_pass,status_fail) + must be true for candidate_edge to be returned. + Returns: + The next or previous edge in the chain if it exists. + Otherwise, nullptr is returned. + Remarks: + When multiple edges are candidates, there is a bias to preserve smooth/crease and a bias to + preserve face count. If the biases don't reduce the list of candidates to one or bStopAtBreak is true + and a creaase/smooth break is encountered, then ON_SubDEdgePtr::Null is returned. + */ + static const ON_SubDEdgePtr EdgeChainNeighbor( + ON_SubDEdgePtr starting_edge, + ON_ChainDirection search_direction, + bool bStopAtTagChange, + bool bEnableStatusCheck, + ON_ComponentStatus status_pass, + ON_ComponentStatus status_fail + ); + + /* + Description: + Reverse the orientation of the elements and the order + of the elements in the edge_chain[] array. + Parameters: + edge_chain - [in/out] + */ + static void ReverseEdgeChain( + ON_SimpleArray< ON_SubDEdgePtr >& edge_chain + ); + + /* + Description: + Reverse the orientation of the elements and the order + of the elements in the edge_chain[] array. + Parameters: + edge_count - [in] + Number of elements in edge_chain[] + edge_chain - [in/out] + */ + static void ReverseEdgeChain( + size_t edge_count, + ON_SubDEdgePtr* edge_chain + ); + + /* + Description: + Checks that all edge and vertex pointers are not nullptr + and that adjacent edges in the list share the same vertex. + Parameters: + edge_chain - [in] + Edge chain to test + bCheckForDuplicateEdges - [in] + If bCheckForDuplicateEdges true, then false is returned if + there are duplicate edges or duplicate interior vertices. + Returns: + True if edge_chain[] is valid. + */ + static bool IsValidEdgeChain( + const ON_SimpleArray< ON_SubDEdgePtr >& edge_chain, + bool bCheckForDuplicateEdges + ); + + /* + Description: + Checks that all edge and vertex pointers are not nullptr + and that adjacent edges in the list share the same vertex. + Parameters: + edge_count - [in] + Number of elements in edge_chain[] to test. + edge_chain - [in] + Edge chain to test + bCheckForDuplicateEdges - [in] + If bCheckForDuplicateEdges true, then false is returned if + there are duplicate edges or duplicate interior vertices. + Returns: + True if edge_chain[] is valid. + */ + static bool IsValidEdgeChain( + size_t edge_count, + const ON_SubDEdgePtr* edge_chain, + bool bCheckForDuplicateEdges + ); + +public: + /* + Returns: + Current EdgeChain + */ + const ON_SimpleArray& EdgeChain() const; + + + const ON_SubD& SubD() const; + const ON_SubDRef SubDRef() const; + + bool InChain( + const ON_SubDEdgePtr edge_ptr + ) const; + + bool InChain( + const ON_SubDEdge* edge + ) const; + + bool InChain( + const ON_SubDVertex* vertex + ) const; + + bool IsClosedLoop() const; + + unsigned int BeginEdgeChain( + ON_SubDRef subd_ref, + const ON_SubDEdge* initial_edge + ); + + unsigned int BeginEdgeChain( + ON_SubDRef subd_ref, + const ON_SimpleArray& initial_edge_chain + ); + + unsigned int BeginEdgeChain( + ON_SubDRef subd_ref, + size_t edge_count, + const ON_SubDEdge*const* initial_edge_chain + ); + + unsigned int BeginEdgeChain( + ON_SubDRef subd_ref, + ON_SubDEdgePtr initial_edge + ); + + unsigned int BeginEdgeChain( + ON_SubDRef subd_ref, + const ON_SimpleArray& initial_edge_chain + ); + + unsigned int BeginEdgeChain( + ON_SubDRef subd_ref, + size_t edge_count, + const ON_SubDEdgePtr* initial_edge_chain + ); + + unsigned int EdgeCount() const; + + void SetStatusCheck( + bool bEnableStatusCheck, + ON_ComponentStatus status_check_pass, + ON_ComponentStatus status_check_fail + ); + + bool StatusCheckEnabled() const; + + const ON_ComponentStatus StatusCheckPass() const; + + const ON_ComponentStatus StatusCheckFail() const; + + bool StatusCheck( + const ON_SubDEdge* edge + ) const; + + void Reverse(); + + const ON_SubDEdgePtr FirstEdgePtr() const; + const ON_SubDEdgePtr LastEdgePtr() const; + const ON_SubDEdgePtr EdgePtr(int edge_index) const; + + const ON_SubDEdge* FirstEdge() const; + const ON_SubDEdge* LastEdge() const; + const ON_SubDEdge* Edge(int edge_index) const; + + const ON_SubDVertex* FirstVertex() const; + const ON_SubDVertex* LastVertex() const; + const ON_SubDVertex* Vertex(int vertex_index) const; + + unsigned int AddOneNeighbor( + ON_ChainDirection direction, + bool bStopAtTagChange + ); + + unsigned int AddAllNeighbors( + ON_ChainDirection direction, + bool bStopAtTagChange + ); + + unsigned int AddEdge( + const ON_SubDEdge* edge + ); + + unsigned int RemoveEdges( + const ON_SubDEdge* first_edge, + const ON_SubDEdge* last_edge + ); + + void ClearEdgeChain(); + +private: + ON_SubDRef m_subd_ref; + ON_SimpleArray m_edge_chain; + ON_UniqueTester m_unique_tester; + bool m_bEnableStatusCheck = false; + ON_ComponentStatus m_status_check_pass = ON_ComponentStatus::NoneSet; + ON_ComponentStatus m_status_check_fail = ON_ComponentStatus::Selected; +}; + #if defined(ON_COMPILING_OPENNURBS) /* The ON_SubDAsUserData class is used to attach a subd to it proxy mesh diff --git a/opennurbs_subd_archive.cpp b/opennurbs_subd_archive.cpp index 630461f3..c64b4297 100644 --- a/opennurbs_subd_archive.cpp +++ b/opennurbs_subd_archive.cpp @@ -1267,6 +1267,17 @@ bool ON_SubDimple::Read( m_active_level = level; } + + const unsigned int heap_max_vertex_id = m_heap.MaximumVertexId(); + const unsigned int heap_max_edge_id = m_heap.MaximumEdgeId(); + const unsigned int heap_max_face_id = m_heap.MaximumFaceId(); + if (m_max_vertex_id < heap_max_vertex_id) + m_max_vertex_id = heap_max_vertex_id; + if (m_max_edge_id < heap_max_edge_id) + m_max_edge_id = heap_max_edge_id; + if (m_max_face_id < heap_max_face_id) + m_max_face_id = heap_max_face_id; + if ( level_index != level_count) break; @@ -1276,9 +1287,50 @@ bool ON_SubDimple::Read( if (!archive.EndRead3dmChunk()) rc = false; - m_max_vertex_id = max_vertex_id; - m_max_edge_id = max_edge_id; - m_max_face_id = max_face_id; + // Heap id validation. Always an error if max_heap_..._id > m_max_..._id. + if (false == m_heap.IsValid()) + { + ON_SUBD_ERROR("m_heap.IsValid() is false."); + m_heap.ResetId(); // breaks component id references, but this is a serious error. + } + const unsigned int max_heap_vertex_id = m_heap.MaximumVertexId(); + const unsigned int max_heap_edge_id = m_heap.MaximumEdgeId(); + const unsigned int max_heap_face_id = m_heap.MaximumFaceId(); + if (m_max_vertex_id < max_heap_vertex_id) + { + ON_SUBD_ERROR("m_max_vertex_id is too small."); + m_max_vertex_id = max_heap_vertex_id; + } + if (m_max_edge_id < max_heap_edge_id) + { + ON_SUBD_ERROR("m_max_edge_id is too small."); + m_max_edge_id = max_heap_edge_id; + } + if (m_max_face_id < max_heap_face_id) + { + ON_SUBD_ERROR("m_max_face_id is too small."); + m_max_face_id = max_heap_face_id; + } + + if (max_vertex_id > m_max_vertex_id) + m_max_vertex_id = max_vertex_id; + if (max_edge_id > m_max_edge_id) + m_max_edge_id = max_edge_id; + if (max_face_id > m_max_face_id) + m_max_face_id = max_face_id; + + const unsigned int archive_version = archive.ArchiveOpenNURBSVersion(); + const unsigned int bad_counts_cutoff_version = ON_VersionNumberConstruct(6, 12, 2018, 12, 12, 0); + if (archive_version > bad_counts_cutoff_version) + { + if( m_max_vertex_id != max_vertex_id + || m_max_edge_id != max_edge_id + || m_max_face_id != max_face_id + ) + { + ON_SUBD_ERROR("Correct m_max_verrtex/edge/face_id differs from value saved in 3dm archive."); + } + } if (rc) return true; @@ -1427,8 +1479,7 @@ ON_Mesh* ON_SubDMeshProxyUserData::MeshProxyFromSubD( subd_copy = new ON_SubD(*subd); if (nullptr == subd_copy) break; - const ON_SubDDisplayParameters dp = ON_SubDMeshProxyUserData::MeshProxyDisplayParameters(); - mesh = subd_copy->GetLimitSurfaceMesh(dp, nullptr); + mesh = subd_copy->GetControlNetMesh(nullptr); if (false == ON_SubDMeshProxyUserData::Internal_MeshHasFaces(mesh)) break; ON_SubDMeshProxyUserData* ud = new ON_SubDMeshProxyUserData(); diff --git a/opennurbs_subd_copy.cpp b/opennurbs_subd_copy.cpp index 3c7b5a2f..b86f9bd6 100644 --- a/opennurbs_subd_copy.cpp +++ b/opennurbs_subd_copy.cpp @@ -583,7 +583,7 @@ unsigned int ON_SubDArchiveIdMap::ConvertArchiveIdsToRuntimePointers() void ON_SubD::ShareContentsFrom(ON_SubD& src_subd) { - if (this == &ON_SubD::Empty || &src_subd != &ON_SubD::Empty) + if (this == &ON_SubD::Empty || &src_subd == &ON_SubD::Empty) { ON_SubDIncrementErrorCount(); } @@ -634,9 +634,55 @@ void ON_SubD::CopyHelper(const ON_SubD& src) subdimple = new ON_SubDimple(*src_subdimple); } m_subdimple_sp = std::shared_ptr(subdimple); + if (nullptr != subdimple) + subdimple->SetLimitMeshSubDWeakPointer(m_subdimple_sp); } +void ON_SubDimple::SetLimitMeshSubDWeakPointer( + std::shared_ptr& subdimple_sp + ) +{ + // update weak ptrs on limit mesh + const unsigned int level_count = m_levels.UnsignedCount(); + for (unsigned level_index = 0; level_index < level_count; level_index++) + { + ON_SubDLevel* level = m_levels[level_index]; + if (nullptr == level) + continue; + ON_SubDLimitMeshImpl* limple = level->m_limit_mesh.SubLimple(); + if (nullptr == limple) + continue; + limple->SetSubDWeakPointer(level->m_face[0],subdimple_sp); + } +} + +void ON_SubDLimitMeshImpl::SetSubDWeakPointer( + const ON_SubDFace* subd_first_face, + std::shared_ptr& subdimple_sp +) +{ + for (;;) + { + const ON_SubDimple* subdimple = subdimple_sp.get(); + if (nullptr == subdimple) + break; + if (nullptr == subd_first_face) + break; + if (nullptr == m_first_fragment) + break; + if (m_first_fragment->m_face != subd_first_face) + break; + m_subdimple_wp = subdimple_sp; + return; + } + + // It's no longer clear that the subd used to create this limit mesh exist. + ClearFragmentFacePointers(true); +} + + ON_SubDimple::ON_SubDimple(const ON_SubDimple& src) + : RuntimeSerialNumber(++ON_SubDimple::Internal_RuntimeSerialNumberGenerator) { const bool bCopyComponentStatus = true; @@ -668,6 +714,12 @@ ON_SubDimple::ON_SubDimple(const ON_SubDimple& src) if ( src.m_active_level == src_level ) m_active_level = level; } + if (src.m_max_vertex_id > m_max_vertex_id) + m_max_vertex_id = src.m_max_vertex_id; + if (src.m_max_edge_id > m_max_edge_id) + m_max_edge_id = src.m_max_edge_id; + if (src.m_max_face_id > m_max_face_id) + m_max_face_id = src.m_max_face_id; } bool ON_SubDLevel::IsEmpty() const diff --git a/opennurbs_subd_data.cpp b/opennurbs_subd_data.cpp index a63e24b4..a941243e 100644 --- a/opennurbs_subd_data.cpp +++ b/opennurbs_subd_data.cpp @@ -33,6 +33,11 @@ const ON_SubDLevel ON_SubDLevel::Empty; // ON_SubDimple // + +ON_SubDimple::ON_SubDimple() + : RuntimeSerialNumber(++ON_SubDimple::Internal_RuntimeSerialNumberGenerator) +{} + ON_SubDimple::~ON_SubDimple() { Destroy(); @@ -720,31 +725,6 @@ ON_BoundingBox ON_SubDVertex::ControlNetBoundingBox() const } -ON_BoundingBox ON_SubDVertex::LimitSurfaceBoundingBox( - const ON_SubD& subd - ) const -{ - ON_BoundingBox bbox; - - for (;;) - { - const ON_SubDimple* dimple = subd.SubDimple(); - if (nullptr == dimple) - break; - const ON_SubDLevel* level = dimple->SubDLevel(m_level); - if ( nullptr == level ) - break; - ON_SubDSectorLimitPoint limit_point; - if (false == this->GetLimitPoint( - level->m_subdivision_type, Face(0),true,limit_point)) - break; - bbox.m_min = limit_point.m_limitP; - bbox.m_max = bbox.m_min; - break; - } - - return bbox; -} ON_BoundingBox ON_SubDEdge::ControlNetBoundingBox() const { @@ -759,50 +739,6 @@ ON_BoundingBox ON_SubDEdge::ControlNetBoundingBox() const return bbox; } - -ON_BoundingBox ON_SubDEdge::LimitSurfaceBoundingBox( - const ON_SubD& subd - ) const -{ - - ON_BoundingBox bbox; - - for (;;) - { - const ON_SubDFace* face = Face(0); - if ( nullptr == face ) - break; - - // TODO = restrict to just the edge - bbox = face->LimitSurfaceBoundingBox(subd); - break; - - //const ON_SubDimple* dimple = subd.SubDimple(); - //if (nullptr == dimple) - // break; - //const ON_SubDLevel* level = dimple->SubDLevel(m_level); - //if ( nullptr == level ) - // break; - //if (level->m_limit_mesh.IsEmpty()) - //{ - // unsigned int display_density = 4; - // if (m_level < display_density) - // display_density -= m_level; - // else - // display_density = 1; - // ON_SubDDisplayParameters display_parameters = ON_SubDDisplayParameters::CreateFromDisplayDensity(display_density); - // level->UpdateLimitSurfaceMesh(subd, display_parameters); - // if ( level->m_limit_mesh.IsEmpty() ) - // break; - //} - //bbox.m_min = limit_point.m_limitP; - //bbox.m_max = bbox.m_min; - //break; - } - - return bbox; -} - ON_BoundingBox ON_SubDFace::ControlNetBoundingBox() const { ON_BoundingBox bbox; @@ -832,41 +768,3 @@ ON_BoundingBox ON_SubDFace::ControlNetBoundingBox() const return bbox; } - -ON_BoundingBox ON_SubDFace::LimitSurfaceBoundingBox( - const ON_SubD& subd - ) const -{ - ON_BoundingBox bbox; - - for (;;) - { - const ON_SubDimple* dimple = subd.SubDimple(); - if (nullptr == dimple) - break; - const ON_SubDLevel* level = dimple->SubDLevel(m_level); - if ( nullptr == level ) - break; - if (level->m_limit_mesh.IsEmpty()) - { - unsigned int display_density = 4; - if (m_level < display_density) - display_density -= m_level; - else - display_density = 1; - ON_SubDDisplayParameters display_parameters = ON_SubDDisplayParameters::CreateFromDisplayDensity(display_density); - level->UpdateLimitSurfaceMesh(subd, display_parameters); - if ( level->m_limit_mesh.IsEmpty() ) - break; - } - for (const ON_SubDLimitMeshFragment* fragment = level->m_limit_mesh.FirstFragment(); nullptr != fragment; fragment = fragment->m_next_fragment) - { - if (this == fragment->m_face) - bbox.Union(fragment->m_bbox); - } - break; - } - - return bbox; - -} diff --git a/opennurbs_subd_data.h b/opennurbs_subd_data.h index d5c00e90..6dfd815f 100644 --- a/opennurbs_subd_data.h +++ b/opennurbs_subd_data.h @@ -48,6 +48,15 @@ #if defined(OPENNURBS_SUBD_WIP) +bool ON_SubDFaceRegionBreakpoint( + unsigned int level0_face_id, + const class ON_SubDComponentRegionIndex& region_index +); + +bool ON_SubDComponentRegionBreakpoint( + const class ON_SubDComponentRegion* component_region +); + ////////////////////////////////////////////////////////////////////////// // @@ -295,7 +304,7 @@ public: /* Description: - Get the limit surface for the entrie quad + Get the limit surface for the entire quad */ bool GetLimitSurfaceCV( //double srf_cv[4][4][3] @@ -314,37 +323,6 @@ public: double srf_cv[4][4][3] ); - /* - Parameters: - bEnableApproximatePatch - [in] - If true, an approximate patches may be generated. - This parameter should be true only when all permitted subdivisions have - been performed and a mininimum of 2 subdivisions have been performed. - This insures all faces are quads and each quad has at most one extraordinary - vertex and the approximate patches will have C2 continuity with any - neighboring exact patches. - srf_cv[5][5] - CVs for a uniform cubic NURBS patch. - If a patch cv srf_cv[j][j] does not exist or cannot be set, then all three - coordinates of srf_cv[j][j] are set to ON_UNSET_VALUE. - patch_type - [out] - patch_type[0] - the type of cubic bispan for the 4x4 grid (srf_cv[0][0] ... srf_cv[3][3]) - patch_type[1] - the type of cubic bispan for the 4x4 grid (srf_cv[1][0] ... srf_cv[4][3]) - patch_type[2] - the type of cubic bispan for the 4x4 grid (srf_cv[1][1] ... srf_cv[4][4]) - patch_type[3] - the type of cubic bispan for the 4x4 grid (srf_cv[0][1] ... srf_cv[3][4]) - Returns: - 0 - 4: - Number of bispans set in srf_cv[5][5] - */ - unsigned int GetLimitSubSurfaceMultiPatchCV( - bool bEnableApproximatePatch, - double srf_cv[5][5][3], - ON_SubDLimitNurbsFragment::BispanType patch_type[4] - ); private: unsigned int SetLimitSubSurfaceExactCVs( @@ -500,12 +478,17 @@ void ON_SubDIncrementErrorCount(); // defined in opennurbs_subd.cpp // // m_saved_points_flags // -#define ON_SUBD_CACHE_TYPE_MASK (0x1FU) +//#define ON_SUBD_CACHE_TYPE_MASK (0x0FU) +//#define ON_SUBD_CACHE_POINT_FLAG_MASK (0x20U) +//#define ON_SUBD_CACHE_DISPLACEMENT_FLAG_MASK (0x40U) +//#define ON_SUBD_CACHE_LIMIT_FLAG_MASK (0x80U) +#define ON_SUBD_CACHE_TYPE_MASK ON_SubDComponentBase::SavedPointsFlags::SubDTypeMask -#define ON_SUBD_CACHE_POINT_FLAG_MASK (0x20U) -#define ON_SUBD_CACHE_DISPLACEMENT_FLAG_MASK (0x40U) -#define ON_SUBD_CACHE_LIMIT_FLAG_MASK (0x80U) -#define ON_SUBD_CACHE_FLAGS_MASK (ON_SUBD_CACHE_POINT_FLAG_MASK|ON_SUBD_CACHE_DISPLACEMENT_FLAG_MASK|ON_SUBD_CACHE_LIMIT_FLAG_MASK) +#define ON_SUBD_CACHE_POINT_FLAG_MASK ON_SubDComponentBase::SavedPointsFlags::SubdivisionPointIsSet +#define ON_SUBD_CACHE_DISPLACEMENT_FLAG_MASK ON_SubDComponentBase::SavedPointsFlags::DisplacementVectorIsSet +#define ON_SUBD_CACHE_LIMIT_FLAG_MASK ON_SubDComponentBase::SavedPointsFlags::LimitPointIsSet + +#define ON_SUBD_CACHE_FLAGS_MASK ON_SubDComponentBase::SavedPointsFlags::CachedPointMask #define ON_SUBD_CACHE_TYPE(cache_subd_flags) (ON_SUBD_CACHE_TYPE_MASK&(cache_subd_flags)) #define ON_SUBD_CACHE_FLAGS(cache_subd_flags) (ON_SUBD_CACHE_FLAGS_MASK&(cache_subd_flags)) @@ -572,6 +555,9 @@ public: unsigned int DumpTopology( + const unsigned int validate_max_vertex_id, + const unsigned int validate_max_edge_id, + const unsigned int validate_max_face_id, ON_2udex vertex_id_range, ON_2udex edge_id_range, ON_2udex face_id_range, @@ -585,7 +571,7 @@ private: public: unsigned int m_level_index = ON_UNSET_UINT_INDEX; - ON_SubD::SubDType m_subdivision_type = ON_SubD::SubDType::Unset; + ON_SubD::SubDType m_subdivision_type = ON_SubD::SubDType::QuadCatmullClark; unsigned char m_ordinary_vertex_valence = 0; // 0 = none, 4 = quads, 6 = triangles unsigned char m_ordinary_face_edge_count = 0; // 0 = none, 4 = quads, 3 = triangles @@ -1089,12 +1075,6 @@ public: unsigned int EdgeFlags() const; - ON_SubDLimitMesh UpdateLimitSurfaceMesh( - const ON_SubD& subd, - const class ON_SubDDisplayParameters& display_parameters - ) const; - - unsigned int LimitSurfaceMeshFragmentCount() const; private: mutable ON_SubDAggregates m_aggregates; @@ -1190,6 +1170,13 @@ public: unsigned int face_id ) const; + unsigned int MaximumVertexId() const; + unsigned int MaximumEdgeId() const; + unsigned int MaximumFaceId() const; + + bool IsValid() const; + void ResetId(); + private: @@ -1304,14 +1291,20 @@ private: ON_SubDEdge* m_unused_edge = nullptr; ON_SubDFace* m_unused_face = nullptr; - ON__UINT_PTR* AllocateOversizedElementQWERTY( + unsigned int m_max_fspv_id = 0; + unsigned int m_max_fspe_id = 0; + unsigned int m_max_fspf_id = 0; + + unsigned int m_reserved = 0; + + ON__UINT_PTR* AllocateOversizedElement( size_t* capacity ); void ReturnOversizedElement( size_t capacity, ON__UINT_PTR* a ); - static size_t OversizedElementCapacityQWERTY( + static size_t OversizedElementCapacity( size_t count ); @@ -1328,35 +1321,41 @@ private: class ON_SubDimple { +private: + static std::atomic Internal_RuntimeSerialNumberGenerator; + +public: + /* + Returns: + A runtime serial number identifying this ON_SubDimple instance. + Remarks: + ON_SubD is a shared pointer to an implementation. As such, there can + be multiple ON_SubD instances that reference the same implementation. + The runtime serial number uniquely identifies a particular instance + of an implementation. + The empty subd has runtime serial number = 0. + */ + const ON__UINT64 RuntimeSerialNumber; + #if defined(ON_SUBD_CENSUS) ON_SubDImpleCensusCounter m_census_counter; #endif public: - ON_SubDimple() = default; + ON_SubDimple(); ~ON_SubDimple(); ON_SubDimple(const ON_SubDimple&); + void SetLimitMeshSubDWeakPointer( + std::shared_ptr& subdimple_sp + ); + + bool IsValid( const ON_SubD& subd, bool bSilentError, ON_TextLog* ) const; - //unsigned int MaximumVertexId() const - //{ - // return m_max_vertex_id; - //} - - //unsigned int MaximumEdgeId() const - //{ - // return m_max_edge_id; - //} - - //unsigned int MaximumFaceId() const - //{ - // return m_max_face_id; - //} - bool Write( ON_BinaryArchive& archive ) const; @@ -1688,6 +1687,18 @@ private: ON_SubDLevel* m_active_level = nullptr; // m_active_level = nullptr or m_active_level = m_levels[m_active_level->m_level_index]. public: + unsigned int MaximumVertexId() const + { + return m_max_vertex_id; + } + unsigned int MaximumEdgeId() const + { + return m_max_edge_id; + } + unsigned int MaximumFaceId() const + { + return m_max_face_id; + } /* Returns: Active level @@ -1783,275 +1794,6 @@ private: -////////////////////////////////////////////////////////////////////////// -// -// ON_SubDQuadFaceMesher -// - -class ON_SubDQuadFacePatcher -{ -public: - ON_SubDLimitNurbsFragment m_patch_fragment = ON_SubDLimitNurbsFragment::Empty; - unsigned int m_display_density = 0; - unsigned int m_patch_count = 0; - ON__UINT_PTR m_fragment_callback_context = 0; - bool (*m_fragment_callback_function)(ON__UINT_PTR, const class ON_SubDLimitNurbsFragment*) = nullptr; - bool m_bCallbackResult = false; - - bool Send4x4Patch( - unsigned int display_density, - const class ON_SubDFaceRegion& face_region, - ON_SubDLimitNurbsFragment::BispanType bispan_type - ); - - bool Send5x5Patch( - unsigned int display_density, - const class ON_SubDFaceRegion& face_region, - const ON_SubDLimitNurbsFragment::BispanType bispan_type[4] - ); -}; - -class ON_SubDQuadFaceMesher -{ -public: - ON_SubDQuadFaceMesher() = default; - - ~ON_SubDQuadFaceMesher() - { - if (nullptr != m__buffer) - { - delete[] m__buffer; - m__buffer = nullptr; - m__buffer_capacity = 0; - } - } - - enum class Output : unsigned int - { - unset = 0, - mesh = 1, - patches = 2 - }; - - ON_SubDQuadFaceMesher::Output m_output = ON_SubDQuadFaceMesher::Output::unset; - - // The m_display_density parameter determines the density of output. - unsigned int m_display_density = 0; - -private: - unsigned int m_reserved = 0; -public: - - ////////////////////////////////////////////////////////////////////////////////// - // - // SubD limit surface mesh output - // - // The mesh has m_count x m_count quads, where m_count = 2^m_mesh_density. - // There are (m_count+1)*(m_count+1) points in the mesh. - unsigned int m_count = 0; - unsigned int m_capacity = 0; - - - // Point(i,j) = (m_points + i*m_point_stride0 + j*m_point_stride1)[0,1,2] - size_t m_point_stride0 = 0; - size_t m_point_stride1 = 0; - double* m_points = nullptr; - - // Normal(i,j) = (m_normals + i*m_normal_stride0 + j*m_normal_stride1)[0,1,2] - size_t m_normal_stride0 = 0; - size_t m_normal_stride1 = 0; - double* m_normals = nullptr; - - ////////////////////////////////////////////////////////////////////////////////// - // - // SubD limit surface NURBS patch output - // - class ON_SubDQuadFacePatcher* m_patcher = nullptr; - - /* - Description: - Allocates memory this ON_SubDQuadFaceMesher manages (m_buffer) and - uses it for the m_points and m_normals arrays. - */ - bool SetDestinationToLocalMeshBuffer( - unsigned int mesh_density - ); - - /* - Description: - Set the first coordinate of every point to ON_UNSET_VALUE. - */ - bool UnsetMeshPoints(); - - /* - Description: - Uses memory on fragment as the destination and sets the m_points and m_normals - arrays on this to point to appropriate locations in fragment.m_P and fragment.m_N. - */ - bool SetDestinationToMeshFragment( - unsigned int mesh_density, - class ON_SubDLimitMeshFragment& mesh_fragment - ); - - bool SetDestinationToPatchFragment( - class ON_SubDQuadFacePatcher& patcher - ); - - bool GetLimitMesh( - const ON_SubDFaceRegion& face_region, - const ON_SubDFace* face - ); - - bool GetLimitPatches( - const ON_SubDFaceRegion& face_region, - const ON_SubDFace* face - ); - -private: - double* Internal_Buffer(size_t buffer_capacity); - - size_t m__buffer_capacity = 0; - double* m__buffer = nullptr; - - /* - Description: - Get values of cubic uniform B-spline basis functions times 6. - Parameters: - t - [in] 0.0 <= t <= 1.0 - Evaluation parameter - b - [out] - t^3, - (1 + 3t + 3t^2 - 3t^3) - (1 + 3(1-t) + 3(1-t)^2 - 3(1-t)^3) - (1-t)^3 - Remarks: - If C0, C1, C2, C3 are the control points for a span - of a uniform cubic B-spline and the domain of the - span is 0 <= t <= 1, then - C(t) = (b[0]*C0 + b[1]*C1 + b[2]*C2 + b[3]*C3)/6. - */ - static void Get6xCubicBasis( - double t, - double b6[4] - ); - - /* - Description: - Get values of derviatives of cubic uniform B-spline basis functions. - Parameters: - t - [in] 0.0 <= t <= 1.0 - Evaluation parameter - b - [out] - t^3, - (1 + 3t + 3t^2 - 3t^3) - (1 + 3(1-t) + 3(1-t)^2 - 3(1-t)^3) - (1-t)^3 - Remarks: - If C0, C1, C2, C3 are the control points for a span - of a uniform cubic B-spline and the domain of the - span is 0 <= t <= 1, then the first derivative is - C'(t) = (b[0]*C0 + b[1]*C1 + b[2]*C2 + b[3]*C3). - */ - static void GetCubicBasisDerivative( - double t, - double b[4] - ); - - /* - Description: - Calcululate a quad mesh from the bi-cubic uniform B-spline surface. - Parameters: - Returns: - true: - successful - false: - failure - */ - bool EvaluateSurface( - unsigned int count, - unsigned int point_i0, - unsigned int point_j0 - ) const; - - bool GetLimitSubMeshOrPatch( - const ON_SubDFaceRegion& face_region, - class ON_SubDQuadNeighborhood* qft, // may be destroyed - unsigned int display_density, - unsigned int point_i0, - unsigned int point_j0 - ); - -private: - bool Internal_EvaluateSurfaceNormalBackup1( - double s, - double t, - unsigned int count, - unsigned int i, - unsigned int j, - double* N - ) const; - bool Internal_EvaluateSurfaceNormalBackup2( - const double* P00, - unsigned int count, - unsigned int i, - unsigned int j, - double* N - ) const; - // Workspaces used by GetLimitSubMesh() - double m_srf_cv[4][4][3]; - ON_SubD_FixedSizeHeap m_fshx[5]; - - ON_SubD_FixedSizeHeap* CheckOutLocalFixedSizeHeap() - { - const size_t count = sizeof(m_fshx) / sizeof(m_fshx[0]); - for (size_t i = 0; i < count; i++) - { - if ( false == m_fshx[i].InUse() ) - return &m_fshx[i]; - } - return ON_SUBD_RETURN_ERROR(nullptr); - } - - bool ReturnLocalFixedSizeHeap(ON_SubD_FixedSizeHeap* fsh) - { - if ( nullptr == fsh) - return ON_SUBD_RETURN_ERROR(false); - - const size_t count = sizeof(m_fshx) / sizeof(m_fshx[0]); - for (size_t i = 0; i < count; i++) - { - if (fsh == &m_fshx[i]) - { - m_fshx[i].Reset(); - return true; - } - } - - return false; // not an error - } - - void ReturnAllFixedSizeHeaps() - { - const size_t count = sizeof(m_fshx) / sizeof(m_fshx[0]); - for (size_t i = 0; i < count; i++) - { - m_fshx[i].Reset(); - } - } - -private: - void SetDestinationInitialize( - ON_SubDQuadFaceMesher::Output output - ); - - -private: - // copies not supported - ON_SubDQuadFaceMesher(const ON_SubDQuadFaceMesher&) = delete; - ON_SubDQuadFaceMesher& operator=(const ON_SubDQuadFaceMesher&) = delete; -}; - - class ON_SubDArchiveIdMap { // used for file IO, copy construction, and operator= where the problem @@ -2227,6 +1969,14 @@ public: ON_SubDLimitMeshFragment* fragment ); + /* + After all fragments have been collected, it's necessary to seal + the edges of the fragments along shared subd edges because the + locations and normals are computed by evaluating different + sides. + */ + void SealEdges(); + const ON_RTree& FragmentTree() const; void ClearTree(); @@ -2253,7 +2003,14 @@ public: // ON_SubDimple. std::weak_ptr m_subdimple_wp; - void ClearFragmentFacePointers(); + void SetSubDWeakPointer( + const ON_SubDFace* subd_first_face, + std::shared_ptr& subdimple_sp + ); + + void ClearFragmentFacePointers( + bool bResetSubDWeakPtr + ); private: ON_RTree* m_fragment_tree = nullptr; diff --git a/opennurbs_subd_eval.cpp b/opennurbs_subd_eval.cpp index f7b3752d..c95dbc9c 100644 --- a/opennurbs_subd_eval.cpp +++ b/opennurbs_subd_eval.cpp @@ -716,7 +716,7 @@ static bool SetLimitPointSectorCheck( limit_point_sector_face = sit.IncrementToCrease(-1); unsigned int sector_face_count = 0; const ON_SubDFace* sector_face0 = sit.CurrentFace(); - for (const ON_SubDFace* sector_face = sector_face0; nullptr != sector_face && sector_face_count <= vertex_face_count; sector_face = sit.NextFace(true)) + for (const ON_SubDFace* sector_face = sector_face0; nullptr != sector_face && sector_face_count <= vertex_face_count; sector_face = sit.NextFace(ON_SubDSectorIterator::StopAt::AnyCrease)) { if (sector_face == sector_face0 && sector_face_count == vertex_face_count && vertex->IsSmoothOrDart()) { @@ -781,6 +781,11 @@ bool ON_SubDVertex::SetSavedLimitPoint( // set the point *lp = limit_point; + + // first limit point location wins + ON_SubDLimitMeshFragment::SealPoints(true, m_limit_point.m_limitP, lp->m_limitP); + + // it is expected that the limit normal and tangents will be substantually different. lp->m_next_sector_limit_point = nullptr; // append the point to the vertex's linked list. diff --git a/opennurbs_subd_fragment.cpp b/opennurbs_subd_fragment.cpp index a73b5b08..798b84af 100644 --- a/opennurbs_subd_fragment.cpp +++ b/opennurbs_subd_fragment.cpp @@ -411,16 +411,15 @@ const class ON_SubDEdgePtr ON_SubDLimitMeshFragment::EdgePtr( unsigned int grid_side_index ) const { - const int grid_side_count = 4; // will be 3 for tri grid - if (nullptr != m_face && grid_side_index < grid_side_count) + const unsigned int grid_side_count = 4; // will be 3 for a tri sub-D + if (nullptr != m_face && m_face->m_edge_count >= 3 && grid_side_index < grid_side_count) { unsigned short fei = m_face_vertex_index[grid_side_index]; if (fei < m_face->m_edge_count) return m_face->EdgePtr(fei); - grid_side_index = (grid_side_index+grid_side_count-1) % grid_side_count; - fei = m_face_vertex_index[grid_side_index]; + fei = m_face_vertex_index[(grid_side_index+1) % grid_side_count]; if (fei < m_face->m_edge_count) - return m_face->EdgePtr(fei); + return m_face->EdgePtr((fei+m_face->m_edge_count-1) % m_face->m_edge_count); } return ON_SubDEdgePtr::Null; } @@ -429,27 +428,159 @@ const class ON_SubDVertex* ON_SubDLimitMeshFragment::Vertex( unsigned int grid_corner_index ) const { - ON_SubDEdgePtr eptr = EdgePtr(grid_corner_index); - const ON_SubDEdge* edge = eptr.Edge(); - if ( nullptr != edge ) - return edge->m_vertex[eptr.EdgeDirection()]; + if (nullptr != m_face && m_face->m_edge_count >= 3 && grid_corner_index < 4) + { + const unsigned short fei = m_face_vertex_index[grid_corner_index]; + if (fei < m_face->m_edge_count) + return m_face->Vertex(fei); + } return nullptr; } -ON_3dPoint ON_SubDLimitMeshFragment::CornerPoint( +const ON_3dPoint ON_SubDLimitMeshFragment::CornerPoint( unsigned int grid_corner_index ) const { if ( grid_corner_index >= 4 || nullptr == m_P || m_P_stride <= 0 || nullptr == m_grid.m_S ) - return ON_3dPoint::UnsetPoint; + return ON_3dPoint::NanPoint; - //unsigned int i = grid_corner_index*m_grid.m_side_segment_count + 1; - unsigned int i = grid_corner_index*m_grid.m_side_segment_count; + const unsigned int i = m_grid.m_S[grid_corner_index * m_grid.m_side_segment_count]; return ON_3dPoint(m_P + (i*m_P_stride)); } +const ON_3dVector ON_SubDLimitMeshFragment::CornerNormal(unsigned int grid_corner_index) const +{ + if ( grid_corner_index >= 4 || nullptr == m_N || m_N_stride <= 0 || nullptr == m_grid.m_S ) + return ON_3dPoint::NanPoint; + + const unsigned int i = m_grid.m_S[grid_corner_index * m_grid.m_side_segment_count]; + return ON_3dVector(m_N + (i*m_N_stride)); +} + +const ON_3dPoint ON_SubDLimitMeshFragment::SidePoint(unsigned int grid_side_index) const +{ + if ( grid_side_index >= 4 || nullptr == m_P || m_P_stride <= 0 || nullptr == m_grid.m_S ) + return ON_3dPoint::NanPoint; + + const unsigned int i = grid_side_index*m_grid.m_side_segment_count + m_grid.m_side_segment_count/2; + + return ON_3dPoint(m_P + (i*m_P_stride)); +} + +const ON_3dVector ON_SubDLimitMeshFragment::SideNormal(unsigned int grid_side_index) const +{ + if ( grid_side_index >= 4 || nullptr == m_N || m_N_stride <= 0 || nullptr == m_grid.m_S ) + return ON_3dPoint::NanPoint; + + const unsigned int i = grid_side_index*m_grid.m_side_segment_count + m_grid.m_side_segment_count/2; + + return ON_3dVector(m_N + (i*m_N_stride)); +} + +const ON_3dPoint ON_SubDLimitMeshFragment::CenterPoint() const +{ + if ( nullptr == m_P || m_P_stride <= 0 || nullptr == m_grid.m_S ) + return ON_3dPoint::NanPoint; + + const unsigned int i = (m_grid.m_side_segment_count*(m_grid.m_side_segment_count + 2)) / 2; + + return ON_3dPoint(m_P + (i*m_P_stride)); +} + +const ON_3dVector ON_SubDLimitMeshFragment::CenterNormal() const +{ + if ( nullptr == m_N || m_N_stride <= 0 || nullptr == m_grid.m_S ) + return ON_3dPoint::NanPoint; + + const unsigned int i = (m_grid.m_side_segment_count*(m_grid.m_side_segment_count + 2)) / 2; + + return ON_3dVector(m_N + (i*m_N_stride)); +} + +bool ON_SubDLimitMeshFragment::Internal_GetFrameHelper( + unsigned int P_dex, + unsigned int Q_dex, + ON_Plane& frame +) const +{ + const unsigned int P_count = m_P_count; + if ( P_dex < P_count + && Q_dex < P_count + && nullptr != m_P + && m_P_stride > 0 + && nullptr != m_N + && m_N_stride > 0 + ) + { + const ON_3dVector Z(m_N + (P_dex*m_N_stride)); + const ON_3dPoint P(m_P + (P_dex*m_P_stride)); + const ON_3dPoint Q (m_P + (Q_dex*m_P_stride)); + ON_3dVector V = (Q - P).UnitVector(); + ON_3dVector X = (V - (frame.zaxis*V)*V).UnitVector(); + if (X.IsUnitVector()) + { + frame.origin = P; + frame.xaxis = X; + frame.yaxis = ON_CrossProduct(Z, X); + frame.zaxis = Z; + frame.UpdateEquation(); + } + else + { + frame = ON_Plane(P, Z); + } + return true; + } + return false; +} + + +bool ON_SubDLimitMeshFragment::GetCornerFrame( + unsigned int grid_corner_index, + ON_Plane& frame +) const +{ + if (grid_corner_index < 4 && m_grid.m_side_segment_count > 0 && nullptr != m_grid.m_S) + { + unsigned int S_dex = grid_corner_index * m_grid.m_side_segment_count; + if (Internal_GetFrameHelper(m_grid.m_S[S_dex], m_grid.m_S[S_dex + 1], frame)) + return true; + } + + frame = ON_Plane::NanPlane; + return false; +} + +bool ON_SubDLimitMeshFragment::GetSideFrame(unsigned int grid_side_index, ON_Plane & frame) const +{ + const unsigned int count = m_grid.m_side_segment_count; + if (grid_side_index < 4U && count > 0 && nullptr != m_grid.m_S) + { + const unsigned int S_dex = grid_side_index * count + count / 2U; + const unsigned int S_dex1 = (S_dex < (4U * count)) ? (S_dex + 1U) : (S_dex - 1U); + if (Internal_GetFrameHelper(m_grid.m_S[S_dex], m_grid.m_S[S_dex1], frame)) + return true; + } + + frame = ON_Plane::NanPlane; + return false; +} + +bool ON_SubDLimitMeshFragment::GetCenterFrame(ON_Plane & frame) const +{ + if (m_grid.m_side_segment_count > 0 && nullptr != m_grid.m_S) + { + const unsigned int n = m_grid.m_side_segment_count; + const unsigned int P_dex = (n * (n + 2))/2; // central point index + if (Internal_GetFrameHelper(P_dex, P_dex+1, frame)) + return true; + } + + frame = ON_Plane::NanPlane; + return false; +} const class ON_SubDVertexPtr ON_SubDLimitMeshFragment::VertexPtr( unsigned int grid_corner_index @@ -464,19 +595,31 @@ ON_ComponentStatus ON_SubDLimitMeshFragment::Status() const } -bool ON_SubDLimitMeshFragment::IsSubFragment() const +bool ON_SubDLimitMeshFragment::IsPartialFragment() const { - return ( nullptr != m_face && m_face_vertex_index[0] < ON_SubDFace::MaximumEdgeCount ); + return ( + nullptr != m_face + && m_face->m_edge_count < ON_SubDFace::MaximumEdgeCount + && m_face_vertex_index[0] >= ON_SubDFace::MaximumEdgeCount + && m_face_vertex_index[1] >= ON_SubDFace::MaximumEdgeCount + && m_face_vertex_index[2] < m_face->m_edge_count + && m_face_vertex_index[3] >= ON_SubDFace::MaximumEdgeCount + && m_grid.m_side_segment_count > 0 && nullptr != m_grid.m_S + ); } - bool ON_SubDLimitMeshFragment::IsCompleteFragment() const { + // For a complete fragment, + // m_face_vertex_index[i] = {0,1,2,3} for quads + // m_face_vertex_index[i] = {0,1,2,?} for tris return ( nullptr != m_face - && m_face_vertex_index[0] < ON_SubDFace::MaximumEdgeCount - && m_face_vertex_index[1] < ON_SubDFace::MaximumEdgeCount - && m_face_vertex_index[2] < ON_SubDFace::MaximumEdgeCount + && m_face->m_edge_count < ON_SubDFace::MaximumEdgeCount + && m_face_vertex_index[0] < m_face->m_edge_count + && m_face_vertex_index[1] < m_face->m_edge_count + && m_face_vertex_index[2] < m_face->m_edge_count + && m_grid.m_side_segment_count > 0 && nullptr != m_grid.m_S ); } @@ -603,13 +746,11 @@ ON_SubDLimitMeshFragmentGrid ON_SubDLimitMeshFragmentGrid::Quads( return *fragment_grid; } -#if defined(OPENNURBS_SLEEPLOCK_AVAILABLE) // The code below is thread safe and constructs the ON_SubDLimitMeshFragmentGrids // that are resources shared by all ON_SubDLimitMeshFragments. static ON_SleepLock lock; const bool bReturnLock = lock.GetLock(50,ON_SleepLock::OneMinute,true); -#endif // The ON_SubDLimitMeshFragmentGrid classes created below are created one time as needed @@ -710,10 +851,8 @@ ON_SubDLimitMeshFragmentGrid ON_SubDLimitMeshFragmentGrid::Quads( grid_cache[i] = first_lod; } -#if defined(OPENNURBS_SLEEPLOCK_AVAILABLE) if ( bReturnLock ) lock.ReturnLock(); -#endif if (vdex != vdex1) { @@ -736,10 +875,14 @@ ON_SubDLimitMeshFragmentGrid ON_SubDLimitMeshFragmentGrid::Quads( // ON_SubDLimitMesh // -void ON_SubDLimitMeshImpl::ClearFragmentFacePointers() +void ON_SubDLimitMeshImpl::ClearFragmentFacePointers( + bool bResetSubDWeakPtr +) { // When the ON_SubDimple that manages the faces referenced by // fragment->m_face is deleted, fragment->m_face must be set to zero. + if (bResetSubDWeakPtr) + m_subdimple_wp.reset(); if (nullptr != m_first_fragment && nullptr != m_first_fragment->m_face) { for (auto fragment = m_first_fragment; nullptr != fragment; fragment = fragment->m_next_fragment) @@ -898,6 +1041,324 @@ bool ON_SubDLimitMeshImpl::AddFinishedFragment( return true; } + +////class ON_SubDLimitMeshSealVertexInfo +////{ +////public: +//// ON_SubDLimitMeshSealVertexInfo() = default; +//// ~ON_SubDLimitMeshSealVertexInfo() = default; +//// ON_SubDLimitMeshSealVertexInfo(const ON_SubDLimitMeshSealVertexInfo&) = default; +//// ON_SubDLimitMeshSealVertexInfo& operator=(const ON_SubDLimitMeshSealVertexInfo&) = default; +//// +//// static const ON_SubDLimitMeshSealVertexInfo Unset; +//// +//// enum Bits : unsigned char +//// { +//// // Set if the vertex is smooth and normals should be sealed along with location. +//// SmoothVertex = 0x01, +//// +//// // Set if the edge is smooth and normals should be sealed along with location. +//// SmoothEdge = 0x02, +//// }; +//// +////public: +//// unsigned int m_vertex_id = 0; +//// unsigned int m_edge_id = 0; +//// unsigned char m_bits = 0; +//// double* m_P = nullptr; +//// double* m_N = nullptr; +////}; +////const ON_SubDLimitMeshSealVertexInfo ON_SubDLimitMeshSealVertexInfo::Unset ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_SubDLimitMeshSealVertexInfo); + +class ON_SubDLimitMeshSealEdgeInfo +{ +public: + ON_SubDLimitMeshSealEdgeInfo() = default; + ~ON_SubDLimitMeshSealEdgeInfo() = default; + ON_SubDLimitMeshSealEdgeInfo(const ON_SubDLimitMeshSealEdgeInfo&) = default; + ON_SubDLimitMeshSealEdgeInfo& operator=(const ON_SubDLimitMeshSealEdgeInfo&) = default; + + static const ON_SubDLimitMeshSealEdgeInfo Unset; + + enum Bits : unsigned char + { + // Set if edge orientation is opposite face boundary ccw orientation. + EdgeDir = 0x01, + + // Set if the edge is smooth and normals should be sealed along with location. + Smooth = 0x02, + + // The high bits are used for half edges so they will sort after full edges + FirstHalf = 0x40, + SecondHalf = 0x80, + HalfMask = 0xC0, + + // Used to ignore edge dir + IgnoreEdgeDirMask = 0xFE + }; + + bool SetEdge( + unsigned int grid_side_dex + ) + { + for (;;) + { + if (nullptr == m_fragment || nullptr == m_fragment->m_face) + break; + + m_face_edge_count = m_fragment->m_face->m_edge_count; + if (m_face_edge_count < 3 || m_face_edge_count > ON_SubDFace::MaximumEdgeCount) + break; // bogus face + + const ON_SubDEdgePtr eptr = m_fragment->EdgePtr(grid_side_dex); + const ON_SubDEdge* edge = eptr.Edge(); + m_edge_id = (nullptr != edge && edge->m_face_count >= 2) ? edge->m_id : 0; + if (0 == m_edge_id) + break; // nothing to seal + + const bool bCompleteFragment = m_fragment->IsCompleteFragment(); + const bool bPartialFragment + = (false == bCompleteFragment) + && m_fragment->m_face_vertex_index[0] > ON_SubDFace::MaximumEdgeCount + && m_fragment->m_face_vertex_index[1] > ON_SubDFace::MaximumEdgeCount + && m_fragment->m_face_vertex_index[2] < ON_SubDFace::MaximumEdgeCount + && m_fragment->m_face_vertex_index[3] > ON_SubDFace::MaximumEdgeCount + ; + + if (false == bCompleteFragment && false == bPartialFragment) + { + ON_SUBD_ERROR("Unexpected m_face_vertex[] falues in partial fragment."); + break; + } + + m_bits = 0; + const ON__UINT_PTR edge_dir = eptr.EdgeDirection(); + if (bPartialFragment) + { + // The high bit is used for partial fragments so they will sort after full fragments. + if ( 1 == grid_side_dex ) + m_bits |= (0==edge_dir) ? Bits::SecondHalf : Bits::FirstHalf; + else if ( 2 == grid_side_dex ) + m_bits |= (0==edge_dir) ? Bits::FirstHalf : Bits::SecondHalf; + else + { + // this is an inteior edge of a partial fragment and it + // is alwasy sealed with its neighbor when it is created. + break; + } + } + if (0 != edge_dir) + m_bits |= Bits::EdgeDir; + if (edge->IsSmooth()) + m_bits |= Bits::Smooth; + + m_grid_side_dex = (unsigned char)grid_side_dex; // 0,1,2, or 3 + + return true; + } + + + m_edge_id = 0; + m_bits = 0; + m_grid_side_dex = 0; + m_face_edge_count = 0; + return false; + } + + static bool Seal( + const ON_SubDLimitMeshSealEdgeInfo& src, + const ON_SubDLimitMeshSealEdgeInfo& dst + ); + + + static int CompareEdgeIdBitsFaceId( + const ON_SubDLimitMeshSealEdgeInfo* lhs, + const ON_SubDLimitMeshSealEdgeInfo* rhs + ) + { + // sort by edge id + if (lhs->m_edge_id < rhs->m_edge_id) + return -1; + if (lhs->m_edge_id > rhs->m_edge_id) + return 1; + + // then sort by bits with full edges before half edges + // Critical to ignore the EdgeDir bit because we need + // the identical sections of edges to be sorted together. + // When an edge has 2 N-gons (N != 4) attached, we need + // the pairs for the first half and 2nd half sorted toether + const unsigned char lhs_bits = (lhs->m_bits & ON_SubDLimitMeshSealEdgeInfo::Bits::IgnoreEdgeDirMask); + const unsigned char rhs_bits = (rhs->m_bits & ON_SubDLimitMeshSealEdgeInfo::Bits::IgnoreEdgeDirMask); + if (lhs_bits < rhs_bits) + return -1; + if (lhs_bits > rhs_bits) + return 1; + + // then sort by face id + unsigned int lhs_face_id = (nullptr != lhs->m_fragment->m_face) ? lhs->m_fragment->m_face->m_id : 0xFFFFFFFF; + unsigned int rhs_face_id = (nullptr != rhs->m_fragment->m_face) ? rhs->m_fragment->m_face->m_id : 0xFFFFFFFF; + if (0 == lhs_face_id) + lhs_face_id = 0xFFFFFFFE; + if (0 == rhs_face_id) + rhs_face_id = 0xFFFFFFFE; + if (lhs_face_id < rhs_face_id) + return -1; + if (lhs_face_id > rhs_face_id) + return 1; + + return 0; + } + +public: + unsigned int m_edge_id = 0; + + // m_bits is bitwise or of ON_SubDLimitMeshSealEdgeInfo::Bits enum values + unsigned char m_bits = 0; + unsigned char m_grid_side_dex = 0; // 0,1,2,or 3 + unsigned short m_face_edge_count = 0; + + ON_SubDLimitMeshFragment* m_fragment = nullptr; +}; + +const ON_SubDLimitMeshSealEdgeInfo ON_SubDLimitMeshSealEdgeInfo::Unset ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_SubDLimitMeshSealEdgeInfo); + +bool ON_SubDLimitMeshSealEdgeInfo::Seal( + const ON_SubDLimitMeshSealEdgeInfo& src, + const ON_SubDLimitMeshSealEdgeInfo& dst +) +{ + if (src.m_edge_id != dst.m_edge_id || 0 == src.m_edge_id) + return false; + if (nullptr == src.m_fragment || nullptr == dst.m_fragment) + return false; + const bool bSealNormals = (0 != (src.m_bits & ON_SubDLimitMeshSealEdgeInfo::Bits::Smooth)); + const unsigned char src_half = (src.m_bits & ON_SubDLimitMeshSealEdgeInfo::Bits::HalfMask); + const unsigned char dst_half = (dst.m_bits & ON_SubDLimitMeshSealEdgeInfo::Bits::HalfMask); + unsigned int src_side_segment_count = src.m_fragment->m_grid.m_side_segment_count; + unsigned int dst_side_segment_count = dst.m_fragment->m_grid.m_side_segment_count; + unsigned int i0 = src.m_grid_side_dex*src_side_segment_count; + unsigned int i1 = i0 + src_side_segment_count; + + ////unsigned int vid[2] = {}; + ////const ON_SubDEdge* e = src.m_fragment->Edge(src.m_grid_side_dex); + ////if (nullptr != e ) + ////{ + //// const ON_SubDVertex* v = src.m_fragment->Vertex(src.m_grid_side_dex); + //// if ( nullptr != v ) + //// vid[0] = v->m_id; + //// v = src.m_fragment->Vertex((src.m_grid_side_dex+1)%4); + //// if ( nullptr !=v ) + //// vid[1] = v->m_id; + ////} + + // src_dir = 0 if SubD edge and fragment side have compatible natural orientations + const unsigned char src_dir = (src.m_bits&ON_SubDLimitMeshSealEdgeInfo::Bits::EdgeDir); + + if (src_half != dst_half || src_side_segment_count != dst_side_segment_count) + { + if ( + 0 == src_half + && 4 == src.m_face_edge_count + && 4 != dst.m_face_edge_count + && 2 * dst_side_segment_count == src_side_segment_count) + { + // The face for src_half is a quad and the face for dest_half is an N-gon with N != 3, + // and src_fragment is a full sized fragment and dst_fragment is a half sized fragment. + if (ON_SubDLimitMeshSealEdgeInfo::Bits::FirstHalf == dst_half) + { + // only copy half of src_fragment side + if (0 == src_dir) + { + i1 -= dst_side_segment_count; // copy first half of src_framgent side + ////vid[1] = 0; + } + else + { + i0 += dst_side_segment_count; // copy 2nd half of src_framgent side + ////vid[0] = 0; + } + } + else if (ON_SubDLimitMeshSealEdgeInfo::Bits::SecondHalf == dst_half) + { + // only copy half of src_fragment side + if (0 == src_dir) + { + i0 += dst_side_segment_count; // copy 2nd half of src_framgent side + ////vid[0] = 0; + } + else + { + i1 -= dst_side_segment_count; // copy first half of src_framgent side + ////vid[1] = 0; + } + } + else + { + // bug in this code or the code that sets the m_bits information. + ON_SUBD_ERROR("unexpected dst_half"); + return false; + } + } + else + { + // Either the parent subd is invalid or information + // set in the fragments, or the sorting in ON_SubDLimitMeshImpl::SealEdges() + // is not valid (or some other bug). + ON_SUBD_ERROR("unexpected sealing fragment portions"); + return false; + } + } + + // seal this edge + const bool bSameDirection = (src_dir == (dst.m_bits&ON_SubDLimitMeshSealEdgeInfo::Bits::EdgeDir)); + const unsigned int j0 = dst.m_grid_side_dex*dst_side_segment_count + (bSameDirection?0:dst_side_segment_count); + const unsigned int j1 = bSameDirection ? (j0+dst_side_segment_count) : (j0-dst_side_segment_count); + ON_SubDLimitMeshFragment::SealAdjacentSides( + true, // bTestNearEqual, + bSealNormals, + *src.m_fragment, + i0, + i1, + *dst.m_fragment, + j0, + j1 + ); + return true; +} + + +void ON_SubDLimitMeshImpl::SealEdges() +{ + ON_SimpleArray fe_list(m_fragment_count); + ON_SubDLimitMeshSealEdgeInfo fe; + for (fe.m_fragment = m_first_fragment; nullptr != fe.m_fragment; fe.m_fragment = fe.m_fragment->m_next_fragment) + { + if (nullptr == fe.m_fragment->m_face) + continue; + for (unsigned int grid_side_dex = 0; grid_side_dex < 4; grid_side_dex++) + { + if ( fe.SetEdge(grid_side_dex) ) + fe_list.Append(fe); + } + } + + fe_list.QuickSort(ON_SubDLimitMeshSealEdgeInfo::CompareEdgeIdBitsFaceId); + const unsigned int fe_list_count = fe_list.UnsignedCount(); + unsigned int i0 = 0; + while ( i0 < fe_list_count ) + { + fe = fe_list[i0]; + const unsigned char src_half_mask = (fe.m_bits & ON_SubDLimitMeshSealEdgeInfo::Bits::HalfMask); + for ( i0++; i0 < fe_list_count && fe.m_edge_id == fe_list[i0].m_edge_id; i0++ ) + { + if (0 != src_half_mask && 0 == (src_half_mask & fe_list[i0].m_bits)) + break; // necessary when all faces attached to an edge are not quads. + ON_SubDLimitMeshSealEdgeInfo::Seal(fe, fe_list[i0]); + } + } +} + + unsigned int ON_SubDLimitMeshImpl::Internal_NextContentSerialNumber() { static unsigned int serial_number = 0; @@ -1001,393 +1462,6 @@ bool ON_SubDLimitMeshImpl::GetTightBoundingBox( return true; } -class /*DO NOT EXPORT*/ON_SubDLimitMeshImpl_CallbackContext -{ -public: -#if defined(OPENNURBS_SLEEPLOCK_AVAILABLE) - bool m_bUseMultipleThreads = false; // If true, callback uses the lock - ON_SleepLock m_lock; -#endif - ON_SubDLimitMeshImpl* m_impl = nullptr; - ON_SimpleArray< ON_SubDLimitMeshFragment* > m_face_mesh_fragments; - - static bool FragmentCallbackFunction( - ON__UINT_PTR impl_context_as_void, - const class ON_SubDLimitMeshFragment* fragment - ); - - /* - Description: - compares the face id and then the fragment index. - Parameters: - a - [in] - b - [in] - The caller insures that a, b, a->m_face and b->m_face are not nullptr. - */ - static int CompareFragmentIndex( - ON_SubDLimitMeshFragment*const* a, - ON_SubDLimitMeshFragment*const* b - ); - - /* - Description: - Once all the mesh fragments for a face are delivered and sorted, - this function is called to make coincident vertices have identical - locations and normals. - - Parameters: - count - [in] - Number of elements in mesh_fragments[]. This should be the same - number as sub_fragments[i].m_face_fragment_count. - mesh_fragments - [in] - Every element in the array should have the same ON_SubDLimitMeshFragment.m_face->m_id - */ - static bool ProcessSortedFragments( - unsigned int count, - ON_SubDLimitMeshFragment** mesh_fragments - ); - - static bool CoincidentSideCopy( - ON_SubDLimitMeshFragment* prev_fragment, - ON_SubDLimitMeshFragment* fragment - ); -}; - -bool ON_SubDLimitMeshImpl_CallbackContext::FragmentCallbackFunction( - ON__UINT_PTR callback_context_as_void, - const class ON_SubDLimitMeshFragment* callback_fragment - ) -{ - bool bContinueMeshCalculation = true; - - ON_SubDLimitMeshImpl_CallbackContext* context = (ON_SubDLimitMeshImpl_CallbackContext*)callback_context_as_void; - -#if defined(OPENNURBS_SLEEPLOCK_AVAILABLE) - bool bReleaseLock = false; - if (context->m_bUseMultipleThreads) - { - if (false == context->m_lock.GetLock(0, ON_SleepLock::OneSecond)) - { - // return true to keep going, but something is really hogging the lock - return ON_SUBD_RETURN_ERROR(bContinueMeshCalculation); - } - } -#endif - - for (;;) - { - ON_SubDLimitMeshFragment* impl_managed_fragment = context->m_impl->CopyCallbackFragment(callback_fragment); - if (nullptr == impl_managed_fragment) - { - ON_SubDIncrementErrorCount(); - bContinueMeshCalculation = false; // terminate meshing - break; - } - - const unsigned int face_fragment_count = impl_managed_fragment->m_face_fragment_count; - - const unsigned int face_id - = (nullptr == impl_managed_fragment->m_face) - ? 0 - : impl_managed_fragment->m_face->m_id; - - - if ( 0 == face_id || ON_UNSET_UINT_INDEX == face_id - || face_fragment_count <= 1 - || impl_managed_fragment->m_face_fragment_index >= face_fragment_count - ) - { - // simple case where no additional processing is reqired - context->m_impl->AddFinishedFragment(impl_managed_fragment); - break; - } - - - unsigned int count = context->m_face_mesh_fragments.UnsignedCount(); - - if (count + 1 < face_fragment_count) - { - // waiting on more fragments - context->m_face_mesh_fragments.Append(impl_managed_fragment); - break; - } - - ON_SubDLimitMeshFragment** mesh_fragments = context->m_face_mesh_fragments.Array(); - unsigned int delivered_mesh_fragment_count = 0; - unsigned int i0 = ON_UNSET_UINT_INDEX; - for (unsigned int i = 0; i < count; i++) - { - if ( face_id != mesh_fragments[i]->m_face->m_id) - continue; - if ( 0 == delivered_mesh_fragment_count++) - i0 = i; - if ( delivered_mesh_fragment_count + 1 == face_fragment_count ) - break; - } - - if (delivered_mesh_fragment_count + 1 < face_fragment_count) - { - // waiting on more fragments - context->m_face_mesh_fragments.Append(impl_managed_fragment); - break; - } - - // copy the fragments we need to process to local storage I can release the lock. - ON_SimpleArray< ON_SubDLimitMeshFragment* > local_mesh_fragments(face_fragment_count); - if (delivered_mesh_fragment_count == count) - { - local_mesh_fragments.Append(count,mesh_fragments); - context->m_face_mesh_fragments.SetCount(0); - } - else - { - local_mesh_fragments.Append(mesh_fragments[i0]); - unsigned int count1 = 0; - for (unsigned int i = i0+1; i < count; i++) - { - if (face_id != mesh_fragments[i]->m_face->m_id) - { - mesh_fragments[count1++] = mesh_fragments[i]; - continue; - } - local_mesh_fragments.Append(mesh_fragments[i]); - } - context->m_face_mesh_fragments.SetCount(count1); - } - -#if defined(OPENNURBS_SLEEPLOCK_AVAILABLE) - if (bReleaseLock) - { - // don't keep lock during processing - context->m_lock.ReturnLock(); - bReleaseLock = false; - } -#endif - - local_mesh_fragments.Append(impl_managed_fragment); - local_mesh_fragments.QuickSort(ON_SubDLimitMeshImpl_CallbackContext::CompareFragmentIndex); - count = local_mesh_fragments.UnsignedCount(); - mesh_fragments = local_mesh_fragments.Array(); - - if ( count == face_fragment_count) - ON_SubDLimitMeshImpl_CallbackContext::ProcessSortedFragments(count,mesh_fragments); - else - { - // there is a bug in the code above - ON_SubDIncrementErrorCount(); - } - -#if defined(OPENNURBS_SLEEPLOCK_AVAILABLE) - // If needed, get a lock before adding the processed fragments to - // the linked list. - if (context->m_bUseMultipleThreads) - { - if (false == context->m_lock.GetLock(0, ON_SleepLock::OneSecond)) - { - // return true to keep going, but something is really hogging the lock - return ON_SUBD_RETURN_ERROR(bContinueMeshCalculation); - } - } -#endif - - for ( unsigned int i = 0; i < count; i++ ) - context->m_impl->AddFinishedFragment(local_mesh_fragments[i]); - - break; - } - -#if defined(OPENNURBS_SLEEPLOCK_AVAILABLE) - if (bReleaseLock) - context->m_lock.ReturnLock(); -#endif - - return bContinueMeshCalculation; -} - - -int ON_SubDLimitMeshImpl_CallbackContext::CompareFragmentIndex( - ON_SubDLimitMeshFragment*const* a, - ON_SubDLimitMeshFragment*const* b - ) -{ - if ( a == b ) - return 0; - - // caller insures no nulls - //if ( nullptr == a ) - // return 1; - //if ( nullptr == b ) - // return -1; - - //unsigned int a_id = ( nullptr == (*a)->m_face ) ? ON_UNSET_UINT_INDEX : (*a)->m_face->m_id; - //unsigned int b_id = ( nullptr == (*b)->m_face ) ? ON_UNSET_UINT_INDEX : (*b)->m_face->m_id; - //if ( a_id < b_id ) - // return -1; - //if ( a_id > b_id ) - // return 1; - - unsigned int a_index = (*a)->m_face_fragment_index; - unsigned int b_index = (*b)->m_face_fragment_index; - if ( a_index < b_index ) - return -1; - if ( a_index > b_index ) - return 1; - - // identical values should never appear in separate entries. - return ON_SUBD_RETURN_ERROR(0); -} - - -bool ON_SubDLimitMeshImpl_CallbackContext::CoincidentSideCopy( - ON_SubDLimitMeshFragment* prev_fragment, - ON_SubDLimitMeshFragment* fragment - ) -{ - if ( prev_fragment->m_grid.m_side_segment_count != fragment->m_grid.m_side_segment_count) - return ON_SUBD_RETURN_ERROR(false); - - //const unsigned int side_segment_count = fragment->m_grid.m_side_segment_count; - //const unsigned int* S0 = prev_fragment->m_grid.m_S + 4*side_segment_count; - //const unsigned int* S1 = fragment->m_grid.m_S; - - //const double* P0 = prev_fragment->m_P; - //double* P1 = fragment->m_P; - //const double* N0 = prev_fragment->m_N; - //double* N1 = fragment->m_N; - - // const double* src; - // double* dst; - - - //for (const unsigned int* S1max = S1 + side_segment_count; S1 < S1max; S1++, S0--) - //{ - // src = P0 + P0_stride * *S0; - // dst = P1 + P1_stride * *S1; - // - // d = fabs(src[0] - dst[0]) + fabs(src[1] - dst[1]) + fabs(src[2] - dst[2]); - // if (!(d <= 1e-8)) - // return ON_SUBD_RETURN_ERROR(false); - - // *dst++ = *src++; - // *dst++ = *src++; - // *dst = *src; - - // src = N0 + N0_stride * *S0; - // dst = N1 + N1_stride * *S1; - - // d = fabs(src[0] - dst[0]) + fabs(src[1] - dst[1]) + fabs(src[2] - dst[2]); - // if (!(d <= 0.01)) - // return ON_SUBD_RETURN_ERROR(false); - - // *dst++ = *src++; - // *dst++ = *src++; - // *dst = *src; - //} - - const size_t side_point_count = 1U + (unsigned int)(fragment->m_grid.m_side_segment_count); - - const double* P0 = prev_fragment->m_P; - const double* N0 = prev_fragment->m_N; - const size_t P0_stride = side_point_count*prev_fragment->m_P_stride; - const size_t N0_stride = side_point_count*prev_fragment->m_N_stride; - - double* P1 = fragment->m_P; - double* N1 = fragment->m_N; - const size_t P1_stride = fragment->m_P_stride; - const size_t N1_stride = fragment->m_N_stride; - - double* P1max = P1 + side_point_count*P1_stride; - while ( P1 < P1max ) - { - double d = fabs(P0[0] - P1[0]) + fabs(P0[1] - P1[1]) + fabs(P0[2] - P1[2]); - if (!(d <= 1e-8)) - return ON_SUBD_RETURN_ERROR(false); - - P1[0] = P0[0]; - P1[1] = P0[1]; - P1[2] = P0[2]; - P0 += P0_stride; - P1 += P1_stride; - - d = fabs(N0[0] - N1[0]) + fabs(N0[1] - N1[1]) + fabs(N0[2] - N1[2]); - if (!(d <= 0.01)) - return ON_SUBD_RETURN_ERROR(false); - - N1[0] = N0[0]; - N1[1] = N0[1]; - N1[2] = N0[2]; - N0 += N0_stride; - N1 += N1_stride; - } - - return true; -} - - -bool ON_SubDLimitMeshImpl_CallbackContext::ProcessSortedFragments( - unsigned int count, - ON_SubDLimitMeshFragment** mesh_fragments - ) -{ - if ( count < 2 || nullptr == mesh_fragments) - return ON_SUBD_RETURN_ERROR(false); - if ( nullptr == mesh_fragments[0] - || nullptr == mesh_fragments[0]->m_face - || nullptr == mesh_fragments[0]->m_P - || nullptr == mesh_fragments[0]->m_N - || 0 != mesh_fragments[0]->m_face_fragment_index - || count != mesh_fragments[0]->m_face_fragment_count - ) - return ON_SUBD_RETURN_ERROR(false); - - const unsigned int face_id = mesh_fragments[0]->m_face->m_id; - const unsigned int grid_F_count = mesh_fragments[0]->m_grid.m_F_count; - const unsigned int grid_side_count = mesh_fragments[0]->m_grid.m_side_segment_count; - if ( 0 == face_id || ON_UNSET_UINT_INDEX == face_id ) - return ON_SUBD_RETURN_ERROR(false); - if ( 0 == grid_F_count || grid_side_count*grid_side_count != grid_F_count ) - return ON_SUBD_RETURN_ERROR(false); - - if ( nullptr == mesh_fragments[count-1] - || nullptr == mesh_fragments[count-1]->m_face - || nullptr == mesh_fragments[count-1]->m_P - || nullptr == mesh_fragments[count-1]->m_N - || (count-1) != mesh_fragments[count-1]->m_face_fragment_index - || count != mesh_fragments[count-1]->m_face_fragment_count - || face_id != mesh_fragments[count-1]->m_face->m_id - || grid_F_count != mesh_fragments[count-1]->m_grid.m_F_count - || grid_side_count != mesh_fragments[count-1]->m_grid.m_side_segment_count - ) - return ON_SUBD_RETURN_ERROR(false); - - - ON_SubDLimitMeshFragment* fragment = mesh_fragments[count-1]; - - for (unsigned int i = 0; i < count; i++) - { - ON_SubDLimitMeshFragment* prev_fragment = fragment; - fragment = mesh_fragments[i]; - if (i > 0) - { - if (nullptr == fragment - || nullptr == fragment->m_face - || face_id != fragment->m_face->m_id - || grid_F_count != fragment->m_grid.m_F_count - || grid_side_count != fragment->m_grid.m_side_segment_count - || i != fragment->m_face_fragment_index - || count != fragment->m_face_fragment_count - || nullptr == fragment->m_P - || nullptr == fragment->m_N - ) - return ON_SUBD_RETURN_ERROR(false); - } - - if ( false == ON_SubDLimitMeshImpl_CallbackContext::CoincidentSideCopy(prev_fragment,fragment) ) - return ON_SUBD_RETURN_ERROR(false); - } - return true; -} - #if defined(ON_HAS_RVALUEREF) ON_SubDLimitMesh::ON_SubDLimitMesh( ON_SubDLimitMesh&& src) ON_NOEXCEPT : m_impl_sp(std::move(src.m_impl_sp)) @@ -1440,6 +1514,152 @@ const ON_SubDLimitMeshFragment* ON_SubDLimitMesh::FirstFragment() const : nullptr; } +const ON_SubDLimitMeshFragment* ON_SubDLimitMesh::FaceFragment( + const class ON_SubDFace* face +) const +{ + if (nullptr == face) + return nullptr; + for (const ON_SubDLimitMeshFragment* fragment = FirstFragment(); nullptr != fragment; fragment = fragment->m_next_fragment) + { + if (face == fragment->m_face) + return fragment; + } + return nullptr; +} + +bool ON_SubDLimitMesh::GetFaceCenterPointAndNormal( + const class ON_SubDFace* face, + double* P, + double* N +) const +{ + if (nullptr != P) + { + P[0] = ON_DBL_QNAN; + P[1] = ON_DBL_QNAN; + P[2] = ON_DBL_QNAN; + } + if (nullptr != N) + { + N[0] = ON_DBL_QNAN; + N[1] = ON_DBL_QNAN; + N[2] = ON_DBL_QNAN; + } + const ON_SubDLimitMeshFragment* fragment = FaceFragment(face); + if (nullptr == fragment) + return false; + if (nullptr == fragment->m_P || nullptr == fragment->m_N) + return false; + const unsigned int n = fragment->m_grid.m_side_segment_count; + const unsigned int P_dex = fragment->IsCompleteFragment() ? (n*(n + 2) / 2) : 0; + if (P_dex >= (unsigned int)fragment->m_P_count) + return nullptr; + const double* fragment_P = fragment->m_P + (P_dex * fragment->m_P_stride); + const double* fragment_N = fragment->m_N + (P_dex * fragment->m_N_stride); + if (nullptr != P) + { + P[0] = fragment_P[0]; + P[1] = fragment_P[1]; + P[2] = fragment_P[2]; + } + if (nullptr != N) + { + N[0] = fragment_N[0]; + N[1] = fragment_N[1]; + N[2] = fragment_N[2]; + } + return true; +} + + +bool ON_SubDLimitMesh::GetEdgeCenterPointAndNormal( + const class ON_SubDEdge* edge, + unsigned int edge_face_index, + double* P, + double* N +) const +{ + if (nullptr != P) + { + P[0] = ON_DBL_QNAN; + P[1] = ON_DBL_QNAN; + P[2] = ON_DBL_QNAN; + } + if (nullptr != N) + { + N[0] = ON_DBL_QNAN; + N[1] = ON_DBL_QNAN; + N[2] = ON_DBL_QNAN; + } + if (nullptr == edge) + return false; + const ON_SubDFace* face = edge->Face(edge_face_index); + if (nullptr == face) + return false; + const unsigned int fei = face->EdgeArrayIndex(edge); + if (fei >= face->EdgeCount()) + return false; + + unsigned int P_dex = ON_UNSET_UINT_INDEX; + + bool bIsPartialFragment = false; + const ON_SubDLimitMeshFragment* fragment = nullptr; + for ( + fragment = FaceFragment(face); + nullptr != fragment && fragment->m_face == face; + fragment = bIsPartialFragment ? fragment->m_next_fragment : nullptr + ) + { + bIsPartialFragment = fragment->IsPartialFragment(); + for (unsigned int grid_side_index = 0; grid_side_index < 4; grid_side_index++) + { + const ON_SubDEdge* grid_side_edge = fragment->Edge(grid_side_index); + if (edge != grid_side_edge) + continue; + const unsigned int n = fragment->m_grid.m_side_segment_count; + if (n <= 0 || nullptr == fragment->m_grid.m_S) + break; + if (bIsPartialFragment) + { + const ON_SubDVertex* v = fragment->Vertex(grid_side_index); + if (nullptr != v) + { + const unsigned int evi = (0 == face->EdgeDirection(fei)) ? 0U : 1U; + if (v == edge->Vertex(evi)) + grid_side_index++; + P_dex = fragment->m_grid.m_S[grid_side_index*n]; + } + } + else if (fragment->IsCompleteFragment()) + { + P_dex = fragment->m_grid.m_S[grid_side_index*n + n / 2]; + } + break; + } + if (false == bIsPartialFragment || ON_UNSET_UINT_INDEX != P_dex) + break; + } + if (nullptr == fragment) + return false; + if (P_dex >= (unsigned int)fragment->m_P_count) + return false; + const double* fragment_P = fragment->m_P + (P_dex * fragment->m_P_stride); + const double* fragment_N = fragment->m_N + (P_dex * fragment->m_N_stride); + if (nullptr != P) + { + P[0] = fragment_P[0]; + P[1] = fragment_P[1]; + P[2] = fragment_P[2]; + } + if (nullptr != N) + { + N[0] = fragment_N[0]; + N[1] = fragment_N[1]; + N[2] = fragment_N[2]; + } + return true; +} bool ON_SubDLimitMesh::Update( bool bShareUpdate @@ -1529,16 +1749,6 @@ unsigned int ON_SubDLimitMesh::ContentSerialNumber() const return (nullptr != impl) ? impl->m_limit_mesh_content_serial_number : 0; } -ON_SubDLimitMesh* ON_SubDLimitMesh::Create( - const ON_SubD& subd, - const class ON_SubDDisplayParameters& limit_mesh_parameters, - ON_SubDLimitMesh* destination_mesh - ) -{ - ON_SubDFaceIterator fit = subd.FaceIterator(); - return ON_SubDLimitMesh::Create(fit,limit_mesh_parameters,destination_mesh); -} - ON_SubDLimitMesh& ON_SubDLimitMesh::CopyFrom( const ON_SubDLimitMesh& src ) @@ -1572,96 +1782,6 @@ void ON_SubDLimitMesh::Swap( } } -ON_SubDLimitMesh* ON_SubDLimitMesh::Create( - ON_SubDFaceIterator fit, - const class ON_SubDDisplayParameters& limit_mesh_parameters, - ON_SubDLimitMesh* destination_mesh - ) -{ - ON_SubDLimitMeshImpl* impl = 0; - std::shared_ptr< ON_SubDLimitMeshImpl > impl_sp; - - if (nullptr != destination_mesh) - { - destination_mesh->Clear(); - std::shared_ptr< ON_SubDLimitMeshImpl > dest_sp = destination_mesh->m_impl_sp; - if (1 == dest_sp.use_count()) - { - impl_sp.swap(dest_sp); - impl = impl_sp.get(); - } - } - - if ( limit_mesh_parameters.m_display_density > ON_SubDLimitMesh::MaximumDisplayDensity ) - return ON_SUBD_RETURN_ERROR(nullptr); - - const ON_SubD& subd = fit.SubD(); - - ON_SubD::FacetType facet_type = ON_SubD::FacetTypeFromSubDType(subd.ActiveLevelSubDType()); - - const unsigned int subd_fragment_count = fit.LimitSurfaceMeshFragmentCount(facet_type); - if ( 0 == subd_fragment_count ) - return ON_SUBD_RETURN_ERROR(nullptr); - - if (nullptr == impl) - { - impl = new(std::nothrow) ON_SubDLimitMeshImpl(); - if ( nullptr == impl) - return ON_SUBD_RETURN_ERROR(nullptr); - std::shared_ptr< ON_SubDLimitMeshImpl > new_impl_sp(impl); - impl_sp.swap(new_impl_sp); - } - - if (false == impl->ReserveCapacity(subd_fragment_count,facet_type,limit_mesh_parameters.m_display_density)) - return ON_SUBD_RETURN_ERROR(nullptr); - - ON_SubDLimitMeshImpl_CallbackContext callback_context; - -#if defined(OPENNURBS_SLEEPLOCK_AVAILABLE) - callback_context.m_bUseMultipleThreads = limit_mesh_parameters.m_bUseMultipleThreads; -#endif - - callback_context.m_impl = impl_sp.get(); - subd.GetLimitSurfaceMeshInFragments( - limit_mesh_parameters, - (ON__UINT_PTR)&callback_context, - ON_SubDLimitMeshImpl_CallbackContext::FragmentCallbackFunction - ); - - if (ON_Terminator::TerminationRequested(limit_mesh_parameters.m_terminator)) - { - return nullptr; // not an error - outside intervention canceled meshing - } - - if (impl->m_fragment_count < 1) - return ON_SUBD_RETURN_ERROR(nullptr); - - ON_SubDLimitMesh* limit_mesh - = (nullptr != destination_mesh) - ? destination_mesh - : new ON_SubDLimitMesh(); - - - // The next three lines are conceptually the same as - // impl_sp->m_subdimple_sp = fit.SubD().m_subdimple_sp; - // The three line approach is required because ON_SubD::m_subdimple_sp is private. - { - ON_SubD tmp_sub; - tmp_sub.ShareDimple(fit.SubD()); // tmp_sub.m_subdimple_sp = fit.SubD().m_subdimple_sp (increments fit.SubD().m_subdimple_sp ref count) - // NOTE: - // There are at least std::shared_ptr references to the ON_SubDimple (fit and tmp_subd), - // This insures the std::weak_ptr on impl_sp will be valid. - tmp_sub.SwapDimple(*impl_sp); // swap impl_sp->m_subdimple_wp and tmp_sub.m_subdimple_sp - } - - // Let limit_mesh manage the contents - limit_mesh->m_impl_sp.swap(impl_sp); - - // return the new mesh - return limit_mesh; -} - - ON_SubDDisplayParameters ON_SubDDisplayParameters::CreateFromDisplayDensity( unsigned int display_density ) diff --git a/opennurbs_subd_frommesh.cpp b/opennurbs_subd_frommesh.cpp index b719b09d..6a948dfb 100644 --- a/opennurbs_subd_frommesh.cpp +++ b/opennurbs_subd_frommesh.cpp @@ -117,6 +117,9 @@ ON_SubD* ON_SubD::CreateFromMesh( ON_Workspace ws; + if (nullptr == from_mesh_options) + from_mesh_options = &ON_SubDFromMeshOptions::Smooth; + ON_3dPointListRef mesh_points(level_zero_mesh); const unsigned int mesh_point_count = mesh_points.PointCount(); if (mesh_point_count < 3) @@ -729,5 +732,106 @@ ON_SubD* ON_SubD::CreateFromMesh( : ON_SubD::DefaultSubDType(); new_subd->SetSubDType(subd_type); + /* + // Vid[] + // Vid[] has mesh_point_count values. + // Vid[i] = Vid[j] if and only if mesh->m_V[i] and mesh->m_V[j] are coincident. + // Values in Vid[] run from 0 to VidCount-1. + // There are VidCount unique locations. + // Vindex[] is a permutation of (0, ..., mesh_point_count-1) + // 0 == Vid[Vindex[0]] <= ... <= Vid[Vindex[mesh_point_count-1]] = VidCount-1. + + + //const bool bConcaveCornerTest + // = nullptr != crease_parameters + // && crease_parameters->ConcaveCornerTestIsEnabled(); + + //const double min_cos_concave_corner_angle + // = bConcaveCornerTest + // ? (crease_parameters->MaximumConcaveCornerAngleRadians() < ON_PI ? cos(crease_parameters->MaximumConcaveCornerAngleRadians()) : -2.0) + // : 2.0; + */ + + if (ON_SubDFromMeshOptions::ConvexCornerOption::AtMeshCorner == from_mesh_options->ConvexCornerTest()) + { + // Add corners + ON_SubDVertexIterator vit(*new_subd); + ON_SubDEdge* e[2]; + const ON_SubDFace* f; + const ON_SubDVertex* v[4]; + ON_3dPoint P[4]; + ON_3dVector T[4]; + ON_3dVector N[4]; + double NoN[4]; + const double a = from_mesh_options->MaximumConvexCornerAngleRadians(); + if (a > 0.0 && a < ON_PI) + { + const double NoNtol = 0.2588190451; // sin(15 degrees) + const double min_cos_corner_angle = cos(a); + for (ON_SubDVertex* vertex = const_cast(vit.FirstVertex()); nullptr != vertex; vertex = const_cast(vit.NextVertex())) + { + if (ON_SubD::VertexTag::Crease != vertex->m_vertex_tag) + continue; + if (2 != vertex->m_edge_count) + continue; + e[0] = ON_SUBD_EDGE_POINTER(vertex->m_edges[0].m_ptr); + e[1] = ON_SUBD_EDGE_POINTER(vertex->m_edges[1].m_ptr); + if (nullptr == e[0] || 1 != e[0]->m_face_count || ON_SubD::EdgeTag::Crease != e[0]->m_edge_tag) + continue; + if (nullptr == e[1] || 1 != e[1]->m_face_count || ON_SubD::EdgeTag::Crease != e[1]->m_edge_tag) + continue; + f = ON_SUBD_FACE_POINTER(e[0]->m_face2[0].m_ptr); + if (nullptr == f) + continue; + if (f != ON_SUBD_FACE_POINTER(e[1]->m_face2[0].m_ptr)) + continue; + + const unsigned int vi = f->VertexIndex(vertex); + if (vi >= 4) + continue; + ON_SubDEdgePtr eptr[2]; + if (e[0] == f->Edge(vi)) + { + eptr[0] = ON_SubDEdgePtr::Create(e[0], vertex == e[0]->m_vertex[1] ? 1 : 0); + eptr[1] = ON_SubDEdgePtr::Create(e[1], vertex == e[1]->m_vertex[1] ? 1 : 0); + } + else if (e[1] == f->Edge(vi)) + { + eptr[1] = ON_SubDEdgePtr::Create(e[0], vertex == e[0]->m_vertex[1] ? 1 : 0); + eptr[0] = ON_SubDEdgePtr::Create(e[1], vertex == e[1]->m_vertex[1] ? 1 : 0); + } + const double corner_angle_radians = ON_SubDSectorType::CornerSectorAngleRadiansFromEdges(eptr[0], eptr[1]); + if (!(corner_angle_radians > 0.0 && corner_angle_radians < ON_PI)) + continue; + + // ocnvex quad restriction - for now + if (4 != f->m_edge_count) + continue; + v[0] = vertex; + v[1] = f->Vertex((vi + 1) % 4); + v[2] = f->Vertex((vi + 2) % 4); + v[3] = f->Vertex((vi + 3) % 4); + if (nullptr == v[0] || nullptr == v[1] || nullptr == v[2] || nullptr == v[3]) + continue; + for (int i = 0; i < 4; i++) + P[i] = ON_3dPoint(v[i]->m_P); + for (int i = 0; i < 4; i++) + T[i] = P[(i + 1) % 4] - P[i]; + for (int i = 0; i < 4; i++) + N[i] = -ON_CrossProduct(T[i], T[(i + 3) % 4]).UnitVector(); + for (int i = 0; i < 4; i++) + NoN[i] = N[i] * N[(i + 1) % 4]; + if (false == (NoN[0] >= NoNtol && NoN[1] >= NoNtol && NoN[2] >= NoNtol && NoN[3] >= NoNtol)) + continue; + const double cos_corner_angle = ON_CrossProduct(T[0], T[3]).Length(); + if (false == (cos_corner_angle >= min_cos_corner_angle)) + continue; + vertex->m_vertex_tag = ON_SubD::VertexTag::Corner; + } + } + } + + new_subd->UpdateEdgeSectorCoefficients(false); + return new_subd; } diff --git a/opennurbs_subd_heap.cpp b/opennurbs_subd_heap.cpp index c5c52288..47d31a1a 100644 --- a/opennurbs_subd_heap.cpp +++ b/opennurbs_subd_heap.cpp @@ -935,7 +935,38 @@ const class ON_SubDFace* ON_SubDHeap::FaceFromId( return face; } -size_t ON_SubDHeap::OversizedElementCapacityQWERTY(size_t count) +unsigned int ON_SubDHeap::MaximumVertexId() const +{ + return m_fspv.MaximumElementId(ON_SubDHeap::m_offset_vertex_id); +} + +unsigned int ON_SubDHeap::MaximumEdgeId() const +{ + return m_fspe.MaximumElementId(ON_SubDHeap::m_offset_edge_id); +} + +unsigned int ON_SubDHeap::MaximumFaceId() const +{ + return m_fspf.MaximumElementId(ON_SubDHeap::m_offset_face_id); +} + + +bool ON_SubDHeap::IsValid() const +{ + return m_fspv.ElementIdIsIncreasing(ON_SubDHeap::m_offset_vertex_id) + && m_fspe.ElementIdIsIncreasing(ON_SubDHeap::m_offset_edge_id) + && m_fspf.ElementIdIsIncreasing(ON_SubDHeap::m_offset_face_id); +} + +void ON_SubDHeap::ResetId() +{ + const unsigned int first_id = 1; + m_fspv.ResetElementId(ON_SubDHeap::m_offset_vertex_id,first_id); + m_fspe.ResetElementId(ON_SubDHeap::m_offset_edge_id,first_id); + m_fspf.ResetElementId(ON_SubDHeap::m_offset_face_id,first_id); +} + +size_t ON_SubDHeap::OversizedElementCapacity(size_t count) { size_t capacity = 32 * (count / 32); if (count % 32 > 0 || 0 == count) @@ -943,10 +974,10 @@ size_t ON_SubDHeap::OversizedElementCapacityQWERTY(size_t count) return capacity; } -ON__UINT_PTR* ON_SubDHeap::AllocateOversizedElementQWERTY(size_t* capacity) +ON__UINT_PTR* ON_SubDHeap::AllocateOversizedElement(size_t* capacity) { class tagWSItem* p; - size_t actual_capacity = ON_SubDHeap::OversizedElementCapacityQWERTY(*capacity); + size_t actual_capacity = ON_SubDHeap::OversizedElementCapacity(*capacity); size_t sz = (actual_capacity + 1)*sizeof(ON__UINT_PTR); sz += sizeof(*p); p = (class tagWSItem*)onmalloc(sz); @@ -1325,7 +1356,7 @@ ON__UINT_PTR* ON_SubDHeap::AllocateArray(size_t* capacity) return a; } - return AllocateOversizedElementQWERTY(capacity); + return AllocateOversizedElement(capacity); } void ON_SubDHeap::ReturnArray( diff --git a/opennurbs_subd_iter.cpp b/opennurbs_subd_iter.cpp index d551f9b8..f5b094fa 100644 --- a/opennurbs_subd_iter.cpp +++ b/opennurbs_subd_iter.cpp @@ -1097,22 +1097,22 @@ const ON_SubDVertex* ON_SubDSectorIterator::CurrentEdgeRingVertex( } const ON_SubDFace* ON_SubDSectorIterator::NextFace( - bool bStopAtCrease + ON_SubDSectorIterator::StopAt stop_at ) { - return IncrementFace(1, bStopAtCrease); + return IncrementFace(1, stop_at); } const ON_SubDFace* ON_SubDSectorIterator::PrevFace( - bool bStopAtCrease + ON_SubDSectorIterator::StopAt stop_at ) { - return IncrementFace(-1, bStopAtCrease); + return IncrementFace(-1, stop_at); } const ON_SubDFace* ON_SubDSectorIterator::IncrementFace( int increment_direction, - bool bStopAtCrease + ON_SubDSectorIterator::StopAt stop_at ) { if (nullptr == m_current_face) @@ -1133,12 +1133,23 @@ const ON_SubDFace* ON_SubDSectorIterator::IncrementFace( for (;;) { if (edge->m_face_count != 2) + { + // set stop_at to something not equal to Boundary + stop_at = ON_SubDSectorIterator::StopAt::AnyCrease; break; + } - if (ON_SubD::EdgeTag::Crease == edge->m_edge_tag && bStopAtCrease) - break; - - bStopAtCrease = false; + if (ON_SubDSectorIterator::StopAt::Boundary != stop_at) + { + if (ON_SubD::EdgeTag::Crease == edge->m_edge_tag) + { + if (ON_SubDSectorIterator::StopAt::AnyCrease == stop_at) + break; + if (ON_SubDSectorIterator::StopAt::HardCrease == stop_at && edge->IsHardCrease()) + break; + } + stop_at = ON_SubDSectorIterator::StopAt::Boundary; + } unsigned int efi; if (m_current_face == ON_SUBD_FACE_POINTER(edge->m_face2[0].m_ptr)) @@ -1238,7 +1249,7 @@ const ON_SubDFace* ON_SubDSectorIterator::IncrementFace( m_current_fei[0] = 0; m_current_fei[1] = 0; - if (bStopAtCrease) + if (ON_SubDSectorIterator::StopAt::Boundary != stop_at) { // termination at a crease, nonmanifold edge, or edge with one face m_current_eptr[1 - side_index] = m_current_eptr[side_index]; @@ -1282,7 +1293,7 @@ const ON_SubDFace* ON_SubDSectorIterator::IncrementToCrease( return CurrentFace(); } - const ON_SubDFace* face = sit.IncrementFace(increment_direction,true); + const ON_SubDFace* face = sit.IncrementFace(increment_direction,ON_SubDSectorIterator::StopAt::AnyCrease); if (nullptr == face) return ON_SUBD_RETURN_ERROR(nullptr); if ( face == face0 ) diff --git a/opennurbs_subd_limit.cpp b/opennurbs_subd_limit.cpp index bc3c6642..f8ed8107 100644 --- a/opennurbs_subd_limit.cpp +++ b/opennurbs_subd_limit.cpp @@ -164,7 +164,7 @@ bool ON_SubDQuadNeighborhood::IsValid() const if (m_bBoundaryCrease[fi]) { // A tag of ON_SubD::EdgeTag::X is an error here - if ( false == quad_edge[fi]->IsCrease(false) ) + if ( false == quad_edge[fi]->IsCrease() ) return ON_SUBD_RETURN_ERROR(false); if ( ON_UNSET_UINT_INDEX == quad_edge[fi]->FaceArrayIndex(quad_face) ) @@ -190,7 +190,7 @@ bool ON_SubDQuadNeighborhood::IsValid() const if ( 2 != quad_edge[fi]->m_face_count) return ON_SUBD_RETURN_ERROR(false); - if (quad_edge[fi]->IsCrease(false)) + if (quad_edge[fi]->IsCrease()) { unsigned int dart_count = 0; unsigned int crease_count = 0; @@ -216,7 +216,7 @@ bool ON_SubDQuadNeighborhood::IsValid() const } else { - if (false == quad_edge[fi]->IsSmooth(true)) + if (false == quad_edge[fi]->IsSmooth()) return ON_SUBD_RETURN_ERROR(false); } const ON_SubDFace* edge_faces[2] = { ON_SUBD_FACE_POINTER(quad_edge[fi]->m_face2[0].m_ptr), ON_SUBD_FACE_POINTER(quad_edge[fi]->m_face2[1].m_ptr) }; @@ -275,12 +275,12 @@ bool ON_SubDQuadNeighborhood::IsValid() const return ON_SUBD_RETURN_ERROR(false); if (2 == e->m_face_count) { - if (e->IsSmooth(true)) + if (e->IsSmooth()) continue; - if (e->IsCrease(false) && ON_SubD::VertexTag::Dart == quad_vertex[fi]->m_vertex_tag) + if (e->IsCrease() && ON_SubD::VertexTag::Dart == quad_vertex[fi]->m_vertex_tag) continue; } - if (false == e->IsCrease(false)) + if (false == e->IsCrease()) return ON_SUBD_RETURN_ERROR(false); bCornerShouldExist = false; break; @@ -455,7 +455,8 @@ static bool IsOrdinarySmoothQuadCornerVertex( const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(corner_vertex->m_edges[vei].m_ptr); if ( nullptr == e ) return ON_SUBD_RETURN_ERROR(false); - if ( false == e->IsSmooth(false) ) + // Test for exact tag here - do not call e->IsSmooth() because this is a rare case where X tags need to be rejected. + if ( false == e->IsSmoothNotX() ) return false; if ( 2 != e->m_face_count) return ON_SUBD_RETURN_ERROR(false); @@ -656,16 +657,16 @@ void ON_SubDQuadNeighborhood::SetPatchStatus( const unsigned int fvi3 = (fvi0+3)%4; bool bCenterEdgeIsSmooth[4] = {}; - bCenterEdgeIsSmooth[fvi0] = m_center_edges[fvi0]->IsSmooth(false); - bCenterEdgeIsSmooth[fvi1] = (0 == delta_subdivision_level) ? m_center_edges[fvi1]->IsSmooth(false) : true; - bCenterEdgeIsSmooth[fvi2] = (0 == delta_subdivision_level) ? m_center_edges[fvi2]->IsSmooth(false) : true; - bCenterEdgeIsSmooth[fvi3] = m_center_edges[fvi3]->IsSmooth(false); + bCenterEdgeIsSmooth[fvi0] = m_center_edges[fvi0]->IsSmoothNotX(); + bCenterEdgeIsSmooth[fvi1] = (0 == delta_subdivision_level) ? m_center_edges[fvi1]->IsSmoothNotX() : true; + bCenterEdgeIsSmooth[fvi2] = (0 == delta_subdivision_level) ? m_center_edges[fvi2]->IsSmoothNotX() : true; + bCenterEdgeIsSmooth[fvi3] = m_center_edges[fvi3]->IsSmoothNotX(); bool bCenterEdgeIsCrease[4] = {}; - bCenterEdgeIsCrease[fvi0] = bCenterEdgeIsSmooth[fvi0] ? false : m_center_edges[fvi0]->IsCrease(false); - bCenterEdgeIsCrease[fvi1] = (0 == delta_subdivision_level) ? m_center_edges[fvi1]->IsCrease(false) : false; - bCenterEdgeIsCrease[fvi2] = (0 == delta_subdivision_level) ? m_center_edges[fvi2]->IsCrease(false) : false; - bCenterEdgeIsCrease[fvi3] = bCenterEdgeIsSmooth[fvi3] ? false : m_center_edges[fvi3]->IsCrease(false); + bCenterEdgeIsCrease[fvi0] = bCenterEdgeIsSmooth[fvi0] ? false : m_center_edges[fvi0]->IsCrease(); + bCenterEdgeIsCrease[fvi1] = (0 == delta_subdivision_level) ? m_center_edges[fvi1]->IsCrease() : false; + bCenterEdgeIsCrease[fvi2] = (0 == delta_subdivision_level) ? m_center_edges[fvi2]->IsCrease() : false; + bCenterEdgeIsCrease[fvi3] = bCenterEdgeIsSmooth[fvi3] ? false : m_center_edges[fvi3]->IsCrease(); bool bEdgeTagX = false; for (unsigned int i = 0; i < 4; i++) @@ -738,7 +739,7 @@ void ON_SubDQuadNeighborhood::SetPatchStatus( if (quad_vertex[corner_index]->IsCrease()) { const ON_SubDEdge* e = m_edge_grid[(corner_index + 3) % 4][1]; - if (nullptr != e && e->IsCrease(false)) + if (nullptr != e && e->IsCrease()) { const ON_SubDFace* f = SideFace((corner_index + 3) % 4); if (nullptr != f && 4 == f->m_edge_count) @@ -753,7 +754,7 @@ void ON_SubDQuadNeighborhood::SetPatchStatus( if (quad_vertex[corner_index]->IsCrease()) { const ON_SubDEdge* e = m_edge_grid[corner_index][0]; - if (nullptr != e && e->IsCrease(false)) + if (nullptr != e && e->IsCrease()) { const ON_SubDFace* f = SideFace(corner_index); if (nullptr != f && 4 == f->m_edge_count) @@ -774,7 +775,7 @@ void ON_SubDQuadNeighborhood::SetPatchStatus( { const ON_SubDEdge* e1 = m_edge_grid[(corner_index + 1) % 4][0]; const ON_SubDEdge* e2 = m_edge_grid[(corner_index + 2) % 4][1]; - if (nullptr != e1 && nullptr != e2 && e1->IsCrease(false) && e2->IsCrease(false)) + if (nullptr != e1 && nullptr != e2 && e1->IsCrease() && e2->IsCrease()) { if (delta_subdivision_level > 0) { @@ -865,7 +866,7 @@ bool ON_SubDQuadNeighborhood::Set( if (nullptr == edge->m_vertex[1]) return ON_SUBD_RETURN_ERROR(false); - bIsCreaseEdge[qfei] = edge->IsCrease(false); + bIsCreaseEdge[qfei] = edge->IsCrease(); m_center_edges[qfei] = edge; @@ -933,7 +934,7 @@ bool ON_SubDQuadNeighborhood::Set( { if (nullptr != qf_nbr_face[qfei]) { - if (qf_nbr_face[qfei] != sit0.PrevFace(false)) + if (qf_nbr_face[qfei] != sit0.PrevFace(ON_SubDSectorIterator::StopAt::Boundary)) return ON_SUBD_RETURN_ERROR(false); eptr = sit0.CurrentEdgePtr(0).m_ptr; e[0] = ON_SUBD_EDGE_POINTER(eptr); @@ -945,7 +946,12 @@ bool ON_SubDQuadNeighborhood::Set( if (b4EdgedCorner) { - corner_faces[qfei] = sit0.PrevFace(false == bIsDartVertex[qfei]); + ON_SubDSectorIterator::StopAt stop_at + = bIsDartVertex[qfei] + ? ON_SubDSectorIterator::StopAt::Boundary + : ON_SubDSectorIterator::StopAt::AnyCrease + ; + corner_faces[qfei] = sit0.PrevFace(stop_at); if (nullptr != corner_faces[qfei]) { eptr = sit0.CurrentEdgePtr(0).m_ptr; @@ -962,7 +968,7 @@ bool ON_SubDQuadNeighborhood::Set( if (nullptr == e[1] && nullptr != qf_nbr_face[qfei3]) { - if (qf_nbr_face[qfei3] != sit1.NextFace(false)) + if (qf_nbr_face[qfei3] != sit1.NextFace(ON_SubDSectorIterator::StopAt::Boundary)) return ON_SUBD_RETURN_ERROR(false); eptr = sit1.CurrentEdgePtr(1).m_ptr; e[1] = ON_SUBD_EDGE_POINTER(eptr); @@ -973,7 +979,12 @@ bool ON_SubDQuadNeighborhood::Set( return ON_SUBD_RETURN_ERROR(false); if (b4EdgedCorner && nullptr == corner_faces[qfei]) { - corner_faces[qfei] = sit1.NextFace(false == bIsDartVertex[qfei3]); + ON_SubDSectorIterator::StopAt stop_at + = bIsDartVertex[qfei3] + ? ON_SubDSectorIterator::StopAt::Boundary + : ON_SubDSectorIterator::StopAt::AnyCrease + ; + corner_faces[qfei] = sit1.NextFace(stop_at); if (nullptr != corner_faces[qfei] && nullptr == e[0] ) { eptr = sit1.CurrentEdgePtr(1).m_ptr; @@ -1356,7 +1367,7 @@ unsigned int ON_SubDQuadNeighborhood::SetLimitSubSurfaceExactCVs( P1 = &m_srf_cv1[dex.i][dex.j][0]; if ( !ON_IsValid(P1[0]) ) { - bool bEvaluateCrease = m_bBoundaryCrease[side_fvi] || m_center_edges[side_fvi]->IsCrease(false); + bool bEvaluateCrease = m_bBoundaryCrease[side_fvi] || m_center_edges[side_fvi]->IsCrease(); if (bEvaluateCrease) { ON_2dex Adex = ON_SubDQuadNeighborhood::GridDex(5, side_fvi, 1, 1); @@ -1374,7 +1385,7 @@ unsigned int ON_SubDQuadNeighborhood::SetLimitSubSurfaceExactCVs( Bdex.j += deltadex.j; } } - else if ( m_center_edges[side_fvi]->IsSmooth(false) ) + else if ( m_center_edges[side_fvi]->IsSmoothNotX() ) { const ON_SubDEdge* edge = m_edge_grid[side_fvi][0]; if (nullptr == edge) @@ -1689,458 +1700,7 @@ bool ON_SubDQuadNeighborhood::Internal_GetApproximateCV( return bHaveApproximateCV; } -static bool CheckCV(const double* P) -{ - if ( ((const ON_3dPoint*)P)->IsValid() ) - return true; - ON_SUBD_ERROR("bogus cv"); - return false; -} -static bool Internal_InterpCV( - double srf_cv[5][5][3], - const ON_2udex srf_unset_cv_dex, - const ON_2udex srf_patchcv00_dex, - ON_NurbsSurface& tmp, - const ON_SubDSectorLimitPoint* limit_point - ) -{ - if (nullptr == limit_point || false == limit_point->IsSet() ) - return false; - const ON_3dPoint LP(limit_point->m_limitP); - if (false == LP.IsValid()) - { - ON_SUBD_ERROR("limit_point->m_limitP is not valid."); - return false; - } - - double* P1 = srf_cv[srf_unset_cv_dex.i][srf_unset_cv_dex.j]; - if (!(ON_UNSET_VALUE == P1[0])) - { - ON_SUBD_ERROR("srf_unset_cv_dex parameter does not index an unset cv"); - return false; - } - - const ON_2udex patch_unset_cv_dex(srf_unset_cv_dex.i - srf_patchcv00_dex.i, srf_unset_cv_dex.j - srf_patchcv00_dex.j); - if (0 != patch_unset_cv_dex.i && 3 != patch_unset_cv_dex.i) - { - ON_SUBD_ERROR("Unable to correctly set patch_unset_cv_dex.i"); - return false; - } - if (0 != patch_unset_cv_dex.j && 3 != patch_unset_cv_dex.j) - { - ON_SUBD_ERROR("Unable to correctly set patch_unset_cv_dex.j"); - return false; - } - - tmp.m_cv = srf_cv[srf_patchcv00_dex.i][srf_patchcv00_dex.j]; - if (tmp.CV(patch_unset_cv_dex.i, patch_unset_cv_dex.j) != P1) - { - // There is a bug in this function or in input parameters. - ON_SUBD_ERROR("Unable to correctly set tmp.m_cv."); - tmp.m_cv = nullptr; - return false; - } - P1[0] = 0.0; - P1[1] = 0.0; - P1[2] = 0.0; - const double s = (0 == patch_unset_cv_dex.i) ? 0.0 : 1.0; - const double t = (0 == patch_unset_cv_dex.j) ? 0.0 : 1.0; - const ON_3dPoint A = tmp.PointAt(s,t); - tmp.m_cv = nullptr; - if (false == A.IsValid()) - { - ON_SUBD_ERROR("tmp.PointAt(s,t) failed."); - P1[0] = ON_UNSET_VALUE; - P1[1] = ON_UNSET_VALUE; - P1[2] = ON_UNSET_VALUE; - return false; - } - - // If C = unset cv, the tensor product B-spline coefficient of C is (1/6)*(1/6) = 1/36 - // and A + 1/36*C = limitP. So, C = 36*(LP - A). - const ON_3dPoint C = 36.0*(LP - A); - if (false == C.IsValid()) - { - ON_SUBD_ERROR("36*(LP - A).is not valid."); - P1[0] = ON_UNSET_VALUE; - P1[1] = ON_UNSET_VALUE; - P1[2] = ON_UNSET_VALUE; - return false; - } - P1[0] = C.x; - P1[1] = C.y; - P1[2] = C.z; - - return true; -} - -unsigned int ON_SubDQuadNeighborhood::GetLimitSubSurfaceMultiPatchCV( - bool bEnableApproximatePatch, - double srf_cv[5][5][3], - ON_SubDLimitNurbsFragment::BispanType patch_type[4] - ) -{ - // Each "patch" is a 4x4 grid of CVs and srf_cc are the cvs for a cubic NURBS with 2 x2 spans (5x5 cvs) - // Patch 0 has cvs with indices [0][0] to [3][3]. - // Patch 1 has cvs with indices [1][0] to [4][3]. - // Patch 2 has cvs with indices [1][1] to [4][4]. - // Patch 3 has cvs with indices [0][1] to [3][4]. - // this->m_srf_cv1[5][5][3] are the CVS we could set from SubD control NET. - // Unset CVS have coordinates with values ON_UNSET_VALUE - // This function fills in unset cvs, returns them in srf_cv[5][5][3], - // and sets patch_type[N] to indicate if all the CVs for that patch are set. - // It returns the total number of set patches. - - const ON_2udex patch_cv00[4] = { ON_2udex(0,0),ON_2udex(1,0),ON_2udex(1,1),ON_2udex(0,1) }; - - if (nullptr != patch_type) - { - patch_type[0] = ON_SubDLimitNurbsFragment::BispanType::None; - patch_type[1] = ON_SubDLimitNurbsFragment::BispanType::None; - patch_type[2] = ON_SubDLimitNurbsFragment::BispanType::None; - patch_type[3] = ON_SubDLimitNurbsFragment::BispanType::None; - } - unsigned int quadrant_count = SetLimitSubSurfaceExactCVs(4U); - if ( quadrant_count <= 0 ) - return ON_SUBD_RETURN_ERROR(0); - - unsigned int patch_exact_cv_count[4] = {}; - unsigned int patch_approx_cv_count[4] = {}; - - double* P1 = srf_cv[0][0]; - const double* src = m_srf_cv1[0][0]; - unsigned int exact_cv_count = 0; - unsigned int approx_cv_count = 0; - - // Here "approximate" means the cv valud is not set in m_srf_cv1[][][] and - // the corresponding srf_cv[][][] value was set in this function. - // It also means the resulting cubic patches using that cv are close to but - // generally not exactly equal to the SubD surface location. - // The need for approximation happens because we have an exceptional SubD vertex on the central face. - // Recall that the SubD surface at the limit point of an exceptional vertex is typically G1 but not G2 - // and that a bicubic NURBS surface with simple interior knots is G2. Hence, the bicubic NURBS surface - // in this exceptional case has to be an approximation. When there are no exceptional vertices, - // the bicubic NURBS surface is exactly equal to the SubD surface - // (that's one of the selling points of Catmull-Clark SubD). - // - unsigned char srf_cv_status[5][5] = {}; // 0 = unset, 1 = set, 2 = approximate from subD control poly, 3 = approximate from interpolation - - - // This loop counts the number of set cvs for each of the 4 patches and copies the cv locations - // from the input srv cv array this->m_srf_cv1[][][] to the output cv array srf_cv[][][]. - for (unsigned int i = 0; i < 5U; i++) - { - for (unsigned int j = 0; j < 5U; j++, P1 += 3) - { - // patch_count = number of patches this cv is used by (1 patch for the m_srf_cv1[][][] grid corners up to 4 patches for the central 3x3 grid). - unsigned int active_patch_count = 0; - // indices of patches this cv is active on - unsigned int active_patch_index[4] = {}; - for (unsigned int patch_dex = 0; patch_dex < 4; patch_dex++) - { - if ( - i >= patch_cv00[patch_dex].i && i < patch_cv00[patch_dex].i+4 - && j >= patch_cv00[patch_dex].j && j < patch_cv00[patch_dex].j+4 - ) - active_patch_index[active_patch_count++] = patch_dex; - } - if (ON_UNSET_VALUE != src[0]) - { - // input cv at this->m_srf_cv1[i][j][] is set - copy it. - srf_cv_status[i][j] = 1; - P1[0] = *src++; - P1[1] = *src++; - P1[2] = *src++; - CheckCV(P1); - exact_cv_count++; - for ( unsigned int k = 0; k < active_patch_count; k++ ) - patch_exact_cv_count[active_patch_index[k]]++; - } - else - { - // input cv at this->m_srf_cv1[i][j][] is not set - // output srf_cv[i][j][] = (ON_UNSET_VALUE,ON_UNSET_VALUE,ON_UNSET_VALUE) - P1[0] = ON_UNSET_VALUE; - P1[1] = ON_UNSET_VALUE; - P1[2] = ON_UNSET_VALUE; - src += 3; - if (bEnableApproximatePatch) - { - // see if we can set the srf_cv[i][j][] using the SubD neighborhood edges or faces - // This works for patches that are far enough away from exceptional SubD vertices. - if (Internal_GetApproximateCV(i, j, ON_UNSET_VALUE, P1)) - { - srf_cv_status[i][j] = 2; - CheckCV(P1); - approx_cv_count++; - for ( unsigned int k = 0; k < active_patch_count; k++ ) - patch_approx_cv_count[active_patch_index[k]]++; - } - } - } - } - } - - ON_SubDLimitNurbsFragment::BispanType pt[4] = - { - ON_SubDLimitNurbsFragment::BispanType::None, - ON_SubDLimitNurbsFragment::BispanType::None, - ON_SubDLimitNurbsFragment::BispanType::None, - ON_SubDLimitNurbsFragment::BispanType::None - }; - - unsigned int exact_quadrant_count = 0; - unsigned int approx_quadrant_count = 0; - unsigned int interp_quadrant_count = 0; - for (unsigned int patch_dex = 0; patch_dex < 4; patch_dex++) - { - const unsigned int patch_set_cv_count = patch_exact_cv_count[patch_dex] + patch_approx_cv_count[patch_dex]; - if (16 != patch_set_cv_count) - { - if (patch_set_cv_count > 16) - { - ON_SUBD_ERROR("Bug in patch set cv counter during step 1."); - } - continue; - } - - if (16 == patch_exact_cv_count[patch_dex] ) - { - pt[patch_dex] = ON_SubDLimitNurbsFragment::BispanType::Exact; - exact_quadrant_count++; - } - else - { - // No interpolation is required to approximate the patch bispan for this quadrant. - pt[patch_dex] = ON_SubDLimitNurbsFragment::BispanType::Approximate; - approx_quadrant_count++; - } - } - - if (quadrant_count != exact_quadrant_count) - { - ON_SUBD_ERROR("exact_quadrant_count != SetLimitSubSurfaceExactCVs()"); - } - - if (bEnableApproximatePatch && (exact_quadrant_count + approx_quadrant_count) < 4 ) - { - // See if we can interpolate limit points to set some more srf_cv[][][] locations. - double knot[6] = { -2.0, -1.0, 0.0, 1.0, 2.0, 3.0 }; - ON_NurbsSurface tmp; - tmp.m_dim = 3; - tmp.m_is_rat = 0; - tmp.m_order[0] = 4; - tmp.m_order[1] = 4; - tmp.m_cv_count[0] = 4; - tmp.m_cv_count[1] = 4; - tmp.m_knot[0] = knot; - tmp.m_knot[1] = knot; - const size_t cv_stride0 = &(srf_cv[1][0][0]) - &(srf_cv[0][0][0]); // should be 3*15 = 15 - const size_t cv_stride1 = &(srf_cv[0][1][0]) - &(srf_cv[0][0][0]); // should be 3 - tmp.m_cv_stride[0] = (int)cv_stride0; - tmp.m_cv_stride[1] = (int)cv_stride1; - - // First see if patch 1 and patch 3 can be completed using - // this->m_center_edge*_limit_point locations - for ( unsigned int interp_patch_dex = 0; interp_patch_dex < 4; interp_patch_dex++) - { - if (exact_quadrant_count + approx_quadrant_count + interp_quadrant_count >= 4) - break; // all patches have been set - - if (ON_SubDLimitNurbsFragment::BispanType::None != pt[interp_patch_dex]) - continue; // already have this patch - - if (15 != (patch_exact_cv_count[interp_patch_dex] + patch_approx_cv_count[interp_patch_dex])) - continue; // missing too many cvs for interopolation to work - - ON_2udex srf_cv_dex[2] = {}; - unsigned int edge_index[2] = {}; - switch (interp_patch_dex) - { - case 0: - srf_cv_dex[0] = ON_2udex(0, 3); edge_index[0] = 3; - srf_cv_dex[1] = ON_2udex(3, 0); edge_index[1] = 0; - break; - case 1: - srf_cv_dex[0] = ON_2udex(1, 0); edge_index[0] = 0; - srf_cv_dex[1] = ON_2udex(4, 3); edge_index[1] = 1; - break; - case 2: - srf_cv_dex[0] = ON_2udex(4, 1); edge_index[0] = 1; - srf_cv_dex[1] = ON_2udex(1, 4); edge_index[1] = 2; - break; - case 3: - srf_cv_dex[0] = ON_2udex(3, 4); edge_index[0] = 2; - srf_cv_dex[1] = ON_2udex(0, 1); edge_index[1] = 3; - break; - default: - break; - } - - const int n = (0 == srf_cv_status[srf_cv_dex[0].i][srf_cv_dex[0].j]) ? 0 : 1; - const ON_2udex srf_unset_cv_dex = srf_cv_dex[n]; - - if (0 != srf_cv_status[srf_unset_cv_dex.i][srf_unset_cv_dex.j]) - continue; // the CV we would set using interpolation is already set. - - if (false == m_bCenterEdgeLimitPoint[edge_index[n]]) - continue; // We don't have a limit point to interpolate. - - // We are missing exactly one patch cv and can set it using interpolation through LP - if (false == Internal_InterpCV( - srf_cv, - srf_unset_cv_dex, - patch_cv00[interp_patch_dex], - tmp, - &m_center_edge_limit_point[edge_index[n]] - )) - { - continue; - } - - srf_cv_status[srf_unset_cv_dex.i][srf_unset_cv_dex.j] = 3; - pt[interp_patch_dex] = ON_SubDLimitNurbsFragment::BispanType::Approximate; - interp_quadrant_count++; - approx_cv_count++; - for (unsigned int patch_dex = 0; patch_dex < 4; patch_dex++) - { - if ( - srf_unset_cv_dex.i >= patch_cv00[patch_dex].i && srf_unset_cv_dex.i < patch_cv00[patch_dex].i + 4 - && srf_unset_cv_dex.j >= patch_cv00[patch_dex].j && srf_unset_cv_dex.j < patch_cv00[patch_dex].j + 4 - ) - { - const unsigned int patch_set_cv_count = patch_exact_cv_count[patch_dex] + patch_approx_cv_count[patch_dex]; - - if (patch_set_cv_count < 16) - patch_approx_cv_count[patch_dex]++; - else - { - ON_SUBD_ERROR("Bug in patch set cv counter during step 2."); - } - } - } - } - - // See if interpolating a central quad limpt point will complete a patch. - for ( unsigned int interp_patch_dex = 0; interp_patch_dex < 4; interp_patch_dex++) - { - if (exact_quadrant_count + approx_quadrant_count + interp_quadrant_count >= 4) - break; - - if (ON_SubDLimitNurbsFragment::BispanType::None != pt[interp_patch_dex]) - continue; // already have this patch - - if (15 != (patch_exact_cv_count[interp_patch_dex] + patch_approx_cv_count[interp_patch_dex])) - continue; // missing too many cvs for interopolation to work - - const ON_2udex srf_unset_cv_dex( - (1 == interp_patch_dex || 2 == interp_patch_dex) ? 4 : 0, - (2 == interp_patch_dex || 3 == interp_patch_dex) ? 4 : 0 - ); - - if (0 != srf_cv_status[srf_unset_cv_dex.i][srf_unset_cv_dex.j]) - continue; - - const ON_2udex vertex_grid_dex(0 == srf_unset_cv_dex.i ? 1 : 2, 0 == srf_unset_cv_dex.j ? 1 : 2); - - const ON_SubDVertex* vertex = m_vertex_grid[vertex_grid_dex.i][vertex_grid_dex.j]; - if (nullptr == vertex) - continue; - - ON_SubDSectorLimitPoint vertex_limit_point = ON_SubDSectorLimitPoint::Unset; - const bool bUseSavedLimitPoint = true; - if (false == vertex->GetLimitPoint( - ON_SubD::SubDType::QuadCatmullClark, - m_face_grid[1][1], - bUseSavedLimitPoint, - vertex_limit_point - )) - continue; - - // Calculate value of the unset CV so the surface passes through the limit point. - if (false == Internal_InterpCV( - srf_cv, - srf_unset_cv_dex, - patch_cv00[interp_patch_dex], - tmp, - &vertex_limit_point - )) - { - continue; - } - - srf_cv_status[srf_unset_cv_dex.i][srf_unset_cv_dex.j] = 3; - pt[interp_patch_dex] = ON_SubDLimitNurbsFragment::BispanType::Approximate; - interp_quadrant_count++; - approx_cv_count++; - for (unsigned int patch_dex = 0; patch_dex < 4; patch_dex++) - { - if ( - srf_unset_cv_dex.i >= patch_cv00[patch_dex].i && srf_unset_cv_dex.i < patch_cv00[patch_dex].i + 4 - && srf_unset_cv_dex.j >= patch_cv00[patch_dex].j && srf_unset_cv_dex.j < patch_cv00[patch_dex].j + 4 - ) - { - const unsigned int patch_set_cv_count = patch_exact_cv_count[patch_dex] + patch_approx_cv_count[patch_dex]; - - if (patch_set_cv_count < 16) - patch_approx_cv_count[patch_dex]++; - else - { - ON_SUBD_ERROR("Bug in patch set cv counter during step 2."); - } - } - } - - } - - // Not necessary at the time of writing, but will prevent crashes if tmp m_*_capacity values are - // corrupted by a bug. - tmp.m_cv = nullptr; - tmp.m_knot[0] = nullptr; - tmp.m_knot[1] = nullptr; - } - - unsigned qcheck=0; - for (unsigned patch_dex = 0; patch_dex < 4; patch_dex++) - { - const unsigned int patch_set_cv_count = (patch_exact_cv_count[patch_dex] + patch_approx_cv_count[patch_dex]); - if ( - ON_SubDLimitNurbsFragment::BispanType::Exact == pt[patch_dex] - || ON_SubDLimitNurbsFragment::BispanType::Approximate == pt[patch_dex] - ) - { - if (16 != patch_set_cv_count) - { - ON_SUBD_ERROR("Patch cv count bug 1."); - } - qcheck++; - } - else - { - if (patch_set_cv_count >= 16) - { - ON_SUBD_ERROR("Patch cv count bug 1."); - } - } - } - - if (qcheck != interp_quadrant_count + approx_quadrant_count + exact_quadrant_count) - { - ON_SUBD_ERROR("patch_type[] values are not correct."); - } - - if (nullptr != patch_type) - { - patch_type[0] = pt[0]; - patch_type[1] = pt[1]; - patch_type[2] = pt[2]; - patch_type[3] = pt[3]; - } - - - return (interp_quadrant_count + approx_quadrant_count + exact_quadrant_count); -} static double ON_SubDQuadFaceTopology_CopySectorWeight( const ON_SubDEdge* e0, @@ -2177,7 +1737,7 @@ static ON_SubDEdge* ON_SubDQuadFaceTopology_SubdivideEdge( return ON_SUBD_RETURN_ERROR(nullptr); double v0_weight; - if ( e0->IsSmooth(true) && nullptr != qv0) + if ( e0->IsSmooth() && nullptr != qv0) { // qv1 is the subdivision point of qv0. if ( qv1->m_vertex_tag != qv0->m_vertex_tag ) @@ -2338,13 +1898,18 @@ bool ON_SubDQuadNeighborhood::Subdivide( const bool bIsCreaseOrCornerSector = qv0->IsCreaseOrCorner(); const bool bBoundaryCrease1[4] = { - m_bBoundaryCrease[q0fvi] || (bIsCreaseOrCornerSector && qf0_edges[0]->IsCrease(false)), + m_bBoundaryCrease[q0fvi] || (bIsCreaseOrCornerSector && qf0_edges[0]->IsCrease()), false, false, - m_bBoundaryCrease[(q0fvi+3)%4] || (bIsCreaseOrCornerSector && qf0_edges[3]->IsCrease(false)) + m_bBoundaryCrease[(q0fvi+3)%4] || (bIsCreaseOrCornerSector && qf0_edges[3]->IsCrease()) }; - const bool bStopAtInternalCrease = (false == bIsDartSector); + //const bool bStopAtInternalCrease = (false == bIsDartSector); + const ON_SubDSectorIterator::StopAt stop_at + = (bIsDartSector) + ? ON_SubDSectorIterator::StopAt::Boundary + : ON_SubDSectorIterator::StopAt::AnyCrease + ; if ( false == fsh.ReserveSubDWorkspace( subd_type, N) ) return ON_SUBD_RETURN_ERROR(false); @@ -2421,7 +1986,7 @@ bool ON_SubDQuadNeighborhood::Subdivide( if (!bAtBoundaryCrease) { - f0 = sit.NextFace(bStopAtInternalCrease); + f0 = sit.NextFace(stop_at); if (nullptr == f0) bAtBoundaryCrease = true; if (e0[1] != sit.CurrentEdge(0)) @@ -2480,7 +2045,7 @@ bool ON_SubDQuadNeighborhood::Subdivide( { if ( qf0 != sit.FirstFace()) return ON_SUBD_RETURN_ERROR(false); - f0 = sit.PrevFace(bStopAtInternalCrease); + f0 = sit.PrevFace(stop_at); if ( nullptr == f0 ) return ON_SUBD_RETURN_ERROR(false); if ( edge0 != sit.CurrentEdge(1) ) @@ -2511,7 +2076,7 @@ bool ON_SubDQuadNeighborhood::Subdivide( if (0 == i) face_grid1_10 = f1; - f0 = sit.PrevFace(bStopAtInternalCrease); + f0 = sit.PrevFace(stop_at); if (nullptr == f0 || ON_SubD::EdgeTag::Crease == e0[1]->m_edge_tag) { bFinished = (nullptr == f0 && ON_SubD::EdgeTag::Crease == e0[0]->m_edge_tag && qv1->m_face_count+1 == qv1->m_edge_count); @@ -3059,15 +2624,15 @@ bool ON_SubDFaceNeighborhood::ReserveCapacity( const ON_SubDEdge* edge; const ON_SubDVertex* vertex; unsigned int i; - for (i = 0; i < N; i++) + for (i = 0; i < N; i++, edges++) { if (4 == i) { if (nullptr == face->m_edgex) return ON_SUBD_RETURN_ERROR(false); - edges = face->m_edgex - 4; + edges = face->m_edgex; } - edge_ptr = edges[i].m_ptr; + edge_ptr = edges->m_ptr; edge = ON_SUBD_EDGE_POINTER(edge_ptr); if (nullptr == edge) break; @@ -3087,13 +2652,9 @@ bool ON_SubDFaceNeighborhood::ReserveCapacity( if (ON_SubD::SubDType::QuadCatmullClark == subd_type) { - //v_capacity = 1 + 6*N + 2*E; - //e_capacity = 9*N + 3*E; - //f_capacity = 4*N + E; - //a_capacity = 36*N + 8*E; f_capacity = S; e_capacity = 3*S - N; - v_capacity = 2*S - 2*N + 1; + v_capacity = 2*(S - N) + 1; a_capacity = 4*f_capacity + 2*e_capacity + 2*v_capacity; } else if (ON_SubD::SubDType::TriLoopWarren == subd_type) @@ -3212,11 +2773,36 @@ bool ON_SubDFaceNeighborhood::TriSubdivideHelper( //// return true; ////} + +#if defined(ON_DEBUG) + +//// debugging tool. If a call to this function is left in release code, the compile will fail (on purpose). +//static void ON_DEBUG_SubDBreakPoint(const ON_SubDFace* face) +//{ +// for (;;) +// { +// if (nullptr == face) +// break; +// if (0 != face->m_level) +// break; +// if (34 != face->m_id) +// break; +// ON_TextLog::Null.Print("Breakpoint here."); // does nothing prevents warning as error +// break; +// } +//#if !defined(ON_DEBUG) +//#error Do not use ON_DEBUG_SubDBreakPoint() in release builds. +//#endif +//} + +#endif + bool ON_SubDFaceNeighborhood::QuadSubdivideHelper( const ON_SubDFace* face ) { // input face is valid and space is reserved. + //ON_DEBUG_SubDBreakPoint(face); const ON_SubD::SubDType subd_type = ON_SubD::SubDType::QuadCatmullClark; @@ -3240,25 +2826,24 @@ bool ON_SubDFaceNeighborhood::QuadSubdivideHelper( ON_SubDVertex* center_vertex1 = m_fsh.AllocateVertex(face,subd_type,bUseSavedSubdivisionPoint,N,N); if ( nullptr == center_vertex1) return ON_SUBD_RETURN_ERROR(false); - //center_vertex1->m_vertex_edge_order = ON_SubD::VertexEdgeOrder::radial; - ON_SubDVertex* ring_vertex1[2] = {}; + const ON_SubDVertex* vertex0 = face->Vertex(0); + if ( nullptr == vertex0 ) + return ON_SUBD_RETURN_ERROR(false); + const bool bFirstVertexHasValence2 = (2 == vertex0->m_edge_count && 2 == vertex0->m_face_count && vertex0->IsSmoothOrDart()); - const ON_SubDVertex* vertex0 = nullptr; - const ON_SubDEdge* prev_edge0 = nullptr; - ON__UINT_PTR prev_edge0_dir = 0; const ON_SubDEdge* edge0 = nullptr; ON__UINT_PTR edge0_dir = 0; ON_SubDVertex* vertex1 = nullptr; ON_SubDEdge* edge1 = nullptr; - ON_SubDFace* face1 = nullptr; - ON_SubDEdgePtr face1_eptrs[4] = {ON_SubDEdgePtr::Null,ON_SubDEdgePtr::Null,ON_SubDEdgePtr::Null,ON_SubDEdgePtr::Null}; + + // Calculate 2*N subdivison vertices on the boundary of input "face" + // and N subdivision edge that radiate from center_vertex1 to the input face edge's subdivison points. + // This loop also vertifies that all input vertex and edge pointers are not null and have the + // expected properties so the rest of this function can dispense with checking. const ON_SubDEdgePtr* face_m_edges = face->m_edge4; for (unsigned int i = 0; i < N; i++, face_m_edges++) { - prev_edge0 = edge0; - prev_edge0_dir = edge0_dir; - if (4 == i) { if (nullptr == face->m_edgex) @@ -3268,81 +2853,37 @@ bool ON_SubDFaceNeighborhood::QuadSubdivideHelper( const ON__UINT_PTR edge0_ptr = face_m_edges->m_ptr; edge0 = ON_SUBD_EDGE_POINTER(edge0_ptr); - if (nullptr == edge0) + if (nullptr == edge0 || nullptr == edge0->m_vertex[0] || nullptr == edge0->m_vertex[1]) return ON_SUBD_RETURN_ERROR(false); - edge0_dir = ON_SUBD_EDGE_DIRECTION(edge0_ptr); - + edge0_dir = ON_SUBD_EDGE_DIRECTION(edge0_ptr); vertex0 = edge0->m_vertex[edge0_dir]; - if (nullptr == vertex0) - return ON_SUBD_RETURN_ERROR(false); if (vertex0->m_edge_count < 2) return ON_SUBD_RETURN_ERROR(false); - - // At this time (Jan, 2015) the primary purpose of creating a ON_SubDFaceNeighborhood - // is to get the limit mesh and limit cubic surfaces when the original face - // is not a quad. I need to calculate and save the limit point for extraordinary + + // One of the main reasons for creating a ON_SubDFaceNeighborhood is to calcualtethe + // limit mesh and limit cubic surfaces when the original face is not a quad. + // For these calculations, we need to calculate and save the limit point for extraordinary // verticies while enough information is available to calculate it. Doing the calculation - // before calling m_fsh.AllocateVertex(), insures the information will be copied to - // vertex1; + // before calling m_fsh.AllocateVertex(), insures the information will be copied to vertex1. if ( false == vertex0->GetLimitPoint(subd_type,face,true,limit_point) ) return ON_SUBD_RETURN_ERROR(false); - vertex1 = m_fsh.AllocateVertex(vertex0,subd_type,bUseSavedSubdivisionPoint,vertex0->m_edge_count,vertex0->m_edge_count); if ( nullptr == vertex1) return ON_SUBD_RETURN_ERROR(false); - - if (nullptr != limit_point.m_sector_face) + if (nullptr != limit_point.m_sector_face || subd_type != vertex1->SavedLimitPointType() ) { - // Original vertex had sectors and that will prevent - // m_fsh.AllocateVertex() from copying the limit point. - // vertex1 does not have sectors. - ON_SubDSectorLimitPoint saved_limit_point = limit_point; - saved_limit_point.m_sector_face = nullptr; - saved_limit_point.m_next_sector_limit_point = (ON_SubDSectorLimitPoint*)1; // causes unnecessary test to be skipped - vertex1->SetSavedLimitPoint(subd_type, saved_limit_point); + // While there may be multiple sectors around vertex0, the only limit point + // that matters is this local subdivision is the one for vertex0 in the sector containing the input face. + limit_point.m_next_sector_limit_point = (ON_SubDSectorLimitPoint*)1; // causes unnecessary test to be skipped + limit_point.m_sector_face = nullptr; + vertex1->SetSavedLimitPoint(subd_type, limit_point); } - - if (0 == i) - { - ring_vertex1[0] = vertex1; - } - else - { - if ( nullptr == prev_edge0) - return ON_SUBD_RETURN_ERROR(false); - face1_eptrs[0] = face1_eptrs[3].Reversed(); - edge1 = m_fsh.AllocateEdge( - ring_vertex1[1], - ON_SubDSectorType::IgnoredSectorWeight, - vertex1, - ON_SubDQuadFaceTopology_CopySectorWeight(prev_edge0, vertex0) - ); - if ( nullptr == edge1) - return ON_SUBD_RETURN_ERROR(false); - face1_eptrs[1] = ON_SubDEdgePtr::Create(edge1,0); - } - ring_vertex1[1] = vertex1; - // This vertex is either a crease (3 edges, 2 faces) or a smooth ordinary vertex (4 edges, 4 faces). vertex1 = m_fsh.AllocateVertex(edge0,subd_type,bUseSavedSubdivisionPoint,4,4); if ( nullptr == vertex1) return ON_SUBD_RETURN_ERROR(false); - if (0 != i) - { - edge1 = m_fsh.AllocateEdge( - ring_vertex1[1], - ON_SubDQuadFaceTopology_CopySectorWeight(edge0,vertex0), - vertex1, - ON_SubDSectorType::IgnoredSectorWeight - ); - if (nullptr == edge1) - return ON_SUBD_RETURN_ERROR(false); - face1_eptrs[2] = ON_SubDEdgePtr::Create(edge1,0); - } - ring_vertex1[1] = vertex1; - edge1 = m_fsh.AllocateEdge( center_vertex1, ON_SubDSectorType::IgnoredSectorWeight, @@ -3351,235 +2892,374 @@ bool ON_SubDFaceNeighborhood::QuadSubdivideHelper( ); if ( nullptr == edge1) return ON_SUBD_RETURN_ERROR(false); - face1_eptrs[3] = ON_SubDEdgePtr::Create(edge1,1); + } - if (0==i) - continue; + // ring_vertex1 and last_ring_vertex1 are used to repeatedly iterate through subdivsion vertices on the boundary + // of the original face. + // i = "corner" index (= <= i < N. + // ring_vertex1[3] = last vertex in the boundary and is used for initialization when i = 0. + // ring_vertex1[0] = subdivision vertex on input face->Edge(i-1) + // ring_vertex1[1] = subdivision vertex on input face->Vertex(i) + // ring_vertex1[2] = subdivision vertex on input face->Edge(i) + ON_SubDVertex* ring_vertex1[4] = { nullptr, nullptr, nullptr, vertex1 }; + + + // Calculate the 2*N subdivison edges on the boundary of input "face" + // and N subdivision faces. + ON_SubDFace* face1 = nullptr; + ON_SubDEdgePtr face1_eptrs[4] = {ON_SubDEdgePtr::Null,ON_SubDEdgePtr::Null,ON_SubDEdgePtr::Null,ON_SubDEdgePtr::Create(edge1, 1)}; + face_m_edges = face->m_edge4; + for (unsigned int i = 0; i < N; i++, face_m_edges++) + { + if (4 == i) + face_m_edges = face->m_edgex; + const ON__UINT_PTR edge0_ptr = face_m_edges->m_ptr; + + const ON_SubDEdge* prev_edge0 = edge0; + //const ON__UINT_PTR prev_edge0_dir = edge0_dir; + edge0 = ON_SUBD_EDGE_POINTER(edge0_ptr); + edge0_dir = ON_SUBD_EDGE_DIRECTION(edge0_ptr); + vertex0 = edge0->m_vertex[edge0_dir]; + + // ring_vertex1[0] = subdivision vertex on input face->Edge(i-1) = prev_edge0 + // ring_vertex1[1] = subdivision vertex on input face->Vertex(i) = vertex0 + // ring_vertex1[2] = subdivision vertex on input face->Edge(i) = edge0 + if (0 == i) + { + ring_vertex1[0] = ring_vertex1[3]; + ring_vertex1[1] = const_cast(center_vertex1->m_next_vertex); + ring_vertex1[2] = const_cast(ring_vertex1[1]->m_next_vertex); + } + else + { + ring_vertex1[0] = ring_vertex1[2]; + ring_vertex1[1] = const_cast(ring_vertex1[0]->m_next_vertex); + ring_vertex1[2] = const_cast(ring_vertex1[1]->m_next_vertex); + } + + face1_eptrs[0] = face1_eptrs[3].Reversed(); + face1_eptrs[3] = center_vertex1->m_edges[i].Reversed(); + + edge1 = m_fsh.AllocateEdge( + ring_vertex1[0], + ON_SubDSectorType::IgnoredSectorWeight, + ring_vertex1[1], + ON_SubDQuadFaceTopology_CopySectorWeight(prev_edge0, vertex0) + ); + if ( nullptr == edge1 ) + return ON_SUBD_RETURN_ERROR(false); + face1_eptrs[1] = ON_SubDEdgePtr::Create(edge1, 0); + + + edge1 = m_fsh.AllocateEdge( + ring_vertex1[1], + ON_SubDQuadFaceTopology_CopySectorWeight(edge0, vertex0), + ring_vertex1[2], + ON_SubDSectorType::IgnoredSectorWeight + ); + if ( nullptr == edge1 ) + return ON_SUBD_RETURN_ERROR(false); + face1_eptrs[2] = ON_SubDEdgePtr::Create(edge1, 0); face1 = m_fsh.AllocateQuad(zero_face_id, parent_face_id, face1_eptrs); if ( nullptr == face1) return ON_SUBD_RETURN_ERROR(false); } - // add the quad with diagonal from center to initial ring vertex - // to finish quad subdivision of the initial face. - face1_eptrs[0] = face1_eptrs[3].Reversed(); - - edge1 = m_fsh.AllocateEdge( - ring_vertex1[1], - ON_SubDSectorType::IgnoredSectorWeight, - ring_vertex1[0], - ON_SubDQuadFaceTopology_CopySectorWeight(edge0,face->Vertex(0)) - ); - if ( nullptr == edge1) - return ON_SUBD_RETURN_ERROR(false); - face1_eptrs[1] = ON_SubDEdgePtr::Create(edge1,0); - - edge1 = m_fsh.AllocateEdge( - ring_vertex1[0], - ON_SubDQuadFaceTopology_CopySectorWeight(face->Edge(0),face->Vertex(0)), - const_cast(ring_vertex1[0]->m_next_vertex), - ON_SubDSectorType::IgnoredSectorWeight - ); - if ( nullptr == edge1) - return ON_SUBD_RETURN_ERROR(false); - face1_eptrs[2] = ON_SubDEdgePtr::Create(edge1,0); - - face1_eptrs[3] = center_vertex1->m_edges[0].Reversed(); - - face1 = m_fsh.AllocateQuad(zero_face_id, parent_face_id, face1_eptrs); - if ( nullptr == face1) - return ON_SUBD_RETURN_ERROR(false); - - - // order edges and faces radially counterclockwise with respect to the initial face's orientation. - vertex1 = center_vertex1; - for (unsigned int i = 0; i < N; i++) + ///////////////////////////////////////////////////////////////////////////////// + // First, radially sort the subdivision vertex edge lists. + // This sorting is required for calculations later in this function. + // + // Then, add a subdivision edge from the input face's edge's subdivision point ("ring_vertex1") + // to the neighboring "level 0" face subdivision point. When this subdivision edge + // is added, it is critical below that it be in ring_vertex1->m_edges[3] + // + face_m_edges = face->m_edge4; + for (unsigned int i = 0; i < N; i++, face_m_edges++) { - if ( nullptr == vertex1) - { - // If this failure occurs, there is a bug in the code above - // or somebody changed the way ON_SubD_FixedSizeHeap::AllocateVertex() - // sets m_next_vertex. - return ON_SUBD_RETURN_ERROR(false); - } - vertex1 = const_cast(vertex1->m_next_vertex); - if ( nullptr == vertex1) - { - // If this failure occurs, there is a bug in the code above - // or somebody changed the way ON_SubD_FixedSizeHeap::AllocateVertex() - // sets m_next_vertex. - return ON_SUBD_RETURN_ERROR(false); - } - - // vertex1 = subdivided corner vertex of initial face - if ( 2 != vertex1->m_edge_count || 1 != vertex1->m_face_count ) - { - // If this failure occurs, there is a bug in the code above - // or somebody changed the way ON_SubD_FixedSizeHeap::AllocateVertex() - // sets m_next_vertex. - return ON_SUBD_RETURN_ERROR(false); - } - if (0 == i && vertex1 != ring_vertex1[0]) - { - // If this failure occurs, there is a bug in the code above - // or somebody changed the way ON_SubD_FixedSizeHeap::AllocateVertex() - // sets m_next_vertex. - return ON_SUBD_RETURN_ERROR(false); - } - face1_eptrs[0] = vertex1->m_edges[0]; - vertex1->m_edges[0] = vertex1->m_edges[1]; - vertex1->m_edges[1] = face1_eptrs[0]; - //vertex1->m_vertex_edge_order = ON_SubD::VertexEdgeOrder::radial; - - vertex1 = const_cast(vertex1->m_next_vertex); - if ( nullptr == vertex1) - { - // If this failure occurs, there is a bug in the code above - // or somebody changed the way ON_SubD_FixedSizeHeap::AllocateVertex() - // sets m_next_vertex. - return ON_SUBD_RETURN_ERROR(false); - } - - // vertex1 = subdivided edge vertex of initial face - if ( 3 != vertex1->m_edge_count || 2 != vertex1->m_face_count ) - { - // If this failure occurs, there is a bug in the code above - // or somebody changed the way ON_SubD_FixedSizeHeap::AllocateVertex() - // sets m_next_vertex. - return ON_SUBD_RETURN_ERROR(false); - } + if (4 == i) + face_m_edges = face->m_edgex; + const ON__UINT_PTR edge0_ptr = face_m_edges->m_ptr; + edge0 = ON_SUBD_EDGE_POINTER(edge0_ptr); + edge0_dir = ON_SUBD_EDGE_DIRECTION(edge0_ptr); + vertex0 = edge0->m_vertex[edge0_dir]; + // ring_vertex1[0] = subdivision vertex on input face->Edge(i-1) + // ring_vertex1[1] = subdivision vertex on input face->Vertex(i) = vertex0 + // ring_vertex1[2] = subdivision vertex on input face->Edge(i) = edge0 if (0 == i) { - // edges of the first vertex of this type are created - // in a different order than subsequent vertices. - face1_eptrs[0] = vertex1->m_edges[0]; - vertex1->m_edges[0] = vertex1->m_edges[1]; - vertex1->m_edges[1] = face1_eptrs[0]; + ring_vertex1[0] = ring_vertex1[3]; + ring_vertex1[1] = const_cast(center_vertex1->m_next_vertex); + ring_vertex1[2] = const_cast(ring_vertex1[1]->m_next_vertex); } else { - face1_eptrs[0] = vertex1->m_edges[0]; - vertex1->m_edges[0] = vertex1->m_edges[2]; - vertex1->m_edges[2] = face1_eptrs[0]; - const ON_SubDFace* tmp_f = vertex1->m_faces[0]; - vertex1->m_faces[0] = vertex1->m_faces[1]; - vertex1->m_faces[1] = tmp_f; + ring_vertex1[0] = ring_vertex1[2]; + ring_vertex1[1] = const_cast(ring_vertex1[0]->m_next_vertex); + ring_vertex1[2] = const_cast(ring_vertex1[1]->m_next_vertex); } - //vertex1->m_vertex_edge_order = ON_SubD::VertexEdgeOrder::radial; + + + // debugggin checks. + // ring_vertex1[0] counts vary + if ( 2 != ring_vertex1[1]->m_edge_count || 1 != ring_vertex1[1]->m_face_count) + return ON_SUBD_RETURN_ERROR(false); + if ( 3 != ring_vertex1[2]->m_edge_count || 2 != ring_vertex1[2]->m_face_count ) + return ON_SUBD_RETURN_ERROR(false); + + // swap edges at the corner vertex + face1_eptrs[0] = ring_vertex1[1]->m_edges[0]; + ring_vertex1[1]->m_edges[0] = ring_vertex1[1]->m_edges[1]; + ring_vertex1[1]->m_edges[1] = face1_eptrs[0]; + + if ((N-1) == i) + { + face1_eptrs[0] = ring_vertex1[2]->m_edges[1]; + face1_eptrs[1] = ring_vertex1[2]->m_edges[0]; + face1_eptrs[2] = ring_vertex1[2]->m_edges[2]; + } + else + { + face1_eptrs[0] = ring_vertex1[2]->m_edges[2]; + face1_eptrs[1] = ring_vertex1[2]->m_edges[0]; + face1_eptrs[2] = ring_vertex1[2]->m_edges[1]; + } + ring_vertex1[2]->m_edges[0] = face1_eptrs[0]; + ring_vertex1[2]->m_edges[1] = face1_eptrs[1]; + ring_vertex1[2]->m_edges[2] = face1_eptrs[2]; + + if (bFirstVertexHasValence2) + continue; + + const ON_SubDFace* neighbor_face0 = edge0->NeighborFacePtr(face, false).Face(); + if (nullptr == neighbor_face0) + { + if (false == edge0->IsHardCrease()) + { + // Either input face or edge0 is damaged, but this error can be tolerated. + ON_SubDIncrementErrorCount(); + } + continue; + } + vertex1 = nullptr; + if (i > 0 && 2 == vertex0->m_edge_count && 2 == vertex0->m_face_count && 4 == ring_vertex1[0]->m_edge_count) + { + // uncommon valence 2 case that can be handled here + edge1 = ON_SUBD_EDGE_POINTER(ring_vertex1[0]->m_edges[3].m_ptr); + if ( nullptr == edge1 ) + return ON_SUBD_RETURN_ERROR(false); + vertex1 = const_cast(edge1->m_vertex[1]); + if (nullptr == vertex1) + return ON_SUBD_RETURN_ERROR(false); + } + if (nullptr == vertex1) + { + vertex1 = m_fsh.AllocateVertex(neighbor_face0, subd_type, bUseSavedSubdivisionPoint, 4, 2); + if (nullptr == vertex1) + return ON_SUBD_RETURN_ERROR(false); + } + edge1 = m_fsh.AllocateEdge( + ring_vertex1[2], + at_crease2_weight, // ingored unless ring_vertex1[0] is tagged as a crease + vertex1, + ON_SubDSectorType::IgnoredSectorWeight + ); + if (nullptr == edge1) + return ON_SUBD_RETURN_ERROR(false); } - if ( vertex1 != ring_vertex1[1]) + if (bFirstVertexHasValence2) { - // If this failure occurs, there is a bug in the code above - // or somebody changed the way ON_SubD_FixedSizeHeap::AllocateVertex() - // sets m_next_vertex. - return ON_SUBD_RETURN_ERROR(false); - } + // extremely uncommon case that requires this mess ... + ON_SimpleArray neighbors(N); + face_m_edges = face->m_edge4; + for (unsigned int i = 0; i < N; i++, face_m_edges++) + { + if (4 == i) + face_m_edges = face->m_edgex; + const ON__UINT_PTR edge0_ptr = face_m_edges->m_ptr; + edge0 = ON_SUBD_EDGE_POINTER(edge0_ptr); + neighbors.Append(edge0->NeighborFacePtr(face, edge0->IsHardCrease()).Face()); + } + + ON_SimpleArray neighbors_vertex1(N); + face_m_edges = face->m_edge4; + for (unsigned int i = 0; i < N; i++, face_m_edges++) + { + if (4 == i) + face_m_edges = face->m_edgex; + const ON__UINT_PTR edge0_ptr = face_m_edges->m_ptr; + edge0 = ON_SUBD_EDGE_POINTER(edge0_ptr); + if (0 == i) + { + ring_vertex1[0] = ring_vertex1[3]; + ring_vertex1[1] = const_cast(center_vertex1->m_next_vertex); + ring_vertex1[2] = const_cast(ring_vertex1[1]->m_next_vertex); + } + else + { + ring_vertex1[0] = ring_vertex1[2]; + ring_vertex1[1] = const_cast(ring_vertex1[0]->m_next_vertex); + ring_vertex1[2] = const_cast(ring_vertex1[1]->m_next_vertex); + } + + + const ON_SubDFace* neighbor_face0 = edge0->NeighborFacePtr(face, edge0->IsHardCrease()).Face(); + if (nullptr == neighbor_face0) + { + neighbors_vertex1.Append(nullptr); + continue; + } + for (unsigned int j = 0; j < i; j++) + { + if (neighbor_face0 == neighbors[j]) + { + vertex1 = neighbors_vertex1[j]; + break; + } + } + if (nullptr == vertex1) + { + vertex1 = m_fsh.AllocateVertex(neighbor_face0, subd_type, bUseSavedSubdivisionPoint, 4, 2); + if (nullptr == vertex1) + return ON_SUBD_RETURN_ERROR(false); + } + neighbors_vertex1.Append(vertex1); + edge1 = m_fsh.AllocateEdge( + ring_vertex1[2], + at_crease2_weight, // ingored unless ring_vertex1[0] is tagged as a crease + vertex1, + ON_SubDSectorType::IgnoredSectorWeight + ); + if (nullptr == edge1) + return ON_SUBD_RETURN_ERROR(false); + } + } ///////////////////////////////////////////////////////////////////////////////// // - // At each corner of the original face, add the outer subdivision quads + // At each corner of the original face, add the outer subdivision quads as needed. // - - // Create subdivision edges that radiate from the sides of the original faces - ON_SubDVertex* vertex1_trio[3] = { nullptr, nullptr, center_vertex1 }; - ON_SubDEdge* edge1_quartet[4] = {}; - face_m_edges = face->m_edge4; - for (unsigned int i = 0; i < N; i++, face_m_edges++) - { - vertex1_trio[2] = const_cast(vertex1_trio[2]->m_next_vertex->m_next_vertex); - if (4 == i) - face_m_edges = face->m_edgex; - edge0 = ON_SUBD_EDGE_POINTER(face_m_edges->m_ptr); - if ( false == edge0->IsSmooth(true) || edge0->m_face_count != 2 ) - continue; - const ON_SubDFace* face0 = edge0->NeighborFace(face,true); - if ( nullptr == face0 ) - return ON_SUBD_RETURN_ERROR(false); - vertex1 = m_fsh.AllocateVertex(face0,subd_type,bUseSavedSubdivisionPoint,3,2); - if ( nullptr == vertex1 ) - return ON_SUBD_RETURN_ERROR(false); - edge1 = m_fsh.AllocateEdge(vertex1_trio[2],ON_SubDSectorType::IgnoredSectorWeight,vertex1,ON_SubDSectorType::IgnoredSectorWeight); - if ( nullptr == edge1 ) - return ON_SUBD_RETURN_ERROR(false); - if ( i+1 == N) - edge1_quartet[3] = edge1; - } - - // now visit each corner and add subdivision quads - ON_SubDSectorIterator sit; const ON_SubDEdge* edge0_duo[2] = {nullptr, face->Edge(N-1) }; - const ON_SubDFace* neighbor0_duo[2] = {nullptr, (nullptr == edge0_duo[1] ? nullptr : edge0_duo[1]->NeighborFace(face,true)) }; + const ON_SubDFace* neighbor0_duo[2] = {nullptr, edge0_duo[1]->NeighborFace(face,false) }; + bool edge0_duo_bIsHardCrease[2] = { false, edge0_duo[1]->IsHardCrease() }; + bool edge0_duo_bIsDartCrease[2] = { false, edge0_duo_bIsHardCrease[1] ? false : edge0_duo[1]->IsDartCrease() }; + ON_SubDEdge* edge1_quartet[4] = {nullptr,nullptr,nullptr, ((4==ring_vertex1[2]->m_edge_count)?(ON_SUBD_EDGE_POINTER(ring_vertex1[2]->m_edges[3].m_ptr)):nullptr) }; + ON_SubDSectorIterator sit; face_m_edges = face->m_edge4; for (unsigned int i = 0; i < N; i++, face_m_edges++) { - if (4==i) + if (4 == i) face_m_edges = face->m_edgex; // edge0_duo[0] = face->Edge(i-1) // edge0_duo[1] = face->Edge(i) - // neighbor0_duo[0] = original face neighbor across edge0_duo[0] - // neighbor0_duo[1] = original face neighbor across edge0_duo[0] - // vertex0 = face->Vertex(i) edge0_duo[0] = edge0_duo[1]; - neighbor0_duo[0] = neighbor0_duo[1]; edge0_duo[1] = ON_SUBD_EDGE_POINTER(face_m_edges->m_ptr); - neighbor0_duo[1] = (nullptr == edge0_duo[1] ? nullptr : edge0_duo[1]->NeighborFace(face,true)); - vertex0 = edge0_duo[1]->m_vertex[ON_SUBD_EDGE_DIRECTION(face_m_edges->m_ptr)]; - // tests above edge0_duo[0], edge0_duo[1], and vertex0 are not null. - const double e0_w = ON_SubDQuadFaceTopology_CopySectorWeight(edge0_duo[0], vertex0); - const double e1_w = ON_SubDQuadFaceTopology_CopySectorWeight(edge0_duo[1], vertex0); - double sector_weight; - if (e0_w == e1_w) - sector_weight = e0_w; + // vertex0 = face->Vertex(i) + vertex0 = edge0_duo[1]->m_vertex[ON_SUBD_EDGE_DIRECTION(face_m_edges->m_ptr)]; + //const bool bCornerVertexIsDart = vertex0->IsDart(); + //const bool bCornerVertexIsCreaseOrCorner = bCornerVertexIsDart ? false : vertex0->IsCreaseOrCorner(); + + edge0_duo_bIsHardCrease[0] = edge0_duo_bIsHardCrease[1]; + edge0_duo_bIsDartCrease[0] = edge0_duo_bIsDartCrease[1]; + edge0_duo_bIsHardCrease[1] = edge0_duo[1]->IsHardCrease(); + edge0_duo_bIsDartCrease[1] = edge0_duo_bIsHardCrease[1] ? false : edge0_duo[1]->IsDartCrease(); + //const bool bDartCreaseSituation = edge0_duo_bIsDartCrease[0] || edge0_duo_bIsDartCrease[1]; + + // neighbor0_duo[0] = original face neighbor across edge0_duo[0] + // neighbor0_duo[1] = original face neighbor across edge0_duo[1] + neighbor0_duo[0] = neighbor0_duo[1]; + neighbor0_duo[1] = edge0_duo[1]->NeighborFace(face, false); + + // ring_vertex1[0] = subdivision vertex on input face->Edge(i-1) = edge0_duo[0] + // ring_vertex1[1] = subdivision vertex on input face->Vertex(i) = vertex0 + // ring_vertex1[2] = subdivision vertex on input face->Edge(i) = edge0_duo[1] + if (0 == i) + { + ring_vertex1[0] = ring_vertex1[3]; + ring_vertex1[1] = const_cast(center_vertex1->m_next_vertex); + ring_vertex1[2] = const_cast(ring_vertex1[1]->m_next_vertex); + } else { - if (ON_SubDSectorType::IgnoredSectorWeight == e0_w) - sector_weight = e1_w; - else if (ON_SubDSectorType::IgnoredSectorWeight == e1_w) - sector_weight = e0_w; - else if (ON_SubDSectorType::UnsetSectorWeight == e0_w) - sector_weight = e1_w; - else if (ON_SubDSectorType::UnsetSectorWeight == e1_w) - sector_weight = e0_w; - else - sector_weight = ON_UNSET_VALUE; - if (!(sector_weight >= 0.0 && sector_weight < 1.0)) - sector_weight = ON_SubDSectorType::UnsetSectorWeight; + ring_vertex1[0] = ring_vertex1[2]; + ring_vertex1[1] = const_cast(ring_vertex1[0]->m_next_vertex); + ring_vertex1[2] = const_cast(ring_vertex1[1]->m_next_vertex); } - - // vertex1_trio[0] = subdivision vertex on edge0_duo[0] - // vertex1_trio[1] = subdivision vertex on face->Vertex(i) - // vertex1_trio[2] = subdivision vertex on edge0_duo[1] - // edge1_quartet[0] = null or subdivision edge from vertex1_trio[0] to neighbor0_duo[0] subdivision point - // edge1_quartet[1] = subdivision edge from vertex1_trio[0] to vertex1_trio[1] - // edge1_quartet[2] = subdivision edge from vertex1_trio[1] to vertex1_trio[2] - // edge1_quartet[3] = null or subdivision edge from vertex1_trio[2] to neighbor0_duo[1] subdivision point - vertex1_trio[0] = vertex1_trio[2]; - vertex1_trio[1] = const_cast< ON_SubDVertex* >(( 0 == i ) ? center_vertex1->m_next_vertex : vertex1_trio[0]->m_next_vertex); - vertex1_trio[2] = const_cast< ON_SubDVertex* >(vertex1_trio[1]->m_next_vertex); - edge1_quartet[0] = edge1_quartet[3]; - edge1_quartet[1] = ON_SUBD_EDGE_POINTER(vertex1_trio[1]->m_edges[1].m_ptr); - edge1_quartet[2] = ON_SUBD_EDGE_POINTER(vertex1_trio[1]->m_edges[0].m_ptr); - edge1_quartet[3] = (nullptr == vertex1_trio[2]) ? nullptr : ON_SUBD_EDGE_POINTER(vertex1_trio[2]->m_edges[3].m_ptr); - - if (edge0_duo[0]->IsCrease(false) && edge0_duo[1]->IsCrease(false)) + // edge1_quartet[0] = null or subdivision edge from ring_vertex1[0] to neighbor0_duo[0] subdivision point + // edge1_quartet[1] = subdivision edge from ring_vertex1[0] to ring_vertex1[1] + // edge1_quartet[2] = subdivision edge from ring_vertex1[1] to ring_vertex1[2] + // edge1_quartet[3] = null or subdivision edge from ring_vertex1[2] to neighbor0_duo[1] subdivision point + edge1_quartet[0] = edge1_quartet[3]; + edge1_quartet[1] = ON_SUBD_EDGE_POINTER(ring_vertex1[1]->m_edges[1].m_ptr); + edge1_quartet[2] = ON_SUBD_EDGE_POINTER(ring_vertex1[1]->m_edges[0].m_ptr); + edge1_quartet[3] = (4==ring_vertex1[2]->m_edge_count) ? (ON_SUBD_EDGE_POINTER(ring_vertex1[2]->m_edges[3].m_ptr)) : nullptr; + + if (edge0_duo_bIsHardCrease[0] && edge0_duo_bIsHardCrease[1]) { // no outer quads at this corner continue; } - if (2 != edge0_duo[0]->m_face_count && 2 != edge0_duo[1]->m_face_count) + + if ( false == edge0_duo_bIsHardCrease[0] && false == edge0_duo_bIsDartCrease[0] && false == edge0_duo[0]->IsSmooth() ) { - // no outer quads at this corner + // This is an error condition that we can tolerate and still get a partial result. + ON_SubDIncrementErrorCount(); continue; } + + if ( false == edge0_duo_bIsHardCrease[1] && false == edge0_duo_bIsDartCrease[1] && false == edge0_duo[1]->IsSmooth() ) + { + // This is an error condition that we can tolerate and still get a partial result. + ON_SubDIncrementErrorCount(); + continue; + } + + if (nullptr == neighbor0_duo[0] || nullptr == edge1_quartet[0]) + { + if (false == edge0_duo_bIsHardCrease[0]) + { + // This is an error condition that we can tolerate and still get a partial result. + ON_SubDIncrementErrorCount(); + continue; + } + } + + if (nullptr == neighbor0_duo[1] || nullptr == edge1_quartet[3]) + { + if (false == edge0_duo_bIsHardCrease[1]) + { + // This is an error condition that we can tolerate and still get a partial result. + ON_SubDIncrementErrorCount(); + continue; + } + } + + if (neighbor0_duo[0] == neighbor0_duo[1]) + { + // special case + if ( nullptr == neighbor0_duo[0] || 2 == vertex0->m_edge_count) + { + // This is an error condition that we can tolerate and still get a partial result. + ON_SubDIncrementErrorCount(); + continue; + } + if (nullptr == edge1_quartet[0] || nullptr == edge1_quartet[3] || edge1_quartet[0]->m_vertex[0] != edge1_quartet[3]->m_vertex[0] ) + { + // This is an error condition that we can tolerate and still get a partial result. + ON_SubDIncrementErrorCount(); + continue; + } + continue; + } + if (vertex0->m_face_count <= 1 || vertex0->m_edge_count <= 2) { - // error condition that we can tolerate + // This is an error condition that we can tolerate and still get a partial result. ON_SubDIncrementErrorCount(); continue; } @@ -3587,164 +3267,371 @@ bool ON_SubDFaceNeighborhood::QuadSubdivideHelper( ///////////////////////////////////////////////////////////////////////////////// // // There is at least one outer subdivision quads around the inner corner subd quad at face->Vertex(i) - - face1_eptrs[0] = ON_SubDEdgePtr::Null; - face1_eptrs[1] = ON_SubDEdgePtr::Null; - face1_eptrs[2] = ON_SubDEdgePtr::Null; - face1_eptrs[3] = ON_SubDEdgePtr::Create(edge1_quartet[1],1); - + + if (2 == vertex0->m_edge_count && 2 == vertex0->m_face_count) + { + // valance 2 special case + face1_eptrs[0] = ON_SubDEdgePtr::Create(edge1_quartet[2],1); + face1_eptrs[1] = ON_SubDEdgePtr::Create(edge1_quartet[3],0); + face1_eptrs[2] = ON_SubDEdgePtr::Create(edge1_quartet[0],1); + face1_eptrs[3] = ON_SubDEdgePtr::Create(edge1_quartet[1],1); + face1 = m_fsh.AllocateQuad(zero_face_id, parent_face_id, face1_eptrs); + if ( nullptr == face1) + return ON_SUBD_RETURN_ERROR(false); + continue; + } + if ( vertex0 != sit.Initialize(face,0,i) ) return ON_SUBD_RETURN_ERROR(false); - const ON_SubDFace* face0 = sit.NextFace(true); - if ( edge0_duo[0] != sit.CurrentEdge(0) || face0 != neighbor0_duo[0]) - return ON_SUBD_RETURN_ERROR(false); - - if (nullptr != face0) + if ( + vertex0 != sit.CenterVertex() + || face != sit.CurrentFace() + || (edge0_duo[0] != sit.CurrentEdge(1)) + || (edge0_duo[1] != sit.CurrentEdge(0)) + ) { - // construct the first subd quad counter-clockwise from the interior corner quad at face->Vertex(i). - edge0 = sit.CurrentEdge(1); + return ON_SUBD_RETURN_ERROR(false); + } + + + const unsigned int vertex_face_count = vertex0->m_face_count; + const ON_SubDFace* face0 = nullptr; + + ON_SubDVertex* face1_corners[4] = {ring_vertex1[1],nullptr,nullptr,nullptr}; + + if (vertex0->IsCreaseOrCorner() && edge0_duo_bIsDartCrease[0] && edge0_duo_bIsDartCrease[1]) + { + // special case + if ( vertex_face_count < 3 ) + return ON_SUBD_RETURN_ERROR(false); + if ( nullptr == neighbor0_duo[0] || nullptr == neighbor0_duo[1] ) + return ON_SUBD_RETURN_ERROR(false); + + face0 = sit.PrevFace(ON_SubDSectorIterator::StopAt::HardCrease); + if ( face0 != neighbor0_duo[1] ) + return ON_SUBD_RETURN_ERROR(false); + edge0 = sit.CurrentEdge(0); if ( nullptr == edge0 ) return ON_SUBD_RETURN_ERROR(false); - vertex1 = m_fsh.AllocateVertex(edge0,subd_type,bUseSavedSubdivisionPoint,3,2); - if ( nullptr == vertex1 ) - return ON_SUBD_RETURN_ERROR(false); - edge1 = m_fsh.AllocateEdge(const_cast(edge1_quartet[0]->m_vertex[1]),ON_SubDSectorType::IgnoredSectorWeight,vertex1,at_crease2_weight); + + const bool b3FaceCase = (neighbor0_duo[0] == edge0->NeighborFace(face0, false)); + + face1_corners[1] = m_fsh.AllocateVertex(edge0,subd_type,bUseSavedSubdivisionPoint,3,2); + face1_corners[2] = const_cast(edge1_quartet[3]->m_vertex[1]); + face1_corners[3] = ring_vertex1[2]; + + edge1 = m_fsh.AllocateEdge(face1_corners[0], ON_SubDQuadFaceTopology_CopySectorWeight(edge0, vertex0), face1_corners[1], at_crease2_weight); if ( nullptr == edge1 ) return ON_SUBD_RETURN_ERROR(false); - face1_eptrs[0] = ON_SubDEdgePtr::Create(edge1_quartet[1], 1); - face1_eptrs[1] = ON_SubDEdgePtr::Create(edge1_quartet[0], 0); - face1_eptrs[2] = ON_SubDEdgePtr::Create(edge1,0); - // vertex1_trio[1] is a subdivision of an input face vertex. - edge1 = m_fsh.AllocateEdge(vertex1_trio[1], sector_weight, vertex1, at_crease2_weight); + face1_eptrs[0] = ON_SubDEdgePtr::Create(edge1, 0); + + edge1 = m_fsh.AllocateEdge(face1_corners[1], at_crease2_weight, face1_corners[2], ON_SubDSectorType::IgnoredSectorWeight); if ( nullptr == edge1 ) return ON_SUBD_RETURN_ERROR(false); - face1_eptrs[3] = ON_SubDEdgePtr::Create(edge1,1); + face1_eptrs[1] = ON_SubDEdgePtr::Create(edge1, 0); + + face1_eptrs[2] = ON_SubDEdgePtr::Create(edge1_quartet[3],1); + face1_eptrs[3] = ON_SubDEdgePtr::Create(edge1_quartet[2],1); + face1 = m_fsh.AllocateQuad(zero_face_id, parent_face_id, face1_eptrs); if ( nullptr == face1 ) return ON_SUBD_RETURN_ERROR(false); + + face0 = sit.NextFace(ON_SubDSectorIterator::StopAt::HardCrease); + if ( face0 != face ) + return ON_SUBD_RETURN_ERROR(false); + + face0 = sit.NextFace(ON_SubDSectorIterator::StopAt::HardCrease); + if ( face0 != neighbor0_duo[0] ) + return ON_SUBD_RETURN_ERROR(false); + + if (b3FaceCase) + vertex1 = face1_corners[1]; + else + { + edge0 = sit.CurrentEdge(1); + if ( nullptr == edge1 ) + return ON_SUBD_RETURN_ERROR(false); + vertex1 = m_fsh.AllocateVertex(edge0,subd_type,bUseSavedSubdivisionPoint,3,2); + } + if ( nullptr == vertex1 ) + return ON_SUBD_RETURN_ERROR(false); + face1_corners[3] = vertex1; + face1_corners[1] = ring_vertex1[0]; + face1_corners[2] = const_cast(edge1_quartet[0]->m_vertex[1]); + + if (b3FaceCase) + { + face1_eptrs[3] = face1_eptrs[0].Reversed(); + } + else + { + edge1 = m_fsh.AllocateEdge(face1_corners[0], ON_SubDQuadFaceTopology_CopySectorWeight(edge0, vertex0), face1_corners[3], at_crease2_weight ); + if ( nullptr == edge1 ) + return ON_SUBD_RETURN_ERROR(false); + face1_eptrs[3] = ON_SubDEdgePtr::Create(edge1, 1); + } + + face1_eptrs[0] = ON_SubDEdgePtr::Create(edge1_quartet[1], 1); + face1_eptrs[1] = ON_SubDEdgePtr::Create(edge1_quartet[0], 0); + edge1 = m_fsh.AllocateEdge(face1_corners[2], ON_SubDSectorType::IgnoredSectorWeight, face1_corners[3], at_crease2_weight); + if ( nullptr == edge1 ) + return ON_SUBD_RETURN_ERROR(false); + face1_eptrs[2] = ON_SubDEdgePtr::Create(edge1, 0); + + face1 = m_fsh.AllocateQuad(zero_face_id, parent_face_id, face1_eptrs); + if ( nullptr == face1 ) + return ON_SUBD_RETURN_ERROR(false); + + continue; } - else + + // general case + if (vertex0->IsCreaseOrCorner() && nullptr != neighbor0_duo[1]) { - // edge0_duo[0] is a crease - face1_eptrs[0] = ON_SubDEdgePtr::Null; - face1_eptrs[1] = ON_SubDEdgePtr::Null; - face1_eptrs[2] = ON_SubDEdgePtr::Null; - face1_eptrs[3] = ON_SubDEdgePtr::Null; + // Back up (go clockwise) to an appropriate starting point. + // Here the stop at = ON_SubDSectorIterator::StopAt::HardCrease because we are hopping + // over an edge of the input face and the "other vertex" might be a dart. + face0 = sit.PrevFace(ON_SubDSectorIterator::StopAt::Boundary); + if (face0 != neighbor0_duo[1]) + return ON_SUBD_RETURN_ERROR(false); + for (unsigned int sit_limit = 0; sit_limit <= vertex_face_count && face != face0; sit_limit++) + { + const ON_SubDFace* prev_face0 = face0; + face0 = sit.PrevFace(ON_SubDSectorIterator::StopAt::Boundary); + if (prev_face0 == face0) + return ON_SUBD_RETURN_ERROR(false); + + if (nullptr == face0) + { + // Hit a boundary. Begin ccw iteration here + if (nullptr == prev_face0) + return ON_SUBD_RETURN_ERROR(false); + if (vertex0 != sit.Initialize(prev_face0, 0, vertex0)) + return ON_SUBD_RETURN_ERROR(false); + if (prev_face0 != sit.CurrentFace()) + return ON_SUBD_RETURN_ERROR(false); + break; + } + + if (face0 == face || face0 == neighbor0_duo[0]) + { + // beginning ccw iteration at input face will work fine. + if (vertex0 != sit.Initialize(face, 0, i)) + return ON_SUBD_RETURN_ERROR(false); + face0 = nullptr; + break; + } + + } + if (nullptr != face0) + return ON_SUBD_RETURN_ERROR(false); } + const bool bStopAtInputFace = (face == sit.CurrentFace()); + if (bStopAtInputFace) + { + if (nullptr == neighbor0_duo[0]) + return ON_SUBD_RETURN_ERROR(false); + face0 = sit.NextFace(ON_SubDSectorIterator::StopAt::Boundary); + if (face0 != neighbor0_duo[0]) + return ON_SUBD_RETURN_ERROR(false); + } + + // This information is needed when vertex0 is a crease vertex, one of the neighboring + // input face vertices is a dart, and sit is starting at a hard crease outside of the + // input face. + const ON_SubDEdge* sit_first_edge0 = sit.CurrentEdge(0); + if ( nullptr == sit_first_edge0 ) + return ON_SUBD_RETURN_ERROR(false); + ON_SubDEdgePtr sit_first_eptr1 = ON_SubDEdgePtr::Null; + + face1_eptrs[0] = ON_SubDEdgePtr::Null; + face1_eptrs[1] = ON_SubDEdgePtr::Null; + face1_eptrs[2] = ON_SubDEdgePtr::Null; + face1_eptrs[3] = ON_SubDEdgePtr::Null; + bool bFinishedCorner = false; - bool bCreases = false; - const unsigned int vertex_face_count = vertex0->m_face_count; - unsigned int vertex1_sort_mark = 0; - for (unsigned int sit_limit = 0; sit_limit < vertex_face_count; sit_limit++) { - if ( nullptr != face0 ) - face0 = sit.NextFace(true); - if (nullptr == face0) + if (0 != sit_limit) { - if ( bCreases ) - return ON_SUBD_RETURN_ERROR(false); - bCreases = true; - if (nullptr == neighbor0_duo[1]) + face0 = sit.NextFace(ON_SubDSectorIterator::StopAt::Boundary); + if (nullptr == face0) { - // face->Edge(i) is a crease bFinishedCorner = true; - break; - } - if ( nullptr == sit.Initialize(face,0,i)) - return ON_SUBD_RETURN_ERROR(false); - face0 = sit.IncrementToCrease(-1); - if ( nullptr == face0 || face0 == face) - return ON_SUBD_RETURN_ERROR(false); + break; + } edge0 = sit.CurrentEdge(0); if ( nullptr == edge0 ) return ON_SUBD_RETURN_ERROR(false); - vertex1_sort_mark = vertex1_trio[1]->m_face_count; - vertex1 = m_fsh.AllocateVertex(edge0,subd_type,bUseSavedSubdivisionPoint,2,1); - if ( nullptr == vertex1 ) + } + else + { + face0 = sit.CurrentFace(); + if (nullptr == face0) return ON_SUBD_RETURN_ERROR(false); - edge1 = m_fsh.AllocateEdge(vertex1_trio[1],ON_SubDSectorType::IgnoredSectorWeight,vertex1,ON_SubDSectorType::IgnoredSectorWeight); - if ( nullptr == edge1 ) - return ON_SUBD_RETURN_ERROR(false); - face1_eptrs[0] = ON_SubDEdgePtr::Null; + } + + if (face == face0) + { + if ( bStopAtInputFace ) + { + bFinishedCorner = true; + break; + } + if (vertex0->IsCreaseOrCorner() && edge0_duo_bIsHardCrease[0]) + { + if (edge0_duo[0] == sit.CurrentEdge(1)) + { + bFinishedCorner = true; + break; + } + } + face1_eptrs[0] = ON_SubDEdgePtr::Create(edge1_quartet[2], 0); face1_eptrs[1] = ON_SubDEdgePtr::Null; face1_eptrs[2] = ON_SubDEdgePtr::Null; - face1_eptrs[3] = ON_SubDEdgePtr::Create(edge1,1); + face1_eptrs[3] = ON_SubDEdgePtr::Create(edge1_quartet[1], 0); + continue; } - else if ( face0 == face) - return ON_SUBD_RETURN_ERROR(false); - - edge0 = sit.CurrentEdge(1); - if ( nullptr == edge0 || edge0 == edge0_duo[0] ) - return ON_SUBD_RETURN_ERROR(false); face1_eptrs[0] = face1_eptrs[3].Reversed(); face1_eptrs[1] = ON_SubDEdgePtr::Null; face1_eptrs[2] = ON_SubDEdgePtr::Null; face1_eptrs[3] = ON_SubDEdgePtr::Null; + if (face0 == neighbor0_duo[0]) + { + face1_eptrs[0] = ON_SubDEdgePtr::Create(edge1_quartet[1], 1); + face1_eptrs[1] = ON_SubDEdgePtr::Create(edge1_quartet[0], 0); + } if (face0 == neighbor0_duo[1]) { - // construct the last subd quad clockwise from the interior corner quad at face->Vertex(i). - if (edge0 != edge0_duo[1]) - return ON_SUBD_RETURN_ERROR(false); - edge1 = ON_SUBD_EDGE_POINTER(face1_eptrs[0].m_ptr); - if ( nullptr == edge1 ) - return ON_SUBD_RETURN_ERROR(false); - vertex1 = const_cast(edge1->m_vertex[1]); - if ( nullptr == vertex1 ) - return ON_SUBD_RETURN_ERROR(false); - // vertex1 is from subdividing an edge that radiates out from the input face. - edge1 = m_fsh.AllocateEdge( - vertex1, at_crease2_weight, - const_cast(edge1_quartet[3]->m_vertex[1]),ON_SubDSectorType::IgnoredSectorWeight); - face1_eptrs[1] = ON_SubDEdgePtr::Create(edge1,0); - face1_eptrs[2] = ON_SubDEdgePtr::Create(edge1_quartet[3],1); - face1_eptrs[3] = ON_SubDEdgePtr::Create(edge1_quartet[2],1); - face1 = m_fsh.AllocateQuad(zero_face_id, parent_face_id, face1_eptrs); - if ( nullptr == face1 ) - return ON_SUBD_RETURN_ERROR(false); - bFinishedCorner = true; - break; + face1_eptrs[2] = ON_SubDEdgePtr::Create(edge1_quartet[3], 1); + face1_eptrs[3] = ON_SubDEdgePtr::Create(edge1_quartet[2], 1); + } + else if ((vertex_face_count-1) == sit_limit && sit_first_edge0 == sit.CurrentEdge(1) ) + { + face1_eptrs[3] = sit_first_eptr1.Reversed(); } - if (edge0 == edge0_duo[1]) + face1_corners[1] = nullptr; + face1_corners[2] = nullptr; + face1_corners[3] = nullptr; + for (int j = 1; j < 4;j++) + { + vertex1 = nullptr; + for(;;) + { + vertex1 = const_cast(face1_eptrs[j-1].RelativeVertex(1)); + if (nullptr != vertex1) + break; + vertex1 = const_cast(face1_eptrs[j].RelativeVertex(0)); + if (nullptr != vertex1) + break; + switch (j) + { + case 1: + { + edge0 = sit.CurrentEdge(0); + if (nullptr==edge0) + return ON_SUBD_RETURN_ERROR(false); + vertex1 = m_fsh.AllocateVertex(edge0,subd_type,bUseSavedSubdivisionPoint,3,2); + } + break; + case 2: + vertex1 = m_fsh.AllocateVertex(face0,subd_type,bUseSavedSubdivisionPoint,3,2); + break; + case 3: + { + edge0 = sit.CurrentEdge(1); + if (nullptr == edge0) + return ON_SUBD_RETURN_ERROR(false); + vertex1 = m_fsh.AllocateVertex(edge0,subd_type,bUseSavedSubdivisionPoint,3,2); + } + break; + } + break; + } + if ( nullptr == vertex1 ) return ON_SUBD_RETURN_ERROR(false); + face1_corners[j] = vertex1; + } - edge1 = ON_SUBD_EDGE_POINTER(face1_eptrs[0].m_ptr); - if ( nullptr == edge1 ) - return ON_SUBD_RETURN_ERROR(false); - vertex1 = const_cast(edge1->m_vertex[1]); - if ( nullptr == vertex1 ) - return ON_SUBD_RETURN_ERROR(false); - ON_SubDVertex* vertex2 = m_fsh.AllocateVertex(face0,subd_type,bUseSavedSubdivisionPoint,3,2); - if ( nullptr == vertex2 ) - return ON_SUBD_RETURN_ERROR(false); - // vertex1 is from subdividing an edge that radiates out from the input face. - edge1 = m_fsh.AllocateEdge( vertex1, at_crease2_weight, vertex2, ON_SubDSectorType::IgnoredSectorWeight ); - if ( nullptr == edge1 ) - return ON_SUBD_RETURN_ERROR(false); - face1_eptrs[1] = ON_SubDEdgePtr::Create(edge1,0); - ON_SubDVertex* vertex3 = m_fsh.AllocateVertex(edge0,subd_type,bUseSavedSubdivisionPoint,3,2); - if ( nullptr == vertex3 ) - return ON_SUBD_RETURN_ERROR(false); - edge1 = m_fsh.AllocateEdge(vertex2,ON_SubDSectorType::IgnoredSectorWeight,vertex3,ON_SubDSectorType::IgnoredSectorWeight); - if ( nullptr == edge1 ) - return ON_SUBD_RETURN_ERROR(false); - face1_eptrs[2] = ON_SubDEdgePtr::Create(edge1,0); - edge1 = m_fsh.AllocateEdge(vertex1_trio[1],sector_weight,vertex3,ON_SubDSectorType::IgnoredSectorWeight); - if ( nullptr == edge1 ) - return ON_SUBD_RETURN_ERROR(false); - face1_eptrs[3] = ON_SubDEdgePtr::Create(edge1,1); + for (int j = 0; j < 4; j++) + { + edge1 = ON_SUBD_EDGE_POINTER(face1_eptrs[j].m_ptr); + if (nullptr == edge1) + { + switch (j) + { + case 0: + { + edge0 = sit.CurrentEdge(0); + if (nullptr == edge0) + return ON_SUBD_RETURN_ERROR(false); + edge1 = m_fsh.AllocateEdge( + face1_corners[0], + ON_SubDQuadFaceTopology_CopySectorWeight(edge0, vertex0), + face1_corners[1], + at_crease2_weight + ); + if ( 0 == sit_limit && edge0 == sit_first_edge0 ) + sit_first_eptr1 = ON_SubDEdgePtr::Create(edge1, 0); + } + break; + case 1: + edge1 = m_fsh.AllocateEdge( + face1_corners[1], + at_crease2_weight, + face1_corners[2], + ON_SubDSectorType::IgnoredSectorWeight + ); + break; + case 2: + edge1 = m_fsh.AllocateEdge( + face1_corners[2], + ON_SubDSectorType::IgnoredSectorWeight, + face1_corners[3], + at_crease2_weight + ); + break; + case 3: + { + edge0 = sit.CurrentEdge(1); + if (nullptr == edge0) + return ON_SUBD_RETURN_ERROR(false); + edge1 = m_fsh.AllocateEdge( + face1_corners[3], + at_crease2_weight, + face1_corners[0], + ON_SubDQuadFaceTopology_CopySectorWeight(edge0, vertex0) + ); + } + break; + } + if ( nullptr == edge1 ) + return ON_SUBD_RETURN_ERROR(false); + face1_eptrs[j] = ON_SubDEdgePtr::Create(edge1, 0); + } + } face1 = m_fsh.AllocateQuad(zero_face_id, parent_face_id, face1_eptrs); if ( nullptr == face1 ) return ON_SUBD_RETURN_ERROR(false); } - if ( !bFinishedCorner ) + for(;;) + { + if (bFinishedCorner) + break; + + edge0 = sit.CurrentEdge(1); + if (nullptr != edge0 && edge0->IsHardCrease()) + break; + return ON_SUBD_RETURN_ERROR(false); + } } m_center_vertex1 = center_vertex1; diff --git a/opennurbs_subd_matrix.cpp b/opennurbs_subd_matrix.cpp index 6c256dd1..a4140132 100644 --- a/opennurbs_subd_matrix.cpp +++ b/opennurbs_subd_matrix.cpp @@ -755,335 +755,6 @@ bool ON_SubDMatrix::EvaluateCosAndSin( return true; } -#if 0 - -static -unsigned int GetQuadSmoothLevFromCache( - unsigned int F, // sector face count - size_t LPev_capacity, - double* LPev, - size_t LT0ev_capacity, - double* LT0ev, - size_t LT1ev_capacity, - double* LT1ev - ) -{ - // High precision LT0ev coefficients for 3 <= F <= 15 - // "static" is important for performance and minimal stack use. - static const double LT0cache[247] = { -0, 0.86602540378443864676, 0, -0.86602540378443864676, -3.7839351485861304728, 0, -3.7839351485861304728, 0, -1.0000000000000000000, 1.0000000000000000000, 0, --1.0000000000000000000, -1.0000000000000000000, --1.0000000000000000000, 0, 1.0000000000000000000, 0, -0.95105651629515357212, 0.87872622515830928074, -0.58778525229247312917, 0, -0.58778525229247312917, --0.87872622515830928074, -0.95105651629515357212, --0.54308267395372808534, 0, 0.54308267395372808534, 0, -0.86602540378443864676, 0.77709852745195996865, -0.86602540378443864676, 0.38854926372597998433, 0, --0.38854926372597998433, -0.86602540378443864676, --0.77709852745195996865, -0.86602540378443864676, --0.38854926372597998433, 0, 0.38854926372597998433, 0, -0.78183148246802980871, 0.68971219669360492516, -0.97492791218182360702, 0.55310623737345078177, -0.43388373911755812048, 0, -0.43388373911755812048, --0.55310623737345078177, -0.97492791218182360702, --0.68971219669360492516, -0.78183148246802980871, --0.30695080433864438319, 0, 0.30695080433864438319, 0, -0.70710678118654752440, 0.61678125730815119369, -1.0000000000000000000, 0.61678125730815119369, -0.70710678118654752440, 0.25547916179456587087, 0, --0.25547916179456587087, -0.70710678118654752440, --0.61678125730815119369, -1.0000000000000000000, --0.61678125730815119369, -0.70710678118654752440, --0.25547916179456587087, 0, 0.25547916179456587087, 0, -0.64278760968653932632, 0.55622544001433811680, -0.98480775301220805937, 0.63251623261284041596, -0.86602540378443864676, 0.41284565033689650257, -0.34202014332566873304, 0, -0.34202014332566873304, --0.41284565033689650257, -0.86602540378443864676, --0.63251623261284041596, -0.98480775301220805937, --0.55622544001433811680, -0.64278760968653932632, --0.21967058227594391339, 0, 0.21967058227594391339, 0, -0.58778525229247312917, 0.50566567125052178998, -0.95105651629515357212, 0.62503714355370602145, -0.95105651629515357212, 0.50566567125052178998, -0.58778525229247312917, 0.19314709947366877925, 0, --0.19314709947366877925, -0.58778525229247312917, --0.50566567125052178998, -0.95105651629515357212, --0.62503714355370602145, -0.95105651629515357212, --0.50566567125052178998, -0.58778525229247312917, --0.19314709947366877925, 0, 0.19314709947366877925, 0, -0.54064081745559758211, 0.46306074794954127410, -0.90963199535451837141, 0.60648060252697850912, -0.98982144188093273238, 0.55734715098926683027, -0.75574957435425828377, 0.33125991703925043383, -0.28173255684142969771, 0, -0.28173255684142969771, --0.33125991703925043383, -0.75574957435425828377, --0.55734715098926683027, -0.98982144188093273238, --0.60648060252697850912, -0.90963199535451837141, --0.46306074794954127410, -0.54064081745559758211, --0.17262237772902294759, 0, 0.17262237772902294759, 0, -0.50000000000000000000, 0.42679488752454714383, -0.86602540378443864676, 0.58301265856385358856, -1.0000000000000000000, 0.58301265856385358856, -0.86602540378443864676, 0.42679488752454714383, -0.50000000000000000000, 0.15621777103930644473, 0, --0.15621777103930644473, -0.50000000000000000000, --0.42679488752454714383, -0.86602540378443864676, --0.58301265856385358856, -1.0000000000000000000, --0.58301265856385358856, -0.86602540378443864676, --0.42679488752454714383, -0.50000000000000000000, --0.15621777103930644473, 0, 0.15621777103930644473, 0, -0.46472317204376854566, 0.39561924119941590595, -0.82298386589365639458, 0.55783106163446715155, -0.99270887409805399280, 0.59225050844211620747, -0.93501624268541482344, 0.49099250115803095369, -0.66312265824079520238, 0.27725402895972208383, -0.23931566428755776715, 0, -0.23931566428755776715, --0.27725402895972208383, -0.66312265824079520238, --0.49099250115803095369, -0.93501624268541482344, --0.59225050844211620747, -0.99270887409805399280, --0.55783106163446715155, -0.82298386589365639458, --0.39561924119941590595, -0.46472317204376854566, --0.14277582033427973597, 0, 0.14277582033427973597, 0, -0.43388373911755812048, 0.36857162184070541924, -0.78183148246802980871, 0.53260142488428001573, -0.97492791218182360702, 0.59114298380170474948, -0.97492791218182360702, 0.53260142488428001573, -0.78183148246802980871, 0.36857162184070541924, -0.43388373911755812048, 0.13154168885727777825, 0, --0.13154168885727777825, -0.43388373911755812048, --0.36857162184070541924, -0.78183148246802980871, --0.53260142488428001573, -0.97492791218182360702, --0.59114298380170474948, -0.97492791218182360702, --0.53260142488428001573, -0.78183148246802980871, --0.36857162184070541924, -0.43388373911755812048, --0.13154168885727777825, 0, 0.13154168885727777825, 0, -0.40673664307580020775, 0.34490678248944930474, -0.74314482547739423501, 0.50817545082738690904, -0.95105651629515357212, 0.58357596708823109868, -0.99452189536827333692, 0.55807089701829604578, -0.86602540378443864676, 0.43607029893896101468, -0.58778525229247312917, 0.23866918459878179394, -0.20791169081775933710, 0, -0.20791169081775933710, --0.23866918459878179394, -0.58778525229247312917, --0.43607029893896101468, -0.86602540378443864676, --0.55807089701829604578, -0.99452189536827333692, --0.58357596708823109868, -0.95105651629515357212, --0.50817545082738690904, -0.74314482547739423501, --0.34490678248944930474, -0.40673664307580020775, --0.12200059807933503110, 0, 0.12200059807933503110 - }; - - - // High precision LT1ev coefficients for 3 <= F <= 15 - // "static" is important for performance and minimal stack use. - static const double LT1cache[sizeof(LT0cache) / sizeof(LT0cache[0])] = { -0, 0.50000000000000000000, -4.3693119532645779871, -0.50000000000000000000, 2.1846559766322889935, --1.0000000000000000000, 2.1846559766322889935, 0, 0, -1.0000000000000000000, 1.0000000000000000000, 1.0000000000000000000, -0, -1.0000000000000000000, -1.0000000000000000000, --1.0000000000000000000, 0, -0.30901699437494742410, -0.28551545815032630017, 0.80901699437494742410, -0.92394743120145227623, 0.80901699437494742410, -0.28551545815032630017, -0.30901699437494742410, --0.74748917375105243829, -1.0000000000000000000, --0.74748917375105243829, 0, -0.50000000000000000000, 0, -0.50000000000000000000, 0.67298706601687631227, -1.0000000000000000000, 0.67298706601687631227, -0.50000000000000000000, 0, -0.50000000000000000000, --0.67298706601687631227, -1.0000000000000000000, --0.67298706601687631227, 0, -0.62348980185873353053, --0.15742230810261087253, 0.22252093395631440429, -0.44108750553020115113, 0.90096886790241912624, -0.70744943095338716474, 0.90096886790241912624, -0.44108750553020115113, 0.22252093395631440429, --0.15742230810261087253, -0.62348980185873353053, --0.63738991290428386096, -1.0000000000000000000, --0.63738991290428386096, 0, -0.70710678118654752440, --0.25547916179456587087, 0, 0.25547916179456587087, -0.70710678118654752440, 0.61678125730815119369, -1.0000000000000000000, 0.61678125730815119369, -0.70710678118654752440, 0.25547916179456587087, 0, --0.25547916179456587087, -0.70710678118654752440, --0.61678125730815119369, -1.0000000000000000000, --0.61678125730815119369, 0, -0.76604444311897803520, --0.32113690752239614989, -0.17364817766693034885, -0.11152967754571525573, 0.50000000000000000000, -0.49201028697588941391, 0.93969262078590838405, -0.64227381504479229978, 0.93969262078590838405, -0.49201028697588941391, 0.50000000000000000000, -0.11152967754571525573, -0.17364817766693034885, --0.32113690752239614989, -0.76604444311897803520, --0.60353996452160466964, -1.0000000000000000000, --0.60353996452160466964, 0, -0.80901699437494742410, --0.36738761511588183857, -0.30901699437494742410, 0, -0.30901699437494742410, 0.36738761511588183857, -0.80901699437494742410, 0.59444564830326145327, -1.0000000000000000000, 0.59444564830326145327, -0.80901699437494742410, 0.36738761511588183857, -0.30901699437494742410, 0, -0.30901699437494742410, --0.36738761511588183857, -0.80901699437494742410, --0.59444564830326145327, -1.0000000000000000000, --0.59444564830326145327, 0, -0.84125353283118116886, --0.40124442216535750795, -0.41541501300188642553, --0.087198746372372473079, 0.14231483827328514044, -0.25453191527694053569, 0.65486073394528506406, -0.51545049226239871039, 0.95949297361449738989, -0.61271717995368812142, 0.95949297361449738989, -0.51545049226239871039, 0.65486073394528506406, -0.25453191527694053569, 0.14231483827328514044, --0.087198746372372473079, -0.41541501300188642553, --0.40124442216535750795, -0.84125353283118116886, --0.58789782897845332576, -1.0000000000000000000, --0.58789782897845332576, 0, -0.86602540378443864676, --0.42679488752454714383, -0.50000000000000000000, --0.15621777103930644473, 0, 0.15621777103930644473, -0.50000000000000000000, 0.42679488752454714383, -0.86602540378443864676, 0.58301265856385358856, -1.0000000000000000000, 0.58301265856385358856, -0.86602540378443864676, 0.42679488752454714383, -0.50000000000000000000, 0.15621777103930644473, 0, --0.15621777103930644473, -0.50000000000000000000, --0.42679488752454714383, -0.86602540378443864676, --0.58301265856385358856, -1.0000000000000000000, --0.58301265856385358856, 0, -0.88545602565320989590, --0.44656180955519116409, -0.56806474673115580251, --0.21155741640550385622, -0.12053668025532305335, -0.071912231299433902890, 0.35460488704253562597, -0.33890765345000607527, 0.74851074817110109863, -0.52826341647516159444, 0.97094181742605202716, -0.59660039705015990059, 0.97094181742605202716, -0.52826341647516159444, 0.74851074817110109863, -0.33890765345000607527, 0.35460488704253562597, -0.071912231299433902890, -0.12053668025532305335, --0.21155741640550385622, -0.56806474673115580251, --0.44656180955519116409, -0.88545602565320989590, --0.57926427378898650258, -1.0000000000000000000, --0.57926427378898650258, 0, -0.90096886790241912624, --0.46217419537626135604, -0.62348980185873353053, --0.25648732816499374939, -0.22252093395631440429, 0, -0.22252093395631440429, 0.25648732816499374939, -0.62348980185873353053, 0.46217419537626135604, -0.90096886790241912624, 0.57632179499872958303, -1.0000000000000000000, 0.57632179499872958303, -0.90096886790241912624, 0.46217419537626135604, -0.62348980185873353053, 0.25648732816499374939, -0.22252093395631440429, 0, -0.22252093395631440429, --0.25648732816499374939, -0.62348980185873353053, --0.46217419537626135604, -0.90096886790241912624, --0.57632179499872958303, -1.0000000000000000000, --0.57632179499872958303, 0, -0.91354545764260089550, --0.47472345966636156286, -0.66913060635885821383, --0.29339523333075126287, -0.30901699437494742410, --0.061336305740236105673, 0.10452846326765347140, -0.18132822633561029999, 0.50000000000000000000, -0.39263946076280856068, 0.80901699437494742410, -0.53605976540659766854, 0.97814760073380563793, -0.58679046666150252574, 0.97814760073380563793, -0.53605976540659766854, 0.80901699437494742410, -0.39263946076280856068, 0.50000000000000000000, -0.18132822633561029999, 0.10452846326765347140, --0.061336305740236105673, -0.30901699437494742410, --0.29339523333075126287, -0.66913060635885821383, --0.47472345966636156286, -0.91354545764260089550, --0.57396768709841886067, -1.0000000000000000000, --0.57396768709841886067}; - - - if (F < 2) - return ON_SUBD_RETURN_ERROR(0); - - const unsigned int R = 1 + 2 * F; - - if (nullptr != LPev) - { - // In this case, LPev values are calculated accurately using double precision arithmetic. - // Lpev[] - const double f = F; - LPev[0] = f / (5.0 + f); // center point coefficient - const double y = f*(f + 5.0); - const double p = 4.0 / y; // edge point coefficient - const double q = 1.0 / y; // face point coefficient - for (unsigned int i = 1; i < R; i++) - { - LPev[i] = p; - i++; - LPev[i] = q; - } - } - - if (nullptr != LT0ev || nullptr != LT1ev) - { - if (2 == F) - { - if (nullptr != LT1ev) - { - for (unsigned int i = 0; i < R; i++) - LT0ev[i] = 0.0; - } - if (nullptr != LT1ev) - { - for (unsigned int i = 0; i < R; i++) - LT1ev[i] = 0.0; - } - } - else - { - if (F > 15) - return 0; // not an error - - const unsigned int R = 1 + 2 * F; - - if (R > LPev_capacity || R > LT0ev_capacity || R > LT1ev_capacity) - return ON_SUBD_RETURN_ERROR(0); - - const unsigned int cache_capacity = (unsigned int)(sizeof(LT0cache) / sizeof(LT0cache[0])); - const unsigned int cache_offset = (F - 3)*(F + 3); - if (cache_capacity < cache_offset + R) - { - // cached values are damaged. - return ON_SUBD_RETURN_ERROR(0); - } - - const double* LT0src = LT0cache + cache_offset; - const double* LT1src = LT1cache + cache_offset; - - if (0.0 != LT0src[0] || 0.0 != LT1src[0]) - { - // cached values are damaged. - return ON_SUBD_RETURN_ERROR(0); - } - - if (nullptr != LT0ev && nullptr != LT1ev) - { - for (unsigned int i = 0; i < R; i++) - { - LT0ev[i] = LT0src[i]; - LT1ev[i] = LT1src[i]; - } - } - else if (nullptr != LT0ev) - { - for (unsigned int i = 0; i < R; i++) - LT0ev[i] = LT0src[i]; - } - else if (nullptr != LT1ev) - { - for (unsigned int i = 0; i < R; i++) - LT1ev[i] = LT1src[i]; - } - } - } - - return R; -} - -#endif - unsigned int ON_SubDSectorType::GetSubdivisionMatrix( size_t S_capacity, @@ -2467,10 +2138,9 @@ const ON_SubDMatrix& ON_SubDMatrix::FromCache( ON_SubDSectorType sector_type ) { -#if defined(OPENNURBS_SLEEPLOCK_AVAILABLE) + // The ON_SubDMatrix cache is a global resource for the entire application. // This is a thread safe function that manages the ON_SubDMatrix cache. static ON_SleepLock lock; -#endif // The sector_type.Hash() is used to find elements in the cache. static ON_SubDMatrixHashElement* hash_table[256] = { 0 }; @@ -2490,12 +2160,10 @@ const ON_SubDMatrix& ON_SubDMatrix::FromCache( // We need to add this case to the cache. -#if defined(OPENNURBS_SLEEPLOCK_AVAILABLE) // Lock the cache - bool bReturnLock = lock.GetLock(0,ON_SleepLock::ThirtySeconds); + bool bReturnLock = lock.GetLock(0,30*ON_SleepLock::OneSecond); if (false == bReturnLock) bReturnLock = lock.GetLock(0, ON_SleepLock::OneSecond); -#endif // see if another thread made the sector matrix while we waited hash_element = FindMatrixHashElement(sector_type,hash_table[hash_index]); @@ -2553,11 +2221,9 @@ const ON_SubDMatrix& ON_SubDMatrix::FromCache( } } -#if defined(OPENNURBS_SLEEPLOCK_AVAILABLE) // Unlock the cache if (bReturnLock) lock.ReturnLock(); -#endif if ( nullptr != hash_element ) return hash_element->m_sm; diff --git a/opennurbs_subd_mesh.cpp b/opennurbs_subd_mesh.cpp index b2bfaba5..084e1110 100644 --- a/opennurbs_subd_mesh.cpp +++ b/opennurbs_subd_mesh.cpp @@ -26,6 +26,14 @@ //////////////////////////////////////////////////////////////// */ +void ON_SubD::ClearLimitSurfaceMesh() const +{ + const ON_SubDLevel* level = ActiveLevelConstPointer(); + + if ( nullptr != level ) + level->m_limit_mesh = ON_SubDLimitMesh::Empty; +} + bool ON_SubDFaceRegionBreakpoint( unsigned int level0_face_id, const class ON_SubDComponentRegionIndex& region_index @@ -79,353 +87,6 @@ bool ON_SubDComponentRegionBreakpoint(const ON_SubDComponentRegion* component_re return false; } - -bool ON_SubDLimitNurbsFragment::IsEmpty() const -{ - return 0 == SetBispanCount(); -} - -unsigned int ON_SubDLimitNurbsFragment::MaximumBispanCount() const -{ - if (ON_SubDLimitNurbsFragment::Type::BicubicSingle == m_type) - return 1; - if (ON_SubDLimitNurbsFragment::Type::BicubicQuadrant == m_type) - return 4; - return 0; -} - -unsigned int ON_SubDLimitNurbsFragment::SetBispanCount() const -{ - unsigned int set_bispan_count = 0; - const unsigned int imax = MaximumBispanCount(); - for (unsigned int i = 0; i < imax; i++) - { - if ( - ON_SubDLimitNurbsFragment::BispanType::Exact == m_bispan_type[i] - || ON_SubDLimitNurbsFragment::BispanType::Approximate == m_bispan_type[i] - ) - { - set_bispan_count++; - } - } - - return set_bispan_count; -} - - -unsigned int ON_SubDLimitNurbsFragment::UnsetBispanCount() const -{ - return MaximumBispanCount() - SetBispanCount(); -} - -static bool Internal_CheckNurbsSurfaceCVs( - const ON_NurbsSurface& s - ) -{ - for (int i = 0; i < s.m_cv_count[0]; i++) - { - for (int j = 0; j < s.m_cv_count[1]; j++) - { - double * cv = s.CV(i, j); - for (unsigned k = 0; k < 3; k++) - { - if (!ON_IsValid(cv[k])) - { - return ON_SUBD_RETURN_ERROR(false); - } - } - } - } - return true; -} - -bool ON_SubDLimitNurbsFragment::IsApproximate() const -{ - const unsigned int imax = MaximumBispanCount(); - for (unsigned int i = 0; i < imax; i++) - { - if (ON_SubDLimitNurbsFragment::BispanType::Approximate == m_bispan_type[i]) - return true; - } - return false; -} - -ON_NurbsSurface* ON_SubDLimitNurbsFragment::GetSurface( - ON_NurbsSurface* destination_surface -) const -{ - const unsigned int bispan_count = SetBispanCount(); - if (bispan_count != MaximumBispanCount()) - return nullptr; - - const double knots[7] = { -2,-1,0,1,2,3,4 }; - ON_NurbsSurface patch_srf; - patch_srf.m_dim = 3; - patch_srf.m_is_rat = 0; - patch_srf.m_order[0] = 4; - patch_srf.m_order[1] = 4; - patch_srf.m_knot[0] = (double*)knots; - patch_srf.m_knot[1] = (double*)knots; - patch_srf.m_cv_stride[0] = 5 * 3; - patch_srf.m_cv_stride[1] = 3; - - patch_srf.m_cv_count[0] = (1 == bispan_count) ? 4 : 5; - patch_srf.m_cv_count[1] = patch_srf.m_cv_count[0]; - patch_srf.m_cv = (double*)m_patch_cv[0][0]; - ON_NurbsSurface* surface = nullptr; - if (destination_surface) - { - surface = destination_surface; - *surface = patch_srf; - } - else - { - surface = new ON_NurbsSurface(patch_srf); - } - - Internal_CheckNurbsSurfaceCVs(*surface); - - return surface; -} - -ON_NurbsSurface* ON_SubDLimitNurbsFragment::GetQuadrantSurface( - unsigned int quadrant_index, - ON_NurbsSurface* destination_surface -) const -{ - if (quadrant_index >= 4) - return nullptr; - - if ( - ON_SubDLimitNurbsFragment::BispanType::Exact != m_bispan_type[quadrant_index] - && ON_SubDLimitNurbsFragment::BispanType::Approximate != m_bispan_type[quadrant_index] - ) - return nullptr; - - //const ON_2dex cvdex[4] = { { 0, 0 }, { 1, 0 }, { 1, 1 }, { 0, 1 } }; - const ON_2dex cvdex( - (1 == quadrant_index || 2 == quadrant_index) ? 1 : 0, - (2 == quadrant_index || 3 == quadrant_index) ? 1 : 0 - ); - - const double knots[7] = {-2,-1,0,1,2,3,4}; - ON_NurbsSurface patch_srf; - patch_srf.m_dim = 3; - patch_srf.m_is_rat = 0; - patch_srf.m_order[0] = 4; - patch_srf.m_order[1] = 4; - patch_srf.m_knot[0] = (double*)(knots+cvdex.i); - patch_srf.m_knot[1] = (double*)(knots+cvdex.j); - patch_srf.m_cv_stride[0] = 5*3; - patch_srf.m_cv_stride[1] = 3; - - patch_srf.m_cv_count[0] = 4; - patch_srf.m_cv_count[1] = 4; - patch_srf.m_cv = (double*)m_patch_cv[cvdex.i][cvdex.j]; - - ON_NurbsSurface* surface = nullptr; - if (destination_surface) - { - surface = destination_surface; - *surface = patch_srf; - } - else - { - surface = new ON_NurbsSurface(patch_srf); - } - - Internal_CheckNurbsSurfaceCVs(*surface); - - return surface; -} - -const ON_SubDComponentRegion Internal_CreateSubdivisionEdgeRegion(unsigned short subdivision_count, bool bReversedEdge ) -{ - return ON_SubDComponentRegion::CreateSubdivisionRegion(ON_SubDComponentPtr::Type::Edge, bReversedEdge, subdivision_count, true); -} - -class Internal_SubQuadTransientComponents -{ -public: - Internal_SubQuadTransientComponents(ON_SubDFaceIterator& fit) - { - // The edge_transient_vertex_id[] array stores ON_2udex - // that are used to record the transient vertex id - // for a level 0 edge subdivision point. - // ON_2udex.i = level 0 edge id - // ON_2udex.j = transient vertex id to use for the edge subdivision vertex - // - // These are required when an edge is shared by 2 N-gons with N != 4 because - // each N-gon must use the same transient vertex id in order for the - // final NURBS surface to be easily and quickly joined into an ON_Brep. - - m_edge_transient_vertex_id_map.Reserve(64); - ON_2udex u; - u.i = 0; - u.j = 0; - for (const ON_SubDFace* face = fit.FirstFace(); nullptr != face; face = fit.NextFace()) - { - const unsigned int edge_count = face->EdgeCount(); - if (edge_count <= 0 || 4 == edge_count) - continue; - for (unsigned int fei = 0; fei < edge_count; fei++) - { - const ON_SubDEdge* e = face->Edge(fei); - if (nullptr == e) - continue; - u.i = e->m_id; - m_edge_transient_vertex_id_map.Append(u); - } - } - - const unsigned int count0 = m_edge_transient_vertex_id_map.UnsignedCount(); - if (count0 <= 0) - return; - - m_edge_transient_vertex_id_map.QuickSort(ON_2udex::DictionaryCompare); - unsigned int count1 = 0; - unsigned int prev_edge_id = 0; - ON_2udex* a = m_edge_transient_vertex_id_map.Array(); - for (unsigned int i = 0; i < count0; i++) - { - u = a[i]; - if (u.i <= prev_edge_id) - continue; // paired edge - prev_edge_id = u.i; - u.j = ON_SubDComponentRegion::NewTransientId(); - a[count1++] = u; - } - m_edge_transient_vertex_id_map.SetCount(count1); - } - - ~Internal_SubQuadTransientComponents() = default; - -public: - static const Internal_SubQuadTransientComponents Empty; - -public: - void Initialize( - const ON_SubDFace* face - ) - { - m_face = nullptr; - m_edge_count = 0; - - if (m_edge_transient_vertex_id_map.UnsignedCount() <= 0) - return; - if (nullptr == face) - return; - const unsigned short edge_count = face->m_edge_count; - if (edge_count < 3 || 4 == edge_count) - return; - - // Used to create edge regions for subdivision edges because their is no "real" - // edge to reference. - // We need a unique id and region so merging and brep joining work correctly. - m_edge_count = edge_count; - m_fei = edge_count-1; - m_face = face; - m_radial_edge_region[2] = ON_SubDComponentRegion::CreateSubdivisionRegion(ON_SubDComponentPtr::Type::Edge, false, 1, true); - m_radial_edge_region[1] = m_radial_edge_region[2]; - m_face_center_vertex_id = ON_SubDComponentRegion::NewTransientId(); - m_radial_vertex_id[2] = Internal_EdgeTransientVertexId(); - m_radial_vertex_id[1] = m_radial_vertex_id[2]; - - } - - void NextSubQuad() - { - if (0 == m_edge_count || nullptr == m_face || 0 == m_edge_transient_vertex_id_map.UnsignedCount() ) - return; - m_fei = (m_fei + 1) % m_edge_count; - const bool bCreateSubdivisionEdgeRegion = (m_fei + 1 < m_edge_count); - m_radial_edge_region[0] = m_radial_edge_region[1]; - m_radial_edge_region[1] - = bCreateSubdivisionEdgeRegion - ? ON_SubDComponentRegion::CreateSubdivisionRegion(ON_SubDComponentPtr::Type::Edge, false, 1, true) - : m_radial_edge_region[2]; - m_radial_vertex_id[0] = m_radial_vertex_id[1]; - m_radial_vertex_id[1] - = bCreateSubdivisionEdgeRegion - ? Internal_EdgeTransientVertexId() - : m_radial_vertex_id[2]; - } - -public: - const ON_SubDFace* m_face = nullptr; - ON_SubDComponentRegion m_radial_edge_region[3] = {}; - unsigned int m_face_center_vertex_id = 0; - unsigned int m_radial_vertex_id[3] = {}; - unsigned short m_edge_count = 0; - unsigned short m_fei = 0; - - ON_SimpleArray< ON_2udex > m_edge_transient_vertex_id_map; - -private: - Internal_SubQuadTransientComponents() = delete; - Internal_SubQuadTransientComponents(const Internal_SubQuadTransientComponents&) = delete; - Internal_SubQuadTransientComponents& operator= (const Internal_SubQuadTransientComponents&) = delete; - -private: - unsigned int Internal_EdgeTransientVertexId() const - { - const ON_SubDEdge* e = (m_edge_count>0) ? m_face->Edge((m_fei+1)%m_edge_count) : nullptr; - if (nullptr == e) - return 0; - const ON_2udex key(e->m_id, 0); - int i = m_edge_transient_vertex_id_map.BinarySearch(&key, ON_2udex::CompareFirstIndex); - return (i >= 0) ?m_edge_transient_vertex_id_map[i].j : 0; - } -}; - - -static void Internal_SetLevel0FaceAndEdgeRegion( - const ON_SubDFace* face, - unsigned int qi, - const Internal_SubQuadTransientComponents& face_transient_components, - ON_SubDFaceRegion& face_region -) -{ - face_region = ON_SubDFaceRegion::Empty; - const unsigned int N = face->EdgeCount(); - face_region.m_face_region.SetLevel0Face(face); - if ( 4 == N ) - { - for (unsigned int fei = 0; fei < 4; fei++) - face_region.m_edge_region[fei].SetLevel0EdgePtr(face->EdgePtr(fei)); - for (unsigned int fvi = 0; fvi < 4; fvi++) - { - const ON_SubDVertex* v = face->Vertex(fvi); - if (nullptr != v) - face_region.m_vertex_id[fvi] = v->m_id; - } - } - else if (N >= 3 && qi < N) - { - face_region.Push(qi); // original N-gon (N != 4) was subdivided into N quads. - - const ON_SubDVertex* v = face->Vertex((qi+1)%N); - if (nullptr != v) - face_region.m_vertex_id[2] = v->m_id; - face_region.m_vertex_id[0] = face_transient_components.m_face_center_vertex_id; - face_region.m_vertex_id[1] = face_transient_components.m_radial_vertex_id[0]; - face_region.m_vertex_id[3] = face_transient_components.m_radial_vertex_id[1]; - - face_region.m_edge_region[0] = face_transient_components.m_radial_edge_region[0]; - face_region.m_edge_region[1].SetLevel0EdgePtr(face->EdgePtr(qi)); - face_region.m_edge_region[1].PushAdjusted(1); - face_region.m_edge_region[2].SetLevel0EdgePtr(face->EdgePtr((qi+1)%N)); - face_region.m_edge_region[2].PushAdjusted(0); - face_region.m_edge_region[3] = face_transient_components.m_radial_edge_region[1]; - face_region.m_edge_region[3].m_level0_component = face_region.m_edge_region[3].m_level0_component.SetMark(); - } - else - { - ON_SUBD_ERROR("Unexpected parameters."); - } -} - - const ON_SubDComponentRegion ON_SubDComponentRegion::Create( const class ON_SubDFace* level0_face ) @@ -846,9 +507,11 @@ bool ON_SubDFaceRegion::IsValid( } } + const unsigned short face_region_subdivision_count = m_face_region.SubdivisionCount(); + bool bPeristentVertex[4] = { bIsQuad, bIsQuad, true, bIsQuad }; bool bPeristentEdge[4] = { bIsQuad, true, true, bIsQuad }; - for (unsigned short i = bIsQuad?0:1; i < m_face_region.SubdivisionCount() && i < ON_SubDComponentRegionIndex::IndexCapacity; i++) + for (unsigned short i = bIsQuad?0:1; i < face_region_subdivision_count && i < ON_SubDComponentRegionIndex::IndexCapacity; i++) { const unsigned short r = m_face_region.m_region_index.m_index[i]; if (r >= 4) @@ -866,6 +529,9 @@ bool ON_SubDFaceRegion::IsValid( break; } + unsigned int fei[4] = { ON_UNSET_UINT_INDEX,ON_UNSET_UINT_INDEX,ON_UNSET_UINT_INDEX,ON_UNSET_UINT_INDEX }; + const ON_SubDVertex* fv[4] = {}; + for (int ei = 0; ei < 4; ei++) { const bool bEmptyEdge = m_edge_region[ei].IsEmptyRegion(); @@ -915,6 +581,20 @@ bool ON_SubDFaceRegion::IsValid( ON_SUBD_ERROR("Unexpected value for m_edge_region[].m_level0_component_id"); return false; } + fei[ei] = face->EdgeArrayIndex(edge); + if (ON_UNSET_UINT_INDEX == fei[ei]) + { + if (false == bSilentError) + ON_SUBD_ERROR("m_edge_region[].m_level0_component.Edge() not in face."); + return false; + } + fv[ei] = face->Vertex(ei); + if ( nullptr == fv[ei] ) + { + if (false == bSilentError) + ON_SUBD_ERROR("m_edge_region[].m_level0_component.Edge()->Vertex() is missing."); + return false; + } } else { @@ -927,8 +607,7 @@ bool ON_SubDFaceRegion::IsValid( } } - - for (int vi = 0; vi < 4; vi++) + for (unsigned int vi = 0; vi < 4; vi++) { if (bPeristentVertex[vi]) { @@ -937,6 +616,29 @@ bool ON_SubDFaceRegion::IsValid( if (false == bSilentError) ON_SUBD_ERROR("m_vertex_id[] missing a persistent vertex id."); return false; + } + + if (face_region_subdivision_count <= 1) + { + unsigned int fvi = ON_UNSET_UINT_INDEX; + + if (0 == face_region_subdivision_count) + fvi = vi; + else if (1 == face_region_subdivision_count) + fvi = m_face_region.m_region_index.m_index[0]; + const ON_SubDVertex* v = face->Vertex(fvi); + if (nullptr == v) + { + if (false == bSilentError) + ON_SUBD_ERROR("face->Vertex() is nullptr."); + return false; + } + if (v->m_id != m_vertex_id[vi]) + { + if (false == bSilentError) + ON_SUBD_ERROR("m_vertex_id[] and face->Vertex()->m_id are different."); + return false; + } } } else if ( 0 != m_vertex_id[vi] ) @@ -966,8 +668,8 @@ bool ON_SubDComponentRegion::IsPersistentId() const static unsigned int Internal_TransientIdHelper( bool bReset ) -{ - static unsigned int src = 0; // shoule be a "atomic" unsigned - but not critical +{ + static std::atomic src(0); if (bReset) { @@ -978,8 +680,14 @@ static unsigned int Internal_TransientIdHelper( unsigned int transient_id = ++src; if (0 != (ON_SubDComponentRegion::TransientIdBit & transient_id)) { - src = 1; - transient_id = 1; // because other threads may have modified src. + // This should be extremely rare. + // Calculations that use transient_id are time consuming, src should be small + // after the first thread sets it back to zero. + static ON_SleepLock global_resource_lock; + ON_SleepLockGuard guard(global_resource_lock); + if (0 != (ON_SubDComponentRegion::TransientIdBit & src)) + src = 0; + transient_id = ++src; } transient_id |= ON_SubDComponentRegion::TransientIdBit; return transient_id; @@ -1331,36 +1039,6 @@ void ON_SubDComponentRegionIndex::FromCompressedRegionIndex( } -static ON_ProgressStepCounter CreateFragmentProgressStepCounter( - ON_SubDFaceIterator& fit, - const ON_SubDDisplayParameters& limit_mesh_parameters - ) -{ - unsigned int progress_step_count = 0; - - if (nullptr != limit_mesh_parameters.m_progress_reporter) - { - for (const ON_SubDFace* face = fit.FirstFace(); nullptr != face; face = fit.NextFace()) - { - if ( 4 == face->m_edge_count) - progress_step_count++; - else - progress_step_count += face->m_edge_count; - } - } - - ON_ProgressStepCounter counter = ON_ProgressStepCounter::Create( - limit_mesh_parameters.m_progress_reporter, - progress_step_count, - limit_mesh_parameters.m_progress_reporter_interval[0], - limit_mesh_parameters.m_progress_reporter_interval[1], - 100 - ); - - return counter; - -} - wchar_t* ON_SubDFaceRegion::ToString( wchar_t* s, size_t s_capacity @@ -1426,936 +1104,126 @@ const ON_wString ON_SubDFaceRegion::ToString() const return ON_wString::EmptyString; } -static unsigned int GetQuadLimitSurfaceMeshFragmentsHelper( - ON_SubDFaceIterator& fit, - const ON_SubDDisplayParameters& limit_mesh_parameters, - ON__UINT_PTR fragment_callback_context, - bool(*mesh_fragment_callback_function)(ON__UINT_PTR, const class ON_SubDLimitMeshFragment*) - ) +static bool Internal_Seal3d(const double* src, double* dst, double tol ) { - ON_ProgressStepCounter counter = CreateFragmentProgressStepCounter(fit,limit_mesh_parameters); - - if (nullptr == fit.FirstFace() ) - return ON_SUBD_RETURN_ERROR(0); - - if ( nullptr == mesh_fragment_callback_function ) - return ON_SUBD_RETURN_ERROR(0); - - unsigned int display_density = limit_mesh_parameters.m_display_density; - //const bool bUseMultipleThreads = limit_mesh_parameters.m_bUseMultipleThreads; - - - ON_SubDManagedLimitMeshFragment fragment; - ON_SubDManagedLimitMeshFragment sub_fragment; - ON_SubDQuadFaceMesher qfm; - ON_SubDQuadFaceMesher sub_qfm; - ON_SubDFaceNeighborhood fnbd; - - qfm.m_output = ON_SubDQuadFaceMesher::Output::mesh; - - ON_SubDLimitMeshFragment* callback_fragment = nullptr; - const ON_SubDFace** quad_faces = nullptr; - unsigned int quad_face_count = 0; - - unsigned int fragment_count = 0; - - if (0 == display_density) +#if 1 + // coded this way for debugging. + // Release build optimization will inline this static and doubles will be in registers. + const double d = (fabs(src[0] - dst[0]) + fabs(src[1] - dst[1]) + fabs(src[2] - dst[2])); + if (d <= tol) { - // make sure all faces are quads. If not, increase density to 1 - for (const ON_SubDFace* face = fit.FirstFace(); nullptr != face; face = fit.NextFace()) - { - if (4 == face->m_edge_count) - continue; - display_density = 1; - break; - } - } - - Internal_SubQuadTransientComponents face_transient_components(fit); - - // TODO - // - // Support multiple threads by adding more fragment, sub_fragment qfm, sub_qfm and fnbd - // resources and managing them. - // - const unsigned int subquad_display_density = (display_density > 1) ? (display_density - 1) : 0; - const unsigned short unset_face_edge_index = 0xFFFFU; - for (const ON_SubDFace* face = fit.FirstFace(); nullptr != face; face = fit.NextFace()) - { - if (face->m_edge_count < 3) - continue; - - face_transient_components.Initialize(face); - - if (4 == face->m_edge_count) - { - // face is a quad - quad_faces = &face; - quad_face_count = 1; - if (callback_fragment != &fragment) - { - if (0 == fragment.m_P_capacity) - { - fragment.ReserveCapacity(ON_SubD::FacetType::Quad, display_density); - fragment.m_P_count = (unsigned short)ON_SubDLimitMeshFragment::PointCountFromDisplayDensity(ON_SubD::FacetType::Quad,display_density); - if ( fragment.m_P_count > fragment.m_P_capacity) - return ON_SUBD_RETURN_ERROR(0); - fragment.m_face_vertex_index[0] = 0; - fragment.m_face_vertex_index[1] = 1; - fragment.m_face_vertex_index[2] = 2; - fragment.m_face_vertex_index[3] = 3; - } - callback_fragment = &fragment; - qfm.SetDestinationToMeshFragment(display_density, fragment); - } - } - else - { - // face is not a quad. It will be subdivided into quads and each - // of those quads is meshed a a level of display_density-1. - if (false == fnbd.Subdivide(ON_SubD::SubDType::QuadCatmullClark, face)) - continue; - quad_faces = fnbd.m_center_vertex1->m_faces; - quad_face_count = fnbd.m_center_vertex1->m_face_count; - if (quad_face_count < 2) - continue; - if (callback_fragment != &sub_fragment) - { - if (0 == sub_fragment.m_P_capacity) - { - sub_fragment.ReserveCapacity(ON_SubD::FacetType::Quad, subquad_display_density); - sub_fragment.m_P_count = (unsigned short)ON_SubDLimitMeshFragment::PointCountFromDisplayDensity(ON_SubD::FacetType::Quad,subquad_display_density); - if ( sub_fragment.m_P_count > sub_fragment.m_P_capacity) - return ON_SUBD_RETURN_ERROR(0); - sub_fragment.m_face_vertex_index[0] = unset_face_edge_index; - sub_fragment.m_face_vertex_index[1] = unset_face_edge_index; - sub_fragment.m_face_vertex_index[2] = unset_face_edge_index; - sub_fragment.m_face_vertex_index[3] = unset_face_edge_index; - } - callback_fragment = &sub_fragment; - qfm.SetDestinationToMeshFragment(subquad_display_density, sub_fragment); - } - } - - callback_fragment->m_face = face; - callback_fragment->m_face_fragment_count = (unsigned short)quad_face_count; - - for (unsigned int qi = 0; qi < quad_face_count; qi++) - { - face_transient_components.NextSubQuad(); - ON_SubDFaceRegion face_region; - Internal_SetLevel0FaceAndEdgeRegion( - face, - qi, face_transient_components, // qi and subdivsion_edge_region[] are used only when "f" is a level 1 sud quad of "face" - face_region - ); - - const ON_SubDFace* f = quad_faces[qi]; - if (unset_face_edge_index == callback_fragment->m_face_vertex_index[0]) - callback_fragment->m_face_vertex_index[2] = (unsigned short)((qi+1)%quad_face_count); - - callback_fragment->m_face_fragment_index = (unsigned short)qi; - - if (false == qfm.GetLimitMesh(face_region, f)) - continue; - - fragment_count++; - callback_fragment->m_bbox = ON_PointListBoundingBox(3,0,callback_fragment->m_P_count,(int)callback_fragment->m_P_stride,callback_fragment->m_P); - if (false == mesh_fragment_callback_function(fragment_callback_context, callback_fragment)) - return true; - - if (0 == (fragment_count % 16)) - { - if (ON_Terminator::TerminationRequested(limit_mesh_parameters.m_terminator)) - return 0; // not an error - } - counter.IncrementStep(); - } - } - - counter.Finished(); - - return fragment_count; -} - - -static unsigned int GetQuadLimitSurfacePatchFragmentsHelper( - ON_SubDFaceIterator& fit, - const ON_SubDDisplayParameters& limit_mesh_parameters, - ON__UINT_PTR fragment_callback_context, - bool(*begin_face_callback_function)(ON__UINT_PTR ,const ON_SubDFaceRegion&),//, const class ON_SubDFace*, const class ON_SubDFace*, unsigned int), - bool(*patch_fragment_callback_function)(ON__UINT_PTR, const class ON_SubDLimitNurbsFragment*) - ) -{ - ON_ProgressStepCounter counter = CreateFragmentProgressStepCounter(fit,limit_mesh_parameters); - - if (nullptr == fit.FirstFace() ) - return ON_SUBD_RETURN_ERROR(0); - - if ( nullptr == patch_fragment_callback_function ) - return ON_SUBD_RETURN_ERROR(0); - - unsigned int display_density = limit_mesh_parameters.m_display_density; - //const bool bUseMultipleThreads = limit_mesh_parameters.m_bUseMultipleThreads; - - ON_SubDQuadFacePatcher patcher; - ON_SubDQuadFaceMesher qfm; - ON_SubDQuadFaceMesher sub_qfm; - ON_SubDFaceNeighborhood fnbd; - - Internal_SubQuadTransientComponents face_transient_components(fit); - - const ON_SubDFace** quad_faces = nullptr; - unsigned int quad_face_count = 0; - - unsigned int fragment_count = 0; - - if (0 == display_density) - { - // make sure all faces are quads. If not, increase density to 1 - for (const ON_SubDFace* face = fit.FirstFace(); nullptr != face; face = fit.NextFace()) - { - if (4 == face->m_edge_count) - { - display_density = 1; - break; - } - } - } - - patcher.m_fragment_callback_context = fragment_callback_context; - patcher.m_fragment_callback_function = patch_fragment_callback_function; - const unsigned int subquad_display_density = (display_density > 1) ? (display_density - 1) : 0; - - for (const ON_SubDFace* face = fit.FirstFace(); nullptr != face; face = fit.NextFace()) - { - if (face->m_edge_count < 3) - continue; - - face_transient_components.Initialize(face); - - if (4 == face->m_edge_count) - { - // face is a quad - quad_faces = &face; - quad_face_count = 1; - patcher.m_display_density = display_density; - } - else - { - // face is not a quad. It will be subdivided into quads and each - // of those quads is meshed a a level of display_density-1. - if (false == fnbd.Subdivide(ON_SubD::SubDType::QuadCatmullClark, face)) - continue; - quad_faces = fnbd.m_center_vertex1->m_faces; - quad_face_count = fnbd.m_center_vertex1->m_face_count; - patcher.m_display_density = subquad_display_density; - if (quad_face_count < 2) - continue; - } - - patcher.m_patch_fragment = ON_SubDLimitNurbsFragment::Unset; - qfm.SetDestinationToPatchFragment(patcher); - - for (unsigned int qi = 0; qi < quad_face_count; qi++) - { - face_transient_components.NextSubQuad(); - - // Internal_SetLevel0FaceAndEdgeRegion() sets face_region and patcher.m_patch_fragment.m_edge_region[] - // In the case when face is not a quad and "f" is the level 1 subd quad, - // the regions (face_region and patcher.m_patch_fragment.m_edge_region[]) - // are "Pushed()" so they indicate what portion of "face" and it's original edges are being used. - ON_SubDFaceRegion face_region; - Internal_SetLevel0FaceAndEdgeRegion( - face, - qi, face_transient_components, // qi and subdivsion_edge_region[] are used only when "f" is a level 1 sud quad of "face" - face_region - ); - - const ON_SubDFace* f = quad_faces[qi]; - if (nullptr != begin_face_callback_function) - { - // SubD to NURBS case: - // fragment_callback_context = pointer to Internal_SubDNurbsPatchGetter class - // begin_face_callback_function = Internal_SubDNurbsPatchGetter::BeginFaceCallback() - // Internal_SubDNurbsPatchGetter::BeginFaceCallback() flushes accumulated surface content from previous SUbD face. - // Then it initializes fragment_callback_context for current "face" and, it it exits, sub-quad "f" - begin_face_callback_function(fragment_callback_context, face_region);//, face, (f != face) ? f : nullptr, qi); - } - - patcher.m_patch_fragment.m_face_region = face_region; - - if (false == qfm.GetLimitPatches(face_region, f)) - continue; - - fragment_count++; - - if (0 == (fragment_count % 16)) - { - if (ON_Terminator::TerminationRequested(limit_mesh_parameters.m_terminator)) - return 0; // not an error - } - counter.IncrementStep(); - } - } - - counter.Finished(); - - return fragment_count; -} - -class GetLimitSurfaceMesh_context_FragmentMark -{ -public: - class GetLimitSurfaceMesh_context* m_context = nullptr; - - size_t m_point_count0 = 0; - size_t m_quad_count0 = 0; - size_t m_fragment_count0 = 0; - - size_t m_point_count1 = 0; - size_t m_quad_count1 = 0; - size_t m_fragment_count1 = 0; - - unsigned int m_mark_index = 0; - unsigned int m_face_id = 0; - unsigned int m_zero_face_id = 0; - unsigned int m_parent_face_id = 0; - unsigned int m_face_fragment_count = 0; - unsigned int m_face_fragment_index = 0; - - ON__UINT_PTR m_fragment_group_id = 0; - - // points to memory in the m_context that is under construction. - ON_SubDLimitMeshFragment m_fragment = ON_SubDLimitMeshFragment::Empty; - - static int CompareFaceIdAndFragmentIndex( - const GetLimitSurfaceMesh_context_FragmentMark* a, - const GetLimitSurfaceMesh_context_FragmentMark* b - ); -}; - -int GetLimitSurfaceMesh_context_FragmentMark::CompareFaceIdAndFragmentIndex( - const GetLimitSurfaceMesh_context_FragmentMark* a, - const GetLimitSurfaceMesh_context_FragmentMark* b - ) -{ - if ( a == b ) - return 0; - if ( nullptr == a ) - return -1; - if ( nullptr == b ) - return 1; - if ( a->m_face_id < b->m_face_id ) - return -1; - if ( a->m_face_id > b->m_face_id ) - return 1; - if ( a->m_face_fragment_index < b->m_face_fragment_index ) - return -1; - if ( a->m_face_fragment_index > b->m_face_fragment_index ) - return 1; - return 0; -} - -class GetLimitSurfaceMesh_context -{ -public: - GetLimitSurfaceMesh_context() = default; - -#if defined(OPENNURBS_SLEEPLOCK_AVAILABLE) - bool m_bUseMultipleThreads = false; // If true, callback uses the lock - ON_SleepLock m_lock; -#endif - - size_t m_point_capacity = 0; - double* m_points = nullptr; - size_t m_point_stride = 0; - - size_t m_normal_capacity = 0; - double* m_normals = nullptr; - size_t m_normal_stride = 0; - - size_t m_quad_capacity = 0; - - unsigned int* m_quads = nullptr; - size_t m_quad_stride = 0; // >= 4 - - ON__UINT_PTR* m_quad_group_ids = nullptr; - size_t m_quad_group_id_stride = 0; - - // counts are updated as meshing progresses - size_t m_point_count = 0; - size_t m_quad_count = 0; - size_t m_fragment_count = 0; - - ON_SimpleArray< GetLimitSurfaceMesh_context_FragmentMark > m_marks; -}; - -static bool CoincidentPointTest( - unsigned int i, - unsigned int j, - const double* points, - const size_t point_stride, - const double* normals, - const size_t normal_stride - ) -{ - if ( i == j) + *dst++ = *src++; + *dst++ = *src++; + *dst = *src; return true; - - const double* a = points + i*point_stride; - const double* b = points + j*point_stride; - double d = fabs(a[0]-b[0]) + fabs(a[1]-b[1]) + fabs(a[2]-b[2]); - if (!(d <= 1e-8)) - return ON_SUBD_RETURN_ERROR(false); - - a = normals + i*normal_stride; - b = normals + j*normal_stride; - d = fabs(a[0]-b[0]) + fabs(a[1]-b[1]) + fabs(a[2]-b[2]); - if (!(d <= 0.01)) - return ON_SUBD_RETURN_ERROR(false); - + } + return false; +#else + // to see what happens if no "micro gap" sealing occurs. + return true; +#endif +} + + +bool ON_SubDLimitMeshFragment::SealPoints( + bool bTestNearEqual, + const double* src, + double* dst +) +{ + if (bTestNearEqual) + return Internal_Seal3d( src, dst, 1.0e-8 ); + *dst++ = *src++; + *dst++ = *src++; + *dst = *src; return true; } -static bool GetLimitSurfaceMesh_callback(ON__UINT_PTR void_context, const class ON_SubDLimitMeshFragment* fragment) +bool ON_SubDLimitMeshFragment::SealNormals( + bool bTestNearEqual, + const double* src, + double* dst +) { - GetLimitSurfaceMesh_context* context = (GetLimitSurfaceMesh_context*)void_context; - - unsigned int mark_count = 0; - ON_SimpleArray< GetLimitSurfaceMesh_context_FragmentMark > face_fragment_marks; - - // When using multiple threads, - // get the lock, - // quickly reserve the section of the desitinataion arrays that will be used by this fragment, - // and then return the lock. - // SInce the space is now reserved, the actual copying can be done after returning the lock. - GetLimitSurfaceMesh_context_FragmentMark mark; - mark.m_context = context; - mark.m_fragment = *fragment; - if (nullptr != fragment->m_face) - { - mark.m_face_id = fragment->m_face->m_id; - mark.m_zero_face_id = fragment->m_face->m_zero_face_id; - mark.m_parent_face_id = fragment->m_face->m_parent_face_id; - } - mark.m_face_fragment_count = fragment->m_face_fragment_count; - mark.m_face_fragment_index = fragment->m_face_fragment_index; - -#if defined(OPENNURBS_SLEEPLOCK_AVAILABLE) - bool bReleaseLock = false; - if (context->m_bUseMultipleThreads) - { - if (false == context->m_lock.GetLock(0, ON_SleepLock::OneSecond)) - { - // return tru to keep going, but something is really hogging the lock - return ON_SUBD_RETURN_ERROR(true); - } - } -#endif - - mark.m_point_count0 = context->m_point_count; - mark.m_quad_count0 = context->m_quad_count; - mark.m_point_count1 = mark.m_point_count0 + fragment->m_P_count; - mark.m_quad_count1 = mark.m_quad_count0 + fragment->m_grid.m_F_count; - if (mark.m_point_count1 > context->m_point_capacity || mark.m_quad_count1 > context->m_quad_capacity) - { -#if defined(OPENNURBS_SLEEPLOCK_AVAILABLE) - if (bReleaseLock) - context->m_lock.ReturnLock(); -#endif - return ON_SUBD_RETURN_ERROR(false); - } - mark.m_mark_index = context->m_marks.UnsignedCount(); - - mark.m_fragment_group_id = (ON__UINT_PTR)(context->m_fragment_count+1); - - if (mark.m_face_fragment_count <= 1 || 0 == mark.m_face_id || ON_UNSET_UINT_INDEX == mark.m_face_id) - { - mark_count = 1; - } - else if ( context->m_marks.UnsignedCount() + 1 >= mark.m_face_fragment_count ) - { - GetLimitSurfaceMesh_context_FragmentMark* context_fragment_marks = context->m_marks.Array(); - const unsigned int context_mark_count0 = context->m_marks.UnsignedCount(); - - // See if we have all the subfragments for this face - for (unsigned int i = 0; i < context_mark_count0; i++) - { - if ( context_fragment_marks[i].m_face_id == mark.m_face_id ) - mark_count++; - } - - if (mark_count + 1 >= mark.m_face_fragment_count) - { - // move all the marks for this face from context->m_marks[] (possibly used by multiple threads) - // to the local context_fragment_marks[] array for processing below. - unsigned int context_mark_count1 = 0; - face_fragment_marks.Reserve(mark.m_face_fragment_count); - if (context_mark_count0 + 1 >= mark.m_face_fragment_count) - { - face_fragment_marks.Append((int)context_mark_count0, context_fragment_marks); - } - else - { - for (unsigned int i = 0; i < context_mark_count0; i++) - { - if (context_fragment_marks[i].m_face_id == mark.m_face_id) - face_fragment_marks.Append(context_fragment_marks[i]); - else - { - if (i < context_mark_count1) - context_fragment_marks[context_mark_count1] = context_fragment_marks[i]; - context_mark_count1++; - } - } - } - context->m_marks.SetCount(context_mark_count1); - face_fragment_marks.Append(mark); - mark_count++; - } - else - { - // This face will have more subfragments delivered in the future. - mark_count = 0; - context->m_marks.AppendNew() = mark; - } - } - else - { - // This face will have more subfragments delivered in the future. - context->m_marks.AppendNew() = mark; - } - - const size_t P_stride = context->m_point_stride; - const size_t N_stride = context->m_normal_stride; - const size_t F_stride = context->m_quad_stride; - const size_t GID_stride = context->m_quad_group_id_stride; - - double* P - = (nullptr != context->m_points) - ? (context->m_points + mark.m_point_count0*P_stride) - : nullptr; - double* N - = (nullptr != context->m_normals) - ? (context->m_normals + mark.m_point_count0*N_stride) - : nullptr; - unsigned int* F - = (nullptr != context->m_quads) - ? (context->m_quads + mark.m_quad_count0*F_stride) - : nullptr; - ON__UINT_PTR* GID - = (nullptr != context->m_quad_group_ids) - ? (context->m_quad_group_ids + mark.m_quad_count0*GID_stride) - : nullptr; - - context->m_point_count = mark.m_point_count1; - context->m_quad_count = mark.m_quad_count1; - context->m_fragment_count++; - -#if defined(OPENNURBS_SLEEPLOCK_AVAILABLE) - if (bReleaseLock) - context->m_lock.ReturnLock(); -#endif - - // Copy the mesh from the fragment to the destination arrays - // - // All the code below must be thread safe. - // - // Basically, space context->m_point_stride[] and the other arrays - // is reserved above and time consuming calculations take place below so - // any other threads working to create this mesh can continue. - if ( nullptr != P ) - { - const double* srcP1 = fragment->m_P + fragment->m_P_count*fragment->m_P_stride; - for (const double* srcP = fragment->m_P; srcP < srcP1; srcP += fragment->m_P_stride) - { - P[0] = srcP[0]; - P[1] = srcP[1]; - P[2] = srcP[2]; - P += P_stride; - } - } - - if ( nullptr != N ) - { - const double* srcN1 = fragment->m_N + fragment->m_P_count*fragment->m_N_stride; - for (const double* srcN = fragment->m_N; srcN < srcN1; srcN += fragment->m_N_stride) - { - N[0] = srcN[0]; - N[1] = srcN[1]; - N[2] = srcN[2]; - N += N_stride; - } - } - - if ( nullptr != F ) - { - const ON_SubDLimitMeshFragmentGrid& quads = fragment->m_grid; - const unsigned int fvi0 = (unsigned int)mark.m_point_count0; - const unsigned int* srcF1 = quads.m_F + quads.m_F_count*quads.m_F_stride; - for (const unsigned int* srcF = quads.m_F; srcF < srcF1; srcF += quads.m_F_stride) - { - F[0] = srcF[0] + fvi0; - F[1] = srcF[1] + fvi0; - F[2] = srcF[2] + fvi0; - F[3] = srcF[3] + fvi0; - F += F_stride; - } - } - - if ( nullptr != GID ) - { - const ON__UINT_PTR* group_id1 = GID + fragment->m_grid.m_F_count*GID_stride; - while(GID < group_id1) - { - *GID = mark.m_fragment_group_id; - GID += GID_stride; - } - } - - while (mark_count >= 2 && face_fragment_marks.UnsignedCount() == mark_count ) - { - // combine subfragments into a single fragment. - face_fragment_marks.QuickSort( GetLimitSurfaceMesh_context_FragmentMark::CompareFaceIdAndFragmentIndex ); - if (face_fragment_marks[0].m_face_id != face_fragment_marks[mark_count - 1].m_face_id ) - break; - if (0 != face_fragment_marks[0].m_face_fragment_index) - break; - if (mark_count - 1 != face_fragment_marks[mark_count - 1].m_face_fragment_index) - break; - - mark = face_fragment_marks[0]; - unsigned int* quads = mark.m_context->m_quads; - const size_t quad_stride = mark.m_context->m_quad_stride; - const double* points = mark.m_context->m_points; - const size_t point_stride = mark.m_context->m_point_stride; - const double* normals = mark.m_context->m_normals; - const size_t normal_stride = mark.m_context->m_normal_stride; - ON__UINT_PTR* group_ids = mark.m_context->m_quad_group_ids; - const size_t group_id_stride = mark.m_context->m_quad_group_id_stride; - - if ( nullptr == quads || nullptr == points || nullptr == normals) - break; - if ( quad_stride < 4 || point_stride < 3 || normal_stride < 3 ) - break; - - const unsigned int grid_F_count = mark.m_fragment.m_grid.m_F_count; - const unsigned int grid_side_count = mark.m_fragment.m_grid.SideSegmentCount(); - const size_t magic_stride = grid_side_count*quad_stride; - - const ON__UINT_PTR group_id = - (nullptr != group_ids && group_id_stride > 0) - ? group_ids[mark.m_quad_count0*group_id_stride] - : 0; - - const unsigned int apex_point_index = (unsigned int)mark.m_point_count0; - const ON_3dPoint apex_point(points+apex_point_index*point_stride); - const ON_3dVector apex_normal(normals+apex_point_index*normal_stride); - - unsigned int* subfragment_quads = quads + face_fragment_marks[mark_count-1].m_quad_count0*quad_stride; - unsigned int i; - for ( i = 0; i < mark_count; i++ ) - { - mark = face_fragment_marks[i]; - if ( grid_F_count != mark.m_fragment.m_grid.m_F_count) - break; - unsigned int* prev_subfragment_quads = subfragment_quads; - subfragment_quads = quads + mark.m_quad_count0*quad_stride; - - if (false == CoincidentPointTest(apex_point_index,subfragment_quads[0],points,point_stride,normals,normal_stride)) - break; - subfragment_quads[0] = apex_point_index; - - unsigned int n; - for (n = 0; n < grid_side_count; n++) - { - if (false == CoincidentPointTest(subfragment_quads[0],prev_subfragment_quads[0],points,point_stride,normals,normal_stride)) - break; - prev_subfragment_quads[0] = subfragment_quads[0]; - - if (false == CoincidentPointTest(subfragment_quads[1],prev_subfragment_quads[3],points,point_stride,normals,normal_stride)) - break; - prev_subfragment_quads[3] = subfragment_quads[1]; - - subfragment_quads += quad_stride; - prev_subfragment_quads += magic_stride; - } - - if (n < grid_side_count) - break; - - subfragment_quads -= magic_stride; - - if (0 != group_id) - { - ON__UINT_PTR* p0 = group_ids + mark.m_quad_count0*group_id_stride; - if (group_id != p0[0]) - { - for ( ON__UINT_PTR* p1 = p0 + grid_F_count; p0 < p1; p0 += group_id_stride) - *p0 = group_id; - } - } - - } - if ( mark_count != i ) - break; - - break; - } - + if (bTestNearEqual) + return Internal_Seal3d( src, dst, 1.0e-2 ); + *dst++ = *src++; + *dst++ = *src++; + *dst = *src; return true; } -static int CompareQuadGroupId(const void* a, const void* b) +bool ON_SubDLimitMeshFragment::SealAdjacentSides( + bool bTestNearEqual, + bool bCopyNormals, + const ON_SubDLimitMeshFragment& src_fragment, + unsigned int i0, + unsigned int i1, + ON_SubDLimitMeshFragment& dst_fragment, + unsigned int j0, + unsigned int j1 +) { - ON__UINT_PTR ai = *(const ON__UINT_PTR*)a; - ON__UINT_PTR bi = *(const ON__UINT_PTR*)b; - if (ai < bi) - return -1; - if (ai > bi) - return 1; - return 0; -} - -ON_Mesh* ON_SubD::GetLimitSurfaceMesh( - const class ON_SubDDisplayParameters& limit_mesh_parameters, - ON_Mesh* destination_mesh - ) const -{ - ON_SubDDisplayParameters local_limit_mesh_parameters = limit_mesh_parameters; - ON_ProgressReporter::ReportProgress( local_limit_mesh_parameters.m_progress_reporter, 0.0 ); - - if (destination_mesh) - destination_mesh->Destroy(); - - const ON_SubDLevel& active_level = ActiveLevel(); - if (active_level.IsEmpty()) - return ON_SUBD_RETURN_ERROR(nullptr); - - if (ON_SubD::SubDType::Unset == active_level.m_subdivision_type ) - const_cast(this)->SetSubDType(ON_SubD::SubDType::QuadCatmullClark); - - const unsigned int fragment_count = LimitSurfaceMeshFragmentCount(); - if ( fragment_count <= 0 ) - return ON_SUBD_RETURN_ERROR(nullptr); - - const ON_SubD::SubDType subd_type = active_level.m_subdivision_type; - - if (ON_SubD::SubDType::QuadCatmullClark != subd_type) - { - // TODO - support tri subd after quad stuff is finished. - return ON_SUBD_RETURN_ERROR(nullptr); - } - - if (0 == local_limit_mesh_parameters.m_display_density && fragment_count > FaceCount()) - local_limit_mesh_parameters.m_display_density = 1; - - ////////////////////////////////////////////////////////////////// - // - // Set: - // vertex_count = number of points needed to calculate the mesh - // face_count = number of quad faces needed to calculate the mesh - // - - unsigned int vertex_count = 0; - unsigned int face_count = 0; - - unsigned int tri_count = 0; // tri count - unsigned int quad_count = 0; // quad count - unsigned int ngon_edge_sum = 0; // sum of edge counts for faces with m_edge_count >= 5. - ON_SubDFaceIterator fit(*this); - for (const ON_SubDFace* f = fit.FirstFace(); nullptr != f; f = fit.NextFace()) - { - if (3 == f->m_edge_count) - tri_count++; - if (4 == f->m_edge_count) - quad_count++; - else - ngon_edge_sum += f->m_edge_count; - } - - if (ON_SubD::SubDType::QuadCatmullClark == subd_type) - { - const unsigned int m0 = 1 << local_limit_mesh_parameters.m_display_density; - const unsigned int m1 = (m0 > 1) ? m0 / 2 : 1; - if (ngon_edge_sum > 0 || tri_count > 0) - { - ngon_edge_sum += 4 * quad_count + 3 * tri_count; - quad_count = 0; - tri_count = 0; - } - - vertex_count = quad_count*(m0 + 1)*(m0 + 1) + ngon_edge_sum*(m1 + 1)*(m1 + 1); - face_count = quad_count*m0*m0 + ngon_edge_sum*m1*m1; - } - else if (ON_SubD::SubDType::TriLoopWarren == subd_type) - { - // TODO ADD TRI SUPPORT - return ON_SUBD_RETURN_ERROR(nullptr); - } - - if (vertex_count < 4 || face_count < 1) - return ON_SUBD_RETURN_ERROR(nullptr); - - std::unique_ptr< ON_Mesh > up; - ON_Mesh* mesh = nullptr; - if (nullptr != destination_mesh) - mesh = destination_mesh; - else - { - up = std::make_unique< ON_Mesh >(); - mesh = up.get(); - } - - if ( nullptr == mesh) - return ON_SUBD_RETURN_ERROR(nullptr); - - // mesh vertices - - // mesh face group ids - - GetLimitSurfaceMesh_context context; - - ON_3dPointArray& D = mesh->DoublePrecisionVertices(); - context.m_point_capacity = vertex_count; - context.m_points = (double*)D.Reserve(vertex_count); - context.m_point_stride = 3; - - ON_SimpleArray< ON_3dVector > mesh_N; - context.m_normal_capacity = vertex_count; - context.m_normals = (double*)mesh_N.Reserve(vertex_count); - context.m_normal_stride = 3; - - context.m_quad_capacity = face_count; - context.m_quads = (unsigned int*)mesh->m_F.Reserve(face_count); - context.m_quad_stride = 4; - - ON_SimpleArray< ON__UINT_PTR > quad_group_ids_buffer; - ON__UINT_PTR* quad_group_ids = quad_group_ids_buffer.Reserve(face_count); - context.m_quad_group_ids = quad_group_ids; - context.m_quad_group_id_stride = 1; - -#if defined(OPENNURBS_SLEEPLOCK_AVAILABLE) - context.m_bUseMultipleThreads = local_limit_mesh_parameters.m_bUseMultipleThreads; -#endif - - if (ON_SubD::SubDType::QuadCatmullClark == subd_type) - { - //ON_Interval progress_limits(0.1,0.9); - const ON_Interval progress_limits = local_limit_mesh_parameters.m_progress_reporter_interval; - if (progress_limits.IsIncreasing()) - { - local_limit_mesh_parameters.m_progress_reporter_interval.Set( - progress_limits.ParameterAt(0.0), - progress_limits.ParameterAt(0.5) - ); - } - unsigned int quad_fragment_count = GetQuadLimitSurfaceMeshFragmentsHelper( - fit, - local_limit_mesh_parameters, - (ON__UINT_PTR)&context, - GetLimitSurfaceMesh_callback - ); - local_limit_mesh_parameters.m_progress_reporter_interval = progress_limits; - if ( 0 == quad_fragment_count ) - return ON_SUBD_RETURN_ERROR(nullptr); - } - else if (ON_SubD::SubDType::TriLoopWarren == subd_type) - { - // todo - add tri support - return ON_SUBD_RETURN_ERROR(nullptr); - } - else - { - return ON_SUBD_RETURN_ERROR(nullptr); - } - - const unsigned int mesh_point_count = (unsigned int)context.m_point_count; - const unsigned int mesh_face_count = (unsigned int)context.m_quad_count; - - if (mesh_point_count < 3 || mesh_face_count < 1) - return ON_SUBD_RETURN_ERROR(nullptr); - - // Set face count - mesh->m_F.SetCount(mesh_face_count); - - // Set vertex counts - D.SetCount(mesh_point_count); - quad_group_ids_buffer.SetCount(mesh_face_count); - mesh->UpdateSinglePrecisionVertices(); - - // copy ON_3dVector vertex normals in mesh_N[] to ON_3fVector normals in mesh->m_N[] - mesh_N.SetCount(mesh_point_count); - const ON_3dVector* dN = mesh_N.Array(); - const ON_3dVector* dN1 = dN + mesh_point_count; - ON_3fVector* fN = mesh->m_N.Reserve(mesh_point_count); - mesh->m_N.SetCount(mesh_point_count); - while (dN < dN1) - *fN++ = *dN++; - - // set bounding boxes - mesh->BoundingBox(); - - // group all mesh faces that came from a subd control net face into an ngon. for (;;) { - const ON_3dPointListRef mesh_vertex_list(mesh); - const ON_MeshFaceList mesh_face_list(mesh); - ON_MeshVertexFaceMap vf_map; - if (!vf_map.SetFromFaceList(mesh_vertex_list.PointCount(), mesh_face_list, false)) + unsigned int m = 4 * src_fragment.m_grid.m_side_segment_count; + if (i0 > m || i1 > m) break; - const unsigned int *const* vertex_face_map = vf_map.VertexFaceMap(); - if (nullptr == vertex_face_map) + m = 4 * dst_fragment.m_grid.m_side_segment_count; + if (j0 > m || j1 > m) break; - - ON_SimpleArray< unsigned int > mesh_face_index_buffer; - mesh_face_index_buffer.Reserve(mesh_face_count); - mesh_face_index_buffer.SetCount(mesh_face_count); - unsigned int* mesh_face_index = mesh_face_index_buffer.Array(); - - // mesh_face_index[] = permutation of (0,1,...,quad_count-1) - // such that the mesh faces that came from the same level zero subd face - // are grouped together. - ON_Sort(ON::sort_algorithm::quick_sort, mesh_face_index, quad_group_ids, mesh_face_count, sizeof(quad_group_ids[0]), CompareQuadGroupId); - - ON_SimpleArray< unsigned int> ngon_fi(256); - ON_SimpleArray< unsigned int> ngon_vi; - ON_ProgressStepCounter counter = ON_ProgressStepCounter::Create( - local_limit_mesh_parameters.m_progress_reporter, - mesh_face_count, - 0.5, 1.0, - 50 - ); - for (unsigned int i = 0; i < mesh_face_count; /*empty increment*/) + m = (i0 > i1) ? i0 - i1 : i1 - i0; + if ( m != ((j0 > j1) ? j0 - j1 : j1 - j0)) + break; + if (i0 > i1) { - // get list of faces in the mesh ngon - ngon_fi.SetCount(0); - ngon_fi.Append(mesh_face_index[i]); - const ON__UINT_PTR ngon_group_id = quad_group_ids[mesh_face_index[i]]; - for (i++; i < mesh_face_count && ngon_group_id == quad_group_ids[mesh_face_index[i]]; i++) - { - ngon_fi.Append(mesh_face_index[i]); - } + m = i0; + i0 = i1; + i1 = m; + m = j0; + j0 = j1; + j1 = m; + } + const int delta_j = (j0 < j1) ? 1 : -1; - counter.IncrementStep(); - if (ngon_fi.Count() < 2) - continue; + const double* src; + double* dst; - // create ngon - ngon_vi.SetCount(0); - if (ON_MeshNgon::FindNgonOuterBoundary(mesh_vertex_list, mesh_face_list, vertex_face_map, ngon_fi.UnsignedCount(), ngon_fi.Array(), ngon_vi) >= 3) + unsigned int src_stride = (unsigned int)src_fragment.m_P_stride; + unsigned int dst_stride = (unsigned int)dst_fragment.m_P_stride; + int j = (int)j0; + for (unsigned int i = i0; i <= i1; i++, j += delta_j) + { + src = &src_fragment.m_P[src_fragment.m_grid.m_S[i]*src_stride]; + dst = &dst_fragment.m_P[dst_fragment.m_grid.m_S[j]*dst_stride]; + if (false == ON_SubDLimitMeshFragment::SealPoints(bTestNearEqual,src,dst)) { - mesh->AddNgon(ngon_vi.UnsignedCount(), ngon_vi.Array(), ngon_fi.UnsignedCount(), ngon_fi.Array()); + ON_SUBD_ERROR("Point locations failed near equal test."); + return false; } } - break; + + if (bCopyNormals) + { + src_stride = (unsigned int)src_fragment.m_N_stride; + dst_stride = (unsigned int)dst_fragment.m_N_stride; + j = (int)j0; + for (unsigned int i = i0; i <= i1; i++, j += delta_j) + { + src = &src_fragment.m_N[src_fragment.m_grid.m_S[i] * src_stride]; + dst = &dst_fragment.m_N[dst_fragment.m_grid.m_S[j] * dst_stride]; + if (false == ON_SubDLimitMeshFragment::SealNormals(bTestNearEqual,src,dst)) + { + ON_SUBD_ERROR("Normal locations failed near equal test."); + return false; + } + } + } + return true; } - // success - ON_ProgressReporter::ReportProgress(local_limit_mesh_parameters.m_progress_reporter,1.0); - up.release(); - return mesh; + ON_SUBD_ERROR("Invalid input."); + return false; } class VertexToDuplicate @@ -2424,7 +1292,7 @@ bool VertexToDuplicate::NeedsDuplicated( for (unsigned int vei = 0; vei < edge_count; vei++) { const ON_SubDEdge* edge = vertex->Edge(vei); - if ( nullptr != edge && false == edge->IsSmooth(true) && edge->m_face_count > 1 ) + if ( nullptr != edge && false == edge->IsSmooth() && edge->m_face_count > 1 ) return true; } return false; @@ -2555,7 +1423,7 @@ static bool DuplicateVerticesAtCreases( if (dup.m_vertex->IsDart()) { const ON_SubDEdge* edge = sit.CurrentEdge(0); - if (nullptr == edge || false == edge->IsCrease(false) || 2 != edge->m_face_count) + if (nullptr == edge || false == edge->IsCrease() || 2 != edge->m_face_count) { ON_SubDIncrementErrorCount(); bDupError = true; @@ -2574,7 +1442,7 @@ static bool DuplicateVerticesAtCreases( } } - sit.NextFace(true); + sit.NextFace(ON_SubDSectorIterator::StopAt::AnyCrease); } sector_count++; @@ -2588,7 +1456,7 @@ static bool DuplicateVerticesAtCreases( dup.m_mesh_V_index = mesh_V_index0; } - for (dup.m_face = sit.CurrentFace(); nullptr != dup.m_face && false == bDupError; dup.m_face = sit.NextFace(true)) + for (dup.m_face = sit.CurrentFace(); nullptr != dup.m_face && false == bDupError; dup.m_face = sit.NextFace(ON_SubDSectorIterator::StopAt::AnyCrease)) { if (false == ChangeMeshFaceIndex(mesh_V_index0, mesh_F_count, mesh, dup, dups_sub_array)) { @@ -2608,8 +1476,6 @@ static bool DuplicateVerticesAtCreases( return true; } - - ON_Mesh* ON_SubD::GetControlNetMesh( ON_Mesh* destination_mesh ) const @@ -2856,1198 +1722,6 @@ ON_Mesh* ON_SubD::GetControlNetMesh( return mesh; } -double* ON_SubDQuadFaceMesher::Internal_Buffer(size_t buffer_capacity) -{ - if (buffer_capacity < m__buffer_capacity) - return m__buffer; - if (nullptr != m__buffer) - { - delete[] m__buffer; - m__buffer = nullptr; - } - m__buffer_capacity = 0; - m__buffer = new(std::nothrow) double[buffer_capacity]; - if (nullptr != m__buffer) - m__buffer_capacity = buffer_capacity; - return m__buffer; -} - -void ON_SubDQuadFaceMesher::SetDestinationInitialize( - ON_SubDQuadFaceMesher::Output output - ) -{ - // Reuse workspaces - ReturnAllFixedSizeHeaps(); - - m_output = output; - m_display_density = 0; - m_count = 0; - m_capacity = 0; - m_point_stride0 = 0; - m_point_stride1 = 0; - m_points = nullptr; - m_normal_stride0 = 0; - m_normal_stride1 = 0; - m_normals = nullptr; - m_patcher = nullptr; -} - -bool ON_SubDQuadFaceMesher::SetDestinationToLocalMeshBuffer( - unsigned int mesh_density - ) -{ - const unsigned int count = ON_SubDLimitMeshFragment::SideSegmentCountFromDisplayDensity(mesh_density); - - const size_t point_count = (count > 0) ? ((count + 1)*(count + 1)) : 0; - - // initialize this to make another mesh. - SetDestinationInitialize(ON_SubDQuadFaceMesher::Output::mesh); - - double* buffer = Internal_Buffer(6 * point_count); - if (point_count > 0 && nullptr == buffer ) - { - return ON_SUBD_RETURN_ERROR(false); - } - - if (0 == count && mesh_density > 0) - return ON_SUBD_RETURN_ERROR(false); - - m_points = buffer; - m_normals = buffer + 3*point_count; - m_point_stride0 = 3; - m_point_stride1 = (count+1)*m_point_stride0; - m_normal_stride0 = m_point_stride0; - m_normal_stride1 = m_point_stride1; - - m_display_density = mesh_density; - m_count = 0; - m_capacity = count; - - return (count == m_count); -} - -bool ON_SubDQuadFaceMesher::SetDestinationToMeshFragment( - unsigned int mesh_density, - class ON_SubDLimitMeshFragment& fragment - ) -{ - // initialize this - SetDestinationInitialize(ON_SubDQuadFaceMesher::Output::mesh); - - if ( nullptr == fragment.m_P || nullptr == fragment.m_N) - return ON_SUBD_RETURN_ERROR(false); - - const unsigned int count = ON_SubDLimitMeshFragment::SideSegmentCountFromDisplayDensity(mesh_density); - const unsigned int point_count = (count + 1)*(count + 1); - if ( point_count > fragment.m_P_capacity ) - return ON_SUBD_RETURN_ERROR(false); - - const unsigned int quad_count = count*count; - if (fragment.m_grid.m_F_count != quad_count || 0 != fragment.m_grid.m_F_level_of_detail - || nullptr == fragment.m_grid.m_F || fragment.m_grid.m_F_stride != 4) - { - fragment.m_grid = ON_SubDLimitMeshFragmentGrid::Quads(count,0); - if ( nullptr == fragment.m_grid.m_F) - return ON_SUBD_RETURN_ERROR(false); - } - - m_point_stride0 = fragment.m_P_stride; - m_point_stride1 = (count+1)*m_point_stride0; - m_points = fragment.m_P; - - m_normal_stride0 = fragment.m_N_stride; - m_normal_stride1 = (count+1)*m_normal_stride0; - m_normals = fragment.m_N; - - m_display_density = mesh_density; - m_count = 0; - m_capacity = count; - - return true; -} - -bool ON_SubDQuadFaceMesher::SetDestinationToPatchFragment( - class ON_SubDQuadFacePatcher& patcher - ) -{ - // initialize this - SetDestinationInitialize(ON_SubDQuadFaceMesher::Output::patches); - m_patcher = &patcher; - m_display_density = patcher.m_display_density; - - return true; -} - - -bool ON_SubDQuadFaceMesher::UnsetMeshPoints() -{ - if (ON_SubDQuadFaceMesher::Output::mesh != m_output) - return ON_SUBD_RETURN_ERROR(false); - - if (nullptr == m_points || 0 == m_count) - return ON_SUBD_RETURN_ERROR(false); - - double* p1end = m_points + (m_count + 1)*m_point_stride1; - for (double* p1 = m_points; p1 < p1end; p1 += m_point_stride1) - { - double* pend = p1 + (m_count + 1)*m_point_stride0; - for (double* p = p1; p < pend; p += m_point_stride0) - *p = ON_UNSET_VALUE; - } - - return true; -} - -void ON_SubDQuadFaceMesher::Get6xCubicBasis( - double t, - double b6[4] - ) -{ - const double knot[6] = {-2.0,-1.0,0.0,1.0,2.0,3.0}; - double N[16]; - ON_EvaluateNurbsBasis( 4, knot, t, N ); - b6[0] = 6.0*N[0]; - b6[1] = 6.0*N[1]; - b6[2] = 6.0*N[2]; - b6[3] = 6.0*N[3]; - - // TODO - hard code polynomial formula - //const double tt = t*t; - //const double s = 1.0 - t; - //const double ss = s*s; - - //b6[0] = (t*tt); - //b6[1] = (1.0 + 3.0*(s*tt)); - //b6[2] = (1.0 + 3.0*(t*ss)); - //b6[3] = (s*ss); -} - - -void ON_SubDQuadFaceMesher::GetCubicBasisDerivative( - double t, - double d[4] - ) -{ - const double knot[6] = {-2.0,-1.0,0.0,1.0,2.0,3.0}; - double N[16]; - ON_EvaluateNurbsBasis( 4, knot, t, N ); - ON_EvaluateNurbsBasisDerivatives( 4, knot, 1, N ); - d[0] = N[4]; - d[1] = N[5]; - d[2] = N[6]; - d[3] = N[7]; - - // TODO - hard code polynomial formula - //const double tt = t*t; - //const double s = 1.0 - t; - //const double ss = s*s; - - //d[0] = tt; - //d[1] = (0.5 + t) - 1.5*tt; - //d[2] = -(0.5 + s) + 1.5*ss; - //d[3] = -ss; -} - -bool ON_SubDQuadFaceMesher::Internal_EvaluateSurfaceNormalBackup1( - double s, - double t, - unsigned int count, - unsigned int i, - unsigned int j, - double* N -) const -{ - const double knot[6] = {-2.0,-1.0,0.0,1.0,2.0,3.0}; - ON_3dVector v[6]; // P, Du, Dv, Duu, Duv, Dvv - bool bHaveNormal = ON_EvaluateNurbsSurfaceSpan( - 3, // dim - 0, // is_rat - 4, 4, // order, order - knot, // knot0[] array of (2*order0-2) doubles - knot, // knot1[] array of (2*order1-2) doubles - 12,3, // cv_stride0, cv_stride1 - &m_srf_cv[0][0][0],// cv at "lower left" of bispan - 2, // der_count, // number of derivatives to compute (>=0) - s,t, // evaluation parameters - 3, // v_stride - &v[0][0] // P, Du, Dv, Duu, Duv, Dvv returned here - ); - - if (bHaveNormal) - { - // P[], Du, Dv and V[0], V[1], V[2] should be nearly identical - int limit_dir = 0; - if (0 == i) - { - limit_dir = (j < count) ? 1 : 2; - } - else if (count == i) - { - limit_dir = (j < count) ? 4 : 3; - } - if (0 == j) - { - limit_dir = 1; - } - else if (count == j) - { - limit_dir = 4; - } - bHaveNormal = ON_EvNormal(limit_dir, v[1], v[2], v[3], v[4], v[5], *((ON_3dVector*)N)); - } - - return bHaveNormal; -} - -bool ON_SubDQuadFaceMesher::Internal_EvaluateSurfaceNormalBackup2( - const double* P00, - unsigned int count, - unsigned int i, - unsigned int j, - double* N -) const -{ - if (false == ((const ON_3dPoint*)(P00 + ((i * m_point_stride0) + (j * m_point_stride1))))->IsValid() ) - return false; - - ON_2dex corners_dex[4]; - ON_3dPoint corners[4]; - if ( i <= 0 || i >= count || j <= 0 || j >= count ) - { - corners_dex[0].i = (i > 0) ? (i - 1) : 0; - corners_dex[0].j = (j > 0) ? (j - 1) : 0; - corners_dex[2].i = (i < count) ? (i + 1) : count; - corners_dex[2].j = (j < count) ? (j + 1) : count; - corners_dex[1].i = corners_dex[2].i; - corners_dex[1].j = corners_dex[0].j; - corners_dex[3].i = corners_dex[0].i; - corners_dex[3].j = corners_dex[2].j; - } - else - { - corners_dex[0].i = (i - 1); - corners_dex[0].j = j; - corners_dex[1].i = i; - corners_dex[1].j = (j - 1); - corners_dex[2].i = (i + 1); - corners_dex[2].j = j; - corners_dex[3].i = i; - corners_dex[3].j = (j + 1); - } - - for (int k = 0; k < 4; k++) - { - corners[k] = P00 + ((corners_dex[k].i * m_point_stride0) + (corners_dex[k].j * m_point_stride1)); - if (false == corners[k].IsValid()) - return false; - } - const ON_3dVector A (corners[2] - corners[0]); - const ON_3dVector B (corners[3] - corners[1]); - *((ON_3dVector*)N) = ON_CrossProduct(A, B); - bool bHaveNormal = ((ON_3dVector*)N)->Unitize(); - return bHaveNormal; -} - -bool ON_SubDQuadFaceMesher::EvaluateSurface( - unsigned int count, - unsigned int point_i0, - unsigned int point_j0 - ) const -{ - double iso_cv[4][3]; - double b[4], Du[3], Dv[3], s, t; - unsigned int i, j; - - if ( nullptr == m_points || m_point_stride0 < 3 || m_point_stride1 < 3) - return ON_SUBD_RETURN_ERROR(false); - - // verify that count is a power of 2 - for ( i = count; i > 1; i /= 2) - { - if (0 != (i%2)) - return ON_SUBD_RETURN_ERROR(false); // count != 2^n - } - if (1 != i) - return ON_SUBD_RETURN_ERROR(false); // count != 2^n - - double* P00 = m_points + point_i0*m_point_stride0 + point_j0*m_point_stride1; - - const size_t normal_stride[2] = {(nullptr != m_normals)?m_normal_stride0:0,(nullptr != m_normals)?m_normal_stride1:0}; - double* N00 = m_normals + point_i0*normal_stride[0] + point_j0*normal_stride[1]; - - const double delta_t = 1.0 / ((double)count); - - if (nullptr != N00) - { - for (i = 0; i <= count; i++) - { - - // Set iso_cv[][] = cvs for isocurve(s) = srf(i*delta_t,s) - s = (i < count) ? (i*delta_t) : 1.0; - ON_SubDQuadFaceMesher::Get6xCubicBasis(s, b); - const double* srf = &m_srf_cv[0][0][0]; - iso_cv[0][0] = ((b[0] * srf[0] + b[3] * srf[36]) + (b[1] * srf[12] + b[2] * srf[24])) / 6.0; - srf++; - iso_cv[0][1] = ((b[0] * srf[0] + b[3] * srf[36]) + (b[1] * srf[12] + b[2] * srf[24])) / 6.0; - srf++; - iso_cv[0][2] = ((b[0] * srf[0] + b[3] * srf[36]) + (b[1] * srf[12] + b[2] * srf[24])) / 6.0; - srf++; - iso_cv[1][0] = ((b[0] * srf[0] + b[3] * srf[36]) + (b[1] * srf[12] + b[2] * srf[24])) / 6.0; - srf++; - iso_cv[1][1] = ((b[0] * srf[0] + b[3] * srf[36]) + (b[1] * srf[12] + b[2] * srf[24])) / 6.0; - srf++; - iso_cv[1][2] = ((b[0] * srf[0] + b[3] * srf[36]) + (b[1] * srf[12] + b[2] * srf[24])) / 6.0; - srf++; - iso_cv[2][0] = ((b[0] * srf[0] + b[3] * srf[36]) + (b[1] * srf[12] + b[2] * srf[24])) / 6.0; - srf++; - iso_cv[2][1] = ((b[0] * srf[0] + b[3] * srf[36]) + (b[1] * srf[12] + b[2] * srf[24])) / 6.0; - srf++; - iso_cv[2][2] = ((b[0] * srf[0] + b[3] * srf[36]) + (b[1] * srf[12] + b[2] * srf[24])) / 6.0; - srf++; - iso_cv[3][0] = ((b[0] * srf[0] + b[3] * srf[36]) + (b[1] * srf[12] + b[2] * srf[24])) / 6.0; - srf++; - iso_cv[3][1] = ((b[0] * srf[0] + b[3] * srf[36]) + (b[1] * srf[12] + b[2] * srf[24])) / 6.0; - srf++; - iso_cv[3][2] = ((b[0] * srf[0] + b[3] * srf[36]) + (b[1] * srf[12] + b[2] * srf[24])) / 6.0; - - double* P = P00 + (i * m_point_stride0); - double* N = N00 + (i * normal_stride[0]); - for (j = 0; j <= count; j++) - { - if (ON_UNSET_VALUE == P[0]) - { - // evaluate Dv(i*delta_t,j*delta_t) and save it in N[0,1,2] - t = (j < count) ? (j*delta_t) : 1.0; - ON_SubDQuadFaceMesher::GetCubicBasisDerivative(t, b); - N[0] = ((b[0] * iso_cv[0][0] + b[3] * iso_cv[3][0]) + (b[1] * iso_cv[1][0] + b[2] * iso_cv[2][0])); - N[1] = ((b[0] * iso_cv[0][1] + b[3] * iso_cv[3][1]) + (b[1] * iso_cv[1][1] + b[2] * iso_cv[2][1])); - N[2] = ((b[0] * iso_cv[0][2] + b[3] * iso_cv[3][2]) + (b[1] * iso_cv[1][2] + b[2] * iso_cv[2][2])); - } - P += m_point_stride1; - N += normal_stride[1]; - } - } - } - - bool bUseSurfaceNormalBackup2 = false; - - for (j = 0; j <= count; j++) - { - // Set iso_cv[][] = cvs for isocurve(s) = srf(s,j*delta_t) - t = (j < count) ? (j*delta_t) : 1.0; - ON_SubDQuadFaceMesher::Get6xCubicBasis(t, b); - const double* srf = &m_srf_cv[0][0][0]; - // The ((first + fourth) + (second + third)) insures the values of the - // mesh points and normals will be exactly symmetric when the control points are exactly - // symmetric. - iso_cv[0][0] = ((b[0] * srf[0] + b[3] * srf[9]) + (b[1] * srf[3] + b[2] * srf[6])) / 6.0; - srf++; - iso_cv[0][1] = ((b[0] * srf[0] + b[3] * srf[9]) + (b[1] * srf[3] + b[2] * srf[6])) / 6.0; - srf++; - iso_cv[0][2] = ((b[0] * srf[0] + b[3] * srf[9]) + (b[1] * srf[3] + b[2] * srf[6])) / 6.0; - srf = &m_srf_cv[1][0][0]; - iso_cv[1][0] = ((b[0] * srf[0] + b[3] * srf[9]) + (b[1] * srf[3] + b[2] * srf[6])) / 6.0; - srf++; - iso_cv[1][1] = ((b[0] * srf[0] + b[3] * srf[9]) + (b[1] * srf[3] + b[2] * srf[6])) / 6.0; - srf++; - iso_cv[1][2] = ((b[0] * srf[0] + b[3] * srf[9]) + (b[1] * srf[3] + b[2] * srf[6])) / 6.0; - srf = &m_srf_cv[2][0][0]; - iso_cv[2][0] = ((b[0] * srf[0] + b[3] * srf[9]) + (b[1] * srf[3] + b[2] * srf[6])) / 6.0; - srf++; - iso_cv[2][1] = ((b[0] * srf[0] + b[3] * srf[9]) + (b[1] * srf[3] + b[2] * srf[6])) / 6.0; - srf++; - iso_cv[2][2] = ((b[0] * srf[0] + b[3] * srf[9]) + (b[1] * srf[3] + b[2] * srf[6])) / 6.0; - srf = &m_srf_cv[3][0][0]; - iso_cv[3][0] = ((b[0] * srf[0] + b[3] * srf[9]) + (b[1] * srf[3] + b[2] * srf[6])) / 6.0; - srf++; - iso_cv[3][1] = ((b[0] * srf[0] + b[3] * srf[9]) + (b[1] * srf[3] + b[2] * srf[6])) / 6.0; - srf++; - iso_cv[3][2] = ((b[0] * srf[0] + b[3] * srf[9]) + (b[1] * srf[3] + b[2] * srf[6])) / 6.0; - - double* P = P00 + (j * m_point_stride1); - double* N = N00 + (j * normal_stride[1]); - for (i = 0; i <= count; i++) - { - if (ON_UNSET_VALUE == P[0]) - { - s = (i < count) ? (i*delta_t) : 1.0; - ON_SubDQuadFaceMesher::Get6xCubicBasis(s, b); - // P = srf(i*delta_t,j*delta_t) - P[0] = ((b[0] * iso_cv[0][0] + b[3] * iso_cv[3][0]) + (b[1] * iso_cv[1][0] + b[2] * iso_cv[2][0])) / 6.0; - P[1] = ((b[0] * iso_cv[0][1] + b[3] * iso_cv[3][1]) + (b[1] * iso_cv[1][1] + b[2] * iso_cv[2][1])) / 6.0; - P[2] = ((b[0] * iso_cv[0][2] + b[3] * iso_cv[3][2]) + (b[1] * iso_cv[1][2] + b[2] * iso_cv[2][2])) / 6.0; - - if (nullptr != N) - { - // Above, I saved Dv in N[]; - Dv[0] = N[0]; - Dv[1] = N[1]; - Dv[2] = N[2]; - - ON_SubDQuadFaceMesher::GetCubicBasisDerivative(s, b); - // evaluate = Du(i*delta_t,j*delta_t) - Du[0] = ((b[0] * iso_cv[0][0] + b[3] * iso_cv[3][0]) + (b[1] * iso_cv[1][0] + b[2] * iso_cv[2][0])); - Du[1] = ((b[0] * iso_cv[0][1] + b[3] * iso_cv[3][1]) + (b[1] * iso_cv[1][1] + b[2] * iso_cv[2][1])); - Du[2] = ((b[0] * iso_cv[0][2] + b[3] * iso_cv[3][2]) + (b[1] * iso_cv[1][2] + b[2] * iso_cv[2][2])); - - // surface normal = Du X Dv - *((ON_3dVector*)N) = ON_CrossProduct(Du, Dv); - bool bHaveNormal = ((ON_3dVector*)N)->Unitize(); - if (false == bHaveNormal) - { - bHaveNormal = Internal_EvaluateSurfaceNormalBackup1(s,t,count,i,j,N); - if (false == bHaveNormal) - { - bUseSurfaceNormalBackup2 = true; - N[0] = 0.0; - N[1] = 0.0; - N[2] = 0.0; - } - } - } - } - P += m_point_stride0; - N += normal_stride[0]; - } - } - - if (bUseSurfaceNormalBackup2) - { - for (j = 0; j <= count; j++) - { - double* N = N00 + (j * normal_stride[1]); - for (i = 0; i <= count; i++) - { - if (0.0 == N[0] && 0.0 == N[1] && 0.0 == N[2] ) - { - if ( false == Internal_EvaluateSurfaceNormalBackup2(P00,count,i,j,N) ) - { - bUseSurfaceNormalBackup2 = true; - ON_ERROR("Unable to get surface normal."); - N[0] = 0.0; - N[1] = 0.0; - N[2] = 0.0; - } - } - N += normal_stride[0]; - } - } - } - - return true; -} - -bool ON_SubDQuadFacePatcher::Send4x4Patch( - unsigned int display_density, - const class ON_SubDFaceRegion& face_region, - ON_SubDLimitNurbsFragment::BispanType bispan_type - ) -{ - m_patch_fragment.m_type = ON_SubDLimitNurbsFragment::Type::BicubicSingle; - m_patch_fragment.m_bispan_type[0] = bispan_type; - m_patch_fragment.m_bispan_type[1] = ON_SubDLimitNurbsFragment::BispanType::None; - m_patch_fragment.m_bispan_type[2] = ON_SubDLimitNurbsFragment::BispanType::None; - m_patch_fragment.m_bispan_type[3] = ON_SubDLimitNurbsFragment::BispanType::None; - - m_patch_fragment.m_face_region = face_region; - - const bool rc = m_fragment_callback_function(m_fragment_callback_context,&m_patch_fragment); - - // erase any region modifications made by the callback - m_patch_fragment.m_face_region = face_region; - - return rc; -} - -bool ON_SubDQuadFacePatcher::Send5x5Patch( - unsigned int display_density, - const class ON_SubDFaceRegion& face_region, - const ON_SubDLimitNurbsFragment::BispanType bispan_type[4] - ) -{ - m_patch_fragment.m_type = ON_SubDLimitNurbsFragment::Type::BicubicQuadrant; - m_patch_fragment.m_bispan_type[0] = bispan_type[0]; - m_patch_fragment.m_bispan_type[1] = bispan_type[1]; - m_patch_fragment.m_bispan_type[2] = bispan_type[2]; - m_patch_fragment.m_bispan_type[3] = bispan_type[3]; - - m_patch_fragment.m_face_region = face_region; - - const bool rc = m_fragment_callback_function(m_fragment_callback_context,&m_patch_fragment); - - // erase any region modifications made by the callback - m_patch_fragment.m_face_region = face_region; - - return rc; -} - -bool ON_SubDQuadFaceMesher::GetLimitSubMeshOrPatch( - const ON_SubDFaceRegion& face_region, - ON_SubDQuadNeighborhood* qft, - unsigned int display_density, - unsigned int point_i0, - unsigned int point_j0 - ) -{ - // count = power of 2 = 2^display_density - unsigned int count = ON_SubDLimitMeshFragment::SideSegmentCountFromDisplayDensity(display_density); - if ( 0 == count ) - return ON_SUBD_RETURN_ERROR(false); - - if (nullptr == qft || false == qft->IsSet()) - return ON_SUBD_RETURN_ERROR(false); - - if ( ON_SubDQuadFaceMesher::Output::mesh == m_output && count > m_capacity) - return ON_SUBD_RETURN_ERROR(false); - - if (qft->m_bIsCubicPatch) - { - switch (m_output) - { - case ON_SubDQuadFaceMesher::Output::mesh: - if (false == qft->GetLimitSurfaceCV(&m_srf_cv[0][0][0], 4U)) - return ON_SUBD_RETURN_ERROR(false); - return EvaluateSurface(count, point_i0, point_j0); - break; - - case ON_SubDQuadFaceMesher::Output::patches: - if (false == qft->GetLimitSurfaceCV(&m_patcher->m_patch_fragment.m_patch_cv[0][0][0], 5U)) - return ON_SUBD_RETURN_ERROR(false); - //Internal_UpdateFaceRegionCorners(qft, face_region); - m_patcher->Send4x4Patch(display_density,face_region,ON_SubDLimitNurbsFragment::BispanType::Exact); - return true; // even if callback returns false - break; - } - return ON_SUBD_RETURN_ERROR(false); - } - - if (count > 1 && display_density > 0) - { - count /= 2; - - bool bSubDivide[4] = {}; - unsigned int subdivide_count = 0; - for (unsigned int q0fvi = 0; q0fvi < 4; q0fvi++) - { - if ( qft->m_bExactQuadrantPatch[q0fvi] ) - { - if (ON_SubDQuadFaceMesher::Output::mesh == m_output) - { - // Use the exact patch to calcuate exact mesh vertex and normal values - if (qft->GetLimitSubSurfaceSinglePatchCV(q0fvi, m_srf_cv)) - { - unsigned int submesh_point_i0 = point_i0 + ((1 == q0fvi || 2 == q0fvi) ? count : 0); - unsigned int submesh_point_j0 = point_j0 + ((2 == q0fvi || 3 == q0fvi) ? count : 0); - if (false == EvaluateSurface(count, submesh_point_i0, submesh_point_j0)) - return ON_SUBD_RETURN_ERROR(false); - continue; - } - } - } - else - { - // local subdivision is required - bSubDivide[q0fvi] = true; - subdivide_count++; - } - } - - if (ON_SubDQuadFaceMesher::Output::patches == m_output && subdivide_count < 4 ) - { - // Get the bicubic NURBS control points for the patches that are exact. - // (subdivide_count < 4) means there is at least one. - // These are delivered as a collection to enable merging them into as - // large a face/patch/... as possible. - ON_SubDLimitNurbsFragment::BispanType pt[4] = - { - ON_SubDLimitNurbsFragment::BispanType::None, - ON_SubDLimitNurbsFragment::BispanType::None, - ON_SubDLimitNurbsFragment::BispanType::None, - ON_SubDLimitNurbsFragment::BispanType::None - }; - // Harvest any exact patches that are available at this subdivision level - const bool bEnableApproximatePatch = false; - unsigned int quadrant_count = qft->GetLimitSubSurfaceMultiPatchCV( - bEnableApproximatePatch, - m_patcher->m_patch_fragment.m_patch_cv, - pt - ); - if ( 4 != subdivide_count + quadrant_count ) - return ON_SUBD_RETURN_ERROR(false); - m_patcher->m_patch_fragment.m_type = ON_SubDLimitNurbsFragment::Type::BicubicQuadrant; - //Internal_UpdateFaceRegionCorners(qft, face_region); - bool bCallbackResult = m_patcher->Send5x5Patch( display_density, face_region, pt ); - if ( false == bCallbackResult) - return true; - } - - for (unsigned int q0fvi = 0; q0fvi < 4; q0fvi++) - { - if ( false == bSubDivide[q0fvi]) - continue; - - // local subdivision is required - ON_SubD_FixedSizeHeap* fsh = CheckOutLocalFixedSizeHeap(); - if ( nullptr == fsh ) - return ON_SUBD_RETURN_ERROR(false); - - ON_SubDQuadNeighborhood qft1; - if (false == qft->Subdivide( q0fvi, *fsh, &qft1 )) - { - ReturnLocalFixedSizeHeap(fsh); - return ON_SUBD_RETURN_ERROR(false); - } - //if (ON_SubDQuadFaceMesher::Output::patches == m_output) - // Internal_UpdateFaceRegionCorners(qft, face_region); - - subdivide_count--; - if (0 == subdivide_count) - { - // If there is an exraordiary vertex and qft is using one of the - // m_fsh[] on this class, then the recursion will need the - // m_fsh the no longer needed qft is using. - const bool bRetainFixedSizeHeap - = nullptr == qft->m_fsh - || false == ReturnLocalFixedSizeHeap(qft->m_fsh); - ON_SubDQuadNeighborhood::Clear(qft, bRetainFixedSizeHeap); - } - - unsigned int submesh_point_i0 = point_i0 + ((1==q0fvi || 2==q0fvi) ? count : 0); - unsigned int submesh_point_j0 = point_j0 + ((2==q0fvi || 3==q0fvi) ? count : 0); - - ON_SubDFaceRegion face_corner_region(face_region); - face_corner_region.Push(q0fvi); - const bool rc = GetLimitSubMeshOrPatch(face_corner_region, &qft1, display_density-1, submesh_point_i0, submesh_point_j0 ); - - ReturnLocalFixedSizeHeap(fsh); - if ( false == rc ) - return ON_SUBD_RETURN_ERROR(false); - } - - return true; - } - - if (1 == count && 0 == display_density) - { - // No more subdivison steps are permitted - if (ON_SubDQuadFaceMesher::Output::patches == m_output) - { - ON_SubDLimitNurbsFragment::BispanType pt[4] = - { - ON_SubDLimitNurbsFragment::BispanType::None, - ON_SubDLimitNurbsFragment::BispanType::None, - ON_SubDLimitNurbsFragment::BispanType::None, - ON_SubDLimitNurbsFragment::BispanType::None - }; - - ON_SubDComponentRegionBreakpoint(&face_region.m_face_region); - - // Harvest whatever patches are available and allow approximate patches to be returned - // if an appropriate number of subdivisions have been performed - const bool bEnableApproximatePatch - = qft->m_extraordinary_corner_vertex_count <= 1 - && face_region.m_face_region.SubdivisionCount() >= 2; - - while( - bEnableApproximatePatch - && 1 == qft->m_extraordinary_corner_vertex_count - && 1 == qft->m_exact_quadrant_patch_count - && nullptr != qft->m_center_edges[0] - && nullptr != qft->m_center_edges[1] - && nullptr != qft->m_center_edges[2] - && nullptr != qft->m_center_edges[3] - ) - { - bool bIsDart = false; - unsigned int extraordinary_vertex_index = qft->ExtraordinaryCenterVertexIndex(ON_SubD::VertexTag::Crease, 4); - if (extraordinary_vertex_index > 3 && ON_SubDQuadFaceMesher::Output::patches == m_output) - { - // Test for Dart fixes RH-31322 - extraordinary_vertex_index = qft->ExtraordinaryCenterVertexIndex(ON_SubD::VertexTag::Dart, 4); - if ( extraordinary_vertex_index > 3) - break; - bIsDart = true; - } - const ON_SubDVertex* extraordinary_vertex = qft->CenterVertex(extraordinary_vertex_index); - if (nullptr == extraordinary_vertex) - break; - - - ON_SubD_FixedSizeHeap* fsh = CheckOutLocalFixedSizeHeap(); - if (nullptr == fsh) - break; - ON_SubDQuadNeighborhood qft1; - - // claculate limit points on edges needed to get approximate NURBS patches near the singular point. - if (qft->Subdivide(extraordinary_vertex_index, *fsh, &qft1)) - { - ON_2dex qft1_vdex[2] = {ON_2dex::Unset,ON_2dex::Unset}; - unsigned int center_edge_index[2] = { ON_UNSET_UINT_INDEX,ON_UNSET_UINT_INDEX }; - switch (extraordinary_vertex_index) - { - case 0: - center_edge_index[0] = 3; - qft1_vdex[0] = ON_2dex(1, 2); - center_edge_index[1] = 0; - qft1_vdex[1] = ON_2dex(2, 1); - break; - case 1: - center_edge_index[0] = 0; - qft1_vdex[0] = ON_2dex(1, 1); - center_edge_index[1] = 1; - qft1_vdex[1] = ON_2dex(2, 2); - break; - case 2: - center_edge_index[0] = 1; - qft1_vdex[0] = ON_2dex(2, 1); - center_edge_index[1] = 2; - qft1_vdex[1] = ON_2dex(1, 2); - break; - case 3: - center_edge_index[0] = 2; - qft1_vdex[0] = ON_2dex(2, 2); - center_edge_index[1] = 3; - qft1_vdex[1] = ON_2dex(1, 1); - break; - default: - center_edge_index[0] = ON_UNSET_UINT_INDEX; - qft1_vdex[0] = ON_2dex::Unset; - center_edge_index[1] = ON_UNSET_UINT_INDEX; - qft1_vdex[1] = ON_2dex::Unset; - break; - } - for (int n = 0; n < 2; n++) - { - if (center_edge_index[n] >= 4) - continue; - if (qft->m_bCenterEdgeLimitPoint[center_edge_index[n]]) - continue; - const ON_SubDVertex* v = qft1.m_vertex_grid[qft1_vdex[n].i][qft1_vdex[n].j]; - if (nullptr == v) - continue; - qft->m_bCenterEdgeLimitPoint[center_edge_index[n]] = v->GetLimitPoint(ON_SubD::SubDType::QuadCatmullClark, qft1.m_face_grid[1][1], true, qft->m_center_edge_limit_point[center_edge_index[n]]); - } - } - - const ON_2dex srf_cv1_side_midpoint_dex[4] = { ON_2dex(2,0), ON_2dex(4,2), ON_2dex(2,4), ON_2dex(0,2) }; - const ON_2dex srf_cv1_ccw_dex[4] = { ON_2dex(1,0), ON_2dex(0,1), ON_2dex(-1,0), ON_2dex(0,-1) }; - if (ON_SubD::EdgeTag::Crease == qft->m_center_edges[extraordinary_vertex_index]->m_edge_tag) - { - const ON_2dex delta(srf_cv1_ccw_dex[extraordinary_vertex_index]); - const ON_2dex dex0 = srf_cv1_side_midpoint_dex[extraordinary_vertex_index]; - const ON_2dex dex1(dex0.i + delta.i, dex0.j + delta.j); - const ON_2dex dex2(dex1.i + delta.i, dex1.j + delta.j); - double *P[3] = { &qft->m_srf_cv1[dex0.i][dex0.j][0], &qft->m_srf_cv1[dex1.i][dex1.j][0], &qft->m_srf_cv1[dex2.i][dex2.j][0] }; - while (ON_UNSET_VALUE == P[0][0] && ON_UNSET_VALUE == P[1][0] && ON_UNSET_VALUE == P[2][0]) - { - // calculate 3 additional subd points needed to get patch 3 - ON_SubDQuadNeighborhood::Clear(&qft1, false); - if (false == qft->Subdivide((extraordinary_vertex_index + 1) % 4, *fsh, &qft1)) - break; - if (1 != qft1.m_boundary_crease_count) - break; - for (int n = 0; n < 4; n++) - { - if ( - qft1.m_bBoundaryCrease[n] - && nullptr != qft1.m_center_edges[n] - && ON_SubD::EdgeTag::Crease == qft1.m_center_edges[n]->m_edge_tag - ) - { - ON_2dex crease_dex[3]; - crease_dex[0] = ON_SubDQuadNeighborhood::CenterVertexDex(n); - crease_dex[1] = ON_SubDQuadNeighborhood::CenterVertexDex((n+1)%4); - ON_2dex d(crease_dex[1].i - crease_dex[0].i, crease_dex[1].j - crease_dex[0].j); - crease_dex[2] = ON_2dex(crease_dex[1].i + d.i, crease_dex[1].j + d.j); - int tmp = d.j; d.j = d.i; d.i = -tmp; - const ON_2dex smooth_dex[3] = { - ON_2dex(crease_dex[0].i + d.i, crease_dex[0].j + d.j), - ON_2dex(crease_dex[1].i + d.i, crease_dex[1].j + d.j), - ON_2dex(crease_dex[2].i + d.i, crease_dex[2].j + d.j) - }; - for (int k = 0; k < 3; k++) - { - const ON_SubDVertex* crease_vertex = qft1.m_vertex_grid[crease_dex[k].i][crease_dex[k].j]; - if (nullptr == crease_vertex) - continue; - if ( ON_SubD::VertexTag::Crease != crease_vertex->m_vertex_tag) - continue; - const ON_SubDVertex* smooth_vertex = qft1.m_vertex_grid[smooth_dex[k].i][smooth_dex[k].j]; - if (nullptr == smooth_vertex) - continue; - if ( ON_SubD::VertexTag::Smooth != smooth_vertex->m_vertex_tag) - continue; - P[k][0] = 2.0*crease_vertex->m_P[0] - smooth_vertex->m_P[0]; - P[k][1] = 2.0*crease_vertex->m_P[1] - smooth_vertex->m_P[1]; - P[k][2] = 2.0*crease_vertex->m_P[2] - smooth_vertex->m_P[2]; - } - break; - } - } - break; - } - } - - if (ON_SubD::EdgeTag::Crease == qft->m_center_edges[(extraordinary_vertex_index+3)%4]->m_edge_tag) - { - const ON_2dex delta(srf_cv1_ccw_dex[(extraordinary_vertex_index+1)%4]); // +1 to go reverse direction - const ON_2dex dex0(srf_cv1_side_midpoint_dex[(extraordinary_vertex_index + 3)%4]); - const ON_2dex dex1(dex0.i + delta.i, dex0.j + delta.j); - const ON_2dex dex2(dex1.i + delta.i, dex1.j + delta.j); - double *P[3] = { &qft->m_srf_cv1[dex0.i][dex0.j][0], &qft->m_srf_cv1[dex1.i][dex1.j][0], &qft->m_srf_cv1[dex2.i][dex2.j][0] }; - if (ON_UNSET_VALUE == P[0][0] && ON_UNSET_VALUE == P[1][0] && ON_UNSET_VALUE == P[2][0]) - { - // calculate 3 additional subd points needed to get patch 1 - - ON_SubDQuadNeighborhood::Clear(&qft1, false); - if (false == qft->Subdivide((extraordinary_vertex_index + 3) % 4, *fsh, &qft1)) - break; - if (1 != qft1.m_boundary_crease_count) - break; - for (int n = 0; n < 4; n++) - { - if ( - qft1.m_bBoundaryCrease[n] - && nullptr != qft1.m_center_edges[n] - && ON_SubD::EdgeTag::Crease == qft1.m_center_edges[n]->m_edge_tag - ) - { - ON_2dex crease_dex[3]; - crease_dex[1] = ON_SubDQuadNeighborhood::CenterVertexDex(n); - crease_dex[0] = ON_SubDQuadNeighborhood::CenterVertexDex((n+1)%4); - ON_2dex d(crease_dex[1].i - crease_dex[0].i, crease_dex[1].j - crease_dex[0].j); - crease_dex[2] = ON_2dex(crease_dex[1].i + d.i, crease_dex[1].j + d.j); - int tmp = d.i; d.i = d.j; d.j = -tmp; - const ON_2dex smooth_dex[3] = { - ON_2dex(crease_dex[0].i + d.i, crease_dex[0].j + d.j), - ON_2dex(crease_dex[1].i + d.i, crease_dex[1].j + d.j), - ON_2dex(crease_dex[2].i + d.i, crease_dex[2].j + d.j) - }; - for (int k = 0; k < 3; k++) - { - const ON_SubDVertex* crease_vertex = qft1.m_vertex_grid[crease_dex[k].i][crease_dex[k].j]; - if (nullptr == crease_vertex) - continue; - if ( ON_SubD::VertexTag::Crease != crease_vertex->m_vertex_tag) - continue; - const ON_SubDVertex* smooth_vertex = qft1.m_vertex_grid[smooth_dex[k].i][smooth_dex[k].j]; - if (nullptr == smooth_vertex) - continue; - if ( ON_SubD::VertexTag::Smooth != smooth_vertex->m_vertex_tag) - continue; - P[k][0] = 2.0*crease_vertex->m_P[0] - smooth_vertex->m_P[0]; - P[k][1] = 2.0*crease_vertex->m_P[1] - smooth_vertex->m_P[1]; - P[k][2] = 2.0*crease_vertex->m_P[2] - smooth_vertex->m_P[2]; - } - break; - } - } - } - } - - if (nullptr != fsh) - ReturnLocalFixedSizeHeap(fsh); - break; - } - - - unsigned int quadrant_count = qft->GetLimitSubSurfaceMultiPatchCV( - bEnableApproximatePatch, - m_patcher->m_patch_fragment.m_patch_cv, - pt - ); - - if (quadrant_count > 0) - { - m_patcher->m_patch_fragment.m_type = ON_SubDLimitNurbsFragment::Type::BicubicQuadrant; - bool bCallbackResult = m_patcher->Send5x5Patch(display_density, face_region, pt); - - if (false == bCallbackResult) - return true; - } - - return true; - } - - // Use limit point evalators to get exact locations and normals - // for the unset corners of this mesh quad. - // In a qft, all the vertices have single sectors - double* P[2][2]; - double* N[2][2]; - ON_SubDSectorLimitPoint limit_point; - for (unsigned int i = 0; i < 2; i++) - { - for (unsigned int j = 0; j < 2; j++) - { - P[i][j] = m_points + (point_i0 + i*count)*m_point_stride0 + (point_j0 + j*count)*m_point_stride1; - if (ON_UNSET_VALUE == P[i][j][0]) - { - N[i][j] = m_normals + (point_i0 + i*count)*m_normal_stride0 + (point_j0 + j*count)*m_normal_stride1; - - if (false == qft->m_vertex_grid[1 + i][1 + j]->GetLimitPoint(ON_SubD::SubDType::QuadCatmullClark, qft->m_face_grid[1][1], true, limit_point )) - return ON_SUBD_RETURN_ERROR(false); - - P[i][j][0] = limit_point.m_limitP[0]; - P[i][j][1] = limit_point.m_limitP[1]; - P[i][j][2] = limit_point.m_limitP[2]; - - N[i][j][0] = limit_point.m_limitN[0]; - N[i][j][1] = limit_point.m_limitN[1]; - N[i][j][2] = limit_point.m_limitN[2]; - } - } - } - return true; - } - - return ON_SUBD_RETURN_ERROR(false); -} - -bool ON_SubDQuadFaceMesher::GetLimitMesh( - const ON_SubDFaceRegion& face_region, - const ON_SubDFace* face - ) -{ - - ReturnAllFixedSizeHeaps(); - - m_count = 0; - - if (ON_SubDQuadFaceMesher::Output::mesh != m_output) - return ON_SUBD_RETURN_ERROR(false); - - if (nullptr == face || 4 != face->m_edge_count) - return ON_SUBD_RETURN_ERROR(false); - - unsigned int count = ON_SubDLimitMeshFragment::SideSegmentCountFromDisplayDensity(m_display_density); - - if (0 == count) - { - return ON_SUBD_RETURN_ERROR(false); - } - - - if (count > m_capacity) - { - return ON_SUBD_RETURN_ERROR(false); - } - - // Get neighborhood topology information - ON_SubDQuadNeighborhood qft; - if (false == qft.Set(face)) - return ON_SUBD_RETURN_ERROR(false); - - // GetLimitSubMesh is recursive. - m_count = count; - UnsetMeshPoints(); - - return GetLimitSubMeshOrPatch(face_region,&qft,m_display_density,0,0); -} - - -bool ON_SubDQuadFaceMesher::GetLimitPatches( - const ON_SubDFaceRegion& face_region, - const ON_SubDFace* face - ) -{ - ReturnAllFixedSizeHeaps(); - - m_count = 0; - - if (ON_SubDQuadFaceMesher::Output::patches != m_output) - return ON_SUBD_RETURN_ERROR(false); - - if (nullptr == face || 4 != face->m_edge_count) - return ON_SUBD_RETURN_ERROR(false); - - unsigned int count = ON_SubDLimitMeshFragment::SideSegmentCountFromDisplayDensity(m_display_density); - - if (0 == count) - { - return ON_SUBD_RETURN_ERROR(false); - } - - // Get neighborhood topology information - ON_SubDQuadNeighborhood qft; - if (false == qft.Set(face)) - return ON_SUBD_RETURN_ERROR(false); - - // GetLimitSubMesh is recursive. - return GetLimitSubMeshOrPatch(face_region,&qft,m_display_density,0,0); -} - - -static bool GetLimitSurfaceInStepsSetup( - const ON_SubD& subd, - ON_SubDDisplayParameters& limit_mesh_parameters - ) -{ - const unsigned int level_count = subd.LevelCount(); - if (0 == level_count) - return ON_SUBD_RETURN_ERROR(false); - - - if (1 == level_count && ON_SubD::SubDType::Unset == subd.ActiveLevelSubDType()) - const_cast< ON_SubD& >(subd).SetSubDType(ON_SubD::SubDType::QuadCatmullClark); - - const ON_SubD::SubDType subd_type = subd.ActiveLevelSubDType(); - if (ON_SubD::SubDType::QuadCatmullClark != subd_type) - { - // TODO - support tri subd after quad stuff is finished. - return ON_SUBD_RETURN_ERROR(false); - } - - limit_mesh_parameters.m_progress_reporter_interval.Intersection(ON_Interval::ZeroToOne); - if ( false == limit_mesh_parameters.m_progress_reporter_interval.IsIncreasing()) - limit_mesh_parameters.m_progress_reporter_interval = ON_Interval::ZeroToOne; - - return true; -} - -unsigned int ON_SubD::GetLimitSurfaceMeshInFragments( - const class ON_SubDDisplayParameters& limit_mesh_parameters, - ON__UINT_PTR fragment_callback_context, - bool(*fragment_callback_function)(ON__UINT_PTR, const class ON_SubDLimitMeshFragment*) - ) const -{ - ON_SubDDisplayParameters local_limit_mesh_parameters = limit_mesh_parameters; - - if ( false == GetLimitSurfaceInStepsSetup(*this,local_limit_mesh_parameters) ) - return ON_SUBD_RETURN_ERROR(0); - - - ON_SubDFaceIterator fit(*this); - unsigned int fragment_count = GetQuadLimitSurfaceMeshFragmentsHelper( - fit, - local_limit_mesh_parameters, - fragment_callback_context, - fragment_callback_function - ); - - if ( fragment_count > 0 ) - return fragment_count; - - return ON_SUBD_RETURN_ERROR(0); -} - -unsigned int ON_SubD::GetLimitSurfaceNurbsFragments( - const class ON_SubDDisplayParameters& limit_mesh_parameters, - ON__UINT_PTR fragment_callback_context, - bool(*begin_face_callback_function)(ON__UINT_PTR ,const ON_SubDFaceRegion&),//, const class ON_SubDFace*, const class ON_SubDFace*, unsigned int), - bool(*fragment_callback_function)(ON__UINT_PTR, const class ON_SubDLimitNurbsFragment*) - ) const -{ - ON_SubDDisplayParameters local_limit_mesh_parameters = limit_mesh_parameters; - - if ( false == GetLimitSurfaceInStepsSetup(*this,local_limit_mesh_parameters) ) - return ON_SUBD_RETURN_ERROR(0); - - ON_SubDFaceIterator fit(*this); - unsigned int fragment_count = GetQuadLimitSurfacePatchFragmentsHelper( - fit, - local_limit_mesh_parameters, - fragment_callback_context, - begin_face_callback_function, - fragment_callback_function - ); - - if ( fragment_count > 0 ) - return fragment_count; - - return ON_SUBD_RETURN_ERROR(0); -} - - - -unsigned int ON_SubDFaceIterator::LimitSurfaceMeshFragmentCount( - ON_SubD::FacetType facet_type - ) const -{ - unsigned int fragment_count = 0; - unsigned short ordinary_edge_count - = (ON_SubD::FacetType::Tri == facet_type) - ? 3U - : 4U; // default is quads - - for (const ON_SubDFace* face = m_face_first; nullptr != face; face = face->m_next_face) - { - if ( ordinary_edge_count == face->m_edge_count ) - fragment_count++; - else - fragment_count += face->m_edge_count; - } - return fragment_count; -} - - -unsigned int ON_SubD::LimitSurfaceMeshFragmentCount() const -{ - return ActiveLevel().LimitSurfaceMeshFragmentCount(); -} - -unsigned int ON_SubDLevel::LimitSurfaceMeshFragmentCount() const -{ - unsigned int fragment_count = 0; - ON_SubD::FacetType facet_type = ON_SubD::FacetTypeFromSubDType(m_subdivision_type); - unsigned short ordinary_edge_count - = (ON_SubD::FacetType::Tri == facet_type) - ? 3U - : 4U; // default is quads - - for (const ON_SubDFace* face = m_face[0]; nullptr != face; face = face->m_next_face) - { - if ( ordinary_edge_count == face->m_edge_count ) - fragment_count++; - else - fragment_count += face->m_edge_count; - } - return fragment_count; -} - -ON_SubDLimitMesh ON_SubD::UpdateLimitSurfaceMesh( - unsigned int minimum_display_density - ) const -{ - - ON_SubDDisplayParameters display_parameters; - display_parameters.m_display_density = minimum_display_density; - return ActiveLevel().UpdateLimitSurfaceMesh(*this,display_parameters); -} - -ON_SubDLimitMesh ON_SubDLevel::UpdateLimitSurfaceMesh( - const ON_SubD& subd, - const class ON_SubDDisplayParameters& display_parameters - ) const -{ - if ( IsEmpty() ) - return ON_SubDLimitMesh::Empty; - - if (m_limit_mesh.IsEmpty() || display_parameters.m_display_density > m_limit_mesh.DisplayParameters().m_display_density) - { - ON_SubDLimitMesh local_limit_mesh; - if (nullptr != ON_SubDLimitMesh::Create(subd, display_parameters, &local_limit_mesh)) - { - ON_SubDLimitMesh::Swap(m_limit_mesh,local_limit_mesh); - local_limit_mesh.Clear(); - } - } - - return m_limit_mesh; -} - -class ON_SubDLimitMesh ON_SubD::LimitSurfaceMesh() const -{ - return ActiveLevel().m_limit_mesh; -} - -void ON_SubD::ClearLimitSurfaceMesh() const -{ - const ON_SubDLevel* level = ActiveLevelConstPointer(); - - if ( nullptr != level ) - level->m_limit_mesh = ON_SubDLimitMesh::Empty; -} - void ON_SubD::ClearEvaluationCache() const { const ON_SubDLevel* level = ActiveLevelConstPointer(); @@ -4061,1427 +1735,5 @@ void ON_SubD::ClearEvaluationCache() const } } - - //////////////////////////////////////////////////////////////////////////// -class ON_SUBD_CLASS ON_SubDLimitSurfaceFragment -{ -public: - ON_SubDLimitSurfaceFragment() = default; - ~ON_SubDLimitSurfaceFragment() = default; - ON_SubDLimitSurfaceFragment(const ON_SubDLimitSurfaceFragment&) = default; - ON_SubDLimitSurfaceFragment& operator=(const ON_SubDLimitSurfaceFragment&) = default; - -public: - static const ON_SubDLimitSurfaceFragment Empty; - -public: - // m_face_region identifies what part of the SubD level0 face is or will be modeled by m_surface. - ON_SubDFaceRegion m_face_region; - - // knot vector is uniform and not clamped. - ON_NurbsSurface* m_surface = nullptr; - - ON_SubDLimitSurfaceFragment* Quadrant(unsigned int quadrant_index, bool bAllocateIfMissing); - ON_SubDLimitSurfaceFragment* Parent(); - - static ON_SubDLimitSurfaceFragment* AllocateSurfaceFragment(); - static void ReturnSurfaceFragment(ON_SubDLimitSurfaceFragment*); - - bool SetSurface(ON_NurbsSurface* surface); - - bool SetSurfaceFromQuadrants( - ON_SubD::NurbsSurfaceType nurbs_surface_type - ); - - bool SetQuadrantSurface(ON_NurbsSurface* quadrant_surface,unsigned int quadrant_index); - -private: - // Parent fragment for this - ON_SubDLimitSurfaceFragment* m_parent = nullptr; - - // The 4 quadrants of this region - ON_SubDLimitSurfaceFragment* m_quadrants[4] = {}; - - static ON_FixedSizePool m_fsp; -}; - -ON_FixedSizePool ON_SubDLimitSurfaceFragment::m_fsp; - -ON_SubDLimitSurfaceFragment* ON_SubDLimitSurfaceFragment::AllocateSurfaceFragment() -{ - ON_MemoryAllocationTracking disable_tracking(false); - if (0 == ON_SubDLimitSurfaceFragment::m_fsp.SizeofElement()) - { - ON_SubDLimitSurfaceFragment::m_fsp.Create(sizeof(ON_SubDLimitSurfaceFragment), 64, 64); - } - ON_SubDLimitSurfaceFragment* f = (ON_SubDLimitSurfaceFragment*)ON_SubDLimitSurfaceFragment::m_fsp.AllocateElement(); - if (nullptr == f) - { - ON_SUBD_ERROR("Allocation failed"); - } - return f; -} - -void ON_SubDLimitSurfaceFragment::ReturnSurfaceFragment(ON_SubDLimitSurfaceFragment* f ) -{ - if (nullptr != f) - ON_SubDLimitSurfaceFragment::m_fsp.ReturnElement(f); -} - -bool ON_SubDLimitSurfaceFragment::SetSurface(ON_NurbsSurface* surface) -{ - if (nullptr == surface) - return false; - if (nullptr != m_surface) - { - ON_SUBD_ERROR("Surface exists."); - return false; - } - if ( - nullptr != m_quadrants[0] - || nullptr != m_quadrants[1] - || nullptr != m_quadrants[2] - || nullptr != m_quadrants[3] - ) - { - ON_SUBD_ERROR("Setting surface when quadrants exist."); - } - m_surface = surface; - return true; -} - -bool ON_SubDLimitSurfaceFragment::SetQuadrantSurface(ON_NurbsSurface* quadrant_surface, unsigned int quadrant_index) -{ - if (nullptr == quadrant_surface) - return false; - ON_SubDLimitSurfaceFragment* q = Quadrant(quadrant_index, true); - if (nullptr == q) - return false; - return q->SetSurface(quadrant_surface); -} - -static bool Internal_EqualKnots( - double knot_tol, - int dir, - const ON_NurbsSurface* lhs, - const ON_NurbsSurface* rhs -) -{ - // all orders are 4 - const int knot_count = lhs->KnotCount(dir); - if (knot_count != rhs->KnotCount(dir)) - return false; - const double* lhs_knot = lhs->m_knot[dir]; - const double* rhs_knot = rhs->m_knot[dir]; - for (int i = 0; i < knot_count; i++) - { - if ( !(fabs(lhs_knot[i] - rhs_knot[i]) <= knot_tol) ) - return false; - } - return true; -} - - -static bool Internal_OverlapingKnots( - double knot_tol, - int dir, - const ON_NurbsSurface* lhs, - const ON_NurbsSurface* rhs -) -{ - // all orders are 4 - const double* lhs_knot = lhs->m_knot[dir]; - const double* rhs_knot = rhs->m_knot[dir]; - if (!(rhs_knot[0] < rhs_knot[1] && rhs_knot[1] < rhs_knot[2] && rhs_knot[2] < rhs_knot[3])) - return false; - const unsigned int lhs_knot_count = lhs->KnotCount(dir); - lhs_knot += (lhs_knot_count - 5); - for (unsigned int i = 0; i < 5; i++) - { - if (!(fabs(lhs_knot[i] - rhs_knot[i]) <= knot_tol)) - return false; - } - return true; -} - -static ON_NurbsSurface* Internal_MergeC2Neighbors( - ON_SubD::NurbsSurfaceType nurbs_surface_type, - int dir, - ON_NurbsSurface* lhs, - ON_NurbsSurface* rhs -) -{ - // Context: - // lhs and rhs are cubic non-rational NURBS and are known to meed C2 at the shared edge. - // - // dir = 0: join East side of lhs to West side of rhs - // dir = 1: join North side of lhs to South side of rhs - // Remaining comments are for dir = 0: - - if ( - dir < 0 || dir > 1 - || nullptr == lhs || nullptr == rhs - || 4 != lhs->m_order[0] || 4 != lhs->m_order[1] - || 4 != rhs->m_order[0] || 4 != rhs->m_order[1] - || 0 != lhs->m_is_rat || 0 != rhs->m_is_rat - || 3 != lhs->m_dim || 3 != rhs->m_dim - ) - { - ON_SUBD_ERROR("Invalid input."); - return nullptr; - } - - const int dir1 = 1 - dir; - - if (!(lhs->Domain(dir).m_t[1] == rhs->Domain(dir)[0])) - { - ON_SUBD_ERROR("Invalid dir or input domains."); - return nullptr; - } - - if (!(lhs->Domain(dir1) == rhs->Domain(dir1))) - { - ON_SUBD_ERROR("Invalid dir or input domains."); - return nullptr; - } - - const double knot_tol = 1e-8; - - // merged->m_knots[dir][...] begins with lhs->m_knot[dir][...] and ends with rhs->m_knot[dir][...] - const bool bOverlapMerge = Internal_OverlapingKnots(knot_tol, dir, lhs, rhs); - if (false == bOverlapMerge) - { - if (ON_SubD::NurbsSurfaceType::Large != nurbs_surface_type) - return nullptr; // not permitted to modify knots. - lhs->ClampEnd(dir, 2); - rhs->ClampEnd(dir, 2); - } - - // We need lhs->m_knot[dir1][...] = rhs->m_knot[dir1][...] - // and merged->m_knots[dir1][...] = lhs->m_knot[dir1][...] - if (false == Internal_EqualKnots(knot_tol, dir1, lhs, rhs)) - { - if (ON_SubD::NurbsSurfaceType::Large != nurbs_surface_type) - return nullptr; // not permitted to modify knots. - - lhs->ClampEnd(dir1, 2); - rhs->ClampEnd(dir1, 2); - if (false == Internal_EqualKnots(knot_tol, dir1, lhs, rhs)) - { - // Insert knots to make lhs->m_knot[dir1][...] = rhs->m_knot[dir1][...] equal - double lhs_k = lhs->m_knot[dir1][2]; - double rhs_k = rhs->m_knot[dir1][2]; - int lhs_i = 3; - int rhs_i= 3; - while (lhs_i < lhs->m_cv_count[dir1] && rhs_i < rhs->m_cv_count[dir1]) - { - double lhs_k0 = lhs_k; - double rhs_k0 = rhs_k; - lhs_k = lhs->m_knot[dir1][lhs_i]; - rhs_k = rhs->m_knot[dir1][rhs_i]; - if (!(lhs_k0+knot_tol < lhs_k)) - { - ON_SUBD_ERROR("Invalid lhs knots or a bug."); - return nullptr; - } - if (!(rhs_k0+knot_tol < rhs_k)) - { - ON_SUBD_ERROR("Invalid rhs knots or a bug."); - return nullptr; - } - - if (lhs_k + knot_tol < rhs_k) - { - // insert knot in rhs at lhs_k - if (false == rhs->InsertKnot(dir1, lhs_k, 1)) - { - ON_SUBD_ERROR("rhs knot insertion failed."); - return nullptr; - } - rhs_k = rhs->m_knot[dir1][rhs_i]; - } - - if (rhs_k + knot_tol < lhs_k) - { - // insert knot in lhs at rhs_k - if ( false == lhs->InsertKnot(dir1, rhs_k, 1) ) - { - ON_SUBD_ERROR("lhs knot insertion failed."); - return nullptr; - } - lhs_k = lhs->m_knot[dir1][lhs_i]; - } - if (!(fabs(lhs_k - rhs_k) <= knot_tol)) - { - ON_SUBD_ERROR("Unexpected knot insertion failure."); - return nullptr; - } - lhs_i++; - rhs_i++; - } - if (false == Internal_EqualKnots(knot_tol, dir1, lhs, rhs)) - { - ON_SUBD_ERROR("Unexpected different knot vectors.8"); - return nullptr; - } - } - } - - //// DEBUGGING - //if (false == lhs->IsValid()) - //{ - // ON_SUBD_ERROR("lhs is not valid."); - // return nullptr; - //} - - // // DEBUGGING - //if (false == rhs->IsValid()) - //{ - // ON_SUBD_ERROR("rhs is not valid."); - // return nullptr; - //} - - // Fill in merged surface - const int lhs_cv_count0 = lhs->m_cv_count[0]; - const int lhs_cv_count1 = lhs->m_cv_count[1]; - const int rhs_cv_count0 = rhs->m_cv_count[0]; - const int rhs_cv_count1 = rhs->m_cv_count[1]; - int cv_count0 = lhs_cv_count0; - int cv_count1 = lhs_cv_count1; - int rhs_cv_dex0 = 0; - int rhs_cv_dex1 = 0; - if (0 == dir) - { - if (lhs_cv_count1 != rhs_cv_count1) - { - ON_SUBD_ERROR("Bug in dir=0 merging."); - return nullptr; - } - rhs_cv_dex0 = (bOverlapMerge) ? 3 : 1; - cv_count0 += (rhs_cv_count0 - rhs_cv_dex0); - } - else - { - if (lhs_cv_count0 != rhs_cv_count0) - { - ON_SUBD_ERROR("Bug in dir=1 merging."); - return nullptr; - } - rhs_cv_dex1 = (bOverlapMerge) ? 3 : 1; - cv_count1 += (rhs_cv_count1 - rhs_cv_dex1); - } - ON_NurbsSurface* merged_srf = new ON_NurbsSurface(3, 0, 4, 4, cv_count0, cv_count1); - - const double* src; - double* dst; - for (int i = 0; i < lhs_cv_count0; i++) - { - for (int j = 0; j < lhs_cv_count1; j++) - { - src = lhs->CV(i, j); - dst = merged_srf->CV(i, j); - dst[0] = src[0]; - dst[1] = src[1]; - dst[2] = src[2]; - } - } - - if (0 == dir) - { - int dst_i = lhs_cv_count0; - for (int i = rhs_cv_dex0; i < rhs_cv_count0; i++, dst_i++) - { - for (int j = 0; j < rhs_cv_count1; j++) - { - src = rhs->CV(i, j); - dst = merged_srf->CV(dst_i, j); - dst[0] = src[0]; - dst[1] = src[1]; - dst[2] = src[2]; - } - } - } - else - { - int dst_j = lhs_cv_count1; - for (int j = rhs_cv_dex1; j < rhs_cv_count1; j++, dst_j++) - { - for (int i = 0; i < rhs_cv_count0; i++) - { - src = rhs->CV(i, j); - dst = merged_srf->CV(i, dst_j); - dst[0] = src[0]; - dst[1] = src[1]; - dst[2] = src[2]; - } - } - } - - const int lhs_knot_count0 = lhs->KnotCount(0); - const int lhs_knot_count1 = lhs->KnotCount(1); - for (int i = 0; i < lhs_knot_count0; i++) - merged_srf->m_knot[0][i] = lhs->m_knot[0][i]; - for (int i = 0; i < lhs_knot_count1; i++) - merged_srf->m_knot[1][i] = lhs->m_knot[1][i]; - - if (0 == dir) - { - const int rhs_knot_count0 = rhs->KnotCount(0); - dst = &merged_srf->m_knot[0][lhs_knot_count0]; - for (int i = 2 + rhs_cv_dex0; i < rhs_knot_count0; i++) - *dst++ = rhs->m_knot[0][i]; - } - else - { - const int rhs_knot_count1 = rhs->KnotCount(1); - dst = &merged_srf->m_knot[1][lhs_knot_count1]; - for (int i = 2 + rhs_cv_dex1; i < rhs_knot_count1; i++) - *dst++ = rhs->m_knot[1][i]; - } - - - //// DEBUGGING - //if (false == merged_srf->IsValid()) - //{ - // ON_SUBD_ERROR("merged_srf is not valid."); - // delete merged_srf; - // merged_srf = nullptr; - //} - - return merged_srf; -} - -bool ON_SubDLimitSurfaceFragment::SetSurfaceFromQuadrants( - ON_SubD::NurbsSurfaceType nurbs_surface_type - ) -{ - if (nullptr != m_surface) - return true; - - ON_NurbsSurface* s[4] = { 0 }; - for (unsigned int quadrant_index = 0; quadrant_index < 4; quadrant_index++) - { - if (nullptr == m_quadrants[quadrant_index]) - return false; - - if (nullptr == m_quadrants[quadrant_index]->m_surface) - return false; - s[quadrant_index] = m_quadrants[quadrant_index]->m_surface; - if (ON_SubD::NurbsSurfaceType::Large != nurbs_surface_type) - { - // not permitted to add knots - if ( - s[0]->m_cv_count[0] != s[quadrant_index]->m_cv_count[0] - || s[0]->m_cv_count[1] != s[quadrant_index]->m_cv_count[1] - ) - { - return false; - } - } - } - - s[0]->SetDomain(0, 0.0, 0.5); - s[1]->SetDomain(0, 0.5, 1.0); - s[2]->SetDomain(0, 0.5, 1.0); - s[3]->SetDomain(0, 0.0, 0.5); - - s[0]->SetDomain(1, 0.0, 0.5); - s[1]->SetDomain(1, 0.0, 0.5); - s[2]->SetDomain(1, 0.5, 1.0); - s[3]->SetDomain(1, 0.5, 1.0); - - ON_NurbsSurface* bottom = Internal_MergeC2Neighbors(nurbs_surface_type, 0, s[0], s[1]); - if (nullptr == bottom) - return false; - ON_NurbsSurface* top = Internal_MergeC2Neighbors(nurbs_surface_type, 0, s[3], s[2]); - if (nullptr == top) - { - delete bottom; - return false; - } - - m_surface = Internal_MergeC2Neighbors(nurbs_surface_type, 1, bottom, top); - delete bottom; - delete top; - if (nullptr == m_surface) - return false; - - for (unsigned int quadrant_index = 0; quadrant_index < 4; quadrant_index++) - { - delete m_quadrants[quadrant_index]->m_surface; - m_quadrants[quadrant_index]->m_surface = nullptr; - m_quadrants[quadrant_index]->m_parent = nullptr; - ON_SubDLimitSurfaceFragment::ReturnSurfaceFragment(m_quadrants[quadrant_index]); - m_quadrants[quadrant_index] = nullptr; - } - - return true; -} - -const ON_SubDLimitSurfaceFragment ON_SubDLimitSurfaceFragment::Empty ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_SubDLimitSurfaceFragment); - -class Internal_SubDNurbsPatchGetter -{ -public: - Internal_SubDNurbsPatchGetter( - const ON_SubD& subd, - unsigned int patch_density, - ON_SubD::NurbsSurfaceType nurbs_surface_type, - ON__UINT_PTR nurbs_callback_context, - bool(*nurbs_callback_function)(ON__UINT_PTR, const ON_SubDFaceRegion&, class ON_NurbsSurface*) - ) - : m_subd(subd) - , m_patch_density(patch_density) - , m_nurbs_surface_type(ON_SubD::NurbsSurfaceType::Unset == nurbs_surface_type ? ON_SubD::NurbsSurfaceType::Medium : nurbs_surface_type) - , m_nurbs_callback_context(nurbs_callback_context) - , m_nurbs_callback_function(nurbs_callback_function) - {} - - const ON_SubD& m_subd; - - const unsigned int m_patch_density = 2; - const ON_SubD::NurbsSurfaceType m_nurbs_surface_type = ON_SubD::NurbsSurfaceType::Unset; - - // m_current_face_region identifies the current region being accumulated in m_fragment_tree - // and is set in Internal_SubDNurbsPatchGetter::BeginFaceCallback(). - // If the level 0 face is a quad, then m_fragments_face_region.m_subdivision_count = 0; - // If the level 0 face is an N-gon (N != 4), then m_fragments_face_region.m_subdivision_count = 1. - ON_SubDFaceRegion m_current_face_region; - - enum : unsigned int - { - fragments_acculator_capacity = 5 - }; - ON_SubDLimitSurfaceFragment* m_fragment_tree = nullptr; - ON_SubDLimitSurfaceFragment* FragmentLeaf( - const ON_SubDFaceRegion& face_region - ); - - - void AddOutputSurface( - const ON_SubDFaceRegion& face_region, - ON_NurbsSurface* output_surface - ); - - ON__UINT_PTR m_nurbs_callback_context = 0; - bool(*m_nurbs_callback_function)(ON__UINT_PTR, const ON_SubDFaceRegion&, class ON_NurbsSurface*) = nullptr; - - unsigned int m_bicubic_span_count = 0; - - void AddPatch( - const ON_SubDLimitNurbsFragment* patch_fragment - ); - - void ConvertFragmentsToSurfaces(); - - void ConvertPatchToSurfaces( - const ON_SubDLimitNurbsFragment& patch_fragment - ); - - static bool BeginFaceCallback( - ON__UINT_PTR context, // contest = Internal_SubDNurbsPatchGetter* - const ON_SubDFaceRegion& face_region - //, - //const class ON_SubDFace* level0_face_OBSOLETE, - //const class ON_SubDFace* level1_face_OBSOLETE, - //unsigned int level1_face_region_index_OBSOLETE - ); - - - static bool GetLimitSurfaceInPatchesCallback( - ON__UINT_PTR context, // contest = Internal_SubDNurbsPatchGetter* - const ON_SubDLimitNurbsFragment* patch_fragment - ); - - static bool AddToSurfaceArrayCallback1( - ON__UINT_PTR context, // ON_SimpleArray* - const ON_SubDFaceRegion& face_region, - class ON_NurbsSurface* nurbs_surface - ); - - static bool AddToSurfaceArrayCallback2( - ON__UINT_PTR context, // ON_SimpleArray* - const ON_SubDFaceRegion& face_region, - class ON_NurbsSurface* nurbs_surface - ); - -private: - unsigned int m_new_subdivision_edge_id = 0; - - wchar_t* AppendUnsigned( - wchar_t prefix, - unsigned int i, - wchar_t* s, - wchar_t* send - ); -private: - Internal_SubDNurbsPatchGetter() = delete; - Internal_SubDNurbsPatchGetter(const Internal_SubDNurbsPatchGetter&) = delete; - Internal_SubDNurbsPatchGetter& operator=(const Internal_SubDNurbsPatchGetter&) = delete; -}; - -bool Internal_SubDNurbsPatchGetter::BeginFaceCallback( - ON__UINT_PTR context, // contest = Internal_SubDNurbsPatchGetter* - const ON_SubDFaceRegion& face_region -) -{ - Internal_SubDNurbsPatchGetter* p = (Internal_SubDNurbsPatchGetter*)context; - if (nullptr == p) - return true; - - // Flush accumulated fragments from previous face - p->ConvertFragmentsToSurfaces(); - - p->m_current_face_region = face_region; - return true; -} - -bool Internal_SubDNurbsPatchGetter::GetLimitSurfaceInPatchesCallback( - ON__UINT_PTR context, - const ON_SubDLimitNurbsFragment* patch_fragment - ) -{ - ((Internal_SubDNurbsPatchGetter*)context)->AddPatch(patch_fragment); - return true; -} - -wchar_t* Internal_SubDNurbsPatchGetter::AppendUnsigned( - wchar_t prefix, - unsigned int i, - wchar_t* s, - wchar_t* send - ) -{ - if ( 0 != prefix && s < send) - *s++ = prefix; - wchar_t buffer[64]; - wchar_t* sdigit = buffer; - wchar_t* sdigit1 = sdigit + (sizeof(buffer)/sizeof(buffer[0])); - for ( *sdigit++ = 0; sdigit < sdigit1; sdigit++ ) - { - *sdigit = (wchar_t)('0' + (i%10)); - i /= 10; - if (0 == i) - { - while ( s < send && 0 != (*s = *sdigit--) ) - s++; - return s; - } - } - return s; -} - -ON_SubDLimitSurfaceFragment* ON_SubDLimitSurfaceFragment::Parent() -{ - return m_parent; -} - -ON_SubDLimitSurfaceFragment* ON_SubDLimitSurfaceFragment::Quadrant(unsigned int quadrant_index, bool bAllocateIfMissing) -{ - if (quadrant_index >= 4) - { - ON_SUBD_ERROR("Invalid quadrant_index value."); - return nullptr; - } - if (nullptr == m_quadrants[quadrant_index] && bAllocateIfMissing) - { - m_quadrants[quadrant_index] = ON_SubDLimitSurfaceFragment::AllocateSurfaceFragment(); - if (nullptr != m_quadrants[quadrant_index]) - { - m_quadrants[quadrant_index]->m_parent = this; - m_quadrants[quadrant_index]->m_face_region = m_face_region; - m_quadrants[quadrant_index]->m_face_region.Push(quadrant_index); - } - } - return m_quadrants[quadrant_index]; -} - - -ON_SubDLimitSurfaceFragment* Internal_SubDNurbsPatchGetter::FragmentLeaf( - const ON_SubDFaceRegion& patch_face_region -) -{ - const ON_SubDComponentRegion& fragment_fr = m_current_face_region.m_face_region; - if (fragment_fr.m_level0_component_id == 0) - { - ON_SUBD_ERROR("m_fragments_face_region.m_level0_component_id not set."); - return nullptr; - } - - const ON_SubDComponentRegion& patch_fr = patch_face_region.m_face_region; - if (fragment_fr.m_level0_component_id != patch_fr.m_level0_component_id) - { - ON_SUBD_ERROR("m_fragments_face_region.m_level0_component_id != patch_face_region.m_level0_component_id"); - return nullptr; - } - if (patch_fr.SubdivisionCount() < fragment_fr.SubdivisionCount()) - { - ON_SUBD_ERROR("patch_face_region.SubdivisionCount() < m_fragments_face_region.SubdivisionCount()"); - return nullptr; - } - - if (patch_fr.SubdivisionCount() > ON_SubDComponentRegionIndex::IndexCapacity) - { - // unreasonable number of subdivisions - ON_SUBD_ERROR("patch_face_region.SubdivisionCount() > ON_SubDComponentRegionIndex::IndexCapacity"); - return nullptr; - } - - for ( unsigned short i = 0; i < fragment_fr.SubdivisionCount(); i++ ) - { - if (fragment_fr.m_region_index.m_index[i] != patch_fr.m_region_index.m_index[i]) - { - ON_SUBD_ERROR("m_fragments_face_region.m_region_index[] differs from patch_face_region"); - return nullptr; - } - } - - if (nullptr == m_fragment_tree) - { - m_fragment_tree = ON_SubDLimitSurfaceFragment::AllocateSurfaceFragment(); - m_fragment_tree->m_face_region = m_current_face_region; - } - - ON_SubDLimitSurfaceFragment* fragment_leaf = m_fragment_tree; - ON_SubDComponentRegion r = fragment_fr; - while (r.SubdivisionCount() < patch_fr.SubdivisionCount()) - { - unsigned short quadrant_dex = patch_fr.m_region_index.Index(r.SubdivisionCount()); - if (quadrant_dex >= 4) - { - ON_SUBD_ERROR("patch_face_region.m_region_index[] value >= 4."); - return nullptr; - } - r.PushAbsolute(quadrant_dex); // increments r.m_subdivision_count - fragment_leaf = fragment_leaf->Quadrant(quadrant_dex, true); - if (nullptr == fragment_leaf) - { - ON_SUBD_ERROR("fragment tree allocation error."); - return nullptr; - } - if (0 != ON_SubDComponentRegion::CompareTypeIdMarkSubregion(&r, &fragment_leaf->m_face_region.m_face_region)) - { - ON_SUBD_ERROR("corrupt fragment tree."); - return nullptr; - } - } - - return fragment_leaf; -} - - -void Internal_SubDNurbsPatchGetter::AddPatch( - const ON_SubDLimitNurbsFragment* patch_fragment -) -{ - if (nullptr == patch_fragment) - return; - - // face_region identifies what part of the SubD region is represented by patch_fragment - const ON_SubDFaceRegion face_region = patch_fragment->m_face_region; - - ON_SubDLimitNurbsFragment local_patch_fragment(*patch_fragment); - patch_fragment = &local_patch_fragment; - - - bool rc = false; - for (;;) - { - if (m_current_face_region.m_face_region.m_level0_component_id == 0) - { - ON_SUBD_ERROR("m_fragments_face_region.m_level0_component_id == 0"); - break; - } - - if (patch_fragment->m_face_region.m_face_region.m_level0_component_id == 0) - { - ON_SUBD_ERROR("patch_fragment->m_face_region.m_level0_component_id == 0"); - break; - } - - const unsigned int max_bispan_count = patch_fragment->MaximumBispanCount(); - const unsigned int bispan_count = patch_fragment->SetBispanCount(); - if (0 == bispan_count || 0 == max_bispan_count) - { - ON_SUBD_ERROR("No bispans in patch_fragment."); - break; - } - - if ( - ON_SubD::NurbsSurfaceType::Small == m_nurbs_surface_type - || ON_SubD::NurbsSurfaceType::Unprocessed == m_nurbs_surface_type - ) - { - // happens when debugging - rc = true; - break; - } - - ON_SubDLimitSurfaceFragment* fragment_leaf = FragmentLeaf(patch_fragment->m_face_region); - if (nullptr == fragment_leaf) - { - ON_SUBD_ERROR("Unable to get surface holder for patch_fragment->m_face_region."); -break; - } - - if (nullptr != fragment_leaf->m_surface) - { - ON_SUBD_ERROR("fragment_leaf->m_surface is already set."); - break; - } - - // patch_fragment is part of the face we are currently patching - if (bispan_count == max_bispan_count) - { - if (false == fragment_leaf->SetSurface(patch_fragment->GetSurface(nullptr))) - { - ON_SUBD_ERROR("Failed to set surface."); - break; - } - local_patch_fragment = ON_SubDLimitNurbsFragment::Empty; - } - else - { - for (unsigned int quadrant_index = 0; quadrant_index < max_bispan_count; quadrant_index++) - { - if (ON_SubDLimitNurbsFragment::BispanType::None == patch_fragment->m_bispan_type[quadrant_index]) - continue; - if (false == fragment_leaf->SetQuadrantSurface(patch_fragment->GetQuadrantSurface(quadrant_index, nullptr), quadrant_index)) - { - ON_SUBD_ERROR("Failed to set quadrant surface."); - continue; - } - local_patch_fragment.m_bispan_type[quadrant_index] = ON_SubDLimitNurbsFragment::BispanType::None; - } - } - - if (0 == local_patch_fragment.SetBispanCount()) - rc = true; - - while (nullptr != fragment_leaf && fragment_leaf->SetSurfaceFromQuadrants(m_nurbs_surface_type)) - { - fragment_leaf = fragment_leaf->Parent(); - } - - break; - } - - if (false == rc) - ConvertFragmentsToSurfaces(); - - // Convert this patch. Patches should all set to none if we are merging patches.) - // Restore face and edge region information - local_patch_fragment.m_face_region = face_region; - - ConvertPatchToSurfaces(*patch_fragment); - - return; -} - -void Internal_SubDNurbsPatchGetter::ConvertFragmentsToSurfaces() -{ - if (nullptr == m_fragment_tree) - return; - ON_SimpleArray a(64); - a.Append(m_fragment_tree); - m_fragment_tree = nullptr; - unsigned int a_count = a.UnsignedCount(); - while ( a_count > 0 ) - { - for (unsigned int i = 0; i < a_count; i++) - { - ON_SubDLimitSurfaceFragment* f = a[i]; - if (nullptr == f) - continue; - a[i] = 0; - if (f->m_surface) - { - AddOutputSurface(f->m_face_region, f->m_surface); - f->m_surface = nullptr; // receiver must manage f->m_surface. - } - for (unsigned int j = 0; j < 4; j++) - { - ON_SubDLimitSurfaceFragment* q = f->Quadrant(j, false); - if (q) - a.Append(q); - } - ON_SubDLimitSurfaceFragment::ReturnSurfaceFragment(f); - } - unsigned int k = a_count; - const unsigned int kmax = a.UnsignedCount(); - a_count = 0; - while (k < kmax) - a[a_count++] = a[k++]; - a.SetCount(a_count); - } - m_current_face_region = ON_SubDFaceRegion::Empty; -} - - -void Internal_SubDNurbsPatchGetter::AddOutputSurface( - const ON_SubDFaceRegion& face_region, - ON_NurbsSurface* output_surface -) -{ - if (nullptr == output_surface) - return; - - if ( ON_SubD::NurbsSurfaceType::Unprocessed != m_nurbs_surface_type ) - { - output_surface->ClampEnd(0, 2); - output_surface->ClampEnd(1, 2); - Internal_CheckNurbsSurfaceCVs(*output_surface); - } - - m_nurbs_callback_function(m_nurbs_callback_context, face_region, output_surface); -} - -void Internal_SubDNurbsPatchGetter::ConvertPatchToSurfaces( - const ON_SubDLimitNurbsFragment& patch_fragment - ) -{ - // Exports patches as is with no merging. - unsigned int bispan_count = patch_fragment.SetBispanCount(); - if (bispan_count <= 0) - return; - - unsigned int maximum_bispan_count = patch_fragment.MaximumBispanCount(); - ON_SubDComponentRegion central_edge_regions[4] = {}; - ON_SubDFaceRegion local_face_region = patch_fragment.m_face_region; - for (unsigned int quadrant_index = 0; quadrant_index < maximum_bispan_count; quadrant_index++) - { - if (ON_SubDLimitNurbsFragment::BispanType::None == patch_fragment.m_bispan_type[quadrant_index]) - continue; - ON_NurbsSurface* output_surface = patch_fragment.GetQuadrantSurface(quadrant_index, nullptr); - if (nullptr == output_surface) - continue; - if (maximum_bispan_count > 1) - { - // region information must be subdivided - local_face_region.Push(quadrant_index); - const unsigned short central_edge_region_subdivision_count = local_face_region.m_face_region.SubdivisionCount(); - - if (0 == central_edge_regions[quadrant_index].m_level0_component_id) - central_edge_regions[quadrant_index] = Internal_CreateSubdivisionEdgeRegion(central_edge_region_subdivision_count,false); - local_face_region.m_edge_region[(quadrant_index + 1) % 4] = central_edge_regions[quadrant_index]; - local_face_region.m_edge_region[(quadrant_index + 1) % 4].m_level0_component = central_edge_regions[quadrant_index].m_level0_component.ToggleMark(); - - const unsigned int cdex3 = (quadrant_index + 3) % 4; - if (0 == central_edge_regions[cdex3].m_level0_component_id) - central_edge_regions[cdex3] = Internal_CreateSubdivisionEdgeRegion(central_edge_region_subdivision_count,false); - local_face_region.m_edge_region[(quadrant_index+2)%4] = central_edge_regions[cdex3]; - } - - AddOutputSurface(local_face_region, output_surface); - - if (maximum_bispan_count > 1) - local_face_region = patch_fragment.m_face_region; - } -} - - -unsigned int ON_SubD::GetLimitSurfaceNurbs( - const class ON_SubDDisplayParameters& display_parameters, - ON_SubD::NurbsSurfaceType nurbs_surface_type, - ON__UINT_PTR callback_context, - bool(*nurbs_callback_function)(ON__UINT_PTR, const ON_SubDFaceRegion&, class ON_NurbsSurface*) -) const -{ - // TODO: Restructure the code to support this callback function. - Internal_SubDNurbsPatchGetter patch_getter( - *this, - display_parameters.m_display_density, - nurbs_surface_type, - callback_context, - nurbs_callback_function - ); - - GetLimitSurfaceNurbsFragments( - display_parameters, - (ON__UINT_PTR)&patch_getter, - Internal_SubDNurbsPatchGetter::BeginFaceCallback, - Internal_SubDNurbsPatchGetter::GetLimitSurfaceInPatchesCallback - ); - - // Flush the final batch of patches. - patch_getter.ConvertFragmentsToSurfaces(); - - return patch_getter.m_bicubic_span_count; -} - -bool Internal_SubDNurbsPatchGetter::AddToSurfaceArrayCallback1( - ON__UINT_PTR context, // ON_SimpleArray* - const ON_SubDFaceRegion& face_region, - class ON_NurbsSurface* nurbs_surface -) -{ - if (nullptr != nurbs_surface && 0 != context) - { - ON_wString region_id = face_region.ToString(); - nurbs_surface->SetUserString(L"SubDRegionId", region_id); - ((ON_SimpleArray< ON_NurbsSurface* >*)context)->Append(nurbs_surface); - } - return true; -} - -bool Internal_SubDNurbsPatchGetter::AddToSurfaceArrayCallback2( - ON__UINT_PTR context, // ON_SimpleArray* - const ON_SubDFaceRegion& face_region, - class ON_NurbsSurface* nurbs_surface -) -{ - if (nullptr != nurbs_surface && 0 != context) - { - ON_SubDFaceRegionAndNurbs& e = ((ON_SimpleArray< ON_SubDFaceRegionAndNurbs >*)context)->AppendNew(); - e.m_face_region = face_region; - e.m_nurbs_surface = nurbs_surface; - } - return true; -} - -unsigned int ON_SubD::GetLimitSurfaceNurbs( - const class ON_SubDDisplayParameters& display_parameters, - ON_SubD::NurbsSurfaceType nurbs_surface_type, - ON_SimpleArray< ON_NurbsSurface* >& patches - ) const -{ - return GetLimitSurfaceNurbs(display_parameters, nurbs_surface_type, (ON__UINT_PTR)&patches, Internal_SubDNurbsPatchGetter::AddToSurfaceArrayCallback1); -} - -unsigned int ON_SubD::GetLimitSurfaceNurbs( - const class ON_SubDDisplayParameters& display_parameters, - ON_SubD::NurbsSurfaceType nurbs_surface_type, - ON_SimpleArray< ON_SubDFaceRegionAndNurbs >& patches - ) const -{ - return GetLimitSurfaceNurbs(display_parameters, nurbs_surface_type, (ON__UINT_PTR)&patches, Internal_SubDNurbsPatchGetter::AddToSurfaceArrayCallback2); -} - - -static bool Internal_ToBrep( - ON_SimpleArray< ON_SubDFaceRegionAndNurbs >& patches, - ON_Brep& brep -); - -ON_Brep* ON_SubD::GetLimitSurfaceNurbs( - const class ON_SubDDisplayParameters& display_parameters, - ON_Brep* destination_brep - ) const -{ - if (nullptr != destination_brep) - destination_brep->Destroy(); - ON_SimpleArray< ON_SubDFaceRegionAndNurbs > patches; - GetLimitSurfaceNurbs(display_parameters, ON_SubD::NurbsSurfaceType::Large, (ON__UINT_PTR)&patches, Internal_SubDNurbsPatchGetter::AddToSurfaceArrayCallback2); - const unsigned int face_count = patches.UnsignedCount(); - if (face_count <= 0) - return nullptr; - - ON_Brep* brep - = (nullptr != destination_brep) - ? destination_brep - : ON_Brep::New(); - if (false == Internal_ToBrep(patches, *brep)) - { - if (brep != destination_brep) - delete brep; - brep = nullptr; - } - return brep; -} - -static bool Internal_ToBrep( - ON_SimpleArray< ON_SubDFaceRegionAndNurbs >& patches, - ON_Brep& brep -) -{ - const unsigned int face_count = patches.UnsignedCount(); - if (face_count <= 0) - return false; - - const unsigned int split_edge_mark = 0xFFFFFFFF; - const unsigned int bad_edge_mark = 0xFFFFFFFE; - const unsigned int bad_vertex_mark = 0xFFFFFFFD; - - - ON_SimpleArray vertex_map(3 * face_count + 32); - ON_SimpleArray edge_map(4 * face_count + 32); - ON_2udex vm(0, 0); - ON_3udex em(0, 0, 0); - - // Begin working on building vertex_map[] and edge_map[] - for (unsigned int fi = 0; fi < face_count; fi++) - { - const ON_SubDFaceRegionAndNurbs& r = patches[fi]; - ON_NurbsSurface* nurbs_surface = r.m_nurbs_surface; - if (nullptr == nurbs_surface) - continue; - - for (int i = 0; i < 4; i++) - { - vm.i = r.m_face_region.m_vertex_id[i]; - vm.j = fi; - if ( 0 != vm.i) - vertex_map.Append(vm); - - em.i = r.m_face_region.m_edge_region[i].m_level0_component_id; - if (0 != em.i) - { - em.j = r.m_face_region.m_edge_region[i].m_region_index.ToCompressedRegionIndex(); - em.k = fi; - edge_map.Append(em); - } - } - } - - // Remove duplicates from vertex_map[] - vertex_map.QuickSort(ON_2udex::DictionaryCompare); - unsigned int count0 = vertex_map.UnsignedCount(); - unsigned int prev_id = 0; - unsigned int brep_vertex_count = 0; - for (unsigned int i = 0; i < count0; i++) - { - vm = vertex_map[i]; - if (vm.i <= prev_id) - continue; - prev_id = vm.i; - vertex_map[brep_vertex_count++] = vm; - } - vertex_map.SetCount(brep_vertex_count); - - // Create brep vertices and set vertex_map[].j to brep index values so we - // can use vertex_map[] to go from a subd vertex id to a brep vertex index. - brep.m_V.Reserve(brep_vertex_count); - for (unsigned int i = 0; i < brep_vertex_count; i++) - { - ON_3dPoint P(ON_3dPoint::NanPoint); - vm = vertex_map[i]; - const unsigned fi = vm.j; - vertex_map[i].j = bad_vertex_mark; // will be updated below if everything works right - vm.j = bad_vertex_mark; - const ON_SubDFaceRegionAndNurbs& r = patches[fi]; - if (nullptr != r.m_nurbs_surface) - { - for (int j = 0; j < 4; j++) - { - if (vm.i == r.m_face_region.m_vertex_id[j]) - { - const double s = r.m_nurbs_surface->Domain(0)[(1==j||2==j) ? 1 : 0]; - const double t = r.m_nurbs_surface->Domain(1)[(j > 1) ? 1 : 0]; - P = r.m_nurbs_surface->PointAt(s, t); - break; - } - } - } - if (false == P.IsValid()) - { - ON_SUBD_ERROR("Unable to calculate vertex location."); - } - vm.j = brep.m_V.UnsignedCount(); - vertex_map[i].j = vm.j; - brep.NewVertex(P, 0.0); - } - - // Remove duplicates from edge_map[] - - edge_map.QuickSort(ON_3udex::DictionaryCompare); - count0 = edge_map.UnsignedCount(); - unsigned int edge_map_count = 0; - ON_3udex prev_em(0, 0, 0); - for (unsigned int i = 0; i < count0; i++) - { - em = edge_map[i]; - if (prev_em.i == em.i && prev_em.j == em.j) - continue; - prev_em = em; - edge_map[edge_map_count++] = em; - } - // NOTE: final brep edge count <= edge_map_count. - edge_map.SetCount(edge_map_count); - - // create the edges - brep.m_C3.Reserve(edge_map_count); - brep.m_E.Reserve(edge_map_count); - unsigned int brep_edge_count = 0; - for (unsigned int i = 0; i < edge_map_count; i++) - { - em = edge_map[i]; - const unsigned int fi = em.k; - em.k = bad_edge_mark; - edge_map[i].k = bad_edge_mark; // will be update below if we find all the parts. - - if (0 == em.j && i + 2 < edge_map_count && em.i == edge_map[i + 1].i && em.i == edge_map[i + 2].i) - { - // This edge is shared between a subd quad face and N-gon with N != 4. - // The N-gon had to be subdivided one time to create quads and the - // edge will be split on the quad face side. - edge_map[i].k = split_edge_mark; // marker on the quad face edge for this situation - continue; - } - - ON_Curve* edge_curve = nullptr; - const ON_SubDFaceRegionAndNurbs& r = patches[fi]; - const ON_NurbsSurface* nurbs_surface = r.m_nurbs_surface; - unsigned vertex_id[2] = {}; - int brep_vi[2] = { -1,-1 }; - if (nullptr != nurbs_surface) - { - for (int j = 0; j < 4; j++) - { - if (em.i == r.m_face_region.m_edge_region[j].m_level0_component_id) - { - const bool bRevEdge = (0 != r.m_face_region.m_edge_region[j].m_level0_component.ComponentMark()); - vertex_id[bRevEdge?1:0] = r.m_face_region.m_vertex_id[j]; - vertex_id[bRevEdge?0:1] = r.m_face_region.m_vertex_id[(j+1)%4]; - - for (int k = 0; k < 2; k++) - { - vm.i = vertex_id[k]; - vm.j = 0; - int mapdex = vertex_map.BinarySearch(&vm, ON_2udex::CompareFirstIndex); - if (mapdex >= 0) - { - vm = vertex_map[mapdex]; - brep_vi[k] = (int)vm.j; - } - } - const int iso_dir = j % 2; - const double c = nurbs_surface->Domain(1 - iso_dir)[(1==j || 2==j) ? 1 : 0]; - edge_curve = nurbs_surface->IsoCurve(iso_dir, c); - bool bReverseCurve = (j >= 2); - if (bRevEdge) - bReverseCurve = !bReverseCurve; - if (bReverseCurve && nullptr != edge_curve) - edge_curve->Reverse(); - break; - } - } - } - - if (nullptr != edge_curve) - { - for (int k = 0; k < 2; k++) - { - if (brep_vi[k] >= 0) - continue; - ON_SUBD_ERROR("brep edge vertex creation error."); - ON_3dPoint P(ON_3dPoint::NanPoint); - if (nullptr != edge_curve) - P = (0 == k) ? edge_curve->PointAtStart() : edge_curve->PointAtEnd(); - brep_vi[k] = brep.m_V.Count(); - brep.NewVertex(P, 0.0); - } - int c3i = -1; - if (nullptr != edge_curve) - { - c3i = brep.m_C3.Count(); - brep.m_C3.Append(edge_curve); - } - em.k = brep.m_E.UnsignedCount(); - edge_map[i].k = em.k; - brep.NewEdge(brep.m_V[brep_vi[0]], brep.m_V[brep_vi[1]], c3i, nullptr, 0.0); - brep_edge_count++; - } - else - { - ON_SUBD_ERROR("brep edge creation error."); - } - } - - // Now build faces - ON_NurbsCurve trim_curve; - ON_2dPoint trim_curve_cvs[2]; - double trim_curve_knots[2] = { 0.0,1.0 }; - trim_curve.m_dim = 2; - trim_curve.m_is_rat = 0; - trim_curve.m_order = 2; - trim_curve.m_cv_count = 2; - trim_curve.m_cv_stride = 2; - trim_curve.m_cv = &trim_curve_cvs[0].x; - trim_curve.m_knot = trim_curve_knots; - - for (unsigned int fi = 0; fi < face_count; fi++) - { - const ON_SubDFaceRegionAndNurbs& r = patches[fi]; - if (nullptr == r.m_nurbs_surface) - continue; - - const ON_Interval srf_domain[2] = { r.m_nurbs_surface->Domain(0),r.m_nurbs_surface->Domain(1) }; - const ON_2dPoint srf_uv[4] = - { - ON_2dPoint(srf_domain[0][0], srf_domain[1][0]), - ON_2dPoint(srf_domain[0][1], srf_domain[1][0]), - ON_2dPoint(srf_domain[0][1], srf_domain[1][1]), - ON_2dPoint(srf_domain[0][0], srf_domain[1][1]) - }; - - ON_3udex brep_em[8] = {}; - bool trimRev3d[8] = {}; - ON_2udex brep_vm[8] = {}; - - // Fallback if we fail to get complete information - bool bFail = false; - int fail_vid[4] = { -1,-1,-1,-1 }; - int fail_eid[4] = { -1,-1,-1,-1 }; - bool fail_bRev3d[4] = { false,false,false,false }; - - for (int i = 0; i < 4; i++) - { - brep_vm[2 * i] = ON_2udex::Zero; - brep_vm[2 * i + 1] = ON_2udex::Zero; - brep_em[2 * i] = ON_3udex::Zero; - brep_em[2 * i + 1] = ON_3udex::Zero; - - vm.i = r.m_face_region.m_vertex_id[i]; - vm.j = 0; - int k = vertex_map.BinarySearch(&vm, ON_2udex::CompareFirstIndex); - if (k < 0) - { - bFail = true; - } - else - { - vm = vertex_map[k]; - if (vm.j < bad_vertex_mark) - { - brep_vm[2 * i] = vm; - fail_vid[i] = vm.j; - } - else - bFail = true; - } - - const bool bRev3d = (0 != r.m_face_region.m_edge_region[i].m_level0_component.ComponentMark()) ? true : false; - trimRev3d[2 * i] = bRev3d; - em.i = r.m_face_region.m_edge_region[i].m_level0_component_id; - em.j = r.m_face_region.m_edge_region[i].m_region_index.ToCompressedRegionIndex(); - em.k = 0; - k = edge_map.BinarySearch(&em, ON_3udex::CompareFirstAndSecondIndex); - if (k < 0) - { - bFail = true; - } - else - { - em = edge_map[k]; - if (split_edge_mark == em.k) - { - if (0 != em.j || (unsigned)(k + 2) >= edge_map_count || em.i != edge_map[k+1].i || em.i != edge_map[k+2].i ) - bFail = true; - else - { - trimRev3d[2 * i+1] = bRev3d; - if (bRev3d) - { - brep_em[2 * i] = edge_map[k + 2]; - brep_em[2 * i + 1] = edge_map[k + 1]; - } - else - { - brep_em[2 * i] = edge_map[k + 1]; - brep_em[2 * i + 1] = edge_map[k + 2]; - } - } - } - else if ( em.k < bad_edge_mark ) - { - brep_em[2 * i] = em; - fail_eid[i] = em.k; - fail_bRev3d[i] = bRev3d; - } - else - { - bFail = true; - } - } - } - - if (bFail) - { - // Use simple face maker and end up with interior naked edges - brep.NewFace( - r.m_nurbs_surface, - fail_vid, - fail_eid, - fail_bRev3d - ); - } - else - { - const int si = brep.m_S.Count(); - brep.m_S.Append(r.m_nurbs_surface); - ON_BrepFace& brep_face = brep.NewFace(si); - ON_BrepLoop& brep_loop = brep.NewLoop(ON_BrepLoop::TYPE::outer, brep_face); - for (int i = 0; i < 4; i++) - { - trim_curve_cvs[0] = srf_uv[i]; - trim_curve_cvs[1] = srf_uv[(i+1)%4]; - - if ( - 0 == brep_em[2 * i].i - || brep_em[2 * i].k >= brep_edge_count - ) - { - ON_SUBD_ERROR("Bog in SubD to joined brep code."); - continue; - } - - ON_NurbsCurve* curve2d[2] = {}; - if ( - 0 != brep_em[2 * i].j - && brep_em[2 * i].i == brep_em[2 * i + 1].i - && 0 != brep_em[2 * i + 1].j - && brep_em[2 * i + 1].k < brep_edge_count - ) - { - // This face/edge corresponds to a subd quad face and edge that - // is shared by subd N-gon with N != 4. - // This side of the brep face needs 2 brep edges so - // and will join to 2 brep faces coming from level 1 subdivision - // quads in the original subd. - const ON_2dPoint Q( - (trim_curve_cvs[0].x == trim_curve_cvs[1].x) ? trim_curve_cvs[0].x : 0.5*(trim_curve_cvs[0].x + trim_curve_cvs[1].x), - (trim_curve_cvs[0].y == trim_curve_cvs[1].y) ? trim_curve_cvs[0].y : 0.5*(trim_curve_cvs[0].y + trim_curve_cvs[1].y) - ); - trim_curve_cvs[1] = Q; - curve2d[0] = trim_curve.Duplicate(); - trim_curve_cvs[0] = Q; - trim_curve_cvs[1] = srf_uv[(i+1)%4]; - curve2d[1] = trim_curve.Duplicate(); - } - else - { - curve2d[0] = trim_curve.Duplicate(); - curve2d[1] = nullptr; - } - for ( int k = 0; k < 2; k++) - { - ON_NurbsCurve* c2 = curve2d[k]; - if (nullptr == c2) - continue; - em = brep_em[2 * i + k]; - const bool bRev3d = trimRev3d[2 * i + k]; - ON_BrepEdge& brep_edge = brep.m_E[em.k]; - ON_Interval trim_domain = brep_edge.Domain(); - if (bRev3d) - trim_domain.Reverse(); - c2->SetDomain(trim_domain[0],trim_domain[1]); - int c2i = brep.m_C2.Count(); - brep.m_C2.Append(c2); - ON_BrepTrim& brep_trim = brep.NewTrim(brep_edge, bRev3d, brep_loop, c2i); - brep_trim.m_tolerance[0] = 0.0; - brep_trim.m_tolerance[1] = 0.0; - } - } - - //Internal_DebugLocations(brep_face); - } - } - - trim_curve.m_cv = nullptr; - trim_curve.m_knot = nullptr; - - - return true; -} diff --git a/opennurbs_subd_ref.cpp b/opennurbs_subd_ref.cpp index 40a5aab7..8c0afc93 100644 --- a/opennurbs_subd_ref.cpp +++ b/opennurbs_subd_ref.cpp @@ -89,6 +89,7 @@ class ON_SubD& ON_SubDRef::NewSubD() { ON_SubD* subd = new ON_SubD(); ON_SubD* managed_subd = SetSubDForExperts(subd); + managed_subd->SetSubDType(ON_SubD::SubDType::QuadCatmullClark); return *managed_subd; } @@ -202,6 +203,35 @@ ON_SubDComponentRef& ON_SubDComponentRef::operator=(ON_SubDComponentRef&& src) #endif + +int ON_SubDComponentRef::Compare(const ON_SubDComponentRef* lhs, const ON_SubDComponentRef* rhs) +{ + if (lhs == rhs) + return 0; + if (nullptr == lhs) + return 1; + if (nullptr == rhs) + return -1; + const ON__UINT64 lhs_sn = lhs->m_subd_ref.SubD().RuntimeSerialNumber(); + const ON__UINT64 rhs_sn = rhs->m_subd_ref.SubD().RuntimeSerialNumber(); + if (lhs_sn < rhs_sn) + return -1; + if (lhs_sn > rhs_sn) + return 1; + return ON_COMPONENT_INDEX::Compare(&lhs->m_component_index, &rhs->m_component_index); +} + +int ON_SubDComponentRef::Compare2(const ON_SubDComponentRef* const* lhs, const ON_SubDComponentRef* const* rhs) +{ + if (lhs == rhs) + return 0; + if (nullptr == lhs) + return 1; + if (nullptr == rhs) + return -1; + return ON_SubDComponentRef::Compare(*lhs, *rhs); +} + ON_SubDComponentRef ON_SubDComponentRef::Create( const ON_SubDRef& subd_ref, ON_COMPONENT_INDEX component_index, @@ -399,12 +429,11 @@ bool ON_SubDComponentRef::GetBBox( break; switch (m_component_location) { + case ON_SubDComponentLocation::LimitSurface: + // public opennubs does not provide limit mesh tools. case ON_SubDComponentLocation::ControlNet: bbox = vertex->ControlNetBoundingBox(); break; - case ON_SubDComponentLocation::LimitSurface: - bbox = vertex->LimitSurfaceBoundingBox(SubD()); - break; } } break; @@ -415,12 +444,11 @@ bool ON_SubDComponentRef::GetBBox( break; switch (m_component_location) { + case ON_SubDComponentLocation::LimitSurface: + // public opennubs does not provide limit mesh tools. case ON_SubDComponentLocation::ControlNet: bbox = edge->ControlNetBoundingBox(); break; - case ON_SubDComponentLocation::LimitSurface: - bbox = edge->LimitSurfaceBoundingBox(SubD()); - break; } } break; @@ -431,12 +459,11 @@ bool ON_SubDComponentRef::GetBBox( break; switch (m_component_location) { + case ON_SubDComponentLocation::LimitSurface: + // public opennubs does not provide limit mesh tools. case ON_SubDComponentLocation::ControlNet: bbox = face->ControlNetBoundingBox(); break; - case ON_SubDComponentLocation::LimitSurface: - bbox = face->LimitSurfaceBoundingBox(SubD()); - break; } } break; @@ -463,4 +490,367 @@ bool ON_SubDComponentRef::GetBBox( } +void ON_SubDComponentRefList::Internal_Destroy() +{ + for (unsigned int i = 0; i < m_list.UnsignedCount(); i++) + { + ON_SubDComponentRef* p = m_list[i]; + m_list[i] = nullptr; + if (nullptr != p) + delete p; + } + m_list.SetCount(0); + m_bIsClean = false; +} + +void ON_SubDComponentRefList::Internal_CopyFrom(const ON_SubDComponentRefList& src) +{ + const unsigned int count = src.m_list.UnsignedCount(); + m_list.Reserve(count); + m_list.SetCount(0); + for (unsigned int i = 0; i < count; i++) + { + const ON_SubDComponentRef* p = src.m_list[i]; + if (nullptr == p) + m_list.Append(nullptr); + else + m_list.Append(new ON_SubDComponentRef(*p)); + + m_subd_count = src.m_subd_count; + m_subd_vertex_smooth_count = src.m_subd_vertex_smooth_count; + m_subd_vertex_dart_count = src.m_subd_vertex_dart_count; + m_subd_vertex_crease_count = src.m_subd_vertex_crease_count; + m_subd_vertex_corner_count = src.m_subd_vertex_corner_count; + m_subd_edge_smooth_count = src.m_subd_edge_smooth_count; + m_subd_edge_crease_count = src.m_subd_edge_crease_count; + m_subd_face_count = src.m_subd_face_count; + + m_bIsClean = src.m_bIsClean; + } +} + +ON_SubDComponentRefList::~ON_SubDComponentRefList() +{ + Internal_Destroy(); +} + +ON_SubDComponentRefList::ON_SubDComponentRefList(const ON_SubDComponentRefList& src) +{ + Internal_CopyFrom(src); +} + +ON_SubDComponentRefList& ON_SubDComponentRefList::operator=(const ON_SubDComponentRefList& src) +{ + if (this != &src) + { + Internal_Destroy(); + Internal_CopyFrom(src); + } + return *this; +} + +bool ON_SubDComponentRefList::Internal_UpdateCount(const ON_SubDComponentRef& r, int i) +{ + if (r.SubD().IsEmpty()) + return false; + + bool rc = false; + ON_SubDComponentPtr cptr = r.ComponentPtr(); + switch (cptr.ComponentType()) + { + case ON_SubDComponentPtr::Type::Vertex: + { + const ON_SubDVertex* v = cptr.Vertex(); + if (nullptr == v) + break; + switch (v->m_vertex_tag) + { + case ON_SubD::VertexTag::Smooth: + m_subd_vertex_smooth_count += i; + rc = true; + break; + case ON_SubD::VertexTag::Crease: + m_subd_vertex_crease_count += i; + rc = true; + break; + case ON_SubD::VertexTag::Corner: + m_subd_vertex_corner_count += i; + rc = true; + break; + case ON_SubD::VertexTag::Dart: + m_subd_vertex_dart_count += i; + rc = true; + break; + } + } + break; + + case ON_SubDComponentPtr::Type::Edge: + { + const ON_SubDEdge* e = cptr.Edge(); + if (nullptr == e) + break; + switch (e->m_edge_tag) + { + case ON_SubD::EdgeTag::Smooth: + case ON_SubD::EdgeTag::X: + m_subd_edge_smooth_count += i; + rc = true; + break; + case ON_SubD::EdgeTag::Crease: + m_subd_edge_crease_count += i; + rc = true; + break; + } + } + break; + + case ON_SubDComponentPtr::Type::Face: + if (nullptr != cptr.Face()) + { + m_subd_face_count += i; + rc = true; + } + break; + } + + return rc; +} + +const ON_SubDComponentRef& ON_SubDComponentRefList::Append(const ON_SubDRef & subd_ref, ON_COMPONENT_INDEX ci, ON_SubDComponentLocation component_location) +{ + for (;;) + { + if (subd_ref.SubD().IsEmpty()) + break; + if (false == ci.IsSubDComponentIndex()) + break; + const ON_SubDComponentRef r(ON_SubDComponentRef::Create(subd_ref, ci, component_location)); + return Append(&r); + } + return ON_SubDComponentRef::Empty; +} + +const ON_SubDComponentRef& ON_SubDComponentRefList::Append(const ON_SubDRef & subd_ref, ON_SubDComponentPtr component_ptr, ON_SubDComponentLocation component_location) +{ + for (;;) + { + if (subd_ref.SubD().IsEmpty()) + break; + if (false == component_ptr.IsNull()) + break; + const ON_SubDComponentRef r(ON_SubDComponentRef::Create(subd_ref, component_ptr, component_location)); + return Append(&r); + } + return ON_SubDComponentRef::Empty; +} + +const ON_SubDComponentRef& ON_SubDComponentRefList::Append(const ON_SubDComponentRef* src_ref) +{ + for (;;) + { + if (nullptr == src_ref) + break; + if (src_ref->SubD().IsEmpty()) + break; + if (false == Internal_UpdateCount(*src_ref,1)) + break; + m_list.Append(new ON_SubDComponentRef(*src_ref)); + m_bIsClean = false; + return **(m_list.Last()); + } + return ON_SubDComponentRef::Empty; +} + +const ON_SubDComponentRef& ON_SubDComponentRefList::AppendForExperts(ON_SubDComponentRef*& ref) +{ + for (;;) + { + if (nullptr == ref) + break; + if (ref->SubD().IsEmpty()) + break; + if (false == Internal_UpdateCount(*ref,1)) + break; + m_list.Append(ref); + m_bIsClean = false; + return *ref; + } + return ON_SubDComponentRef::Empty; +} + +const ON_SubDComponentRef& ON_SubDComponentRefList::AppendForExperts( + const ON_SubD& subd, + ON_COMPONENT_INDEX ci, + ON_SubDComponentLocation component_location +) +{ + for (;;) + { + if (subd.IsEmpty()) + break; + return Append(ON_SubDRef::CreateReferenceForExperts(subd),ci,component_location); + } + return ON_SubDComponentRef::Empty; +} + +const ON_SubDComponentRef& ON_SubDComponentRefList::AppendForExperts( + const ON_SubD& subd, + ON_SubDComponentPtr component_ptr, + ON_SubDComponentLocation component_location +) +{ + for (;;) + { + if (subd.IsEmpty()) + break; + return Append(ON_SubDRef::CreateReferenceForExperts(subd),component_ptr,component_location); + } + return ON_SubDComponentRef::Empty; +} + + +int ON_SubDComponentRefList::Clean() +{ + if (m_bIsClean) + return m_list.UnsignedCount(); + + const unsigned dirty_count = m_list.UnsignedCount(); + + ((ON_SimpleArray< const ON_SubDComponentRef* > *)(&m_list))->QuickSort(ON_SubDComponentRef::Compare2); + + m_subd_count = 0; + m_subd_vertex_smooth_count = 0; + m_subd_vertex_dart_count = 0; + m_subd_vertex_crease_count = 0; + m_subd_vertex_corner_count = 0; + m_subd_edge_smooth_count = 0; + m_subd_edge_crease_count = 0; + m_subd_face_count = 0; + + unsigned int clean_count = 0; + const ON_SubDComponentRef* prev_scr = nullptr; + for (unsigned int i = 0; i < dirty_count; i++) + { + ON_SubDComponentRef* scr = m_list[i]; + if (nullptr == scr) + continue; + if ( + 0 == ON_SubDComponentRef::Compare(prev_scr, scr) + || false == Internal_UpdateCount(*scr,1) + ) + { + delete scr; + continue; + } + if (nullptr == prev_scr || prev_scr->SubD().RuntimeSerialNumber() != scr->SubD().RuntimeSerialNumber()) + m_subd_count++; + m_list[clean_count++] = scr; + prev_scr = scr; + } + for (unsigned i = clean_count; i < dirty_count; i++) + m_list[i] = nullptr; + m_list.SetCount(clean_count); + m_bIsClean = true; + return clean_count; +} + +int ON_SubDComponentRefList::Count() const +{ + return m_list.Count(); +} + +void ON_SubDComponentRefList::Remove(int i) +{ + ON_SubDComponentRef* p = TransferForExperts(i); + if (nullptr != p) + delete p; +} + +ON_SubDComponentRef * ON_SubDComponentRefList::TransferForExperts(int i) +{ + ON_SubDComponentRef * p = (i >= 0 && i < m_list.Count()) ? m_list[i] : nullptr; + if (p != nullptr) + { + Internal_UpdateCount(*p, -1); + m_bIsClean = false; + } + return p; +} + +const ON_SubDComponentRef& ON_SubDComponentRefList::operator[](int i) const +{ + const ON_SubDComponentRef * p = (i >= 0 && i < m_list.Count()) ? m_list[i] : nullptr; + return (nullptr == p) ? ON_SubDComponentRef::Empty : *p; +} + +int ON_SubDComponentRefList::SubDCount() const +{ + return m_bIsClean ? m_subd_count : 0; +} + +int ON_SubDComponentRefList::VertexCount() const +{ + return + m_subd_vertex_smooth_count + + m_subd_vertex_dart_count + + m_subd_vertex_crease_count + + m_subd_vertex_corner_count; +} + +int ON_SubDComponentRefList::VertexCount(ON_SubD::VertexTag vertex_tag) const +{ + int c = 0; + switch (vertex_tag) + { + case ON_SubD::VertexTag::Smooth: + c = m_subd_vertex_smooth_count; + break; + case ON_SubD::VertexTag::Crease: + c = m_subd_vertex_crease_count; + break; + case ON_SubD::VertexTag::Corner: + c = m_subd_vertex_corner_count; + break; + case ON_SubD::VertexTag::Dart: + c = m_subd_vertex_dart_count; + break; + } + + return c; +} + +int ON_SubDComponentRefList::EdgeCount() const +{ + return m_subd_edge_crease_count + m_subd_edge_smooth_count; +} + +int ON_SubDComponentRefList::EdgeCount(ON_SubD::EdgeTag edge_tag) const +{ + int c = 0; + switch (edge_tag) + { + case ON_SubD::EdgeTag::Unset: + break; + case ON_SubD::EdgeTag::Smooth: + c = m_subd_edge_smooth_count; + break; + case ON_SubD::EdgeTag::Crease: + c = m_subd_edge_crease_count; + break; + } + return c; +} + +int ON_SubDComponentRefList::FaceCount() const +{ + return m_subd_face_count; +} + +int ON_SubDComponentRefList::ComponentCount() const +{ + return m_list.Count(); +} + + #endif diff --git a/opennurbs_subd_ring.cpp b/opennurbs_subd_ring.cpp index d89c0370..774ff415 100644 --- a/opennurbs_subd_ring.cpp +++ b/opennurbs_subd_ring.cpp @@ -480,7 +480,7 @@ bool ON_SubD::ComponentRingIsValid( } else { - if (2 != edge->m_face_count || false == edge->IsSmooth(true)) + if (2 != edge->m_face_count || false == edge->IsSmooth()) return ON_SUBD_RETURN_ERROR(false); } @@ -927,7 +927,7 @@ unsigned int ON_SubD::GetSectorComponentRing( const unsigned int N = vertex->m_edge_count; // for () used to prevent infinite recursion when vertex is not valid for (unsigned int i = 0; i < N; i++) { - const ON_SubDFace* face = localsit.NextFace(true); + const ON_SubDFace* face = localsit.NextFace(ON_SubDSectorIterator::StopAt::AnyCrease); edgeptr = localsit.CurrentEdgePtr(0); const ON_SubDEdge* edge = edgeptr.Edge(); @@ -969,7 +969,7 @@ unsigned int ON_SubD::GetSectorComponentRing( return ON_SUBD_RETURN_ERROR(0); } - if ( false == edge->IsSmooth(true) || 2 != edge->m_face_count ) + if ( false == edge->IsSmooth() || 2 != edge->m_face_count ) return ON_SUBD_RETURN_ERROR(false); if ( component_ring_count >= component_ring_capacity) diff --git a/opennurbs_subd_sector.cpp b/opennurbs_subd_sector.cpp index 3fa25556..1e7cbf0a 100644 --- a/opennurbs_subd_sector.cpp +++ b/opennurbs_subd_sector.cpp @@ -970,7 +970,7 @@ ON_SubDSectorType ON_SubDSectorType::Create( sector_face_count++; if ( sector_face_count > vertex_face_count ) return ON_SUBD_RETURN_ERROR(ON_SubDSectorType::Empty); - if (face0 == local_sit.NextFace(true)) + if (face0 == local_sit.NextFace(ON_SubDSectorIterator::StopAt::AnyCrease)) { double corner_sector_angle_radians = (ON_SubD::VertexTag::Corner == vertex_tag) diff --git a/opennurbs_system.h b/opennurbs_system.h index 140b5bd3..e2ff70a5 100644 --- a/opennurbs_system.h +++ b/opennurbs_system.h @@ -109,6 +109,7 @@ #endif #endif + #include "opennurbs_system_compiler.h" #include "opennurbs_system_runtime.h" @@ -594,6 +595,22 @@ typedef ON__UINT32 wchar_t; #include // for std:atomic #pragma ON_PRAGMA_WARNING_AFTER_DIRTY_INCLUDE +#pragma ON_PRAGMA_WARNING_BEFORE_DIRTY_INCLUDE +#include // for std:chrono::high_resolution_clock +#pragma ON_PRAGMA_WARNING_AFTER_DIRTY_INCLUDE + +#pragma ON_PRAGMA_WARNING_BEFORE_DIRTY_INCLUDE +#if !defined(OPENNURBS_NO_STD_THREAD) +#include // for std::this_thread::sleep_for +#endif +#pragma ON_PRAGMA_WARNING_AFTER_DIRTY_INCLUDE + +#pragma ON_PRAGMA_WARNING_BEFORE_DIRTY_INCLUDE +#if !defined(OPENNURBS_NO_STD_MUTEX) +#include // for std:mutex +#endif +#pragma ON_PRAGMA_WARNING_AFTER_DIRTY_INCLUDE + #define ON_NO_SHARED_PTR_DTOR(T) [=](T*){} #define ON_MANAGED_SHARED_PTR(T, p) std::shared_ptr(p) @@ -601,6 +618,10 @@ typedef ON__UINT32 wchar_t; #if defined(ON_RUNTIME_APPLE) +// To handle single stroke fonts on MacOS, we need freetype tools. +// See ON_AppleFontGetGlyphOutline() for details. +// freetype linking is broken in current project // #define OPENNURBS_FREETYPE_SUPPORT + #if defined(ON_COMPILER_CLANG) #pragma ON_PRAGMA_WARNING_BEFORE_DIRTY_INCLUDE #include diff --git a/opennurbs_testclass.cpp b/opennurbs_testclass.cpp new file mode 100644 index 00000000..24785990 --- /dev/null +++ b/opennurbs_testclass.cpp @@ -0,0 +1,131 @@ +#include "opennurbs.h" +#include "opennurbs_testclass.h" + +#if !defined(OPENNURBS_NO_STD_MUTEX) + +ON__UINT64 ON_TestClass::CurrentCount() +{ + ON__UINT64 constructed_count = 0; + ON__UINT64 current_count = 0; + ON_TestClass::GetCurrentAndConstructedCount(constructed_count, current_count); + return current_count; +} + +ON__UINT64 ON_TestClass::ConstructedCount() +{ + ON__UINT64 constructed_count = 0; + ON__UINT64 current_count = 0; + ON_TestClass::GetCurrentAndConstructedCount(constructed_count, current_count); + return constructed_count; +} + +ON__UINT64 ON_TestClass::DestructedCount() +{ + ON__UINT64 constructed_count = 0; + ON__UINT64 current_count = 0; + ON_TestClass::GetCurrentAndConstructedCount(constructed_count, current_count); + return constructed_count - current_count; +} + +ON__UINT64 ON_TestClass::Internal_CounterHelper( + int task, // 0 = construct, 1 = destruct, 2 = query + ON__UINT64* constructed_count, + ON__UINT64* current_count +) +{ + std::lock_guard lock(ON_TestClass::internal_counter_mutex); + ON__UINT64 rc = 0; + if (0 == task) + { + ++ON_TestClass::internal_PopulationCounter; + rc = ++ON_TestClass::internal_CtorSerialNumberGenerator; + } + else if (1 == task) + { + --ON_TestClass::internal_PopulationCounter; + } + else + { + *constructed_count = ON_TestClass::internal_CtorSerialNumberGenerator; + *current_count = ON_TestClass::internal_PopulationCounter; + } + return rc; +} + +void ON_TestClass::GetCurrentAndConstructedCount( + ON__UINT64& constructed_count, + ON__UINT64& current_count +) +{ + ON_TestClass::Internal_CounterHelper(2, &constructed_count, ¤t_count); +} + +ON_TestClass::ON_TestClass() + : SerialNumber(ON_TestClass::Internal_CounterHelper(0,nullptr,nullptr)) + , CopiedFrom(0) + , ConstructedAt((ON__UINT64)time(nullptr)) +{} + +ON_TestClass::~ON_TestClass() +{ + ON_TestClass::Internal_CounterHelper(1, nullptr, nullptr); +} + +ON_TestClass::ON_TestClass(const ON_TestClass& src) + : SerialNumber(ON_TestClass::Internal_CounterHelper(0,nullptr,nullptr)) + , CopiedFrom(src.SerialNumber) + , ConstructedAt((ON__UINT64)time(nullptr)) + , m_string(src.Value()) +{} + +ON_TestClass& ON_TestClass::operator=(const ON_TestClass& src) +{ + if (this != &src) + { + SetValue(src.Value()); + } + return *this; +} + +const ON_wString ON_TestClass::Value() const +{ + std::lock_guard lock(m_local_mutex); + return m_string; +} + +void ON_TestClass::SetValue( + const ON_wString s +) +{ + std::lock_guard lock(m_local_mutex); + m_string = s; +} + +void ON_TestClass::Dump(class ON_TextLog& text_log) const +{ + const ON_wString s(ToString()); + text_log.PrintString(s); + text_log.PrintNewLine(); +} + +const ON_wString ON_TestClass::ToString() const +{ + const ON_wString v = Value(); + + const ON_wString ymdhms = ON_wString::FromSecondsSinceJanuaryFirst1970( + ConstructedAt, + ON_DateFormat::YearMonthDay, + ON_TimeFormat::HourMinuteSecond24, + 0,0,0 + ); + + + return ON_wString::FormatToString( + L"ON_TestClass[%llu] @ %s \"%ls\"", + SerialNumber, + static_cast(ymdhms), + static_cast(v) + ); +} + +#endif diff --git a/opennurbs_testclass.h b/opennurbs_testclass.h new file mode 100644 index 00000000..94137296 --- /dev/null +++ b/opennurbs_testclass.h @@ -0,0 +1,168 @@ +/* +// Copyright (c) 1993-2016 Robert McNeel & Associates. All rights reserved. +// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert +// McNeel & Associates. +// +// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY. +// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF +// MERCHANTABILITY ARE HEREBY DISCLAIMED. +// +// For complete openNURBS copyright information see . +// +//////////////////////////////////////////////////////////////// +*/ + +#if !defined(OPENNURBS_TEST_CLASS_INC_) +#define OPENNURBS_TEST_CLASS_INC_ + +#if defined(ON_CPLUSPLUS) + +#if !defined(OPENNURBS_NO_STD_MUTEX) + +class ON_CLASS ON_TestClass +{ +public: + ON_TestClass(); + ~ON_TestClass(); + ON_TestClass(const ON_TestClass&); + ON_TestClass& operator=(const ON_TestClass&); + +public: + + /* + The first instance of a ON_TestClass has serial number 1. + */ + const ON__UINT64 SerialNumber; + + /* + If this class was created by the copy constructor, then + CopiedFrom is the serial number of the source class. + Otherwise, CopiedFrom is zero. + */ + const ON__UINT64 CopiedFrom; + + /* + Time this instance was constructed. + Seconds since Jan 1, 1970, UCT from C-runtime time(nullptr). + */ + const ON__UINT64 ConstructedAt; + + /* + Returns: + Number of ON_TestClass instances that have been constructed. + Remarks: + Thread safe. + If you need to know two or more of ConstructedCount(), DestructedCount(), + and CurrentCount() the same time, call GetCurrentAndConstructedCount(). + */ + static ON__UINT64 ConstructedCount(); + + /* + Returns: + Number of ON_TestClass instances that have been destroyed. + Remarks: + Thread safe. + If you need to know two or more of ConstructedCount(), DestructedCount(), + and CurrentCount() the same time, call GetCurrentAndConstructedCount(). + */ + static ON__UINT64 DestructedCount(); + + /* + Returns: + Number of ON_TestClass instances that currently exist. + Remarks: + Thread safe. + If you need to know two or more of ConstructedCount(), DestructedCount(), + and CurrentCount() the same time, call GetCurrentAndConstructedCount(). + */ + static ON__UINT64 CurrentCount(); + + /* + Parameters: + constructed_count - [out] + Number of ON_TestClass that have been constructed. + current_count - [out] + Number of ON_TestClass that currently exist. + Remarks: + Thread safe. + */ + static void GetCurrentAndConstructedCount( + ON__UINT64& constructed_count, + ON__UINT64& current_count + ); + + /* + Returns: + String value. + Remarks: + Thread safe. + */ + const ON_wString Value() const; + + /* + Returns: + String value. + Remarks: + Thread safe. + */ + void SetValue( + const ON_wString s + ); + + /* + Returns: + String value. + Remarks: + Thread safe. + */ + void Dump(class ON_TextLog& text_log) const; + + /* + Returns: + A string with the format + ON_TestClass[] \"\" + where is this->SerialNumber and + is the string returned by this->Value(). + + Remarks: + Thread safe. + */ + const ON_wString ToString() const; + +private: +#pragma ON_PRAGMA_WARNING_PUSH +#pragma ON_PRAGMA_WARNING_DISABLE_MSC( 4251 ) + // C4251: ... needs to have dll-interface to be used by clients of class ... + // m_mutex is private and all code that manages m_mutex is explicitly implemented in the DLL. + // + // m_local_mutex is used for thread safe access to instance members + mutable std::mutex m_local_mutex; +#pragma ON_PRAGMA_WARNING_POP + + + // ON_wString is multi-thread safe +private: + ON_wString m_string; + +private: +#pragma ON_PRAGMA_WARNING_PUSH +#pragma ON_PRAGMA_WARNING_DISABLE_MSC( 4251 ) + // C4251: ... needs to have dll-interface to be used by clients of class ... + // These are private and all code that manages m_mutex is explicitly implemented in the DLL. + // internal_counter_mutex protects access to internal_* counters. + static std::mutex internal_counter_mutex; + static ON__UINT64 internal_CtorSerialNumberGenerator; + static ON__UINT64 internal_PopulationCounter; + static ON__UINT64 Internal_CounterHelper( + int task, // 0 = construct, 1 = destruct, 2 = query + ON__UINT64* constructed_count, + ON__UINT64* current_count + ); +#pragma ON_PRAGMA_WARNING_POP +}; + +#endif + +#endif + +#endif diff --git a/opennurbs_textglyph.cpp b/opennurbs_textglyph.cpp index 41d141c0..361d3bac 100644 --- a/opennurbs_textglyph.cpp +++ b/opennurbs_textglyph.cpp @@ -629,7 +629,7 @@ void ON_FontGlyph::Dump( text_log.PrintString(s); text_log.PrintNewLine(); -#if defined(OPENNURBS_FREETYPE_SUPPORT) +#if !defined(ON_RUNTIME_APPLE) && defined(OPENNURBS_FREETYPE_SUPPORT) // Look in opennurbs_system_rumtime.h for the correct place to define OPENNURBS_FREETYPE_SUPPORT. // Do NOT define OPENNURBS_FREETYPE_SUPPORT here or in your project setting ("makefile"). if ( bPrintMaps && nullptr != g ) @@ -851,7 +851,7 @@ ON_FontGlyph* ON_FontGlyph::Internal_AllocateManagedGlyph( // managed glyphs are app resources, allocated once per instance and never freed. ON_MemoryAllocationTracking disable_tracking(false); - ON_FontGlyph* managed_glyph = (ON_FontGlyph*)ON_Internal_FontGlyphPool::theGlyphItemPool.AllocateElement(); + ON_FontGlyph* managed_glyph = (ON_FontGlyph*)ON_Internal_FontGlyphPool::theGlyphItemPool.ThreadSafeAllocateElement(); if (nullptr != managed_glyph) { managed_glyph = new (managed_glyph)ON_FontGlyph(); @@ -895,6 +895,12 @@ const ON_FontGlyph* ON_GlyphMap::FindGlyph(const ON__UINT32 unicode_codepoint) c { if ( false == ON_IsValidUnicodeCodePoint(unicode_codepoint) ) return nullptr; // invalid codepoint + + // The glyph map is a global resource. + // When multiple threads are simultaneously rendering text (very uncommon), + // then they must take turns. + ON_SleepLockGuard lock_guard(m_sleep_lock); + const unsigned count = m_glyphs.UnsignedCount(); if (unicode_codepoint < 256) { @@ -946,6 +952,11 @@ const ON_FontGlyph* ON_GlyphMap::InsertGlyph(const ON_FontGlyph& glyph ) const int base_count = 256; + // The glyph map is a global resource. + // When multiple threads are simultaneously rendering text (very uncommon), + // then they must take turns. + ON_SleepLockGuard lock_guard(m_sleep_lock); + if (0 == m_glyphs.Count()) { // codepoints 0 to base_count-1 are in m_glyphs[] by code point value. diff --git a/opennurbs_textiterator.cpp b/opennurbs_textiterator.cpp index 0a905971..06657d40 100644 --- a/opennurbs_textiterator.cpp +++ b/opennurbs_textiterator.cpp @@ -379,7 +379,7 @@ void ON_TextBuilder::SetReadingFontDefinition(bool b) void ON_TextBuilder::FinishFontDef() { - m_bReadingFontDefinition = false; + SetReadingFontDefinition(false); } void ON_TextBuilder::FlushText(size_t count, ON__UINT32* cp) @@ -475,7 +475,7 @@ void ON_TextBuilder::CharSet(const wchar_t* value) // \fcharsetN if (sp > value) { //if (m_font_table_level >= 0 && m_level >= m_font_table_level) - if(m_bReadingFontDefinition) + if(ReadingFontDefinition()) { // This is a charset specification in a font definition in the font table // the value is convertable to a codepage to use for interpreting the text chars @@ -492,7 +492,7 @@ void ON_TextBuilder::CodePage(const wchar_t* value) // \cpgN if (sp > value) { //if (m_font_table_level >= 0 && m_level >= m_font_table_level) - if(m_bReadingFontDefinition) + if(ReadingFontDefinition()) { // This is a codepage specification in a font definition in the font table m_current_props.SetCodePage(codepage); @@ -1098,24 +1098,43 @@ void ON_TextRunBuilder::FinishFontDef() size_t cpcount = ON_TextRun::CodepointCount(RunCodePoints(m_current_run)); if (0 != cpcount) { - ON_wString str; - ON_TextContext::ConvertCodepointsToString((int)cpcount, RunCodePoints(m_current_run), str); - if (!str.IsEmpty()) + ON_wString rtf_fontname_string; + ON_TextContext::ConvertCodepointsToString((int)cpcount, RunCodePoints(m_current_run), rtf_fontname_string); + if (!rtf_fontname_string.IsEmpty()) { - str.Remove(L';'); // facename delimiter from rtf + rtf_fontname_string.Remove(L';'); // facename delimiter from rtf - if (!IsValidFontName(str)) + if (!IsValidFontName(rtf_fontname_string)) { ON_ERROR("Invalid font name found in rtf string"); - str = L"Arial"; + rtf_fontname_string = L"Arial"; } + + const ON_Font* managed_font = ON_Font::GetManagedFont(rtf_fontname_string); + if (nullptr == managed_font) + managed_font = &ON_Font::Default; + rtf_fontname_string = managed_font->RichTextFontName(); + ON_FaceNameKey& fn_key = m_facename_map.AppendNew(); fn_key.m_rtf_font_index = m_font_index; - fn_key.m_rtf_font_name = str; + fn_key.m_rtf_font_name = rtf_fontname_string; fn_key.m_codepage = m_current_props.CodePage(); fn_key.m_charset = m_current_props.CharSet(); } } + + if (m_current_run.Type() == ON_TextRun::RunType::kFontdef && m_level == m_font_table_level) + { + if (m_font_stack.Count() > 0 && m_prop_stack.Count() > 0) + { + m_current_font = *m_font_stack.Last(); // pop + m_font_stack.Remove(); + m_current_props = *m_prop_stack.Last(); // pop + m_prop_stack.Remove(); + } + m_current_run.Init(m_current_font, m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), + m_current_props.IsBold(), m_current_props.IsItalic(), m_current_props.IsUnderlined(), m_current_props.IsStrikethrough()); + } SetReadingFontDefinition(false); } } @@ -1896,7 +1915,6 @@ const ON_wString ON_RtfStringBuilder::OutputString() // or to store the definition for the nth font in the font table void ON_RtfStringBuilder::FontTag(const wchar_t* value) { - if (SkippingFacename()) return; @@ -1906,6 +1924,14 @@ void ON_RtfStringBuilder::FontTag(const wchar_t* value) { if (ReadingFontTable()) { + if (m_current_run.Type() == ON_TextRun::RunType::kFonttbl && m_level == m_font_table_level) + { + m_string_out += m_current_run.TextString(); + m_current_run.EmptyText(); + m_current_run.SetTerminated(true); + PushRun(m_current_run); + m_have_rtf = true; + } m_current_run.SetType(ON_TextRun::RunType::kFontdef); if (!SettingFacename()) { @@ -2416,108 +2442,112 @@ bool ON_RtfParser::ProcessTag(const wchar_t* name, const wchar_t* value, bool op if( tagname.IsEmpty() ) return false; + bool rc = true; + const bool bOrdinalIgnoreCase = true; - if (0 == tagname.CompareOrdinal(tagRtf, bOrdinalIgnoreCase)) - { - m_in_real_rtf = true; - m_builder.BeginHeader(); - } - else if (0 == tagname.CompareOrdinal(tagFontTable, bOrdinalIgnoreCase)) - m_builder.BeginFontTable(); - else if(0 == tagname.CompareOrdinal(tagDefaultFont,bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) - m_builder.DefaultFont(value); - else if(0 == tagname.CompareOrdinal(tagFont,bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) - m_builder.FontTag(value); - else if(0 == tagname.CompareOrdinal(tagFontSize,bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) - m_builder.FontSize(value); - else if (0 == tagname.CompareOrdinal(tagCharSet, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) - m_builder.CharSet(value); - else if (0 == tagname.CompareOrdinal(tagCodePage, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) - m_builder.CodePage(value); +if (0 == tagname.CompareOrdinal(tagRtf, bOrdinalIgnoreCase)) +{ + m_in_real_rtf = true; + m_builder.BeginHeader(); +} +else if (0 == tagname.CompareOrdinal(tagFontTable, bOrdinalIgnoreCase)) +m_builder.BeginFontTable(); +else if (0 == tagname.CompareOrdinal(tagDefaultFont, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) +m_builder.DefaultFont(value); +else if (0 == tagname.CompareOrdinal(tagFont, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) +m_builder.FontTag(value); +else if (0 == tagname.CompareOrdinal(tagFontSize, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) +m_builder.FontSize(value); +else if (0 == tagname.CompareOrdinal(tagCharSet, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) +m_builder.CharSet(value); +else if (0 == tagname.CompareOrdinal(tagCodePage, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) +m_builder.CodePage(value); - else if(0 == tagname.CompareOrdinal(tagNewline,bOrdinalIgnoreCase)) - m_builder.Newline(); - else if(0 == tagname.CompareOrdinal(tagParagraph,bOrdinalIgnoreCase)) - m_builder.Paragraph(); - else if(0 == tagname.CompareOrdinal(tagParagraphDefaults,bOrdinalIgnoreCase)) - m_builder.ParagraphDefaults(); - else if(0 == tagname.CompareOrdinal(tagSection,bOrdinalIgnoreCase)) - m_builder.Section(); - else if(0 == tagname.CompareOrdinal(tagTabulator,bOrdinalIgnoreCase)) - m_builder.Tab(); +else if (0 == tagname.CompareOrdinal(tagNewline, bOrdinalIgnoreCase)) +m_builder.Newline(); +else if (0 == tagname.CompareOrdinal(tagParagraph, bOrdinalIgnoreCase)) +m_builder.Paragraph(); +else if (0 == tagname.CompareOrdinal(tagParagraphDefaults, bOrdinalIgnoreCase)) +m_builder.ParagraphDefaults(); +else if (0 == tagname.CompareOrdinal(tagSection, bOrdinalIgnoreCase)) +m_builder.Section(); +else if (0 == tagname.CompareOrdinal(tagTabulator, bOrdinalIgnoreCase)) +m_builder.Tab(); - else if(0 == tagname.CompareOrdinal(tagBold,bOrdinalIgnoreCase)) - m_builder.Bold(value); - else if(0 == tagname.CompareOrdinal(tagItalic,bOrdinalIgnoreCase)) - m_builder.Italic(value); - else if (0 == tagname.CompareOrdinal(tagUnderLine, bOrdinalIgnoreCase)) - { - if((*value) == L'0') - m_builder.UnderlineOff(); - else - m_builder.UnderlineOn(); - } - else if (0 == tagname.CompareOrdinal(tagUnderLineNone, bOrdinalIgnoreCase)) +else if (0 == tagname.CompareOrdinal(tagBold, bOrdinalIgnoreCase)) +m_builder.Bold(value); +else if (0 == tagname.CompareOrdinal(tagItalic, bOrdinalIgnoreCase)) +m_builder.Italic(value); +else if (0 == tagname.CompareOrdinal(tagUnderLine, bOrdinalIgnoreCase)) +{ + if ((*value) == L'0') m_builder.UnderlineOff(); - else if(0 == tagname.CompareOrdinal(tagStrikeThrough,bOrdinalIgnoreCase)) - m_builder.Strikethrough(value); + else + m_builder.UnderlineOn(); +} +else if (0 == tagname.CompareOrdinal(tagUnderLineNone, bOrdinalIgnoreCase)) +m_builder.UnderlineOff(); +else if (0 == tagname.CompareOrdinal(tagStrikeThrough, bOrdinalIgnoreCase)) +m_builder.Strikethrough(value); - else if(0 == tagname.CompareOrdinal(tagSuperscript,bOrdinalIgnoreCase)) - m_builder.Superscript(); - else if(0 == tagname.CompareOrdinal(tagSubscript,bOrdinalIgnoreCase)) - m_builder.Subscript(); - else if(0 == tagname.CompareOrdinal(tagNoSuperSub,bOrdinalIgnoreCase)) - m_builder.NoSuperSub(); - - else if(0 == tagname.CompareOrdinal(tagColorTable,bOrdinalIgnoreCase)) - m_builder.BeginColorTable(); - else if(0 == tagname.CompareOrdinal(tagColorRed,bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) - m_builder.ColorRed(value); - else if(0 == tagname.CompareOrdinal(tagColorGreen,bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) - m_builder.ColorGreen(value); - else if(0 == tagname.CompareOrdinal(tagColorBlue,bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) - m_builder.ColorBlue(value); - else if(0 == tagname.CompareOrdinal(tagColorForeground,bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) - m_builder.ColorForeground(value); - else if(0 == tagname.CompareOrdinal(tagColorBackground,bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) - m_builder.ColorBackground(value); +else if (0 == tagname.CompareOrdinal(tagSuperscript, bOrdinalIgnoreCase)) +m_builder.Superscript(); +else if (0 == tagname.CompareOrdinal(tagSubscript, bOrdinalIgnoreCase)) +m_builder.Subscript(); +else if (0 == tagname.CompareOrdinal(tagNoSuperSub, bOrdinalIgnoreCase)) +m_builder.NoSuperSub(); - else if (0 == tagname.CompareOrdinal(tagStackFraction, bOrdinalIgnoreCase)) - m_builder.SetStackScale(value); - else if (0 == tagname.CompareOrdinal(tagStackText, bOrdinalIgnoreCase)) - m_builder.StackFraction(value); - else if (0 == tagname.CompareOrdinal(tagStackEnd, bOrdinalIgnoreCase)) - m_builder.StackEnd(); +else if (0 == tagname.CompareOrdinal(tagColorTable, bOrdinalIgnoreCase)) +m_builder.BeginColorTable(); +else if (0 == tagname.CompareOrdinal(tagColorRed, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) +m_builder.ColorRed(value); +else if (0 == tagname.CompareOrdinal(tagColorGreen, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) +m_builder.ColorGreen(value); +else if (0 == tagname.CompareOrdinal(tagColorBlue, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) +m_builder.ColorBlue(value); +else if (0 == tagname.CompareOrdinal(tagColorForeground, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) +m_builder.ColorForeground(value); +else if (0 == tagname.CompareOrdinal(tagColorBackground, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) +m_builder.ColorBackground(value); - else if (0 == ON_wString::CompareOrdinal(name, tagField, bOrdinalIgnoreCase)) - m_builder.TextField(value); +else if (0 == tagname.CompareOrdinal(tagStackFraction, bOrdinalIgnoreCase)) +m_builder.SetStackScale(value); +else if (0 == tagname.CompareOrdinal(tagStackText, bOrdinalIgnoreCase)) +m_builder.StackFraction(value); +else if (0 == tagname.CompareOrdinal(tagStackEnd, bOrdinalIgnoreCase)) +m_builder.StackEnd(); - else if (0 == ON_wString::CompareOrdinal(name, tagUniCpCount, bOrdinalIgnoreCase)) - m_builder.UniCpCount(value); - else if (0 == ON_wString::CompareOrdinal(name, tagUniCharDec, bOrdinalIgnoreCase)) - m_builder.UniDecimal(value); - else if (0 == ON_wString::CompareOrdinal(name, tagUniTwoDest, bOrdinalIgnoreCase)) - m_builder.UniEmbeddedDest(value); - else if (0 == ON_wString::CompareOrdinal(name, tagUniDest, bOrdinalIgnoreCase)) - m_builder.UniDest(value); +else if (0 == ON_wString::CompareOrdinal(name, tagField, bOrdinalIgnoreCase)) +m_builder.TextField(value); - else if (0 == ON_wString::CompareOrdinal(name, taglquote, bOrdinalIgnoreCase)) - m_builder.LQuote(); - else if (0 == ON_wString::CompareOrdinal(name, tagrquote, bOrdinalIgnoreCase)) - m_builder.RQuote(); - else if (0 == ON_wString::CompareOrdinal(name, tagldblquote, bOrdinalIgnoreCase)) - m_builder.LDblQuote(); - else if (0 == ON_wString::CompareOrdinal(name, tagrdblquote, bOrdinalIgnoreCase)) - m_builder.RDblQuote(); - else if (0 == ON_wString::CompareOrdinal(name, tagbullet, bOrdinalIgnoreCase)) - m_builder.Bullet(); - else if (0 == ON_wString::CompareOrdinal(name, tagendash, bOrdinalIgnoreCase)) - m_builder.EnDash(); - else if (0 == ON_wString::CompareOrdinal(name, tagemdash, bOrdinalIgnoreCase)) - m_builder.EmDash(); +else if (0 == ON_wString::CompareOrdinal(name, tagUniCpCount, bOrdinalIgnoreCase)) +m_builder.UniCpCount(value); +else if (0 == ON_wString::CompareOrdinal(name, tagUniCharDec, bOrdinalIgnoreCase)) +m_builder.UniDecimal(value); +else if (0 == ON_wString::CompareOrdinal(name, tagUniTwoDest, bOrdinalIgnoreCase)) +m_builder.UniEmbeddedDest(value); +else if (0 == ON_wString::CompareOrdinal(name, tagUniDest, bOrdinalIgnoreCase)) +m_builder.UniDest(value); - return true; +else if (0 == ON_wString::CompareOrdinal(name, taglquote, bOrdinalIgnoreCase)) +m_builder.LQuote(); +else if (0 == ON_wString::CompareOrdinal(name, tagrquote, bOrdinalIgnoreCase)) +m_builder.RQuote(); +else if (0 == ON_wString::CompareOrdinal(name, tagldblquote, bOrdinalIgnoreCase)) +m_builder.LDblQuote(); +else if (0 == ON_wString::CompareOrdinal(name, tagrdblquote, bOrdinalIgnoreCase)) +m_builder.RDblQuote(); +else if (0 == ON_wString::CompareOrdinal(name, tagbullet, bOrdinalIgnoreCase)) +m_builder.Bullet(); +else if (0 == ON_wString::CompareOrdinal(name, tagendash, bOrdinalIgnoreCase)) +m_builder.EnDash(); +else if (0 == ON_wString::CompareOrdinal(name, tagemdash, bOrdinalIgnoreCase)) +m_builder.EmDash(); +else +rc = false; + + return rc; } // Called after '\\*' is read @@ -2543,6 +2573,8 @@ bool ON_RtfParser::ReadOptionalTag() { case ON_UnicodeCodePoint::ON_Backslash: rc = ReadTag(true); + if (!rc && m_suspend_to_close == 0) + m_suspend_to_close = 1; break; default: // terminates an optional tag end_of_tag = true; @@ -2625,7 +2657,7 @@ bool ON_RtfParser::Parse() while(!end_of_string) { - if (m_ti.AtBackslashTic()) + if (m_suspend_to_close <= 0 && m_ti.AtBackslashTic()) { // parse the entire contiguous MBCS string up to // \`XX\`XX...\`XX @@ -2640,6 +2672,22 @@ bool ON_RtfParser::Parse() if(false == m_ti.ReadCodePoint(rtf_code_point)) break; + if (m_suspend_to_close > 0) + { + switch (rtf_code_point) + { + case L'}': + m_suspend_to_close--; + break; + case L'{': + m_suspend_to_close++; + break; + } + if (m_suspend_to_close == 0) + m_ti.Back(); + continue; + } + // Found a single byte character switch (rtf_code_point) { @@ -2694,12 +2742,12 @@ bool ON_RtfParser::Parse() case '*': ReadOptionalTag(); break; - case 'n': - //FlushCurText(m_builder.m_current_codepoints); - m_builder.GroupEnd(); - ProcessTag(L"par", nullptr, false); - m_builder.GroupBegin(); - break; + //case 'n': + // //FlushCurText(m_builder.m_current_codepoints); + // m_builder.GroupEnd(); + // ProcessTag(L"par", nullptr, false); + // m_builder.GroupBegin(); + // break; default: // Tag names are always low ascii alphabetic m_ti.Back(); @@ -2787,7 +2835,7 @@ unsigned int RtfComposer::GetFacenameKey(const ON_Font* font, ON_SimpleArray< wc return 0; // Depending on what created the RTF, the face name in the RTF can be a // PostScript name, LOGFONT.lfFaceName, IDWriteFont family name, ... - const ON_wString rtf_facename = font->QuartetName(); // ON_Font::RichTextFontName(font, true); + const ON_wString rtf_facename = font->QuartetName(ON_Font::NameLocale::English); if (rtf_facename.IsEmpty()) return 0; @@ -3163,10 +3211,11 @@ bool RtfComposer::Compose( temp.Format(L"{\\f%d %s;}", fi, fonttable[fi]); fonttable_string += temp; } - fonttable_string += "}"; + fonttable_string += "}\\fs40 "; rtf += fonttable_string; } rtf += run_strings; + // backing out the change made for RH-49725 because it causes the problem reported in RH-50733 rtf += L"\\par}"; } else diff --git a/opennurbs_textiterator.h b/opennurbs_textiterator.h index 5c81bdc2..df7846b1 100644 --- a/opennurbs_textiterator.h +++ b/opennurbs_textiterator.h @@ -835,6 +835,9 @@ private: ON_TextBuilder& m_builder; int m_p_level = 0; bool m_in_real_rtf = false; + // suspend to close means ignore everything until the current group is closed + // It's used to skip over commands and parameters in optional tags + int m_suspend_to_close = 0; bool FlushCurText(ON_SimpleArray< ON__UINT32 >& cp_array); bool ReadTag(bool optional); diff --git a/opennurbs_textlog.cpp b/opennurbs_textlog.cpp index 8158d1d5..96d52b6b 100644 --- a/opennurbs_textlog.cpp +++ b/opennurbs_textlog.cpp @@ -522,18 +522,29 @@ void ON_TextLog::Print( const ON_COMPONENT_INDEX& ci ) void ON_TextLog::Print( const ON_wString& string ) { - const wchar_t* s = static_cast< const wchar_t* >(string); + const ON_wString local(string); // increment reference count to insure s is valid for the duration of this call. + const wchar_t* s = static_cast< const wchar_t* >(local); if ( s && *s ) + { + if (m_beginning_of_line && m_indent.IsNotEmpty()) + AppendText(static_cast(m_indent)); AppendText(s); + } } void ON_TextLog::Print( const ON_String& string ) { - const char* s = static_cast< const char* >(string); - if ( s && *s ) + const ON_String local(string); // increment reference count to insure s is valid for the duration of this call. + const char* s = static_cast< const char* >(local); + if (s && *s) + { + if (m_beginning_of_line && m_indent.IsNotEmpty()) + AppendText(static_cast(m_indent)); AppendText(s); + } } + void ON_TextLog::PrintString( const char* s ) { if (s && *s) @@ -549,7 +560,6 @@ void ON_TextLog::PrintNewLine() Print("\n"); } - void ON_TextLog::PrintString( const wchar_t* s ) { if (s && *s) diff --git a/opennurbs_textlog.h b/opennurbs_textlog.h index 69885494..ab948b7a 100644 --- a/opennurbs_textlog.h +++ b/opennurbs_textlog.h @@ -159,7 +159,7 @@ public: s - [in] nullptr terminated ASCII string. */ void PrintString( const char* s ); - + /* Description: Print an unformatted UNICODE string of any length. @@ -167,7 +167,7 @@ public: s - [in] nullptr terminated UNICODE string. */ void PrintString( const wchar_t* s ); - + void PrintRGB( const ON_Color& ); /* diff --git a/opennurbs_textobject.cpp b/opennurbs_textobject.cpp index 7a943947..d21a48be 100644 --- a/opennurbs_textobject.cpp +++ b/opennurbs_textobject.cpp @@ -315,7 +315,16 @@ bool ON_Text::GetTextXform( ON_Xform tp2sxf; // Text point to view plane rotation ON_3dVector view_z = ON_CrossProduct(view_x, view_y); ON_3dPoint text_point_3d = Plane().origin; - rotation_xf.Rotation(text_point_3d, textobjectplane.xaxis, textobjectplane.yaxis, textobjectplane.zaxis, text_point_3d, view_x, view_y, view_z); + ON_3dVector text_xdir = textobjectplane.xaxis; + ON_3dVector text_ydir = textobjectplane.yaxis; + ON_3dVector text_zdir = textobjectplane.zaxis; + if (nullptr != model_xform) + { + text_xdir.Transform(*model_xform); + text_ydir.Transform(*model_xform); + text_zdir.Transform(*model_xform); + } + rotation_xf.Rotation(text_point_3d, text_xdir, text_ydir, text_zdir, text_point_3d, view_x, view_y, view_z); text_xform_out = wcs2obj_xf * textscale_xf; text_xform_out = rotation_xf * text_xform_out; return true; @@ -325,8 +334,8 @@ bool ON_Text::GetTextXform( double textrotation = TextRotationRadians(); if (fabs(textrotation) > ON_SQRT_EPSILON) rotation_xf.Rotation(textrotation, ON_3dVector::ZAxis, ON_3dPoint::Origin); // Text rotation - - ON_Xform textcenter_xf(ON_Xform::IdentityTransformation); + + //ON_Xform textcenter_xf(ON_Xform::IdentityTransformation); if (dimstyle->DrawForward()) { // Check if the text is right-reading by comparing @@ -367,7 +376,7 @@ bool ON_Text::GetTextXform( } text_xform_out = textscale_xf; text_xform_out = rotation_xf * text_xform_out; - text_xform_out = textcenter_xf * text_xform_out; + //text_xform_out = textcenter_xf * text_xform_out; text_xform_out = wcs2obj_xf * text_xform_out; return true; diff --git a/opennurbs_textrun.cpp b/opennurbs_textrun.cpp index 7c819a26..112b3c2d 100644 --- a/opennurbs_textrun.cpp +++ b/opennurbs_textrun.cpp @@ -22,15 +22,19 @@ #error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs #endif -class ON_CLASS ON_TextRunPool : public ON_FixedSizePool +class ON_TextRunPool : public ON_FixedSizePool { public: static ON_TextRunPool thePool; + private: ON_TextRunPool() { ON_FixedSizePool::Create(sizeof(ON_TextRun), 0, 0); } + ~ON_TextRunPool() = default; + ON_TextRunPool(const ON_TextRunPool&) = delete; + ON_TextRunPool& operator=(const ON_TextRunPool&) = delete; }; ON_TextRunPool ON_TextRunPool::thePool; @@ -41,8 +45,14 @@ ON_TextRun::ON_TextRun(bool bManagedTextRun) ON_TextRun* ON_TextRun::GetManagedTextRun() { - void* p = ON_TextRunPool::thePool.AllocateDirtyElement(); - ON_TextRun* run = new(p)ON_TextRun(true); + // .NET wrappers manage objects with test runs and .NET GC runs in its own thread, + // so text run allocation/return using the global ON_TextRunPool::thePool resource + // must be thread safe. + void* p = ON_TextRunPool::thePool.ThreadSafeAllocateDirtyElement(); + ON_TextRun* run + = (nullptr != p) + ? new(p)ON_TextRun(true) // always happens in practice + : new ON_TextRun(false); // could occur if a crashing working thread leaves the pool locked. return run; } @@ -66,14 +76,16 @@ bool ON_TextRun::ReturnManagedTextRun( if ( nullptr == run ) return true; - if (1 == run->m_managed_status) { if (0 == run->m_active_status) { run->Internal_Destroy(); run->m_active_status = 1; - ON_TextRunPool::thePool.ReturnElement(run); + // .NET wrappers manage objects with test runs and .NET GC runs in its own thread, + // so text run allocation/return using the global ON_TextRunPool::thePool resource + // must be thread safe. + ON_TextRunPool::thePool.ThreadSafeReturnElement(run); return true; } ON_ERROR("Attempt to return a managed run that is not active."); diff --git a/opennurbs_topology.cpp b/opennurbs_topology.cpp index 0cab0483..cd32c2a6 100644 --- a/opennurbs_topology.cpp +++ b/opennurbs_topology.cpp @@ -29,9 +29,8 @@ bool ON_ComponentAttributes::IsSolid( unsigned int aggregate_edge_component_attributes ) { - const unsigned int required_bits + const unsigned int required_bits = ON_ComponentAttributes::EdgeFlags::Oriented - | ON_ComponentAttributes::EdgeFlags::Damaged ; if ( required_bits != (required_bits & aggregate_edge_component_attributes) ) return false; diff --git a/opennurbs_unicode.h b/opennurbs_unicode.h index dce0b687..06f1efa7 100644 --- a/opennurbs_unicode.h +++ b/opennurbs_unicode.h @@ -54,6 +54,7 @@ enum ON_UnicodeCodePoint ON_Backslash = 0x5C, // REVERSE SOLIDUS U+005C (decimal 92)ere ON_Underscore = 0x5F, // Unicode LOW LINE U+005F ON_Pipe = 0x7C, // VERTICAL LINE U+007C (decimal 124) + ON_Tilde = 0x7E, // TILDE U+007E (decimal 126) // // NOTE: UTF-8 encodings of the codepoint values below this comment require multiple bytes. diff --git a/opennurbs_viewport.cpp b/opennurbs_viewport.cpp index a554a70b..866a7f0b 100644 --- a/opennurbs_viewport.cpp +++ b/opennurbs_viewport.cpp @@ -1200,6 +1200,23 @@ bool ON_Viewport::DollyFrustum( double dollyDistance ) return rc; } +bool ON_Viewport::GetCameraFrame( + class ON_Plane& camera_frame +) const +{ + bool rc = GetCameraFrame( + &camera_frame.origin.x, + &camera_frame.xaxis.x, + &camera_frame.yaxis.x, + &camera_frame.zaxis.x + ); + if (rc) + rc = camera_frame.UpdateEquation(); + if (false == rc) + camera_frame = ON_Plane::NanPlane; + return rc; +} + bool ON_Viewport::GetCameraFrame( double* CameraLocation, double* CameraX, diff --git a/opennurbs_viewport.h b/opennurbs_viewport.h index a55d30fc..60801584 100644 --- a/opennurbs_viewport.h +++ b/opennurbs_viewport.h @@ -361,6 +361,10 @@ public: double* // CameraZ[3] ) const; + bool GetCameraFrame( + class ON_Plane& camera_frame + ) const; + // these do not check for a valid camera orientation ON_3dVector CameraX() const; // unit to right vector ON_3dVector CameraY() const; // unit up vector diff --git a/opennurbs_win_dwrite.cpp b/opennurbs_win_dwrite.cpp index 4117f813..fcfa26a7 100644 --- a/opennurbs_win_dwrite.cpp +++ b/opennurbs_win_dwrite.cpp @@ -1038,7 +1038,22 @@ unsigned int ON_Font::GetInstalledWindowsDWriteFonts( if (0 == fi.m_gdi_interop_logfont.lfFaceName[0]) { // During testing on Windows 10, this never occured. - ON_ERROR("Empty LOGFONT lfFaceName"); + // ... + // Dale Lear 7 Jan 2019. + // It appears Turbo Tax 2018 installs 4 faces of Avenir LT with + // empty family name and empty logfont name. It has non-empty PostScript names. + // + // C:\PROGRAM FILES (X86)\TURBOTAX\DELUXE 2018\32BIT\LOCAL\FUEGO\HOST\AVENIR\ + // Avenir LT 35 Light: AVENIRLT-LIGHT.TTF + // Avenir LT 55 Roman: AVENIRLT-ROMAN.TTF + // Avenir LT 65 Medium: AVENIRLT-MEDIUM.TTF + // Avenir LT 85 Heavy: AVENIRLT-HEAVY.TTF + // + // These fonts never appear in Windows Notepad, WordPad, or Word font UI. + // The Windows Setting font list leaves the name of this font blank as well. + // So I'm going to comment out the call to ON_ERROR. + // + ////ON_ERROR("Empty LOGFONT lfFaceName"); continue; } diff --git a/opennurbs_wstring.cpp b/opennurbs_wstring.cpp index 093231e0..cbd95c2b 100644 --- a/opennurbs_wstring.cpp +++ b/opennurbs_wstring.cpp @@ -24,8 +24,6 @@ #error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs #endif -#include "opennurbs_atomic_op.h" - // wide char (utf-8 / utf-16 / utf-23) <-> char (utf-8) converter 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 @@ -148,29 +146,66 @@ void ON_String::CopyToArray( int w_count, const wchar_t* w ) ///////////////////////////////////////////////////////////////////////////// // Empty strings point at empty_wstring -struct ON_wStringHeader +class ON_wStringHeader { +private: + ON_wStringHeader() = delete; +public: + ~ON_wStringHeader() = default; + ON_wStringHeader(const ON_wStringHeader&) = default; + ON_wStringHeader& operator=(const ON_wStringHeader&) = default; + +public: + ON_wStringHeader( + int initial_ref_count, + int capacity + ) + : ref_count(initial_ref_count) + , string_capacity(capacity) + {} + +public: // NOTE WELL: // ref_count must be a signed 32-bit integer type that // supports atomic increment/decrement operations. - int ref_count; // reference count (>=0 or -1 for empty string) - int string_length; // does not include any terminators - int string_capacity; // does not include any terminators + std::atomic ref_count; + int string_length=0; // does not include null terminator + int string_capacity; // does not include null terminator wchar_t* string_array() {return (wchar_t*)(this+1);} }; -static struct { +class ON_Internal_Empty_wString +{ +private: + ON_Internal_Empty_wString(const ON_Internal_Empty_wString&) = delete; + ON_Internal_Empty_wString& operator=(const ON_Internal_Empty_wString&) = delete; + +public: + ON_Internal_Empty_wString() + : header(-1,0) + {} + ~ON_Internal_Empty_wString() = default; + +public: ON_wStringHeader header; - wchar_t s; -} empty_wstring = { {-1, 0, 0}, 0 }; // ref_count=-1, length=0, capacity=0, s=0 + wchar_t s = 0; +}; + +static ON_Internal_Empty_wString empty_wstring; static const ON_wStringHeader* pEmptyStringHeader = &empty_wstring.header; static const wchar_t* pEmptywString = &empty_wstring.s; -static void ON_wStringHeader_DecrementRefCountAndDeleteIfZero(struct ON_wStringHeader* hdr) +static void ON_wStringHeader_DecrementRefCountAndDeleteIfZero(class ON_wStringHeader* hdr) { + //// sz must be = 12 or SDK breaks + //size_t sz = sizeof(*hdr); + //ON_TextLog::Null.Print((const char*)nullptr, (int)sz); + if (nullptr == hdr || hdr == pEmptyStringHeader) return; - const int ref_count = ON_AtomicDecrementInt32(&hdr->ref_count); + + //const int ref_count = ON_AtomicDecrementInt32(&hdr->ref_count); + const int ref_count = --hdr->ref_count; if (0 == ref_count) { // zero entire header to help prevent crashes from corrupt string bug @@ -185,6 +220,10 @@ static void ON_wStringHeader_DecrementRefCountAndDeleteIfZero(struct ON_wStringH void ON_wString::Create() { + //// sz must be = sizeof(void*) or SDK breaks + //size_t sz = sizeof(*this); + //ON_TextLog::Null.Print((const char*)nullptr, (int)sz); + m_s = (wchar_t*)pEmptywString; } @@ -236,7 +275,7 @@ bool ON_wString::IsValid( break; if (string_length > string_capacity) break; - const int ref_count = hdr->ref_count; + const int ref_count = (int)(hdr->ref_count); if (ref_count <= 0) break; const wchar_t* s1 = s + string_length; @@ -283,26 +322,27 @@ bool ON_wString::IsValid( ON_wStringHeader* ON_wString::IncrementedHeader() const { - ON_wStringHeader* p = (ON_wStringHeader*)m_s; - if (nullptr == p) + ON_wStringHeader* hdr = (ON_wStringHeader*)m_s; + if (nullptr == hdr) return nullptr; - p--; - if (p == pEmptyStringHeader) + hdr--; + if (hdr == pEmptyStringHeader) return nullptr; - ON_AtomicIncrementInt32(&p->ref_count); - return p; + //ON_AtomicIncrementInt32(&hdr->ref_count); + ++hdr->ref_count; + return hdr; } ON_wStringHeader* ON_wString::Header() const { - ON_wStringHeader* p = (ON_wStringHeader*)m_s; - if (p) - p--; + ON_wStringHeader* hdr = (ON_wStringHeader*)m_s; + if (hdr) + hdr--; else - p = &empty_wstring.header; - return p; + hdr = &empty_wstring.header; + return hdr; } wchar_t* ON_wString::CreateArray( int capacity ) @@ -316,12 +356,9 @@ wchar_t* ON_wString::CreateArray( int capacity ) if ( capacity > 0 ) { // This scope does not need atomic operations - ON_wStringHeader* p = - (ON_wStringHeader*)onmalloc( sizeof(ON_wStringHeader) + (capacity+1)*sizeof(*m_s) ); - p->ref_count = 1; - p->string_length = 0; - p->string_capacity = capacity; - m_s = p->string_array(); + void* buffer = onmalloc( sizeof(ON_wStringHeader) + (capacity+1)*sizeof(*m_s) ); + ON_wStringHeader* hdr = new(buffer) ON_wStringHeader(1,capacity); + m_s = hdr->string_array(); memset( m_s, 0, (capacity+1)*sizeof(*m_s) ); return m_s; } @@ -331,7 +368,7 @@ wchar_t* ON_wString::CreateArray( int capacity ) void ON_wString::Destroy() { ON_wStringHeader* hdr = Header(); - if ( hdr != pEmptyStringHeader && nullptr != hdr && hdr->ref_count > 0 ) + if ( hdr != pEmptyStringHeader && nullptr != hdr && (int)(hdr->ref_count) > 0 ) ON_wStringHeader_DecrementRefCountAndDeleteIfZero(hdr); Create(); } @@ -364,7 +401,7 @@ void ON_wString::CopyArray() // Call CopyArray() before modifying array contents. // hdr0 = original header ON_wStringHeader* hdr0 = Header(); - if ( hdr0 != pEmptyStringHeader && nullptr != hdr0 && 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(); @@ -399,7 +436,7 @@ wchar_t* ON_wString::ReserveArray( size_t array_capacity ) { CreateArray(capacity); } - else if ( hdr0->ref_count > 1 ) + else if ( (int)(hdr0->ref_count) > 1 ) { // Calling Create() here insures hdr0 remains valid until we decrement below. Create(); @@ -443,7 +480,7 @@ void ON_wString::ShrinkArray() Destroy(); Create(); } - else if ( hdr0->ref_count > 1 ) + else if ( (int)(hdr0->ref_count) > 1 ) { // Calling Create() here insures hdr0 remains valid until we decrement below. Create(); diff --git a/opennurbs_xform.cpp b/opennurbs_xform.cpp index beb69a22..b7fefb32 100644 --- a/opennurbs_xform.cpp +++ b/opennurbs_xform.cpp @@ -1169,46 +1169,283 @@ int ON_Xform::Compare( const ON_Xform& rhs ) const return 0; } + + +static +ON_Interval BoundEVals( const ON_Xform& M ) +{ + // assume M is Linear(). + // Bound the eigenvalues. Spectrum ( M ) \in [emin, emax] + // that is if lambda is a eigenvalue of M then emin<=Re(lambda)<=emax + // Uses Gershgorin circle theorem. + + ON_Interval SpectrumHull; + for (int i = 0; i < 3; i++) + { + double R = 0.0; + for (int j = 0; j < 3; j++) + if (j != i) R += fabs( M[i][j] ); + ON_Interval GCircle ( M[i][i] - R , M[i][i] + R ); + if (i == 0) + SpectrumHull = GCircle; + else + SpectrumHull.Union(GCircle); + } + return SpectrumHull; +} + +// Given a Linear transformation L. return an interval containing Spectrum(L^T *L) +static ON_Interval ApproxSpectrumLTL(const ON_Xform& L) +{ + // L.IsLinear() is a precondition + + // LTL = L^T * L + ON_Xform LTL = L; + LTL.Transpose(); + LTL = LTL * L; + + return BoundEVals(LTL); +} + +// Given a linear transformation bound distance to group of orthogonal transformations +// dist ( L, O(3) ) < ApproxDist2Ortho(L) +// L = R * P is the polar decomposition of L. R is the closest rotation to L and +// P = (L^T * L)^(-1/2) +// So || L-R || = || R*( P - I ) || = || P - I || = || (L^T L)^(1/2) - I || +static double ApproxDist2Ortho(const ON_Xform& L) +{ + // L.IsLinear() is a precondition + + ON_Interval Spec = ApproxSpectrumLTL(L); + if (Spec[0] < 0) Spec[0] = 0.0; + Spec[0] = sqrt(Spec[0]) - 1.0; + Spec[1] = sqrt(Spec[1]) - 1.0; // contains Spectrum of (L^T L)^(-1/2) - I + double dist = fabs(Spec[0]); + if (dist < fabs(Spec[1])) + dist = fabs(Spec[1]); + + return dist; +} + int ON_Xform::IsSimilarity() const { - int rc = 0; - if (IsAffine()) - { - const double tol = 1.0e-4; - const double dottol = 1.0e-3; - const double det = Determinant(); - // GBA (28-Nov-17) note: det = lambda^3 when this xform is a dialation by lambda. - // The new threshold allows for lambda~>1e-5. I had an example of - // a change of coordintes from millimeters to meters (lambda=1e-3) that - // was not being reported as a similarity transformation. - if ( fabs(det) > ON_EPSILON ) - { - const ON_3dVector X(m_xform[0][0],m_xform[1][0],m_xform[2][0]); - const ON_3dVector Y(m_xform[0][1],m_xform[1][1],m_xform[2][1]); - const ON_3dVector Z(m_xform[0][2],m_xform[1][2],m_xform[2][2]); - const double sx = X.Length(); - const double sy = Y.Length(); - const double sz = Z.Length(); - if ( - sx > 0.0 && sy > 0.0 && sz > 0.0 - && fabs(sx-sy) <= tol - && fabs(sy-sz) <= tol - && fabs(sz-sx) <= tol ) - { - const double xy = (X*Y)/(sx*sy); - const double yz = (Y*Z)/(sy*sz); - const double zx = (Z*X)/(sz*sx); - if ( fabs(xy) <= dottol && fabs(yz) <= dottol && fabs(zx) <= dottol ) - { - rc = (det > 0.0) ? 1 : -1; - } - } - } - } - - return rc; + return IsSimilarity(ON_ZERO_TOLERANCE); } +int ON_Xform::IsSimilarity(double tol) const +{ + // This function does not construt a similarity transformation, + // ( see ON_Xform::DecomposeSimilarity() for this ). It mearly + // indicates that this transformation is sufficiently close to a similatiry. + // However using with a tight tolerance like toldist ) + { + double det = L.Determinant(); + rval = (det > 0) ? 1 : -1; + } + } + return rval; +} + + +int ON_Xform::DecomposeSimilarity(ON_3dVector& T, double& dilation, ON_Xform& R, double tolerance) const +{ + int rval = 0; + if (IsAffine()) + { + ON_Xform L; + DecomposeAffine(T, L); + + /* Three cases: + I. L is within OrthogonalTol of being orthogonal then just return R = Linear + (this is an optimization to avoid doing an eigen solve) + II. Linear<10*tolerance or tol>1.0 then find the closest orthogonal matix R. + test the final solution to see if |*this-R| 0) ? 1 : -1; + } + + else if (dist < 10 * tolerance || tolerance>1.0) + { + // Case II. + ON_Xform Q; // ortho change of coordinate matrix + ON_3dVector lambda; + ON_3dVector Ttrash; + if (L.DecomposeAffine(Ttrash, R, Q, lambda)) + { + // Find the min and max eigen-values + int mini=0, maxi=0; + double l0 = ON_DBL_MAX; + double l1 = ON_DBL_MIN; + for (int i = 0; i < 3; i++) + { + if (lambda[i] < l0) + { + mini = i; l0 = lambda[i]; + } + if (l1< lambda[i]) + { + maxi = i; l1 = lambda[i]; + } + } + double err = (lambda[maxi] - lambda[mini]) / 2.0; + if (err > tolerance) + rval = 0; + else + { + dilation = (lambda[mini] + lambda[maxi]) / 2.0; + rval = (dilation > 0) ? 1 : -1; + //dilation; + } + } + } + } + return rval; +} + + +bool ON_Xform::DecomposeSymmetric(ON_Xform& Q, ON_3dVector& diagonal) const +{ + bool rc = false; + if (IsLinear()) + { + bool symmetric = ( m_xform[0][1] == m_xform[1][0] && + m_xform[0][2] == m_xform[2][0] && + m_xform[1][2] == m_xform[2][1] ); + if (symmetric) + { + ON_3dVector evec[3]; + rc = ON_Sym3x3EigenSolver(m_xform[0][0], m_xform[1][1], m_xform[2][2], + m_xform[0][1], m_xform[1][2], m_xform[0][2], + &diagonal.x, evec[0], + &diagonal.y, evec[1], + &diagonal.z, evec[2]); + if (rc) + { + Q = ON_Xform(ON_3dPoint::Origin, evec[0], evec[1], evec[2]); + } + } + } + return rc; +} + + +int ON_Xform::DecomposeRigid(ON_3dVector& T, ON_Xform& R, double tolerance) const +{ + int rval = 0; + if (IsAffine()) + { + ON_Xform Linear; + DecomposeAffine(T, Linear); + + + /* Three cases: + I. Linear is within OrthogonalTol of being orthogonal then just return R = Linear + (this is an optimization to avoid doing an eigen solve) + II. Linear~~<10*tolerance or tol>1.0 then find the closest orthogonal matix R. + test the final solution to see if |*this-R| 0) ? 1: -1; + } + + else if (dist < 10*tolerance || tolerance>1.0) + { + // Case II. + // Closest orthogonal matrix is given by polar decomposition + // BHP Horn, http://people.csail.mit.edu/bkph/articles/Nearest_Orthonormal_Matrix.pdf + ON_Xform Q; + ON_3dVector lambda; + if (DecomposeAffine(T, R, Q, lambda)) + { + // Is ||R - Linear|| = || R ( I - Q lam QT ) || = || I - Q lam QT ||= || Q QT - Q lam QT ||= || I - lam || + double err = 0.0; + for (int i = 0; i < 3; i++) + { + double x = fabs(1.0 - lambda[i]); + if (x > err) + err = x; + } + if (err < tolerance) + { + double det = lambda[0] * lambda[1] * lambda[2]; + rval = (det > 0) ? 1 : -1; + } + } + } + } + return rval; +} + +int ON_Xform::IsRigid(double tolerance) const +{ + // This function does not construt a rigid transformation, + // ( see ON_Xform::DecomposeRigid() for this ). It mearly + // indicates that this trasformation is sufficiently close to a rigid one. + // However using with a tight tolerance like tol0; + } + return rc; +} + +bool ON_Xform::Orthogonalize(double tol) +{ + bool rc = false; + if (IsAffine()) + { + ON_3dVector T; + ON_Xform L; + DecomposeAffine(T, L); + ON_Xform LTL = L; + LTL.Transpose(); + LTL = LTL * L; + if (!LTL.IsIdentity(tol)) + { + // Gram - Schmidt + ON_3dVector V[3]; + V[0] = ON_3dVector(m_xform[0]); + V[1] = ON_3dVector(m_xform[1]); + V[2] = ON_3dVector(m_xform[2]); + rc = true; + for (int i = 0; rc && i < 3; i++) + { + for (int j = 0; j < i; j++) + V[i] -= V[i] * V[j] * V[j]; + rc = V[i].Unitize(); + } + if (rc) + { + *(reinterpret_cast(m_xform[0])) = V[0]; + *(reinterpret_cast(m_xform[1])) = V[1]; + *(reinterpret_cast(m_xform[2])) = V[2]; + } + } + else + rc = true; + } + return rc; +} + + +bool ON_Xform::DecomposeAffine(ON_3dVector& T, ON_Xform& R, + ON_Xform& Q, ON_3dVector& lambda) const +{ + bool rc = false; + if (IsAffine()) + { + ON_Xform L; + DecomposeAffine(T, L); + ON_Xform LT = L; + LT.Transpose(); + ON_Xform LTL = LT * L; + + rc = LTL.DecomposeSymmetric(Q, lambda); + if (rc) + { + rc = (lambda[0] > 0 && lambda[1] > 0 && lambda[2] > 0); + if(rc) + { + lambda[0] = sqrt(lambda[0]); + lambda[1] = sqrt(lambda[1]); + lambda[2] = sqrt(lambda[2]); + ON_Xform QT = Q; + QT.Transpose(); + ON_Xform Diag = ON_Xform::DiagonalTransformation(1.0 / lambda[0], 1.0 / lambda[1], 1.0 / lambda[2]); + R = Q * Diag * QT; + R = L * R; + + if (R.Determinant() < 0) + { + R = ON_Xform(-1) * R; + lambda = -1 * lambda; + } + R.Orthogonalize(ON_ZERO_TOLERANCE); // tune it up - tol should be <= that in + // Is_Rotation() + } + } + } + return rc; +} + + + + +bool ON_Xform::DecomposeAffine(ON_3dVector& T, ON_Xform& L) const +{ + bool rc = IsAffine(); + if (rc) + { + T = ON_3dVector(m_xform[0][3], m_xform[1][3], m_xform[2][3]); + L = (*this); + L.m_xform[0][3] = L.m_xform[1][3] = L.m_xform[2][3] = 0.0; + } + return rc; +} + + +// Suppose the transformation is given by f(x) = Lx + B. If L is invertible then +// f(x) = L ( x + L^(-1) B) so T = L^(-1) B. +bool ON_Xform::DecomposeAffine(ON_Xform& L, ON_3dVector& T) const +{ + bool rc = IsAffine(); + if (rc) + { + ON_Xform Linv = *this; + rc = Linv.Invert(); + if (rc) + { + T = - ON_3dVector( Linv[0][3], Linv[1][3], Linv[2][3] ); + L = (*this); + L[0][3] = L[1][3] = L[2][3] = 0.0; + } + /* + TODO: A more thourough solution would be to take a tolerance and + compute the best approximate T using psuodoinvese and comparing it + using the tolerance. + */ + } + return rc; +} + bool ON_Xform::IsZero() const { @@ -1238,9 +1629,23 @@ bool ON_Xform::IsZero4x4() const bool ON_Xform::IsZeroTransformation() const { - return (1.0 == m_xform[3][3] && IsZero()); + return IsZeroTransformation(0.0); } +bool ON_Xform::IsZeroTransformation(double tol) const +{ + bool rc = true; + for(int i=0; rc && i<4; i++) + for (int j = 0; rc && j < 4; j++) + { + if (i == 3 && j == 3) + continue; + rc = fabs(m_xform[i][j]) < tol; + } + return (rc && 1.0 == m_xform[3][3] ); +} + + void ON_Xform::Transpose() { double t; @@ -1569,6 +1974,77 @@ void ON_Xform::Rotation( // (not strictly a rotation) *this = T1*R*T0; } +void ON_Xform::RotationZYX(double yaw, double pitch, double roll) +{ + ON_Xform Rx; + Rx.Rotation(roll, ON_3dVector::XAxis, ON_3dPoint::Origin); + ON_Xform Ry; + Ry.Rotation( pitch, ON_3dVector::YAxis, ON_3dPoint::Origin); + ON_Xform Rz; + Rz.Rotation(yaw, ON_3dVector::ZAxis, ON_3dPoint::Origin); + (*this) = Rz * Ry * Rx; +} + +void ON_Xform::RotationZYZ(double alpha, double beta, double gamma) +{ + ON_Xform Rz; + Rz.Rotation(gamma, ON_3dVector::ZAxis, ON_3dPoint::Origin); + ON_Xform Ry; + Ry.Rotation(beta, ON_3dVector::YAxis, ON_3dPoint::Origin); + ON_Xform Rzz; + Rzz.Rotation(alpha, ON_3dVector::ZAxis, ON_3dPoint::Origin); + (*this) = Rzz * Ry * Rz; +} + +bool ON_Xform::GetYawPitchRoll(double& yaw, double& pitch, double& roll)const +{ + bool rc = IsRotation(); + if (rc) + { + if( + (m_xform[1][0] == 0.0 && m_xform[0][0] == 0.0) + || + (m_xform[2][1] == 0.0 && m_xform[2][2] == 0.0) || + (fabs(m_xform[2][0])>=1.0) ) + { + pitch = (m_xform[2][0] > 0) ? -ON_PI / 2.0 : ON_PI / 2.0; + yaw = atan2(-m_xform[0][1], m_xform[1][1] ); + roll = 0.0; + } + else + { + yaw = atan2(m_xform[1][0], m_xform[0][0]); + roll = atan2(m_xform[2][1], m_xform[2][2]); + pitch = asin(-m_xform[2][0]); + } + } + return rc; +} + +bool ON_Xform::GetEulerZYZ(double& alpha, double& beta, double& gamma)const +{ + bool rc = IsRotation(); + if(rc) + { + if ((fabs(m_xform[2][2]) >= 1.0) || + (m_xform[1][2] == 0.0 && m_xform[0][2] == 0.0) || + (m_xform[2][1] == 0.0 && m_xform[2][0] == 0.0)) + { + beta = (m_xform[2][2] > 0) ? 0.0 : ON_PI; + alpha = atan2(-m_xform[0][1], m_xform[1][1]); + gamma = 0.0; + } + else + { + beta = acos(m_xform[2][2]); + alpha = atan2(m_xform[1][2], m_xform[0][2]); + gamma = atan2(m_xform[2][1], -m_xform[2][0]); + } + } + return rc; +} + + void ON_Xform::Mirror( ON_3dPoint point_on_mirror_plane, ON_3dVector normal_to_mirror_plane diff --git a/opennurbs_xform.h b/opennurbs_xform.h index 585afdfd..19638995 100644 --- a/opennurbs_xform.h +++ b/opennurbs_xform.h @@ -224,32 +224,146 @@ public: 0 0 0 0 0 0 0 0 0 0 0 1 + An element of the matrix is "zero" if fabs(x) <= zero_tolerance. + IsZeroTolerance() is the same as IsZeroTransformation( 0.0 ); */ - bool IsZeroTransformation() const; + bool IsZeroTransformation() const; + bool IsZeroTransformation(double zero_tolerance ) const; /* Description: A similarity transformation can be broken into a sequence - of dialations, translations, rotations, and reflections. + of a dialation, translation, rotation, and a reflection. + Parameters: + *this - must be IsAffine(). + Translation - [out] Translation vector + dilation - [out] dialation, (dilation <0 iff this is an orientation reversing similarity ) + Rotation - [out] a proper rotation transformation ie. R*Transpose(R)=I and det(R)=1 + Details: + If X.DecomposeSimilarity(T, d, R, tol) !=0 then X ~ TranslationTransformation(T)*DiagonalTransformation(d)*R + note when d>0 the transformation is orientation preserving. + Note: + If dilation<0 then DiagonalTransformation( dilation) is actually a reflection + combined with a "true" dialation, i.e. + DiagonalTransformation( dilation) = DiagonalTransformation(-1) * DiagonalTransformation( |diagonal| ) Returns: +1: This transformation is an orientation preserving similarity. -1: This transformation is an orientation reversing similarity. 0: This transformation is not a similarity. */ - int IsSimilarity() const; + int IsSimilarity() const; + int IsSimilarity(double tolerance) const; + int DecomposeSimilarity(ON_3dVector& Translation, double& dilation, ON_Xform& Rotation, double tolerance) const; + + /* + Description: + A rigid transformation can be broken into a proper rotation and a translation. + while an isometry transformation could also include a reflection. + Parameters: + *this - must be IsAffine(). + Translation - [out] Translation vector + Rotation - [out] Proper Rotation transformation, ie. R*Transpose(R)=I and det(R)=1 + Details: + If X.DecomposeRigid(T, R) is 1 then X ~ TranslationTransformation(T)*R + -1 X ~ ON_Xform(-1) *TranslationTransformation(T)*R + where ~ means approximates to within tolerance. + DecomposeRigid will find the closest rotation to the linear part of this transformation. + Returns: + +1: This transformation is an rigid transformation. + -1: This transformation is an orientation reversing isometry. + 0 : This transformation is not an orthogonal transformation. + */ + int IsRigid(double tolerance = ON_ZERO_TOLERANCE) const; + int DecomposeRigid(ON_3dVector& Translation, ON_Xform& Rotation, double tolerance = ON_ZERO_TOLERANCE) const; /* Description: A transformation is affine if it is valid and its last row is 0 0 0 1 + If in addition its last column is ( 0, 0, 0, 1)^T then it is linear. An affine transformation can be broken into a linear transformation and a translation. + Parameters: + *this - IsAffine() must be true for DecomposeAffine(..) to succeed + Translation - [out] Translation vector + Linear - [out] Linear transformation Example: A perspective transformation is not affine. + Details: + If X.DecomposeAffine(T, L) is true then X == TranslationTransformation(T)*L + If X.DecomposeAffine(L, T) is true then X == L* TranslationTransformation(T). + Note: + DecomposeAffine(T,L) succeeds for all affine transformations and is a simple copying of values. + DecomposeAffine(L, T), on the otherhand, may fail for affine transformations if L is not invertible + and is more computationally expensive. Returns: - True if this is an affine transformation. + True - if sucessfull decomposition */ bool IsAffine() const; + bool IsLinear() const; + bool DecomposeAffine(ON_3dVector& Translation, ON_Xform& Linear) const; + bool DecomposeAffine(ON_Xform& Linear, ON_3dVector& Translation ) const; + + // true if this is a proper rotation. + bool IsRotation() const; + + /* + Description: + An affine transformation can be decomposed into a Symmetric, Rotation and Translation. + Then the Symmetric component may be further decomposed as non-uniform scale in an orthonormal + coordinate system. + Parameters: + *this - must be IsAffine(). + Translation-[out] Translation vector + Rotation - [out] Proper Rotation transformation + OrthBasis - [out] Orthogonal Basis + Diagonal - [out] diagonal elements of a DiagonalTransformation + Details: + DecomposeAffine(T,R,Q,diag) + (*this) == TranslationTransformation(T) * R * Q * DiagonalTransformation(diag) * Q.Transpose() + Returns: + true if decomposition succeeds + */ + bool DecomposeAffine(ON_3dVector& Translation, ON_Xform& Rotation, ON_Xform& OrthBasis, ON_3dVector& Diagonal) const; + + /* + Description: + Replace last row with 0 0 0 1 discarding any perspecive part of this transform + */ + void Affineize(); + + /* + Description: + Affineize() and replace last column with (0 0 0 1)^T + discarding any translation part of this transform. + */ + void Linearize(); + + /* + Description: + Force the linear part of this transformation to be a rotation (or a rotation with reflection). + This is probably best to perform minute tweeks. + Use DecomposeRigid(T,R) to find the nearest rotation + */ + bool Orthogonalize(double tol); + + /* + Description: + A Symmetric linear transformation can be decomposed A = Q * Diag * Q^T where Diag is a diagonal + transformation. Diag[i][i] is an eigenvalue of A and the i-th coulmn of Q is a corresponding + unit length eigenvector. + Parameters: + This transformation must be Linear() and Symmetric, that is *this==Transpose(). + Q -[out] is set to an orthonormal matrix of eigenvectors when true is returned. + diagonal -[out] is set to a vector of eigenvalues when true is returned. + Details: + If success then *this== Q*DiagonalTransformation(diagonal) * QT, where QT == Q.Transpose(). + If L.IsLinear() and LT==L.Transpose() then LT*L is symmetric and is a common source of symmetric + transformations. + Return: + true if success. + */ + bool DecomposeSymmetric(ON_Xform& Q, ON_3dVector& diagonal) const; /* Description: @@ -639,6 +753,68 @@ public: const ON_Plane& plane1 ); + /* + Description: + Create rotation transformation From Tait-Byran angles (also loosely known as Euler angles). + Parameters: + yaw - angle (in radians) to rotate about the Z axis + pitch - angle (in radians) to rotate about the Y axis + roll - angle (in radians) to rotate about the X axis + Details: + RotationZYX(yaw, pitch, roll) = R_z( yaw) * R_y(pitch) * R_x(roll) + where R_*(angle) is rotation of angle radians about the corresponding world coordinate axis. + */ + void RotationZYX(double yaw, double pitch, double roll); + + /* + Description: + Find the Tait-Byran angles (also loosely called Euler angles) for a rotation transformation. + Parameters: + yaw - angle (in radians) to rotate about the Z axis + pitch - angle (in radians) to rotate about the Y axis + roll - angle (in radians) to rotate about the X axis + Details: + When true is returned. + this = RotationZYX(yaw, pitch, roll) = R_z( yaw) * R_y(pitch) * R_x(roll) + where R_*(angle) is rotation of angle radians about the corresponding world coordinate axis. + Returns false if this is not a rotation. + Notes: + roll and yaw are in the range (-pi, pi] and pitch is in [-pi/2, pi/2] + + */ + bool GetYawPitchRoll(double& yaw, double& pitch, double& roll)const; + +/* +Description: + Create rotation transformation From Euler angles. +Parameters: + alpha - angle (in radians) to rotate about the Z axis + beta - angle (in radians) to rotate about the Y axis + gamma - angle (in radians) to rotate about the Z axis +Details: + RotationZYZ(alpha, beta, gamma) = R_z( alpha) * R_y(beta) * R_z(gamma) + where R_*(angle) is rotation of angle radians about the corresponding *-world coordinate axis. +*/ + void RotationZYZ(double alpha, double beta, double gamma); + +/* +Description: + Find the Euler angles for a rotation transformation. +Parameters: + alpha - angle (in radians) to rotate about the Z axis + beta - angle (in radians) to rotate about the Y axis + gamma - angle (in radians) to rotate about the Z axis +Details: + When true is returned. + *this = RotationZYZ(alpha, beta, gamma) = R_z( alpha) * R_y(beta) * R_z(gamma) + where R_*(angle) is rotation of angle radians about the corresponding *-world coordinate axis. + Returns false if this is not a rotation. +Notes: + alpha and gamma are in the range (-pi, pi] while beta in in the range [0, pi] +*/ + bool GetEulerZYZ(double& alpha, double& beta, double& gamma )const; + + /* Description: Create mirror transformation matrix. diff --git a/zlib/opennurbs_public_zlib.xcodeproj/project.pbxproj b/zlib/opennurbs_public_zlib.xcodeproj/project.pbxproj index 8238d8a9..ae49d584 100644 --- a/zlib/opennurbs_public_zlib.xcodeproj/project.pbxproj +++ b/zlib/opennurbs_public_zlib.xcodeproj/project.pbxproj @@ -320,6 +320,55 @@ }; name = Release; }; + DFBF8429220394E000BF1536 /* Distribution */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + }; + name = Distribution; + }; + DFBF842A220394E000BF1536 /* Distribution */ = { + isa = XCBuildConfiguration; + buildSettings = { + EXECUTABLE_PREFIX = lib; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Distribution; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -328,6 +377,7 @@ buildConfigurations = ( 1DB0281F1ED6430300FA9144 /* Debug */, 1DB028201ED6430300FA9144 /* Release */, + DFBF8429220394E000BF1536 /* Distribution */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -337,6 +387,7 @@ buildConfigurations = ( 1DB028221ED6430300FA9144 /* Debug */, 1DB028231ED6430300FA9144 /* Release */, + DFBF842A220394E000BF1536 /* Distribution */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release;