Something went wrong. Try again.
Homebrew 4D Game Engine In Rust, inspired by Engine4D
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998//! Camera controller for 4D Golf-style input handling//!//! Controls://! - W/S: Forward/backward (Z)//! - A/D: Left/right strafe (X)//! - Q/E: Ana/kata movement (W)//! - Space/Shift: Up/down (Y)//! - Mouse drag: 3D camera rotation//! - Right-click + drag: W-axis rotation
use crate::{ActionMap, CameraAction};use rust4d_math::Vec4;use winit::event::{ElementState, MouseButton};use winit::keyboard::KeyCode;
/// Camera controller for handling inputpub struct CameraController { // Movement state forward: bool, backward: bool, left: bool, right: bool, up: bool, down: bool, ana: bool, // Q - move toward +W (ana) kata: bool, // E - move toward -W (kata)
// Jump state (for physics-based movement) jump_pressed: bool,
// Mouse state mouse_pressed: bool, w_rotation_mode: bool, // Right-click held pending_yaw: f32, pending_pitch: f32,
// Input smoothing state smooth_yaw: f32, smooth_pitch: f32,
// Semantic key bindings action_map: ActionMap,
// Configuration pub move_speed: f32, pub w_move_speed: f32, pub mouse_sensitivity: f32, pub w_rotation_sensitivity: f32, pub smoothing_half_life: f32, // Exponential smoothing half-life in seconds pub smoothing_enabled: bool,}
impl Default for CameraController { fn default() -> Self { Self::new() }}
impl CameraController { pub fn new() -> Self { Self { forward: false, backward: false, left: false, right: false, up: false, down: false, ana: false, kata: false,
jump_pressed: false,
mouse_pressed: false, w_rotation_mode: false, pending_yaw: 0.0, pending_pitch: 0.0,
smooth_yaw: 0.0, smooth_pitch: 0.0,
move_speed: 3.0, w_move_speed: 2.0, mouse_sensitivity: 0.002, // Standard FPS sensitivity w_rotation_sensitivity: 0.005, smoothing_half_life: 0.05, // 50ms half-life when enabled smoothing_enabled: false, // Disabled by default for responsive FPS feel action_map: ActionMap::default(), } }
/// Replace the key/action binding map. pub fn with_action_map(mut self, action_map: ActionMap) -> Self { self.action_map = action_map; self }
/// Read the active key/action binding map. pub fn action_map(&self) -> &ActionMap { &self.action_map }
/// Mutate the active key/action binding map. pub fn action_map_mut(&mut self) -> &mut ActionMap { &mut self.action_map }
/// Process a semantic camera action. pub fn process_action(&mut self, action: CameraAction, state: ElementState) { let pressed = state == ElementState::Pressed; match action { CameraAction::MoveForward => self.forward = pressed, CameraAction::MoveBackward => self.backward = pressed, CameraAction::MoveLeft => self.left = pressed, CameraAction::MoveRight => self.right = pressed, CameraAction::MoveUp => { self.up = pressed; if pressed { self.jump_pressed = true; } } CameraAction::MoveDown => self.down = pressed, CameraAction::MoveAna => self.ana = pressed, CameraAction::MoveKata => self.kata = pressed, } }
/// Process keyboard input through the active [`ActionMap`]. pub fn process_keyboard(&mut self, key: KeyCode, state: ElementState) -> bool { let actions: Vec<_> = self.action_map.actions_for_key(key).collect(); for action in &actions { self.process_action(*action, state); } !actions.is_empty() }
/// Process mouse button input pub fn process_mouse_button(&mut self, button: MouseButton, state: ElementState) { let pressed = state == ElementState::Pressed;
match button { MouseButton::Left => { self.mouse_pressed = pressed; } MouseButton::Right => { self.w_rotation_mode = pressed; } _ => {} } }
/// Process mouse movement pub fn process_mouse_motion(&mut self, delta_x: f64, delta_y: f64) { self.pending_yaw += delta_x as f32; self.pending_pitch += delta_y as f32; }
/// Update the camera based on accumulated input /// /// When `cursor_captured` is true, free look is enabled (no click required). /// Returns the camera position for debug display. pub fn update<C: CameraControl>( &mut self, camera: &mut C, dt: f32, cursor_captured: bool, ) -> Vec4 { // Calculate movement deltas let fwd = (self.forward as i32 - self.backward as i32) as f32; let rgt = (self.right as i32 - self.left as i32) as f32; let up_down = (self.up as i32 - self.down as i32) as f32; let w = (self.ana as i32 - self.kata as i32) as f32;
// Apply movement camera.move_local_xz(fwd * self.move_speed * dt, rgt * self.move_speed * dt); camera.move_y(up_down * self.move_speed * dt); camera.move_w(w * self.w_move_speed * dt);
// Apply exponential smoothing to mouse input (engine4d-style) let (yaw_input, pitch_input) = if self.smoothing_enabled && dt > 0.0 { // Exponential smoothing: new = old * factor + input * (1 - factor) // factor = 2^(-dt / half_life), so smaller half_life = faster response let smooth_factor = 2.0f32.powf(-dt / self.smoothing_half_life); self.smooth_yaw = self.smooth_yaw * smooth_factor + self.pending_yaw * (1.0 - smooth_factor); self.smooth_pitch = self.smooth_pitch * smooth_factor + self.pending_pitch * (1.0 - smooth_factor); (self.smooth_yaw, self.smooth_pitch) } else { // No smoothing - use raw input (self.pending_yaw, self.pending_pitch) };
// Apply rotation // Free look when cursor is captured, or when mouse button is pressed let can_look = cursor_captured || self.mouse_pressed; if can_look || self.w_rotation_mode { if self.w_rotation_mode { // Right-click: W-rotation mode // Horizontal mouse: ZW rotation (roll_w) // Vertical mouse: XW rotation (roll_xw) camera.rotate_w(yaw_input * self.w_rotation_sensitivity); camera.rotate_xw(pitch_input * self.w_rotation_sensitivity); } else if can_look { // Free look: Standard 3D FPS rotation // Mouse right (positive delta_x) should turn camera right (positive yaw) // Mouse down (positive delta_y) should look down (negative pitch) camera.rotate_3d( yaw_input * self.mouse_sensitivity, -pitch_input * self.mouse_sensitivity, ); } }
// Reset pending mouse movement self.pending_yaw = 0.0; self.pending_pitch = 0.0;
camera.position() }
/// Check if any movement keys are pressed pub fn is_moving(&self) -> bool { self.forward || self.backward || self.left || self.right || self.up || self.down || self.ana || self.kata }
/// Toggle input smoothing on/off pub fn toggle_smoothing(&mut self) -> bool { self.smoothing_enabled = !self.smoothing_enabled; // Reset smoothing state when toggling self.smooth_yaw = 0.0; self.smooth_pitch = 0.0; self.smoothing_enabled }
/// Check if smoothing is enabled pub fn is_smoothing_enabled(&self) -> bool { self.smoothing_enabled }
/// Consume the jump input flag /// /// Returns true if jump was pressed since last consume, then clears the flag. /// Use this for physics-based movement where jump should trigger once per press. pub fn consume_jump(&mut self) -> bool { let was_pressed = self.jump_pressed; self.jump_pressed = false; was_pressed }
/// Get raw movement input for physics-based movement /// /// Returns (forward, right) input values in range -1.0 to 1.0. /// Forward is positive when W is pressed, negative when S is pressed. /// Right is positive when D is pressed, negative when A is pressed. pub fn get_movement_input(&self) -> (f32, f32) { let forward = (self.forward as i32 - self.backward as i32) as f32; let right = (self.right as i32 - self.left as i32) as f32; (forward, right) }
/// Get W-axis (ana/kata) movement input /// /// Returns input value in range -1.0 to 1.0. /// Positive when Q is pressed (ana), negative when E is pressed (kata). pub fn get_w_input(&self) -> f32 { (self.ana as i32 - self.kata as i32) as f32 }
/// Builder: set movement speed pub fn with_move_speed(mut self, speed: f32) -> Self { self.move_speed = speed; self }
/// Builder: set W-axis movement speed pub fn with_w_move_speed(mut self, speed: f32) -> Self { self.w_move_speed = speed; self }
/// Builder: set mouse sensitivity pub fn with_mouse_sensitivity(mut self, sensitivity: f32) -> Self { self.mouse_sensitivity = sensitivity; self }
/// Builder: set W-axis rotation sensitivity pub fn with_w_rotation_sensitivity(mut self, sensitivity: f32) -> Self { self.w_rotation_sensitivity = sensitivity; self }
/// Builder: set smoothing half-life (lower = more responsive) pub fn with_smoothing_half_life(mut self, half_life: f32) -> Self { self.smoothing_half_life = half_life; self }
/// Builder: enable or disable smoothing pub fn with_smoothing(mut self, enabled: bool) -> Self { self.smoothing_enabled = enabled; self }}
/// Trait for camera control/// Allows the controller to work with different camera implementationspub trait CameraControl { fn move_local_xz(&mut self, forward: f32, right: f32); fn move_y(&mut self, delta: f32); fn move_w(&mut self, delta: f32); fn rotate_3d(&mut self, delta_yaw: f32, delta_pitch: f32); fn rotate_w(&mut self, delta: f32); fn rotate_xw(&mut self, delta: f32); fn position(&self) -> Vec4;}
#[cfg(test)]mod tests { use super::*; use winit::event::ElementState; use winit::keyboard::KeyCode;
// ==================== Builder Pattern Tests ====================
#[test] fn test_default_values() { let controller = CameraController::new(); assert_eq!(controller.move_speed, 3.0); assert_eq!(controller.w_move_speed, 2.0); assert_eq!(controller.mouse_sensitivity, 0.002); assert_eq!(controller.w_rotation_sensitivity, 0.005); assert_eq!(controller.smoothing_half_life, 0.05); assert!(!controller.is_smoothing_enabled()); }
#[test] fn test_default_trait() { let controller = CameraController::default(); assert_eq!(controller.move_speed, 3.0); assert!(!controller.is_smoothing_enabled()); }
#[test] fn test_builder_move_speed() { let controller = CameraController::new().with_move_speed(5.0); assert_eq!(controller.move_speed, 5.0); }
#[test] fn test_builder_w_move_speed() { let controller = CameraController::new().with_w_move_speed(4.0); assert_eq!(controller.w_move_speed, 4.0); }
#[test] fn test_builder_mouse_sensitivity() { let controller = CameraController::new().with_mouse_sensitivity(0.005); assert_eq!(controller.mouse_sensitivity, 0.005); }
#[test] fn test_builder_w_rotation_sensitivity() { let controller = CameraController::new().with_w_rotation_sensitivity(0.01); assert_eq!(controller.w_rotation_sensitivity, 0.01); }
#[test] fn test_builder_smoothing_half_life() { let controller = CameraController::new().with_smoothing_half_life(0.1); assert_eq!(controller.smoothing_half_life, 0.1); }
#[test] fn test_builder_smoothing() { let controller = CameraController::new().with_smoothing(true); assert!(controller.is_smoothing_enabled()); }
#[test] fn test_builder_chaining() { let controller = CameraController::new() .with_move_speed(5.0) .with_w_move_speed(3.0) .with_mouse_sensitivity(0.005) .with_w_rotation_sensitivity(0.01) .with_smoothing(true) .with_smoothing_half_life(0.1);
assert_eq!(controller.move_speed, 5.0); assert_eq!(controller.w_move_speed, 3.0); assert_eq!(controller.mouse_sensitivity, 0.005); assert_eq!(controller.w_rotation_sensitivity, 0.01); assert!(controller.is_smoothing_enabled()); assert_eq!(controller.smoothing_half_life, 0.1); }
// ==================== Key State Tests ====================
#[test] fn test_initial_state_not_moving() { let controller = CameraController::new(); assert!(!controller.is_moving()); }
#[test] fn test_key_pressed_w() { let mut controller = CameraController::new();
// Initially not moving assert!(!controller.is_moving());
// Press W key let handled = controller.process_keyboard(KeyCode::KeyW, ElementState::Pressed); assert!(handled); assert!(controller.is_moving());
// Release W key let handled = controller.process_keyboard(KeyCode::KeyW, ElementState::Released); assert!(handled); assert!(!controller.is_moving()); }
#[test] fn test_key_pressed_s() { let mut controller = CameraController::new();
controller.process_keyboard(KeyCode::KeyS, ElementState::Pressed); assert!(controller.is_moving());
controller.process_keyboard(KeyCode::KeyS, ElementState::Released); assert!(!controller.is_moving()); }
#[test] fn test_key_pressed_a() { let mut controller = CameraController::new();
controller.process_keyboard(KeyCode::KeyA, ElementState::Pressed); assert!(controller.is_moving()); }
#[test] fn test_key_pressed_d() { let mut controller = CameraController::new();
controller.process_keyboard(KeyCode::KeyD, ElementState::Pressed); assert!(controller.is_moving()); }
#[test] fn test_key_pressed_q() { let mut controller = CameraController::new();
controller.process_keyboard(KeyCode::KeyQ, ElementState::Pressed); assert!(controller.is_moving()); assert_eq!(controller.get_w_input(), 1.0); }
#[test] fn test_key_pressed_e() { let mut controller = CameraController::new();
controller.process_keyboard(KeyCode::KeyE, ElementState::Pressed); assert!(controller.is_moving()); assert_eq!(controller.get_w_input(), -1.0); }
#[test] fn test_key_pressed_space() { let mut controller = CameraController::new();
controller.process_keyboard(KeyCode::Space, ElementState::Pressed); assert!(controller.is_moving()); }
#[test] fn test_key_pressed_shift() { let mut controller = CameraController::new();
controller.process_keyboard(KeyCode::ShiftLeft, ElementState::Pressed); assert!(controller.is_moving());
controller.process_keyboard(KeyCode::ShiftLeft, ElementState::Released); assert!(!controller.is_moving());
controller.process_keyboard(KeyCode::ShiftRight, ElementState::Pressed); assert!(controller.is_moving()); }
#[test] fn test_unhandled_key() { let mut controller = CameraController::new();
let handled = controller.process_keyboard(KeyCode::KeyX, ElementState::Pressed); assert!(!handled); assert!(!controller.is_moving()); }
#[test] fn test_multiple_keys() { let mut controller = CameraController::new();
controller.process_keyboard(KeyCode::KeyW, ElementState::Pressed); controller.process_keyboard(KeyCode::KeyA, ElementState::Pressed); assert!(controller.is_moving());
// Release one key, still moving controller.process_keyboard(KeyCode::KeyW, ElementState::Released); assert!(controller.is_moving());
// Release all keys controller.process_keyboard(KeyCode::KeyA, ElementState::Released); assert!(!controller.is_moving()); }
// ==================== Movement Direction Tests ====================
#[test] fn test_forward_movement() { let mut controller = CameraController::new(); controller.process_keyboard(KeyCode::KeyW, ElementState::Pressed);
let (forward, right) = controller.get_movement_input(); assert_eq!(forward, 1.0); assert_eq!(right, 0.0); }
#[test] fn test_backward_movement() { let mut controller = CameraController::new(); controller.process_keyboard(KeyCode::KeyS, ElementState::Pressed);
let (forward, right) = controller.get_movement_input(); assert_eq!(forward, -1.0); assert_eq!(right, 0.0); }
#[test] fn test_right_movement() { let mut controller = CameraController::new(); controller.process_keyboard(KeyCode::KeyD, ElementState::Pressed);
let (forward, right) = controller.get_movement_input(); assert_eq!(forward, 0.0); assert_eq!(right, 1.0); }
#[test] fn test_left_movement() { let mut controller = CameraController::new(); controller.process_keyboard(KeyCode::KeyA, ElementState::Pressed);
let (forward, right) = controller.get_movement_input(); assert_eq!(forward, 0.0); assert_eq!(right, -1.0); }
#[test] fn test_diagonal_movement_forward_right() { let mut controller = CameraController::new(); controller.process_keyboard(KeyCode::KeyW, ElementState::Pressed); controller.process_keyboard(KeyCode::KeyD, ElementState::Pressed);
let (forward, right) = controller.get_movement_input(); assert_eq!(forward, 1.0); assert_eq!(right, 1.0); }
#[test] fn test_opposing_keys_cancel_forward_backward() { let mut controller = CameraController::new(); controller.process_keyboard(KeyCode::KeyW, ElementState::Pressed); controller.process_keyboard(KeyCode::KeyS, ElementState::Pressed);
let (forward, _) = controller.get_movement_input(); assert_eq!(forward, 0.0); // Forward and back cancel }
#[test] fn test_opposing_keys_cancel_left_right() { let mut controller = CameraController::new(); controller.process_keyboard(KeyCode::KeyA, ElementState::Pressed); controller.process_keyboard(KeyCode::KeyD, ElementState::Pressed);
let (_, right) = controller.get_movement_input(); assert_eq!(right, 0.0); // Left and right cancel }
#[test] fn test_w_axis_input_ana() { let mut controller = CameraController::new(); controller.process_keyboard(KeyCode::KeyQ, ElementState::Pressed);
assert_eq!(controller.get_w_input(), 1.0); }
#[test] fn test_w_axis_input_kata() { let mut controller = CameraController::new(); controller.process_keyboard(KeyCode::KeyE, ElementState::Pressed);
assert_eq!(controller.get_w_input(), -1.0); }
#[test] fn test_w_axis_input_cancel() { let mut controller = CameraController::new(); controller.process_keyboard(KeyCode::KeyQ, ElementState::Pressed); controller.process_keyboard(KeyCode::KeyE, ElementState::Pressed);
assert_eq!(controller.get_w_input(), 0.0); }
// ==================== Jump Tests ====================
#[test] fn test_jump_initial_state() { let mut controller = CameraController::new();
// Initially no jump assert!(!controller.consume_jump()); }
#[test] fn test_jump_pressed() { let mut controller = CameraController::new();
controller.process_keyboard(KeyCode::Space, ElementState::Pressed);
// Jump should be consumable once assert!(controller.consume_jump()); // Second consume should return false assert!(!controller.consume_jump()); }
#[test] fn test_jump_press_and_release() { let mut controller = CameraController::new();
controller.process_keyboard(KeyCode::Space, ElementState::Pressed); controller.process_keyboard(KeyCode::Space, ElementState::Released);
// Jump should still be consumable (it was pressed once) assert!(controller.consume_jump()); assert!(!controller.consume_jump()); }
#[test] fn test_jump_multiple_presses() { let mut controller = CameraController::new();
// Press space twice without consuming controller.process_keyboard(KeyCode::Space, ElementState::Pressed); controller.process_keyboard(KeyCode::Space, ElementState::Released); controller.process_keyboard(KeyCode::Space, ElementState::Pressed);
// Should only get one jump per consume assert!(controller.consume_jump()); assert!(!controller.consume_jump()); }
// ==================== Mouse Input Tests ====================
#[test] fn test_mouse_motion_accumulation() { let mut controller = CameraController::new();
controller.process_mouse_motion(10.0, 5.0); controller.process_mouse_motion(5.0, 3.0);
// Verify pending values accumulated assert_eq!(controller.pending_yaw, 15.0); assert_eq!(controller.pending_pitch, 8.0); }
#[test] fn test_mouse_button_left() { let mut controller = CameraController::new();
controller.process_mouse_button(MouseButton::Left, ElementState::Pressed); assert!(controller.mouse_pressed);
controller.process_mouse_button(MouseButton::Left, ElementState::Released); assert!(!controller.mouse_pressed); }
#[test] fn test_mouse_button_right() { let mut controller = CameraController::new();
controller.process_mouse_button(MouseButton::Right, ElementState::Pressed); assert!(controller.w_rotation_mode);
controller.process_mouse_button(MouseButton::Right, ElementState::Released); assert!(!controller.w_rotation_mode); }
#[test] fn test_mouse_button_other() { let mut controller = CameraController::new();
// Middle button should not affect state controller.process_mouse_button(MouseButton::Middle, ElementState::Pressed); assert!(!controller.mouse_pressed); assert!(!controller.w_rotation_mode); }
// ==================== Smoothing Tests ====================
#[test] fn test_toggle_smoothing() { let mut controller = CameraController::new(); assert!(!controller.is_smoothing_enabled());
let result = controller.toggle_smoothing(); assert!(result); assert!(controller.is_smoothing_enabled());
let result = controller.toggle_smoothing(); assert!(!result); assert!(!controller.is_smoothing_enabled()); }
#[test] fn test_toggle_smoothing_resets_state() { let mut controller = CameraController::new();
// Set some smoothing state controller.smooth_yaw = 5.0; controller.smooth_pitch = 3.0;
// Toggle should reset state controller.toggle_smoothing();
assert_eq!(controller.smooth_yaw, 0.0); assert_eq!(controller.smooth_pitch, 0.0); }
// ==================== Mock Camera for Update Tests ====================
/// Mock camera that records all calls for testing struct MockCamera { pub position: Vec4, pub forward_moved: f32, pub right_moved: f32, pub y_moved: f32, pub w_moved: f32, pub yaw_rotated: f32, pub pitch_rotated: f32, pub w_rotated: f32, pub xw_rotated: f32, }
impl MockCamera { fn new() -> Self { Self { position: Vec4::ZERO, forward_moved: 0.0, right_moved: 0.0, y_moved: 0.0, w_moved: 0.0, yaw_rotated: 0.0, pitch_rotated: 0.0, w_rotated: 0.0, xw_rotated: 0.0, } } }
impl CameraControl for MockCamera { fn move_local_xz(&mut self, forward: f32, right: f32) { self.forward_moved += forward; self.right_moved += right; }
fn move_y(&mut self, delta: f32) { self.y_moved += delta; }
fn move_w(&mut self, delta: f32) { self.w_moved += delta; }
fn rotate_3d(&mut self, delta_yaw: f32, delta_pitch: f32) { self.yaw_rotated += delta_yaw; self.pitch_rotated += delta_pitch; }
fn rotate_w(&mut self, delta: f32) { self.w_rotated += delta; }
fn rotate_xw(&mut self, delta: f32) { self.xw_rotated += delta; }
fn position(&self) -> Vec4 { self.position } }
// ==================== Update Tests ====================
#[test] fn test_update_forward_movement() { let mut controller = CameraController::new().with_move_speed(10.0); let mut camera = MockCamera::new();
controller.process_keyboard(KeyCode::KeyW, ElementState::Pressed); controller.update(&mut camera, 0.1, false);
// forward = 1.0 * 10.0 * 0.1 = 1.0 assert!((camera.forward_moved - 1.0).abs() < 0.001); assert_eq!(camera.right_moved, 0.0); }
#[test] fn test_update_strafe_movement() { let mut controller = CameraController::new().with_move_speed(10.0); let mut camera = MockCamera::new();
controller.process_keyboard(KeyCode::KeyD, ElementState::Pressed); controller.update(&mut camera, 0.1, false);
assert_eq!(camera.forward_moved, 0.0); assert!((camera.right_moved - 1.0).abs() < 0.001); }
#[test] fn test_update_vertical_movement() { let mut controller = CameraController::new().with_move_speed(10.0); let mut camera = MockCamera::new();
controller.process_keyboard(KeyCode::Space, ElementState::Pressed); controller.update(&mut camera, 0.1, false);
assert!((camera.y_moved - 1.0).abs() < 0.001); }
#[test] fn test_update_w_movement() { let mut controller = CameraController::new().with_w_move_speed(10.0); let mut camera = MockCamera::new();
controller.process_keyboard(KeyCode::KeyQ, ElementState::Pressed); controller.update(&mut camera, 0.1, false);
// w = 1.0 * 10.0 * 0.1 = 1.0 assert!((camera.w_moved - 1.0).abs() < 0.001); }
#[test] fn test_update_no_rotation_without_capture() { let mut controller = CameraController::new(); let mut camera = MockCamera::new();
controller.process_mouse_motion(100.0, 50.0); controller.update(&mut camera, 0.1, false); // cursor not captured
// No rotation should occur without cursor captured or mouse pressed assert_eq!(camera.yaw_rotated, 0.0); assert_eq!(camera.pitch_rotated, 0.0); }
#[test] fn test_update_rotation_with_cursor_captured() { let mut controller = CameraController::new().with_mouse_sensitivity(0.01); let mut camera = MockCamera::new();
controller.process_mouse_motion(100.0, 50.0); controller.update(&mut camera, 0.1, true); // cursor captured
// yaw = 100.0 * 0.01 = 1.0 // pitch = -50.0 * 0.01 = -0.5 (note the sign flip in update) assert!((camera.yaw_rotated - 1.0).abs() < 0.001); assert!((camera.pitch_rotated - (-0.5)).abs() < 0.001); }
#[test] fn test_update_rotation_with_mouse_pressed() { let mut controller = CameraController::new().with_mouse_sensitivity(0.01); let mut camera = MockCamera::new();
controller.process_mouse_button(MouseButton::Left, ElementState::Pressed); controller.process_mouse_motion(100.0, 50.0); controller.update(&mut camera, 0.1, false); // cursor not captured but mouse pressed
assert!((camera.yaw_rotated - 1.0).abs() < 0.001); assert!((camera.pitch_rotated - (-0.5)).abs() < 0.001); }
#[test] fn test_update_w_rotation_mode() { let mut controller = CameraController::new().with_w_rotation_sensitivity(0.01); let mut camera = MockCamera::new();
controller.process_mouse_button(MouseButton::Right, ElementState::Pressed); controller.process_mouse_motion(100.0, 50.0); controller.update(&mut camera, 0.1, false);
// w_rotation = 100.0 * 0.01 = 1.0 // xw_rotation = 50.0 * 0.01 = 0.5 assert!((camera.w_rotated - 1.0).abs() < 0.001); assert!((camera.xw_rotated - 0.5).abs() < 0.001);
// 3D rotation should not be applied assert_eq!(camera.yaw_rotated, 0.0); assert_eq!(camera.pitch_rotated, 0.0); }
#[test] fn test_update_clears_pending_mouse() { let mut controller = CameraController::new(); let mut camera = MockCamera::new();
controller.process_mouse_motion(100.0, 50.0); controller.update(&mut camera, 0.1, true);
// Pending should be cleared after update assert_eq!(controller.pending_yaw, 0.0); assert_eq!(controller.pending_pitch, 0.0); }
#[test] fn test_update_smoothing_enabled() { let mut controller = CameraController::new() .with_smoothing(true) .with_smoothing_half_life(0.1) .with_mouse_sensitivity(0.01); let mut camera = MockCamera::new();
controller.process_mouse_motion(100.0, 50.0); controller.update(&mut camera, 0.016, true);
// With smoothing, the rotation should be less than direct input // because we only take a portion based on the smoothing factor assert!(camera.yaw_rotated.abs() < 1.0); // Less than full 100 * 0.01 assert!(camera.yaw_rotated.abs() > 0.0); // But still some rotation }
#[test] fn test_update_smoothing_disabled_direct() { let mut controller = CameraController::new() .with_smoothing(false) .with_mouse_sensitivity(0.01); let mut camera = MockCamera::new();
controller.process_mouse_motion(100.0, 50.0); controller.update(&mut camera, 0.016, true);
// Without smoothing, should get full rotation assert!((camera.yaw_rotated - 1.0).abs() < 0.001); }
#[test] fn test_custom_action_map_rebinds_forward() { let mut map = ActionMap::empty(); map.bind(KeyCode::ArrowUp, CameraAction::MoveForward); let mut controller = CameraController::new().with_action_map(map); let mut camera = MockCamera::new();
assert!(!controller.process_keyboard(KeyCode::KeyW, ElementState::Pressed)); assert!(controller.process_keyboard(KeyCode::ArrowUp, ElementState::Pressed)); assert_eq!(controller.get_movement_input(), (1.0, 0.0));
controller.update(&mut camera, 1.0, false); assert_eq!(camera.forward_moved, controller.move_speed); }
#[test] fn test_process_action_bypasses_keyboard_layout() { let mut controller = CameraController::new(); controller.process_action(CameraAction::MoveAna, ElementState::Pressed); assert_eq!(controller.get_w_input(), 1.0); controller.process_action(CameraAction::MoveAna, ElementState::Released); assert_eq!(controller.get_w_input(), 0.0); }
#[test] fn test_update_returns_camera_position() { let mut controller = CameraController::new(); let mut camera = MockCamera::new(); camera.position = Vec4::new(1.0, 2.0, 3.0, 4.0);
let pos = controller.update(&mut camera, 0.1, false);
assert_eq!(pos, Vec4::new(1.0, 2.0, 3.0, 4.0)); }}