Add constraints and common submodules to waveform_collapse map builder

This commit is contained in:
Timothy Warren 2021-12-08 15:31:52 -05:00
parent 3ce4994417
commit 5fcc22ab6a
5 changed files with 243 additions and 4 deletions

View File

@ -7,7 +7,7 @@ pub const MAP_WIDTH: usize = 80;
pub const MAP_HEIGHT: usize = 43;
pub const MAP_COUNT: usize = MAP_HEIGHT * MAP_WIDTH;
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
#[derive(PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)]
pub enum TileType {
Wall,
Floor,

View File

@ -0,0 +1,13 @@
use crate::TileType;
#[derive(PartialEq, Eq, Hash, Clone)]
pub struct MapChunk {
pub pattern: Vec<TileType>,
pub exits: [Vec<bool>; 4],
pub has_exits: bool,
pub compatible_with: [Vec<usize>; 4],
}
pub fn tile_idx_in_chunk(chunk_size: i32, x: i32, y: i32) -> usize {
((y * chunk_size) + x) as usize
}

View File

@ -0,0 +1,226 @@
use super::MapChunk;
use crate::map_builders::waveform_collapse::common::tile_idx_in_chunk;
use crate::{Map, TileType};
use std::collections::HashSet;
pub fn build_patterns(
map: &Map,
chunk_size: i32,
include_flipping: bool,
dedupe: bool,
) -> Vec<Vec<TileType>> {
let chunks_x = map.width / chunk_size;
let chunks_y = map.height / chunk_size;
let mut patterns = Vec::new();
for cy in 0..chunks_y {
for cx in 0..chunks_x {
// Normal orientation
let mut pattern: Vec<TileType> = Vec::new();
let start_x = cx * chunk_size;
let end_x = (cx + 1) * chunk_size;
let start_y = cy * chunk_size;
let end_y = (cy + 1) * chunk_size;
for y in start_y..end_y {
for x in start_x..end_x {
let idx = map.xy_idx(x, y);
pattern.push(map.tiles[idx]);
}
}
patterns.push(pattern);
if include_flipping {
// Flip horizontal
pattern = Vec::new();
for y in start_y..end_y {
for x in start_x..end_x {
let idx = map.xy_idx(end_x - (x + 1), y);
pattern.push(map.tiles[idx]);
}
}
patterns.push(pattern);
// Flip vertical
pattern = Vec::new();
for y in start_y..end_y {
for x in start_x..end_x {
let idx = map.xy_idx(x, end_y - (y + 1));
pattern.push(map.tiles[idx]);
}
}
patterns.push(pattern);
// Flip both
pattern = Vec::new();
for y in start_y..end_y {
for x in start_x..end_x {
let idx = map.xy_idx(end_x - (x + 1), end_y - (y + 1));
pattern.push(map.tiles[idx]);
}
}
patterns.push(pattern);
}
}
}
// Dedupe
if dedupe {
rltk::console::log(format!(
"Pre de-duplication, there are {} patterns",
patterns.len()
));
// Use a set to de-duplicate
let set: HashSet<Vec<TileType>> = patterns.drain(..).collect();
patterns.extend(set.into_iter());
rltk::console::log(format!("There are {} patterns", patterns.len()));
}
patterns
}
pub fn render_pattern_to_map(
map: &mut Map,
chunk: &MapChunk,
chunk_size: i32,
start_x: i32,
start_y: i32,
) {
let mut i = 0_usize;
for tile_y in 0..chunk_size {
for tile_x in 0..chunk_size {
let map_idx = map.xy_idx(start_x + tile_x, start_y + tile_y);
map.tiles[map_idx] = chunk.pattern[i];
map.visible_tiles[map_idx] = true;
i += 1;
}
}
for (x, northbound) in chunk.exits[0].iter().enumerate() {
if *northbound {
let map_idx = map.xy_idx(start_x + x as i32, start_y);
map.tiles[map_idx] = TileType::DownStairs;
}
}
for (x, southbound) in chunk.exits[1].iter().enumerate() {
if *southbound {
let map_idx = map.xy_idx(start_x + x as i32, start_y + chunk_size - 1);
map.tiles[map_idx] = TileType::DownStairs;
}
}
for (x, westbound) in chunk.exits[2].iter().enumerate() {
if *westbound {
let map_idx = map.xy_idx(start_x, start_y + x as i32);
map.tiles[map_idx] = TileType::DownStairs;
}
}
for (x, eastbound) in chunk.exits[3].iter().enumerate() {
if *eastbound {
let map_idx = map.xy_idx(start_x + chunk_size - 1, start_y + x as i32);
map.tiles[map_idx] = TileType::DownStairs;
}
}
}
pub fn patterns_to_constraints(patterns: Vec<Vec<TileType>>, chunk_size: i32) -> Vec<MapChunk> {
// Move into the new constraints object
let mut constraints: Vec<MapChunk> = Vec::new();
for p in patterns {
let mut new_chunk = MapChunk {
pattern: p,
exits: [Vec::new(), Vec::new(), Vec::new(), Vec::new()],
has_exits: true,
compatible_with: [Vec::new(), Vec::new(), Vec::new(), Vec::new()],
};
for exit in new_chunk.exits.iter_mut() {
for _i in 0..chunk_size {
exit.push(false);
}
}
let mut n_exits = 0;
for x in 0..chunk_size {
// Check for north-bound exits
let north_idx = tile_idx_in_chunk(chunk_size, x, 0);
if new_chunk.pattern[north_idx] == TileType::Floor {
new_chunk.exits[0][x as usize] = true;
n_exits += 1;
}
// Check for south-bound exits
let south_idx = tile_idx_in_chunk(chunk_size, x, chunk_size - 1);
if new_chunk.pattern[south_idx] == TileType::Floor {
new_chunk.exits[1][x as usize] = true;
n_exits += 1;
}
// Check for west-bound exits
let west_idx = tile_idx_in_chunk(chunk_size, 0, x);
if new_chunk.pattern[west_idx] == TileType::Floor {
new_chunk.exits[2][x as usize] = true;
n_exits += 1;
}
// Check for east-bound exits
let east_idx = tile_idx_in_chunk(chunk_size, chunk_size - 1, x);
if new_chunk.pattern[east_idx] == TileType::Floor {
new_chunk.exits[3][x as usize] = true;
n_exits += 1;
}
}
if n_exits == 0 {
new_chunk.has_exits = false;
}
constraints.push(new_chunk);
}
// Build compatibility matrix
let ch = constraints.clone();
for c in constraints.iter_mut() {
for (j, potential) in ch.iter().enumerate() {
// If there are no exits at all, it's compatible
if !c.has_exits || !potential.has_exits {
for compat in c.compatible_with.iter_mut() {
compat.push(j);
}
} else {
// Evaluate compatibility by direction
for (direction, exit_list) in c.exits.iter_mut().enumerate() {
let opposite = match direction {
0 => 1, // Our North, Their South
1 => 0, // Our South, Their North
2 => 3, // Our West, Their East
_ => 2, // Our East, Their West
};
let mut it_fits = false;
let mut has_any = false;
for (slot, can_enter) in exit_list.iter().enumerate() {
if *can_enter {
has_any = true;
if potential.exits[opposite][slot] {
it_fits = true;
}
}
}
if it_fits {
c.compatible_with[direction].push(j);
}
if !has_any {
// There's no exits on this side, we don't care what goes there
for compat in c.compatible_with.iter_mut() {
compat.push(j);
}
}
}
}
}
}
constraints
}

View File

@ -1,5 +1,5 @@
use rltk::rex::XpFile;
use crate::{Map, TileType};
use rltk::rex::XpFile;
/// Loads a RexPaint file, and converts it into our map format
pub fn load_rex_map(new_depth: i32, xp_file: &XpFile) -> Map {
@ -13,7 +13,7 @@ pub fn load_rex_map(new_depth: i32, xp_file: &XpFile) -> Map {
let idx = map.xy_idx(x as i32, y as i32);
match cell.ch {
32 => map.tiles[idx] = TileType::Floor, // #
35 => map.tiles[idx] = TileType::Wall, // #
35 => map.tiles[idx] = TileType::Wall, // #
_ => {}
}
}
@ -22,4 +22,4 @@ pub fn load_rex_map(new_depth: i32, xp_file: &XpFile) -> Map {
}
map
}
}