mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Better rocks
This commit is contained in:
parent
40d5920ca2
commit
bd8a7a0506
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Waypoints saved between sessions and shared with group members.
|
- Waypoints saved between sessions and shared with group members.
|
||||||
|
- New rocks
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
(
|
(
|
||||||
caverns: false, // TODO: Disabled by default until cave overhaul
|
caverns: false, // TODO: Disabled by default until cave overhaul
|
||||||
caves: true,
|
caves: true,
|
||||||
|
rocks: true,
|
||||||
shrubs: true,
|
shrubs: true,
|
||||||
trees: true,
|
trees: true,
|
||||||
scatter: true,
|
scatter: true,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::util::math::close;
|
use crate::util::math::close;
|
||||||
use enum_iterator::IntoEnumIterator;
|
use enum_iterator::IntoEnumIterator;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use vek::*;
|
use vek::Vec2;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, IntoEnumIterator)]
|
#[derive(Copy, Clone, Debug, IntoEnumIterator)]
|
||||||
pub enum ForestKind {
|
pub enum ForestKind {
|
||||||
@ -113,6 +113,8 @@ impl ForestKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Not currently used with trees generated by the tree layer, needs to be
|
||||||
|
/// reworked
|
||||||
pub struct TreeAttr {
|
pub struct TreeAttr {
|
||||||
pub pos: Vec2<i32>,
|
pub pos: Vec2<i32>,
|
||||||
pub seed: u32,
|
pub seed: u32,
|
||||||
|
@ -77,7 +77,7 @@ impl<'a> BlockGen<'a> {
|
|||||||
marble: _,
|
marble: _,
|
||||||
marble_mid: _,
|
marble_mid: _,
|
||||||
marble_small: _,
|
marble_small: _,
|
||||||
rock,
|
rock_density: _,
|
||||||
// temp,
|
// temp,
|
||||||
// humidity,
|
// humidity,
|
||||||
stone_col,
|
stone_col,
|
||||||
@ -183,28 +183,6 @@ impl<'a> BlockGen<'a> {
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
.or_else(|| {
|
|
||||||
// Rocks
|
|
||||||
if (height + 2.5 - wposf.z as f32).div(7.5).abs().powi(2) < rock {
|
|
||||||
let field0 = RandomField::new(world.seed + 0);
|
|
||||||
let field1 = RandomField::new(world.seed + 1);
|
|
||||||
let field2 = RandomField::new(world.seed + 2);
|
|
||||||
|
|
||||||
Some(Block::new(
|
|
||||||
BlockKind::WeakRock,
|
|
||||||
stone_col.map2(
|
|
||||||
Rgb::new(
|
|
||||||
field0.get(wpos) as u8 % 16,
|
|
||||||
field1.get(wpos) as u8 % 16,
|
|
||||||
field2.get(wpos) as u8 % 16,
|
|
||||||
),
|
|
||||||
|stone, x| stone.saturating_sub(x),
|
|
||||||
),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
let over_water = height < water_height;
|
let over_water = height < water_height;
|
||||||
// Water
|
// Water
|
||||||
@ -232,11 +210,9 @@ impl<'a> ZCache<'a> {
|
|||||||
- self.sample.cliff_offset.max(0.0);
|
- self.sample.cliff_offset.max(0.0);
|
||||||
let min = min - 4.0;
|
let min = min - 4.0;
|
||||||
|
|
||||||
let rocks = if self.sample.rock > 0.0 { 12.0 } else { 0.0 };
|
|
||||||
|
|
||||||
let warp = self.sample.chaos * 32.0;
|
let warp = self.sample.chaos * 32.0;
|
||||||
|
|
||||||
let ground_max = self.sample.alt + warp + rocks + 2.0;
|
let ground_max = self.sample.alt + warp + 2.0;
|
||||||
|
|
||||||
let max = ground_max.max(self.sample.water_level + 2.0 + self.sample.ice_depth);
|
let max = ground_max.max(self.sample.water_level + 2.0 + self.sample.ice_depth);
|
||||||
|
|
||||||
|
@ -841,17 +841,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
|||||||
let alt = alt + riverless_alt_delta;
|
let alt = alt + riverless_alt_delta;
|
||||||
let basement =
|
let basement =
|
||||||
alt + sim.get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?;
|
alt + sim.get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?;
|
||||||
|
// Adjust this to make rock placement better
|
||||||
let rock = (sim.gen_ctx.small_nz.get(
|
let rock_density = rockiness;
|
||||||
Vec3::new(wposf.x, wposf.y, alt as f64)
|
|
||||||
.div(100.0)
|
|
||||||
.into_array(),
|
|
||||||
) as f32)
|
|
||||||
//.mul(water_dist.map(|wd| (wd / 2.0).clamped(0.0, 1.0).sqrt()).unwrap_or(1.0))
|
|
||||||
.mul(rockiness)
|
|
||||||
.sub(0.4)
|
|
||||||
.max(0.0)
|
|
||||||
.mul(8.0);
|
|
||||||
|
|
||||||
// Columns near water have a more stable temperature and so get pushed towards
|
// Columns near water have a more stable temperature and so get pushed towards
|
||||||
// the average (0)
|
// the average (0)
|
||||||
@ -1182,7 +1173,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
|||||||
marble,
|
marble,
|
||||||
marble_mid,
|
marble_mid,
|
||||||
marble_small,
|
marble_small,
|
||||||
rock,
|
rock_density,
|
||||||
temp,
|
temp,
|
||||||
humidity,
|
humidity,
|
||||||
spawn_rate,
|
spawn_rate,
|
||||||
@ -1217,7 +1208,7 @@ pub struct ColumnSample<'a> {
|
|||||||
pub marble: f32,
|
pub marble: f32,
|
||||||
pub marble_mid: f32,
|
pub marble_mid: f32,
|
||||||
pub marble_small: f32,
|
pub marble_small: f32,
|
||||||
pub rock: f32,
|
pub rock_density: f32,
|
||||||
pub temp: f32,
|
pub temp: f32,
|
||||||
pub humidity: f32,
|
pub humidity: f32,
|
||||||
pub spawn_rate: f32,
|
pub spawn_rate: f32,
|
||||||
|
@ -81,6 +81,7 @@ pub const CONFIG: Config = Config {
|
|||||||
pub struct Features {
|
pub struct Features {
|
||||||
pub caverns: bool,
|
pub caverns: bool,
|
||||||
pub caves: bool,
|
pub caves: bool,
|
||||||
|
pub rocks: bool,
|
||||||
pub shrubs: bool,
|
pub shrubs: bool,
|
||||||
pub trees: bool,
|
pub trees: bool,
|
||||||
pub scatter: bool,
|
pub scatter: bool,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
pub mod rock;
|
||||||
pub mod scatter;
|
pub mod scatter;
|
||||||
pub mod shrub;
|
pub mod shrub;
|
||||||
pub mod spot;
|
pub mod spot;
|
||||||
@ -5,7 +6,8 @@ pub mod tree;
|
|||||||
pub mod wildlife;
|
pub mod wildlife;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
scatter::apply_scatter_to, shrub::apply_shrubs_to, spot::apply_spots_to, tree::apply_trees_to,
|
rock::apply_rocks_to, scatter::apply_scatter_to, shrub::apply_shrubs_to, spot::apply_spots_to,
|
||||||
|
tree::apply_trees_to,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
348
world/src/layer/rock.rs
Normal file
348
world/src/layer/rock.rs
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
use crate::{
|
||||||
|
util::{
|
||||||
|
gen_cache::StructureGenCache, seed_expan, RandomField, Sampler, StructureGen2d,
|
||||||
|
UnitChooser, NEIGHBORS, NEIGHBORS3,
|
||||||
|
},
|
||||||
|
Canvas, ColumnSample, CONFIG,
|
||||||
|
};
|
||||||
|
use common::terrain::{Block, BlockKind};
|
||||||
|
use ordered_float::NotNan;
|
||||||
|
use rand::prelude::*;
|
||||||
|
use rand_chacha::ChaChaRng;
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
|
struct Rock {
|
||||||
|
wpos: Vec3<i32>,
|
||||||
|
seed: u32,
|
||||||
|
units: Vec2<Vec2<i32>>,
|
||||||
|
kind: RockKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_rocks_to(canvas: &mut Canvas, _dynamic_rng: &mut impl Rng) {
|
||||||
|
let mut rock_gen = StructureGenCache::new(StructureGen2d::new(canvas.index().seed, 24, 10));
|
||||||
|
|
||||||
|
let info = canvas.info();
|
||||||
|
canvas.foreach_col(|canvas, wpos2d, col| {
|
||||||
|
let rocks = rock_gen.get(wpos2d, |wpos, seed| {
|
||||||
|
let col = info.col_or_gen(wpos)?;
|
||||||
|
|
||||||
|
let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
|
||||||
|
|
||||||
|
const BASE_ROCK_DENSITY: f64 = 0.15;
|
||||||
|
if rng.gen_bool((BASE_ROCK_DENSITY * col.rock_density as f64).clamped(0.0, 1.0))
|
||||||
|
&& col.path.map_or(true, |(d, _, _, _)| d > 6.0)
|
||||||
|
{
|
||||||
|
match (
|
||||||
|
(col.alt - CONFIG.sea_level) as i32,
|
||||||
|
(col.alt - col.water_level) as i32,
|
||||||
|
col.water_dist.map_or(i32::MAX, |d| d as i32),
|
||||||
|
) {
|
||||||
|
(-3..=2, _, _) => {
|
||||||
|
if rng.gen_bool(0.3) {
|
||||||
|
Some(RockKind::Rauk(Pillar::generate(&mut rng)))
|
||||||
|
} else {
|
||||||
|
Some(RockKind::Rock(VoronoiCell::generate(
|
||||||
|
rng.gen_range(1.0..3.0),
|
||||||
|
&mut rng,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(_, -15..=3, _) => Some(RockKind::Rock(VoronoiCell::generate(
|
||||||
|
rng.gen_range(1.0..4.0),
|
||||||
|
&mut rng,
|
||||||
|
))),
|
||||||
|
(5..=i32::MAX, _, 10..=i32::MAX) => {
|
||||||
|
if col.temp > CONFIG.desert_temp - 0.1
|
||||||
|
&& col.humidity < CONFIG.desert_hum + 0.1
|
||||||
|
{
|
||||||
|
Some(RockKind::Sandstone(VoronoiCell::generate(
|
||||||
|
rng.gen_range(2.0..20.0 - 15.0 * col.tree_density),
|
||||||
|
&mut rng,
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Some(RockKind::Rock(VoronoiCell::generate(
|
||||||
|
rng.gen_range(2.0..20.0 - 15.0 * col.tree_density),
|
||||||
|
&mut rng,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
.map(|kind| Rock {
|
||||||
|
wpos: wpos.with_z(col.alt as i32),
|
||||||
|
seed,
|
||||||
|
units: UnitChooser::new(seed).get(seed).into(),
|
||||||
|
kind,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for rock in rocks {
|
||||||
|
let bounds = rock.kind.get_bounds();
|
||||||
|
|
||||||
|
let rpos2d = (wpos2d - rock.wpos.xy())
|
||||||
|
.map2(rock.units, |p, unit| unit * p)
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
if !Aabr::from(bounds).contains_point(rpos2d) {
|
||||||
|
// Skip this column
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut is_top = true;
|
||||||
|
let mut last_block = Block::empty();
|
||||||
|
for z in (bounds.min.z..bounds.max.z).rev() {
|
||||||
|
let wpos = Vec3::new(wpos2d.x, wpos2d.y, rock.wpos.z + z);
|
||||||
|
let model_pos = (wpos - rock.wpos)
|
||||||
|
.xy()
|
||||||
|
.map2(rock.units, |rpos, unit| unit * rpos)
|
||||||
|
.sum()
|
||||||
|
.with_z(wpos.z - rock.wpos.z);
|
||||||
|
|
||||||
|
rock.kind
|
||||||
|
.take_sample(model_pos, rock.seed, last_block, col)
|
||||||
|
.map(|block| {
|
||||||
|
if col.snow_cover && is_top && block.is_filled() {
|
||||||
|
canvas.set(
|
||||||
|
wpos + Vec3::unit_z(),
|
||||||
|
Block::new(BlockKind::Snow, Rgb::new(210, 210, 255)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
canvas.set(wpos, block);
|
||||||
|
is_top = false;
|
||||||
|
last_block = block;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VoronoiCell {
|
||||||
|
size: f32,
|
||||||
|
points: [Vec3<f32>; 26],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VoronoiCell {
|
||||||
|
fn generate(size: f32, rng: &mut impl Rng) -> Self {
|
||||||
|
let mut points = [Vec3::zero(); 26];
|
||||||
|
for (i, p) in NEIGHBORS3.iter().enumerate() {
|
||||||
|
points[i] = p.as_() * size
|
||||||
|
+ Vec3::new(
|
||||||
|
rng.gen_range(-0.5..=0.5) * size,
|
||||||
|
rng.gen_range(-0.5..=0.5) * size,
|
||||||
|
rng.gen_range(-0.5..=0.5) * size,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Self { size, points }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_at(&self, rpos: Vec3<i32>) -> bool {
|
||||||
|
let rposf = rpos.as_();
|
||||||
|
// Would theoretically only need to compare with 7 other points rather than 26,
|
||||||
|
// by checking all the points in the cells touching the closest corner of this
|
||||||
|
// point.
|
||||||
|
rposf.magnitude_squared()
|
||||||
|
<= *(0..26)
|
||||||
|
.map(|i| self.points[i].distance_squared(rposf))
|
||||||
|
.map(|d| NotNan::new(d).unwrap())
|
||||||
|
.min()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Pillar {
|
||||||
|
height: f32,
|
||||||
|
max_extent: Vec2<f32>,
|
||||||
|
extents: [Vec2<f32>; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pillar {
|
||||||
|
fn generate(rng: &mut impl Rng) -> Self {
|
||||||
|
let extents = [
|
||||||
|
Vec2::new(rng.gen_range(0.5..1.5), rng.gen_range(0.5..1.5)),
|
||||||
|
Vec2::new(rng.gen_range(0.8..2.8), rng.gen_range(0.8..2.8)),
|
||||||
|
Vec2::new(rng.gen_range(0.5..1.5), rng.gen_range(0.5..3.5)),
|
||||||
|
];
|
||||||
|
Self {
|
||||||
|
height: rng.gen_range(6.0..16.0),
|
||||||
|
extents,
|
||||||
|
max_extent: extents
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.reduce(|accum, item| accum.map2(item, |a, b| a.max(b)))
|
||||||
|
.unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_at(&self, rpos: Vec3<i32>) -> bool {
|
||||||
|
let h = rpos.z as f32 / self.height;
|
||||||
|
let extent = if h < 0.0 {
|
||||||
|
self.extents[0] * (-h).max(1.0)
|
||||||
|
} else if h < 0.5 {
|
||||||
|
self.extents[0].map2(self.extents[1], |l, m| f32::lerp(l, m, h * 2.0))
|
||||||
|
} else if h < 1.0 {
|
||||||
|
self.extents[1].map2(self.extents[2], |m, t| f32::lerp(m, t, (h - 0.5) * 2.0))
|
||||||
|
} else {
|
||||||
|
self.extents[2]
|
||||||
|
};
|
||||||
|
h < 1.0
|
||||||
|
&& extent
|
||||||
|
.map2(rpos.xy(), |e, p| p.abs() < e.ceil() as i32)
|
||||||
|
.reduce_and()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RockKind {
|
||||||
|
// A normal rock with a size
|
||||||
|
Rock(VoronoiCell),
|
||||||
|
Sandstone(VoronoiCell),
|
||||||
|
Rauk(Pillar),
|
||||||
|
// Arch,
|
||||||
|
// Hoodoos,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RockKind {
|
||||||
|
fn take_sample(
|
||||||
|
&self,
|
||||||
|
rpos: Vec3<i32>,
|
||||||
|
seed: u32,
|
||||||
|
last_block: Block,
|
||||||
|
col: &ColumnSample,
|
||||||
|
) -> Option<Block> {
|
||||||
|
// Used to debug get_bounds
|
||||||
|
/*
|
||||||
|
let bounds = self.get_bounds();
|
||||||
|
if rpos
|
||||||
|
.map3(
|
||||||
|
bounds.min,
|
||||||
|
bounds.max,
|
||||||
|
|e, a, b| if e == a || e == b { 1 } else { 0 },
|
||||||
|
)
|
||||||
|
.sum()
|
||||||
|
>= 2
|
||||||
|
{
|
||||||
|
return Some(Block::new(BlockKind::Rock, Rgb::red()));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
match self {
|
||||||
|
RockKind::Rock(cell) => {
|
||||||
|
if cell.sample_at(rpos) {
|
||||||
|
let mossiness = 0.1
|
||||||
|
+ RandomField::new(seed).get_f32(Vec3::zero()) * 0.3
|
||||||
|
+ col.humidity * 0.9;
|
||||||
|
Some(
|
||||||
|
if last_block.is_filled()
|
||||||
|
|| (rpos.z as f32 / cell.size
|
||||||
|
+ RandomField::new(seed).get_f32(rpos) * 0.3
|
||||||
|
> mossiness)
|
||||||
|
{
|
||||||
|
let mut i = 0;
|
||||||
|
Block::new(
|
||||||
|
BlockKind::WeakRock,
|
||||||
|
col.stone_col.map(|c| {
|
||||||
|
i += 1;
|
||||||
|
c + RandomField::new(seed).get(rpos) as u8 % 10
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Block::new(
|
||||||
|
BlockKind::Grass,
|
||||||
|
col.surface_color.map(|e| (e * 255.0) as u8),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RockKind::Sandstone(cell) => {
|
||||||
|
if cell.sample_at(rpos) {
|
||||||
|
let sandiness = 0.3 + RandomField::new(seed).get_f32(Vec3::zero()) * 0.4;
|
||||||
|
Some(
|
||||||
|
if last_block.is_filled()
|
||||||
|
|| (rpos.z as f32 / cell.size
|
||||||
|
+ RandomField::new(seed).get_f32(rpos) * 0.3
|
||||||
|
> sandiness)
|
||||||
|
{
|
||||||
|
let mut i = 0;
|
||||||
|
Block::new(
|
||||||
|
BlockKind::WeakRock,
|
||||||
|
Rgb::new(220, 160, 100).map(|c| {
|
||||||
|
i += 1;
|
||||||
|
c + RandomField::new(seed + i).get(Vec2::zero().with_z(rpos.z))
|
||||||
|
as u8
|
||||||
|
% 30
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Block::new(
|
||||||
|
BlockKind::Grass,
|
||||||
|
col.surface_color.map(|e| (e * 255.0) as u8),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RockKind::Rauk(pillar) => {
|
||||||
|
let max_extent = *pillar
|
||||||
|
.max_extent
|
||||||
|
.map(|e| NotNan::new(e).unwrap())
|
||||||
|
.reduce_max();
|
||||||
|
let is_filled = |rpos| {
|
||||||
|
pillar.sample_at(rpos)
|
||||||
|
&& RandomField::new(seed).chance(
|
||||||
|
rpos,
|
||||||
|
1.5 - rpos.z as f32 / pillar.height
|
||||||
|
- rpos.xy().as_::<f32>().magnitude() / max_extent,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if is_filled(rpos) ||
|
||||||
|
// Prevent floating blocks
|
||||||
|
(last_block.is_filled()
|
||||||
|
&& NEIGHBORS
|
||||||
|
.iter()
|
||||||
|
.map(|n| !is_filled(rpos + n.with_z(0)))
|
||||||
|
.all(|b| b))
|
||||||
|
{
|
||||||
|
Some(Block::new(
|
||||||
|
BlockKind::WeakRock,
|
||||||
|
Rgb::new(
|
||||||
|
190 + RandomField::new(seed + 1).get(rpos) as u8 % 10,
|
||||||
|
190 + RandomField::new(seed + 2).get(rpos) as u8 % 10,
|
||||||
|
190 + RandomField::new(seed + 3).get(rpos) as u8 % 10,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_bounds(&self) -> Aabb<i32> {
|
||||||
|
match self {
|
||||||
|
RockKind::Rock(VoronoiCell { size, .. })
|
||||||
|
| RockKind::Sandstone(VoronoiCell { size, .. }) => {
|
||||||
|
// Need to use full size because rock can bleed over into other cells
|
||||||
|
let extent = *size as i32;
|
||||||
|
Aabb {
|
||||||
|
min: Vec3::broadcast(-extent),
|
||||||
|
max: Vec3::broadcast(extent),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RockKind::Rauk(Pillar {
|
||||||
|
max_extent: extent,
|
||||||
|
height,
|
||||||
|
..
|
||||||
|
}) => Aabb {
|
||||||
|
min: (-extent.as_()).with_z(-2),
|
||||||
|
max: extent.as_().with_z(*height as i32),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,12 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
all::ForestKind,
|
all::ForestKind,
|
||||||
util::{seed_expan, Sampler, StructureGen2d, UnitChooser},
|
util::{gen_cache::StructureGenCache, seed_expan, Sampler, StructureGen2d, UnitChooser},
|
||||||
Canvas,
|
Canvas,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
assets::AssetHandle,
|
assets::AssetHandle,
|
||||||
terrain::structure::{Structure, StructuresGroup},
|
terrain::structure::{Structure, StructuresGroup},
|
||||||
};
|
};
|
||||||
use hashbrown::HashMap;
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use rand_chacha::ChaChaRng;
|
use rand_chacha::ChaChaRng;
|
||||||
@ -29,14 +28,12 @@ struct Shrub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_shrubs_to(canvas: &mut Canvas, _dynamic_rng: &mut impl Rng) {
|
pub fn apply_shrubs_to(canvas: &mut Canvas, _dynamic_rng: &mut impl Rng) {
|
||||||
let mut shrub_cache = HashMap::new();
|
let mut shrub_gen = StructureGenCache::new(StructureGen2d::new(canvas.index().seed, 8, 4));
|
||||||
|
|
||||||
let shrub_gen = StructureGen2d::new(canvas.index().seed, 8, 4);
|
|
||||||
|
|
||||||
let info = canvas.info();
|
let info = canvas.info();
|
||||||
|
|
||||||
canvas.foreach_col(|_, wpos2d, _| {
|
canvas.foreach_col(|_, wpos2d, _| {
|
||||||
for (wpos, seed) in shrub_gen.get(wpos2d) {
|
shrub_gen.get(wpos2d, |wpos, seed| {
|
||||||
shrub_cache.entry(wpos).or_insert_with(|| {
|
|
||||||
let col = info.col_or_gen(wpos)?;
|
let col = info.col_or_gen(wpos)?;
|
||||||
|
|
||||||
let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
|
let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
|
||||||
@ -66,10 +63,9 @@ pub fn apply_shrubs_to(canvas: &mut Canvas, _dynamic_rng: &mut impl Rng) {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
for shrub in shrub_cache.values().filter_map(|s| s.as_ref()) {
|
for shrub in shrub_gen.generated() {
|
||||||
let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(shrub.seed));
|
let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(shrub.seed));
|
||||||
|
|
||||||
let units = UnitChooser::new(shrub.seed).get(shrub.seed).into();
|
let units = UnitChooser::new(shrub.seed).get(shrub.seed).into();
|
||||||
|
@ -2,7 +2,7 @@ use crate::{
|
|||||||
all::*,
|
all::*,
|
||||||
block::block_from_structure,
|
block::block_from_structure,
|
||||||
column::ColumnGen,
|
column::ColumnGen,
|
||||||
util::{RandomPerm, Sampler, UnitChooser},
|
util::{gen_cache::StructureGenCache, RandomPerm, Sampler, UnitChooser},
|
||||||
Canvas,
|
Canvas,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
@ -14,7 +14,6 @@ use common::{
|
|||||||
},
|
},
|
||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
};
|
};
|
||||||
use hashbrown::HashMap;
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use std::{f32, ops::Range};
|
use std::{f32, ops::Range};
|
||||||
@ -51,24 +50,23 @@ pub fn apply_trees_to(
|
|||||||
model: TreeModel,
|
model: TreeModel,
|
||||||
seed: u32,
|
seed: u32,
|
||||||
units: (Vec2<i32>, Vec2<i32>),
|
units: (Vec2<i32>, Vec2<i32>),
|
||||||
|
lights: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut tree_cache = HashMap::new();
|
|
||||||
|
|
||||||
let info = canvas.info();
|
let info = canvas.info();
|
||||||
canvas.foreach_col(|canvas, wpos2d, col| {
|
let mut tree_cache = StructureGenCache::new(info.chunks().gen_ctx.structure_gen.clone());
|
||||||
let trees = info.chunks().get_near_trees(wpos2d);
|
|
||||||
|
|
||||||
for TreeAttr {
|
canvas.foreach_col(|canvas, wpos2d, col| {
|
||||||
pos,
|
let trees = tree_cache.get(wpos2d, |wpos, seed| {
|
||||||
seed,
|
let scale = 1.0;
|
||||||
scale,
|
let inhabited = false;
|
||||||
forest_kind,
|
let forest_kind = *info
|
||||||
inhabited,
|
.chunks()
|
||||||
} in trees
|
.make_forest_lottery(wpos)
|
||||||
{
|
.choose_seeded(seed)
|
||||||
let tree = if let Some(tree) = tree_cache.entry(pos).or_insert_with(|| {
|
.as_ref()?;
|
||||||
let col = ColumnGen::new(info.chunks()).get((pos, info.index(), calendar))?;
|
|
||||||
|
let col = ColumnGen::new(info.chunks()).get((wpos, info.index(), calendar))?;
|
||||||
|
|
||||||
// Ensure that it's valid to place a *thing* here
|
// Ensure that it's valid to place a *thing* here
|
||||||
if col.alt < col.water_level
|
if col.alt < col.water_level
|
||||||
@ -85,12 +83,10 @@ pub fn apply_trees_to(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Some(Tree {
|
Some(Tree {
|
||||||
pos: Vec3::new(pos.x, pos.y, col.alt as i32),
|
pos: Vec3::new(wpos.x, wpos.y, col.alt as i32),
|
||||||
model: 'model: {
|
model: 'model: {
|
||||||
let models: AssetHandle<_> = match forest_kind {
|
let models: AssetHandle<_> = match forest_kind {
|
||||||
ForestKind::Oak if QUIRKY_RAND.chance(seed + 1, 1.0 / 16.0) => {
|
ForestKind::Oak if QUIRKY_RAND.chance(seed + 1, 1.0 / 16.0) => *OAK_STUMPS,
|
||||||
*OAK_STUMPS
|
|
||||||
},
|
|
||||||
ForestKind::Oak if QUIRKY_RAND.chance(seed + 2, 1.0 / 20.0) => {
|
ForestKind::Oak if QUIRKY_RAND.chance(seed + 2, 1.0 / 20.0) => {
|
||||||
break 'model TreeModel::Procedural(
|
break 'model TreeModel::Procedural(
|
||||||
ProceduralTree::generate(
|
ProceduralTree::generate(
|
||||||
@ -140,11 +136,7 @@ pub fn apply_trees_to(
|
|||||||
ForestKind::Pine => {
|
ForestKind::Pine => {
|
||||||
break 'model TreeModel::Procedural(
|
break 'model TreeModel::Procedural(
|
||||||
ProceduralTree::generate(
|
ProceduralTree::generate(
|
||||||
TreeConfig::pine(
|
TreeConfig::pine(&mut RandomPerm::new(seed), scale, calendar),
|
||||||
&mut RandomPerm::new(seed),
|
|
||||||
scale,
|
|
||||||
calendar,
|
|
||||||
),
|
|
||||||
&mut RandomPerm::new(seed),
|
&mut RandomPerm::new(seed),
|
||||||
),
|
),
|
||||||
StructureBlock::PineLeaves,
|
StructureBlock::PineLeaves,
|
||||||
@ -191,11 +183,7 @@ pub fn apply_trees_to(
|
|||||||
ForestKind::Giant => {
|
ForestKind::Giant => {
|
||||||
break 'model TreeModel::Procedural(
|
break 'model TreeModel::Procedural(
|
||||||
ProceduralTree::generate(
|
ProceduralTree::generate(
|
||||||
TreeConfig::giant(
|
TreeConfig::giant(&mut RandomPerm::new(seed), scale, inhabited),
|
||||||
&mut RandomPerm::new(seed),
|
|
||||||
scale,
|
|
||||||
inhabited,
|
|
||||||
),
|
|
||||||
&mut RandomPerm::new(seed),
|
&mut RandomPerm::new(seed),
|
||||||
),
|
),
|
||||||
StructureBlock::TemperateLeaves,
|
StructureBlock::TemperateLeaves,
|
||||||
@ -205,20 +193,18 @@ pub fn apply_trees_to(
|
|||||||
|
|
||||||
let models = models.read();
|
let models = models.read();
|
||||||
TreeModel::Structure(
|
TreeModel::Structure(
|
||||||
models[(MODEL_RAND.get(seed.wrapping_mul(17)) / 13) as usize
|
models
|
||||||
% models.len()]
|
[(MODEL_RAND.get(seed.wrapping_mul(17)) / 13) as usize % models.len()]
|
||||||
.clone(),
|
.clone(),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
seed,
|
seed,
|
||||||
units: UNIT_CHOOSER.get(seed),
|
units: UNIT_CHOOSER.get(seed),
|
||||||
|
lights: inhabited,
|
||||||
})
|
})
|
||||||
}) {
|
});
|
||||||
tree
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
for tree in trees {
|
||||||
let bounds = match &tree.model {
|
let bounds = match &tree.model {
|
||||||
TreeModel::Structure(s) => s.get_bounds(),
|
TreeModel::Structure(s) => s.get_bounds(),
|
||||||
TreeModel::Procedural(t, _) => t.get_bounds().map(|e| e as i32),
|
TreeModel::Procedural(t, _) => t.get_bounds().map(|e| e as i32),
|
||||||
@ -279,7 +265,7 @@ pub fn apply_trees_to(
|
|||||||
)
|
)
|
||||||
.map(|block| {
|
.map(|block| {
|
||||||
// Add lights to the tree
|
// Add lights to the tree
|
||||||
if inhabited
|
if tree.lights
|
||||||
&& last_block.is_air()
|
&& last_block.is_air()
|
||||||
&& block.kind() == BlockKind::Wood
|
&& block.kind() == BlockKind::Wood
|
||||||
&& dynamic_rng.gen_range(0..256) == 0
|
&& dynamic_rng.gen_range(0..256) == 0
|
||||||
|
@ -137,7 +137,7 @@ pub fn spawn_manifest() -> Vec<(&'static str, DensityFn)> {
|
|||||||
// **Tundra**
|
// **Tundra**
|
||||||
// Rock animals
|
// Rock animals
|
||||||
("world.wildlife.spawn.tundra.rock", |c, col| {
|
("world.wildlife.spawn.tundra.rock", |c, col| {
|
||||||
close(c.temp, CONFIG.snow_temp, 0.15) * BASE_DENSITY * col.rock * 1.0
|
close(c.temp, CONFIG.snow_temp, 0.15) * BASE_DENSITY * col.rock_density * 1.0
|
||||||
}),
|
}),
|
||||||
// Core animals
|
// Core animals
|
||||||
("world.wildlife.spawn.tundra.core", |c, _col| {
|
("world.wildlife.spawn.tundra.core", |c, _col| {
|
||||||
@ -282,7 +282,7 @@ pub fn spawn_manifest() -> Vec<(&'static str, DensityFn)> {
|
|||||||
}),
|
}),
|
||||||
// Rock animals
|
// Rock animals
|
||||||
("world.wildlife.spawn.tropical.rock", |c, col| {
|
("world.wildlife.spawn.tropical.rock", |c, col| {
|
||||||
close(c.temp, CONFIG.tropical_temp + 0.1, 0.5) * col.rock * BASE_DENSITY * 5.0
|
close(c.temp, CONFIG.tropical_temp + 0.1, 0.5) * col.rock_density * BASE_DENSITY * 5.0
|
||||||
}),
|
}),
|
||||||
// **Desert**
|
// **Desert**
|
||||||
// Area animals
|
// Area animals
|
||||||
|
@ -366,6 +366,9 @@ impl World {
|
|||||||
if index.features.caves {
|
if index.features.caves {
|
||||||
layer::apply_caves_to(&mut canvas, &mut dynamic_rng);
|
layer::apply_caves_to(&mut canvas, &mut dynamic_rng);
|
||||||
}
|
}
|
||||||
|
if index.features.rocks {
|
||||||
|
layer::apply_rocks_to(&mut canvas, &mut dynamic_rng);
|
||||||
|
}
|
||||||
if index.features.shrubs {
|
if index.features.shrubs {
|
||||||
layer::apply_shrubs_to(&mut canvas, &mut dynamic_rng);
|
layer::apply_shrubs_to(&mut canvas, &mut dynamic_rng);
|
||||||
}
|
}
|
||||||
|
@ -2124,6 +2124,7 @@ impl WorldSim {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// WARNING: Not currently used by the tree layer. Needs to be reworked.
|
||||||
/// Return an iterator over candidate tree positions (note that only some of
|
/// Return an iterator over candidate tree positions (note that only some of
|
||||||
/// these will become trees since environmental parameters may forbid
|
/// these will become trees since environmental parameters may forbid
|
||||||
/// them spawning).
|
/// them spawning).
|
||||||
|
41
world/src/util/gen_cache.rs
Normal file
41
world/src/util/gen_cache.rs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
use hashbrown::HashMap;
|
||||||
|
use vek::Vec2;
|
||||||
|
|
||||||
|
use super::{Sampler, StructureGen2d};
|
||||||
|
|
||||||
|
pub struct StructureGenCache<T> {
|
||||||
|
gen: StructureGen2d,
|
||||||
|
// TODO: Compare performance of using binary search instead of hashmap
|
||||||
|
cache: HashMap<Vec2<i32>, Option<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> StructureGenCache<T> {
|
||||||
|
pub fn new(gen: StructureGen2d) -> Self {
|
||||||
|
Self {
|
||||||
|
gen,
|
||||||
|
cache: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(
|
||||||
|
&mut self,
|
||||||
|
index: Vec2<i32>,
|
||||||
|
mut generate: impl FnMut(Vec2<i32>, u32) -> Option<T>,
|
||||||
|
) -> Vec<&T> {
|
||||||
|
let close = self.gen.get(index);
|
||||||
|
for (wpos, seed) in close {
|
||||||
|
self.cache
|
||||||
|
.entry(wpos)
|
||||||
|
.or_insert_with(|| generate(wpos, seed));
|
||||||
|
}
|
||||||
|
|
||||||
|
close
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(wpos, _)| self.cache.get(wpos).unwrap().as_ref())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generated(&self) -> impl Iterator<Item = &T> {
|
||||||
|
self.cache.values().filter_map(|v| v.as_ref())
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
pub mod fast_noise;
|
pub mod fast_noise;
|
||||||
|
pub mod gen_cache;
|
||||||
pub mod map_array;
|
pub mod map_array;
|
||||||
pub mod map_vec;
|
pub mod map_vec;
|
||||||
pub mod math;
|
pub mod math;
|
||||||
@ -64,6 +65,35 @@ pub const NEIGHBORS: [Vec2<i32>; 8] = [
|
|||||||
Vec2::new(1, -1),
|
Vec2::new(1, -1),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub const NEIGHBORS3: [Vec3<i32>; 26] = [
|
||||||
|
Vec3::new(0, 0, -1),
|
||||||
|
Vec3::new(0, 0, 1),
|
||||||
|
Vec3::new(0, -1, 0),
|
||||||
|
Vec3::new(0, -1, -1),
|
||||||
|
Vec3::new(0, -1, 1),
|
||||||
|
Vec3::new(0, 1, 0),
|
||||||
|
Vec3::new(0, 1, -1),
|
||||||
|
Vec3::new(0, 1, 1),
|
||||||
|
Vec3::new(-1, 0, 0),
|
||||||
|
Vec3::new(-1, 0, -1),
|
||||||
|
Vec3::new(-1, 0, 1),
|
||||||
|
Vec3::new(-1, -1, 0),
|
||||||
|
Vec3::new(-1, -1, -1),
|
||||||
|
Vec3::new(-1, -1, 1),
|
||||||
|
Vec3::new(-1, 1, 0),
|
||||||
|
Vec3::new(-1, 1, -1),
|
||||||
|
Vec3::new(-1, 1, 1),
|
||||||
|
Vec3::new(1, 0, 0),
|
||||||
|
Vec3::new(1, 0, -1),
|
||||||
|
Vec3::new(1, 0, 1),
|
||||||
|
Vec3::new(1, -1, 0),
|
||||||
|
Vec3::new(1, -1, -1),
|
||||||
|
Vec3::new(1, -1, 1),
|
||||||
|
Vec3::new(1, 1, 0),
|
||||||
|
Vec3::new(1, 1, -1),
|
||||||
|
Vec3::new(1, 1, 1),
|
||||||
|
];
|
||||||
|
|
||||||
pub const LOCALITY: [Vec2<i32>; 9] = [
|
pub const LOCALITY: [Vec2<i32>; 9] = [
|
||||||
Vec2::new(0, 0),
|
Vec2::new(0, 0),
|
||||||
Vec2::new(0, 1),
|
Vec2::new(0, 1),
|
||||||
|
@ -10,8 +10,10 @@ pub struct RandomField {
|
|||||||
impl RandomField {
|
impl RandomField {
|
||||||
pub const fn new(seed: u32) -> Self { Self { seed } }
|
pub const fn new(seed: u32) -> Self { Self { seed } }
|
||||||
|
|
||||||
pub fn chance(&self, pos: Vec3<i32>, chance: f32) -> bool {
|
pub fn chance(&self, pos: Vec3<i32>, chance: f32) -> bool { self.get_f32(pos) < chance }
|
||||||
(self.get(pos) % (1 << 16)) as f32 / ((1 << 16) as f32) < chance
|
|
||||||
|
pub fn get_f32(&self, pos: Vec3<i32>) -> f32 {
|
||||||
|
(self.get(pos) % (1 << 16)) as f32 / ((1 << 16) as f32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ use super::{RandomField, Sampler};
|
|||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct StructureGen2d {
|
pub struct StructureGen2d {
|
||||||
freq: u32,
|
freq: u32,
|
||||||
spread: u32,
|
spread: u32,
|
||||||
|
Loading…
Reference in New Issue
Block a user