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 };
|
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 }) {
|
export default function SVT({ values }) {
|
||||||
const elRef = useRef(null);
|
const elRef = useRef(null);
|
||||||
const mapRef = useRef(null);
|
const mapRef = useRef(null);
|
||||||
@@ -77,9 +97,9 @@ export default function SVT({ values }) {
|
|||||||
style: STYLE,
|
style: STYLE,
|
||||||
center: [num(values.lon, -122.31), num(values.lat, 47.45)],
|
center: [num(values.lon, -122.31), num(values.lat, 47.45)],
|
||||||
zoom: 11.5,
|
zoom: 11.5,
|
||||||
pitch: 72,
|
pitch: cameraPitchForAircraft(num(values.pitch)),
|
||||||
bearing: num(values.heading),
|
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
|
pixelRatio: 1, // don't render at 2× on retina — big perf/bandwidth win
|
||||||
renderWorldCopies: false,
|
renderWorldCopies: false,
|
||||||
maxTileCacheSize: 40,
|
maxTileCacheSize: 40,
|
||||||
@@ -154,7 +174,7 @@ export default function SVT({ values }) {
|
|||||||
map.jumpTo({
|
map.jumpTo({
|
||||||
center: [num(v.lon, -122.31), num(v.lat, 47.45)],
|
center: [num(v.lon, -122.31), num(v.lat, 47.45)],
|
||||||
bearing: num(v.heading),
|
bearing: num(v.heading),
|
||||||
pitch: Math.max(58, Math.min(76, 72 + num(v.pitch))),
|
pitch: cameraPitchForAircraft(num(v.pitch)),
|
||||||
zoom,
|
zoom,
|
||||||
});
|
});
|
||||||
updateTerrainAwareness(num(v.altitude, 5500));
|
updateTerrainAwareness(num(v.altitude, 5500));
|
||||||
|
|||||||
Reference in New Issue
Block a user