1use qualia_core_db::container_10d::provenance_section::ProvenanceSidecar;
2use qualia_core_db::render::assets::Mesh;
3use qualia_core_db::render::compile_10d::compile_mesh_to_10d_with_provenance;
4
5use super::layers::{
6 catalog::{find_layer, LayerDefinition, LayerSource},
7 mesh_gen, nasa_gibs, starfield,
8};
9
10pub struct CompiledLayerAsset {
11 pub layer_id: String,
12 pub container_10d: Vec<u8>,
13 pub vertex_count: u32,
14 pub triangle_count: u32,
15 pub positions: Vec<[f32; 3]>,
16 pub colors: Vec<[f32; 4]>,
17 pub indices: Vec<u32>,
18 pub source_format: String,
19 pub license: String,
20}
21
22pub async fn download_and_compile_layer(
23 layer_id: &str,
24 resolution: u32,
25) -> Result<CompiledLayerAsset, String> {
26 let layer = find_layer(layer_id).ok_or_else(|| format!("Unknown layer: {layer_id}"))?;
27
28 match &layer.source {
29 LayerSource::NasaGibs {
30 layer: gibs_layer,
31 projection,
32 } => download_and_compile_earth(gibs_layer, projection, resolution, layer).await,
33 LayerSource::YaleBrightStars => compile_bright_stars(layer),
34 LayerSource::HipparcosCatalog => compile_synthetic_stars(layer, resolution),
35 LayerSource::WmsImagery {
36 layer: wms_layer, ..
37 } if layer_id.starts_with("mars") || layer_id.starts_with("moon") => {
38 compile_planetary(layer, wms_layer, resolution)
39 }
40 _ => Err(format!(
41 "Layer '{}' source not yet supported for download",
42 layer_id
43 )),
44 }
45}
46
47async fn download_and_compile_earth(
48 gibs_layer: &str,
49 projection: &str,
50 resolution: u32,
51 layer_def: &LayerDefinition,
52) -> Result<CompiledLayerAsset, String> {
53 let req = nasa_gibs::GibsRequest {
54 layer: gibs_layer.to_string(),
55 projection: projection.to_string(),
56 width: resolution,
57 height: resolution / 2,
58 };
59
60 let texture = nasa_gibs::download_gibs_texture(&req)
61 .await
62 .map_err(|e| {
63 format!(
64 "GIBS download failed (layer: {gibs_layer}): {e}. Falling back to synthetic Earth."
65 )
66 })
67 .or_else(|e| {
68 eprintln!("GIBS download error: {e}");
69 Err(e)
70 });
71
72 let texture = match texture {
73 Ok(t) => Some(t),
74 Err(_) => None,
75 };
76
77 let segments = (resolution / 4).max(32).min(256);
78 let rings = segments / 2;
79
80 let (positions, colors, indices) =
81 mesh_gen::generate_sphere_mesh_colored(segments, rings, |lat, lon| {
82 if let Some(ref tex) = texture {
83 tex.sample(lat, lon)
84 } else {
85 let ocean = lat.abs() < 60.0;
86 let land = (lon.sin() * lat.cos() * 3.0).fract() > 0.3;
87 if ocean && land {
88 [0.2, 0.5, 0.2]
89 } else if ocean {
90 [0.1, 0.2, 0.5]
91 } else {
92 [0.9, 0.9, 0.95]
93 }
94 }
95 });
96
97 let mesh = Mesh {
98 positions: positions.clone(),
99 triangles: indices.chunks(3).map(|c| [c[0], c[1], c[2]]).collect(),
100 min: [-1.0, -1.0, -1.0],
101 max: [1.0, 1.0, 1.0],
102 };
103
104 let provenance = ProvenanceSidecar {
105 source_bytes: format!("NASA GIBS WMS: {gibs_layer}").into_bytes(),
106 source_media_type: "image/jpeg".to_string(),
107 licence: layer_def.license.to_string(),
108 vc: Vec::new(),
109 semantic_metadata: Vec::new(),
110 timestamp_epoch_s: std::time::SystemTime::now()
111 .duration_since(std::time::UNIX_EPOCH)
112 .unwrap_or_default()
113 .as_secs(),
114 version_hash: [0u8; 32],
115 };
116
117 let container_10d = compile_mesh_to_10d_with_provenance(&mesh, Some(&provenance))
118 .map_err(|e| format!("10D compilation: {e:?}"))?;
119
120 Ok(CompiledLayerAsset {
121 layer_id: layer_def.id.to_string(),
122 container_10d,
123 vertex_count: positions.len() as u32,
124 triangle_count: (indices.len() / 3) as u32,
125 positions,
126 colors,
127 indices,
128 source_format: "NASA GIBS WMS JPEG".to_string(),
129 license: layer_def.license.to_string(),
130 })
131}
132
133fn compile_bright_stars(layer_def: &LayerDefinition) -> Result<CompiledLayerAsset, String> {
134 let radius = 100.0f64;
135 let (positions, colors) = starfield::bright_stars_mesh(radius);
136
137 let indices: Vec<u32> = (0..positions.len() as u32).collect();
138
139 let mesh = Mesh {
140 positions: positions.clone(),
141 triangles: indices.chunks(1).map(|c| [c[0], c[0], c[0]]).collect(),
142 min: [-radius as f32, -radius as f32, -radius as f32],
143 max: [radius as f32, radius as f32, radius as f32],
144 };
145
146 let provenance = ProvenanceSidecar {
147 source_bytes: b"Yale Bright Star Catalog (embedded)".to_vec(),
148 source_media_type: "text/csv".to_string(),
149 licence: layer_def.license.to_string(),
150 vc: Vec::new(),
151 semantic_metadata: Vec::new(),
152 timestamp_epoch_s: 0,
153 version_hash: [0u8; 32],
154 };
155
156 let container_10d = compile_mesh_to_10d_with_provenance(&mesh, Some(&provenance))
157 .map_err(|e| format!("10D compilation: {e:?}"))?;
158
159 Ok(CompiledLayerAsset {
160 layer_id: layer_def.id.to_string(),
161 container_10d,
162 vertex_count: positions.len() as u32,
163 triangle_count: positions.len() as u32,
164 positions,
165 colors,
166 indices,
167 source_format: "Yale Bright Star Catalog".to_string(),
168 license: layer_def.license.to_string(),
169 })
170}
171
172fn compile_synthetic_stars(
173 layer_def: &LayerDefinition,
174 count: u32,
175) -> Result<CompiledLayerAsset, String> {
176 let radius = 200.0f64;
177 let count = count.min(50_000);
178 let (positions, colors) = starfield::generate_synthetic_starfield(count, radius, 42);
179
180 let indices: Vec<u32> = (0..positions.len() as u32).collect();
181
182 let mesh = Mesh {
183 positions: positions.clone(),
184 triangles: indices.chunks(1).map(|c| [c[0], c[0], c[0]]).collect(),
185 min: [-radius as f32, -radius as f32, -radius as f32],
186 max: [radius as f32, radius as f32, radius as f32],
187 };
188
189 let provenance = ProvenanceSidecar {
190 source_bytes: b"Synthetic starfield (procedural generation)".to_vec(),
191 source_media_type: "application/octet-stream".to_string(),
192 licence: layer_def.license.to_string(),
193 vc: Vec::new(),
194 semantic_metadata: Vec::new(),
195 timestamp_epoch_s: 0,
196 version_hash: [0u8; 32],
197 };
198
199 let container_10d = compile_mesh_to_10d_with_provenance(&mesh, Some(&provenance))
200 .map_err(|e| format!("10D compilation: {e:?}"))?;
201
202 Ok(CompiledLayerAsset {
203 layer_id: layer_def.id.to_string(),
204 container_10d,
205 vertex_count: positions.len() as u32,
206 triangle_count: positions.len() as u32,
207 positions,
208 colors,
209 indices,
210 source_format: "Synthetic procedural starfield".to_string(),
211 license: layer_def.license.to_string(),
212 })
213}
214
215fn compile_planetary(
216 layer_def: &LayerDefinition,
217 _wms_layer: &str,
218 resolution: u32,
219) -> Result<CompiledLayerAsset, String> {
220 let segments = (resolution / 4).max(32).min(128);
221 let rings = segments / 2;
222
223 let base_color = layer_def.preview_color;
224 let body_id = layer_def.id;
225
226 let (positions, colors, indices) =
227 mesh_gen::generate_sphere_mesh_colored(segments, rings, |lat, lon| {
228 let crater_noise = ((lat * 7.0).sin() * (lon * 5.0).cos()
229 + (lat * 3.0).cos() * (lon * 11.0).sin())
230 * 0.15;
231 let r = (base_color[0] + crater_noise).clamp(0.0, 1.0);
232 let g = (base_color[1] + crater_noise * 0.8).clamp(0.0, 1.0);
233 let b = (base_color[2] + crater_noise * 0.6).clamp(0.0, 1.0);
234 let _ = body_id;
235 [r, g, b]
236 });
237
238 let mesh = Mesh {
239 positions: positions.clone(),
240 triangles: indices.chunks(3).map(|c| [c[0], c[1], c[2]]).collect(),
241 min: [-1.0, -1.0, -1.0],
242 max: [1.0, 1.0, 1.0],
243 };
244
245 let provenance = ProvenanceSidecar {
246 source_bytes: format!("Planetary body: {}", layer_def.name).into_bytes(),
247 source_media_type: "model/gltf-binary".to_string(),
248 licence: layer_def.license.to_string(),
249 vc: Vec::new(),
250 semantic_metadata: Vec::new(),
251 timestamp_epoch_s: 0,
252 version_hash: [0u8; 32],
253 };
254
255 let container_10d = compile_mesh_to_10d_with_provenance(&mesh, Some(&provenance))
256 .map_err(|e| format!("10D compilation: {e:?}"))?;
257
258 Ok(CompiledLayerAsset {
259 layer_id: layer_def.id.to_string(),
260 container_10d,
261 vertex_count: positions.len() as u32,
262 triangle_count: (indices.len() / 3) as u32,
263 positions,
264 colors,
265 indices,
266 source_format: "Procedural planetary surface".to_string(),
267 license: layer_def.license.to_string(),
268 })
269}