From 7a720b63d1fe4e2f50813d5c1a1d89579258d453 Mon Sep 17 00:00:00 2001 From: gennyble Date: Sat, 30 Nov 2024 04:20:28 -0600 Subject: Add 50% deadzone Also: - fixes my a mispelling - lets you erase with the South button - tunes thumbstick angle movement --- .vscode/settings.json | 5 +++ README.md | 3 +- TODO.md | 10 +++++ src/image.rs | 27 +++++++----- src/main.rs | 120 ++++++++++++++++++++++++++++++++++---------------- 5 files changed, 115 insertions(+), 50 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 TODO.md diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6b9efa1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "COLOUR" + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 8449bbb..7500868 100644 --- a/README.md +++ b/README.md @@ -1,2 +1 @@ -Written, initially, in a sprint. -Start: 2024, Nov 29 00:33 +that really etches my sketch. diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..9f4ea70 --- /dev/null +++ b/TODO.md @@ -0,0 +1,10 @@ +Keyboard Input + the main input method right now is spinning the two thumbsticks on + some kind of game controller, but you can't spin a keyboard key. + + so i was thinking: + the left stick, horizontal, could be asdf and the right cjkl; and + you would sort of pulse down the keys to imitate a spin. the timing + between the presses, your acceleration per se, would determine how + fast the line is being drawn. a-f is to the right and f-a is to the + left. j-; is up, ;-j is down. \ No newline at end of file diff --git a/src/image.rs b/src/image.rs index 0ce41d9..6f03db5 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,3 +1,5 @@ +use crate::Vec2; + #[allow(dead_code)] pub struct Image { width: u32, @@ -36,12 +38,16 @@ impl Image { self.height } - pub fn rect(&mut self, x: u32, y: u32, width: u32, height: u32, clr: Color) { - let x_start = x as usize; - let x_end = (x + width) as usize; + pub fn fill(&mut self, clr: Color) { + self.data.fill(clr.into()); + } + + pub fn rect(&mut self, pos: Vec2, dim: Vec2, clr: Color) { + let x_start = pos.x as usize; + let x_end = (pos.x + dim.x) as usize; for idx in x_start..x_end { - for idy in y as usize..y as usize + height as usize { + for idy in pos.y as usize..pos.y as usize + dim.y as usize { let data_idx = idx + idy * self.width as usize; self.data[data_idx] = clr.into(); } @@ -50,11 +56,11 @@ impl Image { /// Primitive, naive line drawing funciton. lerps the points together and /// draws a rect of the specified width at that location. - pub fn line(&mut self, x1: u32, y1: u32, x2: u32, y2: u32, width: u32, clr: Color) { - let start_x = x1.min(x2) as f32; - let start_y = y1.min(y2) as f32; - let end_x = x1.max(x2) as f32; - let end_y = y1.max(y2) as f32; + pub fn line(&mut self, p1: Vec2, p2: Vec2, width: u32, clr: Color) { + let start_x = p1.x.min(p2.x) as f32; + let start_y = p1.y.min(p2.y) as f32; + let end_x = p1.x.max(p2.x) as f32; + let end_y = p1.y.max(p2.y) as f32; tracing::trace!("start_x = {start_x} / end_x = {end_x}"); tracing::trace!("start_y = {start_y} / end_y = {end_y}"); @@ -63,11 +69,12 @@ impl Image { let dy = end_y - start_y; let long = dx.max(dy); + let dim = Vec2::new(width, width); for idx in 0..long as usize { let x = start_x + dx * (idx as f32 / long); let y = start_y + dy * (idx as f32 / long); - self.rect(x as u32, y as u32, width, width, clr); + self.rect(Vec2::new(x, y).as_u32(), dim, clr); } } } diff --git a/src/main.rs b/src/main.rs index 6d07411..c6563f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use std::{ time::{Duration, Instant}, }; -use gilrs::{Axis, Gilrs}; +use gilrs::{Axis, Button, Gilrs}; use image::Image; use softbuffer::{Context, Surface}; use tracing::level_filters::LevelFilter; @@ -36,18 +36,13 @@ fn main() { y: HEIGHT / 2.0, }, - dial: DialState { - left_x: 0.0, - left_y: 0.0, - right_x: 0.0, - right_y: 0.0, - }, + dial: DialState::default(), left_angle: 0.0, right_angle: 0.0, next_check: Instant::now(), }; - el.run_app(&mut etch); + el.run_app(&mut etch).unwrap(); } fn setup_logging() { @@ -59,11 +54,8 @@ fn setup_logging() { #[derive(Copy, Clone, Debug, Default)] struct DialState { - left_x: f32, - left_y: f32, - - right_x: f32, - right_y: f32, + left: Vec2, + right: Vec2, } struct SurfacedWindow { @@ -75,7 +67,7 @@ struct Etch { window: Option, gilrs: Gilrs, img: Image, - stylus: Vec2, + stylus: Vec2, dial: DialState, left_angle: f32, @@ -98,10 +90,20 @@ impl Etch { } match axis { - Axis::LeftStickX => self.dial.left_x = value * 100.0, - Axis::LeftStickY => self.dial.left_y = value * 100.0, - Axis::RightStickX => self.dial.right_x = value * 100.0, - Axis::RightStickY => self.dial.right_y = value * 100.0, + Axis::LeftStickX => self.dial.left.x = value * 100.0, + Axis::LeftStickY => self.dial.left.y = value * 100.0, + Axis::RightStickX => self.dial.right.x = value * 100.0, + Axis::RightStickY => self.dial.right.y = value * 100.0, + _ => (), + } + } + gilrs::EventType::ButtonPressed(btn, code) => { + tracing::trace!("Button press! {btn:?}"); + + match btn { + Button::South => { + self.img.fill(BACKGROUND_COLOUR.into()); + } _ => (), } } @@ -112,13 +114,14 @@ impl Etch { } // Why are my consts HERE of all places -const DIAL_SENSETIVITY: f32 = 2.0; +const DIAL_SENSITIVITY: f32 = 10.0; const WIDTH: f32 = 640.0; const HEIGHT: f32 = 480.0; // a very sublte gentle, dark-and-dull green const BACKGROUND_COLOUR: u32 = 0x00868886; const LINE_COLOUR: u32 = 0x00303230; +const STYLUS_COLOUR: u32 = 0x00a0a0a0; impl ApplicationHandler for Etch { fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { @@ -147,45 +150,60 @@ impl ApplicationHandler for Etch { // We check the state of the joystick at 40fps if self.next_check.elapsed() > Duration::from_millis(25) { - let left_angle = xy_to_deg(self.dial.left_x, self.dial.left_y); - let left_delta = if !left_angle.is_nan() { + let left_angle = xy_to_deg(self.dial.left.x, self.dial.left.y); + + let left_is_large = self.dial.left.mag() > 50.0; + let left_neither_nan = !left_angle.is_nan() && !self.left_angle.is_nan(); + + let left_delta = if left_is_large && left_neither_nan { let delta = angle_delta(left_angle, self.left_angle); - self.left_angle = left_angle; delta } else { 0.0 }; + self.left_angle = left_angle; + + let right_angle = xy_to_deg(self.dial.right.x, self.dial.right.y); - let right_angle = xy_to_deg(self.dial.right_x, self.dial.right_y); - let right_delta = if !right_angle.is_nan() { + let right_is_large = self.dial.right.mag() > 50.0; + let right_neither_nan = !right_angle.is_nan() && !self.right_angle.is_nan(); + + let right_delta = if right_is_large && right_neither_nan { let delta = angle_delta(right_angle, self.right_angle); - self.right_angle = right_angle; delta } else { 0.0 }; + self.right_angle = right_angle; tracing::info!( - "ANGLE {left_angle} // {left_delta}v -=- {right_angle} // {right_delta}v" + "ANGLE ({}) {left_angle} // {left_delta}v -=- ({}) {right_angle} // {right_delta}v", + self.dial.left.mag(), self.dial.right.mag() ); let stylus_prev = self.stylus; - let movement_x = left_delta / 10.0; - let movement_y = right_delta / 10.0; + let movement_x = left_delta / DIAL_SENSITIVITY; + let movement_y = right_delta / DIAL_SENSITIVITY; self.stylus.x = (self.stylus.x + movement_x).clamp(0.0, self.img.width() as f32); self.stylus.y = (self.stylus.y - movement_y).clamp(0.0, self.img.height() as f32); self.img.line( - self.stylus.x as u32, - self.stylus.y as u32, - stylus_prev.x as u32, - stylus_prev.y as u32, + self.stylus.as_u32(), + stylus_prev.as_u32(), 2, LINE_COLOUR.into(), ); + // Letting the line-draw take care of overdrawing the stylus + // worked well except going up for some reason, so we + // explicitly overdraw here. + self.img.rect(stylus_prev.as_u32(), Vec2::new(2, 2), LINE_COLOUR.into()); + + // And then draw the stylus + self.img.rect(self.stylus.as_u32(), Vec2::new(2, 2), STYLUS_COLOUR.into()); + tracing::info!("STYLUS: ({},{})", self.stylus.x, self.stylus.y); self.next_check = Instant::now(); @@ -252,10 +270,17 @@ fn xy_to_deg(x: f32, y: f32) -> f32 { /// and the left-hand side. Intelligently handles the zero-crossing. /// lhs should be the newer value, rhs the older. fn angle_delta(lhs: f32, rhs: f32) -> f32 { - if rhs >= 270.0 && lhs < 90.0 { + // If we move too fast, or flick the stick just right, these values can be + // bypassed. Perhaps we can check on either side of 180, but this is working + // for now. It used to be 270 and 90 for high/low, respectively, but it was + // getting a touch jumpy. + const HIGH: f32 = 225.0; + const LOW: f32 = 135.0; + + if rhs >= HIGH && lhs < LOW { // It is likely we crossed zero in the clockwise direction (lhs + 360.0) - rhs - } else if rhs < 90.0 && lhs > 270.0 { + } else if rhs < LOW && lhs > HIGH { // It is likely we crossed zero in the anti-clockwise direction lhs - (rhs + 360.0) } else { @@ -263,8 +288,27 @@ fn angle_delta(lhs: f32, rhs: f32) -> f32 { } } -#[derive(Copy, Clone, Debug)] -pub struct Vec2 { - x: f32, - y: f32, +#[derive(Copy, Clone, Debug, Default)] +pub struct Vec2 { + pub x: T, + pub y: T, +} + +impl Vec2 { + pub fn new(x: T, y: T) -> Self { + Self { x, y } + } +} + +impl Vec2 { + pub fn as_u32(&self) -> Vec2 { + Vec2 { + x: self.x as u32, + y: self.y as u32, + } + } + + pub fn mag(&self) -> f32 { + (self.x * self.x + self.y * self.y).sqrt() + } } -- cgit 1.4.1-3-g733a5