Browse Source

Added path types for worldgen

master
Thomas Johnson 3 years ago
parent
commit
2975aee3d9
  1. 318
      src/world/gen.rs
  2. 21
      src/world/mod.rs

318
src/world/gen.rs

@ -1,10 +1,11 @@
use rogue_util::coord::{R2i, V2i};
use rogue_util::grid::{Grid};
use rogue_util::raster;
use super::{Tile, Content};
use rand::rngs::SmallRng;
use rand::SeedableRng;
use rand_distr::{Poisson, Distribution, weighted::WeightedIndex, Uniform};
use rand::{Rng, SeedableRng};
use rand_distr::{Poisson, Distribution, weighted::WeightedIndex, Uniform, Bernoulli};
use std::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;
@ -12,26 +13,34 @@ const GRID_SIZE: isize = 32;
const GRID_USIZE: usize = GRID_SIZE as usize;
const AVG_ROOMS_PER_CELL: f64 = 2.0;
// room types: [rectangle, circle]
const ROOM_TYPE_WEIGHTS: [usize; 2] = [1, 1];
const RECT_MIN_SIZE: isize = 3;
const RECT_MAX_SIZE: isize = 10;
const CIRC_MIN_RAD: isize = 1;
const CIRC_MAX_RAD: isize = 5;
const CIRC_MIN_RAD: isize = 2;
const CIRC_MAX_RAD: isize = 7;
// path strategies: [no path, Bresenham line between centers, orthogonal paths, random paths nearby]
const PATH_STRATEGY_WEIGHTS: [usize; 4] = [15, 0, 0, 1];
const USE_RECTANGLE_CENTER_FOR_PATH_END_PROB: f64 = 0.5;
const PATH_ORTHOG_AVG_NUM_STOPS_PER_LEN: f64 = 0.1;
const PATH_CIRC_AVG_NUM_STOPS_PER_LEN: f64 = 0.3;
pub mod extra_utils
{
use rogue_util::coord::{R2i, V2i};
pub fn enlarge_rect(rect: R2i, upper: V2i, lower: V2i) -> R2i
{
R2i::origin_dim(rect.origin() - lower, rect.dim() + upper + lower)
}
use rand_distr::{Uniform, Distribution};
use rand::Rng;
pub fn rects_have_intersection_or_common_edge(r1: R2i, r2: R2i) -> bool
{
let enlarged = enlarge_rect(r1, V2i(1, 1), V2i(1, 1));
let enlarged = r1.grow(1);
if let Some(_) = enlarged.intersect(r2)
{
true
@ -40,11 +49,34 @@ pub mod extra_utils
false
}
}
pub fn get_rect_edge_by_index(r: R2i, i: isize) -> V2i
{
let orig = r.origin();
let V2i(xd, yd) = r.dim();
match i
{
i if i < 0 => panic!("index out of bounds when finding a rectangle edge"),
i if i < xd - 1 => orig + V2i(i, 0),
i if i < 2 * xd - 2 => orig + V2i(i - xd + 2, yd - 1),
i if i < 2 * xd + yd - 3 => orig + V2i(0, i - 2 * xd + 3),
i if i < 2 * xd + 2 * yd - 4 => orig + V2i(xd - 1, i - 2 * xd - yd + 3),
_ => panic!("index out of bounds when finding a rectangle edge"),
}
}
pub fn gen_rand_in_rect<R: Rng>(r: &R2i, rg: &mut R) -> V2i
{
let orig = r.origin();
let dims = r.dim();
V2i(Uniform::new(orig.0, orig.0 + dims.0).sample(rg),
Uniform::new(orig.1, orig.1 + dims.1).sample(rg))
}
}
use extra_utils::*;
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
enum RoomShape
{
Rect(R2i),
@ -71,6 +103,52 @@ impl RoomShape
Self::Circ(center, radius) => R2i::origin_dim(*center - V2i(radius - 1, radius - 1), V2i(2 * radius, 2 * radius)),
}
}
fn into_unique_form(&self, grid_coords: V2i) -> RoomShape
{
let coords = grid_coords * V2i(GRID_SIZE, GRID_SIZE);
match self
{
Self::Rect(rect) => Self::Rect(rect.translate(coords)),
Self::Circ(c, r) => Self::Circ(*c + coords, *r),
}
}
fn origin(&self) -> V2i
{
match self
{
Self::Rect(rect) => rect.origin(),
Self::Circ(c, _) => *c,
}
}
fn gen_rand_in<R: Rng>(&self, rg: &mut R) -> V2i
{
match self
{
Self::Rect(rect) => gen_rand_in_rect(rect, rg),
Self::Circ(c, r) =>
{
let bbox = self.get_bounding_rect();
let r2 = r * r;
loop
{
let p = gen_rand_in_rect(&bbox, rg);
if (p - *c).l2_sq() < r2
{
break p;
}
}
},
}
}
}
enum Path
{
Piecewise(Vec<Path>),
Line(V2i, V2i),
}
fn get_random_point<T: rand::Rng>(rng: &mut T) -> V2i
@ -108,18 +186,18 @@ fn get_room_shapes(seed: u64, coords: V2i) -> Vec<RoomShape>
{
RoomShape::Circ(get_random_point(&mut rg), circ_rad_dist.sample(&mut rg))
},
_ => panic!("random value out of range in worldgen"),
_ => panic!("random index out of range in worldgen (room generation - unknown room type)"),
};
rooms.push(shape);
}
rooms
}
fn get_nearby_room_shapes(seed: u64, coords: V2i) -> impl Iterator<Item=RoomShape>
fn get_nearby_room_shapes(seed: u64, coords: V2i, radius: isize) -> impl Iterator<Item=RoomShape>
{
R2i::origin_dim(V2i(-1, -1), V2i(2, 2)).iter()
.map(move |v| get_room_shapes(seed, v + coords).into_iter()
.map(move |shape| shape.offset_grid(v))).flatten()
R2i::origin_opp(V2i(-radius, -radius), V2i(1 + radius, 1 + radius)).iter()
.map(move |v| { get_room_shapes(seed, v + coords).into_iter()
.map(move |shape| shape.offset_grid(v)) }).flatten()
}
fn carve_rooms<T: Iterator<Item=RoomShape>>(map: &mut [Tile], shapes: T)
@ -130,15 +208,22 @@ fn carve_rooms<T: Iterator<Item=RoomShape>>(map: &mut [Tile], shapes: T)
let bbox = shape.get_bounding_rect().intersect(gbox);
if let Some(bbox) = bbox
{
for tile in bbox.iter()
match shape
{
if match shape
RoomShape::Rect(rect) =>
{
RoomShape::Rect(rect) => true,
RoomShape::Circ(c, r) => (tile - c).l2_sq() < r * r,
} {
map[(tile.0 * GRID_SIZE + tile.1) as usize].content = Content::Air;
}
for tile in bbox.iter()
{ map[(tile.1 * GRID_SIZE + tile.0) as usize].content = Content::Air; }
},
RoomShape::Circ(c, r) =>
{
for tile in bbox.iter()
{
if (tile - c).l2_sq() < r * r {
map[(tile.1 * GRID_SIZE + tile.0) as usize].content = Content::Air;
}
}
},
}
}
}
@ -153,7 +238,7 @@ fn test_rooms_intersect(rs1: RoomShape, rs2: RoomShape) -> bool
(Rect(r), Circ(c, rad)) |
(Circ(c, rad), Rect(r)) =>
{
enlarge_rect(r, V2i(1, 1), V2i(1, 1)).iter().fold(false, |b, t| b | ((c - t).l2_sq() < rad * rad))
r.grow(1).iter().fold(false, |b, t| b | ((c - t).l2_sq() < rad * rad))
},
(Circ(c1, r1), Circ(c2, r2)) =>
{
@ -163,14 +248,165 @@ fn test_rooms_intersect(rs1: RoomShape, rs2: RoomShape) -> bool
}
}
fn hash_unique_pair<H: Hasher>(rs1: RoomShape, rs2: RoomShape)
fn hash_unique_pair<H: Hasher>(coords: V2i, rs1: &RoomShape, rs2: &RoomShape, hasher: &mut H)
{
let rs1 = rs1.into_unique_form(coords);
let rs2 = rs2.into_unique_form(coords);
rs1.hash(hasher);
rs2.hash(hasher);
}
fn get_path_endpoint<R: Rng>(rs: &RoomShape, rg: &mut R) -> V2i
{
use RoomShape::*;
match rs
{
Circ(c, _) => *c,
Rect(r) =>
{
let bd = Bernoulli::new(USE_RECTANGLE_CENTER_FOR_PATH_END_PROB).unwrap();
if bd.sample(rg)
{
r.origin() + r.dim() / V2i(2, 2)
} else
{
let V2i(xd, yd) = r.dim();
let unif = Uniform::new(0, 2 * (xd + yd) + 4);
get_rect_edge_by_index(r.grow(1), unif.sample(rg))
}
}
}
}
fn carve_between(coords: V2i, rs1: &RoomShape, rs2: &RoomShape) -> Option<Path>
{
if rs2 < rs1 { return carve_between(coords, rs2, rs1); }
let mut hasher = DefaultHasher::new();
hash_unique_pair(coords, &rs1, &rs2, &mut hasher);
let mut rg = SmallRng::seed_from_u64(hasher.finish());
let mut ep1 = get_path_endpoint(&rs1, &mut rg);
let mut ep2 = get_path_endpoint(&rs2, &mut rg);
if Bernoulli::new(0.5).unwrap().sample(&mut rg) { std::mem::swap(&mut ep1, &mut ep2); }
let chooser = WeightedIndex::new(&PATH_STRATEGY_WEIGHTS).unwrap();
match chooser.sample(&mut rg)
{
0 => None,
1 =>
{
Some(Path::Line(ep1, ep2))
},
2 =>
{
let brect = R2i::origin_opp(ep1, ep2);
let V2i(xd, yd) = brect.dim();
let area = xd * yd;
if area == 0 { return None; }
let npoints: u64 = Poisson::new(PATH_ORTHOG_AVG_NUM_STOPS_PER_LEN * (area as f64).sqrt())
.unwrap().sample(&mut rg);
let mut tpoints = Vec::new();
tpoints.push(ep1);
for ii in 0 .. npoints
{
tpoints.push(gen_rand_in_rect(&brect, &mut rg));
}
tpoints.push(ep2);
tpoints.sort_by(|a, b| (*a - ep1).linf().cmp(&(*b - ep2).linf()));
let mut stops = Vec::new();
for ii in 0 .. npoints as usize + 1
{
let diff = tpoints[ii] - ep1;
if diff.0 > diff.1
{
stops.push(Path::Line(tpoints[ii], V2i(tpoints[ii].0, tpoints[ii + 1].1)));
stops.push(Path::Line(V2i(tpoints[ii].0, tpoints[ii + 1].1), tpoints[ii + 1]));
} else
{
stops.push(Path::Line(tpoints[ii], V2i(tpoints[ii + 1].0, tpoints[ii].1)));
stops.push(Path::Line(V2i(tpoints[ii + 1].0, tpoints[ii].1), tpoints[ii + 1]));
}
}
Some(Path::Piecewise(stops))
}
3 =>
{
let brect = R2i::origin_opp(ep1, ep2);
let bcirc = RoomShape::Circ(brect.origin() + brect.dim() / V2i(2, 2), (brect.dim().l2_sq() as f64).sqrt().ceil() as isize);
let V2i(xd, yd) = brect.dim();
let area = xd * yd;
if area == 0 { return None; }
let npoints: u64 = Poisson::new(PATH_CIRC_AVG_NUM_STOPS_PER_LEN * (area as f64).sqrt())
.unwrap().sample(&mut rg);
let mut tpoints = Vec::new();
tpoints.push(ep1);
for ii in 0 .. npoints
{
tpoints.push(bcirc.gen_rand_in(&mut rg));
}
tpoints.push(ep2);
fn s(a: V2i) -> isize { a.0 + a.1 }
fn dot(a: V2i, b: V2i) -> isize { s(a * b) }
let diff = ep2 - ep1;
tpoints.sort_by(|a, b| dot((*a - ep1), diff).cmp(&dot((*b - ep1), diff)));
let mut stops = Vec::new();
for ii in 0 .. npoints as usize + 1
{
stops.push(Path::Line(tpoints[ii], tpoints[ii + 1]));
}
Some(Path::Piecewise(stops))
}
_ => panic!("random index out of range in worldgen (path generation - unknown path strategy)"),
}
}
fn maybe_carve_between(coords: V2i, rs1: &RoomShape, rs2: &RoomShape, radius: isize) -> Option<Path>
{
let o1 = rs1.origin().div_euclid(V2i(GRID_SIZE, GRID_SIZE));
let o2 = rs2.origin().div_euclid(V2i(GRID_SIZE, GRID_SIZE));
if (o1 - o2).linf() <= radius
{
carve_between(coords, rs1, rs2)
} else
{
None
}
}
fn carve_paths<T: Iterator<Item=Path>>(map: &mut [Tile], paths: T)
{
use Path::*;
let gbox = R2i::origin_dim(V2i(0, 0), V2i(GRID_SIZE, GRID_SIZE));
for path in paths
{
match path
{
Piecewise(v) =>
{
carve_paths(map, v.into_iter());
},
Line(p1, p2) =>
{
let mut lineiter = raster::line(p1, p2);
for tile in lineiter
{
if gbox.contains(tile)
{
map[(tile.1 * GRID_SIZE + tile.0) as usize].content = Content::Air;
}
}
}
}
}
}
pub fn carve(seed: u64, grid: &mut Grid<Tile>, coords: V2i)
{
let rsi = get_nearby_room_shapes(seed, coords);
carve_rooms(grid.array_mut(), rsi);
let rooms = get_nearby_room_shapes(seed, coords, 1).collect::<Vec<_>>();
carve_rooms(grid.array_mut(), rooms.iter().map(|c| c.clone()));
let cpi = rooms.iter().map(|a| rooms.iter().map(move |b| (a, b))).flatten().map(|(a, b)| (a, b));
let paths = cpi.map(|(a, b)| maybe_carve_between(coords, a, b, 1)).filter_map(|o| o);
carve_paths(grid.array_mut(), paths);
}
#[cfg(test)]
@ -181,33 +417,45 @@ mod test
#[test]
fn list_rooms()
{
for ii in 0u64..20u64
for ii in 0u64..1u64
{
let rooms = get_room_shapes(ii, V2i(0, 0));
println!("rooms: {:?}", rooms);
let rooms = get_nearby_room_shapes(ii, V2i(0, 0), 1);
println!("rooms: {:?}", rooms.collect::<Vec<_>>());
}
}
/*
#[test]
fn show_rooms()
{
for seed in 0u64..20u64
{
let origin = V2i(0, 0);
let mut map = [false; GRID_USIZE * GRID_USIZE];
carve_rooms(&mut map, get_nearby_room_shapes(seed, origin));
let mut map = vec![Tile { content: Content::Stone }; GRID_USIZE * GRID_USIZE];
carve_rooms(&mut map[..], get_nearby_room_shapes(seed, origin, 1));
for x in 0 .. GRID_USIZE
{
for y in 0 .. GRID_USIZE
{
print!("{}", if map[x * GRID_USIZE + y] { ' ' } else { '#' });
print!("{}", if let Tile{ content: Content::Air } = map[x * GRID_USIZE + y] { ' ' } else { '#' });
}
println!("");
}
println!("");
}
}
*/
#[test]
fn rect_edge_test()
{
let (xd, yd) = (3 ,5);
let r = R2i::origin_dim(V2i(0, 0), V2i(xd, yd));
println!("rectangle: {:?}", r);
for i in 0 .. 2 * (xd + yd) - 4
{
let e = get_rect_edge_by_index(r, i);
println!("edge at: {:?}", e);
assert!(r.contains(e));
}
}
}

