pub mod herringbone; pub mod tileset_loader; 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}, }, sim::{cell::Cell, cell_manager::chunk::Chunk, entity::EntityDef}, }; #[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 cell(&self, solidity: f32, world: IVec2) -> Cell; fn derive_entities_from_tile(&self, _tile: &Tile) -> Vec { Vec::new() } } pub struct TilesetWorldGenerator { tileset: Tileset, biome: Box, } impl TilesetWorldGenerator { pub fn new(tileset_path: &str, biome: Box) -> Self { TilesetWorldGenerator { tileset: load_tileset(tileset_path), biome, } } fn pixel_at(&self, pixel: IVec2) -> TilesetPixelType { let (grid, local, orientation) = split_position(pixel, self.tileset.dimensions.short as i32); let tiles = match orientation { TileOrientation::Horizontal => &self.tileset.horizontal_tiles, TileOrientation::Vertical => &self.tileset.vertical_tiles, }; tiles[variant_index(grid, tiles.len())].pixel_at(local) } 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]; 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, }; } } BiomeChunkContext { instantaneous_solidity: occupancy, } } 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 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.mark_collider_dirty(0); chunk } }