feat: add first person view (FPV) functionality and HUD (#9)
* feat: add first person view (FPV) functionality and HUD - Updated FlightCard component to include FPV toggle button and state management. - Introduced FpvHud component for displaying flight data in FPV mode. - Enhanced useFlights hook to support FPV bounding box logic for fetching flights. - Added keyboard shortcuts for toggling FPV mode. - Updated settings to include FPV-related configurations (pitch, chase distance). - Implemented major airports caching for improved performance. - Added fetchFlightByIcao24 function for single aircraft state retrieval. * Refactor CameraController and ControlPanel components; enhance flight search functionality - Simplified CameraController by removing unused refs and effects, and centralized map interaction management. - Updated ControlPanel to support flight lookup with new props and integrated flight search results. - Enhanced SearchContent to include flight matching logic and improved user feedback for flight searches. - Introduced caching for flight callsign lookups in OpenSky API integration to optimize performance. - Removed unnecessary settings related to FPV pitch and free camera mode from use-settings hook. * feat: enhance FPV functionality and improve flight data handling - Added `projectLngLatElevationPixelDelta` function to calculate pixel deltas based on longitude, latitude, and elevation. - Updated `CameraController` to utilize new FPV parameters and improve camera behavior during flight. - Enhanced flight data handling in `FlightLayers` to ensure proper tracking and display of flight information. - Improved UI components for better user experience, including adjustments to the FPV HUD and flight card. - Added error handling for image loading in the control panel. - Refactored altitude and speed calculations to ensure they handle non-finite values gracefully. - Adjusted map attribution behavior for better responsiveness on different screen sizes.
This commit is contained in:
93
src/components/map/camera-controller-utils.ts
Normal file
93
src/components/map/camera-controller-utils.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import type maplibregl from "maplibre-gl";
|
||||
import { MercatorCoordinate } from "maplibre-gl";
|
||||
|
||||
export const FPV_DISTANCE_ZOOM_OFFSET = 1.1;
|
||||
|
||||
export function clamp01(value: number): number {
|
||||
return Math.max(0, Math.min(1, value));
|
||||
}
|
||||
|
||||
export function smoothstep(t: number): number {
|
||||
return t * t * (3 - 2 * t);
|
||||
}
|
||||
|
||||
export function lerp(from: number, to: number, t: number): number {
|
||||
return from + (to - from) * t;
|
||||
}
|
||||
|
||||
export function normalizeLng(lng: number): number {
|
||||
return ((lng + 540) % 360) - 180;
|
||||
}
|
||||
|
||||
export function lerpLng(from: number, to: number, t: number): number {
|
||||
const delta = ((to - from + 540) % 360) - 180;
|
||||
return normalizeLng(from + delta * t);
|
||||
}
|
||||
|
||||
export function fpvZoomForAltitude(altMeters: number): number {
|
||||
if (!Number.isFinite(altMeters)) return 12;
|
||||
const alt = Math.max(altMeters, 0);
|
||||
if (alt < 50) return 16.2;
|
||||
const zoom = 18.1 - 2.0 * Math.log10(Math.max(alt, 50));
|
||||
return Math.max(10.1, Math.min(16.2, zoom));
|
||||
}
|
||||
|
||||
export function projectLngLatElevationPixelDelta(
|
||||
map: maplibregl.Map,
|
||||
lng: number,
|
||||
lat: number,
|
||||
elevationMeters: number,
|
||||
): { dx: number; dy: number } | null {
|
||||
type Transform3DLike = {
|
||||
_pixelMatrix3D?: unknown;
|
||||
centerPoint?: { x: number; y: number };
|
||||
coordinatePoint: (
|
||||
coord: MercatorCoordinate,
|
||||
elevation: number,
|
||||
pixelMatrix3D: unknown,
|
||||
) => { x: number; y: number } | null;
|
||||
};
|
||||
|
||||
const tr = (map as unknown as { transform?: Transform3DLike }).transform;
|
||||
if (!tr || typeof tr.coordinatePoint !== "function") return null;
|
||||
|
||||
const pixelMatrix3D = tr._pixelMatrix3D;
|
||||
const centerPoint = tr.centerPoint;
|
||||
if (!pixelMatrix3D || !centerPoint) return null;
|
||||
|
||||
let p: { x: number; y: number } | null = null;
|
||||
try {
|
||||
p = tr.coordinatePoint(
|
||||
MercatorCoordinate.fromLngLat({ lng, lat }),
|
||||
elevationMeters,
|
||||
pixelMatrix3D,
|
||||
);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!p || !Number.isFinite(p.x) || !Number.isFinite(p.y)) return null;
|
||||
return { dx: p.x - centerPoint.x, dy: p.y - centerPoint.y };
|
||||
}
|
||||
|
||||
export function setMapInteractionsEnabled(
|
||||
map: maplibregl.Map,
|
||||
enabled: boolean,
|
||||
): void {
|
||||
if (enabled) {
|
||||
map.dragPan.enable();
|
||||
map.dragRotate.enable();
|
||||
map.scrollZoom.enable();
|
||||
map.touchZoomRotate.enable();
|
||||
map.doubleClickZoom.enable();
|
||||
map.keyboard.enable();
|
||||
return;
|
||||
}
|
||||
|
||||
map.dragPan.disable();
|
||||
map.dragRotate.disable();
|
||||
map.scrollZoom.disable();
|
||||
map.touchZoomRotate.disable();
|
||||
map.doubleClickZoom.disable();
|
||||
map.keyboard.disable();
|
||||
}
|
||||
Reference in New Issue
Block a user