diff --git a/changelog.d/1697-fleet-models.md b/changelog.d/1697-fleet-models.md new file mode 100644 index 000000000..963242a49 --- /dev/null +++ b/changelog.d/1697-fleet-models.md @@ -0,0 +1,8 @@ +- Draw every 3D model from the project's procedural fleet set: 121 models + (about 2 MB, MIT like the code) chosen per vehicle by type and drawn life + size, replacing the eight third-party aircraft models. Flights, Military + Flights and Local ADS-B pick a model by ICAO type, class and emitter + category; Vessels, Transit and Rail now draw models too, under the same 3D + toggle and distances, a ship at its AIS length and beam and a tram, bus or + train in its mode or category colour. The whole set loads once when a layer + starts (samehkhamis, #1697). diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 6073b5c84..4ab5329ba 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -2420,8 +2420,9 @@ is validated against Cesium 1.138.0 and must be revalidated on upgrades. `scripts/qa-anime-sky.mjs` checks asset readiness, visible Normal sky and Anime band coverage against a Vite dev server. -`scripts/qa-sky-model-lighting.mjs` compares a lit `airplane.glb` against -black and white environment maps in isolated scenes. +`scripts/qa-sky-model-lighting.mjs` compares a lit fleet airliner +(`public/models/fleet/aircraft/a320.glb`) against black and white environment +maps in isolated scenes. ## Model atmosphere on Apple Metal @@ -3606,20 +3607,22 @@ This is the current runtime/source-of-truth snapshot for the project. > 24.09 m aft (70 % of the model's rendered envelope, so inside it), 2.22 m > below centre (6 %), and 2e-9 m off the centreline. > - **The tracked trail attaches to REAL HULL, aft and below (2026-08-23):** -> `MODEL_TRAIL_ANCHOR_NATIVE` (`src/data/modelVisualAnchor.js`) holds, per GLB -> and in RAW glTF coordinates (see the transform-chain entry below), the point -> of the hull's CENTRELINE PROFILE (its y = 0 slice) closest to the aft-belly -> AABB corner. It is NOT the corner: a bounding-box corner is empty -> space, 4.80 m off the nearest triangle on `airplane.glb` and 6.14 m on -> `jet.glb`, so the trail ended in mid air beside the aircraft. The head segment +> `FLEET_TRAIL_ANCHORS` (`src/data/fleetTrailAnchors.js`, generated by +> `scripts/measure-fleet-trail-anchors.mjs`) holds, per fleet aircraft and +> helicopter model and in RAW glTF coordinates (see the transform-chain entry +> below), the point of the hull's CENTRELINE PROFILE (its centreline slice) +> closest to the aft-belly AABB corner. It is NOT the corner: a bounding-box +> corner is empty space (4.80 m off the nearest triangle on the airliner +> asset this was first measured on, 6.14 m on the jet), so the trail ended in +> mid air beside the aircraft. The head segment > is drawn from a point behind the aircraft to this anchor, so the anchor must > stay aft (83–96 % of each aft extreme) or the segment enters at the tail and -> stops inside the fuselage. `modelScale.test.mjs` reads the POSITION BUFFERS — +> stops inside the fuselage. `fleetModels.test.mjs` reads the POSITION BUFFERS — > real vertices and triangles, because accessor min/max cannot tell a corner > from a surface — and re-derives on-hull, aft, lowest-at-its-station, > on-centreline, and the construction itself. Re-measure, never re-guess. > - **The trail anchor rides CESIUM'S transform chain, not a hand-rolled one -> (2026-08-23 regression fix):** `MODEL_TRAIL_ANCHOR_NATIVE` stores RAW +> (2026-08-23 regression fix):** `FLEET_TRAIL_ANCHORS` stores RAW > glTF coordinates and `modelAnchorWorld()` assembles > `modelMatrix × components.transform × axisCorrection` — the same chain > `ModelSceneGraph` renders with, built from Cesium's own exported @@ -3654,7 +3657,7 @@ This is the current runtime/source-of-truth snapshot for the project. > ENVELOPE, and between them the drawn start slides along the segment so the > visible length grows CONTINUOUSLY from zero. Three earlier cuts are recorded > because their shape matters: testing segment LENGTH against the radius HID -> real trail (58 m aft of `airplane.glb`'s 34.41 m envelope gives a 33.98 m +> real trail (58 m aft of an airliner's 34.41 m envelope gives a 33.98 m > segment, suppressed, though ~23.6 m of it is open air); CLIPPING at the > envelope and keeping only what lay outside stopped a moving trail visibly > SHORT of the aircraft, since a bounding sphere encloses a lot of empty space @@ -4830,8 +4833,9 @@ activeCameraCardEnabled: true })` may publish it through the retained > - **Flights 3D/cockpit hardening:** the destination-direction cue is an > inline SVG rather than a remotely loaded Material Icons ligature, so a > blocked or late font can no longer expand the literal word `navigation` -> across the cockpit. The grounded `airplane.glb` belly offset is calibrated -> to the current asset's measured Y-up bounds (`0.063` native units). +> across the cockpit. A grounded aircraft stands its fleet model's scaled +> clearance (the catalog's `clearance`, measured below the model's origin) +> above the ground. > Contact Previous/Next and Live Signals aircraft handoffs now > re-seed the first-person camera anchor at the selected flight immediately; > bounded feed-correction smoothing remains scoped to the same aircraft. @@ -7290,7 +7294,8 @@ pins and share links already supply complete boxes. normal four-samples-per-250-ms budget. Retry timestamps more than the maximum 30-second backoff ahead of the current clock are ignored. A smaller rollback can retain a bounded retry delay of up to 30 seconds. -- Commercial and military aircraft use the same high-level FLEET model regime: 2D billboards when zoomed out, optional glTF models when closer, controlled by the DISPLAY rail `3D` toggle and `Proximity` / `All` modes. Since 2026-08-16 (Hangar fleet) models are PER-CLASS: real CC-BY GLBs for light/bizjet/turboprop/widebody/helicopter/uav (`CLASS_MODEL_REAL` in `src/data/aircraftClass.js` — meters-baked, scale 1, per-model belly/radius; provenance in `public/models/README.md`), the shared `airplane.glb` for the remaining civilian classes, and the military layer maps weight classes (real GLBs / 747 heavies / `jet.glb` fastjets, per-model heading offsets, flat amber or emergency red). Textured civilians carry a HEAVY tint, not a light one: `MODEL_COLOR_BLEND_AMOUNT` is `0.94` in BOTH layers under Cesium's `ColorBlendMode.MIX`, so the class colour supplies 94% of the surface and the asset's own texture ~6%. The visual direction is clean light silhouettes with only a weak diffuse contribution from the approved textures, so liveries deliberately do NOT read. IR boost raises the blend to a full `1.0`. Under NVG/FLIR (map preset or Cockpit vision) models render unlit flat-white at full alpha and scene fog is disabled (fog otherwise blacks out distant models with the globe hidden); state restores on exit. +- Commercial and military aircraft use the same high-level FLEET model regime: 2D billboards when zoomed out, optional glTF models when closer, controlled by the DISPLAY rail `3D` toggle and `Proximity` / `All` modes. Every model is one of the project's procedural fleet models (`public/models/fleet/`, MIT, 121 GLBs, about 2 MB in all; `public/models/README.md`), chosen per aircraft by `aircraftModelSpec` (`src/data/fleetModels.js`): its ICAO type's entry in `types.json`, else the model of its `classifyAircraft` class for a type the map lacks, else its ADS-B emitter category's, else the default airliner. It is drawn LIFE SIZE: the type's published length over the model's built length (`src/layers/flights/modelSpec.js`, shared by Flights, Military Flights and Local ADS-B), and a grounded aircraft stands the model's scaled clearance above the ground. `classifyAircraft` still picks the 2D icons. The whole set loads once, up front, when a layer that draws it starts (`loadFleetModelSet`, which logs `[Fleet] N/121 models (X MB) loaded in N ms`); models are built from those bytes, never fetched mid-session, and an aircraft stays its billboard until the set has loaded. Textured civilians carry a HEAVY tint, not a light one: `MODEL_COLOR_BLEND_AMOUNT` is `0.94` in BOTH layers under Cesium's `ColorBlendMode.MIX`, so the class colour supplies 94% of the surface and the model's own vertex colour ~6%; military models are flat amber, or emergency red. IR boost raises the blend to a full `1.0`. Under NVG/FLIR (map preset or Cockpit vision) models render unlit flat-white at full alpha and scene fog is disabled (fog otherwise blacks out distant models with the globe hidden); state restores on exit. +- Vessels, Transit and Rail draw fleet models under the same DISPLAY rail `3D` toggle, at the same altitude ceiling, caps and add/keep radii as the aircraft (`FLEET_MODEL_POLICY`, `src/layers/flights/policy.js`; `createFleetModelLayer`, `src/data/fleetModelInstances.js`). A vessel's model follows its AIS ship type and is drawn at its reported length and beam (AIS static dimensions A+B and C+D, now carried on vessel rows as `length` and `beam`), standing at the sea surface, heading by true heading then course. A transit vehicle's model follows its mode, a train's its rail category and mode; both stand on the road or rail top they are drawn on (a monorail or gondola raised by `transit.elevation`) and are painted in their mode or category colour where the model's `_TINT` attribute marks it. Each vehicle's icon stays the visual until its model is loaded, ready and placed; a model is picked as its icon is. - **Aircraft models skip Cesium's dynamic environment map (2026-10-24).** Flights, Military Flights and Local ADS-B pass `environmentMapOptions: AIRCRAFT_ENVIRONMENT_MAP_OPTIONS` (`{ enabled: false }`, `src/data/aircraftModelLighting.js`) to every drawn model. Otherwise each `Model` renders the sky around it and reads it back from the GPU (`readPixels`) when it is first placed, and again whenever it moves more than 1 km, the scene's atmosphere settings change, or (under sunlight dynamic lighting) an hour of scene time passes (`DynamicEnvironmentMapManager`, Cesium 1.138). With the class colour supplying 94% of the surface, the models are lit from Cesium's default spherical harmonics instead, with no specular environment map, so the sky's model-lighting cube map no longer reaches aircraft. `src/data/aircraftModelLighting.test.mjs` pins the option at every drawn model call; the two preload calls only warm the glTF cache and are never drawn. - The DISPLAY-rail `3D` toggle is the user-facing activation path for both aircraft layers. Their small approved GLBs build their render resources without Cesium's frame-spread job queue, preventing continuous Photorealistic 3D Tiles streaming from starving model readiness; model caps, tracking, camera, and fallback billboards are unchanged. - **The `3D` toggle DEFAULTS ON in `proximity` on a first run.** Proximity is itself the budget — models appear only below `MODEL_ALT_CEIL_M` and only for the nearest `MODEL_MAX` in view — so the default costs nothing at globe scale, and `all` remains a deliberate opt-in. A fresh boot runs NO layer-state restoration (`LayerStateCoordinator.start()` returns early with neither a share payload nor stored state), so four independent initializers decide what a first-run operator sees and must agree: `booleanOption('models3d', 'e', true)` in `src/data/layerState.js`, `_models3dEnabled = true` in BOTH flight layers, `this._models3dEnabled = true` in `src/ui/aircraftDisplay.js`, and the `active` / `visible` classes on `#models3d-toggle` / `#models3d-mode-row` in `index.html`. All four are pinned together in `src/data/layerState.test.mjs`. Explicit state still wins: because the codec omits default-valued options, `models3d: false` is now what travels in a link (`lo=…f.e.0`) and restores OFF at both aircraft layers. **Consequence for returning users:** a stored `gev:layer-state:v2` blob is a FULL options snapshot, so a session that wrote one before this change carries `models3d:false` and keeps 3D off until the operator flips it (or clears the key) — the durable snapshot is treated as the recipient's own state, by design.