rustial_engine/terrain/
elevation_source.rs1use crate::terrain::error::TerrainError;
4use rustial_math::{ElevationGrid, TileId};
5
6#[derive(Debug, Clone, Default, PartialEq, Eq)]
8pub struct ElevationSourceFailureDiagnostics {
9 pub network_failures: usize,
11 pub decode_failures: usize,
13 pub unsupported_format_failures: usize,
15 pub other_failures: usize,
17 pub ignored_completed_responses: usize,
19}
20
21#[derive(Debug, Clone, Default, PartialEq, Eq)]
23pub struct ElevationSourceDiagnostics {
24 pub queued_requests: usize,
26 pub in_flight_requests: usize,
28 pub max_concurrent_requests: usize,
30 pub known_requests: usize,
32 pub cancelled_in_flight_requests: usize,
34 pub failure_diagnostics: ElevationSourceFailureDiagnostics,
36}
37
38pub trait ElevationSource: Send + Sync {
43 fn request(&self, id: TileId);
45
46 fn poll(&self) -> Vec<(TileId, Result<ElevationGrid, TerrainError>)>;
48
49 fn cancel(&self, _id: TileId) -> bool {
54 false
55 }
56
57 fn diagnostics(&self) -> Option<ElevationSourceDiagnostics> {
59 None
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum TerrainRgbEncoding {
66 Terrarium,
69 Mapbox,
72}
73
74impl TerrainRgbEncoding {
75 #[inline]
83 pub fn decode(&self, r: u8, g: u8, b: u8) -> f32 {
84 let raw = match self {
85 TerrainRgbEncoding::Terrarium => {
86 (r as f32 * 256.0 + g as f32 + b as f32 / 256.0) - 32768.0
87 }
88 TerrainRgbEncoding::Mapbox => {
89 -10000.0 + (r as f32 * 65536.0 + g as f32 * 256.0 + b as f32) * 0.1
90 }
91 };
92 raw.clamp(-500.0, 9_100.0)
93 }
94}
95
96pub struct FlatElevationSource {
98 width: u32,
99 height: u32,
100 pending: std::sync::Mutex<Vec<TileId>>,
101}
102
103impl FlatElevationSource {
104 pub fn new(width: u32, height: u32) -> Self {
106 Self {
107 width,
108 height,
109 pending: std::sync::Mutex::new(Vec::new()),
110 }
111 }
112}
113
114impl ElevationSource for FlatElevationSource {
115 fn request(&self, id: TileId) {
116 if let Ok(mut pending) = self.pending.lock() {
117 pending.push(id);
118 }
119 }
120
121 fn poll(&self) -> Vec<(TileId, Result<ElevationGrid, TerrainError>)> {
122 let tiles = if let Ok(mut pending) = self.pending.lock() {
123 std::mem::take(&mut *pending)
124 } else {
125 return Vec::new();
126 };
127
128 tiles
129 .into_iter()
130 .map(|id| {
131 let grid = ElevationGrid::flat(id, self.width, self.height);
132 (id, Ok(grid))
133 })
134 .collect()
135 }
136
137 fn diagnostics(&self) -> Option<ElevationSourceDiagnostics> {
138 let pending = self.pending.lock().map(|p| p.len()).unwrap_or(0);
139 Some(ElevationSourceDiagnostics {
140 queued_requests: 0,
141 in_flight_requests: pending,
142 max_concurrent_requests: 0,
143 known_requests: pending,
144 cancelled_in_flight_requests: 0,
145 failure_diagnostics: ElevationSourceFailureDiagnostics::default(),
146 })
147 }
148
149 fn cancel(&self, id: TileId) -> bool {
150 if let Ok(mut pending) = self.pending.lock() {
151 let before = pending.len();
152 pending.retain(|queued| *queued != id);
153 return pending.len() != before;
154 }
155 false
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn terrarium_decode_sea_level() {
165 let elev = TerrainRgbEncoding::Terrarium.decode(128, 0, 0);
168 assert!((elev - 0.0).abs() < 0.01);
169 }
170
171 #[test]
172 fn terrarium_decode_positive() {
173 let elev = TerrainRgbEncoding::Terrarium.decode(131, 232, 0);
176 assert!((elev - 1000.0).abs() < 1.0);
177 }
178
179 #[test]
180 fn mapbox_decode_sea_level() {
181 let elev = TerrainRgbEncoding::Mapbox.decode(1, 134, 160);
185 assert!((elev - 0.0).abs() < 0.1);
186 }
187
188 #[test]
189 fn mapbox_decode_positive() {
190 let elev = TerrainRgbEncoding::Mapbox.decode(1, 173, 208);
194 assert!((elev - 1003.2).abs() < 1.0);
195 }
196
197 #[test]
198 fn flat_source_returns_zero() {
199 let source = FlatElevationSource::new(3, 3);
200 source.request(TileId::new(5, 10, 10));
201 let results = source.poll();
202 assert_eq!(results.len(), 1);
203 let (id, grid) = &results[0];
204 assert_eq!(*id, TileId::new(5, 10, 10));
205 let grid = grid.as_ref().unwrap();
206 assert_eq!(grid.sample(0.5, 0.5), Some(0.0));
207 }
208}