diff --git a/changelog.d/1731-aircraft-mesh-colours.md b/changelog.d/1731-aircraft-mesh-colours.md new file mode 100644 index 000000000..5443c3a1d --- /dev/null +++ b/changelog.d/1731-aircraft-mesh-colours.md @@ -0,0 +1,3 @@ +- Draw aircraft models in their own mesh colours instead of a 94% flat + white (amber for military) over the model; the tracked and emergency + colours now tint the mesh without hiding its paint (samehkhamis, #1731). diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index c651a95a8..59077f153 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -7429,9 +7429,9 @@ 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. Every model is one of the project's procedural fleet models (`public/models/fleet/`, MIT, 122 GLBs, about 2 MB in all; `public/models/README.md`), chosen per aircraft by `aircraftModelSpec` (`src/data/fleetModels.js`): the first of its ICAO type's callsign rules in `types.json` (`aircraft_callsign`) its callsign matches, else its type's entry, 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. An airport ground vehicle (feed type `ZVEH`) is a fire truck (`FIRE…`), pushback tug (`PUSH…`, `TUG…`), van (`DEICER…`, `SQUID…`, `SQB…`) or else a car by its callsign, and a ground station (`GRND`) is an 11.8 m radio mast; these models (catalog category `other`) are drawn at their built size, and such a contact with no altitude stands on the ground instead of at the airborne default (`src/layers/flights/records.js`). `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/122 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. +- 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, 122 GLBs, about 2 MB in all; `public/models/README.md`), chosen per aircraft by `aircraftModelSpec` (`src/data/fleetModels.js`): the first of its ICAO type's callsign rules in `types.json` (`aircraft_callsign`) its callsign matches, else its type's entry, 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. An airport ground vehicle (feed type `ZVEH`) is a fire truck (`FIRE…`), pushback tug (`PUSH…`, `TUG…`), van (`DEICER…`, `SQUID…`, `SQB…`) or else a car by its callsign, and a ground station (`GRND`) is an 11.8 m radio mast; these models (catalog category `other`) are drawn at their built size, and such a contact with no altitude stands on the ground instead of at the airborne default (`src/layers/flights/records.js`). `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/122 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. Every aircraft model draws its mesh's own colours (`aircraftModelBlend`, `src/data/aircraftModelLighting.js`): Cesium's `ColorBlendMode.HIGHLIGHT` with white, in Flights, Military Flights and Local ADS-B alike; the tracked colour (cyan, or amber in Military Flights) and the emergency red multiply into the mesh, so its paint still shows. IR boost draws them flat white (`MIX` at `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 layer mode (bus, tram, subway, rail or ferry; a vehicle of `unknown` mode keeps its icon), a train's its rail category and mode (rail, metro or tram). Neither the transit nor the rail sources carry a GTFS `route_type` or a finer mode (monorail, gondola, funicular, cable car), so the set's models for those and their `transit.elevation` lifts are not reached by any live source today. Both stand on the road or rail top they are drawn on, without the 1.5 m `GROUND_FLOOR_LIFT_M` their icons carry, and are painted in their mode or category colour where the model's `_TINT` attribute marks it. A vessel model is clipped at its waterline (one clipping plane per model, at the model origin's horizontal plane), so its hull below the water never shows through a plain globe, which Cesium does not depth-test models against. Under the NVG/FLIR styles (the Flights `irBoost` parameter) these models render unlit flat white, as aircraft models do. Each vehicle's icon stays the visual until its model is loaded, ready and placed; a model is picked as its icon is, and a vehicle drawn as its model keeps its selection card and its place in the detection overlay. -- **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; every model is built from the fleet set loaded up front, so there is no separate preload. +- **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). 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; every model is built from the fleet set loaded up front, so there is no separate preload. - 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. - **Consequence of the flip on the recorded tracked/fleet inversion:** the 150–800 km band where surrounding contacts draw as models while the SELECTED one is still a glyph is now what an operator sees WITHOUT arming anything. The inversion itself is unchanged and still deliberate (see `src/data/trackedModelRegime.js`); only its reachability changed. diff --git a/src/data/aircraftEmergencyAnalyst.test.mjs b/src/data/aircraftEmergencyAnalyst.test.mjs index 27deb2b46..f26c28abc 100644 --- a/src/data/aircraftEmergencyAnalyst.test.mjs +++ b/src/data/aircraftEmergencyAnalyst.test.mjs @@ -458,7 +458,8 @@ test('tint priorities cover normal, priority, emergency, tracking and Cockpit do ); flightState._trackedIcao = null; flightState.records.data.set('civil', { emergency: null }); - assert.equal(rendering._modelColor('civil'), normal); + // An ordinary model draws its mesh's own colours, whatever the billboard's. + assert.equal(rendering._modelColor('civil'), Cesium.Color.WHITE); } }); diff --git a/src/data/aircraftModelLighting.js b/src/data/aircraftModelLighting.js index c8051c67e..38725be93 100644 --- a/src/data/aircraftModelLighting.js +++ b/src/data/aircraftModelLighting.js @@ -1,3 +1,5 @@ +import * as Cesium from 'cesium'; + /** * Lighting options shared by every aircraft 3D model (Flights, Military * Flights, Local ADS-B). @@ -6,12 +8,24 @@ * the model and reads the result back from the GPU (`readPixels`) to light it, * when the model is first placed and again when it moves more than 1 km, when * the scene's atmosphere settings change, or every scene hour under sunlight - * lighting. Aircraft models take 94% of their surface from the class colour - * (`colorBlendAmount`), so that lighting contributes little; without it they - * are lit from Cesium's default spherical harmonics. Measured 2026-10-24 - * (Intel Arc, Google 3D, ~150 models in the scene): GPU per frame at rest - * 14.3-14.8 -> 12.1-12.5 ms. + * lighting. Without it they are lit from Cesium's default spherical + * harmonics. Measured 2026-10-24 (Intel Arc, Google 3D, ~150 models in the + * scene): GPU per frame at rest 14.3-14.8 -> 12.1-12.5 ms. */ export const AIRCRAFT_ENVIRONMENT_MAP_OPTIONS = Object.freeze({ enabled: false, }); + +/** + * How an aircraft model's colour meets its mesh. HIGHLIGHT multiplies, so + * white draws the mesh's own colours as built, and the tracked or emergency + * colour tints the mesh without hiding its paint. The IR boost draws the + * model flat white instead (MIX at 1, with its unlit shader). + * @param {boolean} irBoost + * @returns {{colorBlendMode: Cesium.ColorBlendMode, colorBlendAmount: number}} + */ +export function aircraftModelBlend(irBoost) { + return irBoost + ? { colorBlendMode: Cesium.ColorBlendMode.MIX, colorBlendAmount: 1 } + : { colorBlendMode: Cesium.ColorBlendMode.HIGHLIGHT, colorBlendAmount: 0 }; +} diff --git a/src/data/aircraftRecession.js b/src/data/aircraftRecession.js index 04c41e604..0cc012e68 100644 --- a/src/data/aircraftRecession.js +++ b/src/data/aircraftRecession.js @@ -243,7 +243,7 @@ export function applyAircraftBillboardTreatment({ /** * Apply layer-owned hue and composed aircraft alpha to an ambient glTF model without - * disturbing Cesium's existing MIX/colorBlendAmount presentation semantics. + * changing the model's colour blend mode or amount. * @param {object} input * @param {object} input.model * @param {{withAlpha:(alpha:number)=>object}} input.baseColor diff --git a/src/data/trackedReadout.test.mjs b/src/data/trackedReadout.test.mjs index 425a69ceb..a6b69c7e4 100644 --- a/src/data/trackedReadout.test.mjs +++ b/src/data/trackedReadout.test.mjs @@ -291,7 +291,7 @@ test('tracking layers write gevLabelModel and expose only their cached display p assert.equal(sources['satellites.js'].includes('_trackedEntity.label.text'), false); }); -test('civilian and military trail heads use the lower-centre model anchor and weak-texture tint', async () => { +test('civilian and military trail heads use the lower-centre model anchor', async () => { const files = await Promise.all(['flights.js', 'militaryFlights.js'].map(async (name) => ( [name, readLayerSource(new URL(`./${name}`, import.meta.url))] ))); @@ -300,7 +300,5 @@ test('civilian and military trail heads use the lower-centre model anchor and we (/const\s*head\s*=\s*(?:parts\.motion\.)?_trackedTrailCached\(\s*,?\s*\)\s*\|\|\s*(?:parts\.motion\.)?_trackedDisplayPosition\(\s*(?:flightState\.)?_trackedIcao,?\s*\);/).test(source), `${name} trail head uses the dedicated lower-centre model anchor`, ); - assert.ok(source.includes('const MODEL_COLOR_BLEND_AMOUNT = 0.94;'), - `${name} keeps diffuse texture contribution weak through code-side MIX`); } }); diff --git a/src/layers/flights/modelSpec.js b/src/layers/flights/modelSpec.js index 444a22702..2a4455b0e 100644 --- a/src/layers/flights/modelSpec.js +++ b/src/layers/flights/modelSpec.js @@ -4,7 +4,6 @@ import { aircraftModelSpec, fleetModelSet, } from '../../data/fleetModels.js'; -import { MODEL_COLOR_BLEND_AMOUNT } from './policy.js'; /** * What an aircraft draws with before the fleet model set has loaded: no @@ -22,7 +21,6 @@ export const UNLOADED_AIRCRAFT_MODEL_SPEC = Object.freeze({ bellyM: 0, elevationM: 0, tinted: false, - blendAmount: MODEL_COLOR_BLEND_AMOUNT, visualCenterNative: Object.freeze([0, 0, 0]), trailAnchorNative: Object.freeze([0, 0, 0]), key: 'unloaded', @@ -58,7 +56,6 @@ export function aircraftLayerModelSpec(aircraft, set = fleetModelSet()) { layerSpec = Object.freeze({ ...spec, bellyM: spec.clearanceM, - blendAmount: MODEL_COLOR_BLEND_AMOUNT, }); cache.set(key, layerSpec); } diff --git a/src/layers/flights/policy.js b/src/layers/flights/policy.js index 21a559769..3b89a26f6 100644 --- a/src/layers/flights/policy.js +++ b/src/layers/flights/policy.js @@ -85,10 +85,6 @@ export const MODEL_ALL_KEEP_M = 450000; export const MODEL_HEADING_OFFSET_DEG = 180; // every fleet model is nose -X: opposite Cesium heading 0 -// Owner launch-polish direction: models should read as clean light silhouettes, -// with only a weak diffuse contribution from the models' own colours. - -export const MODEL_COLOR_BLEND_AMOUNT = 0.94; export const CYAN_TRANSPARENT = Cesium.Color.CYAN.withAlpha(0); diff --git a/src/layers/flights/rendering.js b/src/layers/flights/rendering.js index 1ebbf495f..64d9064ff 100644 --- a/src/layers/flights/rendering.js +++ b/src/layers/flights/rendering.js @@ -45,7 +45,10 @@ import { STALE_ALPHA, displayDelayMs, } from './policy.js'; -import { AIRCRAFT_ENVIRONMENT_MAP_OPTIONS } from '../../data/aircraftModelLighting.js'; +import { + AIRCRAFT_ENVIRONMENT_MAP_OPTIONS, + aircraftModelBlend, +} from '../../data/aircraftModelLighting.js'; export function createRendering({ flightState, @@ -192,11 +195,12 @@ export function createRendering({ * @returns {void} */ - /** Model tint, mirroring the billboard color rules. */ + /** Model colour: the mesh's own (white), tinted cyan while tracked and red + * while broadcasting an emergency. */ function _modelColor(icao24) { if (icao24 === flightState._trackedIcao) return Cesium.Color.CYAN; - return _fleetBillboardColor(icao24); + return _isEmergency(icao24) ? EMERGENCY_TINT : Cesium.Color.WHITE; } /** The FLEET's 3D-model regime: models3d enabled AND the camera zoomed in past the altitude @@ -526,10 +530,7 @@ export function createRendering({ minimumPixelSize: MODEL_MIN_PX, scale: spec.scale, color: flightState._irBoost ? Cesium.Color.WHITE : _modelColor(icao24), - colorBlendMode: Cesium.ColorBlendMode.MIX, - // Launch presentation keeps the code-side tint dominant for every approved - // model; IR boost removes the remaining diffuse hint with flat UNLIT white. - colorBlendAmount: flightState._irBoost ? 1.0 : spec.blendAmount, + ...aircraftModelBlend(flightState._irBoost), customShader: flightState._irBoost ? flightState._IR_UNLIT_SHADER : undefined, @@ -748,10 +749,7 @@ export function createRendering({ minimumPixelSize: TRACKED_MODEL_MIN_PX, scale: trackedSpec.scale, color: flightState._irBoost ? Cesium.Color.WHITE : Cesium.Color.CYAN, - colorBlendMode: Cesium.ColorBlendMode.MIX, - // The tracked aircraft uses the same dominant light tint as the fleet; - // IR boost removes the remaining diffuse hint with flat UNLIT white. - colorBlendAmount: flightState._irBoost ? 1.0 : trackedSpec.blendAmount, + ...aircraftModelBlend(flightState._irBoost), customShader: flightState._irBoost ? flightState._IR_UNLIT_SHADER : undefined, diff --git a/src/layers/localAdsb/index.js b/src/layers/localAdsb/index.js index a8c62b2bc..7cf01031c 100644 --- a/src/layers/localAdsb/index.js +++ b/src/layers/localAdsb/index.js @@ -700,7 +700,6 @@ export function createLocalAdsbLayer({ if (viewer.scene?.primitives) { models = createLocalAdsbModels({ viewer, - color, resolveAsset, groundSnap: services.groundSnap?.createGroundSnap?.() || null, ...(loadModel ? { loadModel } : {}), diff --git a/src/layers/localAdsb/index.test.mjs b/src/layers/localAdsb/index.test.mjs index 431caf9b6..414bca52f 100644 --- a/src/layers/localAdsb/index.test.mjs +++ b/src/layers/localAdsb/index.test.mjs @@ -503,7 +503,7 @@ test('adsbdb type, operator and route reach the card for the selected aircraft o assert.equal(queries.length, 2, 'each key is asked for once'); }); -test('the DISPLAY 3D toggle gives local aircraft magenta class models', async (t) => { +test('the DISPLAY 3D toggle gives local aircraft their fleet models in their mesh colours', async (t) => { const receiver = fakeReceiver({ mode: 'adsb', connected: true }); const clock = { now: 100_000 }; const loads = []; @@ -548,13 +548,10 @@ test('the DISPLAY 3D toggle gives local aircraft magenta class models', async (t assert.equal(model.scale, spec.scale, 'drawn life size'); assert.equal(model.id, 'local-adsb:a0b702'); assert.ok( - Cesium.Color.equals( - model.color, - Cesium.Color.fromCssColorString('#ff4fd8'), - ), + Cesium.Color.equals(model.color, Cesium.Color.WHITE), + 'the mesh draws its own colours', ); - assert.equal(model.colorBlendMode, Cesium.ColorBlendMode.MIX); - assert.equal(model.colorBlendAmount, 0.94); + assert.equal(model.colorBlendMode, Cesium.ColorBlendMode.HIGHLIGHT); assert.equal(model.show, true); const heli = sources[0].entities.getById('local-adsb:a0b702'); assert.equal( diff --git a/src/layers/localAdsb/models.js b/src/layers/localAdsb/models.js index 9f6700847..307412a78 100644 --- a/src/layers/localAdsb/models.js +++ b/src/layers/localAdsb/models.js @@ -16,7 +16,10 @@ import { MODEL_PROX_ADD_M, MODEL_PROX_KEEP_M, } from '../flights/policy.js'; -import { AIRCRAFT_ENVIRONMENT_MAP_OPTIONS } from '../../data/aircraftModelLighting.js'; +import { + AIRCRAFT_ENVIRONMENT_MAP_OPTIONS, + aircraftModelBlend, +} from '../../data/aircraftModelLighting.js'; const specOf = (marker) => aircraftLayerModelSpec({ @@ -31,15 +34,14 @@ const specOf = (marker) => * Mirrors the public Flights fleet: models only below the altitude ceiling, * the same proximity/all caps and add/keep radii, the same visible-first * eligibility (`selectModelEligible`), the same fleet model per type, life - * size (`aircraftLayerModelSpec`) and the same tint path (a MIX colour blend, here - * magenta, as Flights tints military amber). Each model owns its matrix. A + * size (`aircraftLayerModelSpec`) and the same colours: the mesh's own + * (`aircraftModelBlend`). Each model owns its matrix. A * billboard stays the visual until its model is loaded AND placed; a grounded * aircraft's model rides the one-shot ground snap plus the class belly * offset, and without a resolved ground it stays a billboard. Every fleet * model loads once, up front, when the layer starts (`loadFleetModelSet`). * @param {object} options * @param {object} options.viewer Cesium viewer. - * @param {Cesium.Color} options.color Model tint. * @param {(url: string) => string} options.resolveAsset * @param {object|null} [options.groundSnap] `createGroundSnap()` instance. * @param {(options: object) => Promise} [options.loadModel] @@ -47,7 +49,6 @@ const specOf = (marker) => */ export function createLocalAdsbModels({ viewer, - color, resolveAsset, groundSnap = null, loadModel = (options) => Cesium.Model.fromGltfAsync(options), @@ -104,9 +105,8 @@ export function createLocalAdsbModels({ environmentMapOptions: AIRCRAFT_ENVIRONMENT_MAP_OPTIONS, minimumPixelSize: MODEL_MIN_PX, scale: spec.scale, - color, - colorBlendMode: Cesium.ColorBlendMode.MIX, - colorBlendAmount: spec.blendAmount, + color: Cesium.Color.WHITE, + ...aircraftModelBlend(false), id, }, { resolveAsset, load: loadModel }, diff --git a/src/layers/military/policy.js b/src/layers/military/policy.js index 7a62bc92d..ac23188ff 100644 --- a/src/layers/military/policy.js +++ b/src/layers/military/policy.js @@ -107,10 +107,6 @@ export const MODEL_ALL_KEEP_M = 450000; export const MODEL_HEADING_OFFSET_DEG = 180; // every fleet model is nose -X: opposite Cesium heading 0 -// Preserve the layer's amber identity while reducing the models' own colours -// to a weak diffuse hint, matching civilian launch presentation. - -export const MODEL_COLOR_BLEND_AMOUNT = 0.94; /** Amber fade target for the tracked billboard once the model takes over (mirrors flights.js CYAN_TRANSPARENT). */ diff --git a/src/layers/military/rendering.js b/src/layers/military/rendering.js index a63955a36..7e19b4567 100644 --- a/src/layers/military/rendering.js +++ b/src/layers/military/rendering.js @@ -34,7 +34,6 @@ import { MODEL_PROX_KEEP_M, IR_RELOAD_BATCH, MODEL_MIN_PX, - MODEL_COLOR_BLEND_AMOUNT, TRACKED_MODEL_MIN_PX, TRACKED_MODEL_MAX_PX, FLEET_DR_INTERVAL_MS, @@ -43,7 +42,10 @@ import { COURSE_MAX_DPS, STALE_ALPHA, } from './policy.js'; -import { AIRCRAFT_ENVIRONMENT_MAP_OPTIONS } from '../../data/aircraftModelLighting.js'; +import { + AIRCRAFT_ENVIRONMENT_MAP_OPTIONS, + aircraftModelBlend, +} from '../../data/aircraftModelLighting.js'; export function createRendering({ flightState, @@ -81,7 +83,6 @@ export function createRendering({ const base = aircraftLayerModelSpec(meta); spec = Object.freeze({ ...base, - blendAmount: MODEL_COLOR_BLEND_AMOUNT, headingOffsetDeg: MODEL_HEADING_OFFSET_DEG, }); // Not cached before the model set loads: the answer changes once it has. @@ -152,11 +153,15 @@ export function createRendering({ const _iconKind = (icao24, klass) => tr3bIconKind(icao24, klass, { hot: flightState._irBoost }); - /** Model tint, mirroring the billboard color rules (amber instead of cyan/white). */ + /** Model colour: the mesh's own (white), tinted amber while tracked and red + * while broadcasting an emergency. */ function _modelColor(icao24) { if (icao24 === flightState._trackedIcao) return TRACKED_ICON_COLOR; - return _fleetBillboardColor(icao24); + const info = flightState.records.data.get(icao24); + return isEmergency(info?.emergency) + ? EMERGENCY_ICON_COLOR + : Cesium.Color.WHITE; } /** Fleet (untracked) tint: red while the latest poll broadcast an emergency, @@ -443,9 +448,7 @@ export function createRendering({ minimumPixelSize: MODEL_MIN_PX, scale: spec.scale, color: flightState._irBoost ? Cesium.Color.WHITE : _modelColor(icao24), - colorBlendMode: Cesium.ColorBlendMode.MIX, - // near self-illuminated tint so planes read uniform near AND far; IR boost → flat UNLIT white (hot) - colorBlendAmount: flightState._irBoost ? 1.0 : MODEL_COLOR_BLEND_AMOUNT, + ...aircraftModelBlend(flightState._irBoost), customShader: flightState._irBoost ? flightState._IR_UNLIT_SHADER : undefined, @@ -662,9 +665,7 @@ export function createRendering({ minimumPixelSize: TRACKED_MODEL_MIN_PX, scale: trackedSpec.scale, color: flightState._irBoost ? Cesium.Color.WHITE : TRACKED_ICON_COLOR, - colorBlendMode: Cesium.ColorBlendMode.MIX, - // near self-illuminated tint so planes read uniform near AND far; IR boost → flat UNLIT white (hot) - colorBlendAmount: flightState._irBoost ? 1.0 : MODEL_COLOR_BLEND_AMOUNT, + ...aircraftModelBlend(flightState._irBoost), customShader: flightState._irBoost ? flightState._IR_UNLIT_SHADER : undefined,