Shortest Path
Shortest Path is an experimental library finding the shortest path from A to B, implemented in Rust.
It aims primarily at powering Liquid War 7 but could be of wider use. Who knows.

Status
For now this is a toy project, clearly NOT suitable for production use.
Usage
Add this to your Cargo.toml:
[]
= "0.10"
Optional Features
Enable optional features for additional input sources:
[]
= { = "0.10", = ["image"] }
Available features:
image: Load 2D/3D volumes from images (PNG, JPEG)serde: Enable serialization/deserialization support for all types
Quick Example
Here's a simple 2D pathfinding example with walls:
use ;
// Create a 5x5 grid with walls (#) and walkable cells (.)
let map = "\
.....
.###.
.#...
...#.
.....";
let mesh = from_text?;
// Create a gradient and set the target (bottom-right corner)
let mut gradient = from_mesh;
let target = mesh.xy_to_index?;
gradient.set_distance;
gradient.spread;
// Query distance from any position
let start = mesh.xy_to_index?;
let distance = gradient.get_distance;
println!;
// Follow the gradient from start
let path = gradient.follow;
if !path.is_empty
// Or use find() to do everything in one call (resets and computes gradient)
let mut gradient2 = from_mesh;
if let Some = gradient2.find
// Or get just the best next move from any position
if let Some = gradient.best
# Ok::
Pathfinding Methods
The library provides two main approaches for pathfinding:
Complete Pathfinding (find)
find(mesh, start, target, backward): All-in-one method that resets the gradient, sets the target, spreads the distances, and returns the complete path. This modifies the gradient but is the simplest way to find a path. ReturnsOption<Vec<Gate>>.
Manual Gradient Control (follow)
For more control or when reusing gradients:
reset(): Resets all distances to DISTANCE_MAXset_distance(target, 0.0): Sets the target distance to 0spread(mesh): Computes the gradient across the meshfollow(mesh, start, backward): Follows the pre-computed gradient from start, moving to better positions until no improvement is available. Does not check if a target was reached. ReturnsVec<Gate>(may be empty if no moves improve the position).
Single-Step Methods
After spreading the gradient, you can also query individual moves:
-
best(mesh, node, backward): ReturnsOption<Gate>with the best next move from a given node. ReturnsNoneif all neighbors are farther from the target. -
worst(mesh, node, backward): ReturnsOption<Gate>with the worst next move (highest distance to target). Useful for debugging or exploring suboptimal paths. -
moves(mesh, node, backward): Returns a sortedVec<Gate>of all possible moves from a node, ordered by distance to target.
All methods support the backward parameter for deterministic tie-breaking. In follow() and find(), this parameter alternates automatically at each step for path stability.
Features
The library supports:
- 2D grids (
mesh_2d): Classic grid-based pathfinding with 8-way movement - 2.5D layers (
mesh_25d): Stacked 2D grids with vertical movement between layers (10 neighbors: 8 in-layer + 2 vertical) - True 3D volumes (
mesh_3d): Full 26-neighbor volumetric pathfinding with diagonal transitions - Dynamic topology (
mesh_topo): Conditional connectivity (doors, one-way passages, etc.) - Zone filtering:
unique()method to keep only the connected zone reachable from the origin - Flexible input sources (
mesh_source):- Parse maps from ASCII text (
.= walkable,#= wall) - Load 2D/3D volumes from images (optional
imagefeature) - Extensible via
Source2DandSource3Dtraits for custom sources
- Parse maps from ASCII text (
Creating Meshes from Text
You can create meshes directly from ASCII art maps:
use Compact2D;
use Compact3D;
// 2D from text (. = walkable, # = wall)
let mesh_2d = from_text?;
// 3D from text (layers separated by ---)
let map_3d = "\
...
.#.
...
---
...
...
...";
let mesh_3d = from_text?;
# Ok::
Creating Meshes from Images
With the optional image feature, you can load meshes directly from images (dark pixels become walls, light pixels become walkable):
use Compact2D;
use Compact3D;
// 2D from image file (uses default 0.5 brightness threshold)
let mesh_2d = from_path?;
// 2D with custom brightness threshold (0.0-1.0)
let mesh_2d = from_path_with_threshold?;
// 3D from stack of images (each image = one z-layer)
let paths = vec!;
let mesh_3d = from_paths?; // Uses default 0.5 threshold
// 3D with custom threshold
let mesh_3d = from_paths_with_threshold?;
// You can also use pre-loaded images
use DynamicImage;
let img = open?;
let mesh_2d = from_image?;
# Ok::
Extensibility
For custom input sources, you can implement the Source2D or Source3D traits from the mesh_source module and use them with from_source():
use ;
use Compact2D;
// The mesh_source module provides low-level sources
let source = from_text;
let mesh = from_source?;
// Or use the FromStr trait for parsing
let mesh: Compact2D = "...\n.#.\n...".parse?;
# Ok::
Performance
The library is optimized for many-to-one pathfinding scenarios where multiple agents need to find paths to a common target. Performance scales based on mesh dimensionality and neighbor count:
- 2D meshes (8 neighbors) provide the fastest pathfinding
- 2.5D meshes (10 neighbors: 8 in-layer + 2 vertical) offer a middle ground for layered environments
- 3D meshes (26 neighbors with full diagonal connectivity) handle true volumetric pathfinding
The Compact mesh variants pre-compute neighbor relationships and support walls, making them more memory-efficient for sparse environments. The Full variants compute successors on-the-fly and work best for fully-connected grids.
Below is the result of a bench executed on a standard CI runner:
running 6 tests
test tests::bench_compact_25d_160x90x3 ... bench: 2,680,485.75 ns/iter (+/- 901,204.19)
test tests::bench_compact_2d_160x90 ... bench: 676,991.05 ns/iter (+/- 22,940.60)
test tests::bench_compact_3d_160x90x3 ... bench: 4,960,161.85 ns/iter (+/- 1,297,727.76)
test tests::bench_full_25d_160x90x3 ... bench: 4,823,626.70 ns/iter (+/- 151,326.60)
test tests::bench_full_2d_160x90 ... bench: 750,262.20 ns/iter (+/- 9,177.50)
test tests::bench_full_3d_160x90x3 ... bench: 5,390,642.50 ns/iter (+/- 145,018.10)
test result: ok. 0 passed; 0 failed; 0 ignored; 6 measured; 0 filtered out; finished in 14.22s
History
Technically, this is a variant of Dijkstra's Algorithm.
A famous, classical algorithm which we re-discovered (as in, re-invent the wheel) with my friend Thomas Colcombet back in 1995. Back in those days we did not have access to Internet and never stumbled on that great work by Dijkstra but somehow managed to use its main idea. First code snippets in Liquid War 3.
This implementation tries to make it independant from the Liquid War game and offer a multi-purpose version. It still aims at speed execution rather than exactness, in the context of many agents trying to find the shortest point to a single target.
Similar packages
The pathfinding crate has a multi-purpose, very likely stricter version of this, along with many other path finding algorithms.
It has been a great source of inspiration.
License
Shortest Path is licensed under the MIT license.