From 1ccd1195186f7aca6fd23630ca7ec77f1afefc0a Mon Sep 17 00:00:00 2001 From: Braden Ganetsky Date: Mon, 28 Jul 2025 17:00:08 -0500 Subject: [PATCH 1/4] Move the set_uniform() calls to all be inside render() --- src/pstack/gui/viewport.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/pstack/gui/viewport.cpp b/src/pstack/gui/viewport.cpp index f9e471d..3a02547 100644 --- a/src/pstack/gui/viewport.cpp +++ b/src/pstack/gui/viewport.cpp @@ -107,8 +107,6 @@ void viewport::set_mesh(const calc::mesh& mesh, const geo::point3& centro const auto size = bounding.max - bounding.min; const auto zoom_factor = 1 / std::max({ size.x, size.y, size.z }); _transform.scale_mesh(zoom_factor); - _shader.set_uniform("transform_vertices", _transform.for_vertices()); - _shader.set_uniform("transform_normals", _transform.for_normals()); render(); } @@ -121,8 +119,6 @@ void viewport::remove_mesh() { _transform.translation({ 0, 0, 0 }); _transform.scale_mesh(1); - _shader.set_uniform("transform_vertices", _transform.for_vertices()); - _shader.set_uniform("transform_normals", _transform.for_normals()); render(); } @@ -140,7 +136,10 @@ void viewport::render(wxDC& dc) { SetCurrent(*_opengl_context); graphics::clear(40 / 255.0, 50 / 255.0, 120 / 255.0, 1); + _shader.use_program(); + _shader.set_uniform("transform_vertices", _transform.for_vertices()); + _shader.set_uniform("transform_normals", _transform.for_normals()); _vao.bind_arrays(); graphics::draw_triangles(_vao[0].size()); @@ -164,8 +163,6 @@ void viewport::on_size(wxSizeEvent& event) { static constexpr float scale_baseline = 866; _transform.scale_screen(scale_baseline / _viewport_size.x, scale_baseline / _viewport_size.y); - _shader.set_uniform("transform_vertices", _transform.for_vertices()); - _shader.set_uniform("transform_normals", _transform.for_normals()); if (first_appearance) { render(); @@ -200,8 +197,6 @@ void viewport::on_scroll(wxMouseEvent& evt) { const double zoom_amount = ((double)evt.GetWheelRotation() / (double)evt.GetWheelDelta()) / 4; const float zoom_factor = (float)std::pow(2.0, _scroll_direction * zoom_amount); _transform.zoom_by(zoom_factor); - _shader.set_uniform("transform_vertices", _transform.for_vertices()); - _shader.set_uniform("transform_normals", _transform.for_normals()); render(); } @@ -209,8 +204,6 @@ void viewport::on_move_by(wxPoint position) { const auto [dx, dy] = _cached_position - position; _cached_position = position; _transform.rotate_by((float)dy / 256, (float)dx / 256); - _shader.set_uniform("transform_vertices", _transform.for_vertices()); - _shader.set_uniform("transform_normals", _transform.for_normals()); render(); } From 775ff4b7ce377b0ecf44c6d96ba2cef64baf5602 Mon Sep 17 00:00:00 2001 From: Braden Ganetsky Date: Mon, 28 Jul 2025 17:00:08 -0500 Subject: [PATCH 2/4] Rename the mesh shader and mesh vao, to make room for other shaders and vaos in the future --- src/pstack/gui/viewport.cpp | 35 +++++++++++++++++------------------ src/pstack/gui/viewport.hpp | 4 ++-- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/pstack/gui/viewport.cpp b/src/pstack/gui/viewport.cpp index 3a02547..02ec60c 100644 --- a/src/pstack/gui/viewport.cpp +++ b/src/pstack/gui/viewport.cpp @@ -28,7 +28,7 @@ viewport::viewport(main_window* parent, const wxGLAttributes& canvasAttrs) Bind(wxEVT_MOUSEWHEEL, &viewport::on_scroll, this); } -constexpr auto vertex_shader_source = R"( +constexpr auto mesh_vertex_shader_source = R"( #version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; @@ -41,7 +41,7 @@ constexpr auto vertex_shader_source = R"( } )"; -constexpr auto fragment_shader_source = R"( +constexpr auto mesh_fragment_shader_source = R"( #version 330 core in vec4 frag_normal; out vec4 frag_colour; @@ -69,22 +69,22 @@ bool viewport::initialize() { return false; } - if (const auto err = _shader.initialize(vertex_shader_source, fragment_shader_source); + if (const auto err = _mesh_shader.initialize(mesh_vertex_shader_source, mesh_fragment_shader_source); not err.has_value()) { - wxMessageBox(wxString::Format("Error in creating OpenGL shader.\n%s", err.error()), - "OpenGL shader error", wxOK | wxICON_ERROR, this); + wxMessageBox(wxString::Format("Error in creating OpenGL shader for the mesh.\n%s", err.error()), + "OpenGL mesh shader error", wxOK | wxICON_ERROR, this); return false; } - _vao.initialize(); + _mesh_vao.initialize(); remove_mesh(); return true; } void viewport::set_mesh(const calc::mesh& mesh, const geo::point3& centroid) { - _vao.clear(); + _mesh_vao.clear(); using vector3 = geo::vector3; @@ -99,8 +99,8 @@ void viewport::set_mesh(const calc::mesh& mesh, const geo::point3& centro normals.push_back(t.normal); } - _vao.add_vertex_buffer(0, std::move(vertices)); - _vao.add_vertex_buffer(1, std::move(normals)); + _mesh_vao.add_vertex_buffer(0, std::move(vertices)); + _mesh_vao.add_vertex_buffer(1, std::move(normals)); const auto bounding = mesh.bounding(); _transform.translation(geo::origin3 - centroid); @@ -112,10 +112,9 @@ void viewport::set_mesh(const calc::mesh& mesh, const geo::point3& centro } void viewport::remove_mesh() { - _vao.clear(); - - _vao.add_vertex_buffer(0, {}); - _vao.add_vertex_buffer(1, {}); + _mesh_vao.clear(); + _mesh_vao.add_vertex_buffer(0, {}); + _mesh_vao.add_vertex_buffer(1, {}); _transform.translation({ 0, 0, 0 }); _transform.scale_mesh(1); @@ -137,11 +136,11 @@ void viewport::render(wxDC& dc) { graphics::clear(40 / 255.0, 50 / 255.0, 120 / 255.0, 1); - _shader.use_program(); - _shader.set_uniform("transform_vertices", _transform.for_vertices()); - _shader.set_uniform("transform_normals", _transform.for_normals()); - _vao.bind_arrays(); - graphics::draw_triangles(_vao[0].size()); + _mesh_shader.use_program(); + _mesh_shader.set_uniform("transform_vertices", _transform.for_vertices()); + _mesh_shader.set_uniform("transform_normals", _transform.for_normals()); + _mesh_vao.bind_arrays(); + graphics::draw_triangles(_mesh_vao[0].size()); SwapBuffers(); } diff --git a/src/pstack/gui/viewport.hpp b/src/pstack/gui/viewport.hpp index 07a4aaf..1301023 100644 --- a/src/pstack/gui/viewport.hpp +++ b/src/pstack/gui/viewport.hpp @@ -51,8 +51,8 @@ class viewport : public wxGLCanvas { std::unique_ptr _opengl_context = nullptr; bool _opengl_initialized = false; - graphics::shader _shader{}; - graphics::vertex_array_object _vao{}; + graphics::shader _mesh_shader{}; + graphics::vertex_array_object _mesh_vao{}; transformation _transform{}; wxSize _viewport_size{}; From 9f26dc60a7d8a05e38da62f2b0c5b07229d78a14 Mon Sep 17 00:00:00 2001 From: Braden Ganetsky Date: Mon, 28 Jul 2025 17:00:08 -0500 Subject: [PATCH 3/4] Support displaying the bounding box in the viewport --- src/pstack/graphics/global.cpp | 4 ++ src/pstack/graphics/global.hpp | 2 + src/pstack/gui/viewport.cpp | 100 +++++++++++++++++++++++++++------ src/pstack/gui/viewport.hpp | 11 ++++ 4 files changed, 100 insertions(+), 17 deletions(-) diff --git a/src/pstack/graphics/global.cpp b/src/pstack/graphics/global.cpp index d6a9525..0bc76f4 100644 --- a/src/pstack/graphics/global.cpp +++ b/src/pstack/graphics/global.cpp @@ -31,4 +31,8 @@ void draw_triangles(GLsizei count) { glDrawArrays(GL_TRIANGLES, 0, count); } +void draw_lines(GLsizei count) { + glDrawArrays(GL_LINES, 0, count); +} + } // namespace pstack::graphics diff --git a/src/pstack/graphics/global.hpp b/src/pstack/graphics/global.hpp index 8cb4077..40f7d50 100644 --- a/src/pstack/graphics/global.hpp +++ b/src/pstack/graphics/global.hpp @@ -14,6 +14,8 @@ void clear(float r, float g, float b, float a); void draw_triangles(int count); +void draw_lines(int count); + } // namespace pstack::graphics #endif // PSTACK_GRAPHICS_GLOBAL_HPP diff --git a/src/pstack/gui/viewport.cpp b/src/pstack/gui/viewport.cpp index 02ec60c..1dabe93 100644 --- a/src/pstack/gui/viewport.cpp +++ b/src/pstack/gui/viewport.cpp @@ -54,6 +54,23 @@ constexpr auto mesh_fragment_shader_source = R"( } )"; +constexpr auto bounding_box_vertex_shader_source = R"( + #version 330 core + layout (location = 0) in vec3 aPos; + uniform mat4 transform_vertices; + void main() { + gl_Position = transform_vertices * vec4(aPos, 1.0); + } +)"; + +constexpr auto bounding_box_fragment_shader_source = R"( + #version 330 core + out vec4 frag_colour; + void main() { + frag_colour = vec4(224.0 / 255.0, 110.0 / 255.0, 0.0, 1.0); + } +)"; + bool viewport::initialize() { if (!_opengl_context) { return false; @@ -77,32 +94,27 @@ bool viewport::initialize() { return false; } + if (const auto err = _bounding_box_shader.initialize(bounding_box_vertex_shader_source, bounding_box_fragment_shader_source); + not err.has_value()) + { + wxMessageBox(wxString::Format("Error in creating OpenGL shader for the bounding box.\n%s", err.error()), + "OpenGL line shader error", wxOK | wxICON_ERROR, this); + return false; + } + _mesh_vao.initialize(); + _bounding_box_vao.initialize(); remove_mesh(); return true; } void viewport::set_mesh(const calc::mesh& mesh, const geo::point3& centroid) { - _mesh_vao.clear(); - - using vector3 = geo::vector3; - - std::vector vertices; - std::vector normals; - for (const auto& t : mesh.triangles()) { - vertices.push_back(t.v1.as_vector()); - vertices.push_back(t.v2.as_vector()); - vertices.push_back(t.v3.as_vector()); - normals.push_back(t.normal); - normals.push_back(t.normal); - normals.push_back(t.normal); - } + const auto bounding = mesh.bounding(); - _mesh_vao.add_vertex_buffer(0, std::move(vertices)); - _mesh_vao.add_vertex_buffer(1, std::move(normals)); + set_mesh_vao(mesh); + set_bounding_box_vao(bounding.min, bounding.max); - const auto bounding = mesh.bounding(); _transform.translation(geo::origin3 - centroid); const auto size = bounding.max - bounding.min; const auto zoom_factor = 1 / std::max({ size.x, size.y, size.z }); @@ -116,12 +128,59 @@ void viewport::remove_mesh() { _mesh_vao.add_vertex_buffer(0, {}); _mesh_vao.add_vertex_buffer(1, {}); + _bounding_box_vao.clear(); + _bounding_box_vao.add_vertex_buffer(0, {}); + _transform.translation({ 0, 0, 0 }); _transform.scale_mesh(1); render(); } +void viewport::set_mesh_vao(const calc::mesh& mesh) { + _mesh_vao.clear(); + std::vector> vertices; + std::vector> normals; + for (const auto& t : mesh.triangles()) { + vertices.push_back(t.v1.as_vector()); + vertices.push_back(t.v2.as_vector()); + vertices.push_back(t.v3.as_vector()); + normals.push_back(t.normal); + normals.push_back(t.normal); + normals.push_back(t.normal); + } + _mesh_vao.add_vertex_buffer(0, std::move(vertices)); + _mesh_vao.add_vertex_buffer(1, std::move(normals)); +} + +void viewport::set_bounding_box_vao(const geo::point3 min, const geo::point3 max) { + _bounding_box_vao.clear(); + std::vector> vertices{ + { min.x, min.y, min.z }, + { min.x, min.y, max.z }, + { min.x, max.y, min.z }, + { min.x, max.y, max.z }, + { max.x, min.y, min.z }, + { max.x, min.y, max.z }, + { max.x, max.y, min.z }, + { max.x, max.y, max.z }, + }; + _bounding_box_vao.add_vertex_buffer(0, std::vector{ + vertices[0], vertices[1], + vertices[2], vertices[3], + vertices[4], vertices[5], + vertices[6], vertices[7], + vertices[0], vertices[2], + vertices[1], vertices[3], + vertices[4], vertices[6], + vertices[5], vertices[7], + vertices[0], vertices[4], + vertices[1], vertices[5], + vertices[2], vertices[6], + vertices[3], vertices[7], + }); +} + void viewport::render() { wxClientDC dc(this); render(dc); @@ -142,6 +201,13 @@ void viewport::render(wxDC& dc) { _mesh_vao.bind_arrays(); graphics::draw_triangles(_mesh_vao[0].size()); + if (_show_bounding_box) { + _bounding_box_shader.use_program(); + _bounding_box_shader.set_uniform("transform_vertices", _transform.for_vertices()); + _bounding_box_vao.bind_arrays(); + graphics::draw_lines(_bounding_box_vao[0].size()); + } + SwapBuffers(); } diff --git a/src/pstack/gui/viewport.hpp b/src/pstack/gui/viewport.hpp index 1301023..c2e8c1d 100644 --- a/src/pstack/gui/viewport.hpp +++ b/src/pstack/gui/viewport.hpp @@ -27,6 +27,12 @@ class viewport : public wxGLCanvas { public: void set_mesh(const calc::mesh& mesh, const geo::point3& centroid); void remove_mesh(); + +private: + void set_mesh_vao(const calc::mesh& mesh); + void set_bounding_box_vao(geo::point3 min, geo::point3 max); + +public: void render(); void scroll_direction(bool invert_scroll) { @@ -53,6 +59,11 @@ class viewport : public wxGLCanvas { graphics::shader _mesh_shader{}; graphics::vertex_array_object _mesh_vao{}; + + graphics::shader _bounding_box_shader{}; + graphics::vertex_array_object _bounding_box_vao{}; + bool _show_bounding_box = false; + transformation _transform{}; wxSize _viewport_size{}; From a34d57f11b0447a077b8bc139610316316920d7d Mon Sep 17 00:00:00 2001 From: Braden Ganetsky Date: Mon, 28 Jul 2025 17:00:09 -0500 Subject: [PATCH 4/4] Add a menu preference to show the bounding box of the currently displayed mesh, and remove the part button for previewing the bounding box --- src/pstack/gui/controls.cpp | 4 ---- src/pstack/gui/controls.hpp | 1 - src/pstack/gui/main_window.cpp | 13 +++++++------ src/pstack/gui/preferences.hpp | 1 + src/pstack/gui/viewport.hpp | 4 ++++ 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/pstack/gui/controls.cpp b/src/pstack/gui/controls.cpp index 36415fb..866d75e 100644 --- a/src/pstack/gui/controls.cpp +++ b/src/pstack/gui/controls.cpp @@ -76,8 +76,6 @@ void controls::initialize(main_window* parent) { rotation_dropdown->Disable(); preview_voxelization_button = new wxButton(panel, wxID_ANY, "Preview voxelization"); preview_voxelization_button->Disable(); - preview_bounding_box_button = new wxButton(panel, wxID_ANY, "Preview bounding box"); - preview_bounding_box_button->Disable(); const wxString quantity_tooltip = "How many copies of the selected parts should be included in the stacking"; const wxString min_hole_tooltip = @@ -97,7 +95,6 @@ void controls::initialize(main_window* parent) { "Cubic = The parts will be rotated by some multiple of 90 degrees from their starting orientations.\n\n" "Arbitrary = The parts will be oriented in one of 32 random possible rotations. The rotations are constant for the duration of the application, and will be re-randomized next time the application is launched."; const wxString preview_voxelization_tooltip = "*NOT YET IMPLEMENTED*\nShows a preview of the voxelization. Used to check if there are any open holes into the internal volume of the part."; - const wxString preview_bounding_box_tooltip = "*NOT YET IMPLEMENTED*\nShows a preview of the bounding box. Used to check the part's orientation."; quantity_text->SetToolTip(quantity_tooltip); min_hole_text->SetToolTip(min_hole_tooltip); @@ -108,7 +105,6 @@ void controls::initialize(main_window* parent) { rotation_text->SetToolTip(rotation_tooltip); rotation_dropdown->SetToolTip(rotation_tooltip); preview_voxelization_button->SetToolTip(preview_voxelization_tooltip); - preview_bounding_box_button->SetToolTip(preview_bounding_box_tooltip); } { diff --git a/src/pstack/gui/controls.hpp b/src/pstack/gui/controls.hpp index 8456723..d04afb3 100644 --- a/src/pstack/gui/controls.hpp +++ b/src/pstack/gui/controls.hpp @@ -50,7 +50,6 @@ struct controls { wxStaticText* rotation_text; wxChoice* rotation_dropdown; wxButton* preview_voxelization_button; - wxButton* preview_bounding_box_button; // Stack settings tab wxStaticText* initial_x_text; diff --git a/src/pstack/gui/main_window.cpp b/src/pstack/gui/main_window.cpp index 4a766a9..a5c02d5 100644 --- a/src/pstack/gui/main_window.cpp +++ b/src/pstack/gui/main_window.cpp @@ -117,7 +117,6 @@ void main_window::enable_part_settings(bool enable) { _controls.minimize_checkbox->Enable(enable); _controls.rotation_dropdown->Enable(enable); _controls.preview_voxelization_button->Enable(enable); - _controls.preview_bounding_box_button->Enable(enable); } void main_window::on_select_results(const std::vector& indices) { @@ -295,7 +294,7 @@ wxMenuBar* main_window::make_menu_bar() { // Menu items cannot be 0 on Mac new_ = 1, open, save, close, import, export_, - pref_scroll, pref_extra, + pref_scroll, pref_extra, pref_bounding_box, about, website, issue, }; menu_bar->Bind(wxEVT_MENU, [this](wxCommandEvent& event) { @@ -332,6 +331,11 @@ wxMenuBar* main_window::make_menu_bar() { _parts_list.reload_all_text(); break; } + case menu_item::pref_bounding_box: { + _preferences.show_bounding_box = not _preferences.show_bounding_box; + _viewport->show_bounding_box(_preferences.show_bounding_box); + break; + } case menu_item::about: { constexpr auto str = "PartStacker Community Edition\n\n" @@ -368,6 +372,7 @@ wxMenuBar* main_window::make_menu_bar() { auto preferences_menu = new wxMenu(); preferences_menu->AppendCheckItem((int)menu_item::pref_scroll, "Invert &scroll", "Change the viewport scroll direction"); preferences_menu->AppendCheckItem((int)menu_item::pref_extra, "Display &extra parts", "Display the quantity of extra parts separately"); + preferences_menu->AppendCheckItem((int)menu_item::pref_bounding_box, "Show &bounding box", "Show the bounding box around the currently displayed mesh"); menu_bar->Append(preferences_menu, "&Preferences"); auto help_menu = new wxMenu(); @@ -449,9 +454,6 @@ void main_window::bind_all_controls() { _controls.preview_voxelization_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { wxMessageBox("Not yet implemented", "Error", wxICON_WARNING); }); - _controls.preview_bounding_box_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { - wxMessageBox("Not yet implemented", "Error", wxICON_WARNING); - }); _controls.section_view_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { wxMessageBox("Not yet implemented", "Error", wxICON_WARNING); @@ -696,7 +698,6 @@ void main_window::arrange_tab_part_settings(wxPanel* panel) { rotation_sizer->Add(_controls.rotation_dropdown, 0, wxALIGN_CENTER_VERTICAL); button_sizer->Add(rotation_sizer); button_sizer->Add(_controls.preview_voxelization_button); - button_sizer->Add(_controls.preview_bounding_box_button); lower_sizer->Add(label_sizer, 0, wxEXPAND); lower_sizer->AddSpacer(panel->FromDIP(3 * constants::inner_border)); diff --git a/src/pstack/gui/preferences.hpp b/src/pstack/gui/preferences.hpp index 3287dd5..59f37f4 100644 --- a/src/pstack/gui/preferences.hpp +++ b/src/pstack/gui/preferences.hpp @@ -6,6 +6,7 @@ namespace pstack::gui { struct preferences { bool invert_scroll = false; bool extra_parts = false; + bool show_bounding_box = false; }; } // namespace pstack::gui diff --git a/src/pstack/gui/viewport.hpp b/src/pstack/gui/viewport.hpp index c2e8c1d..3cd0849 100644 --- a/src/pstack/gui/viewport.hpp +++ b/src/pstack/gui/viewport.hpp @@ -38,6 +38,10 @@ class viewport : public wxGLCanvas { void scroll_direction(bool invert_scroll) { _scroll_direction = invert_scroll ? -1 : 1; } + void show_bounding_box(bool show) { + _show_bounding_box = show; + render(); + } private: void render(wxDC& dc);