Skip to main content

gcrecomp_runtime/input/
gamecube_mapping.rs

1// GameCube controller button mapping
2use crate::input::backends::{ControllerType, RawInput};
3use crate::input::controller::{GameCubeButtons, GameCubeInput};
4
5#[derive(Debug, Clone)]
6pub struct GameCubeMapping {
7    pub controller_type: ControllerType,
8    pub button_mappings: ButtonMappings,
9    pub stick_mappings: StickMappings,
10    pub trigger_mappings: TriggerMappings,
11    pub dead_zones: DeadZones,
12    pub sensitivity: Sensitivity,
13}
14
15#[derive(Debug, Clone)]
16pub struct ButtonMappings {
17    pub a: ButtonMapping,
18    pub b: ButtonMapping,
19    pub x: ButtonMapping,
20    pub y: ButtonMapping,
21    pub start: ButtonMapping,
22    pub d_up: ButtonMapping,
23    pub d_down: ButtonMapping,
24    pub d_left: ButtonMapping,
25    pub d_right: ButtonMapping,
26    pub l: ButtonMapping,
27    pub r: ButtonMapping,
28    pub z: ButtonMapping,
29}
30
31#[derive(Debug, Clone)]
32pub enum ButtonMapping {
33    Button(usize),
34    AxisPositive(usize),
35    AxisNegative(usize),
36    Trigger(usize, f32), // trigger index, threshold
37    None,
38}
39
40#[derive(Debug, Clone)]
41pub struct StickMappings {
42    pub left_stick: AxisMapping,
43    pub right_stick: AxisMapping,
44}
45
46#[derive(Debug, Clone)]
47pub struct AxisMapping {
48    pub x_axis: usize,
49    pub y_axis: usize,
50    pub invert_x: bool,
51    pub invert_y: bool,
52}
53
54#[derive(Debug, Clone)]
55pub struct TriggerMappings {
56    pub left_trigger: usize,
57    pub right_trigger: usize,
58}
59
60#[derive(Debug, Clone)]
61pub struct DeadZones {
62    pub left_stick: f32,
63    pub right_stick: f32,
64    pub left_trigger: f32,
65    pub right_trigger: f32,
66}
67
68#[derive(Debug, Clone)]
69pub struct Sensitivity {
70    pub left_stick: f32,
71    pub right_stick: f32,
72}
73
74impl GameCubeMapping {
75    pub fn default_for_controller(
76        controller_info: &crate::input::backends::ControllerInfo,
77    ) -> Result<Self> {
78        match controller_info.controller_type {
79            ControllerType::Xbox => Ok(Self::xbox_default()),
80            ControllerType::PlayStation => Ok(Self::playstation_default()),
81            ControllerType::SwitchPro => Ok(Self::switch_pro_default()),
82            _ => Ok(Self::generic_default()),
83        }
84    }
85
86    pub fn xbox_default() -> Self {
87        Self {
88            controller_type: ControllerType::Xbox,
89            button_mappings: ButtonMappings {
90                a: ButtonMapping::Button(0),        // A button
91                b: ButtonMapping::Button(1),        // B button
92                x: ButtonMapping::Button(2),        // X button
93                y: ButtonMapping::Button(3),        // Y button
94                start: ButtonMapping::Button(6),    // Menu button
95                d_up: ButtonMapping::Button(11),    // D-pad up
96                d_down: ButtonMapping::Button(12),  // D-pad down
97                d_left: ButtonMapping::Button(13),  // D-pad left
98                d_right: ButtonMapping::Button(14), // D-pad right
99                l: ButtonMapping::Trigger(4, 0.3),  // Left trigger
100                r: ButtonMapping::Trigger(5, 0.3),  // Right trigger
101                z: ButtonMapping::Button(4),        // Left bumper
102            },
103            stick_mappings: StickMappings {
104                left_stick: AxisMapping {
105                    x_axis: 0,
106                    y_axis: 1,
107                    invert_x: false,
108                    invert_y: true, // Y is typically inverted
109                },
110                right_stick: AxisMapping {
111                    x_axis: 2,
112                    y_axis: 3,
113                    invert_x: false,
114                    invert_y: true,
115                },
116            },
117            trigger_mappings: TriggerMappings {
118                left_trigger: 4,
119                right_trigger: 5,
120            },
121            dead_zones: DeadZones {
122                left_stick: 0.15,
123                right_stick: 0.15,
124                left_trigger: 0.1,
125                right_trigger: 0.1,
126            },
127            sensitivity: Sensitivity {
128                left_stick: 1.0,
129                right_stick: 1.0,
130            },
131        }
132    }
133
134    pub fn playstation_default() -> Self {
135        // Similar to Xbox but with different button indices
136        let mut xbox = Self::xbox_default();
137        xbox.controller_type = ControllerType::PlayStation;
138        xbox.button_mappings.start = ButtonMapping::Button(9); // Options button
139        xbox
140    }
141
142    pub fn switch_pro_default() -> Self {
143        let mut xbox = Self::xbox_default();
144        xbox.controller_type = ControllerType::SwitchPro;
145        // Switch Pro has similar layout to Xbox
146        xbox
147    }
148
149    pub fn generic_default() -> Self {
150        Self::xbox_default()
151    }
152
153    pub fn map_to_gamecube(&self, input: &RawInput) -> GameCubeInput {
154        let mut buttons = GameCubeButtons::default();
155
156        // Map buttons
157        buttons.a = self.get_button_state(&self.button_mappings.a, input);
158        buttons.b = self.get_button_state(&self.button_mappings.b, input);
159        buttons.x = self.get_button_state(&self.button_mappings.x, input);
160        buttons.y = self.get_button_state(&self.button_mappings.y, input);
161        buttons.start = self.get_button_state(&self.button_mappings.start, input);
162        buttons.d_up = self.get_button_state(&self.button_mappings.d_up, input);
163        buttons.d_down = self.get_button_state(&self.button_mappings.d_down, input);
164        buttons.d_left = self.get_button_state(&self.button_mappings.d_left, input);
165        buttons.d_right = self.get_button_state(&self.button_mappings.d_right, input);
166        buttons.l = self.get_button_state(&self.button_mappings.l, input);
167        buttons.r = self.get_button_state(&self.button_mappings.r, input);
168        buttons.z = self.get_button_state(&self.button_mappings.z, input);
169
170        // Map sticks with dead zones and sensitivity
171        let left_stick = self.map_stick(
172            &self.stick_mappings.left_stick,
173            &self.dead_zones.left_stick,
174            self.sensitivity.left_stick,
175            input,
176        );
177
178        let right_stick = self.map_stick(
179            &self.stick_mappings.right_stick,
180            &self.dead_zones.right_stick,
181            self.sensitivity.right_stick,
182            input,
183        );
184
185        // Map triggers
186        let left_trigger = self.map_trigger(
187            self.trigger_mappings.left_trigger,
188            self.dead_zones.left_trigger,
189            input,
190        );
191
192        let right_trigger = self.map_trigger(
193            self.trigger_mappings.right_trigger,
194            self.dead_zones.right_trigger,
195            input,
196        );
197
198        GameCubeInput {
199            buttons,
200            left_stick,
201            right_stick,
202            left_trigger,
203            right_trigger,
204        }
205    }
206
207    fn get_button_state(&self, mapping: &ButtonMapping, input: &RawInput) -> bool {
208        match mapping {
209            ButtonMapping::Button(idx) => input.buttons.get(*idx).copied().unwrap_or(false),
210            ButtonMapping::AxisPositive(idx) => {
211                input.axes.get(*idx).map(|&v| v > 0.5).unwrap_or(false)
212            }
213            ButtonMapping::AxisNegative(idx) => {
214                input.axes.get(*idx).map(|&v| v < -0.5).unwrap_or(false)
215            }
216            ButtonMapping::Trigger(idx, threshold) => input
217                .triggers
218                .get(*idx)
219                .map(|&v| v > *threshold)
220                .unwrap_or(false),
221            ButtonMapping::None => false,
222        }
223    }
224
225    fn map_stick(
226        &self,
227        axis_mapping: &AxisMapping,
228        dead_zone: &f32,
229        sensitivity: f32,
230        input: &RawInput,
231    ) -> (f32, f32) {
232        let x = input.axes.get(axis_mapping.x_axis).copied().unwrap_or(0.0);
233        let y = input.axes.get(axis_mapping.y_axis).copied().unwrap_or(0.0);
234
235        let mut x = if axis_mapping.invert_x { -x } else { x };
236        let mut y = if axis_mapping.invert_y { -y } else { y };
237
238        // Apply dead zone
239        let magnitude = (x * x + y * y).sqrt();
240        if magnitude < *dead_zone {
241            return (0.0, 0.0);
242        }
243
244        // Normalize and scale
245        if magnitude > 1.0 {
246            x /= magnitude;
247            y /= magnitude;
248        }
249
250        // Apply sensitivity
251        x *= sensitivity;
252        y *= sensitivity;
253
254        (x, y)
255    }
256
257    fn map_trigger(&self, trigger_idx: usize, dead_zone: f32, input: &RawInput) -> f32 {
258        let value = input.triggers.get(trigger_idx).copied().unwrap_or(0.0);
259        if value < dead_zone {
260            0.0
261        } else {
262            // Normalize from dead_zone to 1.0
263            (value - dead_zone) / (1.0 - dead_zone)
264        }
265    }
266}
267
268use anyhow::Result;