diff --git a/assets/ground_truth/002-mesh-surface-headless_cross_section_fragment_gt.png b/assets/ground_truth/002-mesh-surface-headless_cross_section_fragment_gt.png new file mode 100644 index 0000000000..a7ff71c120 Binary files /dev/null and b/assets/ground_truth/002-mesh-surface-headless_cross_section_fragment_gt.png differ diff --git a/assets/ground_truth/002-mesh-surface-headless_cross_section_vertex_gt.png b/assets/ground_truth/002-mesh-surface-headless_cross_section_vertex_gt.png new file mode 100644 index 0000000000..eb19b649cc Binary files /dev/null and b/assets/ground_truth/002-mesh-surface-headless_cross_section_vertex_gt.png differ diff --git a/assets/ground_truth/005-mesh-wireframe-headless_cross_section_fragment_gt.png b/assets/ground_truth/005-mesh-wireframe-headless_cross_section_fragment_gt.png new file mode 100644 index 0000000000..bd8899d12b Binary files /dev/null and b/assets/ground_truth/005-mesh-wireframe-headless_cross_section_fragment_gt.png differ diff --git a/assets/ground_truth/005-mesh-wireframe-headless_cross_section_vertex_gt.png b/assets/ground_truth/005-mesh-wireframe-headless_cross_section_vertex_gt.png new file mode 100644 index 0000000000..3f12708e21 Binary files /dev/null and b/assets/ground_truth/005-mesh-wireframe-headless_cross_section_vertex_gt.png differ diff --git a/tests/render/002-mesh-surface-headless/main.cpp b/tests/render/002-mesh-surface-headless/main.cpp index 7350e97238..5ae37f5e8d 100644 --- a/tests/render/002-mesh-surface-headless/main.cpp +++ b/tests/render/002-mesh-surface-headless/main.cpp @@ -248,3 +248,51 @@ TEST_CASE("Mesh Surface Selection") 1.57079632679f); } } + +TEST_CASE("Mesh Surface Cross Section") +{ + SECTION("Vertex Cross Section") + { + runRenderTest( + TEST_NAME, "cross_section_vertex", [](vcl::HeadlessMeshViewer& mv) { + auto mesh = getDrawableMesh("bimba.obj"); + + vcl::CrossSectionSettings css(mesh); + css.type() = + vcl::CrossSectionSettings::CrossSectionType::PER_VERTEX; + + vcl::Point3f min = css.boundingBox().min(); + vcl::Point3f max = css.boundingBox().max(); + css.setLowerUpper( + min + (max - min) * 0.25f, max - (max - min) * 0.25f); + + mesh.setCrossSectionSettings(css); + + mv.pushDrawableObject(std::move(mesh)); + }); + } + + SECTION("Fragment Cross Section") + { + runRenderTest( + TEST_NAME, + "cross_section_fragment", + [](vcl::HeadlessMeshViewer& mv) { + auto mesh = getDrawableMesh("bimba.obj"); + + vcl::CrossSectionSettings css(mesh); + css.type() = + vcl::CrossSectionSettings::CrossSectionType::PER_FRAGMENT; + + vcl::Point3f min = css.boundingBox().min(); + vcl::Point3f max = css.boundingBox().max(); + css.setLowerUpper( + min + (max - min) * 0.25f, max - (max - min) * 0.25f); + + mesh.setCrossSectionSettings(css); + + mv.pushDrawableObject(std::move(mesh)); + }); + } +} + diff --git a/tests/render/005-mesh-wireframe-headless/main.cpp b/tests/render/005-mesh-wireframe-headless/main.cpp index dec966838c..23e777fd59 100644 --- a/tests/render/005-mesh-wireframe-headless/main.cpp +++ b/tests/render/005-mesh-wireframe-headless/main.cpp @@ -208,3 +208,74 @@ TEST_CASE("Wireframe Depth Offset") -150.f); } } + +TEST_CASE("Wireframe Cross Section") +{ + SECTION("Vertex Cross Section") + { + runRenderTest( + TEST_NAME, + "cross_section_vertex", + [](vcl::HeadlessMeshViewer& mv) { + auto mesh = + getDrawableMesh("bunny_simplified.obj"); + + auto settings = mesh.renderSettings(); + settings.setSurface( + vcl::MeshRenderInfo::Surface::VISIBLE, false); + settings.setWireframe( + vcl::MeshRenderInfo::Wireframe::VISIBLE, true); + settings.setWireframeWidth(3); + mesh.setRenderSettings(settings); + + vcl::CrossSectionSettings css(mesh); + css.type() = + vcl::CrossSectionSettings::CrossSectionType::PER_VERTEX; + + vcl::Point3f min = css.boundingBox().min(); + vcl::Point3f max = css.boundingBox().max(); + css.setLowerUpper( + min + (max - min) * 0.25f, max - (max - min) * 0.25f); + + mesh.setCrossSectionSettings(css); + + mv.pushDrawableObject(std::move(mesh)); + }, + 0.f, + -150.f); + } + + SECTION("Fragment Cross Section") + { + runRenderTest( + TEST_NAME, + "cross_section_fragment", + [](vcl::HeadlessMeshViewer& mv) { + auto mesh = + getDrawableMesh("bunny_simplified.obj"); + + auto settings = mesh.renderSettings(); + settings.setSurface( + vcl::MeshRenderInfo::Surface::VISIBLE, false); + settings.setWireframe( + vcl::MeshRenderInfo::Wireframe::VISIBLE, true); + settings.setWireframeWidth(3); + mesh.setRenderSettings(settings); + + vcl::CrossSectionSettings css(mesh); + css.type() = + vcl::CrossSectionSettings::CrossSectionType::PER_FRAGMENT; + + vcl::Point3f min = css.boundingBox().min(); + vcl::Point3f max = css.boundingBox().max(); + css.setLowerUpper( + min + (max - min) * 0.25f, max - (max - min) * 0.25f); + + mesh.setCrossSectionSettings(css); + + mv.pushDrawableObject(std::move(mesh)); + }, + 0.f, + -150.f); + } +} diff --git a/vclib/core/include/vclib/space/core/point.h b/vclib/core/include/vclib/space/core/point.h index 9a5e037f6e..cca508739e 100644 --- a/vclib/core/include/vclib/space/core/point.h +++ b/vclib/core/include/vclib/space/core/point.h @@ -397,6 +397,70 @@ class Point : public Eigen::Matrix at(i) = args[i]; } + /** + * @brief Returns true if all the components of this point are strictly less + * than the corresponding components of another point p1. + * + * @param[in] p1: The Point object to compare with. + * @return true if this point is strictly below p1, false otherwise. + */ + bool allLessThan(const Point& p1) const + { + for (uint i = 0; i < N; i++) { + if (at(i) >= p1(i)) + return false; + } + return true; + } + + /** + * @brief Returns true if all the components of this point are less than or + * equal to the corresponding components of another point p1. + * + * @param[in] p1: The Point object to compare with. + * @return true if this point is below or equal to p1, false otherwise. + */ + bool allLessEqualThan(const Point& p1) const + { + for (uint i = 0; i < N; i++) { + if (at(i) > p1(i)) + return false; + } + return true; + } + + /** + * @brief Returns true if all the components of this point are strictly + * greater than the corresponding components of another point p1. + * + * @param[in] p1: The Point object to compare with. + * @return true if this point is strictly above p1, false otherwise. + */ + bool allGreaterThan(const Point& p1) const + { + for (uint i = 0; i < N; i++) { + if (at(i) <= p1(i)) + return false; + } + return true; + } + + /** + * @brief Returns true if all the components of this point are greater than + * or equal to the corresponding components of another point p1. + * + * @param[in] p1: The Point object to compare with. + * @return true if this point is above or equal to p1, false otherwise. + */ + bool allGreaterEqualThan(const Point& p1) const + { + for (uint i = 0; i < N; i++) { + if (at(i) < p1(i)) + return false; + } + return true; + } + /** * @brief Returns the outer product between this point p and p1, which is * p * p1^T diff --git a/vclib/render/include/vclib/bgfx/drawable/drawable_mesh_bgfx.h b/vclib/render/include/vclib/bgfx/drawable/drawable_mesh_bgfx.h index e649ed7318..dd4a18ad8f 100644 --- a/vclib/render/include/vclib/bgfx/drawable/drawable_mesh_bgfx.h +++ b/vclib/render/include/vclib/bgfx/drawable/drawable_mesh_bgfx.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -94,6 +95,9 @@ class DrawableMeshBGFX : public AbstractDrawableMesh, public MeshType mMRB.update(*this, buffersToUpdate); mMRS.setRenderCapabilityFrom(*this); setRenderSettings(mMRS); + + mCSS.setBoundingBox(*this); + mMRB.updateCrossSectionSettings(mCSS); } void updateRenderSettingsCapabilities() override @@ -109,6 +113,12 @@ class DrawableMeshBGFX : public AbstractDrawableMesh, public MeshType mMRB.updatePointsSettings(rs); } + void setCrossSectionSettings(const CrossSectionSettings& css) override + { + AbstractDrawableMesh::setCrossSectionSettings(css); + mMRB.updateCrossSectionSettings(css); + } + const AbstractMeshProvider& meshProvider() const override { return mProvider; @@ -205,6 +215,8 @@ class DrawableMeshBGFX : public AbstractDrawableMesh, public MeshType DrawableMeshUniforms::setColor(*this); MeshRenderSettingsUniforms::set(mMRS); + updateCrossSectionUniforms(); + if (mMRS.isSurface(MRI::Surface::VISIBLE)) { const DrawableEnvironment* env = settings.environment; @@ -314,6 +326,8 @@ class DrawableMeshBGFX : public AbstractDrawableMesh, public MeshType model = MeshType::transformMatrix().template cast(); } + updateCrossSectionUniforms(); + if (mMRS.isSurface(MRI::Surface::VISIBLE)) { mMRB.bindVertexBuffers(mMRS); mMRB.bindIndexBuffers(mMRS); @@ -349,6 +363,21 @@ class DrawableMeshBGFX : public AbstractDrawableMesh, public MeshType { MeshRenderSettingsUniforms::bind(); DrawableMeshUniforms::bind(); + CrossSectionUniforms::bind(); + } + + void updateCrossSectionUniforms() const + { + if (AbstractDrawableMesh::mCSS.isEnabled()) { + using enum CrossSectionSettings::CrossSectionType; + CrossSectionUniforms::set( + AbstractDrawableMesh::mCSS.lower(), + AbstractDrawableMesh::mCSS.upper(), + AbstractDrawableMesh::mCSS.type() == PER_FRAGMENT); + } + else { + CrossSectionUniforms::set(); + } } /** @@ -400,12 +429,17 @@ class DrawableMeshBGFX : public AbstractDrawableMesh, public MeshType { using enum MeshRenderInfo::Surface; + uint section = 0; uint shading = 0; uint color = 0; uint selection = 0; uint backFace = 0; uint specular = 0; + if (!mCSS.isEnabled()) { + section = 1; + } + if (mMRS.isSurface(SHADING_FLAT)) { shading = 0; } @@ -455,18 +489,21 @@ class DrawableMeshBGFX : public AbstractDrawableMesh, public MeshType constexpr uint N_SELECTION_MODES = 2; constexpr uint N_BACK_FACE_MODES = 2; constexpr uint N_SPECULAR_MODES = 2; + constexpr uint N_SECTION_MODES = 2; // the first shader of all the combinations uint base = toUnderlying( VertFragProgram:: - DRAWABLE_MESH_SURFACE_SHADING_FLAT_COLOR_FACE_SELECTION_ON_BACK_FACE_DOUBLE_OFF_SPECULAR_OFF); + DRAWABLE_MESH_SURFACE_SECTION_ON_SHADING_FLAT_COLOR_FACE_SELECTION_ON_BACK_FACE_DOUBLE_OFF_SPECULAR_OFF); uint offset = linearizeIndex< + N_SECTION_MODES, N_SHADING_MODES, N_COLOR_MODES, N_SELECTION_MODES, N_BACK_FACE_MODES, - N_SPECULAR_MODES>(shading, color, selection, backFace, specular); + N_SPECULAR_MODES>( + section, shading, color, selection, backFace, specular); uint program = base + offset; diff --git a/vclib/render/include/vclib/bgfx/drawable/mesh/mesh_render_buffers.h b/vclib/render/include/vclib/bgfx/drawable/mesh/mesh_render_buffers.h index bb7999352f..46d08b07d0 100644 --- a/vclib/render/include/vclib/bgfx/drawable/mesh/mesh_render_buffers.h +++ b/vclib/render/include/vclib/bgfx/drawable/mesh/mesh_render_buffers.h @@ -426,6 +426,13 @@ class MeshRenderBuffers : public MeshRenderData> mPoints.setSelectionColor(mrs.pointSelectionColor()); } + void updateCrossSectionSettings(const CrossSectionSettings& css) + { + mEdgeLines.setCrossSectionSettings(css); + mWireframeLines.setCrossSectionSettings(css); + mPoints.setCrossSectionSettings(css); + } + private: void setVertexPositionsBuffer(const MeshType& mesh) // override { diff --git a/vclib/render/include/vclib/bgfx/drawable/uniforms/cross_section_uniforms.h b/vclib/render/include/vclib/bgfx/drawable/uniforms/cross_section_uniforms.h new file mode 100644 index 0000000000..104687db05 --- /dev/null +++ b/vclib/render/include/vclib/bgfx/drawable/uniforms/cross_section_uniforms.h @@ -0,0 +1,83 @@ +/***************************************************************************** + * VCLib * + * Visual Computing Library * + * * + * Copyright(C) 2021-2026 * + * Visual Computing Lab * + * ISTI - Italian National Research Council * + * * + * All rights reserved. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the Mozilla Public License Version 2.0 as published * + * by the Mozilla Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Mozilla Public License Version 2.0 * + * (https://www.mozilla.org/en-US/MPL/2.0/) for more details. * + ****************************************************************************/ + +#ifndef VCL_BGFX_DRAWABLE_UNIFORMS_CROSS_SECTION_UNIFORMS_H +#define VCL_BGFX_DRAWABLE_UNIFORMS_CROSS_SECTION_UNIFORMS_H + +#include + +#include + +namespace vcl { + +class CrossSectionUniforms +{ + inline static std::array sCrossSectionMinData = { + Point3f::min().x(), // x min + Point3f::min().y(), // y min + Point3f::min().z(), // z min + 0.0 // per-fragment flag + // (0.0 = per-vertex, 1.0 = per-fragment) + }; + + inline static std::array sCrossSectionMaxData = { + Point3f::max().x(), // x max + Point3f::max().y(), // y max + Point3f::max().z(), // z max + 0.0 // unused + }; + + inline static StaticUniform sCrossSectionMinDataUniform { + "u_crossSectionMinData", + bgfx::UniformType::Vec4}; + inline static StaticUniform sCrossSectionMaxDataUniform { + "u_crossSectionMaxData", + bgfx::UniformType::Vec4}; + +public: + CrossSectionUniforms() = delete; + + static void set( + const Point3f& minPoint = Point3f::min(), + const Point3f& maxPoint = Point3f::max(), + bool perFragment = false) + { + sCrossSectionMinData[0] = minPoint.x(); + sCrossSectionMinData[1] = minPoint.y(); + sCrossSectionMinData[2] = minPoint.z(); + sCrossSectionMinData[3] = float(perFragment); + + sCrossSectionMaxData[0] = maxPoint.x(); + sCrossSectionMaxData[1] = maxPoint.y(); + sCrossSectionMaxData[2] = maxPoint.z(); + } + + static void bind() + { + sCrossSectionMinDataUniform.bind(sCrossSectionMinData.data()); + sCrossSectionMaxDataUniform.bind(sCrossSectionMaxData.data()); + } +}; + +} // namespace vcl + +#endif // VCL_BGFX_DRAWABLE_UNIFORMS_CROSS_SECTION_UNIFORMS_H diff --git a/vclib/render/include/vclib/bgfx/primitives/lines.h b/vclib/render/include/vclib/bgfx/primitives/lines.h index 7d94462acb..c7ba1f7b77 100644 --- a/vclib/render/include/vclib/bgfx/primitives/lines.h +++ b/vclib/render/include/vclib/bgfx/primitives/lines.h @@ -10,6 +10,8 @@ #include +#include + namespace vcl { /** @@ -65,14 +67,15 @@ class Lines uint mLineNorCount = 0; uint mLineSelCount = 0; - float mWidth = 1.0f; - Topology mTopology = Topology::LINES; - ColorSetting mColorSetting = ColorSetting::GENERAL; - Shading mShading = Shading::NONE; - Color mGeneralColor = Color::Black; - float mDepthOffset = 0.0f; + float mWidth = 1.0f; + Topology mTopology = Topology::LINES; + ColorSetting mColorSetting = ColorSetting::GENERAL; + Shading mShading = Shading::NONE; + Color mGeneralColor = Color::Black; + float mDepthOffset = 0.0f; Color mSelectionColor = Color(0x88FF9732, Color::Format::ABGR); bool mSelectionVisibility = false; + CrossSectionSettings mCrossSectionSettings; OwnedOrRefBuffer mVertexPositions; OwnedOrRefBuffer mVertexColors; @@ -157,6 +160,25 @@ class Lines */ Color selectionColor() const { return mSelectionColor; } + /* + * @brief Returns the current cross section settings. + * @return The current cross section settings. + */ + const CrossSectionSettings& crossSectionSettings() const + { + return mCrossSectionSettings; + } + + /** + * @brief Sets the cross section settings. + * @param[in] settings: The cross section settings to apply. + */ + void setCrossSectionSettings(const CrossSectionSettings& settings) + { + mCrossSectionSettings = settings; + mIsUpdateProgramNeeded = true; + } + /** * @brief Returns whether the line set has valid vertex positions. */ diff --git a/vclib/render/include/vclib/bgfx/primitives/points.h b/vclib/render/include/vclib/bgfx/primitives/points.h index 529b738be2..8b2a4af4b1 100644 --- a/vclib/render/include/vclib/bgfx/primitives/points.h +++ b/vclib/render/include/vclib/bgfx/primitives/points.h @@ -13,6 +13,8 @@ #include #include +#include + namespace vcl { /** @@ -55,9 +57,10 @@ class Points Shading shading = Shading::NONE; Shape shape = Shape::SQUARE; Color generalColor = Color::Black; - float depthOffset = 0.0f; - Color selectionColor = Color(0x88FF9732, Color::Format::ABGR); - bool selectionVisibility = false; + float depthOffset = 0.0f; + Color selectionColor = Color(0x88FF9732, Color::Format::ABGR); + bool selectionVisibility = false; + CrossSectionSettings crossSectionSettings; }; private: @@ -186,6 +189,15 @@ class Points */ const Settings& settings() const { return mSettings; } + /** + * @brief Returns the current cross section settings. + * @return The current cross section settings. + */ + const CrossSectionSettings& crossSectionSettings() const + { + return mSettings.crossSectionSettings; + } + /** * @brief Returns whether the point set has valid vertex positions. * @@ -470,6 +482,16 @@ class Points mIsUpdateProgramNeeded = true; } + /** + * @brief Sets the cross section settings. + * @param[in] settings: The cross section settings to apply. + */ + void setCrossSectionSettings(const CrossSectionSettings& settings) + { + mSettings.crossSectionSettings = settings; + mIsUpdateProgramNeeded = true; + } + void draw(bgfx::ViewId viewId) const; void drawId(bgfx::ViewId viewId, uint32_t id) const; diff --git a/vclib/render/include/vclib/qt/gui/mesh_render_settings_frame.h b/vclib/render/include/vclib/qt/gui/mesh_render_settings_frame.h index b563736bd7..6d9c721104 100644 --- a/vclib/render/include/vclib/qt/gui/mesh_render_settings_frame.h +++ b/vclib/render/include/vclib/qt/gui/mesh_render_settings_frame.h @@ -8,9 +8,11 @@ #ifndef VCL_QT_GUI_MESH_RENDER_SETTINGS_FRAME_H #define VCL_QT_GUI_MESH_RENDER_SETTINGS_FRAME_H +#include "mesh_render_settings_frame/cross_section_settings_frame.h" #include "mesh_render_settings_frame/generic_mesh_render_settings_frame.h" #include +#include #include @@ -29,7 +31,8 @@ class MeshRenderSettingsFrame : public QFrame Ui::MeshRenderSettingsFrame* mUI; MeshRenderSettings mMRS; - std::vector frames; + std::vector mFrames; + CrossSectionSettingsFrame* mCrossSectionFrame = nullptr; public: explicit MeshRenderSettingsFrame(QWidget* parent = nullptr); @@ -37,14 +40,21 @@ class MeshRenderSettingsFrame : public QFrame const MeshRenderSettings& meshRenderSettings() const; + const CrossSectionSettings& crossSectionSettings() const; + void setMeshRenderSettings( const MeshRenderSettings& settings, bool changeCurrentTab = false); + void setCrossSectionSettings(const CrossSectionSettings& settings); + bool isApplyToAllEnabled() const; signals: - void settingsUpdated(); + void meshRenderSettingsUpdated(); + + void crossSectionSettingsUpdated(); + void applyToAllToggled(bool checked); private slots: diff --git a/vclib/render/include/vclib/qt/gui/mesh_render_settings_frame/cross_section_settings_frame.h b/vclib/render/include/vclib/qt/gui/mesh_render_settings_frame/cross_section_settings_frame.h new file mode 100644 index 0000000000..bcc13d673b --- /dev/null +++ b/vclib/render/include/vclib/qt/gui/mesh_render_settings_frame/cross_section_settings_frame.h @@ -0,0 +1,100 @@ +/***************************************************************************** + * VCLib * + * Visual Computing Library * + * * + * Copyright(C) 2021-2026 * + * Visual Computing Lab * + * ISTI - Italian National Research Council * + * * + * All rights reserved. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the Mozilla Public License Version 2.0 as published * + * by the Mozilla Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Mozilla Public License Version 2.0 * + * (https://www.mozilla.org/en-US/MPL/2.0/) for more details. * + ****************************************************************************/ + +#ifndef VCL_QT_GUI_MESH_RENDER_SETTINGS_FRAME_CROSS_SECTION_SETTINGS_FRAME_H +#define VCL_QT_GUI_MESH_RENDER_SETTINGS_FRAME_CROSS_SECTION_SETTINGS_FRAME_H + +#include +#include + +#include +#include +#include + +#include + +namespace vcl::qt { + +namespace Ui { +class CrossSectionSettingsFrame; +} // namespace Ui + +class CrossSectionSettingsFrame : public QFrame +{ + Q_OBJECT + + enum Axis {X= 0, Y, Z, AXIS_COUNT}; + + Ui::CrossSectionSettingsFrame* mUI; + + std::array mFloatSliders; + std::array mMinSpinBoxes; + std::array mMaxSpinBoxes; + + CrossSectionSettings mCSS; + +public: + explicit CrossSectionSettingsFrame( + const CrossSectionSettings& css, + QWidget* parent = nullptr); + + explicit CrossSectionSettingsFrame(QWidget* parent = nullptr); + + ~CrossSectionSettingsFrame(); + + const CrossSectionSettings& crossSectionSettings() const; + + void setCrossSectionSettings(const CrossSectionSettings& settings); + + QCheckBox* visibilityCheckBox() const; + +signals: + void crossSectionSettingsUpdated(); + +private: + void updateFrameFromSettings(); + void updateSlider( + FloatRangeSlider* slider, + QDoubleSpinBox* minSpinBox, + QDoubleSpinBox* maxSpinBox, + float min, + float max, + float lower, + float upper); + + float mStep = -1.0f; + +private slots: + void onCrossSectionEnabledChanged(Qt::CheckState arg1); + void onPerVertexToggled(bool checked); + void onPerFragmentToggled(bool checked); + void onStepSpinBoxValueChanged(int value); + + void onFloatRangeSliderLowerValueChanged(Axis axis, float value); + void onFloatRangeSliderUpperValueChanged(Axis axis, float value); + void onMinSpinBoxValueChanged(Axis axis, double value); + void onMaxSpinBoxValueChanged(Axis axis, double value); +}; + +} // namespace vcl::qt + +#endif // VCL_QT_GUI_MESH_RENDER_SETTINGS_FRAME_CROSS_SECTION_SETTINGS_FRAME_H diff --git a/vclib/render/include/vclib/qt/gui/mesh_render_settings_frame/generic_mesh_render_settings_frame.h b/vclib/render/include/vclib/qt/gui/mesh_render_settings_frame/generic_mesh_render_settings_frame.h index 01ca7b72fe..fb5d073042 100644 --- a/vclib/render/include/vclib/qt/gui/mesh_render_settings_frame/generic_mesh_render_settings_frame.h +++ b/vclib/render/include/vclib/qt/gui/mesh_render_settings_frame/generic_mesh_render_settings_frame.h @@ -36,7 +36,7 @@ class GenericMeshRenderSettingsFrame : public QFrame virtual QCheckBox* visibilityCheckBox() = 0; signals: - void settingsUpdated(); + void meshRenderSettingsUpdated(); protected: static void setButtonBackGround(QPushButton* b, const QColor& c); diff --git a/vclib/render/include/vclib/qt/mesh_viewer.h b/vclib/render/include/vclib/qt/mesh_viewer.h index 4a626335a0..985e7d8eb6 100644 --- a/vclib/render/include/vclib/qt/mesh_viewer.h +++ b/vclib/render/include/vclib/qt/mesh_viewer.h @@ -334,6 +334,8 @@ private slots: void meshRenderSettingsUpdated(); + void crossSectionSettingsUpdated(); + void applyToAllToggled(bool checked); void renderModeChanged(); diff --git a/vclib/render/include/vclib/render/drawable/abstract_drawable_mesh.h b/vclib/render/include/vclib/render/drawable/abstract_drawable_mesh.h index 317d4f53e5..a8b7d14669 100644 --- a/vclib/render/include/vclib/render/drawable/abstract_drawable_mesh.h +++ b/vclib/render/include/vclib/render/drawable/abstract_drawable_mesh.h @@ -12,6 +12,7 @@ #include "mesh/mesh_render_settings.h" #include +#include #include #include @@ -32,6 +33,7 @@ class AbstractDrawableMesh : public vcl::DrawableObject { protected: MeshRenderSettings mMRS; + CrossSectionSettings mCSS; std::function mOnSelectionUpdated; @@ -43,12 +45,14 @@ class AbstractDrawableMesh : public vcl::DrawableObject AbstractDrawableMesh(const AbstractDrawableMesh& other) = default; template - AbstractDrawableMesh(const MeshType& m) : mMRS(m) + AbstractDrawableMesh(const MeshType& m) : mMRS(m), mCSS(m) { } const MeshRenderSettings& renderSettings() const { return mMRS; } + const CrossSectionSettings& crossSectionSettings() const { return mCSS; } + virtual void updateBuffers( MeshRenderInfo::BuffersBitSet buffersToUpdate = MeshRenderInfo::BUFFERS_ALL) = 0; @@ -57,6 +61,11 @@ class AbstractDrawableMesh : public vcl::DrawableObject virtual void setRenderSettings(const MeshRenderSettings& rs) { mMRS = rs; } + virtual void setCrossSectionSettings(const CrossSectionSettings& css) + { + mCSS = css; + } + virtual const AbstractMeshProvider& meshProvider() const = 0; virtual void computeSelection(const SelectionParameters& params) {} @@ -93,6 +102,7 @@ class AbstractDrawableMesh : public vcl::DrawableObject using std::swap; vcl::DrawableObject::swap(other); swap(mMRS, other.mMRS); + swap(mCSS, other.mCSS); } }; diff --git a/vclib/render/include/vclib/render/settings/cross_section_settings.h b/vclib/render/include/vclib/render/settings/cross_section_settings.h new file mode 100644 index 0000000000..d486d347aa --- /dev/null +++ b/vclib/render/include/vclib/render/settings/cross_section_settings.h @@ -0,0 +1,198 @@ +/***************************************************************************** + * VCLib * + * Visual Computing Library * + * * + * Copyright(C) 2021-2026 * + * Visual Computing Lab * + * ISTI - Italian National Research Council * + * * + * All rights reserved. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the Mozilla Public License Version 2.0 as published * + * by the Mozilla Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Mozilla Public License Version 2.0 * + * (https://www.mozilla.org/en-US/MPL/2.0/) for more details. * + ****************************************************************************/ + +#ifndef VCL_RENDER_SETTINGS_CROSS_SECTION_SETTINGS_H +#define VCL_RENDER_SETTINGS_CROSS_SECTION_SETTINGS_H + +#include +#include +#include + +namespace vcl { + +class CrossSectionSettings +{ +public: + enum class CrossSectionType { NONE, PER_VERTEX, PER_FRAGMENT }; + +private: + CrossSectionType mType = CrossSectionType::NONE; + + Box3f mBBox = Box3f(Point3f::min(), Point3f::max()); + + Point3f mLower = Point3f::min(); + Point3f mUpper = Point3f::max(); + +public: + CrossSectionSettings() = default; + + template + CrossSectionSettings(const MeshType& m, float epsilon = 0.02f) + { + setBoundingBox(m, epsilon); + } + + bool isEnabled() const { return mType != CrossSectionType::NONE; } + + CrossSectionType& type() { return mType; } + + CrossSectionType type() const { return mType; } + + const Box3f& boundingBox() const { return mBBox; } + + const Point3f& lower() const { return mLower; } + + const Point3f& upper() const { return mUpper; } + + /** + * @brief Sets the bounding box for the cross-section settings. + * + * The bounding box is updated to the provided value, and the lower and + * upper points are adjusted to be within the new bounding box if necessary. + * + * @param[in] bbox: The new bounding box to set. + */ + void setBoundingBox(const Box3f& bbox) + { + if (!bbox.isNull()) { + for (int i = 0; i < 3; ++i) { + if (mLower[i] <= mBBox.min()[i]) { + mLower[i] = bbox.min()[i]; + } + if (mUpper[i] >= mBBox.max()[i]) { + mUpper[i] = bbox.max()[i]; + } + } + + mBBox = bbox; + + Point3f clampedLower = + mLower.cwiseMax(bbox.min()).cwiseMin(bbox.max()); + Point3f clampedUpper = + mUpper.cwiseMax(bbox.min()).cwiseMin(bbox.max()); + + mLower = clampedLower.cwiseMin(clampedUpper); + mUpper = clampedLower.cwiseMax(clampedUpper); + + if (!mLower.allLessEqualThan(mUpper)) { + mLower = bbox.min(); + mUpper = bbox.max(); + } + } + else { + mBBox = Box3f(Point3f::min(), Point3f::max()); + } + } + + /** + * @brief Sets the bounding box for the cross-section settings based on the + * given mesh. + * + * The bounding box is computed from the vertices of the provided mesh, and + * the lower and upper points are adjusted to be within the new bounding box + * if necessary. Bounding box is inflated by an epsilon of its diagonal to + * avoid numerical issues when the cross-section plane is very close to the + * bounding box. + * + * Model transformation is applied to the bounding box if the mesh has a + * transform matrix. + * + * @tparam MeshType: The type of the mesh for which to compute the bounding + * box. The mesh type must satisfy the MeshConcept. + * + * @param[in] m: The mesh from which to compute the bounding box. + * @param[in] epsilon: The epsilon value used to inflate the bounding box. + * It is expressed as a fraction of the bounding box diagonal. + */ + template + void setBoundingBox(const MeshType& m, float epsilon = 0.02f) + { + auto bbox = vcl::boundingBox(m).template cast(); + + if (!bbox.isNull()) { + if constexpr (HasTransformMatrix) { + Matrix44f transform = m.transformMatrix().template cast(); + bbox = transformBox(bbox, transform); + } + // inflate bbox by epsilon of its diagonal + float eps = bbox.diagonal() * epsilon; + + bbox.min() -= eps; + bbox.max() += eps; + + setBoundingBox(bbox); + } + } + + /** + * @brief Sets the lower point of the cross-section settings. + * + * The lower point is updated only if it is inside the bounding box and + * strictly below or equal to the upper point. + * + * @param[in] lower: The new lower point to set. + */ + void setLower(const Point3f& lower) + { + if (mBBox.isInside(lower) && lower.allLessEqualThan(mUpper)) { + mLower = lower; + } + } + + /** + * @brief Sets the upper point of the cross-section settings. + * + * The upper point is updated only if it is inside the bounding box and + * strictly above or equal to the lower point. + * + * @param[in] upper: The new upper point to set. + */ + void setUpper(const Point3f& upper) + { + if (mBBox.isInside(upper) && upper.allGreaterEqualThan(mLower)) { + mUpper = upper; + } + } + + /** + * @brief Sets both the lower and upper points of the cross-section + * settings. + * + * Both points are updated only if they are inside the bounding box and + * the lower point is strictly below or equal to the upper point. + * + * @param[in] lower: The new lower point to set. + * @param[in] upper: The new upper point to set. + */ + void setLowerUpper(const Point3f& lower, const Point3f& upper) + { + if (mBBox.isInside(lower) && mBBox.isInside(upper) && + lower.allLessEqualThan(upper)) { + mLower = lower; + mUpper = upper; + } + } +}; + +} // namespace vcl + +#endif // VCL_RENDER_SETTINGS_CROSS_SECTION_SETTINGS_H diff --git a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface/fs_surface_in.sh b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface/fs_surface_in.sh index 299f69a54d..ad41828d2f 100644 --- a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface/fs_surface_in.sh +++ b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface/fs_surface_in.sh @@ -7,9 +7,13 @@ $input v_position, v_normal, v_tangent, v_color, v_texcoord0, v_texcoord1 +// cross section +$input v_worldPos, v_discardFlag + #include #include #include +#include #include #include @@ -32,6 +36,10 @@ DECLARE_FETCH_VEC3_FROM_FLOAT(fetchPrimitiveNormal, primitiveNormals) void main() { +#ifdef SURFACE_SECTION_ON + discardIfCrossSectionClipped(v_discardFlag, v_worldPos); +#endif + // color vec4 color = vec4(0.0, 0.0, 0.0, 1.0); diff --git a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface/surface.config b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface/surface.config index f40cf74e4a..74dc76fd6c 100644 --- a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface/surface.config +++ b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface/surface.config @@ -1,13 +1,18 @@ ENUM_PREFIX DRAWABLE_MESH_SURFACE DEFINE_PREFIX SURFACE -# VS is static (not generated for each combination) -VS_FILE vs_surface.sc +# VS is generated dynamically based on combinations +VS_IN vs_surface_in.sh +VS_PREFIX vs_surface # FS is generated dynamically based on combinations FS_IN fs_surface_in.sh FS_PREFIX fs_surface +DIM_BOTH SECTION + ON + OFF + DIM_FS SHADING FLAT NONE diff --git a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface/varying.def.sc b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface/varying.def.sc index af6b7b69d9..c35f7fcb34 100644 --- a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface/varying.def.sc +++ b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface/varying.def.sc @@ -18,3 +18,6 @@ vec4 v_tangent : TANGENT; vec4 v_color : COLOR0; vec2 v_texcoord0 : TEXCOORD0; vec2 v_texcoord1 : TEXCOORD1; + +vec3 v_worldPos : TEXCOORD3 = vec3(0.0, 0.0, 0.0); +float v_discardFlag : TEXCOORD4 = 0; diff --git a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface/vs_surface.sc b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface/vs_surface_in.sh similarity index 72% rename from vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface/vs_surface.sc rename to vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface/vs_surface_in.sh index 687befcb5f..64d12384bf 100644 --- a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface/vs_surface.sc +++ b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface/vs_surface_in.sh @@ -8,6 +8,10 @@ $input a_position, a_normal, a_tangent, a_color0, a_texcoord0, a_texcoord1 $output v_position, v_normal, v_tangent, v_color, v_texcoord0, v_texcoord1 +// cross section +$output v_worldPos, v_discardFlag + +#include #include void main() @@ -22,4 +26,12 @@ void main() // default case - color is taken from buffer v_color = a_color0; + +#ifdef SURFACE_SECTION_ON + v_worldPos = mul(u_model[0], vec4(a_position, 1.0)).xyz; + // discard flag - used to discard the whole vertex, but in fragment shader + v_discardFlag = computeDiscardFlag(v_worldPos); +#else + v_discardFlag = 1; +#endif } diff --git a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_id/fs_surface_id.sc b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_id/fs_surface_id.sc index d82b954e31..b1a7f2714e 100644 --- a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_id/fs_surface_id.sc +++ b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_id/fs_surface_id.sc @@ -5,12 +5,18 @@ // v. 2.0. If a copy of the MPL was not distributed with this file, You can // obtain one at https://mozilla.org/MPL/2.0/. +// cross section +$input v_worldPos, v_discardFlag + #include #include +#include void main() { + discardIfCrossSectionClipped(v_discardFlag, v_worldPos); + /***** render ID to color ******/ vec4 color = uintABGRToVec4Color(u_meshId); diff --git a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_id/varying.def.sc b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_id/varying.def.sc index 7473cecd11..accca66c61 100644 --- a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_id/varying.def.sc +++ b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_id/varying.def.sc @@ -5,4 +5,7 @@ // v. 2.0. If a copy of the MPL was not distributed with this file, You can // obtain one at https://mozilla.org/MPL/2.0/. -vec3 a_position : POSITION; \ No newline at end of file +vec3 a_position : POSITION; + +vec3 v_worldPos : TEXCOORD0 = vec3(0.0, 0.0, 0.0); +float v_discardFlag : TEXCOORD1 = 0; diff --git a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_id/vs_surface_id.sc b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_id/vs_surface_id.sc index abeb9d7767..99cdef0308 100644 --- a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_id/vs_surface_id.sc +++ b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_id/vs_surface_id.sc @@ -7,9 +7,18 @@ $input a_position +// cross section +$output v_worldPos, v_discardFlag + #include +#include + void main() { + v_worldPos = mul(u_model[0], vec4(a_position, 1.0)).xyz; gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0)); + + // discard flag - used to discard the whole vertex, but in fragment shader + v_discardFlag = computeDiscardFlag(v_worldPos); } diff --git a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_pbr/fs_surface_pbr.sc b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_pbr/fs_surface_pbr.sc index 5413cfcd47..c0438ad460 100644 --- a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_pbr/fs_surface_pbr.sc +++ b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_pbr/fs_surface_pbr.sc @@ -7,12 +7,16 @@ $input v_position, v_normal, v_tangent, v_color, v_texcoord0, v_texcoord1 +// cross section +$input v_worldPos, v_discardFlag + #include #include #include #include #include +#include #include #include @@ -34,6 +38,8 @@ SAMPLERCUBE(s_specular, VCL_MRB_CUBEMAP1); void main() { + discardIfCrossSectionClipped(v_discardFlag, v_worldPos); + // texcoord to use bool useTexture = isSurfaceTexVertex() || isSurfaceTexWedge(); diff --git a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_pbr/varying.def.sc b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_pbr/varying.def.sc index af6b7b69d9..c35f7fcb34 100644 --- a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_pbr/varying.def.sc +++ b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_pbr/varying.def.sc @@ -18,3 +18,6 @@ vec4 v_tangent : TANGENT; vec4 v_color : COLOR0; vec2 v_texcoord0 : TEXCOORD0; vec2 v_texcoord1 : TEXCOORD1; + +vec3 v_worldPos : TEXCOORD3 = vec3(0.0, 0.0, 0.0); +float v_discardFlag : TEXCOORD4 = 0; diff --git a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_pbr/vs_surface_pbr.sc b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_pbr/vs_surface_pbr.sc index 687befcb5f..40e7a946f8 100644 --- a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_pbr/vs_surface_pbr.sc +++ b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_pbr/vs_surface_pbr.sc @@ -8,6 +8,10 @@ $input a_position, a_normal, a_tangent, a_color0, a_texcoord0, a_texcoord1 $output v_position, v_normal, v_tangent, v_color, v_texcoord0, v_texcoord1 +// cross section +$output v_worldPos, v_discardFlag + +#include #include void main() @@ -22,4 +26,7 @@ void main() // default case - color is taken from buffer v_color = a_color0; + + // discard flag - used to discard the whole vertex, but in fragment shader + v_discardFlag = computeDiscardFlag(v_worldPos); } diff --git a/vclib/render/shaders/vclib/bgfx/drawable/uniforms/cross_section_uniforms.sh b/vclib/render/shaders/vclib/bgfx/drawable/uniforms/cross_section_uniforms.sh new file mode 100644 index 0000000000..f11a5bb1a8 --- /dev/null +++ b/vclib/render/shaders/vclib/bgfx/drawable/uniforms/cross_section_uniforms.sh @@ -0,0 +1,78 @@ +/***************************************************************************** + * VCLib * + * Visual Computing Library * + * * + * Copyright(C) 2021-2026 * + * Visual Computing Lab * + * ISTI - Italian National Research Council * + * * + * All rights reserved. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the Mozilla Public License Version 2.0 as published * + * by the Mozilla Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Mozilla Public License Version 2.0 * + * (https://www.mozilla.org/en-US/MPL/2.0/) for more details. * + ****************************************************************************/ + +#ifndef VCL_EXT_BGFX_UNIFORMS_CROSS_SECTION_UNIFORMS_SH +#define VCL_EXT_BGFX_UNIFORMS_CROSS_SECTION_UNIFORMS_SH + +uniform vec4 u_crossSectionMinData; +uniform vec4 u_crossSectionMaxData; + +#define u_crossSectionPerFragment bool(u_crossSectionMinData.w != 0.0) + +#define u_crossSectionMinX u_crossSectionMinData.x +#define u_crossSectionMinY u_crossSectionMinData.y +#define u_crossSectionMinZ u_crossSectionMinData.z + +#define u_crossSectionMaxX u_crossSectionMaxData.x +#define u_crossSectionMaxY u_crossSectionMaxData.y +#define u_crossSectionMaxZ u_crossSectionMaxData.z + +/** + * Given the world position of the vertex, compute and returns the discard flag + * for the cross section test (1.0 if vertex should be discarded, 0.0 otherwise) + * + * This macro is meant to be used in the vertex shader. The result of this test + * can be passed to the fragment shader as a varying, so that the fragment + * shader can also discard fragments based on the same test + * + * pos: the world position of the vertex (vec3) + * returns: the discard flag (float) + */ +#define computeDiscardFlag(pos) ( \ + (!u_crossSectionPerFragment && ( \ + (pos).x < u_crossSectionMinX || (pos).x > u_crossSectionMaxX || \ + (pos).y < u_crossSectionMinY || (pos).y > u_crossSectionMaxY || \ + (pos).z < u_crossSectionMinZ || (pos).z > u_crossSectionMaxZ \ + )) ? 1.0 : 0.0 \ +) + +/** + * Given the discard flag computed by computeDiscardFlag and the world position + * of the fragment, discard the fragment if the flag is greater than 0.0 or if + * the fragment is outside the cross section bounds (if + * u_crossSectionPerFragment is true). + * + * This macro is meant to be used in the fragment shader. + * + * flag: the discard flag computed by computeDiscardFlag and passed as a varying + * from the vertex shader (float) + * pos: the world position of the fragment (vec3) + */ +#define discardIfCrossSectionClipped(flag, pos) \ + if (flag > 0.0) discard; \ + else if (u_crossSectionPerFragment && ( \ + (pos).x < u_crossSectionMinX || (pos).x > u_crossSectionMaxX || \ + (pos).y < u_crossSectionMinY || (pos).y > u_crossSectionMaxY || \ + (pos).z < u_crossSectionMinZ || (pos).z > u_crossSectionMaxZ \ + )) discard; + +#endif // VCL_EXT_BGFX_UNIFORMS_CROSS_SECTION_UNIFORMS_SH diff --git a/vclib/render/shaders/vclib/bgfx/primitives/lines/fs_lines_id_in.sh b/vclib/render/shaders/vclib/bgfx/primitives/lines/fs_lines_id_in.sh index 989b443e36..4075d29003 100644 --- a/vclib/render/shaders/vclib/bgfx/primitives/lines/fs_lines_id_in.sh +++ b/vclib/render/shaders/vclib/bgfx/primitives/lines/fs_lines_id_in.sh @@ -7,9 +7,17 @@ $input v_color, v_normal, v_selected +// cross section +$input v_worldPos0, v_worldPos1, v_discardFlag, v_t + +#include #include #include void main() { +#if LINES_ID_SECTION_ON + vec3 fragPos = mix(v_worldPos0, v_worldPos1, v_t); + discardIfCrossSectionClipped(v_discardFlag, fragPos); +#endif gl_FragColor = u_linesId; } diff --git a/vclib/render/shaders/vclib/bgfx/primitives/lines/fs_lines_in.sh b/vclib/render/shaders/vclib/bgfx/primitives/lines/fs_lines_in.sh index 6c6d74ba67..6036c24c5e 100644 --- a/vclib/render/shaders/vclib/bgfx/primitives/lines/fs_lines_in.sh +++ b/vclib/render/shaders/vclib/bgfx/primitives/lines/fs_lines_in.sh @@ -7,14 +7,20 @@ $input v_color, v_normal, v_selected -#include -#if LINES_SHADING_PER_VERTEX || LINES_SHADING_PER_LINE +// cross section +$input v_worldPos0, v_worldPos1, v_discardFlag, v_t + +#include #include -#endif #include #include void main() { +#if LINES_SECTION_ON + vec3 fragPos = mix(v_worldPos0, v_worldPos1, v_t); + discardIfCrossSectionClipped(v_discardFlag, fragPos); +#endif + vec4 color = v_color; #if LINES_SHADING_PER_VERTEX || LINES_SHADING_PER_LINE diff --git a/vclib/render/shaders/vclib/bgfx/primitives/lines/lines.config b/vclib/render/shaders/vclib/bgfx/primitives/lines/lines.config index 0dd0967797..ef3c43744a 100644 --- a/vclib/render/shaders/vclib/bgfx/primitives/lines/lines.config +++ b/vclib/render/shaders/vclib/bgfx/primitives/lines/lines.config @@ -31,6 +31,10 @@ DIM_BOTH SELECTION OFF ON +DIM_BOTH SECTION + OFF + ON + ENUM_PREFIX PRIMITIVE_LINES_ID DEFINE_PREFIX LINES @@ -53,3 +57,7 @@ DIM_VS TOPO DIM_VS COLOR GENERAL + +DIM_BOTH SECTION + OFF + ON diff --git a/vclib/render/shaders/vclib/bgfx/primitives/lines/varying.def.sc b/vclib/render/shaders/vclib/bgfx/primitives/lines/varying.def.sc index 77517576d2..b4f27fd7e8 100644 --- a/vclib/render/shaders/vclib/bgfx/primitives/lines/varying.def.sc +++ b/vclib/render/shaders/vclib/bgfx/primitives/lines/varying.def.sc @@ -8,3 +8,8 @@ vec4 v_color : COLOR0; vec3 v_normal : NORMAL; flat float v_selected : TEXCOORD0 = 0.0; + +vec3 v_worldPos0 : TEXCOORD2 = vec3(0.0, 0.0, 0.0); +vec3 v_worldPos1 : TEXCOORD3 = vec3(0.0, 0.0, 0.0); +float v_discardFlag : TEXCOORD4 = 0; +float v_t : TEXCOORD5 = 0; // interpolation parameter along segment diff --git a/vclib/render/shaders/vclib/bgfx/primitives/lines/vs_lines_in.sh b/vclib/render/shaders/vclib/bgfx/primitives/lines/vs_lines_in.sh index 1cbb076539..de67114639 100644 --- a/vclib/render/shaders/vclib/bgfx/primitives/lines/vs_lines_in.sh +++ b/vclib/render/shaders/vclib/bgfx/primitives/lines/vs_lines_in.sh @@ -7,7 +7,11 @@ $output v_color, v_normal, v_selected +// cross section +$output v_worldPos0, v_worldPos1, v_discardFlag, v_t + #include +#include #include #include #include @@ -67,6 +71,12 @@ void main() { vec3 p0 = getVertexPos(vertexIndex0); vec3 p1 = getVertexPos(vertexIndex1); +#if defined(LINES_SECTION_ON) || defined(LINES_ID_SECTION_ON) + // compute initial world positions + vec3 worldP0 = mul(u_model[0], vec4(p0, 1.0)).xyz; + vec3 worldP1 = mul(u_model[0], vec4(p1, 1.0)).xyz; +#endif + vec4 p0_NDC = mul(u_modelViewProj, vec4(p0, 1.0)); vec4 p1_NDC = mul(u_modelViewProj, vec4(p1, 1.0)); @@ -103,18 +113,28 @@ void main() { vec4 clippedColor1 = c1; vec3 clippedNormal0 = n0_NDC; vec3 clippedNormal1 = n1_NDC; +#if defined(LINES_SECTION_ON) || defined(LINES_ID_SECTION_ON) + vec3 clippedWorldP0 = worldP0; + vec3 clippedWorldP1 = worldP1; +#endif if (p0_NDC.w < NEAR_EPSILON) { float t = (NEAR_EPSILON - p0_NDC.w) / (p1_NDC.w - p0_NDC.w); clippedP0 = mix(p0_NDC, p1_NDC, t); clippedColor0 = mix(c0, c1, t); clippedNormal0 = mix(n0_NDC, n1_NDC, t); +#if defined(LINES_SECTION_ON) || defined(LINES_ID_SECTION_ON) + clippedWorldP0 = mix(worldP0, worldP1, t); +#endif } if (p1_NDC.w < NEAR_EPSILON) { float t = (NEAR_EPSILON - p1_NDC.w) / (p0_NDC.w - p1_NDC.w); clippedP1 = mix(p1_NDC, p0_NDC, t); clippedColor1 = mix(c1, c0, t); clippedNormal1 = mix(n1_NDC, n0_NDC, t); +#if defined(LINES_SECTION_ON) || defined(LINES_ID_SECTION_ON) + clippedWorldP1 = mix(worldP1, worldP0, t); +#endif } const vec2 offsets[6] = { @@ -149,6 +169,18 @@ void main() { v_color = color; v_normal = normal; +#if defined(LINES_SECTION_ON) || defined(LINES_ID_SECTION_ON) + v_worldPos0 = clippedWorldP0; + v_worldPos1 = clippedWorldP1; + v_t = uv.x; + // discard flag - 1.0 if either endpoint is outside the cross section + // (only used in per-vertex mode; in per-fragment mode computeDiscardFlag returns 0.0) + v_discardFlag = + max(computeDiscardFlag(v_worldPos0), computeDiscardFlag(v_worldPos1)); +#else + v_discardFlag = 1.0; +#endif + // Apply depth offset in clip space. // We scale the offset by w to maintain it consistently after the perspective divide. p.z += -u_depthOffset * p.w; diff --git a/vclib/render/shaders/vclib/bgfx/primitives/points/fs_points_id_in.sh b/vclib/render/shaders/vclib/bgfx/primitives/points/fs_points_id_in.sh index d2768d89f8..f2ecf5d896 100644 --- a/vclib/render/shaders/vclib/bgfx/primitives/points/fs_points_id_in.sh +++ b/vclib/render/shaders/vclib/bgfx/primitives/points/fs_points_id_in.sh @@ -6,12 +6,18 @@ // obtain one at https://mozilla.org/MPL/2.0/. $input v_normal, v_texcoord0, v_color, v_selected +$input v_worldPos, v_discardFlag +#include #include #include void main() { +#if POINTS_ID_SECTION_ON + discardIfCrossSectionClipped(v_discardFlag, v_worldPos); +#endif + #if POINTS_ID_SHAPE_CIRCLE // circle mode (if outside of the circle, discard) vec2 uv = v_texcoord0 * 2.0 - vec2(1.0, 1.0); diff --git a/vclib/render/shaders/vclib/bgfx/primitives/points/fs_points_in.sh b/vclib/render/shaders/vclib/bgfx/primitives/points/fs_points_in.sh index e80643e845..1af888788d 100644 --- a/vclib/render/shaders/vclib/bgfx/primitives/points/fs_points_in.sh +++ b/vclib/render/shaders/vclib/bgfx/primitives/points/fs_points_in.sh @@ -6,13 +6,19 @@ // obtain one at https://mozilla.org/MPL/2.0/. $input v_normal, v_texcoord0, v_color, v_selected +$input v_worldPos, v_discardFlag +#include #include #include #include void main() { +#if POINTS_SECTION_ON + discardIfCrossSectionClipped(v_discardFlag, v_worldPos); +#endif + vec4 color = v_color; #if POINTS_SHAPE_CIRCLE diff --git a/vclib/render/shaders/vclib/bgfx/primitives/points/points.config b/vclib/render/shaders/vclib/bgfx/primitives/points/points.config index 3132b9f428..677fadb7ee 100644 --- a/vclib/render/shaders/vclib/bgfx/primitives/points/points.config +++ b/vclib/render/shaders/vclib/bgfx/primitives/points/points.config @@ -25,6 +25,10 @@ DIM_BOTH SELECTION OFF ON +DIM_BOTH SECTION + OFF + ON + ENUM_PREFIX PRIMITIVE_POINTS_ID DEFINE_PREFIX POINTS @@ -43,3 +47,7 @@ DIM_VS COLOR DIM_FS SHAPE SQUARE CIRCLE + +DIM_BOTH SECTION + OFF + ON diff --git a/vclib/render/shaders/vclib/bgfx/primitives/points/varying.def.sc b/vclib/render/shaders/vclib/bgfx/primitives/points/varying.def.sc index 50c4b0442c..2bdf571b2a 100644 --- a/vclib/render/shaders/vclib/bgfx/primitives/points/varying.def.sc +++ b/vclib/render/shaders/vclib/bgfx/primitives/points/varying.def.sc @@ -5,7 +5,9 @@ // v. 2.0. If a copy of the MPL was not distributed with this file, You can // obtain one at https://mozilla.org/MPL/2.0/. -vec3 v_normal : NORMAL = vec3(0.0, 0.0, 0.0); -vec2 v_texcoord0 : TEXCOORD0; -flat vec4 v_color : COLOR0; -flat float v_selected : TEXCOORD1 = 0.0; +vec3 v_normal : NORMAL = vec3(0.0, 0.0, 0.0); +vec2 v_texcoord0 : TEXCOORD0; +flat vec4 v_color : COLOR0; +flat float v_selected : TEXCOORD1 = 0.0; +vec3 v_worldPos : TEXCOORD2 = vec3(0.0, 0.0, 0.0); +flat float v_discardFlag : TEXCOORD3 = 1.0; diff --git a/vclib/render/shaders/vclib/bgfx/primitives/points/vs_points_in.sh b/vclib/render/shaders/vclib/bgfx/primitives/points/vs_points_in.sh index f0511c9c62..c745da53ee 100644 --- a/vclib/render/shaders/vclib/bgfx/primitives/points/vs_points_in.sh +++ b/vclib/render/shaders/vclib/bgfx/primitives/points/vs_points_in.sh @@ -6,9 +6,11 @@ // obtain one at https://mozilla.org/MPL/2.0/. $output v_normal, v_texcoord0, v_color, v_selected +$output v_worldPos, v_discardFlag #include #include +#include #include BUFFER_RAW_RO(pointsBuffer, 0); // 3D point positions @@ -75,4 +77,11 @@ void main() #if POINTS_SELECTION_ON v_selected = float(getBoolFromBuffer(vertexSelected, pointIndex)); #endif + +#if defined(POINTS_SECTION_ON) || defined(POINTS_ID_SECTION_ON) + v_worldPos = mul(u_model[0], vec4(centerPos, 1.0)).xyz; + v_discardFlag = computeDiscardFlag(v_worldPos); +#else + v_discardFlag = 1.0; +#endif } diff --git a/vclib/render/src/vclib/bgfx/primitives/lines.cpp b/vclib/render/src/vclib/bgfx/primitives/lines.cpp index 355516d14b..b4a109d9ea 100644 --- a/vclib/render/src/vclib/bgfx/primitives/lines.cpp +++ b/vclib/render/src/vclib/bgfx/primitives/lines.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -127,6 +128,19 @@ void Lines::draw(bgfx::ViewId viewId) const 0 | BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_WRITE_Z | BGFX_STATE_DEPTH_TEST_LESS | BGFX_STATE_BLEND_ALPHA); LinesUniforms::bind(); + + if (mCrossSectionSettings.isEnabled()) { + using enum CrossSectionSettings::CrossSectionType; + CrossSectionUniforms::set( + mCrossSectionSettings.lower(), + mCrossSectionSettings.upper(), + mCrossSectionSettings.type() == PER_FRAGMENT); + } + else { + CrossSectionUniforms::set(); + } + CrossSectionUniforms::bind(); + bgfx::submit(viewId, mProgram); } @@ -155,6 +169,19 @@ void Lines::drawId(bgfx::ViewId viewId, uint32_t id) const BGFX_STATE_DEPTH_TEST_LEQUAL | BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_ZERO)); LinesUniforms::bind(); + + if (mCrossSectionSettings.isEnabled()) { + using enum CrossSectionSettings::CrossSectionType; + CrossSectionUniforms::set( + mCrossSectionSettings.lower(), + mCrossSectionSettings.upper(), + mCrossSectionSettings.type() == PER_FRAGMENT); + } + else { + CrossSectionUniforms::set(); + } + CrossSectionUniforms::bind(); + bgfx::submit(viewId, mIdProgram); } @@ -299,23 +326,26 @@ bgfx::ProgramHandle Lines::linesProgramSelector() const constexpr uint N_TOPO_MODES = 2; constexpr uint N_COLOR_MODES = 3; constexpr uint N_SELECTION_MODES = 2; + constexpr uint N_SECTION_MODES = 2; uint shading = toUnderlying(mShading); uint indices = mIndices.isValid() ? 0 : 1; uint topology = toUnderlying(mTopology); uint color = toUnderlying(mColorSetting); - uint select = mSelectionVisibility ? 1 : 0; + uint select = mSelectionVisibility ? 1 : 0; + uint section = mCrossSectionSettings.isEnabled() ? 1 : 0; // the first shader of all the combinations uint base = toUnderlying( - PRIMITIVE_LINES_SHADING_NONE_INDICES_ON_TOPO_LINES_COLOR_PER_VERTEX_SELECTION_OFF); + PRIMITIVE_LINES_SHADING_NONE_INDICES_ON_TOPO_LINES_COLOR_PER_VERTEX_SELECTION_OFF_SECTION_OFF); uint offset = linearizeIndex< N_SHADING_MODES, N_INDEX_MODES, N_TOPO_MODES, N_COLOR_MODES, - N_SELECTION_MODES>(shading, indices, topology, color, select); + N_SELECTION_MODES, + N_SECTION_MODES>(shading, indices, topology, color, select, section); uint program = base + offset; @@ -329,16 +359,18 @@ bgfx::ProgramHandle Lines::linesIdProgramSelector() const constexpr uint N_INDEX_MODES = 2; constexpr uint N_TOPO_MODES = 2; + constexpr uint N_SECTION_MODES = 2; uint indices = mIndices.isValid() ? 0 : 1; uint topology = toUnderlying(mTopology); + uint section = mCrossSectionSettings.isEnabled() ? 1 : 0; // the first shader of all the combinations uint base = toUnderlying( - PRIMITIVE_LINES_ID_SHADING_NONE_INDICES_ON_TOPO_LINES_COLOR_GENERAL); + PRIMITIVE_LINES_ID_SHADING_NONE_INDICES_ON_TOPO_LINES_COLOR_GENERAL_SECTION_OFF); uint offset = - linearizeIndex(indices, topology); + linearizeIndex(indices, topology, section); uint program = base + offset; diff --git a/vclib/render/src/vclib/bgfx/primitives/points.cpp b/vclib/render/src/vclib/bgfx/primitives/points.cpp index 5a38b55335..df4bf42fe9 100644 --- a/vclib/render/src/vclib/bgfx/primitives/points.cpp +++ b/vclib/render/src/vclib/bgfx/primitives/points.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -163,6 +164,19 @@ void Points::draw(bgfx::ViewId viewId) const // Bind the updated uniforms to the shader stage. PointsUniforms::bind(); + // cross section settings + if (mSettings.crossSectionSettings.isEnabled()) { + using enum CrossSectionSettings::CrossSectionType; + CrossSectionUniforms::set( + mSettings.crossSectionSettings.lower(), + mSettings.crossSectionSettings.upper(), + mSettings.crossSectionSettings.type() == PER_FRAGMENT); + } + else { + CrossSectionUniforms::set(); + } + CrossSectionUniforms::bind(); + bgfx::submit(viewId, mProgram); } @@ -201,6 +215,19 @@ void Points::drawId(bgfx::ViewId viewId, uint32_t id) const // Bind the updated uniforms to the shader stage. PointsUniforms::bind(); + // cross section settings + if (mSettings.crossSectionSettings.isEnabled()) { + using enum CrossSectionSettings::CrossSectionType; + CrossSectionUniforms::set( + mSettings.crossSectionSettings.lower(), + mSettings.crossSectionSettings.upper(), + mSettings.crossSectionSettings.type() == PER_FRAGMENT); + } + else { + CrossSectionUniforms::set(); + } + CrossSectionUniforms::bind(); + bgfx::submit(viewId, mIdProgram); } @@ -273,21 +300,24 @@ bgfx::ProgramHandle Points::pointsProgramSelector() const constexpr uint N_COLOR_MODES = 2; constexpr uint N_SHAPE_MODES = 2; constexpr uint N_SELECTION_MODES = 2; + constexpr uint N_SECTION_MODES = 2; uint shading = toUnderlying(mSettings.shading); uint color = toUnderlying(mSettings.colorSetting); uint shape = toUnderlying(mSettings.shape); uint select = mSettings.selectionVisibility ? 1 : 0; + uint section = mSettings.crossSectionSettings.isEnabled() ? 1 : 0; // the first shader of all the combinations uint base = toUnderlying( - PRIMITIVE_POINTS_SHADING_NONE_COLOR_GENERAL_SHAPE_SQUARE_SELECTION_OFF); + PRIMITIVE_POINTS_SHADING_NONE_COLOR_GENERAL_SHAPE_SQUARE_SELECTION_OFF_SECTION_OFF); uint offset = linearizeIndex< N_SHADING_MODES, N_COLOR_MODES, N_SHAPE_MODES, - N_SELECTION_MODES>(shading, color, shape, select); + N_SELECTION_MODES, + N_SECTION_MODES>(shading, color, shape, select, section); uint program = base + offset; @@ -305,13 +335,20 @@ bgfx::ProgramHandle Points::pointsIdProgramSelector() const { using enum VertFragProgram; - uint shape = toUnderlying(mSettings.shape); + constexpr uint N_SHAPE_MODES = 2; + constexpr uint N_SECTION_MODES = 2; + + uint shape = toUnderlying(mSettings.shape); + uint section = mSettings.crossSectionSettings.isEnabled() ? 1 : 0; // the first shader of all the combinations uint base = toUnderlying( - PRIMITIVE_POINTS_ID_SHADING_NONE_COLOR_GENERAL_SHAPE_SQUARE); + PRIMITIVE_POINTS_ID_SHADING_NONE_COLOR_GENERAL_SHAPE_SQUARE_SECTION_OFF); - uint program = base + shape; + uint offset = + linearizeIndex(shape, section); + + uint program = base + offset; ProgramManager& pm = Context::instance().programManager(); return pm.getProgram(VertFragProgram(program)); diff --git a/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame.cpp b/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame.cpp index 8d9a9065a0..a0b2f703eb 100644 --- a/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame.cpp +++ b/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame.cpp @@ -28,16 +28,16 @@ MeshRenderSettingsFrame::MeshRenderSettingsFrame(QWidget* parent) : mUI->tabWidget->tabBar()->setExpanding(false); - auto setupTab = [this]( - int index, - GenericMeshRenderSettingsFrame* frame, - const QString& title) { + auto addTabWithCheckbox = [this]( + int index, + QWidget* frame, + QCheckBox* origCb, + const QString& title) { mUI->tabWidget->addTab(frame, title); QCheckBox* tabCb = new QCheckBox(); tabCb->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); mUI->tabWidget->tabBar()->setTabButton(index, QTabBar::LeftSide, tabCb); - QCheckBox* origCb = frame->visibilityCheckBox(); origCb->hide(); connect(origCb, &QCheckBox::toggled, tabCb, [tabCb](bool checked) { @@ -48,20 +48,41 @@ MeshRenderSettingsFrame::MeshRenderSettingsFrame(QWidget* parent) : if (origCb->isChecked() != checked) origCb->setChecked(checked); }); + }; - frames.push_back(frame); + auto setupGenericTab = [&](int index, + GenericMeshRenderSettingsFrame* frame, + const QString& title) { + addTabWithCheckbox(index, frame, frame->visibilityCheckBox(), title); + mFrames.push_back(frame); }; - setupTab(0, new PointsFrame(mMRS, this), "Points"); - setupTab(1, new SurfaceFrame(mMRS, this), "Surface"); - setupTab(2, new WireframeFrame(mMRS, this), "Wireframe"); - setupTab(3, new EdgesFrame(mMRS, this), "Edges"); + setupGenericTab(0, new PointsFrame(mMRS, this), "Points"); + setupGenericTab(1, new SurfaceFrame(mMRS, this), "Surface"); + setupGenericTab(2, new WireframeFrame(mMRS, this), "Wireframe"); + setupGenericTab(3, new EdgesFrame(mMRS, this), "Edges"); - for (auto* frame : frames) { + for (auto* frame : mFrames) { connect( - frame, SIGNAL(settingsUpdated()), this, SIGNAL(settingsUpdated())); + frame, + SIGNAL(meshRenderSettingsUpdated()), + this, + SIGNAL(meshRenderSettingsUpdated())); } + mCrossSectionFrame = new CrossSectionSettingsFrame(this); + addTabWithCheckbox( + 4, + mCrossSectionFrame, + mCrossSectionFrame->visibilityCheckBox(), + "Cross Section"); + + connect( + mCrossSectionFrame, + SIGNAL(crossSectionSettingsUpdated()), + this, + SIGNAL(crossSectionSettingsUpdated())); + connect( mUI->applyToAllCheckBox, SIGNAL(toggled(bool)), @@ -79,6 +100,12 @@ const MeshRenderSettings& MeshRenderSettingsFrame::meshRenderSettings() const return mMRS; } +const CrossSectionSettings& MeshRenderSettingsFrame::crossSectionSettings() + const +{ + return mCrossSectionFrame->crossSectionSettings(); +} + bool MeshRenderSettingsFrame::isApplyToAllEnabled() const { return mUI->applyToAllCheckBox->isChecked(); @@ -104,11 +131,17 @@ void MeshRenderSettingsFrame::setMeshRenderSettings( updateGuiFromSettings(changeCurrentTab); } +void MeshRenderSettingsFrame::setCrossSectionSettings( + const CrossSectionSettings& settings) +{ + mCrossSectionFrame->setCrossSectionSettings(settings); +} + void MeshRenderSettingsFrame::updateGuiFromSettings(bool changeCurrentTab) { using MRI = MeshRenderInfo; - for (auto* frame : frames) { + for (auto* frame : mFrames) { frame->updateFrameFromSettings(); } diff --git a/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame.ui b/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame.ui index 6fc41a644b..177eb2ae04 100644 --- a/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame.ui +++ b/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame.ui @@ -19,19 +19,7 @@ Frame - - - 0 - - - 0 - - - 0 - - - 0 - + diff --git a/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/cross_section_settings_frame.cpp b/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/cross_section_settings_frame.cpp new file mode 100644 index 0000000000..54e84438ce --- /dev/null +++ b/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/cross_section_settings_frame.cpp @@ -0,0 +1,343 @@ +/***************************************************************************** + * VCLib * + * Visual Computing Library * + * * + * Copyright(C) 2021-2026 * + * Visual Computing Lab * + * ISTI - Italian National Research Council * + * * + * All rights reserved. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the Mozilla Public License Version 2.0 as published * + * by the Mozilla Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Mozilla Public License Version 2.0 * + * (https://www.mozilla.org/en-US/MPL/2.0/) for more details. * + ****************************************************************************/ + +#include + +#include "ui_cross_section_settings_frame.h" + +namespace vcl::qt { + +CrossSectionSettingsFrame::CrossSectionSettingsFrame( + const CrossSectionSettings& css, + QWidget* parent) : + QFrame(parent), mUI(new Ui::CrossSectionSettingsFrame), mCSS(css) +{ + mUI->setupUi(this); + + mFloatSliders[X] = mUI->xFloatRangeSlider; + mFloatSliders[Y] = mUI->yFloatRangeSlider; + mFloatSliders[Z] = mUI->zFloatRangeSlider; + + mMinSpinBoxes[X] = mUI->xMinSpinBox; + mMinSpinBoxes[Y] = mUI->yMinSpinBox; + mMinSpinBoxes[Z] = mUI->zMinSpinBox; + + mMaxSpinBoxes[X] = mUI->xMaxSpinBox; + mMaxSpinBoxes[Y] = mUI->yMaxSpinBox; + mMaxSpinBoxes[Z] = mUI->zMaxSpinBox; + + connect( + mUI->crossSectionEnabledCheckBox, + SIGNAL(checkStateChanged(Qt::CheckState)), + this, + SLOT(onCrossSectionEnabledChanged(Qt::CheckState))); + + connect( + mUI->perVertexRadioButton, + SIGNAL(toggled(bool)), + this, + SLOT(onPerVertexToggled(bool))); + + connect( + mUI->perFragmentRadioButton, + SIGNAL(toggled(bool)), + this, + SLOT(onPerFragmentToggled(bool))); + + connect( + mUI->stepSpinBox, + SIGNAL(valueChanged(int)), + this, + SLOT(onStepSpinBoxValueChanged(int))); + + for (uint i = X; i < AXIS_COUNT; ++i) { + connect( + mFloatSliders[i], + &FloatRangeSlider::lowerValueChanged, + [this, i](float value) { + onFloatRangeSliderLowerValueChanged( + static_cast(i), value); + }); + + connect( + mFloatSliders[i], + &FloatRangeSlider::upperValueChanged, + [this, i](float value) { + onFloatRangeSliderUpperValueChanged( + static_cast(i), value); + }); + + connect( + mMinSpinBoxes[i], + &QDoubleSpinBox::valueChanged, + [this, i](double value) { + onMinSpinBoxValueChanged(static_cast(i), value); + }); + + connect( + mMaxSpinBoxes[i], + &QDoubleSpinBox::valueChanged, + [this, i](double value) { + onMaxSpinBoxValueChanged(static_cast(i), value); + }); + } + + onStepSpinBoxValueChanged(mUI->stepSpinBox->value()); + + updateFrameFromSettings(); +} + +CrossSectionSettingsFrame::CrossSectionSettingsFrame(QWidget* parent) : + CrossSectionSettingsFrame(CrossSectionSettings(), parent) +{ +} + +CrossSectionSettingsFrame::~CrossSectionSettingsFrame() +{ + delete mUI; +} + +const CrossSectionSettings& CrossSectionSettingsFrame::crossSectionSettings() + const +{ + return mCSS; +} + +QCheckBox* CrossSectionSettingsFrame::visibilityCheckBox() const +{ + return mUI->crossSectionEnabledCheckBox; +} + +void CrossSectionSettingsFrame::setCrossSectionSettings( + const CrossSectionSettings& settings) +{ + mCSS = settings; + updateFrameFromSettings(); +} + +void CrossSectionSettingsFrame::updateFrameFromSettings() +{ + using enum CrossSectionSettings::CrossSectionType; + + // checkbox + mUI->crossSectionEnabledCheckBox->blockSignals(true); + + if (mCSS.type() == NONE) { + mUI->crossSectionEnabledCheckBox->setCheckState( + Qt::CheckState::Unchecked); + mUI->controlsFrame->setEnabled(false); + } + else { + mUI->crossSectionEnabledCheckBox->setCheckState(Qt::CheckState::Checked); + mUI->controlsFrame->setEnabled(true); + } + + mUI->crossSectionEnabledCheckBox->blockSignals(false); + + // radio buttons + mUI->perVertexRadioButton->blockSignals(true); + mUI->perFragmentRadioButton->blockSignals(true); + + if (mCSS.type() == PER_VERTEX) + mUI->perVertexRadioButton->setChecked(true); + else if (mCSS.type() == PER_FRAGMENT) + mUI->perFragmentRadioButton->setChecked(true); + + mUI->perVertexRadioButton->blockSignals(false); + mUI->perFragmentRadioButton->blockSignals(false); + + // sliders and spinboxes + const auto& bb = mCSS.boundingBox(); + const auto& l = mCSS.lower(); + const auto& u = mCSS.upper(); + + if (!bb.isNull()) { + float minSize = std::numeric_limits::max(); + for (int i=0; i<3; ++i) { + if (bb.dim(i) > 1e-6f && bb.dim(i) < minSize) { + minSize = bb.dim(i); + } + } + if (minSize != std::numeric_limits::max()) { + int order = static_cast(std::floor(std::log10(minSize))); + int maxExp = order - 1; + mUI->stepSpinBox->setMaximum(maxExp); + } + } + + for (uint i = X; i < AXIS_COUNT; ++i) { + updateSlider( + mFloatSliders[i], + mMinSpinBoxes[i], + mMaxSpinBoxes[i], + bb.min()[i], + bb.max()[i], + l[i], + u[i]); + } +} + +void CrossSectionSettingsFrame::updateSlider( + FloatRangeSlider* slider, + QDoubleSpinBox* minSpinBox, + QDoubleSpinBox* maxSpinBox, + float min, + float max, + float lower, + float upper) +{ + slider->blockSignals(true); + + slider->setRange(min, max); + slider->setLowerValue(lower); + slider->setUpperValue(upper); + + slider->blockSignals(false); + + minSpinBox->blockSignals(true); + + minSpinBox->setMinimum(min); + minSpinBox->setMaximum(upper); + minSpinBox->setValue(lower); + + minSpinBox->blockSignals(false); + + maxSpinBox->blockSignals(true); + + maxSpinBox->setMinimum(lower); + maxSpinBox->setMaximum(max); + maxSpinBox->setValue(upper); + + maxSpinBox->blockSignals(false); +} + +void CrossSectionSettingsFrame::onCrossSectionEnabledChanged( + Qt::CheckState arg1) +{ + using enum CrossSectionSettings::CrossSectionType; + + if (arg1 != Qt::CheckState::Checked) { + mCSS.type() = NONE; + } + else { + if (mUI->perVertexRadioButton->isChecked()) + mCSS.type() = PER_VERTEX; + else + mCSS.type() = PER_FRAGMENT; + } + + mUI->controlsFrame->setEnabled(arg1 == Qt::CheckState::Checked); + emit crossSectionSettingsUpdated(); +} + +void CrossSectionSettingsFrame::onPerVertexToggled(bool checked) +{ + using enum CrossSectionSettings::CrossSectionType; + if (checked) { + mCSS.type() = PER_VERTEX; + emit crossSectionSettingsUpdated(); + } +} + +void CrossSectionSettingsFrame::onPerFragmentToggled(bool checked) +{ + using enum CrossSectionSettings::CrossSectionType; + if (checked) { + mCSS.type() = PER_FRAGMENT; + emit crossSectionSettingsUpdated(); + } +} + +void CrossSectionSettingsFrame::onFloatRangeSliderLowerValueChanged( + Axis axis, + float value) +{ + auto l = mCSS.lower(); + l[axis] = value; + mCSS.setLower(l); + + mMinSpinBoxes[axis]->blockSignals(true); + mMinSpinBoxes[axis]->setValue(value); + mMinSpinBoxes[axis]->blockSignals(false); + + mMaxSpinBoxes[axis]->blockSignals(true); + mMaxSpinBoxes[axis]->setMinimum(value); + mMaxSpinBoxes[axis]->blockSignals(false); + + emit crossSectionSettingsUpdated(); +} + +void CrossSectionSettingsFrame::onFloatRangeSliderUpperValueChanged( + Axis axis, + float value) +{ + auto u = mCSS.upper(); + u[axis] = value; + mCSS.setUpper(u); + + mMaxSpinBoxes[axis]->blockSignals(true); + mMaxSpinBoxes[axis]->setValue(value); + mMaxSpinBoxes[axis]->blockSignals(false); + + mMinSpinBoxes[axis]->blockSignals(true); + mMinSpinBoxes[axis]->setMaximum(value); + mMinSpinBoxes[axis]->blockSignals(false); + + emit crossSectionSettingsUpdated(); +} + +void CrossSectionSettingsFrame::onMinSpinBoxValueChanged( + Axis axis, + double value) +{ + mFloatSliders[axis]->setLowerValue(static_cast(value)); +} + +void CrossSectionSettingsFrame::onMaxSpinBoxValueChanged( + Axis axis, + double value) +{ + mFloatSliders[axis]->setUpperValue(static_cast(value)); +} + +void CrossSectionSettingsFrame::onStepSpinBoxValueChanged(int exp) +{ + mStep = std::pow(10.0f, static_cast(exp)); + + int decimals = std::max(2, -exp + 1); + + for (uint i = X; i < AXIS_COUNT; ++i) { + mFloatSliders[i]->setStep(mStep); + + mMinSpinBoxes[i]->blockSignals(true); + mMinSpinBoxes[i]->setSingleStep(mStep); + mMinSpinBoxes[i]->setDecimals(decimals); + mMinSpinBoxes[i]->blockSignals(false); + + mMaxSpinBoxes[i]->blockSignals(true); + mMaxSpinBoxes[i]->setSingleStep(mStep); + mMaxSpinBoxes[i]->setDecimals(decimals); + mMaxSpinBoxes[i]->blockSignals(false); + } +} + +} // namespace vcl::qt diff --git a/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/cross_section_settings_frame.ui b/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/cross_section_settings_frame.ui new file mode 100644 index 0000000000..ebed3b32f8 --- /dev/null +++ b/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/cross_section_settings_frame.ui @@ -0,0 +1,293 @@ + + + vcl::qt::CrossSectionSettingsFrame + + + + 0 + 0 + 400 + 300 + + + + Frame + + + + + + + + + + + + + false + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Plain + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Per Vertex + + + true + + + crossSectionTypeButtonGroup + + + + + + + Per Fragment + + + crossSectionTypeButtonGroup + + + + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Plain + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + 0.010000000000000 + + + + + + + + 0 + 0 + + + + 0.010000000000000 + + + + + + + + 0 + 0 + + + + 0.010000000000000 + + + + + + + Qt::Orientation::Horizontal + + + + + + + + 0 + 0 + + + + Y: + + + + + + + + 0 + 0 + + + + Z: + + + + + + + 0.010000000000000 + + + + + + + 0.010000000000000 + + + + + + + 0.010000000000000 + + + + + + + Qt::Orientation::Horizontal + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + X: + + + + + + + Qt::Orientation::Horizontal + + + + + + + + + + + + Precision (10^x): + + + + + + + -6 + + + 6 + + + -2 + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + vcl::qt::FloatRangeSlider + QWidget +
vclib/qt/gui/float_range_slider.h
+ 1 +
+
+ + + + + +
diff --git a/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/edges_frame.cpp b/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/edges_frame.cpp index a1564dbf28..78b41e6982 100644 --- a/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/edges_frame.cpp +++ b/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/edges_frame.cpp @@ -144,14 +144,14 @@ void EdgesFrame::updateColorComboBoxFromSettings() void EdgesFrame::onVisibilityChanged(Qt::CheckState arg1) { mMRS.setEdges(VISIBLE, arg1 == Qt::CheckState::Checked); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } void EdgesFrame::onShadingSmoothToggled(bool checked) { if (checked) { mMRS.setEdges(SHADING_SMOOTH); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } } @@ -159,7 +159,7 @@ void EdgesFrame::onShadingFlatToggled(bool checked) { if (checked) { mMRS.setEdges(SHADING_FLAT); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } } @@ -167,7 +167,7 @@ void EdgesFrame::onShadingNoneToggled(bool checked) { if (checked) { mMRS.setEdges(SHADING_NONE); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } } @@ -180,19 +180,19 @@ void EdgesFrame::onColorComboBoxChanged(int index) case E_USER: mMRS.setEdges(COLOR_USER); break; } mUI->userColorFrame->setEnabled(index == E_USER); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } void EdgesFrame::onUserColorChanged(const QColor& c) { mMRS.setEdgesUserColor(c.redF(), c.greenF(), c.blueF(), c.alphaF()); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } void EdgesFrame::onSizeChanged(int value) { mMRS.setEdgesWidth(value); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } } // namespace vcl::qt diff --git a/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/points_frame.cpp b/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/points_frame.cpp index fa78baa32d..273deb6d38 100644 --- a/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/points_frame.cpp +++ b/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/points_frame.cpp @@ -165,14 +165,14 @@ void PointsFrame::updateColorComboBoxFromSettings() void PointsFrame::onVisibilityChanged(Qt::CheckState arg1) { mMRS.setPoints(VISIBLE, arg1 == Qt::CheckState::Checked); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } void PointsFrame::onShapeCircleToggled(bool checked) { if (checked) { mMRS.setPoints(SHAPE_CIRCLE); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } } @@ -180,7 +180,7 @@ void PointsFrame::onShapePixelToggled(bool checked) { if (checked) { mMRS.setPoints(SHAPE_PIXEL); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } } @@ -188,7 +188,7 @@ void PointsFrame::onShadingVertexToggled(bool checked) { if (checked) { mMRS.setPoints(SHADING_VERT); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } } @@ -196,7 +196,7 @@ void PointsFrame::onShadingNoneToggled(bool checked) { if (checked) { mMRS.setPoints(SHADING_NONE); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } } @@ -208,32 +208,32 @@ void PointsFrame::onColorComboBoxChanged(int index) case P_USER: mMRS.setPoints(COLOR_USER); break; } mUI->userColorFrame->setEnabled(index == P_USER); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } void PointsFrame::onUserColorChanged(const QColor& c) { mMRS.setPointsUserColor(c.redF(), c.greenF(), c.blueF(), c.alphaF()); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } void PointsFrame::onSizeChanged(int value) { mMRS.setPointsWidth(value); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } void PointsFrame::onSelectionVisibilityChanged(Qt::CheckState arg1) { mMRS.setPoints(SELECTION, arg1 == Qt::CheckState::Checked); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } void PointsFrame::onSelectionColorChanged(const QColor& c) { // alpha is always 0.5 mMRS.setPointSelectionColor(c.redF(), c.greenF(), c.blueF(), 0.5); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } } // namespace vcl::qt diff --git a/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/surface_frame.cpp b/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/surface_frame.cpp index 40ce4a1ccb..fb7689bba9 100644 --- a/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/surface_frame.cpp +++ b/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/surface_frame.cpp @@ -224,14 +224,14 @@ void SurfaceFrame::updateBackFaceRadioButtonsFromSettings() void SurfaceFrame::onVisibilityChanged(Qt::CheckState arg1) { mMRS.setSurface(VISIBLE, arg1 == Qt::CheckState::Checked); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } void SurfaceFrame::onShadingSmoothToggled(bool checked) { if (checked) { mMRS.setSurface(SHADING_SMOOTH); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } } @@ -239,7 +239,7 @@ void SurfaceFrame::onShadingFlatToggled(bool checked) { if (checked) { mMRS.setSurface(SHADING_FLAT); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } } @@ -247,7 +247,7 @@ void SurfaceFrame::onShadingNoneToggled(bool checked) { if (checked) { mMRS.setSurface(SHADING_NONE); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } } @@ -255,7 +255,7 @@ void SurfaceFrame::onShadingNormalMapToggled(bool checked) { if (checked) { mMRS.setSurface(SHADING_NORMAL_MAP); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } } @@ -270,33 +270,33 @@ void SurfaceFrame::onColorComboBoxChanged(int index) case SC_USER: mMRS.setSurface(COLOR_USER); break; } mUI->userColorFrame->setEnabled(index == SC_USER); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } void SurfaceFrame::onUserColorChanged(const QColor& c) { mMRS.setSurfaceUserColor(c.redF(), c.greenF(), c.blueF(), c.alphaF()); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } void SurfaceFrame::onSelectionVisibilityChanged(Qt::CheckState arg1) { mMRS.setSurface(SELECTION, arg1 == Qt::CheckState::Checked); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } void SurfaceFrame::onSelectionColorChanged(const QColor& c) { // alpha is always 0.5 mMRS.setSurfaceSelectionColor(c.redF(), c.greenF(), c.blueF(), 0.5); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } void SurfaceFrame::onBackFaceSingleToggled(bool checked) { if (checked) { mMRS.setSurface(BACKFACE_SINGLE, true); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } } @@ -304,7 +304,7 @@ void SurfaceFrame::onBackFaceDoubleToggled(bool checked) { if (checked) { mMRS.setSurface(BACKFACE_DOUBLE, true); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } } @@ -312,14 +312,14 @@ void SurfaceFrame::onBackFaceCullToggled(bool checked) { if (checked) { mMRS.setSurface(BACKFACE_CULL, true); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } } void SurfaceFrame::onSpecularChanged(Qt::CheckState arg1) { mMRS.setSurface(SPECULAR, arg1 == Qt::CheckState::Checked); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } } // namespace vcl::qt diff --git a/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/wireframe_frame.cpp b/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/wireframe_frame.cpp index e0814eb2fc..e1584ff2a7 100644 --- a/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/wireframe_frame.cpp +++ b/vclib/render/src/vclib/qt/gui/mesh_render_settings_frame/wireframe_frame.cpp @@ -126,14 +126,14 @@ void WireframeFrame::updateColorComboBoxFromSettings() void WireframeFrame::onVisibilityChanged(Qt::CheckState arg1) { mMRS.setWireframe(VISIBLE, arg1 == Qt::CheckState::Checked); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } void WireframeFrame::onShadingVertexToggled(bool checked) { if (checked) { mMRS.setWireframe(SHADING_VERT); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } } @@ -141,7 +141,7 @@ void WireframeFrame::onShadingNoneToggled(bool checked) { if (checked) { mMRS.setWireframe(SHADING_NONE); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } } @@ -153,19 +153,19 @@ void WireframeFrame::onColorComboBoxChanged(int index) case W_USER: mMRS.setWireframe(COLOR_USER); break; } mUI->userColorFrame->setEnabled(index == W_USER); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } void WireframeFrame::onUserColorChanged(const QColor& c) { mMRS.setWireframeUserColor(c.redF(), c.greenF(), c.blueF(), c.alphaF()); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } void WireframeFrame::onSizeChanged(int value) { mMRS.setWireframeWidth(value); - emit settingsUpdated(); + emit meshRenderSettingsUpdated(); } } // namespace vcl::qt diff --git a/vclib/render/src/vclib/qt/mesh_viewer.cpp b/vclib/render/src/vclib/qt/mesh_viewer.cpp index 5da93ca992..4c28a9ce63 100644 --- a/vclib/render/src/vclib/qt/mesh_viewer.cpp +++ b/vclib/render/src/vclib/qt/mesh_viewer.cpp @@ -127,10 +127,16 @@ MeshViewer::MeshViewer(QWidget* parent, const std::string& settingsFilePath) : // meshRenderSettingsUpdated() member function connect( mUI->meshRenderSettingsFrame, - SIGNAL(settingsUpdated()), + SIGNAL(meshRenderSettingsUpdated()), this, SLOT(meshRenderSettingsUpdated())); + connect( + mUI->meshRenderSettingsFrame, + SIGNAL(crossSectionSettingsUpdated()), + this, + SLOT(crossSectionSettingsUpdated())); + connect( mUI->meshRenderSettingsFrame, SIGNAL(applyToAllToggled(bool)), @@ -341,6 +347,21 @@ void MeshViewer::updateGUI() if (!mUI->meshRenderSettingsFrame->isApplyToAllEnabled()) { mUI->meshRenderSettingsFrame->setMeshRenderSettings( m->renderSettings(), true); + mUI->meshRenderSettingsFrame->setCrossSectionSettings( + m->crossSectionSettings()); + } + else { + vcl::Box3f globalBox; + for (uint j = 0; j < mDrawableObjectVector->size(); ++j) { + auto mesh = std::dynamic_pointer_cast( + mDrawableObjectVector->at(j)); + if (mesh) { + globalBox.add(mesh->crossSectionSettings().boundingBox()); + } + } + auto css = mUI->meshRenderSettingsFrame->crossSectionSettings(); + css.setBoundingBox(globalBox); + mUI->meshRenderSettingsFrame->setCrossSectionSettings(css); } mUI->meshRenderSettingsFrame->setEnabled(true); } @@ -450,6 +471,8 @@ void MeshViewer::visibilityDrawableObjectChanged() if (!mUI->meshRenderSettingsFrame->isApplyToAllEnabled()) { mUI->meshRenderSettingsFrame->setMeshRenderSettings( m->renderSettings()); + mUI->meshRenderSettingsFrame->setCrossSectionSettings( + m->crossSectionSettings()); } } mUI->viewer->update(); @@ -474,6 +497,8 @@ void MeshViewer::selectedDrawableObjectChanged(uint i) if (!mUI->meshRenderSettingsFrame->isApplyToAllEnabled()) { mUI->meshRenderSettingsFrame->setMeshRenderSettings( m->renderSettings()); + mUI->meshRenderSettingsFrame->setCrossSectionSettings( + m->crossSectionSettings()); } mUI->meshRenderSettingsFrame->setEnabled(true); } @@ -529,9 +554,57 @@ void MeshViewer::meshRenderSettingsUpdated() } } +void MeshViewer::crossSectionSettingsUpdated() +{ + // The user changed the CrossSectionSettings of the ith object. + uint i = mUI->drawVectorTree->selectedDrawableObject(); + if (i != UINT_NULL && mDrawableObjectVector->size() > 0) { + // The selected object must always be a AbstractDrawableMesh, because + // the CrossSectionSettingsFrame (which called this member function) is + // visible only when the selected Object is a AbstractDrawableMesh + auto m = std::dynamic_pointer_cast( + mDrawableObjectVector->at(i)); + if (m) { // just to be sure, but it should always be true + bool applyToAll = mUI->meshRenderSettingsFrame->isApplyToAllEnabled(); + const auto& newSettings = mUI->meshRenderSettingsFrame->crossSectionSettings(); + + if (applyToAll) { + for (uint j = 0; j < mDrawableObjectVector->size(); ++j) { + auto mesh = std::dynamic_pointer_cast( + mDrawableObjectVector->at(j)); + if (mesh) { + mesh->setCrossSectionSettings(newSettings); + } + } + } + else { + m->setCrossSectionSettings(newSettings); + } + mUI->viewer->update(); + } + } +} + void MeshViewer::applyToAllToggled(bool checked) { if (!checked) { + // Restore individual bounding boxes for all meshes + for (uint j = 0; j < mDrawableObjectVector->size(); ++j) { + auto mesh = std::dynamic_pointer_cast( + mDrawableObjectVector->at(j)); + if (mesh) { + auto css = mesh->crossSectionSettings(); + auto bbox = mesh->boundingBox().cast(); + if (!bbox.isNull()) { + float eps = bbox.diagonal() * 0.02f; + bbox.min() -= eps; + bbox.max() += eps; + } + css.setBoundingBox(bbox); + mesh->setCrossSectionSettings(css); + } + } + uint i = mUI->drawVectorTree->selectedDrawableObject(); if (i != UINT_NULL && mDrawableObjectVector->size() > 0) { auto m = std::dynamic_pointer_cast( @@ -539,11 +612,26 @@ void MeshViewer::applyToAllToggled(bool checked) if (m) { mUI->meshRenderSettingsFrame->setMeshRenderSettings( m->renderSettings()); + mUI->meshRenderSettingsFrame->setCrossSectionSettings( + m->crossSectionSettings()); } } } else { + vcl::Box3f globalBox; + for (uint j = 0; j < mDrawableObjectVector->size(); ++j) { + auto mesh = std::dynamic_pointer_cast( + mDrawableObjectVector->at(j)); + if (mesh) { + globalBox.add(mesh->crossSectionSettings().boundingBox()); + } + } + auto css = mUI->meshRenderSettingsFrame->crossSectionSettings(); + css.setBoundingBox(globalBox); + mUI->meshRenderSettingsFrame->setCrossSectionSettings(css); + meshRenderSettingsUpdated(); + crossSectionSettingsUpdated(); } }