1#[must_use]
14pub fn mouse_multiplier(sensitivity: f32) -> f32 {
15 let s = sensitivity * 0.6 + 0.2;
16 s * s * s * 8.0
17}
18
19#[must_use]
21pub fn mouse_gcd(sensitivity: f32) -> f32 {
22 mouse_multiplier(sensitivity) * 0.15
23}
24
25#[must_use]
27pub fn wrap_degrees(a: f32) -> f32 {
28 let mut v = a % 360.0;
29 if v >= 180.0 {
30 v -= 360.0;
31 }
32 if v < -180.0 {
33 v += 360.0;
34 }
35 v
36}
37
38fn java_round(x: f64) -> i64 {
40 (x + 0.5).floor() as i64
42}
43
44#[derive(Debug, Clone, Copy, Default, PartialEq)]
46pub struct MouseStep {
47 budget: f64,
48}
49
50impl MouseStep {
51 pub fn counts(&mut self, delta: f32, multiplier: f32, degrees_per_tick: f64) -> i64 {
54 let step = f64::from(multiplier) * 0.15;
55 let per_tick = degrees_per_tick / step;
56 self.budget = (self.budget + per_tick).min(per_tick + 1.0);
57 let wanted = java_round(f64::from(delta) / step);
58 let lim = self.budget.trunc() as i64;
59 let count = wanted.clamp(-lim, lim);
60 self.budget -= count.unsigned_abs() as f64;
61 if count == wanted {
62 self.budget = 0.0;
63 }
64 count
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq)]
70pub struct Rotation {
71 sensitivity: f32,
72 yaw_step: MouseStep,
73 pitch_step: MouseStep,
74 target_yaw: f32,
75 target_pitch: f32,
76}
77
78impl Rotation {
79 pub const MAX_YAW_PER_TICK: f64 = 14.0;
81 pub const MAX_PITCH_PER_TICK: f64 = 10.5;
83
84 #[must_use]
86 pub fn new(sensitivity: f32) -> Self {
87 Rotation {
88 sensitivity,
89 yaw_step: MouseStep::default(),
90 pitch_step: MouseStep::default(),
91 target_yaw: 0.0,
92 target_pitch: 0.0,
93 }
94 }
95
96 #[must_use]
98 pub fn sensitivity(&self) -> f32 {
99 self.sensitivity
100 }
101
102 pub fn reset(&mut self, yaw: f32, pitch: f32) {
104 self.yaw_step = MouseStep::default();
105 self.pitch_step = MouseStep::default();
106 self.target_yaw = yaw;
107 self.target_pitch = pitch;
108 }
109
110 pub fn set_target(&mut self, yaw: f32, pitch: f32) {
112 self.target_yaw = yaw;
113 self.target_pitch = pitch.clamp(-89.5, 89.5);
114 }
115
116 #[must_use]
118 pub fn target(&self) -> (f32, f32) {
119 (self.target_yaw, self.target_pitch)
120 }
121
122 pub fn step(&mut self, yaw: f32, pitch: f32) -> (f32, f32) {
127 let m = mouse_multiplier(self.sensitivity);
128 let dx = self.yaw_step.counts(
129 wrap_degrees(self.target_yaw - yaw),
130 m,
131 Self::MAX_YAW_PER_TICK,
132 );
133 let dy = self
134 .pitch_step
135 .counts(self.target_pitch - pitch, m, Self::MAX_PITCH_PER_TICK);
136 let fx = dx as f32 * m;
138 let fy = -(dy as f32) * m;
139 let new_yaw = (f64::from(yaw) + f64::from(fx) * 0.15) as f32;
140 let new_pitch = ((f64::from(pitch) - f64::from(fy) * 0.15) as f32).clamp(-90.0, 90.0);
141 (new_yaw, new_pitch)
142 }
143
144 #[must_use]
146 pub fn yaw_error(&self, yaw: f32) -> f32 {
147 wrap_degrees(self.target_yaw - yaw).abs()
148 }
149
150 #[must_use]
152 pub fn pitch_error(&self, pitch: f32) -> f32 {
153 (self.target_pitch - pitch).abs()
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn gcd_steps() {
163 let sens = 0.338_028_2_f32;
164 let gcd = f64::from(mouse_gcd(sens));
165 let mut r = Rotation::new(sens);
166 let (mut yaw, mut pitch) = (12.5_f32, 3.0_f32);
167 r.set_target(100.0, 40.0);
168 for _ in 0..40 {
169 let (ny, np) = r.step(yaw, pitch);
170 let dy = f64::from(ny) - f64::from(yaw);
171 let dp = f64::from(np) - f64::from(pitch);
172 assert!(dy.abs() <= Rotation::MAX_YAW_PER_TICK + gcd + 1e-3);
173 assert!(dp.abs() <= Rotation::MAX_PITCH_PER_TICK + gcd + 1e-3);
174 let k = dy / gcd;
176 assert!(
177 (k - k.round()).abs() < 1e-2,
178 "yaw step {dy} is not a multiple of {gcd}"
179 );
180 yaw = ny;
181 pitch = np;
182 }
183 assert!(r.yaw_error(yaw) < gcd as f32);
184 assert!(r.pitch_error(pitch) < gcd as f32);
185 }
186
187 #[test]
188 fn wrap() {
189 assert_eq!(wrap_degrees(190.0), -170.0);
190 assert_eq!(wrap_degrees(-190.0), 170.0);
191 assert_eq!(wrap_degrees(540.0), -180.0);
192 assert_eq!(wrap_degrees(180.0), -180.0);
193 }
194
195 #[test]
196 fn java_rounding() {
197 assert_eq!(java_round(2.5), 3);
198 assert_eq!(java_round(-2.5), -2);
199 assert_eq!(java_round(-2.6), -3);
200 }
201
202 #[test]
204 fn multiplier_is_f32() {
205 assert!((mouse_multiplier(0.5) - 1.0).abs() < 1e-6);
206 let m = mouse_multiplier(0.338_028_2);
207 assert_eq!(m, {
208 let s: f32 = 0.338_028_2 * 0.6 + 0.2;
209 s * s * s * 8.0
210 });
211 }
212}