use cgmath::EuclideanSpace as _;
use std::borrow::Cow;
use std::sync::Arc;
use crate::block::{
AnimationHint, Block, BlockAttributes, BlockCollision, BlockDef, BlockParts, BlockPtr,
Modifier, Primitive, Resolution, RotationPlacementRule,
};
use crate::drawing::VoxelBrush;
use crate::math::{GridPoint, Rgb, Rgba};
use crate::space::{SetCubeError, Space};
use crate::universe::{Name, URef, Universe, UniverseIndex};
#[derive(Clone, Debug, Eq, PartialEq)]
#[must_use]
pub struct BlockBuilder<P> {
attributes: BlockAttributes,
primitive_builder: P,
modifiers: Vec<Modifier>,
}
impl Default for BlockBuilder<NeedsPrimitive> {
fn default() -> Self {
Self::new()
}
}
impl BlockBuilder<NeedsPrimitive> {
pub(super) const fn new() -> BlockBuilder<NeedsPrimitive> {
BlockBuilder {
attributes: BlockAttributes::default(),
primitive_builder: NeedsPrimitive,
modifiers: Vec::new(),
}
}
}
impl<C> BlockBuilder<C> {
pub fn attributes(mut self, value: BlockAttributes) -> Self {
self.attributes = value;
self
}
pub fn display_name(mut self, value: impl Into<Cow<'static, str>>) -> Self {
self.attributes.display_name = value.into();
self
}
pub const fn selectable(mut self, value: bool) -> Self {
self.attributes.selectable = value;
self
}
pub const fn collision(mut self, value: BlockCollision) -> Self {
self.attributes.collision = value;
self
}
pub const fn rotation_rule(mut self, value: RotationPlacementRule) -> Self {
self.attributes.rotation_rule = value;
self
}
pub fn light_emission(mut self, value: impl Into<Rgb>) -> Self {
self.attributes.light_emission = value.into();
self
}
pub fn tick_action(mut self, value: Option<VoxelBrush<'static>>) -> Self {
self.attributes.tick_action = value;
self
}
pub fn animation_hint(mut self, value: AnimationHint) -> Self {
self.attributes.animation_hint = value;
self
}
pub fn modifier(mut self, modifier: Modifier) -> Self {
self.modifiers.push(modifier);
self
}
pub fn color(self, color: impl Into<Rgba>) -> BlockBuilder<Rgba> {
BlockBuilder {
attributes: self.attributes,
primitive_builder: color.into(),
modifiers: Vec::new(),
}
}
pub fn voxels_ref(
self,
resolution: Resolution,
space: URef<Space>,
) -> BlockBuilder<BlockBuilderVoxels> {
BlockBuilder {
attributes: self.attributes,
primitive_builder: BlockBuilderVoxels {
space,
resolution,
offset: GridPoint::origin(),
},
modifiers: Vec::new(),
}
}
pub fn voxels_fn<F, B>(
self,
universe: &mut Universe,
resolution: Resolution,
mut function: F,
) -> Result<BlockBuilder<BlockBuilderVoxels>, SetCubeError>
where
F: FnMut(GridPoint) -> B,
B: std::borrow::Borrow<Block>,
{
let mut space = Space::for_block(resolution).build();
space.fill(space.bounds(), |point| Some(function(point)))?;
Ok(self.voxels_ref(resolution, universe.insert_anonymous(space)))
}
pub fn build(self) -> Block
where
C: BuildPrimitiveIndependent,
{
Block(BlockPtr::Owned(Arc::new(BlockParts {
primitive: self.primitive_builder.build_i(self.attributes),
modifiers: self.modifiers,
})))
}
pub fn into_named_definition(
self,
universe: &mut Universe,
name: impl Into<Name>,
) -> Result<Block, crate::universe::InsertError>
where
C: BuildPrimitiveInUniverse,
{
let block = Block(BlockPtr::Owned(Arc::new(BlockParts {
primitive: self.primitive_builder.build_u(self.attributes, universe),
modifiers: self.modifiers,
})));
let def_ref = universe.insert(name.into(), BlockDef::new(block))?;
Ok(Block::from_primitive(Primitive::Indirect(def_ref)))
}
}
impl BlockBuilder<BlockBuilderVoxels> {
pub fn offset(mut self, offset: GridPoint) -> Self {
self.primitive_builder.offset = offset;
self
}
}
impl<C: BuildPrimitiveIndependent> From<BlockBuilder<C>> for Block {
fn from(builder: BlockBuilder<C>) -> Self {
builder.build()
}
}
impl From<Rgba> for BlockBuilder<Rgba> {
fn from(color: Rgba) -> Self {
Block::builder().color(color)
}
}
impl From<Rgb> for BlockBuilder<Rgba> {
fn from(color: Rgb) -> Self {
Block::builder().color(color.with_alpha_one())
}
}
#[allow(clippy::exhaustive_structs)]
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)]
pub struct NeedsPrimitive;
pub trait BuildPrimitiveIndependent {
fn build_i(self, attributes: BlockAttributes) -> Primitive;
}
pub trait BuildPrimitiveInUniverse {
fn build_u(self, attributes: BlockAttributes, universe: &mut Universe) -> Primitive;
}
impl<T: BuildPrimitiveIndependent> BuildPrimitiveInUniverse for T {
fn build_u(self, attributes: BlockAttributes, _: &mut Universe) -> Primitive {
self.build_i(attributes)
}
}
impl BuildPrimitiveIndependent for Rgba {
fn build_i(self, attributes: BlockAttributes) -> Primitive {
Primitive::Atom(attributes, self)
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct BlockBuilderVoxels {
space: URef<Space>,
resolution: Resolution,
offset: GridPoint,
}
impl BuildPrimitiveIndependent for BlockBuilderVoxels {
fn build_i(self, attributes: BlockAttributes) -> Primitive {
Primitive::Recur {
attributes,
offset: self.offset,
resolution: self.resolution,
space: self.space,
}
}
}
#[cfg(test)]
mod tests {
use crate::block::{Resolution::*, AIR};
use crate::math::{Face6, GridAab};
use crate::space::SpacePhysics;
use super::*;
#[test]
fn defaults() {
let color = Rgba::new(0.1, 0.2, 0.3, 0.4);
assert_eq!(
Block::builder().color(color).build(),
Block::from_primitive(Primitive::Atom(BlockAttributes::default(), color)),
);
}
#[test]
fn default_equivalent() {
assert_eq!(
BlockBuilder::<NeedsPrimitive>::new(),
<BlockBuilder<NeedsPrimitive> as Default>::default()
);
}
#[test]
fn every_field_nondefault() {
let color = Rgba::new(0.1, 0.2, 0.3, 0.4);
let light_emission = Rgb::new(0.1, 3.0, 0.1);
let rotation_rule = RotationPlacementRule::Attach { by: Face6::NZ };
let tick_action = Some(VoxelBrush::single(AIR));
assert_eq!(
Block::builder()
.color(color)
.display_name("hello world")
.collision(BlockCollision::Recur)
.rotation_rule(rotation_rule)
.selectable(false)
.light_emission(light_emission)
.tick_action(tick_action.clone())
.animation_hint(AnimationHint::TEMPORARY)
.build(),
Block::from_primitive(Primitive::Atom(
BlockAttributes {
display_name: "hello world".into(),
collision: BlockCollision::Recur,
rotation_rule,
selectable: false,
light_emission,
tick_action,
animation_hint: AnimationHint::TEMPORARY,
},
color
)),
);
}
#[test]
fn voxels_from_space() {
let mut universe = Universe::new();
let space_ref = universe.insert_anonymous(Space::empty_positive(1, 1, 1));
assert_eq!(
Block::builder()
.display_name("hello world")
.voxels_ref(R2, space_ref.clone())
.build(),
Block::from_primitive(Primitive::Recur {
attributes: BlockAttributes {
display_name: "hello world".into(),
..BlockAttributes::default()
},
offset: GridPoint::origin(),
resolution: R2, space: space_ref
}),
);
}
#[test]
fn voxels_from_fn() {
let mut universe = Universe::new();
let resolution = R8;
let block = Block::builder()
.display_name("hello world")
.voxels_fn(&mut universe, resolution, |_cube| &AIR)
.unwrap()
.build();
let space_ref = if let Primitive::Recur { space, .. } = block.primitive() {
space.clone()
} else {
panic!("expected Recur, found {block:?}");
};
assert_eq!(
block,
Block::from_primitive(Primitive::Recur {
attributes: BlockAttributes {
display_name: "hello world".into(),
..BlockAttributes::default()
},
offset: GridPoint::origin(),
resolution,
space: space_ref.clone()
}),
);
assert_eq!(
space_ref.read().unwrap().bounds(),
GridAab::for_block(resolution)
);
assert_eq!(
space_ref.read().unwrap().physics(),
&SpacePhysics::DEFAULT_FOR_BLOCK
);
}
}