From eae8e18c3a927a0d353f6ab344ec7ccc18873eee Mon Sep 17 00:00:00 2001 From: QIDI TECH <893239786@qq.com> Date: Mon, 5 May 2025 15:13:42 +0800 Subject: [PATCH] update src --- CMakeLists.txt | 4 +- src/CMakeLists.txt | 1 + src/QIDIStudio.cpp | 961 ++- src/QIDIStudio_app_msvc.cpp | 235 +- src/admesh/stl.h | 15 +- src/admesh/stlinit.cpp | 12 +- src/build-utils/CMakeLists.txt | 2 +- .../include/clipper2/clipper.core.h | 1620 ++-- .../include/clipper2/clipper.engine.h | 321 +- .../include/clipper2/clipper.export.h | 1210 +-- .../Clipper2Lib/include/clipper2/clipper.h | 796 +- .../include/clipper2/clipper.minkowski.h | 43 +- .../include/clipper2/clipper.offset.h | 96 +- .../include/clipper2/clipper.rectclip.h | 74 +- .../include/clipper2/clipper.version.h | 6 + .../Clipper2Lib/src/clipper.engine.cpp | 6629 ++++++++--------- .../Clipper2Lib/src/clipper.offset.cpp | 745 +- .../Clipper2Lib/src/clipper.rectclip.cpp | 826 +- src/imgui/imconfig.h | 2 + src/imgui/imgui_widgets.cpp | 4 +- src/imgui/imstb_truetype.h | 2 + src/imguizmo/CMakeLists.txt | 9 + src/imguizmo/ImGuizmo.cpp | 3148 ++++++++ src/imguizmo/ImGuizmo.h | 295 + src/imguizmo/LICENSE | 21 + src/imguizmo/README.md | 192 + .../include/libnest2d/geometry_traits.hpp | 2 +- src/libnest2d/include/libnest2d/nester.hpp | 9 +- .../include/libnest2d/placers/nfpplacer.hpp | 199 +- .../include/libnest2d/selections/firstfit.hpp | 98 +- .../include/libnest2d/utils/boost_alg.hpp | 11 +- .../include/libnest2d/utils/rotcalipers.hpp | 2 + src/libnest2d/tools/svgtools.hpp | 18 +- src/mcut/include/mcut/internal/frontend.h | 1 + src/platform/osx/Info.plist.in | 10 +- 35 files changed, 11268 insertions(+), 6351 deletions(-) create mode 100644 src/clipper2/Clipper2Lib/include/clipper2/clipper.version.h create mode 100644 src/imguizmo/CMakeLists.txt create mode 100644 src/imguizmo/ImGuizmo.cpp create mode 100644 src/imguizmo/ImGuizmo.h create mode 100644 src/imguizmo/LICENSE create mode 100644 src/imguizmo/README.md diff --git a/CMakeLists.txt b/CMakeLists.txt index ee79eaa..58fd2b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -504,7 +504,7 @@ find_package(cereal REQUIRED) set(L10N_DIR "${SLIC3R_RESOURCES_DIR}/i18n") set(QDT_L18N_DIR "${CMAKE_CURRENT_SOURCE_DIR}/qdt/i18n") add_custom_target(gettext_make_pot - COMMAND xgettext --keyword=L --keyword=_L --keyword=_u8L --keyword=L_CONTEXT:1,2c --keyword=_L_PLURAL:1,2 --add-comments=TRN --from-code=UTF-8 --no-location --debug --boost + COMMAND xgettext --keyword=L --no-wrap --keyword=_L --keyword=_u8L --keyword=L_CONTEXT:1,2c --keyword=_L_PLURAL:1,2 --add-comments=TRN --from-code=UTF-8 --no-location --debug --boost -f "${QDT_L18N_DIR}/list.txt" -o "${QDT_L18N_DIR}/QIDIStudio.pot" COMMAND hintsToPot ${SLIC3R_RESOURCES_DIR} ${QDT_L18N_DIR} @@ -521,7 +521,7 @@ foreach(po_file ${QDT_L10N_PO_FILES}) SET(po_new_file "${po_dir}/QIDIStudio_.po") add_custom_command( TARGET gettext_merge_po_with_pot PRE_BUILD - COMMAND msgmerge -N -o ${po_file} ${po_file} "${QDT_L18N_DIR}/QIDIStudio.pot" + COMMAND msgmerge --no-wrap -N -o ${po_file} ${po_file} "${QDT_L18N_DIR}/QIDIStudio.pot" DEPENDS ${po_file} ) endforeach() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 90c4633..d9acc4c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -25,6 +25,7 @@ add_subdirectory(libslic3r) if (SLIC3R_GUI) add_subdirectory(imgui) + add_subdirectory(imguizmo) add_subdirectory(hidapi) include_directories(hidapi/include) diff --git a/src/QIDIStudio.cpp b/src/QIDIStudio.cpp index 95dbb5d..1fc5bc8 100644 --- a/src/QIDIStudio.cpp +++ b/src/QIDIStudio.cpp @@ -144,8 +144,12 @@ std::map cli_errors = { {CLI_OBJECT_COLLISION_IN_SEQ_PRINT, "Object conflicts were detected when using print-by-object mode. Please verify the slicing of all plates in QIDI Studio before uploading."}, {CLI_OBJECT_COLLISION_IN_LAYER_PRINT, "Object conflicts were detected. Please verify the slicing of all plates in QIDI Studio before uploading."}, {CLI_SPIRAL_MODE_INVALID_PARAMS, "Some slicing parameters cannot work with Spiral Vase mode. Please solve the issue in QIDI Studio before uploading."}, + {CLI_FILAMENT_CAN_NOT_MAP, "Some filaments cannot be mapped to correct extruders for multi-extruder Printer."}, + {CLI_ONLY_ONE_TPU_SUPPORTED, "Not support printing 2 or more TPU filaments."}, + {CLI_FILAMENTS_NOT_SUPPORTED_BY_EXTRUDER, "Some filaments cannot be printed on the extruder mapped to."}, {CLI_SLICING_ERROR, "Failed slicing the model. Please verify the slicing of all plates on QIDI Studio before uploading."}, {CLI_GCODE_PATH_CONFLICTS, " G-code conflicts detected after slicing. Please make sure the 3mf file can be successfully sliced in the latest QIDI Studio."}, + {CLI_GCODE_PATH_IN_UNPRINTABLE_AREA, "Found G-code in unprintable area of multi-extruder printers after slicing. Please make sure the 3mf file can be successfully sliced in the latest QIDI Studio."}, {CLI_FILAMENT_UNPRINTABLE_ON_FIRST_LAYER, "Found some filament unprintable at first layer on current Plate. Please make sure the 3mf file can be successfully sliced with the same Plate type in the latest QIDI Studio."} }; @@ -172,13 +176,12 @@ typedef struct _sliced_plate_info{ int plate_id{0}; size_t sliced_time {0}; size_t sliced_time_with_cache {0}; - //1.9.5 size_t make_perimeters_time {0}; size_t infill_time {0}; size_t generate_support_material_time {0}; size_t triangle_count{0}; std::string warning_message; - + float total_predication{0.f}; float main_predication{0.f}; int filament_change_times {0}; @@ -869,7 +872,6 @@ void merge_or_add_object(assemble_plate_info_t& assemble_plate_info, Model &mode } } -//1.9.5 bool convert_obj_cluster_colors(std::vector& input_colors, std::vector& all_colours, int max_filament_count, std::vector& output_filament_ids, int& first_filament_id) { using namespace Slic3r::GUI; @@ -880,7 +882,7 @@ bool convert_obj_cluster_colors(std::vector& input_colors, std::ve std::vector cluster_labels; char cluster_number = -1; - obj_color_deal_algo(input_colors, cluster_colors, cluster_labels, cluster_number); + obj_color_deal_algo(input_colors, cluster_colors, cluster_labels, cluster_number, (int)EnforcerBlockerType::ExtruderMax); std::vector cluster_color_maps; BOOST_LOG_TRIVIAL(info) << boost::format("%1%:%2%, after obj_color_deal_algo, cluster_colors size %3%, all_colours size %4%, max_filament_count=%5%")%__FUNCTION__ %__LINE__%cluster_colors.size() %all_colours.size() %max_filament_count; @@ -943,14 +945,12 @@ bool convert_obj_cluster_colors(std::vector& input_colors, std::ve #else #define DIR_SEPARATOR '/' #endif -//1.9.5 -static int construct_assemble_list(std::vector& assemble_plate_info_list, Model& model, PlateDataPtrs& plate_list, std::vector& all_colours) +static int construct_assemble_list(std::vector &assemble_plate_info_list, Model &model, PlateDataPtrs &plate_list, std::vector& all_colours) { int ret = 0; int plate_count = assemble_plate_info_list.size(); ConfigSubstitutionContext config_substitutions(ForwardCompatibilitySubstitutionRule::Enable); Model temp_model; - //1.9.5 const int max_filament_count = size_t(EnforcerBlockerType::ExtruderMax); plate_list.resize(plate_count); @@ -985,7 +985,6 @@ static int construct_assemble_list(std::vector& assemble_ { assemble_object_info_t& assemble_object = assemble_plate_info.assemble_obj_list[obj_index]; std::string object_name; - //1.9.5 std::string object_1_name; ModelObject* object = nullptr; TriangleMesh mesh; @@ -1011,7 +1010,6 @@ static int construct_assemble_list(std::vector& assemble_ BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": found no mesh data from stl file %1%, plate index %2%, object index %3%") % assemble_object.path % (index + 1) % (obj_index + 1); return CLI_DATA_FILE_ERROR; } - //1.9.5 object_name.erase(object_name.end() - 4, object_name.end()); object_1_name = object_name + "_1"; object = temp_model.add_object(object_1_name.c_str(), path_str, std::move(mesh)); @@ -1029,7 +1027,21 @@ static int construct_assemble_list(std::vector& assemble_ BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": failed to read a valid mesh from obj file %1%, plate index %2%, object index %3%, error %4%") % assemble_object.path % (index + 1) % (obj_index + 1) % message; return CLI_DATA_FILE_ERROR; } - //1.9.5 + if (obj_info.lost_material_name != "") { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": failed to read a valid mesh from obj file %1%, plate index %2%, object index %3%, mtl lost material: %4% ,please check mtl file") % + assemble_object.path % (index + 1) % (obj_index + 1) % obj_info.lost_material_name; + return CLI_DATA_FILE_ERROR; + } + if (obj_info.face_colors.size() > 0) { + auto temp0 = obj_info.face_colors.size(); + auto temp1 = mesh.facets_count(); + bool some_face_no_color = temp0 < temp1; + if (some_face_no_color) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< boost::format(": failed to read a valid mesh from obj file %1%, plate index %2%, object index %3%, error:some_face_no_color,please check mtl file and obj file.") % + assemble_object.path % (index + 1) % (obj_index + 1); + return CLI_DATA_FILE_ERROR; + } + } object_name.erase(object_name.end() - 4, object_name.end()); object_1_name = object_name + "_1"; //process colors @@ -1073,7 +1085,7 @@ static int construct_assemble_list(std::vector& assemble_ BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": unsupported file %1%, plate index %2%, object index %3%, subtype %4%") % assemble_object.path % (index + 1) % (obj_index + 1) %(int)(assemble_object.subtype); return CLI_INVALID_PARAMS; } - //1.9.5 + if (!skip_filament) { object->config.set_key_value("extruder", new ConfigOptionInt(assemble_object.filaments[0])); used_filaments.emplace(assemble_object.filaments[0]); @@ -1085,6 +1097,7 @@ static int construct_assemble_list(std::vector& assemble_ used_filaments.insert(volume_extruders.begin(), volume_extruders.end()); } } + if (!assemble_object.print_params.empty()) { for (auto param_iter = assemble_object.print_params.begin(); param_iter != assemble_object.print_params.end(); param_iter++) @@ -1134,6 +1147,7 @@ static int construct_assemble_list(std::vector& assemble_ object->translate(assemble_object.pos_x[0], assemble_object.pos_y[0], assemble_object.pos_z[0]); merge_or_add_object(assemble_plate_info, model, assemble_object.assemble_index[0], merged_objects, object, assemble_object.subtype); + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": object %1%, name %2%, pos_x %3% pos_y %4%, pos_z %5%, filament %6%, assemble_index %7%") %obj_index %object->name %assemble_object.pos_x[0] %assemble_object.pos_y[0] %assemble_object.pos_z[0] %assemble_object.filaments[0] %assemble_object.assemble_index[0]; @@ -1152,7 +1166,7 @@ static int construct_assemble_list(std::vector& assemble_ array_index = copy_index; else array_index = 0; - //1.9.5 + if (!skip_filament) { copy_obj->config.set_key_value("extruder", new ConfigOptionInt(assemble_object.filaments[array_index])); used_filaments.emplace(assemble_object.filaments[array_index]); @@ -1259,22 +1273,22 @@ static void load_downward_settings_list_from_config(std::string config_file, std json downward_check_json = printer_model_json["downward_check"]; if (downward_check_json.contains(printer_name)) { downward_settings = downward_check_json[printer_name].get>(); - BOOST_LOG_TRIVIAL(info) << boost::format("got %1% downward settings of %2% in cli_config.json")%downward_settings.size() %printer_name; + BOOST_LOG_TRIVIAL(info) << boost::format("got %1% downward settings of %2% in %3%")%downward_settings.size() %printer_name %config_file; } else { - BOOST_LOG_TRIVIAL(info) << boost::format("can not find %1% in downward_check of %2% in cli_config.json")%printer_name %printer_model; + BOOST_LOG_TRIVIAL(info) << boost::format("can not find %1% in downward_check of %2% in %3%")%printer_name %printer_model %config_file; } } else { - BOOST_LOG_TRIVIAL(info) << boost::format("can not find downward_check for %1% in cli_config.json")%printer_model; + BOOST_LOG_TRIVIAL(info) << boost::format("can not find downward_check for %1% in %2%")%printer_model%config_file; } } else { - BOOST_LOG_TRIVIAL(info) << boost::format("can not find printer_model %1% in the file")%printer_model; + BOOST_LOG_TRIVIAL(info) << boost::format("can not find printer_model %1% in %2%")%printer_model%config_file; } } else { - BOOST_LOG_TRIVIAL(warning) << boost::format("can not find key printer in the file"); + BOOST_LOG_TRIVIAL(warning) << boost::format("can not find key printer in %1%")%config_file; } } catch (std::exception &err) { @@ -1320,31 +1334,30 @@ int CLI::run(int argc, char **argv) } /*BOOST_LOG_TRIVIAL(info) << "begin to setup params, argc=" << argc << std::endl; - for (int index=0; index < argc; index++) - BOOST_LOG_TRIVIAL(info) << "index="<< index <<", arg is "<< argv[index] <setup(debug_argc, debug_argv))*/ + "--filament-colour", + "#FF00FFFF;#F4EE2AFF;#FFFFFFFF;#F99963FF;#F99963FF;#FF0000FF", + "--nozzle-volume-type", + "Standard,Standard", + "--filament-map-mode", + "Auto For Flush", + "--slice=1", + "--min-save", + "h2d_filament_map_test.3mf" + }; + if (!this->setup(debug_argc, debug_argv))*/ if (!this->setup(argc, argv)) { boost::nowide::cerr << "setup params error" << std::endl; @@ -1368,11 +1381,11 @@ int CLI::run(int argc, char **argv) boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer"); #endif // _WIN32*/ - bool translate_old = false, regenerate_thumbnails = false, filament_color_changed = false, downward_check = false; + bool translate_old = false, regenerate_thumbnails = false, keep_old_params = false, filament_color_changed = false, downward_check = false; int current_printable_width, current_printable_depth, current_printable_height, shrink_to_new_bed = 0; int old_printable_height = 0, old_printable_width = 0, old_printable_depth = 0; Pointfs old_printable_area, old_exclude_area; - float old_max_radius = 0.f, old_height_to_rod = 0.f, old_height_to_lid = 0.f; + float old_max_radius = 0.f, old_height_to_rod = 0.f, old_height_to_lid = 0.f, old_filament_prime_volume = 0.f; std::vector old_max_layer_height, old_min_layer_height; std::string outfile_dir = m_config.opt_string("outputdir", true); const std::vector &load_configs = m_config.option("load_settings", true)->values; @@ -1460,7 +1473,7 @@ int CLI::run(int argc, char **argv) set_logging_level(2); } } - //1.9.5 + global_begin_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); BOOST_LOG_TRIVIAL(warning) << boost::format("cli mode, Current QIDIStudio Version %1%")%SLIC3R_VERSION; @@ -1468,15 +1481,15 @@ int CLI::run(int argc, char **argv) PlateDataPtrs plate_data_src; std::vector plate_obj_size_infos; //int arrange_option; - int plate_to_slice = 0, filament_count = 0, duplicate_count = 0, real_duplicate_count = 0; + int plate_to_slice = 0, filament_count = 0, duplicate_count = 0, real_duplicate_count = 0, current_extruder_count = 1, new_extruder_count = 1; bool first_file = true, is_qdt_3mf = false, need_arrange = true, has_thumbnails = false, up_config_to_date = false, normative_check = true, duplicate_single_object = false, use_first_fila_as_default = false, minimum_save = false, enable_timelapse = false; - bool allow_rotations = true, skip_modified_gcodes = false, avoid_extrusion_cali_region = false, skip_useless_pick = false, allow_newer_file = false; + bool allow_rotations = true, skip_modified_gcodes = false, avoid_extrusion_cali_region = false, skip_useless_pick = false, allow_newer_file = false, current_is_multi_extruder = false, new_is_multi_extruder = false, allow_mix_temp = false; Semver file_version; std::map orients_requirement; std::vector project_presets; - std::string new_printer_name, current_printer_name, new_process_name, current_process_name, current_printer_system_name, current_process_system_name, new_process_system_name, new_printer_system_name, printer_model_id, current_printer_model, printer_model;//, printer_inherits, print_inherits; + std::string new_printer_name, current_printer_name, new_process_name, current_process_name, current_printer_system_name, current_process_system_name, new_process_system_name, new_printer_system_name, printer_model_id, current_printer_model, printer_model, new_default_process_name; std::vector upward_compatible_printers, new_print_compatible_printers, current_print_compatible_printers, current_different_settings; - std::vector current_filaments_name, current_filaments_system_name, current_inherits_group; + std::vector current_filaments_name, current_filaments_system_name, current_inherits_group, current_extruder_variants; DynamicPrintConfig load_process_config, load_machine_config; bool new_process_config_is_system = true, new_printer_config_is_system = true; std::string pipe_name, makerlab_name, makerlab_version, different_process_setting; @@ -1532,6 +1545,10 @@ int CLI::run(int argc, char **argv) if (allow_newer_file_option) allow_newer_file = allow_newer_file_option->value; + ConfigOptionBool* allow_mix_temp_option = m_config.option("allow_mix_temp"); + if (allow_mix_temp_option) + allow_mix_temp = allow_mix_temp_option->value; + ConfigOptionBool* avoid_extrusion_cali_region_option = m_config.option("avoid_extrusion_cali_region"); if (avoid_extrusion_cali_region_option) avoid_extrusion_cali_region = avoid_extrusion_cali_region_option->value; @@ -1583,8 +1600,8 @@ int CLI::run(int argc, char **argv) const std::vector clone_objects = m_config.option("clone_objects", true)->values; //when load objects from stl/obj, the total used filaments set std::set used_filament_set; - BOOST_LOG_TRIVIAL(info) << boost::format("allow_multicolor_oneplate %1%, allow_rotations %2% skip_modified_gcodes %3% avoid_extrusion_cali_region %4% loaded_filament_ids size %5%, clone_objects size %6%, skip_useless_pick %7%, allow_newer_file %8%") - %allow_multicolor_oneplate %allow_rotations %skip_modified_gcodes %avoid_extrusion_cali_region %loaded_filament_ids.size() %clone_objects.size() %skip_useless_pick %allow_newer_file; + BOOST_LOG_TRIVIAL(info) << boost::format("allow_multicolor_oneplate %1%, allow_rotations %2% skip_modified_gcodes %3% avoid_extrusion_cali_region %4% loaded_filament_ids size %5%, clone_objects size %6%, skip_useless_pick %7%, allow_newer_file %8%, allow_mix_temp %9%") + %allow_multicolor_oneplate %allow_rotations %skip_modified_gcodes %avoid_extrusion_cali_region %loaded_filament_ids.size() %clone_objects.size() %skip_useless_pick %allow_newer_file %allow_mix_temp; if (clone_objects.size() > 0) { if (clone_objects.size() != m_input_files.size()) @@ -1624,7 +1641,6 @@ int CLI::run(int argc, char **argv) }*/ BOOST_LOG_TRIVIAL(info) << boost::format("plate_to_slice=%1%, normative_check=%2%, use_first_fila_as_default=%3%")%plate_to_slice %normative_check %use_first_fila_as_default; unsigned int input_index = 0; - //1.9.5 std::vector input_obj_colours; if (!load_assemble_list.empty() && (m_input_files.size() > 0)) { @@ -1685,12 +1701,12 @@ int CLI::run(int argc, char **argv) }*/ Semver cli_ver = *Semver::parse(SLIC3R_VERSION); - if (!allow_newer_file && ((cli_ver.maj() != file_version.maj()) || (cli_ver.min() < file_version.min()))){ + if (!allow_newer_file && ((cli_ver.maj() < file_version.maj()) || ((cli_ver.maj() == file_version.maj()) && (cli_ver.min() < file_version.min())))){ BOOST_LOG_TRIVIAL(error) << boost::format("Version Check: File Version %1% not supported by current cli version %2%")%file_version.to_string() %SLIC3R_VERSION; record_exit_reson(outfile_dir, CLI_FILE_VERSION_NOT_SUPPORTED, 0, cli_errors[CLI_FILE_VERSION_NOT_SUPPORTED], sliced_info); flush_and_exit(CLI_FILE_VERSION_NOT_SUPPORTED); } - Semver old_version(1, 5, 9), old_version2(1, 5, 9); + Semver old_version(1, 5, 9), old_version2(1, 5, 9), old_version3(2, 0, 0); if ((file_version < old_version) && !config.empty()) { translate_old = true; BOOST_LOG_TRIVIAL(info) << boost::format("old 3mf version %1%, need to translate")%file_version.to_string(); @@ -1699,6 +1715,21 @@ int CLI::run(int argc, char **argv) regenerate_thumbnails = true; BOOST_LOG_TRIVIAL(info) << boost::format("old 3mf version %1%, need to regenerate_thumbnails for all")%file_version.to_string(); } + if ((file_version < old_version3) && !config.empty()) { + keep_old_params = true; + ConfigOptionFloats *filament_prime_volume_option = config.option("filament_prime_volume"); + if (filament_prime_volume_option) { + std::vector& filament_prime_volume_values = filament_prime_volume_option->values; + if (!filament_prime_volume_values.empty()) { + old_filament_prime_volume = filament_prime_volume_values[0]; + ConfigOptionStrings* filament_colors_option = config.option("filament_colour", true); + if (filament_colors_option->size() > 1) + filament_prime_volume_values.resize(filament_colors_option->size(), old_filament_prime_volume); + } + } + + BOOST_LOG_TRIVIAL(info) << boost::format("old 3mf version %1%, need to keep old params")%file_version.to_string(); + } if (normative_check) { ConfigOptionStrings* postprocess_scripts = config.option("post_process"); @@ -1722,6 +1753,19 @@ int CLI::run(int argc, char **argv) current_process_name = config.option("print_settings_id")->value; current_printer_model = config.option("printer_model", true)->value; current_filaments_name = config.option("filament_settings_id")->values; + current_extruder_count = config.option("nozzle_diameter")->values.size(); + current_is_multi_extruder = current_extruder_count > 1; + if (current_is_multi_extruder) { + auto opt_extruder_type = dynamic_cast(config.option("extruder_type")); + auto opt_nozzle_volume_type = dynamic_cast(config.option("nozzle_volume_type")); + current_extruder_variants.resize(current_extruder_count, ""); + for (int e_index = 0; e_index < current_extruder_count; e_index++) + { + ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(e_index)); + NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(e_index)); + current_extruder_variants[e_index] = get_extruder_variant_string(extruder_type, nozzle_volume_type); + } + } BOOST_LOG_TRIVIAL(info) << boost::format("current_printer_name %1%, current_process_name %2%")%current_printer_name %current_process_name; ConfigOptionStrings* option_strings = config.option("inherits_group"); @@ -1782,10 +1826,10 @@ int CLI::run(int argc, char **argv) old_height_to_lid = config.opt_float("extruder_clearance_height_to_lid"); if (config.option("extruder_clearance_max_radius")) old_max_radius = config.opt_float("extruder_clearance_max_radius"); - if (config.option("max_layer_height")) - old_max_layer_height = config.option("max_layer_height")->values; - if (config.option("min_layer_height")) - old_min_layer_height = config.option("min_layer_height")->values; + if (config.option("max_layer_height")) + old_max_layer_height = config.option("max_layer_height")->values; + if (config.option("min_layer_height")) + old_min_layer_height = config.option("min_layer_height")->values; BOOST_LOG_TRIVIAL(info) << boost::format("old printable size from 3mf: {%1%, %2%, %3%}")%old_printable_width %old_printable_depth %old_printable_height; BOOST_LOG_TRIVIAL(info) << boost::format("old extruder_clearance_height_to_rod %1%, extruder_clearance_height_to_lid %2%, extruder_clearance_max_radius %3%}")%old_height_to_rod %old_height_to_lid %old_max_radius; } @@ -1891,7 +1935,6 @@ int CLI::run(int argc, char **argv) } try { - //1.9.5 ret = construct_assemble_list(assemble_plate_info_list, model, plate_data_src, input_obj_colours); if (ret) { record_exit_reson(outfile_dir, ret, 0, cli_errors[ret], sliced_info); @@ -2060,6 +2103,9 @@ int CLI::run(int argc, char **argv) } } } + //new_default_process_name + if (config.option("default_print_profile")) + new_default_process_name = config.option("default_print_profile")->value; //printer_inherits = config.option("inherits", true)->value; load_machine_config = std::move(config); @@ -2209,7 +2255,7 @@ int CLI::run(int argc, char **argv) continue; } } - //1.9.5 + //add logic for obj auto colors if (input_obj_colours.size() > 0) { BOOST_LOG_TRIVIAL(info) << boost::format("%1%:%2%, got input obj colors %3%")%__FUNCTION__ %__LINE__ %input_obj_colours.size(); @@ -2431,14 +2477,12 @@ int CLI::run(int argc, char **argv) { if (uptodate_filaments.size() > 0) { - //1.9.5 if (uptodate_filaments.size() != (size_t)filament_count) { BOOST_LOG_TRIVIAL(error) << boost::format("uptodate_filaments size %1% not equal to filament_count %2% ")%uptodate_filaments.size() %filament_count; record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, 0, cli_errors[CLI_INVALID_PARAMS], sliced_info); flush_and_exit(CLI_INVALID_PARAMS); } - //1.9.5 for (int index = 0; index < filament_count; index ++) { std::string file = uptodate_filaments[index]; @@ -2619,7 +2663,8 @@ int CLI::run(int argc, char **argv) } if (!process_compatible && !new_printer_name.empty() && !current_printer_name.empty() && (new_printer_name != current_printer_name)) { //set all printer to compatible - process_compatible = true; + if (new_process_name.empty()) + process_compatible = true; machine_switch = true; BOOST_LOG_TRIVIAL(info) << boost::format("switch to new printers, set to compatible"); if (upward_compatible_printers.size() > 0) { @@ -2869,6 +2914,10 @@ int CLI::run(int argc, char **argv) flush_and_exit(ret); } } + if (m_print_config.option("nozzle_diameter")) { + new_extruder_count = m_print_config.option("nozzle_diameter")->values.size(); + new_is_multi_extruder = new_extruder_count > 1; + } //set the process settings into print config std::vector& print_compatible_printers = m_print_config.option("print_compatible_printers", true)->values; @@ -2910,7 +2959,7 @@ int CLI::run(int argc, char **argv) int ret; - load_default_gcodes_to_config(load_machine_config, Preset::TYPE_PRINT); + load_default_gcodes_to_config(load_process_config, Preset::TYPE_PRINT); if (new_process_name.empty()) { int diff_keys_size = different_keys_set.size(); ret = update_full_config(m_print_config, load_process_config, different_keys_set, false, skip_modified_gcodes); @@ -2955,11 +3004,72 @@ int CLI::run(int argc, char **argv) if (need_insert) different_settings[0] = old_setting + ";compatible_printers"; } + + //add multi-extruder related logic + if (new_process_name.empty() && (current_is_multi_extruder != new_is_multi_extruder)) { + if (new_default_process_name.empty()) { + BOOST_LOG_TRIVIAL(error) << boost::format("machine_switch: default process configis null, should not happen, new_printer_name %1%")%new_printer_name; + record_exit_reson(outfile_dir, CLI_CONFIG_FILE_ERROR, 0, cli_errors[CLI_CONFIG_FILE_ERROR], sliced_info); + flush_and_exit(CLI_CONFIG_FILE_ERROR); + } + else { + std::string default_path = resources_dir() + "/profiles/QDT/process_full/"; + std::string file_path = default_path + new_default_process_name + ".json"; + DynamicPrintConfig config; + std::string config_type, config_name, filament_id, config_from, downward_printer; + int ret = load_config_file(file_path, config, config_type, config_name, filament_id, config_from); + if (ret) { + record_exit_reson(outfile_dir, ret, 0, cli_errors[ret], sliced_info); + flush_and_exit(ret); + } + if ((config_type != "process") || (config_from != "system")) { + BOOST_LOG_TRIVIAL(error) << boost::format("found invalid config type %1% or from %2% in file %3% when downward_check")%config_type %config_from %file_path; + record_exit_reson(outfile_dir, CLI_CONFIG_FILE_ERROR, 0, cli_errors[CLI_CONFIG_FILE_ERROR], sliced_info); + flush_and_exit(CLI_CONFIG_FILE_ERROR); + } + BOOST_LOG_TRIVIAL(info) << boost::format("machine_switch: loaded default process config %1%, from %2%")%config_name %file_path ; + + if (!current_is_multi_extruder && new_is_multi_extruder) { + //single -> multiple + ret = m_print_config.update_values_from_single_to_multi(config, print_options_with_variant, "print_extruder_id", "print_extruder_variant"); + } + else if (current_is_multi_extruder && !new_is_multi_extruder) { + //multiple -> single + ret = m_print_config.update_values_from_multi_to_single(config, print_options_with_variant, "print_extruder_id", "print_extruder_variant", current_extruder_variants); + } + + if (ret) { + BOOST_LOG_TRIVIAL(error) << boost::format("current_is_multi_extruder %1% new_is_multi_extruder %2%, update_values failed") % current_is_multi_extruder % new_is_multi_extruder; + record_exit_reson(outfile_dir, CLI_CONFIG_FILE_ERROR, 0, cli_errors[CLI_CONFIG_FILE_ERROR], sliced_info); + flush_and_exit(CLI_CONFIG_FILE_ERROR); + } + } + } } //set the filament settings into print config if ((load_filament_count > 0) || (up_config_to_date)) { + std::vector filament_variant_count(filament_count, 1); + std::vector old_start_index(filament_count, 0); + std::vector old_variant_count(filament_count, 1); + if (current_is_multi_extruder) { + std::vector& old_self_indice = m_print_config.option("filament_self_index", true)->values; + int old_self_indice_size = old_self_indice.size(); + int k = -1, current_filament = 0; + for (size_t i = 0; i < old_self_indice_size; i++) { + if (old_self_indice[i] > current_filament) { + current_filament = old_self_indice[i]; + old_start_index[++k] = i; + old_variant_count[k] = 1; + } + else { + old_variant_count[k] = old_variant_count[k] + 1; + } + } + if (load_filament_count == 0) + filament_variant_count = old_variant_count; + } for (int index = 0; index < load_filaments_config.size(); index++) { DynamicPrintConfig& config = load_filaments_config[index]; int filament_index = load_filaments_index[index]; @@ -2976,13 +3086,6 @@ int CLI::run(int argc, char **argv) opt_filament_settings->set_at(filament_name_setting, filament_index-1, 0); config.erase("filament_settings_id"); - std::string& filament_id = load_filaments_id[index]; - ConfigOptionStrings *opt_filament_ids = static_cast (m_print_config.option("filament_ids", true)); - ConfigOptionString* filament_id_setting = new ConfigOptionString(filament_id); - if (opt_filament_ids->size() < filament_count) - opt_filament_ids->resize(filament_count, filament_id_setting); - opt_filament_ids->set_at(filament_id_setting, filament_index-1, 0); - //todo: update different settings of filaments different_settings[filament_index] = ""; inherits_group[filament_index] = load_filaments_inherit[index]; @@ -2996,6 +3099,14 @@ int CLI::run(int argc, char **argv) } } + //add filament_id + std::string& filament_id = load_filaments_id[index]; + ConfigOptionStrings *opt_filament_ids = static_cast (m_print_config.option("filament_ids", true)); + ConfigOptionString* filament_id_setting = new ConfigOptionString(filament_id); + if (opt_filament_ids->size() < filament_count) + opt_filament_ids->resize(filament_count, filament_id_setting); + opt_filament_ids->set_at(filament_id_setting, filament_index-1, 0); + //parse the filament value to index th //loop through options and apply them std::set different_keys_set(different_keys.begin(), different_keys.end()); @@ -3065,7 +3176,30 @@ int CLI::run(int argc, char **argv) } ConfigOptionVectorBase* opt_vec_dst = static_cast(opt); const ConfigOptionVectorBase* opt_vec_src = static_cast(source_opt); - opt_vec_dst->set_at(opt_vec_src, filament_index-1, 0); + if (new_is_multi_extruder && (filament_options_with_variant.find(opt_key) != filament_options_with_variant.end())) { + if (load_filament_count > 0) { + if (filament_index == 1) + opt_vec_dst->set(opt_vec_src); + else + opt_vec_dst->append(opt_vec_src); + + if (opt_key == "filament_extruder_variant") + filament_variant_count[filament_index - 1] = opt_vec_src->size(); + } + else { + //only update + for (size_t i = 0; i < old_variant_count[filament_index-1]; i++) { + opt_vec_dst->set_at(opt_vec_src, old_start_index[filament_index - 1] + i, i); + } + } + } + else { + if (current_is_multi_extruder && !new_is_multi_extruder && filament_options_with_variant.find(opt_key) != filament_options_with_variant.end()) { + if (opt_vec_dst->size() > filament_count) + opt_vec_dst->resize(filament_count); + } + opt_vec_dst->set_at(opt_vec_src, filament_index - 1, 0); + } } } @@ -3079,6 +3213,18 @@ int CLI::run(int argc, char **argv) BOOST_LOG_TRIVIAL(info) << boost::format("filament %1% new different key size %2%, different_settings %3%")%filament_index %different_keys_set.size() %different_settings[filament_index]; } } + + if (m_print_config.option("filament_extruder_variant")) { + std::vector& filament_self_indice = m_print_config.option("filament_self_index", true)->values; + int index_size = m_print_config.option("filament_extruder_variant")->size(); + filament_self_indice.resize(index_size, 1); + int k = 0; + for (size_t i = 0; i < filament_count; i++) { + for (size_t j = 0; j < filament_variant_count[i]; j++) { + filament_self_indice[k++] = i + 1; + } + } + } } //compute the flush volume @@ -3091,7 +3237,7 @@ int CLI::run(int argc, char **argv) std::vector& project_filament_colors = project_filament_colors_option->values; project_filament_colors.resize(filament_count, "#FFFFFF"); } - if (project_filament_colors_option && (selected_filament_colors_option || !m_print_config.option("flush_volumes_matrix"))) + if (project_filament_colors_option && (selected_filament_colors_option || !m_print_config.option("flush_volumes_matrix") || (new_extruder_count != current_extruder_count))) { std::vector selected_filament_colors; if (selected_filament_colors_option) { @@ -3151,14 +3297,8 @@ int CLI::run(int argc, char **argv) //computing ConfigOptionBools* filament_is_support = m_print_config.option("filament_is_support", true); - std::vector& flush_vol_matrix = m_print_config.option("flush_volumes_matrix", true)->values; - //std::vector& flush_vol_vector = m_print_config.option("flush_volumes_vector", true)->values; - flush_vol_matrix.resize(project_filament_count*project_filament_count, 0.f); - //flush_vol_vector.resize(project_filament_count); - //set multiplier to 1? - m_print_config.option("flush_multiplier", true)->set(new ConfigOptionFloat(1.f)); - const std::vector& min_flush_volumes = Slic3r::GUI::get_min_flush_volumes(m_print_config); + const std::vector &min_flush_volumes = Slic3r::GUI::get_min_flush_volumes(m_print_config, 0); if (filament_is_support->size() != project_filament_count) { @@ -3172,42 +3312,59 @@ int CLI::run(int argc, char **argv) std::copy(min_flush_volumes.begin(), min_flush_volumes.end(), std::ostream_iterator(volumes_str, ",")); BOOST_LOG_TRIVIAL(info) << boost::format("extra_flush_volume: %1%") % volumes_str.str(); BOOST_LOG_TRIVIAL(info) << boost::format("filament_is_support: %1%") % filament_is_support->serialize(); - BOOST_LOG_TRIVIAL(info) << boost::format("flush_volumes_matrix before computing: %1%") % m_print_config.option("flush_volumes_matrix")->serialize(); + BOOST_LOG_TRIVIAL(info) << boost::format("flush_volumes_matrix before computing: %1%") % m_print_config.option("flush_volumes_matrix", true)->serialize(); } - for (int from_idx = 0; from_idx < project_filament_count; from_idx++) { - const std::string& from_color = project_filament_colors[from_idx]; - unsigned char from_rgb[4] = {}; - Slic3r::GUI::BitmapCache::parse_color4(from_color, from_rgb); - bool is_from_support = filament_is_support->get_at(from_idx); - for (int to_idx = 0; to_idx < project_filament_count; to_idx++) { - bool is_to_support = filament_is_support->get_at(to_idx); - if (from_idx == to_idx) { - flush_vol_matrix[project_filament_count*from_idx + to_idx] = 0.f; - } - else { - int flushing_volume = 0; - if (is_to_support) { - flushing_volume = Slic3r::g_flush_volume_to_support; - } - else { - const std::string& to_color = project_filament_colors[to_idx]; - unsigned char to_rgb[4] = {}; - Slic3r::GUI::BitmapCache::parse_color4(to_color, to_rgb); - //BOOST_LOG_TRIVIAL(info) << boost::format("src_idx %1%, src color %2%, dst idex %3%, dst color %4%")%from_idx %from_color %to_idx %to_color; - //BOOST_LOG_TRIVIAL(info) << boost::format("src_rgba {%1%,%2%,%3%,%4%} dst_rgba {%5%,%6%,%7%,%8%}")%(unsigned int)(from_rgb[0]) %(unsigned int)(from_rgb[1]) %(unsigned int)(from_rgb[2]) %(unsigned int)(from_rgb[3]) - // %(unsigned int)(to_rgb[0]) %(unsigned int)(to_rgb[1]) %(unsigned int)(to_rgb[2]) %(unsigned int)(to_rgb[3]); - Slic3r::FlushVolCalculator calculator(min_flush_volumes[from_idx], Slic3r::g_max_flush_volume); + std::vector &flush_vol_matrix = m_print_config.option("flush_volumes_matrix", true)->values; + flush_vol_matrix.resize(project_filament_count * project_filament_count * new_extruder_count, 0.f); - flushing_volume = calculator.calc_flush_vol(from_rgb[3], from_rgb[0], from_rgb[1], from_rgb[2], to_rgb[3], to_rgb[0], to_rgb[1], to_rgb[2]); - if (is_from_support) { - flushing_volume = std::max(Slic3r::g_min_flush_volume_from_support, flushing_volume); + // set multiplier to 1? + std::vector& flush_multipliers = m_print_config.option("flush_multiplier", true)->values; + flush_multipliers.resize(new_extruder_count, 1.f); + + ConfigOptionEnumsGeneric* nozzle_volume_opt = nullptr; + if (m_print_config.has("nozzle_volume_type")) + nozzle_volume_opt = m_print_config.option("nozzle_volume_type"); + if (m_extra_config.has("nozzle_volume_type")) + nozzle_volume_opt = m_extra_config.option("nozzle_volume_type"); + + std::vector volume_type_list; + if (nozzle_volume_opt) { + for (size_t idx = 0; idx < nozzle_volume_opt->values.size(); ++idx) { + volume_type_list.emplace_back(NozzleVolumeType(nozzle_volume_opt->values[idx])); + } + } + volume_type_list.resize(new_extruder_count, NozzleVolumeType::nvtStandard); + + for (size_t nozzle_id = 0; nozzle_id < new_extruder_count; ++nozzle_id) { + std::vector flush_vol_mtx = get_flush_volumes_matrix(flush_vol_matrix, nozzle_id, new_extruder_count); + for (int from_idx = 0; from_idx < project_filament_count; from_idx++) { + const std::string &from_color = project_filament_colors[from_idx]; + unsigned char from_rgb[4] = {}; + Slic3r::GUI::BitmapCache::parse_color4(from_color, from_rgb); + bool is_from_support = filament_is_support->get_at(from_idx); + for (int to_idx = 0; to_idx < project_filament_count; to_idx++) { + bool is_to_support = filament_is_support->get_at(to_idx); + if (from_idx == to_idx) { + flush_vol_mtx[project_filament_count * from_idx + to_idx] = 0.f; + } else { + int flushing_volume = 0; + if (is_to_support) { + flushing_volume = Slic3r::g_flush_volume_to_support; + } else { + const std::string &to_color = project_filament_colors[to_idx]; + unsigned char to_rgb[4] = {}; + Slic3r::GUI::BitmapCache::parse_color4(to_color, to_rgb); + + Slic3r::FlushVolCalculator calculator(min_flush_volumes[from_idx], Slic3r::g_max_flush_volume, new_extruder_count > 1, volume_type_list[nozzle_id]); + flushing_volume = calculator.calc_flush_vol(from_rgb[3], from_rgb[0], from_rgb[1], from_rgb[2], to_rgb[3], to_rgb[0], to_rgb[1], to_rgb[2]); + if (is_from_support) { flushing_volume = std::max(Slic3r::g_min_flush_volume_from_support, flushing_volume); } } - } - flush_vol_matrix[project_filament_count * from_idx + to_idx] = flushing_volume; - //flushing_volume = int(flushing_volume * get_flush_multiplier()); + flush_vol_mtx[project_filament_count * from_idx + to_idx] = flushing_volume; + } } + set_flush_volumes_matrix(flush_vol_matrix, flush_vol_mtx, nozzle_id, new_extruder_count); } } BOOST_LOG_TRIVIAL(info) << boost::format("flush_volumes_matrix after computed: %1%")%m_print_config.option("flush_volumes_matrix")->serialize(); @@ -3246,6 +3403,49 @@ int CLI::run(int argc, char **argv) m_models.emplace_back(std::move(m)); } + + //update the object config due to extruder count change + if ((machine_switch) && (new_extruder_count != current_extruder_count)) + { + //process the object params here + size_t num_objects = m_models[0].objects.size(); + for (int i = 0; i < num_objects; ++i) { + ModelObject* object = m_models[0].objects[i]; + DynamicPrintConfig object_config = object->config.get(); + if (!object_config.empty()) { + if (current_extruder_count < new_extruder_count) + object_config.update_values_from_single_to_multi_2(m_print_config, print_options_with_variant); + else + object_config.update_values_from_multi_to_single_2(print_options_with_variant); + object->config.assign_config(std::move(object_config)); + } + for (ModelVolume* v : object->volumes) { + if (v->is_model_part() || v->is_modifier()) { + DynamicPrintConfig volume_config = v->config.get(); + if (!volume_config.empty()) { + if (current_extruder_count < new_extruder_count) + volume_config.update_values_from_single_to_multi_2(m_print_config, print_options_with_variant); + else + volume_config.update_values_from_multi_to_single_2(print_options_with_variant); + v->config.assign_config(std::move(volume_config)); + } + } + } + + for (auto &layer_config_it : object->layer_config_ranges) { + ModelConfig& layer_model_config = layer_config_it.second; + DynamicPrintConfig layer_config = layer_model_config.get(); + if (!layer_config.empty()) { + if (current_extruder_count < new_extruder_count) + layer_config.update_values_from_single_to_multi_2(m_print_config, print_options_with_variant); + else + layer_config.update_values_from_multi_to_single_2(print_options_with_variant); + layer_model_config.assign_config(std::move(layer_config)); + } + } + } + } + //load custom gcodes into model if needed if ((custom_gcodes_map.size() > 0)&&(m_models.size() > 0)) { @@ -3361,14 +3561,22 @@ int CLI::run(int argc, char **argv) //use Pointfs insteadof Points Pointfs current_printable_area = m_print_config.opt("printable_area")->values; Pointfs current_exclude_area = m_print_config.opt("bed_exclude_area")->values; + std::vector current_extruder_areas; //update part plate's size double print_height = m_print_config.opt_float("printable_height"); + std::vector current_extruder_print_heights; double height_to_lid = m_print_config.opt_float("extruder_clearance_height_to_lid"); double height_to_rod = m_print_config.opt_float("extruder_clearance_height_to_rod"); double cleareance_radius = m_print_config.opt_float("extruder_clearance_max_radius"); //double plate_stride; std::string bed_texture; + if (m_print_config.opt("extruder_printable_area")) { + current_extruder_areas = m_print_config.opt("extruder_printable_area")->values; + } + if (m_print_config.opt("extruder_printable_height")) { + current_extruder_print_heights = m_print_config.opt("extruder_printable_height")->values; + } current_printable_width = current_printable_area[2].x() - current_printable_area[0].x(); current_printable_depth = current_printable_area[2].y() - current_printable_area[0].y(); current_printable_height = print_height; @@ -3419,10 +3627,85 @@ int CLI::run(int argc, char **argv) else { partplate_list.reset_size(old_printable_width, old_printable_depth, old_printable_height, false); } - partplate_list.set_shapes(current_printable_area, current_exclude_area, bed_texture, height_to_lid, height_to_rod); + partplate_list.set_shapes(current_printable_area, current_exclude_area, current_extruder_areas, current_extruder_print_heights, bed_texture, height_to_lid, height_to_rod); //plate_stride = partplate_list.plate_stride_x(); } + //process some old params + if (is_qdt_3mf && keep_old_params) { + std::vector different_keys; + Slic3r::unescape_strings_cstyle(different_settings[0], different_keys); + std::set different_key_set(different_keys.begin(), different_keys.end()); + BOOST_LOG_TRIVIAL(info) << boost::format("%1%, process old params for support and wipe tower")%__LINE__; + + //wipe tower params process + ConfigOptionBool *prime_tower_rib_wall_option = m_print_config.option("prime_tower_rib_wall", true); + prime_tower_rib_wall_option->value = false; + + ConfigOptionPercent *prime_tower_infill_gap_option = m_print_config.option("prime_tower_infill_gap", true); + prime_tower_infill_gap_option->value = 100; + + ConfigOptionInts *filament_adhesiveness_category_option = m_print_config.option("filament_adhesiveness_category", true); + std::vector& filament_adhesiveness_category_values = filament_adhesiveness_category_option->values; + filament_adhesiveness_category_values.resize(filament_count); + for (int index = 0; index < filament_count; index++) + filament_adhesiveness_category_values[index] = 100; + + ConfigOptionFloats *filament_prime_volume_option = m_print_config.option("filament_prime_volume", true); + std::vector& filament_prime_volume_values = filament_prime_volume_option->values; + filament_prime_volume_values.resize(filament_count); + for (int index = 0; index < filament_count; index++) { + if (old_filament_prime_volume != 0.f) + filament_prime_volume_values[index] = old_filament_prime_volume; + else + filament_prime_volume_values[index] = filament_prime_volume_values[0]; + } + + //support params process + ConfigOptionBool *enable_support_option = m_print_config.option("enable_support", true); + ConfigOptionEnum* support_type_option = m_print_config.option>("support_type", true); + ConfigOptionEnum* support_style_option = m_print_config.option>("support_style", true); + ConfigOptionFloat *support_top_z_distance_option = m_print_config.option("support_top_z_distance", true); + if (support_type_option->value == stTreeAuto) + { + if (different_key_set.find("support_type") == different_key_set.end()) + support_type_option->value = stNormalAuto; + } + + //traverse each object one by one + size_t num_objects = m_models[0].objects.size(); + for (int i = 0; i < num_objects; ++i) { + ModelObject* object = m_models[0].objects[i]; + DynamicPrintConfig object_config = object->config.get(); + ConfigOptionBool *obj_enable_support_option = object_config.option("enable_support"); + if (enable_support_option->value || (obj_enable_support_option && obj_enable_support_option->value)) { + ConfigOptionEnum* obj_support_type_option = object_config.option>("support_type"); + ConfigOptionEnum* obj_support_style_option = object_config.option>("support_style"); + ConfigOptionFloat *obj_support_top_z_distance_option = object_config.option("support_top_z_distance"); + + SupportType obj_support_type = obj_support_type_option? obj_support_type_option->value: support_type_option->value; + SupportMaterialStyle obj_support_style = obj_support_style_option? obj_support_style_option->value: support_style_option->value; + if ((obj_support_type == stTreeAuto) && (obj_support_style == smsDefault )) + { + float support_top_z_distance = obj_support_top_z_distance_option? obj_support_top_z_distance_option->value: support_top_z_distance_option->value; + if (!object->has_custom_layering() && (support_top_z_distance == 0)) { + obj_support_style_option = object_config.option>("support_style", true); + obj_support_style_option->value = smsTreeOrganic; + } + } + } + } + + //travel_acceleration + ConfigOptionFloatsNullable *travel_acceleration_option = m_print_config.option("travel_acceleration", true); + ConfigOptionFloatsNullable *default_acceleration_option = m_print_config.option("default_acceleration"); + travel_acceleration_option->values = default_acceleration_option->values; + + ConfigOptionFloatsNullable *initial_layer_travel_acceleration_option = m_print_config.option("initial_layer_travel_acceleration", true); + ConfigOptionFloatsNullable *initial_layer_acceleration_option = m_print_config.option("initial_layer_acceleration"); + initial_layer_travel_acceleration_option->values = initial_layer_acceleration_option->values; + } + auto get_print_sequence = [](Slic3r::GUI::PartPlate* plate, DynamicPrintConfig& print_config, bool &is_seq_print) { PrintSequence curr_plate_seq = plate->get_print_seq(); if (curr_plate_seq == PrintSequence::ByDefault) { @@ -3438,7 +3721,7 @@ int CLI::run(int argc, char **argv) } }; - auto check_plate_wipe_tower = [get_print_sequence, is_smooth_timelapse](Slic3r::GUI::PartPlate* plate, int plate_index, DynamicPrintConfig& print_config, plate_obj_size_info_t &plate_obj_size_info) { + auto check_plate_wipe_tower = [get_print_sequence, is_smooth_timelapse, new_extruder_count](Slic3r::GUI::PartPlate* plate, int plate_index, DynamicPrintConfig& print_config, plate_obj_size_info_t &plate_obj_size_info) { plate_obj_size_info.obj_bbox= plate->get_objects_bounding_box(); BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%, object bbox: min {%2%, %3%, %4%} - max {%5%, %6%, %7%}") %(plate_index+1) %plate_obj_size_info.obj_bbox.min.x() % plate_obj_size_info.obj_bbox.min.y() % plate_obj_size_info.obj_bbox.min.z() %plate_obj_size_info.obj_bbox.max.x() % plate_obj_size_info.obj_bbox.max.y() % plate_obj_size_info.obj_bbox.max.z(); @@ -3447,7 +3730,7 @@ int CLI::run(int argc, char **argv) BOOST_LOG_TRIVIAL(info) << boost::format("can not found wipe_tower_x in config, set to no wipe tower"); return; } - //1.9.5 + bool wipe_tower_enabled = false; if (print_config.has("enable_prime_tower")) { wipe_tower_enabled = print_config.option("enable_prime_tower")->value; @@ -3492,11 +3775,13 @@ int CLI::run(int argc, char **argv) ConfigOptionFloat* brim_width_option = print_config.option("prime_tower_brim_width", true); float brim_width = brim_width_option->value; + if (brim_width < 0) brim_width = WipeTower::get_auto_brim_by_height((float)plate_obj_size_info.obj_bbox.max.z()); - ConfigOptionFloat* volume_option = print_config.option("prime_volume", true); - float wipe_volume = volume_option->value; + ConfigOptionFloats* volume_option = print_config.option("filament_prime_volume", true); + std::vector wipe_volume = volume_option->values; - Vec3d wipe_tower_size = plate->estimate_wipe_tower_size(print_config, plate_obj_size_info.wipe_width, wipe_volume, filaments_cnt); + Vec3d wipe_tower_size = plate->estimate_wipe_tower_size(print_config, plate_obj_size_info.wipe_width, get_max_element(wipe_volume), new_extruder_count, filaments_cnt); + plate_obj_size_info.wipe_width = wipe_tower_size(0); plate_obj_size_info.wipe_depth = wipe_tower_size(1); Vec3d origin = plate->get_origin(); @@ -3673,8 +3958,7 @@ int CLI::run(int argc, char **argv) BOOST_LOG_TRIVIAL(info) << boost::format("downward_check: printable size{%1%,%2%, %3%}, exclude area{%4%, %5%: %6% x %7%}") %printer_plate.printable_width %printer_plate.printable_depth %printer_plate.printable_height %printer_plate.exclude_x %printer_plate.exclude_y %printer_plate.exclude_width %printer_plate.exclude_depth; - - //1.9.7.52 + if (config.option("extruder_clearance_height_to_lid")) printer_plate.height_to_lid = config.opt_float("extruder_clearance_height_to_lid"); @@ -3699,7 +3983,6 @@ int CLI::run(int argc, char **argv) Slic3r::GUI::PartPlate* cur_plate = (Slic3r::GUI::PartPlate *)partplate_list.get_plate(index); Vec3d size = plate_obj_size_infos[index].obj_bbox.size(); - //1.9.7.52 bool is_sequence = false; get_print_sequence(cur_plate, m_print_config, is_sequence); has_sequence_plates |= is_sequence; @@ -3717,8 +4000,7 @@ int CLI::run(int argc, char **argv) if (downward_check_status[index2]) continue; printer_plate_info_t& plate_info = downward_check_printers[index2]; - - //1.9.7.52 + if (is_sequence) { if ((plate_info.cleareance_radius > 0.f) && (plate_info.height_to_rod > 0.f) && (plate_info.height_to_lid > 0.f)) { if ((cleareance_radius < plate_info.cleareance_radius) @@ -3984,7 +4266,6 @@ int CLI::run(int argc, char **argv) // this affects volumes: o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), Y); } else if (opt_key == "scale") { - //1.9.5 float ratio = m_config.opt_float(opt_key); if (ratio <= 0.f) { BOOST_LOG_TRIVIAL(error) << boost::format("Invalid params:invalid scale ratio %1%")%ratio; @@ -4174,6 +4455,10 @@ int CLI::run(int argc, char **argv) oriented_or_arranged |= need_arrange; BOOST_LOG_TRIVIAL(info) << boost::format("before arrange, need_arrange=%1%, duplicate_count %2%, filament_color_changed %3%")%need_arrange %duplicate_count %filament_color_changed; + // if (!assemble_plate_info_list.empty() || need_arrange) + BOOST_LOG_TRIVIAL(debug) << "filament_count: " << filament_count; + Model::setExtruderParams(m_print_config, filament_count); + if (need_arrange || filament_color_changed) { for (int index = 0; index < partplate_list.get_plate_count(); index ++) @@ -4274,23 +4559,33 @@ int CLI::run(int argc, char **argv) y = WIPE_TOWER_MARGIN; } - ConfigOptionFloat wt_x_opt(x); - ConfigOptionFloat wt_y_opt(y); - //create the options using default if neccessary ConfigOptionFloats* wipe_x_option = m_print_config.option("wipe_tower_x", true); ConfigOptionFloats* wipe_y_option = m_print_config.option("wipe_tower_y", true); ConfigOptionFloat* width_option = m_print_config.option("prime_tower_width", true); ConfigOptionFloat* rotation_angle_option = m_print_config.option("wipe_tower_rotation_angle", true); - ConfigOptionFloat* volume_option = m_print_config.option("prime_volume", true); + ConfigOptionFloats *volume_option = m_print_config.option("filament_prime_volume", true); + ConfigOptionBool *prime_tower_rib_wall_option = m_print_config.option("prime_tower_rib_wall", true); + std::vector wipe_volume = volume_option->values; - BOOST_LOG_TRIVIAL(info) << boost::format("prime_tower_width %1% wipe_tower_rotation_angle %2% prime_volume %3%") % width_option->value % rotation_angle_option->value % volume_option->value; + BOOST_LOG_TRIVIAL(info) << boost::format("prime_tower_width %1% wipe_tower_rotation_angle %2% prime_volume %3%, rib_wall %4%") % width_option->value % rotation_angle_option->value % get_max_element(wipe_volume) %prime_tower_rib_wall_option->value; + + ConfigOptionFloat wt_x_opt(x); + ConfigOptionFloat wt_y_opt(y); wipe_x_option->set_at(&wt_x_opt, i, 0); wipe_y_option->set_at(&wt_y_opt, i, 0); + Vec3d wipe_tower_size, wipe_tower_pos; + ArrangePolygon wipe_tower_ap = cur_plate->estimate_wipe_tower_polygon(m_print_config, i, wipe_tower_pos, wipe_tower_size, new_extruder_count, assemble_plate.filaments_count, true); - ArrangePolygon wipe_tower_ap = cur_plate->estimate_wipe_tower_polygon(m_print_config, i, assemble_plate.filaments_count, true); + //update the new wp position + wt_x_opt.value = wipe_tower_pos(0); + wt_y_opt.value = wipe_tower_pos(1); + BOOST_LOG_TRIVIAL(info) << boost::format("%1%, after estimate_wipe_tower_polygon pos {%2%, %3%}, size {%4%, %5%}")%__LINE__ % wipe_tower_pos(0) % wipe_tower_pos(1) % wipe_tower_size(0) %wipe_tower_size(1); + + wipe_x_option->set_at(&wt_x_opt, i, 0); + wipe_y_option->set_at(&wt_y_opt, i, 0); wipe_tower_ap.bed_idx = i; unselected.emplace_back(wipe_tower_ap); @@ -4331,7 +4626,7 @@ int CLI::run(int argc, char **argv) BOOST_LOG_TRIVIAL(info) << "Arrange full params: " << arrange_cfg.to_json(); BOOST_LOG_TRIVIAL(info) << boost::format("arrange: items selected before arranging: %1%") % selected.size(); for (auto item : selected) - BOOST_LOG_TRIVIAL(trace) << item.name << ", extruder: " << item.extrude_ids.back() << ", bed: " << item.bed_idx + BOOST_LOG_TRIVIAL(trace) << item.name << ", extruder: " << item.extrude_id_filament_types.begin()->first << ", bed: " << item.bed_idx << ", trans: " << item.translation.transpose(); BOOST_LOG_TRIVIAL(info) << boost::format("arrange: items unselected before arranging: %1%") % unselected.size(); for (auto item : unselected) @@ -4533,9 +4828,10 @@ int CLI::run(int argc, char **argv) ConfigOptionFloats* wipe_y_option = m_print_config.option("wipe_tower_y", true); ConfigOptionFloat* width_option = m_print_config.option("prime_tower_width", true); ConfigOptionFloat* rotation_angle_option = m_print_config.option("wipe_tower_rotation_angle", true); - ConfigOptionFloat* volume_option = m_print_config.option("prime_volume", true); + ConfigOptionFloats *volume_option = m_print_config.option("filament_prime_volume", true); + std::vector wipe_volume = volume_option->values; - BOOST_LOG_TRIVIAL(info) << boost::format("prime_tower_width %1% wipe_tower_rotation_angle %2% prime_volume %3%")%width_option->value %rotation_angle_option->value %volume_option->value ; + BOOST_LOG_TRIVIAL(info) << boost::format("prime_tower_width %1% wipe_tower_rotation_angle %2% prime_volume %3%")%width_option->value %rotation_angle_option->value %get_max_element(wipe_volume); for (int bedid = 0; bedid < MAX_PLATE_COUNT; bedid++) { @@ -4545,8 +4841,19 @@ int CLI::run(int argc, char **argv) wipe_y_option->set_at(&wt_y_opt, plate_index_valid, 0); } + Vec3d wipe_tower_size, wipe_tower_pos; + ArrangePolygon wipe_tower_ap = partplate_list.get_plate(plate_index_valid)->estimate_wipe_tower_polygon(m_print_config, plate_index_valid, wipe_tower_pos, wipe_tower_size, new_extruder_count, extruder_size, true); - ArrangePolygon wipe_tower_ap = partplate_list.get_plate(plate_index_valid)->estimate_wipe_tower_polygon(m_print_config, plate_index_valid, extruder_size, true); + //update the new wp position + if (bedid < plate_count) { + wt_x_opt.value = wipe_tower_pos(0); + wt_y_opt.value = wipe_tower_pos(1); + + wipe_x_option->set_at(&wt_x_opt, plate_index_valid, 0); + wipe_y_option->set_at(&wt_y_opt, plate_index_valid, 0); + + BOOST_LOG_TRIVIAL(info) << boost::format("%1%, after estimate_wipe_tower_polygon, pos {%2%, %3%}, size {%4%, %5%}, plate %6%")%__LINE__ % wipe_tower_pos(0) % wipe_tower_pos(1) % wipe_tower_size(0) %wipe_tower_size(1) %bedid; + } wipe_tower_ap.bed_idx = bedid; unselected.emplace_back(wipe_tower_ap); @@ -4613,7 +4920,7 @@ int CLI::run(int argc, char **argv) } float w = dynamic_cast(m_print_config.option("prime_tower_width"))->value; float a = dynamic_cast(m_print_config.option("wipe_tower_rotation_angle"))->value; - float v = dynamic_cast(m_print_config.option("prime_volume"))->value; + std::vector v = dynamic_cast(m_print_config.option("filament_prime_volume"))->values; unsigned int filaments_cnt = plate_data_src[plate_to_slice-1]->slice_filaments_info.size(); if ((filaments_cnt == 0) || need_skip) { @@ -4636,7 +4943,7 @@ int CLI::run(int argc, char **argv) //float depth = v * (filaments_cnt - 1) / (layer_height * w); - Vec3d wipe_tower_size = cur_plate->estimate_wipe_tower_size(m_print_config, w, v, filaments_cnt); + Vec3d wipe_tower_size = cur_plate->estimate_wipe_tower_size(m_print_config, w, get_max_element(v), new_extruder_count, filaments_cnt); Vec3d plate_origin = cur_plate->get_origin(); int plate_width, plate_depth, plate_height; partplate_list.get_plate_size(plate_width, plate_depth, plate_height); @@ -4645,11 +4952,13 @@ int CLI::run(int argc, char **argv) ConfigOption *wipe_tower_brim_width_opt = m_print_config.option("prime_tower_brim_width"); if (wipe_tower_brim_width_opt ) { wp_brim_width = wipe_tower_brim_width_opt->getFloat(); + if (wp_brim_width < 0) wp_brim_width = WipeTower::get_auto_brim_by_height((float) wipe_tower_size.z()); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("arrange wipe_tower: wp_brim_width %1%")%wp_brim_width; } + w = wipe_tower_size(0); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("arrange wipe_tower: x=%1%, y=%2%, width=%3%, depth=%4%, angle=%5%, prime_volume=%6%, filaments_cnt=%7%, layer_height=%8%, plate_width=%9%, plate_depth=%10%") - %x %y %w %depth %a %v %filaments_cnt %layer_height %plate_width %plate_depth; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("arrange wipe_tower: x=%1%, y=%2%, width=%3%, depth=%4%, angle=%5%, prime_volume=%6%, filaments_cnt=%7%, layer_height=%8%, plate_width=%9%, plate_depth=%10%") % + x % y % w % depth % a % get_max_element(v) % filaments_cnt % layer_height % plate_width % plate_depth; if ((y + depth + margin + wp_brim_width) > (float)plate_depth) { y = (float)plate_depth - depth - margin - wp_brim_width; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("arrange wipe_tower: exceeds the border, change y to %1%, plate_depth=%2%")%y %plate_depth; @@ -4730,12 +5039,9 @@ int CLI::run(int argc, char **argv) BOOST_LOG_TRIVIAL(debug) << "arrange bedpts:" << beds[0].transpose() << ", " << beds[1].transpose() << ", " << beds[2].transpose() << ", " << beds[3].transpose(); BOOST_LOG_TRIVIAL(info)<< "Arrange full params: "<< arrange_cfg.to_json(); BOOST_LOG_TRIVIAL(info) << boost::format("arrange: items selected before arranging: %1%")%selected.size(); - for (auto item : selected) - BOOST_LOG_TRIVIAL(trace) << item.name << ", extruder: " << item.extrude_ids.back() << ", bed: " << item.bed_idx - << ", trans: " << item.translation.transpose(); + for (auto item : selected) BOOST_LOG_TRIVIAL(trace) << item.name << ", bed: " << item.bed_idx << ", trans: " << item.translation.transpose(); BOOST_LOG_TRIVIAL(info) << boost::format("arrange: items unselected before arranging: %1%") % unselected.size(); - for (auto item : unselected) - BOOST_LOG_TRIVIAL(trace) << item.name << ", bed: " << item.bed_idx << ", trans: " << item.translation.transpose(); + for (auto item : unselected) BOOST_LOG_TRIVIAL(trace) << item.name << ", bed: " << item.bed_idx << ", trans: " << item.translation.transpose(); } arrange_cfg.progressind= [](unsigned st, std::string str = "") { //boost::nowide::cout << "st=" << st << ", " << str << std::endl; @@ -5023,15 +5329,15 @@ int CLI::run(int argc, char **argv) int max_slicing_time_per_plate = 0, max_triangle_count_per_plate = 0, sliced_plate = -1; std::vector plate_has_skips(partplate_list.get_plate_count(), false); std::vector> plate_skipped_objects(partplate_list.get_plate_count()); - //1.9.5 + global_current_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); sliced_info.prepare_time = (size_t) (global_current_time - global_begin_time); global_begin_time = global_current_time; - //1.9.5 //opengl related - Slic3r::GUI::OpenGLManager opengl_mgr; - GLShaderProgram* shader = nullptr; + std::shared_ptr p_opengl_mgr = std::make_shared(); + p_opengl_mgr->set_legacy_framebuffer_enabled(false); + std::shared_ptr shader = nullptr; GLVolumeCollection glvolume_collection; bool opengl_valid = false; const ConfigOptionStrings* filament_color = dynamic_cast(m_print_config.option("filament_colour")); @@ -5042,7 +5348,7 @@ int CLI::run(int argc, char **argv) else colors.push_back("#FFFFFFFF"); std::vector> colors_out(colors.size()); - auto init_opengl_and_colors = [&opengl_mgr, &colors_out, &glvolume_collection, &shader, &filament_color](Model &model, std::vector& f_colors) -> bool { + auto init_opengl_and_colors = [&p_opengl_mgr, &colors_out, &glvolume_collection, &shader, &filament_color](Model &model, std::vector& f_colors) -> bool { unsigned char rgb_color[4] = {}; for (const std::string& color : f_colors) { Slic3r::GUI::BitmapCache::parse_color4(color, rgb_color); @@ -5093,14 +5399,14 @@ int CLI::run(int argc, char **argv) glfwMakeContextCurrent(window); } - bool gl_valid = opengl_mgr.init_gl(false); + bool gl_valid = p_opengl_mgr->init_gl(false); if (!gl_valid) { BOOST_LOG_TRIVIAL(error) << "init opengl failed! skip thumbnail generating" << std::endl; } else { BOOST_LOG_TRIVIAL(info) << "glewInit Sucess." << std::endl; - shader = opengl_mgr.get_shader("thumbnail"); + shader = p_opengl_mgr->get_shader("thumbnail"); if (!shader) { BOOST_LOG_TRIVIAL(error) << boost::format("can not get shader for rendering thumbnail"); gl_valid = false; @@ -5303,7 +5609,6 @@ int CLI::run(int argc, char **argv) model.curr_plate_index = index; BOOST_LOG_TRIVIAL(info) << boost::format("Plate %1%: pre_check %2%, start")%(index+1)%pre_check; - //1.9.5 long long start_time = 0, end_time = 0, temp_time = 0; std::unordered_map slice_time; @@ -5336,7 +5641,7 @@ int CLI::run(int argc, char **argv) BOOST_LOG_TRIVIAL(info) << boost::format("print_volume {%1%,%2%,%3%}->{%4%, %5%, %6%}") % print_volume.min(0) % print_volume.min(1) % print_volume.min(2) % print_volume.max(0) % print_volume.max(1) % print_volume.max(2) << std::endl; #else - BuildVolume build_volume(part_plate->get_shape(), print_height); + BuildVolume build_volume(part_plate->get_shape(), print_height, part_plate->get_extruder_areas(), current_extruder_print_heights); //model.update_print_volume_state(build_volume); unsigned int count = model.update_print_volume_state(build_volume); @@ -5349,6 +5654,7 @@ int CLI::run(int argc, char **argv) long long triangle_count = 0; int printable_instances = 0; int skipped_count = 0; + std::vector> unprintable_filament_ids(new_extruder_count, std::set()); for (ModelObject* model_object : model.objects) for (ModelInstance *i : model_object->instances) { @@ -5392,6 +5698,14 @@ int CLI::run(int argc, char **argv) } else if (i->print_volume_state == ModelInstancePVS_Inside) { + const Transform3d& inst_matrix = i->get_transformation().get_matrix(); + + //get object filaments + std::set object_filaments; + for (const ModelVolume *vol : model_object->volumes) { + std::vector filaments = vol->get_extruders(); + object_filaments.insert(filaments.begin(), filaments.end()); + } for (const ModelVolume* vol : model_object->volumes) { if (vol->is_model_part()) { @@ -5404,6 +5718,24 @@ int CLI::run(int argc, char **argv) record_exit_reson(outfile_dir, CLI_TRIANGLE_COUNT_EXCEEDS_LIMIT, index+1, cli_errors[CLI_TRIANGLE_COUNT_EXCEEDS_LIMIT], sliced_info); flush_and_exit(CLI_TRIANGLE_COUNT_EXCEEDS_LIMIT); } + + if (new_extruder_count > 1) { + BoundingBoxf3 bbox = vol->get_convex_hull().transformed_bounding_box(inst_matrix * vol->get_matrix()); + std::vector inside_extruders; + BuildVolume::ObjectState state = build_volume.check_volume_bbox_state_with_extruder_areas(bbox, inside_extruders); + if (state == BuildVolume::ObjectState::Limited) { + if (object_filaments.size() == 1) { + // Only check for single-color object + for (size_t j = 0; j < inside_extruders.size(); ++j) { + if (!inside_extruders[j]) { + std::vector filaments = vol->get_extruders(); + unprintable_filament_ids[j].insert(filaments.begin(), filaments.end()); + } + } + } + } + + } } } } @@ -5418,6 +5750,118 @@ int CLI::run(int argc, char **argv) flush_and_exit(CLI_NO_SUITABLE_OBJECTS_AFTER_SKIP); } + std::vector plate_filaments = part_plate->get_extruders_under_cli(true, m_print_config); + std::vector used_tpu_filaments; + for (int f_index = 0; f_index < plate_filaments.size(); f_index++) { + if (plate_filaments[f_index] <= filament_count) { + std::string filament_type; + m_print_config.get_filament_type(filament_type, plate_filaments[f_index]-1); + if (filament_type == "TPU") { + used_tpu_filaments.push_back(plate_filaments[f_index]); + } + } + } + bool tpu_valid = part_plate->check_tpu_printable_status(m_print_config, used_tpu_filaments); + if (!tpu_valid) { + BOOST_LOG_TRIVIAL(error) << boost::format("plate %1% : Found 2 or more tpu filaments on plate ") % (index + 1); + record_exit_reson(outfile_dir, CLI_ONLY_ONE_TPU_SUPPORTED, index + 1, cli_errors[CLI_ONLY_ONE_TPU_SUPPORTED], sliced_info); + flush_and_exit(CLI_ONLY_ONE_TPU_SUPPORTED); + } + + if (new_extruder_count > 1) { + std::vector> unprintable_filament_vec; + for (const std::set& filamnt_ids : unprintable_filament_ids) { + unprintable_filament_vec.emplace_back(std::vector(filamnt_ids.begin(), filamnt_ids.end())); + } + + FilamentMapMode mode; + if (m_extra_config.option>("filament_map_mode")) + mode = m_extra_config.option>("filament_map_mode")->value; + else + mode = part_plate->get_real_filament_map_mode(m_print_config); + if (mode < FilamentMapMode::fmmManual) { + std::vector conflict_filament_vector; + for (int index = 0; index < new_extruder_count; index++) + { + if (!unprintable_filament_vec[index].empty()) + { + std::sort(unprintable_filament_vec[index].begin(), unprintable_filament_vec[index].end()); + if (index == 0) + conflict_filament_vector = unprintable_filament_vec[index]; + else + { + std::vector result_filaments; + //result_filaments.reserve(conflict_filaments.size()); + std::set_intersection(conflict_filament_vector.begin(), conflict_filament_vector.end(), unprintable_filament_vec[index].begin(), + unprintable_filament_vec[index].end(), insert_iterator>(result_filaments, result_filaments.begin())); + conflict_filament_vector = result_filaments; + } + } + else + { + conflict_filament_vector.clear(); + break; + } + } + //check whether has filament can not map + if (!conflict_filament_vector.empty()) + { + BOOST_LOG_TRIVIAL(error) << boost::format("plate %1% : some filaments can not be mapped under auto mode for multi extruder printer ")% (index + 1); + record_exit_reson(outfile_dir, CLI_FILAMENT_CAN_NOT_MAP, index + 1, cli_errors[CLI_FILAMENT_CAN_NOT_MAP], sliced_info); + flush_and_exit(CLI_FILAMENT_CAN_NOT_MAP); + } + } + else { + std::vector filament_maps; + if (m_extra_config.option("filament_map")) { + filament_maps = m_extra_config.option("filament_map")->values; + part_plate->set_filament_maps(filament_maps); + } + else + filament_maps = part_plate->get_real_filament_maps(m_print_config); + + std::vector& unprintable_filament_types = m_print_config.option("unprintable_filament_types", true)->values; + std::vector>unprintable_filament_type_list; + unprintable_filament_type_list.resize(new_extruder_count); + for (int index = 0; index < new_extruder_count; index++) + { + std::vector unprintable_list; + if (unprintable_filament_types.size() > index) + unprintable_list = split_string(unprintable_filament_types[index], ','); + unprintable_filament_type_list[index] = unprintable_list; + } + + for (int index = 0; index < filament_maps.size(); index++) + { + int filament_extruder = filament_maps[index]; + if (unprintable_filament_ids[filament_extruder - 1].find(index + 1) != unprintable_filament_ids[filament_extruder - 1].end()) + { + BOOST_LOG_TRIVIAL(error) << boost::format("plate %1% : some filaments can not be mapped under manual mode for multi extruder printer ") % (index + 1); + record_exit_reson(outfile_dir, CLI_FILAMENT_CAN_NOT_MAP, index + 1, cli_errors[CLI_FILAMENT_CAN_NOT_MAP], sliced_info); + flush_and_exit(CLI_FILAMENT_CAN_NOT_MAP); + } + } + + for (int f_index = 0; f_index < plate_filaments.size(); f_index++) { + if (plate_filaments[f_index] <= filament_count) { + int filament_extruder = filament_maps[plate_filaments[f_index] - 1]; + std::vector& unprintable_list = unprintable_filament_type_list[filament_extruder-1]; + std::string filament_type; + m_print_config.get_filament_type(filament_type, plate_filaments[f_index]-1); + if (unprintable_list.size() > 0) + { + auto iter = std::find(unprintable_list.begin(), unprintable_list.end(), filament_type); + if (iter != unprintable_list.end()) { + BOOST_LOG_TRIVIAL(error) << boost::format("plate %1% : filament %2% can not be printed on extruder %3%, under manual mode for multi extruder printer") % (index + 1) %filament_type %filament_extruder; + record_exit_reson(outfile_dir, CLI_FILAMENTS_NOT_SUPPORTED_BY_EXTRUDER, index + 1, cli_errors[CLI_FILAMENTS_NOT_SUPPORTED_BY_EXTRUDER], sliced_info); + flush_and_exit(CLI_FILAMENTS_NOT_SUPPORTED_BY_EXTRUDER); + } + } + } + } + } + } + plate_triangle_counts[index] = triangle_count; plate_object_count[index] = printable_instances; BOOST_LOG_TRIVIAL(info) << "plate "<< index+1<< ": load cached data success, go on."; @@ -5429,10 +5873,59 @@ int CLI::run(int argc, char **argv) DynamicPrintConfig new_print_config = m_print_config; new_print_config.apply(*part_plate->config()); new_print_config.apply(m_extra_config, true); + if (new_extruder_count > 1) { + FilamentMapMode map_mode = fmmAutoForFlush; + if (new_print_config.option>("filament_map_mode")) + map_mode = new_print_config.option>("filament_map_mode")->value; + + if (map_mode < fmmManual) { + //set default params for auto map + std::vector extruder_ams_count(new_extruder_count, ""); + std::vector> extruder_filament_info(new_extruder_count, std::vector()); + int color_count = 0; + + const ConfigOptionStrings* filament_type = dynamic_cast(m_print_config.option("filament_type")); + std::vector types = filament_type ? filament_type->vserialize() : std::vector{"PLA"}; + + for (int e_index = 0; e_index < new_extruder_count; e_index++) + { + extruder_ams_count[e_index] = "1#0|4#1"; + for (int color_index = 0; color_index < 4; color_index++) + { + DynamicPrintConfig temp_config; + std::vector temp_colors(1, "#FFFFFFFF"); + std::vector temp_types(1, "PLA"); + if (filament_color) { + temp_colors[0] = colors[color_count % colors.size()]; + } + if (filament_type) + temp_types[0] = types[color_count % types.size()]; + + temp_config.option("filament_colour", true)->values = temp_colors; + temp_config.option("filament_type", true)->values = temp_types; + temp_config.option("filament_is_support",true)->values = { 0 }; + extruder_filament_info[e_index].push_back(std::move(temp_config)); + color_count++; + } + } + new_print_config.option("extruder_ams_count", true)->values = extruder_ams_count; + print_fff->set_extruder_filament_info(extruder_filament_info); + } + } + + //set filament_map + std::vector& final_filament_maps = new_print_config.option("filament_map", true)->values; + if (final_filament_maps.size() < filament_count) + final_filament_maps.resize(filament_count, 1); + if (new_extruder_count == 1) { + for (int index = 0; index < filament_count; index++) + final_filament_maps[index] = 1; + } print->apply(model, new_print_config); BOOST_LOG_TRIVIAL(info) << boost::format("set no_check to %1%:")%no_check; print->set_no_check_flag(no_check);//QDS StringObjectException warning; + print_fff->set_check_multi_filaments_compatibility(!allow_mix_temp); auto err = print->validate(&warning); if (!err.string.empty()) { if ((STRING_EXCEPT_LAYER_HEIGHT_EXCEEDS_LIMIT == err.type) && no_check) { @@ -5547,8 +6040,7 @@ int CLI::run(int argc, char **argv) } } else { - //1.9.5 - print->process(&slice_time); + print->process(&slice_time); BOOST_LOG_TRIVIAL(info) << "print::process: first time_using_cache is " << slice_time[TIME_USING_CACHE] << " secs."; } if (printer_technology == ptFFF) { @@ -5590,38 +6082,23 @@ int CLI::run(int argc, char **argv) g_slicing_warnings.clear(); } sliced_plate_info.triangle_count = plate_triangle_counts[index]; - //1.9.5 - auto cli_generate_thumbnails = [&partplate_list, &model, &glvolume_collection, &colors_out, &shader](const ThumbnailsParams& params) -> ThumbnailsList{ + + auto cli_generate_thumbnails = [&partplate_list, &model, &glvolume_collection, &colors_out, &shader, &p_opengl_mgr](const ThumbnailsParams& params) -> ThumbnailsList{ ThumbnailsList thumbnails; + p_opengl_mgr->bind_vao(); + p_opengl_mgr->bind_shader(shader); for (const Vec2d& size : params.sizes) { thumbnails.push_back(ThumbnailData()); Point isize(size); // round to ints ThumbnailData& thumbnail_data = thumbnails.back(); - switch (Slic3r::GUI::OpenGLManager::get_framebuffers_type()) - { - case Slic3r::GUI::OpenGLManager::EFramebufferType::Arb: - { - BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: ARB"); - Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer(thumbnail_data, - isize.x(), isize.y(), params, - partplate_list, model.objects, glvolume_collection, colors_out, shader, Slic3r::GUI::Camera::EType::Ortho); - break; - } - case Slic3r::GUI::OpenGLManager::EFramebufferType::Ext: - { - BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: EXT"); - Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer_ext(thumbnail_data, - isize.x(), isize.y(), params, - partplate_list, model.objects, glvolume_collection, colors_out, shader, Slic3r::GUI::Camera::EType::Ortho); - break; - } - default: - BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: unknown"); - break; - } + Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer(p_opengl_mgr, thumbnail_data, + isize.x(), isize.y(), params, + partplate_list, model.objects, glvolume_collection, colors_out, shader, Slic3r::GUI::Camera::EType::Ortho); if (!thumbnails.back().is_valid()) thumbnails.pop_back(); } + p_opengl_mgr->unbind_shader(); + p_opengl_mgr->unbind_vao(); return thumbnails; }; @@ -5635,7 +6112,6 @@ int CLI::run(int argc, char **argv) part_plate->set_tmp_gcode_path(outfile); } BOOST_LOG_TRIVIAL(info) << "process finished, will export gcode temporily to " << outfile << std::endl; - //1.9.5 temp_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); if (is_qdt_vendor_preset) { outfile = print_fff->export_gcode(outfile, gcode_result, nullptr); @@ -5648,6 +6124,13 @@ int CLI::run(int argc, char **argv) slice_time[TIME_USING_CACHE] = slice_time[TIME_USING_CACHE] + ((long long)Slic3r::Utils::get_current_milliseconds_time_utc() - temp_time); BOOST_LOG_TRIVIAL(info) << "export_gcode finished: time_using_cache update to " << slice_time[TIME_USING_CACHE] << " secs."; + if (gcode_result && gcode_result->gcode_check_result.error_code) { + //found gcode error + BOOST_LOG_TRIVIAL(error) << "plate " << index + 1 << ": found gcode in unprintable area of multi extruder printers!" << std::endl; + record_exit_reson(outfile_dir, CLI_GCODE_PATH_IN_UNPRINTABLE_AREA, index + 1, cli_errors[CLI_GCODE_PATH_IN_UNPRINTABLE_AREA], sliced_info); + flush_and_exit(CLI_GCODE_PATH_IN_UNPRINTABLE_AREA); + } + if (gcode_result && gcode_result->filament_printable_reuslt.has_value()) { //found gcode error BOOST_LOG_TRIVIAL(error) << "plate " << index + 1 << ": found some filament unprintable on current bed- "<< gcode_result->filament_printable_reuslt.plate_name << std::endl; @@ -5695,10 +6178,8 @@ int CLI::run(int argc, char **argv) flush_and_exit(ret); } } - //1.9.5 end_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); sliced_plate_info.sliced_time = end_time - start_time; - //1.9.5 sliced_plate_info.sliced_time_with_cache = slice_time[TIME_USING_CACHE]; sliced_plate_info.make_perimeters_time = slice_time[TIME_MAKE_PERIMETERS]; sliced_plate_info.infill_time = slice_time[TIME_INFILL]; @@ -5710,7 +6191,7 @@ int CLI::run(int argc, char **argv) auto it_wipe = std::find_if(time_mode.roles_times.begin(), time_mode.roles_times.end(), [](const std::pair& item) { return ExtrusionRole::erWipeTower == item.first; }); sliced_plate_info.total_predication = time_mode.time; sliced_plate_info.main_predication = time_mode.time - time_mode.prepare_time; - sliced_plate_info.filament_change_times = print_estimated_stat.total_filamentchanges; + sliced_plate_info.filament_change_times = print_estimated_stat.total_filament_changes; if (it_wipe != time_mode.roles_times.end()) { //filament changes time will be included in prime tower time later //ConfigOptionFloat* machine_load_filament_time_opt = m_print_config.option("machine_load_filament_time"); @@ -5734,7 +6215,7 @@ int CLI::run(int argc, char **argv) } } if (has_tool_change) - sliced_plate_info.layer_filament_change = print_estimated_stat.total_filamentchanges; + sliced_plate_info.layer_filament_change = print_estimated_stat.total_filament_changes; //filaments auto* filament_ids = dynamic_cast(m_print_config.option("filament_ids")); @@ -5789,10 +6270,8 @@ int CLI::run(int argc, char **argv) sliced_plate_info.objects.push_back(std::move(object_info)); } - if (max_slicing_time_per_plate != 0) { long long time_cost = end_time - start_time; - //1.9.5 if (time_cost > max_slicing_time_per_plate * 1000) { sliced_plate_info.warning_message = (boost::format("plate %1%'s slice time %2% exceeds the limit %3%, return error.")%(index+1) %time_cost %(max_slicing_time_per_plate * 1000)).str(); BOOST_LOG_TRIVIAL(error) << sliced_plate_info.warning_message; @@ -5860,7 +6339,7 @@ int CLI::run(int argc, char **argv) flush_and_exit(CLI_UNSUPPORTED_OPERATION); } } - //1.9.5 + global_begin_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); if (export_to_3mf) { //QDS: export as qdt 3mf @@ -5908,11 +6387,10 @@ int CLI::run(int argc, char **argv) bool need_create_thumbnail_group = false, need_create_no_light_group = false, need_create_top_group = false; // get type and color for platedata - //1.9.5 //auto* filament_types = dynamic_cast(m_print_config.option("filament_type")); //const ConfigOptionStrings* filament_color = dynamic_cast(m_print_config.option("filament_colour")); auto* filament_id = dynamic_cast(m_print_config.option("filament_ids")); - const ConfigOptionFloats* nozzle_diameter_option = dynamic_cast(m_print_config.option("nozzle_diameter")); + const ConfigOptionFloatsNullable* nozzle_diameter_option = dynamic_cast(m_print_config.option("nozzle_diameter")); std::string nozzle_diameter_str; if (nozzle_diameter_option) nozzle_diameter_str = nozzle_diameter_option->serialize(); @@ -6010,8 +6488,7 @@ int CLI::run(int argc, char **argv) } if (need_regenerate_thumbnail || need_regenerate_no_light_thumbnail || need_regenerate_top_thumbnail) { - //1.9.5 - if (!opengl_valid) + if (!opengl_valid) opengl_valid = init_opengl_and_colors(m_models[0], colors); /*std::vector colors; if (filament_color) { @@ -6129,9 +6606,10 @@ int CLI::run(int argc, char **argv) BOOST_LOG_TRIVIAL(error) << boost::format("can not get shader for rendering thumbnail"); } else {*/ - //1.9.5 if (opengl_valid) { Model &model = m_models[0]; + p_opengl_mgr->bind_vao(); + p_opengl_mgr->bind_shader(shader); for (int i = 0; i < partplate_list.get_plate_count(); i++) { Slic3r::GUI::PartPlate *part_plate = partplate_list.get_plate(i); PlateData *plate_data = plate_data_list[i]; @@ -6173,28 +6651,9 @@ int CLI::run(int argc, char **argv) const ThumbnailsParams thumbnail_params = {{}, false, true, true, true, i}; BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%'s thumbnail, need to regenerate")%(i+1); - switch (Slic3r::GUI::OpenGLManager::get_framebuffers_type()) - { - case Slic3r::GUI::OpenGLManager::EFramebufferType::Arb: - { - BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: ARB"); - Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer(*thumbnail_data, - thumbnail_width, thumbnail_height, thumbnail_params, - partplate_list, model.objects, glvolume_collection, colors_out, shader, Slic3r::GUI::Camera::EType::Ortho); - break; - } - case Slic3r::GUI::OpenGLManager::EFramebufferType::Ext: - { - BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: EXT"); - Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer_ext(*thumbnail_data, - thumbnail_width, thumbnail_height, thumbnail_params, - partplate_list, model.objects, glvolume_collection, colors_out, shader, Slic3r::GUI::Camera::EType::Ortho); - break; - } - default: - BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: unknown"); - break; - } + Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer(p_opengl_mgr, *thumbnail_data, + thumbnail_width, thumbnail_height, thumbnail_params, + partplate_list, model.objects, glvolume_collection, colors_out, shader, Slic3r::GUI::Camera::EType::Ortho); BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%'s thumbnail,finished rendering")%(i+1); } } @@ -6225,28 +6684,11 @@ int CLI::run(int argc, char **argv) const ThumbnailsParams thumbnail_params = { {}, false, true, false, true, i }; BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%'s no_light_thumbnail_file missed, need to regenerate")%(i+1); - switch (Slic3r::GUI::OpenGLManager::get_framebuffers_type()) - { - case Slic3r::GUI::OpenGLManager::EFramebufferType::Arb: - { - BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: ARB"); - Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer(*no_light_thumbnail, - thumbnail_width, thumbnail_height, thumbnail_params, - partplate_list, model.objects, glvolume_collection, colors_out, shader, Slic3r::GUI::Camera::EType::Ortho, false, false, true); - break; - } - case Slic3r::GUI::OpenGLManager::EFramebufferType::Ext: - { - BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: EXT"); - Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer_ext(*no_light_thumbnail, - thumbnail_width, thumbnail_height, thumbnail_params, - partplate_list, model.objects, glvolume_collection, colors_out, shader, Slic3r::GUI::Camera::EType::Ortho, false, false, true); - break; - } - default: - BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: unknown"); - break; - } + Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer(p_opengl_mgr, *no_light_thumbnail, + thumbnail_width, thumbnail_height, thumbnail_params, + partplate_list, model.objects, glvolume_collection, colors_out, shader, + Slic3r::GUI::Camera::EType::Ortho, Slic3r::GUI::Camera::ViewAngleType::Iso, + false, true); plate_data->no_light_thumbnail_file = "valid_no_light"; BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%'s no_light thumbnail,finished rendering")%(i+1); } @@ -6309,34 +6751,18 @@ int CLI::run(int argc, char **argv) BOOST_LOG_TRIVIAL(info) << boost::format("skip rendering for top&&pick"); } else { - switch (Slic3r::GUI::OpenGLManager::get_framebuffers_type()) - { - case Slic3r::GUI::OpenGLManager::EFramebufferType::Arb: - { - BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: ARB"); - Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer(*top_thumbnail, - thumbnail_width, thumbnail_height, thumbnail_params, - partplate_list, model.objects, glvolume_collection, colors_out, shader, Slic3r::GUI::Camera::EType::Ortho, true, false); - Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer(*picking_thumbnail, - thumbnail_width, thumbnail_height, thumbnail_params, - partplate_list, model.objects, glvolume_collection, colors_out, shader, Slic3r::GUI::Camera::EType::Ortho, true, true); - break; - } - case Slic3r::GUI::OpenGLManager::EFramebufferType::Ext: - { - BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: EXT"); - Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer_ext(*top_thumbnail, - thumbnail_width, thumbnail_height, thumbnail_params, - partplate_list, model.objects, glvolume_collection, colors_out, shader, Slic3r::GUI::Camera::EType::Ortho, true, false); - Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer_ext(*picking_thumbnail, - thumbnail_width, thumbnail_height, thumbnail_params, - partplate_list, model.objects, glvolume_collection, colors_out, shader, Slic3r::GUI::Camera::EType::Ortho, true, true); - break; - } - default: - BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: unknown"); - break; - } + const auto fb_type = Slic3r::GUI::OpenGLManager::get_framebuffers_type(); + BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: %1%") % Slic3r::GUI::OpenGLManager::framebuffer_type_to_string(fb_type).c_str(); + Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer(p_opengl_mgr, *top_thumbnail, + thumbnail_width, thumbnail_height, thumbnail_params, + partplate_list, model.objects, glvolume_collection, colors_out, shader, + Slic3r::GUI::Camera::EType::Ortho, Slic3r::GUI::Camera::ViewAngleType::Top_Plate, + false); + Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer(p_opengl_mgr, *picking_thumbnail, + thumbnail_width, thumbnail_height, thumbnail_params, + partplate_list, model.objects, glvolume_collection, colors_out, shader, + Slic3r::GUI::Camera::EType::Ortho, + Slic3r::GUI::Camera::ViewAngleType::Top_Plate, true, true); plate_data->top_file = "valid_top"; plate_data->pick_file = "valid_pick"; BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%'s top_thumbnail,finished rendering")%(i+1); @@ -6350,10 +6776,10 @@ int CLI::run(int argc, char **argv) BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%: add thumbnail data for top and pick into group")%(i+1); } } + p_opengl_mgr->unbind_shader(); + p_opengl_mgr->unbind_vao(); } - } - //QDS: release glfw - glfwTerminate(); + } } else { BOOST_LOG_TRIVIAL(info) << boost::format("Line %1%: use previous thumbnails, no need to regenerate")%__LINE__; @@ -6486,7 +6912,7 @@ int CLI::run(int argc, char **argv) plate_bbox->bed_type = bed_type_to_gcode_string(plate_bed_type); } // get nozzle diameter - auto opt_nozzle_diameters = m_print_config.option("nozzle_diameter"); + auto opt_nozzle_diameters = m_print_config.option("nozzle_diameter"); if (opt_nozzle_diameters != nullptr) plate_bbox->nozzle_diameter = float(opt_nozzle_diameters->get_at(plate_bbox->first_extruder)); @@ -6600,12 +7026,19 @@ int CLI::run(int argc, char **argv) g_cli_callback_mgr.stop(); #endif - for (Model &model : m_models) { - model.remove_backup_path_if_exist(); + if (opengl_valid) { + for (Model& model : m_models) { + model.remove_backup_path_if_exist(); + model.objects.clear(); + } + shader = nullptr; + glvolume_collection.clear(); + p_opengl_mgr = nullptr; + //QDS: release glfw + glfwTerminate(); } //QDS: flush logs BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", Finished" << std::endl; - //1.9.5 global_current_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); sliced_info.export_time = (size_t) (global_current_time - global_begin_time); diff --git a/src/QIDIStudio_app_msvc.cpp b/src/QIDIStudio_app_msvc.cpp index 1c91216..b364dab 100644 --- a/src/QIDIStudio_app_msvc.cpp +++ b/src/QIDIStudio_app_msvc.cpp @@ -6,9 +6,6 @@ #include #include #include - - - #ifdef SLIC3R_GUI extern "C" { @@ -18,22 +15,16 @@ extern "C" __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 0; } #endif /* SLIC3R_GUI */ - #include #include - #ifdef SLIC3R_GUI #include #endif /* SLIC3R_GUI */ - #include #include - #include #include - #include - #ifdef SLIC3R_GUI class OpenGLVersionCheck { @@ -42,42 +33,45 @@ public: std::string glsl_version; std::string vendor; std::string renderer; - HINSTANCE hOpenGL = nullptr; bool success = false; - bool load_opengl_dll() { - MSG msg = {0}; - WNDCLASS wc = {0}; - wc.lpfnWndProc = OpenGLVersionCheck::supports_opengl2_wndproc; - wc.hInstance = (HINSTANCE)GetModuleHandle(nullptr); + MSG msg = { 0 }; + WNDCLASS wc = { 0 }; + wc.lpfnWndProc = OpenGLVersionCheck::supports_opengl2_wndproc; + wc.hInstance = (HINSTANCE)GetModuleHandle(nullptr); wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND); - wc.lpszClassName = L"QIDIStudio_opengl_version_check"; + wc.lpszClassName = L"BambuStudio_opengl_version_check"; wc.style = CS_OWNDC; if (RegisterClass(&wc)) { - HWND hwnd = CreateWindowW(wc.lpszClassName, L"QIDIStudio_opengl_version_check", WS_OVERLAPPEDWINDOW, 0, 0, 640, 480, 0, 0, wc.hInstance, (LPVOID)this); + HWND hwnd = CreateWindowW(wc.lpszClassName, L"BambuStudio_opengl_version_check", WS_OVERLAPPEDWINDOW, 0, 0, 640, 480, 0, 0, wc.hInstance, (LPVOID)this); if (hwnd) { message_pump_exit = false; - while (GetMessage(&msg, NULL, 0, 0 ) > 0 && ! message_pump_exit) + while (GetMessage(&msg, NULL, 0, 0) > 0 && !message_pump_exit) DispatchMessage(&msg); } } return this->success; } - - void unload_opengl_dll() + bool unload_opengl_dll() { - if (this->hOpenGL) { - BOOL released = FreeLibrary(this->hOpenGL); - if (released) - printf("System OpenGL library released\n"); + if (this->hOpenGL != nullptr) { + if (::FreeLibrary(this->hOpenGL) != FALSE) { + if (::GetModuleHandle(L"opengl32.dll") == nullptr) { + printf("System OpenGL library successfully released\n"); + this->hOpenGL = nullptr; + return true; + } + else + printf("System OpenGL library released but not removed\n"); + } else printf("System OpenGL library NOT released\n"); - this->hOpenGL = nullptr; + return false; } + return true; } - bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { // printf("is_version_greater_or_equal_to, version: %s\n", version.c_str()); @@ -85,10 +79,8 @@ public: boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on); if (tokens.empty()) return false; - std::vector numbers; boost::split(numbers, tokens[0], boost::is_any_of("."), boost::token_compress_on); - unsigned int gl_major = 0; unsigned int gl_minor = 0; if (numbers.size() > 0) @@ -103,10 +95,8 @@ public: else return gl_minor >= minor; } - protected: static bool message_pump_exit; - void check(HWND hWnd) { hOpenGL = LoadLibraryExW(L"opengl32.dll", nullptr, 0); @@ -114,22 +104,18 @@ protected: printf("Failed loading the system opengl32.dll\n"); return; } - - typedef HGLRC (WINAPI *Func_wglCreateContext)(HDC); - typedef BOOL (WINAPI *Func_wglMakeCurrent )(HDC, HGLRC); - typedef BOOL (WINAPI *Func_wglDeleteContext)(HGLRC); - typedef GLubyte* (WINAPI *Func_glGetString )(GLenum); - + typedef HGLRC(WINAPI* Func_wglCreateContext)(HDC); + typedef BOOL(WINAPI* Func_wglMakeCurrent)(HDC, HGLRC); + typedef BOOL(WINAPI* Func_wglDeleteContext)(HGLRC); + typedef GLubyte* (WINAPI* Func_glGetString)(GLenum); Func_wglCreateContext wglCreateContext = (Func_wglCreateContext)GetProcAddress(hOpenGL, "wglCreateContext"); - Func_wglMakeCurrent wglMakeCurrent = (Func_wglMakeCurrent) GetProcAddress(hOpenGL, "wglMakeCurrent"); + Func_wglMakeCurrent wglMakeCurrent = (Func_wglMakeCurrent)GetProcAddress(hOpenGL, "wglMakeCurrent"); Func_wglDeleteContext wglDeleteContext = (Func_wglDeleteContext)GetProcAddress(hOpenGL, "wglDeleteContext"); - Func_glGetString glGetString = (Func_glGetString) GetProcAddress(hOpenGL, "glGetString"); - + Func_glGetString glGetString = (Func_glGetString)GetProcAddress(hOpenGL, "glGetString"); if (wglCreateContext == nullptr || wglMakeCurrent == nullptr || wglDeleteContext == nullptr || glGetString == nullptr) { printf("Failed loading the system opengl32.dll: The library is invalid.\n"); return; } - PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), @@ -149,7 +135,6 @@ protected: 0, 0, 0, 0 }; - HDC ourWindowHandleToDeviceContext = ::GetDC(hWnd); // Gdi32.dll int letWindowsChooseThisPixelFormat = ::ChoosePixelFormat(ourWindowHandleToDeviceContext, &pfd); @@ -159,7 +144,7 @@ protected: HGLRC glcontext = wglCreateContext(ourWindowHandleToDeviceContext); wglMakeCurrent(ourWindowHandleToDeviceContext, glcontext); // Opengl32.dll - const char *data = (const char*)glGetString(GL_VERSION); + const char* data = (const char*)glGetString(GL_VERSION); if (data != nullptr) this->version = data; // printf("check -version: %s\n", version.c_str()); @@ -177,15 +162,14 @@ protected: ::ReleaseDC(hWnd, ourWindowHandleToDeviceContext); this->success = true; } - static LRESULT CALLBACK supports_opengl2_wndproc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { - switch(message) + switch (message) { case WM_CREATE: { - CREATESTRUCT *pCreate = reinterpret_cast(lParam); - OpenGLVersionCheck *ogl_data = reinterpret_cast(pCreate->lpCreateParams); + CREATESTRUCT* pCreate = reinterpret_cast(lParam); + OpenGLVersionCheck* ogl_data = reinterpret_cast(pCreate->lpCreateParams); ogl_data->check(hWnd); DestroyWindow(hWnd); return 0; @@ -198,113 +182,108 @@ protected: } } }; - bool OpenGLVersionCheck::message_pump_exit = false; #endif /* SLIC3R_GUI */ - extern "C" { - typedef int (__stdcall *Slic3rMainFunc)(int argc, wchar_t **argv); + typedef int(__stdcall* Slic3rMainFunc)(int argc, wchar_t** argv); Slic3rMainFunc qidistu_main = nullptr; } - extern "C" { #ifdef SLIC3R_WRAPPER_NOCONSOLE int APIENTRY wWinMain(HINSTANCE /* hInstance */, HINSTANCE /* hPrevInstance */, PWSTR /* lpCmdLine */, int /* nCmdShow */) { int argc; - wchar_t **argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); #else -int wmain(int argc, wchar_t **argv) -{ + int wmain(int argc, wchar_t** argv) + { #endif - // Allow the asserts to open message box, such message box allows to ignore the assert and continue with the application. - // Without this call, the seemingly same message box is being opened by the abort() function, but that is too late and - // the application will be killed even if "Ignore" button is pressed. - _set_error_mode(_OUT_TO_MSGBOX); - - std::vector argv_extended; - argv_extended.emplace_back(argv[0]); - + // Allow the asserts to open message box, such message box allows to ignore the assert and continue with the application. + // Without this call, the seemingly same message box is being opened by the abort() function, but that is too late and + // the application will be killed even if "Ignore" button is pressed. + _set_error_mode(_OUT_TO_MSGBOX); + std::vector argv_extended; + argv_extended.emplace_back(argv[0]); #ifdef SLIC3R_WRAPPER_GCODEVIEWER - wchar_t gcodeviewer_param[] = L"--gcodeviewer"; - argv_extended.emplace_back(gcodeviewer_param); + wchar_t gcodeviewer_param[] = L"--gcodeviewer"; + argv_extended.emplace_back(gcodeviewer_param); #endif /* SLIC3R_WRAPPER_GCODEVIEWER */ - #ifdef SLIC3R_GUI - // Here one may push some additional parameters based on the wrapper type. - bool force_mesa = false; + // Here one may push some additional parameters based on the wrapper type. + bool force_mesa = false; #endif /* SLIC3R_GUI */ - for (int i = 1; i < argc; ++ i) { + for (int i = 1; i < argc; ++i) { #ifdef SLIC3R_GUI - if (wcscmp(argv[i], L"--sw-renderer") == 0) - force_mesa = true; - else if (wcscmp(argv[i], L"--no-sw-renderer") == 0) - force_mesa = false; + if (wcscmp(argv[i], L"--sw-renderer") == 0) + force_mesa = true; + else if (wcscmp(argv[i], L"--no-sw-renderer") == 0) + force_mesa = false; #endif /* SLIC3R_GUI */ - argv_extended.emplace_back(argv[i]); - } - argv_extended.emplace_back(nullptr); - + argv_extended.emplace_back(argv[i]); + } + argv_extended.emplace_back(nullptr); #ifdef SLIC3R_GUI - OpenGLVersionCheck opengl_version_check; - bool load_mesa = - // Forced from the command line. - force_mesa || - // Try to load the default OpenGL driver and test its context version. - ! opengl_version_check.load_opengl_dll() || ! opengl_version_check.is_version_greater_or_equal_to(2, 0); + OpenGLVersionCheck opengl_version_check; + bool load_mesa = + // Forced from the command line. + force_mesa || + // Try to load the default OpenGL driver and test its context version. + !opengl_version_check.load_opengl_dll() || !opengl_version_check.is_version_greater_or_equal_to(3, 2); #endif /* SLIC3R_GUI */ - - wchar_t path_to_exe[MAX_PATH + 1] = { 0 }; - ::GetModuleFileNameW(nullptr, path_to_exe, MAX_PATH); - wchar_t drive[_MAX_DRIVE]; - wchar_t dir[_MAX_DIR]; - wchar_t fname[_MAX_FNAME]; - wchar_t ext[_MAX_EXT]; - _wsplitpath(path_to_exe, drive, dir, fname, ext); - _wmakepath(path_to_exe, drive, dir, nullptr, nullptr); - + wchar_t path_to_exe[MAX_PATH + 1] = { 0 }; + ::GetModuleFileNameW(nullptr, path_to_exe, MAX_PATH); + wchar_t drive[_MAX_DRIVE]; + wchar_t dir[_MAX_DIR]; + wchar_t fname[_MAX_FNAME]; + wchar_t ext[_MAX_EXT]; + _wsplitpath(path_to_exe, drive, dir, fname, ext); + _wmakepath(path_to_exe, drive, dir, nullptr, nullptr); #ifdef SLIC3R_GUI -// https://wiki.qt.io/Cross_compiling_Mesa_for_Windows -// http://download.qt.io/development_releases/prebuilt/llvmpipe/windows/ - if (load_mesa) { - opengl_version_check.unload_opengl_dll(); - wchar_t path_to_mesa[MAX_PATH + 1] = { 0 }; - wcscpy(path_to_mesa, path_to_exe); - wcscat(path_to_mesa, L"mesa\\opengl32.dll"); - printf("Loading MESA OpenGL library: %S\n", path_to_mesa); - HINSTANCE hInstance_OpenGL = LoadLibraryExW(path_to_mesa, nullptr, 0); - if (hInstance_OpenGL == nullptr) { - printf("MESA OpenGL library was not loaded\n"); - } else - printf("MESA OpenGL library was loaded sucessfully\n"); - } + // https://wiki.qt.io/Cross_compiling_Mesa_for_Windows + // http://download.qt.io/development_releases/prebuilt/llvmpipe/windows/ + if (load_mesa) { + bool res = opengl_version_check.unload_opengl_dll(); + if (!res) { + MessageBox(nullptr, L"Error:QIDIStudio was unable to automatically switch to MESA OpenGL library.", + L"QIDIStudio Error", MB_OK); + return -1; + } + else { + wchar_t path_to_mesa[MAX_PATH + 1] = { 0 }; + wcscpy(path_to_mesa, path_to_exe); + wcscat(path_to_mesa, L"mesa\\opengl32.dll"); + printf("Loading MESA OpenGL library: %S\n", path_to_mesa); + HINSTANCE hInstance_OpenGL = LoadLibraryExW(path_to_mesa, nullptr, 0); + if (hInstance_OpenGL == nullptr) + printf("MESA OpenGL library was not loaded\n"); + else + printf("MESA OpenGL library was loaded sucessfully\n"); + } + } #endif /* SLIC3R_GUI */ - - - wchar_t path_to_slic3r[MAX_PATH + 1] = { 0 }; - wcscpy(path_to_slic3r, path_to_exe); - wcscat(path_to_slic3r, L"QIDIStudio.dll"); -// printf("Loading Slic3r library: %S\n", path_to_slic3r); - HINSTANCE hInstance_Slic3r = LoadLibraryExW(path_to_slic3r, nullptr, 0); - if (hInstance_Slic3r == nullptr) { - printf("QIDIStudio.dll was not loaded, error=%d\n", GetLastError()); - return -1; - } - - // resolve function address here - qidistu_main = (Slic3rMainFunc)GetProcAddress(hInstance_Slic3r, + wchar_t path_to_slic3r[MAX_PATH + 1] = { 0 }; + wcscpy(path_to_slic3r, path_to_exe); + wcscat(path_to_slic3r, L"QIDIStudio.dll"); + // printf("Loading Slic3r library: %S\n", path_to_slic3r); + HINSTANCE hInstance_Slic3r = LoadLibraryExW(path_to_slic3r, nullptr, 0); + if (hInstance_Slic3r == nullptr) { + printf("QIDIStudio.dll was not loaded, error=%d\n", GetLastError()); + return -1; + } + // resolve function address here + qidistu_main = (Slic3rMainFunc)GetProcAddress(hInstance_Slic3r, #ifdef _WIN64 - // there is just a single calling conversion, therefore no mangling of the function name. - "qidistu_main" + // there is just a single calling conversion, therefore no mangling of the function name. + "qidistu_main" #else // stdcall calling convention declaration - "_qidistu_main@8" + "_qidistu_main@8" #endif ); - if (qidistu_main == nullptr) { - printf("could not locate the function qidistu_main in QIDIStudio.dll\n"); - return -1; + if (qidistu_main == nullptr) { + printf("could not locate the function bambustu_main in QIDIStudio.dll\n"); + return -1; + } + // argc minus the trailing nullptr of the argv + return qidistu_main((int)argv_extended.size() - 1, argv_extended.data()); } - // argc minus the trailing nullptr of the argv - return qidistu_main((int)argv_extended.size() - 1, argv_extended.data()); -} } diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 0a4d07f..4849bd3 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -28,7 +28,7 @@ #include #include -#include +#include // Size of the binary STL header, free form. #define LABEL_SIZE 80 @@ -178,7 +178,7 @@ struct FaceProperty std::string to_string() const { std::string str; - // skip normal type facet to improve performance + // skip normal type facet to improve performance if (type > eNormal && type < eMaxNumFaceTypes) { str += std::to_string(type); if (area != 0.f) @@ -230,7 +230,16 @@ struct indexed_triangle_set size_t memsize() const { return sizeof(*this) + (sizeof(stl_triangle_vertex_indices) + sizeof(FaceProperty)) * indices.size() + sizeof(stl_vertex) * vertices.size(); } - + void add_indice(int f0, int f1, int f2, bool check_pts_size = false) + { + if (f0 < 0 || f1 < 0 || f2 < 0) { return; } + if (check_pts_size) { + auto pts_size = vertices.size(); + if (f0 < pts_size && f1 < pts_size && f2 < pts_size) { indices.emplace_back(f0, f1, f2); } + } else { + indices.emplace_back(f0, f1, f2); + } + } std::vector indices; std::vector vertices; std::vector properties; diff --git a/src/admesh/stlinit.cpp b/src/admesh/stlinit.cpp index 96532e2..a6c0c5c 100644 --- a/src/admesh/stlinit.cpp +++ b/src/admesh/stlinit.cpp @@ -121,7 +121,7 @@ static FILE *stl_open_count_facets(stl_file *stl, const char *file, unsigned int fclose(fp); return nullptr; } - + // Find the number of facets. char linebuf[100]; int num_lines = 1; @@ -136,9 +136,9 @@ static FILE *stl_open_count_facets(stl_file *stl, const char *file, unsigned int } rewind(fp); - + // Get the header. - int i = 0; + unsigned int i = 0; for (; i < custom_header_length && (stl->stats.header[i] = getc(fp)) != '\n'; ++ i) ; stl->stats.header[i] = '\0'; // Lose the '\n' stl->stats.header[custom_header_length] = '\0'; @@ -225,10 +225,10 @@ static bool stl_read(stl_file *stl, FILE *fp, int first_facet, bool first, Impor } catch (...){ } - + rewind(fp); } - + char normal_buf[3][32]; @@ -247,7 +247,7 @@ static bool stl_read(stl_file *stl, FILE *fp, int first_facet, bool first, Impor stl_facet facet; if (stl->stats.type == binary) { - + // Read a single facet from a binary .STL file. We assume little-endian architecture! if (fread(&facet, 1, SIZEOF_STL_FACET, fp) != SIZEOF_STL_FACET) diff --git a/src/build-utils/CMakeLists.txt b/src/build-utils/CMakeLists.txt index 464fd9c..2c67101 100644 --- a/src/build-utils/CMakeLists.txt +++ b/src/build-utils/CMakeLists.txt @@ -1,5 +1,5 @@ -option(SLIC3R_ENC_CHECK "Verify encoding of source files" 1) +option(SLIC3R_ENC_CHECK "Verify encoding of source files" 0) if (IS_CROSS_COMPILE) # Force disable due to cross compilation. This fact is already printed on cli for users diff --git a/src/clipper2/Clipper2Lib/include/clipper2/clipper.core.h b/src/clipper2/Clipper2Lib/include/clipper2/clipper.core.h index f8d6b42..aaa9f9a 100644 --- a/src/clipper2/Clipper2Lib/include/clipper2/clipper.core.h +++ b/src/clipper2/Clipper2Lib/include/clipper2/clipper.core.h @@ -10,643 +10,1067 @@ #ifndef CLIPPER_CORE_H #define CLIPPER_CORE_H -#include -#include +#include "clipper2/clipper.version.h" +#include #include #include #include #include -#include +#include +#include -//#define NO_EXCEPTIONS - -namespace Clipper2Lib +namespace Clipper2Lib { -#ifndef NO_EXCEPTIONS - static const char* precision_error = - "Precision exceeds the permitted range"; +#if (defined(__cpp_exceptions) && __cpp_exceptions) || (defined(__EXCEPTIONS) && __EXCEPTIONS) + + class Clipper2Exception : public std::exception { + public: + explicit Clipper2Exception(const char* description) : + m_descr(description) {} + virtual const char* what() const noexcept override { return m_descr.c_str(); } + private: + std::string m_descr; + }; + + static const char* precision_error = + "Precision exceeds the permitted range"; + static const char* range_error = + "Values exceed permitted range"; + static const char* scale_error = + "Invalid scale (either 0 or too large)"; + static const char* non_pair_error = + "There must be 2 values for each coordinate"; + static const char* undefined_error = + "There is an undefined error in Clipper2"; #endif - static double const PI = 3.141592653589793238; - static int64_t const MAX_COORD = LLONG_MAX / 2; - - //By far the most widely used filling rules for polygons are EvenOdd - //and NonZero, sometimes called Alternate and Winding respectively. - //https://en.wikipedia.org/wiki/Nonzero-rule - enum class FillRule { EvenOdd, NonZero, Positive, Negative }; - -// Point ------------------------------------------------------------------------ + // error codes (2^n) + const int precision_error_i = 1; // non-fatal + const int scale_error_i = 2; // non-fatal + const int non_pair_error_i = 4; // non-fatal + const int undefined_error_i = 32; // fatal + const int range_error_i = 64; + +#ifndef PI + static const double PI = 3.141592653589793238; +#endif + +#ifdef CLIPPER2_MAX_DECIMAL_PRECISION + const int CLIPPER2_MAX_DEC_PRECISION = CLIPPER2_MAX_DECIMAL_PRECISION; +#else + const int CLIPPER2_MAX_DEC_PRECISION = 8; // see Discussions #564 +#endif + + static const int64_t MAX_COORD = INT64_MAX >> 2; + static const int64_t MIN_COORD = -MAX_COORD; + static const int64_t INVALID = INT64_MAX; + const double max_coord = static_cast(MAX_COORD); + const double min_coord = static_cast(MIN_COORD); + + static const double MAX_DBL = (std::numeric_limits::max)(); + + static void DoError([[maybe_unused]] int error_code) + { +#if (defined(__cpp_exceptions) && __cpp_exceptions) || (defined(__EXCEPTIONS) && __EXCEPTIONS) + switch (error_code) + { + case precision_error_i: + throw Clipper2Exception(precision_error); + case scale_error_i: + throw Clipper2Exception(scale_error); + case non_pair_error_i: + throw Clipper2Exception(non_pair_error); + case undefined_error_i: + throw Clipper2Exception(undefined_error); + case range_error_i: + throw Clipper2Exception(range_error); + // Should never happen, but adding this to stop a compiler warning + default: + throw Clipper2Exception("Unknown error"); + } +#else + ++error_code; // only to stop compiler warning +#endif + } + + // can we call std::round on T? (default false) (#824) + template + struct is_round_invocable : std::false_type {}; + + template + struct is_round_invocable()))>> : std::true_type {}; + + + //By far the most widely used filling rules for polygons are EvenOdd + //and NonZero, sometimes called Alternate and Winding respectively. + //https://en.wikipedia.org/wiki/Nonzero-rule + enum class FillRule { EvenOdd, NonZero, Positive, Negative }; -template -struct Point { - T x; - T y; #ifdef USINGZ - int64_t z; - - template - inline void Init(const T2 x_ = 0, const T2 y_ = 0, const int64_t z_ = 0) - { - if constexpr (std::numeric_limits::is_integer && - !std::numeric_limits::is_integer) - { - x = static_cast(std::round(x_)); - y = static_cast(std::round(y_)); - z = z_; - } - else - { - x = static_cast(x_); - y = static_cast(y_); - z = z_; - } - } - - explicit Point() : x(0), y(0), z(0) {}; - - template - Point(const T2 x_, const T2 y_, const int64_t z_ = 0) - { - Init(x_, y_); - z = z_; - } - - template - explicit Point(const Point& p) - { - Init(p.x, p.y, p.z); - } - - Point operator * (const double scale) const - { - return Point(x * scale, y * scale, z); - } - - - friend std::ostream& operator<<(std::ostream& os, const Point& point) - { - os << point.x << "," << point.y << "," << point.z; - return os; - } - -#else - - template - inline void Init(const T2 x_ = 0, const T2 y_ = 0) - { - if constexpr (std::numeric_limits::is_integer && - !std::numeric_limits::is_integer) - { - x = static_cast(std::round(x_)); - y = static_cast(std::round(y_)); - } - else - { - x = static_cast(x_); - y = static_cast(y_); - } - } - - explicit Point() : x(0), y(0) {}; - - template - Point(const T2 x_, const T2 y_) { Init(x_, y_); } - - template - explicit Point(const Point& p) { Init(p.x, p.y); } - - Point operator * (const double scale) const - { - return Point(x * scale, y * scale); - } - - friend std::ostream& operator<<(std::ostream& os, const Point& point) - { - os << point.x << "," << point.y; - return os; - } + using z_type = int64_t; #endif - friend bool operator==(const Point &a, const Point &b) - { - return a.x == b.x && a.y == b.y; - } + // Point ------------------------------------------------------------------------ - friend bool operator!=(const Point& a, const Point& b) - { - return !(a == b); - } - - inline Point operator-() const - { - return Point(-x,-y); - } - - inline Point operator+(const Point &b) const - { - return Point(x+b.x, y+b.y); - } - - inline Point operator-(const Point &b) const - { - return Point(x-b.x, y-b.y); - } - - inline void Negate() { x = -x; y = -y; } - -}; - -//nb: using 'using' here (instead of typedef) as they can be used in templates -using Point64 = Point; -using PointD = Point; - -template -using Path = std::vector>; -template -using Paths = std::vector>; - -using Path64 = Path; -using PathD = Path; -using Paths64 = std::vector< Path64>; -using PathsD = std::vector< PathD>; - -template -std::ostream& operator << (std::ostream& outstream, const Path& path) -{ - if (!path.empty()) - { - auto pt = path.cbegin(), last = path.cend() - 1; - while (pt != last) - outstream << *pt++ << ", "; - outstream << *last << std::endl; - } - return outstream; -} - -template -std::ostream& operator << (std::ostream& outstream, const Paths& paths) -{ - for (auto p : paths) - outstream << p; - return outstream; -} - -template -inline Path ScalePath(const Path& path, double scale) -{ - Path result; - result.reserve(path.size()); + template + struct Point { + T x; + T y; #ifdef USINGZ - for (const Point& pt : path) - result.push_back(Point(pt.x * scale, pt.y * scale, pt.z)); + z_type z; + + template + inline void Init(const T2 x_ = 0, const T2 y_ = 0, const z_type z_ = 0) + { + if constexpr (std::is_integral_v && + is_round_invocable::value && !std::is_integral_v) + { + x = static_cast(std::round(x_)); + y = static_cast(std::round(y_)); + z = z_; + } + else + { + x = static_cast(x_); + y = static_cast(y_); + z = z_; + } + } + + explicit Point() : x(0), y(0), z(0) {}; + + template + Point(const T2 x_, const T2 y_, const z_type z_ = 0) + { + Init(x_, y_); + z = z_; + } + + template + explicit Point(const Point& p) + { + Init(p.x, p.y, p.z); + } + + template + explicit Point(const Point& p, z_type z_) + { + Init(p.x, p.y, z_); + } + + Point operator * (const double scale) const + { + return Point(x * scale, y * scale, z); + } + + void SetZ(const z_type z_value) { z = z_value; } + + friend std::ostream& operator<<(std::ostream& os, const Point& point) + { + os << point.x << "," << point.y << "," << point.z; + return os; + } + #else - for (const Point& pt : path) - result.push_back(Point(pt.x * scale, pt.y * scale)); -#endif - return result; -} -template -inline Paths ScalePaths(const Paths& paths, double scale) -{ - Paths result; - result.reserve(paths.size()); - for (const Path& path : paths) - result.push_back(ScalePath(path, scale)); - return result; -} + template + inline void Init(const T2 x_ = 0, const T2 y_ = 0) + { + if constexpr (std::is_integral_v && + is_round_invocable::value && !std::is_integral_v) + { + x = static_cast(std::round(x_)); + y = static_cast(std::round(y_)); + } + else + { + x = static_cast(x_); + y = static_cast(y_); + } + } -template -inline Path TransformPath(const Path& path) -{ - Path result; - result.reserve(path.size()); - std::transform(path.cbegin(), path.cend(), std::back_inserter(result), - [](const Point& pt) {return Point(pt); }); - return result; -} + explicit Point() : x(0), y(0) {}; -template -inline Paths TransformPaths(const Paths& paths) -{ - Paths result; - std::transform(paths.cbegin(), paths.cend(), std::back_inserter(result), - [](const Path& path) {return TransformPath(path); }); - return result; -} + template + Point(const T2 x_, const T2 y_) { Init(x_, y_); } -inline PathD Path64ToPathD(const Path64& path) -{ - return TransformPath(path); -} + template + explicit Point(const Point& p) { Init(p.x, p.y); } -inline PathsD Paths64ToPathsD(const Paths64& paths) -{ - return TransformPaths(paths); -} + Point operator * (const double scale) const + { + return Point(x * scale, y * scale); + } -inline Path64 PathDToPath64(const PathD& path) -{ - return TransformPath(path); -} - -inline Paths64 PathsDToPaths64(const PathsD& paths) -{ - return TransformPaths(paths); -} - -template -inline double Sqr(T val) -{ - return static_cast(val) * static_cast(val); -} - -template -inline bool NearEqual(const Point& p1, - const Point& p2, double max_dist_sqrd) -{ - return Sqr(p1.x - p2.x) + Sqr(p1.y - p2.y) < max_dist_sqrd; -} - -template -inline Path StripNearEqual(const Path& path, - double max_dist_sqrd, bool is_closed_path) -{ - if (path.size() == 0) return Path(); - Path result; - result.reserve(path.size()); - typename Path::const_iterator path_iter = path.cbegin(); - Point first_pt = *path_iter++, last_pt = first_pt; - result.push_back(first_pt); - for (; path_iter != path.cend(); ++path_iter) - { - if (!NearEqual(*path_iter, last_pt, max_dist_sqrd)) - { - last_pt = *path_iter; - result.push_back(last_pt); - } - } - if (!is_closed_path) return result; - while (result.size() > 1 && - NearEqual(result.back(), first_pt, max_dist_sqrd)) result.pop_back(); - return result; -} - -template -inline Paths StripNearEqual(const Paths& paths, - double max_dist_sqrd, bool is_closed_path) -{ - Paths result; - result.reserve(paths.size()); - for (typename Paths::const_iterator paths_citer = paths.cbegin(); - paths_citer != paths.cend(); ++paths_citer) - { - result.push_back(StripNearEqual(*paths_citer, max_dist_sqrd, is_closed_path)); - } - return result; -} - -template -inline Path StripDuplicates(const Path& path, bool is_closed_path) -{ - if (path.size() == 0) return Path(); - Path result; - result.reserve(path.size()); - typename Path::const_iterator path_iter = path.cbegin(); - Point first_pt = *path_iter++, last_pt = first_pt; - result.push_back(first_pt); - for (; path_iter != path.cend(); ++path_iter) - { - if (*path_iter != last_pt) - { - last_pt = *path_iter; - result.push_back(last_pt); - } - } - if (!is_closed_path) return result; - while (result.size() > 1 && result.back() == first_pt) result.pop_back(); - return result; -} - -template -inline Paths StripDuplicates(const Paths& paths, bool is_closed_path) -{ - Paths result; - result.reserve(paths.size()); - for (typename Paths::const_iterator paths_citer = paths.cbegin(); - paths_citer != paths.cend(); ++paths_citer) - { - result.push_back(StripDuplicates(*paths_citer, is_closed_path)); - } - return result; -} - -// Rect ------------------------------------------------------------------------ - -template -struct Rect; - -using Rect64 = Rect; -using RectD = Rect; - -template -struct Rect { - T left; - T top; - T right; - T bottom; - - Rect() : - left(0), - top(0), - right(0), - bottom(0) {} - - Rect(T l, T t, T r, T b) : - left(l), - top(t), - right(r), - bottom(b) {} - - - T Width() const { return right - left; } - T Height() const { return bottom - top; } - void Width(T width) { right = left + width; } - void Height(T height) { bottom = top + height; } - - Point MidPoint() const - { - return Point((left + right) / 2, (top + bottom) / 2); - } - - Path AsPath() const - { - Path result; - result.reserve(4); - result.push_back(Point(left, top)); - result.push_back(Point(right, top)); - result.push_back(Point(right, bottom)); - result.push_back(Point(left, bottom)); - return result; - } - - bool Contains(const Point& pt) const - { - return pt.x > left && pt.x < right&& pt.y > top && pt.y < bottom; - } - - bool Contains(const Rect& rec) const - { - return rec.left >= left && rec.right <= right && - rec.top >= top && rec.bottom <= bottom; - } - - void Scale(double scale) { - left *= scale; - top *= scale; - right *= scale; - bottom *= scale; - } - - bool IsEmpty() const { return bottom <= top || right <= left; }; - - bool Intersects(const Rect& rec) const - { - return (std::max(left, rec.left) < std::min(right, rec.right)) && - (std::max(top, rec.top) < std::min(bottom, rec.bottom)); - }; - - friend std::ostream &operator<<(std::ostream &os, const Rect &rect) { - os << "(" - << rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom - << ")"; - return os; - } -}; - -template -inline Rect ScaleRect(const Rect& rect, double scale) -{ - Rect result; - - if constexpr (std::numeric_limits::is_integer && - !std::numeric_limits::is_integer) - { - result.left = static_cast(std::round(rect.left * scale)); - result.top = static_cast(std::round(rect.top * scale)); - result.right = static_cast(std::round(rect.right * scale)); - result.bottom = static_cast(std::round(rect.bottom * scale)); - } - else - { - result.left = rect.left * scale; - result.top = rect.top * scale; - result.right = rect.right * scale; - result.bottom = rect.bottom * scale; - } - return result; -} - -// clipper2Exception --------------------------------------------------------- - -#ifndef NO_EXCEPTIONS -class Clipper2Exception : public std::exception { -public: - explicit Clipper2Exception(const char *description) : - m_descr(description) {} - virtual const char *what() const throw() { return m_descr.c_str(); } - -private: - std::string m_descr; -}; + friend std::ostream& operator<<(std::ostream& os, const Point& point) + { + os << point.x << "," << point.y; + return os; + } #endif -// Miscellaneous ------------------------------------------------------------ + friend bool operator==(const Point& a, const Point& b) + { + return a.x == b.x && a.y == b.y; + } -inline void CheckPrecision(int& precision) -{ - if (precision >= -8 && precision <= 8) return; -#ifdef NO_EXCEPTIONS - precision = precision > 8 ? 8 : -8; + friend bool operator!=(const Point& a, const Point& b) + { + return !(a == b); + } + + inline Point operator-() const + { + return Point(-x, -y); + } + + inline Point operator+(const Point& b) const + { + return Point(x + b.x, y + b.y); + } + + inline Point operator-(const Point& b) const + { + return Point(x - b.x, y - b.y); + } + + inline void Negate() { x = -x; y = -y; } + + }; + + //nb: using 'using' here (instead of typedef) as they can be used in templates + using Point64 = Point; + using PointD = Point; + + template + using Path = std::vector>; + template + using Paths = std::vector>; + + using Path64 = Path; + using PathD = Path; + using Paths64 = std::vector< Path64>; + using PathsD = std::vector< PathD>; + + static const Point64 InvalidPoint64 = Point64( + (std::numeric_limits::max)(), + (std::numeric_limits::max)()); + static const PointD InvalidPointD = PointD( + (std::numeric_limits::max)(), + (std::numeric_limits::max)()); + + template + static inline Point MidPoint(const Point& p1, const Point& p2) + { + Point result; + result.x = (p1.x + p2.x) / 2; + result.y = (p1.y + p2.y) / 2; + return result; + } + + // Rect ------------------------------------------------------------------------ + + template + struct Rect; + + using Rect64 = Rect; + using RectD = Rect; + + template + struct Rect { + T left; + T top; + T right; + T bottom; + + Rect(T l, T t, T r, T b) : + left(l), + top(t), + right(r), + bottom(b) {} + + Rect(bool is_valid = true) + { + if (is_valid) + { + left = right = top = bottom = 0; + } + else + { + left = top = (std::numeric_limits::max)(); + right = bottom = std::numeric_limits::lowest(); + } + } + + static Rect InvalidRect() + { + return { + (std::numeric_limits::max)(), + (std::numeric_limits::max)(), + std::numeric_limits::lowest(), + std::numeric_limits::lowest() }; + } + + bool IsValid() const { return left != (std::numeric_limits::max)(); } + + T Width() const { return right - left; } + T Height() const { return bottom - top; } + void Width(T width) { right = left + width; } + void Height(T height) { bottom = top + height; } + + Point MidPoint() const + { + return Point((left + right) / 2, (top + bottom) / 2); + } + + Path AsPath() const + { + Path result; + result.reserve(4); + result.emplace_back(left, top); + result.emplace_back(right, top); + result.emplace_back(right, bottom); + result.emplace_back(left, bottom); + return result; + } + + bool Contains(const Point& pt) const + { + return pt.x > left && pt.x < right&& pt.y > top && pt.y < bottom; + } + + bool Contains(const Rect& rec) const + { + return rec.left >= left && rec.right <= right && + rec.top >= top && rec.bottom <= bottom; + } + + void Scale(double scale) { + left *= scale; + top *= scale; + right *= scale; + bottom *= scale; + } + + bool IsEmpty() const { return bottom <= top || right <= left; }; + + bool Intersects(const Rect& rec) const + { + return ((std::max)(left, rec.left) <= (std::min)(right, rec.right)) && + ((std::max)(top, rec.top) <= (std::min)(bottom, rec.bottom)); + }; + + bool operator==(const Rect& other) const { + return left == other.left && right == other.right && + top == other.top && bottom == other.bottom; + } + + Rect& operator+=(const Rect& other) + { + left = (std::min)(left, other.left); + top = (std::min)(top, other.top); + right = (std::max)(right, other.right); + bottom = (std::max)(bottom, other.bottom); + return *this; + } + + Rect operator+(const Rect& other) const + { + Rect result = *this; + result += other; + return result; + } + + friend std::ostream& operator<<(std::ostream& os, const Rect& rect) { + os << "(" << rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom << ") "; + return os; + } + }; + + template + inline Rect ScaleRect(const Rect& rect, double scale) + { + Rect result; + + if constexpr (std::is_integral_v && + is_round_invocable::value && !std::is_integral_v) + { + result.left = static_cast(std::round(rect.left * scale)); + result.top = static_cast(std::round(rect.top * scale)); + result.right = static_cast(std::round(rect.right * scale)); + result.bottom = static_cast(std::round(rect.bottom * scale)); + } + else + { + result.left = static_cast(rect.left * scale); + result.top = static_cast(rect.top * scale); + result.right = static_cast(rect.right * scale); + result.bottom = static_cast(rect.bottom * scale); + } + return result; + } + + static const Rect64 InvalidRect64 = Rect64::InvalidRect(); + static const RectD InvalidRectD = RectD::InvalidRect(); + + template + Rect GetBounds(const Path& path) + { + T xmin = (std::numeric_limits::max)(); + T ymin = (std::numeric_limits::max)(); + T xmax = std::numeric_limits::lowest(); + T ymax = std::numeric_limits::lowest(); + for (const auto& p : path) + { + if (p.x < xmin) xmin = p.x; + if (p.x > xmax) xmax = p.x; + if (p.y < ymin) ymin = p.y; + if (p.y > ymax) ymax = p.y; + } + return Rect(xmin, ymin, xmax, ymax); + } + + template + Rect GetBounds(const Paths& paths) + { + T xmin = (std::numeric_limits::max)(); + T ymin = (std::numeric_limits::max)(); + T xmax = std::numeric_limits::lowest(); + T ymax = std::numeric_limits::lowest(); + for (const Path& path : paths) + for (const Point& p : path) + { + if (p.x < xmin) xmin = p.x; + if (p.x > xmax) xmax = p.x; + if (p.y < ymin) ymin = p.y; + if (p.y > ymax) ymax = p.y; + } + return Rect(xmin, ymin, xmax, ymax); + } + + template + Rect GetBounds(const Path& path) + { + T xmin = (std::numeric_limits::max)(); + T ymin = (std::numeric_limits::max)(); + T xmax = std::numeric_limits::lowest(); + T ymax = std::numeric_limits::lowest(); + for (const auto& p : path) + { + if (p.x < xmin) xmin = static_cast(p.x); + if (p.x > xmax) xmax = static_cast(p.x); + if (p.y < ymin) ymin = static_cast(p.y); + if (p.y > ymax) ymax = static_cast(p.y); + } + return Rect(xmin, ymin, xmax, ymax); + } + + template + Rect GetBounds(const Paths& paths) + { + T xmin = (std::numeric_limits::max)(); + T ymin = (std::numeric_limits::max)(); + T xmax = std::numeric_limits::lowest(); + T ymax = std::numeric_limits::lowest(); + for (const Path& path : paths) + for (const Point& p : path) + { + if (p.x < xmin) xmin = static_cast(p.x); + if (p.x > xmax) xmax = static_cast(p.x); + if (p.y < ymin) ymin = static_cast(p.y); + if (p.y > ymax) ymax = static_cast(p.y); + } + return Rect(xmin, ymin, xmax, ymax); + } + + template + std::ostream& operator << (std::ostream& outstream, const Path& path) + { + if (!path.empty()) + { + auto pt = path.cbegin(), last = path.cend() - 1; + while (pt != last) + outstream << *pt++ << ", "; + outstream << *last << std::endl; + } + return outstream; + } + + template + std::ostream& operator << (std::ostream& outstream, const Paths& paths) + { + for (auto p : paths) + outstream << p; + return outstream; + } + + + template + inline Path ScalePath(const Path& path, + double scale_x, double scale_y, int& error_code) + { + Path result; + if (scale_x == 0 || scale_y == 0) + { + error_code |= scale_error_i; + DoError(scale_error_i); + // if no exception, treat as non-fatal error + if (scale_x == 0) scale_x = 1.0; + if (scale_y == 0) scale_y = 1.0; + } + + result.reserve(path.size()); +#ifdef USINGZ + std::transform(path.begin(), path.end(), back_inserter(result), + [scale_x, scale_y](const auto& pt) + { return Point(pt.x * scale_x, pt.y * scale_y, pt.z); }); #else - throw Clipper2Exception(precision_error); + std::transform(path.begin(), path.end(), back_inserter(result), + [scale_x, scale_y](const auto& pt) + { return Point(pt.x * scale_x, pt.y * scale_y); }); #endif + return result; + } + + template + inline Path ScalePath(const Path& path, + double scale, int& error_code) + { + return ScalePath(path, scale, scale, error_code); + } + + template + inline Paths ScalePaths(const Paths& paths, + double scale_x, double scale_y, int& error_code) + { + Paths result; + + if constexpr (std::is_integral_v) + { + RectD r = GetBounds(paths); + if ((r.left * scale_x) < min_coord || + (r.right * scale_x) > max_coord || + (r.top * scale_y) < min_coord || + (r.bottom * scale_y) > max_coord) + { + error_code |= range_error_i; + DoError(range_error_i); + return result; // empty path + } + } + + result.reserve(paths.size()); + std::transform(paths.begin(), paths.end(), back_inserter(result), + [=, &error_code](const auto& path) + { return ScalePath(path, scale_x, scale_y, error_code); }); + return result; + } + + template + inline Paths ScalePaths(const Paths& paths, + double scale, int& error_code) + { + return ScalePaths(paths, scale, scale, error_code); + } + + template + inline Path TransformPath(const Path& path) + { + Path result; + result.reserve(path.size()); + std::transform(path.cbegin(), path.cend(), std::back_inserter(result), + [](const Point& pt) {return Point(pt); }); + return result; + } + + template + inline Paths TransformPaths(const Paths& paths) + { + Paths result; + std::transform(paths.cbegin(), paths.cend(), std::back_inserter(result), + [](const Path& path) {return TransformPath(path); }); + return result; + } + + template + inline double Sqr(T val) + { + return static_cast(val) * static_cast(val); + } + + template + inline bool NearEqual(const Point& p1, + const Point& p2, double max_dist_sqrd) + { + return Sqr(p1.x - p2.x) + Sqr(p1.y - p2.y) < max_dist_sqrd; + } + + template + inline Path StripNearEqual(const Path& path, + double max_dist_sqrd, bool is_closed_path) + { + if (path.size() == 0) return Path(); + Path result; + result.reserve(path.size()); + typename Path::const_iterator path_iter = path.cbegin(); + Point first_pt = *path_iter++, last_pt = first_pt; + result.emplace_back(first_pt); + for (; path_iter != path.cend(); ++path_iter) + { + if (!NearEqual(*path_iter, last_pt, max_dist_sqrd)) + { + last_pt = *path_iter; + result.emplace_back(last_pt); + } + } + if (!is_closed_path) return result; + while (result.size() > 1 && + NearEqual(result.back(), first_pt, max_dist_sqrd)) result.pop_back(); + return result; + } + + template + inline Paths StripNearEqual(const Paths& paths, + double max_dist_sqrd, bool is_closed_path) + { + Paths result; + result.reserve(paths.size()); + for (typename Paths::const_iterator paths_citer = paths.cbegin(); + paths_citer != paths.cend(); ++paths_citer) + { + result.emplace_back(std::move(StripNearEqual(*paths_citer, max_dist_sqrd, is_closed_path))); + } + return result; + } + + template + inline void StripDuplicates( Path& path, bool is_closed_path) + { + //https://stackoverflow.com/questions/1041620/whats-the-most-efficient-way-to-erase-duplicates-and-sort-a-vector#:~:text=Let%27s%20compare%20three%20approaches%3A + path.erase(std::unique(path.begin(), path.end()), path.end()); + if (is_closed_path) + while (path.size() > 1 && path.back() == path.front()) path.pop_back(); + } + + template + inline void StripDuplicates( Paths& paths, bool is_closed_path) + { + for (typename Paths::iterator paths_citer = paths.begin(); + paths_citer != paths.end(); ++paths_citer) + { + StripDuplicates(*paths_citer, is_closed_path); + } + } + + // Miscellaneous ------------------------------------------------------------ + + inline void CheckPrecisionRange(int& precision, int& error_code) + { + if (precision >= -CLIPPER2_MAX_DEC_PRECISION && + precision <= CLIPPER2_MAX_DEC_PRECISION) return; + error_code |= precision_error_i; // non-fatal error + DoError(precision_error_i); // does nothing when exceptions are disabled + precision = precision > 0 ? CLIPPER2_MAX_DEC_PRECISION : -CLIPPER2_MAX_DEC_PRECISION; + } + + inline void CheckPrecisionRange(int& precision) + { + int error_code = 0; + CheckPrecisionRange(precision, error_code); + } + + inline int TriSign(int64_t x) // returns 0, 1 or -1 + { + return (x > 0) - (x < 0); + } + + struct MultiplyUInt64Result + { + const uint64_t result = 0; + const uint64_t carry = 0; + + bool operator==(const MultiplyUInt64Result& other) const + { + return result == other.result && carry == other.carry; + }; + }; + + inline MultiplyUInt64Result Multiply(uint64_t a, uint64_t b) // #834, #835 + { + const auto lo = [](uint64_t x) { return x & 0xFFFFFFFF; }; + const auto hi = [](uint64_t x) { return x >> 32; }; + + const uint64_t x1 = lo(a) * lo(b); + const uint64_t x2 = hi(a) * lo(b) + hi(x1); + const uint64_t x3 = lo(a) * hi(b) + lo(x2); + const uint64_t result = lo(x3) << 32 | lo(x1); + const uint64_t carry = hi(a) * hi(b) + hi(x2) + hi(x3); + + return { result, carry }; + } + + // returns true if (and only if) a * b == c * d + inline bool ProductsAreEqual(int64_t a, int64_t b, int64_t c, int64_t d) + { +#if (defined(__clang__) || defined(__GNUC__)) && UINTPTR_MAX >= UINT64_MAX + const auto ab = static_cast<__int128_t>(a) * static_cast<__int128_t>(b); + const auto cd = static_cast<__int128_t>(c) * static_cast<__int128_t>(d); + return ab == cd; +#else + // nb: unsigned values needed for calculating overflow carry + const auto abs_a = static_cast(std::abs(a)); + const auto abs_b = static_cast(std::abs(b)); + const auto abs_c = static_cast(std::abs(c)); + const auto abs_d = static_cast(std::abs(d)); + + const auto abs_ab = Multiply(abs_a, abs_b); + const auto abs_cd = Multiply(abs_c, abs_d); + + // nb: it's important to differentiate 0 values here from other values + const auto sign_ab = TriSign(a) * TriSign(b); + const auto sign_cd = TriSign(c) * TriSign(d); + + return abs_ab == abs_cd && sign_ab == sign_cd; +#endif + } + + template + inline bool IsCollinear(const Point& pt1, + const Point& sharedPt, const Point& pt2) // #777 + { + const auto a = sharedPt.x - pt1.x; + const auto b = pt2.y - sharedPt.y; + const auto c = sharedPt.y - pt1.y; + const auto d = pt2.x - sharedPt.x; + // When checking for collinearity with very large coordinate values + // then ProductsAreEqual is more accurate than using CrossProduct. + return ProductsAreEqual(a, b, c, d); + } + + + template + inline double CrossProduct(const Point& pt1, const Point& pt2, const Point& pt3) { + return (static_cast(pt2.x - pt1.x) * static_cast(pt3.y - + pt2.y) - static_cast(pt2.y - pt1.y) * static_cast(pt3.x - pt2.x)); + } + + template + inline double CrossProduct(const Point& vec1, const Point& vec2) + { + return static_cast(vec1.y * vec2.x) - static_cast(vec2.y * vec1.x); + } + + template + inline double DotProduct(const Point& pt1, const Point& pt2, const Point& pt3) { + return (static_cast(pt2.x - pt1.x) * static_cast(pt3.x - pt2.x) + + static_cast(pt2.y - pt1.y) * static_cast(pt3.y - pt2.y)); + } + + template + inline double DotProduct(const Point& vec1, const Point& vec2) + { + return static_cast(vec1.x * vec2.x) + static_cast(vec1.y * vec2.y); + } + + template + inline double DistanceSqr(const Point pt1, const Point pt2) + { + return Sqr(pt1.x - pt2.x) + Sqr(pt1.y - pt2.y); + } + + template + inline double PerpendicDistFromLineSqrd(const Point& pt, + const Point& line1, const Point& line2) + { + //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²) + //see https://en.wikipedia.org/wiki/Perpendicular_distance + double a = static_cast(pt.x - line1.x); + double b = static_cast(pt.y - line1.y); + double c = static_cast(line2.x - line1.x); + double d = static_cast(line2.y - line1.y); + if (c == 0 && d == 0) return 0; + return Sqr(a * d - c * b) / (c * c + d * d); + } + + template + inline double Area(const Path& path) + { + size_t cnt = path.size(); + if (cnt < 3) return 0.0; + double a = 0.0; + typename Path::const_iterator it1, it2 = path.cend() - 1, stop = it2; + if (!(cnt & 1)) ++stop; + for (it1 = path.cbegin(); it1 != stop;) + { + a += static_cast(it2->y + it1->y) * (it2->x - it1->x); + it2 = it1 + 1; + a += static_cast(it1->y + it2->y) * (it1->x - it2->x); + it1 += 2; + } + if (cnt & 1) + a += static_cast(it2->y + it1->y) * (it2->x - it1->x); + return (a * 0.5); + } + + template + inline double Area(const Paths& paths) + { + double a = 0.0; + for (typename Paths::const_iterator paths_iter = paths.cbegin(); + paths_iter != paths.cend(); ++paths_iter) + { + a += Area(*paths_iter); + } + return a; + } + + template + inline bool IsPositive(const Path& poly) + { + // A curve has positive orientation [and area] if a region 'R' + // is on the left when traveling around the outside of 'R'. + //https://mathworld.wolfram.com/CurveOrientation.html + //nb: This statement is premised on using Cartesian coordinates + return Area(poly) >= 0; + } + +#if CLIPPER2_HI_PRECISION + // caution: this will compromise performance + // https://github.com/AngusJohnson/Clipper2/issues/317#issuecomment-1314023253 + // See also CPP/BenchMark/GetIntersectPtBenchmark.cpp + #define CC_MIN(x,y) ((x)>(y)?(y):(x)) + #define CC_MAX(x,y) ((x)<(y)?(y):(x)) + template + inline bool GetSegmentIntersectPt(const Point& ln1a, const Point& ln1b, + const Point& ln2a, const Point& ln2b, Point& ip) + { + double ln1dy = static_cast(ln1b.y - ln1a.y); + double ln1dx = static_cast(ln1a.x - ln1b.x); + double ln2dy = static_cast(ln2b.y - ln2a.y); + double ln2dx = static_cast(ln2a.x - ln2b.x); + double det = (ln2dy * ln1dx) - (ln1dy * ln2dx); + if (det == 0.0) return false; + T bb0minx = CC_MIN(ln1a.x, ln1b.x); + T bb0miny = CC_MIN(ln1a.y, ln1b.y); + T bb0maxx = CC_MAX(ln1a.x, ln1b.x); + T bb0maxy = CC_MAX(ln1a.y, ln1b.y); + T bb1minx = CC_MIN(ln2a.x, ln2b.x); + T bb1miny = CC_MIN(ln2a.y, ln2b.y); + T bb1maxx = CC_MAX(ln2a.x, ln2b.x); + T bb1maxy = CC_MAX(ln2a.y, ln2b.y); + + if constexpr (std::is_integral_v) + { + int64_t originx = (CC_MIN(bb0maxx, bb1maxx) + CC_MAX(bb0minx, bb1minx)) >> 1; + int64_t originy = (CC_MIN(bb0maxy, bb1maxy) + CC_MAX(bb0miny, bb1miny)) >> 1; + double ln0c = (ln1dy * static_cast(ln1a.x - originx)) + + (ln1dx * static_cast(ln1a.y - originy)); + double ln1c = (ln2dy * static_cast(ln2a.x - originx)) + + (ln2dx * static_cast(ln2a.y - originy)); + double hitx = ((ln1dx * ln1c) - (ln2dx * ln0c)) / det; + double hity = ((ln2dy * ln0c) - (ln1dy * ln1c)) / det; + + ip.x = originx + (T)nearbyint(hitx); + ip.y = originy + (T)nearbyint(hity); + } + else + { + double originx = (CC_MIN(bb0maxx, bb1maxx) + CC_MAX(bb0minx, bb1minx)) / 2.0; + double originy = (CC_MIN(bb0maxy, bb1maxy) + CC_MAX(bb0miny, bb1miny)) / 2.0; + double ln0c = (ln1dy * static_cast(ln1a.x - originx)) + + (ln1dx * static_cast(ln1a.y - originy)); + double ln1c = (ln2dy * static_cast(ln2a.x - originx)) + + (ln2dx * static_cast(ln2a.y - originy)); + double hitx = ((ln1dx * ln1c) - (ln2dx * ln0c)) / det; + double hity = ((ln2dy * ln0c) - (ln1dy * ln1c)) / det; + + ip.x = originx + static_cast(hitx); + ip.y = originy + static_cast(hity); + } + return true; } +#else + template + inline bool GetSegmentIntersectPt(const Point& ln1a, const Point& ln1b, + const Point& ln2a, const Point& ln2b, Point& ip) + { + // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection + double dx1 = static_cast(ln1b.x - ln1a.x); + double dy1 = static_cast(ln1b.y - ln1a.y); + double dx2 = static_cast(ln2b.x - ln2a.x); + double dy2 = static_cast(ln2b.y - ln2a.y); -template -inline double CrossProduct(const Point& pt1, const Point& pt2, const Point& pt3) { - return (static_cast(pt2.x - pt1.x) * static_cast(pt3.y - - pt2.y) - static_cast(pt2.y - pt1.y) * static_cast(pt3.x - pt2.x)); -} + double det = dy1 * dx2 - dy2 * dx1; + if (det == 0.0) return false; + double t = ((ln1a.x - ln2a.x) * dy2 - (ln1a.y - ln2a.y) * dx2) / det; + if (t <= 0.0) ip = ln1a; + else if (t >= 1.0) ip = ln1b; + else + { + ip.x = static_cast(ln1a.x + t * dx1); + ip.y = static_cast(ln1a.y + t * dy1); + } + return true; + } +#endif -template -inline double CrossProduct(const Point& vec1, const Point& vec2) -{ - return static_cast(vec1.y * vec2.x) - static_cast(vec2.y * vec1.x); -} - -template -inline double DotProduct(const Point& pt1, const Point& pt2, const Point& pt3) { - return (static_cast(pt2.x - pt1.x) * static_cast(pt3.x - pt2.x) + - static_cast(pt2.y - pt1.y) * static_cast(pt3.y - pt2.y)); -} - -template -inline double DotProduct(const Point& vec1, const Point& vec2) -{ - return static_cast(vec1.x * vec2.x) + static_cast(vec1.y * vec2.y); -} - -template -inline double DistanceSqr(const Point pt1, const Point pt2) -{ - return Sqr(pt1.x - pt2.x) + Sqr(pt1.y - pt2.y); -} - -template -inline double DistanceFromLineSqrd(const Point& pt, const Point& ln1, const Point& ln2) -{ - //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²) - //see http://en.wikipedia.org/wiki/Perpendicular_distance - double A = static_cast(ln1.y - ln2.y); - double B = static_cast(ln2.x - ln1.x); - double C = A * ln1.x + B * ln1.y; - C = A * pt.x + B * pt.y - C; - return (C * C) / (A * A + B * B); -} - -template -inline double Area(const Path& path) -{ - size_t cnt = path.size(); - if (cnt < 3) return 0.0; - double a = 0.0; - typename Path::const_iterator it1, it2 = path.cend() - 1, stop = it2; - if (!(cnt & 1)) ++stop; - for (it1 = path.cbegin(); it1 != stop;) - { - a += static_cast(it2->y + it1->y) * (it2->x - it1->x); - it2 = it1 + 1; - a += static_cast(it1->y + it2->y) * (it1->x - it2->x); - it1 += 2; - } - if (cnt & 1) - a += static_cast(it2->y + it1->y) * (it2->x - it1->x); - return a * 0.5; -} - -template -inline double Area(const Paths& paths) -{ - double a = 0.0; - for (typename Paths::const_iterator paths_iter = paths.cbegin(); - paths_iter != paths.cend(); ++paths_iter) - { - a += Area(*paths_iter); - } - return a; -} - -template -inline bool IsPositive(const Path& poly) -{ - // A curve has positive orientation [and area] if a region 'R' - // is on the left when traveling around the outside of 'R'. - //https://mathworld.wolfram.com/CurveOrientation.html - //nb: This statement is premised on using Cartesian coordinates - return Area(poly) >= 0; -} - -inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b, - const Point64& seg2a, const Point64& seg2b, bool inclusive = false) -{ - if (inclusive) - { - double res1 = CrossProduct(seg1a, seg2a, seg2b); - double res2 = CrossProduct(seg1b, seg2a, seg2b); - if (res1 * res2 > 0) return false; - double res3 = CrossProduct(seg2a, seg1a, seg1b); - double res4 = CrossProduct(seg2b, seg1a, seg1b); - if (res3 * res4 > 0) return false; - return (res1 || res2 || res3 || res4); // ensures not collinear - } - else { - double dx1 = static_cast(seg1a.x - seg1b.x); - double dy1 = static_cast(seg1a.y - seg1b.y); - double dx2 = static_cast(seg2a.x - seg2b.x); - double dy2 = static_cast(seg2a.y - seg2b.y); - return (((dy1 * (seg2a.x - seg1a.x) - dx1 * (seg2a.y - seg1a.y)) * - (dy1 * (seg2b.x - seg1a.x) - dx1 * (seg2b.y - seg1a.y)) < 0) && - ((dy2 * (seg1a.x - seg2a.x) - dx2 * (seg1a.y - seg2a.y)) * - (dy2 * (seg1b.x - seg2a.x) - dx2 * (seg1b.y - seg2a.y)) < 0)); - } -} + template + inline Point TranslatePoint(const Point& pt, double dx, double dy) + { +#ifdef USINGZ + return Point(pt.x + dx, pt.y + dy, pt.z); +#else + return Point(pt.x + dx, pt.y + dy); +#endif + } -enum class PointInPolygonResult { IsOn, IsInside, IsOutside }; + template + inline Point ReflectPoint(const Point& pt, const Point& pivot) + { +#ifdef USINGZ + return Point(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y), pt.z); +#else + return Point(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y)); +#endif + } -template -inline PointInPolygonResult PointInPolygon(const Point& pt, const Path& polygon) -{ - if (polygon.size() < 3) - return PointInPolygonResult::IsOutside; + template + inline int GetSign(const T& val) + { + if (!val) return 0; + return (val > 0) ? 1 : -1; + } - int val = 0; - typename Path::const_iterator start = polygon.cbegin(), cit = start; - typename Path::const_iterator cend = polygon.cend(), pit = cend - 1; + inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b, + const Point64& seg2a, const Point64& seg2b, bool inclusive = false) + { + if (inclusive) + { + double res1 = CrossProduct(seg1a, seg2a, seg2b); + double res2 = CrossProduct(seg1b, seg2a, seg2b); + if (res1 * res2 > 0) return false; + double res3 = CrossProduct(seg2a, seg1a, seg1b); + double res4 = CrossProduct(seg2b, seg1a, seg1b); + if (res3 * res4 > 0) return false; + return (res1 || res2 || res3 || res4); // ensures not collinear + } + else { + return (GetSign(CrossProduct(seg1a, seg2a, seg2b)) * + GetSign(CrossProduct(seg1b, seg2a, seg2b)) < 0) && + (GetSign(CrossProduct(seg2a, seg1a, seg1b)) * + GetSign(CrossProduct(seg2b, seg1a, seg1b)) < 0); + } + } - while (pit->y == pt.y) - { - if (pit == start) return PointInPolygonResult::IsOutside; - --pit; - } - bool is_above = pit->y < pt.y; + template + inline Point GetClosestPointOnSegment(const Point& offPt, + const Point& seg1, const Point& seg2) + { + if (seg1.x == seg2.x && seg1.y == seg2.y) return seg1; + double dx = static_cast(seg2.x - seg1.x); + double dy = static_cast(seg2.y - seg1.y); + double q = + (static_cast(offPt.x - seg1.x) * dx + + static_cast(offPt.y - seg1.y) * dy) / + (Sqr(dx) + Sqr(dy)); + if (q < 0) q = 0; else if (q > 1) q = 1; + if constexpr (std::is_integral_v) + return Point( + seg1.x + static_cast(nearbyint(q * dx)), + seg1.y + static_cast(nearbyint(q * dy))); + else + return Point( + seg1.x + static_cast(q * dx), + seg1.y + static_cast(q * dy)); + } - while (cit != cend) - { - if (is_above) - { - while (cit != cend && cit->y < pt.y) ++cit; - if (cit == cend) break; - } - else - { - while (cit != cend && cit->y > pt.y) ++cit; - if (cit == cend) break; - } + enum class PointInPolygonResult { IsOn, IsInside, IsOutside }; - if (cit == start) pit = cend - 1; - else pit = cit - 1; + template + inline PointInPolygonResult PointInPolygon(const Point& pt, const Path& polygon) + { + if (polygon.size() < 3) + return PointInPolygonResult::IsOutside; - if (cit->y == pt.y) - { - if (cit->x == pt.x || (cit->y == pit->y && - ((pt.x < pit->x) != (pt.x < cit->x)))) - return PointInPolygonResult::IsOn; - ++cit; - continue; - } + int val = 0; + typename Path::const_iterator cbegin = polygon.cbegin(), first = cbegin, curr, prev; + typename Path::const_iterator cend = polygon.cend(); - if (pt.x < cit->x && pt.x < pit->x) - { - // we're only interested in edges crossing on the left - } - else if (pt.x > pit->x && pt.x > cit->x) - val = 1 - val; // toggle val - else - { - double d = CrossProduct(*pit, *cit, pt); - if (d == 0) return PointInPolygonResult::IsOn; - if ((d < 0) == is_above) val = 1 - val; - } - is_above = !is_above; - ++cit; - } - return (val == 0) ? - PointInPolygonResult::IsOutside : - PointInPolygonResult::IsInside; -} + while (first != cend && first->y == pt.y) ++first; + if (first == cend) // not a proper polygon + return PointInPolygonResult::IsOutside; + + bool is_above = first->y < pt.y, starting_above = is_above; + curr = first +1; + while (true) + { + if (curr == cend) + { + if (cend == first || first == cbegin) break; + cend = first; + curr = cbegin; + } + + if (is_above) + { + while (curr != cend && curr->y < pt.y) ++curr; + if (curr == cend) continue; + } + else + { + while (curr != cend && curr->y > pt.y) ++curr; + if (curr == cend) continue; + } + + if (curr == cbegin) + prev = polygon.cend() - 1; //nb: NOT cend (since might equal first) + else + prev = curr - 1; + + if (curr->y == pt.y) + { + if (curr->x == pt.x || + (curr->y == prev->y && + ((pt.x < prev->x) != (pt.x < curr->x)))) + return PointInPolygonResult::IsOn; + ++curr; + if (curr == first) break; + continue; + } + + if (pt.x < curr->x && pt.x < prev->x) + { + // we're only interested in edges crossing on the left + } + else if (pt.x > prev->x && pt.x > curr->x) + val = 1 - val; // toggle val + else + { + double d = CrossProduct(*prev, *curr, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + is_above = !is_above; + ++curr; + } + + if (is_above != starting_above) + { + cend = polygon.cend(); + if (curr == cend) curr = cbegin; + if (curr == cbegin) prev = cend - 1; + else prev = curr - 1; + double d = CrossProduct(*prev, *curr, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + + return (val == 0) ? + PointInPolygonResult::IsOutside : + PointInPolygonResult::IsInside; + } } // namespace diff --git a/src/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h b/src/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h index 67383f2..e3ab826 100644 --- a/src/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/src/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h @@ -10,16 +10,10 @@ #ifndef CLIPPER_ENGINE_H #define CLIPPER_ENGINE_H -constexpr auto CLIPPER2_VERSION = "1.0.6"; - -#include +#include "clipper2/clipper.core.h" #include -#include -#include #include -#include -#include -#include "clipper.core.h" +#include namespace Clipper2Lib { @@ -29,15 +23,16 @@ namespace Clipper2Lib { struct Vertex; struct LocalMinima; struct OutRec; - struct Joiner; + struct HorzSegment; //Note: all clipping operations except for Difference are commutative. - enum class ClipType { None, Intersection, Union, Difference, Xor }; + enum class ClipType { NoClip, Intersection, Union, Difference, Xor }; enum class PathType { Subject, Clip }; + enum class JoinWith { NoJoin, Left, Right }; enum class VertexFlags : uint32_t { - None = 0, OpenStart = 1, OpenEnd = 2, LocalMax = 4, LocalMin = 8 + Empty = 0, OpenStart = 1, OpenEnd = 2, LocalMax = 4, LocalMin = 8 }; constexpr enum VertexFlags operator &(enum VertexFlags a, enum VertexFlags b) @@ -54,7 +49,7 @@ namespace Clipper2Lib { Point64 pt; Vertex* next = nullptr; Vertex* prev = nullptr; - VertexFlags flags = VertexFlags::None; + VertexFlags flags = VertexFlags::Empty; }; struct OutPt { @@ -62,7 +57,7 @@ namespace Clipper2Lib { OutPt* next = nullptr; OutPt* prev = nullptr; OutRec* outrec; - Joiner* joiner = nullptr; + HorzSegment* horz = nullptr; OutPt(const Point64& pt_, OutRec* outrec_): pt(pt_), outrec(outrec_) { next = this; @@ -84,15 +79,21 @@ namespace Clipper2Lib { struct OutRec { size_t idx = 0; OutRec* owner = nullptr; - OutRecList* splits = nullptr; Active* front_edge = nullptr; Active* back_edge = nullptr; OutPt* pts = nullptr; PolyPath* polypath = nullptr; + OutRecList* splits = nullptr; + OutRec* recursive_split = nullptr; Rect64 bounds = {}; Path64 path; bool is_open = false; - ~OutRec() { if (splits) delete splits; }; + + ~OutRec() { + if (splits) delete splits; + // nb: don't delete the split pointers + // as these are owned by ClipperBase's outrec_list_ + }; }; /////////////////////////////////////////////////////////////////// @@ -124,6 +125,7 @@ namespace Clipper2Lib { Vertex* vertex_top = nullptr; LocalMinima* local_min = nullptr; // the bottom of an edge 'bound' (also Vatti) bool is_left_bound = false; + JoinWith join_with = JoinWith::NoJoin; }; struct LocalMinima { @@ -138,26 +140,58 @@ namespace Clipper2Lib { Point64 pt; Active* edge1; Active* edge2; - IntersectNode() : pt(Point64(0, 0)), edge1(NULL), edge2(NULL) {} + IntersectNode() : pt(Point64(0,0)), edge1(NULL), edge2(NULL) {} IntersectNode(Active* e1, Active* e2, Point64& pt_) : - pt(pt_), edge1(e1), edge2(e2) - { - } + pt(pt_), edge1(e1), edge2(e2) {} + }; + + struct HorzSegment { + OutPt* left_op; + OutPt* right_op = nullptr; + bool left_to_right = true; + HorzSegment() : left_op(nullptr) { } + explicit HorzSegment(OutPt* op) : left_op(op) { } + }; + + struct HorzJoin { + OutPt* op1 = nullptr; + OutPt* op2 = nullptr; + HorzJoin() {}; + explicit HorzJoin(OutPt* ltr, OutPt* rtl) : op1(ltr), op2(rtl) { } }; #ifdef USINGZ - typedef std::function ZCallback64; typedef std::function ZCallbackD; #endif + typedef std::vector HorzSegmentList; + typedef std::unique_ptr LocalMinima_ptr; + typedef std::vector LocalMinimaList; + typedef std::vector IntersectNodeList; + + // ReuseableDataContainer64 ------------------------------------------------ + + class ReuseableDataContainer64 { + private: + friend class ClipperBase; + LocalMinimaList minima_list_; + std::vector vertex_lists_; + void AddLocMin(Vertex& vert, PathType polytype, bool is_open); + public: + virtual ~ReuseableDataContainer64(); + void Clear(); + void AddPaths(const Paths64& paths, PathType polytype, bool is_open); + }; + // ClipperBase ------------------------------------------------------------- class ClipperBase { private: - ClipType cliptype_ = ClipType::None; + ClipType cliptype_ = ClipType::NoClip; FillRule fillrule_ = FillRule::EvenOdd; FillRule fillpos = FillRule::Positive; int64_t bot_y_ = 0; @@ -165,21 +199,21 @@ namespace Clipper2Lib { bool using_polytree_ = false; Active* actives_ = nullptr; Active *sel_ = nullptr; - Joiner *horz_joiners_ = nullptr; - std::vector minima_list_; //pointers in case of memory reallocs - std::vector::iterator current_locmin_iter_; + LocalMinimaList minima_list_; //pointers in case of memory reallocs + LocalMinimaList::iterator current_locmin_iter_; std::vector vertex_lists_; std::priority_queue scanline_list_; - std::vector intersect_nodes_; - std::vector joiner_list_; //pointers in case of memory reallocs + IntersectNodeList intersect_nodes_; + HorzSegmentList horz_seg_list_; + std::vector horz_join_list_; void Reset(); - void InsertScanline(int64_t y); - bool PopScanline(int64_t &y); - bool PopLocalMinima(int64_t y, LocalMinima *&local_minima); + inline void InsertScanline(int64_t y); + inline bool PopScanline(int64_t &y); + inline bool PopLocalMinima(int64_t y, LocalMinima*& local_minima); void DisposeAllOutRecs(); void DisposeVerticesAndLocalMinima(); void DeleteEdges(Active*& e); - void AddLocMin(Vertex &vert, PathType polytype, bool is_open); + inline void AddLocMin(Vertex &vert, PathType polytype, bool is_open); bool IsContributingClosed(const Active &e) const; inline bool IsContributingOpen(const Active &e) const; void SetWindCountForClosedPathEdge(Active &edge); @@ -190,7 +224,7 @@ namespace Clipper2Lib { inline bool PopHorz(Active *&e); inline OutPt* StartOpenPath(Active &e, const Point64& pt); inline void UpdateEdgeIntoAEL(Active *e); - OutPt* IntersectEdges(Active &e1, Active &e2, const Point64& pt); + void IntersectEdges(Active &e1, Active &e2, const Point64& pt); inline void DeleteFromAEL(Active &e); inline void AdjustCurrXAndCopyToSEL(const int64_t top_y); void DoIntersections(const int64_t top_y); @@ -198,38 +232,41 @@ namespace Clipper2Lib { bool BuildIntersectList(const int64_t top_y); void ProcessIntersectList(); void SwapPositionsInAEL(Active& edge1, Active& edge2); + OutRec* NewOutRec(); OutPt* AddOutPt(const Active &e, const Point64& pt); OutPt* AddLocalMinPoly(Active &e1, Active &e2, const Point64& pt, bool is_new = false); OutPt* AddLocalMaxPoly(Active &e1, Active &e2, const Point64& pt); void DoHorizontal(Active &horz); - bool ResetHorzDirection(const Active &horz, const Active *max_pair, + bool ResetHorzDirection(const Active &horz, const Vertex* max_vertex, int64_t &horz_left, int64_t &horz_right); void DoTopOfScanbeam(const int64_t top_y); Active *DoMaxima(Active &e); void JoinOutrecPaths(Active &e1, Active &e2); - void CompleteSplit(OutPt* op1, OutPt* op2, OutRec& outrec); - bool ValidateClosedPathEx(OutPt*& outrec); - void CleanCollinear(OutRec* outrec); void FixSelfIntersects(OutRec* outrec); void DoSplitOp(OutRec* outRec, OutPt* splitOp); - Joiner* GetHorzTrialParent(const OutPt* op); - bool OutPtInTrialHorzList(OutPt* op); - void SafeDisposeOutPts(OutPt*& op); - void SafeDeleteOutPtJoiners(OutPt* op); - void AddTrialHorzJoin(OutPt* op); - void DeleteTrialHorzJoin(OutPt* op); - void ConvertHorzTrialsToJoins(); - void AddJoin(OutPt* op1, OutPt* op2); - void DeleteJoin(Joiner* joiner); - void ProcessJoinerList(); - OutRec* ProcessJoin(Joiner* joiner); + + inline void AddTrialHorzJoin(OutPt* op); + void ConvertHorzSegsToJoins(); + void ProcessHorzJoins(); + + void Split(Active& e, const Point64& pt); + inline void CheckJoinLeft(Active& e, + const Point64& pt, bool check_curr_x = false); + inline void CheckJoinRight(Active& e, + const Point64& pt, bool check_curr_x = false); protected: + bool preserve_collinear_ = true; + bool reverse_solution_ = false; + int error_code_ = 0; bool has_open_paths_ = false; bool succeeded_ = true; - std::vector outrec_list_; //pointers in case list memory reallocated + OutRecList outrec_list_; //pointers in case list memory reallocated bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees); - bool DeepCheckOwner(OutRec* outrec, OutRec* owner); + void CleanCollinear(OutRec* outrec); + bool CheckBounds(OutRec* outrec); + bool CheckSplitOwner(OutRec* outrec, OutRecList* splits); + void RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath); #ifdef USINGZ ZCallback64 zCallback_ = nullptr; void SetZ(const Active& e1, const Active& e2, Point64& pt); @@ -239,9 +276,16 @@ namespace Clipper2Lib { void AddPaths(const Paths64& paths, PathType polytype, bool is_open); public: virtual ~ClipperBase(); - bool PreserveCollinear = true; - bool ReverseSolution = false; + int ErrorCode() const { return error_code_; }; + void PreserveCollinear(bool val) { preserve_collinear_ = val; }; + bool PreserveCollinear() const { return preserve_collinear_;}; + void ReverseSolution(bool val) { reverse_solution_ = val; }; + bool ReverseSolution() const { return reverse_solution_; }; void Clear(); + void AddReuseableData(const ReuseableDataContainer64& reuseable_data); +#ifdef USINGZ + int64_t DefaultZ = 0; +#endif }; // PolyPath / PolyTree -------------------------------------------------------- @@ -256,7 +300,7 @@ namespace Clipper2Lib { PolyPath* parent_; public: PolyPath(PolyPath* parent = nullptr): parent_(parent){} - virtual ~PolyPath() { Clear(); }; + virtual ~PolyPath() {}; //https://en.cppreference.com/w/cpp/language/rule_of_three PolyPath(const PolyPath&) = delete; PolyPath& operator=(const PolyPath&) = delete; @@ -271,49 +315,58 @@ namespace Clipper2Lib { virtual PolyPath* AddChild(const Path64& path) = 0; - virtual void Clear() {}; + virtual void Clear() = 0; virtual size_t Count() const { return 0; } const PolyPath* Parent() const { return parent_; } bool IsHole() const { - const PolyPath* pp = parent_; - bool is_hole = pp; - while (pp) { - is_hole = !is_hole; - pp = pp->parent_; - } - return is_hole; + unsigned lvl = Level(); + //Even levels except level 0 + return lvl && !(lvl & 1); } }; + typedef typename std::vector> PolyPath64List; + typedef typename std::vector> PolyPathDList; + class PolyPath64 : public PolyPath { private: - std::vector childs_; + PolyPath64List childs_; Path64 polygon_; - typedef typename std::vector::const_iterator pp64_itor; public: - PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {} - PolyPath64* operator [] (size_t index) { return static_cast(childs_[index]); } - pp64_itor begin() const { return childs_.cbegin(); } - pp64_itor end() const { return childs_.cend(); } + explicit PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {} + explicit PolyPath64(PolyPath64* parent, const Path64& path) : PolyPath(parent) { polygon_ = path; } + + ~PolyPath64() { + childs_.resize(0); + } + + PolyPath64* operator [] (size_t index) const + { + return childs_[index].get(); //std::unique_ptr + } + + PolyPath64* Child(size_t index) const + { + return childs_[index].get(); + } + + PolyPath64List::const_iterator begin() const { return childs_.cbegin(); } + PolyPath64List::const_iterator end() const { return childs_.cend(); } PolyPath64* AddChild(const Path64& path) override { - PolyPath64* result = new PolyPath64(this); - childs_.push_back(result); - result->polygon_ = path; - return result; + return childs_.emplace_back(std::make_unique(this, path)).get(); } void Clear() override { - for (const PolyPath64* child : childs_) delete child; childs_.resize(0); } - size_t Count() const override + size_t Count() const override { return childs_.size(); } @@ -322,78 +375,73 @@ namespace Clipper2Lib { double Area() const { - double result = Clipper2Lib::Area(polygon_); - for (const PolyPath64* child : childs_) - result += child->Area(); - return result; - } - - friend std::ostream& operator << (std::ostream& outstream, const PolyPath64& polypath) - { - const size_t level_indent = 4; - const size_t coords_per_line = 4; - const size_t last_on_line = coords_per_line - 1; - unsigned level = polypath.Level(); - if (level > 0) - { - std::string level_padding; - level_padding.insert(0, (level - 1) * level_indent, ' '); - std::string caption = polypath.IsHole() ? "Hole " : "Outer Polygon "; - std::string childs = polypath.Count() == 1 ? " child" : " children"; - outstream << level_padding.c_str() << caption << "with " << polypath.Count() << childs << std::endl; - outstream << level_padding; - size_t i = 0, highI = polypath.Polygon().size() - 1; - for (; i < highI; ++i) - { - outstream << polypath.Polygon()[i] << ' '; - if ((i % coords_per_line) == last_on_line) - outstream << std::endl << level_padding; - } - if (highI > 0) outstream << polypath.Polygon()[i]; - outstream << std::endl; - } - for (auto child : polypath) - outstream << *child; - return outstream; + return std::accumulate(childs_.cbegin(), childs_.cend(), + Clipper2Lib::Area(polygon_), + [](double a, const auto& child) {return a + child->Area(); }); } }; class PolyPathD : public PolyPath { private: - std::vector childs_; - double inv_scale_; + PolyPathDList childs_; + double scale_; PathD polygon_; - typedef typename std::vector::const_iterator ppD_itor; public: - PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent) + explicit PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent) { - inv_scale_ = parent ? parent->inv_scale_ : 1.0; + scale_ = parent ? parent->scale_ : 1.0; } - PolyPathD* operator [] (size_t index) - { - return static_cast(childs_[index]); - } - ppD_itor begin() const { return childs_.cbegin(); } - ppD_itor end() const { return childs_.cend(); } - void SetInvScale(double value) { inv_scale_ = value; } - double InvScale() { return inv_scale_; } + explicit PolyPathD(PolyPathD* parent, const Path64& path) : PolyPath(parent) + { + scale_ = parent ? parent->scale_ : 1.0; + int error_code = 0; + polygon_ = ScalePath(path, scale_, error_code); + } + + explicit PolyPathD(PolyPathD* parent, const PathD& path) : PolyPath(parent) + { + scale_ = parent ? parent->scale_ : 1.0; + polygon_ = path; + } + + ~PolyPathD() { + childs_.resize(0); + } + + PolyPathD* operator [] (size_t index) const + { + return childs_[index].get(); + } + + PolyPathD* Child(size_t index) const + { + return childs_[index].get(); + } + + PolyPathDList::const_iterator begin() const { return childs_.cbegin(); } + PolyPathDList::const_iterator end() const { return childs_.cend(); } + + void SetScale(double value) { scale_ = value; } + double Scale() const { return scale_; } + PolyPathD* AddChild(const Path64& path) override { - PolyPathD* result = new PolyPathD(this); - childs_.push_back(result); - result->polygon_ = ScalePath(path, inv_scale_); - return result; + return childs_.emplace_back(std::make_unique(this, path)).get(); + } + + PolyPathD* AddChild(const PathD& path) + { + return childs_.emplace_back(std::make_unique(this, path)).get(); } void Clear() override { - for (const PolyPathD* child : childs_) delete child; childs_.resize(0); } - size_t Count() const override + size_t Count() const override { return childs_.size(); } @@ -402,10 +450,9 @@ namespace Clipper2Lib { double Area() const { - double result = Clipper2Lib::Area(polygon_); - for (const PolyPathD* child : childs_) - result += child->Area(); - return result; + return std::accumulate(childs_.begin(), childs_.end(), + Clipper2Lib::Area(polygon_), + [](double a, const auto& child) {return a + child->Area(); }); } }; @@ -445,7 +492,7 @@ namespace Clipper2Lib { closed_paths.clear(); open_paths.clear(); if (ExecuteInternal(clip_type, fill_rule, false)) - BuildPaths64(closed_paths, &open_paths); + BuildPaths64(closed_paths, &open_paths); CleanUp(); return succeeded_; } @@ -474,14 +521,14 @@ namespace Clipper2Lib { private: double scale_ = 1.0, invScale_ = 1.0; #ifdef USINGZ - ZCallbackD zCallback_ = nullptr; + ZCallbackD zCallbackD_ = nullptr; #endif void BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen); void BuildTreeD(PolyPathD& polytree, PathsD& open_paths); public: explicit ClipperD(int precision = 2) : ClipperBase() { - CheckPrecision(precision); + CheckPrecisionRange(precision, error_code_); // to optimize scaling / descaling precision // set the scale to a power of double's radix (2) (#25) scale_ = std::pow(std::numeric_limits::radix, @@ -490,7 +537,7 @@ namespace Clipper2Lib { } #ifdef USINGZ - void SetZCallback(ZCallbackD cb) { zCallback_ = cb; }; + void SetZCallback(ZCallbackD cb) { zCallbackD_ = cb; }; void ZCB(const Point64& e1bot, const Point64& e1top, const Point64& e2bot, const Point64& e2top, Point64& pt) @@ -504,13 +551,13 @@ namespace Clipper2Lib { PointD e1t = PointD(e1top) * invScale_; PointD e2b = PointD(e2bot) * invScale_; PointD e2t = PointD(e2top) * invScale_; - zCallback_(e1b,e1t, e2b, e2t, tmp); + zCallbackD_(e1b,e1t, e2b, e2t, tmp); pt.z = tmp.z; // only update 'z' }; void CheckCallback() { - if(zCallback_) + if(zCallbackD_) // if the user defined float point callback has been assigned // then assign the proxy callback function ClipperBase::zCallback_ = @@ -525,17 +572,17 @@ namespace Clipper2Lib { void AddSubject(const PathsD& subjects) { - AddPaths(ScalePaths(subjects, scale_), PathType::Subject, false); + AddPaths(ScalePaths(subjects, scale_, error_code_), PathType::Subject, false); } void AddOpenSubject(const PathsD& open_subjects) { - AddPaths(ScalePaths(open_subjects, scale_), PathType::Subject, true); + AddPaths(ScalePaths(open_subjects, scale_, error_code_), PathType::Subject, true); } void AddClip(const PathsD& clips) { - AddPaths(ScalePaths(clips, scale_), PathType::Clip, false); + AddPaths(ScalePaths(clips, scale_, error_code_), PathType::Clip, false); } bool Execute(ClipType clip_type, FillRule fill_rule, PathsD& closed_paths) @@ -573,7 +620,7 @@ namespace Clipper2Lib { if (ExecuteInternal(clip_type, fill_rule, true)) { polytree.Clear(); - polytree.SetInvScale(invScale_); + polytree.SetScale(invScale_); open_paths.clear(); BuildTreeD(polytree, open_paths); } diff --git a/src/clipper2/Clipper2Lib/include/clipper2/clipper.export.h b/src/clipper2/Clipper2Lib/include/clipper2/clipper.export.h index e20ac91..79856e2 100644 --- a/src/clipper2/Clipper2Lib/include/clipper2/clipper.export.h +++ b/src/clipper2/Clipper2Lib/include/clipper2/clipper.export.h @@ -1,73 +1,134 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 30 October 2022 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2022 * +* Date : 24 January 2025 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2025 * * Purpose : This module exports the Clipper2 Library (ie DLL/so) * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ -// The exported functions below refer to simple structures that -// can be understood across multiple languages. Consequently -// Path64, PathD, Polytree64 etc are converted from C++ classes -// (std::vector<> etc) into the following data structures: -// -// CPath64 (int64_t*) & CPathD (double_t*): -// Path64 and PathD are converted into arrays of x,y coordinates. -// However in these arrays the first x,y coordinate pair is a -// counter with 'x' containing the number of following coordinate -// pairs. ('y' should be 0, with one exception explained below.) -// __________________________________ -// |counter|coord1|coord2|...|coordN| -// |N ,0 |x1, y1|x2, y2|...|xN, yN| -// __________________________________ -// -// CPaths64 (int64_t**) & CPathsD (double_t**): -// These are arrays of pointers to CPath64 and CPathD where -// the first pointer is to a 'counter path'. This 'counter -// path' has a single x,y coord pair with 'y' (not 'x') -// containing the number of paths that follow. ('x' = 0). -// _______________________________ -// |counter|path1|path2|...|pathN| -// |addr0 |addr1|addr2|...|addrN| (*addr0[0]=0; *addr0[1]=N) -// _______________________________ -// -// The structures of CPolytree64 and CPolytreeD are defined -// below and these structures don't need to be explained here. +/* + Boolean clipping: + cliptype: NoClip=0, Intersection=1, Union=2, Difference=3, Xor=4 + fillrule: EvenOdd=0, NonZero=1, Positive=2, Negative=3 + + Polygon offsetting (inflate/deflate): + jointype: Square=0, Bevel=1, Round=2, Miter=3 + endtype: Polygon=0, Joined=1, Butt=2, Square=3, Round=4 + +The path structures used extensively in other parts of this library are all +based on std::vector classes. Since C++ classes can't be accessed by other +languages, these paths are exported here as very simple array structures +(either of int64_t or double) that can be parsed by just about any +programming language. + +These 2D paths are defined by series of x and y coordinates together with an +optional user-defined 'z' value (see Z-values below). Hence, a vertex refers +to a single x and y coordinate (+/- a user-defined value). Data structures +have names with suffixes that indicate the array type (either int64_t or +double). For example, the data structure CPath64 contains an array of int64_t +values, whereas the data structure CPathD contains an array of double. +Where documentation omits the type suffix (eg CPath), it is referring to an +array whose data type could be either int64_t or double. + +For conciseness, the following letters are used in the diagrams below: +N: Number of vertices in a given path +C: Count (ie number) of paths (or PolyPaths) in the structure +A: Number of elements in an array + + +CPath64 and CPathD: +These are arrays of either int64_t or double values. Apart from +the first two elements, these arrays are a series of vertices +that together define a path. The very first element contains the +number of vertices (N) in the path, while second element should +contain a 0 value. +_______________________________________________________________ +| counters | vertex1 | vertex2 | ... | vertexN | +| N, 0 | x1, y1, (z1) | x2, y2, (z2) | ... | xN, yN, (zN) | +--------------------------------------------------------------- + + +CPaths64 and CPathsD: +These are also arrays of either int64_t or double values that +contain any number of consecutive CPath structures. However, +preceding the first path is a pair of values. The first value +contains the length of the entire array structure (A), and the +second contains the number (ie count) of contained paths (C). + Memory allocation for CPaths64 = A * sizeof(int64_t) + Memory allocation for CPathsD = A * sizeof(double) +__________________________________________ +| counters | path1 | path2 | ... | pathC | +| A, C | | | ... | | +------------------------------------------ + + +CPolytree64 and CPolytreeD: +The entire polytree structure is an array of int64_t or double. The +first element in the array indicates the array's total length (A). +The second element indicates the number (C) of CPolyPath structures +that are the TOP LEVEL CPolyPath in the polytree, and these top +level CPolyPath immediately follow these first two array elements. +These top level CPolyPath structures may, in turn, contain nested +CPolyPath children, and these collectively make a tree structure. +_________________________________________________________ +| counters | CPolyPath1 | CPolyPath2 | ... | CPolyPathC | +| A, C | | | ... | | +--------------------------------------------------------- + + +CPolyPath64 and CPolyPathD: +These array structures consist of a pair of counter values followed by a +series of polygon vertices and a series of nested CPolyPath children. +The first counter values indicates the number of vertices in the +polygon (N), and the second counter indicates the CPolyPath child count (C). +_____________________________________________________________________________ +|cntrs |vertex1 |vertex2 |...|vertexN |child1|child2|...|childC| +|N, C |x1, y1, (z1)| x2, y2, (z2)|...|xN, yN, (zN)| | |...| | +----------------------------------------------------------------------------- + + +DisposeArray64 & DisposeArrayD: +All array structures are allocated in heap memory which will eventually +need to be released. However, since applications linking to these DLL +functions may use different memory managers, the only safe way to release +this memory is to use the exported DisposeArray functions. + + +(Optional) Z-Values: +Structures will only contain user-defined z-values when the USINGZ +pre-processor identifier is used. The library does not assign z-values +because this field is intended for users to assign custom values to vertices. +Z-values in input paths (subject and clip) will be copied to solution paths. +New vertices at path intersections will generate a callback event that allows +users to assign z-values at these new vertices. The user's callback function +must conform with the DLLZCallback definition and be registered with the +DLL via SetZCallback. To assist the user in assigning z-values, the library +passes in the callback function the new intersection point together with +the four vertices that define the two segments that are intersecting. + +*/ #ifndef CLIPPER2_EXPORT_H #define CLIPPER2_EXPORT_H -#include -#include - #include "clipper2/clipper.core.h" #include "clipper2/clipper.engine.h" #include "clipper2/clipper.offset.h" #include "clipper2/clipper.rectclip.h" +#include namespace Clipper2Lib { typedef int64_t* CPath64; -typedef int64_t** CPaths64; -typedef double* CPathD; -typedef double** CPathsD; +typedef int64_t* CPaths64; +typedef double* CPathD; +typedef double* CPathsD; -typedef struct CPolyPath64 { - CPath64 polygon; - uint32_t is_hole; - uint32_t child_count; - CPolyPath64* childs; -} -CPolyTree64; - -typedef struct CPolyPathD { - CPathD polygon; - uint32_t is_hole; - uint32_t child_count; - CPolyPathD* childs; -} -CPolyTreeD; +typedef int64_t* CPolyPath64; +typedef int64_t* CPolyTree64; +typedef double* CPolyPathD; +typedef double* CPolyTreeD; template struct CRect { @@ -97,66 +158,79 @@ inline Rect CRectToRect(const CRect& rect) return result; } -#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport) +template +inline T1 Reinterpret(T2 value) { + return *reinterpret_cast(&value); +} + + +#ifdef _WIN32 + #define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport) +#else + #define EXTERN_DLL_EXPORT extern "C" +#endif + ////////////////////////////////////////////////////// -// EXPORTED FUNCTION DEFINITIONS +// EXPORTED FUNCTION DECLARATIONS ////////////////////////////////////////////////////// EXTERN_DLL_EXPORT const char* Version(); -// Some of the functions below will return data in the various CPath -// and CPolyTree structures which are pointers to heap allocated -// memory. Eventually this memory will need to be released with one -// of the following 'DisposeExported' functions. (This may be the -// only safe way to release this memory since the executable -// accessing these exported functions may use a memory manager that -// allocates and releases heap memory in a different way. Also, -// CPath structures that have been constructed by the executable -// should not be destroyed using these 'DisposeExported' functions.) -EXTERN_DLL_EXPORT void DisposeExportedCPath64(CPath64 p); -EXTERN_DLL_EXPORT void DisposeExportedCPaths64(CPaths64& pp); -EXTERN_DLL_EXPORT void DisposeExportedCPathD(CPathD p); -EXTERN_DLL_EXPORT void DisposeExportedCPathsD(CPathsD& pp); -EXTERN_DLL_EXPORT void DisposeExportedCPolyTree64(CPolyTree64*& cpt); -EXTERN_DLL_EXPORT void DisposeExportedCPolyTreeD(CPolyTreeD*& cpt); +EXTERN_DLL_EXPORT void DisposeArray64(int64_t*& p) +{ + delete[] p; +} + +EXTERN_DLL_EXPORT void DisposeArrayD(double*& p) +{ + delete[] p; +} -// Boolean clipping: -// cliptype: None=0, Intersection=1, Union=2, Difference=3, Xor=4 -// fillrule: EvenOdd=0, NonZero=1, Positive=2, Negative=3 EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, uint8_t fillrule, const CPaths64 subjects, const CPaths64 subjects_open, const CPaths64 clips, CPaths64& solution, CPaths64& solution_open, bool preserve_collinear = true, bool reverse_solution = false); -EXTERN_DLL_EXPORT int BooleanOpPt64(uint8_t cliptype, + +EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype, uint8_t fillrule, const CPaths64 subjects, const CPaths64 subjects_open, const CPaths64 clips, - CPolyTree64*& solution, CPaths64& solution_open, + CPolyTree64& sol_tree, CPaths64& solution_open, bool preserve_collinear = true, bool reverse_solution = false); + EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype, uint8_t fillrule, const CPathsD subjects, const CPathsD subjects_open, const CPathsD clips, CPathsD& solution, CPathsD& solution_open, int precision = 2, bool preserve_collinear = true, bool reverse_solution = false); -EXTERN_DLL_EXPORT int BooleanOpPtD(uint8_t cliptype, + +EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype, uint8_t fillrule, const CPathsD subjects, const CPathsD subjects_open, const CPathsD clips, - CPolyTreeD*& solution, CPathsD& solution_open, int precision = 2, + CPolyTreeD& solution, CPathsD& solution_open, int precision = 2, bool preserve_collinear = true, bool reverse_solution = false); -// Polygon offsetting (inflate/deflate): -// jointype: Square=0, Round=1, Miter=2 -// endtype: Polygon=0, Joined=1, Butt=2, Square=3, Round=4 EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths, - double delta, uint8_t jointype, uint8_t endtype, - double miter_limit = 2.0, double arc_tolerance = 0.0, + double delta, uint8_t jointype, uint8_t endtype, + double miter_limit = 2.0, double arc_tolerance = 0.0, bool reverse_solution = false); + EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, double delta, uint8_t jointype, uint8_t endtype, int precision = 2, double miter_limit = 2.0, double arc_tolerance = 0.0, bool reverse_solution = false); +EXTERN_DLL_EXPORT CPaths64 InflatePath64(const CPath64 path, + double delta, uint8_t jointype, uint8_t endtype, + double miter_limit = 2.0, double arc_tolerance = 0.0, + bool reverse_solution = false); + +EXTERN_DLL_EXPORT CPathsD InflatePathD(const CPathD path, + double delta, uint8_t jointype, uint8_t endtype, + int precision = 2, double miter_limit = 2.0, + double arc_tolerance = 0.0, bool reverse_solution = false); + // RectClip & RectClipLines: EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, const CPaths64 paths); @@ -171,67 +245,314 @@ EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, // INTERNAL FUNCTIONS ////////////////////////////////////////////////////// -inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2); -inline CPath64 CreateCPath64(const Path64& p); -inline CPaths64 CreateCPaths64(const Paths64& pp); -inline Path64 ConvertCPath64(const CPath64& p); -inline Paths64 ConvertCPaths64(const CPaths64& pp); +#ifdef USINGZ +ZCallback64 dllCallback64 = nullptr; +ZCallbackD dllCallbackD = nullptr; -inline CPathD CreateCPathD(size_t cnt1, size_t cnt2); -inline CPathD CreateCPathD(const PathD& p); -inline CPathsD CreateCPathsD(const PathsD& pp); -inline PathD ConvertCPathD(const CPathD& p); -inline PathsD ConvertCPathsD(const CPathsD& pp); +constexpr int EXPORT_VERTEX_DIMENSIONALITY = 3; +#else +constexpr int EXPORT_VERTEX_DIMENSIONALITY = 2; +#endif -// the following function avoid multiple conversions -inline CPathD CreateCPathD(const Path64& p, double scale); -inline CPathsD CreateCPathsD(const Paths64& pp, double scale); -inline Path64 ConvertCPathD(const CPathD& p, double scale); -inline Paths64 ConvertCPathsD(const CPathsD& pp, double scale); +template +static void GetPathCountAndCPathsArrayLen(const Paths& paths, + size_t& cnt, size_t& array_len) +{ + array_len = 2; + cnt = 0; + for (const Path& path : paths) + if (path.size()) + { + array_len += path.size() * EXPORT_VERTEX_DIMENSIONALITY + 2; + ++cnt; + } +} -inline CPolyTree64* CreateCPolyTree64(const PolyTree64& pt); -inline CPolyTreeD* CreateCPolyTreeD(const PolyTree64& pt, double scale); +static size_t GetPolyPathArrayLen64(const PolyPath64& pp) +{ + size_t result = 2; // poly_length + child_count + result += pp.Polygon().size() * EXPORT_VERTEX_DIMENSIONALITY; + //plus nested children :) + for (size_t i = 0; i < pp.Count(); ++i) + result += GetPolyPathArrayLen64(*pp[i]); + return result; +} + +static size_t GetPolyPathArrayLenD(const PolyPathD& pp) +{ + size_t result = 2; // poly_length + child_count + result += pp.Polygon().size() * EXPORT_VERTEX_DIMENSIONALITY; + //plus nested children :) + for (size_t i = 0; i < pp.Count(); ++i) + result += GetPolyPathArrayLenD(*pp[i]); + return result; +} + +static void GetPolytreeCountAndCStorageSize64(const PolyTree64& tree, + size_t& cnt, size_t& array_len) +{ + cnt = tree.Count(); // nb: top level count only + array_len = GetPolyPathArrayLen64(tree); +} + +static void GetPolytreeCountAndCStorageSizeD(const PolyTreeD& tree, + size_t& cnt, size_t& array_len) +{ + cnt = tree.Count(); // nb: top level count only + array_len = GetPolyPathArrayLenD(tree); +} + +template +static T* CreateCPathsFromPathsT(const Paths& paths) +{ + size_t cnt = 0, array_len = 0; + GetPathCountAndCPathsArrayLen(paths, cnt, array_len); + T* result = new T[array_len], * v = result; + *v++ = array_len; + *v++ = cnt; + for (const Path& path : paths) + { + if (!path.size()) continue; + *v++ = path.size(); + *v++ = 0; + for (const Point& pt : path) + { + *v++ = pt.x; + *v++ = pt.y; +#ifdef USINGZ + *v++ = Reinterpret(pt.z); +#endif + } + } + return result; +} + +CPathsD CreateCPathsDFromPathsD(const PathsD& paths) +{ + if (!paths.size()) return nullptr; + size_t cnt, array_len; + GetPathCountAndCPathsArrayLen(paths, cnt, array_len); + CPathsD result = new double[array_len], v = result; + *v++ = (double)array_len; + *v++ = (double)cnt; + for (const PathD& path : paths) + { + if (!path.size()) continue; + *v = (double)path.size(); + ++v; *v++ = 0; + for (const PointD& pt : path) + { + *v++ = pt.x; + *v++ = pt.y; +#ifdef USINGZ + * v++ = Reinterpret(pt.z); +#endif + } + } + return result; +} + +CPathsD CreateCPathsDFromPaths64(const Paths64& paths, double scale) +{ + if (!paths.size()) return nullptr; + size_t cnt, array_len; + GetPathCountAndCPathsArrayLen(paths, cnt, array_len); + CPathsD result = new double[array_len], v = result; + *v++ = (double)array_len; + *v++ = (double)cnt; + for (const Path64& path : paths) + { + if (!path.size()) continue; + *v = (double)path.size(); + ++v; *v++ = 0; + for (const Point64& pt : path) + { + *v++ = pt.x * scale; + *v++ = pt.y * scale; +#ifdef USINGZ + *v++ = Reinterpret(pt.z); +#endif + } + } + return result; +} + +template +static Path ConvertCPathToPathT(T* path) +{ + Path result; + if (!path) return result; + T* v = path; + size_t cnt = static_cast(*v); + v += 2; // skip 0 value + result.reserve(cnt); + for (size_t j = 0; j < cnt; ++j) + { + T x = *v++, y = *v++; +#ifdef USINGZ + z_type z = Reinterpret(*v++); + result.emplace_back(x, y, z); +#else + result.emplace_back(x, y); +#endif + } + return result; +} + +template +static Paths ConvertCPathsToPathsT(T* paths) +{ + Paths result; + if (!paths) return result; + T* v = paths; ++v; + size_t cnt = static_cast(*v++); + result.reserve(cnt); + for (size_t i = 0; i < cnt; ++i) + { + size_t cnt2 = static_cast(*v); + v += 2; + Path path; + path.reserve(cnt2); + for (size_t j = 0; j < cnt2; ++j) + { + T x = *v++, y = *v++; +#ifdef USINGZ + z_type z = Reinterpret(*v++); + path.emplace_back(x, y, z); +#else + path.emplace_back(x, y); +#endif + } + result.emplace_back(std::move(path)); + } + return result; +} + +static Path64 ConvertCPathDToPath64WithScale(const CPathD path, double scale) +{ + Path64 result; + if (!path) return result; + double* v = path; + size_t cnt = static_cast(*v); + v += 2; // skip 0 value + result.reserve(cnt); + for (size_t j = 0; j < cnt; ++j) + { + double x = *v++ * scale; + double y = *v++ * scale; +#ifdef USINGZ + z_type z = Reinterpret(*v++); + result.emplace_back(x, y, z); +#else + result.emplace_back(x, y); +#endif + } + return result; +} + +static Paths64 ConvertCPathsDToPaths64(const CPathsD paths, double scale) +{ + Paths64 result; + if (!paths) return result; + double* v = paths; + ++v; // skip the first value (0) + size_t cnt = static_cast(*v++); + result.reserve(cnt); + for (size_t i = 0; i < cnt; ++i) + { + size_t cnt2 = static_cast(*v); + v += 2; + Path64 path; + path.reserve(cnt2); + for (size_t j = 0; j < cnt2; ++j) + { + double x = *v++ * scale; + double y = *v++ * scale; +#ifdef USINGZ + z_type z = Reinterpret(*v++); + path.emplace_back(x, y, z); +#else + path.emplace_back(x, y); +#endif + } + result.emplace_back(std::move(path)); + } + return result; +} + +static void CreateCPolyPath64(const PolyPath64* pp, int64_t*& v) +{ + *v++ = static_cast(pp->Polygon().size()); + *v++ = static_cast(pp->Count()); + for (const Point64& pt : pp->Polygon()) + { + *v++ = pt.x; + *v++ = pt.y; +#ifdef USINGZ + * v++ = Reinterpret(pt.z); // raw memory copy +#endif + } + for (size_t i = 0; i < pp->Count(); ++i) + CreateCPolyPath64(pp->Child(i), v); +} + +static void CreateCPolyPathD(const PolyPathD* pp, double*& v) +{ + *v++ = static_cast(pp->Polygon().size()); + *v++ = static_cast(pp->Count()); + for (const PointD& pt : pp->Polygon()) + { + *v++ = pt.x; + *v++ = pt.y; +#ifdef USINGZ + * v++ = Reinterpret(pt.z); // raw memory copy +#endif + } + for (size_t i = 0; i < pp->Count(); ++i) + CreateCPolyPathD(pp->Child(i), v); +} + +static int64_t* CreateCPolyTree64(const PolyTree64& tree) +{ + size_t cnt, array_len; + GetPolytreeCountAndCStorageSize64(tree, cnt, array_len); + if (!cnt) return nullptr; + // allocate storage + int64_t* result = new int64_t[array_len]; + int64_t* v = result; + *v++ = static_cast(array_len); + *v++ = static_cast(tree.Count()); + for (size_t i = 0; i < tree.Count(); ++i) + CreateCPolyPath64(tree.Child(i), v); + return result; +} + +static double* CreateCPolyTreeD(const PolyTreeD& tree) +{ + double scale = std::log10(tree.Scale()); + size_t cnt, array_len; + GetPolytreeCountAndCStorageSizeD(tree, cnt, array_len); + if (!cnt) return nullptr; + // allocate storage + double* result = new double[array_len]; + double* v = result; + *v++ = static_cast(array_len); + *v++ = static_cast(tree.Count()); + for (size_t i = 0; i < tree.Count(); ++i) + CreateCPolyPathD(tree.Child(i), v); + return result; +} + +////////////////////////////////////////////////////// +// EXPORTED FUNCTION DEFINITIONS +////////////////////////////////////////////////////// EXTERN_DLL_EXPORT const char* Version() { return CLIPPER2_VERSION; } -EXTERN_DLL_EXPORT void DisposeExportedCPath64(CPath64 p) -{ - if (p) delete[] p; -} - -EXTERN_DLL_EXPORT void DisposeExportedCPaths64(CPaths64& pp) -{ - if (!pp) return; - CPaths64 v = pp; - CPath64 cnts = *v; - const size_t cnt = static_cast(cnts[1]); - for (size_t i = 0; i <= cnt; ++i) //nb: cnt +1 - DisposeExportedCPath64(*v++); - delete[] pp; - pp = nullptr; -} - -EXTERN_DLL_EXPORT void DisposeExportedCPathD(CPathD p) -{ - if (p) delete[] p; -} - -EXTERN_DLL_EXPORT void DisposeExportedCPathsD(CPathsD& pp) -{ - if (!pp) return; - CPathsD v = pp; - CPathD cnts = *v; - size_t cnt = static_cast(cnts[1]); - for (size_t i = 0; i <= cnt; ++i) //nb: cnt +1 - DisposeExportedCPathD(*v++); - delete[] pp; - pp = nullptr; -} - -EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, +EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, uint8_t fillrule, const CPaths64 subjects, const CPaths64 subjects_open, const CPaths64 clips, CPaths64& solution, CPaths64& solution_open, @@ -239,50 +560,58 @@ EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, { if (cliptype > static_cast(ClipType::Xor)) return -4; if (fillrule > static_cast(FillRule::Negative)) return -3; - + Paths64 sub, sub_open, clp, sol, sol_open; - sub = ConvertCPaths64(subjects); - sub_open = ConvertCPaths64(subjects_open); - clp = ConvertCPaths64(clips); + sub = ConvertCPathsToPathsT(subjects); + sub_open = ConvertCPathsToPathsT(subjects_open); + clp = ConvertCPathsToPathsT(clips); Clipper64 clipper; - clipper.PreserveCollinear = preserve_collinear; - clipper.ReverseSolution = reverse_solution; + clipper.PreserveCollinear(preserve_collinear); + clipper.ReverseSolution(reverse_solution); +#ifdef USINGZ + if (dllCallback64) + clipper.SetZCallback(dllCallback64); +#endif if (sub.size() > 0) clipper.AddSubject(sub); if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open); if (clp.size() > 0) clipper.AddClip(clp); - if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), sol, sol_open)) + if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), sol, sol_open)) return -1; // clipping bug - should never happen :) - solution = CreateCPaths64(sol); - solution_open = CreateCPaths64(sol_open); + solution = CreateCPathsFromPathsT(sol); + solution_open = CreateCPathsFromPathsT(sol_open); return 0; //success !! } -EXTERN_DLL_EXPORT int BooleanOpPt64(uint8_t cliptype, +EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype, uint8_t fillrule, const CPaths64 subjects, const CPaths64 subjects_open, const CPaths64 clips, - CPolyTree64*& solution, CPaths64& solution_open, + CPolyTree64& sol_tree, CPaths64& solution_open, bool preserve_collinear, bool reverse_solution) { if (cliptype > static_cast(ClipType::Xor)) return -4; if (fillrule > static_cast(FillRule::Negative)) return -3; Paths64 sub, sub_open, clp, sol_open; - sub = ConvertCPaths64(subjects); - sub_open = ConvertCPaths64(subjects_open); - clp = ConvertCPaths64(clips); + sub = ConvertCPathsToPathsT(subjects); + sub_open = ConvertCPathsToPathsT(subjects_open); + clp = ConvertCPathsToPathsT(clips); - PolyTree64 pt; + PolyTree64 tree; Clipper64 clipper; - clipper.PreserveCollinear = preserve_collinear; - clipper.ReverseSolution = reverse_solution; + clipper.PreserveCollinear(preserve_collinear); + clipper.ReverseSolution(reverse_solution); +#ifdef USINGZ + if (dllCallback64) + clipper.SetZCallback(dllCallback64); +#endif if (sub.size() > 0) clipper.AddSubject(sub); if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open); if (clp.size() > 0) clipper.AddClip(clp); - if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), pt, sol_open)) + if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), tree, sol_open)) return -1; // clipping bug - should never happen :) - solution = CreateCPolyTree64(pt); - solution_open = CreateCPaths64(sol_open); + sol_tree = CreateCPolyTree64(tree); + solution_open = CreateCPathsFromPathsT(sol_open); return 0; //success !! } @@ -295,60 +624,64 @@ EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype, if (precision < -8 || precision > 8) return -5; if (cliptype > static_cast(ClipType::Xor)) return -4; if (fillrule > static_cast(FillRule::Negative)) return -3; - const double scale = std::pow(10, precision); + //const double scale = std::pow(10, precision); - Paths64 sub, sub_open, clp, sol, sol_open; - sub = ConvertCPathsD(subjects, scale); - sub_open = ConvertCPathsD(subjects_open, scale); - clp = ConvertCPathsD(clips, scale); + PathsD sub, sub_open, clp, sol, sol_open; + sub = ConvertCPathsToPathsT(subjects); + sub_open = ConvertCPathsToPathsT(subjects_open); + clp = ConvertCPathsToPathsT(clips); - Clipper64 clipper; - clipper.PreserveCollinear = preserve_collinear; - clipper.ReverseSolution = reverse_solution; + ClipperD clipper(precision); + clipper.PreserveCollinear(preserve_collinear); + clipper.ReverseSolution(reverse_solution); +#ifdef USINGZ + if (dllCallbackD) + clipper.SetZCallback(dllCallbackD); +#endif if (sub.size() > 0) clipper.AddSubject(sub); - if (sub_open.size() > 0) - clipper.AddOpenSubject(sub_open); + if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open); if (clp.size() > 0) clipper.AddClip(clp); if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), sol, sol_open)) return -1; - - if (sol.size() > 0) solution = CreateCPathsD(sol, 1 / scale); - if (sol_open.size() > 0) - solution_open = CreateCPathsD(sol_open, 1 / scale); + solution = CreateCPathsDFromPathsD(sol); + solution_open = CreateCPathsDFromPathsD(sol_open); return 0; } -EXTERN_DLL_EXPORT int BooleanOpPtD(uint8_t cliptype, +EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype, uint8_t fillrule, const CPathsD subjects, const CPathsD subjects_open, const CPathsD clips, - CPolyTreeD*& solution, CPathsD& solution_open, int precision, + CPolyTreeD& solution, CPathsD& solution_open, int precision, bool preserve_collinear, bool reverse_solution) { if (precision < -8 || precision > 8) return -5; if (cliptype > static_cast(ClipType::Xor)) return -4; if (fillrule > static_cast(FillRule::Negative)) return -3; - - const double scale = std::pow(10, precision); - Paths64 sub, sub_open, clp, sol_open; - sub = ConvertCPathsD(subjects, scale); - sub_open = ConvertCPathsD(subjects_open, scale); - clp = ConvertCPathsD(clips, scale); + //double scale = std::pow(10, precision); - PolyTree64 sol; - Clipper64 clipper; - clipper.PreserveCollinear = preserve_collinear; - clipper.ReverseSolution = reverse_solution; + int err = 0; + PathsD sub, sub_open, clp, sol_open; + sub = ConvertCPathsToPathsT(subjects); + sub_open = ConvertCPathsToPathsT(subjects_open); + clp = ConvertCPathsToPathsT(clips); + + PolyTreeD tree; + ClipperD clipper(precision); + clipper.PreserveCollinear(preserve_collinear); + clipper.ReverseSolution(reverse_solution); +#ifdef USINGZ + if (dllCallbackD) + clipper.SetZCallback(dllCallbackD); +#endif if (sub.size() > 0) clipper.AddSubject(sub); - if (sub_open.size() > 0) - clipper.AddOpenSubject(sub_open); + if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open); if (clp.size() > 0) clipper.AddClip(clp); - if (!clipper.Execute(ClipType(cliptype), - FillRule(fillrule), sol, sol_open)) return -1; + if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), tree, sol_open)) + return -1; // clipping bug - should never happen :) - solution = CreateCPolyTreeD(sol, 1 / scale); - if (sol_open.size() > 0) - solution_open = CreateCPathsD(sol_open, 1 / scale); - return 0; + solution = CreateCPolyTreeD(tree); + solution_open = CreateCPathsDFromPathsD(sol_open); + return 0; //success !! } EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths, @@ -356,13 +689,13 @@ EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths, double arc_tolerance, bool reverse_solution) { Paths64 pp; - pp = ConvertCPaths64(paths); - - ClipperOffset clip_offset( miter_limit, + pp = ConvertCPathsToPathsT(paths); + ClipperOffset clip_offset( miter_limit, arc_tolerance, reverse_solution); clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype)); - Paths64 result = clip_offset.Execute(delta); - return CreateCPaths64(result); + Paths64 result; + clip_offset.Execute(delta, result); + return CreateCPathsFromPathsT(result); } EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, @@ -371,460 +704,129 @@ EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, double arc_tolerance, bool reverse_solution) { if (precision < -8 || precision > 8 || !paths) return nullptr; + const double scale = std::pow(10, precision); ClipperOffset clip_offset(miter_limit, arc_tolerance, reverse_solution); - Paths64 pp = ConvertCPathsD(paths, scale); + Paths64 pp = ConvertCPathsDToPaths64(paths, scale); clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype)); - Paths64 result = clip_offset.Execute(delta * scale); - return CreateCPathsD(result, 1/scale); + Paths64 result; + clip_offset.Execute(delta * scale, result); + return CreateCPathsDFromPaths64(result, 1 / scale); } -EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, - const CPaths64 paths) + +EXTERN_DLL_EXPORT CPaths64 InflatePath64(const CPath64 path, + double delta, uint8_t jointype, uint8_t endtype, double miter_limit, + double arc_tolerance, bool reverse_solution) +{ + Path64 pp; + pp = ConvertCPathToPathT(path); + ClipperOffset clip_offset(miter_limit, + arc_tolerance, reverse_solution); + clip_offset.AddPath(pp, JoinType(jointype), EndType(endtype)); + Paths64 result; + clip_offset.Execute(delta, result); + return CreateCPathsFromPathsT(result); +} + +EXTERN_DLL_EXPORT CPathsD InflatePathD(const CPathD path, + double delta, uint8_t jointype, uint8_t endtype, + int precision, double miter_limit, + double arc_tolerance, bool reverse_solution) +{ + if (precision < -8 || precision > 8 || !path) return nullptr; + + const double scale = std::pow(10, precision); + ClipperOffset clip_offset(miter_limit, arc_tolerance, reverse_solution); + Path64 pp = ConvertCPathDToPath64WithScale(path, scale); + clip_offset.AddPath(pp, JoinType(jointype), EndType(endtype)); + Paths64 result; + clip_offset.Execute(delta * scale, result); + + return CreateCPathsDFromPaths64(result, 1 / scale); +} + +EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, const CPaths64 paths) { if (CRectIsEmpty(rect) || !paths) return nullptr; Rect64 r64 = CRectToRect(rect); - class RectClip rc(r64); - Paths64 pp = ConvertCPaths64(paths); - Paths64 result; - result.reserve(pp.size()); - for (const Path64& p : pp) - { - Rect64 pathRec = Bounds(p); - if (!r64.Intersects(pathRec)) continue; - - if (r64.Contains(pathRec)) - result.push_back(p); - else - { - Path64 p2 = rc.Execute(p); - if (!p2.empty()) result.push_back(std::move(p2)); - } - } - return CreateCPaths64(result); + class RectClip64 rc(r64); + Paths64 pp = ConvertCPathsToPathsT(paths); + Paths64 result = rc.Execute(pp); + return CreateCPathsFromPathsT(result); } -EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, - const CPathsD paths, int precision) +EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, const CPathsD paths, int precision) { if (CRectIsEmpty(rect) || !paths) return nullptr; if (precision < -8 || precision > 8) return nullptr; const double scale = std::pow(10, precision); - Rect64 r = ScaleRect(CRectToRect(rect), scale); - Paths64 pp = ConvertCPathsD(paths, scale); - class RectClip rc(r); - Paths64 result; - result.reserve(pp.size()); - for (const Path64& p : pp) - { - Rect64 pathRec = Bounds(p); - if (!r.Intersects(pathRec)) continue; - - if (r.Contains(pathRec)) - result.push_back(p); - else - { - Path64 p2 = rc.Execute(p); - if (!p2.empty()) result.push_back(std::move(p2)); - } - } - return CreateCPathsD(result, 1/scale); + + RectD r = CRectToRect(rect); + Rect64 rec = ScaleRect(r, scale); + Paths64 pp = ConvertCPathsDToPaths64(paths, scale); + class RectClip64 rc(rec); + Paths64 result = rc.Execute(pp); + + return CreateCPathsDFromPaths64(result, 1 / scale); } -EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect, +EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect, const CPaths64 paths) { if (CRectIsEmpty(rect) || !paths) return nullptr; Rect64 r = CRectToRect(rect); - class RectClipLines rcl (r); - Paths64 pp = ConvertCPaths64(paths); - Paths64 result; - result.reserve(pp.size()); - - for (const Path64& p : pp) - { - Rect64 pathRec = Bounds(p); - if (!r.Intersects(pathRec)) continue; - - if (r.Contains(pathRec)) - result.push_back(p); - else - { - Paths64 pp2 = rcl.Execute(p); - if (!pp2.empty()) - result.insert(result.end(), pp2.begin(), pp2.end()); - } - } - return CreateCPaths64(result); + class RectClipLines64 rcl (r); + Paths64 pp = ConvertCPathsToPathsT(paths); + Paths64 result = rcl.Execute(pp); + return CreateCPathsFromPathsT(result); } -EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, +EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, const CPathsD paths, int precision) { - Paths64 result; if (CRectIsEmpty(rect) || !paths) return nullptr; if (precision < -8 || precision > 8) return nullptr; + const double scale = std::pow(10, precision); Rect64 r = ScaleRect(CRectToRect(rect), scale); - class RectClipLines rcl(r); - Paths64 pp = ConvertCPathsD(paths, scale); - - result.reserve(pp.size()); - for (const Path64& p : pp) - { - Rect64 pathRec = Bounds(p); - if (!r.Intersects(pathRec)) continue; - - if (r.Contains(pathRec)) - result.push_back(p); - else - { - Paths64 pp2 = rcl.Execute(p); - if (pp2.empty()) continue; - result.insert(result.end(), pp2.begin(), pp2.end()); - } - } - return CreateCPathsD(result, 1/scale); + class RectClipLines64 rcl(r); + Paths64 pp = ConvertCPathsDToPaths64(paths, scale); + Paths64 result = rcl.Execute(pp); + return CreateCPathsDFromPaths64(result, 1 / scale); } -inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2) +EXTERN_DLL_EXPORT CPaths64 MinkowskiSum64(const CPath64& cpattern, const CPath64& cpath, bool is_closed) { - // allocates memory for CPath64, fills in the counter, and - // returns the structure ready to be filled with path data - CPath64 result = new int64_t[2 + cnt1 *2]; - result[0] = cnt1; - result[1] = cnt2; - return result; + Path64 path = ConvertCPathToPathT(cpath); + Path64 pattern = ConvertCPathToPathT(cpattern); + Paths64 solution = MinkowskiSum(pattern, path, is_closed); + return CreateCPathsFromPathsT(solution); } -inline CPath64 CreateCPath64(const Path64& p) +EXTERN_DLL_EXPORT CPaths64 MinkowskiDiff64(const CPath64& cpattern, const CPath64& cpath, bool is_closed) { - // allocates memory for CPath64, fills the counter - // and returns the memory filled with path data - size_t cnt = p.size(); - if (!cnt) return nullptr; - CPath64 result = CreateCPath64(cnt, 0); - CPath64 v = result; - v += 2; // skip counters - for (const Point64& pt : p) - { - *v++ = pt.x; - *v++ = pt.y; - } - return result; + Path64 path = ConvertCPathToPathT(cpath); + Path64 pattern = ConvertCPathToPathT(cpattern); + Paths64 solution = MinkowskiDiff(pattern, path, is_closed); + return CreateCPathsFromPathsT(solution); } -inline Path64 ConvertCPath64(const CPath64& p) +#ifdef USINGZ +typedef void (*DLLZCallback64)(const Point64& e1bot, const Point64& e1top, const Point64& e2bot, const Point64& e2top, Point64& pt); +typedef void (*DLLZCallbackD)(const PointD& e1bot, const PointD& e1top, const PointD& e2bot, const PointD& e2top, PointD& pt); + +EXTERN_DLL_EXPORT void SetZCallback64(DLLZCallback64 callback) { - Path64 result; - if (p && *p) - { - CPath64 v = p; - const size_t cnt = static_cast(p[0]); - v += 2; // skip counters - result.reserve(cnt); - for (size_t i = 0; i < cnt; ++i) - { - // x,y here avoids right to left function evaluation - // result.push_back(Point64(*v++, *v++)); - int64_t x = *v++; - int64_t y = *v++; - result.push_back(Point64(x, y)); - } - } - return result; + dllCallback64 = callback; } -inline CPaths64 CreateCPaths64(const Paths64& pp) +EXTERN_DLL_EXPORT void SetZCallbackD(DLLZCallbackD callback) { - // allocates memory for multiple CPath64 and - // and returns this memory filled with path data - size_t cnt = pp.size(), cnt2 = cnt; - - // don't allocate space for empty paths - for (size_t i = 0; i < cnt; ++i) - if (!pp[i].size()) --cnt2; - if (!cnt2) return nullptr; - - CPaths64 result = new int64_t* [cnt2 + 1]; - CPaths64 v = result; - *v++ = CreateCPath64(0, cnt2); // assign a counter path - for (const Path64& p : pp) - { - *v = CreateCPath64(p); - if (*v) ++v; - } - return result; + dllCallbackD = callback; } -inline Paths64 ConvertCPaths64(const CPaths64& pp) -{ - Paths64 result; - if (pp) - { - CPaths64 v = pp; - CPath64 cnts = pp[0]; - const size_t cnt = static_cast(cnts[1]); // nb 2nd cnt - ++v; // skip cnts - result.reserve(cnt); - for (size_t i = 0; i < cnt; ++i) - result.push_back(ConvertCPath64(*v++)); - } - return result; -} +#endif -inline CPathD CreateCPathD(size_t cnt1, size_t cnt2) -{ - // allocates memory for CPathD, fills in the counter, and - // returns the structure ready to be filled with path data - CPathD result = new double[2 + cnt1 * 2]; - result[0] = static_cast(cnt1); - result[1] = static_cast(cnt2); - return result; } - -inline CPathD CreateCPathD(const PathD& p) -{ - // allocates memory for CPath, fills the counter - // and returns the memory fills with path data - size_t cnt = p.size(); - if (!cnt) return nullptr; - CPathD result = CreateCPathD(cnt, 0); - CPathD v = result; - v += 2; // skip counters - for (const PointD& pt : p) - { - *v++ = pt.x; - *v++ = pt.y; - } - return result; -} - -inline PathD ConvertCPathD(const CPathD& p) -{ - PathD result; - if (p) - { - CPathD v = p; - size_t cnt = static_cast(v[0]); - v += 2; // skip counters - result.reserve(cnt); - for (size_t i = 0; i < cnt; ++i) - { - // x,y here avoids right to left function evaluation - // result.push_back(PointD(*v++, *v++)); - double x = *v++; - double y = *v++; - result.push_back(PointD(x, y)); - } - } - return result; -} - -inline CPathsD CreateCPathsD(const PathsD& pp) -{ - size_t cnt = pp.size(), cnt2 = cnt; - // don't allocate space for empty paths - for (size_t i = 0; i < cnt; ++i) - if (!pp[i].size()) --cnt2; - if (!cnt2) return nullptr; - CPathsD result = new double * [cnt2 + 1]; - CPathsD v = result; - *v++ = CreateCPathD(0, cnt2); // assign counter path - for (const PathD& p : pp) - { - *v = CreateCPathD(p); - if (*v) { ++v; } - } - return result; -} - -inline PathsD ConvertCPathsD(const CPathsD& pp) -{ - PathsD result; - if (pp) - { - CPathsD v = pp; - CPathD cnts = v[0]; - size_t cnt = static_cast(cnts[1]); - ++v; // skip cnts path - result.reserve(cnt); - for (size_t i = 0; i < cnt; ++i) - result.push_back(ConvertCPathD(*v++)); - } - return result; -} - -inline Path64 ConvertCPathD(const CPathD& p, double scale) -{ - Path64 result; - if (p) - { - CPathD v = p; - size_t cnt = static_cast(*v); - v += 2; // skip counters - result.reserve(cnt); - for (size_t i = 0; i < cnt; ++i) - { - // x,y here avoids right to left function evaluation - // result.push_back(PointD(*v++, *v++)); - double x = *v++ * scale; - double y = *v++ * scale; - result.push_back(Point64(x, y)); - } - } - return result; -} - -inline Paths64 ConvertCPathsD(const CPathsD& pp, double scale) -{ - Paths64 result; - if (pp) - { - CPathsD v = pp; - CPathD cnts = v[0]; - size_t cnt = static_cast(cnts[1]); - result.reserve(cnt); - ++v; // skip cnts path - for (size_t i = 0; i < cnt; ++i) - result.push_back(ConvertCPathD(*v++, scale)); - } - return result; -} - -inline CPathD CreateCPathD(const Path64& p, double scale) -{ - // allocates memory for CPathD, fills in the counter, and - // returns the structure filled with *scaled* path data - size_t cnt = p.size(); - if (!cnt) return nullptr; - CPathD result = CreateCPathD(cnt, 0); - CPathD v = result; - v += 2; // skip cnts - for (const Point64& pt : p) - { - *v++ = pt.x * scale; - *v++ = pt.y * scale; - } - return result; -} - -inline CPathsD CreateCPathsD(const Paths64& pp, double scale) -{ - // allocates memory for *multiple* CPathD, and - // returns the structure filled with scaled path data - size_t cnt = pp.size(), cnt2 = cnt; - // don't allocate space for empty paths - for (size_t i = 0; i < cnt; ++i) - if (!pp[i].size()) --cnt2; - if (!cnt2) return nullptr; - CPathsD result = new double* [cnt2 + 1]; - CPathsD v = result; - *v++ = CreateCPathD(0, cnt2); - for (const Path64& p : pp) - { - *v = CreateCPathD(p, scale); - if (*v) ++v; - } - return result; -} - -inline void InitCPolyPath64(CPolyTree64* cpt, - bool is_hole, const PolyPath64* pp) -{ - cpt->polygon = CreateCPath64(pp->Polygon()); - cpt->is_hole = is_hole; - size_t child_cnt = pp->Count(); - cpt->child_count = static_cast(child_cnt); - cpt->childs = nullptr; - if (!child_cnt) return; - cpt->childs = new CPolyPath64[child_cnt]; - CPolyPath64* child = cpt->childs; - for (const PolyPath64* pp_child : *pp) - InitCPolyPath64(child++, !is_hole, pp_child); -} - -inline CPolyTree64* CreateCPolyTree64(const PolyTree64& pt) -{ - CPolyTree64* result = new CPolyTree64(); - result->polygon = nullptr; - result->is_hole = false; - size_t child_cnt = pt.Count(); - result->childs = nullptr; - result->child_count = static_cast(child_cnt); - if (!child_cnt) return result; - result->childs = new CPolyPath64[child_cnt]; - CPolyPath64* child = result->childs; - for (const PolyPath64* pp : pt) - InitCPolyPath64(child++, true, pp); - return result; -} - -inline void DisposeCPolyPath64(CPolyPath64* cpp) -{ - if (!cpp->child_count) return; - CPolyPath64* child = cpp->childs; - for (size_t i = 0; i < cpp->child_count; ++i) - DisposeCPolyPath64(child); - delete[] cpp->childs; -} - -EXTERN_DLL_EXPORT void DisposeExportedCPolyTree64(CPolyTree64*& cpt) -{ - if (!cpt) return; - DisposeCPolyPath64(cpt); - delete cpt; - cpt = nullptr; -} - -inline void InitCPolyPathD(CPolyTreeD* cpt, - bool is_hole, const PolyPath64* pp, double scale) -{ - cpt->polygon = CreateCPathD(pp->Polygon(), scale); - cpt->is_hole = is_hole; - size_t child_cnt = pp->Count(); - cpt->child_count = static_cast(child_cnt); - cpt->childs = nullptr; - if (!child_cnt) return; - cpt->childs = new CPolyPathD[child_cnt]; - CPolyPathD* child = cpt->childs; - for (const PolyPath64* pp_child : *pp) - InitCPolyPathD(child++, !is_hole, pp_child, scale); -} - -inline CPolyTreeD* CreateCPolyTreeD(const PolyTree64& pt, double scale) -{ - CPolyTreeD* result = new CPolyTreeD(); - result->polygon = nullptr; - result->is_hole = false; - size_t child_cnt = pt.Count(); - result->child_count = static_cast(child_cnt); - result->childs = nullptr; - if (!child_cnt) return result; - result->childs = new CPolyPathD[child_cnt]; - CPolyPathD* child = result->childs; - for (const PolyPath64* pp : pt) - InitCPolyPathD(child++, true, pp, scale); - return result; -} - -inline void DisposeCPolyPathD(CPolyPathD* cpp) -{ - if (!cpp->child_count) return; - CPolyPathD* child = cpp->childs; - for (size_t i = 0; i < cpp->child_count; ++i) - DisposeCPolyPathD(child++); - delete[] cpp->childs; -} - -EXTERN_DLL_EXPORT void DisposeExportedCPolyTreeD(CPolyTreeD*& cpt) -{ - if (!cpt) return; - DisposeCPolyPathD(cpt); - delete cpt; - cpt = nullptr; -} - -} // end Clipper2Lib namespace - #endif // CLIPPER2_EXPORT_H diff --git a/src/clipper2/Clipper2Lib/include/clipper2/clipper.h b/src/clipper2/Clipper2Lib/include/clipper2/clipper.h index 8bebfcf..b75bbd3 100644 --- a/src/clipper2/Clipper2Lib/include/clipper2/clipper.h +++ b/src/clipper2/Clipper2Lib/include/clipper2/clipper.h @@ -1,41 +1,27 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 29 October 2022 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2022 * +* Date : 27 April 2024 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2024 * * Purpose : This module provides a simple interface to the Clipper Library * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ #ifndef CLIPPER_H #define CLIPPER_H -#include -#include - -#include "clipper.core.h" -#include "clipper.engine.h" -#include "clipper.offset.h" -#include "clipper.minkowski.h" -#include "clipper.rectclip.h" +#include "clipper2/clipper.core.h" +#include "clipper2/clipper.engine.h" +#include "clipper2/clipper.offset.h" +#include "clipper2/clipper.minkowski.h" +#include "clipper2/clipper.rectclip.h" +#include namespace Clipper2Lib { - static const Rect64 MaxInvalidRect64 = Rect64( - (std::numeric_limits::max)(), - (std::numeric_limits::max)(), - (std::numeric_limits::lowest)(), - (std::numeric_limits::lowest)()); - - static const RectD MaxInvalidRectD = RectD( - (std::numeric_limits::max)(), - (std::numeric_limits::max)(), - (std::numeric_limits::lowest)(), - (std::numeric_limits::lowest)()); - inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule, const Paths64& subjects, const Paths64& clips) - { + { Paths64 result; Clipper64 clipper; clipper.AddSubject(subjects); @@ -55,11 +41,13 @@ namespace Clipper2Lib { } inline PathsD BooleanOp(ClipType cliptype, FillRule fillrule, - const PathsD& subjects, const PathsD& clips, int decimal_prec = 2) + const PathsD& subjects, const PathsD& clips, int precision = 2) { - CheckPrecision(decimal_prec); + int error_code = 0; + CheckPrecisionRange(precision, error_code); PathsD result; - ClipperD clipper(decimal_prec); + if (error_code) return result; + ClipperD clipper(precision); clipper.AddSubject(subjects); clipper.AddClip(clips); clipper.Execute(cliptype, fillrule, result); @@ -67,12 +55,14 @@ namespace Clipper2Lib { } inline void BooleanOp(ClipType cliptype, FillRule fillrule, - const PathsD& subjects, const PathsD& clips, - PolyTreeD& polytree, int decimal_prec = 2) + const PathsD& subjects, const PathsD& clips, + PolyTreeD& polytree, int precision = 2) { - CheckPrecision(decimal_prec); - PathsD result; - ClipperD clipper(decimal_prec); + polytree.Clear(); + int error_code = 0; + CheckPrecisionRange(precision, error_code); + if (error_code) return; + ClipperD clipper(precision); clipper.AddSubject(subjects); clipper.AddClip(clips); clipper.Execute(cliptype, fillrule, polytree); @@ -82,7 +72,7 @@ namespace Clipper2Lib { { return BooleanOp(ClipType::Intersection, fillrule, subjects, clips); } - + inline PathsD Intersect(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2) { return BooleanOp(ClipType::Intersection, fillrule, subjects, clips, decimal_prec); @@ -107,11 +97,13 @@ namespace Clipper2Lib { return result; } - inline PathsD Union(const PathsD& subjects, FillRule fillrule, int decimal_prec = 2) + inline PathsD Union(const PathsD& subjects, FillRule fillrule, int precision = 2) { - CheckPrecision(decimal_prec); PathsD result; - ClipperD clipper(decimal_prec); + int error_code = 0; + CheckPrecisionRange(precision, error_code); + if (error_code) return result; + ClipperD clipper(precision); clipper.AddSubject(subjects); clipper.Execute(ClipType::Union, fillrule, result); return result; @@ -138,265 +130,138 @@ namespace Clipper2Lib { } inline Paths64 InflatePaths(const Paths64& paths, double delta, - JoinType jt, EndType et, double miter_limit = 2.0) + JoinType jt, EndType et, double miter_limit = 2.0, + double arc_tolerance = 0.0) { - ClipperOffset clip_offset(miter_limit); + if (!delta) return paths; + ClipperOffset clip_offset(miter_limit, arc_tolerance); clip_offset.AddPaths(paths, jt, et); - return clip_offset.Execute(delta); + Paths64 solution; + clip_offset.Execute(delta, solution); + return solution; } inline PathsD InflatePaths(const PathsD& paths, double delta, - JoinType jt, EndType et, double miter_limit = 2.0, int precision = 2) + JoinType jt, EndType et, double miter_limit = 2.0, + int precision = 2, double arc_tolerance = 0.0) { - CheckPrecision(precision); + int error_code = 0; + CheckPrecisionRange(precision, error_code); + if (!delta) return paths; + if (error_code) return PathsD(); const double scale = std::pow(10, precision); - ClipperOffset clip_offset(miter_limit); - clip_offset.AddPaths(ScalePaths(paths, scale), jt, et); - Paths64 tmp = clip_offset.Execute(delta * scale); - return ScalePaths(tmp, 1 / scale); + ClipperOffset clip_offset(miter_limit, arc_tolerance); + clip_offset.AddPaths(ScalePaths(paths, scale, error_code), jt, et); + if (error_code) return PathsD(); + Paths64 solution; + clip_offset.Execute(delta * scale, solution); + return ScalePaths(solution, 1 / scale, error_code); + } + + template + inline Path TranslatePath(const Path& path, T dx, T dy) + { + Path result; + result.reserve(path.size()); + std::transform(path.begin(), path.end(), back_inserter(result), + [dx, dy](const auto& pt) { return Point(pt.x + dx, pt.y +dy); }); + return result; } inline Path64 TranslatePath(const Path64& path, int64_t dx, int64_t dy) { - Path64 result; - result.reserve(path.size()); - for (const Point64& pt : path) - result.push_back(Point64(pt.x + dx, pt.y + dy)); - return result; + return TranslatePath(path, dx, dy); } inline PathD TranslatePath(const PathD& path, double dx, double dy) { - PathD result; - result.reserve(path.size()); - for (const PointD& pt : path) - result.push_back(PointD(pt.x + dx, pt.y + dy)); + return TranslatePath(path, dx, dy); + } + + template + inline Paths TranslatePaths(const Paths& paths, T dx, T dy) + { + Paths result; + result.reserve(paths.size()); + std::transform(paths.begin(), paths.end(), back_inserter(result), + [dx, dy](const auto& path) { return TranslatePath(path, dx, dy); }); return result; } inline Paths64 TranslatePaths(const Paths64& paths, int64_t dx, int64_t dy) { - Paths64 result; - result.reserve(paths.size()); - for (const Path64& path : paths) - result.push_back(TranslatePath(path, dx, dy)); - return result; + return TranslatePaths(paths, dx, dy); } inline PathsD TranslatePaths(const PathsD& paths, double dx, double dy) { - PathsD result; - result.reserve(paths.size()); - for (const PathD& path : paths) - result.push_back(TranslatePath(path, dx, dy)); - return result; + return TranslatePaths(paths, dx, dy); } - inline Rect64 Bounds(const Path64& path) - { - Rect64 rec = MaxInvalidRect64; - for (const Point64& pt : path) - { - if (pt.x < rec.left) rec.left = pt.x; - if (pt.x > rec.right) rec.right = pt.x; - if (pt.y < rec.top) rec.top = pt.y; - if (pt.y > rec.bottom) rec.bottom = pt.y; - } - if (rec.IsEmpty()) return Rect64(); - return rec; - } - - inline Rect64 Bounds(const Paths64& paths) - { - Rect64 rec = MaxInvalidRect64; - for (const Path64& path : paths) - for (const Point64& pt : path) - { - if (pt.x < rec.left) rec.left = pt.x; - if (pt.x > rec.right) rec.right = pt.x; - if (pt.y < rec.top) rec.top = pt.y; - if (pt.y > rec.bottom) rec.bottom = pt.y; - } - if (rec.IsEmpty()) return Rect64(); - return rec; - } - - inline RectD Bounds(const PathD& path) - { - RectD rec = MaxInvalidRectD; - for (const PointD& pt : path) - { - if (pt.x < rec.left) rec.left = pt.x; - if (pt.x > rec.right) rec.right = pt.x; - if (pt.y < rec.top) rec.top = pt.y; - if (pt.y > rec.bottom) rec.bottom = pt.y; - } - if (rec.IsEmpty()) return RectD(); - return rec; - } - - inline RectD Bounds(const PathsD& paths) - { - RectD rec = MaxInvalidRectD; - for (const PathD& path : paths) - for (const PointD& pt : path) - { - if (pt.x < rec.left) rec.left = pt.x; - if (pt.x > rec.right) rec.right = pt.x; - if (pt.y < rec.top) rec.top = pt.y; - if (pt.y > rec.bottom) rec.bottom = pt.y; - } - if (rec.IsEmpty()) return RectD(); - return rec; - } - - inline Path64 RectClip(const Rect64& rect, const Path64& path) - { - if (rect.IsEmpty() || path.empty()) return Path64(); - Rect64 pathRec = Bounds(path); - if (!rect.Intersects(pathRec)) return Path64(); - if (rect.Contains(pathRec)) return path; - class RectClip rc(rect); - return rc.Execute(path); - } - inline Paths64 RectClip(const Rect64& rect, const Paths64& paths) { if (rect.IsEmpty() || paths.empty()) return Paths64(); - class RectClip rc(rect); - Paths64 result; - result.reserve(paths.size()); - - for (const Path64& p : paths) - { - Rect64 pathRec = Bounds(p); - if (!rect.Intersects(pathRec)) - continue; - else if (rect.Contains(pathRec)) - result.push_back(p); - else - { - Path64 p2 = rc.Execute(p); - if (!p2.empty()) result.push_back(std::move(p2)); - } - } - return result; + RectClip64 rc(rect); + return rc.Execute(paths); } - inline PathD RectClip(const RectD& rect, const PathD& path, int precision = 2) + inline Paths64 RectClip(const Rect64& rect, const Path64& path) { - if (rect.IsEmpty() || path.empty() || - !rect.Contains(Bounds(path))) return PathD(); - CheckPrecision(precision); - const double scale = std::pow(10, precision); - Rect64 r = ScaleRect(rect, scale); - class RectClip rc(r); - Path64 p = ScalePath(path, scale); - return ScalePath(rc.Execute(p), 1 / scale); + if (rect.IsEmpty() || path.empty()) return Paths64(); + RectClip64 rc(rect); + return rc.Execute(Paths64{ path }); } inline PathsD RectClip(const RectD& rect, const PathsD& paths, int precision = 2) { if (rect.IsEmpty() || paths.empty()) return PathsD(); - CheckPrecision(precision); + int error_code = 0; + CheckPrecisionRange(precision, error_code); + if (error_code) return PathsD(); const double scale = std::pow(10, precision); Rect64 r = ScaleRect(rect, scale); - class RectClip rc(r); - PathsD result; - result.reserve(paths.size()); - for (const PathD& path : paths) - { - RectD pathRec = Bounds(path); - if (!rect.Intersects(pathRec)) - continue; - else if (rect.Contains(pathRec)) - result.push_back(path); - else - { - Path64 p = ScalePath(path, scale); - p = rc.Execute(p); - if (!p.empty()) - result.push_back(ScalePath(p, 1 / scale)); - } - } - return result; + RectClip64 rc(r); + Paths64 pp = ScalePaths(paths, scale, error_code); + if (error_code) return PathsD(); // ie: error_code result is lost + return ScalePaths( + rc.Execute(pp), 1 / scale, error_code); } - inline Paths64 RectClipLines(const Rect64& rect, const Path64& path) + inline PathsD RectClip(const RectD& rect, const PathD& path, int precision = 2) { - Paths64 result; - if (rect.IsEmpty() || path.empty()) return result; - Rect64 pathRec = Bounds(path); - if (!rect.Intersects(pathRec)) return result; - if (rect.Contains(pathRec)) - { - result.push_back(path); - return result; - } - class RectClipLines rcl(rect); - return rcl.Execute(path); + return RectClip(rect, PathsD{ path }, precision); } - inline Paths64 RectClipLines(const Rect64& rect, const Paths64& paths) + inline Paths64 RectClipLines(const Rect64& rect, const Paths64& lines) { - Paths64 result; - if (rect.IsEmpty() || paths.empty()) return result; - class RectClipLines rcl(rect); - for (const Path64& p : paths) - { - Rect64 pathRec = Bounds(p); - if (!rect.Intersects(pathRec)) - continue; - else if (rect.Contains(pathRec)) - result.push_back(p); - else - { - Paths64 pp = rcl.Execute(p); - if (!pp.empty()) - result.insert(result.end(), pp.begin(), pp.end()); - } - } - return result; + if (rect.IsEmpty() || lines.empty()) return Paths64(); + RectClipLines64 rcl(rect); + return rcl.Execute(lines); } - inline PathsD RectClipLines(const RectD& rect, const PathD& path, int precision = 2) + inline Paths64 RectClipLines(const Rect64& rect, const Path64& line) { - if (rect.IsEmpty() || path.empty() || - !rect.Contains(Bounds(path))) return PathsD(); - CheckPrecision(precision); + return RectClipLines(rect, Paths64{ line }); + } + + inline PathsD RectClipLines(const RectD& rect, const PathsD& lines, int precision = 2) + { + if (rect.IsEmpty() || lines.empty()) return PathsD(); + int error_code = 0; + CheckPrecisionRange(precision, error_code); + if (error_code) return PathsD(); const double scale = std::pow(10, precision); Rect64 r = ScaleRect(rect, scale); - class RectClipLines rcl(r); - Path64 p = ScalePath(path, scale); - return ScalePaths(rcl.Execute(p), 1 / scale); + RectClipLines64 rcl(r); + Paths64 p = ScalePaths(lines, scale, error_code); + if (error_code) return PathsD(); + p = rcl.Execute(p); + return ScalePaths(p, 1 / scale, error_code); } - inline PathsD RectClipLines(const RectD& rect, const PathsD& paths, int precision = 2) + inline PathsD RectClipLines(const RectD& rect, const PathD& line, int precision = 2) { - PathsD result; - if (rect.IsEmpty() || paths.empty()) return result; - CheckPrecision(precision); - const double scale = std::pow(10, precision); - Rect64 r = ScaleRect(rect, scale); - class RectClipLines rcl(r); - result.reserve(paths.size()); - for (const PathD& path : paths) - { - RectD pathRec = Bounds(path); - if (!rect.Intersects(pathRec)) - continue; - else if (rect.Contains(pathRec)) - result.push_back(path); - else - { - Path64 p = ScalePath(path, scale); - Paths64 pp = rcl.Execute(p); - if (pp.empty()) continue; - PathsD ppd = ScalePaths(pp, 1 / scale); - result.insert(result.end(), ppd.begin(), ppd.end()); - } - } - return result; + return RectClipLines(rect, PathsD{ line }, precision); } namespace details @@ -404,183 +269,229 @@ namespace Clipper2Lib { inline void PolyPathToPaths64(const PolyPath64& polypath, Paths64& paths) { - paths.push_back(polypath.Polygon()); - for (const PolyPath* child : polypath) - PolyPathToPaths64(*(PolyPath64*)(child), paths); + paths.emplace_back(polypath.Polygon()); + for (const auto& child : polypath) + PolyPathToPaths64(*child, paths); } inline void PolyPathToPathsD(const PolyPathD& polypath, PathsD& paths) { - paths.push_back(polypath.Polygon()); - for (const PolyPath* child : polypath) - PolyPathToPathsD(*(PolyPathD*)(child), paths); + paths.emplace_back(polypath.Polygon()); + for (const auto& child : polypath) + PolyPathToPathsD(*child, paths); } inline bool PolyPath64ContainsChildren(const PolyPath64& pp) { - for (auto ch : pp) + for (const auto& child : pp) { - PolyPath64* child = (PolyPath64*)ch; + // return false if this child isn't fully contained by its parent + + // checking for a single vertex outside is a bit too crude since + // it doesn't account for rounding errors. It's better to check + // for consecutive vertices found outside the parent's polygon. + + int outsideCnt = 0; for (const Point64& pt : child->Polygon()) - if (PointInPolygon(pt, pp.Polygon()) == PointInPolygonResult::IsOutside) - return false; + { + PointInPolygonResult result = PointInPolygon(pt, pp.Polygon()); + if (result == PointInPolygonResult::IsInside) --outsideCnt; + else if (result == PointInPolygonResult::IsOutside) ++outsideCnt; + if (outsideCnt > 1) return false; + else if (outsideCnt < -1) break; + } + + // now check any nested children too if (child->Count() > 0 && !PolyPath64ContainsChildren(*child)) return false; } return true; } - inline bool GetInt(std::string::const_iterator& iter, const - std::string::const_iterator& end_iter, int64_t& val) + static void OutlinePolyPath(std::ostream& os, + size_t idx, bool isHole, size_t count, const std::string& preamble) { - val = 0; - bool is_neg = *iter == '-'; - if (is_neg) ++iter; - std::string::const_iterator start_iter = iter; - while (iter != end_iter && - ((*iter >= '0') && (*iter <= '9'))) - { - val = val * 10 + (static_cast(*iter++) - '0'); - } - if (is_neg) val = -val; - return (iter != start_iter); + std::string plural = (count == 1) ? "." : "s."; + if (isHole) + os << preamble << "+- Hole (" << idx << ") contains " << count << + " nested polygon" << plural << std::endl; + else + os << preamble << "+- Polygon (" << idx << ") contains " << count << + " hole" << plural << std::endl; } - inline bool GetFloat(std::string::const_iterator& iter, const - std::string::const_iterator& end_iter, double& val) + static void OutlinePolyPath64(std::ostream& os, const PolyPath64& pp, + size_t idx, std::string preamble) { - val = 0; - bool is_neg = *iter == '-'; - if (is_neg) ++iter; - int dec_pos = 1; - const std::string::const_iterator start_iter = iter; - while (iter != end_iter && (*iter == '.' || - ((*iter >= '0') && (*iter <= '9')))) - { - if (*iter == '.') - { - if (dec_pos != 1) break; - dec_pos = 0; - ++iter; - continue; - } - if (dec_pos != 1) --dec_pos; - val = val * 10 + ((int64_t)(*iter++) - '0'); - } - if (iter == start_iter || dec_pos == 0) return false; - if (dec_pos < 0) - val *= std::pow(10, dec_pos); - if (is_neg) - val *= -1; - return true; + OutlinePolyPath(os, idx, pp.IsHole(), pp.Count(), preamble); + for (size_t i = 0; i < pp.Count(); ++i) + if (pp.Child(i)->Count()) + details::OutlinePolyPath64(os, *pp.Child(i), i, preamble + " "); } - inline void SkipWhiteSpace(std::string::const_iterator& iter, - const std::string::const_iterator& end_iter) + static void OutlinePolyPathD(std::ostream& os, const PolyPathD& pp, + size_t idx, std::string preamble) { - while (iter != end_iter && *iter <= ' ') ++iter; + OutlinePolyPath(os, idx, pp.IsHole(), pp.Count(), preamble); + for (size_t i = 0; i < pp.Count(); ++i) + if (pp.Child(i)->Count()) + details::OutlinePolyPathD(os, *pp.Child(i), i, preamble + " "); } - inline void SkipSpacesWithOptionalComma(std::string::const_iterator& iter, - const std::string::const_iterator& end_iter) + template + inline constexpr void MakePathGeneric(const T an_array, + size_t array_size, std::vector& result) { - bool comma_seen = false; - while (iter != end_iter) - { - if (*iter == ' ') ++iter; - else if (*iter == ',') - { - if (comma_seen) return; // don't skip 2 commas! - comma_seen = true; - ++iter; - } - else return; - } + result.reserve(array_size / 2); + for (size_t i = 0; i < array_size; i +=2) +#ifdef USINGZ + result.emplace_back( an_array[i], an_array[i + 1], 0 ); +#else + result.emplace_back( an_array[i], an_array[i + 1] ); +#endif } - inline bool has_one_match(const char c, char* chrs) - { - while (*chrs > 0 && c != *chrs) ++chrs; - if (!*chrs) return false; - *chrs = ' '; // only match once per char - return true; - } + } // end details namespace + inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp) + { + std::string plural = (pp.Count() == 1) ? " polygon." : " polygons."; + os << std::endl << "Polytree with " << pp.Count() << plural << std::endl; + for (size_t i = 0; i < pp.Count(); ++i) + if (pp.Child(i)->Count()) + details::OutlinePolyPath64(os, *pp.Child(i), i, " "); + os << std::endl << std::endl; + return os; + } - inline void SkipUserDefinedChars(std::string::const_iterator& iter, - const std::string::const_iterator& end_iter, const std::string& skip_chars) - { - const size_t MAX_CHARS = 16; - char buff[MAX_CHARS] = {0}; - std::copy(skip_chars.cbegin(), skip_chars.cend(), &buff[0]); - while (iter != end_iter && - (*iter <= ' ' || has_one_match(*iter, buff))) ++iter; - return; - } - - } // end details namespace + inline std::ostream& operator<< (std::ostream& os, const PolyTreeD& pp) + { + std::string plural = (pp.Count() == 1) ? " polygon." : " polygons."; + os << std::endl << "Polytree with " << pp.Count() << plural << std::endl; + for (size_t i = 0; i < pp.Count(); ++i) + if (pp.Child(i)->Count()) + details::OutlinePolyPathD(os, *pp.Child(i), i, " "); + os << std::endl << std::endl; + if (!pp.Level()) os << std::endl; + return os; + } inline Paths64 PolyTreeToPaths64(const PolyTree64& polytree) { Paths64 result; - for (auto child : polytree) - details::PolyPathToPaths64(*(PolyPath64*)(child), result); + for (const auto& child : polytree) + details::PolyPathToPaths64(*child, result); return result; } inline PathsD PolyTreeToPathsD(const PolyTreeD& polytree) { PathsD result; - for (auto child : polytree) - details::PolyPathToPathsD(*(PolyPathD*)(child), result); + for (const auto& child : polytree) + details::PolyPathToPathsD(*child, result); return result; } inline bool CheckPolytreeFullyContainsChildren(const PolyTree64& polytree) { - for (auto child : polytree) - if (child->Count() > 0 && - !details::PolyPath64ContainsChildren(*(PolyPath64*)(child))) + for (const auto& child : polytree) + if (child->Count() > 0 && + !details::PolyPath64ContainsChildren(*child)) return false; return true; } - inline Path64 MakePath(const std::string& s) + template::value && + !std::is_same::value, bool + >::type = true> + inline Path64 MakePath(const std::vector& list) { - const std::string skip_chars = " ,(){}[]"; + const auto size = list.size() - list.size() % 2; + if (list.size() != size) + DoError(non_pair_error_i); // non-fatal without exception handling Path64 result; - std::string::const_iterator s_iter = s.cbegin(); - details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars); - while (s_iter != s.cend()) - { - int64_t y = 0, x = 0; - if (!details::GetInt(s_iter, s.cend(), x)) break; - details::SkipSpacesWithOptionalComma(s_iter, s.cend()); - if (!details::GetInt(s_iter, s.cend(), y)) break; - result.push_back(Point64(x, y)); - details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars); - } + details::MakePathGeneric(list, size, result); return result; } - - inline PathD MakePathD(const std::string& s) + + template::value && + !std::is_same::value, bool + >::type = true> + inline Path64 MakePath(const T(&list)[N]) { - const std::string skip_chars = " ,(){}[]"; - PathD result; - std::string::const_iterator s_iter = s.cbegin(); - details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars); - while (s_iter != s.cend()) - { - double y = 0, x = 0; - if (!details::GetFloat(s_iter, s.cend(), x)) break; - details::SkipSpacesWithOptionalComma(s_iter, s.cend()); - if (!details::GetFloat(s_iter, s.cend(), y)) break; - result.push_back(PointD(x, y)); - details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars); - } + // Make the compiler error on unpaired value (i.e. no runtime effects). + static_assert(N % 2 == 0, "MakePath requires an even number of arguments"); + Path64 result; + details::MakePathGeneric(list, N, result); return result; } + template::value && + !std::is_same::value, bool + >::type = true> + inline PathD MakePathD(const std::vector& list) + { + const auto size = list.size() - list.size() % 2; + if (list.size() != size) + DoError(non_pair_error_i); // non-fatal without exception handling + PathD result; + details::MakePathGeneric(list, size, result); + return result; + } + + template::value && + !std::is_same::value, bool + >::type = true> + inline PathD MakePathD(const T(&list)[N]) + { + // Make the compiler error on unpaired value (i.e. no runtime effects). + static_assert(N % 2 == 0, "MakePath requires an even number of arguments"); + PathD result; + details::MakePathGeneric(list, N, result); + return result; + } + +#ifdef USINGZ + template + inline Path64 MakePathZ(const T2(&list)[N]) + { + static_assert(N % 3 == 0 && std::numeric_limits::is_integer, + "MakePathZ requires integer values in multiples of 3"); + std::size_t size = N / 3; + Path64 result(size); + for (size_t i = 0; i < size; ++i) + result[i] = Point64(list[i * 3], + list[i * 3 + 1], list[i * 3 + 2]); + return result; + } + + template + inline PathD MakePathZD(const T2(&list)[N]) + { + static_assert(N % 3 == 0, + "MakePathZD requires values in multiples of 3"); + std::size_t size = N / 3; + PathD result(size); + if constexpr (std::numeric_limits::is_integer) + for (size_t i = 0; i < size; ++i) + result[i] = PointD(list[i * 3], + list[i * 3 + 1], list[i * 3 + 2]); + else + for (size_t i = 0; i < size; ++i) + result[i] = PointD(list[i * 3], list[i * 3 + 1], + static_cast(list[i * 3 + 2])); + return result; + } +#endif + inline Path64 TrimCollinear(const Path64& p, bool is_open_path = false) { size_t len = p.size(); @@ -596,32 +507,32 @@ namespace Clipper2Lib { if (!is_open_path) { - while (srcIt != stop && !CrossProduct(*stop, *srcIt, *(srcIt + 1))) + while (srcIt != stop && IsCollinear(*stop, *srcIt, *(srcIt + 1))) ++srcIt; - while (srcIt != stop && !CrossProduct(*(stop - 1), *stop, *srcIt)) + while (srcIt != stop && IsCollinear(*(stop - 1), *stop, *srcIt)) --stop; if (srcIt == stop) return Path64(); } prevIt = srcIt++; - dst.push_back(*prevIt); + dst.emplace_back(*prevIt); for (; srcIt != stop; ++srcIt) { - if (CrossProduct(*prevIt, *srcIt, *(srcIt + 1))) + if (!IsCollinear(*prevIt, *srcIt, *(srcIt + 1))) { prevIt = srcIt; - dst.push_back(*prevIt); + dst.emplace_back(*prevIt); } } if (is_open_path) - dst.push_back(*srcIt); - else if (CrossProduct(*prevIt, *stop, dst[0])) - dst.push_back(*stop); + dst.emplace_back(*srcIt); + else if (!IsCollinear(*prevIt, *stop, dst[0])) + dst.emplace_back(*stop); else { while (dst.size() > 2 && - !CrossProduct(dst[dst.size() - 1], dst[dst.size() - 2], dst[0])) + IsCollinear(dst[dst.size() - 1], dst[dst.size() - 2], dst[0])) dst.pop_back(); if (dst.size() < 3) return Path64(); } @@ -630,11 +541,14 @@ namespace Clipper2Lib { inline PathD TrimCollinear(const PathD& path, int precision, bool is_open_path = false) { - CheckPrecision(precision); + int error_code = 0; + CheckPrecisionRange(precision, error_code); + if (error_code) return PathD(); const double scale = std::pow(10, precision); - Path64 p = ScalePath(path, scale); + Path64 p = ScalePath(path, scale, error_code); + if (error_code) return PathD(); p = TrimCollinear(p, is_open_path); - return ScalePath(p, 1/scale); + return ScalePath(p, 1/scale, error_code); } template @@ -663,33 +577,33 @@ namespace Clipper2Lib { double cp = std::abs(CrossProduct(pt1, pt2, pt3)); return (cp * cp) / (DistanceSqr(pt1, pt2) * DistanceSqr(pt2, pt3)) < sin_sqrd_min_angle_rads; } - + template - inline Path Ellipse(const Rect& rect, int steps = 0) + inline Path Ellipse(const Rect& rect, size_t steps = 0) { - return Ellipse(rect.MidPoint(), - static_cast(rect.Width()) *0.5, + return Ellipse(rect.MidPoint(), + static_cast(rect.Width()) *0.5, static_cast(rect.Height()) * 0.5, steps); } template inline Path Ellipse(const Point& center, - double radiusX, double radiusY = 0, int steps = 0) + double radiusX, double radiusY = 0, size_t steps = 0) { if (radiusX <= 0) return Path(); if (radiusY <= 0) radiusY = radiusX; if (steps <= 2) - steps = static_cast(PI * sqrt((radiusX + radiusY) / 2)); + steps = static_cast(PI * sqrt((radiusX + radiusY) / 2)); double si = std::sin(2 * PI / steps); double co = std::cos(2 * PI / steps); double dx = co, dy = si; Path result; result.reserve(steps); - result.push_back(Point(center.x + radiusX, static_cast(center.y))); - for (int i = 1; i < steps; ++i) + result.emplace_back(center.x + radiusX, static_cast(center.y)); + for (size_t i = 1; i < steps; ++i) { - result.push_back(Point(center.x + radiusX * dx, center.y + radiusY * dy)); + result.emplace_back(center.x + radiusX * dx, center.y + radiusY * dy); double x = dx * co - dy * si; dy = dy * co + dx * si; dx = x; @@ -697,16 +611,105 @@ namespace Clipper2Lib { return result; } - template - inline double PerpendicDistFromLineSqrd(const Point& pt, - const Point& line1, const Point& line2) + inline size_t GetNext(size_t current, size_t high, + const std::vector& flags) { - double a = static_cast(pt.x - line1.x); - double b = static_cast(pt.y - line1.y); - double c = static_cast(line2.x - line1.x); - double d = static_cast(line2.y - line1.y); - if (c == 0 && d == 0) return 0; - return Sqr(a * d - c * b) / (c * c + d * d); + ++current; + while (current <= high && flags[current]) ++current; + if (current <= high) return current; + current = 0; + while (flags[current]) ++current; + return current; + } + + inline size_t GetPrior(size_t current, size_t high, + const std::vector& flags) + { + if (current == 0) current = high; + else --current; + while (current > 0 && flags[current]) --current; + if (!flags[current]) return current; + current = high; + while (flags[current]) --current; + return current; + } + + template + inline Path SimplifyPath(const Path &path, + double epsilon, bool isClosedPath = true) + { + const size_t len = path.size(), high = len -1; + const double epsSqr = Sqr(epsilon); + if (len < 4) return Path(path); + + std::vector flags(len); + std::vector distSqr(len); + size_t prior = high, curr = 0, start, next, prior2; + if (isClosedPath) + { + distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]); + distSqr[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]); + } + else + { + distSqr[0] = MAX_DBL; + distSqr[high] = MAX_DBL; + } + for (size_t i = 1; i < high; ++i) + distSqr[i] = PerpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]); + + for (;;) + { + if (distSqr[curr] > epsSqr) + { + start = curr; + do + { + curr = GetNext(curr, high, flags); + } while (curr != start && distSqr[curr] > epsSqr); + if (curr == start) break; + } + + prior = GetPrior(curr, high, flags); + next = GetNext(curr, high, flags); + if (next == prior) break; + + // flag for removal the smaller of adjacent 'distances' + if (distSqr[next] < distSqr[curr]) + { + prior2 = prior; + prior = curr; + curr = next; + next = GetNext(next, high, flags); + } + else + prior2 = GetPrior(prior, high, flags); + + flags[curr] = true; + curr = next; + next = GetNext(next, high, flags); + + if (isClosedPath || ((curr != high) && (curr != 0))) + distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]); + if (isClosedPath || ((prior != 0) && (prior != high))) + distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]); + } + Path result; + result.reserve(len); + for (typename Path::size_type i = 0; i < len; ++i) + if (!flags[i]) result.emplace_back(path[i]); + return result; + } + + template + inline Paths SimplifyPaths(const Paths &paths, + double epsilon, bool isClosedPath = true) + { + Paths result; + result.reserve(paths.size()); + for (const auto& path : paths) + result.emplace_back(std::move(SimplifyPath(path, epsilon, isClosedPath))); + return result; } template @@ -743,7 +746,7 @@ namespace Clipper2Lib { result.reserve(len); for (typename Path::size_type i = 0; i < len; ++i) if (flags[i]) - result.push_back(path[i]); + result.emplace_back(path[i]); return result; } @@ -752,8 +755,9 @@ namespace Clipper2Lib { { Paths result; result.reserve(paths.size()); - for (const Path& path : paths) - result.push_back(RamerDouglasPeucker(path, epsilon)); + std::transform(paths.begin(), paths.end(), back_inserter(result), + [epsilon](const auto& path) + { return RamerDouglasPeucker(path, epsilon); }); return result; } diff --git a/src/clipper2/Clipper2Lib/include/clipper2/clipper.minkowski.h b/src/clipper2/Clipper2Lib/include/clipper2/clipper.minkowski.h index ca0ab6b..3a85ba5 100644 --- a/src/clipper2/Clipper2Lib/include/clipper2/clipper.minkowski.h +++ b/src/clipper2/Clipper2Lib/include/clipper2/clipper.minkowski.h @@ -1,21 +1,18 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 15 October 2022 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2022 * +* Date : 1 November 2023 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2023 * * Purpose : Minkowski Sum and Difference * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ #ifndef CLIPPER_MINKOWSKI_H #define CLIPPER_MINKOWSKI_H -#include -#include -#include -#include "clipper.core.h" +#include "clipper2/clipper.core.h" -namespace Clipper2Lib +namespace Clipper2Lib { namespace detail @@ -35,7 +32,7 @@ namespace Clipper2Lib Path64 path2(pattern.size()); std::transform(pattern.cbegin(), pattern.cend(), path2.begin(), [p](const Point64& pt2) {return p + pt2; }); - tmp.push_back(path2); + tmp.emplace_back(std::move(path2)); } } else @@ -45,7 +42,7 @@ namespace Clipper2Lib Path64 path2(pattern.size()); std::transform(pattern.cbegin(), pattern.cend(), path2.begin(), [p](const Point64& pt2) {return p - pt2; }); - tmp.push_back(path2); + tmp.emplace_back(std::move(path2)); } } @@ -59,14 +56,14 @@ namespace Clipper2Lib Path64 quad; quad.reserve(4); { - quad.push_back(tmp[g][h]); - quad.push_back(tmp[i][h]); - quad.push_back(tmp[i][j]); - quad.push_back(tmp[g][j]); + quad.emplace_back(tmp[g][h]); + quad.emplace_back(tmp[i][h]); + quad.emplace_back(tmp[i][j]); + quad.emplace_back(tmp[g][j]); }; if (!IsPositive(quad)) std::reverse(quad.begin(), quad.end()); - result.push_back(quad); + result.emplace_back(std::move(quad)); h = j; } g = i; @@ -92,11 +89,12 @@ namespace Clipper2Lib inline PathsD MinkowskiSum(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2) { + int error_code = 0; double scale = pow(10, decimalPlaces); - Path64 pat64 = ScalePath(pattern, scale); - Path64 path64 = ScalePath(path, scale); + Path64 pat64 = ScalePath(pattern, scale, error_code); + Path64 path64 = ScalePath(path, scale, error_code); Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, true, isClosed), FillRule::NonZero); - return ScalePaths(tmp, 1 / scale); + return ScalePaths(tmp, 1 / scale, error_code); } inline Paths64 MinkowskiDiff(const Path64& pattern, const Path64& path, bool isClosed) @@ -106,11 +104,12 @@ namespace Clipper2Lib inline PathsD MinkowskiDiff(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2) { + int error_code = 0; double scale = pow(10, decimalPlaces); - Path64 pat64 = ScalePath(pattern, scale); - Path64 path64 = ScalePath(path, scale); + Path64 pat64 = ScalePath(pattern, scale, error_code); + Path64 path64 = ScalePath(path, scale, error_code); Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, false, isClosed), FillRule::NonZero); - return ScalePaths(tmp, 1 / scale); + return ScalePaths(tmp, 1 / scale, error_code); } } // Clipper2Lib namespace diff --git a/src/clipper2/Clipper2Lib/include/clipper2/clipper.offset.h b/src/clipper2/Clipper2Lib/include/clipper2/clipper.offset.h index 4fd130b..90ccd5e 100644 --- a/src/clipper2/Clipper2Lib/include/clipper2/clipper.offset.h +++ b/src/clipper2/Clipper2Lib/include/clipper2/clipper.offset.h @@ -1,20 +1,24 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 15 October 2022 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2022 * +* Date : 22 January 2025 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2025 * * Purpose : Path Offset (Inflate/Shrink) * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ #ifndef CLIPPER_OFFSET_H_ #define CLIPPER_OFFSET_H_ #include "clipper.core.h" +#include "clipper.engine.h" +#include namespace Clipper2Lib { -enum class JoinType { Square, Round, Miter }; +enum class JoinType { Square, Bevel, Round, Miter }; +//Square : Joins are 'squared' at exactly the offset distance (more complex code) +//Bevel : Similar to Square, but the offset distance varies with angle (simple code & faster) enum class EndType {Polygon, Joined, Butt, Square, Round}; //Butt : offsets both sides of a path, with square blunt ends @@ -23,49 +27,64 @@ enum class EndType {Polygon, Joined, Butt, Square, Round}; //Joined : offsets both sides of a path, with joined ends //Polygon: offsets only one side of a closed path +typedef std::function DeltaCallback64; + class ClipperOffset { private: class Group { public: - Paths64 paths_in_; - Paths64 paths_out_; - Path64 path_; - bool is_reversed_ = false; - JoinType join_type_; - EndType end_type_; - Group(const Paths64& paths, JoinType join_type, EndType end_type) : - paths_in_(paths), join_type_(join_type), end_type_(end_type) {} + Paths64 paths_in; + std::optional lowest_path_idx{}; + bool is_reversed = false; + JoinType join_type; + EndType end_type; + Group(const Paths64& _paths, JoinType _join_type, EndType _end_type); }; + int error_code_ = 0; + double delta_ = 0.0; double group_delta_ = 0.0; - double abs_group_delta_ = 0.0; double temp_lim_ = 0.0; double steps_per_rad_ = 0.0; + double step_sin_ = 0.0; + double step_cos_ = 0.0; PathD norms; - Paths64 solution; + Path64 path_out; + Paths64* solution = nullptr; + PolyTree64* solution_tree = nullptr; std::vector groups_; - JoinType join_type_ = JoinType::Square; - + JoinType join_type_ = JoinType::Bevel; + EndType end_type_ = EndType::Polygon; + double miter_limit_ = 0.0; double arc_tolerance_ = 0.0; - bool merge_groups_ = true; bool preserve_collinear_ = false; bool reverse_solution_ = false; - void DoSquare(Group& group, const Path64& path, size_t j, size_t k); - void DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a); - void DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle); +#ifdef USINGZ + ZCallback64 zCallback64_ = nullptr; + void ZCB(const Point64& bot1, const Point64& top1, + const Point64& bot2, const Point64& top2, Point64& ip); +#endif + DeltaCallback64 deltaCallback64_ = nullptr; + size_t CalcSolutionCapacity(); + bool CheckReverseOrientation(); + void DoBevel(const Path64& path, size_t j, size_t k); + void DoSquare(const Path64& path, size_t j, size_t k); + void DoMiter(const Path64& path, size_t j, size_t k, double cos_a); + void DoRound(const Path64& path, size_t j, size_t k, double angle); void BuildNormals(const Path64& path); - void OffsetPolygon(Group& group, Path64& path); - void OffsetOpenJoined(Group& group, Path64& path); - void OffsetOpenPath(Group& group, Path64& path, EndType endType); - void OffsetPoint(Group& group, Path64& path, size_t j, size_t& k); - void DoGroupOffset(Group &group, double delta); + void OffsetPolygon(Group& group, const Path64& path); + void OffsetOpenJoined(Group& group, const Path64& path); + void OffsetOpenPath(Group& group, const Path64& path); + void OffsetPoint(Group& group, const Path64& path, size_t j, size_t k); + void DoGroupOffset(Group &group); + void ExecuteInternal(double delta); public: - ClipperOffset(double miter_limit = 2.0, + explicit ClipperOffset(double miter_limit = 2.0, double arc_tolerance = 0.0, - bool preserve_collinear = false, + bool preserve_collinear = false, bool reverse_solution = false) : miter_limit_(miter_limit), arc_tolerance_(arc_tolerance), preserve_collinear_(preserve_collinear), @@ -73,13 +92,14 @@ public: ~ClipperOffset() { Clear(); }; + int ErrorCode() const { return error_code_; }; void AddPath(const Path64& path, JoinType jt_, EndType et_); void AddPaths(const Paths64& paths, JoinType jt_, EndType et_); - void AddPath(const PathD &p, JoinType jt_, EndType et_); - void AddPaths(const PathsD &p, JoinType jt_, EndType et_); void Clear() { groups_.clear(); norms.clear(); }; - Paths64 Execute(double delta); + void Execute(double delta, Paths64& sols_64); + void Execute(double delta, PolyTree64& polytree); + void Execute(DeltaCallback64 delta_cb, Paths64& paths); double MiterLimit() const { return miter_limit_; } void MiterLimit(double miter_limit) { miter_limit_ = miter_limit; } @@ -88,19 +108,17 @@ public: double ArcTolerance() const { return arc_tolerance_; } void ArcTolerance(double arc_tolerance) { arc_tolerance_ = arc_tolerance; } - //MergeGroups: A path group is one or more paths added via the AddPath or - //AddPaths methods. By default these path groups will be offset - //independently of other groups and this may cause overlaps (intersections). - //However, when MergeGroups is enabled, any overlapping offsets will be - //merged (via a clipping union operation) to remove overlaps. - bool MergeGroups() const { return merge_groups_; } - void MergeGroups(bool merge_groups) { merge_groups_ = merge_groups; } - bool PreserveCollinear() const { return preserve_collinear_; } void PreserveCollinear(bool preserve_collinear){preserve_collinear_ = preserve_collinear;} bool ReverseSolution() const { return reverse_solution_; } void ReverseSolution(bool reverse_solution) {reverse_solution_ = reverse_solution;} + +#ifdef USINGZ + void SetZCallback(ZCallback64 cb) { zCallback64_ = cb; } +#endif + void SetDeltaCallback(DeltaCallback64 cb) { deltaCallback64_ = cb; } + }; } diff --git a/src/clipper2/Clipper2Lib/include/clipper2/clipper.rectclip.h b/src/clipper2/Clipper2Lib/include/clipper2/clipper.rectclip.h index 98f43e0..e7cf2f4 100644 --- a/src/clipper2/Clipper2Lib/include/clipper2/clipper.rectclip.h +++ b/src/clipper2/Clipper2Lib/include/clipper2/clipper.rectclip.h @@ -1,49 +1,79 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 October 2022 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2022 * +* Date : 5 July 2024 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2024 * * Purpose : FAST rectangular clipping * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ #ifndef CLIPPER_RECTCLIP_H #define CLIPPER_RECTCLIP_H -#include -#include -#include "clipper.h" -#include "clipper.core.h" +#include "clipper2/clipper.core.h" +#include -namespace Clipper2Lib +namespace Clipper2Lib { + // Location: the order is important here, see StartLocsIsClockwise() enum class Location { Left, Top, Right, Bottom, Inside }; - class RectClip { + class OutPt2; + typedef std::vector OutPt2List; + + class OutPt2 { + public: + Point64 pt; + size_t owner_idx = 0; + OutPt2List* edge = nullptr; + OutPt2* next = nullptr; + OutPt2* prev = nullptr; + }; + + //------------------------------------------------------------------------------ + // RectClip64 + //------------------------------------------------------------------------------ + + class RectClip64 { + private: + void ExecuteInternal(const Path64& path); + Path64 GetPath(OutPt2*& op); protected: const Rect64 rect_; - const Point64 mp_; - const Path64 rectPath_; - Path64 result_; + const Path64 rect_as_path_; + const Point64 rect_mp_; + Rect64 path_bounds_; + std::deque op_container_; + OutPt2List results_; // each path can be broken into multiples + OutPt2List edges_[8]; // clockwise and counter-clockwise std::vector start_locs_; - + void CheckEdges(); + void TidyEdges(size_t idx, OutPt2List& cw, OutPt2List& ccw); void GetNextLocation(const Path64& path, - Location& loc, int& i, int highI); + Location& loc, size_t& i, size_t highI); + OutPt2* Add(Point64 pt, bool start_new = false); void AddCorner(Location prev, Location curr); void AddCorner(Location& loc, bool isClockwise); public: - RectClip(const Rect64& rect) : + explicit RectClip64(const Rect64& rect) : rect_(rect), - mp_(rect.MidPoint()), - rectPath_(rect.AsPath()) {} - Path64 Execute(const Path64& path); + rect_as_path_(rect.AsPath()), + rect_mp_(rect.MidPoint()) {} + Paths64 Execute(const Paths64& paths); }; - class RectClipLines : public RectClip { + //------------------------------------------------------------------------------ + // RectClipLines64 + //------------------------------------------------------------------------------ + + class RectClipLines64 : public RectClip64 { + private: + void ExecuteInternal(const Path64& path); + Path64 GetPath(OutPt2*& op); public: - RectClipLines(const Rect64& rect) : RectClip(rect) {}; - Paths64 Execute(const Path64& path); + explicit RectClipLines64(const Rect64& rect) : RectClip64(rect) {}; + Paths64 Execute(const Paths64& paths); }; } // Clipper2Lib namespace diff --git a/src/clipper2/Clipper2Lib/include/clipper2/clipper.version.h b/src/clipper2/Clipper2Lib/include/clipper2/clipper.version.h new file mode 100644 index 0000000..661b0f1 --- /dev/null +++ b/src/clipper2/Clipper2Lib/include/clipper2/clipper.version.h @@ -0,0 +1,6 @@ +#ifndef CLIPPER_VERSION_H +#define CLIPPER_VERSION_H + +constexpr auto CLIPPER2_VERSION = "1.5.2"; + +#endif // CLIPPER_VERSION_H diff --git a/src/clipper2/Clipper2Lib/src/clipper.engine.cpp b/src/clipper2/Clipper2Lib/src/clipper.engine.cpp index 069b523..927e7a7 100644 --- a/src/clipper2/Clipper2Lib/src/clipper.engine.cpp +++ b/src/clipper2/Clipper2Lib/src/clipper.engine.cpp @@ -1,3594 +1,3147 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 4 November 2022 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2022 * +* Date : 17 September 2024 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2024 * * Purpose : This is the main polygon clipping module * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ -#include -#include -#include -#include -#include -#include #include "clipper2/clipper.engine.h" +#include "clipper2/clipper.h" +#include -#include -#include +// https://github.com/AngusJohnson/Clipper2/discussions/334 +// #discussioncomment-4248602 +#if defined(_MSC_VER) && ( defined(_M_AMD64) || defined(_M_X64) ) +#include +#include +#define fmin(a,b) _mm_cvtsd_f64(_mm_min_sd(_mm_set_sd(a),_mm_set_sd(b))) +#define fmax(a,b) _mm_cvtsd_f64(_mm_max_sd(_mm_set_sd(a),_mm_set_sd(b))) +#define nearbyint(a) _mm_cvtsd_si64(_mm_set_sd(a)) /* Note: expression type is (int64_t) */ +#endif namespace Clipper2Lib { - static const double FloatingPointTolerance = 1.0e-12; - static const Rect64 invalid_rect = Rect64( - std::numeric_limits::max(), - std::numeric_limits::max(), - -std::numeric_limits::max(), - -std::numeric_limits::max() - ); - - //Every closed path (or polygon) is made up of a series of vertices forming - //edges that alternate between going up (relative to the Y-axis) and going - //down. Edges consecutively going up or consecutively going down are called - //'bounds' (or sides if they're simple polygons). 'Local Minima' refer to - //vertices where descending bounds become ascending ones. - - struct Scanline { - int64_t y = 0; - Scanline* next = nullptr; - - explicit Scanline(int64_t y_) : y(y_) {} - }; - - struct Joiner { - int idx; - OutPt* op1; - OutPt* op2; - Joiner* next1; - Joiner* next2; - Joiner* nextH; - - explicit Joiner(OutPt* op1_, OutPt* op2_, Joiner* nexth) : - op1(op1_), op2(op2_), nextH(nexth) - { - idx = -1; - next1 = op1->joiner; - op1->joiner = this; - - if (op2) - { - next2 = op2->joiner; - op2->joiner = this; - } - else - next2 = nullptr; - } - - }; - - struct LocMinSorter { - inline bool operator()(const LocalMinima* locMin1, const LocalMinima* locMin2) - { - if (locMin2->vertex->pt.y != locMin1->vertex->pt.y) - return locMin2->vertex->pt.y < locMin1->vertex->pt.y; - else - return locMin2->vertex->pt.x < locMin1->vertex->pt.x; - } - }; - - inline bool IsOdd(int val) - { - return (val & 1) ? true : false; - } - - - inline bool IsHotEdge(const Active& e) - { - return (e.outrec); - } - - - inline bool IsOpen(const Active& e) - { - return (e.local_min->is_open); - } - - - inline bool IsOpenEnd(const Vertex& v) - { - return (v.flags & (VertexFlags::OpenStart | VertexFlags::OpenEnd)) != - VertexFlags::None; - } - - - inline bool IsOpenEnd(const Active& ae) - { - return IsOpenEnd(*ae.vertex_top); - } - - - inline Active* GetPrevHotEdge(const Active& e) - { - Active* prev = e.prev_in_ael; - while (prev && (IsOpen(*prev) || !IsHotEdge(*prev))) - prev = prev->prev_in_ael; - return prev; - } - - inline bool IsFront(const Active& e) - { - return (&e == e.outrec->front_edge); - } - - inline bool IsInvalidPath(OutPt* op) - { - return (!op || op->next == op); - } - - /******************************************************************************* - * Dx: 0(90deg) * - * | * - * +inf (180deg) <--- o ---> -inf (0deg) * - *******************************************************************************/ - - inline double GetDx(const Point64& pt1, const Point64& pt2) - { - double dy = double(pt2.y - pt1.y); - if (dy != 0) - return double(pt2.x - pt1.x) / dy; - else if (pt2.x > pt1.x) - return -std::numeric_limits::max(); - else - return std::numeric_limits::max(); - } - - - inline int64_t TopX(const Active& ae, const int64_t currentY) - { - if ((currentY == ae.top.y) || (ae.top.x == ae.bot.x)) return ae.top.x; - else if (currentY == ae.bot.y) return ae.bot.x; - else return ae.bot.x + static_cast(std::nearbyint(ae.dx * (currentY - ae.bot.y))); - // nb: std::nearbyint (or std::round) substantially *improves* performance here - // as it greatly improves the likelihood of edge adjacency in ProcessIntersectList(). - } - - - inline bool IsHorizontal(const Active& e) - { - return (e.top.y == e.bot.y); - } - - - inline bool IsHeadingRightHorz(const Active& e) - { - return e.dx == -std::numeric_limits::max(); - } - - - inline bool IsHeadingLeftHorz(const Active& e) - { - return e.dx == std::numeric_limits::max(); - } - - - inline void SwapActives(Active*& e1, Active*& e2) - { - Active* e = e1; - e1 = e2; - e2 = e; - } - - - inline PathType GetPolyType(const Active& e) - { - return e.local_min->polytype; - } - - - inline bool IsSamePolyType(const Active& e1, const Active& e2) - { - return e1.local_min->polytype == e2.local_min->polytype; - } - - inline Point64 GetEndE1ClosestToEndE2( - const Active& e1, const Active& e2) - { - double d[] = { - DistanceSqr(e1.bot, e2.bot), - DistanceSqr(e1.top, e2.top), - DistanceSqr(e1.top, e2.bot), - DistanceSqr(e1.bot, e2.top) - }; - if (d[0] == 0) return e1.bot; - int idx = 0; - for (int i = 1; i < 4; ++i) - { - if (d[i] < d[idx]) idx = i; - if (d[i] == 0) break; - } - switch (idx) - { - case 1: case 2: return e1.top; - default: return e1.bot; - } - } - - Point64 GetIntersectPoint(const Active& e1, const Active& e2) - { - double b1, b2, q = (e1.dx - e2.dx); - if (std::abs(q) < 1e-5) // 1e-5 is a rough empirical limit - return GetEndE1ClosestToEndE2(e1, e2); // ie almost parallel - - if (e1.dx == 0) - { - if (IsHorizontal(e2)) return Point64(e1.bot.x, e2.bot.y); - b2 = e2.bot.y - (e2.bot.x / e2.dx); - return Point64(e1.bot.x, - static_cast(std::round(e1.bot.x / e2.dx + b2))); - } - else if (e2.dx == 0) - { - if (IsHorizontal(e1)) return Point64(e2.bot.x, e1.bot.y); - b1 = e1.bot.y - (e1.bot.x / e1.dx); - return Point64(e2.bot.x, - static_cast(std::round(e2.bot.x / e1.dx + b1))); - } - else - { - b1 = e1.bot.x - e1.bot.y * e1.dx; - b2 = e2.bot.x - e2.bot.y * e2.dx; - - q = (b2 - b1) / q; - return (abs(e1.dx) < abs(e2.dx)) ? - Point64(static_cast(e1.dx * q + b1), - static_cast((q))) : - Point64(static_cast(e2.dx * q + b2), - static_cast((q))); - } - } - - bool GetIntersectPoint(const Point64& ln1a, const Point64& ln1b, - const Point64& ln2a, const Point64& ln2b, PointD& ip) - { - ip = PointD(0, 0); - double m1, b1, m2, b2; - if (ln1b.x == ln1a.x) - { - if (ln2b.x == ln2a.x) return false; - m2 = static_cast(ln2b.y - ln2a.y) / - static_cast(ln2b.x - ln2a.x); - b2 = ln2a.y - m2 * ln2a.x; - ip.x = static_cast(ln1a.x); - ip.y = m2 * ln1a.x + b2; - } - else if (ln2b.x == ln2a.x) - { - m1 = static_cast(ln1b.y - ln1a.y) / - static_cast(ln1b.x - ln1a.x); - b1 = ln1a.y - m1 * ln1a.x; - ip.x = static_cast(ln2a.x); - ip.y = m1 * ln2a.x + b1; - } - else - { - m1 = static_cast(ln1b.y - ln1a.y) / - static_cast(ln1b.x - ln1a.x); - b1 = ln1a.y - m1 * ln1a.x; - m2 = static_cast(ln2b.y - ln2a.y) / - static_cast(ln2b.x - ln2a.x); - b2 = ln2a.y - m2 * ln2a.x; - if (std::abs(m1 - m2) > FloatingPointTolerance) - { - ip.x = (b2 - b1) / (m1 - m2); - ip.y = m1 * ip.x + b1; - } - else - { - ip.x = static_cast(ln1a.x + ln1b.x) / 2; - ip.y = static_cast(ln1a.y + ln1b.y) / 2; - } - } - return true; - } - - - inline void SetDx(Active& e) - { - e.dx = GetDx(e.bot, e.top); - } - - - inline Vertex* NextVertex(const Active& e) - { - if (e.wind_dx > 0) - return e.vertex_top->next; - else - return e.vertex_top->prev; - } - - - //PrevPrevVertex: useful to get the (inverted Y-axis) top of the - //alternate edge (ie left or right bound) during edge insertion. - inline Vertex* PrevPrevVertex(const Active& ae) - { - if (ae.wind_dx > 0) - return ae.vertex_top->prev->prev; - else - return ae.vertex_top->next->next; - } - - - inline Active* ExtractFromSEL(Active* ae) - { - Active* res = ae->next_in_sel; - if (res) - res->prev_in_sel = ae->prev_in_sel; - ae->prev_in_sel->next_in_sel = res; - return res; - } - - - inline void Insert1Before2InSEL(Active* ae1, Active* ae2) - { - ae1->prev_in_sel = ae2->prev_in_sel; - if (ae1->prev_in_sel) - ae1->prev_in_sel->next_in_sel = ae1; - ae1->next_in_sel = ae2; - ae2->prev_in_sel = ae1; - } - - inline bool IsMaxima(const Vertex& v) - { - return ((v.flags & VertexFlags::LocalMax) != VertexFlags::None); - } - - - inline bool IsMaxima(const Active& e) - { - return IsMaxima(*e.vertex_top); - } - - Vertex* GetCurrYMaximaVertex(const Active& e) - { - Vertex* result = e.vertex_top; - if (e.wind_dx > 0) - while (result->next->pt.y == result->pt.y) result = result->next; - else - while (result->prev->pt.y == result->pt.y) result = result->prev; - if (!IsMaxima(*result)) result = nullptr; // not a maxima - return result; - } - - Active* GetMaximaPair(const Active& e) - { - Active* e2; - e2 = e.next_in_ael; - while (e2) - { - if (e2->vertex_top == e.vertex_top) return e2; // Found! - e2 = e2->next_in_ael; - } - return nullptr; - } - - Active* GetHorzMaximaPair(const Active& horz, const Vertex* vert_max) - { - //we can't be sure whether the MaximaPair is on the left or right, so ... - Active* result = horz.prev_in_ael; - while (result && result->curr_x >= vert_max->pt.x) - { - if (result->vertex_top == vert_max) return result; // Found! - result = result->prev_in_ael; - } - result = horz.next_in_ael; - while (result && TopX(*result, horz.top.y) <= vert_max->pt.x) - { - if (result->vertex_top == vert_max) return result; // Found! - result = result->next_in_ael; - } - return nullptr; - } - - inline int PointCount(OutPt* op) - { - OutPt* op2 = op; - int cnt = 0; - do - { - op2 = op2->next; - ++cnt; - } while (op2 != op); - return cnt; - } - - - inline OutPt* InsertOp(const Point64& pt, OutPt* insertAfter) - { - OutPt* result = new OutPt(pt, insertAfter->outrec); - result->next = insertAfter->next; - insertAfter->next->prev = result; - insertAfter->next = result; - result->prev = insertAfter; - return result; - } - - - inline OutPt* DisposeOutPt(OutPt* op) - { - OutPt* result = op->next; - op->prev->next = op->next; - op->next->prev = op->prev; - delete op; - return result; - } - - - inline void DisposeOutPts(OutRec& outrec) - { - if (!outrec.pts) return; - OutPt* op2 = outrec.pts->next; - while (op2 != outrec.pts) - { - OutPt* tmp = op2->next; - delete op2; - op2 = tmp; - } - delete outrec.pts; - outrec.pts = nullptr; - } - - - bool IntersectListSort(const IntersectNode& a, const IntersectNode& b) - { - //note different inequality tests ... - return (a.pt.y == b.pt.y) ? (a.pt.x < b.pt.x) : (a.pt.y > b.pt.y); - } - - - inline void SetSides(OutRec& outrec, Active& start_edge, Active& end_edge) - { - outrec.front_edge = &start_edge; - outrec.back_edge = &end_edge; - } - - - void SwapOutrecs(Active& e1, Active& e2) - { - OutRec* or1 = e1.outrec; - OutRec* or2 = e2.outrec; - if (or1 == or2) - { - Active* e = or1->front_edge; - or1->front_edge = or1->back_edge; - or1->back_edge = e; - return; - } - if (or1) - { - if (&e1 == or1->front_edge) - or1->front_edge = &e2; - else - or1->back_edge = &e2; - } - if (or2) - { - if (&e2 == or2->front_edge) - or2->front_edge = &e1; - else - or2->back_edge = &e1; - } - e1.outrec = or2; - e2.outrec = or1; - } - - - double Area(OutPt* op) - { - //https://en.wikipedia.org/wiki/Shoelace_formula - double result = 0.0; - OutPt* op2 = op; - do - { - result += static_cast(op2->prev->pt.y + op2->pt.y) * - static_cast(op2->prev->pt.x - op2->pt.x); - op2 = op2->next; - } while (op2 != op); - return result * 0.5; - } - - inline double AreaTriangle(const Point64& pt1, - const Point64& pt2, const Point64& pt3) - { - return (static_cast(pt3.y + pt1.y) * static_cast(pt3.x - pt1.x) + - static_cast(pt1.y + pt2.y) * static_cast(pt1.x - pt2.x) + - static_cast(pt2.y + pt3.y) * static_cast(pt2.x - pt3.x)); - } - - void ReverseOutPts(OutPt* op) - { - if (!op) return; - - OutPt* op1 = op; - OutPt* op2; - - do - { - op2 = op1->next; - op1->next = op1->prev; - op1->prev = op2; - op1 = op2; - } while (op1 != op); - } - - - inline void SwapSides(OutRec& outrec) - { - Active* e2 = outrec.front_edge; - outrec.front_edge = outrec.back_edge; - outrec.back_edge = e2; - outrec.pts = outrec.pts->next; - } - - - inline OutRec* GetRealOutRec(OutRec* outrec) - { - while (outrec && !outrec->pts) outrec = outrec->owner; - return outrec; - } - - - inline void UncoupleOutRec(Active ae) - { - OutRec* outrec = ae.outrec; - if (!outrec) return; - outrec->front_edge->outrec = nullptr; - outrec->back_edge->outrec = nullptr; - outrec->front_edge = nullptr; - outrec->back_edge = nullptr; - } - - - inline bool PtsReallyClose(const Point64& pt1, const Point64& pt2) - { - return (std::llabs(pt1.x - pt2.x) < 2) && (std::llabs(pt1.y - pt2.y) < 2); - } - - inline bool IsVerySmallTriangle(const OutPt& op) - { - return op.next->next == op.prev && - (PtsReallyClose(op.prev->pt, op.next->pt) || - PtsReallyClose(op.pt, op.next->pt) || - PtsReallyClose(op.pt, op.prev->pt)); - } - - inline bool IsValidClosedPath(const OutPt* op) - { - return op && (op->next != op) && (op->next != op->prev) && - !IsVerySmallTriangle(*op); - } - - inline bool OutrecIsAscending(const Active* hotEdge) - { - return (hotEdge == hotEdge->outrec->front_edge); - } - - inline void SwapFrontBackSides(OutRec& outrec) - { - Active* tmp = outrec.front_edge; - outrec.front_edge = outrec.back_edge; - outrec.back_edge = tmp; - outrec.pts = outrec.pts->next; - } - - inline bool EdgesAdjacentInAEL(const IntersectNode& inode) - { - return (inode.edge1->next_in_ael == inode.edge2) || (inode.edge1->prev_in_ael == inode.edge2); - } - - inline bool TestJoinWithPrev1(const Active& e) - { - //this is marginally quicker than TestJoinWithPrev2 - //but can only be used when e.PrevInAEL.currX is accurate - return IsHotEdge(e) && !IsOpen(e) && - e.prev_in_ael && e.prev_in_ael->curr_x == e.curr_x && - IsHotEdge(*e.prev_in_ael) && !IsOpen(*e.prev_in_ael) && - (CrossProduct(e.prev_in_ael->top, e.bot, e.top) == 0); - } - - inline bool TestJoinWithPrev2(const Active& e, const Point64& curr_pt) - { - return IsHotEdge(e) && !IsOpen(e) && - e.prev_in_ael && !IsOpen(*e.prev_in_ael) && - IsHotEdge(*e.prev_in_ael) && (e.prev_in_ael->top.y < e.bot.y) && - (std::llabs(TopX(*e.prev_in_ael, curr_pt.y) - curr_pt.x) < 2) && - (CrossProduct(e.prev_in_ael->top, curr_pt, e.top) == 0); - } - - inline bool TestJoinWithNext1(const Active& e) - { - //this is marginally quicker than TestJoinWithNext2 - //but can only be used when e.NextInAEL.currX is accurate - return IsHotEdge(e) && !IsOpen(e) && - e.next_in_ael && (e.next_in_ael->curr_x == e.curr_x) && - IsHotEdge(*e.next_in_ael) && !IsOpen(*e.next_in_ael) && - (CrossProduct(e.next_in_ael->top, e.bot, e.top) == 0); - } - - inline bool TestJoinWithNext2(const Active& e, const Point64& curr_pt) - { - return IsHotEdge(e) && !IsOpen(e) && - e.next_in_ael && !IsOpen(*e.next_in_ael) && - IsHotEdge(*e.next_in_ael) && (e.next_in_ael->top.y < e.bot.y) && - (std::llabs(TopX(*e.next_in_ael, curr_pt.y) - curr_pt.x) < 2) && - (CrossProduct(e.next_in_ael->top, curr_pt, e.top) == 0); - } - - //------------------------------------------------------------------------------ - // ClipperBase methods ... - //------------------------------------------------------------------------------ - - ClipperBase::~ClipperBase() - { - Clear(); - } - - void ClipperBase::DeleteEdges(Active*& e) - { - while (e) - { - Active* e2 = e; - e = e->next_in_ael; - delete e2; - } - } - - void ClipperBase::CleanUp() - { - DeleteEdges(actives_); - scanline_list_ = std::priority_queue(); - intersect_nodes_.clear(); - DisposeAllOutRecs(); - } - - - void ClipperBase::Clear() - { - CleanUp(); - DisposeVerticesAndLocalMinima(); - current_locmin_iter_ = minima_list_.begin(); - minima_list_sorted_ = false; - has_open_paths_ = false; - } - - - void ClipperBase::Reset() - { - if (!minima_list_sorted_) - { - std::sort(minima_list_.begin(), minima_list_.end(), LocMinSorter()); - minima_list_sorted_ = true; - } - std::vector::const_reverse_iterator i; - for (i = minima_list_.rbegin(); i != minima_list_.rend(); ++i) - InsertScanline((*i)->vertex->pt.y); - - current_locmin_iter_ = minima_list_.begin(); - actives_ = nullptr; - sel_ = nullptr; - succeeded_ = true; - } + static const Rect64 invalid_rect = Rect64(false); + + // Every closed path (ie polygon) is made up of a series of vertices forming edge + // 'bounds' that alternate between ascending bounds (containing edges going up + // relative to the Y-axis) and descending bounds. 'Local Minima' refers to + // vertices where ascending and descending bounds join at the bottom, and + // 'Local Maxima' are where ascending and descending bounds join at the top. + + struct Scanline { + int64_t y = 0; + Scanline* next = nullptr; + + explicit Scanline(int64_t y_) : y(y_) {} + }; + + struct HorzSegSorter { + inline bool operator()(const HorzSegment& hs1, const HorzSegment& hs2) + { + if (!hs1.right_op || !hs2.right_op) return (hs1.right_op); + return hs2.left_op->pt.x > hs1.left_op->pt.x; + } + }; + + struct LocMinSorter { + inline bool operator()(const LocalMinima_ptr& locMin1, + const LocalMinima_ptr& locMin2) + { + if (locMin2->vertex->pt.y != locMin1->vertex->pt.y) + return locMin2->vertex->pt.y < locMin1->vertex->pt.y; + else + return locMin2->vertex->pt.x > locMin1->vertex->pt.x; + } + }; + + + inline bool IsOdd(int val) + { + return (val & 1) ? true : false; + } + + + inline bool IsHotEdge(const Active& e) + { + return (e.outrec); + } + + + inline bool IsOpen(const Active& e) + { + return (e.local_min->is_open); + } + + + inline bool IsOpenEnd(const Vertex& v) + { + return (v.flags & (VertexFlags::OpenStart | VertexFlags::OpenEnd)) != + VertexFlags::Empty; + } + + + inline bool IsOpenEnd(const Active& ae) + { + return IsOpenEnd(*ae.vertex_top); + } + + + inline Active* GetPrevHotEdge(const Active& e) + { + Active* prev = e.prev_in_ael; + while (prev && (IsOpen(*prev) || !IsHotEdge(*prev))) + prev = prev->prev_in_ael; + return prev; + } + + inline bool IsFront(const Active& e) + { + return (&e == e.outrec->front_edge); + } + + inline bool IsInvalidPath(OutPt* op) + { + return (!op || op->next == op); + } + + /******************************************************************************* + * Dx: 0(90deg) * + * | * + * +inf (180deg) <--- o ---> -inf (0deg) * + *******************************************************************************/ + + inline double GetDx(const Point64& pt1, const Point64& pt2) + { + double dy = double(pt2.y - pt1.y); + if (dy != 0) + return double(pt2.x - pt1.x) / dy; + else if (pt2.x > pt1.x) + return -std::numeric_limits::max(); + else + return std::numeric_limits::max(); + } + + inline int64_t TopX(const Active& ae, const int64_t currentY) + { + if ((currentY == ae.top.y) || (ae.top.x == ae.bot.x)) return ae.top.x; + else if (currentY == ae.bot.y) return ae.bot.x; + else return ae.bot.x + static_cast(nearbyint(ae.dx * (currentY - ae.bot.y))); + // nb: std::nearbyint (or std::round) substantially *improves* performance here + // as it greatly improves the likelihood of edge adjacency in ProcessIntersectList(). + } + + + inline bool IsHorizontal(const Active& e) + { + return (e.top.y == e.bot.y); + } + + + inline bool IsHeadingRightHorz(const Active& e) + { + return e.dx == -std::numeric_limits::max(); + } + + + inline bool IsHeadingLeftHorz(const Active& e) + { + return e.dx == std::numeric_limits::max(); + } + + + inline void SwapActives(Active*& e1, Active*& e2) + { + Active* e = e1; + e1 = e2; + e2 = e; + } + + inline PathType GetPolyType(const Active& e) + { + return e.local_min->polytype; + } + + inline bool IsSamePolyType(const Active& e1, const Active& e2) + { + return e1.local_min->polytype == e2.local_min->polytype; + } + + inline void SetDx(Active& e) + { + e.dx = GetDx(e.bot, e.top); + } + + inline Vertex* NextVertex(const Active& e) + { + if (e.wind_dx > 0) + return e.vertex_top->next; + else + return e.vertex_top->prev; + } + + //PrevPrevVertex: useful to get the (inverted Y-axis) top of the + //alternate edge (ie left or right bound) during edge insertion. + inline Vertex* PrevPrevVertex(const Active& ae) + { + if (ae.wind_dx > 0) + return ae.vertex_top->prev->prev; + else + return ae.vertex_top->next->next; + } + + + inline Active* ExtractFromSEL(Active* ae) + { + Active* res = ae->next_in_sel; + if (res) + res->prev_in_sel = ae->prev_in_sel; + ae->prev_in_sel->next_in_sel = res; + return res; + } + + + inline void Insert1Before2InSEL(Active* ae1, Active* ae2) + { + ae1->prev_in_sel = ae2->prev_in_sel; + if (ae1->prev_in_sel) + ae1->prev_in_sel->next_in_sel = ae1; + ae1->next_in_sel = ae2; + ae2->prev_in_sel = ae1; + } + + inline bool IsMaxima(const Vertex& v) + { + return ((v.flags & VertexFlags::LocalMax) != VertexFlags::Empty); + } + + + inline bool IsMaxima(const Active& e) + { + return IsMaxima(*e.vertex_top); + } + + inline Vertex* GetCurrYMaximaVertex_Open(const Active& e) + { + Vertex* result = e.vertex_top; + if (e.wind_dx > 0) + while ((result->next->pt.y == result->pt.y) && + ((result->flags & (VertexFlags::OpenEnd | + VertexFlags::LocalMax)) == VertexFlags::Empty)) + result = result->next; + else + while (result->prev->pt.y == result->pt.y && + ((result->flags & (VertexFlags::OpenEnd | + VertexFlags::LocalMax)) == VertexFlags::Empty)) + result = result->prev; + if (!IsMaxima(*result)) result = nullptr; // not a maxima + return result; + } + + inline Vertex* GetCurrYMaximaVertex(const Active& e) + { + Vertex* result = e.vertex_top; + if (e.wind_dx > 0) + while (result->next->pt.y == result->pt.y) result = result->next; + else + while (result->prev->pt.y == result->pt.y) result = result->prev; + if (!IsMaxima(*result)) result = nullptr; // not a maxima + return result; + } + + Active* GetMaximaPair(const Active& e) + { + Active* e2; + e2 = e.next_in_ael; + while (e2) + { + if (e2->vertex_top == e.vertex_top) return e2; // Found! + e2 = e2->next_in_ael; + } + return nullptr; + } + + inline int PointCount(OutPt* op) + { + OutPt* op2 = op; + int cnt = 0; + do + { + op2 = op2->next; + ++cnt; + } while (op2 != op); + return cnt; + } + + inline OutPt* DuplicateOp(OutPt* op, bool insert_after) + { + OutPt* result = new OutPt(op->pt, op->outrec); + if (insert_after) + { + result->next = op->next; + result->next->prev = result; + result->prev = op; + op->next = result; + } + else + { + result->prev = op->prev; + result->prev->next = result; + result->next = op; + op->prev = result; + } + return result; + } + + inline OutPt* DisposeOutPt(OutPt* op) + { + OutPt* result = op->next; + op->prev->next = op->next; + op->next->prev = op->prev; + delete op; + return result; + } + + + inline void DisposeOutPts(OutRec* outrec) + { + OutPt* op = outrec->pts; + op->prev->next = nullptr; + while (op) + { + OutPt* tmp = op; + op = op->next; + delete tmp; + }; + outrec->pts = nullptr; + } + + + bool IntersectListSort(const IntersectNode& a, const IntersectNode& b) + { + //note different inequality tests ... + return (a.pt.y == b.pt.y) ? (a.pt.x < b.pt.x) : (a.pt.y > b.pt.y); + } + + + inline void SetSides(OutRec& outrec, Active& start_edge, Active& end_edge) + { + outrec.front_edge = &start_edge; + outrec.back_edge = &end_edge; + } + + + void SwapOutrecs(Active& e1, Active& e2) + { + OutRec* or1 = e1.outrec; + OutRec* or2 = e2.outrec; + if (or1 == or2) + { + Active* e = or1->front_edge; + or1->front_edge = or1->back_edge; + or1->back_edge = e; + return; + } + if (or1) + { + if (&e1 == or1->front_edge) + or1->front_edge = &e2; + else + or1->back_edge = &e2; + } + if (or2) + { + if (&e2 == or2->front_edge) + or2->front_edge = &e1; + else + or2->back_edge = &e1; + } + e1.outrec = or2; + e2.outrec = or1; + } + + + double Area(OutPt* op) + { + //https://en.wikipedia.org/wiki/Shoelace_formula + double result = 0.0; + OutPt* op2 = op; + do + { + result += static_cast(op2->prev->pt.y + op2->pt.y) * + static_cast(op2->prev->pt.x - op2->pt.x); + op2 = op2->next; + } while (op2 != op); + return result * 0.5; + } + + inline double AreaTriangle(const Point64& pt1, + const Point64& pt2, const Point64& pt3) + { + return (static_cast(pt3.y + pt1.y) * static_cast(pt3.x - pt1.x) + + static_cast(pt1.y + pt2.y) * static_cast(pt1.x - pt2.x) + + static_cast(pt2.y + pt3.y) * static_cast(pt2.x - pt3.x)); + } + + void ReverseOutPts(OutPt* op) + { + if (!op) return; + + OutPt* op1 = op; + OutPt* op2; + + do + { + op2 = op1->next; + op1->next = op1->prev; + op1->prev = op2; + op1 = op2; + } while (op1 != op); + } + + inline void SwapSides(OutRec& outrec) + { + Active* e2 = outrec.front_edge; + outrec.front_edge = outrec.back_edge; + outrec.back_edge = e2; + outrec.pts = outrec.pts->next; + } + + inline OutRec* GetRealOutRec(OutRec* outrec) + { + while (outrec && !outrec->pts) outrec = outrec->owner; + return outrec; + } + + inline bool IsValidOwner(OutRec* outrec, OutRec* testOwner) + { + // prevent outrec owning itself either directly or indirectly + while (testOwner && testOwner != outrec) testOwner = testOwner->owner; + return !testOwner; + } + + inline void UncoupleOutRec(Active ae) + { + OutRec* outrec = ae.outrec; + if (!outrec) return; + outrec->front_edge->outrec = nullptr; + outrec->back_edge->outrec = nullptr; + outrec->front_edge = nullptr; + outrec->back_edge = nullptr; + } + + + inline bool PtsReallyClose(const Point64& pt1, const Point64& pt2) + { + return (std::llabs(pt1.x - pt2.x) < 2) && (std::llabs(pt1.y - pt2.y) < 2); + } + + inline bool IsVerySmallTriangle(const OutPt& op) + { + return op.next->next == op.prev && + (PtsReallyClose(op.prev->pt, op.next->pt) || + PtsReallyClose(op.pt, op.next->pt) || + PtsReallyClose(op.pt, op.prev->pt)); + } + + inline bool IsValidClosedPath(const OutPt* op) + { + return op && (op->next != op) && (op->next != op->prev) && + !IsVerySmallTriangle(*op); + } + + inline bool OutrecIsAscending(const Active* hotEdge) + { + return (hotEdge == hotEdge->outrec->front_edge); + } + + inline void SwapFrontBackSides(OutRec& outrec) + { + Active* tmp = outrec.front_edge; + outrec.front_edge = outrec.back_edge; + outrec.back_edge = tmp; + outrec.pts = outrec.pts->next; + } + + inline bool EdgesAdjacentInAEL(const IntersectNode& inode) + { + return (inode.edge1->next_in_ael == inode.edge2) || (inode.edge1->prev_in_ael == inode.edge2); + } + + inline bool IsJoined(const Active& e) + { + return e.join_with != JoinWith::NoJoin; + } + + inline void SetOwner(OutRec* outrec, OutRec* new_owner) + { + //precondition1: new_owner is never null + while (new_owner->owner && !new_owner->owner->pts) + new_owner->owner = new_owner->owner->owner; + OutRec* tmp = new_owner; + while (tmp && tmp != outrec) tmp = tmp->owner; + if (tmp) new_owner->owner = outrec->owner; + outrec->owner = new_owner; + } + + static PointInPolygonResult PointInOpPolygon(const Point64& pt, OutPt* op) + { + if (op == op->next || op->prev == op->next) + return PointInPolygonResult::IsOutside; + + OutPt* op2 = op; + do + { + if (op->pt.y != pt.y) break; + op = op->next; + } while (op != op2); + if (op->pt.y == pt.y) // not a proper polygon + return PointInPolygonResult::IsOutside; + + bool is_above = op->pt.y < pt.y, starting_above = is_above; + int val = 0; + op2 = op->next; + while (op2 != op) + { + if (is_above) + while (op2 != op && op2->pt.y < pt.y) op2 = op2->next; + else + while (op2 != op && op2->pt.y > pt.y) op2 = op2->next; + if (op2 == op) break; + + // must have touched or crossed the pt.Y horizontal + // and this must happen an even number of times + + if (op2->pt.y == pt.y) // touching the horizontal + { + if (op2->pt.x == pt.x || (op2->pt.y == op2->prev->pt.y && + (pt.x < op2->prev->pt.x) != (pt.x < op2->pt.x))) + return PointInPolygonResult::IsOn; + + op2 = op2->next; + if (op2 == op) break; + continue; + } + + if (pt.x < op2->pt.x && pt.x < op2->prev->pt.x); + // do nothing because + // we're only interested in edges crossing on the left + else if ((pt.x > op2->prev->pt.x && pt.x > op2->pt.x)) + val = 1 - val; // toggle val + else + { + double d = CrossProduct(op2->prev->pt, op2->pt, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + is_above = !is_above; + op2 = op2->next; + } + + if (is_above != starting_above) + { + double d = CrossProduct(op2->prev->pt, op2->pt, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + + if (val == 0) return PointInPolygonResult::IsOutside; + else return PointInPolygonResult::IsInside; + } + + inline Path64 GetCleanPath(OutPt* op) + { + Path64 result; + OutPt* op2 = op; + while (op2->next != op && + ((op2->pt.x == op2->next->pt.x && op2->pt.x == op2->prev->pt.x) || + (op2->pt.y == op2->next->pt.y && op2->pt.y == op2->prev->pt.y))) op2 = op2->next; + result.emplace_back(op2->pt); + OutPt* prevOp = op2; + op2 = op2->next; + while (op2 != op) + { + if ((op2->pt.x != op2->next->pt.x || op2->pt.x != prevOp->pt.x) && + (op2->pt.y != op2->next->pt.y || op2->pt.y != prevOp->pt.y)) + { + result.emplace_back(op2->pt); + prevOp = op2; + } + op2 = op2->next; + } + return result; + } + + inline bool Path1InsidePath2(OutPt* op1, OutPt* op2) + { + // we need to make some accommodation for rounding errors + // so we won't jump if the first vertex is found outside + PointInPolygonResult result; + int outside_cnt = 0; + OutPt* op = op1; + do + { + result = PointInOpPolygon(op->pt, op2); + if (result == PointInPolygonResult::IsOutside) ++outside_cnt; + else if (result == PointInPolygonResult::IsInside) --outside_cnt; + op = op->next; + } while (op != op1 && std::abs(outside_cnt) < 2); + if (std::abs(outside_cnt) > 1) return (outside_cnt < 0); + // since path1's location is still equivocal, check its midpoint + Point64 mp = GetBounds(GetCleanPath(op1)).MidPoint(); + Path64 path2 = GetCleanPath(op2); + return PointInPolygon(mp, path2) != PointInPolygonResult::IsOutside; + } + + //------------------------------------------------------------------------------ + //------------------------------------------------------------------------------ + + void AddLocMin(LocalMinimaList& list, + Vertex& vert, PathType polytype, bool is_open) + { + //make sure the vertex is added only once ... + if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::Empty) return; + + vert.flags = (vert.flags | VertexFlags::LocalMin); + list.emplace_back(std::make_unique (&vert, polytype, is_open)); + } + + void AddPaths_(const Paths64& paths, PathType polytype, bool is_open, + std::vector& vertexLists, LocalMinimaList& locMinList) + { + const auto total_vertex_count = + std::accumulate(paths.begin(), paths.end(), size_t(0), + [](const auto& a, const Path64& path) + {return a + path.size(); }); + if (total_vertex_count == 0) return; + + Vertex* vertices = new Vertex[total_vertex_count], * v = vertices; + for (const Path64& path : paths) + { + //for each path create a circular double linked list of vertices + Vertex* v0 = v, * curr_v = v, * prev_v = nullptr; + + if (path.empty()) + continue; + + v->prev = nullptr; + int cnt = 0; + for (const Point64& pt : path) + { + if (prev_v) + { + if (prev_v->pt == pt) continue; // ie skips duplicates + prev_v->next = curr_v; + } + curr_v->prev = prev_v; + curr_v->pt = pt; + curr_v->flags = VertexFlags::Empty; + prev_v = curr_v++; + cnt++; + } + if (!prev_v || !prev_v->prev) continue; + if (!is_open && prev_v->pt == v0->pt) + prev_v = prev_v->prev; + prev_v->next = v0; + v0->prev = prev_v; + v = curr_v; // ie get ready for next path + if (cnt < 2 || (cnt == 2 && !is_open)) continue; + + //now find and assign local minima + bool going_up, going_up0; + if (is_open) + { + curr_v = v0->next; + while (curr_v != v0 && curr_v->pt.y == v0->pt.y) + curr_v = curr_v->next; + going_up = curr_v->pt.y <= v0->pt.y; + if (going_up) + { + v0->flags = VertexFlags::OpenStart; + AddLocMin(locMinList , *v0, polytype, true); + } + else + v0->flags = VertexFlags::OpenStart | VertexFlags::LocalMax; + } + else // closed path + { + prev_v = v0->prev; + while (prev_v != v0 && prev_v->pt.y == v0->pt.y) + prev_v = prev_v->prev; + if (prev_v == v0) + continue; // only open paths can be completely flat + going_up = prev_v->pt.y > v0->pt.y; + } + + going_up0 = going_up; + prev_v = v0; + curr_v = v0->next; + while (curr_v != v0) + { + if (curr_v->pt.y > prev_v->pt.y && going_up) + { + prev_v->flags = (prev_v->flags | VertexFlags::LocalMax); + going_up = false; + } + else if (curr_v->pt.y < prev_v->pt.y && !going_up) + { + going_up = true; + AddLocMin(locMinList, *prev_v, polytype, is_open); + } + prev_v = curr_v; + curr_v = curr_v->next; + } + + if (is_open) + { + prev_v->flags = prev_v->flags | VertexFlags::OpenEnd; + if (going_up) + prev_v->flags = prev_v->flags | VertexFlags::LocalMax; + else + AddLocMin(locMinList, *prev_v, polytype, is_open); + } + else if (going_up != going_up0) + { + if (going_up0) AddLocMin(locMinList, *prev_v, polytype, false); + else prev_v->flags = prev_v->flags | VertexFlags::LocalMax; + } + } // end processing current path + + vertexLists.emplace_back(vertices); + } + + //------------------------------------------------------------------------------ + // ReuseableDataContainer64 methods ... + //------------------------------------------------------------------------------ + + void ReuseableDataContainer64::AddLocMin(Vertex& vert, PathType polytype, bool is_open) + { + //make sure the vertex is added only once ... + if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::Empty) return; + + vert.flags = (vert.flags | VertexFlags::LocalMin); + minima_list_.emplace_back(std::make_unique (&vert, polytype, is_open)); + } + + void ReuseableDataContainer64::AddPaths(const Paths64& paths, + PathType polytype, bool is_open) + { + AddPaths_(paths, polytype, is_open, vertex_lists_, minima_list_); + } + + ReuseableDataContainer64::~ReuseableDataContainer64() + { + Clear(); + } + + void ReuseableDataContainer64::Clear() + { + minima_list_.clear(); + for (auto v : vertex_lists_) delete[] v; + vertex_lists_.clear(); + } + + //------------------------------------------------------------------------------ + // ClipperBase methods ... + //------------------------------------------------------------------------------ + + ClipperBase::~ClipperBase() + { + Clear(); + } + + void ClipperBase::DeleteEdges(Active*& e) + { + while (e) + { + Active* e2 = e; + e = e->next_in_ael; + delete e2; + } + } + + void ClipperBase::CleanUp() + { + DeleteEdges(actives_); + scanline_list_ = std::priority_queue(); + intersect_nodes_.clear(); + DisposeAllOutRecs(); + horz_seg_list_.clear(); + horz_join_list_.clear(); + } + + + void ClipperBase::Clear() + { + CleanUp(); + DisposeVerticesAndLocalMinima(); + current_locmin_iter_ = minima_list_.begin(); + minima_list_sorted_ = false; + has_open_paths_ = false; + } + + + void ClipperBase::Reset() + { + if (!minima_list_sorted_) + { + std::stable_sort(minima_list_.begin(), minima_list_.end(), LocMinSorter()); //#594 + minima_list_sorted_ = true; + } + LocalMinimaList::const_reverse_iterator i; + for (i = minima_list_.rbegin(); i != minima_list_.rend(); ++i) + InsertScanline((*i)->vertex->pt.y); + + current_locmin_iter_ = minima_list_.begin(); + actives_ = nullptr; + sel_ = nullptr; + succeeded_ = true; + } #ifdef USINGZ - void ClipperBase::SetZ(const Active& e1, const Active& e2, Point64& ip) - { - if (!zCallback_) return; - // prioritize subject over clip vertices by passing - // subject vertices before clip vertices in the callback - if (GetPolyType(e1) == PathType::Subject) - { - if (ip == e1.bot) ip.z = e1.bot.z; - else if (ip == e1.top) ip.z = e1.top.z; - else if (ip == e2.bot) ip.z = e2.bot.z; - else if (ip == e2.top) ip.z = e2.top.z; - zCallback_(e1.bot, e1.top, e2.bot, e2.top, ip); - } - else - { - if (ip == e2.bot) ip.z = e2.bot.z; - else if (ip == e2.top) ip.z = e2.top.z; - else if (ip == e1.bot) ip.z = e1.bot.z; - else if (ip == e1.top) ip.z = e1.top.z; - zCallback_(e2.bot, e2.top, e1.bot, e1.top, ip); - } - } + void ClipperBase::SetZ(const Active& e1, const Active& e2, Point64& ip) + { + if (!zCallback_) return; + // prioritize subject over clip vertices by passing + // subject vertices before clip vertices in the callback + if (GetPolyType(e1) == PathType::Subject) + { + if (ip == e1.bot) ip.z = e1.bot.z; + else if (ip == e1.top) ip.z = e1.top.z; + else if (ip == e2.bot) ip.z = e2.bot.z; + else if (ip == e2.top) ip.z = e2.top.z; + else ip.z = DefaultZ; + zCallback_(e1.bot, e1.top, e2.bot, e2.top, ip); + } + else + { + if (ip == e2.bot) ip.z = e2.bot.z; + else if (ip == e2.top) ip.z = e2.top.z; + else if (ip == e1.bot) ip.z = e1.bot.z; + else if (ip == e1.top) ip.z = e1.top.z; + else ip.z = DefaultZ; + zCallback_(e2.bot, e2.top, e1.bot, e1.top, ip); + } + } #endif - void ClipperBase::AddPath(const Path64& path, PathType polytype, bool is_open) - { - Paths64 tmp; - tmp.push_back(path); - AddPaths(tmp, polytype, is_open); - } - - - void ClipperBase::AddPaths(const Paths64& paths, PathType polytype, bool is_open) - { - if (is_open) has_open_paths_ = true; - minima_list_sorted_ = false; - - Path64::size_type total_vertex_count = 0; - for (const Path64& path : paths) total_vertex_count += path.size(); - if (total_vertex_count == 0) return; - Vertex* vertices = new Vertex[total_vertex_count], *v = vertices; - for (const Path64& path : paths) - { - //for each path create a circular double linked list of vertices - Vertex *v0 = v, *curr_v = v, *prev_v = nullptr; - - v->prev = nullptr; - int cnt = 0; - for (const Point64& pt : path) - { - if (prev_v) - { - if (prev_v->pt == pt) continue; // ie skips duplicates - prev_v->next = curr_v; - } - curr_v->prev = prev_v; - curr_v->pt = pt; - curr_v->flags = VertexFlags::None; - prev_v = curr_v++; - cnt++; - } - if (!prev_v || !prev_v->prev) continue; - if (!is_open && prev_v->pt == v0->pt) - prev_v = prev_v->prev; - prev_v->next = v0; - v0->prev = prev_v; - v = curr_v; // ie get ready for next path - if (cnt < 2 || (cnt == 2 && !is_open)) continue; - - //now find and assign local minima - bool going_up, going_up0; - if (is_open) - { - curr_v = v0->next; - while (curr_v != v0 && curr_v->pt.y == v0->pt.y) - curr_v = curr_v->next; - going_up = curr_v->pt.y <= v0->pt.y; - if (going_up) - { - v0->flags = VertexFlags::OpenStart; - AddLocMin(*v0, polytype, true); - } - else - v0->flags = VertexFlags::OpenStart | VertexFlags::LocalMax; - } - else // closed path - { - prev_v = v0->prev; - while (prev_v != v0 && prev_v->pt.y == v0->pt.y) - prev_v = prev_v->prev; - if (prev_v == v0) - continue; // only open paths can be completely flat - going_up = prev_v->pt.y > v0->pt.y; - } - - going_up0 = going_up; - prev_v = v0; - curr_v = v0->next; - while (curr_v != v0) - { - if (curr_v->pt.y > prev_v->pt.y && going_up) - { - prev_v->flags = (prev_v->flags | VertexFlags::LocalMax); - going_up = false; - } - else if (curr_v->pt.y < prev_v->pt.y && !going_up) - { - going_up = true; - AddLocMin(*prev_v, polytype, is_open); - } - prev_v = curr_v; - curr_v = curr_v->next; - } - - if (is_open) - { - prev_v->flags = prev_v->flags | VertexFlags::OpenEnd; - if (going_up) - prev_v->flags = prev_v->flags | VertexFlags::LocalMax; - else - AddLocMin(*prev_v, polytype, is_open); - } - else if (going_up != going_up0) - { - if (going_up0) AddLocMin(*prev_v, polytype, false); - else prev_v->flags = prev_v->flags | VertexFlags::LocalMax; - } - } // end processing current path - - vertex_lists_.emplace_back(vertices); - } // end AddPaths - - - inline void ClipperBase::InsertScanline(int64_t y) - { - scanline_list_.push(y); - } - - - bool ClipperBase::PopScanline(int64_t& y) - { - if (scanline_list_.empty()) return false; - y = scanline_list_.top(); - scanline_list_.pop(); - while (!scanline_list_.empty() && y == scanline_list_.top()) - scanline_list_.pop(); // Pop duplicates. - return true; - } - - - bool ClipperBase::PopLocalMinima(int64_t y, LocalMinima*& local_minima) - { - if (current_locmin_iter_ == minima_list_.end() || (*current_locmin_iter_)->vertex->pt.y != y) return false; - local_minima = (*current_locmin_iter_++); - return true; - } - - - void ClipperBase::DisposeAllOutRecs() - { - for (auto outrec : outrec_list_) - { - if (outrec->pts) DisposeOutPts(*outrec); - delete outrec; - } - outrec_list_.resize(0); - } - - - void ClipperBase::DisposeVerticesAndLocalMinima() - { - for (auto lm : minima_list_) delete lm; - minima_list_.clear(); - for (auto v : vertex_lists_) delete[] v; - vertex_lists_.clear(); - } - - - void ClipperBase::AddLocMin(Vertex& vert, PathType polytype, bool is_open) - { - //make sure the vertex is added only once ... - if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::None) return; - - vert.flags = (vert.flags | VertexFlags::LocalMin); - minima_list_.push_back(new LocalMinima(&vert, polytype, is_open)); - } - - bool ClipperBase::IsContributingClosed(const Active & e) const - { - switch (fillrule_) - { - case FillRule::EvenOdd: - break; - case FillRule::NonZero: - if (abs(e.wind_cnt) != 1) return false; - break; - case FillRule::Positive: - if (e.wind_cnt != 1) return false; - break; - case FillRule::Negative: - if (e.wind_cnt != -1) return false; - break; - } - - switch (cliptype_) - { - case ClipType::None: - return false; - case ClipType::Intersection: - switch (fillrule_) - { - case FillRule::Positive: - return (e.wind_cnt2 > 0); - case FillRule::Negative: - return (e.wind_cnt2 < 0); - default: - return (e.wind_cnt2 != 0); - } - break; - - case ClipType::Union: - switch (fillrule_) - { - case FillRule::Positive: - return (e.wind_cnt2 <= 0); - case FillRule::Negative: - return (e.wind_cnt2 >= 0); - default: - return (e.wind_cnt2 == 0); - } - break; - - case ClipType::Difference: - bool result; - switch (fillrule_) - { - case FillRule::Positive: - result = (e.wind_cnt2 <= 0); - break; - case FillRule::Negative: - result = (e.wind_cnt2 >= 0); - break; - default: - result = (e.wind_cnt2 == 0); - } - if (GetPolyType(e) == PathType::Subject) - return result; - else - return !result; - break; - - case ClipType::Xor: return true; break; - } - return false; // we should never get here - } - - - inline bool ClipperBase::IsContributingOpen(const Active& e) const - { - bool is_in_clip, is_in_subj; - switch (fillrule_) - { - case FillRule::Positive: - is_in_clip = e.wind_cnt2 > 0; - is_in_subj = e.wind_cnt > 0; - break; - case FillRule::Negative: - is_in_clip = e.wind_cnt2 < 0; - is_in_subj = e.wind_cnt < 0; - break; - default: - is_in_clip = e.wind_cnt2 != 0; - is_in_subj = e.wind_cnt != 0; - } - - switch (cliptype_) - { - case ClipType::Intersection: return is_in_clip; - case ClipType::Union: return (!is_in_subj && !is_in_clip); - default: return !is_in_clip; - } - } - - - void ClipperBase::SetWindCountForClosedPathEdge(Active& e) - { - //Wind counts refer to polygon regions not edges, so here an edge's WindCnt - //indicates the higher of the wind counts for the two regions touching the - //edge. (NB Adjacent regions can only ever have their wind counts differ by - //one. Also, open paths have no meaningful wind directions or counts.) - - Active* e2 = e.prev_in_ael; - //find the nearest closed path edge of the same PolyType in AEL (heading left) - PathType pt = GetPolyType(e); - while (e2 && (GetPolyType(*e2) != pt || IsOpen(*e2))) e2 = e2->prev_in_ael; - - if (!e2) - { - e.wind_cnt = e.wind_dx; - e2 = actives_; - } - else if (fillrule_ == FillRule::EvenOdd) - { - e.wind_cnt = e.wind_dx; - e.wind_cnt2 = e2->wind_cnt2; - e2 = e2->next_in_ael; - } - else - { - //NonZero, positive, or negative filling here ... - //if e's WindCnt is in the SAME direction as its WindDx, then polygon - //filling will be on the right of 'e'. - //NB neither e2.WindCnt nor e2.WindDx should ever be 0. - if (e2->wind_cnt * e2->wind_dx < 0) - { - //opposite directions so 'e' is outside 'e2' ... - if (abs(e2->wind_cnt) > 1) - { - //outside prev poly but still inside another. - if (e2->wind_dx * e.wind_dx < 0) - //reversing direction so use the same WC - e.wind_cnt = e2->wind_cnt; - else - //otherwise keep 'reducing' the WC by 1 (ie towards 0) ... - e.wind_cnt = e2->wind_cnt + e.wind_dx; - } - else - //now outside all polys of same polytype so set own WC ... - e.wind_cnt = (IsOpen(e) ? 1 : e.wind_dx); - } - else - { - //'e' must be inside 'e2' - if (e2->wind_dx * e.wind_dx < 0) - //reversing direction so use the same WC - e.wind_cnt = e2->wind_cnt; - else - //otherwise keep 'increasing' the WC by 1 (ie away from 0) ... - e.wind_cnt = e2->wind_cnt + e.wind_dx; - } - e.wind_cnt2 = e2->wind_cnt2; - e2 = e2->next_in_ael; // ie get ready to calc WindCnt2 - } - - //update wind_cnt2 ... - if (fillrule_ == FillRule::EvenOdd) - while (e2 != &e) - { - if (GetPolyType(*e2) != pt && !IsOpen(*e2)) - e.wind_cnt2 = (e.wind_cnt2 == 0 ? 1 : 0); - e2 = e2->next_in_ael; - } - else - while (e2 != &e) - { - if (GetPolyType(*e2) != pt && !IsOpen(*e2)) - e.wind_cnt2 += e2->wind_dx; - e2 = e2->next_in_ael; - } - } - - - void ClipperBase::SetWindCountForOpenPathEdge(Active& e) - { - Active* e2 = actives_; - if (fillrule_ == FillRule::EvenOdd) - { - int cnt1 = 0, cnt2 = 0; - while (e2 != &e) - { - if (GetPolyType(*e2) == PathType::Clip) - cnt2++; - else if (!IsOpen(*e2)) - cnt1++; - e2 = e2->next_in_ael; - } - e.wind_cnt = (IsOdd(cnt1) ? 1 : 0); - e.wind_cnt2 = (IsOdd(cnt2) ? 1 : 0); - } - else - { - while (e2 != &e) - { - if (GetPolyType(*e2) == PathType::Clip) - e.wind_cnt2 += e2->wind_dx; - else if (!IsOpen(*e2)) - e.wind_cnt += e2->wind_dx; - e2 = e2->next_in_ael; - } - } - } - - - bool IsValidAelOrder(const Active& resident, const Active& newcomer) - { - if (newcomer.curr_x != resident.curr_x) - return newcomer.curr_x > resident.curr_x; - - //get the turning direction a1.top, a2.bot, a2.top - double d = CrossProduct(resident.top, newcomer.bot, newcomer.top); - if (d != 0) return d < 0; - - //edges must be collinear to get here - //for starting open paths, place them according to - //the direction they're about to turn - if (!IsMaxima(resident) && (resident.top.y > newcomer.top.y)) - { - return CrossProduct(newcomer.bot, - resident.top, NextVertex(resident)->pt) <= 0; - } - else if (!IsMaxima(newcomer) && (newcomer.top.y > resident.top.y)) - { - return CrossProduct(newcomer.bot, - newcomer.top, NextVertex(newcomer)->pt) >= 0; - } - - int64_t y = newcomer.bot.y; - bool newcomerIsLeft = newcomer.is_left_bound; - - if (resident.bot.y != y || resident.local_min->vertex->pt.y != y) - return newcomer.is_left_bound; - //resident must also have just been inserted - else if (resident.is_left_bound != newcomerIsLeft) - return newcomerIsLeft; - else if (CrossProduct(PrevPrevVertex(resident)->pt, - resident.bot, resident.top) == 0) return true; - else - //compare turning direction of the alternate bound - return (CrossProduct(PrevPrevVertex(resident)->pt, - newcomer.bot, PrevPrevVertex(newcomer)->pt) > 0) == newcomerIsLeft; - } - - - void ClipperBase::InsertLeftEdge(Active& e) - { - Active* e2; - if (!actives_) - { - e.prev_in_ael = nullptr; - e.next_in_ael = nullptr; - actives_ = &e; - } - else if (!IsValidAelOrder(*actives_, e)) - { - e.prev_in_ael = nullptr; - e.next_in_ael = actives_; - actives_->prev_in_ael = &e; - actives_ = &e; - } - else - { - e2 = actives_; - while (e2->next_in_ael && IsValidAelOrder(*e2->next_in_ael, e)) - e2 = e2->next_in_ael; - e.next_in_ael = e2->next_in_ael; - if (e2->next_in_ael) e2->next_in_ael->prev_in_ael = &e; - e.prev_in_ael = e2; - e2->next_in_ael = &e; - } - } - - - void InsertRightEdge(Active& e, Active& e2) - { - e2.next_in_ael = e.next_in_ael; - if (e.next_in_ael) e.next_in_ael->prev_in_ael = &e2; - e2.prev_in_ael = &e; - e.next_in_ael = &e2; - } - - - void ClipperBase::InsertLocalMinimaIntoAEL(int64_t bot_y) - { - LocalMinima* local_minima; - Active* left_bound, * right_bound; - //Add any local minima (if any) at BotY ... - //nb: horizontal local minima edges should contain locMin.vertex.prev - - while (PopLocalMinima(bot_y, local_minima)) - { - if ((local_minima->vertex->flags & VertexFlags::OpenStart) != VertexFlags::None) - { - left_bound = nullptr; - } - else - { - left_bound = new Active(); - left_bound->bot = local_minima->vertex->pt; - left_bound->curr_x = left_bound->bot.x; - left_bound->wind_cnt = 0, - left_bound->wind_cnt2 = 0, - left_bound->wind_dx = -1, - left_bound->vertex_top = local_minima->vertex->prev; // ie descending - left_bound->top = left_bound->vertex_top->pt; - left_bound->outrec = nullptr; - left_bound->local_min = local_minima; - SetDx(*left_bound); - } - - if ((local_minima->vertex->flags & VertexFlags::OpenEnd) != VertexFlags::None) - { - right_bound = nullptr; - } - else - { - right_bound = new Active(); - right_bound->bot = local_minima->vertex->pt; - right_bound->curr_x = right_bound->bot.x; - right_bound->wind_cnt = 0, - right_bound->wind_cnt2 = 0, - right_bound->wind_dx = 1, - right_bound->vertex_top = local_minima->vertex->next; // ie ascending - right_bound->top = right_bound->vertex_top->pt; - right_bound->outrec = nullptr; - right_bound->local_min = local_minima; - SetDx(*right_bound); - } - - //Currently LeftB is just the descending bound and RightB is the ascending. - //Now if the LeftB isn't on the left of RightB then we need swap them. - if (left_bound && right_bound) - { - if (IsHorizontal(*left_bound)) - { - if (IsHeadingRightHorz(*left_bound)) SwapActives(left_bound, right_bound); - } - else if (IsHorizontal(*right_bound)) - { - if (IsHeadingLeftHorz(*right_bound)) SwapActives(left_bound, right_bound); - } - else if (left_bound->dx < right_bound->dx) - SwapActives(left_bound, right_bound); - } - else if (!left_bound) - { - left_bound = right_bound; - right_bound = nullptr; - } - - bool contributing; - left_bound->is_left_bound = true; - InsertLeftEdge(*left_bound); - - if (IsOpen(*left_bound)) - { - SetWindCountForOpenPathEdge(*left_bound); - contributing = IsContributingOpen(*left_bound); - } - else - { - SetWindCountForClosedPathEdge(*left_bound); - contributing = IsContributingClosed(*left_bound); - } - - if (right_bound) - { - right_bound->is_left_bound = false; - right_bound->wind_cnt = left_bound->wind_cnt; - right_bound->wind_cnt2 = left_bound->wind_cnt2; - InsertRightEdge(*left_bound, *right_bound); /////// - if (contributing) - { - AddLocalMinPoly(*left_bound, *right_bound, left_bound->bot, true); - if (!IsHorizontal(*left_bound) && TestJoinWithPrev1(*left_bound)) - { - OutPt* op = AddOutPt(*left_bound->prev_in_ael, left_bound->bot); - AddJoin(op, left_bound->outrec->pts); - } - } - - while (right_bound->next_in_ael && - IsValidAelOrder(*right_bound->next_in_ael, *right_bound)) - { - IntersectEdges(*right_bound, *right_bound->next_in_ael, right_bound->bot); - SwapPositionsInAEL(*right_bound, *right_bound->next_in_ael); - } - - if (!IsHorizontal(*right_bound) && - TestJoinWithNext1(*right_bound)) - { - OutPt* op = AddOutPt(*right_bound->next_in_ael, right_bound->bot); - AddJoin(right_bound->outrec->pts, op); - } - - if (IsHorizontal(*right_bound)) - PushHorz(*right_bound); - else - InsertScanline(right_bound->top.y); - } - else if (contributing) - { - StartOpenPath(*left_bound, left_bound->bot); - } - - if (IsHorizontal(*left_bound)) - PushHorz(*left_bound); - else - InsertScanline(left_bound->top.y); - } // while (PopLocalMinima()) - } - - - inline void ClipperBase::PushHorz(Active& e) - { - e.next_in_sel = (sel_ ? sel_ : nullptr); - sel_ = &e; - } - - - inline bool ClipperBase::PopHorz(Active*& e) - { - e = sel_; - if (!e) return false; - sel_ = sel_->next_in_sel; - return true; - } - - - OutPt* ClipperBase::AddLocalMinPoly(Active& e1, Active& e2, - const Point64& pt, bool is_new) - { - OutRec* outrec = new OutRec(); - outrec->idx = (unsigned)outrec_list_.size(); - outrec_list_.push_back(outrec); - outrec->pts = nullptr; - outrec->polypath = nullptr; - e1.outrec = outrec; - e2.outrec = outrec; - - //Setting the owner and inner/outer states (above) is an essential - //precursor to setting edge 'sides' (ie left and right sides of output - //polygons) and hence the orientation of output paths ... - - if (IsOpen(e1)) - { - outrec->owner = nullptr; - outrec->is_open = true; - if (e1.wind_dx > 0) - SetSides(*outrec, e1, e2); - else - SetSides(*outrec, e2, e1); - } - else - { - Active* prevHotEdge = GetPrevHotEdge(e1); - //e.windDx is the winding direction of the **input** paths - //and unrelated to the winding direction of output polygons. - //Output orientation is determined by e.outrec.frontE which is - //the ascending edge (see AddLocalMinPoly). - if (prevHotEdge) - { - outrec->owner = prevHotEdge->outrec; - if (OutrecIsAscending(prevHotEdge) == is_new) - SetSides(*outrec, e2, e1); - else - SetSides(*outrec, e1, e2); - } - else - { - outrec->owner = nullptr; - if (is_new) - SetSides(*outrec, e1, e2); - else - SetSides(*outrec, e2, e1); - } - } - - OutPt* op = new OutPt(pt, outrec); - outrec->pts = op; - return op; - } - - - OutPt* ClipperBase::AddLocalMaxPoly(Active& e1, Active& e2, const Point64& pt) - { - if (IsFront(e1) == IsFront(e2)) - { - if (IsOpenEnd(e1)) - SwapFrontBackSides(*e1.outrec); - else if (IsOpenEnd(e2)) - SwapFrontBackSides(*e2.outrec); - else - { - succeeded_ = false; - return nullptr; - } - } - - OutPt* result = AddOutPt(e1, pt); - if (e1.outrec == e2.outrec) - { - OutRec& outrec = *e1.outrec; - outrec.pts = result; - - UncoupleOutRec(e1); - if (!IsOpen(e1)) CleanCollinear(&outrec); - result = outrec.pts; - if (using_polytree_ && outrec.owner && !outrec.owner->front_edge) - outrec.owner = GetRealOutRec(outrec.owner->owner); - } - //and to preserve the winding orientation of outrec ... - else if (IsOpen(e1)) - { - if (e1.wind_dx < 0) - JoinOutrecPaths(e1, e2); - else - JoinOutrecPaths(e2, e1); - } - else if (e1.outrec->idx < e2.outrec->idx) - JoinOutrecPaths(e1, e2); - else - JoinOutrecPaths(e2, e1); - - return result; - } - - void ClipperBase::JoinOutrecPaths(Active& e1, Active& e2) - { - //join e2 outrec path onto e1 outrec path and then delete e2 outrec path - //pointers. (NB Only very rarely do the joining ends share the same coords.) - OutPt* p1_st = e1.outrec->pts; - OutPt* p2_st = e2.outrec->pts; - OutPt* p1_end = p1_st->next; - OutPt* p2_end = p2_st->next; - if (IsFront(e1)) - { - p2_end->prev = p1_st; - p1_st->next = p2_end; - p2_st->next = p1_end; - p1_end->prev = p2_st; - e1.outrec->pts = p2_st; - e1.outrec->front_edge = e2.outrec->front_edge; - if (e1.outrec->front_edge) - e1.outrec->front_edge->outrec = e1.outrec; - } - else - { - p1_end->prev = p2_st; - p2_st->next = p1_end; - p1_st->next = p2_end; - p2_end->prev = p1_st; - e1.outrec->back_edge = e2.outrec->back_edge; - if (e1.outrec->back_edge) - e1.outrec->back_edge->outrec = e1.outrec; - } - - //an owner must have a lower idx otherwise - //it can't be a valid owner - if (e2.outrec->owner && e2.outrec->owner->idx < e1.outrec->idx) - { - if (!e1.outrec->owner || e2.outrec->owner->idx < e1.outrec->owner->idx) - e1.outrec->owner = e2.outrec->owner; - } - - //after joining, the e2.OutRec must contains no vertices ... - e2.outrec->front_edge = nullptr; - e2.outrec->back_edge = nullptr; - e2.outrec->pts = nullptr; - e2.outrec->owner = e1.outrec; - - if (IsOpenEnd(e1)) - { - e2.outrec->pts = e1.outrec->pts; - e1.outrec->pts = nullptr; - } - - //and e1 and e2 are maxima and are about to be dropped from the Actives list. - e1.outrec = nullptr; - e2.outrec = nullptr; - } - - - OutPt* ClipperBase::AddOutPt(const Active& e, const Point64& pt) - { - OutPt* new_op = nullptr; - - //Outrec.OutPts: a circular doubly-linked-list of POutPt where ... - //op_front[.Prev]* ~~~> op_back & op_back == op_front.Next - OutRec* outrec = e.outrec; - bool to_front = IsFront(e); - OutPt* op_front = outrec->pts; - OutPt* op_back = op_front->next; - - if (to_front && (pt == op_front->pt)) - new_op = op_front; - else if (!to_front && (pt == op_back->pt)) - new_op = op_back; - else - { - new_op = new OutPt(pt, outrec); - op_back->prev = new_op; - new_op->prev = op_front; - new_op->next = op_back; - op_front->next = new_op; - if (to_front) outrec->pts = new_op; - } - return new_op; - } - - - bool ClipperBase::ValidateClosedPathEx(OutPt*& outpt) - { - if (IsValidClosedPath(outpt)) return true; - if (outpt) SafeDisposeOutPts(outpt); - return false; - } - - - void ClipperBase::CleanCollinear(OutRec* outrec) - { - outrec = GetRealOutRec(outrec); - if (!outrec || outrec->is_open || - outrec->front_edge || !ValidateClosedPathEx(outrec->pts)) return; - - OutPt* startOp = outrec->pts, * op2 = startOp; - for (; ; ) - { - if (op2->joiner) return; - - //NB if preserveCollinear == true, then only remove 180 deg. spikes - if ((CrossProduct(op2->prev->pt, op2->pt, op2->next->pt) == 0) && - (op2->pt == op2->prev->pt || - op2->pt == op2->next->pt || !PreserveCollinear || - DotProduct(op2->prev->pt, op2->pt, op2->next->pt) < 0)) - { - - if (op2 == outrec->pts) outrec->pts = op2->prev; - - op2 = DisposeOutPt(op2); - if (!ValidateClosedPathEx(op2)) - { - outrec->pts = nullptr; - return; - } - startOp = op2; - continue; - } - op2 = op2->next; - if (op2 == startOp) break; - } - FixSelfIntersects(outrec); - } - - void ClipperBase::DoSplitOp(OutRec* outrec, OutPt* splitOp) - { - // splitOp.prev -> splitOp && - // splitOp.next -> splitOp.next.next are intersecting - OutPt* prevOp = splitOp->prev; - OutPt* nextNextOp = splitOp->next->next; - outrec->pts = prevOp; - PointD ipD; - GetIntersectPoint(prevOp->pt, - splitOp->pt, splitOp->next->pt, nextNextOp->pt, ipD); - Point64 ip = Point64(ipD); + void ClipperBase::AddPath(const Path64& path, PathType polytype, bool is_open) + { + AddPaths(Paths64(1, path), polytype, is_open); + } + + void ClipperBase::AddPaths(const Paths64& paths, PathType polytype, bool is_open) + { + if (is_open) has_open_paths_ = true; + minima_list_sorted_ = false; + AddPaths_(paths, polytype, is_open, vertex_lists_, minima_list_); + } + + void ClipperBase::AddReuseableData(const ReuseableDataContainer64& reuseable_data) + { + // nb: reuseable_data will continue to own the vertices + // and remains responsible for their clean up. + succeeded_ = false; + minima_list_sorted_ = false; + LocalMinimaList::const_iterator i; + for (i = reuseable_data.minima_list_.cbegin(); i != reuseable_data.minima_list_.cend(); ++i) + { + minima_list_.emplace_back(std::make_unique ((*i)->vertex, (*i)->polytype, (*i)->is_open)); + if ((*i)->is_open) has_open_paths_ = true; + } + } + + void ClipperBase::InsertScanline(int64_t y) + { + scanline_list_.push(y); + } + + + bool ClipperBase::PopScanline(int64_t& y) + { + if (scanline_list_.empty()) return false; + y = scanline_list_.top(); + scanline_list_.pop(); + while (!scanline_list_.empty() && y == scanline_list_.top()) + scanline_list_.pop(); // Pop duplicates. + return true; + } + + + bool ClipperBase::PopLocalMinima(int64_t y, LocalMinima*& local_minima) + { + if (current_locmin_iter_ == minima_list_.end() || (*current_locmin_iter_)->vertex->pt.y != y) return false; + local_minima = (current_locmin_iter_++)->get(); + return true; + } + + void ClipperBase::DisposeAllOutRecs() + { + for (auto outrec : outrec_list_) + { + if (outrec->pts) DisposeOutPts(outrec); + delete outrec; + } + outrec_list_.resize(0); + } + + void ClipperBase::DisposeVerticesAndLocalMinima() + { + minima_list_.clear(); + for (auto v : vertex_lists_) delete[] v; + vertex_lists_.clear(); + } + + + void ClipperBase::AddLocMin(Vertex& vert, PathType polytype, bool is_open) + { + //make sure the vertex is added only once ... + if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::Empty) return; + + vert.flags = (vert.flags | VertexFlags::LocalMin); + minima_list_.emplace_back(std::make_unique (&vert, polytype, is_open)); + } + + bool ClipperBase::IsContributingClosed(const Active& e) const + { + switch (fillrule_) + { + case FillRule::EvenOdd: + break; + case FillRule::NonZero: + if (abs(e.wind_cnt) != 1) return false; + break; + case FillRule::Positive: + if (e.wind_cnt != 1) return false; + break; + case FillRule::Negative: + if (e.wind_cnt != -1) return false; + break; + // Should never happen, but adding this to stop a compiler warning + default: + break; + } + + switch (cliptype_) + { + case ClipType::NoClip: + return false; + case ClipType::Intersection: + switch (fillrule_) + { + case FillRule::Positive: + return (e.wind_cnt2 > 0); + case FillRule::Negative: + return (e.wind_cnt2 < 0); + default: + return (e.wind_cnt2 != 0); + } + break; + + case ClipType::Union: + switch (fillrule_) + { + case FillRule::Positive: + return (e.wind_cnt2 <= 0); + case FillRule::Negative: + return (e.wind_cnt2 >= 0); + default: + return (e.wind_cnt2 == 0); + } + break; + + case ClipType::Difference: + bool result; + switch (fillrule_) + { + case FillRule::Positive: + result = (e.wind_cnt2 <= 0); + break; + case FillRule::Negative: + result = (e.wind_cnt2 >= 0); + break; + default: + result = (e.wind_cnt2 == 0); + } + if (GetPolyType(e) == PathType::Subject) + return result; + else + return !result; + break; + + case ClipType::Xor: return true; break; + // Should never happen, but adding this to stop a compiler warning + default: + break; + } + return false; // we should never get here + } + + + inline bool ClipperBase::IsContributingOpen(const Active& e) const + { + bool is_in_clip, is_in_subj; + switch (fillrule_) + { + case FillRule::Positive: + is_in_clip = e.wind_cnt2 > 0; + is_in_subj = e.wind_cnt > 0; + break; + case FillRule::Negative: + is_in_clip = e.wind_cnt2 < 0; + is_in_subj = e.wind_cnt < 0; + break; + default: + is_in_clip = e.wind_cnt2 != 0; + is_in_subj = e.wind_cnt != 0; + } + + switch (cliptype_) + { + case ClipType::Intersection: return is_in_clip; + case ClipType::Union: return (!is_in_subj && !is_in_clip); + default: return !is_in_clip; + } + } + + + void ClipperBase::SetWindCountForClosedPathEdge(Active& e) + { + //Wind counts refer to polygon regions not edges, so here an edge's WindCnt + //indicates the higher of the wind counts for the two regions touching the + //edge. (NB Adjacent regions can only ever have their wind counts differ by + //one. Also, open paths have no meaningful wind directions or counts.) + + Active* e2 = e.prev_in_ael; + //find the nearest closed path edge of the same PolyType in AEL (heading left) + PathType pt = GetPolyType(e); + while (e2 && (GetPolyType(*e2) != pt || IsOpen(*e2))) e2 = e2->prev_in_ael; + + if (!e2) + { + e.wind_cnt = e.wind_dx; + e2 = actives_; + } + else if (fillrule_ == FillRule::EvenOdd) + { + e.wind_cnt = e.wind_dx; + e.wind_cnt2 = e2->wind_cnt2; + e2 = e2->next_in_ael; + } + else + { + //NonZero, positive, or negative filling here ... + //if e's WindCnt is in the SAME direction as its WindDx, then polygon + //filling will be on the right of 'e'. + //NB neither e2.WindCnt nor e2.WindDx should ever be 0. + if (e2->wind_cnt * e2->wind_dx < 0) + { + //opposite directions so 'e' is outside 'e2' ... + if (abs(e2->wind_cnt) > 1) + { + //outside prev poly but still inside another. + if (e2->wind_dx * e.wind_dx < 0) + //reversing direction so use the same WC + e.wind_cnt = e2->wind_cnt; + else + //otherwise keep 'reducing' the WC by 1 (ie towards 0) ... + e.wind_cnt = e2->wind_cnt + e.wind_dx; + } + else + //now outside all polys of same polytype so set own WC ... + e.wind_cnt = (IsOpen(e) ? 1 : e.wind_dx); + } + else + { + //'e' must be inside 'e2' + if (e2->wind_dx * e.wind_dx < 0) + //reversing direction so use the same WC + e.wind_cnt = e2->wind_cnt; + else + //otherwise keep 'increasing' the WC by 1 (ie away from 0) ... + e.wind_cnt = e2->wind_cnt + e.wind_dx; + } + e.wind_cnt2 = e2->wind_cnt2; + e2 = e2->next_in_ael; // ie get ready to calc WindCnt2 + } + + //update wind_cnt2 ... + if (fillrule_ == FillRule::EvenOdd) + while (e2 != &e) + { + if (GetPolyType(*e2) != pt && !IsOpen(*e2)) + e.wind_cnt2 = (e.wind_cnt2 == 0 ? 1 : 0); + e2 = e2->next_in_ael; + } + else + while (e2 != &e) + { + if (GetPolyType(*e2) != pt && !IsOpen(*e2)) + e.wind_cnt2 += e2->wind_dx; + e2 = e2->next_in_ael; + } + } + + + void ClipperBase::SetWindCountForOpenPathEdge(Active& e) + { + Active* e2 = actives_; + if (fillrule_ == FillRule::EvenOdd) + { + int cnt1 = 0, cnt2 = 0; + while (e2 != &e) + { + if (GetPolyType(*e2) == PathType::Clip) + cnt2++; + else if (!IsOpen(*e2)) + cnt1++; + e2 = e2->next_in_ael; + } + e.wind_cnt = (IsOdd(cnt1) ? 1 : 0); + e.wind_cnt2 = (IsOdd(cnt2) ? 1 : 0); + } + else + { + while (e2 != &e) + { + if (GetPolyType(*e2) == PathType::Clip) + e.wind_cnt2 += e2->wind_dx; + else if (!IsOpen(*e2)) + e.wind_cnt += e2->wind_dx; + e2 = e2->next_in_ael; + } + } + } + + bool IsValidAelOrder(const Active& resident, const Active& newcomer) + { + if (newcomer.curr_x != resident.curr_x) + return newcomer.curr_x > resident.curr_x; + + //get the turning direction a1.top, a2.bot, a2.top + double d = CrossProduct(resident.top, newcomer.bot, newcomer.top); + if (d != 0) return d < 0; + + //edges must be collinear to get here + //for starting open paths, place them according to + //the direction they're about to turn + if (!IsMaxima(resident) && (resident.top.y > newcomer.top.y)) + { + return CrossProduct(newcomer.bot, + resident.top, NextVertex(resident)->pt) <= 0; + } + else if (!IsMaxima(newcomer) && (newcomer.top.y > resident.top.y)) + { + return CrossProduct(newcomer.bot, + newcomer.top, NextVertex(newcomer)->pt) >= 0; + } + + int64_t y = newcomer.bot.y; + bool newcomerIsLeft = newcomer.is_left_bound; + + if (resident.bot.y != y || resident.local_min->vertex->pt.y != y) + return newcomer.is_left_bound; + //resident must also have just been inserted + else if (resident.is_left_bound != newcomerIsLeft) + return newcomerIsLeft; + else if (IsCollinear(PrevPrevVertex(resident)->pt, + resident.bot, resident.top)) return true; + else + //compare turning direction of the alternate bound + return (CrossProduct(PrevPrevVertex(resident)->pt, + newcomer.bot, PrevPrevVertex(newcomer)->pt) > 0) == newcomerIsLeft; + } + + + void ClipperBase::InsertLeftEdge(Active& e) + { + Active* e2; + if (!actives_) + { + e.prev_in_ael = nullptr; + e.next_in_ael = nullptr; + actives_ = &e; + } + else if (!IsValidAelOrder(*actives_, e)) + { + e.prev_in_ael = nullptr; + e.next_in_ael = actives_; + actives_->prev_in_ael = &e; + actives_ = &e; + } + else + { + e2 = actives_; + while (e2->next_in_ael && IsValidAelOrder(*e2->next_in_ael, e)) + e2 = e2->next_in_ael; + if (e2->join_with == JoinWith::Right) + e2 = e2->next_in_ael; + if (!e2) return; // should never happen and stops compiler warning :) + e.next_in_ael = e2->next_in_ael; + if (e2->next_in_ael) e2->next_in_ael->prev_in_ael = &e; + e.prev_in_ael = e2; + e2->next_in_ael = &e; + } + } + + + void InsertRightEdge(Active& e, Active& e2) + { + e2.next_in_ael = e.next_in_ael; + if (e.next_in_ael) e.next_in_ael->prev_in_ael = &e2; + e2.prev_in_ael = &e; + e.next_in_ael = &e2; + } + + + void ClipperBase::InsertLocalMinimaIntoAEL(int64_t bot_y) + { + LocalMinima* local_minima; + Active* left_bound, * right_bound; + //Add any local minima (if any) at BotY ... + //nb: horizontal local minima edges should contain locMin.vertex.prev + + while (PopLocalMinima(bot_y, local_minima)) + { + if ((local_minima->vertex->flags & VertexFlags::OpenStart) != VertexFlags::Empty) + { + left_bound = nullptr; + } + else + { + left_bound = new Active(); + left_bound->bot = local_minima->vertex->pt; + left_bound->curr_x = left_bound->bot.x; + left_bound->wind_dx = -1; + left_bound->vertex_top = local_minima->vertex->prev; // ie descending + left_bound->top = left_bound->vertex_top->pt; + left_bound->local_min = local_minima; + SetDx(*left_bound); + } + + if ((local_minima->vertex->flags & VertexFlags::OpenEnd) != VertexFlags::Empty) + { + right_bound = nullptr; + } + else + { + right_bound = new Active(); + right_bound->bot = local_minima->vertex->pt; + right_bound->curr_x = right_bound->bot.x; + right_bound->wind_dx = 1; + right_bound->vertex_top = local_minima->vertex->next; // ie ascending + right_bound->top = right_bound->vertex_top->pt; + right_bound->local_min = local_minima; + SetDx(*right_bound); + } + + //Currently LeftB is just the descending bound and RightB is the ascending. + //Now if the LeftB isn't on the left of RightB then we need swap them. + if (left_bound && right_bound) + { + if (IsHorizontal(*left_bound)) + { + if (IsHeadingRightHorz(*left_bound)) SwapActives(left_bound, right_bound); + } + else if (IsHorizontal(*right_bound)) + { + if (IsHeadingLeftHorz(*right_bound)) SwapActives(left_bound, right_bound); + } + else if (left_bound->dx < right_bound->dx) + SwapActives(left_bound, right_bound); + } + else if (!left_bound) + { + left_bound = right_bound; + right_bound = nullptr; + } + + bool contributing; + left_bound->is_left_bound = true; + InsertLeftEdge(*left_bound); + + if (IsOpen(*left_bound)) + { + SetWindCountForOpenPathEdge(*left_bound); + contributing = IsContributingOpen(*left_bound); + } + else + { + SetWindCountForClosedPathEdge(*left_bound); + contributing = IsContributingClosed(*left_bound); + } + + if (right_bound) + { + right_bound->is_left_bound = false; + right_bound->wind_cnt = left_bound->wind_cnt; + right_bound->wind_cnt2 = left_bound->wind_cnt2; + InsertRightEdge(*left_bound, *right_bound); /////// + if (contributing) + { + AddLocalMinPoly(*left_bound, *right_bound, left_bound->bot, true); + if (!IsHorizontal(*left_bound)) + CheckJoinLeft(*left_bound, left_bound->bot); + } + + while (right_bound->next_in_ael && + IsValidAelOrder(*right_bound->next_in_ael, *right_bound)) + { + IntersectEdges(*right_bound, *right_bound->next_in_ael, right_bound->bot); + SwapPositionsInAEL(*right_bound, *right_bound->next_in_ael); + } + + if (IsHorizontal(*right_bound)) + PushHorz(*right_bound); + else + { + CheckJoinRight(*right_bound, right_bound->bot); + InsertScanline(right_bound->top.y); + } + } + else if (contributing) + { + StartOpenPath(*left_bound, left_bound->bot); + } + + if (IsHorizontal(*left_bound)) + PushHorz(*left_bound); + else + InsertScanline(left_bound->top.y); + } // while (PopLocalMinima()) + } + + + inline void ClipperBase::PushHorz(Active& e) + { + e.next_in_sel = (sel_ ? sel_ : nullptr); + sel_ = &e; + } + + + inline bool ClipperBase::PopHorz(Active*& e) + { + e = sel_; + if (!e) return false; + sel_ = sel_->next_in_sel; + return true; + } + + + OutPt* ClipperBase::AddLocalMinPoly(Active& e1, Active& e2, + const Point64& pt, bool is_new) + { + OutRec* outrec = NewOutRec(); + e1.outrec = outrec; + e2.outrec = outrec; + + if (IsOpen(e1)) + { + outrec->owner = nullptr; + outrec->is_open = true; + if (e1.wind_dx > 0) + SetSides(*outrec, e1, e2); + else + SetSides(*outrec, e2, e1); + } + else + { + Active* prevHotEdge = GetPrevHotEdge(e1); + //e.windDx is the winding direction of the **input** paths + //and unrelated to the winding direction of output polygons. + //Output orientation is determined by e.outrec.frontE which is + //the ascending edge (see AddLocalMinPoly). + if (prevHotEdge) + { + if (using_polytree_) + SetOwner(outrec, prevHotEdge->outrec); + if (OutrecIsAscending(prevHotEdge) == is_new) + SetSides(*outrec, e2, e1); + else + SetSides(*outrec, e1, e2); + } + else + { + outrec->owner = nullptr; + if (is_new) + SetSides(*outrec, e1, e2); + else + SetSides(*outrec, e2, e1); + } + } + + OutPt* op = new OutPt(pt, outrec); + outrec->pts = op; + return op; + } + + + OutPt* ClipperBase::AddLocalMaxPoly(Active& e1, Active& e2, const Point64& pt) + { + if (IsJoined(e1)) Split(e1, pt); + if (IsJoined(e2)) Split(e2, pt); + + if (IsFront(e1) == IsFront(e2)) + { + if (IsOpenEnd(e1)) + SwapFrontBackSides(*e1.outrec); + else if (IsOpenEnd(e2)) + SwapFrontBackSides(*e2.outrec); + else + { + succeeded_ = false; + return nullptr; + } + } + + OutPt* result = AddOutPt(e1, pt); + if (e1.outrec == e2.outrec) + { + OutRec& outrec = *e1.outrec; + outrec.pts = result; + + if (using_polytree_) + { + Active* e = GetPrevHotEdge(e1); + if (!e) + outrec.owner = nullptr; + else + SetOwner(&outrec, e->outrec); + // nb: outRec.owner here is likely NOT the real + // owner but this will be checked in RecursiveCheckOwners() + } + + UncoupleOutRec(e1); + result = outrec.pts; + if (outrec.owner && !outrec.owner->front_edge) + outrec.owner = GetRealOutRec(outrec.owner); + } + //and to preserve the winding orientation of outrec ... + else if (IsOpen(e1)) + { + if (e1.wind_dx < 0) + JoinOutrecPaths(e1, e2); + else + JoinOutrecPaths(e2, e1); + } + else if (e1.outrec->idx < e2.outrec->idx) + JoinOutrecPaths(e1, e2); + else + JoinOutrecPaths(e2, e1); + return result; + } + + void ClipperBase::JoinOutrecPaths(Active& e1, Active& e2) + { + //join e2 outrec path onto e1 outrec path and then delete e2 outrec path + //pointers. (NB Only very rarely do the joining ends share the same coords.) + OutPt* p1_st = e1.outrec->pts; + OutPt* p2_st = e2.outrec->pts; + OutPt* p1_end = p1_st->next; + OutPt* p2_end = p2_st->next; + if (IsFront(e1)) + { + p2_end->prev = p1_st; + p1_st->next = p2_end; + p2_st->next = p1_end; + p1_end->prev = p2_st; + e1.outrec->pts = p2_st; + e1.outrec->front_edge = e2.outrec->front_edge; + if (e1.outrec->front_edge) + e1.outrec->front_edge->outrec = e1.outrec; + } + else + { + p1_end->prev = p2_st; + p2_st->next = p1_end; + p1_st->next = p2_end; + p2_end->prev = p1_st; + e1.outrec->back_edge = e2.outrec->back_edge; + if (e1.outrec->back_edge) + e1.outrec->back_edge->outrec = e1.outrec; + } + + //after joining, the e2.OutRec must contains no vertices ... + e2.outrec->front_edge = nullptr; + e2.outrec->back_edge = nullptr; + e2.outrec->pts = nullptr; + + if (IsOpenEnd(e1)) + { + e2.outrec->pts = e1.outrec->pts; + e1.outrec->pts = nullptr; + } + else + SetOwner(e2.outrec, e1.outrec); + + //and e1 and e2 are maxima and are about to be dropped from the Actives list. + e1.outrec = nullptr; + e2.outrec = nullptr; + } + + OutRec* ClipperBase::NewOutRec() + { + OutRec* result = new OutRec(); + result->idx = outrec_list_.size(); + outrec_list_.emplace_back(result); + result->pts = nullptr; + result->owner = nullptr; + result->polypath = nullptr; + result->is_open = false; + result->splits = nullptr; + return result; + } + + + OutPt* ClipperBase::AddOutPt(const Active& e, const Point64& pt) + { + OutPt* new_op = nullptr; + + //Outrec.OutPts: a circular doubly-linked-list of POutPt where ... + //op_front[.Prev]* ~~~> op_back & op_back == op_front.Next + OutRec* outrec = e.outrec; + bool to_front = IsFront(e); + OutPt* op_front = outrec->pts; + OutPt* op_back = op_front->next; + + if (to_front) + { + if (pt == op_front->pt) + return op_front; + } + else if (pt == op_back->pt) + return op_back; + + new_op = new OutPt(pt, outrec); + op_back->prev = new_op; + new_op->prev = op_front; + new_op->next = op_back; + op_front->next = new_op; + if (to_front) outrec->pts = new_op; + return new_op; + } + + void ClipperBase::CleanCollinear(OutRec* outrec) + { + outrec = GetRealOutRec(outrec); + if (!outrec || outrec->is_open) return; + if (!IsValidClosedPath(outrec->pts)) + { + DisposeOutPts(outrec); + return; + } + + OutPt* startOp = outrec->pts, * op2 = startOp; + for (; ; ) + { + //NB if preserveCollinear == true, then only remove 180 deg. spikes + if (IsCollinear(op2->prev->pt, op2->pt, op2->next->pt) && + (op2->pt == op2->prev->pt || + op2->pt == op2->next->pt || !preserve_collinear_ || + DotProduct(op2->prev->pt, op2->pt, op2->next->pt) < 0)) + { + + if (op2 == outrec->pts) outrec->pts = op2->prev; + + op2 = DisposeOutPt(op2); + if (!IsValidClosedPath(op2)) + { + DisposeOutPts(outrec); + return; + } + startOp = op2; + continue; + } + op2 = op2->next; + if (op2 == startOp) break; + } + FixSelfIntersects(outrec); + } + + void ClipperBase::DoSplitOp(OutRec* outrec, OutPt* splitOp) + { + // splitOp.prev -> splitOp && + // splitOp.next -> splitOp.next.next are intersecting + OutPt* prevOp = splitOp->prev; + OutPt* nextNextOp = splitOp->next->next; + outrec->pts = prevOp; + + Point64 ip; + GetSegmentIntersectPt(prevOp->pt, splitOp->pt, + splitOp->next->pt, nextNextOp->pt, ip); + #ifdef USINGZ - if (zCallback_) - zCallback_(prevOp->pt, splitOp->pt, splitOp->next->pt, nextNextOp->pt, ip); + if (zCallback_) zCallback_(prevOp->pt, splitOp->pt, + splitOp->next->pt, nextNextOp->pt, ip); #endif - double area1 = Area(outrec->pts); - double absArea1 = std::fabs(area1); - if (absArea1 < 2) - { - SafeDisposeOutPts(outrec->pts); - // outrec.pts == nil; :) - return; - } + double area1 = Area(outrec->pts); + double absArea1 = std::fabs(area1); + if (absArea1 < 2) + { + DisposeOutPts(outrec); + return; + } - // nb: area1 is the path's area *before* splitting, whereas area2 is - // the area of the triangle containing splitOp & splitOp.next. - // So the only way for these areas to have the same sign is if - // the split triangle is larger than the path containing prevOp or - // if there's more than one self=intersection. - double area2 = AreaTriangle(ip, splitOp->pt, splitOp->next->pt); - double absArea2 = std::fabs(area2); + double area2 = AreaTriangle(ip, splitOp->pt, splitOp->next->pt); + double absArea2 = std::fabs(area2); - // de-link splitOp and splitOp.next from the path - // while inserting the intersection point - if (ip == prevOp->pt || ip == nextNextOp->pt) - { - nextNextOp->prev = prevOp; - prevOp->next = nextNextOp; - } - else - { - OutPt* newOp2 = new OutPt(ip, prevOp->outrec); - newOp2->prev = prevOp; - newOp2->next = nextNextOp; - nextNextOp->prev = newOp2; - prevOp->next = newOp2; - } + // de-link splitOp and splitOp.next from the path + // while inserting the intersection point + if (ip == prevOp->pt || ip == nextNextOp->pt) + { + nextNextOp->prev = prevOp; + prevOp->next = nextNextOp; + } + else + { + OutPt* newOp2 = new OutPt(ip, prevOp->outrec); + newOp2->prev = prevOp; + newOp2->next = nextNextOp; + nextNextOp->prev = newOp2; + prevOp->next = newOp2; + } - SafeDeleteOutPtJoiners(splitOp->next); - SafeDeleteOutPtJoiners(splitOp); + // area1 is the path's area *before* splitting, whereas area2 is + // the area of the triangle containing splitOp & splitOp.next. + // So the only way for these areas to have the same sign is if + // the split triangle is larger than the path containing prevOp or + // if there's more than one self-intersection. + if (absArea2 >= 1 && + (absArea2 > absArea1 || (area2 > 0) == (area1 > 0))) + { + OutRec* newOr = NewOutRec(); + newOr->owner = outrec->owner; - if (absArea2 >= 1 && - (absArea2 > absArea1 || (area2 > 0) == (area1 > 0))) - { - OutRec* newOutRec = new OutRec(); - newOutRec->idx = outrec_list_.size(); - outrec_list_.push_back(newOutRec); - newOutRec->owner = prevOp->outrec->owner; - newOutRec->polypath = nullptr; - splitOp->outrec = newOutRec; - splitOp->next->outrec = newOutRec; + splitOp->outrec = newOr; + splitOp->next->outrec = newOr; + OutPt* newOp = new OutPt(ip, newOr); + newOp->prev = splitOp->next; + newOp->next = splitOp; + newOr->pts = newOp; + splitOp->prev = newOp; + splitOp->next->next = newOp; - OutPt* newOp = new OutPt(ip, newOutRec); - newOp->prev = splitOp->next; - newOp->next = splitOp; - newOutRec->pts = newOp; - splitOp->prev = newOp; - splitOp->next->next = newOp; - } - else - { - delete splitOp->next; - delete splitOp; - } - } + if (using_polytree_) + { + if (Path1InsidePath2(prevOp, newOp)) + { + newOr->splits = new OutRecList(); + newOr->splits->emplace_back(outrec); + } + else + { + if (!outrec->splits) outrec->splits = new OutRecList(); + outrec->splits->emplace_back(newOr); + } + } + } + else + { + delete splitOp->next; + delete splitOp; + } + } - void ClipperBase::FixSelfIntersects(OutRec* outrec) - { - OutPt* op2 = outrec->pts; - for (; ; ) - { - // triangles can't self-intersect - if (op2->prev == op2->next->next) break; - if (SegmentsIntersect(op2->prev->pt, - op2->pt, op2->next->pt, op2->next->next->pt)) - { - if (op2 == outrec->pts || op2->next == outrec->pts) - outrec->pts = outrec->pts->prev; - DoSplitOp(outrec, op2); - if (!outrec->pts) break; - op2 = outrec->pts; - continue; - } - else - op2 = op2->next; + void ClipperBase::FixSelfIntersects(OutRec* outrec) + { + OutPt* op2 = outrec->pts; + for (; ; ) + { + // triangles can't self-intersect + if (op2->prev == op2->next->next) break; + if (SegmentsIntersect(op2->prev->pt, + op2->pt, op2->next->pt, op2->next->next->pt)) + { + if (op2 == outrec->pts || op2->next == outrec->pts) + outrec->pts = outrec->pts->prev; + DoSplitOp(outrec, op2); + if (!outrec->pts) break; + op2 = outrec->pts; + continue; + } + else + op2 = op2->next; - if (op2 == outrec->pts) break; - } - } + if (op2 == outrec->pts) break; + } + } - inline void UpdateOutrecOwner(OutRec* outrec) - { - OutPt* opCurr = outrec->pts; - for (; ; ) - { - opCurr->outrec = outrec; - opCurr = opCurr->next; - if (opCurr == outrec->pts) return; - } - } + inline void UpdateOutrecOwner(OutRec* outrec) + { + OutPt* opCurr = outrec->pts; + for (; ; ) + { + opCurr->outrec = outrec; + opCurr = opCurr->next; + if (opCurr == outrec->pts) return; + } + } - void ClipperBase::SafeDisposeOutPts(OutPt*& op) - { - OutRec* outrec = GetRealOutRec(op->outrec); - if (outrec->front_edge) - outrec->front_edge->outrec = nullptr; - if (outrec->back_edge) - outrec->back_edge->outrec = nullptr; + OutPt* ClipperBase::StartOpenPath(Active& e, const Point64& pt) + { + OutRec* outrec = NewOutRec(); + outrec->is_open = true; - op->prev->next = nullptr; - while (op) - { - SafeDeleteOutPtJoiners(op); - OutPt* tmp = op->next; - delete op; - op = tmp; - } - outrec->pts = nullptr; - } + if (e.wind_dx > 0) + { + outrec->front_edge = &e; + outrec->back_edge = nullptr; + } + else + { + outrec->front_edge = nullptr; + outrec->back_edge = &e; + } + + e.outrec = outrec; + + OutPt* op = new OutPt(pt, outrec); + outrec->pts = op; + return op; + } + + inline void TrimHorz(Active& horzEdge, bool preserveCollinear) + { + bool wasTrimmed = false; + Point64 pt = NextVertex(horzEdge)->pt; + while (pt.y == horzEdge.top.y) + { + //always trim 180 deg. spikes (in closed paths) + //but otherwise break if preserveCollinear = true + if (preserveCollinear && + ((pt.x < horzEdge.top.x) != (horzEdge.bot.x < horzEdge.top.x))) + break; + + horzEdge.vertex_top = NextVertex(horzEdge); + horzEdge.top = pt; + wasTrimmed = true; + if (IsMaxima(horzEdge)) break; + pt = NextVertex(horzEdge)->pt; + } + + if (wasTrimmed) SetDx(horzEdge); // +/-infinity + } - void ClipperBase::CompleteSplit(OutPt* op1, OutPt* op2, OutRec& outrec) - { - double area1 = Area(op1); - double area2 = Area(op2); - bool signs_change = (area1 > 0) == (area2 < 0); + inline void ClipperBase::UpdateEdgeIntoAEL(Active* e) + { + e->bot = e->top; + e->vertex_top = NextVertex(*e); + e->top = e->vertex_top->pt; + e->curr_x = e->bot.x; + SetDx(*e); - if (area1 == 0 || (signs_change && std::abs(area1) < 2)) - { - SafeDisposeOutPts(op1); - outrec.pts = op2; - } - else if (area2 == 0 || (signs_change && std::abs(area2) < 2)) - { - SafeDisposeOutPts(op2); - outrec.pts = op1; - } - else - { - OutRec* newOr = new OutRec(); - newOr->idx = outrec_list_.size(); - outrec_list_.push_back(newOr); - newOr->polypath = nullptr; + if (IsJoined(*e)) Split(*e, e->bot); - if (using_polytree_) - { - if (!outrec.splits) outrec.splits = new OutRecList(); - outrec.splits->push_back(newOr); - } + if (IsHorizontal(*e)) + { + if (!IsOpen(*e)) TrimHorz(*e, preserve_collinear_); + return; + } - if (std::abs(area1) >= std::abs(area2)) - { - outrec.pts = op1; - newOr->pts = op2; - } - else - { - outrec.pts = op2; - newOr->pts = op1; - } + InsertScanline(e->top.y); + CheckJoinLeft(*e, e->bot); + CheckJoinRight(*e, e->bot, true); // (#500) + } - if ((area1 > 0) == (area2 > 0)) - newOr->owner = outrec.owner; - else - newOr->owner = &outrec; - - UpdateOutrecOwner(newOr); - CleanCollinear(newOr); - } - } + Active* FindEdgeWithMatchingLocMin(Active* e) + { + Active* result = e->next_in_ael; + while (result) + { + if (result->local_min == e->local_min) return result; + else if (!IsHorizontal(*result) && e->bot != result->bot) result = nullptr; + else result = result->next_in_ael; + } + result = e->prev_in_ael; + while (result) + { + if (result->local_min == e->local_min) return result; + else if (!IsHorizontal(*result) && e->bot != result->bot) return nullptr; + else result = result->prev_in_ael; + } + return result; + } - OutPt* ClipperBase::StartOpenPath(Active& e, const Point64& pt) - { - OutRec* outrec = new OutRec(); - outrec->idx = outrec_list_.size(); - outrec_list_.push_back(outrec); - outrec->owner = nullptr; - outrec->is_open = true; - outrec->pts = nullptr; - outrec->polypath = nullptr; + void ClipperBase::IntersectEdges(Active& e1, Active& e2, const Point64& pt) + { + //MANAGE OPEN PATH INTERSECTIONS SEPARATELY ... + if (has_open_paths_ && (IsOpen(e1) || IsOpen(e2))) + { + if (IsOpen(e1) && IsOpen(e2)) return; + Active* edge_o, * edge_c; + if (IsOpen(e1)) + { + edge_o = &e1; + edge_c = &e2; + } + else + { + edge_o = &e2; + edge_c = &e1; + } + if (IsJoined(*edge_c)) Split(*edge_c, pt); // needed for safety - if (e.wind_dx > 0) - { - outrec->front_edge = &e; - outrec->back_edge = nullptr; - } - else - { - outrec->front_edge = nullptr; - outrec->back_edge =& e; - } + if (abs(edge_c->wind_cnt) != 1) return; + switch (cliptype_) + { + case ClipType::Union: + if (!IsHotEdge(*edge_c)) return; + break; + default: + if (edge_c->local_min->polytype == PathType::Subject) + return; + } - e.outrec = outrec; + switch (fillrule_) + { + case FillRule::Positive: + if (edge_c->wind_cnt != 1) return; + break; + case FillRule::Negative: + if (edge_c->wind_cnt != -1) return; + break; + default: + if (std::abs(edge_c->wind_cnt) != 1) return; + } - OutPt* op = new OutPt(pt, outrec); - outrec->pts = op; - return op; - } - - - inline void ClipperBase::UpdateEdgeIntoAEL(Active* e) - { - e->bot = e->top; - e->vertex_top = NextVertex(*e); - e->top = e->vertex_top->pt; - e->curr_x = e->bot.x; - SetDx(*e); - if (IsHorizontal(*e)) return; - InsertScanline(e->top.y); - if (TestJoinWithPrev1(*e)) - { - OutPt* op1 = AddOutPt(*e->prev_in_ael, e->bot); - OutPt* op2 = AddOutPt(*e, e->bot); - AddJoin(op1, op2); - } - } - - - Active* FindEdgeWithMatchingLocMin(Active* e) - { - Active* result = e->next_in_ael; - while (result) - { - if (result->local_min == e->local_min) return result; - else if (!IsHorizontal(*result) && e->bot != result->bot) result = nullptr; - else result = result->next_in_ael; - } - result = e->prev_in_ael; - while (result) - { - if (result->local_min == e->local_min) return result; - else if (!IsHorizontal(*result) && e->bot != result->bot) return nullptr; - else result = result->prev_in_ael; - } - return result; - } - - - OutPt* ClipperBase::IntersectEdges(Active& e1, Active& e2, const Point64& pt) - { - //MANAGE OPEN PATH INTERSECTIONS SEPARATELY ... - if (has_open_paths_ && (IsOpen(e1) || IsOpen(e2))) - { - if (IsOpen(e1) && IsOpen(e2)) return nullptr; - - Active* edge_o, * edge_c; - if (IsOpen(e1)) - { - edge_o = &e1; - edge_c = &e2; - } - else - { - edge_o = &e2; - edge_c = &e1; - } - - if (abs(edge_c->wind_cnt) != 1) return nullptr; - switch (cliptype_) - { - case ClipType::Union: - if (!IsHotEdge(*edge_c)) return nullptr; - break; - default: - if (edge_c->local_min->polytype == PathType::Subject) - return nullptr; - } - - switch (fillrule_) - { - case FillRule::Positive: if (edge_c->wind_cnt != 1) return nullptr; break; - case FillRule::Negative: if (edge_c->wind_cnt != -1) return nullptr; break; - default: if (std::abs(edge_c->wind_cnt) != 1) return nullptr; break; - } - - //toggle contribution ... - if (IsHotEdge(*edge_o)) - { - OutPt* resultOp = AddOutPt(*edge_o, pt); #ifdef USINGZ - if (zCallback_) SetZ(e1, e2, resultOp->pt); + OutPt* resultOp; #endif - if (IsFront(*edge_o)) edge_o->outrec->front_edge = nullptr; - else edge_o->outrec->back_edge = nullptr; - edge_o->outrec = nullptr; - return resultOp; - } - - //horizontal edges can pass under open paths at a LocMins - else if (pt == edge_o->local_min->vertex->pt && - !IsOpenEnd(*edge_o->local_min->vertex)) - { - //find the other side of the LocMin and - //if it's 'hot' join up with it ... - Active* e3 = FindEdgeWithMatchingLocMin(edge_o); - if (e3 && IsHotEdge(*e3)) - { - edge_o->outrec = e3->outrec; - if (edge_o->wind_dx > 0) - SetSides(*e3->outrec, *edge_o, *e3); - else - SetSides(*e3->outrec, *e3, *edge_o); - return e3->outrec->pts; - } - else - return StartOpenPath(*edge_o, pt); - } - else - return StartOpenPath(*edge_o, pt); - } - - - //MANAGING CLOSED PATHS FROM HERE ON - - //UPDATE WINDING COUNTS... - - int old_e1_windcnt, old_e2_windcnt; - if (e1.local_min->polytype == e2.local_min->polytype) - { - if (fillrule_ == FillRule::EvenOdd) - { - old_e1_windcnt = e1.wind_cnt; - e1.wind_cnt = e2.wind_cnt; - e2.wind_cnt = old_e1_windcnt; - } - else - { - if (e1.wind_cnt + e2.wind_dx == 0) - e1.wind_cnt = -e1.wind_cnt; - else - e1.wind_cnt += e2.wind_dx; - if (e2.wind_cnt - e1.wind_dx == 0) - e2.wind_cnt = -e2.wind_cnt; - else - e2.wind_cnt -= e1.wind_dx; - } - } - else - { - if (fillrule_ != FillRule::EvenOdd) - { - e1.wind_cnt2 += e2.wind_dx; - e2.wind_cnt2 -= e1.wind_dx; - } - else - { - e1.wind_cnt2 = (e1.wind_cnt2 == 0 ? 1 : 0); - e2.wind_cnt2 = (e2.wind_cnt2 == 0 ? 1 : 0); - } - } - - switch (fillrule_) - { - case FillRule::EvenOdd: - case FillRule::NonZero: - old_e1_windcnt = abs(e1.wind_cnt); - old_e2_windcnt = abs(e2.wind_cnt); - break; - default: - if (fillrule_ == fillpos) - { - old_e1_windcnt = e1.wind_cnt; - old_e2_windcnt = e2.wind_cnt; - } - else - { - old_e1_windcnt = -e1.wind_cnt; - old_e2_windcnt = -e2.wind_cnt; - } - break; - } - - const bool e1_windcnt_in_01 = old_e1_windcnt == 0 || old_e1_windcnt == 1; - const bool e2_windcnt_in_01 = old_e2_windcnt == 0 || old_e2_windcnt == 1; - - if ((!IsHotEdge(e1) && !e1_windcnt_in_01) || (!IsHotEdge(e2) && !e2_windcnt_in_01)) - { - return nullptr; - } - - //NOW PROCESS THE INTERSECTION ... - OutPt* resultOp = nullptr; - //if both edges are 'hot' ... - if (IsHotEdge(e1) && IsHotEdge(e2)) - { - if ((old_e1_windcnt != 0 && old_e1_windcnt != 1) || (old_e2_windcnt != 0 && old_e2_windcnt != 1) || - (e1.local_min->polytype != e2.local_min->polytype && cliptype_ != ClipType::Xor)) - { - resultOp = AddLocalMaxPoly(e1, e2, pt); + //toggle contribution ... + if (IsHotEdge(*edge_o)) + { #ifdef USINGZ - if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt); -#endif - } - else if (IsFront(e1) || (e1.outrec == e2.outrec)) - { - //this 'else if' condition isn't strictly needed but - //it's sensible to split polygons that ony touch at - //a common vertex (not at common edges). - - resultOp = AddLocalMaxPoly(e1, e2, pt); - OutPt* op2 = AddLocalMinPoly(e1, e2, pt); -#ifdef USINGZ - if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt); - if (zCallback_) SetZ(e1, e2, op2->pt); -#endif - if (resultOp && resultOp->pt == op2->pt && - !IsHorizontal(e1) && !IsHorizontal(e2) && - (CrossProduct(e1.bot, resultOp->pt, e2.bot) == 0)) - AddJoin(resultOp, op2); - } - else - { - resultOp = AddOutPt(e1, pt); -#ifdef USINGZ - OutPt* op2 = AddOutPt(e2, pt); - if (zCallback_) - { - SetZ(e1, e2, resultOp->pt); - SetZ(e1, e2, op2->pt); - } + resultOp = AddOutPt(*edge_o, pt); #else - AddOutPt(e2, pt); + AddOutPt(*edge_o, pt); #endif - SwapOutrecs(e1, e2); - } - } - else if (IsHotEdge(e1)) - { - resultOp = AddOutPt(e1, pt); + if (IsFront(*edge_o)) edge_o->outrec->front_edge = nullptr; + else edge_o->outrec->back_edge = nullptr; + edge_o->outrec = nullptr; + } + + //horizontal edges can pass under open paths at a LocMins + else if (pt == edge_o->local_min->vertex->pt && + !IsOpenEnd(*edge_o->local_min->vertex)) + { + //find the other side of the LocMin and + //if it's 'hot' join up with it ... + Active* e3 = FindEdgeWithMatchingLocMin(edge_o); + if (e3 && IsHotEdge(*e3)) + { + edge_o->outrec = e3->outrec; + if (edge_o->wind_dx > 0) + SetSides(*e3->outrec, *edge_o, *e3); + else + SetSides(*e3->outrec, *e3, *edge_o); + return; + } + else #ifdef USINGZ - if (zCallback_) SetZ(e1, e2, resultOp->pt); + resultOp = StartOpenPath(*edge_o, pt); +#else + StartOpenPath(*edge_o, pt); #endif - SwapOutrecs(e1, e2); - } - else if (IsHotEdge(e2)) - { - resultOp = AddOutPt(e2, pt); + } + else #ifdef USINGZ - if (zCallback_) SetZ(e1, e2, resultOp->pt); + resultOp = StartOpenPath(*edge_o, pt); +#else + StartOpenPath(*edge_o, pt); #endif - SwapOutrecs(e1, e2); - } - else - { - int64_t e1Wc2, e2Wc2; - switch (fillrule_) - { - case FillRule::EvenOdd: - case FillRule::NonZero: - e1Wc2 = abs(e1.wind_cnt2); - e2Wc2 = abs(e2.wind_cnt2); - break; - default: - if (fillrule_ == fillpos) - { - e1Wc2 = e1.wind_cnt2; - e2Wc2 = e2.wind_cnt2; - } - else - { - e1Wc2 = -e1.wind_cnt2; - e2Wc2 = -e2.wind_cnt2; - } - break; - } - if (!IsSamePolyType(e1, e2)) - { - resultOp = AddLocalMinPoly(e1, e2, pt, false); #ifdef USINGZ - if (zCallback_) SetZ(e1, e2, resultOp->pt); + if (zCallback_) SetZ(*edge_o, *edge_c, resultOp->pt); #endif - } - else if (old_e1_windcnt == 1 && old_e2_windcnt == 1) - { - resultOp = nullptr; - switch (cliptype_) - { - case ClipType::Union: - if (e1Wc2 <= 0 && e2Wc2 <= 0) - resultOp = AddLocalMinPoly(e1, e2, pt, false); - break; - case ClipType::Difference: - if (((GetPolyType(e1) == PathType::Clip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || - ((GetPolyType(e1) == PathType::Subject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) - { - resultOp = AddLocalMinPoly(e1, e2, pt, false); - } - break; - case ClipType::Xor: - resultOp = AddLocalMinPoly(e1, e2, pt, false); - break; - default: - if (e1Wc2 > 0 && e2Wc2 > 0) - resultOp = AddLocalMinPoly(e1, e2, pt, false); - break; - } + return; + } // end of an open path intersection + + //MANAGING CLOSED PATHS FROM HERE ON + + if (IsJoined(e1)) Split(e1, pt); + if (IsJoined(e2)) Split(e2, pt); + + //UPDATE WINDING COUNTS... + + int old_e1_windcnt, old_e2_windcnt; + if (e1.local_min->polytype == e2.local_min->polytype) + { + if (fillrule_ == FillRule::EvenOdd) + { + old_e1_windcnt = e1.wind_cnt; + e1.wind_cnt = e2.wind_cnt; + e2.wind_cnt = old_e1_windcnt; + } + else + { + if (e1.wind_cnt + e2.wind_dx == 0) + e1.wind_cnt = -e1.wind_cnt; + else + e1.wind_cnt += e2.wind_dx; + if (e2.wind_cnt - e1.wind_dx == 0) + e2.wind_cnt = -e2.wind_cnt; + else + e2.wind_cnt -= e1.wind_dx; + } + } + else + { + if (fillrule_ != FillRule::EvenOdd) + { + e1.wind_cnt2 += e2.wind_dx; + e2.wind_cnt2 -= e1.wind_dx; + } + else + { + e1.wind_cnt2 = (e1.wind_cnt2 == 0 ? 1 : 0); + e2.wind_cnt2 = (e2.wind_cnt2 == 0 ? 1 : 0); + } + } + + switch (fillrule_) + { + case FillRule::EvenOdd: + case FillRule::NonZero: + old_e1_windcnt = abs(e1.wind_cnt); + old_e2_windcnt = abs(e2.wind_cnt); + break; + default: + if (fillrule_ == fillpos) + { + old_e1_windcnt = e1.wind_cnt; + old_e2_windcnt = e2.wind_cnt; + } + else + { + old_e1_windcnt = -e1.wind_cnt; + old_e2_windcnt = -e2.wind_cnt; + } + break; + } + + const bool e1_windcnt_in_01 = old_e1_windcnt == 0 || old_e1_windcnt == 1; + const bool e2_windcnt_in_01 = old_e2_windcnt == 0 || old_e2_windcnt == 1; + + if ((!IsHotEdge(e1) && !e1_windcnt_in_01) || + (!IsHotEdge(e2) && !e2_windcnt_in_01)) + return; + + //NOW PROCESS THE INTERSECTION ... #ifdef USINGZ - if (resultOp && zCallback_) SetZ(e1, e2, resultOp->pt); + OutPt* resultOp = nullptr; #endif - } - } - return resultOp; - } - - - inline void ClipperBase::DeleteFromAEL(Active& e) - { - Active* prev = e.prev_in_ael; - Active* next = e.next_in_ael; - if (!prev && !next && (&e != actives_)) return; // already deleted - if (prev) - prev->next_in_ael = next; - else - actives_ = next; - if (next) next->prev_in_ael = prev; - delete& e; - } - - - inline void ClipperBase::AdjustCurrXAndCopyToSEL(const int64_t top_y) - { - Active* e = actives_; - sel_ = e; - while (e) - { - e->prev_in_sel = e->prev_in_ael; - e->next_in_sel = e->next_in_ael; - e->jump = e->next_in_sel; - e->curr_x = TopX(*e, top_y); - e = e->next_in_ael; - } - } - - - bool ClipperBase::ExecuteInternal(ClipType ct, FillRule fillrule, bool use_polytrees) - { - cliptype_ = ct; - fillrule_ = fillrule; - using_polytree_ = use_polytrees; - Reset(); - int64_t y; - if (ct == ClipType::None || !PopScanline(y)) return true; - - while (succeeded_) - { - InsertLocalMinimaIntoAEL(y); - Active* e; - while (PopHorz(e)) DoHorizontal(*e); - if (horz_joiners_) ConvertHorzTrialsToJoins(); - bot_y_ = y; // bot_y_ == bottom of scanbeam - if (!PopScanline(y)) break; // y new top of scanbeam - DoIntersections(y); - DoTopOfScanbeam(y); - while (PopHorz(e)) DoHorizontal(*e); - } - ProcessJoinerList(); - return succeeded_; - } - - void ClipperBase::DoIntersections(const int64_t top_y) - { - if (BuildIntersectList(top_y)) - { - ProcessIntersectList(); - intersect_nodes_.clear(); - } - } - - void ClipperBase::AddNewIntersectNode(Active& e1, Active& e2, int64_t top_y) - { - Point64 pt = GetIntersectPoint(e1, e2); - - //rounding errors can occasionally place the calculated intersection - //point either below or above the scanbeam, so check and correct ... - if (pt.y > bot_y_) - { - //e.curr.y is still the bottom of scanbeam - pt.y = bot_y_; - //use the more vertical of the 2 edges to derive pt.x ... - if (abs(e1.dx) < abs(e2.dx)) - pt.x = TopX(e1, bot_y_); - else - pt.x = TopX(e2, bot_y_); - } - else if (pt.y < top_y) - { - //top_y is at the top of the scanbeam - pt.y = top_y; - if (e1.top.y == top_y) - pt.x = e1.top.x; - else if (e2.top.y == top_y) - pt.x = e2.top.x; - else if (abs(e1.dx) < abs(e2.dx)) - pt.x = e1.curr_x; - else - pt.x = e2.curr_x; - } - - intersect_nodes_.push_back(IntersectNode(&e1, &e2, pt)); - } - - - bool ClipperBase::BuildIntersectList(const int64_t top_y) - { - if (!actives_ || !actives_->next_in_ael) return false; - - //Calculate edge positions at the top of the current scanbeam, and from this - //we will determine the intersections required to reach these new positions. - AdjustCurrXAndCopyToSEL(top_y); - //Find all edge intersections in the current scanbeam using a stable merge - //sort that ensures only adjacent edges are intersecting. Intersect info is - //stored in FIntersectList ready to be processed in ProcessIntersectList. - //Re merge sorts see https://stackoverflow.com/a/46319131/359538 - - Active* left = sel_, * right, * l_end, * r_end, * curr_base, * tmp; - - while (left && left->jump) - { - Active* prev_base = nullptr; - while (left && left->jump) - { - curr_base = left; - right = left->jump; - l_end = right; - r_end = right->jump; - left->jump = r_end; - while (left != l_end && right != r_end) - { - if (right->curr_x < left->curr_x) - { - tmp = right->prev_in_sel; - for (; ; ) - { - AddNewIntersectNode(*tmp, *right, top_y); - if (tmp == left) break; - tmp = tmp->prev_in_sel; - } - - tmp = right; - right = ExtractFromSEL(tmp); - l_end = right; - Insert1Before2InSEL(tmp, left); - if (left == curr_base) - { - curr_base = tmp; - curr_base->jump = r_end; - if (!prev_base) sel_ = curr_base; - else prev_base->jump = curr_base; - } - } - else left = left->next_in_sel; - } - prev_base = curr_base; - left = r_end; - } - left = sel_; - } - return intersect_nodes_.size() > 0; - } - - void ClipperBase::ProcessIntersectList() - { - //We now have a list of intersections required so that edges will be - //correctly positioned at the top of the scanbeam. However, it's important - //that edge intersections are processed from the bottom up, but it's also - //crucial that intersections only occur between adjacent edges. - - //First we do a quicksort so intersections proceed in a bottom up order ... - std::sort(intersect_nodes_.begin(), intersect_nodes_.end(), IntersectListSort); - //Now as we process these intersections, we must sometimes adjust the order - //to ensure that intersecting edges are always adjacent ... - - std::vector::iterator node_iter, node_iter2; - for (node_iter = intersect_nodes_.begin(); - node_iter != intersect_nodes_.end(); ++node_iter) - { - if (!EdgesAdjacentInAEL(*node_iter)) - { - node_iter2 = node_iter + 1; - while (!EdgesAdjacentInAEL(*node_iter2)) ++node_iter2; - std::swap(*node_iter, *node_iter2); - } - - IntersectNode& node = *node_iter; - IntersectEdges(*node.edge1, *node.edge2, node.pt); - SwapPositionsInAEL(*node.edge1, *node.edge2); - - if (TestJoinWithPrev2(*node.edge2, node.pt)) - { - OutPt* op1 = AddOutPt(*node.edge2->prev_in_ael, node.pt); - OutPt* op2 = AddOutPt(*node.edge2, node.pt); - if (op1 != op2) AddJoin(op1, op2); - } - else if (TestJoinWithNext2(*node.edge1, node.pt)) - { - OutPt* op1 = AddOutPt(*node.edge1, node.pt); - OutPt* op2 = AddOutPt(*node.edge1->next_in_ael, node.pt); - if (op1 != op2) AddJoin(op1, op2); - } - } - } - - - void ClipperBase::SwapPositionsInAEL(Active& e1, Active& e2) - { - //preconditon: e1 must be immediately to the left of e2 - Active* next = e2.next_in_ael; - if (next) next->prev_in_ael = &e1; - Active* prev = e1.prev_in_ael; - if (prev) prev->next_in_ael = &e2; - e2.prev_in_ael = prev; - e2.next_in_ael = &e1; - e1.prev_in_ael = &e2; - e1.next_in_ael = next; - if (!e2.prev_in_ael) actives_ = &e2; - } - - - bool ClipperBase::ResetHorzDirection(const Active& horz, - const Active* max_pair, int64_t& horz_left, int64_t& horz_right) - { - if (horz.bot.x == horz.top.x) - { - //the horizontal edge is going nowhere ... - horz_left = horz.curr_x; - horz_right = horz.curr_x; - Active* e = horz.next_in_ael; - while (e && e != max_pair) e = e->next_in_ael; - return e != nullptr; - } - else if (horz.curr_x < horz.top.x) - { - horz_left = horz.curr_x; - horz_right = horz.top.x; - return true; - } - else - { - horz_left = horz.top.x; - horz_right = horz.curr_x; - return false; // right to left - } - } - - inline bool HorzIsSpike(const Active& horzEdge) - { - Point64 nextPt = NextVertex(horzEdge)->pt; - return (nextPt.y == horzEdge.bot.y) && - (horzEdge.bot.x < horzEdge.top.x) != (horzEdge.top.x < nextPt.x); - } - - inline void TrimHorz(Active& horzEdge, bool preserveCollinear) - { - bool wasTrimmed = false; - Point64 pt = NextVertex(horzEdge)->pt; - while (pt.y == horzEdge.top.y) - { - //always trim 180 deg. spikes (in closed paths) - //but otherwise break if preserveCollinear = true - if (preserveCollinear && - ((pt.x < horzEdge.top.x) != (horzEdge.bot.x < horzEdge.top.x))) - break; - - horzEdge.vertex_top = NextVertex(horzEdge); - horzEdge.top = pt; - wasTrimmed = true; - if (IsMaxima(horzEdge)) break; - pt = NextVertex(horzEdge)->pt; - } - - if (wasTrimmed) SetDx(horzEdge); // +/-infinity - } - - - void ClipperBase::DoHorizontal(Active& horz) - /******************************************************************************* - * Notes: Horizontal edges (HEs) at scanline intersections (ie at the top or * - * bottom of a scanbeam) are processed as if layered.The order in which HEs * - * are processed doesn't matter. HEs intersect with the bottom vertices of * - * other HEs[#] and with non-horizontal edges [*]. Once these intersections * - * are completed, intermediate HEs are 'promoted' to the next edge in their * - * bounds, and they in turn may be intersected[%] by other HEs. * - * * - * eg: 3 horizontals at a scanline: / | / / * - * | / | (HE3)o ========%========== o * - * o ======= o(HE2) / | / / * - * o ============#=========*======*========#=========o (HE1) * - * / | / | / * - *******************************************************************************/ - { - Point64 pt; - bool horzIsOpen = IsOpen(horz); - int64_t y = horz.bot.y; - Vertex* vertex_max = nullptr; - Active* max_pair = nullptr; - - if (!horzIsOpen) - { - vertex_max = GetCurrYMaximaVertex(horz); - if (vertex_max) - { - max_pair = GetHorzMaximaPair(horz, vertex_max); - //remove 180 deg.spikes and also simplify - //consecutive horizontals when PreserveCollinear = true - if (vertex_max != horz.vertex_top) - TrimHorz(horz, PreserveCollinear); - } - } - - int64_t horz_left, horz_right; - bool is_left_to_right = - ResetHorzDirection(horz, max_pair, horz_left, horz_right); - - if (IsHotEdge(horz)) - AddOutPt(horz, Point64(horz.curr_x, y)); - - OutPt* op; - while (true) // loop through consec. horizontal edges - { - if (horzIsOpen && IsMaxima(horz) && !IsOpenEnd(horz)) - { - vertex_max = GetCurrYMaximaVertex(horz); - if (vertex_max) - max_pair = GetHorzMaximaPair(horz, vertex_max); - } - - Active* e; - if (is_left_to_right) e = horz.next_in_ael; - else e = horz.prev_in_ael; - - while (e) - { - - if (e == max_pair) - { - if (IsHotEdge(horz)) - { - while (horz.vertex_top != e->vertex_top) - { - AddOutPt(horz, horz.top); - UpdateEdgeIntoAEL(&horz); - } - op = AddLocalMaxPoly(horz, *e, horz.top); - if (op && !IsOpen(horz) && op->pt == horz.top) - AddTrialHorzJoin(op); - } - DeleteFromAEL(*e); - DeleteFromAEL(horz); - return; - } - - //if horzEdge is a maxima, keep going until we reach - //its maxima pair, otherwise check for break conditions - if (vertex_max != horz.vertex_top || IsOpenEnd(horz)) - { - //otherwise stop when 'ae' is beyond the end of the horizontal line - if ((is_left_to_right && e->curr_x > horz_right) || - (!is_left_to_right && e->curr_x < horz_left)) break; - - if (e->curr_x == horz.top.x && !IsHorizontal(*e)) - { - pt = NextVertex(horz)->pt; - if (is_left_to_right) - { - //with open paths we'll only break once past horz's end - if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e)) - { - if (TopX(*e, pt.y) > pt.x) break; - } - //otherwise we'll only break when horz's outslope is greater than e's - else if (TopX(*e, pt.y) >= pt.x) break; - } - else - { - if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e)) - { - if (TopX(*e, pt.y) < pt.x) break; - } - else if (TopX(*e, pt.y) <= pt.x) break; - } - } - } - - pt = Point64(e->curr_x, horz.bot.y); - - if (is_left_to_right) - { - op = IntersectEdges(horz, *e, pt); - SwapPositionsInAEL(horz, *e); - // todo: check if op->pt == pt test is still needed - // expect op != pt only after AddLocalMaxPoly when horz.outrec == nullptr - if (IsHotEdge(horz) && op && !IsOpen(horz) && op->pt == pt) - AddTrialHorzJoin(op); - - if (!IsHorizontal(*e) && TestJoinWithPrev1(*e)) - { - op = AddOutPt(*e->prev_in_ael, pt); - OutPt* op2 = AddOutPt(*e, pt); - AddJoin(op, op2); - } - - horz.curr_x = e->curr_x; - e = horz.next_in_ael; - } - else - { - op = IntersectEdges(*e, horz, pt); - SwapPositionsInAEL(*e, horz); - - if (IsHotEdge(horz) && op && - !IsOpen(horz) && op->pt == pt) - AddTrialHorzJoin(op); - - if (!IsHorizontal(*e) && TestJoinWithNext1(*e)) - { - op = AddOutPt(*e, pt); - OutPt* op2 = AddOutPt(*e->next_in_ael, pt); - AddJoin(op, op2); - } - - horz.curr_x = e->curr_x; - e = horz.prev_in_ael; - } - } - - //check if we've finished with (consecutive) horizontals ... - if (horzIsOpen && IsOpenEnd(horz)) // ie open at top - { - if (IsHotEdge(horz)) - { - AddOutPt(horz, horz.top); - if (IsFront(horz)) - horz.outrec->front_edge = nullptr; - else - horz.outrec->back_edge = nullptr; - horz.outrec = nullptr; - } - DeleteFromAEL(horz); - return; - } - else if (NextVertex(horz)->pt.y != horz.top.y) - break; - - //still more horizontals in bound to process ... - if (IsHotEdge(horz)) - AddOutPt(horz, horz.top); - UpdateEdgeIntoAEL(&horz); - - if (PreserveCollinear && !horzIsOpen && HorzIsSpike(horz)) - TrimHorz(horz, true); - - is_left_to_right = - ResetHorzDirection(horz, max_pair, horz_left, horz_right); - } - - if (IsHotEdge(horz)) - { - op = AddOutPt(horz, horz.top); - if (!IsOpen(horz)) - AddTrialHorzJoin(op); - } - else - op = nullptr; - - if ((horzIsOpen && !IsOpenEnd(horz)) || - (!horzIsOpen && vertex_max != horz.vertex_top)) - { - UpdateEdgeIntoAEL(&horz); // this is the end of an intermediate horiz. - if (IsOpen(horz)) return; - - if (is_left_to_right && TestJoinWithNext1(horz)) - { - OutPt* op2 = AddOutPt(*horz.next_in_ael, horz.bot); - AddJoin(op, op2); - } - else if (!is_left_to_right && TestJoinWithPrev1(horz)) - { - OutPt* op2 = AddOutPt(*horz.prev_in_ael, horz.bot); - AddJoin(op2, op); - } - } - else if (IsHotEdge(horz)) - AddLocalMaxPoly(horz, *max_pair, horz.top); - else - { - DeleteFromAEL(*max_pair); - DeleteFromAEL(horz); - } - } - - - void ClipperBase::DoTopOfScanbeam(const int64_t y) - { - sel_ = nullptr; // sel_ is reused to flag horizontals (see PushHorz below) - Active* e = actives_; - while (e) - { - //nb: 'e' will never be horizontal here - if (e->top.y == y) - { - e->curr_x = e->top.x; - if (IsMaxima(*e)) - { - e = DoMaxima(*e); // TOP OF BOUND (MAXIMA) - continue; - } - else - { - //INTERMEDIATE VERTEX ... - if (IsHotEdge(*e)) AddOutPt(*e, e->top); - UpdateEdgeIntoAEL(e); - if (IsHorizontal(*e)) - PushHorz(*e); // horizontals are processed later - } - } - else // i.e. not the top of the edge - e->curr_x = TopX(*e, y); - - e = e->next_in_ael; - } - } - - - Active* ClipperBase::DoMaxima(Active& e) - { - Active* next_e, * prev_e, * max_pair; - prev_e = e.prev_in_ael; - next_e = e.next_in_ael; - if (IsOpenEnd(e)) - { - if (IsHotEdge(e)) AddOutPt(e, e.top); - if (!IsHorizontal(e)) - { - if (IsHotEdge(e)) - { - if (IsFront(e)) - e.outrec->front_edge = nullptr; - else - e.outrec->back_edge = nullptr; - e.outrec = nullptr; - } - DeleteFromAEL(e); - } - return next_e; - } - else - { - max_pair = GetMaximaPair(e); - if (!max_pair) return next_e; // eMaxPair is horizontal - } - - //only non-horizontal maxima here. - //process any edges between maxima pair ... - while (next_e != max_pair) - { - IntersectEdges(e, *next_e, e.top); - SwapPositionsInAEL(e, *next_e); - next_e = e.next_in_ael; - } - - if (IsOpen(e)) - { - if (IsHotEdge(e)) - AddLocalMaxPoly(e, *max_pair, e.top); - DeleteFromAEL(*max_pair); - DeleteFromAEL(e); - return (prev_e ? prev_e->next_in_ael : actives_); - } - - //here E.next_in_ael == ENext == EMaxPair ... - if (IsHotEdge(e)) - AddLocalMaxPoly(e, *max_pair, e.top); - - DeleteFromAEL(e); - DeleteFromAEL(*max_pair); - return (prev_e ? prev_e->next_in_ael : actives_); - } - - - void ClipperBase::SafeDeleteOutPtJoiners(OutPt* op) - { - Joiner* joiner = op->joiner; - if (!joiner) return; - - while (joiner) - { - if (joiner->idx < 0) - DeleteTrialHorzJoin(op); - else if (horz_joiners_) - { - if (OutPtInTrialHorzList(joiner->op1)) - DeleteTrialHorzJoin(joiner->op1); - if (OutPtInTrialHorzList(joiner->op2)) - DeleteTrialHorzJoin(joiner->op2); - DeleteJoin(joiner); - } - else - DeleteJoin(joiner); - joiner = op->joiner; - } - } - - - Joiner* ClipperBase::GetHorzTrialParent(const OutPt* op) - { - Joiner* joiner = op->joiner; - while (joiner) - { - if (joiner->op1 == op) - { - if (joiner->next1 && joiner->next1->idx < 0) return joiner; - else joiner = joiner->next1; - } - else - { - if (joiner->next2 && joiner->next2->idx < 0) return joiner; - else joiner = joiner->next1; - } - } - return joiner; - } - - - bool ClipperBase::OutPtInTrialHorzList(OutPt* op) - { - return op->joiner && ((op->joiner->idx < 0) || GetHorzTrialParent(op)); - } - - - void ClipperBase::AddTrialHorzJoin(OutPt* op) - { - //make sure 'op' isn't added more than once - if (!op->outrec->is_open && !OutPtInTrialHorzList(op)) - horz_joiners_ = new Joiner(op, nullptr, horz_joiners_); - } - - - Joiner* FindTrialJoinParent(Joiner*& joiner, const OutPt* op) - { - Joiner* parent = joiner; - while (parent) - { - if (op == parent->op1) - { - if (parent->next1 && parent->next1->idx < 0) - { - joiner = parent->next1; - return parent; - } - parent = parent->next1; - } - else - { - if (parent->next2 && parent->next2->idx < 0) - { - joiner = parent->next2; - return parent; - } - parent = parent->next2; - } - } - return nullptr; - } - - - void ClipperBase::DeleteTrialHorzJoin(OutPt* op) - { - if (!horz_joiners_) return; - - Joiner* joiner = op->joiner; - Joiner* parentH, * parentOp = nullptr; - while (joiner) - { - if (joiner->idx < 0) - { - //first remove joiner from FHorzTrials - if (joiner == horz_joiners_) - horz_joiners_ = joiner->nextH; - else - { - parentH = horz_joiners_; - while (parentH->nextH != joiner) - parentH = parentH->nextH; - parentH->nextH = joiner->nextH; - } - - //now remove joiner from op's joiner list - if (!parentOp) - { - //joiner must be first one in list - op->joiner = joiner->next1; - delete joiner; - joiner = op->joiner; - } - else - { - //the trial joiner isn't first - if (op == parentOp->op1) - parentOp->next1 = joiner->next1; - else - parentOp->next2 = joiner->next1; - delete joiner; - joiner = parentOp; - } - } - else - { - //not a trial join so look further along the linked list - parentOp = FindTrialJoinParent(joiner, op); - if (!parentOp) break; - } - //loop in case there's more than one trial join - } - } - - - inline bool GetHorzExtendedHorzSeg(OutPt*& op, OutPt*& op2) - { - OutRec* outrec = GetRealOutRec(op->outrec); - op2 = op; - if (outrec->front_edge) - { - while (op->prev != outrec->pts && - op->prev->pt.y == op->pt.y) op = op->prev; - while (op2 != outrec->pts && - op2->next->pt.y == op2->pt.y) op2 = op2->next; - return op2 != op; - } - else - { - while (op->prev != op2 && op->prev->pt.y == op->pt.y) - op = op->prev; - while (op2->next != op && op2->next->pt.y == op2->pt.y) - op2 = op2->next; - return op2 != op && op2->next != op; - } - } - - - inline bool HorzEdgesOverlap(int64_t x1a, int64_t x1b, int64_t x2a, int64_t x2b) - { - const int64_t minOverlap = 2; - if (x1a > x1b + minOverlap) - { - if (x2a > x2b + minOverlap) - return !((x1a <= x2b) || (x2a <= x1b)); - else - return !((x1a <= x2a) || (x2b <= x1b)); - } - else if (x1b > x1a + minOverlap) - { - if (x2a > x2b + minOverlap) - return !((x1b <= x2b) || (x2a <= x1a)); - else - return !((x1b <= x2a) || (x2b <= x1a)); - } - else - return false; - } - - - inline bool ValueBetween(int64_t val, int64_t end1, int64_t end2) - { - //NB accommodates axis aligned between where end1 == end2 - return ((val != end1) == (val != end2)) && - ((val > end1) == (val < end2)); - } - - - inline bool ValueEqualOrBetween(int64_t val, int64_t end1, int64_t end2) - { - return (val == end1) || (val == end2) || ((val > end1) == (val < end2)); - } - - - inline bool PointBetween(Point64 pt, Point64 corner1, Point64 corner2) - { - //NB points may not be collinear - return ValueBetween(pt.x, corner1.x, corner2.x) && - ValueBetween(pt.y, corner1.y, corner2.y); - } - - inline bool PointEqualOrBetween(Point64 pt, Point64 corner1, Point64 corner2) - { - //NB points may not be collinear - return ValueEqualOrBetween(pt.x, corner1.x, corner2.x) && - ValueEqualOrBetween(pt.y, corner1.y, corner2.y); - } - - - Joiner* FindJoinParent(const Joiner* joiner, OutPt* op) - { - Joiner* result = op->joiner; - for (; ; ) - { - if (op == result->op1) - { - if (result->next1 == joiner) return result; - else result = result->next1; - } - else - { - if (result->next2 == joiner) return result; - else result = result->next2; - } - } - } - - - void ClipperBase::ConvertHorzTrialsToJoins() - { - while (horz_joiners_) - { - Joiner* joiner = horz_joiners_; - horz_joiners_ = horz_joiners_->nextH; - OutPt* op1a = joiner->op1; - if (op1a->joiner == joiner) - { - op1a->joiner = joiner->next1; - } - else - { - Joiner* joinerParent = FindJoinParent(joiner, op1a); - if (joinerParent->op1 == op1a) - joinerParent->next1 = joiner->next1; - else - joinerParent->next2 = joiner->next1; - } - delete joiner; - - OutPt* op1b; - if (!GetHorzExtendedHorzSeg(op1a, op1b)) - { - CleanCollinear(op1a->outrec); - continue; - } - - bool joined = false; - joiner = horz_joiners_; - while (joiner) - { - OutPt* op2a = joiner->op1, * op2b; - if (GetHorzExtendedHorzSeg(op2a, op2b) && - HorzEdgesOverlap(op1a->pt.x, op1b->pt.x, op2a->pt.x, op2b->pt.x)) - { - //overlap found so promote to a 'real' join - joined = true; - if (op1a->pt == op2b->pt) - AddJoin(op1a, op2b); - else if (op1b->pt == op2a->pt) - AddJoin(op1b, op2a); - else if (op1a->pt == op2a->pt) - AddJoin(op1a, op2a); - else if (op1b->pt == op2b->pt) - AddJoin(op1b, op2b); - else if (ValueBetween(op1a->pt.x, op2a->pt.x, op2b->pt.x)) - AddJoin(op1a, InsertOp(op1a->pt, op2a)); - else if (ValueBetween(op1b->pt.x, op2a->pt.x, op2b->pt.x)) - AddJoin(op1b, InsertOp(op1b->pt, op2a)); - else if (ValueBetween(op2a->pt.x, op1a->pt.x, op1b->pt.x)) - AddJoin(op2a, InsertOp(op2a->pt, op1a)); - else if (ValueBetween(op2b->pt.x, op1a->pt.x, op1b->pt.x)) - AddJoin(op2b, InsertOp(op2b->pt, op1a)); - break; - } - joiner = joiner->nextH; - } - if (!joined) - CleanCollinear(op1a->outrec); - } - } - - - void ClipperBase::AddJoin(OutPt* op1, OutPt* op2) - { - if ((op1->outrec == op2->outrec) && ((op1 == op2) || - //unless op1.next or op1.prev crosses the start-end divide - //don't waste time trying to join adjacent vertices - ((op1->next == op2) && (op1 != op1->outrec->pts)) || - ((op2->next == op1) && (op2 != op1->outrec->pts)))) return; - - Joiner* j = new Joiner(op1, op2, nullptr); - j->idx = static_cast(joiner_list_.size()); - joiner_list_.push_back(j); - } - - - void ClipperBase::DeleteJoin(Joiner* joiner) - { - //This method deletes a single join, and it doesn't check for or - //delete trial horz. joins. For that, use the following method. - OutPt* op1 = joiner->op1, * op2 = joiner->op2; - - Joiner* parent_joiner; - if (op1->joiner != joiner) - { - parent_joiner = FindJoinParent(joiner, op1); - if (parent_joiner->op1 == op1) - parent_joiner->next1 = joiner->next1; - else - parent_joiner->next2 = joiner->next1; - } - else - op1->joiner = joiner->next1; - - if (op2->joiner != joiner) - { - parent_joiner = FindJoinParent(joiner, op2); - if (parent_joiner->op1 == op2) - parent_joiner->next1 = joiner->next2; - else - parent_joiner->next2 = joiner->next2; - } - else - op2->joiner = joiner->next2; - - joiner_list_[joiner->idx] = nullptr; - delete joiner; - } - - - void ClipperBase::ProcessJoinerList() - { - for (Joiner* j : joiner_list_) - { - if (!j) continue; - if (succeeded_) - { - OutRec* outrec = ProcessJoin(j); - CleanCollinear(outrec); - } - else - delete j; - } - - joiner_list_.resize(0); - } - - - bool CheckDisposeAdjacent(OutPt*& op, const OutPt* guard, OutRec& outRec) - { - bool result = false; - while (op->prev != op) - { - if (op->pt == op->prev->pt && op != guard && - op->prev->joiner && !op->joiner) - { - if (op == outRec.pts) outRec.pts = op->prev; - op = DisposeOutPt(op); - op = op->prev; - } - else - break; - } - - while (op->next != op) - { - if (op->pt == op->next->pt && op != guard && - op->next->joiner && !op->joiner) - { - if (op == outRec.pts) outRec.pts = op->prev; - op = DisposeOutPt(op); - op = op->prev; - } - else - break; - } - return result; - } - - - inline bool IsValidPath(OutPt* op) - { - return (op && op->next != op); - } - - - bool CollinearSegsOverlap(const Point64& seg1a, const Point64& seg1b, - const Point64& seg2a, const Point64& seg2b) - { - //precondition: seg1 and seg2 are collinear - if (seg1a.x == seg1b.x) - { - if (seg2a.x != seg1a.x || seg2a.x != seg2b.x) return false; - } - else if (seg1a.x < seg1b.x) - { - if (seg2a.x < seg2b.x) - { - if (seg2a.x >= seg1b.x || seg2b.x <= seg1a.x) return false; - } - else - { - if (seg2b.x >= seg1b.x || seg2a.x <= seg1a.x) return false; - } - } - else - { - if (seg2a.x < seg2b.x) - { - if (seg2a.x >= seg1a.x || seg2b.x <= seg1b.x) return false; - } - else - { - if (seg2b.x >= seg1a.x || seg2a.x <= seg1b.x) return false; - } - } - - if (seg1a.y == seg1b.y) - { - if (seg2a.y != seg1a.y || seg2a.y != seg2b.y) return false; - } - else if (seg1a.y < seg1b.y) - { - if (seg2a.y < seg2b.y) - { - if (seg2a.y >= seg1b.y || seg2b.y <= seg1a.y) return false; - } - else - { - if (seg2b.y >= seg1b.y || seg2a.y <= seg1a.y) return false; - } - } - else - { - if (seg2a.y < seg2b.y) - { - if (seg2a.y >= seg1a.y || seg2b.y <= seg1b.y) return false; - } - else - { - if (seg2b.y >= seg1a.y || seg2a.y <= seg1b.y) return false; - } - } - return true; - } - - OutRec* ClipperBase::ProcessJoin(Joiner* joiner) - { - OutPt* op1 = joiner->op1, * op2 = joiner->op2; - OutRec* or1 = GetRealOutRec(op1->outrec); - OutRec* or2 = GetRealOutRec(op2->outrec); - DeleteJoin(joiner); - - if (or2->pts == nullptr) return or1; - else if (!IsValidClosedPath(op2)) - { - SafeDisposeOutPts(op2); - return or1; - } - else if ((or1->pts == nullptr) || !IsValidClosedPath(op1)) - { - SafeDisposeOutPts(op1); - return or2; - } - else if (or1 == or2 && - ((op1 == op2) || (op1->next == op2) || (op1->prev == op2))) return or1; - - CheckDisposeAdjacent(op1, op2, *or1); - CheckDisposeAdjacent(op2, op1, *or2); - if (op1->next == op2 || op2->next == op1) return or1; - OutRec* result = or1; - - for (; ; ) - { - if (!IsValidPath(op1) || !IsValidPath(op2) || - (or1 == or2 && (op1->prev == op2 || op1->next == op2))) return or1; - - if (op1->prev->pt == op2->next->pt || - ((CrossProduct(op1->prev->pt, op1->pt, op2->next->pt) == 0) && - CollinearSegsOverlap(op1->prev->pt, op1->pt, op2->pt, op2->next->pt))) - { - if (or1 == or2) - { - //SPLIT REQUIRED - //make sure op1.prev and op2.next match positions - //by inserting an extra vertex if needed - if (op1->prev->pt != op2->next->pt) - { - if (PointEqualOrBetween(op1->prev->pt, op2->pt, op2->next->pt)) - op2->next = InsertOp(op1->prev->pt, op2); - else - op1->prev = InsertOp(op2->next->pt, op1->prev); - } - - //current to new - //op1.p[opA] >>> op1 ... opA \ / op1 - //op2.n[opB] <<< op2 ... opB / \ op2 - OutPt* opA = op1->prev, * opB = op2->next; - opA->next = opB; - opB->prev = opA; - op1->prev = op2; - op2->next = op1; - CompleteSplit(op1, opA, *or1); - } - else - { - //JOIN, NOT SPLIT - OutPt* opA = op1->prev, * opB = op2->next; - opA->next = opB; - opB->prev = opA; - op1->prev = op2; - op2->next = op1; - - //SafeDeleteOutPtJoiners(op2); - //DisposeOutPt(op2); - - if (or1->idx < or2->idx) - { - or1->pts = op1; - or2->pts = nullptr; - if (or1->owner && (!or2->owner || - or2->owner->idx < or1->owner->idx)) - or1->owner = or2->owner; - or2->owner = or1; - } - else - { - result = or2; - or2->pts = op1; - or1->pts = nullptr; - if (or2->owner && (!or1->owner || - or1->owner->idx < or2->owner->idx)) - or2->owner = or1->owner; - or1->owner = or2; - } - } - break; - } - else if (op1->next->pt == op2->prev->pt || - ((CrossProduct(op1->next->pt, op2->pt, op2->prev->pt) == 0) && - CollinearSegsOverlap(op1->next->pt, op1->pt, op2->pt, op2->prev->pt))) - { - if (or1 == or2) - { - //SPLIT REQUIRED - //make sure op2.prev and op1.next match positions - //by inserting an extra vertex if needed - if (op2->prev->pt != op1->next->pt) - { - if (PointEqualOrBetween(op2->prev->pt, op1->pt, op1->next->pt)) - op1->next = InsertOp(op2->prev->pt, op1); - else - op2->prev = InsertOp(op1->next->pt, op2->prev); - } - - //current to new - //op2.p[opA] >>> op2 ... opA \ / op2 - //op1.n[opB] <<< op1 ... opB / \ op1 - OutPt* opA = op2->prev, * opB = op1->next; - opA->next = opB; - opB->prev = opA; - op2->prev = op1; - op1->next = op2; - CompleteSplit(op1, opA, *or1); - } - else - { - //JOIN, NOT SPLIT - OutPt* opA = op1->next, * opB = op2->prev; - opA->prev = opB; - opB->next = opA; - op1->next = op2; - op2->prev = op1; - - //SafeDeleteOutPtJoiners(op2); - //DisposeOutPt(op2); - - if (or1->idx < or2->idx) - { - or1->pts = op1; - or2->pts = nullptr; - if (or1->owner && (!or2->owner || - or2->owner->idx < or1->owner->idx)) - or1->owner = or2->owner; - or2->owner = or1; - } - else - { - result = or2; - or2->pts = op1; - or1->pts = nullptr; - if (or2->owner && (!or1->owner || - or1->owner->idx < or2->owner->idx)) - or2->owner = or1->owner; - or1->owner = or2; - } - } - break; - } - else if (PointBetween(op1->next->pt, op2->pt, op2->prev->pt) && - DistanceFromLineSqrd(op1->next->pt, op2->pt, op2->prev->pt) < 2.01) - { - InsertOp(op1->next->pt, op2->prev); - continue; - } - else if (PointBetween(op2->next->pt, op1->pt, op1->prev->pt) && - DistanceFromLineSqrd(op2->next->pt, op1->pt, op1->prev->pt) < 2.01) - { - InsertOp(op2->next->pt, op1->prev); - continue; - } - else if (PointBetween(op1->prev->pt, op2->pt, op2->next->pt) && - DistanceFromLineSqrd(op1->prev->pt, op2->pt, op2->next->pt) < 2.01) - { - InsertOp(op1->prev->pt, op2); - continue; - } - else if (PointBetween(op2->prev->pt, op1->pt, op1->next->pt) && - DistanceFromLineSqrd(op2->prev->pt, op1->pt, op1->next->pt) < 2.01) - { - InsertOp(op2->prev->pt, op1); - continue; - } - - //something odd needs tidying up - if (CheckDisposeAdjacent(op1, op2, *or1)) continue; - else if (CheckDisposeAdjacent(op2, op1, *or1)) continue; - else if (op1->prev->pt != op2->next->pt && - (DistanceSqr(op1->prev->pt, op2->next->pt) < 2.01)) - { - op1->prev->pt = op2->next->pt; - continue; - } - else if (op1->next->pt != op2->prev->pt && - (DistanceSqr(op1->next->pt, op2->prev->pt) < 2.01)) - { - op2->prev->pt = op1->next->pt; - continue; - } - else - { - //OK, there doesn't seem to be a way to join after all - //so just tidy up the polygons - or1->pts = op1; - if (or2 != or1) - { - or2->pts = op2; - CleanCollinear(or2); - } - break; - } - } - return result; - - } - - inline bool Path1InsidePath2(const OutRec* or1, const OutRec* or2) - { - PointInPolygonResult result = PointInPolygonResult::IsOn; - OutPt* op = or1->pts; - do - { - result = PointInPolygon(op->pt, or2->path); - if (result != PointInPolygonResult::IsOn) break; - op = op->next; - } while (op != or1->pts); - if (result == PointInPolygonResult::IsOn) - return Area(op) < Area(or2->pts); - else - return result == PointInPolygonResult::IsInside; - } - - inline Rect64 GetBounds(const Path64& path) - { - if (path.empty()) return Rect64(); - Rect64 result = invalid_rect; - for(const Point64& pt : path) - { - if (pt.x < result.left) result.left = pt.x; - if (pt.x > result.right) result.right = pt.x; - if (pt.y < result.top) result.top = pt.y; - if (pt.y > result.bottom) result.bottom = pt.y; - } - return result; - } - - bool BuildPath64(OutPt* op, bool reverse, bool isOpen, Path64& path) - { - if (op->next == op || (!isOpen && op->next == op->prev)) - return false; - - path.resize(0); - Point64 lastPt; - OutPt* op2; - if (reverse) - { - lastPt = op->pt; - op2 = op->prev; - } - else - { - op = op->next; - lastPt = op->pt; - op2 = op->next; - } - path.push_back(lastPt); - - while (op2 != op) - { - if (op2->pt != lastPt) - { - lastPt = op2->pt; - path.push_back(lastPt); - } - if (reverse) - op2 = op2->prev; - else - op2 = op2->next; - } - - if (path.size() == 3 && IsVerySmallTriangle(*op2)) return false; - else return true; - } - - bool ClipperBase::DeepCheckOwner(OutRec* outrec, OutRec* owner) - { - if (owner->bounds.IsEmpty()) owner->bounds = GetBounds(owner->path); - bool is_inside_owner_bounds = owner->bounds.Contains(outrec->bounds); - - // while looking for the correct owner, check the owner's - // splits **before** checking the owner itself because - // splits can occur internally, and checking the owner - // first would miss the inner split's true ownership - if (owner->splits) - { - for (OutRec* split : *owner->splits) - { - split = GetRealOutRec(split); - if (!split || split->idx <= owner->idx || split == outrec) continue; - - if (split->splits && DeepCheckOwner(outrec, split)) return true; - - if (!split->path.size()) - BuildPath64(split->pts, ReverseSolution, false, split->path); - if (split->bounds.IsEmpty()) split->bounds = GetBounds(split->path); - - if (split->bounds.Contains(outrec->bounds) && - Path1InsidePath2(outrec, split)) - { - outrec->owner = split; - return true; - } - } - } - - // only continue past here when not inside recursion - if (owner != outrec->owner) return false; - - for (;;) - { - if (is_inside_owner_bounds && Path1InsidePath2(outrec, outrec->owner)) - return true; - // otherwise keep trying with owner's owner - outrec->owner = outrec->owner->owner; - if (!outrec->owner) return true; // true or false - is_inside_owner_bounds = outrec->owner->bounds.Contains(outrec->bounds); - } - } - - void Clipper64::BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen) - { - solutionClosed.resize(0); - solutionClosed.reserve(outrec_list_.size()); - if (solutionOpen) - { - solutionOpen->resize(0); - solutionOpen->reserve(outrec_list_.size()); - } - - for (OutRec* outrec : outrec_list_) - { - if (outrec->pts == nullptr) continue; - - Path64 path; - if (solutionOpen && outrec->is_open) - { - if (BuildPath64(outrec->pts, ReverseSolution, true, path)) - solutionOpen->emplace_back(std::move(path)); - } - else - { - //closed paths should always return a Positive orientation - if (BuildPath64(outrec->pts, ReverseSolution, false, path)) - solutionClosed.emplace_back(std::move(path)); - } - } - } - - void Clipper64::BuildTree64(PolyPath64& polytree, Paths64& open_paths) - { - polytree.Clear(); - open_paths.resize(0); - if (has_open_paths_) - open_paths.reserve(outrec_list_.size()); - - for (OutRec* outrec : outrec_list_) - { - if (!outrec || !outrec->pts) continue; - if (outrec->is_open) - { - Path64 path; - if (BuildPath64(outrec->pts, ReverseSolution, true, path)) - open_paths.push_back(path); - continue; - } - - if (!BuildPath64(outrec->pts, ReverseSolution, false, outrec->path)) - continue; - if (outrec->bounds.IsEmpty()) outrec->bounds = GetBounds(outrec->path); - outrec->owner = GetRealOutRec(outrec->owner); - if (outrec->owner) DeepCheckOwner(outrec, outrec->owner); - - // swap the order when a child preceeds its owner - // (because owners must preceed children in polytrees) - if (outrec->owner && outrec->idx < outrec->owner->idx) - { - OutRec* tmp = outrec->owner; - outrec_list_[outrec->owner->idx] = outrec; - outrec_list_[outrec->idx] = tmp; - size_t tmp_idx = outrec->idx; - outrec->idx = tmp->idx; - tmp->idx = tmp_idx; - outrec = tmp; - outrec->owner = GetRealOutRec(outrec->owner); - BuildPath64(outrec->pts, ReverseSolution, false, outrec->path); - if (outrec->bounds.IsEmpty()) outrec->bounds = GetBounds(outrec->path); - if (outrec->owner) DeepCheckOwner(outrec, outrec->owner); - } - - PolyPath* owner_polypath; - if (outrec->owner && outrec->owner->polypath) - owner_polypath = outrec->owner->polypath; - else - owner_polypath = &polytree; - outrec->polypath = owner_polypath->AddChild(outrec->path); - } - } - - bool BuildPathD(OutPt* op, bool reverse, bool isOpen, PathD& path, double inv_scale) - { - if (op->next == op || (!isOpen && op->next == op->prev)) return false; - path.resize(0); - Point64 lastPt; - OutPt* op2; - if (reverse) - { - lastPt = op->pt; - op2 = op->prev; - } - else - { - op = op->next; - lastPt = op->pt; - op2 = op->next; - } - path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale)); - - while (op2 != op) - { - if (op2->pt != lastPt) - { - lastPt = op2->pt; - path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale)); - } - if (reverse) - op2 = op2->prev; - else - op2 = op2->next; - } - if (path.size() == 3 && IsVerySmallTriangle(*op2)) return false; - return true; - } - - void ClipperD::BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen) - { - solutionClosed.resize(0); - solutionClosed.reserve(outrec_list_.size()); - if (solutionOpen) - { - solutionOpen->resize(0); - solutionOpen->reserve(outrec_list_.size()); - } - - for (OutRec* outrec : outrec_list_) - { - if (outrec->pts == nullptr) continue; - - PathD path; - if (solutionOpen && outrec->is_open) - { - if (BuildPathD(outrec->pts, ReverseSolution, true, path, invScale_)) - solutionOpen->emplace_back(std::move(path)); - } - else - { - //closed paths should always return a Positive orientation - if (BuildPathD(outrec->pts, ReverseSolution, false, path, invScale_)) - solutionClosed.emplace_back(std::move(path)); - } - } - } - - void ClipperD::BuildTreeD(PolyPathD& polytree, PathsD& open_paths) - { - polytree.Clear(); - open_paths.resize(0); - if (has_open_paths_) - open_paths.reserve(outrec_list_.size()); - - for (OutRec* outrec : outrec_list_) - { - if (!outrec || !outrec->pts) continue; - if (outrec->is_open) - { - PathD path; - if (BuildPathD(outrec->pts, ReverseSolution, true, path, invScale_)) - open_paths.push_back(path); - continue; - } - - if (!BuildPath64(outrec->pts, ReverseSolution, false, outrec->path)) - continue; - if (outrec->bounds.IsEmpty()) outrec->bounds = GetBounds(outrec->path); - outrec->owner = GetRealOutRec(outrec->owner); - if (outrec->owner) DeepCheckOwner(outrec, outrec->owner); - - // swap the order when a child preceeds its owner - // (because owners must preceed children in polytrees) - if (outrec->owner && outrec->idx < outrec->owner->idx) - { - OutRec* tmp = outrec->owner; - outrec_list_[outrec->owner->idx] = outrec; - outrec_list_[outrec->idx] = tmp; - size_t tmp_idx = outrec->idx; - outrec->idx = tmp->idx; - tmp->idx = tmp_idx; - outrec = tmp; - outrec->owner = GetRealOutRec(outrec->owner); - BuildPath64(outrec->pts, ReverseSolution, false, outrec->path); - if (outrec->bounds.IsEmpty()) outrec->bounds = GetBounds(outrec->path); - if (outrec->owner) DeepCheckOwner(outrec, outrec->owner); - } - - PolyPath* owner_polypath; - if (outrec->owner && outrec->owner->polypath) - owner_polypath = outrec->owner->polypath; - else - owner_polypath = &polytree; - outrec->polypath = owner_polypath->AddChild(outrec->path); - } - } + //if both edges are 'hot' ... + if (IsHotEdge(e1) && IsHotEdge(e2)) + { + if ((old_e1_windcnt != 0 && old_e1_windcnt != 1) || (old_e2_windcnt != 0 && old_e2_windcnt != 1) || + (e1.local_min->polytype != e2.local_min->polytype && cliptype_ != ClipType::Xor)) + { +#ifdef USINGZ + resultOp = AddLocalMaxPoly(e1, e2, pt); + if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt); +#else + AddLocalMaxPoly(e1, e2, pt); +#endif + } + else if (IsFront(e1) || (e1.outrec == e2.outrec)) + { + //this 'else if' condition isn't strictly needed but + //it's sensible to split polygons that only touch at + //a common vertex (not at common edges). + +#ifdef USINGZ + resultOp = AddLocalMaxPoly(e1, e2, pt); + OutPt* op2 = AddLocalMinPoly(e1, e2, pt); + if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt); + if (zCallback_) SetZ(e1, e2, op2->pt); +#else + AddLocalMaxPoly(e1, e2, pt); + AddLocalMinPoly(e1, e2, pt); +#endif + } + else + { +#ifdef USINGZ + resultOp = AddOutPt(e1, pt); + OutPt* op2 = AddOutPt(e2, pt); + if (zCallback_) + { + SetZ(e1, e2, resultOp->pt); + SetZ(e1, e2, op2->pt); + } +#else + AddOutPt(e1, pt); + AddOutPt(e2, pt); +#endif + SwapOutrecs(e1, e2); + } + } + else if (IsHotEdge(e1)) + { +#ifdef USINGZ + resultOp = AddOutPt(e1, pt); + if (zCallback_) SetZ(e1, e2, resultOp->pt); +#else + AddOutPt(e1, pt); +#endif + SwapOutrecs(e1, e2); + } + else if (IsHotEdge(e2)) + { +#ifdef USINGZ + resultOp = AddOutPt(e2, pt); + if (zCallback_) SetZ(e1, e2, resultOp->pt); +#else + AddOutPt(e2, pt); +#endif + SwapOutrecs(e1, e2); + } + else + { + int64_t e1Wc2, e2Wc2; + switch (fillrule_) + { + case FillRule::EvenOdd: + case FillRule::NonZero: + e1Wc2 = abs(e1.wind_cnt2); + e2Wc2 = abs(e2.wind_cnt2); + break; + default: + if (fillrule_ == fillpos) + { + e1Wc2 = e1.wind_cnt2; + e2Wc2 = e2.wind_cnt2; + } + else + { + e1Wc2 = -e1.wind_cnt2; + e2Wc2 = -e2.wind_cnt2; + } + break; + } + + if (!IsSamePolyType(e1, e2)) + { +#ifdef USINGZ + resultOp = AddLocalMinPoly(e1, e2, pt, false); + if (zCallback_) SetZ(e1, e2, resultOp->pt); +#else + AddLocalMinPoly(e1, e2, pt, false); +#endif + } + else if (old_e1_windcnt == 1 && old_e2_windcnt == 1) + { +#ifdef USINGZ + resultOp = nullptr; +#endif + switch (cliptype_) + { + case ClipType::Union: + if (e1Wc2 <= 0 && e2Wc2 <= 0) +#ifdef USINGZ + resultOp = AddLocalMinPoly(e1, e2, pt, false); +#else + AddLocalMinPoly(e1, e2, pt, false); +#endif + break; + case ClipType::Difference: + if (((GetPolyType(e1) == PathType::Clip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || + ((GetPolyType(e1) == PathType::Subject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) + { +#ifdef USINGZ + resultOp = AddLocalMinPoly(e1, e2, pt, false); +#else + AddLocalMinPoly(e1, e2, pt, false); +#endif + } + break; + case ClipType::Xor: +#ifdef USINGZ + resultOp = AddLocalMinPoly(e1, e2, pt, false); +#else + AddLocalMinPoly(e1, e2, pt, false); +#endif + break; + default: + if (e1Wc2 > 0 && e2Wc2 > 0) +#ifdef USINGZ + resultOp = AddLocalMinPoly(e1, e2, pt, false); +#else + AddLocalMinPoly(e1, e2, pt, false); +#endif + break; + } +#ifdef USINGZ + if (resultOp && zCallback_) SetZ(e1, e2, resultOp->pt); +#endif + } + } + } + + inline void ClipperBase::DeleteFromAEL(Active& e) + { + Active* prev = e.prev_in_ael; + Active* next = e.next_in_ael; + if (!prev && !next && (&e != actives_)) return; // already deleted + if (prev) + prev->next_in_ael = next; + else + actives_ = next; + if (next) next->prev_in_ael = prev; + delete& e; + } + + + inline void ClipperBase::AdjustCurrXAndCopyToSEL(const int64_t top_y) + { + Active* e = actives_; + sel_ = e; + while (e) + { + e->prev_in_sel = e->prev_in_ael; + e->next_in_sel = e->next_in_ael; + e->jump = e->next_in_sel; + if (e->join_with == JoinWith::Left) + e->curr_x = e->prev_in_ael->curr_x; // also avoids complications + else + e->curr_x = TopX(*e, top_y); + e = e->next_in_ael; + } + } + + bool ClipperBase::ExecuteInternal(ClipType ct, FillRule fillrule, bool use_polytrees) + { + cliptype_ = ct; + fillrule_ = fillrule; + using_polytree_ = use_polytrees; + Reset(); + int64_t y; + if (ct == ClipType::NoClip || !PopScanline(y)) return true; + + while (succeeded_) + { + InsertLocalMinimaIntoAEL(y); + Active* e; + while (PopHorz(e)) DoHorizontal(*e); + if (horz_seg_list_.size() > 0) + { + ConvertHorzSegsToJoins(); + horz_seg_list_.clear(); + } + bot_y_ = y; // bot_y_ == bottom of scanbeam + if (!PopScanline(y)) break; // y new top of scanbeam + DoIntersections(y); + DoTopOfScanbeam(y); + while (PopHorz(e)) DoHorizontal(*e); + } + if (succeeded_) ProcessHorzJoins(); + return succeeded_; + } + + inline void FixOutRecPts(OutRec* outrec) + { + OutPt* op = outrec->pts; + do { + op->outrec = outrec; + op = op->next; + } while (op != outrec->pts); + } + + inline bool SetHorzSegHeadingForward(HorzSegment& hs, OutPt* opP, OutPt* opN) + { + if (opP->pt.x == opN->pt.x) return false; + if (opP->pt.x < opN->pt.x) + { + hs.left_op = opP; + hs.right_op = opN; + hs.left_to_right = true; + } + else + { + hs.left_op = opN; + hs.right_op = opP; + hs.left_to_right = false; + } + return true; + } + + inline bool UpdateHorzSegment(HorzSegment& hs) + { + OutPt* op = hs.left_op; + OutRec* outrec = GetRealOutRec(op->outrec); + bool outrecHasEdges = outrec->front_edge; + int64_t curr_y = op->pt.y; + OutPt* opP = op, * opN = op; + if (outrecHasEdges) + { + OutPt* opA = outrec->pts, * opZ = opA->next; + while (opP != opZ && opP->prev->pt.y == curr_y) + opP = opP->prev; + while (opN != opA && opN->next->pt.y == curr_y) + opN = opN->next; + } + else + { + while (opP->prev != opN && opP->prev->pt.y == curr_y) + opP = opP->prev; + while (opN->next != opP && opN->next->pt.y == curr_y) + opN = opN->next; + } + bool result = + SetHorzSegHeadingForward(hs, opP, opN) && + !hs.left_op->horz; + + if (result) + hs.left_op->horz = &hs; + else + hs.right_op = nullptr; // (for sorting) + return result; + } + + void ClipperBase::ConvertHorzSegsToJoins() + { + auto j = std::count_if(horz_seg_list_.begin(), + horz_seg_list_.end(), + [](HorzSegment& hs) { return UpdateHorzSegment(hs); }); + if (j < 2) return; + + std::stable_sort(horz_seg_list_.begin(), horz_seg_list_.end(), HorzSegSorter()); + + HorzSegmentList::iterator hs1 = horz_seg_list_.begin(), hs2; + HorzSegmentList::iterator hs_end = hs1 +j; + HorzSegmentList::iterator hs_end1 = hs_end - 1; + + for (; hs1 != hs_end1; ++hs1) + { + for (hs2 = hs1 + 1; hs2 != hs_end; ++hs2) + { + if ((hs2->left_op->pt.x >= hs1->right_op->pt.x) || + (hs2->left_to_right == hs1->left_to_right) || + (hs2->right_op->pt.x <= hs1->left_op->pt.x)) continue; + int64_t curr_y = hs1->left_op->pt.y; + if (hs1->left_to_right) + { + while (hs1->left_op->next->pt.y == curr_y && + hs1->left_op->next->pt.x <= hs2->left_op->pt.x) + hs1->left_op = hs1->left_op->next; + while (hs2->left_op->prev->pt.y == curr_y && + hs2->left_op->prev->pt.x <= hs1->left_op->pt.x) + hs2->left_op = hs2->left_op->prev; + HorzJoin join = HorzJoin( + DuplicateOp(hs1->left_op, true), + DuplicateOp(hs2->left_op, false)); + horz_join_list_.emplace_back(join); + } + else + { + while (hs1->left_op->prev->pt.y == curr_y && + hs1->left_op->prev->pt.x <= hs2->left_op->pt.x) + hs1->left_op = hs1->left_op->prev; + while (hs2->left_op->next->pt.y == curr_y && + hs2->left_op->next->pt.x <= hs1->left_op->pt.x) + hs2->left_op = hs2->left_op->next; + HorzJoin join = HorzJoin( + DuplicateOp(hs2->left_op, true), + DuplicateOp(hs1->left_op, false)); + horz_join_list_.emplace_back(join); + } + } + } + } + + void MoveSplits(OutRec* fromOr, OutRec* toOr) + { + if (!fromOr->splits) return; + if (!toOr->splits) toOr->splits = new OutRecList(); + OutRecList::iterator orIter = fromOr->splits->begin(); + for (; orIter != fromOr->splits->end(); ++orIter) + toOr->splits->emplace_back(*orIter); + fromOr->splits->clear(); + } + + + void ClipperBase::ProcessHorzJoins() + { + for (const HorzJoin& j : horz_join_list_) + { + OutRec* or1 = GetRealOutRec(j.op1->outrec); + OutRec* or2 = GetRealOutRec(j.op2->outrec); + + OutPt* op1b = j.op1->next; + OutPt* op2b = j.op2->prev; + j.op1->next = j.op2; + j.op2->prev = j.op1; + op1b->prev = op2b; + op2b->next = op1b; + + if (or1 == or2) // 'join' is really a split + { + or2 = NewOutRec(); + or2->pts = op1b; + FixOutRecPts(or2); + + //if or1->pts has moved to or2 then update or1->pts!! + if (or1->pts->outrec == or2) + { + or1->pts = j.op1; + or1->pts->outrec = or1; + } + + if (using_polytree_) //#498, #520, #584, D#576, #618 + { + if (Path1InsidePath2(or1->pts, or2->pts)) + { + //swap or1's & or2's pts + OutPt* tmp = or1->pts; + or1->pts = or2->pts; + or2->pts = tmp; + FixOutRecPts(or1); + FixOutRecPts(or2); + //or2 is now inside or1 + or2->owner = or1; + } + else if (Path1InsidePath2(or2->pts, or1->pts)) + { + or2->owner = or1; + } + else + or2->owner = or1->owner; + + if (!or1->splits) or1->splits = new OutRecList(); + or1->splits->emplace_back(or2); + } + else + or2->owner = or1; + } + else + { + or2->pts = nullptr; + if (using_polytree_) + { + SetOwner(or2, or1); + MoveSplits(or2, or1); //#618 + } + else + or2->owner = or1; + } + } + } + + void ClipperBase::DoIntersections(const int64_t top_y) + { + if (BuildIntersectList(top_y)) + { + ProcessIntersectList(); + intersect_nodes_.clear(); + } + } + + void ClipperBase::AddNewIntersectNode(Active& e1, Active& e2, int64_t top_y) + { + Point64 ip; + if (!GetSegmentIntersectPt(e1.bot, e1.top, e2.bot, e2.top, ip)) + ip = Point64(e1.curr_x, top_y); //parallel edges + + //rounding errors can occasionally place the calculated intersection + //point either below or above the scanbeam, so check and correct ... + if (ip.y > bot_y_ || ip.y < top_y) + { + double abs_dx1 = std::fabs(e1.dx); + double abs_dx2 = std::fabs(e2.dx); + if (abs_dx1 > 100 && abs_dx2 > 100) + { + if (abs_dx1 > abs_dx2) + ip = GetClosestPointOnSegment(ip, e1.bot, e1.top); + else + ip = GetClosestPointOnSegment(ip, e2.bot, e2.top); + } + else if (abs_dx1 > 100) + ip = GetClosestPointOnSegment(ip, e1.bot, e1.top); + else if (abs_dx2 > 100) + ip = GetClosestPointOnSegment(ip, e2.bot, e2.top); + else + { + if (ip.y < top_y) ip.y = top_y; + else ip.y = bot_y_; + if (abs_dx1 < abs_dx2) ip.x = TopX(e1, ip.y); + else ip.x = TopX(e2, ip.y); + } + } + intersect_nodes_.emplace_back(&e1, &e2, ip); + } + + bool ClipperBase::BuildIntersectList(const int64_t top_y) + { + if (!actives_ || !actives_->next_in_ael) return false; + + //Calculate edge positions at the top of the current scanbeam, and from this + //we will determine the intersections required to reach these new positions. + AdjustCurrXAndCopyToSEL(top_y); + //Find all edge intersections in the current scanbeam using a stable merge + //sort that ensures only adjacent edges are intersecting. Intersect info is + //stored in FIntersectList ready to be processed in ProcessIntersectList. + //Re merge sorts see https://stackoverflow.com/a/46319131/359538 + + Active* left = sel_, * right, * l_end, * r_end, * curr_base, * tmp; + + while (left && left->jump) + { + Active* prev_base = nullptr; + while (left && left->jump) + { + curr_base = left; + right = left->jump; + l_end = right; + r_end = right->jump; + left->jump = r_end; + while (left != l_end && right != r_end) + { + if (right->curr_x < left->curr_x) + { + tmp = right->prev_in_sel; + for (; ; ) + { + AddNewIntersectNode(*tmp, *right, top_y); + if (tmp == left) break; + tmp = tmp->prev_in_sel; + } + + tmp = right; + right = ExtractFromSEL(tmp); + l_end = right; + Insert1Before2InSEL(tmp, left); + if (left == curr_base) + { + curr_base = tmp; + curr_base->jump = r_end; + if (!prev_base) sel_ = curr_base; + else prev_base->jump = curr_base; + } + } + else left = left->next_in_sel; + } + prev_base = curr_base; + left = r_end; + } + left = sel_; + } + return intersect_nodes_.size() > 0; + } + + void ClipperBase::ProcessIntersectList() + { + //We now have a list of intersections required so that edges will be + //correctly positioned at the top of the scanbeam. However, it's important + //that edge intersections are processed from the bottom up, but it's also + //crucial that intersections only occur between adjacent edges. + + //First we do a quicksort so intersections proceed in a bottom up order ... + std::sort(intersect_nodes_.begin(), intersect_nodes_.end(), IntersectListSort); + //Now as we process these intersections, we must sometimes adjust the order + //to ensure that intersecting edges are always adjacent ... + + IntersectNodeList::iterator node_iter, node_iter2; + for (node_iter = intersect_nodes_.begin(); + node_iter != intersect_nodes_.end(); ++node_iter) + { + if (!EdgesAdjacentInAEL(*node_iter)) + { + node_iter2 = node_iter + 1; + while (!EdgesAdjacentInAEL(*node_iter2)) ++node_iter2; + std::swap(*node_iter, *node_iter2); + } + + IntersectNode& node = *node_iter; + IntersectEdges(*node.edge1, *node.edge2, node.pt); + SwapPositionsInAEL(*node.edge1, *node.edge2); + + node.edge1->curr_x = node.pt.x; + node.edge2->curr_x = node.pt.x; + CheckJoinLeft(*node.edge2, node.pt, true); + CheckJoinRight(*node.edge1, node.pt, true); + } + } + + void ClipperBase::SwapPositionsInAEL(Active& e1, Active& e2) + { + //preconditon: e1 must be immediately to the left of e2 + Active* next = e2.next_in_ael; + if (next) next->prev_in_ael = &e1; + Active* prev = e1.prev_in_ael; + if (prev) prev->next_in_ael = &e2; + e2.prev_in_ael = prev; + e2.next_in_ael = &e1; + e1.prev_in_ael = &e2; + e1.next_in_ael = next; + if (!e2.prev_in_ael) actives_ = &e2; + } + + inline OutPt* GetLastOp(const Active& hot_edge) + { + OutRec* outrec = hot_edge.outrec; + OutPt* result = outrec->pts; + if (&hot_edge != outrec->front_edge) + result = result->next; + return result; + } + + void ClipperBase::AddTrialHorzJoin(OutPt* op) + { + if (op->outrec->is_open) return; + horz_seg_list_.emplace_back(op); + } + + bool ClipperBase::ResetHorzDirection(const Active& horz, + const Vertex* max_vertex, int64_t& horz_left, int64_t& horz_right) + { + if (horz.bot.x == horz.top.x) + { + //the horizontal edge is going nowhere ... + horz_left = horz.curr_x; + horz_right = horz.curr_x; + Active* e = horz.next_in_ael; + while (e && e->vertex_top != max_vertex) e = e->next_in_ael; + return e != nullptr; + } + else if (horz.curr_x < horz.top.x) + { + horz_left = horz.curr_x; + horz_right = horz.top.x; + return true; + } + else + { + horz_left = horz.top.x; + horz_right = horz.curr_x; + return false; // right to left + } + } + + void ClipperBase::DoHorizontal(Active& horz) + /******************************************************************************* + * Notes: Horizontal edges (HEs) at scanline intersections (ie at the top or * + * bottom of a scanbeam) are processed as if layered.The order in which HEs * + * are processed doesn't matter. HEs intersect with the bottom vertices of * + * other HEs[#] and with non-horizontal edges [*]. Once these intersections * + * are completed, intermediate HEs are 'promoted' to the next edge in their * + * bounds, and they in turn may be intersected[%] by other HEs. * + * * + * eg: 3 horizontals at a scanline: / | / / * + * | / | (HE3)o ========%========== o * + * o ======= o(HE2) / | / / * + * o ============#=========*======*========#=========o (HE1) * + * / | / | / * + *******************************************************************************/ + { + Point64 pt; + bool horzIsOpen = IsOpen(horz); + int64_t y = horz.bot.y; + Vertex* vertex_max; + if (horzIsOpen) + vertex_max = GetCurrYMaximaVertex_Open(horz); + else + vertex_max = GetCurrYMaximaVertex(horz); + + //// remove 180 deg.spikes and also simplify + //// consecutive horizontals when PreserveCollinear = true + //if (!horzIsOpen && vertex_max != horz.vertex_top) + // TrimHorz(horz, PreserveCollinear); + + int64_t horz_left, horz_right; + bool is_left_to_right = + ResetHorzDirection(horz, vertex_max, horz_left, horz_right); + + if (IsHotEdge(horz)) + { +#ifdef USINGZ + OutPt* op = AddOutPt(horz, Point64(horz.curr_x, y, horz.bot.z)); +#else + OutPt* op = AddOutPt(horz, Point64(horz.curr_x, y)); +#endif + AddTrialHorzJoin(op); + } + + while (true) // loop through consec. horizontal edges + { + Active* e; + if (is_left_to_right) e = horz.next_in_ael; + else e = horz.prev_in_ael; + + while (e) + { + if (e->vertex_top == vertex_max) + { + if (IsHotEdge(horz) && IsJoined(*e)) + Split(*e, e->top); + + //if (IsHotEdge(horz) != IsHotEdge(*e)) + // DoError(undefined_error_i); + + if (IsHotEdge(horz)) + { + while (horz.vertex_top != vertex_max) + { + AddOutPt(horz, horz.top); + UpdateEdgeIntoAEL(&horz); + } + if (is_left_to_right) + AddLocalMaxPoly(horz, *e, horz.top); + else + AddLocalMaxPoly(*e, horz, horz.top); + } + DeleteFromAEL(*e); + DeleteFromAEL(horz); + return; + } + + //if horzEdge is a maxima, keep going until we reach + //its maxima pair, otherwise check for break conditions + if (vertex_max != horz.vertex_top || IsOpenEnd(horz)) + { + //otherwise stop when 'ae' is beyond the end of the horizontal line + if ((is_left_to_right && e->curr_x > horz_right) || + (!is_left_to_right && e->curr_x < horz_left)) break; + + if (e->curr_x == horz.top.x && !IsHorizontal(*e)) + { + pt = NextVertex(horz)->pt; + if (is_left_to_right) + { + //with open paths we'll only break once past horz's end + if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e)) + { + if (TopX(*e, pt.y) > pt.x) break; + } + //otherwise we'll only break when horz's outslope is greater than e's + else if (TopX(*e, pt.y) >= pt.x) break; + } + else + { + if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e)) + { + if (TopX(*e, pt.y) < pt.x) break; + } + else if (TopX(*e, pt.y) <= pt.x) break; + } + } + } + + pt = Point64(e->curr_x, horz.bot.y); + if (is_left_to_right) + { + IntersectEdges(horz, *e, pt); + SwapPositionsInAEL(horz, *e); + CheckJoinLeft(*e, pt); + horz.curr_x = e->curr_x; + e = horz.next_in_ael; + } + else + { + IntersectEdges(*e, horz, pt); + SwapPositionsInAEL(*e, horz); + CheckJoinRight(*e, pt); + horz.curr_x = e->curr_x; + e = horz.prev_in_ael; + } + + if (horz.outrec) + { + //nb: The outrec containing the op returned by IntersectEdges + //above may no longer be associated with horzEdge. + AddTrialHorzJoin(GetLastOp(horz)); + } + } + + //check if we've finished with (consecutive) horizontals ... + if (horzIsOpen && IsOpenEnd(horz)) // ie open at top + { + if (IsHotEdge(horz)) + { + AddOutPt(horz, horz.top); + if (IsFront(horz)) + horz.outrec->front_edge = nullptr; + else + horz.outrec->back_edge = nullptr; + horz.outrec = nullptr; + } + DeleteFromAEL(horz); + return; + } + else if (NextVertex(horz)->pt.y != horz.top.y) + break; + + //still more horizontals in bound to process ... + if (IsHotEdge(horz)) + AddOutPt(horz, horz.top); + UpdateEdgeIntoAEL(&horz); + + is_left_to_right = + ResetHorzDirection(horz, vertex_max, horz_left, horz_right); + } + + if (IsHotEdge(horz)) + { + OutPt* op = AddOutPt(horz, horz.top); + AddTrialHorzJoin(op); + } + + UpdateEdgeIntoAEL(&horz); // end of an intermediate horiz. + } + + void ClipperBase::DoTopOfScanbeam(const int64_t y) + { + sel_ = nullptr; // sel_ is reused to flag horizontals (see PushHorz below) + Active* e = actives_; + while (e) + { + //nb: 'e' will never be horizontal here + if (e->top.y == y) + { + e->curr_x = e->top.x; + if (IsMaxima(*e)) + { + e = DoMaxima(*e); // TOP OF BOUND (MAXIMA) + continue; + } + else + { + //INTERMEDIATE VERTEX ... + if (IsHotEdge(*e)) AddOutPt(*e, e->top); + UpdateEdgeIntoAEL(e); + if (IsHorizontal(*e)) + PushHorz(*e); // horizontals are processed later + } + } + else // i.e. not the top of the edge + e->curr_x = TopX(*e, y); + + e = e->next_in_ael; + } + } + + + Active* ClipperBase::DoMaxima(Active& e) + { + Active* next_e, * prev_e, * max_pair; + prev_e = e.prev_in_ael; + next_e = e.next_in_ael; + if (IsOpenEnd(e)) + { + if (IsHotEdge(e)) AddOutPt(e, e.top); + if (!IsHorizontal(e)) + { + if (IsHotEdge(e)) + { + if (IsFront(e)) + e.outrec->front_edge = nullptr; + else + e.outrec->back_edge = nullptr; + e.outrec = nullptr; + } + DeleteFromAEL(e); + } + return next_e; + } + + max_pair = GetMaximaPair(e); + if (!max_pair) return next_e; // eMaxPair is horizontal + + if (IsJoined(e)) Split(e, e.top); + if (IsJoined(*max_pair)) Split(*max_pair, max_pair->top); + + //only non-horizontal maxima here. + //process any edges between maxima pair ... + while (next_e != max_pair) + { + IntersectEdges(e, *next_e, e.top); + SwapPositionsInAEL(e, *next_e); + next_e = e.next_in_ael; + } + + if (IsOpen(e)) + { + if (IsHotEdge(e)) + AddLocalMaxPoly(e, *max_pair, e.top); + DeleteFromAEL(*max_pair); + DeleteFromAEL(e); + return (prev_e ? prev_e->next_in_ael : actives_); + } + + // e.next_in_ael== max_pair ... + if (IsHotEdge(e)) + AddLocalMaxPoly(e, *max_pair, e.top); + + DeleteFromAEL(e); + DeleteFromAEL(*max_pair); + return (prev_e ? prev_e->next_in_ael : actives_); + } + + void ClipperBase::Split(Active& e, const Point64& pt) + { + if (e.join_with == JoinWith::Right) + { + e.join_with = JoinWith::NoJoin; + e.next_in_ael->join_with = JoinWith::NoJoin; + AddLocalMinPoly(e, *e.next_in_ael, pt, true); + } + else + { + e.join_with = JoinWith::NoJoin; + e.prev_in_ael->join_with = JoinWith::NoJoin; + AddLocalMinPoly(*e.prev_in_ael, e, pt, true); + } + } + + void ClipperBase::CheckJoinLeft(Active& e, + const Point64& pt, bool check_curr_x) + { + Active* prev = e.prev_in_ael; + if (!prev || + !IsHotEdge(e) || !IsHotEdge(*prev) || + IsHorizontal(e) || IsHorizontal(*prev) || + IsOpen(e) || IsOpen(*prev) ) return; + if ((pt.y < e.top.y + 2 || pt.y < prev->top.y + 2) && + ((e.bot.y > pt.y) || (prev->bot.y > pt.y))) return; // avoid trivial joins + + if (check_curr_x) + { + if (PerpendicDistFromLineSqrd(pt, prev->bot, prev->top) > 0.25) return; + } + else if (e.curr_x != prev->curr_x) return; + if (!IsCollinear(e.top, pt, prev->top)) return; + + if (e.outrec->idx == prev->outrec->idx) + AddLocalMaxPoly(*prev, e, pt); + else if (e.outrec->idx < prev->outrec->idx) + JoinOutrecPaths(e, *prev); + else + JoinOutrecPaths(*prev, e); + prev->join_with = JoinWith::Right; + e.join_with = JoinWith::Left; + } + + void ClipperBase::CheckJoinRight(Active& e, + const Point64& pt, bool check_curr_x) + { + Active* next = e.next_in_ael; + if (!next || + !IsHotEdge(e) || !IsHotEdge(*next) || + IsHorizontal(e) || IsHorizontal(*next) || + IsOpen(e) || IsOpen(*next)) return; + if ((pt.y < e.top.y +2 || pt.y < next->top.y +2) && + ((e.bot.y > pt.y) || (next->bot.y > pt.y))) return; // avoid trivial joins + + if (check_curr_x) + { + if (PerpendicDistFromLineSqrd(pt, next->bot, next->top) > 0.35) return; + } + else if (e.curr_x != next->curr_x) return; + if (!IsCollinear(e.top, pt, next->top)) return; + + if (e.outrec->idx == next->outrec->idx) + AddLocalMaxPoly(e, *next, pt); + else if (e.outrec->idx < next->outrec->idx) + JoinOutrecPaths(e, *next); + else + JoinOutrecPaths(*next, e); + + e.join_with = JoinWith::Right; + next->join_with = JoinWith::Left; + } + + inline bool GetHorzExtendedHorzSeg(OutPt*& op, OutPt*& op2) + { + OutRec* outrec = GetRealOutRec(op->outrec); + op2 = op; + if (outrec->front_edge) + { + while (op->prev != outrec->pts && + op->prev->pt.y == op->pt.y) op = op->prev; + while (op2 != outrec->pts && + op2->next->pt.y == op2->pt.y) op2 = op2->next; + return op2 != op; + } + else + { + while (op->prev != op2 && op->prev->pt.y == op->pt.y) + op = op->prev; + while (op2->next != op && op2->next->pt.y == op2->pt.y) + op2 = op2->next; + return op2 != op && op2->next != op; + } + } + + bool BuildPath64(OutPt* op, bool reverse, bool isOpen, Path64& path) + { + if (!op || op->next == op || (!isOpen && op->next == op->prev)) + return false; + + path.resize(0); + Point64 lastPt; + OutPt* op2; + if (reverse) + { + lastPt = op->pt; + op2 = op->prev; + } + else + { + op = op->next; + lastPt = op->pt; + op2 = op->next; + } + path.emplace_back(lastPt); + + while (op2 != op) + { + if (op2->pt != lastPt) + { + lastPt = op2->pt; + path.emplace_back(lastPt); + } + if (reverse) + op2 = op2->prev; + else + op2 = op2->next; + } + + if (!isOpen && path.size() == 3 && IsVerySmallTriangle(*op2)) return false; + else return true; + } + + bool ClipperBase::CheckBounds(OutRec* outrec) + { + if (!outrec->pts) return false; + if (!outrec->bounds.IsEmpty()) return true; + CleanCollinear(outrec); + if (!outrec->pts || + !BuildPath64(outrec->pts, reverse_solution_, false, outrec->path)){ + return false;} + outrec->bounds = GetBounds(outrec->path); + return true; + } + + bool ClipperBase::CheckSplitOwner(OutRec* outrec, OutRecList* splits) + { + for (auto split : *splits) + { + split = GetRealOutRec(split); + if(!split || split == outrec || split->recursive_split == outrec) continue; + split->recursive_split = outrec; // prevent infinite loops + + if (split->splits && CheckSplitOwner(outrec, split->splits)) + return true; + else if (CheckBounds(split) && + IsValidOwner(outrec, split) && + split->bounds.Contains(outrec->bounds) && + Path1InsidePath2(outrec->pts, split->pts)) + { + outrec->owner = split; //found in split + return true; + } + } + return false; + } + + void ClipperBase::RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath) + { + // pre-condition: outrec will have valid bounds + // post-condition: if a valid path, outrec will have a polypath + + if (outrec->polypath || outrec->bounds.IsEmpty()) return; + + while (outrec->owner) + { + if (outrec->owner->splits && CheckSplitOwner(outrec, outrec->owner->splits)) break; + if (outrec->owner->pts && CheckBounds(outrec->owner) && + outrec->owner->bounds.Contains(outrec->bounds) && + Path1InsidePath2(outrec->pts, outrec->owner->pts)) break; + outrec->owner = outrec->owner->owner; + } + + if (outrec->owner) + { + if (!outrec->owner->polypath) + RecursiveCheckOwners(outrec->owner, polypath); + outrec->polypath = outrec->owner->polypath->AddChild(outrec->path); + } + else + outrec->polypath = polypath->AddChild(outrec->path); + } + + void Clipper64::BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen) + { + solutionClosed.resize(0); + solutionClosed.reserve(outrec_list_.size()); + if (solutionOpen) + { + solutionOpen->resize(0); + solutionOpen->reserve(outrec_list_.size()); + } + + // nb: outrec_list_.size() may change in the following + // while loop because polygons may be split during + // calls to CleanCollinear which calls FixSelfIntersects + for (size_t i = 0; i < outrec_list_.size(); ++i) + { + OutRec* outrec = outrec_list_[i]; + if (outrec->pts == nullptr) continue; + + Path64 path; + if (solutionOpen && outrec->is_open) + { + if (BuildPath64(outrec->pts, reverse_solution_, true, path)) + solutionOpen->emplace_back(std::move(path)); + } + else + { + // nb: CleanCollinear can add to outrec_list_ + CleanCollinear(outrec); + //closed paths should always return a Positive orientation + if (BuildPath64(outrec->pts, reverse_solution_, false, path)) + solutionClosed.emplace_back(std::move(path)); + } + } + } + + void Clipper64::BuildTree64(PolyPath64& polytree, Paths64& open_paths) + { + polytree.Clear(); + open_paths.resize(0); + if (has_open_paths_) + open_paths.reserve(outrec_list_.size()); + + // outrec_list_.size() is not static here because + // CheckBounds below can indirectly add additional + // OutRec (via FixOutRecPts & CleanCollinear) + for (size_t i = 0; i < outrec_list_.size(); ++i) + { + OutRec* outrec = outrec_list_[i]; + if (!outrec || !outrec->pts) continue; + if (outrec->is_open) + { + Path64 path; + if (BuildPath64(outrec->pts, reverse_solution_, true, path)) + open_paths.emplace_back(std::move(path)); + continue; + } + + if (CheckBounds(outrec)) + RecursiveCheckOwners(outrec, &polytree); + } + } + + bool BuildPathD(OutPt* op, bool reverse, bool isOpen, PathD& path, double inv_scale) + { + if (!op || op->next == op || (!isOpen && op->next == op->prev)) + return false; + + path.resize(0); + Point64 lastPt; + OutPt* op2; + if (reverse) + { + lastPt = op->pt; + op2 = op->prev; + } + else + { + op = op->next; + lastPt = op->pt; + op2 = op->next; + } +#ifdef USINGZ + path.emplace_back(lastPt.x * inv_scale, lastPt.y * inv_scale, lastPt.z); +#else + path.emplace_back(lastPt.x * inv_scale, lastPt.y * inv_scale); +#endif + + while (op2 != op) + { + if (op2->pt != lastPt) + { + lastPt = op2->pt; +#ifdef USINGZ + path.emplace_back(lastPt.x * inv_scale, lastPt.y * inv_scale, lastPt.z); +#else + path.emplace_back(lastPt.x * inv_scale, lastPt.y * inv_scale); +#endif + + } + if (reverse) + op2 = op2->prev; + else + op2 = op2->next; + } + if (path.size() == 3 && IsVerySmallTriangle(*op2)) return false; + return true; + } + + void ClipperD::BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen) + { + solutionClosed.resize(0); + solutionClosed.reserve(outrec_list_.size()); + if (solutionOpen) + { + solutionOpen->resize(0); + solutionOpen->reserve(outrec_list_.size()); + } + + // outrec_list_.size() is not static here because + // CleanCollinear below can indirectly add additional + // OutRec (via FixOutRecPts) + for (std::size_t i = 0; i < outrec_list_.size(); ++i) + { + OutRec* outrec = outrec_list_[i]; + if (outrec->pts == nullptr) continue; + + PathD path; + if (solutionOpen && outrec->is_open) + { + if (BuildPathD(outrec->pts, reverse_solution_, true, path, invScale_)) + solutionOpen->emplace_back(std::move(path)); + } + else + { + CleanCollinear(outrec); + //closed paths should always return a Positive orientation + if (BuildPathD(outrec->pts, reverse_solution_, false, path, invScale_)) + solutionClosed.emplace_back(std::move(path)); + } + } + } + + void ClipperD::BuildTreeD(PolyPathD& polytree, PathsD& open_paths) + { + polytree.Clear(); + open_paths.resize(0); + if (has_open_paths_) + open_paths.reserve(outrec_list_.size()); + + // outrec_list_.size() is not static here because + // BuildPathD below can indirectly add additional OutRec //#607 + for (size_t i = 0; i < outrec_list_.size(); ++i) + { + OutRec* outrec = outrec_list_[i]; + if (!outrec || !outrec->pts) continue; + if (outrec->is_open) + { + PathD path; + if (BuildPathD(outrec->pts, reverse_solution_, true, path, invScale_)) + open_paths.emplace_back(std::move(path)); + continue; + } + + if (CheckBounds(outrec)) + RecursiveCheckOwners(outrec, &polytree); + } + } } // namespace clipper2lib diff --git a/src/clipper2/Clipper2Lib/src/clipper.offset.cpp b/src/clipper2/Clipper2Lib/src/clipper.offset.cpp index a19a6ff..23e405e 100644 --- a/src/clipper2/Clipper2Lib/src/clipper.offset.cpp +++ b/src/clipper2/Clipper2Lib/src/clipper.offset.cpp @@ -1,48 +1,72 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 15 October 2022 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2022 * +* Date : 22 January 2025 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2025 * * Purpose : Path Offset (Inflate/Shrink) * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ -#include #include "clipper2/clipper.h" #include "clipper2/clipper.offset.h" namespace Clipper2Lib { -const double default_arc_tolerance = 0.25; const double floating_point_tolerance = 1e-12; +// Clipper2 approximates arcs by using series of relatively short straight +//line segments. And logically, shorter line segments will produce better arc +// approximations. But very short segments can degrade performance, usually +// with little or no discernable improvement in curve quality. Very short +// segments can even detract from curve quality, due to the effects of integer +// rounding. Since there isn't an optimal number of line segments for any given +// arc radius (that perfectly balances curve approximation with performance), +// arc tolerance is user defined. Nevertheless, when the user doesn't define +// an arc tolerance (ie leaves alone the 0 default value), the calculated +// default arc tolerance (offset_radius / 500) generally produces good (smooth) +// arc approximations without producing excessively small segment lengths. +// See also: https://www.angusj.com/clipper2/Docs/Trigonometry.htm +const double arc_const = 0.002; // <-- 1/500 + + //------------------------------------------------------------------------------ // Miscellaneous methods //------------------------------------------------------------------------------ -Paths64::size_type GetLowestPolygonIdx(const Paths64& paths) +std::optional GetLowestClosedPathIdx(const Paths64& paths) { - Paths64::size_type result = 0; - Point64 lp = Point64(static_cast(0), - std::numeric_limits::min()); - - for (Paths64::size_type i = 0 ; i < paths.size(); ++i) - for (const Point64& p : paths[i]) - { - if (p.y < lp.y || (p.y == lp.y && p.x >= lp.x)) continue; - result = i; - lp = p; - } + std::optional result; + Point64 botPt = Point64(INT64_MAX, INT64_MIN); + for (size_t i = 0; i < paths.size(); ++i) + { + for (const Point64& pt : paths[i]) + { + if ((pt.y < botPt.y) || + ((pt.y == botPt.y) && (pt.x >= botPt.x))) continue; + result = i; + botPt.x = pt.x; + botPt.y = pt.y; + } + } return result; } -PointD GetUnitNormal(const Point64& pt1, const Point64& pt2) +inline double Hypot(double x, double y) +{ + // given that this is an internal function, and given the x and y parameters + // will always be coordinate values (or the difference between coordinate values), + // x and y should always be within INT64_MIN to INT64_MAX. Consequently, + // there should be no risk that the following computation will overflow + // see https://stackoverflow.com/a/32436148/359538 + return std::sqrt(x * x + y * y); +} + +static PointD GetUnitNormal(const Point64& pt1, const Point64& pt2) { - double dx, dy, inverse_hypot; if (pt1 == pt2) return PointD(0.0, 0.0); - dx = static_cast(pt2.x - pt1.x); - dy = static_cast(pt2.y - pt1.y); - inverse_hypot = 1.0 / hypot(dx, dy); + double dx = static_cast(pt2.x - pt1.x); + double dy = static_cast(pt2.y - pt1.y); + double inverse_hypot = 1.0 / Hypot(dx, dy); dx *= inverse_hypot; dy *= inverse_hypot; return PointD(dy, -dx); @@ -53,15 +77,8 @@ inline bool AlmostZero(double value, double epsilon = 0.001) return std::fabs(value) < epsilon; } -inline double Hypot(double x, double y) -{ - //see https://stackoverflow.com/a/32436148/359538 - return std::sqrt(x * x + y * y); -} - inline PointD NormalizeVector(const PointD& vec) { - double h = Hypot(vec.x, vec.y); if (AlmostZero(h)) return PointD(0,0); double inverseHypot = 1 / h; @@ -78,14 +95,63 @@ inline bool IsClosedPath(EndType et) return et == EndType::Polygon || et == EndType::Joined; } -inline Point64 GetPerpendic(const Point64& pt, const PointD& norm, double delta) +static inline Point64 GetPerpendic(const Point64& pt, const PointD& norm, double delta) { +#ifdef USINGZ + return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z); +#else return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta); +#endif } inline PointD GetPerpendicD(const Point64& pt, const PointD& norm, double delta) { +#ifdef USINGZ + return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z); +#else return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta); +#endif +} + +inline void NegatePath(PathD& path) +{ + for (PointD& pt : path) + { + pt.x = -pt.x; + pt.y = -pt.y; +#ifdef USINGZ + pt.z = pt.z; +#endif + } +} + + +//------------------------------------------------------------------------------ +// ClipperOffset::Group methods +//------------------------------------------------------------------------------ + +ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType _end_type): + paths_in(_paths), join_type(_join_type), end_type(_end_type) +{ + bool is_joined = + (end_type == EndType::Polygon) || + (end_type == EndType::Joined); + for (Path64& p: paths_in) + StripDuplicates(p, is_joined); + + if (end_type == EndType::Polygon) + { + lowest_path_idx = GetLowestClosedPathIdx(paths_in); + // the lowermost path must be an outer path, so if its orientation is negative, + // then flag the whole group is 'reversed' (will negate delta etc.) + // as this is much more efficient than reversing every path. + is_reversed = (lowest_path_idx.has_value()) && Area(paths_in[lowest_path_idx.value()]) < 0; + } + else + { + lowest_path_idx = std::nullopt; + is_reversed = false; + } } //------------------------------------------------------------------------------ @@ -94,28 +160,13 @@ inline PointD GetPerpendicD(const Point64& pt, const PointD& norm, double delta) void ClipperOffset::AddPath(const Path64& path, JoinType jt_, EndType et_) { - Paths64 paths; - paths.push_back(path); - AddPaths(paths, jt_, et_); + groups_.emplace_back(Paths64(1, path), jt_, et_); } void ClipperOffset::AddPaths(const Paths64 &paths, JoinType jt_, EndType et_) { if (paths.size() == 0) return; - groups_.push_back(Group(paths, jt_, et_)); -} - -void ClipperOffset::AddPath(const Clipper2Lib::PathD& path, JoinType jt_, EndType et_) -{ - PathsD paths; - paths.push_back(path); - AddPaths(paths, jt_, et_); -} - -void ClipperOffset::AddPaths(const PathsD& paths, JoinType jt_, EndType et_) -{ - if (paths.size() == 0) return; - groups_.push_back(Group(PathsDToPaths64(paths), jt_, et_)); + groups_.emplace_back(paths, jt_, et_); } void ClipperOffset::BuildNormals(const Path64& path) @@ -123,64 +174,55 @@ void ClipperOffset::BuildNormals(const Path64& path) norms.clear(); norms.reserve(path.size()); if (path.size() == 0) return; - Path64::const_iterator path_iter, path_last_iter = --path.cend(); - for (path_iter = path.cbegin(); path_iter != path_last_iter; ++path_iter) - norms.push_back(GetUnitNormal(*path_iter,*(path_iter +1))); - norms.push_back(GetUnitNormal(*path_last_iter, *(path.cbegin()))); + Path64::const_iterator path_iter, path_stop_iter = --path.cend(); + for (path_iter = path.cbegin(); path_iter != path_stop_iter; ++path_iter) + norms.emplace_back(GetUnitNormal(*path_iter,*(path_iter +1))); + norms.emplace_back(GetUnitNormal(*path_stop_iter, *(path.cbegin()))); } -inline PointD TranslatePoint(const PointD& pt, double dx, double dy) +void ClipperOffset::DoBevel(const Path64& path, size_t j, size_t k) { - return PointD(pt.x + dx, pt.y + dy); -} - -inline PointD ReflectPoint(const PointD& pt, const PointD& pivot) -{ - return PointD(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y)); -} - -PointD IntersectPoint(const PointD& pt1a, const PointD& pt1b, - const PointD& pt2a, const PointD& pt2b) -{ - if (pt1a.x == pt1b.x) //vertical + PointD pt1, pt2; + if (j == k) { - if (pt2a.x == pt2b.x) return PointD(0, 0); - - double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x); - double b2 = pt2a.y - m2 * pt2a.x; - return PointD(pt1a.x, m2 * pt1a.x + b2); - } - else if (pt2a.x == pt2b.x) //vertical - { - double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x); - double b1 = pt1a.y - m1 * pt1a.x; - return PointD(pt2a.x, m1 * pt2a.x + b1); + double abs_delta = std::abs(group_delta_); +#ifdef USINGZ + pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y, path[j].z); + pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y, path[j].z); +#else + pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y); + pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y); +#endif } else { - double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x); - double b1 = pt1a.y - m1 * pt1a.x; - double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x); - double b2 = pt2a.y - m2 * pt2a.x; - if (m1 == m2) return PointD(0, 0); - double x = (b2 - b1) / (m1 - m2); - return PointD(x, m1 * x + b1); +#ifdef USINGZ + pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y, path[j].z); + pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y, path[j].z); +#else + pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y); + pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y); +#endif } + path_out.emplace_back(pt1); + path_out.emplace_back(pt2); } -void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t k) +void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k) { PointD vec; - if (j == k) - vec = PointD(norms[0].y, -norms[0].x); + if (j == k) + vec = PointD(norms[j].y, -norms[j].x); else vec = GetAvgUnitVector( PointD(-norms[k].y, norms[k].x), PointD(norms[j].y, -norms[j].x)); + double abs_delta = std::abs(group_delta_); + // now offset the original vertex delta units along unit vector PointD ptQ = PointD(path[j]); - ptQ = TranslatePoint(ptQ, abs_group_delta_ * vec.x, abs_group_delta_ * vec.y); + ptQ = TranslatePoint(ptQ, abs_delta * vec.x, abs_delta * vec.y); // get perpendicular vertices PointD pt1 = TranslatePoint(ptQ, group_delta_ * vec.y, group_delta_ * -vec.x); PointD pt2 = TranslatePoint(ptQ, group_delta_ * -vec.y, group_delta_ * vec.x); @@ -189,51 +231,77 @@ void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t if (j == k) { PointD pt4 = PointD(pt3.x + vec.x * group_delta_, pt3.y + vec.y * group_delta_); - PointD pt = IntersectPoint(pt1, pt2, pt3, pt4); + PointD pt = ptQ; + GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt); //get the second intersect point through reflecion - group.path_.push_back(Point64(ReflectPoint(pt, ptQ))); - group.path_.push_back(Point64(pt)); + path_out.emplace_back(ReflectPoint(pt, ptQ)); + path_out.emplace_back(pt); } else { PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_); - PointD pt = IntersectPoint(pt1, pt2, pt3, pt4); - group.path_.push_back(Point64(pt)); + PointD pt = ptQ; + GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt); + path_out.emplace_back(pt); //get the second intersect point through reflecion - group.path_.push_back(Point64(ReflectPoint(pt, ptQ))); + path_out.emplace_back(ReflectPoint(pt, ptQ)); } } -void ClipperOffset::DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a) +void ClipperOffset::DoMiter(const Path64& path, size_t j, size_t k, double cos_a) { double q = group_delta_ / (cos_a + 1); - group.path_.push_back(Point64( +#ifdef USINGZ + path_out.emplace_back( path[j].x + (norms[k].x + norms[j].x) * q, - path[j].y + (norms[k].y + norms[j].y) * q)); + path[j].y + (norms[k].y + norms[j].y) * q, + path[j].z); +#else + path_out.emplace_back( + path[j].x + (norms[k].x + norms[j].x) * q, + path[j].y + (norms[k].y + norms[j].y) * q); +#endif } -void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle) +void ClipperOffset::DoRound(const Path64& path, size_t j, size_t k, double angle) { - //even though angle may be negative this is a convex join - Point64 pt = path[j]; - int steps = static_cast(std::ceil(steps_per_rad_ * std::abs(angle))); - double step_sin = std::sin(angle / steps); - double step_cos = std::cos(angle / steps); - - PointD pt2 = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_); - if (j == k) pt2.Negate(); - - group.path_.push_back(Point64(pt.x + pt2.x, pt.y + pt2.y)); - for (int i = 0; i < steps; i++) - { - pt2 = PointD(pt2.x * step_cos - step_sin * pt2.y, - pt2.x * step_sin + pt2.y * step_cos); - group.path_.push_back(Point64(pt.x + pt2.x, pt.y + pt2.y)); + if (deltaCallback64_) { + // when deltaCallback64_ is assigned, group_delta_ won't be constant, + // so we'll need to do the following calculations for *every* vertex. + double abs_delta = std::fabs(group_delta_); + double arcTol = (arc_tolerance_ > floating_point_tolerance ? + std::min(abs_delta, arc_tolerance_) : abs_delta * arc_const); + double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI); + step_sin_ = std::sin(2 * PI / steps_per_360); + step_cos_ = std::cos(2 * PI / steps_per_360); + if (group_delta_ < 0.0) step_sin_ = -step_sin_; + steps_per_rad_ = steps_per_360 / (2 * PI); } - group.path_.push_back(GetPerpendic(path[j], norms[j], group_delta_)); + + Point64 pt = path[j]; + PointD offsetVec = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_); + + if (j == k) offsetVec.Negate(); +#ifdef USINGZ + path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z); +#else + path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y); +#endif + int steps = static_cast(std::ceil(steps_per_rad_ * std::abs(angle))); // #448, #456 + for (int i = 1; i < steps; ++i) // ie 1 less than steps + { + offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y, + offsetVec.x * step_sin_ + offsetVec.y * step_cos_); +#ifdef USINGZ + path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z); +#else + path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y); +#endif + } + path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_)); } -void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k) +void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size_t k) { // Let A = change in angle where edges join // A == 0: ie no change in angle (flat join) @@ -241,245 +309,346 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k) // sin(A) < 0: right turning // cos(A) < 0: change in angle is more than 90 degree - if (path[j] == path[k]) { k = j; return; } + if (path[j] == path[k]) return; double sin_a = CrossProduct(norms[j], norms[k]); double cos_a = DotProduct(norms[j], norms[k]); if (sin_a > 1.0) sin_a = 1.0; else if (sin_a < -1.0) sin_a = -1.0; - bool almostNoAngle = AlmostZero(sin_a) && cos_a > 0; - // when there's almost no angle of deviation or it's concave - if (almostNoAngle || (sin_a * group_delta_ < 0)) - { - Point64 p1 = Point64( - path[j].x + norms[k].x * group_delta_, - path[j].y + norms[k].y * group_delta_); - Point64 p2 = Point64( - path[j].x + norms[j].x * group_delta_, - path[j].y + norms[j].y * group_delta_); - group.path_.push_back(p1); - if (p1 != p2) - { - // when concave add an extra vertex to ensure neat clipping - if (!almostNoAngle) group.path_.push_back(path[j]); - group.path_.push_back(p2); - } + if (deltaCallback64_) { + group_delta_ = deltaCallback64_(path, norms, j, k); + if (group.is_reversed) group_delta_ = -group_delta_; } - else // it's convex + if (std::fabs(group_delta_) <= floating_point_tolerance) { - if (join_type_ == JoinType::Round) - DoRound(group, path, j, k, std::atan2(sin_a, cos_a)); - else if (join_type_ == JoinType::Miter) - { - // miter unless the angle is so acute the miter would exceeds ML - if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a); - else DoSquare(group, path, j, k); - } - // don't bother squaring angles that deviate < ~20 degrees because - // squaring will be indistinguishable from mitering and just be a lot slower - else if (cos_a > 0.9) - DoMiter(group, path, j, k, cos_a); - else - DoSquare(group, path, j, k); + path_out.emplace_back(path[j]); + return; } - k = j; + + if (cos_a > -0.999 && (sin_a * group_delta_ < 0)) // test for concavity first (#593) + { + // is concave + // by far the simplest way to construct concave joins, especially those joining very + // short segments, is to insert 3 points that produce negative regions. These regions + // will be removed later by the finishing union operation. This is also the best way + // to ensure that path reversals (ie over-shrunk paths) are removed. +#ifdef USINGZ + path_out.emplace_back(GetPerpendic(path[j], norms[k], group_delta_), path[j].z); + path_out.emplace_back(path[j]); // (#405, #873, #916) + path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_), path[j].z); +#else + path_out.emplace_back(GetPerpendic(path[j], norms[k], group_delta_)); + path_out.emplace_back(path[j]); // (#405, #873, #916) + path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_)); +#endif + } + else if (cos_a > 0.999 && join_type_ != JoinType::Round) + { + // almost straight - less than 2.5 degree (#424, #482, #526 & #724) + DoMiter(path, j, k, cos_a); + } + else if (join_type_ == JoinType::Miter) + { + // miter unless the angle is sufficiently acute to exceed ML + if (cos_a > temp_lim_ - 1) DoMiter(path, j, k, cos_a); + else DoSquare(path, j, k); + } + else if (join_type_ == JoinType::Round) + DoRound(path, j, k, std::atan2(sin_a, cos_a)); + else if ( join_type_ == JoinType::Bevel) + DoBevel(path, j, k); + else + DoSquare(path, j, k); } -void ClipperOffset::OffsetPolygon(Group& group, Path64& path) +void ClipperOffset::OffsetPolygon(Group& group, const Path64& path) { - group.path_.clear(); - for (Path64::size_type i = 0, j = path.size() -1; i < path.size(); j = i, ++i) - OffsetPoint(group, path, i, j); - group.paths_out_.push_back(group.path_); + path_out.clear(); + for (Path64::size_type j = 0, k = path.size() - 1; j < path.size(); k = j, ++j) + OffsetPoint(group, path, j, k); + solution->emplace_back(path_out); } -void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path) +void ClipperOffset::OffsetOpenJoined(Group& group, const Path64& path) { OffsetPolygon(group, path); - std::reverse(path.begin(), path.end()); - BuildNormals(path); - OffsetPolygon(group, path); + Path64 reverse_path(path); + std::reverse(reverse_path.begin(), reverse_path.end()); + + //rebuild normals + std::reverse(norms.begin(), norms.end()); + norms.emplace_back(norms[0]); + norms.erase(norms.begin()); + NegatePath(norms); + + OffsetPolygon(group, reverse_path); } -void ClipperOffset::OffsetOpenPath(Group& group, Path64& path, EndType end_type) +void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path) { - group.path_.clear(); - // do the line start cap - switch (end_type) + if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, 0, 0); + + if (std::fabs(group_delta_) <= floating_point_tolerance) + path_out.emplace_back(path[0]); + else { - case EndType::Butt: - group.path_.push_back(Point64( - path[0].x - norms[0].x * group_delta_, - path[0].y - norms[0].y * group_delta_)); - group.path_.push_back(GetPerpendic(path[0], norms[0], group_delta_)); - break; - case EndType::Round: - DoRound(group, path, 0,0, PI); - break; - default: - DoSquare(group, path, 0, 0); - break; + switch (end_type_) + { + case EndType::Butt: + DoBevel(path, 0, 0); + break; + case EndType::Round: + DoRound(path, 0, 0, PI); + break; + default: + DoSquare(path, 0, 0); + break; + } } size_t highI = path.size() - 1; - // offset the left side going forward - for (Path64::size_type i = 1, k = 0; i < highI; ++i) - OffsetPoint(group, path, i, k); + for (Path64::size_type j = 1, k = 0; j < highI; k = j, ++j) + OffsetPoint(group, path, j, k); - // reverse normals + // reverse normals for (size_t i = highI; i > 0; --i) norms[i] = PointD(-norms[i - 1].x, -norms[i - 1].y); norms[0] = norms[highI]; // do the line end cap - switch (end_type) + if (deltaCallback64_) + group_delta_ = deltaCallback64_(path, norms, highI, highI); + + if (std::fabs(group_delta_) <= floating_point_tolerance) + path_out.emplace_back(path[highI]); + else { - case EndType::Butt: - group.path_.push_back(Point64( - path[highI].x - norms[highI].x * group_delta_, - path[highI].y - norms[highI].y * group_delta_)); - group.path_.push_back(GetPerpendic(path[highI], norms[highI], group_delta_)); - break; - case EndType::Round: - DoRound(group, path, highI, highI, PI); - break; - default: - DoSquare(group, path, highI, highI); - break; + switch (end_type_) + { + case EndType::Butt: + DoBevel(path, highI, highI); + break; + case EndType::Round: + DoRound(path, highI, highI, PI); + break; + default: + DoSquare(path, highI, highI); + break; + } } - for (size_t i = highI, k = 0; i > 0; --i) - OffsetPoint(group, path, i, k); - group.paths_out_.push_back(group.path_); + for (size_t j = highI -1, k = highI; j > 0; k = j, --j) + OffsetPoint(group, path, j, k); + solution->emplace_back(path_out); } -void ClipperOffset::DoGroupOffset(Group& group, double delta) +void ClipperOffset::DoGroupOffset(Group& group) { - if (group.end_type_ != EndType::Polygon) delta = std::abs(delta) * 0.5; - bool isClosedPaths = IsClosedPath(group.end_type_); - - if (isClosedPaths) + if (group.end_type == EndType::Polygon) { - //the lowermost polygon must be an outer polygon. So we can use that as the - //designated orientation for outer polygons (needed for tidy-up clipping) - Paths64::size_type lowestIdx = GetLowestPolygonIdx(group.paths_in_); - // nb: don't use the default orientation here ... - double area = Area(group.paths_in_[lowestIdx]); - if (area == 0) return; - group.is_reversed_ = (area < 0); - if (group.is_reversed_) delta = -delta; - } + // a straight path (2 points) can now also be 'polygon' offset + // where the ends will be treated as (180 deg.) joins + if (!group.lowest_path_idx.has_value()) delta_ = std::abs(delta_); + group_delta_ = (group.is_reversed) ? -delta_ : delta_; + } else - group.is_reversed_ = false; + group_delta_ = std::abs(delta_);// *0.5; - group_delta_ = delta; - abs_group_delta_ = std::abs(group_delta_); - join_type_ = group.join_type_; + double abs_delta = std::fabs(group_delta_); + join_type_ = group.join_type; + end_type_ = group.end_type; - double arcTol = (arc_tolerance_ > floating_point_tolerance ? arc_tolerance_ - : std::log10(2 + abs_group_delta_) * default_arc_tolerance); // empirically derived - -//calculate a sensible number of steps (for 360 deg for the given offset - if (group.join_type_ == JoinType::Round || group.end_type_ == EndType::Round) + if (group.join_type == JoinType::Round || group.end_type == EndType::Round) { - steps_per_rad_ = PI / std::acos(1 - arcTol / abs_group_delta_) / (PI *2); + // calculate the number of steps required to approximate a circle + // (see https://www.angusj.com/clipper2/Docs/Trigonometry.htm) + // arcTol - when arc_tolerance_ is undefined (0) then curve imprecision + // will be relative to the size of the offset (delta). Obviously very + //large offsets will almost always require much less precision. + double arcTol = (arc_tolerance_ > floating_point_tolerance) ? + std::min(abs_delta, arc_tolerance_) : abs_delta * arc_const; + + double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI); + step_sin_ = std::sin(2 * PI / steps_per_360); + step_cos_ = std::cos(2 * PI / steps_per_360); + if (group_delta_ < 0.0) step_sin_ = -step_sin_; + steps_per_rad_ = steps_per_360 / (2 * PI); } - bool is_closed_path = IsClosedPath(group.end_type_); - Paths64::const_iterator path_iter; - for(path_iter = group.paths_in_.cbegin(); path_iter != group.paths_in_.cend(); ++path_iter) + //double min_area = PI * Sqr(group_delta_); + Paths64::const_iterator path_in_it = group.paths_in.cbegin(); + for ( ; path_in_it != group.paths_in.cend(); ++path_in_it) { - Path64 path = StripDuplicates(*path_iter, is_closed_path); - Path64::size_type cnt = path.size(); - if (cnt == 0) continue; + Path64::size_type pathLen = path_in_it->size(); + path_out.clear(); - if (cnt == 1) // single point - only valid with open paths + if (pathLen == 1) // single point { - group.path_ = Path64(); - //single vertex so build a circle or square ... - if (group.join_type_ == JoinType::Round) + if (deltaCallback64_) { - double radius = abs_group_delta_; - group.path_ = Ellipse(path[0], radius, radius); + group_delta_ = deltaCallback64_(*path_in_it, norms, 0, 0); + if (group.is_reversed) group_delta_ = -group_delta_; + abs_delta = std::fabs(group_delta_); + } + + if (group_delta_ < 1) continue; + const Point64& pt = (*path_in_it)[0]; + //single vertex so build a circle or square ... + if (group.join_type == JoinType::Round) + { + double radius = abs_delta; + size_t steps = steps_per_rad_ > 0 ? static_cast(std::ceil(steps_per_rad_ * 2 * PI)) : 0; //#617 + path_out = Ellipse(pt, radius, radius, steps); +#ifdef USINGZ + for (auto& p : path_out) p.z = pt.z; +#endif } else { - int d = (int)std::ceil(abs_group_delta_); - Rect64 r = Rect64(path[0].x - d, path[0].y - d, path[0].x + d, path[0].y + d); - group.path_ = r.AsPath(); + int d = (int)std::ceil(abs_delta); + Rect64 r = Rect64(pt.x - d, pt.y - d, pt.x + d, pt.y + d); + path_out = r.AsPath(); +#ifdef USINGZ + for (auto& p : path_out) p.z = pt.z; +#endif } - group.paths_out_.push_back(group.path_); - } - else - { - BuildNormals(path); - if (group.end_type_ == EndType::Polygon) OffsetPolygon(group, path); - else if (group.end_type_ == EndType::Joined) OffsetOpenJoined(group, path); - else OffsetOpenPath(group, path, group.end_type_); - } - } - if (!merge_groups_) - { - //clean up self-intersections ... - Clipper64 c; - c.PreserveCollinear = false; - //the solution should retain the orientation of the input - c.ReverseSolution = reverse_solution_ != group.is_reversed_; - c.AddSubject(group.paths_out_); - if (group.is_reversed_) - c.Execute(ClipType::Union, FillRule::Negative, group.paths_out_); - else - c.Execute(ClipType::Union, FillRule::Positive, group.paths_out_); - } + solution->emplace_back(path_out); + continue; + } // end of offsetting a single point - solution.reserve(solution.size() + group.paths_out_.size()); - copy(group.paths_out_.begin(), group.paths_out_.end(), back_inserter(solution)); - group.paths_out_.clear(); + if ((pathLen == 2) && (group.end_type == EndType::Joined)) + end_type_ = (group.join_type == JoinType::Round) ? + EndType::Round : + EndType::Square; + + BuildNormals(*path_in_it); + if (end_type_ == EndType::Polygon) OffsetPolygon(group, *path_in_it); + else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, *path_in_it); + else OffsetOpenPath(group, *path_in_it); + } } -Paths64 ClipperOffset::Execute(double delta) +#ifdef USINGZ +void ClipperOffset::ZCB(const Point64& bot1, const Point64& top1, + const Point64& bot2, const Point64& top2, Point64& ip) { - solution.clear(); - if (std::abs(delta) < default_arc_tolerance) - { - for (const Group& group : groups_) + if (bot1.z && ((bot1.z == bot2.z) || (bot1.z == top2.z))) ip.z = bot1.z; + else if (bot2.z && (bot2.z == top1.z)) ip.z = bot2.z; + else if (top1.z && (top1.z == top2.z)) ip.z = top1.z; + else if (zCallback64_) zCallback64_(bot1, top1, bot2, top2, ip); +} +#endif + +size_t ClipperOffset::CalcSolutionCapacity() +{ + size_t result = 0; + for (const Group& g : groups_) + result += (g.end_type == EndType::Joined) ? g.paths_in.size() * 2 : g.paths_in.size(); + return result; +} + +bool ClipperOffset::CheckReverseOrientation() +{ + // nb: this assumes there's consistency in orientation between groups + bool is_reversed_orientation = false; + for (const Group& g : groups_) + if (g.end_type == EndType::Polygon) { - solution.reserve(solution.size() + group.paths_in_.size()); - copy(group.paths_in_.begin(), group.paths_in_.end(), back_inserter(solution)); + is_reversed_orientation = g.is_reversed; + break; + } + return is_reversed_orientation; +} + +void ClipperOffset::ExecuteInternal(double delta) +{ + error_code_ = 0; + if (groups_.size() == 0) return; + solution->reserve(CalcSolutionCapacity()); + + if (std::abs(delta) < 0.5) // ie: offset is insignificant + { + Paths64::size_type sol_size = 0; + for (const Group& group : groups_) sol_size += group.paths_in.size(); + solution->reserve(sol_size); + for (const Group& group : groups_) + copy(group.paths_in.begin(), group.paths_in.end(), back_inserter(*solution)); + } + else + { + + temp_lim_ = (miter_limit_ <= 1) ? + 2.0 : + 2.0 / (miter_limit_ * miter_limit_); + + delta_ = delta; + std::vector::iterator git; + for (git = groups_.begin(); git != groups_.end(); ++git) + { + DoGroupOffset(*git); + if (!error_code_) continue; // all OK + solution->clear(); } - return solution; } - temp_lim_ = (miter_limit_ <= 1) ? - 2.0 : - 2.0 / (miter_limit_ * miter_limit_); + if (!solution->size()) return; - std::vector::iterator groups_iter; - for (groups_iter = groups_.begin(); - groups_iter != groups_.end(); ++groups_iter) + bool paths_reversed = CheckReverseOrientation(); + //clean up self-intersections ... + Clipper64 c; + c.PreserveCollinear(false); + //the solution should retain the orientation of the input + c.ReverseSolution(reverse_solution_ != paths_reversed); +#ifdef USINGZ + auto fp = std::bind(&ClipperOffset::ZCB, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, + std::placeholders::_4, std::placeholders::_5); + c.SetZCallback(fp); +#endif + c.AddSubject(*solution); + if (solution_tree) { - DoGroupOffset(*groups_iter, delta); - } - - if (merge_groups_ && groups_.size() > 0) - { - //clean up self-intersections ... - Clipper64 c; - c.PreserveCollinear = false; - //the solution should retain the orientation of the input - c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed_; - - c.AddSubject(solution); - if (groups_[0].is_reversed_) - c.Execute(ClipType::Union, FillRule::Negative, solution); + if (paths_reversed) + c.Execute(ClipType::Union, FillRule::Negative, *solution_tree); else - c.Execute(ClipType::Union, FillRule::Positive, solution); + c.Execute(ClipType::Union, FillRule::Positive, *solution_tree); } - return solution; + else + { + if (paths_reversed) + c.Execute(ClipType::Union, FillRule::Negative, *solution); + else + c.Execute(ClipType::Union, FillRule::Positive, *solution); + } +} + +void ClipperOffset::Execute(double delta, Paths64& paths64) +{ + paths64.clear(); + solution = &paths64; + solution_tree = nullptr; + ExecuteInternal(delta); +} + + +void ClipperOffset::Execute(double delta, PolyTree64& polytree) +{ + polytree.Clear(); + solution_tree = &polytree; + solution = new Paths64(); + ExecuteInternal(delta); + delete solution; + solution = nullptr; +} + +void ClipperOffset::Execute(DeltaCallback64 delta_cb, Paths64& paths) +{ + deltaCallback64_ = delta_cb; + Execute(1.0, paths); } } // namespace diff --git a/src/clipper2/Clipper2Lib/src/clipper.rectclip.cpp b/src/clipper2/Clipper2Lib/src/clipper.rectclip.cpp index 0f6bb9c..a5d66df 100644 --- a/src/clipper2/Clipper2Lib/src/clipper.rectclip.cpp +++ b/src/clipper2/Clipper2Lib/src/clipper.rectclip.cpp @@ -1,13 +1,12 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 October 2022 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2022 * +* Date : 5 July 2024 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2024 * * Purpose : FAST rectangular clipping * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ -#include #include "clipper2/clipper.h" #include "clipper2/clipper.rectclip.h" @@ -17,15 +16,22 @@ namespace Clipper2Lib { // Miscellaneous methods //------------------------------------------------------------------------------ - inline PointInPolygonResult Path1ContainsPath2(Path64 path1, Path64 path2) + inline bool Path1ContainsPath2(const Path64& path1, const Path64& path2) { - PointInPolygonResult result = PointInPolygonResult::IsOn; - for(const Point64& pt : path2) + int io_count = 0; + // precondition: no (significant) overlap + for (const Point64& pt : path2) { - result = PointInPolygon(pt, path1); - if (result != PointInPolygonResult::IsOn) break; + PointInPolygonResult pip = PointInPolygon(pt, path1); + switch (pip) + { + case PointInPolygonResult::IsOutside: ++io_count; break; + case PointInPolygonResult::IsInside: --io_count; break; + default: continue; + } + if (std::abs(io_count) > 1) break; } - return result; + return io_count <= 0; } inline bool GetLocation(const Rect64& rec, @@ -59,37 +65,54 @@ namespace Clipper2Lib { return true; } - Point64 GetIntersectPoint64(const Point64& ln1a, const Point64& ln1b, - const Point64& ln2a, const Point64& ln2b) + inline bool IsHorizontal(const Point64& pt1, const Point64& pt2) { - // see http://astronomy.swin.edu.au/~pbourke/geometry/lineline2d/ - if (ln1b.x == ln1a.x) + return pt1.y == pt2.y; + } + + bool GetSegmentIntersection(const Point64& p1, + const Point64& p2, const Point64& p3, const Point64& p4, Point64& ip) + { + double res1 = CrossProduct(p1, p3, p4); + double res2 = CrossProduct(p2, p3, p4); + if (res1 == 0) { - if (ln2b.x == ln2a.x) return Point64(); // parallel lines - double m2 = static_cast(ln2b.y - ln2a.y) / (ln2b.x - ln2a.x); - double b2 = ln2a.y - m2 * ln2a.x; - return Point64(ln1a.x, static_cast(std::round(m2 * ln1a.x + b2))); + ip = p1; + if (res2 == 0) return false; // segments are collinear + else if (p1 == p3 || p1 == p4) return true; + //else if (p2 == p3 || p2 == p4) { ip = p2; return true; } + else if (IsHorizontal(p3, p4)) return ((p1.x > p3.x) == (p1.x < p4.x)); + else return ((p1.y > p3.y) == (p1.y < p4.y)); } - else if (ln2b.x == ln2a.x) + else if (res2 == 0) { - double m1 = static_cast(ln1b.y - ln1a.y) / (ln1b.x - ln1a.x); - double b1 = ln1a.y - m1 * ln1a.x; - return Point64(ln2a.x, static_cast(std::round(m1 * ln2a.x + b1))); + ip = p2; + if (p2 == p3 || p2 == p4) return true; + else if (IsHorizontal(p3, p4)) return ((p2.x > p3.x) == (p2.x < p4.x)); + else return ((p2.y > p3.y) == (p2.y < p4.y)); } - else + if ((res1 > 0) == (res2 > 0)) return false; + + double res3 = CrossProduct(p3, p1, p2); + double res4 = CrossProduct(p4, p1, p2); + if (res3 == 0) { - double m1 = static_cast(ln1b.y - ln1a.y) / (ln1b.x - ln1a.x); - double b1 = ln1a.y - m1 * ln1a.x; - double m2 = static_cast(ln2b.y - ln2a.y) / (ln2b.x - ln2a.x); - double b2 = ln2a.y - m2 * ln2a.x; - if (std::fabs(m1 - m2) > 1.0E-15) - { - double x = (b2 - b1) / (m1 - m2); - return Point64(x, m1 * x + b1); - } - else - return Point64((ln1a.x + ln1b.x) * 0.5, (ln1a.y + ln1b.y) * 0.5); + ip = p3; + if (p3 == p1 || p3 == p2) return true; + else if (IsHorizontal(p1, p2)) return ((p3.x > p1.x) == (p3.x < p2.x)); + else return ((p3.y > p1.y) == (p3.y < p2.y)); } + else if (res4 == 0) + { + ip = p4; + if (p4 == p1 || p4 == p2) return true; + else if (IsHorizontal(p1, p2)) return ((p4.x > p1.x) == (p4.x < p2.x)); + else return ((p4.y > p1.y) == (p4.y < p2.y)); + } + if ((res3 > 0) == (res4 > 0)) return false; + + // segments must intersect to get here + return GetSegmentIntersectPt(p1, p2, p3, p4, ip); } inline bool GetIntersection(const Path64& rectPath, @@ -100,101 +123,84 @@ namespace Clipper2Lib { switch (loc) { case Location::Left: - if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) - ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[3]); - else if (p.y < rectPath[0].y && - SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) + if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip)) return true; + else if ((p.y < rectPath[0].y) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip)) { - ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[1]); loc = Location::Top; + return true; } - else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) + else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip)) { - ip = GetIntersectPoint64(p, p2, rectPath[2], rectPath[3]); loc = Location::Bottom; + return true; } else return false; - break; case Location::Top: - if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) - ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[1]); - else if (p.x < rectPath[0].x && - SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) + if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip)) return true; + else if ((p.x < rectPath[0].x) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip)) { - ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[3]); loc = Location::Left; + return true; } - else if (p.x > rectPath[1].x && - SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) + else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip)) { - ip = GetIntersectPoint64(p, p2, rectPath[1], rectPath[2]); loc = Location::Right; + return true; } else return false; - break; case Location::Right: - if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) - ip = GetIntersectPoint64(p, p2, rectPath[1], rectPath[2]); - else if (p.y < rectPath[0].y && - SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) + if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip)) return true; + else if ((p.y < rectPath[1].y) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip)) { - ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[1]); loc = Location::Top; + return true; } - else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) + else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip)) { - ip = GetIntersectPoint64(p, p2, rectPath[2], rectPath[3]); loc = Location::Bottom; + return true; } else return false; - break; case Location::Bottom: - if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) - ip = GetIntersectPoint64(p, p2, rectPath[2], rectPath[3]); - else if (p.x < rectPath[3].x && - SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) + if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip)) return true; + else if ((p.x < rectPath[3].x) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip)) { - ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[3]); loc = Location::Left; + return true; } - else if (p.x > rectPath[2].x && - SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) + else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip)) { - ip = GetIntersectPoint64(p, p2, rectPath[1], rectPath[2]); loc = Location::Right; + return true; } else return false; - break; default: // loc == rInside - if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) + if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip)) { - ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[3]); loc = Location::Left; + return true; } - else if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) + else if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip)) { - ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[1]); loc = Location::Top; + return true; } - else if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) + else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip)) { - ip = GetIntersectPoint64(p, p2, rectPath[1], rectPath[2]); loc = Location::Right; + return true; } - else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) + else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip)) { - ip = GetIntersectPoint64(p, p2, rectPath[2], rectPath[3]); loc = Location::Bottom; + return true; } else return false; - break; } - - return true; } inline Location GetAdjacentLocation(Location loc, bool isClockwise) @@ -214,7 +220,7 @@ namespace Clipper2Lib { } inline bool IsClockwise(Location prev, Location curr, - Point64 prev_pt, Point64 curr_pt, Point64 rect_mp) + const Point64& prev_pt, const Point64& curr_pt, const Point64& rect_mp) { if (AreOpposites(prev, curr)) return CrossProduct(prev_pt, rect_mp, curr_pt) < 0; @@ -222,34 +228,148 @@ namespace Clipper2Lib { return HeadingClockwise(prev, curr); } + inline OutPt2* UnlinkOp(OutPt2* op) + { + if (op->next == op) return nullptr; + op->prev->next = op->next; + op->next->prev = op->prev; + return op->next; + } + + inline OutPt2* UnlinkOpBack(OutPt2* op) + { + if (op->next == op) return nullptr; + op->prev->next = op->next; + op->next->prev = op->prev; + return op->prev; + } + + inline uint32_t GetEdgesForPt(const Point64& pt, const Rect64& rec) + { + uint32_t result = 0; + if (pt.x == rec.left) result = 1; + else if (pt.x == rec.right) result = 4; + if (pt.y == rec.top) result += 2; + else if (pt.y == rec.bottom) result += 8; + return result; + } + + inline bool IsHeadingClockwise(const Point64& pt1, const Point64& pt2, int edgeIdx) + { + switch (edgeIdx) + { + case 0: return pt2.y < pt1.y; + case 1: return pt2.x > pt1.x; + case 2: return pt2.y > pt1.y; + default: return pt2.x < pt1.x; + } + } + + inline bool HasHorzOverlap(const Point64& left1, const Point64& right1, + const Point64& left2, const Point64& right2) + { + return (left1.x < right2.x) && (right1.x > left2.x); + } + + inline bool HasVertOverlap(const Point64& top1, const Point64& bottom1, + const Point64& top2, const Point64& bottom2) + { + return (top1.y < bottom2.y) && (bottom1.y > top2.y); + } + + inline void AddToEdge(OutPt2List& edge, OutPt2* op) + { + if (op->edge) return; + op->edge = &edge; + edge.emplace_back(op); + } + + inline void UncoupleEdge(OutPt2* op) + { + if (!op->edge) return; + for (size_t i = 0; i < op->edge->size(); ++i) + { + OutPt2* op2 = (*op->edge)[i]; + if (op2 == op) + { + (*op->edge)[i] = nullptr; + break; + } + } + op->edge = nullptr; + } + + inline void SetNewOwner(OutPt2* op, size_t new_idx) + { + op->owner_idx = new_idx; + OutPt2* op2 = op->next; + while (op2 != op) + { + op2->owner_idx = new_idx; + op2 = op2->next; + } + } + //---------------------------------------------------------------------------- // RectClip64 //---------------------------------------------------------------------------- - void RectClip::AddCorner(Location prev, Location curr) + OutPt2* RectClip64::Add(Point64 pt, bool start_new) { - if (HeadingClockwise(prev, curr)) - result_.push_back(rectPath_[static_cast(prev)]); + // this method is only called by InternalExecute. + // Later splitting & rejoining won't create additional op's, + // though they will change the (non-storage) results_ count. + size_t curr_idx = results_.size(); + OutPt2* result; + if (curr_idx == 0 || start_new) + { + result = &op_container_.emplace_back(OutPt2()); + result->pt = pt; + result->next = result; + result->prev = result; + results_.emplace_back(result); + } else - result_.push_back(rectPath_[static_cast(curr)]); + { + --curr_idx; + OutPt2* prevOp = results_[curr_idx]; + if (prevOp->pt == pt) return prevOp; + result = &op_container_.emplace_back(OutPt2()); + result->owner_idx = curr_idx; + result->pt = pt; + result->next = prevOp->next; + prevOp->next->prev = result; + prevOp->next = result; + result->prev = prevOp; + results_[curr_idx] = result; + } + return result; } - void RectClip::AddCorner(Location& loc, bool isClockwise) + void RectClip64::AddCorner(Location prev, Location curr) + { + if (HeadingClockwise(prev, curr)) + Add(rect_as_path_[static_cast(prev)]); + else + Add(rect_as_path_[static_cast(curr)]); + } + + void RectClip64::AddCorner(Location& loc, bool isClockwise) { if (isClockwise) { - result_.push_back(rectPath_[static_cast(loc)]); + Add(rect_as_path_[static_cast(loc)]); loc = GetAdjacentLocation(loc, true); } else { loc = GetAdjacentLocation(loc, false); - result_.push_back(rectPath_[static_cast(loc)]); + Add(rect_as_path_[static_cast(loc)]); } } - void RectClip::GetNextLocation(const Path64& path, - Location& loc, int& i, int highI) + void RectClip64::GetNextLocation(const Path64& path, + Location& loc, size_t& i, size_t highI) { switch (loc) { @@ -296,34 +416,56 @@ namespace Clipper2Lib { else if (path[i].x > rect_.right) loc = Location::Right; else if (path[i].y > rect_.bottom) loc = Location::Bottom; else if (path[i].y < rect_.top) loc = Location::Top; - else { result_.push_back(path[i]); ++i; continue; } + else { Add(path[i]); ++i; continue; } break; //inner loop } break; - } //switch + } //switch } - Path64 RectClip::Execute(const Path64& path) + bool StartLocsAreClockwise(const std::vector& startlocs) { - if (rect_.IsEmpty() || path.size() < 3) return Path64(); + int result = 0; + for (size_t i = 1; i < startlocs.size(); ++i) + { + int d = static_cast(startlocs[i]) - static_cast(startlocs[i - 1]); + switch (d) + { + case -1: result -= 1; break; + case 1: result += 1; break; + case -3: result += 1; break; + case 3: result -= 1; break; + } + } + return result > 0; + } - result_.clear(); - start_locs_.clear(); - int i = 0, highI = static_cast(path.size()) - 1; + void RectClip64::ExecuteInternal(const Path64& path) + { + if (path.size() < 1) + return; + + size_t highI = path.size() - 1; Location prev = Location::Inside, loc; Location crossing_loc = Location::Inside; Location first_cross_ = Location::Inside; if (!GetLocation(rect_, path[highI], loc)) { - i = highI - 1; - while (i >= 0 && !GetLocation(rect_, path[i], prev)) --i; - if (i < 0) return path; + size_t i = highI; + while (i > 0 && !GetLocation(rect_, path[i - 1], prev)) + --i; + if (i == 0) + { + // all of path must be inside fRect + for (const auto& pt : path) Add(pt); + return; + } if (prev == Location::Inside) loc = Location::Inside; - i = 0; } Location starting_loc = loc; /////////////////////////////////////////////////// + size_t i = 0; while (i <= highI) { prev = loc; @@ -333,25 +475,27 @@ namespace Clipper2Lib { if (i > highI) break; Point64 ip, ip2; - Point64 prev_pt = (i) ? path[static_cast(i - 1)] : path[highI]; + Point64 prev_pt = (i) ? + path[static_cast(i - 1)] : + path[highI]; crossing_loc = loc; - if (!GetIntersection(rectPath_, path[i], prev_pt, crossing_loc, ip)) + if (!GetIntersection(rect_as_path_, + path[i], prev_pt, crossing_loc, ip)) { // ie remaining outside - if (crossing_prev == Location::Inside) { - bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], mp_); + bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], rect_mp_); do { - start_locs_.push_back(prev); + start_locs_.emplace_back(prev); prev = GetAdjacentLocation(prev, isClockw); } while (prev != loc); - crossing_loc = crossing_prev; // still not crossed + crossing_loc = crossing_prev; // still not crossed } else if (prev != Location::Inside && prev != loc) { - bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], mp_); + bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], rect_mp_); do { AddCorner(prev, isClockw); } while (prev != loc); @@ -369,11 +513,11 @@ namespace Clipper2Lib { if (first_cross_ == Location::Inside) { first_cross_ = crossing_loc; - start_locs_.push_back(prev); + start_locs_.emplace_back(prev); } else if (prev != crossing_loc) { - bool isClockw = IsClockwise(prev, crossing_loc, prev_pt, path[i], mp_); + bool isClockw = IsClockwise(prev, crossing_loc, prev_pt, path[i], rect_mp_); do { AddCorner(prev, isClockw); } while (prev != crossing_loc); @@ -381,21 +525,21 @@ namespace Clipper2Lib { } else if (prev != Location::Inside) { - // passing right through rect. 'ip' here will be the second + // passing right through rect. 'ip' here will be the second // intersect pt but we'll also need the first intersect pt (ip2) loc = prev; - GetIntersection(rectPath_, prev_pt, path[i], loc, ip2); - if (crossing_prev != Location::Inside) + GetIntersection(rect_as_path_, prev_pt, path[i], loc, ip2); + if (crossing_prev != Location::Inside && crossing_prev != loc) //579 AddCorner(crossing_prev, loc); if (first_cross_ == Location::Inside) { first_cross_ = loc; - start_locs_.push_back(prev); + start_locs_.emplace_back(prev); } loc = crossing_loc; - result_.push_back(ip2); + Add(ip2); if (ip == ip2) { // it's very likely that path[i] is on rect @@ -412,23 +556,35 @@ namespace Clipper2Lib { first_cross_ = crossing_loc; } - result_.push_back(ip); + Add(ip); } //while i <= highI /////////////////////////////////////////////////// if (first_cross_ == Location::Inside) { - if (starting_loc == Location::Inside) return path; - Rect64 tmp_rect = Bounds(path); - if (tmp_rect.Contains(rect_) && - Path1ContainsPath2(path, rectPath_) != - PointInPolygonResult::IsOutside) return rectPath_; - else - return Path64(); + // path never intersects + if (starting_loc != Location::Inside) + { + // path is outside rect + // but being outside, it still may not contain rect + if (path_bounds_.Contains(rect_) && + Path1ContainsPath2(path, rect_as_path_)) + { + // yep, the path does fully contain rect + // so add rect to the solution + bool is_clockwise_path = StartLocsAreClockwise(start_locs_); + for (size_t j = 0; j < 4; ++j) + { + size_t k = is_clockwise_path ? j : 3 - j; // reverses result path + Add(rect_as_path_[k]); + // we may well need to do some splitting later, so + AddToEdge(edges_[k * 2], results_[0]); + } + } + } } - - if (loc != Location::Inside && + else if (loc != Location::Inside && (loc != first_cross_ || start_locs_.size() > 2)) { if (start_locs_.size() > 0) @@ -445,54 +601,369 @@ namespace Clipper2Lib { if (loc != first_cross_) AddCorner(loc, HeadingClockwise(loc, first_cross_)); } - - if (result_.size() < 3) return Path64(); - - // tidy up duplicates and collinear segments - Path64 res; - res.reserve(result_.size()); - size_t k = 0; highI = static_cast(result_.size()) - 1; - Point64 prev_pt = result_[highI]; - res.push_back(result_[0]); - Path64::const_iterator cit; - for (cit = result_.cbegin() + 1; cit != result_.cend(); ++cit) - { - if (CrossProduct(prev_pt, res[k], *cit)) - { - prev_pt = res[k++]; - res.push_back(*cit); - } - else - res[k] = *cit; - } - - if (k < 2) return Path64(); - // and a final check for collinearity - else if (!CrossProduct(res[0], res[k - 1], res[k])) res.pop_back(); - return res; } - Paths64 RectClipLines::Execute(const Path64& path) + void RectClip64::CheckEdges() { - result_.clear(); - Paths64 result; - if (rect_.IsEmpty() || path.size() == 0) return result; + for (size_t i = 0; i < results_.size(); ++i) + { + OutPt2* op = results_[i]; + if (!op) continue; + OutPt2* op2 = op; + do + { + if (IsCollinear(op2->prev->pt, op2->pt, op2->next->pt)) + { + if (op2 == op) + { + op2 = UnlinkOpBack(op2); + if (!op2) break; + op = op2->prev; + } + else + { + op2 = UnlinkOpBack(op2); + if (!op2) break; + } + } + else + op2 = op2->next; + } while (op2 != op); - int i = 1, highI = static_cast(path.size()) - 1; + if (!op2) + { + results_[i] = nullptr; + continue; + } + results_[i] = op; // safety first + + uint32_t edgeSet1 = GetEdgesForPt(op->prev->pt, rect_); + op2 = op; + do + { + uint32_t edgeSet2 = GetEdgesForPt(op2->pt, rect_); + if (edgeSet2 && !op2->edge) + { + uint32_t combinedSet = (edgeSet1 & edgeSet2); + for (int j = 0; j < 4; ++j) + { + if (combinedSet & (1 << j)) + { + if (IsHeadingClockwise(op2->prev->pt, op2->pt, j)) + AddToEdge(edges_[j * 2], op2); + else + AddToEdge(edges_[j * 2 + 1], op2); + } + } + } + edgeSet1 = edgeSet2; + op2 = op2->next; + } while (op2 != op); + } + } + + void RectClip64::TidyEdges(size_t idx, OutPt2List& cw, OutPt2List& ccw) + { + if (ccw.empty()) return; + bool isHorz = ((idx == 1) || (idx == 3)); + bool cwIsTowardLarger = ((idx == 1) || (idx == 2)); + size_t i = 0, j = 0; + OutPt2* p1, * p2, * p1a, * p2a, * op, * op2; + + while (i < cw.size()) + { + p1 = cw[i]; + if (!p1 || p1->next == p1->prev) + { + cw[i++] = nullptr; + j = 0; + continue; + } + + size_t jLim = ccw.size(); + while (j < jLim && + (!ccw[j] || ccw[j]->next == ccw[j]->prev)) ++j; + + if (j == jLim) + { + ++i; + j = 0; + continue; + } + + if (cwIsTowardLarger) + { + // p1 >>>> p1a; + // p2 <<<< p2a; + p1 = cw[i]->prev; + p1a = cw[i]; + p2 = ccw[j]; + p2a = ccw[j]->prev; + } + else + { + // p1 <<<< p1a; + // p2 >>>> p2a; + p1 = cw[i]; + p1a = cw[i]->prev; + p2 = ccw[j]->prev; + p2a = ccw[j]; + } + + if ((isHorz && !HasHorzOverlap(p1->pt, p1a->pt, p2->pt, p2a->pt)) || + (!isHorz && !HasVertOverlap(p1->pt, p1a->pt, p2->pt, p2a->pt))) + { + ++j; + continue; + } + + // to get here we're either splitting or rejoining + bool isRejoining = cw[i]->owner_idx != ccw[j]->owner_idx; + + if (isRejoining) + { + results_[p2->owner_idx] = nullptr; + SetNewOwner(p2, p1->owner_idx); + } + + // do the split or re-join + if (cwIsTowardLarger) + { + // p1 >> | >> p1a; + // p2 << | << p2a; + p1->next = p2; + p2->prev = p1; + p1a->prev = p2a; + p2a->next = p1a; + } + else + { + // p1 << | << p1a; + // p2 >> | >> p2a; + p1->prev = p2; + p2->next = p1; + p1a->next = p2a; + p2a->prev = p1a; + } + + if (!isRejoining) + { + size_t new_idx = results_.size(); + results_.emplace_back(p1a); + SetNewOwner(p1a, new_idx); + } + + if (cwIsTowardLarger) + { + op = p2; + op2 = p1a; + } + else + { + op = p1; + op2 = p2a; + } + results_[op->owner_idx] = op; + results_[op2->owner_idx] = op2; + + // and now lots of work to get ready for the next loop + + bool opIsLarger, op2IsLarger; + if (isHorz) // X + { + opIsLarger = op->pt.x > op->prev->pt.x; + op2IsLarger = op2->pt.x > op2->prev->pt.x; + } + else // Y + { + opIsLarger = op->pt.y > op->prev->pt.y; + op2IsLarger = op2->pt.y > op2->prev->pt.y; + } + + if ((op->next == op->prev) || + (op->pt == op->prev->pt)) + { + if (op2IsLarger == cwIsTowardLarger) + { + cw[i] = op2; + ccw[j++] = nullptr; + } + else + { + ccw[j] = op2; + cw[i++] = nullptr; + } + } + else if ((op2->next == op2->prev) || + (op2->pt == op2->prev->pt)) + { + if (opIsLarger == cwIsTowardLarger) + { + cw[i] = op; + ccw[j++] = nullptr; + } + else + { + ccw[j] = op; + cw[i++] = nullptr; + } + } + else if (opIsLarger == op2IsLarger) + { + if (opIsLarger == cwIsTowardLarger) + { + cw[i] = op; + UncoupleEdge(op2); + AddToEdge(cw, op2); + ccw[j++] = nullptr; + } + else + { + cw[i++] = nullptr; + ccw[j] = op2; + UncoupleEdge(op); + AddToEdge(ccw, op); + j = 0; + } + } + else + { + if (opIsLarger == cwIsTowardLarger) + cw[i] = op; + else + ccw[j] = op; + if (op2IsLarger == cwIsTowardLarger) + cw[i] = op2; + else + ccw[j] = op2; + } + } + } + + Path64 RectClip64::GetPath(OutPt2*& op) + { + if (!op || op->next == op->prev) return Path64(); + + OutPt2* op2 = op->next; + while (op2 && op2 != op) + { + if (IsCollinear(op2->prev->pt, + op2->pt, op2->next->pt)) + { + op = op2->prev; + op2 = UnlinkOp(op2); + } + else + op2 = op2->next; + } + op = op2; // needed for op cleanup + if (!op2) return Path64(); + + Path64 result; + result.emplace_back(op->pt); + op2 = op->next; + while (op2 != op) + { + result.emplace_back(op2->pt); + op2 = op2->next; + } + return result; + } + + Paths64 RectClip64::Execute(const Paths64& paths) + { + Paths64 result; + if (rect_.IsEmpty()) return result; + + for (const Path64& path : paths) + { + if (path.size() < 3) continue; + path_bounds_ = GetBounds(path); + if (!rect_.Intersects(path_bounds_)) + continue; // the path must be completely outside rect_ + else if (rect_.Contains(path_bounds_)) + { + // the path must be completely inside rect_ + result.emplace_back(path); + continue; + } + + ExecuteInternal(path); + CheckEdges(); + for (size_t i = 0; i < 4; ++i) + TidyEdges(i, edges_[i * 2], edges_[i * 2 + 1]); + + for (OutPt2*& op : results_) + { + Path64 tmp = GetPath(op); + if (!tmp.empty()) + result.emplace_back(std::move(tmp)); + } + + //clean up after every loop + op_container_ = std::deque(); + results_.clear(); + for (OutPt2List &edge : edges_) edge.clear(); + start_locs_.clear(); + } + return result; + } + + //------------------------------------------------------------------------------ + // RectClipLines64 + //------------------------------------------------------------------------------ + + Paths64 RectClipLines64::Execute(const Paths64& paths) + { + Paths64 result; + if (rect_.IsEmpty()) return result; + + for (const auto& path : paths) + { + Rect64 pathrec = GetBounds(path); + if (!rect_.Intersects(pathrec)) continue; + + ExecuteInternal(path); + + for (OutPt2*& op : results_) + { + Path64 tmp = GetPath(op); + if (!tmp.empty()) + result.emplace_back(std::move(tmp)); + } + results_.clear(); + + op_container_ = std::deque(); + start_locs_.clear(); + } + return result; + } + + void RectClipLines64::ExecuteInternal(const Path64& path) + { + if (rect_.IsEmpty() || path.size() < 2) return; + + results_.clear(); + op_container_ = std::deque(); + start_locs_.clear(); + + size_t i = 1, highI = path.size() - 1; Location prev = Location::Inside, loc; - Location crossing_loc = Location::Inside; + Location crossing_loc; if (!GetLocation(rect_, path[0], loc)) { while (i <= highI && !GetLocation(rect_, path[i], prev)) ++i; - if (i > highI) { - result.push_back(path); - return result; + if (i > highI) + { + // all of path must be inside fRect + for (const auto& pt : path) Add(pt); + return; } if (prev == Location::Inside) loc = Location::Inside; i = 1; } - if (loc == Location::Inside) result_.push_back(path[0]); + if (loc == Location::Inside) Add(path[0]); /////////////////////////////////////////////////// while (i <= highI) @@ -504,7 +975,8 @@ namespace Clipper2Lib { Point64 prev_pt = path[static_cast(i - 1)]; crossing_loc = loc; - if (!GetIntersection(rectPath_, path[i], prev_pt, crossing_loc, ip)) + if (!GetIntersection(rect_as_path_, + path[i], prev_pt, crossing_loc, ip)) { // ie remaining outside ++i; @@ -517,30 +989,38 @@ namespace Clipper2Lib { if (loc == Location::Inside) // path must be entering rect { - result_.push_back(ip); + Add(ip, true); } else if (prev != Location::Inside) { - // passing right through rect. 'ip' here will be the second + // passing right through rect. 'ip' here will be the second // intersect pt but we'll also need the first intersect pt (ip2) crossing_loc = prev; - GetIntersection(rectPath_, prev_pt, path[i], crossing_loc, ip2); - result_.push_back(ip2); - result_.push_back(ip); - result.push_back(result_); - result_.clear(); + GetIntersection(rect_as_path_, + prev_pt, path[i], crossing_loc, ip2); + Add(ip2, true); + Add(ip); } else // path must be exiting rect { - result_.push_back(ip); - result.push_back(result_); - result_.clear(); + Add(ip); } } //while i <= highI /////////////////////////////////////////////////// + } - if (result_.size() > 1) - result.push_back(result_); + Path64 RectClipLines64::GetPath(OutPt2*& op) + { + Path64 result; + if (!op || op == op->next) return result; + op = op->next; // starting at path beginning + result.emplace_back(op->pt); + OutPt2 *op2 = op->next; + while (op2 != op) + { + result.emplace_back(op2->pt); + op2 = op2->next; + } return result; } diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index e9914c9..188e265 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -215,5 +215,7 @@ namespace ImGui const wchar_t ClipboardBtnIcon = 0x0848; const wchar_t ClipboardBtnDarkIcon = 0x0849; // void MyFunction(const char* name, const MyMatrix44& v); + + const wchar_t FilamentGreen = 0x0850; } diff --git a/src/imgui/imgui_widgets.cpp b/src/imgui/imgui_widgets.cpp index 727c497..f945951 100644 --- a/src/imgui/imgui_widgets.cpp +++ b/src/imgui/imgui_widgets.cpp @@ -2134,7 +2134,7 @@ bool ImGui::QDTBeginCombo(const char *label, const char *preview_value, ImGuiCom bool hovered, held; bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held); - bool push_color_count = 0; + unsigned int push_color_count = 0; if (hovered || g.ActiveId == id) { ImGui::PushStyleColor(ImGuiCol_Border, GetColorU32(ImGuiCol_BorderActive)); push_color_count = 1; @@ -4170,7 +4170,7 @@ bool ImGui::QDTInputScalar(const char *label, ImGuiDataType data_type, void *p_d // We are only allowed to access the state if we are already the active widget. ImGuiInputTextState *state = GetInputTextState(id); - bool push_color_count = 0; + unsigned int push_color_count = 0; if (hovered || g.ActiveId == id) { ImGui::PushStyleColor(ImGuiCol_Border, GetColorU32(ImGuiCol_BorderActive)); push_color_count = 1; diff --git a/src/imgui/imstb_truetype.h b/src/imgui/imstb_truetype.h index c35d745..de3c313 100644 --- a/src/imgui/imstb_truetype.h +++ b/src/imgui/imstb_truetype.h @@ -1556,7 +1556,9 @@ STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codep return 0; // not found } // @TODO +#ifndef __APPLE__ STBTT_assert(0); +#endif return 0; } diff --git a/src/imguizmo/CMakeLists.txt b/src/imguizmo/CMakeLists.txt new file mode 100644 index 0000000..fec4431 --- /dev/null +++ b/src/imguizmo/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 2.8.12) +project(imguizmo) + +add_library(imguizmo STATIC + ImGuizmo.h + ImGuizmo.cpp +) + +target_link_libraries(imguizmo PRIVATE imgui) diff --git a/src/imguizmo/ImGuizmo.cpp b/src/imguizmo/ImGuizmo.cpp new file mode 100644 index 0000000..1acf8b6 --- /dev/null +++ b/src/imguizmo/ImGuizmo.cpp @@ -0,0 +1,3148 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.89 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include +#include +#include "ImGuizmo.h" + +#if defined(_MSC_VER) || defined(__MINGW32__) +#include +#endif +#if !defined(_MSC_VER) && !defined(__MINGW64_VERSION_MAJOR) +#define _malloca(x) alloca(x) +#define _freea(x) +#endif + +// includes patches for multiview from +// https://github.com/CedricGuillemet/ImGuizmo/issues/15 + +namespace IMGUIZMO_NAMESPACE +{ + static const float ZPI = 3.14159265358979323846f; + static const float RAD2DEG = (180.f / ZPI); + static const float DEG2RAD = (ZPI / 180.f); + const float screenRotateSize = 0.06f; + // scale a bit so translate axis do not touch when in universal + const float rotationDisplayFactor = 1.2f; + + static OPERATION operator&(OPERATION lhs, OPERATION rhs) + { + return static_cast(static_cast(lhs) & static_cast(rhs)); + } + + static bool operator!=(OPERATION lhs, int rhs) + { + return static_cast(lhs) != rhs; + } + + static bool Intersects(OPERATION lhs, OPERATION rhs) + { + return (lhs & rhs) != 0; + } + + // True if lhs contains rhs + static bool Contains(OPERATION lhs, OPERATION rhs) + { + return (lhs & rhs) == rhs; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // utility and math + + void FPU_MatrixF_x_MatrixF(const float* a, const float* b, float* r) + { + r[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12]; + r[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13]; + r[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14]; + r[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15]; + + r[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12]; + r[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13]; + r[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14]; + r[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15]; + + r[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12]; + r[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13]; + r[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14]; + r[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15]; + + r[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12]; + r[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13]; + r[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14]; + r[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15]; + } + + void Frustum(float left, float right, float bottom, float top, float znear, float zfar, float* m16) + { + float temp, temp2, temp3, temp4; + temp = 2.0f * znear; + temp2 = right - left; + temp3 = top - bottom; + temp4 = zfar - znear; + m16[0] = temp / temp2; + m16[1] = 0.0; + m16[2] = 0.0; + m16[3] = 0.0; + m16[4] = 0.0; + m16[5] = temp / temp3; + m16[6] = 0.0; + m16[7] = 0.0; + m16[8] = (right + left) / temp2; + m16[9] = (top + bottom) / temp3; + m16[10] = (-zfar - znear) / temp4; + m16[11] = -1.0f; + m16[12] = 0.0; + m16[13] = 0.0; + m16[14] = (-temp * zfar) / temp4; + m16[15] = 0.0; + } + + void Perspective(float fovyInDegrees, float aspectRatio, float znear, float zfar, float* m16) + { + float ymax, xmax; + ymax = znear * tanf(fovyInDegrees * DEG2RAD); + xmax = ymax * aspectRatio; + Frustum(-xmax, xmax, -ymax, ymax, znear, zfar, m16); + } + + void Cross(const float* a, const float* b, float* r) + { + r[0] = a[1] * b[2] - a[2] * b[1]; + r[1] = a[2] * b[0] - a[0] * b[2]; + r[2] = a[0] * b[1] - a[1] * b[0]; + } + + float Dot(const float* a, const float* b) + { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + + void Normalize(const float* a, float* r) + { + float il = 1.f / (sqrtf(Dot(a, a)) + FLT_EPSILON); + r[0] = a[0] * il; + r[1] = a[1] * il; + r[2] = a[2] * il; + } + + void LookAt(const float* eye, const float* at, const float* up, float* m16) + { + float X[3], Y[3], Z[3], tmp[3]; + + tmp[0] = eye[0] - at[0]; + tmp[1] = eye[1] - at[1]; + tmp[2] = eye[2] - at[2]; + Normalize(tmp, Z); + Normalize(up, Y); + Cross(Y, Z, tmp); + Normalize(tmp, X); + Cross(Z, X, tmp); + Normalize(tmp, Y); + + m16[0] = X[0]; + m16[1] = Y[0]; + m16[2] = Z[0]; + m16[3] = 0.0f; + m16[4] = X[1]; + m16[5] = Y[1]; + m16[6] = Z[1]; + m16[7] = 0.0f; + m16[8] = X[2]; + m16[9] = Y[2]; + m16[10] = Z[2]; + m16[11] = 0.0f; + m16[12] = -Dot(X, eye); + m16[13] = -Dot(Y, eye); + m16[14] = -Dot(Z, eye); + m16[15] = 1.0f; + } + + template T Clamp(T x, T y, T z) { return ((x < y) ? y : ((x > z) ? z : x)); } + template T max(T x, T y) { return (x > y) ? x : y; } + template T min(T x, T y) { return (x < y) ? x : y; } + template bool IsWithin(T x, T y, T z) { return (x >= y) && (x <= z); } + + struct matrix_t; + struct vec_t + { + public: + float x, y, z, w; + + void Lerp(const vec_t& v, float t) + { + x += (v.x - x) * t; + y += (v.y - y) * t; + z += (v.z - z) * t; + w += (v.w - w) * t; + } + + void Set(float v) { x = y = z = w = v; } + void Set(float _x, float _y, float _z = 0.f, float _w = 0.f) { x = _x; y = _y; z = _z; w = _w; } + + vec_t& operator -= (const vec_t& v) { x -= v.x; y -= v.y; z -= v.z; w -= v.w; return *this; } + vec_t& operator += (const vec_t& v) { x += v.x; y += v.y; z += v.z; w += v.w; return *this; } + vec_t& operator *= (const vec_t& v) { x *= v.x; y *= v.y; z *= v.z; w *= v.w; return *this; } + vec_t& operator *= (float v) { x *= v; y *= v; z *= v; w *= v; return *this; } + + vec_t operator * (float f) const; + vec_t operator - () const; + vec_t operator - (const vec_t& v) const; + vec_t operator + (const vec_t& v) const; + vec_t operator * (const vec_t& v) const; + + const vec_t& operator + () const { return (*this); } + float Length() const { return sqrtf(x * x + y * y + z * z); }; + float LengthSq() const { return (x * x + y * y + z * z); }; + vec_t Normalize() { (*this) *= (1.f / ( Length() > FLT_EPSILON ? Length() : FLT_EPSILON ) ); return (*this); } + vec_t Normalize(const vec_t& v) { this->Set(v.x, v.y, v.z, v.w); this->Normalize(); return (*this); } + vec_t Abs() const; + + void Cross(const vec_t& v) + { + vec_t res; + res.x = y * v.z - z * v.y; + res.y = z * v.x - x * v.z; + res.z = x * v.y - y * v.x; + + x = res.x; + y = res.y; + z = res.z; + w = 0.f; + } + + void Cross(const vec_t& v1, const vec_t& v2) + { + x = v1.y * v2.z - v1.z * v2.y; + y = v1.z * v2.x - v1.x * v2.z; + z = v1.x * v2.y - v1.y * v2.x; + w = 0.f; + } + + float Dot(const vec_t& v) const + { + return (x * v.x) + (y * v.y) + (z * v.z) + (w * v.w); + } + + float Dot3(const vec_t& v) const + { + return (x * v.x) + (y * v.y) + (z * v.z); + } + + void Transform(const matrix_t& matrix); + void Transform(const vec_t& s, const matrix_t& matrix); + + void TransformVector(const matrix_t& matrix); + void TransformPoint(const matrix_t& matrix); + void TransformVector(const vec_t& v, const matrix_t& matrix) { (*this) = v; this->TransformVector(matrix); } + void TransformPoint(const vec_t& v, const matrix_t& matrix) { (*this) = v; this->TransformPoint(matrix); } + + float& operator [] (size_t index) { return ((float*)&x)[index]; } + const float& operator [] (size_t index) const { return ((float*)&x)[index]; } + bool operator!=(const vec_t& other) const { return memcmp(this, &other, sizeof(vec_t)) != 0; } + }; + + vec_t makeVect(float _x, float _y, float _z = 0.f, float _w = 0.f) { vec_t res; res.x = _x; res.y = _y; res.z = _z; res.w = _w; return res; } + vec_t makeVect(ImVec2 v) { vec_t res; res.x = v.x; res.y = v.y; res.z = 0.f; res.w = 0.f; return res; } + vec_t vec_t::operator * (float f) const { return makeVect(x * f, y * f, z * f, w * f); } + vec_t vec_t::operator - () const { return makeVect(-x, -y, -z, -w); } + vec_t vec_t::operator - (const vec_t& v) const { return makeVect(x - v.x, y - v.y, z - v.z, w - v.w); } + vec_t vec_t::operator + (const vec_t& v) const { return makeVect(x + v.x, y + v.y, z + v.z, w + v.w); } + vec_t vec_t::operator * (const vec_t& v) const { return makeVect(x * v.x, y * v.y, z * v.z, w * v.w); } + vec_t vec_t::Abs() const { return makeVect(fabsf(x), fabsf(y), fabsf(z)); } + + vec_t Normalized(const vec_t& v) { vec_t res; res = v; res.Normalize(); return res; } + vec_t Cross(const vec_t& v1, const vec_t& v2) + { + vec_t res; + res.x = v1.y * v2.z - v1.z * v2.y; + res.y = v1.z * v2.x - v1.x * v2.z; + res.z = v1.x * v2.y - v1.y * v2.x; + res.w = 0.f; + return res; + } + + float Dot(const vec_t& v1, const vec_t& v2) + { + return (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z); + } + + vec_t BuildPlan(const vec_t& p_point1, const vec_t& p_normal) + { + vec_t normal, res; + normal.Normalize(p_normal); + res.w = normal.Dot(p_point1); + res.x = normal.x; + res.y = normal.y; + res.z = normal.z; + return res; + } + + struct matrix_t + { + public: + + union + { + float m[4][4]; + float m16[16]; + struct + { + vec_t right, up, dir, position; + } v; + vec_t component[4]; + }; + + operator float* () { return m16; } + operator const float* () const { return m16; } + void Translation(float _x, float _y, float _z) { this->Translation(makeVect(_x, _y, _z)); } + + void Translation(const vec_t& vt) + { + v.right.Set(1.f, 0.f, 0.f, 0.f); + v.up.Set(0.f, 1.f, 0.f, 0.f); + v.dir.Set(0.f, 0.f, 1.f, 0.f); + v.position.Set(vt.x, vt.y, vt.z, 1.f); + } + + void Scale(float _x, float _y, float _z) + { + v.right.Set(_x, 0.f, 0.f, 0.f); + v.up.Set(0.f, _y, 0.f, 0.f); + v.dir.Set(0.f, 0.f, _z, 0.f); + v.position.Set(0.f, 0.f, 0.f, 1.f); + } + void Scale(const vec_t& s) { Scale(s.x, s.y, s.z); } + + matrix_t& operator *= (const matrix_t& mat) + { + matrix_t tmpMat; + tmpMat = *this; + tmpMat.Multiply(mat); + *this = tmpMat; + return *this; + } + matrix_t operator * (const matrix_t& mat) const + { + matrix_t matT; + matT.Multiply(*this, mat); + return matT; + } + + void Multiply(const matrix_t& matrix) + { + matrix_t tmp; + tmp = *this; + + FPU_MatrixF_x_MatrixF((float*)&tmp, (float*)&matrix, (float*)this); + } + + void Multiply(const matrix_t& m1, const matrix_t& m2) + { + FPU_MatrixF_x_MatrixF((float*)&m1, (float*)&m2, (float*)this); + } + + float GetDeterminant() const + { + return m[0][0] * m[1][1] * m[2][2] + m[0][1] * m[1][2] * m[2][0] + m[0][2] * m[1][0] * m[2][1] - + m[0][2] * m[1][1] * m[2][0] - m[0][1] * m[1][0] * m[2][2] - m[0][0] * m[1][2] * m[2][1]; + } + + float Inverse(const matrix_t& srcMatrix, bool affine = false); + void SetToIdentity() + { + v.right.Set(1.f, 0.f, 0.f, 0.f); + v.up.Set(0.f, 1.f, 0.f, 0.f); + v.dir.Set(0.f, 0.f, 1.f, 0.f); + v.position.Set(0.f, 0.f, 0.f, 1.f); + } + void Transpose() + { + matrix_t tmpm; + for (int l = 0; l < 4; l++) + { + for (int c = 0; c < 4; c++) + { + tmpm.m[l][c] = m[c][l]; + } + } + (*this) = tmpm; + } + + void RotationAxis(const vec_t& axis, float angle); + + void OrthoNormalize() + { + v.right.Normalize(); + v.up.Normalize(); + v.dir.Normalize(); + } + }; + + void vec_t::Transform(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + w * matrix.m[3][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + w * matrix.m[3][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + w * matrix.m[3][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + w * matrix.m[3][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + void vec_t::Transform(const vec_t& s, const matrix_t& matrix) + { + *this = s; + Transform(matrix); + } + + void vec_t::TransformPoint(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + matrix.m[3][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + matrix.m[3][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + matrix.m[3][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + matrix.m[3][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + void vec_t::TransformVector(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + float matrix_t::Inverse(const matrix_t& srcMatrix, bool affine) + { + float det = 0; + + if (affine) + { + det = GetDeterminant(); + float s = 1 / det; + m[0][0] = (srcMatrix.m[1][1] * srcMatrix.m[2][2] - srcMatrix.m[1][2] * srcMatrix.m[2][1]) * s; + m[0][1] = (srcMatrix.m[2][1] * srcMatrix.m[0][2] - srcMatrix.m[2][2] * srcMatrix.m[0][1]) * s; + m[0][2] = (srcMatrix.m[0][1] * srcMatrix.m[1][2] - srcMatrix.m[0][2] * srcMatrix.m[1][1]) * s; + m[1][0] = (srcMatrix.m[1][2] * srcMatrix.m[2][0] - srcMatrix.m[1][0] * srcMatrix.m[2][2]) * s; + m[1][1] = (srcMatrix.m[2][2] * srcMatrix.m[0][0] - srcMatrix.m[2][0] * srcMatrix.m[0][2]) * s; + m[1][2] = (srcMatrix.m[0][2] * srcMatrix.m[1][0] - srcMatrix.m[0][0] * srcMatrix.m[1][2]) * s; + m[2][0] = (srcMatrix.m[1][0] * srcMatrix.m[2][1] - srcMatrix.m[1][1] * srcMatrix.m[2][0]) * s; + m[2][1] = (srcMatrix.m[2][0] * srcMatrix.m[0][1] - srcMatrix.m[2][1] * srcMatrix.m[0][0]) * s; + m[2][2] = (srcMatrix.m[0][0] * srcMatrix.m[1][1] - srcMatrix.m[0][1] * srcMatrix.m[1][0]) * s; + m[3][0] = -(m[0][0] * srcMatrix.m[3][0] + m[1][0] * srcMatrix.m[3][1] + m[2][0] * srcMatrix.m[3][2]); + m[3][1] = -(m[0][1] * srcMatrix.m[3][0] + m[1][1] * srcMatrix.m[3][1] + m[2][1] * srcMatrix.m[3][2]); + m[3][2] = -(m[0][2] * srcMatrix.m[3][0] + m[1][2] * srcMatrix.m[3][1] + m[2][2] * srcMatrix.m[3][2]); + } + else + { + // transpose matrix + float src[16]; + for (int i = 0; i < 4; ++i) + { + src[i] = srcMatrix.m16[i * 4]; + src[i + 4] = srcMatrix.m16[i * 4 + 1]; + src[i + 8] = srcMatrix.m16[i * 4 + 2]; + src[i + 12] = srcMatrix.m16[i * 4 + 3]; + } + + // calculate pairs for first 8 elements (cofactors) + float tmp[12]; // temp array for pairs + tmp[0] = src[10] * src[15]; + tmp[1] = src[11] * src[14]; + tmp[2] = src[9] * src[15]; + tmp[3] = src[11] * src[13]; + tmp[4] = src[9] * src[14]; + tmp[5] = src[10] * src[13]; + tmp[6] = src[8] * src[15]; + tmp[7] = src[11] * src[12]; + tmp[8] = src[8] * src[14]; + tmp[9] = src[10] * src[12]; + tmp[10] = src[8] * src[13]; + tmp[11] = src[9] * src[12]; + + // calculate first 8 elements (cofactors) + m16[0] = (tmp[0] * src[5] + tmp[3] * src[6] + tmp[4] * src[7]) - (tmp[1] * src[5] + tmp[2] * src[6] + tmp[5] * src[7]); + m16[1] = (tmp[1] * src[4] + tmp[6] * src[6] + tmp[9] * src[7]) - (tmp[0] * src[4] + tmp[7] * src[6] + tmp[8] * src[7]); + m16[2] = (tmp[2] * src[4] + tmp[7] * src[5] + tmp[10] * src[7]) - (tmp[3] * src[4] + tmp[6] * src[5] + tmp[11] * src[7]); + m16[3] = (tmp[5] * src[4] + tmp[8] * src[5] + tmp[11] * src[6]) - (tmp[4] * src[4] + tmp[9] * src[5] + tmp[10] * src[6]); + m16[4] = (tmp[1] * src[1] + tmp[2] * src[2] + tmp[5] * src[3]) - (tmp[0] * src[1] + tmp[3] * src[2] + tmp[4] * src[3]); + m16[5] = (tmp[0] * src[0] + tmp[7] * src[2] + tmp[8] * src[3]) - (tmp[1] * src[0] + tmp[6] * src[2] + tmp[9] * src[3]); + m16[6] = (tmp[3] * src[0] + tmp[6] * src[1] + tmp[11] * src[3]) - (tmp[2] * src[0] + tmp[7] * src[1] + tmp[10] * src[3]); + m16[7] = (tmp[4] * src[0] + tmp[9] * src[1] + tmp[10] * src[2]) - (tmp[5] * src[0] + tmp[8] * src[1] + tmp[11] * src[2]); + + // calculate pairs for second 8 elements (cofactors) + tmp[0] = src[2] * src[7]; + tmp[1] = src[3] * src[6]; + tmp[2] = src[1] * src[7]; + tmp[3] = src[3] * src[5]; + tmp[4] = src[1] * src[6]; + tmp[5] = src[2] * src[5]; + tmp[6] = src[0] * src[7]; + tmp[7] = src[3] * src[4]; + tmp[8] = src[0] * src[6]; + tmp[9] = src[2] * src[4]; + tmp[10] = src[0] * src[5]; + tmp[11] = src[1] * src[4]; + + // calculate second 8 elements (cofactors) + m16[8] = (tmp[0] * src[13] + tmp[3] * src[14] + tmp[4] * src[15]) - (tmp[1] * src[13] + tmp[2] * src[14] + tmp[5] * src[15]); + m16[9] = (tmp[1] * src[12] + tmp[6] * src[14] + tmp[9] * src[15]) - (tmp[0] * src[12] + tmp[7] * src[14] + tmp[8] * src[15]); + m16[10] = (tmp[2] * src[12] + tmp[7] * src[13] + tmp[10] * src[15]) - (tmp[3] * src[12] + tmp[6] * src[13] + tmp[11] * src[15]); + m16[11] = (tmp[5] * src[12] + tmp[8] * src[13] + tmp[11] * src[14]) - (tmp[4] * src[12] + tmp[9] * src[13] + tmp[10] * src[14]); + m16[12] = (tmp[2] * src[10] + tmp[5] * src[11] + tmp[1] * src[9]) - (tmp[4] * src[11] + tmp[0] * src[9] + tmp[3] * src[10]); + m16[13] = (tmp[8] * src[11] + tmp[0] * src[8] + tmp[7] * src[10]) - (tmp[6] * src[10] + tmp[9] * src[11] + tmp[1] * src[8]); + m16[14] = (tmp[6] * src[9] + tmp[11] * src[11] + tmp[3] * src[8]) - (tmp[10] * src[11] + tmp[2] * src[8] + tmp[7] * src[9]); + m16[15] = (tmp[10] * src[10] + tmp[4] * src[8] + tmp[9] * src[9]) - (tmp[8] * src[9] + tmp[11] * src[10] + tmp[5] * src[8]); + + // calculate determinant + det = src[0] * m16[0] + src[1] * m16[1] + src[2] * m16[2] + src[3] * m16[3]; + + // calculate matrix inverse + float invdet = 1 / det; + for (int j = 0; j < 16; ++j) + { + m16[j] *= invdet; + } + } + + return det; + } + + void matrix_t::RotationAxis(const vec_t& axis, float angle) + { + float length2 = axis.LengthSq(); + if (length2 < FLT_EPSILON) + { + SetToIdentity(); + return; + } + + vec_t n = axis * (1.f / sqrtf(length2)); + float s = sinf(angle); + float c = cosf(angle); + float k = 1.f - c; + + float xx = n.x * n.x * k + c; + float yy = n.y * n.y * k + c; + float zz = n.z * n.z * k + c; + float xy = n.x * n.y * k; + float yz = n.y * n.z * k; + float zx = n.z * n.x * k; + float xs = n.x * s; + float ys = n.y * s; + float zs = n.z * s; + + m[0][0] = xx; + m[0][1] = xy + zs; + m[0][2] = zx - ys; + m[0][3] = 0.f; + m[1][0] = xy - zs; + m[1][1] = yy; + m[1][2] = yz + xs; + m[1][3] = 0.f; + m[2][0] = zx + ys; + m[2][1] = yz - xs; + m[2][2] = zz; + m[2][3] = 0.f; + m[3][0] = 0.f; + m[3][1] = 0.f; + m[3][2] = 0.f; + m[3][3] = 1.f; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + + enum MOVETYPE + { + MT_NONE, + MT_MOVE_X, + MT_MOVE_Y, + MT_MOVE_Z, + MT_MOVE_YZ, + MT_MOVE_ZX, + MT_MOVE_XY, + MT_MOVE_SCREEN, + MT_ROTATE_X, + MT_ROTATE_Y, + MT_ROTATE_Z, + MT_ROTATE_SCREEN, + MT_SCALE_X, + MT_SCALE_Y, + MT_SCALE_Z, + MT_SCALE_XYZ + }; + + static bool IsTranslateType(int type) + { + return type >= MT_MOVE_X && type <= MT_MOVE_SCREEN; + } + + static bool IsRotateType(int type) + { + return type >= MT_ROTATE_X && type <= MT_ROTATE_SCREEN; + } + + static bool IsScaleType(int type) + { + return type >= MT_SCALE_X && type <= MT_SCALE_XYZ; + } + + // Matches MT_MOVE_AB order + static const OPERATION TRANSLATE_PLANS[3] = { TRANSLATE_Y | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Y }; + + Style::Style() + { + // default values + TranslationLineThickness = 3.0f; + TranslationLineArrowSize = 6.0f; + RotationLineThickness = 2.0f; + RotationOuterLineThickness = 3.0f; + ScaleLineThickness = 3.0f; + ScaleLineCircleSize = 6.0f; + HatchedAxisLineThickness = 6.0f; + CenterCircleSize = 6.0f; + + // initialize default colors + Colors[DIRECTION_X] = ImVec4(0.666f, 0.000f, 0.000f, 1.000f); + Colors[DIRECTION_Y] = ImVec4(0.000f, 0.666f, 0.000f, 1.000f); + Colors[DIRECTION_Z] = ImVec4(0.000f, 0.000f, 0.666f, 1.000f); + Colors[PLANE_X] = ImVec4(0.666f, 0.000f, 0.000f, 0.380f); + Colors[PLANE_Y] = ImVec4(0.000f, 0.666f, 0.000f, 0.380f); + Colors[PLANE_Z] = ImVec4(0.000f, 0.000f, 0.666f, 0.380f); + Colors[SELECTION] = ImVec4(1.000f, 0.500f, 0.062f, 0.541f); + Colors[INACTIVE] = ImVec4(0.600f, 0.600f, 0.600f, 0.600f); + Colors[TRANSLATION_LINE] = ImVec4(0.666f, 0.666f, 0.666f, 0.666f); + Colors[SCALE_LINE] = ImVec4(0.250f, 0.250f, 0.250f, 1.000f); + Colors[ROTATION_USING_BORDER] = ImVec4(1.000f, 0.500f, 0.062f, 1.000f); + Colors[ROTATION_USING_FILL] = ImVec4(1.000f, 0.500f, 0.062f, 0.500f); + Colors[HATCHED_AXIS_LINES] = ImVec4(0.000f, 0.000f, 0.000f, 0.500f); + Colors[TEXT] = ImVec4(1.000f, 1.000f, 1.000f, 1.000f); + Colors[TEXT_SHADOW] = ImVec4(0.000f, 0.000f, 0.000f, 1.000f); + Colors[FACE] = ImVec4(0.776f, 0.804f, 0.839f, 1.000f); + + strcpy(AxisLabels[Axis_X], "x"); + strcpy(AxisLabels[Axis_Y], "y"); + strcpy(AxisLabels[Axis_Z], "z"); + + strcpy(FaceLabels[FACE_BACK], "back"); + strcpy(FaceLabels[FACE_TOP], "top"); + strcpy(FaceLabels[FACE_RIGHT], "right"); + strcpy(FaceLabels[FACE_FRONT], "front"); + strcpy(FaceLabels[FACE_BOTTOM], "bottom"); + strcpy(FaceLabels[FACE_LEFT], "left"); + } + + struct Context + { + Context() : mbUsing(false), mbEnable(true), mbUsingBounds(false) + { + } + + ImDrawList* mDrawList; + Style mStyle; + + MODE mMode; + matrix_t mViewMat; + matrix_t mProjectionMat; + matrix_t mModel; + matrix_t mModelLocal; // orthonormalized model + matrix_t mModelInverse; + matrix_t mModelSource; + matrix_t mModelSourceInverse; + matrix_t mMVP; + matrix_t mMVPLocal; // MVP with full model matrix whereas mMVP's model matrix might only be translation in case of World space edition + matrix_t mViewProjection; + + vec_t mModelScaleOrigin; + vec_t mCameraEye; + vec_t mCameraRight; + vec_t mCameraDir; + vec_t mCameraUp; + vec_t mRayOrigin; + vec_t mRayVector; + + float mRadiusSquareCenter; + ImVec2 mScreenSquareCenter; + ImVec2 mScreenSquareMin; + ImVec2 mScreenSquareMax; + + float mScreenFactor; + vec_t mRelativeOrigin; + + bool mbUsing; + bool mbEnable; + bool mbMouseOver; + bool mReversed; // reversed projection matrix + + // translation + vec_t mTranslationPlan; + vec_t mTranslationPlanOrigin; + vec_t mMatrixOrigin; + vec_t mTranslationLastDelta; + + // rotation + vec_t mRotationVectorSource; + float mRotationAngle; + float mRotationAngleOrigin; + //vec_t mWorldToLocalAxis; + + // scale + vec_t mScale; + vec_t mScaleValueOrigin; + vec_t mScaleLast; + float mSaveMousePosx; + + // save axis factor when using gizmo + bool mBelowAxisLimit[3]; + bool mBelowPlaneLimit[3]; + float mAxisFactor[3]; + + float mAxisLimit=0.0025f; + float mPlaneLimit=0.02f; + + // bounds stretching + vec_t mBoundsPivot; + vec_t mBoundsAnchor; + vec_t mBoundsPlan; + vec_t mBoundsLocalPivot; + int mBoundsBestAxis; + int mBoundsAxis[2]; + bool mbUsingBounds; + matrix_t mBoundsMatrix; + + // + int mCurrentOperation; + + float mX = 0.f; + float mY = 0.f; + float mWidth = 0.f; + float mHeight = 0.f; + float mXMax = 0.f; + float mYMax = 0.f; + float mDisplayRatio = 1.f; + + bool mIsOrthographic = false; + + int mActualID = -1; + int mEditingID = -1; + OPERATION mOperation = OPERATION(-1); + + bool mAllowAxisFlip = true; + float mGizmoSizeClipSpace = 0.1f; + }; + + static Context gContext; + + static const vec_t directionUnary[3] = { makeVect(1.f, 0.f, 0.f), makeVect(0.f, 1.f, 0.f), makeVect(0.f, 0.f, 1.f) }; + static const char* translationInfoMask[] = { "X : %5.3f", "Y : %5.3f", "Z : %5.3f", + "Y : %5.3f Z : %5.3f", "X : %5.3f Z : %5.3f", "X : %5.3f Y : %5.3f", + "X : %5.3f Y : %5.3f Z : %5.3f" }; + static const char* scaleInfoMask[] = { "X : %5.2f", "Y : %5.2f", "Z : %5.2f", "XYZ : %5.2f" }; + static const char* rotationInfoMask[] = { "X : %5.2f deg %5.2f rad", "Y : %5.2f deg %5.2f rad", "Z : %5.2f deg %5.2f rad", "Screen : %5.2f deg %5.2f rad" }; + static const int translationInfoIndex[] = { 0,0,0, 1,0,0, 2,0,0, 1,2,0, 0,2,0, 0,1,0, 0,1,2 }; + static const float quadMin = 0.5f; + static const float quadMax = 0.8f; + static const float quadUV[8] = { quadMin, quadMin, quadMin, quadMax, quadMax, quadMax, quadMax, quadMin }; + static const int halfCircleSegmentCount = 64; + static const float snapTension = 0.5f; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion); + static int GetRotateType(OPERATION op); + static int GetScaleType(OPERATION op); + + Style& GetStyle() + { + return gContext.mStyle; + } + + static ImU32 GetColorU32(int idx) + { + IM_ASSERT(idx < COLOR::COUNT); + return ImGui::ColorConvertFloat4ToU32(gContext.mStyle.Colors[idx]); + } + + static ImVec2 worldToPos(const vec_t& worldPos, const matrix_t& mat, ImVec2 position = ImVec2(gContext.mX, gContext.mY), ImVec2 size = ImVec2(gContext.mWidth, gContext.mHeight)) + { + vec_t trans; + trans.TransformPoint(worldPos, mat); + trans *= 0.5f / trans.w; + trans += makeVect(0.5f, 0.5f); + trans.y = 1.f - trans.y; + trans.x *= size.x; + trans.y *= size.y; + trans.x += position.x; + trans.y += position.y; + return ImVec2(trans.x, trans.y); + } + + static void ComputeCameraRay(vec_t& rayOrigin, vec_t& rayDir, ImVec2 position = ImVec2(gContext.mX, gContext.mY), ImVec2 size = ImVec2(gContext.mWidth, gContext.mHeight)) + { + ImGuiIO& io = ImGui::GetIO(); + + matrix_t mViewProjInverse; + mViewProjInverse.Inverse(gContext.mViewMat * gContext.mProjectionMat); + + const float mox = ((io.MousePos.x - position.x) / size.x) * 2.f - 1.f; + const float moy = (1.f - ((io.MousePos.y - position.y) / size.y)) * 2.f - 1.f; + + const float zNear = gContext.mReversed ? (1.f - FLT_EPSILON) : 0.f; + const float zFar = gContext.mReversed ? 0.f : (1.f - FLT_EPSILON); + + rayOrigin.Transform(makeVect(mox, moy, zNear, 1.f), mViewProjInverse); + rayOrigin *= 1.f / rayOrigin.w; + vec_t rayEnd; + rayEnd.Transform(makeVect(mox, moy, zFar, 1.f), mViewProjInverse); + rayEnd *= 1.f / rayEnd.w; + rayDir = Normalized(rayEnd - rayOrigin); + } + + static float GetSegmentLengthClipSpace(const vec_t& start, const vec_t& end, const bool localCoordinates = false) + { + vec_t startOfSegment = start; + const matrix_t& mvp = localCoordinates ? gContext.mMVPLocal : gContext.mMVP; + startOfSegment.TransformPoint(mvp); + if (fabsf(startOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction + { + startOfSegment *= 1.f / startOfSegment.w; + } + + vec_t endOfSegment = end; + endOfSegment.TransformPoint(mvp); + if (fabsf(endOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction + { + endOfSegment *= 1.f / endOfSegment.w; + } + + vec_t clipSpaceAxis = endOfSegment - startOfSegment; + if (gContext.mDisplayRatio < 1.0) + clipSpaceAxis.x *= gContext.mDisplayRatio; + else + clipSpaceAxis.y /= gContext.mDisplayRatio; + float segmentLengthInClipSpace = sqrtf(clipSpaceAxis.x * clipSpaceAxis.x + clipSpaceAxis.y * clipSpaceAxis.y); + return segmentLengthInClipSpace; + } + + static float GetParallelogram(const vec_t& ptO, const vec_t& ptA, const vec_t& ptB) + { + vec_t pts[] = { ptO, ptA, ptB }; + for (unsigned int i = 0; i < 3; i++) + { + pts[i].TransformPoint(gContext.mMVP); + if (fabsf(pts[i].w) > FLT_EPSILON) // check for axis aligned with camera direction + { + pts[i] *= 1.f / pts[i].w; + } + } + vec_t segA = pts[1] - pts[0]; + vec_t segB = pts[2] - pts[0]; + segA.y /= gContext.mDisplayRatio; + segB.y /= gContext.mDisplayRatio; + vec_t segAOrtho = makeVect(-segA.y, segA.x); + segAOrtho.Normalize(); + float dt = segAOrtho.Dot3(segB); + float surface = sqrtf(segA.x * segA.x + segA.y * segA.y) * fabsf(dt); + return surface; + } + + inline vec_t PointOnSegment(const vec_t& point, const vec_t& vertPos1, const vec_t& vertPos2) + { + vec_t c = point - vertPos1; + vec_t V; + + V.Normalize(vertPos2 - vertPos1); + float d = (vertPos2 - vertPos1).Length(); + float t = V.Dot3(c); + + if (t < 0.f) + { + return vertPos1; + } + + if (t > d) + { + return vertPos2; + } + + return vertPos1 + V * t; + } + + static float IntersectRayPlane(const vec_t& rOrigin, const vec_t& rVector, const vec_t& plan) + { + const float numer = plan.Dot3(rOrigin) - plan.w; + const float denom = plan.Dot3(rVector); + + if (fabsf(denom) < FLT_EPSILON) // normal is orthogonal to vector, cant intersect + { + return -1.0f; + } + + return -(numer / denom); + } + + static float DistanceToPlane(const vec_t& point, const vec_t& plan) + { + return plan.Dot3(point) + plan.w; + } + + static bool IsInContextRect(ImVec2 p) + { + return IsWithin(p.x, gContext.mX, gContext.mXMax) && IsWithin(p.y, gContext.mY, gContext.mYMax); + } + + static bool IsHoveringWindow() + { + ImGuiContext& g = *ImGui::GetCurrentContext(); + ImGuiWindow* window = ImGui::FindWindowByName(gContext.mDrawList->_OwnerName); + if (g.HoveredWindow == window) // Mouse hovering drawlist window + return true; + if (g.HoveredWindow != NULL) // Any other window is hovered + return false; + if (ImGui::IsMouseHoveringRect(window->InnerRect.Min, window->InnerRect.Max, false)) // Hovering drawlist window rect, while no other window is hovered (for _NoInputs windows) + return true; + return false; + } + + void SetRect(float x, float y, float width, float height) + { + gContext.mX = x; + gContext.mY = y; + gContext.mWidth = width; + gContext.mHeight = height; + gContext.mXMax = gContext.mX + gContext.mWidth; + gContext.mYMax = gContext.mY + gContext.mXMax; + gContext.mDisplayRatio = width / height; + } + + void SetOrthographic(bool isOrthographic) + { + gContext.mIsOrthographic = isOrthographic; + } + + void SetDrawlist(ImDrawList* drawlist) + { + gContext.mDrawList = drawlist ? drawlist : ImGui::GetWindowDrawList(); + } + + void SetImGuiContext(ImGuiContext* ctx) + { + ImGui::SetCurrentContext(ctx); + } + + void BeginFrame() + { + const ImU32 flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus; + +#ifdef IMGUI_HAS_VIEWPORT + ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->Pos); +#else + ImGuiIO& io = ImGui::GetIO(); + ImGui::SetNextWindowSize(io.DisplaySize); + ImGui::SetNextWindowPos(ImVec2(0, 0)); +#endif + + ImGui::PushStyleColor(ImGuiCol_WindowBg, 0); + ImGui::PushStyleColor(ImGuiCol_Border, 0); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + + ImGui::Begin("gizmo", NULL, flags); + gContext.mDrawList = ImGui::GetWindowDrawList(); + ImGui::End(); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(2); + } + + bool IsUsing() + { + return (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) || gContext.mbUsingBounds; + } + + bool IsUsingAny() + { + return gContext.mbUsing || gContext.mbUsingBounds; + } + + bool IsOver() + { + return (Intersects(gContext.mOperation, TRANSLATE) && GetMoveType(gContext.mOperation, NULL) != MT_NONE) || + (Intersects(gContext.mOperation, ROTATE) && GetRotateType(gContext.mOperation) != MT_NONE) || + (Intersects(gContext.mOperation, SCALE) && GetScaleType(gContext.mOperation) != MT_NONE) || IsUsing(); + } + + bool IsOver(OPERATION op) + { + if(IsUsing()) + { + return true; + } + if(Intersects(op, SCALE) && GetScaleType(op) != MT_NONE) + { + return true; + } + if(Intersects(op, ROTATE) && GetRotateType(op) != MT_NONE) + { + return true; + } + if(Intersects(op, TRANSLATE) && GetMoveType(op, NULL) != MT_NONE) + { + return true; + } + return false; + } + + void Enable(bool enable) + { + gContext.mbEnable = enable; + if (!enable) + { + gContext.mbUsing = false; + gContext.mbUsingBounds = false; + } + } + + static void ComputeContext(const float* view, const float* projection, float* matrix, MODE mode) + { + gContext.mMode = mode; + gContext.mViewMat = *(matrix_t*)view; + gContext.mProjectionMat = *(matrix_t*)projection; + gContext.mbMouseOver = IsHoveringWindow(); + + gContext.mModelLocal = *(matrix_t*)matrix; + gContext.mModelLocal.OrthoNormalize(); + + if (mode == LOCAL) + { + gContext.mModel = gContext.mModelLocal; + } + else + { + gContext.mModel.Translation(((matrix_t*)matrix)->v.position); + } + gContext.mModelSource = *(matrix_t*)matrix; + gContext.mModelScaleOrigin.Set(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); + + gContext.mModelInverse.Inverse(gContext.mModel); + gContext.mModelSourceInverse.Inverse(gContext.mModelSource); + gContext.mViewProjection = gContext.mViewMat * gContext.mProjectionMat; + gContext.mMVP = gContext.mModel * gContext.mViewProjection; + gContext.mMVPLocal = gContext.mModelLocal * gContext.mViewProjection; + + matrix_t viewInverse; + viewInverse.Inverse(gContext.mViewMat); + gContext.mCameraDir = viewInverse.v.dir; + gContext.mCameraEye = viewInverse.v.position; + gContext.mCameraRight = viewInverse.v.right; + gContext.mCameraUp = viewInverse.v.up; + + // projection reverse + vec_t nearPos, farPos; + nearPos.Transform(makeVect(0, 0, 1.f, 1.f), gContext.mProjectionMat); + farPos.Transform(makeVect(0, 0, 2.f, 1.f), gContext.mProjectionMat); + + gContext.mReversed = (nearPos.z/nearPos.w) > (farPos.z / farPos.w); + + // compute scale from the size of camera right vector projected on screen at the matrix position + vec_t pointRight = viewInverse.v.right; + pointRight.TransformPoint(gContext.mViewProjection); + gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / (pointRight.x / pointRight.w - gContext.mMVP.v.position.x / gContext.mMVP.v.position.w); + + vec_t rightViewInverse = viewInverse.v.right; + rightViewInverse.TransformVector(gContext.mModelInverse); + float rightLength = GetSegmentLengthClipSpace(makeVect(0.f, 0.f), rightViewInverse); + gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / rightLength; + + ImVec2 centerSSpace = worldToPos(makeVect(0.f, 0.f), gContext.mMVP); + gContext.mScreenSquareCenter = centerSSpace; + gContext.mScreenSquareMin = ImVec2(centerSSpace.x - 10.f, centerSSpace.y - 10.f); + gContext.mScreenSquareMax = ImVec2(centerSSpace.x + 10.f, centerSSpace.y + 10.f); + + ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector); + } + + static void ComputeColors(ImU32* colors, int type, OPERATION operation) + { + if (gContext.mbEnable) + { + ImU32 selectionColor = GetColorU32(SELECTION); + + switch (operation) + { + case TRANSLATE: + colors[0] = (type == MT_MOVE_SCREEN) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_MOVE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i); + colors[i + 4] = (type == (int)(MT_MOVE_YZ + i)) ? selectionColor : GetColorU32(PLANE_X + i); + colors[i + 4] = (type == MT_MOVE_SCREEN) ? selectionColor : colors[i + 4]; + } + break; + case ROTATE: + colors[0] = (type == MT_ROTATE_SCREEN) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_ROTATE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i); + } + break; + case SCALEU: + case SCALE: + colors[0] = (type == MT_SCALE_XYZ) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_SCALE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i); + } + break; + // note: this internal function is only called with three possible values for operation + default: + break; + } + } + else + { + ImU32 inactiveColor = GetColorU32(INACTIVE); + for (int i = 0; i < 7; i++) + { + colors[i] = inactiveColor; + } + } + } + + static void ComputeTripodAxisAndVisibility(const int axisIndex, vec_t& dirAxis, vec_t& dirPlaneX, vec_t& dirPlaneY, bool& belowAxisLimit, bool& belowPlaneLimit, const bool localCoordinates = false) + { + dirAxis = directionUnary[axisIndex]; + dirPlaneX = directionUnary[(axisIndex + 1) % 3]; + dirPlaneY = directionUnary[(axisIndex + 2) % 3]; + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + // when using, use stored factors so the gizmo doesn't flip when we translate + belowAxisLimit = gContext.mBelowAxisLimit[axisIndex]; + belowPlaneLimit = gContext.mBelowPlaneLimit[axisIndex]; + + dirAxis *= gContext.mAxisFactor[axisIndex]; + dirPlaneX *= gContext.mAxisFactor[(axisIndex + 1) % 3]; + dirPlaneY *= gContext.mAxisFactor[(axisIndex + 2) % 3]; + } + else + { + // new method + float lenDir = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirAxis, localCoordinates); + float lenDirMinus = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirAxis, localCoordinates); + + float lenDirPlaneX = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirPlaneX, localCoordinates); + float lenDirMinusPlaneX = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirPlaneX, localCoordinates); + + float lenDirPlaneY = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirPlaneY, localCoordinates); + float lenDirMinusPlaneY = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirPlaneY, localCoordinates); + + // For readability + bool & allowFlip = gContext.mAllowAxisFlip; + float mulAxis = (allowFlip && lenDir < lenDirMinus&& fabsf(lenDir - lenDirMinus) > FLT_EPSILON) ? -1.f : 1.f; + float mulAxisX = (allowFlip && lenDirPlaneX < lenDirMinusPlaneX&& fabsf(lenDirPlaneX - lenDirMinusPlaneX) > FLT_EPSILON) ? -1.f : 1.f; + float mulAxisY = (allowFlip && lenDirPlaneY < lenDirMinusPlaneY&& fabsf(lenDirPlaneY - lenDirMinusPlaneY) > FLT_EPSILON) ? -1.f : 1.f; + dirAxis *= mulAxis; + dirPlaneX *= mulAxisX; + dirPlaneY *= mulAxisY; + + // for axis + float axisLengthInClipSpace = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirAxis * gContext.mScreenFactor, localCoordinates); + + float paraSurf = GetParallelogram(makeVect(0.f, 0.f, 0.f), dirPlaneX * gContext.mScreenFactor, dirPlaneY * gContext.mScreenFactor); + belowPlaneLimit = (paraSurf > gContext.mAxisLimit); + belowAxisLimit = (axisLengthInClipSpace > gContext.mPlaneLimit); + + // and store values + gContext.mAxisFactor[axisIndex] = mulAxis; + gContext.mAxisFactor[(axisIndex + 1) % 3] = mulAxisX; + gContext.mAxisFactor[(axisIndex + 2) % 3] = mulAxisY; + gContext.mBelowAxisLimit[axisIndex] = belowAxisLimit; + gContext.mBelowPlaneLimit[axisIndex] = belowPlaneLimit; + } + } + + static void ComputeSnap(float* value, float snap) + { + if (snap <= FLT_EPSILON) + { + return; + } + + float modulo = fmodf(*value, snap); + float moduloRatio = fabsf(modulo) / snap; + if (moduloRatio < snapTension) + { + *value -= modulo; + } + else if (moduloRatio > (1.f - snapTension)) + { + *value = *value - modulo + snap * ((*value < 0.f) ? -1.f : 1.f); + } + } + static void ComputeSnap(vec_t& value, const float* snap) + { + for (int i = 0; i < 3; i++) + { + ComputeSnap(&value[i], snap[i]); + } + } + + static float ComputeAngleOnPlan() + { + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t localPos = Normalized(gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position); + + vec_t perpendicularVector; + perpendicularVector.Cross(gContext.mRotationVectorSource, gContext.mTranslationPlan); + perpendicularVector.Normalize(); + float acosAngle = Clamp(Dot(localPos, gContext.mRotationVectorSource), -1.f, 1.f); + float angle = acosf(acosAngle); + angle *= (Dot(localPos, perpendicularVector) < 0.f) ? 1.f : -1.f; + return angle; + } + + static void DrawRotationGizmo(OPERATION op, int type) + { + if(!Intersects(op, ROTATE)) + { + return; + } + ImDrawList* drawList = gContext.mDrawList; + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, ROTATE); + + vec_t cameraToModelNormalized; + if (gContext.mIsOrthographic) + { + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)&gContext.mViewMat); + cameraToModelNormalized = -viewInverse.v.dir; + } + else + { + cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye); + } + + cameraToModelNormalized.TransformVector(gContext.mModelInverse); + + gContext.mRadiusSquareCenter = screenRotateSize * gContext.mHeight; + + bool hasRSC = Intersects(op, ROTATE_SCREEN); + for (int axis = 0; axis < 3; axis++) + { + if(!Intersects(op, static_cast(ROTATE_Z >> axis))) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_ROTATE_Z - axis); + const int circleMul = (hasRSC && !usingAxis ) ? 1 : 2; + + ImVec2* circlePos = (ImVec2*)alloca(sizeof(ImVec2) * (circleMul * halfCircleSegmentCount + 1)); + + float angleStart = atan2f(cameraToModelNormalized[(4 - axis) % 3], cameraToModelNormalized[(3 - axis) % 3]) + ZPI * 0.5f; + + for (int i = 0; i < circleMul * halfCircleSegmentCount + 1; i++) + { + float ng = angleStart + (float)circleMul * ZPI * ((float)i / (float)(circleMul * halfCircleSegmentCount)); + vec_t axisPos = makeVect(cosf(ng), sinf(ng), 0.f); + vec_t pos = makeVect(axisPos[axis], axisPos[(axis + 1) % 3], axisPos[(axis + 2) % 3]) * gContext.mScreenFactor * rotationDisplayFactor; + circlePos[i] = worldToPos(pos, gContext.mMVP); + } + if (!gContext.mbUsing || usingAxis) + { + drawList->AddPolyline(circlePos, circleMul* halfCircleSegmentCount + 1, colors[3 - axis], false, gContext.mStyle.RotationLineThickness); + } + + float radiusAxis = sqrtf((ImLengthSqr(worldToPos(gContext.mModel.v.position, gContext.mViewProjection) - circlePos[0]))); + if (radiusAxis > gContext.mRadiusSquareCenter) + { + gContext.mRadiusSquareCenter = radiusAxis; + } + } + if(hasRSC && (!gContext.mbUsing || type == MT_ROTATE_SCREEN)) + { + drawList->AddCircle(worldToPos(gContext.mModel.v.position, gContext.mViewProjection), gContext.mRadiusSquareCenter, colors[0], 64, gContext.mStyle.RotationOuterLineThickness); + } + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsRotateType(type)) + { + ImVec2 circlePos[halfCircleSegmentCount + 1]; + + circlePos[0] = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + for (unsigned int i = 1; i < halfCircleSegmentCount + 1; i++) + { + float ng = gContext.mRotationAngle * ((float)(i - 1) / (float)(halfCircleSegmentCount - 1)); + matrix_t rotateVectorMatrix; + rotateVectorMatrix.RotationAxis(gContext.mTranslationPlan, ng); + vec_t pos; + pos.TransformPoint(gContext.mRotationVectorSource, rotateVectorMatrix); + pos *= gContext.mScreenFactor * rotationDisplayFactor; + circlePos[i] = worldToPos(pos + gContext.mModel.v.position, gContext.mViewProjection); + } + drawList->AddConvexPolyFilled(circlePos, halfCircleSegmentCount + 1, GetColorU32(ROTATION_USING_FILL)); + drawList->AddPolyline(circlePos, halfCircleSegmentCount + 1, GetColorU32(ROTATION_USING_BORDER), true, gContext.mStyle.RotationLineThickness); + + ImVec2 destinationPosOnScreen = circlePos[1]; + char tmps[512]; + ImFormatString(tmps, sizeof(tmps), rotationInfoMask[type - MT_ROTATE_X], (gContext.mRotationAngle / ZPI) * 180.f, gContext.mRotationAngle); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + static void DrawHatchedAxis(const vec_t& axis) + { + if (gContext.mStyle.HatchedAxisLineThickness <= 0.0f) + { + return; + } + + for (int j = 1; j < 10; j++) + { + ImVec2 baseSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2) * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2 + 1) * gContext.mScreenFactor, gContext.mMVP); + gContext.mDrawList->AddLine(baseSSpace2, worldDirSSpace2, GetColorU32(HATCHED_AXIS_LINES), gContext.mStyle.HatchedAxisLineThickness); + } + } + + static void DrawScaleGizmo(OPERATION op, int type) + { + ImDrawList* drawList = gContext.mDrawList; + + if(!Intersects(op, SCALE)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, SCALE); + + // draw + vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f }; + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + scaleDisplay = gContext.mScale; + } + + for (int i = 0; i < 3; i++) + { + if(!Intersects(op, static_cast(SCALE_X << i))) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_SCALE_X + i); + if (!gContext.mbUsing || usingAxis) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVP); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + ImU32 scaleLineColor = GetColorU32(SCALE_LINE); + drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, scaleLineColor, gContext.mStyle.ScaleLineThickness); + drawList->AddCircleFilled(worldDirSSpaceNoScale, gContext.mStyle.ScaleLineCircleSize, scaleLineColor); + } + + if (!hasTranslateOnAxis || gContext.mbUsing) + { + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], gContext.mStyle.ScaleLineThickness); + } + drawList->AddCircleFilled(worldDirSSpace, gContext.mStyle.ScaleLineCircleSize, colors[i + 1]); + + if (gContext.mAxisFactor[i] < 0.f) + { + DrawHatchedAxis(dirAxis * scaleDisplay[i]); + } + } + } + } + + // draw screen cirle + drawList->AddCircleFilled(gContext.mScreenSquareCenter, gContext.mStyle.CenterCircleSize, colors[0], 32); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(type)) + { + //ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y); + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + */ + char tmps[512]; + //vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_SCALE_X) * 3; + ImFormatString(tmps, sizeof(tmps), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + + static void DrawScaleUniveralGizmo(OPERATION op, int type) + { + ImDrawList* drawList = gContext.mDrawList; + + if (!Intersects(op, SCALEU)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, SCALEU); + + // draw + vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f }; + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + scaleDisplay = gContext.mScale; + } + + for (int i = 0; i < 3; i++) + { + if (!Intersects(op, static_cast(SCALE_XU << i))) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_SCALE_X + i); + if (!gContext.mbUsing || usingAxis) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + //ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVPLocal); + //ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVPLocal); + +#if 0 + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, IM_COL32(0x40, 0x40, 0x40, 0xFF), 3.f); + drawList->AddCircleFilled(worldDirSSpaceNoScale, 6.f, IM_COL32(0x40, 0x40, 0x40, 0xFF)); + } + /* + if (!hasTranslateOnAxis || gContext.mbUsing) + { + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f); + } + */ +#endif + drawList->AddCircleFilled(worldDirSSpace, 12.f, colors[i + 1]); + } + } + } + + // draw screen cirle + drawList->AddCircle(gContext.mScreenSquareCenter, 20.f, colors[0], 32, gContext.mStyle.CenterCircleSize); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(type)) + { + //ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y); + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + */ + char tmps[512]; + //vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_SCALE_X) * 3; + ImFormatString(tmps, sizeof(tmps), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + static void DrawTranslationGizmo(OPERATION op, int type) + { + ImDrawList* drawList = gContext.mDrawList; + if (!drawList) + { + return; + } + + if(!Intersects(op, TRANSLATE)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, TRANSLATE); + + const ImVec2 origin = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + + // draw + bool belowAxisLimit = false; + bool belowPlaneLimit = false; + for (int i = 0; i < 3; ++i) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + + if (!gContext.mbUsing || (gContext.mbUsing && type == MT_MOVE_X + i)) + { + // draw axis + if (belowAxisLimit && Intersects(op, static_cast(TRANSLATE_X << i))) + { + ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos(dirAxis * gContext.mScreenFactor, gContext.mMVP); + + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], gContext.mStyle.TranslationLineThickness); + + // Arrow head begin + ImVec2 dir(origin - worldDirSSpace); + + float d = sqrtf(ImLengthSqr(dir)); + dir /= d; // Normalize + dir *= gContext.mStyle.TranslationLineArrowSize; + + ImVec2 ortogonalDir(dir.y, -dir.x); // Perpendicular vector + ImVec2 a(worldDirSSpace + dir); + drawList->AddTriangleFilled(worldDirSSpace - dir, a + ortogonalDir, a - ortogonalDir, colors[i + 1]); + // Arrow head end + + if (gContext.mAxisFactor[i] < 0.f) + { + DrawHatchedAxis(dirAxis); + } + } + } + // draw plane + if (!gContext.mbUsing || (gContext.mbUsing && type == MT_MOVE_YZ + i)) + { + if (belowPlaneLimit && Contains(op, TRANSLATE_PLANS[i])) + { + ImVec2 screenQuadPts[4]; + for (int j = 0; j < 4; ++j) + { + vec_t cornerWorldPos = (dirPlaneX * quadUV[j * 2] + dirPlaneY * quadUV[j * 2 + 1]) * gContext.mScreenFactor; + screenQuadPts[j] = worldToPos(cornerWorldPos, gContext.mMVP); + } + drawList->AddPolyline(screenQuadPts, 4, GetColorU32(DIRECTION_X + i), true, 1.0f); + drawList->AddConvexPolyFilled(screenQuadPts, 4, colors[i + 4]); + } + } + } + + drawList->AddCircleFilled(gContext.mScreenSquareCenter, gContext.mStyle.CenterCircleSize, colors[0], 32); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsTranslateType(type)) + { + ImU32 translationLineColor = GetColorU32(TRANSLATION_LINE); + + ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + vec_t dif = { destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y, 0.f, 0.f }; + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + + char tmps[512]; + vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_MOVE_X) * 3; + ImFormatString(tmps, sizeof(tmps), translationInfoMask[type - MT_MOVE_X], deltaInfo[translationInfoIndex[componentInfoIndex]], deltaInfo[translationInfoIndex[componentInfoIndex + 1]], deltaInfo[translationInfoIndex[componentInfoIndex + 2]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + static bool CanActivate() + { + if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && !ImGui::IsAnyItemActive()) + { + return true; + } + return false; + } + + static void HandleAndDrawLocalBounds(const float* bounds, matrix_t* matrix, const float* snapValues, OPERATION operation) + { + ImGuiIO& io = ImGui::GetIO(); + ImDrawList* drawList = gContext.mDrawList; + + // compute best projection axis + vec_t axesWorldDirections[3]; + vec_t bestAxisWorldDirection = { 0.0f, 0.0f, 0.0f, 0.0f }; + int axes[3]; + unsigned int numAxes = 1; + axes[0] = gContext.mBoundsBestAxis; + int bestAxis = axes[0]; + if (!gContext.mbUsingBounds) + { + numAxes = 0; + float bestDot = 0.f; + for (int i = 0; i < 3; i++) + { + vec_t dirPlaneNormalWorld; + dirPlaneNormalWorld.TransformVector(directionUnary[i], gContext.mModelSource); + dirPlaneNormalWorld.Normalize(); + + float dt = fabsf(Dot(Normalized(gContext.mCameraEye - gContext.mModelSource.v.position), dirPlaneNormalWorld)); + if (dt >= bestDot) + { + bestDot = dt; + bestAxis = i; + bestAxisWorldDirection = dirPlaneNormalWorld; + } + + if (dt >= 0.1f) + { + axes[numAxes] = i; + axesWorldDirections[numAxes] = dirPlaneNormalWorld; + ++numAxes; + } + } + } + + if (numAxes == 0) + { + axes[0] = bestAxis; + axesWorldDirections[0] = bestAxisWorldDirection; + numAxes = 1; + } + + else if (bestAxis != axes[0]) + { + unsigned int bestIndex = 0; + for (unsigned int i = 0; i < numAxes; i++) + { + if (axes[i] == bestAxis) + { + bestIndex = i; + break; + } + } + int tempAxis = axes[0]; + axes[0] = axes[bestIndex]; + axes[bestIndex] = tempAxis; + vec_t tempDirection = axesWorldDirections[0]; + axesWorldDirections[0] = axesWorldDirections[bestIndex]; + axesWorldDirections[bestIndex] = tempDirection; + } + + for (unsigned int axisIndex = 0; axisIndex < numAxes; ++axisIndex) + { + bestAxis = axes[axisIndex]; + bestAxisWorldDirection = axesWorldDirections[axisIndex]; + + // corners + vec_t aabb[4]; + + int secondAxis = (bestAxis + 1) % 3; + int thirdAxis = (bestAxis + 2) % 3; + + for (int i = 0; i < 4; i++) + { + aabb[i][3] = aabb[i][bestAxis] = 0.f; + aabb[i][secondAxis] = bounds[secondAxis + 3 * (i >> 1)]; + aabb[i][thirdAxis] = bounds[thirdAxis + 3 * ((i >> 1) ^ (i & 1))]; + } + + // draw bounds + unsigned int anchorAlpha = gContext.mbEnable ? IM_COL32_BLACK : IM_COL32(0, 0, 0, 0x80); + + matrix_t boundsMVP = gContext.mModelSource * gContext.mViewProjection; + for (int i = 0; i < 4; i++) + { + ImVec2 worldBound1 = worldToPos(aabb[i], boundsMVP); + ImVec2 worldBound2 = worldToPos(aabb[(i + 1) % 4], boundsMVP); + if (!IsInContextRect(worldBound1) || !IsInContextRect(worldBound2)) + { + continue; + } + float boundDistance = sqrtf(ImLengthSqr(worldBound1 - worldBound2)); + int stepCount = (int)(boundDistance / 10.f); + stepCount = min(stepCount, 1000); + for (int j = 0; j < stepCount; j++) + { + float stepLength = 1.f / (float)stepCount; + float t1 = (float)j * stepLength; + float t2 = (float)j * stepLength + stepLength * 0.5f; + ImVec2 worldBoundSS1 = ImLerp(worldBound1, worldBound2, ImVec2(t1, t1)); + ImVec2 worldBoundSS2 = ImLerp(worldBound1, worldBound2, ImVec2(t2, t2)); + //drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0, 0, 0, 0) + anchorAlpha, 3.f); + drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha, 2.f); + } + vec_t midPoint = (aabb[i] + aabb[(i + 1) % 4]) * 0.5f; + ImVec2 midBound = worldToPos(midPoint, boundsMVP); + static const float AnchorBigRadius = 8.f; + static const float AnchorSmallRadius = 6.f; + bool overBigAnchor = ImLengthSqr(worldBound1 - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius); + bool overSmallAnchor = ImLengthSqr(midBound - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius); + + int type = MT_NONE; + vec_t gizmoHitProportion; + + if(Intersects(operation, TRANSLATE)) + { + type = GetMoveType(operation, &gizmoHitProportion); + } + if(Intersects(operation, ROTATE) && type == MT_NONE) + { + type = GetRotateType(operation); + } + if(Intersects(operation, SCALE) && type == MT_NONE) + { + type = GetScaleType(operation); + } + + if (type != MT_NONE) + { + overBigAnchor = false; + overSmallAnchor = false; + } + + ImU32 selectionColor = GetColorU32(SELECTION); + + unsigned int bigAnchorColor = overBigAnchor ? selectionColor : (IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha); + unsigned int smallAnchorColor = overSmallAnchor ? selectionColor : (IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha); + + drawList->AddCircleFilled(worldBound1, AnchorBigRadius, IM_COL32_BLACK); + drawList->AddCircleFilled(worldBound1, AnchorBigRadius - 1.2f, bigAnchorColor); + + drawList->AddCircleFilled(midBound, AnchorSmallRadius, IM_COL32_BLACK); + drawList->AddCircleFilled(midBound, AnchorSmallRadius - 1.2f, smallAnchorColor); + int oppositeIndex = (i + 2) % 4; + // big anchor on corners + if (!gContext.mbUsingBounds && gContext.mbEnable && overBigAnchor && CanActivate()) + { + gContext.mBoundsPivot.TransformPoint(aabb[(i + 2) % 4], gContext.mModelSource); + gContext.mBoundsAnchor.TransformPoint(aabb[i], gContext.mModelSource); + gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); + gContext.mBoundsBestAxis = bestAxis; + gContext.mBoundsAxis[0] = secondAxis; + gContext.mBoundsAxis[1] = thirdAxis; + + gContext.mBoundsLocalPivot.Set(0.f); + gContext.mBoundsLocalPivot[secondAxis] = aabb[oppositeIndex][secondAxis]; + gContext.mBoundsLocalPivot[thirdAxis] = aabb[oppositeIndex][thirdAxis]; + + gContext.mbUsingBounds = true; + gContext.mEditingID = gContext.mActualID; + gContext.mBoundsMatrix = gContext.mModelSource; + } + // small anchor on middle of segment + if (!gContext.mbUsingBounds && gContext.mbEnable && overSmallAnchor && CanActivate()) + { + vec_t midPointOpposite = (aabb[(i + 2) % 4] + aabb[(i + 3) % 4]) * 0.5f; + gContext.mBoundsPivot.TransformPoint(midPointOpposite, gContext.mModelSource); + gContext.mBoundsAnchor.TransformPoint(midPoint, gContext.mModelSource); + gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); + gContext.mBoundsBestAxis = bestAxis; + int indices[] = { secondAxis , thirdAxis }; + gContext.mBoundsAxis[0] = indices[i % 2]; + gContext.mBoundsAxis[1] = -1; + + gContext.mBoundsLocalPivot.Set(0.f); + gContext.mBoundsLocalPivot[gContext.mBoundsAxis[0]] = aabb[oppositeIndex][indices[i % 2]];// bounds[gContext.mBoundsAxis[0]] * (((i + 1) & 2) ? 1.f : -1.f); + + gContext.mbUsingBounds = true; + gContext.mEditingID = gContext.mActualID; + gContext.mBoundsMatrix = gContext.mModelSource; + } + } + + if (gContext.mbUsingBounds && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + matrix_t scale; + scale.SetToIdentity(); + + // compute projected mouse position on plan + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mBoundsPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + + // compute a reference and delta vectors base on mouse move + vec_t deltaVector = (newPos - gContext.mBoundsPivot).Abs(); + vec_t referenceVector = (gContext.mBoundsAnchor - gContext.mBoundsPivot).Abs(); + + // for 1 or 2 axes, compute a ratio that's used for scale and snap it based on resulting length + for (int i = 0; i < 2; i++) + { + int axisIndex1 = gContext.mBoundsAxis[i]; + if (axisIndex1 == -1) + { + continue; + } + + float ratioAxis = 1.f; + vec_t axisDir = gContext.mBoundsMatrix.component[axisIndex1].Abs(); + + float dtAxis = axisDir.Dot(referenceVector); + float boundSize = bounds[axisIndex1 + 3] - bounds[axisIndex1]; + if (dtAxis > FLT_EPSILON) + { + ratioAxis = axisDir.Dot(deltaVector) / dtAxis; + } + + if (snapValues) + { + float length = boundSize * ratioAxis; + ComputeSnap(&length, snapValues[axisIndex1]); + if (boundSize > FLT_EPSILON) + { + ratioAxis = length / boundSize; + } + } + scale.component[axisIndex1] *= ratioAxis; + } + + // transform matrix + matrix_t preScale, postScale; + preScale.Translation(-gContext.mBoundsLocalPivot); + postScale.Translation(gContext.mBoundsLocalPivot); + matrix_t res = preScale * scale * postScale * gContext.mBoundsMatrix; + *matrix = res; + + // info text + char tmps[512]; + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + ImFormatString(tmps, sizeof(tmps), "X: %.2f Y: %.2f Z: %.2f" + , (bounds[3] - bounds[0]) * gContext.mBoundsMatrix.component[0].Length() * scale.component[0].Length() + , (bounds[4] - bounds[1]) * gContext.mBoundsMatrix.component[1].Length() * scale.component[1].Length() + , (bounds[5] - bounds[2]) * gContext.mBoundsMatrix.component[2].Length() * scale.component[2].Length() + ); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + + if (!io.MouseDown[0]) { + gContext.mbUsingBounds = false; + gContext.mEditingID = -1; + } + if (gContext.mbUsingBounds) + { + break; + } + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + + static int GetScaleType(OPERATION op) + { + if (gContext.mbUsing) + { + return MT_NONE; + } + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + // screen + if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && + io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y && + Contains(op, SCALE)) + { + type = MT_SCALE_XYZ; + } + + // compute + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if(!Intersects(op, static_cast(SCALE_X << i))) + { + continue; + } + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + dirAxis.TransformVector(gContext.mModelLocal); + dirPlaneX.TransformVector(gContext.mModelLocal); + dirPlaneY.TransformVector(gContext.mModelLocal); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModelLocal.v.position, dirAxis)); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; + + const float startOffset = Contains(op, static_cast(TRANSLATE_X << i)) ? 1.0f : 0.1f; + const float endOffset = Contains(op, static_cast(TRANSLATE_X << i)) ? 1.4f : 1.0f; + const ImVec2 posOnPlanScreen = worldToPos(posOnPlan, gContext.mViewProjection); + const ImVec2 axisStartOnScreen = worldToPos(gContext.mModelLocal.v.position + dirAxis * gContext.mScreenFactor * startOffset, gContext.mViewProjection); + const ImVec2 axisEndOnScreen = worldToPos(gContext.mModelLocal.v.position + dirAxis * gContext.mScreenFactor * endOffset, gContext.mViewProjection); + + vec_t closestPointOnAxis = PointOnSegment(makeVect(posOnPlanScreen), makeVect(axisStartOnScreen), makeVect(axisEndOnScreen)); + + if ((closestPointOnAxis - makeVect(posOnPlanScreen)).Length() < 12.f) // pixel size + { + type = MT_SCALE_X + i; + } + } + + // universal + + vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f }; + float dist = deltaScreen.Length(); + if (Contains(op, SCALEU) && dist >= 17.0f && dist < 23.0f) + { + type = MT_SCALE_XYZ; + } + + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if (!Intersects(op, static_cast(SCALE_XU << i))) + { + continue; + } + + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + //ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVPLocal); + //ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale) * gContext.mScreenFactor, gContext.mMVPLocal); + + float distance = sqrtf(ImLengthSqr(worldDirSSpace - io.MousePos)); + if (distance < 12.f) + { + type = MT_SCALE_X + i; + } + } + } + return type; + } + + static int GetRotateType(OPERATION op) + { + if (gContext.mbUsing) + { + return MT_NONE; + } + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f }; + float dist = deltaScreen.Length(); + if (Intersects(op, ROTATE_SCREEN) && dist >= (gContext.mRadiusSquareCenter - 4.0f) && dist < (gContext.mRadiusSquareCenter + 4.0f)) + { + type = MT_ROTATE_SCREEN; + } + + const vec_t planNormals[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir }; + + vec_t modelViewPos; + modelViewPos.TransformPoint(gContext.mModel.v.position, gContext.mViewMat); + + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if(!Intersects(op, static_cast(ROTATE_X << i))) + { + continue; + } + // pickup plan + vec_t pickupPlan = BuildPlan(gContext.mModel.v.position, planNormals[i]); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, pickupPlan); + const vec_t intersectWorldPos = gContext.mRayOrigin + gContext.mRayVector * len; + vec_t intersectViewPos; + intersectViewPos.TransformPoint(intersectWorldPos, gContext.mViewMat); + + if (ImAbs(modelViewPos.z) - ImAbs(intersectViewPos.z) < -FLT_EPSILON) + { + continue; + } + + const vec_t localPos = intersectWorldPos - gContext.mModel.v.position; + vec_t idealPosOnCircle = Normalized(localPos); + idealPosOnCircle.TransformVector(gContext.mModelInverse); + const ImVec2 idealPosOnCircleScreen = worldToPos(idealPosOnCircle * rotationDisplayFactor * gContext.mScreenFactor, gContext.mMVP); + + //gContext.mDrawList->AddCircle(idealPosOnCircleScreen, 5.f, IM_COL32_WHITE); + const ImVec2 distanceOnScreen = idealPosOnCircleScreen - io.MousePos; + + const float distance = makeVect(distanceOnScreen).Length(); + if (distance < 8.f) // pixel size + { + type = MT_ROTATE_X + i; + } + } + + return type; + } + + static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion) + { + if(!Intersects(op, TRANSLATE) || gContext.mbUsing || !gContext.mbMouseOver) + { + return MT_NONE; + } + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + // screen + if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && + io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y && + Contains(op, TRANSLATE)) + { + type = MT_MOVE_SCREEN; + } + + const vec_t screenCoord = makeVect(io.MousePos - ImVec2(gContext.mX, gContext.mY)); + + // compute + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + dirAxis.TransformVector(gContext.mModel); + dirPlaneX.TransformVector(gContext.mModel); + dirPlaneY.TransformVector(gContext.mModel); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModel.v.position, dirAxis)); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; + + const ImVec2 axisStartOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor * 0.1f, gContext.mViewProjection) - ImVec2(gContext.mX, gContext.mY); + const ImVec2 axisEndOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor, gContext.mViewProjection) - ImVec2(gContext.mX, gContext.mY); + + vec_t closestPointOnAxis = PointOnSegment(screenCoord, makeVect(axisStartOnScreen), makeVect(axisEndOnScreen)); + if ((closestPointOnAxis - screenCoord).Length() < 12.f && Intersects(op, static_cast(TRANSLATE_X << i))) // pixel size + { + type = MT_MOVE_X + i; + } + + const float dx = dirPlaneX.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + const float dy = dirPlaneY.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + if (belowPlaneLimit && dx >= quadUV[0] && dx <= quadUV[4] && dy >= quadUV[1] && dy <= quadUV[3] && Contains(op, TRANSLATE_PLANS[i])) + { + type = MT_MOVE_YZ + i; + } + + if (gizmoHitProportion) + { + *gizmoHitProportion = makeVect(dx, dy, 0.f); + } + } + return type; + } + + static bool HandleTranslation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if(!Intersects(op, TRANSLATE) || type != MT_NONE) + { + return false; + } + const ImGuiIO& io = ImGui::GetIO(); + const bool applyRotationLocaly = gContext.mMode == LOCAL || type == MT_MOVE_SCREEN; + bool modified = false; + + // move + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsTranslateType(gContext.mCurrentOperation)) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + const float signedLength = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + const float len = fabsf(signedLength); // near plan + const vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + + // compute delta + const vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; + vec_t delta = newOrigin - gContext.mModel.v.position; + + // 1 axis constraint + if (gContext.mCurrentOperation >= MT_MOVE_X && gContext.mCurrentOperation <= MT_MOVE_Z) + { + const int axisIndex = gContext.mCurrentOperation - MT_MOVE_X; + const vec_t& axisValue = *(vec_t*)&gContext.mModel.m[axisIndex]; + const float lengthOnAxis = Dot(axisValue, delta); + delta = axisValue * lengthOnAxis; + } + + // snap + if (snap) + { + vec_t cumulativeDelta = gContext.mModel.v.position + delta - gContext.mMatrixOrigin; + if (applyRotationLocaly) + { + matrix_t modelSourceNormalized = gContext.mModelSource; + modelSourceNormalized.OrthoNormalize(); + matrix_t modelSourceNormalizedInverse; + modelSourceNormalizedInverse.Inverse(modelSourceNormalized); + cumulativeDelta.TransformVector(modelSourceNormalizedInverse); + ComputeSnap(cumulativeDelta, snap); + cumulativeDelta.TransformVector(modelSourceNormalized); + } + else + { + ComputeSnap(cumulativeDelta, snap); + } + delta = gContext.mMatrixOrigin + cumulativeDelta - gContext.mModel.v.position; + + } + + if (delta != gContext.mTranslationLastDelta) + { + modified = true; + } + gContext.mTranslationLastDelta = delta; + + // compute matrix & delta + matrix_t deltaMatrixTranslation; + deltaMatrixTranslation.Translation(delta); + if (deltaMatrix) + { + memcpy(deltaMatrix, deltaMatrixTranslation.m16, sizeof(float) * 16); + } + + const matrix_t res = gContext.mModelSource * deltaMatrixTranslation; + *(matrix_t*)matrix = res; + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + } + + type = gContext.mCurrentOperation; + } + else + { + // find new possible way to move + vec_t gizmoHitProportion; + type = GetMoveType(op, &gizmoHitProportion); + if (type != MT_NONE) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + } + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + vec_t movePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, + gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, + -gContext.mCameraDir }; + + vec_t cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye); + for (unsigned int i = 0; i < 3; i++) + { + vec_t orthoVector = Cross(movePlanNormal[i], cameraToModelNormalized); + movePlanNormal[i].Cross(orthoVector); + movePlanNormal[i].Normalize(); + } + // pickup plan + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_MOVE_X]); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; + gContext.mMatrixOrigin = gContext.mModel.v.position; + + gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor); + } + } + return modified; + } + + static bool HandleScale(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if((!Intersects(op, SCALE) && !Intersects(op, SCALEU)) || type != MT_NONE || !gContext.mbMouseOver) + { + return false; + } + ImGuiIO& io = ImGui::GetIO(); + bool modified = false; + + if (!gContext.mbUsing) + { + // find new possible way to scale + type = GetScaleType(op); + if (type != MT_NONE) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + } + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + const vec_t movePlanNormal[] = { gContext.mModel.v.up, gContext.mModel.v.dir, gContext.mModel.v.right, gContext.mModel.v.dir, gContext.mModel.v.up, gContext.mModel.v.right, -gContext.mCameraDir }; + // pickup plan + + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_SCALE_X]); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; + gContext.mMatrixOrigin = gContext.mModel.v.position; + gContext.mScale.Set(1.f, 1.f, 1.f); + gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor); + gContext.mScaleValueOrigin = makeVect(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); + gContext.mSaveMousePosx = io.MousePos.x; + } + } + // scale + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(gContext.mCurrentOperation)) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; + vec_t delta = newOrigin - gContext.mModelLocal.v.position; + + // 1 axis constraint + if (gContext.mCurrentOperation >= MT_SCALE_X && gContext.mCurrentOperation <= MT_SCALE_Z) + { + int axisIndex = gContext.mCurrentOperation - MT_SCALE_X; + const vec_t& axisValue = *(vec_t*)&gContext.mModelLocal.m[axisIndex]; + float lengthOnAxis = Dot(axisValue, delta); + delta = axisValue * lengthOnAxis; + + vec_t baseVector = gContext.mTranslationPlanOrigin - gContext.mModelLocal.v.position; + float ratio = Dot(axisValue, baseVector + delta) / Dot(axisValue, baseVector); + + gContext.mScale[axisIndex] = max(ratio, 0.001f); + } + else + { + float scaleDelta = (io.MousePos.x - gContext.mSaveMousePosx) * 0.01f; + gContext.mScale.Set(max(1.f + scaleDelta, 0.001f)); + } + + // snap + if (snap) + { + float scaleSnap[] = { snap[0], snap[0], snap[0] }; + ComputeSnap(gContext.mScale, scaleSnap); + } + + // no 0 allowed + for (int i = 0; i < 3; i++) + gContext.mScale[i] = max(gContext.mScale[i], 0.001f); + + if (gContext.mScaleLast != gContext.mScale) + { + modified = true; + } + gContext.mScaleLast = gContext.mScale; + + // compute matrix & delta + matrix_t deltaMatrixScale; + deltaMatrixScale.Scale(gContext.mScale * gContext.mScaleValueOrigin); + + matrix_t res = deltaMatrixScale * gContext.mModelLocal; + *(matrix_t*)matrix = res; + + if (deltaMatrix) + { + vec_t deltaScale = gContext.mScale * gContext.mScaleValueOrigin; + + vec_t originalScaleDivider; + originalScaleDivider.x = 1 / gContext.mModelScaleOrigin.x; + originalScaleDivider.y = 1 / gContext.mModelScaleOrigin.y; + originalScaleDivider.z = 1 / gContext.mModelScaleOrigin.z; + + deltaScale = deltaScale * originalScaleDivider; + + deltaMatrixScale.Scale(deltaScale); + memcpy(deltaMatrix, deltaMatrixScale.m16, sizeof(float) * 16); + } + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + gContext.mScale.Set(1.f, 1.f, 1.f); + } + + type = gContext.mCurrentOperation; + } + return modified; + } + + static bool HandleRotation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if(!Intersects(op, ROTATE) || type != MT_NONE || !gContext.mbMouseOver) + { + return false; + } + ImGuiIO& io = ImGui::GetIO(); + bool applyRotationLocaly = gContext.mMode == LOCAL; + bool modified = false; + + if (!gContext.mbUsing) + { + type = GetRotateType(op); + + if (type != MT_NONE) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + } + + if (type == MT_ROTATE_SCREEN) + { + applyRotationLocaly = true; + } + + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + const vec_t rotatePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, -gContext.mCameraDir }; + // pickup plan + if (applyRotationLocaly) + { + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, rotatePlanNormal[type - MT_ROTATE_X]); + } + else + { + gContext.mTranslationPlan = BuildPlan(gContext.mModelSource.v.position, directionUnary[type - MT_ROTATE_X]); + } + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t localPos = gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position; + gContext.mRotationVectorSource = Normalized(localPos); + gContext.mRotationAngleOrigin = ComputeAngleOnPlan(); + } + } + + // rotation + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsRotateType(gContext.mCurrentOperation)) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + gContext.mRotationAngle = ComputeAngleOnPlan(); + if (snap) + { + float snapInRadian = snap[0] * DEG2RAD; + ComputeSnap(&gContext.mRotationAngle, snapInRadian); + } + vec_t rotationAxisLocalSpace; + + rotationAxisLocalSpace.TransformVector(makeVect(gContext.mTranslationPlan.x, gContext.mTranslationPlan.y, gContext.mTranslationPlan.z, 0.f), gContext.mModelInverse); + rotationAxisLocalSpace.Normalize(); + + matrix_t deltaRotation; + deltaRotation.RotationAxis(rotationAxisLocalSpace, gContext.mRotationAngle - gContext.mRotationAngleOrigin); + if (gContext.mRotationAngle != gContext.mRotationAngleOrigin) + { + modified = true; + } + gContext.mRotationAngleOrigin = gContext.mRotationAngle; + + matrix_t scaleOrigin; + scaleOrigin.Scale(gContext.mModelScaleOrigin); + + if (applyRotationLocaly) + { + *(matrix_t*)matrix = scaleOrigin * deltaRotation * gContext.mModelLocal; + } + else + { + matrix_t res = gContext.mModelSource; + res.v.position.Set(0.f); + + *(matrix_t*)matrix = res * deltaRotation; + ((matrix_t*)matrix)->v.position = gContext.mModelSource.v.position; + } + + if (deltaMatrix) + { + *(matrix_t*)deltaMatrix = gContext.mModelInverse * deltaRotation * gContext.mModel; + } + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + gContext.mEditingID = -1; + } + type = gContext.mCurrentOperation; + } + return modified; + } + + void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale) + { + matrix_t mat = *(matrix_t*)matrix; + + scale[0] = mat.v.right.Length(); + scale[1] = mat.v.up.Length(); + scale[2] = mat.v.dir.Length(); + + mat.OrthoNormalize(); + + rotation[0] = RAD2DEG * atan2f(mat.m[1][2], mat.m[2][2]); + rotation[1] = RAD2DEG * atan2f(-mat.m[0][2], sqrtf(mat.m[1][2] * mat.m[1][2] + mat.m[2][2] * mat.m[2][2])); + rotation[2] = RAD2DEG * atan2f(mat.m[0][1], mat.m[0][0]); + + translation[0] = mat.v.position.x; + translation[1] = mat.v.position.y; + translation[2] = mat.v.position.z; + } + + void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix) + { + matrix_t& mat = *(matrix_t*)matrix; + + matrix_t rot[3]; + for (int i = 0; i < 3; i++) + { + rot[i].RotationAxis(directionUnary[i], rotation[i] * DEG2RAD); + } + + mat = rot[0] * rot[1] * rot[2]; + + float validScale[3]; + for (int i = 0; i < 3; i++) + { + if (fabsf(scale[i]) < FLT_EPSILON) + { + validScale[i] = 0.001f; + } + else + { + validScale[i] = scale[i]; + } + } + mat.v.right *= validScale[0]; + mat.v.up *= validScale[1]; + mat.v.dir *= validScale[2]; + mat.v.position.Set(translation[0], translation[1], translation[2], 1.f); + } + + void SetID(int id) + { + gContext.mActualID = id; + } + + void AllowAxisFlip(bool value) + { + gContext.mAllowAxisFlip = value; + } + + void SetAxisLimit(float value) + { + gContext.mAxisLimit=value; + } + + void SetPlaneLimit(float value) + { + gContext.mPlaneLimit = value; + } + + bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix, const float* snap, const float* localBounds, const float* boundsSnap) + { + // Scale is always local or matrix will be skewed when applying world scale or oriented matrix + ComputeContext(view, projection, matrix, (operation & SCALE) ? LOCAL : mode); + + // set delta to identity + if (deltaMatrix) + { + ((matrix_t*)deltaMatrix)->SetToIdentity(); + } + + // behind camera + vec_t camSpacePosition; + camSpacePosition.TransformPoint(makeVect(0.f, 0.f, 0.f), gContext.mMVP); + if (!gContext.mIsOrthographic && camSpacePosition.z < 0.001f && !gContext.mbUsing) + { + return false; + } + + // -- + int type = MT_NONE; + bool manipulated = false; + if (gContext.mbEnable) + { + if (!gContext.mbUsingBounds) + { + manipulated = HandleTranslation(matrix, deltaMatrix, operation, type, snap) || + HandleScale(matrix, deltaMatrix, operation, type, snap) || + HandleRotation(matrix, deltaMatrix, operation, type, snap); + } + } + + if (localBounds && !gContext.mbUsing) + { + HandleAndDrawLocalBounds(localBounds, (matrix_t*)matrix, boundsSnap, operation); + } + + gContext.mOperation = operation; + if (!gContext.mbUsingBounds) + { + DrawRotationGizmo(operation, type); + DrawTranslationGizmo(operation, type); + DrawScaleGizmo(operation, type); + DrawScaleUniveralGizmo(operation, type); + } + return manipulated; + } + + void SetGizmoSizeClipSpace(float value) + { + gContext.mGizmoSizeClipSpace = value; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////// + void ComputeFrustumPlanes(vec_t* frustum, const float* clip) + { + frustum[0].x = clip[3] - clip[0]; + frustum[0].y = clip[7] - clip[4]; + frustum[0].z = clip[11] - clip[8]; + frustum[0].w = clip[15] - clip[12]; + + frustum[1].x = clip[3] + clip[0]; + frustum[1].y = clip[7] + clip[4]; + frustum[1].z = clip[11] + clip[8]; + frustum[1].w = clip[15] + clip[12]; + + frustum[2].x = clip[3] + clip[1]; + frustum[2].y = clip[7] + clip[5]; + frustum[2].z = clip[11] + clip[9]; + frustum[2].w = clip[15] + clip[13]; + + frustum[3].x = clip[3] - clip[1]; + frustum[3].y = clip[7] - clip[5]; + frustum[3].z = clip[11] - clip[9]; + frustum[3].w = clip[15] - clip[13]; + + frustum[4].x = clip[3] - clip[2]; + frustum[4].y = clip[7] - clip[6]; + frustum[4].z = clip[11] - clip[10]; + frustum[4].w = clip[15] - clip[14]; + + frustum[5].x = clip[3] + clip[2]; + frustum[5].y = clip[7] + clip[6]; + frustum[5].z = clip[11] + clip[10]; + frustum[5].w = clip[15] + clip[14]; + + for (int i = 0; i < 6; i++) + { + frustum[i].Normalize(); + } + } + + void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount) + { + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)view); + + struct CubeFace + { + float z; + ImVec2 faceCoordsScreen[4]; + ImU32 color; + }; + CubeFace* faces = (CubeFace*)_malloca(sizeof(CubeFace) * matrixCount * 6); + + if (!faces) + { + return; + } + + vec_t frustum[6]; + matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection; + ComputeFrustumPlanes(frustum, viewProjection.m16); + + int cubeFaceCount = 0; + for (int cube = 0; cube < matrixCount; cube++) + { + const float* matrix = &matrices[cube * 16]; + + matrix_t res = *(matrix_t*)matrix * *(matrix_t*)view * *(matrix_t*)projection; + + for (int iFace = 0; iFace < 6; iFace++) + { + const int normalIndex = (iFace % 3); + const int perpXIndex = (normalIndex + 1) % 3; + const int perpYIndex = (normalIndex + 2) % 3; + const float invert = (iFace > 2) ? -1.f : 1.f; + + const vec_t faceCoords[4] = { directionUnary[normalIndex] + directionUnary[perpXIndex] + directionUnary[perpYIndex], + directionUnary[normalIndex] + directionUnary[perpXIndex] - directionUnary[perpYIndex], + directionUnary[normalIndex] - directionUnary[perpXIndex] - directionUnary[perpYIndex], + directionUnary[normalIndex] - directionUnary[perpXIndex] + directionUnary[perpYIndex], + }; + + // clipping + /* + bool skipFace = false; + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + vec_t camSpacePosition; + camSpacePosition.TransformPoint(faceCoords[iCoord] * 0.5f * invert, res); + if (camSpacePosition.z < 0.001f) + { + skipFace = true; + break; + } + } + if (skipFace) + { + continue; + } + */ + vec_t centerPosition, centerPositionVP; + centerPosition.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, *(matrix_t*)matrix); + centerPositionVP.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, res); + + bool inFrustum = true; + for (int iFrustum = 0; iFrustum < 6; iFrustum++) + { + float dist = DistanceToPlane(centerPosition, frustum[iFrustum]); + if (dist < 0.f) + { + inFrustum = false; + break; + } + } + + if (!inFrustum) + { + continue; + } + CubeFace& cubeFace = faces[cubeFaceCount]; + + // 3D->2D + //ImVec2 faceCoordsScreen[4]; + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + cubeFace.faceCoordsScreen[iCoord] = worldToPos(faceCoords[iCoord] * 0.5f * invert, res); + } + + ImU32 directionColor = GetColorU32(DIRECTION_X + normalIndex); + cubeFace.color = directionColor | IM_COL32(0x80, 0x80, 0x80, 0); + + cubeFace.z = centerPositionVP.z / centerPositionVP.w; + cubeFaceCount++; + } + } + qsort(faces, cubeFaceCount, sizeof(CubeFace), [](void const* _a, void const* _b) { + CubeFace* a = (CubeFace*)_a; + CubeFace* b = (CubeFace*)_b; + if (a->z < b->z) + { + return 1; + } + return -1; + }); + // draw face with lighter color + for (int iFace = 0; iFace < cubeFaceCount; iFace++) + { + const CubeFace& cubeFace = faces[iFace]; + gContext.mDrawList->AddConvexPolyFilled(cubeFace.faceCoordsScreen, 4, cubeFace.color); + } + + _freea(faces); + } + + void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize) + { + matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection; + vec_t frustum[6]; + ComputeFrustumPlanes(frustum, viewProjection.m16); + matrix_t res = *(matrix_t*)matrix * viewProjection; + + for (float f = -gridSize; f <= gridSize; f += 1.f) + { + for (int dir = 0; dir < 2; dir++) + { + vec_t ptA = makeVect(dir ? -gridSize : f, 0.f, dir ? f : -gridSize); + vec_t ptB = makeVect(dir ? gridSize : f, 0.f, dir ? f : gridSize); + bool visible = true; + for (int i = 0; i < 6; i++) + { + float dA = DistanceToPlane(ptA, frustum[i]); + float dB = DistanceToPlane(ptB, frustum[i]); + if (dA < 0.f && dB < 0.f) + { + visible = false; + break; + } + if (dA > 0.f && dB > 0.f) + { + continue; + } + if (dA < 0.f) + { + float len = fabsf(dA - dB); + float t = fabsf(dA) / len; + ptA.Lerp(ptB, t); + } + if (dB < 0.f) + { + float len = fabsf(dB - dA); + float t = fabsf(dB) / len; + ptB.Lerp(ptA, t); + } + } + if (visible) + { + ImU32 col = IM_COL32(0x80, 0x80, 0x80, 0xFF); + col = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? IM_COL32(0x90, 0x90, 0x90, 0xFF) : col; + col = (fabsf(f) < FLT_EPSILON) ? IM_COL32(0x40, 0x40, 0x40, 0xFF): col; + + float thickness = 1.f; + thickness = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? 1.5f : thickness; + thickness = (fabsf(f) < FLT_EPSILON) ? 2.3f : thickness; + + gContext.mDrawList->AddLine(worldToPos(ptA, res), worldToPos(ptB, res), col, thickness); + } + } + } + } + + bool ViewManipulate(float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor) + { + // Scale is always local or matrix will be skewed when applying world scale or oriented matrix + ComputeContext(view, projection, matrix, (operation & SCALE) ? LOCAL : mode); + return ViewManipulate(view, length, position, size, backgroundColor); + } + + void OrthoGraphic(const float l, float r, float b, const float t, float zn, const float zf, float* m16) + { + m16[0] = 2 / (r - l); + m16[1] = 0.0f; + m16[2] = 0.0f; + m16[3] = 0.0f; + m16[4] = 0.0f; + m16[5] = 2 / (t - b); + m16[6] = 0.0f; + m16[7] = 0.0f; + m16[8] = 0.0f; + m16[9] = 0.0f; + m16[10] = 1.0f / (zf - zn); + m16[11] = 0.0f; + m16[12] = (l + r) / (l - r); + m16[13] = (t + b) / (b - t); + m16[14] = zn / (zn - zf); + m16[15] = 1.0f; + } + + bool ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor) + { + static bool isDraging = false; + static bool isClicking = false; + static bool isInside = false; + static vec_t interpolationUp; + static vec_t interpolationDir; + static int interpolationFrames = 0; + const vec_t referenceUp = makeVect(0.f, 1.f, 0.f); + + matrix_t svgView, svgProjection; + svgView = gContext.mViewMat; + svgProjection = gContext.mProjectionMat; + + ImGuiIO& io = ImGui::GetIO(); + gContext.mDrawList->AddRectFilled(position, position + size, backgroundColor); + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)view); + + const vec_t camTarget = viewInverse.v.position - viewInverse.v.dir * length; + + // view/projection matrices + const float distance = 3.f; + matrix_t cubeProjection, cubeView; + OrthoGraphic(-1, 1, -1, 1, 0.01f, 1000.f, cubeProjection.m16); + + vec_t dir = makeVect(viewInverse.m[2][0], viewInverse.m[2][1], viewInverse.m[2][2]); + vec_t up = makeVect(viewInverse.m[1][0], viewInverse.m[1][1], viewInverse.m[1][2]); + vec_t eye = dir * distance; + vec_t zero = makeVect(0.f, 0.f); + LookAt(&eye.x, &zero.x, &up.x, cubeView.m16); + + // set context + gContext.mViewMat = cubeView; + gContext.mProjectionMat = cubeProjection; + ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector, position, size); + + const matrix_t res = cubeView * cubeProjection; + + // panels + static const ImVec2 panelPosition[9] = { ImVec2(0.75f,0.75f), ImVec2(0.25f, 0.75f), ImVec2(0.f, 0.75f), + ImVec2(0.75f, 0.25f), ImVec2(0.25f, 0.25f), ImVec2(0.f, 0.25f), + ImVec2(0.75f, 0.f), ImVec2(0.25f, 0.f), ImVec2(0.f, 0.f) }; + + static const ImVec2 panelSize[9] = { ImVec2(0.25f,0.25f), ImVec2(0.5f, 0.25f), ImVec2(0.25f, 0.25f), + ImVec2(0.25f, 0.5f), ImVec2(0.5f, 0.5f), ImVec2(0.25f, 0.5f), + ImVec2(0.25f, 0.25f), ImVec2(0.5f, 0.25f), ImVec2(0.25f, 0.25f) }; + + // tag faces + bool boxes[27]{}; + static int overBox = -1; + for (int iPass = 0; iPass < 2; iPass++) + { + for (int iFace = 0; iFace < 6; iFace++) + { + const int normalIndex = (iFace % 3); + const int perpXIndex = (normalIndex + 1) % 3; + const int perpYIndex = (normalIndex + 2) % 3; + const float invert = (iFace > 2) ? -1.f : 1.f; + const vec_t indexVectorX = directionUnary[perpXIndex] * invert; + const vec_t indexVectorY = directionUnary[perpYIndex] * invert; + const vec_t boxOrigin = directionUnary[normalIndex] * -invert - indexVectorX - indexVectorY; + + // plan local space + const vec_t n = directionUnary[normalIndex] * invert; + vec_t viewSpaceNormal = n; + vec_t viewSpacePoint = n * 0.f; + viewSpaceNormal.TransformVector(cubeView); + viewSpaceNormal.Normalize(); + viewSpacePoint.TransformPoint(cubeView); + const vec_t viewSpaceFacePlan = BuildPlan(viewSpacePoint, viewSpaceNormal); + + // back face culling + if (viewSpaceFacePlan.w > 0.f) + { + continue; + } + + const vec_t facePlan = BuildPlan(n * 0.5f, n); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, facePlan); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len - (n * 0.5f); + + float localx = Dot(directionUnary[perpXIndex], posOnPlan) * invert + 0.5f; + float localy = Dot(directionUnary[perpYIndex], posOnPlan) * invert + 0.5f; + + // panels + const vec_t dx = directionUnary[perpXIndex]; + const vec_t dy = directionUnary[perpYIndex]; + const vec_t origin = directionUnary[normalIndex] - dx - dy; + ImU32 faceColor = GetColorU32(FACE); + for (int iPanel = 0; iPanel < 9; iPanel++) + { + vec_t boxCoord = boxOrigin + indexVectorX * float(iPanel % 3) + indexVectorY * float(iPanel / 3) + makeVect(1.f, 1.f, 1.f); + const ImVec2 p = panelPosition[iPanel] * 2.f; + const ImVec2 s = panelSize[iPanel] * 2.f; + ImVec2 faceCoordsScreen[4]; + vec_t panelPos[4] = { dx * p.x + dy * p.y, + dx * p.x + dy * (p.y + s.y), + dx * (p.x + s.x) + dy * (p.y + s.y), + dx * (p.x + s.x) + dy * p.y }; + + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + faceCoordsScreen[iCoord] = worldToPos((panelPos[iCoord] + origin) * 0.5f * invert, res, position, size); + } + + const ImVec2 panelCorners[2] = { panelPosition[iPanel], panelPosition[iPanel] + panelSize[iPanel] }; + bool insidePanel = localx > panelCorners[0].x && localx < panelCorners[1].x && localy > panelCorners[0].y && localy < panelCorners[1].y; + int boxCoordInt = int(boxCoord.x * 9.f + boxCoord.y * 3.f + boxCoord.z); + IM_ASSERT(boxCoordInt < 27); + boxes[boxCoordInt] |= insidePanel && (!isDraging) && gContext.mbMouseOver; + + // draw face with lighter color + if (iPass) + { + gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, faceColor); + if (boxes[boxCoordInt]) + { + gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, IM_COL32(0xF0, 0xA0, 0x60, 0x80)); + +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + if (io.MouseDown[0] && !isClicking && !isDraging && GImGui->ActiveId == 0) { + overBox = boxCoordInt; + isClicking = true; + isDraging = true; + } + } + } + } + + if (iPass) { + // Draw face label + ImDrawList* drawList = gContext.mDrawList; + int vtx_write_start = drawList->VtxBuffer.Size; + + const auto label = gContext.mStyle.FaceLabels[iFace]; + ImVec2 labelSize = ImGui::CalcTextSize(label); + float scaleFactor = 2 / size.y; + auto labelOrigin = labelSize * 0.5; + + drawList->AddText(ImVec2(0, 0), GetColorU32(TEXT), label); + ImDrawVert* vtx_write_end = drawList->_VtxWritePtr; + + vec_t tdx = directionUnary[perpXIndex]; + vec_t tdy = directionUnary[perpYIndex]; + ImVec2 invert2 = {1, 1}; + switch (iFace) { + case 0: // Back + tdx = directionUnary[2]; + tdy = directionUnary[1]; + invert2 = {-1, - 1}; + break; + case 3: // Front + tdx = directionUnary[2]; + tdy = directionUnary[1]; + invert2.x = -1; + break; + case 1: // Top + invert2.y = -1; + break; + case 4: // Bottom + invert2 = {-1, -1}; + break; + case 2: // Right + invert2.y = -1; + break; + case 5: // Left + break; + } + + for (auto v = (drawList->VtxBuffer.Data + vtx_write_start); v < vtx_write_end; v++) { + auto pp = ((v->pos - labelOrigin) * scaleFactor * invert2 + ImVec2{0.5, 0.5}) * 2.f; + vec_t pt = tdx * pp.x + tdy * pp.y; + v->pos = worldToPos((pt + origin) * 0.5 * invert, res, position, size); + } + } + } + } + + // draw axis + { + ImDrawList* drawList = gContext.mDrawList; + const vec_t origin = makeVect(-0.5f, -0.5f, -0.5f); + for (int i = 0; i < 3; ++i) { + vec_t dirAxis = directionUnary[i]; + ImVec2 baseSSpace = worldToPos(origin, res, position, size); + ImVec2 worldDirSSpace = worldToPos(origin + dirAxis, res, position, size); + + bool visible = false; + { + vec_t mid = origin + (dirAxis * 0.5); + vec_t eye = makeVect(0.f, 0.f, 0.5f); + eye.Normalize(); + for (int j = 1; j <= 2; j++) { + vec_t f = mid + (directionUnary[(i + j) % 3] * 0.5); + f.TransformVector(cubeView); + f.Normalize(); + auto w = f.Dot(eye); + if (w > 0) { + visible = true; + break; + } + } + } + + ImVec4 directionColorV = gContext.mStyle.Colors[DIRECTION_X + i]; + if (!visible) { + directionColorV.w *= 0.3f; + } + ImU32 directionColor = ImGui::ColorConvertFloat4ToU32(directionColorV) | IM_COL32(0x80, 0x80, 0x80, 0x00); + drawList->AddLine(baseSSpace, worldDirSSpace, directionColor, gContext.mStyle.TranslationLineThickness); + + // Arrow head begin + ImVec2 dir(baseSSpace - worldDirSSpace); + + float d = sqrtf(ImLengthSqr(dir)); + dir /= d; // Normalize + dir *= gContext.mStyle.TranslationLineArrowSize; + + ImVec2 ortogonalDir(dir.y, -dir.x); // Perpendicular vector + ImVec2 a(worldDirSSpace + dir); + drawList->AddTriangleFilled(worldDirSSpace - dir, a + ortogonalDir, a - ortogonalDir, directionColor); + // Arrow head end + + // Axis text + ImVec2 labelSSpace = worldToPos(origin + dirAxis * 1.3f, res, position, size); + ImVec2 labelSize = ImGui::CalcTextSize(gContext.mStyle.AxisLabels[i]); + drawList->AddText(labelSSpace - labelSize * 0.5, directionColor, gContext.mStyle.AxisLabels[i]); + } + } + + bool viewUpdated = false; + if (interpolationFrames) + { + interpolationFrames--; + vec_t newDir = viewInverse.v.dir; + newDir.Lerp(interpolationDir, 0.2f); + newDir.Normalize(); + + vec_t newUp = viewInverse.v.up; + newUp.Lerp(interpolationUp, 0.3f); + newUp.Normalize(); + newUp = interpolationUp; + vec_t newEye = camTarget + newDir * length; + LookAt(&newEye.x, &camTarget.x, &newUp.x, view); + viewUpdated = true; + } + isInside = gContext.mbMouseOver && ImRect(position, position + size).Contains(io.MousePos); + + if (io.MouseDown[0] && (fabsf(io.MouseDelta[0]) || fabsf(io.MouseDelta[1])) && isClicking) + { + isClicking = false; + +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + } + + if (!io.MouseDown[0]) + { + if (isClicking) + { + // apply new view direction + int cx = overBox / 9; + int cy = (overBox - cx * 9) / 3; + int cz = overBox % 3; + interpolationDir = makeVect(1.f - (float)cx, 1.f - (float)cy, 1.f - (float)cz); + interpolationDir.Normalize(); + + if (fabsf(Dot(interpolationDir, referenceUp)) > 1.0f - 0.01f) + { + vec_t right = viewInverse.v.right; + if (fabsf(right.x) > fabsf(right.z)) + { + right.z = 0.f; + } + else + { + right.x = 0.f; + } + right.Normalize(); + interpolationUp = Cross(interpolationDir, right); + interpolationUp.Normalize(); + } + else + { + interpolationUp = referenceUp; + } + interpolationFrames = 40; + + } + isClicking = false; + isDraging = false; + } + + + if (isDraging) + { + matrix_t rx, ry, roll; + + rx.RotationAxis(referenceUp, -io.MouseDelta.x * 0.01f); + ry.RotationAxis(viewInverse.v.right, -io.MouseDelta.y * 0.01f); + + roll = rx * ry; + + vec_t newDir = viewInverse.v.dir; + newDir.TransformVector(roll); + newDir.Normalize(); + + // clamp + vec_t planDir = Cross(viewInverse.v.right, referenceUp); + planDir.y = 0.f; + planDir.Normalize(); + float dt = Dot(planDir, newDir); + if (dt < 0.0f) + { + newDir += planDir * dt; + newDir.Normalize(); + } + + vec_t newEye = camTarget + newDir * length; + LookAt(&newEye.x, &camTarget.x, &referenceUp.x, view); +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + viewUpdated = true; + } + + // restore view/projection because it was used to compute ray + ComputeContext(svgView.m16, svgProjection.m16, gContext.mModelSource.m16, gContext.mMode); + + return viewUpdated; + } +}; diff --git a/src/imguizmo/ImGuizmo.h b/src/imguizmo/ImGuizmo.h new file mode 100644 index 0000000..7f20bc1 --- /dev/null +++ b/src/imguizmo/ImGuizmo.h @@ -0,0 +1,295 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.89 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// ------------------------------------------------------------------------------------------- +// History : +// 2019/11/03 View gizmo +// 2016/09/11 Behind camera culling. Scaling Delta matrix not multiplied by source matrix scales. local/world rotation and translation fixed. Display message is incorrect (X: ... Y:...) in local mode. +// 2016/09/09 Hatched negative axis. Snapping. Documentation update. +// 2016/09/04 Axis switch and translation plan autohiding. Scale transform stability improved +// 2016/09/01 Mogwai changed to Manipulate. Draw debug cube. Fixed inverted scale. Mixing scale and translation/rotation gives bad results. +// 2016/08/31 First version +// +// ------------------------------------------------------------------------------------------- +// Future (no order): +// +// - Multi view +// - display rotation/translation/scale infos in local/world space and not only local +// - finish local/world matrix application +// - OPERATION as bitmask +// +// ------------------------------------------------------------------------------------------- +// Example +#if 0 +void EditTransform(const Camera& camera, matrix_t& matrix) +{ + static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::ROTATE); + static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::WORLD); + if (ImGui::IsKeyPressed(90)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + if (ImGui::IsKeyPressed(69)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + if (ImGui::IsKeyPressed(82)) // r Key + mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) + mCurrentGizmoOperation = ImGuizmo::SCALE; + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + ImGuizmo::DecomposeMatrixToComponents(matrix.m16, matrixTranslation, matrixRotation, matrixScale); + ImGui::InputFloat3("Tr", matrixTranslation, 3); + ImGui::InputFloat3("Rt", matrixRotation, 3); + ImGui::InputFloat3("Sc", matrixScale, 3); + ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix.m16); + + if (mCurrentGizmoOperation != ImGuizmo::SCALE) + { + if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL)) + mCurrentGizmoMode = ImGuizmo::LOCAL; + ImGui::SameLine(); + if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD)) + mCurrentGizmoMode = ImGuizmo::WORLD; + } + static bool useSnap(false); + if (ImGui::IsKeyPressed(83)) + useSnap = !useSnap; + ImGui::Checkbox("", &useSnap); + ImGui::SameLine(); + vec_t snap; + switch (mCurrentGizmoOperation) + { + case ImGuizmo::TRANSLATE: + snap = config.mSnapTranslation; + ImGui::InputFloat3("Snap", &snap.x); + break; + case ImGuizmo::ROTATE: + snap = config.mSnapRotation; + ImGui::InputFloat("Angle Snap", &snap.x); + break; + case ImGuizmo::SCALE: + snap = config.mSnapScale; + ImGui::InputFloat("Scale Snap", &snap.x); + break; + } + ImGuiIO& io = ImGui::GetIO(); + ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + ImGuizmo::Manipulate(camera.mView.m16, camera.mProjection.m16, mCurrentGizmoOperation, mCurrentGizmoMode, matrix.m16, NULL, useSnap ? &snap.x : NULL); +} +#endif +#pragma once + +#ifdef USE_IMGUI_API +#include "imconfig.h" +#endif +#ifndef IMGUI_API +#define IMGUI_API +#endif + +#ifndef IMGUIZMO_NAMESPACE +#define IMGUIZMO_NAMESPACE ImGuizmo +#endif + +namespace IMGUIZMO_NAMESPACE +{ + // call inside your own window and before Manipulate() in order to draw gizmo to that window. + // Or pass a specific ImDrawList to draw to (e.g. ImGui::GetForegroundDrawList()). + IMGUI_API void SetDrawlist(ImDrawList* drawlist = nullptr); + + // call BeginFrame right after ImGui_XXXX_NewFrame(); + IMGUI_API void BeginFrame(); + + // this is necessary because when imguizmo is compiled into a dll, and imgui into another + // globals are not shared between them. + // More details at https://stackoverflow.com/questions/19373061/what-happens-to-global-and-static-variables-in-a-shared-library-when-it-is-dynam + // expose method to set imgui context + IMGUI_API void SetImGuiContext(ImGuiContext* ctx); + + // return true if mouse cursor is over any gizmo control (axis, plan or screen component) + IMGUI_API bool IsOver(); + + // return true if mouse IsOver or if the gizmo is in moving state + IMGUI_API bool IsUsing(); + + // return true if any gizmo is in moving state + IMGUI_API bool IsUsingAny(); + + // enable/disable the gizmo. Stay in the state until next call to Enable. + // gizmo is rendered with gray half transparent color when disabled + IMGUI_API void Enable(bool enable); + + // helper functions for manualy editing translation/rotation/scale with an input float + // translation, rotation and scale float points to 3 floats each + // Angles are in degrees (more suitable for human editing) + // example: + // float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + // ImGuizmo::DecomposeMatrixToComponents(gizmoMatrix.m16, matrixTranslation, matrixRotation, matrixScale); + // ImGui::InputFloat3("Tr", matrixTranslation, 3); + // ImGui::InputFloat3("Rt", matrixRotation, 3); + // ImGui::InputFloat3("Sc", matrixScale, 3); + // ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, gizmoMatrix.m16); + // + // These functions have some numerical stability issues for now. Use with caution. + IMGUI_API void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale); + IMGUI_API void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix); + + IMGUI_API void SetRect(float x, float y, float width, float height); + // default is false + IMGUI_API void SetOrthographic(bool isOrthographic); + + // Render a cube with face color corresponding to face normal. Usefull for debug/tests + IMGUI_API void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount); + IMGUI_API void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize); + + // call it when you want a gizmo + // Needs view and projection matrices. + // matrix parameter is the source matrix (where will be gizmo be drawn) and might be transformed by the function. Return deltaMatrix is optional + // translation is applied in world space + enum OPERATION + { + TRANSLATE_X = (1u << 0), + TRANSLATE_Y = (1u << 1), + TRANSLATE_Z = (1u << 2), + ROTATE_X = (1u << 3), + ROTATE_Y = (1u << 4), + ROTATE_Z = (1u << 5), + ROTATE_SCREEN = (1u << 6), + SCALE_X = (1u << 7), + SCALE_Y = (1u << 8), + SCALE_Z = (1u << 9), + BOUNDS = (1u << 10), + SCALE_XU = (1u << 11), + SCALE_YU = (1u << 12), + SCALE_ZU = (1u << 13), + + TRANSLATE = TRANSLATE_X | TRANSLATE_Y | TRANSLATE_Z, + ROTATE = ROTATE_X | ROTATE_Y | ROTATE_Z | ROTATE_SCREEN, + SCALE = SCALE_X | SCALE_Y | SCALE_Z, + SCALEU = SCALE_XU | SCALE_YU | SCALE_ZU, // universal + UNIVERSAL = TRANSLATE | ROTATE | SCALEU + }; + + inline OPERATION operator|(OPERATION lhs, OPERATION rhs) + { + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + enum MODE + { + LOCAL, + WORLD + }; + + IMGUI_API bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix = NULL, const float* snap = NULL, const float* localBounds = NULL, const float* boundsSnap = NULL); + // + // Please note that this cubeview is patented by Autodesk : https://patents.google.com/patent/US7782319B2/en + // It seems to be a defensive patent in the US. I don't think it will bring troubles using it as + // other software are using the same mechanics. But just in case, you are now warned! + // + IMGUI_API bool ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor); + + // use this version if you did not call Manipulate before and you are just using ViewManipulate + IMGUI_API bool ViewManipulate(float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor); + + IMGUI_API void SetID(int id); + + // return true if the cursor is over the operation's gizmo + IMGUI_API bool IsOver(OPERATION op); + IMGUI_API void SetGizmoSizeClipSpace(float value); + + // Allow axis to flip + // When true (default), the guizmo axis flip for better visibility + // When false, they always stay along the positive world/local axis + IMGUI_API void AllowAxisFlip(bool value); + + // Configure the limit where axis are hidden + IMGUI_API void SetAxisLimit(float value); + // Configure the limit where planes are hiden + IMGUI_API void SetPlaneLimit(float value); + + enum COLOR + { + DIRECTION_X, // directionColor[0] + DIRECTION_Y, // directionColor[1] + DIRECTION_Z, // directionColor[2] + PLANE_X, // planeColor[0] + PLANE_Y, // planeColor[1] + PLANE_Z, // planeColor[2] + SELECTION, // selectionColor + INACTIVE, // inactiveColor + TRANSLATION_LINE, // translationLineColor + SCALE_LINE, + ROTATION_USING_BORDER, + ROTATION_USING_FILL, + HATCHED_AXIS_LINES, + TEXT, + TEXT_SHADOW, + FACE, + COUNT + }; + + enum Axis + { + Axis_X, + Axis_Y, + Axis_Z, + Axis_COUNT, + }; + + enum FACES + { + FACE_BACK, + FACE_TOP, + FACE_RIGHT, + FACE_FRONT, + FACE_BOTTOM, + FACE_LEFT, + FACES_COUNT + }; + + struct Style + { + IMGUI_API Style(); + + float TranslationLineThickness; // Thickness of lines for translation gizmo + float TranslationLineArrowSize; // Size of arrow at the end of lines for translation gizmo + float RotationLineThickness; // Thickness of lines for rotation gizmo + float RotationOuterLineThickness; // Thickness of line surrounding the rotation gizmo + float ScaleLineThickness; // Thickness of lines for scale gizmo + float ScaleLineCircleSize; // Size of circle at the end of lines for scale gizmo + float HatchedAxisLineThickness; // Thickness of hatched axis lines + float CenterCircleSize; // Size of circle at the center of the translate/scale gizmo + + ImVec4 Colors[COLOR::COUNT]; + + char AxisLabels[Axis::Axis_COUNT][32]; + char FaceLabels[FACES::FACES_COUNT][32]; + }; + + IMGUI_API Style& GetStyle(); +} diff --git a/src/imguizmo/LICENSE b/src/imguizmo/LICENSE new file mode 100644 index 0000000..dcbee45 --- /dev/null +++ b/src/imguizmo/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Cedric Guillemet + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/imguizmo/README.md b/src/imguizmo/README.md new file mode 100644 index 0000000..c4b0818 --- /dev/null +++ b/src/imguizmo/README.md @@ -0,0 +1,192 @@ +# ImGuizmo + +Latest stable tagged version is 1.83. Current master version is 1.84 WIP. + +What started with the gizmo is now a collection of dear imgui widgets and more advanced controls. + +## Guizmos + +### ImViewGizmo + +Manipulate view orientation with 1 single line of code + +![Image of ImViewGizmo](http://i.imgur.com/7UVcyDd.gif) + +### ImGuizmo + +ImGizmo is a small (.h and .cpp) library built ontop of Dear ImGui that allow you to manipulate(Rotate & translate at the moment) 4x4 float matrices. No other dependancies. Coded with Immediate Mode (IM) philosophy in mind. + +Built against DearImgui 1.53WIP + +![Image of Rotation](http://i.imgur.com/y4mcVoT.gif) +![Image of Translation](http://i.imgur.com/o8q8iHq.gif) +![Image of Bounds](http://i.imgur.com/3Ez5LBr.gif) + +There is now a sample for Win32/OpenGL ! With a binary in bin directory. +![Image of Sample](https://i.imgur.com/nXlzyqD.png) + +### ImSequencer + +A WIP little sequencer used to edit frame start/end for different events in a timeline. +![Image of Rotation](http://i.imgur.com/BeyNwCn.png) +Check the sample for the documentation. More to come... + +### Graph Editor + +Nodes + connections. Custom draw inside nodes is possible with the delegate system in place. +![Image of GraphEditor](Images/nodeeditor.jpg) + +### API doc + +Call BeginFrame right after ImGui_XXXX_NewFrame(); + +```C++ +void BeginFrame(); +``` + +return true if mouse cursor is over any gizmo control (axis, plan or screen component) + +```C++ +bool IsOver();** +``` + +return true if mouse IsOver or if the gizmo is in moving state + +```C++ +bool IsUsing();** +``` + +enable/disable the gizmo. Stay in the state until next call to Enable. gizmo is rendered with gray half transparent color when disabled + +```C++ +void Enable(bool enable);** +``` + +helper functions for manualy editing translation/rotation/scale with an input float +translation, rotation and scale float points to 3 floats each +Angles are in degrees (more suitable for human editing) +example: + +```C++ + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + ImGuizmo::DecomposeMatrixToComponents(gizmoMatrix.m16, matrixTranslation, matrixRotation, matrixScale); + ImGui::InputFloat3("Tr", matrixTranslation, 3); + ImGui::InputFloat3("Rt", matrixRotation, 3); + ImGui::InputFloat3("Sc", matrixScale, 3); + ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, gizmoMatrix.m16); +``` + +These functions have some numerical stability issues for now. Use with caution. + +```C++ +void DecomposeMatrixToComponents(const float *matrix, float *translation, float *rotation, float *scale); +void RecomposeMatrixFromComponents(const float *translation, const float *rotation, const float *scale, float *matrix);** +``` + +Render a cube with face color corresponding to face normal. Usefull for debug/test + +```C++ +void DrawCube(const float *view, const float *projection, float *matrix);** +``` + +Call it when you want a gizmo +Needs view and projection matrices. +Matrix parameter is the source matrix (where will be gizmo be drawn) and might be transformed by the function. Return deltaMatrix is optional. snap points to a float[3] for translation and to a single float for scale or rotation. Snap angle is in Euler Degrees. + +```C++ + enum OPERATION + { + TRANSLATE, + ROTATE, + SCALE + }; + + enum MODE + { + LOCAL, + WORLD + }; + +void Manipulate(const float *view, const float *projection, OPERATION operation, MODE mode, float *matrix, float *deltaMatrix = 0, float *snap = 0);** +``` + +### ImGui Example + +Code for : + +![Image of dialog](http://i.imgur.com/GL5flN1.png) + +```C++ +void EditTransform(const Camera& camera, matrix_t& matrix) +{ + static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::ROTATE); + static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::WORLD); + if (ImGui::IsKeyPressed(90)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + if (ImGui::IsKeyPressed(69)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + if (ImGui::IsKeyPressed(82)) // r Key + mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) + mCurrentGizmoOperation = ImGuizmo::SCALE; + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + ImGuizmo::DecomposeMatrixToComponents(matrix.m16, matrixTranslation, matrixRotation, matrixScale); + ImGui::InputFloat3("Tr", matrixTranslation, 3); + ImGui::InputFloat3("Rt", matrixRotation, 3); + ImGui::InputFloat3("Sc", matrixScale, 3); + ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix.m16); + + if (mCurrentGizmoOperation != ImGuizmo::SCALE) + { + if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL)) + mCurrentGizmoMode = ImGuizmo::LOCAL; + ImGui::SameLine(); + if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD)) + mCurrentGizmoMode = ImGuizmo::WORLD; + } + static bool useSnap(false); + if (ImGui::IsKeyPressed(83)) + useSnap = !useSnap; + ImGui::Checkbox("", &useSnap); + ImGui::SameLine(); + vec_t snap; + switch (mCurrentGizmoOperation) + { + case ImGuizmo::TRANSLATE: + snap = config.mSnapTranslation; + ImGui::InputFloat3("Snap", &snap.x); + break; + case ImGuizmo::ROTATE: + snap = config.mSnapRotation; + ImGui::InputFloat("Angle Snap", &snap.x); + break; + case ImGuizmo::SCALE: + snap = config.mSnapScale; + ImGui::InputFloat("Scale Snap", &snap.x); + break; + } + ImGuiIO& io = ImGui::GetIO(); + ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + ImGuizmo::Manipulate(camera.mView.m16, camera.mProjection.m16, mCurrentGizmoOperation, mCurrentGizmoMode, matrix.m16, NULL, useSnap ? &snap.x : NULL); +} +``` + +## Install + +ImGuizmo can be installed via [vcpkg](https://github.com/microsoft/vcpkg) and used cmake + +```bash +vcpkg install imguizmo +``` + +See the [vcpkg example](/vcpkg-example) for more details + +## License + +ImGuizmo is licensed under the MIT License, see [LICENSE](/LICENSE) for more information. diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp index a4767c7..f9e32b2 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -656,7 +656,7 @@ inline std::string toString(const S& /*sh*/) } template -inline std::string serialize(const S& /*sh*/, double /*scale*/=1, std::string fill = "none", std::string stroke = "black", float stroke_width = 1) +inline std::string serialize(const S & /*sh*/, const std::string &name = "", double /*scale*/ = 1, std::string fill = "none", std::string stroke = "black", float stroke_width = 1) { static_assert(always_false::value, "shapelike::serialize() unimplemented!"); diff --git a/src/libnest2d/include/libnest2d/nester.hpp b/src/libnest2d/include/libnest2d/nester.hpp index 782eae3..e57220e 100644 --- a/src/libnest2d/include/libnest2d/nester.hpp +++ b/src/libnest2d/include/libnest2d/nester.hpp @@ -10,6 +10,7 @@ #include #define LARGE_COST_TO_REJECT 1e7 +#define COST_OF_NEW_PLATE 0.1 namespace libnest2d { @@ -75,8 +76,8 @@ class _Item { public: int itemid_{ 0 }; - std::vector extrude_ids; - int filament_temp_type = -1; // -1 means unset. otherwise should be {0,1,2} + std::map extrude_id_filament_types; // extrude id to filament type + int filament_temp_type = -1; // -1 means unset. otherwise should be one of FilamentTempType ie {0,1,2} double height{ 0 }; double print_temp{ 0 }; double bed_temp{ 0 }; @@ -85,7 +86,9 @@ public: //QDS: virtual object to mark unprintable region on heatbed bool is_virt_object{ false }; bool is_wipe_tower{ false }; - bool has_tried_with_excluded{ false }; + bool is_extrusion_cali_object{ false }; + bool has_tried_without_extrusion_cali_obj{ false }; + std::vector allowed_rotations{0.}; /// The type of the shape which was handed over as the template argument. using ShapeType = RawShape; diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index dee2a91..2d84f29 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -18,8 +18,8 @@ #include "placer_boilerplate.hpp" // temporary -//#include "../tools/svgtools.hpp" -//#include // setprecision +#include "../tools/svgtools.hpp" +#include // setprecision #include @@ -130,6 +130,9 @@ struct NfpPConfig { //QDS: sort function for selector std::function& i1, _Item& i2)> sortfunc; + + std::function progressFunc = {}; + //QDS: excluded region for V4 bed std::vector<_Item > m_excluded_regions; _ItemGroup m_excluded_items; @@ -557,10 +560,12 @@ public: template> PackResult trypack(Item& item, const Range& remaining = Range()) { - auto result = _trypack(item, remaining); + if (item.is_wipe_tower) { + PackResult result1 = _trypack_with_original_pos(item); + if (result1.score() >= 0 && result1.score() < LARGE_COST_TO_REJECT) return result1; + } - // Experimental - // if(!result) repack(item, result); + auto result = _trypack(item, remaining); return result; } @@ -685,11 +690,20 @@ private: }; using Edges = EdgeCache; + // item won't overlap with virtual objects if it's inside or touches NFP + // @return 1 if current item overlaps with virtual objects, 0 otherwise + bool overlapWithVirtObject(const Item& item, const Box& binbb){ + if (items_.empty()) return 0; + Shapes nfps = calcnfp(item, binbb, Lvl()); + auto v = item.referenceVertex(); + for (const RawShape &nfp : nfps) { + if (sl::isInside(v, nfp) || sl::touches(v, nfp)) { return false; } + } + return true; + }; template> - PackResult _trypack( - Item& item, - const Range& remaining = Range()) { + PackResult _trypack(Item& item, const Range& remaining = Range()) { PackResult ret; @@ -755,41 +769,41 @@ private: }; } - bool first_object = std::all_of(items_.begin(), items_.end(), [&](const Item &rawShape) { return rawShape.is_virt_object && !rawShape.is_wipe_tower; }); + for (auto &it : items_) { + config_.progressFunc((boost::format("_trypack: plate: %4%, existing object: %1%, pos: (%2%, %3%)") % it.get().name % unscale_(it.get().translation()[0]) % + unscale_(it.get().translation()[1]) % plateID()) + .str()); + } - // item won't overlap with virtual objects if it's inside or touches NFP - // @return 1 if current item overlaps with virtual objects, 0 otherwise - auto overlapWithVirtObject = [&]() -> double { - if (items_.empty()) return 0; - nfps = calcnfp(item, binbb, Lvl()); - auto v = item.referenceVertex(); - for (const RawShape &nfp : nfps) { - if (sl::isInside(v, nfp) || sl::touches(v, nfp)) { return 0; } - } - return 1; - }; + bool first_object = std::all_of(items_.begin(), items_.end(), [&](const Item &rawShape) { return rawShape.is_virt_object && !rawShape.is_wipe_tower; }); if (first_object) { setInitialPosition(item); auto best_tr = item.translation(); auto best_rot = item.rotation(); - best_overfit = overfit(item.transformedShape(), bin_) + overlapWithVirtObject(); + best_overfit = std::numeric_limits::max(); // try normal inflation first, then 0 inflation if not fit. See STUDIO-5566. // Note for by-object printing, bed is expanded by -config_.bed_shrink.x(). Coord inflation_back = item.inflation(); - Coord inflations[2]={inflation_back, std::abs(config_.bed_shrink.x())}; + Coord inflations[2]={inflation_back, 0}; for (size_t i = 0; i < 2; i++) { item.inflation(inflations[i]); - for (auto rot : config_.rotations) { + for (auto rot : item.allowed_rotations) { item.translation(initial_tr); item.rotation(initial_rot + rot); setInitialPosition(item); - double of = overfit(item.transformedShape(), bin_); - if (of + overlapWithVirtObject() < best_overfit) { - best_overfit = of; - best_tr = item.translation(); - best_rot = item.rotation(); + if (!overlapWithVirtObject(item, binbb)) { + double of = overfit(item.transformedShape(), bin_); + if (of < best_overfit) { + best_overfit = of; + best_tr = item.translation(); + best_rot = item.rotation(); + if (best_overfit <= 0) { + config_.progressFunc("First object " + item.name + " can fit with rot=" + std::to_string(rot)); + break; + } + } } } can_pack = best_overfit <= 0; @@ -806,7 +820,7 @@ private: Pile merged_pile = merged_pile_; - for(auto rot : config_.rotations) { + for(auto rot : item.allowed_rotations) { item.translation(initial_tr); item.rotation(initial_rot + rot); @@ -991,6 +1005,7 @@ private: final_rot = initial_rot + rot; can_pack = true; global_score = best_score; + break; } } @@ -998,41 +1013,7 @@ private: item.rotation(final_rot); } -#ifdef SVGTOOLS_HPP - if (config_.save_svg) { - svg::SVGWriter svgwriter; - Box binbb2(binbb.width() * 2, binbb.height() * 2, binbb.center()); // expand bbox to allow object be drawed outside - svgwriter.setSize(binbb2); - svgwriter.conf_.x0 = binbb.width(); - svgwriter.conf_.y0 = -binbb.height() / 2; // origin is top left corner - svgwriter.add_comment("bed"); - svgwriter.writeShape(box2RawShape(binbb), "none", "black"); - svgwriter.add_comment("nfps"); - for (int i = 0; i < nfps.size(); i++) svgwriter.writeShape(nfps[i], "none", "blue"); - for (int i = 0; i < items_.size(); i++) { - svgwriter.add_comment(items_[i].get().name); - svgwriter.writeItem(items_[i], "none", "black"); - } - svgwriter.add_comment("merged_pile_"); - for (int i = 0; i < merged_pile_.size(); i++) svgwriter.writeShape(merged_pile_[i], "none", "yellow"); - svgwriter.add_comment("current item"); - svgwriter.writeItem(item, "red", "red", 2); - - std::stringstream ss; - ss.setf(std::ios::fixed | std::ios::showpoint); - ss.precision(1); - ss << "t=" << round(item.translation().x() / 1e6) << "," - << round(item.translation().y() / 1e6) - //<< "-rot=" << round(item.rotation().toDegrees()) - << "-sco=" << round(global_score); - svgwriter.draw_text(20, 20, ss.str(), "blue", 20); - ss.str(""); - ss << "items.size=" << items_.size() << "-merged_pile.size=" << merged_pile_.size(); - svgwriter.draw_text(20, 40, ss.str(), "blue", 20); - svgwriter.save(boost::filesystem::path("C:/Users/arthur.tang/AppData/Roaming/QIDIStudioInternal/SVG") / - ("nfpplacer_" + std::to_string(plate_id) + "_" + ss.str() + "_" + item.name + ".svg")); - } -#endif + if (config_.save_svg) saveSVG(binbb, nfps, item, global_score, can_pack); if(can_pack) { ret = PackResult(item); @@ -1045,6 +1026,87 @@ private: return ret; } + PackResult _trypack_with_original_pos(Item &item) + { + PackResult ret; + + bool can_pack = false; + double best_overfit = std::numeric_limits::max(); + double global_score = std::numeric_limits::max(); + + auto initial_tr = item.translation(); + auto initial_rot = item.rotation(); + Vertex final_tr = initial_tr; + Radians final_rot = initial_rot; + Shapes nfps; + + auto binbb = sl::boundingBox(bin_); + + for (auto &it : items_) { + config_.progressFunc((boost::format("_trypack_with_original_pos: plate: %4%, existing object: %1%, pos: (%2%, %3%)") % it.get().name % unscale_(it.get().translation()[0]) % + unscale_(it.get().translation()[1]) % plateID()) + .str()); + } + + { + for (auto rot : item.allowed_rotations) { + item.translation(initial_tr); + item.rotation(initial_rot + rot); + + if (!overlapWithVirtObject(item, binbb)) { + can_pack = true; + final_tr = initial_tr; + final_rot = initial_rot + rot; + global_score = 0.3; + break; + } + } + + item.translation(final_tr); + item.rotation(final_rot); + } + + if (config_.save_svg) saveSVG(binbb, nfps, item, global_score, can_pack); + + if (can_pack) { + ret = PackResult(item); + ret.score_ = global_score; + // merged_pile_ = nfp::merge(merged_pile_, item.transformedShape()); + config_.progressFunc((boost::format("_trypack_with_original_pos: item %1% can pack") % item.name).str()); + } else { + ret = PackResult(best_overfit); + } + + return ret; + } + + void saveSVG(Box &binbb, Shapes &nfps, Item &item, double global_score, bool can_pack) + { + svg::SVGWriter svgwriter; + Box binbb2(binbb.width() * 2, binbb.height() * 2, binbb.center()); // expand bbox to allow object be drawed outside + svgwriter.setSize(binbb2); + svgwriter.conf_.x0 = binbb.width(); + svgwriter.conf_.y0 = -binbb.height() / 2; // origin is top left corner + svgwriter.writeShape(box2RawShape(binbb), "bed", "none", "black"); + for (int i = 0; i < nfps.size(); i++) svgwriter.writeShape(nfps[i], "nfp_" + std::to_string(i), "none", "blue", 0.2); + for (int i = 0; i < items_.size(); i++) { svgwriter.writeItem(items_[i], items_[i].get().name, "none", "black", 0.2); } + for (int i = 0; i < merged_pile_.size(); i++) svgwriter.writeShape(merged_pile_[i], "merged_pile_" + std::to_string(i), "none", "yellow", 0.2); + svgwriter.writeItem(item, item.name, "red", "red", 0.3); + + std::stringstream ss; + ss.setf(std::ios::fixed | std::ios::showpoint); + ss.precision(1); + ss << "t=" << round(item.translation().x() / 1e6) << "," + << round(item.translation().y() / 1e6) + //<< "-rot=" << round(item.rotation().toDegrees()) + << "-sco=" << round(global_score); + svgwriter.draw_text(20, 20, ss.str(), "blue", 10); + ss.str(""); + ss << "items.size=" << items_.size() << "-merged_pile.size=" << merged_pile_.size(); + svgwriter.draw_text(20, 40, ss.str(), "blue", 10); + svgwriter.save(boost::filesystem::path("SVG") / ("plate_" + std::to_string(plate_id) + "_" + ss.str() + "_" + item.name + "_canPack=" + std::to_string(can_pack) + ".svg")); + } + RawShape box2RawShape(Box& bbin) { RawShape binrsh; @@ -1158,16 +1220,7 @@ private: auto d = cb - ci; // QDS make sure the item won't clash with excluded regions - // do we have wipe tower after arranging? size_t n_objs = 0; - std::set extruders; - for (const Item& item : items_) { - if (!item.is_virt_object) { - extruders.insert(item.extrude_ids.begin(), item.extrude_ids.end()); - n_objs++; - } - } - bool need_wipe_tower = extruders.size() > 1; std::vector objs,excludes; for (Item &item : items_) { diff --git a/src/libnest2d/include/libnest2d/selections/firstfit.hpp b/src/libnest2d/include/libnest2d/selections/firstfit.hpp index bc23069..85e54f3 100644 --- a/src/libnest2d/include/libnest2d/selections/firstfit.hpp +++ b/src/libnest2d/include/libnest2d/selections/firstfit.hpp @@ -41,7 +41,7 @@ public: std::vector placers; placers.reserve(last-first); - + typename Base::PackGroup fixed_bins; std::for_each(first, last, [this, &fixed_bins](Item& itm) { if (itm.isFixed()) { @@ -67,7 +67,7 @@ public: std::for_each(first, last, [this,&svgwriter](Item &itm) { svgwriter.writeShape(itm, "none", "blue"); }); svgwriter.save(boost::filesystem::path("SVG") / "all_items.svg"); #endif - + std::function sortfunc; if (pconfig.sortfunc) sortfunc = pconfig.sortfunc; @@ -88,7 +88,23 @@ public: for (auto it = store_.begin(); it != store_.end(); ++it) { std::stringstream ss; ss << "initial order: " << it->get().name << ", p=" << it->get().priority() << ", bed_temp=" << it->get().bed_temp << ", height=" << it->get().height - << ", area=" << it->get().area(); + << ", area=" << it->get().area() << ", allowed_rotations="; + for(auto r: it->get().allowed_rotations) ss << r << ", "; + if (this->unfitindicator_) + this->unfitindicator_(ss.str()); + } + // debug: write down fixed items + for (size_t i = 0; i < fixed_bins.size(); i++) { + if (fixed_bins[i].empty()) + continue; + std::stringstream ss; + ss << "fixed bin " << i << ": "; + for (auto it = fixed_bins[i].begin(); it != fixed_bins[i].end(); ++it) { + ss << it->get().name << ", p=" << it->get().priority() << ", bed_temp=" << it->get().bed_temp << ", height=" << it->get().height + << ", area=" << it->get().area() << ", allowed_rotations="; + for(auto r: it->get().allowed_rotations) ss << r << ", "; + ss << "; "; + } if (this->unfitindicator_) this->unfitindicator_(ss.str()); } @@ -101,8 +117,8 @@ public: }; auto& cancelled = this->stopcond_; - - this->template remove_unpackable_items(store_, bin, pconfig); + + //this->template remove_unpackable_items(store_, bin, pconfig); for (auto it = store_.begin(); it != store_.end() && !cancelled(); ++it) { // skip unpackable item @@ -115,13 +131,21 @@ public: double score_all_plates = 0, score_all_plates_best = std::numeric_limits::max(); typename Placer::PackResult result, result_best, result_firstfit; int j = 0; - while(!was_packed && !cancelled()) { - for(; j < placers.size() && !was_packed && !cancelled(); j++) { + while (!was_packed && !cancelled() && placers.size() <= MAX_NUM_PLATES) { + for(; j < placers.size() && !was_packed && !cancelled() && jget().is_wipe_tower && it->get().binId() != placers[j].plateID()) { + if (this->unfitindicator_) + this->unfitindicator_(it->get().name + " cant be placed in plate_id=" + std::to_string(j) + "/" + std::to_string(placers.size()) + ", continue to next plate"); + continue; + } result = placers[j].pack(*it, rem(it, store_)); score = result.score(); - score_all_plates = score; + score_all_plates = score + COST_OF_NEW_PLATE * j; // add a larger cost to larger plate id to encourace to use less plates for (int i = 0; i < placers.size(); i++) { score_all_plates += placers[i].score();} - if (this->unfitindicator_) this->unfitindicator_(it->get().name + " bed_id="+std::to_string(j) + ",score=" + std::to_string(score)+", score_all_plates="+std::to_string(score_all_plates)); + if (this->unfitindicator_) + this->unfitindicator_((boost::format("item %1% bed_id=%2%, score=%3%, score_all_plates=%4%, pos=(%5%, %6%)") % it->get().name % j % score % + score_all_plates % unscale_(it->get().translation()[0]) % unscale_(it->get().translation()[1])) + .str()); if(score >= 0 && score < LARGE_COST_TO_REJECT) { if (bed_id_firstfit == -1) { @@ -161,42 +185,42 @@ public: makeProgress(placers[j], j); } - if (was_packed && it->get().has_tried_with_excluded) { - placers[j].clearItems([](const Item &itm) { return itm.isFixed() && !itm.is_wipe_tower; }); - if (fixed_bins.size() >= placers.size()) - placers[j].preload(fixed_bins[placers.size() - 1]); - } - bool placer_not_packed = !was_packed && !placers.empty() && j == placers.size() && placers[j - 1].getPackedSize() == 0; // large item is not placed into the bin + // if the object can't be packed, try to pack it without extrusion calibration object + bool placer_not_packed = !was_packed && j > 0 && j == placers.size() && placers[j - 1].getPackedSize() == 0; // large item is not placed into the bin if (placer_not_packed) { - if (it->get().has_tried_with_excluded == false) { - it->get().has_tried_with_excluded = true; - placers[j - 1].clearItems([](const Item &itm) { return itm.isFixed()&&!itm.is_wipe_tower; }); - placers[j - 1].preload(pconfig.m_excluded_items); + if (it->get().has_tried_without_extrusion_cali_obj == false) { + it->get().has_tried_without_extrusion_cali_obj = true; + placers[j - 1].clearItems([](const Item &itm) { return itm.is_extrusion_cali_object; }); j = j - 1; continue; - } else { - placers[j - 1].clearItems([](const Item &itm) { return itm.isFixed() && !itm.is_wipe_tower; }); - placers[j - 1].preload(fixed_bins[placers.size() - 1]); } } if(!was_packed){ if (this->unfitindicator_ && !placers.empty()) - this->unfitindicator_(it->get().name + " not fit! height=" +std::to_string(it->get().height) - + " ,plate_id=" + std::to_string(j-1) - + ", score=" + std::to_string(score) - + ", best_bed_id=" + std::to_string(best_bed_id) - + ", score_all_plates=" + std::to_string(score_all_plates) - +", overfit=" + std::to_string(result.overfit())); - - placers.emplace_back(bin); - placers.back().plateID(placers.size() - 1); - placers.back().configure(pconfig); - if (fixed_bins.size() >= placers.size()) - placers.back().preload(fixed_bins[placers.size() - 1]); - //placers.back().preload(pconfig.m_excluded_items); - packed_bins_.emplace_back(); - j = placers.size() - 1; + this->unfitindicator_(it->get().name + " not fit! plate_id=" + std::to_string(placers.back().plateID()) + ", score=" + std::to_string(score) + + ", best_bed_id=" + std::to_string(best_bed_id) + ", score_all_plates=" + std::to_string(score_all_plates) + + ", item.bed_id=" + std::to_string(it->get().binId())); + if (!placers.empty() && placers.back().getItems().empty()) { + it->get().binId(BIN_ID_UNFIT); + if (this->unfitindicator_) this->unfitindicator_(it->get().name + " can't fit into a new bin. Can't fit!"); + // remove the last empty placer to force next item to be fit in existing plates first + if (placers.size() > 1) placers.pop_back(); + break; + } + if (placers.size() < MAX_NUM_PLATES) { + placers.emplace_back(bin); + placers.back().plateID(placers.size() - 1); + placers.back().configure(pconfig); + if (fixed_bins.size() >= placers.size()) placers.back().preload(fixed_bins[placers.size() - 1]); + // placers.back().preload(pconfig.m_excluded_items); + packed_bins_.emplace_back(); + j = placers.size() - 1; + } else { + it->get().binId(BIN_ID_UNFIT); + if (this->unfitindicator_) this->unfitindicator_(it->get().name + " can't fit into any bin. Can't fit!"); + break; + } } } } diff --git a/src/libnest2d/include/libnest2d/utils/boost_alg.hpp b/src/libnest2d/include/libnest2d/utils/boost_alg.hpp index c98161f..960a948 100644 --- a/src/libnest2d/include/libnest2d/utils/boost_alg.hpp +++ b/src/libnest2d/include/libnest2d/utils/boost_alg.hpp @@ -443,7 +443,7 @@ inline void offset(PolygonImpl& sh, bp2d::Coord distance) #ifndef DISABLE_BOOST_SERIALIZE template<> inline std::string serialize( - const PolygonImpl& sh, double scale, std::string fill, std::string stroke, float stroke_width) + const PolygonImpl& sh, const std::string& name, double scale, std::string fill, std::string stroke, float stroke_width) { std::stringstream ss; std::string style = "fill: "+fill+"; stroke: "+stroke+"; stroke-width: "+std::to_string(stroke_width)+"px; "; @@ -478,7 +478,14 @@ template<> inline std::string serialize( ss << svg_data << std::endl; - return ss.str(); + std::string svg_content = ss.str(); + if (!name.empty()) { + size_t pos = svg_content.find_first_of("(box.height() + conf_.y0*2) / conf_.mm_in_coord_units; conf_.width = static_cast(box.width() + conf_.x0*2) / conf_.mm_in_coord_units; } - void writeShape(RawShape tsh, std::string fill = "none", std::string stroke = "black", float stroke_width = 1) { + void writeShape(RawShape tsh, const std::string &name = "", std::string fill = "none", std::string stroke = "black", float stroke_width = 1) + { if(svg_layers_.empty()) addLayer(); if(conf_.origo_location == BOTTOMLEFT) { auto d = static_cast( @@ -74,13 +75,14 @@ public: } } currentLayer() += - shapelike::serialize(tsh, + shapelike::serialize(tsh, name, 1.0 / conf_.mm_in_coord_units, fill, stroke, stroke_width) + "\n"; } - void writeItem(const Item& item, std::string fill = "none", std::string stroke = "black", float stroke_width = 1) { - writeShape(item.transformedShape(), fill, stroke, stroke_width); + void writeItem(const Item &item, const std::string &name = "", std::string fill = "none", std::string stroke = "black", float stroke_width = 1) + { + writeShape(item.transformedShape(), name, fill, stroke, stroke_width); } void writePackGroup(const PackGroup& result) { @@ -97,7 +99,7 @@ public: auto it = from; PackGroup pg; while(it != to) { - if(it->binId() == BIN_ID_UNSET) continue; + if (it->binId() == BIN_ID_UNFIT) continue; while(pg.size() <= size_t(it->binId())) pg.emplace_back(); pg[it->binId()].emplace_back(*it); ++it; @@ -159,6 +161,8 @@ public: currentLayer() += "\n"; } + void clear() { svg_layers_.clear(); } + private: std::string& currentLayer() { return svg_layers_.back(); } diff --git a/src/mcut/include/mcut/internal/frontend.h b/src/mcut/include/mcut/internal/frontend.h index ccc2a2b..1aaffc2 100644 --- a/src/mcut/include/mcut/internal/frontend.h +++ b/src/mcut/include/mcut/internal/frontend.h @@ -38,6 +38,7 @@ #include "mcut/mcut.h" +#include #include #include #include diff --git a/src/platform/osx/Info.plist.in b/src/platform/osx/Info.plist.in index a3dc4cb..5b9792a 100644 --- a/src/platform/osx/Info.plist.in +++ b/src/platform/osx/Info.plist.in @@ -22,6 +22,11 @@ ???? CFBundleVersion @SLIC3R_BUILD_ID@ + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + CFBundleURLTypes @@ -132,10 +137,5 @@ ASAN_OPTIONS detect_container_overflow=0 - NSAppTransportSecurity - - NSAllowsArbitraryLoads - -