diagnose-far-shells.mjs 1.4 KB

12345678910111213141516171819
  1. // Offline rejection evidence. Never modifies served models.
  2. import {readFileSync,writeFileSync} from 'node:fs'
  3. import {join,resolve} from 'node:path'
  4. import {parseGlb} from './instance-repeated-meshes.mjs'
  5. import {decodePrimitive} from './instance-safe-primitives.mjs'
  6. const dir=resolve(process.argv[2]),m=JSON.parse(readFileSync(join(dir,'manifest.json'))),bad=[],cells=new Set()
  7. for(const r of m.records.filter(r=>r.level==='shell')){
  8. const {json,bin}=parseGlb(readFileSync(join(dir,r.uri)));let failures=0,minDot=1,degenerate=0
  9. for(const p of json.meshes[0].primitives){const g=decodePrimitive(json,bin,p)
  10. for(let i=0;i<g.indices.length;i+=3){const ids=g.indices.slice(i,i+3),ps=ids.map(j=>g.positions.slice(j*3,j*3+3)),a=ps[1].map((v,k)=>v-ps[0][k]),b=ps[2].map((v,k)=>v-ps[0][k]),cross=[a[1]*b[2]-a[2]*b[1],a[2]*b[0]-a[0]*b[2],a[0]*b[1]-a[1]*b[0]],len=Math.hypot(...cross)
  11. if(len<1e-10){degenerate++;continue}
  12. for(const j of ids){const n=g.normals.slice(j*3,j*3+3),dot=n.reduce((s,v,k)=>s+v*cross[k],0)/Math.hypot(...n)/len;minDot=Math.min(minDot,dot);if(dot<.9)failures++}
  13. }
  14. }
  15. if(failures||degenerate){r.sourceCells.forEach(c=>cells.add(c));bad.push({uri:r.uri,sourceCells:r.sourceCells,failures,degenerate,minDot})}
  16. }
  17. const result={failedGroups:bad.length,cells:[...cells],bad}
  18. if(process.argv[3])writeFileSync(process.argv[3],JSON.stringify(result,null,2))
  19. console.log(JSON.stringify(result))