SVT: align synthetic terrain horizon with the attitude horizon
The 3D terrain showed almost no sky — the horizon sat far above the attitude horizon line. Base camera pitch was 72°, but with MapLibre's 36.87° vertical FOV the flat horizon only reaches the attitude line (28% of the SVT box) at ~82°. Invert the perspective to derive the camera pitch from aircraft pitch so the synthetic horizon lands exactly on the attitude horizon and tracks it 1:1 (accounting for the 1.5× canvas scale). Raise maxPitch to 85. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -63,6 +63,26 @@ function runwayGeoJSON(list) {
|
||||
return { type: 'FeatureCollection', features: feats };
|
||||
}
|
||||
|
||||
// Place the synthetic horizon exactly where the PFD attitude horizon sits, so
|
||||
// the 3D terrain and the 2D attitude agree. The attitude horizon is at 28% of
|
||||
// the SVT box at level flight and moves PITCH_PX (9 px) per degree within the
|
||||
// 706-px-tall box; the canvas is scaled 1.5× about that 28% line (to cover the
|
||||
// corners when banked). We invert MapLibre's perspective to find the camera
|
||||
// pitch that lands the flat horizon there. With the default vertical FOV of
|
||||
// 36.87°, the focal-length/height ratio is 0.5/tan(fov/2) = 1.5, independent of
|
||||
// resolution. Screen offset of the horizon above centre = f·tan(90°−pitch).
|
||||
const CANVAS_SCALE = 1.5; // matches .svt-canvas CSS transform scale
|
||||
const FOV_FH = 1.5; // 0.5 / tan(fov/2), fov = 36.87° (MapLibre default)
|
||||
const HORIZON0 = (270 - 74) / 706; // attitude horizon as a fraction of the SVT box (≈0.2776)
|
||||
const PX_PER_DEG = 9 / 706; // PITCH_PX / box height — displayed horizon travel per °
|
||||
function cameraPitchForAircraft(aircraftPitchDeg) {
|
||||
const dispFrac = HORIZON0 + PX_PER_DEG * aircraftPitchDeg; // where the horizon must appear
|
||||
const rawFrac = HORIZON0 + (dispFrac - HORIZON0) / CANVAS_SCALE; // undo the 1.5× canvas scale
|
||||
const t = (0.5 - rawFrac) / FOV_FH; // = tan(90°−pitch)
|
||||
const pitch = 90 - (Math.atan(t) * 180) / Math.PI;
|
||||
return Math.max(60, Math.min(85, pitch));
|
||||
}
|
||||
|
||||
export default function SVT({ values }) {
|
||||
const elRef = useRef(null);
|
||||
const mapRef = useRef(null);
|
||||
@@ -77,9 +97,9 @@ export default function SVT({ values }) {
|
||||
style: STYLE,
|
||||
center: [num(values.lon, -122.31), num(values.lat, 47.45)],
|
||||
zoom: 11.5,
|
||||
pitch: 72,
|
||||
pitch: cameraPitchForAircraft(num(values.pitch)),
|
||||
bearing: num(values.heading),
|
||||
maxPitch: 76, // lower max pitch = nearer horizon = less distant terrain
|
||||
maxPitch: 85, // horizon placement needs ~82° at level, more when pitched up
|
||||
pixelRatio: 1, // don't render at 2× on retina — big perf/bandwidth win
|
||||
renderWorldCopies: false,
|
||||
maxTileCacheSize: 40,
|
||||
@@ -154,7 +174,7 @@ export default function SVT({ values }) {
|
||||
map.jumpTo({
|
||||
center: [num(v.lon, -122.31), num(v.lat, 47.45)],
|
||||
bearing: num(v.heading),
|
||||
pitch: Math.max(58, Math.min(76, 72 + num(v.pitch))),
|
||||
pitch: cameraPitchForAircraft(num(v.pitch)),
|
||||
zoom,
|
||||
});
|
||||
updateTerrainAwareness(num(v.altitude, 5500));
|
||||
|
||||
Reference in New Issue
Block a user