bsp interior
This commit is contained in:
parent
394e8203ec
commit
3b08166867
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "mapgen"
|
name = "mapgen"
|
||||||
version = "0.2.1"
|
version = "0.3.0"
|
||||||
authors = ["Krzysztof Langner <klangner@gmail.com>"]
|
authors = ["Krzysztof Langner <klangner@gmail.com>"]
|
||||||
description = "Map generator for games (dungeons, worlds etc.)"
|
description = "Map generator for games (dungeons, worlds etc.)"
|
||||||
keywords = ["game", "map", "map-generator"]
|
keywords = ["game", "map", "map-generator"]
|
||||||
|
|
|
@ -12,7 +12,7 @@ Generate procedural maps for games. [Try it in the browser](https://klangner.git
|
||||||
### Dungeons
|
### Dungeons
|
||||||
|
|
||||||
* Map generators
|
* Map generators
|
||||||
* [ ] BSP Interior
|
* [x] BSP Interior
|
||||||
* [x] BSP Rooms
|
* [x] BSP Rooms
|
||||||
* [x] Cellular automata
|
* [x] Cellular automata
|
||||||
* [ ] Diffusion-Limited Aggregation (DLA)
|
* [ ] Diffusion-Limited Aggregation (DLA)
|
||||||
|
|
|
@ -6,7 +6,7 @@ use mapgen::dungeon::{
|
||||||
map::TileType,
|
map::TileType,
|
||||||
cellular_automata::CellularAutomataGen,
|
cellular_automata::CellularAutomataGen,
|
||||||
simple_rooms::SimpleRoomsGen,
|
simple_rooms::SimpleRoomsGen,
|
||||||
bsp_rooms::BspRoomsGen,
|
bsp_interior::BspInteriorGen,
|
||||||
starting_point::{AreaStartingPosition, XStart, YStart},
|
starting_point::{AreaStartingPosition, XStart, YStart},
|
||||||
cull_unreachable::CullUnreachable,
|
cull_unreachable::CullUnreachable,
|
||||||
distant_exit::DistantExit,
|
distant_exit::DistantExit,
|
||||||
|
@ -64,11 +64,10 @@ impl World {
|
||||||
tiles }
|
tiles }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_bsp_rooms(width: u32, height: u32, seed: u32) -> World {
|
pub fn new_bsp_interior(width: u32, height: u32, seed: u32) -> World {
|
||||||
World::print_map_info(format!("BSP Rooms with the seed: {}", seed));
|
World::print_map_info(format!("BSP Interior with the seed: {}", seed));
|
||||||
let mut rng = StdRng::seed_from_u64(seed as u64);
|
let mut rng = StdRng::seed_from_u64(seed as u64);
|
||||||
let map = MapBuilder::new(BspRoomsGen::new())
|
let map = MapBuilder::new(BspInteriorGen::new())
|
||||||
.with(NearestCorridors::new())
|
|
||||||
.build_map_with_rng(width as usize, height as usize, &mut rng);
|
.build_map_with_rng(width as usize, height as usize, &mut rng);
|
||||||
let tiles = (0..map.tiles.len())
|
let tiles = (0..map.tiles.len())
|
||||||
.map(|i| if map.tiles[i] == TileType::Floor {Cell::Floor} else {Cell::Wall})
|
.map(|i| if map.tiles[i] == TileType::Floor {Cell::Floor} else {Cell::Wall})
|
||||||
|
@ -87,7 +86,7 @@ impl World {
|
||||||
} else if px < 0.6666 {
|
} else if px < 0.6666 {
|
||||||
World::new_simple_rooms(width, height, seed)
|
World::new_simple_rooms(width, height, seed)
|
||||||
} else {
|
} else {
|
||||||
World::new_bsp_rooms(width, height, seed)
|
World::new_bsp_interior(width, height, seed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||||
<a class="dropdown-item" id="cellular-automata-option">Cellular Automata</a>
|
<a class="dropdown-item" id="cellular-automata-option">Cellular Automata</a>
|
||||||
<a class="dropdown-item" id="simple-rooms-option">Simple Rooms</a>
|
<a class="dropdown-item" id="simple-rooms-option">Simple Rooms</a>
|
||||||
<a class="dropdown-item" id="bsp-rooms-option">BSP Rooms</a>
|
<a class="dropdown-item" id="bsp-interior-option">BSP Interior</a>
|
||||||
<a class="dropdown-item" id="random-option">Random Generator</a>
|
<a class="dropdown-item" id="random-option">Random Generator</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -32,9 +32,9 @@ function newSimpleRooms() {
|
||||||
requestAnimationFrame(renderLoop);
|
requestAnimationFrame(renderLoop);
|
||||||
}
|
}
|
||||||
|
|
||||||
function newBspRooms() {
|
function newBspInterior() {
|
||||||
var seed = Date.now();
|
var seed = Date.now();
|
||||||
world = World.new_bsp_rooms(width, height, seed);
|
world = World.new_bsp_interior(width, height, seed);
|
||||||
requestAnimationFrame(renderLoop);
|
requestAnimationFrame(renderLoop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,5 +107,5 @@ newRandomGen();
|
||||||
// Connect UI element
|
// Connect UI element
|
||||||
document.getElementById('cellular-automata-option').addEventListener('click', newCellularAutomata);
|
document.getElementById('cellular-automata-option').addEventListener('click', newCellularAutomata);
|
||||||
document.getElementById('simple-rooms-option').addEventListener('click', newSimpleRooms);
|
document.getElementById('simple-rooms-option').addEventListener('click', newSimpleRooms);
|
||||||
document.getElementById('bsp-rooms-option').addEventListener('click', newBspRooms);
|
document.getElementById('bsp-interior-option').addEventListener('click', newBspInterior);
|
||||||
document.getElementById('random-option').addEventListener('click', newRandomGen);
|
document.getElementById('random-option').addEventListener('click', newRandomGen);
|
||||||
|
|
File diff suppressed because one or more lines are too long
65
docs/bootstrap.js
vendored
65
docs/bootstrap.js
vendored
|
@ -55,6 +55,69 @@
|
||||||
/******/ "../pkg/mapgen_demo_bg.wasm": function() {
|
/******/ "../pkg/mapgen_demo_bg.wasm": function() {
|
||||||
/******/ return {
|
/******/ return {
|
||||||
/******/ "./mapgen_demo_bg.js": {
|
/******/ "./mapgen_demo_bg.js": {
|
||||||
|
/******/ "__wbindgen_object_drop_ref": function(p0i32) {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbindgen_object_drop_ref"](p0i32);
|
||||||
|
/******/ },
|
||||||
|
/******/ "__wbg_getRandomValues_3ac1b33c90b52596": function(p0i32,p1i32,p2i32) {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbg_getRandomValues_3ac1b33c90b52596"](p0i32,p1i32,p2i32);
|
||||||
|
/******/ },
|
||||||
|
/******/ "__wbg_randomFillSync_6f956029658662ec": function(p0i32,p1i32,p2i32) {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbg_randomFillSync_6f956029658662ec"](p0i32,p1i32,p2i32);
|
||||||
|
/******/ },
|
||||||
|
/******/ "__wbg_self_1c83eb4471d9eb9b": function() {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbg_self_1c83eb4471d9eb9b"]();
|
||||||
|
/******/ },
|
||||||
|
/******/ "__wbg_static_accessor_MODULE_abf5ae284bffdf45": function() {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbg_static_accessor_MODULE_abf5ae284bffdf45"]();
|
||||||
|
/******/ },
|
||||||
|
/******/ "__wbg_require_5b2b5b594d809d9f": function(p0i32,p1i32,p2i32) {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbg_require_5b2b5b594d809d9f"](p0i32,p1i32,p2i32);
|
||||||
|
/******/ },
|
||||||
|
/******/ "__wbg_crypto_c12f14e810edcaa2": function(p0i32) {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbg_crypto_c12f14e810edcaa2"](p0i32);
|
||||||
|
/******/ },
|
||||||
|
/******/ "__wbg_msCrypto_679be765111ba775": function(p0i32) {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbg_msCrypto_679be765111ba775"](p0i32);
|
||||||
|
/******/ },
|
||||||
|
/******/ "__wbindgen_is_undefined": function(p0i32) {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbindgen_is_undefined"](p0i32);
|
||||||
|
/******/ },
|
||||||
|
/******/ "__wbg_getRandomValues_05a60bf171bfc2be": function(p0i32) {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbg_getRandomValues_05a60bf171bfc2be"](p0i32);
|
||||||
|
/******/ },
|
||||||
|
/******/ "__wbg_instanceof_Window_adf3196bdc02b386": function(p0i32) {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbg_instanceof_Window_adf3196bdc02b386"](p0i32);
|
||||||
|
/******/ },
|
||||||
|
/******/ "__wbg_document_6cc8d0b87c0a99b9": function(p0i32) {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbg_document_6cc8d0b87c0a99b9"](p0i32);
|
||||||
|
/******/ },
|
||||||
|
/******/ "__wbg_getElementById_0cb6ad9511b1efc0": function(p0i32,p1i32,p2i32) {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbg_getElementById_0cb6ad9511b1efc0"](p0i32,p1i32,p2i32);
|
||||||
|
/******/ },
|
||||||
|
/******/ "__wbg_setinnerHTML_4ff235db1a3cb4d8": function(p0i32,p1i32,p2i32) {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbg_setinnerHTML_4ff235db1a3cb4d8"](p0i32,p1i32,p2i32);
|
||||||
|
/******/ },
|
||||||
|
/******/ "__wbg_call_8e95613cc6524977": function(p0i32,p1i32) {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbg_call_8e95613cc6524977"](p0i32,p1i32);
|
||||||
|
/******/ },
|
||||||
|
/******/ "__wbindgen_object_clone_ref": function(p0i32) {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbindgen_object_clone_ref"](p0i32);
|
||||||
|
/******/ },
|
||||||
|
/******/ "__wbg_newnoargs_f3b8a801d5d4b079": function(p0i32,p1i32) {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbg_newnoargs_f3b8a801d5d4b079"](p0i32,p1i32);
|
||||||
|
/******/ },
|
||||||
|
/******/ "__wbg_self_07b2f89e82ceb76d": function() {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbg_self_07b2f89e82ceb76d"]();
|
||||||
|
/******/ },
|
||||||
|
/******/ "__wbg_window_ba85d88572adc0dc": function() {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbg_window_ba85d88572adc0dc"]();
|
||||||
|
/******/ },
|
||||||
|
/******/ "__wbg_globalThis_b9277fc37e201fe5": function() {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbg_globalThis_b9277fc37e201fe5"]();
|
||||||
|
/******/ },
|
||||||
|
/******/ "__wbg_global_e16303fe83e1d57f": function() {
|
||||||
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbg_global_e16303fe83e1d57f"]();
|
||||||
|
/******/ },
|
||||||
/******/ "__wbindgen_throw": function(p0i32,p1i32) {
|
/******/ "__wbindgen_throw": function(p0i32,p1i32) {
|
||||||
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbindgen_throw"](p0i32,p1i32);
|
/******/ return installedModules["../pkg/mapgen_demo_bg.js"].exports["__wbindgen_throw"](p0i32,p1i32);
|
||||||
/******/ }
|
/******/ }
|
||||||
|
@ -159,7 +222,7 @@
|
||||||
/******/ promises.push(installedWasmModuleData);
|
/******/ promises.push(installedWasmModuleData);
|
||||||
/******/ else {
|
/******/ else {
|
||||||
/******/ var importObject = wasmImportObjects[wasmModuleId]();
|
/******/ var importObject = wasmImportObjects[wasmModuleId]();
|
||||||
/******/ var req = fetch(__webpack_require__.p + "" + {"../pkg/mapgen_demo_bg.wasm":"bbf7fe2631d06e11148e"}[wasmModuleId] + ".module.wasm");
|
/******/ var req = fetch(__webpack_require__.p + "" + {"../pkg/mapgen_demo_bg.wasm":"cbb67d3121ca19dbc9af"}[wasmModuleId] + ".module.wasm");
|
||||||
/******/ var promise;
|
/******/ var promise;
|
||||||
/******/ if(importObject instanceof Promise && typeof WebAssembly.compileStreaming === 'function') {
|
/******/ if(importObject instanceof Promise && typeof WebAssembly.compileStreaming === 'function') {
|
||||||
/******/ promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) {
|
/******/ promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) {
|
||||||
|
|
BIN
docs/cbb67d3121ca19dbc9af.module.wasm
Normal file
BIN
docs/cbb67d3121ca19dbc9af.module.wasm
Normal file
Binary file not shown.
|
@ -35,8 +35,10 @@
|
||||||
Select generator
|
Select generator
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||||
<a class="dropdown-item" href="#" id="cellular-automata-option">Cellular Automata</a>
|
<a class="dropdown-item" id="cellular-automata-option">Cellular Automata</a>
|
||||||
<a class="dropdown-item" id="simple-rooms-option">Simple Rooms</a>
|
<a class="dropdown-item" id="simple-rooms-option">Simple Rooms</a>
|
||||||
|
<a class="dropdown-item" id="bsp-interior-option">BSP Interior</a>
|
||||||
|
<a class="dropdown-item" id="random-option">Random Generator</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -30,24 +30,44 @@ impl Point {
|
||||||
/// Rectangle region on the map
|
/// Rectangle region on the map
|
||||||
#[derive(PartialEq, Copy, Clone)]
|
#[derive(PartialEq, Copy, Clone)]
|
||||||
pub struct Rect {
|
pub struct Rect {
|
||||||
pub x1 : i32,
|
pub x1 : usize,
|
||||||
pub x2 : i32,
|
pub x2 : usize,
|
||||||
pub y1 : i32,
|
pub y1 : usize,
|
||||||
pub y2 : i32
|
pub y2 : usize
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Rect {
|
impl Rect {
|
||||||
pub fn new(x:i32, y: i32, width:i32, height:i32) -> Rect {
|
pub fn new(x: usize, y: usize, width: usize, height: usize) -> Rect {
|
||||||
Rect{x1:x, y1:y, x2:x+width, y2:y+height}
|
Rect{x1:x, y1:y, x2:x+width, y2:y+height}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_i32(x:i32, y: i32, width:i32, height:i32) -> Rect {
|
||||||
|
Rect::new(x as usize, y as usize, width as usize, height as usize)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if this overlaps with other
|
/// Returns true if this overlaps with other
|
||||||
pub fn intersect(&self, other:&Rect) -> bool {
|
pub fn intersect(&self, other:&Rect) -> bool {
|
||||||
self.x1 <= other.x2 && self.x2 >= other.x1 && self.y1 <= other.y2 && self.y2 >= other.y1
|
self.x1 <= other.x2 && self.x2 >= other.x1 && self.y1 <= other.y2 && self.y2 >= other.y1
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn center(&self) -> Point {
|
pub fn center(&self) -> Point {
|
||||||
Point::new_i32((self.x1 + self.x2)/2, (self.y1 + self.y2)/2)
|
Point::new((self.x1 + self.x2)/2, (self.y1 + self.y2)/2)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn width(&self) -> usize {
|
||||||
|
if self.x2 >= self.x1 {
|
||||||
|
self.x2 - self.x1
|
||||||
|
} else {
|
||||||
|
self.x1 - self.x2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn height(&self) -> usize {
|
||||||
|
if self.y2 >= self.y1 {
|
||||||
|
self.y2 - self.y1
|
||||||
|
} else {
|
||||||
|
self.y1 - self.y2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,4 +92,12 @@ mod tests {
|
||||||
let rect2 = Rect::new(30, 30, 60, 60);
|
let rect2 = Rect::new(30, 30, 60, 60);
|
||||||
assert!(rect1.intersect(&rect2));
|
assert!(rect1.intersect(&rect2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_size() {
|
||||||
|
let rect1 = Rect::new(10, 10, 40, 30);
|
||||||
|
assert_eq!(rect1.width(), 40);
|
||||||
|
assert_eq!(rect1.height(), 30);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -2,14 +2,25 @@
|
||||||
//!
|
//!
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
|
|
||||||
|
pub trait Rng {
|
||||||
|
/// Generate random number between start and end (bot inclusive).
|
||||||
|
fn roll_dice(&mut self, min: usize, max: usize) -> usize;
|
||||||
|
/// Generate random number between start (inclusive) and end (exclusive).
|
||||||
|
fn random_range(&mut self, start: usize, end: usize) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
/// Generate random number between start (inclusive) and end (exclusive).
|
impl Rng for StdRng {
|
||||||
pub fn random_range(rng: &mut StdRng, start: usize, end: usize) -> usize {
|
fn roll_dice(&mut self, min: usize, max: usize) -> usize {
|
||||||
|
self.random_range(min, max+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn random_range(&mut self, start: usize, end: usize) -> usize {
|
||||||
let max = (end - start) as u32;
|
let max = (end - start) as u32;
|
||||||
if max == 0 {
|
if max == 0 {
|
||||||
start
|
start
|
||||||
} else {
|
} else {
|
||||||
((rng.next_u32() % max) + start as u32) as usize
|
((self.next_u32() % max) + start as u32) as usize
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,16 +29,40 @@ pub fn random_range(rng: &mut StdRng, start: usize, end: usize) -> usize {
|
||||||
/// ------------------------------------------------------------------------------------------------
|
/// ------------------------------------------------------------------------------------------------
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use super::*;
|
use super::Rng;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_range() {
|
fn test_range() {
|
||||||
let system_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Can't access system time");
|
let mut rng = StdRng::seed_from_u64(100);
|
||||||
let mut rng = StdRng::seed_from_u64(system_time.as_millis() as u64);
|
let x = rng.random_range(5, 8);
|
||||||
let x = random_range(&mut rng, 5, 8);
|
|
||||||
assert!(x >= 5 && x < 8);
|
assert!(x >= 5 && x < 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_range_average() {
|
||||||
|
let num_op = 1000;
|
||||||
|
let mut rng = StdRng::seed_from_u64(1000);
|
||||||
|
let xs: Vec<usize> = (0..num_op).map(|_| rng.random_range(5, 10)).collect();
|
||||||
|
let mean = xs.iter().sum::<usize>() / num_op;
|
||||||
|
let min = *xs.iter().min().expect("no min");
|
||||||
|
let max = *xs.iter().max().expect("no max");
|
||||||
|
assert_eq!(mean, 7);
|
||||||
|
assert_eq!(min, 5);
|
||||||
|
assert_eq!(max, 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_roll_dice() {
|
||||||
|
let num_op = 1000;
|
||||||
|
let mut rng = StdRng::seed_from_u64(2000);
|
||||||
|
let xs: Vec<usize> = (0..num_op).map(|_| rng.roll_dice(1, 7)).collect();
|
||||||
|
let mean = xs.iter().sum::<usize>() as f32 / num_op as f32 + 0.5;
|
||||||
|
let min = *xs.iter().min().expect("no min");
|
||||||
|
let max = *xs.iter().max().expect("no max");
|
||||||
|
assert_eq!(mean as usize, 4);
|
||||||
|
assert_eq!(min, 1);
|
||||||
|
assert_eq!(max, 7);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
108
src/dungeon/bsp_interior.rs
Normal file
108
src/dungeon/bsp_interior.rs
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
//! Random rooms map generator.
|
||||||
|
//!
|
||||||
|
//! Try to generate rooms of different size to fill the map area.
|
||||||
|
//! Rooms will not overlap.
|
||||||
|
//!
|
||||||
|
//! Example generator usage:
|
||||||
|
//! ```
|
||||||
|
//! use rand::prelude::*;
|
||||||
|
//! use mapgen::dungeon::{
|
||||||
|
//! MapGenerator,
|
||||||
|
//! bsp_interior::BspInteriorGen
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! let mut rng = StdRng::seed_from_u64(100);
|
||||||
|
//! let gen = BspInteriorGen::new();
|
||||||
|
//! let map = gen.generate_map(80, 50, &mut rng);
|
||||||
|
//!
|
||||||
|
//! assert_eq!(map.width, 80);
|
||||||
|
//! assert_eq!(map.height, 50);
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
|
||||||
|
use rand::prelude::*;
|
||||||
|
use super::MapGenerator;
|
||||||
|
use crate::common::geometry::{Point, Rect};
|
||||||
|
use crate::common::random::Rng;
|
||||||
|
use super::map::Map;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct BspInteriorGen {
|
||||||
|
min_room_size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MapGenerator for BspInteriorGen {
|
||||||
|
fn generate_map(&self, width: usize, height: usize, rng : &mut StdRng) -> Map {
|
||||||
|
self.build(rng, width, height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BspInteriorGen {
|
||||||
|
|
||||||
|
pub fn new() -> Box<BspInteriorGen> {
|
||||||
|
Box::new(BspInteriorGen{
|
||||||
|
min_room_size: 8,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build(&self, rng: &mut StdRng, width: usize, height: usize) -> Map {
|
||||||
|
let mut map = Map::new(width, height);
|
||||||
|
let mut rects: Vec<Rect> = Vec::new();
|
||||||
|
rects.push( Rect::new(1, 1, map.width-2, map.height-2) );
|
||||||
|
let first_room = rects[0];
|
||||||
|
// Divide the first room
|
||||||
|
self.add_subrects(first_room, rng, &mut rects);
|
||||||
|
|
||||||
|
let rooms_copy = rects.clone();
|
||||||
|
for r in rooms_copy.iter() {
|
||||||
|
let room = *r;
|
||||||
|
map.add_room(room);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we want corridors
|
||||||
|
for i in 0..map.rooms.len()-1 {
|
||||||
|
let room = map.rooms[i];
|
||||||
|
let next_room = map.rooms[i+1];
|
||||||
|
let start_x = room.x1 + rng.random_range(1, room.width());
|
||||||
|
let start_y = room.y1 + rng.random_range(1, room.height());
|
||||||
|
let end_x = next_room.x1 + (rng.random_range(1, next_room.width()));
|
||||||
|
let end_y = next_room.y1 + (rng.random_range(1, next_room.width()));
|
||||||
|
map.add_corridor(Point::new(start_x, start_y), Point::new(end_x, end_y));
|
||||||
|
}
|
||||||
|
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_subrects(&self, rect: Rect, rng: &mut StdRng, rects: &mut Vec<Rect>) {
|
||||||
|
// Remove the last rect from the list
|
||||||
|
if !rects.is_empty() {
|
||||||
|
rects.remove(rects.len() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate boundaries
|
||||||
|
let width = rect.x2 - rect.x1;
|
||||||
|
let height = rect.y2 - rect.y1;
|
||||||
|
let half_width = width / 2;
|
||||||
|
let half_height = height / 2;
|
||||||
|
|
||||||
|
let split = rng.roll_dice(1, 4);
|
||||||
|
|
||||||
|
if split <= 2 {
|
||||||
|
// Horizontal split
|
||||||
|
let h1 = Rect::new( rect.x1, rect.y1, half_width-1, height );
|
||||||
|
rects.push( h1 );
|
||||||
|
if half_width > self.min_room_size { self.add_subrects(h1, rng, rects); }
|
||||||
|
let h2 = Rect::new( rect.x1 + half_width, rect.y1, half_width, height );
|
||||||
|
rects.push( h2 );
|
||||||
|
if half_width > self.min_room_size { self.add_subrects(h2, rng, rects); }
|
||||||
|
} else {
|
||||||
|
// Vertical split
|
||||||
|
let v1 = Rect::new( rect.x1, rect.y1, width, half_height-1 );
|
||||||
|
rects.push(v1);
|
||||||
|
if half_height > self.min_room_size { self.add_subrects(v1, rng, rects); }
|
||||||
|
let v2 = Rect::new( rect.x1, rect.y1 + half_height, width, half_height );
|
||||||
|
rects.push(v2);
|
||||||
|
if half_height > self.min_room_size { self.add_subrects(v2, rng, rects); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,7 +23,7 @@
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use super::MapGenerator;
|
use super::MapGenerator;
|
||||||
use crate::common::geometry::Rect;
|
use crate::common::geometry::Rect;
|
||||||
use crate::common::random;
|
use crate::common::random::Rng;
|
||||||
use super::map::{Map, TileType};
|
use super::map::{Map, TileType};
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ impl BspRoomsGen {
|
||||||
let mut map = Map::new(width, height);
|
let mut map = Map::new(width, height);
|
||||||
let mut rects: Vec<Rect> = Vec::new();
|
let mut rects: Vec<Rect> = Vec::new();
|
||||||
// Start with a single map-sized rectangle
|
// Start with a single map-sized rectangle
|
||||||
rects.push( Rect::new(2, 2, (width-5) as i32, (height-5) as i32) );
|
rects.push( Rect::new(2, 2, width-5, height-5) );
|
||||||
let first_room = rects[0];
|
let first_room = rects[0];
|
||||||
rects.append(&mut self.split_into_subrects(first_room)); // Divide the first room
|
rects.append(&mut self.split_into_subrects(first_room)); // Divide the first room
|
||||||
|
|
||||||
|
@ -71,10 +71,10 @@ impl BspRoomsGen {
|
||||||
|
|
||||||
fn split_into_subrects(&self, rect: Rect) -> Vec<Rect> {
|
fn split_into_subrects(&self, rect: Rect) -> Vec<Rect> {
|
||||||
let mut rects: Vec<Rect> = Vec::new();
|
let mut rects: Vec<Rect> = Vec::new();
|
||||||
let width = i32::abs(rect.x1 - rect.x2);
|
let width = rect.width();
|
||||||
let height = i32::abs(rect.y1 - rect.y2);
|
let height = rect.height();
|
||||||
let half_width = i32::max(width / 2, 1);
|
let half_width = usize::max(width / 2, 1);
|
||||||
let half_height = i32::max(height / 2, 1);
|
let half_height = usize::max(height / 2, 1);
|
||||||
|
|
||||||
rects.push(Rect::new( rect.x1, rect.y1, half_width, half_height ));
|
rects.push(Rect::new( rect.x1, rect.y1, half_width, half_height ));
|
||||||
rects.push(Rect::new( rect.x1, rect.y1 + half_height, half_width, half_height ));
|
rects.push(Rect::new( rect.x1, rect.y1 + half_height, half_width, half_height ));
|
||||||
|
@ -86,22 +86,22 @@ impl BspRoomsGen {
|
||||||
|
|
||||||
fn get_random_rect(&self, rng : &mut StdRng, rects: &Vec<Rect>) -> Rect {
|
fn get_random_rect(&self, rng : &mut StdRng, rects: &Vec<Rect>) -> Rect {
|
||||||
if rects.len() == 1 { return rects[0]; }
|
if rects.len() == 1 { return rects[0]; }
|
||||||
let idx = random::random_range(rng, 0, rects.len());
|
let idx = rng.random_range(0, rects.len());
|
||||||
rects[idx]
|
rects[idx]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_random_sub_rect(&self, rect: Rect, rng: &mut StdRng) -> Rect {
|
fn get_random_sub_rect(&self, rect: Rect, rng: &mut StdRng) -> Rect {
|
||||||
let mut result = rect;
|
let mut result = rect;
|
||||||
let rect_width = i32::abs(rect.x1 - rect.x2);
|
let rect_width = rect.width();
|
||||||
let rect_height = i32::abs(rect.y1 - rect.y2);
|
let rect_height = rect.height();
|
||||||
|
|
||||||
let w = usize::max(3, random::random_range(rng, 1, usize::min(rect_width as usize, 20))) + 1;
|
let w = usize::max(3, rng.random_range(1, usize::min(rect_width as usize, 20))) + 1;
|
||||||
let h = usize::max(3, random::random_range(rng, 1, usize::min(rect_height as usize, 20))) + 1;
|
let h = usize::max(3, rng.random_range(1, usize::min(rect_height as usize, 20))) + 1;
|
||||||
|
|
||||||
result.x1 += random::random_range(rng, 0, 6) as i32;
|
result.x1 += rng.random_range(0, 6);
|
||||||
result.y1 += random::random_range(rng, 0, 6) as i32;
|
result.y1 += rng.random_range(0, 6);
|
||||||
result.x2 = result.x1 + w as i32;
|
result.x2 = result.x1 + w;
|
||||||
result.y2 = result.y1 + h as i32;
|
result.y2 = result.y1 + h;
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
@ -121,8 +121,8 @@ impl BspRoomsGen {
|
||||||
|
|
||||||
for y in expanded.y1 ..= expanded.y2 {
|
for y in expanded.y1 ..= expanded.y2 {
|
||||||
for x in expanded.x1 ..= expanded.x2 {
|
for x in expanded.x1 ..= expanded.x2 {
|
||||||
if x > map.width as i32 -2 { can_build = false; }
|
if x > map.width - 2 { can_build = false; }
|
||||||
if y > map.height as i32 -2 { can_build = false; }
|
if y > map.height - 2 { can_build = false; }
|
||||||
if x < 1 { can_build = false; }
|
if x < 1 { can_build = false; }
|
||||||
if y < 1 { can_build = false; }
|
if y < 1 { can_build = false; }
|
||||||
if can_build {
|
if can_build {
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
pub mod map;
|
pub mod map;
|
||||||
pub mod cellular_automata;
|
pub mod cellular_automata;
|
||||||
pub mod cull_unreachable;
|
pub mod cull_unreachable;
|
||||||
|
pub mod bsp_interior;
|
||||||
pub mod bsp_rooms;
|
pub mod bsp_rooms;
|
||||||
pub mod distant_exit;
|
pub mod distant_exit;
|
||||||
pub mod simple_rooms;
|
pub mod simple_rooms;
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use super::MapGenerator;
|
use super::MapGenerator;
|
||||||
use crate::common::geometry::Rect;
|
use crate::common::geometry::Rect;
|
||||||
use crate::common::random;
|
use crate::common::random::Rng;
|
||||||
use super::map::{Map};
|
use super::map::{Map};
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,11 +54,11 @@ impl SimpleRoomsGen {
|
||||||
|
|
||||||
// Create room dimensions
|
// Create room dimensions
|
||||||
for _ in 0..self.max_rooms {
|
for _ in 0..self.max_rooms {
|
||||||
let w = random::random_range(rng, self.min_room_size, self.max_room_size);
|
let w = rng.random_range(self.min_room_size, self.max_room_size);
|
||||||
let h = random::random_range(rng, self.min_room_size, self.max_room_size);
|
let h = rng.random_range(self.min_room_size, self.max_room_size);
|
||||||
let x = random::random_range(rng, 1, width - w);
|
let x = rng.random_range(1, width - w);
|
||||||
let y = random::random_range(rng, 1, height - h);
|
let y = rng.random_range(1, height - h);
|
||||||
let new_room = Rect::new(x as i32, y as i32, w as i32, h as i32);
|
let new_room = Rect::new(x, y, w, h);
|
||||||
let intersects = map.rooms.iter().any(|r| new_room.intersect(r));
|
let intersects = map.rooms.iter().any(|r| new_room.intersect(r));
|
||||||
if !intersects {
|
if !intersects {
|
||||||
map.add_room(new_room);
|
map.add_room(new_room);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user