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