outfly/src/camera.rs

332 lines
13 KiB
Rust

use bevy::prelude::*;
use bevy::input::mouse::MouseMotion;
use bevy::window::PrimaryWindow;
use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings};
use bevy::core_pipeline::tonemapping::Tonemapping;
use bevy::pbr::CascadeShadowConfigBuilder;
use bevy::transform::TransformSystem;
use bevy::math::DVec3;
use bevy_xpbd_3d::prelude::*;
use std::f32::consts::PI;
use crate::{settings, audio, actor};
pub struct CameraPlugin;
impl Plugin for CameraPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup_camera);
app.add_systems(Update, handle_input);
app.add_systems(Update, manage_player_actor.after(handle_input));
app.add_systems(PostUpdate, sync_camera_to_player
.after(PhysicsSet::Sync)
.after(apply_input_to_player)
.before(TransformSystem::TransformPropagate));
app.add_systems(Update, update_fov);
app.add_systems(PostUpdate, apply_input_to_player
.after(PhysicsSet::Sync)
.before(TransformSystem::TransformPropagate));
}
}
pub fn setup_camera(
mut commands: Commands,
) {
// Add player
commands.spawn((
Camera3dBundle {
camera: Camera {
hdr: true, // HDR is required for bloom
clear_color: ClearColorConfig::Custom(Color::BLACK),
..default()
},
tonemapping: Tonemapping::TonyMcMapface,
transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
},
BloomSettings {
composite_mode: BloomCompositeMode::EnergyConserving,
..default()
},
));
// Add Light from the Sun
commands.spawn(DirectionalLightBundle {
directional_light: DirectionalLight {
illuminance: 1000.0,
shadows_enabled: false,
..default()
},
transform: Transform::from_rotation(Quat::from_rotation_y(PI/2.0)),
cascade_shadow_config: CascadeShadowConfigBuilder {
first_cascade_far_bound: 7.0,
maximum_distance: 25.0,
..default()
}
.into(),
..default()
});
}
pub fn sync_camera_to_player(
settings: Res<settings::Settings>,
mut q_camera: Query<&mut Transform, With<Camera>>,
q_playercam: Query<(&actor::Actor, &Transform), (With<actor::PlayerCamera>, Without<Camera>)>,
) {
if q_camera.is_empty() || q_playercam.is_empty() {
return;
}
let mut camera_transform = q_camera.get_single_mut().unwrap();
let (actor, player_transform) = q_playercam.get_single().unwrap();
// Rotation
camera_transform.rotation = player_transform.rotation * Quat::from_array([0.0, -1.0, 0.0, 0.0]);
// Translation
if settings.third_person {
camera_transform.translation = player_transform.translation + camera_transform.rotation * (actor.camdistance * Vec3::new(0.0, 0.2, 1.0));
}
else {
camera_transform.translation = player_transform.translation;
}
}
pub fn update_fov(
q_player: Query<&actor::LifeForm, With<actor::Player>>,
mut q_camera: Query<&mut Projection, With<Camera>>,
) {
if let (Ok(lifeform), Ok(mut projection)) = (q_player.get_single(), q_camera.get_single_mut())
{
let fov = (lifeform.adrenaline.powf(3.0) * 45.0 + 45.0).to_radians();
*projection = Projection::Perspective(PerspectiveProjection { fov: fov, ..default() });
}
}
pub fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut settings: ResMut<settings::Settings>,
) {
if keyboard_input.just_pressed(settings.key_camera) {
settings.third_person ^= true;
}
}
fn manage_player_actor(
settings: Res<settings::Settings>,
mut q_playercam: Query<&mut Visibility, With<actor::PlayerCamera>>,
mut q_hiddenplayer: Query<(&mut Visibility, &mut Transform, &mut LinearVelocity, &mut AngularVelocity), (With<actor::Player>, Without<actor::PlayerCamera>)>,
q_ride: Query<(&Transform, &LinearVelocity, &AngularVelocity), (With<actor::PlayerDrivesThis>, Without<actor::Player>)>,
) {
for mut vis in &mut q_playercam {
if settings.third_person {
*vis = Visibility::Inherited;
}
else {
*vis = Visibility::Hidden;
}
}
for (mut vis, mut trans, mut v, mut angv) in &mut q_hiddenplayer {
*vis = Visibility::Hidden;
if let Ok((ride_trans, ride_v, ride_angv)) = q_ride.get_single() {
trans.translation = ride_trans.translation + Vec3::new(0.0, 0.0, 10.0);
trans.rotation = ride_trans.rotation;
*v = ride_v.clone();
*angv = ride_angv.clone();
}
}
}
#[allow(clippy::too_many_arguments)]
fn apply_input_to_player(
time: Res<Time>,
settings: Res<settings::Settings>,
mut windows: Query<&mut Window, With<PrimaryWindow>>,
mut mouse_events: EventReader<MouseMotion>,
key_input: Res<ButtonInput<KeyCode>>,
thruster_sound_controller: Query<&AudioSink, With<audio::ComponentThrusterSound>>,
rocket_sound_controller: Query<&AudioSink, With<audio::ComponentRocketSound>>,
ion_sound_controller: Query<&AudioSink, With<audio::ComponentIonSound>>,
electricmotor_sound_controller: Query<&AudioSink, With<audio::ComponentElectricMotorSound>>,
mut q_playercam: Query<(
&Transform,
&mut actor::Engine,
&mut AngularVelocity,
&mut LinearVelocity,
&mut ExternalTorque,
), (With<actor::PlayerCamera>, Without<Camera>)>,
) {
let dt = time.delta_seconds();
let mut play_thruster_sound = false;
let mut axis_input: DVec3 = DVec3::ZERO;
let window_result = windows.get_single_mut();
let mut focused = true;
if window_result.is_ok() {
focused = window_result.unwrap().focused;
}
if let Ok((player_transform, mut engine, mut angularvelocity, mut v, mut torque)) = q_playercam.get_single_mut() {
// Handle key input
if focused {
if key_input.pressed(settings.key_forward) {
axis_input.z += 1.2;
}
if key_input.pressed(settings.key_back) {
axis_input.z -= 1.2;
}
if key_input.pressed(settings.key_right) {
axis_input.x -= 1.2;
}
if key_input.pressed(settings.key_left) {
axis_input.x += 1.2;
}
if key_input.pressed(settings.key_up) {
axis_input.y += 1.2;
}
if key_input.pressed(settings.key_down) {
axis_input.y -= 1.2;
}
if key_input.pressed(settings.key_stop) {
let stop_direction = -v.normalize();
if stop_direction.length_squared() > 0.3 {
axis_input += 1.0 * DVec3::from(player_transform.rotation.inverse() * stop_direction.as_vec3());
}
}
}
// In typical games we would normalize the input vector so that diagonal movement is as
// fast as forward or sideways movement. But here, we merely clamp each direction to an
// absolute maximum of 1, since every thruster can be used separately. If the forward
// thrusters and the leftward thrusters are active at the same time, then of course the
// total diagonal acceleration is faster than the forward acceleration alone.
axis_input = axis_input.clamp(DVec3::splat(-1.0), DVec3::splat(1.0));
// Apply movement update
let forward_factor = engine.current_warmup * (if axis_input.z > 0.0 {
engine.thrust_forward
} else {
engine.thrust_back
});
let right_factor = engine.thrust_sideways * engine.current_warmup;
let up_factor = engine.thrust_sideways * engine.current_warmup;
let factor = DVec3::new(right_factor as f64, up_factor as f64, forward_factor as f64);
if axis_input.length_squared() > 0.003 {
let acceleration_global: DVec3 = DVec3::from(player_transform.rotation * (axis_input * factor).as_vec3());
let mut acceleration_total: DVec3 = (actor::ENGINE_SPEED_FACTOR * dt) as f64 * acceleration_global;
let threshold = 1e-5;
if key_input.pressed(settings.key_stop) {
// Decelerate
for i in 0..3 {
if v[i].abs() < threshold {
v[i] = 0.0;
}
else if v[i].signum() != (v[i] + acceleration_total[i]).signum() {
// Almost stopped, but we overshot v=0
v[i] = 0.0;
acceleration_total[i] = 0.0;
}
}
}
v.0 += acceleration_total;
engine.current_warmup = (engine.current_warmup + dt / engine.warmup_seconds).clamp(0.0, 1.0);
play_thruster_sound = !settings.mute_sfx;
}
else {
engine.current_warmup = (engine.current_warmup - dt / engine.warmup_seconds).clamp(0.0, 1.0);
}
// Handle mouse input and mouse-like key bindings
let mut play_reactionwheel_sound = false;
let mut mouse_delta = Vec2::ZERO;
let mut pitch_yaw_rot = Vec3::ZERO;
let mouseless_sensitivity = 40.0;
if key_input.pressed(settings.key_mouseup) {
pitch_yaw_rot[0] -= mouseless_sensitivity;
}
if key_input.pressed(settings.key_mousedown) {
pitch_yaw_rot[0] += mouseless_sensitivity;
}
if key_input.pressed(settings.key_mouseleft) {
pitch_yaw_rot[1] += mouseless_sensitivity; }
if key_input.pressed(settings.key_mouseright) {
pitch_yaw_rot[1] -= mouseless_sensitivity;
}
if key_input.pressed(settings.key_rotateleft) {
pitch_yaw_rot[2] -= mouseless_sensitivity;
}
if key_input.pressed(settings.key_rotateright) {
pitch_yaw_rot[2] += mouseless_sensitivity;
}
for mouse_event in mouse_events.read() {
mouse_delta += mouse_event.delta;
}
if mouse_delta != Vec2::ZERO {
if key_input.pressed(settings.key_rotate) {
pitch_yaw_rot[2] += mouse_delta.x;
} else {
pitch_yaw_rot[0] += mouse_delta.y;
pitch_yaw_rot[1] -= mouse_delta.x;
}
}
let angular_slowdown: f64 = (2.0 - engine.reaction_wheels.powf(0.01).clamp(1.001, 1.1)) as f64;
if pitch_yaw_rot.length_squared() > 0.0001 {
play_reactionwheel_sound = true;
pitch_yaw_rot *= settings.mouse_sensitivity * engine.reaction_wheels;
torque.apply_torque(DVec3::from(
player_transform.rotation * Vec3::new(
pitch_yaw_rot[0] * 2.0,
pitch_yaw_rot[1],
pitch_yaw_rot[2])));
angularvelocity.0 *= angular_slowdown.clamp(0.97, 1.0) as f64;
}
else {
if angularvelocity.length_squared() > 0.001 {
angularvelocity.0 *= angular_slowdown;
}
else {
angularvelocity.0 = DVec3::splat(0.0);
}
}
// Play sound effects
if let Ok(sink) = electricmotor_sound_controller.get_single() {
let volume = sink.volume();
let speed = sink.speed();
let action = pitch_yaw_rot.length_squared().powf(0.2) * 0.0005;
if play_reactionwheel_sound {
sink.set_volume((volume + action).clamp(0.0, 1.0));
sink.set_speed((speed + action * 0.2).clamp(0.2, 0.5));
sink.play()
} else {
if volume <= 0.01 {
sink.pause()
} else {
sink.set_volume((volume - 0.01).clamp(0.0, 1.0));
sink.set_speed((speed - 0.03).clamp(0.2, 0.5));
}
}
}
if let Ok(sink) = thruster_sound_controller.get_single() {
if play_thruster_sound && engine.engine_type == actor::EngineType::Monopropellant {
sink.play()
} else {
sink.pause()
}
}
if let Ok(sink) = rocket_sound_controller.get_single() {
if play_thruster_sound && engine.engine_type == actor::EngineType::Rocket {
sink.play()
} else {
sink.pause()
}
}
if let Ok(sink) = ion_sound_controller.get_single() {
if play_thruster_sound && engine.engine_type == actor::EngineType::Ion {
sink.play()
} else {
sink.pause()
}
}
}
}