1use std::sync::LazyLock;
9
10use smallvec::SmallVec;
11use steel_registry::{
12 REGISTRY,
13 blocks::{BlockRef, block_state_ext::BlockStateExt},
14 vanilla_block_tags::BlockTag,
15};
16use steel_utils::BlockStateId;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub enum HeightmapType {
21 WorldSurface,
24 MotionBlocking,
26 MotionBlockingNoLeaves,
28 OceanFloor,
30 WorldSurfaceWg,
33 OceanFloorWg,
35}
36
37impl HeightmapType {
38 const WORLD_SURFACE_MASK: u8 = 1 << 0;
39 const MOTION_BLOCKING_MASK: u8 = 1 << 1;
40 const MOTION_BLOCKING_NO_LEAVES_MASK: u8 = 1 << 2;
41 const OCEAN_FLOOR_MASK: u8 = 1 << 3;
42 const WORLD_SURFACE_WG_MASK: u8 = 1 << 4;
43 const OCEAN_FLOOR_WG_MASK: u8 = 1 << 5;
44
45 #[must_use]
47 pub const fn worldgen_types() -> &'static [HeightmapType] {
48 &[HeightmapType::WorldSurfaceWg, HeightmapType::OceanFloorWg]
49 }
50
51 #[must_use]
53 pub const fn final_types() -> &'static [HeightmapType] {
54 &[
55 HeightmapType::WorldSurface,
56 HeightmapType::MotionBlocking,
57 HeightmapType::MotionBlockingNoLeaves,
58 HeightmapType::OceanFloor,
59 ]
60 }
61
62 #[must_use]
64 pub const fn all_types() -> &'static [HeightmapType] {
65 &[
66 HeightmapType::WorldSurface,
67 HeightmapType::MotionBlocking,
68 HeightmapType::MotionBlockingNoLeaves,
69 HeightmapType::OceanFloor,
70 HeightmapType::WorldSurfaceWg,
71 HeightmapType::OceanFloorWg,
72 ]
73 }
74
75 #[must_use]
76 pub(crate) const fn persistence_id(self) -> u8 {
77 match self {
78 Self::WorldSurface => 0,
79 Self::MotionBlocking => 1,
80 Self::MotionBlockingNoLeaves => 2,
81 Self::OceanFloor => 3,
82 Self::WorldSurfaceWg => 4,
83 Self::OceanFloorWg => 5,
84 }
85 }
86
87 #[must_use]
88 pub(crate) const fn from_persistence_id(id: u8) -> Option<Self> {
89 match id {
90 0 => Some(Self::WorldSurface),
91 1 => Some(Self::MotionBlocking),
92 2 => Some(Self::MotionBlockingNoLeaves),
93 3 => Some(Self::OceanFloor),
94 4 => Some(Self::WorldSurfaceWg),
95 5 => Some(Self::OceanFloorWg),
96 _ => None,
97 }
98 }
99
100 #[must_use]
106 pub fn is_opaque(self, state: BlockStateId) -> bool {
107 heightmap_opacity_mask(state, self.mask()) != 0
108 }
109
110 fn is_leaves(block: BlockRef) -> bool {
112 block.has_tag(&BlockTag::LEAVES)
113 }
114
115 const fn mask(self) -> u8 {
116 match self {
117 Self::WorldSurface => Self::WORLD_SURFACE_MASK,
118 Self::MotionBlocking => Self::MOTION_BLOCKING_MASK,
119 Self::MotionBlockingNoLeaves => Self::MOTION_BLOCKING_NO_LEAVES_MASK,
120 Self::OceanFloor => Self::OCEAN_FLOOR_MASK,
121 Self::WorldSurfaceWg => Self::WORLD_SURFACE_WG_MASK,
122 Self::OceanFloorWg => Self::OCEAN_FLOOR_WG_MASK,
123 }
124 }
125}
126
127static WORLD_SURFACE_OPACITY_MASK_BY_STATE: LazyLock<Box<[u8]>> =
128 LazyLock::new(build_world_surface_opacity_masks);
129static HEIGHTMAP_OPACITY_MASK_BY_STATE: LazyLock<Box<[u8]>> =
130 LazyLock::new(build_state_opacity_masks);
131
132fn build_world_surface_opacity_masks() -> Box<[u8]> {
133 let mut masks = Vec::with_capacity(REGISTRY.blocks.state_to_block_lookup.len());
134 for (state_index, &block) in REGISTRY.blocks.state_to_block_lookup.iter().enumerate() {
135 let Ok(_) = u16::try_from(state_index) else {
136 panic!("block state registry exceeded BlockStateId range");
137 };
138 let mask = if block.config.is_air {
139 0
140 } else {
141 HeightmapType::WORLD_SURFACE_MASK | HeightmapType::WORLD_SURFACE_WG_MASK
142 };
143 masks.push(mask);
144 }
145 masks.into_boxed_slice()
146}
147
148fn build_state_opacity_masks() -> Box<[u8]> {
149 let mut masks = Vec::with_capacity(REGISTRY.blocks.state_to_block_lookup.len());
150 for (state_index, &block) in REGISTRY.blocks.state_to_block_lookup.iter().enumerate() {
151 let Ok(raw_state_id) = u16::try_from(state_index) else {
152 panic!("block state registry exceeded BlockStateId range");
153 };
154 let state = BlockStateId(raw_state_id);
155 let mut mask = 0;
156 if !block.config.is_air {
157 mask |= HeightmapType::WORLD_SURFACE_MASK | HeightmapType::WORLD_SURFACE_WG_MASK;
158
159 let blocks_motion = BlockStateExt::blocks_motion(&state);
160 if blocks_motion {
161 mask |= HeightmapType::OCEAN_FLOOR_MASK | HeightmapType::OCEAN_FLOOR_WG_MASK;
162 }
163
164 if blocks_motion || state.has_fluid() {
165 mask |= HeightmapType::MOTION_BLOCKING_MASK;
166 if !HeightmapType::is_leaves(block) {
167 mask |= HeightmapType::MOTION_BLOCKING_NO_LEAVES_MASK;
168 }
169 }
170 }
171 masks.push(mask);
172 }
173 masks.into_boxed_slice()
174}
175
176#[inline]
177fn heightmap_opacity_mask(state: BlockStateId, requested_mask: u8) -> u8 {
178 let world_surface_mask =
179 HeightmapType::WORLD_SURFACE_MASK | HeightmapType::WORLD_SURFACE_WG_MASK;
180 if requested_mask & !world_surface_mask == 0 {
181 let Some(&state_mask) = WORLD_SURFACE_OPACITY_MASK_BY_STATE.get(state.0 as usize) else {
182 panic!("invalid block state id {}", state.0);
183 };
184 return state_mask & requested_mask;
185 }
186
187 let Some(&state_mask) = HEIGHTMAP_OPACITY_MASK_BY_STATE.get(state.0 as usize) else {
188 panic!("invalid block state id {}", state.0);
189 };
190 state_mask & requested_mask
191}
192
193#[derive(Debug, Clone)]
198pub struct Heightmap {
199 data: Box<[u16; 256]>,
202 map_type: HeightmapType,
204 min_y: i32,
206 height: i32,
208}
209
210impl Heightmap {
211 #[must_use]
213 pub fn new(map_type: HeightmapType, min_y: i32, height: i32) -> Self {
214 Self {
215 data: Box::new([0; 256]),
216 map_type,
217 min_y,
218 height,
219 }
220 }
221
222 #[must_use]
224 pub const fn from_raw_data(
225 map_type: HeightmapType,
226 min_y: i32,
227 height: i32,
228 data: Box<[u16; 256]>,
229 ) -> Self {
230 Self {
231 data,
232 map_type,
233 min_y,
234 height,
235 }
236 }
237
238 #[must_use]
240 pub const fn heightmap_type(&self) -> HeightmapType {
241 self.map_type
242 }
243
244 #[inline]
246 const fn get_index(local_x: usize, local_z: usize) -> usize {
247 local_x + local_z * 16
248 }
249
250 #[must_use]
252 pub fn get_first_available(&self, local_x: usize, local_z: usize) -> i32 {
253 debug_assert!(local_x < 16 && local_z < 16);
254 let index = Self::get_index(local_x, local_z);
255 i32::from(self.data[index]) + self.min_y
256 }
257
258 #[must_use]
260 pub fn get_highest_taken(&self, local_x: usize, local_z: usize) -> i32 {
261 self.get_first_available(local_x, local_z) - 1
262 }
263
264 pub fn set_height(&mut self, local_x: usize, local_z: usize, height: i32) {
266 debug_assert!(local_x < 16 && local_z < 16);
267 let index = Self::get_index(local_x, local_z);
268 self.data[index] = (height - self.min_y) as u16;
269 }
270
271 pub fn update<F>(
282 &mut self,
283 local_x: usize,
284 y: i32,
285 local_z: usize,
286 state: BlockStateId,
287 get_block: F,
288 ) -> bool
289 where
290 F: Fn(usize, i32, usize) -> BlockStateId,
291 {
292 let first_available = self.get_first_available(local_x, local_z);
293
294 if y <= first_available - 2 {
296 return false;
297 }
298
299 if self.map_type.is_opaque(state) {
300 if y >= first_available {
302 self.set_height(local_x, local_z, y + 1);
303 return true;
304 }
305 } else if first_available - 1 == y {
306 for scan_y in (self.min_y..y).rev() {
308 let scan_state = get_block(local_x, scan_y, local_z);
309 if self.map_type.is_opaque(scan_state) {
310 self.set_height(local_x, local_z, scan_y + 1);
311 return true;
312 }
313 }
314 self.set_height(local_x, local_z, self.min_y);
316 return true;
317 }
318
319 false
320 }
321
322 pub fn update_for_initial_fill(
328 &mut self,
329 local_x: usize,
330 y: i32,
331 local_z: usize,
332 state: BlockStateId,
333 ) -> bool {
334 let first_available = self.get_first_available(local_x, local_z);
335 if self.map_type.is_opaque(state) && y >= first_available {
336 self.set_height(local_x, local_z, y + 1);
337 return true;
338 }
339
340 false
341 }
342
343 #[must_use]
347 pub fn raw_data(&self) -> &[u16; 256] {
348 &self.data
349 }
350
351 #[must_use]
356 pub fn get_raw_data(&self) -> Vec<i64> {
357 let bits_per_value = Self::calculate_bits_per_value(self.height);
358 let values_per_long = 64 / bits_per_value;
359 let num_longs = 256_usize.div_ceil(values_per_long);
360
361 let mut result = vec![0i64; num_longs];
362 let mask = (1u64 << bits_per_value) - 1;
363
364 for (i, &height) in self.data.iter().enumerate() {
365 let long_index = i / values_per_long;
366 let bit_offset = (i % values_per_long) * bits_per_value;
367 result[long_index] |= ((u64::from(height) & mask) << bit_offset) as i64;
368 }
369
370 result
371 }
372
373 #[inline]
375 const fn calculate_bits_per_value(height: i32) -> usize {
376 let max_value = height as u32 + 1;
379 if max_value <= 1 {
380 1
381 } else {
382 32 - (max_value - 1).leading_zeros() as usize
383 }
384 }
385}
386
387#[derive(Debug, Clone)]
395pub struct ChunkHeightmaps {
396 world_surface_wg: Option<Heightmap>,
397 ocean_floor_wg: Option<Heightmap>,
398 world_surface: Option<Heightmap>,
399 motion_blocking: Option<Heightmap>,
400 motion_blocking_no_leaves: Option<Heightmap>,
401 ocean_floor: Option<Heightmap>,
402}
403
404impl ChunkHeightmaps {
405 #[must_use]
407 pub const fn empty() -> Self {
408 Self {
409 world_surface_wg: None,
410 ocean_floor_wg: None,
411 world_surface: None,
412 motion_blocking: None,
413 motion_blocking_no_leaves: None,
414 ocean_floor: None,
415 }
416 }
417
418 #[must_use]
420 pub fn new(min_y: i32, height: i32) -> Self {
421 Self::with_types(HeightmapType::final_types(), min_y, height)
422 }
423
424 #[must_use]
426 pub fn with_types(types: &[HeightmapType], min_y: i32, height: i32) -> Self {
427 let mut heightmaps = Self::empty();
428 for &heightmap_type in types {
429 heightmaps.get_or_insert(heightmap_type, min_y, height);
430 }
431 heightmaps
432 }
433
434 #[must_use]
436 pub const fn get(&self, heightmap_type: HeightmapType) -> Option<&Heightmap> {
437 match heightmap_type {
438 HeightmapType::WorldSurfaceWg => self.world_surface_wg.as_ref(),
439 HeightmapType::OceanFloorWg => self.ocean_floor_wg.as_ref(),
440 HeightmapType::WorldSurface => self.world_surface.as_ref(),
441 HeightmapType::MotionBlocking => self.motion_blocking.as_ref(),
442 HeightmapType::MotionBlockingNoLeaves => self.motion_blocking_no_leaves.as_ref(),
443 HeightmapType::OceanFloor => self.ocean_floor.as_ref(),
444 }
445 }
446
447 #[must_use]
449 pub const fn get_mut(&mut self, heightmap_type: HeightmapType) -> Option<&mut Heightmap> {
450 match heightmap_type {
451 HeightmapType::WorldSurfaceWg => self.world_surface_wg.as_mut(),
452 HeightmapType::OceanFloorWg => self.ocean_floor_wg.as_mut(),
453 HeightmapType::WorldSurface => self.world_surface.as_mut(),
454 HeightmapType::MotionBlocking => self.motion_blocking.as_mut(),
455 HeightmapType::MotionBlockingNoLeaves => self.motion_blocking_no_leaves.as_mut(),
456 HeightmapType::OceanFloor => self.ocean_floor.as_mut(),
457 }
458 }
459
460 pub fn replace(&mut self, heightmap: Heightmap) {
462 let heightmap_type = heightmap.heightmap_type();
463 match heightmap_type {
464 HeightmapType::WorldSurfaceWg => self.world_surface_wg = Some(heightmap),
465 HeightmapType::OceanFloorWg => self.ocean_floor_wg = Some(heightmap),
466 HeightmapType::WorldSurface => self.world_surface = Some(heightmap),
467 HeightmapType::MotionBlocking => self.motion_blocking = Some(heightmap),
468 HeightmapType::MotionBlockingNoLeaves => {
469 self.motion_blocking_no_leaves = Some(heightmap);
470 }
471 HeightmapType::OceanFloor => self.ocean_floor = Some(heightmap),
472 }
473 }
474
475 fn get_or_insert(
477 &mut self,
478 heightmap_type: HeightmapType,
479 min_y: i32,
480 height: i32,
481 ) -> &mut Heightmap {
482 let slot = match heightmap_type {
483 HeightmapType::WorldSurfaceWg => &mut self.world_surface_wg,
484 HeightmapType::OceanFloorWg => &mut self.ocean_floor_wg,
485 HeightmapType::WorldSurface => &mut self.world_surface,
486 HeightmapType::MotionBlocking => &mut self.motion_blocking,
487 HeightmapType::MotionBlockingNoLeaves => &mut self.motion_blocking_no_leaves,
488 HeightmapType::OceanFloor => &mut self.ocean_floor,
489 };
490 slot.get_or_insert_with(|| Heightmap::new(heightmap_type, min_y, height))
491 }
492
493 #[must_use]
498 pub fn get_final(&self, heightmap_type: HeightmapType) -> &Heightmap {
499 if matches!(
500 heightmap_type,
501 HeightmapType::WorldSurfaceWg | HeightmapType::OceanFloorWg
502 ) {
503 panic!("worldgen heightmap {heightmap_type:?} is not a final chunk heightmap");
504 }
505 let Some(heightmap) = self.get(heightmap_type) else {
506 panic!("full chunk is missing required heightmap {heightmap_type:?}");
507 };
508 heightmap
509 }
510
511 #[must_use]
516 pub fn get_final_mut(&mut self, heightmap_type: HeightmapType) -> &mut Heightmap {
517 if matches!(
518 heightmap_type,
519 HeightmapType::WorldSurfaceWg | HeightmapType::OceanFloorWg
520 ) {
521 panic!("worldgen heightmap {heightmap_type:?} is not a final chunk heightmap");
522 }
523 let Some(heightmap) = self.get_mut(heightmap_type) else {
524 panic!("full chunk is missing required heightmap {heightmap_type:?}");
525 };
526 heightmap
527 }
528
529 pub fn update_final<F>(
534 &mut self,
535 local_x: usize,
536 y: i32,
537 local_z: usize,
538 state: BlockStateId,
539 get_block: F,
540 ) where
541 F: Fn(usize, i32, usize) -> BlockStateId + Copy,
542 {
543 for &heightmap_type in HeightmapType::final_types() {
544 self.get_final_mut(heightmap_type)
545 .update(local_x, y, local_z, state, get_block);
546 }
547 }
548
549 fn set_primed_height(
550 &mut self,
551 heightmap_type: HeightmapType,
552 local_x: usize,
553 local_z: usize,
554 height: i32,
555 ) {
556 let Some(heightmap) = self.get_mut(heightmap_type) else {
557 panic!("heightmap {heightmap_type:?} missing after priming");
558 };
559 heightmap.set_height(local_x, local_z, height);
560 }
561
562 pub fn prime_from_sections(
564 &mut self,
565 types: &[HeightmapType],
566 min_y: i32,
567 height: i32,
568 sections: &[super::section::SectionHolder],
569 ) {
570 let mut types_to_prime = SmallVec::<[(HeightmapType, u8); 4]>::new();
571 let mut pending_mask_base = 0;
572 for &hm_type in types {
573 if self.get(hm_type).is_none() {
574 let mask = hm_type.mask();
575 types_to_prime.push((hm_type, mask));
576 pending_mask_base |= mask;
577 }
578 }
579
580 if types_to_prime.is_empty() {
581 return;
582 }
583
584 for &(hm_type, _) in &types_to_prime {
585 self.get_or_insert(hm_type, min_y, height);
586 }
587
588 let mut pending_masks = [pending_mask_base; 16 * 16];
589 let mut pending_columns = pending_masks.len();
590
591 'sections: for section_idx in (0..sections.len()).rev() {
592 let guard = sections[section_idx].read();
593 if matches!(
594 &guard.states,
595 super::paletted_container::BlockPalette::Homogeneous(state) if state.is_air()
596 ) {
597 continue;
598 }
599
600 for local_y in (0..16).rev() {
602 let y = min_y + (section_idx * 16 + local_y) as i32;
603 let layer_start = local_y * 16 * 16;
604
605 for (column_index, pending_mask) in pending_masks.iter_mut().enumerate() {
606 if *pending_mask == 0 {
607 continue;
608 }
609
610 let state = guard.states.get_at_index(layer_start + column_index);
611 let matched_mask = heightmap_opacity_mask(state, *pending_mask);
612 if matched_mask == 0 {
613 continue;
614 }
615
616 let x = column_index % 16;
617 let z = column_index / 16;
618 for &(hm_type, mask) in &types_to_prime {
619 if matched_mask & mask != 0 {
620 self.set_primed_height(hm_type, x, z, y + 1);
621 }
622 }
623 *pending_mask &= !matched_mask;
624 if *pending_mask == 0 {
625 pending_columns -= 1;
626 }
627 }
628
629 if pending_columns == 0 {
630 break 'sections;
631 }
632 }
633 }
634 }
635}
636
637impl Default for ChunkHeightmaps {
638 fn default() -> Self {
639 Self::empty()
640 }
641}
642
643#[cfg(test)]
644mod tests {
645 use std::sync::Once;
646
647 use steel_registry::{
648 blocks::{block_state_ext::BlockStateExt, properties::BlockStateProperties},
649 init_vanilla_registry, vanilla_blocks,
650 };
651
652 use crate::behavior::init_behaviors;
653 use crate::chunk::section::{ChunkSection, Sections};
654
655 use super::*;
656
657 static INIT_BEHAVIORS: Once = Once::new();
658
659 fn init_test_state() {
660 init_vanilla_registry();
661 INIT_BEHAVIORS.call_once(init_behaviors);
662 }
663
664 #[test]
665 fn test_bits_per_value() {
666 assert_eq!(Heightmap::calculate_bits_per_value(384), 9);
668 assert_eq!(Heightmap::calculate_bits_per_value(256), 9);
670 assert_eq!(Heightmap::calculate_bits_per_value(16), 5);
672 }
673
674 #[test]
675 fn test_get_index() {
676 assert_eq!(Heightmap::get_index(0, 0), 0);
677 assert_eq!(Heightmap::get_index(15, 0), 15);
678 assert_eq!(Heightmap::get_index(0, 1), 16);
679 assert_eq!(Heightmap::get_index(15, 15), 255);
680 }
681
682 #[test]
683 fn heightmap_predicates_use_blocks_motion_and_fluid_state() {
684 init_test_state();
685
686 let water = REGISTRY.blocks.get_default_state_id(&vanilla_blocks::WATER);
687 assert!(!HeightmapType::OceanFloorWg.is_opaque(water));
688 assert!(HeightmapType::MotionBlocking.is_opaque(water));
689
690 let slab = REGISTRY
691 .blocks
692 .get_default_state_id(&vanilla_blocks::OAK_SLAB);
693 let waterlogged_slab = slab.set_value(&BlockStateProperties::WATERLOGGED, true);
694 assert!(waterlogged_slab.has_fluid());
695 assert!(HeightmapType::MotionBlocking.is_opaque(waterlogged_slab));
696
697 let cobweb = REGISTRY
698 .blocks
699 .get_default_state_id(&vanilla_blocks::COBWEB);
700 assert!(!HeightmapType::OceanFloorWg.is_opaque(cobweb));
701 }
702
703 #[test]
704 fn initial_fill_update_tracks_only_matching_blocks() {
705 init_test_state();
706
707 let water = REGISTRY.blocks.get_default_state_id(&vanilla_blocks::WATER);
708 let stone = REGISTRY.blocks.get_default_state_id(&vanilla_blocks::STONE);
709
710 let mut ocean_floor = Heightmap::new(HeightmapType::OceanFloorWg, 0, 16);
711 assert!(!ocean_floor.update_for_initial_fill(0, 12, 0, water));
712 assert_eq!(ocean_floor.get_first_available(0, 0), 0);
713
714 assert!(ocean_floor.update_for_initial_fill(0, 5, 0, stone));
715 assert_eq!(ocean_floor.get_first_available(0, 0), 6);
716
717 assert!(!ocean_floor.update_for_initial_fill(0, 4, 0, stone));
718 assert_eq!(ocean_floor.get_first_available(0, 0), 6);
719 }
720
721 #[test]
722 fn section_priming_preserves_heightmap_predicates_across_sections() {
723 init_test_state();
724
725 let stone = REGISTRY.blocks.get_default_state_id(&vanilla_blocks::STONE);
726 let water = REGISTRY.blocks.get_default_state_id(&vanilla_blocks::WATER);
727 let leaves = REGISTRY
728 .blocks
729 .get_default_state_id(&vanilla_blocks::OAK_LEAVES);
730 let cobweb = REGISTRY
731 .blocks
732 .get_default_state_id(&vanilla_blocks::COBWEB);
733 let mut lower = ChunkSection::new_empty();
734 let mut upper = ChunkSection::new_empty();
735
736 lower.set_block_state(0, 15, 0, stone);
737 upper.set_block_state(0, 10, 0, water);
738 upper.set_block_state(1, 4, 2, stone);
739 upper.set_block_state(1, 12, 2, leaves);
740 upper.set_block_state(3, 3, 4, stone);
741 upper.set_block_state(3, 14, 4, cobweb);
742
743 let sections = Sections::from_owned(vec![lower, upper].into_boxed_slice());
744 let mut heightmaps = ChunkHeightmaps::empty();
745 heightmaps.prime_from_sections(
746 &[
747 HeightmapType::WorldSurface,
748 HeightmapType::MotionBlocking,
749 HeightmapType::MotionBlockingNoLeaves,
750 HeightmapType::OceanFloor,
751 HeightmapType::WorldSurfaceWg,
752 HeightmapType::OceanFloorWg,
753 ],
754 -16,
755 32,
756 §ions.sections,
757 );
758
759 let first_available = |heightmap_type, x, z| {
760 let Some(heightmap) = heightmaps.get(heightmap_type) else {
761 panic!("heightmap {heightmap_type:?} was not primed");
762 };
763 heightmap.get_first_available(x, z)
764 };
765
766 assert_eq!(first_available(HeightmapType::WorldSurface, 0, 0), 11);
767 assert_eq!(first_available(HeightmapType::MotionBlocking, 0, 0), 11);
768 assert_eq!(
769 first_available(HeightmapType::MotionBlockingNoLeaves, 0, 0),
770 11
771 );
772 assert_eq!(first_available(HeightmapType::OceanFloor, 0, 0), 0);
773 assert_eq!(first_available(HeightmapType::WorldSurfaceWg, 0, 0), 11);
774 assert_eq!(first_available(HeightmapType::OceanFloorWg, 0, 0), 0);
775
776 assert_eq!(first_available(HeightmapType::WorldSurface, 1, 2), 13);
777 assert_eq!(first_available(HeightmapType::MotionBlocking, 1, 2), 13);
778 assert_eq!(
779 first_available(HeightmapType::MotionBlockingNoLeaves, 1, 2),
780 5
781 );
782 assert_eq!(first_available(HeightmapType::OceanFloor, 1, 2), 13);
783
784 assert_eq!(first_available(HeightmapType::WorldSurface, 3, 4), 15);
785 assert_eq!(first_available(HeightmapType::MotionBlocking, 3, 4), 4);
786 assert_eq!(first_available(HeightmapType::OceanFloor, 3, 4), 4);
787 assert_eq!(first_available(HeightmapType::WorldSurface, 15, 15), -16);
788 }
789}