This commit is contained in:
Joshua Barretto 2021-03-05 01:23:11 +00:00
parent 02d86f0fb0
commit 862cd5fe49
27 changed files with 800 additions and 939 deletions

897
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -33,12 +33,10 @@ impl<T> FromIterator<T> for Path<T> {
}
impl<T> IntoIterator for Path<T> {
type Item = T;
type IntoIter = std::vec::IntoIter<T>;
type Item = T;
fn into_iter(self) -> Self::IntoIter {
self.nodes.into_iter()
}
fn into_iter(self) -> Self::IntoIter { self.nodes.into_iter() }
}
impl<T> Path<T> {

View File

@ -1,5 +1,5 @@
use std::{
cmp::{Eq, PartialEq, Ord, PartialOrd, Ordering},
cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd},
fmt, hash,
marker::PhantomData,
ops::{Index, IndexMut},
@ -30,14 +30,10 @@ impl<T> PartialEq for Id<T> {
fn eq(&self, other: &Self) -> bool { self.idx == other.idx && self.gen == other.gen }
}
impl<T> Ord for Id<T> {
fn cmp(&self, other: &Self) -> Ordering {
(self.idx, self.gen).cmp(&(other.idx, other.gen))
}
fn cmp(&self, other: &Self) -> Ordering { (self.idx, self.gen).cmp(&(other.idx, other.gen)) }
}
impl<T> PartialOrd for Id<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
impl<T> fmt::Debug for Id<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -77,9 +73,7 @@ impl<T> Default for Store<T> {
}
impl<T> Store<T> {
pub fn len(&self) -> usize {
self.len
}
pub fn len(&self) -> usize { self.len }
pub fn contains(&self, id: Id<T>) -> bool {
self.entries

View File

@ -364,7 +364,7 @@ image_ids! {
mmap_site_cave_bg: "voxygen.element.map.cave_bg",
mmap_site_cave_hover: "voxygen.element.map.cave_hover",
mmap_site_cave: "voxygen.element.map.cave",
mmap_site_excl: "voxygen.element.map.excl",
mmap_site_excl: "voxygen.element.map.excl",
// Window Parts
window_3: "voxygen.element.frames.window_3",

View File

@ -586,8 +586,10 @@ impl<'a> Widget for Map<'a> {
if show_caves {
site_btn.set(state.ids.mmap_site_icons[i], ui);
}
},
_ => {site_btn.set(state.ids.mmap_site_icons[i], ui);},
},
_ => {
site_btn.set(state.ids.mmap_site_icons[i], ui);
},
}
// Difficulty from 0-6

View File

@ -380,9 +380,15 @@ impl<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug + 'static>
let greedy_size_cross = Vec3::new(greedy_size.x - 1, greedy_size.y - 1, greedy_size.z);
let draw_delta = Vec3::new(1, 1, z_start);
let get_light = |_: &mut (), pos: Vec3<i32>| volume
.get(range.min + pos)
.map_or(1.0, |b| if b.is_opaque() { 0.0 } else { light(pos + range.min) });
let get_light = |_: &mut (), pos: Vec3<i32>| {
volume.get(range.min + pos).map_or(1.0, |b| {
if b.is_opaque() {
0.0
} else {
light(pos + range.min)
}
})
};
let get_glow = |_: &mut (), pos: Vec3<i32>| glow(pos + range.min);
let get_color =
|_: &mut (), pos: Vec3<i32>| flat_get(pos).get_color().unwrap_or(Rgb::zero());

View File

@ -2,7 +2,8 @@ use core::{iter, mem};
use hashbrown::HashMap;
use num::traits::Float;
pub use vek::{geom::repr_simd::*, mat::repr_simd::column_major::Mat4, ops::*, vec::repr_simd::*};
//pub use vek::{geom::repr_c::*, mat::repr_c::column_major::Mat4, ops::*, vec::repr_c::*};
//pub use vek::{geom::repr_c::*, mat::repr_c::column_major::Mat4, ops::*,
// vec::repr_c::*};
pub fn aabb_to_points<T: Float>(bounds: Aabb<T>) -> [Vec3<T>; 8] {
[

View File

@ -3,7 +3,7 @@ mod watcher;
pub use self::watcher::BlocksOfInterest;
use crate::{
mesh::{greedy::GreedyMesh, Meshable, terrain::SUNLIGHT},
mesh::{greedy::GreedyMesh, terrain::SUNLIGHT, Meshable},
render::{
ColLightFmt, ColLightInfo, Consts, FluidPipeline, GlobalModel, Instances, Mesh, Model,
RenderError, Renderer, ShadowPipeline, SpriteInstance, SpriteLocals, SpritePipeline,
@ -524,22 +524,35 @@ impl<V: RectRasterableVol> Terrain<V> {
.map(|c| c.blocks_of_interest.lights.iter())
.into_iter()
.flatten()
.map(move |(lpos, level)| (Vec3::<i32>::from(chunk_pos * TerrainChunk::RECT_SIZE.map(|e| e as i32)) + *lpos, level))
.map(move |(lpos, level)| {
(
Vec3::<i32>::from(
chunk_pos * TerrainChunk::RECT_SIZE.map(|e| e as i32),
) + *lpos,
level,
)
})
})
.flatten()
.fold((Vec3::broadcast(0.001), 0.0, 0.0f32), |(bias, total, max), (lpos, level)| {
let rpos = lpos.map(|e| e as f32 + 0.5) - wpos;
let level = (*level as f32 - rpos.magnitude()).max(0.0) / SUNLIGHT as f32;
(
bias + rpos.try_normalized().unwrap_or_else(Vec3::zero) * level,
total + level,
max.max(level),
)
});
.fold(
(Vec3::broadcast(0.001), 0.0, 0.0f32),
|(bias, total, max), (lpos, level)| {
let rpos = lpos.map(|e| e as f32 + 0.5) - wpos;
let level = (*level as f32 - rpos.magnitude()).max(0.0) / SUNLIGHT as f32;
(
bias + rpos.try_normalized().unwrap_or_else(Vec3::zero) * level,
total + level,
max.max(level),
)
},
);
let bias_factor = bias.magnitude() * (1.0 - AMBIANCE) / total.max(0.001);
(bias.try_normalized().unwrap_or_else(Vec3::zero) * bias_factor.powf(0.5), self.glow_at_wpos(wpos.map(|e| e.floor() as i32)))
(
bias.try_normalized().unwrap_or_else(Vec3::zero) * bias_factor.powf(0.5),
self.glow_at_wpos(wpos.map(|e| e.floor() as i32)),
)
}
/// Maintain terrain data. To be called once per tick.
@ -630,21 +643,19 @@ impl<V: RectRasterableVol> Terrain<V> {
// be meshed
span!(guard, "Add chunks with modified blocks to mesh todo list");
// TODO: would be useful if modified blocks were grouped by chunk
for (&pos, &block) in scene_data
.state
.terrain_changes()
.modified_blocks
.iter()
{
// TODO: Be cleverer about this to avoid remeshing all neighbours. There are a few things that can create
// an 'effect at a distance'. These are as follows:
// - A glowing block is added or removed, thereby causing a lighting recalculation proportional to its glow
// radius.
// - An opaque block that was blocking sunlight from entering a cavity is removed (or added) thereby
for (&pos, &block) in scene_data.state.terrain_changes().modified_blocks.iter() {
// TODO: Be cleverer about this to avoid remeshing all neighbours. There are a
// few things that can create an 'effect at a distance'. These are
// as follows:
// - A glowing block is added or removed, thereby causing a lighting
// recalculation proportional to its glow radius.
// - An opaque block that was blocking sunlight from entering a cavity is
// removed (or added) thereby
// changing the way that sunlight propagates into the cavity.
//
// We can and should be cleverer about this, but it's non-trivial. For now, just conservatively assume that
// the lighting in all neighbouring chunks is invalidated. Thankfully, this doesn't need to happen often
// We can and should be cleverer about this, but it's non-trivial. For now, just
// conservatively assume that the lighting in all neighbouring
// chunks is invalidated. Thankfully, this doesn't need to happen often
// because block modification is unusual in Veloren.
// let block_effect_radius = block.get_glow().unwrap_or(0).max(1);
let block_effect_radius = crate::mesh::terrain::MAX_LIGHT_DIST;

View File

@ -1,7 +1,7 @@
use std::ops::Range;
use enum_iterator::IntoEnumIterator;
use vek::*;
use crate::util::math::close;
use enum_iterator::IntoEnumIterator;
use std::ops::Range;
use vek::*;
#[derive(Copy, Clone, Debug, IntoEnumIterator)]
pub enum ForestKind {
@ -33,7 +33,7 @@ impl ForestKind {
ForestKind::Birch => 0.0..0.6,
ForestKind::Mangrove => 0.65..1.3,
ForestKind::Swamp => 0.5..1.1,
_ => 0.0..0.0
_ => 0.0..0.0,
}
}
@ -76,9 +76,11 @@ impl ForestKind {
pub fn proclivity(&self, env: &Environment) -> f32 {
self.ideal_proclivity()
* close(env.humid, self.humid_range())
* close(env.temp, self.temp_range())
* self.near_water_range().map_or(1.0, |near_water_range| close(env.near_water, near_water_range))
* close(env.humid, self.humid_range())
* close(env.temp, self.temp_range())
* self.near_water_range().map_or(1.0, |near_water_range| {
close(env.near_water, near_water_range)
})
}
}

View File

@ -1,6 +1,6 @@
use crate::{
column::{ColumnGen, ColumnSample},
util::{RandomField, Sampler, SmallCache, FastNoise},
util::{FastNoise, RandomField, Sampler, SmallCache},
IndexRef,
};
use common::terrain::{
@ -121,14 +121,22 @@ impl<'a> BlockGen<'a> {
if stone_factor >= 0.5 {
if wposf.z as f32 > height - cliff_offset.max(0.0) {
if cliff_offset.max(0.0) > cliff_height - (FastNoise::new(37).get(wposf / Vec3::new(6.0, 6.0, 10.0)) * 0.5 + 0.5) * (height - grass_depth - wposf.z as f32).mul(0.25).clamped(0.0, 8.0) {
if cliff_offset.max(0.0)
> cliff_height
- (FastNoise::new(37).get(wposf / Vec3::new(6.0, 6.0, 10.0)) * 0.5
+ 0.5)
* (height - grass_depth - wposf.z as f32)
.mul(0.25)
.clamped(0.0, 8.0)
{
Some(Block::empty())
} else {
let col = Lerp::lerp(
col.map(|e| e as f32),
col.map(|e| e as f32) * 0.7,
(wposf.z as f32 - basement * 0.3).div(2.0).sin() * 0.5 + 0.5,
).map(|e| e as u8);
)
.map(|e| e as u8);
Some(Block::new(BlockKind::Rock, col))
}
} else {
@ -198,7 +206,9 @@ pub struct ZCache<'a> {
impl<'a> ZCache<'a> {
pub fn get_z_limits(&self) -> (f32, f32) {
let min = self.sample.alt - (self.sample.chaos.min(1.0) * 16.0) - self.sample.cliff_offset.max(0.0);
let min = self.sample.alt
- (self.sample.chaos.min(1.0) * 16.0)
- self.sample.cliff_offset.max(0.0);
let min = min - 4.0;
let rocks = if self.sample.rock > 0.0 { 12.0 } else { 0.0 };

View File

@ -2,9 +2,9 @@ use crate::{
block::ZCache,
column::ColumnSample,
index::IndexRef,
land::Land,
sim::{SimChunk, WorldSim},
util::Grid,
land::Land,
};
use common::{
terrain::{Block, TerrainChunk, TerrainChunkSize},
@ -48,9 +48,7 @@ impl<'a> CanvasInfo<'a> {
pub fn chunks(&self) -> &'a WorldSim { self.chunks }
pub fn land(&self) -> Land<'_> {
Land::from_sim(self.chunks)
}
pub fn land(&self) -> Land<'_> { Land::from_sim(self.chunks) }
}
pub struct Canvas<'a> {
@ -66,7 +64,11 @@ impl<'a> Canvas<'a> {
pub fn info(&mut self) -> CanvasInfo<'a> { self.info }
pub fn get(&mut self, pos: Vec3<i32>) -> Block {
self.chunk.get(pos - self.wpos()).ok().copied().unwrap_or(Block::empty())
self.chunk
.get(pos - self.wpos())
.ok()
.copied()
.unwrap_or(Block::empty())
}
pub fn set(&mut self, pos: Vec3<i32>, block: Block) {

View File

@ -7,10 +7,9 @@ use crate::{
config::CONFIG,
sim::WorldSim,
site::{namegen::NameGen, Castle, Dungeon, Settlement, Site as WorldSite},
util::{attempt, seed_expan, MapVec, CARDINALS, NEIGHBORS},
site2,
Index,
Land,
util::{attempt, seed_expan, MapVec, CARDINALS, NEIGHBORS},
Index, Land,
};
use common::{
astar::Astar,
@ -216,9 +215,11 @@ impl Civs {
SiteKind::Castle => {
WorldSite::castle(Castle::generate(wpos, Some(ctx.sim), &mut rng))
},
SiteKind::Refactor => {
WorldSite::refactor(site2::Site::generate(&Land::from_sim(&ctx.sim), &mut rng, wpos))
},
SiteKind::Refactor => WorldSite::refactor(site2::Site::generate(
&Land::from_sim(&ctx.sim),
&mut rng,
wpos,
)),
});
sim_site.site_tmp = Some(site);
let site_ref = &index.sites[site];

View File

@ -272,11 +272,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
.rem_euclid(200.0)
/ 64.0
- 1.0;
let cliff_scale = ((self.sim.gen_ctx.hill_nz.get(wposf.div(128.0).into_array()) as f32 * 1.5 + 0.75)
+ self.sim.gen_ctx.hill_nz.get(wposf.div(48.0).into_array()) as f32 * 0.1)
.clamped(0.0, 1.0).powf(2.0);
let cliff_height =
sim.get_interpolated(wpos, |chunk| chunk.cliff_height)? * cliff_scale;
let cliff_scale =
((self.sim.gen_ctx.hill_nz.get(wposf.div(128.0).into_array()) as f32 * 1.5 + 0.75)
+ self.sim.gen_ctx.hill_nz.get(wposf.div(48.0).into_array()) as f32 * 0.1)
.clamped(0.0, 1.0)
.powf(2.0);
let cliff_height = sim.get_interpolated(wpos, |chunk| chunk.cliff_height)? * cliff_scale;
let cliff = if cliff_factor < 0.0 {
cliff_factor.abs().powf(1.5)
} else {

View File

@ -1,31 +1,31 @@
use crate::sim;
use common::{
terrain::TerrainChunkSize,
vol::RectVolSize,
};
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
use vek::*;
/// A wrapper type that may contain a reference to a generated world. If not, default values will be provided.
/// A wrapper type that may contain a reference to a generated world. If not,
/// default values will be provided.
pub struct Land<'a> {
sim: Option<&'a sim::WorldSim>,
}
impl<'a> Land<'a> {
pub fn empty() -> Self {
Self { sim: None }
}
pub fn empty() -> Self { Self { sim: None } }
pub fn from_sim(sim: &'a sim::WorldSim) -> Self {
Self { sim: Some(sim) }
}
pub fn from_sim(sim: &'a sim::WorldSim) -> Self { Self { sim: Some(sim) } }
pub fn get_alt_approx(&self, wpos: Vec2<i32>) -> f32 {
self.sim.and_then(|sim| sim.get_alt_approx(wpos)).unwrap_or(0.0)
self.sim
.and_then(|sim| sim.get_alt_approx(wpos))
.unwrap_or(0.0)
}
pub fn get_gradient_approx(&self, wpos: Vec2<i32>) -> f32 {
self.sim
.and_then(|sim| sim.get_gradient_approx(wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e.div_euclid(sz as i32))))
.and_then(|sim| {
sim.get_gradient_approx(
wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e.div_euclid(sz as i32)),
)
})
.unwrap_or(0.0)
}

View File

@ -161,7 +161,10 @@ 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)).is_filled() {
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,11 +577,15 @@ 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)).is_solid()
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)).is_solid()
!canvas
.get(Vec3::new(wpos2d.x, wpos2d.y, alt + z))
.is_solid()
})
})
{

View File

@ -60,7 +60,14 @@ pub fn apply_trees_to(canvas: &mut Canvas, dynamic_rng: &mut impl Rng) {
canvas.foreach_col(|canvas, wpos2d, col| {
let trees = info.chunks().get_near_trees(wpos2d);
for TreeAttr { pos, seed, scale, forest_kind, inhabited } in trees {
for TreeAttr {
pos,
seed,
scale,
forest_kind,
inhabited,
} in trees
{
let tree = if let Some(tree) = tree_cache.entry(pos).or_insert_with(|| {
let col = ColumnGen::new(info.chunks()).get((pos, info.index()))?;
@ -127,7 +134,11 @@ pub fn apply_trees_to(canvas: &mut Canvas, dynamic_rng: &mut impl Rng) {
ForestKind::Giant => {
break 'model TreeModel::Procedural(
ProceduralTree::generate(
TreeConfig::giant(&mut RandomPerm::new(seed), scale, inhabited),
TreeConfig::giant(
&mut RandomPerm::new(seed),
scale,
inhabited,
),
&mut RandomPerm::new(seed),
),
StructureBlock::TemperateLeaves,
@ -184,7 +195,9 @@ pub fn apply_trees_to(canvas: &mut Canvas, dynamic_rng: &mut impl Rng) {
TreeModel::Structure(s) => s.get(model_pos).ok().copied(),
TreeModel::Procedural(t, leaf_block) => Some(
match t.is_branch_or_leaves_at(model_pos.map(|e| e as f32 + 0.5)) {
(_, _, true, _) => StructureBlock::Block(BlockKind::Wood, Rgb::new(110, 68, 22)),
(_, _, true, _) => {
StructureBlock::Block(BlockKind::Wood, Rgb::new(110, 68, 22))
},
(_, _, _, true) => StructureBlock::None,
(true, _, _, _) => StructureBlock::Log,
(_, true, _, _) => *leaf_block,
@ -204,9 +217,14 @@ pub fn apply_trees_to(canvas: &mut Canvas, dynamic_rng: &mut impl Rng) {
)
.map(|block| {
// Add lights to the tree
if inhabited && last_block.is_air() && block.kind() == BlockKind::Wood && dynamic_rng.gen_range(0..256) == 0 {
if inhabited
&& last_block.is_air()
&& block.kind() == BlockKind::Wood
&& dynamic_rng.gen_range(0..256) == 0
{
canvas.set(wpos + Vec3::unit_z(), Block::air(SpriteKind::Lantern));
// Add a snow covering to the block above under certain circumstances
// Add a snow covering to the block above under certain
// circumstances
} else if col.snow_cover
&& ((block.kind() == BlockKind::Leaves && is_leaf_top)
|| (is_top && block.is_filled()))
@ -423,8 +441,7 @@ impl ProceduralTree {
const PHI: f32 = 0.618;
const RAD_PER_BRANCH: f32 = f32::consts::TAU * PHI;
let screw = (screw_shift + dist * splits as f32 * RAD_PER_BRANCH).sin()
* x_axis
let screw = (screw_shift + dist * splits as f32 * RAD_PER_BRANCH).sin() * x_axis
+ (screw_shift + dist * splits as f32 * RAD_PER_BRANCH).cos() * y_axis;
// Choose a point close to the branch to act as the target direction for the
@ -488,7 +505,12 @@ impl ProceduralTree {
pub fn get_bounds(&self) -> Aabb<f32> { self.branches[self.trunk_idx].aabb }
// Recursively search for branches or leaves by walking the tree's branch graph.
fn is_branch_or_leaves_at_inner(&self, pos: Vec3<f32>, parent: &Branch, branch_idx: usize) -> (bool, bool, bool, bool) {
fn is_branch_or_leaves_at_inner(
&self,
pos: Vec3<f32>,
parent: &Branch,
branch_idx: usize,
) -> (bool, bool, bool, bool) {
let branch = &self.branches[branch_idx];
// Always probe the sibling branch, since our AABB doesn't include its bounds
// (it's not one of our children)
@ -506,7 +528,8 @@ impl ProceduralTree {
let siblings = branch_or_leaves | Vec4::from(this);
// Probe the children of this branch
let children = branch.child_idx
let children = branch
.child_idx
.map(|idx| Vec4::<bool>::from(self.is_branch_or_leaves_at_inner(pos, branch, idx)))
.unwrap_or_default();
@ -521,8 +544,9 @@ impl ProceduralTree {
/// position in the tree.
#[inline(always)]
pub fn is_branch_or_leaves_at(&self, pos: Vec3<f32>) -> (bool, bool, bool, bool) {
let (log, leaf, platform, air) = self.is_branch_or_leaves_at_inner(pos, &self.branches[self.trunk_idx], self.trunk_idx);
(log /*& !air*/, leaf & !air, platform & !air, air)
let (log, leaf, platform, air) =
self.is_branch_or_leaves_at_inner(pos, &self.branches[self.trunk_idx], self.trunk_idx);
(log /* & !air */, leaf & !air, platform & !air, air)
}
}
@ -547,7 +571,11 @@ struct Branch {
impl Branch {
/// Determine whether there are either branches or leaves at the given
/// position in the branch.
pub fn is_branch_or_leaves_at(&self, pos: Vec3<f32>, parent: &Branch) -> ((bool, bool, bool, bool), f32) {
pub fn is_branch_or_leaves_at(
&self,
pos: Vec3<f32>,
parent: &Branch,
) -> ((bool, bool, bool, bool), f32) {
// fn finvsqrt(x: f32) -> f32 {
// let y = f32::from_bits(0x5f375a86 - (x.to_bits() >> 1));
// y * (1.5 - ( x * 0.5 * y * y ))
@ -588,20 +616,32 @@ impl Branch {
let stair_thickness = 2.0;
let stair_space = 5.0;
if self.has_stairs {
let (platform, air) = if pos.z >= self.line.start.z.min(self.line.end.z) - 1.0 && pos.z <= self.line.start.z.max(self.line.end.z) + stair_thickness + stair_space && d2 < (wood_radius + stair_width).powi(2) {
let (platform, air) = if pos.z >= self.line.start.z.min(self.line.end.z) - 1.0
&& pos.z
<= self.line.start.z.max(self.line.end.z) + stair_thickness + stair_space
&& d2 < (wood_radius + stair_width).powi(2)
{
let rpos = pos.xy() - p;
let stretch = 32.0;
let stair_section = ((rpos.x as f32).atan2(rpos.y as f32) / (f32::consts::PI * 2.0) * stretch + pos.z).rem_euclid(stretch);
(stair_section < stair_thickness, stair_section >= stair_thickness && stair_section < stair_thickness + stair_space) // Stairs
let stair_section =
((rpos.x as f32).atan2(rpos.y as f32) / (f32::consts::PI * 2.0) * stretch
+ pos.z)
.rem_euclid(stretch);
(
stair_section < stair_thickness,
stair_section >= stair_thickness
&& stair_section < stair_thickness + stair_space,
) // Stairs
} else {
(false, false)
};
let platform = platform || (self.has_stairs
&& self.wood_radius > 4.0
&& !air
&& d2 < (wood_radius + 10.0).powi(2)
&& pos.z % 48.0 < stair_thickness);
let platform = platform
|| (self.has_stairs
&& self.wood_radius > 4.0
&& !air
&& d2 < (wood_radius + 10.0).powi(2)
&& pos.z % 48.0 < stair_thickness);
(false, false, platform, air)
} else {

View File

@ -166,7 +166,11 @@ pub fn sample_pos(
});
let downhill_wpos = downhill.unwrap_or(wpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32));
let alt = if is_basement { basement } else { column_rgb_alt.map_or(alt, |(_, alt)| alt) };
let alt = if is_basement {
basement
} else {
column_rgb_alt.map_or(alt, |(_, alt)| alt)
};
let true_water_alt = (alt.max(water_alt) as f64 - focus.z) / gain as f64;
let true_alt = (alt as f64 - focus.z) / gain as f64;

View File

@ -29,22 +29,22 @@ use crate::{
column::ColumnGen,
site::Site,
util::{
seed_expan, FastNoise, FastNoise2d, RandomField, RandomPerm, Sampler, StructureGen2d, LOCALITY,
NEIGHBORS, CARDINALS, DHashSet,
seed_expan, DHashSet, FastNoise, FastNoise2d, RandomField, RandomPerm, Sampler,
StructureGen2d, CARDINALS, LOCALITY, NEIGHBORS,
},
IndexRef, CONFIG,
};
use common::{
assets::{self, AssetExt},
grid::Grid,
lottery::Lottery,
spiral::Spiral2d,
store::Id,
terrain::{
map::MapConfig, uniform_idx_as_vec2, vec2_as_uniform_idx, BiomeKind, MapSizeLg,
TerrainChunkSize,
},
vol::RectVolSize,
lottery::Lottery,
spiral::Spiral2d,
};
use common_net::msg::WorldMapMsg;
use enum_iterator::IntoEnumIterator;
@ -1535,7 +1535,10 @@ impl WorldSim {
pos += CARDINALS
.iter()
.copied()
.max_by_key(|rpos| self.get_gradient_approx(pos + rpos).map_or(0, |g| (g * 1000.0) as i32))
.max_by_key(|rpos| {
self.get_gradient_approx(pos + rpos)
.map_or(0, |g| (g * 1000.0) as i32)
})
.unwrap(); // Can't fail
} else {
break;
@ -1563,8 +1566,8 @@ impl WorldSim {
// self.get_mut(locs[2].0).unwrap().cliff.0.neighbors |=
// 1 << ((to_next_idx as u8 + 4) % 8);
// self.get_mut(locs[1].0).unwrap().cliff.0.offset = Vec2::new(rng.gen_range(-16..17), rng.gen_range(-16..17));
// }
// self.get_mut(locs[1].0).unwrap().cliff.0.offset =
// Vec2::new(rng.gen_range(-16..17), rng.gen_range(-16..17)); }
for cliff in cliffs {
let alt = self.get(cliff).map_or(0.0, |c| c.alt);
@ -2076,21 +2079,30 @@ impl WorldSim {
let env = Environment {
humid: chunk.humidity,
temp: chunk.temp,
near_water: if chunk.river.is_lake() || chunk.river.near_river() { 1.0 } else { 0.0 },
near_water: if chunk.river.is_lake() || chunk.river.near_river() {
1.0
} else {
0.0
},
};
Some(TreeAttr {
pos,
seed,
scale: 1.0,
forest_kind: *Lottery::from(ForestKind::into_enum_iter()
.enumerate()
.map(|(i, fk)| {
const CLUSTER_SIZE: f64 = 48.0;
let nz = (FastNoise2d::new(i as u32 * 37).get(pos.map(|e| e as f64) / CLUSTER_SIZE) + 1.0) / 2.0;
(fk.proclivity(&env) * nz, fk)
})
.collect::<Vec<_>>())
.choose_seeded(seed),
forest_kind: *Lottery::from(
ForestKind::into_enum_iter()
.enumerate()
.map(|(i, fk)| {
const CLUSTER_SIZE: f64 = 48.0;
let nz = (FastNoise2d::new(i as u32 * 37)
.get(pos.map(|e| e as f64) / CLUSTER_SIZE)
+ 1.0)
/ 2.0;
(fk.proclivity(&env) * nz, fk)
})
.collect::<Vec<_>>(),
)
.choose_seeded(seed),
inhabited: false,
})
});
@ -2344,7 +2356,11 @@ impl SimChunk {
let env = Environment {
humid: humidity,
temp,
near_water: if river.is_lake() || river.near_river() { 1.0 } else { 0.0 },
near_water: if river.is_lake() || river.near_river() {
1.0
} else {
0.0
},
};
ForestKind::into_enum_iter()
@ -2393,7 +2409,5 @@ impl SimChunk {
}
}
pub fn near_cliffs(&self) -> bool {
self.cliff_height > 0.0
}
pub fn near_cliffs(&self) -> bool { self.cliff_height > 0.0 }
}

View File

@ -11,12 +11,7 @@ pub use self::{
settlement::Settlement,
};
use crate::{
column::ColumnSample,
IndexRef,
site2,
Canvas,
};
use crate::{column::ColumnSample, site2, Canvas, IndexRef};
use common::{
generation::ChunkSupplement,
terrain::Block,
@ -118,11 +113,7 @@ impl Site {
}
}
pub fn apply_to<'a>(
&'a self,
canvas: &mut Canvas,
dynamic_rng: &mut impl Rng,
) {
pub fn apply_to<'a>(&'a self, canvas: &mut Canvas, dynamic_rng: &mut impl Rng) {
let info = canvas.info();
let get_col = |wpos| info.col(wpos + info.wpos);
match &self.kind {

View File

@ -1,7 +1,7 @@
use super::*;
use common::{
terrain::Block,
store::{Id, Store},
terrain::Block,
};
use vek::*;
@ -26,9 +26,11 @@ pub struct Fill {
impl Fill {
fn contains_at(&self, tree: &Store<Primitive>, prim: Id<Primitive>, pos: Vec3<i32>) -> bool {
// Custom closure because vek's impl of `contains_point` is inclusive :(
let aabb_contains = |aabb: Aabb<i32>, pos: Vec3<i32>| (aabb.min.x..aabb.max.x).contains(&pos.x)
&& (aabb.min.y..aabb.max.y).contains(&pos.y)
&& (aabb.min.z..aabb.max.z).contains(&pos.z);
let aabb_contains = |aabb: Aabb<i32>, pos: Vec3<i32>| {
(aabb.min.x..aabb.max.x).contains(&pos.x)
&& (aabb.min.y..aabb.max.y).contains(&pos.y)
&& (aabb.min.z..aabb.max.z).contains(&pos.z)
};
match &tree[prim] {
Primitive::Empty => false,
@ -36,15 +38,28 @@ impl Fill {
Primitive::Aabb(aabb) => aabb_contains(*aabb, pos),
Primitive::Pyramid { aabb, inset } => {
let inset = (*inset).max(aabb.size().reduce_min());
let inner = Aabr { min: aabb.min.xy() - 1 + inset, max: aabb.max.xy() - inset };
aabb_contains(*aabb, pos) && (inner.projected_point(pos.xy()) - pos.xy())
.map(|e| e.abs())
.reduce_max() as f32 / (inset as f32) < 1.0 - ((pos.z - aabb.min.z) as f32 + 0.5) / (aabb.max.z - aabb.min.z) as f32
let inner = Aabr {
min: aabb.min.xy() - 1 + inset,
max: aabb.max.xy() - inset,
};
aabb_contains(*aabb, pos)
&& (inner.projected_point(pos.xy()) - pos.xy())
.map(|e| e.abs())
.reduce_max() as f32
/ (inset as f32)
< 1.0
- ((pos.z - aabb.min.z) as f32 + 0.5) / (aabb.max.z - aabb.min.z) as f32
},
Primitive::And(a, b) => self.contains_at(tree, *a, pos) && self.contains_at(tree, *b, pos),
Primitive::Or(a, b) => self.contains_at(tree, *a, pos) || self.contains_at(tree, *b, pos),
Primitive::Xor(a, b) => self.contains_at(tree, *a, pos) ^ self.contains_at(tree, *b, pos),
Primitive::And(a, b) => {
self.contains_at(tree, *a, pos) && self.contains_at(tree, *b, pos)
},
Primitive::Or(a, b) => {
self.contains_at(tree, *a, pos) || self.contains_at(tree, *b, pos)
},
Primitive::Xor(a, b) => {
self.contains_at(tree, *a, pos) ^ self.contains_at(tree, *b, pos)
},
}
}
@ -65,14 +80,22 @@ impl Fill {
Primitive::Empty => return None,
Primitive::Aabb(aabb) => *aabb,
Primitive::Pyramid { aabb, .. } => *aabb,
Primitive::And(a, b) => or_zip_with(self.get_bounds_inner(tree, *a), self.get_bounds_inner(tree, *b), |a, b| a.intersection(b))?,
Primitive::Or(a, b) | Primitive::Xor(a, b) =>
or_zip_with(self.get_bounds_inner(tree, *a), self.get_bounds_inner(tree, *b), |a, b| a.union(b))?,
Primitive::And(a, b) => or_zip_with(
self.get_bounds_inner(tree, *a),
self.get_bounds_inner(tree, *b),
|a, b| a.intersection(b),
)?,
Primitive::Or(a, b) | Primitive::Xor(a, b) => or_zip_with(
self.get_bounds_inner(tree, *a),
self.get_bounds_inner(tree, *b),
|a, b| a.union(b),
)?,
})
}
pub fn get_bounds(&self, tree: &Store<Primitive>) -> Aabb<i32> {
self.get_bounds_inner(tree, self.prim).unwrap_or_else(|| Aabb::new_empty(Vec3::zero()))
self.get_bounds_inner(tree, self.prim)
.unwrap_or_else(|| Aabb::new_empty(Vec3::zero()))
}
}
@ -82,7 +105,8 @@ pub trait Structure {
site: &Site,
prim: F,
fill: G,
) {}
) {
}
// Generate a primitive tree and fills for this structure
fn render_collect(&self, site: &Site) -> (Store<Primitive>, Vec<Fill>) {

View File

@ -3,38 +3,37 @@ mod plot;
mod tile;
use self::{
gen::{Fill, Primitive, Structure},
plot::{Plot, PlotKind},
tile::{TileGrid, Tile, TileKind, HazardKind, TILE_SIZE},
gen::{Primitive, Fill, Structure},
tile::{HazardKind, Tile, TileGrid, TileKind, TILE_SIZE},
};
use crate::{
site::SpawnRules,
util::{Grid, attempt, DHashSet, CARDINALS, SQUARE_4, SQUARE_9, LOCALITY},
Canvas,
Land,
util::{attempt, DHashSet, Grid, CARDINALS, LOCALITY, SQUARE_4, SQUARE_9},
Canvas, Land,
};
use common::{
terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
vol::RectVolSize,
store::{Id, Store},
astar::Astar,
lottery::Lottery,
spiral::Spiral2d,
store::{Id, Store},
terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
vol::RectVolSize,
};
use hashbrown::hash_map::DefaultHashBuilder;
use rand::prelude::*;
use rand_chacha::ChaChaRng;
use vek::*;
use std::ops::Range;
use vek::*;
/// 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]>())
}
/// 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 {
@ -47,15 +46,23 @@ pub struct Site {
impl Site {
pub fn radius(&self) -> f32 {
((self.tiles.bounds.min.map(|e| e.abs()).reduce_max()
.max(self.tiles.bounds.max.map(|e| e.abs()).reduce_max()) + 1) * tile::TILE_SIZE as i32) as f32
((self
.tiles
.bounds
.min
.map(|e| e.abs())
.reduce_max()
.max(self.tiles.bounds.max.map(|e| e.abs()).reduce_max())
+ 1)
* tile::TILE_SIZE as i32) as f32
}
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
SpawnRules {
trees: SQUARE_9
.iter()
.all(|&rpos| self.wpos_tile(wpos + rpos * tile::TILE_SIZE as i32).is_empty()),
trees: SQUARE_9.iter().all(|&rpos| {
self.wpos_tile(wpos + rpos * tile::TILE_SIZE as i32)
.is_empty()
}),
..SpawnRules::default()
}
}
@ -82,7 +89,14 @@ impl Site {
}
}
pub fn create_road(&mut self, land: &Land, rng: &mut impl Rng, a: Vec2<i32>, b: Vec2<i32>, w: u16) -> 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>| {
@ -95,17 +109,22 @@ impl Site {
}
(tile.distance_squared(b) as f32).sqrt()
};
let path = Astar::new(MAX_ITERS, a, &heuristic, DefaultHashBuilder::default()).poll(
MAX_ITERS,
&heuristic,
|tile| { let tile = *tile; CARDINALS.iter().map(move |dir| tile + *dir) },
|a, b| {
let alt_a = land.get_alt_approx(self.tile_center_wpos(*a));
let alt_b = land.get_alt_approx(self.tile_center_wpos(*b));
(alt_a - alt_b).abs() / TILE_SIZE as f32
},
|tile| *tile == b,
).into_path()?;
let path = Astar::new(MAX_ITERS, a, &heuristic, DefaultHashBuilder::default())
.poll(
MAX_ITERS,
&heuristic,
|tile| {
let tile = *tile;
CARDINALS.iter().map(move |dir| tile + *dir)
},
|a, b| {
let alt_a = land.get_alt_approx(self.tile_center_wpos(*a));
let alt_b = land.get_alt_approx(self.tile_center_wpos(*b));
(alt_a - alt_b).abs() / TILE_SIZE as f32
},
|tile| *tile == b,
)
.into_path()?;
let plot = self.create_plot(Plot {
kind: PlotKind::Road(path.clone()),
@ -134,24 +153,41 @@ impl Site {
Some(plot)
}
pub fn find_aabr(&mut self, search_pos: Vec2<i32>, area_range: Range<u32>, min_dims: Extent2<u32>) -> Option<(Aabr<i32>, Vec2<i32>)> {
self.tiles.find_near(
search_pos,
|center, _| self.tiles.grow_aabr(center, area_range.clone(), min_dims)
pub fn find_aabr(
&mut self,
search_pos: Vec2<i32>,
area_range: Range<u32>,
min_dims: Extent2<u32>,
) -> Option<(Aabr<i32>, Vec2<i32>)> {
self.tiles.find_near(search_pos, |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)).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())
}),
)
(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())
})
})
}
pub fn find_roadside_aabr(&mut self, rng: &mut impl Rng, area_range: Range<u32>, min_dims: Extent2<u32>) -> Option<(Aabr<i32>, Vec2<i32>)> {
let dir = Vec2::<f32>::zero().map(|_| rng.gen_range(-1.0..1.0)).normalized();
pub fn find_roadside_aabr(
&mut self,
rng: &mut impl Rng,
area_range: Range<u32>,
min_dims: Extent2<u32>,
) -> Option<(Aabr<i32>, Vec2<i32>)> {
let dir = Vec2::<f32>::zero()
.map(|_| rng.gen_range(-1.0..1.0))
.normalized();
let search_pos = if rng.gen() {
self.plot(*self.plazas.choose(rng)?).root_tile + (dir * 4.0).map(|e: f32| e.round() as i32)
self.plot(*self.plazas.choose(rng)?).root_tile
+ (dir * 4.0).map(|e: f32| e.round() as i32)
} else {
if let PlotKind::Road(path) = &self.plot(*self.roads.choose(rng)?).kind {
*path.nodes().choose(rng)? + (dir * 1.0).map(|e: f32| e.round() as i32)
@ -167,17 +203,27 @@ impl Site {
let pos = attempt(32, || {
self.plazas
.choose(rng)
.map(|&p| self.plot(p).root_tile + (Vec2::new(rng.gen_range(-1.0..1.0), rng.gen_range(-1.0..1.0)).normalized() * 24.0).map(|e| e as i32))
.map(|&p| {
self.plot(p).root_tile
+ (Vec2::new(rng.gen_range(-1.0..1.0), rng.gen_range(-1.0..1.0))
.normalized()
* 24.0)
.map(|e| e as i32)
})
.filter(|tile| !self.tiles.get(*tile).is_obstacle())
.filter(|&tile| self
.plazas
.iter()
.all(|&p| self.plot(p).root_tile.distance_squared(tile) > 20i32.pow(2))
&& rng.gen_range(0..48) > tile.map(|e| e.abs()).reduce_max())
.filter(|&tile| {
self.plazas
.iter()
.all(|&p| self.plot(p).root_tile.distance_squared(tile) > 20i32.pow(2))
&& rng.gen_range(0..48) > tile.map(|e| e.abs()).reduce_max()
})
})
.unwrap_or_else(Vec2::zero);
.unwrap_or_else(Vec2::zero);
let aabr = Aabr { min: pos + Vec2::broadcast(-3), max: pos + Vec2::broadcast(4) };
let aabr = Aabr {
min: pos + Vec2::broadcast(-3),
max: pos + Vec2::broadcast(4),
};
let plaza = self.create_plot(Plot {
kind: PlotKind::Plaza,
root_tile: pos,
@ -193,7 +239,8 @@ impl Site {
let mut already_pathed = vec![plaza];
// One major, one minor road
for width in (1..=2).rev() {
if let Some(&p) = self.plazas
if let Some(&p) = self
.plazas
.iter()
.filter(|p| !already_pathed.contains(p))
.min_by_key(|&&p| self.plot(p).root_tile.distance_squared(pos))
@ -217,7 +264,9 @@ impl Site {
if let Some(kind) = wpos_is_hazard(land, self.tile_wpos(tile)) {
for &rpos in &SQUARE_4 {
// `get_mut` doesn't increase generation bounds
self.tiles.get_mut(tile - rpos - 1).map(|tile| tile.kind = TileKind::Hazard(kind));
self.tiles
.get_mut(tile - rpos - 1)
.map(|tile| tile.kind = TileKind::Hazard(kind));
}
}
});
@ -235,12 +284,7 @@ impl Site {
site.make_plaza(land, &mut rng);
let build_chance = Lottery::from(vec![
(64.0, 1),
(5.0, 2),
(20.0, 3),
(0.75, 4),
]);
let build_chance = Lottery::from(vec![(64.0, 1), (5.0, 2), (20.0, 3), (0.75, 4)]);
let mut castles = 0;
@ -249,9 +293,21 @@ impl Site {
// House
1 => {
let size = (2.0 + rng.gen::<f32>().powf(8.0) * 3.0).round() as u32;
if let Some((aabr, door_tile)) = attempt(32, || site.find_roadside_aabr(&mut 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, &mut reseed(&mut 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(),
@ -267,7 +323,9 @@ impl Site {
},
// Guard tower
2 => {
if let Some((aabr, _)) = attempt(10, || site.find_roadside_aabr(&mut 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(),
@ -285,28 +343,26 @@ impl Site {
3 => {
attempt(10, || {
let search_pos = attempt(16, || {
let tile = (Vec2::new(
rng.gen_range(-1.0..1.0),
rng.gen_range(-1.0..1.0),
).normalized() * rng.gen_range(16.0..48.0)).map(|e| e as i32);
let tile =
(Vec2::new(rng.gen_range(-1.0..1.0), rng.gen_range(-1.0..1.0))
.normalized()
* rng.gen_range(16.0..48.0))
.map(|e| e as i32);
if site
.plazas
.iter()
.all(|&p| site.plot(p).root_tile.distance_squared(tile) > 20i32.pow(2))
&& rng.gen_range(0..48) > tile.map(|e| e.abs()).reduce_max()
if site.plazas.iter().all(|&p| {
site.plot(p).root_tile.distance_squared(tile) > 20i32.pow(2)
}) && rng.gen_range(0..48) > tile.map(|e| e.abs()).reduce_max()
{
Some(tile)
} else {
None
}
})
.unwrap_or_else(Vec2::zero);
.unwrap_or_else(Vec2::zero);
site.tiles.find_near(
search_pos,
|center, _| site.tiles.grow_organic(&mut rng, center, 12..64).ok()
)
site.tiles.find_near(search_pos, |center, _| {
site.tiles.grow_organic(&mut rng, center, 12..64).ok()
})
})
.map(|(tiles, _)| {
for tile in tiles {
@ -319,7 +375,9 @@ impl Site {
},
// Castle
4 if castles < 1 => {
if let Some((aabr, _)) = attempt(10, || site.find_roadside_aabr(&mut rng, 16 * 16..18 * 18, Extent2::new(16, 16))) {
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(),
@ -337,22 +395,38 @@ impl Site {
kind: TileKind::Castle,
plot: Some(plot),
};
site.tiles.set(Vec2::new(aabr.min.x, aabr.min.y), tower.clone());
site.tiles.set(Vec2::new(aabr.max.x - 1, aabr.min.y), tower.clone());
site.tiles.set(Vec2::new(aabr.min.x, aabr.max.y - 1), tower.clone());
site.tiles.set(Vec2::new(aabr.max.x - 1, aabr.max.y - 1), tower.clone());
site.tiles
.set(Vec2::new(aabr.min.x, aabr.min.y), tower.clone());
site.tiles
.set(Vec2::new(aabr.max.x - 1, aabr.min.y), tower.clone());
site.tiles
.set(Vec2::new(aabr.min.x, aabr.max.y - 1), tower.clone());
site.tiles
.set(Vec2::new(aabr.max.x - 1, aabr.max.y - 1), tower.clone());
// Courtyard
site.blit_aabr(Aabr { min: aabr.min + 1, max: aabr.max - 1 } , Tile {
kind: TileKind::Road { a: 0, b: 0, w: 0 },
plot: Some(plot),
});
site.blit_aabr(
Aabr {
min: aabr.min + 1,
max: aabr.max - 1,
},
Tile {
kind: TileKind::Road { a: 0, b: 0, w: 0 },
plot: Some(plot),
},
);
// Keep
site.blit_aabr(Aabr { min: aabr.center() - 3, max: aabr.center() + 3 }, Tile {
kind: TileKind::Castle,
plot: Some(plot),
});
site.blit_aabr(
Aabr {
min: aabr.center() - 3,
max: aabr.center() + 3,
},
Tile {
kind: TileKind::Castle,
plot: Some(plot),
},
);
castles += 1;
}
@ -384,7 +458,12 @@ impl Site {
let tile = self.tiles.get(tpos);
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();
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(_) => {},
@ -418,15 +497,16 @@ impl Site {
// // .filter_map(|line| Some(line?.projected_point(wpos2df)))
// // .min_by_key(|p| p.distance_squared(wpos2df) as i32);
// // let is_near_road = nearest_road.map_or(false, |r| r.distance_squared(wpos2df) < 3.0f32.powi(2));
// // 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 {
// // 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))
@ -562,7 +642,9 @@ impl Site {
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
max: self
.wpos_tile_pos(canvas.wpos() + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + 2)
+ 3, // Round up, uninclusive, border
};
// Don't double-generate the same plot per chunk!
@ -621,10 +703,9 @@ fn wpos_is_hazard(land: &Land, wpos: Vec2<i32>) -> Option<HazardKind> {
}
}
pub fn aabr_tiles(aabr: Aabr<i32>) -> impl Iterator<Item=Vec2<i32>> {
pub fn aabr_tiles(aabr: Aabr<i32>) -> impl Iterator<Item = Vec2<i32>> {
(0..aabr.size().h)
.map(move |y| (0..aabr.size().w)
.map(move |x| aabr.min + Vec2::new(x, y)))
.map(move |y| (0..aabr.size().w).map(move |x| aabr.min + Vec2::new(x, y)))
.flatten()
}

View File

@ -1,8 +1,6 @@
mod house;
pub use self::{
house::House,
};
pub use self::house::House;
use super::*;
use crate::util::DHashSet;

View File

@ -80,16 +80,22 @@ impl Structure for House {
let mut pillars_y = prim(Primitive::Empty);
for x in self.tile_aabr.min.x..self.tile_aabr.max.x + 2 {
let pillar = prim(Primitive::Aabb(Aabb {
min: site.tile_wpos(Vec2::new(x, self.tile_aabr.min.y)).with_z(self.alt),
max: (site.tile_wpos(Vec2::new(x, self.tile_aabr.max.y + 1)) + Vec2::unit_x()).with_z(self.alt + roof),
min: site
.tile_wpos(Vec2::new(x, self.tile_aabr.min.y))
.with_z(self.alt),
max: (site.tile_wpos(Vec2::new(x, self.tile_aabr.max.y + 1)) + Vec2::unit_x())
.with_z(self.alt + roof),
}));
pillars_y = prim(Primitive::Or(pillars_y, pillar));
}
let mut pillars_x = prim(Primitive::Empty);
for y in self.tile_aabr.min.y..self.tile_aabr.max.y + 2 {
let pillar = prim(Primitive::Aabb(Aabb {
min: site.tile_wpos(Vec2::new(self.tile_aabr.min.x, y)).with_z(self.alt),
max: (site.tile_wpos(Vec2::new(self.tile_aabr.max.x + 1, y)) + Vec2::unit_y()).with_z(self.alt + roof),
min: site
.tile_wpos(Vec2::new(self.tile_aabr.min.x, y))
.with_z(self.alt),
max: (site.tile_wpos(Vec2::new(self.tile_aabr.max.x + 1, y)) + Vec2::unit_y())
.with_z(self.alt + roof),
}));
pillars_x = prim(Primitive::Or(pillars_x, pillar));
}
@ -109,8 +115,12 @@ impl Structure for House {
let mut windows = prim(Primitive::Empty);
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 + 2 + window_height),
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 + 2 + window_height),
}));
windows = prim(Primitive::Or(windows, window));
}
@ -124,8 +134,12 @@ impl Structure for House {
let mut windows = prim(Primitive::Empty);
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 + 2 + window_height),
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 + 2 + window_height),
}));
windows = prim(Primitive::Or(windows, window));
}

View File

@ -41,13 +41,15 @@ impl TileGrid {
// WILL NOT EXPAND BOUNDS!
pub fn get_mut(&mut self, tpos: Vec2<i32>) -> Option<&mut Tile> {
let tpos = tpos + TILE_RADIUS as i32;
self.zones.get_mut(tpos.map(|e| e.div_euclid(ZONE_SIZE as i32))).and_then(|zone| {
zone.get_or_insert_with(|| {
Grid::populate_from(Vec2::broadcast(ZONE_SIZE as i32), |_| None)
self.zones
.get_mut(tpos.map(|e| e.div_euclid(ZONE_SIZE as i32)))
.and_then(|zone| {
zone.get_or_insert_with(|| {
Grid::populate_from(Vec2::broadcast(ZONE_SIZE as i32), |_| None)
})
.get_mut(tpos.map(|e| e.rem_euclid(ZONE_SIZE as i32)))
.map(|tile| tile.get_or_insert_with(|| Tile::empty()))
})
.get_mut(tpos.map(|e| e.rem_euclid(ZONE_SIZE as i32)))
.map(|tile| tile.get_or_insert_with(|| Tile::empty()))
})
}
pub fn set(&mut self, tpos: Vec2<i32>, tile: Tile) -> Option<Tile> {
@ -55,7 +57,11 @@ impl TileGrid {
self.get_mut(tpos).map(|t| std::mem::replace(t, tile))
}
pub fn find_near<R>(&self, tpos: Vec2<i32>, mut f: impl FnMut(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()
@ -64,8 +70,16 @@ impl TileGrid {
.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>> {
let mut aabr = Aabr { min: center, max: center + 1 };
pub fn grow_aabr(
&self,
center: Vec2<i32>,
area_range: Range<u32>,
min_dims: Extent2<u32>,
) -> Result<Aabr<i32>, Aabr<i32>> {
let mut aabr = Aabr {
min: center,
max: center + 1,
};
if !self.get(center).is_empty() {
return Err(aabr);
@ -75,27 +89,42 @@ impl TileGrid {
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 {
} else if aabr.size().product()
+ if i % 2 == 0 {
aabr.size().h
} else {
aabr.size().w
}
> area_range.end as i32
{
break;
} else {
// `center.sum()` to avoid biasing certain directions
match (i + center.sum().abs()) % 4 {
0 if (aabr.min.y..aabr.max.y + 1).all(|y| self.get(Vec2::new(aabr.max.x, y)).is_empty()) => {
0 if (aabr.min.y..aabr.max.y + 1)
.all(|y| self.get(Vec2::new(aabr.max.x, y)).is_empty()) =>
{
aabr.max.x += 1;
last_growth = i;
},
1 if (aabr.min.x..aabr.max.x + 1).all(|x| self.get(Vec2::new(x, aabr.max.y)).is_empty()) => {
}
1 if (aabr.min.x..aabr.max.x + 1)
.all(|x| self.get(Vec2::new(x, aabr.max.y)).is_empty()) =>
{
aabr.max.y += 1;
last_growth = i;
},
2 if (aabr.min.y..aabr.max.y + 1).all(|y| self.get(Vec2::new(aabr.min.x - 1, y)).is_empty()) => {
}
2 if (aabr.min.y..aabr.max.y + 1)
.all(|y| self.get(Vec2::new(aabr.min.x - 1, y)).is_empty()) =>
{
aabr.min.x -= 1;
last_growth = i;
},
3 if (aabr.min.x..aabr.max.x + 1).all(|x| self.get(Vec2::new(x, aabr.min.y - 1)).is_empty()) => {
}
3 if (aabr.min.x..aabr.max.x + 1)
.all(|x| self.get(Vec2::new(x, aabr.min.y - 1)).is_empty()) =>
{
aabr.min.y -= 1;
last_growth = i;
},
}
_ => {},
}
}
@ -111,7 +140,12 @@ impl TileGrid {
}
}
pub fn grow_organic(&self, rng: &mut impl Rng, center: Vec2<i32>, area_range: Range<u32>) -> Result<DHashSet<Vec2<i32>>, DHashSet<Vec2<i32>>> {
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();
@ -165,26 +199,16 @@ impl Tile {
}
/// Create a tile that is not associated with any plot.
pub const fn free(kind: TileKind) -> Self {
Self {
kind,
plot: None,
}
}
pub const fn free(kind: TileKind) -> Self { Self { kind, plot: None } }
pub fn is_empty(&self) -> bool { self.kind == TileKind::Empty }
pub fn is_road(&self) -> bool {
matches!(self.kind, TileKind::Road { .. })
}
pub fn is_road(&self) -> bool { matches!(self.kind, TileKind::Road { .. }) }
pub fn is_obstacle(&self) -> bool {
matches!(
self.kind,
TileKind::Hazard(_)
| TileKind::Building
| TileKind::Castle
| TileKind::Wall
TileKind::Hazard(_) | TileKind::Building | TileKind::Castle | TileKind::Wall
)
}
}

View File

@ -1,9 +1,10 @@
use std::ops::Range;
use vek::*;
/// Return a value between 0 and 1 corresponding to how close to the centre of `range` `x` is.
/// The exact function used is left unspecified, but it shall have the shape of a bell-like curve.
/// This function is required to return `0` (or a value extremely close to `0`) when `x` is outside of `range`.
/// Return a value between 0 and 1 corresponding to how close to the centre of
/// `range` `x` is. The exact function used is left unspecified, but it shall
/// have the shape of a bell-like curve. This function is required to return `0`
/// (or a value extremely close to `0`) when `x` is outside of `range`.
pub fn close(x: f32, range: Range<f32>) -> f32 {
let mean = (range.start + range.end) / 2.0;
let width = (range.end - range.start) / 2.0;

View File

@ -1,6 +1,6 @@
pub mod fast_noise;
pub mod math;
pub mod map_vec;
pub mod math;
pub mod random;
pub mod sampler;
pub mod seed_expan;