| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091 |
- <!doctype html>
- <html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>管段可见性验证</title><style>html,body,#map{margin:0;width:100%;height:100%;overflow:hidden;background:#04121f;color:white;font:14px sans-serif}aside{position:absolute;top:8px;left:8px;z-index:5;background:#04121fe8;padding:10px;max-width:90vw}button{padding:8px;margin:3px}pre{font:12px monospace;white-space:pre-wrap;max-height:24vh;overflow:auto}</style></head><body><div id="map"></div><aside><strong>真实管段可见性:颜色像素与管体就绪分开核验</strong><br><button id="load">加载真实管段近景</button><button id="scan">测量管体颜色像素</button><button id="ground">地面遮挡</button><button id="pipeline">地下透视</button><button id="fps">10秒运动FPS</button><button id="mapfps">仅地图FPS(隐藏管段)</button><button id="emptyfps">空场景FPS</button><button id="raf">浏览器帧调度</button><button id="far">远景800m</button><pre id="state">准备</pre><pre id="pixels"></pre><pre id="perf"></pre></aside><script type="module">
- window.CESIUM_BASE_URL='/cesium/'
- const transformed=await fetch('/src/modules/cockpit/services/cesiumPipeModels.js').then(r=>r.text())
- const cesiumUrl=transformed.match(/from ["']([^"']*\/cesium\.js[^"']*)["']/)?.[1]||'/.cache/deps/cesium.js'
- const {Cartesian3,Cartographic,HeadingPitchRange,sampleTerrainMostDetailed,TileMapServiceImageryProvider,ImageryLayer,UrlTemplateImageryProvider}=await import(cesiumUrl)
- const {createViewer,applyTerrain,applyViewModeLayers}=await import('/src/modules/cockpit/services/cesiumEngine.js')
- const {planViewMode}=await import('/src/modules/cockpit/domain/viewModes.js')
- const {syncPipeLayer,clearPipeLayer,PIPE_SOURCE_NAME}=await import('/src/modules/cockpit/services/cesiumPipeLayer.js')
- const {toPipeNetwork}=await import('/src/modules/cockpit/domain/pipeNetwork.js')
- const cfg=await fetch('/cockpit/config.json').then(r=>r.json())
- const viewer=createViewer(document.querySelector('#map'),{ready:true,kind:'tms',url:'Assets/Textures/NaturalEarthII'})
- await applyTerrain(viewer,{ready:true,url:cfg.map.terrain.url,requestVertexNormals:true})
- for(const overlay of cfg.map.overlays||[]) viewer.imageryLayers.add(new ImageryLayer(new UrlTemplateImageryProvider({url:overlay.url})))
- let target=null,latest=null,network=null,range=160
- const renderState=()=>{const {covered=[],...counts}=latest||{};document.querySelector('#state').textContent=JSON.stringify({target:target?.code,range,canvas:[viewer.canvas.width,viewer.canvas.height],...counts,covered:covered.length},null,0)}
- const look=(heading=0)=>{if(target)viewer.camera.lookAt(Cartesian3.fromDegrees(target.lon,target.lat,target.height),new HeadingPitchRange(heading,-Math.PI/4,range));viewer.scene.requestRender()}
- const mode=(value)=>{applyViewModeLayers(viewer,[],planViewMode(value));viewer.scene.requestRender()}
- document.querySelector('#load').onclick=async()=>{
- const envelope=await fetch(cfg.api.baseUrl+'/infrastructure/geometry').then(r=>r.json())
- network=toPipeNetwork(envelope.data)
- const pipe=network.pipes.find(p=>p.diameter>0&&p.industry==='DRAINAGE'&&/污水/.test(p.remark)&&!/雨水/.test(p.remark))
- const a=pipe.positions[0],b=pipe.positions.at(-1)
- const lon=(a[0]+b[0])/2,lat=(a[1]+b[1])/2
- const [ground]=await sampleTerrainMostDetailed(viewer.terrainProvider,[Cartographic.fromDegrees(lon,lat)])
- target={code:pipe.code,lon,lat,height:ground.height}
- look();mode('pipeline')
- await syncPipeLayer(viewer,network,{onModelChange:s=>{latest=s;renderState()}})
- }
- document.querySelector('#far').onclick=()=>{range=800;look();renderState()}
- document.querySelector('#ground').onclick=()=>mode('ground')
- document.querySelector('#pipeline').onclick=()=>mode('pipeline')
- document.querySelector('#scan').onclick=()=>{
- const source=viewer.dataSources.getByName(PIPE_SOURCE_NAME)[0]
- const wasShown=source.show;source.show=false
- const models=[]
- for(let i=0;i<viewer.scene.primitives.length;i++){const p=viewer.scene.primitives.get(i);if(p.id?.kind==='pipe-model-batch')models.push(p)}
- const prior=models.map(m=>m.show)
- const readPixels=()=>{const canvas=document.createElement('canvas');canvas.width=viewer.canvas.width;canvas.height=viewer.canvas.height;const ctx=canvas.getContext('2d',{willReadFrequently:true});ctx.drawImage(viewer.canvas,0,0);return ctx.getImageData(0,0,canvas.width,canvas.height).data}
- let frames=0,baseline=null
- for(const m of models)m.show=false
- const detach=viewer.scene.postRender.addEventListener(()=>{
- if(++frames<3){viewer.scene.requestRender();return}
- if(!baseline){baseline=readPixels();models.forEach((m,i)=>m.show=prior[i]);frames=0;viewer.scene.requestRender();return}
- const data=readPixels(),counts={red:0,blue:0,green:0,cyan:0,purple:0};let changed=0
- for(let i=0;i<data.length;i+=4){
- const r=data[i],g=data[i+1],b=data[i+2]
- if(Math.abs(r-baseline[i])+Math.abs(g-baseline[i+1])+Math.abs(b-baseline[i+2])<35)continue
- changed++
- if(r>130&&r>g*1.7&&r>b*1.4)counts.red++
- if(b>130&&b>r*1.7&&b>g*1.15)counts.blue++
- if(g>130&&g>r*1.7&&g>b*1.4)counts.green++
- if(g>130&&b>130&&r<90&&Math.abs(g-b)<80)counts.cyan++
- if(r>100&&b>130&&g<100&&b>g*1.4)counts.purple++
- }
- const result={target:target.code,modelOnly:true,backgroundSubtracted:true,canvas:[viewer.canvas.width,viewer.canvas.height],range,terrainAlpha:viewer.scene.globe.translucency.frontFaceAlpha,counts,changed,pass:counts.green>=80,expected:'sewage glTF green difference pixels >=80; entities/background excluded',visible:document.visibilityState}
- document.querySelector('#pixels').textContent=JSON.stringify(result,null,2)
- document.body.dataset.pixelPass=result.pass
- source.show=wasShown;detach();viewer.scene.requestRender()
- });viewer.scene.requestRender()
- }
- function benchmark(testMode='full') {
- const gl=viewer.canvas.getContext('webgl2')||viewer.canvas.getContext('webgl'),debug=gl.getExtension('WEBGL_debug_renderer_info')
- const environment={renderer:debug?gl.getParameter(debug.UNMASKED_RENDERER_WEBGL):gl.getParameter(gl.RENDERER),vendor:debug?gl.getParameter(debug.UNMASKED_VENDOR_WEBGL):gl.getParameter(gl.VENDOR),userAgent:navigator.userAgent,devicePixelRatio,focused:document.hasFocus(),hardwareConcurrency:navigator.hardwareConcurrency}
- const old=viewer.scene.requestRenderMode,globeShow=viewer.scene.globe.show,models=[],source=viewer.dataSources.getByName(PIPE_SOURCE_NAME)[0],sourceShow=source?.show
- for(let i=0;i<viewer.scene.primitives.length;i++){const p=viewer.scene.primitives.get(i);if(p.id?.kind==='pipe-model-batch')models.push([p,p.show])}
- if(testMode!=='full'){models.forEach(([p])=>p.show=false);if(source)source.show=false}
- if(testMode==='empty')viewer.scene.globe.show=false
- viewer.scene.requestRenderMode=false
- const times=[],start=performance.now(),focusStart=document.hasFocus();let hidden=false,focusLost=false
- const detach=viewer.scene.postRender.addEventListener(()=>{
- const now=performance.now();times.push(now);hidden ||= document.visibilityState!=='visible';focusLost ||= !document.hasFocus()
- if(now-start<10000){look((now-start)/10000*Math.PI/4);return}
- detach();viewer.scene.requestRenderMode=old;viewer.scene.globe.show=globeShow;models.forEach(([p,show])=>p.show=show);if(source)source.show=sourceShow
- const gaps=times.slice(1).map((t,i)=>t-times[i]).sort((a,b)=>a-b),seconds=(times.at(-1)-times[0])/1000
- document.querySelector('#perf').textContent=JSON.stringify({testMode,frames:times.length,seconds,fps:(times.length-1)/seconds,p95FrameMs:gaps[Math.floor(gaps.length*.95)],maxFrameMs:gaps.at(-1),canvas:[viewer.canvas.width,viewer.canvas.height],viewport:[innerWidth,innerHeight],visible:document.visibilityState,hidden,focusStart,focusLost,environment,motion:'45deg/10s'},null,2)
- viewer.scene.requestRender()
- })
- }
- document.querySelector('#fps').onclick=()=>benchmark('full')
- document.querySelector('#mapfps').onclick=()=>benchmark('map-only')
- document.querySelector('#emptyfps').onclick=()=>benchmark('empty')
- document.querySelector('#raf').onclick=()=>{
- const start=performance.now(),times=[]
- function frame(now){times.push(now);if(now-start<5000){requestAnimationFrame(frame);return}
- const span=(times.at(-1)-times[0])/1000
- document.querySelector('#perf').textContent=JSON.stringify({testMode:'native-rAF',frames:times.length,seconds:span,fps:(times.length-1)/span,visible:document.visibilityState,focused:document.hasFocus(),canvas:[viewer.canvas.width,viewer.canvas.height]},null,2)}
- requestAnimationFrame(frame)
- }
- document.querySelector('#state').textContent='引擎就绪;选择加载真实管段近景'
- </script></body></html>
|