1use crate::engine::ecs::ComponentId;
2use crate::engine::ecs::World;
3use crate::engine::ecs::component::{
4 ComponentRef, ForwardAxis, InputComponent, InputTransformModeComponent, QueryRootMode,
5 RollAxis, TransformComponent, resolve_component_ref,
6};
7use crate::engine::ecs::system::System;
8use crate::engine::graphics::VisualWorld;
9use crate::engine::user_input::InputState;
10use crate::utils::math;
11use std::collections::HashMap;
12use winit::event::MouseButton;
13use winit::keyboard::{Key, NamedKey};
14
15#[derive(Debug, Default)]
20pub struct InputSystem {
21 inputs: Vec<ComponentId>,
22
23 fps_yaw_pitch_roll: HashMap<ComponentId, (f32, f32, f32)>,
26}
27
28impl InputSystem {
29 pub fn new() -> Self {
30 Self {
31 inputs: Vec::new(),
32 fps_yaw_pitch_roll: HashMap::new(),
33 }
34 }
35
36 pub fn register_input(&mut self, component: ComponentId) {
38 if !self.inputs.iter().any(|c| *c == component) {
39 self.inputs.push(component);
40 }
41 }
42
43 fn compute_rotation(
44 &self,
45 roll_axis: RollAxis,
46 input: &InputState,
47 dt_sec: f32,
48 rotation: &mut [f32; 4],
49 ) {
50 let q = input.key_down(&Key::Character("q".into()));
52 let e = input.key_down(&Key::Character("e".into()));
53
54 let (drag_dx, drag_dy) = input.mouse_drag_delta_button(MouseButton::Right);
56
57 const MOUSE_SENS_RAD_PER_PX: f32 = 0.003;
59 let yaw_delta = drag_dx * MOUSE_SENS_RAD_PER_PX;
60 let pitch_delta = drag_dy * MOUSE_SENS_RAD_PER_PX;
61
62 if yaw_delta != 0.0 {
64 let q_yaw = math::quat_from_axis_angle([0.0, 1.0, 0.0], yaw_delta);
65 *rotation = math::quat_mul(*rotation, q_yaw);
66 }
67 if pitch_delta != 0.0 {
68 let q_pitch = math::quat_from_axis_angle([1.0, 0.0, 0.0], pitch_delta);
69 *rotation = math::quat_mul(*rotation, q_pitch);
70 }
71
72 if q || e {
73 const ROT_SPEED_RAD_PER_SEC: f32 = 1.5;
74 let dir = (q as i32) as f32 - (e as i32) as f32;
75 let dtheta = dir * ROT_SPEED_RAD_PER_SEC * dt_sec;
76 let axis = match roll_axis {
77 RollAxis::X => [1.0, 0.0, 0.0],
78 RollAxis::Y => [0.0, 1.0, 0.0],
79 RollAxis::Z => [0.0, 0.0, 1.0],
80 };
81 let q_roll = math::quat_from_axis_angle(axis, dtheta);
82 *rotation = math::quat_mul(*rotation, q_roll);
83 }
84 }
85
86 fn compute_rotation_fps(
87 &mut self,
88 transform_cid: ComponentId,
89 roll_axis: RollAxis,
90 input: &InputState,
91 dt_sec: f32,
92 rotation: &mut [f32; 4],
93 ) {
94 let q = input.key_down(&Key::Character("q".into()));
96 let e = input.key_down(&Key::Character("e".into()));
97
98 let (drag_dx, drag_dy) = input.mouse_drag_delta_button(MouseButton::Right);
100
101 const MOUSE_SENS_RAD_PER_PX: f32 = 0.003;
103 let yaw_delta = drag_dx * MOUSE_SENS_RAD_PER_PX;
104 let pitch_delta = drag_dy * MOUSE_SENS_RAD_PER_PX;
105
106 let qe_delta = if q || e {
108 const ROT_SPEED_RAD_PER_SEC: f32 = 1.5;
109 let dir = (q as i32) as f32 - (e as i32) as f32;
110 dir * ROT_SPEED_RAD_PER_SEC * dt_sec
111 } else {
112 0.0
113 };
114
115 if yaw_delta == 0.0 && pitch_delta == 0.0 && qe_delta == 0.0 {
116 return;
117 }
118
119 let (mut yaw, mut pitch, mut roll) = self
121 .fps_yaw_pitch_roll
122 .get(&transform_cid)
123 .copied()
124 .unwrap_or_else(|| {
125 let right =
126 math::vec3_normalize(math::quat_rotate_vec3(*rotation, [1.0, 0.0, 0.0]));
127 let fwd = math::vec3_normalize(math::quat_rotate_vec3(*rotation, [0.0, 0.0, -1.0]));
128
129 let yaw = right[2].atan2(right[0]);
131 let pitch = fwd[1].clamp(-1.0, 1.0).asin();
133 (yaw, pitch, 0.0)
136 });
137
138 yaw += yaw_delta;
140 pitch += pitch_delta;
141
142 match roll_axis {
144 RollAxis::Y => yaw += qe_delta,
145 RollAxis::X => pitch += qe_delta,
146 RollAxis::Z => roll += qe_delta,
147 }
148
149 const MAX_PITCH: f32 = 1.55; pitch = pitch.clamp(-MAX_PITCH, MAX_PITCH);
151
152 self.fps_yaw_pitch_roll
154 .insert(transform_cid, (yaw, pitch, roll));
155
156 let q_yaw = math::quat_from_axis_angle([0.0, 1.0, 0.0], yaw);
160 let right = math::quat_rotate_vec3(q_yaw, [1.0, 0.0, 0.0]);
161 let q_pitch = math::quat_from_axis_angle(right, pitch);
162
163 let q_base = math::quat_mul(q_pitch, q_yaw);
164 let fwd_world = math::vec3_normalize(math::quat_rotate_vec3(q_base, [0.0, 0.0, -1.0]));
165 let q_bank = math::quat_from_axis_angle(fwd_world, roll);
166
167 *rotation = math::quat_mul(q_bank, q_base);
168 }
169
170 fn compute_translation(
171 &self,
172 forward_axis: ForwardAxis,
173 fps_rotation: bool,
174 fps_yaw: Option<f32>,
175 speed_units_per_sec: f32,
176 input: &InputState,
177 dt_sec: f32,
178 rotation: [f32; 4],
179 translation: &mut [f32; 3],
180 ) {
181 let w = input.key_down(&Key::Character("w".into()));
183 let a = input.key_down(&Key::Character("a".into()));
184 let s = input.key_down(&Key::Character("s".into()));
185 let d = input.key_down(&Key::Character("d".into()));
186 let r: bool = input.key_down(&Key::Character("r".into()));
187 let f: bool = input.key_down(&Key::Character("f".into()));
188
189 let speed_multiplier = if input.key_down(&Key::Named(NamedKey::Shift)) {
191 3.0
192 } else {
193 1.0
194 };
195
196 let speed = speed_units_per_sec * speed_multiplier * dt_sec;
197
198 match forward_axis {
199 ForwardAxis::Y => {
200 let mut dx = 0.0f32;
202 let mut dy = 0.0f32;
203
204 if w {
205 dy += 1.0;
206 }
207 if s {
208 dy -= 1.0;
209 }
210 if a {
211 dx -= 1.0;
212 }
213 if d {
214 dx += 1.0;
215 }
216
217 let len = (dx * dx + dy * dy).sqrt();
219 if len > 0.0 {
220 dx /= len;
221 dy /= len;
222 }
223
224 let v = math::quat_rotate_vec3(rotation, [dx, dy, 0.0]);
226 translation[0] += v[0] * speed;
227 translation[1] += v[1] * speed;
228 }
229
230 ForwardAxis::Z => {
231 let mut dx = 0.0f32;
232 let mut dy: f32 = 0.0f32;
233 let mut dz = 0.0f32;
234
235 if a {
236 dx -= 1.0;
237 }
238 if d {
239 dx += 1.0;
240 }
241 if r {
242 dy += 1.0;
243 }
244 if f {
245 dy -= 1.0;
246 }
247 if w {
248 dz -= 1.0;
249 }
250 if s {
251 dz += 1.0;
252 }
253
254 let len = (dx * dx + dy * dy + dz * dz).sqrt();
256 if len > 0.0 {
257 dx /= len;
258 dy /= len;
259 dz /= len;
260 }
261
262 if fps_rotation {
263 let yaw = fps_yaw.unwrap_or_else(|| {
265 let right = math::quat_rotate_vec3(rotation, [1.0, 0.0, 0.0]);
266 right[2].atan2(right[0])
267 });
268 let q_yaw = math::quat_from_axis_angle([0.0, 1.0, 0.0], yaw);
269 let v = math::quat_rotate_vec3(q_yaw, [dx, 0.0, dz]);
270 translation[0] += v[0] * speed;
271 translation[1] += dy * speed;
272 translation[2] += v[2] * speed;
273 } else {
274 let v = math::quat_rotate_vec3(rotation, [dx, dy, dz]);
276 translation[0] += v[0] * speed;
277 translation[1] += v[1] * speed;
278 translation[2] += v[2] * speed;
279 }
280 }
281 }
282 }
283
284 fn resolve_translation_basis_rotation(
285 &self,
286 world: &World,
287 mode_component: Option<ComponentId>,
288 source: Option<&ComponentRef>,
289 fallback_rotation: [f32; 4],
290 ) -> [f32; 4] {
291 let Some(source) = source else {
292 return fallback_rotation;
293 };
294 let Some(target) =
295 resolve_component_ref(world, source, mode_component, QueryRootMode::SelfSubtree)
296 else {
297 return fallback_rotation;
298 };
299 self.nearest_transform_world_rotation(world, target)
300 .unwrap_or(fallback_rotation)
301 }
302
303 fn nearest_transform_world_rotation(
304 &self,
305 world: &World,
306 start: ComponentId,
307 ) -> Option<[f32; 4]> {
308 let mut current = Some(start);
309 while let Some(component) = current {
310 if let Some(transform) = world.get_component_by_id_as::<TransformComponent>(component) {
311 return Some(math::mat_to_quat(transform.transform.matrix_world));
312 }
313 current = world.parent_of(component);
314 }
315 None
316 }
317
318 pub fn process_input(
323 &mut self,
324 world: &mut World,
325 input: &InputState,
326 emit: &mut dyn crate::engine::ecs::SignalEmitter,
327 dt_sec: f32,
328 ) {
329 let any_move = input.key_down(&Key::Character("w".into()))
331 || input.key_down(&Key::Character("a".into()))
332 || input.key_down(&Key::Character("s".into()))
333 || input.key_down(&Key::Character("d".into()))
334 || input.key_down(&Key::Character("r".into()))
335 || input.key_down(&Key::Character("f".into()))
336 || input.key_down(&Key::Character("q".into()))
337 || input.key_down(&Key::Character("e".into()));
338
339 let any_drag = input.mouse_dragging_button(MouseButton::Right);
340
341 if !any_move && !any_drag {
342 return;
343 }
344
345 let inputs = self.inputs.clone();
346 for input_cid in inputs {
347 let speed_units_per_sec =
348 match world.get_component_by_id_as::<InputComponent>(input_cid) {
349 Some(input_comp) => input_comp.speed,
350 None => continue,
351 };
352
353 let transform_child = world.children_of(input_cid).iter().copied().find(|&cid| {
355 world
356 .get_component_by_id_as::<TransformComponent>(cid)
357 .is_some()
358 });
359
360 let (
362 mode_component,
363 forward_axis,
364 roll_axis,
365 rotation_enabled,
366 fps_rotation,
367 translation_basis_source,
368 ) = world
369 .children_of(input_cid)
370 .iter()
371 .copied()
372 .find_map(|cid| {
373 world
374 .get_component_by_id_as::<InputTransformModeComponent>(cid)
375 .map(|m| {
376 (
377 Some(cid),
378 m.forward_axis,
379 m.roll_axis,
380 m.rotation_enabled,
381 m.fps_rotation,
382 m.translation_basis_source.clone(),
383 )
384 })
385 })
386 .unwrap_or((None, ForwardAxis::Y, RollAxis::Z, true, false, None));
387
388 let Some(transform_cid) = transform_child else {
389 continue;
390 };
391
392 let external_basis_rotation = translation_basis_source.as_ref().map(|source| {
393 self.resolve_translation_basis_rotation(
394 world,
395 mode_component,
396 Some(source),
397 [0.0, 0.0, 0.0, 1.0],
398 )
399 });
400
401 if let Some(transform_comp_mut) =
402 world.get_component_by_id_as_mut::<TransformComponent>(transform_cid)
403 {
404 if rotation_enabled && fps_rotation {
405 self.compute_rotation_fps(
406 transform_cid,
407 roll_axis,
408 input,
409 dt_sec,
410 &mut transform_comp_mut.transform.rotation,
411 );
412 } else if rotation_enabled {
413 self.compute_rotation(
414 roll_axis,
415 input,
416 dt_sec,
417 &mut transform_comp_mut.transform.rotation,
418 );
419 }
420 let fps_yaw = if fps_rotation {
421 self.fps_yaw_pitch_roll
422 .get(&transform_cid)
423 .map(|(y, _, _)| *y)
424 } else {
425 None
426 };
427 let translation_basis_rotation =
428 external_basis_rotation.unwrap_or(transform_comp_mut.transform.rotation);
429 self.compute_translation(
430 forward_axis,
431 fps_rotation,
432 fps_yaw,
433 speed_units_per_sec,
434 input,
435 dt_sec,
436 translation_basis_rotation,
437 &mut transform_comp_mut.transform.translation,
438 );
439
440 transform_comp_mut.transform.recompute_model();
441 emit.push_intent_now(
442 transform_cid,
443 crate::engine::ecs::IntentValue::UpdateTransform {
444 component_ids: vec![transform_cid],
445 translation: transform_comp_mut.transform.translation,
446 rotation_quat_xyzw: transform_comp_mut.transform.rotation,
447 scale: transform_comp_mut.transform.scale,
448 },
449 );
450 }
451 }
452 }
453}
454
455impl System for InputSystem {
456 fn tick(
457 &mut self,
458 _world: &mut World,
459 _visuals: &mut VisualWorld,
460 _input: &InputState,
461 _dt_sec: f32,
462 ) {
463 }
465}