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/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.cpp b/src/pstack/gui/viewport.cpp index f9e471d..1dabe93 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; @@ -54,6 +54,23 @@ constexpr auto 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; @@ -69,64 +86,101 @@ 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(); + 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) { - _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(); - _vao.add_vertex_buffer(0, std::move(vertices)); - _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 }); _transform.scale_mesh(zoom_factor); - _shader.set_uniform("transform_vertices", _transform.for_vertices()); - _shader.set_uniform("transform_normals", _transform.for_normals()); render(); } void viewport::remove_mesh() { - _vao.clear(); + _mesh_vao.clear(); + _mesh_vao.add_vertex_buffer(0, {}); + _mesh_vao.add_vertex_buffer(1, {}); - _vao.add_vertex_buffer(0, {}); - _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); - _shader.set_uniform("transform_vertices", _transform.for_vertices()); - _shader.set_uniform("transform_normals", _transform.for_normals()); 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); @@ -140,9 +194,19 @@ void viewport::render(wxDC& dc) { SetCurrent(*_opengl_context); graphics::clear(40 / 255.0, 50 / 255.0, 120 / 255.0, 1); - _shader.use_program(); - _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()); + + 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(); } @@ -164,8 +228,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 +262,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 +269,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(); } diff --git a/src/pstack/gui/viewport.hpp b/src/pstack/gui/viewport.hpp index 07a4aaf..3cd0849 100644 --- a/src/pstack/gui/viewport.hpp +++ b/src/pstack/gui/viewport.hpp @@ -27,11 +27,21 @@ 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) { _scroll_direction = invert_scroll ? -1 : 1; } + void show_bounding_box(bool show) { + _show_bounding_box = show; + render(); + } private: void render(wxDC& dc); @@ -51,8 +61,13 @@ 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{}; + + graphics::shader _bounding_box_shader{}; + graphics::vertex_array_object _bounding_box_vao{}; + bool _show_bounding_box = false; + transformation _transform{}; wxSize _viewport_size{};