diff options
Diffstat (limited to 'src/proc_gen/mod.rs')
| -rw-r--r-- | src/proc_gen/mod.rs | 131 |
1 files changed, 104 insertions, 27 deletions
diff --git a/src/proc_gen/mod.rs b/src/proc_gen/mod.rs index 4a1003a..abdd0b8 100644 --- a/src/proc_gen/mod.rs +++ b/src/proc_gen/mod.rs @@ -1,19 +1,29 @@ pub mod herringbone; pub mod tileset_loader; +use std::{ + cmp::Ordering, + collections::HashMap, + ops::{Add, Mul, Sub}, +}; + +use fxhash::FxHashMap; use glam::IVec2; use crate::{ config::{CHUNK_SIZE, TILESET_SCALING}, proc_gen::{ herringbone::{split_position, variant_index}, - tileset_loader::{Tile, TileOrientation, Tileset, TilesetPixelType, load_tileset}, + tileset_loader::{Tile, TileOrientation, TilePixelType, Tileset, load_tileset}, }, sim::{cell::Cell, cell_manager::chunk::Chunk, entity::EntityDef}, }; #[inline] -fn lerp(a: f32, b: f32, t: f32) -> f32 { +fn lerp<T>(a: T, b: T, t: f32) -> T +where + T: Copy + Add<Output = T> + Sub<Output = T> + Mul<f32, Output = T>, +{ a + (b - a) * t } @@ -22,11 +32,16 @@ const PIXELS_NEIGHBOURHOOD_SIZE: i32 = CHUNK_PIXELS + 2; // 2d slice of solidity; chunk + 1 margin struct BiomeChunkContext { - instantaneous_solidity: [f32; (PIXELS_NEIGHBOURHOOD_SIZE * PIXELS_NEIGHBOURHOOD_SIZE) as usize], + pixels: [TilePixelType; (PIXELS_NEIGHBOURHOOD_SIZE * PIXELS_NEIGHBOURHOOD_SIZE) as usize], +} + +pub struct InterpolatedPixel { + pub interpolated_pixel_index: u8, + pub interpolated_solidity: f32, } impl BiomeChunkContext { - fn lerped_solidity_at(&self, local: IVec2) -> f32 { + fn interpolated_at(&self, local: IVec2, tileset: &Tileset) -> InterpolatedPixel { // pixel coord centre of cell coord local let pixel_position = (local.as_vec2() + 0.5) / TILESET_SCALING as f32 + 0.5; let pixel = pixel_position.floor(); @@ -34,22 +49,86 @@ impl BiomeChunkContext { let frac = pixel_position - pixel; let pixel = pixel.as_ivec2(); - let at = |dx, dy| { - self.instantaneous_solidity - [((pixel.x + dx) + (pixel.y + dy) * PIXELS_NEIGHBOURHOOD_SIZE) as usize] + let instantaneous_pixel_at = |dx, dy| { + self.pixels[((pixel.x + dx) + (pixel.y + dy) * PIXELS_NEIGHBOURHOOD_SIZE) as usize] + }; + + let instantaneous_idx_at = |dx, dy| match instantaneous_pixel_at(dx, dy) { + TilePixelType::Void => 255, + TilePixelType::Terrain(_) => 0, + TilePixelType::Custom(i) => i, + }; + + let instantaneous_solidity_at = |dx, dy| match instantaneous_pixel_at(dx, dy) { + TilePixelType::Void => 0.0, + TilePixelType::Terrain(o) => o, + TilePixelType::Custom(i) => { + tileset.manifest.palette.get(&i).map_or(0.0, |p| p.solidity) + } }; // linearly interpolate between the solidity of the horizontal cells above and below according to the fractional x component - let top = lerp(at(0, 0), at(1, 0), frac.x); - let bottom = lerp(at(0, 1), at(1, 1), frac.x); + let s_top = lerp( + instantaneous_solidity_at(0, 0), + instantaneous_solidity_at(1, 0), + frac.x, + ); + let s_bottom = lerp( + instantaneous_solidity_at(0, 1), + instantaneous_solidity_at(1, 1), + frac.x, + ); // and then interpolate between those according to the y component - lerp(top, bottom, frac.y) - 0.5 + let interpolated_solidity = lerp(s_top, s_bottom, frac.y) - 0.5; + + // same for the pixel + // TODO don't allocate per-cell + let mut idxes: Vec<u8> = Vec::new(); + let tl = instantaneous_idx_at(0, 0); + if tl != 255 && !idxes.contains(&tl) { + idxes.push(tl); + } + let tr = instantaneous_idx_at(1, 0); + if tr != 255 && !idxes.contains(&tr) { + idxes.push(tr); + } + let bl = instantaneous_idx_at(0, 1); + if bl != 255 && !idxes.contains(&bl) { + idxes.push(bl); + } + let br = instantaneous_idx_at(1, 1); + if br != 255 && !idxes.contains(&br) { + idxes.push(br); + } + + let idx_strengths = idxes.iter().map(|&i| { + let i_top = lerp( + if tl == i { 1.0 } else { 0.0 }, + if tr == i { 1.0 } else { 0.0 }, + frac.x, + ); + let i_bottom = lerp( + if bl == i { 1.0 } else { 0.0 }, + if br == i { 1.0 } else { 0.0 }, + frac.x, + ); + + let interpolated_strength = lerp(i_top, i_bottom, frac.y); + (i, interpolated_strength) + }); + + InterpolatedPixel { + interpolated_pixel_index: idx_strengths + .max_by(|&(_, s1), &(_, s2)| s1.total_cmp(&s2)) + .map_or(255, |(i, _)| i), + interpolated_solidity, + } } } pub trait Biome { - fn cell(&self, solidity: f32, world: IVec2) -> Cell; + fn fragment(&self, pixel: InterpolatedPixel, world: IVec2) -> Cell; fn derive_entities_from_tile(&self, _tile: &Tile) -> Vec<EntityDef> { Vec::new() @@ -69,9 +148,9 @@ impl TilesetWorldGenerator { } } - fn pixel_at(&self, pixel: IVec2) -> TilesetPixelType { + fn pixel_at(&self, pixel: IVec2) -> TilePixelType { let (grid, local, orientation) = - split_position(pixel, self.tileset.dimensions.short as i32); + split_position(pixel, self.tileset.manifest.dimensions.short as i32); let tiles = match orientation { TileOrientation::Horizontal => &self.tileset.horizontal_tiles, @@ -81,39 +160,37 @@ impl TilesetWorldGenerator { tiles[variant_index(grid, tiles.len())].pixel_at(local) } - fn structure_around(&self, chunk_position: IVec2) -> BiomeChunkContext { + fn get_biome_chunk_context(&self, chunk_position: IVec2) -> BiomeChunkContext { let origin = chunk_position * CHUNK_PIXELS - 1; - let mut occupancy = [0.0; (PIXELS_NEIGHBOURHOOD_SIZE * PIXELS_NEIGHBOURHOOD_SIZE) as usize]; + let mut pixels = + [TilePixelType::Void; (PIXELS_NEIGHBOURHOOD_SIZE * PIXELS_NEIGHBOURHOOD_SIZE) as usize]; for y in 0..PIXELS_NEIGHBOURHOOD_SIZE { for x in 0..PIXELS_NEIGHBOURHOOD_SIZE { - occupancy[(x + y * PIXELS_NEIGHBOURHOOD_SIZE) as usize] = - match self.pixel_at(origin + IVec2::new(x, y)) { - TilesetPixelType::Void => 0.0, - TilesetPixelType::Terrain => 1.0, - }; + pixels[(x + y * PIXELS_NEIGHBOURHOOD_SIZE) as usize] = + self.pixel_at(origin + IVec2::new(x, y)); } } - BiomeChunkContext { - instantaneous_solidity: occupancy, - } + BiomeChunkContext { pixels } } pub fn generate_chunk(&self, chunk_position: IVec2) -> Chunk { let mut chunk = Chunk::void(); - let structure = self.structure_around(chunk_position); + let biome_chunk_context = self.get_biome_chunk_context(chunk_position); let chunk_min = chunk_position * CHUNK_SIZE; for y in 0..CHUNK_SIZE { for x in 0..CHUNK_SIZE { let local = IVec2::new(x, y); - chunk.cells[(x + y * CHUNK_SIZE) as usize] = self - .biome - .cell(structure.lerped_solidity_at(local), chunk_min + local); + chunk.cells[(x + y * CHUNK_SIZE) as usize] = self.biome.fragment( + biome_chunk_context.interpolated_at(local, &self.tileset), + chunk_min + local, + ); } } + chunk.sleeping = false; chunk.mark_collider_dirty(0); chunk |
