verify-region-city.mjs 4.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
  1. import assert from 'node:assert/strict'
  2. import {readFileSync,writeFileSync} from 'node:fs'
  3. import {resolve,join} from 'node:path'
  4. import {parseGlb} from './instance-repeated-meshes.mjs'
  5. import {unpackInstanceBuffers} from './instance-buffer-codec.mjs'
  6. import {selectDetailRegions} from '../../src/modules/cockpit/services/regionLoading.js'
  7. import {decodePrimitive} from './instance-safe-primitives.mjs'
  8. import {createHash} from 'node:crypto'
  9. const out=resolve(process.argv[2]),m=JSON.parse(readFileSync(join(out,'manifest.json'))),base=resolve(out,'../'+m.source),tree=JSON.parse(readFileSync(join(base,'tileset.json'))),source=JSON.parse(readFileSync(join(base,'manifest.json')))
  10. assert.equal(m.regions.length,tree.root.children.length);assert.equal(m.regions.length,113)
  11. const seenFine=new Set(),seenCoarse=new Set()
  12. let coarseBytes=0,maxRegionBytes=0
  13. const signature=(json,bin,p)=>{const g=decodePrimitive(json,bin,p);return createHash('sha256').update(JSON.stringify({positions:g.positions,normals:g.normals,indices:g.indices,material:json.materials[p.material]})).digest('hex')}
  14. for(const r of m.regions){
  15. const path=join(out,r.url),rt=JSON.parse(readFileSync(path)),expected=[]
  16. function fine(t){if(t.content?.uri.endsWith('-lod0.glb'))expected.push(resolve(base,t.content.uri));for(const c of t.children||[])fine(c)}
  17. fine(tree.root.children[r.id]);assert.deepEqual(rt.root.transform,tree.root.transform);assert.deepEqual(rt.root.boundingVolume,r.bounds)
  18. assert.equal(r.contentCount,rt.root.children.length);assert.ok(r.contentCount<=128)
  19. const actual=rt.root.children.map(t=>resolve(path,'../'+t.content.uri))
  20. assert.deepEqual(actual.sort(),expected.sort());for(const p of actual){assert.ok(!seenFine.has(p));seenFine.add(p)}
  21. maxRegionBytes=Math.max(maxRegionBytes,r.decodedGeometryBytes)
  22. }
  23. for(const r of m.coarseRecords){const target=unpackInstanceBuffers(readFileSync(join(out,r.uri))),old=parseGlb(readFileSync(resolve(base,r.sourceUri)));assert.ok(!seenCoarse.has(r.uri));seenCoarse.add(r.uri);coarseBytes+=readFileSync(join(out,r.uri)).length
  24. assert.deepEqual(target.json.nodes,old.json.nodes,'coarse transforms changed')
  25. const ext=target.json.extensions.EXT_structural_metadata,table=ext.propertyTables[0],regionView=target.json.bufferViews[table.properties.region.values]
  26. assert.equal(target.bin.readUInt16LE(regionView.byteOffset),r.region)
  27. assert.equal(table.count,1);assert.equal(table.class,'BuildingRegion')
  28. assert.equal(target.json.meshes[0].primitives.length,old.json.meshes[0].primitives.length)
  29. for(let i=0;i<target.json.meshes[0].primitives.length;i++){const p=target.json.meshes[0].primitives[i],prev=old.json.meshes[0].primitives[i]
  30. assert.equal(signature(target.json,target.bin,p),signature(old.json,old.bin,prev),'geometry/material changed')
  31. const f=p.extensions.EXT_mesh_features.featureIds[0],a=target.json.accessors[p.attributes._FEATURE_ID_0],view=target.json.bufferViews[a.bufferView]
  32. assert.equal(f.attribute,0);assert.equal(f.featureCount,1);assert.equal(f.propertyTable,0);assert.equal(a.count,target.json.accessors[p.attributes.POSITION].count);assert.equal(a.type,'SCALAR')
  33. assert.equal(a.componentType,5125);assert.ok(target.bin.subarray(view.byteOffset,view.byteOffset+view.byteLength).every(b=>b===0),'feature ID not constant zero')
  34. }
  35. }
  36. assert.equal(seenFine.size,3023);assert.equal(seenCoarse.size,113)
  37. const samples=[]
  38. for(const pose of [[0,-1000,350],[0,-1000,50],[2000,1000,180],[0,-1000,10000]]){
  39. const regions=m.regions.map(r=>{const s=r.bounds.sphere;return{...r,retryAt:0,active:false,distance:Math.max(0,Math.hypot(...s.slice(0,3).map((v,a)=>v-pose[a]))-s[3])}})
  40. const ids=selectDetailRegions(regions),chosen=regions.filter(r=>ids.includes(r.id)),contents=chosen.reduce((s,r)=>s+r.contentCount,0),memory=chosen.reduce((s,r)=>s+r.decodedGeometryBytes,0)
  41. assert.ok(ids.length<=2&&contents<=180&&memory<=96*1024*1024)
  42. assert.deepEqual(selectDetailRegions(regions,{pipeline:true}),[])
  43. samples.push({cameraENU:pose,regions:ids,detailContentCap:contents,decodedGeometryEstimate:memory,pipelineRegions:0})
  44. }
  45. const coarseTree=JSON.parse(readFileSync(join(out,'coarse-tileset.json')))
  46. assert.ok(coarseTree.root.children.every(t=>t.geometricError===0&&!t.children),'coarse-only dataset must have no detail descendants')
  47. const result={passed:true,regions:m.regions.length,fineFiles:seenFine.size,coarseFiles:seenCoarse.size,maxRegionContents:m.maxRegionContents,maxRegionDecodedGeometryEstimate:maxRegionBytes,coarseBytes,
  48. indexedCoarseGeometryUnchanged:true,everyCoarseVertexFeatureZero:true,allFineUrisUnchanged:true,pipelineDetailAdmission:0,samples,renderingValidated:false,fpsValidated:false}
  49. if(process.argv[3])writeFileSync(process.argv[3],JSON.stringify(result,null,2));console.log(JSON.stringify(result))