diff --git a/common/src/util/find_dist.rs b/common/src/util/find_dist.rs index eec2a615a6..cb02c728fe 100644 --- a/common/src/util/find_dist.rs +++ b/common/src/util/find_dist.rs @@ -16,7 +16,7 @@ pub trait FindDist { #[derive(Clone, Copy, Debug)] pub struct Cylinder { /// Center of the cylinder - pub pos: Vec3, + pub center: Vec3, /// Radius of the cylinder pub radius: f32, /// Height of the cylinder @@ -26,8 +26,8 @@ pub struct Cylinder { impl Cylinder { fn aabb(&self) -> Aabb { Aabb { - min: self.pos - Vec3::new(self.radius, self.radius, self.height) / 2.0, - max: self.pos + Vec3::new(self.radius, self.radius, self.height) / 2.0, + min: self.center - Vec3::new(self.radius, self.radius, self.height / 2.0), + max: self.center + Vec3::new(self.radius, self.radius, self.height / 2.0), } } @@ -49,7 +49,7 @@ impl Cylinder { .unwrap_or((-0.5 * z_limit_modifier, 0.5 * z_limit_modifier)); Self { - pos: pos + Vec3::unit_z() * (z_top + z_bottom) / 2.0, + center: pos + Vec3::unit_z() * (z_top + z_bottom) / 2.0, radius, height: z_top - z_bottom, } @@ -60,7 +60,7 @@ impl Cylinder { #[derive(Clone, Copy, Debug)] pub struct Cube { /// The position of min corner of the cube - pub pos: Vec3, + pub min: Vec3, /// The side length of the cube pub side_length: f32, } @@ -69,8 +69,8 @@ impl FindDist for Cube { #[inline] fn approx_in_range(self, other: Cylinder, range: f32) -> bool { let cube_plus_range_aabb = Aabb { - min: self.pos - Vec3::from(range), - max: self.pos + Vec3::from(range), + min: self.min - range, + max: self.min + self.side_length + range, }; let cylinder_aabb = other.aabb(); @@ -80,15 +80,15 @@ impl FindDist for Cube { #[inline] fn min_distance(self, other: Cylinder) -> f32 { // Distance between centers along the z-axis - let z_center_dist = (self.pos.z + self.side_length / 2.0 - other.pos.z).abs(); + let z_center_dist = (self.min.z + self.side_length / 2.0 - other.center.z).abs(); // Distance between surfaces projected onto the z-axis let z_dist = (z_center_dist - (self.side_length + other.height) / 2.0).max(0.0); // Distance between shapes projected onto the xy plane as a square/circle let square_aabr = Aabr { - min: self.pos.xy(), - max: self.pos.xy() + self.side_length, + min: self.min.xy(), + max: self.min.xy() + self.side_length, }; - let xy_dist = (square_aabr.distance_to_point(other.pos.xy()) - other.radius).max(0.0); + let xy_dist = (square_aabr.distance_to_point(other.center.xy()) - other.radius).max(0.0); // Overall distance by pythagoras (z_dist.powi(2) + xy_dist.powi(2)).sqrt() } @@ -115,12 +115,12 @@ impl FindDist for Cylinder { #[inline] fn min_distance(self, other: Cylinder) -> f32 { // Distance between centers along the z-axis - let z_center_dist = (self.pos.z - other.pos.z).abs(); + let z_center_dist = (self.center.z - other.center.z).abs(); // Distance between surfaces projected onto the z-axis let z_dist = (z_center_dist - (self.height + other.height) / 2.0).max(0.0); // Distance between shapes projected onto the xy plane as a circles let xy_dist = - (self.pos.xy().distance(other.pos.xy()) - self.radius - other.radius).max(0.0); + (self.center.xy().distance(other.center.xy()) - self.radius - other.radius).max(0.0); // Overall distance by pythagoras (z_dist.powi(2) + xy_dist.powi(2)).sqrt() } @@ -139,12 +139,80 @@ impl FindDist> for Cylinder { #[inline] fn min_distance(self, other: Vec3) -> f32 { // Distance between center and point along the z-axis - let z_center_dist = (self.pos.z - other.z).abs(); + let z_center_dist = (self.center.z - other.z).abs(); // Distance between surface and point projected onto the z-axis let z_dist = (z_center_dist - self.height / 2.0).max(0.0); // Distance between shapes projected onto the xy plane - let xy_dist = (self.pos.xy().distance(other.xy()) - self.radius).max(0.0); + let xy_dist = (self.center.xy().distance(other.xy()) - self.radius).max(0.0); // Overall distance by pythagoras (z_dist.powi(2) + xy_dist.powi(2)).sqrt() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn cylinder_vs_cube() { + //let offset = Vec3::new(1213.323, 5424.0, -231.0); + let offset = Vec3::zero(); + let cylinder = Cylinder { + center: Vec3::new(0.0, 0.0, 0.0) + offset, + radius: 2.0, + height: 4.0, + }; + + let cube = Cube { + min: Vec3::new(-0.5, -0.5, -0.5) + offset, + side_length: 1.0, + }; + + assert!(cube.approx_in_range(cylinder, 0.0)); + assert!(cube.min_distance(cylinder).abs() < f32::EPSILON); + assert!((cube.min_distance(cylinder) - cylinder.min_distance(cube)).abs() < 0.001); + + let cube = Cube { + min: cube.min + Vec3::unit_x() * 50.0, + side_length: 1.0, + }; + + assert!(!cube.approx_in_range(cylinder, 5.0)); // Note: technically it is not breaking any promises if this returns true but this will be useful as a warning if the filtering is not tight as we were expecting + assert!(cube.approx_in_range(cylinder, 47.51)); + assert!((cube.min_distance(cylinder) - 47.5).abs() < 0.001); + assert!((cube.min_distance(cylinder) - cylinder.min_distance(cube)).abs() < 0.001); + } + + #[test] + fn zero_size_cylinder() { + let cylinder = Cylinder { + center: Vec3::new(1.0, 2.0, 3.0), + radius: 0.0, + height: 0.0, + }; + + let point = Vec3::new(1.0, 2.5, 3.5); + + assert!(cylinder.approx_in_range(point, 0.71)); + assert!(cylinder.min_distance(point) < 0.71); + assert!(cylinder.min_distance(point) > 0.70); + + let cube = Cube { + min: Vec3::new(0.5, 1.9, 2.1), + side_length: 1.0, + }; + + assert!(cylinder.approx_in_range(cube, 0.0)); + assert!(cylinder.min_distance(cube) < f32::EPSILON); + + let cube = Cube { + min: Vec3::new(1.0, 2.0, 4.5), + side_length: 1.0, + }; + + assert!(cylinder.approx_in_range(cube, 1.51)); + assert!(cylinder.approx_in_range(cube, 100.51)); + assert!(cylinder.min_distance(cube) < 1.501); + assert!(cylinder.min_distance(cube) > 1.499); + } +} diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index 81a3eb3e7a..789ea9b56e 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -39,6 +39,23 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv let mut dropped_items = Vec::new(); let mut thrown_items = Vec::new(); + let get_cylinder = |state: &common::state::State, entity| { + let ecs = state.ecs(); + let positions = ecs.read_storage::(); + let scales = ecs.read_storage::(); + let colliders = ecs.read_storage::(); + let char_states = ecs.read_storage::(); + + positions.get(entity).map(|p| { + find_dist::Cylinder::from_components( + p.0, + scales.get(entity).copied(), + colliders.get(entity).copied(), + char_states.get(entity), + ) + }) + }; + match manip { comp::InventoryManip::Pickup(uid) => { let picked_up_item: Option; @@ -60,32 +77,14 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv ) { picked_up_item = Some(item.clone()); - { - let ecs = state.ecs(); - let positions = ecs.read_storage::(); - let scales = ecs.read_storage::(); - let colliders = ecs.read_storage::(); - let char_states = ecs.read_storage::(); - - let cylinder = |entity| { - positions.get(entity).map(|p| { - find_dist::Cylinder::from_components( - p.0, - scales.get(entity).copied(), - colliders.get(entity).copied(), - char_states.get(entity), - ) - }) - }; - let entity_cylinder = cylinder(entity); - if !within_pickup_range(entity_cylinder, || cylinder(item_entity)) { - debug!( - ?entity_cylinder, - "Failed to pick up item as not within range, Uid: {}", uid - ); - return; - }; - } + let entity_cylinder = get_cylinder(state, entity); + if !within_pickup_range(entity_cylinder, || get_cylinder(state, item_entity)) { + debug!( + ?entity_cylinder, + "Failed to pick up item as not within range, Uid: {}", uid + ); + return; + }; // Grab the health from the entity and check if the entity is dead. let healths = state.ecs().read_storage::(); @@ -131,34 +130,19 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv if let Some(block) = block { if block.is_collectible() && state.can_set_block(pos) { // Check if the block is within pickup range - { - let ecs = state.ecs(); - let positions = ecs.read_storage::(); - let scales = ecs.read_storage::(); - let colliders = ecs.read_storage::(); - let char_states = ecs.read_storage::(); - - let entity_cylinder = positions.get(entity).map(|p| { - find_dist::Cylinder::from_components( - p.0, - scales.get(entity).copied(), - colliders.get(entity).copied(), - char_states.get(entity), - ) - }); - if !within_pickup_range(entity_cylinder, || { - Some(find_dist::Cube { - pos: pos.as_(), - side_length: 1.0, - }) - }) { - debug!( - ?entity_cylinder, - "Failed to pick up block as not within range, block pos: {}", pos - ); - return; - }; - } + let entity_cylinder = get_cylinder(state, entity); + if !within_pickup_range(entity_cylinder, || { + Some(find_dist::Cube { + min: pos.as_(), + side_length: 1.0, + }) + }) { + debug!( + ?entity_cylinder, + "Failed to pick up block as not within range, block pos: {}", pos + ); + return; + }; if let Some(item) = comp::Item::try_reclaim_from_block(block) { let (event, item_was_added) = if let Some(inv) = state diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 5f8fb6eedb..a2e4857613 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -1383,7 +1383,7 @@ fn select_interactable( // Pick closer one if they exist closest_interactable_block_pos - .filter(|block_pos| player_cylinder.min_distance(Cube { pos: block_pos.as_(), side_length: 1.0}) < search_dist) + .filter(|block_pos| player_cylinder.min_distance(Cube { min: block_pos.as_(), side_length: 1.0}) < search_dist) .and_then(|block_pos| client.state().terrain().get(block_pos).ok().copied() .map(|b| Interactable::Block(b, block_pos))