shortestpath 0.10.0

Shortest Path is an experimental library finding the shortest path from A to B.
Documentation
// Copyright (C) 2025 Christian Mauduit <ufoot@ufoot.org>

use crate::distance::*;

/// Represents a connection to a neighboring node in the pathfinding graph.
///
/// A `Gate` is the fundamental unit that connects nodes in the pathfinding graph,
/// similar to an edge in graph theory. It stores both the destination ("where you
/// can go") and the cost to reach it ("how far it is").
///
/// # Fields
///
/// * `target` - The index of the neighboring node
/// * `distance` - The cost/distance to reach that neighbor
///
/// # Usage
///
/// Gates are primarily used in:
/// - The `Mesh` trait's `successors()` method to enumerate possible moves
/// - The `Gradient` struct to store pathfinding results
/// - 2D/3D mesh navigation to represent connections between cells
///
/// # Example
///
/// ```
/// use shortestpath::{Gate, DISTANCE_STRAIGHT};
///
/// // Create a gate to node 5 with a straight-line distance
/// let gate = Gate::new(5, DISTANCE_STRAIGHT);
/// assert_eq!(gate.target, 5);
/// ```
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Gate {
    /// The index of the neighboring node this gate connects to
    pub target: u32,
    /// The cost/distance to reach the target node
    pub distance: f32,
}

impl Gate {
    /// Returns the target index as usize for array indexing.
    #[inline]
    pub fn target(&self) -> usize {
        self.target as usize
    }
}

impl Gate {
    /// Creates a new `Gate` to the specified target with the given distance.
    ///
    /// # Arguments
    ///
    /// * `target` - The index of the neighboring node (as usize, converted to u32)
    /// * `distance` - The cost/distance to reach that neighbor
    ///
    /// # Example
    ///
    /// ```
    /// use shortestpath::Gate;
    ///
    /// let gate = Gate::new(10, 1.5);
    /// assert_eq!(gate.target, 10);
    /// assert_eq!(gate.distance, 1.5);
    /// ```
    pub fn new(target: usize, distance: f32) -> Self {
        Self {
            target: target as u32,
            distance,
        }
    }
}

impl PartialEq for Gate {
    /// Compares two gates for equality.
    ///
    /// Two gates are considered equal if:
    /// - Their target indices are identical
    /// - Their distances are within `DISTANCE_PRECISION` tolerance
    ///
    /// This custom equality implementation accounts for floating-point
    /// precision issues when comparing distances.
    fn eq(&self, other: &Gate) -> bool {
        if self.target != other.target {
            return false;
        }
        let diff = f32::abs(self.distance - other.distance);
        diff < DISTANCE_PRECISION
    }
}

/// Gates can be used in hash-based collections after implementing `Eq`.
///
/// Note: The equality comparison uses floating-point tolerance via `DISTANCE_PRECISION`.
impl Eq for Gate {}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_eq() {
        let a = Gate::new(1, 1.0);
        let b = Gate::new(1, 1.001); // Difference of 1e-3, greater than DISTANCE_PRECISION (1e-6)
        let c = Gate::new(1, 1.000_000_1); // Difference of 1e-7, less than DISTANCE_PRECISION (1e-6)
        let d = Gate::new(2, 1.0);

        assert_eq!(a, a);
        assert_eq!(b, b);
        assert_eq!(c, c);
        assert_eq!(c, c);
        assert_ne!(a, b); // Different because 1e-3 > 1e-6
        assert_eq!(a, c); // Same because 1e-7 < 1e-6
        assert_ne!(a, d); // Different target
    }

    #[test]
    #[cfg(feature = "serde")]
    fn test_serde() {
        let gate = Gate::new(42, 1.5);
        let json = serde_json::to_string(&gate).unwrap();
        let deserialized: Gate = serde_json::from_str(&json).unwrap();
        assert_eq!(gate, deserialized);
    }
}