mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'woeful/shoulder_aiming_camera' into 'master'
Third person over-the-shoulder camera and look_dir calculations when aiming See merge request veloren/veloren!4285
This commit is contained in:
commit
2c9ceb51d5
@ -57,6 +57,8 @@ hud-settings-walking_speed_behavior = Walking speed behavior
|
|||||||
hud-settings-walking_speed = Walking speed
|
hud-settings-walking_speed = Walking speed
|
||||||
hud-settings-camera_clamp_behavior = Camera clamp behavior
|
hud-settings-camera_clamp_behavior = Camera clamp behavior
|
||||||
hud-settings-zoom_lock_behavior = Camera zoom lock 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-player_physics_behavior = Player physics (experimental)
|
||||||
hud-settings-stop_auto_walk_on_input = Stop auto walk on movement
|
hud-settings-stop_auto_walk_on_input = Stop auto walk on movement
|
||||||
hud-settings-auto_camera = Auto camera
|
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-reset_chat = Reset to Defaults
|
||||||
hud-settings-third_party_integrations = Third-party Integrations
|
hud-settings-third_party_integrations = Third-party Integrations
|
||||||
hud-settings-enable_discord_integration = Enable Discord Integration
|
hud-settings-enable_discord_integration = Enable Discord Integration
|
||||||
hud-settings-subtitles = Subtitles
|
hud-settings-subtitles = Subtitles
|
@ -2200,6 +2200,43 @@ fn closest_points(n: LineSegment2<f32>, m: LineSegment2<f32>) -> (Vec2<f32>, Vec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get closest point between 2 3D line segments https://math.stackexchange.com/a/4289668
|
||||||
|
pub fn closest_points_3d(n: LineSegment3<f32>, m: LineSegment3<f32>) -> (Vec3<f32>, Vec3<f32>) {
|
||||||
|
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
|
/// Find pushback vector and collision_distance we assume between this
|
||||||
/// colliders assuming that only one of them is capsule prism.
|
/// colliders assuming that only one of them is capsule prism.
|
||||||
fn capsule2cylinder(c0: ColliderContext, c1: ColliderContext) -> (Vec2<f32>, f32) {
|
fn capsule2cylinder(c0: ColliderContext, c1: ColliderContext) -> (Vec2<f32>, f32) {
|
||||||
|
@ -59,6 +59,12 @@ widget_ids! {
|
|||||||
bow_zoom_label,
|
bow_zoom_label,
|
||||||
zoom_lock_button,
|
zoom_lock_button,
|
||||||
zoom_lock_label,
|
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)
|
.color(TEXT_COLOR)
|
||||||
.set(state.ids.zoom_lock_label, ui);
|
.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(&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)
|
||||||
|
.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(&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)
|
||||||
|
.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
|
// Reset the gameplay settings to the default settings
|
||||||
if Button::image(self.imgs.button)
|
if Button::image(self.imgs.button)
|
||||||
.w_h(RESET_BUTTONS_WIDTH, RESET_BUTTONS_HEIGHT)
|
.w_h(RESET_BUTTONS_WIDTH, RESET_BUTTONS_HEIGHT)
|
||||||
.hover_image(self.imgs.button_hover)
|
.hover_image(self.imgs.button_hover)
|
||||||
.press_image(self.imgs.button_press)
|
.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(
|
.label(
|
||||||
&self
|
&self
|
||||||
.localized_strings
|
.localized_strings
|
||||||
|
@ -33,7 +33,10 @@ use crate::{
|
|||||||
use client::Client;
|
use client::Client;
|
||||||
use common::{
|
use common::{
|
||||||
calendar::Calendar,
|
calendar::Calendar,
|
||||||
comp::{self, ship::figuredata::VOXEL_COLLIDER_MANIFEST},
|
comp::{
|
||||||
|
self, item::ItemDesc, ship::figuredata::VOXEL_COLLIDER_MANIFEST, slot::EquipSlot,
|
||||||
|
tool::ToolKind,
|
||||||
|
},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
resources::{DeltaTime, TimeScale},
|
resources::{DeltaTime, TimeScale},
|
||||||
terrain::{BlockKind, TerrainChunk, TerrainGrid},
|
terrain::{BlockKind, TerrainChunk, TerrainGrid},
|
||||||
@ -526,6 +529,7 @@ impl Scene {
|
|||||||
audio: &mut AudioFrontend,
|
audio: &mut AudioFrontend,
|
||||||
scene_data: &SceneData,
|
scene_data: &SceneData,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
|
settings: &Settings,
|
||||||
) {
|
) {
|
||||||
span!(_guard, "maintain", "Scene::maintain");
|
span!(_guard, "maintain", "Scene::maintain");
|
||||||
// Get player position.
|
// Get player position.
|
||||||
@ -621,6 +625,19 @@ impl Scene {
|
|||||||
.get(scene_data.viewpoint_entity)
|
.get(scene_data.viewpoint_entity)
|
||||||
.map(|p| p.on_ground.is_some());
|
.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() {
|
let up = match self.camera.get_mode() {
|
||||||
CameraMode::FirstPerson => {
|
CameraMode::FirstPerson => {
|
||||||
if viewpoint_rolling {
|
if viewpoint_rolling {
|
||||||
@ -632,15 +649,29 @@ impl Scene {
|
|||||||
viewpoint_eye_height
|
viewpoint_eye_height
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
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 if scene_data.is_aiming => viewpoint_height * 1.16,
|
||||||
CameraMode::ThirdPerson => viewpoint_eye_height,
|
CameraMode::ThirdPerson => viewpoint_eye_height,
|
||||||
CameraMode::Freefly => 0.0,
|
CameraMode::Freefly => 0.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let right = match self.camera.get_mode() {
|
||||||
|
CameraMode::FirstPerson => 0.0,
|
||||||
|
CameraMode::ThirdPerson if scene_data.is_aiming && holding_ranged => {
|
||||||
|
settings.gameplay.aim_offset_x
|
||||||
|
},
|
||||||
|
CameraMode::ThirdPerson => 0.0,
|
||||||
|
CameraMode::Freefly => 0.0,
|
||||||
|
};
|
||||||
|
|
||||||
// Alter camera position to match player.
|
// Alter camera position to match player.
|
||||||
let tilt = self.camera.get_orientation().y;
|
let tilt = self.camera.get_orientation().y;
|
||||||
let dist = self.camera.get_distance();
|
let dist = self.camera.get_distance();
|
||||||
|
|
||||||
Vec3::unit_z() * (up * viewpoint_scale - tilt.min(0.0).sin() * dist * 0.6)
|
Vec3::unit_z() * (up * viewpoint_scale - tilt.min(0.0).sin() * dist * 0.6)
|
||||||
|
+ self.camera.right() * (right * viewpoint_scale)
|
||||||
} else {
|
} else {
|
||||||
self.figure_mgr
|
self.figure_mgr
|
||||||
.viewpoint_offset(scene_data, scene_data.viewpoint_entity)
|
.viewpoint_offset(scene_data, scene_data.viewpoint_entity)
|
||||||
|
@ -50,6 +50,7 @@ use crate::{
|
|||||||
menu::char_selection::CharSelectionState,
|
menu::char_selection::CharSelectionState,
|
||||||
render::{Drawer, GlobalsBindGroup},
|
render::{Drawer, GlobalsBindGroup},
|
||||||
scene::{camera, CameraMode, DebugShapeId, Scene, SceneData},
|
scene::{camera, CameraMode, DebugShapeId, Scene, SceneData},
|
||||||
|
session::target::ray_entities,
|
||||||
settings::Settings,
|
settings::Settings,
|
||||||
window::{AnalogGameInput, Event},
|
window::{AnalogGameInput, Event},
|
||||||
Direction, GlobalState, PlayState, PlayStateResult,
|
Direction, GlobalState, PlayState, PlayStateResult,
|
||||||
@ -1349,8 +1350,88 @@ impl PlayState for SessionState {
|
|||||||
if !self.free_look {
|
if !self.free_look {
|
||||||
self.walk_forward_dir = self.scene.camera().forward_xy();
|
self.walk_forward_dir = self.scene.camera().forward_xy();
|
||||||
self.walk_right_dir = self.scene.camera().right_xy();
|
self.walk_right_dir = self.scene.camera().right_xy();
|
||||||
self.inputs.look_dir =
|
|
||||||
Dir::from_unnormalized(cam_dir + aim_dir_offset).unwrap();
|
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
|
||||||
|
&& 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 = {
|
||||||
|
// 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
|
||||||
|
.state()
|
||||||
|
.read_storage::<comp::Ori>()
|
||||||
|
.get(player_entity)
|
||||||
|
.copied()
|
||||||
|
.unwrap();
|
||||||
|
// Get player scale
|
||||||
|
let scale = client
|
||||||
|
.state()
|
||||||
|
.read_storage::<comp::Scale>()
|
||||||
|
.get(player_entity)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(comp::Scale(1.0));
|
||||||
|
// Get player body offsets
|
||||||
|
let body = client
|
||||||
|
.state()
|
||||||
|
.read_storage::<comp::Body>()
|
||||||
|
.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::<Pos>()
|
||||||
|
.get(player_entity)
|
||||||
|
.copied()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
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!(
|
self.inputs.strafing = matches!(
|
||||||
@ -2040,6 +2121,7 @@ impl PlayState for SessionState {
|
|||||||
&mut global_state.audio,
|
&mut global_state.audio,
|
||||||
&scene_data,
|
&scene_data,
|
||||||
&client,
|
&client,
|
||||||
|
&global_state.settings,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Process outcomes from client
|
// Process outcomes from client
|
||||||
|
@ -77,6 +77,9 @@ pub enum Gameplay {
|
|||||||
ChangeBowZoom(bool),
|
ChangeBowZoom(bool),
|
||||||
ChangeZoomLock(bool),
|
ChangeZoomLock(bool),
|
||||||
|
|
||||||
|
AdjustAimOffsetX(f32),
|
||||||
|
AdjustAimOffsetY(f32),
|
||||||
|
|
||||||
ResetGameplaySettings,
|
ResetGameplaySettings,
|
||||||
}
|
}
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -427,6 +430,12 @@ impl SettingsChange {
|
|||||||
Gameplay::ChangeZoomLock(state) => {
|
Gameplay::ChangeZoomLock(state) => {
|
||||||
settings.gameplay.zoom_lock = 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 => {
|
Gameplay::ResetGameplaySettings => {
|
||||||
// Reset Gameplay Settings
|
// Reset Gameplay Settings
|
||||||
settings.gameplay = GameplaySettings::default();
|
settings.gameplay = GameplaySettings::default();
|
||||||
|
@ -13,6 +13,7 @@ use common::{
|
|||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
};
|
};
|
||||||
use common_base::span;
|
use common_base::span;
|
||||||
|
use common_systems::phys::closest_points_3d;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Target<T> {
|
pub struct Target<T> {
|
||||||
@ -238,3 +239,111 @@ pub(super) fn targets_under_cursor(
|
|||||||
terrain_target,
|
terrain_target,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn ray_entities(
|
||||||
|
client: &Client,
|
||||||
|
start: Vec3<f32>,
|
||||||
|
end: Vec3<f32>,
|
||||||
|
cast_dist: f32,
|
||||||
|
) -> (f32, Option<Entity>) {
|
||||||
|
let player_entity = client.entity();
|
||||||
|
let ecs = client.state().ecs();
|
||||||
|
let positions = ecs.read_storage::<comp::Pos>();
|
||||||
|
let colliders = ecs.read_storage::<comp::Collider>();
|
||||||
|
|
||||||
|
let mut nearby = (
|
||||||
|
&ecs.entities(),
|
||||||
|
&positions,
|
||||||
|
&colliders,
|
||||||
|
)
|
||||||
|
.join()
|
||||||
|
.filter(|(e, _, _)| *e != player_entity)
|
||||||
|
.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);
|
||||||
|
(e, pos, radius, dist_sqr, c)
|
||||||
|
})
|
||||||
|
// Roughly filter out entities farther than ray distance
|
||||||
|
.filter(|(_, _, _, d_sqr, _)| *d_sqr <= cast_dist.powi(2))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
// 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, _, c)| {
|
||||||
|
let nearest = seg_ray.projected_point(*p);
|
||||||
|
|
||||||
|
return match c {
|
||||||
|
comp::Collider::CapsulePrism {
|
||||||
|
p0,
|
||||||
|
p1,
|
||||||
|
radius,
|
||||||
|
z_min,
|
||||||
|
z_max,
|
||||||
|
} => {
|
||||||
|
// 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::<comp::Ori>()
|
||||||
|
.get(*e)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let entity_position = ecs.read_storage::<comp::Pos>().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_3d(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)));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
entity
|
||||||
|
.map(|(dist, e)| (dist, Some(e)))
|
||||||
|
.unwrap_or((cast_dist, None))
|
||||||
|
}
|
||||||
|
@ -21,6 +21,8 @@ pub struct GameplaySettings {
|
|||||||
pub auto_camera: bool,
|
pub auto_camera: bool,
|
||||||
pub bow_zoom: bool,
|
pub bow_zoom: bool,
|
||||||
pub zoom_lock: bool,
|
pub zoom_lock: bool,
|
||||||
|
pub aim_offset_x: f32,
|
||||||
|
pub aim_offset_y: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GameplaySettings {
|
impl Default for GameplaySettings {
|
||||||
@ -42,6 +44,8 @@ impl Default for GameplaySettings {
|
|||||||
auto_camera: false,
|
auto_camera: false,
|
||||||
bow_zoom: true,
|
bow_zoom: true,
|
||||||
zoom_lock: false,
|
zoom_lock: false,
|
||||||
|
aim_offset_x: 1.0,
|
||||||
|
aim_offset_y: 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user