Build the catalog from one layer manifest and attach one tools tree

src/app/layers/renderers.js now holds LAYER_MANIFEST, one entry per
layer in directory order: a display (the same render/display.js on every
engine, with the contract primitives it needs and, for CCTV and Global
Context, a Navara backend loaded by import()), a layer that draws itself
through the contract, or per-engine renderers for the layers not on a
display yet (weather, wind, Bhote Koshi, sea marks, ski pistes). A layer
is available where the engine has the primitives it needs. The Cesium
renderer table, CESIUM_BUILTIN, the registry and the Navara mode's map of
about 35 loaders go; the application loads a kind's renderers before it
builds the catalog.

The tools attach through one tree over the engine, in Cesium's order.
Each engine kind adds its own tools from one backend that reads its
extension once: the Cesium backend holds the viewer-only tools, the
Navara backend the annotation renderer, draw surface and file import
drawing.

Coupling: engine.extensions reads fall from 12 to 8, engine-name literal
lines from 73 to 66.
This commit is contained in:
German Khokhlov committed 2026-11-07 09:41:02 +04:00
1 parent f1ec9d4a22
commit 48885d9e61
46 files changed
+1057 -899

No files matched your search

+45 -12
View File
@@ -194,14 +194,25 @@ one application, and only the globe-bound parts move to the next engine.
corridors (a width in px or m), draped over terrain and 3D tiles. Each
item has its own fill, outline and `pickId`. A set has a draw `order`, and
shows, hides and replaces its items in bulk.
- A pixel corridor can take Cesium's old looks: a `glow`
(PolylineGlow's falloff), an alpha `pulse` and a flowing `dash` (the
annotation route's). The GPU works them out from the time each frame;
while a shown set has a pulse or a dash, the engine draws a frame after
every frame, as Cesium's continuous render did. Traffic's jam heat
lines glow and pulse, and the Directions route's dashes flow (it also
holds continuous render again while drawn).
- Cesium draws them with batched `GroundPrimitive` and
`GroundPolylinePrimitive`.
`GroundPolylinePrimitive`; a corridor with a look draws through one
material of its own (`GevGroundCorridor`), which drew the jam lines and
the route pixel for pixel as the old PolylineGlow and route flow
materials did.
- Navara draws them in one screen pass classified on the scene depth
(`src/globe/navara/ground/`).
(`src/globe/navara/ground/`). A corridor set is tiles of binned
segments (`corridors.js`): 10,000 corridors draw in about 12 ms still
and 15 ms moving, where Cesium takes 23 ms (the chunks before took 151
and 196 ms).
- Navara's vector layers were measured against that pass and lost
(`docs/GLOBE-PRIMITIVES.md`).
- No layer draws through `engine.ground` yet. Earthquakes and fire
perimeters have prototypes in `render/ground.js`.
- On November 7, 2026, `scripts/qa-ground-suite.mjs` (the ground checks
with pixels) passed 3 of 3 on both engines.
- **Rasters on the ground** (`engine.imagery`) are required of every engine
@@ -254,20 +265,42 @@ one application, and only the globe-bound parts move to the next engine.
contract has no primitive for yet, draw behind one seam,
`src/layers/awareness/render/pulseDisc.js` (`render/cesiumPulses.js`, a
material disc, on Cesium; `render/navaraPulses.js`, a surface pass that the
Navara mode loads and passes in).
layer manifest loads and passes in).
The Active Fires far-zoom heatmap is an `engine.imagery` layer, and Recent
Imagery draws its days through `engine.imagery` from its own
`render/display.js`. CCTV's monitor plane and viewshed volume, which the
contract has no primitive for yet, draw behind one seam,
`src/layers/cctv/render/meshes.js` (`render/cesiumMeshes.js` on Cesium;
`render/navaraMeshes.js`, which the Navara mode loads and passes in). Cesium
renderers are listed in `src/app/layers/renderers.js`
(`CESIUM_LAYER_RENDERERS`), Navara ones are loaded by
`registerNavaraLayerRenderers` in `src/app/navaraMode.js`; both register the
same display factory for a layer that has one. Navara
ground effects (heat, pulses, trails, pads, drapes) are declared with
`defineGroundEffect` (`src/layers/shared/render/navaraGroundEffect.js`);
`render/navaraMeshes.js`, which the layer manifest loads and passes in).
Navara ground effects (heat, pulses, trails, pads, drapes) are declared
with `defineGroundEffect` (`src/layers/shared/render/navaraGroundEffect.js`);
their insert order is pinned by `navaraGroundOrder.test.mjs`.
- **One layer manifest.** `LAYER_MANIFEST` in `src/app/layers/renderers.js`
says how each directory layer draws: its display with the contract
primitives it needs (and an engine-specific backend option keyed by engine
kind: CCTV's meshes and Global Context's pulses on Navara), a layer that
draws itself through the contract (Wildlife Sightings), or, without a
display, its drawing per engine kind (wind, the weather rasters and the
Bhote Koshi event and locator draw themselves on Cesium and through a
Navara renderer on Navara; Sea Marks and Ski Pistes drape themselves on
both). A layer is available on an engine when that engine has the
primitives it needs, or, without a display, when its entry lists that
kind; today every layer shows on both. The manifest's Navara backends
and renderers load through `import()` (`loadLayerRenderers(kind)`, awaited
by `src/standalone/application.js` before the engine's catalog), so
Cesium mode fetches none of them; `rendererFor(id, engine, options)`
answers synchronously after that. The layer list reads
`createLayerAvailability(kind)`.
- **One tools tree.** `src/app/tools.js` attaches the same tools to every
engine (`attachEngineTools`): the annotation board, Draw, Import, the
Recent Imagery box and panel, the render governor, the scope mask,
hidden-tab handling and voice. Each engine kind's tool backend says where
they draw; Cesium's also holds the tools that still drive its viewer
(release trim, tracked-entity hold, the render-loop suspension, the
embedded views), which read it through `engine.extensions.cesium?.viewer`
and so stay absent on Navara. Navara's backend (`navaraToolBackend` in
`src/app/navaraMode.js`, loaded with its mode) adds the daylight of a
restored view and the engine notice.
- **Cesium only on demand.** The production build loads `/cesium/Cesium.js`
when a Cesium globe starts or a Navara user turns on a feature that still
uses Cesium's maths or entity model (traffic, Street Level, the Bhote Koshi
+85 -9
View File
@@ -86,6 +86,31 @@ Item options:
- `drape` is `'both'` (the default), `'terrain'` or `'tiles'`. Cesium
honours it; Navara drapes on whatever surface it draws.
A `widthPx` corridor can also take the looks Cesium's ground polyline
materials gave it. The GPU works them out every frame from the time, so a
commit never rebuilds them:
| look | fields | Cesium's |
| ------- | ------------------------------------------------ | ---------------------------------------------------------------- |
| `glow` | `power`, `taper` (1: none) | `PolylineGlow`'s `glowPower` and `taperPower` |
| `pulse` | `periodMs`, `min`, `max` | the alpha times a factor from `min` to `max` and back, a sine |
| `dash` | `cells`, `duty`, `speed`, `gapAlpha`, `emission` | the annotation route's `GevRouteFlow`: dashes flowing to the end |
- `glow` brightens the line toward white along its centre and fades it to
nothing at its edges.
- `pulse` follows the page's monotonic clock (`performance.now()`), as the
old materials did, not the app clock.
- `dash` lays `cells` dashes along the whole line. Each is lit for its first
`duty`, with soft edges of 0.08 of a cell, and brightened by `emission`
times its colour. Between them the alpha is times `gapAlpha`. The dashes
move toward the line's end at `speed` cells a second.
- A shown set with a `pulse` or a `dash` makes the engine draw a frame after
every frame, as Cesium's continuous render did. `list()` reports it as
`animated`. A `glow` alone does not animate.
Traffic's jam heat lines glow (power 0.25) and pulse (alpha 0.35 to 0.75,
1.63 s). The Directions route's 64 dashes flow at 0.55 cells a second.
### How each engine draws the ground
**Cesium** (`src/globe/cesium/ground.js`) builds at most two batches per set:
@@ -94,7 +119,11 @@ Item options:
metre corridors);
- one `GroundPolylinePrimitive` for all the outlines and pixel corridors.
Both use per-instance colours and the set's `classificationType`. Cesium
Both use per-instance colours and the set's `classificationType`. A
corridor with a look draws in a `GroundPolylinePrimitive` of its own look
and colour, over the set's other batches. Its material does the glow, pulse
and dash. The engine writes its time before every frame, and asks for the
next frame after every frame while the set is shown. Cesium
builds them in workers; the set's `ready()` turns true once they are drawn.
A set that is not pickable stays out of the pick pass, so it never hides
what lies under it.
@@ -106,7 +135,7 @@ its screen cell:
- a disc against its radius;
- an area against its edges;
- a corridor against its distance to the line.
- a corridor tile against the corridor segments near the pixel.
The tests run in each item's own gnomonic plane, so great-circle edges stay
straight. Fills and outlines are anti-aliased across one pixel and blended
@@ -116,9 +145,38 @@ Large shapes get extra structure:
- An area with more than 32 edges gets a grid of bins. A pixel then tests
only the edges of its own bin.
- A long corridor is split into chunks of 4 segments.
- A corridor set becomes tiles, set-wide grids of bins
(`src/globe/navara/ground/corridors.js`). See below.
- The terrain heights under each item bound its screen box.
**Corridor tiles.** A set's lines are cut into pieces of at most 0.5°
along their great circles. The pieces fall into a tree of longitude and
latitude tiles, each with at most 20 000 pieces and at most 20° a side. A
tile is one pass item:
- a gnomonic plane of its own;
- the pieces that come near it, as segments in that plane;
- a pyramid of square bins. A bin of level z is 2^z times the finest bin.
It lists runs of segments, item by item in draw order: the segments that
reach within its margin. The margin is half a bin for the set's thinnest
corridor, more for a wider one, plus a width in metres.
The finest bin is sized so that it holds about two runs per piece. The
coarsest level has at most 8 bins across.
A pixel finds its ground in world space once. It takes the one tile of the
set whose cell holds it: the cells are tested with the meridians' normals
and the parallels' sines, so two neighbouring tiles never both draw a
pixel. The pixel then takes the finest level whose margin covers the
reach of the set's thinnest corridor, and tests only its bin's runs. Each
item takes its nearest segment there, and blends once, in item order. When
the pixel is wider than the coarsest margin covers (a tile under about 30
pixels), lines near a tile's edge can lose a little of a neighbour's
reach; the tile is a solid blur by then.
A commit packs the tiles once. Only the heights are sampled again when the
terrain loads later.
Picks are CPU picks at the ground under the pointer (`src/globe/ground.js`).
Navara's vector layers (`src/globe/navara/ground/vector.js`) were measured
@@ -178,6 +236,24 @@ Corridors (40-point lines, 2.5 px), still / moving:
At 10 000 corridors Cesium allocated 613 MB of WebGL memory, the pass 44 MB
and the vector layers about 70 MB.
The pass with corridor tiles, measured again with the pass before them on
the same afternoon (the machine busier, the empty scene 6.0–8.9 ms):
| N | Cesium | pass before | pass with tiles |
| ------ | -------------- | -------------- | --------------- |
| 1 000 | 12.8 / 13.9 ms | 19.8 / 24.9 ms | 9.1 / 12.6 ms |
| 10 000 | 23.1 / 23.2 ms | 151 / 196 ms | 12.1 / 15.4 ms |
| 15 000 | 31.8 / 31.4 ms | 226 / 289 ms | 17.7 / 19.7 ms |
The tiles hold about what the chunks held: 47 MB of textures at 10 000
corridors (46 MB before), 68 MB at 15 000 (69 MB). Packing takes longer:
the commit of 10 000 corridors went from 0.6 s to about 1.9 s, and 15 000
from 0.8 s to 3.0 s.
A pixel corridor's width is now measured across the line (the gradient of
the distance to it), so it is the same at any angle, as on Cesium. Before,
a diagonal line drew up to √2 wider.
Edge quality over the Hajar mountains, oblique view:
- **Cesium:** a sharp drape with its outline.
@@ -207,10 +283,9 @@ more on every frame the camera moves, whatever their count. They draw no
ground outlines and no metre widths. Their colours are lit, and hiding them
needs a rebuild.
Corridors are the pass's limit: about 1 000 lines draw at 18 ms, but 10 000
take 135 ms. A layer with more lines than that (Street Level's up to 14 400
sequences) needs a set-wide grid of bins first, as areas have, before it
moves to `engine.ground`.
Corridors were the pass's limit: 10 000 lines took 135 ms. With a
set-wide grid of bins (corridor tiles), 10 000 take 12 ms, under Cesium's
23 ms.
## Imagery: rasters on the ground
@@ -335,7 +410,8 @@ near the camera from one 8×8 atlas over the whole view.
- a line on the ground: `corridor`, with `widthM` when its width is a
real width (an outline in metres) and `widthPx` otherwise.
- Group items that change together into one set, and replace them in bulk.
A ground set is rebuilt on every commit, so an animated fill (a pulse, a
flowing route) is not a ground set yet.
A ground set is rebuilt on every commit, so a fill that changes over time
is not a ground set, except a pixel corridor's glow, pulse and dash, which
the engine animates.
- **Pixels on the ground** (tiles, a raster, a photo): an imagery layer.
Fade it with `setAlpha`, and give a new raster to `setSource`.