// Real render regression using the application engine and an entirely buried red 3D Tiles cube. // Run with COCKPIT_BASE and OCCLUSION_TERRAIN=local|ellipsoid; /gis/model-review mounts this directory. import assert from 'node:assert/strict' import { mkdirSync, writeFileSync } from 'node:fs' import { resolve } from 'node:path' import { PNG } from 'pngjs' import { Cartesian3, Transforms, Matrix4 } from 'cesium' const BASE = process.env.MODEL_REVIEW_BASE || 'http://127.0.0.1:8092' const USE_DEM = process.env.OCCLUSION_TERRAIN === 'local' const OUT = resolve('.scratch/cockpit-shell-panel-layout/verify/artifacts/t35/occlusion' + (USE_DEM ? '-dem' : '')) const FIXTURE = resolve('tools/city-massing/district/occlusion-fixture') mkdirSync(OUT, { recursive: true }) mkdirSync(FIXTURE, { recursive: true }) const p = [ -40, -110, -40, 40, -110, -40, 40, -30, -40, -40, -30, -40, -40, -110, 40, 40, -110, 40, 40, -30, 40, -40, -30, 40, ] const ix = [0, 1, 2, 0, 2, 3, 4, 6, 5, 4, 7, 6, 0, 4, 5, 0, 5, 1, 3, 2, 6, 3, 6, 7, 0, 3, 7, 0, 7, 4, 1, 5, 6, 1, 6, 2] const bin = Buffer.concat([Buffer.from(new Float32Array(p).buffer), Buffer.from(new Uint16Array(ix).buffer)]) const doc = { asset: { version: '2.0' }, scene: 0, scenes: [{ nodes: [0] }], nodes: [{ mesh: 0 }], meshes: [{ primitives: [{ attributes: { POSITION: 0 }, indices: 1, material: 0 }] }], materials: [ { extensions: { KHR_materials_unlit: {} }, pbrMetallicRoughness: { baseColorFactor: [1, 0, 0, 1] }, doubleSided: true, }, ], extensionsUsed: ['KHR_materials_unlit'], buffers: [{ byteLength: bin.length }], bufferViews: [ { buffer: 0, byteOffset: 0, byteLength: 96 }, { buffer: 0, byteOffset: 96, byteLength: 72 }, ], accessors: [ { bufferView: 0, componentType: 5126, count: 8, type: 'VEC3', min: [-40, -110, -40], max: [40, -30, 40] }, { bufferView: 1, componentType: 5123, count: 36, type: 'SCALAR' }, ], } const txt = Buffer.from(JSON.stringify(doc)) const js = Buffer.concat([txt, Buffer.alloc((4 - (txt.length % 4)) % 4, 32)]) const header = Buffer.alloc(20) header.write('glTF') header.writeUInt32LE(2, 4) header.writeUInt32LE(28 + js.length + bin.length, 8) header.writeUInt32LE(js.length, 12) header.write('JSON', 16) const bh = Buffer.alloc(8) bh.writeUInt32LE(bin.length) bh.write('BIN\0', 4) writeFileSync(resolve(FIXTURE, 'red.glb'), Buffer.concat([header, js, bh, bin])) writeFileSync( resolve(FIXTURE, 'tileset.json'), JSON.stringify({ asset: { version: '1.1' }, geometricError: 1000, root: { transform: Matrix4.toArray( Transforms.eastNorthUpToFixedFrame(Cartesian3.fromDegrees(110.39028164, 28.4436787)) ), boundingVolume: { box: [0, 0, -70, 40, 0, 0, 0, 40, 0, 0, 0, 40] }, geometricError: 0, content: { uri: 'red.glb' }, }, }) ) const puppeteer = ( await import( 'file:///C:/Users/ASUS/.dsh/profiles/desktop/node_modules/puppeteer-core/lib/puppeteer/puppeteer-core.js' ) ).default const browser = await puppeteer.launch({ executablePath: 'C:/Program Files/Google/Chrome/Application/chrome.exe', headless: true, defaultViewport: { width: 600, height: 600 }, args: ['--no-sandbox'], }) const results = [] try { const page = await browser.newPage() await page.goto(BASE + '/preview.html#automation', { waitUntil: 'domcontentloaded' }) await page.waitForFunction(() => typeof window.startPreview === 'function') await page.evaluate( (dem) => window.startPreview({ model: '/review-assets/district/occlusion-fixture/tileset.json', terrain: dem, imagery: false, }), USE_DEM ) await page.evaluate(() => { const { v, C } = window.scene35 v.camera.setView({ destination: C.Cartesian3.fromDegrees(110.39028164, 28.4436787, 750), orientation: { heading: 0, pitch: -Math.PI / 2, roll: 0 }, }) document.querySelector('#caption').style.display = 'none' }) await page.waitForFunction(() => window.scene35.v.scene.globe._surface._tilesToRender.length > 0, { timeout: 45000, }) async function capture(name, mode) { const state = await page.evaluate((mode) => { const { v, E, tiles } = window.scene35 if (mode === 'off') v.scene.globe.depthTestAgainstTerrain = false if (mode === 'on') v.scene.globe.depthTestAgainstTerrain = true if (mode === 'ground') E.applyViewModeLayers(v, tiles, { mode: 'ground', terrainAlpha: 1, tilesetAlpha: 1 }) if (mode === 'pipeline') E.applyViewModeLayers(v, tiles, { mode: 'pipeline', terrainAlpha: 0.45, tilesetAlpha: 0.4 }) return { depth: v.scene.globe.depthTestAgainstTerrain, translucent: v.scene.globe.translucency.enabled } }, mode) await page.waitForFunction(() => window.scene35.tiles[0].root.content != null, { timeout: 30000 }) const frames = await page.evaluate(() => window.frameCount) await page.waitForFunction((f) => window.frameCount > f + 40, {}, frames) const im = PNG.sync.read(Buffer.from(await page.screenshot({ path: resolve(OUT, name + '.png') }))) let red = 0 for (let y = 190; y < 410; y++) for (let x = 190; x < 410; x++) { const i = (y * im.width + x) * 4 if (im.data[i] > 100 && im.data[i] > im.data[i + 1] * 1.8 && im.data[i] > im.data[i + 2] * 1.8) red++ } const row = { name, ...state, redPixels: red } results.push(row) console.log(JSON.stringify(row)) return red } const initial = await capture('01-actual-initial', 'initial') const off = await capture('02-control-depth-off', 'off') const on = await capture('03-control-depth-on', 'on') const pipe = await capture('04-pipeline', 'pipeline') const ground = await capture('05-ground-return', 'ground') writeFileSync( resolve(OUT, 'report.json'), JSON.stringify({ terrain: USE_DEM ? 'local-dem' : 'ellipsoid', results }, null, 2) ) assert(off > 100, 'Positive control must show buried model with depth off') assert(on < 5, 'Depth-on must hide buried model') assert(initial < 5, 'Actual createViewer must not see through terrain') assert(pipe > 100, 'Explicit pipeline translucency must permit viewing below ground') assert(ground < 5, 'Ground mode must restore terrain occlusion') console.log('PASS actual terrain depth, negative control, pipeline and return') } finally { await browser.close() }