verify-continuous-request-profile.mjs 5.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. // Installed traversal over the real index with synthetic ENU camera/readiness.
  2. // All-frustum-visible worst-case proxy. No requests sent, decoding, GPU or browser.
  3. import assert from 'node:assert/strict'
  4. import { readFileSync, writeFileSync } from 'node:fs'
  5. import { resolve, join } from 'node:path'
  6. import Traversal from '../../node_modules/@cesium/engine/Source/Scene/Cesium3DTilesetTraversal.js'
  7. import Base from '../../node_modules/@cesium/engine/Source/Scene/Cesium3DTilesetBaseTraversal.js'
  8. import Refine from '../../node_modules/@cesium/engine/Source/Scene/Cesium3DTileRefine.js'
  9. const dir=resolve(process.argv[2]),tree=JSON.parse(readFileSync(join(dir,'tileset.json'))),manifest=JSON.parse(readFileSync(join(dir,'manifest.json')))
  10. const records=new Map(manifest.records.map(r=>[r.uri,r])),height=1080,denominator=2*Math.tan(Math.PI/6),threshold=96
  11. const ts={_requestedTiles:[],_selectedTiles:[],_selectedTilesToStyle:[],_emptyTiles:[],memoryAdjustedScreenSpaceError:threshold,
  12. debugFreezeFrame:false,loadSiblings:false,hasExtension(){return false},_minimumPriority:{distance:Infinity,depth:Infinity,foveatedFactor:Infinity,reverseScreenSpaceError:Infinity},
  13. _maximumPriority:{distance:0,depth:0,foveatedFactor:0,reverseScreenSpaceError:0},_statistics:{visited:0},_cache:{touch(){}},_cullRequestsWhileMoving:false}
  14. let camera,readiness,number=0
  15. function sphere(volume){if(volume.sphere)return volume.sphere;const b=volume.box;return [...b.slice(0,3),Math.hypot(b[3],b[7],b[11])]}
  16. function make(json,parent){const s=sphere(json.boundingVolume),r=json.content?records.get(json.content.uri):null
  17. const t={tileset:ts,parent,children:[],refine:Refine.REPLACE,hasRenderableContent:!!json.content,hasEmptyContent:!json.content,contentExpired:false,_depth:parent?parent._depth+1:0,
  18. _visible:true,_inRequestVolume:true,_distanceToCamera:1,_foveatedFactor:0,_priorityReverseScreenSpaceError:0,_selectedFrame:-1,
  19. get isVisible(){return this._visible&&this._inRequestVolume},getScreenSpaceError(){return this._screenSpaceError},updateExpiration(){},updatePriority(){},
  20. content:{featurePropertiesDirty:false},contentVisibility(){return 1},record:r,
  21. updateVisibility(){const dist=Math.hypot(...s.slice(0,3).map((v,a)=>v-camera[a]));this._distanceToCamera=Math.max(0.01,dist-s[3]);this._screenSpaceError=json.geometricError*height/(this._distanceToCamera*denominator)
  22. this._visible=true;const gate=json.viewerRequestVolume?.sphere;this._inRequestVolume=!gate||Math.hypot(...gate.slice(0,3).map((v,a)=>v-camera[a]))<=gate[3]
  23. this.contentAvailable=!!r&&readiness.has(r.level);this.hasUnloadedRenderableContent=!!r&&!this.contentAvailable}}
  24. t.children=(json.children||[]).map(c=>make(c,t));return t}
  25. ts.root=make(tree.root,null)
  26. const original=Traversal.selectTile;Traversal.selectTile=t=>ts._selectedTiles.push(t)
  27. import { parseGlb } from './instance-repeated-meshes.mjs'
  28. const resourceStats=new Map()
  29. function resources(list){const total={files:0,triangles:0,vertices:0,primitives:0,bytes:0}
  30. for(const t of list){const uri=t.record.uri;if(!resourceStats.has(uri)){const data=readFileSync(resolve(dir,uri)),{json}=parseGlb(data),value={files:1,triangles:0,vertices:0,primitives:0,bytes:data.length};for(const n of json.nodes)if(n.mesh!==undefined)for(const p of json.meshes[n.mesh].primitives){value.triangles+=json.accessors[p.indices].count/3;value.vertices+=json.accessors[p.attributes.POSITION].count;value.primitives++}resourceStats.set(uri,value)}const value=resourceStats.get(uri);for(const k of Object.keys(total))total[k]+=value[k]}
  31. return total
  32. }
  33. function sample(label,pose,loaded){camera=pose;readiness=new Set(loaded);Base.selectTiles(ts,{frameNumber:++number,camera:{timeSinceMoved:1,positionWCDeltaMagnitude:0,positionWCDeltaMagnitudeLastFrame:0}})
  34. const tally=list=>list.reduce((o,t)=>{const k=t.record?.level||'empty';o[k]=(o[k]||0)+1;return o},{shell:0,far:0,near:0,fine:0})
  35. return {label,cameraENU:pose,readyLevels:loaded,requested:tally(ts._requestedTiles),selected:tally(ts._selectedTiles),selectedResources:resources(ts._selectedTiles)} }
  36. try{
  37. const overview=sample('overview cold',[0,-1000,10000],[])
  38. assert.equal(overview.requested.fine,0,'overview must not request citywide fine')
  39. assert.ok(overview.requested.far<=530&&overview.requested.near<200,'cold overview requests bounded coarse, not all3022parts')
  40. const waitingNear=sample('near, local coarse pending',[0,-1000,350],manifest.shellFiles?['shell']:['far'])
  41. if(manifest.shellFiles)assert.equal(waitingNear.selected.shell,manifest.shellFiles,'light shell must survive pending coarse descendants')
  42. else assert.equal(waitingNear.selected.far,manifest.far,'far content must survive pending near descendants')
  43. assert.equal(waitingNear.selected.near,0)
  44. const zoom=sample('near, fine pending',[0,-1000,350],['shell','far','near'])
  45. assert.ok(zoom.requested.fine>0&&zoom.requested.fine<manifest.fine,'fine requests local only')
  46. assert.ok(zoom.selected.shell+zoom.selected.far+zoom.selected.near>0,'pending fine needs coarse fallback')
  47. const ready=sample('near, fine ready',[0,-1000,350],['shell','far','near','fine'])
  48. assert.ok(ready.selected.fine>0&&ready.selected.shell+ready.selected.far+ready.selected.near>0,'local fine plus distant coarse coexist')
  49. const back=sample('overview again',[0,-1000,10000],['shell','far','near','fine'])
  50. assert.equal(back.selected.fine,0)
  51. const result={passed:true,actualCesiumBaseTraversal:true,cameraMath:'synthetic sphere-distance SSE at1080p60deg, every tile treated frustum-visible',samples:[overview,waitingNear,zoom,ready,back],requestsSent:false,renderingValidated:false,fpsValidated:false}
  52. if(process.argv[3])writeFileSync(process.argv[3],JSON.stringify(result,null,2));console.log(JSON.stringify(result))
  53. }finally{Traversal.selectTile=original}