21
src/world/mod.rs

@ -67,21 +67,26 @@ mod test {
#[test]
fn room_align() -> std::io::Result<()> {
let mut w = World::new(0);
let mut w = World::new(2);
{
let r = w.region_mut();
let gs = r.grid_size();
r.get_or_create(V2i(0, 0));
r.get_or_create(V2i(0, gs.1));
r.get_or_create(V2i(gs.0, 0));
r.get_or_create(gs);
r.get_or_create(V2i(0, 0) - gs);
r.get_or_create(V2i(0, -gs.1));
r.get_or_create(V2i(-gs.0, 0));
for v in R2i::origin_dim(V2i(-10, -2), V2i(20, 5)).iter()
{
r.get_or_create(v * gs);
}
//r.get_or_create(V2i(0, 0));
//r.get_or_create(V2i(0, gs.1));
//r.get_or_create(V2i(gs.0, 0));
//r.get_or_create(gs);
//r.get_or_create(V2i(0, 0) - gs);
//r.get_or_create(V2i(0, -gs.1));
//r.get_or_create(V2i(-gs.0, 0));
}
let (width, height) = termion::terminal_size()?;
for ii in 0..height { println!(""); }
let rs = render::RenderState::new(V2i(width as isize, height as isize));
let mut out = std::io::stdout();

Loading…
Cancel
Save