Number of cells the last compute reached, goals included.
The maxCost the last compute ran with.
Rebuilds the field for a new goal set, reusing the existing buffers.
Cost is one Dijkstra sweep over the reachable area — the same work getMovementRange does for one unit, shared by all of them.
One HexCoord or several. Out-of-bounds goals are ignored;
with none left in bounds every cell reports unreachable.
Movement cost, called as costFn(from, to) in the
direction of travel. Return Infinity for impassable.
Cost from (col, row) to the nearest goal, or Infinity if unreachable.
Cost from hex to the nearest goal, or Infinity if unreachable.
true if a path from (col, row) to some goal exists within maxCost.
The direction index (0–5, indexing HEX_DIRECTIONS) of the next step
from (col, row). Returns -1 at a goal and for unreachable cells — call
costAt to tell those two apart.
Walks the field from from to the goal it flows to, returning the same
shape findPath does — from first, goal last, both inclusive — so
it can be handed straight to HexUnit.travel().
Returns null if from is unreachable, and [from] if it is already a
goal. Following the field is O(path length): no search runs here.
A normalised world-space (x, z) direction to steer toward, or null at a
goal or on an unreachable cell.
This is not just next converted to world space. It blends the directions of every neighbour the field descends into, weighted by how much cost each one saves, which is what makes a crowd read as a crowd: units crossing open ground aim at the true bearing rather than snapping to one of six axes, and a column meeting an obstacle splits around both sides instead of filing through a single hex. Edges the cost function rejects are excluded, so a cell whose cheap-looking neighbour sits across an impassable cliff edge does not steer into it.
Costs ≤6 costFn calls — per unit per frame, not per cell.
Visits every cell the field reached, in row-major order. Scans the whole grid — O(width × height) — so it belongs in debug overlays and field visualisations, not in a per-unit hot path.
A single-source-set Dijkstra field: the cheapest cost from every cell to the nearest goal, plus the neighbour to step to next.
This is the many-units-one-destination counterpart to findPath. A* costs one search per unit; a flow field costs one search per destination, after which every unit reads its next step in O(1) no matter how many of them there are or how far away they stand. For an army converging on a rally point, or a horde chasing one target, that is the difference between hundreds of searches a turn and one.
The cost function runs backwards
The field is built by expanding outward from the goals, so when the search reaches cell
Xfrom an already-settled cellY, the move a unit will actually make isX → Y.costFnis therefore called ascostFn(X, Y)— the direction of travel, not the direction of expansion. Symmetric cost functions (the common case) can ignore this entirely; asymmetric ones (uphill costs more than downhill, one-way fords) get the right answer without doing anything special.Several goals
Pass an array and every cell flows to whichever goal is cheapest from it, with the watershed between them falling out of the search. One field can serve "retreat to the nearest fort".
Cells you can leave but not enter
Because the cost asked about is always the move out of a cell, a cell your cost function refuses to admit anyone into still gets a direction if it has a passable way out — a unit spawned or shoved onto a wall is given a route off it rather than being told it is stranded. Nothing routes through such a cell, since the step into it is still rejected.
Memory
Dense typed arrays — 13 bytes per map cell, allocated once. compute reuses them, so re-targeting the field every frame allocates nothing.
Example
Example