Skip to main content

qualia_core_db/domains/geospatial/
terrain_pipeline.rs

1//! DEM heightfield → terrain mesh → geodetically anchored scene coordinates (P1).
2
3use super::dem::{generate_terrain_mesh, TerrainMesh};
4use super::geodetic::{ecef_to_enu, lat_lon_alt_to_ecef};
5
6/// A terrain tile positioned at a geodetic anchor.
7#[derive(Debug, Clone)]
8pub struct GeodeticTerrainTile {
9    pub mesh: TerrainMesh,
10    /// Scene-space ENU origin for this tile (metres).
11    pub enu_origin: (f64, f64, f64),
12    /// Geodetic centre of the tile.
13    pub centre_lat: f64,
14    pub centre_lon: f64,
15    pub centre_alt_m: f64,
16}
17
18/// Build a terrain mesh from a heightfield and anchor it relative to a world origin.
19pub fn compile_terrain_tile(
20    heightfield: &[f32],
21    width: usize,
22    height: usize,
23    cell_size_m: f64,
24    centre_lat: f64,
25    centre_lon: f64,
26    centre_alt_m: f64,
27    world_origin_lat: f64,
28    world_origin_lon: f64,
29    world_origin_alt_m: f64,
30) -> GeodeticTerrainTile {
31    let mesh = generate_terrain_mesh(heightfield, width, height, cell_size_m);
32
33    let (cx, cy, cz) = lat_lon_alt_to_ecef(centre_lat, centre_lon, centre_alt_m);
34    let enu = ecef_to_enu(
35        cx,
36        cy,
37        cz,
38        world_origin_lat,
39        world_origin_lon,
40        world_origin_alt_m,
41    );
42
43    GeodeticTerrainTile {
44        mesh,
45        enu_origin: enu,
46        centre_lat,
47        centre_lon,
48        centre_alt_m,
49    }
50}
51
52use crate::container_10d::header::Container10dHeader;
53use crate::container_10d::integrity::seal_whole_file_crc32c;
54use crate::container_10d::mesh_section::{encode_mesh_section, encoded_len};
55use crate::container_10d::provenance_section::{encode_provenance_section, ProvenanceSidecar};
56use crate::container_10d::section::{encode_container, AlignmentTier, SectionInput, SectionType};
57use crate::render::assets::Mesh;
58use std::time::{SystemTime, UNIX_EPOCH};
59
60/// Compiles a DEM heightfield into a native `.10d` QuantizedMesh tensor file,
61/// wrapping it with CBOR-LD provenance for full compliance.
62pub fn compile_and_encode_10d_tile(
63    heightfield: &[f32],
64    width: usize,
65    height: usize,
66    cell_size_m: f64,
67    source_bytes: Vec<u8>,
68    licence: String,
69    cbor_ld_metadata: Vec<u8>,
70) -> Result<Vec<u8>, String> {
71    // 1. Generate standard TerrainMesh
72    let terrain = generate_terrain_mesh(heightfield, width, height, cell_size_m);
73
74    // 2. Convert to render::assets::Mesh
75    let mut triangles = Vec::with_capacity(terrain.indices.len() / 3);
76    for chunk in terrain.indices.chunks_exact(3) {
77        triangles.push([chunk[0], chunk[1], chunk[2]]);
78    }
79    let mut min = [f32::INFINITY; 3];
80    let mut max = [f32::NEG_INFINITY; 3];
81    for p in &terrain.vertices {
82        for i in 0..3 {
83            if p[i] < min[i] {
84                min[i] = p[i];
85            }
86            if p[i] > max[i] {
87                max[i] = p[i];
88            }
89        }
90    }
91    if terrain.vertices.is_empty() {
92        min = [0.0; 3];
93        max = [0.0; 3];
94    }
95
96    let render_mesh = Mesh {
97        positions: terrain.vertices,
98        triangles,
99        min,
100        max,
101    };
102
103    // 3. Encode QuantizedMesh Section
104    let mesh_need = encoded_len(render_mesh.positions.len(), render_mesh.triangles.len());
105    let mut mesh_payload = vec![0u8; mesh_need];
106    let mesh_size =
107        encode_mesh_section(&render_mesh, &mut mesh_payload).map_err(|e| e.to_string())?;
108    mesh_payload.truncate(mesh_size);
109
110    // 4. Build Provenance Sidecar
111    let now = SystemTime::now()
112        .duration_since(UNIX_EPOCH)
113        .unwrap()
114        .as_secs();
115    let mut sidecar = ProvenanceSidecar::new(source_bytes, "application/octet-stream", licence);
116    sidecar.semantic_metadata = cbor_ld_metadata;
117    sidecar.timestamp_epoch_s = now;
118    sidecar.version_hash = [0x42; 32];
119
120    let sidecar_need = sidecar.source_bytes.len()
121        + sidecar.source_media_type.len()
122        + sidecar.licence.len()
123        + sidecar.vc.len()
124        + sidecar.semantic_metadata.len()
125        + 1024;
126    let mut sidecar_payload = vec![0u8; sidecar_need];
127    let sidecar_size =
128        encode_provenance_section(&sidecar, &mut sidecar_payload).map_err(|e| e.to_string())?;
129    sidecar_payload.truncate(sidecar_size);
130
131    // 5. Encode 10D Container
132    let h = Container10dHeader::proposed();
133    let inputs = [
134        SectionInput {
135            section_type: SectionType::QuantizedMesh,
136            alignment_tier: AlignmentTier::Word,
137            stride: 0,
138            element_count: 0,
139            payload: &mesh_payload,
140        },
141        SectionInput {
142            section_type: SectionType::ProvenanceSidecar,
143            alignment_tier: AlignmentTier::Word,
144            stride: 0,
145            element_count: 0,
146            payload: &sidecar_payload,
147        },
148    ];
149
150    // Allocate 10D container size plus header padding
151    let mut out = vec![0u8; mesh_size + sidecar_size + 1024];
152    let n = encode_container(&h, &inputs, &mut out).map_err(|e| e.to_string())?;
153    out.truncate(n);
154
155    seal_whole_file_crc32c(&mut out);
156
157    Ok(out)
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn terrain_tile_has_enu_origin() {
166        let heights = vec![0.0f32; 9];
167        let tile = compile_terrain_tile(
168            &heights, 3, 3, 10.0, -33.8688, 151.2093, 0.0, -33.8688, 151.2093, 0.0,
169        );
170        assert_eq!(tile.mesh.vertices.len(), 9);
171        assert!((tile.enu_origin.0).abs() < 1.0);
172        assert!((tile.enu_origin.1).abs() < 1.0);
173    }
174}