// src/data/aircraftIcons.js /** * Nose-up white aircraft silhouettes, one per classifyAircraft() kind, as SVG * data URIs for Cesium billboards. * * FIDELITY NOTE (2026-07-02): redrawn at a 96×96 viewBox (glyph centered at * 48,48) instead of the old 32×32. The billboards themselves are still added at * width/height 20 (24 tracked) in flights.js / militaryFlights.js, so Cesium * DOWN-samples this larger, higher-precision source to the same on-screen * footprint — crisp anti-aliased edges instead of an upscaled 32px bitmap. The * on-screen size is unchanged and CLASS_SCALE_2D is untouched (no scale * compensation needed: the billboard's explicit width/height, not the SVG's, * sets the pixel size — the intrinsic SVG size only controls raster fidelity). * * All glyphs are nose-up (nose toward -Y at rotation 0; the screen-projected * rotation pipeline in iconOrientation.js spins the whole glyph). Fill is white * with a subtle dark hairline stroke so the tint pipeline (billboard.color = * white / cyan-tracked / amber-military, plus .withAlpha fades) keeps working — * no per-glyph hardcoded colors that would fight the tint. * * MIXED-SET UPDATE (2026-08-15, owner Hangar picks): airliner/widebody/ * turboprop/helicopter use the "refined" recognition-chart redraw; * quadjet/glider use the "bold" chart-symbol redraw; light/fastjet keep the * original drawings. Raster fidelity doubled (192px source, same 96 coords). * * Silhouette language follows skylight's type-aware glyphs (MIT, * https://github.com/cpaczek/skylight): slender fuselages, swept wings for * jets, long high-aspect wings for the glider, straight wings + prop discs for * props, a rotor disc + tail boom + tail-rotor for the helicopter, and a delta * for the fast jet. Each class has a DISTINCT planform so it reads at ~20px by * shape, not just by footprint size. */ const VIEW = 96; const C = VIEW / 2; // 48 — glyph centre // Hairline dark edge (scaled for the 96 box: ~1.5u ≈ the old 0.5u at 32). const STROKE = 'stroke="rgba(0,0,0,0.32)" stroke-width="1.4" stroke-linejoin="round"'; // Heavier edge for the chart-symbol ("bold") glyphs adopted 2026-08-15. const STROKE_BOLD = 'stroke="rgba(0,0,0,0.38)" stroke-width="2" stroke-linejoin="round"'; // Softer white for translucent detail (prop discs, rotor disc) — still white so // the tint multiplies cleanly; only the alpha differs. const DISC = 'fill="white" fill-opacity="0.5"'; // Each body is drawn in a centred coordinate frame (origin 0,0 = glyph centre), // nose toward -Y. Numbers are in the 96-unit space (half-extent up to ~45). const BODIES = { // ── Airliner: slender swept-wing narrow-body, two underwing engine pods, // swept tailplane. The canonical jet; everything else reads against it. airliner: ` `, // ── Widebody: same swept jet but noticeably BIGGER span + fatter fuselage, // twin engines set further out. Reads as a heavier airliner. widebody: ` `, // ── Quadjet: the widest swept jet, with FOUR bold underwing engine pods (two // per wing) that hang well below the trailing edge — the wing reads as a // 4-lumped bar even when the individual pods blur together at ~20px. quadjet: ` `, // ── Turboprop: bold STRAIGHT (unswept) wings — thicker chord than a jet — two // fat nacelles that jut ahead of the leading edge, each capped by a solid // prop disc. Reads as "straight wing with two lumps + discs" at ~20px. turboprop: ` `, // ── Light GA: small and CHUNKY — short, DEEP-chord straight wings and a fat // stubby fuselage make it read as a solid little block (not a thin cross), // with a bold nose prop disc. The short deep wing is the anti-glider cue. light: ` `, // ── Glider: VERY long, high-aspect TAPERED wings (fat root → thin tips) on a // slim fuselage with a T-tail. The extreme span is the whole identity — it // reads as "the wide skinny one" against light's short stubby wings. glider: ` `, // ── Helicopter: a SOLID translucent main-rotor disc (survives downscale as an // obvious circle, unlike a thin ring) with a bold two-blade cross, a // teardrop cabin, a tail boom, and a tail rotor. Unmistakable "disc on top" // silhouette that never reads as a fixed-wing. helicopter: ` `, // ── Fast jet: sharp delta/cropped-delta with a pointed nose, LERX root // blend, and twin tail fins. Aggressive, all-wing. fastjet: ` `, // 2026-08-15 Hangar additions: two NEW classes shipped with the real-model // fleet (CLASS_MODEL_REAL). Same contract as the set above: 96×96 nose-up, // white fill, hairline stroke, tint-safe. // Business jet — slender fuselage, modest swept wing, aft engines, T-tail. bizjet: ` `, // ── TR-3B (hidden 9th kind, Easter egg — NOT a classifyAircraft() output). // The canonical black-triangle silhouette: a dark isosceles delta with a // light at each corner and a dimmer one at the centre. Reached only by // explicit user conversion (see tr3bRegistry.js), never by classification. // // DELIBERATE BREAK from the white-fill contract above: this glyph is a // SHADOW, so it carries its own near-black fill. The billboard tint still // multiplies (white fleet / cyan tracked / amber military), which darkens // rather than lightens — the triangle stays a silhouette in every layer, // and the corner lights pick the tint up as their hue. tr3b: ` `, // ── TR-3B, thermal-reactive variant. Same cold airframe, but the three // corner emitters + the centre one render HOT: near-white cores inside a // baked radial glow, so the FLIR/NVG luminance mapping (and any bloom) // lights them up while the hull stays cold. Selected by the layers // whenever their `irBoost` style param is on (surveillance/thermal/nvg). tr3bHot: ` `, // Large UAV (Reaper-class) — bulbous sensor nose, very long slender // straight wings, slim tail boom, canted V-tail. uav: ` `, }; const _iconCache = new Map(); const _b64 = (s) => typeof btoa === 'function' ? btoa(s) : Buffer.from(s, 'utf8').toString('base64'); /** Fleet raster: billboards render at ~40–58 DEVICE px (width 20–24 CSS × * Retina × class scale). Cesium's billboard atlas has no mipmaps, so a big * texture gets GPU-minified into mush (192 px ÷ 40 = 4.8× — the owner's * "soft" glyphs; the pre-2026-08-15 96 px raster aliased instead). Rastering * NEAR the display size lets the browser's SVG AA do the work: crisp AND * smooth. 64 covers the 40–58 px fleet band with ≤1.6× minification. */ const FLEET_RASTER_PX = 64; /** Tracked raster: the tracked billboard is the one SUSTAINED large 2D glyph * (close-zoom fleet flybys hand off to 3D models). Keep the 192 px texture so * it stays crisp at its biggest on-screen sizes. */ const TRACKED_RASTER_PX = 192; /** Data URI for a class silhouette (lazily built, cached per kind+size). * Default size serves the fleet; pass `aircraftIcon(kind, TRACKED_ICON_PX)` * (re-exported below) for the tracked billboard. */ export const TRACKED_ICON_PX = TRACKED_RASTER_PX; export function aircraftIcon(kind, px = FLEET_RASTER_PX) { const k = BODIES[kind] ? kind : 'airliner'; const key = `${k}@${px}`; let uri = _iconCache.get(key); if (!uri) { const svg = `${BODIES[k]}`; uri = 'data:image/svg+xml;base64,' + _b64(svg); _iconCache.set(key, uri); } return uri; }