1use crate::view::{add3, cross3, dot3, len3, norm3, rotate3, scale3, sub3, Projection, ViewCamera};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum Gesture {
18 None,
19 Rotate,
20 Pan,
21}
22
23pub const BUTTON_LEFT: i32 = 0;
25pub const BUTTON_MIDDLE: i32 = 1;
26pub const BUTTON_RIGHT: i32 = 2;
27
28const ZOOM_PER_NOTCH: f64 = 1.08;
49const WHEEL_POINTS_PER_NOTCH: f64 = 70.0;
51const MAX_WHEEL_NOTCHES_PER_EVENT: f64 = 3.0;
56const MIN_ORTHO_HALF_HEIGHT: f64 = 1e-9;
57const MIN_PERSP_DISTANCE: f64 = 1e-6;
58
59#[derive(Debug, Default)]
60pub struct ArcballControls {
61 pub enabled: bool,
62 gesture: GestureState,
63}
64
65#[derive(Debug)]
66struct GestureState {
67 kind: Gesture,
68 last: (f64, f64),
69}
70
71impl Default for GestureState {
72 fn default() -> Self {
73 Self {
74 kind: Gesture::None,
75 last: (0.0, 0.0),
76 }
77 }
78}
79
80impl ArcballControls {
81 pub fn new() -> Self {
82 Self {
83 enabled: true,
84 gesture: GestureState::default(),
85 }
86 }
87
88 pub fn active_gesture(&self) -> Gesture {
89 self.gesture.kind
90 }
91
92 pub fn pointer_down(&mut self, x: f64, y: f64, button: i32) -> bool {
95 if !self.enabled {
96 return false;
97 }
98 self.gesture.kind = match button {
99 BUTTON_LEFT => Gesture::Rotate,
100 BUTTON_MIDDLE | BUTTON_RIGHT => Gesture::Pan,
101 _ => Gesture::None,
102 };
103 self.gesture.last = (x, y);
104 self.gesture.kind != Gesture::None
105 }
106
107 pub fn pointer_move(&mut self, camera: &mut ViewCamera, x: f64, y: f64) -> bool {
110 if !self.enabled || self.gesture.kind == Gesture::None {
111 return false;
112 }
113 let (lx, ly) = self.gesture.last;
114 if (x - lx).abs() < f64::EPSILON && (y - ly).abs() < f64::EPSILON {
115 return false;
116 }
117 match self.gesture.kind {
118 Gesture::Rotate => rotate_arcball(camera, (lx, ly), (x, y)),
119 Gesture::Pan => pan(camera, x - lx, y - ly),
120 Gesture::None => {}
121 }
122 self.gesture.last = (x, y);
123 true
124 }
125
126 pub fn pointer_up(&mut self) -> bool {
128 let was = self.gesture.kind != Gesture::None;
129 self.gesture.kind = Gesture::None;
130 was
131 }
132
133 pub fn wheel(&mut self, camera: &mut ViewCamera, delta_y: f64, cursor: Option<[f64; 2]>) -> bool {
137 if !self.enabled || delta_y == 0.0 {
138 return false;
139 }
140 let notches = (delta_y / WHEEL_POINTS_PER_NOTCH)
144 .clamp(-MAX_WHEEL_NOTCHES_PER_EVENT, MAX_WHEEL_NOTCHES_PER_EVENT);
145 let factor = ZOOM_PER_NOTCH.powf(notches);
146 match cursor {
147 Some([cx, cy]) => zoom_toward(camera, factor, cx, cy),
148 None => zoom(camera, factor),
149 }
150 true
151 }
152}
153
154pub fn zoom_toward(camera: &mut ViewCamera, factor: f64, cx: f64, cy: f64) {
157 let wpp = camera.world_per_pixel();
158 let (right, up, _) = camera.basis();
159 let sx = cx - camera.width * 0.5;
160 let sy = -(cy - camera.height * 0.5); let off = add3(scale3(right, sx * wpp), scale3(up, sy * wpp));
163 match &mut camera.projection {
164 Projection::Orthographic { half_height } => {
165 let old = *half_height;
166 *half_height = (old * factor).max(MIN_ORTHO_HALF_HEIGHT);
167 let f = *half_height / old; let shift = scale3(off, 1.0 - f);
169 camera.target = add3(camera.target, shift);
170 camera.eye = add3(camera.eye, shift);
171 }
172 Projection::Perspective { .. } => {
173 let cursor_world = add3(camera.target, off);
176 let nt = add3(cursor_world, scale3(sub3(camera.target, cursor_world), factor));
177 let ne = add3(cursor_world, scale3(sub3(camera.eye, cursor_world), factor));
178 let dir = sub3(ne, nt);
179 let dist = len3(dir).max(MIN_PERSP_DISTANCE);
180 camera.target = nt;
181 camera.eye = add3(nt, scale3(norm3(dir), dist));
182 }
183 }
184}
185
186pub fn zoom(camera: &mut ViewCamera, factor: f64) {
188 match &mut camera.projection {
189 Projection::Orthographic { half_height } => {
190 *half_height = (*half_height * factor).max(MIN_ORTHO_HALF_HEIGHT);
191 }
192 Projection::Perspective { .. } => {
193 let dir = sub3(camera.eye, camera.target);
194 let dist = (len3(dir) * factor).max(MIN_PERSP_DISTANCE);
195 camera.eye = add3(camera.target, scale3(norm3(dir), dist));
196 }
197 }
198}
199
200pub fn pan(camera: &mut ViewCamera, dx: f64, dy: f64) {
203 let (right, up, _) = camera.basis();
204 let wpp = camera.world_per_pixel();
205 let offset = add3(scale3(right, -dx * wpp), scale3(up, dy * wpp));
206 camera.eye = add3(camera.eye, offset);
207 camera.target = add3(camera.target, offset);
208}
209
210fn trackball_point(camera: &ViewCamera, x: f64, y: f64) -> [f64; 3] {
214 let radius = 0.5 * camera.width.min(camera.height).max(1.0) * 0.75;
215 let cx = camera.width * 0.5;
216 let cy = camera.height * 0.5;
217 let px = x - cx;
218 let py = cy - y; let r2 = radius * radius;
220 let d2 = px * px + py * py;
221 let pz = if d2 <= r2 * 0.5 {
222 (r2 - d2).sqrt()
223 } else {
224 r2 * 0.5 / d2.sqrt()
226 };
227 norm3([px, py, pz])
228}
229
230fn rotate_arcball(camera: &mut ViewCamera, from: (f64, f64), to: (f64, f64)) {
234 let v0 = trackball_point(camera, from.0, from.1);
235 let v1 = trackball_point(camera, to.0, to.1);
236 let axis_cam = cross3(v0, v1);
237 let axis_len = len3(axis_cam);
238 if axis_len < 1e-12 {
239 return;
240 }
241 let angle = dot3(v0, v1).clamp(-1.0, 1.0).acos();
242 if angle.abs() < 1e-12 {
243 return;
244 }
245 let (right, up, forward) = camera.basis();
248 let axis_cam = scale3(axis_cam, 1.0 / axis_len);
249 let axis_world = norm3(add3(
250 add3(scale3(right, axis_cam[0]), scale3(up, axis_cam[1])),
251 scale3(forward, -axis_cam[2]),
252 ));
253 let offset = sub3(camera.eye, camera.target);
254 camera.eye = add3(camera.target, rotate3(offset, axis_world, -angle));
255 camera.up = norm3(rotate3(camera.up, axis_world, -angle));
256}
257
258