1pub mod biome;
2pub mod block;
3pub mod chonk;
4pub mod map;
5pub mod site;
6pub mod sprite;
7pub mod structure;
8
9use std::ops::{Add, Mul};
10
11pub use self::{
13 biome::BiomeKind,
14 block::{Block, BlockKind},
15 map::MapSizeLg,
16 site::SiteKindMeta,
17 sprite::{SpriteCfg, SpriteKind, UnlockKind},
18 structure::{Structure, StructuresGroup},
19};
20use hashbrown::HashMap;
21use roots::find_roots_cubic;
22use serde::{Deserialize, Serialize};
23
24use crate::{
25 vol::{ReadVol, RectVolSize},
26 volumes::vol_grid_2d::VolGrid2d,
27};
28use vek::*;
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct TerrainChunkSize;
34
35pub const TERRAIN_CHUNK_BLOCKS_LG: u32 = 5;
47
48impl RectVolSize for TerrainChunkSize {
49 const RECT_SIZE: Vec2<u32> = Vec2 {
50 x: (1 << TERRAIN_CHUNK_BLOCKS_LG),
51 y: (1 << TERRAIN_CHUNK_BLOCKS_LG),
52 };
53}
54
55impl TerrainChunkSize {
56 #[inline(always)]
57 pub fn blocks(chunks: Vec2<u32>) -> Vec2<u32> { chunks * Self::RECT_SIZE }
65
66 pub fn center_wpos(chunk_pos: Vec2<i32>) -> Vec2<i32> {
78 chunk_pos * Self::RECT_SIZE.as_::<i32>() + Self::RECT_SIZE.as_::<i32>() / 2
79 }
80}
81
82pub trait CoordinateConversions {
83 fn wpos_to_cpos(&self) -> Self;
84 fn cpos_to_wpos(&self) -> Self;
85 fn cpos_to_wpos_center(&self) -> Self;
86}
87
88impl CoordinateConversions for Vec2<i32> {
89 #[inline]
90 fn wpos_to_cpos(&self) -> Self {
91 self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e.div_euclid(sz as i32))
92 }
93
94 #[inline]
95 fn cpos_to_wpos(&self) -> Self { self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e * sz as i32) }
96
97 #[inline]
98 fn cpos_to_wpos_center(&self) -> Self {
99 self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| {
100 e * sz as i32 + sz as i32 / 2
101 })
102 }
103}
104
105impl CoordinateConversions for Vec2<f32> {
106 #[inline]
107 fn wpos_to_cpos(&self) -> Self { self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e / sz as f32) }
108
109 #[inline]
110 fn cpos_to_wpos(&self) -> Self { self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e * sz as f32) }
111
112 #[inline]
113 fn cpos_to_wpos_center(&self) -> Self {
114 self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| {
115 e * sz as f32 + sz as f32 / 2.0
116 })
117 }
118}
119
120impl CoordinateConversions for Vec2<f64> {
121 #[inline]
122 fn wpos_to_cpos(&self) -> Self { self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e / sz as f64) }
123
124 #[inline]
125 fn cpos_to_wpos(&self) -> Self { self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e * sz as f64) }
126
127 #[inline]
128 fn cpos_to_wpos_center(&self) -> Self {
129 self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| {
130 e * sz as f64 + sz as f64 / 2.0
131 })
132 }
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct TerrainChunkMeta {
139 name: Option<String>,
140 biome: BiomeKind,
141 alt: f32,
142 tree_density: f32,
143 contains_cave: bool,
144 contains_river: bool,
145 near_water: bool,
146 river_velocity: Vec3<f32>,
147 approx_chunk_terrain_normal: Option<Vec3<f32>>,
148 rockiness: f32,
149 cliff_height: f32,
150 temp: f32,
151 humidity: f32,
152 site: Option<SiteKindMeta>,
153 tracks: Vec<CubicBezier3<f32>>,
154 debug_points: Vec<Vec3<f32>>,
155 debug_lines: Vec<LineSegment3<f32>>,
156 sprite_cfgs: HashMap<Vec3<i32>, SpriteCfg>,
157}
158
159impl TerrainChunkMeta {
160 pub fn new(
161 name: Option<String>,
162 biome: BiomeKind,
163 alt: f32,
164 tree_density: f32,
165 contains_cave: bool,
166 contains_river: bool,
167 near_water: bool,
168 river_velocity: Vec3<f32>,
169 temp: f32,
170 humidity: f32,
171 site: Option<SiteKindMeta>,
172 approx_chunk_terrain_normal: Option<Vec3<f32>>,
173 rockiness: f32,
174 cliff_height: f32,
175 ) -> Self {
176 Self {
177 name,
178 biome,
179 alt,
180 tree_density,
181 contains_cave,
182 contains_river,
183 near_water,
184 river_velocity,
185 temp,
186 humidity,
187 site,
188 tracks: Vec::new(),
189 debug_points: Vec::new(),
190 debug_lines: Vec::new(),
191 sprite_cfgs: HashMap::default(),
192 approx_chunk_terrain_normal,
193 rockiness,
194 cliff_height,
195 }
196 }
197
198 pub fn void() -> Self {
199 Self {
200 name: None,
201 biome: BiomeKind::Void,
202 alt: 0.0,
203 tree_density: 0.0,
204 contains_cave: false,
205 contains_river: false,
206 near_water: false,
207 river_velocity: Vec3::zero(),
208 temp: 0.0,
209 humidity: 0.0,
210 site: None,
211 tracks: Vec::new(),
212 debug_points: Vec::new(),
213 debug_lines: Vec::new(),
214 sprite_cfgs: HashMap::default(),
215 approx_chunk_terrain_normal: None,
216 rockiness: 0.0,
217 cliff_height: 0.0,
218 }
219 }
220
221 pub fn name(&self) -> Option<&str> { self.name.as_deref() }
222
223 pub fn biome(&self) -> BiomeKind { self.biome }
224
225 pub fn alt(&self) -> f32 { self.alt }
227
228 pub fn tree_density(&self) -> f32 { self.tree_density }
229
230 pub fn contains_cave(&self) -> bool { self.contains_cave }
231
232 pub fn contains_river(&self) -> bool { self.contains_river }
233
234 pub fn near_water(&self) -> bool { self.near_water }
235
236 pub fn river_velocity(&self) -> Vec3<f32> { self.river_velocity }
237
238 pub fn site(&self) -> Option<SiteKindMeta> { self.site }
239
240 pub fn temp(&self) -> f32 { self.temp }
242
243 pub fn humidity(&self) -> f32 { self.humidity }
244
245 pub fn tracks(&self) -> &[CubicBezier3<f32>] { &self.tracks }
246
247 pub fn add_track(&mut self, bezier: CubicBezier3<f32>) { self.tracks.push(bezier); }
248
249 pub fn debug_points(&self) -> &[Vec3<f32>] { &self.debug_points }
250
251 pub fn add_debug_point(&mut self, point: Vec3<f32>) { self.debug_points.push(point); }
252
253 pub fn debug_lines(&self) -> &[LineSegment3<f32>] { &self.debug_lines }
254
255 pub fn add_debug_line(&mut self, line: LineSegment3<f32>) { self.debug_lines.push(line); }
256
257 pub fn sprite_cfg_at(&self, rpos: Vec3<i32>) -> Option<&SpriteCfg> {
258 self.sprite_cfgs.get(&rpos)
259 }
260
261 pub fn set_sprite_cfg_at(&mut self, rpos: Vec3<i32>, sprite_cfg: SpriteCfg) {
262 self.sprite_cfgs.insert(rpos, sprite_cfg);
263 }
264
265 pub fn approx_chunk_terrain_normal(&self) -> Option<Vec3<f32>> {
266 self.approx_chunk_terrain_normal
267 }
268
269 pub fn rockiness(&self) -> f32 { self.rockiness }
270
271 pub fn cliff_height(&self) -> f32 { self.cliff_height }
272}
273
274pub type TerrainChunk = chonk::Chonk<Block, TerrainChunkSize, TerrainChunkMeta>;
277pub type TerrainGrid = VolGrid2d<TerrainChunk>;
278
279const TERRAIN_GRID_SEARCH_DIST: i32 = 63;
280
281impl TerrainGrid {
282 pub fn find_ground(&self, pos: Vec3<i32>) -> Vec3<i32> {
285 self.try_find_ground(pos).unwrap_or(pos)
286 }
287
288 pub fn is_space(&self, pos: Vec3<i32>) -> bool {
289 (0..2).all(|z| {
290 self.get(pos + Vec3::unit_z() * z)
291 .map_or(true, |b| !b.is_solid())
292 })
293 }
294
295 pub fn try_find_space(&self, pos: Vec3<i32>) -> Option<Vec3<i32>> {
296 (0..TERRAIN_GRID_SEARCH_DIST * 2 + 1)
297 .map(|i| if i % 2 == 0 { i } else { -i } / 2)
298 .map(|z_diff| pos + Vec3::unit_z() * z_diff)
299 .find(|pos| self.is_space(*pos))
300 }
301
302 pub fn try_find_ground(&self, pos: Vec3<i32>) -> Option<Vec3<i32>> {
303 (0..TERRAIN_GRID_SEARCH_DIST * 2 + 1)
304 .map(|i| if i % 2 == 0 { i } else { -i } / 2)
305 .map(|z_diff| pos + Vec3::unit_z() * z_diff)
306 .find(|pos| {
307 self.get(pos - Vec3::unit_z()).is_ok_and(|b| b.is_filled()) && self.is_space(*pos)
308 })
309 }
310
311 pub fn get_interpolated<T, F>(&self, pos: Vec2<i32>, mut f: F) -> Option<T>
312 where
313 T: Copy + Default + Add<Output = T> + Mul<f32, Output = T>,
314 F: FnMut(&TerrainChunk) -> T,
315 {
316 let pos = pos.as_::<f64>().wpos_to_cpos();
317
318 let cubic = |a: T, b: T, c: T, d: T, x: f32| -> T {
319 let x2 = x * x;
320
321 let co0 = a * -0.5 + b * 1.5 + c * -1.5 + d * 0.5;
323 let co1 = a + b * -2.5 + c * 2.0 + d * -0.5;
324 let co2 = a * -0.5 + c * 0.5;
325 let co3 = b;
326
327 co0 * x2 * x + co1 * x2 + co2 * x + co3
328 };
329
330 let mut x = [T::default(); 4];
331
332 for (x_idx, j) in (-1..3).enumerate() {
333 let y0 = f(self.get_key(pos.map2(Vec2::new(j, -1), |e, q| e.max(0.0) as i32 + q))?);
334 let y1 = f(self.get_key(pos.map2(Vec2::new(j, 0), |e, q| e.max(0.0) as i32 + q))?);
335 let y2 = f(self.get_key(pos.map2(Vec2::new(j, 1), |e, q| e.max(0.0) as i32 + q))?);
336 let y3 = f(self.get_key(pos.map2(Vec2::new(j, 2), |e, q| e.max(0.0) as i32 + q))?);
337
338 x[x_idx] = cubic(y0, y1, y2, y3, pos.y.fract() as f32);
339 }
340
341 Some(cubic(x[0], x[1], x[2], x[3], pos.x.fract() as f32))
342 }
343}
344
345impl TerrainChunk {
346 pub fn water(sea_level: i32) -> TerrainChunk {
348 TerrainChunk::new(
349 sea_level,
350 Block::water(SpriteKind::Empty),
351 Block::air(SpriteKind::Empty),
352 TerrainChunkMeta::void(),
353 )
354 }
355
356 pub fn find_accessible_pos(&self, spawn_wpos: Vec2<i32>, ascending: bool) -> Vec3<f32> {
358 let min_z = self.get_min_z();
359 let max_z = self.get_max_z();
360
361 let pos = Vec3::new(
362 spawn_wpos.x,
363 spawn_wpos.y,
364 if ascending { min_z } else { max_z },
365 );
366 (0..(max_z - min_z))
367 .map(|z_diff| {
368 if ascending {
369 pos + Vec3::unit_z() * z_diff
370 } else {
371 pos - Vec3::unit_z() * z_diff
372 }
373 })
374 .find(|test_pos| {
375 let chunk_relative_xy = test_pos
376 .xy()
377 .map2(TerrainChunkSize::RECT_SIZE, |e, sz| e.rem_euclid(sz as i32));
378 self.get(
379 Vec3::new(chunk_relative_xy.x, chunk_relative_xy.y, test_pos.z)
380 - Vec3::unit_z(),
381 )
382 .is_ok_and(|b| b.is_filled())
383 && (0..3).all(|z| {
384 self.get(
385 Vec3::new(chunk_relative_xy.x, chunk_relative_xy.y, test_pos.z)
386 + Vec3::unit_z() * z,
387 )
388 .map_or(true, |b| !b.is_solid())
389 })
390 })
391 .unwrap_or(pos)
392 .map(|e| e as f32)
393 + 0.5
394 }
395}
396
397#[inline(always)]
404pub fn uniform_idx_as_vec2(map_size_lg: MapSizeLg, idx: usize) -> Vec2<i32> {
405 let x_mask = (1 << map_size_lg.vec().x) - 1;
406 Vec2::new((idx & x_mask) as i32, (idx >> map_size_lg.vec().x) as i32)
407}
408
409#[inline(always)]
413pub fn vec2_as_uniform_idx(map_size_lg: MapSizeLg, idx: Vec2<i32>) -> usize {
414 ((idx.y as usize) << map_size_lg.vec().x) | idx.x as usize
415}
416
417pub const NEIGHBOR_DELTA: [(i32, i32); 8] = [
419 (-1, -1),
420 (0, -1),
421 (1, -1),
422 (-1, 0),
423 (1, 0),
424 (-1, 1),
425 (0, 1),
426 (1, 1),
427];
428
429#[inline(always)]
431pub fn neighbors(map_size_lg: MapSizeLg, posi: usize) -> impl Clone + Iterator<Item = usize> {
432 let pos = uniform_idx_as_vec2(map_size_lg, posi);
433 let world_size = map_size_lg.chunks();
434 NEIGHBOR_DELTA
435 .iter()
436 .map(move |&(x, y)| Vec2::new(pos.x + x, pos.y + y))
437 .filter(move |pos| {
438 pos.x >= 0 && pos.y >= 0 && pos.x < world_size.x as i32 && pos.y < world_size.y as i32
439 })
440 .map(move |pos| vec2_as_uniform_idx(map_size_lg, pos))
441}
442
443pub fn river_spline_coeffs(
444 chunk_pos: Vec2<f64>,
446 spline_derivative: Vec2<f32>,
447 downhill_pos: Vec2<f64>,
448) -> Vec3<Vec2<f64>> {
449 let dxy = downhill_pos - chunk_pos;
450 let spline_derivative = spline_derivative.map(|e| e as f64);
457 Vec3::new(dxy - spline_derivative, spline_derivative, chunk_pos)
458}
459
460pub fn quadratic_nearest_point(
467 spline: &Vec3<Vec2<f64>>,
468 point: Vec2<f64>,
469 _line: Vec2<Vec2<f64>>, ) -> Option<(f64, Vec2<f64>, f64)> {
471 let a = spline.z.x;
517 let b = spline.y.x;
518 let c = spline.x.x;
519 let d = point.x;
520 let e = spline.z.y;
521 let f = spline.y.y;
522 let g = spline.x.y;
523 let h = point.y;
524 let a_ = (c * c + g * g) * 2.0;
539 let b_ = (b * c + g * f) * 3.0;
540 let a_d = a - d;
541 let e_h = e - h;
542 let c_ = a_d * c * 2.0 + b * b + e_h * g * 2.0 + f * f;
543 let d_ = a_d * b + e_h * f;
544 let roots = find_roots_cubic(a_, b_, c_, d_);
545 let roots = roots.as_ref();
546
547 let min_root = roots
548 .iter()
549 .copied()
550 .map(|root| {
551 let river_point = spline.x * root * root + spline.y * root + spline.z;
552 if root > 0.0 && root < 1.0 {
553 (root, river_point)
554 } else {
555 let root = root.clamped(0.0, 1.0);
556 let river_point = spline.x * root * root + spline.y * root + spline.z;
557 (root, river_point)
558 }
559 })
560 .map(|(root, river_point)| {
561 let river_distance = river_point.distance_squared(point);
562 (root, river_point, river_distance)
563 })
564 .min_by(|&(ap, _, a), &(bp, _, b)| {
567 (a, !(0.0..=1.0).contains(&ap), ap)
568 .partial_cmp(&(b, !(0.0..=1.0).contains(&bp), bp))
569 .unwrap()
570 });
571 min_root
572}