terrain_codec/heightmap/
cesium.rs1pub const TILE_SIZE: u32 = 65;
38
39pub const MIN_ELEVATION: f64 = -1000.0;
41
42pub const MAX_ELEVATION: f64 = 12107.0;
44
45pub const SCALE: f64 = 5.0;
47
48#[inline]
52pub fn elevation_to_u16(elevation: f64) -> u16 {
53 let clamped = elevation.clamp(MIN_ELEVATION, MAX_ELEVATION);
54 ((clamped - MIN_ELEVATION) * SCALE).round() as u16
55}
56
57#[inline]
59pub fn u16_to_elevation(value: u16) -> f64 {
60 value as f64 / SCALE + MIN_ELEVATION
61}
62
63pub fn encode_heights_into(elevations: &[f64], width: u32, height: u32, out: &mut [u8]) {
74 let expected = (width as usize) * (height as usize);
75 assert!(
76 elevations.len() >= expected,
77 "expected at least {expected} elevations, got {}",
78 elevations.len()
79 );
80 assert_eq!(
81 out.len(),
82 expected * 2,
83 "out buffer length mismatch: expected {}, got {}",
84 expected * 2,
85 out.len()
86 );
87 for (&e, chunk) in elevations[..expected].iter().zip(out.chunks_exact_mut(2)) {
88 chunk.copy_from_slice(&elevation_to_u16(e).to_le_bytes());
89 }
90}
91
92pub fn encode_heights_to<W: std::io::Write>(
95 elevations: &[f64],
96 width: u32,
97 height: u32,
98 mut writer: W,
99) -> std::io::Result<()> {
100 let expected = (width as usize) * (height as usize);
101 assert!(
102 elevations.len() >= expected,
103 "expected at least {expected} elevations, got {}",
104 elevations.len()
105 );
106 let mut buf = [0u8; 4096];
107 let mut len = 0;
108 for &e in &elevations[..expected] {
109 let bytes = elevation_to_u16(e).to_le_bytes();
110 buf[len] = bytes[0];
111 buf[len + 1] = bytes[1];
112 len += 2;
113 if len + 2 > buf.len() {
114 writer.write_all(&buf[..len])?;
115 len = 0;
116 }
117 }
118 if len > 0 {
119 writer.write_all(&buf[..len])?;
120 }
121 Ok(())
122}
123
124pub fn encode_heights(elevations: &[f64], width: u32, height: u32) -> Vec<u8> {
130 let expected = (width as usize) * (height as usize);
131 let mut out = vec![0u8; expected * 2];
132 encode_heights_into(elevations, width, height, &mut out);
133 out
134}
135
136pub fn iter_heights(data: &[u8]) -> impl Iterator<Item = f64> + '_ {
139 data.chunks_exact(2)
140 .map(|c| u16_to_elevation(u16::from_le_bytes([c[0], c[1]])))
141}
142
143pub fn decode_heights(data: &[u8]) -> Vec<f64> {
147 iter_heights(data).collect()
148}
149
150#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
162pub struct ChildTileMask {
163 pub southwest: bool,
165 pub southeast: bool,
167 pub northwest: bool,
169 pub northeast: bool,
171}
172
173impl ChildTileMask {
174 pub const fn all() -> Self {
176 Self {
177 southwest: true,
178 southeast: true,
179 northwest: true,
180 northeast: true,
181 }
182 }
183
184 pub const fn none() -> Self {
186 Self {
187 southwest: false,
188 southeast: false,
189 northwest: false,
190 northeast: false,
191 }
192 }
193
194 pub const fn to_byte(self) -> u8 {
196 let mut mask = 0u8;
197 if self.southwest {
198 mask |= 1;
199 }
200 if self.southeast {
201 mask |= 2;
202 }
203 if self.northwest {
204 mask |= 4;
205 }
206 if self.northeast {
207 mask |= 8;
208 }
209 mask
210 }
211
212 pub const fn from_byte(byte: u8) -> Self {
214 Self {
215 southwest: byte & 1 != 0,
216 southeast: byte & 2 != 0,
217 northwest: byte & 4 != 0,
218 northeast: byte & 8 != 0,
219 }
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn elevation_roundtrip_within_range() {
229 for &e in &[-1000.0_f64, 0.0, 100.0, 8848.0, 12107.0] {
230 let back = u16_to_elevation(elevation_to_u16(e));
231 assert!((e - back).abs() < 0.2, "{e} → {back}");
232 }
233 }
234
235 #[test]
236 fn elevation_clamps_out_of_range() {
237 let low = u16_to_elevation(elevation_to_u16(-2000.0));
238 assert!((low - MIN_ELEVATION).abs() < 0.2);
239 let high = u16_to_elevation(elevation_to_u16(20000.0));
240 assert!((high - MAX_ELEVATION).abs() < 0.2);
241 }
242
243 #[test]
244 fn encode_decode_bulk() {
245 let elevations: Vec<f64> = vec![0.0, 100.0, -100.0, 1000.0];
246 let bytes = encode_heights(&elevations, 2, 2);
247 assert_eq!(bytes.len(), 8); let back = decode_heights(&bytes);
249 for (a, b) in elevations.iter().zip(&back) {
250 assert!((a - b).abs() < 0.2);
251 }
252 }
253
254 #[test]
255 fn child_mask_bits_match_spec() {
256 assert_eq!(ChildTileMask::all().to_byte(), 0b1111);
257 assert_eq!(ChildTileMask::none().to_byte(), 0);
258 let only_se = ChildTileMask {
259 southeast: true,
260 ..Default::default()
261 };
262 assert_eq!(only_se.to_byte(), 0b0010);
263 assert_eq!(
264 ChildTileMask::from_byte(0b1010),
265 ChildTileMask {
266 southwest: false,
267 southeast: true,
268 northwest: false,
269 northeast: true,
270 }
271 );
272 }
273}