diff options
| author | Kai Stevenson <kai@kaistevenson.com> | 2026-08-31 00:03:32 -0700 |
|---|---|---|
| committer | Kai Stevenson <kai@kaistevenson.com> | 2026-08-31 00:03:32 -0700 |
| commit | d68cf09f7b8e5cb783dc495097c17eaf2a4d5427 (patch) | |
| tree | 59216cae8c466ad63fa6ff47dc5c7f35c64c23d2 /src/proc_gen | |
| parent | 8e128d3ca24fa10dc7d871f7daf6c43eaddc385c (diff) | |
cleanupkai/herringbone
Diffstat (limited to 'src/proc_gen')
| -rw-r--r-- | src/proc_gen/herringbone.rs | 113 | ||||
| -rw-r--r-- | src/proc_gen/mod.rs | 101 | ||||
| -rw-r--r-- | src/proc_gen/tileset_loader.rs | 53 |
3 files changed, 134 insertions, 133 deletions
diff --git a/src/proc_gen/herringbone.rs b/src/proc_gen/herringbone.rs index 271b7a1..e8569c8 100644 --- a/src/proc_gen/herringbone.rs +++ b/src/proc_gen/herringbone.rs @@ -1,111 +1,30 @@ use fxhash::hash32; use glam::IVec2; -use crate::{ - config::TILESET_SCALING, - proc_gen::tileset_loader::{Tile, TileOrientation, Tileset, TilesetPixelType}, -}; +use crate::proc_gen::tileset_loader::TileOrientation; -pub fn split_position(world: IVec2, tileset: &Tileset) -> (IVec2, IVec2, TileOrientation) { - let s = tileset.dimensions.short as i32; - let tx = world.x.div_euclid(s); - let ty = world.y.div_euclid(s); - let fx = world.x.rem_euclid(s); - let fy = world.y.rem_euclid(s); - - match (tx - ty).rem_euclid(4) { - // left half of horizontal tile - 0 => ( - IVec2::new(tx, ty), - IVec2::new(fx, fy), - TileOrientation::Horizontal, - ), - // right half of horizontal tile - 1 => ( - IVec2::new(tx - 1, ty), - IVec2::new(fx + s, fy), - TileOrientation::Horizontal, - ), - // top half of vertical tile - 3 => ( - IVec2::new(tx, ty), - IVec2::new(fx, fy), - TileOrientation::Vertical, - ), - // bottom half of vertical tile - 2 => ( - IVec2::new(tx, ty - 1), - IVec2::new(fx, fy + s), - TileOrientation::Vertical, - ), - _ => unreachable!(), +fn tile_starting_at(grid: IVec2) -> Option<TileOrientation> { + 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, } } +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 { (((h as u128) * (n as u128)) >> 32) as usize } -fn pick_variant(tile_pos: IVec2, orientation: TileOrientation, tileset: &Tileset) -> &Tile { - let h = hash32(&tile_pos); - match orientation { - TileOrientation::Horizontal => { - &tileset.horizontal_tiles[reduce(h, tileset.horizontal_tiles.len())] - } - TileOrientation::Vertical => { - &tileset.vertical_tiles[reduce(h, tileset.vertical_tiles.len())] - } - } -} - -// TODO: optimize -fn get_pixel_at_position(world: IVec2, tileset: &Tileset) -> TilesetPixelType { - let scaled_world = world.div_euclid(IVec2::new(TILESET_SCALING, TILESET_SCALING)); - let (tile_pos, local, orientation) = split_position(scaled_world, tileset); - let tile = pick_variant(tile_pos, orientation, tileset); - let width = match orientation { - TileOrientation::Horizontal => tileset.dimensions.long as i32, - TileOrientation::Vertical => tileset.dimensions.short as i32, - }; - tile.pixels[(local.x + local.y * width) as usize] +pub fn variant_index(grid: IVec2, variants: usize) -> usize { + reduce(hash32(&grid), variants) } - -pub fn tiles_overlapping( - a: IVec2, - b: IVec2, - tileset: &Tileset, -) -> impl Iterator<Item = (IVec2, &Tile, TileOrientation)> { - let s = IVec2::new( - tileset.dimensions.short as i32, - tileset.dimensions.short as i32, - ); - - let ta = a.div_euclid(s) - 1; - let tb = (b - 1).div_euclid(s); - - (ta.y..=tb.y) - .flat_map(move |ty| (ta.x..=tb.x).map(move |tx| (tx, ty))) - .filter_map(move |(tx, ty)| match (tx - ty).rem_euclid(4) { - 0 => Some(( - IVec2::new(tx, ty), - pick_variant(IVec2::new(tx, ty), TileOrientation::Horizontal, tileset), - TileOrientation::Horizontal, - )), - 3 => Some(( - IVec2::new(tx, ty), - pick_variant(IVec2::new(tx, ty), TileOrientation::Vertical, tileset), - TileOrientation::Vertical, - )), - _ => None, - }) -} - -// pub fn get_cell_at_position(world: IVec2, tileset: &Tileset) -> Cell { -// let pixel = get_pixel_at_position(world, tileset); -// match pixel { -// TilesetPixelType::Void => Cell::void(), -// TilesetPixelType::Terrain => Cell::from_material(MaterialId::Dirt), -// } -// } diff --git a/src/proc_gen/mod.rs b/src/proc_gen/mod.rs index 049ad1a..caec241 100644 --- a/src/proc_gen/mod.rs +++ b/src/proc_gen/mod.rs @@ -6,66 +6,97 @@ use glam::IVec2; use crate::{ config::{CHUNK_SIZE, TILESET_SCALING}, proc_gen::{ - herringbone::tiles_overlapping, + herringbone::{tiles_overlapping, variant_index}, tileset_loader::{Tile, TileOrientation, Tileset, load_tileset}, }, sim::{cell::Cell, cell_manager::chunk::Chunk, entity::EntityDef}, }; +pub struct TileCells { + pub size: IVec2, + pub cells: Vec<Cell>, +} + pub trait Biome { - fn derive_world_from_tile(&self, _tile: &Tile) -> Vec<Cell> { - Vec::new() - } - fn derive_entities_from_tile(&self, _tile: &Tile) -> Vec<EntityDef> { + fn derive_world_from_tile(&self, tile: &Tile) -> TileCells; + fn derive_entities_from_tile(&self, tile: &Tile) -> Vec<EntityDef> { Vec::new() } } pub struct TilesetWorldGenerator { tileset: Tileset, - biome: Box<dyn Biome>, + horizontal_tiles: Vec<TileCells>, + vertical_tiles: Vec<TileCells>, } 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(); + + TilesetWorldGenerator { + tileset, + horizontal_tiles, + vertical_tiles, + } + } + + fn grid_size(&self) -> IVec2 { + IVec2::splat(self.tileset.dimensions.short as i32 * TILESET_SCALING) + } + + fn tile_at(&self, grid: IVec2, orientation: TileOrientation) -> &TileCells { + let tiles = match orientation { + TileOrientation::Horizontal => &self.horizontal_tiles, + TileOrientation::Vertical => &self.vertical_tiles, + }; + &tiles[variant_index(grid, tiles.len())] + } + pub fn generate_chunk(&self, chunk_position: IVec2) -> Chunk { let mut chunk = Chunk::void(); - let a = chunk_position * CHUNK_SIZE; - let b = (chunk_position + 1) * CHUNK_SIZE; - let tiles = tiles_overlapping(a * TILESET_SCALING, b * TILESET_SCALING, &self.tileset); - let world_tiles = tiles.map(|(p, t, o)| (p, self.biome.derive_world_from_tile(t), o)); + 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 (grid_position, orientation) in tiles { + let tile = self.tile_at(grid_position, orientation); + let tile_min = grid_position * grid; - for (tp, t, o) in world_tiles { - let p = tp * self.tileset.dimensions.short as i32 * TILESET_SCALING; - let ta = p.max(a); - let tb = p.min(b); - if ta.x >= tb.x || ta.y >= tb.y { + // 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; - for y in ta.y..tb.y { - for x in ta.x..tb.x { - let sw = match o { - TileOrientation::Horizontal => { - self.tileset.dimensions.long as i32 * TILESET_SCALING - } - TileOrientation::Vertical => { - self.tileset.dimensions.short as i32 * TILESET_SCALING - } - }; - - let src = (y - p.y) * sw + (x - p.x); - let dst = (y - a.y) * CHUNK_SIZE + (x - a.x); - chunk.cells[dst as usize] = t[src as usize]; - } + 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]); } } - chunk - } + chunk.mark_collider_dirty(0); - pub fn new(tileset_path: &str, biome: Box<dyn Biome>) -> Self { - let tileset = load_tileset(tileset_path); - TilesetWorldGenerator { tileset, biome } + chunk } } diff --git a/src/proc_gen/tileset_loader.rs b/src/proc_gen/tileset_loader.rs index acb4d65..e0d9d18 100644 --- a/src/proc_gen/tileset_loader.rs +++ b/src/proc_gen/tileset_loader.rs @@ -1,6 +1,9 @@ +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); @@ -32,7 +35,7 @@ pub struct TileDimensions { pub long: u32, } -#[derive(Deserialize, Clone, Copy, Debug)] +#[derive(Deserialize, Clone, Copy, Debug, PartialEq, Eq)] pub enum TilesetPixelType { Void = 0, Terrain, @@ -55,6 +58,54 @@ pub struct Tile { 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() + } +} + +impl Tile { + pub fn size(&self) -> IVec2 { + let short = self.dimensions.short as i32; + let long = self.dimensions.long as i32; + match self.orientation { + TileOrientation::Horizontal => IVec2::new(long, short), + TileOrientation::Vertical => IVec2::new(short, long), + } + } + + #[inline] + pub fn pixel_at(&self, position: IVec2) -> TilesetPixelType { + self.pixels[(position.x + position.y * self.size().x) as usize] + } +} + #[derive(Debug)] pub struct Tileset { pub dimensions: TileDimensions, |
