1#![expect(clippy::clone_on_copy)] use crate::{
4 mesh::{
5 MeshGen,
6 greedy::{self, GreedyConfig, GreedyMesh},
7 },
8 render::{AltIndices, FluidVertex, Mesh, TerrainAtlasData, TerrainVertex, Vertex},
9 scene::terrain::{BlocksOfInterest, DEEP_ALT, SHALLOW_ALT},
10};
11use common::{
12 terrain::{Block, TerrainChunk},
13 util::either_with,
14 vol::{ReadVol, RectRasterableVol},
15 volumes::vol_grid_2d::{CachedVolGrid2d, VolGrid2d},
16};
17use common_base::span;
18use std::{collections::VecDeque, fmt::Debug, sync::Arc};
19use tracing::error;
20use vek::*;
21
22#[derive(Clone, Copy, PartialEq)]
23pub enum FaceKind {
24 Opaque(bool),
27 Fluid,
30}
31
32pub const SUNLIGHT: u8 = 24;
33pub const SUNLIGHT_INV: f32 = 1.0 / SUNLIGHT as f32;
34pub const MAX_LIGHT_DIST: i32 = SUNLIGHT as i32;
35
36fn calc_light<V: RectRasterableVol<Vox = Block> + ReadVol + Debug>(
37 is_sunlight: bool,
38 default_light: u8,
40 bounds: Aabb<i32>,
41 vol: &VolGrid2d<V>,
42 lit_blocks: impl Iterator<Item = (Vec3<i32>, u8)>,
43) -> impl Fn(Vec3<i32>) -> f32 + 'static + Send + Sync {
44 span!(_guard, "calc_light");
45 const UNKNOWN: u8 = 255;
46 const OPAQUE: u8 = 254;
47
48 let outer = Aabb {
49 min: bounds.min - Vec3::new(SUNLIGHT as i32, SUNLIGHT as i32, 1),
50 max: bounds.max + Vec3::new(SUNLIGHT as i32, SUNLIGHT as i32, 1),
51 };
52
53 let mut vol_cached = vol.cached();
54
55 let mut light_map = vec![UNKNOWN; outer.size().product() as usize];
56 let lm_idx = {
57 let (w, h, _) = outer.clone().size().into_tuple();
58 move |x, y, z| (w * h * z + h * x + y) as usize
59 };
60 let mut prop_que = lit_blocks
62 .map(|(pos, light)| {
63 let rpos = pos - outer.min;
64 light_map[lm_idx(rpos.x, rpos.y, rpos.z)] = light.min(SUNLIGHT); (rpos.x as u8, rpos.y as u8, rpos.z as u16)
66 })
67 .collect::<VecDeque<_>>();
68 if is_sunlight {
70 for x in 0..outer.size().w {
71 for y in 0..outer.size().h {
72 let mut light = SUNLIGHT as f32;
73 for z in (0..outer.size().d).rev() {
74 let (min_light, attenuation) = vol_cached
75 .get(outer.min + Vec3::new(x, y, z))
76 .map_or((0, 0.0), |b| b.get_max_sunlight());
77
78 if light > min_light as f32 {
79 light = (light - attenuation).max(min_light as f32);
80 }
81
82 light_map[lm_idx(x, y, z)] = light.floor() as u8;
83
84 if light <= 0.0 {
85 break;
86 } else {
87 prop_que.push_back((x as u8, y as u8, z as u16));
88 }
89 }
90 }
91 }
92 }
93
94 let propagate = |src: u8,
96 dest: &mut u8,
97 pos: Vec3<i32>,
98 prop_que: &mut VecDeque<_>,
99 vol: &mut CachedVolGrid2d<V>| {
100 if *dest != OPAQUE {
101 if *dest == UNKNOWN {
102 if vol.get(outer.min + pos).ok().is_some_and(|b| b.is_fluid()) {
103 *dest = src.saturating_sub(1);
104 if *dest > 1 {
106 prop_que.push_back((pos.x as u8, pos.y as u8, pos.z as u16));
107 }
108 } else {
109 *dest = OPAQUE;
110 }
111 } else if *dest < src.saturating_sub(1) {
112 *dest = src - 1;
113 if *dest > 1 {
115 prop_que.push_back((pos.x as u8, pos.y as u8, pos.z as u16));
116 }
117 }
118 }
119 };
120
121 while let Some(pos) = prop_que.pop_front() {
123 let pos = Vec3::new(pos.0 as i32, pos.1 as i32, pos.2 as i32);
124 let light = light_map[lm_idx(pos.x, pos.y, pos.z)];
125
126 if pos.z + 1 < outer.size().d {
129 propagate(
130 light,
131 light_map.get_mut(lm_idx(pos.x, pos.y, pos.z + 1)).unwrap(),
132 Vec3::new(pos.x, pos.y, pos.z + 1),
133 &mut prop_que,
134 &mut vol_cached,
135 )
136 }
137 if pos.z > 0 {
139 propagate(
140 light,
141 light_map.get_mut(lm_idx(pos.x, pos.y, pos.z - 1)).unwrap(),
142 Vec3::new(pos.x, pos.y, pos.z - 1),
143 &mut prop_que,
144 &mut vol_cached,
145 )
146 }
147 if pos.y + 1 < outer.size().h {
149 propagate(
150 light,
151 light_map.get_mut(lm_idx(pos.x, pos.y + 1, pos.z)).unwrap(),
152 Vec3::new(pos.x, pos.y + 1, pos.z),
153 &mut prop_que,
154 &mut vol_cached,
155 )
156 }
157 if pos.y > 0 {
158 propagate(
159 light,
160 light_map.get_mut(lm_idx(pos.x, pos.y - 1, pos.z)).unwrap(),
161 Vec3::new(pos.x, pos.y - 1, pos.z),
162 &mut prop_que,
163 &mut vol_cached,
164 )
165 }
166 if pos.x + 1 < outer.size().w {
167 propagate(
168 light,
169 light_map.get_mut(lm_idx(pos.x + 1, pos.y, pos.z)).unwrap(),
170 Vec3::new(pos.x + 1, pos.y, pos.z),
171 &mut prop_que,
172 &mut vol_cached,
173 )
174 }
175 if pos.x > 0 {
176 propagate(
177 light,
178 light_map.get_mut(lm_idx(pos.x - 1, pos.y, pos.z)).unwrap(),
179 Vec3::new(pos.x - 1, pos.y, pos.z),
180 &mut prop_que,
181 &mut vol_cached,
182 )
183 }
184 }
185
186 let min_bounds = Aabb {
187 min: bounds.min - 1,
188 max: bounds.max + 1,
189 };
190
191 let mut light_map2 = vec![UNKNOWN; min_bounds.size().product() as usize];
194 let lm_idx2 = {
195 let (w, h, _) = min_bounds.clone().size().into_tuple();
196 move |x, y, z| (w * h * z + h * x + y) as usize
197 };
198 for x in 0..min_bounds.size().w {
199 for y in 0..min_bounds.size().h {
200 for z in 0..min_bounds.size().d {
201 let off = min_bounds.min - outer.min;
202 light_map2[lm_idx2(x, y, z)] = light_map[lm_idx(x + off.x, y + off.y, z + off.z)];
203 }
204 }
205 }
206
207 drop(light_map);
208
209 move |wpos| {
210 let pos = wpos - min_bounds.min;
211 let l = light_map2
212 .get(lm_idx2(pos.x, pos.y, pos.z))
213 .copied()
214 .unwrap_or(default_light);
215
216 if l != OPAQUE && l != UNKNOWN {
217 l as f32 * SUNLIGHT_INV
218 } else {
219 0.0
220 }
221 }
222}
223
224#[expect(clippy::type_complexity)]
225pub fn generate_mesh<'a>(
226 vol: &'a VolGrid2d<TerrainChunk>,
227 (range, max_texture_size, _boi): (Aabb<i32>, Vec2<u16>, &'a BlocksOfInterest),
228) -> MeshGen<
229 TerrainVertex,
230 FluidVertex,
231 TerrainVertex,
232 (
233 Aabb<f32>,
234 TerrainAtlasData,
235 Vec2<u16>,
236 Arc<dyn Fn(Vec3<i32>) -> f32 + Send + Sync>,
237 Arc<dyn Fn(Vec3<i32>) -> f32 + Send + Sync>,
238 AltIndices,
239 (f32, f32),
240 ),
241> {
242 span!(
243 _guard,
244 "generate_mesh",
245 "<&VolGrid2d as Meshable<_, _>>::generate_mesh"
246 );
247
248 let mut glow_blocks = Vec::new();
257
258 let mut volume = vol.cached();
260 for x in -MAX_LIGHT_DIST..range.size().w + MAX_LIGHT_DIST {
261 for y in -MAX_LIGHT_DIST..range.size().h + MAX_LIGHT_DIST {
262 for z in -1..range.size().d + 1 {
263 let wpos = range.min + Vec3::new(x, y, z);
264 volume
265 .get(wpos)
266 .ok()
267 .and_then(|b| b.get_glow())
268 .map(|glow| glow_blocks.push((wpos, glow)));
269 }
270 }
271 }
272
273 let light = calc_light(true, SUNLIGHT, range, vol, core::iter::empty());
275 let glow = calc_light(false, 0, range, vol, glow_blocks.into_iter());
276
277 let (underground_alt, deep_alt) = vol
278 .get_key(vol.pos_key((range.min + range.max) / 2))
279 .map_or((0.0, 0.0), |c| {
280 (c.meta().alt() - SHALLOW_ALT, c.meta().alt() - DEEP_ALT)
281 });
282
283 let mut opaque_limits = None::<Limits>;
284 let mut fluid_limits = None::<Limits>;
285 let mut air_limits = None::<Limits>;
286 let flat_get = {
287 span!(_guard, "copy to flat array");
288 let (w, h, d) = range.size().into_tuple();
289 let d = d + 2;
291 let flat = {
292 let mut volume = vol.cached();
293 const AIR: Block = Block::empty();
294 let mut flat = vec![AIR; (w * h * d) as usize];
297 let mut i = 0;
298 for x in 0..range.size().w {
299 for y in 0..range.size().h {
300 for z in -1..range.size().d + 1 {
301 let wpos = range.min + Vec3::new(x, y, z);
302 let block = volume
303 .get(wpos)
304 .copied()
305 .unwrap_or(AIR);
308 if block.is_opaque() {
309 opaque_limits = opaque_limits
310 .map(|l| l.including(z))
311 .or_else(|| Some(Limits::from_value(z)));
312 } else if block.is_liquid() {
313 fluid_limits = fluid_limits
314 .map(|l| l.including(z))
315 .or_else(|| Some(Limits::from_value(z)));
316 } else {
317 air_limits = air_limits
319 .map(|l| l.including(z))
320 .or_else(|| Some(Limits::from_value(z)));
321 };
322 flat[i] = block;
323 i += 1;
324 }
325 }
326 }
327 flat
328 };
329
330 move |Vec3 { x, y, z }| {
331 let z = z + 1;
333 match flat.get((x * h * d + y * d + z) as usize).copied() {
334 Some(b) => b,
335 None => panic!("x {} y {} z {} d {} h {}", x, y, z, d, h),
336 }
337 }
338 };
339
340 let (z_start, z_end) = match (air_limits, fluid_limits, opaque_limits) {
342 (Some(air), Some(fluid), Some(opaque)) => air.three_way_intersection(fluid, opaque),
343 (Some(air), Some(fluid), None) => air.intersection(fluid),
344 (Some(air), None, Some(opaque)) => air.intersection(opaque),
345 (None, Some(fluid), Some(opaque)) => fluid.intersection(opaque),
346 (Some(_), None, None) | (None, Some(_), None) | (None, None, Some(_)) => None,
348 (None, None, None) => {
349 error!("Impossible unless given an input AABB that has a height of zero");
350 None
351 },
352 }
353 .map_or((0, 0), |limits| {
354 let (start, end) = limits.into_tuple();
355 let start = start.max(0);
356 let end = end.clamp(start, range.size().d - 1);
357 (start, end)
358 });
359
360 let max_size = max_texture_size;
361 assert!(z_end >= z_start);
362 let greedy_size = Vec3::new(range.size().w - 2, range.size().h - 2, z_end - z_start + 1);
363 assert!(greedy_size.x <= 32 && greedy_size.y <= 32 && greedy_size.z <= 16384);
368 let max_bounds: Vec3<f32> = greedy_size.as_::<f32>();
371 let greedy_size = greedy_size.as_::<usize>();
374 let greedy_size_cross = Vec3::new(greedy_size.x - 1, greedy_size.y - 1, greedy_size.z);
375 let draw_delta = Vec3::new(1, 1, z_start);
376
377 let get_light = |_: &mut (), pos: Vec3<i32>| {
378 if flat_get(pos).is_opaque() {
379 0.0
380 } else {
381 light(pos + range.min)
382 }
383 };
384 let get_ao = |_: &mut (), pos: Vec3<i32>| {
385 if flat_get(pos).is_opaque() { 0.0 } else { 1.0 }
386 };
387 let get_glow = |_: &mut (), pos: Vec3<i32>| glow(pos + range.min);
388 let get_color =
389 |_: &mut (), pos: Vec3<i32>| flat_get(pos).get_color().unwrap_or_else(Rgb::zero);
390 let get_kind = |_: &mut (), pos: Vec3<i32>| flat_get(pos).kind() as u8;
391 let get_opacity = |_: &mut (), pos: Vec3<i32>| !flat_get(pos).is_opaque();
392 let should_draw = |_: &mut (), pos: Vec3<i32>, delta: Vec3<i32>, _uv| {
393 should_draw_greedy(pos, delta, &flat_get)
394 };
395 let mesh_delta = Vec3::new(0.0, 0.0, (z_start + range.min.z) as f32);
397 let create_opaque =
398 |atlas_pos, pos, norm, meta| TerrainVertex::new(atlas_pos, pos + mesh_delta, norm, meta);
399 let create_transparent = |_atlas_pos, pos: Vec3<f32>, norm| {
400 let key = vol.pos_key(range.min + pos.as_());
409 let v00 = vol
410 .get_key(key + Vec2::new(0, 0))
411 .map_or(Vec3::zero(), |c| c.meta().river_velocity());
412 let v10 = vol
413 .get_key(key + Vec2::new(1, 0))
414 .map_or(Vec3::zero(), |c| c.meta().river_velocity());
415 let v01 = vol
416 .get_key(key + Vec2::new(0, 1))
417 .map_or(Vec3::zero(), |c| c.meta().river_velocity());
418 let v11 = vol
419 .get_key(key + Vec2::new(1, 1))
420 .map_or(Vec3::zero(), |c| c.meta().river_velocity());
421 let factor =
422 (range.min + pos.as_()).map(|e| e as f32) / TerrainChunk::RECT_SIZE.map(|e| e as f32);
423 let vel = Lerp::lerp(
424 Lerp::lerp(v00, v10, factor.x.rem_euclid(1.0)),
425 Lerp::lerp(v01, v11, factor.x.rem_euclid(1.0)),
426 factor.y.rem_euclid(1.0),
427 );
428 FluidVertex::new(pos + mesh_delta, norm, vel.xy())
429 };
430
431 let mut greedy = GreedyMesh::<TerrainAtlasData, guillotiere::SimpleAtlasAllocator>::new(
432 max_size,
433 greedy::general_config(),
434 );
435 let mut opaque_deep = Vec::new();
436 let mut opaque_shallow = Vec::new();
437 let mut opaque_surface = Vec::new();
438 let mut fluid_mesh = Mesh::new();
439 greedy.push(GreedyConfig {
440 data: (),
441 draw_delta,
442 greedy_size,
443 greedy_size_cross,
444 get_ao,
445 get_light,
446 get_glow,
447 get_opacity,
448 should_draw,
449 push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &FaceKind| match meta {
450 FaceKind::Opaque(meta) => {
451 let mut max_z = None;
452 let mut min_z = None;
453 let quad = greedy::create_quad(
454 atlas_origin,
455 dim,
456 origin,
457 draw_dim,
458 norm,
459 meta,
460 |atlas_pos, pos, norm, &meta| {
461 max_z = Some(max_z.map_or(pos.z, |z: f32| z.max(pos.z)));
462 min_z = Some(min_z.map_or(pos.z, |z: f32| z.min(pos.z)));
463 create_opaque(atlas_pos, pos, norm, meta)
464 },
465 );
466 let max_alt = mesh_delta.z + max_z.expect("quad had no vertices?");
467 let min_alt = mesh_delta.z + min_z.expect("quad had no vertices?");
468
469 if max_alt < deep_alt {
470 opaque_deep.push(quad);
471 } else if min_alt > underground_alt {
472 opaque_surface.push(quad);
473 } else {
474 opaque_shallow.push(quad);
475 }
476 },
477 FaceKind::Fluid => {
478 fluid_mesh.push_quad(greedy::create_quad(
479 atlas_origin,
480 dim,
481 origin,
482 draw_dim,
483 norm,
484 &(),
485 |atlas_pos, pos, norm, &_meta| create_transparent(atlas_pos, pos, norm),
486 ));
487 },
488 },
489 make_face_texel: |(col_light, kind): (&mut [u8; 4], &mut u8),
490 data: &mut (),
491 pos,
492 light,
493 glow,
494 ao| {
495 *col_light = TerrainVertex::make_col_light(light, glow, get_color(data, pos), ao);
496 *kind = get_kind(data, pos);
497 },
498 });
499
500 let min_bounds = mesh_delta;
501 let bounds = Aabb {
502 min: min_bounds,
503 max: max_bounds + min_bounds,
504 };
505 let (atlas_data, atlas_size) = greedy.finalize();
506
507 let deep_end = opaque_deep.len()
508 * if TerrainVertex::QUADS_INDEX.is_some() {
509 4
510 } else {
511 6
512 };
513 let alt_indices = AltIndices {
514 deep_end,
515 underground_end: deep_end
516 + opaque_shallow.len()
517 * if TerrainVertex::QUADS_INDEX.is_some() {
518 4
519 } else {
520 6
521 },
522 };
523 let sun_occluder_z_bounds = (underground_alt.max(bounds.min.z), bounds.max.z);
524
525 (
526 opaque_deep
527 .into_iter()
528 .chain(opaque_shallow)
529 .chain(opaque_surface)
530 .collect(),
531 fluid_mesh,
532 Mesh::new(),
533 (
534 bounds,
535 atlas_data,
536 atlas_size,
537 Arc::new(light),
538 Arc::new(glow),
539 alt_indices,
540 sun_occluder_z_bounds,
541 ),
542 )
543}
544
545pub fn should_draw_greedy(
548 pos: Vec3<i32>,
549 delta: Vec3<i32>,
550 flat_get: impl Fn(Vec3<i32>) -> Block,
551) -> Option<(bool, FaceKind)> {
552 let from = flat_get(pos - delta);
553 let to = flat_get(pos);
554 let from_filled = from.is_filled();
556 if from_filled == to.is_filled() {
557 let from_liquid = from.is_liquid();
559 if from_liquid == to.is_liquid() || from.is_filled() || to.is_filled() {
560 None
561 } else {
562 Some((from_liquid, FaceKind::Fluid))
566 }
567 } else {
568 Some((
571 from_filled,
572 FaceKind::Opaque(if from_filled {
573 to.is_liquid()
574 } else {
575 from.is_liquid()
576 }),
577 ))
578 }
579}
580
581#[derive(Copy, Clone, Debug)]
583struct Limits {
584 min: i32,
585 max: i32,
586}
587
588impl Limits {
589 fn from_value(v: i32) -> Self { Self { min: v, max: v } }
590
591 fn including(mut self, v: i32) -> Self {
592 if v < self.min {
593 self.min = v
594 } else if v > self.max {
595 self.max = v
596 }
597 self
598 }
599
600 fn union(self, other: Self) -> Self {
601 Self {
602 min: self.min.min(other.min),
603 max: self.max.max(other.max),
604 }
605 }
606
607 fn intersection(self, other: Self) -> Option<Self> {
609 let min = self.min.max(other.min) - 1;
612 let max = self.max.min(other.max) + 1;
613
614 (min < max).then_some(Self { min, max })
615 }
616
617 fn three_way_intersection(self, two: Self, three: Self) -> Option<Self> {
619 let intersection = self.intersection(two);
620 let intersection = either_with(self.intersection(three), intersection, Limits::union);
621 either_with(two.intersection(three), intersection, Limits::union)
622 }
623
624 fn into_tuple(self) -> (i32, i32) { (self.min, self.max) }
625}