diff options
Diffstat (limited to 'src/proc_gen')
| -rw-r--r-- | src/proc_gen/herringbone.rs | 29 | ||||
| -rw-r--r-- | src/proc_gen/mod.rs | 133 |
2 files changed, 93 insertions, 69 deletions
diff --git a/src/proc_gen/herringbone.rs b/src/proc_gen/herringbone.rs index e8569c8..0a67b0c 100644 --- a/src/proc_gen/herringbone.rs +++ b/src/proc_gen/herringbone.rs @@ -3,22 +3,27 @@ use glam::IVec2; use crate::proc_gen::tileset_loader::TileOrientation; -fn tile_starting_at(grid: IVec2) -> Option<TileOrientation> { +pub fn split_position(pixel: IVec2, short: i32) -> (IVec2, IVec2, TileOrientation) { + let grid = pixel.div_euclid(IVec2::splat(short)); + let local = pixel.rem_euclid(IVec2::splat(short)); + match (grid.x - grid.y).rem_euclid(4) { - // 1 is the right half of the horizontal tile starting at 0, - // 2 is the bottom half of the vertical tile starting at 3 - 0 => Some(TileOrientation::Horizontal), - 3 => Some(TileOrientation::Vertical), - _ => None, + 0 => (grid, local, TileOrientation::Horizontal), + 1 => ( + grid - IVec2::X, + local + IVec2::new(short, 0), + TileOrientation::Horizontal, + ), + 3 => (grid, local, TileOrientation::Vertical), + 2 => ( + grid - IVec2::Y, + local + IVec2::new(0, short), + TileOrientation::Vertical, + ), + _ => unreachable!(), } } -pub fn tiles_overlapping(min: IVec2, max: IVec2) -> impl Iterator<Item = (IVec2, TileOrientation)> { - ((min.y - 1)..max.y) - .flat_map(move |y| ((min.x - 1)..max.x).map(move |x| IVec2::new(x, y))) - .filter_map(|grid| tile_starting_at(grid).map(|orientation| (grid, orientation))) -} - // better "%" for hashes since it prefers the high bits #[inline] fn reduce(h: u32, n: usize) -> usize { diff --git a/src/proc_gen/mod.rs b/src/proc_gen/mod.rs index caec241..4a1003a 100644 --- a/src/proc_gen/mod.rs +++ b/src/proc_gen/mod.rs @@ -6,92 +6,111 @@ use glam::IVec2; use crate::{ config::{CHUNK_SIZE, TILESET_SCALING}, proc_gen::{ - herringbone::{tiles_overlapping, variant_index}, - tileset_loader::{Tile, TileOrientation, Tileset, load_tileset}, + herringbone::{split_position, variant_index}, + tileset_loader::{Tile, TileOrientation, Tileset, TilesetPixelType, load_tileset}, }, sim::{cell::Cell, cell_manager::chunk::Chunk, entity::EntityDef}, }; -pub struct TileCells { - pub size: IVec2, - pub cells: Vec<Cell>, +#[inline] +fn lerp(a: f32, b: f32, t: f32) -> f32 { + a + (b - a) * t +} + +const CHUNK_PIXELS: i32 = CHUNK_SIZE / TILESET_SCALING; +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], +} + +impl BiomeChunkContext { + fn lerped_solidity_at(&self, local: IVec2) -> f32 { + // 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(); + // distance from the current pixel to the centre of the cell + 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] + }; + + // 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); + + // and then interpolate between those according to the y component + lerp(top, bottom, frac.y) - 0.5 + } } pub trait Biome { - fn derive_world_from_tile(&self, tile: &Tile) -> TileCells; - fn derive_entities_from_tile(&self, tile: &Tile) -> Vec<EntityDef> { + fn cell(&self, solidity: f32, world: IVec2) -> Cell; + + fn derive_entities_from_tile(&self, _tile: &Tile) -> Vec<EntityDef> { Vec::new() } } pub struct TilesetWorldGenerator { tileset: Tileset, - horizontal_tiles: Vec<TileCells>, - vertical_tiles: Vec<TileCells>, + biome: Box<dyn Biome>, } impl TilesetWorldGenerator { - pub fn new(tileset_path: &str, biome: &dyn Biome) -> Self { - let tileset = load_tileset(tileset_path); - - let horizontal_tiles = tileset - .horizontal_tiles - .iter() - .map(|tile| biome.derive_world_from_tile(tile)) - .collect(); - let vertical_tiles = tileset - .vertical_tiles - .iter() - .map(|tile| biome.derive_world_from_tile(tile)) - .collect(); - + pub fn new(tileset_path: &str, biome: Box<dyn Biome>) -> Self { TilesetWorldGenerator { - tileset, - horizontal_tiles, - vertical_tiles, + tileset: load_tileset(tileset_path), + biome, } } - fn grid_size(&self) -> IVec2 { - IVec2::splat(self.tileset.dimensions.short as i32 * TILESET_SCALING) - } + fn pixel_at(&self, pixel: IVec2) -> TilesetPixelType { + let (grid, local, orientation) = + split_position(pixel, self.tileset.dimensions.short as i32); - fn tile_at(&self, grid: IVec2, orientation: TileOrientation) -> &TileCells { let tiles = match orientation { - TileOrientation::Horizontal => &self.horizontal_tiles, - TileOrientation::Vertical => &self.vertical_tiles, + TileOrientation::Horizontal => &self.tileset.horizontal_tiles, + TileOrientation::Vertical => &self.tileset.vertical_tiles, }; - &tiles[variant_index(grid, tiles.len())] + + tiles[variant_index(grid, tiles.len())].pixel_at(local) } - pub fn generate_chunk(&self, chunk_position: IVec2) -> Chunk { - let mut chunk = Chunk::void(); + fn structure_around(&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 chunk_min = chunk_position * CHUNK_SIZE; - let chunk_max = chunk_min + CHUNK_SIZE; - - let grid = self.grid_size(); - let tiles = tiles_overlapping( - chunk_min.div_euclid(grid), - (chunk_max - 1).div_euclid(grid) + 1, - ); + 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, + }; + } + } - for (grid_position, orientation) in tiles { - let tile = self.tile_at(grid_position, orientation); - let tile_min = grid_position * grid; + BiomeChunkContext { + instantaneous_solidity: occupancy, + } + } - // subset of tile in chunk - let min = tile_min.max(chunk_min); - let max = (tile_min + tile.size).min(chunk_max); - if min.x >= max.x { - continue; - } - let width = (max.x - min.x) as usize; + pub fn generate_chunk(&self, chunk_position: IVec2) -> Chunk { + let mut chunk = Chunk::void(); + let structure = self.structure_around(chunk_position); + let chunk_min = chunk_position * CHUNK_SIZE; - for y in min.y..max.y { - let src = ((y - tile_min.y) * tile.size.x + (min.x - tile_min.x)) as usize; - let dst = ((y - chunk_min.y) * CHUNK_SIZE + (min.x - chunk_min.x)) as usize; - chunk.cells[dst..dst + width].copy_from_slice(&tile.cells[src..src + width]); + 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); } } |
