From 23c774c8daad0549f42049248a219c79f1e67a38 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 7 Jul 2020 12:42:05 +0100 Subject: [PATCH 1/7] Fixed missed VD update bug --- server/src/sys/message.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index 3ea09a0003..f346f1fab0 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -146,20 +146,20 @@ impl Sys { }, ClientMsg::SetViewDistance(view_distance) => { if let ClientState::Character { .. } = client.client_state { + players.get_mut(entity).map(|player| { + player.view_distance = Some( + settings + .max_view_distance + .map(|max| view_distance.min(max)) + .unwrap_or(view_distance), + ) + }); + if settings .max_view_distance - .map(|max| view_distance <= max) - .unwrap_or(true) + .map(|max| view_distance > max) + .unwrap_or(false) { - players.get_mut(entity).map(|player| { - player.view_distance = Some( - settings - .max_view_distance - .map(|max| view_distance.min(max)) - .unwrap_or(view_distance), - ) - }); - } else { client.notify(ServerMsg::SetViewDistance( settings.max_view_distance.unwrap_or(0), )); From 47e413c530d226ab7467c5a82f167b610740d3fa Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 7 Jul 2020 18:23:01 +0100 Subject: [PATCH 2/7] Improved pathfinding tolerance and reliability --- common/src/path.rs | 210 ++++++++++++++++++++++++++-------------- common/src/sys/agent.rs | 2 +- 2 files changed, 136 insertions(+), 76 deletions(-) diff --git a/common/src/path.rs b/common/src/path.rs index 8388edb36c..b51bea7cb3 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -75,86 +75,124 @@ impl Route { where V: BaseVol + ReadVol, { - let next0 = self - .next(0) - .unwrap_or_else(|| pos.map(|e| e.floor() as i32)); - let next1 = self.next(1).unwrap_or(next0); - if vol.get(next0).map(|b| b.is_solid()).unwrap_or(false) { - None - } else { - let next_tgt = next0.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0); - if pos.xy().distance_squared(next_tgt.xy()) < traversal_tolerance.powf(2.0) - && next_tgt.z - pos.z < 0.2 - && next_tgt.z - pos.z > -2.2 + let (next0, next1, next_tgt) = loop { + let next0 = self + .next(0) + .unwrap_or_else(|| pos.map(|e| e.floor() as i32)); + + // Stop using obstructed paths + if vol.get(next0).map(|b| b.is_solid()).unwrap_or(false) { + return None; + } + + let next1 = self.next(1).unwrap_or(next0); + let next0_tgt = next0.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0); + let next1_tgt = next1.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0); + + // We might be able to skip a node in some cases to avoid doubling-back + let closest_tgt = if next0_tgt.distance_squared(pos) < next1_tgt.distance_squared(pos) { + next0_tgt + } else { + next1_tgt + }; + + // Determine whether we're close enough to the next to to consider it completed + if pos.xy().distance_squared(closest_tgt.xy()) < traversal_tolerance.powf(2.0) + && closest_tgt.z - pos.z < 0.2 + && closest_tgt.z - pos.z > -2.2 && vel.z <= 0.0 + // Only consider the node reached if there's nothing solid between us and it && vol - .ray(pos + Vec3::unit_z() * 0.5, next_tgt + Vec3::unit_z() * 0.5) + .ray(pos + Vec3::unit_z() * 1.5, closest_tgt + Vec3::unit_z() * 1.5) .until(|block| block.is_solid()) .cast() .0 - > pos.distance(next_tgt) * 0.9 + > pos.distance(closest_tgt) * 0.9 + && self.next_idx < self.path.len() { + // Node completed, move on to the next one self.next_idx += 1; + } else { + // The next node hasn't been reached yet, use it as a target + break (next0, next1, next0_tgt); } + }; - let line = LineSegment2 { - start: pos.xy(), - end: pos.xy() + vel.xy() * 100.0, - }; + let line = LineSegment2 { + start: pos.xy(), + end: pos.xy() + vel.xy() * 100.0, + }; - let align = |block_pos: Vec3| { - (0..2) - .map(|i| (0..2).map(move |j| Vec2::new(i, j))) - .flatten() - .map(|rpos| block_pos + rpos) - .map(|block_pos| { - let block_posf = block_pos.xy().map(|e| e as f32); - let proj = line.projected_point(block_posf); - let clamped = proj.clamped( - block_pos.xy().map(|e| e as f32), - block_pos.xy().map(|e| e as f32), - ); + // We don't always want to aim for the centre of block since this can create + // jerky zig-zag movement. This function attempts to find a position + // inside a target block's area that aligned nicely with our velocity. + // This has a twofold benefit: + // + // 1. Entities can move at any angle when + // running on a flat surface + // + // 2. We don't have to search diagonals when + // pathfinding - cartesian positions are enough since this code will + // make the entity move smoothly along them + let align = |block_pos: Vec3| { + (0..2) + .map(|i| (0..2).map(move |j| Vec2::new(i, j))) + .flatten() + .map(|rpos| block_pos + rpos) + .map(|block_pos| { + let block_posf = block_pos.xy().map(|e| e as f32); + let proj = line.projected_point(block_posf); + let clamped = proj.clamped( + block_pos.xy().map(|e| e as f32), + block_pos.xy().map(|e| e as f32), + ); - (proj.distance_squared(clamped), clamped) - }) - .min_by_key(|(d2, _)| (d2 * 1000.0) as i32) - .unwrap() - .1 - }; + (proj.distance_squared(clamped), clamped) + }) + .min_by_key(|(d2, _)| (d2 * 1000.0) as i32) + .unwrap() + .1 + }; - let cb = CubicBezier2 { - start: pos.xy(), - ctrl0: pos.xy() + vel.xy().try_normalized().unwrap_or_else(Vec2::zero), - ctrl1: align(next0), - end: align(next1), - }; + let cb = CubicBezier2 { + start: pos.xy(), + ctrl0: pos.xy() + vel.xy().try_normalized().unwrap_or_else(Vec2::zero) * 1.25, + ctrl1: align(next0), + end: align(next1), + }; - let tgt2d = cb.evaluate(0.5); - let tgt = Vec3::from(tgt2d) + Vec3::unit_z() * next_tgt.z; - let tgt_dir = (tgt - pos) - .xy() - .try_normalized() - .unwrap_or_else(Vec2::unit_y); - let next_dir = cb - .evaluate_derivative(0.5) - .try_normalized() - .unwrap_or(tgt_dir); + // Use a cubic spline of the next few targets to come up with a sensible target + // position. We want to use a position that gives smooth movement but is + // also accurate enough to avoid the agent getting stuck under ledges or + // falling off walls. + let tgt2d = cb.evaluate(0.5); + let tgt = Vec3::from(tgt2d) + Vec3::unit_z() * next_tgt.z; + let tgt_dir = (tgt - pos) + .xy() + .try_normalized() + .unwrap_or_else(Vec2::unit_y); + let next_dir = cb + .evaluate_derivative(0.5) + .try_normalized() + .unwrap_or(tgt_dir); - //let vel_dir = vel.xy().try_normalized().unwrap_or(Vec2::zero()); - //let avg_dir = (tgt_dir * 0.2 + vel_dir * - // 0.8).try_normalized().unwrap_or(Vec2::zero()); let bearing = - // Vec3::::from(avg_dir * (tgt - pos).xy().magnitude()) + Vec3::unit_z() * - // (tgt.z - pos.z); + //let vel_dir = vel.xy().try_normalized().unwrap_or(Vec2::zero()); + //let avg_dir = (tgt_dir * 0.2 + vel_dir * + // 0.8).try_normalized().unwrap_or(Vec2::zero()); let bearing = + // Vec3::::from(avg_dir * (tgt - pos).xy().magnitude()) + Vec3::unit_z() * + // (tgt.z - pos.z); - Some(( - tgt - pos, - next_dir - .dot(vel.xy().try_normalized().unwrap_or_else(Vec2::zero)) - .max(0.0) - * 0.75 - + 0.25, - )) - } + Some(( + tgt - pos, + // Control the entity's speed to hopefully stop us falling off walls on sharp corners. + // This code is very imperfect: it does its best but it can still fail for particularly + // fast entities. + next_dir + .dot(vel.xy().try_normalized().unwrap_or_else(Vec2::zero)) + .max(0.0) + * 0.75 + + 0.25, + )) } } @@ -186,33 +224,53 @@ impl Chaser { { let pos_to_tgt = pos.distance(tgt); + // If we're already close to the target then there's nothing to do if ((pos - tgt) * Vec3::new(1.0, 1.0, 2.0)).magnitude_squared() < min_dist.powf(2.0) { + self.route = None; return None; } let bearing = if let Some(end) = self.route.as_ref().and_then(|r| r.path().end().copied()) { let end_to_tgt = end.map(|e| e as f32).distance(tgt); - if end_to_tgt > pos_to_tgt * 0.3 + 5.0 || thread_rng().gen::() < 0.005 { + // If the target has moved significantly since the path was generated then it's + // time to search for a new path. Also, do this randomly from time + // to time to avoid any edge cases that cause us to get stuck. In + // theory this shouldn't happen, but in practice the world is full + // of unpredictable obstacles that are more than willing to mess up + // our day. TODO: Come up with a better heuristic for this + if end_to_tgt > pos_to_tgt * 0.3 + 5.0 + /* || thread_rng().gen::() < 0.005 */ + { None } else { self.route .as_mut() .and_then(|r| r.traverse(vol, pos, vel, traversal_tolerance)) + // In theory this filter isn't needed, but in practice agents often try to take + // stale paths that start elsewhere. This code makes sure that we're only using + // paths that start near us, avoiding the agent doubling back to chase a stale + // path. + .filter(|(bearing, _)| bearing.xy() + .magnitude_squared() < (traversal_tolerance * 3.0).powf(2.0)) } } else { None }; - // TODO: What happens when we get stuck? if let Some(bearing) = bearing { Some(bearing) } else { + // Only search for a path if the target has moved from their last position. We + // don't want to be thrashing the pathfinding code for targets that + // we're unable to access! if self .last_search_tgt .map(|last_tgt| last_tgt.distance(tgt) > pos_to_tgt * 0.15 + 5.0) .unwrap_or(true) + || self.route.is_none() { let (start_pos, path) = find_path(&mut self.astar, vol, pos, tgt); + // Don't use a stale path if start_pos.distance_squared(pos) < 4.0f32.powf(2.0) { self.route = path.map(Route::from); } else { @@ -331,14 +389,14 @@ where .unwrap_or(true))) }) .map(move |(pos, dir)| pos + dir) - .chain( - DIAGONALS - .iter() - .filter(move |(dir, [a, b])| { - is_walkable(&(pos + *dir)) && walkable[*a] && walkable[*b] - }) - .map(move |(dir, _)| pos + *dir), - ) + // .chain( + // DIAGONALS + // .iter() + // .filter(move |(dir, [a, b])| { + // is_walkable(&(pos + *dir)) && walkable[*a] && + // walkable[*b] }) + // .map(move |(dir, _)| pos + *dir), + // ) }; let crow_line = LineSegment2 { @@ -347,6 +405,8 @@ where }; let transition = |a: &Vec3, b: &Vec3| { + // Modify the heuristic a little in order to prefer paths that take us on a + // straight line toward our target. This means we get smoother movement. 1.0 + crow_line.distance_to_point(b.xy().map(|e| e as f32)) * 0.025 + (b.z - a.z - 1).max(0) as f32 * 3.0 }; diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 5fcfd5d47a..8e96fa54e9 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -126,7 +126,7 @@ impl<'a> System<'a> for Sys { // and so can afford to be less precise when trying to move around // the world (especially since they would otherwise get stuck on // obstacles that smaller entities would not). - let traversal_tolerance = scale + vel.0.magnitude() * 0.3; + let traversal_tolerance = scale + vel.0.magnitude() * 0.25; let mut do_idle = false; let mut choose_target = false; From ac30fcbd0ee968fa73c22e21b1746f358cef9773 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 8 Jul 2020 13:42:13 +0100 Subject: [PATCH 3/7] Commented unused but potentially useful code --- common/src/path.rs | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/common/src/path.rs b/common/src/path.rs index b51bea7cb3..051d68846f 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -4,7 +4,6 @@ use crate::{ vol::{BaseVol, ReadVol}, }; use hashbrown::hash_map::DefaultHashBuilder; -use rand::{thread_rng, Rng}; use std::iter::FromIterator; use vek::*; @@ -349,23 +348,23 @@ where Vec3::new(0, 0, -1), // Downwards ]; - let walkable = [ - is_walkable(&(pos + Vec3::new(1, 0, 0))), - is_walkable(&(pos + Vec3::new(-1, 0, 0))), - is_walkable(&(pos + Vec3::new(0, 1, 0))), - is_walkable(&(pos + Vec3::new(0, -1, 0))), - ]; + // let walkable = [ + // is_walkable(&(pos + Vec3::new(1, 0, 0))), + // is_walkable(&(pos + Vec3::new(-1, 0, 0))), + // is_walkable(&(pos + Vec3::new(0, 1, 0))), + // is_walkable(&(pos + Vec3::new(0, -1, 0))), + // ]; - const DIAGONALS: [(Vec3, [usize; 2]); 8] = [ - (Vec3::new(1, 1, 0), [0, 2]), - (Vec3::new(-1, 1, 0), [1, 2]), - (Vec3::new(1, -1, 0), [0, 3]), - (Vec3::new(-1, -1, 0), [1, 3]), - (Vec3::new(1, 1, 1), [0, 2]), - (Vec3::new(-1, 1, 1), [1, 2]), - (Vec3::new(1, -1, 1), [0, 3]), - (Vec3::new(-1, -1, 1), [1, 3]), - ]; + // const DIAGONALS: [(Vec3, [usize; 2]); 8] = [ + // (Vec3::new(1, 1, 0), [0, 2]), + // (Vec3::new(-1, 1, 0), [1, 2]), + // (Vec3::new(1, -1, 0), [0, 3]), + // (Vec3::new(-1, -1, 0), [1, 3]), + // (Vec3::new(1, 1, 1), [0, 2]), + // (Vec3::new(-1, 1, 1), [1, 2]), + // (Vec3::new(1, -1, 1), [0, 3]), + // (Vec3::new(-1, -1, 1), [1, 3]), + // ]; DIRS.iter() .map(move |dir| (pos, dir)) From 951a977b2ff7a3fa64f419b9d5ab20ec3f8973bd Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 10 Jul 2020 00:43:11 +0100 Subject: [PATCH 4/7] Improved hill path following --- common/src/path.rs | 165 +++++++++++++++++++++++++--------------- common/src/sys/agent.rs | 10 ++- 2 files changed, 110 insertions(+), 65 deletions(-) diff --git a/common/src/path.rs b/common/src/path.rs index 051d68846f..4d91693f2e 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -69,6 +69,7 @@ impl Route { vol: &V, pos: Vec3, vel: Vec3, + on_ground: bool, traversal_tolerance: f32, ) -> Option<(Vec3, f32)> where @@ -85,42 +86,51 @@ impl Route { } let next1 = self.next(1).unwrap_or(next0); - let next0_tgt = next0.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0); - let next1_tgt = next1.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0); - - // We might be able to skip a node in some cases to avoid doubling-back - let closest_tgt = if next0_tgt.distance_squared(pos) < next1_tgt.distance_squared(pos) { - next0_tgt - } else { - next1_tgt - }; + let next_tgt = next0.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0); // Determine whether we're close enough to the next to to consider it completed - if pos.xy().distance_squared(closest_tgt.xy()) < traversal_tolerance.powf(2.0) - && closest_tgt.z - pos.z < 0.2 - && closest_tgt.z - pos.z > -2.2 + if pos.xy().distance_squared(next_tgt.xy()) < traversal_tolerance.powf(2.0) + && (pos.z - next_tgt.z > 1.2 || (pos.z - next_tgt.z > -0.2 && on_ground)) + && pos.z - next_tgt.z < 2.2 && vel.z <= 0.0 // Only consider the node reached if there's nothing solid between us and it && vol - .ray(pos + Vec3::unit_z() * 1.5, closest_tgt + Vec3::unit_z() * 1.5) + .ray(pos + Vec3::unit_z() * 1.5, next_tgt + Vec3::unit_z() * 1.5) .until(|block| block.is_solid()) .cast() .0 - > pos.distance(closest_tgt) * 0.9 + > pos.distance(next_tgt) * 0.9 && self.next_idx < self.path.len() { // Node completed, move on to the next one self.next_idx += 1; } else { // The next node hasn't been reached yet, use it as a target - break (next0, next1, next0_tgt); + break (next0, next1, next_tgt); } }; - let line = LineSegment2 { - start: pos.xy(), - end: pos.xy() + vel.xy() * 100.0, - }; + fn gradient(line: LineSegment2) -> f32 { + let r = (line.start.y - line.end.y) / (line.start.x - line.end.x); + if r.is_nan() { 100000.0 } else { r } + } + + fn intersect(a: LineSegment2, b: LineSegment2) -> Option> { + let ma = gradient(a); + let mb = gradient(b); + + let ca = a.start.y - ma * a.start.x; + let cb = b.start.y - mb * b.start.x; + + if (ma - mb).abs() < 0.0001 || (ca - cb).abs() < 0.0001 { + None + } else { + let x = (cb - ca) / (ma - mb); + let y = ma * x + ca; + + Some(Vec2::new(x, y)) + } + } // We don't always want to aim for the centre of block since this can create // jerky zig-zag movement. This function attempts to find a position @@ -133,65 +143,92 @@ impl Route { // 2. We don't have to search diagonals when // pathfinding - cartesian positions are enough since this code will // make the entity move smoothly along them - let align = |block_pos: Vec3| { - (0..2) - .map(|i| (0..2).map(move |j| Vec2::new(i, j))) - .flatten() - .map(|rpos| block_pos + rpos) - .map(|block_pos| { - let block_posf = block_pos.xy().map(|e| e as f32); - let proj = line.projected_point(block_posf); - let clamped = proj.clamped( - block_pos.xy().map(|e| e as f32), - block_pos.xy().map(|e| e as f32), - ); + let corners = [ + Vec2::new(0, 0), + Vec2::new(1, 0), + Vec2::new(1, 1), + Vec2::new(0, 1), + Vec2::new(0, 0), // Repeated start + ]; - (proj.distance_squared(clamped), clamped) - }) - .min_by_key(|(d2, _)| (d2 * 1000.0) as i32) - .unwrap() - .1 + let vel_line = LineSegment2 { + start: pos.xy(), + end: pos.xy() + vel.xy() * 100.0, }; - let cb = CubicBezier2 { + let align = |block_pos: Vec3, precision: f32| { + let lerp_block = |x, precision| Lerp::lerp(x, block_pos.xy().map(|e| e as f32), precision); + + (0..4) + .filter_map(|i| { + let edge_line = LineSegment2 { + start: lerp_block((block_pos.xy() + corners[i]).map(|e| e as f32), precision), + end: lerp_block((block_pos.xy() + corners[i + 1]).map(|e| e as f32), precision), + }; + intersect(vel_line, edge_line) + .filter(|intersect| intersect.clamped( + block_pos.xy().map(|e| e as f32), + block_pos.xy().map(|e| e as f32 + 1.0), + ).distance_squared(*intersect) < 0.001) + }) + .min_by_key(|intersect: &Vec2| (intersect.distance_squared(vel_line.end) * 1000.0) as i32) + .unwrap_or_else(|| (0..2) + .map(|i| (0..2).map(move |j| Vec2::new(i, j))) + .flatten() + .map(|rpos| block_pos + rpos) + .map(|block_pos| { + let block_posf = block_pos.xy().map(|e| e as f32); + let proj = vel_line.projected_point(block_posf); + let clamped = lerp_block(proj.clamped( + block_pos.xy().map(|e| e as f32), + block_pos.xy().map(|e| e as f32), + ), precision); + + (proj.distance_squared(clamped), clamped) + }) + .min_by_key(|(d2, _)| (d2 * 1000.0) as i32) + .unwrap() + .1) + }; + + let bez = CubicBezier2 { start: pos.xy(), - ctrl0: pos.xy() + vel.xy().try_normalized().unwrap_or_else(Vec2::zero) * 1.25, - ctrl1: align(next0), - end: align(next1), + ctrl0: pos.xy() + vel.xy().try_normalized().unwrap_or(Vec2::zero()) * 1.0, + ctrl1: align(next0, 1.0), + end: align(next1, 1.0), }; // Use a cubic spline of the next few targets to come up with a sensible target // position. We want to use a position that gives smooth movement but is // also accurate enough to avoid the agent getting stuck under ledges or // falling off walls. - let tgt2d = cb.evaluate(0.5); - let tgt = Vec3::from(tgt2d) + Vec3::unit_z() * next_tgt.z; - let tgt_dir = (tgt - pos) - .xy() + let next_dir = bez + .evaluate_derivative(0.85) .try_normalized() - .unwrap_or_else(Vec2::unit_y); - let next_dir = cb - .evaluate_derivative(0.5) - .try_normalized() - .unwrap_or(tgt_dir); + .unwrap_or(Vec2::zero()); + let straight_factor = next_dir + .dot(vel.xy().try_normalized().unwrap_or(next_dir)) + .max(0.0) + .powf(2.0); - //let vel_dir = vel.xy().try_normalized().unwrap_or(Vec2::zero()); - //let avg_dir = (tgt_dir * 0.2 + vel_dir * - // 0.8).try_normalized().unwrap_or(Vec2::zero()); let bearing = - // Vec3::::from(avg_dir * (tgt - pos).xy().magnitude()) + Vec3::unit_z() * - // (tgt.z - pos.z); + let bez = CubicBezier2 { + start: pos.xy(), + ctrl0: pos.xy() + vel.xy().try_normalized().unwrap_or(Vec2::zero()) * 1.0, + ctrl1: align(next0, (1.0 - straight_factor * if (next0.z as f32 - pos.z).abs() < 0.25 { 1.0 } else { 0.0 }).max(0.1)), + end: align(next1, 1.0), + }; + + let tgt2d = bez.evaluate(if (next0.z as f32 - pos.z).abs() < 0.25 { 0.25 } else { 0.5 }); + let tgt = Vec3::from(tgt2d) + Vec3::unit_z() * next_tgt.z; Some(( tgt - pos, // Control the entity's speed to hopefully stop us falling off walls on sharp corners. // This code is very imperfect: it does its best but it can still fail for particularly // fast entities. - next_dir - .dot(vel.xy().try_normalized().unwrap_or_else(Vec2::zero)) - .max(0.0) - * 0.75 - + 0.25, + straight_factor * 0.75 + 0.25, )) + .filter(|(bearing, _)| bearing.z < 2.1) } } @@ -214,6 +251,7 @@ impl Chaser { vol: &V, pos: Vec3, vel: Vec3, + on_ground: bool, tgt: Vec3, min_dist: f32, traversal_tolerance: f32, @@ -244,7 +282,7 @@ impl Chaser { } else { self.route .as_mut() - .and_then(|r| r.traverse(vol, pos, vel, traversal_tolerance)) + .and_then(|r| r.traverse(vol, pos, vel, on_ground, traversal_tolerance)) // In theory this filter isn't needed, but in practice agents often try to take // stale paths that start elsewhere. This code makes sure that we're only using // paths that start near us, avoiding the agent doubling back to chase a stale @@ -256,8 +294,8 @@ impl Chaser { None }; - if let Some(bearing) = bearing { - Some(bearing) + if let Some((bearing, speed)) = bearing { + Some((bearing, speed)) } else { // Only search for a path if the target has moved from their last position. We // don't want to be thrashing the pathfinding code for targets that @@ -266,6 +304,7 @@ impl Chaser { .last_search_tgt .map(|last_tgt| last_tgt.distance(tgt) > pos_to_tgt * 0.15 + 5.0) .unwrap_or(true) + || self.astar.is_some() || self.route.is_none() { let (start_pos, path) = find_path(&mut self.astar, vol, pos, tgt); @@ -407,7 +446,7 @@ where // Modify the heuristic a little in order to prefer paths that take us on a // straight line toward our target. This means we get smoother movement. 1.0 + crow_line.distance_to_point(b.xy().map(|e| e as f32)) * 0.025 - + (b.z - a.z - 1).max(0) as f32 * 3.0 + + (b.z - a.z - 1).max(0) as f32 * 10.0 }; let satisfied = |pos: &Vec3| pos == &end; diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 8e96fa54e9..0a3797dea1 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -4,7 +4,7 @@ use crate::{ agent::Activity, item::{tool::ToolKind, ItemKind}, Agent, Alignment, CharacterState, ChatMsg, ControlAction, Controller, Loadout, MountState, - Ori, Pos, Scale, Stats, Vel, + Ori, Pos, Scale, Stats, Vel, PhysicsState, }, event::{EventBus, ServerEvent}, path::Chaser, @@ -38,6 +38,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Stats>, ReadStorage<'a, Loadout>, ReadStorage<'a, CharacterState>, + ReadStorage<'a, PhysicsState>, ReadStorage<'a, Uid>, ReadExpect<'a, TerrainGrid>, ReadStorage<'a, Alignment>, @@ -62,6 +63,7 @@ impl<'a> System<'a> for Sys { stats, loadouts, character_states, + physics_states, uids, terrain, alignments, @@ -78,6 +80,7 @@ impl<'a> System<'a> for Sys { alignment, loadout, character_state, + physics_state, uid, agent, controller, @@ -90,6 +93,7 @@ impl<'a> System<'a> for Sys { alignments.maybe(), &loadouts, &character_states, + &physics_states, &uids, &mut agents, &mut controllers, @@ -126,7 +130,7 @@ impl<'a> System<'a> for Sys { // and so can afford to be less precise when trying to move around // the world (especially since they would otherwise get stuck on // obstacles that smaller entities would not). - let traversal_tolerance = scale + vel.0.magnitude() * 0.25; + let traversal_tolerance = scale + vel.0.xy().magnitude() * 0.2; let mut do_idle = false; let mut choose_target = false; @@ -198,6 +202,7 @@ impl<'a> System<'a> for Sys { &*terrain, pos.0, vel.0, + physics_state.on_ground, tgt_pos.0, AVG_FOLLOW_DIST, traversal_tolerance, @@ -314,6 +319,7 @@ impl<'a> System<'a> for Sys { &*terrain, pos.0, vel.0, + physics_state.on_ground, tgt_pos.0, 1.25, traversal_tolerance, From cf69d0c5d885eb2754095cb6c30775b638ec8753 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 10 Jul 2020 15:00:20 +0100 Subject: [PATCH 5/7] Added minimap compass --- CHANGELOG.md | 1 + common/src/path.rs | 172 ++++++++++++++++++++++++++----------- common/src/states/utils.rs | 6 +- common/src/sys/agent.rs | 11 ++- voxygen/src/hud/minimap.rs | 46 +++++++++- voxygen/src/hud/mod.rs | 4 +- 6 files changed, 182 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2a842d4df..66cec07206 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Training dummy items - Added spin attack for axe - Creature specific stats +- Minimap compass ### Changed diff --git a/common/src/path.rs b/common/src/path.rs index 4d91693f2e..cf8e6b8e73 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -4,6 +4,7 @@ use crate::{ vol::{BaseVol, ReadVol}, }; use hashbrown::hash_map::DefaultHashBuilder; +use rand::prelude::*; use std::iter::FromIterator; use vek::*; @@ -71,11 +72,12 @@ impl Route { vel: Vec3, on_ground: bool, traversal_tolerance: f32, + slow_factor: f32, ) -> Option<(Vec3, f32)> where V: BaseVol + ReadVol, { - let (next0, next1, next_tgt) = loop { + let (next0, next1, next_tgt, be_precise) = loop { let next0 = self .next(0) .unwrap_or_else(|| pos.map(|e| e.floor() as i32)); @@ -85,28 +87,57 @@ impl Route { return None; } + let diagonals = [ + Vec2::new(1, 0), + Vec2::new(1, 1), + Vec2::new(0, 1), + Vec2::new(-1, 1), + Vec2::new(-1, 0), + Vec2::new(-1, -1), + Vec2::new(0, -1), + Vec2::new(1, -1), + ]; + let next1 = self.next(1).unwrap_or(next0); - let next_tgt = next0.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0); + + let be_precise = diagonals.iter().any(|pos| { + !walkable(vol, next0 + Vec3::new(pos.x, pos.y, 0)) + && !walkable(vol, next0 + Vec3::new(pos.x, pos.y, -1)) + && !walkable(vol, next0 + Vec3::new(pos.x, pos.y, -2)) + && !walkable(vol, next0 + Vec3::new(pos.x, pos.y, 1)) + }); + + let next0_tgt = next0.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0); + let next1_tgt = next1.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0); + let next_tgt = next0_tgt; + + // Maybe skip a node (useful with traversing downhill) + let closest_tgt = if next0_tgt.distance_squared(pos) < next1_tgt.distance_squared(pos) { + next0_tgt + } else { + next1_tgt + }; // Determine whether we're close enough to the next to to consider it completed - if pos.xy().distance_squared(next_tgt.xy()) < traversal_tolerance.powf(2.0) - && (pos.z - next_tgt.z > 1.2 || (pos.z - next_tgt.z > -0.2 && on_ground)) - && pos.z - next_tgt.z < 2.2 + let dist_sqrd = pos.xy().distance_squared(closest_tgt.xy()); + if dist_sqrd < traversal_tolerance.powf(2.0) * if be_precise { 0.25 } else { 1.0 } + && (pos.z - closest_tgt.z > 1.2 || (pos.z - closest_tgt.z > -0.2 && on_ground)) + && (pos.z - closest_tgt.z < 1.2 || (pos.z - closest_tgt.z < 2.9 && vel.z < -0.05)) && vel.z <= 0.0 // Only consider the node reached if there's nothing solid between us and it && vol - .ray(pos + Vec3::unit_z() * 1.5, next_tgt + Vec3::unit_z() * 1.5) + .ray(pos + Vec3::unit_z() * 1.5, closest_tgt + Vec3::unit_z() * 1.5) .until(|block| block.is_solid()) .cast() .0 - > pos.distance(next_tgt) * 0.9 + > pos.distance(closest_tgt) * 0.9 && self.next_idx < self.path.len() { // Node completed, move on to the next one self.next_idx += 1; } else { // The next node hasn't been reached yet, use it as a target - break (next0, next1, next_tgt); + break (next0, next1, next_tgt, be_precise); } }; @@ -157,38 +188,56 @@ impl Route { }; let align = |block_pos: Vec3, precision: f32| { - let lerp_block = |x, precision| Lerp::lerp(x, block_pos.xy().map(|e| e as f32), precision); + let lerp_block = + |x, precision| Lerp::lerp(x, block_pos.xy().map(|e| e as f32), precision); (0..4) .filter_map(|i| { let edge_line = LineSegment2 { - start: lerp_block((block_pos.xy() + corners[i]).map(|e| e as f32), precision), - end: lerp_block((block_pos.xy() + corners[i + 1]).map(|e| e as f32), precision), + start: lerp_block( + (block_pos.xy() + corners[i]).map(|e| e as f32), + precision, + ), + end: lerp_block( + (block_pos.xy() + corners[i + 1]).map(|e| e as f32), + precision, + ), }; - intersect(vel_line, edge_line) - .filter(|intersect| intersect.clamped( - block_pos.xy().map(|e| e as f32), - block_pos.xy().map(|e| e as f32 + 1.0), - ).distance_squared(*intersect) < 0.001) + intersect(vel_line, edge_line).filter(|intersect| { + intersect + .clamped( + block_pos.xy().map(|e| e as f32), + block_pos.xy().map(|e| e as f32 + 1.0), + ) + .distance_squared(*intersect) + < 0.001 + }) }) - .min_by_key(|intersect: &Vec2| (intersect.distance_squared(vel_line.end) * 1000.0) as i32) - .unwrap_or_else(|| (0..2) - .map(|i| (0..2).map(move |j| Vec2::new(i, j))) + .min_by_key(|intersect: &Vec2| { + (intersect.distance_squared(vel_line.end) * 1000.0) as i32 + }) + .unwrap_or_else(|| { + (0..2) + .map(|i| (0..2).map(move |j| Vec2::new(i, j))) .flatten() .map(|rpos| block_pos + rpos) .map(|block_pos| { let block_posf = block_pos.xy().map(|e| e as f32); let proj = vel_line.projected_point(block_posf); - let clamped = lerp_block(proj.clamped( - block_pos.xy().map(|e| e as f32), - block_pos.xy().map(|e| e as f32), - ), precision); + let clamped = lerp_block( + proj.clamped( + block_pos.xy().map(|e| e as f32), + block_pos.xy().map(|e| e as f32), + ), + precision, + ); (proj.distance_squared(clamped), clamped) }) .min_by_key(|(d2, _)| (d2 * 1000.0) as i32) .unwrap() - .1) + .1 + }) }; let bez = CubicBezier2 { @@ -214,21 +263,38 @@ impl Route { let bez = CubicBezier2 { start: pos.xy(), ctrl0: pos.xy() + vel.xy().try_normalized().unwrap_or(Vec2::zero()) * 1.0, - ctrl1: align(next0, (1.0 - straight_factor * if (next0.z as f32 - pos.z).abs() < 0.25 { 1.0 } else { 0.0 }).max(0.1)), + ctrl1: align( + next0, + (1.0 - straight_factor + * if (next0.z as f32 - pos.z).abs() < 0.25 && !be_precise { + 1.0 + } else { + 0.0 + }) + .max(0.1), + ), end: align(next1, 1.0), }; - let tgt2d = bez.evaluate(if (next0.z as f32 - pos.z).abs() < 0.25 { 0.25 } else { 0.5 }); - let tgt = Vec3::from(tgt2d) + Vec3::unit_z() * next_tgt.z; + let tgt2d = bez.evaluate(if (next0.z as f32 - pos.z).abs() < 0.25 { + 0.25 + } else { + 0.5 + }); + let tgt = if be_precise { + next_tgt + } else { + Vec3::from(tgt2d) + Vec3::unit_z() * next_tgt.z + }; Some(( tgt - pos, // Control the entity's speed to hopefully stop us falling off walls on sharp corners. // This code is very imperfect: it does its best but it can still fail for particularly // fast entities. - straight_factor * 0.75 + 0.25, + straight_factor * slow_factor + (1.0 - slow_factor), )) - .filter(|(bearing, _)| bearing.z < 2.1) + .filter(|(bearing, _)| bearing.z < 2.1) } } @@ -255,6 +321,7 @@ impl Chaser { tgt: Vec3, min_dist: f32, traversal_tolerance: f32, + slow_factor: f32, ) -> Option<(Vec3, f32)> where V: BaseVol + ReadVol, @@ -282,13 +349,14 @@ impl Chaser { } else { self.route .as_mut() - .and_then(|r| r.traverse(vol, pos, vel, on_ground, traversal_tolerance)) + .and_then(|r| r.traverse(vol, pos, vel, on_ground, traversal_tolerance, slow_factor)) // In theory this filter isn't needed, but in practice agents often try to take // stale paths that start elsewhere. This code makes sure that we're only using // paths that start near us, avoiding the agent doubling back to chase a stale // path. .filter(|(bearing, _)| bearing.xy() - .magnitude_squared() < (traversal_tolerance * 3.0).powf(2.0)) + .magnitude_squared() < 1.75f32.powf(2.0) + && thread_rng().gen::() > 0.025) } } else { None @@ -321,6 +389,24 @@ impl Chaser { } } +#[allow(clippy::float_cmp)] // TODO: Pending review in #587 +fn walkable(vol: &V, pos: Vec3) -> bool +where + V: BaseVol + ReadVol, +{ + vol.get(pos - Vec3::new(0, 0, 1)) + .map(|b| b.is_solid() && b.get_height() == 1.0) + .unwrap_or(false) + && vol + .get(pos + Vec3::new(0, 0, 0)) + .map(|b| !b.is_solid()) + .unwrap_or(true) + && vol + .get(pos + Vec3::new(0, 0, 1)) + .map(|b| !b.is_solid()) + .unwrap_or(true) +} + #[allow(clippy::float_cmp)] // TODO: Pending review in #587 fn find_path( astar: &mut Option, DefaultHashBuilder>>, @@ -331,19 +417,7 @@ fn find_path( where V: BaseVol + ReadVol, { - let is_walkable = |pos: &Vec3| { - vol.get(*pos - Vec3::new(0, 0, 1)) - .map(|b| b.is_solid() && b.get_height() == 1.0) - .unwrap_or(false) - && vol - .get(*pos + Vec3::new(0, 0, 0)) - .map(|b| !b.is_solid()) - .unwrap_or(true) - && vol - .get(*pos + Vec3::new(0, 0, 1)) - .map(|b| !b.is_solid()) - .unwrap_or(true) - }; + let is_walkable = |pos: &Vec3| walkable(vol, *pos); let get_walkable_z = |pos| { let mut z_incr = 0; for _ in 0..32 { @@ -437,12 +511,12 @@ where // ) }; - let crow_line = LineSegment2 { - start: startf.xy(), - end: endf.xy(), - }; - let transition = |a: &Vec3, b: &Vec3| { + let crow_line = LineSegment2 { + start: startf.xy(), + end: endf.xy(), + }; + // Modify the heuristic a little in order to prefer paths that take us on a // straight line toward our target. This means we get smoother movement. 1.0 + crow_line.distance_to_point(b.xy().map(|e| e as f32)) * 0.025 diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 7ebec30dc4..ce01809f80 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -31,9 +31,9 @@ impl Body { pub fn base_accel(&self) -> f32 { match self { Body::Humanoid(_) => 100.0, - Body::QuadrupedSmall(_) => 80.0, + Body::QuadrupedSmall(_) => 85.0, Body::QuadrupedMedium(_) => 180.0, - Body::BirdMedium(_) => 70.0, + Body::BirdMedium(_) => 80.0, Body::FishMedium(_) => 50.0, Body::Dragon(_) => 250.0, Body::BirdSmall(_) => 75.0, @@ -41,7 +41,7 @@ impl Body { Body::BipedLarge(_) => 120.0, Body::Object(_) => 40.0, Body::Golem(_) => 130.0, - Body::Critter(_) => 65.0, + Body::Critter(_) => 85.0, Body::QuadrupedLow(_) => 120.0, } } diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 0a3797dea1..aa6159f0a9 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -3,8 +3,8 @@ use crate::{ self, agent::Activity, item::{tool::ToolKind, ItemKind}, - Agent, Alignment, CharacterState, ChatMsg, ControlAction, Controller, Loadout, MountState, - Ori, Pos, Scale, Stats, Vel, PhysicsState, + Agent, Alignment, Body, CharacterState, ChatMsg, ControlAction, Controller, Loadout, + MountState, Ori, PhysicsState, Pos, Scale, Stats, Vel, }, event::{EventBus, ServerEvent}, path::Chaser, @@ -42,6 +42,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Uid>, ReadExpect<'a, TerrainGrid>, ReadStorage<'a, Alignment>, + ReadStorage<'a, Body>, WriteStorage<'a, Agent>, WriteStorage<'a, Controller>, ReadStorage<'a, MountState>, @@ -67,6 +68,7 @@ impl<'a> System<'a> for Sys { uids, terrain, alignments, + bodies, mut agents, mut controllers, mount_states, @@ -81,6 +83,7 @@ impl<'a> System<'a> for Sys { loadout, character_state, physics_state, + body, uid, agent, controller, @@ -94,6 +97,7 @@ impl<'a> System<'a> for Sys { &loadouts, &character_states, &physics_states, + bodies.maybe(), &uids, &mut agents, &mut controllers, @@ -131,6 +135,7 @@ impl<'a> System<'a> for Sys { // the world (especially since they would otherwise get stuck on // obstacles that smaller entities would not). let traversal_tolerance = scale + vel.0.xy().magnitude() * 0.2; + let slow_factor = body.map(|b| b.base_accel() / 250.0).unwrap_or(0.0).min(1.0); let mut do_idle = false; let mut choose_target = false; @@ -206,6 +211,7 @@ impl<'a> System<'a> for Sys { tgt_pos.0, AVG_FOLLOW_DIST, traversal_tolerance, + slow_factor, ) { inputs.move_dir = bearing.xy().try_normalized().unwrap_or(Vec2::zero()) @@ -323,6 +329,7 @@ impl<'a> System<'a> for Sys { tgt_pos.0, 1.25, traversal_tolerance, + slow_factor, ) { inputs.move_dir = Vec2::from(bearing) .try_normalized() diff --git a/voxygen/src/hud/minimap.rs b/voxygen/src/hud/minimap.rs index 3a0225539b..efc330ce7b 100644 --- a/voxygen/src/hud/minimap.rs +++ b/voxygen/src/hud/minimap.rs @@ -8,7 +8,7 @@ use common::{comp, terrain::TerrainChunkSize, vol::RectVolSize}; use conrod_core::{ color, position, widget::{self, Button, Image, Rectangle, Text}, - widget_ids, Colorable, Positionable, Sizeable, Widget, WidgetCommon, + widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, }; use specs::WorldExt; use vek::*; @@ -23,7 +23,11 @@ widget_ids! { mmap_plus, mmap_minus, grid, - indicator + indicator, + mmap_north, + mmap_east, + mmap_south, + mmap_west, } } @@ -39,6 +43,7 @@ pub struct MiniMap<'a> { fonts: &'a ConrodVoxygenFonts, #[conrod(common_builder)] common: widget::CommonBuilder, + ori: Vec3, } impl<'a> MiniMap<'a> { @@ -49,6 +54,7 @@ impl<'a> MiniMap<'a> { rot_imgs: &'a ImgsRot, world_map: &'a (img_ids::Rotations, Vec2), fonts: &'a ConrodVoxygenFonts, + ori: Vec3, ) -> Self { Self { show, @@ -58,6 +64,7 @@ impl<'a> MiniMap<'a> { world_map, fonts, common: widget::CommonBuilder::default(), + ori, } } } @@ -195,10 +202,12 @@ impl<'a> Widget for MiniMap<'a> { [w_src, h_src], ); + let map_size = Vec2::new(170.0, 170.0); + // Map Image Image::new(world_map.source_north) .middle_of(state.ids.mmap_frame_bg) - .w_h(170.0 * SCALE, 170.0 * SCALE) + .w_h(map_size.x * SCALE, map_size.y * SCALE) .parent(state.ids.mmap_frame_bg) .source_rectangle(rect_src) .set(state.ids.grid, ui); @@ -212,6 +221,37 @@ impl<'a> Widget for MiniMap<'a> { .floating(true) .parent(ui.window) .set(state.ids.indicator, ui); + + // Compass directions + let dirs = [ + (Vec2::new(0.0, 1.0), state.ids.mmap_north, "N", true), + (Vec2::new(1.0, 0.0), state.ids.mmap_east, "E", false), + (Vec2::new(0.0, -1.0), state.ids.mmap_south, "S", false), + (Vec2::new(-1.0, 0.0), state.ids.mmap_west, "W", false), + ]; + for (dir, id, name, bold) in dirs.iter() { + let cardinal_dir = Vec2::unit_x().rotated_z(self.ori.x as f64) * dir.x + + Vec2::unit_y().rotated_z(self.ori.x as f64) * dir.y; + let clamped = (cardinal_dir * 3.0) + / (cardinal_dir * 3.0).map(|e| e.abs()).reduce_partial_max(); + let pos = clamped * (map_size * 0.75 - 10.0); + Text::new(name) + .x_y_position_relative_to( + state.ids.grid, + position::Relative::Scalar(pos.x), + position::Relative::Scalar(pos.y), + ) + .font_size(self.fonts.cyri.scale(18)) + .font_id(self.fonts.cyri.conrod_id) + .color(if *bold { + Color::Rgba(0.7, 0.3, 0.3, 1.0) + } else { + TEXT_COLOR + }) + .floating(true) + .parent(ui.window) + .set(*id, ui); + } } else { Image::new(self.imgs.mmap_frame_closed) .w_h(174.0 * SCALE, 18.0 * SCALE) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index da04cedf2f..2768a7ab4b 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -598,6 +598,7 @@ impl Hud { debug_info: DebugInfo, dt: Duration, info: HudInfo, + camera: &Camera, ) -> Vec { let mut events = std::mem::replace(&mut self.events, Vec::new()); let (ref mut ui_widgets, ref mut tooltip_manager) = self.ui.set_widgets(); @@ -1497,6 +1498,7 @@ impl Hud { &self.rot_imgs, &self.world_map, &self.fonts, + camera.get_orientation(), ) .set(self.ids.minimap, ui_widgets) { @@ -2246,7 +2248,7 @@ impl Hud { if let Some(maybe_id) = self.to_focus.take() { self.ui.focus_widget(maybe_id); } - let events = self.update_layout(client, global_state, debug_info, dt, info); + let events = self.update_layout(client, global_state, debug_info, dt, info, camera); let camera::Dependents { view_mat, proj_mat, .. } = camera.dependents(); From 3c72022c81b28c2ea452dd6cdf0b74dd3f271f69 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 10 Jul 2020 18:21:34 +0100 Subject: [PATCH 6/7] Nicer north colour --- voxygen/src/hud/minimap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voxygen/src/hud/minimap.rs b/voxygen/src/hud/minimap.rs index efc330ce7b..c47a2aa420 100644 --- a/voxygen/src/hud/minimap.rs +++ b/voxygen/src/hud/minimap.rs @@ -244,7 +244,7 @@ impl<'a> Widget for MiniMap<'a> { .font_size(self.fonts.cyri.scale(18)) .font_id(self.fonts.cyri.conrod_id) .color(if *bold { - Color::Rgba(0.7, 0.3, 0.3, 1.0) + Color::Rgba(0.75, 0.0, 0.0, 1.0) } else { TEXT_COLOR }) From 3e5c3de2ac1d46ff8be505802c4b4996c4cdfee3 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 13 Jul 2020 23:23:44 +0100 Subject: [PATCH 7/7] Neater compass --- common/src/path.rs | 50 ++++++++++++++++++++++---------------- common/src/spiral.rs | 1 + common/src/sys/agent.rs | 24 ++++++++++-------- voxygen/src/hud/minimap.rs | 2 +- 4 files changed, 45 insertions(+), 32 deletions(-) diff --git a/common/src/path.rs b/common/src/path.rs index cf8e6b8e73..d6c10bed98 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -56,6 +56,18 @@ impl From>> for Route { fn from(path: Path>) -> Self { Self { path, next_idx: 0 } } } +pub struct TraversalConfig { + /// The distance to a node at which node is considered visited. + pub node_tolerance: f32, + /// The slowdown factor when following corners. + /// 0.0 = no slowdown on corners, 1.0 = total slowdown on corners. + pub slow_factor: f32, + /// Whether the agent is currently on the ground. + pub on_ground: bool, + /// The distance to the target below which it is considered reached. + pub min_tgt_dist: f32, +} + impl Route { pub fn path(&self) -> &Path> { &self.path } @@ -70,9 +82,7 @@ impl Route { vol: &V, pos: Vec3, vel: Vec3, - on_ground: bool, - traversal_tolerance: f32, - slow_factor: f32, + traversal_cfg: TraversalConfig, ) -> Option<(Vec3, f32)> where V: BaseVol + ReadVol, @@ -120,8 +130,8 @@ impl Route { // Determine whether we're close enough to the next to to consider it completed let dist_sqrd = pos.xy().distance_squared(closest_tgt.xy()); - if dist_sqrd < traversal_tolerance.powf(2.0) * if be_precise { 0.25 } else { 1.0 } - && (pos.z - closest_tgt.z > 1.2 || (pos.z - closest_tgt.z > -0.2 && on_ground)) + if dist_sqrd < traversal_cfg.node_tolerance.powf(2.0) * if be_precise { 0.25 } else { 1.0 } + && (pos.z - closest_tgt.z > 1.2 || (pos.z - closest_tgt.z > -0.2 && traversal_cfg.on_ground)) && (pos.z - closest_tgt.z < 1.2 || (pos.z - closest_tgt.z < 2.9 && vel.z < -0.05)) && vel.z <= 0.0 // Only consider the node reached if there's nothing solid between us and it @@ -242,7 +252,7 @@ impl Route { let bez = CubicBezier2 { start: pos.xy(), - ctrl0: pos.xy() + vel.xy().try_normalized().unwrap_or(Vec2::zero()) * 1.0, + ctrl0: pos.xy() + vel.xy().try_normalized().unwrap_or_default() * 1.0, ctrl1: align(next0, 1.0), end: align(next1, 1.0), }; @@ -254,7 +264,7 @@ impl Route { let next_dir = bez .evaluate_derivative(0.85) .try_normalized() - .unwrap_or(Vec2::zero()); + .unwrap_or_default(); let straight_factor = next_dir .dot(vel.xy().try_normalized().unwrap_or(next_dir)) .max(0.0) @@ -262,15 +272,14 @@ impl Route { let bez = CubicBezier2 { start: pos.xy(), - ctrl0: pos.xy() + vel.xy().try_normalized().unwrap_or(Vec2::zero()) * 1.0, + ctrl0: pos.xy() + vel.xy().try_normalized().unwrap_or_default() * 1.0, ctrl1: align( next0, - (1.0 - straight_factor - * if (next0.z as f32 - pos.z).abs() < 0.25 && !be_precise { - 1.0 - } else { - 0.0 - }) + (1.0 - if (next0.z as f32 - pos.z).abs() < 0.25 && !be_precise { + straight_factor + } else { + 0.0 + }) .max(0.1), ), end: align(next1, 1.0), @@ -292,7 +301,7 @@ impl Route { // Control the entity's speed to hopefully stop us falling off walls on sharp corners. // This code is very imperfect: it does its best but it can still fail for particularly // fast entities. - straight_factor * slow_factor + (1.0 - slow_factor), + straight_factor * traversal_cfg.slow_factor + (1.0 - traversal_cfg.slow_factor), )) .filter(|(bearing, _)| bearing.z < 2.1) } @@ -317,11 +326,8 @@ impl Chaser { vol: &V, pos: Vec3, vel: Vec3, - on_ground: bool, tgt: Vec3, - min_dist: f32, - traversal_tolerance: f32, - slow_factor: f32, + traversal_cfg: TraversalConfig, ) -> Option<(Vec3, f32)> where V: BaseVol + ReadVol, @@ -329,7 +335,9 @@ impl Chaser { let pos_to_tgt = pos.distance(tgt); // If we're already close to the target then there's nothing to do - if ((pos - tgt) * Vec3::new(1.0, 1.0, 2.0)).magnitude_squared() < min_dist.powf(2.0) { + if ((pos - tgt) * Vec3::new(1.0, 1.0, 2.0)).magnitude_squared() + < traversal_cfg.min_tgt_dist.powf(2.0) + { self.route = None; return None; } @@ -349,7 +357,7 @@ impl Chaser { } else { self.route .as_mut() - .and_then(|r| r.traverse(vol, pos, vel, on_ground, traversal_tolerance, slow_factor)) + .and_then(|r| r.traverse(vol, pos, vel, traversal_cfg)) // In theory this filter isn't needed, but in practice agents often try to take // stale paths that start elsewhere. This code makes sure that we're only using // paths that start near us, avoiding the agent doubling back to chase a stale diff --git a/common/src/spiral.rs b/common/src/spiral.rs index 5a13b93d9d..2a76a3484d 100644 --- a/common/src/spiral.rs +++ b/common/src/spiral.rs @@ -16,6 +16,7 @@ impl Spiral2d { impl Iterator for Spiral2d { type Item = Vec2; + #[allow(clippy::erasing_op)] fn next(&mut self) -> Option { let layer_size = (self.layer * 8 + 4 * self.layer.min(1) - 4).max(1); if self.i >= layer_size { diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index aa6159f0a9..36b8afd4e9 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -7,7 +7,7 @@ use crate::{ MountState, Ori, PhysicsState, Pos, Scale, Stats, Vel, }, event::{EventBus, ServerEvent}, - path::Chaser, + path::{Chaser, TraversalConfig}, state::{DeltaTime, Time}, sync::{Uid, UidAllocator}, terrain::TerrainGrid, @@ -134,7 +134,7 @@ impl<'a> System<'a> for Sys { // and so can afford to be less precise when trying to move around // the world (especially since they would otherwise get stuck on // obstacles that smaller entities would not). - let traversal_tolerance = scale + vel.0.xy().magnitude() * 0.2; + let node_tolerance = scale + vel.0.xy().magnitude() * 0.2; let slow_factor = body.map(|b| b.base_accel() / 250.0).unwrap_or(0.0).min(1.0); let mut do_idle = false; @@ -207,11 +207,13 @@ impl<'a> System<'a> for Sys { &*terrain, pos.0, vel.0, - physics_state.on_ground, tgt_pos.0, - AVG_FOLLOW_DIST, - traversal_tolerance, - slow_factor, + TraversalConfig { + node_tolerance, + slow_factor, + on_ground: physics_state.on_ground, + min_tgt_dist: AVG_FOLLOW_DIST, + }, ) { inputs.move_dir = bearing.xy().try_normalized().unwrap_or(Vec2::zero()) @@ -325,11 +327,13 @@ impl<'a> System<'a> for Sys { &*terrain, pos.0, vel.0, - physics_state.on_ground, tgt_pos.0, - 1.25, - traversal_tolerance, - slow_factor, + TraversalConfig { + node_tolerance, + slow_factor, + on_ground: physics_state.on_ground, + min_tgt_dist: 1.25, + }, ) { inputs.move_dir = Vec2::from(bearing) .try_normalized() diff --git a/voxygen/src/hud/minimap.rs b/voxygen/src/hud/minimap.rs index c47a2aa420..b696c2c66b 100644 --- a/voxygen/src/hud/minimap.rs +++ b/voxygen/src/hud/minimap.rs @@ -234,7 +234,7 @@ impl<'a> Widget for MiniMap<'a> { + Vec2::unit_y().rotated_z(self.ori.x as f64) * dir.y; let clamped = (cardinal_dir * 3.0) / (cardinal_dir * 3.0).map(|e| e.abs()).reduce_partial_max(); - let pos = clamped * (map_size * 0.75 - 10.0); + let pos = clamped * (map_size * 0.73 - 10.0); Text::new(name) .x_y_position_relative_to( state.ids.grid,