36 lines
1.4 KiB
TypeScript
36 lines
1.4 KiB
TypeScript
import type { Camera, Viewport } from "./camera";
|
|
import { layoutScene } from "./layout";
|
|
import type { MapPoint } from "./projection";
|
|
import type { Scene } from "./scene";
|
|
|
|
export type MapSelection =
|
|
{ kind: "location"; id: string } | { kind: "flight"; dispatchId: string };
|
|
|
|
/**
|
|
* What a tap at `tap` (screen pixels) selects: the nearest ship or location
|
|
* whose tap target (at least `minTapPx`, Step 10C.6) contains it, as laid out
|
|
* on screen. Ships win ties, since they are drawn on top.
|
|
*/
|
|
export function hitTest(
|
|
scene: Scene,
|
|
tap: MapPoint,
|
|
viewport: Viewport,
|
|
camera: Camera,
|
|
): MapSelection | null {
|
|
const layout = layoutScene(scene, viewport, camera);
|
|
let best: { selection: MapSelection; distance: number } | null = null;
|
|
const consider = (center: MapPoint, size: number, selection: MapSelection, bias: number) => {
|
|
const distance = Math.hypot(center.x - tap.x, center.y - tap.y);
|
|
if (distance <= size / 2 && (!best || distance - bias < best.distance)) {
|
|
best = { selection, distance: distance - bias };
|
|
}
|
|
};
|
|
for (const location of layout.locations) {
|
|
consider(location.center, location.tap, { kind: "location", id: location.id }, 0);
|
|
}
|
|
for (const flight of layout.flights) {
|
|
consider(flight.center, flight.tap, { kind: "flight", dispatchId: flight.dispatchId }, 4);
|
|
}
|
|
return (best as { selection: MapSelection } | null)?.selection ?? null;
|
|
}
|