| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- // Real Cesium/DEM screenshots, with camera range readback. No live config is modified.
- import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'
- import { resolve } from 'node:path'
- import { PNG } from 'pngjs'
- const BASE = process.env.MODEL_REVIEW_BASE || 'http://127.0.0.1:8092'
- const DIR = resolve('tools/city-massing/district/generated')
- const OUT = resolve('.scratch/cockpit-shell-panel-layout/verify/artifacts/t35/district')
- mkdirSync(OUT, { recursive: true })
- const plan = JSON.parse(readFileSync(resolve(DIR, 'placement-report.json')))
- 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: 1500, height: 1000 },
- args: ['--no-sandbox'],
- })
- const images = [],
- requests = [],
- errors = []
- const page = await browser.newPage()
- page.on('pageerror', (e) => errors.push(e.message))
- page.on('response', (r) => {
- if (r.url().endsWith('.glb')) requests.push({ url: r.url(), status: r.status() })
- })
- try {
- await page.goto(BASE + '/preview.html#automation', { waitUntil: 'domcontentloaded' })
- await page.waitForFunction(() => typeof window.startPreview === 'function')
- await page.evaluate(() => window.startPreview({
- models: [
- '/review-assets/district/lod-preview/tileset.json',
- '/review-assets/city/regular-estate/lod-preview/tileset.json',
- '/review-assets/city/oldtown/lod-preview/tileset.json',
- ],
- }))
- const apartment =
- plan.buildings.find((b) => b.sourceId === 'osm-1285336673') ||
- plan.buildings.find((b) => b.asset.includes('slab'))
- const tower = plan.buildings.find((b) => b.asset.includes('midrise'))
- const cases = [
- {
- id: '01-satellite-control',
- lon: 110.395,
- lat: 28.454,
- height: 135,
- range: 1500,
- pitch: -89,
- heading: 0,
- show: false,
- label: '原始卫星影像 · 屋顶对照',
- },
- {
- id: '02-neighborhood-overview',
- lon: 110.395,
- lat: 28.454,
- height: 135,
- range: 1300,
- pitch: -50,
- heading: 10,
- show: true,
- label: '示范街区 · 有来源的逐栋布置 · 未覆盖区域保留原影像',
- },
- {
- id: '03-near-apartment',
- ...apartment.center,
- height: apartment.floorHeight + 9,
- range: 230,
- pitch: -30,
- heading: 200,
- show: true,
- label: '多层住宅 · 230m 实际视距 · 门窗 / 阳台 / 屋檐',
- },
- {
- id: '04-near-tower',
- ...tower.center,
- height: tower.floorHeight + 18,
- range: 300,
- pitch: -25,
- heading: 170,
- show: true,
- label: '较高住宅 · 300m 实际视距 · 台阶屋顶 / 层次变化',
- },
- {
- id: '05-top-built',
- lon: 110.395,
- lat: 28.454,
- height: 135,
- range: 1500,
- pitch: -89,
- heading: 0,
- show: true,
- label: '同视角顶视 · 与原始影像轮廓核对',
- },
- {
- id: '06-near-facade',
- ...apartment.center,
- height: apartment.floorHeight + 8,
- range: 150,
- pitch: -22,
- heading: 200,
- show: true,
- label: '住宅立面 · 150m 实际视距 · 非特写近景',
- },
- {
- id: '07-mobile-near',
- ...apartment.center,
- height: apartment.floorHeight + 8,
- range: 230,
- pitch: -26,
- heading: 200,
- show: true,
- mobile: true,
- label: '窄屏样片 · 230m 实际视距',
- },
- ]
- for (const a of cases) {
- if (a.mobile) {
- await page.setViewport({ width: 430, height: 932 })
- await page.evaluate(() => window.scene35.v.resize())
- }
- await page.evaluate((a) => {
- const { C, v, tiles } = window.scene35
- tiles.forEach((t) => (t.show = a.show))
- v.camera.lookAtTransform(
- C.Transforms.eastNorthUpToFixedFrame(C.Cartesian3.fromDegrees(a.lon, a.lat, a.height)),
- new C.HeadingPitchRange(C.Math.toRadians(a.heading), C.Math.toRadians(a.pitch), a.range)
- )
- document.querySelector('#caption').textContent = a.label + ' | 立面与楼层为估计,非测绘数据'
- }, a)
- await page.waitForFunction(() => window.scene35.v.scene.globe._surface._tilesToRender.length > 0, {
- timeout: 45000,
- })
- if (a.show)
- await page.waitForFunction(() => window.scene35.tiles.some(tile => tile.root?.content && tile.tilesLoaded), {
- timeout: 45000,
- })
- const f = await page.evaluate(() => window.frameCount)
- await page.waitForFunction((f) => window.frameCount > f + 60, { timeout: 20000 }, f)
- const state = await page.evaluate(() => {
- const { v, tiles, E, C } = window.scene35
- return {
- range: C.Cartesian3.magnitude(v.camera.position),
- depth: v.scene.globe.depthTestAgainstTerrain,
- groundTiles: v.scene.globe._surface._tilesToRender.length,
- tiles: E.readTilesetDiagnostics(tiles),
- }
- })
- if (Math.abs(state.range - a.range) > 1) throw Error('Camera range was not applied')
- const file = resolve(OUT, a.id + '.png')
- const png = PNG.sync.read(Buffer.from(await page.screenshot({ path: file })))
- // Capture the same canvas with the model hidden; actual model pixels must differ in the central scene.
- let modelPixels = null
- if (a.show) {
- await page.evaluate(() => window.scene35.tiles.forEach((t) => (t.show = false)))
- const hiddenFrame = await page.evaluate(() => window.frameCount)
- await page.waitForFunction((f) => window.frameCount > f + 3, {}, hiddenFrame)
- const hidden = PNG.sync.read(Buffer.from(await page.screenshot()))
- modelPixels = 0
- for (let y = 60; y < png.height - 100; y++) for (let x = 10; x < png.width - 10; x++) {
- const i = (y * png.width + x) * 4
- if (Math.abs(png.data[i] - hidden.data[i]) + Math.abs(png.data[i + 1] - hidden.data[i + 1]) + Math.abs(png.data[i + 2] - hidden.data[i + 2]) > 45) modelPixels++
- }
- if (modelPixels < 100) throw Error('Model has no visible rendered canvas pixels at ' + a.id)
- await page.evaluate(() => window.scene35.tiles.forEach((t) => (t.show = true)))
- }
- images.push({ ...a, ...state, modelPixels, file })
- console.log(a.id, JSON.stringify(state))
- }
- writeFileSync(resolve(OUT, 'report.json'), JSON.stringify({ images, requests, errors }, null, 2))
- if (errors.length) throw Error(errors.join('\n'))
- } catch (error) {
- await page.screenshot({ path: resolve(OUT, 'failure.png') })
- throw error
- } finally {
- await browser.close()
- }
|