Auto-install Lua, smooth all panels, airspace overlay + launcher region picker
FlyWithLua auto-install: bridge drops fms-sync/ui-sync/terrain-probe into X-Plane's FlyWithLua Scripts dir on startup and self-updates (content-compare). Graceful when no X-Plane / no FlyWithLua. /api/lua/install + status in health. Desktop app bundles the scripts and passes LUA_SRC_DIR to the sidecar. Smoothing: shared useEased/useEasedAngle hook (api/ease.js) with render-bail on settle. VFR steam gauges now interpolate to 60fps instead of stepping at the ~10Hz value stream. MFD ownship no longer vibrates — position/heading eased in a single rAF loop, follow-pan without animated-panTo pile-up (pauses on range zoom). Airspace overlay: server/airspace.js loads per-region GeoJSON, classifies (B/C/D/TMA/CTR/MOA/Restricted/Prohibited/Danger), bbox query, and downloads regions on demand — FAA (US, key-free) and OpenAIP (Europe, user key). New AIRSPACE softkey draws chart-coloured boundaries (B blue, C magenta, D dashed), non-interactive so map-clicks still drop waypoints. Launcher gains a "Lufträume" section to pick/download regions via the running bridge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
|
||||
// Frame-rate-independent easing of a scalar toward a moving target (alpha from
|
||||
// dt + a time constant). The rAF loop runs continuously so the value keeps
|
||||
// gliding between the bridge's discrete value updates — turning a stuttery
|
||||
// ~10–20 Hz stream into a smooth 60 fps motion. To avoid needless React work it
|
||||
// only re-renders the consumer while the value is actually moving: once settled,
|
||||
// the functional setState returns the same number and React bails (Object.is).
|
||||
export function useEased(target, tau = 0.08) {
|
||||
const [, force] = useState(0);
|
||||
const cur = useRef(target), tg = useRef(target);
|
||||
tg.current = target;
|
||||
useEffect(() => {
|
||||
let raf, last = 0;
|
||||
const loop = (now) => {
|
||||
const dt = last ? Math.min(0.05, (now - last) / 1000) : 0.016; last = now;
|
||||
const k = 1 - Math.exp(-dt / tau);
|
||||
const next = cur.current + (tg.current - cur.current) * k;
|
||||
const settled = Math.abs(tg.current - next) < 0.02;
|
||||
const val = settled ? tg.current : next;
|
||||
if (val !== cur.current) { cur.current = val; force((n) => (n + 1) & 0xffff); }
|
||||
raf = requestAnimationFrame(loop);
|
||||
};
|
||||
raf = requestAnimationFrame(loop);
|
||||
return () => cancelAnimationFrame(raf);
|
||||
}, [tau]);
|
||||
return cur.current;
|
||||
}
|
||||
|
||||
// As above but eases along the shortest arc across the 0↔360 wrap (headings).
|
||||
export function useEasedAngle(target, tau = 0.08) {
|
||||
const [, force] = useState(0);
|
||||
const cur = useRef(target), tg = useRef(target);
|
||||
tg.current = target;
|
||||
useEffect(() => {
|
||||
let raf, last = 0;
|
||||
const loop = (now) => {
|
||||
const dt = last ? Math.min(0.05, (now - last) / 1000) : 0.016; last = now;
|
||||
const k = 1 - Math.exp(-dt / tau);
|
||||
const d = ((tg.current - cur.current + 540) % 360) - 180; // shortest signed arc
|
||||
const next = Math.abs(d) < 0.05 ? tg.current : cur.current + d * k;
|
||||
const val = ((next % 360) + 360) % 360;
|
||||
if (val !== cur.current) { cur.current = val; force((n) => (n + 1) & 0xffff); }
|
||||
raf = requestAnimationFrame(loop);
|
||||
};
|
||||
raf = requestAnimationFrame(loop);
|
||||
return () => cancelAnimationFrame(raf);
|
||||
}, [tau]);
|
||||
return cur.current;
|
||||
}
|
||||
Reference in New Issue
Block a user