mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
engine.ground draws sets of polygons (with holes), discs, cells and
corridors draped on the surface, with fill and outline colours, order and
picks, written in bulk and committed. Every engine implements it:
- Cesium batches each set into one GroundPrimitive for fills and one
GroundPolylinePrimitive for outlines and pixel corridors, classified
against terrain and 3D tiles; sets that are not pickable stay out of the
pick pass.
- Navara draws them in one screen pass after tone mapping that finds each
pixel's ground from the scene depth and tests the items in its screen
cell; long rings are split into edge bins. Measured at Dubai against
SDK vector layers, the pass matched Cesium's frame time up to 10,000
polygons with a few megabytes instead of 70 to 146, drew exact colours
and outlines, and hid reliably across an engine switch, where vector
layers hidden with update({ show: false }) never hid. Dense corridors
remain its limit (10,000 cost 135 ms a frame).
- The fake engine keeps the same registry and CPU picks.
The contract suite checks it on both engines (Cesium 66/66, Navara 69/69).
Earthquakes and fire perimeters have unwired ground prototypes whose
drawing matches today's Cesium one (0.998 on Cesium, 0.976 on Navara).
docs/GLOBE-PRIMITIVES.md describes the primitives and when to use each.
176 lines
6.0 KiB
JavaScript
176 lines
6.0 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Show and hide of ground fills on Navara before and after an engine switch
|
|
* (Navara → Cesium → Navara, the parked view taken up again): Navara 0.2.1
|
|
* dropped the first `show: false` of a vector layer added right after a
|
|
* switch. For engine.ground's screen pass and the vector-layer prototype
|
|
* (hidden by layer.update with show false, or by deleting its layers): a
|
|
* magenta cell hidden right after its commit must not show, then show, then
|
|
* hide again. The pixels are read from the engine canvas.
|
|
*
|
|
* GOOGLE_MAPS_API_KEY= CESIUM_ION_TOKEN= npm run dev -- --port 4271
|
|
* node scripts/qa-ground-switch.mjs http://localhost:4271
|
|
*/
|
|
import puppeteer from 'puppeteer';
|
|
import { pickEngine, waitForEngine } from './qa-strict-harness.mjs';
|
|
|
|
const BLOCKED_HOSTS =
|
|
/(^|\.)(googleapis\.com|google\.com|gstatic\.com|cesium\.com)$/;
|
|
const origin = process.argv[2];
|
|
if (!origin) {
|
|
console.error('Usage: node scripts/qa-ground-switch.mjs <origin>');
|
|
process.exit(2);
|
|
}
|
|
const APPROACHES = ['pass', 'vector-update', 'vector-delete'];
|
|
|
|
async function trial(page, approach, round) {
|
|
return page.evaluate(
|
|
async (approach, round) => {
|
|
const { engine } = window.__godsEyeView;
|
|
const geo = await import('/src/geo/index.js');
|
|
const frame = () =>
|
|
new Promise((resolve) => {
|
|
const remove = engine.frames.postRender.addEventListener(() => {
|
|
remove();
|
|
resolve();
|
|
});
|
|
engine.render.request('ground-switch');
|
|
});
|
|
let ground = engine.ground;
|
|
let owner = null;
|
|
if (approach !== 'pass') {
|
|
const { createNavaraVectorGround } =
|
|
await import('/src/globe/navara/ground/vector.js');
|
|
owner = createNavaraVectorGround(engine.extensions.navara.view, {
|
|
hide: approach === 'vector-update' ? 'update' : 'delete',
|
|
});
|
|
ground = owner.ground;
|
|
}
|
|
const lon = 20 + round * 1.5 + APPROACH_INDEX[approach] * 0.5;
|
|
const spot = engine.projector.worldToWindow(
|
|
geo.cartesian3.fromDegrees(lon + 0.15, 10),
|
|
);
|
|
const magenta = () => {
|
|
const canvas = engine.host.canvas;
|
|
const copy = document.createElement('canvas');
|
|
copy.width = 16;
|
|
copy.height = 16;
|
|
const context = copy.getContext('2d');
|
|
const scale = canvas.width / canvas.clientWidth;
|
|
context.drawImage(
|
|
canvas,
|
|
(spot.x - 8) * scale,
|
|
(spot.y - 8) * scale,
|
|
16 * scale,
|
|
16 * scale,
|
|
0,
|
|
0,
|
|
16,
|
|
16,
|
|
);
|
|
const data = context.getImageData(0, 0, 16, 16).data;
|
|
let lit = 0;
|
|
for (let i = 0; i < data.length; i += 4)
|
|
if (
|
|
data[i] > 150 &&
|
|
data[i + 2] > 150 &&
|
|
data[i + 1] < Math.min(data[i], data[i + 2]) - 60
|
|
)
|
|
lit++;
|
|
return lit / 256;
|
|
};
|
|
// Frames until `want` (shown or not) holds, up to 4 s.
|
|
const until = async (want) => {
|
|
const started = performance.now();
|
|
while (performance.now() - started < 4000) {
|
|
await frame();
|
|
if (magenta() > 0.5 === want) return true;
|
|
}
|
|
return false;
|
|
};
|
|
const set = ground.createSet({
|
|
id: `switch-${approach}-${round}`,
|
|
kind: 'cell',
|
|
});
|
|
set.replace([
|
|
{
|
|
west: lon,
|
|
south: 9.85,
|
|
east: lon + 0.3,
|
|
north: 10.15,
|
|
fill: [1, 0, 1, 1],
|
|
},
|
|
]);
|
|
set.commit();
|
|
set.show(false);
|
|
// Hidden right after the commit: never drawn.
|
|
let drawnWhileHidden = false;
|
|
for (let index = 0; index < 90; index++) {
|
|
await frame();
|
|
if (magenta() > 0.5) drawnWhileHidden = true;
|
|
}
|
|
set.show(true);
|
|
const shows = await until(true);
|
|
set.show(false);
|
|
const hides = await until(false);
|
|
set.destroy();
|
|
owner?.destroy();
|
|
return { hiddenAfterCommit: !drawnWhileHidden, shows, hides };
|
|
},
|
|
approach,
|
|
round,
|
|
);
|
|
}
|
|
|
|
const browser = await puppeteer.launch({ headless: true });
|
|
let failed = false;
|
|
try {
|
|
const page = await browser.newPage();
|
|
await page.setViewport({ width: 1280, height: 800 });
|
|
page.on('pageerror', (error) => console.log(' page error:', error.message));
|
|
await page.setRequestInterception(true);
|
|
page.on('request', (request) => {
|
|
if (BLOCKED_HOSTS.test(new URL(request.url()).hostname)) request.abort();
|
|
else request.continue();
|
|
});
|
|
await page.evaluateOnNewDocument((approaches) => {
|
|
window.APPROACH_INDEX = Object.fromEntries(
|
|
approaches.map((name, index) => [name, index]),
|
|
);
|
|
}, APPROACHES);
|
|
await page.goto(
|
|
`${origin}/?welcome=0&engine=navara#v=2&lat=10&lon=21&alt=900000&heading=0&pitch=-90&map=osm&l=`,
|
|
);
|
|
await waitForEngine(page, 'navara', { timeout: 300_000 });
|
|
await page.evaluate(async () => {
|
|
const { appClock } = await import('/src/time/clock.js');
|
|
appClock.freeze(Date.UTC(2026, 10, 7, 11, 0));
|
|
});
|
|
const report = (label, result) => {
|
|
const ok = result.hiddenAfterCommit && result.shows && result.hides;
|
|
if (!ok) failed = true;
|
|
console.log(
|
|
` ${label}: hidden right after commit ${result.hiddenAfterCommit ? 'yes' : 'NO'}, shows ${result.shows ? 'yes' : 'NO'}, hides again ${result.hides ? 'yes' : 'NO'}`,
|
|
);
|
|
};
|
|
console.log('before a switch:');
|
|
for (const approach of APPROACHES)
|
|
report(approach, await trial(page, approach, 0));
|
|
await pickEngine(page, 'cesium');
|
|
await waitForEngine(page, 'cesium', { timeout: 300_000 });
|
|
await pickEngine(page, 'navara');
|
|
await waitForEngine(page, 'navara', { timeout: 300_000 });
|
|
await page.evaluate(async () => {
|
|
const geo = await import('/src/geo/index.js');
|
|
window.__godsEyeView.engine.camera.setView({
|
|
destination: geo.cartesian3.fromDegrees(21, 10, 900000),
|
|
});
|
|
});
|
|
console.log('after Navara → Cesium → Navara:');
|
|
for (const approach of APPROACHES)
|
|
report(approach, await trial(page, approach, 1));
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
process.exit(failed ? 1 : 0);
|