Remove spurious uses of Vox.

In the process, also try to address a few edge cases related to block
detection, such as adding back previously solid sprites and removing
filters that may be vestiges of earlier logic.
This commit is contained in:
Joshua Yanovski 2020-09-26 15:55:01 +02:00
parent 98c8240879
commit 938039a56e
32 changed files with 385 additions and 326 deletions

View File

@ -4,7 +4,7 @@ use vek::*;
use veloren_common::{
terrain::{
block::{Block, BlockKind},
TerrainChunk, TerrainChunkMeta,
SpriteKind, TerrainChunk, TerrainChunkMeta,
},
vol::*,
};
@ -17,7 +17,7 @@ fn criterion_benchmark(c: &mut Criterion) {
let mut chunk = TerrainChunk::new(
MIN_Z,
Block::new(BlockKind::Rock, Rgb::zero()),
Block::empty(),
Block::air(SpriteKind::Empty),
TerrainChunkMeta::void(),
);
for pos in chunk.pos_iter(

View File

@ -133,7 +133,7 @@ impl Route {
// Only consider the node reached if there's nothing solid between us and it
&& (vol
.ray(pos + Vec3::unit_z() * 1.5, closest_tgt + Vec3::unit_z() * 1.5)
.until(|block| block.is_solid())
.until(Block::is_solid)
.cast()
.0
> pos.distance(closest_tgt) * 0.9 || dist_sqrd < 0.5)

View File

@ -1,11 +1,8 @@
use crate::{
span,
vol::{ReadVol, Vox},
};
use crate::{span, vol::ReadVol};
use vek::*;
pub trait RayUntil<V: Vox> = FnMut(&V) -> bool;
pub trait RayForEach<V: Vox> = FnMut(&V, Vec3<i32>);
pub trait RayUntil<V> = FnMut(&V) -> bool;
pub trait RayForEach<V> = FnMut(&V, Vec3<i32>);
pub struct Ray<'a, V: ReadVol, F: RayUntil<V::Vox>, G: RayForEach<V::Vox>> {
vol: &'a V,

View File

@ -15,7 +15,7 @@ use crate::{
span,
state::{DeltaTime, Time},
sync::{Uid, UidAllocator},
terrain::TerrainGrid,
terrain::{Block, TerrainGrid},
util::Dir,
vol::ReadVol,
};
@ -195,7 +195,7 @@ impl<'a> System<'a> for Sys {
* 5.0
+ Vec3::unit_z(),
)
.until(|block| block.is_solid())
.until(Block::is_solid)
.cast()
.1
.map_or(true, |b| b.is_none())
@ -374,7 +374,7 @@ impl<'a> System<'a> for Sys {
{
let can_see_tgt = terrain
.ray(pos.0 + Vec3::unit_z(), tgt_pos.0 + Vec3::unit_z())
.until(|block| !block.is_air())
.until(Block::is_opaque)
.cast()
.0
.powf(2.0)
@ -473,7 +473,7 @@ impl<'a> System<'a> for Sys {
// Can we even see them?
.filter(|(_, e_pos, _, _)| terrain
.ray(pos.0 + Vec3::unit_z(), e_pos.0 + Vec3::unit_z())
.until(|block| !block.is_air())
.until(Block::is_opaque)
.cast()
.0 >= e_pos.0.distance(pos.0))
.min_by_key(|(_, e_pos, _, _)| (e_pos.0.distance_squared(pos.0) * 100.0) as i32)

View File

@ -622,7 +622,7 @@ impl<'a> System<'a> for Sys {
},
Collider::Point => {
let (dist, block) = terrain.ray(pos.0, pos.0 + pos_delta)
.until(|vox| !vox.is_air() && !vox.is_liquid())
.until(|block| block.is_filled())
.ignore_error().cast();
pos.0 += pos_delta.try_normalized().unwrap_or(Vec3::zero()) * dist;

View File

@ -1,5 +1,5 @@
use super::SpriteKind;
use crate::{make_case_elim, vol::Vox};
use crate::make_case_elim;
use enum_iterator::IntoEnumIterator;
use lazy_static::lazy_static;
use num_derive::FromPrimitive;
@ -101,20 +101,10 @@ impl Deref for Block {
fn deref(&self) -> &Self::Target { &self.kind }
}
impl Vox for Block {
fn empty() -> Self {
Self {
kind: BlockKind::Air,
attr: [0; 3],
}
}
fn is_empty(&self) -> bool { *self == Block::empty() }
}
impl Block {
pub const MAX_HEIGHT: f32 = 3.0;
#[inline]
pub const fn new(kind: BlockKind, color: Rgb<u8>) -> Self {
Self {
kind,
@ -127,6 +117,7 @@ impl Block {
}
}
#[inline]
pub const fn air(sprite: SpriteKind) -> Self {
Self {
kind: BlockKind::Air,
@ -237,7 +228,9 @@ impl Block {
if self.is_fluid() {
Block::new(self.kind(), Rgb::zero())
} else {
Block::empty()
// FIXME: Figure out if there's some sensible way to determine what medium to
// replace a filled block with if it's removed.
Block::air(SpriteKind::Empty)
}
}
}

View File

@ -1,7 +1,7 @@
use crate::{
vol::{
BaseVol, IntoPosIterator, IntoVolIterator, ReadVol, RectRasterableVol, RectVolSize,
VolSize, Vox, WriteVol,
VolSize, WriteVol,
},
volumes::chunk::{Chunk, ChunkError, ChunkPosIter, ChunkVolIter},
};
@ -34,7 +34,7 @@ impl<ChonkSize: RectVolSize> VolSize for SubChunkSize<ChonkSize> {
type SubChunk<V, S, M> = Chunk<V, SubChunkSize<S>, M>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Chonk<V: Vox, S: RectVolSize, M: Clone> {
pub struct Chonk<V, S: RectVolSize, M: Clone> {
z_offset: i32,
sub_chunks: Vec<SubChunk<V, S, M>>,
below: V,
@ -43,7 +43,7 @@ pub struct Chonk<V: Vox, S: RectVolSize, M: Clone> {
phantom: PhantomData<S>,
}
impl<V: Vox, S: RectVolSize, M: Clone> Chonk<V, S, M> {
impl<V, S: RectVolSize, M: Clone> Chonk<V, S, M> {
pub fn new(z_offset: i32, below: V, above: V, meta: M) -> Self {
Self {
z_offset,
@ -82,16 +82,16 @@ impl<V: Vox, S: RectVolSize, M: Clone> Chonk<V, S, M> {
fn sub_chunk_min_z(&self, z: i32) -> i32 { z - self.sub_chunk_z(z) }
}
impl<V: Vox, S: RectVolSize, M: Clone> BaseVol for Chonk<V, S, M> {
impl<V, S: RectVolSize, M: Clone> BaseVol for Chonk<V, S, M> {
type Error = ChonkError;
type Vox = V;
}
impl<V: Vox, S: RectVolSize, M: Clone> RectRasterableVol for Chonk<V, S, M> {
impl<V, S: RectVolSize, M: Clone> RectRasterableVol for Chonk<V, S, M> {
const RECT_SIZE: Vec2<u32> = S::RECT_SIZE;
}
impl<V: Vox, S: RectVolSize, M: Clone> ReadVol for Chonk<V, S, M> {
impl<V, S: RectVolSize, M: Clone> ReadVol for Chonk<V, S, M> {
#[inline(always)]
fn get(&self, pos: Vec3<i32>) -> Result<&V, Self::Error> {
if pos.z < self.get_min_z() {
@ -113,7 +113,7 @@ impl<V: Vox, S: RectVolSize, M: Clone> ReadVol for Chonk<V, S, M> {
}
}
impl<V: Vox, S: RectVolSize, M: Clone> WriteVol for Chonk<V, S, M> {
impl<V: Clone + PartialEq, S: RectVolSize, M: Clone> WriteVol for Chonk<V, S, M> {
#[inline(always)]
fn set(&mut self, pos: Vec3<i32>, block: Self::Vox) -> Result<(), Self::Error> {
let mut sub_chunk_idx = self.sub_chunk_idx(pos.z);
@ -140,14 +140,14 @@ impl<V: Vox, S: RectVolSize, M: Clone> WriteVol for Chonk<V, S, M> {
}
}
struct ChonkIterHelper<V: Vox, S: RectVolSize, M: Clone> {
struct ChonkIterHelper<V, S: RectVolSize, M: Clone> {
sub_chunk_min_z: i32,
lower_bound: Vec3<i32>,
upper_bound: Vec3<i32>,
phantom: PhantomData<Chonk<V, S, M>>,
}
impl<V: Vox, S: RectVolSize, M: Clone> Iterator for ChonkIterHelper<V, S, M> {
impl<V, S: RectVolSize, M: Clone> Iterator for ChonkIterHelper<V, S, M> {
type Item = (i32, Vec3<i32>, Vec3<i32>);
#[inline(always)]
@ -168,12 +168,12 @@ impl<V: Vox, S: RectVolSize, M: Clone> Iterator for ChonkIterHelper<V, S, M> {
}
#[allow(clippy::type_complexity)] // TODO: Pending review in #587
pub struct ChonkPosIter<V: Vox, S: RectVolSize, M: Clone> {
pub struct ChonkPosIter<V, S: RectVolSize, M: Clone> {
outer: ChonkIterHelper<V, S, M>,
opt_inner: Option<(i32, ChunkPosIter<V, SubChunkSize<S>, M>)>,
}
impl<V: Vox, S: RectVolSize, M: Clone> Iterator for ChonkPosIter<V, S, M> {
impl<V, S: RectVolSize, M: Clone> Iterator for ChonkPosIter<V, S, M> {
type Item = Vec3<i32>;
#[inline(always)]
@ -195,18 +195,18 @@ impl<V: Vox, S: RectVolSize, M: Clone> Iterator for ChonkPosIter<V, S, M> {
}
}
enum InnerChonkVolIter<'a, V: Vox, S: RectVolSize, M: Clone> {
enum InnerChonkVolIter<'a, V, S: RectVolSize, M: Clone> {
Vol(ChunkVolIter<'a, V, SubChunkSize<S>, M>),
Pos(ChunkPosIter<V, SubChunkSize<S>, M>),
}
pub struct ChonkVolIter<'a, V: Vox, S: RectVolSize, M: Clone> {
pub struct ChonkVolIter<'a, V, S: RectVolSize, M: Clone> {
chonk: &'a Chonk<V, S, M>,
outer: ChonkIterHelper<V, S, M>,
opt_inner: Option<(i32, InnerChonkVolIter<'a, V, S, M>)>,
}
impl<'a, V: Vox, S: RectVolSize, M: Clone> Iterator for ChonkVolIter<'a, V, S, M> {
impl<'a, V, S: RectVolSize, M: Clone> Iterator for ChonkVolIter<'a, V, S, M> {
type Item = (Vec3<i32>, &'a V);
#[inline(always)]
@ -249,7 +249,7 @@ impl<'a, V: Vox, S: RectVolSize, M: Clone> Iterator for ChonkVolIter<'a, V, S, M
}
}
impl<'a, V: Vox, S: RectVolSize, M: Clone> IntoPosIterator for &'a Chonk<V, S, M> {
impl<'a, V, S: RectVolSize, M: Clone> IntoPosIterator for &'a Chonk<V, S, M> {
type IntoIter = ChonkPosIter<V, S, M>;
fn pos_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> Self::IntoIter {
@ -265,7 +265,7 @@ impl<'a, V: Vox, S: RectVolSize, M: Clone> IntoPosIterator for &'a Chonk<V, S, M
}
}
impl<'a, V: Vox, S: RectVolSize, M: Clone> IntoVolIterator<'a> for &'a Chonk<V, S, M> {
impl<'a, V, S: RectVolSize, M: Clone> IntoVolIterator<'a> for &'a Chonk<V, S, M> {
type IntoIter = ChonkVolIter<'a, V, S, M>;
fn vol_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> Self::IntoIter {

View File

@ -139,6 +139,24 @@ impl SpriteKind {
SpriteKind::WardrobeSingle => 3.0,
SpriteKind::WardrobeDouble => 3.0,
SpriteKind::Pot => 0.90,
// TODO: Find suitable heights.
SpriteKind::BarrelCactus
| SpriteKind::RoundCactus
| SpriteKind::ShortCactus
| SpriteKind::MedFlatCactus
| SpriteKind::ShortFlatCactus
| SpriteKind::Apple
| SpriteKind::Velorite
| SpriteKind::VeloriteFrag
| SpriteKind::Coconut
| SpriteKind::StreetLampTall
| SpriteKind::Window1
| SpriteKind::Window2
| SpriteKind::Window3
| SpriteKind::Window4
| SpriteKind::DropGate => 1.0,
// TODO: Figure out if this should be solid or not.
SpriteKind::Shelf => 1.0,
_ => return None,
})
}

View File

@ -2,7 +2,7 @@ use super::BlockKind;
use crate::{
assets::{self, Asset, Ron},
make_case_elim,
vol::{BaseVol, ReadVol, SizedVol, Vox, WriteVol},
vol::{BaseVol, ReadVol, SizedVol, WriteVol},
volumes::dyna::{Dyna, DynaError},
};
use dot_vox::DotVoxData;
@ -34,12 +34,6 @@ make_case_elim!(
}
);
impl Vox for StructureBlock {
fn empty() -> Self { StructureBlock::None }
fn is_empty(&self) -> bool { matches!(self, StructureBlock::None) }
}
#[derive(Debug)]
pub enum StructureError {}
@ -112,7 +106,7 @@ impl Asset for Structure {
let mut vol = Dyna::filled(
Vec3::new(model.size.x, model.size.y, model.size.z),
StructureBlock::empty(),
StructureBlock::None,
(),
);
@ -147,14 +141,14 @@ impl Asset for Structure {
Ok(Structure {
center: Vec3::zero(),
vol,
empty: StructureBlock::empty(),
empty: StructureBlock::None,
default_kind: BlockKind::Misc,
})
} else {
Ok(Self {
center: Vec3::zero(),
vol: Dyna::filled(Vec3::zero(), StructureBlock::empty(), ()),
empty: StructureBlock::empty(),
vol: Dyna::filled(Vec3::zero(), StructureBlock::None, ()),
empty: StructureBlock::None,
default_kind: BlockKind::Misc,
})
}

View File

@ -22,7 +22,7 @@ pub trait Vox: Sized + Clone + PartialEq {
/// A volume that contains voxel data.
pub trait BaseVol {
type Vox: Vox;
type Vox;
type Error: Debug;
fn scaled_by(self, scale: Vec3<f32>) -> Scaled<Self>
@ -98,6 +98,9 @@ pub trait ReadVol: BaseVol {
fn get<'a>(&'a self, pos: Vec3<i32>) -> Result<&'a Self::Vox, Self::Error>;
#[allow(clippy::type_complexity)] // TODO: Pending review in #587
/// NOTE: By default, this ray will simply run from `from` to `to` without
/// stopping. To make something interesting happen, call `until` or
/// `for_each`.
fn ray<'a>(
&'a self,
from: Vec3<f32>,
@ -106,7 +109,7 @@ pub trait ReadVol: BaseVol {
where
Self: Sized,
{
Ray::new(self, from, to, |vox| !vox.is_empty())
Ray::new(self, from, to, |_| true)
}
}

View File

@ -1,5 +1,5 @@
use crate::vol::{
BaseVol, IntoPosIterator, IntoVolIterator, RasterableVol, ReadVol, VolSize, Vox, WriteVol,
BaseVol, IntoPosIterator, IntoVolIterator, RasterableVol, ReadVol, VolSize, WriteVol,
};
use serde::{Deserialize, Serialize};
use std::{iter::Iterator, marker::PhantomData};
@ -46,7 +46,7 @@ pub enum ChunkError {
/// index buffer can consist of `u8`s. This keeps the space requirement for the
/// index buffer as low as 4 cache lines.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Chunk<V: Vox, S: VolSize, M> {
pub struct Chunk<V, S: VolSize, M> {
indices: Vec<u8>, /* TODO (haslersn): Box<[u8; S::SIZE.x * S::SIZE.y * S::SIZE.z]>, this is
* however not possible in Rust yet */
vox: Vec<V>,
@ -55,7 +55,7 @@ pub struct Chunk<V: Vox, S: VolSize, M> {
phantom: PhantomData<S>,
}
impl<V: Vox, S: VolSize, M> Chunk<V, S, M> {
impl<V, S: VolSize, M> Chunk<V, S, M> {
const GROUP_COUNT: Vec3<u32> = Vec3::new(
S::SIZE.x / Self::GROUP_SIZE.x,
S::SIZE.y / Self::GROUP_SIZE.y,
@ -151,7 +151,10 @@ impl<V: Vox, S: VolSize, M> Chunk<V, S, M> {
}
#[inline(always)]
fn force_idx_unchecked(&mut self, pos: Vec3<i32>) -> usize {
fn force_idx_unchecked(&mut self, pos: Vec3<i32>) -> usize
where
V: Clone,
{
let grp_idx = Self::grp_idx(pos);
let rel_idx = Self::rel_idx(pos);
let base = &mut self.indices[grp_idx as usize];
@ -173,7 +176,10 @@ impl<V: Vox, S: VolSize, M> Chunk<V, S, M> {
}
#[inline(always)]
fn set_unchecked(&mut self, pos: Vec3<i32>, vox: V) {
fn set_unchecked(&mut self, pos: Vec3<i32>, vox: V)
where
V: Clone + PartialEq,
{
if vox != self.default {
let idx = self.force_idx_unchecked(pos);
self.vox[idx] = vox;
@ -183,16 +189,16 @@ impl<V: Vox, S: VolSize, M> Chunk<V, S, M> {
}
}
impl<V: Vox, S: VolSize, M> BaseVol for Chunk<V, S, M> {
impl<V, S: VolSize, M> BaseVol for Chunk<V, S, M> {
type Error = ChunkError;
type Vox = V;
}
impl<V: Vox, S: VolSize, M> RasterableVol for Chunk<V, S, M> {
impl<V, S: VolSize, M> RasterableVol for Chunk<V, S, M> {
const SIZE: Vec3<u32> = S::SIZE;
}
impl<V: Vox, S: VolSize, M> ReadVol for Chunk<V, S, M> {
impl<V, S: VolSize, M> ReadVol for Chunk<V, S, M> {
#[inline(always)]
fn get(&self, pos: Vec3<i32>) -> Result<&Self::Vox, Self::Error> {
if !pos
@ -206,7 +212,7 @@ impl<V: Vox, S: VolSize, M> ReadVol for Chunk<V, S, M> {
}
}
impl<V: Vox, S: VolSize, M> WriteVol for Chunk<V, S, M> {
impl<V: Clone + PartialEq, S: VolSize, M> WriteVol for Chunk<V, S, M> {
#[inline(always)]
#[allow(clippy::unit_arg)] // TODO: Pending review in #587
fn set(&mut self, pos: Vec3<i32>, vox: Self::Vox) -> Result<(), Self::Error> {
@ -221,7 +227,7 @@ impl<V: Vox, S: VolSize, M> WriteVol for Chunk<V, S, M> {
}
}
pub struct ChunkPosIter<V: Vox, S: VolSize, M> {
pub struct ChunkPosIter<V, S: VolSize, M> {
// Store as `u8`s so as to reduce memory footprint.
lb: Vec3<i32>,
ub: Vec3<i32>,
@ -229,7 +235,7 @@ pub struct ChunkPosIter<V: Vox, S: VolSize, M> {
phantom: PhantomData<Chunk<V, S, M>>,
}
impl<V: Vox, S: VolSize, M> ChunkPosIter<V, S, M> {
impl<V, S: VolSize, M> ChunkPosIter<V, S, M> {
fn new(lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> Self {
// If the range is empty, then we have the special case `ub = lower_bound`.
let ub = if lower_bound.map2(upper_bound, |l, u| l < u).reduce_and() {
@ -246,7 +252,7 @@ impl<V: Vox, S: VolSize, M> ChunkPosIter<V, S, M> {
}
}
impl<V: Vox, S: VolSize, M> Iterator for ChunkPosIter<V, S, M> {
impl<V, S: VolSize, M> Iterator for ChunkPosIter<V, S, M> {
type Item = Vec3<i32>;
#[inline(always)]
@ -301,12 +307,12 @@ impl<V: Vox, S: VolSize, M> Iterator for ChunkPosIter<V, S, M> {
}
}
pub struct ChunkVolIter<'a, V: Vox, S: VolSize, M> {
pub struct ChunkVolIter<'a, V, S: VolSize, M> {
chunk: &'a Chunk<V, S, M>,
iter_impl: ChunkPosIter<V, S, M>,
}
impl<'a, V: Vox, S: VolSize, M> Iterator for ChunkVolIter<'a, V, S, M> {
impl<'a, V, S: VolSize, M> Iterator for ChunkVolIter<'a, V, S, M> {
type Item = (Vec3<i32>, &'a V);
#[inline(always)]
@ -317,7 +323,7 @@ impl<'a, V: Vox, S: VolSize, M> Iterator for ChunkVolIter<'a, V, S, M> {
}
}
impl<V: Vox, S: VolSize, M> Chunk<V, S, M> {
impl<V, S: VolSize, M> Chunk<V, S, M> {
/// It's possible to obtain a positional iterator without having a `Chunk`
/// instance.
pub fn pos_iter(lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> ChunkPosIter<V, S, M> {
@ -325,7 +331,7 @@ impl<V: Vox, S: VolSize, M> Chunk<V, S, M> {
}
}
impl<'a, V: Vox, S: VolSize, M> IntoPosIterator for &'a Chunk<V, S, M> {
impl<'a, V, S: VolSize, M> IntoPosIterator for &'a Chunk<V, S, M> {
type IntoIter = ChunkPosIter<V, S, M>;
fn pos_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> Self::IntoIter {
@ -333,7 +339,7 @@ impl<'a, V: Vox, S: VolSize, M> IntoPosIterator for &'a Chunk<V, S, M> {
}
}
impl<'a, V: Vox, S: VolSize, M> IntoVolIterator<'a> for &'a Chunk<V, S, M> {
impl<'a, V, S: VolSize, M> IntoVolIterator<'a> for &'a Chunk<V, S, M> {
type IntoIter = ChunkVolIter<'a, V, S, M>;
fn vol_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> Self::IntoIter {

View File

@ -1,6 +1,6 @@
use crate::vol::{
BaseVol, DefaultPosIterator, DefaultVolIterator, IntoPosIterator, IntoVolIterator, ReadVol,
SizedVol, Vox, WriteVol,
SizedVol, WriteVol,
};
use serde::{Deserialize, Serialize};
use vek::*;
@ -15,14 +15,14 @@ pub enum DynaError {
// S = Size (replace when const generics are a thing)
// M = Metadata
#[derive(Debug, Serialize, Deserialize)]
pub struct Dyna<V: Vox, M, A: Access = ColumnAccess> {
pub struct Dyna<V, M, A: Access = ColumnAccess> {
vox: Vec<V>,
meta: M,
pub sz: Vec3<u32>,
_phantom: std::marker::PhantomData<A>,
}
impl<V: Vox, M: Clone, A: Access> Clone for Dyna<V, M, A> {
impl<V: Clone, M: Clone, A: Access> Clone for Dyna<V, M, A> {
fn clone(&self) -> Self {
Self {
vox: self.vox.clone(),
@ -33,7 +33,7 @@ impl<V: Vox, M: Clone, A: Access> Clone for Dyna<V, M, A> {
}
}
impl<V: Vox, M, A: Access> Dyna<V, M, A> {
impl<V, M, A: Access> Dyna<V, M, A> {
/// Used to transform a voxel position in the volume into its corresponding
/// index in the voxel array.
#[inline(always)]
@ -46,12 +46,12 @@ impl<V: Vox, M, A: Access> Dyna<V, M, A> {
}
}
impl<V: Vox, M, A: Access> BaseVol for Dyna<V, M, A> {
impl<V, M, A: Access> BaseVol for Dyna<V, M, A> {
type Error = DynaError;
type Vox = V;
}
impl<V: Vox, M, A: Access> SizedVol for Dyna<V, M, A> {
impl<V, M, A: Access> SizedVol for Dyna<V, M, A> {
#[inline(always)]
fn lower_bound(&self) -> Vec3<i32> { Vec3::zero() }
@ -59,7 +59,7 @@ impl<V: Vox, M, A: Access> SizedVol for Dyna<V, M, A> {
fn upper_bound(&self) -> Vec3<i32> { self.sz.map(|e| e as i32) }
}
impl<'a, V: Vox, M, A: Access> SizedVol for &'a Dyna<V, M, A> {
impl<'a, V, M, A: Access> SizedVol for &'a Dyna<V, M, A> {
#[inline(always)]
fn lower_bound(&self) -> Vec3<i32> { (*self).lower_bound() }
@ -67,7 +67,7 @@ impl<'a, V: Vox, M, A: Access> SizedVol for &'a Dyna<V, M, A> {
fn upper_bound(&self) -> Vec3<i32> { (*self).upper_bound() }
}
impl<V: Vox, M, A: Access> ReadVol for Dyna<V, M, A> {
impl<V, M, A: Access> ReadVol for Dyna<V, M, A> {
#[inline(always)]
fn get(&self, pos: Vec3<i32>) -> Result<&V, DynaError> {
Self::idx_for(self.sz, pos)
@ -76,7 +76,7 @@ impl<V: Vox, M, A: Access> ReadVol for Dyna<V, M, A> {
}
}
impl<V: Vox, M, A: Access> WriteVol for Dyna<V, M, A> {
impl<V, M, A: Access> WriteVol for Dyna<V, M, A> {
#[inline(always)]
fn set(&mut self, pos: Vec3<i32>, vox: Self::Vox) -> Result<(), DynaError> {
Self::idx_for(self.sz, pos)
@ -86,7 +86,7 @@ impl<V: Vox, M, A: Access> WriteVol for Dyna<V, M, A> {
}
}
impl<'a, V: Vox, M, A: Access> IntoPosIterator for &'a Dyna<V, M, A> {
impl<'a, V, M, A: Access> IntoPosIterator for &'a Dyna<V, M, A> {
type IntoIter = DefaultPosIterator;
fn pos_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> Self::IntoIter {
@ -94,7 +94,7 @@ impl<'a, V: Vox, M, A: Access> IntoPosIterator for &'a Dyna<V, M, A> {
}
}
impl<'a, V: Vox, M, A: Access> IntoVolIterator<'a> for &'a Dyna<V, M, A> {
impl<'a, V, M, A: Access> IntoVolIterator<'a> for &'a Dyna<V, M, A> {
type IntoIter = DefaultVolIterator<'a, Dyna<V, M, A>>;
fn vol_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> Self::IntoIter {
@ -102,7 +102,7 @@ impl<'a, V: Vox, M, A: Access> IntoVolIterator<'a> for &'a Dyna<V, M, A> {
}
}
impl<V: Vox + Clone, M, A: Access> Dyna<V, M, A> {
impl<V: Clone, M, A: Access> Dyna<V, M, A> {
/// Create a new `Dyna` with the provided dimensions and all voxels filled
/// with duplicates of the provided voxel.
pub fn filled(sz: Vec3<u32>, vox: V, meta: M) -> Self {

View File

@ -11,7 +11,10 @@ impl<V: BaseVol> BaseVol for Scaled<V> {
type Vox = V::Vox;
}
impl<V: ReadVol> ReadVol for Scaled<V> {
impl<V: ReadVol> ReadVol for Scaled<V>
where
V::Vox: Vox,
{
#[inline(always)]
fn get(&self, pos: Vec3<i32>) -> Result<&Self::Vox, Self::Error> {
// let ideal_pos = pos.map2(self.scale, |e, scale| (e as f32 + 0.5) / scale);

View File

@ -14,7 +14,7 @@ use common::{
sync::{Uid, WorldSyncExt},
terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
util::Dir,
vol::{RectVolSize, Vox},
vol::RectVolSize,
LoadoutBuilder,
};
use rand::Rng;
@ -233,7 +233,8 @@ fn handle_make_sprite(
let new_block = server
.state
.get_block(pos)
.unwrap_or_else(Block::empty)
// TODO: Make more principled.
.unwrap_or_else(|| Block::air(SpriteKind::Empty))
.with_sprite(sk);
server.state.set_block(pos, new_block);
},

View File

@ -534,6 +534,7 @@ pub fn handle_explosion(
let _ = ecs
.read_resource::<TerrainGrid>()
.ray(pos, pos + dir * color_range)
// TODO: Faster RNG
.until(|_| rand::random::<f32>() < 0.05)
.for_each(|_: &Block, pos| touched_blocks.push(pos))
.cast();
@ -569,6 +570,7 @@ pub fn handle_explosion(
let terrain = ecs.read_resource::<TerrainGrid>();
let _ = terrain
.ray(pos, pos + dir * power)
// TODO: Faster RNG
.until(|block| block.is_liquid() || rand::random::<f32>() < 0.05)
.for_each(|block: &Block, pos| {
if block.is_explodable() {

View File

@ -22,8 +22,8 @@ use common::{
span,
state::{BlockChange, Time},
sync::Uid,
terrain::{Block, TerrainChunkSize, TerrainGrid},
vol::{RectVolSize, Vox},
terrain::{TerrainChunkSize, TerrainGrid},
vol::{ReadVol, RectVolSize},
};
use futures_executor::block_on;
use futures_timer::Delay;
@ -309,8 +309,8 @@ impl Sys {
_ => client.error_state(RequestStateError::Impossible),
},
ClientMsg::BreakBlock(pos) => {
if can_build.get(entity).is_some() {
block_changes.set(pos, Block::empty());
if let Some(block) = can_build.get(entity).and_then(|_| terrain.get(pos).ok()) {
block_changes.set(pos, block.into_vacant());
}
},
ClientMsg::PlaceBlock(pos, block) => {

View File

@ -1,7 +1,9 @@
use common::{
generation::{ChunkSupplement, EntityInfo},
terrain::{Block, BlockKind, MapSizeLg, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
vol::{ReadVol, RectVolSize, Vox, WriteVol},
terrain::{
Block, BlockKind, MapSizeLg, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize,
},
vol::{ReadVol, RectVolSize, WriteVol},
};
use rand::{prelude::*, rngs::SmallRng};
use std::time::Duration;
@ -60,7 +62,7 @@ impl World {
TerrainChunk::new(
256 + if rng.gen::<u8>() < 64 { height } else { 0 },
Block::new(BlockKind::Grass, Rgb::new(11, 102, 35)),
Block::empty(),
Block::air(SpriteKind::Empty),
TerrainChunkMeta::void(),
),
supplement,

View File

@ -1,6 +1,6 @@
use common::{
terrain::{Block, TerrainGrid},
vol::{SampleVol, Vox},
terrain::{Block, SpriteKind, TerrainGrid},
vol::SampleVol,
};
use criterion::{black_box, criterion_group, criterion_main, Benchmark, Criterion};
use std::sync::Arc;
@ -69,7 +69,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
"meshing",
Benchmark::new("copying 1,1 into flat array", move |b| {
b.iter(|| {
let mut flat = vec![Block::empty(); range.size().product() as usize];
let mut flat = vec![Block::air(SpriteKind::Empty); range.size().product() as usize];
let mut i = 0;
let mut volume = volume.cached();
for x in 0..range.size().w {

View File

@ -9,7 +9,7 @@ use common::{
span,
terrain::Block,
util::either_with,
vol::{ReadVol, RectRasterableVol, Vox},
vol::{ReadVol, RectRasterableVol},
volumes::vol_grid_2d::{CachedVolGrid2d, VolGrid2d},
};
use std::{collections::VecDeque, fmt::Debug};
@ -255,7 +255,12 @@ impl<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug>
let d = d + 2;
let flat = {
let mut volume = self.cached();
let mut flat = vec![Block::empty(); (w * h * d) as usize];
const AIR: Block = Block::air(common::terrain::sprite::SpriteKind::Empty);
// TODO: Once we can manage it sensibly, consider using something like
// Option<Block> instead of just assuming air.
let mut flat = vec![AIR; (w * h * d) as usize];
let mut i = 0;
for x in 0..range.size().w {
for y in 0..range.size().h {
@ -263,7 +268,9 @@ impl<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug>
let block = volume
.get(range.min + Vec3::new(x, y, z))
.map(|b| *b)
.unwrap_or(Block::empty());
// TODO: Replace with None or some other more reasonable value,
// since it's not clear this will work properly with liquid.
.unwrap_or(AIR);
if block.is_opaque() {
opaque_limits = opaque_limits
.map(|l| l.including(z))

View File

@ -1,7 +1,4 @@
use common::{
span,
vol::{ReadVol, Vox},
};
use common::{span, terrain::TerrainGrid, vol::ReadVol};
use std::f32::consts::PI;
use treeculler::Frustum;
use vek::*;
@ -80,7 +77,16 @@ impl Camera {
/// Compute the transformation matrices (view matrix and projection matrix)
/// and position of the camera.
pub fn compute_dependents(&mut self, terrain: &impl ReadVol) {
pub fn compute_dependents(&mut self, terrain: &TerrainGrid) {
self.compute_dependents_full(terrain, |block| !block.is_opaque())
}
/// The is_fluid argument should return true for transparent voxels.
pub fn compute_dependents_full<V: ReadVol>(
&mut self,
terrain: &V,
is_transparent: fn(&V::Vox) -> bool,
) {
span!(_guard, "compute_dependents", "Camera::compute_dependents");
let dist = {
let (start, end) = (self.focus - self.forward() * self.dist, self.focus);
@ -89,7 +95,7 @@ impl Camera {
.ray(start, end)
.ignore_error()
.max_iter(500)
.until(|b| b.is_empty())
.until(is_transparent)
.cast()
{
(d, Ok(Some(_))) => f32::min(self.dist - d - 0.03, self.dist),

View File

@ -22,27 +22,18 @@ use common::{
comp::{humanoid, item::ItemKind, Loadout},
figure::Segment,
terrain::BlockKind,
vol::{BaseVol, ReadVol, Vox},
vol::{BaseVol, ReadVol},
};
use tracing::error;
use vek::*;
#[derive(PartialEq, Eq, Copy, Clone)]
struct VoidVox;
impl Vox for VoidVox {
fn empty() -> Self { VoidVox }
fn is_empty(&self) -> bool { true }
fn or(self, _other: Self) -> Self { VoidVox }
}
struct VoidVol;
impl BaseVol for VoidVol {
type Error = ();
type Vox = VoidVox;
type Vox = ();
}
impl ReadVol for VoidVol {
fn get<'a>(&'a self, _pos: Vec3<i32>) -> Result<&'a Self::Vox, Self::Error> { Ok(&VoidVox) }
fn get<'a>(&'a self, _pos: Vec3<i32>) -> Result<&'a Self::Vox, Self::Error> { Ok(&()) }
}
fn generate_mesh<'a>(
@ -234,7 +225,7 @@ impl Scene {
scene_data.mouse_smoothing,
);
self.camera.compute_dependents(&VoidVol);
self.camera.compute_dependents_full(&VoidVol, |_| true);
let camera::Dependents {
view_mat,
proj_mat,

View File

@ -18,7 +18,7 @@ use common::{
span,
spiral::Spiral2d,
terrain::{sprite, Block, SpriteKind, TerrainChunk},
vol::{BaseVol, ReadVol, RectRasterableVol, SampleVol, Vox},
vol::{BaseVol, ReadVol, RectRasterableVol, SampleVol},
volumes::vol_grid_2d::{VolGrid2d, VolGrid2dError},
};
use core::{f32, fmt::Debug, i32, marker::PhantomData, time::Duration};
@ -145,7 +145,11 @@ fn mesh_worker<V: BaseVol<Vox = Block> + RectRasterableVol + ReadVol + Debug>(
let rel_pos = Vec3::new(x, y, z);
let wpos = Vec3::from(pos * V::RECT_SIZE.map(|e: u32| e as i32)) + rel_pos;
let block = volume.get(wpos).ok().copied().unwrap_or(Block::empty());
let block = if let Ok(block) = volume.get(wpos) {
block
} else {
continue;
};
let sprite = if let Some(sprite) = block.get_sprite() {
sprite
} else {

View File

@ -10,7 +10,7 @@ use common::{
structure::{self, StructureBlock},
Block, BlockKind, SpriteKind, Structure,
},
vol::{ReadVol, Vox},
vol::ReadVol,
};
use core::ops::{Div, Mul, Range};
use serde::Deserialize;
@ -164,7 +164,8 @@ impl<'a> BlockGen<'a> {
} = self;
let world = column_gen.sim;
let sample = &z_cache?.sample;
let z_cache = z_cache?;
let sample = &z_cache.sample;
let &ColumnSample {
alt,
basement,
@ -188,7 +189,7 @@ impl<'a> BlockGen<'a> {
..
} = sample;
let structures = &z_cache?.structures;
let structures = &z_cache.structures;
let wposf = wpos.map(|e| e as f64);
@ -330,7 +331,7 @@ impl<'a> BlockGen<'a> {
})
.or(block);
Some(block.unwrap_or_else(Block::empty))
block
}
}
@ -467,6 +468,8 @@ impl StructureInfo {
self.pos.into(),
self.seed,
sample,
// TODO: Take environment into account.
Block::air,
)
})
},
@ -481,49 +484,55 @@ pub fn block_from_structure(
structure_pos: Vec2<i32>,
structure_seed: u32,
sample: &ColumnSample,
mut with_sprite: impl FnMut(SpriteKind) -> Block,
) -> Option<Block> {
let field = RandomField::new(structure_seed);
let lerp = ((field.get(Vec3::from(structure_pos)).rem_euclid(256)) as f32 / 255.0) * 0.85
+ ((field.get(pos + std::i32::MAX / 2).rem_euclid(256)) as f32 / 255.0) * 0.15;
const EMPTY_SPRITE: Rgb<u8> = Rgb::new(SpriteKind::Empty as u8, 0, 0);
match sblock {
StructureBlock::None => None,
StructureBlock::Hollow => Some(Block::empty()),
StructureBlock::Hollow => Some(with_sprite(SpriteKind::Empty)),
StructureBlock::Grass => Some(Block::new(
BlockKind::Grass,
sample.surface_color.map(|e| (e * 255.0) as u8),
)),
StructureBlock::Normal(color) => {
Some(Block::new(BlockKind::Misc, color)).filter(|block| !block.is_empty())
},
// Water / sludge throw away their color bits currently, so we don't set anyway.
StructureBlock::Water => Some(Block::new(BlockKind::Water, Rgb::zero())),
StructureBlock::Normal(color) => Some(Block::new(BlockKind::Misc, color)),
StructureBlock::Water => Some(Block::new(BlockKind::Water, EMPTY_SPRITE)),
StructureBlock::GreenSludge => Some(Block::new(
BlockKind::Water,
// TODO: If/when liquid supports other colors again, revisit this.
Rgb::zero(),
BlockKind::Water,
EMPTY_SPRITE,
)),
// None of these BlockKinds has an orientation, so we just use zero for the other color
// bits.
StructureBlock::Liana => Some(Block::air(SpriteKind::Liana)),
StructureBlock::Fruit => Some(if field.get(pos + structure_pos) % 24 == 0 {
Block::air(SpriteKind::Beehive)
} else if field.get(pos + structure_pos + 1) % 3 == 0 {
Block::air(SpriteKind::Apple)
} else {
Block::empty()
}),
StructureBlock::Coconut => Some(if field.get(pos + structure_pos) % 3 > 0 {
Block::empty()
} else {
Block::air(SpriteKind::Coconut)
}),
StructureBlock::Chest => Some(if structure_seed % 10 < 7 {
Block::empty()
} else {
Block::air(SpriteKind::Chest)
}),
StructureBlock::Liana => Some(with_sprite(SpriteKind::Liana)),
StructureBlock::Fruit => {
if field.get(pos + structure_pos) % 24 == 0 {
Some(with_sprite(SpriteKind::Beehive))
} else if field.get(pos + structure_pos + 1) % 3 == 0 {
Some(with_sprite(SpriteKind::Apple))
} else {
None
}
},
StructureBlock::Coconut => {
if field.get(pos + structure_pos) % 3 > 0 {
None
} else {
Some(with_sprite(SpriteKind::Coconut))
}
},
StructureBlock::Chest => {
if structure_seed % 10 < 7 {
None
} else {
Some(with_sprite(SpriteKind::Chest))
}
},
// We interpolate all these BlockKinds as needed.
StructureBlock::TemperateLeaves
| StructureBlock::PineLeaves

View File

@ -13,7 +13,7 @@ use common::{
generation::{ChunkSupplement, EntityInfo},
lottery::Lottery,
terrain::{Block, BlockKind, SpriteKind},
vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol},
vol::{BaseVol, ReadVol, RectSizedVol, WriteVol},
};
use noise::NoiseFn;
use rand::prelude::*;
@ -30,6 +30,8 @@ pub struct Colors {
pub stalagtite: (u8, u8, u8),
}
const EMPTY_AIR: Block = Block::air(SpriteKind::Empty);
pub fn apply_paths_to<'a>(
wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
@ -113,7 +115,7 @@ pub fn apply_paths_to<'a>(
for z in inset..inset + head_space {
let pos = Vec3::new(offs.x, offs.y, surface_z + z);
if vol.get(pos).unwrap().kind() != BlockKind::Water {
let _ = vol.set(pos, Block::empty());
let _ = vol.set(pos, EMPTY_AIR);
}
}
}
@ -163,7 +165,7 @@ pub fn apply_caves_to<'a>(
.into_array(),
) < 0.0
{
let _ = vol.set(Vec3::new(offs.x, offs.y, z), Block::empty());
let _ = vol.set(Vec3::new(offs.x, offs.y, z), EMPTY_AIR);
}
}
@ -207,7 +209,8 @@ pub fn apply_caves_to<'a>(
}
#[allow(clippy::eval_order_dependence)]
pub fn apply_caves_supplement<'a>(
rng: &mut impl Rng,
// NOTE: Used only for dynamic elemens like chests and entities!
dynamic_rng: &mut impl Rng,
wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &(impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
@ -253,42 +256,42 @@ pub fn apply_caves_supplement<'a>(
wpos2d.y as f32,
cave_base as f32,
))
.with_body(match rng.gen_range(0, 6) {
.with_body(match dynamic_rng.gen_range(0, 6) {
0 => {
is_hostile = false;
let species = match rng.gen_range(0, 4) {
let species = match dynamic_rng.gen_range(0, 4) {
0 => comp::quadruped_small::Species::Truffler,
1 => comp::quadruped_small::Species::Dodarock,
2 => comp::quadruped_small::Species::Holladon,
_ => comp::quadruped_small::Species::Batfox,
};
comp::quadruped_small::Body::random_with(rng, &species).into()
comp::quadruped_small::Body::random_with(dynamic_rng, &species).into()
},
1 => {
is_hostile = true;
let species = match rng.gen_range(0, 5) {
let species = match dynamic_rng.gen_range(0, 5) {
0 => comp::quadruped_medium::Species::Tarasque,
_ => comp::quadruped_medium::Species::Bonerattler,
};
comp::quadruped_medium::Body::random_with(rng, &species).into()
comp::quadruped_medium::Body::random_with(dynamic_rng, &species).into()
},
2 => {
is_hostile = true;
let species = match rng.gen_range(0, 4) {
let species = match dynamic_rng.gen_range(0, 4) {
1 => comp::quadruped_low::Species::Rocksnapper,
_ => comp::quadruped_low::Species::Salamander,
};
comp::quadruped_low::Body::random_with(rng, &species).into()
comp::quadruped_low::Body::random_with(dynamic_rng, &species).into()
},
_ => {
is_hostile = true;
let species = match rng.gen_range(0, 8) {
let species = match dynamic_rng.gen_range(0, 8) {
0 => comp::biped_large::Species::Ogre,
1 => comp::biped_large::Species::Cyclops,
2 => comp::biped_large::Species::Wendigo,
_ => comp::biped_large::Species::Troll,
};
comp::biped_large::Body::random_with(rng, &species).into()
comp::biped_large::Body::random_with(dynamic_rng, &species).into()
},
})
.with_alignment(if is_hostile {

View File

@ -38,8 +38,8 @@ use common::{
comp::{self, bird_medium, quadruped_low, quadruped_medium, quadruped_small},
generation::{ChunkSupplement, EntityInfo},
msg::server::WorldMapMsg,
terrain::{Block, BlockKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
vol::{ReadVol, RectVolSize, Vox, WriteVol},
terrain::{Block, BlockKind, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
vol::{ReadVol, RectVolSize, WriteVol},
};
use rand::Rng;
use serde::Deserialize;
@ -114,7 +114,7 @@ impl World {
|offs| sampler.get_z_cache(chunk_wpos2d - grid_border + offs, index),
);
let air = Block::empty();
let air = Block::air(SpriteKind::Empty);
let stone = Block::new(
BlockKind::Rock,
zcache_grid
@ -195,7 +195,8 @@ impl World {
.map(|zc| &zc.sample)
};
let mut rng = rand::thread_rng();
// Only use for rng affecting dynamic elements like chests and entities!
let mut dynamic_rng = rand::thread_rng();
// Apply layers (paths, caves, etc.)
layer::apply_caves_to(chunk_wpos2d, sample_get, &mut chunk, index);
@ -207,17 +208,17 @@ impl World {
index.sites[*site].apply_to(index, chunk_wpos2d, sample_get, &mut chunk)
});
let gen_entity_pos = || {
let gen_entity_pos = |dynamic_rng: &mut rand::rngs::ThreadRng| {
let lpos2d = TerrainChunkSize::RECT_SIZE
.map(|sz| rand::thread_rng().gen::<u32>().rem_euclid(sz) as i32);
.map(|sz| dynamic_rng.gen::<u32>().rem_euclid(sz) as i32);
let mut lpos = Vec3::new(
lpos2d.x,
lpos2d.y,
sample_get(lpos2d).map(|s| s.alt as i32 - 32).unwrap_or(0),
);
while chunk.get(lpos).map(|vox| !vox.is_empty()).unwrap_or(false) {
lpos.z += 1;
while let Some(block) = chunk.get(lpos).ok().copied().filter(Block::is_solid) {
lpos.z += block.solid_height().ceil() as i32;
}
(Vec3::from(chunk_wpos2d) + lpos).map(|e: i32| e as f32) + 0.5
@ -225,18 +226,18 @@ impl World {
const SPAWN_RATE: f32 = 0.1;
let mut supplement = ChunkSupplement {
entities: if rng.gen::<f32>() < SPAWN_RATE
entities: if dynamic_rng.gen::<f32>() < SPAWN_RATE
&& sim_chunk.chaos < 0.5
&& !sim_chunk.is_underwater()
{
// TODO: REFACTOR: Define specific alignments in a config file instead of here
let is_hostile: bool;
let is_giant = rng.gen_range(0, 8) == 0;
let is_giant = dynamic_rng.gen_range(0, 8) == 0;
let quadmed = comp::Body::QuadrupedMedium(quadruped_medium::Body::random()); // Not all of them are hostile so we have to do the rng here
let quadlow = comp::Body::QuadrupedLow(quadruped_low::Body::random()); // Not all of them are hostile so we have to do the rng here
let entity = EntityInfo::at(gen_entity_pos())
let entity = EntityInfo::at(gen_entity_pos(&mut dynamic_rng))
.do_if(is_giant, |e| e.into_giant())
.with_body(match rng.gen_range(0, 5) {
.with_body(match dynamic_rng.gen_range(0, 5) {
0 => {
match quadmed {
comp::Body::QuadrupedMedium(quadruped_medium) => {
@ -292,12 +293,12 @@ impl World {
};
if sim_chunk.contains_waypoint {
supplement.add_entity(EntityInfo::at(gen_entity_pos()).into_waypoint());
supplement.add_entity(EntityInfo::at(gen_entity_pos(&mut dynamic_rng)).into_waypoint());
}
// Apply layer supplement
layer::apply_caves_supplement(
&mut rng,
&mut dynamic_rng,
chunk_wpos2d,
sample_get,
&chunk,
@ -307,7 +308,12 @@ impl World {
// Apply site supplementary information
sim_chunk.sites.iter().for_each(|site| {
index.sites[*site].apply_supplement(&mut rng, chunk_wpos2d, sample_get, &mut supplement)
index.sites[*site].apply_supplement(
&mut dynamic_rng,
chunk_wpos2d,
sample_get,
&mut supplement,
)
});
Ok((chunk, supplement))

View File

@ -1,27 +1,32 @@
use common::{terrain::Block, vol::Vox};
use common::terrain::Block;
#[derive(Copy, Clone)]
pub struct BlockMask {
block: Block,
block: Option<Block>,
priority: i32,
}
impl BlockMask {
pub fn new(block: Block, priority: i32) -> Self { Self { block, priority } }
pub fn nothing() -> Self {
pub const fn new(block: Block, priority: i32) -> Self {
Self {
block: Block::empty(),
block: Some(block),
priority,
}
}
pub const fn nothing() -> Self {
Self {
block: None,
priority: 0,
}
}
pub fn with_priority(mut self, priority: i32) -> Self {
pub const fn with_priority(mut self, priority: i32) -> Self {
self.priority = priority;
self
}
pub fn resolve_with(self, other: Self) -> Self {
pub const fn resolve_with(self, other: Self) -> Self {
if self.priority >= other.priority {
self
} else {
@ -29,11 +34,5 @@ impl BlockMask {
}
}
pub fn finish(self) -> Option<Block> {
if self.priority > 0 {
Some(self.block)
} else {
None
}
}
pub const fn finish(self) -> Option<Block> { self.block }
}

View File

@ -10,8 +10,8 @@ use crate::{
};
use common::{
generation::ChunkSupplement,
terrain::{Block, BlockKind},
vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol},
terrain::{Block, BlockKind, SpriteKind},
vol::{BaseVol, ReadVol, RectSizedVol, WriteVol},
};
use core::f32;
use rand::prelude::*;
@ -202,7 +202,8 @@ impl Castle {
if z > 0 {
if vol.get(pos).unwrap().kind() != BlockKind::Water {
let _ = vol.set(pos, Block::empty());
// TODO: Take environment into account.
let _ = vol.set(pos, Block::air(SpriteKind::Empty));
}
} else {
let _ = vol.set(
@ -415,7 +416,8 @@ impl Castle {
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
pub fn apply_supplement<'a>(
&'a self,
_rng: &mut impl Rng,
// NOTE: Used only for dynamic elemens like chests and entities!
_dynamic_rng: &mut impl Rng,
_wpos2d: Vec2<i32>,
_get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
_supplement: &mut ChunkSupplement,

View File

@ -15,7 +15,7 @@ use common::{
lottery::Lottery,
store::{Id, Store},
terrain::{Block, BlockKind, SpriteKind, Structure, TerrainChunkSize},
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol},
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, WriteVol},
};
use core::{f32, hash::BuildHasherDefault};
use fxhash::FxHasher64;
@ -127,6 +127,8 @@ impl Dungeon {
self.origin,
self.seed,
col_sample,
// TODO: Take environment into account.
Block::air,
)
})
.unwrap_or(None)
@ -140,7 +142,13 @@ impl Dungeon {
for floor in &self.floors {
z -= floor.total_depth();
let mut sampler = floor.col_sampler(index, rpos, z);
let mut sampler = floor.col_sampler(
index,
rpos,
z,
// TODO: Take environment into account.
Block::air,
);
for rz in 0..floor.total_depth() {
if let Some(block) = sampler(rz).finish() {
@ -155,7 +163,8 @@ impl Dungeon {
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
pub fn apply_supplement<'a>(
&'a self,
rng: &mut impl Rng,
// NOTE: Used only for dynamic elemens like chests and entities!
dynamic_rng: &mut impl Rng,
wpos2d: Vec2<i32>,
_get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
supplement: &mut ChunkSupplement,
@ -170,7 +179,7 @@ impl Dungeon {
for floor in &self.floors {
z -= floor.total_depth();
let origin = Vec3::new(self.origin.x, self.origin.y, z);
floor.apply_supplement(rng, area, origin, supplement);
floor.apply_supplement(dynamic_rng, area, origin, supplement);
}
}
}
@ -208,7 +217,7 @@ pub struct Room {
pillars: Option<i32>, // Pillars with the given separation
}
pub struct Floor {
struct Floor {
tile_offset: Vec2<i32>,
tiles: Grid<Tile>,
rooms: Store<Room>,
@ -222,7 +231,7 @@ pub struct Floor {
const FLOOR_SIZE: Vec2<i32> = Vec2::new(18, 18);
impl Floor {
pub fn generate(
fn generate(
ctx: &mut GenCtx<impl Rng>,
stair_tile: Vec2<i32>,
level: i32,
@ -406,9 +415,10 @@ impl Floor {
}
#[allow(clippy::match_single_binding)] // TODO: Pending review in #587
pub fn apply_supplement(
fn apply_supplement(
&self,
rng: &mut impl Rng,
// NOTE: Used only for dynamic elemens like chests and entities!
dynamic_rng: &mut impl Rng,
area: Aabr<i32>,
origin: Vec3<i32>,
supplement: &mut ChunkSupplement,
@ -417,9 +427,12 @@ impl Floor {
Vec3::from((self.stair_tile + self.tile_offset).map(|e| e * TILE_SIZE + TILE_SIZE / 2));
if area.contains_point(stair_rcenter.xy()) {
let offs = Vec2::new(rng.gen_range(-1.0, 1.0), rng.gen_range(-1.0, 1.0))
.try_normalized()
.unwrap_or_else(Vec2::unit_y)
let offs = Vec2::new(
dynamic_rng.gen_range(-1.0, 1.0),
dynamic_rng.gen_range(-1.0, 1.0),
)
.try_normalized()
.unwrap_or_else(Vec2::unit_y)
* (TILE_SIZE as f32 / 2.0 - 4.0);
if !self.final_level {
supplement.add_entity(
@ -453,16 +466,17 @@ impl Floor {
if room
.enemy_density
.map(|density| rng.gen_range(0, density.recip() as usize) == 0)
.map(|density| dynamic_rng.gen_range(0, density.recip() as usize) == 0)
.unwrap_or(false)
&& !tile_is_pillar
{
// Bad
let chosen = Lottery::<String>::load_expect(match rng.gen_range(0, 5) {
0 => "common.loot_tables.loot_table_humanoids",
1 => "common.loot_tables.loot_table_armor_misc",
_ => "common.loot_tables.loot_table_cultists",
});
let chosen =
Lottery::<String>::load_expect(match dynamic_rng.gen_range(0, 5) {
0 => "common.loot_tables.loot_table_humanoids",
1 => "common.loot_tables.loot_table_armor_misc",
_ => "common.loot_tables.loot_table_cultists",
});
let chosen = chosen.choose();
let entity = EntityInfo::at(
tile_wcenter.map(|e| e as f32)
@ -476,7 +490,7 @@ impl Floor {
.with_body(comp::Body::Humanoid(comp::humanoid::Body::random()))
.with_automatic_name()
.with_loot_drop(comp::Item::new_from_asset_expect(chosen))
.with_main_tool(comp::Item::new_from_asset_expect(match rng.gen_range(0, 6) {
.with_main_tool(comp::Item::new_from_asset_expect(match dynamic_rng.gen_range(0, 6) {
0 => "common.items.npc_weapons.axe.malachite_axe-0",
1 => "common.items.npc_weapons.sword.cultist_purp_2h-0",
2 => "common.items.npc_weapons.sword.cultist_purp_2h-0",
@ -508,10 +522,10 @@ impl Floor {
);
let chosen = chosen.choose();
let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_level(rng.gen_range(1, 5))
.with_level(dynamic_rng.gen_range(1, 5))
.with_alignment(comp::Alignment::Enemy)
.with_body(comp::Body::Golem(comp::golem::Body::random_with(
rng,
dynamic_rng,
&comp::golem::Species::StoneGolem,
)))
.with_name("Stonework Defender".to_string())
@ -525,9 +539,9 @@ impl Floor {
}
}
pub fn total_depth(&self) -> i32 { self.solid_depth + self.hollow_depth }
fn total_depth(&self) -> i32 { self.solid_depth + self.hollow_depth }
pub fn nearest_wall(&self, rpos: Vec2<i32>) -> Option<Vec2<i32>> {
fn nearest_wall(&self, rpos: Vec2<i32>) -> Option<Vec2<i32>> {
let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE));
DIRS.iter()
@ -548,11 +562,12 @@ impl Floor {
}
#[allow(clippy::unnested_or_patterns)] // TODO: Pending review in #587
pub fn col_sampler<'a>(
fn col_sampler<'a>(
&'a self,
index: IndexRef<'a>,
pos: Vec2<i32>,
floor_z: i32,
mut with_sprite: impl FnMut(SpriteKind) -> Block,
) -> impl FnMut(i32) -> BlockMask + 'a {
let rpos = pos - self.tile_offset * TILE_SIZE;
let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE));
@ -561,7 +576,7 @@ impl Floor {
let colors = &index.colors.site.dungeon;
let empty = BlockMask::new(Block::empty(), 1);
let vacant = BlockMask::new(with_sprite(SpriteKind::Empty), 1);
let make_staircase = move |pos: Vec3<i32>, radius: f32, inner_radius: f32, stretch: f32| {
let stone = BlockMask::new(Block::new(BlockKind::Rock, colors.stone.into()), 5);
@ -576,7 +591,7 @@ impl Floor {
{
stone
} else {
empty
vacant
}
} else {
BlockMask::nothing()
@ -593,7 +608,7 @@ impl Floor {
let floor_sprite = if RandomField::new(7331).chance(Vec3::from(pos), 0.00005) {
BlockMask::new(
Block::air(
with_sprite(
match (RandomField::new(1337).get(Vec3::from(pos)) / 2) % 20 {
0 => SpriteKind::Apple,
1 => SpriteKind::VeloriteFrag,
@ -609,12 +624,12 @@ impl Floor {
{
let room = &self.rooms[*room];
if RandomField::new(room.seed).chance(Vec3::from(pos), room.loot_density * 0.5) {
BlockMask::new(Block::air(SpriteKind::Chest), 1)
BlockMask::new(with_sprite(SpriteKind::Chest), 1)
} else {
empty
vacant
}
} else {
empty
vacant
};
let tunnel_height = if self.final_level { 16.0 } else { 8.0 };
@ -625,7 +640,7 @@ impl Floor {
if dist_to_wall >= wall_thickness
&& (z as f32) < tunnel_height * (1.0 - tunnel_dist.powf(4.0))
{
if z == 0 { floor_sprite } else { empty }
if z == 0 { floor_sprite } else { vacant }
} else {
BlockMask::nothing()
}
@ -651,12 +666,12 @@ impl Floor {
if z == 0 {
floor_sprite
} else {
empty
vacant
}
},
Some(Tile::DownStair(_)) => {
make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), 0.0, 0.5, 9.0)
.resolve_with(empty)
.resolve_with(vacant)
},
Some(Tile::UpStair(room)) => {
let mut block = make_staircase(
@ -666,7 +681,7 @@ impl Floor {
9.0,
);
if z < self.rooms[*room].height {
block = block.resolve_with(empty);
block = block.resolve_with(vacant);
}
block
},

View File

@ -108,15 +108,18 @@ impl Site {
pub fn apply_supplement<'a>(
&'a self,
rng: &mut impl Rng,
// NOTE: Used only for dynamic elemens like chests and entities!
dynamic_rng: &mut impl Rng,
wpos2d: Vec2<i32>,
get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
supplement: &mut ChunkSupplement,
) {
match &self.kind {
SiteKind::Settlement(s) => s.apply_supplement(rng, wpos2d, get_column, supplement),
SiteKind::Dungeon(d) => d.apply_supplement(rng, wpos2d, get_column, supplement),
SiteKind::Castle(c) => c.apply_supplement(rng, wpos2d, get_column, supplement),
SiteKind::Settlement(s) => {
s.apply_supplement(dynamic_rng, wpos2d, get_column, supplement)
},
SiteKind::Dungeon(d) => d.apply_supplement(dynamic_rng, wpos2d, get_column, supplement),
SiteKind::Castle(c) => c.apply_supplement(dynamic_rng, wpos2d, get_column, supplement),
}
}
}

View File

@ -9,7 +9,6 @@ use crate::{
use common::{
make_case_elim,
terrain::{Block, BlockKind, SpriteKind},
vol::Vox,
};
use rand::prelude::*;
use serde::Deserialize;
@ -293,8 +292,9 @@ impl Archetype for House {
let floor = make_block(colors.floor);
let wall = make_block(wall_color).with_priority(facade_layer);
let roof = make_block(roof_color).with_priority(facade_layer - 1);
let empty = BlockMask::nothing();
let internal = BlockMask::new(Block::empty(), internal_layer);
const EMPTY: BlockMask = BlockMask::nothing();
// TODO: Take environment into account.
let internal = BlockMask::new(Block::air(SpriteKind::Empty), internal_layer);
let end_window = BlockMask::new(
Block::air(attr.window)
.with_ori(match ori {
@ -399,7 +399,7 @@ impl Archetype for House {
let roof_level = roof_top - roof_profile.x.max(mansard);
if profile.y > roof_level {
return None;
return EMPTY;
}
// Roof
@ -409,9 +409,9 @@ impl Archetype for House {
if (roof_profile.x == 0 && mansard == 0) || roof_dist == width + 2 || is_ribbing
{
// Eaves
return Some(log);
return log;
} else {
return Some(roof);
return roof;
}
}
@ -427,44 +427,42 @@ impl Archetype for House {
&& attr.storey_fill.has_lower()
&& storey == 0
{
return Some(
if (bound_offset.x == (width - 1) / 2
|| bound_offset.x == (width - 1) / 2 + 1)
&& profile.y <= foundation_height + 3
{
// Doors on first floor only
if profile.y == foundation_height + 1 {
BlockMask::new(
Block::air(SpriteKind::Door)
.with_ori(
match ori {
Ori::East => 2,
Ori::North => 0,
} + if bound_offset.x == (width - 1) / 2 {
0
} else {
4
},
)
.unwrap(),
structural_layer,
)
} else {
empty.with_priority(structural_layer)
}
return if (bound_offset.x == (width - 1) / 2
|| bound_offset.x == (width - 1) / 2 + 1)
&& profile.y <= foundation_height + 3
{
// Doors on first floor only
if profile.y == foundation_height + 1 {
BlockMask::new(
Block::air(SpriteKind::Door)
.with_ori(
match ori {
Ori::East => 2,
Ori::North => 0,
} + if bound_offset.x == (width - 1) / 2 {
0
} else {
4
},
)
.unwrap(),
structural_layer,
)
} else {
wall
},
);
EMPTY.with_priority(structural_layer)
}
} else {
wall
};
}
if bound_offset.x == bound_offset.y || profile.y == ceil_height {
// Support beams
return Some(log);
return log;
} else if !attr.storey_fill.has_lower() && profile.y < ceil_height {
return Some(empty);
return EMPTY;
} else if !attr.storey_fill.has_upper() {
return Some(empty);
return EMPTY;
} else {
let (frame_bounds, frame_borders) = if profile.y >= ceil_height {
(
@ -495,19 +493,19 @@ impl Archetype for House {
// Window frame is large enough for a window
let surface_pos = Vec2::new(bound_offset.x, profile.y);
if window_bounds.contains_point(surface_pos) {
return Some(end_window);
return end_window;
} else if frame_bounds.contains_point(surface_pos) {
return Some(log.with_priority(structural_layer));
return log.with_priority(structural_layer);
};
}
// Wall
return Some(if attr.central_supports && profile.x == 0 {
return if attr.central_supports && profile.x == 0 {
// Support beams
log.with_priority(structural_layer)
} else {
wall
});
};
}
}
@ -516,15 +514,15 @@ impl Archetype for House {
if profile.y == ceil_height {
if profile.x == 0 {
// Rafters
return Some(log);
return log;
} else if attr.storey_fill.has_upper() {
// Ceiling
return Some(floor);
return floor;
}
} else if (!attr.storey_fill.has_lower() && profile.y < ceil_height)
|| (!attr.storey_fill.has_upper() && profile.y >= ceil_height)
{
return Some(empty);
return EMPTY;
// Furniture
} else if dist == width - 1
&& center_offset.sum() % 2 == 0
@ -533,7 +531,8 @@ impl Archetype for House {
.noise
.chance(Vec3::new(center_offset.x, center_offset.y, z), 0.2)
{
let mut rng = rand::thread_rng();
// NOTE: Used only for dynamic elemens like chests and entities!
let mut dynamic_rng = rand::thread_rng();
let furniture = match self.noise.get(Vec3::new(
center_offset.x,
center_offset.y,
@ -545,7 +544,7 @@ impl Archetype for House {
2 => SpriteKind::ChairDouble,
3 => SpriteKind::CoatRack,
4 => {
if rng.gen_range(0, 8) == 0 {
if dynamic_rng.gen_range(0, 8) == 0 {
SpriteKind::Chest
} else {
SpriteKind::Crate
@ -558,12 +557,12 @@ impl Archetype for House {
_ => SpriteKind::Pot,
};
return Some(BlockMask::new(
return BlockMask::new(
Block::air(furniture).with_ori(edge_ori).unwrap(),
internal_layer,
));
);
} else {
return Some(internal);
return internal;
}
}
@ -587,38 +586,30 @@ impl Archetype for House {
_ => SpriteKind::DungeonWallDecor,
};
Some(BlockMask::new(
BlockMask::new(
Block::air(ornament).with_ori((edge_ori + 4) % 8).unwrap(),
internal_layer,
))
)
} else {
None
EMPTY
}
};
let mut cblock = empty;
if let Some(block) =
do_roof_wall(profile, width, dist, bound_offset, roof_top, attr.mansard)
{
cblock = cblock.resolve_with(block);
}
let mut cblock = do_roof_wall(profile, width, dist, bound_offset, roof_top, attr.mansard);
if let Pillar::Tower(tower_height) = attr.pillar {
let tower_top = roof_top + tower_height;
let profile = Vec2::new(center_offset.x.abs(), profile.y);
let dist = center_offset.map(|e| e.abs()).reduce_max();
if let Some(block) = do_roof_wall(
cblock = cblock.resolve_with(do_roof_wall(
profile,
4,
dist,
center_offset.map(|e| e.abs()),
tower_top,
attr.mansard,
) {
cblock = cblock.resolve_with(block);
}
));
}
cblock

View File

@ -7,7 +7,6 @@ use crate::{
use common::{
make_case_elim,
terrain::{Block, BlockKind, SpriteKind},
vol::Vox,
};
use rand::prelude::*;
use serde::Deserialize;
@ -168,8 +167,9 @@ impl Archetype for Keep {
make_block(colors.pole.0, colors.pole.1, colors.pole.2).with_priority(important_layer);
let flag =
make_block(flag_color.0, flag_color.1, flag_color.2).with_priority(important_layer);
let internal = BlockMask::new(Block::empty(), internal_layer);
let empty = BlockMask::nothing();
const AIR: Block = Block::air(SpriteKind::Empty);
const EMPTY: BlockMask = BlockMask::nothing();
let internal = BlockMask::new(AIR, internal_layer);
let make_staircase = move |pos: Vec3<i32>, radius: f32, inner_radius: f32, stretch: f32| {
let stone = BlockMask::new(Block::new(BlockKind::Rock, dungeon_stone.into()), 5);
@ -187,7 +187,7 @@ impl Archetype for Keep {
internal
}
} else {
BlockMask::nothing()
EMPTY
}
};
@ -255,21 +255,21 @@ impl Archetype for Keep {
{
flag
} else {
empty
EMPTY
}
} else if min_dist <= rampart_width {
if profile.y < rampart_height {
wall
} else {
empty
EMPTY
}
} else {
empty
EMPTY
}
} else if profile.y < roof_height && min_dist < width {
internal
} else {
empty
EMPTY
}
.resolve_with(
if attr.is_tower && profile.y > 0 && profile.y <= roof_height {
@ -280,7 +280,7 @@ impl Archetype for Keep {
9.0,
)
} else {
BlockMask::nothing()
EMPTY
},
)
}

View File

@ -20,7 +20,7 @@ use common::{
spiral::Spiral2d,
store::{Id, Store},
terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol},
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, WriteVol},
};
use fxhash::FxHasher64;
use hashbrown::{HashMap, HashSet};
@ -726,25 +726,26 @@ impl Settlement {
for z in -8 - diff..4 + diff {
let pos = Vec3::new(offs.x, offs.y, surface_z + z);
let block = vol.get(pos).ok().copied().unwrap_or_else(Block::empty);
if block.is_empty() {
let block = if let Ok(&block) = vol.get(pos) {
// TODO: Figure out whether extra filters are needed.
block
} else {
break;
}
};
if let (0, Some(sprite)) = (z, surface_sprite) {
let _ = vol.set(
pos,
// TODO: Make more principled.
if block.is_fluid() {
block
block.with_sprite(sprite)
} else {
Block::empty()
}
.with_sprite(sprite),
Block::air(sprite)
},
);
} else if z >= 0 {
if block.kind() != BlockKind::Water {
let _ = vol.set(pos, Block::empty());
let _ = vol.set(pos, Block::air(SpriteKind::Empty));
}
} else {
let _ = vol.set(
@ -835,7 +836,8 @@ impl Settlement {
#[allow(clippy::eval_order_dependence)] // TODO: Pending review in #587
pub fn apply_supplement<'a>(
&'a self,
rng: &mut impl Rng,
// NOTE: Used only for dynamic elemens like chests and entities!
dynamic_rng: &mut impl Rng,
wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
supplement: &mut ChunkSupplement,
@ -865,24 +867,25 @@ impl Settlement {
let is_dummy =
RandomField::new(self.seed + 1).chance(Vec3::from(wpos2d), 1.0 / 15.0);
let entity = EntityInfo::at(entity_wpos)
.with_body(match rng.gen_range(0, 4) {
.with_body(match dynamic_rng.gen_range(0, 4) {
_ if is_dummy => {
is_human = false;
object::Body::TrainingDummy.into()
},
0 => {
let species = match rng.gen_range(0, 3) {
let species = match dynamic_rng.gen_range(0, 3) {
0 => quadruped_small::Species::Pig,
1 => quadruped_small::Species::Sheep,
_ => quadruped_small::Species::Cat,
};
is_human = false;
comp::Body::QuadrupedSmall(quadruped_small::Body::random_with(
rng, &species,
dynamic_rng,
&species,
))
},
1 => {
let species = match rng.gen_range(0, 4) {
let species = match dynamic_rng.gen_range(0, 4) {
0 => bird_medium::Species::Duck,
1 => bird_medium::Species::Chicken,
2 => bird_medium::Species::Goose,
@ -890,7 +893,8 @@ impl Settlement {
};
is_human = false;
comp::Body::BirdMedium(bird_medium::Body::random_with(
rng, &species,
dynamic_rng,
&species,
))
},
_ => {
@ -906,9 +910,9 @@ impl Settlement {
} else {
comp::Alignment::Tame
})
.do_if(is_human && rng.gen(), |entity| {
.do_if(is_human && dynamic_rng.gen(), |entity| {
entity.with_main_tool(Item::new_from_asset_expect(
match rng.gen_range(0, 7) {
match dynamic_rng.gen_range(0, 7) {
0 => "common.items.npc_weapons.tool.broom",
1 => "common.items.npc_weapons.tool.hoe",
2 => "common.items.npc_weapons.tool.pickaxe",