import assert from 'node:assert/strict' import { readFileSync, writeFileSync, existsSync } from 'node:fs' import { resolve, join } from 'node:path' import { parseGlb } from './instance-repeated-meshes.mjs' import { normalizeConfig } from '../../src/modules/cockpit/services/cockpitConfig.js' import { planTilesets } from '../../src/modules/cockpit/domain/mapLayers.js' import { tilesetOptions } from '../../src/modules/cockpit/services/tilesetOptions.js' const dir = resolve(process.argv[2]),tree=JSON.parse(readFileSync(join(dir,'tileset.json'))),report=JSON.parse(readFileSync(join(dir,'manifest.json'))) assert.equal(tree.root.content,undefined,'empty root must not wait for entire city') assert.equal(tree.root.viewerRequestVolume,undefined) const sourceDir=resolve(dir,'../'+report.source),source=JSON.parse(readFileSync(join(sourceDir,'manifest.json'))) const expected=new Map(source.records.map(r=>[resolve(sourceDir,r.uri),r])),seen=new Set(),ids=new Set() let maxTriangles=0,maxVertices=0,coarseBytes=0,far=0,near=0,fine=0 for(const c of report.decisions)for(const id of c.buildingIds){assert.ok(!ids.has(id));ids.add(id)} function visit(t){ if(t.content){const path=resolve(dir,t.content.uri),r=expected.get(path);assert.ok(r&&existsSync(path));assert.ok(!seen.has(path));seen.add(path) const bytes=readFileSync(path),{json}=parseGlb(bytes) let triangles=0,vertices=0 for(const n of json.nodes)if(n.mesh!==undefined)for(const p of json.meshes[n.mesh].primitives){triangles+=json.accessors[p.indices].count/3;vertices+=json.accessors[p.attributes.POSITION].count} assert.ok(triangles<=20000&&vertices<=60000,path+' budget');maxTriangles=Math.max(maxTriangles,triangles);maxVertices=Math.max(maxVertices,vertices) if(r.level==='far'){far++;coarseBytes+=bytes.length;assert.equal(t.geometricError,report.errors.far);assert.deepEqual(t.viewerRequestVolume.sphere,[...t.boundingVolume.sphere.slice(0,3),t.boundingVolume.sphere[3]+report.coarseRequestMargin])} else{assert.equal(t.viewerRequestVolume,undefined);if(r.level==='near'){near++;assert.equal(t.geometricError,report.errors.near)}else{fine++;assert.equal(t.geometricError,0)}} }else assert.equal(t.viewerRequestVolume,undefined) if(t.children?.length){ assert.equal(t.refine,'REPLACE');assert.ok(t===tree.root||t.boundingVolume.sphere) if(t.content){ assert.ok(t.geometricError>0&&t.geometricError<1000000,'finite content error required') assert.ok(t.children.every(c=>c.content),'content parent must wait on renderable children, not empty wrappers') } } for(const c of t.children||[])visit(c) } visit(tree.root) assert.equal(ids.size,30172);assert.equal(seen.size,expected.size);assert.equal(far,report.far);assert.equal(near,report.near);assert.equal(fine,report.fine) const config=normalizeConfig({map:{tilesets:[{url:'/gis/example',progressiveRefinement:true,maximumScreenSpaceError:96}]}}) assert.deepEqual(config.issues,[]) const planned=planTilesets(config.config.map)[0],options=tilesetOptions(planned) assert.equal(planned.progressiveRefinement,true);assert.equal(options.skipLevelOfDetail,false);assert.equal(options.maximumScreenSpaceError,96) assert.equal(options.preloadWhenHidden,false);assert.equal(options.preloadFlightDestinations,false) assert.equal(options.cacheBytes,64*1024*1024);assert.equal(options.maximumCacheOverflowBytes,32*1024*1024) assert.equal(tilesetOptions(planTilesets(normalizeConfig({map:{tilesets:[{url:'old'}]}}).config.map)[0]).skipLevelOfDetail,true,'old deployments unchanged') const bad=normalizeConfig({map:{tilesets:[{url:'x',progressiveRefinement:'yes'}]}}) assert.ok(bad.issues.some(s=>s.includes('progressiveRefinement')));assert.equal(planTilesets(bad.config.map)[0].progressiveRefinement,false) // Nominal SSE examples (actual viewport/FOV/cache may change the distance). const height=1080,denominator=2*Math.tan(Math.PI/6),sse=options.maximumScreenSpaceError const result={passed:true,buildings:ids.size,content:seen.size,far,near,fine,maxTriangles,maxVertices,farBytes:coarseBytes, geometryChanged:false,allContentUrisMatchSource:true,finiteErrorPolicy:report.errors, nominal1080p60degFineDistanceM:report.errors.near*height/(denominator*sse),coarseRequestMargin:report.coarseRequestMargin, configPipelineValidated:true,renderingValidated:false,fpsValidated:false} if(process.argv[3])writeFileSync(process.argv[3],JSON.stringify(result,null,2));console.log(JSON.stringify(result))