| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172 |
- // Split verified coarse ownership into bounded independent detail regions.
- import assert from 'node:assert/strict'
- import {readFileSync,writeFileSync,mkdirSync,existsSync} from 'node:fs'
- import {resolve,join,relative} from 'node:path'
- import {addRegionMetadata} from './region-metadata.mjs'
- import {compressInstanceBuffers} from './instance-buffer-codec.mjs'
- import {parseGlb} from './instance-repeated-meshes.mjs'
- import {textureCoarseRoofs} from './texture-coarse-roofs.mjs'
- import {tileRasterBudget} from './raster-budget.mjs'
- const base=resolve('tools/city-massing/city/cad-bim/city-far-shell-v4'),out=resolve(process.argv[2]||'tools/city-massing/city/cad-bim/city-regions-v6')
- const withRoofs=process.argv.includes('--satellite-roofs')
- assert.ok(!existsSync(out),'use immutable generation')
- const tree=JSON.parse(readFileSync(join(base,'tileset.json'))),manifest=JSON.parse(readFileSync(join(base,'manifest.json'))),regions=[],coarse=[],coarseRecords=[],seen=new Set()
- mkdirSync(out,{recursive:true})
- function fineLeaves(t,list=[]){if(t.content?.uri?.endsWith('-lod0.glb'))list.push(t);for(const c of t.children||[])fineLeaves(c,list);return list}
- for(let id=0;id<tree.root.children.length;id++){
- const original=tree.root.children[id],leaves=fineLeaves(original)
- assert.ok(leaves.length>0&&leaves.length<=128,'region content budget')
- let decodedGeometryBytes=0
- for(const leaf of leaves){const p=resolve(base,leaf.content.uri);assert.ok(!seen.has(p),'fine repeated across regions');seen.add(p)
- const {json,bin}=parseGlb(readFileSync(p))
- if(json.images?.length){const images=json.images.map(image=>{const v=json.bufferViews[image.bufferView];return{bytes:bin.subarray(v.byteOffset||0,(v.byteOffset||0)+v.byteLength),mimeType:image.mimeType}});decodedGeometryBytes+=tileRasterBudget(images,json.textures||[]).decodedTextureBytes}
- for(const mesh of json.meshes)for(const primitive of mesh.primitives){
- for(const i of Object.values(primitive.attributes)){const a=json.accessors[i];decodedGeometryBytes+=a.count*({VEC2:2,VEC3:3,VEC4:4,SCALAR:1}[a.type])*4}
- decodedGeometryBytes+=json.accessors[primitive.indices].count*4
- }
- }
- const regionDir=join(out,'region-'+id);mkdirSync(regionDir)
- const fineRecords=[]
- const fine=leaves.map((t,index)=>{
- const path=resolve(base,t.content.uri),raw=readFileSync(path),{json}=parseGlb(raw)
- let uri=relative(regionDir,path).replaceAll('\\','/')
- if(withRoofs&&!json.images?.length){const result=textureCoarseRoofs(raw,{maxDimension:64}),name=`fine-${index}.glb`;writeFileSync(join(regionDir,name),compressInstanceBuffers(result.bytes));uri=name;decodedGeometryBytes+=(result.report.uvBytes||0)+(result.report.decodedTextureEstimate||0);fineRecords.push({uri:name,sourceUri:t.content.uri,...result.report})}
- else fineRecords.push({uri,sourceUri:t.content.uri,textured:false,preservedOriginal:true})
- return{...structuredClone(t),content:{uri},geometricError:0}
- })
- fine.forEach(t=>{delete t.children;delete t.viewerRequestVolume})
- const fineMap=new Map(leaves.map((t,i)=>[resolve(base,t.content.uri),fine[i]]))
- const previewRecords=[],previewTiles=[]
- if(withRoofs){
- const branches=original.content?.uri?.startsWith('shell-')?original.children:original.content?[original]:original.children
- for(let p=0;p<branches.length;p++){
- const branch=branches[p];assert.ok(branch.content)
- const raw=readFileSync(resolve(base,branch.content.uri)),{json}=parseGlb(raw)
- for(const mesh of json.meshes)for(const primitive of mesh.primitives){for(const i of Object.values(primitive.attributes)){const a=json.accessors[i];decodedGeometryBytes+=a.count*({VEC2:2,VEC3:3,VEC4:4,SCALAR:1}[a.type])*4}decodedGeometryBytes+=json.accessors[primitive.indices].count*4}
- const result=textureCoarseRoofs(raw,{maxDimension:128}),name=`preview-${p}.glb`
- decodedGeometryBytes+=(result.report.uvBytes||0)+(result.report.decodedTextureEstimate||0)
- writeFileSync(join(regionDir,name),compressInstanceBuffers(result.bytes))
- previewRecords.push({uri:name,sourceUri:branch.content.uri,...result.report})
- const childLeaves=fineLeaves(branch).map(t=>structuredClone(fineMap.get(resolve(base,t.content.uri))))
- childLeaves.forEach(t=>{delete t.children;delete t.viewerRequestVolume})
- previewTiles.push({boundingVolume:branch.boundingVolume,geometricError:48,refine:'REPLACE',content:{uri:name},children:childLeaves})
- }
- }
- const root={transform:tree.root.transform,boundingVolume:original.boundingVolume,geometricError:1e6,refine:'REPLACE',children:withRoofs?previewTiles:fine}
- writeFileSync(join(regionDir,'tileset.json'),JSON.stringify({asset:{version:'1.1'},geometricError:1e6,root}))
- const contents=original.content?[original]:original.children
- for(let i=0;i<contents.length;i++){
- const t=contents[i],uri=`coarse-${id}-${i}.glb`,raw=readFileSync(resolve(base,t.content.uri)),textured=withRoofs?textureCoarseRoofs(raw):{bytes:raw,report:{textured:false}},bytes=compressInstanceBuffers(addRegionMetadata(textured.bytes,id))
- writeFileSync(join(out,uri),bytes)
- coarse.push({boundingVolume:t.boundingVolume,geometricError:0,content:{uri}})
- coarseRecords.push({uri,region:id,sourceUri:t.content.uri,bytes:bytes.length,...textured.report})
- }
- const info={id,url:`region-${id}/tileset.json`,bounds:original.boundingVolume,contentCount:fine.length+previewRecords.length,decodedGeometryBytes,
- previewCount:previewRecords.length,previewRecords,previewContentUrls:previewRecords.map(p=>p.uri),
- contentUrls:[...fine.map(t=>t.content.uri),...previewRecords.map(p=>p.uri)],fineRecords,sourceCells:original.content?.uri?.startsWith('shell-')?manifest.records.find(r=>r.uri===original.content.uri).sourceCells:[original.extras.fallbackSourceCell]}
- regions.push(info)
- }
- assert.equal(seen.size,3023)
- writeFileSync(join(out,'coarse-tileset.json'),JSON.stringify({asset:{version:'1.1'},geometricError:1e6,root:{...tree.root,children:coarse}}))
- const report={transform:tree.root.transform,buildings:manifest.buildings,regions,coarseRecords,regionCount:regions.length,fineFiles:seen.size,source:'city-far-shell-v4',maxRegionContents:Math.max(...regions.map(r=>r.contentCount)),policy:'Coarse always active; bounded independent region fine loads. Metadata.region masks coarse only after all region contents loaded and visible. Pipeline loads zero detail regions.',runtimeValidated:false}
- writeFileSync(join(out,'manifest.json'),JSON.stringify(report,null,2));console.log(JSON.stringify({out,regions:regions.length,maxRegionContents:report.maxRegionContents,fineFiles:seen.size,coarseFiles:coarse.length}))
|