deadhead-space-freight-tycoon/apps/web/src/map/hitTest.ts

37 lines
1.3 KiB
TypeScript

import { toScreen, type Camera, type Viewport } from "./camera";
import type { MapPoint } from "./projection";
import type { Scene } from "./scene";
export type MapSelection =
{ kind: "location"; id: string } | { kind: "flight"; dispatchId: string };
/** How close (screen pixels) a tap must be to a location or ship: about a fingertip. */
export const TAP_RADIUS = 22;
/**
* What a tap at `tap` (screen pixels) selects: the nearest ship marker or
* location within reach. Ships win ties, since they are drawn on top.
*/
export function hitTest(
scene: Scene,
tap: MapPoint,
viewport: Viewport,
camera: Camera,
): MapSelection | null {
let best: { selection: MapSelection; distance: number } | null = null;
const consider = (point: MapPoint, selection: MapSelection, bias: number) => {
const p = toScreen(point, viewport, camera);
const distance = Math.hypot(p.x - tap.x, p.y - tap.y) - bias;
if (distance <= TAP_RADIUS && (!best || distance < best.distance)) {
best = { selection, distance };
}
};
for (const location of scene.locations) {
consider(location.point, { kind: "location", id: location.id }, 0);
}
for (const flight of scene.flights) {
consider(flight.marker, { kind: "flight", dispatchId: flight.dispatchId }, 4);
}
return (best as { selection: MapSelection } | null)?.selection ?? null;
}