Skip to main content

qualia_client_core/chora/
asset_pipeline.rs

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}