| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546 |
- // Actual decoded geometry gate for the four-building, unmerged offline pilot.
- import assert from 'node:assert/strict'
- import {readFileSync,writeFileSync} from 'node:fs'
- import {resolve,join} from 'node:path'
- import {createHash} from 'node:crypto'
- import {parseGlb} from './instance-repeated-meshes.mjs'
- import {decodePrimitive} from './instance-safe-primitives.mjs'
- import {tileRasterBudget} from './raster-budget.mjs'
- import {verifyMaterialClosure} from './verify-material-closure.mjs'
- const base=resolve('.scratch/cockpit-shell-panel-layout/verify/artifacts/t35/negative-estate-pilot'),dir=join(base,'export-v3'),plan=JSON.parse(readFileSync(join(base,'input-v2/seated-plan.json'))),manifest=JSON.parse(readFileSync(join(dir,'manifest.json'))),textureMap=JSON.parse(readFileSync(join(base,'input-v2/roof-textures.json')))
- const byId=new Map(plan.buildings.map(b=>[b.id,b])),seen=new Set(),rows=[];let decodedEstimate=0,minDot=1
- const area=r=>Math.abs(r.reduce((s,p,i)=>{const q=r[(i+1)%r.length];return s+p[0]*q[1]-q[0]*p[1]},0)/2)
- for(const cell of manifest.cells)for(const level of ['lod1','lod0']){
- const file=join(dir,cell[level].uri),bytes=readFileSync(file),{json,bin}=parseGlb(bytes);verifyMaterialClosure(json,file)
- const rasterBudget=tileRasterBudget((json.images||[]).map(im=>{const v=json.bufferViews[im.bufferView];return{bytes:bin.subarray(v.byteOffset||0,(v.byteOffset||0)+v.byteLength),mimeType:im.mimeType}}),json.textures||[]);decodedEstimate+=rasterBudget.decodedTextureBytes
- for(const node of json.nodes.filter(n=>n.mesh!==undefined)){
- const b=byId.get(node.name);assert.ok(b,'unexpected node '+node.name);assert.ok(!node.matrix&&!node.rotation&&!node.scale);assert.ok(!seen.has(level+b.id));seen.add(level+b.id)
- assert.ok(node.translation.every((v,a)=>Math.abs(v-[b.positionENU[0],b.positionENU[2],-b.positionENU[1]][a])<.002))
- const ring=b.footprintENU,top=b.positionENU[2]+b.heightM,bottom=b.foundationBottom,edges=new Map();let topArea=0,bottomArea=0,triangles=0,glassTriangles=0,doorTriangles=0,coreTriangles=0,maxRingError=0,roofTextured=false
- for(const p of json.meshes[node.mesh].primitives){const geom=decodePrimitive(json,bin,p),positions=geom.positions,normals=geom.normals,indices=geom.indices,mat=json.materials[p.material];triangles+=indices.length/3;decodedEstimate+=positions.length*8+indices.length*4
- if(mat.name.startsWith('Glass_'))glassTriangles+=indices.length/3
- if(mat.name.startsWith('Door_'))doorTriangles+=indices.length/3
- if(mat.name==='SatelliteRoof_'+b.id){roofTextured=true;const t=json.textures[mat.pbrMetallicRoughness.baseColorTexture.index],im=json.images[t.source],v=json.bufferViews[im.bufferView],payload=bin.subarray(v.byteOffset||0,(v.byteOffset||0)+v.byteLength);assert.equal(createHash('sha256').update(payload).digest('hex'),textureMap[b.id].imageSha256)}
- for(let i=0;i<indices.length;i+=3){const points=indices.slice(i,i+3).map(ix=>[positions[ix*3]+node.translation[0],-(positions[ix*3+2]+node.translation[2]),positions[ix*3+1]+node.translation[1]])
- const a=indices[i]*3,c=indices[i+1]*3,d=indices[i+2]*3,u=[0,1,2].map(k=>positions[c+k]-positions[a+k]),v=[0,1,2].map(k=>positions[d+k]-positions[a+k]),cross=[u[1]*v[2]-u[2]*v[1],u[2]*v[0]-u[0]*v[2],u[0]*v[1]-u[1]*v[0]],length=Math.hypot(...cross)
- assert.ok(length>1e-10,'degenerate '+b.id)
- for(const ix of indices.slice(i,i+3)){const normal=normals.slice(ix*3,ix*3+3);assert.ok(Math.abs(Math.hypot(...normal)-1)<.001);const dot=cross.reduce((s,v,k)=>s+v*normal[k],0)/length;minDot=Math.min(minDot,dot);assert.ok(dot>.95,'normal/winding '+b.id+' '+mat.name+' '+dot)}
- // Shell vertices are exactly at the surveyed ring's top or buried bottom.
- const shell=points.map(pt=>{const zKey=Math.abs(pt[2]-top)<.002?1:Math.abs(pt[2]-bottom)<.002?0:null;if(zKey===null)return null;let best=Infinity,index=-1;ring.forEach((r,k)=>{const e=Math.hypot(pt[0]-r[0],pt[1]-r[1]);if(e<best){best=e;index=k}});if(best>.002)return null;maxRingError=Math.max(maxRingError,best);return index+zKey*ring.length})
- if(shell.every(v=>v!==null)){
- coreTriangles++;for(let k=0;k<3;k++){const x=shell[k],y=shell[(k+1)%3],key=x<y?x+':'+y:y+':'+x;edges.set(key,(edges.get(key)||0)+1)}
- const ar=area(points.map(p=>p.slice(0,2)));if(shell.every(x=>x>=ring.length))topArea+=ar;if(shell.every(x=>x<ring.length))bottomArea+=ar
- }
- }
- }
- assert.ok(coreTriangles>0&&[...edges.values()].every(n=>n===2),'surveyed shell not closed '+b.id)
- assert.ok(Math.abs(topArea-area(ring))<.02&&Math.abs(bottomArea-area(ring))<.02,'caps miss survey area '+b.id)
- assert.ok(glassTriangles>0&&doorTriangles>=12,'facade/door missing '+b.id)
- if(level==='lod0')assert.ok(roofTextured,'roof texture missing')
- rows.push({id:b.id,level,triangles,glassTriangles,doorTriangles,coreTriangles,shellClosed:true,roofArea:topArea,surveyArea:area(ring),maxRingErrorM:maxRingError,roofTextured})
- }
- }
- assert.equal(seen.size,8);for(const b of plan.buildings)assert.ok(rows.find(r=>r.id===b.id&&r.level==='lod0').triangles>rows.find(r=>r.id===b.id&&r.level==='lod1').triangles)
- assert.ok(decodedEstimate<plan.caps.maxDecodedBytes);assert.equal(manifest.count,4);assert.equal(manifest.excludedUnfittedIds.length,0)
- const result={passed:true,buildings:4,cells:manifest.cellCount,files:manifest.cells.length*2,rows,minNormalDot:minDot,decodedResourcesEstimate:decodedEstimate,cap:plan.caps.maxDecodedBytes,footprintsPreserved:true,seatingPreserved:true,roofImagesHashMatch:true,configurationChanged:false,renderingValidated:false}
- writeFileSync(join(base,'export-v3-verification.json'),JSON.stringify(result,null,2));console.log(JSON.stringify(result))
|