git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

commitf3351334fe075ce111fa5223ce120ee5e44359e0
parentc32ef627c8
authorLucas Galante <[email protected]>
date2026-05-23 18:49
Implement inertial rotation for the viewport

 src/main.rs | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 66 insertions(+)

diff --git a/src/main.rs b/src/main.rs
index 09e08d2..a4a846d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1298,6 +1298,12 @@ struct State {
     depth_texture_view: wgpu::TextureView,
     rotation_y: f32,
     rotation_x: f32,
+    is_rotating_viewport: bool,
+    last_rotate_time: Instant,
+    rotate_accum_yaw: f32,
+    rotate_accum_pitch: f32,
+    rotate_velocity_yaw: f32,
+    rotate_velocity_pitch: f32,
     show_grid: bool,
     show_cube: bool,
     show_origin: bool,
@@ -2189,6 +2195,12 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             depth_texture_view,
             rotation_y: 0.0,
             rotation_x: 0.0,
+            is_rotating_viewport: false,
+            last_rotate_time: Instant::now(),
+            rotate_accum_yaw: 0.0,
+            rotate_accum_pitch: 0.0,
+            rotate_velocity_yaw: 0.0,
+            rotate_velocity_pitch: 0.0,
             show_grid: true,
             show_cube: false,
             show_origin: true,
@@ -2877,6 +2889,14 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                             self.rotation_y += dx;
                             self.rotation_x -= dy;
                             self.rotation_x = self.rotation_x.clamp(-std::f32::consts::FRAC_PI_2 + 0.01, std::f32::consts::FRAC_PI_2 - 0.01);
+
+                            self.is_rotating_viewport = false;
+                            let dt_scroll = Instant::now().duration_since(self.last_frame).as_secs_f32().min(0.1);
+                            let vel_yaw = if dt_scroll > 1e-4 { dx / dt_scroll } else { dx * 60.0 };
+                            let vel_pitch = if dt_scroll > 1e-4 { -dy / dt_scroll } else { -dy * 60.0 };
+                            self.rotate_velocity_yaw = self.rotate_velocity_yaw * 0.4 + vel_yaw * 0.6;
+                            self.rotate_velocity_pitch = self.rotate_velocity_pitch * 0.4 + vel_pitch * 0.6;
+
                             true
                         }
                         winit::event::MouseScrollDelta::PixelDelta(pos) => {
@@ -2885,6 +2905,15 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                             self.rotation_y += dx;
                             self.rotation_x -= dy;
                             self.rotation_x = self.rotation_x.clamp(-std::f32::consts::FRAC_PI_2 + 0.01, std::f32::consts::FRAC_PI_2 - 0.01);
+
+                            self.is_rotating_viewport = match phase {
+                                winit::event::TouchPhase::Started | winit::event::TouchPhase::Moved => true,
+                                winit::event::TouchPhase::Ended | winit::event::TouchPhase::Cancelled => false,
+                            };
+                            self.last_rotate_time = Instant::now();
+                            self.rotate_accum_yaw += dx;
+                            self.rotate_accum_pitch -= dy;
+
                             true
                         }
                     }
@@ -2951,6 +2980,12 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                     self.is_scrolling_trackpad = false;
                     self.scroll_accum_x = 0.0;
                     self.scroll_accum_y = 0.0;
+
+                    self.rotate_velocity_yaw = 0.0;
+                    self.rotate_velocity_pitch = 0.0;
+                    self.is_rotating_viewport = false;
+                    self.rotate_accum_yaw = 0.0;
+                    self.rotate_accum_pitch = 0.0;
                 }
                 let node_area_y = HEADER_H + MENUBAR_H + BREADCRUMB_H;
                 let dialog_open = self.widgets[CONFIG_DIALOG_IDX].visible() || self.node_palette_visible;
@@ -3302,6 +3337,20 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             self.scroll_accum_y = 0.0;
         }
 
+        // Viewport rotation velocity tracking & timeout detection
+        if self.is_rotating_viewport {
+            if now.duration_since(self.last_rotate_time).as_secs_f32() > 0.05 {
+                self.is_rotating_viewport = false;
+            } else if dt > 1e-5 {
+                let vel_yaw = self.rotate_accum_yaw / dt;
+                let vel_pitch = self.rotate_accum_pitch / dt;
+                self.rotate_velocity_yaw = self.rotate_velocity_yaw * 0.4 + vel_yaw * 0.6;
+                self.rotate_velocity_pitch = self.rotate_velocity_pitch * 0.4 + vel_pitch * 0.6;
+            }
+            self.rotate_accum_yaw = 0.0;
+            self.rotate_accum_pitch = 0.0;
+        }
+
         let mut tick_changed = false;
         for w in &mut self.widgets {
             if w.tick(dt) {
@@ -3333,6 +3382,23 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             tick_changed = true;
         }
 
+        // Viewport rotation kinetic slide
+        if !self.is_rotating_viewport && (self.rotate_velocity_yaw.abs() > 0.001 || self.rotate_velocity_pitch.abs() > 0.001) {
+            self.rotation_y += self.rotate_velocity_yaw * dt;
+            self.rotation_x += self.rotate_velocity_pitch * dt;
+            self.rotation_x = self.rotation_x.clamp(-std::f32::consts::FRAC_PI_2 + 0.01, std::f32::consts::FRAC_PI_2 - 0.01);
+
+            // Apply friction decay
+            let friction = 5.0_f32;
+            let decay = (-friction * dt).exp();
+            self.rotate_velocity_yaw *= decay;
+            self.rotate_velocity_pitch *= decay;
+
+            if self.rotate_velocity_yaw.abs() < 0.01 { self.rotate_velocity_yaw = 0.0; }
+            if self.rotate_velocity_pitch.abs() < 0.01 { self.rotate_velocity_pitch = 0.0; }
+            tick_changed = true;
+        }
+
         if tick_changed {
             self.upload_vertices();
         }