From 1a55540493f84eb037045a24e85eb70511f6aef5 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 5 Mar 2021 00:40:15 +0000 Subject: [PATCH] Switched to 6x6 tiles, more natural paths --- world/src/canvas.rs | 4 +- world/src/layer/mod.rs | 14 +- world/src/layer/scatter.rs | 10 +- world/src/site2/mod.rs | 337 ++++++++++++++++++++++------------ world/src/site2/plot/house.rs | 42 +++-- world/src/site2/tile.rs | 42 ++++- 6 files changed, 293 insertions(+), 156 deletions(-) diff --git a/world/src/canvas.rs b/world/src/canvas.rs index 02de530bba..abc6f3a4b7 100644 --- a/world/src/canvas.rs +++ b/world/src/canvas.rs @@ -65,8 +65,8 @@ impl<'a> Canvas<'a> { /// inner `CanvasInfo` such that it may be used independently. pub fn info(&mut self) -> CanvasInfo<'a> { self.info } - pub fn get(&mut self, pos: Vec3) -> Option { - self.chunk.get(pos - self.wpos()).ok().copied() + pub fn get(&mut self, pos: Vec3) -> Block { + self.chunk.get(pos - self.wpos()).ok().copied().unwrap_or(Block::empty()) } pub fn set(&mut self, pos: Vec3, block: Block) { diff --git a/world/src/layer/mod.rs b/world/src/layer/mod.rs index 6ce4c16494..2ee84d558f 100644 --- a/world/src/layer/mod.rs +++ b/world/src/layer/mod.rs @@ -99,7 +99,7 @@ pub fn apply_paths_to(canvas: &mut Canvas) { let head_space = path.head_space(path_dist); for z in inset..inset + head_space { let pos = Vec3::new(wpos2d.x, wpos2d.y, surface_z + z); - if canvas.get(pos).unwrap().kind() != BlockKind::Water { + if canvas.get(pos).kind() != BlockKind::Water { let _ = canvas.set(pos, EMPTY_AIR); } } @@ -135,11 +135,7 @@ pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) { { // If the block a little above is liquid, we should stop carving out the cave in // order to leave a ceiling, and not floating water - if canvas - .get(Vec3::new(wpos2d.x, wpos2d.y, z + 2)) - .map(|b| b.is_liquid()) - .unwrap_or(false) - { + if canvas.get(Vec3::new(wpos2d.x, wpos2d.y, z + 2)).is_liquid() { break; } @@ -165,11 +161,7 @@ pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) { .mul(45.0) as i32; // Generate stalagtites if there's something for them to hold on to - if canvas - .get(Vec3::new(wpos2d.x, wpos2d.y, cave_roof)) - .map(|b| b.is_filled()) - .unwrap_or(false) - { + if canvas.get(Vec3::new(wpos2d.x, wpos2d.y, cave_roof)).is_filled() { for z in cave_roof - stalagtites..cave_roof { canvas.set( Vec3::new(wpos2d.x, wpos2d.y, z), diff --git a/world/src/layer/scatter.rs b/world/src/layer/scatter.rs index b1a153d6d9..870ba6ad0e 100644 --- a/world/src/layer/scatter.rs +++ b/world/src/layer/scatter.rs @@ -577,17 +577,11 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { // surface if let Some(solid_end) = (-4..8) .find(|z| { - canvas - .get(Vec3::new(wpos2d.x, wpos2d.y, alt + z)) - .map(|b| b.is_solid()) - .unwrap_or(false) + canvas.get(Vec3::new(wpos2d.x, wpos2d.y, alt + z)).is_solid() }) .and_then(|solid_start| { (1..8).map(|z| solid_start + z).find(|z| { - canvas - .get(Vec3::new(wpos2d.x, wpos2d.y, alt + z)) - .map(|b| !b.is_solid()) - .unwrap_or(true) + !canvas.get(Vec3::new(wpos2d.x, wpos2d.y, alt + z)).is_solid() }) }) { diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index 567fdfae58..f8a3a6855c 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -23,9 +23,19 @@ use common::{ }; use hashbrown::hash_map::DefaultHashBuilder; use rand::prelude::*; +use rand_chacha::ChaChaRng; use vek::*; use std::ops::Range; +/// Seed a new RNG from an old RNG, thereby making the old RNG indepedent of changing use of the new RNG. The practical +/// effect of this is to reduce the extent to which changes to child generation algorithm produce a 'butterfly effect' +/// on their parent generators, meaning that generators will be less likely to produce entirely different outcomes if +/// some detail of a generation algorithm changes slightly. This is generally good and makes worldgen code easier to +/// maintain and less liable to breaking changes. +fn reseed(rng: &mut impl Rng) -> impl Rng { + ChaChaRng::from_seed(rng.gen::<[u8; 32]>()) +} + #[derive(Default)] pub struct Site { pub(crate) origin: Vec2, @@ -72,11 +82,12 @@ impl Site { } } - pub fn create_road(&mut self, land: &Land, rng: &mut impl Rng, a: Vec2, b: Vec2, w: i32) -> Option> { + pub fn create_road(&mut self, land: &Land, rng: &mut impl Rng, a: Vec2, b: Vec2, w: u16) -> Option> { const MAX_ITERS: usize = 4096; + let range = -(w as i32) / 2..w as i32 - w as i32 / 2; let heuristic = |tile: &Vec2| { - for y in 0..w { - for x in 0..w { + for y in range.clone() { + for x in range.clone() { if self.tiles.get(*tile + Vec2::new(x, y)).is_obstacle() { return 1000.0; } @@ -105,11 +116,15 @@ impl Site { self.roads.push(plot); - for &tile in path.iter() { - for y in 0..w { - for x in 0..w { + for (i, &tile) in path.iter().enumerate() { + for y in range.clone() { + for x in range.clone() { self.tiles.set(tile + Vec2::new(x, y), Tile { - kind: TileKind::Road, + kind: TileKind::Road { + a: i.saturating_sub(1) as u16, + b: (i + 1).min(path.len() - 1) as u16, + w, + }, plot: Some(plot), }); } @@ -125,10 +140,10 @@ impl Site { |center, _| self.tiles.grow_aabr(center, area_range.clone(), min_dims) .ok() .filter(|aabr| { - (aabr.min.x..aabr.max.x).any(|x| self.tiles.get(Vec2::new(x, aabr.min.y - 1)).kind == TileKind::Road) - || (aabr.min.x..aabr.max.x).any(|x| self.tiles.get(Vec2::new(x, aabr.max.y)).kind == TileKind::Road) - || (aabr.min.y..aabr.max.y).any(|y| self.tiles.get(Vec2::new(aabr.min.x - 1, y)).kind == TileKind::Road) - || (aabr.min.y..aabr.max.y).any(|y| self.tiles.get(Vec2::new(aabr.max.x, y)).kind == TileKind::Road) + (aabr.min.x..aabr.max.x).any(|x| self.tiles.get(Vec2::new(x, aabr.min.y - 1)).is_road()) + || (aabr.min.x..aabr.max.x).any(|x| self.tiles.get(Vec2::new(x, aabr.max.y)).is_road()) + || (aabr.min.y..aabr.max.y).any(|y| self.tiles.get(Vec2::new(aabr.min.x - 1, y)).is_road()) + || (aabr.min.y..aabr.max.y).any(|y| self.tiles.get(Vec2::new(aabr.max.x, y)).is_road()) }), ) } @@ -171,7 +186,7 @@ impl Site { }); self.plazas.push(plaza); self.blit_aabr(aabr, Tile { - kind: TileKind::Road, + kind: TileKind::Road { a: 0, b: 0, w: 0 }, plot: Some(plaza), }); @@ -209,6 +224,8 @@ impl Site { } pub fn generate(land: &Land, rng: &mut impl Rng, origin: Vec2) -> Self { + let mut rng = reseed(rng); + let mut site = Site { origin, ..Site::default() @@ -216,34 +233,25 @@ impl Site { site.demarcate_obstacles(land); - site.make_plaza(land, rng); + site.make_plaza(land, &mut rng); let build_chance = Lottery::from(vec![ - (1.0, 0), - (48.0, 1), + (64.0, 1), (5.0, 2), (20.0, 3), - (1.0, 4), + (0.75, 4), ]); let mut castles = 0; - for _ in 0..1000 { - if site.plots.len() - site.plazas.len() > 80 { - break; - } - + for _ in 0..120 { match *build_chance.choose_seeded(rng.gen()) { - // Plaza - 0 => { - site.make_plaza(land, rng); - }, // House 1 => { let size = (2.0 + rng.gen::().powf(8.0) * 3.0).round() as u32; - if let Some((aabr, door_tile)) = attempt(10, || site.find_roadside_aabr(rng, 4..(size + 1).pow(2), Extent2::broadcast(size))) { + if let Some((aabr, door_tile)) = attempt(32, || site.find_roadside_aabr(&mut rng, 4..(size + 1).pow(2), Extent2::broadcast(size))) { let plot = site.create_plot(Plot { - kind: PlotKind::House(plot::House::generate(land, rng, &site, door_tile, aabr)), + kind: PlotKind::House(plot::House::generate(land, &mut reseed(&mut rng), &site, door_tile, aabr)), root_tile: aabr.center(), tiles: aabr_tiles(aabr).collect(), seed: rng.gen(), @@ -253,11 +261,13 @@ impl Site { kind: TileKind::Building, plot: Some(plot), }); + } else { + site.make_plaza(land, &mut rng); } }, // Guard tower 2 => { - if let Some((aabr, _)) = attempt(10, || site.find_roadside_aabr(rng, 4..4, Extent2::new(2, 2))) { + if let Some((aabr, _)) = attempt(10, || site.find_roadside_aabr(&mut rng, 4..4, Extent2::new(2, 2))) { let plot = site.create_plot(Plot { kind: PlotKind::Castle, root_tile: aabr.center(), @@ -278,7 +288,7 @@ impl Site { let tile = (Vec2::new( rng.gen_range(-1.0..1.0), rng.gen_range(-1.0..1.0), - ).normalized() * rng.gen_range(32.0..48.0)).map(|e| e as i32); + ).normalized() * rng.gen_range(16.0..48.0)).map(|e| e as i32); if site .plazas @@ -292,20 +302,24 @@ impl Site { } }) .unwrap_or_else(Vec2::zero); + site.tiles.find_near( search_pos, - |center, _| site.tiles.grow_aabr(center, 9..25, Extent2::new(3, 3)).ok()) + |center, _| site.tiles.grow_organic(&mut rng, center, 12..64).ok() + ) }) - .map(|(aabr, _)| { - site.blit_aabr(aabr, Tile { - kind: TileKind::Field, - plot: None, - }); + .map(|(tiles, _)| { + for tile in tiles { + site.tiles.set(tile, Tile { + kind: TileKind::Field, + plot: None, + }); + } }); }, // Castle - _ if castles < 1 => { - if let Some((aabr, _)) = attempt(10, || site.find_roadside_aabr(rng, 16 * 16..18 * 18, Extent2::new(16, 16))) { + 4 if castles < 1 => { + if let Some((aabr, _)) = attempt(10, || site.find_roadside_aabr(&mut rng, 16 * 16..18 * 18, Extent2::new(16, 16))) { let plot = site.create_plot(Plot { kind: PlotKind::Castle, root_tile: aabr.center(), @@ -330,7 +344,7 @@ impl Site { // Courtyard site.blit_aabr(Aabr { min: aabr.min + 1, max: aabr.max - 1 } , Tile { - kind: TileKind::Road, + kind: TileKind::Road { a: 0, b: 0, w: 0 }, plot: Some(plot), }); @@ -368,52 +382,184 @@ impl Site { pub fn render_tile(&self, canvas: &mut Canvas, dynamic_rng: &mut impl Rng, tpos: Vec2) { let tile = self.tiles.get(tpos); - let twpos = self.tile_center_wpos(tpos); - let cols = (-(TILE_SIZE as i32)..TILE_SIZE as i32 * 2).map(|y| (-(TILE_SIZE as i32)..TILE_SIZE as i32 * 2).map(move |x| (twpos + Vec2::new(x, y), Vec2::new(x, y)))).flatten(); + let twpos = self.tile_wpos(tpos); + let border = TILE_SIZE as i32; + let cols = (-border..TILE_SIZE as i32 + border).map(|y| (-border..TILE_SIZE as i32 + border).map(move |x| (twpos + Vec2::new(x, y), Vec2::new(x, y)))).flatten(); match &tile.kind { TileKind::Empty | TileKind::Hazard(_) => {}, - TileKind::Road => { - let near_roads = CARDINALS - .map(|rpos| if self.tiles.get(tpos + rpos) == tile { - Some(LineSegment2 { - start: self.tile_center_wpos(tpos).map(|e| e as f32), - end: self.tile_center_wpos(tpos + rpos).map(|e| e as f32), - }) - } else { - None - }); + // TileKind::Road => { + // let near_roads = CARDINALS + // .map(|rpos| if self.tiles.get(tpos + rpos) == tile { + // Some(LineSegment2 { + // start: self.tile_center_wpos(tpos).map(|e| e as f32), + // end: self.tile_center_wpos(tpos + rpos).map(|e| e as f32), + // }) + // } else { + // None + // }); - cols.for_each(|(wpos2d, offs)| { - let wpos2df = wpos2d.map(|e| e as f32); - let nearest_road = near_roads - .iter() - .copied() - .filter_map(|line| Some(line?.projected_point(wpos2df))) - .min_by_key(|p| p.distance_squared(wpos2df) as i32); + // let near_roads = CARDINALS + // .iter() + // .filter_map(|rpos| if self.tiles.get(tpos + rpos) == tile { + // Some(Aabr { + // min: self.tile_wpos(tpos).map(|e| e as f32), + // max: self.tile_wpos(tpos + 1).map(|e| e as f32), + // }) + // } else { + // None + // }); - let is_near_road = nearest_road.map_or(false, |r| r.distance_squared(wpos2df) < 3.0f32.powi(2)); + // // cols.for_each(|(wpos2d, offs)| { + // // let wpos2df = wpos2d.map(|e| e as f32); + // // let nearest_road = near_roads + // // .iter() + // // .copied() + // // .filter_map(|line| Some(line?.projected_point(wpos2df))) + // // .min_by_key(|p| p.distance_squared(wpos2df) as i32); - if let Some(nearest_road) = nearest_road - .filter(|r| r.distance_squared(wpos2df) < 4.0f32.powi(2)) - { - let road_alt = canvas.col(nearest_road.map(|e| e.floor() as i32)).map_or(0, |col| col.alt as i32); - (-4..5).for_each(|z| canvas.map( - Vec3::new(wpos2d.x, wpos2d.y, road_alt + z), - |b| if z > 0 { - Block::air(SpriteKind::Empty) - } else { - Block::new(BlockKind::Rock, Rgb::new(55, 45, 65)) - }, - )); - } - }); - }, + // // let is_near_road = nearest_road.map_or(false, |r| r.distance_squared(wpos2df) < 3.0f32.powi(2)); + + // // if let Some(nearest_road) = nearest_road + // // .filter(|r| r.distance_squared(wpos2df) < 6.0f32.powi(2)) + // // { + // // let road_alt = canvas.col(nearest_road.map(|e| e.floor() as i32)).map_or(0, |col| col.alt as i32); + // // (-4..5).for_each(|z| canvas.map( + // // Vec3::new(wpos2d.x, wpos2d.y, road_alt + z), + // // |b| if z > 0 { + // // Block::air(SpriteKind::Empty) + // // } else { + // // Block::new(BlockKind::Rock, Rgb::new(55, 45, 65)) + // // }, + // // )); + // // } + // // }); + + // cols.for_each(|(wpos2d, offs)| { + // let wpos2df = wpos2d.map(|e| e as f32); + + // let dist = near_roads + // .clone() + // .map(|aabr| aabr.distance_to_point(wpos2df).powi(2)) + // .sum::() + // .sqrt(); + + // if dist < 4.0 { + // let mut z = canvas.col(wpos2d).map_or(0, |col| col.alt as i32) + 8; + // for _ in 0..16 { + // let pos = Vec3::new(wpos2d.x, wpos2d.y, z); + // z -= 1; + // if !canvas.get(pos).is_filled() { + // canvas.map(pos, |b| b.with_sprite(SpriteKind::Empty)); + // } else { + // canvas.set(pos, Block::empty()); + // break; + // } + // } + + // for _ in 0..2 { + // let pos = Vec3::new(wpos2d.x, wpos2d.y, z); + // z -= 1; + // canvas.set(pos, Block::new(BlockKind::Rock, Rgb::new(55, 45, 50))); + // } + // } + // }); + // }, _ => {}, } } pub fn render(&self, canvas: &mut Canvas, dynamic_rng: &mut impl Rng) { + canvas.foreach_col(|canvas, wpos2d, col| { + + let tpos = self.wpos_tile_pos(wpos2d); + let near_roads = SQUARE_9 + .iter() + .filter_map(|rpos| { + let tile = self.tiles.get(tpos + rpos); + if let TileKind::Road { a, b, w } = &tile.kind { + if let Some(PlotKind::Road(path)) = tile.plot.map(|p| &self.plot(p).kind) { + Some((LineSegment2 { + start: self.tile_center_wpos(path.nodes()[*a as usize]).map(|e| e as f32), + end: self.tile_center_wpos(path.nodes()[*b as usize]).map(|e| e as f32), + }, *w)) + } else { + None + } + } else { + None + } + }); + + let wpos2df = wpos2d.map(|e| e as f32); + let dist = near_roads + .map(|(line, w)| (line.distance_to_point(wpos2df) - w as f32 * 2.0).max(0.0)) + .min_by_key(|d| (*d * 100.0) as i32); + + if dist.map_or(false, |d| d <= 0.75) { + let mut z = canvas.col(wpos2d).map_or(0, |col| col.alt as i32) + 8; + for _ in 0..16 { + let pos = Vec3::new(wpos2d.x, wpos2d.y, z); + z -= 1; + if !canvas.get(pos).is_filled() { + canvas.map(pos, |b| b.with_sprite(SpriteKind::Empty)); + } else { + canvas.set(pos, Block::empty()); + break; + } + } + + for _ in 0..2 { + let pos = Vec3::new(wpos2d.x, wpos2d.y, z); + z -= 1; + canvas.set(pos, Block::new(BlockKind::Rock, Rgb::new(55, 45, 50))); + } + } + + let tile = self.wpos_tile(wpos2d); + let seed = tile.plot.map_or(0, |p| self.plot(p).seed); + match tile.kind { + TileKind::Field /*| TileKind::Road*/ => (-4..5).for_each(|z| canvas.map( + Vec3::new(wpos2d.x, wpos2d.y, col.alt as i32 + z), + |b| if [ + BlockKind::Grass, + BlockKind::Earth, + BlockKind::Sand, + BlockKind::Snow, + BlockKind::Rock, + ] + .contains(&b.kind()) { + match tile.kind { + TileKind::Field => Block::new(BlockKind::Earth, Rgb::new(40, 5 + (seed % 32) as u8, 0)), + TileKind::Road { .. } => Block::new(BlockKind::Rock, Rgb::new(55, 45, 65)), + _ => unreachable!(), + } + } else { + b.with_sprite(SpriteKind::Empty) + }, + )), + // TileKind::Building => { + // let base_alt = tile.plot.map(|p| self.plot(p)).map_or(col.alt as i32, |p| p.base_alt); + // for z in base_alt - 12..base_alt + 16 { + // canvas.set( + // Vec3::new(wpos2d.x, wpos2d.y, z), + // Block::new(BlockKind::Wood, Rgb::new(180, 90 + (seed % 64) as u8, 120)) + // ); + // } + // }, + // TileKind::Castle | TileKind::Wall => { + // let base_alt = tile.plot.map(|p| self.plot(p)).map_or(col.alt as i32, |p| p.base_alt); + // for z in base_alt - 12..base_alt + if tile.kind == TileKind::Wall { 24 } else { 40 } { + // canvas.set( + // Vec3::new(wpos2d.x, wpos2d.y, z), + // Block::new(BlockKind::Wood, Rgb::new(40, 40, 55)) + // ); + // } + // }, + _ => {}, + } + }); + let tile_aabr = Aabr { min: self.wpos_tile_pos(canvas.wpos()) - 1, max: self.wpos_tile_pos(canvas.wpos() + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + 2) + 3, // Round up, uninclusive, border @@ -457,51 +603,6 @@ impl Site { } } } - - // canvas.foreach_col(|canvas, wpos2d, col| { - // let tile = self.wpos_tile(wpos2d); - // let seed = tile.plot.map_or(0, |p| self.plot(p).seed); - // match tile.kind { - // TileKind::Field /*| TileKind::Road*/ => (-4..5).for_each(|z| canvas.map( - // Vec3::new(wpos2d.x, wpos2d.y, col.alt as i32 + z), - // |b| if [ - // BlockKind::Grass, - // BlockKind::Earth, - // BlockKind::Sand, - // BlockKind::Snow, - // BlockKind::Rock, - // ] - // .contains(&b.kind()) { - // match tile.kind { - // TileKind::Field => Block::new(BlockKind::Earth, Rgb::new(40, 5 + (seed % 32) as u8, 0)), - // TileKind::Road => Block::new(BlockKind::Rock, Rgb::new(55, 45, 65)), - // _ => unreachable!(), - // } - // } else { - // b.with_sprite(SpriteKind::Empty) - // }, - // )), - // TileKind::Building => { - // let base_alt = tile.plot.map(|p| self.plot(p)).map_or(col.alt as i32, |p| p.base_alt); - // for z in base_alt - 12..base_alt + 16 { - // canvas.set( - // Vec3::new(wpos2d.x, wpos2d.y, z), - // Block::new(BlockKind::Wood, Rgb::new(180, 90 + (seed % 64) as u8, 120)) - // ); - // } - // }, - // TileKind::Castle | TileKind::Wall => { - // let base_alt = tile.plot.map(|p| self.plot(p)).map_or(col.alt as i32, |p| p.base_alt); - // for z in base_alt - 12..base_alt + if tile.kind == TileKind::Wall { 24 } else { 40 } { - // canvas.set( - // Vec3::new(wpos2d.x, wpos2d.y, z), - // Block::new(BlockKind::Wood, Rgb::new(40, 40, 55)) - // ); - // } - // }, - // _ => {}, - // } - // }); } } diff --git a/world/src/site2/plot/house.rs b/world/src/site2/plot/house.rs index 5cb2aeae57..8a6455db34 100644 --- a/world/src/site2/plot/house.rs +++ b/world/src/site2/plot/house.rs @@ -10,6 +10,7 @@ pub struct House { bounds: Aabr, alt: i32, levels: u32, + roof_color: Rgb, } impl House { @@ -28,7 +29,19 @@ impl House { max: site.tile_wpos(tile_aabr.max), }, alt: land.get_alt_approx(site.tile_center_wpos(door_tile)) as i32 + 2, - levels: rng.gen_range(1..3), + levels: rng.gen_range(1..2 + (tile_aabr.max - tile_aabr.min).product() / 6) as u32, + roof_color: { + let colors = [ + Rgb::new(21, 43, 48), + Rgb::new(11, 23, 38), + Rgb::new(45, 28, 21), + Rgb::new(10, 55, 40), + Rgb::new(5, 35, 15), + Rgb::new(40, 5, 11), + Rgb::new(55, 45, 11), + ]; + *colors.choose(rng).unwrap() + }, } } } @@ -40,24 +53,28 @@ impl Structure for House { mut prim: F, mut fill: G, ) { - let storey = 6; + let storey = 5; let roof = storey * self.levels as i32; let foundations = 12; // Walls - let outer = prim(Primitive::Aabb(Aabb { - min: self.bounds.min.with_z(self.alt - foundations), - max: (self.bounds.max + 1).with_z(self.alt + roof), - })); let inner = prim(Primitive::Aabb(Aabb { min: (self.bounds.min + 1).with_z(self.alt), max: self.bounds.max.with_z(self.alt + roof), })); - let walls = prim(Primitive::Xor(outer, inner)); + let outer = prim(Primitive::Aabb(Aabb { + min: self.bounds.min.with_z(self.alt - foundations), + max: (self.bounds.max + 1).with_z(self.alt + roof), + })); fill(Fill { - prim: walls, + prim: outer, block: Block::new(BlockKind::Rock, Rgb::new(181, 170, 148)), }); + fill(Fill { + prim: inner, + block: Block::empty(), + }); + let walls = prim(Primitive::Xor(outer, inner)); // wall pillars let mut pillars_y = prim(Primitive::Empty); @@ -79,12 +96,13 @@ impl Structure for House { let pillars = prim(Primitive::And(pillars_x, pillars_y)); fill(Fill { prim: pillars, - block: Block::new(BlockKind::Wood, Rgb::new(89, 44, 14)), + block: Block::new(BlockKind::Wood, Rgb::new(55, 25, 8)), }); // For each storey... for i in 0..self.levels + 1 { let height = storey * i as i32; + let window_height = storey - 3; // Windows x axis { @@ -92,7 +110,7 @@ impl Structure for House { for y in self.tile_aabr.min.y..self.tile_aabr.max.y { let window = prim(Primitive::Aabb(Aabb { min: (site.tile_wpos(Vec2::new(self.tile_aabr.min.x, y)) + Vec2::unit_y() * 2).with_z(self.alt + height + 2), - max: (site.tile_wpos(Vec2::new(self.tile_aabr.max.x, y + 1)) + Vec2::new(1, -1)).with_z(self.alt + height + 5), + max: (site.tile_wpos(Vec2::new(self.tile_aabr.max.x, y + 1)) + Vec2::new(1, -1)).with_z(self.alt + height + 2 + window_height), })); windows = prim(Primitive::Or(windows, window)); } @@ -107,7 +125,7 @@ impl Structure for House { for x in self.tile_aabr.min.x..self.tile_aabr.max.x { let window = prim(Primitive::Aabb(Aabb { min: (site.tile_wpos(Vec2::new(x, self.tile_aabr.min.y)) + Vec2::unit_x() * 2).with_z(self.alt + height + 2), - max: (site.tile_wpos(Vec2::new(x + 1, self.tile_aabr.max.y)) + Vec2::new(-1, 1)).with_z(self.alt + height + 5), + max: (site.tile_wpos(Vec2::new(x + 1, self.tile_aabr.max.y)) + Vec2::new(-1, 1)).with_z(self.alt + height + 2 + window_height), })); windows = prim(Primitive::Or(windows, window)); } @@ -156,7 +174,7 @@ impl Structure for House { }, inset: roof_height, }), - block: Block::new(BlockKind::Wood, Rgb::new(21, 43, 48)), + block: Block::new(BlockKind::Wood, self.roof_color), }); // Foundations diff --git a/world/src/site2/tile.rs b/world/src/site2/tile.rs index b9947c5643..f80fac3bec 100644 --- a/world/src/site2/tile.rs +++ b/world/src/site2/tile.rs @@ -1,8 +1,9 @@ use super::*; +use crate::util::DHashSet; use common::spiral::Spiral2d; use std::ops::Range; -pub const TILE_SIZE: u32 = 7; +pub const TILE_SIZE: u32 = 6; pub const ZONE_SIZE: u32 = 16; pub const ZONE_RADIUS: u32 = 16; pub const TILE_RADIUS: u32 = ZONE_SIZE * ZONE_RADIUS; @@ -54,13 +55,13 @@ impl TileGrid { self.get_mut(tpos).map(|t| std::mem::replace(t, tile)) } - pub fn find_near(&self, tpos: Vec2, f: impl Fn(Vec2, &Tile) -> Option) -> Option<(R, Vec2)> { + pub fn find_near(&self, tpos: Vec2, mut f: impl FnMut(Vec2, &Tile) -> Option) -> Option<(R, Vec2)> { const MAX_SEARCH_RADIUS_BLOCKS: u32 = 70; const MAX_SEARCH_CELLS: u32 = ((MAX_SEARCH_RADIUS_BLOCKS / TILE_SIZE) * 2 + 1).pow(2); Spiral2d::new() .take(MAX_SEARCH_CELLS as usize) .map(|r| tpos + r) - .find_map(|tpos| (&f)(tpos, self.get(tpos)).zip(Some(tpos))) + .find_map(|tpos| (&mut f)(tpos, self.get(tpos)).zip(Some(tpos))) } pub fn grow_aabr(&self, center: Vec2, area_range: Range, min_dims: Extent2) -> Result, Aabr> { @@ -71,7 +72,7 @@ impl TileGrid { }; let mut last_growth = 0; - for i in 0.. { + for i in 0..32 { if i - last_growth >= 4 { break; } else if aabr.size().product() + if i % 2 == 0 { aabr.size().h } else { aabr.size().w } > area_range.end as i32 { @@ -109,6 +110,33 @@ impl TileGrid { Err(aabr) } } + + pub fn grow_organic(&self, rng: &mut impl Rng, center: Vec2, area_range: Range) -> Result>, DHashSet>> { + let mut tiles = DHashSet::default(); + let mut open = Vec::new(); + + tiles.insert(center); + open.push(center); + + while tiles.len() < area_range.end as usize && !open.is_empty() { + let tile = open.remove(rng.gen_range(0..open.len())); + + for &rpos in CARDINALS.iter() { + let neighbor = tile + rpos; + + if self.get(neighbor).is_empty() && !tiles.contains(&neighbor) { + tiles.insert(neighbor); + open.push(neighbor); + } + } + } + + if tiles.len() >= area_range.start as usize { + Ok(tiles) + } else { + Err(tiles) + } + } } #[derive(Clone, PartialEq)] @@ -116,7 +144,7 @@ pub enum TileKind { Empty, Hazard(HazardKind), Field, - Road, + Road { a: u16, b: u16, w: u16 }, Building, Castle, Wall, @@ -146,6 +174,10 @@ impl Tile { pub fn is_empty(&self) -> bool { self.kind == TileKind::Empty } + pub fn is_road(&self) -> bool { + matches!(self.kind, TileKind::Road { .. }) + } + pub fn is_obstacle(&self) -> bool { matches!( self.kind,