Compare commits

..

2 commits

Author SHA1 Message Date
griffi-gh b34f1a94b1 fix ctl speed 2024-02-15 01:54:31 +01:00
griffi-gh 1466f62b5a pl ctl 2024-02-15 01:51:41 +01:00
4 changed files with 337 additions and 312 deletions

View file

@ -1,297 +1,301 @@
use gilrs::{Gilrs, GamepadId, Button, Event, Axis}; use gilrs::{Gilrs, GamepadId, Button, Event, Axis};
use glam::{Vec2, DVec2, vec2, dvec2}; use glam::{Vec2, DVec2, vec2, dvec2};
use winit::{ use winit::{
keyboard::{KeyCode, PhysicalKey}, keyboard::{KeyCode, PhysicalKey},
event::{DeviceEvent, DeviceId, ElementState, TouchPhase} event::{DeviceEvent, DeviceId, ElementState, TouchPhase}
}; };
use hashbrown::HashMap; use hashbrown::HashMap;
use tinyset::{SetU32, SetU64}; use tinyset::{SetU32, SetU64};
use nohash_hasher::BuildNoHashHasher; use nohash_hasher::BuildNoHashHasher;
use shipyard::{AllStoragesView, Unique, View, IntoIter, UniqueViewMut, Workload, IntoWorkload, UniqueView, NonSendSync}; use shipyard::{AllStoragesView, Unique, View, IntoIter, UniqueViewMut, Workload, IntoWorkload, UniqueView, NonSendSync};
use crate::{ use crate::{
events::{InputDeviceEvent, TouchEvent}, events::{InputDeviceEvent, TouchEvent},
rendering::WindowSize rendering::WindowSize
}; };
#[derive(Unique, Clone, Copy, Default, Debug)] #[derive(Unique, Clone, Copy, Default, Debug)]
pub struct Inputs { pub struct Inputs {
pub movement: Vec2, pub movement: Vec2,
pub look: Vec2, pub look: Vec2,
pub action_a: bool, pub action_a: bool,
pub action_b: bool, pub action_b: bool,
} pub jump: bool,
}
#[derive(Unique, Clone, Copy, Default, Debug)]
pub struct PrevInputs(pub Inputs); #[derive(Unique, Clone, Copy, Default, Debug)]
pub struct PrevInputs(pub Inputs);
#[derive(Unique, Clone, Default, Debug)]
pub struct RawKbmInputState { #[derive(Unique, Clone, Default, Debug)]
pub keyboard_state: SetU32, pub struct RawKbmInputState {
pub button_state: [bool; 32], pub keyboard_state: SetU32,
pub mouse_delta: DVec2 pub button_state: [bool; 32],
} pub mouse_delta: DVec2
}
#[derive(Clone, Copy, Debug, Default)]
pub enum FingerCheck { #[derive(Clone, Copy, Debug, Default)]
#[default] pub enum FingerCheck {
Start, #[default]
Current, Start,
StartOrCurrent, Current,
StartAndCurrent, StartOrCurrent,
NotMoved, StartAndCurrent,
} NotMoved,
}
#[derive(Clone, Copy, Debug)]
pub struct Finger { #[derive(Clone, Copy, Debug)]
pub id: u64, pub struct Finger {
pub device_id: DeviceId, pub id: u64,
pub prev_position: DVec2, pub device_id: DeviceId,
pub start_position: DVec2, pub prev_position: DVec2,
pub current_position: DVec2, pub start_position: DVec2,
pub has_moved: bool, pub current_position: DVec2,
} pub has_moved: bool,
impl Finger { }
pub fn within_area(&self, area_pos: DVec2, area_size: DVec2, check: FingerCheck) -> bool { impl Finger {
let within_area = |pos: DVec2| -> bool { pub fn within_area(&self, area_pos: DVec2, area_size: DVec2, check: FingerCheck) -> bool {
((pos - area_pos).min_element() >= 0.) && let within_area = |pos: DVec2| -> bool {
((pos - (area_pos + area_size)).max_element() <= 0.) ((pos - area_pos).min_element() >= 0.) &&
}; ((pos - (area_pos + area_size)).max_element() <= 0.)
let start = within_area(self.start_position); };
let current = within_area(self.current_position); let start = within_area(self.start_position);
match check { let current = within_area(self.current_position);
FingerCheck::Start => start, match check {
FingerCheck::Current => current, FingerCheck::Start => start,
FingerCheck::StartOrCurrent => start || current, FingerCheck::Current => current,
FingerCheck::StartAndCurrent => start && current, FingerCheck::StartOrCurrent => start || current,
FingerCheck::NotMoved => current && !self.has_moved, FingerCheck::StartAndCurrent => start && current,
} FingerCheck::NotMoved => current && !self.has_moved,
} }
} }
}
#[derive(Unique, Clone, Default, Debug)]
pub struct RawTouchState { #[derive(Unique, Clone, Default, Debug)]
//TODO: handle multiple touch devices somehow pub struct RawTouchState {
pub fingers: HashMap<u64, Finger, BuildNoHashHasher<u64>> //TODO: handle multiple touch devices somehow
} pub fingers: HashMap<u64, Finger, BuildNoHashHasher<u64>>
}
impl RawTouchState {
pub fn query_area(&self, area_pos: DVec2, area_size: DVec2, check: FingerCheck) -> impl Iterator<Item = Finger> + '_ { impl RawTouchState {
self.fingers.iter().filter_map(move |(_, &finger)| { pub fn query_area(&self, area_pos: DVec2, area_size: DVec2, check: FingerCheck) -> impl Iterator<Item = Finger> + '_ {
finger.within_area(area_pos, area_size, check).then_some(finger) self.fingers.iter().filter_map(move |(_, &finger)| {
}) finger.within_area(area_pos, area_size, check).then_some(finger)
} })
} }
}
#[derive(Unique)]
pub struct GilrsWrapper(Option<Gilrs>); #[derive(Unique)]
pub struct GilrsWrapper(Option<Gilrs>);
#[derive(Unique, Default, Clone, Copy)]
pub struct ActiveGamepad(Option<GamepadId>); #[derive(Unique, Default, Clone, Copy)]
pub struct ActiveGamepad(Option<GamepadId>);
//maybe we should manage gamepad state ourselves just like keyboard?
//at least for the sake of consitency //maybe we should manage gamepad state ourselves just like keyboard?
//at least for the sake of consitency
fn process_events(
device_events: View<InputDeviceEvent>, fn process_events(
mut input_state: UniqueViewMut<RawKbmInputState>, device_events: View<InputDeviceEvent>,
) { mut input_state: UniqueViewMut<RawKbmInputState>,
input_state.mouse_delta = DVec2::ZERO; ) {
for event in device_events.iter() { input_state.mouse_delta = DVec2::ZERO;
match &event.event { for event in device_events.iter() {
DeviceEvent::MouseMotion { delta } => { match &event.event {
input_state.mouse_delta = DVec2::from(*delta); DeviceEvent::MouseMotion { delta } => {
}, input_state.mouse_delta = DVec2::from(*delta);
DeviceEvent::Key(input) => { },
if let PhysicalKey::Code(code) = input.physical_key { DeviceEvent::Key(input) => {
match input.state { if let PhysicalKey::Code(code) = input.physical_key {
ElementState::Pressed => input_state.keyboard_state.insert(code as u32), match input.state {
ElementState::Released => input_state.keyboard_state.remove(code as u32), ElementState::Pressed => input_state.keyboard_state.insert(code as u32),
}; ElementState::Released => input_state.keyboard_state.remove(code as u32),
} };
}, }
DeviceEvent::Button { button, state } => { },
if *button < 32 { DeviceEvent::Button { button, state } => {
input_state.button_state[*button as usize] = matches!(*state, ElementState::Pressed); if *button < 32 {
} input_state.button_state[*button as usize] = matches!(*state, ElementState::Pressed);
}, }
_ => () },
} _ => ()
} }
} }
}
fn process_touch_events(
touch_events: View<TouchEvent>, fn process_touch_events(
mut touch_state: UniqueViewMut<RawTouchState>, touch_events: View<TouchEvent>,
) { mut touch_state: UniqueViewMut<RawTouchState>,
for (_, finger) in &mut touch_state.fingers { ) {
finger.prev_position = finger.current_position; for (_, finger) in &mut touch_state.fingers {
} finger.prev_position = finger.current_position;
for event in touch_events.iter() { }
let position = dvec2(event.0.location.x, event.0.location.y); for event in touch_events.iter() {
match event.0.phase { let position = dvec2(event.0.location.x, event.0.location.y);
TouchPhase::Started => { match event.0.phase {
//println!("touch started: finger {}", event.0.id); TouchPhase::Started => {
touch_state.fingers.insert(event.0.id, Finger { //println!("touch started: finger {}", event.0.id);
id: event.0.id, touch_state.fingers.insert(event.0.id, Finger {
device_id: event.0.device_id, id: event.0.id,
start_position: position, device_id: event.0.device_id,
current_position: position, start_position: position,
prev_position: position, current_position: position,
has_moved: false prev_position: position,
}); has_moved: false
}, });
TouchPhase::Moved => { },
if let Some(finger) = touch_state.fingers.get_mut(&event.0.id) { TouchPhase::Moved => {
finger.has_moved = true; if let Some(finger) = touch_state.fingers.get_mut(&event.0.id) {
finger.current_position = position; finger.has_moved = true;
} finger.current_position = position;
}, }
TouchPhase::Ended | TouchPhase::Cancelled => { },
//println!("touch ended: finger {}", event.0.id); TouchPhase::Ended | TouchPhase::Cancelled => {
touch_state.fingers.remove(&event.0.id); //println!("touch ended: finger {}", event.0.id);
}, touch_state.fingers.remove(&event.0.id);
} },
} }
} }
}
fn process_gilrs_events(
mut gilrs: NonSendSync<UniqueViewMut<GilrsWrapper>>, fn process_gilrs_events(
mut active_gamepad: UniqueViewMut<ActiveGamepad> mut gilrs: NonSendSync<UniqueViewMut<GilrsWrapper>>,
) { mut active_gamepad: UniqueViewMut<ActiveGamepad>
if let Some(gilrs) = &mut gilrs.0 { ) {
while let Some(Event { id, event: _, time: _ }) = gilrs.next_event() { if let Some(gilrs) = &mut gilrs.0 {
active_gamepad.0 = Some(id); while let Some(Event { id, event: _, time: _ }) = gilrs.next_event() {
} active_gamepad.0 = Some(id);
} }
} }
}
fn input_start(
mut inputs: UniqueViewMut<Inputs>, fn input_start(
mut prev_inputs: UniqueViewMut<PrevInputs>, mut inputs: UniqueViewMut<Inputs>,
) { mut prev_inputs: UniqueViewMut<PrevInputs>,
prev_inputs.0 = *inputs; ) {
*inputs = Inputs::default(); prev_inputs.0 = *inputs;
} *inputs = Inputs::default();
}
fn update_input_state (
raw_inputs: UniqueView<RawKbmInputState>, fn update_input_state (
mut inputs: UniqueViewMut<Inputs>, raw_inputs: UniqueView<RawKbmInputState>,
) { mut inputs: UniqueViewMut<Inputs>,
inputs.movement += Vec2::new( ) {
raw_inputs.keyboard_state.contains(KeyCode::KeyD as u32) as u32 as f32 - inputs.movement += Vec2::new(
raw_inputs.keyboard_state.contains(KeyCode::KeyA as u32) as u32 as f32, raw_inputs.keyboard_state.contains(KeyCode::KeyD as u32) as u32 as f32 -
raw_inputs.keyboard_state.contains(KeyCode::KeyW as u32) as u32 as f32 - raw_inputs.keyboard_state.contains(KeyCode::KeyA as u32) as u32 as f32,
raw_inputs.keyboard_state.contains(KeyCode::KeyS as u32) as u32 as f32 raw_inputs.keyboard_state.contains(KeyCode::KeyW as u32) as u32 as f32 -
); raw_inputs.keyboard_state.contains(KeyCode::KeyS as u32) as u32 as f32
inputs.look += raw_inputs.mouse_delta.as_vec2(); );
inputs.action_a |= raw_inputs.button_state[0]; inputs.look += raw_inputs.mouse_delta.as_vec2();
inputs.action_b |= raw_inputs.button_state[1]; inputs.action_a |= raw_inputs.button_state[0];
} inputs.action_b |= raw_inputs.button_state[1];
inputs.jump |= raw_inputs.button_state[2];
fn update_input_state_gamepad ( }
gilrs: NonSendSync<UniqueView<GilrsWrapper>>,
active_gamepad: UniqueView<ActiveGamepad>, fn update_input_state_gamepad (
mut inputs: UniqueViewMut<Inputs>, gilrs: NonSendSync<UniqueView<GilrsWrapper>>,
) { active_gamepad: UniqueView<ActiveGamepad>,
if let Some(gilrs) = &gilrs.0 { mut inputs: UniqueViewMut<Inputs>,
if let Some(gamepad) = active_gamepad.0.map(|id| gilrs.gamepad(id)) { ) {
let left_stick = vec2(gamepad.value(Axis::LeftStickX), gamepad.value(Axis::LeftStickY)); if let Some(gilrs) = &gilrs.0 {
let right_stick = vec2(gamepad.value(Axis::RightStickX), -gamepad.value(Axis::RightStickY)); if let Some(gamepad) = active_gamepad.0.map(|id| gilrs.gamepad(id)) {
inputs.movement += left_stick; let left_stick = vec2(gamepad.value(Axis::LeftStickX), gamepad.value(Axis::LeftStickY));
inputs.look += right_stick; let right_stick = vec2(gamepad.value(Axis::RightStickX), -gamepad.value(Axis::RightStickY));
inputs.action_a |= gamepad.is_pressed(Button::South); inputs.movement += left_stick;
inputs.action_b |= gamepad.is_pressed(Button::East); //HACK: for now, we multiply look by 2 to make it feel more responsive
} inputs.look += right_stick * 2.;
} inputs.action_a |= gamepad.is_pressed(Button::West);
} inputs.action_b |= gamepad.is_pressed(Button::East);
inputs.jump |= gamepad.is_pressed(Button::South);
fn update_input_state_touch ( }
touch_state: UniqueView<RawTouchState>, }
win_size: UniqueView<WindowSize>, }
mut inputs: UniqueViewMut<Inputs>,
) { fn update_input_state_touch (
let w = win_size.0.as_dvec2(); touch_state: UniqueView<RawTouchState>,
win_size: UniqueView<WindowSize>,
//Movement mut inputs: UniqueViewMut<Inputs>,
if let Some(finger) = touch_state.query_area( ) {
dvec2(0., 0.), let w = win_size.0.as_dvec2();
dvec2(w.x / 2., w.y),
FingerCheck::Start //Movement
).next() { if let Some(finger) = touch_state.query_area(
inputs.movement += (((finger.current_position - finger.start_position) / (w.x / 4.)) * dvec2(1., -1.)).as_vec2(); dvec2(0., 0.),
} dvec2(w.x / 2., w.y),
FingerCheck::Start
//Action buttons ).next() {
let action_button_fingers = { inputs.movement += (((finger.current_position - finger.start_position) / (w.x / 4.)) * dvec2(1., -1.)).as_vec2();
let mut action_button_fingers = SetU64::new(); }
//Creates iterator of fingers that started within action button area //Action buttons
let action_finger_iter = || touch_state.query_area( let action_button_fingers = {
dvec2(w.x * 0.75, w.y * 0.666), let mut action_button_fingers = SetU64::new();
dvec2(w.x * 0.25, w.y * 0.333),
FingerCheck::Start //Creates iterator of fingers that started within action button area
); let action_finger_iter = || touch_state.query_area(
dvec2(w.x * 0.75, w.y * 0.666),
//Action button A dvec2(w.x * 0.25, w.y * 0.333),
inputs.action_a |= action_finger_iter().filter(|finger| finger.within_area( FingerCheck::Start
dvec2(w.x * (0.75 + 0.125), w.y * 0.666), );
dvec2(w.x * 0.125, w.y * 0.333),
FingerCheck::StartOrCurrent //Action button A
)).map(|x| action_button_fingers.insert(x.id)).next().is_some(); inputs.action_a |= action_finger_iter().filter(|finger| finger.within_area(
dvec2(w.x * (0.75 + 0.125), w.y * 0.666),
//Action button B dvec2(w.x * 0.125, w.y * 0.333),
inputs.action_b |= action_finger_iter().filter(|finger| finger.within_area( FingerCheck::StartOrCurrent
dvec2(w.x * 0.75, w.y * 0.666), )).map(|x| action_button_fingers.insert(x.id)).next().is_some();
dvec2(w.x * 0.125, w.y * 0.333),
FingerCheck::StartOrCurrent //Action button B
)).map(|x| action_button_fingers.insert(x.id)).next().is_some(); inputs.action_b |= action_finger_iter().filter(|finger| finger.within_area(
dvec2(w.x * 0.75, w.y * 0.666),
action_button_fingers dvec2(w.x * 0.125, w.y * 0.333),
}; FingerCheck::StartOrCurrent
)).map(|x| action_button_fingers.insert(x.id)).next().is_some();
//Camera controls
if let Some(finger) = touch_state.query_area( action_button_fingers
dvec2(w.x / 2., 0.), };
dvec2(w.x / 2., w.y),
FingerCheck::Start //Camera controls
).find(|x| !action_button_fingers.contains(x.id)) { if let Some(finger) = touch_state.query_area(
inputs.look += (((finger.current_position - finger.prev_position) / (w.x / 4.)) * 300.).as_vec2(); dvec2(w.x / 2., 0.),
} dvec2(w.x / 2., w.y),
} FingerCheck::Start
).find(|x| !action_button_fingers.contains(x.id)) {
fn input_end( inputs.look += (((finger.current_position - finger.prev_position) / (w.x / 4.)) * 300.).as_vec2();
mut inputs: UniqueViewMut<Inputs>, }
) { }
if inputs.movement.length() >= 1. {
inputs.movement = inputs.movement.normalize(); fn input_end(
} mut inputs: UniqueViewMut<Inputs>,
} ) {
if inputs.movement.length() >= 1. {
pub fn init_input ( inputs.movement = inputs.movement.normalize();
storages: AllStoragesView }
) { }
storages.add_unique_non_send_sync(GilrsWrapper(
Gilrs::new().map_err(|x| { pub fn init_input (
log::error!("Failed to initialize Gilrs"); storages: AllStoragesView
x ) {
}).ok() storages.add_unique_non_send_sync(GilrsWrapper(
)); Gilrs::new().map_err(|x| {
storages.add_unique(ActiveGamepad::default()); log::error!("Failed to initialize Gilrs");
storages.add_unique(Inputs::default()); x
storages.add_unique(PrevInputs::default()); }).ok()
storages.add_unique(RawKbmInputState::default()); ));
storages.add_unique(RawTouchState::default()); storages.add_unique(ActiveGamepad::default());
} storages.add_unique(Inputs::default());
storages.add_unique(PrevInputs::default());
pub fn process_inputs() -> Workload { storages.add_unique(RawKbmInputState::default());
( storages.add_unique(RawTouchState::default());
process_events, }
process_touch_events,
process_gilrs_events, pub fn process_inputs() -> Workload {
input_start, (
update_input_state, process_events,
update_input_state_touch, process_touch_events,
update_input_state_gamepad, process_gilrs_events,
input_end, input_start,
).into_sequential_workload() update_input_state,
} update_input_state_touch,
update_input_state_gamepad,
input_end,
).into_sequential_workload()
}

