diff options
| -rw-r--r-- | src/content/world/mines.rs | 126 | ||||
| -rw-r--r-- | src/main.rs | 2 | ||||
| -rw-r--r-- | src/proc_gen/herringbone.rs | 29 | ||||
| -rw-r--r-- | src/proc_gen/mod.rs | 133 |
4 files changed, 109 insertions, 181 deletions
diff --git a/src/content/world/mines.rs b/src/content/world/mines.rs index c9c0aad..d7e23bc 100644 --- a/src/content/world/mines.rs +++ b/src/content/world/mines.rs @@ -1,129 +1,33 @@ -use glam::{IVec2, Vec2}; +use fastnoise_lite::{FastNoiseLite, FractalType}; +use glam::IVec2; -use crate::{ - config::TILESET_SCALING, - content::materials::MaterialId, - proc_gen::{ - Biome, TileCells, - tileset_loader::{Tile, TilesetPixelType}, - }, - sim::{cell::Cell, lib::marching_squares::compute_types}, -}; +use crate::{content::materials::MaterialId, proc_gen::Biome, sim::cell::Cell}; -const FULL: [bool; (TILESET_SCALING * TILESET_SCALING) as usize] = - [true; (TILESET_SCALING * TILESET_SCALING) as usize]; - -const EMPTY: [bool; (TILESET_SCALING * TILESET_SCALING) as usize] = - [false; (TILESET_SCALING * TILESET_SCALING) as usize]; +const ROUGHNESS: f32 = 0.375; pub struct MinesBiome { - pixel_expansion_by_type: [[bool; (TILESET_SCALING * TILESET_SCALING) as usize]; 16], -} - -fn circular_pixel_expansion( - c: Vec2, - r: f32, -) -> [bool; (TILESET_SCALING * TILESET_SCALING) as usize] { - let rs = r.powi(2); - let mut ret = [false; (TILESET_SCALING * TILESET_SCALING) as usize]; - - for y in 0..TILESET_SCALING { - for x in 0..TILESET_SCALING { - // definitely can be optimized a lot - let dist = Vec2::new(x as f32, y as f32).distance_squared(c); - if dist <= rs { - ret[(x + y * TILESET_SCALING) as usize] = true; - } - } - } - - ret + pub edge_roughness: FastNoiseLite, } impl MinesBiome { pub fn new() -> Self { - MinesBiome { - pixel_expansion_by_type: [ - // empty - EMPTY, - // BL - circular_pixel_expansion( - Vec2::new(0.0, TILESET_SCALING as f32 - 1.0), - TILESET_SCALING as f32, - ), - // BR - circular_pixel_expansion( - Vec2::new(TILESET_SCALING as f32 - 1.0, TILESET_SCALING as f32 - 1.0), - TILESET_SCALING as f32, - ), - // BL BR - FULL, - // TR - circular_pixel_expansion( - Vec2::new(TILESET_SCALING as f32 - 1.0, 0.0), - TILESET_SCALING as f32, - ), - // TR BL - FULL, - // TR BR - FULL, - // TR BR BL - FULL, - // TL - circular_pixel_expansion(Vec2::new(0.0, 0.0), TILESET_SCALING as f32), - // TL BL - FULL, - // TL BR - FULL, - // TL BR BL - FULL, - // TL TR - FULL, - // TL TR BL - FULL, - // TL TR BR - FULL, - // surrounded - FULL, - ], - } + let mut edge_roughness = FastNoiseLite::new(); + edge_roughness.octaves = 2; + edge_roughness.fractal_type = FractalType::Ridged; + MinesBiome { edge_roughness } } } impl Biome for MinesBiome { - fn derive_world_from_tile(&self, tile: &Tile) -> TileCells { - let pixels = tile.size(); - let marched_pixels = compute_types(tile); - let size = pixels * TILESET_SCALING; - let mut cells = vec![Cell::void(); (size.x * size.y) as usize]; - - for py in 0..pixels.y { - for px in 0..pixels.x { - let variant = marched_pixels[(px + py * (pixels.x + 1)) as usize]; - - let expansion = self.pixel_expansion_by_type[variant as usize]; - // TODO optimize - let expansion_cells = expansion.map(|c| { - if c { - Cell::from_material(MaterialId::Dirt) - } else { - Cell::void() - } - }); + fn cell(&self, solidity: f32, world: IVec2) -> Cell { + let world = world.as_vec2(); - let origin = IVec2::new(px, py) * TILESET_SCALING; - for i in 0..TILESET_SCALING { - let dst_y = i + origin.y; - let dst_row = (dst_y * size.x + origin.x) as usize; - let src_row = (i * TILESET_SCALING) as usize; + let displacement = ROUGHNESS * self.edge_roughness.get_noise_2d(world.x, world.y); - cells[dst_row..dst_row + TILESET_SCALING as usize].copy_from_slice( - &expansion_cells[src_row..src_row + TILESET_SCALING as usize], - ); - } - } + if solidity + displacement <= 0.0 { + return Cell::void(); } - TileCells { size, cells } + Cell::from_material(MaterialId::Dirt) } } diff --git a/src/main.rs b/src/main.rs index e9cae2d..1c7abb1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -150,7 +150,7 @@ impl Default for App { sim_manager: Some(SimManager::new()), world_generator: TilesetWorldGenerator::new( "assets/tilesets/tileset1", - &MinesBiome::new(), + Box::new(MinesBiome::new()), ), last_render: Instant::now(), 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); } } |
