Add the explicit breaker option instead of .next(true) protocol.
This commit is contained in:
parent
96d85d3164
commit
3bc89af23b
|
|
@ -1,6 +1,6 @@
|
||||||
import { clamp } from "@common/utils";
|
import { clamp } from "@common/utils";
|
||||||
|
|
||||||
export interface Point {
|
interface Point {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
}
|
}
|
||||||
|
|
@ -21,16 +21,27 @@ interface BresenhamOptions {
|
||||||
|
|
||||||
export interface BresenhamLineOptions extends BresenhamOptions {}
|
export interface BresenhamLineOptions extends BresenhamOptions {}
|
||||||
|
|
||||||
export interface BresenhamCircleOptions<T = boolean | 'fov'> extends BresenhamOptions {
|
interface BresenhamCircleBaseOptions<T extends boolean | 'fov'> extends BresenhamOptions {
|
||||||
/**
|
/**
|
||||||
* - `false` (default) — outline only.
|
* - `false` (default) — outline only.
|
||||||
* - `true` — filled disc (span-from-outline scanline).
|
* - `true` — filled disc (span-from-outline scanline).
|
||||||
* - `'fov'` — ray-casting field of view; the generator accepts `true` via
|
* - `'fov'` — ray-casting field of view.
|
||||||
* `.next(true)` to signal an obstacle and skip the rest of that ray.
|
|
||||||
*/
|
*/
|
||||||
fill?: T;
|
fill?: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BresenhamCircleFillOptions = BresenhamCircleBaseOptions<boolean>;
|
||||||
|
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
|
// Cohen-Sutherland outcodes
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -247,30 +258,16 @@ export const bresenhamLine = (
|
||||||
*
|
*
|
||||||
* ## FOV mode (`fill: 'fov'`) — ray-casting field of view
|
* ## FOV mode (`fill: 'fov'`) — ray-casting field of view
|
||||||
*
|
*
|
||||||
* For each outline point, casts an 8-connected Bresenham ray from the centre
|
* For each outline point, casts an 4-connected Bresenham ray from the centre
|
||||||
* to that point and yields every cell along the ray. The generator uses the
|
* to that point and yields every cell along the ray. You can pass `breaker`
|
||||||
* **send protocol**: the caller can pass `true` to `.next(true)` after
|
* to signal an obstacle — the rest of that ray is then
|
||||||
* receiving a cell to signal an obstacle — the rest of that ray is then
|
|
||||||
* skipped and the next outline point's ray begins. The obstacle cell itself
|
* 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).
|
* is not yielded (obstacle is hidden).
|
||||||
*
|
|
||||||
* ```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
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
*
|
||||||
* **Bounds:** The outline is always generated without clipping so that rays
|
* **Bounds:** The outline is always generated without clipping so that rays
|
||||||
* extend to the full circle perimeter. The individual rays are clipped to
|
* extend to the full circle perimeter. The individual rays are clipped to
|
||||||
* the supplied bounds, so only in-bounds cells are yielded.
|
* 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
|
* ## Clipping
|
||||||
|
|
@ -284,25 +281,13 @@ export const bresenhamLine = (
|
||||||
* yields only the centre point.
|
* yields only the centre point.
|
||||||
* @param options - Optional `fill` flag and clip bounds.
|
* @param options - Optional `fill` flag and clip bounds.
|
||||||
*/
|
*/
|
||||||
export function bresenhamCircleGen(
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
r: number,
|
|
||||||
options?: BresenhamCircleOptions<boolean>,
|
|
||||||
): Generator<Point, void, void>;
|
|
||||||
export function bresenhamCircleGen(
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
r: number,
|
|
||||||
options: BresenhamCircleOptions<'fov'>,
|
|
||||||
): Generator<Point, void, boolean | void>;
|
|
||||||
|
|
||||||
export function* bresenhamCircleGen(
|
export function* bresenhamCircleGen(
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
r: number,
|
r: number,
|
||||||
options?: BresenhamCircleOptions,
|
options?: BresenhamCircleOptions,
|
||||||
): Generator<Point, void, boolean | void> {
|
): Generator<Point, void, void> {
|
||||||
if (options?.fill === 'fov') {
|
if (options?.fill === 'fov') {
|
||||||
const lineBounds: BresenhamLineOptions = {
|
const lineBounds: BresenhamLineOptions = {
|
||||||
directions: options.directions,
|
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 { x: ox, y: oy } of bresenhamCircleGen(x, y, r, { fill: false })) {
|
||||||
for (const linePoint of bresenhamLineGen(x, y, ox, oy, lineBounds)) {
|
for (const linePoint of bresenhamLineGen(x, y, ox, oy, lineBounds)) {
|
||||||
const skipLine = yield linePoint;
|
if (options?.breaker?.(linePoint.x, linePoint.y)) break;
|
||||||
if (skipLine) break;
|
yield linePoint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
@ -403,5 +388,5 @@ export const bresenhamCircle = (
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
r: number,
|
r: number,
|
||||||
options?: BresenhamCircleOptions<boolean>,
|
options?: BresenhamCircleFillOptions,
|
||||||
): Point[] => Array.from(bresenhamCircleGen(x, y, r, options));
|
): Point[] => Array.from(bresenhamCircleGen(x, y, r, options));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue