Sampled caverns

This commit is contained in:
IsseW 2022-03-01 22:12:25 +01:00 committed by Sam
parent abf3546e64
commit 731195f864
5 changed files with 116 additions and 27 deletions

View File

@ -51,12 +51,14 @@ fn main() -> Result {
for x in aabb.min.x..aabb.max.x {
for y in aabb.min.y..aabb.max.y {
// TODO: remove unwrap
let col = canvas.col(Vec2::new(x, y)).unwrap().get_info();
for z in aabb.min.z..aabb.max.z {
let pos = Vec3::new(x, y, z);
let _ = volume.map(pos, |block| {
if let Some(block) =
fill.sample_at(&prim_tree, prim, pos, canvas, block)
fill.sample_at(&prim_tree, prim, pos, canvas, block, &col)
{
block
} else {

View File

@ -1245,3 +1245,24 @@ pub struct ColumnSample<'a> {
pub chunk: &'a SimChunk,
}
impl ColumnSample<'_> {
pub fn get_info(&self) -> ColInfo {
ColInfo {
alt: self.alt,
basement: self.basement,
cliff_offset: self.cliff_offset,
cliff_height: self.cliff_height,
}
}
}
// For a version of ColumnSample that can easily be moved around. Feel free to
// add non reference fields as needed.
#[derive(Clone)]
pub struct ColInfo {
pub alt: f32,
pub basement: f32,
pub cliff_offset: f32,
pub cliff_height: f32,
}

View File

@ -4,6 +4,7 @@ use {crate::LIB, std::ffi::CStr};
use super::*;
use crate::{
block::block_from_structure,
column::ColInfo,
site2::util::Dir,
util::{RandomField, Sampler},
CanvasInfo,
@ -72,6 +73,7 @@ pub enum Primitive {
/// A sampling function is always a subset of another primitive to avoid
/// needing infinite bounds
Sampling(Id<Primitive>, Box<dyn Fn(Vec3<i32>) -> bool>),
ColSampling(Id<Primitive>, Box<dyn Fn(Vec3<i32>, &ColInfo) -> bool>),
Prefab(Box<PrefabStructure>),
// Combinators
@ -139,6 +141,7 @@ impl std::fmt::Debug for Primitive {
.field(&height)
.finish(),
Primitive::Sampling(prim, _) => f.debug_tuple("Sampling").field(&prim).finish(),
Primitive::ColSampling(prim, _) => f.debug_tuple("ColSampling").field(&prim).finish(),
Primitive::Prefab(prefab) => f.debug_tuple("Prefab").field(&prefab).finish(),
Primitive::Intersect(a, b) => f.debug_tuple("Intersect").field(&a).field(&b).finish(),
Primitive::Union(a, b) => f.debug_tuple("Union").field(&a).field(&b).finish(),
@ -180,6 +183,13 @@ impl Primitive {
Self::Sampling(a.into(), f)
}
pub fn column_sampling(
a: impl Into<Id<Primitive>>,
f: Box<dyn Fn(Vec3<i32>, &ColInfo) -> bool>,
) -> Self {
Self::ColSampling(a.into(), f)
}
pub fn translate(a: impl Into<Id<Primitive>>, trans: Vec3<i32>) -> Self {
Self::Translate(a.into(), trans)
}
@ -216,7 +226,12 @@ pub enum Fill {
}
impl Fill {
fn contains_at(tree: &Store<Primitive>, prim: Id<Primitive>, pos: Vec3<i32>) -> bool {
fn contains_at(
tree: &Store<Primitive>,
prim: Id<Primitive>,
pos: Vec3<i32>,
col: &ColInfo,
) -> 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)
@ -375,19 +390,20 @@ impl Fill {
let z_check = (projected_z..=(projected_z + height)).contains(&(pos.z as f32));
xy_check && z_check
},
Primitive::Sampling(a, f) => Self::contains_at(tree, *a, pos) && f(pos),
Primitive::Sampling(a, f) => Self::contains_at(tree, *a, pos, col) && f(pos),
Primitive::ColSampling(a, f) => Self::contains_at(tree, *a, pos, col) && f(pos, col),
Primitive::Prefab(p) => !matches!(p.get(pos), Err(_) | Ok(StructureBlock::None)),
Primitive::Intersect(a, b) => {
Self::contains_at(tree, *a, pos) && Self::contains_at(tree, *b, pos)
Self::contains_at(tree, *a, pos, col) && Self::contains_at(tree, *b, pos, col)
},
Primitive::Union(a, b) => {
Self::contains_at(tree, *a, pos) || Self::contains_at(tree, *b, pos)
Self::contains_at(tree, *a, pos, col) || Self::contains_at(tree, *b, pos, col)
},
Primitive::Without(a, b) => {
Self::contains_at(tree, *a, pos) && !Self::contains_at(tree, *b, pos)
Self::contains_at(tree, *a, pos, col) && !Self::contains_at(tree, *b, pos, col)
},
Primitive::Translate(prim, vec) => {
Self::contains_at(tree, *prim, pos.map2(*vec, i32::saturating_sub))
Self::contains_at(tree, *prim, pos.map2(*vec, i32::saturating_sub), col)
},
Primitive::Scale(prim, vec) => {
let center = Self::get_bounds(tree, *prim).as_::<f32>().center();
@ -395,12 +411,17 @@ impl Fill {
let spos = (center + ((fpos - center) / vec))
.map(|x| x.round())
.as_::<i32>();
Self::contains_at(tree, *prim, spos)
Self::contains_at(tree, *prim, spos, col)
},
Primitive::RotateAbout(prim, mat, vec) => {
let mat = mat.as_::<f32>().transposed();
let vec = vec - 0.5;
Self::contains_at(tree, *prim, (vec + mat * (pos.as_::<f32>() - vec)).as_())
Self::contains_at(
tree,
*prim,
(vec + mat * (pos.as_::<f32>() - vec)).as_(),
col,
)
},
Primitive::Repeat(prim, offset, count) => {
if count == &0 {
@ -419,7 +440,7 @@ impl Fill {
.reduce_min()
.clamp(0, count as i32);
let pos = pos - offset * min;
Self::contains_at(tree, *prim, pos)
Self::contains_at(tree, *prim, pos, col)
}
},
}
@ -432,8 +453,9 @@ impl Fill {
pos: Vec3<i32>,
canvas_info: &CanvasInfo,
old_block: Block,
col: &ColInfo,
) -> Option<Block> {
if Self::contains_at(tree, prim, pos) {
if Self::contains_at(tree, prim, pos, col) {
match self {
Fill::Block(block) => Some(*block),
Fill::Sprite(sprite) => Some(if old_block.is_filled() {
@ -553,7 +575,9 @@ impl Fill {
};
vec![Aabb { min, max }]
},
Primitive::Sampling(a, _) => Self::get_bounds_inner(tree, *a),
Primitive::Sampling(a, _) | Primitive::ColSampling(a, _) => {
Self::get_bounds_inner(tree, *a)
},
Primitive::Prefab(p) => vec![p.get_bounds()],
Primitive::Intersect(a, b) => or_zip_with(
Self::get_bounds_opt(tree, *a),
@ -693,6 +717,7 @@ impl Painter {
| Primitive::SegmentPrism { .. }
| Primitive::Prefab(_) => prev_depth,
Primitive::Sampling(a, _)
| Primitive::ColSampling(a, _)
| Primitive::Translate(a, _)
| Primitive::Scale(a, _)
| Primitive::RotateAbout(a, _, _)
@ -1261,6 +1286,17 @@ impl<'a> PrimitiveRef<'a> {
.prim(Primitive::sampling(self, Box::new(sampling)))
}
/// Returns a `PrimitiveRef` that conforms to the provided sampling
/// function.
#[must_use]
pub fn sample_with_column(
self,
sampling: impl Fn(Vec3<i32>, &ColInfo) -> bool + 'static,
) -> PrimitiveRef<'a> {
self.painter
.prim(Primitive::column_sampling(self, Box::new(sampling)))
}
/// Rotates a primitive about it's own's bounds minimum point,
#[must_use]
pub fn rotate_about_min(self, mat: Mat3<i32>) -> PrimitiveRef<'a> {

View File

@ -1404,7 +1404,8 @@ impl Site {
for x in aabb.min.x..aabb.max.x {
for y in aabb.min.y..aabb.max.y {
let col_tile = self.wpos_tile(Vec2::new(x, y));
let wpos = Vec2::new(x, y);
let col_tile = self.wpos_tile(wpos);
if
/* col_tile.is_building() && */
col_tile
@ -1416,12 +1417,16 @@ impl Site {
continue;
}
let mut last_block = None;
// TODO: Don't unwrap here
let col = canvas.col(wpos).unwrap().get_info();
for z in aabb.min.z..aabb.max.z {
let pos = Vec3::new(x, y, z);
canvas.map(pos, |block| {
let current_block =
fill.sample_at(&prim_tree, prim, pos, &info, block);
fill.sample_at(&prim_tree, prim, pos, &info, block, &col);
if let (Some(last_block), None) = (last_block, current_block) {
spawn(pos, last_block);
}

View File

@ -2,7 +2,7 @@ use super::*;
use crate::{
assets::AssetHandle,
site2::{gen::PrimitiveTransform, util::Dir},
util::{attempt, sampler::Sampler, RandomField},
util::{attempt, sampler::Sampler, FastNoise, RandomField},
Land,
};
use common::{
@ -84,8 +84,8 @@ impl AdletStronghold {
// Go 50% below minimum height needed, unless entrance already below that height
// TODO: Get better heuristic for this
let cavern_alt = (land.get_alt_approx(origin) - cavern_radius as f32 * 1.5)
.min(land.get_alt_approx(entrance));
let cavern_alt =
(land.get_alt_approx(origin) - cavern_radius as f32).min(land.get_alt_approx(entrance));
Self {
name,
@ -155,6 +155,31 @@ impl Structure for AdletStronghold {
min: (self.origin - self.cavern_radius).with_z(self.cavern_alt as i32),
max: self.origin.with_z(self.cavern_alt as i32) + self.cavern_radius,
}))
.sample_with_column({
let origin = self.origin.with_z(self.cavern_alt as i32);
let radius_sqr = self.cavern_radius * self.cavern_radius;
move |pos, col| {
let alt = col.basement - col.cliff_offset;
let sphere_alt = ((radius_sqr - origin.xy().distance_squared(pos.xy())) as f32)
.sqrt()
+ origin.z as f32;
// Some sort of smooth min
let alt = if alt < sphere_alt {
alt
} else if sphere_alt - alt < 10.0 {
f32::lerp(sphere_alt, alt, 1.0 / (alt - sphere_alt).max(1.0))
} else {
sphere_alt
};
let noise = FastNoise::new(333);
let alt_offset = noise.get(pos.with_z(0).as_() / 5.0).powi(2) * 15.0;
let alt = alt - alt_offset;
pos.z < alt as i32
}
})
.clear();
// Create outer wall
@ -310,16 +335,16 @@ mod tests {
#[test]
fn test_creating_entities() {
let pos = Vec3::zero();
let mut rng = thread_rng();
// let pos = Vec3::zero();
// let mut rng = thread_rng();
gnarling_mugger(pos, &mut rng);
gnarling_stalker(pos, &mut rng);
gnarling_logger(pos, &mut rng);
gnarling_chieftain(pos, &mut rng);
deadwood(pos, &mut rng);
mandragora(pos, &mut rng);
wood_golem(pos, &mut rng);
harvester_boss(pos, &mut rng);
// gnarling_mugger(pos, &mut rng);
// gnarling_stalker(pos, &mut rng);
// gnarling_logger(pos, &mut rng);
// gnarling_chieftain(pos, &mut rng);
// deadwood(pos, &mut rng);
// mandragora(pos, &mut rng);
// wood_golem(pos, &mut rng);
// harvester_boss(pos, &mut rng);
}
}