From 6eb7847b0dda3c7c5ac0d08febc4a1e3bf29af3a Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Thu, 3 Jul 2025 11:46:58 +0800 Subject: [PATCH 1/3] canvas: adjustments to make it easier to integrate canvas to an existing imgui window --- src/renderer/include/renderer/camera.hpp | 4 +++- .../include/renderer/camera_controller.hpp | 6 ++++++ .../include/renderer/gl_scene_manager.hpp | 4 ++++ src/renderer/src/camera.cpp | 2 +- src/renderer/src/camera_controller.cpp | 4 ++-- src/renderer/src/gl_scene_manager.cpp | 18 ++++++++++++++---- 6 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/renderer/include/renderer/camera.hpp b/src/renderer/include/renderer/camera.hpp index 16c43a4..62b786b 100644 --- a/src/renderer/include/renderer/camera.hpp +++ b/src/renderer/include/renderer/camera.hpp @@ -22,6 +22,7 @@ class Camera { static constexpr float default_movement_speed = 2.5f; static constexpr float default_mouse_sensitivity = 0.05f; static constexpr float default_fov = 45.0f; + static constexpr float default_zoom_speed = 2.0f; static constexpr float pitch_min = -89.0f; static constexpr float pitch_max = 89.0f; static constexpr float fov_min = 1.0f; @@ -49,7 +50,7 @@ class Camera { // public methods glm::mat4 GetViewMatrix() const; glm::mat4 GetProjectionMatrix(float aspect_ratio, float z_near = 0.1f, - float z_far = 100.0f) const; + float z_far = 1000.0f) const; void Reset(); void SetWorldUpVector(glm::vec3 up); @@ -83,6 +84,7 @@ class Camera { float movement_speed_ = default_movement_speed; float mouse_sensitivity_ = default_mouse_sensitivity; float fov_ = default_fov; + float zoom_speed_ = default_zoom_speed; }; } // namespace quickviz diff --git a/src/renderer/include/renderer/camera_controller.hpp b/src/renderer/include/renderer/camera_controller.hpp index 6f940fe..1ea5391 100644 --- a/src/renderer/include/renderer/camera_controller.hpp +++ b/src/renderer/include/renderer/camera_controller.hpp @@ -35,6 +35,8 @@ class CameraController { private: static constexpr float initial_orbit_distance = 10.0f; static constexpr float initial_top_down_height = 10.0f; + static constexpr float default_orbit_zoom_speed = 2.0f; + static constexpr float default_topdown_zoom_speed = 2.0f; void UpdateOrbitPosition(); @@ -49,6 +51,10 @@ class CameraController { // For tracking rotation in TopDown mode float top_down_rotation_ = 0.0f; + + // Zoom speed multipliers + float orbit_zoom_speed_ = default_orbit_zoom_speed; + float topdown_zoom_speed_ = default_topdown_zoom_speed; }; } // namespace quickviz diff --git a/src/renderer/include/renderer/gl_scene_manager.hpp b/src/renderer/include/renderer/gl_scene_manager.hpp index 58ceda1..8902325 100644 --- a/src/renderer/include/renderer/gl_scene_manager.hpp +++ b/src/renderer/include/renderer/gl_scene_manager.hpp @@ -44,6 +44,7 @@ class GlSceneManager : public Panel { void SetShowRenderingInfo(bool show); void SetBackgroundColor(float r, float g, float b, float a); + void SetClippingPlanes(float z_near, float z_far); void AddOpenGLObject(const std::string& name, std::unique_ptr object); @@ -85,6 +86,7 @@ class GlSceneManager : public Panel { } void Draw() override; + void RenderInsideWindow(); protected: void UpdateView(const glm::mat4& projection, const glm::mat4& view); @@ -106,6 +108,8 @@ class GlSceneManager : public Panel { // Coordinate system transformation bool use_coord_transform_ = true; glm::mat4 coord_transform_ = glm::mat4(1.0f); + float z_near_ = 0.1f; + float z_far_ = 1000.0f; // Pre-draw callback PreDrawCallback pre_draw_callback_; diff --git a/src/renderer/src/camera.cpp b/src/renderer/src/camera.cpp index bc6fde1..7fc4c70 100644 --- a/src/renderer/src/camera.cpp +++ b/src/renderer/src/camera.cpp @@ -131,7 +131,7 @@ void Camera::ProcessMouseMovement(float x_offset, float y_offset, } void Camera::ProcessMouseScroll(float y_offset) { - fov_ -= y_offset; + fov_ -= y_offset * zoom_speed_; if (fov_ < fov_min) fov_ = fov_min; if (fov_ > fov_max) fov_ = fov_max; } diff --git a/src/renderer/src/camera_controller.cpp b/src/renderer/src/camera_controller.cpp index c393688..c03c30c 100644 --- a/src/renderer/src/camera_controller.cpp +++ b/src/renderer/src/camera_controller.cpp @@ -132,13 +132,13 @@ void CameraController::ProcessMouseMovement(float x_offset, float y_offset) { void CameraController::ProcessMouseScroll(float y_offset) { if (mode_ == Mode::kOrbit) { - orbit_distance_ -= y_offset; + orbit_distance_ -= y_offset * orbit_zoom_speed_; if (orbit_distance_ < 1.0f) orbit_distance_ = 1.0f; UpdateOrbitPosition(); } else if (mode_ == Mode::kTopDown) { glm::vec3 position = camera_.GetPosition(); // In TopDown mode, adjust Y position (height) with scroll - position.y -= y_offset; + position.y -= y_offset * topdown_zoom_speed_; if (position.y < 1.0f) position.y = 1.0f; // Set a minimum height camera_.SetPosition(position); } else { diff --git a/src/renderer/src/gl_scene_manager.cpp b/src/renderer/src/gl_scene_manager.cpp index 2d5acf6..090d21b 100644 --- a/src/renderer/src/gl_scene_manager.cpp +++ b/src/renderer/src/gl_scene_manager.cpp @@ -52,6 +52,11 @@ void GlSceneManager::SetBackgroundColor(float r, float g, float b, float a) { background_color_ = glm::vec4(r, g, b, a); } +void GlSceneManager::SetClippingPlanes(float z_near, float z_far) { + z_near_ = z_near; + z_far_ = z_far; +} + void GlSceneManager::AddOpenGLObject(const std::string& name, std::unique_ptr object) { if (object == nullptr) { @@ -117,9 +122,7 @@ void GlSceneManager::DrawOpenGLObject() { } } -void GlSceneManager::Draw() { - Begin(); - +void GlSceneManager::RenderInsideWindow() { // update view according to user input ImGuiIO& io = ImGui::GetIO(); ImVec2 content_size = ImGui::GetContentRegionAvail(); @@ -159,7 +162,8 @@ void GlSceneManager::Draw() { ? static_cast(content_size.x) / static_cast(content_size.y) : frame_buffer_->GetAspectRatio(); - glm::mat4 projection = camera_->GetProjectionMatrix(aspect_ratio); + glm::mat4 projection = + camera_->GetProjectionMatrix(aspect_ratio, z_near_, z_far_); glm::mat4 view = camera_->GetViewMatrix(); UpdateView(projection, view); @@ -181,6 +185,12 @@ void GlSceneManager::Draw() { ImGui::PopStyleColor(); ImGui::PopFont(); } +} + +void GlSceneManager::Draw() { + Begin(); + + RenderInsideWindow(); End(); } From 626b9dfe3a75847e1d480b51a75113da0ab47869 Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Thu, 3 Jul 2025 12:34:06 +0800 Subject: [PATCH 2/3] canvs: fixed 2d image loading memory alignment issue --- docs/opengl_texture_alignment_fix.md | 184 +++++++++++++++++++++++++ src/renderer/src/renderable/canvas.cpp | 77 +++++++++-- 2 files changed, 253 insertions(+), 8 deletions(-) create mode 100644 docs/opengl_texture_alignment_fix.md diff --git a/docs/opengl_texture_alignment_fix.md b/docs/opengl_texture_alignment_fix.md new file mode 100644 index 0000000..b4f9763 --- /dev/null +++ b/docs/opengl_texture_alignment_fix.md @@ -0,0 +1,184 @@ +# OpenGL Texture Alignment Issue and Fix + +## Overview + +This document explains a critical issue that was discovered in the QuickViz Canvas implementation where certain image dimensions would cause application crashes during texture upload to OpenGL, and describes the implemented fix. + +## Problem Description + +### Symptoms +- Application crashes with segmentation fault during `glTexImage2D` calls +- Crashes occurred specifically with images having dimensions 1203×809 pixels +- Other dimensions like 1756×1152 or 800×538 worked perfectly fine +- The issue was consistent across different image formats (PNG) and content + +### Initial Investigation +The crash occurred in `Canvas::SetupBackgroundImage()` specifically during the OpenGL texture upload phase: + +```cpp +glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, + 0, format, GL_UNSIGNED_BYTE, data); +``` + +The STB image loading was successful - the crash happened during GPU texture creation. + +## Root Cause Analysis + +### The Real Issue: OpenGL Row Alignment + +The root cause was **OpenGL row alignment requirements**. OpenGL expects texture data to be aligned to specific byte boundaries, controlled by the `GL_UNPACK_ALIGNMENT` parameter (default: 4 bytes). + +### Mathematical Analysis + +For RGB images (3 bytes per pixel): +- **1203 × 3 = 3609 bytes per row** +- **3609 % 4 = 1** (not aligned to 4-byte boundary) ❌ + +Working images: +- **1756 × 3 = 5268 bytes per row** +- **5268 % 4 = 0** (aligned) ✅ +- **800 × 3 = 2400 bytes per row** +- **2400 % 4 = 0** (aligned) ✅ + +### Why This Causes Crashes + +When `GL_UNPACK_ALIGNMENT` is set to 4 (default), OpenGL expects each row of texture data to start at a 4-byte aligned memory address. For unaligned data: + +1. OpenGL may read beyond the allocated memory boundary +2. This can cause segmentation faults or memory corruption +3. The behavior is undefined and driver-dependent + +## Solution Implementation + +### The Fix + +A generic solution was implemented that automatically detects and handles alignment issues: + +```cpp +// Check for OpenGL row alignment issues +// OpenGL expects texture data to be aligned to 4-byte boundaries by default +int bytes_per_pixel = (format == GL_RGBA) ? 4 : (format == GL_RGB) ? 3 : 1; +int bytes_per_row = width * bytes_per_pixel; +bool alignment_issue = (bytes_per_row % 4) != 0; + +if (alignment_issue) { + std::cout << "Canvas: Adjusting alignment for " << width << "x" << height + << " texture (row size: " << bytes_per_row << " bytes)" << std::endl; + // Set pixel alignment to 1 byte to handle unaligned row data + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); +} + +glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, + 0, format, GL_UNSIGNED_BYTE, data); + +// Restore default alignment if we changed it +if (alignment_issue) { + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // Restore default +} +``` + +### How the Fix Works + +1. **Detection**: Calculate bytes per row and check if divisible by 4 +2. **Adjustment**: Set `GL_UNPACK_ALIGNMENT` to 1 for problematic textures +3. **Upload**: Perform normal texture upload with adjusted alignment +4. **Restoration**: Restore default alignment setting + +### Why GL_UNPACK_ALIGNMENT = 1 Works + +Setting `GL_UNPACK_ALIGNMENT` to 1 tells OpenGL: +- "Don't assume any row alignment" +- "Read pixel data byte-by-byte without alignment requirements" +- This works for any row size but may have minor performance implications + +## Technical Details + +### OpenGL Alignment Parameters + +- `GL_UNPACK_ALIGNMENT`: Controls alignment for data read from client memory (default: 4) +- `GL_PACK_ALIGNMENT`: Controls alignment for data written to client memory (default: 4) +- Valid values: 1, 2, 4, 8 bytes + +### Performance Considerations + +- Aligned textures (4-byte rows) have optimal performance +- Unaligned textures with `GL_UNPACK_ALIGNMENT=1` work correctly but may be slightly slower +- The performance difference is negligible for most applications + +### Affected Image Formats + +This issue affects images where `width × channels % 4 ≠ 0`: + +| Format | Problematic Width Examples | +|--------|---------------------------| +| RGB (3 channels) | 1203, 1205, 1207, 1209, ... (any width where width × 3 % 4 ≠ 0) | +| Grayscale (1 channel) | Any non-multiple of 4 | +| RGBA (4 channels) | Never problematic (4 × anything is divisible by 4) | + +## Alternative Solutions Considered + +### 1. Image Resizing (Workaround) +- Resize problematic images to aligned dimensions +- **Pros**: Guaranteed compatibility +- **Cons**: Requires manual intervention, data loss + +### 2. Row Padding (Memory Solution) +- Add padding bytes to make rows 4-byte aligned +- **Pros**: Maintains optimal performance +- **Cons**: Complex implementation, memory overhead + +### 3. Generic Alignment Fix (Chosen Solution) +- Automatically adjust `GL_UNPACK_ALIGNMENT` when needed +- **Pros**: Automatic, generic, no data loss +- **Cons**: Minor performance impact for unaligned textures + +## Testing and Validation + +### Test Cases +1. **Original problematic images**: Now load successfully +2. **Working images**: Continue to work without alignment adjustment +3. **Various dimensions**: Tested multiple alignment scenarios + +### Verification +The fix was verified with: +- Original 1203×809 images (both PNG variants) +- Working 1756×1152 and 800×538 images +- Edge cases with different channel counts + +## Best Practices + +### For Image Assets +1. **Prefer aligned dimensions** when possible for optimal performance +2. **RGB images**: Use widths that make `width × 3` divisible by 4 +3. **RGBA images**: Any width works (always aligned) + +### For Developers +1. **Don't hard-code dimension checks** - use generic alignment detection +2. **Always restore OpenGL state** after temporary changes +3. **Log alignment adjustments** for debugging purposes + +## Code Location + +The fix is implemented in: +- **File**: `src/renderer/src/renderable/canvas.cpp` +- **Function**: `Canvas::SetupBackgroundImage()` +- **Lines**: Texture upload section (~415-440) + +## Future Enhancements + +Potential improvements: +1. **Global alignment policy**: Allow configuration of default alignment behavior +2. **Performance metrics**: Measure alignment impact in real applications +3. **Memory optimization**: Implement row padding for frequently used textures + +## References + +- [OpenGL specification on pixel storage](https://www.khronos.org/opengl/wiki/Pixel_Transfer#Pixel_layout) +- [GL_UNPACK_ALIGNMENT documentation](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glPixelStore.xhtml) +- [STB Image library](https://github.com/nothings/stb) + +--- + +**Author**: Claude Code Assistant +**Date**: July 2025 +**Version**: 1.0 \ No newline at end of file diff --git a/src/renderer/src/renderable/canvas.cpp b/src/renderer/src/renderable/canvas.cpp index 27fd73d..1c19a7e 100644 --- a/src/renderer/src/renderable/canvas.cpp +++ b/src/renderer/src/renderable/canvas.cpp @@ -142,11 +142,11 @@ void main() { FragColor = texColor; // Add a subtle border for visual feedback on the texture boundaries - float border = 0.005; - if (TexCoord.x < border || TexCoord.x > 1.0 - border || - TexCoord.y < border || TexCoord.y > 1.0 - border) { - FragColor = mix(vec4(1.0, 0.0, 0.0, 1.0), texColor, 0.3); // Subtle red border - } + // float border = 0.005; + // if (TexCoord.x < border || TexCoord.x > 1.0 - border || + // TexCoord.y < border || TexCoord.y > 1.0 - border) { + // FragColor = mix(vec4(1.0, 0.0, 0.0, 1.0), texColor, 0.3); // Subtle red border + // } } )"; } // namespace @@ -408,6 +408,22 @@ void Canvas::SetupBackgroundImage(int width, int height, int channels, uint32_t texture_id = background_texture_.load(); glBindTexture(GL_TEXTURE_2D, texture_id); + // Check for OpenGL row alignment issues + // OpenGL expects texture data to be aligned to 4-byte boundaries by default + int bytes_per_pixel = (format == GL_RGBA) ? 4 : (format == GL_RGB) ? 3 : 1; + int bytes_per_row = width * bytes_per_pixel; + bool alignment_issue = (bytes_per_row % 4) != 0; + + if (alignment_issue) { + std::cout << "Canvas: Adjusting alignment for " << width << "x" << height + << " texture (row size: " << bytes_per_row << " bytes)" << std::endl; + // Set pixel alignment to 1 byte to handle unaligned row data + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + } + + // Clear any previous OpenGL errors + while (glGetError() != GL_NO_ERROR); + glTexImage2D(GL_TEXTURE_2D, 0, // Mipmap level internal_format, // Internal format @@ -417,14 +433,59 @@ void Canvas::SetupBackgroundImage(int width, int height, int channels, GL_UNSIGNED_BYTE, // Type data // Data ); + + // Restore default alignment if we changed it + if (alignment_issue) { + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // Restore default + } + + // Check for GL errors after texture upload + GLenum error = glGetError(); + if (error != GL_NO_ERROR) { + std::cerr << "OpenGL error after glTexImage2D: " << error; + switch (error) { + case GL_INVALID_ENUM: + std::cerr << " (GL_INVALID_ENUM - format/internal format invalid)"; + break; + case GL_INVALID_VALUE: + std::cerr << " (GL_INVALID_VALUE - width/height invalid)"; + break; + case GL_INVALID_OPERATION: + std::cerr << " (GL_INVALID_OPERATION - format/internal format incompatible)"; + break; + case GL_OUT_OF_MEMORY: + std::cerr << " (GL_OUT_OF_MEMORY - insufficient memory)"; + break; + default: + std::cerr << " (Unknown error)"; + break; + } + std::cerr << std::endl; + glBindTexture(GL_TEXTURE_2D, 0); + return; + } // Generate mipmaps for better quality when scaled glGenerateMipmap(GL_TEXTURE_2D); - // Check for GL errors - GLenum error = glGetError(); + // Check for GL errors after mipmap generation + error = glGetError(); if (error != GL_NO_ERROR) { - std::cerr << "OpenGL error after texture setup: " << error << std::endl; + std::cerr << "OpenGL error after glGenerateMipmap: " << error; + switch (error) { + case GL_INVALID_ENUM: + std::cerr << " (GL_INVALID_ENUM - target invalid)"; + break; + case GL_INVALID_OPERATION: + std::cerr << " (GL_INVALID_OPERATION - texture not complete)"; + break; + default: + std::cerr << " (Unknown error)"; + break; + } + std::cerr << std::endl; + glBindTexture(GL_TEXTURE_2D, 0); + return; } // Unbind the texture From f4362692641f4cd81d6d7f72903ef73f5852ab86 Mon Sep 17 00:00:00 2001 From: Ruixiang Du Date: Thu, 3 Jul 2025 16:39:51 +0800 Subject: [PATCH 3/3] camera_controller: added position control for better camera control --- .../include/renderer/camera_controller.hpp | 16 +++-- src/renderer/src/camera_controller.cpp | 68 ++++++++++++------- 2 files changed, 55 insertions(+), 29 deletions(-) diff --git a/src/renderer/include/renderer/camera_controller.hpp b/src/renderer/include/renderer/camera_controller.hpp index 1ea5391..ba77a0b 100644 --- a/src/renderer/include/renderer/camera_controller.hpp +++ b/src/renderer/include/renderer/camera_controller.hpp @@ -27,14 +27,19 @@ class CameraController { void ProcessKeyboard(CameraMovement direction, float delta_time); void ProcessMouseMovement(float x_offset, float y_offset); void ProcessMouseScroll(float y_offset); - + // Set the current mouse button state for camera control void SetActiveMouseButton(int button); int GetActiveMouseButton() const { return active_mouse_button_; } + float GetHeight() const { return camera_.GetPosition().y; } + void SetHeight(float height); + + glm::vec2 GetPosition() const; + void SetPosition(const glm::vec2& position); + private: static constexpr float initial_orbit_distance = 10.0f; - static constexpr float initial_top_down_height = 10.0f; static constexpr float default_orbit_zoom_speed = 2.0f; static constexpr float default_topdown_zoom_speed = 2.0f; @@ -44,14 +49,13 @@ class CameraController { Mode mode_ = Mode::kOrbit; glm::vec3 orbit_target_ = glm::vec3(0.0f, 0.0f, 0.0f); float orbit_distance_ = initial_orbit_distance; - float top_down_height_ = initial_top_down_height; - + // For tracking mouse button states, -1 means no button pressed int active_mouse_button_ = MouseButton::kNone; - + // For tracking rotation in TopDown mode float top_down_rotation_ = 0.0f; - + // Zoom speed multipliers float orbit_zoom_speed_ = default_orbit_zoom_speed; float topdown_zoom_speed_ = default_topdown_zoom_speed; diff --git a/src/renderer/src/camera_controller.cpp b/src/renderer/src/camera_controller.cpp index c03c30c..ae555a4 100644 --- a/src/renderer/src/camera_controller.cpp +++ b/src/renderer/src/camera_controller.cpp @@ -21,9 +21,7 @@ CameraController::CameraController(Camera& camera, glm::vec3 position, UpdateOrbitPosition(); } -void CameraController::Reset() { - camera_.Reset(); -} +void CameraController::Reset() { camera_.Reset(); } void CameraController::SetMode(CameraController::Mode mode) { if (mode == mode_) return; @@ -39,7 +37,7 @@ void CameraController::SetMode(CameraController::Mode mode) { glm::vec3 position = camera_.GetPosition(); if (position.y < 1.0f) position.y = 1.0f; // Set a minimum height camera_.SetPosition(position); - + // Reset rotation angle for top-down view top_down_rotation_ = 0.0f; } @@ -49,6 +47,25 @@ void CameraController::SetActiveMouseButton(int button) { active_mouse_button_ = button; } +void CameraController::SetHeight(float height) { + glm::vec3 position = camera_.GetPosition(); + position.y = height; + camera_.SetPosition(position); +} + +glm::vec2 CameraController::GetPosition() const { + auto pos = camera_.GetPosition(); + // Return only X and Z coordinates for 2D position + return glm::vec2(pos.x, pos.z); +} + +void CameraController::SetPosition(const glm::vec2& position) { + glm::vec3 pos = camera_.GetPosition(); + pos.x = position.x; + pos.z = position.y; // Use Y for Z in 2D view + camera_.SetPosition(pos); +} + void CameraController::ProcessKeyboard( CameraController::CameraMovement direction, float delta_time) { if (mode_ == Mode::kOrbit) return; @@ -83,43 +100,48 @@ void CameraController::ProcessMouseMovement(float x_offset, float y_offset) { case Mode::kTopDown: // Handle mouse movement for top-down view based on mouse button if (active_mouse_button_ == MouseButton::kLeft) { - // Instead of directly setting camera yaw, we'll track rotation angle ourselves - // and apply it only when there's significant mouse movement + // Instead of directly setting camera yaw, we'll track rotation angle + // ourselves and apply it only when there's significant mouse movement float rotation_sensitivity = 0.5f; - + // Only process rotation if there's actual mouse movement if (std::abs(x_offset) > 0.1f) { // Update our own rotation variable top_down_rotation_ += x_offset * rotation_sensitivity; - + // Keep rotation in range [0, 360) while (top_down_rotation_ >= 360.0f) top_down_rotation_ -= 360.0f; while (top_down_rotation_ < 0.0f) top_down_rotation_ += 360.0f; - + // Set the camera yaw directly instead of using ProcessMouseMovement camera_.SetYaw(top_down_rotation_); } - } - else if (active_mouse_button_ == MouseButton::kMiddle) { - // Translation/panning on the X-Z plane - implement true dragging behavior + } else if (active_mouse_button_ == MouseButton::kMiddle) { + // Translation/panning on the X-Z plane - implement true dragging + // behavior float sensitivity = 0.01f; - - // Calculate movement based on camera height for consistent speed at different zoom levels - float height_factor = camera_.GetPosition().y / 10.0f; // Normalize based on height - if (height_factor < 0.1f) height_factor = 0.1f; // Minimum factor - - // Apply rotation to the mouse movement vectors to correctly map to the rotated world + + // Calculate movement based on camera height for consistent speed at + // different zoom levels + float height_factor = + camera_.GetPosition().y / 10.0f; // Normalize based on height + if (height_factor < 0.1f) height_factor = 0.1f; // Minimum factor + + // Apply rotation to the mouse movement vectors to correctly map to the + // rotated world // 1. Create a rotation matrix for the current rotation angle float angle_rad = glm::radians(top_down_rotation_); - + // 2. Calculate rotated axes based on the current rotation - float rot_dx = -y_offset * std::cos(angle_rad) - x_offset * std::sin(angle_rad); - float rot_dz = -y_offset * std::sin(angle_rad) + x_offset * std::cos(angle_rad); - + float rot_dx = + -y_offset * std::cos(angle_rad) - x_offset * std::sin(angle_rad); + float rot_dz = + -y_offset * std::sin(angle_rad) + x_offset * std::cos(angle_rad); + // 3. Apply sensitivity and height scaling rot_dx *= sensitivity * height_factor; rot_dz *= sensitivity * height_factor; - + // 4. Update camera position glm::vec3 position = camera_.GetPosition(); position.x += rot_dx;