// 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 = ``;
uri = 'data:image/svg+xml;base64,' + _b64(svg);
_iconCache.set(key, uri);
}
return uri;
}