summaryrefslogtreecommitdiff
path: root/src/proc_gen
diff options
context:
space:
mode:
authorKai Stevenson <kai@kaistevenson.com>2026-08-31 19:43:16 -0700
committerKai Stevenson <kai@kaistevenson.com>2026-08-31 19:43:16 -0700
commit9e0ec02fcbb3b82a2a73d2713c41af5a6c4523f9 (patch)
tree6c1dc26b67b62997e286eb7db2e09d8ffbd98e6a /src/proc_gen
parent8a4dabfc1196e6263dcc6ed64f4abbb5965a9c34 (diff)
remove ms, use noise + lerp
Diffstat (limited to 'src/proc_gen')
-rw-r--r--src/proc_gen/herringbone.rs29
-rw-r--r--src/proc_gen/mod.rs133
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);
}
}