View file

@ -23,7 +23,7 @@ pub(crate) mod settings;
pub(crate) mod camera; pub(crate) mod camera;
pub(crate) mod events; pub(crate) mod events;
pub(crate) mod input; pub(crate) mod input;
pub(crate) mod fly_controller; pub(crate) mod player_controller;
pub(crate) mod block_placement; pub(crate) mod block_placement;
pub(crate) mod delta_time; pub(crate) mod delta_time;
pub(crate) mod cursor_lock; pub(crate) mod cursor_lock;
@ -57,7 +57,7 @@ use events::{
player_actions::generate_move_events, player_actions::generate_move_events,
}; };
use input::{init_input, process_inputs}; use input::{init_input, process_inputs};
use fly_controller::update_controllers; use player_controller::update_player_controllers;
use rendering::{ use rendering::{
Renderer, Renderer,
RenderTarget, RenderTarget,
@ -133,7 +133,7 @@ fn update() -> Workload {
update_loaded_world_around_player, update_loaded_world_around_player,
).into_sequential_workload().run_if(is_ingame_or_loading), ).into_sequential_workload().run_if(is_ingame_or_loading),
( (
update_controllers, update_player_controllers,
update_client_physics_late, update_client_physics_late,
generate_move_events, generate_move_events,
update_raycasts, update_raycasts,

View file

@ -12,7 +12,7 @@ use kubi_shared::{
use crate::{ use crate::{
camera::Camera, camera::Camera,
client_physics::ClPhysicsActor, client_physics::ClPhysicsActor,
fly_controller::FlyController, player_controller::PlayerController,
transform::Transform, transform::Transform,
world::raycast::LookingAtBlock world::raycast::LookingAtBlock
}; };
@ -31,7 +31,7 @@ pub fn spawn_player (
Health::new(PLAYER_HEALTH), Health::new(PLAYER_HEALTH),
Transform::default(), Transform::default(),
Camera::default(), Camera::default(),
FlyController, PlayerController::DEFAULT_FPS_CTL,
LookingAtBlock::default(), LookingAtBlock::default(),
PlayerHolding(Some(Block::Cobblestone)), PlayerHolding(Some(Block::Cobblestone)),
Username("LocalPlayer".into()), Username("LocalPlayer".into()),
@ -53,7 +53,7 @@ pub fn spawn_local_player_multiplayer (
init.health, init.health,
Transform(Mat4::from_rotation_translation(init.direction, init.position)), Transform(Mat4::from_rotation_translation(init.direction, init.position)),
Camera::default(), Camera::default(),
FlyController, PlayerController::DEFAULT_FPS_CTL,
LookingAtBlock::default(), LookingAtBlock::default(),
PlayerHolding::default(), PlayerHolding::default(),
),( ),(

View file

@ -3,10 +3,31 @@ use shipyard::{Component, View, ViewMut, IntoIter, UniqueView, Workload, IntoWor
use std::f32::consts::PI; use std::f32::consts::PI;
use crate::{transform::Transform, input::Inputs, settings::GameSettings, delta_time::DeltaTime}; use crate::{transform::Transform, input::Inputs, settings::GameSettings, delta_time::DeltaTime};
#[derive(Component)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FlyController; pub enum PlayerControllerType {
FlyCam,
FpsCtl,
}
pub fn update_controllers() -> Workload { #[derive(Component)]
pub struct PlayerController {
pub control_type: PlayerControllerType,
pub speed: f32,
}
impl PlayerController {
pub const DEFAULT_FLY_CAM: Self = Self {
control_type: PlayerControllerType::FlyCam,
speed: 30.,
};
pub const DEFAULT_FPS_CTL: Self = Self {
control_type: PlayerControllerType::FpsCtl,
speed: 10.,
};
}
pub fn update_player_controllers() -> Workload {
( (
update_look, update_look,
update_movement update_movement
@ -16,7 +37,7 @@ pub fn update_controllers() -> Workload {
const MAX_PITCH: f32 = PI/2. - 0.05; const MAX_PITCH: f32 = PI/2. - 0.05;
fn update_look( fn update_look(
controllers: View<FlyController>, controllers: View<PlayerController>,
mut transforms: ViewMut<Transform, track::All>, mut transforms: ViewMut<Transform, track::All>,
inputs: UniqueView<Inputs>, inputs: UniqueView<Inputs>,
settings: UniqueView<GameSettings>, settings: UniqueView<GameSettings>,
@ -36,18 +57,18 @@ fn update_look(
} }
fn update_movement( fn update_movement(
controllers: View<FlyController>, controllers: View<PlayerController>,
mut transforms: ViewMut<Transform, track::All>, mut transforms: ViewMut<Transform, track::All>,
inputs: UniqueView<Inputs>, inputs: UniqueView<Inputs>,
dt: UniqueView<DeltaTime>, dt: UniqueView<DeltaTime>,
) { ) {
if inputs.movement == Vec2::ZERO { return } if inputs.movement == Vec2::ZERO { return }
let movement = inputs.movement * 30. * dt.0.as_secs_f32(); let movement = inputs.movement * dt.0.as_secs_f32();
for (_, mut transform) in (&controllers, &mut transforms).iter() { for (ctl, mut transform) in (&controllers, &mut transforms).iter() {
let (scale, rotation, mut translation) = transform.0.to_scale_rotation_translation(); let (scale, rotation, mut translation) = transform.0.to_scale_rotation_translation();
let rotation_norm = rotation.normalize(); let rotation_norm = rotation.normalize();
translation += (rotation_norm * Vec3::NEG_Z).normalize() * movement.y; translation += (rotation_norm * Vec3::NEG_Z).normalize() * movement.y * ctl.speed;
translation += (rotation_norm * Vec3::X).normalize() * movement.x; translation += (rotation_norm * Vec3::X).normalize() * movement.x * ctl.speed;
transform.0 = Mat4::from_scale_rotation_translation(scale, rotation_norm, translation); transform.0 = Mat4::from_scale_rotation_translation(scale, rotation_norm, translation);
} }
} }