59 lines
1.8 KiB
TypeScript
59 lines
1.8 KiB
TypeScript
import type { Point } from '../types';
|
|
|
|
export function pointInPolygon(p: Point, poly: Point[]): boolean {
|
|
if (poly.length < 3) return true;
|
|
let inside = false;
|
|
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
|
const a = poly[i];
|
|
const b = poly[j];
|
|
if (onSegment(a, b, p)) return true;
|
|
const intersect = a.y > p.y !== b.y > p.y && p.x < ((b.x - a.x) * (p.y - a.y)) / (b.y - a.y) + a.x;
|
|
if (intersect) inside = !inside;
|
|
}
|
|
return inside;
|
|
}
|
|
|
|
function onSegment(a: Point, b: Point, p: Point): boolean {
|
|
const cross = (b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x);
|
|
if (Math.abs(cross) > 1e-9) return false;
|
|
const dot = (p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y);
|
|
if (dot < 0) return false;
|
|
const len2 = (b.x - a.x) ** 2 + (b.y - a.y) ** 2;
|
|
return dot <= len2;
|
|
}
|
|
|
|
function closestOnSegment(a: Point, b: Point, p: Point): Point {
|
|
const dx = b.x - a.x;
|
|
const dy = b.y - a.y;
|
|
const len2 = dx * dx + dy * dy;
|
|
if (len2 === 0) return { ...a };
|
|
let t = ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2;
|
|
t = Math.max(0, Math.min(1, t));
|
|
return { x: a.x + t * dx, y: a.y + t * dy };
|
|
}
|
|
|
|
export function nearestPointInPolygon(p: Point, poly: Point[]): Point {
|
|
if (poly.length < 3) return { ...p };
|
|
if (pointInPolygon(p, poly)) return { ...p };
|
|
let best = poly[0];
|
|
let bestD = Infinity;
|
|
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
|
const q = closestOnSegment(poly[j], poly[i], p);
|
|
const d = (q.x - p.x) ** 2 + (q.y - p.y) ** 2;
|
|
if (d < bestD) {
|
|
bestD = d;
|
|
best = q;
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
export function rectanglePoints(width: number, height: number): Point[] {
|
|
return [
|
|
{ x: 0, y: 0 },
|
|
{ x: width, y: 0 },
|
|
{ x: width, y: height },
|
|
{ x: 0, y: height },
|
|
];
|
|
}
|