diff --git a/deps/internal/vneinteraction b/deps/internal/vneinteraction index 71c538e..fe614a0 160000 --- a/deps/internal/vneinteraction +++ b/deps/internal/vneinteraction @@ -1 +1 @@ -Subproject commit 71c538e581f915f0803f442f61312f7aed5d5ba8 +Subproject commit fe614a0aff5d7e3365eda556e83d49e0fda2ba61 diff --git a/deps/internal/vnescene b/deps/internal/vnescene index bf074df..d6600d2 160000 --- a/deps/internal/vnescene +++ b/deps/internal/vnescene @@ -1 +1 @@ -Subproject commit bf074dfea3e6d7a4da9822a46b9943a524020bb2 +Subproject commit d6600d258ab8cffdcc8cbe9159e500c5d9df8d8d diff --git a/samples/glfw_opengl/00_hello_testbed/README.md b/samples/glfw_opengl/00_hello_testbed/README.md index 8cb71fc..a3bf9bf 100644 --- a/samples/glfw_opengl/00_hello_testbed/README.md +++ b/samples/glfw_opengl/00_hello_testbed/README.md @@ -8,7 +8,7 @@ The baseline demo that every subsequent GLFW/OpenGL sample is built on. Proves t 2. **Viewport resize**: Resizing the viewport keeps the image filling the panel (no stretching). 3. **Vsync**: Toggle in the application responds immediately. 4. **Grid and axes**: Spatial reference and confirmation that the camera and scene work. -5. **Orbit-arcball** (when `vne::interaction` is enabled): LMB drag rotates, RMB drag pans, scroll zooms. +5. **Orbit / trackball** (when `vne::interaction` is enabled): LMB drag rotates, RMB drag pans, scroll zooms. 6. **ImGui Settings**: Demo-specific section **Viewport** with controls reference and axis legend. ## Screenshots and Demo @@ -42,7 +42,7 @@ Note: The GIF is frame-skipped and also sped up so it can include the full recor - uncheck **Vsync**, - set **Viewport Layout** to **2**, **3**, or **4** viewports for a multi-pane layout (the combo also offers **1** for a single viewport). 3. Use mouse: - - **LMB** drag: rotate (orbit-arcball), + - **LMB** drag: rotate (orbit / trackball inspect), - **RMB** drag: pan, - **Scroll wheel**: zoom. @@ -108,7 +108,7 @@ Exit with **ESC** or by closing the window. ## Key Concepts - **BaseSceneLayer**: Renders grid, axes, and a perspective camera; shared with other samples via `samples/glfw_opengl/common/base_scene_layer.h`. -- **BaseInteractionLayer**: Orbit-arcball manipulator (optional); LMB orbit, RMB pan, scroll zoom. +- **BaseInteractionLayer**: Orbit / trackball inspect manipulator (optional); LMB rotate, RMB pan, scroll zoom. - **ImGui viewport**: The scene is drawn into an ImGui window; **Viewport Layout** can be set to 1–4 viewports when ImGui docking is enabled. - **Libraries**: vne::testbed, vne::scene, vne::interaction (optional). diff --git a/samples/glfw_opengl/00_hello_testbed/demo_hello_testbed.cpp b/samples/glfw_opengl/00_hello_testbed/demo_hello_testbed.cpp index c39ea03..42b2954 100644 --- a/samples/glfw_opengl/00_hello_testbed/demo_hello_testbed.cpp +++ b/samples/glfw_opengl/00_hello_testbed/demo_hello_testbed.cpp @@ -85,7 +85,7 @@ void registerHelloTestbedDemo(vne::testbed::Application& app) { app.getLayerStack().pushLayer(std::unique_ptr(scene), app.getAppContext()); #ifdef VNE_TESTBED_INTERACTION - // Layer 2: orbit-arcball interaction (per-viewport cameras when using multiple viewports) + // Layer 2: orbit/trackball interaction (per-viewport cameras when using multiple viewports) auto* interaction = new BaseInteractionLayer("HelloInteractionLayer"); interaction->setSceneLayer(scene); app.getLayerStack().pushLayer(std::unique_ptr(interaction), app.getAppContext()); diff --git a/samples/glfw_opengl/01_test_events/demo_test_events.cpp b/samples/glfw_opengl/01_test_events/demo_test_events.cpp index 2aebd89..4e0b2a7 100644 --- a/samples/glfw_opengl/01_test_events/demo_test_events.cpp +++ b/samples/glfw_opengl/01_test_events/demo_test_events.cpp @@ -381,7 +381,7 @@ void registerTestEventsDemo(vne::testbed::Application& app) { app.getLayerStack().pushLayer(std::unique_ptr(scene), app.getAppContext()); #ifdef VNE_TESTBED_INTERACTION - // Layer 2: orbit-arcball interaction (per-viewport cameras when using multiple viewports) + // Layer 2: orbit/trackball interaction (per-viewport cameras when using multiple viewports) auto* interaction = new BaseInteractionLayer("TestEventsInteractionLayer"); interaction->setSceneLayer(scene); app.getLayerStack().pushLayer(std::unique_ptr(interaction), app.getAppContext()); diff --git a/samples/glfw_opengl/02_test_scene/demo_test_scene.cpp b/samples/glfw_opengl/02_test_scene/demo_test_scene.cpp index 6b8d004..a7f9eb7 100644 --- a/samples/glfw_opengl/02_test_scene/demo_test_scene.cpp +++ b/samples/glfw_opengl/02_test_scene/demo_test_scene.cpp @@ -1023,7 +1023,7 @@ void registerTestSceneDemo(vne::testbed::Application& app) { app.getLayerStack().pushLayer(std::unique_ptr(scene), app.getAppContext()); #ifdef VNE_TESTBED_INTERACTION - // Layer 2: orbit-arcball (per-viewport cameras; use active set so ortho/persp switch works) + // Layer 2: orbit/trackball interaction (per-viewport cameras; use active set so ortho/persp switch works) auto* interaction = new BaseInteractionLayer("TestSceneInteractionLayer"); interaction->setCameras(scene->getActiveCameras()); app.getLayerStack().pushLayer(std::unique_ptr(interaction), app.getAppContext()); diff --git a/samples/glfw_opengl/03_test_interaction/README.md b/samples/glfw_opengl/03_test_interaction/README.md index 048e476..4e7661f 100644 --- a/samples/glfw_opengl/03_test_interaction/README.md +++ b/samples/glfw_opengl/03_test_interaction/README.md @@ -1,13 +1,13 @@ # 03 - Test Interaction -Full **interaction** demo on top of [`BaseSceneLayer`](../common/base_scene_layer.h): every high-level controller type (Inspect orbit/arcball, 3D navigation with FPS/Fly/Game modes, orthographic pan/zoom, follow), zoom methods, view-direction presets, and optional mesh loading from testdata when `VNE_TESTBED_VNEIO` is enabled. It shares the same **build / run / IDE** workflow as [`00_hello_testbed`](../00_hello_testbed/README.md) and uses the same Blinn-Phong [`MeshLayer`](../../../include/vertexnova/testbed/utils/mesh_layer.h) path as the scene samples when meshes are available. +Full **interaction** demo on top of [`BaseSceneLayer`](../common/base_scene_layer.h): every high-level controller type (Inspect orbit or virtual trackball with Hyperbolic/Rim projection, 3D navigation with FPS/Fly/Game modes, orthographic pan/zoom, follow), zoom methods, view-direction presets, and optional mesh loading from testdata when `VNE_TESTBED_VNEIO` is enabled. It shares the same **build / run / IDE** workflow as [`00_hello_testbed`](../00_hello_testbed/README.md) and uses the same Blinn-Phong [`MeshLayer`](../../../include/vertexnova/testbed/utils/mesh_layer.h) path as the scene samples when meshes are available. Screenshots are not included yet; they may be added in a later revision. ## What This Sample Shows 1. **Base scene**: Grid, axes, perspective or orthographic camera from `BaseSceneLayer`; settings can toggle grid/axes and switch projection. -2. **Controllers**: Runtime selection among Inspect (orbit or arcball), Navigation (FPS / Fly / Game via `NavigateMode`), Ortho2D, and Follow; per-controller ImGui tuning (pivot mode, speeds, navigation multipliers, ortho/follow options). +2. **Controllers**: Runtime selection among Inspect (Euler orbit or trackball), Navigation (FPS / Fly / Game via `NavigateMode`), Ortho 2D (`Ortho2DController`), and Follow; per-controller ImGui tuning (trackball projection when in trackball inspect mode, pivot mode, speeds, navigation multipliers, ortho/follow options). 3. **Zoom**: Dolly-to-COI, scene-scale, or FOV change where applicable; matrix readouts for view and projection when enabled. 4. **Mesh browser** (with vneio): Lists mesh files under the configured directory (PLY, OBJ, STL, FBX, glTF), loads on click, drag-and-drop to the viewport, plus lighting and mesh transform panels when a mesh is loaded. 5. **Layer stack**: `BaseSceneLayer` → `InteractionTestLayer` → optional `MeshLayer` → `InteractionSettingsLayer` (ImGui settings). diff --git a/samples/glfw_opengl/03_test_interaction/demo_test_interaction.cpp b/samples/glfw_opengl/03_test_interaction/demo_test_interaction.cpp index ded0aab..ef8d751 100644 --- a/samples/glfw_opengl/03_test_interaction/demo_test_interaction.cpp +++ b/samples/glfw_opengl/03_test_interaction/demo_test_interaction.cpp @@ -23,9 +23,9 @@ #include "vertexnova/events/types.h" #include "vertexnova/events/window_event.h" -#include "vertexnova/interaction/orbit_arcball_behavior.h" +#include "vertexnova/interaction/orbital_camera_behavior.h" +#include "vertexnova/interaction/ortho_2d_behavior.h" #include "vertexnova/interaction/free_look_behavior.h" -#include "vertexnova/interaction/ortho_pan_zoom_behavior.h" #include "vertexnova/interaction/follow_behavior.h" #ifdef VNE_TESTBED_IMGUI @@ -64,13 +64,13 @@ const vne::math::Vec3f kDefaultTargetPosition{0.0f, 0.0f, 0.0f}; ControllerVariant makeController(ControllerKind kind, vne::interaction::NavigateMode nav_mode) { switch (kind) { case ControllerKind::eInspectOrbit: { - vne::interaction::InspectController c; + vne::interaction::Inspect3DController c; c.setRotationMode(vne::interaction::OrbitRotationMode::eOrbit); return c; } - case ControllerKind::eInspectArcball: { - vne::interaction::InspectController c; - c.setRotationMode(vne::interaction::OrbitRotationMode::eArcball); + case ControllerKind::eInspectTrackball: { + vne::interaction::Inspect3DController c; + c.setRotationMode(vne::interaction::OrbitRotationMode::eTrackball); return c; } case ControllerKind::eNavigation: { @@ -78,14 +78,14 @@ ControllerVariant makeController(ControllerKind kind, vne::interaction::Navigate c.setMode(nav_mode); return c; } - case ControllerKind::eOrtho: { + case ControllerKind::eOrtho2D: { return vne::interaction::Ortho2DController{}; } case ControllerKind::eFollow: { return vne::interaction::FollowController{}; } } - return vne::interaction::InspectController{}; + return vne::interaction::Inspect3DController{}; } } // namespace @@ -97,7 +97,7 @@ ControllerVariant makeController(ControllerKind kind, vne::interaction::Navigate InteractionTestLayer::InteractionTestLayer() : vne::testbed::ILayer("InteractionTestLayer") { for (size_t i = 0; i < static_cast(kMaxViewports); ++i) { - controllers_[i] = makeController(ControllerKind::eInspectArcball, navigation_mode_); + controllers_[i] = makeController(ControllerKind::eInspectTrackball, navigation_mode_); } } @@ -130,7 +130,7 @@ void InteractionTestLayer::setCamerasFromScene() { } bool InteractionTestLayer::isManipulatorCompatibleWithCamera(bool use_perspective) const { - if (current_kind_ == ControllerKind::eOrtho) { + if (current_kind_ == ControllerKind::eOrtho2D) { return !use_perspective; } return true; @@ -230,14 +230,6 @@ void InteractionTestLayer::onUpdate(float dt) { } #endif dispatchUpdate(static_cast(dt)); -#ifdef VNE_TESTBED_IMGUI - // Apply scene scale from eSceneScale zoom method to the mesh transform each frame - if (mesh_layer_) { - if (auto* insp = getInspectController(0)) { - mesh_layer_->setUniformScale(insp->orbitArcballBehavior().getZoomScale()); - } - } -#endif } void InteractionTestLayer::onEvent(const vne::events::Event& event) { @@ -313,13 +305,13 @@ void InteractionTestLayer::setZoomMethod(vne::interaction::ZoomMethod method) { for (auto& v : controllers_) { std::visit( [method](auto& c) { - if constexpr (std::is_same_v, vne::interaction::InspectController>) { - c.orbitArcballBehavior().setZoomMethod(method); + if constexpr (std::is_same_v, vne::interaction::Inspect3DController>) { + c.orbitalCameraBehavior().setZoomMethod(method); } else if constexpr (std::is_same_v, vne::interaction::Navigation3DController>) { c.freeLookBehavior().setZoomMethod(method); } else if constexpr (std::is_same_v, vne::interaction::Ortho2DController>) { - c.orthoPanZoomBehavior().setZoomMethod(method); + c.ortho2DBehavior().setZoomMethod(method); } else if constexpr (std::is_same_v, vne::interaction::FollowController>) { c.followBehavior().setZoomMethod(method); } @@ -332,8 +324,8 @@ void InteractionTestLayer::setViewDirection(vne::interaction::ViewDirection dir) for (auto& v : controllers_) { std::visit( [dir](auto& c) { - if constexpr (std::is_same_v, vne::interaction::InspectController>) { - c.orbitArcballBehavior().setViewDirection(dir); + if constexpr (std::is_same_v, vne::interaction::Inspect3DController>) { + c.orbitalCameraBehavior().setViewDirection(dir); } }, v); @@ -382,8 +374,8 @@ void InteractionTestLayer::setRotationPivotMode(vne::interaction::OrbitPivotMode for (auto& v : controllers_) { std::visit( [mode](auto& c) { - if constexpr (std::is_same_v, vne::interaction::InspectController>) { - c.orbitArcballBehavior().setPivotMode(mode); + if constexpr (std::is_same_v, vne::interaction::Inspect3DController>) { + c.orbitalCameraBehavior().setPivotMode(mode); } }, v); @@ -395,7 +387,7 @@ void InteractionTestLayer::setRotationEnabled(bool enabled) { if (insp) { insp->setRotationEnabled(enabled); } - auto* ortho = getOrthoController(0); + auto* ortho = getOrtho2DController(0); if (ortho) { ortho->setRotationEnabled(enabled); } @@ -406,7 +398,7 @@ void InteractionTestLayer::setPanEnabled(bool enabled) { if (insp) { insp->setPanEnabled(enabled); } - auto* ortho = getOrthoController(0); + auto* ortho = getOrtho2DController(0); if (ortho) { ortho->setPanEnabled(enabled); } @@ -417,7 +409,7 @@ void InteractionTestLayer::setZoomEnabled(bool enabled) { if (insp) { insp->setZoomEnabled(enabled); } - auto* ortho = getOrthoController(0); + auto* ortho = getOrtho2DController(0); if (ortho) { ortho->setZoomEnabled(enabled); } @@ -440,12 +432,12 @@ vne::math::Vec3f InteractionTestLayer::cameraTarget() const { return camera_ ? camera_->getTarget() : vne::math::Vec3f{}; } -vne::interaction::InspectController* InteractionTestLayer::getInspectController(int index) noexcept { +vne::interaction::Inspect3DController* InteractionTestLayer::getInspectController(int index) noexcept { if (index < 0 || index >= kMaxViewports - || (current_kind_ != ControllerKind::eInspectOrbit && current_kind_ != ControllerKind::eInspectArcball)) { + || (current_kind_ != ControllerKind::eInspectOrbit && current_kind_ != ControllerKind::eInspectTrackball)) { return nullptr; } - return std::get_if(&controllers_[static_cast(index)]); + return std::get_if(&controllers_[static_cast(index)]); } vne::interaction::Navigation3DController* InteractionTestLayer::getNavController(int index) noexcept { @@ -455,8 +447,8 @@ vne::interaction::Navigation3DController* InteractionTestLayer::getNavController return std::get_if(&controllers_[static_cast(index)]); } -vne::interaction::Ortho2DController* InteractionTestLayer::getOrthoController(int index) noexcept { - if (index < 0 || index >= kMaxViewports || current_kind_ != ControllerKind::eOrtho) { +vne::interaction::Ortho2DController* InteractionTestLayer::getOrtho2DController(int index) noexcept { + if (index < 0 || index >= kMaxViewports || current_kind_ != ControllerKind::eOrtho2D) { return nullptr; } return std::get_if(&controllers_[static_cast(index)]); @@ -597,24 +589,24 @@ void InteractionSettingsLayer::renderPanel() { const ControllerKind cur = il.getControllerKind(); const bool show_zoom = - (cur == ControllerKind::eInspectOrbit || cur == ControllerKind::eInspectArcball - || cur == ControllerKind::eNavigation || cur == ControllerKind::eOrtho || cur == ControllerKind::eFollow); + (cur == ControllerKind::eInspectOrbit || cur == ControllerKind::eInspectTrackball + || cur == ControllerKind::eNavigation || cur == ControllerKind::eOrtho2D || cur == ControllerKind::eFollow); if (show_zoom) { if (ImGui::CollapsingHeader("Zoom Method", ImGuiTreeNodeFlags_DefaultOpen)) { using ZM = vne::interaction::ZoomMethod; - const char* znames[] = {"DollyToCoi", "SceneScale", "ChangeFov"}; - const ZM zvals[] = {ZM::eDollyToCoi, ZM::eSceneScale, ZM::eChangeFov}; + const char* znames[] = {"SceneScale", "ChangeFov", "DollyToCoi"}; + const ZM zvals[] = {ZM::eSceneScale, ZM::eChangeFov, ZM::eDollyToCoi}; if (ImGui::Combo("Method##zoom", &ui.zoom_idx, znames, 3)) { il.setZoomMethod(zvals[ui.zoom_idx]); } ImGui::Spacing(); - ImGui::TextDisabled("DollyToCoi: move along ray to pivot"); - ImGui::TextDisabled("SceneScale: virtual scene scale"); - ImGui::TextDisabled("ChangeFov: widen/narrow FOV angle"); + ImGui::TextDisabled("SceneScale: XY scene scale in view (virtual zoom)"); + ImGui::TextDisabled("ChangeFov: widen/narrow FOV (perspective) or ortho extents"); + ImGui::TextDisabled("DollyToCoi: move along view ray toward pivot"); } } - if (cur == ControllerKind::eInspectOrbit || cur == ControllerKind::eInspectArcball) { + if (cur == ControllerKind::eInspectOrbit || cur == ControllerKind::eInspectTrackball) { if (ImGui::CollapsingHeader("View Direction", ImGuiTreeNodeFlags_DefaultOpen)) { using VD = vne::interaction::ViewDirection; struct { @@ -680,13 +672,13 @@ void InteractionSettingsLayer::renderCameraSettings() { sl.syncCameraPositionTargetUp(); sl.setUsePerspective(use_persp); if (!use_persp && !il.isManipulatorCompatibleWithCamera(false)) { - il.setControllerKind(ControllerKind::eInspectArcball); + il.setControllerKind(ControllerKind::eInspectTrackball); } il.setCamerasFromScene(); } } if (ImGui::BeginPopupModal("ManipulatorIncompatible", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("Ortho controller requires Orthographic camera."); + ImGui::Text("Ortho 2D controller requires Orthographic camera."); ImGui::Text("Switch to Perspective first, or change controller to Inspect."); if (ImGui::Button("OK")) { ImGui::CloseCurrentPopup(); @@ -753,11 +745,11 @@ void InteractionSettingsLayer::renderManipulatorSettings() { const ControllerKind cur = il.getControllerKind(); if (ImGui::CollapsingHeader("Controller", ImGuiTreeNodeFlags_DefaultOpen)) { - const char* types[] = {"Inspect (Orbit)", "Inspect (Arcball)", "Navigation", "Ortho", "Follow"}; + const char* types[] = {"Inspect (Orbit)", "Inspect (Trackball)", "Navigation", "Ortho 2D", "Follow"}; const ControllerKind values[] = {ControllerKind::eInspectOrbit, - ControllerKind::eInspectArcball, + ControllerKind::eInspectTrackball, ControllerKind::eNavigation, - ControllerKind::eOrtho, + ControllerKind::eOrtho2D, ControllerKind::eFollow}; int idx = 0; for (int i = 0; i < 5; ++i) { @@ -766,22 +758,22 @@ void InteractionSettingsLayer::renderManipulatorSettings() { break; } } - const bool ortho_only = (cur == ControllerKind::eOrtho); + const bool ortho_only = (cur == ControllerKind::eOrtho2D); const bool need_ortho = ortho_only && scene_layer_->uiSettings().use_perspective; if (need_ortho) { - ImGui::TextColored(ImVec4(1.f, 0.4f, 0.4f, 1.f), "Ortho requires Orthographic camera"); + ImGui::TextColored(ImVec4(1.f, 0.4f, 0.4f, 1.f), "Ortho 2D requires Orthographic camera"); } if (ImGui::Combo("Type##ctrl", &idx, types, 5)) { const ControllerKind new_kind = values[idx]; - if (new_kind == ControllerKind::eOrtho && scene_layer_->uiSettings().use_perspective) { - ImGui::OpenPopup("OrthoNeedsOrtho"); + if (new_kind == ControllerKind::eOrtho2D && scene_layer_->uiSettings().use_perspective) { + ImGui::OpenPopup("Ortho2DNeedsOrthographicCamera"); } else { il.setControllerKind(new_kind); il.setCamerasFromScene(); } } - if (ImGui::BeginPopupModal("OrthoNeedsOrtho", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("Ortho works only with Orthographic camera."); + if (ImGui::BeginPopupModal("Ortho2DNeedsOrthographicCamera", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("Ortho 2D works only with Orthographic camera."); ImGui::Text("Switch camera to Orthographic first."); if (ImGui::Button("OK")) { ImGui::CloseCurrentPopup(); @@ -810,13 +802,13 @@ void InteractionSettingsLayer::renderManipulatorSettings() { case ControllerKind::eInspectOrbit: ImGui::TextDisabled("LMB rotate RMB pan Scroll zoom"); break; - case ControllerKind::eInspectArcball: - ImGui::TextDisabled("LMB rotate RMB pan Scroll zoom (arcball)"); + case ControllerKind::eInspectTrackball: + ImGui::TextDisabled("LMB rotate RMB pan Scroll zoom (trackball)"); break; case ControllerKind::eNavigation: ImGui::TextDisabled("RMB + WASD/QE move Mouse look (Fps/Fly/Game)"); break; - case ControllerKind::eOrtho: + case ControllerKind::eOrtho2D: ImGui::TextDisabled("LMB/RMB pan Scroll zoom (no rotate)"); break; case ControllerKind::eFollow: @@ -826,8 +818,17 @@ void InteractionSettingsLayer::renderManipulatorSettings() { // Per-controller settings if (auto* insp = il.getInspectController()) { - auto& orb = insp->orbitArcballBehavior(); + auto& orb = insp->orbitalCameraBehavior(); if (ImGui::TreeNodeEx("Inspect Settings", ImGuiTreeNodeFlags_DefaultOpen)) { + if (cur == ControllerKind::eInspectTrackball) { + using TPM = vne::interaction::TrackballBehavior::ProjectionMode; + int proj_idx = (orb.getTrackballProjectionMode() == TPM::eHyperbolic) ? 0 : 1; + const char* proj_names[] = {"Hyperbolic", "Rim"}; + if (ImGui::Combo("Trackball projection##insp", &proj_idx, proj_names, 2)) { + orb.setTrackballProjectionMode(proj_idx == 0 ? TPM::eHyperbolic : TPM::eRim); + } + ImGui::TextDisabled("Hyperbolic: cap + continuation; Rim: hemisphere + equatorial rim"); + } using OPM = vne::interaction::OrbitPivotMode; int pivot_idx = static_cast(orb.getPivotMode()); const char* pivot_names[] = {"COI (pan moves pivot)", @@ -891,9 +892,9 @@ void InteractionSettingsLayer::renderManipulatorSettings() { } ImGui::TreePop(); } - } else if (auto* ortho = il.getOrthoController()) { - auto& opz = ortho->orthoPanZoomBehavior(); - if (ImGui::TreeNodeEx("Ortho Settings", ImGuiTreeNodeFlags_DefaultOpen)) { + } else if (auto* ortho = il.getOrtho2DController()) { + auto& opz = ortho->ortho2DBehavior(); + if (ImGui::TreeNodeEx("Ortho 2D Settings", ImGuiTreeNodeFlags_DefaultOpen)) { bool rot_en = ortho->isRotationEnabled(); if (ImGui::Checkbox("Rotation enabled##ortho", &rot_en)) { ortho->setRotationEnabled(rot_en); diff --git a/samples/glfw_opengl/03_test_interaction/demo_test_interaction.h b/samples/glfw_opengl/03_test_interaction/demo_test_interaction.h index 4d31f14..8ea2296 100644 --- a/samples/glfw_opengl/03_test_interaction/demo_test_interaction.h +++ b/samples/glfw_opengl/03_test_interaction/demo_test_interaction.h @@ -15,7 +15,7 @@ #include "vertexnova/testbed/layer.h" -#include "vertexnova/interaction/inspect_controller.h" +#include "vertexnova/interaction/inspect_3d_controller.h" #include "vertexnova/interaction/navigation_3d_controller.h" #include "vertexnova/interaction/ortho_2d_controller.h" #include "vertexnova/interaction/follow_controller.h" @@ -50,16 +50,16 @@ class BaseSceneLayer; namespace vne::samples { -/** Controller type for the interaction demo; maps to one of the four high-level controllers. */ +/** Controller type for the interaction demo; maps to one of the high-level controllers. */ enum class ControllerKind : int { eInspectOrbit = 0, - eInspectArcball, + eInspectTrackball, eNavigation, // Fps / Fly / Game chosen via setNavigationMode - eOrtho, + eOrtho2D, eFollow, }; -using ControllerVariant = std::variant; @@ -89,7 +89,7 @@ class InteractionTestLayer : public vne::testbed::ILayer { void setControllerKind(ControllerKind kind); [[nodiscard]] ControllerKind getControllerKind() const noexcept { return current_kind_; } - /** @brief Check if current controller supports the given camera type. Ortho requires orthographic. */ + /** @brief Check if current controller supports the given camera type. Ortho 2D requires orthographic. */ [[nodiscard]] bool isManipulatorCompatibleWithCamera(bool use_perspective) const; /** @brief Set cameras from scene (call after scene camera type or params change). */ @@ -116,9 +116,9 @@ class InteractionTestLayer : public vne::testbed::ILayer { [[nodiscard]] vne::math::Vec3f cameraPosition() const; [[nodiscard]] vne::math::Vec3f cameraTarget() const; - [[nodiscard]] vne::interaction::InspectController* getInspectController(int index = 0) noexcept; + [[nodiscard]] vne::interaction::Inspect3DController* getInspectController(int index = 0) noexcept; [[nodiscard]] vne::interaction::Navigation3DController* getNavController(int index = 0) noexcept; - [[nodiscard]] vne::interaction::Ortho2DController* getOrthoController(int index = 0) noexcept; + [[nodiscard]] vne::interaction::Ortho2DController* getOrtho2DController(int index = 0) noexcept; [[nodiscard]] vne::interaction::FollowController* getFollowController(int index = 0) noexcept; private: @@ -131,7 +131,7 @@ class InteractionTestLayer : public vne::testbed::ILayer { BaseSceneLayer* scene_layer_{nullptr}; std::shared_ptr camera_; std::array controllers_; - ControllerKind current_kind_{ControllerKind::eInspectArcball}; + ControllerKind current_kind_{ControllerKind::eInspectTrackball}; #ifdef VNE_TESTBED_IMGUI vne::testbed::MeshLayer* mesh_layer_{nullptr}; #endif diff --git a/samples/glfw_opengl/common/base_scene_layer.cpp b/samples/glfw_opengl/common/base_scene_layer.cpp index 2c5a0ad..4aacced 100644 --- a/samples/glfw_opengl/common/base_scene_layer.cpp +++ b/samples/glfw_opengl/common/base_scene_layer.cpp @@ -98,14 +98,27 @@ void BaseSceneLayer::onRender(const vne::testbed::RenderContext& render_context) } } else { if (auto* o = dynamic_cast(camera)) { - const float hw = ui_settings_.ortho_half * aspect; - o->setBounds(-hw, - hw, - -ui_settings_.ortho_half, - ui_settings_.ortho_half, - ui_settings_.ortho_near, - ui_settings_.ortho_far); - o->updateProjectionMatrix(); + const int vp_w = render_context.frame_info.width; + const int vp_h = render_context.frame_info.height; + const bool need_ortho_proj_sync = vp_w != ortho_proj_sync_vp_w_ || vp_h != ortho_proj_sync_vp_h_ + || ui_settings_.ortho_half != ortho_proj_sync_half_ + || ui_settings_.ortho_near != ortho_proj_sync_near_ + || ui_settings_.ortho_far != ortho_proj_sync_far_; + if (need_ortho_proj_sync) { + const float hw = ui_settings_.ortho_half * aspect; + o->setBounds(-hw, + hw, + -ui_settings_.ortho_half, + ui_settings_.ortho_half, + ui_settings_.ortho_near, + ui_settings_.ortho_far); + o->updateProjectionMatrix(); + ortho_proj_sync_vp_w_ = vp_w; + ortho_proj_sync_vp_h_ = vp_h; + ortho_proj_sync_half_ = ui_settings_.ortho_half; + ortho_proj_sync_near_ = ui_settings_.ortho_near; + ortho_proj_sync_far_ = ui_settings_.ortho_far; + } } } } @@ -240,6 +253,9 @@ void BaseSceneLayer::buildCameras(int w, int h) { ortho->updateMatrices(); cameras_ortho_[static_cast(i)] = std::move(ortho); } + + // Next `onRender` must re-apply ortho bounds if UI sliders / viewport no longer match the camera. + ortho_proj_sync_vp_w_ = -1; } void BaseSceneLayer::drawGrid() const { @@ -268,7 +284,7 @@ BaseInteractionLayer::BaseInteractionLayer(const char* name) : vne::testbed::ILayer(name) { controllers_.reserve(static_cast(kMaxViewports)); for (int i = 0; i < kMaxViewports; ++i) { - vne::interaction::InspectController c; + vne::interaction::Inspect3DController c; c.setRotationMode(vne::interaction::OrbitRotationMode::eOrbit); controllers_.push_back(std::move(c)); } @@ -400,11 +416,11 @@ void BaseInteractionLayer::onEvent(const vne::events::Event& event) { } } -vne::interaction::InspectController* BaseInteractionLayer::getInspectController() { +vne::interaction::Inspect3DController* BaseInteractionLayer::getInspectController() { return controllers_.empty() ? nullptr : &controllers_[0]; } -vne::interaction::InspectController* BaseInteractionLayer::getInspectController(int index) { +vne::interaction::Inspect3DController* BaseInteractionLayer::getInspectController(int index) { if (index >= 0 && index < static_cast(controllers_.size())) { return &controllers_[static_cast(index)]; } diff --git a/samples/glfw_opengl/common/base_scene_layer.h b/samples/glfw_opengl/common/base_scene_layer.h index 37b2926..871c025 100644 --- a/samples/glfw_opengl/common/base_scene_layer.h +++ b/samples/glfw_opengl/common/base_scene_layer.h @@ -25,7 +25,7 @@ #ifdef VNE_TESTBED_INTERACTION #include "vertexnova/events/event.h" #include "vertexnova/events/event_listener.h" -#include "vertexnova/interaction/inspect_controller.h" +#include "vertexnova/interaction/inspect_3d_controller.h" #endif #ifdef VNE_TESTBED_IMGUI @@ -99,10 +99,18 @@ class BaseSceneLayer : public vne::testbed::ILayer { std::vector> cameras_ortho_; vne::scene::SceneState scene_state_; vne::testbed::IDebugDraw* debug_draw_{nullptr}; + + /// Last orthographic projection pushed from `ui_settings_` in `onRender`. When unchanged, we skip + /// `setBounds` so interaction-driven frustum changes (ortho zoom) are not reset every frame. + int ortho_proj_sync_vp_w_{-1}; + int ortho_proj_sync_vp_h_{-1}; + float ortho_proj_sync_half_{0.0f}; + float ortho_proj_sync_near_{0.0f}; + float ortho_proj_sync_far_{0.0f}; }; // --------------------------------------------------------------------------- -// BaseInteractionLayer — orbit-arcball driven by EventManager +// BaseInteractionLayer — orbit / trackball inspect driven by EventManager // Pair with BaseSceneLayer: call setCamera(scene->getActiveCamera()) before attach. // Only compiled when VNE_TESTBED_INTERACTION is defined. // --------------------------------------------------------------------------- @@ -129,11 +137,11 @@ class BaseInteractionLayer : public vne::testbed::ILayer, public vne::events::Ev void onUpdate(float dt) override; void onEvent(const vne::events::Event& event) override; - [[nodiscard]] vne::interaction::InspectController* getInspectController(); - [[nodiscard]] vne::interaction::InspectController* getInspectController(int index); + [[nodiscard]] vne::interaction::Inspect3DController* getInspectController(); + [[nodiscard]] vne::interaction::Inspect3DController* getInspectController(int index); private: - std::vector controllers_; + std::vector controllers_; bool interaction_enabled_{true}; double last_x_{0.0}; double last_y_{0.0};