diff --git a/assets/ground_truth/007-info-editor-headless_face_info_gt.png b/assets/ground_truth/007-info-editor-headless_face_info_gt.png new file mode 100644 index 0000000000..67920ccef8 Binary files /dev/null and b/assets/ground_truth/007-info-editor-headless_face_info_gt.png differ diff --git a/assets/ground_truth/007-info-editor-headless_poly_info_gt.png b/assets/ground_truth/007-info-editor-headless_poly_info_gt.png new file mode 100644 index 0000000000..c73cbc633f Binary files /dev/null and b/assets/ground_truth/007-info-editor-headless_poly_info_gt.png differ diff --git a/tests/render/007-info-editor-headless/CMakeLists.txt b/tests/render/007-info-editor-headless/CMakeLists.txt new file mode 100644 index 0000000000..7ccc2aea34 --- /dev/null +++ b/tests/render/007-info-editor-headless/CMakeLists.txt @@ -0,0 +1,12 @@ +# VCLib - Visual Computing Library +# Copyright (C) 2021-2026 Visual Computing Lab, ISTI - CNR. +# +# This Source Code Form is subject to the terms of the Mozilla Public License, +# 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/. + +vclib_add_test( + 007-info-editor-headless + MODULE render + SOURCES main.cpp +) diff --git a/tests/render/007-info-editor-headless/main.cpp b/tests/render/007-info-editor-headless/main.cpp new file mode 100644 index 0000000000..77a8a54aa2 --- /dev/null +++ b/tests/render/007-info-editor-headless/main.cpp @@ -0,0 +1,60 @@ +// VCLib - Visual Computing Library +// Copyright (C) 2021-2026 Visual Computing Lab, ISTI - CNR. +// +// This Source Code Form is subject to the terms of the Mozilla Public License, +// 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/. + +#include "get_drawable_mesh.h" +#include "run_render_test.h" + +static const std::string TEST_NAME = "007-info-editor-headless"; + +TEST_CASE("Info Editor Rendering") +{ + SECTION("face_info") + { + runRenderTest( + TEST_NAME, + "face_info", + [](vcl::HeadlessMeshViewer& mv) { + auto mesh = + getDrawableMesh("bunny_simplified.obj"); + mv.pushDrawableObject(std::move(mesh)); + }, + 0.0f, + -150.0f, + 2, + 0.005f, + [](vcl::HeadlessMeshViewer& mv) { + mv.pushEditor(true); + mv.simulateMousePress( + vcl::MouseButton::LEFT, + mv.width() / 2.0, + mv.height() / 2.0); + }); + } + + SECTION("poly_info") + { + runRenderTest( + TEST_NAME, + "poly_info", + [](vcl::HeadlessMeshViewer& mv) { + auto mesh = getDrawableMesh( + "spot/spot_quadrangulated.obj"); + mv.pushDrawableObject(std::move(mesh)); + }, + 1.57079632679f, + -150.0f, + 2, + 0.005f, + [](vcl::HeadlessMeshViewer& mv) { + mv.pushEditor(true); + mv.simulateMousePress( + vcl::MouseButton::LEFT, + mv.width() / 2.0, + mv.height() / 2.0); + }); + } +} diff --git a/tests/render/CMakeLists.txt b/tests/render/CMakeLists.txt index cc0c6059c2..b8af7c85d4 100644 --- a/tests/render/CMakeLists.txt +++ b/tests/render/CMakeLists.txt @@ -20,4 +20,5 @@ if(TARGET vclib-3rd-bgfx) add_subdirectory(004-lines-headless) add_subdirectory(005-mesh-wireframe-headless) add_subdirectory(006-mesh-pbr-headless) + add_subdirectory(007-info-editor-headless) endif() diff --git a/vclib/render/include/vclib/bgfx/canvas.h b/vclib/render/include/vclib/bgfx/canvas.h index 76273877b1..f0bbaec0e5 100644 --- a/vclib/render/include/vclib/bgfx/canvas.h +++ b/vclib/render/include/vclib/bgfx/canvas.h @@ -352,7 +352,7 @@ class CanvasBGFX } mReadRequest.emplace( - ReadFromGPUBuffer::Target::ID, mSize, mDefaultClearColor); + ReadFromGPUBuffer::Target::ID, mSize, vcl::Color::White); mReadRequest->setPendingRead(point, callback); return true; } 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..337f74805d 100644 --- a/vclib/render/include/vclib/bgfx/drawable/drawable_mesh_bgfx.h +++ b/vclib/render/include/vclib/bgfx/drawable/drawable_mesh_bgfx.h @@ -298,10 +298,6 @@ class DrawableMeshBGFX : public AbstractDrawableMesh, public MeshType void drawId(const DrawObjectSettings& settings) override { - using enum VertFragProgram; - - ProgramManager& pm = Context::instance().programManager(); - uint64_t state = 0 | BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_WRITE_Z | BGFX_STATE_DEPTH_TEST_LEQUAL | @@ -318,25 +314,27 @@ class DrawableMeshBGFX : public AbstractDrawableMesh, public MeshType mMRB.bindVertexBuffers(mMRS); mMRB.bindIndexBuffers(mMRS); mMRB.bindTriToPolyBuffer(); - DrawableMeshUniforms::setMeshId(settings.objectId); + DrawableMeshUniforms::setMeshId( + settings.objectId | vcl::ElemId::FACE); DrawableMeshUniforms::setFirstChunkIndex(0); bindUniforms(); bgfx::setState(state); bgfx::setTransform(model.data()); - bgfx::submit( - settings.viewId, pm.getProgram()); + bgfx::submit(settings.viewId, surfaceIdProgramSelector()); } if (mMRS.isEdges(MRI::Edges::VISIBLE)) { bgfx::setTransform(model.data()); - mMRB.drawEdgeLinesId(settings.viewId, settings.objectId); + mMRB.drawEdgeLinesId( + settings.viewId, settings.objectId | vcl::ElemId::EDGE); } if (mMRS.isPoints(MRI::Points::VISIBLE)) { bgfx::setTransform(model.data()); - mMRB.drawPointsId(settings.viewId, settings.objectId); + mMRB.drawPointsId( + settings.viewId, settings.objectId | vcl::ElemId::VERTEX); } } @@ -473,6 +471,27 @@ class DrawableMeshBGFX : public AbstractDrawableMesh, public MeshType ProgramManager& pm = Context::instance().programManager(); return pm.getProgram(VertFragProgram(program)); } + + /** + * @brief Selects the correct shader program for surface ID rendering. + * + * It chooses between trivial and non-trivial ID mapping based on the + * mesh render buffers configuration, ensuring correct mapping from + * primitive ID to face ID when rendering the mesh. + * + * @return The appropriate BGFX program handle. + */ + bgfx::ProgramHandle surfaceIdProgramSelector() const + { + ProgramManager& pm = Context::instance().programManager(); + + if (mMRB.isMappingTrivial()) + return pm.getProgram< + VertFragProgram::DRAWABLE_MESH_SURFACE_ID_MAPPING_TRIVIAL_ON>(); + else + return pm.getProgram< + VertFragProgram::DRAWABLE_MESH_SURFACE_ID_MAPPING_TRIVIAL_OFF>(); + } }; } // namespace vcl diff --git a/vclib/render/include/vclib/bgfx/editors/info_editor_bgfx.h b/vclib/render/include/vclib/bgfx/editors/info_editor_bgfx.h new file mode 100644 index 0000000000..98833a696b --- /dev/null +++ b/vclib/render/include/vclib/bgfx/editors/info_editor_bgfx.h @@ -0,0 +1,389 @@ +// VCLib - Visual Computing Library +// Copyright (C) 2021-2026 Visual Computing Lab, ISTI - CNR. +// +// This Source Code Form is subject to the terms of the Mozilla Public License, +// 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/. + +#ifndef VCL_BGFX_EDITORS_INFO_EDITOR_BGFX_H +#define VCL_BGFX_EDITORS_INFO_EDITOR_BGFX_H + +#include +#include +#include + +#include +#include +#include + +#include + +#include + +namespace vcl { + +/** + * @brief The InfoEditorBGFX class provides an editor for displaying information + * about clicked mesh elements (vertices, edges, or faces). + * + * It uses a GPU readback to identify the element under the cursor, retrieves + * its data from the mesh provider, and overlays text and highlights on the + * screen to provide visual feedback and data values (e.g., coordinates, IDs). + */ +template +class InfoEditorBGFX : public Editor +{ + using Base = Editor; + + ushort mLastObjectId = USHORT_NULL; + ushort mLastElementType = USHORT_NULL; + uint mLastElementId = 0; + + vcl::Lines mOutlineLines; + vcl::Points mOutlinePoints; + + vcl::TextView mTextView; + bool mTextViewInitialized = false; + + std::vector mLastElementPositions; + std::vector mLastElementVertexIds; + + vcl::Color mTextColor = vcl::Color::Black; + int mTextSize = 20; + + InfoEditorSettings mSettings; + +public: + InfoEditorBGFX() + { + mOutlineLines.setGeneralColor(vcl::Color::Red); + mOutlineLines.setWidth(5.0f); + mOutlineLines.setTopology(vcl::Lines::Topology::LINE_STRIP); + mOutlineLines.setDepthOffset(0.00012f); + + mOutlinePoints.setGeneralColor(vcl::Color::Red); + mOutlinePoints.setWidth(5.0f); + mOutlinePoints.setShape(vcl::Points::Shape::CIRCLE); + mOutlinePoints.setDepthOffset(0.00012f); + } + + std::string name() const override { return "Info"; } + + InfoEditorSettings& settings() override { return mSettings; } + + const InfoEditorSettings& settings() const override { return mSettings; } + + void loadSettings(const nlohmann::json& j) override + { + mSettings.loadSettings(j); + } + + void saveSettings(nlohmann::json& j) const override + { + mSettings.saveSettings(j); + } + + // Editor implementation + + void setActive(bool active) override + { + Base::setActive(active); + if (mTextViewInitialized) { + mTextView.enableText(active); + } + Base::viewerUpdate(); + } + + void refreshSettings() override + { + mOutlineLines.setGeneralColor(this->settings().color); + mOutlinePoints.setGeneralColor(this->settings().color); + + mOutlineLines.setWidth(this->settings().thickness); + mOutlinePoints.setWidth(this->settings().thickness); + + mTextColor = this->settings().textColor; + mTextSize = this->settings().textSize; + + if (mTextViewInitialized) { + auto dpi = Base::viewerDpiScale(); + mTextView.setTextFont( + vcl::VclFont::DROID_SANS, mTextSize * dpi.x()); + } + + Base::viewerUpdate(); + } + + void drawContent(uint viewId) override + { + if (mLastObjectId != USHORT_NULL) { + if (mLastElementType == vcl::MeshInfo::FACE && + mOutlineLines.hasPositions()) { + mOutlineLines.draw(viewId); + } + else if ( + mLastElementType == vcl::MeshInfo::VERTEX && + mOutlinePoints.hasPositions()) { + mOutlinePoints.draw(viewId); + } + else if ( + mLastElementType == vcl::MeshInfo::EDGE && + mOutlineLines.hasPositions()) { + mOutlineLines.draw(viewId); + } + } + + auto size = Base::viewerCanvasSize(); + if (!mTextViewInitialized) { + mTextView.init(size.x(), size.y()); + auto dpi = Base::viewerDpiScale(); + mTextView.setTextFont( + vcl::VclFont::DROID_SANS, mTextSize * dpi.x()); + mTextView.enableText(Base::isActive()); + mTextViewInitialized = true; + } + else { + mTextView.resize(size.x(), size.y()); + } + + mTextView.clearText(); + + if (mLastObjectId != USHORT_NULL) { + if (mLastElementType == vcl::MeshInfo::FACE && + mOutlineLines.hasPositions()) { + drawFaceInfo(viewId, size); + } + else if ( + mLastElementType == vcl::MeshInfo::VERTEX && + mOutlinePoints.hasPositions()) { + drawVertexInfo(viewId, size); + } + else if ( + mLastElementType == vcl::MeshInfo::EDGE && + mOutlineLines.hasPositions()) { + drawEdgeInfo(viewId, size); + } + } + + mTextView.frame(Base::viewerCanvasFrameBuffer()); + } + + bool onMousePress( + vcl::MouseButton::Enum button, + double x, + double y, + const vcl::KeyModifiers& modifiers) override + { + bool block = Base::onMousePress(button, x, y, modifiers); + + if (!block && button == vcl::MouseButton::LEFT) { + block = true; // consume the event to prevent further propagation + + // The callback receives the exact Object ID, Element Type, and + // Element ID from the GPU readback, avoiding the need for expensive + // CPU ray-tracing. + auto callback = [this]( + ushort objectId, + ushort elementType, + uint elementId) { + if (objectId == 0xFFFF) { + mLastObjectId = 0xFFFF; + Base::viewerUpdate(); + return; + } + + mLastObjectId = objectId; + mLastElementType = elementType; + mLastElementId = elementId; + + if (elementType == vcl::MeshInfo::FACE) { + auto list = Base::drawList(); + if (list && objectId < list->size()) { + auto obj = list->at(objectId); + auto meshObj = std::dynamic_pointer_cast< + vcl::AbstractDrawableMesh>(obj); + if (meshObj) { + auto positions = + meshObj->meshProvider().facePositions( + elementId); + auto vIds = + meshObj->meshProvider().faceVertices(elementId); + + if (positions.size() > 0) { + positions.push_back(positions[0]); + } + auto T = meshObj->meshProvider().transformMatrix(); + multiplyPointsByMatrix(positions, T); + mOutlineLines.setVertices(positions); + mLastElementPositions = positions; + mLastElementVertexIds = vIds; + } + } + } + else if (elementType == vcl::MeshInfo::VERTEX) { + auto list = Base::drawList(); + if (list && objectId < list->size()) { + auto obj = list->at(objectId); + auto meshObj = std::dynamic_pointer_cast< + vcl::AbstractDrawableMesh>(obj); + if (meshObj) { + auto pos = meshObj->meshProvider().vertexPosition( + elementId); + auto T = meshObj->meshProvider().transformMatrix(); + std::vector positions = {pos}; + multiplyPointsByMatrix(positions, T); + mOutlinePoints.setVertices(positions); + mLastElementPositions = positions; + mLastElementVertexIds = {elementId}; + } + } + } + else if (elementType == vcl::MeshInfo::EDGE) { + auto list = Base::drawList(); + if (list && objectId < list->size()) { + auto obj = list->at(objectId); + auto meshObj = std::dynamic_pointer_cast< + vcl::AbstractDrawableMesh>(obj); + if (meshObj) { + auto edgeVIds = + meshObj->meshProvider().edgeVertices(elementId); + auto posPair = + meshObj->meshProvider().edgePositions( + elementId); + + std::vector positions = { + posPair.first, posPair.second}; + auto T = meshObj->meshProvider().transformMatrix(); + multiplyPointsByMatrix(positions, T); + mOutlineLines.setVertices(positions); + mLastElementPositions = positions; + mLastElementVertexIds = { + edgeVIds.first, edgeVIds.second}; + } + } + } + else { + mOutlineLines.setVertices(std::vector()); + mOutlinePoints.setVertices(std::vector()); + mLastElementPositions.clear(); + mLastElementVertexIds.clear(); + } + + Base::viewerUpdate(); + }; + + Base::viewerReadElementIdRequest(x, y, callback); + } + return block; + } + +private: + std::optional projectPoint( + const vcl::Point3d& point3d, + const vcl::Matrix44f& viewMatrix, + const vcl::Matrix44f& projMatrix, + const Point2& size) const + { + vcl::Point4f p(point3d.x(), point3d.y(), point3d.z(), 1.0f); + p = projMatrix * (viewMatrix * p); + + if (p.w() != 0.0f) { + p /= p.w(); + return vcl::Point2f( + (p.x() + 1.0f) * 0.5f * size.x(), + (1.0f - p.y()) * 0.5f * size.y()); + } + return std::nullopt; + } + + void drawFaceInfo(uint viewId, const Point2& size) + { + const auto& pts = mLastElementPositions; + if (pts.size() > 1) { + vcl::Point3d barycenter(0.0, 0.0, 0.0); + for (size_t i = 0; i < pts.size() - 1; ++i) { + barycenter += pts[i]; + } + barycenter /= double(pts.size() - 1); + + vcl::Matrix44f mv = Base::viewerViewMatrix(); + vcl::Matrix44f proj = Base::viewerProjectionMatrix(); + + // Draw face info at barycenter + if (auto pos2D = projectPoint(barycenter, mv, proj, size)) { + std::stringstream faceSS; + faceSS << "f#" << mLastElementId << " - v#("; + for (size_t i = 0; i < mLastElementVertexIds.size(); ++i) { + faceSS << mLastElementVertexIds[i]; + if (i < mLastElementVertexIds.size() - 1) + faceSS << ", "; + } + faceSS << ")"; + + mTextView.appendTransientText(*pos2D, faceSS.str(), mTextColor); + } + + // Draw vertex info at each vertex position + for (size_t i = 0; i < mLastElementVertexIds.size(); ++i) { + if (auto pos2D = projectPoint(pts[i], mv, proj, size)) { + std::stringstream vertSS; + vertSS << "fv[" << i << "] : v#" << mLastElementVertexIds[i] + << " - pos " << pts[i]; + + mTextView.appendTransientText( + *pos2D, vertSS.str(), mTextColor); + } + } + } + } + + void drawVertexInfo(uint viewId, const Point2& size) + { + const auto& pts = mLastElementPositions; + if (!pts.empty() && !mLastElementVertexIds.empty()) { + vcl::Matrix44f mv = Base::viewerViewMatrix(); + vcl::Matrix44f proj = Base::viewerProjectionMatrix(); + + if (auto pos2D = projectPoint(pts[0], mv, proj, size)) { + std::stringstream vertSS; + vertSS << "v#" << mLastElementVertexIds[0] << " - pos " + << pts[0]; + mTextView.appendTransientText(*pos2D, vertSS.str(), mTextColor); + } + } + } + + void drawEdgeInfo(uint viewId, const Point2& size) + { + const auto& pts = mLastElementPositions; + if (pts.size() == 2 && mLastElementVertexIds.size() == 2) { + vcl::Matrix44f mv = Base::viewerViewMatrix(); + vcl::Matrix44f proj = Base::viewerProjectionMatrix(); + + // Draw edge info at edge center + vcl::Point3d center = (pts[0] + pts[1]) * 0.5; + if (auto pos2D = projectPoint(center, mv, proj, size)) { + std::stringstream edgeSS; + edgeSS << "e#" << mLastElementId << " - v#(" + << mLastElementVertexIds[0] << ", " + << mLastElementVertexIds[1] << ")"; + mTextView.appendTransientText(*pos2D, edgeSS.str(), mTextColor); + } + + // Draw vertex info at each vertex position + for (size_t i = 0; i < 2; ++i) { + if (auto pos2D = projectPoint(pts[i], mv, proj, size)) { + std::stringstream vertSS; + vertSS << "ev[" << i << "] : v#" << mLastElementVertexIds[i] + << " - pos " << pts[i]; + mTextView.appendTransientText( + *pos2D, vertSS.str(), mTextColor); + } + } + } + } +}; + +} // namespace vcl + +#endif // VCL_BGFX_EDITORS_INFO_EDITOR_BGFX_H diff --git a/vclib/render/include/vclib/bgfx/read_from_gpu_buffer.h b/vclib/render/include/vclib/bgfx/read_from_gpu_buffer.h index 2117172ea6..d125517c37 100644 --- a/vclib/render/include/vclib/bgfx/read_from_gpu_buffer.h +++ b/vclib/render/include/vclib/bgfx/read_from_gpu_buffer.h @@ -78,7 +78,8 @@ class ReadFromGPUBuffer // ------------------------------------------------------------------------- FrameBuffer mOffscreenFbh; bgfx::ViewId mViewOffscreenId = BGFX_INVALID_VIEW; - bgfx::TextureHandle mBlitTexture = BGFX_INVALID_HANDLE; + std::array mBlitTexture = {{ + BGFX_INVALID_HANDLE, BGFX_INVALID_HANDLE}}; Point2 mBlitSize = {0, 0}; Color mClearColor = Color::Black; diff --git a/vclib/render/include/vclib/imgui/gui/toolbar_frames/info_editor_frame.h b/vclib/render/include/vclib/imgui/gui/toolbar_frames/info_editor_frame.h new file mode 100644 index 0000000000..19e7c01b34 --- /dev/null +++ b/vclib/render/include/vclib/imgui/gui/toolbar_frames/info_editor_frame.h @@ -0,0 +1,123 @@ +// VCLib - Visual Computing Library +// Copyright (C) 2021-2026 Visual Computing Lab, ISTI - CNR. +// +// This Source Code Form is subject to the terms of the Mozilla Public License, +// 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/. + +#ifndef VCL_IMGUI_GUI_TOOLBAR_FRAMES_INFO_EDITOR_FRAME_H +#define VCL_IMGUI_GUI_TOOLBAR_FRAMES_INFO_EDITOR_FRAME_H + +#include "../editor_frame.h" + +#include + +#include + +#include + +#include + +namespace vcl::imgui { + +template +class InfoEditorFrameImgui : public EditorFrameImgui +{ + std::shared_ptr> mEditor; + +public: + explicit InfoEditorFrameImgui( + std::shared_ptr> editor) : mEditor(editor) + { + } + + void draw() override + { + if (!mEditor) + return; + + bool active = mEditor->isActive(); + if (ImGui::Button(active ? "[ I ]" : " I ")) { + mEditor->setActive(!active); + } + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) + ImGui::SetTooltip("Info Tool"); + + ImGui::SameLine(0, 2); + if (ImGui::Button("v##InfoSettings")) { + ImGui::OpenPopup("##InfoSettingsPopup"); + } + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) + ImGui::SetTooltip("Info Tool Settings"); + + if (ImGui::BeginPopup("##InfoSettingsPopup")) { + drawInfoSettings(); + ImGui::EndPopup(); + } + } + +private: + void drawInfoSettings() + { + vcl::InfoEditorSettings& sts = mEditor->settings(); + + // Highlight width + float thickness = sts.thickness; + ImGui::Text("Highlight Width:"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(130); + if (ImGui::SliderFloat( + "##InfoThickness", &thickness, 1.0f, 10.0f, "%.1f")) { + sts.thickness = thickness; + mEditor->refreshSettings(); + } + + // Highlight color + ImGui::Text("Highlight Color:"); + ImGui::SameLine(); + ImGui::ColorEdit4( + "##InfoColor", + [&] { + return sts.color; + }, + [&](vcl::Color c) { + sts.color = c; + mEditor->refreshSettings(); + }, + ImGuiColorEditFlags_NoInputs); + // Text Size + int textSize = sts.textSize; + ImGui::Text("Text Size:"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(130); + if (ImGui::SliderInt( + "##InfoTextSize", &textSize, 5, 100)) { + sts.textSize = textSize; + mEditor->refreshSettings(); + } + + // Text Color + ImGui::Text("Text Color:"); + ImGui::SameLine(); + ImGui::ColorEdit4( + "##InfoTextColor", + [&] { + return sts.textColor; + }, + [&](vcl::Color c) { + sts.textColor = c; + mEditor->refreshSettings(); + }, + ImGuiColorEditFlags_NoInputs); + } +}; + +template +struct EditorFrameTraits +{ + using ToolbarFrameType = InfoEditorFrameImgui; +}; + +} // namespace vcl::imgui + +#endif // VCL_IMGUI_GUI_TOOLBAR_FRAMES_INFO_EDITOR_FRAME_H diff --git a/vclib/render/include/vclib/imgui/mesh_viewer_imgui_drawer.h b/vclib/render/include/vclib/imgui/mesh_viewer_imgui_drawer.h index 57b4885b09..5e42be9021 100644 --- a/vclib/render/include/vclib/imgui/mesh_viewer_imgui_drawer.h +++ b/vclib/render/include/vclib/imgui/mesh_viewer_imgui_drawer.h @@ -12,6 +12,7 @@ #include #include +#include #include #include #include diff --git a/vclib/render/include/vclib/qt/gui/editor_settings_frames/info_editor_settings_frame.h b/vclib/render/include/vclib/qt/gui/editor_settings_frames/info_editor_settings_frame.h new file mode 100644 index 0000000000..b6380e9da8 --- /dev/null +++ b/vclib/render/include/vclib/qt/gui/editor_settings_frames/info_editor_settings_frame.h @@ -0,0 +1,54 @@ +// VCLib - Visual Computing Library +// Copyright (C) 2021-2026 Visual Computing Lab, ISTI - CNR. +// +// This Source Code Form is subject to the terms of the Mozilla Public License, +// 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/. + +#ifndef VCL_QT_GUI_EDITOR_SETTINGS_FRAMES_INFO_EDITOR_SETTINGS_FRAME_H +#define VCL_QT_GUI_EDITOR_SETTINGS_FRAMES_INFO_EDITOR_SETTINGS_FRAME_H + +#include + +#include +#include + +namespace vcl::qt { + +namespace Ui { +class InfoEditorSettingsFrame; +} // namespace Ui + +class InfoEditorSettingsFrame : public QFrame +{ + Q_OBJECT + + Ui::InfoEditorSettingsFrame* mUI; + InfoEditorSettings& mSettings; + +public: + explicit InfoEditorSettingsFrame( + InfoEditorSettings& sts, + QWidget* parent = nullptr); + ~InfoEditorSettingsFrame(); + + void updateGUI(); + +signals: + void settingsUpdated(); + +private slots: + void onLinesWidthSliderValueChanged(int value); + + void onColorChanged(const QColor& c); + + void onTextSizeChanged(int value); + + void onTextColorChanged(const QColor& c); + + void onResetDefaultClicked(); +}; + +} // namespace vcl::qt + +#endif // VCL_QT_GUI_EDITOR_SETTINGS_FRAMES_INFO_EDITOR_SETTINGS_FRAME_H diff --git a/vclib/render/include/vclib/qt/gui/toolbar_frames.h b/vclib/render/include/vclib/qt/gui/toolbar_frames.h index e872c37aa5..21d1f342c7 100644 --- a/vclib/render/include/vclib/qt/gui/toolbar_frames.h +++ b/vclib/render/include/vclib/qt/gui/toolbar_frames.h @@ -10,6 +10,7 @@ #include "toolbar_frames/axis_frame.h" #include "toolbar_frames/bounding_box_editor_frame.h" +#include "toolbar_frames/info_editor_frame.h" #include "toolbar_frames/not_editing_frame.h" #include "toolbar_frames/selection_editor_frame.h" #include "toolbar_frames/trackball_frame.h" diff --git a/vclib/render/include/vclib/qt/gui/toolbar_frames/info_editor_frame.h b/vclib/render/include/vclib/qt/gui/toolbar_frames/info_editor_frame.h new file mode 100644 index 0000000000..65fe112f6f --- /dev/null +++ b/vclib/render/include/vclib/qt/gui/toolbar_frames/info_editor_frame.h @@ -0,0 +1,75 @@ +// VCLib - Visual Computing Library +// Copyright (C) 2021-2026 Visual Computing Lab, ISTI - CNR. +// +// This Source Code Form is subject to the terms of the Mozilla Public License, +// 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/. + +#ifndef VCL_QT_GUI_TOOLBAR_FRAMES_INFO_EDITOR_FRAME_H +#define VCL_QT_GUI_TOOLBAR_FRAMES_INFO_EDITOR_FRAME_H + +#include "generic_editor_frame.h" + +#include + +#include + +#include +#include + +namespace vcl::qt { + +template +class InfoEditorFrame : public GenericEditorFrame +{ + using Base = GenericEditorFrame; + + std::shared_ptr> mInfoEditor; + +public: + explicit InfoEditorFrame( + std::shared_ptr> ptr, + QWidget* parent = nullptr) : GenericEditorFrame(parent) + { + mInfoEditor = ptr; + + QIcon ic = QApplication::style()->standardIcon( + QStyle::SP_MessageBoxInformation); + + QPushButton* editorButton = Base::addButton(ic, true); + editorButton->setToolTip("Info"); + + connect( + editorButton, &QPushButton::clicked, this, [this](bool checked) { + if (mInfoEditor) { + mInfoEditor->setActive(checked); + } + }); + + InfoEditorSettingsFrame* sf = + Base::setSettingsFrame( + mInfoEditor->settings()); + + connect(sf, SIGNAL(settingsUpdated()), this, SLOT(refreshSettings())); + } + +private slots: + + void refreshSettings() override + { + if (mInfoEditor) { + mInfoEditor->refreshSettings(); + } + } +}; + +template +struct EditorFrameTraits +{ + using ToolbarFrameType = InfoEditorFrame; + using SettingsFrameType = InfoEditorSettingsFrame; +}; + +} // namespace vcl::qt + +#endif // VCL_QT_GUI_TOOLBAR_FRAMES_INFO_EDITOR_FRAME_H diff --git a/vclib/render/include/vclib/render/drawable/drawable_object.h b/vclib/render/include/vclib/render/drawable/drawable_object.h index 315a7ba519..ba67d32b03 100644 --- a/vclib/render/include/vclib/render/drawable/drawable_object.h +++ b/vclib/render/include/vclib/render/drawable/drawable_object.h @@ -69,6 +69,10 @@ class DrawableObject * It will be called on request when the renderer needs to draw the ID of * the object. * + * @note The `settings.objectId` passed to this function must be pre-shifted + * by 16 bits (i.e. `objectId << 16`). The lower 16 bits are reserved for + * the element type (e.g., Vertex, Face, Edge). + * * @param[in] settings: The settings to use to draw the object. */ virtual void drawId(const DrawObjectSettings& settings) {}; diff --git a/vclib/render/include/vclib/render/drawers/abstract_viewer_drawer.h b/vclib/render/include/vclib/render/drawers/abstract_viewer_drawer.h index a2e0655ed6..89d06616ba 100644 --- a/vclib/render/include/vclib/render/drawers/abstract_viewer_drawer.h +++ b/vclib/render/include/vclib/render/drawers/abstract_viewer_drawer.h @@ -724,13 +724,53 @@ class AbstractViewerDrawer : public TrackBallEventDrawer // create the callback auto callback = [=, this](const ReadData& dt) { const auto& data = std::get(dt); - assert(data.size() == 4); - // TODO: check how to do this properly - const uint id = *(uint32_t*) &data[0]; + assert(data.size() == 8); // 8 bytes for 64-bit ID + + // First 4 bytes contain Object ID (16 bits) and Element Type (16 bits) + const uint id_with_type = *(uint32_t*) &data[0]; + const uint objectId = id_with_type >> 16; + + // The Element ID is in the next 4 bytes (data[4..7]) if needed in the future + + mReadRequested = false; + + idCallback(objectId); + derived()->update(); + }; + + mReadRequested = + DRA::DRW::readId(derived(), Point2i(p.x(), p.y()), callback); + if (mReadRequested) + derived()->update(); + } + + void readElementIdRequest( + double x, double y, std::function idCallback) + { + using ReadData = ReadBufferTypes::ReadData; + + if (mReadRequested) + return; + + // get point + const Point2d p(x, y); + + // create the callback + auto callback = [=, this](const ReadData& dt) { + const auto& data = std::get(dt); + assert(data.size() == 8); // 8 bytes for 64-bit ID + + // First 4 bytes contain Object ID (16 bits) and Element Type (16 bits) + const uint id_with_type = *(uint32_t*) &data[0]; + const ushort objectId = id_with_type >> 16; + const ushort elementType = id_with_type & 0xFFFF; + + // Next 4 bytes contain Element ID + const uint elementId = *(uint32_t*) &data[4]; mReadRequested = false; - idCallback(id); + idCallback(objectId, elementType, elementId); derived()->update(); }; diff --git a/vclib/render/include/vclib/render/editors.h b/vclib/render/include/vclib/render/editors.h index f49710fc4a..5f5965698f 100644 --- a/vclib/render/include/vclib/render/editors.h +++ b/vclib/render/include/vclib/render/editors.h @@ -9,6 +9,7 @@ #define VCL_RENDER_EDITORS_H #include "editors/bounding_box_editor.h" +#include "editors/info_editor.h" #include "editors/mesh_selector_editor.h" #include "editors/selection_editor.h" diff --git a/vclib/render/include/vclib/render/editors/editor.h b/vclib/render/include/vclib/render/editors/editor.h index 5380804d5d..d7287cd17b 100644 --- a/vclib/render/include/vclib/render/editors/editor.h +++ b/vclib/render/include/vclib/render/editors/editor.h @@ -380,6 +380,25 @@ class Editor mViewer->readIdRequest(x, y, std::move(idCallback)); } + /** + * @brief Requests the viewer to read the Object ID, Element Type, and Element ID + * of the object at the given screen coordinates. + * + * The result is delivered asynchronously via the provided callback. + * + * @param[in] x: the x coordinate of the point, in window pixels. + * @param[in] y: the y coordinate of the point, in window pixels. + * @param[in] idCallback: callback invoked with the objectId, elementType, and elementId + */ + void viewerReadElementIdRequest( + double x, + double y, + std::function idCallback) + { + assert(mViewer); + mViewer->readElementIdRequest(x, y, std::move(idCallback)); + } + /** * @brief Requests the viewer to redraw the frame. * diff --git a/vclib/render/include/vclib/render/editors/info_editor.h b/vclib/render/include/vclib/render/editors/info_editor.h new file mode 100644 index 0000000000..b294ff9fd1 --- /dev/null +++ b/vclib/render/include/vclib/render/editors/info_editor.h @@ -0,0 +1,78 @@ +// VCLib - Visual Computing Library +// Copyright (C) 2021-2026 Visual Computing Lab, ISTI - CNR. +// +// This Source Code Form is subject to the terms of the Mozilla Public License, +// 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/. + +#ifndef VCL_RENDER_EDITORS_INFO_EDITOR_H +#define VCL_RENDER_EDITORS_INFO_EDITOR_H + +#ifdef VCLIB_RENDER_BACKEND_BGFX +#include +#endif + +#ifdef VCLIB_RENDER_BACKEND_OPENGL2 +#include "editor.h" + +#include +#endif + +namespace vcl { + +/** + * @brief The InfoEditor class provides a viewer editor for displaying + * information about the mesh elements (e.g. vertices, edges, faces) being + * clicked. + * + * It retrieves the object and element ID beneath the mouse cursor from the GPU + * readback, reads the element details from the mesh provider, and overlays this + * data in the viewer as transient text and highlighting. + * + * @ingroup render_editors + */ +#ifdef VCLIB_RENDER_BACKEND_BGFX +template +using InfoEditor = InfoEditorBGFX; +#endif + +#ifdef VCLIB_RENDER_BACKEND_OPENGL2 +// TODO: implement InfoEditorOpenGL2 +template +class InfoEditor : public Editor +{ + using Base = Editor; + InfoEditorSettings mSettings; + +public: + InfoEditor() = default; + + std::string name() const override { return "Info"; } + + InfoEditorSettings& settings() override { return mSettings; } + + const InfoEditorSettings& settings() const override { return mSettings; } + + void loadSettings(const nlohmann::json& j) override + { + mSettings.loadSettings(j); + } + + void saveSettings(nlohmann::json& j) const override + { + mSettings.saveSettings(j); + } + + void setActive(bool active) override + { + Base::setActive(active); + Base::viewerUpdate(); + } + + void draw(uint) override {} +}; +#endif + +} // namespace vcl + +#endif // VCL_RENDER_EDITORS_INFO_EDITOR_H diff --git a/vclib/render/include/vclib/render/mesh_viewer.h b/vclib/render/include/vclib/render/mesh_viewer.h index 258ca1e0b1..4e1a5bba01 100644 --- a/vclib/render/include/vclib/render/mesh_viewer.h +++ b/vclib/render/include/vclib/render/mesh_viewer.h @@ -68,6 +68,7 @@ inline void pushDefaultEditors(MeshViewerConcept auto& viewer) { viewer.template pushEditor(true); viewer.template pushEditor(); + viewer.template pushEditor(); viewer.template pushEditor(); } diff --git a/vclib/render/include/vclib/render/settings/info_editor_settings.h b/vclib/render/include/vclib/render/settings/info_editor_settings.h new file mode 100644 index 0000000000..1172304634 --- /dev/null +++ b/vclib/render/include/vclib/render/settings/info_editor_settings.h @@ -0,0 +1,67 @@ +// VCLib - Visual Computing Library +// Copyright (C) 2021-2026 Visual Computing Lab, ISTI - CNR. +// +// This Source Code Form is subject to the terms of the Mozilla Public License, +// 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/. + +#ifndef VCL_RENDER_SETTINGS_INFO_EDITOR_SETTINGS_H +#define VCL_RENDER_SETTINGS_INFO_EDITOR_SETTINGS_H + +#include + +#include + +#include + +namespace vcl { + +struct InfoEditorSettings : public EditorSettings +{ + vcl::Color color = vcl::Color::Red; + float thickness = 5.0f; + vcl::Color textColor = vcl::Color::Black; + int textSize = 20; + + /** + * @brief Resets the settings to their default values. + */ + void resetDefaults() + { + color = vcl::Color::Red; + thickness = 5.0f; + textColor = vcl::Color::Black; + textSize = 20; + } + + /** + * @brief Loads the settings from a JSON object. + * @param[in] j: the JSON object to read from. + */ + void loadSettings(const nlohmann::json& j) + { + if (j.contains("InfoEditor")) { + const auto& jBox = j["InfoEditor"]; + color = jBox.value("color", color); + thickness = jBox.value("thickness", thickness); + textColor = jBox.value("textColor", textColor); + textSize = jBox.value("textSize", textSize); + } + } + + /** + * @brief Saves the settings to a JSON object. + * @param[out] j: the JSON object to write to. + */ + void saveSettings(nlohmann::json& j) const + { + j["InfoEditor"]["color"] = color; + j["InfoEditor"]["thickness"] = thickness; + j["InfoEditor"]["textColor"] = textColor; + j["InfoEditor"]["textSize"] = textSize; + } +}; + +} // namespace vcl + +#endif // VCL_RENDER_SETTINGS_INFO_EDITOR_SETTINGS_H diff --git a/vclib/render/shaders/embedded_vf_programs.config b/vclib/render/shaders/embedded_vf_programs.config index 3573802fe6..6e4249b99e 100644 --- a/vclib/render/shaders/embedded_vf_programs.config +++ b/vclib/render/shaders/embedded_vf_programs.config @@ -17,10 +17,6 @@ DRAWABLE_ENVIRONMENT_PBR vclib/shaders/drawable/drawable_environment/vs_drawable_environment_pbr.sc vclib/shaders/drawable/drawable_environment/fs_drawable_environment_pbr.sc -DRAWABLE_MESH_SURFACE_ID - vclib/shaders/drawable/drawable_mesh/surface_id/vs_surface_id.sc - vclib/shaders/drawable/drawable_mesh/surface_id/fs_surface_id.sc - DRAWABLE_MESH_SURFACE_PBR vclib/shaders/drawable/drawable_mesh/surface_pbr/vs_surface_pbr.sc vclib/shaders/drawable/drawable_mesh/surface_pbr/fs_surface_pbr.sc 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 deleted file mode 100644 index d82b954e31..0000000000 --- a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_id/fs_surface_id.sc +++ /dev/null @@ -1,18 +0,0 @@ -// VCLib - Visual Computing Library -// Copyright (C) 2021-2026 Visual Computing Lab, ISTI - CNR. -// -// This Source Code Form is subject to the terms of the Mozilla Public License, -// 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/. - -#include - -#include - -void main() -{ - /***** render ID to color ******/ - vec4 color = uintABGRToVec4Color(u_meshId); - - gl_FragColor = color; -} diff --git a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_id/fs_surface_id_in.sh b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_id/fs_surface_id_in.sh new file mode 100644 index 0000000000..b8f9230296 --- /dev/null +++ b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_id/fs_surface_id_in.sh @@ -0,0 +1,30 @@ +// VCLib - Visual Computing Library +// Copyright (C) 2021-2026 Visual Computing Lab, ISTI - CNR. +// +// This Source Code Form is subject to the terms of the Mozilla Public License, +// 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/. + +#include + +#include + +#include + +#if defined(SURFACE_ID_MAPPING_TRIVIAL_OFF) +BUFFER_RO(tri_to_poly, uint, VCL_MRB_TRI_TO_POLY_BUFFER); +#endif + +void main() +{ + // Target 0: Object ID (16 bit) + Element Type (16 bit) + // (already combined in u_meshId) + gl_FragData[0] = uintABGRToVec4Color(u_meshId); + + // Target 1: Element ID (32 bit) + uint polyId = gl_PrimitiveID + u_firstChunkPrimitiveID; +#if defined(SURFACE_ID_MAPPING_TRIVIAL_OFF) + polyId = tri_to_poly[polyId]; +#endif + gl_FragData[1] = uintABGRToVec4Color(polyId); +} diff --git a/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_id/surface_id.config b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_id/surface_id.config new file mode 100644 index 0000000000..8b45384a5b --- /dev/null +++ b/vclib/render/shaders/vclib/bgfx/drawable/drawable_mesh/surface_id/surface_id.config @@ -0,0 +1,13 @@ +ENUM_PREFIX DRAWABLE_MESH_SURFACE_ID +DEFINE_PREFIX SURFACE_ID + +# VS is static (not generated for each combination) +VS_FILE vs_surface_id.sc + +# FS is generated dynamically based on combinations +FS_IN fs_surface_id_in.sh +FS_PREFIX fs_surface_id + +DIM_FS MAPPING_TRIVIAL + ON + OFF 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..b7bb8f2036 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 @@ -5,11 +5,16 @@ // 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/. -$input v_color, v_normal, v_selected +$input v_color, v_normal, v_selected, v_lineIndex #include #include void main() { - gl_FragColor = u_linesId; + // Target 0: Object ID (16 bit) + Element Type (16 bit) (combined in u_linesId) + gl_FragData[0] = u_linesId; + + // Target 1: Element ID (32 bit) + uint elementId = uint(v_lineIndex); + gl_FragData[1] = uintABGRToVec4Color(elementId); } 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..b6c1eda6c5 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 @@ -5,7 +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/. -$input v_color, v_normal, v_selected +$input v_color, v_normal, v_selected, v_lineIndex #include #if LINES_SHADING_PER_VERTEX || LINES_SHADING_PER_LINE 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..16d44fd826 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,4 @@ vec4 v_color : COLOR0; vec3 v_normal : NORMAL; flat float v_selected : TEXCOORD0 = 0.0; +flat float v_lineIndex : TEXCOORD1 = 0.0; 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..953513bde4 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 @@ -5,7 +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/. -$output v_color, v_normal, v_selected +$output v_color, v_normal, v_selected, v_lineIndex #include #include @@ -148,6 +148,7 @@ void main() { v_color = color; v_normal = normal; + v_lineIndex = float(lineIndex); // Apply depth offset in clip space. // We scale the offset by w to maintain it consistently after the perspective divide. 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..e2b8e0905f 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 @@ -5,7 +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/. -$input v_normal, v_texcoord0, v_color, v_selected +$input v_normal, v_texcoord0, v_color, v_selected, v_pointIndex #include #include @@ -20,5 +20,10 @@ void main() } #endif - gl_FragColor = u_pointsId; + // Target 0: Object ID (16 bit) + Element Type (16 bit) (already combined in u_pointsId) + gl_FragData[0] = u_pointsId; + + // Target 1: Element ID (32 bit) + uint elementId = uint(v_pointIndex); + gl_FragData[1] = uintABGRToVec4Color(elementId); } 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..f18f44a8d9 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 @@ -5,7 +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/. -$input v_normal, v_texcoord0, v_color, v_selected +$input v_normal, v_texcoord0, v_color, v_selected, v_pointIndex #include #include 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..4ae7494080 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,8 @@ // 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; +flat float v_pointIndex : TEXCOORD2 = 0.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..da449986f8 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 @@ -5,7 +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/. -$output v_normal, v_texcoord0, v_color, v_selected +$output v_normal, v_texcoord0, v_color, v_selected, v_pointIndex #include #include @@ -75,4 +75,6 @@ void main() #if POINTS_SELECTION_ON v_selected = float(getBoolFromBuffer(vertexSelected, pointIndex)); #endif + + v_pointIndex = float(pointIndex); } diff --git a/vclib/render/src/vclib/bgfx/read_from_gpu_buffer.cpp b/vclib/render/src/vclib/bgfx/read_from_gpu_buffer.cpp index 5a5d133d3e..dba12bce3a 100644 --- a/vclib/render/src/vclib/bgfx/read_from_gpu_buffer.cpp +++ b/vclib/render/src/vclib/bgfx/read_from_gpu_buffer.cpp @@ -101,6 +101,8 @@ ReadFromGPUBuffer::ReadFromGPUBuffer( const uint pixelCount = uint(mBlitSize.x()) * uint(mBlitSize.y()); if (target == Target::DEPTH) mReadData = FloatData(pixelCount); + else if (target == Target::ID) + mReadData = ByteData(pixelCount * 8); // 8 bytes for 64-bit ID else mReadData = ByteData(pixelCount * 4); @@ -113,11 +115,49 @@ ReadFromGPUBuffer::ReadFromGPUBuffer( clearColor.rgba(); // Create the offscreen framebuffer - mOffscreenFbh.create( - uint16_t(size.x()), - uint16_t(size.y()), - offscreenColorFormat(), - offscreenDepthFormat()); + if (target == Target::ID) { + // For ID reading, we use Multiple Render Targets (MRT) + // target 0: Color attachment (Object ID + Element Type) + // target 1: Color attachment (Element ID) + // target 2: Depth attachment + bgfx::TextureHandle fbtextures[3]; + const uint64_t kMRTRenderBufferflags = + BGFX_TEXTURE_RT | BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT | + BGFX_SAMPLER_MIP_POINT | BGFX_SAMPLER_U_CLAMP | + BGFX_SAMPLER_V_CLAMP; + + fbtextures[0] = bgfx::createTexture2D( + uint16_t(size.x()), + uint16_t(size.y()), + false, + 1, + offscreenColorFormat(), + kMRTRenderBufferflags); + fbtextures[1] = bgfx::createTexture2D( + uint16_t(size.x()), + uint16_t(size.y()), + false, + 1, + offscreenColorFormat(), + kMRTRenderBufferflags); + fbtextures[2] = bgfx::createTexture2D( + uint16_t(size.x()), + uint16_t(size.y()), + false, + 1, + offscreenDepthFormat(), + kMRTRenderBufferflags); + + mOffscreenFbh.create(fbtextures, 3, true); + } + else { + // For COLOR or DEPTH, a standard framebuffer is sufficient + mOffscreenFbh.create( + uint16_t(size.x()), + uint16_t(size.y()), + offscreenColorFormat(), + offscreenDepthFormat()); + } assert(mOffscreenFbh.isValid()); // Initialize the view @@ -128,13 +168,23 @@ ReadFromGPUBuffer::ReadFromGPUBuffer( clearValue, Context::DEFAULT_CLEAR_DEPTH, Context::DEFAULT_CLEAR_STENCIL); + + // For MRT, set clear for the second attachment as well using the multiple + // attachment clear function if needed, but setViewClear clears all color + // attachments by default. bgfx::setViewRect( mViewOffscreenId, 0, 0, uint16_t(size.x()), uint16_t(size.y())); bgfx::touch(mViewOffscreenId); - mBlitTexture = bgfx::createTexture2D( + mBlitTexture[0] = bgfx::createTexture2D( mBlitSize.x(), mBlitSize.y(), false, 1, blitFormat, kBlitFlags); - assert(bgfx::isValid(mBlitTexture)); + assert(bgfx::isValid(mBlitTexture[0])); + + if (target == Target::ID) { + mBlitTexture[1] = bgfx::createTexture2D( + mBlitSize.x(), mBlitSize.y(), false, 1, blitFormat, kBlitFlags); + assert(bgfx::isValid(mBlitTexture[1])); + } } ReadFromGPUBuffer::ReadFromGPUBuffer(uint maxByteSize) : @@ -417,9 +467,13 @@ std::vector ReadFromGPUBuffer::getResultsCopy() const void ReadFromGPUBuffer::destroyFramebufferResources() { - if (bgfx::isValid(mBlitTexture)) { - bgfx::destroy(mBlitTexture); - mBlitTexture = BGFX_INVALID_HANDLE; + if (bgfx::isValid(mBlitTexture[0])) { + bgfx::destroy(mBlitTexture[0]); + mBlitTexture[0] = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(mBlitTexture[1])) { + bgfx::destroy(mBlitTexture[1]); + mBlitTexture[1] = BGFX_INVALID_HANDLE; } // FrameBuffer handles its own destruction mOffscreenFbh.destroy(); @@ -444,7 +498,7 @@ void ReadFromGPUBuffer::destroyComputeResources() void ReadFromGPUBuffer::submitFramebufferBlit() { // Determine which framebuffer attachment to read from: - // COLOR → attachment 0, DEPTH → attachment 1, ID → attachment 0 + // COLOR → attachment 0, DEPTH → attachment 1, ID → attachment 0 & 1 const uint8_t attachment = (mTarget == Target::DEPTH) ? uint8_t(1) : uint8_t(0); const bgfx::TextureHandle srcBuffer = @@ -457,7 +511,7 @@ void ReadFromGPUBuffer::submitFramebufferBlit() if (pixelCount == 1) { // Vulkan/Metal: blit only the queried pixel bgfx::TextureRegion dstRegion; - dstRegion.init(mBlitTexture, 0, 0, 1, 1); + dstRegion.init(mBlitTexture[0], 0, 0, 1, 1); bgfx::TextureRegion srcRegion; srcRegion.init( srcBuffer, uint16_t(mPoint.x()), uint16_t(mPoint.y()), 1, 1); @@ -466,32 +520,61 @@ void ReadFromGPUBuffer::submitFramebufferBlit() else { // D3D: blit the full depth buffer; pixel extracted in performRead bgfx::TextureRegion dstRegion; - dstRegion.handle = mBlitTexture; + dstRegion.handle = mBlitTexture[0]; bgfx::TextureRegion srcRegion; srcRegion.handle = srcBuffer; bgfx::blit(mViewOffscreenId, dstRegion, srcRegion); } bgfx::TextureRegion readRegion; - readRegion.handle = mBlitTexture; + readRegion.handle = mBlitTexture[0]; mFrameAvailable = bgfx::read(readRegion, std::get(mReadData).data()); } break; - case Target::COLOR: - case Target::ID: { + case Target::COLOR: { assert(std::holds_alternative(mReadData)); bgfx::TextureRegion dstRegion; - dstRegion.handle = mBlitTexture; + dstRegion.handle = mBlitTexture[0]; bgfx::TextureRegion srcRegion; srcRegion.handle = srcBuffer; bgfx::blit(mViewOffscreenId, dstRegion, srcRegion); bgfx::TextureRegion readRegion; - readRegion.handle = mBlitTexture; + readRegion.handle = mBlitTexture[0]; mFrameAvailable = bgfx::read(readRegion, std::get(mReadData).data()); } break; + case Target::ID: { + assert(std::holds_alternative(mReadData)); + const bgfx::TextureHandle srcBuffer2 = + bgfx::getTexture(mOffscreenFbh, 1); + + bgfx::TextureRegion dstRegion0; + dstRegion0.handle = mBlitTexture[0]; + bgfx::TextureRegion srcRegion0; + srcRegion0.handle = srcBuffer; + bgfx::blit(mViewOffscreenId, dstRegion0, srcRegion0); + + bgfx::TextureRegion dstRegion1; + dstRegion1.handle = mBlitTexture[1]; + bgfx::TextureRegion srcRegion1; + srcRegion1.handle = srcBuffer2; + bgfx::blit(mViewOffscreenId, dstRegion1, srcRegion1); + + uint8_t* dataPtr = std::get(mReadData).data(); + const uint pixelCount = uint(mBlitSize.x()) * uint(mBlitSize.y()); + + // read schedule reads. We need to wait for the latest one. + bgfx::TextureRegion readRegion0; + readRegion0.handle = mBlitTexture[0]; + bgfx::read(readRegion0, dataPtr); + + bgfx::TextureRegion readRegion1; + readRegion1.handle = mBlitTexture[1]; + mFrameAvailable = bgfx::read(readRegion1, dataPtr + pixelCount * 4); + } break; + default: assert(false && "FRAMEBUFFER submit called with RAW target"); } } @@ -519,14 +602,26 @@ void ReadFromGPUBuffer::performFramebufferRead() const case Target::ID: { assert(std::holds_alternative(mReadData)); const auto& data = std::get(mReadData); - if (data.size() == 4) { + if (data.size() == 8) { + // Full buffer readback: return directly mReadCallback(mReadData); } else { - ByteData idPixel(4); + // Single pixel readback: extract 8 bytes from the combined MRT data + // buffer + ByteData idPixel(8); + const uint pixelCount = uint(mBlitSize.x()) * uint(mBlitSize.y()); const auto offset = - (uint(mPoint.y()) * mBlitSize.x() + uint(mPoint.x())) * 4; - std::copy_n(data.begin() + offset, 4, idPixel.begin()); + uint(mPoint.y()) * mBlitSize.x() + uint(mPoint.x()); + + // First 4 bytes: target 0 (Object ID + Element Type) + std::copy_n(data.begin() + (offset * 4), 4, idPixel.begin()); + // Next 4 bytes: target 1 (Element ID), stored after target 0's data + std::copy_n( + data.begin() + (pixelCount * 4) + (offset * 4), + 4, + idPixel.begin() + 4); + mReadCallback(idPixel); } } break; diff --git a/vclib/render/src/vclib/qt/gui/editor_settings_frames/info_editor_settings_frame.cpp b/vclib/render/src/vclib/qt/gui/editor_settings_frames/info_editor_settings_frame.cpp new file mode 100644 index 0000000000..097385a388 --- /dev/null +++ b/vclib/render/src/vclib/qt/gui/editor_settings_frames/info_editor_settings_frame.cpp @@ -0,0 +1,121 @@ +// VCLib - Visual Computing Library +// Copyright (C) 2021-2026 Visual Computing Lab, ISTI - CNR. +// +// This Source Code Form is subject to the terms of the Mozilla Public License, +// 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/. + +#include + +#include + +#include "ui_info_editor_settings_frame.h" + +namespace vcl::qt { + +InfoEditorSettingsFrame::InfoEditorSettingsFrame( + InfoEditorSettings& sts, + QWidget* parent) : + QFrame(parent), mUI(new Ui::InfoEditorSettingsFrame), mSettings(sts) +{ + mUI->setupUi(this); + + updateGUI(); + + connect( + mUI->highlightWidthSlider, + &QSlider::valueChanged, + this, + &InfoEditorSettingsFrame::onLinesWidthSliderValueChanged); + + connect( + mUI->highlightColorPushButton, + SIGNAL(colorChanged(const QColor&)), + this, + SLOT(onColorChanged(const QColor&))); + + connect( + mUI->textSizeSpinBox, + QOverload::of(&QSpinBox::valueChanged), + this, + &InfoEditorSettingsFrame::onTextSizeChanged); + + connect( + mUI->textColorPushButton, + SIGNAL(colorChanged(const QColor&)), + this, + SLOT(onTextColorChanged(const QColor&))); + + connect( + mUI->resetDefaultButton, + SIGNAL(clicked()), + this, + SLOT(onResetDefaultClicked())); +} + +InfoEditorSettingsFrame::~InfoEditorSettingsFrame() +{ + delete mUI; +} + +void InfoEditorSettingsFrame::updateGUI() +{ + bool b1 = mUI->highlightWidthSlider->blockSignals(true); + bool b2 = mUI->highlightColorPushButton->blockSignals(true); + bool b3 = mUI->textSizeSpinBox->blockSignals(true); + bool b4 = mUI->textColorPushButton->blockSignals(true); + + Color c = mSettings.color; + float thickness = mSettings.thickness; + Color tc = mSettings.textColor; + int textSize = mSettings.textSize; + + mUI->editModeFrame->hide(); + mUI->highlightWidthSlider->setValue(int(thickness)); + mUI->highlightColorPushButton->setBackgroundColor( + QColor(c.red(), c.green(), c.blue(), c.alpha())); + + mUI->textSizeSpinBox->setValue(textSize); + mUI->textColorPushButton->setBackgroundColor( + QColor(tc.red(), tc.green(), tc.blue(), tc.alpha())); + + mUI->highlightWidthSlider->blockSignals(b1); + mUI->highlightColorPushButton->blockSignals(b2); + mUI->textSizeSpinBox->blockSignals(b3); + mUI->textColorPushButton->blockSignals(b4); +} + +void InfoEditorSettingsFrame::onLinesWidthSliderValueChanged(int value) +{ + mSettings.thickness = float(value); + emit settingsUpdated(); +} + +void InfoEditorSettingsFrame::onColorChanged(const QColor& c) +{ + mSettings.color = + Color(c.red(), c.green(), c.blue(), c.alpha()); + emit settingsUpdated(); +} + +void InfoEditorSettingsFrame::onTextSizeChanged(int value) +{ + mSettings.textSize = value; + emit settingsUpdated(); +} + +void InfoEditorSettingsFrame::onTextColorChanged(const QColor& c) +{ + mSettings.textColor = + Color(c.red(), c.green(), c.blue(), c.alpha()); + emit settingsUpdated(); +} + +void InfoEditorSettingsFrame::onResetDefaultClicked() +{ + mSettings.resetDefaults(); + updateGUI(); + emit settingsUpdated(); +} + +} // namespace vcl::qt diff --git a/vclib/render/src/vclib/qt/gui/editor_settings_frames/info_editor_settings_frame.ui b/vclib/render/src/vclib/qt/gui/editor_settings_frames/info_editor_settings_frame.ui new file mode 100644 index 0000000000..a1394b7cf3 --- /dev/null +++ b/vclib/render/src/vclib/qt/gui/editor_settings_frames/info_editor_settings_frame.ui @@ -0,0 +1,139 @@ + + + vcl::qt::InfoEditorSettingsFrame + + + + 0 + 0 + 400 + 181 + + + + Frame + + + + + + + + + + + + + Highlight Color: + + + + + + + 5 + + + 100 + + + 20 + + + + + + + Highlight Width: + + + + + + + Text Color: + + + + + + + + + + + + + + 1 + + + 10 + + + 2 + + + Qt::Orientation::Horizontal + + + + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Plain + + + + + + + Text Size: + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + Reset Defaults + + + + + + + + + + vcl::qt::EditModeSettingsFrame + QFrame +
vclib/qt/gui/editor_settings_frames/edit_mode_settings_frame.h
+ 1 +
+ + vcl::qt::ColorPushButton + QPushButton +
vclib/qt/gui/color_push_button.h
+
+
+ + +
diff --git a/vclib/render/src/vclib/render/drawable/drawable_object_vector.cpp b/vclib/render/src/vclib/render/drawable/drawable_object_vector.cpp index f63d7b941c..2ff6f58711 100644 --- a/vclib/render/src/vclib/render/drawable/drawable_object_vector.cpp +++ b/vclib/render/src/vclib/render/drawable/drawable_object_vector.cpp @@ -31,8 +31,8 @@ void DrawableObjectVector::drawId(const DrawObjectSettings& settings) DrawObjectSettings sts = settings; if (isVisible()) { for (size_t idx = 0; idx < Base::size(); idx++) { - // TODO: combine idx with the content of settings.objectId - sts.objectId = idx; + // The objectId is shifted by 16 bits as expected by DrawableObject + sts.objectId = (idx << 16); const auto& p = Base::at(idx);