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; }