diff options
Diffstat (limited to 'src/proc_gen')
| -rw-r--r-- | src/proc_gen/herringbone.rs | 4 | ||||
| -rw-r--r-- | src/proc_gen/mod.rs | 131 | ||||
| -rw-r--r-- | src/proc_gen/tileset_loader.rs | 92 |
3 files changed, 142 insertions, 85 deletions
diff --git a/src/proc_gen/herringbone.rs b/src/proc_gen/herringbone.rs index 0a67b0c..a01c010 100644 --- a/src/proc_gen/herringbone.rs +++ b/src/proc_gen/herringbone.rs @@ -3,6 +3,8 @@ use glam::IVec2; use crate::proc_gen::tileset_loader::TileOrientation; +const SEED: u32 = 800; + 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)); @@ -31,5 +33,5 @@ fn reduce(h: u32, n: usize) -> usize { } pub fn variant_index(grid: IVec2, variants: usize) -> usize { - reduce(hash32(&grid), variants) + reduce(hash32(&(grid, SEED)), variants) } 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 diff --git a/src/proc_gen/tileset_loader.rs b/src/proc_gen/tileset_loader.rs index e0d9d18..24bb053 100644 --- a/src/proc_gen/tileset_loader.rs +++ b/src/proc_gen/tileset_loader.rs @@ -2,8 +2,6 @@ use glam::IVec2; use serde::Deserialize; use std::{collections::HashMap, error::Error, fs::File, io::BufReader, path::Path}; -use crate::sim::lib::marching_squares::Marchable; - fn load_img(path: &Path) -> Result<(Vec<u8>, u32, u32), Box<dyn Error>> { let mut decoder = png::Decoder::new(BufReader::new(File::open(path)?)); decoder.set_transformations(png::Transformations::IDENTITY); @@ -35,16 +33,22 @@ pub struct TileDimensions { pub long: u32, } -#[derive(Deserialize, Clone, Copy, Debug, PartialEq, Eq)] -pub enum TilesetPixelType { - Void = 0, - Terrain, +#[derive(Deserialize, Clone, Copy, Debug)] +pub enum TilePixelType { + Void, + Terrain(f32), + Custom(u8), +} + +#[derive(Deserialize, Debug)] +pub struct PaletteEntry { + pub solidity: f32, } #[derive(Deserialize, Debug)] -struct TilesetManifest { - dimensions: TileDimensions, - palette: HashMap<u8, TilesetPixelType>, +pub struct TilesetManifest { + pub dimensions: TileDimensions, + pub palette: HashMap<u8, PaletteEntry>, } fn load_manifest(path: &Path) -> Result<TilesetManifest, Box<dyn Error>> { @@ -55,39 +59,7 @@ fn load_manifest(path: &Path) -> Result<TilesetManifest, Box<dyn Error>> { pub struct Tile { pub orientation: TileOrientation, pub dimensions: TileDimensions, - pub pixels: Vec<TilesetPixelType>, -} - -// TODO might want to use newtypes to hoist to biome -impl Marchable for Tile { - fn occupied(&self, pos: IVec2) -> bool { - // TODO this will break for tileset dimensions other than 22x44 - if pos.x < 0 || pos.x >= self.size().x || pos.y < 0 || pos.y >= self.size().y { - match self.orientation { - TileOrientation::Horizontal => { - let a = (pos.x == -1 || pos.x == (self.dimensions.long as i32)) - && pos.y >= 8 - && pos.y <= 13; - let b = (pos.y == -1 || pos.y == (self.dimensions.short as i32)) - && ((pos.x >= 8 && pos.x <= 13) || (pos.x >= 30 && pos.x <= 35)); - !(a || b) - } - TileOrientation::Vertical => { - let a = (pos.y == -1 || pos.y == (self.dimensions.long as i32)) - && pos.x >= 8 - && pos.x <= 13; - let b = (pos.x == -1 || pos.x == (self.dimensions.short as i32)) - && ((pos.y >= 8 && pos.y <= 13) || (pos.y >= 30 && pos.y <= 35)); - !(a || b) - } - } - } else { - self.pixel_at(pos) == TilesetPixelType::Terrain - } - } - fn marchable_size(&self) -> IVec2 { - self.size() - } + pub pixels: Vec<TilePixelType>, } impl Tile { @@ -101,14 +73,14 @@ impl Tile { } #[inline] - pub fn pixel_at(&self, position: IVec2) -> TilesetPixelType { + pub fn pixel_at(&self, position: IVec2) -> TilePixelType { self.pixels[(position.x + position.y * self.size().x) as usize] } } #[derive(Debug)] pub struct Tileset { - pub dimensions: TileDimensions, + pub manifest: TilesetManifest, pub vertical_tiles: Vec<Tile>, pub horizontal_tiles: Vec<Tile>, } @@ -136,8 +108,7 @@ fn parse_row( break; } let left_border = img.0[(x - 1 + y * img.1) as usize]; - // 2 = border color - if left_border != 2 { + if left_border != 9 { break; } @@ -150,16 +121,23 @@ fn parse_row( for py in y..ey { for px in x..ex { let idx = img.0[(px + py * img.1) as usize]; - let palette = manifest.palette.get(&idx); - match palette { - Some(pixel_type) => tile_pixels.push(*pixel_type), - None => { - return Err(format!( - "Pixel ({px},{py}) resolved to idx {idx} which isn't in the palette" - )); - } - } + let pixel_type = match idx { + 0 => TilePixelType::Void, + 1 => TilePixelType::Terrain(0.125), + 2 => TilePixelType::Terrain(0.25), + 3 => TilePixelType::Terrain(0.375), + 4 => TilePixelType::Terrain(0.5), + 5 => TilePixelType::Terrain(0.625), + 6 => TilePixelType::Terrain(0.75), + 7 => TilePixelType::Terrain(0.875), + 8 => TilePixelType::Terrain(1.0), + // 9 = border + 9 => unreachable!(), + _ => TilePixelType::Custom(idx), + }; + + tile_pixels.push(pixel_type); } } @@ -190,7 +168,7 @@ fn parse_tiles( break; } - let edge = img.0[((y) * img.1) as usize] == 2; + let edge = img.0[((y) * img.1) as usize] == 9; if !edge { y += 1; continue; @@ -242,7 +220,7 @@ pub fn load_tileset(path: &str) -> Tileset { parse_tiles(&img_result, &manifest).unwrap(); Tileset { - dimensions: manifest.dimensions, + manifest, vertical_tiles, horizontal_tiles, } |
