Switched to 6x6 tiles, more natural paths

This commit is contained in:
Joshua Barretto 2021-03-05 00:40:15 +00:00
parent a229d65932
commit 02d86f0fb0
6 changed files with 293 additions and 156 deletions

View File

@ -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<i32>) -> Option<Block> {
self.chunk.get(pos - self.wpos()).ok().copied()
pub fn get(&mut self, pos: Vec3<i32>) -> Block {
self.chunk.get(pos - self.wpos()).ok().copied().unwrap_or(Block::empty())
}
pub fn set(&mut self, pos: Vec3<i32>, block: Block) {

View File

@ -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),

View File

@ -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()
})
})
{

View File

@ -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<i32>,
@ -72,11 +82,12 @@ impl Site {
}
}
pub fn create_road(&mut self, land: &Land, rng: &mut impl Rng, a: Vec2<i32>, b: Vec2<i32>, w: i32) -> Option<Id<Plot>> {
pub fn create_road(&mut self, land: &Land, rng: &mut impl Rng, a: Vec2<i32>, b: Vec2<i32>, w: u16) -> Option<Id<Plot>> {
const MAX_ITERS: usize = 4096;
let range = -(w as i32) / 2..w as i32 - w as i32 / 2;
let heuristic = |tile: &Vec2<i32>| {
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<i32>) -> 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::<f32>().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 {
.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<i32>) {
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::<f32>()
// .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))
// );
// }
// },
// _ => {},
// }
// });
}
}

View File

@ -10,6 +10,7 @@ pub struct House {
bounds: Aabr<i32>,
alt: i32,
levels: u32,
roof_color: Rgb<u8>,
}
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

View File

@ -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<R>(&self, tpos: Vec2<i32>, f: impl Fn(Vec2<i32>, &Tile) -> Option<R>) -> Option<(R, Vec2<i32>)> {
pub fn find_near<R>(&self, tpos: Vec2<i32>, mut f: impl FnMut(Vec2<i32>, &Tile) -> Option<R>) -> Option<(R, Vec2<i32>)> {
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<i32>, area_range: Range<u32>, min_dims: Extent2<u32>) -> Result<Aabr<i32>, Aabr<i32>> {
@ -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<i32>, area_range: Range<u32>) -> Result<DHashSet<Vec2<i32>>, DHashSet<Vec2<i32>>> {
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,