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