From a7602dfef4173a3dda50b5668980808207aa36f3 Mon Sep 17 00:00:00 2001 From: Woeful_Wolf <54476280+WoefulWolf@users.noreply.github.com> Date: Sun, 28 Jan 2024 16:08:28 +0200 Subject: [PATCH 01/18] Added basic ray_entities and closest_line_seg_line_seg methods --- voxygen/src/session/target.rs | 157 ++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/voxygen/src/session/target.rs b/voxygen/src/session/target.rs index c61f5920b2..04921fe987 100644 --- a/voxygen/src/session/target.rs +++ b/voxygen/src/session/target.rs @@ -1,4 +1,7 @@ +use core::prelude::v1; + use specs::{Join, LendJoin, WorldExt}; +use tracing::{debug, info}; use vek::*; use client::{self, Client}; @@ -14,6 +17,8 @@ use common::{ }; use common_base::span; +use crate::scene::Scene; + #[derive(Clone, Copy, Debug)] pub struct Target { pub kind: T, @@ -238,3 +243,155 @@ pub(super) fn targets_under_cursor( terrain_target, ) } + +pub(super) fn ray_entities( + client: &Client, + start: Vec3, + end: Vec3, + cast_dist: f32, +) -> Option<(f32, Entity)> { + let player_entity = client.entity(); + let ecs = client.state().ecs(); + let positions = ecs.read_storage::(); + let colliders = ecs.read_storage::(); + + let mut nearby = ( + &ecs.entities(), + &positions, + &colliders, + ) + .join() + .filter(|(e, _, _)| *e != player_entity) + .filter_map(|(e, p, c)| { + let height = c.get_height(); + let radius = c.bounding_radius().max(height / 2.0); + // Move position up from the feet + let pos = Vec3::new(p.0.x, p.0.y, p.0.z + c.get_z_limits(1.0).0 + height/2.0); + // Distance squared from start to the entity + let dist_sqr = pos.distance_squared(start); + Some((e, pos, radius, dist_sqr, c)) + }) + // Roughly filter out entities farther than ray distance + .filter(|(_, _, _, d_sqr, c)| *d_sqr <= cast_dist.powi(2)) + .collect::>(); + // Sort by distance + nearby.sort_unstable_by(|a, b| a.3.partial_cmp(&b.3).unwrap()); + + let seg_ray = LineSegment3 { start, end }; + + let entity = nearby.iter().find_map(|(e, p, r, d_sqr, c)| { + let nearest = seg_ray.projected_point(*p); + + return match c { + comp::Collider::CapsulePrism { + p0, + p1, + radius, + z_min, + z_max, + } => { + if nearest.distance_squared(*p) < (r * 1.732).powi(2) { + // 1.732 = sqrt(3) + let entity_rotation = ecs + .read_storage::() + .get(*e) + .copied() + .unwrap_or_default(); + let entity_position = ecs.read_storage::().get(*e).copied().unwrap(); + let world_p0 = entity_position.0 + + (entity_rotation.to_quat() + * Vec3::new(p0.x, p0.y, z_min + c.get_height() / 2.0)); + let world_p1 = entity_position.0 + + (entity_rotation.to_quat() + * Vec3::new(p1.x, p1.y, z_min + c.get_height() / 2.0)); + + let (p_a, p_b) = if p0 != p1 { + let seg_capsule = LineSegment3 { + start: world_p0, + end: world_p1, + }; + closest_line_seg_line_seg( + seg_ray.start, + seg_ray.end, + seg_capsule.start, + seg_capsule.end, + ) + } else { + let nearest = seg_ray.projected_point(world_p0); + (nearest, world_p0) + }; + + let distance = p_a.xy().distance_squared(p_b.xy()); + + if distance < radius.powi(2) + && p_a.z >= entity_position.0.z + z_min + && p_a.z <= entity_position.0.z + z_max + { + return Some((p_a.distance(start), Entity(*e))); + } + } + None + }, + _ => { + if nearest.distance_squared(*p) < r.powi(2) { + return Some((nearest.distance(start), Entity(*e))); + } + None + }, + }; + }); + + return entity; +} + +// Get closest point between 2 line segments https://math.stackexchange.com/a/4289668 +fn closest_line_seg_line_seg( + p1: Vec3, + p2: Vec3, + p3: Vec3, + p4: Vec3, +) -> (Vec3, Vec3) { + let p1 = Vec3::new(p1.x as f64, p1.y as f64, p1.z as f64); + let p2 = Vec3::new(p2.x as f64, p2.y as f64, p2.z as f64); + let p3 = Vec3::new(p3.x as f64, p3.y as f64, p3.z as f64); + let p4 = Vec3::new(p4.x as f64, p4.y as f64, p4.z as f64); + + let P1 = p1; + let P2 = p3; + let V1 = p2 - p1; + let V2 = p4 - p3; + let V21 = P2 - P1; + + let v22 = V2.dot(V2); + let v11 = V1.dot(V1); + let v21 = V2.dot(V1); + let v21_1 = V21.dot(V1); + let v21_2 = V21.dot(V2); + + let denom = v21 * v21 - v22 * v11; + + let (s, t) = if denom == 0.0 { + let s = 0.0; + let t = (v11 * s - v21_1) / v21; + (s, t) + } else { + let s = (v21_2 * v21 - v22 * v21_1) / denom; + let t = (-v21_1 * v21 + v11 * v21_2) / denom; + (s, t) + }; + + let (s, t) = (s.clamp(0.0, 1.0), t.clamp(0.0, 1.0)); + + if s == 0.0 { + info!("p3: {}, p4: {}", p3, p4); + info!("s: {}, t: {}", s, t); + } + + let p_a = P1 + s * V1; + let p_b = P2 + t * V2; + + ( + Vec3::new(p_a.x as f32, p_a.y as f32, p_a.z as f32), + Vec3::new(p_b.x as f32, p_b.y as f32, p_b.z as f32), + ) +} From 6b6c5f18ec28f317e6cbd2ee5610a30be2717ccb Mon Sep 17 00:00:00 2001 From: Woeful_Wolf <54476280+WoefulWolf@users.noreply.github.com> Date: Sun, 28 Jan 2024 16:10:10 +0200 Subject: [PATCH 02/18] Added x and y camera offsets when aiming in third person --- voxygen/src/scene/mod.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index a813459769..ed31085b28 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -523,6 +523,7 @@ impl Scene { audio: &mut AudioFrontend, scene_data: &SceneData, client: &Client, + settings: &Settings, ) { span!(_guard, "maintain", "Scene::maintain"); // Get player position. @@ -629,15 +630,28 @@ impl Scene { viewpoint_eye_height } }, - CameraMode::ThirdPerson if scene_data.is_aiming => viewpoint_height * 1.16, + CameraMode::ThirdPerson if scene_data.is_aiming => { + viewpoint_height * 1.16 + settings.gameplay.aim_offset_y + }, CameraMode::ThirdPerson => viewpoint_eye_height, CameraMode::Freefly => 0.0, }; + + let right = match self.camera.get_mode() { + CameraMode::FirstPerson => 0.0, + CameraMode::ThirdPerson if scene_data.is_aiming => { + settings.gameplay.aim_offset_x + }, + CameraMode::ThirdPerson => 0.0, + CameraMode::Freefly => 0.0, + }; + // Alter camera position to match player. let tilt = self.camera.get_orientation().y; let dist = self.camera.get_distance(); Vec3::unit_z() * (up * viewpoint_scale - tilt.min(0.0).sin() * dist * 0.6) + + self.camera.right() * (right * viewpoint_scale) } else { self.figure_mgr .viewpoint_offset(scene_data, scene_data.viewpoint_entity) From 714dbc9c6f95228c596ebb92ba5d4efd9a3b5f89 Mon Sep 17 00:00:00 2001 From: Woeful_Wolf <54476280+WoefulWolf@users.noreply.github.com> Date: Sun, 28 Jan 2024 16:11:21 +0200 Subject: [PATCH 03/18] Added x and y aiming offsets to settings --- voxygen/src/hud/settings_window/gameplay.rs | 74 ++++++++++++++++++++- voxygen/src/session/settings_change.rs | 9 +++ voxygen/src/settings/gameplay.rs | 4 ++ 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/voxygen/src/hud/settings_window/gameplay.rs b/voxygen/src/hud/settings_window/gameplay.rs index d64d4614c6..6645e350a6 100644 --- a/voxygen/src/hud/settings_window/gameplay.rs +++ b/voxygen/src/hud/settings_window/gameplay.rs @@ -59,6 +59,12 @@ widget_ids! { bow_zoom_label, zoom_lock_button, zoom_lock_label, + aim_offset_x_slider, + aim_offset_x_label, + aim_offset_x_value, + aim_offset_y_slider, + aim_offset_y_label, + aim_offset_y_value, } } @@ -662,12 +668,78 @@ impl<'a> Widget for Gameplay<'a> { .color(TEXT_COLOR) .set(state.ids.zoom_lock_label, ui); + // Aim offset x + let display_aim_offset_x = self.global_state.settings.gameplay.aim_offset_x; + Text::new("Aim Offset X") + .down_from(state.ids.zoom_lock_behavior_list, 10.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.aim_offset_x_label, ui); + + if let Some(new_val) = ImageSlider::continuous( + display_aim_offset_x, + -3.0, + 3.0, + self.imgs.slider_indicator, + self.imgs.slider, + ) + .w_h(550.0, 22.0) + .down_from(state.ids.aim_offset_x_label, 10.0) + .track_breadth(30.0) + .slider_length(10.0) + .pad_track((5.0, 5.0)) + .set(state.ids.aim_offset_x_slider, ui) + { + events.push(AdjustAimOffsetX(new_val)); + } + + Text::new(&format!("{:.2}", display_aim_offset_x)) + .right_from(state.ids.aim_offset_x_slider, 8.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.aim_offset_x_value, ui); + + // Aim offset y + let display_aim_offset_y = self.global_state.settings.gameplay.aim_offset_y; + Text::new("Aim Offset Y") + .down_from(state.ids.aim_offset_x_slider, 10.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.aim_offset_y_label, ui); + + if let Some(new_val) = ImageSlider::continuous( + display_aim_offset_y, + -3.0, + 3.0, + self.imgs.slider_indicator, + self.imgs.slider, + ) + .w_h(550.0, 22.0) + .down_from(state.ids.aim_offset_y_label, 10.0) + .track_breadth(30.0) + .slider_length(10.0) + .pad_track((5.0, 5.0)) + .set(state.ids.aim_offset_y_slider, ui) + { + events.push(AdjustAimOffsetY(new_val)); + } + + Text::new(&format!("{:.2}", display_aim_offset_y)) + .right_from(state.ids.aim_offset_y_slider, 8.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.aim_offset_y_value, ui); + // Reset the gameplay settings to the default settings if Button::image(self.imgs.button) .w_h(RESET_BUTTONS_WIDTH, RESET_BUTTONS_HEIGHT) .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) - .down_from(state.ids.zoom_lock_behavior_list, 12.0) + .down_from(state.ids.aim_offset_y_slider, 12.0) .label( &self .localized_strings diff --git a/voxygen/src/session/settings_change.rs b/voxygen/src/session/settings_change.rs index a4be0be0d8..0e7096230a 100644 --- a/voxygen/src/session/settings_change.rs +++ b/voxygen/src/session/settings_change.rs @@ -77,6 +77,9 @@ pub enum Gameplay { ChangeBowZoom(bool), ChangeZoomLock(bool), + AdjustAimOffsetX(f32), + AdjustAimOffsetY(f32), + ResetGameplaySettings, } #[derive(Clone)] @@ -427,6 +430,12 @@ impl SettingsChange { Gameplay::ChangeZoomLock(state) => { settings.gameplay.zoom_lock = state; }, + Gameplay::AdjustAimOffsetX(offset) => { + settings.gameplay.aim_offset_x = offset; + }, + Gameplay::AdjustAimOffsetY(offset) => { + settings.gameplay.aim_offset_y = offset; + }, Gameplay::ResetGameplaySettings => { // Reset Gameplay Settings settings.gameplay = GameplaySettings::default(); diff --git a/voxygen/src/settings/gameplay.rs b/voxygen/src/settings/gameplay.rs index ebb06d6627..839d50c260 100644 --- a/voxygen/src/settings/gameplay.rs +++ b/voxygen/src/settings/gameplay.rs @@ -21,6 +21,8 @@ pub struct GameplaySettings { pub auto_camera: bool, pub bow_zoom: bool, pub zoom_lock: bool, + pub aim_offset_x: f32, + pub aim_offset_y: f32, } impl Default for GameplaySettings { @@ -42,6 +44,8 @@ impl Default for GameplaySettings { auto_camera: false, bow_zoom: true, zoom_lock: false, + aim_offset_x: 1.0, + aim_offset_y: 0.0, } } } From ca022a1d1bcfd0b09787c2d8f3d51baca52f0d42 Mon Sep 17 00:00:00 2001 From: Woeful_Wolf <54476280+WoefulWolf@users.noreply.github.com> Date: Sun, 28 Jan 2024 16:12:25 +0200 Subject: [PATCH 04/18] Changed how look_dir is calculated when aiming, now ray casts entities and terrain to find target point --- voxygen/src/session/mod.rs | 72 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 1ec6afb16e..24199f1c40 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -50,6 +50,7 @@ use crate::{ menu::char_selection::CharSelectionState, render::{Drawer, GlobalsBindGroup}, scene::{camera, CameraMode, DebugShapeId, Scene, SceneData}, + session::target::ray_entities, settings::Settings, window::{AnalogGameInput, Event}, Direction, GlobalState, PlayState, PlayStateResult, @@ -1349,8 +1350,74 @@ impl PlayState for SessionState { if !self.free_look { self.walk_forward_dir = self.scene.camera().forward_xy(); self.walk_right_dir = self.scene.camera().right_xy(); - self.inputs.look_dir = - Dir::from_unnormalized(cam_dir + aim_dir_offset).unwrap(); + + let dir = if is_aiming { + let client = self.client.borrow(); + // Shoot ray from camera forward direction and get the point it hits an + // entity or terrain + let ray_start = cam_pos + cam_dir * 2.0; + let entity_ray_end = ray_start + cam_dir * 500.0; + let terrain_ray_end = ray_start + cam_dir * 1000.0; + + let aim_point = match ray_entities( + &client, + ray_start, + entity_ray_end, + 500.0, + ) { + Some((dist, _)) => ray_start + cam_dir * dist, + None => { + let terrain_ray_distance = client + .state() + .terrain() + .ray(ray_start, terrain_ray_end) + .max_iter(1000) + .until(Block::is_solid) + .cast() + .0; + ray_start + cam_dir * terrain_ray_distance + }, + }; + + // Get player orientation + let ori = client + .state() + .read_storage::() + .get(player_entity) + .copied() + .unwrap(); + // Get player scale + let scale = client + .state() + .read_storage::() + .get(player_entity) + .copied() + .unwrap_or(comp::Scale(1.0)); + // Get player body offsets + let body = client + .state() + .read_storage::() + .get(player_entity) + .copied() + .unwrap(); + let body_offsets = body.projectile_offsets(ori.look_vec(), scale.0); + + // Get direction from player character to aim point + let player_pos = client + .state() + .read_storage::() + .get(player_entity) + .copied() + .unwrap(); + // self.scene.debug.add_shape(crate::scene::DebugShape::Line([aim_point, + // (player_pos.0 + Vec3 { x: 0.0, y: 0.0, z: 1000.0 })])); + drop(client); + aim_point - (player_pos.0 + body_offsets) + } else { + cam_dir + aim_dir_offset + }; + + self.inputs.look_dir = Dir::from_unnormalized(dir).unwrap(); } } self.inputs.strafing = matches!( @@ -2040,6 +2107,7 @@ impl PlayState for SessionState { &mut global_state.audio, &scene_data, &client, + &global_state.settings, ); // Process outcomes from client From a262521875e3cc23f995361af7bb740e8cde1c97 Mon Sep 17 00:00:00 2001 From: Woeful_Wolf <54476280+WoefulWolf@users.noreply.github.com> Date: Sun, 28 Jan 2024 16:34:52 +0200 Subject: [PATCH 05/18] Cleaned up some extra imports and debug code --- voxygen/src/session/mod.rs | 3 +-- voxygen/src/session/target.rs | 32 ++++++++++---------------------- 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 24199f1c40..c64f885aa2 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -1409,8 +1409,7 @@ impl PlayState for SessionState { .get(player_entity) .copied() .unwrap(); - // self.scene.debug.add_shape(crate::scene::DebugShape::Line([aim_point, - // (player_pos.0 + Vec3 { x: 0.0, y: 0.0, z: 1000.0 })])); + drop(client); aim_point - (player_pos.0 + body_offsets) } else { diff --git a/voxygen/src/session/target.rs b/voxygen/src/session/target.rs index 04921fe987..5256577840 100644 --- a/voxygen/src/session/target.rs +++ b/voxygen/src/session/target.rs @@ -1,7 +1,4 @@ -use core::prelude::v1; - use specs::{Join, LendJoin, WorldExt}; -use tracing::{debug, info}; use vek::*; use client::{self, Client}; @@ -17,8 +14,6 @@ use common::{ }; use common_base::span; -use crate::scene::Scene; - #[derive(Clone, Copy, Debug)] pub struct Target { pub kind: T, @@ -356,17 +351,15 @@ fn closest_line_seg_line_seg( let p3 = Vec3::new(p3.x as f64, p3.y as f64, p3.z as f64); let p4 = Vec3::new(p4.x as f64, p4.y as f64, p4.z as f64); - let P1 = p1; - let P2 = p3; - let V1 = p2 - p1; - let V2 = p4 - p3; - let V21 = P2 - P1; + let d1 = p2 - p1; + let d2 = p4 - p3; + let d21 = p3 - p1; - let v22 = V2.dot(V2); - let v11 = V1.dot(V1); - let v21 = V2.dot(V1); - let v21_1 = V21.dot(V1); - let v21_2 = V21.dot(V2); + let v22 = d2.dot(d2); + let v11 = d1.dot(d1); + let v21 = d2.dot(d1); + let v21_1 = d21.dot(d1); + let v21_2 = d21.dot(d2); let denom = v21 * v21 - v22 * v11; @@ -382,13 +375,8 @@ fn closest_line_seg_line_seg( let (s, t) = (s.clamp(0.0, 1.0), t.clamp(0.0, 1.0)); - if s == 0.0 { - info!("p3: {}, p4: {}", p3, p4); - info!("s: {}, t: {}", s, t); - } - - let p_a = P1 + s * V1; - let p_b = P2 + t * V2; + let p_a = p1 + s * d1; + let p_b = p3 + t * d2; ( Vec3::new(p_a.x as f32, p_a.y as f32, p_a.z as f32), From 29550f99628ca9117838bc86bf11a60251a12aef Mon Sep 17 00:00:00 2001 From: Woeful_Wolf <54476280+WoefulWolf@users.noreply.github.com> Date: Sun, 28 Jan 2024 16:51:00 +0200 Subject: [PATCH 06/18] code-quality edits --- voxygen/src/session/mod.rs | 34 +++++++++++++++------------------- voxygen/src/session/target.rs | 10 +++++----- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index c64f885aa2..37561f0f65 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -1359,25 +1359,21 @@ impl PlayState for SessionState { let entity_ray_end = ray_start + cam_dir * 500.0; let terrain_ray_end = ray_start + cam_dir * 1000.0; - let aim_point = match ray_entities( - &client, - ray_start, - entity_ray_end, - 500.0, - ) { - Some((dist, _)) => ray_start + cam_dir * dist, - None => { - let terrain_ray_distance = client - .state() - .terrain() - .ray(ray_start, terrain_ray_end) - .max_iter(1000) - .until(Block::is_solid) - .cast() - .0; - ray_start + cam_dir * terrain_ray_distance - }, - }; + let aim_point = + match ray_entities(&client, ray_start, entity_ray_end, 500.0) { + Some((dist, _)) => ray_start + cam_dir * dist, + None => { + let terrain_ray_distance = client + .state() + .terrain() + .ray(ray_start, terrain_ray_end) + .max_iter(1000) + .until(Block::is_solid) + .cast() + .0; + ray_start + cam_dir * terrain_ray_distance + }, + }; // Get player orientation let ori = client diff --git a/voxygen/src/session/target.rs b/voxygen/src/session/target.rs index 5256577840..70fd3c015a 100644 --- a/voxygen/src/session/target.rs +++ b/voxygen/src/session/target.rs @@ -257,24 +257,24 @@ pub(super) fn ray_entities( ) .join() .filter(|(e, _, _)| *e != player_entity) - .filter_map(|(e, p, c)| { + .map(|(e, p, c)| { let height = c.get_height(); let radius = c.bounding_radius().max(height / 2.0); // Move position up from the feet let pos = Vec3::new(p.0.x, p.0.y, p.0.z + c.get_z_limits(1.0).0 + height/2.0); // Distance squared from start to the entity let dist_sqr = pos.distance_squared(start); - Some((e, pos, radius, dist_sqr, c)) + (e, pos, radius, dist_sqr, c) }) // Roughly filter out entities farther than ray distance - .filter(|(_, _, _, d_sqr, c)| *d_sqr <= cast_dist.powi(2)) + .filter(|(_, _, _, d_sqr, _)| *d_sqr <= cast_dist.powi(2)) .collect::>(); // Sort by distance nearby.sort_unstable_by(|a, b| a.3.partial_cmp(&b.3).unwrap()); let seg_ray = LineSegment3 { start, end }; - let entity = nearby.iter().find_map(|(e, p, r, d_sqr, c)| { + let entity = nearby.iter().find_map(|(e, p, r, _, c)| { let nearest = seg_ray.projected_point(*p); return match c { @@ -336,7 +336,7 @@ pub(super) fn ray_entities( }; }); - return entity; + entity } // Get closest point between 2 line segments https://math.stackexchange.com/a/4289668 From 9af75e1fc920492c6bed14bcba87a6e3b192253e Mon Sep 17 00:00:00 2001 From: Woeful_Wolf <54476280+WoefulWolf@users.noreply.github.com> Date: Sun, 28 Jan 2024 18:00:56 +0200 Subject: [PATCH 07/18] Settings labels use i18n --- assets/voxygen/i18n/en/hud/settings.ftl | 4 +++- voxygen/src/hud/settings_window/gameplay.rs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/assets/voxygen/i18n/en/hud/settings.ftl b/assets/voxygen/i18n/en/hud/settings.ftl index b61b457e7d..2a3d61bca0 100644 --- a/assets/voxygen/i18n/en/hud/settings.ftl +++ b/assets/voxygen/i18n/en/hud/settings.ftl @@ -57,6 +57,8 @@ hud-settings-walking_speed_behavior = Walking speed behavior hud-settings-walking_speed = Walking speed hud-settings-camera_clamp_behavior = Camera clamp behavior hud-settings-zoom_lock_behavior = Camera zoom lock behavior +hud-settings-aim_offset_x = Horizontal Aim Offset +hud-settings-aim_offset_y = Vertical Aim Offset hud-settings-player_physics_behavior = Player physics (experimental) hud-settings-stop_auto_walk_on_input = Stop auto walk on movement hud-settings-auto_camera = Auto camera @@ -158,4 +160,4 @@ hud-settings-group_only = Group only hud-settings-reset_chat = Reset to Defaults hud-settings-third_party_integrations = Third-party Integrations hud-settings-enable_discord_integration = Enable Discord Integration -hud-settings-subtitles = Subtitles +hud-settings-subtitles = Subtitles \ No newline at end of file diff --git a/voxygen/src/hud/settings_window/gameplay.rs b/voxygen/src/hud/settings_window/gameplay.rs index 6645e350a6..bd3d641af0 100644 --- a/voxygen/src/hud/settings_window/gameplay.rs +++ b/voxygen/src/hud/settings_window/gameplay.rs @@ -670,7 +670,7 @@ impl<'a> Widget for Gameplay<'a> { // Aim offset x let display_aim_offset_x = self.global_state.settings.gameplay.aim_offset_x; - Text::new("Aim Offset X") + Text::new(&self.localized_strings.get_msg("hud-settings-aim_offset_x")) .down_from(state.ids.zoom_lock_behavior_list, 10.0) .font_size(self.fonts.cyri.scale(14)) .font_id(self.fonts.cyri.conrod_id) @@ -703,7 +703,7 @@ impl<'a> Widget for Gameplay<'a> { // Aim offset y let display_aim_offset_y = self.global_state.settings.gameplay.aim_offset_y; - Text::new("Aim Offset Y") + Text::new(&self.localized_strings.get_msg("hud-settings-aim_offset_y")) .down_from(state.ids.aim_offset_x_slider, 10.0) .font_size(self.fonts.cyri.scale(14)) .font_id(self.fonts.cyri.conrod_id) From c78f6c0bc2f39b36e1f2250f32fdfbc48862452e Mon Sep 17 00:00:00 2001 From: Woeful_Wolf <54476280+WoefulWolf@users.noreply.github.com> Date: Sun, 28 Jan 2024 16:08:28 +0200 Subject: [PATCH 08/18] Added basic ray_entities and closest_line_seg_line_seg methods --- voxygen/src/session/target.rs | 157 ++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/voxygen/src/session/target.rs b/voxygen/src/session/target.rs index c61f5920b2..04921fe987 100644 --- a/voxygen/src/session/target.rs +++ b/voxygen/src/session/target.rs @@ -1,4 +1,7 @@ +use core::prelude::v1; + use specs::{Join, LendJoin, WorldExt}; +use tracing::{debug, info}; use vek::*; use client::{self, Client}; @@ -14,6 +17,8 @@ use common::{ }; use common_base::span; +use crate::scene::Scene; + #[derive(Clone, Copy, Debug)] pub struct Target { pub kind: T, @@ -238,3 +243,155 @@ pub(super) fn targets_under_cursor( terrain_target, ) } + +pub(super) fn ray_entities( + client: &Client, + start: Vec3, + end: Vec3, + cast_dist: f32, +) -> Option<(f32, Entity)> { + let player_entity = client.entity(); + let ecs = client.state().ecs(); + let positions = ecs.read_storage::(); + let colliders = ecs.read_storage::(); + + let mut nearby = ( + &ecs.entities(), + &positions, + &colliders, + ) + .join() + .filter(|(e, _, _)| *e != player_entity) + .filter_map(|(e, p, c)| { + let height = c.get_height(); + let radius = c.bounding_radius().max(height / 2.0); + // Move position up from the feet + let pos = Vec3::new(p.0.x, p.0.y, p.0.z + c.get_z_limits(1.0).0 + height/2.0); + // Distance squared from start to the entity + let dist_sqr = pos.distance_squared(start); + Some((e, pos, radius, dist_sqr, c)) + }) + // Roughly filter out entities farther than ray distance + .filter(|(_, _, _, d_sqr, c)| *d_sqr <= cast_dist.powi(2)) + .collect::>(); + // Sort by distance + nearby.sort_unstable_by(|a, b| a.3.partial_cmp(&b.3).unwrap()); + + let seg_ray = LineSegment3 { start, end }; + + let entity = nearby.iter().find_map(|(e, p, r, d_sqr, c)| { + let nearest = seg_ray.projected_point(*p); + + return match c { + comp::Collider::CapsulePrism { + p0, + p1, + radius, + z_min, + z_max, + } => { + if nearest.distance_squared(*p) < (r * 1.732).powi(2) { + // 1.732 = sqrt(3) + let entity_rotation = ecs + .read_storage::() + .get(*e) + .copied() + .unwrap_or_default(); + let entity_position = ecs.read_storage::().get(*e).copied().unwrap(); + let world_p0 = entity_position.0 + + (entity_rotation.to_quat() + * Vec3::new(p0.x, p0.y, z_min + c.get_height() / 2.0)); + let world_p1 = entity_position.0 + + (entity_rotation.to_quat() + * Vec3::new(p1.x, p1.y, z_min + c.get_height() / 2.0)); + + let (p_a, p_b) = if p0 != p1 { + let seg_capsule = LineSegment3 { + start: world_p0, + end: world_p1, + }; + closest_line_seg_line_seg( + seg_ray.start, + seg_ray.end, + seg_capsule.start, + seg_capsule.end, + ) + } else { + let nearest = seg_ray.projected_point(world_p0); + (nearest, world_p0) + }; + + let distance = p_a.xy().distance_squared(p_b.xy()); + + if distance < radius.powi(2) + && p_a.z >= entity_position.0.z + z_min + && p_a.z <= entity_position.0.z + z_max + { + return Some((p_a.distance(start), Entity(*e))); + } + } + None + }, + _ => { + if nearest.distance_squared(*p) < r.powi(2) { + return Some((nearest.distance(start), Entity(*e))); + } + None + }, + }; + }); + + return entity; +} + +// Get closest point between 2 line segments https://math.stackexchange.com/a/4289668 +fn closest_line_seg_line_seg( + p1: Vec3, + p2: Vec3, + p3: Vec3, + p4: Vec3, +) -> (Vec3, Vec3) { + let p1 = Vec3::new(p1.x as f64, p1.y as f64, p1.z as f64); + let p2 = Vec3::new(p2.x as f64, p2.y as f64, p2.z as f64); + let p3 = Vec3::new(p3.x as f64, p3.y as f64, p3.z as f64); + let p4 = Vec3::new(p4.x as f64, p4.y as f64, p4.z as f64); + + let P1 = p1; + let P2 = p3; + let V1 = p2 - p1; + let V2 = p4 - p3; + let V21 = P2 - P1; + + let v22 = V2.dot(V2); + let v11 = V1.dot(V1); + let v21 = V2.dot(V1); + let v21_1 = V21.dot(V1); + let v21_2 = V21.dot(V2); + + let denom = v21 * v21 - v22 * v11; + + let (s, t) = if denom == 0.0 { + let s = 0.0; + let t = (v11 * s - v21_1) / v21; + (s, t) + } else { + let s = (v21_2 * v21 - v22 * v21_1) / denom; + let t = (-v21_1 * v21 + v11 * v21_2) / denom; + (s, t) + }; + + let (s, t) = (s.clamp(0.0, 1.0), t.clamp(0.0, 1.0)); + + if s == 0.0 { + info!("p3: {}, p4: {}", p3, p4); + info!("s: {}, t: {}", s, t); + } + + let p_a = P1 + s * V1; + let p_b = P2 + t * V2; + + ( + Vec3::new(p_a.x as f32, p_a.y as f32, p_a.z as f32), + Vec3::new(p_b.x as f32, p_b.y as f32, p_b.z as f32), + ) +} From 43566bca3212719e1b0fb587801c94f2e15ca987 Mon Sep 17 00:00:00 2001 From: Woeful_Wolf <54476280+WoefulWolf@users.noreply.github.com> Date: Sun, 28 Jan 2024 16:10:10 +0200 Subject: [PATCH 09/18] Added x and y camera offsets when aiming in third person --- voxygen/src/scene/mod.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 0b9aa85653..dee86e5e85 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -526,6 +526,7 @@ impl Scene { audio: &mut AudioFrontend, scene_data: &SceneData, client: &Client, + settings: &Settings, ) { span!(_guard, "maintain", "Scene::maintain"); // Get player position. @@ -632,15 +633,28 @@ impl Scene { viewpoint_eye_height } }, - CameraMode::ThirdPerson if scene_data.is_aiming => viewpoint_height * 1.16, + CameraMode::ThirdPerson if scene_data.is_aiming => { + viewpoint_height * 1.16 + settings.gameplay.aim_offset_y + }, CameraMode::ThirdPerson => viewpoint_eye_height, CameraMode::Freefly => 0.0, }; + + let right = match self.camera.get_mode() { + CameraMode::FirstPerson => 0.0, + CameraMode::ThirdPerson if scene_data.is_aiming => { + settings.gameplay.aim_offset_x + }, + CameraMode::ThirdPerson => 0.0, + CameraMode::Freefly => 0.0, + }; + // Alter camera position to match player. let tilt = self.camera.get_orientation().y; let dist = self.camera.get_distance(); Vec3::unit_z() * (up * viewpoint_scale - tilt.min(0.0).sin() * dist * 0.6) + + self.camera.right() * (right * viewpoint_scale) } else { self.figure_mgr .viewpoint_offset(scene_data, scene_data.viewpoint_entity) From e270992bda5dcb2e318ae1de121a386019ef7b88 Mon Sep 17 00:00:00 2001 From: Woeful_Wolf <54476280+WoefulWolf@users.noreply.github.com> Date: Sun, 28 Jan 2024 16:11:21 +0200 Subject: [PATCH 10/18] Added x and y aiming offsets to settings --- voxygen/src/hud/settings_window/gameplay.rs | 74 ++++++++++++++++++++- voxygen/src/session/settings_change.rs | 9 +++ voxygen/src/settings/gameplay.rs | 4 ++ 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/voxygen/src/hud/settings_window/gameplay.rs b/voxygen/src/hud/settings_window/gameplay.rs index d64d4614c6..6645e350a6 100644 --- a/voxygen/src/hud/settings_window/gameplay.rs +++ b/voxygen/src/hud/settings_window/gameplay.rs @@ -59,6 +59,12 @@ widget_ids! { bow_zoom_label, zoom_lock_button, zoom_lock_label, + aim_offset_x_slider, + aim_offset_x_label, + aim_offset_x_value, + aim_offset_y_slider, + aim_offset_y_label, + aim_offset_y_value, } } @@ -662,12 +668,78 @@ impl<'a> Widget for Gameplay<'a> { .color(TEXT_COLOR) .set(state.ids.zoom_lock_label, ui); + // Aim offset x + let display_aim_offset_x = self.global_state.settings.gameplay.aim_offset_x; + Text::new("Aim Offset X") + .down_from(state.ids.zoom_lock_behavior_list, 10.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.aim_offset_x_label, ui); + + if let Some(new_val) = ImageSlider::continuous( + display_aim_offset_x, + -3.0, + 3.0, + self.imgs.slider_indicator, + self.imgs.slider, + ) + .w_h(550.0, 22.0) + .down_from(state.ids.aim_offset_x_label, 10.0) + .track_breadth(30.0) + .slider_length(10.0) + .pad_track((5.0, 5.0)) + .set(state.ids.aim_offset_x_slider, ui) + { + events.push(AdjustAimOffsetX(new_val)); + } + + Text::new(&format!("{:.2}", display_aim_offset_x)) + .right_from(state.ids.aim_offset_x_slider, 8.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.aim_offset_x_value, ui); + + // Aim offset y + let display_aim_offset_y = self.global_state.settings.gameplay.aim_offset_y; + Text::new("Aim Offset Y") + .down_from(state.ids.aim_offset_x_slider, 10.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.aim_offset_y_label, ui); + + if let Some(new_val) = ImageSlider::continuous( + display_aim_offset_y, + -3.0, + 3.0, + self.imgs.slider_indicator, + self.imgs.slider, + ) + .w_h(550.0, 22.0) + .down_from(state.ids.aim_offset_y_label, 10.0) + .track_breadth(30.0) + .slider_length(10.0) + .pad_track((5.0, 5.0)) + .set(state.ids.aim_offset_y_slider, ui) + { + events.push(AdjustAimOffsetY(new_val)); + } + + Text::new(&format!("{:.2}", display_aim_offset_y)) + .right_from(state.ids.aim_offset_y_slider, 8.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.aim_offset_y_value, ui); + // Reset the gameplay settings to the default settings if Button::image(self.imgs.button) .w_h(RESET_BUTTONS_WIDTH, RESET_BUTTONS_HEIGHT) .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) - .down_from(state.ids.zoom_lock_behavior_list, 12.0) + .down_from(state.ids.aim_offset_y_slider, 12.0) .label( &self .localized_strings diff --git a/voxygen/src/session/settings_change.rs b/voxygen/src/session/settings_change.rs index a4be0be0d8..0e7096230a 100644 --- a/voxygen/src/session/settings_change.rs +++ b/voxygen/src/session/settings_change.rs @@ -77,6 +77,9 @@ pub enum Gameplay { ChangeBowZoom(bool), ChangeZoomLock(bool), + AdjustAimOffsetX(f32), + AdjustAimOffsetY(f32), + ResetGameplaySettings, } #[derive(Clone)] @@ -427,6 +430,12 @@ impl SettingsChange { Gameplay::ChangeZoomLock(state) => { settings.gameplay.zoom_lock = state; }, + Gameplay::AdjustAimOffsetX(offset) => { + settings.gameplay.aim_offset_x = offset; + }, + Gameplay::AdjustAimOffsetY(offset) => { + settings.gameplay.aim_offset_y = offset; + }, Gameplay::ResetGameplaySettings => { // Reset Gameplay Settings settings.gameplay = GameplaySettings::default(); diff --git a/voxygen/src/settings/gameplay.rs b/voxygen/src/settings/gameplay.rs index ebb06d6627..839d50c260 100644 --- a/voxygen/src/settings/gameplay.rs +++ b/voxygen/src/settings/gameplay.rs @@ -21,6 +21,8 @@ pub struct GameplaySettings { pub auto_camera: bool, pub bow_zoom: bool, pub zoom_lock: bool, + pub aim_offset_x: f32, + pub aim_offset_y: f32, } impl Default for GameplaySettings { @@ -42,6 +44,8 @@ impl Default for GameplaySettings { auto_camera: false, bow_zoom: true, zoom_lock: false, + aim_offset_x: 1.0, + aim_offset_y: 0.0, } } } From 1660d703ac8320b8ddf431a7054bbf594ebdd44c Mon Sep 17 00:00:00 2001 From: Woeful_Wolf <54476280+WoefulWolf@users.noreply.github.com> Date: Sun, 28 Jan 2024 16:12:25 +0200 Subject: [PATCH 11/18] Changed how look_dir is calculated when aiming, now ray casts entities and terrain to find target point --- voxygen/src/session/mod.rs | 72 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 1ec6afb16e..24199f1c40 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -50,6 +50,7 @@ use crate::{ menu::char_selection::CharSelectionState, render::{Drawer, GlobalsBindGroup}, scene::{camera, CameraMode, DebugShapeId, Scene, SceneData}, + session::target::ray_entities, settings::Settings, window::{AnalogGameInput, Event}, Direction, GlobalState, PlayState, PlayStateResult, @@ -1349,8 +1350,74 @@ impl PlayState for SessionState { if !self.free_look { self.walk_forward_dir = self.scene.camera().forward_xy(); self.walk_right_dir = self.scene.camera().right_xy(); - self.inputs.look_dir = - Dir::from_unnormalized(cam_dir + aim_dir_offset).unwrap(); + + let dir = if is_aiming { + let client = self.client.borrow(); + // Shoot ray from camera forward direction and get the point it hits an + // entity or terrain + let ray_start = cam_pos + cam_dir * 2.0; + let entity_ray_end = ray_start + cam_dir * 500.0; + let terrain_ray_end = ray_start + cam_dir * 1000.0; + + let aim_point = match ray_entities( + &client, + ray_start, + entity_ray_end, + 500.0, + ) { + Some((dist, _)) => ray_start + cam_dir * dist, + None => { + let terrain_ray_distance = client + .state() + .terrain() + .ray(ray_start, terrain_ray_end) + .max_iter(1000) + .until(Block::is_solid) + .cast() + .0; + ray_start + cam_dir * terrain_ray_distance + }, + }; + + // Get player orientation + let ori = client + .state() + .read_storage::() + .get(player_entity) + .copied() + .unwrap(); + // Get player scale + let scale = client + .state() + .read_storage::() + .get(player_entity) + .copied() + .unwrap_or(comp::Scale(1.0)); + // Get player body offsets + let body = client + .state() + .read_storage::() + .get(player_entity) + .copied() + .unwrap(); + let body_offsets = body.projectile_offsets(ori.look_vec(), scale.0); + + // Get direction from player character to aim point + let player_pos = client + .state() + .read_storage::() + .get(player_entity) + .copied() + .unwrap(); + // self.scene.debug.add_shape(crate::scene::DebugShape::Line([aim_point, + // (player_pos.0 + Vec3 { x: 0.0, y: 0.0, z: 1000.0 })])); + drop(client); + aim_point - (player_pos.0 + body_offsets) + } else { + cam_dir + aim_dir_offset + }; + + self.inputs.look_dir = Dir::from_unnormalized(dir).unwrap(); } } self.inputs.strafing = matches!( @@ -2040,6 +2107,7 @@ impl PlayState for SessionState { &mut global_state.audio, &scene_data, &client, + &global_state.settings, ); // Process outcomes from client From 5b00d41cd4abf9ecbeaf8a784bae8e00816f0612 Mon Sep 17 00:00:00 2001 From: Woeful_Wolf <54476280+WoefulWolf@users.noreply.github.com> Date: Sun, 28 Jan 2024 16:34:52 +0200 Subject: [PATCH 12/18] Cleaned up some extra imports and debug code --- voxygen/src/session/mod.rs | 3 +-- voxygen/src/session/target.rs | 32 ++++++++++---------------------- 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 24199f1c40..c64f885aa2 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -1409,8 +1409,7 @@ impl PlayState for SessionState { .get(player_entity) .copied() .unwrap(); - // self.scene.debug.add_shape(crate::scene::DebugShape::Line([aim_point, - // (player_pos.0 + Vec3 { x: 0.0, y: 0.0, z: 1000.0 })])); + drop(client); aim_point - (player_pos.0 + body_offsets) } else { diff --git a/voxygen/src/session/target.rs b/voxygen/src/session/target.rs index 04921fe987..5256577840 100644 --- a/voxygen/src/session/target.rs +++ b/voxygen/src/session/target.rs @@ -1,7 +1,4 @@ -use core::prelude::v1; - use specs::{Join, LendJoin, WorldExt}; -use tracing::{debug, info}; use vek::*; use client::{self, Client}; @@ -17,8 +14,6 @@ use common::{ }; use common_base::span; -use crate::scene::Scene; - #[derive(Clone, Copy, Debug)] pub struct Target { pub kind: T, @@ -356,17 +351,15 @@ fn closest_line_seg_line_seg( let p3 = Vec3::new(p3.x as f64, p3.y as f64, p3.z as f64); let p4 = Vec3::new(p4.x as f64, p4.y as f64, p4.z as f64); - let P1 = p1; - let P2 = p3; - let V1 = p2 - p1; - let V2 = p4 - p3; - let V21 = P2 - P1; + let d1 = p2 - p1; + let d2 = p4 - p3; + let d21 = p3 - p1; - let v22 = V2.dot(V2); - let v11 = V1.dot(V1); - let v21 = V2.dot(V1); - let v21_1 = V21.dot(V1); - let v21_2 = V21.dot(V2); + let v22 = d2.dot(d2); + let v11 = d1.dot(d1); + let v21 = d2.dot(d1); + let v21_1 = d21.dot(d1); + let v21_2 = d21.dot(d2); let denom = v21 * v21 - v22 * v11; @@ -382,13 +375,8 @@ fn closest_line_seg_line_seg( let (s, t) = (s.clamp(0.0, 1.0), t.clamp(0.0, 1.0)); - if s == 0.0 { - info!("p3: {}, p4: {}", p3, p4); - info!("s: {}, t: {}", s, t); - } - - let p_a = P1 + s * V1; - let p_b = P2 + t * V2; + let p_a = p1 + s * d1; + let p_b = p3 + t * d2; ( Vec3::new(p_a.x as f32, p_a.y as f32, p_a.z as f32), From f4aa9ecbe3bb5c42ce686f3aac785014fb26c649 Mon Sep 17 00:00:00 2001 From: Woeful_Wolf <54476280+WoefulWolf@users.noreply.github.com> Date: Sun, 28 Jan 2024 16:51:00 +0200 Subject: [PATCH 13/18] code-quality edits --- voxygen/src/session/mod.rs | 34 +++++++++++++++------------------- voxygen/src/session/target.rs | 10 +++++----- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index c64f885aa2..37561f0f65 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -1359,25 +1359,21 @@ impl PlayState for SessionState { let entity_ray_end = ray_start + cam_dir * 500.0; let terrain_ray_end = ray_start + cam_dir * 1000.0; - let aim_point = match ray_entities( - &client, - ray_start, - entity_ray_end, - 500.0, - ) { - Some((dist, _)) => ray_start + cam_dir * dist, - None => { - let terrain_ray_distance = client - .state() - .terrain() - .ray(ray_start, terrain_ray_end) - .max_iter(1000) - .until(Block::is_solid) - .cast() - .0; - ray_start + cam_dir * terrain_ray_distance - }, - }; + let aim_point = + match ray_entities(&client, ray_start, entity_ray_end, 500.0) { + Some((dist, _)) => ray_start + cam_dir * dist, + None => { + let terrain_ray_distance = client + .state() + .terrain() + .ray(ray_start, terrain_ray_end) + .max_iter(1000) + .until(Block::is_solid) + .cast() + .0; + ray_start + cam_dir * terrain_ray_distance + }, + }; // Get player orientation let ori = client diff --git a/voxygen/src/session/target.rs b/voxygen/src/session/target.rs index 5256577840..70fd3c015a 100644 --- a/voxygen/src/session/target.rs +++ b/voxygen/src/session/target.rs @@ -257,24 +257,24 @@ pub(super) fn ray_entities( ) .join() .filter(|(e, _, _)| *e != player_entity) - .filter_map(|(e, p, c)| { + .map(|(e, p, c)| { let height = c.get_height(); let radius = c.bounding_radius().max(height / 2.0); // Move position up from the feet let pos = Vec3::new(p.0.x, p.0.y, p.0.z + c.get_z_limits(1.0).0 + height/2.0); // Distance squared from start to the entity let dist_sqr = pos.distance_squared(start); - Some((e, pos, radius, dist_sqr, c)) + (e, pos, radius, dist_sqr, c) }) // Roughly filter out entities farther than ray distance - .filter(|(_, _, _, d_sqr, c)| *d_sqr <= cast_dist.powi(2)) + .filter(|(_, _, _, d_sqr, _)| *d_sqr <= cast_dist.powi(2)) .collect::>(); // Sort by distance nearby.sort_unstable_by(|a, b| a.3.partial_cmp(&b.3).unwrap()); let seg_ray = LineSegment3 { start, end }; - let entity = nearby.iter().find_map(|(e, p, r, d_sqr, c)| { + let entity = nearby.iter().find_map(|(e, p, r, _, c)| { let nearest = seg_ray.projected_point(*p); return match c { @@ -336,7 +336,7 @@ pub(super) fn ray_entities( }; }); - return entity; + entity } // Get closest point between 2 line segments https://math.stackexchange.com/a/4289668 From c89e3f2b5eca242dc81195cc075a559f52ee944e Mon Sep 17 00:00:00 2001 From: Woeful_Wolf <54476280+WoefulWolf@users.noreply.github.com> Date: Sun, 28 Jan 2024 18:00:56 +0200 Subject: [PATCH 14/18] Settings labels use i18n --- assets/voxygen/i18n/en/hud/settings.ftl | 4 +++- voxygen/src/hud/settings_window/gameplay.rs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/assets/voxygen/i18n/en/hud/settings.ftl b/assets/voxygen/i18n/en/hud/settings.ftl index c5a4866dbf..f0e3d6c693 100644 --- a/assets/voxygen/i18n/en/hud/settings.ftl +++ b/assets/voxygen/i18n/en/hud/settings.ftl @@ -57,6 +57,8 @@ hud-settings-walking_speed_behavior = Walking speed behavior hud-settings-walking_speed = Walking speed hud-settings-camera_clamp_behavior = Camera clamp behavior hud-settings-zoom_lock_behavior = Camera zoom lock behavior +hud-settings-aim_offset_x = Horizontal Aim Offset +hud-settings-aim_offset_y = Vertical Aim Offset hud-settings-player_physics_behavior = Player physics (experimental) hud-settings-stop_auto_walk_on_input = Stop auto walk on movement hud-settings-auto_camera = Auto camera @@ -159,4 +161,4 @@ hud-settings-group_only = Group only hud-settings-reset_chat = Reset to Defaults hud-settings-third_party_integrations = Third-party Integrations hud-settings-enable_discord_integration = Enable Discord Integration -hud-settings-subtitles = Subtitles +hud-settings-subtitles = Subtitles \ No newline at end of file diff --git a/voxygen/src/hud/settings_window/gameplay.rs b/voxygen/src/hud/settings_window/gameplay.rs index 6645e350a6..bd3d641af0 100644 --- a/voxygen/src/hud/settings_window/gameplay.rs +++ b/voxygen/src/hud/settings_window/gameplay.rs @@ -670,7 +670,7 @@ impl<'a> Widget for Gameplay<'a> { // Aim offset x let display_aim_offset_x = self.global_state.settings.gameplay.aim_offset_x; - Text::new("Aim Offset X") + Text::new(&self.localized_strings.get_msg("hud-settings-aim_offset_x")) .down_from(state.ids.zoom_lock_behavior_list, 10.0) .font_size(self.fonts.cyri.scale(14)) .font_id(self.fonts.cyri.conrod_id) @@ -703,7 +703,7 @@ impl<'a> Widget for Gameplay<'a> { // Aim offset y let display_aim_offset_y = self.global_state.settings.gameplay.aim_offset_y; - Text::new("Aim Offset Y") + Text::new(&self.localized_strings.get_msg("hud-settings-aim_offset_y")) .down_from(state.ids.aim_offset_x_slider, 10.0) .font_size(self.fonts.cyri.scale(14)) .font_id(self.fonts.cyri.conrod_id) From c86f000881bb0160b3f3076b18ee05056b7961cd Mon Sep 17 00:00:00 2001 From: Woeful_Wolf <54476280+WoefulWolf@users.noreply.github.com> Date: Thu, 1 Feb 2024 22:34:42 +0200 Subject: [PATCH 15/18] Moved closest_line_seg_line_seg to common/systems/src/phys.rs and renamed to closest_points_ls3 --- common/systems/src/phys.rs | 37 ++++++++++++++++++++++++ voxygen/src/session/target.rs | 53 ++--------------------------------- 2 files changed, 39 insertions(+), 51 deletions(-) diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index f12a5738e0..acc93766cd 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -2200,6 +2200,43 @@ fn closest_points(n: LineSegment2, m: LineSegment2) -> (Vec2, Vec } } +// Get closest point between 2 3D line segments https://math.stackexchange.com/a/4289668 +pub fn closest_points_ls3(n: LineSegment3, m: LineSegment3) -> (Vec3, Vec3) { + let p1 = n.start; + let p2 = n.end; + let p3 = m.start; + let p4 = m.end; + + let d1 = p2 - p1; + let d2 = p4 - p3; + let d21 = p3 - p1; + + let v22 = d2.dot(d2); + let v11 = d1.dot(d1); + let v21 = d2.dot(d1); + let v21_1 = d21.dot(d1); + let v21_2 = d21.dot(d2); + + let denom = v21 * v21 - v22 * v11; + + let (s, t) = if denom == 0.0 { + let s = 0.0; + let t = (v11 * s - v21_1) / v21; + (s, t) + } else { + let s = (v21_2 * v21 - v22 * v21_1) / denom; + let t = (-v21_1 * v21 + v11 * v21_2) / denom; + (s, t) + }; + + let (s, t) = (s.clamp(0.0, 1.0), t.clamp(0.0, 1.0)); + + let p_a = p1 + s * d1; + let p_b = p3 + t * d2; + + (p_a, p_b) +} + /// Find pushback vector and collision_distance we assume between this /// colliders assuming that only one of them is capsule prism. fn capsule2cylinder(c0: ColliderContext, c1: ColliderContext) -> (Vec2, f32) { diff --git a/voxygen/src/session/target.rs b/voxygen/src/session/target.rs index 70fd3c015a..2cde3bfbac 100644 --- a/voxygen/src/session/target.rs +++ b/voxygen/src/session/target.rs @@ -13,6 +13,7 @@ use common::{ vol::ReadVol, }; use common_base::span; +use common_systems::phys::closest_points_ls3; #[derive(Clone, Copy, Debug)] pub struct Target { @@ -305,12 +306,7 @@ pub(super) fn ray_entities( start: world_p0, end: world_p1, }; - closest_line_seg_line_seg( - seg_ray.start, - seg_ray.end, - seg_capsule.start, - seg_capsule.end, - ) + closest_points_ls3(seg_ray, seg_capsule) } else { let nearest = seg_ray.projected_point(world_p0); (nearest, world_p0) @@ -338,48 +334,3 @@ pub(super) fn ray_entities( entity } - -// Get closest point between 2 line segments https://math.stackexchange.com/a/4289668 -fn closest_line_seg_line_seg( - p1: Vec3, - p2: Vec3, - p3: Vec3, - p4: Vec3, -) -> (Vec3, Vec3) { - let p1 = Vec3::new(p1.x as f64, p1.y as f64, p1.z as f64); - let p2 = Vec3::new(p2.x as f64, p2.y as f64, p2.z as f64); - let p3 = Vec3::new(p3.x as f64, p3.y as f64, p3.z as f64); - let p4 = Vec3::new(p4.x as f64, p4.y as f64, p4.z as f64); - - let d1 = p2 - p1; - let d2 = p4 - p3; - let d21 = p3 - p1; - - let v22 = d2.dot(d2); - let v11 = d1.dot(d1); - let v21 = d2.dot(d1); - let v21_1 = d21.dot(d1); - let v21_2 = d21.dot(d2); - - let denom = v21 * v21 - v22 * v11; - - let (s, t) = if denom == 0.0 { - let s = 0.0; - let t = (v11 * s - v21_1) / v21; - (s, t) - } else { - let s = (v21_2 * v21 - v22 * v21_1) / denom; - let t = (-v21_1 * v21 + v11 * v21_2) / denom; - (s, t) - }; - - let (s, t) = (s.clamp(0.0, 1.0), t.clamp(0.0, 1.0)); - - let p_a = p1 + s * d1; - let p_b = p3 + t * d2; - - ( - Vec3::new(p_a.x as f32, p_a.y as f32, p_a.z as f32), - Vec3::new(p_b.x as f32, p_b.y as f32, p_b.z as f32), - ) -} From 1c69614b4ec59ca92ea187669668e7dba6560477 Mon Sep 17 00:00:00 2001 From: Woeful_Wolf <54476280+WoefulWolf@users.noreply.github.com> Date: Thu, 1 Feb 2024 23:32:24 +0200 Subject: [PATCH 16/18] Cleanup and comments --- voxygen/src/session/target.rs | 81 ++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/voxygen/src/session/target.rs b/voxygen/src/session/target.rs index 2cde3bfbac..639f015bad 100644 --- a/voxygen/src/session/target.rs +++ b/voxygen/src/session/target.rs @@ -286,43 +286,55 @@ pub(super) fn ray_entities( z_min, z_max, } => { - if nearest.distance_squared(*p) < (r * 1.732).powi(2) { - // 1.732 = sqrt(3) - let entity_rotation = ecs - .read_storage::() - .get(*e) - .copied() - .unwrap_or_default(); - let entity_position = ecs.read_storage::().get(*e).copied().unwrap(); - let world_p0 = entity_position.0 - + (entity_rotation.to_quat() - * Vec3::new(p0.x, p0.y, z_min + c.get_height() / 2.0)); - let world_p1 = entity_position.0 - + (entity_rotation.to_quat() - * Vec3::new(p1.x, p1.y, z_min + c.get_height() / 2.0)); - - let (p_a, p_b) = if p0 != p1 { - let seg_capsule = LineSegment3 { - start: world_p0, - end: world_p1, - }; - closest_points_ls3(seg_ray, seg_capsule) - } else { - let nearest = seg_ray.projected_point(world_p0); - (nearest, world_p0) - }; - - let distance = p_a.xy().distance_squared(p_b.xy()); - - if distance < radius.powi(2) - && p_a.z >= entity_position.0.z + z_min - && p_a.z <= entity_position.0.z + z_max - { - return Some((p_a.distance(start), Entity(*e))); - } + // Check if the nearest point is within the capsule's inclusive radius (radius + // from center to furthest possible edge corner) If not, then + // the ray doesn't intersect the capsule at all and we can skip it + if nearest.distance_squared(*p) > (r * 3.0_f32.sqrt()).powi(2) { + return None; } + + let entity_rotation = ecs + .read_storage::() + .get(*e) + .copied() + .unwrap_or_default(); + let entity_position = ecs.read_storage::().get(*e).copied().unwrap(); + let world_p0 = entity_position.0 + + (entity_rotation.to_quat() + * Vec3::new(p0.x, p0.y, z_min + c.get_height() / 2.0)); + let world_p1 = entity_position.0 + + (entity_rotation.to_quat() + * Vec3::new(p1.x, p1.y, z_min + c.get_height() / 2.0)); + + // Get the closest points between the ray and the capsule's line segment + // If the capsule's line segment is a point, then the closest point is the point + // itself + let (p_a, p_b) = if p0 != p1 { + let seg_capsule = LineSegment3 { + start: world_p0, + end: world_p1, + }; + closest_points_ls3(seg_ray, seg_capsule) + } else { + let nearest = seg_ray.projected_point(world_p0); + (nearest, world_p0) + }; + + // Check if the distance between the closest points are within the capsule + // prism's radius on the xy plane and if the closest points are + // within the capsule prism's z range + let distance = p_a.xy().distance_squared(p_b.xy()); + if distance < radius.powi(2) + && p_a.z >= entity_position.0.z + z_min + && p_a.z <= entity_position.0.z + z_max + { + return Some((p_a.distance(start), Entity(*e))); + } + + // If all else fails, then the ray doesn't intersect the capsule None }, + // TODO: handle other collider types, for now just use the bounding sphere _ => { if nearest.distance_squared(*p) < r.powi(2) { return Some((nearest.distance(start), Entity(*e))); @@ -331,6 +343,5 @@ pub(super) fn ray_entities( }, }; }); - entity } From 63394719a1e4f6d90053eaf804d4a02a1dbf070f Mon Sep 17 00:00:00 2001 From: Woeful_Wolf <54476280+WoefulWolf@users.noreply.github.com> Date: Thu, 1 Feb 2024 23:33:05 +0200 Subject: [PATCH 17/18] Make camera offsets and aiming system only apply when wielding ranged --- voxygen/src/scene/mod.rs | 23 ++++++++++++++++++++--- voxygen/src/session/mod.rs | 19 ++++++++++++++++--- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index dee86e5e85..c87821d971 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -33,7 +33,10 @@ use crate::{ use client::Client; use common::{ calendar::Calendar, - comp::{self, ship::figuredata::VOXEL_COLLIDER_MANIFEST}, + comp::{ + self, item::ItemDesc, ship::figuredata::VOXEL_COLLIDER_MANIFEST, slot::EquipSlot, + tool::ToolKind, + }, outcome::Outcome, resources::{DeltaTime, TimeScale}, terrain::{BlockKind, TerrainChunk, TerrainGrid}, @@ -622,6 +625,19 @@ impl Scene { .get(scene_data.viewpoint_entity) .map(|p| p.on_ground.is_some()); + let player_entity = client.entity(); + let holding_ranged = client + .inventories() + .get(player_entity) + .and_then(|inv| inv.equipped(EquipSlot::ActiveMainhand)) + .and_then(|item| item.tool_info()) + .is_some_and(|tool_kind| { + matches!( + tool_kind, + ToolKind::Bow | ToolKind::Staff | ToolKind::Sceptre + ) + }); + let up = match self.camera.get_mode() { CameraMode::FirstPerson => { if viewpoint_rolling { @@ -633,16 +649,17 @@ impl Scene { viewpoint_eye_height } }, - CameraMode::ThirdPerson if scene_data.is_aiming => { + CameraMode::ThirdPerson if scene_data.is_aiming && holding_ranged => { viewpoint_height * 1.16 + settings.gameplay.aim_offset_y }, + CameraMode::ThirdPerson if scene_data.is_aiming => viewpoint_height * 1.16, CameraMode::ThirdPerson => viewpoint_eye_height, CameraMode::Freefly => 0.0, }; let right = match self.camera.get_mode() { CameraMode::FirstPerson => 0.0, - CameraMode::ThirdPerson if scene_data.is_aiming => { + CameraMode::ThirdPerson if scene_data.is_aiming && holding_ranged => { settings.gameplay.aim_offset_x }, CameraMode::ThirdPerson => 0.0, diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 37561f0f65..418d527c77 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -1351,11 +1351,24 @@ impl PlayState for SessionState { self.walk_forward_dir = self.scene.camera().forward_xy(); self.walk_right_dir = self.scene.camera().right_xy(); - let dir = if is_aiming { - let client = self.client.borrow(); + let client = self.client.borrow(); + + let holding_ranged = client + .inventories() + .get(player_entity) + .and_then(|inv| inv.equipped(EquipSlot::ActiveMainhand)) + .and_then(|item| item.tool_info()) + .is_some_and(|tool_kind| { + matches!( + tool_kind, + ToolKind::Bow | ToolKind::Staff | ToolKind::Sceptre + ) + }); + + let dir = if is_aiming && holding_ranged { // Shoot ray from camera forward direction and get the point it hits an // entity or terrain - let ray_start = cam_pos + cam_dir * 2.0; + let ray_start = cam_pos + cam_dir * self.scene.camera().get_distance(); let entity_ray_end = ray_start + cam_dir * 500.0; let terrain_ray_end = ray_start + cam_dir * 1000.0; From 7b843444be0a66ccaeb22a7fd3d1692d7bdcaa04 Mon Sep 17 00:00:00 2001 From: Woeful_Wolf <54476280+WoefulWolf@users.noreply.github.com> Date: Fri, 2 Feb 2024 18:11:37 +0200 Subject: [PATCH 18/18] Renamed closest_points_ls3 to closest_points_3d, added comments, disable raycast aiming in FPV, get shortest between entity and terrain raycast --- common/systems/src/phys.rs | 2 +- voxygen/src/session/mod.rs | 46 ++++++++++++++++++++--------------- voxygen/src/session/target.rs | 8 +++--- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index acc93766cd..ed87707b8b 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -2201,7 +2201,7 @@ fn closest_points(n: LineSegment2, m: LineSegment2) -> (Vec2, Vec } // Get closest point between 2 3D line segments https://math.stackexchange.com/a/4289668 -pub fn closest_points_ls3(n: LineSegment3, m: LineSegment3) -> (Vec3, Vec3) { +pub fn closest_points_3d(n: LineSegment3, m: LineSegment3) -> (Vec3, Vec3) { let p1 = n.start; let p2 = n.end; let p3 = m.start; diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 418d527c77..12213465cd 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -1365,28 +1365,34 @@ impl PlayState for SessionState { ) }); - let dir = if is_aiming && holding_ranged { - // Shoot ray from camera forward direction and get the point it hits an - // entity or terrain - let ray_start = cam_pos + cam_dir * self.scene.camera().get_distance(); - let entity_ray_end = ray_start + cam_dir * 500.0; + let dir = if is_aiming + && holding_ranged + && self.scene.camera().get_mode() == CameraMode::ThirdPerson + { + // Shoot ray from camera focus forwards and get the point it hits an + // entity or terrain. The ray starts from the camera focus point + // so that the player won't aim at things behind them, in front of the + // camera. + let ray_start = self.scene.camera().get_focus_pos(); + let entity_ray_end = ray_start + cam_dir * 1000.0; let terrain_ray_end = ray_start + cam_dir * 1000.0; - let aim_point = - match ray_entities(&client, ray_start, entity_ray_end, 500.0) { - Some((dist, _)) => ray_start + cam_dir * dist, - None => { - let terrain_ray_distance = client - .state() - .terrain() - .ray(ray_start, terrain_ray_end) - .max_iter(1000) - .until(Block::is_solid) - .cast() - .0; - ray_start + cam_dir * terrain_ray_distance - }, - }; + let aim_point = { + // Get the distance to nearest entity and terrain + let entity_dist = + ray_entities(&client, ray_start, entity_ray_end, 1000.0).0; + let terrain_ray_distance = client + .state() + .terrain() + .ray(ray_start, terrain_ray_end) + .max_iter(1000) + .until(Block::is_solid) + .cast() + .0; + + // Return the hit point of whichever was smaller + ray_start + cam_dir * entity_dist.min(terrain_ray_distance) + }; // Get player orientation let ori = client diff --git a/voxygen/src/session/target.rs b/voxygen/src/session/target.rs index 639f015bad..bc9a9ff06d 100644 --- a/voxygen/src/session/target.rs +++ b/voxygen/src/session/target.rs @@ -13,7 +13,7 @@ use common::{ vol::ReadVol, }; use common_base::span; -use common_systems::phys::closest_points_ls3; +use common_systems::phys::closest_points_3d; #[derive(Clone, Copy, Debug)] pub struct Target { @@ -245,7 +245,7 @@ pub(super) fn ray_entities( start: Vec3, end: Vec3, cast_dist: f32, -) -> Option<(f32, Entity)> { +) -> (f32, Option) { let player_entity = client.entity(); let ecs = client.state().ecs(); let positions = ecs.read_storage::(); @@ -314,7 +314,7 @@ pub(super) fn ray_entities( start: world_p0, end: world_p1, }; - closest_points_ls3(seg_ray, seg_capsule) + closest_points_3d(seg_ray, seg_capsule) } else { let nearest = seg_ray.projected_point(world_p0); (nearest, world_p0) @@ -344,4 +344,6 @@ pub(super) fn ray_entities( }; }); entity + .map(|(dist, e)| (dist, Some(e))) + .unwrap_or((cast_dist, None)) }