summaryrefslogtreecommitdiff
path: root/src/proc_gen
diff options
context:
space:
mode:
Diffstat (limited to 'src/proc_gen')
-rw-r--r--src/proc_gen/herringbone.rs4
-rw-r--r--src/proc_gen/mod.rs131
-rw-r--r--src/proc_gen/tileset_loader.rs92
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,
}