From 06922c353a6145ce673050aab1d51058d8b74a18 Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 20:37:06 +0800 Subject: [PATCH 01/22] refactor: rename imview module to viewer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mechanical rename of the app-shell module so the directory name matches the central class users instantiate. Reads as `viewer/viewer.hpp` → `quickviz::Viewer`, the same pattern as `boost::filesystem`. Renames performed: - src/imview/ → src/viewer/ - src/viewer/include/imview/ → src/viewer/include/viewer/ - tests/integration/test_imview_integration.cpp → test_viewer_integration.cpp - All `imview` tokens → `viewer` (CMake target, link directives, includes, doc references) - All `IMVIEW` tokens → `VIEWER` (header guards, IMVIEW_WITH_GLAD → VIEWER_WITH_GLAD CMake option) No behavior changes. Build configures and links cleanly. Pre-existing PCLLoaderTest.InvalidFileError still fails (unrelated, tracked in TODO). Library boundary holds: src/ contains no `#include` of sample/ paths. Part of the visualization-first module reorg; subsequent commits rename gldraw → scene, split widget into canvas + plot, and merge cvdraw into a new image module. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitmodules | 3 +++ CLAUDE.md | 10 +++++----- CMakeLists.txt | 4 ++-- README.md | 6 +++--- TODO.md | 2 +- docs/imview_design.md | 12 +++++------ docs/notes/core_imview_code_quality.md | 14 ++++++------- docs/notes/session_summary_2025-01-28.md | 4 ++-- docs/notes/shader_compilation_linking_fix.md | 6 +++--- docs/notes/unified_input_architecture.md | 6 +++--- sample/editor/README.md | 2 +- sample/editor/main.cpp | 4 ++-- sample/editor/panels/editor_tool_panel.hpp | 2 +- sample/editor/panels/history_panel.hpp | 2 +- sample/pointcloud_viewer/main.cpp | 6 +++--- .../point_cloud_info_panel.hpp | 2 +- .../point_cloud_tool_panel.hpp | 2 +- sample/quickviz_demo_app/CMakeLists.txt | 2 +- sample/quickviz_demo_app/main.cpp | 2 +- .../quickviz_demo_app/panels/config_panel.cpp | 2 +- .../quickviz_demo_app/panels/config_panel.hpp | 2 +- .../panels/console_panel.cpp | 4 ++-- .../panels/console_panel.hpp | 2 +- .../panels/main_docking_panel.hpp | 2 +- sample/quickviz_demo_app/panels/menu_bar.hpp | 2 +- .../quickviz_demo_app/panels/scene_panel.cpp | 2 +- .../quickviz_demo_app/panels/scene_panel.hpp | 4 ++-- .../quickviz_application.hpp | 4 ++-- src/CMakeLists.txt | 2 +- src/core/include/core/event/input_event.hpp | 2 +- src/core/include/core/event/input_mapping.hpp | 2 +- src/core/test/CMakeLists.txt | 8 ++++---- src/core/test/unit_test/CMakeLists.txt | 4 ++-- src/gldraw/CMakeLists.txt | 6 +++--- .../include/gldraw/camera_control_config.hpp | 2 +- src/gldraw/include/gldraw/gl_scene_panel.hpp | 4 ++-- src/gldraw/include/gldraw/gl_viewer.hpp | 4 ++-- .../include/gldraw/scene_input_handler.hpp | 6 +++--- .../include/gldraw/tools/interaction_tool.hpp | 2 +- src/gldraw/src/gl_scene_panel.cpp | 6 +++--- src/gldraw/src/gl_viewer.cpp | 2 +- src/gldraw/src/renderable/mesh.cpp | 2 +- src/gldraw/src/scene_input_handler.cpp | 2 +- src/gldraw/test/feature/test_camera.cpp | 4 ++-- .../feature/test_camera_configuration.cpp | 4 ++-- .../feature/test_camera_control_mappings.cpp | 4 ++-- .../test/feature/test_gl_scene_panel.cpp | 4 ++-- src/gldraw/test/feature/test_layer_system.cpp | 4 ++-- src/gldraw/test/feature/test_robot_frames.cpp | 4 ++-- .../feature/test_visual_feedback_system.cpp | 4 ++-- src/gldraw/test/input/test_gamepad_input.cpp | 4 ++-- .../test/selection/selection_test_utils.hpp | 8 ++++---- .../test/selection/test_object_selection.cpp | 8 ++++---- src/gldraw/test/test_camera_raw.cpp | 2 +- src/gldraw/test/test_canvas_st.cpp | 4 ++-- src/gldraw/test/test_coordinate_system.cpp | 4 ++-- src/gldraw/test/test_framebuffer.cpp | 2 +- src/gldraw/test/test_layer_system_box.cpp | 4 ++-- src/gldraw/test/test_nav_map_rendering.cpp | 4 ++-- .../test_point_cloud_buffer_strategies.cpp | 4 ++-- src/gldraw/test/test_point_cloud_realtime.cpp | 4 ++-- src/gldraw/test/test_primitive_drawing.cpp | 4 ++-- src/gldraw/test/test_shader.cpp | 2 +- src/pcl_bridge/test/test_pcd.cpp | 4 ++-- src/pcl_bridge/test/test_pcl_bridge.cpp | 4 ++-- .../test/test_pcl_loader_render.cpp | 6 +++--- src/pcl_bridge/test/unit_test/CMakeLists.txt | 4 ++-- src/{imview => viewer}/CMakeLists.txt | 16 +++++++-------- .../imview => viewer/include/viewer}/box.hpp | 6 +++--- .../include/viewer}/fonts.hpp | 0 .../include/viewer}/input/gamepad_manager.hpp | 6 +++--- .../viewer}/input/imgui_input_utils.hpp | 8 ++++---- .../viewer}/input/input_dispatcher.hpp | 12 +++++------ .../include/viewer}/input/input_manager.hpp | 10 +++++----- .../include/viewer}/input/input_policy.hpp | 6 +++--- .../include/viewer}/input/input_types.hpp | 0 .../include/viewer}/interface/container.hpp | 2 +- .../include/viewer}/interface/renderable.hpp | 0 .../include/viewer}/interface/resizable.hpp | 2 +- .../viewer}/logging/app_log_handler.hpp | 2 +- .../include/viewer}/logging/log_processor.hpp | 0 .../include/viewer}/panel.hpp | 14 ++++++------- .../include/viewer}/popup.hpp | 6 +++--- .../include/viewer}/popup_manager.hpp | 2 +- .../include/viewer}/scene_object.hpp | 4 ++-- .../include/viewer}/styling.hpp | 0 .../include/viewer}/terminal/tui_panel.hpp | 8 ++++---- .../include/viewer}/terminal/tui_text.hpp | 6 +++--- .../include/viewer}/terminal/tui_viewer.hpp | 10 +++++----- .../include/viewer}/viewer.hpp | 12 +++++------ .../include/viewer}/window.hpp | 10 +++++----- src/{imview => viewer}/src/box.cpp | 4 ++-- src/{imview => viewer}/src/fonts.cpp | 2 +- .../src/input/gamepad_manager.cpp | 2 +- .../src/input/imgui_input_utils.cpp | 4 ++-- .../src/input/input_dispatcher.cpp | 2 +- .../src/input/input_manager.cpp | 2 +- .../src/logging/app_log_handler.cpp | 2 +- .../src/logging/log_processor.cpp | 2 +- .../src/opengl_capability_checker.cpp | 2 +- .../src/opengl_capability_checker.hpp | 0 src/{imview => viewer}/src/panel.cpp | 4 ++-- src/{imview => viewer}/src/popup.cpp | 2 +- src/{imview => viewer}/src/popup_manager.cpp | 2 +- src/{imview => viewer}/src/scene_object.cpp | 2 +- .../src/terminal/tui_panel.cpp | 2 +- .../src/terminal/tui_text.cpp | 2 +- .../src/terminal/tui_viewer.cpp | 2 +- src/{imview => viewer}/src/viewer.cpp | 6 +++--- src/{imview => viewer}/src/window.cpp | 8 ++++---- src/{imview => viewer}/src/yoga_utils.cpp | 0 src/{imview => viewer}/src/yoga_utils.hpp | 2 +- src/{imview => viewer}/test/CMakeLists.txt | 8 ++++---- .../test/feature/CMakeLists.txt | 16 +++++++-------- .../gl_triangle_scene_object.hpp | 2 +- .../scene_objects/imgui_fixed_panel.hpp | 4 ++-- .../feature/scene_objects/imtext_panel.hpp | 2 +- .../scene_objects/opengl_scene_object.hpp | 2 +- .../test/feature/test_auto_layout.cpp | 4 ++-- .../test/feature/test_centralized_input.cpp | 6 +++--- .../test/feature/test_joystick_input.cpp | 6 +++--- .../feature/test_keyboard_mouse_input.cpp | 4 ++-- .../test/feature/test_popup.cpp | 10 +++++----- .../test/feature/test_tui_composer.cpp | 4 ++-- .../test/feature/test_viewer.cpp | 4 ++-- .../test/feature/test_window.cpp | 2 +- src/{imview => viewer}/test/test_box.cpp | 4 ++-- .../test/test_box_inside_box.cpp | 4 ++-- src/{imview => viewer}/test/test_panel.cpp | 4 ++-- .../test/test_viewer_fonts.cpp | 2 +- .../test/test_window_gl_triangle.cpp | 2 +- src/widget/CMakeLists.txt | 2 +- .../widget/buffered_cv_image_widget.hpp | 2 +- src/widget/include/widget/cairo_widget.hpp | 2 +- src/widget/include/widget/cv_image_widget.hpp | 2 +- .../include/widget/rt_line_plot_widget.hpp | 2 +- .../test/test_buffered_cv_image_widget.cpp | 4 ++-- src/widget/test/test_cairo_context.cpp | 4 ++-- src/widget/test/test_cairo_draw.cpp | 6 +++--- src/widget/test/test_cairo_normalize.cpp | 6 +++--- src/widget/test/test_cairo_widget.cpp | 2 +- src/widget/test/test_cv_image_widget.cpp | 2 +- src/widget/test/test_implot_widget.cpp | 2 +- src/widget/test/test_plot_buffer.cpp | 4 ++-- tests/CMakeLists.txt | 20 +++++++++---------- tests/README.md | 2 +- tests/integration/test_renderer_pipeline.cpp | 2 +- ...ration.cpp => test_viewer_integration.cpp} | 12 +++++------ third_party/imcore/implot3d | 1 + 149 files changed, 318 insertions(+), 314 deletions(-) rename src/{imview => viewer}/CMakeLists.txt (84%) rename src/{imview/include/imview => viewer/include/viewer}/box.hpp (91%) rename src/{imview/include/imview => viewer/include/viewer}/fonts.hpp (100%) rename src/{imview/include/imview => viewer/include/viewer}/input/gamepad_manager.hpp (97%) rename src/{imview/include/imview => viewer/include/viewer}/input/imgui_input_utils.hpp (96%) rename src/{imview/include/imview => viewer/include/viewer}/input/input_dispatcher.hpp (89%) rename src/{imview/include/imview => viewer/include/viewer}/input/input_manager.hpp (91%) rename src/{imview/include/imview => viewer/include/viewer}/input/input_policy.hpp (98%) rename src/{imview/include/imview => viewer/include/viewer}/input/input_types.hpp (100%) rename src/{imview/include/imview => viewer/include/viewer}/interface/container.hpp (93%) rename src/{imview/include/imview => viewer/include/viewer}/interface/renderable.hpp (100%) rename src/{imview/include/imview => viewer/include/viewer}/interface/resizable.hpp (98%) rename src/{imview/include/imview => viewer/include/viewer}/logging/app_log_handler.hpp (97%) rename src/{imview/include/imview => viewer/include/viewer}/logging/log_processor.hpp (100%) rename src/{imview/include/imview => viewer/include/viewer}/panel.hpp (88%) rename src/{imview/include/imview => viewer/include/viewer}/popup.hpp (77%) rename src/{imview/include/imview => viewer/include/viewer}/popup_manager.hpp (97%) rename src/{imview/include/imview => viewer/include/viewer}/scene_object.hpp (97%) rename src/{imview/include/imview => viewer/include/viewer}/styling.hpp (100%) rename src/{imview/include/imview => viewer/include/viewer}/terminal/tui_panel.hpp (82%) rename src/{imview/include/imview => viewer/include/viewer}/terminal/tui_text.hpp (93%) rename src/{imview/include/imview => viewer/include/viewer}/terminal/tui_viewer.hpp (80%) rename src/{imview/include/imview => viewer/include/viewer}/viewer.hpp (90%) rename src/{imview/include/imview => viewer/include/viewer}/window.hpp (92%) rename src/{imview => viewer}/src/box.cpp (97%) rename src/{imview => viewer}/src/fonts.cpp (98%) rename src/{imview => viewer}/src/input/gamepad_manager.cpp (99%) rename src/{imview => viewer}/src/input/imgui_input_utils.cpp (99%) rename src/{imview => viewer}/src/input/input_dispatcher.cpp (98%) rename src/{imview => viewer}/src/input/input_manager.cpp (96%) rename src/{imview => viewer}/src/logging/app_log_handler.cpp (82%) rename src/{imview => viewer}/src/logging/log_processor.cpp (98%) rename src/{imview => viewer}/src/opengl_capability_checker.cpp (99%) rename src/{imview => viewer}/src/opengl_capability_checker.hpp (100%) rename src/{imview => viewer}/src/panel.cpp (99%) rename src/{imview => viewer}/src/popup.cpp (98%) rename src/{imview => viewer}/src/popup_manager.cpp (98%) rename src/{imview => viewer}/src/scene_object.cpp (99%) rename src/{imview => viewer}/src/terminal/tui_panel.cpp (97%) rename src/{imview => viewer}/src/terminal/tui_text.cpp (95%) rename src/{imview => viewer}/src/terminal/tui_viewer.cpp (97%) rename src/{imview => viewer}/src/viewer.cpp (99%) rename src/{imview => viewer}/src/window.cpp (98%) rename src/{imview => viewer}/src/yoga_utils.cpp (100%) rename src/{imview => viewer}/src/yoga_utils.hpp (96%) rename src/{imview => viewer}/test/CMakeLists.txt (59%) rename src/{imview => viewer}/test/feature/CMakeLists.txt (54%) rename src/{imview => viewer}/test/feature/scene_objects/gl_triangle_scene_object.hpp (99%) rename src/{imview => viewer}/test/feature/scene_objects/imgui_fixed_panel.hpp (95%) rename src/{imview => viewer}/test/feature/scene_objects/imtext_panel.hpp (96%) rename src/{imview => viewer}/test/feature/scene_objects/opengl_scene_object.hpp (96%) rename src/{imview => viewer}/test/feature/test_auto_layout.cpp (97%) rename src/{imview => viewer}/test/feature/test_centralized_input.cpp (97%) rename src/{imview => viewer}/test/feature/test_joystick_input.cpp (98%) rename src/{imview => viewer}/test/feature/test_keyboard_mouse_input.cpp (99%) rename src/{imview => viewer}/test/feature/test_popup.cpp (91%) rename src/{imview => viewer}/test/feature/test_tui_composer.cpp (96%) rename src/{imview => viewer}/test/feature/test_viewer.cpp (98%) rename src/{imview => viewer}/test/feature/test_window.cpp (97%) rename src/{imview => viewer}/test/test_box.cpp (96%) rename src/{imview => viewer}/test/test_box_inside_box.cpp (97%) rename src/{imview => viewer}/test/test_panel.cpp (98%) rename src/{imview => viewer}/test/test_viewer_fonts.cpp (98%) rename src/{imview => viewer}/test/test_window_gl_triangle.cpp (99%) rename tests/integration/{test_imview_integration.cpp => test_viewer_integration.cpp} (95%) create mode 160000 third_party/imcore/implot3d diff --git a/.gitmodules b/.gitmodules index 72bdfad..4294597 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "third_party/stb"] path = third_party/stb url = https://github.com/rxdu/stb.git +[submodule "third_party/imcore/implot3d"] + path = third_party/imcore/implot3d + url = https://github.com/brenocq/implot3d diff --git a/CLAUDE.md b/CLAUDE.md index 4bf38cc..b7cf63e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,7 +5,7 @@ This document provides comprehensive guidance for working with the QuickViz C++ ## Project Overview QuickViz is a C++ visualization library for robotics applications, providing: -- **imview**: Automatic layout management and UI widgets (buttons, sliders, text boxes) +- **viewer**: Automatic layout management and UI widgets (buttons, sliders, text boxes) - **gldraw**: 2D/3D real-time rendering with OpenGL - **widget**: Cairo-based drawing and plotting widgets - **core**: Event system, buffers, and shared utilities @@ -70,7 +70,7 @@ Create (or split) a module when: ``` src/ ├── core/ # Event system, buffers, utilities (depends on nothing) -├── imview/ # GLFW window management, ImGui integration +├── viewer/ # GLFW window management, ImGui integration ├── widget/ # Cairo drawing, image widgets, plotting ├── gldraw/ # OpenGL 3D rendering, point clouds, textures ├── pcl_bridge/ # Optional PCL adapter (file loading, conversions) @@ -105,7 +105,7 @@ operations) live in `sample/` and consume the library, never the reverse. ### Key Design Patterns -#### 1. Scene Object Hierarchy (imview) +#### 1. Scene Object Hierarchy (viewer) - `Window` → `Viewer` → `SceneObject` - `SceneObject` implements: `Renderable`, `Resizable`, `InputHandler` - `Panel` extends `SceneObject` for ImGui panels @@ -153,7 +153,7 @@ make -j8 - `QUICKVIZ_DEV_MODE`: Development mode, forces tests (OFF by default) - `ENABLE_AUTO_LAYOUT`: Enable Yoga-based automatic layout (ON by default, requires C++20) - `BUILD_QUICKVIZ_APP`: Build the quickviz application (OFF by default) -- `IMVIEW_WITH_GLAD`: Integrate GLAD for OpenGL loading (ON by default) +- `VIEWER_WITH_GLAD`: Integrate GLAD for OpenGL loading (ON by default) - `STATIC_CHECK`: Enable cppcheck static analysis (OFF by default) ### Dependencies @@ -388,7 +388,7 @@ ctest --output-on-failure 4. Add to `GlSceneManager` in application **Create Custom UI Panel**: -1. Inherit from `Panel` in `src/imview/` +1. Inherit from `Panel` in `src/viewer/` 2. Override `Begin()` and `End()` methods with ImGui calls 3. Add panel to `Viewer` or `Box` container diff --git a/CMakeLists.txt b/CMakeLists.txt index cfad8b8..926fb65 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ message(STATUS "------------------------------------------------") ## Project Options option(ENABLE_AUTO_LAYOUT "Enable autolayout" ON) -option(IMVIEW_WITH_GLAD "Integrate glad into imview" ON) +option(VIEWER_WITH_GLAD "Integrate glad into viewer" ON) option(BUILD_QUICKVIZ_APP "Build quickviz app" OFF) option(BUILD_TESTING "Build tests" OFF) option(QUICKVIZ_DEV_MODE "Development mode forces building tests" OFF) @@ -153,7 +153,7 @@ endforeach () # targets to install defined in each module add_library(quickviz INTERFACE) -target_link_libraries(quickviz INTERFACE core imview widget gldraw visualization) +target_link_libraries(quickviz INTERFACE core viewer widget gldraw visualization) # export target configuration include(CMakePackageConfigHelpers) diff --git a/README.md b/README.md index aa56017..4fb8501 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ This repository provides a collection of C++ libraries for creating data visualization and basic UI applications, primarily focused on use cases in robotics. -* For UI design, `imview` includes automatic layout management and widgets such as buttons, sliders, and text boxes. -* For data visualization, `imview` and `renderer` provide a set of API functions to easily plot 2D time-series data, draw 2D primitives and render 3D objects in real-time. +* For UI design, `viewer` includes automatic layout management and widgets such as buttons, sliders, and text boxes. +* For data visualization, `viewer` and `renderer` provide a set of API functions to easily plot 2D time-series data, draw 2D primitives and render 3D objects in real-time. -The design of the libraries is documented in [docs/imview_design.md](docs/imview_design.md) and [docs/renderer_design.md](docs/renderer_design.md). If you are interested in using the libraries in your own project, it's recommended to read the design documents first. Additionally, an app named "quickviz" is included with commonly used data visualization functions (to support development of [libxmotion](https://github.com/rxdu/libxmotion)). It also serves as an example to demonstrate the usage of the `imview` library. +The design of the libraries is documented in [docs/viewer_design.md](docs/viewer_design.md) and [docs/renderer_design.md](docs/renderer_design.md). If you are interested in using the libraries in your own project, it's recommended to read the design documents first. Additionally, an app named "quickviz" is included with commonly used data visualization functions (to support development of [libxmotion](https://github.com/rxdu/libxmotion)). It also serves as an example to demonstrate the usage of the `viewer` library. diff --git a/TODO.md b/TODO.md index 9395b89..be040c9 100644 --- a/TODO.md +++ b/TODO.md @@ -97,7 +97,7 @@ a visualization-justified hook. is in place) **Focus**: Re-anchor the library on visualization, then build the editor sample as the API check. -**Architecture**: Library = `core` + `imview` + `widget` + `gldraw` + +**Architecture**: Library = `core` + `viewer` + `widget` + `gldraw` + `pcl_bridge` + `cvdraw` (optional). Apps live in `sample/`. --- diff --git a/docs/imview_design.md b/docs/imview_design.md index cf22086..0a51d99 100644 --- a/docs/imview_design.md +++ b/docs/imview_design.md @@ -1,20 +1,20 @@ # ImView Design -For GUI applications, imview provides automatic layout management and commonly used UI widgets such as buttons, sliders, and text boxes. +For GUI applications, viewer provides automatic layout management and commonly used UI widgets such as buttons, sliders, and text boxes. ## Implementation ### Core Components -* **Window management**: imview uses GLFW for window management. GLFW is a lightweight library that provides a simple API for creating windows and handling input events. GLFW is cross-platform and supports Windows, macOS, and Linux. -* **UI**: imview uses `Dear ImGui`, a lightweight GUI library that provides a simple API for creating UI elements. -* **2D plotting**: imview uses `ImPlot` for 2D plotting. ImPlot is a lightweight plotting library built on top of `Dear ImGui`. +* **Window management**: viewer uses GLFW for window management. GLFW is a lightweight library that provides a simple API for creating windows and handling input events. GLFW is cross-platform and supports Windows, macOS, and Linux. +* **UI**: viewer uses `Dear ImGui`, a lightweight GUI library that provides a simple API for creating UI elements. +* **2D plotting**: viewer uses `ImPlot` for 2D plotting. ImPlot is a lightweight plotting library built on top of `Dear ImGui`. -The class diagram below shows the main classes in the imview library: +The class diagram below shows the main classes in the viewer library: ```mermaid --- -title: imview core components +title: viewer core components --- classDiagram Window <|-- Viewer diff --git a/docs/notes/core_imview_code_quality.md b/docs/notes/core_imview_code_quality.md index d8aeee6..9431b42 100644 --- a/docs/notes/core_imview_code_quality.md +++ b/docs/notes/core_imview_code_quality.md @@ -2,7 +2,7 @@ Date: 2025-09-01 -This document captures targeted, high‑impact improvements for the `core` and `imview` modules. Items are grouped by module with concrete actions and file pointers. +This document captures targeted, high‑impact improvements for the `core` and `viewer` modules. Items are grouped by module with concrete actions and file pointers. ## Core @@ -49,20 +49,20 @@ This document captures targeted, high‑impact improvements for the `core` and ` ### Centralize input: single InputManager per Window - Problem: `Panel` keeps its own `InputManager` and polls ImGui state; `Window` also owns an `InputManager`. - - Files: `src/imview/include/imview/panel.hpp`, `src/imview/src/panel.cpp`, `src/imview/include/imview/window.hpp`, `src/imview/src/window.cpp` + - Files: `src/viewer/include/viewer/panel.hpp`, `src/viewer/src/panel.cpp`, `src/viewer/include/viewer/window.hpp`, `src/viewer/src/window.cpp` - Improve: - Panels should register as `InputEventHandler`s on the Window’s single `InputManager`; provide `AttachTo(Window&)` or inject via ctor. - Poll ImGui once per frame at the Window and dispatch through the centralized `InputDispatcher`. ### ImGui capture and debug output - Problem: Hidden Ctrl+Shift+K bypass and periodic `std::cout` noise in release. - - File: `src/imview/src/input/imgui_input_utils.cpp` + - File: `src/viewer/src/input/imgui_input_utils.cpp` - Improve: Gate with `#ifdef QUICKVIZ_INPUT_DEBUG` or a runtime flag; route through logger. Make bypass behavior configurable via `InputManager`/Window settings. ### GamepadManager: state ownership, singleton removal path - Problems: - `GamepadManager` is a singleton; `ImGuiInputUtils` uses a static `previous_states` map outside the manager. - - Files: `src/imview/include/imview/input/gamepad_manager.hpp`, `src/imview/src/input/gamepad_manager.cpp`, `src/imview/src/input/imgui_input_utils.cpp` + - Files: `src/viewer/include/viewer/input/gamepad_manager.hpp`, `src/viewer/src/input/gamepad_manager.cpp`, `src/viewer/src/input/imgui_input_utils.cpp` - Improve: - Move previous-state tracking into `GamepadManager` per device and expose delta-friendly queries. - Long term: make `GamepadManager` instance-owned by `Window` (align with `InputManager`). @@ -73,13 +73,13 @@ This document captures targeted, high‑impact improvements for the `core` and ` ### Window lifecycle: GLFW ownership - Problem: `Window::~Window()` calls `glfwTerminate()` unconditionally; breaks multi-window use. - - File: `src/imview/src/window.cpp` + - File: `src/viewer/src/window.cpp` - Improve: Centralize GLFW init/term in an `Application`-level owner or use ref-counting; windows only destroy their own contexts. ### CMake options and link scopes -- Add `option(ENABLE_AUTO_LAYOUT ...)`, `option(ENABLE_TUI_SUPPORT ...)`, `option(IMVIEW_WITH_GLAD ...)` with defaults and help strings. +- Add `option(ENABLE_AUTO_LAYOUT ...)`, `option(ENABLE_TUI_SUPPORT ...)`, `option(VIEWER_WITH_GLAD ...)` with defaults and help strings. - Use proper scope for compile definitions and link interfaces; minimize PUBLIC exposure. - - Files: `src/imview/CMakeLists.txt`, `src/core/CMakeLists.txt` + - Files: `src/viewer/CMakeLists.txt`, `src/core/CMakeLists.txt` ## Cross‑Cutting diff --git a/docs/notes/session_summary_2025-01-28.md b/docs/notes/session_summary_2025-01-28.md index 451cf31..d2267c6 100644 --- a/docs/notes/session_summary_2025-01-28.md +++ b/docs/notes/session_summary_2025-01-28.md @@ -42,7 +42,7 @@ Comprehensive review of GLDraw module and design of enhanced input handling syst **DECISION: Use Core Module (Option 1)** - Extend existing `core` module with InputEvent classes - Leverage existing EventDispatcher and thread-safe infrastructure -- Clean dependency: core → imview → gldraw +- Clean dependency: core → viewer → gldraw **Rationale**: - Reuses robust event system already in core @@ -106,7 +106,7 @@ SelectionManager::Select() ``` InputEvent (core) → InputDispatcher (core) ↓ -InputMapping (imview/gldraw) +InputMapping (viewer/gldraw) ↓ SelectionManager handlers (gldraw) ↓ diff --git a/docs/notes/shader_compilation_linking_fix.md b/docs/notes/shader_compilation_linking_fix.md index e64ec74..44ecf34 100644 --- a/docs/notes/shader_compilation_linking_fix.md +++ b/docs/notes/shader_compilation_linking_fix.md @@ -392,9 +392,9 @@ The fixes are implemented across multiple files: - **Grid Renderer**: `src/renderer/src/renderable/grid.cpp` (lines 96-118) - **Coordinate Frame**: `src/renderer/src/renderable/coordinate_frame.cpp` (lines 160-182) - **Canvas Background**: `src/renderer/src/renderable/canvas.cpp` (lines 340-364) -- **OpenGL Detection**: `src/imview/src/viewer.cpp` (lines 90-91, 418-451) -- **Capability Checker**: `src/imview/include/imview/opengl_capability_checker.hpp` -- **Window Fallbacks**: `src/imview/src/window.cpp` (lines 35-60) +- **OpenGL Detection**: `src/viewer/src/viewer.cpp` (lines 90-91, 418-451) +- **Capability Checker**: `src/viewer/include/viewer/opengl_capability_checker.hpp` +- **Window Fallbacks**: `src/viewer/src/window.cpp` (lines 35-60) ## References diff --git a/docs/notes/unified_input_architecture.md b/docs/notes/unified_input_architecture.md index d905a85..4c66b0c 100644 --- a/docs/notes/unified_input_architecture.md +++ b/docs/notes/unified_input_architecture.md @@ -144,19 +144,19 @@ Input Source → ImGuiInputUtils → InputEvent → InputDispatcher → InputEve ### Actual Implementation -1. **GamepadManager** (src/imview/input/gamepad_manager.hpp) +1. **GamepadManager** (src/viewer/input/gamepad_manager.hpp) - Meyer's Singleton pattern for thread-safe initialization - Direct GLFW polling for multiple gamepad support - Connection/disconnection monitoring with callbacks - Hardware state caching with GamepadState struct -2. **ImGuiInputUtils::PollGamepadEvents()** (src/imview/input/imgui_input_utils.cpp) +2. **ImGuiInputUtils::PollGamepadEvents()** (src/viewer/input/imgui_input_utils.cpp) - Uses GamepadManager instead of ImGui's gamepad system - Proper state tracking with static map (OUTSIDE loop - critical bug fix) - Handles button count changes for hot-plug support - Generates InputEvent objects for unified processing -3. **Viewer Integration** (src/imview/viewer.cpp) +3. **Viewer Integration** (src/viewer/viewer.cpp) - Polls events AFTER CreateNewImGuiFrame() for valid context - One-time handler registration in AddSceneObject() - Proper cleanup in RemoveSceneObject() and destructor diff --git a/sample/editor/README.md b/sample/editor/README.md index 855b702..cdc85a3 100644 --- a/sample/editor/README.md +++ b/sample/editor/README.md @@ -40,5 +40,5 @@ cmake --build build -j ## Boundary rule This directory **may** include from the library's public headers -(`gldraw/`, `imview/`, `pcl_bridge/`, `core/`). It must never be included +(`gldraw/`, `viewer/`, `pcl_bridge/`, `core/`). It must never be included *by* anything in `src/`. The `boundary-check` CI job enforces this. diff --git a/sample/editor/main.cpp b/sample/editor/main.cpp index ed74aa3..8d5cc50 100644 --- a/sample/editor/main.cpp +++ b/sample/editor/main.cpp @@ -19,8 +19,8 @@ #include -#include "imview/box.hpp" -#include "imview/viewer.hpp" +#include "viewer/box.hpp" +#include "viewer/viewer.hpp" #include "gldraw/renderable/grid.hpp" #include "gldraw/renderable/point_cloud.hpp" diff --git a/sample/editor/panels/editor_tool_panel.hpp b/sample/editor/panels/editor_tool_panel.hpp index 91ec8fd..8fd1e16 100644 --- a/sample/editor/panels/editor_tool_panel.hpp +++ b/sample/editor/panels/editor_tool_panel.hpp @@ -10,7 +10,7 @@ #include -#include "imview/panel.hpp" +#include "viewer/panel.hpp" namespace quickviz::editor { diff --git a/sample/editor/panels/history_panel.hpp b/sample/editor/panels/history_panel.hpp index a94452a..ff2b741 100644 --- a/sample/editor/panels/history_panel.hpp +++ b/sample/editor/panels/history_panel.hpp @@ -10,7 +10,7 @@ #include -#include "imview/panel.hpp" +#include "viewer/panel.hpp" namespace quickviz::editor { diff --git a/sample/pointcloud_viewer/main.cpp b/sample/pointcloud_viewer/main.cpp index 68aa443..96f3633 100644 --- a/sample/pointcloud_viewer/main.cpp +++ b/sample/pointcloud_viewer/main.cpp @@ -19,9 +19,9 @@ #include #include -#include "imview/box.hpp" -#include "imview/viewer.hpp" -#include "imview/panel.hpp" +#include "viewer/box.hpp" +#include "viewer/viewer.hpp" +#include "viewer/panel.hpp" #include "gldraw/scene_manager.hpp" #include "gldraw/renderable/grid.hpp" diff --git a/sample/pointcloud_viewer/point_cloud_info_panel.hpp b/sample/pointcloud_viewer/point_cloud_info_panel.hpp index f734e35..50f886f 100644 --- a/sample/pointcloud_viewer/point_cloud_info_panel.hpp +++ b/sample/pointcloud_viewer/point_cloud_info_panel.hpp @@ -9,7 +9,7 @@ #ifndef QUICKVIZ_POINT_CLOUD_INFO_PANEL_HPP #define QUICKVIZ_POINT_CLOUD_INFO_PANEL_HPP -#include "imview/panel.hpp" +#include "viewer/panel.hpp" #include "pcl_bridge/pcl_loader.hpp" namespace quickviz { diff --git a/sample/pointcloud_viewer/point_cloud_tool_panel.hpp b/sample/pointcloud_viewer/point_cloud_tool_panel.hpp index 0060480..9feccf5 100644 --- a/sample/pointcloud_viewer/point_cloud_tool_panel.hpp +++ b/sample/pointcloud_viewer/point_cloud_tool_panel.hpp @@ -9,7 +9,7 @@ #ifndef QUICKVIZ_POINT_CLOUD_TOOL_PANEL_HPP #define QUICKVIZ_POINT_CLOUD_TOOL_PANEL_HPP -#include "imview/panel.hpp" +#include "viewer/panel.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/tools/point_selection_tool.hpp" diff --git a/sample/quickviz_demo_app/CMakeLists.txt b/sample/quickviz_demo_app/CMakeLists.txt index 8a3bf74..83ac024 100644 --- a/sample/quickviz_demo_app/CMakeLists.txt +++ b/sample/quickviz_demo_app/CMakeLists.txt @@ -8,7 +8,7 @@ if (BUILD_QUICKVIZ_APP) panels/scene_panel.cpp panels/config_panel.cpp panels/console_panel.cpp) - target_link_libraries(quickviz_demo_app PRIVATE imview gldraw pcl_bridge) + target_link_libraries(quickviz_demo_app PRIVATE viewer gldraw pcl_bridge) target_include_directories(quickviz_demo_app PRIVATE .) install(TARGETS quickviz_demo_app diff --git a/sample/quickviz_demo_app/main.cpp b/sample/quickviz_demo_app/main.cpp index a98ddb6..939f107 100644 --- a/sample/quickviz_demo_app/main.cpp +++ b/sample/quickviz_demo_app/main.cpp @@ -8,7 +8,7 @@ #include -#include "imview/viewer.hpp" +#include "viewer/viewer.hpp" #include "quickviz_application.hpp" diff --git a/sample/quickviz_demo_app/panels/config_panel.cpp b/sample/quickviz_demo_app/panels/config_panel.cpp index 9d9f812..6ec23f8 100644 --- a/sample/quickviz_demo_app/panels/config_panel.cpp +++ b/sample/quickviz_demo_app/panels/config_panel.cpp @@ -8,7 +8,7 @@ #include "panels/config_panel.hpp" -#include "imview/fonts.hpp" +#include "viewer/fonts.hpp" namespace quickviz { ConfigPanel::ConfigPanel(std::string name) : Panel(name) { diff --git a/sample/quickviz_demo_app/panels/config_panel.hpp b/sample/quickviz_demo_app/panels/config_panel.hpp index c1fa2b0..0a4c67e 100644 --- a/sample/quickviz_demo_app/panels/config_panel.hpp +++ b/sample/quickviz_demo_app/panels/config_panel.hpp @@ -9,7 +9,7 @@ #ifndef QUICKVIZ_CONFIG_PANEL_HPP #define QUICKVIZ_CONFIG_PANEL_HPP -#include "imview/panel.hpp" +#include "viewer/panel.hpp" namespace quickviz { class ConfigPanel : public Panel { diff --git a/sample/quickviz_demo_app/panels/console_panel.cpp b/sample/quickviz_demo_app/panels/console_panel.cpp index 96ea474..543b503 100644 --- a/sample/quickviz_demo_app/panels/console_panel.cpp +++ b/sample/quickviz_demo_app/panels/console_panel.cpp @@ -8,8 +8,8 @@ #include "panels/console_panel.hpp" -#include "imview/fonts.hpp" -#include "imview/logging/app_log_handler.hpp" +#include "viewer/fonts.hpp" +#include "viewer/logging/app_log_handler.hpp" namespace quickviz { ConsolePanel::ConsolePanel(std::string name) : Panel(name) { diff --git a/sample/quickviz_demo_app/panels/console_panel.hpp b/sample/quickviz_demo_app/panels/console_panel.hpp index ddd9152..a9fba1e 100644 --- a/sample/quickviz_demo_app/panels/console_panel.hpp +++ b/sample/quickviz_demo_app/panels/console_panel.hpp @@ -9,7 +9,7 @@ #ifndef QUICKVIZ_CONSOLE_PANEL_HPP #define QUICKVIZ_CONSOLE_PANEL_HPP -#include "imview/panel.hpp" +#include "viewer/panel.hpp" namespace quickviz { class ConsolePanel : public Panel { diff --git a/sample/quickviz_demo_app/panels/main_docking_panel.hpp b/sample/quickviz_demo_app/panels/main_docking_panel.hpp index 0177805..a5db58b 100644 --- a/sample/quickviz_demo_app/panels/main_docking_panel.hpp +++ b/sample/quickviz_demo_app/panels/main_docking_panel.hpp @@ -9,7 +9,7 @@ #ifndef QUICKVIZ_MAIN_DOCKING_PANEL_HPP #define QUICKVIZ_MAIN_DOCKING_PANEL_HPP -#include "imview/panel.hpp" +#include "viewer/panel.hpp" #include "panels/menu_bar.hpp" #include "panels/config_panel.hpp" diff --git a/sample/quickviz_demo_app/panels/menu_bar.hpp b/sample/quickviz_demo_app/panels/menu_bar.hpp index 0eae9ae..c5a96aa 100644 --- a/sample/quickviz_demo_app/panels/menu_bar.hpp +++ b/sample/quickviz_demo_app/panels/menu_bar.hpp @@ -11,7 +11,7 @@ #include -#include "imview/panel.hpp" +#include "viewer/panel.hpp" namespace quickviz { class MenuBar : public Panel { diff --git a/sample/quickviz_demo_app/panels/scene_panel.cpp b/sample/quickviz_demo_app/panels/scene_panel.cpp index d61c600..06c6f90 100644 --- a/sample/quickviz_demo_app/panels/scene_panel.cpp +++ b/sample/quickviz_demo_app/panels/scene_panel.cpp @@ -13,7 +13,7 @@ #include #include -#include "imview/logging/app_log_handler.hpp" +#include "viewer/logging/app_log_handler.hpp" namespace quickviz { ScenePanel::ScenePanel(const std::string& panel_name) diff --git a/sample/quickviz_demo_app/panels/scene_panel.hpp b/sample/quickviz_demo_app/panels/scene_panel.hpp index 98a3ab9..fa73164 100644 --- a/sample/quickviz_demo_app/panels/scene_panel.hpp +++ b/sample/quickviz_demo_app/panels/scene_panel.hpp @@ -11,8 +11,8 @@ #include -#include "imview/component/opengl/renderer/grid.hpp" -#include "imview/component/opengl/gl_scene_manager.hpp" +#include "viewer/component/opengl/renderer/grid.hpp" +#include "viewer/component/opengl/gl_scene_manager.hpp" namespace quickviz { class ScenePanel : public GlSceneManager { diff --git a/sample/quickviz_demo_app/quickviz_application.hpp b/sample/quickviz_demo_app/quickviz_application.hpp index 17a302e..3ede94c 100644 --- a/sample/quickviz_demo_app/quickviz_application.hpp +++ b/sample/quickviz_demo_app/quickviz_application.hpp @@ -9,8 +9,8 @@ #ifndef QUICKVIZ_QUICKVIZ_APPLICATION_HPP #define QUICKVIZ_QUICKVIZ_APPLICATION_HPP -#include "imview/viewer.hpp" -#include "imview/box.hpp" +#include "viewer/viewer.hpp" +#include "viewer/box.hpp" #include "panels/main_docking_panel.hpp" #include "data_reader.hpp" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dc88f02..9707024 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,6 @@ # core modules add_subdirectory(core) -add_subdirectory(imview) +add_subdirectory(viewer) add_subdirectory(widget) add_subdirectory(gldraw) add_subdirectory(pcl_bridge) diff --git a/src/core/include/core/event/input_event.hpp b/src/core/include/core/event/input_event.hpp index 701d8aa..1d5f40e 100644 --- a/src/core/include/core/event/input_event.hpp +++ b/src/core/include/core/event/input_event.hpp @@ -56,7 +56,7 @@ struct ModifierKeys { bool IsEmpty() const { return !ctrl && !shift && !alt && !super; } }; -// Use existing MouseButton enum from imview/input/mouse.hpp +// Use existing MouseButton enum from viewer/input/mouse.hpp // No need to redefine it here class InputEvent : public BaseEvent { diff --git a/src/core/include/core/event/input_mapping.hpp b/src/core/include/core/event/input_mapping.hpp index f3c5be2..cb4cfec 100644 --- a/src/core/include/core/event/input_mapping.hpp +++ b/src/core/include/core/event/input_mapping.hpp @@ -228,7 +228,7 @@ class InputMapping { private: void SetupDefaultMappings() { - // Mouse button constants (from imview/input/mouse.hpp) + // Mouse button constants (from viewer/input/mouse.hpp) const int kLeft = 0; const int kRight = 1; const int kMiddle = 2; diff --git a/src/core/test/CMakeLists.txt b/src/core/test/CMakeLists.txt index 8201c31..20e8ace 100644 --- a/src/core/test/CMakeLists.txt +++ b/src/core/test/CMakeLists.txt @@ -3,16 +3,16 @@ add_subdirectory(unit_test) add_executable(test_event test_event.cpp) -target_link_libraries(test_event PRIVATE imview) +target_link_libraries(test_event PRIVATE viewer) add_executable(test_async_event test_async_event.cpp) -target_link_libraries(test_async_event PRIVATE imview) +target_link_libraries(test_async_event PRIVATE viewer) find_package(OpenCV QUIET) if (OpenCV_FOUND) add_executable(test_double_buffer test_double_buffer.cpp) - target_link_libraries(test_double_buffer PRIVATE imview ${OpenCV_LIBS}) + target_link_libraries(test_double_buffer PRIVATE viewer ${OpenCV_LIBS}) add_executable(test_ring_buffer test_ring_buffer.cpp) - target_link_libraries(test_ring_buffer PRIVATE imview ${OpenCV_LIBS}) + target_link_libraries(test_ring_buffer PRIVATE viewer ${OpenCV_LIBS}) endif () \ No newline at end of file diff --git a/src/core/test/unit_test/CMakeLists.txt b/src/core/test/unit_test/CMakeLists.txt index b570bc6..0347dfe 100644 --- a/src/core/test/unit_test/CMakeLists.txt +++ b/src/core/test/unit_test/CMakeLists.txt @@ -6,8 +6,8 @@ add_executable(core_unit_tests test_event_system.cpp test_thread_safe_queue.cpp test_buffer_registry.cpp) -target_link_libraries(core_unit_tests PRIVATE gtest_main gmock imview) -# get_target_property(PRIVATE_HEADERS imview INCLUDE_DIRECTORIES) +target_link_libraries(core_unit_tests PRIVATE gtest_main gmock viewer) +# get_target_property(PRIVATE_HEADERS viewer INCLUDE_DIRECTORIES) target_include_directories(core_unit_tests PRIVATE ${PRIVATE_HEADERS}) gtest_discover_tests(core_unit_tests) diff --git a/src/gldraw/CMakeLists.txt b/src/gldraw/CMakeLists.txt index 2bddbae..3ead424 100644 --- a/src/gldraw/CMakeLists.txt +++ b/src/gldraw/CMakeLists.txt @@ -52,12 +52,12 @@ add_library(gldraw src/tools/interaction_tool.cpp src/tools/point_selection_tool.cpp ) -target_link_libraries(gldraw PUBLIC core imcore imview stb +target_link_libraries(gldraw PUBLIC core imcore viewer stb Threads::Threads OpenGL::GL) -if (IMVIEW_WITH_GLAD) +if (VIEWER_WITH_GLAD) target_link_libraries(gldraw PUBLIC glad) - target_compile_definitions(gldraw PUBLIC IMVIEW_WITH_GLAD) + target_compile_definitions(gldraw PUBLIC VIEWER_WITH_GLAD) endif () target_include_directories(gldraw PUBLIC $ diff --git a/src/gldraw/include/gldraw/camera_control_config.hpp b/src/gldraw/include/gldraw/camera_control_config.hpp index 2beaf02..1b1be10 100644 --- a/src/gldraw/include/gldraw/camera_control_config.hpp +++ b/src/gldraw/include/gldraw/camera_control_config.hpp @@ -9,7 +9,7 @@ #ifndef GLDRAW_CAMERA_CONTROL_CONFIG_HPP #define GLDRAW_CAMERA_CONTROL_CONFIG_HPP -#include "imview/input/input_types.hpp" +#include "viewer/input/input_types.hpp" #include "core/event/input_event.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/gl_scene_panel.hpp b/src/gldraw/include/gldraw/gl_scene_panel.hpp index 930a2aa..766b724 100644 --- a/src/gldraw/include/gldraw/gl_scene_panel.hpp +++ b/src/gldraw/include/gldraw/gl_scene_panel.hpp @@ -16,7 +16,7 @@ #include -#include "imview/panel.hpp" +#include "viewer/panel.hpp" #include "gldraw/scene_manager.hpp" #include "gldraw/interface/opengl_object.hpp" #include "gldraw/camera.hpp" @@ -195,7 +195,7 @@ class GlScenePanel : public Panel { // UI state bool show_rendering_info_ = true; - // Modern imview-based input system - all input goes through this handler + // Modern viewer-based input system - all input goes through this handler std::shared_ptr scene_input_handler_; // Cached content position and size for coordinate conversion diff --git a/src/gldraw/include/gldraw/gl_viewer.hpp b/src/gldraw/include/gldraw/gl_viewer.hpp index 865f82b..9d1abb8 100644 --- a/src/gldraw/include/gldraw/gl_viewer.hpp +++ b/src/gldraw/include/gldraw/gl_viewer.hpp @@ -20,8 +20,8 @@ #include -#include "imview/viewer.hpp" -#include "imview/box.hpp" +#include "viewer/viewer.hpp" +#include "viewer/box.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/renderable/grid.hpp" #include "gldraw/renderable/coordinate_frame.hpp" diff --git a/src/gldraw/include/gldraw/scene_input_handler.hpp b/src/gldraw/include/gldraw/scene_input_handler.hpp index e4b020e..d9cf6f0 100644 --- a/src/gldraw/include/gldraw/scene_input_handler.hpp +++ b/src/gldraw/include/gldraw/scene_input_handler.hpp @@ -1,7 +1,7 @@ /* * @file scene_input_handler.hpp * @date 9/1/25 - * @brief Bridge between imview input system and gldraw 3D interactions + * @brief Bridge between viewer input system and gldraw 3D interactions * * @copyright Copyright (c) 2025 Ruixiang Du (rdu) */ @@ -12,7 +12,7 @@ #include #include -#include "imview/input/input_dispatcher.hpp" +#include "viewer/input/input_dispatcher.hpp" #include "core/event/input_event.hpp" #include "gldraw/camera.hpp" #include "gldraw/camera_controller.hpp" @@ -27,7 +27,7 @@ class SceneManager; /** * @brief Input handler bridge for 3D scene interactions * - * Bridges imview's InputEventHandler system with gldraw's 3D-specific + * Bridges viewer's InputEventHandler system with gldraw's 3D-specific * functionality like camera control and object selection. */ class SceneInputHandler : public InputEventHandler { diff --git a/src/gldraw/include/gldraw/tools/interaction_tool.hpp b/src/gldraw/include/gldraw/tools/interaction_tool.hpp index 9860b11..d3e00e4 100644 --- a/src/gldraw/include/gldraw/tools/interaction_tool.hpp +++ b/src/gldraw/include/gldraw/tools/interaction_tool.hpp @@ -14,7 +14,7 @@ #include #include -#include "imview/input/input_dispatcher.hpp" +#include "viewer/input/input_dispatcher.hpp" #include "core/event/input_event.hpp" namespace quickviz { diff --git a/src/gldraw/src/gl_scene_panel.cpp b/src/gldraw/src/gl_scene_panel.cpp index 7e533c8..b9492a8 100644 --- a/src/gldraw/src/gl_scene_panel.cpp +++ b/src/gldraw/src/gl_scene_panel.cpp @@ -13,8 +13,8 @@ #include #include "imgui.h" -#include "imview/fonts.hpp" -#include "imview/input/input_policy.hpp" +#include "viewer/fonts.hpp" +#include "viewer/input/input_policy.hpp" #include "gldraw/renderable/point_cloud.hpp" #include "gldraw/selection_manager.hpp" @@ -221,7 +221,7 @@ void GlScenePanel::RenderInfoOverlay(const ImVec2& content_size, draw_list->AddText(text_pos, text_color, fps_text); } -// New imview-based input handling methods +// New viewer-based input handling methods bool GlScenePanel::OnInputEvent(const InputEvent& event) { if (scene_input_handler_) { // Update viewport size for the handler diff --git a/src/gldraw/src/gl_viewer.cpp b/src/gldraw/src/gl_viewer.cpp index 6d05639..5395f61 100644 --- a/src/gldraw/src/gl_viewer.cpp +++ b/src/gldraw/src/gl_viewer.cpp @@ -13,7 +13,7 @@ #include #include -#include "imview/styling.hpp" +#include "viewer/styling.hpp" namespace quickviz { diff --git a/src/gldraw/src/renderable/mesh.cpp b/src/gldraw/src/renderable/mesh.cpp index db4e0da..bc92d82 100644 --- a/src/gldraw/src/renderable/mesh.cpp +++ b/src/gldraw/src/renderable/mesh.cpp @@ -9,7 +9,7 @@ #include "gldraw/renderable/mesh.hpp" -#ifdef IMVIEW_WITH_GLAD +#ifdef VIEWER_WITH_GLAD #include #else #include diff --git a/src/gldraw/src/scene_input_handler.cpp b/src/gldraw/src/scene_input_handler.cpp index 74a8d26..358ceb1 100644 --- a/src/gldraw/src/scene_input_handler.cpp +++ b/src/gldraw/src/scene_input_handler.cpp @@ -10,7 +10,7 @@ #include "gldraw/scene_manager.hpp" #include "gldraw/camera_control_config.hpp" #include "gldraw/tools/interaction_tool.hpp" -#include "imview/input/input_types.hpp" +#include "viewer/input/input_types.hpp" #include "core/event/input_mapping.hpp" namespace quickviz { diff --git a/src/gldraw/test/feature/test_camera.cpp b/src/gldraw/test/feature/test_camera.cpp index 76f75a7..8bbd390 100644 --- a/src/gldraw/test/feature/test_camera.cpp +++ b/src/gldraw/test/feature/test_camera.cpp @@ -11,8 +11,8 @@ #include #include -#include "imview/viewer.hpp" -#include "imview/box.hpp" +#include "viewer/viewer.hpp" +#include "viewer/box.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/renderable/grid.hpp" #include "gldraw/renderable/point_cloud.hpp" diff --git a/src/gldraw/test/feature/test_camera_configuration.cpp b/src/gldraw/test/feature/test_camera_configuration.cpp index 03f52d5..3af20c5 100644 --- a/src/gldraw/test/feature/test_camera_configuration.cpp +++ b/src/gldraw/test/feature/test_camera_configuration.cpp @@ -10,8 +10,8 @@ #include #include -#include "imview/viewer.hpp" -#include "imview/box.hpp" +#include "viewer/viewer.hpp" +#include "viewer/box.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/renderable/grid.hpp" #include "gldraw/renderable/point_cloud.hpp" diff --git a/src/gldraw/test/feature/test_camera_control_mappings.cpp b/src/gldraw/test/feature/test_camera_control_mappings.cpp index 1265030..3540645 100644 --- a/src/gldraw/test/feature/test_camera_control_mappings.cpp +++ b/src/gldraw/test/feature/test_camera_control_mappings.cpp @@ -12,8 +12,8 @@ #include #include -#include "imview/viewer.hpp" -#include "imview/box.hpp" +#include "viewer/viewer.hpp" +#include "viewer/box.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/renderable/grid.hpp" #include "gldraw/renderable/point_cloud.hpp" diff --git a/src/gldraw/test/feature/test_gl_scene_panel.cpp b/src/gldraw/test/feature/test_gl_scene_panel.cpp index 8924e71..574f49a 100644 --- a/src/gldraw/test/feature/test_gl_scene_panel.cpp +++ b/src/gldraw/test/feature/test_gl_scene_panel.cpp @@ -13,8 +13,8 @@ #include #include -#include "imview/box.hpp" -#include "imview/viewer.hpp" +#include "viewer/box.hpp" +#include "viewer/viewer.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/renderable/grid.hpp" diff --git a/src/gldraw/test/feature/test_layer_system.cpp b/src/gldraw/test/feature/test_layer_system.cpp index b9b7bff..8a2f450 100644 --- a/src/gldraw/test/feature/test_layer_system.cpp +++ b/src/gldraw/test/feature/test_layer_system.cpp @@ -12,8 +12,8 @@ #include #include -#include "imview/viewer.hpp" -#include "imview/box.hpp" +#include "viewer/viewer.hpp" +#include "viewer/box.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/renderable/grid.hpp" #include "gldraw/renderable/point_cloud.hpp" diff --git a/src/gldraw/test/feature/test_robot_frames.cpp b/src/gldraw/test/feature/test_robot_frames.cpp index 46fbec6..089ba4b 100644 --- a/src/gldraw/test/feature/test_robot_frames.cpp +++ b/src/gldraw/test/feature/test_robot_frames.cpp @@ -16,8 +16,8 @@ #include #include -#include "imview/box.hpp" -#include "imview/viewer.hpp" +#include "viewer/box.hpp" +#include "viewer/viewer.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/renderable/grid.hpp" diff --git a/src/gldraw/test/feature/test_visual_feedback_system.cpp b/src/gldraw/test/feature/test_visual_feedback_system.cpp index 92f1caa..adbd6ac 100644 --- a/src/gldraw/test/feature/test_visual_feedback_system.cpp +++ b/src/gldraw/test/feature/test_visual_feedback_system.cpp @@ -10,8 +10,8 @@ #include #include -#include "imview/viewer.hpp" -#include "imview/box.hpp" +#include "viewer/viewer.hpp" +#include "viewer/box.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/renderable/grid.hpp" #include "gldraw/renderable/point_cloud.hpp" diff --git a/src/gldraw/test/input/test_gamepad_input.cpp b/src/gldraw/test/input/test_gamepad_input.cpp index 786e595..4a5c28e 100644 --- a/src/gldraw/test/input/test_gamepad_input.cpp +++ b/src/gldraw/test/input/test_gamepad_input.cpp @@ -12,9 +12,9 @@ #include "core/event/input_event.hpp" #include "core/event/input_mapping.hpp" -#include "imview/input/imgui_input_utils.hpp" +#include "viewer/input/imgui_input_utils.hpp" #include "gldraw/input/scene_input_handler.hpp" -#include "imview/input/input_dispatcher.hpp" +#include "viewer/input/input_dispatcher.hpp" using namespace quickviz; diff --git a/src/gldraw/test/selection/selection_test_utils.hpp b/src/gldraw/test/selection/selection_test_utils.hpp index 288701d..644154e 100644 --- a/src/gldraw/test/selection/selection_test_utils.hpp +++ b/src/gldraw/test/selection/selection_test_utils.hpp @@ -21,10 +21,10 @@ #include #include -#include "imview/viewer.hpp" -#include "imview/box.hpp" -#include "imview/panel.hpp" -#include "imview/styling.hpp" +#include "viewer/viewer.hpp" +#include "viewer/box.hpp" +#include "viewer/panel.hpp" +#include "viewer/styling.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/selection_manager.hpp" diff --git a/src/gldraw/test/selection/test_object_selection.cpp b/src/gldraw/test/selection/test_object_selection.cpp index 786ba7b..dff2f99 100644 --- a/src/gldraw/test/selection/test_object_selection.cpp +++ b/src/gldraw/test/selection/test_object_selection.cpp @@ -14,10 +14,10 @@ #include #include -#include "imview/viewer.hpp" -#include "imview/box.hpp" -#include "imview/panel.hpp" -#include "imview/styling.hpp" +#include "viewer/viewer.hpp" +#include "viewer/box.hpp" +#include "viewer/panel.hpp" +#include "viewer/styling.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/renderable/point_cloud.hpp" diff --git a/src/gldraw/test/test_camera_raw.cpp b/src/gldraw/test/test_camera_raw.cpp index baf24af..90b3d2e 100644 --- a/src/gldraw/test/test_camera_raw.cpp +++ b/src/gldraw/test/test_camera_raw.cpp @@ -13,7 +13,7 @@ #include #include "glad/glad.h" -#include "imview/window.hpp" +#include "viewer/window.hpp" #include "gldraw/renderable/grid.hpp" #include "gldraw/camera.hpp" diff --git a/src/gldraw/test/test_canvas_st.cpp b/src/gldraw/test/test_canvas_st.cpp index cef3101..1dc8f6f 100644 --- a/src/gldraw/test/test_canvas_st.cpp +++ b/src/gldraw/test/test_canvas_st.cpp @@ -14,8 +14,8 @@ #include #include -#include "imview/box.hpp" -#include "imview/viewer.hpp" +#include "viewer/box.hpp" +#include "viewer/viewer.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/renderable/grid.hpp" diff --git a/src/gldraw/test/test_coordinate_system.cpp b/src/gldraw/test/test_coordinate_system.cpp index a6fa318..7fd3a0f 100644 --- a/src/gldraw/test/test_coordinate_system.cpp +++ b/src/gldraw/test/test_coordinate_system.cpp @@ -14,8 +14,8 @@ #include #include -#include "imview/box.hpp" -#include "imview/viewer.hpp" +#include "viewer/box.hpp" +#include "viewer/viewer.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/coordinate_transformer.hpp" diff --git a/src/gldraw/test/test_framebuffer.cpp b/src/gldraw/test/test_framebuffer.cpp index 8f49d97..654ad24 100644 --- a/src/gldraw/test/test_framebuffer.cpp +++ b/src/gldraw/test/test_framebuffer.cpp @@ -10,7 +10,7 @@ #include #include "glad/glad.h" -#include "imview/window.hpp" +#include "viewer/window.hpp" #include "gldraw/frame_buffer.hpp" using namespace quickviz; diff --git a/src/gldraw/test/test_layer_system_box.cpp b/src/gldraw/test/test_layer_system_box.cpp index 6a82f98..851b745 100644 --- a/src/gldraw/test/test_layer_system_box.cpp +++ b/src/gldraw/test/test_layer_system_box.cpp @@ -12,8 +12,8 @@ #include #include -#include "imview/viewer.hpp" -#include "imview/box.hpp" +#include "viewer/viewer.hpp" +#include "viewer/box.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/renderable/grid.hpp" #include "gldraw/renderable/point_cloud.hpp" diff --git a/src/gldraw/test/test_nav_map_rendering.cpp b/src/gldraw/test/test_nav_map_rendering.cpp index 4065a9a..f945f56 100644 --- a/src/gldraw/test/test_nav_map_rendering.cpp +++ b/src/gldraw/test/test_nav_map_rendering.cpp @@ -14,8 +14,8 @@ #include #include -#include "imview/box.hpp" -#include "imview/viewer.hpp" +#include "viewer/box.hpp" +#include "viewer/viewer.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/renderable/grid.hpp" diff --git a/src/gldraw/test/test_point_cloud_buffer_strategies.cpp b/src/gldraw/test/test_point_cloud_buffer_strategies.cpp index 24bef10..c090eeb 100644 --- a/src/gldraw/test/test_point_cloud_buffer_strategies.cpp +++ b/src/gldraw/test/test_point_cloud_buffer_strategies.cpp @@ -22,8 +22,8 @@ #include #include -#include "imview/box.hpp" -#include "imview/viewer.hpp" +#include "viewer/box.hpp" +#include "viewer/viewer.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/renderable/grid.hpp" diff --git a/src/gldraw/test/test_point_cloud_realtime.cpp b/src/gldraw/test/test_point_cloud_realtime.cpp index 0633983..42b28fd 100644 --- a/src/gldraw/test/test_point_cloud_realtime.cpp +++ b/src/gldraw/test/test_point_cloud_realtime.cpp @@ -17,8 +17,8 @@ #include #include -#include "imview/box.hpp" -#include "imview/viewer.hpp" +#include "viewer/box.hpp" +#include "viewer/viewer.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/renderable/grid.hpp" diff --git a/src/gldraw/test/test_primitive_drawing.cpp b/src/gldraw/test/test_primitive_drawing.cpp index 66d11a2..6c047e9 100644 --- a/src/gldraw/test/test_primitive_drawing.cpp +++ b/src/gldraw/test/test_primitive_drawing.cpp @@ -14,8 +14,8 @@ #include #include -#include "imview/box.hpp" -#include "imview/viewer.hpp" +#include "viewer/box.hpp" +#include "viewer/viewer.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/renderable/grid.hpp" diff --git a/src/gldraw/test/test_shader.cpp b/src/gldraw/test/test_shader.cpp index 6dea5ee..eff6a54 100644 --- a/src/gldraw/test/test_shader.cpp +++ b/src/gldraw/test/test_shader.cpp @@ -8,7 +8,7 @@ #include -#include "imview/window.hpp" +#include "viewer/window.hpp" #include "gldraw/shader.hpp" #include "gldraw/shader_program.hpp" diff --git a/src/pcl_bridge/test/test_pcd.cpp b/src/pcl_bridge/test/test_pcd.cpp index 243896e..63ab958 100644 --- a/src/pcl_bridge/test/test_pcd.cpp +++ b/src/pcl_bridge/test/test_pcd.cpp @@ -16,8 +16,8 @@ #include #include -#include "imview/box.hpp" -#include "imview/viewer.hpp" +#include "viewer/box.hpp" +#include "viewer/viewer.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/renderable/grid.hpp" diff --git a/src/pcl_bridge/test/test_pcl_bridge.cpp b/src/pcl_bridge/test/test_pcl_bridge.cpp index fd2905d..27dfc3b 100644 --- a/src/pcl_bridge/test/test_pcl_bridge.cpp +++ b/src/pcl_bridge/test/test_pcl_bridge.cpp @@ -11,8 +11,8 @@ #include #include -#include "imview/viewer.hpp" -#include "imview/box.hpp" +#include "viewer/viewer.hpp" +#include "viewer/box.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/renderable/grid.hpp" #include "gldraw/renderable/point_cloud.hpp" diff --git a/src/pcl_bridge/test/test_pcl_loader_render.cpp b/src/pcl_bridge/test/test_pcl_loader_render.cpp index 61c3d36..fc63a7e 100644 --- a/src/pcl_bridge/test/test_pcl_loader_render.cpp +++ b/src/pcl_bridge/test/test_pcl_loader_render.cpp @@ -18,9 +18,9 @@ #include #include -#include "imview/box.hpp" -#include "imview/viewer.hpp" -#include "imview/panel.hpp" +#include "viewer/box.hpp" +#include "viewer/viewer.hpp" +#include "viewer/panel.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/renderable/grid.hpp" diff --git a/src/pcl_bridge/test/unit_test/CMakeLists.txt b/src/pcl_bridge/test/unit_test/CMakeLists.txt index 97f942e..5dafbce 100644 --- a/src/pcl_bridge/test/unit_test/CMakeLists.txt +++ b/src/pcl_bridge/test/unit_test/CMakeLists.txt @@ -1,8 +1,8 @@ # add unit tests add_executable(gldraw_unit_tests test_pcl_loader.cpp) -target_link_libraries(gldraw_unit_tests PRIVATE gtest_main gmock imview gldraw pcl_bridge ${PCL_LIBRARIES}) -# get_target_property(PRIVATE_HEADERS imview INCLUDE_DIRECTORIES) +target_link_libraries(gldraw_unit_tests PRIVATE gtest_main gmock viewer gldraw pcl_bridge ${PCL_LIBRARIES}) +# get_target_property(PRIVATE_HEADERS viewer INCLUDE_DIRECTORIES) target_include_directories(gldraw_unit_tests PRIVATE ${PRIVATE_HEADERS}) gtest_discover_tests(gldraw_unit_tests) diff --git a/src/imview/CMakeLists.txt b/src/viewer/CMakeLists.txt similarity index 84% rename from src/imview/CMakeLists.txt rename to src/viewer/CMakeLists.txt index 56317a3..88c0b20 100644 --- a/src/imview/CMakeLists.txt +++ b/src/viewer/CMakeLists.txt @@ -29,7 +29,7 @@ if (ENABLE_AUTO_LAYOUT) endif () # add library -add_library(imview +add_library(viewer # ui components src/window.cpp src/viewer.cpp @@ -49,7 +49,7 @@ add_library(imview ${AUTO_LAYOUT_SRC} # terminal ${TUI_COMP_SRC}) -target_link_libraries(imview PUBLIC core imcore stb +target_link_libraries(viewer PUBLIC core imcore stb PkgConfig::Cairo PkgConfig::Fontconfig Threads::Threads OpenGL::GL @@ -57,13 +57,13 @@ target_link_libraries(imview PUBLIC core imcore stb ${OPENCV_COMP_LIBS} ${TUI_COMP_LIBS}) if (ENABLE_AUTO_LAYOUT) - target_compile_definitions(imview PUBLIC ENABLE_AUTO_LAYOUT) + target_compile_definitions(viewer PUBLIC ENABLE_AUTO_LAYOUT) endif () -if (IMVIEW_WITH_GLAD) - target_link_libraries(imview PUBLIC glad) - target_compile_definitions(imview PUBLIC IMVIEW_WITH_GLAD) +if (VIEWER_WITH_GLAD) + target_link_libraries(viewer PUBLIC glad) + target_compile_definitions(viewer PUBLIC VIEWER_WITH_GLAD) endif () -target_include_directories(imview PUBLIC +target_include_directories(viewer PUBLIC $ $ PRIVATE src) @@ -72,7 +72,7 @@ if (BUILD_TESTING) add_subdirectory(test) endif () -install(TARGETS imview +install(TARGETS viewer EXPORT quickvizTargets LIBRARY DESTINATION lib ARCHIVE DESTINATION lib diff --git a/src/imview/include/imview/box.hpp b/src/viewer/include/viewer/box.hpp similarity index 91% rename from src/imview/include/imview/box.hpp rename to src/viewer/include/viewer/box.hpp index fc410ab..9e79dea 100644 --- a/src/imview/include/imview/box.hpp +++ b/src/viewer/include/viewer/box.hpp @@ -14,9 +14,9 @@ #include #include -#include "imview/interface/container.hpp" -#include "imview/scene_object.hpp" -#include "imview/input/input_dispatcher.hpp" +#include "viewer/interface/container.hpp" +#include "viewer/scene_object.hpp" +#include "viewer/input/input_dispatcher.hpp" namespace quickviz { class Box : public SceneObject, public Container, public InputEventHandler { diff --git a/src/imview/include/imview/fonts.hpp b/src/viewer/include/viewer/fonts.hpp similarity index 100% rename from src/imview/include/imview/fonts.hpp rename to src/viewer/include/viewer/fonts.hpp diff --git a/src/imview/include/imview/input/gamepad_manager.hpp b/src/viewer/include/viewer/input/gamepad_manager.hpp similarity index 97% rename from src/imview/include/imview/input/gamepad_manager.hpp rename to src/viewer/include/viewer/input/gamepad_manager.hpp index ce14a4b..551b607 100644 --- a/src/imview/include/imview/input/gamepad_manager.hpp +++ b/src/viewer/include/viewer/input/gamepad_manager.hpp @@ -6,8 +6,8 @@ * @copyright Copyright (c) 2025 Ruixiang Du (rdu) */ -#ifndef IMVIEW_GAMEPAD_MANAGER_HPP -#define IMVIEW_GAMEPAD_MANAGER_HPP +#ifndef VIEWER_GAMEPAD_MANAGER_HPP +#define VIEWER_GAMEPAD_MANAGER_HPP #include #include @@ -171,4 +171,4 @@ class GamepadManager { } // namespace quickviz -#endif // IMVIEW_GAMEPAD_MANAGER_HPP \ No newline at end of file +#endif // VIEWER_GAMEPAD_MANAGER_HPP \ No newline at end of file diff --git a/src/imview/include/imview/input/imgui_input_utils.hpp b/src/viewer/include/viewer/input/imgui_input_utils.hpp similarity index 96% rename from src/imview/include/imview/input/imgui_input_utils.hpp rename to src/viewer/include/viewer/input/imgui_input_utils.hpp index ceb266f..60aed16 100644 --- a/src/imview/include/imview/input/imgui_input_utils.hpp +++ b/src/viewer/include/viewer/input/imgui_input_utils.hpp @@ -6,15 +6,15 @@ * @copyright Copyright (c) 2025 Ruixiang Du (rdu) */ -#ifndef IMVIEW_IMGUI_INPUT_UTILS_HPP -#define IMVIEW_IMGUI_INPUT_UTILS_HPP +#ifndef VIEWER_IMGUI_INPUT_UTILS_HPP +#define VIEWER_IMGUI_INPUT_UTILS_HPP #include "imgui.h" #include "core/event/input_event.hpp" #include "core/event/input_mapping.hpp" -#include "imview/input/input_types.hpp" +#include "viewer/input/input_types.hpp" namespace quickviz { /** @@ -140,4 +140,4 @@ class ScopedInputPoller { } // namespace quickviz -#endif // IMVIEW_IMGUI_INPUT_UTILS_HPP \ No newline at end of file +#endif // VIEWER_IMGUI_INPUT_UTILS_HPP \ No newline at end of file diff --git a/src/imview/include/imview/input/input_dispatcher.hpp b/src/viewer/include/viewer/input/input_dispatcher.hpp similarity index 89% rename from src/imview/include/imview/input/input_dispatcher.hpp rename to src/viewer/include/viewer/input/input_dispatcher.hpp index 5ca2150..5332588 100644 --- a/src/imview/include/imview/input/input_dispatcher.hpp +++ b/src/viewer/include/viewer/input/input_dispatcher.hpp @@ -1,13 +1,13 @@ /* * @file input_dispatcher.hpp * @date 9/1/25 - * @brief Central input event dispatcher for imview module + * @brief Central input event dispatcher for viewer module * * @copyright Copyright (c) 2025 Ruixiang Du (rdu) */ -#ifndef IMVIEW_INPUT_DISPATCHER_HPP -#define IMVIEW_INPUT_DISPATCHER_HPP +#ifndef VIEWER_INPUT_DISPATCHER_HPP +#define VIEWER_INPUT_DISPATCHER_HPP #include #include @@ -20,7 +20,7 @@ namespace quickviz { /** - * @brief Priority-based input event handler interface for imview + * @brief Priority-based input event handler interface for viewer * * Extends the basic InputHandler interface with priority support * for proper event ordering and consumption. @@ -53,7 +53,7 @@ class InputEventHandler { }; /** - * @brief Central input event dispatcher for the imview module + * @brief Central input event dispatcher for the viewer module * * Manages priority-based event handling with proper consumption semantics. * Handlers are processed in priority order (highest first) until one @@ -119,4 +119,4 @@ class InputDispatcher { } // namespace quickviz -#endif // IMVIEW_INPUT_DISPATCHER_HPP \ No newline at end of file +#endif // VIEWER_INPUT_DISPATCHER_HPP \ No newline at end of file diff --git a/src/imview/include/imview/input/input_manager.hpp b/src/viewer/include/viewer/input/input_manager.hpp similarity index 91% rename from src/imview/include/imview/input/input_manager.hpp rename to src/viewer/include/viewer/input/input_manager.hpp index e6d9ce4..58a528b 100644 --- a/src/imview/include/imview/input/input_manager.hpp +++ b/src/viewer/include/viewer/input/input_manager.hpp @@ -1,20 +1,20 @@ /* * @file input_manager.hpp * @date 9/1/25 - * @brief ImGui-centric input management for imview windows + * @brief ImGui-centric input management for viewer windows * * @copyright Copyright (c) 2025 Ruixiang Du (rdu) */ -#ifndef IMVIEW_INPUT_MANAGER_HPP -#define IMVIEW_INPUT_MANAGER_HPP +#ifndef VIEWER_INPUT_MANAGER_HPP +#define VIEWER_INPUT_MANAGER_HPP #include #include #include "core/event/input_event.hpp" #include "core/event/input_mapping.hpp" -#include "imview/input/input_dispatcher.hpp" +#include "viewer/input/input_dispatcher.hpp" namespace quickviz { @@ -99,4 +99,4 @@ class InputManager { } // namespace quickviz -#endif // IMVIEW_INPUT_MANAGER_HPP \ No newline at end of file +#endif // VIEWER_INPUT_MANAGER_HPP \ No newline at end of file diff --git a/src/imview/include/imview/input/input_policy.hpp b/src/viewer/include/viewer/input/input_policy.hpp similarity index 98% rename from src/imview/include/imview/input/input_policy.hpp rename to src/viewer/include/viewer/input/input_policy.hpp index bac4e6a..023f1ed 100644 --- a/src/imview/include/imview/input/input_policy.hpp +++ b/src/viewer/include/viewer/input/input_policy.hpp @@ -6,8 +6,8 @@ * @copyright Copyright (c) 2025 Ruixiang Du (rdu) */ -#ifndef IMVIEW_INPUT_POLICY_HPP -#define IMVIEW_INPUT_POLICY_HPP +#ifndef VIEWER_INPUT_POLICY_HPP +#define VIEWER_INPUT_POLICY_HPP #include "core/event/input_event.hpp" @@ -246,4 +246,4 @@ class InputControlled { } // namespace quickviz -#endif // IMVIEW_INPUT_POLICY_HPP \ No newline at end of file +#endif // VIEWER_INPUT_POLICY_HPP \ No newline at end of file diff --git a/src/imview/include/imview/input/input_types.hpp b/src/viewer/include/viewer/input/input_types.hpp similarity index 100% rename from src/imview/include/imview/input/input_types.hpp rename to src/viewer/include/viewer/input/input_types.hpp diff --git a/src/imview/include/imview/interface/container.hpp b/src/viewer/include/viewer/interface/container.hpp similarity index 93% rename from src/imview/include/imview/interface/container.hpp rename to src/viewer/include/viewer/interface/container.hpp index c98eba0..89a1cb4 100644 --- a/src/imview/include/imview/interface/container.hpp +++ b/src/viewer/include/viewer/interface/container.hpp @@ -11,7 +11,7 @@ #include -#include "imview/scene_object.hpp" +#include "viewer/scene_object.hpp" namespace quickviz { class Container { diff --git a/src/imview/include/imview/interface/renderable.hpp b/src/viewer/include/viewer/interface/renderable.hpp similarity index 100% rename from src/imview/include/imview/interface/renderable.hpp rename to src/viewer/include/viewer/interface/renderable.hpp diff --git a/src/imview/include/imview/interface/resizable.hpp b/src/viewer/include/viewer/interface/resizable.hpp similarity index 98% rename from src/imview/include/imview/interface/resizable.hpp rename to src/viewer/include/viewer/interface/resizable.hpp index 554b0b6..c0bbefb 100644 --- a/src/imview/include/imview/interface/resizable.hpp +++ b/src/viewer/include/viewer/interface/resizable.hpp @@ -9,7 +9,7 @@ #ifndef QUICKVIZ_RESIZABLE_HPP #define QUICKVIZ_RESIZABLE_HPP -#include "imview/styling.hpp" +#include "viewer/styling.hpp" namespace quickviz { class Resizable { diff --git a/src/imview/include/imview/logging/app_log_handler.hpp b/src/viewer/include/viewer/logging/app_log_handler.hpp similarity index 97% rename from src/imview/include/imview/logging/app_log_handler.hpp rename to src/viewer/include/viewer/logging/app_log_handler.hpp index d93a2a6..d39d06b 100644 --- a/src/imview/include/imview/logging/app_log_handler.hpp +++ b/src/viewer/include/viewer/logging/app_log_handler.hpp @@ -9,7 +9,7 @@ #ifndef APP_LOG_HANDLER_HPP #define APP_LOG_HANDLER_HPP -#include "imview/logging/log_processor.hpp" +#include "viewer/logging/log_processor.hpp" #include #include diff --git a/src/imview/include/imview/logging/log_processor.hpp b/src/viewer/include/viewer/logging/log_processor.hpp similarity index 100% rename from src/imview/include/imview/logging/log_processor.hpp rename to src/viewer/include/viewer/logging/log_processor.hpp diff --git a/src/imview/include/imview/panel.hpp b/src/viewer/include/viewer/panel.hpp similarity index 88% rename from src/imview/include/imview/panel.hpp rename to src/viewer/include/viewer/panel.hpp index 54ab4ed..5c856cc 100644 --- a/src/imview/include/imview/panel.hpp +++ b/src/viewer/include/viewer/panel.hpp @@ -7,18 +7,18 @@ * Copyright (c) 2022 Ruixiang Du (rdu) */ -#ifndef ROBOSW_SRC_VISUALIZATION_IMVIEW_INCLUDE_IMVIEW_PANEL_HPP -#define ROBOSW_SRC_VISUALIZATION_IMVIEW_INCLUDE_IMVIEW_PANEL_HPP +#ifndef ROBOSW_SRC_VISUALIZATION_VIEWER_INCLUDE_VIEWER_PANEL_HPP +#define ROBOSW_SRC_VISUALIZATION_VIEWER_INCLUDE_VIEWER_PANEL_HPP #include #include #include #include "imgui.h" -#include "imview/scene_object.hpp" -#include "imview/input/imgui_input_utils.hpp" -#include "imview/input/input_policy.hpp" -#include "imview/input/input_dispatcher.hpp" +#include "viewer/scene_object.hpp" +#include "viewer/input/imgui_input_utils.hpp" +#include "viewer/input/input_policy.hpp" +#include "viewer/input/input_dispatcher.hpp" namespace quickviz { class Window; // Forward declaration @@ -103,4 +103,4 @@ class Panel : public SceneObject, public InputControlled, public InputEventHandl }; } // namespace quickviz -#endif // ROBOSW_SRC_VISUALIZATION_IMVIEW_INCLUDE_IMVIEW_PANEL_HPP +#endif // ROBOSW_SRC_VISUALIZATION_VIEWER_INCLUDE_VIEWER_PANEL_HPP diff --git a/src/imview/include/imview/popup.hpp b/src/viewer/include/viewer/popup.hpp similarity index 77% rename from src/imview/include/imview/popup.hpp rename to src/viewer/include/viewer/popup.hpp index d8163d7..5deb967 100644 --- a/src/imview/include/imview/popup.hpp +++ b/src/viewer/include/viewer/popup.hpp @@ -7,8 +7,8 @@ * Copyright (c) 2022 Ruixiang Du (rdu) */ -#ifndef ROBOSW_SRC_VISUALIZATION_IMVIEW_INCLUDE_IMVIEW_POPUP_HPP -#define ROBOSW_SRC_VISUALIZATION_IMVIEW_INCLUDE_IMVIEW_POPUP_HPP +#ifndef ROBOSW_SRC_VISUALIZATION_VIEWER_INCLUDE_VIEWER_POPUP_HPP +#define ROBOSW_SRC_VISUALIZATION_VIEWER_INCLUDE_VIEWER_POPUP_HPP #include #include @@ -26,4 +26,4 @@ void ShowConfirmationPopup(std::string title, std::string msg, float width = 300, float height = 150); } // namespace quickviz -#endif // ROBOSW_SRC_VISUALIZATION_IMVIEW_INCLUDE_IMVIEW_POPUP_HPP +#endif // ROBOSW_SRC_VISUALIZATION_VIEWER_INCLUDE_VIEWER_POPUP_HPP diff --git a/src/imview/include/imview/popup_manager.hpp b/src/viewer/include/viewer/popup_manager.hpp similarity index 97% rename from src/imview/include/imview/popup_manager.hpp rename to src/viewer/include/viewer/popup_manager.hpp index 1e32de9..5b40181 100644 --- a/src/imview/include/imview/popup_manager.hpp +++ b/src/viewer/include/viewer/popup_manager.hpp @@ -11,7 +11,7 @@ #include -#include "imview/popup.hpp" +#include "viewer/popup.hpp" namespace quickviz { class PopupManager { diff --git a/src/imview/include/imview/scene_object.hpp b/src/viewer/include/viewer/scene_object.hpp similarity index 97% rename from src/imview/include/imview/scene_object.hpp rename to src/viewer/include/viewer/scene_object.hpp index 98e76bf..e108984 100644 --- a/src/imview/include/imview/scene_object.hpp +++ b/src/viewer/include/viewer/scene_object.hpp @@ -13,8 +13,8 @@ #include #include -#include "imview/interface/resizable.hpp" -#include "imview/interface/renderable.hpp" +#include "viewer/interface/resizable.hpp" +#include "viewer/interface/renderable.hpp" #ifdef ENABLE_AUTO_LAYOUT struct YGNode; diff --git a/src/imview/include/imview/styling.hpp b/src/viewer/include/viewer/styling.hpp similarity index 100% rename from src/imview/include/imview/styling.hpp rename to src/viewer/include/viewer/styling.hpp diff --git a/src/imview/include/imview/terminal/tui_panel.hpp b/src/viewer/include/viewer/terminal/tui_panel.hpp similarity index 82% rename from src/imview/include/imview/terminal/tui_panel.hpp rename to src/viewer/include/viewer/terminal/tui_panel.hpp index 118dc52..28f4655 100644 --- a/src/imview/include/imview/terminal/tui_panel.hpp +++ b/src/viewer/include/viewer/terminal/tui_panel.hpp @@ -7,15 +7,15 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#ifndef IMVIEW_TERMINAL_TUI_PANEL_HPP -#define IMVIEW_TERMINAL_TUI_PANEL_HPP +#ifndef VIEWER_TERMINAL_TUI_PANEL_HPP +#define VIEWER_TERMINAL_TUI_PANEL_HPP #include #include #include -#include "imview/scene_object.hpp" +#include "viewer/scene_object.hpp" namespace quickviz { class TuiPanel : public SceneObject { @@ -40,4 +40,4 @@ class TuiPanel : public SceneObject { }; } // namespace quickviz -#endif /* IMVIEW_TERMINAL_TUI_PANEL_HPP */ +#endif /* VIEWER_TERMINAL_TUI_PANEL_HPP */ diff --git a/src/imview/include/imview/terminal/tui_text.hpp b/src/viewer/include/viewer/terminal/tui_text.hpp similarity index 93% rename from src/imview/include/imview/terminal/tui_text.hpp rename to src/viewer/include/viewer/terminal/tui_text.hpp index edf29f1..22085af 100644 --- a/src/imview/include/imview/terminal/tui_text.hpp +++ b/src/viewer/include/viewer/terminal/tui_text.hpp @@ -7,8 +7,8 @@ * @copyright Copyright (c) 2023 Ruixiang Du (rdu) */ -#ifndef IMVIEW_TERMINAL_NC_TEXT_HPP -#define IMVIEW_TERMINAL_NC_TEXT_HPP +#ifndef VIEWER_TERMINAL_NC_TEXT_HPP +#define VIEWER_TERMINAL_NC_TEXT_HPP #include @@ -64,4 +64,4 @@ class TuiText { }; } // namespace quickviz -#endif /* IMVIEW_TERMINAL_NC_TEXT_HPP */ +#endif /* VIEWER_TERMINAL_NC_TEXT_HPP */ diff --git a/src/imview/include/imview/terminal/tui_viewer.hpp b/src/viewer/include/viewer/terminal/tui_viewer.hpp similarity index 80% rename from src/imview/include/imview/terminal/tui_viewer.hpp rename to src/viewer/include/viewer/terminal/tui_viewer.hpp index a9f8138..0a9c93c 100644 --- a/src/imview/include/imview/terminal/tui_viewer.hpp +++ b/src/viewer/include/viewer/terminal/tui_viewer.hpp @@ -7,8 +7,8 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#ifndef IMVIEW_TERMINAL_TUI_VIEWER_HPP -#define IMVIEW_TERMINAL_TUI_VIEWER_HPP +#ifndef VIEWER_TERMINAL_TUI_VIEWER_HPP +#define VIEWER_TERMINAL_TUI_VIEWER_HPP #include @@ -17,8 +17,8 @@ #include #include -#include "imview/terminal/tui_text.hpp" -#include "imview/terminal/tui_panel.hpp" +#include "viewer/terminal/tui_text.hpp" +#include "viewer/terminal/tui_panel.hpp" namespace quickviz { class TuiViewer { @@ -49,4 +49,4 @@ class TuiViewer { }; } // namespace quickviz -#endif /* IMVIEW_TERMINAL_TUI_VIEWER_HPP */ +#endif /* VIEWER_TERMINAL_TUI_VIEWER_HPP */ diff --git a/src/imview/include/imview/viewer.hpp b/src/viewer/include/viewer/viewer.hpp similarity index 90% rename from src/imview/include/imview/viewer.hpp rename to src/viewer/include/viewer/viewer.hpp index 3dc4a50..03ff210 100644 --- a/src/imview/include/imview/viewer.hpp +++ b/src/viewer/include/viewer/viewer.hpp @@ -9,8 +9,8 @@ * Copyright (c) 2021 Ruixiang Du (rdu) */ -#ifndef IMVIEW_VIEWER_HPP -#define IMVIEW_VIEWER_HPP +#ifndef VIEWER_VIEWER_HPP +#define VIEWER_VIEWER_HPP #include #include @@ -18,9 +18,9 @@ #include "imgui.h" -#include "imview/fonts.hpp" -#include "imview/window.hpp" -#include "imview/scene_object.hpp" +#include "viewer/fonts.hpp" +#include "viewer/window.hpp" +#include "viewer/scene_object.hpp" namespace quickviz { class Viewer : public Window { @@ -70,4 +70,4 @@ class Viewer : public Window { }; } // namespace quickviz -#endif /* IMVIEW_VIEWER_HPP */ +#endif /* VIEWER_VIEWER_HPP */ diff --git a/src/imview/include/imview/window.hpp b/src/viewer/include/viewer/window.hpp similarity index 92% rename from src/imview/include/imview/window.hpp rename to src/viewer/include/viewer/window.hpp index d9d7054..324cb52 100644 --- a/src/imview/include/imview/window.hpp +++ b/src/viewer/include/viewer/window.hpp @@ -8,10 +8,10 @@ * Copyright (c) 2021 Ruixiang Du (rdu) */ -#ifndef IMVIEW_WINDOW_HPP -#define IMVIEW_WINDOW_HPP +#ifndef VIEWER_WINDOW_HPP +#define VIEWER_WINDOW_HPP -#ifdef IMVIEW_WITH_GLAD +#ifdef VIEWER_WITH_GLAD #include #endif #include @@ -20,7 +20,7 @@ #include #include -#include "imview/input/input_manager.hpp" +#include "viewer/input/input_manager.hpp" namespace quickviz { class Panel; // Forward declaration @@ -80,4 +80,4 @@ class Window { }; } // namespace quickviz -#endif /* IMVIEW_WINDOW_HPP */ +#endif /* VIEWER_WINDOW_HPP */ diff --git a/src/imview/src/box.cpp b/src/viewer/src/box.cpp similarity index 97% rename from src/imview/src/box.cpp rename to src/viewer/src/box.cpp index f3d4126..8c903a3 100644 --- a/src/imview/src/box.cpp +++ b/src/viewer/src/box.cpp @@ -6,12 +6,12 @@ * @copyright Copyright (c) 2024 Ruixiang Du (rdu) */ -#include "imview/box.hpp" +#include "viewer/box.hpp" #include #include -#include "imview/input/input_dispatcher.hpp" +#include "viewer/input/input_dispatcher.hpp" namespace quickviz { namespace { diff --git a/src/imview/src/fonts.cpp b/src/viewer/src/fonts.cpp similarity index 98% rename from src/imview/src/fonts.cpp rename to src/viewer/src/fonts.cpp index c8b6d46..2499e04 100644 --- a/src/imview/src/fonts.cpp +++ b/src/viewer/src/fonts.cpp @@ -6,7 +6,7 @@ * @copyright Copyright (c) 2024 Ruixiang Du (rdu) */ -#include "imview/fonts.hpp" +#include "viewer/fonts.hpp" #include diff --git a/src/imview/src/input/gamepad_manager.cpp b/src/viewer/src/input/gamepad_manager.cpp similarity index 99% rename from src/imview/src/input/gamepad_manager.cpp rename to src/viewer/src/input/gamepad_manager.cpp index 652ea71..97dd10b 100644 --- a/src/imview/src/input/gamepad_manager.cpp +++ b/src/viewer/src/input/gamepad_manager.cpp @@ -6,7 +6,7 @@ * @copyright Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "imview/input/gamepad_manager.hpp" +#include "viewer/input/gamepad_manager.hpp" #include #include #include diff --git a/src/imview/src/input/imgui_input_utils.cpp b/src/viewer/src/input/imgui_input_utils.cpp similarity index 99% rename from src/imview/src/input/imgui_input_utils.cpp rename to src/viewer/src/input/imgui_input_utils.cpp index e1b476f..24dcf7c 100644 --- a/src/imview/src/input/imgui_input_utils.cpp +++ b/src/viewer/src/input/imgui_input_utils.cpp @@ -6,8 +6,8 @@ * @copyright Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "imview/input/imgui_input_utils.hpp" -#include "imview/input/gamepad_manager.hpp" +#include "viewer/input/imgui_input_utils.hpp" +#include "viewer/input/gamepad_manager.hpp" #include #include #include diff --git a/src/imview/src/input/input_dispatcher.cpp b/src/viewer/src/input/input_dispatcher.cpp similarity index 98% rename from src/imview/src/input/input_dispatcher.cpp rename to src/viewer/src/input/input_dispatcher.cpp index f94a09b..35225e9 100644 --- a/src/imview/src/input/input_dispatcher.cpp +++ b/src/viewer/src/input/input_dispatcher.cpp @@ -6,7 +6,7 @@ * @copyright Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "imview/input/input_dispatcher.hpp" +#include "viewer/input/input_dispatcher.hpp" #include diff --git a/src/imview/src/input/input_manager.cpp b/src/viewer/src/input/input_manager.cpp similarity index 96% rename from src/imview/src/input/input_manager.cpp rename to src/viewer/src/input/input_manager.cpp index c55a495..e4df3be 100644 --- a/src/imview/src/input/input_manager.cpp +++ b/src/viewer/src/input/input_manager.cpp @@ -6,7 +6,7 @@ * @copyright Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "imview/input/input_manager.hpp" +#include "viewer/input/input_manager.hpp" namespace quickviz { diff --git a/src/imview/src/logging/app_log_handler.cpp b/src/viewer/src/logging/app_log_handler.cpp similarity index 82% rename from src/imview/src/logging/app_log_handler.cpp rename to src/viewer/src/logging/app_log_handler.cpp index 930564e..3265750 100644 --- a/src/imview/src/logging/app_log_handler.cpp +++ b/src/viewer/src/logging/app_log_handler.cpp @@ -6,7 +6,7 @@ * @copyright Copyright (c) 2024 Ruixiang Du (rdu) */ -#include "imview/logging/app_log_handler.hpp" +#include "viewer/logging/app_log_handler.hpp" namespace quickviz { void AppLogHandler::Draw() { processor_.Draw("AppLog"); } diff --git a/src/imview/src/logging/log_processor.cpp b/src/viewer/src/logging/log_processor.cpp similarity index 98% rename from src/imview/src/logging/log_processor.cpp rename to src/viewer/src/logging/log_processor.cpp index 05aa555..d30c388 100644 --- a/src/imview/src/logging/log_processor.cpp +++ b/src/viewer/src/logging/log_processor.cpp @@ -6,7 +6,7 @@ * @copyright Copyright (c) 2024 Ruixiang Du (rdu) */ -#include "imview/logging/log_processor.hpp" +#include "viewer/logging/log_processor.hpp" namespace quickviz { LogProcessor::LogProcessor() { diff --git a/src/imview/src/opengl_capability_checker.cpp b/src/viewer/src/opengl_capability_checker.cpp similarity index 99% rename from src/imview/src/opengl_capability_checker.cpp rename to src/viewer/src/opengl_capability_checker.cpp index 30c480d..736e298 100644 --- a/src/imview/src/opengl_capability_checker.cpp +++ b/src/viewer/src/opengl_capability_checker.cpp @@ -14,7 +14,7 @@ #include #include -#ifdef IMVIEW_WITH_GLAD +#ifdef VIEWER_WITH_GLAD #include "glad/glad.h" #else #include diff --git a/src/imview/src/opengl_capability_checker.hpp b/src/viewer/src/opengl_capability_checker.hpp similarity index 100% rename from src/imview/src/opengl_capability_checker.hpp rename to src/viewer/src/opengl_capability_checker.hpp diff --git a/src/imview/src/panel.cpp b/src/viewer/src/panel.cpp similarity index 99% rename from src/imview/src/panel.cpp rename to src/viewer/src/panel.cpp index 262c57a..8fad949 100644 --- a/src/imview/src/panel.cpp +++ b/src/viewer/src/panel.cpp @@ -7,10 +7,10 @@ * Copyright (c) 2022 Ruixiang Du (rdu) */ -#include "imview/panel.hpp" +#include "viewer/panel.hpp" #include "imgui_internal.h" -#include "imview/window.hpp" +#include "viewer/window.hpp" namespace quickviz { Panel::Panel(std::string name) : SceneObject(name) { diff --git a/src/imview/src/popup.cpp b/src/viewer/src/popup.cpp similarity index 98% rename from src/imview/src/popup.cpp rename to src/viewer/src/popup.cpp index bc86c8e..a549c59 100644 --- a/src/imview/src/popup.cpp +++ b/src/viewer/src/popup.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2022 Ruixiang Du (rdu) */ -#include "imview/popup.hpp" +#include "viewer/popup.hpp" #include "imgui.h" diff --git a/src/imview/src/popup_manager.cpp b/src/viewer/src/popup_manager.cpp similarity index 98% rename from src/imview/src/popup_manager.cpp rename to src/viewer/src/popup_manager.cpp index a4fb40a..ebc7cc7 100644 --- a/src/imview/src/popup_manager.cpp +++ b/src/viewer/src/popup_manager.cpp @@ -6,7 +6,7 @@ * @copyright Copyright (c) 2024 Ruixiang Du (rdu) */ -#include "imview/popup_manager.hpp" +#include "viewer/popup_manager.hpp" #include "imgui/imgui.h" diff --git a/src/imview/src/scene_object.cpp b/src/viewer/src/scene_object.cpp similarity index 99% rename from src/imview/src/scene_object.cpp rename to src/viewer/src/scene_object.cpp index e995ae9..20cc2fe 100644 --- a/src/imview/src/scene_object.cpp +++ b/src/viewer/src/scene_object.cpp @@ -6,7 +6,7 @@ * @copyright Copyright (c) 2024 Ruixiang Du (rdu) */ -#include "imview/scene_object.hpp" +#include "viewer/scene_object.hpp" #include diff --git a/src/imview/src/terminal/tui_panel.cpp b/src/viewer/src/terminal/tui_panel.cpp similarity index 97% rename from src/imview/src/terminal/tui_panel.cpp rename to src/viewer/src/terminal/tui_panel.cpp index c5219e0..0e3166b 100644 --- a/src/imview/src/terminal/tui_panel.cpp +++ b/src/viewer/src/terminal/tui_panel.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "imview/terminal/tui_panel.hpp" +#include "viewer/terminal/tui_panel.hpp" #include diff --git a/src/imview/src/terminal/tui_text.cpp b/src/viewer/src/terminal/tui_text.cpp similarity index 95% rename from src/imview/src/terminal/tui_text.cpp rename to src/viewer/src/terminal/tui_text.cpp index 0c72a4c..1a2f328 100644 --- a/src/imview/src/terminal/tui_text.cpp +++ b/src/viewer/src/terminal/tui_text.cpp @@ -6,7 +6,7 @@ * @copyright Copyright (c) 2023 Ruixiang Du (rdu) */ -#include "imview/terminal/tui_text.hpp" +#include "viewer/terminal/tui_text.hpp" #include diff --git a/src/imview/src/terminal/tui_viewer.cpp b/src/viewer/src/terminal/tui_viewer.cpp similarity index 97% rename from src/imview/src/terminal/tui_viewer.cpp rename to src/viewer/src/terminal/tui_viewer.cpp index 420a2d8..bdc0c26 100644 --- a/src/imview/src/terminal/tui_viewer.cpp +++ b/src/viewer/src/terminal/tui_viewer.cpp @@ -12,7 +12,7 @@ #include #include -#include "imview/terminal/tui_viewer.hpp" +#include "viewer/terminal/tui_viewer.hpp" namespace quickviz { TuiViewer::TuiViewer(const std::string &title, bool has_border) diff --git a/src/imview/src/viewer.cpp b/src/viewer/src/viewer.cpp similarity index 99% rename from src/imview/src/viewer.cpp rename to src/viewer/src/viewer.cpp index 72f33f4..525985f 100644 --- a/src/imview/src/viewer.cpp +++ b/src/viewer/src/viewer.cpp @@ -15,7 +15,7 @@ * Copyright (c) 2021 Ruixiang Du (rdu) */ -#include "imview/viewer.hpp" +#include "viewer/viewer.hpp" #include #include @@ -25,8 +25,8 @@ #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" #include "opengl_capability_checker.hpp" -#include "imview/panel.hpp" -#include "imview/input/imgui_input_utils.hpp" +#include "viewer/panel.hpp" +#include "viewer/input/imgui_input_utils.hpp" namespace quickviz { namespace { diff --git a/src/imview/src/window.cpp b/src/viewer/src/window.cpp similarity index 98% rename from src/imview/src/window.cpp rename to src/viewer/src/window.cpp index b35e2f3..b3ff903 100644 --- a/src/imview/src/window.cpp +++ b/src/viewer/src/window.cpp @@ -7,14 +7,14 @@ * Copyright (c) 2021 Ruixiang Du (rdu) */ -#include "imview/window.hpp" +#include "viewer/window.hpp" #include #include #include -#include "imview/panel.hpp" -#include "imview/input/gamepad_manager.hpp" +#include "viewer/panel.hpp" +#include "viewer/input/gamepad_manager.hpp" namespace quickviz { namespace { @@ -71,7 +71,7 @@ Window::Window(std::string title, uint32_t width, uint32_t height, glfwMakeContextCurrent(win_); glfwSwapInterval(1); -#ifdef IMVIEW_WITH_GLAD +#ifdef VIEWER_WITH_GLAD // initialize GLAD if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { throw std::runtime_error("Failed to initialize GLAD"); diff --git a/src/imview/src/yoga_utils.cpp b/src/viewer/src/yoga_utils.cpp similarity index 100% rename from src/imview/src/yoga_utils.cpp rename to src/viewer/src/yoga_utils.cpp diff --git a/src/imview/src/yoga_utils.hpp b/src/viewer/src/yoga_utils.hpp similarity index 96% rename from src/imview/src/yoga_utils.hpp rename to src/viewer/src/yoga_utils.hpp index 605fd0b..db0e590 100644 --- a/src/imview/src/yoga_utils.hpp +++ b/src/viewer/src/yoga_utils.hpp @@ -10,7 +10,7 @@ #define QUICKVIZ_YOGA_UTILS_HPP #include "yoga/Yoga.h" -#include "imview/styling.hpp" +#include "viewer/styling.hpp" namespace quickviz { namespace YogaUtils { diff --git a/src/imview/test/CMakeLists.txt b/src/viewer/test/CMakeLists.txt similarity index 59% rename from src/imview/test/CMakeLists.txt rename to src/viewer/test/CMakeLists.txt index b1521de..374a516 100644 --- a/src/imview/test/CMakeLists.txt +++ b/src/viewer/test/CMakeLists.txt @@ -3,15 +3,15 @@ add_subdirectory(feature) ## additional tests/sample add_executable(test_window_gl_triangle test_window_gl_triangle.cpp) -target_link_libraries(test_window_gl_triangle PRIVATE imview) +target_link_libraries(test_window_gl_triangle PRIVATE viewer) if (ENABLE_AUTO_LAYOUT) add_executable(test_box test_box.cpp) - target_link_libraries(test_box PRIVATE imview) + target_link_libraries(test_box PRIVATE viewer) add_executable(test_box_inside_box test_box_inside_box.cpp) - target_link_libraries(test_box_inside_box PRIVATE imview) + target_link_libraries(test_box_inside_box PRIVATE viewer) endif () #add_executable(test_panel test_panel.cpp) -#target_link_libraries(test_panel imview) +#target_link_libraries(test_panel viewer) diff --git a/src/imview/test/feature/CMakeLists.txt b/src/viewer/test/feature/CMakeLists.txt similarity index 54% rename from src/imview/test/feature/CMakeLists.txt rename to src/viewer/test/feature/CMakeLists.txt index 2a50f15..c9de8d9 100644 --- a/src/imview/test/feature/CMakeLists.txt +++ b/src/viewer/test/feature/CMakeLists.txt @@ -1,27 +1,27 @@ add_executable(test_window test_window.cpp) -target_link_libraries(test_window PRIVATE imview) +target_link_libraries(test_window PRIVATE viewer) if(ENABLE_AUTO_LAYOUT) add_executable(test_viewer test_viewer.cpp) - target_link_libraries(test_viewer PRIVATE imview) + target_link_libraries(test_viewer PRIVATE viewer) add_executable(test_auto_layout test_auto_layout.cpp) - target_link_libraries(test_auto_layout PRIVATE imview) + target_link_libraries(test_auto_layout PRIVATE viewer) if(ENABLE_TUI_SUPPORT) add_executable(test_tui_composer test_tui_composer.cpp) - target_link_libraries(test_tui_composer PRIVATE imview) + target_link_libraries(test_tui_composer PRIVATE viewer) endif() endif() add_executable(test_popup test_popup.cpp) -target_link_libraries(test_popup PRIVATE imview) +target_link_libraries(test_popup PRIVATE viewer) add_executable(test_joystick_input test_joystick_input.cpp) -target_link_libraries(test_joystick_input PRIVATE imview) +target_link_libraries(test_joystick_input PRIVATE viewer) add_executable(test_keyboard_mouse_input test_keyboard_mouse_input.cpp) -target_link_libraries(test_keyboard_mouse_input PRIVATE imview) +target_link_libraries(test_keyboard_mouse_input PRIVATE viewer) add_executable(test_centralized_input test_centralized_input.cpp) -target_link_libraries(test_centralized_input PRIVATE imview gtest gtest_main) +target_link_libraries(test_centralized_input PRIVATE viewer gtest gtest_main) diff --git a/src/imview/test/feature/scene_objects/gl_triangle_scene_object.hpp b/src/viewer/test/feature/scene_objects/gl_triangle_scene_object.hpp similarity index 99% rename from src/imview/test/feature/scene_objects/gl_triangle_scene_object.hpp rename to src/viewer/test/feature/scene_objects/gl_triangle_scene_object.hpp index 935a4bf..03e8381 100644 --- a/src/imview/test/feature/scene_objects/gl_triangle_scene_object.hpp +++ b/src/viewer/test/feature/scene_objects/gl_triangle_scene_object.hpp @@ -11,7 +11,7 @@ #include #include "glad/glad.h" -#include "imview/panel.hpp" +#include "viewer/panel.hpp" namespace quickviz { class GLTriangleSceneObject : public Panel { diff --git a/src/imview/test/feature/scene_objects/imgui_fixed_panel.hpp b/src/viewer/test/feature/scene_objects/imgui_fixed_panel.hpp similarity index 95% rename from src/imview/test/feature/scene_objects/imgui_fixed_panel.hpp rename to src/viewer/test/feature/scene_objects/imgui_fixed_panel.hpp index 3a273bd..07bd7a0 100644 --- a/src/imview/test/feature/scene_objects/imgui_fixed_panel.hpp +++ b/src/viewer/test/feature/scene_objects/imgui_fixed_panel.hpp @@ -9,8 +9,8 @@ #ifndef QUICKVIZ_IMGUI_FIXED_PANEL_HPP #define QUICKVIZ_IMGUI_FIXED_PANEL_HPP -#include "imview/viewer.hpp" -#include "imview/panel.hpp" +#include "viewer/viewer.hpp" +#include "viewer/panel.hpp" namespace quickviz { class ImGuiFixedPanel : public Panel { diff --git a/src/imview/test/feature/scene_objects/imtext_panel.hpp b/src/viewer/test/feature/scene_objects/imtext_panel.hpp similarity index 96% rename from src/imview/test/feature/scene_objects/imtext_panel.hpp rename to src/viewer/test/feature/scene_objects/imtext_panel.hpp index 553d8d4..02296e0 100644 --- a/src/imview/test/feature/scene_objects/imtext_panel.hpp +++ b/src/viewer/test/feature/scene_objects/imtext_panel.hpp @@ -9,7 +9,7 @@ #ifndef QUICKVIZ_IMTEXT_PANEL_HPP #define QUICKVIZ_IMTEXT_PANEL_HPP -#include "imview/panel.hpp" +#include "viewer/panel.hpp" namespace quickviz { class ImTextPanel : public Panel { diff --git a/src/imview/test/feature/scene_objects/opengl_scene_object.hpp b/src/viewer/test/feature/scene_objects/opengl_scene_object.hpp similarity index 96% rename from src/imview/test/feature/scene_objects/opengl_scene_object.hpp rename to src/viewer/test/feature/scene_objects/opengl_scene_object.hpp index 3179536..e1ad012 100644 --- a/src/imview/test/feature/scene_objects/opengl_scene_object.hpp +++ b/src/viewer/test/feature/scene_objects/opengl_scene_object.hpp @@ -9,7 +9,7 @@ #define QUICKVIZ_OPENGL_SCENE_OBJECT_HPP #include "glad/glad.h" -#include "imview/panel.hpp" +#include "viewer/panel.hpp" namespace quickviz { class OpenGLSceneObject : public Panel { diff --git a/src/imview/test/feature/test_auto_layout.cpp b/src/viewer/test/feature/test_auto_layout.cpp similarity index 97% rename from src/imview/test/feature/test_auto_layout.cpp rename to src/viewer/test/feature/test_auto_layout.cpp index 2a05fc9..f2640d7 100644 --- a/src/imview/test/feature/test_auto_layout.cpp +++ b/src/viewer/test/feature/test_auto_layout.cpp @@ -9,8 +9,8 @@ #include -#include "imview/viewer.hpp" -#include "imview/box.hpp" +#include "viewer/viewer.hpp" +#include "viewer/box.hpp" #include "scene_objects/imtext_panel.hpp" #include "scene_objects/imgui_fixed_panel.hpp" diff --git a/src/imview/test/feature/test_centralized_input.cpp b/src/viewer/test/feature/test_centralized_input.cpp similarity index 97% rename from src/imview/test/feature/test_centralized_input.cpp rename to src/viewer/test/feature/test_centralized_input.cpp index d0acdad..73ec8cf 100644 --- a/src/imview/test/feature/test_centralized_input.cpp +++ b/src/viewer/test/feature/test_centralized_input.cpp @@ -10,9 +10,9 @@ #include #include -#include "imview/window.hpp" -#include "imview/panel.hpp" -#include "imview/input/gamepad_manager.hpp" +#include "viewer/window.hpp" +#include "viewer/panel.hpp" +#include "viewer/input/gamepad_manager.hpp" #include "core/event/input_event.hpp" using namespace quickviz; diff --git a/src/imview/test/feature/test_joystick_input.cpp b/src/viewer/test/feature/test_joystick_input.cpp similarity index 98% rename from src/imview/test/feature/test_joystick_input.cpp rename to src/viewer/test/feature/test_joystick_input.cpp index 073666d..08264db 100644 --- a/src/imview/test/feature/test_joystick_input.cpp +++ b/src/viewer/test/feature/test_joystick_input.cpp @@ -10,9 +10,9 @@ #include #include -#include "imview/panel.hpp" -#include "imview/viewer.hpp" -#include "imview/input/gamepad_manager.hpp" +#include "viewer/panel.hpp" +#include "viewer/viewer.hpp" +#include "viewer/input/gamepad_manager.hpp" using namespace quickviz; diff --git a/src/imview/test/feature/test_keyboard_mouse_input.cpp b/src/viewer/test/feature/test_keyboard_mouse_input.cpp similarity index 99% rename from src/imview/test/feature/test_keyboard_mouse_input.cpp rename to src/viewer/test/feature/test_keyboard_mouse_input.cpp index 97ad9f4..b43bc55 100644 --- a/src/imview/test/feature/test_keyboard_mouse_input.cpp +++ b/src/viewer/test/feature/test_keyboard_mouse_input.cpp @@ -13,8 +13,8 @@ #include #include -#include "imview/panel.hpp" -#include "imview/viewer.hpp" +#include "viewer/panel.hpp" +#include "viewer/viewer.hpp" using namespace quickviz; diff --git a/src/imview/test/feature/test_popup.cpp b/src/viewer/test/feature/test_popup.cpp similarity index 91% rename from src/imview/test/feature/test_popup.cpp rename to src/viewer/test/feature/test_popup.cpp index efde977..191779a 100644 --- a/src/imview/test/feature/test_popup.cpp +++ b/src/viewer/test/feature/test_popup.cpp @@ -9,13 +9,13 @@ #include -#include "imview/viewer.hpp" -#include "imview/box.hpp" +#include "viewer/viewer.hpp" +#include "viewer/box.hpp" -#include "imview/panel.hpp" -#include "imview/popup.hpp" +#include "viewer/panel.hpp" +#include "viewer/popup.hpp" -#include "imview/popup_manager.hpp" +#include "viewer/popup_manager.hpp" using namespace quickviz; diff --git a/src/imview/test/feature/test_tui_composer.cpp b/src/viewer/test/feature/test_tui_composer.cpp similarity index 96% rename from src/imview/test/feature/test_tui_composer.cpp rename to src/viewer/test/feature/test_tui_composer.cpp index 1192a26..0e7851d 100644 --- a/src/imview/test/feature/test_tui_composer.cpp +++ b/src/viewer/test/feature/test_tui_composer.cpp @@ -7,8 +7,8 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "imview/box.hpp" -#include "imview/terminal/tui_viewer.hpp" +#include "viewer/box.hpp" +#include "viewer/terminal/tui_viewer.hpp" using namespace quickviz; diff --git a/src/imview/test/feature/test_viewer.cpp b/src/viewer/test/feature/test_viewer.cpp similarity index 98% rename from src/imview/test/feature/test_viewer.cpp rename to src/viewer/test/feature/test_viewer.cpp index 66bda5e..5b0bd71 100644 --- a/src/imview/test/feature/test_viewer.cpp +++ b/src/viewer/test/feature/test_viewer.cpp @@ -9,8 +9,8 @@ #include -#include "imview/viewer.hpp" -#include "imview/box.hpp" +#include "viewer/viewer.hpp" +#include "viewer/box.hpp" #include "scene_objects/imtext_panel.hpp" #include "scene_objects/imgui_fixed_panel.hpp" diff --git a/src/imview/test/feature/test_window.cpp b/src/viewer/test/feature/test_window.cpp similarity index 97% rename from src/imview/test/feature/test_window.cpp rename to src/viewer/test/feature/test_window.cpp index 4225fbf..d2e893a 100644 --- a/src/imview/test/feature/test_window.cpp +++ b/src/viewer/test/feature/test_window.cpp @@ -10,7 +10,7 @@ #include #include "glad/glad.h" -#include "imview/window.hpp" +#include "viewer/window.hpp" using namespace quickviz; diff --git a/src/imview/test/test_box.cpp b/src/viewer/test/test_box.cpp similarity index 96% rename from src/imview/test/test_box.cpp rename to src/viewer/test/test_box.cpp index b2289b7..29c99c2 100644 --- a/src/imview/test/test_box.cpp +++ b/src/viewer/test/test_box.cpp @@ -8,8 +8,8 @@ #include -#include "imview/viewer.hpp" -#include "imview/box.hpp" +#include "viewer/viewer.hpp" +#include "viewer/box.hpp" #include "feature/scene_objects/imgui_fixed_panel.hpp" diff --git a/src/imview/test/test_box_inside_box.cpp b/src/viewer/test/test_box_inside_box.cpp similarity index 97% rename from src/imview/test/test_box_inside_box.cpp rename to src/viewer/test/test_box_inside_box.cpp index 0ffb7aa..1ec973c 100644 --- a/src/imview/test/test_box_inside_box.cpp +++ b/src/viewer/test/test_box_inside_box.cpp @@ -9,8 +9,8 @@ #include -#include "imview/viewer.hpp" -#include "imview/box.hpp" +#include "viewer/viewer.hpp" +#include "viewer/box.hpp" #include "feature/scene_objects/imtext_panel.hpp" #include "feature/scene_objects/imgui_fixed_panel.hpp" diff --git a/src/imview/test/test_panel.cpp b/src/viewer/test/test_panel.cpp similarity index 98% rename from src/imview/test/test_panel.cpp rename to src/viewer/test/test_panel.cpp index 7e5518b..ee809b0 100644 --- a/src/imview/test/test_panel.cpp +++ b/src/viewer/test/test_panel.cpp @@ -7,8 +7,8 @@ * Copyright (c) 2021 Weston Robot Pte. Ltd. */ -#include "imview/viewer.hpp" -#include "imview/panel.hpp" +#include "viewer/viewer.hpp" +#include "viewer/panel.hpp" using namespace quickviz::swviz; diff --git a/src/imview/test/test_viewer_fonts.cpp b/src/viewer/test/test_viewer_fonts.cpp similarity index 98% rename from src/imview/test/test_viewer_fonts.cpp rename to src/viewer/test/test_viewer_fonts.cpp index 9a16577..58c481e 100644 --- a/src/imview/test/test_viewer_fonts.cpp +++ b/src/viewer/test/test_viewer_fonts.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2021 Ruixiang Du (rdu) */ -#include "imview/viewer.hpp" +#include "viewer/viewer.hpp" using namespace quickviz; diff --git a/src/imview/test/test_window_gl_triangle.cpp b/src/viewer/test/test_window_gl_triangle.cpp similarity index 99% rename from src/imview/test/test_window_gl_triangle.cpp rename to src/viewer/test/test_window_gl_triangle.cpp index 7dcffd5..8c622c8 100644 --- a/src/imview/test/test_window_gl_triangle.cpp +++ b/src/viewer/test/test_window_gl_triangle.cpp @@ -9,7 +9,7 @@ #include -#include "imview/window.hpp" +#include "viewer/window.hpp" using namespace quickviz; diff --git a/src/widget/CMakeLists.txt b/src/widget/CMakeLists.txt index a38db2c..9b4597f 100644 --- a/src/widget/CMakeLists.txt +++ b/src/widget/CMakeLists.txt @@ -31,7 +31,7 @@ add_library(widget ${OPENCV_COMP_SRC}) target_link_libraries(widget PUBLIC core - imcore imview + imcore viewer stb PkgConfig::Cairo PkgConfig::Fontconfig Threads::Threads diff --git a/src/widget/include/widget/buffered_cv_image_widget.hpp b/src/widget/include/widget/buffered_cv_image_widget.hpp index dcbdb50..f327fab 100644 --- a/src/widget/include/widget/buffered_cv_image_widget.hpp +++ b/src/widget/include/widget/buffered_cv_image_widget.hpp @@ -17,7 +17,7 @@ #include "glad/glad.h" -#include "imview/panel.hpp" +#include "viewer/panel.hpp" namespace quickviz { class BufferedCvImageWidget : public Panel { diff --git a/src/widget/include/widget/cairo_widget.hpp b/src/widget/include/widget/cairo_widget.hpp index 61b1a13..2258f09 100644 --- a/src/widget/include/widget/cairo_widget.hpp +++ b/src/widget/include/widget/cairo_widget.hpp @@ -20,7 +20,7 @@ #include "imgui.h" -#include "imview/panel.hpp" +#include "viewer/panel.hpp" #include "widget/details/cairo_context.hpp" #include "widget/details/cairo_draw.hpp" diff --git a/src/widget/include/widget/cv_image_widget.hpp b/src/widget/include/widget/cv_image_widget.hpp index d1075c2..488519d 100644 --- a/src/widget/include/widget/cv_image_widget.hpp +++ b/src/widget/include/widget/cv_image_widget.hpp @@ -14,7 +14,7 @@ #include -#include "imview/panel.hpp" +#include "viewer/panel.hpp" namespace quickviz { class CvImageWidget : public Panel { diff --git a/src/widget/include/widget/rt_line_plot_widget.hpp b/src/widget/include/widget/rt_line_plot_widget.hpp index c7b908c..1f4eb59 100644 --- a/src/widget/include/widget/rt_line_plot_widget.hpp +++ b/src/widget/include/widget/rt_line_plot_widget.hpp @@ -14,7 +14,7 @@ #include "core/buffer/buffer_registry.hpp" -#include "imview/panel.hpp" +#include "viewer/panel.hpp" #include "widget/details/scrolling_plot_buffer.hpp" diff --git a/src/widget/test/test_buffered_cv_image_widget.cpp b/src/widget/test/test_buffered_cv_image_widget.cpp index de9ff41..cfb3250 100644 --- a/src/widget/test/test_buffered_cv_image_widget.cpp +++ b/src/widget/test/test_buffered_cv_image_widget.cpp @@ -16,8 +16,8 @@ #include "core/buffer/ring_buffer.hpp" #include "core/buffer/double_buffer.hpp" -#include "imview/viewer.hpp" -#include "imview/box.hpp" +#include "viewer/viewer.hpp" +#include "viewer/box.hpp" // #include "scene_objects/gl_triangle_scene_object.hpp" #include "widget/buffered_cv_image_widget.hpp" diff --git a/src/widget/test/test_cairo_context.cpp b/src/widget/test/test_cairo_context.cpp index e7ecd0d..7130bc9 100644 --- a/src/widget/test/test_cairo_context.cpp +++ b/src/widget/test/test_cairo_context.cpp @@ -9,8 +9,8 @@ #include -#include "imview/viewer.hpp" -#include "imview/cairo_widget.hpp" +#include "viewer/viewer.hpp" +#include "viewer/cairo_widget.hpp" using namespace quickviz::swviz; diff --git a/src/widget/test/test_cairo_draw.cpp b/src/widget/test/test_cairo_draw.cpp index fdc635a..50f9bb3 100644 --- a/src/widget/test/test_cairo_draw.cpp +++ b/src/widget/test/test_cairo_draw.cpp @@ -9,9 +9,9 @@ #include -#include "imview/viewer.hpp" -#include "imview/cairo_widget.hpp" -#include "imview/cairo_draw.hpp" +#include "viewer/viewer.hpp" +#include "viewer/cairo_widget.hpp" +#include "viewer/cairo_draw.hpp" using namespace quickviz::swviz; diff --git a/src/widget/test/test_cairo_normalize.cpp b/src/widget/test/test_cairo_normalize.cpp index b0f1884..c8afcf4 100644 --- a/src/widget/test/test_cairo_normalize.cpp +++ b/src/widget/test/test_cairo_normalize.cpp @@ -9,9 +9,9 @@ #include -#include "imview/viewer.hpp" -#include "imview/cairo_widget.hpp" -#include "imview/cairo_draw.hpp" +#include "viewer/viewer.hpp" +#include "viewer/cairo_widget.hpp" +#include "viewer/cairo_draw.hpp" using namespace quickviz::swviz; diff --git a/src/widget/test/test_cairo_widget.cpp b/src/widget/test/test_cairo_widget.cpp index a771f24..c27f225 100644 --- a/src/widget/test/test_cairo_widget.cpp +++ b/src/widget/test/test_cairo_widget.cpp @@ -10,7 +10,7 @@ #include #include -#include "imview/viewer.hpp" +#include "viewer/viewer.hpp" #include "widget/cairo_widget.hpp" #ifndef M_PI diff --git a/src/widget/test/test_cv_image_widget.cpp b/src/widget/test/test_cv_image_widget.cpp index 6f4a46b..428ee31 100644 --- a/src/widget/test/test_cv_image_widget.cpp +++ b/src/widget/test/test_cv_image_widget.cpp @@ -12,7 +12,7 @@ #include -#include "imview/viewer.hpp" +#include "viewer/viewer.hpp" #include "widget/cv_image_widget.hpp" // #include "scene_objects/gl_triangle_scene_object.hpp" diff --git a/src/widget/test/test_implot_widget.cpp b/src/widget/test/test_implot_widget.cpp index 16e1a9a..ccf3205 100644 --- a/src/widget/test/test_implot_widget.cpp +++ b/src/widget/test/test_implot_widget.cpp @@ -14,7 +14,7 @@ #include "core/buffer/buffer_registry.hpp" #include "core/buffer/ring_buffer.hpp" -#include "imview/viewer.hpp" +#include "viewer/viewer.hpp" #include "widget/rt_line_plot_widget.hpp" // #include "scene_objects/imtext_panel.hpp" // #include "scene_objects/gl_triangle_scene_object.hpp" diff --git a/src/widget/test/test_plot_buffer.cpp b/src/widget/test/test_plot_buffer.cpp index b3b09c4..8ae82c1 100644 --- a/src/widget/test/test_plot_buffer.cpp +++ b/src/widget/test/test_plot_buffer.cpp @@ -9,8 +9,8 @@ #include -#include "imview/viewer.hpp" -#include "imview/data_buffer.hpp" +#include "viewer/viewer.hpp" +#include "viewer/data_buffer.hpp" using namespace quickviz::swviz; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 18839ce..fad8366 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -78,7 +78,7 @@ add_executable(test_renderer_pipeline target_link_libraries(test_renderer_pipeline PRIVATE gtest_main - imview + viewer gldraw test_utils ) @@ -87,16 +87,16 @@ gtest_discover_tests(test_renderer_pipeline ) # ImView integration tests -add_executable(test_imview_integration - integration/test_imview_integration.cpp +add_executable(test_viewer_integration + integration/test_viewer_integration.cpp ) -target_link_libraries(test_imview_integration +target_link_libraries(test_viewer_integration PRIVATE gtest_main - imview + viewer test_utils ) -gtest_discover_tests(test_imview_integration +gtest_discover_tests(test_viewer_integration PROPERTIES LABELS "integration" ) @@ -111,7 +111,7 @@ add_executable(test_memory_leaks target_link_libraries(test_memory_leaks PRIVATE gtest_main - imview + viewer gldraw test_utils ) @@ -131,7 +131,7 @@ if(benchmark_FOUND) target_link_libraries(benchmark_rendering PRIVATE benchmark::benchmark - imview + viewer gldraw core ) @@ -214,8 +214,8 @@ if(ENABLE_COVERAGE AND CMAKE_BUILD_TYPE STREQUAL "Debug") target_compile_options(test_renderer_pipeline PRIVATE --coverage) target_link_options(test_renderer_pipeline PRIVATE --coverage) - target_compile_options(test_imview_integration PRIVATE --coverage) - target_link_options(test_imview_integration PRIVATE --coverage) + target_compile_options(test_viewer_integration PRIVATE --coverage) + target_link_options(test_viewer_integration PRIVATE --coverage) target_compile_options(test_memory_leaks PRIVATE --coverage) target_link_options(test_memory_leaks PRIVATE --coverage) diff --git a/tests/README.md b/tests/README.md index 5827cf9..7fa9cd1 100644 --- a/tests/README.md +++ b/tests/README.md @@ -26,7 +26,7 @@ tests/ ### 2. Integration Tests (`integration/`) - **Renderer Pipeline Tests** (`test_renderer_pipeline.cpp`): End-to-end rendering pipeline testing -- **ImView Integration Tests** (`test_imview_integration.cpp`): GUI component integration testing +- **ImView Integration Tests** (`test_viewer_integration.cpp`): GUI component integration testing ### 3. Performance Benchmarks (`benchmarks/`) diff --git a/tests/integration/test_renderer_pipeline.cpp b/tests/integration/test_renderer_pipeline.cpp index d259f4a..ebc4940 100644 --- a/tests/integration/test_renderer_pipeline.cpp +++ b/tests/integration/test_renderer_pipeline.cpp @@ -13,7 +13,7 @@ #include #include -#include "imview/viewer.hpp" +#include "viewer/viewer.hpp" #include "gldraw/gl_scene_panel.hpp" #include "gldraw/renderable/triangle.hpp" #include "gldraw/renderable/point_cloud.hpp" diff --git a/tests/integration/test_imview_integration.cpp b/tests/integration/test_viewer_integration.cpp similarity index 95% rename from tests/integration/test_imview_integration.cpp rename to tests/integration/test_viewer_integration.cpp index fc5b1e5..19aa513 100644 --- a/tests/integration/test_imview_integration.cpp +++ b/tests/integration/test_viewer_integration.cpp @@ -1,5 +1,5 @@ /* - * @file test_imview_integration.cpp + * @file test_viewer_integration.cpp * @date 2024-06-25 * @brief Integration tests for ImView GUI components * @@ -11,11 +11,11 @@ #include #include -#include "imview/viewer.hpp" -#include "imview/panel.hpp" -#include "imview/box.hpp" -#include "imview/scene_object.hpp" -#include "imview/styling.hpp" +#include "viewer/viewer.hpp" +#include "viewer/panel.hpp" +#include "viewer/box.hpp" +#include "viewer/scene_object.hpp" +#include "viewer/styling.hpp" using namespace quickviz; diff --git a/third_party/imcore/implot3d b/third_party/imcore/implot3d new file mode 160000 index 0000000..41ae3e4 --- /dev/null +++ b/third_party/imcore/implot3d @@ -0,0 +1 @@ +Subproject commit 41ae3e447c0de20ecab95d38a4b4dc0835a3efc2 From 96d125fc68e2677700ad743f708b4339b1756d0a Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 20:38:53 +0800 Subject: [PATCH 02/22] refactor: rename gldraw module to scene MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renames the 3D rendering module from `gldraw` (tech-flavored) to `scene` (intent-flavored). Aligns with the visualization-first naming scheme: users pick the module by what they want to visualize, not by which backend it uses internally. Renames performed: - src/gldraw/ → src/scene/ - src/scene/include/gldraw/ → src/scene/include/scene/ - docs/notes/gldraw_refactor_plan.md → scene_refactor_plan.md - docs/notes/input_handling_system_for_gldraw.md → input_handling_system_for_scene.md - All `gldraw` tokens → `scene` (CMake target, link directives, includes, doc references) - All `GLDRAW` tokens → `SCENE` (header guards, macros) The `Gl*` class prefix (GlSceneManager, GlScenePanel, GlViewer) is deliberately untouched — it correctly signals "this class wraps OpenGL handles" and is independent of the module name. No behavior changes. Build configures and links cleanly. Same 2 pre-existing PCL test failures, no new regressions. Library boundary holds. Part of the visualization-first module reorg. Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 8 +-- CMakeLists.txt | 2 +- TODO.md | 4 +- docs/notes/canvas_optimization_analysis.md | 12 ++--- docs/notes/core_imview_code_quality.md | 2 +- ....md => input_handling_system_for_scene.md} | 10 ++-- ...efactor_plan.md => scene_refactor_plan.md} | 4 +- docs/notes/session_summary_2025-01-28.md | 18 +++---- docs/notes/sphere_migration_example.md | 2 +- sample/editor/CMakeLists.txt | 2 +- sample/editor/README.md | 2 +- sample/editor/editor_state.cpp | 6 +-- sample/editor/editor_viewport.hpp | 4 +- sample/editor/main.cpp | 4 +- sample/editor/panels/editor_tool_panel.cpp | 2 +- sample/pointcloud_viewer/CMakeLists.txt | 2 +- .../interactive_scene_manager.hpp | 6 +-- sample/pointcloud_viewer/main.cpp | 6 +-- .../point_cloud_tool_panel.hpp | 4 +- sample/quickviz_demo_app/CMakeLists.txt | 2 +- src/CMakeLists.txt | 2 +- src/gldraw/test/renderable/CMakeLists.txt | 52 ------------------- src/pcl_bridge/CMakeLists.txt | 4 +- .../include/pcl_bridge/pcl_conversions.hpp | 2 +- src/pcl_bridge/src/pcl_conversions.cpp | 2 +- src/pcl_bridge/src/pcl_loader.cpp | 2 +- src/pcl_bridge/src/pcl_visualization.cpp | 2 +- src/pcl_bridge/test/CMakeLists.txt | 8 +-- src/pcl_bridge/test/test_pcd.cpp | 6 +-- src/pcl_bridge/test/test_pcl_bridge.cpp | 6 +-- .../test/test_pcl_loader_render.cpp | 6 +-- src/pcl_bridge/test/unit_test/CMakeLists.txt | 10 ++-- .../test/unit_test/test_pcl_loader.cpp | 2 +- src/{gldraw => scene}/CMakeLists.txt | 12 ++--- .../gldraw => scene/include/scene}/camera.hpp | 0 .../include/scene}/camera_control_config.hpp | 6 +-- .../include/scene}/camera_controller.hpp | 2 +- .../include/scene}/coordinate_transformer.hpp | 0 .../feedback/object_feedback_handler.hpp | 2 +- .../feedback/point_cloud_feedback_handler.hpp | 4 +- .../feedback/visual_feedback_system.hpp | 0 .../include/scene}/font_renderer.hpp | 0 .../include/scene}/frame_buffer.hpp | 0 .../include/scene}/gl_scene_panel.hpp | 10 ++-- .../include/scene}/gl_viewer.hpp | 6 +-- .../scene}/interface/opengl_object.hpp | 0 .../include/scene}/renderable/arrow.hpp | 2 +- .../include/scene}/renderable/billboard.hpp | 4 +- .../scene}/renderable/bounding_box.hpp | 2 +- .../include/scene}/renderable/canvas.hpp | 8 +-- .../scene}/renderable/coordinate_frame.hpp | 2 +- .../include/scene}/renderable/cylinder.hpp | 2 +- .../renderable/details/canvas_batching.hpp | 2 +- .../renderable/details/canvas_performance.hpp | 0 .../details/point_layer_manager.hpp | 0 .../include/scene}/renderable/frustum.hpp | 2 +- .../scene}/renderable/geometric_primitive.hpp | 2 +- .../include/scene}/renderable/grid.hpp | 2 +- .../include/scene}/renderable/line_strip.hpp | 4 +- .../include/scene}/renderable/mesh.hpp | 2 +- .../include/scene}/renderable/path.hpp | 2 +- .../include/scene}/renderable/plane.hpp | 2 +- .../include/scene}/renderable/point_cloud.hpp | 4 +- .../include/scene}/renderable/pose.hpp | 2 +- .../include/scene}/renderable/sphere.hpp | 2 +- .../include/scene}/renderable/texture.hpp | 2 +- .../include/scene}/renderable/triangle.hpp | 2 +- .../include/scene}/renderable/types.hpp | 0 .../include/scene}/scene_input_handler.hpp | 18 +++---- .../include/scene}/scene_manager.hpp | 12 ++--- .../include/scene}/selection_manager.hpp | 2 +- .../gldraw => scene/include/scene}/shader.hpp | 0 .../include/scene}/shader_program.hpp | 0 .../include/scene}/tools/interaction_tool.hpp | 0 .../scene}/tools/point_selection_tool.hpp | 4 +- src/{gldraw => scene}/src/camera.cpp | 2 +- .../src/camera_controller.cpp | 2 +- .../src/feedback/object_feedback_handler.cpp | 6 +-- .../feedback/point_cloud_feedback_handler.cpp | 4 +- .../src/feedback/visual_feedback_system.cpp | 16 +++--- src/{gldraw => scene}/src/font_renderer.cpp | 2 +- src/{gldraw => scene}/src/frame_buffer.cpp | 2 +- src/{gldraw => scene}/src/gl_scene_panel.cpp | 8 +-- src/{gldraw => scene}/src/gl_viewer.cpp | 2 +- .../src/renderable/arrow.cpp | 4 +- .../src/renderable/billboard.cpp | 4 +- .../src/renderable/bounding_box.cpp | 4 +- .../src/renderable/canvas.cpp | 6 +-- .../src/renderable/coordinate_frame.cpp | 2 +- .../src/renderable/cylinder.cpp | 4 +- .../details/batched_render_strategy.cpp | 4 +- .../details/batched_render_strategy.hpp | 2 +- .../src/renderable/details/canvas_data.hpp | 2 +- .../details/canvas_data_manager.cpp | 0 .../details/canvas_data_manager.hpp | 4 +- .../details/individual_render_strategy.cpp | 2 +- .../details/individual_render_strategy.hpp | 0 .../details/opengl_resource_pool.cpp | 0 .../details/opengl_resource_pool.hpp | 0 .../details/point_layer_manager.cpp | 2 +- .../renderable/details/render_strategy.hpp | 0 .../renderable/details/shape_generators.cpp | 0 .../renderable/details/shape_generators.hpp | 0 .../src/renderable/details/shape_renderer.cpp | 2 +- .../src/renderable/details/shape_renderer.hpp | 0 .../details/shape_renderer_utils.cpp | 2 +- .../details/shape_renderer_utils.hpp | 2 +- .../src/renderable/frustum.cpp | 4 +- .../src/renderable/geometric_primitive.cpp | 4 +- src/{gldraw => scene}/src/renderable/grid.cpp | 4 +- .../src/renderable/line_strip.cpp | 4 +- src/{gldraw => scene}/src/renderable/mesh.cpp | 4 +- src/{gldraw => scene}/src/renderable/path.cpp | 4 +- .../src/renderable/plane.cpp | 4 +- .../src/renderable/point_cloud.cpp | 4 +- src/{gldraw => scene}/src/renderable/pose.cpp | 4 +- .../src/renderable/sphere.cpp | 4 +- .../src/renderable/texture.cpp | 2 +- .../src/renderable/triangle.cpp | 2 +- .../src/scene_input_handler.cpp | 8 +-- src/{gldraw => scene}/src/scene_manager.cpp | 12 ++--- .../src/selection_manager.cpp | 8 +-- src/{gldraw => scene}/src/shader.cpp | 2 +- src/{gldraw => scene}/src/shader_program.cpp | 2 +- .../src/tools/interaction_tool.cpp | 4 +- .../src/tools/point_selection_tool.cpp | 6 +-- src/{gldraw => scene}/test/CMakeLists.txt | 22 ++++---- .../test/feature/CMakeLists.txt | 14 ++--- .../test/feature/test_camera.cpp | 6 +-- .../feature/test_camera_configuration.cpp | 8 +-- .../feature/test_camera_control_mappings.cpp | 10 ++-- .../test/feature/test_gl_scene_panel.cpp | 6 +-- .../test/feature/test_layer_system.cpp | 8 +-- .../test/feature/test_robot_frames.cpp | 6 +-- .../feature/test_visual_feedback_system.cpp | 10 ++-- .../test/input/test_gamepad_input.cpp | 2 +- src/scene/test/renderable/CMakeLists.txt | 52 +++++++++++++++++++ .../test/renderable/test_arrow.cpp | 4 +- .../test/renderable/test_billboard.cpp | 10 ++-- .../test/renderable/test_bounding_box.cpp | 4 +- .../test/renderable/test_canvas.cpp | 6 +-- .../test/renderable/test_coordinate_frame.cpp | 6 +-- .../test/renderable/test_cylinder.cpp | 4 +- .../test/renderable/test_frustum.cpp | 6 +-- .../test/renderable/test_grid.cpp | 4 +- .../test/renderable/test_line_strip.cpp | 4 +- .../test/renderable/test_mesh.cpp | 4 +- .../test/renderable/test_path.cpp | 6 +-- .../test/renderable/test_plane.cpp | 8 +-- .../test/renderable/test_point_cloud.cpp | 4 +- .../test/renderable/test_pose.cpp | 6 +-- .../test/renderable/test_sphere.cpp | 4 +- .../test/renderable/test_texture.cpp | 4 +- .../test/renderable/test_triangle.cpp | 4 +- .../test/selection/CMakeLists.txt | 4 +- .../test/selection/README.md | 0 .../test/selection/selection_test_utils.cpp | 12 ++--- .../test/selection/selection_test_utils.hpp | 6 +-- .../selection/test_bounding_box_selection.cpp | 2 +- .../test_comprehensive_selection.cpp | 8 +-- .../selection/test_cylinder_selection.cpp | 2 +- .../test/selection/test_id_buffer_debug.cpp | 2 +- .../selection/test_line_strip_selection.cpp | 2 +- .../test/selection/test_mesh_selection.cpp | 2 +- .../test/selection/test_object_selection.cpp | 10 ++-- .../selection/test_point_cloud_selection.cpp | 2 +- .../test/selection/test_sphere_selection.cpp | 2 +- .../test/test_camera_raw.cpp | 6 +-- src/{gldraw => scene}/test/test_canvas_st.cpp | 10 ++-- .../test/test_coordinate_system.cpp | 8 +-- .../test/test_font_renderer.cpp | 2 +- .../test/test_framebuffer.cpp | 2 +- .../test/test_layer_system_box.cpp | 8 +-- .../test/test_nav_map_rendering.cpp | 10 ++-- .../test_point_cloud_buffer_strategies.cpp | 6 +-- .../test/test_point_cloud_realtime.cpp | 6 +-- .../test/test_primitive_drawing.cpp | 10 ++-- src/{gldraw => scene}/test/test_shader.cpp | 4 +- .../test/tools/test_point_selection_tool.cpp | 8 +-- tests/CMakeLists.txt | 8 +-- .../benchmarks/profile_canvas_performance.cpp | 2 +- tests/integration/test_renderer_pipeline.cpp | 12 ++--- tests/unit/test_geometric_primitive_types.cpp | 2 +- 183 files changed, 450 insertions(+), 450 deletions(-) rename docs/notes/{input_handling_system_for_gldraw.md => input_handling_system_for_scene.md} (96%) rename docs/notes/{gldraw_refactor_plan.md => scene_refactor_plan.md} (98%) delete mode 100644 src/gldraw/test/renderable/CMakeLists.txt rename src/{gldraw => scene}/CMakeLists.txt (89%) rename src/{gldraw/include/gldraw => scene/include/scene}/camera.hpp (100%) rename src/{gldraw/include/gldraw => scene/include/scene}/camera_control_config.hpp (98%) rename src/{gldraw/include/gldraw => scene/include/scene}/camera_controller.hpp (99%) rename src/{gldraw/include/gldraw => scene/include/scene}/coordinate_transformer.hpp (100%) rename src/{gldraw/include/gldraw => scene/include/scene}/feedback/object_feedback_handler.hpp (99%) rename src/{gldraw/include/gldraw => scene/include/scene}/feedback/point_cloud_feedback_handler.hpp (97%) rename src/{gldraw/include/gldraw => scene/include/scene}/feedback/visual_feedback_system.hpp (100%) rename src/{gldraw/include/gldraw => scene/include/scene}/font_renderer.hpp (100%) rename src/{gldraw/include/gldraw => scene/include/scene}/frame_buffer.hpp (100%) rename src/{gldraw/include/gldraw => scene/include/scene}/gl_scene_panel.hpp (96%) rename src/{gldraw/include/gldraw => scene/include/scene}/gl_viewer.hpp (97%) rename src/{gldraw/include/gldraw => scene/include/scene}/interface/opengl_object.hpp (100%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/arrow.hpp (98%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/billboard.hpp (98%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/bounding_box.hpp (98%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/canvas.hpp (98%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/coordinate_frame.hpp (97%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/cylinder.hpp (99%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/details/canvas_batching.hpp (98%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/details/canvas_performance.hpp (100%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/details/point_layer_manager.hpp (100%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/frustum.hpp (99%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/geometric_primitive.hpp (99%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/grid.hpp (96%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/line_strip.hpp (97%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/mesh.hpp (98%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/path.hpp (99%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/plane.hpp (98%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/point_cloud.hpp (98%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/pose.hpp (99%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/sphere.hpp (99%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/texture.hpp (98%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/triangle.hpp (95%) rename src/{gldraw/include/gldraw => scene/include/scene}/renderable/types.hpp (100%) rename src/{gldraw/include/gldraw => scene/include/scene}/scene_input_handler.hpp (91%) rename src/{gldraw/include/gldraw => scene/include/scene}/scene_manager.hpp (96%) rename src/{gldraw/include/gldraw => scene/include/scene}/selection_manager.hpp (99%) rename src/{gldraw/include/gldraw => scene/include/scene}/shader.hpp (100%) rename src/{gldraw/include/gldraw => scene/include/scene}/shader_program.hpp (100%) rename src/{gldraw/include/gldraw => scene/include/scene}/tools/interaction_tool.hpp (100%) rename src/{gldraw/include/gldraw => scene/include/scene}/tools/point_selection_tool.hpp (99%) rename src/{gldraw => scene}/src/camera.cpp (99%) rename src/{gldraw => scene}/src/camera_controller.cpp (99%) rename src/{gldraw => scene}/src/feedback/object_feedback_handler.cpp (98%) rename src/{gldraw => scene}/src/feedback/point_cloud_feedback_handler.cpp (98%) rename src/{gldraw => scene}/src/feedback/visual_feedback_system.cpp (97%) rename src/{gldraw => scene}/src/font_renderer.cpp (99%) rename src/{gldraw => scene}/src/frame_buffer.cpp (99%) rename src/{gldraw => scene}/src/gl_scene_panel.cpp (97%) rename src/{gldraw => scene}/src/gl_viewer.cpp (99%) rename src/{gldraw => scene}/src/renderable/arrow.cpp (99%) rename src/{gldraw => scene}/src/renderable/billboard.cpp (99%) rename src/{gldraw => scene}/src/renderable/bounding_box.cpp (99%) rename src/{gldraw => scene}/src/renderable/canvas.cpp (99%) rename src/{gldraw => scene}/src/renderable/coordinate_frame.cpp (99%) rename src/{gldraw => scene}/src/renderable/cylinder.cpp (99%) rename src/{gldraw => scene}/src/renderable/details/batched_render_strategy.cpp (98%) rename src/{gldraw => scene}/src/renderable/details/batched_render_strategy.hpp (98%) rename src/{gldraw => scene}/src/renderable/details/canvas_data.hpp (99%) rename src/{gldraw => scene}/src/renderable/details/canvas_data_manager.cpp (100%) rename src/{gldraw => scene}/src/renderable/details/canvas_data_manager.hpp (98%) rename src/{gldraw => scene}/src/renderable/details/individual_render_strategy.cpp (99%) rename src/{gldraw => scene}/src/renderable/details/individual_render_strategy.hpp (100%) rename src/{gldraw => scene}/src/renderable/details/opengl_resource_pool.cpp (100%) rename src/{gldraw => scene}/src/renderable/details/opengl_resource_pool.hpp (100%) rename src/{gldraw => scene}/src/renderable/details/point_layer_manager.cpp (99%) rename src/{gldraw => scene}/src/renderable/details/render_strategy.hpp (100%) rename src/{gldraw => scene}/src/renderable/details/shape_generators.cpp (100%) rename src/{gldraw => scene}/src/renderable/details/shape_generators.hpp (100%) rename src/{gldraw => scene}/src/renderable/details/shape_renderer.cpp (99%) rename src/{gldraw => scene}/src/renderable/details/shape_renderer.hpp (100%) rename src/{gldraw => scene}/src/renderable/details/shape_renderer_utils.cpp (99%) rename src/{gldraw => scene}/src/renderable/details/shape_renderer_utils.hpp (99%) rename src/{gldraw => scene}/src/renderable/frustum.cpp (99%) rename src/{gldraw => scene}/src/renderable/geometric_primitive.cpp (99%) rename src/{gldraw => scene}/src/renderable/grid.cpp (98%) rename src/{gldraw => scene}/src/renderable/line_strip.cpp (99%) rename src/{gldraw => scene}/src/renderable/mesh.cpp (99%) rename src/{gldraw => scene}/src/renderable/path.cpp (99%) rename src/{gldraw => scene}/src/renderable/plane.cpp (99%) rename src/{gldraw => scene}/src/renderable/point_cloud.cpp (99%) rename src/{gldraw => scene}/src/renderable/pose.cpp (99%) rename src/{gldraw => scene}/src/renderable/sphere.cpp (99%) rename src/{gldraw => scene}/src/renderable/texture.cpp (99%) rename src/{gldraw => scene}/src/renderable/triangle.cpp (99%) rename src/{gldraw => scene}/src/scene_input_handler.cpp (98%) rename src/{gldraw => scene}/src/scene_manager.cpp (95%) rename src/{gldraw => scene}/src/selection_manager.cpp (99%) rename src/{gldraw => scene}/src/shader.cpp (99%) rename src/{gldraw => scene}/src/shader_program.cpp (99%) rename src/{gldraw => scene}/src/tools/interaction_tool.cpp (98%) rename src/{gldraw => scene}/src/tools/point_selection_tool.cpp (99%) rename src/{gldraw => scene}/test/CMakeLists.txt (62%) rename src/{gldraw => scene}/test/feature/CMakeLists.txt (52%) rename src/{gldraw => scene}/test/feature/test_camera.cpp (95%) rename src/{gldraw => scene}/test/feature/test_camera_configuration.cpp (97%) rename src/{gldraw => scene}/test/feature/test_camera_control_mappings.cpp (97%) rename src/{gldraw => scene}/test/feature/test_gl_scene_panel.cpp (92%) rename src/{gldraw => scene}/test/feature/test_layer_system.cpp (98%) rename src/{gldraw => scene}/test/feature/test_robot_frames.cpp (97%) rename src/{gldraw => scene}/test/feature/test_visual_feedback_system.cpp (97%) rename src/{gldraw => scene}/test/input/test_gamepad_input.cpp (99%) create mode 100644 src/scene/test/renderable/CMakeLists.txt rename src/{gldraw => scene}/test/renderable/test_arrow.cpp (98%) rename src/{gldraw => scene}/test/renderable/test_billboard.cpp (98%) rename src/{gldraw => scene}/test/renderable/test_bounding_box.cpp (98%) rename src/{gldraw => scene}/test/renderable/test_canvas.cpp (98%) rename src/{gldraw => scene}/test/renderable/test_coordinate_frame.cpp (97%) rename src/{gldraw => scene}/test/renderable/test_cylinder.cpp (98%) rename src/{gldraw => scene}/test/renderable/test_frustum.cpp (98%) rename src/{gldraw => scene}/test/renderable/test_grid.cpp (98%) rename src/{gldraw => scene}/test/renderable/test_line_strip.cpp (98%) rename src/{gldraw => scene}/test/renderable/test_mesh.cpp (99%) rename src/{gldraw => scene}/test/renderable/test_path.cpp (99%) rename src/{gldraw => scene}/test/renderable/test_plane.cpp (98%) rename src/{gldraw => scene}/test/renderable/test_point_cloud.cpp (98%) rename src/{gldraw => scene}/test/renderable/test_pose.cpp (98%) rename src/{gldraw => scene}/test/renderable/test_sphere.cpp (98%) rename src/{gldraw => scene}/test/renderable/test_texture.cpp (99%) rename src/{gldraw => scene}/test/renderable/test_triangle.cpp (98%) rename src/{gldraw => scene}/test/selection/CMakeLists.txt (93%) rename src/{gldraw => scene}/test/selection/README.md (100%) rename src/{gldraw => scene}/test/selection/selection_test_utils.cpp (98%) rename src/{gldraw => scene}/test/selection/selection_test_utils.hpp (98%) rename src/{gldraw => scene}/test/selection/test_bounding_box_selection.cpp (99%) rename src/{gldraw => scene}/test/selection/test_comprehensive_selection.cpp (98%) rename src/{gldraw => scene}/test/selection/test_cylinder_selection.cpp (99%) rename src/{gldraw => scene}/test/selection/test_id_buffer_debug.cpp (98%) rename src/{gldraw => scene}/test/selection/test_line_strip_selection.cpp (99%) rename src/{gldraw => scene}/test/selection/test_mesh_selection.cpp (99%) rename src/{gldraw => scene}/test/selection/test_object_selection.cpp (99%) rename src/{gldraw => scene}/test/selection/test_point_cloud_selection.cpp (99%) rename src/{gldraw => scene}/test/selection/test_sphere_selection.cpp (99%) rename src/{gldraw => scene}/test/test_camera_raw.cpp (97%) rename src/{gldraw => scene}/test/test_canvas_st.cpp (96%) rename src/{gldraw => scene}/test/test_coordinate_system.cpp (96%) rename src/{gldraw => scene}/test/test_font_renderer.cpp (99%) rename src/{gldraw => scene}/test/test_framebuffer.cpp (99%) rename src/{gldraw => scene}/test/test_layer_system_box.cpp (97%) rename src/{gldraw => scene}/test/test_nav_map_rendering.cpp (97%) rename src/{gldraw => scene}/test/test_point_cloud_buffer_strategies.cpp (98%) rename src/{gldraw => scene}/test/test_point_cloud_realtime.cpp (97%) rename src/{gldraw => scene}/test/test_primitive_drawing.cpp (97%) rename src/{gldraw => scene}/test/test_shader.cpp (97%) rename src/{gldraw => scene}/test/tools/test_point_selection_tool.cpp (98%) diff --git a/CLAUDE.md b/CLAUDE.md index b7cf63e..3a8ee3c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ This document provides comprehensive guidance for working with the QuickViz C++ QuickViz is a C++ visualization library for robotics applications, providing: - **viewer**: Automatic layout management and UI widgets (buttons, sliders, text boxes) -- **gldraw**: 2D/3D real-time rendering with OpenGL +- **scene**: 2D/3D real-time rendering with OpenGL - **widget**: Cairo-based drawing and plotting widgets - **core**: Event system, buffers, and shared utilities @@ -72,7 +72,7 @@ src/ ├── core/ # Event system, buffers, utilities (depends on nothing) ├── viewer/ # GLFW window management, ImGui integration ├── widget/ # Cairo drawing, image widgets, plotting -├── gldraw/ # OpenGL 3D rendering, point clouds, textures +├── scene/ # OpenGL 3D rendering, point clouds, textures ├── pcl_bridge/ # Optional PCL adapter (file loading, conversions) ├── cvdraw/ # OpenCV-based drawing utilities (optional bridge) └── third_party/ # imgui, implot, stb, yoga, googletest @@ -111,7 +111,7 @@ operations) live in `sample/` and consume the library, never the reverse. - `Panel` extends `SceneObject` for ImGui panels - `Box` provides container with automatic layout via Yoga -#### 2. OpenGL Rendering Pipeline (gldraw) +#### 2. OpenGL Rendering Pipeline (scene) - `GlSceneManager` manages OpenGL objects and framebuffer - `OpenGlObject` interface for all renderable 3D objects - Render-to-texture approach with `FrameBuffer` @@ -382,7 +382,7 @@ ctest --output-on-failure ### Common Tasks **Add New Renderable Object**: -1. Inherit from `OpenGlObject` in `src/gldraw/renderable/` +1. Inherit from `OpenGlObject` in `src/scene/renderable/` 2. Implement shader loading and VAO/VBO setup 3. Override `OnDraw()` with OpenGL render calls 4. Add to `GlSceneManager` in application diff --git a/CMakeLists.txt b/CMakeLists.txt index 926fb65..f94a142 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,7 +153,7 @@ endforeach () # targets to install defined in each module add_library(quickviz INTERFACE) -target_link_libraries(quickviz INTERFACE core viewer widget gldraw visualization) +target_link_libraries(quickviz INTERFACE core viewer widget scene visualization) # export target configuration include(CMakePackageConfigHelpers) diff --git a/TODO.md b/TODO.md index be040c9..5c540a5 100644 --- a/TODO.md +++ b/TODO.md @@ -52,7 +52,7 @@ a visualization-justified hook. lightweight logger (also audit for leftover noise after the deletions) ### Smaller cleanups -- [ ] `src/gldraw/src/renderable/canvas.cpp` is 2069 LOC; split into +- [ ] `src/scene/src/renderable/canvas.cpp` is 2069 LOC; split into cohesive sub-files (~500 LOC target per CLAUDE.md) - [ ] Audit `interactive_scene_manager.cpp` for disabled/legacy paths left over from the editor migration; either finish or remove @@ -97,7 +97,7 @@ a visualization-justified hook. is in place) **Focus**: Re-anchor the library on visualization, then build the editor sample as the API check. -**Architecture**: Library = `core` + `viewer` + `widget` + `gldraw` + +**Architecture**: Library = `core` + `viewer` + `widget` + `scene` + `pcl_bridge` + `cvdraw` (optional). Apps live in `sample/`. --- diff --git a/docs/notes/canvas_optimization_analysis.md b/docs/notes/canvas_optimization_analysis.md index 0339ffa..ceb176f 100644 --- a/docs/notes/canvas_optimization_analysis.md +++ b/docs/notes/canvas_optimization_analysis.md @@ -13,7 +13,7 @@ Canvas implements a **production-grade optimization architecture** with sophisti ### 1. **Advanced Batching System** ✅ **ALREADY IMPLEMENTED** -**Files**: `src/gldraw/include/gldraw/renderable/details/canvas_batching.hpp` +**Files**: `src/scene/include/scene/renderable/details/canvas_batching.hpp` **Architecture**: - **Multi-tier batching system** with separate batches for different primitive types: @@ -49,7 +49,7 @@ struct BatchOrderTracker { ### 2. **Comprehensive Performance Monitoring** ✅ **ALREADY IMPLEMENTED** -**Files**: `src/gldraw/include/gldraw/renderable/details/canvas_performance.hpp` +**Files**: `src/scene/include/scene/renderable/details/canvas_performance.hpp` **Features**: - **Real-time rendering statistics** with detailed metrics: @@ -92,7 +92,7 @@ struct PerformanceConfig { ### 3. **Thread-Safe Data Management** ✅ **ALREADY IMPLEMENTED** -**Files**: `src/gldraw/src/renderable/details/canvas_data_manager.hpp/.cpp` +**Files**: `src/scene/src/renderable/details/canvas_data_manager.hpp/.cpp` **Architecture**: - **Complete thread-safe operation** with mutex protection on all data access @@ -132,7 +132,7 @@ public: ### 4. **GPU Resource Pool Management** ✅ **ALREADY IMPLEMENTED** -**Files**: `src/gldraw/src/renderable/details/opengl_resource_pool.hpp` +**Files**: `src/scene/src/renderable/details/opengl_resource_pool.hpp` **Features**: - **VAO/VBO pooling system** to eliminate per-frame OpenGL resource allocation @@ -169,8 +169,8 @@ public: ### 5. **Dual Render Strategy System** ✅ **ALREADY IMPLEMENTED** **Files**: -- `src/gldraw/src/renderable/details/batched_render_strategy.hpp` -- `src/gldraw/src/renderable/details/individual_render_strategy.hpp` +- `src/scene/src/renderable/details/batched_render_strategy.hpp` +- `src/scene/src/renderable/details/individual_render_strategy.hpp` **Architecture**: - **Strategy pattern implementation** with runtime switching: diff --git a/docs/notes/core_imview_code_quality.md b/docs/notes/core_imview_code_quality.md index 9431b42..ccdfe36 100644 --- a/docs/notes/core_imview_code_quality.md +++ b/docs/notes/core_imview_code_quality.md @@ -109,6 +109,6 @@ This document captures targeted, high‑impact improvements for the `core` and ` ## Notes - Related references in-tree: - - BufferRegistry usages: search for `BufferRegistry::GetInstance()` across `widget/` and `gldraw/`. + - BufferRegistry usages: search for `BufferRegistry::GetInstance()` across `widget/` and `scene/`. - Existing design notes: `docs/notes/core_module_review_2025-01-28.md`. diff --git a/docs/notes/input_handling_system_for_gldraw.md b/docs/notes/input_handling_system_for_scene.md similarity index 96% rename from docs/notes/input_handling_system_for_gldraw.md rename to docs/notes/input_handling_system_for_scene.md index 5a8d96b..8c6b3c5 100644 --- a/docs/notes/input_handling_system_for_gldraw.md +++ b/docs/notes/input_handling_system_for_scene.md @@ -1,6 +1,6 @@ -# Input Handling System for gldraw +# Input Handling System for scene -This directory contains a comprehensive input handling system for the gldraw module, providing flexible and extensible user interaction capabilities. +This directory contains a comprehensive input handling system for the scene module, providing flexible and extensible user interaction capabilities. ## Overview @@ -17,9 +17,9 @@ The input handling system is designed around the **InteractionMode** pattern, wh ## Quick Start ```cpp -#include "gldraw/gui/gl_input_handler.hpp" -#include "gldraw/input/camera_interaction_mode.hpp" -#include "gldraw/input/selection_interaction_mode.hpp" +#include "scene/gui/gl_input_handler.hpp" +#include "scene/input/camera_interaction_mode.hpp" +#include "scene/input/selection_interaction_mode.hpp" // Create input handler auto input_handler = std::make_unique(); diff --git a/docs/notes/gldraw_refactor_plan.md b/docs/notes/scene_refactor_plan.md similarity index 98% rename from docs/notes/gldraw_refactor_plan.md rename to docs/notes/scene_refactor_plan.md index baab585..136e89a 100644 --- a/docs/notes/gldraw_refactor_plan.md +++ b/docs/notes/scene_refactor_plan.md @@ -2,7 +2,7 @@ ## Overview -This document outlines a comprehensive refactor plan for the gldraw module to make it high-quality and high-performance for critical use cases, rather than complete but low-quality. The focus is on simplifying the architecture, optimizing performance bottlenecks, and creating a lean, maintainable codebase. +This document outlines a comprehensive refactor plan for the scene module to make it high-quality and high-performance for critical use cases, rather than complete but low-quality. The focus is on simplifying the architecture, optimizing performance bottlenecks, and creating a lean, maintainable codebase. ## Current Architecture Analysis @@ -393,7 +393,7 @@ struct PerformanceConfig { #### 5.1 Streamlined Directory Structure ```cpp -src/gldraw/ +src/scene/ ├── core/ // Essential components only │ ├── renderer.hpp // Main rendering coordinator │ ├── shader_manager.hpp // Cached shader system diff --git a/docs/notes/session_summary_2025-01-28.md b/docs/notes/session_summary_2025-01-28.md index d2267c6..c03bc8d 100644 --- a/docs/notes/session_summary_2025-01-28.md +++ b/docs/notes/session_summary_2025-01-28.md @@ -14,10 +14,10 @@ Comprehensive review of GLDraw module and design of enhanced input handling syst - ⚠️ Selection support needed for other renderables (mesh, cylinder, box, etc.) **Key Files Reviewed**: -- `src/gldraw/include/gldraw/selection_manager.hpp` - Main selection system -- `src/gldraw/include/gldraw/gl_scene_manager.hpp` - Scene management -- `src/gldraw/include/gldraw/renderable/point_cloud.hpp` - Point cloud with layers -- `src/gldraw/include/gldraw/renderable/layer_manager.hpp` - Layer system +- `src/scene/include/scene/selection_manager.hpp` - Main selection system +- `src/scene/include/scene/gl_scene_manager.hpp` - Scene management +- `src/scene/include/scene/renderable/point_cloud.hpp` - Point cloud with layers +- `src/scene/include/scene/renderable/layer_manager.hpp` - Layer system ### 2. Input Handling System Design @@ -42,7 +42,7 @@ Comprehensive review of GLDraw module and design of enhanced input handling syst **DECISION: Use Core Module (Option 1)** - Extend existing `core` module with InputEvent classes - Leverage existing EventDispatcher and thread-safe infrastructure -- Clean dependency: core → viewer → gldraw +- Clean dependency: core → viewer → scene **Rationale**: - Reuses robust event system already in core @@ -106,9 +106,9 @@ SelectionManager::Select() ``` InputEvent (core) → InputDispatcher (core) ↓ -InputMapping (viewer/gldraw) +InputMapping (viewer/scene) ↓ -SelectionManager handlers (gldraw) +SelectionManager handlers (scene) ↓ Visual feedback via LayerManager ``` @@ -122,8 +122,8 @@ Visual feedback via LayerManager ### 5. Files to Modify - `src/core/CMakeLists.txt` - Add new input files -- `src/gldraw/src/gl_scene_panel.cpp` - Update HandleInput() -- `src/gldraw/src/selection_manager.cpp` - Add multiple handlers +- `src/scene/src/gl_scene_panel.cpp` - Update HandleInput() +- `src/scene/src/selection_manager.cpp` - Add multiple handlers ## Session Metrics - Files reviewed: 15+ diff --git a/docs/notes/sphere_migration_example.md b/docs/notes/sphere_migration_example.md index 99907a0..aadd7df 100644 --- a/docs/notes/sphere_migration_example.md +++ b/docs/notes/sphere_migration_example.md @@ -13,7 +13,7 @@ This document shows how to migrate the existing Sphere class to inherit from Geo ```cpp // sphere.hpp - Updated to inherit from GeometricPrimitive -#include "gldraw/renderable/geometric_primitive.hpp" +#include "scene/renderable/geometric_primitive.hpp" namespace quickviz { diff --git a/sample/editor/CMakeLists.txt b/sample/editor/CMakeLists.txt index faaf79c..30fce07 100644 --- a/sample/editor/CMakeLists.txt +++ b/sample/editor/CMakeLists.txt @@ -9,7 +9,7 @@ add_executable(quickviz_editor target_compile_definitions(quickviz_editor PRIVATE ${PCL_DEFINITIONS}) target_link_libraries(quickviz_editor PRIVATE - gldraw + scene pcl_bridge ${PCL_LIBRARIES}) target_include_directories(quickviz_editor PRIVATE diff --git a/sample/editor/README.md b/sample/editor/README.md index cdc85a3..3ca49ce 100644 --- a/sample/editor/README.md +++ b/sample/editor/README.md @@ -40,5 +40,5 @@ cmake --build build -j ## Boundary rule This directory **may** include from the library's public headers -(`gldraw/`, `viewer/`, `pcl_bridge/`, `core/`). It must never be included +(`scene/`, `viewer/`, `pcl_bridge/`, `core/`). It must never be included *by* anything in `src/`. The `boundary-check` CI job enforces this. diff --git a/sample/editor/editor_state.cpp b/sample/editor/editor_state.cpp index 9618417..0c35cc5 100644 --- a/sample/editor/editor_state.cpp +++ b/sample/editor/editor_state.cpp @@ -8,9 +8,9 @@ #include -#include "gldraw/renderable/point_cloud.hpp" -#include "gldraw/selection_manager.hpp" -#include "gldraw/tools/point_selection_tool.hpp" +#include "scene/renderable/point_cloud.hpp" +#include "scene/selection_manager.hpp" +#include "scene/tools/point_selection_tool.hpp" namespace quickviz::editor { diff --git a/sample/editor/editor_viewport.hpp b/sample/editor/editor_viewport.hpp index 8e19e09..1508d0f 100644 --- a/sample/editor/editor_viewport.hpp +++ b/sample/editor/editor_viewport.hpp @@ -15,8 +15,8 @@ #include #include -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/tools/point_selection_tool.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/tools/point_selection_tool.hpp" namespace quickviz::editor { diff --git a/sample/editor/main.cpp b/sample/editor/main.cpp index 8d5cc50..4d203ff 100644 --- a/sample/editor/main.cpp +++ b/sample/editor/main.cpp @@ -22,8 +22,8 @@ #include "viewer/box.hpp" #include "viewer/viewer.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/point_cloud.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/point_cloud.hpp" #include "pcl_bridge/pcl_loader.hpp" #include "editor_state.hpp" diff --git a/sample/editor/panels/editor_tool_panel.cpp b/sample/editor/panels/editor_tool_panel.cpp index 9e66665..4e6d20a 100644 --- a/sample/editor/panels/editor_tool_panel.cpp +++ b/sample/editor/panels/editor_tool_panel.cpp @@ -10,7 +10,7 @@ #include -#include "gldraw/tools/point_selection_tool.hpp" +#include "scene/tools/point_selection_tool.hpp" #include "../commands/delete_points_command.hpp" #include "../editor_state.hpp" diff --git a/sample/pointcloud_viewer/CMakeLists.txt b/sample/pointcloud_viewer/CMakeLists.txt index 1c29278..0bb6730 100644 --- a/sample/pointcloud_viewer/CMakeLists.txt +++ b/sample/pointcloud_viewer/CMakeLists.txt @@ -4,5 +4,5 @@ add_executable(point_cloud_viewer point_cloud_tool_panel.cpp interactive_scene_manager.cpp) target_compile_definitions(point_cloud_viewer PRIVATE ${PCL_DEFINITIONS}) -target_link_libraries(point_cloud_viewer PRIVATE gldraw pcl_bridge ${PCL_LIBRARIES}) +target_link_libraries(point_cloud_viewer PRIVATE scene pcl_bridge ${PCL_LIBRARIES}) target_include_directories(point_cloud_viewer PRIVATE ${PCL_INCLUDE_DIRS} ${CURRENT_CMAKE_SOURCE_DIR}) diff --git a/sample/pointcloud_viewer/interactive_scene_manager.hpp b/sample/pointcloud_viewer/interactive_scene_manager.hpp index b2cfc09..41f2922 100644 --- a/sample/pointcloud_viewer/interactive_scene_manager.hpp +++ b/sample/pointcloud_viewer/interactive_scene_manager.hpp @@ -9,9 +9,9 @@ #ifndef QUICKVIZ_INTERACTIVE_SCENE_MANAGER_HPP #define QUICKVIZ_INTERACTIVE_SCENE_MANAGER_HPP -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/renderable/point_cloud.hpp" -#include "gldraw/tools/point_selection_tool.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/renderable/point_cloud.hpp" +#include "scene/tools/point_selection_tool.hpp" #include namespace quickviz { diff --git a/sample/pointcloud_viewer/main.cpp b/sample/pointcloud_viewer/main.cpp index 96f3633..462d3ab 100644 --- a/sample/pointcloud_viewer/main.cpp +++ b/sample/pointcloud_viewer/main.cpp @@ -23,9 +23,9 @@ #include "viewer/viewer.hpp" #include "viewer/panel.hpp" -#include "gldraw/scene_manager.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/point_cloud.hpp" +#include "scene/scene_manager.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/point_cloud.hpp" #include "pcl_bridge/pcl_loader.hpp" #include "point_cloud_info_panel.hpp" diff --git a/sample/pointcloud_viewer/point_cloud_tool_panel.hpp b/sample/pointcloud_viewer/point_cloud_tool_panel.hpp index 9feccf5..e774946 100644 --- a/sample/pointcloud_viewer/point_cloud_tool_panel.hpp +++ b/sample/pointcloud_viewer/point_cloud_tool_panel.hpp @@ -10,8 +10,8 @@ #define QUICKVIZ_POINT_CLOUD_TOOL_PANEL_HPP #include "viewer/panel.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/tools/point_selection_tool.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/tools/point_selection_tool.hpp" namespace quickviz { class InteractiveSceneManager; diff --git a/sample/quickviz_demo_app/CMakeLists.txt b/sample/quickviz_demo_app/CMakeLists.txt index 83ac024..d16b300 100644 --- a/sample/quickviz_demo_app/CMakeLists.txt +++ b/sample/quickviz_demo_app/CMakeLists.txt @@ -8,7 +8,7 @@ if (BUILD_QUICKVIZ_APP) panels/scene_panel.cpp panels/config_panel.cpp panels/console_panel.cpp) - target_link_libraries(quickviz_demo_app PRIVATE viewer gldraw pcl_bridge) + target_link_libraries(quickviz_demo_app PRIVATE viewer scene pcl_bridge) target_include_directories(quickviz_demo_app PRIVATE .) install(TARGETS quickviz_demo_app diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9707024..491b6c5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,7 +2,7 @@ add_subdirectory(core) add_subdirectory(viewer) add_subdirectory(widget) -add_subdirectory(gldraw) +add_subdirectory(scene) add_subdirectory(pcl_bridge) find_package(OpenCV QUIET) diff --git a/src/gldraw/test/renderable/CMakeLists.txt b/src/gldraw/test/renderable/CMakeLists.txt deleted file mode 100644 index b4d2414..0000000 --- a/src/gldraw/test/renderable/CMakeLists.txt +++ /dev/null @@ -1,52 +0,0 @@ -add_executable(test_arrow test_arrow.cpp) -target_link_libraries(test_arrow PRIVATE gldraw) - -add_executable(test_billboard test_billboard.cpp) -target_link_libraries(test_billboard PRIVATE gldraw) - -add_executable(test_bounding_box test_bounding_box.cpp) -target_link_libraries(test_bounding_box PRIVATE gldraw) - -add_executable(test_canvas test_canvas.cpp) -target_link_libraries(test_canvas PRIVATE gldraw) - -add_executable(test_coordinate_frame test_coordinate_frame.cpp) -target_link_libraries(test_coordinate_frame PRIVATE gldraw) - -add_executable(test_cylinder test_cylinder.cpp) -target_link_libraries(test_cylinder PRIVATE gldraw) - -add_executable(test_frustum test_frustum.cpp) -target_link_libraries(test_frustum PRIVATE gldraw) - -add_executable(test_grid test_grid.cpp) -target_link_libraries(test_grid PRIVATE gldraw) - -add_executable(test_line_strip test_line_strip.cpp) -target_link_libraries(test_line_strip PRIVATE gldraw) - -add_executable(test_mesh test_mesh.cpp) -target_link_libraries(test_mesh PRIVATE gldraw) - -add_executable(test_point_cloud test_point_cloud.cpp) -target_link_libraries(test_point_cloud PRIVATE gldraw) - -add_executable(test_path test_path.cpp) -target_link_libraries(test_path PRIVATE gldraw) - -add_executable(test_plane test_plane.cpp) -target_link_libraries(test_plane PRIVATE gldraw) - -add_executable(test_pose test_pose.cpp) -target_link_libraries(test_pose PRIVATE gldraw) - -add_executable(test_sphere test_sphere.cpp) -target_link_libraries(test_sphere PRIVATE gldraw) - - -add_executable(test_texture test_texture.cpp) -target_link_libraries(test_texture PRIVATE gldraw) - -add_executable(test_triangle test_triangle.cpp) -target_link_libraries(test_triangle PRIVATE gldraw) - diff --git a/src/pcl_bridge/CMakeLists.txt b/src/pcl_bridge/CMakeLists.txt index c799eb3..782b685 100644 --- a/src/pcl_bridge/CMakeLists.txt +++ b/src/pcl_bridge/CMakeLists.txt @@ -19,8 +19,8 @@ else() message(STATUS "PCL not found - PCL bridge utilities will not be available") endif() -# Link with gldraw for rendering capabilities -target_link_libraries(pcl_bridge PUBLIC gldraw) +# Link with scene for rendering capabilities +target_link_libraries(pcl_bridge PUBLIC scene) # Include directories target_include_directories(pcl_bridge PUBLIC diff --git a/src/pcl_bridge/include/pcl_bridge/pcl_conversions.hpp b/src/pcl_bridge/include/pcl_bridge/pcl_conversions.hpp index 0692079..ac4892a 100644 --- a/src/pcl_bridge/include/pcl_bridge/pcl_conversions.hpp +++ b/src/pcl_bridge/include/pcl_bridge/pcl_conversions.hpp @@ -13,7 +13,7 @@ #include #include #include -#include "gldraw/renderable/point_cloud.hpp" +#include "scene/renderable/point_cloud.hpp" // Forward declarations for PCL types to avoid requiring PCL headers in this file namespace pcl { diff --git a/src/pcl_bridge/src/pcl_conversions.cpp b/src/pcl_bridge/src/pcl_conversions.cpp index bdc5897..2b41b91 100644 --- a/src/pcl_bridge/src/pcl_conversions.cpp +++ b/src/pcl_bridge/src/pcl_conversions.cpp @@ -7,7 +7,7 @@ */ #include "pcl_bridge/pcl_conversions.hpp" -#include "gldraw/renderable/point_cloud.hpp" +#include "scene/renderable/point_cloud.hpp" // Include PCL headers only in implementation #include diff --git a/src/pcl_bridge/src/pcl_loader.cpp b/src/pcl_bridge/src/pcl_loader.cpp index d4aa9b7..7f3e091 100644 --- a/src/pcl_bridge/src/pcl_loader.cpp +++ b/src/pcl_bridge/src/pcl_loader.cpp @@ -8,7 +8,7 @@ #include "pcl_bridge/pcl_loader.hpp" #include "pcl_bridge/pcl_conversions.hpp" -#include "gldraw/renderable/point_cloud.hpp" +#include "scene/renderable/point_cloud.hpp" #include #include diff --git a/src/pcl_bridge/src/pcl_visualization.cpp b/src/pcl_bridge/src/pcl_visualization.cpp index 54c78b2..56e8eb2 100644 --- a/src/pcl_bridge/src/pcl_visualization.cpp +++ b/src/pcl_bridge/src/pcl_visualization.cpp @@ -7,7 +7,7 @@ */ #include "pcl_bridge/pcl_visualization.hpp" -#include "gldraw/renderable/point_cloud.hpp" +#include "scene/renderable/point_cloud.hpp" #include #include diff --git a/src/pcl_bridge/test/CMakeLists.txt b/src/pcl_bridge/test/CMakeLists.txt index c3d99ed..593c9a2 100644 --- a/src/pcl_bridge/test/CMakeLists.txt +++ b/src/pcl_bridge/test/CMakeLists.txt @@ -11,23 +11,23 @@ if(PCL_FOUND) add_executable(test_pcd test_pcd.cpp) target_include_directories(test_pcd PRIVATE ${PCL_INCLUDE_DIRS}) - target_link_libraries(test_pcd PRIVATE gldraw pcl_bridge ${PCL_LIBRARIES}) + target_link_libraries(test_pcd PRIVATE scene pcl_bridge ${PCL_LIBRARIES}) target_compile_definitions(test_pcd PRIVATE ${PCL_DEFINITIONS}) add_executable(test_pcl_bridge test_pcl_bridge.cpp) target_include_directories(test_pcl_bridge PRIVATE ${PCL_INCLUDE_DIRS}) - target_link_libraries(test_pcl_bridge PRIVATE gldraw pcl_bridge ${PCL_LIBRARIES}) + target_link_libraries(test_pcl_bridge PRIVATE scene pcl_bridge ${PCL_LIBRARIES}) target_compile_definitions(test_pcl_bridge PRIVATE ${PCL_DEFINITIONS}) # add_executable(test_pcl_loader unit_test/test_pcl_loader.cpp) # target_include_directories(test_pcl_loader PRIVATE ${PCL_INCLUDE_DIRS}) -# target_link_libraries(test_pcl_loader PRIVATE gldraw ${PCL_LIBRARIES} gtest_main) +# target_link_libraries(test_pcl_loader PRIVATE scene ${PCL_LIBRARIES} gtest_main) # target_compile_definitions(test_pcl_loader PRIVATE ${PCL_DEFINITIONS}) add_executable(test_pcl_loader_render test_pcl_loader_render.cpp) target_include_directories(test_pcl_loader_render PRIVATE ${PCL_INCLUDE_DIRS}) - target_link_libraries(test_pcl_loader_render PRIVATE gldraw pcl_bridge ${PCL_LIBRARIES}) + target_link_libraries(test_pcl_loader_render PRIVATE scene pcl_bridge ${PCL_LIBRARIES}) target_compile_definitions(test_pcl_loader_render PRIVATE ${PCL_DEFINITIONS}) diff --git a/src/pcl_bridge/test/test_pcd.cpp b/src/pcl_bridge/test/test_pcd.cpp index 63ab958..34844a6 100644 --- a/src/pcl_bridge/test/test_pcd.cpp +++ b/src/pcl_bridge/test/test_pcd.cpp @@ -19,9 +19,9 @@ #include "viewer/box.hpp" #include "viewer/viewer.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/point_cloud.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/point_cloud.hpp" using namespace quickviz; diff --git a/src/pcl_bridge/test/test_pcl_bridge.cpp b/src/pcl_bridge/test/test_pcl_bridge.cpp index 27dfc3b..8bd8771 100644 --- a/src/pcl_bridge/test/test_pcl_bridge.cpp +++ b/src/pcl_bridge/test/test_pcl_bridge.cpp @@ -13,9 +13,9 @@ #include "viewer/viewer.hpp" #include "viewer/box.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/point_cloud.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/point_cloud.hpp" #ifdef QUICKVIZ_WITH_PCL #include diff --git a/src/pcl_bridge/test/test_pcl_loader_render.cpp b/src/pcl_bridge/test/test_pcl_loader_render.cpp index fc63a7e..9cd567f 100644 --- a/src/pcl_bridge/test/test_pcl_loader_render.cpp +++ b/src/pcl_bridge/test/test_pcl_loader_render.cpp @@ -22,9 +22,9 @@ #include "viewer/viewer.hpp" #include "viewer/panel.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/point_cloud.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/point_cloud.hpp" #include "pcl_bridge/pcl_loader.hpp" using namespace quickviz; diff --git a/src/pcl_bridge/test/unit_test/CMakeLists.txt b/src/pcl_bridge/test/unit_test/CMakeLists.txt index 5dafbce..9b7914c 100644 --- a/src/pcl_bridge/test/unit_test/CMakeLists.txt +++ b/src/pcl_bridge/test/unit_test/CMakeLists.txt @@ -1,9 +1,9 @@ # add unit tests -add_executable(gldraw_unit_tests +add_executable(scene_unit_tests test_pcl_loader.cpp) -target_link_libraries(gldraw_unit_tests PRIVATE gtest_main gmock viewer gldraw pcl_bridge ${PCL_LIBRARIES}) +target_link_libraries(scene_unit_tests PRIVATE gtest_main gmock viewer scene pcl_bridge ${PCL_LIBRARIES}) # get_target_property(PRIVATE_HEADERS viewer INCLUDE_DIRECTORIES) -target_include_directories(gldraw_unit_tests PRIVATE ${PRIVATE_HEADERS}) +target_include_directories(scene_unit_tests PRIVATE ${PRIVATE_HEADERS}) -gtest_discover_tests(gldraw_unit_tests) -add_test(NAME gtest_all COMMAND gldraw_unit_tests) +gtest_discover_tests(scene_unit_tests) +add_test(NAME gtest_all COMMAND scene_unit_tests) diff --git a/src/pcl_bridge/test/unit_test/test_pcl_loader.cpp b/src/pcl_bridge/test/unit_test/test_pcl_loader.cpp index f683a1c..92bc113 100644 --- a/src/pcl_bridge/test/unit_test/test_pcl_loader.cpp +++ b/src/pcl_bridge/test/unit_test/test_pcl_loader.cpp @@ -12,7 +12,7 @@ #include #include "pcl_bridge/pcl_loader.hpp" -#include "gldraw/renderable/point_cloud.hpp" +#include "scene/renderable/point_cloud.hpp" #ifdef QUICKVIZ_WITH_PCL #include diff --git a/src/gldraw/CMakeLists.txt b/src/scene/CMakeLists.txt similarity index 89% rename from src/gldraw/CMakeLists.txt rename to src/scene/CMakeLists.txt index 3ead424..5aefe09 100644 --- a/src/gldraw/CMakeLists.txt +++ b/src/scene/CMakeLists.txt @@ -3,7 +3,7 @@ find_package(Threads REQUIRED) find_package(OpenGL REQUIRED) # add library -add_library(gldraw +add_library(scene ## gui integration src/gl_scene_panel.cpp src/gl_viewer.cpp @@ -52,14 +52,14 @@ add_library(gldraw src/tools/interaction_tool.cpp src/tools/point_selection_tool.cpp ) -target_link_libraries(gldraw PUBLIC core imcore viewer stb +target_link_libraries(scene PUBLIC core imcore viewer stb Threads::Threads OpenGL::GL) if (VIEWER_WITH_GLAD) - target_link_libraries(gldraw PUBLIC glad) - target_compile_definitions(gldraw PUBLIC VIEWER_WITH_GLAD) + target_link_libraries(scene PUBLIC glad) + target_compile_definitions(scene PUBLIC VIEWER_WITH_GLAD) endif () -target_include_directories(gldraw PUBLIC +target_include_directories(scene PUBLIC $ $ PRIVATE src) @@ -68,7 +68,7 @@ if (BUILD_TESTING) add_subdirectory(test) endif () -install(TARGETS gldraw +install(TARGETS scene EXPORT quickvizTargets LIBRARY DESTINATION lib ARCHIVE DESTINATION lib diff --git a/src/gldraw/include/gldraw/camera.hpp b/src/scene/include/scene/camera.hpp similarity index 100% rename from src/gldraw/include/gldraw/camera.hpp rename to src/scene/include/scene/camera.hpp diff --git a/src/gldraw/include/gldraw/camera_control_config.hpp b/src/scene/include/scene/camera_control_config.hpp similarity index 98% rename from src/gldraw/include/gldraw/camera_control_config.hpp rename to src/scene/include/scene/camera_control_config.hpp index 1b1be10..735074d 100644 --- a/src/gldraw/include/gldraw/camera_control_config.hpp +++ b/src/scene/include/scene/camera_control_config.hpp @@ -6,8 +6,8 @@ * @copyright Copyright (c) 2025 Ruixiang Du (rdu) */ -#ifndef GLDRAW_CAMERA_CONTROL_CONFIG_HPP -#define GLDRAW_CAMERA_CONTROL_CONFIG_HPP +#ifndef SCENE_CAMERA_CONTROL_CONFIG_HPP +#define SCENE_CAMERA_CONTROL_CONFIG_HPP #include "viewer/input/input_types.hpp" #include "core/event/input_event.hpp" @@ -210,4 +210,4 @@ struct CameraControlConfig { } // namespace quickviz -#endif // GLDRAW_CAMERA_CONTROL_CONFIG_HPP \ No newline at end of file +#endif // SCENE_CAMERA_CONTROL_CONFIG_HPP \ No newline at end of file diff --git a/src/gldraw/include/gldraw/camera_controller.hpp b/src/scene/include/scene/camera_controller.hpp similarity index 99% rename from src/gldraw/include/gldraw/camera_controller.hpp rename to src/scene/include/scene/camera_controller.hpp index 52f5d20..9dac979 100644 --- a/src/gldraw/include/gldraw/camera_controller.hpp +++ b/src/scene/include/scene/camera_controller.hpp @@ -9,7 +9,7 @@ #ifndef QUICKVIZ_CAMERA_CONTROLLER_HPP #define QUICKVIZ_CAMERA_CONTROLLER_HPP -#include "gldraw/camera.hpp" +#include "scene/camera.hpp" #include #include #include diff --git a/src/gldraw/include/gldraw/coordinate_transformer.hpp b/src/scene/include/scene/coordinate_transformer.hpp similarity index 100% rename from src/gldraw/include/gldraw/coordinate_transformer.hpp rename to src/scene/include/scene/coordinate_transformer.hpp diff --git a/src/gldraw/include/gldraw/feedback/object_feedback_handler.hpp b/src/scene/include/scene/feedback/object_feedback_handler.hpp similarity index 99% rename from src/gldraw/include/gldraw/feedback/object_feedback_handler.hpp rename to src/scene/include/scene/feedback/object_feedback_handler.hpp index 877d717..702dac6 100644 --- a/src/gldraw/include/gldraw/feedback/object_feedback_handler.hpp +++ b/src/scene/include/scene/feedback/object_feedback_handler.hpp @@ -21,7 +21,7 @@ #include #include -#include "gldraw/feedback/visual_feedback_system.hpp" +#include "scene/feedback/visual_feedback_system.hpp" // Forward declarations namespace quickviz { diff --git a/src/gldraw/include/gldraw/feedback/point_cloud_feedback_handler.hpp b/src/scene/include/scene/feedback/point_cloud_feedback_handler.hpp similarity index 97% rename from src/gldraw/include/gldraw/feedback/point_cloud_feedback_handler.hpp rename to src/scene/include/scene/feedback/point_cloud_feedback_handler.hpp index 0bdfd7d..24f0abc 100644 --- a/src/gldraw/include/gldraw/feedback/point_cloud_feedback_handler.hpp +++ b/src/scene/include/scene/feedback/point_cloud_feedback_handler.hpp @@ -20,8 +20,8 @@ #include -#include "gldraw/feedback/visual_feedback_system.hpp" -#include "gldraw/renderable/details/point_layer_manager.hpp" +#include "scene/feedback/visual_feedback_system.hpp" +#include "scene/renderable/details/point_layer_manager.hpp" // Forward declarations namespace quickviz { diff --git a/src/gldraw/include/gldraw/feedback/visual_feedback_system.hpp b/src/scene/include/scene/feedback/visual_feedback_system.hpp similarity index 100% rename from src/gldraw/include/gldraw/feedback/visual_feedback_system.hpp rename to src/scene/include/scene/feedback/visual_feedback_system.hpp diff --git a/src/gldraw/include/gldraw/font_renderer.hpp b/src/scene/include/scene/font_renderer.hpp similarity index 100% rename from src/gldraw/include/gldraw/font_renderer.hpp rename to src/scene/include/scene/font_renderer.hpp diff --git a/src/gldraw/include/gldraw/frame_buffer.hpp b/src/scene/include/scene/frame_buffer.hpp similarity index 100% rename from src/gldraw/include/gldraw/frame_buffer.hpp rename to src/scene/include/scene/frame_buffer.hpp diff --git a/src/gldraw/include/gldraw/gl_scene_panel.hpp b/src/scene/include/scene/gl_scene_panel.hpp similarity index 96% rename from src/gldraw/include/gldraw/gl_scene_panel.hpp rename to src/scene/include/scene/gl_scene_panel.hpp index 766b724..146083a 100644 --- a/src/gldraw/include/gldraw/gl_scene_panel.hpp +++ b/src/scene/include/scene/gl_scene_panel.hpp @@ -17,11 +17,11 @@ #include #include "viewer/panel.hpp" -#include "gldraw/scene_manager.hpp" -#include "gldraw/interface/opengl_object.hpp" -#include "gldraw/camera.hpp" -#include "gldraw/camera_controller.hpp" -#include "gldraw/selection_manager.hpp" +#include "scene/scene_manager.hpp" +#include "scene/interface/opengl_object.hpp" +#include "scene/camera.hpp" +#include "scene/camera_controller.hpp" +#include "scene/selection_manager.hpp" #include "scene_input_handler.hpp" // Forward declarations diff --git a/src/gldraw/include/gldraw/gl_viewer.hpp b/src/scene/include/scene/gl_viewer.hpp similarity index 97% rename from src/gldraw/include/gldraw/gl_viewer.hpp rename to src/scene/include/scene/gl_viewer.hpp index 9d1abb8..8815637 100644 --- a/src/gldraw/include/gldraw/gl_viewer.hpp +++ b/src/scene/include/scene/gl_viewer.hpp @@ -22,9 +22,9 @@ #include "viewer/viewer.hpp" #include "viewer/box.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/coordinate_frame.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/coordinate_frame.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/interface/opengl_object.hpp b/src/scene/include/scene/interface/opengl_object.hpp similarity index 100% rename from src/gldraw/include/gldraw/interface/opengl_object.hpp rename to src/scene/include/scene/interface/opengl_object.hpp diff --git a/src/gldraw/include/gldraw/renderable/arrow.hpp b/src/scene/include/scene/renderable/arrow.hpp similarity index 98% rename from src/gldraw/include/gldraw/renderable/arrow.hpp rename to src/scene/include/scene/renderable/arrow.hpp index 7301a6e..975a7ef 100644 --- a/src/gldraw/include/gldraw/renderable/arrow.hpp +++ b/src/scene/include/scene/renderable/arrow.hpp @@ -13,7 +13,7 @@ #include #include -#include "gldraw/interface/opengl_object.hpp" +#include "scene/interface/opengl_object.hpp" #include "../shader_program.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/renderable/billboard.hpp b/src/scene/include/scene/renderable/billboard.hpp similarity index 98% rename from src/gldraw/include/gldraw/renderable/billboard.hpp rename to src/scene/include/scene/renderable/billboard.hpp index 7522f14..99027fb 100644 --- a/src/gldraw/include/gldraw/renderable/billboard.hpp +++ b/src/scene/include/scene/renderable/billboard.hpp @@ -15,8 +15,8 @@ #include #include -#include "gldraw/interface/opengl_object.hpp" -#include "gldraw/font_renderer.hpp" +#include "scene/interface/opengl_object.hpp" +#include "scene/font_renderer.hpp" #include "../shader_program.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/renderable/bounding_box.hpp b/src/scene/include/scene/renderable/bounding_box.hpp similarity index 98% rename from src/gldraw/include/gldraw/renderable/bounding_box.hpp rename to src/scene/include/scene/renderable/bounding_box.hpp index f25ab20..a7d5f30 100644 --- a/src/gldraw/include/gldraw/renderable/bounding_box.hpp +++ b/src/scene/include/scene/renderable/bounding_box.hpp @@ -13,7 +13,7 @@ #include #include -#include "gldraw/renderable/geometric_primitive.hpp" +#include "scene/renderable/geometric_primitive.hpp" #include "../shader_program.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/renderable/canvas.hpp b/src/scene/include/scene/renderable/canvas.hpp similarity index 98% rename from src/gldraw/include/gldraw/renderable/canvas.hpp rename to src/scene/include/scene/renderable/canvas.hpp index fa17e56..1415f73 100644 --- a/src/gldraw/include/gldraw/renderable/canvas.hpp +++ b/src/scene/include/scene/renderable/canvas.hpp @@ -22,11 +22,11 @@ #include -#include "gldraw/interface/opengl_object.hpp" +#include "scene/interface/opengl_object.hpp" #include "../shader_program.hpp" -#include "gldraw/renderable/types.hpp" -#include "gldraw/renderable/details/canvas_batching.hpp" -#include "gldraw/renderable/details/canvas_performance.hpp" +#include "scene/renderable/types.hpp" +#include "scene/renderable/details/canvas_batching.hpp" +#include "scene/renderable/details/canvas_performance.hpp" // Forward declarations for internal components namespace quickviz { diff --git a/src/gldraw/include/gldraw/renderable/coordinate_frame.hpp b/src/scene/include/scene/renderable/coordinate_frame.hpp similarity index 97% rename from src/gldraw/include/gldraw/renderable/coordinate_frame.hpp rename to src/scene/include/scene/renderable/coordinate_frame.hpp index fee2070..e64ea54 100644 --- a/src/gldraw/include/gldraw/renderable/coordinate_frame.hpp +++ b/src/scene/include/scene/renderable/coordinate_frame.hpp @@ -14,7 +14,7 @@ #include #include -#include "gldraw/interface/opengl_object.hpp" +#include "scene/interface/opengl_object.hpp" #include "../shader_program.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/renderable/cylinder.hpp b/src/scene/include/scene/renderable/cylinder.hpp similarity index 99% rename from src/gldraw/include/gldraw/renderable/cylinder.hpp rename to src/scene/include/scene/renderable/cylinder.hpp index d578f0b..5fbab9e 100644 --- a/src/gldraw/include/gldraw/renderable/cylinder.hpp +++ b/src/scene/include/scene/renderable/cylinder.hpp @@ -13,7 +13,7 @@ #include #include -#include "gldraw/renderable/geometric_primitive.hpp" +#include "scene/renderable/geometric_primitive.hpp" #include "../shader_program.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/renderable/details/canvas_batching.hpp b/src/scene/include/scene/renderable/details/canvas_batching.hpp similarity index 98% rename from src/gldraw/include/gldraw/renderable/details/canvas_batching.hpp rename to src/scene/include/scene/renderable/details/canvas_batching.hpp index a2f4d77..8aee1fd 100644 --- a/src/gldraw/include/gldraw/renderable/details/canvas_batching.hpp +++ b/src/scene/include/scene/renderable/details/canvas_batching.hpp @@ -13,7 +13,7 @@ #include #include #include -#include "gldraw/renderable/types.hpp" +#include "scene/renderable/types.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/renderable/details/canvas_performance.hpp b/src/scene/include/scene/renderable/details/canvas_performance.hpp similarity index 100% rename from src/gldraw/include/gldraw/renderable/details/canvas_performance.hpp rename to src/scene/include/scene/renderable/details/canvas_performance.hpp diff --git a/src/gldraw/include/gldraw/renderable/details/point_layer_manager.hpp b/src/scene/include/scene/renderable/details/point_layer_manager.hpp similarity index 100% rename from src/gldraw/include/gldraw/renderable/details/point_layer_manager.hpp rename to src/scene/include/scene/renderable/details/point_layer_manager.hpp diff --git a/src/gldraw/include/gldraw/renderable/frustum.hpp b/src/scene/include/scene/renderable/frustum.hpp similarity index 99% rename from src/gldraw/include/gldraw/renderable/frustum.hpp rename to src/scene/include/scene/renderable/frustum.hpp index 9ff4e25..b8009bd 100644 --- a/src/gldraw/include/gldraw/renderable/frustum.hpp +++ b/src/scene/include/scene/renderable/frustum.hpp @@ -13,7 +13,7 @@ #include #include -#include "gldraw/interface/opengl_object.hpp" +#include "scene/interface/opengl_object.hpp" #include "../shader_program.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/renderable/geometric_primitive.hpp b/src/scene/include/scene/renderable/geometric_primitive.hpp similarity index 99% rename from src/gldraw/include/gldraw/renderable/geometric_primitive.hpp rename to src/scene/include/scene/renderable/geometric_primitive.hpp index f90b1f2..29c9efc 100644 --- a/src/gldraw/include/gldraw/renderable/geometric_primitive.hpp +++ b/src/scene/include/scene/renderable/geometric_primitive.hpp @@ -18,7 +18,7 @@ #include #include -#include "gldraw/interface/opengl_object.hpp" +#include "scene/interface/opengl_object.hpp" #include "../shader_program.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/renderable/grid.hpp b/src/scene/include/scene/renderable/grid.hpp similarity index 96% rename from src/gldraw/include/gldraw/renderable/grid.hpp rename to src/scene/include/scene/renderable/grid.hpp index aeeca35..0f6ee17 100644 --- a/src/gldraw/include/gldraw/renderable/grid.hpp +++ b/src/scene/include/scene/renderable/grid.hpp @@ -14,7 +14,7 @@ #include -#include "gldraw/interface/opengl_object.hpp" +#include "scene/interface/opengl_object.hpp" #include "../shader_program.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/renderable/line_strip.hpp b/src/scene/include/scene/renderable/line_strip.hpp similarity index 97% rename from src/gldraw/include/gldraw/renderable/line_strip.hpp rename to src/scene/include/scene/renderable/line_strip.hpp index e8f4462..7fc9708 100644 --- a/src/gldraw/include/gldraw/renderable/line_strip.hpp +++ b/src/scene/include/scene/renderable/line_strip.hpp @@ -13,9 +13,9 @@ #include #include -#include "gldraw/interface/opengl_object.hpp" +#include "scene/interface/opengl_object.hpp" #include "../shader_program.hpp" -#include "gldraw/renderable/types.hpp" +#include "scene/renderable/types.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/renderable/mesh.hpp b/src/scene/include/scene/renderable/mesh.hpp similarity index 98% rename from src/gldraw/include/gldraw/renderable/mesh.hpp rename to src/scene/include/scene/renderable/mesh.hpp index 6d1420c..d58ecf0 100644 --- a/src/gldraw/include/gldraw/renderable/mesh.hpp +++ b/src/scene/include/scene/renderable/mesh.hpp @@ -13,7 +13,7 @@ #include #include -#include "gldraw/interface/opengl_object.hpp" +#include "scene/interface/opengl_object.hpp" #include "../shader_program.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/renderable/path.hpp b/src/scene/include/scene/renderable/path.hpp similarity index 99% rename from src/gldraw/include/gldraw/renderable/path.hpp rename to src/scene/include/scene/renderable/path.hpp index efad7f0..88e1d88 100644 --- a/src/gldraw/include/gldraw/renderable/path.hpp +++ b/src/scene/include/scene/renderable/path.hpp @@ -14,7 +14,7 @@ #include #include -#include "gldraw/interface/opengl_object.hpp" +#include "scene/interface/opengl_object.hpp" #include "../shader_program.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/renderable/plane.hpp b/src/scene/include/scene/renderable/plane.hpp similarity index 98% rename from src/gldraw/include/gldraw/renderable/plane.hpp rename to src/scene/include/scene/renderable/plane.hpp index b5ee57a..37dd5b5 100644 --- a/src/gldraw/include/gldraw/renderable/plane.hpp +++ b/src/scene/include/scene/renderable/plane.hpp @@ -13,7 +13,7 @@ #include #include -#include "gldraw/interface/opengl_object.hpp" +#include "scene/interface/opengl_object.hpp" #include "../shader_program.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/renderable/point_cloud.hpp b/src/scene/include/scene/renderable/point_cloud.hpp similarity index 98% rename from src/gldraw/include/gldraw/renderable/point_cloud.hpp rename to src/scene/include/scene/renderable/point_cloud.hpp index 46d6ed1..32e62bd 100644 --- a/src/gldraw/include/gldraw/renderable/point_cloud.hpp +++ b/src/scene/include/scene/renderable/point_cloud.hpp @@ -16,9 +16,9 @@ #include -#include "gldraw/interface/opengl_object.hpp" +#include "scene/interface/opengl_object.hpp" #include "../shader_program.hpp" -#include "gldraw/renderable/types.hpp" +#include "scene/renderable/types.hpp" #include "details/point_layer_manager.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/renderable/pose.hpp b/src/scene/include/scene/renderable/pose.hpp similarity index 99% rename from src/gldraw/include/gldraw/renderable/pose.hpp rename to src/scene/include/scene/renderable/pose.hpp index 0fa2d24..947ffb1 100644 --- a/src/gldraw/include/gldraw/renderable/pose.hpp +++ b/src/scene/include/scene/renderable/pose.hpp @@ -15,7 +15,7 @@ #include #include -#include "gldraw/interface/opengl_object.hpp" +#include "scene/interface/opengl_object.hpp" #include "../shader_program.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/renderable/sphere.hpp b/src/scene/include/scene/renderable/sphere.hpp similarity index 99% rename from src/gldraw/include/gldraw/renderable/sphere.hpp rename to src/scene/include/scene/renderable/sphere.hpp index 6a45c55..512e04e 100644 --- a/src/gldraw/include/gldraw/renderable/sphere.hpp +++ b/src/scene/include/scene/renderable/sphere.hpp @@ -13,7 +13,7 @@ #include #include -#include "gldraw/renderable/geometric_primitive.hpp" +#include "scene/renderable/geometric_primitive.hpp" #include "../shader_program.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/renderable/texture.hpp b/src/scene/include/scene/renderable/texture.hpp similarity index 98% rename from src/gldraw/include/gldraw/renderable/texture.hpp rename to src/scene/include/scene/renderable/texture.hpp index b42ebd8..5264219 100644 --- a/src/gldraw/include/gldraw/renderable/texture.hpp +++ b/src/scene/include/scene/renderable/texture.hpp @@ -16,7 +16,7 @@ #include -#include "gldraw/interface/opengl_object.hpp" +#include "scene/interface/opengl_object.hpp" #include "../shader_program.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/renderable/triangle.hpp b/src/scene/include/scene/renderable/triangle.hpp similarity index 95% rename from src/gldraw/include/gldraw/renderable/triangle.hpp rename to src/scene/include/scene/renderable/triangle.hpp index 5784d9e..c124fe0 100644 --- a/src/gldraw/include/gldraw/renderable/triangle.hpp +++ b/src/scene/include/scene/renderable/triangle.hpp @@ -12,7 +12,7 @@ #include -#include "gldraw/interface/opengl_object.hpp" +#include "scene/interface/opengl_object.hpp" #include "../shader_program.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/renderable/types.hpp b/src/scene/include/scene/renderable/types.hpp similarity index 100% rename from src/gldraw/include/gldraw/renderable/types.hpp rename to src/scene/include/scene/renderable/types.hpp diff --git a/src/gldraw/include/gldraw/scene_input_handler.hpp b/src/scene/include/scene/scene_input_handler.hpp similarity index 91% rename from src/gldraw/include/gldraw/scene_input_handler.hpp rename to src/scene/include/scene/scene_input_handler.hpp index d9cf6f0..60f3e71 100644 --- a/src/gldraw/include/gldraw/scene_input_handler.hpp +++ b/src/scene/include/scene/scene_input_handler.hpp @@ -1,23 +1,23 @@ /* * @file scene_input_handler.hpp * @date 9/1/25 - * @brief Bridge between viewer input system and gldraw 3D interactions + * @brief Bridge between viewer input system and scene 3D interactions * * @copyright Copyright (c) 2025 Ruixiang Du (rdu) */ -#ifndef GLDRAW_SCENE_INPUT_HANDLER_HPP -#define GLDRAW_SCENE_INPUT_HANDLER_HPP +#ifndef SCENE_SCENE_INPUT_HANDLER_HPP +#define SCENE_SCENE_INPUT_HANDLER_HPP #include #include #include "viewer/input/input_dispatcher.hpp" #include "core/event/input_event.hpp" -#include "gldraw/camera.hpp" -#include "gldraw/camera_controller.hpp" -#include "gldraw/selection_manager.hpp" -#include "gldraw/camera_control_config.hpp" +#include "scene/camera.hpp" +#include "scene/camera_controller.hpp" +#include "scene/selection_manager.hpp" +#include "scene/camera_control_config.hpp" namespace quickviz { @@ -27,7 +27,7 @@ class SceneManager; /** * @brief Input handler bridge for 3D scene interactions * - * Bridges viewer's InputEventHandler system with gldraw's 3D-specific + * Bridges viewer's InputEventHandler system with scene's 3D-specific * functionality like camera control and object selection. */ class SceneInputHandler : public InputEventHandler { @@ -149,4 +149,4 @@ class SceneInputHandlerFactory { } // namespace quickviz -#endif // GLDRAW_SCENE_INPUT_HANDLER_HPP \ No newline at end of file +#endif // SCENE_SCENE_INPUT_HANDLER_HPP \ No newline at end of file diff --git a/src/gldraw/include/gldraw/scene_manager.hpp b/src/scene/include/scene/scene_manager.hpp similarity index 96% rename from src/gldraw/include/gldraw/scene_manager.hpp rename to src/scene/include/scene/scene_manager.hpp index 604d73f..5e4f37f 100644 --- a/src/gldraw/include/gldraw/scene_manager.hpp +++ b/src/scene/include/scene/scene_manager.hpp @@ -18,13 +18,13 @@ #include #include -#include "gldraw/interface/opengl_object.hpp" +#include "scene/interface/opengl_object.hpp" -#include "gldraw/frame_buffer.hpp" -#include "gldraw/camera.hpp" -#include "gldraw/camera_controller.hpp" -#include "gldraw/coordinate_transformer.hpp" -#include "gldraw/selection_manager.hpp" +#include "scene/frame_buffer.hpp" +#include "scene/camera.hpp" +#include "scene/camera_controller.hpp" +#include "scene/coordinate_transformer.hpp" +#include "scene/selection_manager.hpp" // Forward declarations namespace quickviz { diff --git a/src/gldraw/include/gldraw/selection_manager.hpp b/src/scene/include/scene/selection_manager.hpp similarity index 99% rename from src/gldraw/include/gldraw/selection_manager.hpp rename to src/scene/include/scene/selection_manager.hpp index fc1a446..73fb14e 100644 --- a/src/gldraw/include/gldraw/selection_manager.hpp +++ b/src/scene/include/scene/selection_manager.hpp @@ -19,7 +19,7 @@ #include #include -#include "gldraw/interface/opengl_object.hpp" +#include "scene/interface/opengl_object.hpp" namespace quickviz { diff --git a/src/gldraw/include/gldraw/shader.hpp b/src/scene/include/scene/shader.hpp similarity index 100% rename from src/gldraw/include/gldraw/shader.hpp rename to src/scene/include/scene/shader.hpp diff --git a/src/gldraw/include/gldraw/shader_program.hpp b/src/scene/include/scene/shader_program.hpp similarity index 100% rename from src/gldraw/include/gldraw/shader_program.hpp rename to src/scene/include/scene/shader_program.hpp diff --git a/src/gldraw/include/gldraw/tools/interaction_tool.hpp b/src/scene/include/scene/tools/interaction_tool.hpp similarity index 100% rename from src/gldraw/include/gldraw/tools/interaction_tool.hpp rename to src/scene/include/scene/tools/interaction_tool.hpp diff --git a/src/gldraw/include/gldraw/tools/point_selection_tool.hpp b/src/scene/include/scene/tools/point_selection_tool.hpp similarity index 99% rename from src/gldraw/include/gldraw/tools/point_selection_tool.hpp rename to src/scene/include/scene/tools/point_selection_tool.hpp index 43a1d03..8a692cd 100644 --- a/src/gldraw/include/gldraw/tools/point_selection_tool.hpp +++ b/src/scene/include/scene/tools/point_selection_tool.hpp @@ -14,8 +14,8 @@ #include #include -#include "gldraw/tools/interaction_tool.hpp" -#include "gldraw/selection_manager.hpp" +#include "scene/tools/interaction_tool.hpp" +#include "scene/selection_manager.hpp" namespace quickviz { diff --git a/src/gldraw/src/camera.cpp b/src/scene/src/camera.cpp similarity index 99% rename from src/gldraw/src/camera.cpp rename to src/scene/src/camera.cpp index 10b5049..a10d71f 100644 --- a/src/gldraw/src/camera.cpp +++ b/src/scene/src/camera.cpp @@ -6,7 +6,7 @@ * @copyright Copyright (c) 2024 Ruixiang Du (rdu) */ -#include "gldraw/camera.hpp" +#include "scene/camera.hpp" #include diff --git a/src/gldraw/src/camera_controller.cpp b/src/scene/src/camera_controller.cpp similarity index 99% rename from src/gldraw/src/camera_controller.cpp rename to src/scene/src/camera_controller.cpp index 837ef53..375485c 100644 --- a/src/gldraw/src/camera_controller.cpp +++ b/src/scene/src/camera_controller.cpp @@ -6,7 +6,7 @@ * @copyright Copyright (c) 2024 Ruixiang Du (rdu) */ -#include "gldraw/camera_controller.hpp" +#include "scene/camera_controller.hpp" namespace quickviz { diff --git a/src/gldraw/src/feedback/object_feedback_handler.cpp b/src/scene/src/feedback/object_feedback_handler.cpp similarity index 98% rename from src/gldraw/src/feedback/object_feedback_handler.cpp rename to src/scene/src/feedback/object_feedback_handler.cpp index 1c59f78..87dcd20 100644 --- a/src/gldraw/src/feedback/object_feedback_handler.cpp +++ b/src/scene/src/feedback/object_feedback_handler.cpp @@ -10,14 +10,14 @@ * @copyright Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/feedback/object_feedback_handler.hpp" +#include "scene/feedback/object_feedback_handler.hpp" #include #include #include -#include "gldraw/scene_manager.hpp" -#include "gldraw/renderable/sphere.hpp" +#include "scene/scene_manager.hpp" +#include "scene/renderable/sphere.hpp" namespace quickviz { diff --git a/src/gldraw/src/feedback/point_cloud_feedback_handler.cpp b/src/scene/src/feedback/point_cloud_feedback_handler.cpp similarity index 98% rename from src/gldraw/src/feedback/point_cloud_feedback_handler.cpp rename to src/scene/src/feedback/point_cloud_feedback_handler.cpp index d38ef30..e006305 100644 --- a/src/gldraw/src/feedback/point_cloud_feedback_handler.cpp +++ b/src/scene/src/feedback/point_cloud_feedback_handler.cpp @@ -10,13 +10,13 @@ * @copyright Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/feedback/point_cloud_feedback_handler.hpp" +#include "scene/feedback/point_cloud_feedback_handler.hpp" #include #include #include -#include "gldraw/renderable/point_cloud.hpp" +#include "scene/renderable/point_cloud.hpp" namespace quickviz { diff --git a/src/gldraw/src/feedback/visual_feedback_system.cpp b/src/scene/src/feedback/visual_feedback_system.cpp similarity index 97% rename from src/gldraw/src/feedback/visual_feedback_system.cpp rename to src/scene/src/feedback/visual_feedback_system.cpp index 17fcf03..1520952 100644 --- a/src/gldraw/src/feedback/visual_feedback_system.cpp +++ b/src/scene/src/feedback/visual_feedback_system.cpp @@ -6,14 +6,14 @@ * @copyright Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/feedback/visual_feedback_system.hpp" -#include "gldraw/feedback/point_cloud_feedback_handler.hpp" -#include "gldraw/feedback/object_feedback_handler.hpp" - -#include "gldraw/scene_manager.hpp" -#include "gldraw/selection_manager.hpp" -#include "gldraw/renderable/point_cloud.hpp" -#include "gldraw/interface/opengl_object.hpp" +#include "scene/feedback/visual_feedback_system.hpp" +#include "scene/feedback/point_cloud_feedback_handler.hpp" +#include "scene/feedback/object_feedback_handler.hpp" + +#include "scene/scene_manager.hpp" +#include "scene/selection_manager.hpp" +#include "scene/renderable/point_cloud.hpp" +#include "scene/interface/opengl_object.hpp" #include #include diff --git a/src/gldraw/src/font_renderer.cpp b/src/scene/src/font_renderer.cpp similarity index 99% rename from src/gldraw/src/font_renderer.cpp rename to src/scene/src/font_renderer.cpp index f566959..c73ad57 100644 --- a/src/gldraw/src/font_renderer.cpp +++ b/src/scene/src/font_renderer.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/font_renderer.hpp" +#include "scene/font_renderer.hpp" #include #include diff --git a/src/gldraw/src/frame_buffer.cpp b/src/scene/src/frame_buffer.cpp similarity index 99% rename from src/gldraw/src/frame_buffer.cpp rename to src/scene/src/frame_buffer.cpp index f8a9da6..9e9c41a 100644 --- a/src/gldraw/src/frame_buffer.cpp +++ b/src/scene/src/frame_buffer.cpp @@ -6,7 +6,7 @@ * @copyright Copyright (c) 2024 Ruixiang Du (rdu) */ -#include "gldraw/frame_buffer.hpp" +#include "scene/frame_buffer.hpp" #include #include diff --git a/src/gldraw/src/gl_scene_panel.cpp b/src/scene/src/gl_scene_panel.cpp similarity index 97% rename from src/gldraw/src/gl_scene_panel.cpp rename to src/scene/src/gl_scene_panel.cpp index b9492a8..49b5ea0 100644 --- a/src/gldraw/src/gl_scene_panel.cpp +++ b/src/scene/src/gl_scene_panel.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/gl_scene_panel.hpp" +#include "scene/gl_scene_panel.hpp" #include #include @@ -16,9 +16,9 @@ #include "viewer/fonts.hpp" #include "viewer/input/input_policy.hpp" -#include "gldraw/renderable/point_cloud.hpp" -#include "gldraw/selection_manager.hpp" -#include "gldraw/feedback/visual_feedback_system.hpp" +#include "scene/renderable/point_cloud.hpp" +#include "scene/selection_manager.hpp" +#include "scene/feedback/visual_feedback_system.hpp" namespace quickviz { diff --git a/src/gldraw/src/gl_viewer.cpp b/src/scene/src/gl_viewer.cpp similarity index 99% rename from src/gldraw/src/gl_viewer.cpp rename to src/scene/src/gl_viewer.cpp index 5395f61..d0dd29a 100644 --- a/src/gldraw/src/gl_viewer.cpp +++ b/src/scene/src/gl_viewer.cpp @@ -8,7 +8,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/gl_viewer.hpp" +#include "scene/gl_viewer.hpp" #include #include diff --git a/src/gldraw/src/renderable/arrow.cpp b/src/scene/src/renderable/arrow.cpp similarity index 99% rename from src/gldraw/src/renderable/arrow.cpp rename to src/scene/src/renderable/arrow.cpp index f3caa5a..5876c44 100644 --- a/src/gldraw/src/renderable/arrow.cpp +++ b/src/scene/src/renderable/arrow.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/renderable/arrow.hpp" +#include "scene/renderable/arrow.hpp" #include #include @@ -17,7 +17,7 @@ #include #include -#include "gldraw/shader.hpp" +#include "scene/shader.hpp" namespace quickviz { diff --git a/src/gldraw/src/renderable/billboard.cpp b/src/scene/src/renderable/billboard.cpp similarity index 99% rename from src/gldraw/src/renderable/billboard.cpp rename to src/scene/src/renderable/billboard.cpp index 98c3e1f..747e5c2 100644 --- a/src/gldraw/src/renderable/billboard.cpp +++ b/src/scene/src/renderable/billboard.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/renderable/billboard.hpp" +#include "scene/renderable/billboard.hpp" #include #include @@ -18,7 +18,7 @@ #include #include -#include "gldraw/shader.hpp" +#include "scene/shader.hpp" namespace quickviz { diff --git a/src/gldraw/src/renderable/bounding_box.cpp b/src/scene/src/renderable/bounding_box.cpp similarity index 99% rename from src/gldraw/src/renderable/bounding_box.cpp rename to src/scene/src/renderable/bounding_box.cpp index e5cfadf..63a3348 100644 --- a/src/gldraw/src/renderable/bounding_box.cpp +++ b/src/scene/src/renderable/bounding_box.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/renderable/bounding_box.hpp" +#include "scene/renderable/bounding_box.hpp" #include @@ -15,7 +15,7 @@ #include #include -#include "gldraw/shader.hpp" +#include "scene/shader.hpp" namespace quickviz { diff --git a/src/gldraw/src/renderable/canvas.cpp b/src/scene/src/renderable/canvas.cpp similarity index 99% rename from src/gldraw/src/renderable/canvas.cpp rename to src/scene/src/renderable/canvas.cpp index 61b50af..0a822b0 100644 --- a/src/gldraw/src/renderable/canvas.cpp +++ b/src/scene/src/renderable/canvas.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/renderable/canvas.hpp" +#include "scene/renderable/canvas.hpp" #include #include @@ -22,8 +22,8 @@ #include #include "renderable/details/canvas_data.hpp" -#include "gldraw/renderable/details/canvas_batching.hpp" -#include "gldraw/renderable/details/canvas_performance.hpp" +#include "scene/renderable/details/canvas_batching.hpp" +#include "scene/renderable/details/canvas_performance.hpp" #include "renderable/details/render_strategy.hpp" #include "renderable/details/batched_render_strategy.hpp" #include "renderable/details/individual_render_strategy.hpp" diff --git a/src/gldraw/src/renderable/coordinate_frame.cpp b/src/scene/src/renderable/coordinate_frame.cpp similarity index 99% rename from src/gldraw/src/renderable/coordinate_frame.cpp rename to src/scene/src/renderable/coordinate_frame.cpp index 52b8113..e8f6899 100644 --- a/src/gldraw/src/renderable/coordinate_frame.cpp +++ b/src/scene/src/renderable/coordinate_frame.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/renderable/coordinate_frame.hpp" +#include "scene/renderable/coordinate_frame.hpp" #include #include diff --git a/src/gldraw/src/renderable/cylinder.cpp b/src/scene/src/renderable/cylinder.cpp similarity index 99% rename from src/gldraw/src/renderable/cylinder.cpp rename to src/scene/src/renderable/cylinder.cpp index 1b891c8..1734ba3 100644 --- a/src/gldraw/src/renderable/cylinder.cpp +++ b/src/scene/src/renderable/cylinder.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/renderable/cylinder.hpp" +#include "scene/renderable/cylinder.hpp" #include #include @@ -16,7 +16,7 @@ #include #include -#include "gldraw/shader.hpp" +#include "scene/shader.hpp" namespace quickviz { diff --git a/src/gldraw/src/renderable/details/batched_render_strategy.cpp b/src/scene/src/renderable/details/batched_render_strategy.cpp similarity index 98% rename from src/gldraw/src/renderable/details/batched_render_strategy.cpp rename to src/scene/src/renderable/details/batched_render_strategy.cpp index 32a2543..8398c03 100644 --- a/src/gldraw/src/renderable/details/batched_render_strategy.cpp +++ b/src/scene/src/renderable/details/batched_render_strategy.cpp @@ -12,8 +12,8 @@ #include #include "glad/glad.h" -#include "gldraw/shader_program.hpp" -#include "gldraw/renderable/canvas.hpp" +#include "scene/shader_program.hpp" +#include "scene/renderable/canvas.hpp" #include "canvas_data.hpp" namespace quickviz { diff --git a/src/gldraw/src/renderable/details/batched_render_strategy.hpp b/src/scene/src/renderable/details/batched_render_strategy.hpp similarity index 98% rename from src/gldraw/src/renderable/details/batched_render_strategy.hpp rename to src/scene/src/renderable/details/batched_render_strategy.hpp index e031cdc..6c04977 100644 --- a/src/gldraw/src/renderable/details/batched_render_strategy.hpp +++ b/src/scene/src/renderable/details/batched_render_strategy.hpp @@ -13,7 +13,7 @@ #include #include "render_strategy.hpp" #include "shape_renderer.hpp" -#include "gldraw/renderable/types.hpp" +#include "scene/renderable/types.hpp" namespace quickviz { diff --git a/src/gldraw/src/renderable/details/canvas_data.hpp b/src/scene/src/renderable/details/canvas_data.hpp similarity index 99% rename from src/gldraw/src/renderable/details/canvas_data.hpp rename to src/scene/src/renderable/details/canvas_data.hpp index 157e20e..1812771 100644 --- a/src/gldraw/src/renderable/details/canvas_data.hpp +++ b/src/scene/src/renderable/details/canvas_data.hpp @@ -15,7 +15,7 @@ #include #include "glad/glad.h" -#include "gldraw/renderable/types.hpp" +#include "scene/renderable/types.hpp" namespace quickviz { diff --git a/src/gldraw/src/renderable/details/canvas_data_manager.cpp b/src/scene/src/renderable/details/canvas_data_manager.cpp similarity index 100% rename from src/gldraw/src/renderable/details/canvas_data_manager.cpp rename to src/scene/src/renderable/details/canvas_data_manager.cpp diff --git a/src/gldraw/src/renderable/details/canvas_data_manager.hpp b/src/scene/src/renderable/details/canvas_data_manager.hpp similarity index 98% rename from src/gldraw/src/renderable/details/canvas_data_manager.hpp rename to src/scene/src/renderable/details/canvas_data_manager.hpp index f9759fc..78e650b 100644 --- a/src/gldraw/src/renderable/details/canvas_data_manager.hpp +++ b/src/scene/src/renderable/details/canvas_data_manager.hpp @@ -18,9 +18,9 @@ #include #include -#include "gldraw/renderable/types.hpp" +#include "scene/renderable/types.hpp" #include "canvas_data.hpp" -#include "gldraw/renderable/details/canvas_batching.hpp" +#include "scene/renderable/details/canvas_batching.hpp" namespace quickviz { namespace internal { diff --git a/src/gldraw/src/renderable/details/individual_render_strategy.cpp b/src/scene/src/renderable/details/individual_render_strategy.cpp similarity index 99% rename from src/gldraw/src/renderable/details/individual_render_strategy.cpp rename to src/scene/src/renderable/details/individual_render_strategy.cpp index 3ff7e65..f65375a 100644 --- a/src/gldraw/src/renderable/details/individual_render_strategy.cpp +++ b/src/scene/src/renderable/details/individual_render_strategy.cpp @@ -13,7 +13,7 @@ #include #include #include "glad/glad.h" -#include "gldraw/shader_program.hpp" +#include "scene/shader_program.hpp" #include "canvas_data.hpp" namespace quickviz { diff --git a/src/gldraw/src/renderable/details/individual_render_strategy.hpp b/src/scene/src/renderable/details/individual_render_strategy.hpp similarity index 100% rename from src/gldraw/src/renderable/details/individual_render_strategy.hpp rename to src/scene/src/renderable/details/individual_render_strategy.hpp diff --git a/src/gldraw/src/renderable/details/opengl_resource_pool.cpp b/src/scene/src/renderable/details/opengl_resource_pool.cpp similarity index 100% rename from src/gldraw/src/renderable/details/opengl_resource_pool.cpp rename to src/scene/src/renderable/details/opengl_resource_pool.cpp diff --git a/src/gldraw/src/renderable/details/opengl_resource_pool.hpp b/src/scene/src/renderable/details/opengl_resource_pool.hpp similarity index 100% rename from src/gldraw/src/renderable/details/opengl_resource_pool.hpp rename to src/scene/src/renderable/details/opengl_resource_pool.hpp diff --git a/src/gldraw/src/renderable/details/point_layer_manager.cpp b/src/scene/src/renderable/details/point_layer_manager.cpp similarity index 99% rename from src/gldraw/src/renderable/details/point_layer_manager.cpp rename to src/scene/src/renderable/details/point_layer_manager.cpp index be615e9..2d4ff0a 100644 --- a/src/gldraw/src/renderable/details/point_layer_manager.cpp +++ b/src/scene/src/renderable/details/point_layer_manager.cpp @@ -6,7 +6,7 @@ * @copyright Copyright (c) 2024 Ruixiang Du (rdu) */ -#include "../../../include/gldraw/renderable/details/point_layer_manager.hpp" +#include "../../../include/scene/renderable/details/point_layer_manager.hpp" #include #include #include diff --git a/src/gldraw/src/renderable/details/render_strategy.hpp b/src/scene/src/renderable/details/render_strategy.hpp similarity index 100% rename from src/gldraw/src/renderable/details/render_strategy.hpp rename to src/scene/src/renderable/details/render_strategy.hpp diff --git a/src/gldraw/src/renderable/details/shape_generators.cpp b/src/scene/src/renderable/details/shape_generators.cpp similarity index 100% rename from src/gldraw/src/renderable/details/shape_generators.cpp rename to src/scene/src/renderable/details/shape_generators.cpp diff --git a/src/gldraw/src/renderable/details/shape_generators.hpp b/src/scene/src/renderable/details/shape_generators.hpp similarity index 100% rename from src/gldraw/src/renderable/details/shape_generators.hpp rename to src/scene/src/renderable/details/shape_generators.hpp diff --git a/src/gldraw/src/renderable/details/shape_renderer.cpp b/src/scene/src/renderable/details/shape_renderer.cpp similarity index 99% rename from src/gldraw/src/renderable/details/shape_renderer.cpp rename to src/scene/src/renderable/details/shape_renderer.cpp index 5536587..8f238dc 100644 --- a/src/gldraw/src/renderable/details/shape_renderer.cpp +++ b/src/scene/src/renderable/details/shape_renderer.cpp @@ -8,7 +8,7 @@ */ #include "shape_renderer.hpp" -#include "gldraw/shader_program.hpp" +#include "scene/shader_program.hpp" #include namespace quickviz { diff --git a/src/gldraw/src/renderable/details/shape_renderer.hpp b/src/scene/src/renderable/details/shape_renderer.hpp similarity index 100% rename from src/gldraw/src/renderable/details/shape_renderer.hpp rename to src/scene/src/renderable/details/shape_renderer.hpp diff --git a/src/gldraw/src/renderable/details/shape_renderer_utils.cpp b/src/scene/src/renderable/details/shape_renderer_utils.cpp similarity index 99% rename from src/gldraw/src/renderable/details/shape_renderer_utils.cpp rename to src/scene/src/renderable/details/shape_renderer_utils.cpp index ebcdd74..536d3a0 100644 --- a/src/gldraw/src/renderable/details/shape_renderer_utils.cpp +++ b/src/scene/src/renderable/details/shape_renderer_utils.cpp @@ -11,7 +11,7 @@ #include #include #include "glad/glad.h" -#include "gldraw/shader_program.hpp" +#include "scene/shader_program.hpp" namespace quickviz { namespace internal { diff --git a/src/gldraw/src/renderable/details/shape_renderer_utils.hpp b/src/scene/src/renderable/details/shape_renderer_utils.hpp similarity index 99% rename from src/gldraw/src/renderable/details/shape_renderer_utils.hpp rename to src/scene/src/renderable/details/shape_renderer_utils.hpp index afb78c0..752d68c 100644 --- a/src/gldraw/src/renderable/details/shape_renderer_utils.hpp +++ b/src/scene/src/renderable/details/shape_renderer_utils.hpp @@ -13,7 +13,7 @@ #include #include #include -#include "gldraw/renderable/types.hpp" +#include "scene/renderable/types.hpp" #include "opengl_resource_pool.hpp" namespace quickviz { diff --git a/src/gldraw/src/renderable/frustum.cpp b/src/scene/src/renderable/frustum.cpp similarity index 99% rename from src/gldraw/src/renderable/frustum.cpp rename to src/scene/src/renderable/frustum.cpp index bdcfd6d..7935da6 100644 --- a/src/gldraw/src/renderable/frustum.cpp +++ b/src/scene/src/renderable/frustum.cpp @@ -7,14 +7,14 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/renderable/frustum.hpp" +#include "scene/renderable/frustum.hpp" #include #include #include #include #include #include -#include "gldraw/shader.hpp" +#include "scene/shader.hpp" namespace quickviz { diff --git a/src/gldraw/src/renderable/geometric_primitive.cpp b/src/scene/src/renderable/geometric_primitive.cpp similarity index 99% rename from src/gldraw/src/renderable/geometric_primitive.cpp rename to src/scene/src/renderable/geometric_primitive.cpp index d68062a..5f585cf 100644 --- a/src/gldraw/src/renderable/geometric_primitive.cpp +++ b/src/scene/src/renderable/geometric_primitive.cpp @@ -7,13 +7,13 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/renderable/geometric_primitive.hpp" +#include "scene/renderable/geometric_primitive.hpp" #include #include #include -#include "gldraw/shader.hpp" +#include "scene/shader.hpp" namespace quickviz { diff --git a/src/gldraw/src/renderable/grid.cpp b/src/scene/src/renderable/grid.cpp similarity index 98% rename from src/gldraw/src/renderable/grid.cpp rename to src/scene/src/renderable/grid.cpp index e754e8b..7551112 100644 --- a/src/gldraw/src/renderable/grid.cpp +++ b/src/scene/src/renderable/grid.cpp @@ -7,13 +7,13 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/renderable/grid.hpp" +#include "scene/renderable/grid.hpp" #include #include "glad/glad.h" #include -#include "gldraw/shader.hpp" +#include "scene/shader.hpp" namespace quickviz { namespace { diff --git a/src/gldraw/src/renderable/line_strip.cpp b/src/scene/src/renderable/line_strip.cpp similarity index 99% rename from src/gldraw/src/renderable/line_strip.cpp rename to src/scene/src/renderable/line_strip.cpp index 8c575f5..459bff3 100644 --- a/src/gldraw/src/renderable/line_strip.cpp +++ b/src/scene/src/renderable/line_strip.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/renderable/line_strip.hpp" +#include "scene/renderable/line_strip.hpp" #include #include @@ -17,7 +17,7 @@ #include #include -#include "gldraw/shader.hpp" +#include "scene/shader.hpp" namespace quickviz { diff --git a/src/gldraw/src/renderable/mesh.cpp b/src/scene/src/renderable/mesh.cpp similarity index 99% rename from src/gldraw/src/renderable/mesh.cpp rename to src/scene/src/renderable/mesh.cpp index bc92d82..373be03 100644 --- a/src/gldraw/src/renderable/mesh.cpp +++ b/src/scene/src/renderable/mesh.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/renderable/mesh.hpp" +#include "scene/renderable/mesh.hpp" #ifdef VIEWER_WITH_GLAD #include @@ -20,7 +20,7 @@ #include #include -#include "gldraw/shader.hpp" +#include "scene/shader.hpp" namespace quickviz { diff --git a/src/gldraw/src/renderable/path.cpp b/src/scene/src/renderable/path.cpp similarity index 99% rename from src/gldraw/src/renderable/path.cpp rename to src/scene/src/renderable/path.cpp index 8578c85..be57054 100644 --- a/src/gldraw/src/renderable/path.cpp +++ b/src/scene/src/renderable/path.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/renderable/path.hpp" +#include "scene/renderable/path.hpp" #include #include @@ -17,7 +17,7 @@ #include #include -#include "gldraw/shader.hpp" +#include "scene/shader.hpp" namespace quickviz { diff --git a/src/gldraw/src/renderable/plane.cpp b/src/scene/src/renderable/plane.cpp similarity index 99% rename from src/gldraw/src/renderable/plane.cpp rename to src/scene/src/renderable/plane.cpp index b8c6d06..d6f514b 100644 --- a/src/gldraw/src/renderable/plane.cpp +++ b/src/scene/src/renderable/plane.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/renderable/plane.hpp" +#include "scene/renderable/plane.hpp" #include #include @@ -16,7 +16,7 @@ #include #include -#include "gldraw/shader.hpp" +#include "scene/shader.hpp" namespace quickviz { diff --git a/src/gldraw/src/renderable/point_cloud.cpp b/src/scene/src/renderable/point_cloud.cpp similarity index 99% rename from src/gldraw/src/renderable/point_cloud.cpp rename to src/scene/src/renderable/point_cloud.cpp index da5709b..963ab20 100644 --- a/src/gldraw/src/renderable/point_cloud.cpp +++ b/src/scene/src/renderable/point_cloud.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/renderable/point_cloud.hpp" +#include "scene/renderable/point_cloud.hpp" #include #include @@ -16,7 +16,7 @@ #include #include -#include "gldraw/shader.hpp" +#include "scene/shader.hpp" namespace quickviz { diff --git a/src/gldraw/src/renderable/pose.cpp b/src/scene/src/renderable/pose.cpp similarity index 99% rename from src/gldraw/src/renderable/pose.cpp rename to src/scene/src/renderable/pose.cpp index 7a55f2a..285449d 100644 --- a/src/gldraw/src/renderable/pose.cpp +++ b/src/scene/src/renderable/pose.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/renderable/pose.hpp" +#include "scene/renderable/pose.hpp" #include #include @@ -17,7 +17,7 @@ #include #include -#include "gldraw/shader.hpp" +#include "scene/shader.hpp" namespace quickviz { diff --git a/src/gldraw/src/renderable/sphere.cpp b/src/scene/src/renderable/sphere.cpp similarity index 99% rename from src/gldraw/src/renderable/sphere.cpp rename to src/scene/src/renderable/sphere.cpp index 12a2962..2eb1260 100644 --- a/src/gldraw/src/renderable/sphere.cpp +++ b/src/scene/src/renderable/sphere.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/renderable/sphere.hpp" +#include "scene/renderable/sphere.hpp" #include #include @@ -16,7 +16,7 @@ #include #include -#include "gldraw/shader.hpp" +#include "scene/shader.hpp" namespace quickviz { diff --git a/src/gldraw/src/renderable/texture.cpp b/src/scene/src/renderable/texture.cpp similarity index 99% rename from src/gldraw/src/renderable/texture.cpp rename to src/scene/src/renderable/texture.cpp index 2edfe88..17d7152 100644 --- a/src/gldraw/src/renderable/texture.cpp +++ b/src/scene/src/renderable/texture.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/renderable/texture.hpp" +#include "scene/renderable/texture.hpp" #include #include diff --git a/src/gldraw/src/renderable/triangle.cpp b/src/scene/src/renderable/triangle.cpp similarity index 99% rename from src/gldraw/src/renderable/triangle.cpp rename to src/scene/src/renderable/triangle.cpp index 0e6cafd..b152210 100644 --- a/src/gldraw/src/renderable/triangle.cpp +++ b/src/scene/src/renderable/triangle.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/renderable/triangle.hpp" +#include "scene/renderable/triangle.hpp" #include diff --git a/src/gldraw/src/scene_input_handler.cpp b/src/scene/src/scene_input_handler.cpp similarity index 98% rename from src/gldraw/src/scene_input_handler.cpp rename to src/scene/src/scene_input_handler.cpp index 358ceb1..967be8f 100644 --- a/src/gldraw/src/scene_input_handler.cpp +++ b/src/scene/src/scene_input_handler.cpp @@ -6,10 +6,10 @@ * @copyright Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/scene_input_handler.hpp" -#include "gldraw/scene_manager.hpp" -#include "gldraw/camera_control_config.hpp" -#include "gldraw/tools/interaction_tool.hpp" +#include "scene/scene_input_handler.hpp" +#include "scene/scene_manager.hpp" +#include "scene/camera_control_config.hpp" +#include "scene/tools/interaction_tool.hpp" #include "viewer/input/input_types.hpp" #include "core/event/input_mapping.hpp" diff --git a/src/gldraw/src/scene_manager.cpp b/src/scene/src/scene_manager.cpp similarity index 95% rename from src/gldraw/src/scene_manager.cpp rename to src/scene/src/scene_manager.cpp index 92b2bc8..5e6dd13 100644 --- a/src/gldraw/src/scene_manager.cpp +++ b/src/scene/src/scene_manager.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/scene_manager.hpp" +#include "scene/scene_manager.hpp" #include #include @@ -18,11 +18,11 @@ #include -#include "gldraw/coordinate_transformer.hpp" -#include "gldraw/selection_manager.hpp" -#include "gldraw/tools/interaction_tool.hpp" -#include "gldraw/renderable/point_cloud.hpp" -#include "gldraw/renderable/geometric_primitive.hpp" +#include "scene/coordinate_transformer.hpp" +#include "scene/selection_manager.hpp" +#include "scene/tools/interaction_tool.hpp" +#include "scene/renderable/point_cloud.hpp" +#include "scene/renderable/geometric_primitive.hpp" namespace quickviz { SceneManager::SceneManager(const std::string& name, Mode mode) diff --git a/src/gldraw/src/selection_manager.cpp b/src/scene/src/selection_manager.cpp similarity index 99% rename from src/gldraw/src/selection_manager.cpp rename to src/scene/src/selection_manager.cpp index e4a6e4c..2eb335d 100644 --- a/src/gldraw/src/selection_manager.cpp +++ b/src/scene/src/selection_manager.cpp @@ -7,16 +7,16 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/selection_manager.hpp" +#include "scene/selection_manager.hpp" #include #include #include #include -#include "gldraw/scene_manager.hpp" -#include "gldraw/renderable/point_cloud.hpp" -#include "gldraw/frame_buffer.hpp" +#include "scene/scene_manager.hpp" +#include "scene/renderable/point_cloud.hpp" +#include "scene/frame_buffer.hpp" namespace quickviz { diff --git a/src/gldraw/src/shader.cpp b/src/scene/src/shader.cpp similarity index 99% rename from src/gldraw/src/shader.cpp rename to src/scene/src/shader.cpp index 2a9aea4..c101270 100644 --- a/src/gldraw/src/shader.cpp +++ b/src/scene/src/shader.cpp @@ -6,7 +6,7 @@ * @copyright Copyright (c) 2024 Ruixiang Du (rdu) */ -#include "gldraw/shader.hpp" +#include "scene/shader.hpp" #include #include diff --git a/src/gldraw/src/shader_program.cpp b/src/scene/src/shader_program.cpp similarity index 99% rename from src/gldraw/src/shader_program.cpp rename to src/scene/src/shader_program.cpp index 82e17f5..ac2ad09 100644 --- a/src/gldraw/src/shader_program.cpp +++ b/src/scene/src/shader_program.cpp @@ -6,7 +6,7 @@ * @copyright Copyright (c) 2024 Ruixiang Du (rdu) */ -#include "gldraw/shader_program.hpp" +#include "scene/shader_program.hpp" #include diff --git a/src/gldraw/src/tools/interaction_tool.cpp b/src/scene/src/tools/interaction_tool.cpp similarity index 98% rename from src/gldraw/src/tools/interaction_tool.cpp rename to src/scene/src/tools/interaction_tool.cpp index 4179199..4edd9d7 100644 --- a/src/gldraw/src/tools/interaction_tool.cpp +++ b/src/scene/src/tools/interaction_tool.cpp @@ -7,8 +7,8 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/tools/interaction_tool.hpp" -#include "gldraw/scene_manager.hpp" +#include "scene/tools/interaction_tool.hpp" +#include "scene/scene_manager.hpp" #include #include diff --git a/src/gldraw/src/tools/point_selection_tool.cpp b/src/scene/src/tools/point_selection_tool.cpp similarity index 99% rename from src/gldraw/src/tools/point_selection_tool.cpp rename to src/scene/src/tools/point_selection_tool.cpp index 46f56cf..8646a37 100644 --- a/src/gldraw/src/tools/point_selection_tool.cpp +++ b/src/scene/src/tools/point_selection_tool.cpp @@ -7,9 +7,9 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "gldraw/tools/point_selection_tool.hpp" -#include "gldraw/scene_manager.hpp" -#include "gldraw/renderable/point_cloud.hpp" +#include "scene/tools/point_selection_tool.hpp" +#include "scene/scene_manager.hpp" +#include "scene/renderable/point_cloud.hpp" #include #include diff --git a/src/gldraw/test/CMakeLists.txt b/src/scene/test/CMakeLists.txt similarity index 62% rename from src/gldraw/test/CMakeLists.txt rename to src/scene/test/CMakeLists.txt index 6b805c9..264ade9 100644 --- a/src/gldraw/test/CMakeLists.txt +++ b/src/scene/test/CMakeLists.txt @@ -3,38 +3,38 @@ add_subdirectory(renderable) add_subdirectory(selection) add_executable(test_framebuffer test_framebuffer.cpp) -target_link_libraries(test_framebuffer PRIVATE gldraw) +target_link_libraries(test_framebuffer PRIVATE scene) add_executable(test_shader test_shader.cpp) -target_link_libraries(test_shader PRIVATE gldraw) +target_link_libraries(test_shader PRIVATE scene) add_executable(test_camera_raw test_camera_raw.cpp) -target_link_libraries(test_camera_raw PRIVATE gldraw) +target_link_libraries(test_camera_raw PRIVATE scene) add_executable(test_point_cloud_realtime test_point_cloud_realtime.cpp) -target_link_libraries(test_point_cloud_realtime PRIVATE gldraw) +target_link_libraries(test_point_cloud_realtime PRIVATE scene) add_executable(test_point_cloud_buffer_strategies test_point_cloud_buffer_strategies.cpp) -target_link_libraries(test_point_cloud_buffer_strategies PRIVATE gldraw) +target_link_libraries(test_point_cloud_buffer_strategies PRIVATE scene) # Test external selection demo (replaces old selection integration) # test_pcd_with_selection.cpp now demonstrates external selection visualization add_executable(test_coordinate_system test_coordinate_system.cpp) -target_link_libraries(test_coordinate_system PRIVATE gldraw) +target_link_libraries(test_coordinate_system PRIVATE scene) add_executable(test_primitive_drawing test_primitive_drawing.cpp) -target_link_libraries(test_primitive_drawing PRIVATE gldraw) +target_link_libraries(test_primitive_drawing PRIVATE scene) add_executable(test_canvas_st test_canvas_st.cpp) -target_link_libraries(test_canvas_st PRIVATE gldraw) +target_link_libraries(test_canvas_st PRIVATE scene) add_executable(test_nav_map_rendering test_nav_map_rendering.cpp) -target_link_libraries(test_nav_map_rendering PRIVATE gldraw) +target_link_libraries(test_nav_map_rendering PRIVATE scene) add_executable(test_layer_system_box test_layer_system_box.cpp) -target_link_libraries(test_layer_system_box PRIVATE gldraw) +target_link_libraries(test_layer_system_box PRIVATE scene) add_executable(test_font_renderer test_font_renderer.cpp) -target_link_libraries(test_font_renderer PRIVATE gldraw) +target_link_libraries(test_font_renderer PRIVATE scene) diff --git a/src/gldraw/test/feature/CMakeLists.txt b/src/scene/test/feature/CMakeLists.txt similarity index 52% rename from src/gldraw/test/feature/CMakeLists.txt rename to src/scene/test/feature/CMakeLists.txt index 25ce8d2..7f10f0d 100644 --- a/src/gldraw/test/feature/CMakeLists.txt +++ b/src/scene/test/feature/CMakeLists.txt @@ -1,20 +1,20 @@ add_executable(test_gl_scene_panel test_gl_scene_panel.cpp) -target_link_libraries(test_gl_scene_panel PRIVATE gldraw) +target_link_libraries(test_gl_scene_panel PRIVATE scene) add_executable(test_robot_frames test_robot_frames.cpp) -target_link_libraries(test_robot_frames PRIVATE gldraw) +target_link_libraries(test_robot_frames PRIVATE scene) add_executable(test_camera test_camera.cpp) -target_link_libraries(test_camera PRIVATE gldraw) +target_link_libraries(test_camera PRIVATE scene) add_executable(test_layer_system test_layer_system.cpp) -target_link_libraries(test_layer_system PRIVATE gldraw) +target_link_libraries(test_layer_system PRIVATE scene) add_executable(test_camera_control_mappings test_camera_control_mappings.cpp) -target_link_libraries(test_camera_control_mappings PRIVATE gldraw) +target_link_libraries(test_camera_control_mappings PRIVATE scene) add_executable(test_camera_configuration test_camera_configuration.cpp) -target_link_libraries(test_camera_configuration PRIVATE gldraw) +target_link_libraries(test_camera_configuration PRIVATE scene) add_executable(test_visual_feedback_system test_visual_feedback_system.cpp) -target_link_libraries(test_visual_feedback_system PRIVATE gldraw) +target_link_libraries(test_visual_feedback_system PRIVATE scene) diff --git a/src/gldraw/test/feature/test_camera.cpp b/src/scene/test/feature/test_camera.cpp similarity index 95% rename from src/gldraw/test/feature/test_camera.cpp rename to src/scene/test/feature/test_camera.cpp index 8bbd390..458aa9d 100644 --- a/src/gldraw/test/feature/test_camera.cpp +++ b/src/scene/test/feature/test_camera.cpp @@ -13,9 +13,9 @@ #include "viewer/viewer.hpp" #include "viewer/box.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/point_cloud.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/point_cloud.hpp" using namespace quickviz; diff --git a/src/gldraw/test/feature/test_camera_configuration.cpp b/src/scene/test/feature/test_camera_configuration.cpp similarity index 97% rename from src/gldraw/test/feature/test_camera_configuration.cpp rename to src/scene/test/feature/test_camera_configuration.cpp index 3af20c5..288e6fd 100644 --- a/src/gldraw/test/feature/test_camera_configuration.cpp +++ b/src/scene/test/feature/test_camera_configuration.cpp @@ -12,10 +12,10 @@ #include "viewer/viewer.hpp" #include "viewer/box.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/point_cloud.hpp" -#include "gldraw/camera_controller.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/point_cloud.hpp" +#include "scene/camera_controller.hpp" #include "imgui.h" using namespace quickviz; diff --git a/src/gldraw/test/feature/test_camera_control_mappings.cpp b/src/scene/test/feature/test_camera_control_mappings.cpp similarity index 97% rename from src/gldraw/test/feature/test_camera_control_mappings.cpp rename to src/scene/test/feature/test_camera_control_mappings.cpp index 3540645..d5d07fc 100644 --- a/src/gldraw/test/feature/test_camera_control_mappings.cpp +++ b/src/scene/test/feature/test_camera_control_mappings.cpp @@ -14,11 +14,11 @@ #include "viewer/viewer.hpp" #include "viewer/box.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/point_cloud.hpp" -#include "gldraw/scene_input_handler.hpp" -#include "gldraw/camera_control_config.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/point_cloud.hpp" +#include "scene/scene_input_handler.hpp" +#include "scene/camera_control_config.hpp" #include "imgui.h" using namespace quickviz; diff --git a/src/gldraw/test/feature/test_gl_scene_panel.cpp b/src/scene/test/feature/test_gl_scene_panel.cpp similarity index 92% rename from src/gldraw/test/feature/test_gl_scene_panel.cpp rename to src/scene/test/feature/test_gl_scene_panel.cpp index 574f49a..204323a 100644 --- a/src/gldraw/test/feature/test_gl_scene_panel.cpp +++ b/src/scene/test/feature/test_gl_scene_panel.cpp @@ -16,9 +16,9 @@ #include "viewer/box.hpp" #include "viewer/viewer.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/triangle.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/triangle.hpp" using namespace quickviz; diff --git a/src/gldraw/test/feature/test_layer_system.cpp b/src/scene/test/feature/test_layer_system.cpp similarity index 98% rename from src/gldraw/test/feature/test_layer_system.cpp rename to src/scene/test/feature/test_layer_system.cpp index 8a2f450..6bc8a39 100644 --- a/src/gldraw/test/feature/test_layer_system.cpp +++ b/src/scene/test/feature/test_layer_system.cpp @@ -14,10 +14,10 @@ #include "viewer/viewer.hpp" #include "viewer/box.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/point_cloud.hpp" -#include "../../include/gldraw/renderable/details/point_layer_manager.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/point_cloud.hpp" +#include "../../include/scene/renderable/details/point_layer_manager.hpp" using namespace quickviz; diff --git a/src/gldraw/test/feature/test_robot_frames.cpp b/src/scene/test/feature/test_robot_frames.cpp similarity index 97% rename from src/gldraw/test/feature/test_robot_frames.cpp rename to src/scene/test/feature/test_robot_frames.cpp index 089ba4b..118dcc5 100644 --- a/src/gldraw/test/feature/test_robot_frames.cpp +++ b/src/scene/test/feature/test_robot_frames.cpp @@ -19,9 +19,9 @@ #include "viewer/box.hpp" #include "viewer/viewer.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/coordinate_frame.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/coordinate_frame.hpp" using namespace quickviz; diff --git a/src/gldraw/test/feature/test_visual_feedback_system.cpp b/src/scene/test/feature/test_visual_feedback_system.cpp similarity index 97% rename from src/gldraw/test/feature/test_visual_feedback_system.cpp rename to src/scene/test/feature/test_visual_feedback_system.cpp index adbd6ac..b2f3402 100644 --- a/src/gldraw/test/feature/test_visual_feedback_system.cpp +++ b/src/scene/test/feature/test_visual_feedback_system.cpp @@ -12,11 +12,11 @@ #include "viewer/viewer.hpp" #include "viewer/box.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/point_cloud.hpp" -#include "gldraw/renderable/triangle.hpp" -#include "gldraw/feedback/visual_feedback_system.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/point_cloud.hpp" +#include "scene/renderable/triangle.hpp" +#include "scene/feedback/visual_feedback_system.hpp" #include "imgui.h" using namespace quickviz; diff --git a/src/gldraw/test/input/test_gamepad_input.cpp b/src/scene/test/input/test_gamepad_input.cpp similarity index 99% rename from src/gldraw/test/input/test_gamepad_input.cpp rename to src/scene/test/input/test_gamepad_input.cpp index 4a5c28e..5016204 100644 --- a/src/gldraw/test/input/test_gamepad_input.cpp +++ b/src/scene/test/input/test_gamepad_input.cpp @@ -13,7 +13,7 @@ #include "core/event/input_event.hpp" #include "core/event/input_mapping.hpp" #include "viewer/input/imgui_input_utils.hpp" -#include "gldraw/input/scene_input_handler.hpp" +#include "scene/input/scene_input_handler.hpp" #include "viewer/input/input_dispatcher.hpp" using namespace quickviz; diff --git a/src/scene/test/renderable/CMakeLists.txt b/src/scene/test/renderable/CMakeLists.txt new file mode 100644 index 0000000..8047c65 --- /dev/null +++ b/src/scene/test/renderable/CMakeLists.txt @@ -0,0 +1,52 @@ +add_executable(test_arrow test_arrow.cpp) +target_link_libraries(test_arrow PRIVATE scene) + +add_executable(test_billboard test_billboard.cpp) +target_link_libraries(test_billboard PRIVATE scene) + +add_executable(test_bounding_box test_bounding_box.cpp) +target_link_libraries(test_bounding_box PRIVATE scene) + +add_executable(test_canvas test_canvas.cpp) +target_link_libraries(test_canvas PRIVATE scene) + +add_executable(test_coordinate_frame test_coordinate_frame.cpp) +target_link_libraries(test_coordinate_frame PRIVATE scene) + +add_executable(test_cylinder test_cylinder.cpp) +target_link_libraries(test_cylinder PRIVATE scene) + +add_executable(test_frustum test_frustum.cpp) +target_link_libraries(test_frustum PRIVATE scene) + +add_executable(test_grid test_grid.cpp) +target_link_libraries(test_grid PRIVATE scene) + +add_executable(test_line_strip test_line_strip.cpp) +target_link_libraries(test_line_strip PRIVATE scene) + +add_executable(test_mesh test_mesh.cpp) +target_link_libraries(test_mesh PRIVATE scene) + +add_executable(test_point_cloud test_point_cloud.cpp) +target_link_libraries(test_point_cloud PRIVATE scene) + +add_executable(test_path test_path.cpp) +target_link_libraries(test_path PRIVATE scene) + +add_executable(test_plane test_plane.cpp) +target_link_libraries(test_plane PRIVATE scene) + +add_executable(test_pose test_pose.cpp) +target_link_libraries(test_pose PRIVATE scene) + +add_executable(test_sphere test_sphere.cpp) +target_link_libraries(test_sphere PRIVATE scene) + + +add_executable(test_texture test_texture.cpp) +target_link_libraries(test_texture PRIVATE scene) + +add_executable(test_triangle test_triangle.cpp) +target_link_libraries(test_triangle PRIVATE scene) + diff --git a/src/gldraw/test/renderable/test_arrow.cpp b/src/scene/test/renderable/test_arrow.cpp similarity index 98% rename from src/gldraw/test/renderable/test_arrow.cpp rename to src/scene/test/renderable/test_arrow.cpp index 61c7fa6..8d0cdc1 100644 --- a/src/gldraw/test/renderable/test_arrow.cpp +++ b/src/scene/test/renderable/test_arrow.cpp @@ -15,8 +15,8 @@ #include #include -#include "gldraw/gl_viewer.hpp" -#include "gldraw/renderable/arrow.hpp" +#include "scene/gl_viewer.hpp" +#include "scene/renderable/arrow.hpp" using namespace quickviz; diff --git a/src/gldraw/test/renderable/test_billboard.cpp b/src/scene/test/renderable/test_billboard.cpp similarity index 98% rename from src/gldraw/test/renderable/test_billboard.cpp rename to src/scene/test/renderable/test_billboard.cpp index c9b4988..5e55e5e 100644 --- a/src/gldraw/test/renderable/test_billboard.cpp +++ b/src/scene/test/renderable/test_billboard.cpp @@ -19,11 +19,11 @@ #include #include -#include "gldraw/gl_viewer.hpp" -#include "gldraw/renderable/billboard.hpp" -#include "gldraw/renderable/sphere.hpp" -#include "gldraw/renderable/arrow.hpp" -#include "gldraw/renderable/grid.hpp" +#include "scene/gl_viewer.hpp" +#include "scene/renderable/billboard.hpp" +#include "scene/renderable/sphere.hpp" +#include "scene/renderable/arrow.hpp" +#include "scene/renderable/grid.hpp" using namespace quickviz; diff --git a/src/gldraw/test/renderable/test_bounding_box.cpp b/src/scene/test/renderable/test_bounding_box.cpp similarity index 98% rename from src/gldraw/test/renderable/test_bounding_box.cpp rename to src/scene/test/renderable/test_bounding_box.cpp index 51c8175..81b3860 100644 --- a/src/gldraw/test/renderable/test_bounding_box.cpp +++ b/src/scene/test/renderable/test_bounding_box.cpp @@ -14,8 +14,8 @@ #include #include -#include "gldraw/gl_viewer.hpp" -#include "gldraw/renderable/bounding_box.hpp" +#include "scene/gl_viewer.hpp" +#include "scene/renderable/bounding_box.hpp" using namespace quickviz; diff --git a/src/gldraw/test/renderable/test_canvas.cpp b/src/scene/test/renderable/test_canvas.cpp similarity index 98% rename from src/gldraw/test/renderable/test_canvas.cpp rename to src/scene/test/renderable/test_canvas.cpp index dd1ed9e..3519f6e 100644 --- a/src/gldraw/test/renderable/test_canvas.cpp +++ b/src/scene/test/renderable/test_canvas.cpp @@ -16,9 +16,9 @@ #include #include -#include "gldraw/gl_viewer.hpp" -#include "gldraw/renderable/canvas.hpp" -#include "gldraw/renderable/triangle.hpp" +#include "scene/gl_viewer.hpp" +#include "scene/renderable/canvas.hpp" +#include "scene/renderable/triangle.hpp" using namespace quickviz; namespace fs = std::filesystem; diff --git a/src/gldraw/test/renderable/test_coordinate_frame.cpp b/src/scene/test/renderable/test_coordinate_frame.cpp similarity index 97% rename from src/gldraw/test/renderable/test_coordinate_frame.cpp rename to src/scene/test/renderable/test_coordinate_frame.cpp index 9d62d40..83a2ee7 100644 --- a/src/gldraw/test/renderable/test_coordinate_frame.cpp +++ b/src/scene/test/renderable/test_coordinate_frame.cpp @@ -13,9 +13,9 @@ #include #include -#include "gldraw/gl_viewer.hpp" -#include "gldraw/renderable/coordinate_frame.hpp" -#include "gldraw/renderable/grid.hpp" +#include "scene/gl_viewer.hpp" +#include "scene/renderable/coordinate_frame.hpp" +#include "scene/renderable/grid.hpp" using namespace quickviz; diff --git a/src/gldraw/test/renderable/test_cylinder.cpp b/src/scene/test/renderable/test_cylinder.cpp similarity index 98% rename from src/gldraw/test/renderable/test_cylinder.cpp rename to src/scene/test/renderable/test_cylinder.cpp index fb38d19..b1f2f49 100644 --- a/src/gldraw/test/renderable/test_cylinder.cpp +++ b/src/scene/test/renderable/test_cylinder.cpp @@ -11,8 +11,8 @@ #include #include -#include "gldraw/gl_viewer.hpp" -#include "gldraw/renderable/cylinder.hpp" +#include "scene/gl_viewer.hpp" +#include "scene/renderable/cylinder.hpp" using namespace quickviz; diff --git a/src/gldraw/test/renderable/test_frustum.cpp b/src/scene/test/renderable/test_frustum.cpp similarity index 98% rename from src/gldraw/test/renderable/test_frustum.cpp rename to src/scene/test/renderable/test_frustum.cpp index 2677864..4091a77 100644 --- a/src/gldraw/test/renderable/test_frustum.cpp +++ b/src/scene/test/renderable/test_frustum.cpp @@ -11,9 +11,9 @@ #include #include -#include "gldraw/gl_viewer.hpp" -#include "gldraw/renderable/frustum.hpp" -#include "gldraw/renderable/grid.hpp" +#include "scene/gl_viewer.hpp" +#include "scene/renderable/frustum.hpp" +#include "scene/renderable/grid.hpp" using namespace quickviz; diff --git a/src/gldraw/test/renderable/test_grid.cpp b/src/scene/test/renderable/test_grid.cpp similarity index 98% rename from src/gldraw/test/renderable/test_grid.cpp rename to src/scene/test/renderable/test_grid.cpp index fe8eb8d..83faf03 100644 --- a/src/gldraw/test/renderable/test_grid.cpp +++ b/src/scene/test/renderable/test_grid.cpp @@ -12,8 +12,8 @@ #include #include -#include "gldraw/gl_viewer.hpp" -#include "gldraw/renderable/grid.hpp" +#include "scene/gl_viewer.hpp" +#include "scene/renderable/grid.hpp" using namespace quickviz; diff --git a/src/gldraw/test/renderable/test_line_strip.cpp b/src/scene/test/renderable/test_line_strip.cpp similarity index 98% rename from src/gldraw/test/renderable/test_line_strip.cpp rename to src/scene/test/renderable/test_line_strip.cpp index d4b8c20..49795a6 100644 --- a/src/gldraw/test/renderable/test_line_strip.cpp +++ b/src/scene/test/renderable/test_line_strip.cpp @@ -12,8 +12,8 @@ #include #include -#include "gldraw/gl_viewer.hpp" -#include "gldraw/renderable/line_strip.hpp" +#include "scene/gl_viewer.hpp" +#include "scene/renderable/line_strip.hpp" using namespace quickviz; diff --git a/src/gldraw/test/renderable/test_mesh.cpp b/src/scene/test/renderable/test_mesh.cpp similarity index 99% rename from src/gldraw/test/renderable/test_mesh.cpp rename to src/scene/test/renderable/test_mesh.cpp index d361274..24ee83f 100644 --- a/src/gldraw/test/renderable/test_mesh.cpp +++ b/src/scene/test/renderable/test_mesh.cpp @@ -12,8 +12,8 @@ #include #include -#include "gldraw/gl_viewer.hpp" -#include "gldraw/renderable/mesh.hpp" +#include "scene/gl_viewer.hpp" +#include "scene/renderable/mesh.hpp" using namespace quickviz; diff --git a/src/gldraw/test/renderable/test_path.cpp b/src/scene/test/renderable/test_path.cpp similarity index 99% rename from src/gldraw/test/renderable/test_path.cpp rename to src/scene/test/renderable/test_path.cpp index ffa7915..cfd9604 100644 --- a/src/gldraw/test/renderable/test_path.cpp +++ b/src/scene/test/renderable/test_path.cpp @@ -15,9 +15,9 @@ #include #include -#include "gldraw/gl_viewer.hpp" -#include "gldraw/renderable/path.hpp" -#include "gldraw/renderable/grid.hpp" +#include "scene/gl_viewer.hpp" +#include "scene/renderable/path.hpp" +#include "scene/renderable/grid.hpp" using namespace quickviz; diff --git a/src/gldraw/test/renderable/test_plane.cpp b/src/scene/test/renderable/test_plane.cpp similarity index 98% rename from src/gldraw/test/renderable/test_plane.cpp rename to src/scene/test/renderable/test_plane.cpp index 57786cb..175f936 100644 --- a/src/gldraw/test/renderable/test_plane.cpp +++ b/src/scene/test/renderable/test_plane.cpp @@ -15,10 +15,10 @@ #include #include -#include "gldraw/gl_viewer.hpp" -#include "gldraw/renderable/plane.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/coordinate_frame.hpp" +#include "scene/gl_viewer.hpp" +#include "scene/renderable/plane.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/coordinate_frame.hpp" using namespace quickviz; diff --git a/src/gldraw/test/renderable/test_point_cloud.cpp b/src/scene/test/renderable/test_point_cloud.cpp similarity index 98% rename from src/gldraw/test/renderable/test_point_cloud.cpp rename to src/scene/test/renderable/test_point_cloud.cpp index 20e9cd4..5d6b50b 100644 --- a/src/gldraw/test/renderable/test_point_cloud.cpp +++ b/src/scene/test/renderable/test_point_cloud.cpp @@ -14,8 +14,8 @@ #include #include -#include "gldraw/gl_viewer.hpp" -#include "gldraw/renderable/point_cloud.hpp" +#include "scene/gl_viewer.hpp" +#include "scene/renderable/point_cloud.hpp" using namespace quickviz; diff --git a/src/gldraw/test/renderable/test_pose.cpp b/src/scene/test/renderable/test_pose.cpp similarity index 98% rename from src/gldraw/test/renderable/test_pose.cpp rename to src/scene/test/renderable/test_pose.cpp index a36d6af..b5d40ba 100644 --- a/src/gldraw/test/renderable/test_pose.cpp +++ b/src/scene/test/renderable/test_pose.cpp @@ -16,9 +16,9 @@ #include #include -#include "gldraw/gl_viewer.hpp" -#include "gldraw/renderable/pose.hpp" -#include "gldraw/renderable/grid.hpp" +#include "scene/gl_viewer.hpp" +#include "scene/renderable/pose.hpp" +#include "scene/renderable/grid.hpp" using namespace quickviz; diff --git a/src/gldraw/test/renderable/test_sphere.cpp b/src/scene/test/renderable/test_sphere.cpp similarity index 98% rename from src/gldraw/test/renderable/test_sphere.cpp rename to src/scene/test/renderable/test_sphere.cpp index 7bf3fc2..7425b84 100644 --- a/src/gldraw/test/renderable/test_sphere.cpp +++ b/src/scene/test/renderable/test_sphere.cpp @@ -11,8 +11,8 @@ #include #include -#include "gldraw/gl_viewer.hpp" -#include "gldraw/renderable/sphere.hpp" +#include "scene/gl_viewer.hpp" +#include "scene/renderable/sphere.hpp" using namespace quickviz; diff --git a/src/gldraw/test/renderable/test_texture.cpp b/src/scene/test/renderable/test_texture.cpp similarity index 99% rename from src/gldraw/test/renderable/test_texture.cpp rename to src/scene/test/renderable/test_texture.cpp index 5d7bfc3..f1a750a 100644 --- a/src/gldraw/test/renderable/test_texture.cpp +++ b/src/scene/test/renderable/test_texture.cpp @@ -19,8 +19,8 @@ #include #include -#include "gldraw/gl_viewer.hpp" -#include "gldraw/renderable/texture.hpp" +#include "scene/gl_viewer.hpp" +#include "scene/renderable/texture.hpp" #include "core/buffer/buffer_registry.hpp" #include "core/buffer/ring_buffer.hpp" diff --git a/src/gldraw/test/renderable/test_triangle.cpp b/src/scene/test/renderable/test_triangle.cpp similarity index 98% rename from src/gldraw/test/renderable/test_triangle.cpp rename to src/scene/test/renderable/test_triangle.cpp index 546f9f3..0bc72af 100644 --- a/src/gldraw/test/renderable/test_triangle.cpp +++ b/src/scene/test/renderable/test_triangle.cpp @@ -15,8 +15,8 @@ #include #include -#include "gldraw/gl_viewer.hpp" -#include "gldraw/renderable/triangle.hpp" +#include "scene/gl_viewer.hpp" +#include "scene/renderable/triangle.hpp" using namespace quickviz; diff --git a/src/gldraw/test/selection/CMakeLists.txt b/src/scene/test/selection/CMakeLists.txt similarity index 93% rename from src/gldraw/test/selection/CMakeLists.txt rename to src/scene/test/selection/CMakeLists.txt index 3335e29..af3180b 100644 --- a/src/gldraw/test/selection/CMakeLists.txt +++ b/src/scene/test/selection/CMakeLists.txt @@ -5,11 +5,11 @@ # Shared utilities for all selection tests add_library(selection_test_utils selection_test_utils.cpp selection_test_utils.hpp) -target_link_libraries(selection_test_utils PUBLIC gldraw) +target_link_libraries(selection_test_utils PUBLIC scene) # Original comprehensive test (kept for backward compatibility) #add_executable(test_object_selection test_object_selection.cpp) -#target_link_libraries(test_object_selection PRIVATE gldraw) +#target_link_libraries(test_object_selection PRIVATE scene) # Individual object type selection tests add_executable(test_sphere_selection test_sphere_selection.cpp) diff --git a/src/gldraw/test/selection/README.md b/src/scene/test/selection/README.md similarity index 100% rename from src/gldraw/test/selection/README.md rename to src/scene/test/selection/README.md diff --git a/src/gldraw/test/selection/selection_test_utils.cpp b/src/scene/test/selection/selection_test_utils.cpp similarity index 98% rename from src/gldraw/test/selection/selection_test_utils.cpp rename to src/scene/test/selection/selection_test_utils.cpp index e0b319a..3a68764 100644 --- a/src/gldraw/test/selection/selection_test_utils.cpp +++ b/src/scene/test/selection/selection_test_utils.cpp @@ -13,13 +13,13 @@ #include #include -#include "gldraw/feedback/visual_feedback_system.hpp" +#include "scene/feedback/visual_feedback_system.hpp" -#include "gldraw/renderable/sphere.hpp" -#include "gldraw/renderable/point_cloud.hpp" -#include "gldraw/renderable/line_strip.hpp" -#include "gldraw/renderable/cylinder.hpp" -#include "gldraw/renderable/mesh.hpp" +#include "scene/renderable/sphere.hpp" +#include "scene/renderable/point_cloud.hpp" +#include "scene/renderable/line_strip.hpp" +#include "scene/renderable/cylinder.hpp" +#include "scene/renderable/mesh.hpp" namespace quickviz { namespace selection_test_utils { diff --git a/src/gldraw/test/selection/selection_test_utils.hpp b/src/scene/test/selection/selection_test_utils.hpp similarity index 98% rename from src/gldraw/test/selection/selection_test_utils.hpp rename to src/scene/test/selection/selection_test_utils.hpp index 644154e..5a2c605 100644 --- a/src/gldraw/test/selection/selection_test_utils.hpp +++ b/src/scene/test/selection/selection_test_utils.hpp @@ -26,9 +26,9 @@ #include "viewer/panel.hpp" #include "viewer/styling.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/selection_manager.hpp" -#include "gldraw/renderable/grid.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/selection_manager.hpp" +#include "scene/renderable/grid.hpp" namespace quickviz { namespace selection_test_utils { diff --git a/src/gldraw/test/selection/test_bounding_box_selection.cpp b/src/scene/test/selection/test_bounding_box_selection.cpp similarity index 99% rename from src/gldraw/test/selection/test_bounding_box_selection.cpp rename to src/scene/test/selection/test_bounding_box_selection.cpp index 23f077b..0d1c1b8 100644 --- a/src/gldraw/test/selection/test_bounding_box_selection.cpp +++ b/src/scene/test/selection/test_bounding_box_selection.cpp @@ -15,7 +15,7 @@ */ #include "selection_test_utils.hpp" -#include "gldraw/renderable/bounding_box.hpp" +#include "scene/renderable/bounding_box.hpp" #include #include diff --git a/src/gldraw/test/selection/test_comprehensive_selection.cpp b/src/scene/test/selection/test_comprehensive_selection.cpp similarity index 98% rename from src/gldraw/test/selection/test_comprehensive_selection.cpp rename to src/scene/test/selection/test_comprehensive_selection.cpp index 0fa2e91..121298c 100644 --- a/src/gldraw/test/selection/test_comprehensive_selection.cpp +++ b/src/scene/test/selection/test_comprehensive_selection.cpp @@ -16,10 +16,10 @@ */ #include "selection_test_utils.hpp" -#include "gldraw/renderable/sphere.hpp" -#include "gldraw/renderable/point_cloud.hpp" -#include "gldraw/renderable/line_strip.hpp" -#include "gldraw/feedback/visual_feedback_system.hpp" +#include "scene/renderable/sphere.hpp" +#include "scene/renderable/point_cloud.hpp" +#include "scene/renderable/line_strip.hpp" +#include "scene/feedback/visual_feedback_system.hpp" #include "core/event/input_event.hpp" #include #include diff --git a/src/gldraw/test/selection/test_cylinder_selection.cpp b/src/scene/test/selection/test_cylinder_selection.cpp similarity index 99% rename from src/gldraw/test/selection/test_cylinder_selection.cpp rename to src/scene/test/selection/test_cylinder_selection.cpp index 6aa7245..4ee790c 100644 --- a/src/gldraw/test/selection/test_cylinder_selection.cpp +++ b/src/scene/test/selection/test_cylinder_selection.cpp @@ -15,7 +15,7 @@ */ #include "selection_test_utils.hpp" -#include "gldraw/renderable/cylinder.hpp" +#include "scene/renderable/cylinder.hpp" #include #include diff --git a/src/gldraw/test/selection/test_id_buffer_debug.cpp b/src/scene/test/selection/test_id_buffer_debug.cpp similarity index 98% rename from src/gldraw/test/selection/test_id_buffer_debug.cpp rename to src/scene/test/selection/test_id_buffer_debug.cpp index 8bd9848..a883cd2 100644 --- a/src/gldraw/test/selection/test_id_buffer_debug.cpp +++ b/src/scene/test/selection/test_id_buffer_debug.cpp @@ -11,7 +11,7 @@ */ #include "selection_test_utils.hpp" -#include "gldraw/renderable/point_cloud.hpp" +#include "scene/renderable/point_cloud.hpp" #include #include diff --git a/src/gldraw/test/selection/test_line_strip_selection.cpp b/src/scene/test/selection/test_line_strip_selection.cpp similarity index 99% rename from src/gldraw/test/selection/test_line_strip_selection.cpp rename to src/scene/test/selection/test_line_strip_selection.cpp index 6475a93..0ced693 100644 --- a/src/gldraw/test/selection/test_line_strip_selection.cpp +++ b/src/scene/test/selection/test_line_strip_selection.cpp @@ -14,7 +14,7 @@ */ #include "selection_test_utils.hpp" -#include "gldraw/renderable/line_strip.hpp" +#include "scene/renderable/line_strip.hpp" #include #include diff --git a/src/gldraw/test/selection/test_mesh_selection.cpp b/src/scene/test/selection/test_mesh_selection.cpp similarity index 99% rename from src/gldraw/test/selection/test_mesh_selection.cpp rename to src/scene/test/selection/test_mesh_selection.cpp index 3bd7878..254679f 100644 --- a/src/gldraw/test/selection/test_mesh_selection.cpp +++ b/src/scene/test/selection/test_mesh_selection.cpp @@ -14,7 +14,7 @@ */ #include "selection_test_utils.hpp" -#include "gldraw/renderable/mesh.hpp" +#include "scene/renderable/mesh.hpp" #include #include diff --git a/src/gldraw/test/selection/test_object_selection.cpp b/src/scene/test/selection/test_object_selection.cpp similarity index 99% rename from src/gldraw/test/selection/test_object_selection.cpp rename to src/scene/test/selection/test_object_selection.cpp index dff2f99..de67b58 100644 --- a/src/gldraw/test/selection/test_object_selection.cpp +++ b/src/scene/test/selection/test_object_selection.cpp @@ -19,11 +19,11 @@ #include "viewer/panel.hpp" #include "viewer/styling.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/renderable/point_cloud.hpp" -#include "gldraw/renderable/sphere.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/selection_manager.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/renderable/point_cloud.hpp" +#include "scene/renderable/sphere.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/selection_manager.hpp" using namespace quickviz; diff --git a/src/gldraw/test/selection/test_point_cloud_selection.cpp b/src/scene/test/selection/test_point_cloud_selection.cpp similarity index 99% rename from src/gldraw/test/selection/test_point_cloud_selection.cpp rename to src/scene/test/selection/test_point_cloud_selection.cpp index b69d0cc..6d7b2a3 100644 --- a/src/gldraw/test/selection/test_point_cloud_selection.cpp +++ b/src/scene/test/selection/test_point_cloud_selection.cpp @@ -14,7 +14,7 @@ */ #include "selection_test_utils.hpp" -#include "gldraw/renderable/point_cloud.hpp" +#include "scene/renderable/point_cloud.hpp" #include #include diff --git a/src/gldraw/test/selection/test_sphere_selection.cpp b/src/scene/test/selection/test_sphere_selection.cpp similarity index 99% rename from src/gldraw/test/selection/test_sphere_selection.cpp rename to src/scene/test/selection/test_sphere_selection.cpp index b5ce29b..d6add69 100644 --- a/src/gldraw/test/selection/test_sphere_selection.cpp +++ b/src/scene/test/selection/test_sphere_selection.cpp @@ -14,7 +14,7 @@ */ #include "selection_test_utils.hpp" -#include "gldraw/renderable/sphere.hpp" +#include "scene/renderable/sphere.hpp" #include using namespace quickviz; diff --git a/src/gldraw/test/test_camera_raw.cpp b/src/scene/test/test_camera_raw.cpp similarity index 97% rename from src/gldraw/test/test_camera_raw.cpp rename to src/scene/test/test_camera_raw.cpp index 90b3d2e..d33b4e7 100644 --- a/src/gldraw/test/test_camera_raw.cpp +++ b/src/scene/test/test_camera_raw.cpp @@ -15,9 +15,9 @@ #include "glad/glad.h" #include "viewer/window.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/camera.hpp" -#include "gldraw/camera_controller.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/camera.hpp" +#include "scene/camera_controller.hpp" using namespace quickviz; diff --git a/src/gldraw/test/test_canvas_st.cpp b/src/scene/test/test_canvas_st.cpp similarity index 96% rename from src/gldraw/test/test_canvas_st.cpp rename to src/scene/test/test_canvas_st.cpp index 1dc8f6f..d24c4bc 100644 --- a/src/gldraw/test/test_canvas_st.cpp +++ b/src/scene/test/test_canvas_st.cpp @@ -17,11 +17,11 @@ #include "viewer/box.hpp" #include "viewer/viewer.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/triangle.hpp" -#include "gldraw/renderable/coordinate_frame.hpp" -#include "gldraw/renderable/canvas.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/triangle.hpp" +#include "scene/renderable/coordinate_frame.hpp" +#include "scene/renderable/canvas.hpp" using namespace quickviz; namespace fs = std::filesystem; diff --git a/src/gldraw/test/test_coordinate_system.cpp b/src/scene/test/test_coordinate_system.cpp similarity index 96% rename from src/gldraw/test/test_coordinate_system.cpp rename to src/scene/test/test_coordinate_system.cpp index 7fd3a0f..efa21f3 100644 --- a/src/gldraw/test/test_coordinate_system.cpp +++ b/src/scene/test/test_coordinate_system.cpp @@ -17,10 +17,10 @@ #include "viewer/box.hpp" #include "viewer/viewer.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/coordinate_transformer.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/coordinate_frame.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/coordinate_transformer.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/coordinate_frame.hpp" using namespace quickviz; diff --git a/src/gldraw/test/test_font_renderer.cpp b/src/scene/test/test_font_renderer.cpp similarity index 99% rename from src/gldraw/test/test_font_renderer.cpp rename to src/scene/test/test_font_renderer.cpp index b8e6438..ca58cc7 100644 --- a/src/gldraw/test/test_font_renderer.cpp +++ b/src/scene/test/test_font_renderer.cpp @@ -17,7 +17,7 @@ #include #include -#include "gldraw/font_renderer.hpp" +#include "scene/font_renderer.hpp" using namespace quickviz; diff --git a/src/gldraw/test/test_framebuffer.cpp b/src/scene/test/test_framebuffer.cpp similarity index 99% rename from src/gldraw/test/test_framebuffer.cpp rename to src/scene/test/test_framebuffer.cpp index 654ad24..0ec81a2 100644 --- a/src/gldraw/test/test_framebuffer.cpp +++ b/src/scene/test/test_framebuffer.cpp @@ -11,7 +11,7 @@ #include "glad/glad.h" #include "viewer/window.hpp" -#include "gldraw/frame_buffer.hpp" +#include "scene/frame_buffer.hpp" using namespace quickviz; diff --git a/src/gldraw/test/test_layer_system_box.cpp b/src/scene/test/test_layer_system_box.cpp similarity index 97% rename from src/gldraw/test/test_layer_system_box.cpp rename to src/scene/test/test_layer_system_box.cpp index 851b745..1d72f08 100644 --- a/src/gldraw/test/test_layer_system_box.cpp +++ b/src/scene/test/test_layer_system_box.cpp @@ -14,10 +14,10 @@ #include "viewer/viewer.hpp" #include "viewer/box.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/point_cloud.hpp" -#include "../include/gldraw/renderable/details/point_layer_manager.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/point_cloud.hpp" +#include "../include/scene/renderable/details/point_layer_manager.hpp" using namespace quickviz; diff --git a/src/gldraw/test/test_nav_map_rendering.cpp b/src/scene/test/test_nav_map_rendering.cpp similarity index 97% rename from src/gldraw/test/test_nav_map_rendering.cpp rename to src/scene/test/test_nav_map_rendering.cpp index f945f56..bbbef14 100644 --- a/src/gldraw/test/test_nav_map_rendering.cpp +++ b/src/scene/test/test_nav_map_rendering.cpp @@ -17,11 +17,11 @@ #include "viewer/box.hpp" #include "viewer/viewer.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/triangle.hpp" -#include "gldraw/renderable/coordinate_frame.hpp" -#include "gldraw/renderable/canvas.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/triangle.hpp" +#include "scene/renderable/coordinate_frame.hpp" +#include "scene/renderable/canvas.hpp" using namespace quickviz; namespace fs = std::filesystem; diff --git a/src/gldraw/test/test_point_cloud_buffer_strategies.cpp b/src/scene/test/test_point_cloud_buffer_strategies.cpp similarity index 98% rename from src/gldraw/test/test_point_cloud_buffer_strategies.cpp rename to src/scene/test/test_point_cloud_buffer_strategies.cpp index c090eeb..9a7a808 100644 --- a/src/gldraw/test/test_point_cloud_buffer_strategies.cpp +++ b/src/scene/test/test_point_cloud_buffer_strategies.cpp @@ -25,9 +25,9 @@ #include "viewer/box.hpp" #include "viewer/viewer.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/point_cloud.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/point_cloud.hpp" using namespace quickviz; diff --git a/src/gldraw/test/test_point_cloud_realtime.cpp b/src/scene/test/test_point_cloud_realtime.cpp similarity index 97% rename from src/gldraw/test/test_point_cloud_realtime.cpp rename to src/scene/test/test_point_cloud_realtime.cpp index 42b28fd..70fb654 100644 --- a/src/gldraw/test/test_point_cloud_realtime.cpp +++ b/src/scene/test/test_point_cloud_realtime.cpp @@ -20,9 +20,9 @@ #include "viewer/box.hpp" #include "viewer/viewer.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/point_cloud.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/point_cloud.hpp" #include "core/buffer/buffer_registry.hpp" #include "core/buffer/ring_buffer.hpp" diff --git a/src/gldraw/test/test_primitive_drawing.cpp b/src/scene/test/test_primitive_drawing.cpp similarity index 97% rename from src/gldraw/test/test_primitive_drawing.cpp rename to src/scene/test/test_primitive_drawing.cpp index 6c047e9..4d7c3a3 100644 --- a/src/gldraw/test/test_primitive_drawing.cpp +++ b/src/scene/test/test_primitive_drawing.cpp @@ -17,11 +17,11 @@ #include "viewer/box.hpp" #include "viewer/viewer.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/renderable/triangle.hpp" -#include "gldraw/renderable/coordinate_frame.hpp" -#include "gldraw/renderable/canvas.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/renderable/triangle.hpp" +#include "scene/renderable/coordinate_frame.hpp" +#include "scene/renderable/canvas.hpp" using namespace quickviz; namespace fs = std::filesystem; diff --git a/src/gldraw/test/test_shader.cpp b/src/scene/test/test_shader.cpp similarity index 97% rename from src/gldraw/test/test_shader.cpp rename to src/scene/test/test_shader.cpp index eff6a54..4702a07 100644 --- a/src/gldraw/test/test_shader.cpp +++ b/src/scene/test/test_shader.cpp @@ -9,8 +9,8 @@ #include #include "viewer/window.hpp" -#include "gldraw/shader.hpp" -#include "gldraw/shader_program.hpp" +#include "scene/shader.hpp" +#include "scene/shader_program.hpp" using namespace quickviz; diff --git a/src/gldraw/test/tools/test_point_selection_tool.cpp b/src/scene/test/tools/test_point_selection_tool.cpp similarity index 98% rename from src/gldraw/test/tools/test_point_selection_tool.cpp rename to src/scene/test/tools/test_point_selection_tool.cpp index 7c9d658..0dc9e9f 100644 --- a/src/gldraw/test/tools/test_point_selection_tool.cpp +++ b/src/scene/test/tools/test_point_selection_tool.cpp @@ -10,10 +10,10 @@ #include #include -#include "gldraw/tools/point_selection_tool.hpp" -#include "gldraw/tools/interaction_tool.hpp" -#include "gldraw/scene_manager.hpp" -#include "gldraw/renderable/point_cloud.hpp" +#include "scene/tools/point_selection_tool.hpp" +#include "scene/tools/interaction_tool.hpp" +#include "scene/scene_manager.hpp" +#include "scene/renderable/point_cloud.hpp" #include "core/event/input_event.hpp" namespace quickviz { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fad8366..453f764 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -60,7 +60,7 @@ add_executable(test_geometric_primitive_types target_link_libraries(test_geometric_primitive_types PRIVATE gtest_main - gldraw + scene test_utils ) gtest_discover_tests(test_geometric_primitive_types @@ -79,7 +79,7 @@ target_link_libraries(test_renderer_pipeline PRIVATE gtest_main viewer - gldraw + scene test_utils ) gtest_discover_tests(test_renderer_pipeline @@ -112,7 +112,7 @@ target_link_libraries(test_memory_leaks PRIVATE gtest_main viewer - gldraw + scene test_utils ) gtest_discover_tests(test_memory_leaks @@ -132,7 +132,7 @@ if(benchmark_FOUND) PRIVATE benchmark::benchmark viewer - gldraw + scene core ) diff --git a/tests/benchmarks/profile_canvas_performance.cpp b/tests/benchmarks/profile_canvas_performance.cpp index 37ae78f..86a2948 100644 --- a/tests/benchmarks/profile_canvas_performance.cpp +++ b/tests/benchmarks/profile_canvas_performance.cpp @@ -19,7 +19,7 @@ #include #include -#include "gldraw/renderable/canvas.hpp" +#include "scene/renderable/canvas.hpp" using namespace quickviz; using namespace std::chrono; diff --git a/tests/integration/test_renderer_pipeline.cpp b/tests/integration/test_renderer_pipeline.cpp index ebc4940..fe949cd 100644 --- a/tests/integration/test_renderer_pipeline.cpp +++ b/tests/integration/test_renderer_pipeline.cpp @@ -14,12 +14,12 @@ #include #include "viewer/viewer.hpp" -#include "gldraw/gl_scene_panel.hpp" -#include "gldraw/renderable/triangle.hpp" -#include "gldraw/renderable/point_cloud.hpp" -#include "gldraw/renderable/grid.hpp" -#include "gldraw/camera.hpp" -#include "gldraw/camera_controller.hpp" +#include "scene/gl_scene_panel.hpp" +#include "scene/renderable/triangle.hpp" +#include "scene/renderable/point_cloud.hpp" +#include "scene/renderable/grid.hpp" +#include "scene/camera.hpp" +#include "scene/camera_controller.hpp" using namespace quickviz; diff --git a/tests/unit/test_geometric_primitive_types.cpp b/tests/unit/test_geometric_primitive_types.cpp index 9b130a8..819ff98 100644 --- a/tests/unit/test_geometric_primitive_types.cpp +++ b/tests/unit/test_geometric_primitive_types.cpp @@ -13,7 +13,7 @@ #include #include -#include "gldraw/renderable/geometric_primitive.hpp" +#include "scene/renderable/geometric_primitive.hpp" using namespace quickviz; From d44d5d3d609724925c103bd050a47e335864dbfd Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 20:43:36 +0800 Subject: [PATCH 03/22] feat(plot): split ImPlot widgets out of widget/ into new plot/ module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creates src/plot/ as the home for ImPlot (2D) and ImPlot3D (3D) charting inside QuickViz panels. Distinct from `scene` (interactive 3D world); plot is for *charts of data*. Changes: - third_party/imcore: build implot3d sources alongside implot (implot3d/, implot3d_items, implot3d_meshes, implot3d_demo). Now available everywhere imcore is linked. - New src/plot/ module: - plot/plot2d.hpp — re-exports implot/implot.h - plot/plot3d.hpp — re-exports implot3d/implot3d.h - plot/rt_line_plot_widget.hpp + impl — moved from widget/ - plot/details/scrolling_plot_buffer.hpp + impl — moved from widget/ - test_implot_widget moved to plot/test/ - src/widget/CMakeLists.txt: drops the moved files from sources - src/widget/test/CMakeLists.txt: drops the moved test - src/CMakeLists.txt: registers plot/ Removed src/plot/test/test_plot_buffer.cpp — an orphaned, stale test that referenced viewer/data_buffer.hpp (doesn't exist) and namespace quickviz::swviz (doesn't exist). It wasn't registered in the previous CMakeLists either; just dead code that travelled with the move. Build clean. Same 2 pre-existing PCL failures, no new regressions. Library boundary holds. Part of the visualization-first module reorg. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/CMakeLists.txt | 1 + src/plot/CMakeLists.txt | 35 ++++++++++++ .../plot}/details/scrolling_plot_buffer.hpp | 0 src/plot/include/plot/plot2d.hpp | 17 ++++++ src/plot/include/plot/plot3d.hpp | 18 ++++++ .../include/plot}/rt_line_plot_widget.hpp | 2 +- .../src/details/scrolling_plot_buffer.cpp | 2 +- .../src/rt_line_plot_widget.cpp | 2 +- src/plot/test/CMakeLists.txt | 3 + .../test/test_implot_widget.cpp | 2 +- src/widget/CMakeLists.txt | 2 - src/widget/test/CMakeLists.txt | 3 - src/widget/test/test_plot_buffer.cpp | 56 ------------------- third_party/imcore/CMakeLists.txt | 10 +++- 14 files changed, 86 insertions(+), 67 deletions(-) create mode 100644 src/plot/CMakeLists.txt rename src/{widget/include/widget => plot/include/plot}/details/scrolling_plot_buffer.hpp (100%) create mode 100644 src/plot/include/plot/plot2d.hpp create mode 100644 src/plot/include/plot/plot3d.hpp rename src/{widget/include/widget => plot/include/plot}/rt_line_plot_widget.hpp (96%) rename src/{widget => plot}/src/details/scrolling_plot_buffer.cpp (95%) rename src/{widget => plot}/src/rt_line_plot_widget.cpp (99%) create mode 100644 src/plot/test/CMakeLists.txt rename src/{widget => plot}/test/test_implot_widget.cpp (98%) delete mode 100644 src/widget/test/test_plot_buffer.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 491b6c5..3a59b74 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,6 +3,7 @@ add_subdirectory(core) add_subdirectory(viewer) add_subdirectory(widget) add_subdirectory(scene) +add_subdirectory(plot) add_subdirectory(pcl_bridge) find_package(OpenCV QUIET) diff --git a/src/plot/CMakeLists.txt b/src/plot/CMakeLists.txt new file mode 100644 index 0000000..aee9d32 --- /dev/null +++ b/src/plot/CMakeLists.txt @@ -0,0 +1,35 @@ +# find dependency +find_package(Threads REQUIRED) + +# add library +# +# `plot` provides QuickViz wrappers around ImPlot (2D) and ImPlot3D (3D). +# The umbrella headers `plot/plot2d.hpp` and `plot/plot3d.hpp` simply +# re-export the underlying upstream APIs; concrete widgets like +# `rt_line_plot_widget` build on top of them. +add_library(plot + src/details/scrolling_plot_buffer.cpp + src/rt_line_plot_widget.cpp) +target_link_libraries(plot PUBLIC + core + imcore + viewer + Threads::Threads) +target_include_directories(plot PUBLIC + $ + $ + PRIVATE src) + +if(BUILD_TESTING) + add_subdirectory(test) +endif() + +install(TARGETS plot + EXPORT quickvizTargets + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + RUNTIME DESTINATION bin + INCLUDES DESTINATION include) + +install(DIRECTORY include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/src/widget/include/widget/details/scrolling_plot_buffer.hpp b/src/plot/include/plot/details/scrolling_plot_buffer.hpp similarity index 100% rename from src/widget/include/widget/details/scrolling_plot_buffer.hpp rename to src/plot/include/plot/details/scrolling_plot_buffer.hpp diff --git a/src/plot/include/plot/plot2d.hpp b/src/plot/include/plot/plot2d.hpp new file mode 100644 index 0000000..a30e940 --- /dev/null +++ b/src/plot/include/plot/plot2d.hpp @@ -0,0 +1,17 @@ +/* + * @file plot2d.hpp + * @brief Re-export of ImPlot for 2D charting inside QuickViz panels + * + * Use this header when you want to draw a 2D chart (line, scatter, bar, etc.) + * directly into an ImGui window via ImPlot's immediate-mode API. For a + * ready-made real-time line plot widget, see rt_line_plot_widget.hpp. + * + * Copyright (c) 2026 Ruixiang Du (rdu) + */ + +#ifndef QUICKVIZ_PLOT_PLOT2D_HPP +#define QUICKVIZ_PLOT_PLOT2D_HPP + +#include "implot/implot.h" + +#endif // QUICKVIZ_PLOT_PLOT2D_HPP diff --git a/src/plot/include/plot/plot3d.hpp b/src/plot/include/plot/plot3d.hpp new file mode 100644 index 0000000..4d9b7d5 --- /dev/null +++ b/src/plot/include/plot/plot3d.hpp @@ -0,0 +1,18 @@ +/* + * @file plot3d.hpp + * @brief Re-export of ImPlot3D for 3D charting inside QuickViz panels + * + * Use this header when you want to draw a 3D chart (scatter, surface, mesh, + * lines) directly into an ImGui window via ImPlot3D's immediate-mode API. + * Distinct from the `scene` module: `scene` builds a persistent, interactive + * 3D world; `plot3d` draws a 3D *chart* of data inside an ImGui window. + * + * Copyright (c) 2026 Ruixiang Du (rdu) + */ + +#ifndef QUICKVIZ_PLOT_PLOT3D_HPP +#define QUICKVIZ_PLOT_PLOT3D_HPP + +#include "implot3d/implot3d.h" + +#endif // QUICKVIZ_PLOT_PLOT3D_HPP diff --git a/src/widget/include/widget/rt_line_plot_widget.hpp b/src/plot/include/plot/rt_line_plot_widget.hpp similarity index 96% rename from src/widget/include/widget/rt_line_plot_widget.hpp rename to src/plot/include/plot/rt_line_plot_widget.hpp index 1f4eb59..a99aac2 100644 --- a/src/widget/include/widget/rt_line_plot_widget.hpp +++ b/src/plot/include/plot/rt_line_plot_widget.hpp @@ -16,7 +16,7 @@ #include "viewer/panel.hpp" -#include "widget/details/scrolling_plot_buffer.hpp" +#include "plot/details/scrolling_plot_buffer.hpp" namespace quickviz { class RtLinePlotWidget : public Panel { diff --git a/src/widget/src/details/scrolling_plot_buffer.cpp b/src/plot/src/details/scrolling_plot_buffer.cpp similarity index 95% rename from src/widget/src/details/scrolling_plot_buffer.cpp rename to src/plot/src/details/scrolling_plot_buffer.cpp index 502dad9..b44b3db 100644 --- a/src/widget/src/details/scrolling_plot_buffer.cpp +++ b/src/plot/src/details/scrolling_plot_buffer.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2021 Ruixiang Du (rdu) */ -#include "widget/details/scrolling_plot_buffer.hpp" +#include "plot/details/scrolling_plot_buffer.hpp" #include diff --git a/src/widget/src/rt_line_plot_widget.cpp b/src/plot/src/rt_line_plot_widget.cpp similarity index 99% rename from src/widget/src/rt_line_plot_widget.cpp rename to src/plot/src/rt_line_plot_widget.cpp index d2fd1cd..77e68c2 100644 --- a/src/widget/src/rt_line_plot_widget.cpp +++ b/src/plot/src/rt_line_plot_widget.cpp @@ -6,7 +6,7 @@ * @copyright Copyright (c) 2024 Ruixiang Du (rdu) */ -#include "widget/rt_line_plot_widget.hpp" +#include "plot/rt_line_plot_widget.hpp" #include #include diff --git a/src/plot/test/CMakeLists.txt b/src/plot/test/CMakeLists.txt new file mode 100644 index 0000000..d9eb262 --- /dev/null +++ b/src/plot/test/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(test_implot_widget test_implot_widget.cpp) +target_link_libraries(test_implot_widget PRIVATE plot) + diff --git a/src/widget/test/test_implot_widget.cpp b/src/plot/test/test_implot_widget.cpp similarity index 98% rename from src/widget/test/test_implot_widget.cpp rename to src/plot/test/test_implot_widget.cpp index ccf3205..125e475 100644 --- a/src/widget/test/test_implot_widget.cpp +++ b/src/plot/test/test_implot_widget.cpp @@ -15,7 +15,7 @@ #include "core/buffer/ring_buffer.hpp" #include "viewer/viewer.hpp" -#include "widget/rt_line_plot_widget.hpp" +#include "plot/rt_line_plot_widget.hpp" // #include "scene_objects/imtext_panel.hpp" // #include "scene_objects/gl_triangle_scene_object.hpp" diff --git a/src/widget/CMakeLists.txt b/src/widget/CMakeLists.txt index 9b4597f..3e8bfb1 100644 --- a/src/widget/CMakeLists.txt +++ b/src/widget/CMakeLists.txt @@ -25,9 +25,7 @@ add_library(widget # widgets src/details/cairo_context.cpp src/details/cairo_draw.cpp - src/details/scrolling_plot_buffer.cpp src/cairo_widget.cpp - src/rt_line_plot_widget.cpp ${OPENCV_COMP_SRC}) target_link_libraries(widget PUBLIC core diff --git a/src/widget/test/CMakeLists.txt b/src/widget/test/CMakeLists.txt index b20f894..36099c4 100644 --- a/src/widget/test/CMakeLists.txt +++ b/src/widget/test/CMakeLists.txt @@ -1,6 +1,3 @@ -add_executable(test_implot_widget test_implot_widget.cpp) -target_link_libraries(test_implot_widget PRIVATE widget) - add_executable(test_cairo_widget test_cairo_widget.cpp) target_link_libraries(test_cairo_widget PRIVATE widget) diff --git a/src/widget/test/test_plot_buffer.cpp b/src/widget/test/test_plot_buffer.cpp deleted file mode 100644 index 8ae82c1..0000000 --- a/src/widget/test/test_plot_buffer.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * test_plot_buffer.cpp - * - * Created on: Mar 25, 2021 17:41 - * Description: - * - * Copyright (c) 2021 Ruixiang Du (rdu) - */ - -#include - -#include "viewer/viewer.hpp" -#include "viewer/data_buffer.hpp" - -using namespace quickviz::swviz; - -struct ImDraw : public Viewer { - void Update() override { - // do nothing - ImGui::BulletText("Move your mouse to change the data!"); - ImGui::BulletText( - "This example assumes 60 FPS. Higher FPS requires larger buffer " - "size."); - static DataBuffer sdata1; - static DataBuffer sdata2; - - ImVec2 mouse = ImGui::GetMousePos(); - static float t = 0; - t += ImGui::GetIO().DeltaTime; - sdata1.AddPoint(t, mouse.x * 0.0005f); - sdata2.AddPoint(t, mouse.y * 0.0005f); - - static float history = 10.0f; - ImGui::SliderFloat("History", &history, 1, 30, "%.1f s"); - - static ImPlotAxisFlags rt_axis = ImPlotAxisFlags_NoTickLabels; - // ImPlot::SetNextPlotLimitsX(t - history, t, ImGuiCond_Always); - - if (ImPlot::BeginPlot("##Scrolling")) { - ImPlot::PlotShaded("Data 1", &(sdata1[0].x), &(sdata1[0].y), - sdata1.GetSize(), 0, sdata1.GetOffset(), - 2 * sizeof(float)); - ImPlot::PlotLine("Data 2", &(sdata1[0].x), &(sdata1[0].y), - sdata2.GetSize(), sdata2.GetOffset(), 2 * sizeof(float)); - ImPlot::EndPlot(); - - // std::cout << "buffer size: " << sdata1.GetSize() << std::endl; - } - } -}; - -int main(int argc, char *argv[]) { - ImDraw canvas; - canvas.Show(); - return 0; -} \ No newline at end of file diff --git a/third_party/imcore/CMakeLists.txt b/third_party/imcore/CMakeLists.txt index 434b862..499ae25 100644 --- a/third_party/imcore/CMakeLists.txt +++ b/third_party/imcore/CMakeLists.txt @@ -23,7 +23,13 @@ set(IMPLOT_SRC implot/implot_items.cpp implot/implot_demo.cpp ) -add_library(imcore ${IMGUI_CORE_SRC} ${IMGUI_BACKEND_SRC} ${IMPLOT_SRC}) +set(IMPLOT3D_SRC + implot3d/implot3d.cpp + implot3d/implot3d_items.cpp + implot3d/implot3d_meshes.cpp + implot3d/implot3d_demo.cpp +) +add_library(imcore ${IMGUI_CORE_SRC} ${IMGUI_BACKEND_SRC} ${IMPLOT_SRC} ${IMPLOT3D_SRC}) target_link_libraries(imcore PUBLIC glfw OpenGL::GL ${GLFW3_LIBRARY} ${CMAKE_DL_LIBS}) target_compile_definitions(imcore PUBLIC "-DIMGUI_IMPL_OPENGL_LOADER_GL3W") target_include_directories(imcore PUBLIC @@ -42,6 +48,6 @@ install(TARGETS imcore RUNTIME DESTINATION bin INCLUDES DESTINATION include) -install(DIRECTORY imgui implot +install(DIRECTORY imgui implot implot3d DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} PATTERN "*.h*") \ No newline at end of file From 0479241349cab5c872215a632e00c42927a28859 Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 20:46:04 +0800 Subject: [PATCH 04/22] feat(canvas): split Cairo widgets out of widget/ into new canvas/ module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creates src/canvas/ as the home for 2D vector drawing and immediate widgets backed by Cairo. Use this module for custom 2D figures, plots-as-pictures, and annotations rendered into a panel as a texture. Files moved widget/ → canvas/: - include/widget/cairo_widget.hpp → include/canvas/cairo_widget.hpp - include/widget/details/cairo_draw.hpp → include/canvas/details/cairo_draw.hpp - include/widget/details/cairo_context.hpp → include/canvas/details/cairo_context.hpp - src/cairo_widget.cpp + src/details/cairo_*.cpp → canvas/src/... - test_cairo_widget.cpp → canvas/test/ src/widget/CMakeLists.txt: drops Cairo-related sources, deps, and the PkgConfig::Cairo / Fontconfig links. Module now contains only OpenCV widgets and is set up to early-return if OpenCV isn't found, since nothing else remains. The directory will be deleted entirely once those move to src/image/ in the next commit. Removed three orphaned, never-registered Cairo tests (test_cairo_normalize, test_cairo_draw, test_cairo_context). They referenced an extinct namespace (quickviz::swviz) and headers that don't exist; 5-year-old dead code. Build clean. Same 2 pre-existing PCL test failures, no new regressions. Library boundary holds. Part of the visualization-first module reorg. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/CMakeLists.txt | 1 + src/canvas/CMakeLists.txt | 43 +++++++ .../include/canvas}/cairo_widget.hpp | 4 +- .../include/canvas}/details/cairo_context.hpp | 0 .../include/canvas}/details/cairo_draw.hpp | 0 src/{widget => canvas}/src/cairo_widget.cpp | 2 +- .../src/details/cairo_context.cpp | 2 +- .../src/details/cairo_draw.cpp | 2 +- src/canvas/test/CMakeLists.txt | 2 + .../test/test_cairo_widget.cpp | 2 +- src/widget/CMakeLists.txt | 16 ++- src/widget/test/CMakeLists.txt | 3 - src/widget/test/test_cairo_context.cpp | 80 ------------ src/widget/test/test_cairo_draw.cpp | 119 ------------------ src/widget/test/test_cairo_normalize.cpp | 87 ------------- 15 files changed, 59 insertions(+), 304 deletions(-) create mode 100644 src/canvas/CMakeLists.txt rename src/{widget/include/widget => canvas/include/canvas}/cairo_widget.hpp (94%) rename src/{widget/include/widget => canvas/include/canvas}/details/cairo_context.hpp (100%) rename src/{widget/include/widget => canvas/include/canvas}/details/cairo_draw.hpp (100%) rename src/{widget => canvas}/src/cairo_widget.cpp (98%) rename src/{widget => canvas}/src/details/cairo_context.cpp (98%) rename src/{widget => canvas}/src/details/cairo_draw.cpp (99%) create mode 100644 src/canvas/test/CMakeLists.txt rename src/{widget => canvas}/test/test_cairo_widget.cpp (98%) delete mode 100644 src/widget/test/test_cairo_context.cpp delete mode 100644 src/widget/test/test_cairo_draw.cpp delete mode 100644 src/widget/test/test_cairo_normalize.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3a59b74..89c4d15 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,6 +4,7 @@ add_subdirectory(viewer) add_subdirectory(widget) add_subdirectory(scene) add_subdirectory(plot) +add_subdirectory(canvas) add_subdirectory(pcl_bridge) find_package(OpenCV QUIET) diff --git a/src/canvas/CMakeLists.txt b/src/canvas/CMakeLists.txt new file mode 100644 index 0000000..9128dc3 --- /dev/null +++ b/src/canvas/CMakeLists.txt @@ -0,0 +1,43 @@ +# find dependency +find_package(PkgConfig REQUIRED) +pkg_check_modules(Cairo REQUIRED IMPORTED_TARGET cairo) +pkg_check_modules(Fontconfig REQUIRED IMPORTED_TARGET fontconfig) + +find_package(Threads REQUIRED) +find_package(OpenGL REQUIRED) + +# add library +# +# `canvas` provides 2D vector drawing and immediate widgets backed by +# Cairo. Use this module for custom 2D figures, plots-as-pictures, and +# annotations rendered into a panel as a texture. +add_library(canvas + src/details/cairo_context.cpp + src/details/cairo_draw.cpp + src/cairo_widget.cpp) +target_link_libraries(canvas PUBLIC + core + imcore + viewer + PkgConfig::Cairo + PkgConfig::Fontconfig + Threads::Threads + OpenGL::GL) +target_include_directories(canvas PUBLIC + $ + $ + PRIVATE src) + +if(BUILD_TESTING) + add_subdirectory(test) +endif() + +install(TARGETS canvas + EXPORT quickvizTargets + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + RUNTIME DESTINATION bin + INCLUDES DESTINATION include) + +install(DIRECTORY include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/src/widget/include/widget/cairo_widget.hpp b/src/canvas/include/canvas/cairo_widget.hpp similarity index 94% rename from src/widget/include/widget/cairo_widget.hpp rename to src/canvas/include/canvas/cairo_widget.hpp index 2258f09..020ae34 100644 --- a/src/widget/include/widget/cairo_widget.hpp +++ b/src/canvas/include/canvas/cairo_widget.hpp @@ -21,8 +21,8 @@ #include "imgui.h" #include "viewer/panel.hpp" -#include "widget/details/cairo_context.hpp" -#include "widget/details/cairo_draw.hpp" +#include "canvas/details/cairo_context.hpp" +#include "canvas/details/cairo_draw.hpp" namespace quickviz { class CairoWidget : public Panel { diff --git a/src/widget/include/widget/details/cairo_context.hpp b/src/canvas/include/canvas/details/cairo_context.hpp similarity index 100% rename from src/widget/include/widget/details/cairo_context.hpp rename to src/canvas/include/canvas/details/cairo_context.hpp diff --git a/src/widget/include/widget/details/cairo_draw.hpp b/src/canvas/include/canvas/details/cairo_draw.hpp similarity index 100% rename from src/widget/include/widget/details/cairo_draw.hpp rename to src/canvas/include/canvas/details/cairo_draw.hpp diff --git a/src/widget/src/cairo_widget.cpp b/src/canvas/src/cairo_widget.cpp similarity index 98% rename from src/widget/src/cairo_widget.cpp rename to src/canvas/src/cairo_widget.cpp index f79b477..0546fea 100644 --- a/src/widget/src/cairo_widget.cpp +++ b/src/canvas/src/cairo_widget.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2021 Ruixiang Du (rdu) */ -#include "widget/cairo_widget.hpp" +#include "canvas/cairo_widget.hpp" #include #include diff --git a/src/widget/src/details/cairo_context.cpp b/src/canvas/src/details/cairo_context.cpp similarity index 98% rename from src/widget/src/details/cairo_context.cpp rename to src/canvas/src/details/cairo_context.cpp index 94cd2ec..d423481 100644 --- a/src/widget/src/details/cairo_context.cpp +++ b/src/canvas/src/details/cairo_context.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2021 Ruixiang Du (rdu) */ -#include "widget/details/cairo_context.hpp" +#include "canvas/details/cairo_context.hpp" #include diff --git a/src/widget/src/details/cairo_draw.cpp b/src/canvas/src/details/cairo_draw.cpp similarity index 99% rename from src/widget/src/details/cairo_draw.cpp rename to src/canvas/src/details/cairo_draw.cpp index d121e04..783e075 100644 --- a/src/widget/src/details/cairo_draw.cpp +++ b/src/canvas/src/details/cairo_draw.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2021 Ruixiang Du (rdu) */ -#include "widget/details/cairo_draw.hpp" +#include "canvas/details/cairo_draw.hpp" #include diff --git a/src/canvas/test/CMakeLists.txt b/src/canvas/test/CMakeLists.txt new file mode 100644 index 0000000..d0aadc8 --- /dev/null +++ b/src/canvas/test/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(test_cairo_widget test_cairo_widget.cpp) +target_link_libraries(test_cairo_widget PRIVATE canvas) diff --git a/src/widget/test/test_cairo_widget.cpp b/src/canvas/test/test_cairo_widget.cpp similarity index 98% rename from src/widget/test/test_cairo_widget.cpp rename to src/canvas/test/test_cairo_widget.cpp index c27f225..12d5b8c 100644 --- a/src/widget/test/test_cairo_widget.cpp +++ b/src/canvas/test/test_cairo_widget.cpp @@ -11,7 +11,7 @@ #include #include "viewer/viewer.hpp" -#include "widget/cairo_widget.hpp" +#include "canvas/cairo_widget.hpp" #ifndef M_PI #define M_PI 3.14159265358979323846 diff --git a/src/widget/CMakeLists.txt b/src/widget/CMakeLists.txt index 3e8bfb1..5636370 100644 --- a/src/widget/CMakeLists.txt +++ b/src/widget/CMakeLists.txt @@ -1,7 +1,6 @@ -# find dependency -find_package(PkgConfig REQUIRED) -pkg_check_modules(Cairo REQUIRED IMPORTED_TARGET cairo) -pkg_check_modules(Fontconfig REQUIRED IMPORTED_TARGET fontconfig) +# NOTE: this module is being dissolved. Cairo bits moved to src/canvas/; +# OpenCV bits will move to src/image/ in a follow-up commit. After that +# this entire directory will be removed. find_package(Threads REQUIRED) find_package(OpenGL REQUIRED) @@ -20,18 +19,17 @@ else() message(STATUS "OpenCV not found") endif() +if(NOT OpenCV_FOUND) + return() # nothing left to build in widget/ when OpenCV is absent +endif() + # add library add_library(widget - # widgets - src/details/cairo_context.cpp - src/details/cairo_draw.cpp - src/cairo_widget.cpp ${OPENCV_COMP_SRC}) target_link_libraries(widget PUBLIC core imcore viewer stb - PkgConfig::Cairo PkgConfig::Fontconfig Threads::Threads OpenGL::GL ${OPENCV_COMP_LIBS}) diff --git a/src/widget/test/CMakeLists.txt b/src/widget/test/CMakeLists.txt index 36099c4..34d4486 100644 --- a/src/widget/test/CMakeLists.txt +++ b/src/widget/test/CMakeLists.txt @@ -1,6 +1,3 @@ -add_executable(test_cairo_widget test_cairo_widget.cpp) -target_link_libraries(test_cairo_widget PRIVATE widget) - if(ENABLE_OPENCV_SUPPORT) add_executable(test_cv_image_widget test_cv_image_widget.cpp) target_link_libraries(test_cv_image_widget PRIVATE widget) diff --git a/src/widget/test/test_cairo_context.cpp b/src/widget/test/test_cairo_context.cpp deleted file mode 100644 index 7130bc9..0000000 --- a/src/widget/test/test_cairo_context.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - * test_cairo_canvas.cpp - * - * Created on: Dec 03, 2020 21:27 - * Description: - * - * Copyright (c) 2020 Ruixiang Du - */ - -#include - -#include "viewer/viewer.hpp" -#include "viewer/cairo_widget.hpp" - -using namespace quickviz::swviz; - -void Paint(cairo_t* cr); - -struct DrawArc : public Viewer { - DrawArc() { ctx1_ = std::make_shared(320, 240); } - - std::shared_ptr ctx1_; - - void Update() override { - ImVec2 panel_size = {ImGui::GetIO().DisplaySize.x / 2.0f, - ImGui::GetIO().DisplaySize.y / 2.0f}; - - // show on imgui - { - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowSize(panel_size); - - ImGui::Begin("Cairo Canvas 1", NULL, - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoBringToFrontOnFocus | - ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoScrollbar); - - // do paint with cairo - ctx1_->Draw(Paint); - - // GLuint image = ctx1_->RenderToGlTexture(); - // ImGui::Image((void*)(intptr_t)image, ImGui::GetContentRegionAvail()); - - ImGui::End(); - } - } -}; - -void Paint(cairo_t* cr) { - cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 0.6); - cairo_paint(cr); - - double x = 25.6, y = 128.0; - double x1 = 102.4, y1 = 230.4, x2 = 153.6, y2 = 25.6, x3 = 230.4, y3 = 128.0; - - cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 0.6); - - cairo_move_to(cr, x, y); - cairo_curve_to(cr, x1, y1, x2, y2, x3, y3); - - cairo_set_line_width(cr, 10.0); - cairo_stroke(cr); - - cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.6); - cairo_set_line_width(cr, 6.0); - cairo_move_to(cr, x, y); - cairo_line_to(cr, x1, y1); - cairo_move_to(cr, x2, y2); - cairo_line_to(cr, x3, y3); - cairo_stroke(cr); -} - -int main(int argc, const char* argv[]) { - DrawArc cc; - cc.Show(); - - return 0; -} \ No newline at end of file diff --git a/src/widget/test/test_cairo_draw.cpp b/src/widget/test/test_cairo_draw.cpp deleted file mode 100644 index 50f9bb3..0000000 --- a/src/widget/test/test_cairo_draw.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/* - * test_imgui.cpp - * - * Created on: Mar 04, 2021 15:02 - * Description: - * - * Copyright (c) 2021 Ruixiang Du (rdu) - */ - -#include - -#include "viewer/viewer.hpp" -#include "viewer/cairo_widget.hpp" -#include "viewer/cairo_draw.hpp" - -using namespace quickviz::swviz; - -const std::string img_file = "../data/screenshots/sampling/rrts.png"; - -class MyWin : public Viewer { - public: - MyWin(std::string title = "Canvas", uint32_t width = 1080, - uint32_t height = 720) - : Viewer(title, width, height), cairo_panel_{width, height} { - cairo_panel_.LoadImage(img_file); - } - - bool show_demo_window = true; - bool show_another_window = false; - ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); - double b = 0.0001; - - void Update() override { - ShowCairoPanel(); - ShowImPanel(); - } - - void Paint(cairo_t* cr) { - DrawPoint(cr, {860, 200}); - DrawPoint(cr, {1060, 200}, 10, {1, 0.2, 0.2, 0.6}); - - DrawLine(cr, {860, 150}, {1060, 150}); - DrawLine(cr, {860, 250}, {1060, 250}, 2, {1, 0.2, 0.2, 0.6}); - - DrawCircle(cr, {960, 540}, 30); - DrawCircle(cr, {960, 540}, 50, 5, {1, 0.2, 0.2, 0.6}); - - DrawRing(cr, {960, 540}, 100, 130, 0, M_PI / 4.0); - DrawRing(cr, {960, 540}, 140, 180, 0, M_PI / 4.0, 5, colors[GREEN]); - - DrawRing(cr, {960, 540}, 100, 200, M_PI, M_PI * 1.5f, 5, colors[YELLOW], - true); - DrawRing(cr, {960, 540}, 140, 220, M_PI / 2.0, 2 * M_PI / 3.0, 5, - colors[GREEN], true); - DrawRing(cr, {960, 540}, 140, 220, M_PI / 2.0, 5 * M_PI / 6.0, 5, - colors[PURPLE], false); - - DrawArc(cr, {600, 800}, 60, M_PI, 2 * M_PI / 4.0); - DrawArc(cr, {600, 800}, 70, M_PI / 4.0, M_PI, 5, {1, 0.2, 0.2, 0.6}); - - DrawArcSector(cr, {1060, 800}, 60, 0, -M_PI / 4.0); - DrawArcSector(cr, {1060, 800}, 80, M_PI / 4.0, M_PI, 5, {1, 0.2, 0.2, 0.6}); - DrawArcSector(cr, {1060, 800}, 85, 5.0 * M_PI / 4.0, 6.0 * M_PI / 4.0, 5, - {1, 0.2, 0.2, 0.3}, true); - - DrawRectangle(cr, {400, 400}, {500, 600}); - DrawRectangle(cr, {550, 400}, {600, 600}, 5, colors[MAGENTA]); - DrawRectangle(cr, {650, 400}, {700, 600}, 5, colors[CYAN], true); - - for (int i = 0; i < COLOR_LAST; ++i) { - DrawLine(cr, {200.0f, 300.0f + 20 * i}, {250.0f, 300.0f + 20 * i}, 10, - colors[i]); - } - } - - void ShowCairoPanel() { - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowSize(ImVec2(window_->GetWidth(), window_->GetHeight())); - - ImGui::Begin("Cairo Canvas", NULL, - ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoBringToFrontOnFocus | - ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoBackground); - - int width, height; - glfwGetWindowSize(window_->GetGlfwWindow(), &width, &height); - cairo_panel_.Resize(width, height); - - cairo_panel_.Fill(); - // cairo_panel_.Draw(img_file, 0, 0, M_PI / 4.0f); - cairo_panel_.Draw(std::bind(&MyWin::Paint, this, std::placeholders::_1)); - // cairo_panel_.DrawText("Hello World, Canvas", 100, 100); - - cairo_panel_.Render(); - - ImGui::End(); - } - - void ShowImPanel() { - ImGui::Begin("ImPanel"); - ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", - 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); - ImGui::End(); - } - - private: - CairoWidget cairo_panel_; -}; - -int main(int argc, char* argv[]) { - // MyWin win(1280, 720); - MyWin win("cairo_draw", 1920, 1080); - // MyWin win(1080, 720); - - win.Show(); - return 0; -} diff --git a/src/widget/test/test_cairo_normalize.cpp b/src/widget/test/test_cairo_normalize.cpp deleted file mode 100644 index c8afcf4..0000000 --- a/src/widget/test/test_cairo_normalize.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/* - * test_imgui.cpp - * - * Created on: Mar 04, 2021 15:02 - * Description: - * - * Copyright (c) 2021 Ruixiang Du (rdu) - */ - -#include - -#include "viewer/viewer.hpp" -#include "viewer/cairo_widget.hpp" -#include "viewer/cairo_draw.hpp" - -using namespace quickviz::swviz; - -const std::string img_file = "../image/fish.png"; - -class MyWin : public Viewer { - public: - MyWin(std::string title, uint32_t width, uint32_t height) - : Viewer(title, width, height), cairo_panel_{width, height, true} { - cairo_panel_.LoadImage(img_file); - } - - bool show_demo_window = true; - bool show_another_window = false; - ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); - double b = 0.0001; - - void Update() override { - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowSize(ImVec2(window_->GetWidth(), window_->GetHeight())); - - ImGui::Begin("Cairo Canvas", NULL, - ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoBringToFrontOnFocus | - ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoBackground); - - ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", - 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); - - int width, height; - glfwGetWindowSize(window_->GetGlfwWindow(), &width, &height); - cairo_panel_.Resize(window_->GetWidth(), window_->GetHeight()); - - cairo_panel_.Fill(); - cairo_panel_.Draw(std::bind(&MyWin::Paint, this, std::placeholders::_1)); - - cairo_panel_.DrawText("1.5 m/s", 0.5, 0.5, 0.0, {0.5, 0.5, 0.5, 1}, 0.0140, - 0.05); - - cairo_panel_.Render(); - - ImGui::End(); - } - - void Paint(cairo_t* cr) { - float ratio = - window_->GetWidth() / static_cast(window_->GetHeight()); - float pos_x = 0.5 * cairo_panel_.GetAspectRatio(); - float pos_y = 0.5; - - // std::cout << "first: " << ratio - // << " , second: " << cairo_panel_.GetAspectRatio() << std::endl; - - DrawPoint(cr, {pos_x, pos_y}, 0.01, {1, 0.2, 0.2, 0.6}); - DrawCircle(cr, {pos_x, pos_y}, 0.3, 0.002, {1, 0.2, 0.2, 0.6}); - DrawRectangle(cr, {pos_x - 0.2f, pos_y - 0.2f}, - {pos_x + 0.2f, pos_y + 0.2f}, 0.002); - } - - private: - CairoWidget cairo_panel_; -}; - -int main(int argc, char* argv[]) { - // MyWin win(1280, 720); - // MyWin win(1920, 1080); - MyWin win("cairo_normalize", 1080, 720); - - win.Show(); - return 0; -} From fcb47c5b09bc631aebae426e92dd62c750c4cd16 Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 20:48:34 +0800 Subject: [PATCH 05/22] feat(image): merge cvdraw + widget's OpenCV widgets into new image/ module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creates src/image/ as the home for image display and annotation backed by OpenCV. Use this module for camera frames, debug images, or annotated cv::Mat data inside a panel. Distinct from canvas/ (2D vector drawing) and plot/ (data charts). Files moved cvdraw/ → image/: - cvdraw/include/cvdraw/cvdraw.hpp → image/include/image/image.hpp - cv_io, cv_canvas, cv_colors, color_maps {hpp,cpp,test} → image/ Files moved widget/ → image/: - cv_image_widget {hpp,cpp,test} → image/ - buffered_cv_image_widget {hpp,cpp,test} → image/ - details/image_utils {hpp,cpp} → image/details/ src/widget/ and src/cvdraw/ are now empty and deleted entirely. Their roles distributed to canvas/, plot/, and image/ — each module now has one job. src/CMakeLists.txt rewritten: drops widget/ and cvdraw/ entries, adds image/, and includes a top-of-file map of which module does what. The image/ module gates on OpenCV with an early `return()` if absent — so the rest of the build proceeds normally on systems without OpenCV. The cv_ prefix on file/class names is preserved as an "implementation backend" marker, parallel to the Gl prefix in scene/. If a future backend appears (raw libpng, etc.), they can coexist without conflict. Removed three orphan demo PNGs (basic_colors.png, draw_demo.png, function_plot_demo.png) — checked-in demo outputs not referenced by any code. Build clean. Same 2 pre-existing PCL failures, no new regressions. Library boundary holds. Closes the structural part of the visualization-first reorg. Final module layout: core, viewer, scene, plot, canvas, image, pcl_bridge. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/CMakeLists.txt | 21 ++++--- src/cvdraw/CMakeLists.txt | 28 --------- src/cvdraw/basic_colors.png | Bin 78865 -> 0 bytes src/cvdraw/draw_demo.png | Bin 59072 -> 0 bytes src/cvdraw/function_plot_demo.png | Bin 124377 -> 0 bytes src/cvdraw/test/CMakeLists.txt | 15 ----- src/image/CMakeLists.txt | 49 ++++++++++++++++ .../image}/buffered_cv_image_widget.hpp | 0 .../include/image}/color_maps.hpp | 0 .../include/image}/cv_canvas.hpp | 2 +- .../include/image}/cv_colors.hpp | 0 .../include/image}/cv_image_widget.hpp | 0 .../cvdraw => image/include/image}/cv_io.hpp | 6 +- .../include/image}/details/image_utils.hpp | 0 .../include/image/image.hpp} | 8 +-- .../src/buffered_cv_image_widget.cpp | 4 +- src/{cvdraw => image}/src/color_maps.cpp | 2 +- src/{cvdraw => image}/src/cv_canvas.cpp | 2 +- src/{cvdraw => image}/src/cv_colors.cpp | 2 +- src/{widget => image}/src/cv_image_widget.cpp | 4 +- src/{cvdraw => image}/src/cv_io.cpp | 2 +- .../src/details/image_utils.cpp | 2 +- src/image/test/CMakeLists.txt | 17 ++++++ .../test/test_buffered_cv_image_widget.cpp | 2 +- src/{cvdraw => image}/test/test_cv_canvas.cpp | 2 +- .../test/test_cv_drawfunc.cpp | 2 +- .../test/test_cv_drawmode.cpp | 2 +- .../test/test_cv_image_widget.cpp | 2 +- src/{cvdraw => image}/test/test_image_rw.cpp | 2 +- src/widget/CMakeLists.txt | 53 ------------------ src/widget/test/CMakeLists.txt | 7 --- 31 files changed, 103 insertions(+), 133 deletions(-) delete mode 100644 src/cvdraw/CMakeLists.txt delete mode 100644 src/cvdraw/basic_colors.png delete mode 100644 src/cvdraw/draw_demo.png delete mode 100644 src/cvdraw/function_plot_demo.png delete mode 100644 src/cvdraw/test/CMakeLists.txt create mode 100644 src/image/CMakeLists.txt rename src/{widget/include/widget => image/include/image}/buffered_cv_image_widget.hpp (100%) rename src/{cvdraw/include/cvdraw => image/include/image}/color_maps.hpp (100%) rename src/{cvdraw/include/cvdraw => image/include/image}/cv_canvas.hpp (99%) rename src/{cvdraw/include/cvdraw => image/include/image}/cv_colors.hpp (100%) rename src/{widget/include/widget => image/include/image}/cv_image_widget.hpp (100%) rename src/{cvdraw/include/cvdraw => image/include/image}/cv_io.hpp (89%) rename src/{widget/include/widget => image/include/image}/details/image_utils.hpp (100%) rename src/{cvdraw/include/cvdraw/cvdraw.hpp => image/include/image/image.hpp} (59%) rename src/{widget => image}/src/buffered_cv_image_widget.cpp (96%) rename src/{cvdraw => image}/src/color_maps.cpp (97%) rename src/{cvdraw => image}/src/cv_canvas.cpp (99%) rename src/{cvdraw => image}/src/cv_colors.cpp (98%) rename src/{widget => image}/src/cv_image_widget.cpp (96%) rename src/{cvdraw => image}/src/cv_io.cpp (97%) rename src/{widget => image}/src/details/image_utils.cpp (96%) create mode 100644 src/image/test/CMakeLists.txt rename src/{widget => image}/test/test_buffered_cv_image_widget.cpp (97%) rename src/{cvdraw => image}/test/test_cv_canvas.cpp (95%) rename src/{cvdraw => image}/test/test_cv_drawfunc.cpp (97%) rename src/{cvdraw => image}/test/test_cv_drawmode.cpp (99%) rename src/{widget => image}/test/test_cv_image_widget.cpp (97%) rename src/{cvdraw => image}/test/test_image_rw.cpp (91%) delete mode 100644 src/widget/CMakeLists.txt delete mode 100644 src/widget/test/CMakeLists.txt diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 89c4d15..c15536a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,13 +1,20 @@ -# core modules +# Library modules — visualization-first. +# +# Module layout (one job each): +# core — events, buffers, threading helpers +# viewer — window + panels + layout +# scene — interactive 3D scene rendering (OpenGL) +# plot — data charts (ImPlot 2D + ImPlot3D) +# canvas — 2D vector drawing (Cairo) +# image — image display & annotation (OpenCV, optional) +# pcl_bridge — PCL adapter +# +# Editor / undo / project-files / app frameworks are NOT in scope here; +# they live in sample/ on top of this library. CI enforces the boundary. add_subdirectory(core) add_subdirectory(viewer) -add_subdirectory(widget) add_subdirectory(scene) add_subdirectory(plot) add_subdirectory(canvas) +add_subdirectory(image) # internally returns early if OpenCV is missing add_subdirectory(pcl_bridge) - -find_package(OpenCV QUIET) -if (OpenCV_FOUND) - add_subdirectory(cvdraw) -endif () diff --git a/src/cvdraw/CMakeLists.txt b/src/cvdraw/CMakeLists.txt deleted file mode 100644 index 79bd220..0000000 --- a/src/cvdraw/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -## Dependency libraries -find_package(OpenCV REQUIRED) - -## Add libraries -add_library(cvdraw src/cv_colors.cpp - src/color_maps.cpp - src/cv_canvas.cpp - src/cv_io.cpp) -target_link_libraries(cvdraw ${OpenCV_LIBS}) -target_include_directories(cvdraw PUBLIC - $ - $ - PRIVATE src) - -# Add executables -if(BUILD_TESTING) - add_subdirectory(test) -endif() - -install(TARGETS cvdraw - EXPORT quickvizTargets - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - RUNTIME DESTINATION bin - INCLUDES DESTINATION include) - -install(DIRECTORY include/ - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) \ No newline at end of file diff --git a/src/cvdraw/basic_colors.png b/src/cvdraw/basic_colors.png deleted file mode 100644 index bb6056ed604792a6c11e54f4878306f1283ca10a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78865 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 ACjbBd diff --git a/src/cvdraw/draw_demo.png b/src/cvdraw/draw_demo.png deleted file mode 100644 index dcff951b062d9163e110f5da0f3712932393d3dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59072 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 k4NN=<-`P1!;v=f2wfn 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 D8}r?8 diff --git a/src/cvdraw/test/CMakeLists.txt b/src/cvdraw/test/CMakeLists.txt deleted file mode 100644 index c3ba37b..0000000 --- a/src/cvdraw/test/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -# Dependency libraries -#find_package(LIBRARY_NAME REQUIRED) - -## tests -add_executable(test_image_rw test_image_rw.cpp) -target_link_libraries(test_image_rw PUBLIC cvdraw) - -add_executable(test_cv_canvas test_cv_canvas.cpp) -target_link_libraries(test_cv_canvas PUBLIC cvdraw) - -add_executable(test_cv_drawmode test_cv_drawmode.cpp) -target_link_libraries(test_cv_drawmode PUBLIC cvdraw) - -add_executable(test_cv_drawfunc test_cv_drawfunc.cpp) -target_link_libraries(test_cv_drawfunc PUBLIC cvdraw) \ No newline at end of file diff --git a/src/image/CMakeLists.txt b/src/image/CMakeLists.txt new file mode 100644 index 0000000..68c3086 --- /dev/null +++ b/src/image/CMakeLists.txt @@ -0,0 +1,49 @@ +# `image` provides image display and annotation widgets backed by OpenCV. +# Use this module to show camera frames, debug images, or annotated cv::Mat +# data inside a panel. For 2D vector drawing or charts, see canvas/ or plot/. +# +# OpenCV is required. If absent, the module is skipped quietly. + +find_package(OpenCV QUIET) +if(NOT OpenCV_FOUND) + message(STATUS "OpenCV not found — skipping image/ module") + return() +endif() + +find_package(Threads REQUIRED) +find_package(OpenGL REQUIRED) + +add_library(image + src/cv_io.cpp + src/cv_canvas.cpp + src/cv_colors.cpp + src/color_maps.cpp + src/details/image_utils.cpp + src/cv_image_widget.cpp + src/buffered_cv_image_widget.cpp) +target_link_libraries(image PUBLIC + core + imcore + viewer + stb + Threads::Threads + OpenGL::GL + ${OpenCV_LIBS}) +target_include_directories(image PUBLIC + $ + $ + PRIVATE src ${OpenCV_INCLUDE_DIRS}) + +if(BUILD_TESTING) + add_subdirectory(test) +endif() + +install(TARGETS image + EXPORT quickvizTargets + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + RUNTIME DESTINATION bin + INCLUDES DESTINATION include) + +install(DIRECTORY include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/src/widget/include/widget/buffered_cv_image_widget.hpp b/src/image/include/image/buffered_cv_image_widget.hpp similarity index 100% rename from src/widget/include/widget/buffered_cv_image_widget.hpp rename to src/image/include/image/buffered_cv_image_widget.hpp diff --git a/src/cvdraw/include/cvdraw/color_maps.hpp b/src/image/include/image/color_maps.hpp similarity index 100% rename from src/cvdraw/include/cvdraw/color_maps.hpp rename to src/image/include/image/color_maps.hpp diff --git a/src/cvdraw/include/cvdraw/cv_canvas.hpp b/src/image/include/image/cv_canvas.hpp similarity index 99% rename from src/cvdraw/include/cvdraw/cv_canvas.hpp rename to src/image/include/image/cv_canvas.hpp index 81e6af7..65e5d10 100644 --- a/src/cvdraw/include/cvdraw/cv_canvas.hpp +++ b/src/image/include/image/cv_canvas.hpp @@ -18,7 +18,7 @@ #include -#include "cvdraw/cv_colors.hpp" +#include "image/cv_colors.hpp" namespace quickviz { struct CPoint { diff --git a/src/cvdraw/include/cvdraw/cv_colors.hpp b/src/image/include/image/cv_colors.hpp similarity index 100% rename from src/cvdraw/include/cvdraw/cv_colors.hpp rename to src/image/include/image/cv_colors.hpp diff --git a/src/widget/include/widget/cv_image_widget.hpp b/src/image/include/image/cv_image_widget.hpp similarity index 100% rename from src/widget/include/widget/cv_image_widget.hpp rename to src/image/include/image/cv_image_widget.hpp diff --git a/src/cvdraw/include/cvdraw/cv_io.hpp b/src/image/include/image/cv_io.hpp similarity index 89% rename from src/cvdraw/include/cvdraw/cv_io.hpp rename to src/image/include/image/cv_io.hpp index 6e8c54d..4c3036d 100644 --- a/src/cvdraw/include/cvdraw/cv_io.hpp +++ b/src/image/include/image/cv_io.hpp @@ -14,9 +14,9 @@ #include -#include "cvdraw/cv_colors.hpp" -#include "cvdraw/color_maps.hpp" -#include "cvdraw/cv_canvas.hpp" +#include "image/cv_colors.hpp" +#include "image/color_maps.hpp" +#include "image/cv_canvas.hpp" namespace quickviz { namespace CvIO { diff --git a/src/widget/include/widget/details/image_utils.hpp b/src/image/include/image/details/image_utils.hpp similarity index 100% rename from src/widget/include/widget/details/image_utils.hpp rename to src/image/include/image/details/image_utils.hpp diff --git a/src/cvdraw/include/cvdraw/cvdraw.hpp b/src/image/include/image/image.hpp similarity index 59% rename from src/cvdraw/include/cvdraw/cvdraw.hpp rename to src/image/include/image/image.hpp index bf0f172..5e12597 100644 --- a/src/cvdraw/include/cvdraw/cvdraw.hpp +++ b/src/image/include/image/image.hpp @@ -10,9 +10,9 @@ #ifndef CV_DRAW_HPP #define CV_DRAW_HPP -#include "cvdraw/cv_io.hpp" -#include "cvdraw/cv_canvas.hpp" -#include "cvdraw/cv_colors.hpp" -#include "cvdraw/color_maps.hpp" +#include "image/cv_io.hpp" +#include "image/cv_canvas.hpp" +#include "image/cv_colors.hpp" +#include "image/color_maps.hpp" #endif /* CV_DRAW_HPP */ diff --git a/src/widget/src/buffered_cv_image_widget.cpp b/src/image/src/buffered_cv_image_widget.cpp similarity index 96% rename from src/widget/src/buffered_cv_image_widget.cpp rename to src/image/src/buffered_cv_image_widget.cpp index c7fc144..28b8464 100644 --- a/src/widget/src/buffered_cv_image_widget.cpp +++ b/src/image/src/buffered_cv_image_widget.cpp @@ -6,9 +6,9 @@ * @copyright Copyright (c) 2024 Ruixiang Du (rdu) */ -#include "widget/buffered_cv_image_widget.hpp" +#include "image/buffered_cv_image_widget.hpp" -#include "widget/details/image_utils.hpp" +#include "image/details/image_utils.hpp" namespace quickviz { BufferedCvImageWidget::BufferedCvImageWidget(const std::string& widget_name, diff --git a/src/cvdraw/src/color_maps.cpp b/src/image/src/color_maps.cpp similarity index 97% rename from src/cvdraw/src/color_maps.cpp rename to src/image/src/color_maps.cpp index 814635a..79ab5a9 100644 --- a/src/cvdraw/src/color_maps.cpp +++ b/src/image/src/color_maps.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2019 Ruixiang Du (rdu) */ -#include "cvdraw/color_maps.hpp" +#include "image/color_maps.hpp" using namespace cv; diff --git a/src/cvdraw/src/cv_canvas.cpp b/src/image/src/cv_canvas.cpp similarity index 99% rename from src/cvdraw/src/cv_canvas.cpp rename to src/image/src/cv_canvas.cpp index ec28fa0..8d414b1 100644 --- a/src/cvdraw/src/cv_canvas.cpp +++ b/src/image/src/cv_canvas.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2019 Ruixiang Du (rdu) */ -#include "cvdraw/cv_canvas.hpp" +#include "image/cv_canvas.hpp" #include #include diff --git a/src/cvdraw/src/cv_colors.cpp b/src/image/src/cv_colors.cpp similarity index 98% rename from src/cvdraw/src/cv_colors.cpp rename to src/image/src/cv_colors.cpp index 609665c..49d55c1 100644 --- a/src/cvdraw/src/cv_colors.cpp +++ b/src/image/src/cv_colors.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2019 Ruixiang Du (rdu) */ -#include "cvdraw/cv_colors.hpp" +#include "image/cv_colors.hpp" using namespace quickviz; using namespace cv; diff --git a/src/widget/src/cv_image_widget.cpp b/src/image/src/cv_image_widget.cpp similarity index 96% rename from src/widget/src/cv_image_widget.cpp rename to src/image/src/cv_image_widget.cpp index 82a48e8..392ea27 100644 --- a/src/widget/src/cv_image_widget.cpp +++ b/src/image/src/cv_image_widget.cpp @@ -6,10 +6,10 @@ * @copyright Copyright (c) 2024 Ruixiang Du (rdu) */ -#include "widget/cv_image_widget.hpp" +#include "image/cv_image_widget.hpp" #include "glad/glad.h" -#include "widget/details/image_utils.hpp" +#include "image/details/image_utils.hpp" namespace quickviz { CvImageWidget::CvImageWidget(const std::string& widget_name) diff --git a/src/cvdraw/src/cv_io.cpp b/src/image/src/cv_io.cpp similarity index 97% rename from src/cvdraw/src/cv_io.cpp rename to src/image/src/cv_io.cpp index a8dcd85..d3e8d83 100644 --- a/src/cvdraw/src/cv_io.cpp +++ b/src/image/src/cv_io.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2018 Ruixiang Du (rdu) */ -#include "cvdraw/cv_io.hpp" +#include "image/cv_io.hpp" #include #include diff --git a/src/widget/src/details/image_utils.cpp b/src/image/src/details/image_utils.cpp similarity index 96% rename from src/widget/src/details/image_utils.cpp rename to src/image/src/details/image_utils.cpp index 22b6161..02913c7 100644 --- a/src/widget/src/details/image_utils.cpp +++ b/src/image/src/details/image_utils.cpp @@ -6,7 +6,7 @@ * @copyright Copyright (c) 2024 Ruixiang Du (rdu) */ -#include "widget/details/image_utils.hpp" +#include "image/details/image_utils.hpp" namespace quickviz { cv::Mat GenerateRandomMat(int width, int height) { diff --git a/src/image/test/CMakeLists.txt b/src/image/test/CMakeLists.txt new file mode 100644 index 0000000..9fb0480 --- /dev/null +++ b/src/image/test/CMakeLists.txt @@ -0,0 +1,17 @@ +add_executable(test_image_rw test_image_rw.cpp) +target_link_libraries(test_image_rw PUBLIC image) + +add_executable(test_cv_canvas test_cv_canvas.cpp) +target_link_libraries(test_cv_canvas PUBLIC image) + +add_executable(test_cv_drawmode test_cv_drawmode.cpp) +target_link_libraries(test_cv_drawmode PUBLIC image) + +add_executable(test_cv_drawfunc test_cv_drawfunc.cpp) +target_link_libraries(test_cv_drawfunc PUBLIC image) + +add_executable(test_cv_image_widget test_cv_image_widget.cpp) +target_link_libraries(test_cv_image_widget PUBLIC image) + +add_executable(test_buffered_cv_image_widget test_buffered_cv_image_widget.cpp) +target_link_libraries(test_buffered_cv_image_widget PUBLIC image) diff --git a/src/widget/test/test_buffered_cv_image_widget.cpp b/src/image/test/test_buffered_cv_image_widget.cpp similarity index 97% rename from src/widget/test/test_buffered_cv_image_widget.cpp rename to src/image/test/test_buffered_cv_image_widget.cpp index cfb3250..1a6dcbf 100644 --- a/src/widget/test/test_buffered_cv_image_widget.cpp +++ b/src/image/test/test_buffered_cv_image_widget.cpp @@ -20,7 +20,7 @@ #include "viewer/box.hpp" // #include "scene_objects/gl_triangle_scene_object.hpp" -#include "widget/buffered_cv_image_widget.hpp" +#include "image/buffered_cv_image_widget.hpp" using namespace quickviz; diff --git a/src/cvdraw/test/test_cv_canvas.cpp b/src/image/test/test_cv_canvas.cpp similarity index 95% rename from src/cvdraw/test/test_cv_canvas.cpp rename to src/image/test/test_cv_canvas.cpp index f823a19..66a3f51 100644 --- a/src/cvdraw/test/test_cv_canvas.cpp +++ b/src/image/test/test_cv_canvas.cpp @@ -1,6 +1,6 @@ #include -#include "cvdraw/cv_canvas.hpp" +#include "image/cv_canvas.hpp" using namespace quickviz; diff --git a/src/cvdraw/test/test_cv_drawfunc.cpp b/src/image/test/test_cv_drawfunc.cpp similarity index 97% rename from src/cvdraw/test/test_cv_drawfunc.cpp rename to src/image/test/test_cv_drawfunc.cpp index bf7a18e..b88f3b8 100644 --- a/src/cvdraw/test/test_cv_drawfunc.cpp +++ b/src/image/test/test_cv_drawfunc.cpp @@ -1,6 +1,6 @@ #include -#include "cvdraw/cvdraw.hpp" +#include "image/image.hpp" using namespace quickviz; diff --git a/src/cvdraw/test/test_cv_drawmode.cpp b/src/image/test/test_cv_drawmode.cpp similarity index 99% rename from src/cvdraw/test/test_cv_drawmode.cpp rename to src/image/test/test_cv_drawmode.cpp index 6df182e..074c3f4 100644 --- a/src/cvdraw/test/test_cv_drawmode.cpp +++ b/src/image/test/test_cv_drawmode.cpp @@ -1,6 +1,6 @@ #include -#include "cvdraw/cvdraw.hpp" +#include "image/image.hpp" using namespace quickviz; diff --git a/src/widget/test/test_cv_image_widget.cpp b/src/image/test/test_cv_image_widget.cpp similarity index 97% rename from src/widget/test/test_cv_image_widget.cpp rename to src/image/test/test_cv_image_widget.cpp index 428ee31..f68b41d 100644 --- a/src/widget/test/test_cv_image_widget.cpp +++ b/src/image/test/test_cv_image_widget.cpp @@ -13,7 +13,7 @@ #include #include "viewer/viewer.hpp" -#include "widget/cv_image_widget.hpp" +#include "image/cv_image_widget.hpp" // #include "scene_objects/gl_triangle_scene_object.hpp" diff --git a/src/cvdraw/test/test_image_rw.cpp b/src/image/test/test_image_rw.cpp similarity index 91% rename from src/cvdraw/test/test_image_rw.cpp rename to src/image/test/test_image_rw.cpp index a18b124..ffcb934 100644 --- a/src/cvdraw/test/test_image_rw.cpp +++ b/src/image/test/test_image_rw.cpp @@ -1,6 +1,6 @@ #include -#include "cvdraw/cvdraw.hpp" +#include "image/image.hpp" using namespace quickviz; diff --git a/src/widget/CMakeLists.txt b/src/widget/CMakeLists.txt deleted file mode 100644 index 5636370..0000000 --- a/src/widget/CMakeLists.txt +++ /dev/null @@ -1,53 +0,0 @@ -# NOTE: this module is being dissolved. Cairo bits moved to src/canvas/; -# OpenCV bits will move to src/image/ in a follow-up commit. After that -# this entire directory will be removed. - -find_package(Threads REQUIRED) -find_package(OpenGL REQUIRED) - -# (optional) OpenCV support -find_package(OpenCV QUIET) -if(OpenCV_FOUND) - message(STATUS "OpenCV found: ${OpenCV_VERSION}") - set(ENABLE_OPENCV_SUPPORT ON) - set(OPENCV_COMP_SRC - src/details/image_utils.cpp - src/cv_image_widget.cpp - src/buffered_cv_image_widget.cpp) - set(OPENCV_COMP_LIBS ${OpenCV_LIBS}) -else() - message(STATUS "OpenCV not found") -endif() - -if(NOT OpenCV_FOUND) - return() # nothing left to build in widget/ when OpenCV is absent -endif() - -# add library -add_library(widget - ${OPENCV_COMP_SRC}) -target_link_libraries(widget PUBLIC - core - imcore viewer - stb - Threads::Threads - OpenGL::GL - ${OPENCV_COMP_LIBS}) -target_include_directories(widget PUBLIC - $ - $ - PRIVATE src) - -if(BUILD_TESTING) - add_subdirectory(test) -endif() - -install(TARGETS widget - EXPORT quickvizTargets - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - RUNTIME DESTINATION bin - INCLUDES DESTINATION include) - -install(DIRECTORY include/ - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/src/widget/test/CMakeLists.txt b/src/widget/test/CMakeLists.txt deleted file mode 100644 index 34d4486..0000000 --- a/src/widget/test/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -if(ENABLE_OPENCV_SUPPORT) - add_executable(test_cv_image_widget test_cv_image_widget.cpp) - target_link_libraries(test_cv_image_widget PRIVATE widget) - - add_executable(test_buffered_cv_image_widget test_buffered_cv_image_widget.cpp) - target_link_libraries(test_buffered_cv_image_widget PRIVATE widget) -endif() From 64fad976a5d31413c3e2a68d575d248c854adacb Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 20:50:26 +0800 Subject: [PATCH 06/22] docs: refresh CLAUDE.md and TODO.md for the new module layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CLAUDE.md: Project Overview now lists the seven modules with one-line taglines, and explicitly notes editor frameworks live in sample/, not the library. - CLAUDE.md: Module Structure section rewrites the directory tree, adds the user-intent table ("I want to X → which module"), and explains the implementation-prefix convention (Gl in scene/, Cv in image/). - CLAUDE.md: optional-deps line updated (OpenCV → image, PCL → pcl_bridge). - TODO.md: reshape work marked complete; new "Module reorg by intent" entry added to Recently Completed; status summary refreshed to the final layout. No code changes; closes the visualization-first reorg. Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 55 +++++++++++++++++++++++++++++++++++++++++-------------- TODO.md | 21 ++++++++++++++++++--- 2 files changed, 59 insertions(+), 17 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3a8ee3c..2edc19b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,11 +4,20 @@ This document provides comprehensive guidance for working with the QuickViz C++ ## Project Overview -QuickViz is a C++ visualization library for robotics applications, providing: -- **viewer**: Automatic layout management and UI widgets (buttons, sliders, text boxes) -- **scene**: 2D/3D real-time rendering with OpenGL -- **widget**: Cairo-based drawing and plotting widgets -- **core**: Event system, buffers, and shared utilities +QuickViz is a C++ visualization library for robotics applications. +Each module has one job: + +- **viewer**: window + panels + layout (GLFW + ImGui + Yoga) +- **scene**: interactive 3D scene rendering (OpenGL) +- **plot**: data charts, 2D and 3D (ImPlot + ImPlot3D) +- **canvas**: 2D vector drawing (Cairo) +- **image**: image display & annotation (OpenCV, optional) +- **pcl_bridge**: PCL adapter (optional) +- **core**: shared infrastructure (events, buffers, threading helpers) + +Editor frameworks (undo/redo, history, project files) are **not** in +the library — they live in `sample/` on top of it. CI enforces the +`src/ ↛ sample/` boundary. ## Core Development Principles @@ -69,17 +78,35 @@ Create (or split) a module when: ### Module Structure ``` src/ -├── core/ # Event system, buffers, utilities (depends on nothing) -├── viewer/ # GLFW window management, ImGui integration -├── widget/ # Cairo drawing, image widgets, plotting -├── scene/ # OpenGL 3D rendering, point clouds, textures -├── pcl_bridge/ # Optional PCL adapter (file loading, conversions) -├── cvdraw/ # OpenCV-based drawing utilities (optional bridge) -└── third_party/ # imgui, implot, stb, yoga, googletest - +├── core/ # events, buffers, threading helpers +├── viewer/ # window + panels + layout (GLFW + ImGui + Yoga) +├── scene/ # interactive 3D scene rendering (OpenGL) +├── plot/ # data charts (ImPlot 2D + ImPlot3D) +├── canvas/ # 2D vector drawing (Cairo) +├── image/ # image display & annotation (OpenCV, optional) +└── pcl_bridge/ # PCL adapter (optional) + +third_party/ # imgui, implot, implot3d, stb, yoga, googletest sample/ # Reference applications built ON TOP of the library ``` +Pick the module by what you want to do: + +| I want to… | Module | +| --------------------------------------------- | ------------- | +| Open a window with panels | `viewer` | +| Show a 3D scene (robots, point clouds, mesh) | `scene` | +| Plot a chart of data (2D or 3D) | `plot` | +| Draw a custom 2D figure | `canvas` | +| Display or annotate camera images | `image` | +| Load PCL data | `pcl_bridge` | +| Use shared infrastructure (events, buffers) | `core` | + +Naming convention: backends that may grow alternatives keep an +implementation prefix on internal types (`Gl*` in `scene`, `Cv*` in +`image`). Domain modules themselves stay tech-neutral so a future +Vulkan-backed `scene` or libpng-backed `image` slot in without churn. + ### Dependency Rules - **Core** (math, logging, utilities) depends on nothing else - **Model** (scene/data types, transforms, selection) may depend on Core @@ -158,7 +185,7 @@ make -j8 ### Dependencies **Required**: OpenGL 3.3+, GLFW3, GLM, Cairo -**Optional**: OpenCV (cvdraw), PCL (point cloud bridge), Google Benchmark, Valgrind +**Optional**: OpenCV (`image` module), PCL (`pcl_bridge`), Google Benchmark, Valgrind **Bundled**: Dear ImGui & ImPlot, STB libraries, Yoga, GoogleTest, GLAD ## API Design Standards diff --git a/TODO.md b/TODO.md index 5c540a5..8211848 100644 --- a/TODO.md +++ b/TODO.md @@ -24,9 +24,12 @@ a visualization-justified hook. - [x] Delete `sample/object_management/` (demo of the deleted bridge) - [x] CI `boundary-check` job: `src/` may not include from `sample/` - [x] Update CLAUDE.md, archive stale design docs -- [ ] Build `sample/editor/` MVP as the API completeness check +- [x] Build `sample/editor/` MVP as the API completeness check (load PCD/PLY → render → select → DeleteSelectedPoints → undo/redo via app-side `CommandStack` → minimal history panel) +- [x] Module reorg: imview→viewer, gldraw→scene; widget split into + canvas/plot/image; cvdraw merged into image; new plot module hosts + ImPlot + ImPlot3D - [ ] Add library hooks discovered while building the sample (additive only). Logged candidates from sample/editor MVP: - `SceneManager::GetObjectId(name) / GetObjectName(id)` so editors can @@ -62,6 +65,17 @@ a visualization-justified hook. ## ✅ Recently Completed ### April 2026 +- ✅ **Module reorg by intent** — Final library layout: + `core, viewer, scene, plot, canvas, image, pcl_bridge`. One job per + module, named after what users want to do (not which backend it uses). + Renamed `imview→viewer`, `gldraw→scene`. Dissolved `widget` into + `canvas` (Cairo) + `plot` (ImPlot widgets). Merged `cvdraw` into + `image` along with the cv_image widgets from `widget`. New `plot` + module also hosts ImPlot3D. +- ✅ **`sample/editor/` MVP** — vis+editing reference app on top of + the library, built without any `src/` modifications. Acts as the + dogfood check on library completeness. Load PCD/PLY → select → + DeletePoints with full undo/redo via a sample-private CommandStack. - ✅ **Reshape: visualization-first re-anchor** — Removed the in-library state management module (`scenegraph`) and its sample (`object_management`). Locked the `src/ ↛ sample/` boundary in CI and CLAUDE.md. Editor concerns @@ -97,8 +111,9 @@ a visualization-justified hook. is in place) **Focus**: Re-anchor the library on visualization, then build the editor sample as the API check. -**Architecture**: Library = `core` + `viewer` + `widget` + `scene` + -`pcl_bridge` + `cvdraw` (optional). Apps live in `sample/`. +**Architecture**: Library = `core, viewer, scene, plot, canvas, image, +pcl_bridge` — one job per module, named by user intent. Apps live in +`sample/`. --- From 557a27a85a0c6f9fd6c051119b450b93b7385d32 Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 21:25:28 +0800 Subject: [PATCH 07/22] =?UTF-8?q?refactor(scene):=20rename=20GlViewer=20?= =?UTF-8?q?=E2=86=92=20SceneApp,=20reframe=20as=20quickstart=20facade?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GlViewer was named like a peer of Viewer but was actually a higher-level helper that wraps a Viewer + GlScenePanel + grid + coordinate frame and runs the event loop. The 17 renderable unit tests use it; nothing in sample/ does. Yet it lived in the public include path, which made the naming overlap with Viewer (window + panels primitive) confusing for anyone scanning scene/'s headers. Changes: - Rename src/scene/include/scene/gl_viewer.hpp → scene_app.hpp - Rename src/scene/src/gl_viewer.cpp → scene_app.cpp - Class GlViewer → SceneApp throughout (17 test files updated mechanically) - Header guard QUICKVIZ_GLVIEW_HPP → QUICKVIZ_SCENE_APP_HPP - Rewrite the file-level docstring from "test helper" to a proper quickstart facade description with a usage example. - Tighten the class docstring; note that for richer apps users should drop down to Viewer + panel classes directly. This positions SceneApp as the "five-line entry point for a 3D viewer" that was identified as the highest-leverage ergonomic win during the visualization-first reshape. The class API is unchanged; only the naming and framing moved. No new code; no speculative additions. Build clean. Same 2 pre-existing PCL test failures, no new regressions. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/scene/CMakeLists.txt | 2 +- .../scene/{gl_viewer.hpp => scene_app.hpp} | 67 ++++++++++++------- .../src/{gl_viewer.cpp => scene_app.cpp} | 20 +++--- src/scene/test/renderable/test_arrow.cpp | 6 +- src/scene/test/renderable/test_billboard.cpp | 6 +- .../test/renderable/test_bounding_box.cpp | 6 +- src/scene/test/renderable/test_canvas.cpp | 6 +- .../test/renderable/test_coordinate_frame.cpp | 6 +- src/scene/test/renderable/test_cylinder.cpp | 6 +- src/scene/test/renderable/test_frustum.cpp | 6 +- src/scene/test/renderable/test_grid.cpp | 6 +- src/scene/test/renderable/test_line_strip.cpp | 6 +- src/scene/test/renderable/test_mesh.cpp | 6 +- src/scene/test/renderable/test_path.cpp | 6 +- src/scene/test/renderable/test_plane.cpp | 6 +- .../test/renderable/test_point_cloud.cpp | 6 +- src/scene/test/renderable/test_pose.cpp | 6 +- src/scene/test/renderable/test_sphere.cpp | 6 +- src/scene/test/renderable/test_texture.cpp | 6 +- src/scene/test/renderable/test_triangle.cpp | 6 +- 20 files changed, 105 insertions(+), 86 deletions(-) rename src/scene/include/scene/{gl_viewer.hpp => scene_app.hpp} (64%) rename src/scene/src/{gl_viewer.cpp => scene_app.cpp} (86%) diff --git a/src/scene/CMakeLists.txt b/src/scene/CMakeLists.txt index 5aefe09..3a38840 100644 --- a/src/scene/CMakeLists.txt +++ b/src/scene/CMakeLists.txt @@ -6,7 +6,7 @@ find_package(OpenGL REQUIRED) add_library(scene ## gui integration src/gl_scene_panel.cpp - src/gl_viewer.cpp + src/scene_app.cpp ## core rendering components src/shader.cpp src/shader_program.cpp diff --git a/src/scene/include/scene/gl_viewer.hpp b/src/scene/include/scene/scene_app.hpp similarity index 64% rename from src/scene/include/scene/gl_viewer.hpp rename to src/scene/include/scene/scene_app.hpp index 8815637..7927202 100644 --- a/src/scene/include/scene/gl_viewer.hpp +++ b/src/scene/include/scene/scene_app.hpp @@ -1,17 +1,33 @@ /* - * @file gl_viewer.hpp + * @file scene_app.hpp * @author Ruixiang Du (ruixiang.du@gmail.com) * @date 2025-08-23 - * @brief Reusable OpenGL view class for testing renderable objects + * @brief Five-line quickstart for a 3D viewer * - * This class handles the common boilerplate code for setting up OpenGL rendering tests, - * allowing test cases to focus on creating and configuring renderable objects. + * `SceneApp` is the smallest entry point QuickViz offers for building a + * 3D viewer. It wraps a `Viewer` and a `GlScenePanel`, sets up sensible + * defaults (grid + coordinate frame), and runs the event loop. The + * scene is populated through a callback the caller provides. + * + * Typical use: + * + * quickviz::SceneApp app({.window_title = "My Tool"}); + * app.SetSceneSetup([](SceneManager* scene) { + * scene->AddOpenGLObject("cloud", std::make_unique(...)); + * }); + * app.Run(); + * + * For more control (multiple panels, side widgets, plot/canvas/image + * panels alongside the 3D scene) drop down to `Viewer` directly. SceneApp + * is the path of least resistance, not the only door. + * + * Internally also used by the renderable unit tests in scene/test/. * * Copyright (c) 2025 Ruixiang Du (rdu) */ -#ifndef QUICKVIZ_GLVIEW_HPP -#define QUICKVIZ_GLVIEW_HPP +#ifndef QUICKVIZ_SCENE_APP_HPP +#define QUICKVIZ_SCENE_APP_HPP #include #include @@ -29,20 +45,23 @@ namespace quickviz { /** - * @brief Reusable OpenGL viewer for testing renderable objects - * - * This class encapsulates the common setup and management code needed for - * OpenGL rendering tests. It provides: - * - Automatic viewer and scene manager setup - * - Optional grid and coordinate frame - * - Scene population callback system - * - Standard camera controls and help text - * - Exception handling and error reporting + * @brief Quickstart facade for building a 3D viewer in a few lines + * + * Provides: + * - `Viewer` + `GlScenePanel` set up with sane defaults + * - Optional reference grid and coordinate frame + * - A `SceneSetupCallback` for populating the scene + * - Standard camera controls (inherited from `GlScenePanel`) + * - Optional help-text overlay + * + * For applications that need multiple panels (plots, image views, custom + * UI) compose `Viewer` and the panel classes directly — `SceneApp` is a + * convenience layer, not a base class to extend. */ -class GlViewer { +class SceneApp { public: /** - * @brief Configuration structure for GlView + * @brief Configuration for the SceneApp */ struct Config { std::string window_title; @@ -78,20 +97,20 @@ class GlViewer { * * @param config Configuration for the view */ - explicit GlViewer(const Config& config = Config{}); + explicit SceneApp(const Config& config = Config{}); /** * @brief Destructor */ - ~GlViewer() = default; + ~SceneApp() = default; // Disable copy construction and assignment - GlViewer(const GlViewer&) = delete; - GlViewer& operator=(const GlViewer&) = delete; + SceneApp(const SceneApp&) = delete; + SceneApp& operator=(const SceneApp&) = delete; // Enable move construction and assignment - GlViewer(GlViewer&&) = default; - GlViewer& operator=(GlViewer&&) = default; + SceneApp(SceneApp&&) = default; + SceneApp& operator=(SceneApp&&) = default; /** * @brief Set the scene setup callback @@ -158,4 +177,4 @@ class GlViewer { } // namespace quickviz -#endif // QUICKVIZ_GLVIEW_HPP +#endif // QUICKVIZ_SCENE_APP_HPP diff --git a/src/scene/src/gl_viewer.cpp b/src/scene/src/scene_app.cpp similarity index 86% rename from src/scene/src/gl_viewer.cpp rename to src/scene/src/scene_app.cpp index d0dd29a..a4da022 100644 --- a/src/scene/src/gl_viewer.cpp +++ b/src/scene/src/scene_app.cpp @@ -8,7 +8,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "scene/gl_viewer.hpp" +#include "scene/scene_app.hpp" #include #include @@ -17,9 +17,9 @@ namespace quickviz { -GlViewer::GlViewer(const Config& config) : config_(config) { SetupViewer(); } +SceneApp::SceneApp(const Config& config) : config_(config) { SetupViewer(); } -void GlViewer::SetupViewer() { +void SceneApp::SetupViewer() { // Create box container for layout auto box = std::make_shared("main_box"); box->SetFlexDirection(Styling::FlexDirection::kRow); @@ -37,7 +37,7 @@ void GlViewer::SetupViewer() { viewer_.AddSceneObject(box); } -void GlViewer::SetupBasicScene() { +void SceneApp::SetupBasicScene() { // Add grid if requested if (config_.show_grid) { auto grid = std::make_unique(config_.grid_size, config_.grid_step, @@ -54,22 +54,22 @@ void GlViewer::SetupBasicScene() { } } -void GlViewer::SetSceneSetup(SceneSetupCallback callback) { +void SceneApp::SetSceneSetup(SceneSetupCallback callback) { scene_setup_callback_ = std::move(callback); } -void GlViewer::AddHelpSection(const std::string& section_title, +void SceneApp::AddHelpSection(const std::string& section_title, const std::vector& help_lines) { help_sections_.emplace_back(section_title, help_lines); } -void GlViewer::SetDescription(const std::string& description) { +void SceneApp::SetDescription(const std::string& description) { description_ = description; } -SceneManager* GlViewer::GetSceneManager() const { return scene_panel_->GetSceneManager(); } +SceneManager* SceneApp::GetSceneManager() const { return scene_panel_->GetSceneManager(); } -void GlViewer::DisplayHelp() const { +void SceneApp::DisplayHelp() const { std::cout << "\n=== " << config_.window_title << " ===" << std::endl; if (!description_.empty()) { @@ -94,7 +94,7 @@ void GlViewer::DisplayHelp() const { std::cout << std::endl; } -void GlViewer::Run() { +void SceneApp::Run() { try { // Set up basic scene elements SetupBasicScene(); diff --git a/src/scene/test/renderable/test_arrow.cpp b/src/scene/test/renderable/test_arrow.cpp index 8d0cdc1..1ef8c2d 100644 --- a/src/scene/test/renderable/test_arrow.cpp +++ b/src/scene/test/renderable/test_arrow.cpp @@ -15,7 +15,7 @@ #include #include -#include "scene/gl_viewer.hpp" +#include "scene/scene_app.hpp" #include "scene/renderable/arrow.hpp" using namespace quickviz; @@ -128,12 +128,12 @@ void SetupArrowScene(SceneManager* scene_manager) { int main(int argc, char* argv[]) { try { // Configure the view - GlViewer::Config config; + SceneApp::Config config; config.window_title = "Arrow Rendering Test"; config.coordinate_frame_size = 1.0f; // Create the view - GlViewer view(config); + SceneApp view(config); // Set up description and help sections view.SetDescription("Testing arrow rendering for vectors, directions, and forces"); diff --git a/src/scene/test/renderable/test_billboard.cpp b/src/scene/test/renderable/test_billboard.cpp index 5e55e5e..41fe5c1 100644 --- a/src/scene/test/renderable/test_billboard.cpp +++ b/src/scene/test/renderable/test_billboard.cpp @@ -19,7 +19,7 @@ #include #include -#include "scene/gl_viewer.hpp" +#include "scene/scene_app.hpp" #include "scene/renderable/billboard.hpp" #include "scene/renderable/sphere.hpp" #include "scene/renderable/arrow.hpp" @@ -249,12 +249,12 @@ int main(int argc, char* argv[]) { std::cout << std::endl; // Configure the viewer - GlViewer::Config config; + SceneApp::Config config; config.window_title = "Billboard Primitive Test"; config.coordinate_frame_size = 2.0f; // Create viewer - GlViewer viewer(config); + SceneApp viewer(config); // Set up description and help sections viewer.SetDescription("Testing Billboard primitive with ImGui font integration as Text3D replacement"); diff --git a/src/scene/test/renderable/test_bounding_box.cpp b/src/scene/test/renderable/test_bounding_box.cpp index 81b3860..0035529 100644 --- a/src/scene/test/renderable/test_bounding_box.cpp +++ b/src/scene/test/renderable/test_bounding_box.cpp @@ -14,7 +14,7 @@ #include #include -#include "scene/gl_viewer.hpp" +#include "scene/scene_app.hpp" #include "scene/renderable/bounding_box.hpp" using namespace quickviz; @@ -96,12 +96,12 @@ void SetupBoundingBoxScene(SceneManager* scene_manager) { int main(int argc, char* argv[]) { try { // Configure the view for 3D mode - GlViewer::Config config; + SceneApp::Config config; config.window_title = "BoundingBox Rendering Test"; config.coordinate_frame_size = 2.0f; // Create the view - GlViewer view(config); + SceneApp view(config); // Set up description and help sections view.SetDescription("Testing bounding box rendering for object bounds and region visualization"); diff --git a/src/scene/test/renderable/test_canvas.cpp b/src/scene/test/renderable/test_canvas.cpp index 3519f6e..7f57dcb 100644 --- a/src/scene/test/renderable/test_canvas.cpp +++ b/src/scene/test/renderable/test_canvas.cpp @@ -16,7 +16,7 @@ #include #include -#include "scene/gl_viewer.hpp" +#include "scene/scene_app.hpp" #include "scene/renderable/canvas.hpp" #include "scene/renderable/triangle.hpp" @@ -113,13 +113,13 @@ int main(int argc, char* argv[]) { } // Configure the view for 2D mode (canvas works best in 2D) - GlViewer::Config config; + SceneApp::Config config; config.window_title = "Canvas Rendering Test - 2D Mode"; config.scene_mode = SceneManager::Mode::k2D; config.coordinate_frame_size = 0.5f; // Create the view - GlViewer view(config); + SceneApp view(config); // Set up description and help sections view.SetDescription("Testing canvas 2D drawing functionality with various shapes and styles"); diff --git a/src/scene/test/renderable/test_coordinate_frame.cpp b/src/scene/test/renderable/test_coordinate_frame.cpp index 83a2ee7..5d7c68a 100644 --- a/src/scene/test/renderable/test_coordinate_frame.cpp +++ b/src/scene/test/renderable/test_coordinate_frame.cpp @@ -13,7 +13,7 @@ #include #include -#include "scene/gl_viewer.hpp" +#include "scene/scene_app.hpp" #include "scene/renderable/coordinate_frame.hpp" #include "scene/renderable/grid.hpp" @@ -64,13 +64,13 @@ void SetupCoordinateFrameScene(SceneManager* scene_manager) { int main(int argc, char* argv[]) { try { // Configure the view for 3D mode - GlViewer::Config config; + SceneApp::Config config; config.window_title = "Coordinate Frame Rendering Test"; config.coordinate_frame_size = 2.0f; config.show_coordinate_frame = false; // We'll add our own coordinate frames // Create the view - GlViewer view(config); + SceneApp view(config); // Set up description and help sections view.SetDescription("Testing coordinate frame rendering with various orientations and scales"); diff --git a/src/scene/test/renderable/test_cylinder.cpp b/src/scene/test/renderable/test_cylinder.cpp index b1f2f49..4332a3b 100644 --- a/src/scene/test/renderable/test_cylinder.cpp +++ b/src/scene/test/renderable/test_cylinder.cpp @@ -11,7 +11,7 @@ #include #include -#include "scene/gl_viewer.hpp" +#include "scene/scene_app.hpp" #include "scene/renderable/cylinder.hpp" using namespace quickviz; @@ -51,12 +51,12 @@ void SetupCylinderScene(SceneManager* scene_manager) { int main(int argc, char* argv[]) { try { // Configure the view for 3D mode - GlViewer::Config config; + SceneApp::Config config; config.window_title = "Cylinder Rendering Test"; config.coordinate_frame_size = 2.0f; // Create the view - GlViewer view(config); + SceneApp view(config); // Set up description and help sections view.SetDescription("Testing cylinder rendering with various dimensions and rendering modes"); diff --git a/src/scene/test/renderable/test_frustum.cpp b/src/scene/test/renderable/test_frustum.cpp index 4091a77..9fde161 100644 --- a/src/scene/test/renderable/test_frustum.cpp +++ b/src/scene/test/renderable/test_frustum.cpp @@ -11,7 +11,7 @@ #include #include -#include "scene/gl_viewer.hpp" +#include "scene/scene_app.hpp" #include "scene/renderable/frustum.hpp" #include "scene/renderable/grid.hpp" @@ -93,12 +93,12 @@ void SetupFrustumScene(SceneManager* scene_manager) { int main(int argc, char* argv[]) { try { // Configure the view for 3D mode - GlViewer::Config config; + SceneApp::Config config; config.window_title = "Frustum Rendering Test"; config.coordinate_frame_size = 2.0f; // Create the view - GlViewer view(config); + SceneApp view(config); // Set up description and help sections view.SetDescription("Testing frustum rendering for sensor field-of-view visualization"); diff --git a/src/scene/test/renderable/test_grid.cpp b/src/scene/test/renderable/test_grid.cpp index 83faf03..a19739c 100644 --- a/src/scene/test/renderable/test_grid.cpp +++ b/src/scene/test/renderable/test_grid.cpp @@ -12,7 +12,7 @@ #include #include -#include "scene/gl_viewer.hpp" +#include "scene/scene_app.hpp" #include "scene/renderable/grid.hpp" using namespace quickviz; @@ -46,13 +46,13 @@ void SetupGridScene(SceneManager* scene_manager) { int main(int argc, char* argv[]) { try { // Configure the view for 3D mode - GlViewer::Config config; + SceneApp::Config config; config.window_title = "Grid Rendering Test"; config.coordinate_frame_size = 2.0f; config.show_grid = false; // We'll add our own grids // Create the view - GlViewer view(config); + SceneApp view(config); // Set up description and help sections view.SetDescription("Testing grid rendering with various sizes, steps, and colors"); diff --git a/src/scene/test/renderable/test_line_strip.cpp b/src/scene/test/renderable/test_line_strip.cpp index 49795a6..4624081 100644 --- a/src/scene/test/renderable/test_line_strip.cpp +++ b/src/scene/test/renderable/test_line_strip.cpp @@ -12,7 +12,7 @@ #include #include -#include "scene/gl_viewer.hpp" +#include "scene/scene_app.hpp" #include "scene/renderable/line_strip.hpp" using namespace quickviz; @@ -108,12 +108,12 @@ void SetupLineStripScene(SceneManager* scene_manager) { int main(int argc, char* argv[]) { try { // Configure the view for 3D mode - GlViewer::Config config; + SceneApp::Config config; config.window_title = "LineStrip Rendering Test"; config.coordinate_frame_size = 2.0f; // Create the view - GlViewer view(config); + SceneApp view(config); // Set up description and help sections view.SetDescription("Testing line strip rendering for paths, trajectories, and continuous curves"); diff --git a/src/scene/test/renderable/test_mesh.cpp b/src/scene/test/renderable/test_mesh.cpp index 24ee83f..31e5471 100644 --- a/src/scene/test/renderable/test_mesh.cpp +++ b/src/scene/test/renderable/test_mesh.cpp @@ -12,7 +12,7 @@ #include #include -#include "scene/gl_viewer.hpp" +#include "scene/scene_app.hpp" #include "scene/renderable/mesh.hpp" using namespace quickviz; @@ -179,12 +179,12 @@ void SetupMeshScene(SceneManager* scene_manager) { int main(int argc, char* argv[]) { try { // Configure the view for 3D mode - GlViewer::Config config; + SceneApp::Config config; config.window_title = "Mesh Rendering Test"; config.coordinate_frame_size = 2.0f; // Create the view - GlViewer view(config); + SceneApp view(config); // Set up description and help sections view.SetDescription("Testing mesh rendering with various shapes, materials, and rendering modes"); diff --git a/src/scene/test/renderable/test_path.cpp b/src/scene/test/renderable/test_path.cpp index cfd9604..bda8eb0 100644 --- a/src/scene/test/renderable/test_path.cpp +++ b/src/scene/test/renderable/test_path.cpp @@ -15,7 +15,7 @@ #include #include -#include "scene/gl_viewer.hpp" +#include "scene/scene_app.hpp" #include "scene/renderable/path.hpp" #include "scene/renderable/grid.hpp" @@ -188,12 +188,12 @@ void SetupPathScene(SceneManager* scene_manager) { int main(int argc, char* argv[]) { try { // Configure the view for 3D mode - GlViewer::Config config; + SceneApp::Config config; config.window_title = "Path Rendering Test"; config.coordinate_frame_size = 2.0f; // Create the view - GlViewer view(config); + SceneApp view(config); // Set up description and help sections view.SetDescription("Testing path rendering for trajectory and motion planning visualization"); diff --git a/src/scene/test/renderable/test_plane.cpp b/src/scene/test/renderable/test_plane.cpp index 175f936..a7a1098 100644 --- a/src/scene/test/renderable/test_plane.cpp +++ b/src/scene/test/renderable/test_plane.cpp @@ -15,7 +15,7 @@ #include #include -#include "scene/gl_viewer.hpp" +#include "scene/scene_app.hpp" #include "scene/renderable/plane.hpp" #include "scene/renderable/grid.hpp" #include "scene/renderable/coordinate_frame.hpp" @@ -142,12 +142,12 @@ void SetupPlaneScene(SceneManager* scene_manager) { int main(int argc, char* argv[]) { try { // Configure the view for 3D mode - GlViewer::Config config; + SceneApp::Config config; config.window_title = "Plane Rendering Test"; config.coordinate_frame_size = 2.0f; // Create the view - GlViewer view(config); + SceneApp view(config); // Set up description and help sections view.SetDescription("Testing plane rendering with various orientations and visualization modes"); diff --git a/src/scene/test/renderable/test_point_cloud.cpp b/src/scene/test/renderable/test_point_cloud.cpp index 5d6b50b..22cb81e 100644 --- a/src/scene/test/renderable/test_point_cloud.cpp +++ b/src/scene/test/renderable/test_point_cloud.cpp @@ -14,7 +14,7 @@ #include #include -#include "scene/gl_viewer.hpp" +#include "scene/scene_app.hpp" #include "scene/renderable/point_cloud.hpp" using namespace quickviz; @@ -86,12 +86,12 @@ void SetupPointCloudScene(SceneManager* scene_manager) { int main(int argc, char* argv[]) { try { // Configure the view for 3D mode - GlViewer::Config config; + SceneApp::Config config; config.window_title = "PointCloud Rendering Test"; config.coordinate_frame_size = 2.0f; // Create the view - GlViewer view(config); + SceneApp view(config); // Set up description and help sections view.SetDescription("Testing point cloud rendering with various patterns and coloring schemes"); diff --git a/src/scene/test/renderable/test_pose.cpp b/src/scene/test/renderable/test_pose.cpp index b5d40ba..040c76a 100644 --- a/src/scene/test/renderable/test_pose.cpp +++ b/src/scene/test/renderable/test_pose.cpp @@ -16,7 +16,7 @@ #include #include -#include "scene/gl_viewer.hpp" +#include "scene/scene_app.hpp" #include "scene/renderable/pose.hpp" #include "scene/renderable/grid.hpp" @@ -136,12 +136,12 @@ void SetupPoseScene(SceneManager* scene_manager) { int main(int argc, char* argv[]) { try { // Configure the view for 3D mode - GlViewer::Config config; + SceneApp::Config config; config.window_title = "Pose Rendering Test"; config.coordinate_frame_size = 2.0f; // Create the view - GlViewer view(config); + SceneApp view(config); // Set up description and help sections view.SetDescription("Testing 6-DOF pose visualization with coordinate frames and history trails"); diff --git a/src/scene/test/renderable/test_sphere.cpp b/src/scene/test/renderable/test_sphere.cpp index 7425b84..242f6c1 100644 --- a/src/scene/test/renderable/test_sphere.cpp +++ b/src/scene/test/renderable/test_sphere.cpp @@ -11,7 +11,7 @@ #include #include -#include "scene/gl_viewer.hpp" +#include "scene/scene_app.hpp" #include "scene/renderable/sphere.hpp" using namespace quickviz; @@ -52,12 +52,12 @@ void SetupSphereScene(SceneManager* scene_manager) { int main(int argc, char* argv[]) { try { // Configure the view for 3D mode - GlViewer::Config config; + SceneApp::Config config; config.window_title = "Sphere Rendering Test"; config.coordinate_frame_size = 2.0f; // Create the view - GlViewer view(config); + SceneApp view(config); // Set up description and help sections view.SetDescription("Testing sphere rendering with various sizes, colors, and rendering modes"); diff --git a/src/scene/test/renderable/test_texture.cpp b/src/scene/test/renderable/test_texture.cpp index f1a750a..1f61506 100644 --- a/src/scene/test/renderable/test_texture.cpp +++ b/src/scene/test/renderable/test_texture.cpp @@ -19,7 +19,7 @@ #include #include -#include "scene/gl_viewer.hpp" +#include "scene/scene_app.hpp" #include "scene/renderable/texture.hpp" #include "core/buffer/buffer_registry.hpp" #include "core/buffer/ring_buffer.hpp" @@ -145,13 +145,13 @@ void SetupTextureScene(SceneManager* scene_manager) { int main(int argc, char* argv[]) { try { // Configure the view for 2D mode - GlViewer::Config config; + SceneApp::Config config; config.window_title = "Texture Rendering Test - 2D Mode"; config.scene_mode = SceneManager::Mode::k2D; config.coordinate_frame_size = 2.0f; // Create the view - GlViewer view(config); + SceneApp view(config); // Set up description and help sections view.SetDescription("Testing dynamic texture rendering with animated patterns"); diff --git a/src/scene/test/renderable/test_triangle.cpp b/src/scene/test/renderable/test_triangle.cpp index 0bc72af..25cd335 100644 --- a/src/scene/test/renderable/test_triangle.cpp +++ b/src/scene/test/renderable/test_triangle.cpp @@ -15,7 +15,7 @@ #include #include -#include "scene/gl_viewer.hpp" +#include "scene/scene_app.hpp" #include "scene/renderable/triangle.hpp" using namespace quickviz; @@ -85,14 +85,14 @@ void SetupTriangleScene(SceneManager* scene_manager) { int main(int argc, char* argv[]) { try { // Configure the view for 2D mode - GlViewer::Config config; + SceneApp::Config config; config.window_title = "Triangle Rendering Test - 2D Mode"; config.scene_mode = SceneManager::Mode::k2D; // Set 2D mode config.show_grid = true; // Disable grid for cleaner 2D view config.show_coordinate_frame = true; // Disable 3D coordinate frame // Create the view - GlViewer view(config); + SceneApp view(config); // Set up description and help sections view.SetDescription("Testing triangle rendering in 2D mode for shapes and UI elements"); From e637b8da22aa21f9b58cd411e812b3305cdce763 Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 21:33:11 +0800 Subject: [PATCH 08/22] docs: rewrite CLAUDE.md as a tighter project contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous CLAUDE.md had accumulated through several reshapes and mixed marketing language with hard rules. This rewrite keeps the load-bearing content and drops the rest. Structure (14 short sections): 1. Mission — visualization-first, building blocks, generic terminology 2. Module Map — final layout + intent table + quickstart classes 3. Library Boundary — src/ ↛ sample/ rule (CI-enforced) 4. Dependency Direction — DAG diagram + when to refuse cross-module deps 5. Build System — required/optional deps, cmake invocation, key options 6. Code Style — C++17, naming, prefixes, header-guard form 7. Architecture Patterns — scene/selection/tools/panels in 5 lines each 8. Threading Model — GL main-thread rule, background hand-off 9. API Design Principles — small surface, explicit inputs, ownership 10. Error Handling — validate at boundaries, no silent suppression 11. Decision Heuristics — recurring tradeoff cheatsheet 12. Tests — where they live and the coverage bar 13. Commits & Documentation — message form, TODO discipline 14. Where Things Live — quick index of code, samples, docs, CI Removed: PR Review Checklist (move target: docs/), the long "Building Blocks Philosophy" lecture (3 lines now do the same job), the "Refactor Playbook" (over-prescriptive), "Common Tasks" cookbook (would rot), "Current Development Focus" (TODO.md owns that), "Platform Support" (README's job), and most marketing-style language. Net size: 470 → 308 lines. Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 691 ++++++++++++++++++++---------------------------------- 1 file changed, 260 insertions(+), 431 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2edc19b..3bb0b7c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,81 +1,32 @@ # QuickViz Project Guidelines -This document provides comprehensive guidance for working with the QuickViz C++ visualization library for robotics applications. - -## Project Overview - -QuickViz is a C++ visualization library for robotics applications. -Each module has one job: - -- **viewer**: window + panels + layout (GLFW + ImGui + Yoga) -- **scene**: interactive 3D scene rendering (OpenGL) -- **plot**: data charts, 2D and 3D (ImPlot + ImPlot3D) -- **canvas**: 2D vector drawing (Cairo) -- **image**: image display & annotation (OpenCV, optional) -- **pcl_bridge**: PCL adapter (optional) -- **core**: shared infrastructure (events, buffers, threading helpers) - -Editor frameworks (undo/redo, history, project files) are **not** in -the library — they live in `sample/` on top of it. CI enforces the -`src/ ↛ sample/` boundary. - -## Core Development Principles - -### Design Philosophy -- **Small surface, strong contracts**: Keep public APIs minimal and explicit; hide implementation details -- **Seams before abstractions**: Extract clear boundaries first; abstract later if duplication persists -- **Single responsibility**: Each module/file does one thing well (target ~500 LOC per file) -- **Local reasoning**: Callers shouldn't need global state knowledge to use an API -- **Performance by design**: Favor data-oriented layouts, predictable memory, and measured hot paths -- **Determinism over cleverness**: Prefer simple, reproducible behavior to smart but fragile logic -- **Building blocks philosophy**: Provide generic, composable components that users can combine to build domain-specific applications. Use generic terms (geometry, mesh, camera, viewport) rather than application-specific terminology (map, terrain, navigation) - -### Library Interface Boundaries -QuickViz is designed as a toolkit of building blocks for robotics visualization applications. The interface design clearly distinguishes between different levels of user interaction: - -**Direct Use Components** (Ready-to-use, minimal configuration): -- Core rendering primitives (points, lines, meshes, textures) -- Standard UI widgets (buttons, sliders, text inputs) -- Camera controllers and viewport management -- File I/O utilities (image, mesh formats) - -**Configurable Components** (Parameters and settings exposed): -- Rendering passes and shaders (lighting models, post-processing) -- Layout managers and containers -- Event handling and input mapping -- Color schemes and visual styling - -**Extensible Components** (Virtual interfaces for inheritance): -- `Renderable`, `InputHandler`, `SceneObject` interfaces -- Custom drawing and interaction tools -- Specialized data visualization widgets -- Custom file format adapters - -**Build-Upon Components** (Library hooks apps compose into bigger frameworks): -- Scene composition (`SceneManager` + `OpenGlObject` registration) -- Tool registration (`InteractionTool` interface + `ToolManager`) -- Threading and job hand-off boundaries -- Custom data adapters (e.g., `pcl_bridge` pattern) - -> Editor-shaped frameworks — command/undo stacks, scene graphs with parent/child -> hierarchies, project-file persistence, history panels — are **not** part of the -> library. They live in consuming apps (see `sample/editor/` for a reference -> implementation) and are built on top of the hooks above. - -> **Design Rule**: Generic robotics and graphics terminology should be preferred over domain-specific names. For example, use "Mesh", "PointCloud", "Camera" rather than "Map", "Scan", "Observer". This ensures the library remains broadly applicable across different robotics applications. - -### When to Create or Split Modules -Create (or split) a module when: -- **Two or more** other modules depend on a concept -- The code has **distinct lifecycles** (e.g., GPU resources vs. CPU parsing) -- You need to **swap implementations** behind an interface -- You want **separate testability** (unit tests without GL context) - -**Do not** create a module if it only wraps 1-2 functions without clear benefit. - -## Architecture & Dependencies - -### Module Structure +QuickViz is a C++ visualization library for robotics. Its goal is to let +users build a working UI/visualization tool with minimal boilerplate, then +keep growing the tool from there. This document is the contract for how the +codebase is organized and the rules contributors (human or agent) must +follow. + +If anything in this file conflicts with code on disk, the code wins — +update this file in the same change. + +--- + +## 1. Mission + +- **Visualization first.** The library renders, displays, and interacts. + It does not run editors, manage projects, persist documents, or model + application state. Those belong in apps built *on top* of the library. +- **Building blocks, not a framework.** Provide composable pieces; let + apps decide the architecture. No global state, no singletons exposed in + public headers, no hidden lifecycles. +- **Generic over domain-specific.** Use graphics/robotics terms (`Mesh`, + `PointCloud`, `Camera`) rather than app terms (`Map`, `Scan`, + `Observer`). Keeps the library reusable across robotics applications. + +--- + +## 2. Module Map + ``` src/ ├── core/ # events, buffers, threading helpers @@ -87,10 +38,12 @@ src/ └── pcl_bridge/ # PCL adapter (optional) third_party/ # imgui, implot, implot3d, stb, yoga, googletest -sample/ # Reference applications built ON TOP of the library +sample/ # reference applications built on top of the library +tests/ # cross-module integration tests +docs/ # design notes, architecture references ``` -Pick the module by what you want to do: +Pick a module by **what you want to do**, not by which backend it uses: | I want to… | Module | | --------------------------------------------- | ------------- | @@ -100,380 +53,256 @@ Pick the module by what you want to do: | Draw a custom 2D figure | `canvas` | | Display or annotate camera images | `image` | | Load PCL data | `pcl_bridge` | -| Use shared infrastructure (events, buffers) | `core` | - -Naming convention: backends that may grow alternatives keep an -implementation prefix on internal types (`Gl*` in `scene`, `Cv*` in -`image`). Domain modules themselves stay tech-neutral so a future -Vulkan-backed `scene` or libpng-backed `image` slot in without churn. - -### Dependency Rules -- **Core** (math, logging, utilities) depends on nothing else -- **Model** (scene/data types, transforms, selection) may depend on Core -- **Graphics** (GL wrappers, passes, shaders) may depend on Core and Model -- **Tools/Interaction** (picking, gizmos, measure) may depend on Graphics + Model -- **UI** (ImGui panels, docking) can depend on everything but is never depended on -- **Bridges/Adapters** (OpenCV/PCL/etc.) depend outward; nothing core depends on them - -> **Rule**: Lower layers never include headers from higher layers - -### Library Boundary: `src/` is visualization-only -QuickViz is a visualization library. Editor / app-level concerns -(undo/redo, command history, scene serialization, project files, editing -operations) live in `sample/` and consume the library, never the reverse. - -- `sample/*` may include from `src/*/include/` and link against library targets. -- `src/*` must not include from `sample/`. Enforced by CI (`boundary-check`). -- If a sample needs something from the library, add it as an additive, - visualization-justified hook to the library — do not pull sample code in. -- Sample applications also serve as a dogfood check: if a fully-featured - vis+editing app cannot be built on top of `src/` without modifying `src/`, - the library is missing a hook and we evaluate the gap deliberately. - -### Key Design Patterns - -#### 1. Scene Object Hierarchy (viewer) -- `Window` → `Viewer` → `SceneObject` -- `SceneObject` implements: `Renderable`, `Resizable`, `InputHandler` -- `Panel` extends `SceneObject` for ImGui panels -- `Box` provides container with automatic layout via Yoga - -#### 2. OpenGL Rendering Pipeline (scene) -- `GlSceneManager` manages OpenGL objects and framebuffer -- `OpenGlObject` interface for all renderable 3D objects -- Render-to-texture approach with `FrameBuffer` -- `Camera` + `CameraController` for 3D navigation - -#### 3. Multi-Layer Point Cloud System -- `LayerManager` handles multiple rendering layers with priorities -- `PointLayer` for subset rendering with custom colors/sizes -- PCL bridge utilities for integration with Point Cloud Library - -## Build System & Dependencies - -### Initial Setup -```bash -# Clone with submodules -git clone --recursive https://github.com/rxdu/quickviz.git -git submodule update --init --recursive +| Use shared infrastructure | `core` | -# Install dependencies (Ubuntu 22.04/24.04) -sudo apt-get install libgl1-mesa-dev libglfw3-dev libcairo2-dev \ - libopencv-dev libglm-dev libncurses-dev +Domain modules stay tech-neutral so a future Vulkan-backed `scene` or +libpng-backed `image` slot in without churn. Inside each module, +implementation-specific types keep an explicit prefix (`Gl*` in `scene`, +`Cv*` in `image`). -# Optional: Install PCL for point cloud features -sudo apt-get install libpcl-dev +### Quickstart classes worth knowing -# Optional: Development tools -sudo apt-get install valgrind libbenchmark-dev lcov -``` +- **`quickviz::Viewer`** (`viewer/viewer.hpp`) — the GLFW window + ImGui + app shell. The fundamental primitive every QuickViz app starts from. +- **`quickviz::SceneApp`** (`scene/scene_app.hpp`) — five-line + quickstart for a 3D viewer. Wraps `Viewer + GlScenePanel + grid + + coordinate frame`. Use this when you want to display a scene fast; + drop down to `Viewer` directly when you need richer layouts. -### Build Configuration -```bash -mkdir build && cd build -cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON -make -j8 -``` +--- -### CMake Options -- `BUILD_TESTING`: Enable tests (OFF by default) -- `QUICKVIZ_DEV_MODE`: Development mode, forces tests (OFF by default) -- `ENABLE_AUTO_LAYOUT`: Enable Yoga-based automatic layout (ON by default, requires C++20) -- `BUILD_QUICKVIZ_APP`: Build the quickviz application (OFF by default) -- `VIEWER_WITH_GLAD`: Integrate GLAD for OpenGL loading (ON by default) -- `STATIC_CHECK`: Enable cppcheck static analysis (OFF by default) - -### Dependencies -**Required**: OpenGL 3.3+, GLFW3, GLM, Cairo -**Optional**: OpenCV (`image` module), PCL (`pcl_bridge`), Google Benchmark, Valgrind -**Bundled**: Dear ImGui & ImPlot, STB libraries, Yoga, GoogleTest, GLAD - -## API Design Standards - -### Public API Requirements -- **Explicit inputs**: Functions take `projection`, `view`, sizes, IDs; avoid hidden globals -- **Return handles or IDs**: Use opaque 32-bit IDs for user-visible resources; RAII classes for GL internals -- **Clear ownership**: Prefer `std::unique_ptr` in APIs; only use `std::shared_ptr` for true sharing -- **Narrow types**: Use `span` for read-only bulk data; avoid exposing STL containers -- **Unit awareness**: Document meters/seconds/radians; never assume degrees or "pixels" for world scale -- **Generic terminology**: Use standard robotics/graphics terms (Geometry, Transform, Viewport) over application-specific names (Map, World, Scene). This ensures broad applicability across different domains - -### Interface Design Patterns - -#### Extensibility Levels -Design APIs with clear extensibility boundaries: - -```cpp -// Direct Use: Simple, concrete functions -void DrawPoints(span points, glm::vec3 color, float size); -void DrawMesh(const MeshData& mesh, const Transform& transform); - -// Configurable: Parameter objects for complex configurations -struct RenderConfig { - LightingModel lighting = LightingModel::kPhong; - bool wireframe = false; - float point_size = 1.0f; -}; -void DrawMesh(const MeshData& mesh, const Transform& transform, const RenderConfig& config); - -// Extensible: Virtual interfaces for custom behavior -class Renderable { -public: - virtual void OnRender(const RenderContext& context) = 0; - virtual BoundingBox GetBounds() const = 0; -}; - -// Build-Upon: Framework classes with protected extension points -class InteractionTool { -public: - void HandleInput(const InputEvent& event); // final -protected: - virtual bool OnMouseDown(int x, int y) { return false; } // override points - virtual bool OnMouseDrag(int x, int y) { return false; } - virtual void OnToolActivated() {} -}; -``` +## 3. Library Boundary -### API Pattern Examples +`src/` is visualization-only. Editor / app-level concerns — undo/redo, +command stacks, project files, scene serialization, history panels, full +application frameworks — live in `sample/` or downstream apps and consume +the library, never the reverse. -#### Opaque Handles + Narrow Interface -```cpp -using ObjectId = uint32_t; // 0 reserved as "none" +- `sample/*` may include from `src/*/include/`. +- `src/*` must not include from `sample/`. Enforced by the + `boundary-check` CI job. +- If a sample wants something from the library, add it as a small, + visualization-justified hook to `src/` — never pull sample code in. -struct View { - glm::mat4 projection; // float - glm::mat4 view; // float - int width, height; -}; +`sample/editor/` is the canonical example: a working point-cloud editor +built entirely on top of the library. It's also the dogfood check — if a +fully-featured vis+editing app cannot be built without modifying `src/`, +the library is missing a hook and we evaluate the gap deliberately. -ObjectId CreateMesh(span vertices, span indices); -void SetTransform(ObjectId id, const glm::dmat4& world_from_object); -void DrawView(const View& view); -ObjectId PickAt(const View& view, int x, int y); -``` +--- -#### Core Interfaces -```cpp -class Renderable { - virtual bool IsVisible() = 0; - virtual void OnRender() = 0; -}; +## 4. Dependency Direction -class OpenGlObject { - virtual void OnDraw(const glm::mat4& projection, const glm::mat4& view) = 0; -}; -``` +Modules form a layered DAG. Lower layers never include from higher ones. -## Rendering Pipeline Best Practices - -### OpenGL 3.3+ Guidelines -- **Isolate passes**: Each pass sets all required GL state (program, VAO, FBO, depth, blend, cull) -- **Shared camera block**: Put `proj`/`view` in UBO; bind once per pass -- **Per-draw data**: Prefer small UBO/SSBO structs over many `glUniform*` calls -- **Debug output**: Enable `GL_KHR_debug` in debug builds - -### GPU Resource Management -- **RAII GL objects**: Thin wrappers for VAO/VBO/EBO/FBO/Program; no naked GLuints -- **Buffer usage**: - - Static: `glBufferData` or `glBufferStorage` - - Dynamic: `glMapBufferRange` with appropriate flags -- **Batching**: Sort by program → material → geometry -- **Minimize readbacks**: Only read pixels for picking; never read large buffers per frame - -### GL Pass Pattern -```cpp -class Pass { - public: - void Execute(const View& view) { - BindFbo(); - ConfigureState(); // depth/blend/cull - UseProgram(); // bind UBOs/textures - DrawAll(); // VAO binds and draw calls - } - private: - void BindFbo(); - void ConfigureState(); - void UseProgram(); - void DrawAll(); -}; ``` - -## Coordinate System & Precision - -- **CPU double, GPU float**: Keep CPU transforms in `double`; upload as `float` to shaders -- **Consistent "up" direction**: Support Z-up or Y-up at boundary; convert once internally -- **Camera sanity**: Warn if far/near > 1e6 to avoid z-fighting - -## Point Cloud Enhancement Features - -### PCL Integration -- Import/export between PCL and renderer formats -- Visualization of PCL algorithm results (clusters, surfaces) -- Template-based conversions for all PCL point types - -### Multi-Layer Rendering System -**Core Features**: -- Priority-based layer composition (higher priority renders on top) -- Multiple highlight modes: surface fill, outline, size increase -- Index buffer optimization for efficient batch rendering (60-100x improvement) -- 3D sphere rendering with Phong lighting - -**Usage Example**: -```cpp -// Create selection layer -auto selection_layer = point_cloud->CreateLayer("selection", 100); -selection_layer->SetPoints(selected_indices); -selection_layer->SetColor(glm::vec3(1.0f, 1.0f, 0.0f)); // Yellow -selection_layer->SetPointSizeMultiplier(1.5f); -selection_layer->SetHighlightMode(PointLayer::HighlightMode::kSphereSurface); -selection_layer->SetVisible(true); +core ─► viewer ─► (scene | plot | canvas | image) ─► samples + │ +pcl_bridge ◄──── scene ───────┘ (optional adapters depend outward) ``` -## Interaction & Tool Patterns - -These patterns are in scope for the library — they support inspection, -selection, and visual feedback. Editing semantics (undoable mutations, -project files, history) are an app-side concern; see `sample/editor/`. - -### Two-Stage Picking -1. **GPU ID buffer** → object ID -2. **CPU raycast** → precise hit/feature (BVH/KD-tree) - -### Tool State Machine -- One active tool at a time, registered via `SceneManager::RegisterTool` -- Tools consume input and emit selection / hover / measurement events -- Gizmos drawn as overlay with selective depth testing -- Editor apps may layer their own command-based mutations on top of these - events; the library tools themselves do not record history +- `core` depends on nothing else in the library. +- `viewer` depends on `core` (and ImGui/GLFW/Yoga from third_party). +- Visualization modules (`scene`, `plot`, `canvas`, `image`) depend on + `core` + `viewer`. They do **not** depend on each other unless there + is a concrete need (currently none do). +- Adapters (`pcl_bridge`, optional OpenCV in `image`) depend outward; + no in-library code depends on them. -## Threading Model +When tempted to add a cross-module dependency, ask: is this concept truly +shared, or am I leaking implementation? Prefer to widen the public API of +the lower module than to reach across at the same level. -- **GL main-thread only**: All GL calls and ImGui rendering on render thread -- **Background stages**: File I/O, decoding, normal generation, BVH builds -- **Handoff boundary**: Jobs produce immutable CPU buffers; enqueue main-thread task for GL resources -- **No frame stalls**: Never wait on background jobs in frame loop +--- -## Code Quality Standards +## 5. Build System -### Style Guide -- **C++ Standard**: C++17 (C++14 minimum for Ubuntu 20.04) -- **File Extensions**: `.cpp` for source, `.hpp` for headers -- **Style**: Google C++ style guide, clang-format -- **Line Length**: 100 characters +Required: OpenGL 3.3+, GLFW3, GLM, Cairo. +Optional: OpenCV (enables `image`), PCL (enables `pcl_bridge`), +Google Benchmark, Valgrind, lcov. +Bundled in `third_party/`: ImGui, ImPlot, ImPlot3D, STB, Yoga, GoogleTest, GLAD. -### Testing Strategy -- **Unit Tests**: Core functionality (`tests/unit/`) -- **Integration Tests**: Module interactions (`tests/integration/`) -- **Memory Tests**: Leak detection (`tests/memory/`) -- **Benchmarks**: Performance testing (`tests/benchmarks/`) - -### Test Execution ```bash -# Basic tests +git clone --recursive +git submodule update --init --recursive +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON +make -j$(nproc) ctest --output-on-failure - -# Comprehensive test suite -../scripts/run_tests.sh - -# With Valgrind and coverage -../scripts/run_tests.sh -v -c ``` -### Performance Hygiene -- Avoid per-frame heap churn: pre-allocate vectors, reuse temporaries -- Prefer SoA (structure-of-arrays) for large numeric datasets -- Keep shader branches simple; use separate passes for complex modes -- Profile both CPU and GPU; log slowest draw calls in debug - -## Error Handling & Robustness - -- **Structured logs**: Include frame number, pass name, object ID -- **Graceful degradation**: Render fallback (magenta material) on shader/asset failure -- **Debug assertions**: `DCHECK` invariants in debug builds only -- **GL debug context**: Enable in debug builds; treat high severity as test failures - -## External Dependencies & Bridges - -- Put adapters to heavy dependencies behind **small interfaces** -- Keep core build working when optional dependencies are **absent** -- No upstream types in public headers (don't leak `pcl::PointXYZ`) - -## Development Workflow - -### Feature Development -1. Create feature branch from `devel` -2. Implement with tests using established patterns -3. Run `scripts/run_tests.sh` before committing -4. PR to `devel` for review - -### Common Tasks - -**Add New Renderable Object**: -1. Inherit from `OpenGlObject` in `src/scene/renderable/` -2. Implement shader loading and VAO/VBO setup -3. Override `OnDraw()` with OpenGL render calls -4. Add to `GlSceneManager` in application - -**Create Custom UI Panel**: -1. Inherit from `Panel` in `src/viewer/` -2. Override `Begin()` and `End()` methods with ImGui calls -3. Add panel to `Viewer` or `Box` container - -### Refactor Playbook -1. **Define boundary** (what belongs inside vs. outside) -2. **Write tiny interface** (2-5 functions, minimal templates) -3. **Add facade** that forwards to existing code -4. **Add tests** around facade -5. **Move code** under facade into new module -6. **Delete old paths** once coverage passes -7. **Measure performance** to ensure no regression - -## Decision Heuristics - -- **Expose or hide?** If type couples callers to OpenGL/third-party, **hide** it -- **Template or runtime?** If callers won't benefit from compile-time polymorphism, **prefer runtime** -- **One pass or two?** If branch toggles many states, **split into passes** -- **CPU or GPU?** Precision/topology → **CPU** (BVH/KD); per-pixel labeling → **GPU** (ID buffer) -- **Immediate or queued?** GPU resource allocation should be **queued** to render thread -- **Generic or specific?** Prefer generic robotics/graphics terms over application-specific names to maximize reusability -- **Direct use or extensible?** Simple, common operations should be directly callable; complex customization should use virtual interfaces -- **Framework or library?** Provide building blocks that users compose rather than frameworks that dictate application structure - -## PR Review Checklist - -- [ ] No GL calls off render thread -- [ ] Functions have explicit inputs; no new hidden globals -- [ ] Render paths set depth/blend/cull/program/VAO/FBO explicitly -- [ ] CPU uses double for geometry; GPU uniforms are float -- [ ] Picking reads exactly one pixel; ray logic has tests -- [ ] No raw GL handles in public headers -- [ ] No per-frame allocations on hot paths -- [ ] clang-format/clang-tidy clean; zero new warnings -- [ ] Documentation for non-obvious decisions -- [ ] Generic terminology used (avoid application-specific names) -- [ ] Interface boundaries clearly defined (direct use vs. extensible vs. build-upon) -- [ ] Building blocks remain composable and reusable across different applications - -## Development Rules - -- Remember to update TODO.md after getting approval for new tasks -- Always update TODO.md after finishing tasks -- Document architectural decisions with brief "Why this way?" notes -- Maintain backwards compatibility within major versions -- Prefer measured optimization over premature optimization - -## Platform Support - -- **Linux**: Primary platform (Ubuntu 20.04/22.04/24.04) -- **Windows**: Experimental via vcpkg -- **macOS**: Not officially supported - -## Current Development Focus - -Active areas of development: -- Interactive selection tools -- PCL algorithm result visualization -- Measurement and annotation overlays -- Level-of-detail system for large datasets - -See `TODO.md` for detailed roadmap and implementation status. \ No newline at end of file +Common CMake options: + +- `BUILD_TESTING` (default OFF) — build unit + integration tests +- `ENABLE_AUTO_LAYOUT` (default ON, requires C++20) — Yoga flexbox layout +- `VIEWER_WITH_GLAD` (default ON) — bundle GLAD as the GL loader +- `STATIC_CHECK` (default OFF) — run cppcheck during build + +CI runs the `boundary-check` job before the full build matrix; failing it +blocks the rest of the pipeline. + +--- + +## 6. Code Style + +- **Standard**: C++17 (some C++20 features behind `ENABLE_AUTO_LAYOUT`). +- **Files**: `.cpp` for source, `.hpp` for headers. Snake_case filenames. +- **Style**: Google C++ Style; clang-format applied. Line length 100. +- **Naming**: + - Types: `PascalCase`. Methods: `PascalCase()`. Members: `snake_case_`. + - Implementation prefixes are deliberate signals: `Gl*` means "wraps an + OpenGL handle"; `Cv*` means "uses OpenCV". Don't strip them when a + type genuinely couples to that backend. + - Module directories are snake_case; namespaces are `quickviz::*` (or + a sub-namespace in samples). +- **No `using namespace` in headers.** Inside `.cpp` files, `using + namespace quickviz` is acceptable. +- **Include order**: own header → standard library → third-party → + project headers, separated by blank lines. Each block alphabetized. +- **Header guards**: `QUICKVIZ___HPP` form is preferred; do + not use `#pragma once` in public headers. +- **File size**: aim for ~500 LOC. Files significantly above that are + candidates for splitting along internal seams. + +--- + +## 7. Architecture Patterns + +### Scene rendering +- `OpenGlObject` (`scene/interface/opengl_object.hpp`) is the base for + anything rendered in 3D. Subclasses own GPU resources via RAII. +- `SceneManager` owns OpenGL objects keyed by name; `GlScenePanel` hosts + the scene inside an ImGui window via render-to-texture. +- Per-pass: each draw path explicitly sets program, VAOs, depth, blend, + and cull state — never inherit it from the previous pass. + +### Selection +- Two-stage picking: GPU ID-buffer for object/point ID → CPU raycast for + precise hit. Pick reads exactly one pixel; never large readbacks. +- `SelectionManager` and `PointSelectionTool` provide ready-made + selection without an editor. Selection events are visualization + events; if you want them to be undoable, layer a Command pattern on + top in your app (`sample/editor/` shows how). + +### Tools +- `InteractionTool` registers with `SceneManager`. One active tool at a + time. Tools emit selection / hover / measurement events; they do not + record history. + +### Panels and layout +- `Panel` (in `viewer`) is the ImGui-based base for any UI panel. +- `Box` provides flexbox layout via Yoga (when `ENABLE_AUTO_LAYOUT`). +- For "give me a 3D viewer, fast", use `SceneApp`; for richer layouts, + compose `Viewer` + panels + `Box` directly. + +--- + +## 8. Threading Model + +- **GL main-thread only.** All OpenGL calls and ImGui rendering happen + on the render thread. Never make GL calls from background threads. +- **Background work** (file I/O, decoding, BVH builds, normal generation) + produces immutable CPU buffers and queues a main-thread task to upload + to GPU. +- **No frame stalls.** The render loop must never block on a background + job. Use `core/buffer` (RingBuffer, DoubleBuffer) for handoff. +- `core/event/AsyncEventDispatcher` provides bounded async event delivery + if the app needs it; do not roll a new threading framework into the + library without explicit decision. + +--- + +## 9. API Design Principles + +- **Small surface, strong contracts.** Every public function should have + a clear precondition, postcondition, and ownership story. +- **Explicit inputs.** Functions take `projection`, `view`, sizes, IDs; + no hidden globals. +- **Clear ownership.** Prefer `std::unique_ptr` in APIs; use + `std::shared_ptr` only for true sharing. Return raw pointers/refs only + when ownership is documented as remaining elsewhere. +- **Narrow types.** Prefer `std::span` for read-only bulk data. + Avoid leaking STL container choices in public headers. +- **Unit awareness.** Document meters, seconds, radians. Never assume + degrees or "pixels" for world-scale values. +- **CPU double, GPU float.** Keep CPU transforms in `double`; upload as + `float` to shaders. Caller-visible math is `double` unless documented + otherwise. +- **No upstream types in public headers.** `pcl::*` and `cv::*` types are + hidden behind the bridges/adapters. + +--- + +## 10. Error Handling + +- **Validate at boundaries.** Library entry points check their inputs + and report failures precisely. Internal helpers may trust callers. +- **Fail loud, fail early.** Throw on contract violations; do not return + silently corrupt data. `std::runtime_error` is the default; project + may add specific types where that helps. +- **Graceful rendering degradation.** On shader/asset load failure, log + the error and render a fallback (magenta material) so the rest of the + scene stays visible. +- **No silent error suppression.** `catch(...) {}` requires a comment + explaining why and what's lost. The deleted `SceneManagerBridge` is + the cautionary tale: silent fallbacks that fabricated data hid real + failures. + +--- + +## 11. Decision Heuristics + +When designing or reviewing changes, prefer: + +- **Hide if it leaks.** If a type couples callers to OpenGL or third-party + details, hide it behind an interface. +- **Runtime over compile-time.** Templates earn their keep with concrete + performance or type-safety wins. Otherwise, prefer runtime polymorphism. +- **More passes over more state.** If a render path branches heavily on + state, split it into separate passes. +- **CPU for precision/topology, GPU for per-pixel.** BVH/KD on CPU; ID + buffers and shading on GPU. +- **Queued over immediate** for GPU-resource creation off the main thread. +- **Generic over specific.** Robotics/graphics vocabulary, not + application vocabulary. +- **Library stays a library.** When in doubt, push the feature to + `sample/` and let it prove it belongs in `src/`. + +--- + +## 12. Tests + +- Unit tests live next to the module: `src//test/`. +- Cross-module integration tests live in `tests/integration/`. +- GoogleTest is the framework; new tests follow the patterns of the + existing ones. +- Renderable tests use `SceneApp` as their harness. +- Aim for 80% coverage overall. Designated hot paths should aim for 100%. + +--- + +## 13. Commits & Documentation + +- Commit messages: `type(scope): subject` form. Explain *why* in the + body, not what the diff already shows. One logical change per commit. +- Don't amend pushed commits. Force-push to `main` is forbidden without + explicit approval. +- `TODO.md` tracks active and recent work — keep it terse, factual, + one-bullet-per-outcome. No marketing language. +- `docs/notes/` holds longer design references and historical decisions. +- After completing work, update `TODO.md` in the same change. + +--- + +## 14. Where Things Live + +- **Code by mission**: `src//`, see Module Map above. +- **Reference apps**: `sample/quickstart/` (smallest possible), + `sample/pointcloud_viewer/` (file → render with selection), + `sample/editor/` (vis+editing dogfood check), + `sample/quickviz_demo_app/` (broader app shell demo). +- **Active TODOs**: `TODO.md` (root). +- **Architecture deep-dives**: `docs/notes/`. +- **Build / install**: `README.md`. +- **CI**: `.github/workflows/default.yml` (note: includes a + `boundary-check` job that gates the build matrix). From 135070aa0fffc8db039bc961b624e36036d4a0c0 Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 21:35:58 +0800 Subject: [PATCH 09/22] feat(core): add quickviz::demo::* synthetic data generators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Small, deterministic data generators so users can explore the library without supplying their own data files. Returned structures match the shapes the renderables expect (`PointCloud::SetPoints`, `Mesh::Set...`) so demos and tests can drop them straight in. API in core/demo.hpp: - struct PointCloudData { points, colors } - struct MeshData { vertices, indices } - SpiralCloud(num_points, radius, height, turns) - PlanarPointGrid(rows, cols, spacing) - NoiseCloud(num_points, sigma, seed) - CubeMesh(center, size) - Trajectory(num_points, scale) All generators are pure functions of their inputs — no global state, no I/O, deterministic for the seed/parameters provided. Lives in `core` because the data is generic (just glm vectors); any module can use it. 8 unit tests (test_demo.cpp) covering size correctness, edge cases (zero points), determinism, geometric invariants. Total ctest count: 107 → 115; same 2 pre-existing PCL failures, no new regressions. Used by upcoming sample/quickstart/ to bootstrap a 30-line app without requiring the user to have data on disk. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/core/CMakeLists.txt | 4 +- src/core/include/core/demo.hpp | 114 ++++++++++++++++++++ src/core/src/demo.cpp | 141 +++++++++++++++++++++++++ src/core/test/unit_test/CMakeLists.txt | 3 +- src/core/test/unit_test/test_demo.cpp | 84 +++++++++++++++ 5 files changed, 343 insertions(+), 3 deletions(-) create mode 100644 src/core/include/core/demo.hpp create mode 100644 src/core/src/demo.cpp create mode 100644 src/core/test/unit_test/test_demo.cpp diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index fb78e0c..4011443 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -3,10 +3,10 @@ find_package(Threads REQUIRED) # add library add_library(core - # widgets src/buffer/buffer_registry.cpp src/event/async_event_dispatcher.cpp - src/event/input_mapping.cpp) + src/event/input_mapping.cpp + src/demo.cpp) target_link_libraries(core PUBLIC Threads::Threads imcore) target_include_directories(core PUBLIC $ diff --git a/src/core/include/core/demo.hpp b/src/core/include/core/demo.hpp new file mode 100644 index 0000000..d1e1063 --- /dev/null +++ b/src/core/include/core/demo.hpp @@ -0,0 +1,114 @@ +/* + * @file demo.hpp + * @brief Synthetic data generators for quickstarts, tutorials, and tests + * + * `quickviz::demo` provides small, deterministic data generators so users + * can explore the library without supplying their own data files. Returned + * structures match the shapes expected by the library's renderables — pass + * a `PointCloudData` straight into `PointCloud::SetPoints`, a `MeshData` + * straight into `Mesh::SetVertices`/`SetIndices`. + * + * All generators are pure functions of their inputs: no global state, no + * I/O, deterministic for the seeds you provide. + * + * Copyright (c) 2026 Ruixiang Du (rdu) + */ + +#ifndef QUICKVIZ_CORE_DEMO_HPP +#define QUICKVIZ_CORE_DEMO_HPP + +#include +#include +#include + +#include + +namespace quickviz::demo { + +/** + * @brief Result of a point-cloud generator: positions + per-point RGB. + * + * `points` and `colors` are always the same size. Pass directly into + * `PointCloud::SetPoints(points, colors)`. + */ +struct PointCloudData { + std::vector points; + std::vector colors; +}; + +/** + * @brief Result of a mesh generator: vertex positions + triangle indices. + * + * Pass `vertices` to `Mesh::SetVertices` and `indices` to `Mesh::SetIndices`. + * Indices are 32-bit unsigned for direct compatibility with the GL element + * buffer format used by the `Mesh` renderable. + */ +struct MeshData { + std::vector vertices; + std::vector indices; +}; + +/** + * @brief Generate a vertical helix of coloured points. + * + * Useful as the canonical "hello, world" point cloud — visually + * distinctive, deterministic, and small. Color is HSV-derived from the + * height parameter so the result is colorful without needing extra + * configuration. + * + * @param num_points Number of points along the helix (≥ 1). + * @param radius Maximum spiral radius in world units. + * @param height Total vertical extent (the spiral spans ±height/2). + * @param turns How many full revolutions across the height. + */ +PointCloudData SpiralCloud(std::size_t num_points, + float radius = 3.0f, + float height = 6.0f, + float turns = 8.0f); + +/** + * @brief Generate a regular grid of points lying in the Z=0 plane. + * + * Colors interpolate diagonally so adjacent points are distinguishable. + * + * @param rows Number of grid rows (>= 1). + * @param cols Number of grid columns (>= 1). + * @param spacing Distance between neighbouring points in world units. + */ +PointCloudData PlanarPointGrid(std::size_t rows, + std::size_t cols, + float spacing = 0.1f); + +/** + * @brief Generate a Gaussian-noise point cloud centred on the origin. + * + * @param num_points Number of points. + * @param sigma Standard deviation along each axis in world units. + * @param seed Seed for reproducibility (0 = use a fixed default). + */ +PointCloudData NoiseCloud(std::size_t num_points, + float sigma = 1.0f, + unsigned seed = 0); + +/** + * @brief Generate a unit cube mesh centred on `center` with the given size. + * + * 8 vertices, 12 triangles (36 indices). Suitable as a basic Mesh demo. + */ +MeshData CubeMesh(glm::vec3 center = glm::vec3(0.0f), float size = 1.0f); + +/** + * @brief Generate a smooth synthetic 3D trajectory. + * + * The trajectory is a Lissajous-like curve in 3D — useful for showing + * line strips and path renderables without needing real robot data. + * + * @param num_points Number of samples along the trajectory. + * @param scale Overall extent in world units. + */ +std::vector Trajectory(std::size_t num_points, + float scale = 5.0f); + +} // namespace quickviz::demo + +#endif // QUICKVIZ_CORE_DEMO_HPP diff --git a/src/core/src/demo.cpp b/src/core/src/demo.cpp new file mode 100644 index 0000000..30eca68 --- /dev/null +++ b/src/core/src/demo.cpp @@ -0,0 +1,141 @@ +/* + * @file demo.cpp + * + * Copyright (c) 2026 Ruixiang Du (rdu) + */ + +#include "core/demo.hpp" + +#include +#include + +namespace quickviz::demo { + +namespace { + +// HSV → RGB with H in [0,1), S = V = 1. +glm::vec3 HueToRgb(float h) { + const float r = std::abs(h * 6.0f - 3.0f) - 1.0f; + const float g = 2.0f - std::abs(h * 6.0f - 2.0f); + const float b = 2.0f - std::abs(h * 6.0f - 4.0f); + return glm::clamp(glm::vec3{r, g, b}, 0.0f, 1.0f); +} + +constexpr float kPi = 3.14159265358979323846f; + +} // namespace + +PointCloudData SpiralCloud(std::size_t num_points, + float radius, + float height, + float turns) { + PointCloudData out; + if (num_points == 0) return out; + + out.points.reserve(num_points); + out.colors.reserve(num_points); + + const float denom = static_cast(num_points - 1); + for (std::size_t i = 0; i < num_points; ++i) { + const float t = (num_points == 1) ? 0.0f : static_cast(i) / denom; + const float angle = t * 2.0f * kPi * turns; + const float r = t * radius; + const float z = (t - 0.5f) * height; + out.points.emplace_back(r * std::cos(angle), r * std::sin(angle), z); + out.colors.push_back(HueToRgb(t)); + } + return out; +} + +PointCloudData PlanarPointGrid(std::size_t rows, + std::size_t cols, + float spacing) { + PointCloudData out; + if (rows == 0 || cols == 0) return out; + + const std::size_t n = rows * cols; + out.points.reserve(n); + out.colors.reserve(n); + + const float row_denom = (rows == 1) ? 1.0f : static_cast(rows - 1); + const float col_denom = (cols == 1) ? 1.0f : static_cast(cols - 1); + const float origin_x = -0.5f * static_cast(cols - 1) * spacing; + const float origin_y = -0.5f * static_cast(rows - 1) * spacing; + + for (std::size_t r = 0; r < rows; ++r) { + for (std::size_t c = 0; c < cols; ++c) { + out.points.emplace_back(origin_x + static_cast(c) * spacing, + origin_y + static_cast(r) * spacing, + 0.0f); + const float t = + 0.5f * (static_cast(r) / row_denom + + static_cast(c) / col_denom); + out.colors.push_back(HueToRgb(t)); + } + } + return out; +} + +PointCloudData NoiseCloud(std::size_t num_points, float sigma, unsigned seed) { + PointCloudData out; + if (num_points == 0) return out; + + std::mt19937 rng(seed == 0 ? 0xC0FFEEu : seed); + std::normal_distribution dist(0.0f, sigma); + + out.points.reserve(num_points); + out.colors.reserve(num_points); + for (std::size_t i = 0; i < num_points; ++i) { + out.points.emplace_back(dist(rng), dist(rng), dist(rng)); + // Color encodes distance from origin so denser clusters read visually. + const float d = + glm::length(out.points.back()) / (3.0f * std::max(sigma, 1e-6f)); + out.colors.push_back(HueToRgb(glm::clamp(d, 0.0f, 1.0f))); + } + return out; +} + +MeshData CubeMesh(glm::vec3 center, float size) { + MeshData out; + const float h = 0.5f * size; + + out.vertices = { + // 0..3: -Z face + center + glm::vec3{-h, -h, -h}, center + glm::vec3{ h, -h, -h}, + center + glm::vec3{ h, h, -h}, center + glm::vec3{-h, h, -h}, + // 4..7: +Z face + center + glm::vec3{-h, -h, h}, center + glm::vec3{ h, -h, h}, + center + glm::vec3{ h, h, h}, center + glm::vec3{-h, h, h}, + }; + + // 12 triangles, CCW when viewed from outside. + out.indices = { + 0, 2, 1, 0, 3, 2, // -Z + 4, 5, 6, 4, 6, 7, // +Z + 0, 1, 5, 0, 5, 4, // -Y + 1, 2, 6, 1, 6, 5, // +X + 2, 3, 7, 2, 7, 6, // +Y + 3, 0, 4, 3, 4, 7, // -X + }; + return out; +} + +std::vector Trajectory(std::size_t num_points, float scale) { + std::vector out; + if (num_points == 0) return out; + + out.reserve(num_points); + const float denom = (num_points == 1) ? 1.0f + : static_cast(num_points - 1); + for (std::size_t i = 0; i < num_points; ++i) { + const float t = static_cast(i) / denom; + const float angle = t * 2.0f * kPi; + // 3:2:1 Lissajous in 3D. + out.emplace_back(scale * std::sin(3.0f * angle), + scale * std::sin(2.0f * angle), + 0.5f * scale * std::sin(angle)); + } + return out; +} + +} // namespace quickviz::demo diff --git a/src/core/test/unit_test/CMakeLists.txt b/src/core/test/unit_test/CMakeLists.txt index 0347dfe..d2fbf8e 100644 --- a/src/core/test/unit_test/CMakeLists.txt +++ b/src/core/test/unit_test/CMakeLists.txt @@ -5,7 +5,8 @@ add_executable(core_unit_tests test_input_event.cpp test_event_system.cpp test_thread_safe_queue.cpp - test_buffer_registry.cpp) + test_buffer_registry.cpp + test_demo.cpp) target_link_libraries(core_unit_tests PRIVATE gtest_main gmock viewer) # get_target_property(PRIVATE_HEADERS viewer INCLUDE_DIRECTORIES) target_include_directories(core_unit_tests PRIVATE ${PRIVATE_HEADERS}) diff --git a/src/core/test/unit_test/test_demo.cpp b/src/core/test/unit_test/test_demo.cpp new file mode 100644 index 0000000..ea09b5e --- /dev/null +++ b/src/core/test/unit_test/test_demo.cpp @@ -0,0 +1,84 @@ +/* + * @file test_demo.cpp + * + * Copyright (c) 2026 Ruixiang Du (rdu) + */ + +#include + +#include + +#include "core/demo.hpp" + +namespace quickviz::demo { +namespace { + +TEST(DemoSpiralCloud, SizesMatchAndAreNonEmpty) { + auto result = SpiralCloud(/*num_points=*/100); + EXPECT_EQ(result.points.size(), 100u); + EXPECT_EQ(result.colors.size(), 100u); +} + +TEST(DemoSpiralCloud, ZeroPointsReturnsEmpty) { + auto result = SpiralCloud(0); + EXPECT_TRUE(result.points.empty()); + EXPECT_TRUE(result.colors.empty()); +} + +TEST(DemoSpiralCloud, FillsRequestedHeightRange) { + constexpr float kHeight = 4.0f; + auto result = SpiralCloud(64, /*radius=*/1.0f, kHeight, /*turns=*/2.0f); + ASSERT_FALSE(result.points.empty()); + EXPECT_NEAR(result.points.front().z, -kHeight * 0.5f, 1e-5f); + EXPECT_NEAR(result.points.back().z, kHeight * 0.5f, 1e-5f); +} + +TEST(DemoPlanarPointGrid, GridShape) { + auto result = PlanarPointGrid(3, 4, 0.1f); + EXPECT_EQ(result.points.size(), 12u); + EXPECT_EQ(result.colors.size(), 12u); + // Every point lies in the Z=0 plane. + for (const auto& p : result.points) EXPECT_FLOAT_EQ(p.z, 0.0f); +} + +TEST(DemoNoiseCloud, IsDeterministicForSameSeed) { + auto a = NoiseCloud(50, 1.0f, /*seed=*/42); + auto b = NoiseCloud(50, 1.0f, /*seed=*/42); + ASSERT_EQ(a.points.size(), b.points.size()); + for (std::size_t i = 0; i < a.points.size(); ++i) { + EXPECT_FLOAT_EQ(a.points[i].x, b.points[i].x); + EXPECT_FLOAT_EQ(a.points[i].y, b.points[i].y); + EXPECT_FLOAT_EQ(a.points[i].z, b.points[i].z); + } +} + +TEST(DemoCubeMesh, HasEightVerticesAndTwelveTriangles) { + auto mesh = CubeMesh(); + EXPECT_EQ(mesh.vertices.size(), 8u); + EXPECT_EQ(mesh.indices.size(), 36u); // 12 triangles × 3 indices + for (auto idx : mesh.indices) EXPECT_LT(idx, 8u); +} + +TEST(DemoCubeMesh, RespectsCenterAndSize) { + auto mesh = CubeMesh(glm::vec3{1.0f, 2.0f, 3.0f}, 2.0f); + // All vertices should sit on the bounding box of the requested cube. + for (const auto& v : mesh.vertices) { + EXPECT_NEAR(std::abs(v.x - 1.0f), 1.0f, 1e-5f); + EXPECT_NEAR(std::abs(v.y - 2.0f), 1.0f, 1e-5f); + EXPECT_NEAR(std::abs(v.z - 3.0f), 1.0f, 1e-5f); + } +} + +TEST(DemoTrajectory, HasRequestedSizeAndIsBounded) { + constexpr float kScale = 5.0f; + auto traj = Trajectory(200, kScale); + EXPECT_EQ(traj.size(), 200u); + for (const auto& p : traj) { + EXPECT_LE(std::abs(p.x), kScale + 1e-4f); + EXPECT_LE(std::abs(p.y), kScale + 1e-4f); + EXPECT_LE(std::abs(p.z), 0.5f * kScale + 1e-4f); + } +} + +} // namespace +} // namespace quickviz::demo From c6b9a2a5c473a823750b6a35c32c479a3e107581 Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 21:37:17 +0800 Subject: [PATCH 10/22] feat(sample/quickstart): smallest possible QuickViz program MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A 25-line app (18 lines of meaningful code) that opens a window, draws a colored 3D helix, and lets you orbit the camera. Built entirely from the high-level facades that exist for exactly this purpose: SceneApp — 5-line viewer setup demo::SpiralCloud — synthetic data, no file required PointCloud — the renderable Together they prove the "minimal boilerplate" goal: a roboticist can go from `git clone` to "I see something on screen" in a single readable file. No layout boilerplate, no GLFW glue, no shaders. Files: - sample/quickstart/main.cpp — the program - sample/quickstart/README.md — annotated walkthrough + when to drop down to Viewer + Panel directly - sample/quickstart/CMakeLists.txt — links core, scene, viewer Registered in sample/CMakeLists.txt; doesn't require PCL (unlike the other samples), so the build adds the binary even on minimal systems. Build clean. No new tests; the binary itself is the proof. Library boundary holds (sample → src is one-way, as enforced by CI). Co-Authored-By: Claude Opus 4.7 (1M context) --- sample/CMakeLists.txt | 1 + sample/quickstart/CMakeLists.txt | 5 +++ sample/quickstart/README.md | 75 ++++++++++++++++++++++++++++++++ sample/quickstart/main.cpp | 30 +++++++++++++ 4 files changed, 111 insertions(+) create mode 100644 sample/quickstart/CMakeLists.txt create mode 100644 sample/quickstart/README.md create mode 100644 sample/quickstart/main.cpp diff --git a/sample/CMakeLists.txt b/sample/CMakeLists.txt index 5e0931e..6aeaeee 100644 --- a/sample/CMakeLists.txt +++ b/sample/CMakeLists.txt @@ -1,5 +1,6 @@ # applications if (ENABLE_AUTO_LAYOUT) + add_subdirectory(quickstart) add_subdirectory(quickviz_demo_app) # Silence PCL-era policy warnings, but keep modern behavior where safe diff --git a/sample/quickstart/CMakeLists.txt b/sample/quickstart/CMakeLists.txt new file mode 100644 index 0000000..a702677 --- /dev/null +++ b/sample/quickstart/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(quickviz_quickstart main.cpp) +target_link_libraries(quickviz_quickstart PRIVATE + core + scene + viewer) diff --git a/sample/quickstart/README.md b/sample/quickstart/README.md new file mode 100644 index 0000000..7d841e6 --- /dev/null +++ b/sample/quickstart/README.md @@ -0,0 +1,75 @@ +# sample/quickstart — the smallest QuickViz program + +This sample exists to prove that QuickViz lets you build a working +visualization tool in roughly thirty lines of code. If you just cloned the +repo and want to know "how do I display *something*", read `main.cpp` — +that's the answer. + +## What it does + +Opens a window, draws a colored helix made of 2000 points in 3D space, lets +you orbit the camera with the mouse. Closes when you close the window. + +## The whole program (with annotations) + +```cpp +#include "core/demo.hpp" // synthetic data generators +#include "scene/renderable/point_cloud.hpp" +#include "scene/scene_app.hpp" // 5-line quickstart facade + +int main() { + quickviz::SceneApp::Config config; // sensible defaults: grid + axes on + config.window_title = "QuickViz Quickstart"; + quickviz::SceneApp app(config); + + app.SetSceneSetup([](quickviz::SceneManager* scene) { + // SceneSetup runs once after the OpenGL context is ready. + auto data = quickviz::demo::SpiralCloud(2000); // synthetic points + auto cloud = std::make_unique(); + cloud->SetPointSize(4.0f); + cloud->SetPoints(data.points, data.colors); + scene->AddOpenGLObject("spiral", std::move(cloud)); + }); + + app.Run(); // blocks until window is closed +} +``` + +That's it. No layout boilerplate, no manual ImGui setup, no GLFW glue, no +shaders to write. + +## What you can change without learning more + +- Swap `SpiralCloud` for `PlanarPointGrid(50, 50, 0.1f)` or + `NoiseCloud(5000, 1.5f)` — see `core/demo.hpp` for the full list. +- Add a `Mesh` instead of (or alongside) the cloud: + + ```cpp + auto mesh_data = quickviz::demo::CubeMesh(glm::vec3{0, 0, 0}, 1.5f); + auto mesh = std::make_unique(); + mesh->SetVertices(mesh_data.vertices); + mesh->SetIndices(mesh_data.indices); + scene->AddOpenGLObject("cube", std::move(mesh)); + ``` +- Toggle the reference grid or coordinate frame via + `config.show_grid = false;` etc. + +## When to drop down from `SceneApp` + +`SceneApp` is the path of least resistance, not the only door. Reach for +`Viewer` + `Panel` directly when you need: + +- Multiple panels (3D + plot + image, tabbed layouts, dockable windows) +- Custom UI panels alongside the scene +- A history / tools / properties sidebar + +Look at `sample/pointcloud_viewer/` and `sample/editor/` for examples that +compose `Viewer` directly. + +## Building & running + +```bash +cmake -S . -B build -DBUILD_TESTING=ON +cmake --build build -j +./build/bin/quickviz_quickstart +``` diff --git a/sample/quickstart/main.cpp b/sample/quickstart/main.cpp new file mode 100644 index 0000000..f4be6b0 --- /dev/null +++ b/sample/quickstart/main.cpp @@ -0,0 +1,30 @@ +/* + * @file main.cpp + * @brief The smallest possible QuickViz program — proves SceneApp delivers + * on its "5-line quickstart" promise. ~25 lines of meaningful code. + * + * Copyright (c) 2026 Ruixiang Du (rdu) + */ + +#include + +#include "core/demo.hpp" +#include "scene/renderable/point_cloud.hpp" +#include "scene/scene_app.hpp" + +int main() { + quickviz::SceneApp::Config config; + config.window_title = "QuickViz Quickstart"; + quickviz::SceneApp app(config); + + app.SetSceneSetup([](quickviz::SceneManager* scene) { + auto data = quickviz::demo::SpiralCloud(/*num_points=*/2000); + auto cloud = std::make_unique(); + cloud->SetPointSize(4.0f); + cloud->SetPoints(data.points, data.colors); + scene->AddOpenGLObject("spiral", std::move(cloud)); + }); + + app.Run(); + return 0; +} From 22ef6470fb5c041a540f40b83b948f55be1d4aee Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 21:39:24 +0800 Subject: [PATCH 11/22] feat(core): add quickviz::DataStream for streaming sensor data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DataStream is the recommended way to bring background-thread data (sensor frames, robot poses, processed clouds) to the render thread: // Producer thread (any thread, any frequency): stream.Push(latest_value); // Render thread (once per frame): T out; if (stream.TryPull(out)) renderable->Update(out); Semantics: latest-only, lossy. Intermediate pushes between two pulls are silently dropped — visualization rarely cares about yesterday's frame when today's is in hand. Render thread never blocks. For lossless delivery (every sample), use RingBuffer directly instead. Implementation: thin wrapper over the existing DoubleBuffer primitive in core/buffer/. The novelty is the API shape and the documented semantics, not the synchronization machinery — that was already there. API: - Push(const T&) / Push(T&&) - bool TryPull(T& out) — non-blocking; out-param overload - std::optional TryPull() — convenient if(auto v = ...) form Header-only template in core/data_stream.hpp; no impl file needed. 7 unit tests (test_data_stream.cpp) covering: empty pull, push/pull round-trip, latest-only drop semantics, move overload, and a producer/consumer threaded smoke test that asserts the consumer never sees a value go backwards. ctest count: 115 → 122; same 2 pre-existing PCL failures, no new regressions. Closes the third of three "fast onramp for robotics" workstreams (SceneApp, demo helpers, DataStream). The next sample iteration could demonstrate DataStream by streaming a moving point cloud, but that's a follow-up — not part of this commit's scope. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/core/include/core/data_stream.hpp | 91 ++++++++++++++++ src/core/test/unit_test/CMakeLists.txt | 3 +- src/core/test/unit_test/test_data_stream.cpp | 103 +++++++++++++++++++ 3 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 src/core/include/core/data_stream.hpp create mode 100644 src/core/test/unit_test/test_data_stream.cpp diff --git a/src/core/include/core/data_stream.hpp b/src/core/include/core/data_stream.hpp new file mode 100644 index 0000000..af7585e --- /dev/null +++ b/src/core/include/core/data_stream.hpp @@ -0,0 +1,91 @@ +/* + * @file data_stream.hpp + * @brief Latest-only producer/consumer channel for streaming data + * + * `DataStream` is the recommended way to bring sensor data, robot poses, + * point clouds, or any other periodically-updated value from a background + * thread to the render thread. It exposes a tiny stream-shaped API on top + * of the existing `DoubleBuffer` primitive: + * + * - Producer thread calls `Push(value)` whenever a new sample arrives. + * - Render thread calls `TryPull()` once per frame and updates the + * renderable if a fresh value is available. + * + * Semantics: "latest-only, lossy." The consumer always sees the most + * recent value; intermediate pushes between two pulls are silently + * dropped. This is what visualization usually wants — yesterday's frame + * is not interesting if today's is already in hand. + * + * The render thread never blocks. Push and Pull are thread-safe. + * + * For lossless delivery (e.g. accumulating every sample for a plot), + * use `RingBuffer` directly instead. + * + * Copyright (c) 2026 Ruixiang Du (rdu) + */ + +#ifndef QUICKVIZ_CORE_DATA_STREAM_HPP +#define QUICKVIZ_CORE_DATA_STREAM_HPP + +#include +#include + +#include "core/buffer/double_buffer.hpp" + +namespace quickviz { + +template +class DataStream { + public: + DataStream() = default; + ~DataStream() = default; + + // Non-copyable; the underlying buffer holds mutex/condvar state. + // Movable for placement in containers if useful. + DataStream(const DataStream&) = delete; + DataStream& operator=(const DataStream&) = delete; + + /** + * @brief Publish a new value. Replaces any unread previous value. + * + * Safe to call from any thread, including concurrently with `TryPull`. + * The previous unread value (if any) is discarded — only the most + * recent push is retained. This is the lossy semantics that streaming + * visualization expects. + */ + void Push(const T& value) { buffer_.Write(value); } + void Push(T&& value) { buffer_.Write(std::move(value)); } + + /** + * @brief Consume the latest value, if a fresh one is available. + * + * @param out Receives the latest value when this call returns true. + * Untouched otherwise. + * @return true if a fresh value was available and copied into `out`; + * false if no new value has been pushed since the last + * successful pull. + * + * Non-blocking. Safe to call once per frame on the render thread. + */ + bool TryPull(T& out) { return buffer_.TryRead(out); } + + /** + * @brief Same as `TryPull(T&)` but returns the value by std::optional. + * + * Convenient for `if (auto v = stream.TryPull()) { ... }` patterns. + * Slightly less efficient when T is large because it copies into + * `optional`'s storage; for hot paths prefer the out-param overload. + */ + std::optional TryPull() { + T value; + if (!buffer_.TryRead(value)) return std::nullopt; + return value; + } + + private: + DoubleBuffer buffer_; +}; + +} // namespace quickviz + +#endif // QUICKVIZ_CORE_DATA_STREAM_HPP diff --git a/src/core/test/unit_test/CMakeLists.txt b/src/core/test/unit_test/CMakeLists.txt index d2fbf8e..9a2a05e 100644 --- a/src/core/test/unit_test/CMakeLists.txt +++ b/src/core/test/unit_test/CMakeLists.txt @@ -6,7 +6,8 @@ add_executable(core_unit_tests test_event_system.cpp test_thread_safe_queue.cpp test_buffer_registry.cpp - test_demo.cpp) + test_demo.cpp + test_data_stream.cpp) target_link_libraries(core_unit_tests PRIVATE gtest_main gmock viewer) # get_target_property(PRIVATE_HEADERS viewer INCLUDE_DIRECTORIES) target_include_directories(core_unit_tests PRIVATE ${PRIVATE_HEADERS}) diff --git a/src/core/test/unit_test/test_data_stream.cpp b/src/core/test/unit_test/test_data_stream.cpp new file mode 100644 index 0000000..b22e78d --- /dev/null +++ b/src/core/test/unit_test/test_data_stream.cpp @@ -0,0 +1,103 @@ +/* + * @file test_data_stream.cpp + * + * Copyright (c) 2026 Ruixiang Du (rdu) + */ + +#include +#include +#include + +#include + +#include "core/data_stream.hpp" + +namespace quickviz { +namespace { + +TEST(DataStreamTest, EmptyPullReturnsFalse) { + DataStream stream; + int out = -1; + EXPECT_FALSE(stream.TryPull(out)); + EXPECT_EQ(out, -1); // out untouched on empty +} + +TEST(DataStreamTest, OptionalApiReturnsNulloptWhenEmpty) { + DataStream stream; + EXPECT_FALSE(stream.TryPull().has_value()); +} + +TEST(DataStreamTest, PushThenPullDeliversValue) { + DataStream stream; + stream.Push(42); + int out = 0; + ASSERT_TRUE(stream.TryPull(out)); + EXPECT_EQ(out, 42); +} + +TEST(DataStreamTest, PullReturnsFalseAfterFirstSuccessUntilNextPush) { + DataStream stream; + stream.Push(7); + int out = 0; + ASSERT_TRUE(stream.TryPull(out)); + EXPECT_FALSE(stream.TryPull(out)); // already consumed +} + +TEST(DataStreamTest, IntermediatePushesAreDropped) { + // Latest-only semantics: if the producer pushes faster than the + // consumer pulls, only the most recent value reaches the consumer. + DataStream stream; + for (int i = 1; i <= 5; ++i) stream.Push(i); + int out = 0; + ASSERT_TRUE(stream.TryPull(out)); + EXPECT_EQ(out, 5); + EXPECT_FALSE(stream.TryPull(out)); +} + +TEST(DataStreamTest, MoveOverloadCompiles) { + DataStream stream; + std::string s = "hello"; + stream.Push(std::move(s)); + std::string out; + ASSERT_TRUE(stream.TryPull(out)); + EXPECT_EQ(out, "hello"); +} + +TEST(DataStreamTest, ProducerConsumerThreadsConcurrent) { + // Background producer pushes a counter; foreground "renderer" polls it + // and verifies it always sees a non-decreasing snapshot. This is the + // headline use case: render thread never blocks, never sees torn data. + DataStream stream; + std::atomic stop{false}; + std::atomic last_pulled{-1}; + + std::thread producer([&]() { + for (int i = 0; i < 1000 && !stop.load(); ++i) { + stream.Push(i); + std::this_thread::yield(); + } + }); + + // Pull-loop on the test thread (acts as render thread). + int observed_max = -1; + while (observed_max < 999) { + int out = -1; + if (stream.TryPull(out)) { + ASSERT_GE(out, observed_max); // never goes backwards + observed_max = out; + } + if (last_pulled.load() == 999) break; + if (out >= 0) last_pulled.store(out); + } + stop.store(true); + producer.join(); + + // Once producer is done, one final value should be available + // (or already have been pulled). Either way, observed_max should + // have reached the producer's last value at some point. + EXPECT_GE(observed_max, 0); + EXPECT_LE(observed_max, 999); +} + +} // namespace +} // namespace quickviz From a8d126b56de75258b26bb33daaa9881eb45a3dac Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 22:42:10 +0800 Subject: [PATCH 12/22] docs(TODO): roadmap for "truly useful, low-friction" robotics library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure the tracker around onramp + extensibility goals identified during the post-reorg review. Captures concrete priorities in execution order and pulls deferred items into a deliberate backlog. Top of the new "Active priorities" stack: 1. sample/streaming_demo — close the DataStream loop with a working moving-point-cloud sample (~50 LOC). 2. ROS2 bridge milestone — bridges/ros2/ as the umbrella, with converters for PointCloud2, PoseStamped, OccupancyGrid, TFMessage, Marker. The single biggest gap between "C++ vis library" and "robotics vis library." 3. First standard robotics renderable (Trajectory, OccupancyGrid, or TfFrameTree — pick one, ship it cleanly). 4. Diagnostics: HUD overlay + structured logger + visible UI surface for shader/asset failures. 5. Documentation site: Doxygen + GitHub Pages + a tutorial chapter per existing sample. Backlog (deferred, not abandoned): layout presets, AppState persistence, recording/replay for DataStream, plugin/extension system, the eventual "robotics gold demo" composite sample. Existing entries preserved: library-hook candidates from sample/editor, known visualization gaps, smaller cleanups (canvas.cpp split, etc.). Recently Completed updated with the four post-rewrite commits (CLAUDE.md rewrite, demo helpers, quickstart sample, DataStream). Co-Authored-By: Claude Opus 4.7 (1M context) --- TODO.md | 262 +++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 183 insertions(+), 79 deletions(-) diff --git a/TODO.md b/TODO.md index 8211848..c527989 100644 --- a/TODO.md +++ b/TODO.md @@ -1,95 +1,198 @@ # QuickViz Implementation Tracker *Last Updated: April 26, 2026* -*Purpose: Track implementation status and priorities* +*Purpose: Track active work and concrete priorities. Terse and factual; +no marketing language. One bullet per outcome.* ## Mission -QuickViz is a **visualization-first** C++ library for robotics. The library -provides building blocks (rendering, UI, selection, tools) that consuming -applications compose. Editor / app-level concerns (commands, undo/redo, -project files, history) live in `sample/` or downstream apps, never in `src/`. +Visualization-first C++ library for robotics. The library renders, +displays, and interacts; apps built on top handle editor-style concerns +(undo/redo, project files, etc.). `sample/editor/` is the dogfood check +on library completeness. -`sample/editor/` (planned) is the dogfood check: if a vis+editing app cannot -be built on top of `src/` without modifying `src/`, the library is missing -a visualization-justified hook. +The next chapter — turn this into a *robotics* visualization library +(not a general C++ one) by closing the gap to standard robotics +workflows: ROS2 streams, common primitives, live data, diagnostics. --- -## 🎯 Active Work - -### Reshape — bring the codebase back to visualization-first -- [x] Delete `src/scenegraph/` (state mgmt + command pattern + bridge that - fabricated data — see commit `af77ad4`) -- [x] Delete `sample/object_management/` (demo of the deleted bridge) -- [x] CI `boundary-check` job: `src/` may not include from `sample/` -- [x] Update CLAUDE.md, archive stale design docs -- [x] Build `sample/editor/` MVP as the API completeness check - (load PCD/PLY → render → select → DeleteSelectedPoints → undo/redo - via app-side `CommandStack` → minimal history panel) -- [x] Module reorg: imview→viewer, gldraw→scene; widget split into - canvas/plot/image; cvdraw merged into image; new plot module hosts - ImPlot + ImPlot3D -- [ ] Add library hooks discovered while building the sample (additive only). - Logged candidates from sample/editor MVP: - - `SceneManager::GetObjectId(name) / GetObjectName(id)` so editors can - avoid relying on stringly-typed cloud names. - - `PointSelection::object_id` so selection callbacks don't need - string-equality on cloud_name. - - `PointCloud::SetActiveMask(span)` or `SetActiveIndices(...)` so - editing a point cloud doesn't require rebuilding the full vertex - buffer on every command (current MVP rewrites all visible points). - - Stable point-identity for selection persistence across cloud - mutations (today the editor must `ClearSelection()` on every - rebuild because the tool tracks visible indices). - Re-evaluate each one against the "is this a visualization concern?" - bar before merging to src/. - -### Known visualization gaps -- [ ] Selection support for Arrow, Plane, Path, Triangle, Pose primitives -- [ ] LOD system for >1M point scenes -- [ ] `PCLLoaderTest.InvalidFileError` is failing — modern PCL no longer - throws on corrupt PCDs; rewrite the test to match current behavior -- [ ] Move bundled fonts from `core/include` to `resources/` -- [ ] Replace `std::cerr` / `std::cout` debug spew in library code with a - lightweight logger (also audit for leftover noise after the deletions) - -### Smaller cleanups +## 🎯 Active priorities + +Ordered by what unblocks the most downstream work. + +### Next up — close the streaming-data loop +- [ ] **`sample/streaming_demo/`** — moving point cloud pushed from a + background thread, rendered live via `DataStream`. ~50 LOC. + Validates the API shape and serves as the canonical streaming + example before ROS2 work starts. + +### Milestone — ROS2 integration (`bridges/ros2/`) +The single biggest user-facing gap. Without this QuickViz is a general +C++ vis library; with it, it's the robotics vis library. +- [ ] Stand up `bridges/` umbrella with `bridges/ros2/` as the first + child (deferred from the earlier reorg — now justified by a real + occupant). +- [ ] `sensor_msgs::PointCloud2` ↔ `PointCloud` renderable converter +- [ ] `geometry_msgs::PoseStamped` / `PoseArray` ↔ pose / trajectory +- [ ] `nav_msgs::OccupancyGrid` ↔ grid renderable +- [ ] `tf2_msgs::TFMessage` ↔ frame-tree visualization +- [ ] `visualization_msgs::Marker` / `MarkerArray` ↔ generic + primitives passthrough +- [ ] CMake gating: ROS2 dep is optional; library still builds without + it + +### Milestone — first standard robotics renderable +Pick one and ship it cleanly before the next. Don't backlog all three. +- [ ] **`Trajectory`** renderable — 3D path with timestamps, optional + velocity coloring. Strong companion to `PoseStamped` streaming. +- [ ] `OccupancyGrid` renderable — 2D map projected into the 3D scene. + Strong companion to `nav_msgs::OccupancyGrid`. +- [ ] `TfFrameTree` renderable — animated transform tree with named + frames and optional fixed/moving distinction. Companion to tf2. + +### After ROS2 lands — diagnostics +- [ ] **HUD overlay**: frame time, draw call count, GPU memory, active + tool, scene object count. Toggled by hotkey. ~1 day. +- [ ] **Structured logger** to replace the scattered `std::cerr` / + `std::cout` calls in library code (long-standing TODO; promote + now). Lightweight; ~100 LOC + a level/category enum. +- [ ] Visible UI surface for shader/asset load failures (today these + print to console and the user sees a black scene). + +### After diagnostics — documentation site +- [ ] Doxygen API reference, generated and published via GitHub Pages + from the same repo. ~1 day to set up. +- [ ] `docs/tutorial/01-quickstart.md` — walk through `sample/quickstart/` +- [ ] `docs/tutorial/02-editor.md` — walk through `sample/editor/` +- [ ] `docs/tutorial/03-streaming.md` — walk through `sample/streaming_demo/` +- [ ] `docs/tutorial/04-custom-renderable.md` — extension story +- [ ] `docs/tutorial/05-ros2.md` — once the bridge lands + +--- + +## 📋 Backlog (deliberately deferred) + +These are good ideas with concrete value but aren't on the critical +path right now. Promote one when there's a user pulling for it. + +### Onramp / ergonomics +- [ ] **Layout presets** — `viewer::layout::SidebarLeft(width)`, + `BottomDock(height)`, `Split(ratio)` returning configured `Box` + trees. Removes the FlexGrow/FlexShrink magic-number incantation + from samples. ~3 hours. +- [ ] **`viewer::AppState`** — saves window position, last camera + viewpoint per scene, last-opened files, panel sizes beyond what + `imgui.ini` covers. Loads on startup, saves on shutdown. + ~1 day + a TOML/JSON dep. + +### Tools / data lifecycle +- [ ] **Recording + replay** for `DataStream`s — `core/Record` and + `core/Replay` capture / play back stream traffic to disk. + Critical for deterministic testing and offline analysis. ~2-3 + days. Picks a binary format (raw, msgpack, capnproto). + +### Performance +- [ ] **LOD system** for >1M point scenes (existing TODO, larger). + Likely octree-based with per-tile streaming. + +### Scaling / extension +- [ ] **Plugin / extension system** — runtime loading of renderables + and tools via shared library + small C ABI registration. Heavy + lift (~2-3 weeks); defer until a concrete user appears. + +### Robotics gold demo +- [ ] **Compose** the items above into a single sample that looks + impressive: robot driving through an occupancy grid with a + streaming point cloud, trajectory, sensor frustum, and small + dashboard. Doubles as a marketing screenshot and a + feature-completeness check. + +--- + +## 🔧 Library hooks (driven by `sample/editor`) + +Additive only. Each one was logged when the editor sample wanted it +but worked around the absence. Re-evaluate against "is this a +visualization concern?" before merging. + +- [ ] `SceneManager::GetObjectId(name)` / `GetObjectName(id)` — drop + the stringly-typed name lookup the editor currently uses. +- [ ] `PointSelection::object_id` — selection callbacks no longer + need string-equality on cloud_name. +- [ ] `PointCloud::SetActiveMask(span)` or + `SetActiveIndices(...)` — editing a point cloud no longer + requires rebuilding the full vertex buffer per command. +- [ ] Stable point identity so selections survive cloud mutations + (today the editor must `ClearSelection()` after every rebuild + because the tool tracks visible indices). + +--- + +## 🐛 Known visualization gaps + +- [ ] Selection support for `Arrow`, `Plane`, `Path`, `Triangle`, + `Pose` primitives. +- [ ] `PCLLoaderTest.InvalidFileError` is failing — modern PCL no + longer throws on corrupt PCDs; rewrite the test against current + behavior. +- [ ] Move bundled fonts from `core/include/` to a top-level + `resources/` directory (they're not a public-API concern). + +--- + +## 🧹 Smaller cleanups + - [ ] `src/scene/src/renderable/canvas.cpp` is 2069 LOC; split into - cohesive sub-files (~500 LOC target per CLAUDE.md) -- [ ] Audit `interactive_scene_manager.cpp` for disabled/legacy paths left - over from the editor migration; either finish or remove + cohesive sub-files (~500 LOC target per CLAUDE.md). +- [ ] Audit `sample/pointcloud_viewer/interactive_scene_manager.cpp` + for disabled / legacy paths from the deleted-editor migration; + either finish or remove. +- [ ] Audit `sample/quickviz_demo_app/` for boilerplate that could + now use `SceneApp` and the layout presets when those land. --- ## ✅ Recently Completed ### April 2026 -- ✅ **Module reorg by intent** — Final library layout: - `core, viewer, scene, plot, canvas, image, pcl_bridge`. One job per - module, named after what users want to do (not which backend it uses). - Renamed `imview→viewer`, `gldraw→scene`. Dissolved `widget` into - `canvas` (Cairo) + `plot` (ImPlot widgets). Merged `cvdraw` into - `image` along with the cv_image widgets from `widget`. New `plot` - module also hosts ImPlot3D. -- ✅ **`sample/editor/` MVP** — vis+editing reference app on top of - the library, built without any `src/` modifications. Acts as the - dogfood check on library completeness. Load PCD/PLY → select → - DeletePoints with full undo/redo via a sample-private CommandStack. -- ✅ **Reshape: visualization-first re-anchor** — Removed the in-library - state management module (`scenegraph`) and its sample (`object_management`). - Locked the `src/ ↛ sample/` boundary in CI and CLAUDE.md. Editor concerns - are now built on top of the library, not inside it. Stale architecture and - design docs archived. (commits `af77ad4`, `079eb2b`) + +- ✅ **CLAUDE.md rewrite** — tighter project contract, 470 → 308 lines. + Final module map, library boundary rule, code style, threading model, + decision heuristics. (commit `e637b8d`) +- ✅ **`quickviz::DataStream`** — latest-only producer/consumer + channel for streaming sensor data, header-only over `DoubleBuffer`. + 7 unit tests including a threaded smoke test. (commit `22ef647`) +- ✅ **`sample/quickstart/`** — 18-line app demonstrating `SceneApp` + + synthetic data; the "first 5 minutes" proof point. (commit `c6b9a2a`) +- ✅ **`quickviz::demo::*` synthetic data generators** — SpiralCloud, + PlanarPointGrid, NoiseCloud, CubeMesh, Trajectory. 8 unit tests. + (commit `135070a`) +- ✅ **`GlViewer` → `SceneApp` rename + reframe** as the 5-line + quickstart facade. 17 renderable tests updated. (commit `557a27a`) +- ✅ **Module reorg by intent** — final layout `core, viewer, scene, + plot, canvas, image, pcl_bridge`. One job per module, named after + what users want to do (not which backend). Renames `imview→viewer`, + `gldraw→scene`. Dissolved `widget` into `canvas` (Cairo) + `plot` + (ImPlot widgets). Merged `cvdraw` into `image` along with cv_image + widgets from `widget`. New `plot` module hosts ImPlot3D as well. +- ✅ **`sample/editor/` MVP** — vis+editing reference app on top of the + library, built without any `src/` modifications. Acts as the dogfood + check on library completeness. +- ✅ **Reshape: visualization-first re-anchor** — Removed the + in-library state management module (`scenegraph`) and its sample + (`object_management`). Locked the `src/ ↛ sample/` boundary in CI + and CLAUDE.md. ### September 2025 -- ✅ CameraController refactor (Strategy pattern, configurable parameters, - utility methods) +- ✅ CameraController refactor (Strategy pattern, configurable + parameters, utility methods) - ✅ Input debug message cleanup - ✅ GLDraw architecture review ### December 2024 -- ✅ ThreadSafeQueue, BufferRegistry, AsyncEventDispatcher modernization +- ✅ ThreadSafeQueue, BufferRegistry, AsyncEventDispatcher + modernization ### September 2024 - ✅ Configurable camera controls (Modeling/FPS/CAD/Scientific styles) @@ -107,19 +210,20 @@ a visualization-justified hook. ## 📊 Status Summary -**Branch**: `feature-pointcloud_editing` (will rename once the editor sample -is in place) -**Focus**: Re-anchor the library on visualization, then build the editor -sample as the API check. +**Branch**: `main` (post-PR-#28). **Architecture**: Library = `core, viewer, scene, plot, canvas, image, -pcl_bridge` — one job per module, named by user intent. Apps live in -`sample/`. +pcl_bridge`. Apps live in `sample/`. Editor / ROS-style frameworks live +above the library, never inside. +**Current focus**: Close the streaming loop with `sample/streaming_demo`, +then the ROS2 bridge milestone. --- ## 📝 Notes -- See `docs/notes/` for design deep-dives (rendering, picking, input) -- See `CLAUDE.md` for project guidelines and module boundaries -- Update this file after finishing tasks; keep entries terse, factual, - and one bullet per outcome +- See `docs/notes/` for design deep-dives (rendering, picking, input). +- See `CLAUDE.md` for project guidelines and module boundaries. +- Update this file in the same change as the work itself; keep entries + terse, factual, one bullet per outcome. No marketing language. +- An item moves from Active → Recently Completed only when the work is + actually merged and tests pass — not "started" or "in flight." From f672d855c53eb080afd6bd22a9970afa2a8d9eb0 Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 22:46:38 +0800 Subject: [PATCH 13/22] docs(TODO): sharpen entries with audit results; nothing removed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verified each open item against current code state. All listed items are still valid — no removals — but a few entries got sharper with concrete scope or location data: - Robotics-renderable milestone: drop the standalone "Trajectory" bullet. The existing `Path` renderable already covers 3D motion paths; the streaming/time-aware behaviors should extend `Path`, not create a new class. `OccupancyGrid` is now flagged as the recommended-first item (single-purpose, no overlap, pairs with the ROS2 OccupancyGrid converter). - Structured logger: pinned the actual scope. Audit found ~232 std::cerr/std::cout occurrences in src/, concentrated in scene (179) and viewer (43). The migration is a sed pass after the logger lands. - interactive_scene_manager.cpp cleanup: pointed at the specific leftovers — empty `return;`-only HandleMouseInput, a "Legacy SelectionManager callback disabled" block, and a stale TODO. - quickviz_demo_app audit: noted that it uses `Viewer` directly (a multi-panel app) and the audit may correctly conclude "no change". The actionable part is whether layout presets, when they land, would tighten the layout setup. Re-confirmed still open and unchanged: library hooks (4), selection coverage gaps (Arrow/Plane/Path/Triangle/Pose), canvas.cpp 2069 LOC, fonts in core/include/, PCLLoaderTest.InvalidFileError. Co-Authored-By: Claude Opus 4.7 (1M context) --- TODO.md | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/TODO.md b/TODO.md index c527989..6078ab2 100644 --- a/TODO.md +++ b/TODO.md @@ -44,19 +44,27 @@ C++ vis library; with it, it's the robotics vis library. ### Milestone — first standard robotics renderable Pick one and ship it cleanly before the next. Don't backlog all three. -- [ ] **`Trajectory`** renderable — 3D path with timestamps, optional - velocity coloring. Strong companion to `PoseStamped` streaming. -- [ ] `OccupancyGrid` renderable — 2D map projected into the 3D scene. - Strong companion to `nav_msgs::OccupancyGrid`. +- [ ] **`OccupancyGrid`** renderable — 2D map projected into the 3D + scene. Strong companion to `nav_msgs::OccupancyGrid`. *Recommended + first*: single-purpose, no existing partial implementation, lines + up cleanly with ROS2 work. - [ ] `TfFrameTree` renderable — animated transform tree with named - frames and optional fixed/moving distinction. Companion to tf2. + frames and parent/child relationships. Likely composes multiple + `CoordinateFrame` instances. Companion to tf2. +- [ ] `Trajectory` extensions on the existing `Path` renderable — + timestamp coloring, velocity coloring, animated growth. `Path` + already covers "render a 3D motion path"; this milestone adds the + streaming/time-aware behaviors. (Don't create a new renderable — + extend `Path`.) ### After ROS2 lands — diagnostics - [ ] **HUD overlay**: frame time, draw call count, GPU memory, active tool, scene object count. Toggled by hotkey. ~1 day. - [ ] **Structured logger** to replace the scattered `std::cerr` / - `std::cout` calls in library code (long-standing TODO; promote - now). Lightweight; ~100 LOC + a level/category enum. + `std::cout` calls in library code. Audit count (April 2026): + ~232 occurrences in `src/`, concentrated in `scene` (179) and + `viewer` (43). Lightweight implementation: ~100 LOC + a + level/category enum, then a sed migration. - [ ] Visible UI surface for shader/asset load failures (today these print to console and the user sees a black scene). @@ -145,11 +153,15 @@ visualization concern?" before merging. - [ ] `src/scene/src/renderable/canvas.cpp` is 2069 LOC; split into cohesive sub-files (~500 LOC target per CLAUDE.md). -- [ ] Audit `sample/pointcloud_viewer/interactive_scene_manager.cpp` - for disabled / legacy paths from the deleted-editor migration; - either finish or remove. -- [ ] Audit `sample/quickviz_demo_app/` for boilerplate that could - now use `SceneApp` and the layout presets when those land. +- [ ] Clean `sample/pointcloud_viewer/interactive_scene_manager.cpp` + — confirmed leftovers from the deleted-editor migration: + `HandleMouseInput()` is `return;` only (line 141-144), a + "Legacy SelectionManager callback disabled" block (line 113-124), + and a stale TODO at line 107. Either finish or remove each. +- [ ] Audit `sample/quickviz_demo_app/` after layout presets land. + It uses `Viewer` directly (a multi-panel app — likely correct). + The audit may conclude "no change"; the question is whether the + layout setup could be tightened with the upcoming presets. --- From 1b3022b673fe9d7866f948edb0dd8854e6dba24e Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 23:02:22 +0800 Subject: [PATCH 14/22] feat(sample/streaming_demo): canonical sensor-streaming pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reference for "I have data on a background thread, how do I show it?" Read this before writing your first ROS2 / sensor callback — same shape applies. Architecture (also drawn in the README): background producer ─Push─► DataStream ─TryPull─► render thread (latest-only, lossy, never blocks) What it does: a 2000-point colored spiral rotates around Z at 30 Hz on a background thread; the render thread pulls the latest cloud in a SetPreDrawCallback and updates a single PointCloud renderable. Window-close stops the producer cleanly. Files: - sample/streaming_demo/main.cpp — 53 LOC of meaningful code - sample/streaming_demo/README.md — threading model, DataStream vs RingBuffer guidance, "how to adapt this to a real sensor" - sample/streaming_demo/CMakeLists.txt — links core, scene, viewer - sample/CMakeLists.txt: registers the sample alongside quickstart Demonstrates: DataStream with a non-trivial T, fixed-rate producer, lossy semantics, render-thread pull pattern, clean atomic-bool shutdown. Does *not* demonstrate multi-stream, lossless delivery (that's RingBuffer territory), or ROS2 — those are deliberate follow-ups, signposted in the README. Build clean. Same 2 pre-existing PCL failures, no new regressions. Library boundary holds. TODO.md: marked the "Next up" entry done; new "Recently Completed" entry added. Co-Authored-By: Claude Opus 4.7 (1M context) --- TODO.md | 14 ++-- sample/CMakeLists.txt | 1 + sample/streaming_demo/CMakeLists.txt | 5 ++ sample/streaming_demo/README.md | 99 ++++++++++++++++++++++++++++ sample/streaming_demo/main.cpp | 89 +++++++++++++++++++++++++ 5 files changed, 204 insertions(+), 4 deletions(-) create mode 100644 sample/streaming_demo/CMakeLists.txt create mode 100644 sample/streaming_demo/README.md create mode 100644 sample/streaming_demo/main.cpp diff --git a/TODO.md b/TODO.md index 6078ab2..3030bb1 100644 --- a/TODO.md +++ b/TODO.md @@ -22,10 +22,11 @@ workflows: ROS2 streams, common primitives, live data, diagnostics. Ordered by what unblocks the most downstream work. ### Next up — close the streaming-data loop -- [ ] **`sample/streaming_demo/`** — moving point cloud pushed from a - background thread, rendered live via `DataStream`. ~50 LOC. - Validates the API shape and serves as the canonical streaming - example before ROS2 work starts. +- [x] **`sample/streaming_demo/`** — rotating spiral pushed from a + background thread at ~30 Hz, rendered via `DataStream` and + `SceneManager::SetPreDrawCallback`. Annotated README explains + the threading model and DataStream-vs-RingBuffer choice. The + reference users will copy when wiring sensor streams. ### Milestone — ROS2 integration (`bridges/ros2/`) The single biggest user-facing gap. Without this QuickViz is a general @@ -169,6 +170,11 @@ visualization concern?" before merging. ### April 2026 +- ✅ **`sample/streaming_demo/`** — canonical sensor-streaming pattern. + Background producer rotates a 2000-point spiral around Z and pushes + to `DataStream` at 30 Hz; render thread pulls in a + `SetPreDrawCallback` and updates the renderable. ~50 LOC + a 60-line + README on threading model and DataStream-vs-RingBuffer guidance. - ✅ **CLAUDE.md rewrite** — tighter project contract, 470 → 308 lines. Final module map, library boundary rule, code style, threading model, decision heuristics. (commit `e637b8d`) diff --git a/sample/CMakeLists.txt b/sample/CMakeLists.txt index 6aeaeee..ce25cb4 100644 --- a/sample/CMakeLists.txt +++ b/sample/CMakeLists.txt @@ -1,6 +1,7 @@ # applications if (ENABLE_AUTO_LAYOUT) add_subdirectory(quickstart) + add_subdirectory(streaming_demo) add_subdirectory(quickviz_demo_app) # Silence PCL-era policy warnings, but keep modern behavior where safe diff --git a/sample/streaming_demo/CMakeLists.txt b/sample/streaming_demo/CMakeLists.txt new file mode 100644 index 0000000..3950996 --- /dev/null +++ b/sample/streaming_demo/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(quickviz_streaming_demo main.cpp) +target_link_libraries(quickviz_streaming_demo PRIVATE + core + scene + viewer) diff --git a/sample/streaming_demo/README.md b/sample/streaming_demo/README.md new file mode 100644 index 0000000..93e915e --- /dev/null +++ b/sample/streaming_demo/README.md @@ -0,0 +1,99 @@ +# sample/streaming_demo — sensor data into a 3D scene, the right way + +The canonical pattern for bringing data from a background thread (sensor +driver, ROS subscriber, network socket, processing pipeline) into a +QuickViz scene. Read `main.cpp` before you write your first sensor +callback — the same shape applies. + +## What it does + +Opens a window. A background thread generates a 2000-point colored +helix that rotates around the vertical axis at ~30 Hz and pushes each +new cloud into a `DataStream`. The render thread pulls the latest +cloud once per frame and updates a single `PointCloud` renderable. You +see a smoothly spinning spiral. + +Close the window — the producer thread shuts down cleanly. + +## The threading model + +``` + ┌──────────────────┐ ┌──────────────────┐ + │ producer thread │ │ render thread │ + │ (sensor / ROS / │ Push(PointCloudData) │ (GLFW + GL) │ + │ driver / your │ ────────────────────────► │ │ + │ callback) │ │ TryPull(out) │ + └──────────────────┘ │ SetPoints(out) │ + DataStream └──────────────────┘ + (latest-only, + lossy, never + blocks the + render loop) +``` + +Three rules captured by this layout: + +1. **All OpenGL calls happen on the render thread.** The producer never + touches GL or the renderable directly. It only writes to `stream`. +2. **The render thread never blocks waiting for data.** `TryPull` + returns immediately with either the latest sample or "nothing new" + — in the latter case the previous frame's cloud is reused. +3. **Intermediate samples are dropped silently.** If the producer + pushes 30 clouds per second but the render loop runs at 144 Hz (or + stutters under load), you only ever see the most recent cloud. + Visualization rarely cares about yesterday's frame; this is what + you want. + +## When `DataStream` is right + +Use `DataStream` when: + +- You only care about the **latest** value (poses, sensor frames, + status updates, processed outputs). +- The producer thread should **not block** on the consumer — losing + intermediate values is acceptable. +- The data type is **trivially copyable / movable** as a whole unit + (a struct, a `std::vector`, a small POD). Don't push references. + +Use `RingBuffer` (in `core/buffer/`) instead when: + +- You need to **see every sample** (plotting time-series, replaying, + recording, integration over time). +- The consumer should be able to fall behind without losing data + (within the buffer's capacity). +- Order matters and you process samples one-by-one. + +For most "show me the sensor on screen" robotics workflows, use +`DataStream`. + +## How to adapt this to a real sensor + +Replace the synthetic producer (the `std::thread` block) with whatever +delivers your data: + +```cpp +// instead of a synthetic loop, your driver/ROS callback pushes: +my_sensor.OnFrame([&stream](const RawFrame& frame) { + auto cloud = ConvertToPointCloudData(frame); + stream.Push(std::move(cloud)); +}); +``` + +Everything below the producer stays the same: one `DataStream`, +one `SetPreDrawCallback` doing `TryPull` + `SetPoints`. The renderer +doesn't know or care where the data came from. + +The forthcoming `bridges/ros2/` module will give you ready-made +producers for `sensor_msgs::PointCloud2`, `geometry_msgs::PoseStamped`, +and similar — so the boilerplate disappears for ROS2 users. + +## Building & running + +```bash +cmake -S . -B build -DBUILD_TESTING=ON +cmake --build build -j +./build/bin/quickviz_streaming_demo +``` + +No external data files needed; all data is synthetic via +`quickviz::demo::SpiralCloud`. diff --git a/sample/streaming_demo/main.cpp b/sample/streaming_demo/main.cpp new file mode 100644 index 0000000..bd02e7d --- /dev/null +++ b/sample/streaming_demo/main.cpp @@ -0,0 +1,89 @@ +/* + * @file main.cpp + * @brief Canonical streaming pattern: background producer → DataStream → + * render thread → renderable. + * + * Demonstrates the recommended way to bring sensor-rate data into a + * QuickViz scene without blocking the render loop. Read this file before + * writing your first ROS2 / sensor callback — the same shape applies. + * + * Copyright (c) 2026 Ruixiang Du (rdu) + */ + +#include +#include +#include +#include +#include + +#include "core/data_stream.hpp" +#include "core/demo.hpp" +#include "scene/renderable/point_cloud.hpp" +#include "scene/scene_app.hpp" + +int main() { + using namespace quickviz; + + // The single shared channel between producer and renderer. + // Latest-only / lossy: if the producer outpaces the renderer, the + // intermediate clouds are silently dropped — usually what you want. + DataStream stream; + + // Producer thread: simulates a 30 Hz sensor producing a spiral cloud + // that rotates around the Z axis. In a real app this is your driver + // callback or ROS2 subscriber. + std::atomic stop{false}; + std::thread producer([&]() { + const auto t0 = std::chrono::steady_clock::now(); + while (!stop.load()) { + const float t = std::chrono::duration( + std::chrono::steady_clock::now() - t0).count(); + const float angle = 0.5f * t; // rad/s + const float ca = std::cos(angle); + const float sa = std::sin(angle); + + auto data = demo::SpiralCloud(2000); + for (auto& p : data.points) { + const float x = p.x * ca - p.y * sa; + const float y = p.x * sa + p.y * ca; + p.x = x; + p.y = y; + } + stream.Push(std::move(data)); + + std::this_thread::sleep_for(std::chrono::milliseconds(33)); + } + }); + + SceneApp::Config config; + config.window_title = "QuickViz Streaming Demo"; + SceneApp app(config); + + // Wire up the scene: a single empty point cloud, plus a pre-draw + // callback that drains the stream once per frame. + PointCloud* cloud = nullptr; + app.SetSceneSetup([&](SceneManager* scene) { + auto pc = std::make_unique(); + pc->SetPointSize(4.0f); + cloud = pc.get(); + scene->AddOpenGLObject("stream", std::move(pc)); + + // Pre-draw callback runs on the render thread, just before the + // scene is rendered. Non-blocking; if no fresh value is available, + // the previous frame's cloud is rendered again. + scene->SetPreDrawCallback([&]() { + demo::PointCloudData latest; + if (stream.TryPull(latest)) { + cloud->SetPoints(latest.points, latest.colors); + } + }); + }); + + app.Run(); + + // Clean shutdown: stop the producer before the stream goes out of + // scope. Order matters — the producer holds a reference to `stream`. + stop.store(true); + producer.join(); + return 0; +} From f9c93a8cda8b92f770f035f4100711a3e9542035 Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 23:11:17 +0800 Subject: [PATCH 15/22] feat(scene): OccupancyGrid renderable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the standard 2D occupancy-grid primitive — the renderable a ROS2 nav_msgs::OccupancyGrid converter will produce once the bridge lands. Built and shipped now so the renderable exists when the bridge calls it. API (scene/renderable/occupancy_grid.hpp): SetGrid(width, height, resolution, origin, std::vector) SetFreeColor(vec4) SetOccupiedColor(vec4) SetUnknownColor(vec4) ROS conventions preserved: -1 = unknown, 0..100 = probability of occupancy (percent). Origin is the world-space position of cell (0, 0) and the grid extends to origin + (width, height) * resolution. Implementation: - One quad in the XY plane, sized at draw time by uExtent. - R8 single-channel texture with the encoding "byte 0 = unknown sentinel, byte 1..255 = occupancy 0..100% (linear)". This keeps the texture to 1 byte per cell — a 1000x1000 grid is 1 MiB. - Fragment shader checks the sentinel and otherwise lerps free→occupied. - Lazy texture allocation: AllocateGpuResources sets up shader + VAO only; the texture is created on the first SetGrid() so resizes are handled cleanly. Defaults: light gray free, black occupied, medium gray unknown — read naturally on either light or dark scene backgrounds. Manual visual test (scene/test/renderable/test_occupancy_grid.cpp) renders a 50x50 grid with a hand-crafted pattern that exercises all three states: outer occupied border, intermediate diagonal, free interior, lower-left unknown patch. Reference Grid at the same resolution for cell alignment verification. Build clean. 122 tests pass; same 2 pre-existing PCL failures, no new regressions. Library boundary holds. Active priorities updated in TODO.md (OccupancyGrid checked off; TfFrameTree and Trajectory extensions still open under the same milestone). Co-Authored-By: Claude Opus 4.7 (1M context) --- TODO.md | 15 +- src/scene/CMakeLists.txt | 1 + .../scene/renderable/occupancy_grid.hpp | 109 ++++++++ src/scene/src/renderable/occupancy_grid.cpp | 237 ++++++++++++++++++ src/scene/test/renderable/CMakeLists.txt | 3 + .../test/renderable/test_occupancy_grid.cpp | 98 ++++++++ 6 files changed, 459 insertions(+), 4 deletions(-) create mode 100644 src/scene/include/scene/renderable/occupancy_grid.hpp create mode 100644 src/scene/src/renderable/occupancy_grid.cpp create mode 100644 src/scene/test/renderable/test_occupancy_grid.cpp diff --git a/TODO.md b/TODO.md index 3030bb1..2512ff6 100644 --- a/TODO.md +++ b/TODO.md @@ -45,10 +45,10 @@ C++ vis library; with it, it's the robotics vis library. ### Milestone — first standard robotics renderable Pick one and ship it cleanly before the next. Don't backlog all three. -- [ ] **`OccupancyGrid`** renderable — 2D map projected into the 3D - scene. Strong companion to `nav_msgs::OccupancyGrid`. *Recommended - first*: single-purpose, no existing partial implementation, lines - up cleanly with ROS2 work. +- [x] **`OccupancyGrid`** renderable — 2D map projected into the 3D + scene. ROS-style int8 input (`-1` unknown, `0..100` occupancy + percent). R8 texture + custom colormap shader. Visual test + fixture in `scene/test/renderable/test_occupancy_grid.cpp`. - [ ] `TfFrameTree` renderable — animated transform tree with named frames and parent/child relationships. Likely composes multiple `CoordinateFrame` instances. Companion to tf2. @@ -170,6 +170,13 @@ visualization concern?" before merging. ### April 2026 +- ✅ **`OccupancyGrid` renderable** — 2D probabilistic grid as a + textured quad in the XY plane. ROS-style API + (`SetGrid(width, height, resolution, origin, vector)`, + values: `-1` unknown / `0..100` occupancy). R8 texture with sentinel + encoding (`byte 0 = unknown`, `byte 1..255 = free→occupied`); custom + shader colormaps to configurable free/occupied/unknown colors. + Manual visual test fixture in `scene/test/renderable/`. - ✅ **`sample/streaming_demo/`** — canonical sensor-streaming pattern. Background producer rotates a 2000-point spiral around Z and pushes to `DataStream` at 30 Hz; render thread pulls in a diff --git a/src/scene/CMakeLists.txt b/src/scene/CMakeLists.txt index 3a38840..dc8b4ef 100644 --- a/src/scene/CMakeLists.txt +++ b/src/scene/CMakeLists.txt @@ -26,6 +26,7 @@ add_library(scene src/renderable/details/canvas_data_manager.cpp src/renderable/details/point_layer_manager.cpp src/renderable/grid.cpp + src/renderable/occupancy_grid.cpp src/renderable/triangle.cpp src/renderable/point_cloud.cpp src/renderable/canvas.cpp diff --git a/src/scene/include/scene/renderable/occupancy_grid.hpp b/src/scene/include/scene/renderable/occupancy_grid.hpp new file mode 100644 index 0000000..927bcfc --- /dev/null +++ b/src/scene/include/scene/renderable/occupancy_grid.hpp @@ -0,0 +1,109 @@ +/** + * @file occupancy_grid.hpp + * @brief 2D occupancy-grid renderable + * + * Renders a 2D probabilistic occupancy grid as a textured quad lying in + * the XY plane (Z = 0 in the local frame). Designed to consume ROS-style + * data without lossy conversion: cells are int8_t in the range + * `[-1, 100]`, where `-1` is unknown and `0..100` is the probability + * (percent) of occupancy. + * + * The renderable does not interpret or transform the grid origin + * pose-wise — pass world-space `origin` and `resolution`, or use the + * `SetTransform` machinery on `OpenGlObject` for richer placement. + * + * Copyright (c) 2026 Ruixiang Du (rdu) + */ + +#ifndef QUICKVIZ_OCCUPANCY_GRID_HPP +#define QUICKVIZ_OCCUPANCY_GRID_HPP + +#include +#include + +#include + +#include "scene/interface/opengl_object.hpp" +#include "../shader_program.hpp" + +namespace quickviz { + +class OccupancyGrid : public OpenGlObject { + public: + OccupancyGrid(); + ~OccupancyGrid(); + + // === Data === + + /** + * @brief Set the grid contents. + * + * @param width Number of cells along the X axis. + * @param height Number of cells along the Y axis. + * @param resolution World-space size of a single cell, in meters. + * @param origin World-space position of cell `(0, 0)`. The grid + * spans from `origin` to + * `origin + (width, height) * resolution` in XY. + * @param values Row-major occupancy values. Size must be + * `width * height`. ROS convention: + * -1 → unknown + * 0..100 → probability of occupancy (percent) + * + * Safe to call repeatedly; the texture is updated in place when the + * dimensions don't change. Grid resizes do reallocate the texture. + * Must be called on the render thread. + */ + void SetGrid(uint32_t width, uint32_t height, float resolution, + const glm::vec3& origin, const std::vector& values); + + // === Appearance === + + /// Color used for cells with occupancy = 0 (free). Default: light gray. + void SetFreeColor(const glm::vec4& color) { free_color_ = color; } + /// Color used for cells with occupancy = 100 (occupied). Default: black. + void SetOccupiedColor(const glm::vec4& color) { occupied_color_ = color; } + /// Color used for cells flagged unknown (-1). Default: medium gray. + void SetUnknownColor(const glm::vec4& color) { unknown_color_ = color; } + + // === Inspection === + + uint32_t GetWidth() const { return width_; } + uint32_t GetHeight() const { return height_; } + float GetResolution() const { return resolution_; } + const glm::vec3& GetOrigin() const { return origin_; } + + // === OpenGlObject interface === + + void AllocateGpuResources() override; + void ReleaseGpuResources() noexcept override; + void OnDraw(const glm::mat4& projection, const glm::mat4& view, + const glm::mat4& coord_transform = glm::mat4(1.0f)) override; + bool IsGpuResourcesAllocated() const noexcept override { + return vao_ != 0; + } + + private: + void EnsureTextureSize(uint32_t width, uint32_t height); + void UploadTexel(const std::vector& values); + + // Grid metadata + uint32_t width_ = 0; + uint32_t height_ = 0; + float resolution_ = 1.0f; + glm::vec3 origin_ = glm::vec3(0.0f); + + // Color configuration (sane defaults). + glm::vec4 free_color_ = glm::vec4(0.85f, 0.85f, 0.85f, 1.0f); + glm::vec4 occupied_color_ = glm::vec4(0.05f, 0.05f, 0.05f, 1.0f); + glm::vec4 unknown_color_ = glm::vec4(0.45f, 0.45f, 0.45f, 1.0f); + + // GL resources + uint32_t vao_ = 0; + uint32_t vbo_ = 0; + uint32_t texture_id_ = 0; + ShaderProgram shader_; +}; + +} // namespace quickviz + +#endif // QUICKVIZ_OCCUPANCY_GRID_HPP diff --git a/src/scene/src/renderable/occupancy_grid.cpp b/src/scene/src/renderable/occupancy_grid.cpp new file mode 100644 index 0000000..128c49c --- /dev/null +++ b/src/scene/src/renderable/occupancy_grid.cpp @@ -0,0 +1,237 @@ +/** + * @file occupancy_grid.cpp + * + * Storage encoding (R8 single-channel texture): + * byte 0 → unknown sentinel + * byte 1..255 → occupancy 0..100% (linear; byte = round(1 + occ/100*254)) + * + * The shader checks for the `0` sentinel and otherwise lerps free→occupied. + * + * Copyright (c) 2026 Ruixiang Du (rdu) + */ + +#include "scene/renderable/occupancy_grid.hpp" + +#include +#include +#include + +#include "glad/glad.h" +#include + +#include "scene/shader.hpp" + +namespace quickviz { +namespace { + +constexpr uint8_t kUnknownByte = 0; + +// Two-triangle quad covering the unit square in XY (Z=0). Layout: +// x y u v +// Vertex order: top-left, bottom-left, bottom-right, top-left, bottom-right, top-right. +// Texture coords: +// u: 0..1 along X axis; v: 0..1 along Y axis. +const float kQuadVertices[] = { + 0.0f, 1.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 1.0f, + 1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, +}; + +const std::string kVertexShader = R"( +#version 330 core +layout(location = 0) in vec2 aPos; +layout(location = 1) in vec2 aTex; + +uniform mat4 projection; +uniform mat4 view; +uniform mat4 model; +uniform mat4 coordSystemTransform; + +uniform vec3 uOrigin; +uniform vec3 uExtent; // xy = (width*resolution, height*resolution); z unused + +out vec2 vTex; + +void main() { + // Position the unit quad at uOrigin with extent uExtent.xy. + vec3 world = uOrigin + vec3(aPos * uExtent.xy, 0.0); + gl_Position = projection * view * coordSystemTransform * model * vec4(world, 1.0); + vTex = aTex; +} +)"; + +const std::string kFragmentShader = R"( +#version 330 core +in vec2 vTex; +out vec4 FragColor; + +uniform sampler2D uTex; +uniform vec4 uFree; +uniform vec4 uOccupied; +uniform vec4 uUnknown; + +void main() { + float r = texture(uTex, vTex).r; // 0..1 + // Sentinel: byte 0 == 0.0 means "unknown". + // Sample of byte 0 is exactly 0.0; bytes 1..255 are >= 1/255. + if (r < 0.5/255.0) { + FragColor = uUnknown; + } else { + // Reverse the encoding: occupancy = (byte - 1) / 254. + float occupancy = (r * 255.0 - 1.0) / 254.0; + FragColor = mix(uFree, uOccupied, clamp(occupancy, 0.0, 1.0)); + } +} +)"; + +uint8_t EncodeOccupancy(int8_t v) { + if (v < 0) return kUnknownByte; + // Clamp into [0, 100], then map linearly to [1, 255]. + const int clamped = std::clamp(static_cast(v), 0, 100); + // 1 + round(clamped * 254 / 100). + return static_cast(1 + (clamped * 254 + 50) / 100); +} + +} // namespace + +OccupancyGrid::OccupancyGrid() { AllocateGpuResources(); } + +OccupancyGrid::~OccupancyGrid() { ReleaseGpuResources(); } + +void OccupancyGrid::AllocateGpuResources() { + if (IsGpuResourcesAllocated()) return; + + Shader vs(kVertexShader.c_str(), Shader::Type::kVertex); + Shader fs(kFragmentShader.c_str(), Shader::Type::kFragment); + if (!vs.Compile()) { + throw std::runtime_error("OccupancyGrid: vertex shader compile failed"); + } + if (!fs.Compile()) { + throw std::runtime_error("OccupancyGrid: fragment shader compile failed"); + } + shader_.AttachShader(vs); + shader_.AttachShader(fs); + if (!shader_.LinkProgram()) { + throw std::runtime_error("OccupancyGrid: shader program link failed"); + } + + glGenVertexArrays(1, &vao_); + glGenBuffers(1, &vbo_); + glBindVertexArray(vao_); + glBindBuffer(GL_ARRAY_BUFFER, vbo_); + glBufferData(GL_ARRAY_BUFFER, sizeof(kQuadVertices), kQuadVertices, + GL_STATIC_DRAW); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), + (void*)0); + glEnableVertexAttribArray(0); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), + (void*)(2 * sizeof(float))); + glEnableVertexAttribArray(1); + glBindVertexArray(0); + + // Texture is created lazily on the first SetGrid; AllocateGpuResources + // only sets up shader + VAO/VBO so the renderable is safe to draw + // before the first SetGrid (it'll just render nothing). +} + +void OccupancyGrid::ReleaseGpuResources() noexcept { + if (texture_id_) { + glDeleteTextures(1, &texture_id_); + texture_id_ = 0; + } + if (vbo_) { + glDeleteBuffers(1, &vbo_); + vbo_ = 0; + } + if (vao_) { + glDeleteVertexArrays(1, &vao_); + vao_ = 0; + } +} + +void OccupancyGrid::SetGrid(uint32_t width, uint32_t height, float resolution, + const glm::vec3& origin, + const std::vector& values) { + if (width == 0 || height == 0) { + width_ = height_ = 0; + origin_ = origin; + resolution_ = resolution; + return; + } + if (resolution <= 0.0f) { + throw std::invalid_argument( + "OccupancyGrid::SetGrid: resolution must be positive"); + } + if (values.size() != static_cast(width) * height) { + throw std::invalid_argument( + "OccupancyGrid::SetGrid: values.size() must equal width * height"); + } + + EnsureTextureSize(width, height); + resolution_ = resolution; + origin_ = origin; + UploadTexel(values); +} + +void OccupancyGrid::EnsureTextureSize(uint32_t width, uint32_t height) { + if (texture_id_ == 0) { + glGenTextures(1, &texture_id_); + glBindTexture(GL_TEXTURE_2D, texture_id_); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + if (width_ != width || height_ != height) { + glBindTexture(GL_TEXTURE_2D, texture_id_); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, + GL_UNSIGNED_BYTE, nullptr); + width_ = width; + height_ = height; + } +} + +void OccupancyGrid::UploadTexel(const std::vector& values) { + std::vector packed; + packed.reserve(values.size()); + for (auto v : values) packed.push_back(EncodeOccupancy(v)); + + glBindTexture(GL_TEXTURE_2D, texture_id_); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, GL_RED, + GL_UNSIGNED_BYTE, packed.data()); +} + +void OccupancyGrid::OnDraw(const glm::mat4& projection, const glm::mat4& view, + const glm::mat4& coord_transform) { + if (width_ == 0 || height_ == 0 || texture_id_ == 0) return; + + shader_.Use(); + shader_.SetUniform("projection", projection); + shader_.SetUniform("view", view); + shader_.SetUniform("model", GetTransform()); + shader_.SetUniform("coordSystemTransform", coord_transform); + shader_.SetUniform("uOrigin", origin_); + shader_.SetUniform( + "uExtent", + glm::vec3(static_cast(width_) * resolution_, + static_cast(height_) * resolution_, 0.0f)); + shader_.SetUniform("uFree", free_color_); + shader_.SetUniform("uOccupied", occupied_color_); + shader_.SetUniform("uUnknown", unknown_color_); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture_id_); + shader_.SetUniform("uTex", 0); + + glBindVertexArray(vao_); + glDrawArrays(GL_TRIANGLES, 0, 6); + glBindVertexArray(0); +} + +} // namespace quickviz diff --git a/src/scene/test/renderable/CMakeLists.txt b/src/scene/test/renderable/CMakeLists.txt index 8047c65..4eb6245 100644 --- a/src/scene/test/renderable/CMakeLists.txt +++ b/src/scene/test/renderable/CMakeLists.txt @@ -22,6 +22,9 @@ target_link_libraries(test_frustum PRIVATE scene) add_executable(test_grid test_grid.cpp) target_link_libraries(test_grid PRIVATE scene) +add_executable(test_occupancy_grid test_occupancy_grid.cpp) +target_link_libraries(test_occupancy_grid PRIVATE scene) + add_executable(test_line_strip test_line_strip.cpp) target_link_libraries(test_line_strip PRIVATE scene) diff --git a/src/scene/test/renderable/test_occupancy_grid.cpp b/src/scene/test/renderable/test_occupancy_grid.cpp new file mode 100644 index 0000000..49de779 --- /dev/null +++ b/src/scene/test/renderable/test_occupancy_grid.cpp @@ -0,0 +1,98 @@ +/** + * @file test_occupancy_grid.cpp + * @brief Visual test for the OccupancyGrid renderable + * + * Renders a 50x50 occupancy grid with a hand-crafted pattern that + * exercises all three cell states (free, occupied, unknown). Run and + * inspect: + * + * - Outer frame of black (occupied) cells. + * - Inner light-gray (free) field with a diagonal corridor of + * intermediate occupancy values. + * - A square patch of unknown (medium gray) cells in the lower-left. + * + * Copyright (c) 2026 Ruixiang Du (rdu) + */ + +#include +#include +#include + +#include + +#include "scene/renderable/grid.hpp" +#include "scene/renderable/occupancy_grid.hpp" +#include "scene/scene_app.hpp" + +using namespace quickviz; + +namespace { + +constexpr uint32_t kWidth = 50; +constexpr uint32_t kHeight = 50; +constexpr float kResolution = 0.2f; // 0.2m per cell → 10m × 10m grid + +std::vector BuildPattern() { + std::vector data(kWidth * kHeight, 0); // free everywhere + + for (uint32_t y = 0; y < kHeight; ++y) { + for (uint32_t x = 0; x < kWidth; ++x) { + const std::size_t idx = y * kWidth + x; + + // Outer one-cell border: occupied. + if (x == 0 || y == 0 || x == kWidth - 1 || y == kHeight - 1) { + data[idx] = 100; + } + + // Diagonal corridor with intermediate occupancy values. + if (x == y) { + data[idx] = 50; + } + + // Lower-left square of unknown cells. + if (x < 12 && y < 12 && (x > 0 && y > 0)) { + data[idx] = -1; + } + } + } + return data; +} + +} // namespace + +void Setup(SceneManager* scene) { + // Reference grid at the same resolution / extent so the cells line up + // with the world axes for easy visual verification. + auto reference = std::make_unique( + static_cast(kWidth) * kResolution, kResolution, + glm::vec3(0.6f, 0.6f, 0.6f)); + scene->AddOpenGLObject("grid", std::move(reference)); + + auto og = std::make_unique(); + // Origin chosen so the centre of the grid sits at world origin. + const float half_w = 0.5f * static_cast(kWidth) * kResolution; + const float half_h = 0.5f * static_cast(kHeight) * kResolution; + og->SetGrid(kWidth, kHeight, kResolution, + glm::vec3(-half_w, -half_h, 0.0f), BuildPattern()); + scene->AddOpenGLObject("occupancy_grid", std::move(og)); +} + +int main() { + try { + SceneApp::Config config; + config.window_title = "OccupancyGrid Rendering Test"; + config.show_grid = false; // we add our own at the matching resolution + + SceneApp view(config); + view.SetDescription( + "OccupancyGrid: 50x50 cells at 0.2m resolution. Outer frame " + "occupied (black), interior free (light gray), diagonal " + "intermediate, lower-left corner unknown (medium gray)."); + view.SetSceneSetup(Setup); + view.Run(); + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } + return 0; +} From 54852f0c70b38a6de479ea7f94fdd798d5232ccd Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 23:18:31 +0800 Subject: [PATCH 16/22] feat(scene): TfFrameTree renderable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a renderable tree of named coordinate frames analogous to ROS tf2. Each frame has a name, a parent (or empty for a root), and a transform expressed in the parent's coordinates. World transforms are computed by walking parent chains. API (scene/renderable/tf_frame_tree.hpp): SetFrame(name, parent, transform_in_parent) // add or update RemoveFrame(name) → size_t // removes descendants too Clear() HasFrame(name) → bool GetWorldTransform(name) → mat4 SetAxisLength(float) SetShowConnections(bool) SetConnectionColor(vec3) Each frame renders as a colored axis triplet (X=red, Y=green, Z=blue). Parent → child origin connection lines are drawn in gray when enabled, giving an at-a-glance view of tree topology. Implementation: - Frames stored in unordered_map - World transforms recomputed lazily before each draw when dirty - Cycle detection in GetWorldTransform: bails out and returns identity - Broken parent chains (parent name referenced but not registered): treated as "implicit identity" — frame visualized at its accumulated transform up to the missing link, rather than throwing - Single VAO with position+color vertices, one GL_LINES draw call - Vertex buffer reused with glBufferSubData when frame count fits; reallocates on growth Visual test fixture (scene/test/renderable/test_tf_frame_tree.cpp) animates a six-frame robot-like kinematics tree: world → base → {lidar, arm_base → arm_link_1 → arm_tip} The base spins; the arm shoulder oscillates; grandchildren update without restating the chain because GetWorldTransform walks parents on every draw. Build clean. 122 tests pass; same 2 pre-existing PCL failures. Library boundary holds. Closes the second item under "first standard robotics renderable" in TODO.md (`Trajectory` extensions on `Path` remain). The TfFrameTree is also a prerequisite for the upcoming ROS2 tf2_msgs::TFMessage converter. Co-Authored-By: Claude Opus 4.7 (1M context) --- TODO.md | 13 +- src/scene/CMakeLists.txt | 1 + .../scene/renderable/tf_frame_tree.hpp | 139 ++++++++++ src/scene/src/renderable/tf_frame_tree.cpp | 251 ++++++++++++++++++ src/scene/test/renderable/CMakeLists.txt | 3 + .../test/renderable/test_tf_frame_tree.cpp | 112 ++++++++ 6 files changed, 516 insertions(+), 3 deletions(-) create mode 100644 src/scene/include/scene/renderable/tf_frame_tree.hpp create mode 100644 src/scene/src/renderable/tf_frame_tree.cpp create mode 100644 src/scene/test/renderable/test_tf_frame_tree.cpp diff --git a/TODO.md b/TODO.md index 2512ff6..7303410 100644 --- a/TODO.md +++ b/TODO.md @@ -49,9 +49,10 @@ Pick one and ship it cleanly before the next. Don't backlog all three. scene. ROS-style int8 input (`-1` unknown, `0..100` occupancy percent). R8 texture + custom colormap shader. Visual test fixture in `scene/test/renderable/test_occupancy_grid.cpp`. -- [ ] `TfFrameTree` renderable — animated transform tree with named - frames and parent/child relationships. Likely composes multiple - `CoordinateFrame` instances. Companion to tf2. +- [x] `TfFrameTree` renderable — named frames with parent/child + transforms; renders RGB axes per frame plus optional gray + connection lines. Walks parent chains at draw time; cycles + + broken parent references handled defensively. - [ ] `Trajectory` extensions on the existing `Path` renderable — timestamp coloring, velocity coloring, animated growth. `Path` already covers "render a 3D motion path"; this milestone adds the @@ -170,6 +171,12 @@ visualization concern?" before merging. ### April 2026 +- ✅ **`TfFrameTree` renderable** — tree of named coordinate frames with + parent/child transforms, ROS tf2-style. Each frame renders as RGB + axes; gray lines connect parents to children when enabled. World + transforms computed by walking parent chains; cycles and broken + parent references handled defensively. Visual test fixture animates + a 6-frame robot kinematics tree. - ✅ **`OccupancyGrid` renderable** — 2D probabilistic grid as a textured quad in the XY plane. ROS-style API (`SetGrid(width, height, resolution, origin, vector)`, diff --git a/src/scene/CMakeLists.txt b/src/scene/CMakeLists.txt index dc8b4ef..1c38ecf 100644 --- a/src/scene/CMakeLists.txt +++ b/src/scene/CMakeLists.txt @@ -45,6 +45,7 @@ add_library(scene src/renderable/plane.cpp src/renderable/pose.cpp src/renderable/path.cpp + src/renderable/tf_frame_tree.cpp ## feedback system src/feedback/visual_feedback_system.cpp src/feedback/point_cloud_feedback_handler.cpp diff --git a/src/scene/include/scene/renderable/tf_frame_tree.hpp b/src/scene/include/scene/renderable/tf_frame_tree.hpp new file mode 100644 index 0000000..50769ce --- /dev/null +++ b/src/scene/include/scene/renderable/tf_frame_tree.hpp @@ -0,0 +1,139 @@ +/** + * @file tf_frame_tree.hpp + * @brief Renderable tree of named coordinate frames + * + * Visualizes a hierarchy of coordinate frames analogous to ROS tf2: each + * frame has a name and a transform expressed in its parent's frame. World + * transforms are computed by walking parent chains. + * + * Each frame renders as a small RGB axis triplet (X=red, Y=green, + * Z=blue). Parent → child connection lines are drawn in gray when + * enabled, giving a quick visual of tree topology. + * + * Threading: `SetFrame`, `RemoveFrame`, and `Clear` mutate internal + * state and rebuild the GPU vertex buffer; they must be called on the + * render thread. `OnDraw` is called by the scene manager. + * + * Copyright (c) 2026 Ruixiang Du (rdu) + */ + +#ifndef QUICKVIZ_TF_FRAME_TREE_HPP +#define QUICKVIZ_TF_FRAME_TREE_HPP + +#include +#include +#include +#include + +#include + +#include "scene/interface/opengl_object.hpp" +#include "../shader_program.hpp" + +namespace quickviz { + +class TfFrameTree : public OpenGlObject { + public: + explicit TfFrameTree(float axis_length = 0.3f); + ~TfFrameTree(); + + // === Frame management === + + /** + * @brief Add or update a frame. + * + * @param name Unique frame identifier. + * @param parent Parent frame name, or empty string for a + * root frame (rooted in world). + * @param transform_in_parent 4x4 transform of `name` in `parent`'s + * coordinates (or world if no parent). + * + * If `name` already exists, its parent and transform are updated; + * children are re-rooted automatically because they reference by + * parent name. + */ + void SetFrame(const std::string& name, const std::string& parent, + const glm::mat4& transform_in_parent); + + /** + * @brief Remove a frame and all its descendants. + * @return Number of frames removed. + */ + std::size_t RemoveFrame(const std::string& name); + + /// Remove all frames. + void Clear(); + + /// Number of frames currently in the tree. + std::size_t GetFrameCount() const { return frames_.size(); } + + /// Returns true if the frame exists in the tree. + bool HasFrame(const std::string& name) const; + + /** + * @brief World-space transform for a named frame. + * + * Walks parents until a root is reached. Returns identity if the + * frame doesn't exist or has a broken parent chain. + */ + glm::mat4 GetWorldTransform(const std::string& name) const; + + // === Appearance === + + /// Length (world units) of each axis line. Default: 0.3. + void SetAxisLength(float length); + + /// Whether to draw lines connecting parent origins to child origins. + void SetShowConnections(bool show) { show_connections_ = show; dirty_ = true; } + bool GetShowConnections() const { return show_connections_; } + + /// Color of parent→child connection lines. Default: medium gray. + void SetConnectionColor(const glm::vec3& color) { + connection_color_ = color; + dirty_ = true; + } + + // === OpenGlObject interface === + + void AllocateGpuResources() override; + void ReleaseGpuResources() noexcept override; + void OnDraw(const glm::mat4& projection, const glm::mat4& view, + const glm::mat4& coord_transform = glm::mat4(1.0f)) override; + bool IsGpuResourcesAllocated() const noexcept override { + return vao_ != 0; + } + + private: + struct Frame { + std::string parent; // empty == world root + glm::mat4 local{1.0f}; // transform in parent's frame + }; + + struct Vertex { + glm::vec3 position; + glm::vec3 color; + }; + + // Recompute the vertex buffer from the current tree state. Called + // lazily before drawing if dirty_ is set. + void RebuildVertices(); + + std::unordered_map frames_; + float axis_length_ = 0.3f; + bool show_connections_ = true; + glm::vec3 connection_color_ = glm::vec3(0.5f, 0.5f, 0.5f); + + // GL resources + uint32_t vao_ = 0; + uint32_t vbo_ = 0; + ShaderProgram shader_; + + // CPU-side vertex cache (rebuilt when tree changes). + std::vector vertices_; + bool dirty_ = true; + std::size_t buffer_capacity_ = 0; +}; + +} // namespace quickviz + +#endif // QUICKVIZ_TF_FRAME_TREE_HPP diff --git a/src/scene/src/renderable/tf_frame_tree.cpp b/src/scene/src/renderable/tf_frame_tree.cpp new file mode 100644 index 0000000..374ea9c --- /dev/null +++ b/src/scene/src/renderable/tf_frame_tree.cpp @@ -0,0 +1,251 @@ +/** + * @file tf_frame_tree.cpp + * + * Copyright (c) 2026 Ruixiang Du (rdu) + */ + +#include "scene/renderable/tf_frame_tree.hpp" + +#include +#include + +#include "glad/glad.h" +#include + +#include "scene/shader.hpp" + +namespace quickviz { +namespace { + +const std::string kVertexShader = R"( +#version 330 core +layout(location = 0) in vec3 aPos; +layout(location = 1) in vec3 aColor; + +uniform mat4 projection; +uniform mat4 view; +uniform mat4 model; +uniform mat4 coordSystemTransform; + +out vec3 vColor; + +void main() { + gl_Position = projection * view * coordSystemTransform * model * vec4(aPos, 1.0); + vColor = aColor; +} +)"; + +const std::string kFragmentShader = R"( +#version 330 core +in vec3 vColor; +out vec4 FragColor; +void main() { FragColor = vec4(vColor, 1.0); } +)"; + +} // namespace + +TfFrameTree::TfFrameTree(float axis_length) : axis_length_(axis_length) { + AllocateGpuResources(); +} + +TfFrameTree::~TfFrameTree() { ReleaseGpuResources(); } + +void TfFrameTree::AllocateGpuResources() { + if (IsGpuResourcesAllocated()) return; + + Shader vs(kVertexShader.c_str(), Shader::Type::kVertex); + Shader fs(kFragmentShader.c_str(), Shader::Type::kFragment); + if (!vs.Compile()) { + throw std::runtime_error("TfFrameTree: vertex shader compile failed"); + } + if (!fs.Compile()) { + throw std::runtime_error("TfFrameTree: fragment shader compile failed"); + } + shader_.AttachShader(vs); + shader_.AttachShader(fs); + if (!shader_.LinkProgram()) { + throw std::runtime_error("TfFrameTree: shader program link failed"); + } + + glGenVertexArrays(1, &vao_); + glGenBuffers(1, &vbo_); + glBindVertexArray(vao_); + glBindBuffer(GL_ARRAY_BUFFER, vbo_); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), + (void*)offsetof(Vertex, position)); + glEnableVertexAttribArray(0); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), + (void*)offsetof(Vertex, color)); + glEnableVertexAttribArray(1); + glBindVertexArray(0); +} + +void TfFrameTree::ReleaseGpuResources() noexcept { + if (vbo_) { + glDeleteBuffers(1, &vbo_); + vbo_ = 0; + } + if (vao_) { + glDeleteVertexArrays(1, &vao_); + vao_ = 0; + } +} + +void TfFrameTree::SetFrame(const std::string& name, const std::string& parent, + const glm::mat4& transform_in_parent) { + if (name.empty()) { + throw std::invalid_argument("TfFrameTree::SetFrame: name cannot be empty"); + } + if (name == parent) { + throw std::invalid_argument( + "TfFrameTree::SetFrame: a frame cannot be its own parent"); + } + Frame& f = frames_[name]; + f.parent = parent; + f.local = transform_in_parent; + dirty_ = true; +} + +std::size_t TfFrameTree::RemoveFrame(const std::string& name) { + if (frames_.find(name) == frames_.end()) return 0; + + // Collect descendants by name (children-of relation). + std::unordered_set to_remove; + to_remove.insert(name); + + bool changed = true; + while (changed) { + changed = false; + for (const auto& [n, f] : frames_) { + if (to_remove.count(n)) continue; + if (to_remove.count(f.parent)) { + to_remove.insert(n); + changed = true; + } + } + } + + for (const auto& n : to_remove) frames_.erase(n); + dirty_ = true; + return to_remove.size(); +} + +void TfFrameTree::Clear() { + frames_.clear(); + dirty_ = true; +} + +bool TfFrameTree::HasFrame(const std::string& name) const { + return frames_.find(name) != frames_.end(); +} + +glm::mat4 TfFrameTree::GetWorldTransform(const std::string& name) const { + auto it = frames_.find(name); + if (it == frames_.end()) return glm::mat4(1.0f); + + // Walk parents, accumulating transforms. Detect cycles by visited set. + glm::mat4 accumulated = it->second.local; + std::string current = it->second.parent; + std::unordered_set visited{name}; + while (!current.empty()) { + if (visited.count(current)) { + // Cycle: bail out, return identity for safety. + return glm::mat4(1.0f); + } + visited.insert(current); + auto parent_it = frames_.find(current); + if (parent_it == frames_.end()) { + // Broken chain: parent referenced but not registered. Treat as + // root for visualization purposes (the missing parent is + // implicitly identity). + break; + } + accumulated = parent_it->second.local * accumulated; + current = parent_it->second.parent; + } + return accumulated; +} + +void TfFrameTree::SetAxisLength(float length) { + if (length <= 0.0f) { + throw std::invalid_argument( + "TfFrameTree::SetAxisLength: length must be positive"); + } + axis_length_ = length; + dirty_ = true; +} + +void TfFrameTree::RebuildVertices() { + vertices_.clear(); + if (frames_.empty()) { + dirty_ = false; + return; + } + + // Pre-compute world transforms once. + std::unordered_map world_xforms; + world_xforms.reserve(frames_.size()); + for (const auto& [name, _] : frames_) { + world_xforms.emplace(name, GetWorldTransform(name)); + } + + vertices_.reserve(frames_.size() * 6 + + (show_connections_ ? frames_.size() * 2 : 0)); + + const glm::vec3 red(1.0f, 0.0f, 0.0f); + const glm::vec3 green(0.0f, 1.0f, 0.0f); + const glm::vec3 blue(0.0f, 0.0f, 1.0f); + + for (const auto& [name, frame] : frames_) { + const glm::mat4& W = world_xforms.at(name); + const glm::vec3 origin(W[3]); + const glm::vec3 ex = glm::vec3(W * glm::vec4(axis_length_, 0, 0, 1)); + const glm::vec3 ey = glm::vec3(W * glm::vec4(0, axis_length_, 0, 1)); + const glm::vec3 ez = glm::vec3(W * glm::vec4(0, 0, axis_length_, 1)); + + vertices_.push_back({origin, red}); + vertices_.push_back({ex, red}); + vertices_.push_back({origin, green}); + vertices_.push_back({ey, green}); + vertices_.push_back({origin, blue}); + vertices_.push_back({ez, blue}); + + if (show_connections_ && !frame.parent.empty()) { + auto parent_it = world_xforms.find(frame.parent); + if (parent_it != world_xforms.end()) { + const glm::vec3 parent_origin(parent_it->second[3]); + vertices_.push_back({parent_origin, connection_color_}); + vertices_.push_back({origin, connection_color_}); + } + } + } + + glBindBuffer(GL_ARRAY_BUFFER, vbo_); + const std::size_t bytes = vertices_.size() * sizeof(Vertex); + if (vertices_.size() > buffer_capacity_) { + glBufferData(GL_ARRAY_BUFFER, bytes, vertices_.data(), GL_DYNAMIC_DRAW); + buffer_capacity_ = vertices_.size(); + } else { + glBufferSubData(GL_ARRAY_BUFFER, 0, bytes, vertices_.data()); + } + + dirty_ = false; +} + +void TfFrameTree::OnDraw(const glm::mat4& projection, const glm::mat4& view, + const glm::mat4& coord_transform) { + if (dirty_) RebuildVertices(); + if (vertices_.empty()) return; + + shader_.Use(); + shader_.SetUniform("projection", projection); + shader_.SetUniform("view", view); + shader_.SetUniform("model", GetTransform()); + shader_.SetUniform("coordSystemTransform", coord_transform); + + glBindVertexArray(vao_); + glDrawArrays(GL_LINES, 0, static_cast(vertices_.size())); + glBindVertexArray(0); +} + +} // namespace quickviz diff --git a/src/scene/test/renderable/CMakeLists.txt b/src/scene/test/renderable/CMakeLists.txt index 4eb6245..b02823c 100644 --- a/src/scene/test/renderable/CMakeLists.txt +++ b/src/scene/test/renderable/CMakeLists.txt @@ -25,6 +25,9 @@ target_link_libraries(test_grid PRIVATE scene) add_executable(test_occupancy_grid test_occupancy_grid.cpp) target_link_libraries(test_occupancy_grid PRIVATE scene) +add_executable(test_tf_frame_tree test_tf_frame_tree.cpp) +target_link_libraries(test_tf_frame_tree PRIVATE scene) + add_executable(test_line_strip test_line_strip.cpp) target_link_libraries(test_line_strip PRIVATE scene) diff --git a/src/scene/test/renderable/test_tf_frame_tree.cpp b/src/scene/test/renderable/test_tf_frame_tree.cpp new file mode 100644 index 0000000..4c89d68 --- /dev/null +++ b/src/scene/test/renderable/test_tf_frame_tree.cpp @@ -0,0 +1,112 @@ +/** + * @file test_tf_frame_tree.cpp + * @brief Visual test for TfFrameTree + * + * Builds a small robot-like kinematics tree and animates the joints + * over time so the parent-child relationships are visible. Run and + * inspect: + * + * - Six axis triplets (RGB) at: + * world (origin), base, lidar, arm_base, arm_link_1, arm_tip + * - Gray lines connecting parents to children, showing tree topology + * - Smooth animation of the arm and base over time + * + * Copyright (c) 2026 Ruixiang Du (rdu) + */ + +#include +#include +#include +#include + +#include +#include + +#include "scene/renderable/grid.hpp" +#include "scene/renderable/tf_frame_tree.hpp" +#include "scene/scene_app.hpp" + +using namespace quickviz; + +namespace { + +glm::mat4 Translation(const glm::vec3& t) { + return glm::translate(glm::mat4(1.0f), t); +} + +glm::mat4 RotZ(float angle) { + return glm::rotate(glm::mat4(1.0f), angle, glm::vec3(0.0f, 0.0f, 1.0f)); +} + +} // namespace + +int main() { + try { + SceneApp::Config config; + config.window_title = "TfFrameTree Rendering Test"; + SceneApp view(config); + + view.SetDescription( + "Six-frame robot kinematics tree with animated joints. Each " + "frame draws as RGB axes; gray lines connect parents to children."); + + // Capture by reference into the lambda; SceneApp keeps it alive. + TfFrameTree* tree_ptr = nullptr; + + view.SetSceneSetup([&tree_ptr](SceneManager* scene) { + auto reference = std::make_unique(8.0f, 0.5f, + glm::vec3(0.6f, 0.6f, 0.6f)); + scene->AddOpenGLObject("grid", std::move(reference)); + + auto tree = std::make_unique(/*axis_length=*/0.4f); + + // Static parts of the tree. + tree->SetFrame("world", "", glm::mat4(1.0f)); + tree->SetFrame("base", "world", + Translation(glm::vec3(1.0f, 1.0f, 0.0f)) * RotZ(0.0f)); + tree->SetFrame("lidar", "base", + Translation(glm::vec3(0.3f, 0.0f, 0.4f))); + tree->SetFrame("arm_base", "base", + Translation(glm::vec3(0.0f, 0.0f, 0.5f))); + tree->SetFrame("arm_link_1", "arm_base", + Translation(glm::vec3(0.5f, 0.0f, 0.0f))); + tree->SetFrame("arm_tip", "arm_link_1", + Translation(glm::vec3(0.5f, 0.0f, 0.0f))); + + tree_ptr = tree.get(); + scene->AddOpenGLObject("tf_tree", std::move(tree)); + + // Animate base yaw + arm joint angles each frame. + const auto t0 = std::chrono::steady_clock::now(); + scene->SetPreDrawCallback([&tree_ptr, t0]() { + if (!tree_ptr) return; + const float t = std::chrono::duration( + std::chrono::steady_clock::now() - t0).count(); + + // Base spins slowly. + tree_ptr->SetFrame( + "base", "world", + Translation(glm::vec3(1.0f, 1.0f, 0.0f)) * RotZ(0.3f * t)); + + // Arm shoulder oscillates. + tree_ptr->SetFrame( + "arm_link_1", "arm_base", + RotZ(0.6f * std::sin(1.2f * t)) * + Translation(glm::vec3(0.5f, 0.0f, 0.0f))); + + // Arm tip rotates a bit too — grandchildren update without + // restating the chain because GetWorldTransform walks parents. + tree_ptr->SetFrame( + "arm_tip", "arm_link_1", + RotZ(0.8f * std::cos(1.5f * t)) * + Translation(glm::vec3(0.5f, 0.0f, 0.0f))); + }); + }); + + view.Run(); + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } + return 0; +} From 11147802753a9366ed56c0bb70b86aa26b55a66a Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 23:19:42 +0800 Subject: [PATCH 17/22] docs: codify "external SDK deps must be optional" as a hard rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User direction: ROS2 (and any future module that depends on ROS msgs) must be CMake-gated so the library still compiles in environments without ROS installed. Generalizing to all heavyweight optional SDKs. CLAUDE.md §4 gains an "Optional external dependencies" subsection with the pattern (find_package QUIET → early return from CMakeLists, no leak of optional types into non-gated code) and an explicit ROS2 callout for current/future contributors. TODO.md ROS2 milestone: - Promotes the optional-CMake rule from a checkbox at the bottom to a callout at the top of the milestone, since it constrains every sub-task. - Notes that two of the converter targets (OccupancyGrid, TfFrameTree) already have their renderable counterparts shipped — the bridge work reduces to writing converters into existing renderables. - Adds an explicit "sample app must also be CMake-gated" line so a ROS2 demo doesn't accidentally creep into the always-built sample set. No code change. Same 122 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 20 ++++++++++++++++++++ TODO.md | 19 ++++++++++++++----- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3bb0b7c..8b95fae 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -113,6 +113,26 @@ When tempted to add a cross-module dependency, ask: is this concept truly shared, or am I leaking implementation? Prefer to widen the public API of the lower module than to reach across at the same level. +### Optional external dependencies + +Any module or component that depends on a heavyweight external SDK +(ROS2, PCL, OpenCV, vendor sensor SDKs) **must** be CMake-gated so the +library compiles cleanly without that SDK installed. The pattern: + +- Use `find_package( QUIET)`. If absent, `return()` early from the + module's `CMakeLists.txt`. +- Do not reference the optional dep's headers, types, or functions + outside the gated module's source files. +- Document the dep in §5 Build System under "Optional". +- Library users without the dep get a working library with that + feature missing, not a build failure. + +This applies specifically to ROS2: any current or future module that +consumes ROS messages — `bridges/ros2/`, downstream converters, +sample apps that use them — must be optional. A user who has never +installed ROS must still be able to clone, configure, build, and run +the rest of the library. + --- ## 5. Build System diff --git a/TODO.md b/TODO.md index 7303410..889b5da 100644 --- a/TODO.md +++ b/TODO.md @@ -31,17 +31,26 @@ Ordered by what unblocks the most downstream work. ### Milestone — ROS2 integration (`bridges/ros2/`) The single biggest user-facing gap. Without this QuickViz is a general C++ vis library; with it, it's the robotics vis library. + +**Hard rule for this and any ROS-dependent module**: ROS2 is an +**optional** CMake dep. The library must compile, link, and run +cleanly on systems where ROS2 is not installed — just without ROS +support. Use `find_package(... QUIET)` + early `return()` from the +module's CMakeLists when absent. No ROS headers / types may leak into +non-gated code paths. (See CLAUDE.md §4 "Optional external dependencies".) - [ ] Stand up `bridges/` umbrella with `bridges/ros2/` as the first child (deferred from the earlier reorg — now justified by a real - occupant). + occupant). CMake-gated; absent ROS = silent skip. - [ ] `sensor_msgs::PointCloud2` ↔ `PointCloud` renderable converter - [ ] `geometry_msgs::PoseStamped` / `PoseArray` ↔ pose / trajectory -- [ ] `nav_msgs::OccupancyGrid` ↔ grid renderable -- [ ] `tf2_msgs::TFMessage` ↔ frame-tree visualization +- [ ] `nav_msgs::OccupancyGrid` ↔ `OccupancyGrid` renderable (the + renderable already exists as of April 2026) +- [ ] `tf2_msgs::TFMessage` ↔ `TfFrameTree` renderable (the renderable + already exists as of April 2026) - [ ] `visualization_msgs::Marker` / `MarkerArray` ↔ generic primitives passthrough -- [ ] CMake gating: ROS2 dep is optional; library still builds without - it +- [ ] Sample app demonstrating ROS2 streaming end-to-end. **Also + CMake-gated** — must not be built when ROS2 is absent. ### Milestone — first standard robotics renderable Pick one and ship it cleanly before the next. Don't backlog all three. From 27a258342f92004ffd45a1776a2d170297be1ee3 Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 23:28:45 +0800 Subject: [PATCH 18/22] refactor(scene): move Triangle to scene/test/test_utils/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Triangle is a 44-line test-only renderable — it's used by ~8 test fixtures (renderable demos, integration tests) but zero non-test code. It had accumulated in the public scene/renderable/ namespace where it read like a recommended building block. Move it to where it belongs. Changes: - src/scene/include/scene/renderable/triangle.hpp moved to src/scene/test/test_utils/test_utils/triangle.hpp - src/scene/src/renderable/triangle.cpp moved to src/scene/test/test_utils/triangle.cpp - New scene_test_utils STATIC library (publicly links scene) hosts test-only renderable helpers. Test executables that previously linked scene directly and used Triangle now link scene_test_utils instead; scene is provided transitively. - Update header guard to QUICKVIZ_TEST_UTILS_TRIANGLE_HPP and the internal scene/shader_program.hpp include path. - Sed all `scene/renderable/triangle.hpp` includes → `test_utils/triangle.hpp` across 8 test files. - Drop triangle.cpp from src/scene/CMakeLists.txt sources. - Add `add_subdirectory(test_utils)` to scene/test/CMakeLists.txt before the existing test sub-dirs that consume it. Walked back a related "extract shared axis-vertex generator" refactor that I'd suggested. Closer inspection showed the three axis-rendering classes (CoordinateFrame, Pose, TfFrameTree) draw visually different shapes (cone arrows vs. plain lines) in different coordinate frames (local + model matrix vs. pre-transformed world). A shared helper would force a worse abstraction across genuinely-different implementations. Documented this in TODO.md. Added to TODO.md "Smaller cleanups": audit GeometricPrimitive base class (405-LOC header) for over-scope. Real design review, not a quick rewrite — likely API-breaking. Build clean. 122 tests pass; same 2 pre-existing PCL failures, no new regressions. Library boundary holds. Co-Authored-By: Claude Opus 4.7 (1M context) --- TODO.md | 14 ++++++++++++++ src/scene/CMakeLists.txt | 1 - src/scene/test/CMakeLists.txt | 7 ++++--- src/scene/test/feature/CMakeLists.txt | 4 ++-- src/scene/test/feature/test_gl_scene_panel.cpp | 2 +- .../test/feature/test_visual_feedback_system.cpp | 2 +- src/scene/test/renderable/CMakeLists.txt | 4 ++-- src/scene/test/renderable/test_canvas.cpp | 2 +- src/scene/test/renderable/test_triangle.cpp | 2 +- src/scene/test/test_canvas_st.cpp | 2 +- src/scene/test/test_nav_map_rendering.cpp | 2 +- src/scene/test/test_primitive_drawing.cpp | 2 +- src/scene/test/test_utils/CMakeLists.txt | 15 +++++++++++++++ .../test_utils/test_utils}/triangle.hpp | 8 ++++---- .../renderable => test/test_utils}/triangle.cpp | 2 +- tests/CMakeLists.txt | 2 +- tests/integration/test_renderer_pipeline.cpp | 2 +- 17 files changed, 51 insertions(+), 22 deletions(-) create mode 100644 src/scene/test/test_utils/CMakeLists.txt rename src/scene/{include/scene/renderable => test/test_utils/test_utils}/triangle.hpp (85%) rename src/scene/{src/renderable => test/test_utils}/triangle.cpp (99%) diff --git a/TODO.md b/TODO.md index 889b5da..b8b5b5d 100644 --- a/TODO.md +++ b/TODO.md @@ -162,6 +162,11 @@ visualization concern?" before merging. ## 🧹 Smaller cleanups +- [ ] Audit `GeometricPrimitive` base class (405-LOC header) — it + bundles material system, render modes, and selection state for + `Sphere`/`Cylinder`/`BoundingBox`. The header is overly large + and possibly does too much. A real design review, not a quick + rewrite — likely API-breaking. - [ ] `src/scene/src/renderable/canvas.cpp` is 2069 LOC; split into cohesive sub-files (~500 LOC target per CLAUDE.md). - [ ] Clean `sample/pointcloud_viewer/interactive_scene_manager.cpp` @@ -180,6 +185,15 @@ visualization concern?" before merging. ### April 2026 +- ✅ **Triangle moved to `scene/test/test_utils/`** — accidentally-public + test scaffold removed from the public renderable API. New + `scene_test_utils` library hosts test-only helpers; the 8 tests that + used `Triangle` link against it instead of the main `scene` target. + Headed off a related axis-vertex-generator refactor after closer + inspection: the three axis-rendering classes (`CoordinateFrame`, + `Pose`, `TfFrameTree`) draw genuinely different shapes (cone-arrowed + vs. plain lines) in different coordinate frames; a shared helper + would force a worse abstraction. - ✅ **`TfFrameTree` renderable** — tree of named coordinate frames with parent/child transforms, ROS tf2-style. Each frame renders as RGB axes; gray lines connect parents to children when enabled. World diff --git a/src/scene/CMakeLists.txt b/src/scene/CMakeLists.txt index 1c38ecf..aae95c9 100644 --- a/src/scene/CMakeLists.txt +++ b/src/scene/CMakeLists.txt @@ -27,7 +27,6 @@ add_library(scene src/renderable/details/point_layer_manager.cpp src/renderable/grid.cpp src/renderable/occupancy_grid.cpp - src/renderable/triangle.cpp src/renderable/point_cloud.cpp src/renderable/canvas.cpp src/renderable/coordinate_frame.cpp diff --git a/src/scene/test/CMakeLists.txt b/src/scene/test/CMakeLists.txt index 264ade9..cfc7c0d 100644 --- a/src/scene/test/CMakeLists.txt +++ b/src/scene/test/CMakeLists.txt @@ -1,3 +1,4 @@ +add_subdirectory(test_utils) add_subdirectory(feature) add_subdirectory(renderable) add_subdirectory(selection) @@ -24,14 +25,14 @@ add_executable(test_coordinate_system test_coordinate_system.cpp) target_link_libraries(test_coordinate_system PRIVATE scene) add_executable(test_primitive_drawing test_primitive_drawing.cpp) -target_link_libraries(test_primitive_drawing PRIVATE scene) +target_link_libraries(test_primitive_drawing PRIVATE scene_test_utils) add_executable(test_canvas_st test_canvas_st.cpp) -target_link_libraries(test_canvas_st PRIVATE scene) +target_link_libraries(test_canvas_st PRIVATE scene_test_utils) add_executable(test_nav_map_rendering test_nav_map_rendering.cpp) -target_link_libraries(test_nav_map_rendering PRIVATE scene) +target_link_libraries(test_nav_map_rendering PRIVATE scene_test_utils) add_executable(test_layer_system_box test_layer_system_box.cpp) target_link_libraries(test_layer_system_box PRIVATE scene) diff --git a/src/scene/test/feature/CMakeLists.txt b/src/scene/test/feature/CMakeLists.txt index 7f10f0d..85da9be 100644 --- a/src/scene/test/feature/CMakeLists.txt +++ b/src/scene/test/feature/CMakeLists.txt @@ -1,5 +1,5 @@ add_executable(test_gl_scene_panel test_gl_scene_panel.cpp) -target_link_libraries(test_gl_scene_panel PRIVATE scene) +target_link_libraries(test_gl_scene_panel PRIVATE scene_test_utils) add_executable(test_robot_frames test_robot_frames.cpp) target_link_libraries(test_robot_frames PRIVATE scene) @@ -17,4 +17,4 @@ add_executable(test_camera_configuration test_camera_configuration.cpp) target_link_libraries(test_camera_configuration PRIVATE scene) add_executable(test_visual_feedback_system test_visual_feedback_system.cpp) -target_link_libraries(test_visual_feedback_system PRIVATE scene) +target_link_libraries(test_visual_feedback_system PRIVATE scene_test_utils) diff --git a/src/scene/test/feature/test_gl_scene_panel.cpp b/src/scene/test/feature/test_gl_scene_panel.cpp index 204323a..dddbda7 100644 --- a/src/scene/test/feature/test_gl_scene_panel.cpp +++ b/src/scene/test/feature/test_gl_scene_panel.cpp @@ -18,7 +18,7 @@ #include "scene/gl_scene_panel.hpp" #include "scene/renderable/grid.hpp" -#include "scene/renderable/triangle.hpp" +#include "test_utils/triangle.hpp" using namespace quickviz; diff --git a/src/scene/test/feature/test_visual_feedback_system.cpp b/src/scene/test/feature/test_visual_feedback_system.cpp index b2f3402..f8cb42e 100644 --- a/src/scene/test/feature/test_visual_feedback_system.cpp +++ b/src/scene/test/feature/test_visual_feedback_system.cpp @@ -15,7 +15,7 @@ #include "scene/gl_scene_panel.hpp" #include "scene/renderable/grid.hpp" #include "scene/renderable/point_cloud.hpp" -#include "scene/renderable/triangle.hpp" +#include "test_utils/triangle.hpp" #include "scene/feedback/visual_feedback_system.hpp" #include "imgui.h" diff --git a/src/scene/test/renderable/CMakeLists.txt b/src/scene/test/renderable/CMakeLists.txt index b02823c..bd91d32 100644 --- a/src/scene/test/renderable/CMakeLists.txt +++ b/src/scene/test/renderable/CMakeLists.txt @@ -8,7 +8,7 @@ add_executable(test_bounding_box test_bounding_box.cpp) target_link_libraries(test_bounding_box PRIVATE scene) add_executable(test_canvas test_canvas.cpp) -target_link_libraries(test_canvas PRIVATE scene) +target_link_libraries(test_canvas PRIVATE scene_test_utils) add_executable(test_coordinate_frame test_coordinate_frame.cpp) target_link_libraries(test_coordinate_frame PRIVATE scene) @@ -54,5 +54,5 @@ add_executable(test_texture test_texture.cpp) target_link_libraries(test_texture PRIVATE scene) add_executable(test_triangle test_triangle.cpp) -target_link_libraries(test_triangle PRIVATE scene) +target_link_libraries(test_triangle PRIVATE scene_test_utils) diff --git a/src/scene/test/renderable/test_canvas.cpp b/src/scene/test/renderable/test_canvas.cpp index 7f57dcb..16249f9 100644 --- a/src/scene/test/renderable/test_canvas.cpp +++ b/src/scene/test/renderable/test_canvas.cpp @@ -18,7 +18,7 @@ #include "scene/scene_app.hpp" #include "scene/renderable/canvas.hpp" -#include "scene/renderable/triangle.hpp" +#include "test_utils/triangle.hpp" using namespace quickviz; namespace fs = std::filesystem; diff --git a/src/scene/test/renderable/test_triangle.cpp b/src/scene/test/renderable/test_triangle.cpp index 25cd335..06732b7 100644 --- a/src/scene/test/renderable/test_triangle.cpp +++ b/src/scene/test/renderable/test_triangle.cpp @@ -16,7 +16,7 @@ #include #include "scene/scene_app.hpp" -#include "scene/renderable/triangle.hpp" +#include "test_utils/triangle.hpp" using namespace quickviz; diff --git a/src/scene/test/test_canvas_st.cpp b/src/scene/test/test_canvas_st.cpp index d24c4bc..e5adc85 100644 --- a/src/scene/test/test_canvas_st.cpp +++ b/src/scene/test/test_canvas_st.cpp @@ -19,7 +19,7 @@ #include "scene/gl_scene_panel.hpp" #include "scene/renderable/grid.hpp" -#include "scene/renderable/triangle.hpp" +#include "test_utils/triangle.hpp" #include "scene/renderable/coordinate_frame.hpp" #include "scene/renderable/canvas.hpp" diff --git a/src/scene/test/test_nav_map_rendering.cpp b/src/scene/test/test_nav_map_rendering.cpp index bbbef14..e2dfd45 100644 --- a/src/scene/test/test_nav_map_rendering.cpp +++ b/src/scene/test/test_nav_map_rendering.cpp @@ -19,7 +19,7 @@ #include "scene/gl_scene_panel.hpp" #include "scene/renderable/grid.hpp" -#include "scene/renderable/triangle.hpp" +#include "test_utils/triangle.hpp" #include "scene/renderable/coordinate_frame.hpp" #include "scene/renderable/canvas.hpp" diff --git a/src/scene/test/test_primitive_drawing.cpp b/src/scene/test/test_primitive_drawing.cpp index 4d7c3a3..7457ef6 100644 --- a/src/scene/test/test_primitive_drawing.cpp +++ b/src/scene/test/test_primitive_drawing.cpp @@ -19,7 +19,7 @@ #include "scene/gl_scene_panel.hpp" #include "scene/renderable/grid.hpp" -#include "scene/renderable/triangle.hpp" +#include "test_utils/triangle.hpp" #include "scene/renderable/coordinate_frame.hpp" #include "scene/renderable/canvas.hpp" diff --git a/src/scene/test/test_utils/CMakeLists.txt b/src/scene/test/test_utils/CMakeLists.txt new file mode 100644 index 0000000..0f5db50 --- /dev/null +++ b/src/scene/test/test_utils/CMakeLists.txt @@ -0,0 +1,15 @@ +# Test-only utilities for scene tests. Not part of the public library; +# never link these from non-test code. +# +# Provides simple renderables and helpers used across the renderable + +# selection + integration tests. Triangle lives here (rather than in the +# public scene library) because every real use is a test fixture or +# tutorial example — not application code. + +add_library(scene_test_utils STATIC + triangle.cpp) + +target_link_libraries(scene_test_utils PUBLIC scene) + +target_include_directories(scene_test_utils PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/scene/include/scene/renderable/triangle.hpp b/src/scene/test/test_utils/test_utils/triangle.hpp similarity index 85% rename from src/scene/include/scene/renderable/triangle.hpp rename to src/scene/test/test_utils/test_utils/triangle.hpp index c124fe0..9789c33 100644 --- a/src/scene/include/scene/renderable/triangle.hpp +++ b/src/scene/test/test_utils/test_utils/triangle.hpp @@ -7,13 +7,13 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#ifndef COMPONENT_OPENGL_TRIANGLE_HPP -#define COMPONENT_OPENGL_TRIANGLE_HPP +#ifndef QUICKVIZ_TEST_UTILS_TRIANGLE_HPP +#define QUICKVIZ_TEST_UTILS_TRIANGLE_HPP #include #include "scene/interface/opengl_object.hpp" -#include "../shader_program.hpp" +#include "scene/shader_program.hpp" namespace quickviz { class Triangle : public OpenGlObject { @@ -42,4 +42,4 @@ class Triangle : public OpenGlObject { }; } // namespace quickviz -#endif /* COMPONENT_OPENGL_TRIANGLE_HPP */ \ No newline at end of file +#endif /* QUICKVIZ_TEST_UTILS_TRIANGLE_HPP */ \ No newline at end of file diff --git a/src/scene/src/renderable/triangle.cpp b/src/scene/test/test_utils/triangle.cpp similarity index 99% rename from src/scene/src/renderable/triangle.cpp rename to src/scene/test/test_utils/triangle.cpp index b152210..ef55973 100644 --- a/src/scene/src/renderable/triangle.cpp +++ b/src/scene/test/test_utils/triangle.cpp @@ -7,7 +7,7 @@ * Copyright (c) 2025 Ruixiang Du (rdu) */ -#include "scene/renderable/triangle.hpp" +#include "test_utils/triangle.hpp" #include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 453f764..3e76051 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -79,7 +79,7 @@ target_link_libraries(test_renderer_pipeline PRIVATE gtest_main viewer - scene + scene_test_utils test_utils ) gtest_discover_tests(test_renderer_pipeline diff --git a/tests/integration/test_renderer_pipeline.cpp b/tests/integration/test_renderer_pipeline.cpp index fe949cd..19ecc9f 100644 --- a/tests/integration/test_renderer_pipeline.cpp +++ b/tests/integration/test_renderer_pipeline.cpp @@ -15,7 +15,7 @@ #include "viewer/viewer.hpp" #include "scene/gl_scene_panel.hpp" -#include "scene/renderable/triangle.hpp" +#include "test_utils/triangle.hpp" #include "scene/renderable/point_cloud.hpp" #include "scene/renderable/grid.hpp" #include "scene/camera.hpp" From a12d66dc75c5e37a3aefb07f7d476e118896d498 Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 23:35:29 +0800 Subject: [PATCH 19/22] feat(scene/path): trajectory streaming extensions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the "Trajectory extensions on existing Path renderable" item under the "first standard robotics renderable" milestone. The existing Path already had kVelocity / kTime / kCost color modes, scalar-value input, and SetAnimationProgress for partial drawing — what was missing was streaming-friendly machinery for live trajectories and a correctness bug under subdivided paths. Three changes, all small: 1. Streaming overload: `AddPoint(const glm::vec3& point, float scalar)` appends a control point and its scalar sample together so scalar_values_ stays aligned with control_points_ as new samples arrive. Padding behavior: if the user previously called the no-scalar AddPoint variant, scalar_values_ is back-filled with the newly supplied scalar before the push so alignment is preserved. 2. `EnableAutoColorRange(bool)` — when on, the color range used by kVelocity / kTime / kCost is computed from the current scalar_values_ (min..max) before each color recompute. Avoids the manual `SetColorRange` step that streaming trajectories don't have a sensible up-front value for. Off by default. 3. ComputePathColors fix: previously each path vertex was mapped to `scalar_values_[i]` directly, which is correct for kLineSegments (where path_vertices_.size() == control_points_.size()) but misaligned for kSmoothCurve / kBezierCurve / kSpline (which produce subdivisions × (N-1) + 1 vertices). Now each path vertex's fractional position along the path is computed and the scalar linearly interpolated between adjacent samples — colors transition smoothly along the curve instead of stepping at vertex boundaries. `ClearPath()` also now clears scalar_values_ alongside the geometry, so a reset doesn't leave stale scalars behind. Build clean. 122 tests pass; same 2 pre-existing PCL failures, no new regressions. Library boundary holds. The new APIs are exercised by existing test_path.cpp's color-encoding demos and will get real streaming exercise once the upcoming ROS2 PoseStamped converter and its sample land. Co-Authored-By: Claude Opus 4.7 (1M context) --- TODO.md | 21 +++++-- src/scene/include/scene/renderable/path.hpp | 12 ++++ src/scene/src/renderable/path.cpp | 69 +++++++++++++++++++-- 3 files changed, 91 insertions(+), 11 deletions(-) diff --git a/TODO.md b/TODO.md index b8b5b5d..5653eac 100644 --- a/TODO.md +++ b/TODO.md @@ -62,11 +62,12 @@ Pick one and ship it cleanly before the next. Don't backlog all three. transforms; renders RGB axes per frame plus optional gray connection lines. Walks parent chains at draw time; cycles + broken parent references handled defensively. -- [ ] `Trajectory` extensions on the existing `Path` renderable — - timestamp coloring, velocity coloring, animated growth. `Path` - already covers "render a 3D motion path"; this milestone adds the - streaming/time-aware behaviors. (Don't create a new renderable — - extend `Path`.) +- [x] `Trajectory` extensions on the existing `Path` renderable — + streaming-friendly `AddPoint(point, scalar)` overload, + `EnableAutoColorRange()` for live trajectories, and a correctness + fix for per-vertex scalar→color mapping under subdivided paths. + (Pre-existing color modes `kVelocity` / `kTime` / `kCost` and + `SetAnimationProgress` already covered the rest.) ### After ROS2 lands — diagnostics - [ ] **HUD overlay**: frame time, draw call count, GPU memory, active @@ -185,6 +186,16 @@ visualization concern?" before merging. ### April 2026 +- ✅ **Path: trajectory streaming extensions** — `AddPoint(point, scalar)` + overload pushes positions and scalar samples in lockstep for live + trajectory feeds. `EnableAutoColorRange()` auto-fits the velocity / + time / cost color range to the current `scalar_values_` so the + mapping adapts as samples arrive. Fixed a long-standing bug where + scalar-encoded colors on subdivided paths (smooth curve / Bezier / + spline) misaligned because the per-vertex mapping indexed + `scalar_values_` by raw vertex index; now uses fractional + control-point parameter and lerps. Closes the third item under the + "first standard robotics renderable" milestone. - ✅ **Triangle moved to `scene/test/test_utils/`** — accidentally-public test scaffold removed from the public renderable API. New `scene_test_utils` library hosts test-only helpers; the 8 tests that diff --git a/src/scene/include/scene/renderable/path.hpp b/src/scene/include/scene/renderable/path.hpp index 88e1d88..f5827fb 100644 --- a/src/scene/include/scene/renderable/path.hpp +++ b/src/scene/include/scene/renderable/path.hpp @@ -69,6 +69,10 @@ class Path : public OpenGlObject { // Path definition void SetPoints(const std::vector& points); void AddPoint(const glm::vec3& point); + /// Streaming overload: append a point together with its scalar value + /// (used by kVelocity / kTime / kCost color modes). Keeps scalar_values_ + /// aligned with control_points_ for live-trajectory use cases. + void AddPoint(const glm::vec3& point, float scalar); void InsertPoint(size_t index, const glm::vec3& point); void RemovePoint(size_t index); void ClearPath(); @@ -96,6 +100,13 @@ class Path : public OpenGlObject { ColorMode GetColorMode() const { return color_mode_; } glm::vec3 GetColor() const { return base_color_; } + /// When enabled, the color range used for kVelocity/kTime/kCost is + /// computed from `scalar_values_` (min/max) before each color update. + /// Off by default; enable for streaming trajectories so the color + /// mapping adapts as new samples arrive. + void EnableAutoColorRange(bool enable); + bool IsAutoColorRangeEnabled() const { return auto_color_range_; } + // Directional arrows void SetArrowMode(ArrowMode mode); void SetArrowSize(float size); @@ -165,6 +176,7 @@ class Path : public OpenGlObject { std::vector custom_colors_; std::vector scalar_values_; glm::vec2 color_range_; + bool auto_color_range_ = false; // Arrow properties ArrowMode arrow_mode_; diff --git a/src/scene/src/renderable/path.cpp b/src/scene/src/renderable/path.cpp index be57054..8db4dca 100644 --- a/src/scene/src/renderable/path.cpp +++ b/src/scene/src/renderable/path.cpp @@ -140,6 +140,20 @@ void Path::AddPoint(const glm::vec3& point) { needs_arrow_update_ = true; } +void Path::AddPoint(const glm::vec3& point, float scalar) { + control_points_.push_back(point); + // Keep scalar_values_ aligned with control_points_. If scalar_values_ + // is shorter (e.g. user previously called AddPoint without scalar), + // pad with the new scalar so we don't introduce a tail of stale data. + if (scalar_values_.size() < control_points_.size() - 1) { + scalar_values_.resize(control_points_.size() - 1, scalar); + } + scalar_values_.push_back(scalar); + needs_geometry_update_ = true; + needs_color_update_ = true; + needs_arrow_update_ = true; +} + void Path::InsertPoint(size_t index, const glm::vec3& point) { if (index <= control_points_.size()) { control_points_.insert(control_points_.begin() + index, point); @@ -160,6 +174,7 @@ void Path::RemovePoint(size_t index) { void Path::ClearPath() { control_points_.clear(); + scalar_values_.clear(); path_vertices_.clear(); path_colors_.clear(); path_indices_.clear(); @@ -238,6 +253,17 @@ void Path::SetScalarValues(const std::vector& values) { } } +void Path::EnableAutoColorRange(bool enable) { + if (auto_color_range_ != enable) { + auto_color_range_ = enable; + if (color_mode_ == ColorMode::kVelocity || + color_mode_ == ColorMode::kTime || + color_mode_ == ColorMode::kCost) { + needs_color_update_ = true; + } + } +} + void Path::SetArrowMode(ArrowMode mode) { if (arrow_mode_ != mode) { arrow_mode_ = mode; @@ -545,13 +571,44 @@ void Path::ComputePathColors() { case ColorMode::kVelocity: case ColorMode::kTime: case ColorMode::kCost: { - for (size_t i = 0; i < path_vertices_.size(); ++i) { + // Auto-fit color range from current scalar_values_ if requested. + // Lets streaming trajectories adjust their color mapping as new + // samples arrive without the caller managing the range manually. + if (auto_color_range_ && !scalar_values_.empty()) { + float lo = scalar_values_.front(); + float hi = scalar_values_.front(); + for (float v : scalar_values_) { + if (v < lo) lo = v; + if (v > hi) hi = v; + } + // Avoid degenerate range when all samples are equal. + if (hi <= lo) hi = lo + 1.0f; + color_range_ = glm::vec2(lo, hi); + } + + // Map each path vertex to a fractional control-point index, then + // interpolate scalar_values_ at that fraction. This is correct + // for both line-segment paths (path_vertices_.size() == + // control_points_.size()) and subdivided smooth paths + // (path_vertices_.size() > control_points_.size()), where the + // previous "scalar_values_[i]" indexing produced misaligned + // colors. + const size_t v_count = path_vertices_.size(); + const size_t s_count = scalar_values_.size(); + for (size_t i = 0; i < v_count; ++i) { float value = 0.0f; - if (i < scalar_values_.size()) { - value = scalar_values_[i]; - } else if (!scalar_values_.empty()) { - // Use last available value - value = scalar_values_.back(); + if (s_count >= 2) { + const float t = + (v_count <= 1) ? 0.0f + : (static_cast(i) / + static_cast(v_count - 1)) * + static_cast(s_count - 1); + const size_t lo = static_cast(t); + const size_t hi = std::min(lo + 1, s_count - 1); + const float frac = t - static_cast(lo); + value = glm::mix(scalar_values_[lo], scalar_values_[hi], frac); + } else if (s_count == 1) { + value = scalar_values_[0]; } path_colors_.push_back(ColorFromScalar(value)); } From a477e4f962af3fb1bafbf06afd7e400f3fc8c616 Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 23:43:41 +0800 Subject: [PATCH 20/22] feat(scene/path): per-pose orientation + ArrowMode::kPoseArrows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Path was position-only — its arrows showed direction of motion (path tangent). For real robotics trajectories the heading often differs from the travel direction (parallel parking, drone yaw, manipulator end-effector). Adds optional per-control-point orientations so a single Path can render either a position trajectory (existing behavior) or a full pose trajectory (new). API additions, all opt-in: void SetOrientations(const std::vector&); bool HasOrientations() const; void ClearOrientations(); const std::vector& GetOrientations() const; void AddPoint(const glm::vec3& point, const glm::quat& orientation); void AddPoint(const glm::vec3& point, const glm::quat& orientation, float scalar); enum class ArrowMode { ... kPoseArrows, // NEW }; `kPoseArrows` iterates control_points_ (not path_vertices_) and uses the stored orientation at each point to derive the arrow's basis vectors. Falls back to identity when orientations_ is shorter than control_points_, which renders as a +X-aligned arrow — the ROS-style "X is forward" convention. The other ArrowMode variants are unchanged. Pyramid-arrow geometry was extracted into a small file-private EmitPyramidArrow helper used by both the existing tangent-arrow path and the new pose-arrow path. The user-visible behavior of kEndpoints, kRegular, and kAll is identical to before. Streaming overloads pad in lockstep so AddPoint variants can be mixed: each variant back-fills its own auxiliary array (scalar_values_ or orientations_) up to control_points_.size() - 1 before pushing the new sample. ClearPath also clears orientations_ now. Visual test fixture (test_path.cpp) gains a 9th demo: a 6-pose arc where the yaw sweeps from 0 to 90° while the path lays along -Y. The oriented arrows fan out, visibly distinct from the kRegular tangent arrows in the other demos. Build clean. 122 tests pass; same 2 pre-existing PCL failures, no new regressions. Library boundary holds. Maps onto ROS2 message types the upcoming bridge will produce: - geometry_msgs::PoseArray → SetPoints + SetOrientations - nav_msgs::Path → AddPoint(point, quat) per PoseStamped Co-Authored-By: Claude Opus 4.7 (1M context) --- TODO.md | 11 +++ src/scene/include/scene/renderable/path.hpp | 34 ++++++- src/scene/src/renderable/path.cpp | 98 +++++++++++++++++++++ src/scene/test/renderable/test_path.cpp | 26 ++++++ 4 files changed, 168 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 5653eac..49a0fa3 100644 --- a/TODO.md +++ b/TODO.md @@ -186,6 +186,17 @@ visualization concern?" before merging. ### April 2026 +- ✅ **Path: per-pose orientation + `kPoseArrows`** — closes the gap + between "path of positions" and "trajectory of poses". Optional + `vector orientations_` alongside `control_points_`, + streaming-friendly `AddPoint(point, quat)` and + `AddPoint(point, quat, scalar)` overloads, `SetOrientations` / + `ClearOrientations` / `HasOrientations`. New `ArrowMode::kPoseArrows` + draws arrows oriented by the per-pose quaternion (X-forward + convention, ROS-style) rather than the path tangent — for use cases + where heading differs from travel direction (parallel parking, + drone yaw, manipulator end-effector). Maps cleanly onto the upcoming + ROS2 `geometry_msgs::PoseArray` and `nav_msgs::Path` converters. - ✅ **Path: trajectory streaming extensions** — `AddPoint(point, scalar)` overload pushes positions and scalar samples in lockstep for live trajectory feeds. `EnableAutoColorRange()` auto-fits the velocity / diff --git a/src/scene/include/scene/renderable/path.hpp b/src/scene/include/scene/renderable/path.hpp index f5827fb..1581a95 100644 --- a/src/scene/include/scene/renderable/path.hpp +++ b/src/scene/include/scene/renderable/path.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "scene/interface/opengl_object.hpp" #include "../shader_program.hpp" @@ -59,7 +60,11 @@ class Path : public OpenGlObject { kEndpoints, // Arrows at start and end only kRegular, // Arrows at regular intervals kCurvature, // Arrows at high curvature points - kAll // Arrow at every path point + kAll, // Arrow at every path point (path tangent) + kPoseArrows // Arrow at every control point oriented by + // orientations_[i]; falls back to tangent when + // orientations_ is empty or shorter than + // control_points_. }; Path(); @@ -73,10 +78,32 @@ class Path : public OpenGlObject { /// (used by kVelocity / kTime / kCost color modes). Keeps scalar_values_ /// aligned with control_points_ for live-trajectory use cases. void AddPoint(const glm::vec3& point, float scalar); + /// Streaming overload: append a pose. Keeps orientations_ aligned + /// with control_points_ for live oriented-trajectory feeds (ROS2 + /// `geometry_msgs::PoseStamped` / `nav_msgs::Path`). + void AddPoint(const glm::vec3& point, const glm::quat& orientation); + /// Append a full pose with a scalar sample. + void AddPoint(const glm::vec3& point, const glm::quat& orientation, + float scalar); void InsertPoint(size_t index, const glm::vec3& point); void RemovePoint(size_t index); void ClearPath(); + // === Per-point orientation (optional) === + + /// Set per-control-point orientations. `orientations.size()` should + /// match the current `control_points_` size; shorter or empty input + /// is allowed (missing orientations fall back to identity when + /// drawing pose arrows). + void SetOrientations(const std::vector& orientations); + /// True if any orientations are stored. + bool HasOrientations() const { return !orientations_.empty(); } + /// Remove all stored orientations. Position data is unchanged. + void ClearOrientations(); + const std::vector& GetOrientations() const { + return orientations_; + } + const std::vector& GetPoints() const { return control_points_; } size_t GetPointCount() const { return control_points_.size(); } @@ -156,6 +183,11 @@ class Path : public OpenGlObject { // Control points std::vector control_points_; + + // Optional per-control-point orientations. Empty means "no + // orientation data"; size mismatched with control_points_ is + // tolerated (missing entries treated as identity at draw time). + std::vector orientations_; // Path properties PathType path_type_; diff --git a/src/scene/src/renderable/path.cpp b/src/scene/src/renderable/path.cpp index 8db4dca..9cedc6f 100644 --- a/src/scene/src/renderable/path.cpp +++ b/src/scene/src/renderable/path.cpp @@ -154,6 +154,46 @@ void Path::AddPoint(const glm::vec3& point, float scalar) { needs_arrow_update_ = true; } +void Path::AddPoint(const glm::vec3& point, const glm::quat& orientation) { + control_points_.push_back(point); + // Pad orientations_ similarly to AddPoint(point, scalar): if the user + // started with no-orientation AddPoints, back-fill with the new + // orientation so alignment is preserved. + if (orientations_.size() < control_points_.size() - 1) { + orientations_.resize(control_points_.size() - 1, orientation); + } + orientations_.push_back(orientation); + needs_geometry_update_ = true; + needs_color_update_ = true; + needs_arrow_update_ = true; +} + +void Path::AddPoint(const glm::vec3& point, const glm::quat& orientation, + float scalar) { + control_points_.push_back(point); + if (scalar_values_.size() < control_points_.size() - 1) { + scalar_values_.resize(control_points_.size() - 1, scalar); + } + scalar_values_.push_back(scalar); + if (orientations_.size() < control_points_.size() - 1) { + orientations_.resize(control_points_.size() - 1, orientation); + } + orientations_.push_back(orientation); + needs_geometry_update_ = true; + needs_color_update_ = true; + needs_arrow_update_ = true; +} + +void Path::SetOrientations(const std::vector& orientations) { + orientations_ = orientations; + needs_arrow_update_ = true; +} + +void Path::ClearOrientations() { + orientations_.clear(); + needs_arrow_update_ = true; +} + void Path::InsertPoint(size_t index, const glm::vec3& point) { if (index <= control_points_.size()) { control_points_.insert(control_points_.begin() + index, point); @@ -175,6 +215,7 @@ void Path::RemovePoint(size_t index) { void Path::ClearPath() { control_points_.clear(); scalar_values_.clear(); + orientations_.clear(); path_vertices_.clear(); path_colors_.clear(); path_indices_.clear(); @@ -424,6 +465,44 @@ void Path::GenerateSplineCurve() { } } +namespace { + +// Append a pyramid arrow at `position` oriented by the given basis +// vectors (forward, right, up — assumed orthonormal). `arrow_size` +// controls the overall scale. Pushes 5 vertices and 18 indices into +// the supplied buffers, plus 5 color entries. +void EmitPyramidArrow(std::vector& vertices, + std::vector& colors, + std::vector& indices, + const glm::vec3& position, const glm::vec3& forward, + const glm::vec3& right, const glm::vec3& up, + const glm::vec3& color, float arrow_size) { + const glm::vec3 fwd = forward * arrow_size; + const glm::vec3 r = right * (arrow_size * 0.4f); + const glm::vec3 u = up * (arrow_size * 0.4f); + + const uint32_t base_idx = static_cast(vertices.size()); + vertices.push_back(position + fwd); // 0: tip + vertices.push_back(position + r + u); // 1: top-right + vertices.push_back(position - r + u); // 2: top-left + vertices.push_back(position - r - u); // 3: bottom-left + vertices.push_back(position + r - u); // 4: bottom-right + for (int i = 0; i < 5; ++i) colors.push_back(color); + + const uint32_t tip = base_idx; + const uint32_t tr = base_idx + 1; + const uint32_t tl = base_idx + 2; + const uint32_t bl = base_idx + 3; + const uint32_t br = base_idx + 4; + // Side faces (tip → base edges). + indices.insert(indices.end(), + {tip, tr, br, tip, tl, tr, tip, bl, tl, tip, br, bl}); + // Base square (two triangles). + indices.insert(indices.end(), {tr, tl, bl, tr, bl, br}); +} + +} // namespace + void Path::GenerateArrows() { arrow_vertices_.clear(); arrow_colors_.clear(); @@ -433,6 +512,25 @@ void Path::GenerateArrows() { return; } + // Pose-oriented arrows iterate control_points_ (not path_vertices_) + // and use stored orientations rather than path tangents. Missing + // orientations fall back to identity, which renders as a +X-aligned + // arrow — a sensible default for ROS-style "X is forward". + if (arrow_mode_ == ArrowMode::kPoseArrows) { + for (size_t i = 0; i < control_points_.size(); ++i) { + const glm::quat q = (i < orientations_.size()) + ? orientations_[i] + : glm::quat(1.0f, 0.0f, 0.0f, 0.0f); + const glm::mat3 R = glm::mat3_cast(q); + // Columns of R are the rotated unit axes (X-forward, Y-right, + // Z-up convention). + EmitPyramidArrow(arrow_vertices_, arrow_colors_, arrow_indices_, + control_points_[i], R[0], R[1], R[2], arrow_color_, + arrow_size_); + } + return; + } + std::vector arrow_positions; switch (arrow_mode_) { diff --git a/src/scene/test/renderable/test_path.cpp b/src/scene/test/renderable/test_path.cpp index bda8eb0..ee29a0b 100644 --- a/src/scene/test/renderable/test_path.cpp +++ b/src/scene/test/renderable/test_path.cpp @@ -14,6 +14,7 @@ #include #include +#include #include "scene/scene_app.hpp" #include "scene/renderable/path.hpp" @@ -183,6 +184,31 @@ void SetupPathScene(SceneManager* scene_manager) { path8->SetTransparency(0.4f); // Semi-transparent path8->SetGlowEffect(true, 0.8f); scene_manager->AddOpenGLObject("path_transparent", std::move(path8)); + + // 9. Oriented trajectory (pose-aware): same shape as a path but + // each waypoint carries a full quaternion. Arrows are oriented + // by the per-point pose, not the path tangent — useful when the + // object's heading differs from its travel direction (parallel + // parking, drone yaw, manipulator end-effector). + auto path9 = std::make_unique(); + path9->SetPathType(Path::PathType::kLineSegments); + path9->SetLineWidth(2.0f); + path9->SetColor(glm::vec3(0.7f, 0.85f, 1.0f)); + path9->SetArrowMode(Path::ArrowMode::kPoseArrows); + path9->SetArrowSize(0.3f); + path9->SetArrowColor(glm::vec3(0.9f, 0.5f, 0.1f)); + + // 6 poses along an arc; orientations sweep the heading from +X + // (yaw=0) to roughly +Y (yaw=90°) so the arrows fan out. + const int n = 6; + for (int i = 0; i < n; ++i) { + const float t = static_cast(i) / static_cast(n - 1); + const glm::vec3 pos(-3.0f + 6.0f * t, -4.0f, 2.0f); + const float yaw = glm::radians(t * 90.0f); + const glm::quat q = glm::angleAxis(yaw, glm::vec3(0, 0, 1)); + path9->AddPoint(pos, q); + } + scene_manager->AddOpenGLObject("path_oriented_trajectory", std::move(path9)); } int main(int argc, char* argv[]) { From 4f60a053181fd3f03f1563c738c06da3190afaef Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 26 Apr 2026 23:50:52 +0800 Subject: [PATCH 21/22] docs(TODO): tighten to a tracker; nothing else MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Earlier TODO.md had accreted into tracker + mission statement + style guide + verbose changelog. Strip to just the tracker so a fresh session can scan it in under a minute. Removed: - Mission section (3 paragraphs of prose) — duplicates CLAUDE.md - Status Summary — duplicates active priorities - Notes section about how to use the file — duplicates CLAUDE.md - Pre-2026 "Recently Completed" entries (Sept 2025, Dec 2024, etc.) — older history lives in `git log` - Verbose feature descriptions in completed bullets — replaced with one-line summaries + commit hashes Kept: - Active priorities (3 ordered milestones) - Backlog (deferred items, grouped) - Library hooks (driven by sample/editor) - Visualization gaps - Cleanups - Recent: current-iteration commits with hashes for `git show` Each section is single-purpose. Each item is searchable on a relevant keyword (class name, file path, commit hash). 307 → 122 lines. The "Recent" section uses commit hashes rather than prose so a fresh session can `git show ` to retrieve the full context without having to scroll the tracker. Co-Authored-By: Claude Opus 4.7 (1M context) --- TODO.md | 366 ++++++++++++++------------------------------------------ 1 file changed, 91 insertions(+), 275 deletions(-) diff --git a/TODO.md b/TODO.md index 49a0fa3..099ebed 100644 --- a/TODO.md +++ b/TODO.md @@ -1,306 +1,122 @@ -# QuickViz Implementation Tracker +# QuickViz TODO -*Last Updated: April 26, 2026* -*Purpose: Track active work and concrete priorities. Terse and factual; -no marketing language. One bullet per outcome.* +*Last updated: 2026-04-26* -## Mission - -Visualization-first C++ library for robotics. The library renders, -displays, and interacts; apps built on top handle editor-style concerns -(undo/redo, project files, etc.). `sample/editor/` is the dogfood check -on library completeness. - -The next chapter — turn this into a *robotics* visualization library -(not a general C++ one) by closing the gap to standard robotics -workflows: ROS2 streams, common primitives, live data, diagnostics. +Tracker only. Mission, conventions, and module structure live in +[`CLAUDE.md`](CLAUDE.md). Older history: `git log`. --- -## 🎯 Active priorities - -Ordered by what unblocks the most downstream work. - -### Next up — close the streaming-data loop -- [x] **`sample/streaming_demo/`** — rotating spiral pushed from a - background thread at ~30 Hz, rendered via `DataStream` and - `SceneManager::SetPreDrawCallback`. Annotated README explains - the threading model and DataStream-vs-RingBuffer choice. The - reference users will copy when wiring sensor streams. - -### Milestone — ROS2 integration (`bridges/ros2/`) -The single biggest user-facing gap. Without this QuickViz is a general -C++ vis library; with it, it's the robotics vis library. - -**Hard rule for this and any ROS-dependent module**: ROS2 is an -**optional** CMake dep. The library must compile, link, and run -cleanly on systems where ROS2 is not installed — just without ROS -support. Use `find_package(... QUIET)` + early `return()` from the -module's CMakeLists when absent. No ROS headers / types may leak into -non-gated code paths. (See CLAUDE.md §4 "Optional external dependencies".) -- [ ] Stand up `bridges/` umbrella with `bridges/ros2/` as the first - child (deferred from the earlier reorg — now justified by a real - occupant). CMake-gated; absent ROS = silent skip. -- [ ] `sensor_msgs::PointCloud2` ↔ `PointCloud` renderable converter -- [ ] `geometry_msgs::PoseStamped` / `PoseArray` ↔ pose / trajectory -- [ ] `nav_msgs::OccupancyGrid` ↔ `OccupancyGrid` renderable (the - renderable already exists as of April 2026) -- [ ] `tf2_msgs::TFMessage` ↔ `TfFrameTree` renderable (the renderable - already exists as of April 2026) -- [ ] `visualization_msgs::Marker` / `MarkerArray` ↔ generic - primitives passthrough -- [ ] Sample app demonstrating ROS2 streaming end-to-end. **Also - CMake-gated** — must not be built when ROS2 is absent. - -### Milestone — first standard robotics renderable -Pick one and ship it cleanly before the next. Don't backlog all three. -- [x] **`OccupancyGrid`** renderable — 2D map projected into the 3D - scene. ROS-style int8 input (`-1` unknown, `0..100` occupancy - percent). R8 texture + custom colormap shader. Visual test - fixture in `scene/test/renderable/test_occupancy_grid.cpp`. -- [x] `TfFrameTree` renderable — named frames with parent/child - transforms; renders RGB axes per frame plus optional gray - connection lines. Walks parent chains at draw time; cycles + - broken parent references handled defensively. -- [x] `Trajectory` extensions on the existing `Path` renderable — - streaming-friendly `AddPoint(point, scalar)` overload, - `EnableAutoColorRange()` for live trajectories, and a correctness - fix for per-vertex scalar→color mapping under subdivided paths. - (Pre-existing color modes `kVelocity` / `kTime` / `kCost` and - `SetAnimationProgress` already covered the rest.) - -### After ROS2 lands — diagnostics -- [ ] **HUD overlay**: frame time, draw call count, GPU memory, active - tool, scene object count. Toggled by hotkey. ~1 day. -- [ ] **Structured logger** to replace the scattered `std::cerr` / - `std::cout` calls in library code. Audit count (April 2026): - ~232 occurrences in `src/`, concentrated in `scene` (179) and - `viewer` (43). Lightweight implementation: ~100 LOC + a - level/category enum, then a sed migration. -- [ ] Visible UI surface for shader/asset load failures (today these - print to console and the user sees a black scene). - -### After diagnostics — documentation site -- [ ] Doxygen API reference, generated and published via GitHub Pages - from the same repo. ~1 day to set up. -- [ ] `docs/tutorial/01-quickstart.md` — walk through `sample/quickstart/` -- [ ] `docs/tutorial/02-editor.md` — walk through `sample/editor/` -- [ ] `docs/tutorial/03-streaming.md` — walk through `sample/streaming_demo/` -- [ ] `docs/tutorial/04-custom-renderable.md` — extension story -- [ ] `docs/tutorial/05-ros2.md` — once the bridge lands +## 🎯 Active priorities (in order) + +### 1. ROS2 bridge — `bridges/ros2/` +ROS2 is **optional** in CMake; the library must build and run without +it (CLAUDE.md §4 "Optional external dependencies"). + +- [ ] `bridges/` umbrella + `bridges/ros2/` first child, CMake-gated +- [ ] `sensor_msgs::PointCloud2` ↔ `PointCloud` +- [ ] `geometry_msgs::PoseStamped` / `PoseArray` ↔ `Path` (with orientations) +- [ ] `nav_msgs::OccupancyGrid` ↔ `OccupancyGrid` +- [ ] `tf2_msgs::TFMessage` ↔ `TfFrameTree` +- [ ] `visualization_msgs::Marker` / `MarkerArray` ↔ generic primitives +- [ ] Sample app demonstrating ROS2 streaming end-to-end (also gated) + +### 2. Diagnostics +- [ ] HUD overlay: frame time, draw calls, GPU mem, scene object count, active tool +- [ ] Structured logger (replaces ~232 `std::cerr/cout` in `src/`, mostly in `scene` and `viewer`) +- [ ] Visible UI surface for shader/asset load failures + +### 3. Documentation site +- [ ] Doxygen + GitHub Pages +- [ ] `docs/tutorial/01-quickstart.md` +- [ ] `docs/tutorial/02-editor.md` +- [ ] `docs/tutorial/03-streaming.md` +- [ ] `docs/tutorial/04-custom-renderable.md` +- [ ] `docs/tutorial/05-ros2.md` (after bridge lands) --- -## 📋 Backlog (deliberately deferred) +## 📋 Backlog -These are good ideas with concrete value but aren't on the critical -path right now. Promote one when there's a user pulling for it. +### Onramp +- [ ] Layout presets — `viewer::layout::SidebarLeft(width)` etc. +- [ ] `viewer::AppState` — window pos, camera, panel sizes persisted -### Onramp / ergonomics -- [ ] **Layout presets** — `viewer::layout::SidebarLeft(width)`, - `BottomDock(height)`, `Split(ratio)` returning configured `Box` - trees. Removes the FlexGrow/FlexShrink magic-number incantation - from samples. ~3 hours. -- [ ] **`viewer::AppState`** — saves window position, last camera - viewpoint per scene, last-opened files, panel sizes beyond what - `imgui.ini` covers. Loads on startup, saves on shutdown. - ~1 day + a TOML/JSON dep. - -### Tools / data lifecycle -- [ ] **Recording + replay** for `DataStream`s — `core/Record` and - `core/Replay` capture / play back stream traffic to disk. - Critical for deterministic testing and offline analysis. ~2-3 - days. Picks a binary format (raw, msgpack, capnproto). +### Tools / data +- [ ] Recording + replay for `DataStream` — `core/Record` / `core/Replay` ### Performance -- [ ] **LOD system** for >1M point scenes (existing TODO, larger). - Likely octree-based with per-tile streaming. +- [ ] LOD system for >1M point scenes (octree-based) -### Scaling / extension -- [ ] **Plugin / extension system** — runtime loading of renderables - and tools via shared library + small C ABI registration. Heavy - lift (~2-3 weeks); defer until a concrete user appears. +### Extension +- [ ] Plugin system (defer until concrete user) ### Robotics gold demo -- [ ] **Compose** the items above into a single sample that looks - impressive: robot driving through an occupancy grid with a - streaming point cloud, trajectory, sensor frustum, and small - dashboard. Doubles as a marketing screenshot and a - feature-completeness check. - ---- - -## 🔧 Library hooks (driven by `sample/editor`) - -Additive only. Each one was logged when the editor sample wanted it -but worked around the absence. Re-evaluate against "is this a -visualization concern?" before merging. - -- [ ] `SceneManager::GetObjectId(name)` / `GetObjectName(id)` — drop - the stringly-typed name lookup the editor currently uses. -- [ ] `PointSelection::object_id` — selection callbacks no longer - need string-equality on cloud_name. -- [ ] `PointCloud::SetActiveMask(span)` or - `SetActiveIndices(...)` — editing a point cloud no longer - requires rebuilding the full vertex buffer per command. -- [ ] Stable point identity so selections survive cloud mutations - (today the editor must `ClearSelection()` after every rebuild - because the tool tracks visible indices). +- [ ] Composite sample: robot driving through occupancy grid with + streaming cloud + trajectory + frustum + small dashboard --- -## 🐛 Known visualization gaps - -- [ ] Selection support for `Arrow`, `Plane`, `Path`, `Triangle`, - `Pose` primitives. -- [ ] `PCLLoaderTest.InvalidFileError` is failing — modern PCL no - longer throws on corrupt PCDs; rewrite the test against current - behavior. -- [ ] Move bundled fonts from `core/include/` to a top-level - `resources/` directory (they're not a public-API concern). - ---- +## 🔧 Library hooks (from sample/editor) -## 🧹 Smaller cleanups +Re-evaluate each against "is this a visualization concern?" before merging. -- [ ] Audit `GeometricPrimitive` base class (405-LOC header) — it - bundles material system, render modes, and selection state for - `Sphere`/`Cylinder`/`BoundingBox`. The header is overly large - and possibly does too much. A real design review, not a quick - rewrite — likely API-breaking. -- [ ] `src/scene/src/renderable/canvas.cpp` is 2069 LOC; split into - cohesive sub-files (~500 LOC target per CLAUDE.md). -- [ ] Clean `sample/pointcloud_viewer/interactive_scene_manager.cpp` - — confirmed leftovers from the deleted-editor migration: - `HandleMouseInput()` is `return;` only (line 141-144), a - "Legacy SelectionManager callback disabled" block (line 113-124), - and a stale TODO at line 107. Either finish or remove each. -- [ ] Audit `sample/quickviz_demo_app/` after layout presets land. - It uses `Viewer` directly (a multi-panel app — likely correct). - The audit may conclude "no change"; the question is whether the - layout setup could be tightened with the upcoming presets. +- [ ] `SceneManager::GetObjectId(name)` / `GetObjectName(id)` +- [ ] `PointSelection::object_id` (drop string-equality on `cloud_name`) +- [ ] `PointCloud::SetActiveMask(span)` or `SetActiveIndices(...)` +- [ ] Stable point identity across cloud rebuilds (selections survive) --- -## ✅ Recently Completed +## 🐛 Visualization gaps -### April 2026 - -- ✅ **Path: per-pose orientation + `kPoseArrows`** — closes the gap - between "path of positions" and "trajectory of poses". Optional - `vector orientations_` alongside `control_points_`, - streaming-friendly `AddPoint(point, quat)` and - `AddPoint(point, quat, scalar)` overloads, `SetOrientations` / - `ClearOrientations` / `HasOrientations`. New `ArrowMode::kPoseArrows` - draws arrows oriented by the per-pose quaternion (X-forward - convention, ROS-style) rather than the path tangent — for use cases - where heading differs from travel direction (parallel parking, - drone yaw, manipulator end-effector). Maps cleanly onto the upcoming - ROS2 `geometry_msgs::PoseArray` and `nav_msgs::Path` converters. -- ✅ **Path: trajectory streaming extensions** — `AddPoint(point, scalar)` - overload pushes positions and scalar samples in lockstep for live - trajectory feeds. `EnableAutoColorRange()` auto-fits the velocity / - time / cost color range to the current `scalar_values_` so the - mapping adapts as samples arrive. Fixed a long-standing bug where - scalar-encoded colors on subdivided paths (smooth curve / Bezier / - spline) misaligned because the per-vertex mapping indexed - `scalar_values_` by raw vertex index; now uses fractional - control-point parameter and lerps. Closes the third item under the - "first standard robotics renderable" milestone. -- ✅ **Triangle moved to `scene/test/test_utils/`** — accidentally-public - test scaffold removed from the public renderable API. New - `scene_test_utils` library hosts test-only helpers; the 8 tests that - used `Triangle` link against it instead of the main `scene` target. - Headed off a related axis-vertex-generator refactor after closer - inspection: the three axis-rendering classes (`CoordinateFrame`, - `Pose`, `TfFrameTree`) draw genuinely different shapes (cone-arrowed - vs. plain lines) in different coordinate frames; a shared helper - would force a worse abstraction. -- ✅ **`TfFrameTree` renderable** — tree of named coordinate frames with - parent/child transforms, ROS tf2-style. Each frame renders as RGB - axes; gray lines connect parents to children when enabled. World - transforms computed by walking parent chains; cycles and broken - parent references handled defensively. Visual test fixture animates - a 6-frame robot kinematics tree. -- ✅ **`OccupancyGrid` renderable** — 2D probabilistic grid as a - textured quad in the XY plane. ROS-style API - (`SetGrid(width, height, resolution, origin, vector)`, - values: `-1` unknown / `0..100` occupancy). R8 texture with sentinel - encoding (`byte 0 = unknown`, `byte 1..255 = free→occupied`); custom - shader colormaps to configurable free/occupied/unknown colors. - Manual visual test fixture in `scene/test/renderable/`. -- ✅ **`sample/streaming_demo/`** — canonical sensor-streaming pattern. - Background producer rotates a 2000-point spiral around Z and pushes - to `DataStream` at 30 Hz; render thread pulls in a - `SetPreDrawCallback` and updates the renderable. ~50 LOC + a 60-line - README on threading model and DataStream-vs-RingBuffer guidance. -- ✅ **CLAUDE.md rewrite** — tighter project contract, 470 → 308 lines. - Final module map, library boundary rule, code style, threading model, - decision heuristics. (commit `e637b8d`) -- ✅ **`quickviz::DataStream`** — latest-only producer/consumer - channel for streaming sensor data, header-only over `DoubleBuffer`. - 7 unit tests including a threaded smoke test. (commit `22ef647`) -- ✅ **`sample/quickstart/`** — 18-line app demonstrating `SceneApp` + - synthetic data; the "first 5 minutes" proof point. (commit `c6b9a2a`) -- ✅ **`quickviz::demo::*` synthetic data generators** — SpiralCloud, - PlanarPointGrid, NoiseCloud, CubeMesh, Trajectory. 8 unit tests. - (commit `135070a`) -- ✅ **`GlViewer` → `SceneApp` rename + reframe** as the 5-line - quickstart facade. 17 renderable tests updated. (commit `557a27a`) -- ✅ **Module reorg by intent** — final layout `core, viewer, scene, - plot, canvas, image, pcl_bridge`. One job per module, named after - what users want to do (not which backend). Renames `imview→viewer`, - `gldraw→scene`. Dissolved `widget` into `canvas` (Cairo) + `plot` - (ImPlot widgets). Merged `cvdraw` into `image` along with cv_image - widgets from `widget`. New `plot` module hosts ImPlot3D as well. -- ✅ **`sample/editor/` MVP** — vis+editing reference app on top of the - library, built without any `src/` modifications. Acts as the dogfood - check on library completeness. -- ✅ **Reshape: visualization-first re-anchor** — Removed the - in-library state management module (`scenegraph`) and its sample - (`object_management`). Locked the `src/ ↛ sample/` boundary in CI - and CLAUDE.md. - -### September 2025 -- ✅ CameraController refactor (Strategy pattern, configurable - parameters, utility methods) -- ✅ Input debug message cleanup -- ✅ GLDraw architecture review - -### December 2024 -- ✅ ThreadSafeQueue, BufferRegistry, AsyncEventDispatcher - modernization - -### September 2024 -- ✅ Configurable camera controls (Modeling/FPS/CAD/Scientific styles) -- ✅ Unified input system with gamepad support -- ✅ Selection support for LineStrip, Mesh, Cylinder, BoundingBox - -### Core Infrastructure -- ✅ CMake build system with module-private include layout -- ✅ GoogleTest integration -- ✅ Multi-layer point cloud system (60-100x batching speedup) -- ✅ GPU ID-buffer selection (16.5M point capacity) -- ✅ GeometricPrimitive template pattern +- [ ] Selection support for `Arrow`, `Plane`, `Path`, `Pose` primitives +- [ ] `PCLLoaderTest.InvalidFileError` — modern PCL no longer throws + on corrupt PCDs; rewrite test against current behavior +- [ ] Move bundled fonts from `core/include/` to top-level `resources/` --- -## 📊 Status Summary +## 🧹 Cleanups -**Branch**: `main` (post-PR-#28). -**Architecture**: Library = `core, viewer, scene, plot, canvas, image, -pcl_bridge`. Apps live in `sample/`. Editor / ROS-style frameworks live -above the library, never inside. -**Current focus**: Close the streaming loop with `sample/streaming_demo`, -then the ROS2 bridge milestone. +- [ ] `GeometricPrimitive` 405-LOC header — design review (likely + API-breaking) +- [ ] `scene/src/renderable/canvas.cpp` 2069 LOC — split (~500 target) +- [ ] `sample/pointcloud_viewer/interactive_scene_manager.cpp` — + empty `HandleMouseInput()` line 141, disabled callback line 113, + stale TODO line 107 +- [ ] `sample/quickviz_demo_app/` — audit for boilerplate after layout + presets land --- -## 📝 Notes - -- See `docs/notes/` for design deep-dives (rendering, picking, input). -- See `CLAUDE.md` for project guidelines and module boundaries. -- Update this file in the same change as the work itself; keep entries - terse, factual, one bullet per outcome. No marketing language. -- An item moves from Active → Recently Completed only when the work is - actually merged and tests pass — not "started" or "in flight." +## ✅ Recent (current iteration) + +Use `git show ` for full context. + +- `a477e4f` Path: per-pose orientation + `ArrowMode::kPoseArrows` +- `a12d66d` Path: trajectory streaming extensions + (`AddPoint(p, scalar)`, `EnableAutoColorRange`, + scalar-color subdivision fix) +- `27a2583` Triangle moved to `scene/test/test_utils/`; + new `scene_test_utils` library +- `1114780` ROS-optional CMake rule codified (CLAUDE.md §4) +- `54852f0` `TfFrameTree` renderable +- `f9c93a8` `OccupancyGrid` renderable +- `1b3022b` `sample/streaming_demo/` +- `f672d85` TODO audit +- `a8d126b` TODO roadmap rewrite +- `22ef647` `quickviz::DataStream` +- `c6b9a2a` `sample/quickstart/` +- `135070a` `quickviz::demo::*` synthetic generators +- `e637b8d` CLAUDE.md rewrite (470 → 308 lines) +- `557a27a` `GlViewer` → `SceneApp` rename + reframe +- `64fad97` Module-reorg docs refresh +- `fcb47c5` New `image/` module (cvdraw + widget cv parts) +- `0479241` New `canvas/` module (Cairo bits of widget) +- `d44d5d3` New `plot/` module (ImPlot + ImPlot3D) +- `96d125f` `gldraw` → `scene` rename +- `06922c3` `imview` → `viewer` rename +- `2109140` Mission docs realigned, stale design docs deleted +- `079eb2b` `src/ ↛ sample/` boundary locked in CI + CLAUDE.md +- `af77ad4` Removed in-library state-mgmt module + bridge sample From 63b42c1d996f503685caeeaf664d11166be3c65e Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Sun, 3 May 2026 12:05:02 +0800 Subject: [PATCH 22/22] cmake: leave pcl_bridge out if no pcl lib is found --- src/pcl_bridge/CMakeLists.txt | 62 +++++++++++++++++------------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/pcl_bridge/CMakeLists.txt b/src/pcl_bridge/CMakeLists.txt index 782b685..205096a 100644 --- a/src/pcl_bridge/CMakeLists.txt +++ b/src/pcl_bridge/CMakeLists.txt @@ -1,11 +1,11 @@ -# Create visualization library -add_library(pcl_bridge - # PCL bridge utilities (optional, depends on PCL) -) - # Check for PCL and conditionally compile PCL bridge find_package(PCL QUIET COMPONENTS common io) if(PCL_FOUND) + # Create visualization library + add_library(pcl_bridge + # PCL bridge utilities (optional, depends on PCL) + ) + message(STATUS "Found PCL: ${PCL_VERSION} - enabling PCL bridge utilities") target_sources(pcl_bridge PRIVATE src/pcl_conversions.cpp @@ -15,34 +15,34 @@ if(PCL_FOUND) target_include_directories(pcl_bridge PRIVATE ${PCL_INCLUDE_DIRS}) target_link_libraries(pcl_bridge PRIVATE ${PCL_LIBRARIES}) target_compile_definitions(pcl_bridge PUBLIC QUICKVIZ_WITH_PCL PRIVATE ${PCL_DEFINITIONS}) -else() - message(STATUS "PCL not found - PCL bridge utilities will not be available") -endif() -# Link with scene for rendering capabilities -target_link_libraries(pcl_bridge PUBLIC scene) + # Link with scene for rendering capabilities + target_link_libraries(pcl_bridge PUBLIC scene) -# Include directories -target_include_directories(pcl_bridge PUBLIC - $ - $ - PRIVATE src -) + # Include directories + target_include_directories(pcl_bridge PUBLIC + $ + $ + PRIVATE src + ) -# Tests for pcl_bridge module -if (BUILD_TESTING) - add_subdirectory(test) -endif () + # Tests for pcl_bridge module + if (BUILD_TESTING) + add_subdirectory(test) + endif () -# Installation -install(TARGETS pcl_bridge - EXPORT quickvizTargets - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - RUNTIME DESTINATION bin - INCLUDES DESTINATION include -) + # Installation + install(TARGETS pcl_bridge + EXPORT quickvizTargets + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + RUNTIME DESTINATION bin + INCLUDES DESTINATION include + ) -install(DIRECTORY include/ - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) \ No newline at end of file + install(DIRECTORY include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + ) +else() + message(STATUS "PCL not found - PCL bridge utilities will not be available") +endif() \ No newline at end of file