build-roof-quality-pilot.mjs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536
  1. // Independent roof-quality pilot. No configured asset/index is changed.
  2. import assert from 'node:assert/strict'
  3. import {readFileSync,writeFileSync,mkdirSync,existsSync} from 'node:fs'
  4. import {resolve,join,dirname} from 'node:path'
  5. import {createHash} from 'node:crypto'
  6. import {textureCoarseRoofs} from './texture-coarse-roofs.mjs'
  7. import {parseGlb,accessorValues} from './instance-repeated-meshes.mjs'
  8. import {compressInstanceBuffers,unpackInstanceBuffers} from './instance-buffer-codec.mjs'
  9. import {decodePrimitive} from './instance-safe-primitives.mjs'
  10. import {tileRasterBudget} from './raster-budget.mjs'
  11. const dir=resolve('.scratch/cockpit-shell-panel-layout/verify/artifacts/t35/roof-quality-pilot'),root=resolve('tools/city-massing/city/cad-bim'),m=JSON.parse(readFileSync(join(root,'city-regions-v7/manifest.json'))),base=resolve(root,m.source)
  12. assert.ok(!existsSync(join(dir,'report.json')),'use a new output for subsequent pilot revisions');mkdirSync(dir,{recursive:true})
  13. const sha=bytes=>createHash('sha256').update(bytes).digest('hex'),choices=[{region:25,uri:'fine-27.glb'},{region:25,uri:'fine-0.glb'}],results=[]
  14. function image(bytes){const {json,bin}=unpackInstanceBuffers(bytes),mat=json.materials.find(x=>x.name==='SatelliteRoofAtlas'),t=json.textures[mat.pbrMetallicRoughness.baseColorTexture.index],im=json.images[t.source],v=json.bufferViews[im.bufferView];return {bytes:bin.subarray(v.byteOffset,v.byteOffset+v.byteLength),mimeType:im.mimeType}}
  15. for(const choice of choices){const region=m.regions.find(r=>r.id===choice.region),record=region.fineRecords.find(r=>r.uri===choice.uri);assert.ok(record.textured&&!record.preservedOriginal)
  16. const sourcePath=resolve(base,record.sourceUri),raw=readFileSync(sourcePath),currentPath=resolve(dirname(resolve(root,'city-regions-v7',region.url)),record.uri),current=readFileSync(currentPath),currentDecoded=unpackInstanceBuffers(current)
  17. const baseline=textureCoarseRoofs(raw,{maxDimension:64});assert.equal(baseline.report.rasterSha256,record.rasterSha256,'default processing must reproduce current roof raster bitwise')
  18. const latitude=28.4430329383677,sourceMetresPerPixel=Math.cos(latitude*Math.PI/180)*40075016.68557849/(2**19*256),options={maxDimension:512,metresPerPixel:sourceMetresPerPixel,sharpenAmount:.25,jpegQuality:92}
  19. const highRaw=textureCoarseRoofs(raw,{...options,sharpenAmount:0}),processed=textureCoarseRoofs(raw,options),compressed=compressInstanceBuffers(processed.bytes),after=unpackInstanceBuffers(compressed)
  20. assert.deepEqual(after.json.nodes,currentDecoded.json.nodes)
  21. const beforePs=currentDecoded.json.meshes[0].primitives,afterPs=after.json.meshes[0].primitives;assert.equal(afterPs.length,beforePs.length)
  22. let uvVertices=0
  23. for(let i=0;i<afterPs.length;i++){const a=afterPs[i],b=beforePs[i];assert.deepEqual(decodePrimitive(after.json,after.bin,a),decodePrimitive(currentDecoded.json,currentDecoded.bin,b),'all geometry/normal/index data unchanged')
  24. if(after.json.materials[a.material].name==='SatelliteRoofAtlas'){assert.deepEqual(accessorValues(after.json,after.bin,a.attributes.TEXCOORD_0),accessorValues(currentDecoded.json,currentDecoded.bin,b.attributes.TEXCOORD_0));uvVertices+=after.json.accessors[a.attributes.TEXCOORD_0].count}
  25. else assert.deepEqual(after.json.materials[a.material],currentDecoded.json.materials[b.material],'facade material unchanged')
  26. }
  27. const raster=image(compressed),budget=tileRasterBudget([raster],[{source:0}]),delta=processed.report.decodedTextureEstimate-record.decodedTextureEstimate;assert.equal(budget.decodedTextureBytes,processed.report.decodedTextureEstimate)
  28. for(const s of processed.report.sourceTiles)assert.equal(sha(readFileSync(s.path)),s.sha256)
  29. const stem=`region-${choice.region}-${choice.uri.slice(0,-4)}`,asset=join(dir,stem+'.glb');writeFileSync(asset,compressed)
  30. writeFileSync(join(dir,stem+'-current.jpg'),image(current).bytes);writeFileSync(join(dir,stem+'-source-density.jpg'),image(highRaw.bytes).bytes);writeFileSync(join(dir,stem+'-processed.jpg'),raster.bytes)
  31. results.push({region:choice.region,uri:choice.uri,sourcePath,sourceSha256:sha(raw),currentAssetSha256:sha(current),pilotAsset:asset,pilotAssetSha256:sha(compressed),uvVertices,geometryUnchanged:true,uvUnchanged:true,facadeMaterialsUnchanged:true,current:{width:record.width,height:record.height,decodedTextureEstimate:record.decodedTextureEstimate,metresPerPixel:record.boundsENU.map((_,a)=>(record.boundsENU[1][a]-record.boundsENU[0][a])/[record.width,record.height][a])},processed:processed.report,rasterBudget:budget,deltaDecodedTextureEstimate:delta})
  32. }
  33. const delta=results.reduce((s,r)=>s+r.deltaDecodedTextureEstimate,0),updated=m.regions.map(r=>({id:r.id,estimate:r.decodedGeometryBytes+(r.id===25?delta:0),contents:r.contentCount})),region=updated.find(r=>r.id===25),pairs=updated.filter(r=>r.id!==25&&r.contents+region.contents<=180).map(r=>({ids:[25,r.id],contents:r.contents+region.contents,estimate:r.estimate+region.estimate})).filter(r=>r.estimate<=96*1024*1024)
  34. assert.ok(region.estimate<96*1024*1024)
  35. const report={passed:true,configurationChanged:false,pilotNotConfigured:true,renderingValidated:false,sourceZoom:19,sourceMaximumZoom:19,processing:'same local satellite projection, density-preserving sampling capped512, restrained delta<=8 unsharp .25, JPEG92; no invented imagery',results,region25OldEstimate:m.regions[25].decodedGeometryBytes,region25PilotEstimate:region.estimate,addedDecodedTextureEstimate:delta,admissiblePairsWithRegion25:pairs.length,declaredCaps:{regions:2,contents:180,decodedBytes:96*1024*1024},caveat:'Array buildingIds metadata inherited per cell cannot identify exactly which building is in one fine GLB. Roof atlases may include off-roof pixels unused by UV geometry. Sharpening recovers no new measured detail.'}
  36. writeFileSync(join(dir,'report.json'),JSON.stringify(report,null,2));console.log(JSON.stringify({passed:true,pilotTiles:results.length,dimensions:results.map(r=>({uri:r.uri,old:r.current,new:[r.processed.width,r.processed.height]})),addedDecodedTextureEstimate:delta,region25PilotEstimate:region.estimate,configurationChanged:false}))