Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ CDN usage:
| Search & text | `fuzzySearch`, `fuzzyScore`, `Trie`, `binarySearch`, `levenshteinDistance` | `search/*.ts` | `examples/search.ts` |
| Data & diff pipelines | `diff`, `deepClone`, `groupBy`, `diffJson`, `applyJsonDiff` | `data/*.ts` | `examples/jsonDiff.ts` |
| Graph algorithms | `graphBFS`, `graphDFS`, `topologicalSort` | `graph/traversal.ts` | `examples/graph.ts` |
| Visual & geometry | `convexHull`, `lineIntersection`, `pointInPolygon`, `easing`, `quadraticBezier`, `cubicBezier` | `geometry/*.ts`, `visual/*.ts` | `examples/geometry.ts`, `examples/visual.ts` |
| Visual & geometry | `convexHull`, `lineIntersection`, `pointInPolygon`, `bresenhamLine`, `easing`, `quadraticBezier`, `cubicBezier` | `geometry/*.ts`, `visual/*.ts` | `examples/geometry.ts`, `examples/bresenham.ts`, `examples/visual.ts` |

## Scripts
```bash
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
- [x] Object pool helper for reusable entities
- [x] Weighted random selector (alias method)
- [x] Fisher–Yates shuffle implementation
- [ ] Bresenham line / raster traversal helpers
- [x] Bresenham line / raster traversal helpers
- Real-time systems:
- [ ] 2D camera system (smooth follow, dead zones, screen shake)
- [ ] Particle system with configurable emitters
Expand Down
8 changes: 8 additions & 0 deletions docs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1118,6 +1118,14 @@ export function lineIntersection(
*/
export function pointInPolygon(point: Point, polygon: Point[]): boolean;

/**
* Bresenham line rasterisation.
* Use for: grid traversal, tile picking, pixel plotting.
* Performance: O(max(|dx|, |dy|)).
* Import: geometry/bresenham.ts
*/
export function bresenhamLine(start: Point, end: Point): Point[];

/**
* Common easing curves for animation.
* Use for: UI transitions, motion design, data viz.
Expand Down
4 changes: 4 additions & 0 deletions examples/bresenham.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { bresenhamLine } from '../src/index.js';

const cells = bresenhamLine({ x: 2, y: 3 }, { x: 10, y: 7 });
console.log(cells);
55 changes: 55 additions & 0 deletions src/geometry/bresenham.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { Point } from '../types.js';

function assertPoint(point: Point, name: string): void {
if (typeof point?.x !== 'number' || Number.isNaN(point.x) || !Number.isFinite(point.x)) {
throw new Error(`${name}.x must be a finite number.`);
}
if (typeof point?.y !== 'number' || Number.isNaN(point.y) || !Number.isFinite(point.y)) {
throw new Error(`${name}.y must be a finite number.`);
}
}

function round(value: number): number {
return Math.round(value);
}

/**
* Generates integer raster coordinates using Bresenham's line algorithm.
* Useful for: tile picking, grid ray tracing, and discrete drawing operations.
*/
export function bresenhamLine(start: Point, end: Point): Point[] {
assertPoint(start, 'start');
assertPoint(end, 'end');

let x0 = round(start.x);
let y0 = round(start.y);
const x1 = round(end.x);
const y1 = round(end.y);

const points: Point[] = [];

const dx = Math.abs(x1 - x0);
const sx = x0 < x1 ? 1 : -1;
const dy = -Math.abs(y1 - y0);
const sy = y0 < y1 ? 1 : -1;
let error = dx + dy;

points.push({ x: x0, y: y0 });
while (x0 !== x1 || y0 !== y1) {
const e2 = 2 * error;
if (e2 >= dy) {
error += dy;
x0 += sx;
}
if (e2 <= dx) {
error += dx;
y0 += sy;
}
points.push({ x: x0, y: y0 });
}

return points;
}

/** @internal */
export const __internals = { assertPoint, round };
8 changes: 8 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export const examples = {
convexHull: 'examples/geometry.ts',
lineIntersection: 'examples/geometry.ts',
pointInPolygon: 'examples/geometry.ts',
bresenhamLine: 'examples/bresenham.ts',
},
visual: {
easing: 'examples/visual.ts',
Expand Down Expand Up @@ -547,6 +548,13 @@ export { lineIntersection } from './geometry/lineIntersection.js';
*/
export { pointInPolygon } from './geometry/pointInPolygon.js';

/**
* Bresenham rasterisation for grid-based line traversal.
*
* Example file: examples/bresenham.ts
*/
export { bresenhamLine } from './geometry/bresenham.js';

// ============================================================================
// 🎨 VISUAL & ANIMATION
// ============================================================================
Expand Down
48 changes: 48 additions & 0 deletions tests/bresenham.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { describe, expect, it } from 'vitest';

import { bresenhamLine } from '../src/index.js';

describe('bresenhamLine', () => {
it('handles horizontal lines', () => {
const points = bresenhamLine({ x: 0, y: 2 }, { x: 4, y: 2 });
expect(points).toEqual([
{ x: 0, y: 2 },
{ x: 1, y: 2 },
{ x: 2, y: 2 },
{ x: 3, y: 2 },
{ x: 4, y: 2 },
]);
});

it('handles steep lines', () => {
const points = bresenhamLine({ x: 1, y: 1 }, { x: 3, y: 6 });
expect(points).toEqual([
{ x: 1, y: 1 },
{ x: 1, y: 2 },
{ x: 2, y: 3 },
{ x: 2, y: 4 },
{ x: 3, y: 5 },
{ x: 3, y: 6 },
]);
});

it('rounds floating inputs', () => {
const points = bresenhamLine({ x: 0.4, y: 0.6 }, { x: 2.6, y: 2.4 });
expect(points).toEqual([
{ x: 0, y: 1 },
{ x: 1, y: 1 },
{ x: 2, y: 2 },
{ x: 3, y: 2 },
]);
});

it('handles identical points', () => {
const points = bresenhamLine({ x: 1.2, y: 1.2 }, { x: 1.4, y: 1.4 });
expect(points).toEqual([{ x: 1, y: 1 }]);
});

it('throws on invalid points', () => {
expect(() => bresenhamLine({ x: Number.NaN, y: 0 }, { x: 1, y: 1 })).toThrow(/start.x/);
expect(() => bresenhamLine({ x: 0, y: 0 }, { x: 1, y: Number.POSITIVE_INFINITY })).toThrow(/end.y/);
});
});