| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566 |
- // Reuse verified cell ownership and immutable GLBs. Only rebuild the LOD index.
- // Empty root + finite SSE errors avoid whole-city readiness and hard fine-distance gates.
- import assert from 'node:assert/strict'
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'
- import { resolve, join, relative } from 'node:path'
- const root = resolve('tools/city-massing/city/cad-bim')
- const source = join(root, 'city-progressive-instanced-v1')
- const output = join(root, process.argv[2] || 'city-continuous-v8')
- assert.ok(!existsSync(output), 'use an immutable generation')
- const tree = JSON.parse(readFileSync(join(source, 'tileset.json'), 'utf8'))
- const manifest = JSON.parse(readFileSync(join(source, 'manifest.json'), 'utf8'))
- assert.equal(manifest.instancedCells, 0, 'index rebuild expects the verified original geometry')
- const records = new Map(manifest.records.map(r => [resolve(source, r.uri), r]))
- const errors = { far: 128, near: 48 }
- const coarseRequestMargin = 14000
- const stats = { far: 0, near: 0, fine: 0, contents: 0 }
- function adapt(tile) {
- const t = structuredClone(tile)
- if (tile.content) {
- const path = resolve(source, tile.content.uri), record = records.get(path)
- assert.ok(record && existsSync(path), path)
- t.content.uri = relative(output, path).replaceAll('\\', '/')
- stats[record.level]++; stats.contents++
- if (record.level === 'far') {
- t.geometricError = errors.far
- // Keep distant coarse retirement, preloaded before city overview approaches.
- const volume = t.boundingVolume.sphere
- assert.ok(volume)
- t.viewerRequestVolume = { sphere: [...volume.slice(0, 3), volume[3] + coarseRequestMargin] }
- } else {
- t.geometricError = record.level === 'near' ? errors.near : 0
- // Finite SSE is the request gate. A child outside a hard request sphere
- // would block traditional REPLACE and is requested anyway by base traversal.
- delete t.viewerRequestVolume
- }
- } else {
- t.geometricError = 1000000
- delete t.viewerRequestVolume
- }
- if (t.children?.length) {
- t.refine = 'REPLACE'
- assert.ok(t === tree.root || t.boundingVolume.sphere || !t.content)
- const children = t.content ? t.children.flatMap(child => {
- assert.ok(child.content || child.children?.length, 'empty leaf has no fallback')
- return child.content ? [child] : child.children
- }) : t.children
- t.children = children.map(adapt)
- }
- return t
- }
- const newRoot = adapt(tree.root)
- delete newRoot.content
- assert.equal(stats.contents, manifest.records.length)
- mkdirSync(output, { recursive: true })
- writeFileSync(join(output, 'tileset.json'), JSON.stringify({
- ...tree, asset: { version: '1.1', generator: 'Continuous cell replacement with finite SSE' }, root: newRoot,
- }, null, 2) + '\n')
- writeFileSync(join(output, 'manifest.json'), JSON.stringify({
- buildings: manifest.buildings, errors, coarseRequestMargin, ...stats,
- source: 'city-progressive-instanced-v1', geometryChanged: false, instancingEnabled: false,
- requiredTraversal: 'base', policy: 'Empty root; cell coarse -> bounded local coarse/fine. Finite SSE controls local requests; Parents remain until desired descendants are ready. Distant coarse has a +14000m request margin, bounded by the deployment whole-tree maximum distance. No new GLB, texture or geometry.',
- decisions: manifest.decisions.map(d => ({ cell: d.cell, buildingIds: d.buildingIds, wholeCellCoarse: d.wholeCellCoarse })),
- records: manifest.records.map(r => ({ ...r, uri: relative(output, resolve(source, r.uri)).replaceAll('\\', '/') })),
- runtimeValidated: false,
- }, null, 2) + '\n')
- console.log(JSON.stringify({ output, buildings: manifest.buildings, errors, ...stats, geometryChanged: false }))
|