diff --git a/src/common/navigation/bresenham.ts b/src/common/navigation/bresenham.ts index bf0c999..402d855 100644 --- a/src/common/navigation/bresenham.ts +++ b/src/common/navigation/bresenham.ts @@ -1,6 +1,6 @@ import { clamp } from "@common/utils"; -export interface Point { +interface Point { x: number; y: number; } @@ -21,16 +21,27 @@ interface BresenhamOptions { export interface BresenhamLineOptions extends BresenhamOptions {} -export interface BresenhamCircleOptions extends BresenhamOptions { +interface BresenhamCircleBaseOptions extends BresenhamOptions { /** * - `false` (default) — outline only. * - `true` — filled disc (span-from-outline scanline). - * - `'fov'` — ray-casting field of view; the generator accepts `true` via - * `.next(true)` to signal an obstacle and skip the rest of that ray. + * - `'fov'` — ray-casting field of view. */ fill?: T; } +type BresenhamCircleFillOptions = BresenhamCircleBaseOptions; +interface BresenhamCircleFovOptions extends BresenhamCircleBaseOptions<'fov'> { + /** + * A callback that is called for each point on the ray casted from the start + * point to the current point. If the callback returns `true`, the ray is + * terminated and the generator skips to the next ray. + */ + breaker?: (x: number, y: number) => boolean; +} + +type BresenhamCircleOptions = BresenhamCircleFillOptions | BresenhamCircleFovOptions; + // --------------------------------------------------------------------------- // Cohen-Sutherland outcodes // --------------------------------------------------------------------------- @@ -247,30 +258,16 @@ export const bresenhamLine = ( * * ## FOV mode (`fill: 'fov'`) — ray-casting field of view * - * For each outline point, casts an 8-connected Bresenham ray from the centre - * to that point and yields every cell along the ray. The generator uses the - * **send protocol**: the caller can pass `true` to `.next(true)` after - * receiving a cell to signal an obstacle — the rest of that ray is then + * For each outline point, casts an 4-connected Bresenham ray from the centre + * to that point and yields every cell along the ray. You can pass `breaker` + * to signal an obstacle — the rest of that ray is then * skipped and the next outline point's ray begins. The obstacle cell itself - * is always yielded (it is visible; only the cells behind it are blocked). - * - * ```ts - * const fov = bresenhamCircleGen(cx, cy, r, { fill: 'fov' }); - * let result = fov.next(); - * while (!result.done) { - * const blocked = isWall(result.value); - * result = fov.next(blocked); // pass true to stop this ray - * } - * ``` + * is not yielded (obstacle is hidden). * * **Bounds:** The outline is always generated without clipping so that rays * extend to the full circle perimeter. The individual rays are clipped to * the supplied bounds, so only in-bounds cells are yielded. * - * **Coverage:** One ray is cast per outline point (`4r` rays total). At large - * radii, the angular gap between adjacent rays may leave interior cells - * uncovered. For precise FOV at large radii, prefer a shadowcasting algorithm. - * * --- * * ## Clipping @@ -284,25 +281,13 @@ export const bresenhamLine = ( * yields only the centre point. * @param options - Optional `fill` flag and clip bounds. */ -export function bresenhamCircleGen( - x: number, - y: number, - r: number, - options?: BresenhamCircleOptions, -): Generator; -export function bresenhamCircleGen( - x: number, - y: number, - r: number, - options: BresenhamCircleOptions<'fov'>, -): Generator; export function* bresenhamCircleGen( x: number, y: number, r: number, options?: BresenhamCircleOptions, -): Generator { +): Generator { if (options?.fill === 'fov') { const lineBounds: BresenhamLineOptions = { directions: options.directions, @@ -313,8 +298,8 @@ export function* bresenhamCircleGen( }; for (const { x: ox, y: oy } of bresenhamCircleGen(x, y, r, { fill: false })) { for (const linePoint of bresenhamLineGen(x, y, ox, oy, lineBounds)) { - const skipLine = yield linePoint; - if (skipLine) break; + if (options?.breaker?.(linePoint.x, linePoint.y)) break; + yield linePoint; } } return; @@ -403,5 +388,5 @@ export const bresenhamCircle = ( x: number, y: number, r: number, - options?: BresenhamCircleOptions, + options?: BresenhamCircleFillOptions, ): Point[] => Array.from(bresenhamCircleGen(x, y, r, options));