use crate::ecs::world::World;
use rapier3d::prelude::*;
pub fn character_controller_input_system(world: &mut World) {
#[cfg(feature = "egui")]
{
#[cfg(feature = "openxr")]
let has_xr_input = world.resources.xr.input.is_some();
#[cfg(not(feature = "openxr"))]
let has_xr_input = false;
if !has_xr_input
&& let Some(gui_state) = &mut world.resources.user_interface.state
&& gui_state.egui_ctx().egui_wants_keyboard_input()
{
return;
}
}
let entities: Vec<_> = world
.core
.query_entities(crate::ecs::world::CHARACTER_CONTROLLER)
.collect();
let delta_time = world.resources.physics.fixed_timestep;
#[cfg(feature = "openxr")]
let xr_input = world.resources.xr.input.clone();
for entity in entities {
let (
speed,
acceleration,
mut velocity,
mut can_jump,
jump_impulse,
crouch_enabled,
crouch_speed_multiplier,
sprint_speed_multiplier,
was_crouching,
was_sprinting,
engine_input_enabled,
) = {
let Some(character_controller) = world.core.get_character_controller(entity) else {
continue;
};
(
character_controller.max_speed,
character_controller.acceleration,
character_controller.velocity,
character_controller.can_jump,
character_controller.jump_impulse,
character_controller.crouch_enabled,
character_controller.crouch_speed_multiplier,
character_controller.sprint_speed_multiplier,
character_controller.is_crouching,
character_controller.is_sprinting,
character_controller.engine_input_enabled,
)
};
let (
left_key_pressed,
right_key_pressed,
forward_key_pressed,
backward_key_pressed,
jump_key_pressed,
crouch_key_pressed,
sprint_key_pressed,
) = {
let keyboard = &world.resources.input.keyboard;
(
keyboard.is_key_pressed(winit::keyboard::KeyCode::KeyA),
keyboard.is_key_pressed(winit::keyboard::KeyCode::KeyD),
keyboard.is_key_pressed(winit::keyboard::KeyCode::KeyW),
keyboard.is_key_pressed(winit::keyboard::KeyCode::KeyS),
keyboard.is_key_pressed(winit::keyboard::KeyCode::Space),
keyboard.is_key_pressed(winit::keyboard::KeyCode::ControlLeft)
|| keyboard.is_key_pressed(winit::keyboard::KeyCode::KeyC),
keyboard.is_key_pressed(winit::keyboard::KeyCode::ShiftLeft),
)
};
#[cfg(feature = "gamepad")]
let (gamepad_movement, gamepad_jump, gamepad_crouch, gamepad_sprint) =
if let Some(gamepad) = crate::ecs::input::queries::query_active_gamepad(world) {
let left_stick_x = gamepad.value(gilrs::Axis::LeftStickX);
let left_stick_y = gamepad.value(gilrs::Axis::LeftStickY);
let jump_button = gamepad.is_pressed(gilrs::Button::South);
let crouch_button = gamepad.is_pressed(gilrs::Button::East);
let sprint_button = gamepad.is_pressed(gilrs::Button::LeftThumb);
let deadzone = 0.15;
let left_stick_magnitude =
(left_stick_x * left_stick_x + left_stick_y * left_stick_y).sqrt();
let movement = if left_stick_magnitude > deadzone {
let normalized_magnitude = (left_stick_magnitude - deadzone) / (1.0 - deadzone);
nalgebra_glm::vec2(left_stick_x, left_stick_y) * normalized_magnitude
} else {
nalgebra_glm::vec2(0.0, 0.0)
};
(movement, jump_button, crouch_button, sprint_button)
} else {
(nalgebra_glm::vec2(0.0, 0.0), false, false, false)
};
#[cfg(not(feature = "gamepad"))]
let (gamepad_movement, gamepad_jump, gamepad_crouch, gamepad_sprint) =
(nalgebra_glm::vec2(0.0, 0.0), false, false, false);
#[cfg(feature = "openxr")]
let (xr_movement, xr_jump, xr_crouch, xr_sprint, xr_combined_yaw) =
if let Some(ref xr) = xr_input {
let deadzone = 0.15;
let stick_magnitude =
(xr.thumbstick.x * xr.thumbstick.x + xr.thumbstick.y * xr.thumbstick.y).sqrt();
let movement = if stick_magnitude > deadzone {
let normalized_magnitude = (stick_magnitude - deadzone) / (1.0 - deadzone);
nalgebra_glm::vec2(xr.thumbstick.x, xr.thumbstick.y) * normalized_magnitude
} else {
nalgebra_glm::vec2(0.0, 0.0)
};
let combined_yaw = xr.head_yaw + xr.player_yaw;
(
movement,
xr.a_button,
xr.b_button,
xr.left_thumbstick_click,
Some(combined_yaw),
)
} else {
(nalgebra_glm::vec2(0.0, 0.0), false, false, false, None)
};
#[cfg(feature = "openxr")]
let (forward, right) = if let Some(yaw) = xr_combined_yaw {
let forward = nalgebra_glm::vec3(-yaw.sin(), 0.0, -yaw.cos());
let right = nalgebra_glm::vec3(yaw.cos(), 0.0, -yaw.sin());
(forward, right)
} else if let Some(camera_entity) = world.resources.active_camera {
if let Some(camera_transform) = world.core.get_local_transform(camera_entity) {
let forward_vec = camera_transform.forward_vector();
let right_vec = camera_transform.right_vector();
let forward_horizontal =
nalgebra_glm::normalize(&nalgebra_glm::vec3(forward_vec.x, 0.0, forward_vec.z));
let right_horizontal =
nalgebra_glm::normalize(&nalgebra_glm::vec3(right_vec.x, 0.0, right_vec.z));
(forward_horizontal, right_horizontal)
} else {
(
nalgebra_glm::vec3(0.0, 0.0, -1.0),
nalgebra_glm::vec3(1.0, 0.0, 0.0),
)
}
} else {
(
nalgebra_glm::vec3(0.0, 0.0, -1.0),
nalgebra_glm::vec3(1.0, 0.0, 0.0),
)
};
#[cfg(not(feature = "openxr"))]
let (forward, right) = if let Some(camera_entity) = world.resources.active_camera {
if let Some(camera_transform) = world.core.get_local_transform(camera_entity) {
let forward_vec = camera_transform.forward_vector();
let right_vec = camera_transform.right_vector();
let forward_horizontal =
nalgebra_glm::normalize(&nalgebra_glm::vec3(forward_vec.x, 0.0, forward_vec.z));
let right_horizontal =
nalgebra_glm::normalize(&nalgebra_glm::vec3(right_vec.x, 0.0, right_vec.z));
(forward_horizontal, right_horizontal)
} else {
(
nalgebra_glm::vec3(0.0, 0.0, -1.0),
nalgebra_glm::vec3(1.0, 0.0, 0.0),
)
}
} else {
(
nalgebra_glm::vec3(0.0, 0.0, -1.0),
nalgebra_glm::vec3(1.0, 0.0, 0.0),
)
};
let mut movement_direction = nalgebra_glm::vec3(0.0, 0.0, 0.0);
if forward_key_pressed {
movement_direction += forward;
}
if backward_key_pressed {
movement_direction -= forward;
}
if left_key_pressed {
movement_direction -= right;
}
if right_key_pressed {
movement_direction += right;
}
if nalgebra_glm::length(&gamepad_movement) > 0.0 {
movement_direction = forward * gamepad_movement.y + right * gamepad_movement.x;
}
#[cfg(feature = "openxr")]
if nalgebra_glm::length(&xr_movement) > 0.0 {
movement_direction = forward * xr_movement.y + right * xr_movement.x;
}
if nalgebra_glm::length(&movement_direction) > 0.0 {
movement_direction = nalgebra_glm::normalize(&movement_direction);
}
let has_movement_input = nalgebra_glm::length(&movement_direction) > 0.0;
let (is_crouching, is_sprinting) = if engine_input_enabled {
#[cfg(feature = "openxr")]
let crouch_input_pressed = crouch_key_pressed || gamepad_crouch || xr_crouch;
#[cfg(not(feature = "openxr"))]
let crouch_input_pressed = crouch_key_pressed || gamepad_crouch;
let crouching = crouch_enabled && crouch_input_pressed && (can_jump || was_crouching);
#[cfg(feature = "openxr")]
let sprint_input_pressed = sprint_key_pressed || gamepad_sprint || xr_sprint;
#[cfg(not(feature = "openxr"))]
let sprint_input_pressed = sprint_key_pressed || gamepad_sprint;
let sprinting = sprint_input_pressed && has_movement_input && !crouching;
(crouching, sprinting)
} else {
(was_crouching, was_sprinting)
};
let effective_speed = if is_crouching {
speed * crouch_speed_multiplier
} else if is_sprinting {
speed * sprint_speed_multiplier
} else {
speed
};
let pre_horizontal_speed =
nalgebra_glm::length(&nalgebra_glm::vec3(velocity.x, 0.0, velocity.z));
if pre_horizontal_speed <= effective_speed {
let target_velocity = movement_direction * effective_speed;
let velocity_change = (target_velocity
- nalgebra_glm::vec3(velocity.x, 0.0, velocity.z))
* acceleration
* delta_time;
velocity.x += velocity_change.x;
velocity.z += velocity_change.z;
let horizontal_speed =
nalgebra_glm::length(&nalgebra_glm::vec3(velocity.x, 0.0, velocity.z));
if horizontal_speed > effective_speed {
let normalized =
nalgebra_glm::normalize(&nalgebra_glm::vec3(velocity.x, 0.0, velocity.z));
velocity.x = normalized.x * effective_speed;
velocity.z = normalized.z * effective_speed;
}
} else if pre_horizontal_speed <= effective_speed * 2.0 {
let current_horizontal = nalgebra_glm::vec3(velocity.x, 0.0, velocity.z);
let target_velocity = if has_movement_input {
movement_direction * effective_speed
} else if pre_horizontal_speed > 0.01 {
nalgebra_glm::normalize(¤t_horizontal) * effective_speed
} else {
nalgebra_glm::Vec3::zeros()
};
let velocity_change =
(target_velocity - current_horizontal) * acceleration * delta_time;
velocity.x += velocity_change.x;
velocity.z += velocity_change.z;
let horizontal_speed =
nalgebra_glm::length(&nalgebra_glm::vec3(velocity.x, 0.0, velocity.z));
if horizontal_speed > pre_horizontal_speed {
let normalized =
nalgebra_glm::normalize(&nalgebra_glm::vec3(velocity.x, 0.0, velocity.z));
velocity.x = normalized.x * pre_horizontal_speed;
velocity.z = normalized.z * pre_horizontal_speed;
}
} else if has_movement_input {
let steer_accel = acceleration * 0.15;
let current_horizontal = nalgebra_glm::vec3(velocity.x, 0.0, velocity.z);
let target_velocity = movement_direction * effective_speed;
let velocity_change = (target_velocity - current_horizontal) * steer_accel * delta_time;
velocity.x += velocity_change.x;
velocity.z += velocity_change.z;
let horizontal_speed =
nalgebra_glm::length(&nalgebra_glm::vec3(velocity.x, 0.0, velocity.z));
if horizontal_speed > pre_horizontal_speed {
let normalized =
nalgebra_glm::normalize(&nalgebra_glm::vec3(velocity.x, 0.0, velocity.z));
velocity.x = normalized.x * pre_horizontal_speed;
velocity.z = normalized.z * pre_horizontal_speed;
}
}
if engine_input_enabled {
#[cfg(feature = "openxr")]
let jump_pressed = jump_key_pressed || gamepad_jump || xr_jump;
#[cfg(not(feature = "openxr"))]
let jump_pressed = jump_key_pressed || gamepad_jump;
if jump_pressed && can_jump {
velocity.y = jump_impulse;
can_jump = false;
}
}
if let Some(controller) = world.core.get_character_controller_mut(entity) {
controller.velocity = velocity;
controller.can_jump = can_jump;
controller.is_crouching = is_crouching;
controller.is_sprinting = is_sprinting;
if is_crouching {
controller.shape = super::ColliderShape::Capsule {
half_height: controller.crouching_half_height,
radius: match controller.shape {
super::ColliderShape::Capsule { radius, .. } => radius,
_ => 0.3,
},
};
} else {
controller.shape = super::ColliderShape::Capsule {
half_height: controller.standing_half_height,
radius: match controller.shape {
super::ColliderShape::Capsule { radius, .. } => radius,
_ => 0.3,
},
};
}
}
}
}
pub fn character_controller_system(world: &mut World) {
let entities: Vec<_> = world
.core
.query_entities(
crate::ecs::world::CHARACTER_CONTROLLER | crate::ecs::world::LOCAL_TRANSFORM,
)
.collect();
let delta_time = world.resources.physics.fixed_timestep;
let gravity_y = world.resources.physics.gravity.y;
for entity in entities {
let (mut velocity, shape, controller_config, scale, position) = {
let character_controller = world.core.get_character_controller(entity);
let local_transform = world.core.get_local_transform(entity);
if let (Some(cc), Some(lt)) = (character_controller, local_transform) {
(
cc.velocity,
cc.shape.clone(),
cc.config.clone(),
cc.scale,
lt.translation,
)
} else {
continue;
}
};
velocity.y += gravity_y * delta_time;
let desired_translation = vector![
velocity.x * delta_time,
velocity.y * delta_time,
velocity.z * delta_time,
];
let character_shape = match &shape {
super::ColliderShape::Capsule {
half_height,
radius,
} => SharedShape::capsule_y(*half_height, *radius),
super::ColliderShape::Ball { radius } => SharedShape::ball(*radius),
super::ColliderShape::Cuboid { hx, hy, hz } => SharedShape::cuboid(*hx, *hy, *hz),
_ => SharedShape::capsule_y(0.9, 0.3),
};
let character_pos = rapier3d::na::Isometry3::new(
vector![position.x, position.y, position.z],
rapier3d::na::Vector3::zeros(),
);
let mut collisions = Vec::new();
let rapier_controller = controller_config.to_rapier(scale);
let corrected_movement = {
let physics = &world.resources.physics;
let query_pipeline = physics.query_pipeline();
rapier_controller.move_shape(
delta_time,
&query_pipeline,
character_shape.as_ref(),
&character_pos,
desired_translation,
|collision| collisions.push(collision),
)
};
let new_translation = nalgebra_glm::vec3(
character_pos.translation.x + corrected_movement.translation.x,
character_pos.translation.y + corrected_movement.translation.y,
character_pos.translation.z + corrected_movement.translation.z,
);
if let Some(local_transform) = world.mutate_local_transform(entity) {
local_transform.translation = new_translation;
}
if let Some(interpolation) = world.core.get_physics_interpolation_mut(entity) {
interpolation.update_current(new_translation, nalgebra_glm::quat_identity());
}
for collision in &collisions {
if let Some(collider) = world.resources.physics.collider_set.get(collision.handle)
&& let Some(parent_handle) = collider.parent()
&& let Some(rb) = world
.resources
.physics
.rigid_body_set
.get_mut(parent_handle)
&& rb.body_type() == RigidBodyType::Dynamic
{
let push_strength = 2.0;
let impulse = vector![
velocity.x * push_strength * delta_time,
0.0,
velocity.z * push_strength * delta_time
];
rb.apply_impulse(impulse, true);
}
}
if let Some(character_controller) = world.core.get_character_controller_mut(entity) {
character_controller.grounded = corrected_movement.grounded;
if character_controller.grounded && velocity.y <= 0.0 {
character_controller.can_jump = true;
velocity.y = 0.0;
} else if !character_controller.grounded {
character_controller.can_jump = false;
}
if character_controller.grounded && !character_controller.is_crouching {
let horizontal_speed =
nalgebra_glm::length(&nalgebra_glm::vec3(velocity.x, 0.0, velocity.z));
if horizontal_speed <= character_controller.max_speed {
let friction_factor =
1.0 - (character_controller.friction_rate * delta_time).min(1.0);
velocity.x *= friction_factor;
velocity.z *= friction_factor;
} else {
let friction =
(-character_controller.above_max_friction_rate * delta_time).exp();
velocity.x *= friction;
velocity.z *= friction;
}
}
character_controller.velocity = velocity;
character_controller.config = controller_config;
}
}
}