3D mouse: invert and speed for each of the cap's six axes (#1814)

Thanks @NecroKote.
This commit is contained in:
Mikalai Kukhta authored and GitHub committed 2026-10-17 04:09:55 -04:00
1 parent c9b8267b9e
commit 64235ab12a
3 files changed
+58 -2

No files matched your search

+30 -1
View File
@@ -87,6 +87,16 @@ impl DriverMapping {
mapping
}
/// The mapping with the user's per-axis choices folded in: `reverse` flips and `speed` scales
/// each physical axis of the cap. A speed that is not a finite number counts as 1.
pub fn with_user_axes(mut self, reverse: [bool; 6], speed: [f32; 6]) -> Self {
for ((rule, reverse), speed) in self.axes.iter_mut().zip(reverse).zip(speed) {
rule.reversed ^= reverse;
rule.scale *= if speed.is_finite() { f64::from(speed).clamp(0.0, 3.0) } else { 1.0 };
}
self
}
pub fn apply(self, axes: Axes) -> Motion {
let raw = [
normalized(axes.translation[0]),
@@ -139,7 +149,10 @@ pub fn navigate_frame(app: &mut SolveApp, ctx: &egui::Context, mapping: DriverMa
app.begin_external_navigation();
// A long gap (the first frame after a pause) moves as far as a 50 ms one.
let step = if seconds.is_finite() { seconds.min(0.05) } else { 0.0 };
app.viewport.device_motion = Some(DeviceMotion { motion: mapping.apply(axes), seconds: step * speed.clamp(0.1, 5.0) });
app.viewport.device_motion = Some(DeviceMotion {
motion: mapping.with_user_axes(app.preferences.space_mouse_reverse, app.preferences.space_mouse_axis_speed).apply(axes),
seconds: step * speed.clamp(0.1, 5.0),
});
ctx.request_repaint_after(Duration::from_millis(16));
true
}
@@ -182,6 +195,22 @@ mod tests {
app
}
#[test]
fn per_axis_choices_flip_and_scale_one_physical_axis() {
let mapping = DriverMapping::default().with_user_axes([false, true, false, false, false, false], [1.0, 1.0, 1.0, 0.5, f32::NAN, 1.0]);
let still = Motion::default();
assert_eq!(mapping.apply(Axes { translation: [0, 350, 0], rotation: [0; 3] }).translation[1], -raw(PUSH).translation[1]);
assert_eq!(
mapping.apply(Axes { translation: [350, 0, 0], rotation: [0; 3] }).translation[0],
raw(Axes { translation: [350, 0, 0], rotation: [0; 3] }).translation[0]
);
assert_eq!(mapping.apply(Axes { translation: [0; 3], rotation: [350, 0, 0] }).rotation[0], 0.5);
assert_eq!(mapping.apply(Axes { translation: [0; 3], rotation: [0, 350, 0] }).rotation[1], 1.0);
assert_eq!(mapping.apply(Axes::default()), still);
let off = DriverMapping::default().with_user_axes([false; 6], [0.0; 6]);
assert_eq!(off.apply(PUSH), still);
}
const PUSH: Axes = Axes { translation: [0, 350, 0], rotation: [0; 3] };
fn wheel(dy: f32) -> egui::Event {