outfly/src/camera.rs

734 lines
29 KiB
Rust
Raw Normal View History

2024-04-21 16:23:40 +00:00
// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
2024-04-21 17:34:00 +00:00
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
2024-04-21 16:23:40 +00:00
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
2024-04-23 15:33:36 +00:00
//
// This module manages the game's viewport, handles camera- and
// movement-related keyboard input, and provides some camera-
// related computation functions.
2024-04-21 16:23:40 +00:00
use bevy::prelude::*;
use bevy::input::mouse::{MouseMotion, MouseWheel};
2024-03-16 23:24:47 +00:00
use bevy::window::PrimaryWindow;
use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings};
use bevy::core_pipeline::tonemapping::Tonemapping;
use bevy::pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap};
use bevy::transform::TransformSystem;
use bevy::math::{DVec3, DQuat};
use bevy_xpbd_3d::prelude::*;
use bevy_xpbd_3d::plugins::sync;
use std::f32::consts::PI;
use std::f64::consts::PI as PI64;
2024-05-12 21:42:56 +00:00
use crate::prelude::*;
2024-04-25 02:15:57 +00:00
pub const INITIAL_ZOOM_LEVEL: f64 = 10.0;
2024-03-31 20:08:26 +00:00
pub struct CameraPlugin;
2024-03-31 20:08:26 +00:00
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, update_map_only_object_visibility);
app.add_systems(Update, manage_player_actor.after(handle_input));
app.add_systems(PostUpdate, sync_camera_to_player
.after(PhysicsSet::Sync)
2024-03-30 18:51:41 +00:00
.after(apply_input_to_player)
.before(TransformSystem::TransformPropagate));
app.add_systems(PostUpdate, update_mapcam_center
.before(sync::position_to_transform)
.in_set(sync::SyncSet::PositionToTransform));
2024-04-18 19:01:03 +00:00
app.add_systems(Update, update_map_camera);
app.add_systems(Update, update_fov);
app.add_systems(PreUpdate, apply_input_to_player);
2024-04-18 19:01:03 +00:00
app.insert_resource(MapCam::default());
// To center the renderer origin on the player camera,
// 1. Disable bevy_xpbd's position->transform sync function
app.insert_resource(sync::SyncConfig {
position_to_transform: true,
transform_to_position: false,
});
// 2. Add own position->transform sync function
app.add_systems(PostUpdate, position_to_transform
.after(sync::position_to_transform)
.in_set(sync::SyncSet::PositionToTransform));
2024-04-18 19:01:03 +00:00
}
}
#[derive(Component)]
pub struct ShowOnlyInMap {
pub min_distance: f64,
pub distance_to_id: String,
}
2024-04-18 19:01:03 +00:00
#[derive(Resource)]
pub struct MapCam {
2024-04-19 02:41:07 +00:00
pub initialized: bool,
pub zoom_level: f64,
pub target_zoom_level: f64,
pub pitch: f64,
pub yaw: f64,
pub offset_x: f64,
pub offset_z: f64,
pub center: DVec3,
pub center_on_entity: Option<Entity>,
2024-04-18 19:01:03 +00:00
}
impl Default for MapCam {
fn default() -> Self {
Self {
2024-04-19 02:41:07 +00:00
initialized: false,
zoom_level: 2.0,
2024-04-25 02:15:57 +00:00
target_zoom_level: INITIAL_ZOOM_LEVEL,
pitch: PI64 * 0.3,
2024-04-18 19:01:03 +00:00
yaw: 0.0,
offset_x: 0.0,
offset_z: 0.0,
center: DVec3::new(0.0, 0.0, 0.0),
center_on_entity: None,
2024-04-18 19:01:03 +00:00
}
}
}
pub fn setup_camera(
mut commands: Commands,
settings: Res<var::Settings>,
) {
// Add player
commands.spawn((
Camera3dBundle {
camera: Camera {
hdr: true, // HDR is required for bloom
2024-03-31 20:08:26 +00:00
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 {
2024-04-30 22:44:47 +00:00
illuminance: 2000.0,
shadows_enabled: settings.shadows_sun,
..default()
},
transform: Transform::from_rotation(Quat::from_rotation_y(PI/2.0)),
cascade_shadow_config: CascadeShadowConfigBuilder {
num_cascades: 4,
2024-04-24 18:17:37 +00:00
minimum_distance: 0.1,
maximum_distance: 5000.0,
..default()
}.into(),
..default()
});
2024-04-24 17:59:14 +00:00
commands.insert_resource(DirectionalLightShadowMap {
size: settings.shadowmap_resolution,
});
}
pub fn sync_camera_to_player(
settings: Res<var::Settings>,
2024-04-05 00:58:02 +00:00
mut q_camera: Query<&mut Transform, (With<Camera>, Without<actor::PlayerCamera>)>,
q_playercam: Query<(&actor::Actor, &Transform), (With<actor::PlayerCamera>, Without<Camera>)>,
) {
2024-04-18 19:01:03 +00:00
if settings.map_active || 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
2024-05-08 16:40:01 +00:00
let rotation = player_transform.rotation * Quat::from_array([0.0, -1.0, 0.0, 0.0]);
// Translation
if settings.third_person {
2024-05-08 16:40:01 +00:00
camera_transform.translation = player_transform.translation + rotation * (actor.camdistance * Vec3::new(0.0, 0.2, 1.0));
camera_transform.rotation = rotation * Quat::from_euler(EulerRot::XYZ, -0.02, 0.0, 0.0);
}
else {
camera_transform.translation = player_transform.translation;
2024-05-08 16:40:01 +00:00
camera_transform.rotation = rotation;
}
}
2024-04-18 19:01:03 +00:00
pub fn update_map_camera(
settings: Res<var::Settings>,
mut mapcam: ResMut<MapCam>,
mut q_camera: Query<&mut Transform, (With<Camera>, Without<actor::PlayerCamera>)>,
2024-05-07 23:00:58 +00:00
q_playercam: Query<(Entity, &Transform), (With<actor::PlayerCamera>, Without<Camera>)>,
q_target: Query<(Entity, &Transform), (With<hud::IsTargeted>, Without<Camera>, Without<actor::PlayerCamera>)>,
q_target_changed: Query<(), Changed<hud::IsTargeted>>,
2024-04-18 19:01:03 +00:00
mut mouse_events: EventReader<MouseMotion>,
mut er_mousewheel: EventReader<MouseWheel>,
2024-04-18 19:01:03 +00:00
keyboard_input: Res<ButtonInput<KeyCode>>,
) {
if !settings.map_active || q_camera.is_empty() || q_playercam.is_empty() {
return;
}
let mut camera_transform = q_camera.get_single_mut().unwrap();
2024-05-07 23:00:58 +00:00
let (player_entity, player_trans) = q_playercam.get_single().unwrap();
let (target_entity, target_trans) = if let Ok(target) = q_target.get_single() {
2024-04-19 01:54:17 +00:00
target
} else {
2024-05-07 23:00:58 +00:00
(player_entity, player_trans)
2024-04-19 01:54:17 +00:00
};
mapcam.center_on_entity = Some(target_entity);
2024-04-18 19:01:03 +00:00
// Get mouse movement
let mut mouse_delta = Vec2::ZERO;
for mouse_event in mouse_events.read() {
mouse_delta += mouse_event.delta;
}
// NOTE: we need to subtract a bit from PI/2, otherwise the "up"
// direction parameter for the Transform.look_at function is ambiguous
// at the extreme values and the orientation will flicker back/forth.
2024-04-18 20:48:21 +00:00
let epsilon = 0.001;
let min_zoom: f64 = target_trans.scale.x as f64 * 2.0;
let max_zoom: f64 = 17e18; // at this point, camera starts glitching
mapcam.pitch = (mapcam.pitch + mouse_delta.y as f64 / 180.0 * settings.mouse_sensitivity as f64).clamp(-PI64 / 2.0 + epsilon, PI64 / 2.0 - epsilon);
mapcam.yaw += mouse_delta.x as f64 / 180.0 * settings.mouse_sensitivity as f64;
2024-04-18 19:01:03 +00:00
// Reset movement offset if target changes
if !q_target_changed.is_empty() {
mapcam.offset_x = 0.0;
mapcam.offset_z = 0.0;
}
// Get keyboard movement
let mut offset_x: f64 = 0.0;
let mut offset_z: f64 = 0.0;
if keyboard_input.pressed(settings.key_forward) {
offset_x -= 1.0;
}
if keyboard_input.pressed(settings.key_back) {
offset_x += 1.0;
}
if keyboard_input.pressed(settings.key_right) {
offset_z -= 1.0;
}
if keyboard_input.pressed(settings.key_left) {
offset_z += 1.0;
}
2024-04-21 22:07:45 +00:00
if keyboard_input.pressed(settings.key_stop) {
mapcam.offset_x = 0.0;
mapcam.offset_z = 0.0;
}
2024-04-18 19:01:03 +00:00
// Update zoom level
2024-04-19 02:41:07 +00:00
if !mapcam.initialized {
let factor: f64 = if target_trans == player_trans { 7.0 } else { 1.0 };
mapcam.target_zoom_level *= target_trans.scale.x as f64 * factor;
mapcam.zoom_level *= target_trans.scale.x as f64 * factor;
2024-04-19 02:41:07 +00:00
mapcam.initialized = true;
}
let mut change_zoom: f64 = 0.0;
2024-04-18 19:01:03 +00:00
if keyboard_input.pressed(settings.key_map_zoom_out) {
2024-04-20 00:33:37 +00:00
change_zoom += 0.5;
2024-04-18 19:01:03 +00:00
}
if keyboard_input.pressed(settings.key_map_zoom_in) {
2024-04-20 00:33:37 +00:00
change_zoom -= 0.5;
2024-04-18 19:01:03 +00:00
}
for wheel_event in er_mousewheel.read() {
change_zoom -= wheel_event.y as f64 * 3.0;
}
mapcam.target_zoom_level = (mapcam.target_zoom_level * 1.1f64.powf(change_zoom)).clamp(min_zoom, max_zoom);
2024-04-18 20:48:21 +00:00
let zoom_speed = 0.05; // should be between 0.0001 (slow) and 1.0 (instant)
mapcam.zoom_level = (zoom_speed * mapcam.target_zoom_level + (1.0 - zoom_speed) * mapcam.zoom_level).clamp(min_zoom, max_zoom);
2024-04-18 19:01:03 +00:00
// Update point of view
let pov_rotation = DQuat::from_euler(EulerRot::XYZ, 0.0, mapcam.yaw as f64, mapcam.pitch as f64);
let point_of_view = pov_rotation * (mapcam.zoom_level as f64 * DVec3::new(1.0, 0.0, 0.0));
2024-04-18 19:01:03 +00:00
// Update movement offset
let mut direction = pov_rotation * DVec3::new(offset_x, 0.0, offset_z);
let speed = direction.length();
direction.y = 0.0;
let direction = speed * direction.normalize_or_zero();
mapcam.offset_x += 0.01 * (direction.x * mapcam.zoom_level);
mapcam.offset_z += 0.01 * (direction.z * mapcam.zoom_level);
2024-04-18 19:01:03 +00:00
// Apply updates to camera
camera_transform.translation = point_of_view.as_vec3();
camera_transform.look_at(Vec3::ZERO, Vec3::Y);
2024-04-18 19:01:03 +00:00
}
pub fn update_mapcam_center(
mut mapcam: ResMut<MapCam>,
settings: Res<var::Settings>,
q_pos: Query<&Position>,
) {
if !settings.map_active {
return;
}
if let Some(entity) = mapcam.center_on_entity {
if let Ok(pos) = q_pos.get(entity) {
let offset = DVec3::new(mapcam.offset_x, 0.0, mapcam.offset_z);
mapcam.center = **pos + offset;
}
}
}
pub fn update_fov(
2024-04-08 00:36:47 +00:00
q_player: Query<&actor::ExperiencesGForce, With<actor::Player>>,
2024-04-05 21:38:20 +00:00
mouse_input: Res<ButtonInput<MouseButton>>,
mut settings: ResMut<var::Settings>,
mut q_camera: Query<&mut Projection, With<Camera>>,
) {
2024-04-08 00:36:47 +00:00
if let (Ok(gforce), Ok(mut projection)) = (q_player.get_single(), q_camera.get_single_mut())
{
2024-04-05 21:38:20 +00:00
let fov: f32;
2024-04-05 21:38:27 +00:00
if settings.hud_active && mouse_input.pressed(settings.key_zoom) {
2024-04-08 01:15:45 +00:00
fov = settings.zoom_fov.to_radians();
2024-04-05 21:38:20 +00:00
if !settings.is_zooming {
settings.is_zooming = true;
}
} else {
2024-04-10 20:04:07 +00:00
fov = (gforce.visual_effect.clamp(0.0, 1.0) * settings.fov_highspeed + settings.fov).to_radians();
2024-04-05 21:38:20 +00:00
if settings.is_zooming {
settings.is_zooming = false;
}
};
*projection = Projection::Perspective(PerspectiveProjection { fov: fov, ..default() });
}
}
pub fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>,
2024-04-24 18:40:20 +00:00
mut q_light: Query<&mut DirectionalLight>,
mut settings: ResMut<var::Settings>,
2024-04-18 19:01:03 +00:00
mut mapcam: ResMut<MapCam>,
2024-04-11 19:06:21 +00:00
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_updateoverlays: EventWriter<hud::UpdateOverlayVisibility>,
) {
if keyboard_input.just_pressed(settings.key_camera) {
settings.third_person ^= true;
}
2024-04-24 18:40:20 +00:00
if keyboard_input.just_pressed(settings.key_shadows) {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
2024-04-24 18:40:20 +00:00
settings.shadows_sun ^= true;
for mut light in &mut q_light {
light.shadows_enabled = settings.shadows_sun;
}
}
2024-04-18 19:01:03 +00:00
if keyboard_input.just_pressed(settings.key_map) {
settings.map_active ^= true;
if settings.map_active {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Woosh));
}
2024-04-18 19:01:03 +00:00
*mapcam = MapCam::default();
ew_updateoverlays.send(hud::UpdateOverlayVisibility);
2024-04-18 19:01:03 +00:00
}
2024-04-11 19:06:21 +00:00
if keyboard_input.just_pressed(settings.key_rotation_stabilizer) {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
2024-04-11 19:06:21 +00:00
settings.rotation_stabilizer_active ^= true;
}
}
fn manage_player_actor(
2024-04-17 11:55:39 +00:00
mut commands: Commands,
settings: Res<var::Settings>,
2024-03-30 14:37:51 +00:00
mut q_playercam: Query<&mut Visibility, With<actor::PlayerCamera>>,
2024-04-17 11:55:39 +00:00
mut q_hiddenplayer: Query<(Entity, &mut Visibility, &mut Position, &mut Rotation, &mut LinearVelocity, &mut AngularVelocity, Option<&mut actor::ExperiencesGForce>, Option<&actor::JustNowEnteredVehicle>), (With<actor::Player>, Without<actor::PlayerCamera>)>,
q_ride: Query<(&Transform, &Position, &Rotation, &LinearVelocity, &AngularVelocity), (With<actor::PlayerDrivesThis>, Without<actor::Player>)>,
) {
for mut vis in &mut q_playercam {
2024-04-18 19:01:03 +00:00
if settings.third_person || settings.map_active {
*vis = Visibility::Inherited;
}
else {
*vis = Visibility::Hidden;
}
}
2024-04-17 11:55:39 +00:00
for (entity, mut vis, mut pos, mut rot, mut v, mut angv, mut gforce, entering) in &mut q_hiddenplayer {
2024-04-03 11:53:49 +00:00
// If we are riding a vehicle, place the player at the position where
// it would be after exiting the vehicle.
// I would rather place it in the center of the vehicle, but at the time
// of writing, I couldn't set the position/rotation of the player *during*
// exiting the vehicle, so I'm doing it here instead, as a workaround.
*vis = Visibility::Hidden;
if let Ok((ride_trans, ride_pos, ride_rot, ride_v, ride_angv)) = q_ride.get_single() {
2024-04-03 11:53:49 +00:00
pos.0 = ride_pos.0 + DVec3::from(ride_trans.rotation * Vec3::new(0.0, 0.0, ride_trans.scale.z * 2.0));
rot.0 = ride_rot.0 * DQuat::from_array([-1.0, 0.0, 0.0, 0.0]);
*v = ride_v.clone();
*angv = ride_angv.clone();
2024-04-17 11:55:39 +00:00
// I really don't want people to die from the g-forces of entering
// vehicles at high relative speed, even though they probably should.
if let (Some(gforce), Some(_)) = (&mut gforce, entering) {
gforce.last_linear_velocity = v.0;
commands.entity(entity).remove::<actor::JustNowEnteredVehicle>();
}
}
}
}
#[allow(clippy::too_many_arguments)]
2024-04-05 20:16:01 +00:00
pub fn apply_input_to_player(
time: Res<Time>,
settings: Res<var::Settings>,
windows: Query<&Window, With<PrimaryWindow>>,
mut mouse_events: EventReader<MouseMotion>,
key_input: Res<ButtonInput<KeyCode>>,
2024-03-16 23:41:06 +00:00
thruster_sound_controller: Query<&AudioSink, With<audio::ComponentThrusterSound>>,
2024-03-28 13:10:10 +00:00
rocket_sound_controller: Query<&AudioSink, With<audio::ComponentRocketSound>>,
2024-03-29 03:36:20 +00:00
ion_sound_controller: Query<&AudioSink, With<audio::ComponentIonSound>>,
2024-03-31 00:07:35 +00:00
electricmotor_sound_controller: Query<&AudioSink, With<audio::ComponentElectricMotorSound>>,
q_target: Query<&LinearVelocity, (With<hud::IsTargeted>, Without<actor::PlayerCamera>)>,
mut q_playercam: Query<(
&Transform,
&mut actor::Engine,
&mut AngularVelocity,
&mut LinearVelocity,
&mut ExternalTorque,
Option<&actor::PlayerDrivesThis>,
), (With<actor::PlayerCamera>, Without<Camera>)>,
) {
2024-04-18 19:01:03 +00:00
if settings.map_active {
return;
}
let dt = time.delta_seconds();
2024-03-16 23:41:06 +00:00
let mut play_thruster_sound = false;
let mut axis_input: DVec3 = DVec3::ZERO;
let (win_res_x, win_res_y): (f32, f32);
2024-03-16 23:24:47 +00:00
let mut focused = true;
if let Ok(window) = &windows.get_single() {
focused = window.focused;
win_res_x = window.resolution.width();
win_res_y = window.resolution.height();
}
else {
win_res_x = 1920.0;
win_res_y = 1050.0;
2024-03-16 23:24:47 +00:00
}
let target_v: DVec3 = if let Ok(target) = q_target.get_single() {
target.0
} else {
DVec3::splat(0.0)
};
if let Ok((player_transform, mut engine, mut angularvelocity, mut v, mut torque, bike)) = q_playercam.get_single_mut() {
// Handle key input
2024-03-16 23:24:47 +00:00
if focused {
2024-03-18 03:39:26 +00:00
if key_input.pressed(settings.key_forward) {
axis_input.z += 1.2;
2024-03-16 23:24:47 +00:00
}
2024-03-18 03:39:26 +00:00
if key_input.pressed(settings.key_back) {
axis_input.z -= 1.2;
2024-03-16 23:24:47 +00:00
}
2024-03-18 03:39:26 +00:00
if key_input.pressed(settings.key_right) {
axis_input.x -= 1.2;
2024-03-16 23:24:47 +00:00
}
2024-03-18 03:39:26 +00:00
if key_input.pressed(settings.key_left) {
axis_input.x += 1.2;
2024-03-16 23:24:47 +00:00
}
2024-03-18 03:39:26 +00:00
if key_input.pressed(settings.key_up) {
2024-03-29 01:49:16 +00:00
axis_input.y += 1.2;
2024-03-16 23:24:47 +00:00
}
2024-03-18 03:39:26 +00:00
if key_input.pressed(settings.key_down) {
2024-03-29 01:49:16 +00:00
axis_input.y -= 1.2;
2024-03-16 23:24:47 +00:00
}
2024-03-29 01:40:55 +00:00
if key_input.pressed(settings.key_stop) {
let stop_direction = (target_v - v.0).normalize();
2024-03-29 01:40:55 +00:00
if stop_direction.length_squared() > 0.3 {
axis_input += 1.0 * DVec3::from(player_transform.rotation.inverse() * stop_direction.as_vec3());
2024-03-29 01:40:55 +00:00
}
2024-03-21 17:45:43 +00:00
}
}
// 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));
2024-03-17 13:39:42 +00:00
// Apply movement update
2024-03-30 17:58:45 +00:00
let forward_factor = engine.current_warmup * (if axis_input.z > 0.0 {
engine.thrust_forward
} else {
engine.thrust_back
});
2024-03-29 00:40:58 +00:00
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);
2024-03-29 00:40:58 +00:00
2024-03-29 01:40:55 +00:00
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) {
2024-04-05 21:11:10 +00:00
// Decelerate (or match velocity to target_v)
let dv = v.0 - target_v;
for i in 0..3 {
if dv[i].abs() < threshold {
v[i] = target_v[i];
}
else if dv[i].signum() != (dv[i] + acceleration_total[i]).signum() {
// Almost stopped, but we overshot v=0
v[i] = target_v[i];
acceleration_total[i] = 0.0;
}
}
}
2024-04-16 17:44:05 +00:00
// TODO: handle mass
v.0 += acceleration_total;
engine.current_warmup = (engine.current_warmup + dt / engine.warmup_seconds).clamp(0.0, 1.0);
2024-04-03 12:27:44 +00:00
play_thruster_sound = true;
}
else {
engine.current_warmup = (engine.current_warmup - dt / engine.warmup_seconds).clamp(0.0, 1.0);
}
2024-03-21 17:45:43 +00:00
// Handle mouse input and mouse-like key bindings
2024-03-31 00:07:35 +00:00
let mut play_reactionwheel_sound = false;
let mut mouse_delta = Vec2::ZERO;
let mut pitch_yaw_rot = Vec3::ZERO;
2024-04-05 21:38:20 +00:00
let sensitivity_factor = if settings.is_zooming { settings.zoom_sensitivity_factor } else { 1.0 };
let mouseless_sensitivity = 8.0 * sensitivity_factor;
let mouseless_rotation_sensitivity = 40.0 * sensitivity_factor;
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_rotation_sensitivity;
}
if key_input.pressed(settings.key_rotateright) {
pitch_yaw_rot[2] += mouseless_rotation_sensitivity;
}
2024-03-18 03:39:26 +00:00
for mouse_event in mouse_events.read() {
mouse_delta += mouse_event.delta;
}
if mouse_delta != Vec2::ZERO {
2024-03-30 18:14:59 +00:00
if key_input.pressed(settings.key_rotate) {
pitch_yaw_rot[2] += 1000.0 * mouse_delta.x / win_res_x;
2024-03-30 18:14:59 +00:00
} else {
pitch_yaw_rot[0] += 1000.0 * mouse_delta.y / win_res_y;
pitch_yaw_rot[1] -= 1000.0 * mouse_delta.x / win_res_x;
2024-03-30 18:14:59 +00:00
}
}
2024-04-11 19:06:21 +00:00
let angular_slowdown: f64 = if settings.rotation_stabilizer_active {
2024-05-01 02:02:04 +00:00
(2.0 - engine.reaction_wheels.powf(0.05).clamp(1.001, 1.1)) as f64
2024-04-11 19:06:21 +00:00
} else {
1.0
};
2024-04-08 00:19:33 +00:00
if pitch_yaw_rot.length_squared() > 1.0e-18 {
2024-03-31 00:07:35 +00:00
play_reactionwheel_sound = true;
2024-04-05 21:38:20 +00:00
pitch_yaw_rot *= settings.mouse_sensitivity * sensitivity_factor * engine.reaction_wheels;
torque.apply_torque(DVec3::from(
player_transform.rotation * Vec3::new(
pitch_yaw_rot[0],
pitch_yaw_rot[1],
pitch_yaw_rot[2])));
angularvelocity.0 *= angular_slowdown.clamp(0.97, 1.0) as f64;
}
else {
2024-04-08 00:19:33 +00:00
if angularvelocity.length_squared() > 1.0e-18 {
angularvelocity.0 *= angular_slowdown;
}
else {
angularvelocity.0 = DVec3::splat(0.0);
}
}
2024-03-16 23:41:06 +00:00
// Play sound effects
2024-03-31 00:07:35 +00:00
if let Ok(sink) = electricmotor_sound_controller.get_single() {
let volume = sink.volume();
2024-03-31 03:13:13 +00:00
let speed = sink.speed();
let action = pitch_yaw_rot.length_squared().powf(0.2) * 0.0005;
if play_reactionwheel_sound && !settings.mute_sfx && bike.is_some() {
2024-03-31 03:13:13 +00:00
sink.set_volume((volume + action).clamp(0.0, 1.0));
sink.set_speed((speed + action * 0.2).clamp(0.2, 0.5));
2024-03-31 00:07:35 +00:00
sink.play()
} else {
if volume <= 0.01 {
sink.pause()
} else {
2024-03-31 03:13:13 +00:00
sink.set_volume((volume - 0.01).clamp(0.0, 1.0));
sink.set_speed((speed - 0.03).clamp(0.2, 0.5));
2024-03-31 00:07:35 +00:00
}
}
}
let sinks = vec![
(1.2, actor::EngineType::Monopropellant, thruster_sound_controller.get_single()),
(1.0, actor::EngineType::Rocket, rocket_sound_controller.get_single()),
(1.4, actor::EngineType::Ion, ion_sound_controller.get_single()),
];
let seconds_to_max_vol = 0.05;
let seconds_to_min_vol = 0.05;
for sink_data in sinks {
if let (vol_boost, engine_type, Ok(sink)) = sink_data {
if settings.mute_sfx {
sink.pause();
}
else {
let volume = sink.volume();
if engine.engine_type == engine_type {
if play_thruster_sound {
sink.play();
if volume < 1.0 {
let maxvol = vol_boost;
//let maxvol = engine.current_warmup * vol_boost;
sink.set_volume((volume + dt / seconds_to_max_vol).clamp(0.0, maxvol));
}
} else {
sink.set_volume((volume - dt / seconds_to_min_vol).clamp(0.0, 1.0));
}
}
else if volume > 0.0 {
sink.set_volume((volume - dt / seconds_to_min_vol).clamp(0.0, 1.0));
}
if volume < 0.0001 {
sink.pause();
}
}
2024-03-29 03:36:20 +00:00
}
}
}
}
2024-04-05 17:11:34 +00:00
pub fn update_map_only_object_visibility(
settings: Res<var::Settings>,
q_camera: Query<&Transform, With<Camera>>,
q_player: Query<&Position, With<actor::PlayerCamera>>,
mut q_onlyinmap: Query<(&mut Visibility, &ShowOnlyInMap), Without<Camera>>,
2024-05-12 21:57:21 +00:00
id2pos: Res<game::Id2Pos>,
) {
if q_camera.is_empty() || q_player.is_empty() {
return;
}
let cam: &Transform = q_camera.get_single().unwrap();
let player_pos: &Position = q_player.get_single().unwrap();
let cam_pos: Vec3 = cam.translation + player_pos.as_vec3();
for (mut vis, onlyinmap) in &mut q_onlyinmap {
2024-04-20 00:37:00 +00:00
if settings.map_active && settings.hud_active {
if let Some(pos) = id2pos.0.get(&onlyinmap.distance_to_id) {
let dist = cam_pos.distance(pos.as_vec3());
if dist >= onlyinmap.min_distance as f32 {
*vis = Visibility::Inherited;
}
else {
*vis = Visibility::Hidden;
}
2024-04-20 00:37:00 +00:00
} else {
error!("Failed get position of actor ID '{}'", &onlyinmap.distance_to_id);
*vis = Visibility::Hidden;
}
} else {
*vis = Visibility::Hidden;
}
}
}
2024-04-05 17:11:34 +00:00
// Find the closest world object that the player is looking at
#[inline]
2024-04-05 17:34:01 +00:00
pub fn find_closest_target<TargetSpecifier>(
objects: Vec<(TargetSpecifier, &Transform)>,
camera_transform: &Transform,
) -> (Option<TargetSpecifier>, f32)
where TargetSpecifier: Clone
2024-04-05 17:11:34 +00:00
{
2024-04-05 17:34:01 +00:00
let mut closest_entity: Option<TargetSpecifier> = None;
2024-04-05 17:11:34 +00:00
let mut closest_distance: f32 = f32::MAX;
let target_vector: Vec3 = (camera_transform.rotation * Vec3::new(0.0, 0.0, -1.0))
2024-04-05 17:34:01 +00:00
.normalize_or_zero();
for (entity, trans) in objects {
// Use Transform instead of Position because we're basing this
// not on the player mesh but on the camera, which doesn't have a position.
let (angular_diameter, angle, distance) = calc_angular_diameter_known_target_vector(
trans, camera_transform, &target_vector);
2024-04-24 18:40:40 +00:00
if angle <= angular_diameter.clamp(0.01, PI) {
2024-04-05 17:34:01 +00:00
// It's in the field of view!
//commands.entity(entity).insert(IsTargeted);
2024-04-17 13:24:00 +00:00
let distance_to_surface = distance - trans.scale.x;
if distance_to_surface < closest_distance {
closest_distance = distance_to_surface;
2024-04-05 17:59:53 +00:00
closest_entity = Some(entity);
2024-04-05 17:11:34 +00:00
}
}
}
return (closest_entity, closest_distance);
}
#[inline]
pub fn calc_angular_diameter_known_target_vector(
target: &Transform,
camera: &Transform,
target_vector: &Vec3,
) -> (f32, f32, f32) {
let pos_vector: Vec3 = (target.translation - camera.translation)
.normalize_or_zero();
let cosine_of_angle: f32 = target_vector.dot(pos_vector);
let angle: f32 = cosine_of_angle.acos();
let distance: f32 = target.translation.distance(camera.translation);
let leeway: f32 = 1.3;
let angular_diameter: f32 = if distance > 0.0 {
// Angular Diameter
leeway * (target.scale[0] / distance).asin()
}
else {
0.0
};
return (angular_diameter, angle, distance);
}
#[inline]
pub fn calc_angular_diameter(
target: &Transform,
camera: &Transform,
) -> (f32, f32, f32) {
let target_vector: Vec3 = (camera.rotation * Vec3::new(0.0, 0.0, -1.0))
.normalize_or_zero();
return calc_angular_diameter_known_target_vector(target, camera, &target_vector);
}
// An extension of bevy_xpbd_3d::plugins::position_to_transform that adjusts
// the rendering position to center entities at the player camera.
// This avoids rendering glitches when very far away from the origin.
pub fn position_to_transform(
mapcam: Res<MapCam>,
settings: Res<var::Settings>,
q_player: Query<&Position, With<actor::PlayerCamera>>,
mut q_trans: Query<(&'static mut Transform, &'static Position, &'static Rotation), Without<Parent>>,
) {
let center: DVec3 = if settings.map_active {
mapcam.center
} else if let Ok(player_pos) = q_player.get_single() {
**player_pos
} else {
return;
};
for (mut transform, pos, rot) in &mut q_trans {
transform.translation = Vec3::new(
(pos.x - center.x) as f32,
(pos.y - center.y) as f32,
(pos.z - center.z) as f32,
);
transform.rotation = rot.as_quat();
}
}