| Crates.io | shortestpath |
| lib.rs | shortestpath |
| version | 0.10.0 |
| created_at | 2022-12-24 17:21:19.286843+00 |
| updated_at | 2025-12-11 18:34:18.225164+00 |
| description | Shortest Path is an experimental library finding the shortest path from A to B. |
| homepage | https://gitlab.com/liberecofr/shortestpath |
| repository | https://gitlab.com/liberecofr/shortestpath/tree/main |
| max_upload_size | |
| id | 745006 |
| size | 342,591 |
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.

For now this is a toy project, clearly NOT suitable for production use.
Add this to your Cargo.toml:
[dependencies]
shortestpath = "0.10"
Enable optional features for additional input sources:
[dependencies]
shortestpath = { version = "0.10", features = ["image"] }
Available features:
image: Load 2D/3D volumes from images (PNG, JPEG)serde: Enable serialization/deserialization support for all typesHere's a simple 2D pathfinding example with walls:
use shortestpath::{Mesh, Gradient, mesh_2d::{Compact2D, Index2D}};
// Create a 5x5 grid with walls (#) and walkable cells (.)
let map = "\
.....
.###.
.#...
...#.
.....";
let mesh = Compact2D::from_text(map)?;
// Create a gradient and set the target (bottom-right corner)
let mut gradient = Gradient::from_mesh(&mesh);
let target = mesh.xy_to_index(4, 4)?;
gradient.set_distance(target, 0.0);
gradient.spread(&mesh);
// Query distance from any position
let start = mesh.xy_to_index(0, 0)?;
let distance = gradient.get_distance(start);
println!("Distance from start to goal: {:.2}", distance);
// Follow the gradient from start
let path = gradient.follow(&mesh, start, false);
if !path.is_empty() {
println!("Path found with {} steps", path.len());
for (i, gate) in path.iter().enumerate() {
println!(" Step {}: move to node {}", i + 1, gate.target);
}
}
// Or use find() to do everything in one call (resets and computes gradient)
let mut gradient2 = Gradient::from_mesh(&mesh);
if let Some(path) = gradient2.find(&mesh, start, target, false) {
println!("Path found with {} steps", path.len());
}
// Or get just the best next move from any position
if let Some(best_move) = gradient.best(&mesh, start, false) {
println!("Best move from start: to node {}", best_move.target);
}
# Ok::<(), shortestpath::Error>(())
The library provides two main approaches for pathfinding:
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. Returns Option<Vec<Gate>>.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. Returns Vec<Gate> (may be empty if no moves improve the position).After spreading the gradient, you can also query individual moves:
best(mesh, node, backward): Returns Option<Gate> with the best next move from a given node. Returns None if all neighbors are farther from the target.
worst(mesh, node, backward): Returns Option<Gate> with the worst next move (highest distance to target). Useful for debugging or exploring suboptimal paths.
moves(mesh, node, backward): Returns a sorted Vec<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.
The library supports:
mesh_2d): Classic grid-based pathfinding with 8-way movementmesh_25d): Stacked 2D grids with vertical movement between layers (10 neighbors: 8 in-layer + 2 vertical)mesh_3d): Full 26-neighbor volumetric pathfinding with diagonal transitionsmesh_topo): Conditional connectivity (doors, one-way passages, etc.)unique() method to keep only the connected zone reachable from the originmesh_source):
. = walkable, # = wall)image feature)Source2D and Source3D traits for custom sourcesYou can create meshes directly from ASCII art maps:
use shortestpath::mesh_2d::Compact2D;
use shortestpath::mesh_3d::Compact3D;
// 2D from text (. = walkable, # = wall)
let mesh_2d = Compact2D::from_text("...\n.#.\n...")?;
// 3D from text (layers separated by ---)
let map_3d = "\
...
.#.
...
---
...
...
...";
let mesh_3d = Compact3D::from_text(map_3d)?;
# Ok::<(), shortestpath::Error>(())
With the optional image feature, you can load meshes directly from images (dark pixels become walls, light pixels become walkable):
use shortestpath::mesh_2d::Compact2D;
use shortestpath::mesh_3d::Compact3D;
// 2D from image file (uses default 0.5 brightness threshold)
let mesh_2d = Compact2D::from_path("map.png")?;
// 2D with custom brightness threshold (0.0-1.0)
let mesh_2d = Compact2D::from_path_with_threshold("map.png", 0.3)?;
// 3D from stack of images (each image = one z-layer)
let paths = vec!["layer0.png", "layer1.png", "layer2.png"];
let mesh_3d = Compact3D::from_paths(&paths)?; // Uses default 0.5 threshold
// 3D with custom threshold
let mesh_3d = Compact3D::from_paths_with_threshold(&paths, 0.7)?;
// You can also use pre-loaded images
use image::DynamicImage;
let img = image::open("map.png")?;
let mesh_2d = Compact2D::from_image(&img)?;
# Ok::<(), Box<dyn std::error::Error>>(())
For custom input sources, you can implement the Source2D or Source3D traits from the mesh_source module and use them with from_source():
use shortestpath::mesh_source::{Source2DFromText, Source3DFromText};
use shortestpath::mesh_2d::Compact2D;
// The mesh_source module provides low-level sources
let source = Source2DFromText::from_text("...\n.#.\n...");
let mesh = Compact2D::from_source(&source)?;
// Or use the FromStr trait for parsing
let mesh: Compact2D = "...\n.#.\n...".parse()?;
# Ok::<(), shortestpath::Error>(())
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:
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
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.
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.
Shortest Path is licensed under the MIT license.