qualia_core_db/domains/geospatial/
terrain_pipeline.rs1use super::dem::{generate_terrain_mesh, TerrainMesh};
4use super::geodetic::{ecef_to_enu, lat_lon_alt_to_ecef};
5
6#[derive(Debug, Clone)]
8pub struct GeodeticTerrainTile {
9 pub mesh: TerrainMesh,
10 pub enu_origin: (f64, f64, f64),
12 pub centre_lat: f64,
14 pub centre_lon: f64,
15 pub centre_alt_m: f64,
16}
17
18pub 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
60pub 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 let terrain = generate_terrain_mesh(heightfield, width, height, cell_size_m);
73
74 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 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 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 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 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}