veloren_common/
path.rs

1use crate::{
2    astar::{Astar, PathResult},
3    terrain::Block,
4    vol::{BaseVol, ReadVol},
5};
6use common_base::span;
7use fxhash::FxBuildHasher;
8#[cfg(feature = "rrt_pathfinding")]
9use hashbrown::HashMap;
10#[cfg(feature = "rrt_pathfinding")]
11use kiddo::{SquaredEuclidean, float::kdtree::KdTree, nearest_neighbour::NearestNeighbour}; /* For RRT paths (disabled for now) */
12use rand::{Rng, thread_rng};
13#[cfg(feature = "rrt_pathfinding")]
14use rand::{
15    distributions::{Distribution, Uniform},
16    prelude::IteratorRandom,
17};
18#[cfg(feature = "rrt_pathfinding")]
19use std::f32::consts::PI;
20use std::iter::FromIterator;
21use vek::*;
22
23// Path
24
25#[derive(Clone, Debug)]
26pub struct Path<T> {
27    pub nodes: Vec<T>,
28}
29
30impl<T> Default for Path<T> {
31    fn default() -> Self {
32        Self {
33            nodes: Vec::default(),
34        }
35    }
36}
37
38impl<T> FromIterator<T> for Path<T> {
39    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
40        Self {
41            nodes: iter.into_iter().collect(),
42        }
43    }
44}
45
46impl<T> IntoIterator for Path<T> {
47    type IntoIter = std::vec::IntoIter<T>;
48    type Item = T;
49
50    fn into_iter(self) -> Self::IntoIter { self.nodes.into_iter() }
51}
52
53impl<T> Path<T> {
54    pub fn is_empty(&self) -> bool { self.nodes.is_empty() }
55
56    pub fn len(&self) -> usize { self.nodes.len() }
57
58    pub fn iter(&self) -> impl Iterator<Item = &T> { self.nodes.iter() }
59
60    pub fn start(&self) -> Option<&T> { self.nodes.first() }
61
62    pub fn end(&self) -> Option<&T> { self.nodes.last() }
63
64    pub fn nodes(&self) -> &[T] { &self.nodes }
65}
66
67// Route: A path that can be progressed along
68
69#[derive(Default, Clone, Debug)]
70pub struct Route {
71    path: Path<Vec3<i32>>,
72    next_idx: usize,
73}
74
75impl From<Path<Vec3<i32>>> for Route {
76    fn from(path: Path<Vec3<i32>>) -> Self { Self { path, next_idx: 0 } }
77}
78
79pub struct TraversalConfig {
80    /// The distance to a node at which node is considered visited.
81    pub node_tolerance: f32,
82    /// The slowdown factor when following corners.
83    /// 0.0 = no slowdown on corners, 1.0 = total slowdown on corners.
84    pub slow_factor: f32,
85    /// Whether the agent is currently on the ground.
86    pub on_ground: bool,
87    /// Whether the agent is currently in water.
88    pub in_liquid: bool,
89    /// The distance to the target below which it is considered reached.
90    pub min_tgt_dist: f32,
91    /// Whether the agent can climb.
92    pub can_climb: bool,
93    /// Whether the agent can fly.
94    pub can_fly: bool,
95    /// Whether the agent has vectored propulsion.
96    pub vectored_propulsion: bool,
97    /// Whether chunk containing target position is currently loaded
98    pub is_target_loaded: bool,
99}
100
101const DIAGONALS: [Vec2<i32>; 8] = [
102    Vec2::new(1, 0),
103    Vec2::new(1, 1),
104    Vec2::new(0, 1),
105    Vec2::new(-1, 1),
106    Vec2::new(-1, 0),
107    Vec2::new(-1, -1),
108    Vec2::new(0, -1),
109    Vec2::new(1, -1),
110];
111
112impl Route {
113    pub fn path(&self) -> &Path<Vec3<i32>> { &self.path }
114
115    pub fn next(&self, i: usize) -> Option<Vec3<i32>> {
116        self.path.nodes.get(self.next_idx + i).copied()
117    }
118
119    pub fn is_finished(&self) -> bool { self.next(0).is_none() }
120
121    pub fn traverse<V>(
122        &mut self,
123        vol: &V,
124        pos: Vec3<f32>,
125        vel: Vec3<f32>,
126        traversal_cfg: &TraversalConfig,
127    ) -> Option<(Vec3<f32>, f32)>
128    where
129        V: BaseVol<Vox = Block> + ReadVol,
130    {
131        let (next0, next1, next_tgt, be_precise) = loop {
132            // If we've reached the end of the path, stop
133            let next0 = self.next(0)?;
134            let next1 = self.next(1).unwrap_or(next0);
135
136            // Stop using obstructed paths
137            if !walkable(vol, next0) || !walkable(vol, next1) {
138                return None;
139            }
140
141            // If, in any direction, there is a column of open air of several blocks
142            let open_space_nearby = DIAGONALS.iter().any(|pos| {
143                (-1..2).all(|z| {
144                    vol.get(next0 + Vec3::new(pos.x, pos.y, z))
145                        .map(|b| !b.is_solid())
146                        .unwrap_or(false)
147                })
148            });
149
150            // If, in any direction, there is a solid wall
151            let wall_nearby = DIAGONALS.iter().any(|pos| {
152                (0..2).all(|z| {
153                    vol.get(next0 + Vec3::new(pos.x, pos.y, z))
154                        .map(|b| b.is_solid())
155                        .unwrap_or(true)
156                })
157            });
158
159            // Unwalkable obstacles, such as walls or open space can affect path-finding
160            let be_precise = open_space_nearby | wall_nearby;
161
162            // Map position of node to middle of block
163            let next_tgt = next0.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0);
164            let closest_tgt = next_tgt.map2(pos, |tgt, pos| pos.clamped(tgt.floor(), tgt.ceil()));
165            // Determine whether we're close enough to the next to to consider it completed
166            let dist_sqrd = pos.xy().distance_squared(closest_tgt.xy());
167            if dist_sqrd
168                < traversal_cfg.node_tolerance.powi(2)
169                    * if be_precise {
170                        0.25
171                    } else if traversal_cfg.in_liquid {
172                        2.5
173                    } else {
174                        1.0
175                    }
176                && (((pos.z - closest_tgt.z > 1.2 || (pos.z - closest_tgt.z > -0.2 && traversal_cfg.on_ground))
177                    && (pos.z - closest_tgt.z < 1.2 || (pos.z - closest_tgt.z < 2.9 && vel.z < -0.05))
178                    && vel.z <= 0.0
179                    // Only consider the node reached if there's nothing solid between us and it
180                    && (vol
181                        .ray(pos + Vec3::unit_z() * 1.5, closest_tgt + Vec3::unit_z() * 1.5)
182                        .until(Block::is_solid)
183                        .cast()
184                        .0
185                        > pos.distance(closest_tgt) * 0.9 || dist_sqrd < 0.5)
186                    && self.next_idx < self.path.len())
187                    || (traversal_cfg.in_liquid
188                        && pos.z < closest_tgt.z + 0.8
189                        && pos.z > closest_tgt.z))
190            {
191                // Node completed, move on to the next one
192                self.next_idx += 1;
193            } else {
194                // The next node hasn't been reached yet, use it as a target
195                break (next0, next1, next_tgt, be_precise);
196            }
197        };
198
199        fn gradient(line: LineSegment2<f32>) -> f32 {
200            let r = (line.start.y - line.end.y) / (line.start.x - line.end.x);
201            if r.is_nan() { 100000.0 } else { r }
202        }
203
204        fn intersect(a: LineSegment2<f32>, b: LineSegment2<f32>) -> Option<Vec2<f32>> {
205            let ma = gradient(a);
206            let mb = gradient(b);
207
208            let ca = a.start.y - ma * a.start.x;
209            let cb = b.start.y - mb * b.start.x;
210
211            if (ma - mb).abs() < 0.0001 || (ca - cb).abs() < 0.0001 {
212                None
213            } else {
214                let x = (cb - ca) / (ma - mb);
215                let y = ma * x + ca;
216
217                Some(Vec2::new(x, y))
218            }
219        }
220
221        // We don't always want to aim for the centre of block since this can create
222        // jerky zig-zag movement. This function attempts to find a position
223        // inside a target block's area that aligned nicely with our velocity.
224        // This has a twofold benefit:
225        //
226        // 1. Entities can move at any angle when
227        // running on a flat surface
228        //
229        // 2. We don't have to search diagonals when
230        // pathfinding - cartesian positions are enough since this code will
231        // make the entity move smoothly along them
232        let corners = [
233            Vec2::new(0, 0),
234            Vec2::new(1, 0),
235            Vec2::new(1, 1),
236            Vec2::new(0, 1),
237            Vec2::new(0, 0), // Repeated start
238        ];
239
240        let vel_line = LineSegment2 {
241            start: pos.xy(),
242            end: pos.xy() + vel.xy() * 100.0,
243        };
244
245        let align = |block_pos: Vec3<i32>, precision: f32| {
246            let lerp_block =
247                |x, precision| Lerp::lerp(x, block_pos.xy().map(|e| e as f32), precision);
248
249            (0..4)
250                .filter_map(|i| {
251                    let edge_line = LineSegment2 {
252                        start: lerp_block(
253                            (block_pos.xy() + corners[i]).map(|e| e as f32),
254                            precision,
255                        ),
256                        end: lerp_block(
257                            (block_pos.xy() + corners[i + 1]).map(|e| e as f32),
258                            precision,
259                        ),
260                    };
261                    intersect(vel_line, edge_line).filter(|intersect| {
262                        intersect
263                            .clamped(
264                                block_pos.xy().map(|e| e as f32),
265                                block_pos.xy().map(|e| e as f32 + 1.0),
266                            )
267                            .distance_squared(*intersect)
268                            < 0.001
269                    })
270                })
271                .min_by_key(|intersect: &Vec2<f32>| {
272                    (intersect.distance_squared(vel_line.end) * 1000.0) as i32
273                })
274                .unwrap_or_else(|| {
275                    (0..2)
276                        .flat_map(|i| (0..2).map(move |j| Vec2::new(i, j)))
277                        .map(|rpos| block_pos + rpos)
278                        .map(|block_pos| {
279                            let block_posf = block_pos.xy().map(|e| e as f32);
280                            let proj = vel_line.projected_point(block_posf);
281                            let clamped = lerp_block(
282                                proj.clamped(
283                                    block_pos.xy().map(|e| e as f32),
284                                    block_pos.xy().map(|e| e as f32),
285                                ),
286                                precision,
287                            );
288
289                            (proj.distance_squared(clamped), clamped)
290                        })
291                        .min_by_key(|(d2, _)| (d2 * 1000.0) as i32)
292                        .unwrap()
293                        .1
294                })
295        };
296
297        let bez = CubicBezier2 {
298            start: pos.xy(),
299            ctrl0: pos.xy() + vel.xy().try_normalized().unwrap_or_default() * 1.0,
300            ctrl1: align(next0, 1.0),
301            end: align(next1, 1.0),
302        };
303
304        // Use a cubic spline of the next few targets to come up with a sensible target
305        // position. We want to use a position that gives smooth movement but is
306        // also accurate enough to avoid the agent getting stuck under ledges or
307        // falling off walls.
308        let next_dir = bez
309            .evaluate_derivative(0.85)
310            .try_normalized()
311            .unwrap_or_default();
312        let straight_factor = next_dir
313            .dot(vel.xy().try_normalized().unwrap_or(next_dir))
314            .max(0.0)
315            .powi(2);
316
317        let bez = CubicBezier2 {
318            start: pos.xy(),
319            ctrl0: pos.xy() + vel.xy().try_normalized().unwrap_or_default() * 1.0,
320            ctrl1: align(
321                next0,
322                (1.0 - if (next0.z as f32 - pos.z).abs() < 0.25 && !be_precise {
323                    straight_factor
324                } else {
325                    0.0
326                })
327                .max(0.1),
328            ),
329            end: align(next1, 1.0),
330        };
331
332        let tgt2d = bez.evaluate(if (next0.z as f32 - pos.z).abs() < 0.25 {
333            0.25
334        } else {
335            0.5
336        });
337        let tgt = if be_precise {
338            next_tgt
339        } else {
340            Vec3::from(tgt2d) + Vec3::unit_z() * next_tgt.z
341        };
342
343        Some((
344            tgt - pos,
345            // Control the entity's speed to hopefully stop us falling off walls on sharp
346            // corners. This code is very imperfect: it does its best but it
347            // can still fail for particularly fast entities.
348            straight_factor * traversal_cfg.slow_factor + (1.0 - traversal_cfg.slow_factor),
349        ))
350        .filter(|(bearing, _)| bearing.z < 2.1)
351    }
352}
353
354/// A self-contained system that attempts to chase a moving target, only
355/// performing pathfinding if necessary
356#[derive(Default, Clone, Debug)]
357pub struct Chaser {
358    last_search_tgt: Option<Vec3<f32>>,
359    /// `bool` indicates whether the Route is a complete route to the target
360    route: Option<(Route, bool)>,
361    /// We use this hasher (FxHash) because:
362    /// (1) we don't care about DDOS attacks (We can use FxHash);
363    /// (2) we want this to be constant across compiles because of hot-reloading
364    /// (Ruling out AAHash);
365    astar: Option<Astar<Vec3<i32>, FxBuildHasher>>,
366}
367
368impl Chaser {
369    /// Returns bearing and speed
370    /// Bearing is a Vec3<f32> dictating the direction of movement
371    /// Speed is an f32 between 0.0 and 1.0
372    pub fn chase<V>(
373        &mut self,
374        vol: &V,
375        pos: Vec3<f32>,
376        vel: Vec3<f32>,
377        tgt: Vec3<f32>,
378        traversal_cfg: TraversalConfig,
379    ) -> Option<(Vec3<f32>, f32)>
380    where
381        V: BaseVol<Vox = Block> + ReadVol,
382    {
383        span!(_guard, "chase", "Chaser::chase");
384        let pos_to_tgt = pos.distance(tgt);
385
386        // If we're already close to the target then there's nothing to do
387        let end = self
388            .route
389            .as_ref()
390            .and_then(|(r, _)| r.path.end().copied())
391            .map(|e| e.map(|e| e as f32 + 0.5))
392            .unwrap_or(tgt);
393        if ((pos - end) * Vec3::new(1.0, 1.0, 2.0)).magnitude_squared()
394            < traversal_cfg.min_tgt_dist.powi(2)
395        {
396            self.route = None;
397            return None;
398        }
399
400        let bearing = if let Some((end, complete)) = self
401            .route
402            .as_ref()
403            .and_then(|(r, complete)| Some((r.path().end().copied()?, *complete)))
404        {
405            let end_to_tgt = end.map(|e| e as f32).distance(tgt);
406            // If the target has moved significantly since the path was generated then it's
407            // time to search for a new path. Also, do this randomly from time
408            // to time to avoid any edge cases that cause us to get stuck. In
409            // theory this shouldn't happen, but in practice the world is full
410            // of unpredictable obstacles that are more than willing to mess up
411            // our day. TODO: Come up with a better heuristic for this
412            if end_to_tgt > pos_to_tgt * 0.3 + 5.0 && complete && traversal_cfg.is_target_loaded {
413                None
414            } else if thread_rng().gen::<f32>() < 0.01 {
415                self.route = None;
416                None
417            } else {
418                self.route
419                    .as_mut()
420                    .and_then(|(r, _)| r.traverse(vol, pos, vel, &traversal_cfg))
421            }
422        } else {
423            // There is no route found yet
424            None
425        };
426
427        // If a bearing has already been determined, use that
428        if let Some((bearing, speed)) = bearing {
429            Some((bearing, speed))
430        } else {
431            // Since no bearing has been determined yet, a new route will be
432            // calculated if the target has moved, pathfinding is not complete,
433            // or there is no route
434            let tgt_dir = (tgt - pos).xy().try_normalized().unwrap_or_default();
435
436            // Only search for a path if the target has moved from their last position. We
437            // don't want to be thrashing the pathfinding code for targets that
438            // we're unable to access!
439            if self
440                .last_search_tgt
441                .map(|last_tgt| last_tgt.distance(tgt) > pos_to_tgt * 0.15 + 5.0)
442                .unwrap_or(true)
443                || self.astar.is_some()
444                || self.route.is_none()
445                || !traversal_cfg.is_target_loaded
446            {
447                self.last_search_tgt = Some(tgt);
448
449                // NOTE: Enable air paths when air braking has been figured out
450                let (path, complete) = /*if cfg!(feature = "rrt_pathfinding") && traversal_cfg.can_fly {
451                    find_air_path(vol, pos, tgt, &traversal_cfg)
452                } else */{
453                    find_path(&mut self.astar, vol, pos, tgt, &traversal_cfg)
454                };
455
456                self.route = path.map(|path| {
457                    let start_index = path
458                        .iter()
459                        .enumerate()
460                        .min_by_key(|(_, node)| {
461                            node.map(|e| e as f32).distance_squared(pos + tgt_dir) as i32
462                        })
463                        .map(|(idx, _)| idx);
464
465                    (
466                        Route {
467                            path,
468                            next_idx: start_index.unwrap_or(0),
469                        },
470                        complete,
471                    )
472                });
473            }
474            // Start traversing the new route if it exists
475            if let Some(bearing) = self
476                .route
477                .as_mut()
478                .and_then(|(r, _)| r.traverse(vol, pos, vel, &traversal_cfg))
479            {
480                Some(bearing)
481            } else {
482                // At this point no route is available and no bearing
483                // has been determined, so we start sampling terrain.
484                // Check for falling off walls and try moving straight
485                // towards the target if falling is not a danger
486                let walking_towards_edge = (-8..2).all(|z| {
487                    vol.get(
488                        (pos + Vec3::<f32>::from(tgt_dir) * 2.5).map(|e| e as i32)
489                            + Vec3::unit_z() * z,
490                    )
491                    .map(|b| b.is_air())
492                    .unwrap_or(false)
493                });
494
495                // Enable when airbraking/flight is figured out
496                /*if traversal_cfg.can_fly {
497                    Some(((tgt - pos) , 1.0))
498                } else */
499                if traversal_cfg.can_fly {
500                    Some(((tgt - pos) * Vec3::new(1.0, 1.0, 0.5), 1.0))
501                } else if !walking_towards_edge {
502                    Some(((tgt - pos) * Vec3::new(1.0, 1.0, 0.0), 1.0))
503                } else {
504                    // This is unfortunately where an NPC will stare blankly
505                    // into space. No route has been found and no temporary
506                    // bearing would suffice. Hopefully a route will be found
507                    // in the coming ticks.
508                    None
509                }
510            }
511        }
512    }
513}
514
515fn walkable<V>(vol: &V, pos: Vec3<i32>) -> bool
516where
517    V: BaseVol<Vox = Block> + ReadVol,
518{
519    let below = vol
520        .get(pos - Vec3::unit_z())
521        .ok()
522        .copied()
523        .unwrap_or_else(Block::empty);
524    let a = vol.get(pos).ok().copied().unwrap_or_else(Block::empty);
525    let b = vol
526        .get(pos + Vec3::unit_z())
527        .ok()
528        .copied()
529        .unwrap_or_else(Block::empty);
530
531    let on_ground = below.is_filled();
532    let in_liquid = a.is_liquid();
533    (on_ground || in_liquid) && !a.is_solid() && !b.is_solid()
534}
535
536/// Attempt to search for a path to a target, returning the path (if one was
537/// found) and whether it is complete (reaches the target)
538fn find_path<V>(
539    astar: &mut Option<Astar<Vec3<i32>, FxBuildHasher>>,
540    vol: &V,
541    startf: Vec3<f32>,
542    endf: Vec3<f32>,
543    traversal_cfg: &TraversalConfig,
544) -> (Option<Path<Vec3<i32>>>, bool)
545where
546    V: BaseVol<Vox = Block> + ReadVol,
547{
548    let is_walkable = |pos: &Vec3<i32>| walkable(vol, *pos);
549    let get_walkable_z = |pos| {
550        let mut z_incr = 0;
551        for _ in 0..32 {
552            let test_pos = pos + Vec3::unit_z() * z_incr;
553            if is_walkable(&test_pos) {
554                return Some(test_pos);
555            }
556            z_incr = -z_incr + i32::from(z_incr <= 0);
557        }
558        None
559    };
560
561    let (start, end) = match (
562        get_walkable_z(startf.map(|e| e.floor() as i32)),
563        get_walkable_z(endf.map(|e| e.floor() as i32)),
564    ) {
565        (Some(start), Some(end)) => (start, end),
566
567        // Special case for partially loaded path finding
568        (Some(start), None) if !traversal_cfg.is_target_loaded => {
569            (start, endf.map(|e| e.floor() as i32))
570        },
571
572        _ => return (None, false),
573    };
574
575    let heuristic = |pos: &Vec3<i32>| (pos.distance_squared(end) as f32).sqrt();
576    let transition = |a: Vec3<i32>, b: Vec3<i32>| {
577        let crow_line = LineSegment2 {
578            start: startf.xy(),
579            end: endf.xy(),
580        };
581
582        // Modify the heuristic a little in order to prefer paths that take us on a
583        // straight line toward our target. This means we get smoother movement.
584        1.0 + crow_line.distance_to_point(b.xy().map(|e| e as f32)) * 0.025
585            + (b.z - a.z - 1).max(0) as f32 * 10.0
586    };
587    let neighbors = |pos: &Vec3<i32>| {
588        let pos = *pos;
589        const DIRS: [Vec3<i32>; 17] = [
590            Vec3::new(0, 1, 0),   // Forward
591            Vec3::new(0, 1, 1),   // Forward upward
592            Vec3::new(0, 1, -1),  // Forward downward
593            Vec3::new(0, 1, -2),  // Forward downwardx2
594            Vec3::new(1, 0, 0),   // Right
595            Vec3::new(1, 0, 1),   // Right upward
596            Vec3::new(1, 0, -1),  // Right downward
597            Vec3::new(1, 0, -2),  // Right downwardx2
598            Vec3::new(0, -1, 0),  // Backwards
599            Vec3::new(0, -1, 1),  // Backward Upward
600            Vec3::new(0, -1, -1), // Backward downward
601            Vec3::new(0, -1, -2), // Backward downwardx2
602            Vec3::new(-1, 0, 0),  // Left
603            Vec3::new(-1, 0, 1),  // Left upward
604            Vec3::new(-1, 0, -1), // Left downward
605            Vec3::new(-1, 0, -2), // Left downwardx2
606            Vec3::new(0, 0, -1),  // Downwards
607        ];
608
609        const JUMPS: [Vec3<i32>; 4] = [
610            Vec3::new(0, 1, 2),  // Forward Upwardx2
611            Vec3::new(1, 0, 2),  // Right Upwardx2
612            Vec3::new(0, -1, 2), // Backward Upwardx2
613            Vec3::new(-1, 0, 2), // Left Upwardx2
614        ];
615
616        // let walkable = [
617        //     is_walkable(&(pos + Vec3::new(1, 0, 0))),
618        //     is_walkable(&(pos + Vec3::new(-1, 0, 0))),
619        //     is_walkable(&(pos + Vec3::new(0, 1, 0))),
620        //     is_walkable(&(pos + Vec3::new(0, -1, 0))),
621        // ];
622
623        // const DIAGONALS: [(Vec3<i32>, [usize; 2]); 8] = [
624        //     (Vec3::new(1, 1, 0), [0, 2]),
625        //     (Vec3::new(-1, 1, 0), [1, 2]),
626        //     (Vec3::new(1, -1, 0), [0, 3]),
627        //     (Vec3::new(-1, -1, 0), [1, 3]),
628        //     (Vec3::new(1, 1, 1), [0, 2]),
629        //     (Vec3::new(-1, 1, 1), [1, 2]),
630        //     (Vec3::new(1, -1, 1), [0, 3]),
631        //     (Vec3::new(-1, -1, 1), [1, 3]),
632        // ];
633
634        DIRS.iter()
635            .chain(
636                Some(JUMPS.iter())
637                    .filter(|_| {
638                        vol.get(pos - Vec3::unit_z())
639                            .map(|b| !b.is_liquid())
640                            .unwrap_or(traversal_cfg.is_target_loaded)
641                            || traversal_cfg.can_climb
642                            || traversal_cfg.can_fly
643                    })
644                    .into_iter()
645                    .flatten(),
646            )
647            .map(move |dir| (pos, dir))
648            .filter(move |(pos, dir)| {
649                (traversal_cfg.can_fly || is_walkable(pos) && is_walkable(&(*pos + **dir)))
650                    && ((dir.z < 1
651                        || vol
652                            .get(pos + Vec3::unit_z() * 2)
653                            .map(|b| !b.is_solid())
654                            .unwrap_or(traversal_cfg.is_target_loaded))
655                        && (dir.z < 2
656                            || vol
657                                .get(pos + Vec3::unit_z() * 3)
658                                .map(|b| !b.is_solid())
659                                .unwrap_or(traversal_cfg.is_target_loaded))
660                        && (dir.z >= 0
661                            || vol
662                                .get(pos + *dir + Vec3::unit_z() * 2)
663                                .map(|b| !b.is_solid())
664                                .unwrap_or(traversal_cfg.is_target_loaded)))
665            })
666            .map(|(pos, dir)| {
667                let destination = pos + dir;
668                (destination, transition(pos, destination))
669            })
670        // .chain(
671        //     DIAGONALS
672        //         .iter()
673        //         .filter(move |(dir, [a, b])| {
674        //             is_walkable(&(pos + *dir)) && walkable[*a] &&
675        // walkable[*b]         })
676        //         .map(move |(dir, _)| pos + *dir),
677        // )
678    };
679
680    let satisfied = |pos: &Vec3<i32>| pos == &end;
681
682    let mut new_astar = match astar.take() {
683        None => Astar::new(
684            if traversal_cfg.is_target_loaded {
685                // Normal mode
686                25_000
687            } else {
688                // Most of the times we would need to plot within current chunk,
689                // so half of intra-site limit should be enough in most cases
690                500
691            },
692            start,
693            FxBuildHasher::default(),
694        ),
695        Some(astar) => astar,
696    };
697
698    let path_result = new_astar.poll(100, heuristic, neighbors, satisfied);
699
700    match path_result {
701        PathResult::Path(path, _cost) => (Some(path), true),
702        PathResult::None(path) => (Some(path), false),
703        PathResult::Exhausted(path) => (Some(path), false),
704
705        PathResult::Pending => {
706            // Keep astar for the next iteration
707            *astar = Some(new_astar);
708
709            (None, false)
710        },
711    }
712}
713
714// Enable when airbraking/sensible flight is a thing
715#[cfg(feature = "rrt_pathfinding")]
716fn find_air_path<V>(
717    vol: &V,
718    startf: Vec3<f32>,
719    endf: Vec3<f32>,
720    traversal_cfg: &TraversalConfig,
721) -> (Option<Path<Vec3<i32>>>, bool)
722where
723    V: BaseVol<Vox = Block> + ReadVol,
724{
725    let radius = traversal_cfg.node_tolerance;
726    let total_dist_sqrd = startf.distance_squared(endf);
727    // First check if a straight line path works
728    if vol
729        .ray(startf + Vec3::unit_z(), endf + Vec3::unit_z())
730        .until(Block::is_opaque)
731        .cast()
732        .0
733        .powi(2)
734        >= total_dist_sqrd
735    {
736        let mut path = Vec::new();
737        path.push(endf.map(|e| e.floor() as i32));
738        let connect = true;
739        (Some(path.into_iter().collect()), connect)
740    // Else use RRTs
741    } else {
742        let is_traversable = |start: &Vec3<f32>, end: &Vec3<f32>| {
743            vol.ray(*start, *end)
744                .until(Block::is_solid)
745                .cast()
746                .0
747                .powi(2)
748                > (*start).distance_squared(*end)
749            //vol.get(*pos).ok().copied().unwrap_or_else(Block::empty).
750            // is_fluid();
751        };
752        informed_rrt_connect(vol, startf, endf, is_traversable, radius)
753    }
754}
755
756/// Attempts to find a path from a start to the end using an informed
757/// RRT-Connect algorithm. A point is sampled from a bounding spheroid
758/// between the start and end. Two separate rapidly exploring random
759/// trees extend toward the sampled point. Nodes are stored in k-d trees
760/// for quicker nearest node calculations. Points are sampled until the
761/// trees connect. A final path is then reconstructed from the nodes.
762/// This pathfinding algorithm is more appropriate for 3D pathfinding
763/// with wider gaps, such as flying through a forest than for terrain
764/// with narrow gaps, such as navigating a maze.
765/// Returns a path and whether that path is complete or not.
766#[cfg(feature = "rrt_pathfinding")]
767fn informed_rrt_connect<V>(
768    vol: &V,
769    startf: Vec3<f32>,
770    endf: Vec3<f32>,
771    is_valid_edge: impl Fn(&Vec3<f32>, &Vec3<f32>) -> bool,
772    radius: f32,
773) -> (Option<Path<Vec3<i32>>>, bool)
774where
775    V: BaseVol<Vox = Block> + ReadVol,
776{
777    const MAX_POINTS: usize = 7000;
778    let mut path = Vec::new();
779
780    // Each tree has a vector of nodes
781    let mut node_index1: usize = 0;
782    let mut node_index2: usize = 0;
783    let mut nodes1 = Vec::new();
784    let mut nodes2 = Vec::new();
785
786    // The parents hashmap stores nodes and their parent nodes as pairs to
787    // retrace the complete path once the two RRTs connect
788    let mut parents1 = HashMap::new();
789    let mut parents2 = HashMap::new();
790
791    // The path vector stores the path from the appropriate terminal to the
792    // connecting node or vice versa
793    let mut path1 = Vec::new();
794    let mut path2 = Vec::new();
795
796    // K-d trees are used to find the closest nodes rapidly
797    let mut kdtree1: KdTree<f32, usize, 3, 32, u32> = KdTree::with_capacity(MAX_POINTS);
798    let mut kdtree2: KdTree<f32, usize, 3, 32, u32> = KdTree::with_capacity(MAX_POINTS);
799
800    // Add the start as the first node of the first k-d tree
801    kdtree1.add(&[startf.x, startf.y, startf.z], node_index1);
802    nodes1.push(startf);
803    node_index1 += 1;
804
805    // Add the end as the first node of the second k-d tree
806    kdtree2.add(&[endf.x, endf.y, endf.z], node_index2);
807    nodes2.push(endf);
808    node_index2 += 1;
809
810    let mut connection1_idx = 0;
811    let mut connection2_idx = 0;
812
813    let mut connect = false;
814
815    // Scalar non-dimensional value that is proportional to the size of the
816    // sample spheroid volume. This increases in value until a path is found.
817    let mut search_parameter = 0.01;
818
819    // Maximum of MAX_POINTS iterations
820    for _i in 0..MAX_POINTS {
821        if connect {
822            break;
823        }
824
825        // Sample a point on the bounding spheroid
826        let (sampled_point1, sampled_point2) = {
827            let point = point_on_prolate_spheroid(startf, endf, search_parameter);
828            (point, point)
829        };
830
831        // Find the nearest nodes to the the sampled point
832        let nearest_index1 = kdtree1
833            .nearest_one::<SquaredEuclidean>(&[
834                sampled_point1.x,
835                sampled_point1.y,
836                sampled_point1.z,
837            ])
838            .item;
839        let nearest_index2 = kdtree2
840            .nearest_one::<SquaredEuclidean>(&[
841                sampled_point2.x,
842                sampled_point2.y,
843                sampled_point2.z,
844            ])
845            .item;
846        let nearest1 = nodes1[nearest_index1];
847        let nearest2 = nodes2[nearest_index2];
848
849        // Extend toward the sampled point from the nearest node of each tree
850        let new_point1 = nearest1 + (sampled_point1 - nearest1).normalized().map(|a| a * radius);
851        let new_point2 = nearest2 + (sampled_point2 - nearest2).normalized().map(|a| a * radius);
852
853        // Ensure the new nodes are valid/traversable
854        if is_valid_edge(&nearest1, &new_point1) {
855            kdtree1.add(&[new_point1.x, new_point1.y, new_point1.z], node_index1);
856            nodes1.push(new_point1);
857            parents1.insert(node_index1, nearest_index1);
858            node_index1 += 1;
859            // Check if the trees connect
860            let NearestNeighbour {
861                distance: check,
862                item: index,
863            } = kdtree2.nearest_one::<SquaredEuclidean>(&[
864                new_point1.x,
865                new_point1.y,
866                new_point1.z,
867            ]);
868            if check < radius {
869                let connection = nodes2[index];
870                connection2_idx = index;
871                nodes1.push(connection);
872                connection1_idx = nodes1.len() - 1;
873                parents1.insert(node_index1, node_index1 - 1);
874                connect = true;
875            }
876        }
877
878        // Repeat the validity check for the second tree
879        if is_valid_edge(&nearest2, &new_point2) {
880            kdtree2.add(&[new_point2.x, new_point2.y, new_point1.z], node_index2);
881            nodes2.push(new_point2);
882            parents2.insert(node_index2, nearest_index2);
883            node_index2 += 1;
884            // Again check for a connection
885            let NearestNeighbour {
886                distance: check,
887                item: index,
888            } = kdtree1.nearest_one::<SquaredEuclidean>(&[
889                new_point2.x,
890                new_point2.y,
891                new_point1.z,
892            ]);
893            if check < radius {
894                let connection = nodes1[index];
895                connection1_idx = index;
896                nodes2.push(connection);
897                connection2_idx = nodes2.len() - 1;
898                parents2.insert(node_index2, node_index2 - 1);
899                connect = true;
900            }
901        }
902        // Increase the search parameter to widen the sample volume
903        search_parameter += 0.02;
904    }
905
906    if connect {
907        // Construct paths from the connection node to the start and end
908        let mut current_node_index1 = connection1_idx;
909        while current_node_index1 > 0 {
910            current_node_index1 = *parents1.get(&current_node_index1).unwrap_or(&0);
911            path1.push(nodes1[current_node_index1].map(|e| e.floor() as i32));
912        }
913        let mut current_node_index2 = connection2_idx;
914        while current_node_index2 > 0 {
915            current_node_index2 = *parents2.get(&current_node_index2).unwrap_or(&0);
916            path2.push(nodes2[current_node_index2].map(|e| e.floor() as i32));
917        }
918        // Join the two paths together in the proper order and remove duplicates
919        path1.pop();
920        path1.reverse();
921        path.append(&mut path1);
922        path.append(&mut path2);
923        path.dedup();
924    } else {
925        // If the trees did not connect, construct a path from the start to
926        // the closest node to the end
927        let mut current_node_index1 = kdtree1
928            .nearest_one::<SquaredEuclidean>(&[endf.x, endf.y, endf.z])
929            .item;
930        // Attempt to pick a node other than the start node
931        for _i in 0..3 {
932            if current_node_index1 == 0
933                || nodes1[current_node_index1].distance_squared(startf) < 4.0
934            {
935                if let Some(index) = parents1.values().into_iter().choose(&mut thread_rng()) {
936                    current_node_index1 = *index;
937                } else {
938                    break;
939                }
940            } else {
941                break;
942            }
943        }
944        path1.push(nodes1[current_node_index1].map(|e| e.floor() as i32));
945        // Construct the path
946        while current_node_index1 != 0 && nodes1[current_node_index1].distance_squared(startf) > 4.0
947        {
948            current_node_index1 = *parents1.get(&current_node_index1).unwrap_or(&0);
949            path1.push(nodes1[current_node_index1].map(|e| e.floor() as i32));
950        }
951
952        path1.reverse();
953        path.append(&mut path1);
954    }
955    let mut new_path = Vec::new();
956    let mut node = path[0];
957    new_path.push(node);
958    let mut node_idx = 0;
959    let num_nodes = path.len();
960    let end = path[num_nodes - 1];
961    while node != end {
962        let next_idx = if node_idx + 4 > num_nodes - 1 {
963            num_nodes - 1
964        } else {
965            node_idx + 4
966        };
967        let next_node = path[next_idx];
968        let start_pos = node.map(|e| e as f32 + 0.5);
969        let end_pos = next_node.map(|e| e as f32 + 0.5);
970        if vol
971            .ray(start_pos, end_pos)
972            .until(Block::is_solid)
973            .cast()
974            .0
975            .powi(2)
976            > (start_pos).distance_squared(end_pos)
977        {
978            node_idx = next_idx;
979            new_path.push(next_node);
980        } else {
981            node_idx += 1;
982        }
983        node = path[node_idx];
984    }
985    path = new_path;
986    (Some(path.into_iter().collect()), connect)
987}
988
989/// Returns a random point within a radially symmetrical ellipsoid with given
990/// foci and a `search parameter` to determine the size of the ellipse beyond
991/// the foci. Technically the point is within a prolate spheroid translated and
992/// rotated to the proper place in cartesian space.
993/// The search_parameter is a float that relates to the length of the string for
994/// a two dimensional ellipse or the size of the ellipse beyond the foci. In
995/// this case that analogy still holds as the ellipse is radially symmetrical
996/// along the axis between the foci. The value of the search parameter must be
997/// greater than zero. In order to increase the sample area, the
998/// search_parameter should be increased linearly as the search continues.
999#[cfg(feature = "rrt_pathfinding")]
1000pub fn point_on_prolate_spheroid(
1001    focus1: Vec3<f32>,
1002    focus2: Vec3<f32>,
1003    search_parameter: f32,
1004) -> Vec3<f32> {
1005    let mut rng = thread_rng();
1006    // Uniform distribution
1007    let range = Uniform::from(0.0..1.0);
1008
1009    // Midpoint is used as the local origin
1010    let midpoint = 0.5 * (focus1 + focus2);
1011    // Radius between the start and end of the path
1012    let radius: f32 = focus1.distance(focus2);
1013    // The linear eccentricity of an ellipse is the distance from the origin to a
1014    // focus A prolate spheroid is a half-ellipse rotated for a full revolution
1015    // which is why ellipse variables are used frequently in this function
1016    let linear_eccentricity: f32 = 0.5 * radius;
1017
1018    // For an ellipsoid, three variables determine the shape: a, b, and c.
1019    // These are the distance from the center/origin to the surface on the
1020    // x, y, and z axes, respectively.
1021    // For a prolate spheroid a and b are equal.
1022    // c is determined by adding the search parameter to the linear eccentricity.
1023    // As the search parameter increases the size of the spheroid increases
1024    let c: f32 = linear_eccentricity + search_parameter;
1025    // The width is calculated to prioritize increasing width over length of
1026    // the ellipsoid
1027    let a: f32 = (c.powi(2) - linear_eccentricity.powi(2)).powf(0.5);
1028    // The width should be the same in both the x and y directions
1029    let b: f32 = a;
1030
1031    // The parametric spherical equation for an ellipsoid measuring from the
1032    // center point is as follows:
1033    // x = a * cos(theta) * cos(lambda)
1034    // y = b * cos(theta) * sin(lambda)
1035    // z = c * sin(theta)
1036    //
1037    // where     -0.5 * PI <= theta <= 0.5 * PI
1038    // and       0.0 <= lambda < 2.0 * PI
1039    //
1040    // Select these two angles using the uniform distribution defined at the
1041    // beginning of the function from 0.0 to 1.0
1042    let rtheta: f32 = PI * range.sample(&mut rng) - 0.5 * PI;
1043    let lambda: f32 = 2.0 * PI * range.sample(&mut rng);
1044    // Select a point on the surface of the ellipsoid
1045    let point = Vec3::new(
1046        a * rtheta.cos() * lambda.cos(),
1047        b * rtheta.cos() * lambda.sin(),
1048        c * rtheta.sin(),
1049    );
1050    // NOTE: Theoretically we should sample a point within the spheroid
1051    // requiring selecting a point along the radius. In my tests selecting
1052    // a point *on the surface* of the spheroid results in sampling that is
1053    // "good enough". The following code is commented out to reduce expense.
1054    //let surface_point = Vec3::new(a * rtheta.cos() * lambda.cos(), b *
1055    // rtheta.cos() * lambda.sin(), c * rtheta.sin()); let magnitude =
1056    // surface_point.magnitude(); let direction = surface_point.normalized();
1057    //// Randomly select a point along the vector to the previously selected surface
1058    //// point using the uniform distribution
1059    //let point = magnitude * range.sample(&mut rng) * direction;
1060
1061    // Now that a point has been selected in local space, it must be rotated and
1062    // translated into global coordinates
1063    // NOTE: Don't rotate about the z axis as the point is already randomly
1064    // selected about the z axis
1065    //let dx = focus2.x - focus1.x;
1066    //let dy = focus2.y - focus1.y;
1067    let dz = focus2.z - focus1.z;
1068    // Phi and theta are the angles from the x axis in the x-y plane and from
1069    // the z axis, respectively. (As found in spherical coordinates)
1070    // These angles are used to rotate the random point in the spheroid about
1071    // the local origin
1072    //
1073    // Rotate about z axis by phi
1074    //let phi: f32 = if dx.abs() > 0.0 {
1075    //    (dy / dx).atan()
1076    //} else {
1077    //    0.5 * PI
1078    //};
1079    // This is unnecessary as rtheta is randomly selected between 0.0 and 2.0 * PI
1080    // let rot_z_mat = Mat3::new(phi.cos(), -1.0 * phi.sin(), 0.0, phi.sin(),
1081    // phi.cos(), 0.0, 0.0, 0.0, 1.0);
1082
1083    // Rotate about perpendicular vector in the xy plane by theta
1084    let theta: f32 = if radius > 0.0 {
1085        (dz / radius).acos()
1086    } else {
1087        0.0
1088    };
1089    // Vector from focus1 to focus2
1090    let r_vec = focus2 - focus1;
1091    // Perpendicular vector in xy plane
1092    let perp_vec = Vec3::new(-1.0 * r_vec.y, r_vec.x, 0.0).normalized();
1093    let l = perp_vec.x;
1094    let m = perp_vec.y;
1095    let n = perp_vec.z;
1096    // Rotation matrix for rotation about a vector
1097    let rot_2_mat = Mat3::new(
1098        l * l * (1.0 - theta.cos()),
1099        m * l * (1.0 - theta.cos()) - n * theta.sin(),
1100        n * l * (1.0 - theta.cos()) + m * theta.sin(),
1101        l * m * (1.0 - theta.cos()) + n * theta.sin(),
1102        m * m * (1.0 - theta.cos()) + theta.cos(),
1103        n * m * (1.0 - theta.cos()) - l * theta.sin(),
1104        l * n * (1.0 - theta.cos()) - m * theta.sin(),
1105        m * n * (1.0 - theta.cos()) + l * theta.sin(),
1106        n * n * (1.0 - theta.cos()) + theta.cos(),
1107    );
1108
1109    // Get the global coordinates of the point by rotating and adding the origin
1110    // rot_z_mat is unneeded due to the random rotation defined by lambda
1111    // let global_coords = midpoint + rot_2_mat * (rot_z_mat * point);
1112    midpoint + rot_2_mat * point
1113}