1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
use alloc::sync::Arc;
use core::ops;
// Things mentioned in doc comments only
#[cfg(doc)]
use crate::block::{Primitive, AIR};
use crate::block::{
BlockAttributes, BlockCollision, EvaluatedBlock,
Resolution::{self, R1},
};
use crate::math::{Cube, GridAab, Rgb, Rgba, Vol};
/// Properties of an individual voxel within [`EvaluatedBlock`].
///
/// This is essentially a subset of the information in a full [`EvaluatedBlock`] and
/// its [`BlockAttributes`].
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub struct Evoxel {
// Note: documentation wording here should match [`BlockAttributes`]
/// Diffuse reflection color.
// TODO: Maybe we should convert to a smaller color format at this point?
// These are frequently going to be copied into 32-bit texture color anyway.
pub color: Rgba,
/// Light emitted (not reflected) by the voxel.
///
/// This quantity is the [_luminance_](https://en.wikipedia.org/wiki/Luminance) of
/// the block surface, in unspecified units where 1.0 is the display white level
/// (except for the effects of tone mapping).
/// In the future this may be redefined in terms of a physical unit, but with the same
/// dimensions.
///
/// TODO: Define the interpretation for non-opaque voxels.
pub emission: Rgb,
/// Whether players' [cursors](crate::character::Cursor) target this voxel's containing
/// block or pass through it.
pub selectable: bool,
/// The effect on a [`Body`](crate::physics::Body) of colliding with this voxel.
pub collision: BlockCollision,
}
impl Evoxel {
/// The `Evoxel` value that is contained in [`AIR`].
///
/// This also represents the behavior of the empty space outside the bounds of
/// an [`EvaluatedBlock::voxels`] that is smaller than the full unit cube.
pub const AIR: Self = Self {
color: Rgba::TRANSPARENT,
emission: Rgb::ZERO,
selectable: false,
collision: BlockCollision::None,
};
/// Construct an [`Evoxel`] which represents the given evaluated block.
///
/// This is the same operation as is used for each block/voxel in a [`Primitive::Recur`].
pub fn from_block(block: &EvaluatedBlock) -> Self {
Self {
color: block.color,
emission: block.light_emission,
selectable: block.attributes.selectable,
// TODO: This won't generalize properly to having more than 2 states of
// BlockCollision. We need uniform_collision to carry more info.
collision: block.uniform_collision.unwrap_or(BlockCollision::Hard),
}
}
/// Construct the [`Evoxel`] that would have resulted from evaluating a voxel block
/// with the given color and default attributes.
pub const fn from_color(color: Rgba) -> Self {
Self {
color,
emission: Rgb::ZERO,
// Use the selectable value from BlockAttributes's default for consistency.
// with the result of a default atom block.
selectable: BlockAttributes::DEFAULT_REF.selectable,
collision: BlockCollision::DEFAULT_FOR_FROM_COLOR,
}
}
}
/// Storage of an [`EvaluatedBlock`]'s shape — its _evaluated voxels._
///
/// This voxel data may be smaller than the dimensions implied by [`Self::resolution`],
/// in which case the out-of-bounds space should be treated as [`Evoxel::AIR`].
/// The logical bounds are always the cube computed by [`GridAab::for_block`].
///
/// This improves on a `Vol<Arc<[Evoxel]>>` by avoiding heap allocation and indirection
/// for the case of a single element, and by returning voxels by value rather than
/// reference.
///
/// TODO: Make this opaque instead of an enum; replace all matching on `One` vs. `Many`
/// with calls to [`Self::single_voxel()`] or similar. This will:
///
/// * allow ensuring consistent input (no out-of-bounds data, not using `Many` for one)
/// * allow more compact representations (e.g. when all voxels are solid+selectable)
/// * ensure there is no inappropriate dependence on the representation
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum Evoxels {
/// Compact representation of exactly one voxel. The resolution is implicitly 1.
One(Evoxel),
/// The [`Vol`] should not have any data outside of the expected bounds
/// `GridAab::for_block(resolution)`, but may have less.
Many(Resolution, Vol<Arc<[Evoxel]>>),
}
impl Evoxels {
/// Returns the resolution (scale factor) of this set of voxels.
/// See [`Resolution`] for more information.
#[inline]
pub fn resolution(&self) -> Resolution {
match *self {
Evoxels::One(_) => R1,
Evoxels::Many(resolution, _) => resolution,
}
}
/// Returns the count of voxels, aka [`Vol::volume()`] at the resolution.
pub fn count(&self) -> usize {
match self {
Evoxels::One(_) => 1,
Evoxels::Many(_, voxels) => voxels.volume(),
}
}
/// If this has a resolution of 1, then return that single voxel.
#[inline]
pub fn single_voxel(&self) -> Option<Evoxel> {
match *self {
Evoxels::One(v) => Some(v),
Evoxels::Many(R1, ref voxels) => {
Some(voxels.get([0, 0, 0]).copied().unwrap_or(Evoxel::AIR))
}
Evoxels::Many(_, _) => None,
}
}
/// Returns a [`Vol`] borrowing these voxels.
pub fn as_vol_ref(&self) -> Vol<&[Evoxel]> {
match self {
Evoxels::One(voxel) => {
Vol::from_elements(GridAab::ORIGIN_CUBE, core::slice::from_ref(voxel)).unwrap()
}
Evoxels::Many(_, voxels) => voxels.as_ref(),
}
}
/// Returns a [`Vol`] mutably borrowing these voxels.
pub fn as_vol_mut(&mut self) -> Vol<&mut [Evoxel]> {
match self {
Evoxels::One(voxel) => {
Vol::from_elements(GridAab::ORIGIN_CUBE, core::slice::from_mut(voxel)).unwrap()
}
Evoxels::Many(_, voxels) => {
Vol::from_elements(voxels.bounds(), voxels.make_linear_mut()).unwrap()
}
}
}
/// Get the single voxel at the specified position, or [`None`] if the position is
/// out of bounds of the data (which is not necessarily out of bounds of the block;
/// missing data should be taken as [`Evoxel::AIR`]).
///
/// Generally behaves like [`Vol::get()`].
///
/// TODO: Should we inherently return AIR instead of None?
#[inline]
pub fn get(&self, position: Cube) -> Option<Evoxel> {
match (self, position) {
(&Evoxels::One(voxel), Cube::ORIGIN) => Some(voxel),
(Evoxels::One(_), _) => None,
(Evoxels::Many(_, ref voxels), position) => voxels.get(position).copied(),
}
}
/// Returns the bounds of the voxel data.
#[inline]
pub fn bounds(&self) -> GridAab {
match *self {
Evoxels::One(_) => GridAab::ORIGIN_CUBE,
Evoxels::Many(_, ref voxels) => voxels.bounds(),
}
}
#[cfg(debug_assertions)]
pub(crate) fn consistency_check(&self) {
let allowed_bounds = GridAab::for_block(self.resolution());
let actual_bounds = self.bounds();
assert!(
allowed_bounds.contains_box(actual_bounds),
"Evoxels contains out of bounds voxels {actual_bounds:?} \
within allowed {allowed_bounds:?}"
);
}
}
impl ops::Index<Cube> for Evoxels {
type Output = Evoxel;
#[inline]
#[track_caller]
fn index(&self, position: Cube) -> &Self::Output {
match (self, position) {
(Evoxels::One(voxel), Cube::ORIGIN) => voxel,
(Evoxels::One(_), _) => panic!("out of bounds of Evoxels::One"),
(Evoxels::Many(_, voxels), position) => &voxels[position],
}
}
}
#[cfg(feature = "arbitrary")]
#[mutants::skip]
impl<'a> arbitrary::Arbitrary<'a> for Evoxels {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let resolution = Resolution::arbitrary(u)?;
Ok(if resolution == R1 {
Evoxels::One(u.arbitrary()?)
} else {
// TODO: limit array bounds to the resolution
Evoxels::Many(resolution, Vol::arbitrary(u)?)
})
}
fn size_hint(depth: usize) -> (usize, Option<usize>) {
arbitrary::size_hint::and_all(&[
Resolution::size_hint(depth),
arbitrary::size_hint::or(
Evoxel::size_hint(depth),
Vol::<Arc<[Evoxel]>>::size_hint(depth),
),
])
}
}