| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364 |
- // 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<fin.json.meshes[0].primitives.length;i++){
- const p=fin.json.meshes[0].primitives[i],before=prev.json.meshes[0].primitives[i],g=decodePrimitive(fin.json,fin.bin,p)
- assert.equal(geohash(g),geohash(decodePrimitive(prev.json,prev.bin,before)),path+' coarse geometry changed')
- if(fin.json.materials[p.material].name!=='SatelliteRoofAtlas'){assert.deepEqual(fin.json.materials[p.material],prev.json.materials[before.material]);continue}
- assert.ok(record.textured);const uv=accessorValues(fin.json,fin.bin,p.attributes.TEXCOORD_0),[low,high]=record.boundsENU
- assert.equal(uv.length,g.positions.length/3)
- for(let j=0;j<uv.length;j++){
- const x=g.positions[j*3]+translation[0],y=-(g.positions[j*3+2]+translation[2]),want=[(x-low[0])/(high[0]-low[0]),(high[1]-y)/(high[1]-low[1])]
- uv[j].forEach((v,a)=>{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<r.previewRecords.length;i++){
- const record=r.previewRecords[i],tile=rt.root.children[i];assert.equal(tile.content.uri,record.uri);assert.ok(tile.boundingVolume.sphere);assert.equal(tile.geometricError,48);assert.equal(tile.refine,'REPLACE')
- verifyTexture(resolve(dirname(path),record.uri),resolve(base,record.sourceUri),record);previewCount++
- for(const c of tile.children){assert.equal(c.geometricError,0);assert.ok(!c.viewerRequestVolume);const p=resolve(dirname(path),c.content.uri),record=fineSources.get(p)
- assert.ok(!fine.has(p));fine.add(p)
- if(record){actual.push(resolve(base,record.sourceUri));if(!record.preservedOriginal)verifyTexture(p,resolve(base,record.sourceUri),record);else assert.equal(createHash('sha256').update(readFileSync(p)).digest('hex'),createHash('sha256').update(readFileSync(resolve(base,record.sourceUri))).digest('hex'))}
- else actual.push(p)}
- }
- assert.deepEqual(actual.sort(),expected.sort());assert.equal(r.contentCount,actual.length+r.previewRecords.length)
- }
- assert.equal(fine.size,3023);assert.equal(m.regions.length,113)
- const result={passed:true,regions:113,coarseFiles:m.coarseRecords.length,previewFiles:previewCount,fineFiles:fine.size,textures,texturedUVVertices:vertices,maxUVError:maxUV,rasterBytes,decodedMipEstimate:mipBytes,coarseBytes,sourceTiles:hashes.size,
- geometryUnchanged:true,sourceHashesMatch:true,northUpTmsVerified:true,previewOwnershipVerified:true,renderingValidated:false,fpsValidated:false}
- if(process.argv[3])writeFileSync(process.argv[3],JSON.stringify(result,null,2));console.log(JSON.stringify(result))
|