// Seat estate modules on the original DEM; explicitly leave steep footprints for stepped construction. import { readFileSync, writeFileSync } from 'node:fs' import { Cartesian3, Matrix4 } from 'cesium' const dir = 'tools/city-massing/city/regular-estate/' const plan = JSON.parse(readFileSync(dir + 'plan.json')) const sampled = JSON.parse(readFileSync(dir + 'terrain-samples.json')) if (plan.samplesSha256 !== sampled.samplesSha256) throw new Error('DEM sample coordinates differ from the footprint plan') const inverse = Matrix4.inverseTransformation(Matrix4.fromArray(plan.transform), new Matrix4()) const local = (lon, lat, height) => Matrix4.multiplyByPoint(inverse, Cartesian3.fromDegrees(lon, lat, height), new Cartesian3()) const buildings = [], excluded = [] for (const module of plan.modules) { const heights = module.sampleIndices.map((i) => sampled.heights[i]) if (heights.some((h) => !Number.isFinite(h))) throw new Error(`Missing original DEM sample: ${module.id}`) const min = Math.min(...heights), max = Math.max(...heights), relief = max - min if (relief > 2.5) { excluded.push({ id: module.id, sourceId: module.sourceId, reason: 'DEM relief exceeds 2.5m; requires stepped construction', relief, }) continue } const floorHeight = max - 0.25 const floor = local(module.center.lon, module.center.lat, floorHeight) const bottom = local(module.center.lon, module.center.lat, min - 3) buildings.push({ ...module, positionENU: [floor.x, floor.y, floor.z], foundationBottom: bottom.z, terrainMin: min, terrainMax: max, terrainRelief: relief, floorHeight, groundControls: module.sampleIndices.map((i) => [plan.samples[i].lon, plan.samples[i].lat, sampled.heights[i]]), }) } const output = { ...plan, buildings, excluded, notice: plan.disclaimer + ' Steep modules are excluded from this straight-floor batch.', } writeFileSync(dir + 'seated-plan.json', JSON.stringify(output, null, 2)) console.log( JSON.stringify({ candidateModules: plan.modules.length, accepted: buildings.length, excluded: excluded.length, maxRelief: Math.max(...buildings.map((m) => m.terrainRelief), 0), }) )