// Verify atlas orientation, imagery identity, unchanged decoded geometry and preview ownership. import assert from 'node:assert/strict' import {readFileSync,writeFileSync,existsSync} from 'node:fs' import {resolve,join,dirname} from 'node:path' import {createHash} from 'node:crypto' import jpeg from 'jpeg-js' import {parseGlb,accessorValues} from './instance-repeated-meshes.mjs' import {decodePrimitive} from './instance-safe-primitives.mjs' import {unpackInstanceBuffers} from './instance-buffer-codec.mjs' import {verifyMaterialClosure} from './verify-material-closure.mjs' import {Matrix4,Cartesian3,Cartographic} from 'cesium' const out=resolve(process.argv[2]),m=JSON.parse(readFileSync(join(out,'manifest.json'))),base=resolve(out,'../'+m.source),old=JSON.parse(readFileSync(join(base,'manifest.json'))),oldTree=JSON.parse(readFileSync(join(base,'tileset.json'))),transform=Matrix4.fromArray(m.transform) let textures=0,vertices=0,maxUV=0,rasterBytes=0,mipBytes=0,previewCount=0,coarseBytes=0 const hashes=new Map(),fine=new Set(),geohash=g=>createHash('sha256').update(JSON.stringify(g)).digest('hex') function verifyTexture(path,sourcePath,record){ const fin=unpackInstanceBuffers(readFileSync(path)),prev=parseGlb(readFileSync(sourcePath)),translation=fin.json.nodes[0].translation||[0,0,0] verifyMaterialClosure(fin.json,path) assert.deepEqual(fin.json.nodes,prev.json.nodes);assert.equal(fin.json.meshes[0].primitives.length,prev.json.meshes[0].primitives.length) for(let i=0;i{const error=Math.abs(v-want[a]);assert.ok(Number.isFinite(v)&&error<1e-6);maxUV=Math.max(maxUV,error)}) vertices++ } } if(record.textured){ const mat=fin.json.materials.find(mat=>mat.name==='SatelliteRoofAtlas'),tex=fin.json.textures[mat.pbrMetallicRoughness.baseColorTexture.index],image=fin.json.images[tex.source],v=fin.json.bufferViews[image.bufferView],bytes=fin.bin.subarray(v.byteOffset,v.byteOffset+v.byteLength) assert.equal(createHash('sha256').update(bytes).digest('hex'),record.rasterSha256) const raster=jpeg.decode(bytes);assert.equal(raster.width,record.width);assert.equal(raster.height,record.height);assert.ok(record.width<=256&&record.height<=256) assert.ok(record.rasterBytes<=1048576&&record.decodedTextureEstimate<=4194304) for(const s of record.sourceTiles){assert.ok(existsSync(s.path));if(!hashes.has(s.path))hashes.set(s.path,createHash('sha256').update(readFileSync(s.path)).digest('hex'));assert.equal(hashes.get(s.path),s.sha256)} // Independently reproject a top-left atlas pixel to known satellite tile addressing. const [low,high]=record.boundsENU,enu=new Cartesian3(low[0]+.5/record.width*(high[0]-low[0]),high[1]-.5/record.height*(high[1]-low[1]),record.planeHeight),world=Matrix4.multiplyByPoint(transform,enu,new Cartesian3()),geo=Cartographic.fromCartesian(world),n=2**record.zoom,x=Math.floor((geo.longitude/Math.PI+1)/2*n),y=Math.floor((1-Math.asinh(Math.tan(geo.latitude))/Math.PI)/2*n) assert.ok(record.sourceTiles.some(s=>s.path.endsWith(`/${record.zoom}/${x}/${n-1-y}.png`)),'TMS/north-up source mismatch') textures++;rasterBytes+=bytes.length;mipBytes+=record.decodedTextureEstimate } } for(const r of m.coarseRecords){const path=join(out,r.uri);coarseBytes+=readFileSync(path).length;verifyTexture(path,resolve(base,r.sourceUri),r) const {json,bin}=unpackInstanceBuffers(readFileSync(path)),table=json.extensions.EXT_structural_metadata.propertyTables[0],view=json.bufferViews[table.properties.region.values];assert.equal(bin.readUInt16LE(view.byteOffset),r.region) for(const p of json.meshes[0].primitives){const a=json.accessors[p.attributes._FEATURE_ID_0],v=json.bufferViews[a.bufferView];assert.equal(a.count,json.accessors[p.attributes.POSITION].count);assert.ok(bin.subarray(v.byteOffset,v.byteOffset+v.byteLength).every(x=>x===0))} } for(const r of m.regions){const path=join(out,r.url),rt=JSON.parse(readFileSync(path));assert.deepEqual(rt.root.transform,m.transform);assert.equal(rt.root.children.length,r.previewCount);assert.equal(r.previewContentUrls.length,r.previewCount) const expected=[];function collect(t){if(t.content?.uri.endsWith('-lod0.glb'))expected.push(resolve(base,t.content.uri));for(const c of t.children||[])collect(c)}collect(oldTree.root.children[r.id]) const fineSources=new Map((r.fineRecords||[]).map(f=>[resolve(dirname(path),f.uri),f])) const actual=[] for(let i=0;i