Pathfinding
Pathfinding on 2D weighted grids
Quick Start
Loading...
Source
Loading...
Documentation
Pathfinding
GPU-accelerated shortest path finding on 2D weighted grids. Uses wavefront expansion (parallel Dijkstra) — each iteration expands all frontier cells simultaneously, leveraging massive GPU parallelism.
Usage
import { createPathfinder } from './pathfinding';
const pathfinder = createPathfinder(device, { width: 256, height: 256 });
// Set terrain costs (1.0 = normal, higher = slower, negative = impassable)
pathfinder.setCostGrid(costs);
// Find shortest path
const result = await pathfinder.findPath({
startX: 10,
startY: 10,
endX: 240,
endY: 240
});
if (result.found) {
console.log(`Path length: ${result.path.length}, distance: ${result.distance}`);
// result.path is an array of cell indices (start → end)
}
// Read the full distance field (useful for visualization or multi-target queries)
const distField = await pathfinder.getDistanceField();
pathfinder.destroy();
API
createPathfinder(device, options)
Returns a Pathfinder instance.
| Option | Type | Default | Description |
|---|---|---|---|
width |
number |
(required) | Grid width in cells |
height |
number |
(required) | Grid height in cells |
pathfinder.setCostGrid(costs)
Upload terrain costs. costs is a Float32Array of width * height values.
1.0= normal traversal cost> 1.0= slower terrain (e.g., 3.0 for swamp)< 0= impassable (wall)
The cost grid persists between findPath calls until you call setCostGrid again.
pathfinder.findPath(options)
Returns a Promise<PathResult>.
| Option | Type | Default | Description |
|---|---|---|---|
startX |
number |
(required) | Start cell X coordinate |
startY |
number |
(required) | Start cell Y coordinate |
endX |
number |
(required) | End cell X coordinate |
endY |
number |
(required) | End cell Y coordinate |
maxIterations |
number |
width + height |
Max wavefront iterations |
PathResult:
| Field | Type | Description |
|---|---|---|
path |
number[] |
Cell indices from start to end |
distance |
number |
Total path cost |
found |
boolean |
Whether a path exists |
Cell indices can be converted to coordinates: x = index % width, y = Math.floor(index / width).
pathfinder.getDistanceField()
Returns the full distance field as a Float32Array. Each cell contains the shortest distance from the start position. Unreachable cells have Infinity. Useful for visualization, heatmaps, or multi-target queries.
pathfinder.destroy()
Releases all GPU buffers.
Algorithm
Wavefront expansion is a parallel variant of Dijkstra's algorithm optimized for the GPU:
- Initialize — Set start cell distance to 0, all others to infinity.
- Expand — Each cell checks if it can offer a shorter path to its 4-connected neighbours. Uses
atomicMinfor conflict-free distance updates — multiple threads can relax the same cell safely. - Converge — Repeat until no cell is updated (frontier flag stays 0).
- Trace — Backtrack from end to start using parent pointers.
Unlike CPU A* (which uses a priority queue), this approach processes all frontier cells in parallel each iteration. On a GPU with thousands of cores, this is dramatically faster for large grids.
The distance field is stored as fixed-point u32 (float × 1000) to enable atomicMin operations, since WebGPU doesn't support atomic float operations.
Overflow constraint: The maximum representable distance is ~4,000,000 cost units. For a grid of size W × H with max terrain cost C, the worst-case path cost is W × H × C. Keep this product below 4,000,000 — for example, a 1024×1024 grid with max cost 3.0 works fine (~3.1M), but max cost 5.0 would overflow.
WGSL loading
The default import uses Vite's ?raw suffix:
import shaderSource from './pathfinding.wgsl?raw';
If you're not using a bundler, load via fetch:
const shaderSource = await fetch(new URL('./pathfinding.wgsl', import.meta.url)).then((r) =>
r.text()
);
Modifying
Add diagonal movement (8-connected)
Extend the DX/DY arrays to 8 directions and add diagonal cost (typically sqrt(2) ≈ 1.414):
const DX: array<i32, 8> = array<i32, 8>(0, 0, -1, 1, -1, 1, -1, 1);
const DY: array<i32, 8> = array<i32, 8>(-1, 1, 0, 0, -1, -1, 1, 1);
const COST_MUL: array<f32, 8> = array<f32, 8>(1.0, 1.0, 1.0, 1.0, 1.414, 1.414, 1.414, 1.414);
Add A* heuristic
To prioritize cells closer to the target, add a heuristic term to the distance comparison in the expand kernel. Store g + h for ordering but only g for the actual distance. This requires an additional buffer for the heuristic-adjusted costs.
Multiple pathfinding queries
The distance field from a single findPath call gives shortest distances to all cells from the start. For multiple targets from the same start, call findPath once and read individual distances from getDistanceField().
3D grids
Extend to 3D by adding a Z dimension, 6-connected neighbours, and a 3D grid index. The wavefront approach scales well to 3D since each cell is independent.
Flow fields
Instead of tracing a single path, use the distance field as a flow field: each cell's gradient points toward the shortest path to the target. This is commonly used for crowd simulation where many agents need to navigate to the same destination.
Further Reading
Resources on pathfinding algorithms, GPU parallelization, and game AI navigation.
Core Algorithms
Dijkstra, "A Note on Two Problems in Connexion with Graphs" (1959) The original shortest path algorithm. The wavefront expansion in this module is a parallel variant of Dijkstra's algorithm, using the same greedy relaxation principle. https://dl.acm.org/doi/10.1007/BF01386390
Hart, Nilsson, Raphael, "A Formal Basis for the Heuristic Determination of Minimum Cost Paths" (1968) The A* algorithm paper. Introduces the admissible heuristic concept (
f = g + h) that guides search toward the goal. The README describes how to extend this module with an A* heuristic. https://ieeexplore.ieee.org/document/4082128Harabor, Grastien, "Online Graph Pruning for Pathfinding on Grid Maps" (2011) Jump Point Search (JPS) — a faster variant of A* on uniform-cost grids that skips intermediate nodes. Can be adapted for GPU execution. https://ojs.aaai.org/index.php/AAAI/article/view/7994
GPU Pathfinding
Bleiweiss, "GPU Accelerated Pathfinding" (GDC 2008) One of the earliest practical presentations on GPU pathfinding for games. Covers wavefront expansion, parallel BFS, and integration with game engines. https://developer.nvidia.com/gpugems/gpugems3/part-vi-gpu-computing
Fischer, Dachsbacher, "GPU-Accelerated A* Pathfinding" (2009) Proposes a parallel A* implementation using work queues on the GPU. Demonstrates speedups over CPU A* on large grids.
Delaunay, Meneveaux, "Parallel Dijkstra's Algorithm on GPU" (2019) Analyses wavefront-based parallel Dijkstra on modern GPU architectures with atomic operations, similar to the approach used in this module.
Flow Fields and Navigation
Emerson, "Crowd Pathfinding and Steering Using Flow Field Tiles" (GDC 2015) How Planetary Annihilation uses flow fields (derived from distance fields like ours) for thousands of units navigating simultaneously.
Sturtevant, "Benchmarks for Grid-Based Pathfinding" (2012) Standard benchmark maps and methodology for evaluating pathfinding algorithms. Useful for testing and comparing performance. https://movingai.com/benchmarks/
Game Development Context
Amit Patel, "Introduction to A*" (Red Blob Games) The best interactive visual introduction to A*, Dijkstra, BFS, and graph search in general. Essential reading for understanding pathfinding intuitively. https://www.redblobgames.com/pathfinding/a-star/introduction.html
Amit Patel, "Implementation of A*" (Red Blob Games) Practical implementation guide covering priority queues, heuristics, grid representations, and common pitfalls. Companion to the theory article above. https://www.redblobgames.com/pathfinding/a-star/implementation.html
Millington, Funge, "Artificial Intelligence for Games" (2009) Comprehensive textbook covering pathfinding, steering, decision making, and other game AI topics. Chapters 4-5 cover pathfinding in depth.
WebGPU Atomics
- WebGPU Specification — Atomic Operations
The
atomicMinoperation used in this module's wavefront expansion. Understanding atomic semantics is key to reasoning about the algorithm's correctness. https://www.w3.org/TR/WGSL/#atomic-builtin-functions