Merge branch 'main' into fix/adsblol-true-heading

This commit is contained in:
Sameh Khamis authored and GitHub committed 2026-11-14 09:00:30 -08:00
commit 58776a6976
14 files changed
+61 -61

No files matched your search

+2 -1
View File
@@ -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);
}
});
+19 -5
View File
@@ -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 };
}
+1 -1
View File
@@ -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
+1 -3
View File
@@ -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`);
}
});
-3
View File
@@ -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);
}
-4
View File
@@ -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);
+9 -11
View File
@@ -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,
-1
View File
@@ -700,7 +700,6 @@ export function createLocalAdsbLayer({
if (viewer.scene?.primitives) {
models = createLocalAdsbModels({
viewer,
color,
resolveAsset,
groundSnap: services.groundSnap?.createGroundSnap?.() || null,
...(loadModel ? { loadModel } : {}),
+4 -7
View File
@@ -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(
+8 -8
View File
@@ -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<object>} [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 },
-4
View File
@@ -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). */
+12 -11
View File
@@ -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,