From 1741384d008c865b126de80a667cde5575f66bf8 Mon Sep 17 00:00:00 2001 From: Imbris Date: Fri, 24 Apr 2020 04:02:47 -0400 Subject: [PATCH] Add entity targeting --- voxygen/src/hud/mod.rs | 5 +- voxygen/src/scene/figure/mod.rs | 15 +++- voxygen/src/scene/mod.rs | 1 + voxygen/src/session.rs | 141 ++++++++++++++++++++++++-------- 4 files changed, 121 insertions(+), 41 deletions(-) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 6cad71c471..39edeff12e 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -242,6 +242,7 @@ pub struct DebugInfo { pub struct HudInfo { pub is_aiming: bool, pub is_first_person: bool, + pub target_entity: Option, } pub enum Event { @@ -1045,7 +1046,9 @@ impl Hud { &uids, ) .join() - .filter(|(entity, _, _, stats, _, _, _, _, _, _)| *entity != me && !stats.is_dead) + .filter(|(entity, _, _, stats, _, _, _, _, _, _)| *entity != me && !stats.is_dead + && (stats.health.current() != stats.health.maximum() || info.target_entity.map_or(false, |e| e == *entity)) + ) // Don't show outside a certain range .filter(|(_, pos, _, _, _, _, _, _, hpfl, _)| { pos.0.distance_squared(player_pos) diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 16fb4d1bcd..980433f024 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -22,8 +22,8 @@ use anim::{ }; use common::{ comp::{ - item::ItemKind, Body, CharacterState, Last, LightAnimation, LightEmitter, Loadout, Ori, - PhysicsState, Pos, Scale, Stats, Vel, + item::ItemKind, Body, CharacterState, Item, Last, LightAnimation, LightEmitter, Loadout, + Ori, PhysicsState, Pos, Scale, Stats, Vel, }, state::{DeltaTime, State}, states::triple_strike, @@ -192,7 +192,6 @@ impl FigureMgr { .read_storage::() .get(scene_data.player_entity) .map_or(Vec3::zero(), |pos| pos.0); - for ( i, ( @@ -207,6 +206,7 @@ impl FigureMgr { physics, stats, loadout, + item, ), ) in ( &ecs.entities(), @@ -220,6 +220,7 @@ impl FigureMgr { &ecs.read_storage::(), ecs.read_storage::().maybe(), ecs.read_storage::().maybe(), + ecs.read_storage::().maybe(), ) .join() .enumerate() @@ -519,7 +520,13 @@ impl FigureMgr { (c / (1.0 + DAMAGE_FADE_COEFFICIENT * s.health.last_change.0)) as f32 }) }) - .unwrap_or(Rgba::broadcast(1.0)); + .unwrap_or(Rgba::broadcast(1.0)) + // Highlight targeted collectible entities + * if item.is_some() && scene_data.target_entity.map_or(false, |e| e == entity) { + Rgba::new(2.0, 2.0, 2.0, 1.0) + } else { + Rgba::one() + }; let scale = scale.map(|s| s.0).unwrap_or(1.0); diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 5f449fe15c..4c18a92207 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -70,6 +70,7 @@ pub struct Scene { pub struct SceneData<'a> { pub state: &'a State, pub player_entity: specs::Entity, + pub target_entity: Option, pub loaded_distance: f32, pub view_distance: u32, pub tick: u64, diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 00bffa5338..578dc9b0fa 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -208,18 +208,6 @@ impl PlayState for SessionState { view_mat, cam_pos, .. } = self.scene.camera().dependents(); - // Choose a spot above the player's head for item distance checks - let player_pos = match self - .client - .borrow() - .state() - .read_storage::() - .get(self.client.borrow().entity()) - { - Some(pos) => pos.0 + (Vec3::unit_z() * 2.0), - _ => cam_pos, // Should never happen, but a safe fallback - }; - let (is_aiming, aim_dir_offset) = { let client = self.client.borrow(); let is_aiming = client @@ -243,30 +231,10 @@ impl PlayState for SessionState { let cam_dir: Vec3 = Vec3::from(view_mat.inverted() * -Vec4::unit_z()); // Check to see whether we're aiming at anything - let (build_pos, select_pos) = { - let client = self.client.borrow(); - let terrain = client.state().terrain(); - - let cam_ray = terrain - .ray(cam_pos, cam_pos + cam_dir * 100.0) - .until(|block| block.is_tangible()) - .cast(); - - let cam_dist = cam_ray.0; - - match cam_ray.1 { - Ok(Some(_)) - if player_pos.distance_squared(cam_pos + cam_dir * cam_dist) - <= MAX_PICKUP_RANGE_SQR => - { - ( - Some((cam_pos + cam_dir * (cam_dist - 0.01)).map(|e| e.floor() as i32)), - Some((cam_pos + cam_dir * (cam_dist + 0.01)).map(|e| e.floor() as i32)), - ) - }, - _ => (None, None), - } - }; + let (build_pos, select_pos, target_entity) = + under_cursor(&self.client.borrow(), cam_pos, cam_dir); + // Throw out distance info, it will be useful in the future + let target_entity = target_entity.map(|x| x.0); let can_build = self .client @@ -708,6 +676,7 @@ impl PlayState for SessionState { self.scene.camera().get_mode(), camera::CameraMode::FirstPerson ), + target_entity, }, ); @@ -979,6 +948,7 @@ impl PlayState for SessionState { let scene_data = SceneData { state: client.state(), player_entity: client.entity(), + target_entity, loaded_distance: client.loaded_distance(), view_distance: client.view_distance().unwrap_or(1), tick: client.get_tick(), @@ -1055,3 +1025,102 @@ impl PlayState for SessionState { self.hud.render(renderer, self.scene.globals()); } } + +/// Max distance an entity can be "targeted" +const MAX_TARGET_RANGE: f32 = 30.0; +/// Calculate what the cursor is pointing at within the 3d scene +fn under_cursor( + client: &Client, + cam_pos: Vec3, + cam_dir: Vec3, +) -> ( + Option>, + Option>, + Option<(specs::Entity, f32)>, +) { + // Choose a spot above the player's head for item distance checks + let player_entity = client.entity(); + let player_pos = match client + .state() + .read_storage::() + .get(player_entity) + { + Some(pos) => pos.0 + (Vec3::unit_z() * 2.0), + _ => cam_pos, // Should never happen, but a safe fallback + }; + let terrain = client.state().terrain(); + + let cam_ray = terrain + .ray(cam_pos, cam_pos + cam_dir * 100.0) + .until(|block| block.is_tangible()) + .cast(); + + let cam_dist = cam_ray.0; + + // The ray hit something, is it within range? + let (build_pos, select_pos) = if matches!(cam_ray.1, Ok(Some(_)) if + player_pos.distance_squared(cam_pos + cam_dir * cam_dist) + <= MAX_PICKUP_RANGE_SQR) + { + ( + Some((cam_pos + cam_dir * (cam_dist - 0.01)).map(|e| e.floor() as i32)), + Some((cam_pos + cam_dir * (cam_dist + 0.01)).map(|e| e.floor() as i32)), + ) + } else { + (None, None) + }; + + // See if ray hits entities + // Currently treated as spheres + let ecs = client.state().ecs(); + // Don't cast through blocks + // Could check for intersection with entity from last frame to narrow this down + let cast_dist = if let Ok(Some(_)) = cam_ray.1 { + cam_dist.min(MAX_TARGET_RANGE) + } else { + MAX_TARGET_RANGE + }; + + // Need to raycast by distance to cam + // But also filter out by distance to the player (but this only needs to be done + // on final result) + let mut nearby = ( + &ecs.entities(), + &ecs.read_storage::(), + ecs.read_storage::().maybe(), + &ecs.read_storage::() + ) + .join() + .filter(|(e, _, _, _)| *e != player_entity) + .map(|(e, p, s, b)| { + const RADIUS_SCALE: f32 = 1.2; + let radius = s.map_or(1.0, |s| s.0) * b.radius() * RADIUS_SCALE; + // Move position up from the feet + let pos = Vec3::new(p.0.x, p.0.y, p.0.z + radius); + let dist_sqr = pos.distance_squared(cam_pos); + (e, pos, radius, dist_sqr) + }) + // Ignore entities intersecting the camera + .filter(|(_, _, r, d_sqr)| *d_sqr > r.powi(2)) + .filter(|(_, _, r, d_sqr)| *d_sqr <= cast_dist.powi(2) + 2.0 * cast_dist * r + r.powi(2)) + .map(|(e, p, r, d_sqr)| (e, p, r, d_sqr.sqrt() - r)) + .collect::>(); + nearby.sort_unstable_by(|a, b| a.3.partial_cmp(&b.3).unwrap()); + + let seg_ray = LineSegment3 { + start: cam_pos, + end: cam_pos + cam_dir * cam_dist, + }; + // TODO: fuzzy borders + let target_entity = nearby + .iter() + .map(|(e, p, r, _)| (e, *p, r)) + .find(|(_, p, r)| seg_ray.projected_point(*p).distance_squared(*p) < r.powi(2)) + .and_then(|(e, p, r)| { + let dist_to_player = p.distance(player_pos); + (dist_to_player - r < MAX_TARGET_RANGE).then_some((*e, dist_to_player)) + }); + + // TODO: consider setting build/select to None when targeting an entity + (build_pos, select_pos, target_entity) +}