From 4348b177d6eda0ae2da1bfd91656ab981338cf9b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 21 May 2020 14:17:58 +0200 Subject: [PATCH 01/14] Added processing of a wxEVT_SYS_COLOUR_CHANGED event. Deleted scale from ImGuiWrapper::load_svg(), because it's no needed and it makes icons bad scaled on Retina displays --- src/slic3r/GUI/ImGuiWrapper.cpp | 10 ---------- src/slic3r/GUI/MainFrame.cpp | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 53c309abd..51a9a6d4e 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -805,14 +805,6 @@ static const ImWchar ranges_keyboard_shortcuts[] = std::vector ImGuiWrapper::load_svg(const std::string& bitmap_name, unsigned target_width, unsigned target_height) { -#ifdef __APPLE__ - // Note: win->GetContentScaleFactor() is not used anymore here because it tends to - // return bogus results quite often (such as 1.0 on Retina or even 0.0). - // We're using the max scaling factor across all screens because it's very likely to be good enough. - double scale = mac_max_scaling_factor(); -#else - double scale = 1.0; -#endif std::vector empty_vector; #ifdef __WXMSW__ @@ -827,8 +819,6 @@ std::vector ImGuiWrapper::load_svg(const std::string& bitmap_name if (image == nullptr) return empty_vector; - target_height != 0 ? target_height *= scale : target_width *= scale; - float svg_scale = target_height != 0 ? (float)target_height / image->height : target_width != 0 ? (float)target_width / image->width : 1; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 8c268ed00..69fa4b86b 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -190,6 +190,27 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S event.Skip(); }); + Bind(wxEVT_SYS_COLOUR_CHANGED, [this](wxSysColourChangedEvent& event) + { + bool recreate_gui = false; + { + // the dialog needs to be destroyed before the call to recreate_gui() + // or sometimes the application crashes into wxDialogBase() destructor + // so we put it into an inner scope + wxMessageDialog dialog(nullptr, + _L("System color mode was changed. " + "It is possible to update the Slicer in respect to the system mode.") + "\n" + + _L("You will lose content of the plater.") + "\n\n" + + _L("Do you want to proceed?"), + wxString(SLIC3R_APP_NAME) + " - " + _L("Switching system color mode"), + wxICON_QUESTION | wxOK | wxCANCEL); + recreate_gui = dialog.ShowModal() == wxID_OK; + } + if (recreate_gui) + wxGetApp().recreate_GUI(_L("Changing of an application in respect to the system mode") + dots); + event.Skip(); + }); + wxGetApp().persist_window_geometry(this, true); update_ui_from_settings(); // FIXME (?) From db32c1f15a8e13e3007ea27251367ed2dcbf38d3 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 21 May 2020 17:29:00 +0200 Subject: [PATCH 02/14] Changed processing of a wxEVT_SYS_COLOUR_CHANGED event. Only UI is updated. The application doesn't recreated now --- src/slic3r/GUI/GUI_ObjectList.cpp | 20 ++++++++++ src/slic3r/GUI/GUI_ObjectList.hpp | 1 + src/slic3r/GUI/GUI_ObjectManipulation.cpp | 17 ++++++++ src/slic3r/GUI/GUI_ObjectManipulation.hpp | 1 + src/slic3r/GUI/GUI_Utils.hpp | 7 ++++ src/slic3r/GUI/MainFrame.cpp | 24 ++++++++++++ src/slic3r/GUI/MainFrame.hpp | 1 + src/slic3r/GUI/Plater.cpp | 39 +++++++++++++++++++ src/slic3r/GUI/Plater.hpp | 2 + src/slic3r/GUI/Search.cpp | 8 ++++ src/slic3r/GUI/Search.hpp | 1 + src/slic3r/GUI/Tab.cpp | 47 +++++++++++++++++++++++ src/slic3r/GUI/Tab.hpp | 2 + 13 files changed, 170 insertions(+) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 419f64567..35ec2e485 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -4029,6 +4029,26 @@ void ObjectList::msw_rescale() Layout(); } +void ObjectList::sys_color_changed() +{ + // msw_rescale_icons() updates icons, so use it + msw_rescale_icons(); + + // update existing items with bitmaps + m_objects_model->Rescale(); + + // msw_rescale_menu updates just icons, so use it + for (MenuWithSeparators* menu : { &m_menu_object, + &m_menu_part, + &m_menu_sla_object, + &m_menu_instance, + &m_menu_layer, + &m_menu_default}) + msw_rescale_menu(menu); + + Layout(); +} + void ObjectList::ItemValueChanged(wxDataViewEvent &event) { if (event.GetColumn() == colName) diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index a0370bd9d..0924d4216 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -386,6 +386,7 @@ public: void paste_objects_into_list(const std::vector& object_idxs); void msw_rescale(); + void sys_color_changed(); void update_after_undo_redo(); //update printable state for item from objects model diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 471369a00..2179a9559 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -981,6 +981,23 @@ void ObjectManipulation::msw_rescale() get_og()->msw_rescale(); } +void ObjectManipulation::sys_color_changed() +{ + // btn...->msw_rescale() updates icon on button, so use it + m_mirror_bitmap_on.msw_rescale(); + m_mirror_bitmap_off.msw_rescale(); + m_mirror_bitmap_hidden.msw_rescale(); + m_reset_scale_button->msw_rescale(); + m_reset_rotation_button->msw_rescale(); + m_drop_to_bed_button->msw_rescale(); + m_lock_bnt->msw_rescale(); + + for (int id = 0; id < 3; ++id) + m_mirror_buttons[id].first->msw_rescale(); + + get_og()->msw_rescale(); +} + static const char axes[] = { 'x', 'y', 'z' }; ManipulationEditor::ManipulationEditor(ObjectManipulation* parent, const std::string& opt_key, diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 64a59ac9b..f002491f0 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -173,6 +173,7 @@ public: void update_item_name(const wxString &item_name); void update_warning_icon_state(const wxString& tooltip); void msw_rescale(); + void sys_color_changed(); void on_change(const std::string& opt_key, int axis, double new_value); void set_focused_editor(ManipulationEditor* focused_editor) { #ifndef __APPLE__ diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 0d5249e25..934845fb3 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -124,6 +124,12 @@ public: // set value to _true_ in purpose of possibility of a display dpi changing from System Settings m_can_rescale = true; }); + + this->Bind(wxEVT_SYS_COLOUR_CHANGED, [this](wxSysColourChangedEvent& event) + { + event.Skip(); + on_sys_color_changed(); + }); } virtual ~DPIAware() {} @@ -137,6 +143,7 @@ public: protected: virtual void on_dpi_changed(const wxRect &suggested_rect) = 0; + virtual void on_sys_color_changed() {}; private: float m_scale_factor; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 69fa4b86b..65c212d11 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -190,6 +190,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S event.Skip(); }); + /* Bind(wxEVT_SYS_COLOUR_CHANGED, [this](wxSysColourChangedEvent& event) { bool recreate_gui = false; @@ -210,6 +211,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S wxGetApp().recreate_GUI(_L("Changing of an application in respect to the system mode") + dots); event.Skip(); }); + */ wxGetApp().persist_window_geometry(this, true); @@ -575,6 +577,28 @@ void MainFrame::on_dpi_changed(const wxRect &suggested_rect) this->Maximize(is_maximized); } +void MainFrame::on_sys_color_changed() +{ + wxBusyCursor wait; + + // update label colors in respect to the system mode + wxGetApp().init_label_colours(); + + wxGetApp().preset_bundle->load_default_preset_bitmaps(); + + // update Plater + wxGetApp().plater()->sys_color_changed(); + + // update Tabs + for (auto tab : wxGetApp().tabs_list) + tab->sys_color_changed(); + + // msw_rescale_menu updates just icons, so use it + wxMenuBar* menu_bar = this->GetMenuBar(); + for (size_t id = 0; id < menu_bar->GetMenuCount(); id++) + msw_rescale_menu(menu_bar->GetMenu(id)); +} + void MainFrame::init_menubar() { #ifdef __APPLE__ diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 219f68319..43375d344 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -121,6 +121,7 @@ class MainFrame : public DPIFrame protected: virtual void on_dpi_changed(const wxRect &suggested_rect); + virtual void on_sys_color_changed() override; public: MainFrame(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7c25c8ea9..23ef1acc0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1090,6 +1090,34 @@ void Sidebar::msw_rescale() p->scrolled->Layout(); } +void Sidebar::sys_color_changed() +{ + // Update preset comboboxes in respect to the system color ... + // combo->msw_rescale() updates icon on button, so use it + for (PresetComboBox* combo : std::vector{ p->combo_print, + p->combo_sla_print, + p->combo_sla_material, + p->combo_printer }) + combo->msw_rescale(); + for (PresetComboBox* combo : p->combos_filament) + combo->msw_rescale(); + + // ... then refill them and set min size to correct layout of the sidebar + update_all_preset_comboboxes(); + + p->object_list->sys_color_changed(); + p->object_manipulation->sys_color_changed(); +// p->object_settings->msw_rescale(); +// p->object_layers->msw_rescale(); + + // btn...->msw_rescale() updates icon on button, so use it + p->btn_send_gcode->msw_rescale(); + p->btn_remove_device->msw_rescale(); + p->btn_export_gcode_removable->msw_rescale(); + + p->scrolled->Layout(); +} + void Sidebar::search() { p->searcher.search(); @@ -5475,6 +5503,17 @@ void Plater::msw_rescale() GetParent()->Layout(); } +void Plater::sys_color_changed() +{ + p->sidebar->sys_color_changed(); + + // msw_rescale_menu updates just icons, so use it + p->msw_rescale_object_menu(); + + Layout(); + GetParent()->Layout(); +} + bool Plater::init_view_toolbar() { return p->init_view_toolbar(); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index c67d92bc4..dcbda00f2 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -105,6 +105,7 @@ public: void update_mode_sizer() const; void update_reslice_btn_tooltip() const; void msw_rescale(); + void sys_color_changed(); void search(); void jump_to_option(size_t selected); @@ -308,6 +309,7 @@ public: bool can_reload_from_disk() const; void msw_rescale(); + void sys_color_changed(); bool init_view_toolbar(); diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 967f6c537..a94b776a1 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -668,6 +668,14 @@ void SearchDialog::on_dpi_changed(const wxRect& suggested_rect) Refresh(); } +void SearchDialog::on_sys_color_changed() +{ + // msw_rescale updates just icons, so use it + search_list_model->msw_rescale(); + + Refresh(); +} + // ---------------------------------------------------------------------------- // SearchListModel // ---------------------------------------------------------------------------- diff --git a/src/slic3r/GUI/Search.hpp b/src/slic3r/GUI/Search.hpp index 533ecf8f3..45a96034a 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -198,6 +198,7 @@ public: protected: void on_dpi_changed(const wxRect& suggested_rect) override; + virtual void on_sys_color_changed() override; }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index ae0877763..49fa03618 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -590,6 +590,18 @@ void TabPrinter::msw_rescale() Layout(); } +void TabPrinter::sys_color_changed() +{ + Tab::sys_color_changed(); + + // update missed options_groups + const std::vector& pages = m_printer_technology == ptFFF ? m_pages_sla : m_pages_fff; + for (auto page : pages) + page->msw_rescale(); + + Layout(); +} + void TabSLAMaterial::init_options_list() { if (!m_options_list.empty()) @@ -869,6 +881,41 @@ void Tab::msw_rescale() Layout(); } +void Tab::sys_color_changed() +{ + update_tab_ui(); + + // update buttons and cached bitmaps + for (const auto btn : m_scaled_buttons) + btn->msw_rescale(); + for (const auto bmp : m_scaled_bitmaps) + bmp->msw_rescale(); + for (ScalableBitmap& bmp : m_mode_bitmap_cache) + bmp.msw_rescale(); + + // update icons for tree_ctrl + for (ScalableBitmap& bmp : m_scaled_icons_list) + bmp.msw_rescale(); + // recreate and set new ImageList for tree_ctrl + m_icons->RemoveAll(); + m_icons = new wxImageList(m_scaled_icons_list.front().bmp().GetWidth(), m_scaled_icons_list.front().bmp().GetHeight()); + for (ScalableBitmap& bmp : m_scaled_icons_list) + m_icons->Add(bmp.bmp()); + m_treectrl->AssignImageList(m_icons); + + + // Colors for ui "decoration" + m_sys_label_clr = wxGetApp().get_label_clr_sys(); + m_modified_label_clr = wxGetApp().get_label_clr_modified(); + update_labels_colour(); + + // update options_groups + for (auto page : m_pages) + page->msw_rescale(); + + Layout(); +} + Field* Tab::get_field(const t_config_option_key& opt_key, int opt_index/* = -1*/) const { Field* field = nullptr; diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index a13d13f2d..affe8c2c8 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -318,6 +318,7 @@ public: void update_mode(); void update_visibility(); virtual void msw_rescale(); + virtual void sys_color_changed(); Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const; Field* get_field(const t_config_option_key &opt_key, Page** selected_page, int opt_index = -1); bool set_value(const t_config_option_key& opt_key, const boost::any& value); @@ -436,6 +437,7 @@ public: void on_preset_loaded() override; void init_options_list() override; void msw_rescale() override; + void sys_color_changed() override; bool supports_printer_technology(const PrinterTechnology /* tech */) override { return true; } wxSizer* create_bed_shape_widget(wxWindow* parent); From 02e345159d0075f2b207953cb05c4e0f5d21cfef Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 22 May 2020 09:23:26 +0200 Subject: [PATCH 03/14] Grey out wipe tower related config values when wipe tower is disabled --- src/slic3r/GUI/ConfigManipulation.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index d7f0a37b0..a0df4c659 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -310,7 +310,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) toggle_field("standby_temperature_delta", have_ooze_prevention); bool have_wipe_tower = config->opt_bool("wipe_tower"); - for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging" }) + for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", + "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" }) toggle_field(el, have_wipe_tower); } From abf279fc44f877f79085c707a6c93228aeea7a0d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 20 May 2020 16:28:46 +0200 Subject: [PATCH 04/14] Fixed compilation with Shiny profiler. --- src/clipper/CMakeLists.txt | 4 ++++ src/libslic3r/CMakeLists.txt | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/clipper/CMakeLists.txt b/src/clipper/CMakeLists.txt index 412ab53c7..8a4e92852 100644 --- a/src/clipper/CMakeLists.txt +++ b/src/clipper/CMakeLists.txt @@ -7,3 +7,7 @@ add_library(clipper STATIC clipper_z.cpp clipper_z.hpp ) + +if(SLIC3R_PROFILE) + target_link_libraries(clipper Shiny) +endif() diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 12e61d7f3..26f2190e5 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -304,7 +304,7 @@ if(WIN32) endif() if(SLIC3R_PROFILE) - target_link_libraries(slic3r Shiny) + target_link_libraries(libslic3r Shiny) endif() if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) From eeb9590d2865bca9099441ca5d76eba904360ea5 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 20 May 2020 16:30:30 +0200 Subject: [PATCH 05/14] WIP: own AABBTreeIndirect, builds up the tree 4x quicker than libigl. --- src/libslic3r/AABBTreeIndirect.hpp | 556 ++++++++++++++++++++++++++ tests/libslic3r/CMakeLists.txt | 1 + tests/libslic3r/test_aabbindirect.cpp | 61 +++ 3 files changed, 618 insertions(+) create mode 100644 src/libslic3r/AABBTreeIndirect.hpp create mode 100644 tests/libslic3r/test_aabbindirect.cpp diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp new file mode 100644 index 000000000..a541345ae --- /dev/null +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -0,0 +1,556 @@ +// AABB tree built upon external data set, referencing the external data by integer indices. + +#ifndef slic3r_AABBTreeIndirect_hpp_ +#define slic3r_AABBTreeIndirect_hpp_ + +#include +#include +#include + +#include "Utils.hpp" // for next_highest_power_of_2() + +extern "C" +{ +#include +} +#include +#include + +namespace Slic3r { +namespace AABBTreeIndirect { + +// AABB tree for raycasting and closest triangle search. +template +class Tree +{ +public: + static constexpr int NumDimensions = ANumDimensions; + using CoordType = ACoordType; + using Vec3crd = Eigen::Matrix; + using BoundingBox = Eigen::AlignedBox; + // Following could be static constexpr size_t, but that would not link in C++11 + enum : size_t { + // Node is not used. + npos = size_t(-1), + // Inner node (not leaf). + inner = size_t(-2) + }; + + struct Node { + // Index of the external source entity, for which this AABB tree was built, npos for internal nodes. + size_t idx = npos; + // Bounding box around this entity, possibly with epsilons applied. + BoundingBox bbox; + + bool is_valid() const { return this->idx != npos; } + bool is_inner() const { return this->idx == inner; } + bool is_leaf() const { return ! this->is_inner(); } + + template + void set(const SourceNode &rhs) { + this->idx = rhs.idx(); + this->bbox = rhs.bbox(); + } + }; + + void clear() { m_nodes.clear(); } + + // SourceNode shall implement + // size_t SourceNode::idx() const + // - index to the outside triangle. + // const Vec3crd& SourceNode::centroid() const + // - centroid of this node, for splitting the triangles into left / right bounding box. + // const BoundingBox& SourceNode::bbox() const + // - bounding box of this node, likely expanded with epsilon to account for numeric rounding during tree traversal. + template + void build(std::vector &&input) + { + if (input.empty()) + clear(); + else { + // Allocate enough memory for a full binary tree. + //FIXME fianlize the tree size formula. + m_nodes.assign(next_highest_power_of_2(input.size() * 2 + 1), Node()); + build_recursive(input, 0, 0, input.size() - 1); + } + input.clear(); + } + + const std::vector& nodes() const { return m_nodes; } + const Node& node(size_t idx) const { return m_nodes[idx]; } + bool empty() const { return m_nodes.empty(); } + + template + void build(const std::vector &input) + { + std::vector copy(input); + this->build(std::move(copy)); + } + +private: + // Build a balanced tree by splitting the input sequence by an axis aligned plane at a dimension. + template + void build_recursive(std::vector &input, size_t node, const size_t left, const size_t right) + { + assert(node < m_nodes.size()); + assert(left <= right); + + if (left == right) { + // Insert a node into the balanced tree. + m_nodes[node].set(input[left]); + return; + } + + // Calculate bounding box of the input. + BoundingBox bbox(input[left].bbox()); + for (size_t i = left + 1; i <= right; ++ i) + bbox.extend(input[i].bbox()); + int dimension = -1; + bbox.diagonal().maxCoeff(&dimension); + + // Partition the input to left / right pieces of the same length to produce a balanced tree. + size_t center = (left + right) / 2; + partition_input(input, size_t(dimension), left, right, center); + // Insert a node into the tree. + m_nodes[node].idx = inner; + m_nodes[node].bbox = bbox; + build_recursive(input, node * 2 + 1, left, center); + build_recursive(input, node * 2 + 2, center + 1, right); + } + + // Partition the input m_nodes at "k" and "dimension" using the QuickSelect method: + // https://en.wikipedia.org/wiki/Quickselect + // Items left of the k'th item are lower than the k'th item in the "dimension", + // items right of the k'th item are higher than the k'th item in the "dimension", + template + void partition_input(std::vector &input, const size_t dimension, size_t left, size_t right, const size_t k) const + { + while (left < right) { + size_t center = (left + right) / 2; + CoordType pivot; + { + // Bubble sort the input[left], input[center], input[right], so that a median of the three values + // will end up in input[center]. + CoordType left_value = input[left ].centroid()(dimension); + CoordType center_value = input[center].centroid()(dimension); + CoordType right_value = input[right ].centroid()(dimension); + if (left_value > center_value) { + std::swap(input[left], input[center]); + std::swap(left_value, center_value); + } + if (left_value > right_value) { + std::swap(input[left], input[right]); + right_value = left_value; + } + if (center_value > right_value) { + std::swap(input[center], input[right]); + center_value = right_value; + } + pivot = center_value; + } + if (right <= left + 2) + // The interval is already sorted. + break; + size_t i = left; + size_t j = right - 1; + std::swap(input[center], input[j]); + // Partition the set based on the pivot. + for (;;) { + // Skip left points that are already at correct positions. + // Search will certainly stop at position (right - 1), which stores the pivot. + while (input[++ i].centroid()(dimension) < pivot) ; + // Skip right points that are already at correct positions. + while (input[-- j].centroid()(dimension) > pivot && i < j) ; + if (i >= j) + break; + std::swap(input[i], input[j]); + } + // Restore pivot to the center of the sequence. + std::swap(input[i], input[right - 1]); + // Which side the kth element is in? + if (k < i) + right = i - 1; + else if (k == i) + // Sequence is partitioned, kth element is at its place. + break; + else + left = i + 1; + } + } + + std::vector m_nodes; +}; + +template +inline Tree<3, typename VertexType::Scalar> + build_aabb_tree(const std::vector &vertices, const std::vector &faces) +{ + using TreeType = Tree<3, typename VertexType::Scalar>; + using CoordType = typename TreeType::CoordType; + using Vec3crd = typename TreeType::Vec3crd; + using BoundingBox = typename TreeType::BoundingBox; + static constexpr CoordType eps = CoordType(1e-4); + + struct InputType { + size_t idx() const { return m_idx; } + const BoundingBox& bbox() const { return m_bbox; } + const Vec3crd& centroid() const { return m_centroid; } + + size_t m_idx; + BoundingBox m_bbox; + Vec3crd m_centroid; + }; + + std::vector input; + input.reserve(faces.size()); + Vec3crd veps(eps, eps, eps); + for (size_t i = 0; i < faces.size(); ++ i) { + const IndexedFaceType &face = faces[i]; + const VertexType &v1 = vertices[face(0)]; + const VertexType &v2 = vertices[face(1)]; + const VertexType &v3 = vertices[face(2)]; + InputType n; + n.m_idx = i; + n.m_centroid = (1./3.) * (v1 + v2 + v3); + n.m_bbox = BoundingBox(v1, v1); + n.m_bbox.extend(v2); + n.m_bbox.extend(v3); + n.m_bbox.min() -= veps; + n.m_bbox.max() += veps; + input.emplace_back(n); + } + + TreeType out; + out.build(std::move(input)); + return out; +} + +namespace detail { + template + struct RayIntersector { + using VertexType = AVertexType; + using IndexedFaceType = AIndexedFaceType; + using TreeType = ATreeType; + using VectorType = AVectorType; + + const std::vector &vertices; + const std::vector &faces; + const TreeType &tree; + + const VectorType origin; + const VectorType dir; + }; + + template + struct RayIntersectorHits : RayIntersector { + std::vector hits; + }; + + template + static inline bool intersect_ray_recursive_first_hit( + RayIntersectorType &ray_intersector, + size_t node_idx, + Scalar min_t, + igl::Hit &hit) + { + const auto &nodes = ray_intersector.tree.nodes(); + if (node_idx >= nodes.size()) + return false; + + const auto &node = nodes[node_idx]; + if (! node.is_valid()) + return false; + + { + Scalar t_start, t_end; + if (! igl::ray_box_intersect(ray_intersector.origin, ray_intersector.dir, node.bbox.template cast(), Scalar(0), min_t, t_start, t_end)) + return false; + } + + if (node.is_leaf()) { + using Vector = Eigen::Matrix; + Vector origin_d = ray_intersector.origin.template cast(); + Vector dir_d = ray_intersector.dir .template cast(); + auto face = ray_intersector.faces[node.idx]; + Vector v0 = ray_intersector.vertices[face(0)].template cast(); + Vector v1 = ray_intersector.vertices[face(1)].template cast(); + Vector v2 = ray_intersector.vertices[face(2)].template cast(); + // shoot ray, record hit + double t, u, v; + if (intersect_triangle1(origin_d.data(), dir_d.data(), v0.data(), v1.data(), v2.data(), &t, &u, &v) && t > 0.) { + hit = igl::Hit { int(node.idx), -1, float(u), float(v), float(t) }; + return true; + } + return false; + } + + // Left / right child node index. + size_t left = node_idx * 2 + 1; + size_t right = left + 1; + igl::Hit left_hit; + igl::Hit right_hit; + bool left_ret = intersect_ray_recursive_first_hit(ray_intersector, left, min_t, left_hit); + if (left_ret && left_hit.t < min_t) { + min_t = left_hit.t; + hit = left_hit; + } else + left_ret = false; + bool right_ret = intersect_ray_recursive_first_hit(ray_intersector, right, min_t, right_hit); + if (right_ret && right_hit.t < min_t) + hit = right_hit; + else + right_ret = false; + return left_ret || right_ret; + } + + template + static inline void intersect_ray_recursive_all_hits(RayIntersectorType &ray_intersector, size_t node_idx) + { + using Vector = typename RayIntersectorType::VectorType; + using Scalar = typename Vector::Scalar; + + const auto &node = ray_intersector.tree.node(node_idx); + if (! node.is_valid()) + return; + + { + Scalar t_start, t_end; + if (! igl::ray_box_intersect(ray_intersector.origin, ray_intersector.dir, node.bbox.template cast(), + Scalar(0), std::numeric_limits::infinity(), t_start, t_end)) + return; + } + + if (node.is_leaf()) { + using Vector = Eigen::Matrix; + Vector origin_d = ray_intersector.origin.template cast(); + Vector dir_d = ray_intersector.dir .template cast(); + auto face = ray_intersector.faces[node.idx]; + Vector v0 = ray_intersector.vertices[face(0)].template cast(); + Vector v1 = ray_intersector.vertices[face(1)].template cast(); + Vector v2 = ray_intersector.vertices[face(2)].template cast(); + // shoot ray, record hit + double t, u, v; + if (intersect_triangle1(origin_d.data(), dir_d.data(), v0.data(), v1.data(), v2.data(), &t, &u, &v) && t > 0.) + ray_intersector.hits.emplace_back(igl::Hit{ int(node.idx), -1, float(u), float(v), float(t) }); + return; + } + + // Left / right child node index. + size_t left = node_idx * 2 + 1; + size_t right = left + 1; + intersect_ray_recursive_all_hits(ray_intersector, left); + intersect_ray_recursive_all_hits(ray_intersector, right); + } + + template + struct IndexedTriangleSetDistancer { + using VertexType = AVertexType; + using IndexedFaceType = AIndexedFaceType; + using TreeType = ATreeType; + using VectorType = AVectorType; + + const std::vector &vertices; + const std::vector &faces; + const TreeType &tree; + + const VectorType origin; + }; + + // Real-time collision detection, Ericson, Chapter 5 + template + static inline Vector closest_point_to_triangle(const Vector &p, const Vector &a, const Vector &b, const Vector &c) + { + using Scalar = typename Vector::Scalar; + // Check if P in vertex region outside A + Vector ab = b - a; + Vector ac = c - a; + Vector ap = p - a; + Scalar d1 = ab.dot(ap); + Scalar d2 = ac.dot(ap); + if (d1 <= Scalar(0) && d2 <= Scalar(0)) + return a; + // Check if P in vertex region outside B + Vector bp = p - b; + Scalar d3 = ab.dot(bp); + Scalar d4 = ac.dot(bp); + if (d3 >= Scalar(0) && d4 <= d3) + return b; + // Check if P in edge region of AB, if so return projection of P onto AB + Scalar vc = d1*d4 - d3*d2; + if (a != b && vc <= Scalar(0) && d1 >= Scalar(0) && d3 <= Scalar(0)) { + Scalar v = d1 / (d1 - d3); + return a + v * ab; + } + // Check if P in vertex region outside C + Vector cp = p - c; + Scalar d5 = ab.dot(cp); + Scalar d6 = ac.dot(cp); + if (d6 >= Scalar(0) && d5 <= d6) + return c; + // Check if P in edge region of AC, if so return projection of P onto AC + Scalar vb = d5*d2 - d1*d6; + if (vb <= Scalar(0) && d2 >= Scalar(0) && d6 <= Scalar(0)) { + Scalar w = d2 / (d2 - d6); + return a + w * ac; + } + // Check if P in edge region of BC, if so return projection of P onto BC + Scalar va = d3*d6 - d5*d4; + if (va <= Scalar(0) && (d4 - d3) >= Scalar(0) && (d5 - d6) >= Scalar(0)) { + Scalar w = (d4 - d3) / ((d4 - d3) + (d5 - d6)); + return b + w * (c - b); + } + // P inside face region. Compute Q through its barycentric coordinates (u,v,w) + Scalar denom = Scalar(1.0) / (va + vb + vc); + Scalar v = vb * denom; + Scalar w = vc * denom; + return a + ab * v + ac * w; // = u*a + v*b + w*c, u = va * denom = 1.0-v-w + }; + + template + static inline Scalar squared_distance_recursive( + IndexedTriangleSetDistancerType &distancer, + size_t node_idx, + Scalar low_sqr_d, + Scalar up_sqr_d, + size_t &i, + Eigen::PlainObjectBase &c) + { + using Vector = typename IndexedTriangleSetDistancerType::VectorType; + + if (low_sqr_d > up_sqr_d) + return low_sqr_d; + + auto set_min = [&i, &c, &up_sqr_d](const Scalar sqr_d_candidate, const size_t i_candidate, const Vector &c_candidate) { + if (sqr_d_candidate < up_sqr_d) { + i = i_candidate; + c = c_candidate; + up_sqr_d = sqr_d_candidate; + } + }; + + const auto &node = distancer.tree.node(node_idx); + assert(node.is_valid()); + if (node.is_leaf()) + { + const auto &triangle = distancer.faces[node.idx]; + Vector c_candidate = closest_point_to_triangle( + distancer.origin, + distancer.vertices[triangle(0)].template cast(), + distancer.vertices[triangle(1)].template cast(), + distancer.vertices[triangle(2)].template cast()); + set_min((c_candidate - distancer.origin).squaredNorm(), node.idx, c_candidate); + } + else + { + size_t left_node_idx = node_idx * 2 + 1; + size_t right_node_idx = left_node_idx + 1; + const auto &node_left = distancer.tree.node(left_node_idx); + const auto &node_right = distancer.tree.node(right_node_idx); + assert(node_left.is_valid()); + assert(node_right.is_valid()); + + bool looked_left = false; + bool looked_right = false; + const auto &look_left = [&]() + { + size_t i_left; + Vector c_left = c; + Scalar sqr_d_left = squared_distance_recursive(distancer, left_node_idx, low_sqr_d, up_sqr_d, i_left, c_left); + set_min(sqr_d_left, i_left, c_left); + looked_left = true; + }; + const auto &look_right = [&]() + { + size_t i_right; + Vector c_right = c; + Scalar sqr_d_right = squared_distance_recursive(distancer, right_node_idx, low_sqr_d, up_sqr_d, i_right, c_right); + set_min(sqr_d_right, i_right, c_right); + looked_right = true; + }; + + // must look left or right if in box + using BBoxScalar = typename IndexedTriangleSetDistancerType::TreeType::BoundingBox::Scalar; + if (node_left.bbox.contains(distancer.origin.template cast())) + look_left(); + if (node_right.bbox.contains(distancer.origin.template cast())) + look_right(); + // if haven't looked left and could be less than current min, then look + Scalar left_up_sqr_d = node_left.bbox.squaredExteriorDistance(distancer.origin); + Scalar right_up_sqr_d = node_right.bbox.squaredExteriorDistance(distancer.origin); + if (left_up_sqr_d < right_up_sqr_d) { + if (! looked_left && left_up_sqr_d < up_sqr_d) + look_left(); + if (! looked_right && right_up_sqr_d < up_sqr_d) + look_right(); + } else { + if (! looked_right && right_up_sqr_d < up_sqr_d) + look_right(); + if (! looked_left && left_up_sqr_d < up_sqr_d) + look_left(); + } + } + return up_sqr_d; + } + +} // namespace detail + +template +inline bool intersect_ray_first_hit( + const std::vector &vertices, + const std::vector &faces, + const TreeType &tree, + const VectorType &origin, + const VectorType &dir, + igl::Hit &hit) +{ + using Scalar = typename VectorType::Scalar; + auto ray_intersector = detail::RayIntersector { + vertices, faces, tree, + origin, dir + }; + return ! tree.empty() && detail::intersect_ray_recursive_first_hit( + ray_intersector, size_t(0), std::numeric_limits::infinity(), hit); +} + +template +inline bool intersect_ray_all_hits( + const std::vector &vertices, + const std::vector &faces, + const TreeType &tree, + const VectorType &origin, + const VectorType &dir, + std::vector &hits) +{ + auto ray_intersector = detail::RayIntersectorHits { + vertices, faces, tree, + origin, dir + }; + if (! tree.empty()) { + ray_intersector.hits.reserve(8); + detail::intersect_ray_recursive_all_hits(ray_intersector, 0); + std::swap(hits, ray_intersector.hits); + std::sort(hits.begin(), hits.end(), [](const auto &l, const auto &r) { return l.t < r.t; }); + } + return ! hits.empty(); +} + +// Closest point to triangle test will be performed with the accuracy of VectorType::Scalar. +template +inline typename VectorType::Scalar squared_distance( + const std::vector &vertices, + const std::vector &faces, + const TreeType &tree, + const VectorType &point, + size_t &hit_idx_out, + Eigen::PlainObjectBase &hit_point_out) +{ + using Scalar = typename VectorType::Scalar; + auto distancer = detail::IndexedTriangleSetDistancer + { vertices, faces, tree, point }; + return detail::squared_distance_recursive(distancer, size_t(0), Scalar(0), std::numeric_limits::infinity(), hit_idx_out, hit_point_out); +} + +} // namespace AABBTreeIndirect +} // namespace Slic3r + +#endif /* slic3r_AABBTreeIndirect_hpp_ */ diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index b41dbf8ba..7f86144cd 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -3,6 +3,7 @@ get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests.cpp test_3mf.cpp + test_aabbindirect.cpp test_clipper_offset.cpp test_clipper_utils.cpp test_config.cpp diff --git a/tests/libslic3r/test_aabbindirect.cpp b/tests/libslic3r/test_aabbindirect.cpp new file mode 100644 index 000000000..017df9307 --- /dev/null +++ b/tests/libslic3r/test_aabbindirect.cpp @@ -0,0 +1,61 @@ +#include +#include + +#include +#include + +using namespace Slic3r; + +TEST_CASE("Building a tree over a box, ray caster and closest query", "[AABBIndirect]") +{ + TriangleMesh tmesh = make_cube(1., 1., 1.); + tmesh.repair(); + + auto tree = AABBTreeIndirect::build_aabb_tree(tmesh.its.vertices, tmesh.its.indices); + REQUIRE(! tree.empty()); + + igl::Hit hit; + bool intersected = AABBTreeIndirect::intersect_ray_first_hit( + tmesh.its.vertices, tmesh.its.indices, + tree, + Vec3d(0.5, 0.5, -5.), + Vec3d(0., 0., 1.), + hit); + + REQUIRE(intersected); + REQUIRE(hit.t == Approx(5.)); + + std::vector hits; + bool intersected2 = AABBTreeIndirect::intersect_ray_all_hits( + tmesh.its.vertices, tmesh.its.indices, + tree, + Vec3d(0.3, 0.5, -5.), + Vec3d(0., 0., 1.), + hits); + REQUIRE(intersected2); + REQUIRE(hits.size() == 2); + REQUIRE(hits.front().t == Approx(5.)); + REQUIRE(hits.back().t == Approx(6.)); + + size_t hit_idx; + Vec3d closest_point; + double squared_distance = AABBTreeIndirect::squared_distance( + tmesh.its.vertices, tmesh.its.indices, + tree, + Vec3d(0.3, 0.5, -5.), + hit_idx, closest_point); + REQUIRE(squared_distance == Approx(5. * 5.)); + REQUIRE(closest_point.x() == Approx(0.3)); + REQUIRE(closest_point.y() == Approx(0.5)); + REQUIRE(closest_point.z() == Approx(0.)); + + squared_distance = AABBTreeIndirect::squared_distance( + tmesh.its.vertices, tmesh.its.indices, + tree, + Vec3d(0.3, 0.5, 5.), + hit_idx, closest_point); + REQUIRE(squared_distance == Approx(4. * 4.)); + REQUIRE(closest_point.x() == Approx(0.3)); + REQUIRE(closest_point.y() == Approx(0.5)); + REQUIRE(closest_point.z() == Approx(1.)); +} From 2b8f655020adeab5d5d679fec19bf4f73a8ab16a Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 21 May 2020 11:04:53 +0200 Subject: [PATCH 06/14] WIP AABBIndirect: Documentation, polishing. --- src/libslic3r/AABBTreeIndirect.hpp | 210 +++++++++++++++++--------- tests/libslic3r/test_aabbindirect.cpp | 6 +- 2 files changed, 139 insertions(+), 77 deletions(-) diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index a541345ae..134b15b71 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -1,4 +1,7 @@ // AABB tree built upon external data set, referencing the external data by integer indices. +// The AABB tree balancing and traversal (ray casting, closest triangle of an indexed triangle mesh) +// were adapted from libigl AABB.{cpp,hpp} Copyright (C) 2015 Alec Jacobson +// while the implicit balanced tree representation and memory optimizations are Vojtech's. #ifndef slic3r_AABBTreeIndirect_hpp_ #define slic3r_AABBTreeIndirect_hpp_ @@ -11,22 +14,37 @@ extern "C" { +// Ray-Triangle Intersection Test Routines by Tomas Moller, May 2000 #include } +// Definition of the ray intersection hit structure. #include +// Find intersection parameters of a ray with axis aligned bounding box. #include namespace Slic3r { namespace AABBTreeIndirect { -// AABB tree for raycasting and closest triangle search. +// Static balanced AABB tree for raycasting and closest triangle search. +// The balanced tree is built over a single large std::vector of nodes, where the children of nodes +// are addressed implicitely using a power of two indexing rule. +// Memory for a full balanced tree is allocated, but not all nodes at the last level are used. +// This may seem like a waste of memory, but one saves memory for the node links and there is zero +// overhead of a memory allocator management (usually the memory allocator adds at least one pointer +// before the memory returned). However, allocating memory in a single vector is very fast even +// in multi-threaded environment and it is cache friendly. +// +// A balanced tree is built upon a vector of bounding boxes and their centroids, storing the reference +// to the source entity (a 3D triangle, a 2D segment etc, a 3D or 2D point etc). +// The source bounding boxes may have an epsilon applied to fight numeric rounding errors when +// traversing the AABB tree. template class Tree { public: static constexpr int NumDimensions = ANumDimensions; using CoordType = ACoordType; - using Vec3crd = Eigen::Matrix; + using VectorType = Eigen::Matrix; using BoundingBox = Eigen::AlignedBox; // Following could be static constexpr size_t, but that would not link in C++11 enum : size_t { @@ -36,10 +54,13 @@ public: inner = size_t(-2) }; + // Single node of the implicit balanced AABB tree. There are no links to the children nodes, + // as these links are calculated implicitely using a power of two rule. struct Node { // Index of the external source entity, for which this AABB tree was built, npos for internal nodes. size_t idx = npos; - // Bounding box around this entity, possibly with epsilons applied. + // Bounding box around this entity, possibly with epsilons applied to fight numeric rounding errors + // when traversing the AABB tree. BoundingBox bbox; bool is_valid() const { return this->idx != npos; } @@ -57,11 +78,13 @@ public: // SourceNode shall implement // size_t SourceNode::idx() const - // - index to the outside triangle. - // const Vec3crd& SourceNode::centroid() const - // - centroid of this node, for splitting the triangles into left / right bounding box. + // - Index to the outside entity (triangle, edge, point etc). + // const VectorType& SourceNode::centroid() const + // - Centroid of this node. The centroid is used for balancing the tree. // const BoundingBox& SourceNode::bbox() const - // - bounding box of this node, likely expanded with epsilon to account for numeric rounding during tree traversal. + // - Bounding box of this node, likely expanded with epsilon to account for numeric rounding during tree traversal. + // Union of bounding boxes at a single level of the AABB tree is used for deciding the longest axis aligned dimension + // to split around. template void build(std::vector &&input) { @@ -69,8 +92,7 @@ public: clear(); else { // Allocate enough memory for a full binary tree. - //FIXME fianlize the tree size formula. - m_nodes.assign(next_highest_power_of_2(input.size() * 2 + 1), Node()); + m_nodes.assign(next_highest_power_of_2(input.size()) * 2 - 1, Node()); build_recursive(input, 0, 0, input.size() - 1); } input.clear(); @@ -80,6 +102,12 @@ public: const Node& node(size_t idx) const { return m_nodes[idx]; } bool empty() const { return m_nodes.empty(); } + // Addressing the child nodes using the power of two rule. + static size_t left_child_idx(size_t idx) { return idx * 2 + 1; } + static size_t right_child_idx(size_t idx) { return left_child_idx(idx) + 1; } + const Node& left_child(size_t idx) const { return m_nodes[left_child_idx(idx)]; } + const Node& right_child(size_t idx) const { return m_nodes[right_child_idx(idx)]; } + template void build(const std::vector &input) { @@ -111,7 +139,7 @@ private: // Partition the input to left / right pieces of the same length to produce a balanced tree. size_t center = (left + right) / 2; partition_input(input, size_t(dimension), left, right, center); - // Insert a node into the tree. + // Insert an inner node into the tree. Inner node does not reference any input entity (triangle, line segment etc). m_nodes[node].idx = inner; m_nodes[node].bbox = bbox; build_recursive(input, node * 2 + 1, left, center); @@ -178,53 +206,10 @@ private: } } + // The balanced tree storage. std::vector m_nodes; }; -template -inline Tree<3, typename VertexType::Scalar> - build_aabb_tree(const std::vector &vertices, const std::vector &faces) -{ - using TreeType = Tree<3, typename VertexType::Scalar>; - using CoordType = typename TreeType::CoordType; - using Vec3crd = typename TreeType::Vec3crd; - using BoundingBox = typename TreeType::BoundingBox; - static constexpr CoordType eps = CoordType(1e-4); - - struct InputType { - size_t idx() const { return m_idx; } - const BoundingBox& bbox() const { return m_bbox; } - const Vec3crd& centroid() const { return m_centroid; } - - size_t m_idx; - BoundingBox m_bbox; - Vec3crd m_centroid; - }; - - std::vector input; - input.reserve(faces.size()); - Vec3crd veps(eps, eps, eps); - for (size_t i = 0; i < faces.size(); ++ i) { - const IndexedFaceType &face = faces[i]; - const VertexType &v1 = vertices[face(0)]; - const VertexType &v2 = vertices[face(1)]; - const VertexType &v3 = vertices[face(2)]; - InputType n; - n.m_idx = i; - n.m_centroid = (1./3.) * (v1 + v2 + v3); - n.m_bbox = BoundingBox(v1, v1); - n.m_bbox.extend(v2); - n.m_bbox.extend(v3); - n.m_bbox.min() -= veps; - n.m_bbox.max() += veps; - input.emplace_back(n); - } - - TreeType out; - out.build(std::move(input)); - return out; -} - namespace detail { template struct RayIntersector { @@ -253,14 +238,9 @@ namespace detail { Scalar min_t, igl::Hit &hit) { - const auto &nodes = ray_intersector.tree.nodes(); - if (node_idx >= nodes.size()) - return false; - - const auto &node = nodes[node_idx]; - if (! node.is_valid()) - return false; - + const auto &node = ray_intersector.tree.node(node_idx); + assert(node.is_valid()); + { Scalar t_start, t_end; if (! igl::ray_box_intersect(ray_intersector.origin, ray_intersector.dir, node.bbox.template cast(), Scalar(0), min_t, t_start, t_end)) @@ -268,7 +248,7 @@ namespace detail { } if (node.is_leaf()) { - using Vector = Eigen::Matrix; + using Vector = Eigen::Matrix; Vector origin_d = ray_intersector.origin.template cast(); Vector dir_d = ray_intersector.dir .template cast(); auto face = ray_intersector.faces[node.idx]; @@ -306,12 +286,10 @@ namespace detail { template static inline void intersect_ray_recursive_all_hits(RayIntersectorType &ray_intersector, size_t node_idx) { - using Vector = typename RayIntersectorType::VectorType; - using Scalar = typename Vector::Scalar; + using Scalar = typename RayIntersectorType::VectorType::Scalar; const auto &node = ray_intersector.tree.node(node_idx); - if (! node.is_valid()) - return; + assert(node.is_valid()); { Scalar t_start, t_end; @@ -321,7 +299,7 @@ namespace detail { } if (node.is_leaf()) { - using Vector = Eigen::Matrix; + using Vector = Eigen::Matrix; Vector origin_d = ray_intersector.origin.template cast(); Vector dir_d = ray_intersector.dir .template cast(); auto face = ray_intersector.faces[node.idx]; @@ -342,6 +320,7 @@ namespace detail { intersect_ray_recursive_all_hits(ray_intersector, right); } + // Nothing to do with COVID-19 social distancing. template struct IndexedTriangleSetDistancer { using VertexType = AVertexType; @@ -407,7 +386,7 @@ namespace detail { }; template - static inline Scalar squared_distance_recursive( + static inline Scalar squared_distance_to_indexed_triangle_set_recursive( IndexedTriangleSetDistancerType &distancer, size_t node_idx, Scalar low_sqr_d, @@ -420,6 +399,7 @@ namespace detail { if (low_sqr_d > up_sqr_d) return low_sqr_d; + // Save the best achieved hit. auto set_min = [&i, &c, &up_sqr_d](const Scalar sqr_d_candidate, const size_t i_candidate, const Vector &c_candidate) { if (sqr_d_candidate < up_sqr_d) { i = i_candidate; @@ -455,7 +435,7 @@ namespace detail { { size_t i_left; Vector c_left = c; - Scalar sqr_d_left = squared_distance_recursive(distancer, left_node_idx, low_sqr_d, up_sqr_d, i_left, c_left); + Scalar sqr_d_left = squared_distance_to_indexed_triangle_set_recursive(distancer, left_node_idx, low_sqr_d, up_sqr_d, i_left, c_left); set_min(sqr_d_left, i_left, c_left); looked_left = true; }; @@ -463,7 +443,7 @@ namespace detail { { size_t i_right; Vector c_right = c; - Scalar sqr_d_right = squared_distance_recursive(distancer, right_node_idx, low_sqr_d, up_sqr_d, i_right, c_right); + Scalar sqr_d_right = squared_distance_to_indexed_triangle_set_recursive(distancer, right_node_idx, low_sqr_d, up_sqr_d, i_right, c_right); set_min(sqr_d_right, i_right, c_right); looked_right = true; }; @@ -494,13 +474,73 @@ namespace detail { } // namespace detail +// Build a balanced AABB Tree over an indexed triangles set, balancing the tree +// on centroids of the triangles. +// Epsilon is applied to the bounding boxes of the AABB Tree to cope with numeric inaccuracies +// during tree traversal. +template +inline Tree<3, typename VertexType::Scalar> build_aabb_tree_over_indexed_triangle_set( + // Indexed triangle set - 3D vertices. + const std::vector &vertices, + // Indexed triangle set - triangular faces, references to vertices. + const std::vector &faces) +{ + using TreeType = Tree<3, typename VertexType::Scalar>; + using CoordType = typename TreeType::CoordType; + using VectorType = typename TreeType::VectorType; + using BoundingBox = typename TreeType::BoundingBox; + static constexpr CoordType eps = CoordType(1e-4); + + struct InputType { + size_t idx() const { return m_idx; } + const BoundingBox& bbox() const { return m_bbox; } + const VectorType& centroid() const { return m_centroid; } + + size_t m_idx; + BoundingBox m_bbox; + VectorType m_centroid; + }; + + std::vector input; + input.reserve(faces.size()); + VectorType veps(eps, eps, eps); + for (size_t i = 0; i < faces.size(); ++ i) { + const IndexedFaceType &face = faces[i]; + const VertexType &v1 = vertices[face(0)]; + const VertexType &v2 = vertices[face(1)]; + const VertexType &v3 = vertices[face(2)]; + InputType n; + n.m_idx = i; + n.m_centroid = (1./3.) * (v1 + v2 + v3); + n.m_bbox = BoundingBox(v1, v1); + n.m_bbox.extend(v2); + n.m_bbox.extend(v3); + n.m_bbox.min() -= veps; + n.m_bbox.max() += veps; + input.emplace_back(n); + } + + TreeType out; + out.build(std::move(input)); + return out; +} + +// Find a first intersection of a ray with indexed triangle set. +// Intersection test is calculated with the accuracy of VectorType::Scalar +// even if the triangle mesh and the AABB Tree are built with floats. template inline bool intersect_ray_first_hit( + // Indexed triangle set - 3D vertices. const std::vector &vertices, + // Indexed triangle set - triangular faces, references to vertices. const std::vector &faces, + // AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices. const TreeType &tree, + // Origin of the ray. const VectorType &origin, + // Direction of the ray. const VectorType &dir, + // First intersection of the ray with the indexed triangle set. igl::Hit &hit) { using Scalar = typename VectorType::Scalar; @@ -512,13 +552,24 @@ inline bool intersect_ray_first_hit( ray_intersector, size_t(0), std::numeric_limits::infinity(), hit); } +// Find all intersections of a ray with indexed triangle set. +// Intersection test is calculated with the accuracy of VectorType::Scalar +// even if the triangle mesh and the AABB Tree are built with floats. +// The output hits are sorted by the ray parameter. +// If the ray intersects a shared edge of two triangles, hits for both triangles are returned. template inline bool intersect_ray_all_hits( + // Indexed triangle set - 3D vertices. const std::vector &vertices, + // Indexed triangle set - triangular faces, references to vertices. const std::vector &faces, + // AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices. const TreeType &tree, + // Origin of the ray. const VectorType &origin, + // Direction of the ray. const VectorType &dir, + // All intersections of the ray with the indexed triangle set, sorted by parameter t. std::vector &hits) { auto ray_intersector = detail::RayIntersectorHits { @@ -534,20 +585,31 @@ inline bool intersect_ray_all_hits( return ! hits.empty(); } -// Closest point to triangle test will be performed with the accuracy of VectorType::Scalar. +// Finding a closest triangle, its closest point and squared distance to the closest point +// on a 3D indexed triangle set using a pre-built AABBTreeIndirect::Tree. +// Closest point to triangle test will be performed with the accuracy of VectorType::Scalar +// even if the triangle mesh and the AABB Tree are built with floats. +// Returns squared distance to the closest point or -1 if the input is empty. template -inline typename VectorType::Scalar squared_distance( +inline typename VectorType::Scalar squared_distance_to_indexed_triangle_set( + // Indexed triangle set - 3D vertices. const std::vector &vertices, + // Indexed triangle set - triangular faces, references to vertices. const std::vector &faces, + // AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices. const TreeType &tree, + // Point to which the closest point on the indexed triangle set is searched for. const VectorType &point, + // Index of the closest triangle in faces. size_t &hit_idx_out, + // Position of the closest point on the indexed triangle set. Eigen::PlainObjectBase &hit_point_out) { using Scalar = typename VectorType::Scalar; auto distancer = detail::IndexedTriangleSetDistancer { vertices, faces, tree, point }; - return detail::squared_distance_recursive(distancer, size_t(0), Scalar(0), std::numeric_limits::infinity(), hit_idx_out, hit_point_out); + return tree.empty() ? Scalar(-1) : + detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), std::numeric_limits::infinity(), hit_idx_out, hit_point_out); } } // namespace AABBTreeIndirect diff --git a/tests/libslic3r/test_aabbindirect.cpp b/tests/libslic3r/test_aabbindirect.cpp index 017df9307..c0792a943 100644 --- a/tests/libslic3r/test_aabbindirect.cpp +++ b/tests/libslic3r/test_aabbindirect.cpp @@ -11,7 +11,7 @@ TEST_CASE("Building a tree over a box, ray caster and closest query", "[AABBIndi TriangleMesh tmesh = make_cube(1., 1., 1.); tmesh.repair(); - auto tree = AABBTreeIndirect::build_aabb_tree(tmesh.its.vertices, tmesh.its.indices); + auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(tmesh.its.vertices, tmesh.its.indices); REQUIRE(! tree.empty()); igl::Hit hit; @@ -39,7 +39,7 @@ TEST_CASE("Building a tree over a box, ray caster and closest query", "[AABBIndi size_t hit_idx; Vec3d closest_point; - double squared_distance = AABBTreeIndirect::squared_distance( + double squared_distance = AABBTreeIndirect::squared_distance_to_indexed_triangle_set( tmesh.its.vertices, tmesh.its.indices, tree, Vec3d(0.3, 0.5, -5.), @@ -49,7 +49,7 @@ TEST_CASE("Building a tree over a box, ray caster and closest query", "[AABBIndi REQUIRE(closest_point.y() == Approx(0.5)); REQUIRE(closest_point.z() == Approx(0.)); - squared_distance = AABBTreeIndirect::squared_distance( + squared_distance = AABBTreeIndirect::squared_distance_to_indexed_triangle_set( tmesh.its.vertices, tmesh.its.indices, tree, Vec3d(0.3, 0.5, 5.), From 99514ba42b4b18f36236a1f1546b783b75c6d2ec Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 21 May 2020 17:27:06 +0200 Subject: [PATCH 07/14] WIP: AABBTreeIndirect - optimized ray_box_intersect_invdir() test, sandbox for comparing the AABBTreeIndirect with libigl::AABB --- sandboxes/CMakeLists.txt | 1 + sandboxes/aabb-evaluation/CMakeLists.txt | 2 + sandboxes/aabb-evaluation/aabb-evaluation.cpp | 225 ++++++++++++++++++ src/libslic3r/AABBTreeIndirect.hpp | 65 ++++- 4 files changed, 284 insertions(+), 9 deletions(-) create mode 100644 sandboxes/aabb-evaluation/CMakeLists.txt create mode 100644 sandboxes/aabb-evaluation/aabb-evaluation.cpp diff --git a/sandboxes/CMakeLists.txt b/sandboxes/CMakeLists.txt index a2bd13bb0..23c15f089 100644 --- a/sandboxes/CMakeLists.txt +++ b/sandboxes/CMakeLists.txt @@ -2,3 +2,4 @@ #add_subdirectory(openvdb) add_subdirectory(meshboolean) add_subdirectory(opencsg) +#add_subdirectory(aabb-evaluation) \ No newline at end of file diff --git a/sandboxes/aabb-evaluation/CMakeLists.txt b/sandboxes/aabb-evaluation/CMakeLists.txt new file mode 100644 index 000000000..20011e345 --- /dev/null +++ b/sandboxes/aabb-evaluation/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(aabb-evaluation aabb-evaluation.cpp) +target_link_libraries(aabb-evaluation libslic3r ${Boost_LIBRARIES} ${TBB_LIBRARIES} ${Boost_LIBRARIES} ${CMAKE_DL_LIBS}) diff --git a/sandboxes/aabb-evaluation/aabb-evaluation.cpp b/sandboxes/aabb-evaluation/aabb-evaluation.cpp new file mode 100644 index 000000000..b81fbc96c --- /dev/null +++ b/sandboxes/aabb-evaluation/aabb-evaluation.cpp @@ -0,0 +1,225 @@ +#include +#include +#include + +#include +#include +#include + +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4244) +#pragma warning(disable: 4267) +#endif +#include +#include +#include +#include +#include +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +const std::string USAGE_STR = { + "Usage: aabb-evaluation stlfilename.stl" +}; + +using namespace Slic3r; + +void profile(const TriangleMesh &mesh) +{ + Eigen::MatrixXd V; + Eigen::MatrixXi F; + Eigen::MatrixXd vertex_normals; + sla::to_eigen_mesh(mesh, V, F); + igl::per_vertex_normals(V, F, vertex_normals); + + static constexpr int num_samples = 100; + const int num_vertices = std::min(10000, int(mesh.its.vertices.size())); + const Eigen::MatrixXd dirs = igl::random_dir_stratified(num_samples).cast(); + + Eigen::MatrixXd occlusion_output0; + { + AABBTreeIndirect::Tree3f tree; + { + PROFILE_BLOCK(AABBIndirect_Init); + tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(mesh.its.vertices, mesh.its.indices); + } + { + PROFILE_BLOCK(EigenMesh3D_AABBIndirectF_AmbientOcclusion); + occlusion_output0.resize(num_vertices, 1); + for (int ivertex = 0; ivertex < num_vertices; ++ ivertex) { + const Eigen::Vector3d origin = mesh.its.vertices[ivertex].template cast(); + const Eigen::Vector3d normal = vertex_normals.row(ivertex).template cast(); + int num_hits = 0; + for (int s = 0; s < num_samples; s++) { + Eigen::Vector3d d = dirs.row(s); + if(d.dot(normal) < 0) { + // reverse ray + d *= -1; + } + igl::Hit hit; + if (AABBTreeIndirect::intersect_ray_first_hit(mesh.its.vertices, mesh.its.indices, tree, (origin + 1e-4 * d).eval(), d, hit)) + ++ num_hits; + } + occlusion_output0(ivertex) = (double)num_hits/(double)num_samples; + } + } + + { + PROFILE_BLOCK(EigenMesh3D_AABBIndirectFF_AmbientOcclusion); + occlusion_output0.resize(num_vertices, 1); + for (int ivertex = 0; ivertex < num_vertices; ++ ivertex) { + const Eigen::Vector3d origin = mesh.its.vertices[ivertex].template cast(); + const Eigen::Vector3d normal = vertex_normals.row(ivertex).template cast(); + int num_hits = 0; + for (int s = 0; s < num_samples; s++) { + Eigen::Vector3d d = dirs.row(s); + if(d.dot(normal) < 0) { + // reverse ray + d *= -1; + } + igl::Hit hit; + if (AABBTreeIndirect::intersect_ray_first_hit(mesh.its.vertices, mesh.its.indices, tree, + Eigen::Vector3f((origin + 1e-4 * d).template cast()), + Eigen::Vector3f(d.template cast()), hit)) + ++ num_hits; + } + occlusion_output0(ivertex) = (double)num_hits/(double)num_samples; + } + } + } + + Eigen::MatrixXd occlusion_output1; + { + std::vector vertices; + std::vector triangles; + for (int i = 0; i < V.rows(); ++ i) + vertices.emplace_back(V.row(i).transpose()); + for (int i = 0; i < F.rows(); ++ i) + triangles.emplace_back(F.row(i).transpose()); + AABBTreeIndirect::Tree3d tree; + { + PROFILE_BLOCK(AABBIndirectD_Init); + tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(vertices, triangles); + } + + { + PROFILE_BLOCK(EigenMesh3D_AABBIndirectD_AmbientOcclusion); + occlusion_output1.resize(num_vertices, 1); + for (int ivertex = 0; ivertex < num_vertices; ++ ivertex) { + const Eigen::Vector3d origin = V.row(ivertex).template cast(); + const Eigen::Vector3d normal = vertex_normals.row(ivertex).template cast(); + int num_hits = 0; + for (int s = 0; s < num_samples; s++) { + Eigen::Vector3d d = dirs.row(s); + if(d.dot(normal) < 0) { + // reverse ray + d *= -1; + } + igl::Hit hit; + if (AABBTreeIndirect::intersect_ray_first_hit(vertices, triangles, tree, Eigen::Vector3d(origin + 1e-4 * d), d, hit)) + ++ num_hits; + } + occlusion_output1(ivertex) = (double)num_hits/(double)num_samples; + } + } + } + + // Build the AABB accelaration tree + + Eigen::MatrixXd occlusion_output2; + { + igl::AABB AABB; + { + PROFILE_BLOCK(EigenMesh3D_AABB_Init); + AABB.init(V, F); + } + { + PROFILE_BLOCK(EigenMesh3D_AABB_AmbientOcclusion); + occlusion_output2.resize(num_vertices, 1); + for (int ivertex = 0; ivertex < num_vertices; ++ ivertex) { + const Eigen::Vector3d origin = V.row(ivertex).template cast(); + const Eigen::Vector3d normal = vertex_normals.row(ivertex).template cast(); + int num_hits = 0; + for (int s = 0; s < num_samples; s++) { + Eigen::Vector3d d = dirs.row(s); + if(d.dot(normal) < 0) { + // reverse ray + d *= -1; + } + igl::Hit hit; + if (AABB.intersect_ray(V, F, origin + 1e-4 * d, d, hit)) + ++ num_hits; + } + occlusion_output2(ivertex) = (double)num_hits/(double)num_samples; + } + } + } + + Eigen::MatrixXd occlusion_output3; + { + typedef Eigen::Map> MapMatrixXfUnaligned; + typedef Eigen::Map> MapMatrixXiUnaligned; + igl::AABB AABB; + auto vertices = MapMatrixXfUnaligned(mesh.its.vertices.front().data(), mesh.its.vertices.size(), 3); + auto faces = MapMatrixXiUnaligned(mesh.its.indices.front().data(), mesh.its.indices.size(), 3); + { + PROFILE_BLOCK(EigenMesh3D_AABBf_Init); + AABB.init( + vertices, + faces); + } + + { + PROFILE_BLOCK(EigenMesh3D_AABBf_AmbientOcclusion); + occlusion_output3.resize(num_vertices, 1); + for (int ivertex = 0; ivertex < num_vertices; ++ ivertex) { + const Eigen::Vector3d origin = mesh.its.vertices[ivertex].template cast(); + const Eigen::Vector3d normal = vertex_normals.row(ivertex).template cast(); + int num_hits = 0; + for (int s = 0; s < num_samples; s++) { + Eigen::Vector3d d = dirs.row(s); + if(d.dot(normal) < 0) { + // reverse ray + d *= -1; + } + igl::Hit hit; + if (AABB.intersect_ray(vertices, faces, (origin + 1e-4 * d).eval().template cast(), d.template cast(), hit)) + ++ num_hits; + } + occlusion_output3(ivertex) = (double)num_hits/(double)num_samples; + } + } + } + + PROFILE_UPDATE(); + PROFILE_OUTPUT(nullptr); +} + +int main(const int argc, const char *argv[]) +{ + if(argc < 2) { + std::cout << USAGE_STR << std::endl; + return EXIT_SUCCESS; + } + + TriangleMesh mesh; + if (! mesh.ReadSTLFile(argv[1])) { + std::cerr << "Error loading " << argv[1] << std::endl; + return -1; + } + + mesh.repair(); + if (mesh.facets_count() == 0) { + std::cerr << "Error loading " << argv[1] << " . It is empty." << std::endl; + return -1; + } + + profile(mesh); + + return EXIT_SUCCESS; +} diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index 134b15b71..8d9dfbdef 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -19,8 +19,6 @@ extern "C" } // Definition of the ray intersection hit structure. #include -// Find intersection parameters of a ray with axis aligned bounding box. -#include namespace Slic3r { namespace AABBTreeIndirect { @@ -210,6 +208,11 @@ private: std::vector m_nodes; }; +using Tree2f = Tree<2, float>; +using Tree3f = Tree<3, float>; +using Tree2d = Tree<2, double>; +using Tree3d = Tree<3, double>; + namespace detail { template struct RayIntersector { @@ -224,6 +227,7 @@ namespace detail { const VectorType origin; const VectorType dir; + const VectorType invdir; }; template @@ -231,6 +235,48 @@ namespace detail { std::vector hits; }; + template + inline bool ray_box_intersect_invdir( + const Eigen::MatrixBase &origin, + const Eigen::MatrixBase &inv_dir, + Eigen::AlignedBox box, + const Scalar &t0, + const Scalar &t1, + Scalar &tmin, + Scalar &tmax) { + // http://people.csail.mit.edu/amy/papers/box-jgt.pdf + // "An Efficient and Robust Ray–Box Intersection Algorithm" + if (inv_dir.x() < 0) + std::swap(box.min().x(), box.max().x()); + if (inv_dir.y() < 0) + std::swap(box.min().y(), box.max().y()); + tmin = (box.min().x() - origin.x()) * inv_dir.x(); + Scalar tymax = (box.max().y() - origin.y()) * inv_dir.y(); + if (tmin > tymax) + return false; + tmax = (box.max().x() - origin.x()) * inv_dir.x(); + Scalar tymin = (box.min().y() - origin.y()) * inv_dir.y(); + if (tymin > tmax) + return false; + if (tymin > tmin) + tmin = tymin; + if (tymax < tmax) + tmax = tymax; + if (inv_dir.z() < 0) + std::swap(box.min().z(), box.max().z()); + Scalar tzmin = (box.min().z() - origin.z()) * inv_dir.z(); + if (tzmin > tmax) + return false; + Scalar tzmax = (box.max().z() - origin.z()) * inv_dir.z(); + if (tmin > tzmax) + return false; + if (tzmin > tmin) + tmin = tzmin; + if (tzmax < tmax) + tmax = tzmax; + return tmin < t1 && tmax > t0; + } + template static inline bool intersect_ray_recursive_first_hit( RayIntersectorType &ray_intersector, @@ -243,7 +289,7 @@ namespace detail { { Scalar t_start, t_end; - if (! igl::ray_box_intersect(ray_intersector.origin, ray_intersector.dir, node.bbox.template cast(), Scalar(0), min_t, t_start, t_end)) + if (! ray_box_intersect_invdir(ray_intersector.origin, ray_intersector.invdir, node.bbox.template cast(), Scalar(0), min_t, t_start, t_end)) return false; } @@ -293,7 +339,7 @@ namespace detail { { Scalar t_start, t_end; - if (! igl::ray_box_intersect(ray_intersector.origin, ray_intersector.dir, node.bbox.template cast(), + if (! ray_box_intersect_invdir(ray_intersector.origin, ray_intersector.invdir, node.bbox.template cast(), Scalar(0), std::numeric_limits::infinity(), t_start, t_end)) return; } @@ -483,13 +529,14 @@ inline Tree<3, typename VertexType::Scalar> build_aabb_tree_over_indexed_triangl // Indexed triangle set - 3D vertices. const std::vector &vertices, // Indexed triangle set - triangular faces, references to vertices. - const std::vector &faces) + const std::vector &faces, + //FIXME do we want to apply an epsilon? + const typename VertexType::Scalar eps = 0) { using TreeType = Tree<3, typename VertexType::Scalar>; using CoordType = typename TreeType::CoordType; using VectorType = typename TreeType::VectorType; using BoundingBox = typename TreeType::BoundingBox; - static constexpr CoordType eps = CoordType(1e-4); struct InputType { size_t idx() const { return m_idx; } @@ -503,7 +550,7 @@ inline Tree<3, typename VertexType::Scalar> build_aabb_tree_over_indexed_triangl std::vector input; input.reserve(faces.size()); - VectorType veps(eps, eps, eps); + const VectorType veps(eps, eps, eps); for (size_t i = 0; i < faces.size(); ++ i) { const IndexedFaceType &face = faces[i]; const VertexType &v1 = vertices[face(0)]; @@ -546,7 +593,7 @@ inline bool intersect_ray_first_hit( using Scalar = typename VectorType::Scalar; auto ray_intersector = detail::RayIntersector { vertices, faces, tree, - origin, dir + origin, dir, VectorType(dir.cwiseInverse()) }; return ! tree.empty() && detail::intersect_ray_recursive_first_hit( ray_intersector, size_t(0), std::numeric_limits::infinity(), hit); @@ -574,7 +621,7 @@ inline bool intersect_ray_all_hits( { auto ray_intersector = detail::RayIntersectorHits { vertices, faces, tree, - origin, dir + origin, dir, VectorType(dir.cwiseInverse()) }; if (! tree.empty()) { ray_intersector.hits.reserve(8); From 4c365ad58391e5fb62aa00ba368a96f59d4820a9 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 21 May 2020 17:45:00 +0200 Subject: [PATCH 08/14] Replaced many defines in libslic3r.h with constexpr, removed some macros to support old visual studio compiler. --- src/libslic3r/Arrange.cpp | 4 ++-- src/libslic3r/libslic3r.h | 35 +++++++++++------------------------ 2 files changed, 13 insertions(+), 26 deletions(-) diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 5b048b0ff..3f535db86 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -51,8 +51,8 @@ template struct NfpImpl namespace Slic3r { template, int...EigenArgs> -inline SLIC3R_CONSTEXPR Eigen::Matrix unscaled( - const ClipperLib::IntPoint &v) SLIC3R_NOEXCEPT +inline constexpr Eigen::Matrix unscaled( + const ClipperLib::IntPoint &v) noexcept { return Eigen::Matrix{unscaled(v.X), unscaled(v.Y)}; diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 814ee0807..45ee5acc1 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -24,37 +24,37 @@ #if 1 // Saves around 32% RAM after slicing step, 6.7% after G-code export (tested on PrusaSlicer 2.2.0 final). -typedef int32_t coord_t; +using coord_t = int32_t; #else //FIXME At least FillRectilinear2 requires coord_t to be 32bit. typedef int64_t coord_t; #endif -typedef double coordf_t; +using coordf_t = double; //FIXME This epsilon value is used for many non-related purposes: // For a threshold of a squared Euclidean distance, // for a trheshold in a difference of radians, // for a threshold of a cross product of two non-normalized vectors etc. -#define EPSILON 1e-4 +static constexpr double EPSILON = 1e-4; // Scaling factor for a conversion from coord_t to coordf_t: 10e-6 // This scaling generates a following fixed point representation with for a 32bit integer: // 0..4294mm with 1nm resolution // int32_t fits an interval of (-2147.48mm, +2147.48mm) // with int64_t we don't have to worry anymore about the size of the int. -#define SCALING_FACTOR 0.000001 +static constexpr double SCALING_FACTOR = 0.000001; // RESOLUTION, SCALED_RESOLUTION: Used as an error threshold for a Douglas-Peucker polyline simplification algorithm. -#define RESOLUTION 0.0125 -#define SCALED_RESOLUTION (RESOLUTION / SCALING_FACTOR) -#define PI 3.141592653589793238 +static constexpr double RESOLUTION = 0.0125; +#define SCALED_RESOLUTION (RESOLUTION / SCALING_FACTOR) +static constexpr double PI = 3.141592653589793238; // When extruding a closed loop, the loop is interrupted and shortened a bit to reduce the seam. -#define LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER 0.15 +static constexpr double LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER = 0.15; // Maximum perimeter length for the loop to apply the small perimeter speed. -#define SMALL_PERIMETER_LENGTH (6.5 / SCALING_FACTOR) * 2 * PI -#define INSET_OVERLAP_TOLERANCE 0.4 +#define SMALL_PERIMETER_LENGTH ((6.5 / SCALING_FACTOR) * 2 * PI); +static constexpr double INSET_OVERLAP_TOLERANCE = 0.4; // 3mm ring around the top / bottom / bridging areas. //FIXME This is quite a lot. -#define EXTERNAL_INFILL_MARGIN 3. +static constexpr double EXTERNAL_INFILL_MARGIN = 3.; //FIXME Better to use an inline function with an explicit return type. //inline coord_t scale_(coordf_t v) { return coord_t(floor(v / SCALING_FACTOR + 0.5f)); } #define scale_(val) ((val) / SCALING_FACTOR) @@ -63,14 +63,6 @@ typedef double coordf_t; #define SLIC3R_DEBUG_OUT_PATH_PREFIX "out/" -#if defined(_MSC_VER) && _MSC_VER < 1900 -# define SLIC3R_CONSTEXPR -# define SLIC3R_NOEXCEPT -#else -#define SLIC3R_CONSTEXPR constexpr -#define SLIC3R_NOEXCEPT noexcept -#endif - inline std::string debug_out_path(const char *name, ...) { char buffer[2048]; @@ -92,11 +84,6 @@ inline std::string debug_out_path(const char *name, ...) #define UNUSED(x) (void)(x) #endif /* UNUSED */ -// Detect whether the compiler supports C++11 noexcept exception specifications. -#if defined(_MSC_VER) && _MSC_VER < 1900 - #define noexcept throw() -#endif - // Write slices as SVG images into out directory during the 2D processing of the slices. // #define SLIC3R_DEBUG_SLICE_PROCESSING From 7b6dff3f0373281bd29cf28bc181eaf34e9c4a23 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 21 May 2020 17:47:17 +0200 Subject: [PATCH 09/14] Little simplification of Point to boost::polygon bindings --- src/libslic3r/Point.hpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index e511a6316..b818cd8be 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -369,7 +369,7 @@ namespace boost { namespace polygon { typedef coord_t coordinate_type; static inline coordinate_type get(const Slic3r::Point& point, orientation_2d orient) { - return (orient == HORIZONTAL) ? (coordinate_type)point(0) : (coordinate_type)point(1); + return (coordinate_type)point((orient == HORIZONTAL) ? 0 : 1); } }; @@ -377,16 +377,10 @@ namespace boost { namespace polygon { struct point_mutable_traits { typedef coord_t coordinate_type; static inline void set(Slic3r::Point& point, orientation_2d orient, coord_t value) { - if (orient == HORIZONTAL) - point(0) = value; - else - point(1) = value; + point((orient == HORIZONTAL) ? 0 : 1) = value; } static inline Slic3r::Point construct(coord_t x_value, coord_t y_value) { - Slic3r::Point retval; - retval(0) = x_value; - retval(1) = y_value; - return retval; + return Slic3r::Point(x_value, y_value); } }; } } From c64b7b2e2169592c6ca34e09457f507d349ac485 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 21 May 2020 17:58:07 +0200 Subject: [PATCH 10/14] Fix of the previous commit. --- src/libslic3r/libslic3r.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 45ee5acc1..db375ec14 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -50,7 +50,7 @@ static constexpr double PI = 3.141592653589793238; // When extruding a closed loop, the loop is interrupted and shortened a bit to reduce the seam. static constexpr double LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER = 0.15; // Maximum perimeter length for the loop to apply the small perimeter speed. -#define SMALL_PERIMETER_LENGTH ((6.5 / SCALING_FACTOR) * 2 * PI); +#define SMALL_PERIMETER_LENGTH ((6.5 / SCALING_FACTOR) * 2 * PI) static constexpr double INSET_OVERLAP_TOLERANCE = 0.4; // 3mm ring around the top / bottom / bridging areas. //FIXME This is quite a lot. From dc46589a8e48b6bb6c953cb8e70fb6ddde88f369 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 22 May 2020 09:04:07 +0200 Subject: [PATCH 11/14] AABB - triangle intersection wrapped to mimize copying into Vector3D --- src/libslic3r/AABBTreeIndirect.hpp | 57 +++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index 8d9dfbdef..c5a6c8666 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -8,6 +8,7 @@ #include #include +#include #include #include "Utils.hpp" // for next_highest_power_of_2() @@ -277,6 +278,46 @@ namespace detail { return tmin < t1 && tmax > t0; } + template + std::enable_if_t::value && std::is_same::value, bool> + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + return intersect_triangle1(const_cast(origin.data()), const_cast(dir.data()), + const_cast(v0.data()), const_cast(v1.data()), const_cast(v2.data()), + &t, &u, &v); + } + + template + std::enable_if_t::value && !std::is_same::value, bool> + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + using Vector = Eigen::Matrix; + Vector w0 = v0.template cast(); + Vector w1 = v1.template cast(); + Vector w2 = v2.template cast(); + return intersect_triangle1(const_cast(origin.data()), const_cast(dir.data()), + w0.data(), w1.data(), w2.data(), &t, &u, &v); + } + + template + std::enable_if_t::value && std::is_same::value, bool> + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + using Vector = Eigen::Matrix; + Vector o = origin.template cast(); + Vector d = dir.template cast(); + return intersect_triangle1(o.data(), d.data(), const_cast(v0.data()), const_cast(v1.data()), const_cast(v2.data()), &t, &u, &v); + } + + template + std::enable_if_t::value && ! std::is_same::value, bool> + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + using Vector = Eigen::Matrix; + Vector o = origin.template cast(); + Vector d = dir.template cast(); + Vector w0 = v0.template cast(); + Vector w1 = v1.template cast(); + Vector w2 = v2.template cast(); + return intersect_triangle1(o.data(), d.data(), w0.data(), w1.data(), w2.data(), &t, &u, &v); + } + template static inline bool intersect_ray_recursive_first_hit( RayIntersectorType &ray_intersector, @@ -294,16 +335,14 @@ namespace detail { } if (node.is_leaf()) { - using Vector = Eigen::Matrix; - Vector origin_d = ray_intersector.origin.template cast(); - Vector dir_d = ray_intersector.dir .template cast(); - auto face = ray_intersector.faces[node.idx]; - Vector v0 = ray_intersector.vertices[face(0)].template cast(); - Vector v1 = ray_intersector.vertices[face(1)].template cast(); - Vector v2 = ray_intersector.vertices[face(2)].template cast(); // shoot ray, record hit + auto face = ray_intersector.faces[node.idx]; double t, u, v; - if (intersect_triangle1(origin_d.data(), dir_d.data(), v0.data(), v1.data(), v2.data(), &t, &u, &v) && t > 0.) { + if (intersect_triangle( + ray_intersector.origin, ray_intersector.dir, + ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)], + t, u, v) + && t > 0.) { hit = igl::Hit { int(node.idx), -1, float(u), float(v), float(t) }; return true; } @@ -534,7 +573,7 @@ inline Tree<3, typename VertexType::Scalar> build_aabb_tree_over_indexed_triangl const typename VertexType::Scalar eps = 0) { using TreeType = Tree<3, typename VertexType::Scalar>; - using CoordType = typename TreeType::CoordType; +// using CoordType = typename TreeType::CoordType; using VectorType = typename TreeType::VectorType; using BoundingBox = typename TreeType::BoundingBox; From 925bf1af70142a8bb8b13e3ad9371a9e47bd1bdf Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 22 May 2020 09:04:31 +0200 Subject: [PATCH 12/14] Shiny profiler: Increased resolution of text output to 2 decimals --- src/Shiny/ShinyOutput.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Shiny/ShinyOutput.c b/src/Shiny/ShinyOutput.c index ad02ea003..c2c624d58 100644 --- a/src/Shiny/ShinyOutput.c +++ b/src/Shiny/ShinyOutput.c @@ -40,8 +40,8 @@ THE SOFTWARE. /*---------------------------------------------------------------------------*/ #define OUTPUT_WIDTH_CALL 6 -#define OUTPUT_WIDTH_TIME 6 -#define OUTPUT_WIDTH_PERC 4 +#define OUTPUT_WIDTH_TIME (6+3) +#define OUTPUT_WIDTH_PERC (4+3) #define OUTPUT_WIDTH_SUM 120 #define OUTPUT_WIDTH_DATA (1+OUTPUT_WIDTH_CALL + 1 + 2*(OUTPUT_WIDTH_TIME+4+OUTPUT_WIDTH_PERC+1) + 1) @@ -70,7 +70,7 @@ SHINY_INLINE char* printData(char *output, const ShinyData *a_data, float a_tope const ShinyTimeUnit *totalUnit = ShinyGetTimeUnit(totalTicksAvg); snprintf(output, OUTPUT_WIDTH_DATA + TRAILING, - " %*.1f %*.0f %-2s %*.0f%% %*.0f %-2s %*.0f%%", + " %*.1f %*.2f %-2s %*.2f%% %*.2f %-2s %*.2f%%", OUTPUT_WIDTH_CALL, a_data->entryCount.avg, OUTPUT_WIDTH_TIME, a_data->selfTicks.avg * selfUnit->invTickFreq, selfUnit->suffix, OUTPUT_WIDTH_PERC, a_data->selfTicks.avg * a_topercent, From ac1f24e5c9eb5ee7d50118cc763d07698626b64f Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 22 May 2020 11:35:49 +0200 Subject: [PATCH 13/14] AABB: Some further polishing and a reference to an SSE implementation of the 3D Box vs. ray intersection implementation. --- sandboxes/aabb-evaluation/aabb-evaluation.cpp | 1 - src/libslic3r/AABBTreeIndirect.hpp | 114 +++++++++--------- 2 files changed, 54 insertions(+), 61 deletions(-) diff --git a/sandboxes/aabb-evaluation/aabb-evaluation.cpp b/sandboxes/aabb-evaluation/aabb-evaluation.cpp index b81fbc96c..9ec7451e5 100644 --- a/sandboxes/aabb-evaluation/aabb-evaluation.cpp +++ b/sandboxes/aabb-evaluation/aabb-evaluation.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #ifdef _MSC_VER diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index c5a6c8666..ec9b14a7a 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -236,26 +236,29 @@ namespace detail { std::vector hits; }; + //FIXME implement SSE for float AABB trees with float ray queries. + // SSE/SSE2 is supported by any Intel/AMD x64 processor. + // SSE support requires 16 byte alignment of the AABB nodes, representing the bounding boxes with 4+4 floats, + // storing the node index as the 4th element of the bounding box min value etc. + // https://www.flipcode.com/archives/SSE_RayBox_Intersection_Test.shtml template inline bool ray_box_intersect_invdir( const Eigen::MatrixBase &origin, const Eigen::MatrixBase &inv_dir, Eigen::AlignedBox box, const Scalar &t0, - const Scalar &t1, - Scalar &tmin, - Scalar &tmax) { + const Scalar &t1) { // http://people.csail.mit.edu/amy/papers/box-jgt.pdf // "An Efficient and Robust Ray–Box Intersection Algorithm" if (inv_dir.x() < 0) std::swap(box.min().x(), box.max().x()); if (inv_dir.y() < 0) std::swap(box.min().y(), box.max().y()); - tmin = (box.min().x() - origin.x()) * inv_dir.x(); + Scalar tmin = (box.min().x() - origin.x()) * inv_dir.x(); Scalar tymax = (box.max().y() - origin.y()) * inv_dir.y(); if (tmin > tymax) return false; - tmax = (box.max().x() - origin.x()) * inv_dir.x(); + Scalar tmax = (box.max().x() - origin.x()) * inv_dir.x(); Scalar tymin = (box.min().y() - origin.y()) * inv_dir.y(); if (tymin > tmax) return false; @@ -328,11 +331,8 @@ namespace detail { const auto &node = ray_intersector.tree.node(node_idx); assert(node.is_valid()); - { - Scalar t_start, t_end; - if (! ray_box_intersect_invdir(ray_intersector.origin, ray_intersector.invdir, node.bbox.template cast(), Scalar(0), min_t, t_start, t_end)) - return false; - } + if (! ray_box_intersect_invdir(ray_intersector.origin, ray_intersector.invdir, node.bbox.template cast(), Scalar(0), min_t)) + return false; if (node.is_leaf()) { // shoot ray, record hit @@ -345,27 +345,27 @@ namespace detail { && t > 0.) { hit = igl::Hit { int(node.idx), -1, float(u), float(v), float(t) }; return true; - } - return false; - } - - // Left / right child node index. - size_t left = node_idx * 2 + 1; - size_t right = left + 1; - igl::Hit left_hit; - igl::Hit right_hit; - bool left_ret = intersect_ray_recursive_first_hit(ray_intersector, left, min_t, left_hit); - if (left_ret && left_hit.t < min_t) { - min_t = left_hit.t; - hit = left_hit; - } else - left_ret = false; - bool right_ret = intersect_ray_recursive_first_hit(ray_intersector, right, min_t, right_hit); - if (right_ret && right_hit.t < min_t) - hit = right_hit; - else - right_ret = false; - return left_ret || right_ret; + } else + return false; + } else { + // Left / right child node index. + size_t left = node_idx * 2 + 1; + size_t right = left + 1; + igl::Hit left_hit; + igl::Hit right_hit; + bool left_ret = intersect_ray_recursive_first_hit(ray_intersector, left, min_t, left_hit); + if (left_ret && left_hit.t < min_t) { + min_t = left_hit.t; + hit = left_hit; + } else + left_ret = false; + bool right_ret = intersect_ray_recursive_first_hit(ray_intersector, right, min_t, right_hit); + if (right_ret && right_hit.t < min_t) + hit = right_hit; + else + right_ret = false; + return left_ret || right_ret; + } } template @@ -376,33 +376,27 @@ namespace detail { const auto &node = ray_intersector.tree.node(node_idx); assert(node.is_valid()); - { - Scalar t_start, t_end; - if (! ray_box_intersect_invdir(ray_intersector.origin, ray_intersector.invdir, node.bbox.template cast(), - Scalar(0), std::numeric_limits::infinity(), t_start, t_end)) - return; - } + if (! ray_box_intersect_invdir(ray_intersector.origin, ray_intersector.invdir, node.bbox.template cast(), + Scalar(0), std::numeric_limits::infinity())) + return; if (node.is_leaf()) { - using Vector = Eigen::Matrix; - Vector origin_d = ray_intersector.origin.template cast(); - Vector dir_d = ray_intersector.dir .template cast(); - auto face = ray_intersector.faces[node.idx]; - Vector v0 = ray_intersector.vertices[face(0)].template cast(); - Vector v1 = ray_intersector.vertices[face(1)].template cast(); - Vector v2 = ray_intersector.vertices[face(2)].template cast(); - // shoot ray, record hit + auto face = ray_intersector.faces[node.idx]; double t, u, v; - if (intersect_triangle1(origin_d.data(), dir_d.data(), v0.data(), v1.data(), v2.data(), &t, &u, &v) && t > 0.) + if (intersect_triangle( + ray_intersector.origin, ray_intersector.dir, + ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)], + t, u, v) + && t > 0.) { ray_intersector.hits.emplace_back(igl::Hit{ int(node.idx), -1, float(u), float(v), float(t) }); - return; - } - - // Left / right child node index. - size_t left = node_idx * 2 + 1; - size_t right = left + 1; - intersect_ray_recursive_all_hits(ray_intersector, left); - intersect_ray_recursive_all_hits(ray_intersector, right); + } + } else { + // Left / right child node index. + size_t left = node_idx * 2 + 1; + size_t right = left + 1; + intersect_ray_recursive_all_hits(ray_intersector, left); + intersect_ray_recursive_all_hits(ray_intersector, right); + } } // Nothing to do with COVID-19 social distancing. @@ -431,17 +425,17 @@ namespace detail { Vector ap = p - a; Scalar d1 = ab.dot(ap); Scalar d2 = ac.dot(ap); - if (d1 <= Scalar(0) && d2 <= Scalar(0)) + if (d1 <= 0 && d2 <= 0) return a; // Check if P in vertex region outside B Vector bp = p - b; Scalar d3 = ab.dot(bp); Scalar d4 = ac.dot(bp); - if (d3 >= Scalar(0) && d4 <= d3) + if (d3 >= 0 && d4 <= d3) return b; // Check if P in edge region of AB, if so return projection of P onto AB Scalar vc = d1*d4 - d3*d2; - if (a != b && vc <= Scalar(0) && d1 >= Scalar(0) && d3 <= Scalar(0)) { + if (a != b && vc <= 0 && d1 >= 0 && d3 <= 0) { Scalar v = d1 / (d1 - d3); return a + v * ab; } @@ -449,17 +443,17 @@ namespace detail { Vector cp = p - c; Scalar d5 = ab.dot(cp); Scalar d6 = ac.dot(cp); - if (d6 >= Scalar(0) && d5 <= d6) + if (d6 >= 0 && d5 <= d6) return c; // Check if P in edge region of AC, if so return projection of P onto AC Scalar vb = d5*d2 - d1*d6; - if (vb <= Scalar(0) && d2 >= Scalar(0) && d6 <= Scalar(0)) { + if (vb <= 0 && d2 >= 0 && d6 <= 0) { Scalar w = d2 / (d2 - d6); return a + w * ac; } // Check if P in edge region of BC, if so return projection of P onto BC Scalar va = d3*d6 - d5*d4; - if (va <= Scalar(0) && (d4 - d3) >= Scalar(0) && (d5 - d6) >= Scalar(0)) { + if (va <= 0 && (d4 - d3) >= 0 && (d5 - d6) >= 0) { Scalar w = (d4 - d3) / ((d4 - d3) + (d5 - d6)); return b + w * (c - b); } From c09d7020459bda027b802fd0fc8e6185163343a7 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 22 May 2020 11:36:10 +0200 Subject: [PATCH 14/14] Show Eigen vectorization support in system info dialog. --- src/slic3r/GUI/SysInfoDialog.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index 9e7d1bad6..74b94e1eb 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -6,6 +6,8 @@ #include +#include + #include #include #include "GUI_App.hpp" @@ -145,11 +147,11 @@ SysInfoDialog::SysInfoDialog() "" "" "", bgr_clr_str, text_clr_str, text_clr_str, - get_mem_info(true) + "
" + wxGetApp().get_gl_info(true, true)); + get_mem_info(true) + "
" + wxGetApp().get_gl_info(true, true) + "
Eigen vectorization supported: " + Eigen::SimdInstructionSetsInUse()); m_opengl_info_html->SetPage(text); main_sizer->Add(m_opengl_info_html, 1, wxEXPAND | wxBOTTOM, 15); } - + wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK); m_btn_copy_to_clipboard = new wxButton(this, wxID_ANY, _(L("Copy to Clipboard")), wxDefaultPosition, wxDefaultSize);